"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 || ""); }