rsa-compat.js/node.js

278 lines
9.0 KiB
JavaScript

/*!
* rsa-compat
* Copyright(c) 2016 AJ ONeal <aj@daplie.com> https://daplie.com
* Apache-2.0 OR MIT (and hence also MPL 2.0)
*/
'use strict';
try {
require('buffer-v6-polyfill');
} catch(e) {
/* ignore */
}
var RSA = {};
var NOBJ = {};
function create(deps) {
var crypto = require('crypto');
deps = 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._forgeBytesToBuf = function (bytes) {
var forge = require("node-forge");
return new Buffer(forge.util.bytesToHex(bytes), "hex");
};
RSA._internal = require('./lib/node');//.create(deps);
RSA._thumbprintInput = function (n, e) {
// #L147 const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
return new Buffer('{"e":"'+ e + '","kty":"RSA","n":"'+ n +'"}', 'ascii');
};
RSA.thumbprint = function (keypair) {
var publicKeyJwk = RSA.exportPublicJwk(keypair);
if (!publicKeyJwk.e || !publicKeyJwk.n) {
throw new Error("You must provide an RSA jwk with 'e' and 'n' (the public components)");
}
var input = RSA._thumbprintInput(publicKeyJwk.n, publicKeyJwk.e);
var base64Digest = crypto.createHash('sha256').update(input).digest('base64');
return RSA.utils.toWebsafeBase64(base64Digest);
};
RSA.generateKeypair = function (length, exponent, options, cb) {
if (!RSA._URSA && /arm|mips/i.test(require('os').arch) && !RSA._SLOW_WARN) {
console.warn("================================================================");
console.warn(" WARNING");
console.warn("================================================================");
console.warn("");
console.warn("WARNING: You are generating an RSA key using pure JavaScript on");
console.warn(" a VERY SLOW cpu. This could take DOZENS of minutes!");
console.warn("");
console.warn(" We recommend installing a C compiler and 'ursa'");
console.warn("");
console.warn("EXAMPLE:");
console.warn("");
console.warn(" sudo apt-get install build-essential && npm install ursa");
console.warn("");
console.warn("================================================================");
RSA._SLOW_WARN = true;
}
var keypair = {
privateKeyPem: undefined
, publicKeyPem: undefined
, privateKeyJwk: undefined
, publicKeyJwk: undefined
, _ursa: undefined
, _ursaPublic: undefined
, _forge: undefined
, _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);
if (options.public) {
keypair.publicKeyJwk = RSA._internal.exportPublicJwk(keys);
}
}
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);
}
if (options.internal) {
//keypair._ursa = undefined;
//keypair._forge = undefined;
keypair._ursa = keys._ursa;
keypair._forge = keys._forge;
}
cb(null, keypair);
return;
});
};
RSA.import = function (keypair/*, options*/) {
keypair = RSA._internal.import(keypair, { internal: true });
keypair = RSA._internal.importForge(keypair, { internal: true });
//options = options || NOBJ; // ignore
if (keypair.privateKeyJwk || keypair.privateKeyPem || keypair._ursa || 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._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 bufF = forge.util.createBuffer(sha256Buf.toString('binary'), 'binary');
var md = {
algorithm: 'sha256'
, blockLength: 64
, digestLength: 20
, digest: function () {
return bufF;
}
};
var sigF = keypair._forge.sign(md);
var sig64 = RSA.utils.toWebsafeBase64(
new Buffer(forge.util.bytesToHex(sigF), "hex").toString('base64')
);
return sig64;
};
RSA.signJws = RSA.generateJws = RSA.generateSignatureJws = RSA.generateSignatureJwk =
function (keypair, header, protect, payload) {
// old (keypair, payload, nonce)
var nonce;
keypair = RSA._internal.import(keypair);
keypair = RSA._internal.importForge(keypair);
keypair.publicKeyJwk = RSA.exportPublicJwk(keypair);
if ('string' === typeof protect || ('undefined' === typeof protect && 'undefined' === typeof payload)) {
console.warn("deprecation notice: new signature for signJws(keypair, header, protect, payload)");
// old API
payload = header;
nonce = protect;
protect = undefined;
header = {
alg: "RS256"
, jwk: keypair.publicKeyJwk
};
protect = { nonce: nonce };
}
// Compute JWS signature
var protectedHeader = "";
if (protect) {
protectedHeader = JSON.stringify(protect); // { alg: prot.alg, nonce: prot.nonce, url: prot.url });
}
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: header
, protected: protected64
, payload: payload64
, signature: sig64
};
};
//
// Generate CSR
//
RSA._generateCsrForge = function (keypair, names) {
var forge = require('node-forge');
keypair = RSA._internal.importForge(keypair);
// Create and sign the CSR
var csr = forge.pki.createCertificationRequest();
csr.publicKey = keypair._forgePublic;
// TODO should the commonName be shift()ed off so that it isn't also in altNames?
// http://stackoverflow.com/questions/5935369/ssl-how-do-common-names-cn-and-subject-alternative-names-san-work-together
csr.setSubject([{ name: 'commonName', value: names[0] }]);
var sans = names.map(function (name) {
return { type: 2, value: name };
});
csr.setAttributes([{
name: 'extensionRequest',
extensions: [{name: 'subjectAltName', altNames: sans}]
}]);
// TODO wrap with node crypto (as done for signature)
csr.sign(keypair._forge, forge.md.sha256.create());
return csr;
};
RSA.generateCsrAsn1 = function (keypair, names) {
var forge = require('node-forge');
var csr = RSA._generateCsrForge(keypair, names);
var asn1 = forge.pki.certificationRequestToAsn1(csr);
return RSA.utils._forgeBytesToBuf(asn1);
};
RSA.generateCsrPem = function (keypair, names) {
var forge = require('node-forge');
var csr = RSA._generateCsrForge(keypair, names);
return forge.pki.certificationRequestToPem(csr);
};
RSA.generateCsrDer = function (keypair, names) {
var forge = require('node-forge');
var csr = RSA._generateCsrForge(keypair, names);
var asn1 = forge.pki.certificationRequestToAsn1(csr);
var der = forge.asn1.toDer(asn1);
return RSA.utils._forgeBytesToBuf(der);
};
RSA.generateCsrDerWeb64 =RSA.generateCsrWeb64 = function (keypair, names) {
var buf = RSA.generateCsrDer(keypair, names);
var b64 = buf.toString('base64');
var web64 = RSA.utils.toWebsafeBase64(b64);
return web64;
};
RSA.exportPrivateKey = RSA._internal.exportPrivatePem;
RSA.exportPublicKey = RSA._internal.exportPublicPem;
RSA.exportPrivatePem = RSA._internal.exportPrivatePem;
RSA.exportPublicPem = RSA._internal.exportPublicPem;
RSA.exportPrivateJwk = RSA._internal.exportPrivateJwk;
RSA.exportPublicJwk = RSA._internal.exportPublicJwk;
return RSA;
}
module.exports.RSA = create(/*require('./lib/node')*/);
//module.exports.RSA.create = create;