From 1bd5689d7582b442c80afea146f6087d0a2f3974 Mon Sep 17 00:00:00 2001 From: chesnokovilya Date: Thu, 27 Jun 2019 19:21:32 +0200 Subject: [PATCH] Implement ECDH using Web Crypto for supported (NIST) curves (#914) --- src/crypto/crypto.js | 3 +- src/crypto/public_key/elliptic/curves.js | 9 +- src/crypto/public_key/elliptic/ecdh.js | 269 ++++++++++++++++++++--- test/crypto/elliptic.js | 108 +++++++-- 4 files changed, 337 insertions(+), 52 deletions(-) diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index f2107cfc..ad022b47 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -138,9 +138,10 @@ export default { const kdf_params = key_params[2]; const V = data_params[0].toUint8Array(); const C = data_params[1].data; + const Q = key_params[1].toUint8Array(); const d = key_params[3].toUint8Array(); 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: throw new Error('Invalid public key encryption algorithm.'); diff --git a/src/crypto/public_key/elliptic/curves.js b/src/crypto/public_key/elliptic/curves.js index 742cd5c7..0bdd05c8 100644 --- a/src/crypto/public_key/elliptic/curves.js +++ b/src/crypto/public_key/elliptic/curves.js @@ -64,7 +64,8 @@ const curves = { cipher: enums.symmetric.aes128, node: nodeCurves.p256, web: webCurves.p256, - payloadSize: 32 + payloadSize: 32, + sharedSize: 256 }, p384: { oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22], @@ -73,7 +74,8 @@ const curves = { cipher: enums.symmetric.aes192, node: nodeCurves.p384, web: webCurves.p384, - payloadSize: 48 + payloadSize: 48, + sharedSize: 384 }, p521: { oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23], @@ -82,7 +84,8 @@ const curves = { cipher: enums.symmetric.aes256, node: nodeCurves.p521, web: webCurves.p521, - payloadSize: 66 + payloadSize: 66, + sharedSize: 528 }, secp256k1: { oid: [0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x0A], diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js index 47579141..20bce59c 100644 --- a/src/crypto/public_key/elliptic/ecdh.js +++ b/src/crypto/public_key/elliptic/ecdh.js @@ -39,6 +39,8 @@ import type_kdf_params from '../../../type/kdf_params'; import enums from '../../../enums'; import util from '../../../util'; +const webCrypto = util.getWebCrypto(); + // Build Param for ECDH algorithm (RFC 6637) function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) { 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 function genPublicEphemeralKey(curve, Q) { - if (curve.name === 'curve25519') { - const { secretKey: d } = nacl.box.keyPair(); - const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, d); - let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey); - publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]); - return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below + switch (curve.name) { + case 'curve25519': { + const { secretKey: d } = nacl.box.keyPair(); + const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d); + let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey); + publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]); + return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below + } + case 'p256': + case 'p384': + case 'p521': { + if (curve.web && util.getWebCrypto()) { + try { + const result = await webPublicEphemeralKey(curve, Q); + return result; + } catch (err) { + util.print_debug_error(err); + } + } + } } - 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 }; + return ellipticPublicEphemeralKey(curve, Q); } /** @@ -127,28 +137,37 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) { * * @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} Generated ephemeral secret * @async */ -async function genPrivateEphemeralKey(curve, V, d) { - if (curve.name === 'curve25519') { - const one = new BN(1); - const mask = one.ushln(255 - 3).sub(one).ushln(3); - let secretKey = new BN(d); - secretKey = secretKey.or(one.ushln(255 - 1)); - secretKey = secretKey.and(mask); - secretKey = secretKey.toArrayLike(Uint8Array, 'le', 32); - const sharedKey = nacl.scalarMult(secretKey, V.subarray(1)); - return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below +async function genPrivateEphemeralKey(curve, V, Q, d) { + switch (curve.name) { + case 'curve25519': { + const one = new BN(1); + const mask = one.ushln(255 - 3).sub(one).ushln(3); + let secretKey = new BN(d); + secretKey = secretKey.or(one.ushln(255 - 1)); + secretKey = secretKey.and(mask); + secretKey = secretKey.toArrayLike(Uint8Array, 'le', 32); + const sharedKey = nacl.scalarMult(secretKey, V.subarray(1)); + return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below + } + case 'p256': + case 'p384': + case 'p521': { + if (curve.web && util.getWebCrypto()) { + try { + const result = await webPrivateEphemeralKey(curve, V, Q, d); + return result; + } catch (err) { + util.print_debug_error(err); + } + } + } } - 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 }; + 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 {Uint8Array} V Public part of ephemeral 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 {String} fingerprint Recipient fingerprint * @returns {Promise} Value derived from session * @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 { 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); cipher_algo = enums.read(enums.symmetric, cipher_algo); let err; @@ -182,4 +202,187 @@ async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) { 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} 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} 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 }; diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index f8ac83f9..3cebf3aa 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -1,7 +1,7 @@ const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); -const BN = require('bn.js'); const chai = require('chai'); + chai.use(require('chai-as-promised')); const expect = chai.expect; @@ -25,7 +25,8 @@ describe('Elliptic Curve Cryptography', function () { 0x6F, 0xAA, 0xE7, 0xFD, 0xC4, 0x7D, 0x89, 0xCC, 0x06, 0xCA, 0xFE, 0xAE, 0xCD, 0x0E, 0x9E, 0x62, 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: { priv: new Uint8Array([ @@ -324,7 +325,7 @@ describe('Elliptic Curve Cryptography', 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)) { data = openpgp.util.str_to_Uint8Array(data); } else { @@ -338,6 +339,7 @@ describe('Elliptic Curve Cryptography', function () { hash, new Uint8Array(ephemeral), data, + new Uint8Array(pub), new Uint8Array(priv), new Uint8Array(fingerprint) ); @@ -376,26 +378,26 @@ describe('Elliptic Curve Cryptography', function () { it('Invalid curve oid', function (done) { expect(decrypt_message( - '', 2, 7, [], [], [], [] + '', 2, 7, [], [], [], [], [] )).to.be.rejectedWith(Error, /Not valid curve/).notify(done); }); it('Invalid ephemeral key', function (done) { expect(decrypt_message( - 'secp256k1', 2, 7, [], [], [], [] + 'secp256k1', 2, 7, [], [], [], [], [] )).to.be.rejectedWith(Error, /Unknown point format/).notify(done); }); it('Invalid elliptic public key', function (done) { 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); }); it('Invalid key data integrity', function (done) { 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); }); }); - + const Q1 = new Uint8Array([ 64, 48, 226, 162, 114, 194, 194, 67, 214, @@ -443,10 +445,47 @@ describe('Elliptic Curve Cryptography', function () { ); 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 oid = new openpgp.OID(curveObj.oid); 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 ); let cipher_algo = curveObj.cipher; @@ -460,6 +499,7 @@ describe('Elliptic Curve Cryptography', function () { ); return Z; } + describe('ECDHE key generation', function () { it('Invalid curve', function (done) { 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 () { 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; }); it('Invalid fingerprint', async function () { 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; }); it('Different keys', async function () { const ECDHE_VZ1 = await genPublicEphemeralKey("curve25519", Q1, 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; }); - it('Successful exchange', async function () { + it('Successful exchange curve25519', async function () { 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; }); - }); + 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; + })); + }); + }); });