Skip to content

Commit 49dbbba

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 49dbbba

File tree

3 files changed

+256
-3
lines changed

3 files changed

+256
-3
lines changed

src/internal.c

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6284,6 +6284,150 @@ static int DoKexDhGexRequest(WOLFSSH* ssh,
62846284
}
62856285

62866286

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

63076449
if (ret == WS_SUCCESS)
63086450
ret = GetMpint(&generatorSz, &generator, buf, len, &begin);
63096451

6452+
if (ret == WS_SUCCESS) {
6453+
ret = ValidateKexDhGexGroup(primeGroup, primeGroupSz,
6454+
generator, generatorSz,
6455+
ssh->handshake->dhGexMinSz,
6456+
ssh->handshake->dhGexMaxSz,
6457+
ssh->rng);
6458+
}
6459+
63106460
if (ret == WS_SUCCESS) {
63116461
if (ssh->handshake->primeGroup)
63126462
WFREE(ssh->handshake->primeGroup, ssh->ctx->heap, DYNTYPE_MPINT);
@@ -6340,7 +6490,17 @@ static int DoKexDhGexGroup(WOLFSSH* ssh,
63406490

63416491
return ret;
63426492
}
6493+
6494+
#ifdef WOLFSSH_TEST_INTERNAL
6495+
int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup,
6496+
word32 primeGroupSz, const byte* generator, word32 generatorSz,
6497+
word32 minBits, word32 maxBits, WC_RNG* rng)
6498+
{
6499+
return ValidateKexDhGexGroup(primeGroup, primeGroupSz,
6500+
generator, generatorSz, minBits, maxBits, rng);
6501+
}
63436502
#endif
6503+
#endif /* !WOLFSSH_NO_DH_GEX_SHA256 */
63446504

63456505

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

tests/unit.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131
#include <stdio.h>
3232
#include <wolfssh/ssh.h>
3333
#include <wolfssh/keygen.h>
34+
#include <wolfssh/error.h>
3435
#include <wolfssh/internal.h>
36+
#include <wolfssl/wolfcrypt/random.h>
37+
#include <wolfssl/wolfcrypt/integer.h>
3538
#include <wolfssl/wolfcrypt/hmac.h>
3639

3740
#define WOLFSSH_TEST_HEX2BIN
@@ -439,6 +442,81 @@ static int test_DoReceive_VerifyMacFailure(void)
439442
#endif /* WOLFSSH_TEST_INTERNAL && any HMAC SHA variant enabled */
440443

441444

445+
#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256)
446+
447+
/*
448+
* For testing the ValidateKexDhGexGroup() function, we need to verify that
449+
* the function detects unsafe primes. The following unsafePrime is the
450+
* prime used with GOST-ECC. (RFC 7836) It is fine for that. It isn't safe
451+
* for DH.
452+
*/
453+
static const char unsafePrime[] =
454+
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
455+
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc7";
456+
457+
/*
458+
* We need to verify that that the function detects a safe primes. The
459+
* following safePrime is the MODP 2048-bit group from RFC 3526.
460+
*/
461+
static const char safePrime[] =
462+
"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74"
463+
"020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437"
464+
"4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed"
465+
"ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05"
466+
"98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb"
467+
"9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b"
468+
"e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718"
469+
"3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff";
470+
471+
472+
static int test_DhGexGroupValidate(void)
473+
{
474+
WC_RNG rng;
475+
byte* unsafe = NULL;
476+
word32 unsafeSz = 0;
477+
byte* safe = NULL;
478+
word32 safeSz = 0;
479+
byte generator = 2;
480+
int result = 0, ret;
481+
482+
if (wc_InitRng(&rng) != 0) {
483+
printf("DhGexGroupValidate: wc_InitRng failed\n");
484+
return -110;
485+
}
486+
487+
ret = ConvertHexToBin(unsafePrime, &unsafe, &unsafeSz,
488+
safePrime, &safe, &safeSz,
489+
NULL, NULL, NULL, NULL, NULL, NULL);
490+
if (ret != 0) {
491+
result = -113;
492+
goto cleanup;
493+
}
494+
495+
ret = wolfSSH_TestValidateKexDhGexGroup(unsafe, unsafeSz, &generator, 1,
496+
512, 8192, &rng);
497+
if (ret != WS_CRYPTO_FAILED) {
498+
printf("DhGexGroupValidate: validator returned %d, expected %d\n",
499+
ret, WS_CRYPTO_FAILED);
500+
result = -121;
501+
}
502+
503+
ret = wolfSSH_TestValidateKexDhGexGroup(safe, safeSz, &generator, 1,
504+
1024, 8192, &rng);
505+
if (ret != WS_SUCCESS) {
506+
printf("DhGexGroupValidate: validator returned %d, expected %d\n",
507+
ret, WS_SUCCESS);
508+
result = -122;
509+
}
510+
511+
cleanup:
512+
FreeBins(unsafe, safe, NULL, NULL);
513+
wc_FreeRng(&rng);
514+
return result;
515+
}
516+
517+
#endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_DH_GEX_SHA256 */
518+
519+
442520
/* Error Code And Message Test */
443521

444522
static int test_Errors(void)
@@ -520,6 +598,13 @@ int wolfSSH_UnitTest(int argc, char** argv)
520598
testResult = testResult || unitResult;
521599
#endif
522600

601+
#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256)
602+
unitResult = test_DhGexGroupValidate();
603+
printf("DhGexGroupValidate: %s\n",
604+
(unitResult == 0 ? "SUCCESS" : "FAILED"));
605+
testResult = testResult || unitResult;
606+
#endif
607+
523608
#ifdef WOLFSSH_KEYGEN
524609
#ifndef WOLFSSH_NO_RSA
525610
unitResult = test_RsaKeyGen();

wolfssh/internal.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,9 @@ enum NameIdType {
476476
#define MAX_KEX_KEY_SZ (WOLFSSH_DEFAULT_GEXDH_MAX / 8)
477477
#endif
478478
#endif
479+
#ifndef WOLFSSH_MR_ROUNDS
480+
#define WOLFSSH_MR_ROUNDS 64
481+
#endif
479482
#ifndef WOLFSSH_MAX_FILE_SIZE
480483
#define WOLFSSH_MAX_FILE_SIZE (1024ul * 1024ul * 4)
481484
#endif
@@ -1324,7 +1327,12 @@ enum WS_MessageIdLimits {
13241327
WOLFSSH_API int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg,
13251328
byte state);
13261329
WOLFSSH_API int wolfSSH_TestDoReceive(WOLFSSH* ssh);
1327-
#endif
1330+
#ifndef WOLFSSH_NO_DH_GEX_SHA256
1331+
WOLFSSH_API int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup,
1332+
word32 primeGroupSz, const byte* generator, word32 generatorSz,
1333+
word32 minBits, word32 maxBits, WC_RNG* rng);
1334+
#endif /* !WOLFSSH_NO_DH_GEX_SHA256 */
1335+
#endif /* WOLFSSH_TEST_INTERNAL */
13281336

13291337
/* dynamic memory types */
13301338
enum WS_DynamicTypes {

0 commit comments

Comments
 (0)