From 3b9676f2e9a4984d7607a8552d884dd2365478b2 Mon Sep 17 00:00:00 2001 From: Daniel Huigens Date: Tue, 11 Dec 2018 17:49:49 +0100 Subject: [PATCH] Reject messages encrypted with a symmetric algo not in preferred algos --- src/message.js | 48 +++++++++++++++-------- test/security/index.js | 1 + test/security/preferred_algo_mismatch.js | 49 ++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 test/security/preferred_algo_mismatch.js diff --git a/src/message.js b/src/message.js index d8818616..b40a11c6 100644 --- a/src/message.js +++ b/src/message.js @@ -153,6 +153,7 @@ Message.prototype.decrypt = async function(privateKeys, passwords, sessionKeys, Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { let keyPackets = []; + let exception; if (passwords) { const symESKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); if (!symESKeyPacketlist) { @@ -181,23 +182,36 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { throw new Error('No public key encrypted session key packet found.'); } await Promise.all(pkESKeyPacketlist.map(async function(keyPacket) { - const privateKeyPackets = new packet.List(); - privateKeys.forEach(privateKey => { - privateKeyPackets.concat(privateKey.getKeys(keyPacket.publicKeyId).map(key => key.keyPacket)); - }); - await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { - if (!privateKeyPacket) { - return; - } - if (!privateKeyPacket.isDecrypted()) { - throw new Error('Private key is not decrypted.'); - } - try { - await keyPacket.decrypt(privateKeyPacket); - keyPackets.push(keyPacket); - } catch (err) { - util.print_debug_error(err); + await Promise.all(privateKeys.map(async function(privateKey) { + const primaryUser = await privateKey.getPrimaryUser(); // TODO: Pass userId from somewhere. + let algos = [ + enums.symmetric.aes256, // Old OpenPGP.js default fallback + enums.symmetric.aes128, // RFC4880bis fallback + enums.symmetric.tripledes // RFC4880 fallback + ]; + if (primaryUser && primaryUser.selfCertification.preferredSymmetricAlgorithms) { + algos = algos.concat(primaryUser.selfCertification.preferredSymmetricAlgorithms); } + + const privateKeyPackets = privateKey.getKeys(keyPacket.publicKeyId).map(key => key.keyPacket); + await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) { + if (!privateKeyPacket) { + return; + } + if (!privateKeyPacket.isDecrypted()) { + throw new Error('Private key is not decrypted.'); + } + try { + await keyPacket.decrypt(privateKeyPacket); + if (!algos.includes(enums.write(enums.symmetric, keyPacket.sessionKeyAlgorithm))) { + throw new Error('A non-preferred symmetric algorithm was used.'); + } + keyPackets.push(keyPacket); + } catch (err) { + util.print_debug_error(err); + exception = err; + } + })); })); stream.cancel(keyPacket.encrypted); // Don't keep copy of encrypted data in memory. keyPacket.encrypted = null; @@ -222,7 +236,7 @@ Message.prototype.decryptSessionKeys = async function(privateKeys, passwords) { return keyPackets.map(packet => ({ data: packet.sessionKey, algorithm: packet.sessionKeyAlgorithm })); } - throw new Error('Session key decryption failed.'); + throw exception || new Error('Session key decryption failed.'); }; /** diff --git a/test/security/index.js b/test/security/index.js index 5ca983e1..d515963f 100644 --- a/test/security/index.js +++ b/test/security/index.js @@ -2,4 +2,5 @@ describe('Security', function () { require('./message_signature_bypass'); require('./unsigned_subpackets'); require('./subkey_trust'); + require('./preferred_algo_mismatch'); }); diff --git a/test/security/preferred_algo_mismatch.js b/test/security/preferred_algo_mismatch.js new file mode 100644 index 00000000..74cb644d --- /dev/null +++ b/test/security/preferred_algo_mismatch.js @@ -0,0 +1,49 @@ +const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../../dist/openpgp'); + +const { key, cleartext, enums, packet: { List, Signature } } = openpgp; + +const chai = require('chai'); +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + +const messageArmor = `-----BEGIN PGP MESSAGE----- +Version: OpenPGP.js VERSION +Comment: https://openpgpjs.org + +wYwD3eCUoDfD5yoBA/4rhxaaw+E2ma+LdmLVDBRqxglhIgnM6EgNxzf8J5Ty +ecQBLOf3BjjC72mJ9RqMmvQ16aG4EXXDAUmCP1sBLj+b7V1t4keeyTn+2nXu +7Wgu2yq9CvZahRLsayt3y8VodZwTi3K/+gmx1f8EhdLPONQgGkYAqZ3Tyyd0 +KF3pknplvdI+AXqRs0n2vVr89oIdmQPJFSHEoJtltbSNxhwShdzDvOor2FKJ +vhGWNysion2aBg0fIbgDUKeXKp8YN44LDTk= +=RYrv +-----END PGP MESSAGE-----`; + +const privateKeyArmor = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: OpenPGP.js VERSION +Comment: https://openpgpjs.org + +xcEYBFvbA08BBACl8U5VEY7TNq1PAzwU0f3soqNfFpKtNFt+LY3q5sasouJ7 +zE4/TPYrAaAoM5/yOjfvbfJP5myBUCtkdtIRIY2iP2uOPhfaly8U+zH25Qnq +bmgLfvu4ytPAPrKZF8f98cIeJmHD81SPRgDMuB2U9wwgN6stgVBBCUS+lu/L +/4pyuwARAQABAAP+Jz6BIvcrCuJ0bCo8rEPZRHxWHKfO+m1Wcem+FV6Mf8lp +vJNdsfS2hwc0ZC2JVxTTo6kh1CmPYamfCXxcQ7bmsqWkkq/6d17zKE6BqE/n +spW7qTnZ14VPC0iPrBetAWRlCk+m0cEkRnBxqPOVBNd6VPcZyM7GUOGf/kiw +AsHf+nECANkN1tsqLJ3+pH2MRouF7yHevQ9OGg+rwetBO2a8avvcsAuoFjVw +hERpkHv/PQjKAE7KcBzqLLad0QbrQW+sUcMCAMO3to0tSBJrNA9YkrViT76I +siiahSB/FC9JlO+T46xncRleZeBHc0zoVAP+W/PjRo2CR4ydtwjjalrxcKX9 +E6kCALfDyhkRNzZLxg2XOGDWyeXqe80VWnMBqTZK73nZlACRcUoXuvjRc15Q +K2c3/nZ7LMyQidj8XsTq4sz1zfWz4Cejj80cVGVzdCBVc2VyIDx0ZXN0QGV4 +YW1wbGUuY29tPsK1BBABCAApBQJb2wNPAgsJCRDd4JSgN8PnKgQVCAoCAxYC +AQIZAQIbDwIeBwMiAQIAABGjA/4y6HjthMU03AC3bIUyYPv6EJc9czS5wysa +5rKuNhzka0Klb0INcX1YZ8usPIIl1rtr8f8xxCdSiqhJpn+uqIPVROHi0XLG +ej3gSJM5i1lIt1jxyJlvVI/7W0vzuE85KDzGXQFNFyO/T9D7T1SDHnS8KbBh +EnxUPL95HuMKoVkf4w== +=oopr +-----END PGP PRIVATE KEY BLOCK-----`; + +it('Does not accept message encrypted with algo not mentioned in preferred algorithms', async function() { + const message = await openpgp.message.readArmored(messageArmor); + const privKey = (await openpgp.key.readArmored(privateKeyArmor)).keys[0]; + await expect(openpgp.decrypt({ message, privateKeys: [privKey] })).to.be.rejectedWith('A non-preferred symmetric algorithm was used.'); +});