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