just a little further...

This commit is contained in:
AJ ONeal 2016-08-06 02:05:04 -04:00
parent 16bfac31bb
commit d1c0043f34
1 changed files with 216 additions and 210 deletions

View File

@ -7,32 +7,55 @@ 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;
} }
function createAccount(args, handlers) { return LeCore.getAcmeUrlsAsync(args.server).then(function (data) {
le._ipc.acmeUrlsUpdatedAt = Date.now();
le._ipc.acmeUrls = data;
return le._ipc.acmeUrls;
});
}
//
// The Main Enchilada
//
//
// Accounts
//
, accounts: {
registerAsync: function (args) {
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, { public: true, pem: true }).then(function (keypair) { return RSA.generateKeypairAsync(args.rsaKeySize, 65537, { public: true, pem: true }).then(function (keypair) {
return LeCore.registerNewAccountAsync({ return LeCore.registerNewAccountAsync({
email: args.email email: args.email
, newRegUrl: args._acmeUrls.newReg , newRegUrl: args._acmeUrls.newReg
, agreeToTerms: function (tosUrl, agree) { , agreeToTerms: function (tosUrl, agreeCb) {
if (true === args.agreeTos || tosUrl === args.agreeTos || tosUrl === le.agreeToTerms) {
agreeCb(null, tosUrl);
return;
}
// args.email = email; // already there // args.email = email; // already there
// args.domains = domains // already there
args.tosUrl = tosUrl; args.tosUrl = tosUrl;
handlers.agreeToTerms(args, agree); le.agreeToTerms(args, agreeCb);
} }
, accountKeypair: keypair , accountKeypair: keypair
, debug: defaults.debug || args.debug || handlers.debug , debug: le.debug || args.debug
}).then(function (body) { }).then(function (body) {
// TODO XXX use sha256 (the python client uses md5) // TODO XXX use sha256 (the python client uses md5)
// TODO ssh fingerprint (noted on rsa-compat issues page, I believe) // TODO ssh fingerprint (noted on rsa-compat issues page, I believe)
@ -52,48 +75,63 @@ module.exports.create = function (le) {
args.account = account; args.account = account;
return backend.setAccountAsync(args, account).then(function () { return le.store.accounts.setAsync(args, account).then(function () {
return account; return account;
}); });
}); });
}); });
} }
// getOrCreateAcmeAccount
function getAcmeUrls(args) { , getAsync: function (args) {
var now = Date.now(); return core.accounts.checkAsync(args).then(function (account) {
if (account) {
// TODO check response header on request for cache time return le.store.accounts.checkAccount(args);
if ((now - le._ipc.acmeUrlsUpdatedAt) < 10 * 60 * 1000) { } else {
return PromiseA.resolve(le._ipc.acmeUrls); return core.accounts.registerAsync(args);
} }
return LeCore.getAcmeUrlsAsync(args.server).then(function (data) {
le._ipc.acmeUrlsUpdatedAt = Date.now();
le._ipc.acmeUrls = data;
return le._ipc.acmeUrls;
}); });
} }
, checkAsync: function (args) {
return le.store.accounts.checkAccountId(args).then(function (accountId) {
function getCertificateAsync(args, defaults, handlers) { if (!accountId) {
return null;
}
args.accountId = accountId;
// Note: the ACME urls are always fetched fresh on purpose
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() { function log() {
if (args.debug || defaults.debug) { if (args.debug || le.debug) {
console.log.apply(console, arguments); console.log.apply(console, arguments);
} }
} }
var account = args.account; var account = args.account;
var promise;
var keypairOpts = { public: true, pem: true }; var keypairOpts = { public: true, pem: true };
promise = backend.getPrivatePem(args).then(function (pem) { var promise = le.store.certificates.checkKeypairAsync(args).then(function (keypair) {
return RSA.import({ privateKeyPem: pem }); return RSA.import(keypair);
}, function (/*err*/) { }, function (/*err*/) {
return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) { return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) {
keypair.privateKeyPem = RSA.exportPrivatePem(keypair); keypair.privateKeyPem = RSA.exportPrivatePem(keypair);
keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair); keypair.privateKeyJwk = RSA.exportPrivateJwk(keypair);
return backend.setPrivatePem(args, keypair); return le.store.certificates.setKeypairAsync(args, keypair);
}); });
}); });
@ -104,7 +142,7 @@ module.exports.create = function (le) {
//args.registration = domainKey; //args.registration = domainKey;
return LeCore.getCertificateAsync({ return LeCore.getCertificateAsync({
debug: args.debug debug: args.debug || le.debug
, newAuthzUrl: args._acmeUrls.newAuthz , newAuthzUrl: args._acmeUrls.newAuthz
, newCertUrl: args._acmeUrls.newCert , newCertUrl: args._acmeUrls.newCert
@ -123,94 +161,80 @@ module.exports.create = function (le) {
// (args is per-request, defaults is per instance) // (args is per-request, defaults is per instance)
// //
, setChallenge: function (domain, key, value, done) { , setChallenge: function (domain, key, value, done) {
var copy = handlers.merge({ domains: [domain] }, defaults, backendDefaults); var copy = utils.merge({ domains: [domain] }, le);
handlers.tplCopy(copy); utils.tplCopy(copy);
//args.domains = [domain]; //args.domains = [domain];
args.domains = args.domains || [domain]; args.domains = args.domains || [domain];
if (5 !== handlers.setChallenge.length) { if (5 !== le.challenger.set.length) {
done(new Error("handlers.setChallenge receives the wrong number of arguments." done(new Error("le.challenger.set receives the wrong number of arguments."
+ " You must define setChallenge as function (opts, domain, key, val, cb) { }")); + " You must define setChallenge as function (opts, domain, key, val, cb) { }"));
return; return;
} }
handlers.setChallenge(copy, domain, key, value, done); le.challenger.set(copy, domain, key, value, done);
} }
, removeChallenge: function (domain, key, done) { , removeChallenge: function (domain, key, done) {
var copy = handlers.merge({ domains: [domain] }, defaults, backendDefaults); var copy = utils.merge({ domains: [domain] }, le);
handlers.tplCopy(copy); utils.tplCopy(copy);
if (4 !== handlers.removeChallenge.length) { if (4 !== le.challenger.remove.length) {
done(new Error("handlers.removeChallenge receives the wrong number of arguments." done(new Error("le.challenger.remove receives the wrong number of arguments."
+ " You must define removeChallenge as function (opts, domain, key, cb) { }")); + " You must define removeChallenge as function (opts, domain, key, cb) { }"));
return; return;
} }
handlers.removeChallenge(copy, domain, key, done); le.challenger.remove(copy, domain, key, done);
} }
}).then(attachCertInfo); }).then(utils.attachCertInfo);
}).then(function (results) { }).then(function (results) {
// { cert, chain, fullchain, privkey } // { cert, chain, fullchain, privkey }
args.pems = results; args.pems = results;
return backend.setRegistration(args, defaults, handlers); 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);
function getOrCreateDomainCertificate(args, defaults, handlers) {
if (args.duplicate) { if (args.duplicate) {
// we're forcing a refresh via 'dupliate: true' // we're forcing a refresh via 'dupliate: true'
return getCertificateAsync(args, defaults, handlers); return core.certificates.registerAsync(args);
} }
return wrapped.fetchAsync(args).then(function (certs) { return core.certificates.checkAsync(args).then(function (certs) {
var halfLife = (certs.expiresAt - certs.issuedAt) / 2; var renewableAt = certs.expiresAt - le.renewWithin;
//var halfLife = (certs.expiresAt - certs.issuedAt) / 2;
//var renewable = (Date.now() - certs.issuedAt) > halfLife;
if (!certs || (Date.now() - certs.issuedAt) > halfLife) { if (!certs || Date.now() >= renewableAt) {
// There is no cert available // There is no cert available
// Or the cert is more than half-expired // Or the cert is more than half-expired
return getCertificateAsync(args, defaults, handlers); return core.certificates.registerAsync(args);
} }
return PromiseA.reject(new Error( return PromiseA.reject(new Error(
"[ERROR] Certificate issued at '" "[ERROR] Certificate issued at '"
+ new Date(certs.issuedAt).toISOString() + "' and expires at '" + new Date(certs.issuedAt).toISOString() + "' and expires at '"
+ new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '" + new Date(certs.expiresAt).toISOString() + "'. Ignoring renewal attempt until half-life at '"
+ new Date(certs.issuedA + halfLife).toISOString() + "'. Set { duplicate: true } to force." + new Date(renewableAt).toISOString() + "'. Set { duplicate: true } to force."
)); ));
}); });
} }
}
// returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) } // returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) }
function getOrCreateAcmeAccount(args, defaults, handlers) { , registerAsync: function (args) {
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;
return backend.getAccount(args, handlers);
} else {
log('[le/core.js] create account');
return createAccount(args, handlers);
}
});
});
}
var wrapped = {
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;
}; };