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() {