From 105b3cdde4af84088ec10a6a01363bb24537f7b2 Mon Sep 17 00:00:00 2001 From: larabr Date: Thu, 19 Oct 2023 14:22:31 +0200 Subject: [PATCH] Disregard `config.aeadProtect` when encrypting to public keys (#1678) Determine whether AEAD should be used for encryption solely based the encryption key preferences. Previously, the config flag was also used to control the behaviour, since AEAD messages were not standardised nor widely supported. To generate keys that declare AEAD in their preferences, use `generateKey` with `config.aeadProtect = true`. --- src/config/config.js | 9 ++-- src/key/helper.js | 6 ++- test/general/openpgp.js | 111 +++++++++++++++++++++++++++------------- 3 files changed, 87 insertions(+), 39 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 2b8c35f1..3f93e720 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -42,12 +42,15 @@ export default { * @property {Integer} deflateLevel Default zip/zlib compression level, between 1 and 9 */ deflateLevel: 6, - /** * Use Authenticated Encryption with Additional Data (AEAD) protection for symmetric encryption. + * This option is applicable to: + * - key generation (encryption key preferences), + * - password-based message encryption, and + * - private key encryption. + * In the case of message encryption using public keys, the encryption key preferences are respected instead. * Note: not all OpenPGP implementations are compatible with this option. - * **FUTURE OPENPGP.JS VERSIONS MAY BREAK COMPATIBILITY WHEN USING THIS OPTION** - * @see {@link https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-07|RFC4880bis-07} + * @see {@link https://tools.ietf.org/html/draft-ietf-openpgp-crypto-refresh-10.html|draft-crypto-refresh-10} * @memberof module:config * @property {Boolean} aeadProtect */ diff --git a/src/key/helper.js b/src/key/helper.js index 15ed4370..852a0c40 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -168,7 +168,11 @@ export async function getPreferredCompressionAlgo(keys = [], date = new Date(), */ export async function getPreferredCipherSuite(keys = [], date = new Date(), userIDs = [], config = defaultConfig) { const selfSigs = await Promise.all(keys.map((key, i) => key.getPrimarySelfSignature(date, userIDs[i], config))); - if (config.aeadProtect && selfSigs.every(selfSig => selfSig.features[0] & enums.features.seipdv2)) { + const withAEAD = keys.length ? + selfSigs.every(selfSig => selfSig.features[0] & enums.features.seipdv2) : + config.aeadProtect; + + if (withAEAD) { const defaultCipherSuite = { symmetricAlgo: enums.symmetric.aes128, aeadAlgo: enums.aead.ocb }; const desiredCipherSuite = { symmetricAlgo: config.preferredSymmetricAlgorithm, aeadAlgo: config.preferredAEADAlgorithm }; return selfSigs.every(selfSig => selfSig.preferredCipherSuites && selfSig.preferredCipherSuites.some( diff --git a/test/general/openpgp.js b/test/general/openpgp.js index 948e8862..2ca7bdbd 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -2040,13 +2040,6 @@ aOU= }); describe('encrypt - unit tests', function() { - it('Does not encrypt to expired key (expiration time subpacket on a direct-key signature)', async function() { - const expiredKey = await openpgp.readKey({ armoredKey: expiredPublicKeyThroughDirectSignature }); - await expect( - openpgp.encrypt({ message: await openpgp.createMessage({ text: 'test' }), encryptionKeys: expiredKey }) - ).to.be.rejectedWith(/Primary key is expired/); - }); - it('should output message of expected format', async function() { const passwords = 'password'; const text = 'test'; @@ -2213,6 +2206,55 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu }); expect(decrypted.data).to.equal('test'); }); + + it('does not encrypt to expired key (expiration time subpacket on a direct-key signature)', async function() { + const expiredKey = await openpgp.readKey({ armoredKey: expiredPublicKeyThroughDirectSignature }); + await expect( + openpgp.encrypt({ message: await openpgp.createMessage({ text: 'test' }), encryptionKeys: expiredKey }) + ).to.be.rejectedWith(/Primary key is expired/); + }); + + it('uses AEAD when the encryption key prefs support it (SEIPv2', async function() { + const v4PrivateKeyWithOCBPref = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB +exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ +BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh +RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe +7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/ +LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG +GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE +M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr +k0mXubZvyl4GBg== +-----END PGP PRIVATE KEY BLOCK-----` }); + const v6PrivateKeyWithOCBPref = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB +exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ +BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh +RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe +7XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/ +LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG +GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6 +2azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE +M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr +k0mXubZvyl4GBg== +-----END PGP PRIVATE KEY BLOCK-----` }); + + const encrypted = await openpgp.encrypt({ + message: await openpgp.createMessage({ text: 'test' }), + encryptionKeys: [v4PrivateKeyWithOCBPref, v6PrivateKeyWithOCBPref], + format: 'object' + }); + + const seipd = encrypted.packets[2]; + expect(seipd).to.be.instanceOf(openpgp.SymEncryptedIntegrityProtectedDataPacket); + expect(seipd.version).to.equal(2); + expect(seipd.aeadAlgorithm).to.equal(openpgp.enums.aead.ocb); + }); }); describe('encryptSessionKey - unit tests', function() { @@ -2241,34 +2283,6 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu })).to.be.rejectedWith(/No encryption keys or passwords provided/); }); - it('supports decrypting with argon2 s2k (memory-heavy params)', async function() { - const passwords = 'password'; - // Test vector from https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#appendix-A.8.1 - const armoredMessage = `-----BEGIN PGP MESSAGE----- -Comment: Encrypted using AES with 128-bit key -Comment: Session key: 01FE16BBACFD1E7B78EF3B865187374F - -wycEBwScUvg8J/leUNU1RA7N/zE2AQQVnlL8rSLPP5VlQsunlO+ECxHSPgGYGKY+ -YJz4u6F+DDlDBOr5NRQXt/KJIf4m4mOlKyC/uqLbpnLJZMnTq3o79GxBTdIdOzhH -XfA3pqV4mTzF ------END PGP MESSAGE-----`; - const expectedSessionKey = util.hexToUint8Array('01FE16BBACFD1E7B78EF3B865187374F'); - - try { - const [decryptedSessionKey] = await openpgp.decryptSessionKeys({ - message: await openpgp.readMessage({ armoredMessage }), - passwords - }); - expect(decryptedSessionKey.data).to.deep.equal(expectedSessionKey); - expect(decryptedSessionKey.algorithm).to.equal('aes128'); - } catch (err) { - if (detectBrowser()) { // Expected to fail in the CI, especially in Browserstack - expect(err.message).to.match(/Could not allocate required memory/); - } - } - - }); - // keep this after the 'memory-heavy' test to confirm that the Wasm module was successfully reloaded it('supports encrypting with argon2 s2k', async function() { const config = { s2kType: openpgp.enums.s2k.argon2 }; @@ -2349,6 +2363,33 @@ k0mXubZvyl4GBg== expect(sessionKeys).to.have.length(1); expect(sessionKeys[0].algorithm).to.equal(null); // PKESK v6 does not include the algo info }); + + it('supports decrypting with argon2 s2k (memory-heavy params)', async function() { + const passwords = 'password'; + // Test vector from https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#appendix-A.8.1 + const armoredMessage = `-----BEGIN PGP MESSAGE----- +Comment: Encrypted using AES with 128-bit key +Comment: Session key: 01FE16BBACFD1E7B78EF3B865187374F + +wycEBwScUvg8J/leUNU1RA7N/zE2AQQVnlL8rSLPP5VlQsunlO+ECxHSPgGYGKY+ +YJz4u6F+DDlDBOr5NRQXt/KJIf4m4mOlKyC/uqLbpnLJZMnTq3o79GxBTdIdOzhH +XfA3pqV4mTzF +-----END PGP MESSAGE-----`; + const expectedSessionKey = util.hexToUint8Array('01FE16BBACFD1E7B78EF3B865187374F'); + + try { + const [decryptedSessionKey] = await openpgp.decryptSessionKeys({ + message: await openpgp.readMessage({ armoredMessage }), + passwords + }); + expect(decryptedSessionKey.data).to.deep.equal(expectedSessionKey); + expect(decryptedSessionKey.algorithm).to.equal('aes128'); + } catch (err) { + if (detectBrowser()) { // Expected to fail in the CI, especially in Browserstack + expect(err.message).to.match(/Could not allocate required memory/); + } + } + }); }); describe('encrypt, decrypt, sign, verify - integration tests', function() {