mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-13 17:56:39 +00:00

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. For simplicity, we add the salt to all algos, not just EdDSA, as it may also serve as protection in case of weaknesses in the hash algo, potentially hindering e.g. some chosen-prefix attacks. 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.nonDeterministicSignaturesViaNotation` (defaulting to true) has been added to turn off the feature.
72 lines
2.9 KiB
JavaScript
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/);
|
|
});
|