From 92e436108efe82136a684900564be592ccbd3078 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 1 Aug 2016 17:11:42 -0400 Subject: [PATCH 1/6] moving to rsa-compat for rsa crypto --- lib/accounts.js | 50 ++++++++++++++++++++++++++++++------------------- lib/core.js | 49 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/lib/accounts.js b/lib/accounts.js index bff6e2e..6751292 100644 --- a/lib/accounts.js +++ b/lib/accounts.js @@ -1,8 +1,9 @@ 'use strict'; var PromiseA = require('bluebird'); +var crypto = require('crypto'); var LeCore = require('letiny-core'); -var leCrypto = LeCore.leCrypto; +var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA); var path = require('path'); var mkdirpAsync = PromiseA.promisify(require('mkdirp')); var fs = PromiseA.promisifyAll(require('fs')); @@ -13,8 +14,8 @@ function createAccount(args, handlers) { // TODO support ECDSA // arg.rsaBitLength args.rsaExponent - return leCrypto.generateRsaKeypairAsync(args.rsaKeySize, 65537).then(function (pems) { - /* pems = { privateKeyPem, privateKeyJwk, publicKeyPem, publicKeyMd5, publicKeySha256 } */ + return RSA.generateKeypairAsync(args.rsaKeySize || 1024, 65537, { public: true, pem: true }).then(function (keypair) { + /* pems = { privateKeyPem, privateKeyJwk, publicKeyPem } */ return LeCore.registerNewAccountAsync({ email: args.email @@ -24,12 +25,16 @@ function createAccount(args, handlers) { args.tosUrl = tosUrl; handlers.agreeToTerms(args, agree); } - , accountPrivateKeyPem: pems.privateKeyPem + , accountPrivateKeyPem: RSA.exportPrivatePem(keypair) + , accountKeypair: keypair , debug: args.debug || handlers.debug }).then(function (body) { - // TODO XXX use sha256 - var accountId = pems.publicKeyMd5; + // 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 accountDir = path.join(args.accountsDir, accountId); var regr = { body: body }; @@ -44,11 +49,12 @@ function createAccount(args, handlers) { , creation_dt: isoDate }; + // TODO abstract file writing return PromiseA.all([ // meta.json {"creation_host": "ns1.redirect-www.org", "creation_dt": "2015-12-11T04:14:38Z"} fs.writeFileAsync(path.join(accountDir, 'meta.json'), JSON.stringify(accountMeta), 'utf8') // private_key.json { "e", "d", "n", "q", "p", "kty", "qi", "dp", "dq" } - , fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(pems.privateKeyJwk), 'utf8') + , fs.writeFileAsync(path.join(accountDir, 'private_key.json'), JSON.stringify(RSA.exportPrivateJwk(keypair)), 'utf8') // regr.json: /* { body: { contact: [ 'mailto:coolaj86@gmail.com' ], @@ -60,8 +66,10 @@ function createAccount(args, handlers) { */ , fs.writeFileAsync(path.join(accountDir, 'regr.json'), JSON.stringify(regr), 'utf8') ]).then(function () { + var pems = {}; + pems.meta = accountMeta; - pems.privateKey = pems.privateKeyJwk; + pems.privateKey = RSA.exportPrivateJwk(keypair); pems.regr = regr; pems.accountId = accountId; pems.id = accountId; @@ -106,18 +114,22 @@ function getAccount(args, handlers) { return createAccount(args, handlers); } - return leCrypto.privateJwkToPemsAsync(files.private_key).then(function (keypair) { - files.accountId = accountId; // preserve current account id - files.id = accountId; - files.publicKeySha256 = keypair.publicKeySha256; - files.publicKeyMd5 = keypair.publicKeyMd5; - files.publicKeyPem = keypair.publicKeyPem; // ascii PEM: ----BEGIN... - files.privateKeyPem = keypair.privateKeyPem; // ascii PEM: ----BEGIN... - files.privateKeyJson = keypair.privateKeyJwk; // json { n: ..., e: ..., iq: ..., etc } - files.privateKeyJwk = keypair.privateKeyJwk; // json { n: ..., e: ..., iq: ..., etc } + var keypair = { privateKeyJwk: files.private_key }; + keypair.privateKeyPem = RSA.exportPrivatePem(keypair); + keypair.publicKeyPem = RSA.exportPublicPem(keypair); + keypair.publicKeyMd5 = crypto.createHash('md5').update(keypair.publicKeyPem).digest('hex'); + keypair.publicKeySha256 = crypto.createHash('sha256').update(keypair.publicKeyPem).digest('hex'); - return files; - }); + files.accountId = accountId; // preserve current account id + files.id = accountId; + files.privateKeyJwk = keypair.privateKeyJwk; // json { n: ..., e: ..., iq: ..., etc } + //files.privateKeyJson = keypair.privateKeyJwk; // json { n: ..., e: ..., iq: ..., etc } + files.privateKeyPem = keypair.privateKeyPem; // ascii PEM: ----BEGIN... + files.publicKeyPem = keypair.publicKeyPem; // ascii PEM: ----BEGIN... + files.publicKeyMd5 = keypair.publicKeyMd5; + files.publicKeySha256 = keypair.publicKeySha256; + + return files; }); } diff --git a/lib/core.js b/lib/core.js index 0799cf4..0001a6f 100644 --- a/lib/core.js +++ b/lib/core.js @@ -1,6 +1,7 @@ 'use strict'; var PromiseA = require('bluebird'); +var RSA = PromiseA.promisifyAll(require('rsa-compat').RSA); var mkdirpAsync = PromiseA.promisify(require('mkdirp')); var path = require('path'); var fs = PromiseA.promisifyAll(require('fs')); @@ -195,7 +196,12 @@ function writeCertificateAsync(args, defaults, handlers) { sfs.writeFileAsync(certArchive, result.cert, 'ascii') , sfs.writeFileAsync(chainArchive, result.ca || result.chain, 'ascii') , sfs.writeFileAsync(fullchainArchive, result.fullchain, 'ascii') - , sfs.writeFileAsync(privkeyArchive, result.key || result.privkey || args.domainPrivateKeyPem, 'ascii') + , sfs.writeFileAsync( + privkeyArchive + // TODO nix args.key, args.domainPrivateKeyPem ?? + , result.key || result.privkey || args.domainPrivateKeyPem || RSA.exportPrivateKey(args.domainKeypair) + , 'ascii' + ) ]); }).then(function () { return mkdirpAsync(liveDir); @@ -204,7 +210,12 @@ function writeCertificateAsync(args, defaults, handlers) { sfs.writeFileAsync(certPath, result.cert, 'ascii') , sfs.writeFileAsync(chainPath, result.ca || result.chain, 'ascii') , sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii') - , sfs.writeFileAsync(privkeyPath, result.key || result.privkey || args.domainPrivateKeyPem, 'ascii') + , sfs.writeFileAsync( + privkeyPath + // TODO nix args.key, args.domainPrivateKeyPem ?? + , result.key || result.privkey || args.domainPrivateKeyPem || RSA.exportPrivateKey(args.domainKeypair) + , 'ascii' + ) ]); }).then(function () { obj.checkpoints += 1; @@ -219,8 +230,9 @@ function writeCertificateAsync(args, defaults, handlers) { , fullchainPath: fullchainPath , privkeyPath: privkeyPath + // TODO nix args.key, args.domainPrivateKeyPem ?? // some ambiguity here... - , privkey: result.key || result.privkey || args.domainPrivateKeyPem + , privkey: result.key || result.privkey || args.domainPrivateKeyPem || RSA.exportPrivateKey(args.domainKeypair) , fullchain: result.fullchain || result.cert , chain: result.ca || result.chain // especially this one... might be cert only, might be fullchain @@ -235,21 +247,30 @@ function writeCertificateAsync(args, defaults, handlers) { function getCertificateAsync(args, defaults, handlers) { var account = args.account; var promise; + var keypairOpts = { public: true, pem: true }; - if (args.domainKeyPath) { - // TODO use existing pre-generated key if avaibale - console.warn("[LE /lib/core.js] retrieve from domainKeyPath NOT IMPLEMENTED (please file an issue to remind me about this)"); - promise = leCrypto.generateRsaKeypairAsync(args.rsaKeySize, 65537); - } else { - promise = leCrypto.generateRsaKeypairAsync(args.rsaKeySize, 65537); + if (!args.domainKeyPath) { + // TODO use default path ??? + promise = RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts); } - return promise.then(function (domainKey) { + if (args.domainKeyPath) { + promise = fs.readFileAsync(args.domainKeyPath, 'ascii').then(function (pem) { + return RSA.import({ privateKeyPem: pem }); + }, function (err) { + return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) { + return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii'); + }); + }); + } + + return promise.then(function (domainKeypair) { if (args.debug) { console.log("[letsencrypt/lib/core.js] get certificate"); } - args.domainPrivateKeyPem = args.domainPrivateKeyPem || domainKey.privateKeyPem; + args.domainKeypair = domainKeypair; + args.domainPrivateKeyPem = RSA.exportPrivateKeyPem(domainKeypair); //args.registration = domainKey; return LeCore.getCertificateAsync({ @@ -258,8 +279,10 @@ function getCertificateAsync(args, defaults, handlers) { , newAuthzUrl: args._acmeUrls.newAuthz , newCertUrl: args._acmeUrls.newCert - , accountPrivateKeyPem: account.privateKeyPem - , domainPrivateKeyPem: domainKey.privateKeyPem + , accountPrivateKeyPem: account.keypair || RSA.import({ privateKeyPem: account.privateKeyPem }) + , accountKeypair: RSA.import(account.keypair || { privateKeyPem: account.privateKeyPem }) + , domainPrivateKeyPem: RSA.exportPrivateKeyPem(domainKeypair) + , domainKeypair: domainKeypair , domains: args.domains // From 663ead5ec044cd81d405546b2fdec5d51c293d04 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 1 Aug 2016 21:49:54 -0400 Subject: [PATCH 2/6] cleanup and comments --- lib/accounts.js | 18 +++++++----------- lib/core.js | 26 ++++++++++++++------------ 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/accounts.js b/lib/accounts.js index 6751292..a4f3d3a 100644 --- a/lib/accounts.js +++ b/lib/accounts.js @@ -15,7 +15,7 @@ function createAccount(args, handlers) { // TODO support ECDSA // arg.rsaBitLength args.rsaExponent return RSA.generateKeypairAsync(args.rsaKeySize || 1024, 65537, { public: true, pem: true }).then(function (keypair) { - /* pems = { privateKeyPem, privateKeyJwk, publicKeyPem } */ + /* keypair = { privateKeyPem, privateKeyJwk, publicKeyPem } */ return LeCore.registerNewAccountAsync({ email: args.email @@ -25,7 +25,6 @@ function createAccount(args, handlers) { args.tosUrl = tosUrl; handlers.agreeToTerms(args, agree); } - , accountPrivateKeyPem: RSA.exportPrivatePem(keypair) , accountKeypair: keypair , debug: args.debug || handlers.debug @@ -68,8 +67,9 @@ function createAccount(args, handlers) { ]).then(function () { var pems = {}; + // pems.private_key; pems.meta = accountMeta; - pems.privateKey = RSA.exportPrivateJwk(keypair); + pems.keypair = keypair; pems.regr = regr; pems.accountId = accountId; pems.id = accountId; @@ -117,17 +117,13 @@ function getAccount(args, handlers) { var keypair = { privateKeyJwk: files.private_key }; keypair.privateKeyPem = RSA.exportPrivatePem(keypair); keypair.publicKeyPem = RSA.exportPublicPem(keypair); - keypair.publicKeyMd5 = crypto.createHash('md5').update(keypair.publicKeyPem).digest('hex'); - keypair.publicKeySha256 = crypto.createHash('sha256').update(keypair.publicKeyPem).digest('hex'); + //files.private_key; + //files.regr; + //files.meta; files.accountId = accountId; // preserve current account id files.id = accountId; - files.privateKeyJwk = keypair.privateKeyJwk; // json { n: ..., e: ..., iq: ..., etc } - //files.privateKeyJson = keypair.privateKeyJwk; // json { n: ..., e: ..., iq: ..., etc } - files.privateKeyPem = keypair.privateKeyPem; // ascii PEM: ----BEGIN... - files.publicKeyPem = keypair.publicKeyPem; // ascii PEM: ----BEGIN... - files.publicKeyMd5 = keypair.publicKeyMd5; - files.publicKeySha256 = keypair.publicKeySha256; + files.keypair = keypair; return files; }); diff --git a/lib/core.js b/lib/core.js index 0001a6f..b94852f 100644 --- a/lib/core.js +++ b/lib/core.js @@ -167,7 +167,7 @@ function writeCertificateAsync(args, defaults, handlers) { var obj = args.pyobj; var result = args.pems; - result.fullchain = result.cert + '\n' + result.ca; + result.fullchain = result.cert + '\n' + (result.chain || result.ca); obj.checkpoints = parseInt(obj.checkpoints, 10) || 0; var liveDir = args.liveDir || path.join(args.configDir, 'live', args.domains[0]); @@ -194,12 +194,12 @@ function writeCertificateAsync(args, defaults, handlers) { return mkdirpAsync(archiveDir).then(function () { return PromiseA.all([ sfs.writeFileAsync(certArchive, result.cert, 'ascii') - , sfs.writeFileAsync(chainArchive, result.ca || result.chain, 'ascii') + , sfs.writeFileAsync(chainArchive, (result.chain || result.ca), 'ascii') , sfs.writeFileAsync(fullchainArchive, result.fullchain, 'ascii') , sfs.writeFileAsync( privkeyArchive // TODO nix args.key, args.domainPrivateKeyPem ?? - , result.key || result.privkey || args.domainPrivateKeyPem || RSA.exportPrivateKey(args.domainKeypair) + , (result.privkey || result.key) || RSA.exportPrivateKey(args.domainKeypair) , 'ascii' ) ]); @@ -208,12 +208,12 @@ function writeCertificateAsync(args, defaults, handlers) { }).then(function () { return PromiseA.all([ sfs.writeFileAsync(certPath, result.cert, 'ascii') - , sfs.writeFileAsync(chainPath, result.ca || result.chain, 'ascii') + , sfs.writeFileAsync(chainPath, (result.chain || result.ca), 'ascii') , sfs.writeFileAsync(fullchainPath, result.fullchain, 'ascii') , sfs.writeFileAsync( privkeyPath // TODO nix args.key, args.domainPrivateKeyPem ?? - , result.key || result.privkey || args.domainPrivateKeyPem || RSA.exportPrivateKey(args.domainKeypair) + , (result.privkey || result.key) || RSA.exportPrivateKey(args.domainKeypair) , 'ascii' ) ]); @@ -230,11 +230,14 @@ function writeCertificateAsync(args, defaults, handlers) { , fullchainPath: fullchainPath , privkeyPath: privkeyPath + // TODO nix keypair + , keypair: args.domainKeypair + // TODO nix args.key, args.domainPrivateKeyPem ?? // some ambiguity here... - , privkey: result.key || result.privkey || args.domainPrivateKeyPem || RSA.exportPrivateKey(args.domainKeypair) - , fullchain: result.fullchain || result.cert - , chain: result.ca || result.chain + , privkey: (result.privkey || result.key) || RSA.exportPrivateKey(args.domainKeypair) + , fullchain: result.fullchain || (result.cert + '\n' + result.chain) + , chain: (result.chain || result.ca) // especially this one... might be cert only, might be fullchain , cert: result.cert @@ -270,7 +273,6 @@ function getCertificateAsync(args, defaults, handlers) { } args.domainKeypair = domainKeypair; - args.domainPrivateKeyPem = RSA.exportPrivateKeyPem(domainKeypair); //args.registration = domainKey; return LeCore.getCertificateAsync({ @@ -279,9 +281,7 @@ function getCertificateAsync(args, defaults, handlers) { , newAuthzUrl: args._acmeUrls.newAuthz , newCertUrl: args._acmeUrls.newCert - , accountPrivateKeyPem: account.keypair || RSA.import({ privateKeyPem: account.privateKeyPem }) - , accountKeypair: RSA.import(account.keypair || { privateKeyPem: account.privateKeyPem }) - , domainPrivateKeyPem: RSA.exportPrivateKeyPem(domainKeypair) + , accountKeypair: RSA.import(account.keypair) , domainKeypair: domainKeypair , domains: args.domains @@ -325,6 +325,7 @@ function getCertificateAsync(args, defaults, handlers) { } }); }).then(function (results) { + // { cert, chain, fullchain, privkey } args.pems = results; return writeCertificateAsync(args, defaults, handlers); }); @@ -358,6 +359,7 @@ function getOrCreateDomainCertificate(args, defaults, handlers) { }); } +// returns 'account' from lib/accounts { meta, regr, keypair, accountId (id) } function getOrCreateAcmeAccount(args, defaults, handlers) { var pyconf = PromiseA.promisifyAll(require('pyconf')); From 56e4a949ef6420ee1ac352425676b95f5fbc4ab4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 1 Aug 2016 22:21:49 -0400 Subject: [PATCH 3/6] cleanup --- lib/core.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/core.js b/lib/core.js index b94852f..4b3f720 100644 --- a/lib/core.js +++ b/lib/core.js @@ -8,7 +8,6 @@ var fs = PromiseA.promisifyAll(require('fs')); var sfs = require('safe-replace'); var LE = require('../'); var LeCore = PromiseA.promisifyAll(require('letiny-core')); -var leCrypto = PromiseA.promisifyAll(LeCore.leCrypto); var Accounts = require('./accounts'); var merge = require('./common').merge; From 2f36d31f73d04ce9f50373e510efa8f10887bfb4 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 3 Aug 2016 23:00:41 -0400 Subject: [PATCH 4/6] fix #27 use domainKeyPath, move to rsa-compat, use RSA.exportPrivatePem --- lib/accounts.js | 4 +--- lib/core.js | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/lib/accounts.js b/lib/accounts.js index a4f3d3a..7f8f235 100644 --- a/lib/accounts.js +++ b/lib/accounts.js @@ -12,10 +12,8 @@ function createAccount(args, handlers) { var os = require("os"); var localname = os.hostname(); - // TODO support ECDSA // arg.rsaBitLength args.rsaExponent - return RSA.generateKeypairAsync(args.rsaKeySize || 1024, 65537, { public: true, pem: true }).then(function (keypair) { - /* keypair = { privateKeyPem, privateKeyJwk, publicKeyPem } */ + return RSA.generateKeypairAsync(args.rsaKeySize || 2048, 65537, { public: true, pem: true }).then(function (keypair) { return LeCore.registerNewAccountAsync({ email: args.email diff --git a/lib/core.js b/lib/core.js index b94852f..aa2b4b5 100644 --- a/lib/core.js +++ b/lib/core.js @@ -8,7 +8,6 @@ var fs = PromiseA.promisifyAll(require('fs')); var sfs = require('safe-replace'); var LE = require('../'); var LeCore = PromiseA.promisifyAll(require('letiny-core')); -var leCrypto = PromiseA.promisifyAll(LeCore.leCrypto); var Accounts = require('./accounts'); var merge = require('./common').merge; @@ -199,7 +198,7 @@ function writeCertificateAsync(args, defaults, handlers) { , sfs.writeFileAsync( privkeyArchive // TODO nix args.key, args.domainPrivateKeyPem ?? - , (result.privkey || result.key) || RSA.exportPrivateKey(args.domainKeypair) + , (result.privkey || result.key) || RSA.exportPrivatePem(args.domainKeypair) , 'ascii' ) ]); @@ -213,7 +212,7 @@ function writeCertificateAsync(args, defaults, handlers) { , sfs.writeFileAsync( privkeyPath // TODO nix args.key, args.domainPrivateKeyPem ?? - , (result.privkey || result.key) || RSA.exportPrivateKey(args.domainKeypair) + , (result.privkey || result.key) || RSA.exportPrivatePem(args.domainKeypair) , 'ascii' ) ]); @@ -235,7 +234,7 @@ function writeCertificateAsync(args, defaults, handlers) { // TODO nix args.key, args.domainPrivateKeyPem ?? // some ambiguity here... - , privkey: (result.privkey || result.key) || RSA.exportPrivateKey(args.domainKeypair) + , privkey: (result.privkey || result.key) || RSA.exportPrivatePem(args.domainKeypair) , fullchain: result.fullchain || (result.cert + '\n' + result.chain) , chain: (result.chain || result.ca) // especially this one... might be cert only, might be fullchain @@ -254,15 +253,25 @@ function getCertificateAsync(args, defaults, handlers) { if (!args.domainKeyPath) { // TODO use default path ??? + if (args.debug) { + console.log('[domainKeyPath]: none'); + } promise = RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts); } if (args.domainKeyPath) { + if (args.debug) { + console.log('[domainKeyPath]:', args.domainKeyPath); + } promise = fs.readFileAsync(args.domainKeyPath, 'ascii').then(function (pem) { return RSA.import({ privateKeyPem: pem }); - }, function (err) { + }, function (/*err*/) { return RSA.generateKeypairAsync(args.rsaKeySize, 65537, keypairOpts).then(function (keypair) { - return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii'); + return mkdirpAsync(path.dirname(args.domainKeyPath)).then(function () { + return fs.writeFileAsync(args.domainKeyPath, keypair.privateKeyPem, 'ascii').then(function () { + return keypair; + }); + }); }); }); } From 3e7261cdc6e931549cc5fbaa9d3abb292f54c9f3 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 3 Aug 2016 23:12:13 -0400 Subject: [PATCH 5/6] complete move to letiny-core, rsa-compat --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cf71746..c976a5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "letsencrypt", - "version": "1.4.4", + "version": "1.5.0", "description": "Let's Encrypt for node.js on npm", "main": "index.js", "scripts": { @@ -37,10 +37,11 @@ "dependencies": { "bluebird": "^3.0.6", "homedir": "^0.6.0", - "letiny-core": "^1.0.5", + "letiny-core": "^2.0.1", "mkdirp": "^0.5.1", "pyconf": "^1.1.2", "request": "^2.67.0", + "rsa-compat": "^1.2.1", "safe-replace": "^1.0.2" } } From 56de26fcfd5147a9a5243dadbad37f42a7dc79b7 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Wed, 3 Aug 2016 23:13:40 -0400 Subject: [PATCH 6/6] v1.5 changelog --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a21caeb..2fcac9d 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,7 @@ the python client, but it's not necessary) Change History ============== +* v1.5.0 now using letiny-core v2.0.0 and rsa-compat * v1.4.x I can't remember... but it's better! * v1.1.0 Added letiny-core, removed node-letsencrypt-python * v1.0.2 Works with node-letsencrypt-python