From f7b68a33279dabb60d00f501f27ea0b50e84ef83 Mon Sep 17 00:00:00 2001 From: larabr <7375870+larabr@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:04:41 +0200 Subject: [PATCH] Disable support for parsing v5 entities by default (add `config.enableParsingV5Entities`) Parsing of v5 keys, v5 signatures and AEAD-encrypted data packets now requires turning on the corresponding config flag. The affected entities are non-standard, and in the crypto-refresh RFC they have been superseded by v6 keys, v6 signatures and SEIPDv2 encrypted data, respectively. However, generation of v5 entities was supported behind config flag in OpenPGP.js v5, and some other libraries, hence parsing them might be necessary in some cases. --- src/config/config.js | 9 +++++++++ src/packet/aead_encrypted_data.js | 5 ++++- src/packet/public_key.js | 5 ++++- src/packet/secret_key.js | 2 +- src/packet/signature.js | 5 ++++- test/general/key.js | 29 ++++++++++++++--------------- test/general/packet.js | 14 +++++++------- test/general/signature.js | 7 ++++--- 8 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/config/config.js b/src/config/config.js index 866a0c34..5e4ae45e 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -81,6 +81,15 @@ export default { * @property {Boolean} v6Keys */ v6Keys: false, + /** + * Enable parsing v5 keys, v5 signatures and AEAD-encrypted data packets + * (which is different from the AEAD-encrypted SEIPDv2 packet). + * These are non-standard entities, which in the crypto-refresh have been superseded + * by v6 keys, v6 signatures and SEIPDv2 encrypted data, respectively. + * However, generation of v5 entities was supported behind config flag in OpenPGP.js v5, and some other libraries, + * hence parsing them might be necessary in some cases. + */ + enableParsingV5Entities: false, /** * S2K (String to Key) type, used for key derivation in the context of secret key encryption * and password-encrypted data. Weaker s2k options are not allowed. diff --git a/src/packet/aead_encrypted_data.js b/src/packet/aead_encrypted_data.js index 60bdc3a7..492fa8ef 100644 --- a/src/packet/aead_encrypted_data.js +++ b/src/packet/aead_encrypted_data.js @@ -68,7 +68,10 @@ class AEADEncryptedDataPacket { * @param {Uint8Array | ReadableStream} bytes * @throws {Error} on parsing failure */ - async read(bytes) { + async read(bytes, config = defaultConfig) { + if (!config.enableParsingV5Entities) { + throw new UnsupportedError('Support for parsing v5 entities is disabled; turn on `config.enableParsingV5Entities` if needed'); + } await stream.parse(bytes, async reader => { const version = await reader.readByte(); if (version !== VERSION) { // The only currently defined value is 1. diff --git a/src/packet/public_key.js b/src/packet/public_key.js index 17732e07..c3b13071 100644 --- a/src/packet/public_key.js +++ b/src/packet/public_key.js @@ -104,10 +104,13 @@ class PublicKeyPacket { * @returns {Object} This object with attributes set by the parser * @async */ - async read(bytes) { + async read(bytes, config = defaultConfig) { let pos = 0; // A one-octet version number (4, 5 or 6). this.version = bytes[pos++]; + if (this.version === 5 && !config.enableParsingV5Entities) { + throw new UnsupportedError('Support for parsing v5 entities is disabled; turn on `config.enableParsingV5Entities` if needed'); + } if (this.version === 4 || this.version === 5 || this.version === 6) { // - A four-octet number denoting the time that the key was created. diff --git a/src/packet/secret_key.js b/src/packet/secret_key.js index 2cdac5b6..2257a78b 100644 --- a/src/packet/secret_key.js +++ b/src/packet/secret_key.js @@ -102,7 +102,7 @@ class SecretKeyPacket extends PublicKeyPacket { */ async read(bytes, config = defaultConfig) { // - A Public-Key or Public-Subkey packet, as described above. - let i = await this.readPublicKey(bytes); + let i = await this.readPublicKey(bytes, config); const startOfSecretKeyData = i; // - One octet indicating string-to-key usage conventions. Zero diff --git a/src/packet/signature.js b/src/packet/signature.js index a6bbc8d5..87e0d304 100644 --- a/src/packet/signature.js +++ b/src/packet/signature.js @@ -117,9 +117,12 @@ class SignaturePacket { * @param {String} bytes - Payload of a tag 2 packet * @returns {SignaturePacket} Object representation. */ - read(bytes) { + read(bytes, config = defaultConfig) { let i = 0; this.version = bytes[i++]; + if (this.version === 5 && !config.enableParsingV5Entities) { + throw new UnsupportedError('Support for v5 entities is disabled; turn on `config.enableParsingV5Entities` if needed'); + } if (this.version !== 4 && this.version !== 5 && this.version !== 6) { throw new UnsupportedError(`Version ${this.version} of the signature packet is unsupported.`); diff --git a/test/general/key.js b/test/general/key.js index 570671fd..0ff089b1 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -2951,19 +2951,6 @@ function versionSpecificTests() { await expect(privateKey.verifyPrimaryKey()).to.be.fulfilled; }); }); - - - it('Parses V5 sample key', async function() { - // sec ed25519 2019-03-20 [SC] - // 19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54 - // uid emma.goldman@example.net - // ssb cv25519 2019-03-20 [E] - // E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965 - const key = await openpgp.readKey({ armoredKey: v5_sample_key }); - expect(await key.keyPacket.getFingerprint()).to.equal('19347bc9872464025f99df3ec2e0000ed9884892e1f7b3ea4c94009159569b54'); - expect(await key.subkeys[0].getFingerprint()).to.equal('e4557c2b02ffbf4b04f87401ec336af7133d0f85be7fd09baefd9caeb8c93965'); - await key.verifyPrimaryKey(); - }); } export default () => describe('Key', function() { @@ -3032,6 +3019,18 @@ export default () => describe('Key', function() { expect(key.write()).to.deep.equal(expectedSerializedKey.data); }); + it('Parses V5 sample key', async function() { + // sec ed25519 2019-03-20 [SC] + // 19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54 + // uid emma.goldman@example.net + // ssb cv25519 2019-03-20 [E] + // E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965 + const key = await openpgp.readKey({ armoredKey: v5_sample_key, config: { enableParsingV5Entities: true } }); + expect(await key.keyPacket.getFingerprint()).to.equal('19347bc9872464025f99df3ec2e0000ed9884892e1f7b3ea4c94009159569b54'); + expect(await key.subkeys[0].getFingerprint()).to.equal('e4557c2b02ffbf4b04f87401ec336af7133d0f85be7fd09baefd9caeb8c93965'); + await key.verifyPrimaryKey(); + }); + it('Parsing V5 public key packet', async function() { // Manually modified from https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b2092/back.mkd#sample-eddsa-key const packetBytes = util.hexToUint8Array(` @@ -3065,7 +3064,7 @@ T/efFOC6BDkAAHcjAPwIPNHnR9bKmkVop6cE05dCIpZ/W8zXDGnjKYrrC4Hb =wpkQ -----END PGP PRIVATE KEY BLOCK-----`; const passphrase = 'password'; - const encryptedKey = await openpgp.readKey({ armoredKey }); + const encryptedKey = await openpgp.readKey({ armoredKey, config: { enableParsingV5Entities: true } }); const decryptedKey = await openpgp.decryptKey({ privateKey: encryptedKey, passphrase @@ -3485,7 +3484,7 @@ PzIEeL7UH3trraFmi+Gq8u4kAA== }); it('should pad an ECDSA P-521 key with shorter secret key', async function() { - const key = await openpgp.readKey({ armoredKey: shortP521Key }); + const key = await openpgp.readKey({ armoredKey: shortP521Key, config: { enableParsingV5Entities: true } }); // secret key should be padded expect(key.keyPacket.privateParams.d.length === 66); // sanity check diff --git a/test/general/packet.js b/test/general/packet.js index 78f5d621..5d7449b5 100644 --- a/test/general/packet.js +++ b/test/general/packet.js @@ -174,7 +174,7 @@ export default () => describe('Packet', function() { const msg2 = new openpgp.PacketList(); return enc.encrypt(algo, key, undefined, openpgp.config).then(async function() { - await msg2.read(msg.write(), allAllowedPackets); + await msg2.read(msg.write(), allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true }); return msg2[0].decrypt(algo, key); }).then(async function() { expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); @@ -229,7 +229,7 @@ export default () => describe('Packet', function() { try { await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 0 }); - await msg2.read(msg.write(), allAllowedPackets); + await msg2.read(msg.write(), allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true }); await msg2[0].decrypt(algo, key); expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); expect(encryptStub.callCount > 1).to.be.true; @@ -276,7 +276,7 @@ export default () => describe('Packet', function() { await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 14 }); const data = msg.write(); expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); - await msg2.read(data, allAllowedPackets); + await msg2.read(data, allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true }); await msg2[0].decrypt(algo, key); expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); } finally { @@ -706,7 +706,7 @@ export default () => describe('Packet', function() { await aeadEnc.encrypt(algo, key, undefined, openpgp.config); const msg2 = new openpgp.PacketList(); - await msg2.read(msg.write(), allAllowedPackets); + await msg2.read(msg.write(), allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true }); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; @@ -744,7 +744,7 @@ export default () => describe('Packet', function() { await aeadEnc.encrypt(algo, key, undefined, openpgp.config); const msg2 = new openpgp.PacketList(); - await msg2.read(msg.write(), allAllowedPackets); + await msg2.read(msg.write(), allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true }); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; @@ -820,7 +820,7 @@ export default () => describe('Packet', function() { expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); const msg2 = new openpgp.PacketList(); - await msg2.read(data, allAllowedPackets); + await msg2.read(data, allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true }); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; @@ -899,7 +899,7 @@ export default () => describe('Packet', function() { expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); const msg2 = new openpgp.PacketList(); - await msg2.read(data, allAllowedPackets); + await msg2.read(data, allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true }); await msg2[0].decrypt(passphrase); const key2 = msg2[0].sessionKey; diff --git a/test/general/signature.js b/test/general/signature.js index 8d0de696..648dd890 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -2417,7 +2417,7 @@ oaBUyhCKt8tz6Q== -----END PGP PRIVATE KEY BLOCK-----`; const key = await openpgp.readKey({ armoredKey }); const decrypted = await openpgp.decrypt({ - message: await openpgp.readMessage({ armoredMessage: encrypted }), + message: await openpgp.readMessage({ armoredMessage: encrypted, config: { enableParsingV5Entities: true } }), verificationKeys: key, decryptionKeys: key, config: { minRSABits: 1024 } @@ -2476,7 +2476,8 @@ EYaN9YdDOU2jF+HOaSNaJAsPF8J6BRgTCAAJBQJf0mstAhsMACMiIQUee6Tb AcvDfr9a0Cp4WAVzKDKLUzrRMgEAozi0VyjiBo1U2LcwTPJkA4PEQqQRVW1D KZTMSAH7JEo= =tqWy ------END PGP PRIVATE KEY BLOCK-----` +-----END PGP PRIVATE KEY BLOCK-----`, + config: { enableParsingV5Entities: true } }); const signed = `-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 @@ -2489,7 +2490,7 @@ A3X6psihFkcA+Nuog2qpAq20Zc2lzVjDZzQosb8MLvKMg3UFCX12Oc0BAJwd JImeZLY02MctIpGZULbqgcUGK0P/yqrPL8Pe4lQM =Pacb -----END PGP SIGNATURE-----`; - const message = await openpgp.readCleartextMessage({ cleartextMessage: signed }); + const message = await openpgp.readCleartextMessage({ cleartextMessage: signed, config: { enableParsingV5Entities: true } }); const verified = await openpgp.verify({ verificationKeys: key, message }); expect(await verified.signatures[0].verified).to.be.true; });