elliptic/curves.js uses native code for genkey and sign/verify; sign/verify use async/await

This commit is contained in:
Mahrud Sayrafi 2018-01-04 07:58:15 -08:00 committed by Sanjana Rajan
parent c443988ec4
commit 6886cd648a
4 changed files with 278 additions and 48 deletions

View File

@ -34,10 +34,6 @@ module.exports = function(grunt) {
browser_capabilities = JSON.parse(process.env.SELENIUM_BROWSER_CAPABILITIES); browser_capabilities = JSON.parse(process.env.SELENIUM_BROWSER_CAPABILITIES);
} }
var getSauceKey = function getSaucekey () {
return '60ffb656-2346-4b77-81f3-bc435ff4c103';
};
// Project configuration. // Project configuration.
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
@ -53,8 +49,18 @@ module.exports = function(grunt) {
external: [ 'crypto', 'buffer', 'node-localstorage', 'node-fetch' ], external: [ 'crypto', 'buffer', 'node-localstorage', 'node-fetch' ],
transform: [ transform: [
["babelify", { ["babelify", {
plugins: ["transform-async-to-generator",
"syntax-async-functions",
"transform-regenerator",
"transform-runtime"],
ignore: ['*.min.js'], ignore: ['*.min.js'],
presets: ["es2015"] presets: [
["babel-preset-env", {
"targets": {
"node": "current"
}
}]
]
}] }]
], ],
plugin: [ 'browserify-derequire' ] plugin: [ 'browserify-derequire' ]
@ -72,8 +78,18 @@ module.exports = function(grunt) {
external: [ 'crypto', 'buffer', 'node-localstorage', 'node-fetch' ], external: [ 'crypto', 'buffer', 'node-localstorage', 'node-fetch' ],
transform: [ transform: [
["babelify", { ["babelify", {
plugins: ["transform-async-to-generator",
"syntax-async-functions",
"transform-regenerator",
"transform-runtime"],
ignore: ['*.min.js'], ignore: ['*.min.js'],
presets: ["es2015"] presets: [
["babel-preset-env", {
"targets": {
"node": "current"
}
}]
]
}] }]
], ],
plugin: [ 'browserify-derequire' ] plugin: [ 'browserify-derequire' ]
@ -226,7 +242,7 @@ module.exports = function(grunt) {
all: { all: {
options: { options: {
username: 'openpgpjs', username: 'openpgpjs',
key: getSauceKey, key: '60ffb656-2346-4b77-81f3-bc435ff4c103',
urls: ['http://127.0.0.1:3000/test/unittests.html'], urls: ['http://127.0.0.1:3000/test/unittests.html'],
build: process.env.TRAVIS_BUILD_ID, build: process.env.TRAVIS_BUILD_ID,
testname: 'Sauce Unit Test for openpgpjs', testname: 'Sauce Unit Test for openpgpjs',

View File

@ -30,53 +30,76 @@
import {ec as EC} from 'elliptic'; import {ec as EC} from 'elliptic';
import {KeyPair} from './key.js'; import {KeyPair} from './key.js';
import BigInteger from '../jsbn.js'; import BigInteger from '../jsbn.js';
import config from '../../../config';
import enums from '../../../enums.js'; import enums from '../../../enums.js';
import util from '../../../util.js'; import util from '../../../util.js';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
var webCurves = [], nodeCurves = [];
if (webCrypto && config.use_native) {
// see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API/Supported_algorithms
webCurves = ['P-256', 'P-384', 'P-521'];
} else if (nodeCrypto && config.use_native) {
// FIXME make sure the name translations are correct
nodeCurves = nodeCrypto.getCurves();
}
const curves = { const curves = {
p256: { p256: {
oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]), oid: util.bin2str([0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]),
curveName: 'P-256', namedCurve: 'P-256',
opensslCurve: 'prime256v1',
hashName: 'SHA-256', hashName: 'SHA-256',
hash: enums.hash.sha256, hash: enums.hash.sha256,
cipher: enums.symmetric.aes128, cipher: enums.symmetric.aes128,
nist: true node: nodeCurves.includes('prime256v1'),
web: webCurves.includes('P-256')
}, },
p384: { p384: {
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]), oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x22]),
curveName: 'P-384', namedCurve: 'P-384',
opensslCurve: 'secp384r1', // FIXME
hashName: 'SHA-384', hashName: 'SHA-384',
hash: enums.hash.sha384, hash: enums.hash.sha384,
cipher: enums.symmetric.aes192, cipher: enums.symmetric.aes192,
nist: true node: nodeCurves.includes('secp384r1'), // FIXME
web: webCurves.includes('P-384')
}, },
p521: { p521: {
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]), oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x23]),
curveName: 'P-521', namedCurve: 'P-521',
opensslCurve: 'secp521r1', // FIXME
hashName: 'SHA-512', hashName: 'SHA-512',
hash: enums.hash.sha512, hash: enums.hash.sha512,
cipher: enums.symmetric.aes256, cipher: enums.symmetric.aes256,
nist: true node: nodeCurves.includes('secp521r1'), // FIXME
web: webCurves.includes('P-521')
}, },
secp256k1: { secp256k1: {
oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]), oid: util.bin2str([0x2B, 0x81, 0x04, 0x00, 0x0A]),
curveName: 'SECP-256K1', namedCurve: 'SECP-256K1',
opensslCurve: 'secp256k1',
hashName: 'SHA-256', hashName: 'SHA-256',
hash: enums.hash.sha256, hash: enums.hash.sha256,
cipher: enums.symmetric.aes128, cipher: enums.symmetric.aes128,
nist: false node: nodeCurves.includes('secp256k1'),
web: false
} }
}; };
function Curve(name, {oid, hash, cipher, curveName, hashName, nist}) { function Curve(name, {oid, hash, cipher, namedCurve, opensslCurve, hashName, node, web}) {
this.curve = new EC(name); this.curve = new EC(name);
this.name = name; this.name = name;
this.oid = oid; this.oid = oid;
this.hash = hash; this.hash = hash;
this.cipher = cipher; this.cipher = cipher;
this.curveName= curveName; this.namedCurve= namedCurve;
this.opensslCurve = opensslCurve;
this.hashName = hashName; this.hashName = hashName;
this.nist = nist; this.node = node;
this.web = web;
} }
Curve.prototype.keyFromPrivate = function (priv) { Curve.prototype.keyFromPrivate = function (priv) {
@ -88,25 +111,26 @@ Curve.prototype.keyFromPublic = function (pub) {
}; };
Curve.prototype.genKeyPair = function () { Curve.prototype.genKeyPair = function () {
var r = this.curve.genKeyPair(); var keyPair;
return new KeyPair(this.curve, { if (webCrypto && config.use_native && this.web) {
pub: r.getPublic().encode(), keyPair = webGenKeyPair(this.namedCurve);
priv: r.getPrivate().toArray() } else if (nodeCrypto && config.use_native && this.node) {
}); keyPair = nodeGenKeyPair(this.opensslCurve);
} else {
var r = this.curve.genKeyPair();
keyPair = {
pub: r.getPublic().encode(),
priv: r.getPrivate().toArray()
};
}
return new KeyPair(this.curve, keyPair);
}; };
function get(oid_or_name) { function get(oid_or_name) {
for (var name in curves) { for (var name in curves) {
if (curves[name].oid === oid_or_name || name === oid_or_name) { if (curves[name].oid === oid_or_name || name === oid_or_name) {
return new Curve(name, { return new Curve(name, curves[name]);
oid: curves[name].oid,
hash: curves[name].hash,
cipher: curves[name].cipher,
curveName: curves[name].curveName,
hashName: curves[name].hashName,
nist: curves[name].nist
});
} }
} }
throw new Error('Not valid curve'); throw new Error('Not valid curve');
@ -131,3 +155,44 @@ module.exports = {
generate: generate, generate: generate,
get: get get: get
}; };
//////////////////////////
// //
// Helper functions //
// //
//////////////////////////
function webGenKeyPair(namedCurve) {
return webCrypto.generateKey(
{
name: "ECDSA",
// FIXME
// name: "ECDH",
namedCurve: namedCurve, // "P-256", "P-384", or "P-521"
},
// FIXME
false, // whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] // can be any combination of "sign" and "verify"
// FIXME
// ["deriveKey", "deriveBits"] // can be any combination of "deriveKey" and "deriveBits"
).then(function(key){
return {
pub: key.publicKey.encode(), // FIXME encoding
priv: key.privateKey.toArray() // FIXME encoding
};
}).catch(function(err){
throw new Error(err);
});
}
function nodeGenKeyPair(opensslCurve) {
// TODO turn this into a promise
var ecc = nodeCrypto.createECDH(opensslCurve);
ecc.generateKeys();
return {
pub: ecc.getPrivateKey().toJSON().data,
priv: ecc.getPublicKey().toJSON().data
};
}

View File

@ -25,22 +25,44 @@
'use strict'; 'use strict';
import asn1 from 'asn1';
import jwk2pem from 'jwk-to-pem';
import curves from './curves.js'; import curves from './curves.js';
import BigInteger from '../jsbn.js'; import BigInteger from '../jsbn.js';
import config from '../../../config';
import enums from '../../../enums.js';
import util from '../../../util.js';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
var ECDSASignature = asn1.define('ecdsa-sig', function() {
return this.seq().obj(
this.key('r').int(), // FIXME int or BN?
this.key('s').int() // FIXME int or BN?
);
});
/** /**
* Sign a message using the provided key * Sign a message using the provided key
* @param {String} oid Elliptic curve for the key * @param {String} oid Elliptic curve for the key
* @param {BigInteger} Q Public key used to sign
* @param {enums.hash} hash_algo Hash algorithm used to sign * @param {enums.hash} hash_algo Hash algorithm used to sign
* @param {Uint8Array} m Message to sign * @param {Uint8Array} m Message to sign
* @param {BigInteger} d Private key used to sign * @param {BigInteger} d Private key used to sign
* @return {{r: BigInteger, s: BigInteger}} Signature of the message * @return {{r: BigInteger, s: BigInteger}} Signature of the message
*/ */
function sign(oid, hash_algo, m, d) { function sign(oid, hash_algo, m, d) {
var signature;
const curve = curves.get(oid); const curve = curves.get(oid);
const key = curve.keyFromPrivate(d.toByteArray()); if (webCrypto && config.use_native && curve.web) {
const signature = key.sign(m, hash_algo); signature = webSign(curve, hash_algo, m, d);
} else if (nodeCrypto && config.use_native && curve.node) {
signature = nodeSign(curve, hash_algo, m, d);
} else {
const key = curve.keyFromPrivate(d.toByteArray());
signature = key.sign(m, hash_algo);
}
return { return {
r: new BigInteger(signature.r), r: new BigInteger(signature.r),
s: new BigInteger(signature.s) s: new BigInteger(signature.s)
@ -53,15 +75,141 @@ function sign(oid, hash_algo, m, d) {
* @param {enums.hash} hash_algo Hash algorithm used in the signature * @param {enums.hash} hash_algo Hash algorithm used in the signature
* @param {{r: BigInteger, s: BigInteger}} signature Signature to verify * @param {{r: BigInteger, s: BigInteger}} signature Signature to verify
* @param {Uint8Array} m Message to verify * @param {Uint8Array} m Message to verify
* @param {BigInteger} Q Public key used to verify the message * @param {BigInteger} Q Public key used to verify the message
* @return {Boolean}
*/ */
function verify(oid, hash_algo, signature, m, Q) { function verify(oid, hash_algo, signature, m, Q) {
const curve = curves.get(oid); const curve = curves.get(oid);
const key = curve.keyFromPublic(Q.toByteArray()); if (webCrypto && config.use_native && curve.web) {
return key.verify(m, {r: signature.r.toByteArray(), s: signature.s.toByteArray()}, hash_algo); return webVerify(curve, hash_algo, signature, m, Q);
} else if (nodeCrypto && config.use_native && curve.node) {
return nodeVerify(curve, hash_algo, signature, m, Q);
} else {
const key = curve.keyFromPublic(Q.toByteArray());
return key.verify(m, {r: signature.r.toByteArray(), s: signature.s.toByteArray()}, hash_algo);
}
} }
module.exports = { module.exports = {
sign: sign, sign: sign,
verify: verify verify: verify
}; };
//////////////////////////
// //
// Helper functions //
// //
//////////////////////////
async function webSign(curve, hash_algo, m, d) {
const publicKey = curve.keyFromPrivate(d).getPublic();
const privateKey = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": curve.namedCurve,
"x": publicKey.getX().toBuffer().base64Slice(),
"y": publicKey.getY().toBuffer().base64Slice(),
"d": d.toBuffer().base64Slice(),
"use": "sig",
"kid": "ECDSA Private Key"
},
{
"name": "ECDSA",
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
false,
["sign"]
);
try {
return await webCrypto.sign(
{
"name": 'ECDSA',
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
privateKey,
m
);
} catch(err) {
throw new Error(err);
}
}
async function webVerify(curve, hash_algo, signature, m, Q) {
const publicKey = await webCrypto.importKey(
"jwk",
{
"kty": "EC",
"crv": curve.namedCurve,
"x": Q.getX().toBuffer().base64Slice(),
"y": Q.getY().toBuffer().base64Slice(),
"use": "sig",
"kid": "ECDSA Public Key"
},
{
"name": "ECDSA",
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
false,
["verify"]
);
try {
return await webCrypto.verify(
{
"name": 'ECDSA',
"namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) }
},
publicKey,
signature,
m
);
} catch(err) {
throw new Error(err);
}
}
async function nodeSign(curve, hash_algo, m, d) {
const publicKey = curve.keyFromPrivate(d).getPublic();
const privateKey = jwk2pem(
{"kty": "EC",
"crv": curve.namedCurve,
"x": publicKey.getX().toBuffer().base64Slice(),
"y": publicKey.getY().toBuffer().base64Slice(),
"d": d.toBuffer().base64Slice(),
"use": "sig",
"kid": "ECDSA Private Key"},
{private: true}
);
const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
sign.write(m);
sign.end();
const signature = await sign.sign(privateKey);
return ECDSASignature.decode(signature, 'der');
}
async function nodeVerify(curve, hash_algo, signature, m, Q) {
const publicKey = jwk2pem(
{"kty": "EC",
"crv": curve.namedCurve,
"x": Q.getX().toBuffer().base64Slice(),
"y": Q.getY().toBuffer().base64Slice(),
"use": "sig",
"kid": "ECDSA Public Key"},
{private: false}
);
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
verify.write(m);
verify.end();
return await verify.verify(publicKey, signature);
}

View File

@ -300,9 +300,9 @@ export function sign({ data, privateKeys, armor=true, detached=false}) {
} }
var result = {}; var result = {};
return execute(() => { const promise = async function(){
var message; var message;
if (util.isString(data)) { if (util.isString(data)) {
message = new cleartext.CleartextMessage(data); message = new cleartext.CleartextMessage(data);
} else { } else {
@ -310,14 +310,14 @@ export function sign({ data, privateKeys, armor=true, detached=false}) {
} }
if (detached) { if (detached) {
var signature = message.signDetached(privateKeys); var signature = await message.signDetached(privateKeys);
if (armor) { if (armor) {
result.signature = signature.armor(); result.signature = signature.armor();
} else { } else {
result.signature = signature; result.signature = signature;
} }
} else { } else {
message = message.sign(privateKeys); message = await message.sign(privateKeys);
if (armor) { if (armor) {
result.data = message.armor(); result.data = message.armor();
} else { } else {
@ -326,8 +326,8 @@ export function sign({ data, privateKeys, armor=true, detached=false}) {
} }
return result; return result;
}
}, 'Error signing cleartext message'); return promise().catch(onError.bind(null, 'Error signing cleartext message'));
} }
/** /**
@ -348,7 +348,7 @@ export function verify({ message, publicKeys, signature=null }) {
} }
var result = {}; var result = {};
return execute(() => { const promise = async function(){
if (cleartext.CleartextMessage.prototype.isPrototypeOf(message)) { if (cleartext.CleartextMessage.prototype.isPrototypeOf(message)) {
result.data = message.getText(); result.data = message.getText();
} else { } else {
@ -356,13 +356,14 @@ export function verify({ message, publicKeys, signature=null }) {
} }
if (signature) { if (signature) {
//detached signature //detached signature
result.signatures = message.verifyDetached(signature, publicKeys); result.signatures = await message.verifyDetached(signature, publicKeys);
} else { } else {
result.signatures = message.verify(publicKeys); result.signatures = await message.verify(publicKeys);
} }
return result; return result;
}, 'Error verifying cleartext signed message'); }
return promise().catch(onError.bind(null, 'Error verifying cleartext signed message'));
} }