Implement ECDH using Web Crypto for supported (NIST) curves (#914)
This commit is contained in:
parent
32b4f2bd27
commit
1bd5689d75
|
@ -138,9 +138,10 @@ export default {
|
||||||
const kdf_params = key_params[2];
|
const kdf_params = key_params[2];
|
||||||
const V = data_params[0].toUint8Array();
|
const V = data_params[0].toUint8Array();
|
||||||
const C = data_params[1].data;
|
const C = data_params[1].data;
|
||||||
|
const Q = key_params[1].toUint8Array();
|
||||||
const d = key_params[3].toUint8Array();
|
const d = key_params[3].toUint8Array();
|
||||||
return publicKey.elliptic.ecdh.decrypt(
|
return publicKey.elliptic.ecdh.decrypt(
|
||||||
oid, kdf_params.cipher, kdf_params.hash, V, C, d, fingerprint);
|
oid, kdf_params.cipher, kdf_params.hash, V, C, Q, d, fingerprint);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid public key encryption algorithm.');
|
throw new Error('Invalid public key encryption algorithm.');
|
||||||
|
|
|
@ -64,7 +64,8 @@ const curves = {
|
||||||
cipher: enums.symmetric.aes128,
|
cipher: enums.symmetric.aes128,
|
||||||
node: nodeCurves.p256,
|
node: nodeCurves.p256,
|
||||||
web: webCurves.p256,
|
web: webCurves.p256,
|
||||||
payloadSize: 32
|
payloadSize: 32,
|
||||||
|
sharedSize: 256
|
||||||
},
|
},
|
||||||
p384: {
|
p384: {
|
||||||
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22],
|
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22],
|
||||||
|
@ -73,7 +74,8 @@ const curves = {
|
||||||
cipher: enums.symmetric.aes192,
|
cipher: enums.symmetric.aes192,
|
||||||
node: nodeCurves.p384,
|
node: nodeCurves.p384,
|
||||||
web: webCurves.p384,
|
web: webCurves.p384,
|
||||||
payloadSize: 48
|
payloadSize: 48,
|
||||||
|
sharedSize: 384
|
||||||
},
|
},
|
||||||
p521: {
|
p521: {
|
||||||
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23],
|
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23],
|
||||||
|
@ -82,7 +84,8 @@ const curves = {
|
||||||
cipher: enums.symmetric.aes256,
|
cipher: enums.symmetric.aes256,
|
||||||
node: nodeCurves.p521,
|
node: nodeCurves.p521,
|
||||||
web: webCurves.p521,
|
web: webCurves.p521,
|
||||||
payloadSize: 66
|
payloadSize: 66,
|
||||||
|
sharedSize: 528
|
||||||
},
|
},
|
||||||
secp256k1: {
|
secp256k1: {
|
||||||
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A],
|
oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A],
|
||||||
|
|
|
@ -39,6 +39,8 @@ import type_kdf_params from '../../../type/kdf_params';
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
|
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
|
|
||||||
// Build Param for ECDH algorithm (RFC 6637)
|
// Build Param for ECDH algorithm (RFC 6637)
|
||||||
function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) {
|
function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) {
|
||||||
const kdf_params = new type_kdf_params([hash_algo, cipher_algo]);
|
const kdf_params = new type_kdf_params([hash_algo, cipher_algo]);
|
||||||
|
@ -84,20 +86,28 @@ async function kdf(hash_algo, X, length, param, stripLeading=false, stripTrailin
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function genPublicEphemeralKey(curve, Q) {
|
async function genPublicEphemeralKey(curve, Q) {
|
||||||
if (curve.name === 'curve25519') {
|
switch (curve.name) {
|
||||||
|
case 'curve25519': {
|
||||||
const { secretKey: d } = nacl.box.keyPair();
|
const { secretKey: d } = nacl.box.keyPair();
|
||||||
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, d);
|
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d);
|
||||||
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
|
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
|
||||||
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);
|
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);
|
||||||
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||||
}
|
}
|
||||||
const v = await curve.genKeyPair();
|
case 'p256':
|
||||||
Q = curve.keyFromPublic(Q);
|
case 'p384':
|
||||||
const publicKey = new Uint8Array(v.getPublic());
|
case 'p521': {
|
||||||
const S = v.derive(Q);
|
if (curve.web && util.getWebCrypto()) {
|
||||||
const len = curve.curve.curve.p.byteLength();
|
try {
|
||||||
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
const result = await webPublicEphemeralKey(curve, Q);
|
||||||
return { publicKey, sharedKey };
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
util.print_debug_error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ellipticPublicEphemeralKey(curve, Q);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,12 +137,14 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
|
||||||
*
|
*
|
||||||
* @param {Curve} curve Elliptic curve object
|
* @param {Curve} curve Elliptic curve object
|
||||||
* @param {Uint8Array} V Public part of ephemeral key
|
* @param {Uint8Array} V Public part of ephemeral key
|
||||||
|
* @param {Uint8Array} Q Recipient public key
|
||||||
* @param {Uint8Array} d Recipient private key
|
* @param {Uint8Array} d Recipient private key
|
||||||
* @returns {Promise<BN>} Generated ephemeral secret
|
* @returns {Promise<BN>} Generated ephemeral secret
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function genPrivateEphemeralKey(curve, V, d) {
|
async function genPrivateEphemeralKey(curve, V, Q, d) {
|
||||||
if (curve.name === 'curve25519') {
|
switch (curve.name) {
|
||||||
|
case 'curve25519': {
|
||||||
const one = new BN(1);
|
const one = new BN(1);
|
||||||
const mask = one.ushln(255 - 3).sub(one).ushln(3);
|
const mask = one.ushln(255 - 3).sub(one).ushln(3);
|
||||||
let secretKey = new BN(d);
|
let secretKey = new BN(d);
|
||||||
|
@ -142,13 +154,20 @@ async function genPrivateEphemeralKey(curve, V, d) {
|
||||||
const sharedKey = nacl.scalarMult(secretKey, V.subarray(1));
|
const sharedKey = nacl.scalarMult(secretKey, V.subarray(1));
|
||||||
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||||
}
|
}
|
||||||
V = curve.keyFromPublic(V);
|
case 'p256':
|
||||||
d = curve.keyFromPrivate(d);
|
case 'p384':
|
||||||
const secretKey = new Uint8Array(d.getPrivate());
|
case 'p521': {
|
||||||
const S = d.derive(V);
|
if (curve.web && util.getWebCrypto()) {
|
||||||
const len = curve.curve.curve.p.byteLength();
|
try {
|
||||||
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
const result = await webPrivateEphemeralKey(curve, V, Q, d);
|
||||||
return { secretKey, sharedKey };
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
util.print_debug_error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ellipticPrivateEphemeralKey(curve, V, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -159,14 +178,15 @@ async function genPrivateEphemeralKey(curve, V, d) {
|
||||||
* @param {module:enums.hash} hash_algo Hash algorithm to use
|
* @param {module:enums.hash} hash_algo Hash algorithm to use
|
||||||
* @param {Uint8Array} V Public part of ephemeral key
|
* @param {Uint8Array} V Public part of ephemeral key
|
||||||
* @param {Uint8Array} C Encrypted and wrapped value derived from session key
|
* @param {Uint8Array} C Encrypted and wrapped value derived from session key
|
||||||
|
* @param {Uint8Array} Q Recipient public key
|
||||||
* @param {Uint8Array} d Recipient private key
|
* @param {Uint8Array} d Recipient private key
|
||||||
* @param {String} fingerprint Recipient fingerprint
|
* @param {String} fingerprint Recipient fingerprint
|
||||||
* @returns {Promise<BN>} Value derived from session
|
* @returns {Promise<BN>} Value derived from session
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) {
|
async function decrypt(oid, cipher_algo, hash_algo, V, C, Q, d, fingerprint) {
|
||||||
const curve = new Curve(oid);
|
const curve = new Curve(oid);
|
||||||
const { sharedKey } = await genPrivateEphemeralKey(curve, V, d);
|
const { sharedKey } = await genPrivateEphemeralKey(curve, V, Q, d);
|
||||||
const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint);
|
const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint);
|
||||||
cipher_algo = enums.read(enums.symmetric, cipher_algo);
|
cipher_algo = enums.read(enums.symmetric, cipher_algo);
|
||||||
let err;
|
let err;
|
||||||
|
@ -182,4 +202,187 @@ async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf };
|
/**
|
||||||
|
* Generate ECDHE secret from private key and public part of ephemeral key using webCrypto
|
||||||
|
*
|
||||||
|
* @param {Curve} curve Elliptic curve object
|
||||||
|
* @param {Uint8Array} V Public part of ephemeral key
|
||||||
|
* @param {Uint8Array} Q Recipient public key
|
||||||
|
* @param {Uint8Array} d Recipient private key
|
||||||
|
* @returns {Promise<BN>} Generated ephemeral secret
|
||||||
|
* @async
|
||||||
|
*/
|
||||||
|
async function webPrivateEphemeralKey(curve, V, Q, d) {
|
||||||
|
const recipient = privateToJwk(curve.payloadSize, curve.web.web, d, Q);
|
||||||
|
let privateKey = webCrypto.importKey(
|
||||||
|
"jwk",
|
||||||
|
recipient,
|
||||||
|
{
|
||||||
|
name: "ECDH",
|
||||||
|
namedCurve: curve.web.web
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["deriveKey", "deriveBits"]
|
||||||
|
);
|
||||||
|
const jwk = rawPublicToJwk(curve.payloadSize, curve.web.web, V);
|
||||||
|
let sender = webCrypto.importKey(
|
||||||
|
"jwk",
|
||||||
|
jwk,
|
||||||
|
{
|
||||||
|
name: "ECDH",
|
||||||
|
namedCurve: curve.web.web
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
[privateKey, sender] = await Promise.all([privateKey, sender]);
|
||||||
|
let S = webCrypto.deriveBits(
|
||||||
|
{
|
||||||
|
name: "ECDH",
|
||||||
|
namedCurve: curve.web.web,
|
||||||
|
public: sender
|
||||||
|
},
|
||||||
|
privateKey,
|
||||||
|
curve.web.sharedSize
|
||||||
|
);
|
||||||
|
let secret = webCrypto.exportKey(
|
||||||
|
"jwk",
|
||||||
|
privateKey
|
||||||
|
);
|
||||||
|
[S, secret] = await Promise.all([S, secret]);
|
||||||
|
const sharedKey = new Uint8Array(S);
|
||||||
|
const secretKey = util.b64_to_Uint8Array(secret.d, true);
|
||||||
|
return { secretKey, sharedKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate ECDHE ephemeral key and secret from public key using webCrypto
|
||||||
|
*
|
||||||
|
* @param {Curve} curve Elliptic curve object
|
||||||
|
* @param {Uint8Array} Q Recipient public key
|
||||||
|
* @returns {Promise<{V: Uint8Array, S: BN}>} Returns public part of ephemeral key and generated ephemeral secret
|
||||||
|
* @async
|
||||||
|
*/
|
||||||
|
async function webPublicEphemeralKey(curve, Q) {
|
||||||
|
const jwk = rawPublicToJwk(curve.payloadSize, curve.web.web, Q);
|
||||||
|
let keyPair = webCrypto.generateKey(
|
||||||
|
{
|
||||||
|
name: "ECDH",
|
||||||
|
namedCurve: curve.web.web
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
["deriveKey", "deriveBits"]
|
||||||
|
);
|
||||||
|
let recipient = webCrypto.importKey(
|
||||||
|
"jwk",
|
||||||
|
jwk,
|
||||||
|
{
|
||||||
|
name: "ECDH",
|
||||||
|
namedCurve: curve.web.web
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
[keyPair, recipient] = await Promise.all([keyPair, recipient]);
|
||||||
|
let s = webCrypto.deriveBits(
|
||||||
|
{
|
||||||
|
name: "ECDH",
|
||||||
|
namedCurve: curve.web.web,
|
||||||
|
public: recipient
|
||||||
|
},
|
||||||
|
keyPair.privateKey,
|
||||||
|
curve.web.sharedSize
|
||||||
|
);
|
||||||
|
let p = webCrypto.exportKey(
|
||||||
|
"jwk",
|
||||||
|
keyPair.publicKey
|
||||||
|
);
|
||||||
|
[s, p] = await Promise.all([s, p]);
|
||||||
|
const sharedKey = new Uint8Array(s);
|
||||||
|
const publicKey = new Uint8Array(jwkToRawPublic(p));
|
||||||
|
return { publicKey, sharedKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate ECDHE secret from private key and public part of ephemeral key using webCrypto
|
||||||
|
*
|
||||||
|
* @param {Curve} curve Elliptic curve object
|
||||||
|
* @param {Uint8Array} V Public part of ephemeral key
|
||||||
|
* @param {Uint8Array} d Recipient private key
|
||||||
|
* @returns {Promise<BN>} Generated ephemeral secret
|
||||||
|
* @async
|
||||||
|
*/
|
||||||
|
async function ellipticPrivateEphemeralKey(curve, V, d) {
|
||||||
|
V = curve.keyFromPublic(V);
|
||||||
|
d = curve.keyFromPrivate(d);
|
||||||
|
const secretKey = new Uint8Array(d.getPrivate());
|
||||||
|
const S = d.derive(V);
|
||||||
|
const len = curve.curve.curve.p.byteLength();
|
||||||
|
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
||||||
|
return { secretKey, sharedKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate ECDHE ephemeral key and secret from public key using indutny/elliptic library
|
||||||
|
*
|
||||||
|
* @param {Curve} curve Elliptic curve object
|
||||||
|
* @param {Uint8Array} Q Recipient public key
|
||||||
|
* @returns {Promise<{V: Uint8Array, S: BN}>} Returns public part of ephemeral key and generated ephemeral secret
|
||||||
|
* @async
|
||||||
|
*/
|
||||||
|
async function ellipticPublicEphemeralKey(curve, Q) {
|
||||||
|
const v = await curve.genKeyPair();
|
||||||
|
Q = curve.keyFromPublic(Q);
|
||||||
|
const publicKey = new Uint8Array(v.getPublic());
|
||||||
|
const S = v.derive(Q);
|
||||||
|
const len = curve.curve.curve.p.byteLength();
|
||||||
|
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
|
||||||
|
return { publicKey, sharedKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Integer} payloadSize ec payload size
|
||||||
|
* @param {String} name curve name
|
||||||
|
* @param {Uint8Array} publicKey public key
|
||||||
|
*/
|
||||||
|
function rawPublicToJwk(payloadSize, name, publicKey) {
|
||||||
|
const len = payloadSize;
|
||||||
|
const bufX = publicKey.slice(1, len+1);
|
||||||
|
const bufY = publicKey.slice(len+1, len*2+1);
|
||||||
|
// https://www.rfc-editor.org/rfc/rfc7518.txt
|
||||||
|
const jwKey = {
|
||||||
|
kty: "EC",
|
||||||
|
crv: name,
|
||||||
|
x: util.Uint8Array_to_b64(bufX, true),
|
||||||
|
y: util.Uint8Array_to_b64(bufY, true),
|
||||||
|
ext: true
|
||||||
|
};
|
||||||
|
return jwKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Integer} payloadSize ec payload size
|
||||||
|
* @param {String} name curve name
|
||||||
|
* @param {Uint8Array} publicKey public key
|
||||||
|
* @param {Uint8Array} privateKey private key
|
||||||
|
*/
|
||||||
|
function privateToJwk(payloadSize, name, privateKey, publicKey) {
|
||||||
|
const jwk = rawPublicToJwk(payloadSize, name, publicKey);
|
||||||
|
jwk.d = util.Uint8Array_to_b64(privateKey, true);
|
||||||
|
return jwk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {JsonWebKey} jwk key for conversion
|
||||||
|
*/
|
||||||
|
function jwkToRawPublic(jwk) {
|
||||||
|
const bufX = util.b64_to_Uint8Array(jwk.x);
|
||||||
|
const bufY = util.b64_to_Uint8Array(jwk.y);
|
||||||
|
const publicKey = new Uint8Array(bufX.length + bufY.length + 1);
|
||||||
|
publicKey[0] = 0x04;
|
||||||
|
publicKey.set(bufX, 1);
|
||||||
|
publicKey.set(bufY, bufX.length+1);
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf, webPublicEphemeralKey, ellipticPublicEphemeralKey, ellipticPrivateEphemeralKey, webPrivateEphemeralKey };
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp');
|
const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp');
|
||||||
|
|
||||||
const BN = require('bn.js');
|
|
||||||
const chai = require('chai');
|
const chai = require('chai');
|
||||||
|
|
||||||
chai.use(require('chai-as-promised'));
|
chai.use(require('chai-as-promised'));
|
||||||
|
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
@ -25,7 +25,8 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
0x6F, 0xAA, 0xE7, 0xFD, 0xC4, 0x7D, 0x89, 0xCC,
|
0x6F, 0xAA, 0xE7, 0xFD, 0xC4, 0x7D, 0x89, 0xCC,
|
||||||
0x06, 0xCA, 0xFE, 0xAE, 0xCD, 0x0E, 0x9E, 0x62,
|
0x06, 0xCA, 0xFE, 0xAE, 0xCD, 0x0E, 0x9E, 0x62,
|
||||||
0x57, 0xA4, 0xC3, 0xE7, 0x5E, 0x69, 0x10, 0xEE,
|
0x57, 0xA4, 0xC3, 0xE7, 0x5E, 0x69, 0x10, 0xEE,
|
||||||
0x67, 0xC2, 0x09, 0xF9, 0xEF, 0xE7, 0x9E, 0x56])
|
0x67, 0xC2, 0x09, 0xF9, 0xEF, 0xE7, 0x9E, 0x56
|
||||||
|
])
|
||||||
},
|
},
|
||||||
p384: {
|
p384: {
|
||||||
priv: new Uint8Array([
|
priv: new Uint8Array([
|
||||||
|
@ -324,7 +325,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('ECDH key exchange', function () {
|
describe('ECDH key exchange', function () {
|
||||||
const decrypt_message = function (oid, hash, cipher, priv, ephemeral, data, fingerprint) {
|
const decrypt_message = function (oid, hash, cipher, priv, pub, ephemeral, data, fingerprint) {
|
||||||
if (openpgp.util.isString(data)) {
|
if (openpgp.util.isString(data)) {
|
||||||
data = openpgp.util.str_to_Uint8Array(data);
|
data = openpgp.util.str_to_Uint8Array(data);
|
||||||
} else {
|
} else {
|
||||||
|
@ -338,6 +339,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
hash,
|
hash,
|
||||||
new Uint8Array(ephemeral),
|
new Uint8Array(ephemeral),
|
||||||
data,
|
data,
|
||||||
|
new Uint8Array(pub),
|
||||||
new Uint8Array(priv),
|
new Uint8Array(priv),
|
||||||
new Uint8Array(fingerprint)
|
new Uint8Array(fingerprint)
|
||||||
);
|
);
|
||||||
|
@ -376,22 +378,22 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
|
|
||||||
it('Invalid curve oid', function (done) {
|
it('Invalid curve oid', function (done) {
|
||||||
expect(decrypt_message(
|
expect(decrypt_message(
|
||||||
'', 2, 7, [], [], [], []
|
'', 2, 7, [], [], [], [], []
|
||||||
)).to.be.rejectedWith(Error, /Not valid curve/).notify(done);
|
)).to.be.rejectedWith(Error, /Not valid curve/).notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid ephemeral key', function (done) {
|
it('Invalid ephemeral key', function (done) {
|
||||||
expect(decrypt_message(
|
expect(decrypt_message(
|
||||||
'secp256k1', 2, 7, [], [], [], []
|
'secp256k1', 2, 7, [], [], [], [], []
|
||||||
)).to.be.rejectedWith(Error, /Unknown point format/).notify(done);
|
)).to.be.rejectedWith(Error, /Unknown point format/).notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid elliptic public key', function (done) {
|
it('Invalid elliptic public key', function (done) {
|
||||||
expect(decrypt_message(
|
expect(decrypt_message(
|
||||||
'secp256k1', 2, 7, secp256k1_value, secp256k1_invalid_point, secp256k1_data, []
|
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_invalid_point, secp256k1_data, []
|
||||||
)).to.be.rejectedWith(Error, /Invalid elliptic public key/).notify(done);
|
)).to.be.rejectedWith(Error, /Invalid elliptic public key/).notify(done);
|
||||||
});
|
});
|
||||||
it('Invalid key data integrity', function (done) {
|
it('Invalid key data integrity', function (done) {
|
||||||
expect(decrypt_message(
|
expect(decrypt_message(
|
||||||
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_data, []
|
'secp256k1', 2, 7, secp256k1_value, secp256k1_point, secp256k1_point, secp256k1_data, []
|
||||||
)).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done);
|
)).to.be.rejectedWith(Error, /Key Data Integrity failed/).notify(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -443,10 +445,47 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
);
|
);
|
||||||
return { V, Z };
|
return { V, Z };
|
||||||
}
|
}
|
||||||
async function genPrivateEphemeralKey(curve, V, d, fingerprint) {
|
|
||||||
|
async function genPrivateEphemeralKey(curve, V, Q, d, fingerprint) {
|
||||||
const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve);
|
const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve);
|
||||||
const oid = new openpgp.OID(curveObj.oid);
|
const oid = new openpgp.OID(curveObj.oid);
|
||||||
const { sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPrivateEphemeralKey(
|
const { sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPrivateEphemeralKey(
|
||||||
|
curveObj, V, Q, d
|
||||||
|
);
|
||||||
|
let cipher_algo = curveObj.cipher;
|
||||||
|
const hash_algo = curveObj.hash;
|
||||||
|
const param = openpgp.crypto.publicKey.elliptic.ecdh.buildEcdhParam(
|
||||||
|
openpgp.enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint
|
||||||
|
);
|
||||||
|
cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo);
|
||||||
|
const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf(
|
||||||
|
hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false
|
||||||
|
);
|
||||||
|
return Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genWebPrivateEphemeralKey(curve, V, Q, d, fingerprint) {
|
||||||
|
const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve);
|
||||||
|
const oid = new openpgp.OID(curveObj.oid);
|
||||||
|
const { sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.webPrivateEphemeralKey(
|
||||||
|
curveObj, V, Q, d
|
||||||
|
);
|
||||||
|
let cipher_algo = curveObj.cipher;
|
||||||
|
const hash_algo = curveObj.hash;
|
||||||
|
const param = openpgp.crypto.publicKey.elliptic.ecdh.buildEcdhParam(
|
||||||
|
openpgp.enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint
|
||||||
|
);
|
||||||
|
cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo);
|
||||||
|
const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf(
|
||||||
|
hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false
|
||||||
|
);
|
||||||
|
return Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genEllipticPrivateEphemeralKey(curve, V, Q, d, fingerprint) {
|
||||||
|
const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve);
|
||||||
|
const oid = new openpgp.OID(curveObj.oid);
|
||||||
|
const { sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.ellipticPrivateEphemeralKey(
|
||||||
curveObj, V, d
|
curveObj, V, d
|
||||||
);
|
);
|
||||||
let cipher_algo = curveObj.cipher;
|
let cipher_algo = curveObj.cipher;
|
||||||
|
@ -460,6 +499,7 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
);
|
);
|
||||||
return Z;
|
return Z;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ECDHE key generation', function () {
|
describe('ECDHE key generation', function () {
|
||||||
it('Invalid curve', function (done) {
|
it('Invalid curve', function (done) {
|
||||||
expect(genPublicEphemeralKey("secp256k1", Q1, fingerprint1)
|
expect(genPublicEphemeralKey("secp256k1", Q1, fingerprint1)
|
||||||
|
@ -467,24 +507,62 @@ describe('Elliptic Curve Cryptography', function () {
|
||||||
});
|
});
|
||||||
it('Invalid public part of ephemeral key and private key', async function () {
|
it('Invalid public part of ephemeral key and private key', async function () {
|
||||||
const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1);
|
const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1);
|
||||||
const ECDHE_Z12 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, d2, fingerprint1);
|
const ECDHE_Z12 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, Q2, d2, fingerprint1);
|
||||||
expect(Array.from(ECDHE_Z12).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.false;
|
expect(Array.from(ECDHE_Z12).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.false;
|
||||||
});
|
});
|
||||||
it('Invalid fingerprint', async function () {
|
it('Invalid fingerprint', async function () {
|
||||||
const ECDHE_VZ2 = await genPublicEphemeralKey("curve25519", Q2, fingerprint1);
|
const ECDHE_VZ2 = await genPublicEphemeralKey("curve25519", Q2, fingerprint1);
|
||||||
const ECDHE_Z2 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ2.V, d2, fingerprint2);
|
const ECDHE_Z2 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ2.V, Q2, d2, fingerprint2);
|
||||||
expect(Array.from(ECDHE_Z2).join(' ') === Array.from(ECDHE_VZ2.Z).join(' ')).to.be.false;
|
expect(Array.from(ECDHE_Z2).join(' ') === Array.from(ECDHE_VZ2.Z).join(' ')).to.be.false;
|
||||||
});
|
});
|
||||||
it('Different keys', async function () {
|
it('Different keys', async function () {
|
||||||
const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1);
|
const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1);
|
||||||
const ECDHE_VZ2 = await genPublicEphemeralKey("curve25519", Q2, fingerprint1);
|
const ECDHE_VZ2 = await genPublicEphemeralKey("curve25519", Q2, fingerprint1);
|
||||||
const ECDHE_Z1 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, d1, fingerprint1);
|
const ECDHE_Z1 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, Q1, d1, fingerprint1);
|
||||||
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ2.Z).join(' ')).to.be.false;
|
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ2.Z).join(' ')).to.be.false;
|
||||||
});
|
});
|
||||||
it('Successful exchange', async function () {
|
it('Successful exchange curve25519', async function () {
|
||||||
const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1);
|
const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, fingerprint1);
|
||||||
const ECDHE_Z1 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, d1, fingerprint1);
|
const ECDHE_Z1 = await genPrivateEphemeralKey("curve25519", ECDHE_VZ1.V, Q1, d1, fingerprint1);
|
||||||
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true;
|
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true;
|
||||||
});
|
});
|
||||||
|
it('Successful exchange NIST P256', async function () {
|
||||||
|
const ECDHE_VZ1 = await genPublicEphemeralKey("p256", key_data.p256.pub, fingerprint1);
|
||||||
|
const ECDHE_Z1 = await genPrivateEphemeralKey("p256", ECDHE_VZ1.V, key_data.p256.pub, key_data.p256.priv, fingerprint1);
|
||||||
|
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true;
|
||||||
|
});
|
||||||
|
it('Successful exchange NIST P384', async function () {
|
||||||
|
const ECDHE_VZ1 = await genPublicEphemeralKey("p384", key_data.p384.pub, fingerprint1);
|
||||||
|
const ECDHE_Z1 = await genPrivateEphemeralKey("p384", ECDHE_VZ1.V, key_data.p384.pub, key_data.p384.priv, fingerprint1);
|
||||||
|
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true;
|
||||||
|
});
|
||||||
|
it('Successful exchange NIST P521', async function () {
|
||||||
|
const ECDHE_VZ1 = await genPublicEphemeralKey("p521", key_data.p521.pub, fingerprint1);
|
||||||
|
const ECDHE_Z1 = await genPrivateEphemeralKey("p521", ECDHE_VZ1.V, key_data.p521.pub, key_data.p521.priv, fingerprint1);
|
||||||
|
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true;
|
||||||
|
});
|
||||||
|
it('Comparing keys derived using different algorithms', async function () {
|
||||||
|
const names = ["p256", "p384", "p521"];
|
||||||
|
if (!openpgp.util.getWebCrypto()) {
|
||||||
|
this.skip();
|
||||||
|
}
|
||||||
|
return Promise.all(names.map(async function (name) {
|
||||||
|
const curve = new elliptic_curves.Curve(name);
|
||||||
|
try {
|
||||||
|
await window.crypto.subtle.generateKey({
|
||||||
|
name: "ECDSA",
|
||||||
|
namedCurve: curve.web.web
|
||||||
|
}, false, ["sign", "verify"]);
|
||||||
|
} catch(err) {
|
||||||
|
openpgp.util.print_debug_error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ECDHE_VZ1 = await genPublicEphemeralKey(name, key_data[name].pub, fingerprint1);
|
||||||
|
const ECDHE_Z1 = await genEllipticPrivateEphemeralKey(name, ECDHE_VZ1.V, key_data[name].pub, key_data[name].priv, fingerprint1);
|
||||||
|
const ECDHE_Z2 = await genWebPrivateEphemeralKey(name, ECDHE_VZ1.V, key_data[name].pub, key_data[name].priv, fingerprint1);
|
||||||
|
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_VZ1.Z).join(' ')).to.be.true;
|
||||||
|
expect(Array.from(ECDHE_Z1).join(' ') === Array.from(ECDHE_Z2).join(' ')).to.be.true;
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user