diff --git a/index.js b/index.js new file mode 100644 index 0000000..3b08495 --- /dev/null +++ b/index.js @@ -0,0 +1,134 @@ +;(function (exports) { +'use strict'; + +var PromiseA; +try { + /*global Promise*/ + PromiseA = Promise; +} catch(e) { + PromiseA = require('bluebird'); +} + + +// https://stackoverflow.com/questions/40314257/export-webcrypto-key-to-pem-format +function derToPem(keydata, pemName, privacy){ + var keydataS = arrayBufferToString(keydata); + var keydataB64 = window.btoa(keydataS); + var keydataB64Pem = formatAsPem(keydataB64, pemName, privacy); + return keydataB64Pem; +} + +function arrayBufferToString( buffer ) { + var binary = []; + var bytes = new Uint8Array( buffer ); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary.push(String.fromCharCode( bytes[ i ] )); + } + return binary.join(''); +} + + +function formatAsPem(str, pemName, privacy) { + var privstr = (privacy ? privacy + ' ' : ''); + var finalString = '-----BEGIN ' + pemName + ' ' + privstr + 'KEY-----\n'; + + while (str.length > 0) { + finalString += str.substring(0, 64) + '\n'; + str = str.substring(64); + } + + finalString = finalString + '-----END ' + pemName + ' ' + privstr + 'KEY-----'; + + return finalString; +} + +var Keypairs = exports.Keypairs = { + generate: function(opts) { + if (!opts) { opts = {}; } + if (!opts.type) { opts.type = 'EC'; } + + var supported = [ 'EC', 'RSA' ]; + if (-1 === supported.indexOf(opts.type)) { + return PromiseA.reject(new Error("'" + opts.type + "' not implemented. Try one of " + supported.join(', '))); + } + + if ('EC' === opts.type) { + return Keypairs._generateEc(opts); + } + if ('RSA' === opts.type) { + return Keypairs._generateRsa(opts); + } + } +, _generateEc: function (opts) { + if (!opts.namedCurve) { opts.namedCurve = 'P-256'; } + if ('P-256' !== opts.namedCurve) { + console.warn("'" + opts.namedCurve + "' is not supported, but it _might_ happen to work anyway."); + } + + // https://github.com/diafygi/webcrypto-examples#ecdsa---generatekey + var extractable = true; + + return window.crypto.subtle.generateKey( + { name: "ECDSA", namedCurve: opts.namedCurve } + , extractable + , [ 'sign', 'verify' ] + ).then(function (result) { + return window.crypto.subtle.exportKey( + "jwk" + , result.privateKey + ).then(function (jwk) { + return window.crypto.subtle.exportKey( + "pkcs8" + , result.privateKey + ).then(function (keydata) { + return { + type: 'EC' + , privateJwk: jwk + , privatePem: derToPem(keydata, 'EC', 'PRIVATE') + }; + }); + }); + }); + } +, _generateRsa: function (opts) { + if (!opts.bitlength) { opts.bitlength = 2048; } + if (-1 === [ 2048, 4096 ].indexOf(opts.bitlength)) { + return PromiseA.reject("opts.bitlength = (" + typeof opts.bitlength + ") " + opts.bitlength + ": Are you serious?"); + } + + // https://github.com/diafygi/webcrypto-examples#rsa---generatekey + var extractable = true; + + return window.crypto.subtle.generateKey( + { name: "RSASSA-PKCS1-v1_5" + , modulusLength: opts.bitlength + , publicExponent: new Uint8Array([0x01, 0x00, 0x01]) + , hash: { name: "SHA-256" } + } + , extractable + , [ 'sign', 'verify' ] + ).then(function (result) { + return window.crypto.subtle.exportKey( + "jwk" + , result.privateKey + ).then(function (jwk) { + return window.crypto.subtle.exportKey( + "pkcs8" + , result.privateKey + ).then(function (keydata) { + return { + type: 'RSA' + , privateJwk: jwk + , privatePem: derToPem(keydata, 'RSA', 'PRIVATE') + }; + }); + }); + }); + } +}; + +}('undefined' === typeof module ? window : module.exports)); + +// How we might use this +// var Keypairs = require('keypairs').Keypairs diff --git a/package.json b/package.json new file mode 100644 index 0000000..7f637fe --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "keypairs", + "version": "1.0.0", + "description": "Interchangeably use RSA & ECDSA with PEM and JWK for Signing, Verifying, CSR generation and JOSE. Ugh... that was a mouthful.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://git.coolaj86.com/coolaj86/keypairs.js" + }, + "keywords": [ + "EC", + "RSA", + "ECDSA", + "PEM", + "JWK", + "CSR", + "JOSE" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "(MIT OR Apache-2.0)" +}