diff --git a/src/internal.c b/src/internal.c index 1202d132e..d18982234 100644 --- a/src/internal.c +++ b/src/internal.c @@ -185,6 +185,10 @@ WOLFSSH_NO_CURVE25519_SHA256 Set when Curve25519 or SHA2-256 are disabled in wolfSSL. Set to disable use of Curve25519 key exchange. + WOLFSSH_MR_ROUNDS + Set the number of Miller-Rabin rounds used when the client checks the + server's prime group when using GEX key exchange. The default is 8. More + rounds are better, but also takes a lot longer. */ static const char sshProtoIdStr[] = "SSH-2.0-wolfSSHv" @@ -6284,6 +6288,141 @@ static int DoKexDhGexRequest(WOLFSSH* ssh, } +/* + * Validate a DH GEX group received from the server against RFC 4419 sec. 3 + * requirements, with an additional hardening/policy check: + * - p's bit length falls within the client's requested [minBits, maxBits] + * - p is odd and probably prime + * - (additional hardening requirement) (p-1)/2 is also probably prime, + * i.e. p is a safe prime, which bounds the order of g to q or 2q and + * closes the small-subgroup attack + * Returns WS_SUCCESS if the group is acceptable. + */ +static int ValidateKexDhGexGroup(const byte* primeGroup, word32 primeGroupSz, + const byte* generator, word32 generatorSz, + word32 minBits, word32 maxBits, WC_RNG* rng) +{ + mp_int p, g, q; + int pgqInit = 0; + int bits; + int isPrime = MP_NO; + int ret = WS_SUCCESS; + + if (primeGroup == NULL || primeGroupSz == 0 + || generator == NULL || generatorSz == 0 + || rng == NULL) + ret = WS_BAD_ARGUMENT; + + /* + * Check the bounds on the size of the flat prime group and generator + * values. The prime group shall be LE maxBits. The generator size + * shall be LE prime group size. This is a check on the sizes of values + * sent by the peer before reading them in and checking them as mp_ints. + */ + if (ret == WS_SUCCESS) { + word32 maxBytes = (maxBits / 8) + ((maxBits % 8) ? 1 : 0); + /* Adjust the sizes for signed padding. */ + word32 adjPrimeGroupSz = primeGroupSz - ((primeGroup[0] == 0) ? 1 : 0); + word32 adjGeneratorSz = generatorSz - ((generator[0] == 0) ? 1 : 0); + + if (adjPrimeGroupSz > maxBytes || adjGeneratorSz > adjPrimeGroupSz) { + ret = WS_DH_SIZE_E; + } + } + + if (ret == WS_SUCCESS) { + if (mp_init_multi(&p, &g, &q, NULL, NULL, NULL) != MP_OKAY) { + ret = WS_CRYPTO_FAILED; + } + else { + pgqInit = 1; + } + } + + if (ret == WS_SUCCESS) { + if (mp_read_unsigned_bin(&p, primeGroup, primeGroupSz) != MP_OKAY) + ret = WS_CRYPTO_FAILED; + } + if (ret == WS_SUCCESS) { + if (mp_read_unsigned_bin(&g, generator, generatorSz) != MP_OKAY) + ret = WS_CRYPTO_FAILED; + } + + if (ret == WS_SUCCESS) { + bits = mp_count_bits(&p); + if (bits < (int)minBits || bits > (int)maxBits) { + WLOG(WS_LOG_DEBUG, + "DH GEX: prime size %d outside requested [%u, %u]", + bits, minBits, maxBits); + ret = WS_DH_SIZE_E; + } + } + + if (ret == WS_SUCCESS) { + if (!mp_isodd(&p)) { + WLOG(WS_LOG_DEBUG, "DH GEX: prime is even"); + ret = WS_CRYPTO_FAILED; + } + } + + /* 2 <= g: reject g == 0 and g == 1. */ + if (ret == WS_SUCCESS) { + if (mp_cmp_d(&g, 1) != MP_GT) { + WLOG(WS_LOG_DEBUG, "DH GEX: generator < 2"); + ret = WS_CRYPTO_FAILED; + } + } + + /* g <= p - 2: reject g == p - 1 (order 2) and anything larger. */ + if (ret == WS_SUCCESS) { + if (mp_sub_d(&p, 1, &q) != MP_OKAY) + ret = WS_CRYPTO_FAILED; + else if (mp_cmp(&g, &q) != MP_LT) { + WLOG(WS_LOG_DEBUG, "DH GEX: generator >= p - 1"); + ret = WS_CRYPTO_FAILED; + } + } + + /* Miller-Rabin: p must be prime. */ + if (ret == WS_SUCCESS) { + isPrime = MP_NO; + ret = mp_prime_is_prime_ex(&p, WOLFSSH_MR_ROUNDS, &isPrime, rng); + if (ret != MP_OKAY || isPrime == MP_NO) { + WLOG(WS_LOG_DEBUG, "DH GEX: p is not prime"); + ret = WS_CRYPTO_FAILED; + } + else { + ret = WS_SUCCESS; + } + } + + /* Safe prime check: q = (p - 1) / 2 must also be prime. */ + if (ret == WS_SUCCESS) { + if (mp_sub_d(&p, 1, &q) != MP_OKAY || mp_div_2(&q, &q) != MP_OKAY) + ret = WS_CRYPTO_FAILED; + } + if (ret == WS_SUCCESS) { + isPrime = MP_NO; + ret = mp_prime_is_prime_ex(&q, WOLFSSH_MR_ROUNDS, &isPrime, rng); + if (ret != MP_OKAY || isPrime == MP_NO) { + WLOG(WS_LOG_DEBUG, "DH GEX: (p-1)/2 is not prime, p is not safe"); + ret = WS_CRYPTO_FAILED; + } + else { + ret = WS_SUCCESS; + } + } + + if (pgqInit) { + mp_clear(&q); + mp_clear(&g); + mp_clear(&p); + } + + return ret; +} + + static int DoKexDhGexGroup(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) { @@ -6300,13 +6439,22 @@ static int DoKexDhGexGroup(WOLFSSH* ssh, if (ret == WS_SUCCESS) { begin = *idx; ret = GetMpint(&primeGroupSz, &primeGroup, buf, len, &begin); - if (ret == WS_SUCCESS && primeGroupSz > (MAX_KEX_KEY_SZ + 1)) + if (ret == WS_SUCCESS && primeGroupSz > (MAX_KEX_KEY_SZ + 1)) { ret = WS_DH_SIZE_E; + } } if (ret == WS_SUCCESS) ret = GetMpint(&generatorSz, &generator, buf, len, &begin); + if (ret == WS_SUCCESS) { + ret = ValidateKexDhGexGroup(primeGroup, primeGroupSz, + generator, generatorSz, + ssh->handshake->dhGexMinSz, + ssh->handshake->dhGexMaxSz, + ssh->rng); + } + if (ret == WS_SUCCESS) { if (ssh->handshake->primeGroup) WFREE(ssh->handshake->primeGroup, ssh->ctx->heap, DYNTYPE_MPINT); @@ -6340,7 +6488,17 @@ static int DoKexDhGexGroup(WOLFSSH* ssh, return ret; } + +#ifdef WOLFSSH_TEST_INTERNAL +int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup, + word32 primeGroupSz, const byte* generator, word32 generatorSz, + word32 minBits, word32 maxBits, WC_RNG* rng) +{ + return ValidateKexDhGexGroup(primeGroup, primeGroupSz, + generator, generatorSz, minBits, maxBits, rng); +} #endif +#endif /* !WOLFSSH_NO_DH_GEX_SHA256 */ static int DoIgnore(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) diff --git a/tests/unit.c b/tests/unit.c index 607bf36bd..a3cada9ef 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -31,7 +31,10 @@ #include #include #include +#include #include +#include +#include #include #define WOLFSSH_TEST_HEX2BIN @@ -439,6 +442,190 @@ static int test_DoReceive_VerifyMacFailure(void) #endif /* WOLFSSH_TEST_INTERNAL && any HMAC SHA variant enabled */ +#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256) + +typedef struct { + const char* candidate; + const char* generator; + word32 minBits; + word32 maxBits; + int expectedResult; +} PrimeTestVector; + +static const PrimeTestVector primeTestVectors[] = { + { + /* + * For testing the ValidateKexDhGexGroup() function, we need to + * verify that the function detects unsafe primes. The following + * unsafe prime is the prime used with GOST-ECC. (RFC 7836) It is + * prime and fine for its application. It isn't safe for DH, as + * q = (p-1)/2 is not prime. + */ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc7", + "02", + 512, 8192, WS_CRYPTO_FAILED + }, + { + /* + * We need to verify that the function detects safe primes. The + * following safePrime is the MODP 2048-bit group from RFC 3526. + */ + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" + "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" + "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" + "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", + "02", + 2048, 8192, WS_SUCCESS + }, + { + /* + * This checks for g = p - 1. + */ + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" + "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" + "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" + "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" + "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" + "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" + "3995497cea956ae515d2261898fa051015728e5a8aacaa68fffffffffffffffe", + 2048, 8192, WS_CRYPTO_FAILED + }, + { + /* + * This checks for g = 1. + */ + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" + "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" + "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" + "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", + "01", + 2048, 8192, WS_CRYPTO_FAILED + }, + { + /* + * This checks prime size less than minBits. + */ + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" + "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" + "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" + "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", + "02", + 3072, 8192, WS_DH_SIZE_E + }, + { + /* + * This checks prime size greater than maxBits. + */ + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" + "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" + "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" + "3995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff", + "02", + 512, 1024, WS_DH_SIZE_E + }, + { + /* + * This checks for even p. + */ + "ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74" + "020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f1437" + "4fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7ed" + "ee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf05" + "98da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb" + "9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3b" + "e39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf695581718" + "3995497cea956ae515d2261898fa051015728e5a8aacaa68fffffffffffffffe", + "02", + 2048, 8192, WS_CRYPTO_FAILED + }, + { + /* + * A well known composite number that breaks some MR implementations. + * This is calculated by wolfCrypt for one of its prime tests. + */ + "000000000088cbf655be37a612fa535b4a9b81d394854ecbedfe1a4afbecdc7b" + "a6a263549dd3c17882b054329384962576e7c5aa281e04ab5a0e7245584ad324" + "9c7ac4de7caf5663bae95f6bb9e8bec4124e04d82eac54a246bda49a5c5c2a1b" + "366ef8c085fc7c5f87478a55832d1b2184154c24260df67561d17c4359724403", + "02", + 512, 8192, WS_CRYPTO_FAILED + }, +}; + +static int test_DhGexGroupValidate(void) +{ + WC_RNG rng; + const PrimeTestVector* tv; + byte* candidate; + byte* generator; + word32 candidateSz; + word32 generatorSz; + int tc = (int)(sizeof(primeTestVectors)/sizeof(primeTestVectors[0])); + int result = 0, ret, i; + + if (wc_InitRng(&rng) != 0) { + printf("DhGexGroupValidate: wc_InitRng failed\n"); + return -110; + } + + for (i = 0, tv = primeTestVectors; i < tc && !result; i++, tv++) { + candidate = NULL; + candidateSz = 0; + generator = NULL; + generatorSz = 0; + + ret = ConvertHexToBin(tv->candidate, &candidate, &candidateSz, + tv->generator, &generator, &generatorSz, + NULL, NULL, NULL, NULL, NULL, NULL); + if (ret != 0) { + result = -113; + break; + } + + ret = wolfSSH_TestValidateKexDhGexGroup(candidate, candidateSz, + generator, generatorSz, tv->minBits, tv->maxBits, &rng); + if (ret != tv->expectedResult) { + printf("DhGexGroupValidate: validator returned %d, expected %d\n", + ret, tv->expectedResult); + result = -121; + } + + FreeBins(candidate, generator, NULL, NULL); + } + + wc_FreeRng(&rng); + return result; +} + +#endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_DH_GEX_SHA256 */ + + /* Error Code And Message Test */ static int test_Errors(void) @@ -520,6 +707,13 @@ int wolfSSH_UnitTest(int argc, char** argv) testResult = testResult || unitResult; #endif +#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256) + unitResult = test_DhGexGroupValidate(); + printf("DhGexGroupValidate: %s\n", + (unitResult == 0 ? "SUCCESS" : "FAILED")); + testResult = testResult || unitResult; +#endif + #ifdef WOLFSSH_KEYGEN #ifndef WOLFSSH_NO_RSA unitResult = test_RsaKeyGen(); diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 2aaa3b8a1..9d204137f 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -476,6 +476,9 @@ enum NameIdType { #define MAX_KEX_KEY_SZ (WOLFSSH_DEFAULT_GEXDH_MAX / 8) #endif #endif +#ifndef WOLFSSH_MR_ROUNDS + #define WOLFSSH_MR_ROUNDS 8 +#endif #ifndef WOLFSSH_MAX_FILE_SIZE #define WOLFSSH_MAX_FILE_SIZE (1024ul * 1024ul * 4) #endif @@ -1324,7 +1327,12 @@ enum WS_MessageIdLimits { WOLFSSH_API int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg, byte state); WOLFSSH_API int wolfSSH_TestDoReceive(WOLFSSH* ssh); -#endif +#ifndef WOLFSSH_NO_DH_GEX_SHA256 + WOLFSSH_API int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup, + word32 primeGroupSz, const byte* generator, word32 generatorSz, + word32 minBits, word32 maxBits, WC_RNG* rng); +#endif /* !WOLFSSH_NO_DH_GEX_SHA256 */ +#endif /* WOLFSSH_TEST_INTERNAL */ /* dynamic memory types */ enum WS_DynamicTypes {