From ed777bb2ec9fa6f5e332526fda438fe617ca27ea Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 1 Aug 2016 04:03:50 -0400 Subject: [PATCH] implementing sig & bugfixes --- lib/rsa-extra.js | 41 +++++++++++++++++---- lib/rsa-forge.js | 77 +++++++++++++++++++++++++-------------- lib/rsa-ursa.js | 83 ++++++++++++++++++++++++++++++++++--------- node.js | 79 +++++++++++++++++++++++++++++++++++++++- tests/generate-key.js | 32 ++++++----------- tests/generate-sig.js | 39 ++++++++++++++++++++ 6 files changed, 279 insertions(+), 72 deletions(-) create mode 100644 tests/generate-sig.js diff --git a/lib/rsa-extra.js b/lib/rsa-extra.js index 981645d..2850938 100644 --- a/lib/rsa-extra.js +++ b/lib/rsa-extra.js @@ -6,6 +6,9 @@ var forge = require('node-forge'); function binstrToB64(binstr) { return new Buffer(binstr, 'binary').toString('base64'); } +function b64ToBinstr(b64) { + return new Buffer(b64, 'b64').toString('binary'); +} /* importPemPrivateKey: function(pem) { @@ -37,7 +40,7 @@ var extrac = module.exports = { // internals // _forgeToPrivateJwk: function (keypair) { - var k = keypair._forge.privateKey; + var k = keypair._forge; return { kty: "RSA" @@ -52,7 +55,7 @@ var extrac = module.exports = { }; } , _forgeToPublicJwk: function (keypair) { - var k = keypair._forge.privateKey || keypair._forge.publicKey; + var k = keypair._forge || keypair._forgePublic; return { kty: "RSA" , n: binstrToB64(k.n.toByteArray()) @@ -62,13 +65,37 @@ var extrac = module.exports = { + // + // Import Forge + // +, _forgeImportJwk: require('./rsa-forge')._forgeImportJwk +, _forgeImportPublicJwk: require('./rsa-forge')._forgeImportPublicJwk +, _forgeImportPem: function (keypair) { + keypair._forge = keypair._forge || forge.pki.privateKeyFromPem(keypair.privateKeyPem); + } +, _forgeImportPublicPem: function (keypair) { + keypair._forgePublic = keypair._forgePublic || forge.pki.publicKeyFromPem(keypair.publicKeyPem); + } +, importForge: function (keypair) { + extrac._forgeImportJwk(keypair); + if (keypair.privateKeyPem) { + extrac._forgeImportPem(keypair); + } + if (keypair.publicKeyPem) { + extrac._forgeImportPublicPem(keypair); + } + return keypair; + } + + + // // Export JWK // , exportPrivateJwk: function (keypair) { var hasUrsaPrivate = keypair._ursa && true; var hasPrivatePem = keypair.privateKeyPem && true; - var hasForgePrivate = keypair._forge && keypair._forge.privateKey && true; + var hasForgePrivate = keypair._forge && keypair._forge && true; if (keypair.privateKeyJwk) { return keypair.privateKeyJwk; @@ -80,11 +107,11 @@ var extrac = module.exports = { } if (keypair.privateKeyPem) { - keypair._forge = { privateKey: forge.pki.privateKeyFromPem(keypair.privateKeyPem) }; + extrac._forgeImportPem(keypair); } } - if (keypair._forge && keypair._forge.privateKey) { + if (keypair._forge && keypair._forge) { return extrac._forgeToPrivateJwk(keypair); } @@ -113,11 +140,11 @@ var extrac = module.exports = { } if (keypair.publicKeyPem) { - keypair._forge = { privateKey: forge.pki.publicKeyFromPem(keypair.publicKeyPem) }; + extrac._forgeImportPublicPem(keypair); } } - if (keypair._forge && keypair._forge.privateKey) { + if (keypair._forge && keypair._forge) { return extrac._forgeToPublicJwk(keypair); } diff --git a/lib/rsa-forge.js b/lib/rsa-forge.js index 82bcf80..b1869f5 100644 --- a/lib/rsa-forge.js +++ b/lib/rsa-forge.js @@ -1,7 +1,6 @@ 'use strict'; var forge = require('node-forge'); -var utils = require('./key-utils.js'); function notToJson() { return undefined; @@ -35,15 +34,28 @@ var forgec = module.exports = { // // to components // - _base64ToBn: function (base64) { - return new forge.jsbn.BigInteger(utils.b64dec(base64).toString("hex"), 16); + _toStandardBase64: function (str) { + var b64 = str.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, ""); + + switch (b64.length % 4) { + case 2: b64 += "=="; break; + case 3: b64 += "="; break; + } + + return b64; + } +, _base64ToBin: function (base64) { + var std64 = forgec._toStandardBase64(base64); + var hex = new Buffer(std64, 'base64').toString("hex"); + + return new forge.jsbn.BigInteger(hex, 16); } , _privateJwkToComponents: function (jwk) { var components = []; // [ 'n', 'e', 'd', 'p', 'q', 'dP', 'dQ', 'qInv' ] [ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ].forEach(function (key) { - components.push(new forgec._base64tobin(jwk[key])); + components.push(forgec._base64ToBin(jwk[key])); }); return components; @@ -63,17 +75,42 @@ var forgec = module.exports = { // Generate New Keypair // , generateKeypair: function (bitlen, exp, options, cb) { - var keypair = forge.pki.rsa.generateKeyPair({ bits: bitlen || 1024, e: exp || 0x10001 }); + var fkeypair = forge.pki.rsa.generateKeyPair({ bits: bitlen || 1024, e: exp || 0x10001 }); - keypair.toJSON = notToJson; + fkeypair.toJSON = notToJson; cb(null, { - _forge: keypair + _forge: fkeypair.privateKey + , _forgePublic: fkeypair.publicKey }); } + // + // Import (no-op) + // +, _forgeImportJwk: function (keypair) { + keypair._forge = keypair._forge || forge.pki.rsa.setPrivateKey.apply( + forge.pki.rsa + , forgec._privateJwkToComponents(keypair.privateKeyJwk) + ); + keypair._forge.toJSON = notToJson; + } +, _forgeImportPublicJwk: function (keypair) { + keypair._forgePublic = keypair._forgePublic || forge.pki.rsa.setPublicKey.apply( + forge.pki.rsa + , forgec._publicJwkToComponents(keypair.publicKeyJwk) + ); + keypair._forgePublic.toJSON = notToJson; + } +, import: function (keypair) { + // no-op since this must be done anyway in extra + return keypair; + } + + + // // Export Public / Private PEMs // @@ -82,18 +119,12 @@ var forgec = module.exports = { return keypair.privateKeyPem; } - if (keypair.privateKeyJwk && !(keypair._forge && keypair._forge.privateKey)) { - keypair._forge = { - privateKey: forge.pki.rsa.setPrivateKey.apply( - forge.pki.rsa - , forgec._privateJwkToComponents(keypair.privateKeyJwk) - ) - }; - keypair._forge.toJSON = notToJson; + if (keypair.privateKeyJwk && !(keypair._forge && keypair._forge)) { + forgec._forgeImportJwk(keypair); } - if (keypair._forge && keypair._forge.privateKey) { - return forge.pki.privateKeyToPem(keypair._forge.privateKey); + if (keypair._forge && keypair._forge) { + return forge.pki.privateKeyToPem(keypair._forge); } throw new Error("None of privateKeyPem, _forge, or privateKeyJwk found. No way to export private key PEM"); @@ -104,19 +135,13 @@ var forgec = module.exports = { } if ((keypair.privateKeyJwk || keypair.publicKeyJwk) - && !(keypair._forge && (keypair._forge.privateKey || keypair._forge.publicKey)) + && !(keypair._forge && (keypair._forge || keypair._forgePublic)) ) { - keypair._forge = { - publicKey: forge.pki.rsa.setPublicKey.apply( - forge.pki.rsa - , forgec._publicJwkToComponents(keypair.publicKeyJwk) - ) - }; - keypair._forge.toJSON = notToJson; + forgec._forgeImportPublicJwk(keypair); } if (keypair._forge) { - return forge.pki.publicKeyToPem(keypair._forge.publicKey || keypair._forge.privateKey); + return forge.pki.publicKeyToPem(keypair._forgePublic || keypair._forge); } throw new Error("None of publicKeyPem, _forge, publicKeyJwk, privateKeyPem, or privateKeyJwk found. No way to export public key PEM"); diff --git a/lib/rsa-ursa.js b/lib/rsa-ursa.js index ce562f8..43dec95 100644 --- a/lib/rsa-ursa.js +++ b/lib/rsa-ursa.js @@ -48,6 +48,68 @@ var ursac = module.exports = { + // + // Import + // +, _ursaImportPem: function (keypair) { + if (keypair._ursa) { + return; + } + + if (keypair.privateKeyPem) { + keypair._ursa = ursa.createPrivateKey(keypair.privateKeyPem); + keypair._ursa.toJSON = notToJson; + } + else if (keypair.publicKeyPem) { + ursac._ursaImportPublicPem(keypair); + } + } +, _ursaImportPublicPem: function (keypair) { + if (keypair._ursa || keypair._ursaPublic) { + return; + } + + if (keypair.publicKeyPem) { + keypair._ursaPublic = ursa.createPublicKey(keypair.publicKeyPem); + keypair._ursaPublic.toJSON = notToJson; + } + } +, _ursaImportJwk: function (keypair) { + if (keypair._ursa) { + return; + } + + if (keypair.privateKeyJwk) { + keypair._ursa = ursa.createPrivateKeyFromComponents.apply( + ursa + , ursac._privateJwkToComponents(keypair.privateKeyJwk) + ); + keypair._ursa.toJSON = notToJson; + } + else if (keypair.publicKeyJwk) { + ursac._ursaImportPublicJwk(keypair); + } + } +, _ursaImportPublicJwk: function (keypair) { + if (keypair._ursa || keypair._ursaPublic) { + return; + } + + keypair._ursaPublic = ursa.createPublicKeyFromComponents.apply( + ursa + , ursac._publicJwkToComponents(keypair.publicKeyJwk) + ); + keypair._ursaPublic.toJSON = notToJson; + } +, import: function (keypair) { + ursac._ursaImportJwk(keypair); + ursac._ursaImportPem(keypair); + + return keypair; + } + + + // // Export Public / Private PEMs // @@ -61,11 +123,7 @@ var ursac = module.exports = { } if (keypair.privateKeyJwk) { - keypair._ursa = ursa.createPrivateKeyFromComponents.apply( - ursa - , ursac._privateJwkToComponents(keypair.privateKeyJwk) - ); - keypair._ursa.toJSON = notToJson; + ursac._ursaImportJwk(keypair); return keypair._ursa.toPrivatePem().toString('ascii'); } @@ -82,28 +140,19 @@ var ursac = module.exports = { } if (keypair.publicKeyJwk) { - keypair._ursaPublic = ursa.createPublicKeyFromComponents.apply( - ursa - , ursac._publicJwkToComponents(keypair.publicKeyJwk) - ); - keypair._ursaPublic.toJSON = notToJson; + ursac._ursaImportPublicJwk(keypair); return keypair._ursa.toPublicPem().toString('ascii'); } if (keypair.privateKeyJwk) { - keypair._ursa = ursa.createPrivateKeyFromComponents.apply( - ursa - , ursac._privateJwkToComponents(keypair.privateKeyJwk) - ); - keypair._ursa.toJSON = notToJson; + ursac._ursaImportJwk(keypair); return keypair._ursa.toPublicPem().toString('ascii'); } if (keypair.privateKeyPem) { - keypair._ursa = ursa.createPrivateKey(keypair.privateKeyPem); - keypair._ursa.toJSON = notToJson; + ursac._ursaImportPem(keypair); return keypair._ursa.toPublicPem().toString('ascii'); } diff --git a/node.js b/node.js index af1e137..2e9f31c 100644 --- a/node.js +++ b/node.js @@ -14,7 +14,16 @@ function create(deps) { deps.NOBJ = {}; deps.RSA = RSA; + try { + RSA._URSA = require('ursa'); + } catch(e) { + // ignore + } + RSA.utils = require('./lib/key-utils.js'); + RSA.utils.toWebsafeBase64 = function (b64) { + return b64.replace(/[+]/g, "-").replace(/\//g, "_").replace(/=/g,""); + }; RSA.utils._bytesToBuffer = function (bytes) { var forge = require("node-forge"); @@ -28,7 +37,9 @@ function create(deps) { 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 +'"}'); - return RSA.util.b64enc(crypto.createHash('sha256').update(input).digest()); + var base64Digest = crypto.createHash('sha256').update(input).digest('base64'); + + return RSA.utils.toWebsafeBase64(base64Digest); }; RSA.generateKeypair = function (length, exponent, options, cb) { @@ -74,6 +85,72 @@ function create(deps) { }); }; + RSA.import = function (keypair/*, options*/) { + //options = options || NOBJ; // ignore + if (keypair.privateKeyJwk || keypair.privateKeyPem || keypair._ursa || (keypair._forge && keypair._forge)) { + keypair.privateKeyJwk = RSA._internal.exportPrivateJwk(keypair, { internal: true }); + //keypair.privateKeyPem = RSA._internal.exportPrivatePem(keypair, { internal: true }); + return keypair; + } + + if (keypair.publicKeyJwk || keypair.publicKeyPem || keypair._ursaPublic || (keypair._forge && keypair._forgePublic)) { + keypair.publicKeyJwk = RSA._internal.exportPublicJwk(keypair, { internal: true }); + //keypair.publicKeyPem = RSA._internal.exportPublicPem(keypair, { internal: true }); + return keypair; + } + + throw new Error('found neither private nor public keypair in any supported format'); + }; + + RSA._ursaGenerateSig = function (keypair, sha256Buf) { + var sig = keypair._ursa.sign('sha256', sha256Buf); + var sig64 = RSA.utils.toWebsafeBase64(sig.toString('base64')); + + return sig64; + }; + RSA._forgeGenerateSig = function (keypair, sha256Buf) { + var forge = require('node-forge'); + var md = forge.util.createBuffer(sha256Buf.toString('binary'), 'binary'); + var sigF = keypair._forge.sign(md); + var sig64 = RSA.utils.toWebsafeBase64( + new Buffer(forge.util.bytesToHex(sigF), "hex").toString('base64') + ); + + return sig64; + }; + RSA.generateSignatureJwk = function (keypair, payload, nonce) { + keypair = RSA._internal.import(keypair); + keypair = RSA._internal.importForge(keypair); + keypair.publicKeyJwk = RSA.exportPublicJwk(keypair); + + // Compute JWS signature + var protectedHeader = ""; + if (nonce) { + protectedHeader = JSON.stringify({nonce: nonce}); + } + var protected64 = RSA.utils.toWebsafeBase64(new Buffer(protectedHeader).toString('base64')); + var payload64 = RSA.utils.toWebsafeBase64(payload.toString('base64')); + var raw = protected64 + "." + payload64; + var sha256Buf = crypto.createHash('sha256').update(raw).digest(); + var sig64; + + if (RSA._URSA) { + sig64 = RSA._ursaGenerateSig(keypair, sha256Buf); + } else { + sig64 = RSA._forgeGenerateSig(keypair, sha256Buf); + } + + return { + header: { + alg: "RS256" + , jwk: keypair.publicKeyJwk + } + , protected: protected64 + , payload: payload64 + , signature: sig64 + }; + }; + RSA.exportPrivateKey = RSA._internal.exportPrivatePem; RSA.exportPublicKey = RSA._internal.exportPublicPem; RSA.exportPrivatePem = RSA._internal.exportPrivatePem; diff --git a/tests/generate-key.js b/tests/generate-key.js index 5e5db45..ef23bfe 100644 --- a/tests/generate-key.js +++ b/tests/generate-key.js @@ -2,14 +2,7 @@ var RSA = require('../').RSA; -console.log('RSA'); -console.log(RSA); - RSA.generateKeypair(null, null, null, function (err, keys) { - console.log(''); - console.log('keys'); - console.log(keys); - if (!keys.privateKeyJwk) { throw new Error("Expected privateKeyJwk, but it is missing"); } @@ -22,7 +15,7 @@ RSA.generateKeypair(null, null, null, function (err, keys) { || keys._ursa || keys._forge ) { - console.error(keys); + console.error(Object.keys(keys)); throw new Error("Got unexpected keys"); } @@ -31,24 +24,21 @@ RSA.generateKeypair(null, null, null, function (err, keys) { , pem: true // export pems , jwk: false // export jwks , internal: true // preserve internal intermediate formats (_ursa, _forge) - , thumbprint: true // JWK sha256 thumbprint + //, thumbprint: true // JWK sha256 thumbprint }; RSA.generateKeypair(512, 65537, options, function (err, keys) { - console.log(''); - console.log('keys'); - console.log(keys); - if ( - keys.publicKeyJwk - || keys.privateKeyPem - || keys.publicKeyPem - || keys.thumbprint - || keys._ursa - || keys._forge + (keys.publicKeyJwk && !keys.thumbprint) + || !keys.privateKeyPem + || !keys.publicKeyPem + //|| !keys.thumbprint + || !(keys._ursa || keys._forge) ) { - console.error(keys); - throw new Error("Got unexpected keys"); + console.error(Object.keys(keys)); + throw new Error("Missing expected keys"); } + + console.log('All is well!'); }); }); diff --git a/tests/generate-sig.js b/tests/generate-sig.js new file mode 100644 index 0000000..cddcb82 --- /dev/null +++ b/tests/generate-sig.js @@ -0,0 +1,39 @@ +'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 result = { + "header": { + "alg": "RS256", + "jwk": { + "kty": "RSA", + "n": "AMJubTfOtAarnJytLE8fhNsEI8wnpjRvBXGK/Kp0675J10ORzxyMLqzIZF3tcrUkKBrtdc79u4X0GocDUgukpfkY+2UPUS/GxehUYbYrJYWOLkoJWzxn7wfoo9X1JgvBMY6wHQnTKvnzZdkom2FMhGxkLaEUGDSfsNznTTZNBBg9", + "e": "AQAB" + } + }, + "protected": "eyJub25jZSI6IjhlZjU2MjRmNWVjOWQzZWYifQ", + "payload": "JLzF1NBNCV3kfbJ5sFaFyX94fJuL2H-IzaoBN-ciiHk", + "signature": "Wb2al5SDyh5gjmkV79MK9m3sfNBBPjntSKor-34BBoGwr6n8qEnBmqB1Y4zbo-5rmvsoPmJsnRlP_hRiUY86zSAQyfbisTGrGBl0IQ7ditpkfYVm0rBWJ8WnYNqYNp8K3qcD7NW72tsy-XoWEjNlz4lWJeRdEG2Nt4CJgnREH4Y" +}; + +var jws = RSA.generateSignatureJwk( + keypair +, new Buffer('24bcc5d4d04d095de47db279b05685c97f787c9b8bd87f88cdaa0137e7228879', 'hex') +, '8ef5624f5ec9d3ef' +); + +console.log(JSON.stringify(jws, null, ' '));