Everything in test/crypto/elliptic.js passes; working on test/general/ecc.js

This commit is contained in:
Mahrud Sayrafi 2018-01-04 01:13:34 -08:00 committed by Sanjana Rajan
parent 11a2d0070b
commit 12eb037ba7
4 changed files with 223 additions and 173 deletions

View File

@ -84,7 +84,8 @@ const curves = {
hashName: 'SHA-256', hashName: 'SHA-256',
hash: enums.hash.sha256, hash: enums.hash.sha256,
cipher: enums.symmetric.aes128, cipher: enums.symmetric.aes128,
node: nodeCurves.includes('secp256k1'), node: false, // FIXME nodeCurves.includes('secp256k1'),
// this is because jwk-to-pem does not support this curve.
web: false web: false
} }
}; };
@ -110,12 +111,12 @@ Curve.prototype.keyFromPublic = function (pub) {
return new KeyPair(this.curve, {pub: pub}); return new KeyPair(this.curve, {pub: pub});
}; };
Curve.prototype.genKeyPair = function () { Curve.prototype.genKeyPair = async function () {
var keyPair; var keyPair;
if (webCrypto && config.use_native && this.web) { if (webCrypto && config.use_native && this.web) {
keyPair = webGenKeyPair(this.namedCurve); keyPair = await webGenKeyPair(this.namedCurve);
} else if (nodeCrypto && config.use_native && this.node) { } else if (nodeCrypto && config.use_native && this.node) {
keyPair = nodeGenKeyPair(this.opensslCurve); keyPair = await nodeGenKeyPair(this.opensslCurve);
} else { } else {
var r = this.curve.genKeyPair(); var r = this.curve.genKeyPair();
keyPair = { keyPair = {
@ -136,18 +137,16 @@ function get(oid_or_name) {
throw new Error('Not valid curve'); throw new Error('Not valid curve');
} }
function generate(curve) { async function generate(curve) {
return new Promise(function (resolve) {
curve = get(curve); curve = get(curve);
var keyPair = curve.genKeyPair(); var keyPair = await curve.genKeyPair();
resolve({ return {
oid: curve.oid, oid: curve.oid,
Q: new BigInteger(keyPair.getPublic()), Q: new BigInteger(keyPair.getPublic()),
d: new BigInteger(keyPair.getPrivate()), d: new BigInteger(keyPair.getPrivate()),
hash: curve.hash, hash: curve.hash,
cipher: curve.cipher cipher: curve.cipher
}); };
});
} }
module.exports = { module.exports = {
@ -164,35 +163,40 @@ module.exports = {
////////////////////////// //////////////////////////
function webGenKeyPair(namedCurve) { async function webGenKeyPair(namedCurve) {
return webCrypto.generateKey( try {
var keyPair = await webCrypto.generateKey(
{ {
name: "ECDSA", name: "ECDSA", // FIXME or "ECDH"
// FIXME // "P-256", "P-384", or "P-521"
// name: "ECDH", namedCurve: namedCurve
namedCurve: namedCurve // "P-256", "P-384", or "P-521"
}, },
// FIXME // TODO whether the key is extractable (i.e. can be used in exportKey)
false, // whether the key is extractable (i.e. can be used in exportKey) false,
["sign", "verify"] // can be any combination of "sign" and "verify" // FIXME this can be any combination of "sign" and "verify"
// FIXME // or "deriveKey" and "deriveBits" for ECDH
// ["deriveKey", "deriveBits"] // can be any combination of "deriveKey" and "deriveBits" ["sign", "verify"]
).then(function(key){ );
return { return {
pub: key.publicKey.encode(), // FIXME encoding pub: keyPair.publicKey.encode(), // FIXME encoding
priv: key.privateKey.toArray() // FIXME encoding priv: keyPair.privateKey.toArray() // FIXME encoding
}; };
}).catch(function(err){ } catch(err) {
throw new Error(err); throw new Error(err);
}); }
} }
function nodeGenKeyPair(opensslCurve) { async function nodeGenKeyPair(opensslCurve) {
// TODO turn this into a promise try {
var ecc = nodeCrypto.createECDH(opensslCurve); var ecdh = nodeCrypto.createECDH(opensslCurve);
ecc.generateKeys(); await ecdh.generateKeys();
return { return {
pub: ecc.getPrivateKey().toJSON().data, pub: ecdh.getPublicKey().toJSON().data,
priv: ecc.getPublicKey().toJSON().data priv: ecdh.getPrivateKey().toJSON().data
}; };
} catch(err) {
throw new Error(err);
}
} }

View File

@ -25,20 +25,21 @@
'use strict'; 'use strict';
import asn1 from 'asn1.js'; import BN from 'bn.js';
import jwk2pem from 'jwk-to-pem'; import ASN1 from 'asn1.js';
import jwkToPem 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 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 webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
var ECDSASignature = asn1.define('ecdsa-sig', function() { var ECDSASignature = ASN1.define('ecdsa-sig', function() {
return this.seq().obj( this.seq().obj(
this.key('r').int(), // FIXME int or BN? this.key('r').int(), // FIXME int or BN?
this.key('s').int() // FIXME int or BN? this.key('s').int() // FIXME int or BN?
); );
@ -52,16 +53,16 @@ var ECDSASignature = asn1.define('ecdsa-sig', function() {
* @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) { async function sign(oid, hash_algo, m, d) {
var signature; var signature;
const curve = curves.get(oid); const curve = curves.get(oid);
if (webCrypto && config.use_native && curve.web) {
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()); const key = curve.keyFromPrivate(d.toByteArray());
signature = key.sign(m, hash_algo); if (webCrypto && config.use_native && curve.web) {
signature = await webSign(curve, hash_algo, m, key.keyPair);
} else if (nodeCrypto && config.use_native && curve.node) {
signature = await nodeSign(curve, hash_algo, m, key.keyPair);
} else {
signature = await key.sign(m, hash_algo);
} }
return { return {
r: new BigInteger(signature.r), r: new BigInteger(signature.r),
@ -78,16 +79,20 @@ function sign(oid, hash_algo, m, d) {
* @param {BigInteger} Q Public key used to verify the message * @param {BigInteger} Q Public key used to verify the message
* @return {Boolean} * @return {Boolean}
*/ */
function verify(oid, hash_algo, signature, m, Q) { async function verify(oid, hash_algo, signature, m, Q) {
var result;
const curve = curves.get(oid); const curve = curves.get(oid);
if (webCrypto && config.use_native && curve.web) {
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()); const key = curve.keyFromPublic(Q.toByteArray());
return key.verify(m, {r: signature.r.toByteArray(), s: signature.s.toByteArray()}, hash_algo); if (webCrypto && config.use_native && curve.web) {
result = await webVerify(curve, hash_algo, signature, m, key.keyPair.getPublic());
} else if (nodeCrypto && config.use_native && curve.node) {
result = await nodeVerify(curve, hash_algo, signature, m, key.keyPair.getPublic());
} else {
result = await key.verify(
m, {r: signature.r.toByteArray(), s: signature.s.toByteArray()}, hash_algo
);
} }
return result;
} }
module.exports = { module.exports = {
@ -103,16 +108,16 @@ module.exports = {
////////////////////////// //////////////////////////
async function webSign(curve, hash_algo, m, d) { async function webSign(curve, hash_algo, m, keyPair) {
const publicKey = curve.keyFromPrivate(d).getPublic(); try {
const privateKey = await webCrypto.importKey( const key = await webCrypto.importKey(
"jwk", "jwk",
{ {
"kty": "EC", "kty": "EC",
"crv": curve.namedCurve, "crv": curve.namedCurve,
"x": publicKey.getX().toBuffer().base64Slice(), "x": keyPair.getPublic().getX().toBuffer().base64Slice(),
"y": publicKey.getY().toBuffer().base64Slice(), "y": keyPair.getPublic().getY().toBuffer().base64Slice(),
"d": d.toBuffer().base64Slice(), "d": keyPair.getPrivate().toBuffer().base64Slice(),
"use": "sig", "use": "sig",
"kid": "ECDSA Private Key" "kid": "ECDSA Private Key"
}, },
@ -125,14 +130,13 @@ async function webSign(curve, hash_algo, m, d) {
["sign"] ["sign"]
); );
try {
return await webCrypto.sign( return await webCrypto.sign(
{ {
"name": 'ECDSA', "name": 'ECDSA',
"namedCurve": curve.namedCurve, "namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) } "hash": { name: enums.read(enums.hash, hash_algo) }
}, },
privateKey, key,
m m
); );
} catch(err) { } catch(err) {
@ -140,14 +144,15 @@ async function webSign(curve, hash_algo, m, d) {
} }
} }
async function webVerify(curve, hash_algo, signature, m, Q) { async function webVerify(curve, hash_algo, signature, m, publicKey) {
const publicKey = await webCrypto.importKey( try {
const key = await webCrypto.importKey(
"jwk", "jwk",
{ {
"kty": "EC", "kty": "EC",
"crv": curve.namedCurve, "crv": curve.namedCurve,
"x": Q.getX().toBuffer().base64Slice(), "x": publicKey.getX().toBuffer().base64Slice(),
"y": Q.getY().toBuffer().base64Slice(), "y": publicKey.getY().toBuffer().base64Slice(),
"use": "sig", "use": "sig",
"kid": "ECDSA Public Key" "kid": "ECDSA Public Key"
}, },
@ -160,14 +165,13 @@ async function webVerify(curve, hash_algo, signature, m, Q) {
["verify"] ["verify"]
); );
try {
return await webCrypto.verify( return await webCrypto.verify(
{ {
"name": 'ECDSA', "name": 'ECDSA',
"namedCurve": curve.namedCurve, "namedCurve": curve.namedCurve,
"hash": { name: enums.read(enums.hash, hash_algo) } "hash": { name: enums.read(enums.hash, hash_algo) }
}, },
publicKey, key,
signature, signature,
m m
); );
@ -177,40 +181,60 @@ async function webVerify(curve, hash_algo, signature, m, Q) {
} }
async function nodeSign(curve, hash_algo, m, d) { async function nodeSign(curve, hash_algo, message, keyPair) {
const publicKey = curve.keyFromPrivate(d).getPublic(); if (typeof message === 'string') {
const privateKey = jwk2pem( message = util.str2Uint8Array(message);
{"kty": "EC", }
const key = jwkToPem(
{
"kty": "EC",
"crv": curve.namedCurve, "crv": curve.namedCurve,
"x": publicKey.getX().toBuffer().base64Slice(), "x": keyPair.getPublic().getX().toBuffer().base64Slice(),
"y": publicKey.getY().toBuffer().base64Slice(), "y": keyPair.getPublic().getY().toBuffer().base64Slice(),
"d": d.toBuffer().base64Slice(), "d": keyPair.getPrivate().toBuffer().base64Slice(),
"use": "sig", "use": "sig",
"kid": "ECDSA Private Key"}, "kid": "ECDSA Private Key"
},
{ private: true } { private: true }
); );
// FIXME what happens when hash_algo = undefined?
const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo)); const sign = nodeCrypto.createSign(enums.read(enums.hash, hash_algo));
sign.write(m); sign.write(message);
sign.end(); sign.end();
const signature = await sign.sign(privateKey); const signature = await ECDSASignature.decode(sign.sign(key), 'der');
return ECDSASignature.decode(signature, 'der'); return {
r: signature.r.toArray(),
s: signature.s.toArray()
};
} }
async function nodeVerify(curve, hash_algo, signature, m, Q) { async function nodeVerify(curve, hash_algo, signature, message, publicKey) {
const publicKey = jwk2pem( signature = ECDSASignature.encode(
{"kty": "EC", {
r: new BN(signature.r.toByteArray()),
s: new BN(signature.s.toByteArray())
},
'der');
if (typeof message === 'string') {
message = util.str2Uint8Array(message);
}
const key = jwkToPem(
{
"kty": "EC",
"crv": curve.namedCurve, "crv": curve.namedCurve,
"x": Q.getX().toBuffer().base64Slice(), "x": publicKey.getX().toBuffer().base64Slice(),
"y": Q.getY().toBuffer().base64Slice(), "y": publicKey.getY().toBuffer().base64Slice(),
"use": "sig", "use": "sig",
"kid": "ECDSA Public Key"}, "kid": "ECDSA Public Key"
},
{ private: false } { private: false }
); );
// FIXME what happens when hash_algo = undefined?
const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo)); const verify = nodeCrypto.createVerify(enums.read(enums.hash, hash_algo));
verify.write(m); verify.write(message);
verify.end(); verify.end();
const result = await verify.verify(publicKey, signature); const result = await verify.verify(key, signature);
return result; return result;
} }

View File

@ -19,7 +19,12 @@ export default openpgp;
* import { encryptMessage } from 'openpgp.js' * import { encryptMessage } from 'openpgp.js'
* encryptMessage(keys, text) * encryptMessage(keys, text)
*/ */
// export * from './openpgp'; export {
encrypt, decrypt, sign, verify,
generateKey, reformatKey, decryptKey,
encryptSessionKey, decryptSessionKey,
initWorker, getWorker, destroyWorker
} from './openpgp';
/** /**
* @see module:key * @see module:key

View File

@ -2,7 +2,11 @@
var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); var openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp');
var expect = require('chai').expect; var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
const expect = chai.expect;
var bin2bi = function (bytes) { var bin2bi = function (bytes) {
var mpi = new openpgp.MPI(); var mpi = new openpgp.MPI();
@ -149,9 +153,10 @@ describe('Elliptic Curve Cryptography', function () {
var names = ['p256', 'p384', 'p521', 'secp256k1']; var names = ['p256', 'p384', 'p521', 'secp256k1'];
names.forEach(function (name) { names.forEach(function (name) {
var curve = elliptic_curves.get(name); var curve = elliptic_curves.get(name);
var keyPair = curve.genKeyPair(); curve.genKeyPair().then(keyPair => {
expect(keyPair).to.exist; expect(keyPair).to.exist;
expect(keyPair.isValid()).to.be.true; expect(keyPair.isValid()).to.be.true; // FIXME done will skip this.
});
}); });
done(); done();
}); });
@ -201,21 +206,22 @@ describe('Elliptic Curve Cryptography', function () {
}); });
}); });
describe('ECDSA signature', function () { describe('ECDSA signature', function () {
var verify_signature = function (oid, hash, r, s, pub, message) { var verify_signature = function (oid, hash, r, s, message, pub) {
if (openpgp.util.isString(message)) { if (openpgp.util.isString(message)) {
message = openpgp.util.str2Uint8Array(message); message = openpgp.util.str2Uint8Array(message);
} else if (!openpgp.util.isUint8Array(message)) { } else if (!openpgp.util.isUint8Array(message)) {
message = new Uint8Array(message); message = new Uint8Array(message);
} }
return function () { return Promise.resolve().then(() => {
var ecdsa = elliptic_curves.ecdsa; var ecdsa = elliptic_curves.ecdsa;
return ecdsa.verify(oid, return ecdsa.verify(
oid,
hash, hash,
{r: bin2bi(r), s: bin2bi(s)}, {r: bin2bi(r), s: bin2bi(s)},
message, message,
bin2bi(pub) bin2bi(pub)
); );
}; });
}; };
var secp256k1_dummy_value = new Uint8Array([ var secp256k1_dummy_value = new Uint8Array([
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -236,24 +242,30 @@ describe('Elliptic Curve Cryptography', function () {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
it('Invalid curve oid', function (done) { it('Invalid curve oid', function () {
var res = verify_signature('invalid oid', 8, [], [], [], []); return Promise.all([
expect(res).to.throw(Error, /Not valid curve/); expect(verify_signature(
res = verify_signature("\x00", 8, [], [], [], []); 'invalid oid', 8, [], [], [], []
expect(res).to.throw(Error, /Not valid curve/); )).to.be.rejectedWith(Error, /Not valid curve/),
done(); expect(verify_signature(
"\x00", 8, [], [], [], []
)).to.be.rejectedWith(Error, /Not valid curve/)
]);
}); });
it('Invalid public key', function (done) { it('Invalid public key', function () {
var res = verify_signature('secp256k1', 8, [], [], [], []); return Promise.all([
expect(res).to.throw(Error, /Unknown point format/); expect(verify_signature(
res = verify_signature('secp256k1', 8, [], [], secp256k1_invalid_point, []); 'secp256k1', 8, [], [], [], []
expect(res).to.throw(Error, /Unknown point format/); )).to.be.rejectedWith(Error, /Unknown point format/),
done(); expect(verify_signature(
'secp256k1', 8, [], [], [], secp256k1_invalid_point
)).to.be.rejectedWith(Error, /Unknown point format/)
]);
}); });
it('Invalid signature', function (done) { it('Invalid signature', function (done) {
var res = verify_signature('secp256k1', 8, [], [], secp256k1_dummy_point, []); expect(verify_signature(
expect(res()).to.be.false; 'secp256k1', 8, [], [], [], secp256k1_dummy_point
done(); )).to.eventually.equal(false).notify(done);
}); });
var p384_message = new Uint8Array([ var p384_message = new Uint8Array([
@ -274,23 +286,28 @@ describe('Elliptic Curve Cryptography', function () {
0x68, 0x58, 0x23, 0x1D, 0x11, 0xEF, 0x3D, 0x21, 0x68, 0x58, 0x23, 0x1D, 0x11, 0xEF, 0x3D, 0x21,
0x30, 0x75, 0x24, 0x39, 0x48, 0x89, 0x03, 0xDC]); 0x30, 0x75, 0x24, 0x39, 0x48, 0x89, 0x03, 0xDC]);
it('Valid signature', function (done) { it('Valid signature', function (done) {
var res = verify_signature('p384', 8, p384_r, p384_s, key_data.p384.pub, p384_message); verify_signature('p384', 8, p384_r, p384_s, p384_message, key_data.p384.pub)
expect(res()).to.be.true; .then(res => {
expect(res).to.be.true;
done(); done();
}); });
});
it('Sign and verify message', function (done) { it('Sign and verify message', function (done) {
var curve = elliptic_curves.get('p521'); var curve = elliptic_curves.get('p521');
var keyPair = curve.genKeyPair(); curve.genKeyPair().then(keyPair => {
var keyPublic = bin2bi(keyPair.getPublic()); var keyPublic = bin2bi(keyPair.getPublic());
var keyPrivate = bin2bi(keyPair.getPrivate()); var keyPrivate = bin2bi(keyPair.getPrivate());
var oid = curve.oid; var oid = curve.oid;
var message = p384_message; var message = p384_message;
var signature = elliptic_curves.ecdsa.sign(oid, 10, message, keyPrivate); elliptic_curves.ecdsa.sign(oid, 10, message, keyPrivate).then(signature => {
var verified = elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic); elliptic_curves.ecdsa.verify(oid, 10, signature, message, keyPublic).then(verified => {
expect(verified).to.be.true; expect(verified).to.be.true;
done(); done();
}); });
}); });
});
});
});
describe('ECDH key exchange', function () { describe('ECDH key exchange', function () {
var decrypt_message = function (oid, hash, cipher, priv, ephemeral, data, fingerprint) { var decrypt_message = function (oid, hash, cipher, priv, ephemeral, data, fingerprint) {
if (typeof data === 'string') { if (typeof data === 'string') {
@ -298,7 +315,7 @@ describe('Elliptic Curve Cryptography', function () {
} else { } else {
data = new Uint8Array(data); data = new Uint8Array(data);
} }
return function () { return Promise.resolve().then(() => {
var ecdh = elliptic_curves.ecdh; var ecdh = elliptic_curves.ecdh;
return ecdh.decrypt( return ecdh.decrypt(
oid, oid,
@ -309,7 +326,7 @@ describe('Elliptic Curve Cryptography', function () {
bin2bi(priv), bin2bi(priv),
fingerprint fingerprint
); );
}; });
}; };
var secp256k1_value = new Uint8Array([ var secp256k1_value = new Uint8Array([
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -330,19 +347,19 @@ describe('Elliptic Curve Cryptography', function () {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
it('Invalid curve oid', function (done) { it('Invalid curve oid', function (done) {
var res = decrypt_message('', 2, 7, [], [], [], ''); expect(decrypt_message(
expect(res).to.throw(Error, /Not valid curve/); '', 2, 7, [], [], [], ''
done(); )).to.be.rejectedWith(Error, /Not valid curve/).notify(done);
}); });
it('Invalid ephemeral key', function (done) { it('Invalid ephemeral key', function (done) {
var res = decrypt_message('secp256k1', 2, 7, [], [], [], ''); expect(decrypt_message(
expect(res).to.throw(Error, /Unknown point format/); 'secp256k1', 2, 7, [], [], [], ''
done(); )).to.be.rejectedWith(Error, /Unknown point format/).notify(done);;
}); });
it('Invalid key data integrity', function (done) { it('Invalid key data integrity', function (done) {
var res = decrypt_message('secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_data, ''); expect(decrypt_message(
expect(res).to.throw(Error, /Key Data Integrity failed/); 'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_data, ''
done(); )).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done);
}); });
}); });
}); });