Skip to content

Commit 16ddcd9

Browse files
committed
Validate server's group
The client wasn't validating the DH group parameters in the KEX DH GEX Group message. This adds a function to perform the validation of the prime `p` to verify it is safe. (Prime and that ((p - 1) / 2) is prime.) Also adds a test to a known unsafe prime and known safe prime to verify the validate function. Affected function: DoKexDhGexGroup. Issue: F-1688
1 parent 543a6c2 commit 16ddcd9

File tree

3 files changed

+362
-2
lines changed

3 files changed

+362
-2
lines changed

src/internal.c

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@
185185
WOLFSSH_NO_CURVE25519_SHA256
186186
Set when Curve25519 or SHA2-256 are disabled in wolfSSL. Set to disable use
187187
of Curve25519 key exchange.
188+
WOLFSSH_MR_ROUNDS
189+
Set the number of Miller-Rabin rounds used when the client checks the
190+
server's prime group when using GEX key exchange. The default is 8. More
191+
rounds are better, but also takes a lot longer.
188192
*/
189193

190194
static const char sshProtoIdStr[] = "SSH-2.0-wolfSSHv"
@@ -6284,6 +6288,141 @@ static int DoKexDhGexRequest(WOLFSSH* ssh,
62846288
}
62856289

62866290

