368 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
|   }
 | |
| };
 |