diff --git a/src/wp_ecx_kmgmt.c b/src/wp_ecx_kmgmt.c index 99947824..43fe744e 100644 --- a/src/wp_ecx_kmgmt.c +++ b/src/wp_ecx_kmgmt.c @@ -1090,7 +1090,8 @@ static int wp_ecx_import(wp_Ecx* ecx, int selection, const OSSL_PARAM params[]) } } } - if (ok && ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)) { + /* Any keypair bit (OpenSSL hybrid KEMs import pub with DOMAIN|PRIVATE). */ + if (ok && ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0)) { if (!wp_params_get_octet_string_ptr(params, OSSL_PKEY_PARAM_PUB_KEY, &pubData, &len)) { ok = 0; diff --git a/src/wp_mlkem_kmgmt.c b/src/wp_mlkem_kmgmt.c index 83e10dab..be052393 100644 --- a/src/wp_mlkem_kmgmt.c +++ b/src/wp_mlkem_kmgmt.c @@ -545,7 +545,8 @@ static int wp_mlkem_import(wp_MlKem* mlkem, int selection, } } } - if (ok && ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) != 0)) { + /* Any keypair bit (OpenSSL hybrid KEMs import pub with DOMAIN|PRIVATE). */ + if (ok && ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0)) { if (!wp_params_get_octet_string_ptr(params, OSSL_PKEY_PARAM_PUB_KEY, &pubData, &pubLen)) { ok = 0; diff --git a/test/test_ecx.c b/test/test_ecx.c index faa83124..cb2a13a4 100644 --- a/test/test_ecx.c +++ b/test/test_ecx.c @@ -1186,6 +1186,53 @@ int test_ecx_import_short_pub(void *data) OPENSSL_free(buf); return err; } + +/* OpenSSL's hybrid KEM keymgmt imports X25519 public keys with + * DOMAIN_PARAMETERS|PRIVATE_KEY selection. */ +int test_ecx_import_pub_with_priv_selection(void *data) +{ + int err = 0; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + static const unsigned char pubKey[32] = { + 0x85, 0x20, 0xf0, 0x09, 0x89, 0x30, 0xa7, 0x54, + 0x74, 0x8b, 0x7d, 0xdc, 0xb4, 0x3e, 0xf7, 0x5a, + 0x0d, 0xbf, 0x3a, 0x0d, 0x26, 0x38, 0x1a, 0xf4, + 0xeb, 0xa4, 0xa9, 0x8e, 0xaa, 0x9b, 0x4e, 0x6a + }; + unsigned char out[sizeof(pubKey)]; + size_t outLen = sizeof(out); + OSSL_PARAM params[2]; + + (void)data; + PRINT_MSG("X25519 public import with private-key selection"); + + ctx = EVP_PKEY_CTX_new_from_name(wpLibCtx, "X25519", NULL); + err = (ctx == NULL) || (EVP_PKEY_fromdata_init(ctx) != 1); + if (err == 0) { + params[0] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, + (void*)pubKey, sizeof(pubKey)); + params[1] = OSSL_PARAM_construct_end(); + if (EVP_PKEY_fromdata(ctx, &pkey, + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | + OSSL_KEYMGMT_SELECT_PRIVATE_KEY, params) != 1) { + PRINT_ERR_MSG("X25519 public import failed"); + err = 1; + } + } + if (err == 0) { + if (EVP_PKEY_get_raw_public_key(pkey, out, &outLen) != 1 || + outLen != sizeof(pubKey) || + memcmp(out, pubKey, sizeof(pubKey)) != 0) { + PRINT_ERR_MSG("X25519 imported public key mismatch"); + err = 1; + } + } + + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return err; +} #endif /* WP_HAVE_X25519 */ int test_ecx_dup(void *data) diff --git a/test/test_mlkem.c b/test/test_mlkem.c index 127a56e9..1d6a8403 100644 --- a/test/test_mlkem.c +++ b/test/test_mlkem.c @@ -166,6 +166,7 @@ int test_mlkem_keygen(void* data) * import into a fresh EVP_PKEY via EVP_PKEY_fromdata, re-export, and verify * the bytes match exactly. Proves the OSSL_PARAM marshaling for raw keys is * lossless in both directions. + * Also import the public key with private-key selection. */ int test_mlkem_import_export_roundtrip(void* data) { @@ -173,13 +174,17 @@ int test_mlkem_import_export_roundtrip(void* data) size_t i; EVP_PKEY* k1 = NULL; EVP_PKEY* k2 = NULL; + EVP_PKEY* k3 = NULL; EVP_PKEY_CTX* ctx = NULL; OSSL_PARAM* params = NULL; + OSSL_PARAM pubParams[2]; unsigned char* pub1 = NULL; unsigned char* pub2 = NULL; + unsigned char* pub3 = NULL; unsigned char* priv1 = NULL; unsigned char* priv2 = NULL; - size_t pub1Len = 0, pub2Len = 0, priv1Len = 0, priv2Len = 0; + size_t pub1Len = 0, pub2Len = 0, pub3Len = 0; + size_t priv1Len = 0, priv2Len = 0; (void)data; @@ -249,15 +254,35 @@ int test_mlkem_import_export_roundtrip(void* data) (memcmp(priv1, priv2, priv1Len) != 0); if (err) PRINT_ERR_MSG("Private key roundtrip mismatch"); } + if (err == 0) { + pubParams[0] = OSSL_PARAM_construct_octet_string( + OSSL_PKEY_PARAM_PUB_KEY, pub1, pub1Len); + pubParams[1] = OSSL_PARAM_construct_end(); + err = EVP_PKEY_fromdata_init(ctx) != 1 || + EVP_PKEY_fromdata(ctx, &k3, + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | + OSSL_KEYMGMT_SELECT_PRIVATE_KEY, pubParams) != 1; + if (err) PRINT_ERR_MSG("Public key import failed"); + } + if (err == 0) { + err = mlkem_get_pub(k3, &pub3, &pub3Len); + } + if (err == 0) { + err = (pub1Len != pub3Len) || + (memcmp(pub1, pub3, pub1Len) != 0); + if (err) PRINT_ERR_MSG("Imported public key mismatch"); + } OPENSSL_free(pub1); pub1 = NULL; OPENSSL_free(pub2); pub2 = NULL; + OPENSSL_free(pub3); pub3 = NULL; OPENSSL_clear_free(priv1, priv1Len); priv1 = NULL; priv1Len = 0; OPENSSL_clear_free(priv2, priv2Len); priv2 = NULL; priv2Len = 0; OSSL_PARAM_free(params); params = NULL; EVP_PKEY_CTX_free(ctx); ctx = NULL; EVP_PKEY_free(k1); k1 = NULL; EVP_PKEY_free(k2); k2 = NULL; + EVP_PKEY_free(k3); k3 = NULL; } return err; } diff --git a/test/unit.c b/test/unit.c index be7f72a2..3ddb847a 100644 --- a/test/unit.c +++ b/test/unit.c @@ -593,6 +593,7 @@ TEST_CASE test_case[] = { TEST_DECL(test_ecx_x25519_get_params_stale_ret, NULL), TEST_DECL(test_ecx_import_zero_priv, NULL), TEST_DECL(test_ecx_import_short_pub, NULL), + TEST_DECL(test_ecx_import_pub_with_priv_selection, NULL), #endif TEST_DECL(test_ecx_dup, NULL), TEST_DECL(test_ecx_shared_key_first_use, NULL), diff --git a/test/unit.h b/test/unit.h index 35bc245b..6d0ad7e2 100644 --- a/test/unit.h +++ b/test/unit.h @@ -628,6 +628,7 @@ int test_ecx_x25519_raw_priv_roundtrip(void *data); int test_ecx_x25519_get_params_stale_ret(void *data); int test_ecx_import_zero_priv(void *data); int test_ecx_import_short_pub(void *data); +int test_ecx_import_pub_with_priv_selection(void *data); #endif /* WP_HAVE_X25519 */ int test_ecx_dup(void *data); int test_ecx_shared_key_first_use(void *data);