separating concerns

This commit is contained in:
AJ ONeal 2015-12-15 03:02:19 -08:00
parent 31b25af1cb
commit 89ef517338
3 changed files with 190 additions and 159 deletions

77
lib/account-old.js Normal file
View File

@ -0,0 +1,77 @@
options.newReg=options.newReg || 'https://acme-v01.api.letsencrypt.org/acme/new-reg';
if (!options.email) {
return cb(new Error('No "email" option specified!'));
}
if (typeof options.domains==='string') {
state.domains=options.domains.split(/[, ]+/);
} else if (options.domains && options.domains instanceof Array) {
state.domains=options.domains.slice();
} else {
return cb(new Error('No valid "domains" option specified!'));
}
if ((_DEBUG=options.debug)) {
if (!''.green) {
require('colors');
}
log=console.log.bind(console);
} else {
log=NOOP;
}
if (options.fork && !~process.argv.indexOf('--letiny-fork')) {
state.child=child.fork(__filename, ['--letiny-fork']);
if (options.challenge) {
return cb(new Error('fork+challenge not supported yet'));
}
state.child.send({request:options});
state.child.on('message', function(msg) {
var res;
if (msg.result) {
res=msg.result;
cb(res.err ? new Error(res.err) : null, res.cert, res.key, res.ca);
}
});
return;
}
if (options.accountKey) {
if (options.accountKey.length>255) {
state.accountKeyPEM=options.accountKey;
} else {
try {
state.accountKeyPEM=fs.readFileSync(options.accountKey);
} catch(err) {
if (err.code==='ENOENT') {
makeAccountKeyPair(true);
} else {
return handleErr(err, 'Failed to load accountKey');
}
}
try {
state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM);
} catch(err) {
return handleErr(err, 'Failed to parse accountKey');
}
initAcme();
}
} else {
makeAccountKeyPair();
}
function makeAccountKeyPair(save) {
var keypair;
log('Generating account keypair...');
keypair=pki.rsa.generateKeyPair(2048);
state.accountKeyPEM=pki.privateKeyToPem(keypair.privateKey);
state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM);
if (save) {
try {
fs.writeFileSync(options.accountKey, state.accountKeyPEM);
} catch(err) {
return handleErr(err, 'Failed to save accountKey');
}
}
initAcme();
}

View File

