PKCS#8 algId check and DER-only typed d2i for ML-DSA

This commit is contained in:
Takashi Kojo
2026-08-07 07:38:29 +09:00
parent 69749e8f99
commit 997cfdaeb2
3 changed files with 93 additions and 5 deletions
+72
View File
@@ -30,6 +30,9 @@
#include <wolfssl/openssl/evp.h>
#include <wolfssl/openssl/kdf.h>
#ifdef WOLFSSL_HAVE_MLDSA
#include <wolfssl/wolfcrypt/wc_mldsa.h>
#endif
#include <tests/api/api.h>
#include <tests/api/test_evp_pkey.h>
@@ -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;
+2
View File
@@ -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), \
+19 -5
View File
@@ -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;
}