diff --git a/openpgp.d.ts b/openpgp.d.ts index d50d8a49..2b5509b4 100644 --- a/openpgp.d.ts +++ b/openpgp.d.ts @@ -326,6 +326,7 @@ interface Config { commentString: string; allowInsecureDecryptionWithSigningKeys: boolean; allowInsecureVerificationWithReformattedKeys: boolean; + allowMissingKeyFlags: boolean; constantTimePKCS1Decryption: boolean; constantTimePKCS1DecryptionSupportedSymmetricAlgorithms: Set; v6Keys: boolean; diff --git a/src/config/config.js b/src/config/config.js index c03c0cad..fe944d7a 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -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: diff --git a/src/key/helper.js b/src/key/helper.js index babaf3e6..f2f48e38 100644 --- a/src/key/helper.js +++ b/src/key/helper.js @@ -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; } diff --git a/src/key/key.js b/src/key/key.js index 3b1ed8c2..0349ae96 100644 --- a/src/key/key.js +++ b/src/key/key.js @@ -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; } diff --git a/test/general/key.js b/test/general/key.js index f1ee51a1..341b95d8 100644 --- a/test/general/key.js +++ b/test/general/key.js @@ -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; } diff --git a/test/general/signature.js b/test/general/signature.js index 63ce14ba..01a3a0c0 100644 --- a/test/general/signature.js +++ b/test/general/signature.js @@ -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;