'use strict'; var certificates = module.exports; var store = certificates; var U = require('./utils.js'); var fs = require('fs'); var path = require('path'); var PromiseA = require('./promise.js'); var sfs = require('safe-replace'); var readFileAsync = PromiseA.promisify(fs.readFile); var writeFileAsync = PromiseA.promisify(fs.writeFile); var mkdirpAsync = PromiseA.promisify(require('@root/mkdirp')); // Certificates.check // // Use certificate.id, or subject, if id hasn't been set, to find a certificate. // Return an object with string PEMs for cert and chain (or null, not undefined) certificates.check = function(opts) { // { certificate.id, subject, ... } var id = (opts.certificate && opts.certificate.id) || opts.subject; //console.log('certificates.check for', opts); // For advanced use cases: // This just goes to show that any options set in approveDomains() will be available here // (the same is true for all of the hooks in this file) if (opts.exampleThrowError) { return Promise.reject(new Error('You want an error? You got it!')); } if (opts.exampleReturnNull) { return Promise.resolve(null); } if (opts.exampleReturnCerts) { return Promise.resolve(opts.exampleReturnCerts); } return Promise.all([ readFileAsync(U._tameWild(privkeyPath(store, opts), id), 'ascii'), // 0 // all other PEM types are just readFileAsync(U._tameWild(certPath(store, opts), id), 'ascii'), // 1 // some arrangement of these 3 readFileAsync(U._tameWild(chainPath(store, opts), id), 'ascii') // 2 // (bundle, combined, fullchain, etc) ]) .then(function(all) { //////////////////////// // PAY ATTENTION HERE // //////////////////////// // This is all you have to return: cert, chain return { cert: all[1], // string PEM. the bare cert, half of the concatonated fullchain.pem you need chain: all[2], // string PEM. the bare chain, the second half of the fullchain.pem privkey: all[0] // string PEM. optional, allows checkKeypair to be skipped // These can be useful to store in your database, // but otherwise they're easy to derive from the cert. // (when not available they'll be generated from cert-info) //, subject: certinfo.subject // string domain name //, altnames: certinfo.altnames // array of domain name strings //, issuedAt: certinfo.issuedAt // number in ms (a.k.a. NotBefore) //, expiresAt: certinfo.expiresAt // number in ms (a.k.a. NotAfter) }; }) .catch(function(err) { // Treat non-exceptional failures as null returns (not undefined) if ('ENOENT' === err.code) { return null; } throw err; // True exceptions should be thrown }); }; // Certificates.checkKeypair // // Use certificate.kid, certificate.id, or subject to find a certificate keypair // Return an object with string privateKeyPem and/or object privateKeyJwk (or null, not undefined) certificates.checkKeypair = function(opts) { //console.log('certificates.checkKeypair:', opts); return readFileAsync( U._tameWild(privkeyPath(store, opts), opts.subject), 'ascii' ) .then(function(key) { //////////////////////// // PAY ATTENTION HERE // //////////////////////// return { privateKeyPem: key // In this case we only saved privateKeyPem, so we only return it //privateKeyJwk: null // (but it's fine, just different encodings of the same thing) }; }) .catch(function(err) { if ('ENOENT' === err.code) { return null; } throw err; }); }; // Certificates.setKeypair({ certificate, subject, keypair, ... }): // // Use certificate.kid (or certificate.id or subject if no kid is present) to find a certificate keypair // Return null (not undefined) on success, or throw on error certificates.setKeypair = function(opts) { var keypair = opts.keypair || keypair; // Ignore. // Just specific implementation details. return mkdirpAsync( U._tameWild(path.dirname(privkeyPath(store, opts)), opts.subject) ).then(function() { // keypair is normally an opaque object, but here it's a PEM for the FS (for things like Apache and Nginx) return writeFileAsync( U._tameWild(privkeyPath(store, opts), opts.subject), keypair.privateKeyPem, 'ascii' ).then(function() { return null; }); }); }; // Certificates.set({ subject, pems, ... }): // // Use certificate.id (or subject if no ki is present) to save a certificate // Return null (not undefined) on success, or throw on error certificates.set = function(opts) { //console.log('certificates.set:', opts); var pems = { cert: opts.pems.cert, // string PEM the first half of the concatonated fullchain.pem cert chain: opts.pems.chain, // string PEM the second half (yes, you need this too) privkey: opts.pems.privkey // Ignore. string PEM, useful if you have to create bundle.pem }; // Ignore // Just implementation specific details (writing lots of combinatons of files) return mkdirpAsync(path.dirname(certPath(store, opts))) .then(function() { return mkdirpAsync( path.dirname(U._tameWild(chainPath(store, opts), opts.subject)) ).then(function() { return mkdirpAsync( path.dirname( U._tameWild(fullchainPath(store, opts), opts.subject) ) ).then(function() { return mkdirpAsync( path.dirname( U._tameWild(bundlePath(store, opts), opts.subject) ) ).then(function() { var fullchainPem = [ pems.cert.trim() + '\n', pems.chain.trim() + '\n' ].join('\n'); // for Apache, Nginx, etc var bundlePem = [ pems.privkey, pems.cert, pems.chain ].join('\n'); // for HAProxy return PromiseA.all([ sfs.writeFileAsync( U._tameWild( certPath(store, opts), opts.subject ), pems.cert, 'ascii' ), sfs.writeFileAsync( U._tameWild( chainPath(store, opts), opts.subject ), pems.chain, 'ascii' ), // Most web servers need these two sfs.writeFileAsync( U._tameWild( fullchainPath(store, opts), opts.subject ), fullchainPem, 'ascii' ), // HAProxy needs "bundle.pem" aka "combined.pem" sfs.writeFileAsync( U._tameWild( bundlePath(store, opts), opts.subject ), bundlePem, 'ascii' ) ]); }); }); }); }) .then(function() { // That's your job: return null return null; }); }; function liveDir(store, opts) { return opts.liveDir || path.join(opts.configDir, 'live', opts.subject); } function privkeyPath(store, opts) { var dir = U._tpl( store, opts, opts.serverKeyPath || opts.privkeyPath || opts.domainKeyPath || store.options.serverKeyPath || store.options.privkeyPath || store.options.domainKeyPath || path.join(liveDir(), 'privkey.pem') ); return U._tameWild(dir, opts.subject || ''); } function certPath(store, opts) { var pathname = opts.certPath || store.options.certPath || path.join(liveDir(), 'cert.pem'); var dir = U._tpl(store, opts, pathname); return U._tameWild(dir, opts.subject || ''); } function fullchainPath(store, opts) { var dir = U._tpl( store, opts, opts.fullchainPath || store.options.fullchainPath || path.join(liveDir(), 'fullchain.pem') ); return U._tameWild(dir, opts.subject || ''); } function chainPath(store, opts) { var dir = U._tpl( store, opts, opts.chainPath || store.options.chainPath || path.join(liveDir(), 'chain.pem') ); return U._tameWild(dir, opts.subject || ''); } function bundlePath(store, opts) { var dir = U._tpl( store, opts, opts.bundlePath || store.options.bundlePath || path.join(liveDir(), 'bundle.pem') ); return U._tameWild(dir, opts.subject || ''); }