From 743217216c654bb9ab961fec2e42fe20b153f5b5 Mon Sep 17 00:00:00 2001 From: larabr Date: Wed, 6 Sep 2023 16:39:23 +0200 Subject: [PATCH] Support generating subkeys with 'forwarded communication' flag to decrypt autoforwarded messages (#8) These subkeys must not have the standard encryption flags (EtEr) set, as they are not supposed to be used for direct messages. Also: - preserve 'forwarded communication' key flag when reformatting - fix bug allowing to decrypt forwarded messages by setting `config.allowInsecureDecryptionWithSigningKeys` instead of `config.allowForwardedMessages` - add TS definition for `config.allowForwardedMessages` --- openpgp.d.ts | 2 ++ src/key/factory.js | 3 ++- src/key/helper.js | 15 +++++++++++-- test/general/forwarding.js | 46 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/openpgp.d.ts b/openpgp.d.ts index 939bbd69f..5b4e567ce 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -317,6 +317,7 @@ interface Config { aeadProtect: boolean; allowUnauthenticatedMessages: boolean; allowUnauthenticatedStream: boolean; + allowForwardedMessages: boolean; checksumRequired: boolean; minRSABits: number; passwordCollisionCheck: boolean; @@ -707,6 +708,7 @@ interface SubkeyOptions { keyExpirationTime?: number; date?: Date; sign?: boolean; + forwarding?: boolean; config?: PartialConfig; } diff --git a/src/key/factory.js b/src/key/factory.js index 2a4302669..ddee10bbe 100644 --- a/src/key/factory.js +++ b/src/key/factory.js @@ -138,7 +138,8 @@ export async function reformat(options, config) { helper.getLatestValidSignature(subkey.bindingSignatures, secretKeyPacket, enums.signature.subkeyBinding, dataToVerify, null, config) ).catch(() => ({})); return { - sign: bindingSignature.keyFlags && (bindingSignature.keyFlags[0] & enums.keyFlags.signData) + sign: bindingSignature.keyFlags && (bindingSignature.keyFlags[0] & enums.keyFlags.signData), + forwarding: bindingSignature.keyFlags && (bindingSignature.keyFlags[0] & enums.keyFlags.forwardedCommunication) }; })); } diff --git a/src/key/helper.js b/src/key/helper.js index d9b36257e..1506d95ee 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -93,7 +93,9 @@ export async function createBindingSignature(subkey, primaryKey, options, config signatureType: enums.signature.keyBinding }, options.date, undefined, undefined, undefined, config); } else { - signatureProperties.keyFlags = [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage]; + signatureProperties.keyFlags = options.forwarding ? + [enums.keyFlags.forwardedCommunication] : + [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage]; } if (options.keyExpirationTime > 0) { signatureProperties.keyExpirationTime = options.keyExpirationTime; @@ -325,6 +327,10 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) { options.date = options.date || subkeyDefaults.date; options.sign = options.sign || false; + options.forwarding = options.forwarding || false; + if (options.sign && options.forwarding) { + throw new Error('Incompatible options: "sign" and "forwarding" cannot be set together'); + } switch (options.type) { case 'ecc': @@ -383,7 +389,12 @@ export function isValidEncryptionKeyPacket(keyPacket, signature) { } export function isValidDecryptionKeyPacket(signature, config) { - if (config.allowInsecureDecryptionWithSigningKeys) { + const isSigningKey = !signature.keyFlags || + (signature.keyFlags[0] & enums.keyFlags.sign) !== 0 || + (signature.keyFlags[0] & enums.keyFlags.certifyKeys) !== 0 || + (signature.keyFlags[0] & enums.keyFlags.authentication) !== 0; + + if (isSigningKey && config.allowInsecureDecryptionWithSigningKeys) { // This is only relevant for RSA keys, all other signing algorithms cannot decrypt return true; } diff --git a/test/general/forwarding.js b/test/general/forwarding.js index 5c3f69a4d..7b11a05a9 100644 --- a/test/general/forwarding.js +++ b/test/general/forwarding.js @@ -52,4 +52,50 @@ module.exports = () => describe('Forwarding', function() { const { data: expectedSerializedKey } = await openpgp.unarmor(charlieKeyArmored); expect(serializedKey).to.deep.equal(expectedSerializedKey); }); + + it('generates subkey with forwarding flag (0x40)', async function() { + const { privateKey: armoredKey } = await openpgp.generateKey({ userIDs: { email: 'test@forwarding.it' }, subkeys: [{ forwarding: true }, {}] }); + const privateKey = await openpgp.readKey({ armoredKey }); + + expect(privateKey.subkeys[0].bindingSignatures[0].keyFlags[0]).to.equal(openpgp.enums.keyFlags.forwardedCommunication); + expect(privateKey.subkeys[1].bindingSignatures[0].keyFlags[0]).to.equal(openpgp.enums.keyFlags.encryptCommunication | openpgp.enums.keyFlags.encryptStorage); + }); + + it('reformatting a key preserves its forwarding flags (0x40)', async function() { + // two subkeys, the first with forwarding flag, the second with standard encryption ones + const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK----- + +xVgEZPhkahYJKwYBBAHaRw8BAQdARUPOBft22XPObTCYNRD2VB8ESYHOZsII +XrpUHn2AstUAAQCl30ZHts8cyRRXw7B2595L8RIovkwxhnCRTqe+V92+2BFK +zRQ8dGVzdEBmb3J3YXJkaW5nLml0PsKMBBAWCgA+BYJk+GRqBAsJBwgJkLvy +KUWO/JamAxUICgQWAAIBAhkBApsDAh4BFiEEM00dF5bOjezdbhYlu/IpRY78 +lqYAAP6uAQDt7Xxoh+VUB/xkOX1cj7at7U7zrKAxq7Xh1YbGM+RHKgEAgRoz +UGXKsQigC2KyXGW0nObT8RfUcQIUyrkVdImWiAjHXQRk+GRqEgorBgEEAZdV +AQUBAQdA1E/PrQHG7g8UW7v7fKwgc0x+jTHp8cOa3SGAqd3Pc3gDAQgHAAD/ +TY0mClFVWkDM/W6CnN7pOO36baJ0o1LJAVHucDTbxOgSMMJ4BBgWCAAqBYJk ++GRqCZC78ilFjvyWpgKbQBYhBDNNHReWzo3s3W4WJbvyKUWO/JamAABzegEA +mP3WSG1pceOppv5ncSoZJ9GZoaiXxnkk2TyLvmBQi7kA/1MoAjQDjF3XbX8y +ScSjs3juhSAQ/MnFj8RsDaI7XdIBx10EZPhkahIKKwYBBAGXVQEFAQEHQEyC +E9n5Jo23u9OfoVcUwEfQj4yAMhNBII3j5ePRDaYXAwEIBwAA/2M7YfJN9jV4 +LuiY7ldrWsd875xA5s6I6/8aOtUHuJcYEmPCeAQYFggAKgWCZPhkagmQu/Ip +RY78lqYCmwwWIQQzTR0Xls6N7N1uFiW78ilFjvyWpgAA5KEBAKaoHbyi3wpr +jt2m75fdx10rDOxJDR9H6ilI5ygLWeLsAPoCozX/3KhXLx8WbTe7MFcGl47J +YdgLdgXl0dn/xdXjCQ== +=eC8z +-----END PGP PRIVATE KEY BLOCK-----` }); + + const { privateKey: reformattedKey } = await openpgp.reformatKey({ privateKey, userIDs: { email: 'test@forwarding.it' }, format: 'object' }); + + expect(reformattedKey.subkeys[0].bindingSignatures[0].keyFlags[0]).to.equal(openpgp.enums.keyFlags.forwardedCommunication); + expect(reformattedKey.subkeys[1].bindingSignatures[0].keyFlags[0]).to.equal(openpgp.enums.keyFlags.encryptCommunication | openpgp.enums.keyFlags.encryptStorage); + }); + + it('refuses to encrypt using encryption key with forwarding flag (0x40)', async function() { + const charlieKey = await openpgp.readKey({ armoredKey: charlieKeyArmored }); + + await expect(openpgp.encrypt({ + message: await openpgp.createMessage({ text: 'abc' }), + encryptionKeys: charlieKey + })).to.be.rejectedWith(/Could not find valid encryption key packet/); + }); });