299 lines
9.6 KiB
JavaScript
299 lines
9.6 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 = {};
|
|
var DEFAULT_BITLEN = 2048;
|
|
var DEFAULT_EXPONENT = 65537;
|
|
|
|
function create(deps) {
|
|
var crypto = require('crypto');
|
|
|
|
deps = deps || {};
|
|
deps.NOBJ = {};
|
|
deps.RSA = RSA;
|
|
|
|
try {
|
|
RSA._URSA = require('ursa');
|
|
} catch(e) {
|
|
try {
|
|
RSA._URSA = require('ursa-optional');
|
|
} 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 Buffer.from(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 Buffer.from('{"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);
|
|
};
|
|
|
|
// 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");
|
|
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
|
|
};
|
|
|
|
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 (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
|
|
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(
|
|
Buffer.from(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(Buffer.from(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;
|