Skip to content

Commit

Permalink
Add optional signature verification to prevent outputting faulty sign…
Browse files Browse the repository at this point in the history
…atures

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, hence we add the option to verify the signatures
before outputting them, if the message being signed is deterministic.
  • Loading branch information
larabr committed Nov 15, 2023
1 parent 5420eeb commit 3b572a1
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 2 deletions.
18 changes: 16 additions & 2 deletions nacl-fast.js
Original file line number Diff line number Diff line change
Expand Up @@ -1298,12 +1298,26 @@ nacl.box.keyPair.fromSecretKey = function(secretKey) {
return {publicKey: pk, secretKey: new Uint8Array(secretKey)};
};

nacl.sign = function(msg, secretKey) {
nacl.sign = function(msg, secretKey, isDeterministicMsg) {
checkArrayTypes(msg, secretKey);
if (secretKey.length !== crypto_sign_SECRETKEYBYTES)
throw new Error('bad secret key size');
var signedMsg = new Uint8Array(crypto_sign_BYTES+msg.length);
const signedMsg = new Uint8Array(crypto_sign_BYTES+msg.length);
crypto_sign(signedMsg, msg, msg.length, secretKey);
/**
* Detect faulty signatures caused by random bitflips during `crypto_sign` which could lead to private key extraction
* if two signatures over the same message are obtained.
* See https://github.com/jedisct1/libsodium/issues/170.
* If the input data is not deterministic, e.g. thanks to the random salt in v6 OpenPGP signatures, then the generated signature
* is always safe, and the verification step is skipped.
* Otherwise, we verify the generated to ensure that no bitflip occured:
* - in M between the computation of `r` and `h`.
* - in the public key before computing `h`
* The verification step is almost 2-3 times as slow as signing, but it's faster than re-signing + re-deriving the public key for separate checks.
*/
if (isDeterministicMsg && !nacl.sign.detached.verify(msg, signedMsg.subarray(0, crypto_sign_BYTES), secretKey.subarray(32))) {
throw new Error('transient signing failure');
}
return signedMsg;
};

Expand Down
12 changes: 12 additions & 0 deletions test/08-sign.quick.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ test('nacl.sign and nacl.sign.open', {skip: true}, function(t) {
t.end();
});

test('nacl.sign with reverification for deterministic signature', {skip: true}, function(t) {
var k = nacl.sign.keyPair();
var m = new Uint8Array(100);
var i;
for (i = 0; i < m.length; i++) m[i] = i & 0xff;
var sm = nacl.sign(m, k.secretKey, true);
t.ok(sm.length > m.length, 'signed message length should be greater than message length');
var result = nacl.sign.detached.verify(m, sm, k.publicKey);
t.ok(result, 'signature must be verified');
t.end();
});

test('nacl.sign.detached and nacl.sign.detached.verify', function(t) {
var k = nacl.sign.keyPair();
var m = new Uint8Array(100);
Expand Down

0 comments on commit 3b572a1

Please sign in to comment.