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