diff --git a/lib/key-utils.js b/lib/key-utils.js new file mode 100644 index 0000000..7c5ee5d --- /dev/null +++ b/lib/key-utils.js @@ -0,0 +1,76 @@ +// 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 utils = { + + fromStandardB64: function(x) { + return x.replace(/[+]/g, "-").replace(/\//g, "_").replace(/=/g,""); + }, + + toStandardB64: function(x) { + var b64 = x.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, ""); + + switch (b64.length % 4) { + case 2: b64 += "=="; break; + case 3: b64 += "="; break; + } + + return b64; + }, + + b64enc: function(buffer) { + return utils.fromStandardB64(buffer.toString("base64")); + }, + + b64dec: function(str) { + return new Buffer(utils.toStandardB64(str), "base64"); + }, + + isB64String: function(x) { + return ("string" === typeof x) && !x.match(/[^a-zA-Z0-9_-]/); + }, + + fieldsPresent: function(fields, object) { + for (var i in fields) { + if (!(fields[i] in object)) { + return false; + } + } + return true; + }, + + validSignature: function(sig) { + return (("object" === typeof sig) && + ("alg" in sig) && ("string" === typeof sig.alg) && + ("nonce" in sig) && utils.isB64String(sig.nonce) && + ("sig" in sig) && utils.isB64String(sig.sig) && + ("jwk" in sig) && utils.validJWK(sig.jwk)); + }, + + validJWK: function(jwk) { + return (("object" === typeof jwk) && ("kty" in jwk) && ( + ((jwk.kty === "RSA") + && ("n" in jwk) && utils.isB64String(jwk.n) + && ("e" in jwk) && utils.isB64String(jwk.e)) || + ((jwk.kty === "EC") + && ("crv" in jwk) + && ("x" in jwk) && utils.isB64String(jwk.x) + && ("y" in jwk) && utils.isB64String(jwk.y)) + ) && !("d" in jwk)); + }, + + // A simple, non-standard fingerprint for a JWK, + // just so that we don't have to store objects + keyFingerprint: function(jwk) { + switch (jwk.kty) { + case "RSA": return jwk.n; + case "EC": return jwk.crv + jwk.x + jwk.y; + } + throw "Unrecognized key type"; + } +}; + +module.exports = utils; diff --git a/lib/node.js b/lib/node.js new file mode 100644 index 0000000..4607a4c --- /dev/null +++ b/lib/node.js @@ -0,0 +1,44 @@ +/*! + * rsa-compat + * Copyright(c) 2016 AJ ONeal https://daplie.com + * Apache-2.0 OR MIT (and hence also MPL 2.0) +*/ +'use strict'; + +var cryptoc = {}; +var rsaExtra = require('./rsa-extra'); +var rsaForge = require('./rsa-forge'); +var ursac; + +try { + ursac = require('./rsa-ursa'); +} catch(e) { + ursac = {}; + // things will run a little slower on keygen, but it'll work on windows + // (but don't try this on raspberry pi - 20+ MINUTES key generation) +} + +// order of crypto precdence is +// * native +// * ursa +// * forge extra (the new one aimed to be less-forgey) +// * forge (fallback) +Object.keys(ursac).forEach(function (key) { + if (!cryptoc[key]) { + cryptoc[key] = ursac[key]; + } +}); + +Object.keys(rsaExtra).forEach(function (key) { + if (!cryptoc[key]) { + cryptoc[key] = rsaExtra[key]; + } +}); + +Object.keys(rsaForge).forEach(function (key) { + if (!cryptoc[key]) { + cryptoc[key] = rsaForge[key]; + } +}); + +module.exports.cryptoc = cryptoc; diff --git a/lib/rsa-ursa.js b/lib/rsa-ursa.js new file mode 100644 index 0000000..8c81b7e --- /dev/null +++ b/lib/rsa-ursa.js @@ -0,0 +1,120 @@ +'use strict'; + +var ursa = require('ursa'); + +function notToJson() { + return undefined; +} + +var ursac = { + + + + // + // to components + // + _privateJwkToComponents: function (jwk) { + var components = []; + + [ 'n', 'e', 'p', 'q', 'dp', 'dq', 'qi', 'd' ].forEach(function (key) { + components.push(new Buffer(jwk[key], 'base64')); + }); + + return components; + } +, _publicJwkToComponents: function (jwk) { + var components = []; + [ 'n', 'e' ].forEach(function (key) { + components.push(new Buffer(jwk[key], 'base64')); + }); + + return components; + } + + + + // + // Generate New Keypair + // +, generateKeypair: function (bitlen, exp, options, cb) { + var keypair = ursa.generatePrivateKey(bitlen || 2048, exp || 6553); + + keypair.toJSON = notToJson; + + cb(null, { + _ursa: keypair + }); + } + + + + // + // Export Public / Private PEMs + // +, exportPrivateKeyPem: function (keypair) { + if (keypair.privateKeyPem) { + return keypair.privateKeyPem; + } + + if (keypair._ursa) { + return keypair._ursa.toPrivatePem().toString('ascii'); + } + + if (keypair.privateKeyJwk) { + keypair._ursa = ursa.createPrivateKeyFromComponents.apply( + ursa + , ursac._privateJwkToComponents(keypair.privateKeyJwk) + ); + keypair._ursa.toJSON = notToJson; + + return keypair._ursa.toPrivatePem().toString('ascii'); + } + + throw new Error("None of privateKeyPem, _ursa, or privateKeyJwk found. No way to export private key PEM"); + } +, exportPublicKeyPem: function (keypair) { + if (keypair.publicKeyPem) { + return keypair.publicKeyPem; + } + + if (keypair._ursa || keypair._ursaPublic) { + return (keypair._ursa || keypair._ursaPublic).toPublicPem().toString('ascii'); + } + + if (keypair.publicKeyJwk) { + keypair._ursaPublic = ursa.createPublicKeyFromComponents.apply( + ursa + , ursac._publicJwkToComponents(keypair.publicKeyJwk) + ); + keypair._ursaPublic.toJSON = notToJson; + + return keypair._ursa.toPublicPem().toString('ascii'); + } + + if (keypair.privateKeyJwk) { + keypair._ursa = ursa.createPrivateKeyFromComponents.apply( + ursa + , ursac._privateJwkToComponents(keypair.privateKeyJwk) + ); + keypair._ursa.toJSON = notToJson; + + return keypair._ursa.toPublicPem().toString('ascii'); + } + + if (keypair.privateKeyPem) { + keypair._ursa = ursa.createPrivateKey(keypair.privateKeyPem); + keypair._ursa.toJSON = notToJson; + + return keypair._ursa.toPublicPem().toString('ascii'); + } + + throw new Error("None of publicKeyPem, _ursa, publicKeyJwk, privateKeyPem, or privateKeyJwk found. No way to export public key PEM"); + } +//, exportPrivateKeyJwk: NOT IMPLEMENTED HERE +//, exportPublicKeyJwk: NOT IMPLEMENTED HERE + + + +}; + +return ursac; diff --git a/node.js b/node.js new file mode 100644 index 0000000..73685f5 --- /dev/null +++ b/node.js @@ -0,0 +1,88 @@ +/*! + * rsa-compat + * Copyright(c) 2016 AJ ONeal https://daplie.com + * Apache-2.0 OR MIT (and hence also MPL 2.0) +*/ +'use strict'; +var RSA = {}; +var NOBJ = {}; + +function create(deps) { + var crypto = require('crypto'); + + deps = deps || {}; + deps.NOBJ = {}; + deps.RSA = RSA; + + RSA.utils = require('./lib/key-utils.js'); + + RSA.utils._bytesToBuffer = function (bytes) { + var forge = require("node-forge"); + return new Buffer(forge.util.bytesToHex(bytes), "hex"); + }; + RSA._internal = require('./lib/node').create(deps); + + RSA.thumbprint = function (jwk) { + jwk = jwk.privateKeyJwk || jwk.publicKeyJwk || jwk; + 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 +'"}'); + return RSA.util.b64enc(crypto.createHash('sha256').update(input).digest()); + }; + + RSA.generateKeypair = function (length, exponent, options, cb) { + var keypair = { + privateKeyPem: undefined + , publicKeyPem: undefined + , privateKeyJwk: undefined + , publicKeyJwk: undefined + , _ursa: undefined + , _forge: undefined + }; + + options = options || NOBJ; + + RSA._internal.generateKeypair(length, exponent, options, function (keys) { + if (false !== options.jwk || options.thumbprint) { + keypair.privateKeyJwk = RSA._internal.exportPrivateJwk(keys); + if (options.public) { + keypair.publicKeyJwk = RSA._internal.exportPublicJwk(keys); + /* + return { + kty: keypair.privateKeyJwk.kty + , n: keypair.privateKeyJwk.n + , e: keypair.privateKeyJwk.e + }; + */ + } + } + + if (options.pem) { + keypair.privateKeyPem = RSA._internal.exportPrivatePem(keys); + if (options.public) { + keypair.publicKeyPem = RSA._internal.exportPublicPem(keys); + } + } + + if (options.thumprint) { + keypair.thumbprint = RSA.thumbprint(keypair.privateKeyJwk /*|| keypair.publicKeyJwk*/); + } + + if (options.internal) { + //keypair._ursa = undefined; + //keypair._forge = undefined; + keypair._ursa = keys._ursa; + keypair._forge = keys._forge; + } + + cb(null, keypair); + return; + }); + }; + + return RSA; +} + +module.exports.RSA = create(/*require('./lib/node')*/); +//module.exports.RSA.create = create;