From e637e758916c10a47fbf6709a53099368a0cb9e3 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Wed, 1 May 2019 10:42:35 +0200 Subject: [PATCH] Clean up ECDH API --- src/crypto/crypto.js | 120 +++++++++--------- src/crypto/public_key/elliptic/ecdh.js | 60 +++++---- .../public_key_encrypted_session_key.js | 4 +- test/crypto/crypto.js | 4 +- test/crypto/elliptic.js | 8 +- 5 files changed, 95 insertions(+), 101 deletions(-) diff --git a/src/crypto/crypto.js b/src/crypto/crypto.js index 46675409..f2107cfc 100644 --- a/src/crypto/crypto.js +++ b/src/crypto/crypto.js @@ -69,36 +69,34 @@ export default { */ publicKeyEncrypt: async function(algo, pub_params, data, fingerprint) { const types = this.getEncSessionKeyParamTypes(algo); - return (async function() { - switch (algo) { - case enums.publicKey.rsa_encrypt: - case enums.publicKey.rsa_encrypt_sign: { - const m = data.toBN(); - const n = pub_params[0].toBN(); - const e = pub_params[1].toBN(); - const res = await publicKey.rsa.encrypt(m, n, e); - return constructParams(types, [res]); - } - case enums.publicKey.elgamal: { - const m = data.toBN(); - const p = pub_params[0].toBN(); - const g = pub_params[1].toBN(); - const y = pub_params[2].toBN(); - const res = await publicKey.elgamal.encrypt(m, p, g, y); - return constructParams(types, [res.c1, res.c2]); - } - case enums.publicKey.ecdh: { - const oid = pub_params[0]; - const Q = pub_params[1].toUint8Array(); - const kdf_params = pub_params[2]; - const { V, C } = await publicKey.elliptic.ecdh.encrypt( - oid, kdf_params.cipher, kdf_params.hash, data, Q, fingerprint); - return constructParams(types, [new BN(V), C]); - } - default: - return []; + switch (algo) { + case enums.publicKey.rsa_encrypt: + case enums.publicKey.rsa_encrypt_sign: { + const m = data.toBN(); + const n = pub_params[0].toBN(); + const e = pub_params[1].toBN(); + const res = await publicKey.rsa.encrypt(m, n, e); + return constructParams(types, [res]); } - }()); + case enums.publicKey.elgamal: { + const m = data.toBN(); + const p = pub_params[0].toBN(); + const g = pub_params[1].toBN(); + const y = pub_params[2].toBN(); + const res = await publicKey.elgamal.encrypt(m, p, g, y); + return constructParams(types, [res.c1, res.c2]); + } + case enums.publicKey.ecdh: { + const oid = pub_params[0]; + const Q = pub_params[1].toUint8Array(); + const kdf_params = pub_params[2]; + const { publicKey: V, wrappedKey: C } = await publicKey.elliptic.ecdh.encrypt( + oid, kdf_params.cipher, kdf_params.hash, data, Q, fingerprint); + return constructParams(types, [new BN(V), C]); + } + default: + return []; + } }, /** @@ -112,43 +110,41 @@ export default { module:type/ecdh_symkey>} data_params encrypted session key parameters * @param {String} fingerprint Recipient fingerprint - * @returns {module:type/mpi} An MPI containing the decrypted data + * @returns {BN} A BN containing the decrypted data * @async */ publicKeyDecrypt: async function(algo, key_params, data_params, fingerprint) { - return new type_mpi(await (async function() { - switch (algo) { - case enums.publicKey.rsa_encrypt_sign: - case enums.publicKey.rsa_encrypt: { - const c = data_params[0].toBN(); - const n = key_params[0].toBN(); // n = pq - const e = key_params[1].toBN(); - const d = key_params[2].toBN(); // de = 1 mod (p-1)(q-1) - const p = key_params[3].toBN(); - const q = key_params[4].toBN(); - const u = key_params[5].toBN(); // q^-1 mod p - return publicKey.rsa.decrypt(c, n, e, d, p, q, u); - } - case enums.publicKey.elgamal: { - const c1 = data_params[0].toBN(); - const c2 = data_params[1].toBN(); - const p = key_params[0].toBN(); - const x = key_params[3].toBN(); - return publicKey.elgamal.decrypt(c1, c2, p, x); - } - case enums.publicKey.ecdh: { - const oid = key_params[0]; - const kdf_params = key_params[2]; - const V = data_params[0].toUint8Array(); - const C = data_params[1].data; - const d = key_params[3].toUint8Array(); - return publicKey.elliptic.ecdh.decrypt( - oid, kdf_params.cipher, kdf_params.hash, V, C, d, fingerprint); - } - default: - throw new Error('Invalid public key encryption algorithm.'); + switch (algo) { + case enums.publicKey.rsa_encrypt_sign: + case enums.publicKey.rsa_encrypt: { + const c = data_params[0].toBN(); + const n = key_params[0].toBN(); // n = pq + const e = key_params[1].toBN(); + const d = key_params[2].toBN(); // de = 1 mod (p-1)(q-1) + const p = key_params[3].toBN(); + const q = key_params[4].toBN(); + const u = key_params[5].toBN(); // q^-1 mod p + return publicKey.rsa.decrypt(c, n, e, d, p, q, u); } - }())); + case enums.publicKey.elgamal: { + const c1 = data_params[0].toBN(); + const c2 = data_params[1].toBN(); + const p = key_params[0].toBN(); + const x = key_params[3].toBN(); + return publicKey.elgamal.decrypt(c1, c2, p, x); + } + case enums.publicKey.ecdh: { + const oid = key_params[0]; + const kdf_params = key_params[2]; + const V = data_params[0].toUint8Array(); + const C = data_params[1].data; + const d = key_params[3].toUint8Array(); + return publicKey.elliptic.ecdh.decrypt( + oid, kdf_params.cipher, kdf_params.hash, V, C, d, fingerprint); + } + default: + throw new Error('Invalid public key encryption algorithm.'); + } }, /** Returns the types comprising the private key of an algorithm diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js index 3a1be448..59831073 100644 --- a/src/crypto/public_key/elliptic/ecdh.js +++ b/src/crypto/public_key/elliptic/ecdh.js @@ -52,13 +52,10 @@ function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) { } // Key Derivation Function (RFC 6637) -async function kdf(hash_algo, S, length, param, curve, stripLeading=false, stripTrailing=false) { - const len = curve.curve.curve.p.byteLength(); - // Note: this is not ideal, but the RFC's are unclear +async function kdf(hash_algo, X, length, param, stripLeading=false, stripTrailing=false) { + // Note: X is little endian for Curve25519, big-endian for all others. + // This is not ideal, but the RFC's are unclear // https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-02#appendix-B - let X = curve.curve.curve.type === 'mont' ? - S.toArrayLike(Uint8Array, 'le', len) : - S.toArrayLike(Uint8Array, 'be', len); let i; if (stripLeading) { // Work around old go crypto bug @@ -88,23 +85,19 @@ async function kdf(hash_algo, S, length, param, curve, stripLeading=false, strip */ async function genPublicEphemeralKey(curve, Q) { if (curve.name === 'curve25519') { - const { secretKey } = nacl.box.keyPair(); - const one = curve.curve.curve.one; - const mask = one.ushln(255 - 3).sub(one).ushln(3); - let priv = new BN(secretKey); - priv = priv.or(one.ushln(255 - 1)); - priv = priv.and(mask); - priv = priv.toArrayLike(Uint8Array, 'le', 32); - const S = nacl.scalarMult(priv, Q.subarray(1)); - const { publicKey } = nacl.box.keyPair.fromSecretKey(priv); - const ret = { V: util.concatUint8Array([new Uint8Array([0x40]), publicKey]), S: new BN(S, 'le') }; - return ret; + 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 } const v = await curve.genKeyPair(); Q = curve.keyFromPublic(Q); - const V = new Uint8Array(v.getPublic()); + const publicKey = new Uint8Array(v.getPublic()); const S = v.derive(Q); - return { V, S }; + const len = curve.curve.curve.p.byteLength(); + const sharedKey = S.toArrayLike(Uint8Array, 'be', len); + return { publicKey, sharedKey }; } /** @@ -121,12 +114,12 @@ async function genPublicEphemeralKey(curve, Q) { */ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) { const curve = new Curve(oid); - const { V, S } = await genPublicEphemeralKey(curve, Q); + const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q); const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint); cipher_algo = enums.read(enums.symmetric, cipher_algo); - const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve); - const C = aes_kw.wrap(Z, m.toString()); - return { V, C }; + const Z = await kdf(hash_algo, sharedKey, cipher[cipher_algo].keySize, param); + const wrappedKey = aes_kw.wrap(Z, m.toString()); + return { publicKey, wrappedKey }; } /** @@ -142,15 +135,20 @@ async function genPrivateEphemeralKey(curve, V, d) { if (curve.name === 'curve25519') { const one = curve.curve.curve.one; const mask = one.ushln(255 - 3).sub(one).ushln(3); - let priv = new BN(d); - priv = priv.or(one.ushln(255 - 1)); - priv = priv.and(mask); - const S = nacl.scalarMult(priv.toArrayLike(Uint8Array, 'le', 32), V.subarray(1)); - return new BN(S, 'le'); + 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 } V = curve.keyFromPublic(V); d = curve.keyFromPrivate(d); - return d.derive(V); + 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 }; } /** @@ -168,14 +166,14 @@ async function genPrivateEphemeralKey(curve, V, d) { */ async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) { const curve = new Curve(oid); - const S = await genPrivateEphemeralKey(curve, V, d); + const { sharedKey } = await genPrivateEphemeralKey(curve, V, d); const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint); cipher_algo = enums.read(enums.symmetric, cipher_algo); let err; for (let i = 0; i < 3; i++) { try { // Work around old go crypto bug and old OpenPGP.js bug, respectively. - const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve, i === 1, i === 2); + const Z = await kdf(hash_algo, sharedKey, cipher[cipher_algo].keySize, param, i === 1, i === 2); return new BN(aes_kw.unwrap(Z, C)); } catch (e) { err = e; diff --git a/src/packet/public_key_encrypted_session_key.js b/src/packet/public_key_encrypted_session_key.js index fc93e430..8bab822c 100644 --- a/src/packet/public_key_encrypted_session_key.js +++ b/src/packet/public_key_encrypted_session_key.js @@ -137,8 +137,8 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) { */ PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) { const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm); - const result = await crypto.publicKeyDecrypt( - algo, key.params, this.encrypted, key.getFingerprintBytes()); + const result = new type_mpi(await crypto.publicKeyDecrypt( + algo, key.params, this.encrypted, key.getFingerprintBytes())); let checksum; let decoded; diff --git a/test/crypto/crypto.js b/test/crypto/crypto.js index 6459d346..53a0a321 100644 --- a/test/crypto/crypto.js +++ b/test/crypto/crypto.js @@ -364,7 +364,7 @@ describe('API functional testing', function() { return crypto.publicKeyDecrypt( 1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData ).then(data => { - data = data.write(); + data = new openpgp.MPI(data).write(); data = util.Uint8Array_to_str(data.subarray(2, data.length)); const result = crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength()); @@ -383,7 +383,7 @@ describe('API functional testing', function() { return crypto.publicKeyDecrypt( 16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData ).then(data => { - data = data.write(); + data = new openpgp.MPI(data).write(); data = util.Uint8Array_to_str(data.subarray(2, data.length)); const result = crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength()); diff --git a/test/crypto/elliptic.js b/test/crypto/elliptic.js index a8da94ec..f8ac83f9 100644 --- a/test/crypto/elliptic.js +++ b/test/crypto/elliptic.js @@ -429,7 +429,7 @@ describe('Elliptic Curve Cryptography', function () { async function genPublicEphemeralKey(curve, Q, fingerprint) { const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve); const oid = new openpgp.OID(curveObj.oid); - const { V, S } = await openpgp.crypto.publicKey.elliptic.ecdh.genPublicEphemeralKey( + const { publicKey: V, sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPublicEphemeralKey( curveObj, Q ); let cipher_algo = curveObj.cipher; @@ -439,14 +439,14 @@ describe('Elliptic Curve Cryptography', function () { ); cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo); const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf( - hash_algo, S, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false + hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false ); return { V, Z }; } async function genPrivateEphemeralKey(curve, V, d, fingerprint) { const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve); const oid = new openpgp.OID(curveObj.oid); - const S = await openpgp.crypto.publicKey.elliptic.ecdh.genPrivateEphemeralKey( + const { sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPrivateEphemeralKey( curveObj, V, d ); let cipher_algo = curveObj.cipher; @@ -456,7 +456,7 @@ describe('Elliptic Curve Cryptography', function () { ); cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo); const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf( - hash_algo, S, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false + hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false ); return Z; }