diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index c4fce4f43e..9d7feba739 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -30,6 +30,9 @@ #include #include +#ifdef WOLFSSL_HAVE_MLDSA + #include +#endif #include #include @@ -3127,6 +3130,75 @@ int test_wolfSSL_d2i_PUBKEY_mldsa_reuse(void) return EXPECT_RESULT(); } +/* Typed d2i entry points for ML-DSA: a PKCS#8 key of another algorithm + * and raw (non-DER) bytes must both be rejected; a matching PKCS#8 + * ML-DSA key must decode. */ +int test_wolfSSL_d2i_PrivateKey_mldsa(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_MLDSA) && \ + defined(WOLFSSL_MLDSA_PRIVATE_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_NO_ML_DSA_44) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) + WOLFSSL_EVP_PKEY* pkey = NULL; + const unsigned char* p; + unsigned char mldsaDer[4096]; + unsigned char rsaDer[2048]; + unsigned char rawBlob[2560]; /* ML-DSA-44 raw private key size */ + int mldsaSz = 0; + int rsaSz = 0; + XFILE f = XBADFILE; + + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa44-key.der", "rb")) + != XBADFILE); + ExpectIntGT(mldsaSz = (int)XFREAD(mldsaDer, 1, sizeof(mldsaDer), f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + ExpectTrue((f = XFOPEN("./certs/server-keyPkcs8.der", "rb")) != XBADFILE); + ExpectIntGT(rsaSz = (int)XFREAD(rsaDer, 1, sizeof(rsaDer), f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + } + + /* PKCS#8 ML-DSA private key decodes with the matching type. */ + p = mldsaDer; + ExpectNotNull(pkey = wolfSSL_d2i_PrivateKey(WC_EVP_PKEY_DILITHIUM, NULL, + &p, (long)mldsaSz)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), WC_EVP_PKEY_DILITHIUM); + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + + /* PKCS#8 RSA key requested as ML-DSA is rejected by the algId check. */ + p = rsaDer; + ExpectNull(wolfSSL_d2i_PrivateKey(WC_EVP_PKEY_DILITHIUM, NULL, &p, + (long)rsaSz)); + + /* Raw (non-DER) private key bytes are rejected: d2i is a DER API, the + * size-keyed raw import is for the auto-detect path only. Use genuine + * raw bytes so the rejection is due to the format, not the contents. */ + { + MlDsaKey mldsa; + word32 idx = 0; + word32 rawSz = (word32)sizeof(rawBlob); + + ExpectIntEQ(wc_MlDsaKey_Init(&mldsa, NULL, INVALID_DEVID), 0); + PRIVATE_KEY_UNLOCK(); + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(&mldsa, mldsaDer, + (word32)mldsaSz, &idx), 0); + ExpectIntEQ(wc_MlDsaKey_ExportPrivRaw(&mldsa, rawBlob, &rawSz), 0); + PRIVATE_KEY_LOCK(); + wc_MlDsaKey_Free(&mldsa); + + p = rawBlob; + ExpectNull(wolfSSL_d2i_PrivateKey(WC_EVP_PKEY_DILITHIUM, NULL, &p, + (long)rawSz)); + } +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_EVP_PKEY_x25519(void) { EXPECT_DECLS; diff --git a/tests/api/test_evp_pkey.h b/tests/api/test_evp_pkey.h index 13dccf59b2..704843c75b 100644 --- a/tests/api/test_evp_pkey.h +++ b/tests/api/test_evp_pkey.h @@ -72,6 +72,7 @@ int test_wolfSSL_EVP_PKEY_ed25519(void); int test_wolfSSL_CTX_use_PrivateKey_ed25519(void); int test_wolfSSL_EVP_PKEY_ed448(void); int test_wolfSSL_d2i_PUBKEY_mldsa_reuse(void); +int test_wolfSSL_d2i_PrivateKey_mldsa(void); int test_wolfSSL_EVP_PKEY_x25519(void); int test_wolfSSL_EVP_PKEY_x448(void); int test_wolfSSL_EVP_PKEY_encoded_public_key(void); @@ -127,6 +128,7 @@ int test_wolfSSL_d2i_PrivateKey_reuse_resets_state(void); TEST_DECL_GROUP("evp_pkey", test_wolfSSL_CTX_use_PrivateKey_ed25519), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_ed448), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_d2i_PUBKEY_mldsa_reuse), \ + TEST_DECL_GROUP("evp_pkey", test_wolfSSL_d2i_PrivateKey_mldsa), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_x25519), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_x448), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_encoded_public_key), \ diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index 6f461d90f3..e4b257c443 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -1032,13 +1032,15 @@ static int d2iTryFalconKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, * @param [in] priv 1 means private key, 0 means public key. * @param [in] prePopulated 1 means *out already holds the input bytes * so the d2i_make_pkey allocate/copy is skipped. + * @param [in] allowRaw 1 means size-keyed raw key bytes are accepted + * in addition to DER (auto-detect path only). * @return 1 on success. * @return 0 when input was recognized as this key type but * object creation/import failed. * @return WOLFSSL_FATAL_ERROR when input is not this key type. */ static int d2iTryMlDsaKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, - long memSz, int priv, int prePopulated) + long memSz, int priv, int prePopulated, int allowRaw) { static const byte levels[] = { WC_ML_DSA_44, WC_ML_DSA_65, WC_ML_DSA_87 }; word32 inSz = (word32)memSz; @@ -1063,8 +1065,9 @@ static int d2iTryMlDsaKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, return 0; } - /* Raw key bytes are size-keyed, try each level */ - numLevels = (int)(sizeof(levels) / sizeof(levels[0])); + /* Raw key bytes are size-keyed, try each level. Only the auto-detect + * path accepts raw bytes; the typed d2i entry points are DER APIs. */ + numLevels = allowRaw ? (int)(sizeof(levels) / sizeof(levels[0])) : 0; for (i = 0; i < numLevels && !isMlDsa; i++) { if (wc_MlDsaKey_SetParams(mldsa, levels[i]) != 0) { continue; @@ -1226,7 +1229,7 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey_try(WOLFSSL_EVP_PKEY** out, else #endif /* HAVE_FALCON */ #ifdef WOLFSSL_HAVE_MLDSA - if (d2iTryMlDsaKey(&pkey, *in, inSz, priv, 0) >= 0) { + if (d2iTryMlDsaKey(&pkey, *in, inSz, priv, 0, 1) >= 0) { found = 1; } else @@ -1463,6 +1466,17 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, #endif #ifdef HAVE_ED448 || (type == WC_EVP_PKEY_ED448 && algId != ED448k) + #endif + #ifdef WOLFSSL_HAVE_MLDSA + || (type == WC_EVP_PKEY_DILITHIUM && + algId != ML_DSA_44k && algId != ML_DSA_65k && + algId != ML_DSA_87k + #ifdef WOLFSSL_MLDSA_FIPS204_DRAFT + && algId != DILITHIUM_LEVEL2k + && algId != DILITHIUM_LEVEL3k + && algId != DILITHIUM_LEVEL5k + #endif + ) #endif ) { WOLFSSL_MSG("PKCS8 does not match EVP key type"); @@ -1592,7 +1606,7 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, #if defined(WOLFSSL_HAVE_MLDSA) case WC_EVP_PKEY_DILITHIUM: /* local already holds the input bytes: prePopulated=1. */ - if (d2iTryMlDsaKey(&local, p, local->pkey_sz, priv, 1) != 1) { + if (d2iTryMlDsaKey(&local, p, local->pkey_sz, priv, 1, 0) != 1) { wolfSSL_EVP_PKEY_free(local); return NULL; }