le-acme-core.js/lib/letsencrypt-forge.js

368 lines
11 KiB
JavaScript

// 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 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<csr.subject.attributes.length; ++i) {
if (csr.subject.attributes[i].name === "commonName") {
return csr.subject.attributes[i].value;
}
}
return false;
},
///// CERTIFICATE GENERATION
// 'ca' parameter includes information about the CA
// {
// distinguishedName: /* forge-formatted DN */
// keyPair: {
// publicKey: /* JWK */
// privateKey: /* JWK */
// }
// }
generateCertificate: function(ca, serialNumber, csr_b64) {
var der = bufferToBytes(util.b64dec(csr_b64));
var csr = forge.pki.certificationRequestFromAsn1(forge.asn1.fromDer(der));
// Extract the public key and common name
var publicKey = csr.publicKey;
var commonName = null;
for (var i=0; i<csr.subject.attributes.length; ++i) {
if (csr.subject.attributes[i].name === "commonName") {
commonName = csr.subject.attributes[i].value;
break;
}
}
if (!commonName) { return false; }
// Create the certificate
var cert = forge.pki.createCertificate();
cert.publicKey = publicKey;
cert.serialNumber = serialNumber;
// 1-year validity
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
cert.setSubject([{ name: "commonName", value: commonName }]);
cert.setIssuer(ca.distinguishedName);
cert.setExtensions([
{ name: "basicConstraints", cA: false },
{ name: "keyUsage", digitalSignature: true, keyEncipherment: true },
{ name: "extKeyUsage", serverAuth: true },
{ name: "subjectAltName", altNames: [{ type: 2, value: commonName }] }
]);
// Import signing key and sign
var privateKey = importPrivateKey(ca.keyPair.privateKey);
cert.sign(privateKey);
// Return base64-encoded DER
der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert));
return bytesToBuffer(der);
},
generateDvsniCertificate: function(keyPair, nonceName, zName) {
var cert = forge.pki.createCertificate();
cert.publicKey = importPublicKey(keyPair.publicKey);
cert.serialNumber = '01';
cert.validity.notBefore = new Date();
cert.validity.notAfter = new Date();
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
cert.setSubject([{ name: "commonName", value: nonceName }]);
cert.setIssuer([{ name: "commonName", value: nonceName }]);
cert.setExtensions([
{ name: "basicConstraints", cA: false },
{ name: "keyUsage", digitalSignature: true, keyEncipherment: true },
{ name: "extKeyUsage", serverAuth: true },
{ name: "subjectAltName", altNames: [
{ type: 2, value: nonceName },
{ type: 2, value: zName }
]}
]);
cert.sign(importPrivateKey(keyPair.privateKey));
// Return base64-encoded DER, as above
var der = forge.asn1.toDer(forge.pki.certificateToAsn1(cert));
return util.b64enc(bytesToBuffer(der));
},
///// TLS CONTEXT GENERATION
createContext: function(keyPair, cert) {
var privateKey = importPrivateKey(keyPair.privateKey);
var derCert = bufferToBytes(util.b64dec(cert));
var realCert = forge.pki.certificateFromAsn1(forge.asn1.fromDer(derCert));
return crypto.createCredentials({
key: forge.pki.privateKeyToPem(privateKey),
cert: forge.pki.certificateToPem(realCert)
}).context;
}
};