From 41cc642a32367c42f342757087f09273336a14d5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 29 Jul 2016 10:13:51 -0700 Subject: [PATCH] bip70: javascript rsa implementation. --- lib/bcoin/bip70/asn1.js | 17 ++- lib/bcoin/bip70/pk.js | 261 ++++++++++++++++++++++++++++++++++++++++ lib/bcoin/bip70/x509.js | 119 +++++++++--------- lib/bcoin/workers.js | 2 +- 4 files changed, 331 insertions(+), 68 deletions(-) create mode 100644 lib/bcoin/bip70/pk.js diff --git a/lib/bcoin/bip70/asn1.js b/lib/bcoin/bip70/asn1.js index 39f2ee07..01f7d814 100644 --- a/lib/bcoin/bip70/asn1.js +++ b/lib/bcoin/bip70/asn1.js @@ -348,7 +348,7 @@ asn1.parseRSAPrivate = function parseRSAPrivate(data) { var p = BufferReader(data); p = BufferReader(asn1.parseSeq(p)); return { - version: asn1.parseExplicitInt(p, 0, true), + version: asn1.parseInt(p, true), modulus: asn1.parseInt(p), publicExponent: asn1.parseInt(p), privateExponent: asn1.parseInt(p), @@ -404,15 +404,28 @@ asn1.fromPEM = function fromPEM(pem) { var chunks = asn1.parsePEM(pem); var body = chunks[0]; var extra = chunks[1]; - var params; + var params, alg; if (extra) { if (extra.tag.indexOf('PARAMETERS') !== -1) params = extra.data; } + switch (body.type) { + case 'dsa': + alg = 'dsa'; + break; + case 'rsa': + alg = 'rsa'; + break; + case 'ec': + alg = 'ecdsa'; + break; + } + return { type: body.type, + alg: alg, data: body.data, params: params }; diff --git a/lib/bcoin/bip70/pk.js b/lib/bcoin/bip70/pk.js new file mode 100644 index 00000000..0c2b78cb --- /dev/null +++ b/lib/bcoin/bip70/pk.js @@ -0,0 +1,261 @@ +/*! + * x509.js - x509 handling for bcoin + * Copyright (c) 2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var bn = require('bn.js'); +var asn1 = require('./asn1'); +var elliptic = require('elliptic'); +var utils = require('../utils'); + +var crypto; + +try { + crypto = require('cryp' + 'to'); +} catch (e) { + ; +} + +var pk = exports; +var rsa = {}; +var ecdsa = {}; +var native = {}; + +rsa.prefixes = { + md5: new Buffer('3020300c06082a864886f70d020505000410', 'hex'), + sha1: new Buffer('3021300906052b0e03021a05000414', 'hex'), + sha224: new Buffer('302d300d06096086480165030402040500041c', 'hex'), + sha256: new Buffer('3031300d060960864801650304020105000420', 'hex'), + sha384: new Buffer('3041300d060960864801650304020205000430', 'hex'), + sha512: new Buffer('3051300d060960864801650304020305000440', 'hex'), + md5sha1: new Buffer(0), + ripemd160: new Buffer('30203008060628cf060300310414', 'hex') +}; + +// Ported from: +// https://github.com/golang/go/blob/master/src/crypto/rsa/pkcs1v15.go + +rsa.verify = function verify(hashAlg, msg, sig, key) { + var hash = utils.hash(hashAlg, msg); + var prefix = rsa.prefixes[hashAlg]; + var len = prefix.length + hash.length; + var pub = asn1.parseRSAPublic(key); + var N = new bn(pub.modulus); + var e = new bn(pub.publicExponent); + var k = Math.ceil(N.bitLength() / 8); + var m, em, ok, i; + + if (k < len + 11) + throw new Error('Message too long.'); + + m = rsa.encrypt(N, e, sig); + em = leftpad(m, k); + + ok = ceq(em[0], 0x00); + ok &= ceq(em[1], 0x01); + ok &= utils.ccmp(em.slice(k - hash.length, k), hash); + ok &= utils.ccmp(em.slice(k - len, k - hash.length), prefix); + ok &= ceq(em[k - len - 1], 0x00); + + for (i = 2; i < k - len - 1; i++) + ok &= ceq(em[i], 0xff); + + return ok === 1; +}; + +rsa.sign = function sign(hashAlg, msg, key) { + var hash = utils.hash(hashAlg, msg); + var prefix = rsa.prefixes[hashAlg]; + var len = prefix.length + hash.length; + var priv = asn1.parseRSAPrivate(key); + var N = new bn(priv.modulus); + var D = new bn(priv.privateExponent); + var k = Math.ceil(N.bitLength() / 8); + var i, em; + + if (k < len + 11) + throw new Error('Message too long.'); + + em = new Buffer(k); + em.fill(0); + + em[1] = 0x01; + for (i = 2; i < k - len - 1; i++) + em[i] = 0xff; + + prefix.copy(em, k - len); + hash.copy(em, k - hash.length); + + return rsa.decrypt(N, D, em); +}; + +rsa.decrypt = function decrypt(N, D, m) { + var c = new bn(m); + + if (c.cmp(N) > 0) + throw new Error('Cannot decrypt.'); + + return c + .toRed(bn.red(N)) + .redPow(D) + .fromRed() + .toArrayLike(Buffer, 'be'); +}; + +rsa.encrypt = function encrypt(N, e, m) { + return new bn(m) + .toRed(bn.red(N)) + .redPow(e) + .fromRed() + .toArrayLike(Buffer, 'be'); +}; + +ecdsa.verify = function verify(curve, msg, hashAlg, key, sig) { + var hash = utils.hash(hashAlg, msg); + var ec = elliptic.ec(curve); + return ec.verify(hash, sig, key); +}; + +ecdsa.sign = function sign(curve, msg, hashAlg, key) { + var hash = utils.hash(hashAlg, msg); + var ec = elliptic.ec(curve); + return ec.sign(hash, key); +}; + +native.verify = function verify(alg, hash, msg, sig, key) { + var algo, verify; + + if (!crypto) + return false; + + algo = normalizeAlg(alg, hash); + verify = crypto.createVerify(algo); + verify.update(msg); + + return verify.verify(key, sig); +}; + +native.sign = function _sign(alg, hash, msg, key) { + var algo, sig; + + if (!crypto) + return false; + + algo = normalizeAlg(alg, hash); + sig = crypto.createSign(algo); + sig.update(msg); + return sig.sign(key); +}; + +pk.pemTag = { + dsa: 'DSA', + rsa: 'RSA', + ecdsa: 'EC' +}; + +pk.toPEM = function toPEM(key, type) { + var tag = pk.pemTag[key.alg]; + var pem = asn1.toPEM(key.data, tag, type); + + // Key parameters, usually present + // if selecting an EC curve. + if (key.params) + pem += asn1.toPEM(key.params, tag, 'parameters'); + + return pem; +}; + +pk._verify = function verify(hash, msg, sig, key) { + var pem; + switch (key.alg) { + case 'dsa': + pem = pk.toPEM(key, 'public key'); + return native.verify(key.alg, hash, msg, sig, pem); + case 'rsa': + if (crypto) { + pem = pk.toPEM(key, 'public key'); + return native.verify(key.alg, hash, msg, sig, pem); + } + return rsa.verify(hash, msg, sig, key.data); + case 'ecdsa': + if (!key.curve) + return false; + return ecdsa.verify(key.curve, hash, msg, sig, key.data); + default: + throw new Error('Unsupported algorithm.'); + } +}; + +pk.verify = function verify(hash, msg, sig, key) { + try { + return pk._verify(hash, msg, sig, key); + } catch (e) { + return false; + } +}; + +pk.sign = function sign(hash, msg, key) { + var pem; + switch (key.alg) { + case 'dsa': + pem = pk.toPEM(key, 'private key'); + return native.sign(key.alg, hash, msg, pem); + case 'rsa': + if (crypto) { + pem = pk.toPEM(key, 'private key'); + return native.sign(key.alg, hash, msg, pem); + } + return rsa.sign(hash, msg, key.data); + case 'ecdsa': + if (!key.curve) + return false; + return ecdsa.sign(key.curve, hash, msg, key.data); + default: + throw new Error('Unsupported algorithm.'); + } +}; + +function ceq(a, b) { + var r = ~(a ^ b) & 0xff; + r &= r >>> 4; + r &= r >>> 2; + r &= r >>> 1; + return r === 1; +} + +function leftpad(input, size) { + var n = input.length; + var out; + + if (n > size) + n = size; + + out = new Buffer(size); + out.fill(0); + + input.copy(out, out.length - n); + + return out; +} + +function normalizeAlg(alg, hash) { + var name = alg.toUpperCase() + '-' + hash.toUpperCase(); + + switch (name) { + case 'ECDSA-SHA1': + name = 'ecdsa-with-SHA1'; + break; + case 'ECDSA-SHA256': + name = 'ecdsa-with-SHA256'; + break; + } + + return name; +} + +pk.rsa = rsa; +pk.ecdsa = ecdsa; +pk.native = native; diff --git a/lib/bcoin/bip70/x509.js b/lib/bcoin/bip70/x509.js index 973cca92..18754e93 100644 --- a/lib/bcoin/bip70/x509.js +++ b/lib/bcoin/bip70/x509.js @@ -7,9 +7,9 @@ 'use strict'; var assert = require('assert'); -var crypto = require('crypto'); var asn1 = require('./asn1'); var utils = require('../utils'); +var pk = require('./pk'); var x509 = exports; x509.getSubjectOID = function getSubjectOID(cert, oid) { @@ -112,10 +112,11 @@ x509.oid = { '1.2.840.10045.4.3.4' : { key: 'ecdsa', hash: 'sha512' } }; -x509.pemTag = { - dsa: 'DSA', - rsa: 'RSA', - ecdsa: 'EC' +x509.curves = { + '1.3.132.0.33': 'p224', + '1.2.840.10045.3.1.7': 'p256', + '1.3.132.0.34': 'p384', + '1.3.132.0.35': 'p521' }; x509.getKeyAlgorithm = function getKeyAlgorithm(cert) { @@ -128,6 +129,21 @@ x509.getSigAlgorithm = function getSigAlgorithm(cert) { return x509.oid[alg]; }; +x509.getCurve = function getCurve(params) { + var oid; + + if (!params) + return; + + try { + oid = asn1.parseOID(params); + } catch (e) { + return; + } + + return x509.curves[oid]; +}; + x509.parse = function parse(der) { try { return asn1.parseCert(der); @@ -138,23 +154,23 @@ x509.parse = function parse(der) { x509.getPublicKey = function getPublicKey(cert) { var alg = x509.getKeyAlgorithm(cert); - var key, params, pem, tag; + var key, params, curve; if (!alg) return; key = cert.tbs.pubkey.pubkey; params = cert.tbs.pubkey.alg.params; - tag = x509.pemTag[alg.key]; - pem = asn1.toPEM(key, tag, 'public key'); + if (alg.key === 'ecdsa') + curve = x509.getCurve(params); - // Key parameters, usually present - // if selecting an EC curve. - if (params) - pem += asn1.toPEM(params, tag, 'parameters'); - - return pem; + return { + alg: alg.key, + data: key, + params: params, + curve: curve + }; }; x509.verifyTime = function verifyTime(cert) { @@ -164,26 +180,39 @@ x509.verifyTime = function verifyTime(cert) { }; x509.signSubject = function signSubject(hash, msg, key, chain) { - var cert, alg, tag; + var cert, pub, curve; assert(chain.length !== 0, 'No chain available.'); cert = x509.parse(chain[0]); assert(cert, 'Could not parse certificate.'); - alg = x509.getKeyAlgorithm(cert); - assert(alg, 'Certificate uses an unknown algorithm.'); - - if (Buffer.isBuffer(key)) { - tag = x509.pemTag[alg.key]; - key = asn1.toPEM(key, tag, 'private key'); + if (typeof key === 'string') { + key = asn1.fromPEM(key); + if (key.alg === 'ecdsa') + curve = x509.getCurve(key.params); + key = { + alg: key.alg, + data: key.data, + params: key.params, + curve: curve + }; + } else { + pub = x509.getPublicKey(cert); + assert(pub, 'Certificate uses an unknown algorithm.'); + key = { + alg: pub.alg, + data: key, + params: pub.params, + curve: pub.curve + }; } - return x509.sign(alg.key, hash, msg, key); + return pk.sign(hash, msg, key); }; x509.verifySubject = function verifySubject(hash, msg, sig, chain) { - var cert, key, alg; + var cert, key; if (chain.length === 0) return false; @@ -198,12 +227,7 @@ x509.verifySubject = function verifySubject(hash, msg, sig, chain) { if (!key) return false; - alg = x509.getKeyAlgorithm(cert); - - if (!alg) - return false; - - return x509.verify(alg.key, hash, msg, sig, key); + return pk.verify(hash, msg, sig, key); }; x509.verifyChain = function verifyChain(chain) { @@ -241,7 +265,7 @@ x509.verifyChain = function verifyChain(chain) { if (!key) return false; - if (!x509.verify(alg.key, alg.hash, msg, sig, key)) + if (!pk.verify(alg.hash, msg, sig, key)) return false; } @@ -266,41 +290,6 @@ x509.verifyChain = function verifyChain(chain) { return false; }; -x509.normalizeAlg = function normalizeAlg(alg, hash) { - var name = alg.toUpperCase() + '-' + hash.toUpperCase(); - - switch (name) { - case 'ECDSA-SHA1': - name = 'ecdsa-with-SHA1'; - break; - case 'ECDSA-SHA256': - name = 'ecdsa-with-SHA256'; - break; - } - - return name; -}; - -x509.verify = function verify(alg, hash, msg, sig, key) { - var algo = x509.normalizeAlg(alg, hash); - var verify; - - try { - verify = crypto.createVerify(algo); - verify.update(msg); - return verify.verify(key, sig); - } catch (e) { - return false; - } -}; - -x509.sign = function sign(alg, hash, msg, key) { - var algo = x509.normalizeAlg(alg, hash); - var sig = crypto.createSign(algo); - sig.update(msg); - return sig.sign(key); -}; - x509.asn1 = asn1; function isHash(data) { diff --git a/lib/bcoin/workers.js b/lib/bcoin/workers.js index a9aa8c9c..26631438 100644 --- a/lib/bcoin/workers.js +++ b/lib/bcoin/workers.js @@ -404,7 +404,7 @@ Worker.prototype._init = function _init() { }); this._bind(); -} +}; /** * Initialize worker. Bind to more events.