diff --git a/src/key/helper.js b/src/key/helper.js index 766f41c9..15ed4370 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -134,43 +134,59 @@ export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), us } /** - * Returns the preferred symmetric/aead/compression algorithm for a set of keys - * @param {'symmetric'|'aead'|'compression'} type - Type of preference to return + * Returns the preferred compression algorithm for a set of keys * @param {Array} [keys] - Set of keys * @param {Date} [date] - Use the given date for verification instead of the current time * @param {Array} [userIDs] - User IDs * @param {Object} [config] - Full configuration, defaults to openpgp.config - * @returns {Promise} Preferred algorithm + * @returns {Promise} Preferred compression algorithm * @async */ -export async function getPreferredAlgo(type, keys = [], date = new Date(), userIDs = [], config = defaultConfig) { - const defaultAlgo = { // these are all must-implement in the crypto refresh - 'symmetric': enums.symmetric.aes128, - 'aead': enums.aead.ocb, - 'compression': enums.compression.uncompressed - }[type]; - const preferredSenderAlgo = { - 'symmetric': config.preferredSymmetricAlgorithm, - 'aead': config.preferredAEADAlgorithm, - 'compression': config.preferredCompressionAlgorithm - }[type]; - const prefPropertyName = { - 'symmetric': 'preferredSymmetricAlgorithms', - 'aead': 'preferredAEADAlgorithms', - 'compression': 'preferredCompressionAlgorithms' - }[type]; +export async function getPreferredCompressionAlgo(keys = [], date = new Date(), userIDs = [], config = defaultConfig) { + const defaultAlgo = enums.compression.uncompressed; + const preferredSenderAlgo = config.preferredCompressionAlgorithm; // if preferredSenderAlgo appears in the prefs of all recipients, we pick it // otherwise we use the default algo // if no keys are available, preferredSenderAlgo is returned const senderAlgoSupport = await Promise.all(keys.map(async function(key, i) { const selfCertification = await key.getPrimarySelfSignature(date, userIDs[i], config); - const recipientPrefs = selfCertification[prefPropertyName]; + const recipientPrefs = selfCertification.preferredCompressionAlgorithms; return !!recipientPrefs && recipientPrefs.indexOf(preferredSenderAlgo) >= 0; })); return senderAlgoSupport.every(Boolean) ? preferredSenderAlgo : defaultAlgo; } +/** + * Returns the preferred symmetric and AEAD algorithm (if any) for a set of keys + * @param {Array} [keys] - Set of keys + * @param {Date} [date] - Use the given date for verification instead of the current time + * @param {Array} [userIDs] - User IDs + * @param {Object} [config] - Full configuration, defaults to openpgp.config + * @returns {Promise<{ symmetricAlgo: module:enums.symmetric, aeadAlgo: module:enums.aead | undefined }>} Object containing the preferred symmetric algorithm, and the preferred AEAD algorithm, or undefined if CFB is preferred + * @async + */ +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 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( + cipherSuite => cipherSuite[0] === desiredCipherSuite.symmetricAlgo && cipherSuite[1] === desiredCipherSuite.aeadAlgo + )) ? + desiredCipherSuite : + defaultCipherSuite; + } + const defaultSymAlgo = enums.symmetric.aes128; + const desiredSymAlgo = config.preferredSymmetricAlgorithm; + return { + symmetricAlgo: selfSigs.every(selfSig => selfSig.preferredSymmetricAlgorithms && selfSig.preferredSymmetricAlgorithms.includes(desiredSymAlgo)) ? + desiredSymAlgo : + defaultSymAlgo, + aeadAlgo: undefined + }; +} + /** * Create signature packet * @param {Object} dataToSign - Contains packets to be signed @@ -293,28 +309,6 @@ export function getKeyExpirationTime(keyPacket, signature) { return expirationTime ? new Date(expirationTime) : Infinity; } -/** - * Returns whether aead is supported by all keys in the set - * @param {Array} keys - Set of keys - * @param {Date} [date] - Use the given date for verification instead of the current time - * @param {Array} [userIDs] - User IDs - * @param {Object} config - full configuration - * @returns {Promise} - * @async - */ -export async function isAEADSupported(keys, date = new Date(), userIDs = [], config = defaultConfig) { - let supported = true; - // TODO replace when Promise.some or Promise.any are implemented - await Promise.all(keys.map(async function(key, i) { - const selfCertification = await key.getPrimarySelfSignature(date, userIDs[i], config); - if (!selfCertification.features || - !(selfCertification.features[0] & enums.features.seipdv2)) { - supported = false; - } - })); - return supported; -} - export function sanitizeKeyOptions(options, subkeyDefaults = {}) { options.type = options.type || subkeyDefaults.type; options.curve = options.curve || subkeyDefaults.curve; diff --git a/src/key/index.js b/src/key/index.js index 47c43019..6781baa9 100644 --- a/src/key/index.js +++ b/src/key/index.js @@ -8,9 +8,9 @@ import { } from './factory'; import { - getPreferredAlgo, - isAEADSupported, getPreferredHashAlgo, + getPreferredCompressionAlgo, + getPreferredCipherSuite, createSignaturePacket } from './helper'; @@ -25,9 +25,9 @@ export { readPrivateKeys, generate, reformat, - getPreferredAlgo, - isAEADSupported, getPreferredHashAlgo, + getPreferredCompressionAlgo, + getPreferredCipherSuite, createSignaturePacket, PrivateKey, PublicKey, diff --git a/src/message.js b/src/message.js index a17f523c..fac3eb96 100644 --- a/src/message.js +++ b/src/message.js @@ -24,7 +24,7 @@ import crypto from './crypto'; import enums from './enums'; import util from './util'; import { Signature } from './signature'; -import { getPreferredHashAlgo, getPreferredAlgo, isAEADSupported, createSignaturePacket } from './key'; +import { getPreferredHashAlgo, getPreferredCipherSuite, createSignaturePacket } from './key'; import { PacketList, LiteralDataPacket, @@ -343,23 +343,21 @@ export class Message { * @async */ static async generateSessionKey(encryptionKeys = [], date = new Date(), userIDs = [], config = defaultConfig) { - const algo = await getPreferredAlgo('symmetric', encryptionKeys, date, userIDs, config); - const algorithmName = enums.read(enums.symmetric, algo); - const aeadAlgorithmName = config.aeadProtect && await isAEADSupported(encryptionKeys, date, userIDs, config) ? - enums.read(enums.aead, await getPreferredAlgo('aead', encryptionKeys, date, userIDs, config)) : - undefined; + const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite(encryptionKeys, date, userIDs, config); + const symmetricAlgoName = enums.read(enums.symmetric, symmetricAlgo); + const aeadAlgoName = aeadAlgo ? enums.read(enums.aead, aeadAlgo) : undefined; await Promise.all(encryptionKeys.map(key => key.getEncryptionKey() .catch(() => null) // ignore key strength requirements .then(maybeKey => { - if (maybeKey && (maybeKey.keyPacket.algorithm === enums.publicKey.x25519) && !util.isAES(algo)) { + if (maybeKey && (maybeKey.keyPacket.algorithm === enums.publicKey.x25519) && !util.isAES(symmetricAlgo)) { throw new Error('Could not generate a session key compatible with the given `encryptionKeys`: X22519 keys can only be used to encrypt AES session keys; change `config.preferredSymmetricAlgorithm` accordingly.'); } }) )); - const sessionKeyData = crypto.generateSessionKey(algo); - return { data: sessionKeyData, algorithm: algorithmName, aeadAlgorithm: aeadAlgorithmName }; + const sessionKeyData = crypto.generateSessionKey(symmetricAlgo); + return { data: sessionKeyData, algorithm: symmetricAlgoName, aeadAlgorithm: aeadAlgoName }; } /** diff --git a/src/openpgp.js b/src/openpgp.js index cef01e9b..223ebfdf 100644 --- a/src/openpgp.js +++ b/src/openpgp.js @@ -18,7 +18,7 @@ import * as stream from '@openpgp/web-stream-tools'; import { Message } from './message'; import { CleartextMessage } from './cleartext'; -import { generate, reformat, getPreferredAlgo } from './key'; +import { generate, reformat, getPreferredCompressionAlgo } from './key'; import defaultConfig from './config'; import util from './util'; import { checkKeyRequirements } from './key/helper'; @@ -284,7 +284,7 @@ export async function encrypt({ message, encryptionKeys, signingKeys, passwords, message = await message.sign(signingKeys, signature, signingKeyIDs, date, signingUserIDs, signatureNotations, config); } message = message.compress( - await getPreferredAlgo('compression', encryptionKeys, date, encryptionUserIDs, config), + await getPreferredCompressionAlgo(encryptionKeys, date, encryptionUserIDs, config), config ); message = await message.encrypt(encryptionKeys, passwords, sessionKey, wildcard, encryptionKeyIDs, date, encryptionUserIDs, config); diff --git a/test/general/key.js b/test/general/key.js index 2f266ef9..9ed68d97 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -6,7 +6,7 @@ chaiUse(chaiAsPromised); import openpgp from '../initOpenpgp.js'; import util from '../../src/util.js'; -import { isAEADSupported, getPreferredAlgo } from '../../src/key'; +import { getPreferredCipherSuite } from '../../src/key'; import KeyID from '../../src/type/keyid.js'; @@ -3643,76 +3643,85 @@ aU71tdtNBQ== expect(revKey.armor()).not.to.match(/Comment: This is a revocation certificate/); }); - it("getPreferredAlgo('symmetric') - one key", async function() { + it('getPreferredCipherSuite - one key', async function() { const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys }); - const prefAlgo = await getPreferredAlgo('symmetric', [key1], undefined, undefined, { + const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, { ...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes256 }); - expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes256); + expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256); + expect(aeadAlgo).to.equal(undefined); }); - it("getPreferredAlgo('symmetric') - two key", async function() { + it('getPreferredCipherSuite - two keys', async function() { const { aes128, aes192, cast5 } = openpgp.enums.symmetric; const [key1, key2] = await openpgp.readKeys({ armoredKeys: twoKeys }); const primaryUser = await key2.getPrimaryUser(); primaryUser.selfCertification.preferredSymmetricAlgorithms = [6, aes192, cast5]; - const prefAlgo = await getPreferredAlgo('symmetric', [key1, key2], undefined, undefined, { + const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, { ...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes192 }); - expect(prefAlgo).to.equal(aes192); - const prefAlgo2 = await getPreferredAlgo('symmetric', [key1, key2], undefined, undefined, { + expect(symmetricAlgo).to.equal(aes192); + expect(aeadAlgo).to.equal(undefined); + const { symmetricAlgo: symmetricAlgo2, aeadAlgo: aeadAlgo2 } = await getPreferredCipherSuite([key1, key2], undefined, undefined, { ...openpgp.config, preferredSymmetricAlgorithm: openpgp.enums.symmetric.aes256 }); - expect(prefAlgo2).to.equal(aes128); + expect(symmetricAlgo2).to.equal(aes128); + expect(aeadAlgo2).to.equal(undefined); }); - it("getPreferredAlgo('symmetric') - two key - one without pref", async function() { + it('getPreferredCipherSuite - two keys - one without pref', async function() { const [key1, key2] = await openpgp.readKeys({ armoredKeys: twoKeys }); const primaryUser = await key2.getPrimaryUser(); primaryUser.selfCertification.preferredSymmetricAlgorithms = null; - const prefAlgo = await getPreferredAlgo('symmetric', [key1, key2]); - expect(prefAlgo).to.equal(openpgp.enums.symmetric.aes128); + const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2]); + expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128); + expect(aeadAlgo).to.equal(undefined); }); - it("getPreferredAlgo('aead') - one key - OCB", async function() { + it('getPreferredCipherSuite with AEAD - one key - GCM', async function() { const [key1] = await openpgp.readKeys({ armoredKeys: twoKeys }); const primaryUser = await key1.getPrimaryUser(); - primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag - primaryUser.selfCertification.preferredAEADAlgorithms = [2,1]; - const prefAlgo = await getPreferredAlgo('aead', [key1], undefined, undefined, { - ...openpgp.config, preferredAEADAlgorithm: openpgp.enums.aead.ocb + primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag + primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]]; + const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1], undefined, undefined, { + ...openpgp.config, + aeadProtect: true, + preferredAEADAlgorithm: openpgp.enums.aead.gcm }); - expect(prefAlgo).to.equal(openpgp.enums.aead.ocb); - const supported = await isAEADSupported([key1]); - expect(supported).to.be.true; + expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256); + expect(aeadAlgo).to.equal(openpgp.enums.aead.gcm); }); - it("getPreferredAlgo('aead') - two key - one without pref", async function() { + it('getPreferredCipherSuite with AEAD - two keys - one without pref', async function() { const keys = await openpgp.readKeys({ armoredKeys: twoKeys }); const key1 = keys[0]; const key2 = keys[1]; const primaryUser = await key1.getPrimaryUser(); - primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag - primaryUser.selfCertification.preferredAEADAlgorithms = [2,1]; + primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag + primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]]; const primaryUser2 = await key2.getPrimaryUser(); - primaryUser2.selfCertification.features = [7]; // Monkey-patch AEAD feature flag - const prefAlgo = await getPreferredAlgo('aead', [key1, key2]); - expect(prefAlgo).to.equal(openpgp.enums.aead.eax); - const supported = await isAEADSupported([key1, key2]); - expect(supported).to.be.true; + primaryUser2.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag + const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, { + ...openpgp.config, + aeadProtect: true + }); + expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes128); + expect(aeadAlgo).to.equal(openpgp.enums.aead.ocb); }); - it("getPreferredAlgo('aead') - two key - one with no support", async function() { + it('getPreferredCipherSuite with AEAD - two keys - one with no support', async function() { const keys = await openpgp.readKeys({ armoredKeys: twoKeys }); const key1 = keys[0]; const key2 = keys[1]; const primaryUser = await key1.getPrimaryUser(); - primaryUser.selfCertification.features = [7]; // Monkey-patch AEAD feature flag - primaryUser.selfCertification.preferredAEADAlgorithms = [2,1]; - const prefAlgo = await getPreferredAlgo('aead', [key1, key2]); - expect(prefAlgo).to.equal(openpgp.enums.aead.eax); - const supported = await isAEADSupported([key1, key2]); - expect(supported).to.be.false; + primaryUser.selfCertification.features = [9]; // Monkey-patch SEIPDv2 feature flag + primaryUser.selfCertification.preferredCipherSuites = [[9, 3], [9, 2]]; + const { symmetricAlgo, aeadAlgo } = await getPreferredCipherSuite([key1, key2], undefined, undefined, { + ...openpgp.config, + aeadProtect: true + }); + expect(symmetricAlgo).to.equal(openpgp.enums.symmetric.aes256); + expect(aeadAlgo).to.equal(undefined); }); it('User attribute packet read & write', async function() { diff --git a/test/general/openpgp.js b/test/general/openpgp.js index c0f19df8..16a2c949 100644 --- a/test/general/openpgp.js +++ b/test/general/openpgp.js @@ -11,7 +11,7 @@ import crypto from '../../src/crypto'; import * as random from '../../src/crypto/random.js'; import util from '../../src/util.js'; import keyIDType from '../../src/type/keyid.js'; -import { isAEADSupported } from '../../src/key'; +import { getPreferredCipherSuite } from '../../src/key'; import * as input from './testInputs.js'; @@ -3054,7 +3054,8 @@ XfA3pqV4mTzF it('should fail to decrypt modified message', async function() { const allowUnauthenticatedStream = openpgp.config.allowUnauthenticatedStream; const { privateKey: key } = await openpgp.generateKey({ userIDs: [{ email: 'test@email.com' }], format: 'object' }); - expect(await isAEADSupported([key])).to.equal(openpgp.config.aeadProtect); + const { aeadAlgo } = await getPreferredCipherSuite([key], undefined, undefined, openpgp.config); + expect(!!aeadAlgo).to.equal(openpgp.config.aeadProtect); const data = await openpgp.encrypt({ message: await openpgp.createMessage({ binary: new Uint8Array(500) }), encryptionKeys: [key.toPublic()] }); const encrypted = data.substr(0, 500) + (data[500] === 'a' ? 'b' : 'a') + data.substr(501);