Refuse to use keys without key flags, add config.allowMissingKeyFlags

Key flags are needed to restrict key usage to specific purposes:
https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-10.html#section-5.2.3.29 .
Some older keys (e.g. from OpenPGP.js v1) do not declare any key flags.
In previous OpenPGP.js versions, we've allowed such keys to be used for any operation for which they were compatible.
This behaviour has now changed, and these keys are not allowed to be used for any operation.

The setting  `config.allowMissingKeyFlags` has been added to selectively revert to the past behaviour.
This commit is contained in:
larabr 2023-09-11 17:32:01 +02:00
parent 9a547b4553
commit 690346a854
6 changed files with 57 additions and 28 deletions

1
openpgp.d.ts vendored
View File

@ -326,6 +326,7 @@ interface Config {
commentString: string;
allowInsecureDecryptionWithSigningKeys: boolean;
allowInsecureVerificationWithReformattedKeys: boolean;
allowMissingKeyFlags: boolean;
constantTimePKCS1Decryption: boolean;
constantTimePKCS1DecryptionSupportedSymmetricAlgorithms: Set<enums.symmetric>;
v6Keys: boolean;

View File

@ -174,7 +174,14 @@ export default {
* @property {Boolean} allowInsecureDecryptionWithSigningKeys
*/
allowInsecureVerificationWithReformattedKeys: false,
/**
* Allow using keys that do not have any key flags set.
* Key flags are needed to restrict key usage to specific purposes: for instance, a signing key could only be allowed to certify other keys, and not sign messages
* (see https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-10.html#section-5.2.3.29).
* Some older keys do not declare any key flags, which means they are not allowed to be used for any operation.
* This setting allows using such keys for any operation for which they are compatible, based on their public key algorithm.
*/
allowMissingKeyFlags: false,
/**
* Enable constant-time decryption of RSA- and ElGamal-encrypted session keys, to hinder Bleichenbacher-like attacks (https://link.springer.com/chapter/10.1007/BFb0055716).
* This setting has measurable performance impact and it is only helpful in application scenarios where both of the following conditions apply:

View File

@ -357,32 +357,51 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
return options;
}
export function isValidSigningKeyPacket(keyPacket, signature) {
const keyAlgo = keyPacket.algorithm;
return keyAlgo !== enums.publicKey.rsaEncrypt &&
keyAlgo !== enums.publicKey.elgamal &&
keyAlgo !== enums.publicKey.ecdh &&
keyAlgo !== enums.publicKey.x25519 &&
keyAlgo !== enums.publicKey.x448 &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.signData) !== 0);
export function isValidSigningKeyPacket(keyPacket, signature, config) {
switch (keyPacket.algorithm) {
case enums.publicKey.rsaEncryptSign:
case enums.publicKey.rsaSign:
case enums.publicKey.dsa:
case enums.publicKey.ecdsa:
case enums.publicKey.eddsaLegacy:
case enums.publicKey.ed25519:
case enums.publicKey.ed448: {
if (!signature.keyFlags && !config.allowMissingKeyFlags) {
throw new Error('None of the key flags is set: consider passing `config.allowMissingKeyFlags`');
}
return !signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.signData) !== 0;
}
}
}
export function isValidEncryptionKeyPacket(keyPacket, signature) {
const keyAlgo = keyPacket.algorithm;
return keyAlgo !== enums.publicKey.dsa &&
keyAlgo !== enums.publicKey.rsaSign &&
keyAlgo !== enums.publicKey.ecdsa &&
keyAlgo !== enums.publicKey.eddsaLegacy &&
keyAlgo !== enums.publicKey.ed25519 &&
keyAlgo !== enums.publicKey.ed448 &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.encryptCommunication) !== 0 ||
(signature.keyFlags[0] & enums.keyFlags.encryptStorage) !== 0);
export function isValidEncryptionKeyPacket(keyPacket, signature, config) {
switch (keyPacket.algorithm) {
case enums.publicKey.rsaEncryptSign:
case enums.publicKey.rsaEncrypt:
case enums.publicKey.elgamal:
case enums.publicKey.ecdh:
case enums.publicKey.x25519:
case enums.publicKey.x448: {
if (!signature.keyFlags && !config.allowMissingKeyFlags) {
throw new Error('None of the key flags is set: consider passing `config.allowMissingKeyFlags`');
}
return !signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.encryptCommunication) !== 0 ||
(signature.keyFlags[0] & enums.keyFlags.encryptStorage) !== 0;
}
}
}
export function isValidDecryptionKeyPacket(signature, config) {
if (config.allowInsecureDecryptionWithSigningKeys) {
if (!signature.keyFlags && !config.allowMissingKeyFlags) {
throw new Error('None of the key flags is set: consider passing `config.allowMissingKeyFlags`');
}
const isValidSigningKeyPacket = !signature.keyFlags || (signature.keyFlags[0] & enums.keyFlags.signData) !== 0;
if (isValidSigningKeyPacket && config.allowInsecureDecryptionWithSigningKeys) {
// This is only relevant for RSA keys, all other signing algorithms cannot decrypt
return true;
}

View File

@ -269,7 +269,7 @@ class Key {
const bindingSignature = await helper.getLatestValidSignature(
subkey.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config
);
if (!helper.isValidSigningKeyPacket(subkey.keyPacket, bindingSignature)) {
if (!helper.isValidSigningKeyPacket(subkey.keyPacket, bindingSignature, config)) {
continue;
}
if (!bindingSignature.embeddedSignature) {
@ -322,7 +322,7 @@ class Key {
await subkey.verify(date, config);
const dataToVerify = { key: primaryKey, bind: subkey.keyPacket };
const bindingSignature = await helper.getLatestValidSignature(subkey.bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config);
if (helper.isValidEncryptionKeyPacket(subkey.keyPacket, bindingSignature)) {
if (helper.isValidEncryptionKeyPacket(subkey.keyPacket, bindingSignature, config)) {
helper.checkKeyRequirements(subkey.keyPacket, config);
return subkey;
}
@ -336,7 +336,7 @@ class Key {
// if no valid subkey for encryption, evaluate primary key
const selfCertification = await this.getPrimarySelfSignature(date, userID, config);
if ((!keyID || primaryKey.getKeyID().equals(keyID)) &&
helper.isValidEncryptionKeyPacket(primaryKey, selfCertification)) {
helper.isValidEncryptionKeyPacket(primaryKey, selfCertification, config)) {
helper.checkKeyRequirements(primaryKey, config);
return this;
}

View File

@ -3402,7 +3402,7 @@ PzIEeL7UH3trraFmi+Gq8u4kAA==
expect(selfCertification.valid).to.be.true;
const certifyingKey = await openpgp.readKey({ armoredKey: certifying_key });
const certifyingSigningKey = await certifyingKey.getSigningKey();
const certifyingSigningKey = await certifyingKey.getSigningKey(undefined, undefined, undefined, { ...openpgp.config, allowMissingKeyFlags: true });
const signatures = await pubKey.verifyPrimaryUser([certifyingKey]);
expect(signatures.length).to.equal(2);
expect(signatures[0].keyID.toHex()).to.equal(publicSigningKey.getKeyID().toHex());
@ -3411,7 +3411,9 @@ PzIEeL7UH3trraFmi+Gq8u4kAA==
expect(signatures[1].valid).to.be.false;
const { user } = await pubKey.getPrimaryUser();
await expect(user.verifyCertificate(user.otherCertifications[0], [certifyingKey], undefined, openpgp.config)).to.be.rejectedWith('User certificate is revoked');
await expect(
user.verifyCertificate(user.otherCertifications[0], [certifyingKey], undefined, { ...openpgp.config, allowMissingKeyFlags: true })
).to.be.rejectedWith('User certificate is revoked');
} finally {
openpgp.config.rejectPublicKeyAlgorithms = rejectPublicKeyAlgorithms;
}

View File

@ -2188,7 +2188,7 @@ uDvEBgD+LCEUOPejUTCMqPyd04ssdOq1AlMJOmUGUwLk7kFP7Aw=
const message = await openpgp.createMessage({ text: content });
await message.appendSignature(detachedSig);
const { data, signatures } = await openpgp.verify({ verificationKeys:[publicKey], message, config: { minRSABits: 1024 } });
const { data, signatures } = await openpgp.verify({ verificationKeys:[publicKey], message, config: { minRSABits: 1024, allowMissingKeyFlags: true } });
expect(data).to.equal(content);
expect(signatures).to.have.length(1);
expect(await signatures[0].verified).to.be.true;