6291+
/*
6292+
* Validate a DH GEX group received from the server against RFC 4419 sec. 3
6293+
* requirements, with an additional hardening/policy check:
6294+
* - p's bit length falls within the client's requested [minBits, maxBits]
6295+
* - p is odd and probably prime
6296+
* - (additional hardening requirement) (p-1)/2 is also probably prime,
6297+
* i.e. p is a safe prime, which bounds the order of g to q or 2q and
6298+
* closes the small-subgroup attack
6299+
* Returns WS_SUCCESS if the group is acceptable.
6300+
*/
6301+
static int ValidateKexDhGexGroup(const byte* primeGroup, word32 primeGroupSz,
6302+
const byte* generator, word32 generatorSz,
6303+
word32 minBits, word32 maxBits, WC_RNG* rng)
6304+
{
6305+
mp_int p, g, q;
6306+
int pgqInit = 0;
6307+
int bits;
6308+
int isPrime = MP_NO;
6309+
int ret = WS_SUCCESS;
6310+
6311+
if (primeGroup == NULL || primeGroupSz == 0
6312+
|| generator == NULL || generatorSz == 0
6313+
|| rng == NULL)
6314+
ret = WS_BAD_ARGUMENT;
6315+
6316+
/*
6317+
* Check the bounds on the size of the flat prime group and generator
6318+
* values. The prime group shall be LE maxBits. The generator size
6319+
* shall be LE prime group size. This is a check on the sizes of values
6320+
* sent by the peer before reading them in and checking them as mp_ints.
6321+
*/
6322+
if (ret == WS_SUCCESS) {
6323+
word32 maxBytes = (maxBits / 8) + ((maxBits % 8) ? 1 : 0);
6324+
/* Adjust the sizes for signed padding. */
6325+
word32 adjPrimeGroupSz = primeGroupSz - ((primeGroup[0] == 0) ? 1 : 0);
6326+
word32 adjGeneratorSz = generatorSz - ((generator[0] == 0) ? 1 : 0);
6327+
6328+
if (adjPrimeGroupSz > maxBytes || adjGeneratorSz > adjPrimeGroupSz) {
6329+
ret = WS_DH_SIZE_E;
6330+
}
6331+
}
6332+
6333+
if (ret == WS_SUCCESS) {
6334+
if (mp_init_multi(&p, &g, &q, NULL, NULL, NULL) != MP_OKAY) {
6335+
ret = WS_CRYPTO_FAILED;
6336+
}
6337+
else {
6338+
pgqInit = 1;
6339+
}
6340+
}
6341+
6342+
if (ret == WS_SUCCESS) {
6343+
if (mp_read_unsigned_bin(&p, primeGroup, primeGroupSz) != MP_OKAY)
6344+
ret = WS_CRYPTO_FAILED;
6345+
}
6346+
if (ret == WS_SUCCESS) {
6347+
if (mp_read_unsigned_bin(&g, generator, generatorSz) != MP_OKAY)
6348+
ret = WS_CRYPTO_FAILED;
6349+
}
6350+
6351+
if (ret == WS_SUCCESS) {
6352+
bits = mp_count_bits(&p);
6353+
if (bits < (int)minBits || bits > (int)maxBits) {
6354+
WLOG(WS_LOG_DEBUG,
6355+
"DH GEX: prime size %d outside requested [%u, %u]",
6356+
bits, minBits, maxBits);
6357+
ret = WS_DH_SIZE_E;
6358+
}
6359+
}
6360+
6361+
if (ret == WS_SUCCESS) {
6362+
if (!mp_isodd(&p)) {
6363+
WLOG(WS_LOG_DEBUG, "DH GEX: prime is even");
6364+
ret = WS_CRYPTO_FAILED;
6365+
}
6366+
}
6367+
6368+
/* 2 <= g: reject g == 0 and g == 1. */
6369+
if (ret == WS_SUCCESS) {
6370+
if (mp_cmp_d(&g, 1) != MP_GT) {
6371+
WLOG(WS_LOG_DEBUG, "DH GEX: generator < 2");
6372+
ret = WS_CRYPTO_FAILED;
6373+
}
6374+
}
6375+
6376+
/* g <= p - 2: reject g == p - 1 (order 2) and anything larger. */
6377+
if (ret == WS_SUCCESS) {
6378+
if (mp_sub_d(&p, 1, &q) != MP_OKAY)
6379+
ret = WS_CRYPTO_FAILED;
6380+
else if (mp_cmp(&g, &q) != MP_LT) {
6381+
WLOG(WS_LOG_DEBUG, "DH GEX: generator >= p - 1");
6382+
ret = WS_CRYPTO_FAILED;
6383+
}
6384+
}
6385+
6386+
/* Miller-Rabin: p must be prime. */
6387+
if (ret == WS_SUCCESS) {
6388+
isPrime = MP_NO;
6389+
ret = mp_prime_is_prime_ex(&p, WOLFSSH_MR_ROUNDS, &isPrime, rng);
6390+
if (ret != MP_OKAY || isPrime == MP_NO) {
6391+
WLOG(WS_LOG_DEBUG, "DH GEX: p is not prime");
6392+
ret = WS_CRYPTO_FAILED;
6393+
}
6394+
else {
6395+
ret = WS_SUCCESS;
6396+
}
6397+
}
6398+
6399+
/* Safe prime check: q = (p - 1) / 2 must also be prime. */
6400+
if (ret == WS_SUCCESS) {
6401+
if (mp_sub_d(&p, 1, &q) != MP_OKAY || mp_div_2(&q, &q) != MP_OKAY)
6402+
ret = WS_CRYPTO_FAILED;
6403+
}
6404+
if (ret == WS_SUCCESS) {
6405+
isPrime = MP_NO;
6406+
ret = mp_prime_is_prime_ex(&q, WOLFSSH_MR_ROUNDS, &isPrime, rng);
6407+
if (ret != MP_OKAY || isPrime == MP_NO) {
6408+
WLOG(WS_LOG_DEBUG, "DH GEX: (p-1)/2 is not prime, p is not safe");
6409+
ret = WS_CRYPTO_FAILED;
6410+
}
6411+
else {
6412+
ret = WS_SUCCESS;
6413+
}
6414+
}
6415+
6416+
if (pgqInit) {
6417+
mp_clear(&q);
6418+
mp_clear(&g);
6419+
mp_clear(&p);
6420+
}
6421+
6422+
return ret;
6423+
}
6424+
6425+
62876426
static int DoKexDhGexGroup(WOLFSSH* ssh,
62886427
byte* buf, word32 len, word32* idx)
62896428
{
@@ -6300,13 +6439,22 @@ static int DoKexDhGexGroup(WOLFSSH* ssh,
63006439
if (ret == WS_SUCCESS) {
63016440
begin = *idx;
63026441
ret = GetMpint(&primeGroupSz, &primeGroup, buf, len, &begin);
6303-
if (ret == WS_SUCCESS && primeGroupSz > (MAX_KEX_KEY_SZ + 1))
6442+
if (ret == WS_SUCCESS && primeGroupSz > (MAX_KEX_KEY_SZ + 1)) {
63046443
ret = WS_DH_SIZE_E;
6444+
}
63056445
}
63066446

63076447
if (ret == WS_SUCCESS)
63086448
ret = GetMpint(&generatorSz, &generator, buf, len, &begin);
63096449

6450+
if (ret == WS_SUCCESS) {
6451+
ret = ValidateKexDhGexGroup(primeGroup, primeGroupSz,
6452+
generator, generatorSz,
6453+
ssh->handshake->dhGexMinSz,
6454+
ssh->handshake->dhGexMaxSz,
6455+
ssh->rng);
6456+
}
6457+
63106458
if (ret == WS_SUCCESS) {
63116459
if (ssh->handshake->primeGroup)
63126460
WFREE(ssh->handshake->primeGroup, ssh->ctx->heap, DYNTYPE_MPINT);
@@ -6340,7 +6488,17 @@ static int DoKexDhGexGroup(WOLFSSH* ssh,
63406488

63416489
return ret;
63426490
}
6491+
6492+
#ifdef WOLFSSH_TEST_INTERNAL
6493+
int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup,
6494+
word32 primeGroupSz, const byte* generator, word32 generatorSz,
6495+
word32 minBits, word32 maxBits, WC_RNG* rng)
6496+
{
6497+
return ValidateKexDhGexGroup(primeGroup, primeGroupSz,
6498+
generator, generatorSz, minBits, maxBits, rng);
6499+
}
63436500
#endif
6501+
#endif /* !WOLFSSH_NO_DH_GEX_SHA256 */
63446502

63456503

63466504
static int DoIgnore(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)

0 commit comments

Comments
 (0)