mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-11-24 22:45:48 +00:00
PrivateKey.getDecryptionKeys: throw if no decryption key is found
To avoid returning dummy key packets, and improving error reporting. This new behavior is also better aligned with that of `Key.getSigningKey()`. This is a breaking change for apps that call `getDecryptionKeys()` directly. The related error messages returned by `openpgp.decrypt` have also changed, becoming more specific. This change is also made in preparation of supporting private keys with public key packets (to be released in the next minor version, hence we want to avoid breaking changes there).
This commit is contained in:
parent
5fd7ef370f
commit
b0873eb98d
@ -77,28 +77,45 @@ class PrivateKey extends PublicKey {
|
|||||||
* @param {String} userID, optional
|
* @param {String} userID, optional
|
||||||
* @param {Object} [config] - Full configuration, defaults to openpgp.config
|
* @param {Object} [config] - Full configuration, defaults to openpgp.config
|
||||||
* @returns {Promise<Array<Key|Subkey>>} Array of decryption keys.
|
* @returns {Promise<Array<Key|Subkey>>} Array of decryption keys.
|
||||||
|
* @throws {Error} if no decryption key is found
|
||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async getDecryptionKeys(keyID, date = new Date(), userID = {}, config = defaultConfig) {
|
async getDecryptionKeys(keyID, date = new Date(), userID = {}, config = defaultConfig) {
|
||||||
const primaryKey = this.keyPacket;
|
const primaryKey = this.keyPacket;
|
||||||
const keys = [];
|
const keys = [];
|
||||||
|
let exception = null;
|
||||||
for (let i = 0; i < this.subkeys.length; i++) {
|
for (let i = 0; i < this.subkeys.length; i++) {
|
||||||
if (!keyID || this.subkeys[i].getKeyID().equals(keyID, true)) {
|
if (!keyID || this.subkeys[i].getKeyID().equals(keyID, true)) {
|
||||||
|
if (this.subkeys[i].keyPacket.isDummy()) {
|
||||||
|
exception = exception || new Error('Gnu-dummy key packets cannot be used for decryption');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dataToVerify = { key: primaryKey, bind: this.subkeys[i].keyPacket };
|
const dataToVerify = { key: primaryKey, bind: this.subkeys[i].keyPacket };
|
||||||
const bindingSignature = await helper.getLatestValidSignature(this.subkeys[i].bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config);
|
const bindingSignature = await helper.getLatestValidSignature(this.subkeys[i].bindingSignatures, primaryKey, enums.signature.subkeyBinding, dataToVerify, date, config);
|
||||||
if (helper.validateDecryptionKeyPacket(this.subkeys[i].keyPacket, bindingSignature, config)) {
|
if (helper.validateDecryptionKeyPacket(this.subkeys[i].keyPacket, bindingSignature, config)) {
|
||||||
keys.push(this.subkeys[i]);
|
keys.push(this.subkeys[i]);
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
exception = e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluate primary key
|
// evaluate primary key
|
||||||
const selfCertification = await this.getPrimarySelfSignature(date, userID, config);
|
const selfCertification = await this.getPrimarySelfSignature(date, userID, config);
|
||||||
if ((!keyID || primaryKey.getKeyID().equals(keyID, true)) &&
|
if ((!keyID || primaryKey.getKeyID().equals(keyID, true)) && helper.validateDecryptionKeyPacket(primaryKey, selfCertification, config)) {
|
||||||
helper.validateDecryptionKeyPacket(primaryKey, selfCertification, config)) {
|
if (primaryKey.isDummy()) {
|
||||||
keys.push(this);
|
exception = exception || new Error('Gnu-dummy key packets cannot be used for decryption');
|
||||||
|
} else {
|
||||||
|
keys.push(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.length === 0) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
||||||
|
throw exception || new Error('No decryption key packets found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys;
|
return keys;
|
||||||
|
|||||||
@ -199,6 +199,15 @@ export class Message {
|
|||||||
}
|
}
|
||||||
await Promise.all(pkeskPackets.map(async function(pkeskPacket) {
|
await Promise.all(pkeskPackets.map(async function(pkeskPacket) {
|
||||||
await Promise.all(decryptionKeys.map(async function(decryptionKey) {
|
await Promise.all(decryptionKeys.map(async function(decryptionKey) {
|
||||||
|
let decryptionKeyPackets;
|
||||||
|
try {
|
||||||
|
// do not check key expiration to allow decryption of old messages
|
||||||
|
decryptionKeyPackets = (await decryptionKey.getDecryptionKeys(pkeskPacket.publicKeyID, null, undefined, config)).map(key => key.keyPacket);
|
||||||
|
} catch (err) {
|
||||||
|
exception = err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let algos = [
|
let algos = [
|
||||||
enums.symmetric.aes256, // Old OpenPGP.js default fallback
|
enums.symmetric.aes256, // Old OpenPGP.js default fallback
|
||||||
enums.symmetric.aes128, // RFC4880bis fallback
|
enums.symmetric.aes128, // RFC4880bis fallback
|
||||||
@ -212,8 +221,6 @@ export class Message {
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// do not check key expiration to allow decryption of old messages
|
|
||||||
const decryptionKeyPackets = (await decryptionKey.getDecryptionKeys(pkeskPacket.publicKeyID, null, undefined, config)).map(key => key.keyPacket);
|
|
||||||
await Promise.all(decryptionKeyPackets.map(async function(decryptionKeyPacket) {
|
await Promise.all(decryptionKeyPackets.map(async function(decryptionKeyPacket) {
|
||||||
if (!decryptionKeyPacket || decryptionKeyPacket.isDummy()) {
|
if (!decryptionKeyPacket || decryptionKeyPacket.isDummy()) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -3541,7 +3541,7 @@ PzIEeL7UH3trraFmi+Gq8u4kAA==
|
|||||||
await expect(openpgp.decrypt({
|
await expect(openpgp.decrypt({
|
||||||
message: await openpgp.readMessage({ armoredMessage: encryptedRsaSignOnly }),
|
message: await openpgp.readMessage({ armoredMessage: encryptedRsaSignOnly }),
|
||||||
decryptionKeys: key
|
decryptionKeys: key
|
||||||
})).to.be.rejectedWith(/Session key decryption failed/);
|
})).to.be.rejectedWith(/No decryption key packets found/);
|
||||||
|
|
||||||
await expect(openpgp.decrypt({
|
await expect(openpgp.decrypt({
|
||||||
message: await openpgp.readMessage({ armoredMessage: encryptedRsaSignOnly }),
|
message: await openpgp.readMessage({ armoredMessage: encryptedRsaSignOnly }),
|
||||||
@ -3550,6 +3550,11 @@ PzIEeL7UH3trraFmi+Gq8u4kAA==
|
|||||||
})).to.be.fulfilled;
|
})).to.be.fulfilled;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('PrivateKey.getDecryptionKeys() - should throw for sign-only key', async function() {
|
||||||
|
const key = await openpgp.readKey({ armoredKey: rsaSignOnly });
|
||||||
|
await expect(key.getDecryptionKeys()).to.be.rejectedWith(/No decryption key packets found/);
|
||||||
|
});
|
||||||
|
|
||||||
it('Key.getExpirationTime()', async function() {
|
it('Key.getExpirationTime()', async function() {
|
||||||
const [, pubKey] = await openpgp.readKeys({ armoredKeys: twoKeys });
|
const [, pubKey] = await openpgp.readKeys({ armoredKeys: twoKeys });
|
||||||
expect(pubKey).to.exist;
|
expect(pubKey).to.exist;
|
||||||
|
|||||||
@ -1768,7 +1768,7 @@ aOU=
|
|||||||
message: await openpgp.readMessage({ armoredMessage: encrypted }),
|
message: await openpgp.readMessage({ armoredMessage: encrypted }),
|
||||||
decryptionKeys: privateKeyRSA,
|
decryptionKeys: privateKeyRSA,
|
||||||
config
|
config
|
||||||
})).to.be.rejectedWith(/Session key decryption failed/);
|
})).to.be.rejectedWith(/No decryption key packets found/);
|
||||||
// decryption using ECC key should succeed (PKCS1 is not used, so constant time countermeasures are not applied)
|
// decryption using ECC key should succeed (PKCS1 is not used, so constant time countermeasures are not applied)
|
||||||
const { data } = await openpgp.decrypt({
|
const { data } = await openpgp.decrypt({
|
||||||
message: await openpgp.readMessage({ armoredMessage: encrypted }),
|
message: await openpgp.readMessage({ armoredMessage: encrypted }),
|
||||||
@ -2642,7 +2642,7 @@ XfA3pqV4mTzF
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
throw new Error('Should not decrypt with invalid key');
|
throw new Error('Should not decrypt with invalid key');
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
expect(error.message).to.match(/Error decrypting session keys: Session key decryption failed./);
|
expect(error.message).to.match(/Error decrypting session keys: Could not find valid subkey binding signature in key/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -4174,7 +4174,7 @@ XfA3pqV4mTzF
|
|||||||
decryptionKeys: decryptedKeyDE
|
decryptionKeys: decryptedKeyDE
|
||||||
};
|
};
|
||||||
// binding signature is invalid
|
// binding signature is invalid
|
||||||
await expect(openpgp.decrypt(decOpt)).to.be.rejectedWith(/Session key decryption failed/);
|
await expect(openpgp.decrypt(decOpt)).to.be.rejectedWith(/Could not find valid subkey binding signature in key/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('RSA decryption with PKCS1 padding of wrong length should fail', async function() {
|
it('RSA decryption with PKCS1 padding of wrong length should fail', async function() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user