diff --git a/README.md b/README.md index 24611b47..3db632e3 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,14 @@ Information on how to configure, build, and test wolfProvider can be found here: * Key exchange * Key generation +Parameter generation refuses a requested generator other than 2: wolfSSL +derives the generator from the prime it generates, so `openssl dhparam -3` +and `-5` fail. A request for 2, which `openssl dhparam` sends by default, is +accepted, but the generator is still the one wolfSSL derives. Key generation +from a named group or existing parameters accepts a generator only when the +parameters already have it. A non-zero `priv_len` is refused because wolfSSL +takes the private key length from the group. + ### ECC * ECDSA (signing, verification) * ECDH (key exchange) diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index 508fd781..40f3c1bd 100644 --- a/src/wp_dh_kmgmt.c +++ b/src/wp_dh_kmgmt.c @@ -100,9 +100,7 @@ typedef struct wp_DhGenCtx { char name[WP_MAX_DH_GROUP_NAME_SZ]; /** Number of bits in prime. */ int bits; - /** Length of private key to generate - value ignored. */ - int privLen; - /** DH generator parameter to use in generation - value ignored. */ + /** Generator requested by the caller. 0 when not requested. */ int generator; } wp_DhGenCtx; @@ -1615,7 +1613,6 @@ static wp_DhGenCtx* wp_dh_gen_init(WOLFPROV_CTX* provCtx, ctx->provCtx = provCtx; ctx->selection = selection; ctx->bits = 2048; - ctx->generator = 2; } if (ok) { if (!wp_dh_gen_set_params(ctx, params)) { @@ -1682,6 +1679,8 @@ static int wp_dh_gen_set_params(wp_DhGenCtx* ctx, const OSSL_PARAM params[]) { int ok = 1; int bits; + int privLen; + int generator; const OSSL_PARAM* p; WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_gen_set_params"); @@ -1703,14 +1702,32 @@ static int wp_dh_gen_set_params(wp_DhGenCtx* ctx, const OSSL_PARAM params[]) } if (ok) { p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_DH_PRIV_LEN); - if ((p != NULL) && (!OSSL_PARAM_get_int(p, &ctx->privLen))) { - return 0; + if (p != NULL) { + if (!OSSL_PARAM_get_int(p, &privLen)) { + ok = 0; + } + /* wolfSSL takes the private key length from q, or from the size + * of p when there is no q. A requested length cannot be applied. */ + if (ok && (privLen != 0)) { + ERR_raise(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED); + ok = 0; + } } } if (ok) { p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_DH_GENERATOR); - if ((p != NULL) && (!OSSL_PARAM_get_int(p, &ctx->generator))) { - ok = 0; + if (p != NULL) { + if (!OSSL_PARAM_get_int(p, &generator)) { + ok = 0; + } + /* A generator below 2 is not usable. */ + if (ok && (generator < 2)) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_DATA); + ok = 0; + } + if (ok) { + ctx->generator = generator; + } } } if (ok && (!wp_params_get_utf8_string(params, OSSL_PKEY_PARAM_GROUP_NAME, @@ -1737,11 +1754,20 @@ static int wp_dh_gen_parameters(wp_DhGenCtx *ctx, wp_Dh* dh) WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_gen_parameters"); - rc = wc_DhGenerateParams(&ctx->rng, ctx->bits, &dh->key); - if (rc != 0) { - WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_DhGenerateParams", rc); + /* wc_DhGenerateParams picks the generator itself. OpenSSL applications + * send 2 when the caller named none, so only another value is refused. */ + if ((ctx->generator != 0) && (ctx->generator != 2)) { + ERR_raise(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED); ok = 0; } + if (ok) { + rc = wc_DhGenerateParams(&ctx->rng, ctx->bits, &dh->key); + if (rc != 0) { + WOLFPROV_MSG_DEBUG_RETCODE(WP_LOG_LEVEL_DEBUG, "wc_DhGenerateParams", + rc); + ok = 0; + } + } if (ok) { dh->bits = mp_count_bits(&dh->key.p); } @@ -1799,6 +1825,13 @@ static int wp_dh_gen_copy_parameters(wp_DhGenCtx *ctx, wp_Dh* dh) ok = 0; } + /* A requested generator is met only when the group already has it. */ + if (ok && (ctx->generator != 0) && + (mp_cmp_d(&dh->key.g, (mp_digit)ctx->generator) != MP_EQ)) { + ERR_raise(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED); + ok = 0; + } + WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } @@ -2020,7 +2053,8 @@ static const OSSL_PARAM* wp_dh_gen_settable_params(wp_DhGenCtx* ctx, WOLFPROV_CTX* provCtx) { /** - * Supported settable parameters for DH generation context. + * Settable parameters for DH generation context. Some values are refused + * because wolfSSL cannot apply them. */ static OSSL_PARAM wp_dh_gen_supported_settable[] = { OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0), diff --git a/test/test_dh.c b/test/test_dh.c index 1cf3529e..27da1036 100644 --- a/test/test_dh.c +++ b/test/test_dh.c @@ -1997,4 +1997,262 @@ int test_dh_param_check_q(void *data) return err; } +/* The generation interface must not report success for controls wolfSSL + * cannot apply: it picks the generator during parameter generation and + * derives the private key length from the group. */ +#define TEST_DH_GEN_PRIV_LEN 256 + +/* Parameter generation refuses a generator wolfSSL cannot produce. It takes + * 2 because that is what applications send when the caller named none. */ +static int test_dh_pgen_generator(int gen, int expectOk) +{ + int err = 0; + int rc; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *keyParams = NULL; + BIGNUM *prime = NULL; + BIGNUM *generator = NULL; + + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "DH", NULL); + err = ctx == NULL; + if (err == 0) { + err = EVP_PKEY_paramgen_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, TEST_DH_MIN_BITS) + != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, gen) != 1; + if (err != 0) { + PRINT_MSG("set_dh_paramgen_generator refused a usable generator"); + } + } + if (err == 0) { + rc = EVP_PKEY_paramgen(ctx, &keyParams); + if (expectOk && (rc != 1)) { + PRINT_MSG("paramgen refused the generator applications default to"); + err = 1; + } + if ((!expectOk) && (rc == 1)) { + PRINT_MSG("paramgen accepted a generator it cannot produce"); + err = 1; + } + } + /* wolfSSL derives the generator from the prime it made, so the value + * produced is not the requested one and is not compared with it. */ + if ((err == 0) && expectOk) { + err = EVP_PKEY_get_bn_param(keyParams, OSSL_PKEY_PARAM_FFC_P, &prime) + != 1; + } + if ((err == 0) && expectOk) { + err = EVP_PKEY_get_bn_param(keyParams, OSSL_PKEY_PARAM_FFC_G, + &generator) != 1; + } + if ((err == 0) && expectOk) { + if (BN_num_bits(prime) < TEST_DH_MIN_BITS) { + PRINT_MSG("paramgen produced a prime below the minimum size"); + err = 1; + } + /* A generator must be at least 2 and less than the prime. */ + if ((err == 0) && ((BN_num_bits(generator) < 2) || + (BN_cmp(generator, prime) >= 0))) { + PRINT_MSG("paramgen produced a generator outside 2 to p - 1"); + err = 1; + } + } + + BN_free(generator); + BN_free(prime); + EVP_PKEY_free(keyParams); + EVP_PKEY_CTX_free(ctx); + return err; +} + +/* Build a DH parameters key for the group the generator tests use. */ +static int test_dh_params_from_group(EVP_PKEY **keyParams) +{ + int err; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM params[2]; + + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, + (char*)"ffdhe2048", 0); + params[1] = OSSL_PARAM_construct_end(); + + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "DH", NULL); + err = ctx == NULL; + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_fromdata(ctx, keyParams, EVP_PKEY_KEY_PARAMETERS, + params) != 1; + } + + EVP_PKEY_CTX_free(ctx); + return err; +} + +/* Key generation takes a generator only when the domain parameters already + * have it, whether they come from a group name or a parameters key. */ +static int test_dh_keygen_generator(int gen, int expectOk, int useTemplate) +{ + int err = 0; + int rc; + int idx = 0; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *keyParams = NULL; + EVP_PKEY *key = NULL; + BIGNUM *g = NULL; + OSSL_PARAM params[3]; + + if (useTemplate) { + err = test_dh_params_from_group(&keyParams); + } + else { + params[idx++] = OSSL_PARAM_construct_utf8_string( + OSSL_PKEY_PARAM_GROUP_NAME, (char*)"ffdhe2048", 0); + } + params[idx++] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_DH_GENERATOR, + &gen); + params[idx] = OSSL_PARAM_construct_end(); + + if (err == 0) { + if (useTemplate) { + ctx = EVP_PKEY_CTX_new_from_pkey(wpLibCtx, keyParams, NULL); + } + else { + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "DH", NULL); + } + err = ctx == NULL; + } + if (err == 0) { + err = EVP_PKEY_keygen_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_CTX_set_params(ctx, params) != 1; + } + if (err == 0) { + rc = EVP_PKEY_generate(ctx, &key); + if (expectOk && (rc != 1)) { + PRINT_MSG("keygen refused the generator the parameters have"); + err = 1; + } + if ((!expectOk) && (rc == 1)) { + PRINT_MSG("keygen accepted a generator the parameters lack"); + err = 1; + } + } + if ((err == 0) && expectOk) { + err = EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_FFC_G, &g) != 1; + if ((err == 0) && (!BN_is_word(g, (BN_ULONG)gen))) { + PRINT_MSG("keygen produced a generator other than the requested"); + err = 1; + } + } + + BN_free(g); + EVP_PKEY_free(key); + EVP_PKEY_free(keyParams); + EVP_PKEY_CTX_free(ctx); + return err; +} + +int test_dh_pgen_controls(void *data) +{ + int err = 0; + int privLen = TEST_DH_GEN_PRIV_LEN; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM params[2]; + + (void)data; + + PRINT_MSG("Testing DH generator and private length generation controls"); + + PRINT_MSG("Parameter generation refuses a generator it cannot produce"); + err = test_dh_pgen_generator(5, 0); + if (err == 0) { + PRINT_MSG("Parameter generation takes the generator 2 applications " + "send by default"); + err = test_dh_pgen_generator(2, 1); + } + + PRINT_MSG("A generator below 2 is refused when set"); + if (err == 0) { + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "DH", NULL); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_PKEY_paramgen_init(ctx) != 1; + } + if (err == 0) { + if (EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, 1) == 1) { + PRINT_MSG("set_dh_paramgen_generator accepted a generator of 1"); + err = 1; + } + } + EVP_PKEY_CTX_free(ctx); + ctx = NULL; + + if (err == 0) { + PRINT_MSG("Key generation takes the generator the named group has"); + err = test_dh_keygen_generator(2, 1, 0); + } + if (err == 0) { + PRINT_MSG("Key generation refuses a generator the named group lacks"); + err = test_dh_keygen_generator(5, 0, 0); + } + if (err == 0) { + PRINT_MSG("Key generation takes the generator a parameters key has"); + err = test_dh_keygen_generator(2, 1, 1); + } + if (err == 0) { + PRINT_MSG("Key generation refuses a generator a parameters key lacks"); + err = test_dh_keygen_generator(5, 0, 1); + } + + PRINT_MSG("A private key length is refused when set"); + if (err == 0) { + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "DH", NULL); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_PKEY_keygen_init(ctx) != 1; + } + if (err == 0) { + params[0] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_DH_PRIV_LEN, + &privLen); + params[1] = OSSL_PARAM_construct_end(); + if (EVP_PKEY_CTX_set_params(ctx, params) == 1) { + PRINT_MSG("set_params accepted a private key length"); + err = 1; + } + } + EVP_PKEY_CTX_free(ctx); + ctx = NULL; + + PRINT_MSG("A private key length of 0 is taken"); + if (err == 0) { + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "DH", NULL); + err = ctx == NULL; + } + if (err == 0) { + err = EVP_PKEY_keygen_init(ctx) != 1; + } + if (err == 0) { + privLen = 0; + params[0] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_DH_PRIV_LEN, + &privLen); + params[1] = OSSL_PARAM_construct_end(); + if (EVP_PKEY_CTX_set_params(ctx, params) != 1) { + PRINT_MSG("set_params refused a private key length of 0"); + err = 1; + } + } + EVP_PKEY_CTX_free(ctx); + + return err; +} + #endif /* WP_HAVE_DH */ diff --git a/test/unit.c b/test/unit.c index 5f3f50b9..61437f08 100644 --- a/test/unit.c +++ b/test/unit.c @@ -379,6 +379,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_dh_pgen_min_bits, NULL), TEST_DECL(test_dh_weak_group_rejected, NULL), TEST_DECL(test_dh_param_check_q, NULL), + TEST_DECL(test_dh_pgen_controls, NULL), #ifndef WOLFPROV_QUICKTEST TEST_DECL(test_dh_get_params, NULL), #endif diff --git a/test/unit.h b/test/unit.h index e8c083c4..3174a710 100644 --- a/test/unit.h +++ b/test/unit.h @@ -434,6 +434,7 @@ int test_dh_import_group_no_nul(void *data); int test_dh_pgen_min_bits(void *data); int test_dh_weak_group_rejected(void *data); int test_dh_param_check_q(void *data); +int test_dh_pgen_controls(void *data); #endif /* WP_HAVE_DH */ #ifdef WP_HAVE_ECC