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`.
This commit is contained in:
larabr 2023-10-19 14:22:31 +02:00 committed by larabr
parent 97ebd14829
commit 105b3cdde4
3 changed files with 87 additions and 39 deletions

View File

@ -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
*/

View File

@ -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(

View File

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