moving to rsa-compat

This commit is contained in:
AJ ONeal 2016-08-01 05:53:50 -04:00
parent e0909ad0ca
commit 55707a417d
6 changed files with 118 additions and 148 deletions

View File

@ -11,7 +11,7 @@ module.exports.create = function (deps) {
var NOOP=function () {}; var NOOP=function () {};
var log=NOOP; var log=NOOP;
var request=require('request'); var request=require('request');
var generateSignature=deps.leCrypto.generateSignature; var generateSignature=deps.RSA.signJws;
function Acme(privateKey) { function Acme(privateKey) {
this.privateKey=privateKey; this.privateKey=privateKey;
@ -54,7 +54,7 @@ module.exports.create = function (deps) {
log('Using nonce: '+this.nonces[0]); log('Using nonce: '+this.nonces[0]);
payload=JSON.stringify(body, null, 2); payload=JSON.stringify(body, null, 2);
jws=generateSignature( jws=generateSignature(
this.privateKey, new Buffer(payload), this.nonces.shift() this.privateKey, new Buffer(payload), this.nonces.shift()
); );
signed=JSON.stringify(jws, null, 2); signed=JSON.stringify(jws, null, 2);

View File

@ -9,53 +9,13 @@
module.exports.create = function (deps) { module.exports.create = function (deps) {
var request=deps.request; var request=deps.request;
var toStandardB64 = deps.leUtils.toStandardB64; var toStandardB64 = deps.leUtils.toStandardB64;
var importPemPrivateKey = deps.leCrypto.importPemPrivateKey; //var importPemPrivateKey = deps.leCrypto.importPemPrivateKey;
var thumbprinter = deps.leCrypto.thumbprint; //var thumbprinter = deps.leCrypto.thumbprint;
var generateCsr = deps.leCrypto.generateCsr || deps.leCrypto.generateCSR; //var generateCsr = deps.leCrypto.generateCsr || deps.leCrypto.generateCSR;
var Acme = deps.Acme; var Acme = deps.Acme;
var RSA = deps.RSA;
function getCert(options, cb) { function getCert(options, cb) {
var NOOP = function () {};
var log = options.debug ? console.log : NOOP;
var state={
validatedDomains:[]
, validAuthorizationUrls:[]
, newAuthzUrl: options.newAuthzUrl
, newCertUrl: options.newCertUrl
};
if (!options.newAuthzUrl) {
return handleErr(new Error("options.newAuthzUrl must be the authorization url"));
}
if (!options.newCertUrl) {
return handleErr(new Error("options.newCertUrl must be the new certificate url"));
}
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) {}"));
}
if (!(options.domains && options.domains.length)) {
return handleErr(new Error("options.domains must be an array of domains such as ['example.com', 'www.example.com']"));
}
state.domains = options.domains.slice(0); // copy array
try {
state.accountKeyPem=options.accountPrivateKeyPem;
state.accountKeyPair=importPemPrivateKey(state.accountKeyPem);
state.acme=new Acme(state.accountKeyPair);
state.certPrivateKeyPem=options.domainPrivateKeyPem;
state.certPrivateKey=importPemPrivateKey(state.certPrivateKeyPem);
} catch(err) {
return handleErr(err, 'Failed to parse privateKey');
}
function bodyToError(res, body) { function bodyToError(res, body) {
var err; var err;
@ -80,7 +40,7 @@ module.exports.create = function (deps) {
if (Math.floor(res.statusCode / 100) !== 2) { if (Math.floor(res.statusCode / 100) !== 2) {
err = new Error("[Error] letiny-core: not 200 ok"); err = new Error("[Error] letiny-core: not 200 ok");
err.code = "E_STATUS_CODE"; err.code = "E_STATUS_CODE";
err.type = body.type err.type = body.type;
err.description = body; err.description = body;
err.detail = body.detail; err.detail = body.detail;
console.error("TODO: modules which depend on this module should expose this error properly but since some of them don't, I expose it here directly:"); console.error("TODO: modules which depend on this module should expose this error properly but since some of them don't, I expose it here directly:");
@ -101,8 +61,6 @@ module.exports.create = function (deps) {
return body; return body;
} }
nextDomain();
function nextDomain() { function nextDomain() {
if (state.domains.length > 0) { if (state.domains.length > 0) {
getChallenges(state.domains.shift()); getChallenges(state.domains.shift());
@ -130,44 +88,11 @@ module.exports.create = function (deps) {
} }
} }
getReadyToValidate(err, res, body) getReadyToValidate(err, res, body);
}); });
} }
function getReadyToValidate(err, res, body) { function getReadyToValidate(err, res, body) {
var links, authz, httpChallenges, challenge, thumbprint, keyAuthorization, challengePath;
if (err) {
return handleErr(err);
}
if (Math.floor(res.statusCode/100)!==2) {
return handleErr(null, 'Authorization request failed ('+res.statusCode+')');
}
links=Acme.parseLink(res.headers.link);
if (!links || !('next' in links)) {
return handleErr(err, 'Server didn\'t provide information to proceed (2)');
}
state.authorizationUrl=res.headers.location;
state.newCertUrl=links.next;
authz=body;
httpChallenges=authz.challenges.filter(function(x) {
return x.type==='http-01';
});
if (httpChallenges.length===0) {
return handleErr(null, 'Server didn\'t offer any challenge we can handle.');
}
challenge=httpChallenges[0];
thumbprint=thumbprinter(state.accountKeyPair.publicKey);
keyAuthorization=challenge.token+'.'+thumbprint;
state.responseUrl=challenge.uri;
options.setChallenge(state.domain, challenge.token, keyAuthorization, challengeDone);
function challengeDone(err) { function challengeDone(err) {
if (err) { if (err) {
@ -200,6 +125,41 @@ module.exports.create = function (deps) {
}); });
}); });
} }
var links, authz, httpChallenges, challenge, thumbprint, keyAuthorization;
if (err) {
return handleErr(err);
}
if (Math.floor(res.statusCode/100)!==2) {
return handleErr(null, 'Authorization request failed ('+res.statusCode+')');
}
links=Acme.parseLink(res.headers.link);
if (!links || !('next' in links)) {
return handleErr(err, 'Server didn\'t provide information to proceed (2)');
}
state.authorizationUrl=res.headers.location;
state.newCertUrl=links.next;
authz=body;
httpChallenges=authz.challenges.filter(function(x) {
return x.type==='http-01';
});
if (httpChallenges.length===0) {
return handleErr(null, 'Server didn\'t offer any challenge we can handle.');
}
challenge=httpChallenges[0];
thumbprint=RSA.thumbprint(state.accountKeyPair);
keyAuthorization=challenge.token+'.'+thumbprint;
state.responseUrl=challenge.uri;
options.setChallenge(state.domain, challenge.token, keyAuthorization, challengeDone);
} }
function ensureValidation(err, res, body, unlink) { function ensureValidation(err, res, body, unlink) {
@ -256,7 +216,7 @@ module.exports.create = function (deps) {
} }
function getCertificate() { function getCertificate() {
var csr=generateCsr(state.certPrivateKey, state.validatedDomains); var csr=RSA.generateCsrWeb64(state.certPrivateKey, state.validatedDomains);
log('Requesting certificate...'); log('Requesting certificate...');
state.acme.post(state.newCertUrl, { state.acme.post(state.newCertUrl, {
resource:'new-cert', resource:'new-cert',
@ -374,6 +334,50 @@ module.exports.create = function (deps) {
log(text, err, info); log(text, err, info);
cb(err || new Error(text)); cb(err || new Error(text));
} }
var NOOP = function () {};
var log = options.debug ? console.log : NOOP;
var state={
validatedDomains:[]
, validAuthorizationUrls:[]
, newAuthzUrl: options.newAuthzUrl
, newCertUrl: options.newCertUrl
};
if (!options.newAuthzUrl) {
return handleErr(new Error("options.newAuthzUrl must be the authorization url"));
}
if (!options.newCertUrl) {
return handleErr(new Error("options.newCertUrl must be the new certificate url"));
}
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) {}"));
}
if (!(options.domains && options.domains.length)) {
return handleErr(new Error("options.domains must be an array of domains such as ['example.com', 'www.example.com']"));
}
state.domains = options.domains.slice(0); // copy array
try {
state.accountKeyPem=options.accountPrivateKeyPem;
state.accountKeyPair=RSA.import({ privateKeyPem: state.accountKeyPem });
state.acme=new Acme(state.accountKeyPair);
state.certPrivateKeyPem=options.domainPrivateKeyPem;
state.certPrivateKey=RSA.import({ privateKeyPem: state.certPrivateKeyPem });
} catch(err) {
return handleErr(err, 'Failed to parse privateKey');
}
nextDomain();
} }
function certBufferToPem(cert) { function certBufferToPem(cert) {

View File

@ -6,43 +6,9 @@
'use strict'; 'use strict';
var request = require('request'); var request = require('request');
var RSA = require('rsa-compat').RSA;
var leUtils = require('./acme-util'); var leUtils = require('./acme-util');
var leCrypto = require('./letsencrypt-node-crypto');
var leExtra = require('./letsencrypt-forge-extra');
var leForge = require('./letsencrypt-forge');
var leUrsa;
try {
leUrsa = require('./letsencrypt-ursa');
} catch(e) {
leUrsa = {};
// things will run a little slower on keygen, but it'll work on windows
// (but don't try this on raspberry pi - 20+ MINUTES key generation)
}
// order of crypto precdence is
// * native
// * ursa
// * forge extra (the new one aimed to be less-forgey)
// * forge (fallback)
Object.keys(leUrsa).forEach(function (key) {
if (!leCrypto[key]) {
leCrypto[key] = leUrsa[key];
}
});
Object.keys(leExtra).forEach(function (key) {
if (!leCrypto[key]) {
leCrypto[key] = leExtra[key];
}
});
Object.keys(leForge).forEach(function (key) {
if (!leCrypto[key]) {
leCrypto[key] = leForge[key];
}
});
module.exports.request = request; module.exports.request = request;
module.exports.leCrypto = leCrypto;
module.exports.leUtils = leUtils; module.exports.leUtils = leUtils;
module.exports.RSA = RSA;

View File

@ -9,33 +9,10 @@
module.exports.create = function (deps) { module.exports.create = function (deps) {
var NOOP=function () {}, log=NOOP; var NOOP=function () {}, log=NOOP;
var request=deps.request; var request=deps.request;
var importPemPrivateKey=deps.leCrypto.importPemPrivateKey; var RSA = deps.RSA;
var Acme = deps.Acme; var Acme = deps.Acme;
function registerNewAccount(options, cb) { function registerNewAccount(options, cb) {
var state = {};
if (!options.accountPrivateKeyPem) {
return handleErr(new Error("options.accountPrivateKeyPem must be an ascii private key pem"));
}
if (!options.agreeToTerms) {
cb(new Error("options.agreeToTerms must be function (tosUrl, fn => (err, true))"));
return;
}
if (!options.newRegUrl) {
cb(new Error("options.newRegUrl must be the a new registration url"));
return;
}
if (!options.email) {
cb(new Error("options.email must be an email"));
return;
}
state.accountKeyPem=options.accountPrivateKeyPem;
state.accountKeyPair=importPemPrivateKey(state.accountKeyPem);
state.acme=new Acme(state.accountKeyPair);
register();
function register() { function register() {
state.acme.post(options.newRegUrl, { state.acme.post(options.newRegUrl, {
@ -123,6 +100,30 @@ module.exports.create = function (deps) {
log(text, err, info); log(text, err, info);
cb(err || new Error(text)); cb(err || new Error(text));
} }
var state = {};
if (!options.accountPrivateKeyPem) {
return handleErr(new Error("options.accountPrivateKeyPem must be an ascii private key pem"));
}
if (!options.agreeToTerms) {
cb(new Error("options.agreeToTerms must be function (tosUrl, fn => (err, true))"));
return;
}
if (!options.newRegUrl) {
cb(new Error("options.newRegUrl must be the a new registration url"));
return;
}
if (!options.email) {
cb(new Error("options.email must be an email"));
return;
}
state.accountKeyPem=options.accountPrivateKeyPem;
state.accountKeyPair=RSA.import({ privateKeyPem: state.accountKeyPem });
state.acme=new Acme(state.accountKeyPair);
register();
} }
return registerNewAccount; return registerNewAccount;

View File

@ -26,8 +26,6 @@ function create(deps) {
LeCore.registerNewAccount = require('./lib/register-new-account').create(deps); LeCore.registerNewAccount = require('./lib/register-new-account').create(deps);
LeCore.getCertificate = require('./lib/get-certificate').create(deps); LeCore.getCertificate = require('./lib/get-certificate').create(deps);
LeCore.leCrypto = deps.leCrypto;
return LeCore; return LeCore;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "letiny-core", "name": "letiny-core",
"version": "1.0.5", "version": "1.1.0",
"description": "A framework for building letsencrypt clients, forked from letiny", "description": "A framework for building letsencrypt clients, forked from letiny",
"main": "node.js", "main": "node.js",
"browser": "browser.js", "browser": "browser.js",
@ -30,7 +30,8 @@
], ],
"dependencies": { "dependencies": {
"node-forge": "^0.6.38", "node-forge": "^0.6.38",
"request": "^2.55.0" "request": "^2.55.0",
"rsa-compat": "^1.0.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"ursa": "^0.9.1" "ursa": "^0.9.1"