@@ -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+
62876431static 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
63466506static int DoIgnore(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
0 commit comments