diff --git a/src/pk.c b/src/pk.c index b6a7d32625..7526b4924d 100644 --- a/src/pk.c +++ b/src/pk.c @@ -6589,6 +6589,18 @@ WOLFSSL_EVP_PKEY* wolfSSL_PEM_read_bio_PrivateKey(WOLFSSL_BIO* bio, case ED448k: type = WC_EVP_PKEY_ED448; break; + #endif + #ifdef WOLFSSL_HAVE_MLDSA + case ML_DSA_44k: + case ML_DSA_65k: + case ML_DSA_87k: + #ifdef WOLFSSL_MLDSA_FIPS204_DRAFT + case DILITHIUM_LEVEL2k: + case DILITHIUM_LEVEL3k: + case DILITHIUM_LEVEL5k: + #endif + type = WC_EVP_PKEY_DILITHIUM; + break; #endif default: type = WOLFSSL_FATAL_ERROR; @@ -6746,6 +6758,18 @@ WOLFSSL_EVP_PKEY* wolfSSL_PEM_read_PrivateKey(XFILE fp, WOLFSSL_EVP_PKEY **key, case ED448k: type = WC_EVP_PKEY_ED448; break; + #endif + #ifdef WOLFSSL_HAVE_MLDSA + case ML_DSA_44k: + case ML_DSA_65k: + case ML_DSA_87k: + #ifdef WOLFSSL_MLDSA_FIPS204_DRAFT + case DILITHIUM_LEVEL2k: + case DILITHIUM_LEVEL3k: + case DILITHIUM_LEVEL5k: + #endif + type = WC_EVP_PKEY_DILITHIUM; + break; #endif default: type = WOLFSSL_FATAL_ERROR; diff --git a/src/x509.c b/src/x509.c index 2c702989ae..5fe760e5ce 100644 --- a/src/x509.c +++ b/src/x509.c @@ -12225,6 +12225,14 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) } +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(WOLFSSL_MLDSA_NO_SIGN) + /* ML-DSA X.509 signing needs private key decode and sign support; keep + * every gate that decides "can we sign with ML-DSA" identical so that + * verify-only builds reject the key type up front. */ + #define WOLFSSL_MLDSA_X509_SIGN +#endif + /* returns the sig type to use on success i.e CTC_SHAwRSA and WOLFSSL_FALURE * on fail case */ static int wolfSSL_sigTypeFromPKEY(WOLFSSL_EVP_MD* md, @@ -12234,6 +12242,43 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) int hashType; int sigType = WOLFSSL_FAILURE; + #ifdef WOLFSSL_MLDSA_X509_SIGN + if (pkey->type == WC_EVP_PKEY_DILITHIUM) { + /* ML-DSA does not use a separate hash. A NULL md matches + * OpenSSL's X509_sign(x, pkey, NULL); unlike OpenSSL, a + * non-NULL md is deliberately ignored rather than rejected to + * keep hash-passing callers working. */ + if (md != NULL) { + WOLFSSL_MSG("Ignoring md for ML-DSA signing"); + } + switch (WOLFSSL_ATOMIC_LOAD(pkey->mldsaOID)) { + case ML_DSA_44k: + sigType = CTC_ML_DSA_44; + break; + case ML_DSA_65k: + sigType = CTC_ML_DSA_65; + break; + case ML_DSA_87k: + sigType = CTC_ML_DSA_87; + break; + #ifdef WOLFSSL_MLDSA_FIPS204_DRAFT + case DILITHIUM_LEVEL2k: + sigType = CTC_DILITHIUM_LEVEL2; + break; + case DILITHIUM_LEVEL3k: + sigType = CTC_DILITHIUM_LEVEL3; + break; + case DILITHIUM_LEVEL5k: + sigType = CTC_DILITHIUM_LEVEL5; + break; + #endif + default: + return WOLFSSL_FAILURE; + } + return sigType; + } + #endif /* WOLFSSL_MLDSA_X509_SIGN */ + /* Convert key type and hash algorithm to a signature algorithm */ if (wolfSSL_EVP_get_hashinfo(md, &hashType, NULL) == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) @@ -12792,6 +12837,9 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) int type = -1; int sigType; WC_RNG rng; + #ifdef WOLFSSL_MLDSA_X509_SIGN + wc_MlDsaKey* mldsa = NULL; + #endif (void)req; WOLFSSL_ENTER("wolfSSL_X509_resign_cert"); @@ -12816,14 +12864,100 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) key = pkey->ecc->internal; } #endif + #ifdef WOLFSSL_MLDSA_X509_SIGN + if (pkey->type == WC_EVP_PKEY_DILITHIUM) { + /* Decode the ML-DSA private key held as DER in pkey.ptr. */ + word32 idx = 0; + int oidSum = 0; + int decRet; + + mldsa = (wc_MlDsaKey*)XMALLOC(sizeof(wc_MlDsaKey), x509->heap, + DYNAMIC_TYPE_MLDSA); + if (mldsa == NULL) { + WOLFSSL_MSG("Failed to allocate memory for wc_MlDsaKey"); + return WOLFSSL_FATAL_ERROR; + } + if (wc_MlDsaKey_Init(mldsa, x509->heap, INVALID_DEVID) != 0) { + WOLFSSL_MSG("wc_MlDsaKey_Init error"); + XFREE(mldsa, x509->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FATAL_ERROR; + } + PRIVATE_KEY_UNLOCK(); + decRet = wc_MlDsaKey_PrivateKeyDecode(mldsa, + (const byte*)pkey->pkey.ptr, (word32)pkey->pkey_sz, + &idx); + PRIVATE_KEY_LOCK(); + /* Map the parameter set to its OID with mldsa_get_oid_sum(): + * unlike wc_MlDsaKey_GetParams() it distinguishes FIPS204-draft + * levels. */ + if (decRet != 0 || + mldsa_get_oid_sum(mldsa, &oidSum) != 0) { + WOLFSSL_MSG("Error decoding ML-DSA private key"); + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, x509->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FATAL_ERROR; + } + /* sigType above came from the cached pkey->mldsaOID; require + * the decoded key to agree so a stale cache cannot emit a + * certificate whose signatureAlgorithm disagrees with the + * actual signature. */ + if (oidSum != WOLFSSL_ATOMIC_LOAD(pkey->mldsaOID)) { + WOLFSSL_MSG("ML-DSA key does not match cached pkey OID"); + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, x509->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FATAL_ERROR; + } + switch (oidSum) { + case ML_DSA_44k: + type = ML_DSA_44_TYPE; + break; + case ML_DSA_65k: + type = ML_DSA_65_TYPE; + break; + case ML_DSA_87k: + type = ML_DSA_87_TYPE; + break; + #ifdef WOLFSSL_MLDSA_FIPS204_DRAFT + case DILITHIUM_LEVEL2k: + type = DILITHIUM_LEVEL2_TYPE; + break; + case DILITHIUM_LEVEL3k: + type = DILITHIUM_LEVEL3_TYPE; + break; + case DILITHIUM_LEVEL5k: + type = DILITHIUM_LEVEL5_TYPE; + break; + #endif + default: + WOLFSSL_MSG("Unsupported ML-DSA parameter set"); + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, x509->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FATAL_ERROR; + } + key = mldsa; + } + #endif /* WOLFSSL_MLDSA_X509_SIGN */ /* Sign the certificate (request) body. */ ret = wc_InitRng(&rng); - if (ret != 0) + if (ret != 0) { + #ifdef WOLFSSL_MLDSA_X509_SIGN + if (mldsa != NULL) { + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, x509->heap, DYNAMIC_TYPE_MLDSA); + } + #endif return ret; + } ret = wc_SignCert_ex(certBodySz, sigType, der, (word32)derSz, type, key, &rng); wc_FreeRng(&rng); + #ifdef WOLFSSL_MLDSA_X509_SIGN + if (mldsa != NULL) { + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, x509->heap, DYNAMIC_TYPE_MLDSA); + } + #endif if (ret < 0) { WOLFSSL_LEAVE("wolfSSL_X509_resign_cert", ret); return ret; @@ -12892,24 +13026,76 @@ static int CertFromX509(Cert* cert, WOLFSSL_X509* x509) /* able to override max size until dynamic buffer created */ #define WC_MAX_X509_GEN 4096 #endif +#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WC_MAX_X509_GEN_MLDSA) + /* Base size plus the largest compiled-in ML-DSA signature and its + * ASN.1 overhead; the subject key is added in x509_gen_buf_sz(). */ + #define WC_MAX_X509_GEN_MLDSA \ + (WC_MAX_X509_GEN + MLDSA_MAX_SIG_SIZE + MAX_ALGO_SZ + \ + MAX_SEQ_SZ * 2) +#endif + +/* DER buffer size for certificate/CSR signing: chosen from the signing + * key type, plus the subject public key held in the x509 so that a + * large (e.g. ML-DSA) SPKI fits under a classic signing key too. */ +static int x509_gen_buf_sz(const WOLFSSL_X509* x509, + const WOLFSSL_EVP_PKEY* pkey) +{ + int sz = WC_MAX_X509_GEN; + +#ifdef WOLFSSL_HAVE_MLDSA + if (pkey != NULL && pkey->type == WC_EVP_PKEY_DILITHIUM) { + sz = WC_MAX_X509_GEN_MLDSA; + } +#else + (void)pkey; +#endif + return sz + (int)x509->pubKey.length; +} /* returns the size of signature on success */ int wolfSSL_X509_sign(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey, const WOLFSSL_EVP_MD* md) { int ret; - /* @TODO dynamic set based on expected cert size */ - byte *der = (byte *)XMALLOC(WC_MAX_X509_GEN, NULL, DYNAMIC_TYPE_TMP_BUFFER); - int derSz = WC_MAX_X509_GEN; + byte *der = NULL; + int bufSz = 0; + int derSz = 0; + int sigType; WOLFSSL_ENTER("wolfSSL_X509_sign"); - if (x509 == NULL || pkey == NULL || md == NULL) { + if (x509 == NULL || pkey == NULL) { + ret = WOLFSSL_FAILURE; + goto out; + } + /* md may be NULL for hash-free algorithms (ML-DSA), as in OpenSSL's + * X509_sign(x, pkey, NULL). */ + if (md == NULL +#ifdef WOLFSSL_MLDSA_X509_SIGN + && pkey->type != WC_EVP_PKEY_DILITHIUM +#endif + ) { ret = WOLFSSL_FAILURE; goto out; } - x509->sigOID = wolfSSL_sigTypeFromPKEY((WOLFSSL_EVP_MD*)md, pkey); + bufSz = x509_gen_buf_sz(x509, pkey); + derSz = bufSz; + der = (byte *)XMALLOC((size_t)bufSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) { + ret = WOLFSSL_FAILURE; + goto out; + } + + /* Only update sigOID on success to keep the object unmodified on a + * rejected key/md combination. */ + sigType = wolfSSL_sigTypeFromPKEY((WOLFSSL_EVP_MD*)md, pkey); + if (sigType == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) { + WOLFSSL_MSG("Unsupported key/md combination for signing"); + ret = WOLFSSL_FAILURE; + goto out; + } + x509->sigOID = sigType; if ((ret = wolfssl_x509_make_der(x509, 0, der, &derSz, 0)) != WOLFSSL_SUCCESS) { WOLFSSL_MSG("Unable to make DER for X509"); @@ -12919,7 +13105,7 @@ int wolfSSL_X509_sign(WOLFSSL_X509* x509, WOLFSSL_EVP_PKEY* pkey, goto out; } - ret = wolfSSL_X509_resign_cert(x509, 0, der, WC_MAX_X509_GEN, derSz, + ret = wolfSSL_X509_resign_cert(x509, 0, der, bufSz, derSz, (WOLFSSL_EVP_MD*)md, pkey); if (ret <= 0) { WOLFSSL_LEAVE("wolfSSL_X509_sign", ret); @@ -16421,6 +16607,90 @@ int wolfSSL_X509_set_pubkey(WOLFSSL_X509 *cert, WOLFSSL_EVP_PKEY *pkey) } break; #endif +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PUBLIC_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && defined(WC_ENABLE_ASYM_KEY_EXPORT) + case WC_EVP_PKEY_DILITHIUM: + { + /* Decode key DER (private or public) and export public part. */ + wc_MlDsaKey* mldsa; + word32 idx = 0; + int oidSum = 0; + int decodeOk = 0; + + mldsa = (wc_MlDsaKey*)XMALLOC(sizeof(wc_MlDsaKey), cert->heap, + DYNAMIC_TYPE_MLDSA); + if (mldsa == NULL) { + WOLFSSL_MSG("Failed to allocate memory for wc_MlDsaKey"); + return WOLFSSL_FAILURE; + } + if (wc_MlDsaKey_Init(mldsa, cert->heap, INVALID_DEVID) != 0) { + WOLFSSL_MSG("wc_MlDsaKey_Init error"); + XFREE(mldsa, cert->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FAILURE; + } + #ifdef WOLFSSL_MLDSA_PRIVATE_KEY + PRIVATE_KEY_UNLOCK(); + if (wc_MlDsaKey_PrivateKeyDecode(mldsa, + (const byte*)pkey->pkey.ptr, (word32)pkey->pkey_sz, + &idx) == 0) { + decodeOk = 1; + } + PRIVATE_KEY_LOCK(); + #endif + if (!decodeOk) { + #ifdef WOLFSSL_MLDSA_PRIVATE_KEY + /* A failed PrivateKeyDecode may leave the level pinned; + * reset the key so PublicKeyDecode auto-detects it from + * the SPKI OID. */ + wc_MlDsaKey_Free(mldsa); + if (wc_MlDsaKey_Init(mldsa, cert->heap, INVALID_DEVID) != 0) { + WOLFSSL_MSG("wc_MlDsaKey_Init error"); + XFREE(mldsa, cert->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FAILURE; + } + #endif + idx = 0; + if (wc_MlDsaKey_PublicKeyDecode(mldsa, + (const byte*)pkey->pkey.ptr, (word32)pkey->pkey_sz, + &idx) != 0) { + WOLFSSL_MSG("Error decoding ML-DSA public key"); + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, cert->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FAILURE; + } + } + /* Map the parameter set to its OID with mldsa_get_oid_sum(): + * unlike wc_MlDsaKey_GetParams() it distinguishes FIPS204-draft + * levels, keeping pubKeyOID consistent with the SPKI encoded + * by wc_MlDsaKey_PublicKeyToDer(). */ + if (mldsa_get_oid_sum(mldsa, &oidSum) != 0) { + WOLFSSL_MSG("Error getting ML-DSA OID"); + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, cert->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FAILURE; + } + + derSz = MLDSA_MAX_PUB_KEY_DER_SIZE; + p = (byte*)XMALLOC(derSz, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY); + if (p == NULL) { + WOLFSSL_MSG("malloc error"); + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, cert->heap, DYNAMIC_TYPE_MLDSA); + return WOLFSSL_FAILURE; + } + derSz = wc_MlDsaKey_PublicKeyToDer(mldsa, p, (word32)derSz, 1); + wc_MlDsaKey_Free(mldsa); + XFREE(mldsa, cert->heap, DYNAMIC_TYPE_MLDSA); + if (derSz <= 0) { + WOLFSSL_MSG("Error making ML-DSA public key DER"); + XFREE(p, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY); + return WOLFSSL_FAILURE; + } + cert->pubKeyOID = oidSum; + } + break; +#endif /* WOLFSSL_HAVE_MLDSA && WOLFSSL_MLDSA_PUBLIC_KEY && + * !WOLFSSL_MLDSA_NO_ASN1 && WC_ENABLE_ASYM_KEY_EXPORT */ default: return WOLFSSL_FAILURE; } @@ -16770,33 +17040,53 @@ int wolfSSL_X509_REQ_sign(WOLFSSL_X509 *req, WOLFSSL_EVP_PKEY *pkey, const WOLFSSL_EVP_MD *md) { int ret; - WC_DECLARE_VAR(der, byte, 2048, 0); - int derSz = 2048; + byte* der = NULL; + int bufSz; + int derSz; + int sigType; - if (req == NULL || pkey == NULL || md == NULL) { + /* md may be NULL for hash-free algorithms (ML-DSA), as in OpenSSL's + * X509_REQ_sign(req, pkey, NULL). */ + if (req == NULL || pkey == NULL || (md == NULL +#ifdef WOLFSSL_MLDSA_X509_SIGN + && pkey->type != WC_EVP_PKEY_DILITHIUM +#endif + )) { WOLFSSL_LEAVE("wolfSSL_X509_REQ_sign", BAD_FUNC_ARG); return WOLFSSL_FAILURE; } - WC_ALLOC_VAR_EX(der, byte, derSz, NULL, DYNAMIC_TYPE_TMP_BUFFER, - return WOLFSSL_FAILURE); + bufSz = x509_gen_buf_sz(req, pkey); + derSz = bufSz; + der = (byte*)XMALLOC((size_t)bufSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) { + return WOLFSSL_FAILURE; + } - /* Create a Cert that has the certificate request fields. */ - req->sigOID = wolfSSL_sigTypeFromPKEY((WOLFSSL_EVP_MD*)md, pkey); + /* Create a Cert that has the certificate request fields. Only update + * sigOID on success to keep the object unmodified on a rejected + * key/md combination. */ + sigType = wolfSSL_sigTypeFromPKEY((WOLFSSL_EVP_MD*)md, pkey); + if (sigType == WC_NO_ERR_TRACE(WOLFSSL_FAILURE)) { + WOLFSSL_MSG("Unsupported key/md combination for signing"); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFSSL_FAILURE; + } + req->sigOID = sigType; ret = wolfssl_x509_make_der(req, 1, der, &derSz, 0); if (ret != WOLFSSL_SUCCESS) { - WC_FREE_VAR_EX(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); WOLFSSL_MSG("Unable to make DER for X509"); WOLFSSL_LEAVE("wolfSSL_X509_REQ_sign", ret); return WOLFSSL_FAILURE; } - if (wolfSSL_X509_resign_cert(req, 1, der, 2048, derSz, + if (wolfSSL_X509_resign_cert(req, 1, der, bufSz, derSz, (WOLFSSL_EVP_MD*)md, pkey) <= 0) { - WC_FREE_VAR_EX(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); return WOLFSSL_FAILURE; } - WC_FREE_VAR_EX(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); return WOLFSSL_SUCCESS; } diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index f0d2b838d4..06785cb567 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -30,6 +30,12 @@ #include #include +#ifdef WOLFSSL_HAVE_MLDSA + #include +#endif +#ifdef HAVE_CURVE25519 + #include +#endif #include #include @@ -2956,6 +2962,32 @@ int test_wolfSSL_EVP_PKEY_ed25519(void) wolfSSL_EVP_PKEY_free(pkey); pkey = NULL; +#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) + { + /* Reuse: after decoding an RSA key, reusing the same EVP_PKEY for + * an Ed25519 SPKI must re-populate type and DER and release the + * RSA object. */ + p = client_keypub_der_2048; + ExpectNotNull(wolfSSL_d2i_PUBKEY(&pkey, &p, + (long)sizeof_client_keypub_der_2048)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), EVP_PKEY_RSA); + p = spkiPub; + ExpectNotNull(wolfSSL_d2i_PUBKEY(&pkey, &p, (long)sizeof(spkiPub))); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), EVP_PKEY_ED25519); + if (pkey != NULL) { + /* The stored DER length is how far the Ed25519 decoder advanced + * its index, which differs between the ASN template and + * original implementations - only require that the previous RSA + * DER (larger than the whole SPKI) was replaced. */ + ExpectIntGT(pkey->pkey_sz, 0); + ExpectIntLE(pkey->pkey_sz, (int)sizeof(spkiPub)); + ExpectNull(wolfSSL_EVP_PKEY_get0_RSA(pkey)); + } + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + } +#endif + { static const unsigned char junk[16] = { 0 }; const unsigned char* jp = junk; @@ -3050,6 +3082,231 @@ int test_wolfSSL_EVP_PKEY_ed448(void) return EXPECT_RESULT(); } +/* Decoding into a caller-supplied EVP_PKEY must re-populate the object + * (documented OpenSSL reuse semantics). An ML-DSA decode into a key that + * previously held another key must not return success while leaving the + * old key data in place. */ +int test_wolfSSL_d2i_PUBKEY_mldsa_reuse(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_MLDSA) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_NO_ML_DSA_44) && !defined(WOLFSSL_NO_ML_DSA_65) && \ + !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) && \ + !defined(NO_FILESYSTEM) + WOLFSSL_EVP_PKEY* pkey = NULL; + const unsigned char* p; + unsigned char* der44 = NULL; + unsigned char* der65 = NULL; + int der44Sz = 0; + int der65Sz = 0; + XFILE f = XBADFILE; + + ExpectNotNull(der44 = (unsigned char*)XMALLOC(2048, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(der65 = (unsigned char*)XMALLOC(2600, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa44_pub-spki.der", "rb")) + != XBADFILE); + ExpectIntGT(der44Sz = (int)XFREAD(der44, 1, 2048, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa65_pub-spki.der", "rb")) + != XBADFILE); + ExpectIntGT(der65Sz = (int)XFREAD(der65, 1, 2600, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + +#ifdef HAVE_CURVE25519 + { + /* Start from a raw X25519 key: the RSA decode below repurposes the + * EVP_PKEY and must release the curve25519 object too. */ + static const unsigned char x25519Base[CURVE25519_PUB_KEY_SIZE] = + { 9 }; + ExpectNotNull(pkey = wolfSSL_EVP_PKEY_new_raw_public_key( + WC_EVP_PKEY_X25519, NULL, x25519Base, sizeof(x25519Base))); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), WC_EVP_PKEY_X25519); + } +#endif + + /* Decode an RSA SPKI first so pkey holds a non-ML-DSA key. */ + p = client_keypub_der_2048; + ExpectNotNull(wolfSSL_d2i_PUBKEY(&pkey, &p, + (long)sizeof_client_keypub_der_2048)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), EVP_PKEY_RSA); +#ifdef HAVE_CURVE25519 + if (pkey != NULL) { + /* The X25519 object must have been released and detached when the + * key was repurposed to RSA. */ + ExpectNull(pkey->curve25519); + } +#endif + + /* Reuse the same EVP_PKEY for an ML-DSA SPKI and check the object was + * re-populated with the new key. */ + p = der44; + ExpectNotNull(wolfSSL_d2i_PUBKEY(&pkey, &p, (long)der44Sz)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), WC_EVP_PKEY_DILITHIUM); + if (pkey != NULL) { + ExpectIntEQ(pkey->pkey_sz, der44Sz); + ExpectNotNull(pkey->pkey.ptr); + if (pkey->pkey.ptr != NULL) { + ExpectIntEQ(XMEMCMP(pkey->pkey.ptr, der44, (size_t)der44Sz), 0); + } + /* The RSA object from the first decode must have been released + * and detached when the key was repurposed. */ + ExpectNull(wolfSSL_EVP_PKEY_get0_RSA(pkey)); + } + + /* Reuse again with a different ML-DSA level: same type, new key data. */ + p = der65; + ExpectNotNull(wolfSSL_d2i_PUBKEY(&pkey, &p, (long)der65Sz)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), WC_EVP_PKEY_DILITHIUM); + if (pkey != NULL) { + ExpectIntEQ(pkey->pkey_sz, der65Sz); + ExpectNotNull(pkey->pkey.ptr); + if (pkey->pkey.ptr != NULL) { + ExpectIntEQ(XMEMCMP(pkey->pkey.ptr, der65, (size_t)der65Sz), 0); + } + } + + wolfSSL_EVP_PKEY_free(pkey); + XFREE(der65, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(der44, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + 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_PUBLIC_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 = NULL; + unsigned char* rsaDer = NULL; + unsigned char* rawBlob = NULL; + int mldsaSz = 0; + int rsaSz = 0; + XFILE f = XBADFILE; + + ExpectNotNull(mldsaDer = (unsigned char*)XMALLOC(4096, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(rsaDer = (unsigned char*)XMALLOC(2048, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + /* 2560 = ML-DSA-44 raw private key size */ + ExpectNotNull(rawBlob = (unsigned char*)XMALLOC(2560, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa44-key.der", "rb")) + != XBADFILE); + ExpectIntGT(mldsaSz = (int)XFREAD(mldsaDer, 1, 4096, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + ExpectTrue((f = XFOPEN("./certs/server-keyPkcs8.der", "rb")) != XBADFILE); + ExpectIntGT(rsaSz = (int)XFREAD(rsaDer, 1, 2048, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + + /* 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); + + /* i2d must emit the full PKCS#8 wrapper (the parameter set only exists + * in the AlgorithmIdentifier) so the output round-trips through d2i. */ + { + WOLFSSL_EVP_PKEY* pkey2 = NULL; + unsigned char* out = NULL; + + ExpectIntEQ(wolfSSL_i2d_PrivateKey(pkey, &out), mldsaSz); + ExpectNotNull(out); + if (out != NULL) { + ExpectIntEQ(XMEMCMP(out, mldsaDer, (size_t)mldsaSz), 0); + p = out; + ExpectNotNull(pkey2 = wolfSSL_d2i_PrivateKey( + WC_EVP_PKEY_DILITHIUM, NULL, &p, (long)mldsaSz)); + wolfSSL_EVP_PKEY_free(pkey2); + } + XFREE(out, NULL, DYNAMIC_TYPE_OPENSSL); + } + 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. */ + { + wc_MlDsaKey* mldsa = NULL; + word32 idx = 0; + word32 rawSz = 2560; + int keyRet = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + + ExpectNotNull(mldsa = (wc_MlDsaKey*)XMALLOC(sizeof(*mldsa), NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(keyRet = 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(); + if (keyRet == 0) { + wc_MlDsaKey_Free(mldsa); + } + XFREE(mldsa, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + p = rawBlob; + ExpectNull(wolfSSL_d2i_PrivateKey(WC_EVP_PKEY_DILITHIUM, NULL, &p, + (long)rawSz)); + } + + /* Typed public-key entry point decodes an ML-DSA SPKI. Reuse the + * mldsaDer buffer for the SPKI bytes. */ + { + int spkiSz = 0; + + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa44_pub-spki.der", "rb")) + != XBADFILE); + ExpectIntGT(spkiSz = (int)XFREAD(mldsaDer, 1, 4096, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + p = mldsaDer; + ExpectNotNull(pkey = wolfSSL_d2i_PublicKey(WC_EVP_PKEY_DILITHIUM, + NULL, &p, (long)spkiSz)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), WC_EVP_PKEY_DILITHIUM); + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + } + + XFREE(rawBlob, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(rsaDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(mldsaDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#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 65df74ce18..704843c75b 100644 --- a/tests/api/test_evp_pkey.h +++ b/tests/api/test_evp_pkey.h @@ -71,6 +71,8 @@ int test_wolfSSL_EVP_PKEY_print_public(void); 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); @@ -125,6 +127,8 @@ int test_wolfSSL_d2i_PrivateKey_reuse_resets_state(void); TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_ed25519), \ 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/tests/api/test_ossl_pem.c b/tests/api/test_ossl_pem.c index a023b6188e..89b3b417e7 100644 --- a/tests/api/test_ossl_pem.c +++ b/tests/api/test_ossl_pem.c @@ -603,6 +603,117 @@ int test_wolfSSL_PEM_PrivateKey_dh(void) return EXPECT_RESULT(); } +/* test loading ML-DSA keys with PEM_read_bio_PrivateKey and + * PEM_read_PrivateKey */ +int test_wolfSSL_PEM_PrivateKey_mldsa(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_CERTS) && \ + defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + !defined(WOLFSSL_MLDSA_NO_ASN1) && !defined(NO_FILESYSTEM) && \ + !defined(NO_BIO) + const char* fnames[] = { + #ifndef WOLFSSL_NO_ML_DSA_44 + "./certs/mldsa/mldsa44-key.pem", + #endif + #ifndef WOLFSSL_NO_ML_DSA_65 + "./certs/mldsa/mldsa65-key.pem", + #endif + #ifndef WOLFSSL_NO_ML_DSA_87 + "./certs/mldsa/mldsa87-key.pem", + #endif + }; + BIO* bio = NULL; + EVP_PKEY* pkey = NULL; + XFILE file = XBADFILE; + word32 i; + + for (i = 0; i < (word32)(sizeof(fnames) / sizeof(*fnames)); i++) { + /* BIO variant */ + ExpectNotNull(bio = BIO_new_file(fnames[i], "rb")); + ExpectNotNull(pkey = wolfSSL_PEM_read_bio_PrivateKey(bio, NULL, NULL, + NULL)); + ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM); + BIO_free(bio); + bio = NULL; + EVP_PKEY_free(pkey); + pkey = NULL; + + /* XFILE variant */ + ExpectTrue((file = XFOPEN(fnames[i], "rb")) != XBADFILE); + ExpectNotNull(pkey = wolfSSL_PEM_read_PrivateKey(file, NULL, NULL, + NULL)); + ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM); + if (file != XBADFILE) { + XFCLOSE(file); + file = XBADFILE; + } + EVP_PKEY_free(pkey); + pkey = NULL; + } + + /* Out-parameter reuse form (documented OpenSSL semantics): a second + * read into the same non-NULL EVP_PKEY must re-populate the object. */ + { + word32 last = (word32)(sizeof(fnames) / sizeof(*fnames)) - 1; + int firstSz = 0; + + ExpectNotNull(bio = BIO_new_file(fnames[0], "rb")); + ExpectNotNull(wolfSSL_PEM_read_bio_PrivateKey(bio, &pkey, NULL, + NULL)); + ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM); + if (pkey != NULL) { + firstSz = pkey->pkey_sz; + } + BIO_free(bio); + bio = NULL; + + ExpectNotNull(bio = BIO_new_file(fnames[last], "rb")); + ExpectNotNull(wolfSSL_PEM_read_bio_PrivateKey(bio, &pkey, NULL, + NULL)); + ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM); + if ((last > 0) && (pkey != NULL)) { + /* A different level was read: the held key must have been + * replaced, not left stale. */ + ExpectIntNE(pkey->pkey_sz, firstSz); + } + BIO_free(bio); + bio = NULL; + + /* A failed read into the same pointer must leave the held key + * untouched, not freed (the caller would otherwise be left with + * a dangling pointer and a later double free). */ + { + static const char badPem[] = + "-----BEGIN PRIVATE KEY-----\n" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" + "-----END PRIVATE KEY-----\n"; + static const unsigned char junk[8] = + { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + const unsigned char* jp = junk; + + ExpectNull(wolfSSL_d2i_PrivateKey(WC_EVP_PKEY_DILITHIUM, &pkey, + &jp, (long)sizeof(junk))); + ExpectNotNull(pkey); + ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM); + + ExpectNotNull(bio = BIO_new_mem_buf(badPem, + (int)sizeof(badPem) - 1)); + ExpectNull(wolfSSL_PEM_read_bio_PrivateKey(bio, &pkey, NULL, + NULL)); + ExpectNotNull(pkey); + ExpectIntEQ(EVP_PKEY_id(pkey), EVP_PKEY_DILITHIUM); + BIO_free(bio); + bio = NULL; + } + + EVP_PKEY_free(pkey); + pkey = NULL; + } +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_PEM_PrivateKey(void) { EXPECT_DECLS; diff --git a/tests/api/test_ossl_pem.h b/tests/api/test_ossl_pem.h index 3de900a6fa..996feffbfd 100644 --- a/tests/api/test_ossl_pem.h +++ b/tests/api/test_ossl_pem.h @@ -31,6 +31,7 @@ int test_wolfSSL_PEM_PrivateKey_rsa(void); int test_wolfSSL_PEM_PrivateKey_ecc(void); int test_wolfSSL_PEM_PrivateKey_dsa(void); int test_wolfSSL_PEM_PrivateKey_dh(void); +int test_wolfSSL_PEM_PrivateKey_mldsa(void); int test_wolfSSL_PEM_PrivateKey(void); int test_wolfSSL_PEM_write_PrivateKey(void); int test_wolfSSL_PEM_write_PUBKEY(void); @@ -53,6 +54,7 @@ int test_wolfSSL_PEM_PUBKEY(void); TEST_DECL_GROUP("ossl_pem", test_wolfSSL_PEM_PrivateKey_ecc), \ TEST_DECL_GROUP("ossl_pem", test_wolfSSL_PEM_PrivateKey_dsa), \ TEST_DECL_GROUP("ossl_pem", test_wolfSSL_PEM_PrivateKey_dh), \ + TEST_DECL_GROUP("ossl_pem", test_wolfSSL_PEM_PrivateKey_mldsa), \ TEST_DECL_GROUP("ossl_pem", test_wolfSSL_PEM_PrivateKey), \ TEST_DECL_GROUP("ossl_pem", test_wolfSSL_PEM_write_PrivateKey), \ TEST_DECL_GROUP("ossl_pem", test_wolfSSL_PEM_write_PUBKEY), \ diff --git a/tests/api/test_ossl_x509_pk.c b/tests/api/test_ossl_x509_pk.c index e3b87888a8..84de675afb 100644 --- a/tests/api/test_ossl_x509_pk.c +++ b/tests/api/test_ossl_x509_pk.c @@ -29,6 +29,9 @@ #endif #include +#ifdef WOLFSSL_HAVE_MLDSA + #include +#endif #include #include #include @@ -342,6 +345,335 @@ int test_wolfSSL_X509_set_pubkey(void) wolfSSL_EVP_PKEY_free(pkey); pkey = NULL; #endif +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) && !defined(NO_FILESYSTEM) && \ + !defined(NO_BIO) && !defined(WOLFSSL_NO_ML_DSA_44) + { + WOLFSSL_BIO* bio = NULL; + WOLFSSL_EVP_PKEY* pubkey = NULL; + + /* EVP_PKEY with no key data */ + ExpectNotNull(pkey = wolfSSL_EVP_PKEY_new()); + if (pkey != NULL) { + pkey->type = WC_EVP_PKEY_DILITHIUM; + } + ExpectIntEQ(wolfSSL_X509_set_pubkey(x509, pkey), WOLFSSL_FAILURE); + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + + /* ML-DSA keys loaded from file: cover every compiled-in level. + * ML-DSA-87 (2592-byte public key, 4627-byte signature) is what + * motivates the enlarged DER buffers. */ + { + static const char* keyFiles[] = { + "./certs/mldsa/mldsa44-key.pem", + #ifndef WOLFSSL_NO_ML_DSA_65 + "./certs/mldsa/mldsa65-key.pem", + #endif + #ifndef WOLFSSL_NO_ML_DSA_87 + "./certs/mldsa/mldsa87-key.pem", + #endif + }; + size_t ki; + + for (ki = 0; ki < sizeof(keyFiles) / sizeof(keyFiles[0]); ki++) { + ExpectNotNull(bio = wolfSSL_BIO_new_file(keyFiles[ki], "rb")); + ExpectNotNull(pkey = wolfSSL_PEM_read_bio_PrivateKey(bio, + NULL, NULL, NULL)); + wolfSSL_BIO_free(bio); + bio = NULL; + ExpectIntEQ(wolfSSL_X509_set_pubkey(x509, pkey), + WOLFSSL_SUCCESS); + + /* public key can be retrieved and has the right type */ + ExpectNotNull(pubkey = wolfSSL_X509_get_pubkey(x509)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pubkey), + WC_EVP_PKEY_DILITHIUM); + wolfSSL_EVP_PKEY_free(pubkey); + pubkey = NULL; + + #if defined(WOLFSSL_CERT_GEN) && !defined(NO_PWDBASED) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) + /* sign and verify round trip with the ML-DSA key */ + { + WOLFSSL_X509_NAME* name = NULL; + + ExpectNotNull(name = wolfSSL_X509_NAME_new()); + ExpectIntEQ(wolfSSL_X509_NAME_add_entry_by_txt(name, + "CN", MBSTRING_UTF8, (const byte*)"mldsa-test", -1, + -1, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(x509, name), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(x509, name), + WOLFSSL_SUCCESS); + wolfSSL_X509_NAME_free(name); + + ExpectIntGT(wolfSSL_X509_sign(x509, pkey, + wolfSSL_EVP_sha256()), 0); + ExpectNotNull(pubkey = wolfSSL_X509_get_pubkey(x509)); + ExpectIntEQ(wolfSSL_X509_verify(x509, pubkey), + WOLFSSL_SUCCESS); + wolfSSL_EVP_PKEY_free(pubkey); + pubkey = NULL; + + /* OpenSSL semantics: NULL md is valid for ML-DSA. */ + ExpectIntGT(wolfSSL_X509_sign(x509, pkey, NULL), 0); + ExpectNotNull(pubkey = wolfSSL_X509_get_pubkey(x509)); + ExpectIntEQ(wolfSSL_X509_verify(x509, pubkey), + WOLFSSL_SUCCESS); + wolfSSL_EVP_PKEY_free(pubkey); + pubkey = NULL; + + /* A stale/tampered mldsaOID cache must fail the sign + * rather than emit a certificate whose + * signatureAlgorithm disagrees with the actual key. */ + if (EXPECT_SUCCESS() && pkey != NULL) { + int realOID = WOLFSSL_ATOMIC_LOAD(pkey->mldsaOID); + int wrongOID = (realOID == ML_DSA_44k) ? + ML_DSA_65k : ML_DSA_44k; + WOLFSSL_ATOMIC_STORE(pkey->mldsaOID, wrongOID); + ExpectIntEQ(wolfSSL_X509_sign(x509, pkey, NULL), + WC_NO_ERR_TRACE(WOLFSSL_FAILURE)); + WOLFSSL_ATOMIC_STORE(pkey->mldsaOID, realOID); + } + } + #endif + if (ki + 1 < sizeof(keyFiles) / sizeof(keyFiles[0])) { + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + } + } + } + + /* Public-only EVP_PKEY holding an SPKI: the private-key decode + * fails and the fallback public decode must work on a reset key. */ + { + WOLFSSL_EVP_PKEY* spki = NULL; + unsigned char* der = NULL; + int derSz = 0; + const unsigned char* pp; + XFILE f = XBADFILE; + + ExpectNotNull(der = (unsigned char*)XMALLOC(2048, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa44_pub-spki.der", + "rb")) != XBADFILE); + ExpectIntGT(derSz = (int)XFREAD(der, 1, 2048, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + pp = der; + ExpectNotNull(spki = wolfSSL_d2i_PUBKEY(NULL, &pp, (long)derSz)); + ExpectIntEQ(wolfSSL_X509_set_pubkey(x509, spki), WOLFSSL_SUCCESS); + ExpectNotNull(pubkey = wolfSSL_X509_get_pubkey(x509)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pubkey), WC_EVP_PKEY_DILITHIUM); + wolfSSL_EVP_PKEY_free(pubkey); + pubkey = NULL; + wolfSSL_EVP_PKEY_free(spki); + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + + /* PKCS#8 shapes: a seed-carrying key derives the public half so + * set_pubkey succeeds. For a private-only blob the outcome tracks + * whether wolfCrypt can derive the public half on demand in + * wc_MlDsaKey_PublicKeyToDer() (added by PR #10985). */ + { + unsigned char* der = NULL; + int derSz = 0; + const unsigned char* pp; + XFILE f = XBADFILE; + + ExpectNotNull(der = (unsigned char*)XMALLOC(4096, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + #ifndef WOLFSSL_MLDSA_VERIFY_ONLY + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa44_seed-priv.der", + "rb")) != XBADFILE); + ExpectIntGT(derSz = (int)XFREAD(der, 1, 4096, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + pp = der; + ExpectNotNull(pkey = wolfSSL_d2i_PrivateKey( + WC_EVP_PKEY_DILITHIUM, NULL, &pp, (long)derSz)); + ExpectIntEQ(wolfSSL_X509_set_pubkey(x509, pkey), + WOLFSSL_SUCCESS); + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + #endif + + ExpectTrue((f = XFOPEN("./certs/mldsa/mldsa44_priv-only.der", + "rb")) != XBADFILE); + ExpectIntGT(derSz = (int)XFREAD(der, 1, 4096, f), 0); + if (f != XBADFILE) { + XFCLOSE(f); + f = XBADFILE; + } + { + wc_MlDsaKey* rawKey = NULL; + byte* pubDer = NULL; + word32 kidx = 0; + int expected = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); + int keyRet = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + + ExpectNotNull(pubDer = (byte*)XMALLOC( + MLDSA_MAX_PUB_KEY_DER_SIZE, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(rawKey = (wc_MlDsaKey*)XMALLOC(sizeof(*rawKey), + NULL, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(keyRet = wc_MlDsaKey_Init(rawKey, NULL, + INVALID_DEVID), 0); + PRIVATE_KEY_UNLOCK(); + ExpectIntEQ(wc_MlDsaKey_PrivateKeyDecode(rawKey, der, + (word32)derSz, &kidx), 0); + if (EXPECT_SUCCESS() && + wc_MlDsaKey_PublicKeyToDer(rawKey, pubDer, + MLDSA_MAX_PUB_KEY_DER_SIZE, 1) > 0) { + expected = WOLFSSL_SUCCESS; + } + PRIVATE_KEY_LOCK(); + if (keyRet == 0) { + wc_MlDsaKey_Free(rawKey); + } + XFREE(rawKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(pubDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + pp = der; + ExpectNotNull(pkey = wolfSSL_d2i_PrivateKey( + WC_EVP_PKEY_DILITHIUM, NULL, &pp, (long)derSz)); + ExpectIntEQ(wolfSSL_X509_set_pubkey(x509, pkey), expected); + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + } + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + + #if defined(WOLFSSL_MLDSA_FIPS204_DRAFT) && \ + !defined(WOLFSSL_MLDSA_VERIFY_ONLY) && defined(WOLFSSL_CERT_GEN) && \ + !defined(NO_PWDBASED) && !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) && !defined(WOLFSSL_NO_ML_DSA_44) + /* FIPS204-draft Dilithium key: set_pubkey must keep pubKeyOID + * consistent with the draft-OID SPKI emitted by + * wc_MlDsaKey_PublicKeyToDer(), or the subsequent sign fails. */ + { + wc_MlDsaKey* draftKey = NULL; + WC_RNG rng; + byte* draftDer = NULL; + int draftDerSz = 0; + const unsigned char* dp; + int rngRet = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + int keyRet = WC_NO_ERR_TRACE(BAD_FUNC_ARG); + + ExpectNotNull(draftDer = (byte*)XMALLOC(4096, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(draftKey = (wc_MlDsaKey*)XMALLOC(sizeof(*draftKey), + NULL, DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(rngRet = wc_InitRng(&rng), 0); + ExpectIntEQ(keyRet = wc_MlDsaKey_Init(draftKey, NULL, + INVALID_DEVID), 0); + ExpectIntEQ(wc_MlDsaKey_SetParams(draftKey, WC_ML_DSA_44_DRAFT), + 0); + ExpectIntEQ(wc_MlDsaKey_MakeKey(draftKey, &rng), 0); + /* KeyToDer (priv+pub): the decode of a priv-only PKCS#8 does + * not derive the public part needed by PublicKeyToDer. */ + PRIVATE_KEY_UNLOCK(); + ExpectIntGT(draftDerSz = wc_MlDsaKey_KeyToDer(draftKey, + draftDer, 4096), 0); + PRIVATE_KEY_LOCK(); + if (keyRet == 0) { + wc_MlDsaKey_Free(draftKey); + } + XFREE(draftKey, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (rngRet == 0) { + wc_FreeRng(&rng); + } + + dp = draftDer; + ExpectNotNull(pkey = wolfSSL_d2i_PrivateKey( + WC_EVP_PKEY_DILITHIUM, NULL, &dp, (long)draftDerSz)); + ExpectIntEQ(wolfSSL_X509_set_pubkey(x509, pkey), + WOLFSSL_SUCCESS); + ExpectIntGT(wolfSSL_X509_sign(x509, pkey, NULL), 0); + ExpectNotNull(pubkey = wolfSSL_X509_get_pubkey(x509)); + ExpectIntEQ(wolfSSL_X509_verify(x509, pubkey), WOLFSSL_SUCCESS); + wolfSSL_EVP_PKEY_free(pubkey); + pubkey = NULL; + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + XFREE(draftDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + #endif /* WOLFSSL_MLDSA_FIPS204_DRAFT */ + } +#endif /* WOLFSSL_HAVE_MLDSA */ + +#if defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) && !defined(NO_FILESYSTEM) && \ + !defined(NO_BIO) && !defined(WOLFSSL_NO_ML_DSA_87) && \ + !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048) && \ + defined(WOLFSSL_CERT_GEN) && !defined(NO_PWDBASED) + { + /* Classic-signed certificate carrying a large ML-DSA-87 subject + * public key: the DER buffer must be sized for the subject SPKI + * even though the signing key is RSA. */ + WOLFSSL_X509* cx = NULL; + WOLFSSL_EVP_PKEY* mldsaKey = NULL; + WOLFSSL_EVP_PKEY* rsaKey = NULL; + WOLFSSL_EVP_PKEY* pub = NULL; + WOLFSSL_X509_NAME* name = NULL; + WOLFSSL_BIO* bio = NULL; + const unsigned char* p = client_key_der_2048; + + ExpectNotNull(bio = wolfSSL_BIO_new_file( + "./certs/mldsa/mldsa87-key.pem", "rb")); + ExpectNotNull(mldsaKey = wolfSSL_PEM_read_bio_PrivateKey(bio, NULL, + NULL, NULL)); + wolfSSL_BIO_free(bio); + bio = NULL; + ExpectNotNull(rsaKey = wolfSSL_d2i_PrivateKey(EVP_PKEY_RSA, NULL, &p, + (long)sizeof_client_key_der_2048)); + + ExpectNotNull(cx = wolfSSL_X509_new()); + ExpectNotNull(name = wolfSSL_X509_NAME_new()); + ExpectIntEQ(wolfSSL_X509_NAME_add_entry_by_txt(name, "CN", + MBSTRING_UTF8, (const byte*)"mldsa87-subject", -1, -1, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_subject_name(cx, name), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_set_issuer_name(cx, name), WOLFSSL_SUCCESS); + wolfSSL_X509_NAME_free(name); + ExpectIntEQ(wolfSSL_X509_set_pubkey(cx, mldsaKey), WOLFSSL_SUCCESS); + + /* NULL md is still rejected for a hash-based signing key. */ + ExpectIntEQ(wolfSSL_X509_sign(cx, rsaKey, NULL), WOLFSSL_FAILURE); + + ExpectIntGT(wolfSSL_X509_sign(cx, rsaKey, wolfSSL_EVP_sha256()), 0); + + /* Subject public key kept its type; signature checks out with the + * RSA issuer key. */ + ExpectNotNull(pub = wolfSSL_X509_get_pubkey(cx)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pub), WC_EVP_PKEY_DILITHIUM); + wolfSSL_EVP_PKEY_free(pub); + pub = NULL; + { + const unsigned char* pp = client_keypub_der_2048; + ExpectNotNull(pub = wolfSSL_d2i_PUBKEY(NULL, &pp, + (long)sizeof_client_keypub_der_2048)); + ExpectIntEQ(wolfSSL_X509_verify(cx, pub), WOLFSSL_SUCCESS); + wolfSSL_EVP_PKEY_free(pub); + pub = NULL; + } + + wolfSSL_EVP_PKEY_free(rsaKey); + wolfSSL_EVP_PKEY_free(mldsaKey); + wolfSSL_X509_free(cx); + } +#endif /* classic-signed cert with ML-DSA-87 subject key */ wolfSSL_X509_free(x509); #endif diff --git a/tests/api/test_x509.c b/tests/api/test_x509.c index aee18ecd55..faa710891d 100644 --- a/tests/api/test_x509.c +++ b/tests/api/test_x509.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -1146,3 +1147,93 @@ int test_x509_ReqCertFromX509_ext_critical(void) #endif return EXPECT_RESULT(); } + +/* Sign a certificate request with an ML-DSA key through the compat layer + * (wolfSSL_X509_REQ_sign), round-trip it through DER and verify the + * signature with the public key recovered from the parsed request. The + * REQ path sizes and allocates its DER buffer separately from + * wolfSSL_X509_sign, so it needs its own coverage. */ +int test_x509_REQ_sign_mldsa(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_CERT_REQ) && defined(WOLFSSL_CERT_GEN) && \ + (defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)) && \ + defined(WOLFSSL_HAVE_MLDSA) && defined(WOLFSSL_MLDSA_PRIVATE_KEY) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) && !defined(WOLFSSL_MLDSA_NO_ASN1) && \ + !defined(WOLFSSL_MLDSA_NO_SIGN) && !defined(WOLFSSL_MLDSA_NO_VERIFY) && \ + defined(WC_ENABLE_ASYM_KEY_EXPORT) && !defined(NO_FILESYSTEM) && \ + !defined(NO_BIO) && !defined(NO_PWDBASED) && !defined(WOLFSSL_NO_ML_DSA_44) + + WOLFSSL_BIO* bio = NULL; + WOLFSSL_EVP_PKEY* pkey = NULL; + WOLFSSL_EVP_PKEY* pubkey = NULL; + WOLFSSL_X509* req = NULL; + WOLFSSL_X509* parsed = NULL; + WOLFSSL_X509_NAME* name = NULL; + unsigned char* der = NULL; + int derSz = 0; + /* ML-DSA-87 (4627-byte signature) is what motivates the enlarged DER + * buffers; cover every compiled-in level. */ + static const char* keyFiles[] = { + "./certs/mldsa/mldsa44-key.pem", + #ifndef WOLFSSL_NO_ML_DSA_65 + "./certs/mldsa/mldsa65-key.pem", + #endif + #ifndef WOLFSSL_NO_ML_DSA_87 + "./certs/mldsa/mldsa87-key.pem", + #endif + }; + size_t i; + + for (i = 0; i < sizeof(keyFiles) / sizeof(keyFiles[0]); i++) { + ExpectNotNull(bio = wolfSSL_BIO_new_file(keyFiles[i], "rb")); + ExpectNotNull(pkey = wolfSSL_PEM_read_bio_PrivateKey(bio, NULL, NULL, + NULL)); + wolfSSL_BIO_free(bio); + bio = NULL; + + ExpectNotNull(req = wolfSSL_X509_REQ_new()); + ExpectNotNull(name = wolfSSL_X509_NAME_new()); + ExpectIntEQ(wolfSSL_X509_NAME_add_entry_by_txt(name, "commonName", + MBSTRING_UTF8, (const byte*)"mldsa-req", -1, -1, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_REQ_set_subject_name(req, name), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_X509_REQ_set_pubkey(req, pkey), WOLFSSL_SUCCESS); + + ExpectIntEQ(wolfSSL_X509_REQ_sign(req, pkey, wolfSSL_EVP_sha256()), + WOLFSSL_SUCCESS); + + /* Round-trip the signed request through DER to prove the encoding + * is a complete, parseable CSR (an ML-DSA signature does not fit + * the old fixed 2048-byte buffer). */ + ExpectIntGT((derSz = wolfSSL_i2d_X509_REQ(req, &der)), 0); + ExpectNotNull(der); + ExpectNotNull(parsed = wolfSSL_X509_REQ_d2i(NULL, der, derSz)); + + /* Verify the signature with the public key from the parsed + * request. */ + ExpectNotNull(pubkey = wolfSSL_X509_get_pubkey(parsed)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pubkey), WC_EVP_PKEY_DILITHIUM); + ExpectIntEQ(wolfSSL_X509_REQ_verify(parsed, pubkey), WOLFSSL_SUCCESS); + + /* OpenSSL semantics: NULL md is valid for ML-DSA. */ + ExpectIntEQ(wolfSSL_X509_REQ_sign(req, pkey, NULL), WOLFSSL_SUCCESS); + + wolfSSL_EVP_PKEY_free(pubkey); + pubkey = NULL; + wolfSSL_X509_free(parsed); + parsed = NULL; + XFREE(der, NULL, DYNAMIC_TYPE_OPENSSL); + der = NULL; + derSz = 0; + wolfSSL_X509_NAME_free(name); + name = NULL; + wolfSSL_X509_free(req); + req = NULL; + wolfSSL_EVP_PKEY_free(pkey); + pkey = NULL; + } +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_x509.h b/tests/api/test_x509.h index a0d6bac3b1..326886ca48 100644 --- a/tests/api/test_x509.h +++ b/tests/api/test_x509.h @@ -31,6 +31,7 @@ int test_x509_CertFromX509_akid_overflow(void); int test_x509_ReqCertFromX509_skid_overflow(void); int test_x509_ReqCertFromX509_skid_boundary(void); int test_x509_ReqCertFromX509_ext_critical(void); +int test_x509_REQ_sign_mldsa(void); #define TEST_X509_DECLS \ TEST_DECL_GROUP("x509", test_x509_rfc2818_verification_callback), \ @@ -41,6 +42,7 @@ int test_x509_ReqCertFromX509_ext_critical(void); TEST_DECL_GROUP("x509", test_x509_CertFromX509_akid_overflow), \ TEST_DECL_GROUP("x509", test_x509_ReqCertFromX509_skid_overflow), \ TEST_DECL_GROUP("x509", test_x509_ReqCertFromX509_skid_boundary), \ - TEST_DECL_GROUP("x509", test_x509_ReqCertFromX509_ext_critical) + TEST_DECL_GROUP("x509", test_x509_ReqCertFromX509_ext_critical), \ + TEST_DECL_GROUP("x509", test_x509_REQ_sign_mldsa) #endif /* WOLFCRYPT_TEST_X509_H */ diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index 37f6ba5155..5e88bcd37e 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -9256,6 +9256,59 @@ static void clearEVPPkeyKeys(WOLFSSL_EVP_PKEY *pkey) } pkey->ownEcc = 0; #endif +#ifdef HAVE_ED25519 + if (pkey->ed25519 != NULL && pkey->ownEd25519 == 1) { + wc_ed25519_free(pkey->ed25519); + XFREE(pkey->ed25519, pkey->heap, DYNAMIC_TYPE_ED25519); + pkey->ed25519 = NULL; + } + pkey->ownEd25519 = 0; +#endif +#ifdef HAVE_ED448 + if (pkey->ed448 != NULL && pkey->ownEd448 == 1) { + wc_ed448_free(pkey->ed448); + XFREE(pkey->ed448, pkey->heap, DYNAMIC_TYPE_ED448); + pkey->ed448 = NULL; + } + pkey->ownEd448 = 0; +#endif +#ifdef HAVE_CURVE25519 + if (pkey->curve25519 != NULL && pkey->ownCurve25519 == 1) { + wc_curve25519_free(pkey->curve25519); + XFREE(pkey->curve25519, pkey->heap, DYNAMIC_TYPE_CURVE25519); + pkey->curve25519 = NULL; + } + pkey->ownCurve25519 = 0; +#endif +#ifdef HAVE_CURVE448 + if (pkey->curve448 != NULL && pkey->ownCurve448 == 1) { + wc_curve448_free(pkey->curve448); + XFREE(pkey->curve448, pkey->heap, DYNAMIC_TYPE_CURVE448); + pkey->curve448 = NULL; + } + pkey->ownCurve448 = 0; +#endif +#ifdef HAVE_HKDF + XFREE(pkey->hkdfSalt, NULL, DYNAMIC_TYPE_SALT); + pkey->hkdfSalt = NULL; + if (pkey->hkdfKey != NULL && pkey->hkdfKeySz > 0) { + ForceZero(pkey->hkdfKey, pkey->hkdfKeySz); + } + XFREE(pkey->hkdfKey, NULL, DYNAMIC_TYPE_KEY); + pkey->hkdfKey = NULL; + XFREE(pkey->hkdfInfo, NULL, DYNAMIC_TYPE_INFO); + pkey->hkdfInfo = NULL; + pkey->hkdfSaltSz = 0; + pkey->hkdfKeySz = 0; + pkey->hkdfInfoSz = 0; +#endif +#if defined(WOLFSSL_CMAC) && defined(OPENSSL_EXTRA) && \ + defined(WOLFSSL_AES_DIRECT) + if (pkey->cmacCtx != NULL) { + wolfSSL_CMAC_CTX_free(pkey->cmacCtx); + pkey->cmacCtx = NULL; + } +#endif } #ifndef NO_RSA @@ -9372,6 +9425,7 @@ static int PopulateRSAEvpPkeyDer(WOLFSSL_EVP_PKEY *pkey) ForceZero(keyBuf, (word32)keySz); XFREE(keyBuf, pkey->heap, DYNAMIC_TYPE_DER); pkey->pkey.ptr = (char*)derBuf; + pkey->pkey_sz = (int)pkcs8Sz; } else { /* The encoding is abandoned but keyBuf stays on the pkey, diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index 7a81f0d94a..e70ca24c9f 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -55,6 +55,8 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, int prevSz = 0; int ret = 1; + (void)priv; + /* Get or create the EVP PKEY object. */ if (*out != NULL) { pkey = *out; @@ -72,7 +74,9 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, clearEVPPkeyKeys(pkey); #endif /* Drop metadata describing the key this object held before, so a - * reused object decodes to the same state as a new one. */ + * reused object decodes to the same state as a new one. A failure + * below must not leave the object advertising the old type. */ + pkey->type = WC_EVP_PKEY_NONE; pkey->pkcs8HeaderSz = 0; pkey->save_type = 0; #ifdef HAVE_ECC @@ -90,11 +94,12 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, } } - /* Set the size and allocate memory for key data to be copied into. */ + /* Set the size and allocate memory for key data to be copied into. + * Heap hint and DYNAMIC_TYPE must match the frees of pkey.ptr. */ pkey->pkey_sz = (int)memSz; if (memSz > 0) { pkey->pkey.ptr = (char*)XMALLOC((size_t)memSz, pkey->heap, - priv ? DYNAMIC_TYPE_PRIVATE_KEY : DYNAMIC_TYPE_PUBLIC_KEY); + DYNAMIC_TYPE_PUBLIC_KEY); if (pkey->pkey.ptr == NULL) { /* No encoding held - do not describe one. */ pkey->pkey_sz = 0; @@ -110,8 +115,7 @@ static int d2i_make_pkey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, if (prevSz > 0) { ForceZero(prevData, (word32)prevSz); } - XFREE(prevData, pkey->heap, - priv ? DYNAMIC_TYPE_PRIVATE_KEY : DYNAMIC_TYPE_PUBLIC_KEY); + XFREE(prevData, pkey->heap, DYNAMIC_TYPE_PUBLIC_KEY); } if (ret == 1) { /* Set key type passed in and return object. */ @@ -279,13 +283,15 @@ static int d2iTryEccKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, * @param [in] mem Memory containing key data. * @param [in] memSz Size of key data in bytes. * @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. * @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 d2iTryEd25519Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, - long memSz, int priv) + long memSz, int priv, int prePopulated) { ed25519_key* edKey = NULL; word32 keyIdx = 0; @@ -318,10 +324,10 @@ static int d2iTryEd25519Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, return WOLFSSL_FATAL_ERROR; } - /* Create an EVP PKEY object holding the input DER bytes. If the caller - * already populated the EVP PKEY with the input bytes (pkey.ptr set), - * skip the allocate/copy. */ - if (*out == NULL || (*out)->pkey.ptr == NULL) { + /* Copy the consumed DER into pkey->pkey.ptr, unless the caller + * pre-filled the EVP PKEY with the input bytes (d2i_evp_pkey()). + * A reused key must be re-populated here. */ + if (!prePopulated) { ret = d2i_make_pkey(out, mem, keyIdx, priv, WC_EVP_PKEY_ED25519); } if (ret == 1) { @@ -345,13 +351,15 @@ static int d2iTryEd25519Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, * @param [in] mem Memory containing key data. * @param [in] memSz Size of key data in bytes. * @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. * @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 d2iTryEd448Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, - long memSz, int priv) + long memSz, int priv, int prePopulated) { ed448_key* edKey = NULL; word32 keyIdx = 0; @@ -384,10 +392,10 @@ static int d2iTryEd448Key(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, return WOLFSSL_FATAL_ERROR; } - /* Create an EVP PKEY object holding the input DER bytes. If the caller - * already populated the EVP PKEY with the input bytes (pkey.ptr set), - * skip the allocate/copy. */ - if (*out == NULL || (*out)->pkey.ptr == NULL) { + /* Copy the consumed DER into pkey->pkey.ptr, unless the caller + * pre-filled the EVP PKEY with the input bytes (d2i_evp_pkey()). + * A reused key must be re-populated here. */ + if (!prePopulated) { ret = d2i_make_pkey(out, mem, keyIdx, priv, WC_EVP_PKEY_ED448); } if (ret == 1) { @@ -691,7 +699,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_EVP_PKEY_new_raw_private_key(int type, return NULL; } - pkey->pkey.ptr = (char*)XMALLOC(len, pkey->heap, DYNAMIC_TYPE_PRIVATE_KEY); + pkey->pkey.ptr = (char*)XMALLOC(len, pkey->heap, DYNAMIC_TYPE_PUBLIC_KEY); if (pkey->pkey.ptr == NULL) { wolfSSL_EVP_PKEY_free(pkey); return NULL; @@ -1023,13 +1031,17 @@ static int d2iTryFalconKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, * @param [in] mem Memory containing key data. * @param [in] memSz Size of key data in bytes. * @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) + 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; @@ -1054,8 +1066,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; @@ -1118,8 +1131,13 @@ static int d2iTryMlDsaKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, return WOLFSSL_FATAL_ERROR; } - /* Copy the consumed DER into pkey->pkey.ptr when the input was DER */ - ret = d2i_make_pkey(out, mem, keyIdx, priv, WC_EVP_PKEY_DILITHIUM); + /* Copy the consumed DER into pkey->pkey.ptr, unless the caller + * pre-filled the EVP PKEY with the input bytes (d2i_evp_pkey()). + * A reused key must be re-populated here. */ + ret = 1; + if (!prePopulated) { + ret = d2i_make_pkey(out, mem, keyIdx, priv, WC_EVP_PKEY_DILITHIUM); + } if ((ret == 1) && (out != NULL) && (*out != NULL) && (oidSum != 0)) { WOLFSSL_ATOMIC_STORE((*out)->mldsaOID, oidSum); } @@ -1194,13 +1212,13 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey_try(WOLFSSL_EVP_PKEY** out, #endif /* !NO_DH && OPENSSL_EXTRA && WOLFSSL_DH_EXTRA */ #if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT) - if (d2iTryEd25519Key(&pkey, *in, inSz, priv) >= 0) { + if (d2iTryEd25519Key(&pkey, *in, inSz, priv, 0) >= 0) { found = 1; } else #endif /* HAVE_ED25519 && HAVE_ED25519_KEY_IMPORT */ #if defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT) - if (d2iTryEd448Key(&pkey, *in, inSz, priv) >= 0) { + if (d2iTryEd448Key(&pkey, *in, inSz, priv, 0) >= 0) { found = 1; } else @@ -1212,7 +1230,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) { + if (d2iTryMlDsaKey(&pkey, *in, inSz, priv, 0, 1) >= 0) { found = 1; } else @@ -1449,12 +1467,31 @@ 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"); return NULL; } + #ifdef WOLFSSL_HAVE_MLDSA + /* Keep the full PKCS#8 wrapper for ML-DSA so i2d retains the + * parameter set held in the AlgorithmIdentifier. */ + if (type == WC_EVP_PKEY_DILITHIUM) { + pkcs8HeaderSz = 0; + } + #endif + (void)idx; /* not used */ } /* Ensure no error occurred try to remove any PKCS#8 header. */ @@ -1464,12 +1501,8 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, } } - /* Dispose of any WOLFSSL_EVP_PKEY passed in. */ - if (out != NULL && *out != NULL) { - wolfSSL_EVP_PKEY_free(*out); - *out = NULL; - } - /* Create a new WOLFSSL_EVP_PKEY and populate. */ + /* Create a new WOLFSSL_EVP_PKEY and populate. Any WOLFSSL_EVP_PKEY + * passed in is replaced only on success. */ local = wolfSSL_EVP_PKEY_new(); if (local == NULL) { return NULL; @@ -1557,10 +1590,8 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, #endif /* WOLFSSL_QT || OPENSSL_ALL || WOLFSSL_OPENSSH */ #if defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT) case WC_EVP_PKEY_ED25519: - /* local->pkey.ptr already holds the input bytes, so - * d2iTryEd25519Key will skip the d2i_make_pkey allocate/copy - * and just decode into local->ed25519. */ - if (d2iTryEd25519Key(&local, p, local->pkey_sz, priv) != 1) { + /* local already holds the input bytes: prePopulated=1. */ + if (d2iTryEd25519Key(&local, p, local->pkey_sz, priv, 1) != 1) { wolfSSL_EVP_PKEY_free(local); return NULL; } @@ -1569,12 +1600,21 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, #if defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT) case WC_EVP_PKEY_ED448: /* See WC_EVP_PKEY_ED25519 case above. */ - if (d2iTryEd448Key(&local, p, local->pkey_sz, priv) != 1) { + if (d2iTryEd448Key(&local, p, local->pkey_sz, priv, 1) != 1) { wolfSSL_EVP_PKEY_free(local); return NULL; } break; #endif /* HAVE_ED448 */ +#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, 0) != 1) { + wolfSSL_EVP_PKEY_free(local); + return NULL; + } + break; +#endif /* WOLFSSL_HAVE_MLDSA */ default: WOLFSSL_MSG("Unsupported key type"); wolfSSL_EVP_PKEY_free(local); @@ -1587,6 +1627,8 @@ static WOLFSSL_EVP_PKEY* d2i_evp_pkey(int type, WOLFSSL_EVP_PKEY** out, *in += local->pkey_sz; } if (out != NULL) { + /* Dispose of any WOLFSSL_EVP_PKEY passed in. */ + wolfSSL_EVP_PKEY_free(*out); *out = local; } } @@ -1940,9 +1982,9 @@ WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY( (algId == DILITHIUM_LEVEL3k) || (algId == DILITHIUM_LEVEL5k) || #endif - (algId == ML_DSA_LEVEL2k) || - (algId == ML_DSA_LEVEL3k) || - (algId == ML_DSA_LEVEL5k)) { + (algId == ML_DSA_44k) || + (algId == ML_DSA_65k) || + (algId == ML_DSA_87k)) { /* Keep full PKCS#8 wrapper for level recovery from * AlgorithmIdentifier parameters */