just a little further...
This commit is contained in:
parent
16bfac31bb
commit
d1c0043f34
426
lib/core.js
426
lib/core.js
|
@ -7,210 +7,234 @@ module.exports.create = function (le) {
|
||||||
var LeCore = PromiseA.promisifyAll(require('letiny-core'));
|
var LeCore = PromiseA.promisifyAll(require('letiny-core'));
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
|
|
||||||
function attachCertInfo(results) {
|
var core = {
|
||||||
var getCertInfo = require('./cert-info').getCertInfo;
|
//
|
||||||
// XXX Note: Parsing the certificate info comes at a great cost (~500kb)
|
// Helpers
|
||||||
var certInfo = getCertInfo(results.cert);
|
//
|
||||||
|
getAcmeUrlsAsync: function (args) {
|
||||||
|
var now = Date.now();
|
||||||
|
|
||||||
//results.issuedAt = arr[3].mtime.valueOf()
|
// TODO check response header on request for cache time
|
||||||
results.issuedAt = Date(certInfo.notBefore.value).valueOf(); // Date.now()
|
if ((now - le._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
|
||||||
results.expiresAt = Date(certInfo.notAfter.value).valueOf();
|
return PromiseA.resolve(le._ipc.acmeUrls);
|
||||||
|
}
|
||||||
|
|
||||||
return results;
|
return LeCore.getAcmeUrlsAsync(args.server).then(function (data) {
|
||||||
}
|
le._ipc.acmeUrlsUpdatedAt = Date.now();
|
||||||
|
le._ipc.acmeUrls = data;
|
||||||
|
|
||||||
function createAccount(args, handlers) {
|
return le._ipc.acmeUrls;
|
||||||
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, { public: true, pem: true }).then(function (keypair) {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return LeCore.registerNewAccountAsync({
|
|
||||||
email: args.email
|
|
||||||
, newRegUrl: args._acmeUrls.newReg
|
|
||||||
, agreeToTerms: function (tosUrl, agree) {
|
|
||||||
// args.email = email; // already there
|
|
||||||
args.tosUrl = tosUrl;
|
|
||||||
handlers.agreeToTerms(args, agree);
|
|
||||||
}
|
|
||||||
, accountKeypair: keypair
|
|
||||||
|
|
||||||
, debug: defaults.debug || args.debug || handlers.debug
|
//
|
||||||
}).then(function (body) {
|
// The Main Enchilada
|
||||||
// TODO XXX use sha256 (the python client uses md5)
|
//
|
||||||
// TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
|
|
||||||
keypair.publicKeyMd5 = crypto.createHash('md5').update(RSA.exportPublicPem(keypair)).digest('hex');
|
|
||||||
keypair.publicKeySha256 = crypto.createHash('sha256').update(RSA.exportPublicPem(keypair)).digest('hex');
|
|
||||||
|
|
||||||
var accountId = keypair.publicKeyMd5;
|
//
|
||||||
var regr = { body: body };
|
// Accounts
|
||||||
var account = {};
|
//
|
||||||
|
, accounts: {
|
||||||
|
registerAsync: function (args) {
|
||||||
|
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, { public: true, pem: true }).then(function (keypair) {
|
||||||
|
|
||||||
args.accountId = accountId;
|
return LeCore.registerNewAccountAsync({
|
||||||
|
email: args.email
|
||||||
|
, newRegUrl: args._acmeUrls.newReg
|
||||||
|
, agreeToTerms: function (tosUrl, agreeCb) {
|
||||||
|
if (true === args.agreeTos || tosUrl === args.agreeTos || tosUrl === le.agreeToTerms) {
|
||||||
|
agreeCb(null, tosUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
account.keypair = keypair;
|
// args.email = email; // already there
|
||||||
account.regr = regr;
|
// args.domains = domains // already there
|
||||||
account.accountId = accountId;
|
args.tosUrl = tosUrl;
|
||||||
account.id = accountId;
|
le.agreeToTerms(args, agreeCb);
|
||||||
|
}
|
||||||
|
, accountKeypair: keypair
|
||||||
|
|
||||||
args.account = account;
|
, debug: le.debug || args.debug
|
||||||
|
}).then(function (body) {
|
||||||
|
// TODO XXX use sha256 (the python client uses md5)
|
||||||
|
// TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
|
||||||
|
keypair.publicKeyMd5 = crypto.createHash('md5').update(RSA.exportPublicPem(keypair)).digest('hex');
|
||||||
|
keypair.publicKeySha256 = crypto.createHash('sha256').update(RSA.exportPublicPem(keypair)).digest('hex');
|
||||||
|
|
||||||
return backend.setAccountAsync(args, account).then(function () {
|
var accountId = keypair.publicKeyMd5;
|
||||||
return account;
|
var regr = { body: body };
|
||||||
|
var account = {};
|
||||||
|
|
||||||
|
args.accountId = accountId;
|
||||||
|
|
||||||
|
account.keypair = keypair;
|
||||||
|
account.regr = regr;
|
||||||
|
account.accountId = accountId;
|
||||||
|
account.id = accountId;
|
||||||
|
|
||||||
|
args.account = account;
|
||||||
|
|
||||||
|
return le.store.accounts.setAsync(args, account).then(function () {
|
||||||
|
return account;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAcmeUrls(args) {
|
|
||||||
var now = Date.now();
|
|
||||||
|
|
||||||
// TODO check response header on request for cache time
|
|
||||||
if ((now - le._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) {
|
|
||||||
return PromiseA.resolve(le._ipc.acmeUrls);
|
|
||||||
}
|
|
||||||
|
|
||||||
return LeCore.getAcmeUrlsAsync(args.server).then(function (data) {
|
|
||||||
le._ipc.acmeUrlsUpdatedAt = Date.now();
|
|
||||||
le._ipc.acmeUrls = data;
|
|
||||||
|
|
||||||
return le._ipc.acmeUrls;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCertificateAsync(args, defaults, handlers) {
|
|
||||||
|
|
||||||
function log() {
|
|
||||||
if (args.debug || defaults.debug) {
|
|
||||||
console.log.apply(console, arguments);
|
|
||||||
}
|
}
|
||||||
}
|
// getOrCreateAcmeAccount
|
||||||
|
, getAsync: function (args) {
|
||||||
|
return core.accounts.checkAsync(args).then(function (account) {
|
||||||
|
if (account) {
|
||||||
|
return le.store.accounts.checkAccount(args);
|
||||||
|
} else {
|
||||||
|
return core.accounts.registerAsync(args);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, checkAsync: function (args) {
|
||||||
|
return le.store.accounts.checkAccountId(args).then(function (accountId) {
|
||||||
|
|
||||||
var account = args.account;
|
if (!accountId) {
|
||||||
var promise;
|
return null;
|
||||||
var keypairOpts = { public: true, pem: true };
|
|
||||||
|
|
||||||
promise = backend.getPrivatePem(args).then(function (pem) {
|
|
||||||
return RSA.import({ privateKeyPem: pem });
|
|
||||||
}, function (/*err*/) {
|
|
||||||
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) {
|
|
||||||
keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
|
|
||||||
keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
|
|
||||||
return backend.setPrivatePem(args, keypair);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise.then(function (domainKeypair) {
|
|
||||||
log("[le/core.js] get certificate");
|
|
||||||
|
|
||||||
args.domainKeypair = domainKeypair;
|
|
||||||
//args.registration = domainKey;
|
|
||||||
|
|
||||||
return LeCore.getCertificateAsync({
|
|
||||||
debug: args.debug
|
|
||||||
|
|
||||||
, newAuthzUrl: args._acmeUrls.newAuthz
|
|
||||||
, newCertUrl: args._acmeUrls.newCert
|
|
||||||
|
|
||||||
, accountKeypair: RSA.import(account.keypair)
|
|
||||||
, domainKeypair: domainKeypair
|
|
||||||
, domains: args.domains
|
|
||||||
, challengeType: args.challengeType
|
|
||||||
|
|
||||||
//
|
|
||||||
// IMPORTANT
|
|
||||||
//
|
|
||||||
// setChallenge and removeChallenge are handed defaults
|
|
||||||
// instead of args because getChallenge does not have
|
|
||||||
// access to args
|
|
||||||
// (args is per-request, defaults is per instance)
|
|
||||||
//
|
|
||||||
, setChallenge: function (domain, key, value, done) {
|
|
||||||
var copy = handlers.merge({ domains: [domain] }, defaults, backendDefaults);
|
|
||||||
handlers.tplCopy(copy);
|
|
||||||
|
|
||||||
//args.domains = [domain];
|
|
||||||
args.domains = args.domains || [domain];
|
|
||||||
|
|
||||||
if (5 !== handlers.setChallenge.length) {
|
|
||||||
done(new Error("handlers.setChallenge receives the wrong number of arguments."
|
|
||||||
+ " You must define setChallenge as function (opts, domain, key, val, cb) { }"));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers.setChallenge(copy, domain, key, value, done);
|
|
||||||
}
|
|
||||||
, removeChallenge: function (domain, key, done) {
|
|
||||||
var copy = handlers.merge({ domains: [domain] }, defaults, backendDefaults);
|
|
||||||
handlers.tplCopy(copy);
|
|
||||||
|
|
||||||
if (4 !== handlers.removeChallenge.length) {
|
|
||||||
done(new Error("handlers.removeChallenge receives the wrong number of arguments."
|
|
||||||
+ " You must define removeChallenge as function (opts, domain, key, cb) { }"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handlers.removeChallenge(copy, domain, key, done);
|
|
||||||
}
|
|
||||||
}).then(attachCertInfo);
|
|
||||||
}).then(function (results) {
|
|
||||||
// { cert, chain, fullchain, privkey }
|
|
||||||
|
|
||||||
args.pems = results;
|
|
||||||
return backend.setRegistration(args, defaults, handlers);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOrCreateDomainCertificate(args, defaults, handlers) {
|
|
||||||
if (args.duplicate) {
|
|
||||||
// we're forcing a refresh via 'dupliate: true'
|
|
||||||
return getCertificateAsync(args, defaults, handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapped.fetchAsync(args).then(function (certs) {
|
|
||||||
var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
|
|
||||||
|
|
||||||
if (!certs || (Date.now() - certs.issuedAt) > halfLife) {
|
|
||||||
// There is no cert available
|
|
||||||
// Or the cert is more than half-expired
|
|
||||||
return getCertificateAsync(args, defaults, handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PromiseA.reject(new Error(
|
|
||||||
"[ERROR] Certificate issued at '"
|
|
||||||
+ new Date(certs.issuedAt).toISOString() + "' and expires at '"
|
|
||||||
+ new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '"
|
|
||||||
+ new Date(certs.issuedA + halfLife).toISOString() + "'. Set { duplicate: true } to force."
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
|
|
||||||
function getOrCreateAcmeAccount(args, defaults, handlers) {
|
|
||||||
function log() {
|
|
||||||
if (args.debug) {
|
|
||||||
console.log.apply(console, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return backend.getAccountId(args).then(function (accountId) {
|
|
||||||
|
|
||||||
// Note: the ACME urls are always fetched fresh on purpose
|
|
||||||
return getAcmeUrls(args).then(function (urls) {
|
|
||||||
args._acmeUrls = urls;
|
|
||||||
|
|
||||||
if (accountId) {
|
|
||||||
log('[le/core.js] use account');
|
|
||||||
|
|
||||||
args.accountId = accountId;
|
args.accountId = accountId;
|
||||||
return backend.getAccount(args, handlers);
|
|
||||||
} else {
|
|
||||||
log('[le/core.js] create account');
|
|
||||||
return createAccount(args, handlers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var wrapped = {
|
// Note: the ACME urls are always fetched fresh on purpose
|
||||||
registerAsync: function (args) {
|
return core.getAcmeUrlsAsync(args).then(function (urls) {
|
||||||
|
args._acmeUrls = urls;
|
||||||
|
|
||||||
|
|
||||||
|
// return le.store.accounts.checkAccountId(args).then(function (accountId) {
|
||||||
|
return le.store.accounts.checkAsync(args);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
, certificates: {
|
||||||
|
// getCertificateAsync:
|
||||||
|
registerAsync: function (args) {
|
||||||
|
|
||||||
|
function log() {
|
||||||
|
if (args.debug || le.debug) {
|
||||||
|
console.log.apply(console, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var account = args.account;
|
||||||
|
var keypairOpts = { public: true, pem: true };
|
||||||
|
|
||||||
|
var promise = le.store.certificates.checkKeypairAsync(args).then(function (keypair) {
|
||||||
|
return RSA.import(keypair);
|
||||||
|
}, function (/*err*/) {
|
||||||
|
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) {
|
||||||
|
keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
|
||||||
|
keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
|
||||||
|
return le.store.certificates.setKeypairAsync(args, keypair);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.then(function (domainKeypair) {
|
||||||
|
log("[le/core.js] get certificate");
|
||||||
|
|
||||||
|
args.domainKeypair = domainKeypair;
|
||||||
|
//args.registration = domainKey;
|
||||||
|
|
||||||
|
return LeCore.getCertificateAsync({
|
||||||
|
debug: args.debug || le.debug
|
||||||
|
|
||||||
|
, newAuthzUrl: args._acmeUrls.newAuthz
|
||||||
|
, newCertUrl: args._acmeUrls.newCert
|
||||||
|
|
||||||
|
, accountKeypair: RSA.import(account.keypair)
|
||||||
|
, domainKeypair: domainKeypair
|
||||||
|
, domains: args.domains
|
||||||
|
, challengeType: args.challengeType
|
||||||
|
|
||||||
|
//
|
||||||
|
// IMPORTANT
|
||||||
|
//
|
||||||
|
// setChallenge and removeChallenge are handed defaults
|
||||||
|
// instead of args because getChallenge does not have
|
||||||
|
// access to args
|
||||||
|
// (args is per-request, defaults is per instance)
|
||||||
|
//
|
||||||
|
, setChallenge: function (domain, key, value, done) {
|
||||||
|
var copy = utils.merge({ domains: [domain] }, le);
|
||||||
|
utils.tplCopy(copy);
|
||||||
|
|
||||||
|
//args.domains = [domain];
|
||||||
|
args.domains = args.domains || [domain];
|
||||||
|
|
||||||
|
if (5 !== le.challenger.set.length) {
|
||||||
|
done(new Error("le.challenger.set receives the wrong number of arguments."
|
||||||
|
+ " You must define setChallenge as function (opts, domain, key, val, cb) { }"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
le.challenger.set(copy, domain, key, value, done);
|
||||||
|
}
|
||||||
|
, removeChallenge: function (domain, key, done) {
|
||||||
|
var copy = utils.merge({ domains: [domain] }, le);
|
||||||
|
utils.tplCopy(copy);
|
||||||
|
|
||||||
|
if (4 !== le.challenger.remove.length) {
|
||||||
|
done(new Error("le.challenger.remove receives the wrong number of arguments."
|
||||||
|
+ " You must define removeChallenge as function (opts, domain, key, cb) { }"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
le.challenger.remove(copy, domain, key, done);
|
||||||
|
}
|
||||||
|
}).then(utils.attachCertInfo);
|
||||||
|
}).then(function (results) {
|
||||||
|
// { cert, chain, fullchain, privkey }
|
||||||
|
|
||||||
|
args.pems = results;
|
||||||
|
return le.store.certificates.setAsync(args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// checkAsync
|
||||||
|
, checkAsync: function (args) {
|
||||||
|
var copy = utils.merge(args, le);
|
||||||
|
utils.tplCopy(copy);
|
||||||
|
|
||||||
|
return le.store.certificates.checkAsync(copy).then(utils.attachCertInfo);
|
||||||
|
}
|
||||||
|
// getOrCreateDomainCertificate
|
||||||
|
, getAsync: function (args) {
|
||||||
|
var copy = utils.merge(args, le);
|
||||||
|
utils.tplCopy(copy);
|
||||||
|
|
||||||
|
if (args.duplicate) {
|
||||||
|
// we're forcing a refresh via 'dupliate: true'
|
||||||
|
return core.certificates.registerAsync(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return core.certificates.checkAsync(args).then(function (certs) {
|
||||||
|
var renewableAt = certs.expiresAt - le.renewWithin;
|
||||||
|
//var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
|
||||||
|
//var renewable = (Date.now() - certs.issuedAt) > halfLife;
|
||||||
|
|
||||||
|
if (!certs || Date.now() >= renewableAt) {
|
||||||
|
// There is no cert available
|
||||||
|
// Or the cert is more than half-expired
|
||||||
|
return core.certificates.registerAsync(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PromiseA.reject(new Error(
|
||||||
|
"[ERROR] Certificate issued at '"
|
||||||
|
+ new Date(certs.issuedAt).toISOString() + "' and expires at '"
|
||||||
|
+ new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '"
|
||||||
|
+ new Date(renewableAt).toISOString() + "'. Set { duplicate: true } to force."
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
|
||||||
|
, registerAsync: function (args) {
|
||||||
var err;
|
var err;
|
||||||
|
|
||||||
if (!Array.isArray(args.domains)) {
|
if (!Array.isArray(args.domains)) {
|
||||||
|
@ -226,16 +250,16 @@ module.exports.create = function (le) {
|
||||||
return PromiseA.reject(err);
|
return PromiseA.reject(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var copy = handlers.merge(args, defaults, backendDefaults);
|
var copy = utils.merge(args, le);
|
||||||
handlers.tplCopy(copy);
|
utils.tplCopy(copy);
|
||||||
|
|
||||||
return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) {
|
return core.accounts.getAsync(copy).then(function (account) {
|
||||||
copy.account = account;
|
copy.account = account;
|
||||||
|
|
||||||
return backend.getOrCreateRenewal(copy).then(function (pyobj) {
|
return backend.getOrCreateRenewal(copy).then(function (pyobj) {
|
||||||
|
|
||||||
copy.pyobj = pyobj;
|
copy.pyobj = pyobj;
|
||||||
return getOrCreateDomainCertificate(copy, defaults, handlers);
|
return core.certificates.getAsync(copy);
|
||||||
});
|
});
|
||||||
}).then(function (result) {
|
}).then(function (result) {
|
||||||
return result;
|
return result;
|
||||||
|
@ -243,25 +267,7 @@ module.exports.create = function (le) {
|
||||||
return PromiseA.reject(err);
|
return PromiseA.reject(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
, getOrCreateAccount: function (args) {
|
|
||||||
return createAccount(args, handlers);
|
|
||||||
}
|
|
||||||
, configureAsync: function (hargs) {
|
|
||||||
var copy = handlers.merge(hargs, defaults, backendDefaults);
|
|
||||||
handlers.tplCopy(copy);
|
|
||||||
|
|
||||||
return getOrCreateAcmeAccount(copy, defaults, handlers).then(function (account) {
|
|
||||||
copy.account = account;
|
|
||||||
return backend.getOrCreateRenewal(copy);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
, fetchAsync: function (args) {
|
|
||||||
var copy = handlers.merge(args, defaults);
|
|
||||||
handlers.tplCopy(copy);
|
|
||||||
|
|
||||||
return backend.fetchAsync(copy).then(attachCertInfo);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return wrapped;
|
return core;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue