openpgpjs/test/security/subkey_trust.js
larabr b5f139b3f7 Randomise v4 and v5 EdDSA signatures via custom notation, add config.nonDeterministicEdDSASignaturesViaNotation to disable feature
EdDSA is known to be vulnerable to fault attacks which can lead to secret key extraction if
two signatures over the same data can be collected.
Randomly occurring bitflips in specific parts of the computation might in principle result
in vulnerable faulty signatures being generated.
To protect signatures generated using v4 and v5 keys from this possibility, we randomise each
signature by adding a custom notation with a random value, functioning as a salt.
v6 signatures do not need to rely on this, as they are non-deterministic by design.

While this notation solution is interoperable, it will reveal that the signature has been generated using OpenPGP.js,
which may not be desirable in some cases.
For this reason, the option `config.nonDeterministicEdDSASignaturesViaNotation` (defaulting to true) has been added
to turn off the feature.
2024-03-26 17:11:44 +01:00

72 lines
2.9 KiB
JavaScript

import { use as chaiUse, expect } from 'chai';
import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/newline-after-import
chaiUse(chaiAsPromised);
import openpgp from '../initOpenpgp.js';
const { readKey, PublicKey, readCleartextMessage, createCleartextMessage, enums, PacketList, SignaturePacket } = openpgp;
async function generateTestData() {
const { privateKey: victimPrivKey } = await openpgp.generateKey({
userIDs: [{ name: 'Victim', email: 'victim@example.com' }],
type: 'rsa',
rsaBits: 2048,
subkeys: [{ sign: true }],
format: 'object'
});
const { privateKey: attackerPrivKey } = await openpgp.generateKey({
userIDs: [{ name: 'Attacker', email: 'attacker@example.com' }],
type: 'rsa',
rsaBits: 2048,
subkeys: [],
format: 'object'
});
const signed = await openpgp.sign({
message: await createCleartextMessage({ text: 'I am batman' }),
signingKeys: victimPrivKey
});
return {
victimPubKey: victimPrivKey.toPublic(),
attackerPrivKey,
signed
};
}
export default () => it('Does not trust subkeys without Primary Key Binding Signature', async function() {
// attacker only has his own private key,
// the victim's public key and a signed message
const { victimPubKey, attackerPrivKey, signed } = await generateTestData();
const pktPubVictim = victimPubKey.toPacketList();
const pktPubAttacker = attackerPrivKey.toPublic().toPacketList();
const dataToSign = {
key: attackerPrivKey.toPublic().keyPacket,
bind: pktPubVictim[3] // victim subkey
};
const fakeBindingSignature = new SignaturePacket();
fakeBindingSignature.signatureType = enums.signature.subkeyBinding;
fakeBindingSignature.publicKeyAlgorithm = attackerPrivKey.keyPacket.algorithm;
fakeBindingSignature.hashAlgorithm = enums.hash.sha256;
fakeBindingSignature.keyFlags = [enums.keyFlags.signData];
await fakeBindingSignature.sign(attackerPrivKey.keyPacket, dataToSign, undefined, undefined, openpgp.config);
const newList = new PacketList();
newList.push(
pktPubAttacker[0], // attacker private key
pktPubAttacker[1], // attacker user
pktPubAttacker[2], // attacker self signature
pktPubVictim[3], // victim subkey
fakeBindingSignature // faked key binding
);
let fakeKey = new PublicKey(newList);
fakeKey = await readKey({ armoredKey: await fakeKey.toPublic().armor() });
const verifyAttackerIsBatman = await openpgp.verify({
message: await readCleartextMessage({ cleartextMessage: signed }),
verificationKeys: fakeKey
});
// expect the signature to have the expected keyID, but be invalid due to fake key binding signature in the subkey
expect(verifyAttackerIsBatman.signatures[0].keyID.equals(victimPubKey.subkeys[0].getKeyID())).to.be.true;
await expect(verifyAttackerIsBatman.signatures[0].verified).to.be.rejectedWith(/Could not find valid signing key packet/);
});