implementing sig & bugfixes

This commit is contained in:
AJ ONeal 2016-08-01 04:03:50 -04:00
parent 487d973c91
commit ed777bb2ec
6 changed files with 279 additions and 72 deletions

View File

@ -6,6 +6,9 @@ var forge = require('node-forge');
function binstrToB64(binstr) { function binstrToB64(binstr) {
return new Buffer(binstr, 'binary').toString('base64'); return new Buffer(binstr, 'binary').toString('base64');
} }
function b64ToBinstr(b64) {
return new Buffer(b64, 'b64').toString('binary');
}
/* /*
importPemPrivateKey: function(pem) { importPemPrivateKey: function(pem) {
@ -37,7 +40,7 @@ var extrac = module.exports = {
// internals // internals
// //
_forgeToPrivateJwk: function (keypair) { _forgeToPrivateJwk: function (keypair) {
var k = keypair._forge.privateKey; var k = keypair._forge;
return { return {
kty: "RSA" kty: "RSA"
@ -52,7 +55,7 @@ var extrac = module.exports = {
}; };
} }
, _forgeToPublicJwk: function (keypair) { , _forgeToPublicJwk: function (keypair) {
var k = keypair._forge.privateKey || keypair._forge.publicKey; var k = keypair._forge || keypair._forgePublic;
return { return {
kty: "RSA" kty: "RSA"
, n: binstrToB64(k.n.toByteArray()) , 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 // Export JWK
// //
, exportPrivateJwk: function (keypair) { , exportPrivateJwk: function (keypair) {
var hasUrsaPrivate = keypair._ursa && true; var hasUrsaPrivate = keypair._ursa && true;
var hasPrivatePem = keypair.privateKeyPem && true; var hasPrivatePem = keypair.privateKeyPem && true;
var hasForgePrivate = keypair._forge && keypair._forge.privateKey && true; var hasForgePrivate = keypair._forge && keypair._forge && true;
if (keypair.privateKeyJwk) { if (keypair.privateKeyJwk) {
return keypair.privateKeyJwk; return keypair.privateKeyJwk;
@ -80,11 +107,11 @@ var extrac = module.exports = {
} }
if (keypair.privateKeyPem) { 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); return extrac._forgeToPrivateJwk(keypair);
} }
@ -113,11 +140,11 @@ var extrac = module.exports = {
} }
if (keypair.publicKeyPem) { 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); return extrac._forgeToPublicJwk(keypair);
} }

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
var forge = require('node-forge'); var forge = require('node-forge');
var utils = require('./key-utils.js');
function notToJson() { function notToJson() {
return undefined; return undefined;
@ -35,15 +34,28 @@ var forgec = module.exports = {
// //
// to components // to components
// //
_base64ToBn: function (base64) { _toStandardBase64: function (str) {
return new forge.jsbn.BigInteger(utils.b64dec(base64).toString("hex"), 16); 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) { , _privateJwkToComponents: function (jwk) {
var components = []; var components = [];
// [ 'n', 'e', 'd', 'p', 'q', 'dP', 'dQ', 'qInv' ] // [ 'n', 'e', 'd', 'p', 'q', 'dP', 'dQ', 'qInv' ]
[ 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi' ].forEach(function (key) { [ '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; return components;
@ -63,17 +75,42 @@ var forgec = module.exports = {
// Generate New Keypair // Generate New Keypair
// //
, generateKeypair: function (bitlen, exp, options, cb) { , 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, { 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 // Export Public / Private PEMs
// //
@ -82,18 +119,12 @@ var forgec = module.exports = {
return keypair.privateKeyPem; return keypair.privateKeyPem;
} }
if (keypair.privateKeyJwk && !(keypair._forge && keypair._forge.privateKey)) { if (keypair.privateKeyJwk && !(keypair._forge && keypair._forge)) {
keypair._forge = { forgec._forgeImportJwk(keypair);
privateKey: forge.pki.rsa.setPrivateKey.apply(
forge.pki.rsa
, forgec._privateJwkToComponents(keypair.privateKeyJwk)
)
};
keypair._forge.toJSON = notToJson;
} }
if (keypair._forge && keypair._forge.privateKey) { if (keypair._forge && keypair._forge) {
return forge.pki.privateKeyToPem(keypair._forge.privateKey); return forge.pki.privateKeyToPem(keypair._forge);
} }
throw new Error("None of privateKeyPem, _forge, or privateKeyJwk found. No way to export private key PEM"); 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) if ((keypair.privateKeyJwk || keypair.publicKeyJwk)
&& !(keypair._forge && (keypair._forge.privateKey || keypair._forge.publicKey)) && !(keypair._forge && (keypair._forge || keypair._forgePublic))
) { ) {
keypair._forge = { forgec._forgeImportPublicJwk(keypair);
publicKey: forge.pki.rsa.setPublicKey.apply(
forge.pki.rsa
, forgec._publicJwkToComponents(keypair.publicKeyJwk)
)
};
keypair._forge.toJSON = notToJson;
} }
if (keypair._forge) { 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"); throw new Error("None of publicKeyPem, _forge, publicKeyJwk, privateKeyPem, or privateKeyJwk found. No way to export public key PEM");

View File

@ -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 // Export Public / Private PEMs
// //
@ -61,11 +123,7 @@ var ursac = module.exports = {
} }
if (keypair.privateKeyJwk) { if (keypair.privateKeyJwk) {
keypair._ursa = ursa.createPrivateKeyFromComponents.apply( ursac._ursaImportJwk(keypair);
ursa
, ursac._privateJwkToComponents(keypair.privateKeyJwk)
);
keypair._ursa.toJSON = notToJson;
return keypair._ursa.toPrivatePem().toString('ascii'); return keypair._ursa.toPrivatePem().toString('ascii');
} }
@ -82,28 +140,19 @@ var ursac = module.exports = {
} }
if (keypair.publicKeyJwk) { if (keypair.publicKeyJwk) {
keypair._ursaPublic = ursa.createPublicKeyFromComponents.apply( ursac._ursaImportPublicJwk(keypair);
ursa
, ursac._publicJwkToComponents(keypair.publicKeyJwk)
);
keypair._ursaPublic.toJSON = notToJson;
return keypair._ursa.toPublicPem().toString('ascii'); return keypair._ursa.toPublicPem().toString('ascii');
} }
if (keypair.privateKeyJwk) { if (keypair.privateKeyJwk) {
keypair._ursa = ursa.createPrivateKeyFromComponents.apply( ursac._ursaImportJwk(keypair);
ursa
, ursac._privateJwkToComponents(keypair.privateKeyJwk)
);
keypair._ursa.toJSON = notToJson;
return keypair._ursa.toPublicPem().toString('ascii'); return keypair._ursa.toPublicPem().toString('ascii');
} }
if (keypair.privateKeyPem) { if (keypair.privateKeyPem) {
keypair._ursa = ursa.createPrivateKey(keypair.privateKeyPem); ursac._ursaImportPem(keypair);
keypair._ursa.toJSON = notToJson;
return keypair._ursa.toPublicPem().toString('ascii'); return keypair._ursa.toPublicPem().toString('ascii');
} }

79
node.js
View File

@ -14,7 +14,16 @@ function create(deps) {
deps.NOBJ = {}; deps.NOBJ = {};
deps.RSA = RSA; deps.RSA = RSA;
try {
RSA._URSA = require('ursa');
} catch(e) {
// ignore
}
RSA.utils = require('./lib/key-utils.js'); 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) { RSA.utils._bytesToBuffer = function (bytes) {
var forge = require("node-forge"); 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)"); 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._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) { 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.exportPrivateKey = RSA._internal.exportPrivatePem;
RSA.exportPublicKey = RSA._internal.exportPublicPem; RSA.exportPublicKey = RSA._internal.exportPublicPem;
RSA.exportPrivatePem = RSA._internal.exportPrivatePem; RSA.exportPrivatePem = RSA._internal.exportPrivatePem;

View File

@ -2,14 +2,7 @@
var RSA = require('../').RSA; var RSA = require('../').RSA;
console.log('RSA');
console.log(RSA);
RSA.generateKeypair(null, null, null, function (err, keys) { RSA.generateKeypair(null, null, null, function (err, keys) {
console.log('');
console.log('keys');
console.log(keys);
if (!keys.privateKeyJwk) { if (!keys.privateKeyJwk) {
throw new Error("Expected privateKeyJwk, but it is missing"); throw new Error("Expected privateKeyJwk, but it is missing");
} }
@ -22,7 +15,7 @@ RSA.generateKeypair(null, null, null, function (err, keys) {
|| keys._ursa || keys._ursa
|| keys._forge || keys._forge
) { ) {
console.error(keys); console.error(Object.keys(keys));
throw new Error("Got unexpected keys"); throw new Error("Got unexpected keys");
} }
@ -31,24 +24,21 @@ RSA.generateKeypair(null, null, null, function (err, keys) {
, pem: true // export pems , pem: true // export pems
, jwk: false // export jwks , jwk: false // export jwks
, internal: true // preserve internal intermediate formats (_ursa, _forge) , 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) { RSA.generateKeypair(512, 65537, options, function (err, keys) {
console.log('');
console.log('keys');
console.log(keys);
if ( if (
keys.publicKeyJwk (keys.publicKeyJwk && !keys.thumbprint)
|| keys.privateKeyPem || !keys.privateKeyPem
|| keys.publicKeyPem || !keys.publicKeyPem
|| keys.thumbprint //|| !keys.thumbprint
|| keys._ursa || !(keys._ursa || keys._forge)
|| keys._forge
) { ) {
console.error(keys); console.error(Object.keys(keys));
throw new Error("Got unexpected keys"); throw new Error("Missing expected keys");
} }
console.log('All is well!');
}); });
}); });

39
tests/generate-sig.js Normal file
View File

@ -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, ' '));