openpgpjs/test/security/subkey_trust.js
larabr c68bd960ce
Randomise v4 and v5 signatures via custom notation, add config.nonDeterministicSignaturesViaNotation to disable feature (#1737)
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.
2024-04-02 17:37:57 +02: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/);
});