diff --git a/lib/rsa-extra.js b/lib/rsa-extra.js index 2850938..efdd888 100644 --- a/lib/rsa-extra.js +++ b/lib/rsa-extra.js @@ -71,10 +71,25 @@ var extrac = module.exports = { , _forgeImportJwk: require('./rsa-forge')._forgeImportJwk , _forgeImportPublicJwk: require('./rsa-forge')._forgeImportPublicJwk , _forgeImportPem: function (keypair) { - keypair._forge = keypair._forge || forge.pki.privateKeyFromPem(keypair.privateKeyPem); + if (!keypair._forge && keypair.privateKeyPem) { + keypair._forge = forge.pki.privateKeyFromPem(keypair.privateKeyPem); + } + keypair._forge.toJSON = notToJson; + + extrac._forgeImportPublicPem(keypair); } , _forgeImportPublicPem: function (keypair) { - keypair._forgePublic = keypair._forgePublic || forge.pki.publicKeyFromPem(keypair.publicKeyPem); + if (keypair._forgePublic) { + return; + } + + if (keypair._forge) { + keypair._forgePublic = forge.pki.rsa.setPublicKey(keypair._forge.n, keypair._forge.e); + } + else if (keypair.publicKeyPem) { + keypair._forgePublic = keypair._forgePublic || forge.pki.publicKeyFromPem(keypair.publicKeyPem); + } + keypair._forgePublic.toJSON = notToJson; } , importForge: function (keypair) { extrac._forgeImportJwk(keypair); @@ -150,7 +165,4 @@ var extrac = module.exports = { throw new Error("None of publicKeyPem privateKeyPem, _ursa, _forge, publicKeyJwk, or privateKeyJwk found. No way to export private key Jwk"); } - - - }; diff --git a/lib/rsa-forge.js b/lib/rsa-forge.js index b1869f5..329a925 100644 --- a/lib/rsa-forge.js +++ b/lib/rsa-forge.js @@ -91,17 +91,30 @@ var forgec = module.exports = { // Import (no-op) // , _forgeImportJwk: function (keypair) { - keypair._forge = keypair._forge || forge.pki.rsa.setPrivateKey.apply( - forge.pki.rsa - , forgec._privateJwkToComponents(keypair.privateKeyJwk) - ); + if (!keypair._forge && keypair.privateKeyJwk) { + keypair._forge = forge.pki.rsa.setPrivateKey.apply( + forge.pki.rsa + , forgec._privateJwkToComponents(keypair.privateKeyJwk) + ); + } keypair._forge.toJSON = notToJson; + + forgec._forgeImportPublicJwk(keypair); } , _forgeImportPublicJwk: function (keypair) { - keypair._forgePublic = keypair._forgePublic || forge.pki.rsa.setPublicKey.apply( - forge.pki.rsa - , forgec._publicJwkToComponents(keypair.publicKeyJwk) - ); + if (keypair._forgePublic) { + return; + } + + if (keypair._forge) { + keypair._forgePublic = forge.pki.rsa.setPublicKey(keypair._forge.n, keypair._forge.e); + } + else if (keypair.publicKeyJwk) { + keypair._forgePublic = forge.pki.rsa.setPublicKey.apply( + forge.pki.rsa + , forgec._publicJwkToComponents(keypair.publicKeyJwk || keypair.privateKeyJwk) + ); + } keypair._forgePublic.toJSON = notToJson; } , import: function (keypair) { diff --git a/node.js b/node.js index e1f51b5..4a17ecc 100644 --- a/node.js +++ b/node.js @@ -25,7 +25,7 @@ function create(deps) { return b64.replace(/[+]/g, "-").replace(/\//g, "_").replace(/=/g,""); }; - RSA.utils._bytesToBuffer = function (bytes) { + RSA.utils._forgeBytesToBuf = function (bytes) { var forge = require("node-forge"); return new Buffer(forge.util.bytesToHex(bytes), "hex"); }; @@ -36,7 +36,7 @@ function create(deps) { if (!jwk.e || !jwk.n) { throw new Error("You must provide an RSA jwk with 'e' and 'n' (the public components)"); } - var input = RSA.utils._bytesToBuffer('{"e":"'+ jwk.e + '","kty":"RSA","n":"'+ jwk.n +'"}'); + var input = RSA.utils._forgeBytesToBuf('{"e":"'+ jwk.e + '","kty":"RSA","n":"'+ jwk.n +'"}'); var base64Digest = crypto.createHash('sha256').update(input).digest('base64'); return RSA.utils.toWebsafeBase64(base64Digest); @@ -161,6 +161,60 @@ function create(deps) { }; }; + // + // Generate CSR + // + RSA._generateCsrForge = function (keypair, names) { + var forge = require('node-forge'); + keypair = RSA._internal.importForge(keypair); + + // Create and sign the CSR + var csr = forge.pki.createCertificationRequest(); + csr.publicKey = keypair._forgePublic; + // TODO should the commonName be shift()ed off so that it isn't also in altNames? + // http://stackoverflow.com/questions/5935369/ssl-how-do-common-names-cn-and-subject-alternative-names-san-work-together + csr.setSubject([{ name: 'commonName', value: names[0] }]); + + var sans = names.map(function (name) { + return { type: 2, value: name }; + }); + csr.setAttributes([{ + name: 'extensionRequest', + extensions: [{name: 'subjectAltName', altNames: sans}] + }]); + + // TODO wrap with node crypto (as done for signature) + csr.sign(keypair._forge, forge.md.sha256.create()); + + return csr; + }; + RSA.generateCsrAsn1 = function (keypair, names) { + var forge = require('node-forge'); + var csr = RSA._generateCsrForge(keypair, names); + var asn1 = forge.pki.certificationRequestToAsn1(csr); + + return RSA.utils._forgeBytesToBuf(asn1); + }; + RSA.generateCsrPem = function (keypair, names) { + var forge = require('node-forge'); + var csr = RSA._generateCsrForge(keypair, names); + return forge.pki.certificationRequestToPem(csr); + }; + RSA.generateCsrDer = function (keypair, names) { + var forge = require('node-forge'); + var csr = RSA._generateCsrForge(keypair, names); + var asn1 = forge.pki.certificationRequestToAsn1(csr); + var der = forge.asn1.toDer(asn1); + + return RSA.utils._forgeBytesToBuf(der); + }; + RSA.generateCsrWeb64 = function (keypair, names) { + var buf = RSA.generateCsrDer(keypair, names); + var b64 = buf.toString('base64'); + var web64 = RSA.utils.toWebsafeBase64(b64); + return web64; + }; + RSA.exportPrivateKey = RSA._internal.exportPrivatePem; RSA.exportPublicKey = RSA._internal.exportPublicPem; RSA.exportPrivatePem = RSA._internal.exportPrivatePem; diff --git a/tests/generate-csr.js b/tests/generate-csr.js new file mode 100644 index 0000000..c737458 --- /dev/null +++ b/tests/generate-csr.js @@ -0,0 +1,26 @@ +'use strict'; + +var RSA = require('../').RSA; + +var keypair = { + privateKeyJwk: { + "kty": "RSA", + "n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9", + "e": "AQAB", + "d": "HT8DCrv69G3n9uFNovE4yMEMqW7lX0m75eJkMze3Jj5xNOa/4qlrc+4IuuA2uuyfY72IVQRxqqqXOuvS8ZForZZk+kWSd6z45hrpbNAAHH2Rf7XwnwHY8VJrOQF3UtbktTWqHX36ITZb9Hmf18hWsIeEp8Ng7Ru9h7hNuVxKMjk=", + "p": "AONjOvZVAvhCM2JnLGWJG3+5Boar3MB5P4ezfExDmuyGET/w0C+PS60jbjB8TivQsSdEcGo7GOaOlmAX6EQtAec=", + "q": "ANrllgJsy4rTMfa3mQ50kMIcNahiEOearhAcJgQUCHuOjuEnhU9FfExA/m5FXjmEFQhRwkuhk0QaIqTGbUzxGDs=", + "dp": "ALuxHOpYIatqeZ+wKiVllx1GTOy8z+rQKnCI5wDMjQTPZU2yKSYY0g6IQFwlPyFLke8nvuLxBQzKhbWsBjzAKeE=", + "dq": "XLhDAmPzE6rBzy+VtXnKl247jEd9wZzTfh9uOuwBa9TG0Lhcz2cvb11YaH0ZnGNGRW/cTQzzxDUN1531TlIRYQ==", + "qi": "AI2apz6ECfGwhsvIcU3+yFt+3CA78CUVsX4NUul5m3Cls2m+5MbGQG5K0hGpxjDC3OmXTq1Y5gnep5yUZvVPZI4=" + } +}; + +var csrPem = RSA.generateCsrPem(keypair, ['example.com', 'www.example.com']); +var csr64 = RSA.generateCsrWeb64(keypair, ['example.com', 'www.example.com']); +console.log(''); +console.log('DEBUG csrPem'); +console.log(csrPem); +console.log(''); +console.log('DEBUG csr64'); +console.log(csr64);