diff --git a/src/wp_dh_kmgmt.c b/src/wp_dh_kmgmt.c index 442f1c40..c1a7033b 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; @@ -1585,7 +1583,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)) { @@ -1652,6 +1649,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"); @@ -1673,14 +1672,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, @@ -1707,11 +1724,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); } @@ -1723,7 +1749,8 @@ static int wp_dh_gen_parameters(wp_DhGenCtx *ctx, wp_Dh* dh) /** * Copy the group parameters into the DH key object. * - * Use the template key if available. Otherwise use the group name. + * Use the template key if available. Otherwise use the group name. Fails + * when the caller asked for a generator the group does not have. * * @param [in] ctx DH generation context object. * @param [in, out] dh DH key object. @@ -1733,6 +1760,7 @@ static int wp_dh_gen_parameters(wp_DhGenCtx *ctx, wp_Dh* dh) static int wp_dh_gen_copy_parameters(wp_DhGenCtx *ctx, wp_Dh* dh) { int ok = 1; + mp_int gen; WOLFPROV_ENTER(WP_LOG_COMP_DH, "wp_dh_gen_copy_parameters"); @@ -1769,6 +1797,23 @@ 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)) { + if (mp_init(&gen) != MP_OKAY) { + ok = 0; + } + else { + if (mp_set_int(&gen, (unsigned long)ctx->generator) != MP_OKAY) { + ok = 0; + } + if (ok && (mp_cmp(&dh->key.g, &gen) != MP_EQ)) { + ERR_raise(ERR_LIB_PROV, PROV_R_NOT_SUPPORTED); + ok = 0; + } + mp_clear(&gen); + } + } + WOLFPROV_LEAVE(WP_LOG_COMP_DH, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); return ok; } @@ -1990,7 +2035,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. A value that wolfSSL + * cannot apply is rejected when set. */ 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 da5baf82..84949787 100644 --- a/test/test_dh.c +++ b/test/test_dh.c @@ -1735,4 +1735,236 @@ int test_dh_pgen_min_bits(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; + + 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; + } + } + + 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 c5543dd0..a200e10b 100644 --- a/test/unit.c +++ b/test/unit.c @@ -364,6 +364,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_dh_param_check_explicit, NULL), TEST_DECL(test_dh_import_group_no_nul, NULL), TEST_DECL(test_dh_pgen_min_bits, 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 f7910ba1..894d3a6e 100644 --- a/test/unit.h +++ b/test/unit.h @@ -419,6 +419,7 @@ int test_dh_fromdata_oversize(void *data); int test_dh_param_check_explicit(void *data); int test_dh_import_group_no_nul(void *data); int test_dh_pgen_min_bits(void *data); +int test_dh_pgen_controls(void *data); #endif /* WP_HAVE_DH */ #ifdef WP_HAVE_ECC