372 lines
7.3 KiB
JavaScript
372 lines
7.3 KiB
JavaScript
/*!
|
|
* pk-browser.js - public key algorithms for bcoin
|
|
* Copyright (c) 2016, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var assert = require('assert');
|
|
var BN = require('bn.js');
|
|
var ASN1 = require('../utils/asn1');
|
|
var util = require('../utils/util');
|
|
var co = require('../utils/co');
|
|
var elliptic = require('elliptic');
|
|
var backend = require('./backend');
|
|
var subtle = backend.subtle;
|
|
var dsa, rsa, ecdsa;
|
|
|
|
/*
|
|
* DSA
|
|
*/
|
|
|
|
dsa = {};
|
|
|
|
dsa.verify = function verify(alg, msg, sig, key, params) {
|
|
throw new Error('DSA not implemented.');
|
|
};
|
|
|
|
dsa.verifyAsync = util.promisify(dsa.verify);
|
|
|
|
dsa.sign = function sign(alg, msg, key, params) {
|
|
throw new Error('DSA not implemented.');
|
|
};
|
|
|
|
dsa.signAsync = util.promisify(dsa.sign);
|
|
|
|
/*
|
|
* RSA
|
|
*/
|
|
|
|
rsa = {};
|
|
|
|
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')
|
|
};
|
|
|
|
rsa.verify = function verify(alg, msg, sig, key) {
|
|
var prefix = rsa.prefixes[alg];
|
|
var hash, len, pub;
|
|
var N, e, k, m, em, ok, i;
|
|
|
|
if (!prefix)
|
|
throw new Error('Unknown PKCS prefix.');
|
|
|
|
hash = backend.hash(alg, msg);
|
|
len = prefix.length + hash.length;
|
|
pub = ASN1.parseRSAPublic(key);
|
|
|
|
N = new BN(pub.modulus);
|
|
e = new BN(pub.publicExponent);
|
|
k = Math.ceil(N.bitLength() / 8);
|
|
|
|
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 &= backend.ccmp(em.slice(k - hash.length, k), hash);
|
|
ok &= backend.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(alg, msg, key) {
|
|
var prefix = rsa.prefixes[alg];
|
|
var hash, len, priv;
|
|
var N, D, k, i, em;
|
|
|
|
if (!prefix)
|
|
throw new Error('Unknown PKCS prefix.');
|
|
|
|
hash = backend.hash(alg, msg);
|
|
len = prefix.length + hash.length;
|
|
priv = ASN1.parseRSAPrivate(key);
|
|
|
|
N = new BN(priv.modulus);
|
|
D = new BN(priv.privateExponent);
|
|
k = Math.ceil(N.bitLength() / 8);
|
|
|
|
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.verifyAsync = co(function* verifyAsync(alg, msg, sig, key) {
|
|
var use = ['verify'];
|
|
var name = backend.getHash(alg);
|
|
var pub, data, algo, ckey;
|
|
|
|
if (!name)
|
|
return rsa.verify(alg, msg, sig, key);
|
|
|
|
pub = ASN1.parseRSAPublic(key);
|
|
|
|
data = {
|
|
kty: 'RSA',
|
|
n: toBase64(pub.modulus),
|
|
e: toBase64(pub.publicExponent),
|
|
alg: 'RS256',
|
|
ext: true
|
|
};
|
|
|
|
algo = {
|
|
name: 'RSASSA-PKCS1-v1_5',
|
|
hash: { name: name }
|
|
};
|
|
|
|
ckey = yield subtle.importKey('jwk', data, algo, false, use);
|
|
|
|
algo = {
|
|
name: 'RSASSA-PKCS1-v1_5',
|
|
};
|
|
|
|
return yield subtle.verify(algo, ckey, sig, msg);
|
|
});
|
|
|
|
if (!subtle.verify)
|
|
rsa.verifyAsync = util.promisify(rsa.verify);
|
|
|
|
rsa.signAsync = co(function* signAsync(alg, msg, key) {
|
|
var use = ['sign'];
|
|
var name = backend.getHash(alg);
|
|
var pub, data, algo, ckey;
|
|
|
|
if (!name)
|
|
return rsa.sign(alg, msg, key);
|
|
|
|
pub = ASN1.parseRSAPrivate(key);
|
|
|
|
data = {
|
|
kty: 'RSA',
|
|
n: toBase64(pub.modulus),
|
|
e: toBase64(pub.publicExponent),
|
|
d: toBase64(pub.privateExponent),
|
|
p: toBase64(pub.prime1),
|
|
q: toBase64(pub.prime2),
|
|
dp: toBase64(pub.exponent1),
|
|
dq: toBase64(pub.exponent2),
|
|
qi: toBase64(pub.coefficient),
|
|
alg: 'RS256',
|
|
ext: true
|
|
};
|
|
|
|
algo = {
|
|
name: 'RSASSA-PKCS1-v1_5',
|
|
hash: { name: name }
|
|
};
|
|
|
|
ckey = yield subtle.importKey('jwk', data, algo, false, use);
|
|
|
|
algo = {
|
|
name: 'RSASSA-PKCS1-v1_5',
|
|
};
|
|
|
|
return yield subtle.sign(algo, ckey, msg);
|
|
});
|
|
|
|
if (!subtle.sign)
|
|
rsa.signAsync = util.promisify(rsa.sign);
|
|
|
|
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
|
|
*/
|
|
|
|
ecdsa = {};
|
|
|
|
ecdsa.verify = function verify(curve, alg, msg, key, sig) {
|
|
var ec, hash;
|
|
|
|
assert(curve, 'No curve selected.');
|
|
|
|
ec = elliptic.ec(curve);
|
|
hash = backend.hash(alg, msg);
|
|
|
|
return ec.verify(hash, sig, key);
|
|
};
|
|
|
|
ecdsa.sign = function sign(curve, alg, msg, key) {
|
|
var ec, hash;
|
|
|
|
assert(curve, 'No curve selected.');
|
|
|
|
ec = elliptic.ec(curve);
|
|
hash = backend.hash(alg, msg);
|
|
|
|
return new Buffer(ec.sign(hash, key));
|
|
};
|
|
|
|
ecdsa.verifyAsync = co(function* verifyAsync(curve, alg, msg, sig, key) {
|
|
var use = ['verify'];
|
|
var name = backend.getHash(alg);
|
|
var curveName = getCurve(curve);
|
|
var pub, data, algo, ckey;
|
|
|
|
if (!name || !curveName)
|
|
return ecdsa.verify(curve, alg, msg, sig, key);
|
|
|
|
pub = parseECPublic(key, curve);
|
|
|
|
data = {
|
|
kty: 'EC',
|
|
x: toBase64(pub.x),
|
|
y: toBase64(pub.y),
|
|
ext: true
|
|
};
|
|
|
|
algo = {
|
|
name: 'ECDSA',
|
|
namedCurve: curveName
|
|
};
|
|
|
|
ckey = yield subtle.importKey('jwk', data, algo, false, use);
|
|
|
|
algo = {
|
|
name: 'ECDSA',
|
|
hash: name
|
|
};
|
|
|
|
return yield subtle.verify(algo, ckey, sig, msg);
|
|
});
|
|
|
|
if (!subtle.verify)
|
|
ecdsa.verifyAsync = util.promisify(ecdsa.verify);
|
|
|
|
ecdsa.signAsync = co(function* signAsync(curve, alg, msg, key) {
|
|
var use = ['sign'];
|
|
var name = backend.getHash(alg);
|
|
var curveName = getCurve(curve);
|
|
var algo, ckey;
|
|
|
|
if (!name || !curveName)
|
|
return ecdsa.sign(curve, alg, msg, key);
|
|
|
|
algo = {
|
|
name: 'ECDSA',
|
|
namedCurve: curveName
|
|
};
|
|
|
|
ckey = yield subtle.importKey('raw', key, algo, false, use);
|
|
|
|
algo = {
|
|
name: 'ECDSA',
|
|
hash: name
|
|
};
|
|
|
|
return yield subtle.sign(algo, ckey, msg);
|
|
});
|
|
|
|
if (!subtle.sign)
|
|
ecdsa.signAsync = util.promisify(ecdsa.sign);
|
|
|
|
/*
|
|
* Helpers
|
|
*/
|
|
|
|
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 toBase64(data) {
|
|
var str = data.toString('base64');
|
|
str = str.replace(/\+/g, '-');
|
|
str = str.replace(/\//g, '_');
|
|
str = str.replace(/=+$/, '');
|
|
return str;
|
|
}
|
|
|
|
function getCurve(name) {
|
|
switch (name) {
|
|
case 'p256':
|
|
return 'P-256';
|
|
case 'p384':
|
|
return 'P-384';
|
|
case 'p521':
|
|
return 'P-521';
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function parseECPublic(data, curve) {
|
|
var ec = elliptic.ec(curve).curve;
|
|
var point = ec.decodePoint(data);
|
|
return {
|
|
x: point.toArrayLike(Buffer, 'be', 32),
|
|
y: point.toArrayLike(Buffer, 'be', 32)
|
|
};
|
|
}
|
|
|
|
function ceq(a, b) {
|
|
var r = ~(a ^ b) & 0xff;
|
|
r &= r >>> 4;
|
|
r &= r >>> 2;
|
|
r &= r >>> 1;
|
|
return r === 1;
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
exports.dsa = dsa;
|
|
exports.rsa = rsa;
|
|
exports.ecdsa = ecdsa;
|