Work around go crypto bug in ECDH messages (#869)
This commit is contained in:
parent
10d3bca6d3
commit
a9599fea42
|
@ -50,15 +50,24 @@ function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key Derivation Function (RFC 6637)
|
// Key Derivation Function (RFC 6637)
|
||||||
async function kdf(hash_algo, S, length, param, curve, compat) {
|
async function kdf(hash_algo, S, length, param, curve, stripLeading=false, stripTrailing=false) {
|
||||||
const len = compat ?
|
const len = curve.curve.curve.p.byteLength();
|
||||||
S.byteLength() :
|
|
||||||
curve.curve.curve.p.byteLength();
|
|
||||||
// Note: this is not ideal, but the RFC's are unclear
|
// Note: this is not ideal, but the RFC's are unclear
|
||||||
// https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-02#appendix-B
|
// 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, 'le', len) :
|
||||||
S.toArrayLike(Uint8Array, 'be', 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([
|
const digest = await hash.digest(hash_algo, util.concatUint8Array([
|
||||||
new Uint8Array([0, 0, 0, 1]),
|
new Uint8Array([0, 0, 0, 1]),
|
||||||
X,
|
X,
|
||||||
|
@ -100,7 +109,7 @@ async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
|
||||||
const { V, S } = await genPublicEphemeralKey(curve, Q);
|
const { V, S } = await genPublicEphemeralKey(curve, Q);
|
||||||
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);
|
||||||
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());
|
const C = aes_kw.wrap(Z, m.toString());
|
||||||
return { V, C };
|
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 S = await genPrivateEphemeralKey(curve, V, 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;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
try {
|
try {
|
||||||
const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve, false);
|
// Work around old go crypto bug and old OpenPGP.js bug, respectively.
|
||||||
return new BN(aes_kw.unwrap(Z, C));
|
const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve, i === 1, i === 2);
|
||||||
} 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));
|
return new BN(aes_kw.unwrap(Z, C));
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf };
|
export default { encrypt, decrypt, genPublicEphemeralKey, genPrivateEphemeralKey, buildEcdhParam, kdf };
|
||||||
|
|
|
@ -335,6 +335,45 @@ OBqYz6mzZAWQZqsjbg4=
|
||||||
=zrks
|
=zrks
|
||||||
-----END PGP PRIVATE KEY BLOCK-----`;
|
-----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) {
|
function withCompression(tests) {
|
||||||
const compressionTypes = Object.keys(openpgp.enums.compression).map(k => openpgp.enums.compression[k]);
|
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');
|
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<br><br><br>Sent from ProtonMail mobile<br><br><br>');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Errors', function() {
|
describe('Errors', function() {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user