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

View File

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

View File

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

View File

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