diff --git a/src/dh-gex.c b/src/dh-gex.c index 407c2391..508b6363 100644 --- a/src/dh-gex.c +++ b/src/dh-gex.c @@ -23,6 +23,11 @@ #include "config.h" +#include +#include +#include +#include + #include "libssh/priv.h" #include "libssh/dh-gex.h" #include "libssh/libssh.h" @@ -252,3 +257,250 @@ error: return SSH_PACKET_USED; } + +#ifdef WITH_SERVER + +#define MODULI_FILE "/etc/ssh/moduli" +/* 2 "Safe" prime; (p-1)/2 is also prime. */ +#define SAFE_PRIME 2 +/* 0x04 Probabilistic Miller-Rabin primality tests. */ +#define PRIM_TEST_REQUIRED 0x04 + +/** + * @internal + * + * @brief Determines if the proposed modulus size is more appropriate than the + * current one. + * + * @returns 1 if it's more appropriate. Returns 0 if same or less appropriate + */ +static bool dhgroup_better_size(uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t current_size, + size_t proposed_size) +{ + if (current_size == proposed_size) { + return false; + } + + if (current_size == pn) { + /* can't do better */ + return false; + } + + if (current_size == 0 && proposed_size >= pmin && proposed_size <= pmax) { + return true; + } + + if (proposed_size < pmin || proposed_size > pmax) { + /* out of bounds */ + return false; + } + + if (current_size == 0) { + /* not in the allowed window */ + return false; + } + + if (proposed_size >= pn && proposed_size < current_size) { + return true; + } + + if (proposed_size <= pn && proposed_size > current_size) { + return true; + } + + if (proposed_size >= pn && current_size < pn) { + return true; + } + + /* We're in the allowed window but a better match already exists. */ + return false; +} + +/** @internal + * @brief returns 1 with 1/n probability + * @returns 1 on with P(1/n), 0 with P(n-1/n). + */ +static bool invn_chance(int n) +{ + uint32_t nounce; + ssh_get_random(&nounce, sizeof(nounce), 0); + return (nounce % n) == 0; +} + +/** @internal + * @brief retrieves a DH group from an open moduli file. + */ +static int ssh_retrieve_dhgroup_file(FILE *moduli, + uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *best_size, + char **best_generator, + char **best_modulus) +{ + char timestamp[32] = {0}; + char generator[32] = {0}; + char modulus[4096] = {0}; + size_t type, tests, tries, size, proposed_size; + int firstbyte; + int rc; + size_t line = 0; + size_t best_nlines = 0; + + for(;;) { + line++; + firstbyte = getc(moduli); + if (firstbyte == '#'){ + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + continue; + } + if (firstbyte == EOF) { + break; + } + ungetc(firstbyte, moduli); + rc = fscanf(moduli, + "%31s %zu %zu %zu %zu %31s %4095s\n", + timestamp, + &type, + &tests, + &tries, + &size, + generator, + modulus); + if (rc != 7){ + if (rc == EOF) { + break; + } + SSH_LOG(SSH_LOG_INFO, "Invalid moduli entry line %zu", line); + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + continue; + } + + /* we only want safe primes that were tested */ + if (type != SAFE_PRIME || !(tests & PRIM_TEST_REQUIRED)) { + continue; + } + + proposed_size = size + 1; + if (proposed_size != *best_size && + dhgroup_better_size(pmin, pn, pmax, *best_size, proposed_size)) { + best_nlines = 0; + *best_size = proposed_size; + } + if (proposed_size == *best_size) { + best_nlines++; + } + + /* Use reservoir sampling algorithm */ + if (proposed_size == *best_size && invn_chance(best_nlines)) { + SAFE_FREE(*best_generator); + SAFE_FREE(*best_modulus); + *best_generator = strdup(generator); + if (*best_generator == NULL) { + return SSH_ERROR; + } + *best_modulus = strdup(modulus); + if (*best_modulus == NULL) { + SAFE_FREE(*best_generator); + return SSH_ERROR; + } + } + } + if (*best_size != 0) { + SSH_LOG(SSH_LOG_INFO, + "Selected %zu bits modulus out of %zu candidates in %zu lines", + *best_size, + best_nlines - 1, + line); + } else { + SSH_LOG(SSH_LOG_WARNING, + "No moduli found for [%u:%u:%u]", + pmin, + pn, + pmax); + } + + return SSH_OK; +} + +/** @internal + * @brief retrieves a DH group from the moduli file based on bits len parameters + * @param[in] pmin minimum group size in bits + * @param[in] pn preferred group size + * @param[in] pmax maximum group size + * @param[out] size size of the chosen modulus + * @param[out] p modulus + * @param[out] g generator + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +/* TODO Make this function static when only used in this file */ +int ssh_retrieve_dhgroup(uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *size, + bignum *p, + bignum *g); +int ssh_retrieve_dhgroup(uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *size, + bignum *p, + bignum *g) +{ + FILE *moduli = NULL; + char *generator = NULL; + char *modulus = NULL; + int rc; + + moduli = fopen(MODULI_FILE, "r"); + if (moduli == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Unable to open moduli file: %s", + strerror(errno)); + return SSH_ERROR; + } + + *size = 0; + *p = NULL; + *g = NULL; + + rc = ssh_retrieve_dhgroup_file(moduli, + pmin, + pn, + pmax, + size, + &generator, + &modulus); + if (rc == SSH_ERROR || *size == 0) { + goto error; + } + rc = bignum_hex2bn(generator, g); + if (rc == 0) { + goto error; + } + rc = bignum_hex2bn(modulus, p); + if (rc == 0) { + goto error; + } + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_OK; + +error: + bignum_safe_free(*g); + bignum_safe_free(*p); + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_ERROR; +} + +#endif /* WITH_SERVER */