Throw on encryption using non-standard experimentalGCM AEAD algo

The `enums.aead.gcm` ID standardized by RFC9580 should be used instead.
This commit is contained in:
larabr 2024-11-21 11:24:49 +01:00
parent 4d2d8740dc
commit 6c3b02872d
6 changed files with 19 additions and 14 deletions

View File

@ -468,17 +468,22 @@ export function generateSessionKey(algo) {
/** /**
* Get implementation of the given AEAD mode * Get implementation of the given AEAD mode
* @param {enums.aead} algo * @param {enums.aead} algo
* @param {Boolean} [acceptExperimentalGCM] - whether to allow the non-standard, legacy `experimentalGCM` algo
* @returns {Object} * @returns {Object}
* @throws {Error} on invalid algo * @throws {Error} on invalid algo
*/ */
export function getAEADMode(algo) { export function getAEADMode(algo, acceptExperimentalGCM = false) {
switch (algo) { switch (algo) {
case enums.aead.eax: case enums.aead.eax:
return mode.eax; return mode.eax;
case enums.aead.ocb: case enums.aead.ocb:
return mode.ocb; return mode.ocb;
case enums.aead.gcm: case enums.aead.gcm:
return mode.gcm;
case enums.aead.experimentalGCM: case enums.aead.experimentalGCM:
if (!acceptExperimentalGCM) {
throw new Error('Unexpected non-standard `experimentalGCM` AEAD algorithm provided in `config.preferredAEADAlgorithm`: use `gcm` instead');
}
return mode.gcm; return mode.gcm;
default: default:
throw new Error('Unsupported AEAD mode'); throw new Error('Unsupported AEAD mode');

View File

@ -78,7 +78,7 @@ class AEADEncryptedDataPacket {
this.aeadAlgorithm = await reader.readByte(); this.aeadAlgorithm = await reader.readByte();
this.chunkSizeByte = await reader.readByte(); this.chunkSizeByte = await reader.readByte();
const mode = crypto.getAEADMode(this.aeadAlgorithm); const mode = crypto.getAEADMode(this.aeadAlgorithm, true);
this.iv = await reader.readBytes(mode.ivLength); this.iv = await reader.readBytes(mode.ivLength);
this.encrypted = reader.remainder(); this.encrypted = reader.remainder();
}); });
@ -119,7 +119,7 @@ class AEADEncryptedDataPacket {
async encrypt(sessionKeyAlgorithm, key, config = defaultConfig) { async encrypt(sessionKeyAlgorithm, key, config = defaultConfig) {
this.cipherAlgorithm = sessionKeyAlgorithm; this.cipherAlgorithm = sessionKeyAlgorithm;
const { ivLength } = crypto.getAEADMode(this.aeadAlgorithm); const { ivLength } = crypto.getAEADMode(this.aeadAlgorithm, true);
this.iv = crypto.random.getRandomBytes(ivLength); // generate new random IV this.iv = crypto.random.getRandomBytes(ivLength); // generate new random IV
this.chunkSizeByte = config.aeadChunkSizeByte; this.chunkSizeByte = config.aeadChunkSizeByte;
const data = this.packets.write(); const data = this.packets.write();

View File

@ -454,7 +454,7 @@ class SecretKeyPacket extends PublicKeyPacket {
let cleartext; let cleartext;
if (this.s2kUsage === 253) { if (this.s2kUsage === 253) {
const mode = crypto.getAEADMode(this.aead); const mode = crypto.getAEADMode(this.aead, true);
const modeInstance = await mode(this.symmetric, key); const modeInstance = await mode(this.symmetric, key);
try { try {
const associateData = this.isLegacyAEAD ? const associateData = this.isLegacyAEAD ?

View File

@ -234,7 +234,10 @@ export async function runAEAD(packet, fn, key, data) {
const isAEADP = !isSEIPDv2 && packet.constructor.tag === enums.packet.aeadEncryptedData; // no `instanceof` to avoid importing the corresponding class (circular import) const isAEADP = !isSEIPDv2 && packet.constructor.tag === enums.packet.aeadEncryptedData; // no `instanceof` to avoid importing the corresponding class (circular import)
if (!isSEIPDv2 && !isAEADP) throw new Error('Unexpected packet type'); if (!isSEIPDv2 && !isAEADP) throw new Error('Unexpected packet type');
const mode = crypto.getAEADMode(packet.aeadAlgorithm); // we allow `experimentalGCM` for AEADP for backwards compatibility, since v5 keys from OpenPGP.js v5 might be declaring
// that preference, as the `gcm` ID had not been standardized at the time.
// NB: AEADP are never automatically generate as part of message encryption by OpenPGP.js, the packet must be manually created.
const mode = crypto.getAEADMode(packet.aeadAlgorithm, isAEADP);
const tagLengthIfDecrypting = fn === 'decrypt' ? mode.tagLength : 0; const tagLengthIfDecrypting = fn === 'decrypt' ? mode.tagLength : 0;
const tagLengthIfEncrypting = fn === 'encrypt' ? mode.tagLength : 0; const tagLengthIfEncrypting = fn === 'encrypt' ? mode.tagLength : 0;
const chunkSize = 2 ** (packet.chunkSizeByte + 6) + tagLengthIfDecrypting; // ((uint64_t)1 << (c + 6)) const chunkSize = 2 ** (packet.chunkSizeByte + 6) + tagLengthIfDecrypting; // ((uint64_t)1 << (c + 6))

View File

@ -105,7 +105,7 @@ class SymEncryptedSessionKeyPacket {
offset += this.s2k.read(bytes.subarray(offset, bytes.length)); offset += this.s2k.read(bytes.subarray(offset, bytes.length));
if (this.version >= 5) { if (this.version >= 5) {
const mode = crypto.getAEADMode(this.aeadAlgorithm); const mode = crypto.getAEADMode(this.aeadAlgorithm, true);
// A starting initialization vector of size specified by the AEAD // A starting initialization vector of size specified by the AEAD
// algorithm. // algorithm.
@ -167,7 +167,7 @@ class SymEncryptedSessionKeyPacket {
const key = await this.s2k.produceKey(passphrase, keySize); const key = await this.s2k.produceKey(passphrase, keySize);
if (this.version >= 5) { if (this.version >= 5) {
const mode = crypto.getAEADMode(this.aeadAlgorithm); const mode = crypto.getAEADMode(this.aeadAlgorithm, true);
const adata = new Uint8Array([0xC0 | SymEncryptedSessionKeyPacket.tag, this.version, this.sessionKeyEncryptionAlgorithm, this.aeadAlgorithm]); const adata = new Uint8Array([0xC0 | SymEncryptedSessionKeyPacket.tag, this.version, this.sessionKeyEncryptionAlgorithm, this.aeadAlgorithm]);
const encryptionKey = this.version === 6 ? await computeHKDF(enums.hash.sha256, key, new Uint8Array(), adata, keySize) : key; const encryptionKey = this.version === 6 ? await computeHKDF(enums.hash.sha256, key, new Uint8Array(), adata, keySize) : key;
const modeInstance = await mode(algo, encryptionKey); const modeInstance = await mode(algo, encryptionKey);

View File

@ -2286,7 +2286,7 @@ function versionSpecificTests() {
openpgp.config.preferredSymmetricAlgorithm = openpgp.enums.symmetric.aes192; openpgp.config.preferredSymmetricAlgorithm = openpgp.enums.symmetric.aes192;
openpgp.config.preferredHashAlgorithm = openpgp.enums.hash.sha224; openpgp.config.preferredHashAlgorithm = openpgp.enums.hash.sha224;
openpgp.config.preferredCompressionAlgorithm = openpgp.enums.compression.zlib; openpgp.config.preferredCompressionAlgorithm = openpgp.enums.compression.zlib;
openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.experimentalGCM; openpgp.config.preferredAEADAlgorithm = openpgp.enums.aead.eax;
const testPref = function(key) { const testPref = function(key) {
const selfSignature = openpgp.config.v6Keys ? key.directSignatures[0] : key.users[0].selfCertifications[0]; const selfSignature = openpgp.config.v6Keys ? key.directSignatures[0] : key.users[0].selfCertifications[0];
@ -2301,15 +2301,12 @@ function versionSpecificTests() {
if (openpgp.config.aeadProtect) { if (openpgp.config.aeadProtect) {
const aead = openpgp.enums.aead; const aead = openpgp.enums.aead;
expect(selfSignature.preferredCipherSuites).to.eql([ expect(selfSignature.preferredCipherSuites).to.eql([
[sym.aes192, aead.experimentalGCM],
[sym.aes256, aead.experimentalGCM],
[sym.aes128, aead.experimentalGCM],
[sym.aes192, aead.gcm],
[sym.aes256, aead.gcm],
[sym.aes128, aead.gcm],
[sym.aes192, aead.eax], [sym.aes192, aead.eax],
[sym.aes256, aead.eax], [sym.aes256, aead.eax],
[sym.aes128, aead.eax], [sym.aes128, aead.eax],
[sym.aes192, aead.gcm],
[sym.aes256, aead.gcm],
[sym.aes128, aead.gcm],
[sym.aes192, aead.ocb], [sym.aes192, aead.ocb],
[sym.aes256, aead.ocb], [sym.aes256, aead.ocb],
[sym.aes128, aead.ocb] [sym.aes128, aead.ocb]