greenlock-store-fs.js/certificates.js

266 lines
9.7 KiB
JavaScript

"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) {
// { directoryUrl, subject, certificate.id, ... }
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 || "");
}