diff --git a/src/wp_ecc_kmgmt.c b/src/wp_ecc_kmgmt.c index 6600a685..ca616222 100644 --- a/src/wp_ecc_kmgmt.c +++ b/src/wp_ecc_kmgmt.c @@ -1149,6 +1149,31 @@ static int wp_ecc_import_keypair(wp_Ecc* ecc, const OSSL_PARAM params[], ) { ecc->key.type = ECC_PRIVATEKEY; ecc->hasPriv = 1; + + /* Auto-derive public key if not already present and curve is + * set with OpenSSL version newer than 3.6.0 */ +#if OPENSSL_VERSION_NUMBER > 0x30600000L + if (!ecc->hasPub && ecc->curveId != 0) { + int rc; + + rc = wc_ecc_set_curve(&ecc->key, 0, ecc->curveId); + if (rc == 0) { +#ifdef ECC_TIMING_RESISTANT + rc = wc_ecc_make_pub_ex(&ecc->key, NULL, &ecc->rng); +#else + rc = wc_ecc_make_pub_ex(&ecc->key, NULL, NULL); +#endif + /* Indicate public key is available if derivation succeeds */ + if (rc == 0) { + ecc->key.type = ECC_PRIVATEKEY; + ecc->hasPub = 1; + } + /* If derivation fails, continue. + * The key is still valid for private key operations. + * We will fail later if public key is accessed. */ + } + } +#endif /* OPENSSL_VERSION_NUMBER > 0x30600000L 3.6.0+ */ } WOLFPROV_LEAVE(WP_LOG_COMP_ECC, __FILE__ ":" WOLFPROV_STRINGIZE(__LINE__), ok); diff --git a/test/test_ecc.c b/test/test_ecc.c index 2cd21cf4..b188cef3 100644 --- a/test/test_ecc.c +++ b/test/test_ecc.c @@ -1877,7 +1877,51 @@ static int test_ec_import_priv(void) err = EVP_PKEY_fromdata(ctx2, &pkey2, EVP_PKEY_KEYPAIR, params) != 1; } - /* For imported private only keys, get bn params should fail */ + /* For imported private only keys, public key params behavior depends on OpenSSL version */ +#if OPENSSL_VERSION_NUMBER > 0x30600000L + /* OpenSSL 3.6.0+ auto-derives public keys from private keys */ + if (err == 0) { + err = EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_EC_PUB_X, &x1) != 1; + } + if (err == 0) { + err = EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_EC_PUB_X, &x2) != 1; + } + if (err == 0) { + err = EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_EC_PUB_Y, &y1) != 1; + } + if (err == 0) { + err = EVP_PKEY_get_bn_param(pkey2, OSSL_PKEY_PARAM_EC_PUB_Y, &y2) != 1; + } + + /* Verify public key is available */ + if (err == 0) { + if (EVP_PKEY_get_octet_string_param(pkey1, + OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, (size_t *)&len) != 1) { + err = 1; + } + } + if (err == 0) { + if (EVP_PKEY_get_octet_string_param(pkey2, + OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, (size_t *)&len) != 1) { + err = 1; + } + } + + /* Verify encoded public key is available */ + if (err == 0) { + if (EVP_PKEY_get_octet_string_param(pkey1, + OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0, (size_t *)&len) != 1) { + err = 1; + } + } + if (err == 0) { + if (EVP_PKEY_get_octet_string_param(pkey2, + OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, NULL, 0, (size_t *)&len) != 1) { + err = 1; + } + } +#else + /* OpenSSL < 3.6.0: private-only keys should not have public components */ if (err == 0) { err = EVP_PKEY_get_bn_param(pkey1, OSSL_PKEY_PARAM_EC_PUB_X, &x1) == 1; } @@ -1918,7 +1962,8 @@ static int test_ec_import_priv(void) err = 1; } } -#endif +#endif /* OPENSSL_VERSION_NUMBER >= 0x30006000L 3.0.6+ */ +#endif /* OPENSSL_VERSION_NUMBER > 0x30600000L 3.6.0+ */ EVP_PKEY_free(pkey1); EVP_PKEY_free(pkey2); @@ -2060,4 +2105,105 @@ int test_ec_null_init(void* data) return err; } +#if OPENSSL_VERSION_NUMBER > 0x30600000L +static int test_ec_auto_derive_pub(void) +{ + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY* pkey = NULL; + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *bld = NULL; + BIGNUM* priv = NULL; + BIGNUM* pub_x = NULL; + BIGNUM* pub_y = NULL; + unsigned char pub_key[65] = {0}; + size_t pub_key_len = sizeof(pub_key); + + /* Build params with private key only (no public key) */ + err = (bld = OSSL_PARAM_BLD_new()) == NULL; + if (err == 0) { + err = OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, + ecc_p256_group_str, 0) != 1; + } + if (err == 0) { + err = (priv = BN_bin2bn(ecc_p256_priv, sizeof(ecc_p256_priv), NULL)) == NULL; + } + if (err == 0) { + err = OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, priv) != 1; + } + if (err == 0) { + err = (params = OSSL_PARAM_BLD_to_param(bld)) == NULL; + } + /* Import key using wolfProvider */ + if (err == 0) { + err = (ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "EC", NULL)) == NULL; + } + if (err == 0) { + err = EVP_PKEY_fromdata_init(ctx) != 1; + } + if (err == 0) { + err = EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) != 1; + } + /* Verify public X coordinate is available (auto-derived) */ + if (err == 0) { + err = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &pub_x) != 1; + } + /* Verify public Y coordinate is available (auto-derived) */ + if (err == 0) { + err = EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &pub_y) != 1; + } + /* Verify public key octet string is available */ + if (err == 0) { + err = EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, + pub_key, pub_key_len, &pub_key_len) != 1; + } + if (err == 0) { + if (pub_key_len == 0 || pub_key_len > sizeof(pub_key)) { + err = 1; + } + } + /* Verify encoded public key is available */ + if (err == 0) { + pub_key_len = sizeof(pub_key); + err = EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_ENCODED_PUBLIC_KEY, + pub_key, pub_key_len, &pub_key_len) != 1; + } + if (err == 0) { + if (pub_key_len == 0 || pub_key_len > sizeof(pub_key)) { + err = 1; + } + } + /* Verify the derived public key is valid (non-zero coordinates) */ + if (err == 0) { + if (BN_is_zero(pub_x) || BN_is_zero(pub_y)) { + err = 1; + } + } + + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + OSSL_PARAM_BLD_free(bld); + BN_clear_free(priv); + BN_free(pub_x); + BN_free(pub_y); + + return err; +} +#endif /* OPENSSL_VERSION_NUMBER > 0x30600000L */ + +int test_ec_auto_derive_pubkey(void* data) +{ + int err = 0; + (void)data; + +#if OPENSSL_VERSION_NUMBER > 0x30600000L + err = test_ec_auto_derive_pub(); +#else + err = 0; +#endif + + return err; +} + #endif /* WP_HAVE_ECC */ diff --git a/test/unit.c b/test/unit.c index a55a5bcf..399b3eb2 100644 --- a/test/unit.c +++ b/test/unit.c @@ -355,6 +355,7 @@ TEST_CASE test_case[] = { #endif TEST_DECL(test_ec_decode, NULL), TEST_DECL(test_ec_import, NULL), + TEST_DECL(test_ec_auto_derive_pubkey, NULL), TEST_DECL(test_ec_null_init, NULL), #endif #ifdef WP_HAVE_EC_P384 diff --git a/test/unit.h b/test/unit.h index 5579bf70..b0fdadfd 100644 --- a/test/unit.h +++ b/test/unit.h @@ -396,6 +396,7 @@ int test_ec_load_cert(void* data); int test_ec_decode(void* data); int test_ec_import(void* data); +int test_ec_auto_derive_pubkey(void* data); int test_ec_null_init(void* data); #endif /* WP_HAVE_ECC */