@ -103,114 +103,24 @@ Acme.prototype.post=function(url, body, cb) {
}; };
function getCert(options, cb) { function registerNewAccount(state, options, cb) {
var state={ if (!options.agreeToTerms) {
validatedDomains:[], cb(new Error("options.agreeToTerms must be function (tosUrl, fn => (err, true))"));
validAuthorizationURLs:[] return;
}; }
if (!options.newReg) {
options.newReg=options.newReg || 'https://acme-v01.api.letsencrypt.org/acme/new-reg'; cb(new Error("options.newReg must be the a new registration url"));
return;
}
if (!options.email) { if (!options.email) {
return cb(new Error('No "email" option specified!')); cb(new Error("options.email must be an email"));
}
if (typeof options.domains==='string') {
state.domains=options.domains.split(/[, ]+/);
} else if (options.domains && options.domains instanceof Array) {
state.domains=options.domains.slice();
} else {
return cb(new Error('No valid "domains" option specified!'));
}
if ((_DEBUG=options.debug)) {
if (!''.green) {
require('colors');
}
log=console.log.bind(console);
} else {
log=NOOP;
}
if (options.fork && !~process.argv.indexOf('--letiny-fork')) {
state.child=child.fork(__filename, ['--letiny-fork']);
if (options.challenge) {
return cb(new Error('fork+challenge not supported yet'));
}
state.child.send({request:options});
state.child.on('message', function(msg) {
var res;
if (msg.result) {
res=msg.result;
cb(res.err ? new Error(res.err) : null, res.cert, res.key, res.ca);
}
});
return; return;
} }
if (options.accountKey) {
if (options.accountKey.length>255) {
state.accountKeyPEM=options.accountKey;
} else {
try {
state.accountKeyPEM=fs.readFileSync(options.accountKey);
} catch(err) {
if (err.code==='ENOENT') {
makeAccountKeyPair(true);
} else {
return handleErr(err, 'Failed to load accountKey');
}
}
try {
state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM);
} catch(err) {
return handleErr(err, 'Failed to parse accountKey');
}
initAcme();
}
} else {
makeAccountKeyPair();
}
function makeAccountKeyPair(save) {
var keypair;
log('Generating account keypair...');
keypair=pki.rsa.generateKeyPair(2048);
state.accountKeyPEM=pki.privateKeyToPem(keypair.privateKey);
state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM);
if (save) {
try {
fs.writeFileSync(options.accountKey, state.accountKeyPEM);
} catch(err) {
return handleErr(err, 'Failed to save accountKey');
}
}
initAcme();
}
function initAcme() {
state.acme=new Acme(state.accountKeyPair);
makeKeyPair();
}
function makeKeyPair() {
var keypair;
if (options.privateKey) {
state.certPrivateKeyPEM=options.privateKey;
} else {
log('Generating cert keypair...');
keypair=pki.rsa.generateKeyPair(2048);
state.certPrivateKeyPEM=pki.privateKeyToPem(keypair.privateKey);
}
try {
state.certPrivateKey=cryptoUtil.importPemPrivateKey(state.certPrivateKeyPEM);
} catch(err) {
return handleErr(err, 'Failed to parse privateKey');
}
register(); register();
}
function register() { function register() {
post(options.newReg, { state.acme.post(options.newReg, {
resource:'new-reg', resource:'new-reg',
contact:['mailto:'+options.email] contact:['mailto:'+options.email]
}, getTerms); }, getTerms);
@ -233,15 +143,29 @@ function getCert(options, cb) {
state.termsRequired=('terms-of-service' in links); state.termsRequired=('terms-of-service' in links);
if (state.termsRequired) { if (state.termsRequired) {
state.termsURL=links['terms-of-service'];
options.agreeToTerms({
tosUrl: state.termsURL
, email: options.email
}, function (err, agree) {
if (err) {
return handleErr(err);
}
if (!agree) {
return handleErr(new Error("You must agree to the terms of use at '" + state.termsURL + "'"));
}
state.agreeTerms = agree;
state.termsURL=links['terms-of-service']; state.termsURL=links['terms-of-service'];
log(state.termsURL); log(state.termsURL);
request.get(state.termsURL, getAgreement); request.get(state.termsURL, getAgreement);
});
} else { } else {
getChallenges(); cb();
} }
} }
function getAgreement(err, res, body) { function getAgreement(err/*, res, body*/) {
if (err) { if (err) {
return handleErr(err, 'Couldn\'t get agreement'); return handleErr(err, 'Couldn\'t get agreement');
} }
@ -250,24 +174,61 @@ function getCert(options, cb) {
} }
function sendAgreement() { function sendAgreement() {
if (state.termsRequired && !options.agreeTerms) { if (state.termsRequired && !state.agreeTerms) {
return handleErr(null, 'The CA requires your agreement to terms: '+state.termsURL); return handleErr(null, 'The CA requires your agreement to terms: '+state.termsURL);
} }
log('Posting agreement to: '+state.registrationURL); log('Posting agreement to: '+state.registrationURL);
post(state.registrationURL, { state.acme.post(state.registrationURL, {
resource:'reg', resource:'reg',
agreement:state.termsURL agreement:state.termsURL
}, function(err, res, body) { }, function(err, res, body) {
if (err || Math.floor(res.statusCode/100)!==2) { if (err || Math.floor(res.statusCode/100)!==2) {
return handleErr(err, 'Couldn\'t POST agreement back to server', body); return handleErr(err, 'Couldn\'t POST agreement back to server', body);
} else { } else {
nextDomain(); cb(null, body);
} }
}); });
} }
function handleErr(err, text, info) {
log(text, err, info);
cb(err || new Error(text));
}
}
function getCert(options, cb) {
var state={
validatedDomains:[],
validAuthorizationURLs:[]
};
if (!options.accountPrivateKeyPem) {
return handleErr(new Error("options.accountPrivateKeyPem must be an ascii private key pem"));
}
if (!options.domainPrivateKeyPem) {
return handleErr(new Error("options.domainPrivateKeyPem must be an ascii private key pem"));
}
if (!options.setChallenge) {
return handleErr(new Error("options.setChallenge must be function(hostname, challengeKey, tokenValue, done) {}"));
}
if (!options.removeChallenge) {
return handleErr(new Error("options.removeChallenge must be function(hostname, challengeKey, done) {}"));
}
try {
state.accountKeyPem=options.accountPrivateKeyPem;
state.accountKeyPair=cryptoUtil.importPemPrivateKey(state.accountKeyPEM);
state.acme=new Acme(state.accountKeyPair);
state.certPrivateKeyPEM=options.domainPrivateKeyPem;
state.certPrivateKey=cryptoUtil.importPemPrivateKey(state.certPrivateKeyPEM);
} catch(err) {
return handleErr(err, 'Failed to parse privateKey');
}
nextDomain();
function nextDomain() { function nextDomain() {
if (state.domains.length > 0) { if (state.domains.length > 0) {
getChallenges(state.domains.shift()); getChallenges(state.domains.shift());
@ -280,7 +241,7 @@ function getCert(options, cb) {
function getChallenges(domain) { function getChallenges(domain) {
state.domain=domain; state.domain=domain;
post(state.newAuthorizationURL, { state.acme.post(state.newAuthorizationURL, {
resource:'new-authz', resource:'new-authz',
identifier:{ identifier:{
type:'dns', type:'dns',
@ -320,29 +281,17 @@ function getCert(options, cb) {
state.responseURL=challenge['uri']; state.responseURL=challenge['uri'];
state.path=challengePath; state.path=challengePath;
if (options.webroot) { options.setChallenge(state.domain, '/'+challengePath, keyAuthorization, challengeDone);
try {
mkdirp(path.dirname(options.webroot+'/'+challengePath));
fs.writeFileSync(path.normalize(options.webroot+'/'+challengePath), keyAuthorization);
challengeDone();
} catch(err) {
handleErr(err, 'Could not write challange file to disk');
}
} else if (typeof options.challenge==='function') {
options.challenge(state.domain, '/'+challengePath, keyAuthorization, challengeDone);
} else {
return handleErr(null, 'No "challenge" function or "webroot" option given.');
}
function challengeDone() { function challengeDone() {
post(state.responseURL, { state.acme.post(state.responseURL, {
resource:'challenge', resource:'challenge',
keyAuthorization:keyAuthorization keyAuthorization:keyAuthorization
}, function(err, res, body) { }, function(err, res, body) {
ensureValidation(err, res, body, function unlink() { ensureValidation(err, res, body, function unlink() {
if (options.webroot) { options.removeChallenge(state.domain, '/'+challengePath, function () {
fs.unlinkSync(path.normalize(options.webroot+'/'+challengePath)); // ignore
} });
}); });
}); });
} }
@ -382,7 +331,7 @@ function getCert(options, cb) {
function getCertificate() { function getCertificate() {
var csr=cryptoUtil.generateCSR(state.certPrivateKey, state.validatedDomains); var csr=cryptoUtil.generateCSR(state.certPrivateKey, state.validatedDomains);
log('Requesting certificate...'); log('Requesting certificate...');
post(state.newCertificateURL, { state.acme.post(state.newCertificateURL, {
resource:'new-cert', resource:'new-cert',
csr:csr, csr:csr,
authorizations:state.validAuthorizationURLs authorizations:state.validAuthorizationURLs
@ -440,40 +389,22 @@ function getCert(options, cb) {
} }
function done() { function done() {
var cert, pfx; var cert;
try { try {
cert=certBufferToPEM(state.certificate); cert=certBufferToPEM(state.certificate);
if (options.certFile) { } catch(e) {
fs.writeFileSync(options.certFile, cert); console.error(e.stack);
} //cb(new Error("Could not write output files. Please check permissions!"));
if (options.keyFile) { handleErr(e, 'Could not write output files. Please check permissions!');
fs.writeFileSync(options.keyFile, state.certPrivateKeyPEM); return;
}
if (options.caFile) {
fs.writeFileSync(options.caFile, state.caCert);
}
if (options.pfxFile) {
try {
pfx=forge.pkcs12.toPkcs12Asn1(
pki.privateKeyFromPem(state.certPrivateKeyPEM),
[pki.certificateFromPem(cert), pki.certificateFromPem(state.caCert)],
options.pfxPassword || '',
options.aes ? {} : {algorithm:'3des'}
);
pfx=new Buffer(forge.asn1.toDer(pfx).toHex(), 'hex');
} catch(err) {
handleErr(err, 'Could not convert to PKCS#12');
}
fs.writeFileSync(options.pfxFile, pfx);
}
cb(null, cert, state.certPrivateKeyPEM, state.caCert);
} catch(err) {
handleErr(err, 'Could not write output files. Please check permissions!');
}
} }
function post(url, body, cb) { cb(null, {
return state.acme.post(url, body, cb); cert: cert
, key: state.certPrivateKeyPEM
, ca: state.caCert
});
} }
function handleErr(err, text, info) { function handleErr(err, text, info) {
@ -534,5 +465,5 @@ if (~process.argv.indexOf('--letiny-fork')) {
}); });
} }
exports.registerNewAccount=registerNewAccount;
exports.getCert=getCert; exports.getCert=getCert;

23
lib/write-old.js Normal file
View File

@ -0,0 +1,23 @@
if (options.certFile) {
fs.writeFileSync(options.certFile, cert);
}
if (options.keyFile) {
fs.writeFileSync(options.keyFile, state.certPrivateKeyPEM);
}
if (options.caFile) {
fs.writeFileSync(options.caFile, state.caCert);
}
if (options.pfxFile) {
try {
pfx=forge.pkcs12.toPkcs12Asn1(
pki.privateKeyFromPem(state.certPrivateKeyPEM),
[pki.certificateFromPem(cert), pki.certificateFromPem(state.caCert)],
options.pfxPassword || '',
options.aes ? {} : {algorithm:'3des'}
);
pfx=new Buffer(forge.asn1.toDer(pfx).toHex(), 'hex');
} catch(err) {
handleErr(err, 'Could not convert to PKCS#12');
}
fs.writeFileSync(options.pfxFile, pfx);
}