Disable support for parsing v5 entities by default (add config.enableParsingV5Entities) (#1774)

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-04 13:59:40 +02:00 committed by GitHub
parent 9efdaf14b1
commit 5268c484e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 47 additions and 29 deletions

View File

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

View File

@ -68,7 +68,10 @@ class AEADEncryptedDataPacket {
* @param {Uint8Array | ReadableStream<Uint8Array>} 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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