266 lines
7.6 KiB
JavaScript
266 lines
7.6 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) {
|
|
// { 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 || '');
|
|
}
|