mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-07-03 04:22:47 -05:00
Switch to RFC 6979 for DSA nonce generation.
This fixes a vulnerability that compromises NIST P521 ECDSA keys when
they are used with PuTTY's existing DSA nonce generation code. The
vulnerability has been assigned the identifier CVE-2024-31497.
PuTTY has been doing its DSA signing deterministically for literally
as long as it's been doing it at all, because I didn't trust Windows's
entropy generation. Deterministic nonce generation was introduced in
commit d345ebc2a5
, as part of the initial version of our DSA
signing routine. At the time, there was no standard for how to do it,
so we had to think up the details of our system ourselves, with some
help from the Cambridge University computer security group.
More than ten years later, RFC 6979 was published, recommending a
similar system for general use, naturally with all the details
different. We didn't switch over to doing it that way, because we had
a scheme in place already, and as far as I could see, the differences
were not security-critical - just the normal sort of variation you
expect when any two people design a protocol component of this kind
independently.
As far as I know, the _structure_ of our scheme is still perfectly
fine, in terms of what data gets hashed, how many times, and how the
hash output is converted into a nonce. But the weak spot is the choice
of hash function: inside our dsa_gen_k() function, we generate 512
bits of random data using SHA-512, and then reduce that to the output
range by modular reduction, regardless of what signature algorithm
we're generating a nonce for.
In the original use case, this introduced a theoretical bias (the
output size is an odd prime, which doesn't evenly divide the space of
2^512 possible inputs to the reduction), but the theory was that since
integer DSA uses a modulus prime only 160 bits long (being based on
SHA-1, at least in the form that SSH uses it), the bias would be too
small to be detectable, let alone exploitable.
Then we reused the same function for NIST-style ECDSA, when it
arrived. This is fine for the P256 curve, and even P384. But in P521,
the order of the base point is _greater_ than 2^512, so when we
generate a 512-bit number and reduce it, the reduction never makes any
difference, and our output nonces are all in the first 2^512 elements
of the range of about 2^521. So this _does_ introduce a significant
bias in the nonces, compared to the ideal of uniformly random
distribution over the whole range. And it's been recently discovered
that a bias of this kind is sufficient to expose private keys, given a
manageably small number of signatures to work from.
(Incidentally, none of this affects Ed25519. The spec for that system
includes its own idea of how you should do deterministic nonce
generation - completely different again, naturally - and we did it
that way rather than our way, so that we could use the existing test
vectors.)
The simplest fix would be to patch our existing nonce generator to use
a longer hash, or concatenate a couple of SHA-512 hashes, or something
similar. But I think a more robust approach is to switch it out
completely for what is now the standard system. The main reason why I
prefer that is that the standard system comes with test vectors, which
adds a lot of confidence that I haven't made some other mistake in
following my own design.
So here's a commit that adds an implementation of RFC 6979, and
removes the old dsa_gen_k() function. Tests are added based on the
RFC's appendix of test vectors (as many as are compatible with the
more limited API of PuTTY's crypto code, e.g. we lack support for the
NIST P192 curve, or for doing integer DSA with many different hash
functions). One existing test changes its expected outputs, namely the
one that has a sample key pair and signature for every key algorithm
we support.
This commit is contained in:
116
crypto/dsa.c
116
crypto/dsa.c
@ -340,117 +340,6 @@ static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub)
|
||||
return ret;
|
||||
}
|
||||
|
||||
mp_int *dsa_gen_k(const char *id_string, mp_int *modulus,
|
||||
mp_int *private_key,
|
||||
unsigned char *digest, int digest_len)
|
||||
{
|
||||
/*
|
||||
* The basic DSA signing algorithm is:
|
||||
*
|
||||
* - invent a random k between 1 and q-1 (exclusive).
|
||||
* - Compute r = (g^k mod p) mod q.
|
||||
* - Compute s = k^-1 * (hash + x*r) mod q.
|
||||
*
|
||||
* This has the dangerous properties that:
|
||||
*
|
||||
* - if an attacker in possession of the public key _and_ the
|
||||
* signature (for example, the host you just authenticated
|
||||
* to) can guess your k, he can reverse the computation of s
|
||||
* and work out x = r^-1 * (s*k - hash) mod q. That is, he
|
||||
* can deduce the private half of your key, and masquerade
|
||||
* as you for as long as the key is still valid.
|
||||
*
|
||||
* - since r is a function purely of k and the public key, if
|
||||
* the attacker only has a _range of possibilities_ for k
|
||||
* it's easy for him to work through them all and check each
|
||||
* one against r; he'll never be unsure of whether he's got
|
||||
* the right one.
|
||||
*
|
||||
* - if you ever sign two different hashes with the same k, it
|
||||
* will be immediately obvious because the two signatures
|
||||
* will have the same r, and moreover an attacker in
|
||||
* possession of both signatures (and the public key of
|
||||
* course) can compute k = (hash1-hash2) * (s1-s2)^-1 mod q,
|
||||
* and from there deduce x as before.
|
||||
*
|
||||
* - the Bleichenbacher attack on DSA makes use of methods of
|
||||
* generating k which are significantly non-uniformly
|
||||
* distributed; in particular, generating a 160-bit random
|
||||
* number and reducing it mod q is right out.
|
||||
*
|
||||
* For this reason we must be pretty careful about how we
|
||||
* generate our k. Since this code runs on Windows, with no
|
||||
* particularly good system entropy sources, we can't trust our
|
||||
* RNG itself to produce properly unpredictable data. Hence, we
|
||||
* use a totally different scheme instead.
|
||||
*
|
||||
* What we do is to take a SHA-512 (_big_) hash of the private
|
||||
* key x, and then feed this into another SHA-512 hash that
|
||||
* also includes the message hash being signed. That is:
|
||||
*
|
||||
* proto_k = SHA512 ( SHA512(x) || SHA160(message) )
|
||||
*
|
||||
* This number is 512 bits long, so reducing it mod q won't be
|
||||
* noticeably non-uniform. So
|
||||
*
|
||||
* k = proto_k mod q
|
||||
*
|
||||
* This has the interesting property that it's _deterministic_:
|
||||
* signing the same hash twice with the same key yields the
|
||||
* same signature.
|
||||
*
|
||||
* Despite this determinism, it's still not predictable to an
|
||||
* attacker, because in order to repeat the SHA-512
|
||||
* construction that created it, the attacker would have to
|
||||
* know the private key value x - and by assumption he doesn't,
|
||||
* because if he knew that he wouldn't be attacking k!
|
||||
*
|
||||
* (This trick doesn't, _per se_, protect against reuse of k.
|
||||
* Reuse of k is left to chance; all it does is prevent
|
||||
* _excessively high_ chances of reuse of k due to entropy
|
||||
* problems.)
|
||||
*
|
||||
* Thanks to Colin Plumb for the general idea of using x to
|
||||
* ensure k is hard to guess, and to the Cambridge University
|
||||
* Computer Security Group for helping to argue out all the
|
||||
* fine details.
|
||||
*/
|
||||
ssh_hash *h;
|
||||
unsigned char digest512[64];
|
||||
|
||||
/*
|
||||
* Hash some identifying text plus x.
|
||||
*/
|
||||
h = ssh_hash_new(&ssh_sha512);
|
||||
put_asciz(h, id_string);
|
||||
put_mp_ssh2(h, private_key);
|
||||
ssh_hash_digest(h, digest512);
|
||||
|
||||
/*
|
||||
* Now hash that digest plus the message hash.
|
||||
*/
|
||||
ssh_hash_reset(h);
|
||||
put_data(h, digest512, sizeof(digest512));
|
||||
put_data(h, digest, digest_len);
|
||||
ssh_hash_final(h, digest512);
|
||||
|
||||
/*
|
||||
* Now convert the result into a bignum, and coerce it to the
|
||||
* range [2,q), which we do by reducing it mod q-2 and adding 2.
|
||||
*/
|
||||
mp_int *modminus2 = mp_copy(modulus);
|
||||
mp_sub_integer_into(modminus2, modminus2, 2);
|
||||
mp_int *proto_k = mp_from_bytes_be(make_ptrlen(digest512, 64));
|
||||
mp_int *k = mp_mod(proto_k, modminus2);
|
||||
mp_free(proto_k);
|
||||
mp_free(modminus2);
|
||||
mp_add_integer_into(k, k, 2);
|
||||
|
||||
smemclr(digest512, sizeof(digest512));
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
|
||||
{
|
||||
struct dsa_key *dsa = container_of(key, struct dsa_key, sshk);
|
||||
@ -459,8 +348,9 @@ static void dsa_sign(ssh_key *key, ptrlen data, unsigned flags, BinarySink *bs)
|
||||
|
||||
hash_simple(&ssh_sha1, data, digest);
|
||||
|
||||
mp_int *k = dsa_gen_k("DSA deterministic k generator", dsa->q, dsa->x,
|
||||
digest, sizeof(digest));
|
||||
/* Generate any valid exponent k, using the RFC 6979 deterministic
|
||||
* procedure. */
|
||||
mp_int *k = rfc6979(&ssh_sha1, dsa->q, dsa->x, data);
|
||||
mp_int *kinv = mp_invert(k, dsa->q); /* k^-1 mod q */
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user