diff --git a/src/crypto/public_key/elliptic/ecdh.js b/src/crypto/public_key/elliptic/ecdh.js
index 6c124e5f..87562060 100644
--- a/src/crypto/public_key/elliptic/ecdh.js
+++ b/src/crypto/public_key/elliptic/ecdh.js
@@ -50,15 +50,24 @@ function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) {
}
// Key Derivation Function (RFC 6637)
-async function kdf(hash_algo, S, length, param, curve, compat) {
- const len = compat ?
- S.byteLength() :
- curve.curve.curve.p.byteLength();
+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
// https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-02#appendix-B
- const X = curve.curve.curve.type === 'mont' ?
+ 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
+ for (i = 0; i < X.length && X[i] === 0; i++);
+ X = X.subarray(i);
+ }
+ if (stripTrailing) {
+ // Work around old OpenPGP.js bug
+ for (i = X.length - 1; i >= 0 && X[i] === 0; i--);
+ X = X.subarray(0, i + 1);
+ }
const digest = await hash.digest(hash_algo, util.concatUint8Array([
new Uint8Array([0, 0, 0, 1]),
X,
@@ -100,7 +109,7 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
const { V, S } = 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, false);
+ const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve);
const C = aes_kw.wrap(Z, m.toString());
return { V, C };
}
@@ -138,13 +147,17 @@ async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) {
const S = 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);
- try {
- const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve, false);
- return new BN(aes_kw.unwrap(Z, C));
- } catch(e) {}
- // Work around old OpenPGP.js bug.
- const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve, true);
- return new BN(aes_kw.unwrap(Z, C));
+ 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);
+ return new BN(aes_kw.unwrap(Z, C));
+ } catch (e) {
+ err = e;
+ }
+ }
+ throw err;
}
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf };
diff --git a/test/general/openpgp.js b/test/general/openpgp.js
index 68eed30f..fa2f04f5 100644
--- a/test/general/openpgp.js
+++ b/test/general/openpgp.js
@@ -335,6 +335,45 @@ OBqYz6mzZAWQZqsjbg4=
=zrks
-----END PGP PRIVATE KEY BLOCK-----`;
+const ecdh_msg_bad_2 = `-----BEGIN PGP MESSAGE-----
+Version: ProtonMail
+Comment: https://protonmail.com
+
+wV4DtM+8giJEGNISAQhA2rYu8+B41rJi6Gsr4TVeKyDtI0KjhhlLZs891rCG
+6X4wxNkxCuTJZax7gQZbDKh2kETK/RH75s9g7H/WV9kZ192NTGmMFiKiautH
+c5BGRGxM0sDfAQZb3ZsAUORHKPP7FczMv5aMU2Ko7O2FHc06bMdnZ/ag7GMF
+Bdl4EizttNTQ5sNCAdIXUoA8BJLHPgPiglnfTqqx3ynkBNMzfH46oKf08oJ+
+6CAQhJdif67/iDX8BRtaKDICBpv3b5anJht7irOBqf9XX13SGkmqKYF3T8eB
+W7ZV5EdCTC9KU+1BBPfPEi93F4OHsG/Jo80e5MDN24/wNxC67h7kUQiy3H4s
+al+5mSAKcIfZJA4NfPJg9zSoHgfRNGI8Q7ao+c8CLPiefGcMsakNsWUdRyBT
+SSLH3z/7AH4GxBvhDEEG3cZwmXzZAJMZmzTa+SrsxZzRpGB/aawyRntOWm8w
+6Lq9ntq4S8suj/YK62dJpJxFl8xs+COngpMDvCexX9lYlh/r/y4JRQl06oUK
+wv7trvi89TkK3821qHxr7XwI1Ncr2qDJVNlN4W+b6WFyLXnXaJAUMyZ/6inm
+RR8BoR2KkEAku3Ne/G5QI51ktNJ7cCodeVOkZj8+iip1/AGyjxZCybq/N8rc
+bpOWdMhJ6Hy+JzGNY1qNXcHJPw==
+=99Fs
+-----END PGP MESSAGE-----`;
+
+const ecdh_dec_key_2 = `-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: OpenPGP.js v4.4.9
+Comment: https://openpgpjs.org
+
+xYYEXEg93hYJKwYBBAHaRw8BAQdAeoA+T4vr3P0hFFsbzJpgy7/ZnKCrlehr
+Myk5QAsBYgf+CQMIQ76YL5sEx+Zgr7DLZ5fhQn1U9+8aLIQaIbaT51nEjEMD
+7h6mrJmp7oIr4PyijsIU+0LasXh/qlNeVQVWSygDq9L4nXDEGQhlMq3oH1FN
+NM07InBha292c2thdGVzdEBwcm90b25tYWlsLmNvbSIgPHBha292c2thdGVz
+dEBwcm90b25tYWlsLmNvbT7CdwQQFgoAHwUCXEg93gYLCQcIAwIEFQgKAgMW
+AgECGQECGwMCHgEACgkQp7+eOYEhwd6x5AD9E0LA62odFFDH76wjEYrPCvOH
+cYM56/5ZqZoGPPmbE98BAKCz/SQ90tiCMmlLEDXGX+a1bi6ttozqrnSQigic
+DI4Ix4sEXEg93hIKKwYBBAGXVQEFAQEHQPDXy2mDfbMKOpCBZB2Ic5bfoWGV
+iXvCFMnTLRWfGHUkAwEIB/4JAwhxMnjHjyALomBWSsoYxxB6rj6JKnWeikyj
+yjXZdZqdK5F+0rk4M0l7lF0wt5PhT2uMCLB7aH/mSFN1cz7sBeJl3w2soJsT
+ve/fP/8NfzP0wmEEGBYIAAkFAlxIPd4CGwwACgkQp7+eOYEhwd5MWQEAp0E4
+QTnEnG8lYXhOqnOw676oV2kEU6tcTj3DdM+cW/sA/jH3FQQjPf+mA/7xqKIv
+EQr2Mx42THr260IFYp5E/rIA
+=oA0b
+-----END PGP PRIVATE KEY BLOCK-----`;
+
function withCompression(tests) {
const compressionTypes = Object.keys(openpgp.enums.compression).map(k => openpgp.enums.compression[k]);
@@ -2301,6 +2340,14 @@ describe('[Sauce Labs Group 2] OpenPGP.js public api tests', function() {
expect(decrypted.data).to.equal('\n');
});
+ it('should decrypt broken ECC message from old go crypto', async function() {
+ const { keys: [key] } = await openpgp.key.readArmored(ecdh_dec_key_2);
+ const message = await openpgp.message.readArmored(ecdh_msg_bad_2);
+ await key.decrypt('12345');
+ const decrypted = await openpgp.decrypt({ message, privateKeys: [key] });
+ expect(decrypted.data).to.equal('Tesssst
Sent from ProtonMail mobile
');
+ });
+
});
describe('Errors', function() {