From 023b0b79486f3c1e27eaa98bd62b9022c449cbb0 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 13 Jul 2018 04:09:57 -0600 Subject: [PATCH] v1.5.0: add ursa-optional, deprecate non-abstract function sig --- README.md | 55 +++++++++++++++++++++++---------------- node.js | 27 +++++++++++++++---- package.json | 6 +++-- tests/generate-key-new.js | 46 ++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 30 deletions(-) create mode 100644 tests/generate-key-new.js diff --git a/README.md b/README.md index 2a1c0e0..b06cd6e 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,6 @@ node.js npm install --save rsa-compat ``` -For **more efficient** RSA key generation: -(I dropped `ursa` as an "optional dependency" because the non-fatal error messages on unsupported platforms and node versions were confusing people, but I still recommend installing it) - -```bash -npm install --save ursa -``` - **Node < v6** support: ```bash @@ -42,7 +35,6 @@ npm install --save buffer-v6-polyfill npm install --global rsa-compat ``` - Usage ===== @@ -64,11 +56,9 @@ Generate an RSA Keypair: ```javascript var RSA = require('rsa-compat').RSA; -var bitlen = 2048; -var exp = 65537; -var options = { public: true, pem: true, internal: true }; +var options = { bitlen: 2048, exp: 65537, public: true, pem: true, internal: true }; -RSA.generateKeypair(bitlen, exp, options, function (err, keypair) { +RSA.generateKeypair(options, function (err, keypair) { console.log(keypair); }); ``` @@ -108,11 +98,32 @@ NOTE: this object is JSON safe as _ursa and _forge will be ignored See http://crypto.stackexchange.com/questions/6593/what-data-is-saved-in-rsa-private-key to learn a little more about the meaning of the specific fields in the JWK. +Security and Compatibility +------ + +**TL;DR**: Use the default values 2048 and 65537 unless you have a really, really good reason to do otherwise. + +Various platforms *require* these values. + +Most security experts agree that 4096-bit is no more "secure" than 2048-bit - +a fundamental vulnerability in the RSA algorithm which causes 2048 to be broken +will most likely also cause 4096 to be broken +(i.e. if someone can prove mathematically prove P=NP or a way to predict prime numbers). +Also, many platforms +only support 2048 bit keys due to the insecurity of 1024-bit keys (which are not 1/2 secure +but rather 1/(2^1028) less secure) and the excess computational +cost of 4096-bit keys (it's not a 2x increase, it's more like a 2^2048 increase). + +As to why 65537 is even optional as a prime exponent or why it matters... no idea, +but it does matter. + API --- -* `RSA.generateKeypair(bitlen, exp, options, cb)` -* `RSA.import(keypair, options)` +* `RSA.generateKeypair(options, cb)` + * (deprecated `RSA.generateKeypair(bitlen, exp, options, cb)`) +* `RSA.import(options)` + * (deprecated `RSA.import(keypair, options)`) * `RSA.exportPrivatePem(keypair)` * `RSA.exportPublicPem(keypair)` * `RSA.exportPrivateJwk(keypair)` @@ -124,20 +135,18 @@ API `keypair` can be any object with any of these keys `publicKeyPem, privateKeyPem, publicKeyJwk, privateKeyJwk` -### RSA.generateKeypair(bitlen, exp, options, cb) +### RSA.generateKeypair(options, cb) Create a private keypair and export it as PEM, JWK, and/or internal formats ```javascript -RSA.generateKeypair(null, null, null, function (keypair) { /*...*/ }); +RSA.generateKeypair(null, function (keypair) { /*...*/ }); -RSA.generateKeypair(2048, 65537, { pem: false, public: false, internal: false }, function (keypair) { /*...*/ }); +RSA.generateKeypair({ + bitlen: 2048, exp: 65537, pem: false, public: false, internal: false +}, function (keypair) { /*...*/ }); ``` -`bitlen`: 2048 or 4096 - -`exp`: *65537* (default) - `options`: ```javascript { public: false // export public keys @@ -149,12 +158,12 @@ RSA.generateKeypair(2048, 65537, { pem: false, public: false, internal: false }, } ``` -### RSA.import(keypair, options) +### RSA.import(options) Imports keypair as JWKs and internal values `_ursa` and `_forge`. ```javascript -var keypair = RSA.import({ privateKeyPem: '...'}); +var keypair = RSA.import({ type: 'RSA', privateKeyPem: '...' }); console.log(keypair); ``` diff --git a/node.js b/node.js index 464fced..dabe709 100644 --- a/node.js +++ b/node.js @@ -13,6 +13,8 @@ try { var RSA = {}; var NOBJ = {}; +var DEFAULT_BITLEN = 2048; +var DEFAULT_EXPONENT = 65537; function create(deps) { var crypto = require('crypto'); @@ -22,7 +24,7 @@ function create(deps) { deps.RSA = RSA; try { - RSA._URSA = require('ursa'); + RSA._URSA = require('ursa-optional'); } catch(e) { // ignore } @@ -55,7 +57,20 @@ function create(deps) { return RSA.utils.toWebsafeBase64(base64Digest); }; - RSA.generateKeypair = function (length, exponent, options, cb) { + // length, exponent, options, cb + RSA.generateKeypair = function (options, cb, extra1, extra2) { + var length; + var exponent; + if ('function' === typeof extra2) { + length = options || DEFAULT_BITLEN; + exponent = cb || DEFAULT_EXPONENT; + options = extra1 || NOBJ; + cb = extra2; + } else { + if (!options) { options = NOBJ; } + length = options.bitlen || DEFAULT_BITLEN; + exponent = options.exp || DEFAULT_EXPONENT; + } if (!RSA._URSA && /arm|mips/i.test(require('os').arch) && !RSA._SLOW_WARN) { console.warn("================================================================"); console.warn(" WARNING"); @@ -84,8 +99,6 @@ function create(deps) { , _forgePublic: undefined }; - options = options || NOBJ; - RSA._internal.generateKeypair(length, exponent, options, function (err, keys) { if (false !== options.jwk || options.thumbprint) { keypair.privateKeyJwk = RSA._internal.exportPrivateJwk(keys); @@ -117,7 +130,11 @@ function create(deps) { }); }; - RSA.import = function (keypair/*, options*/) { + RSA.import = function (options/*, options*/) { + var keypair = options; + if (keypair.key) { + keypair = keypair.key; + } keypair = RSA._internal.import(keypair, { internal: true }); keypair = RSA._internal.importForge(keypair, { internal: true }); //options = options || NOBJ; // ignore diff --git a/package.json b/package.json index db253cb..bd7d3c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rsa-compat", - "version": "1.4.2", + "version": "1.5.0", "description": "RSA utils that work on Windows, Mac, and Linux with or without C compiler", "main": "node.js", "bin": { @@ -39,7 +39,9 @@ }, "homepage": "https://git.coolaj86.com/coolaj86/rsa-compat.js#readme", "dependencies": { - "node-forge": "^0.6.41", + "node-forge": "^0.6.41" + }, + "optionalDependencies": { "ursa-optional": "^0.9.4" }, "trulyOptionalDependencies": { diff --git a/tests/generate-key-new.js b/tests/generate-key-new.js new file mode 100644 index 0000000..ec9e354 --- /dev/null +++ b/tests/generate-key-new.js @@ -0,0 +1,46 @@ +'use strict'; + +var RSA = require('../').RSA; + +RSA.generateKeypair(null, function (err, keys) { + if (!keys || !keys.privateKeyJwk) { + throw new Error("Expected privateKeyJwk, but it is missing"); + } + + if ( + keys.publicKeyJwk + || keys.privateKeyPem + || keys.publicKeyPem + || keys.thumbprint + || keys._ursa + || keys._forge + ) { + console.error(Object.keys(keys)); + throw new Error("Got unexpected keys"); + } + + var options = { + public: true // export public keys + , pem: true // export pems + , jwk: false // export jwks + , internal: true // preserve internal intermediate formats (_ursa, _forge) + //, thumbprint: true // JWK sha256 thumbprint + , bitlen: 2048 + , exp: 65537 + }; + RSA.generateKeypair(options, function (err, keys) { + if ( + (keys.publicKeyJwk && !keys.thumbprint) + || !keys.privateKeyPem + || !keys.publicKeyPem + //|| !keys.thumbprint + || !(keys._ursa || keys._forge) + ) { + console.error(Object.keys(keys)); + throw new Error("Missing expected keys"); + } + + console.log('All is well!'); + }); + +});