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.
This commit is contained in:
larabr 2024-07-01 19:04:41 +02:00
parent 8d11c5fd0f
commit f7b68a3327
8 changed files with 47 additions and 29 deletions

View File

@ -81,6 +81,15 @@ export default {
* @property {Boolean} v6Keys * @property {Boolean} v6Keys
*/ */
v6Keys: false, 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 * 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. * and password-encrypted data. Weaker s2k options are not allowed.

View File

@ -68,7 +68,10 @@ class AEADEncryptedDataPacket {
* @param {Uint8Array | ReadableStream<Uint8Array>} bytes * @param {Uint8Array | ReadableStream<Uint8Array>} bytes
* @throws {Error} on parsing failure * @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 => { await stream.parse(bytes, async reader => {
const version = await reader.readByte(); const version = await reader.readByte();
if (version !== VERSION) { // The only currently defined value is 1. if (version !== VERSION) { // The only currently defined value is 1.

View File

@ -104,10 +104,13 @@ class PublicKeyPacket {
* @returns {Object} This object with attributes set by the parser * @returns {Object} This object with attributes set by the parser
* @async * @async
*/ */
async read(bytes) { async read(bytes, config = defaultConfig) {
let pos = 0; let pos = 0;
// A one-octet version number (4, 5 or 6). // A one-octet version number (4, 5 or 6).
this.version = bytes[pos++]; 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) { if (this.version === 4 || this.version === 5 || this.version === 6) {
// - A four-octet number denoting the time that the key was created. // - A four-octet number denoting the time that the key was created.

View File

@ -102,7 +102,7 @@ class SecretKeyPacket extends PublicKeyPacket {
*/ */
async read(bytes, config = defaultConfig) { async read(bytes, config = defaultConfig) {
// - A Public-Key or Public-Subkey packet, as described above. // - 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; const startOfSecretKeyData = i;
// - One octet indicating string-to-key usage conventions. Zero // - One octet indicating string-to-key usage conventions. Zero

View File

@ -117,9 +117,12 @@ class SignaturePacket {
* @param {String} bytes - Payload of a tag 2 packet * @param {String} bytes - Payload of a tag 2 packet
* @returns {SignaturePacket} Object representation. * @returns {SignaturePacket} Object representation.
*/ */
read(bytes) { read(bytes, config = defaultConfig) {
let i = 0; let i = 0;
this.version = bytes[i++]; 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) { if (this.version !== 4 && this.version !== 5 && this.version !== 6) {
throw new UnsupportedError(`Version ${this.version} of the signature packet is unsupported.`); throw new UnsupportedError(`Version ${this.version} of the signature packet is unsupported.`);

View File

@ -2951,19 +2951,6 @@ function versionSpecificTests() {
await expect(privateKey.verifyPrimaryKey()).to.be.fulfilled; 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() { export default () => describe('Key', function() {
@ -3032,6 +3019,18 @@ export default () => describe('Key', function() {
expect(key.write()).to.deep.equal(expectedSerializedKey.data); 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() { it('Parsing V5 public key packet', async function() {
// Manually modified from https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b2092/back.mkd#sample-eddsa-key // Manually modified from https://gitlab.com/openpgp-wg/rfc4880bis/blob/00b2092/back.mkd#sample-eddsa-key
const packetBytes = util.hexToUint8Array(` const packetBytes = util.hexToUint8Array(`
@ -3065,7 +3064,7 @@ T/efFOC6BDkAAHcjAPwIPNHnR9bKmkVop6cE05dCIpZ/W8zXDGnjKYrrC4Hb
=wpkQ =wpkQ
-----END PGP PRIVATE KEY BLOCK-----`; -----END PGP PRIVATE KEY BLOCK-----`;
const passphrase = 'password'; const passphrase = 'password';
const encryptedKey = await openpgp.readKey({ armoredKey }); const encryptedKey = await openpgp.readKey({ armoredKey, config: { enableParsingV5Entities: true } });
const decryptedKey = await openpgp.decryptKey({ const decryptedKey = await openpgp.decryptKey({
privateKey: encryptedKey, privateKey: encryptedKey,
passphrase passphrase
@ -3485,7 +3484,7 @@ PzIEeL7UH3trraFmi+Gq8u4kAA==
}); });
it('should pad an ECDSA P-521 key with shorter secret key', async function() { 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 // secret key should be padded
expect(key.keyPacket.privateParams.d.length === 66); expect(key.keyPacket.privateParams.d.length === 66);
// sanity check // sanity check

View File

@ -174,7 +174,7 @@ export default () => describe('Packet', function() {
const msg2 = new openpgp.PacketList(); const msg2 = new openpgp.PacketList();
return enc.encrypt(algo, key, undefined, openpgp.config).then(async function() { 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); return msg2[0].decrypt(algo, key);
}).then(async function() { }).then(async function() {
expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data);
@ -229,7 +229,7 @@ export default () => describe('Packet', function() {
try { try {
await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 0 }); 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); await msg2[0].decrypt(algo, key);
expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data);
expect(encryptStub.callCount > 1).to.be.true; 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 }); await enc.encrypt(algo, key, { ...openpgp.config, aeadChunkSizeByte: 14 });
const data = msg.write(); const data = msg.write();
expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes); 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); await msg2[0].decrypt(algo, key);
expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data); expect(await stream.readToEnd(msg2[0].packets[0].data)).to.deep.equal(literal.data);
} finally { } finally {
@ -706,7 +706,7 @@ export default () => describe('Packet', function() {
await aeadEnc.encrypt(algo, key, undefined, openpgp.config); await aeadEnc.encrypt(algo, key, undefined, openpgp.config);
const msg2 = new openpgp.PacketList(); 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); await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey; const key2 = msg2[0].sessionKey;
@ -744,7 +744,7 @@ export default () => describe('Packet', function() {
await aeadEnc.encrypt(algo, key, undefined, openpgp.config); await aeadEnc.encrypt(algo, key, undefined, openpgp.config);
const msg2 = new openpgp.PacketList(); 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); await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey; 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); expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes);
const msg2 = new openpgp.PacketList(); const msg2 = new openpgp.PacketList();
await msg2.read(data, allAllowedPackets); await msg2.read(data, allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true });
await msg2[0].decrypt(passphrase); await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey; 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); expect(await stream.readToEnd(stream.clone(data))).to.deep.equal(packetBytes);
const msg2 = new openpgp.PacketList(); const msg2 = new openpgp.PacketList();
await msg2.read(data, allAllowedPackets); await msg2.read(data, allAllowedPackets, { ...openpgp.config, enableParsingV5Entities: true });
await msg2[0].decrypt(passphrase); await msg2[0].decrypt(passphrase);
const key2 = msg2[0].sessionKey; const key2 = msg2[0].sessionKey;

View File

@ -2417,7 +2417,7 @@ oaBUyhCKt8tz6Q==
-----END PGP PRIVATE KEY BLOCK-----`; -----END PGP PRIVATE KEY BLOCK-----`;
const key = await openpgp.readKey({ armoredKey }); const key = await openpgp.readKey({ armoredKey });
const decrypted = await openpgp.decrypt({ const decrypted = await openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: encrypted }), message: await openpgp.readMessage({ armoredMessage: encrypted, config: { enableParsingV5Entities: true } }),
verificationKeys: key, verificationKeys: key,
decryptionKeys: key, decryptionKeys: key,
config: { minRSABits: 1024 } config: { minRSABits: 1024 }
@ -2476,7 +2476,8 @@ EYaN9YdDOU2jF+HOaSNaJAsPF8J6BRgTCAAJBQJf0mstAhsMACMiIQUee6Tb
AcvDfr9a0Cp4WAVzKDKLUzrRMgEAozi0VyjiBo1U2LcwTPJkA4PEQqQRVW1D AcvDfr9a0Cp4WAVzKDKLUzrRMgEAozi0VyjiBo1U2LcwTPJkA4PEQqQRVW1D
KZTMSAH7JEo= KZTMSAH7JEo=
=tqWy =tqWy
-----END PGP PRIVATE KEY BLOCK-----` -----END PGP PRIVATE KEY BLOCK-----`,
config: { enableParsingV5Entities: true }
}); });
const signed = `-----BEGIN PGP SIGNED MESSAGE----- const signed = `-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256 Hash: SHA256
@ -2489,7 +2490,7 @@ A3X6psihFkcA+Nuog2qpAq20Zc2lzVjDZzQosb8MLvKMg3UFCX12Oc0BAJwd
JImeZLY02MctIpGZULbqgcUGK0P/yqrPL8Pe4lQM JImeZLY02MctIpGZULbqgcUGK0P/yqrPL8Pe4lQM
=Pacb =Pacb
-----END PGP SIGNATURE-----`; -----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 }); const verified = await openpgp.verify({ verificationKeys: key, message });
expect(await verified.signatures[0].verified).to.be.true; expect(await verified.signatures[0].verified).to.be.true;
}); });