Skip to content

Commit

Permalink
Support generating subkeys with 'forwarded communication' flag to dec…
Browse files Browse the repository at this point in the history
…rypt 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`
  • Loading branch information
larabr committed Nov 21, 2023
1 parent d1550c5 commit 7432172
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 3 deletions.
2 changes: 2 additions & 0 deletions openpgp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ interface Config {
aeadProtect: boolean;
allowUnauthenticatedMessages: boolean;
allowUnauthenticatedStream: boolean;
allowForwardedMessages: boolean;
checksumRequired: boolean;
minRSABits: number;
passwordCollisionCheck: boolean;
Expand Down Expand Up @@ -707,6 +708,7 @@ interface SubkeyOptions {
keyExpirationTime?: number;
date?: Date;
sign?: boolean;
forwarding?: boolean;
config?: PartialConfig;
}

Expand Down
3 changes: 2 additions & 1 deletion src/key/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
}));
}
Expand Down
15 changes: 13 additions & 2 deletions src/key/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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;
}
Expand Down
46 changes: 46 additions & 0 deletions test/general/forwarding.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '[email protected]' }, 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: '[email protected]' }, 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/);
});
});

0 comments on commit 7432172

Please sign in to comment.