Look up preferred ciphersuite in one go

Instead of calling getPreferredAlgo('symmetric') and
getPreferredAlgo('aead'), we define and call getPreferredCipherSuite()
to determine the preferred symmetric and AEAD algorithm.

Additionally, we remove isAEADSupported(), instead we return
aeadAlgorithm: undefined from getPreferredCipherSuite() if AEAD is not
supported (CFB is used instead).

And finally, we define getPreferredCompressionAlgo() to replace
getPreferredAlgo('compression').
This commit is contained in:
Daniel Huigens 2023-03-16 18:17:26 +01:00 committed by larabr
parent e5fe84dc2e
commit f77ed0c0ed
6 changed files with 96 additions and 94 deletions

View File

@ -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<Key>} [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<module:enums.symmetric|aead|compression>} Preferred algorithm
* @returns {Promise<module:enums.compression>} 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<Key>} [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<Key>} 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<Boolean>}
* @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;

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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);