diff --git a/README.md b/README.md index 5baebda..f6f645e 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ LeCore.getCertificate(options, cb) // returns (err, pems={ key, cert, , setChallenge: fn (hostname, key, val, cb) , removeChallenge: fn (hostname, key, cb) } - + // Discovery URLs LeCore.getAcmeUrls(acmeDiscoveryUrl, cb) // returns (err, acmeUrls={newReg,newAuthz,newCert,revokeCert}) ``` @@ -138,7 +138,7 @@ For testing and development, you can also inject the dependencies you want to us ```javascript LeCore = LeCore.create({ request: require('request') -, leCrypto: rquire('./lib/letsencrypt-forge') +, RSA: rquire('rsa-compat').RSA }); // now uses node `request` (could also use jQuery or Angular in the browser) diff --git a/lib/letsencrypt-forge-extra.js b/lib/letsencrypt-forge-extra.js deleted file mode 100644 index b865e94..0000000 --- a/lib/letsencrypt-forge-extra.js +++ /dev/null @@ -1,122 +0,0 @@ -/*! - * letiny-core - * Copyright(c) 2015 AJ ONeal https://daplie.com - * Apache-2.0 OR MIT (and hence also MPL 2.0) -*/ -'use strict'; - -var crypto = require('crypto'); -var forge = require('node-forge'); - -function binstrToB64(binstr) { - return new Buffer(binstr, 'binary').toString('base64'); -} - -function b64ToBinstr(b64) { - return new Buffer(b64, 'base64').toString('binary'); -} - -function privatePemToJwk(forgePrivkey) { - //var forgePrivkey = forge.pki.privateKeyFromPem(privkeyPem); - - // required in node.js 4.2.2 (but not io.js 1.6.3) - Object.keys(forgePrivkey).forEach(function (k) { - var val = forgePrivkey[k]; - if (val && val.toByteArray) { - forgePrivkey[k] = val.toByteArray(); - } - }); - - return { - kty: "RSA" - , n: binstrToB64(forgePrivkey.n) - , e: binstrToB64(forgePrivkey.e) - , d: binstrToB64(forgePrivkey.d) - , p: binstrToB64(forgePrivkey.p) - , q: binstrToB64(forgePrivkey.q) - , dp: binstrToB64(forgePrivkey.dP) - , dq: binstrToB64(forgePrivkey.dQ) - , qi: binstrToB64(forgePrivkey.qInv) - }; -} - -function toForgePrivateKey(forgePrivkey) { - return forge.pki.rsa.setPrivateKey( - b64ToBinstr(forgePrivkey.n) - , b64ToBinstr(forgePrivkey.e) - , b64ToBinstr(forgePrivkey.d) - , b64ToBinstr(forgePrivkey.p) - , b64ToBinstr(forgePrivkey.q) - , b64ToBinstr(forgePrivkey.dp) - , b64ToBinstr(forgePrivkey.dq) - , b64ToBinstr(forgePrivkey.qi) - ); -} - -// WARNING: with forge this takes 20+ minutes on a Raspberry Pi!!! -// It takes SEVERAL seconds even on a nice macbook pro -function generateRsaKeypair(bitlen, exp, cb) { - var pki = forge.pki; - var keypair = pki.rsa.generateKeyPair({ bits: bitlen || 2048, e: exp || 65537 }); - var pems = { - publicKeyPem: pki.publicKeyToPem(keypair.publicKey) // ascii PEM: ----BEGIN... - , privateKeyPem: pki.privateKeyToPem(keypair.privateKey) // ascii PEM: ----BEGIN... - }; - - // for account id - pems.publicKeySha256 = crypto.createHash('sha256').update(pems.publicKeyPem).digest('hex'); - // for compat with python client account id - pems.publicKeyMd5 = crypto.createHash('md5').update(pems.publicKeyPem).digest('hex'); - // json { n: ..., e: ..., iq: ..., etc } - pems.privateKeyJwk = privatePemToJwk(keypair.privateKey); - // deprecate - pems.privateKeyJson = pems.privateKeyJwk; - - // TODO thumbprint - - cb(null, pems); -} - -function privateJwkToPems(pkj, cb) { - var pki = forge.pki; - - Object.keys(pkj).forEach(function (key) { - pkj[key] = new Buffer(pkj[key], 'base64'); - }); - - var priv; - var pubPem; - - try { - priv = toForgePrivateKey( - pkj.n // modulus - , pkj.e // exponent - , pkj.p - , pkj.q - , pkj.dp - , pkj.dq - , pkj.qi - , pkj.d - ); - } catch(e) { - cb(e); - return; - } - - pubPem = pki.publicKeyToPem(priv.publicKey); - cb(null, { - publicKeyPem: pubPem // ascii PEM: ----BEGIN... - , privateKeyPem: pki.privateKeyToPem(priv.privateKey) // ascii PEM: ----BEGIN... - // json { n: ..., e: ..., iq: ..., etc } - , privateKeyJwt: pkj - // deprecate - , privateKeyJson: pkj - // I would have chosen sha1 or sha2... but whatever - , publicKeyMd5: crypto.createHash('md5').update(pubPem).digest('hex') - , publicKeySha256: crypto.createHash('sha256').update(pubPem).digest('hex') - }); -} - -module.exports.generateRsaKeypair = generateRsaKeypair; -module.exports.privateJwkToPems = privateJwkToPems; -module.exports.privatePemToJwk = privatePemToJwk; diff --git a/lib/letsencrypt-forge.js b/lib/letsencrypt-forge.js deleted file mode 100644 index 122867e..0000000 --- a/lib/letsencrypt-forge.js +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright 2014 ISRG. All rights reserved -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. -'use strict'; - -var crypto = require("crypto"); -var tls = require("tls"); -var forge = require("node-forge"); -var util = require("./acme-util.js"); - -var TOKEN_SIZE = 16; -//var NONCE_SIZE = 16; - -function bytesToBuffer(bytes) { - return new Buffer(forge.util.bytesToHex(bytes), "hex"); -} - -function bufferToBytes(buf) { - return forge.util.hexToBytes(buf.toString("hex")); -} - -function bytesToBase64(bytes) { - return util.b64enc(bytesToBuffer(bytes)); -} - -function base64ToBytes(base64) { - return bufferToBytes(util.b64dec(base64)); -} - -function bnToBase64(bn) { - var hex = bn.toString(16); - if (hex.length % 2 === 1) { hex = "0" + hex; } - return util.b64enc(new Buffer(hex, "hex")); -} - -function base64ToBn(base64) { - return new forge.jsbn.BigInteger(util.b64dec(base64).toString("hex"), 16); -} - -function importPrivateKey(privateKey) { - return forge.pki.rsa.setPrivateKey( - base64ToBn(privateKey.n), - base64ToBn(privateKey.e), base64ToBn(privateKey.d), - base64ToBn(privateKey.p), base64ToBn(privateKey.q), - base64ToBn(privateKey.dp),base64ToBn(privateKey.dq), - base64ToBn(privateKey.qi)); -} - -function importPublicKey(publicKey) { - return forge.pki.rsa.setPublicKey( - base64ToBn(publicKey.n), - base64ToBn(publicKey.e)); -} - -function exportPrivateKey(privateKey) { - return { - "kty": "RSA", - "n": bnToBase64(privateKey.n), - "e": bnToBase64(privateKey.e), - "d": bnToBase64(privateKey.d), - "p": bnToBase64(privateKey.p), - "q": bnToBase64(privateKey.q), - "dp": bnToBase64(privateKey.dP), - "dq": bnToBase64(privateKey.dQ), - "qi": bnToBase64(privateKey.qInv) - }; -} - -function exportPublicKey(publicKey) { - return { - "kty": "RSA", - "n": bnToBase64(publicKey.n), - "e": bnToBase64(publicKey.e) - }; -} - -// A note on formats: -// * Keys are always represented as JWKs -// * Signature objects are in ACME format -// * Certs and CSRs are base64-encoded -module.exports = { - ///// RANDOM STRINGS - - randomString: function(nBytes) { - return bytesToBase64(forge.random.getBytesSync(nBytes)); - }, - - randomSerialNumber: function() { - return forge.util.bytesToHex(forge.random.getBytesSync(4)); - }, - - newToken: function() { - return this.randomString(TOKEN_SIZE); - }, - - ///// SHA-256 - - sha256: function(buf) { - return crypto.createHash('sha256').update(buf).digest('hex'); - }, - - ///// KEY PAIR MANAGEMENT - - generateKeyPair: function(bits) { - var keyPair = forge.pki.rsa.generateKeyPair({bits: bits, e: 0x10001}); - return { - privateKey: exportPrivateKey(keyPair.privateKey), - publicKey: exportPublicKey(keyPair.publicKey) - }; - }, - - importPemPrivateKey: function(pem) { - var key = forge.pki.privateKeyFromPem(pem); - return { - privateKey: exportPrivateKey(key), - publicKey: exportPublicKey(key) - }; - }, - - importPemCertificate: function(pem) { - return forge.pki.certificateFromPem(pem); - }, - - privateKeyToPem: function(privateKey) { - var priv = importPrivateKey(privateKey); - return forge.pki.privateKeyToPem(priv); - }, - - certificateToPem: function(certificate) { - var derCert = base64ToBytes(certificate); - var cert = forge.pki.certificateFromAsn1(forge.asn1.fromDer(derCert)); - return forge.pki.certificateToPem(cert); - }, - - certificateRequestToPem: function(csr) { - var derReq = base64ToBytes(csr); - var c = forge.pki.certificateFromAsn1(forge.asn1.fromDer(derReq)); - return forge.pki.certificateRequestToPem(c); - }, - - thumbprint: function(publicKey) { - // Only handling RSA keys - var input = bytesToBuffer('{"e":"'+ publicKey.e + '","kty":"RSA","n":"'+ publicKey.n +'"}'); - return util.b64enc(crypto.createHash('sha256').update(input).digest()); - }, - - ///// SIGNATURE GENERATION / VERIFICATION - - generateSignature: function(keyPair, payload, nonce) { - var privateKey = importPrivateKey(keyPair.privateKey); - - // Compute JWS signature - var protectedHeader = ""; - if (nonce) { - protectedHeader = JSON.stringify({nonce: nonce}); - } - var protected64 = util.b64enc(new Buffer(protectedHeader)); - var payload64 = util.b64enc(payload); - var signatureInputBuf = new Buffer(protected64 + "." + payload64); - var signatureInput = bufferToBytes(signatureInputBuf); - var md = forge.md.sha256.create(); - md.update(signatureInput); - var sig = privateKey.sign(md); - - return { - header: { - alg: "RS256", - jwk: keyPair.publicKey, - }, - protected: protected64, - payload: payload64, - signature: util.b64enc(bytesToBuffer(sig)), - }; - }, - - verifySignature: function(jws) { - var key; - - if (jws.protected) { - if (!jws.header) { - jws.header = {}; - } - - try { - console.log(jws.protected); - var protectedJSON = util.b64dec(jws.protected).toString(); - console.log(protectedJSON); - var protectedObj = JSON.parse(protectedJSON); - for (key in protectedObj) { - jws.header[key] = protectedObj[key]; - } - } catch (e) { - console.log("error unmarshaling json: "+e); - return false; - } - } - - // Assumes validSignature(sig) - if (!jws.header.jwk || (jws.header.jwk.kty !== "RSA")) { - // Unsupported key type - console.log("Unsupported key type"); - return false; - } else if (!jws.header.alg || !jws.header.alg.match(/^RS/)) { - // Unsupported algorithm - console.log("Unsupported alg: "+jws.header.alg); - return false; - } - - // Compute signature input - var protected64 = (jws.protected)? jws.protected : ""; - var payload64 = (jws.payload)? jws.payload : ""; - var signatureInputBuf = new Buffer(protected64 + "." + payload64); - var signatureInput = bufferToBytes(signatureInputBuf); - - // Compute message digest - var md; - switch (jws.header.alg) { - case "RS1": md = forge.md.sha1.create(); break; - case "RS256": md = forge.md.sha256.create(); break; - case "RS384": md = forge.md.sha384.create(); break; - case "RS512": md = forge.md.sha512.create(); break; - default: return false; // Unsupported algorithm - } - md.update(signatureInput); - - // Import the key and signature - var publicKey = importPublicKey(jws.header.jwk); - var sig = bufferToBytes(util.b64dec(jws.signature)); - - return publicKey.verify(md.digest().bytes(), sig); - }, - - ///// CSR GENERATION / VERIFICATION - - generateCSR: function(keyPair, names) { - var privateKey = importPrivateKey(keyPair.privateKey); - var publicKey = importPublicKey(keyPair.publicKey); - - // Create and sign the CSR - var csr = forge.pki.createCertificationRequest(); - csr.publicKey = publicKey; - csr.setSubject([{ name: 'commonName', value: names[0] }]); - - var sans = []; - var i; - - for (i in names) { - sans.push({ type: 2, value: names[i] }); - } - csr.setAttributes([{ - name: 'extensionRequest', - extensions: [{name: 'subjectAltName', altNames: sans}] - }]); - - csr.sign(privateKey, forge.md.sha256.create()); - - // Convert CSR -> DER -> Base64 - var der = forge.asn1.toDer(forge.pki.certificationRequestToAsn1(csr)); - return util.b64enc(bytesToBuffer(der)); - }, - - verifiedCommonName: function(csr_b64) { - var der = bufferToBytes(util.b64dec(csr_b64)); - var csr = forge.pki.certificationRequestFromAsn1(forge.asn1.fromDer(der)); - - if (!csr.verify()) { - return false; - } - - for (var i=0; i https://daplie.com - * Apache-2.0 OR MIT (and hence also MPL 2.0) -*/ -'use strict'; - -module.exports = {}; diff --git a/lib/letsencrypt-ursa.js b/lib/letsencrypt-ursa.js deleted file mode 100644 index ea8296d..0000000 --- a/lib/letsencrypt-ursa.js +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * letiny-core - * Copyright(c) 2015 AJ ONeal https://daplie.com - * Apache-2.0 OR MIT (and hence also MPL 2.0) -*/ -'use strict'; - -var crypto = require('crypto'); -var ursa = require('ursa'); -var forge = require('node-forge'); - -function binstrToB64(binstr) { - return new Buffer(binstr, 'binary').toString('base64'); -} - -/* -function b64ToBinstr(b64) { - return new Buffer(b64, 'base64').toString('binary'); -} -*/ - -function privatePemToJwk(privkeyPem) { - var forgePrivkey = forge.pki.privateKeyFromPem(privkeyPem); - - // required in node.js 4.2.2 (but not io.js 1.6.3) - Object.keys(forgePrivkey).forEach(function (k) { - var val = forgePrivkey[k]; - if (val && val.toByteArray) { - forgePrivkey[k] = val.toByteArray(); - } - }); - - return { - kty: "RSA" - , n: binstrToB64(forgePrivkey.n) - , e: binstrToB64(forgePrivkey.e) - , d: binstrToB64(forgePrivkey.d) - , p: binstrToB64(forgePrivkey.p) - , q: binstrToB64(forgePrivkey.q) - , dp: binstrToB64(forgePrivkey.dP) - , dq: binstrToB64(forgePrivkey.dQ) - , qi: binstrToB64(forgePrivkey.qInv) - }; -} - -function generateRsaKeypair(bitlen, exp, cb) { - var keypair = ursa.generatePrivateKey(bitlen || 2048, exp || 6553); - var pems = { - publicKeyPem: keypair.toPublicPem().toString('ascii') // ascii PEM: ----BEGIN... - , privateKeyPem: keypair.toPrivatePem().toString('ascii') // ascii PEM: ----BEGIN... - }; - - // for account id - pems.publicKeySha256 = crypto.createHash('sha256').update(pems.publicKeyPem).digest('hex'); - // for compat with python client account id - pems.publicKeyMd5 = crypto.createHash('md5').update(pems.publicKeyPem).digest('hex'); - // json { n: ..., e: ..., iq: ..., etc } - pems.privateKeyJwk = privatePemToJwk(pems.privateKeyPem); - pems.privateKeyJson = pems.privateKeyJwk; - - // TODO thumbprint - - cb(null, pems); -} - -function privateJwkToPems(pkj, cb) { - Object.keys(pkj).forEach(function (key) { - pkj[key] = new Buffer(pkj[key], 'base64'); - }); - - var priv; - var pems; - - try { - priv = ursa.createPrivateKeyFromComponents( - pkj.n // modulus - , pkj.e // exponent - , pkj.p - , pkj.q - , pkj.dp - , pkj.dq - , pkj.qi - , pkj.d - ); - } catch(e) { - cb(e); - return; - } - - pems = { - privateKeyPem: priv.toPrivatePem().toString('ascii') - , publicKeyPem: priv.toPublicPem().toString('ascii') - }; - - // for account id - pems.publicKeySha256 = crypto.createHash('sha256').update(pems.publicKeyPem).digest('hex'); - // for compat with python client account id - pems.publicKeyMd5 = crypto.createHash('md5').update(pems.publicKeyPem).digest('hex'); - // json { n: ..., e: ..., iq: ..., etc } - pems.privateKeyJwk = privatePemToJwk(pems.privateKeyPem); - pems.privateKeyJson = pems.privateKeyJwk; - - cb(null, pems); -} - -module.exports.generateRsaKeypair = generateRsaKeypair; -module.exports.privateJwkToPems = privateJwkToPems; -module.exports.privatePemToJwk = privatePemToJwk;