From f818bbacded60ca7832bdb57a91cdb27030e484e Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 10 Sep 2026 15:27:34 +0000 Subject: [PATCH 1/9] X509: cache the decoded public key and give get0_pubkey borrowed semantics wolfSSL_X509_get_pubkey() built a new WOLFSSL_EVP_PKEY, including a freshly decoded RSA or EC key, on every call, and X509_get0_pubkey() mapped to the same function. Callers following the OpenSSL get0 contract, which returns a pointer owned by the certificate, leaked the whole decoded key graph on each call. Decode the public key once into the certificate's existing WOLFSSL_X509_PUBKEY member, lazily on first use (OPENSSL_ALL already fills it at parse time), and free it with the certificate. wolfSSL_X509_get_pubkey() now returns that key with a new reference, matching X509_get_pubkey(). Add wolfSSL_X509_get0_pubkey(), which returns it without a reference, and map X509_get0_pubkey() and X509_REQ_get0_pubkey() to it. Add wolfSSL_X509_PUBKEY_get0() (X509_PUBKEY_get0). wolfSSL_X509_get_X509_PUBKEY() decodes the key too, so X509_PUBKEY_get() and X509_PUBKEY_get0_param() work outside OPENSSL_ALL. get0_param no longer dereferences a missing key and maps the stored key OID to a NID before creating the algorithm object. A certificate without a public key does not cache an empty key. The lazily decoded key is published with a compare-and-exchange, the same way the context private key cache is, so concurrent first calls on a shared certificate do not leak a key. The EC public point's internal copy is marked as set after SetECKeyExternal(), so readers of a shared key do not rebuild it. wolfSSL_X509_set_pubkey() and re-decoding a certificate drop the cached key; set_pubkey keeps it when handed that very key, and refreshes the key's algorithm OID, algorithm object and curve OID. The key.algor member is now freed in every configuration that can allocate it, not only OPENSSL_ALL. Callers that freed the result of X509_get0_pubkey() must stop; the ChangeLog records this. --- ChangeLog.md | 9 +++ doc/dox_comments/header_files/ssl.h | 85 ++++++++++++++++++++ src/internal.c | 26 +++++-- src/pk_ec.c | 5 ++ src/x509.c | 105 +++++++++++++++++++++++-- tests/api/test_ossl_x509_pk.c | 116 ++++++++++++++++++++++++++++ tests/api/test_ossl_x509_pk.h | 4 +- wolfssl/openssl/ssl.h | 4 +- wolfssl/ssl.h | 2 + 9 files changed, 341 insertions(+), 15 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 11101fa49aa..0d03e07c2e0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,6 +13,15 @@ * Fixed `wc_PKCS7_DecodeEnvelopedData()` and `wc_PKCS7_DecodeAuthEnvelopedData()` failing on a message addressed to more than one recipient; AuthEnvelopedData never supported it at all. A message carrying no recipient for the reader now reports `PKCS7_RECIP_E` rather than a parse error. Streaming an AuthEnvelopedData now buffers the whole RecipientInfo set, as the EnvelopedData decoder already did, so peak memory rises by the size of that set. by @Frauschi (PR 11350) +## Behavioral Changes +* **Behavioral change (`X509_get0_pubkey()` returns a borrowed key)**: + `wolfSSL_X509_get_pubkey()` now returns the public key cached on the + certificate with a new reference, and the new `wolfSSL_X509_get0_pubkey()` + (`X509_get0_pubkey()`, `X509_REQ_get0_pubkey()`) returns it without one, as + in OpenSSL. Code that freed the result of `X509_get0_pubkey()` to avoid a + leak must stop doing so, and the returned key is shared with the + certificate, so it must be treated as read only. + # wolfSSL Release 5.9.4 (Sep 25, 2026) Release 5.9.4 has been developed according to wolfSSL's development and QA diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 5d43a1414ac..55a6f847de3 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -5411,6 +5411,91 @@ long wolfSSL_BIO_get_mem_ptr(WOLFSSL_BIO *bio, WOLFSSL_BUF_MEM **m); */ char* wolfSSL_X509_NAME_oneline(WOLFSSL_X509_NAME* name, char* in, int sz); +/*! + \ingroup CertsKeys + + \brief This function returns the public key of the certificate with a new + reference. The key is decoded once and cached on the certificate, so the + same object is returned on every call. Treat it as read only. Free the + reference with wolfSSL_EVP_PKEY_free(). + + \return pointer to the WOLFSSL_EVP_PKEY on success. + \return NULL if x509 is NULL, has no public key, or the key cannot be + decoded. + + \param x509 pointer to a WOLFSSL_X509 structure. + + _Example_ + \code + WOLFSSL_X509* x509; + WOLFSSL_EVP_PKEY* key; + ... + key = wolfSSL_X509_get_pubkey(x509); + if (key == NULL) { + // failed to get the public key + } + ... + wolfSSL_EVP_PKEY_free(key); + \endcode + + \sa wolfSSL_X509_get0_pubkey + \sa wolfSSL_EVP_PKEY_free +*/ +WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509); + +/*! + \ingroup CertsKeys + + \brief This function returns the public key of the certificate without a + new reference. The key is owned by the certificate, stays valid for its + lifetime, and must not be freed. Treat it as read only. + + \return pointer to the WOLFSSL_EVP_PKEY on success. + \return NULL if x509 is NULL, has no public key, or the key cannot be + decoded. + + \param x509 pointer to a WOLFSSL_X509 structure. + + _Example_ + \code + WOLFSSL_X509* x509; + WOLFSSL_EVP_PKEY* key; + ... + key = wolfSSL_X509_get0_pubkey(x509); + if (key == NULL) { + // failed to get the public key + } + // do not free key + \endcode + + \sa wolfSSL_X509_get_pubkey +*/ +WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509); + +/*! + \ingroup CertsKeys + + \brief This function returns the key held by a WOLFSSL_X509_PUBKEY without + a new reference. The key must not be freed. + + \return pointer to the WOLFSSL_EVP_PKEY on success. + \return NULL if key is NULL or holds no key. + + \param key pointer to a WOLFSSL_X509_PUBKEY structure. + + _Example_ + \code + WOLFSSL_X509* x509; + WOLFSSL_EVP_PKEY* pkey; + ... + pkey = wolfSSL_X509_PUBKEY_get0(wolfSSL_X509_get_X509_PUBKEY(x509)); + \endcode + + \sa wolfSSL_X509_PUBKEY_get + \sa wolfSSL_X509_get_X509_PUBKEY +*/ +WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(WOLFSSL_X509_PUBKEY* key); + /*! \ingroup CertsKeys diff --git a/src/internal.c b/src/internal.c index e3052a49425..e933240882d 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5798,19 +5798,27 @@ static void FreeX509Contents(WOLFSSL_X509* x509) wolfSSL_ASN1_OBJECT_free(x509->algor.algorithm); x509->algor.algorithm = NULL; } + if (x509->subjAltNameSrc != NULL) { + XFREE(x509->subjAltNameSrc, x509->heap, DYNAMIC_TYPE_X509_EXT); + x509->subjAltNameSrc= NULL; + } + #endif /* OPENSSL_ALL */ + #if (defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL)) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) || \ + defined(WOLFSSL_APACHE_HTTPD) || defined(WOLFSSL_HAPROXY) || \ + defined(WOLFSSL_WPAS)) if (x509->key.algor) { wolfSSL_X509_ALGOR_free(x509->key.algor); x509->key.algor = NULL; } + #endif + #ifdef OPENSSL_EXTRA_X509_SMALL + /* Public key cached by wolfSSL_X509_get_pubkey() and friends. */ if (x509->key.pkey) { wolfSSL_EVP_PKEY_free(x509->key.pkey); x509->key.pkey = NULL; } - if (x509->subjAltNameSrc != NULL) { - XFREE(x509->subjAltNameSrc, x509->heap, DYNAMIC_TYPE_X509_EXT); - x509->subjAltNameSrc= NULL; - } - #endif /* OPENSSL_ALL */ + #endif #if defined(WOLFSSL_CERT_REQ) && defined(OPENSSL_ALL) if (x509->reqAttributes) { wolfSSL_sk_pop_free(x509->reqAttributes, NULL); @@ -15563,6 +15571,11 @@ static void CopyDecodedSepFields(WOLFSSL_X509* x509, DecodedCert* dCert) * error, matching the original. */ static int CopyDecodedPubKey(WOLFSSL_X509* x509, DecodedCert* dCert, int ret) { +#ifdef OPENSSL_EXTRA_X509_SMALL + /* Drop the key decoded from a previous public key. */ + wolfSSL_EVP_PKEY_free(x509->key.pkey); + x509->key.pkey = NULL; +#endif if (dCert->publicKey != NULL && dCert->pubKeySize != 0) { x509->pubKey.buffer = (byte*)XMALLOC( dCert->pubKeySize, x509->heap, DYNAMIC_TYPE_PUBLIC_KEY); @@ -15592,9 +15605,6 @@ static int CopyDecodedPubKey(WOLFSSL_X509* x509, DecodedCert* dCert, int ret) } } - wolfSSL_EVP_PKEY_free(x509->key.pkey); - x509->key.pkey = NULL; - switch (dCert->keyOID) { #ifdef HAVE_ED25519 case ED25519k: diff --git a/src/pk_ec.c b/src/pk_ec.c index 7f52df3ea76..23d22e54fdd 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -4394,6 +4394,11 @@ int SetECKeyExternal(WOLFSSL_EC_KEY* eckey) WOLFSSL_MSG("SetECKeyExternal ec_point_external_set failed"); ret = WOLFSSL_FATAL_ERROR; } + /* Both sides of the point match now, so readers of a shared key + * do not rebuild the internal one. */ + if (ret == 1) { + eckey->pub_key->inSet = 1; + } } /* set the external privkey */ diff --git a/src/x509.c b/src/x509.c index 66850941c18..1478a4b3e8a 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6386,19 +6386,19 @@ int wolfSSL_X509_NAME_get_text_by_NID(WOLFSSL_X509_NAME* name, return (textSz - 1); /* do not include null character in size */ } -/* Creates a new WOLFSSL_EVP_PKEY structure that has the public key from x509 +/* Decodes the public key of x509 into a new WOLFSSL_EVP_PKEY. * * returns a pointer to the created WOLFSSL_EVP_PKEY on success and NULL on fail */ -WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) +static WOLFSSL_EVP_PKEY* X509DecodePubKey(WOLFSSL_X509* x509) { WOLFSSL_EVP_PKEY* key = NULL; int ret = 0; (void)ret; - WOLFSSL_ENTER("wolfSSL_X509_get_pubkey"); - if (x509 != NULL) { + if (x509 != NULL && x509->pubKey.buffer != NULL && + x509->pubKey.length > 0) { key = wolfSSL_EVP_PKEY_new_ex(x509->heap); if (key != NULL) { if (x509->pubKeyOID == RSAk) { @@ -6543,6 +6543,70 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) } return key; } + +/* Returns the public key cached in x509, decoding it on first use. + * + * The key is owned by x509 and freed with it. Concurrent first calls each + * decode; the first to publish wins and the others free their copy. + */ +static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) +{ + WOLFSSL_EVP_PKEY* key; + + if (x509 == NULL) + return NULL; + key = x509->key.pkey; + if (key == NULL) { + key = X509DecodePubKey(x509); + if (key != NULL) { + #ifdef WOLFSSL_ATOMIC_OPS + WOLFSSL_EVP_PKEY* current = NULL; + if (!wolfSSL_Atomic_Ptr_CompareExchange( + (void* volatile*)&x509->key.pkey, (void**)¤t, key)) { + wolfSSL_EVP_PKEY_free(key); + key = current; + } + #else + x509->key.pkey = key; + #endif + x509->key.pubKeyOID = x509->pubKeyOID; + } + } + return key; +} + +/* Returns the public key of x509 with a new reference. + * + * returns a pointer to the WOLFSSL_EVP_PKEY on success and NULL on fail. + * The caller frees it with wolfSSL_EVP_PKEY_free(). + */ +WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) +{ + WOLFSSL_EVP_PKEY* key; + + WOLFSSL_ENTER("wolfSSL_X509_get_pubkey"); + key = X509CachedPubKey(x509); + if (key != NULL) { + int ret; + wolfSSL_RefInc(&key->ref, &ret); + if (ret != 0) { + WOLFSSL_MSG("Failed to lock pkey mutex"); + key = NULL; + } + } + return key; +} + +/* Returns the public key of x509 without a new reference. + * + * returns a pointer to the WOLFSSL_EVP_PKEY on success and NULL on fail. + * The key is valid for the lifetime of x509 and must not be freed. + */ +WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509) +{ + WOLFSSL_ENTER("wolfSSL_X509_get0_pubkey"); + return X509CachedPubKey(x509); +} #endif /* OPENSSL_EXTRA_X509_SMALL */ /* End of smaller subset of X509 compatibility functions. Avoid increasing the @@ -11578,6 +11642,9 @@ WOLFSSL_X509_PUBKEY* wolfSSL_X509_get_X509_PUBKEY(const WOLFSSL_X509* x509) return NULL; } + /* Decode the key so pkey is usable through the returned object. */ + (void)X509CachedPubKey((WOLFSSL_X509*)x509); + return (WOLFSSL_X509_PUBKEY*)&x509->key; } @@ -11593,12 +11660,17 @@ int wolfSSL_X509_PUBKEY_get0_param(WOLFSSL_ASN1_OBJECT **ppkalg, WOLFSSL_MSG("X509_PUBKEY struct not populated"); return WOLFSSL_FAILURE; } + if ((pk || ppklen) && !pub->pkey) { + WOLFSSL_MSG("X509_PUBKEY has no decoded key"); + return WOLFSSL_FAILURE; + } if (!pub->algor) { if (!(pub->algor = wolfSSL_X509_ALGOR_new())) { return WOLFSSL_FAILURE; } - pub->algor->algorithm = wolfSSL_OBJ_nid2obj(pub->pubKeyOID); + pub->algor->algorithm = wolfSSL_OBJ_nid2obj( + oid2nid((word32)pub->pubKeyOID, oidKeyType)); if (pub->algor->algorithm == NULL) { WOLFSSL_MSG("Failed to create object from NID"); return WOLFSSL_FAILURE; @@ -11633,6 +11705,15 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get(WOLFSSL_X509_PUBKEY* key) return key->pkey; } +/* Returns the pkey without a new reference. */ +WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(WOLFSSL_X509_PUBKEY* key) +{ + WOLFSSL_ENTER("wolfSSL_X509_PUBKEY_get0"); + if (key == NULL) + return NULL; + return key->pkey; +} + int wolfSSL_X509_PUBKEY_set(WOLFSSL_X509_PUBKEY **x, WOLFSSL_EVP_PKEY *key) { WOLFSSL_X509_PUBKEY *pk = NULL; @@ -16846,6 +16927,7 @@ int wolfSSL_X509_set_pubkey(WOLFSSL_X509 *cert, WOLFSSL_EVP_PKEY *pkey) return WOLFSSL_FAILURE; } cert->pubKeyOID = ECDSAk; + cert->pkCurveOID = ecc->dp->oidSum; } break; #endif @@ -16963,6 +17045,19 @@ int wolfSSL_X509_set_pubkey(WOLFSSL_X509 *cert, WOLFSSL_EVP_PKEY *pkey) XFREE(cert->pubKey.buffer, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY); cert->pubKey.buffer = p; cert->pubKey.length = (unsigned int)derSz; + /* Drop what was decoded from the previous public key, unless the caller + * passed that very key. */ + if (cert->key.pkey != pkey) { + wolfSSL_EVP_PKEY_free(cert->key.pkey); + cert->key.pkey = NULL; + } + cert->key.pubKeyOID = cert->pubKeyOID; +#if defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) || \ + defined(WOLFSSL_APACHE_HTTPD) || defined(WOLFSSL_HAPROXY) || \ + defined(WOLFSSL_WPAS) + wolfSSL_X509_ALGOR_free(cert->key.algor); + cert->key.algor = NULL; +#endif return WOLFSSL_SUCCESS; } diff --git a/tests/api/test_ossl_x509_pk.c b/tests/api/test_ossl_x509_pk.c index 3265f87052f..6a1e90c81c8 100644 --- a/tests/api/test_ossl_x509_pk.c +++ b/tests/api/test_ossl_x509_pk.c @@ -680,3 +680,119 @@ int test_wolfSSL_X509_set_pubkey(void) return EXPECT_RESULT(); } +int test_wolfSSL_X509_get0_pubkey(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) + X509* x509 = NULL; + EVP_PKEY* borrowed = NULL; + EVP_PKEY* owned = NULL; + ASN1_OBJECT* obj = NULL; + const unsigned char* pk = NULL; + int pkLen = 0; + + ExpectNull(X509_get0_pubkey(NULL)); + ExpectNull(X509_get_pubkey(NULL)); + + /* A certificate without a public key has nothing to hand out. */ + ExpectNotNull(x509 = X509_new()); + ExpectNull(X509_get0_pubkey(x509)); + ExpectNull(X509_get_pubkey(x509)); + X509_free(x509); + x509 = NULL; + + ExpectNotNull(x509 = X509_load_certificate_file(caCertFile, + SSL_FILETYPE_PEM)); + + /* get0 hands out the same borrowed key every time. */ + ExpectNotNull(borrowed = X509_get0_pubkey(x509)); + ExpectPtrEq(X509_get0_pubkey(x509), borrowed); + ExpectIntEQ(EVP_PKEY_id(borrowed), EVP_PKEY_RSA); + ExpectIntEQ(X509_verify(x509, borrowed), WOLFSSL_SUCCESS); + /* The embedded X509_PUBKEY reports the same key. */ + ExpectIntEQ(X509_PUBKEY_get0_param(&obj, &pk, &pkLen, NULL, + X509_get_X509_PUBKEY(x509)), 1); + ExpectIntEQ(OBJ_obj2nid(obj), EVP_PKEY_RSA); + ExpectNotNull(pk); + ExpectIntGT(pkLen, 0); + + /* get_pubkey hands out a new reference to that same key. */ + ExpectNotNull(owned = X509_get_pubkey(x509)); + ExpectPtrEq(owned, borrowed); + EVP_PKEY_free(owned); + owned = NULL; + /* The borrowed key is still alive after the owned reference is freed. */ + ExpectPtrEq(X509_get0_pubkey(x509), borrowed); + ExpectIntEQ(X509_verify(x509, borrowed), WOLFSSL_SUCCESS); + + /* X509_PUBKEY_get returns yet another reference to the same key, and + * X509_PUBKEY_get0 the same borrowed pointer. */ + ExpectNotNull(owned = X509_PUBKEY_get(X509_get_X509_PUBKEY(x509))); + ExpectPtrEq(owned, borrowed); + EVP_PKEY_free(owned); + owned = NULL; + ExpectPtrEq(X509_PUBKEY_get0(X509_get_X509_PUBKEY(x509)), borrowed); + ExpectNull(X509_PUBKEY_get0(NULL)); + + /* Setting the certificate's own key back keeps the cached key. */ + ExpectIntEQ(X509_set_pubkey(x509, borrowed), WOLFSSL_SUCCESS); + ExpectPtrEq(X509_get0_pubkey(x509), borrowed); + ExpectIntEQ(X509_verify(x509, borrowed), WOLFSSL_SUCCESS); + + /* An owned reference outlives the certificate. */ + ExpectNotNull(owned = X509_get_pubkey(x509)); + X509_free(x509); + x509 = NULL; + ExpectIntEQ(EVP_PKEY_bits(owned), 2048); + EVP_PKEY_free(owned); + owned = NULL; + + /* Freeing the owned reference before the certificate is fine too. */ + ExpectNotNull(x509 = X509_load_certificate_file(caCertFile, + SSL_FILETYPE_PEM)); + ExpectNotNull(owned = X509_get_pubkey(x509)); + ExpectNotNull(borrowed = X509_get0_pubkey(x509)); + EVP_PKEY_free(owned); + owned = NULL; + ExpectIntEQ(EVP_PKEY_bits(borrowed), 2048); + X509_free(x509); + x509 = NULL; + +#ifdef HAVE_ECC + /* Setting a new public key drops the cached key. */ + { + X509* ecX509 = NULL; + EVP_PKEY* ecKey = NULL; + EVP_PKEY* rsaKey = NULL; + + ExpectNotNull(x509 = X509_load_certificate_file(caCertFile, + SSL_FILETYPE_PEM)); + ExpectNotNull(ecX509 = X509_load_certificate_file(caEccCertFile, + SSL_FILETYPE_PEM)); + ExpectNotNull(rsaKey = X509_get_pubkey(x509)); + ExpectNotNull(ecKey = X509_get_pubkey(ecX509)); + ExpectIntEQ(X509_set_pubkey(x509, ecKey), WOLFSSL_SUCCESS); + ExpectNotNull(borrowed = X509_get0_pubkey(x509)); + ExpectIntEQ(EVP_PKEY_id(borrowed), EVP_PKEY_EC); + /* The algorithm reported by the X509_PUBKEY follows the key. */ + ExpectIntEQ(X509_PUBKEY_get0_param(&obj, NULL, NULL, NULL, + X509_get_X509_PUBKEY(x509)), 1); + ExpectIntEQ(OBJ_obj2nid(obj), EVP_PKEY_EC); + ExpectIntEQ(X509_set_pubkey(x509, rsaKey), WOLFSSL_SUCCESS); + ExpectNotNull(borrowed = X509_get0_pubkey(x509)); + ExpectIntEQ(EVP_PKEY_id(borrowed), EVP_PKEY_RSA); + ExpectIntEQ(X509_PUBKEY_get0_param(&obj, NULL, NULL, NULL, + X509_get_X509_PUBKEY(x509)), 1); + ExpectIntEQ(OBJ_obj2nid(obj), EVP_PKEY_RSA); + /* rsaKey is a separate reference and survives the cache drop. */ + ExpectIntEQ(EVP_PKEY_bits(rsaKey), 2048); + EVP_PKEY_free(rsaKey); + EVP_PKEY_free(ecKey); + X509_free(ecX509); + X509_free(x509); + } +#endif +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_ossl_x509_pk.h b/tests/api/test_ossl_x509_pk.h index cb949bbb88d..4fee22a4432 100644 --- a/tests/api/test_ossl_x509_pk.h +++ b/tests/api/test_ossl_x509_pk.h @@ -30,6 +30,7 @@ int test_wolfSSL_X509_PUBKEY_EC(void); int test_wolfSSL_X509_PUBKEY_DSA(void); int test_wolfSSL_X509_PUBKEY_get(void); int test_wolfSSL_X509_set_pubkey(void); +int test_wolfSSL_X509_get0_pubkey(void); #define TEST_OSSL_X509_PK_DECLS \ TEST_DECL_GROUP("ossl_x509_pk", test_wolfSSL_X509_get_X509_PUBKEY), \ @@ -37,6 +38,7 @@ int test_wolfSSL_X509_set_pubkey(void); TEST_DECL_GROUP("ossl_x509_pk", test_wolfSSL_X509_PUBKEY_EC), \ TEST_DECL_GROUP("ossl_x509_pk", test_wolfSSL_X509_PUBKEY_DSA), \ TEST_DECL_GROUP("ossl_x509_pk", test_wolfSSL_X509_PUBKEY_get), \ - TEST_DECL_GROUP("ossl_x509_pk", test_wolfSSL_X509_set_pubkey) + TEST_DECL_GROUP("ossl_x509_pk", test_wolfSSL_X509_set_pubkey), \ + TEST_DECL_GROUP("ossl_x509_pk", test_wolfSSL_X509_get0_pubkey) #endif /* WOLFCRYPT_TEST_OSSL_X509_PK_H */ diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 71312d68ec8..5187297d109 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -584,8 +584,9 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; #define X509_get_subject_name(x) wolfSSL_X509_get_subject_name((WOLFSSL_X509*)(x)) #define X509_REQ_get_subject_name wolfSSL_X509_get_subject_name #define X509_get_pubkey wolfSSL_X509_get_pubkey -#define X509_get0_pubkey wolfSSL_X509_get_pubkey +#define X509_get0_pubkey wolfSSL_X509_get0_pubkey #define X509_REQ_get_pubkey wolfSSL_X509_get_pubkey +#define X509_REQ_get0_pubkey wolfSSL_X509_get0_pubkey #define X509_get_notBefore wolfSSL_X509_get_notBefore #define X509_get0_notBefore wolfSSL_X509_get_notBefore #define X509_getm_notBefore wolfSSL_X509_get_notBefore @@ -905,6 +906,7 @@ wolfSSL_X509_STORE_set_verify_cb((WOLFSSL_X509_STORE *)(s), (WOLFSSL_X509_STORE_ #define X509_get0_tbs_sigalg wolfSSL_X509_get0_tbs_sigalg #define X509_PUBKEY_get0_param wolfSSL_X509_PUBKEY_get0_param #define X509_PUBKEY_get wolfSSL_X509_PUBKEY_get +#define X509_PUBKEY_get0 wolfSSL_X509_PUBKEY_get0 #define X509_PUBKEY_set wolfSSL_X509_PUBKEY_set #define X509_ALGOR_get0 wolfSSL_X509_ALGOR_get0 #define X509_ALGOR_set0 wolfSSL_X509_ALGOR_set0 diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 871308c7a6f..ad39bee999c 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2518,6 +2518,7 @@ WOLFSSL_API WOLFSSL_ASN1_TIME* wolfSSL_X509_CRL_get_nextUpdate(WOLFSSL_X509_CRL* WOLFSSL_API int wolfSSL_X509_CRL_set_nextUpdate(WOLFSSL_X509_CRL* crl, const WOLFSSL_ASN1_TIME* time); WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509); +WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509); WOLFSSL_API int wolfSSL_X509_CRL_verify(WOLFSSL_X509_CRL* crl, WOLFSSL_EVP_PKEY* pkey); WOLFSSL_API void wolfSSL_X509_OBJECT_free_contents(WOLFSSL_X509_OBJECT* obj); WOLFSSL_API WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY_bio( @@ -6327,6 +6328,7 @@ WOLFSSL_API void wolfSSL_X509_PUBKEY_free(WOLFSSL_X509_PUBKEY *x); WOLFSSL_API WOLFSSL_X509_PUBKEY *wolfSSL_X509_get_X509_PUBKEY(const WOLFSSL_X509* x509); WOLFSSL_API int wolfSSL_X509_PUBKEY_get0_param(WOLFSSL_ASN1_OBJECT **ppkalg, const unsigned char **pk, int *ppklen, WOLFSSL_X509_ALGOR **pa, WOLFSSL_X509_PUBKEY *pub); WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get(WOLFSSL_X509_PUBKEY* key); +WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(WOLFSSL_X509_PUBKEY* key); WOLFSSL_API int wolfSSL_X509_PUBKEY_set(WOLFSSL_X509_PUBKEY **x, WOLFSSL_EVP_PKEY *key); WOLFSSL_API int wolfSSL_i2t_ASN1_OBJECT(char *buf, int buf_len, WOLFSSL_ASN1_OBJECT *a); WOLFSSL_API WOLFSSL_ASN1_OBJECT *wolfSSL_d2i_ASN1_OBJECT(WOLFSSL_ASN1_OBJECT **a, From 59a8c0a7eca81d6210638ccb9b9e693b7efc34d9 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 11 Sep 2026 05:33:04 +0000 Subject: [PATCH 2/9] Fix CI: keep the ML-DSA raw public key in the EVP PKEY d2iTryMlDsaKey() left keyIdx at 0 when the size-keyed raw import path matched, so d2i_make_pkey() copied nothing and the resulting EVP PKEY carried pkey.ptr == NULL and pkey_sz == 0. Under OPENSSL_ALL the certificate parser fills x509->key.pkey through wolfSSL_d2i_PUBKEY(), and an ML-DSA certificate stores the raw public key, so the cached key had no key material. Now that wolfSSL_X509_get_pubkey() returns that cached key, wolfSSL_X509_verify() and wolfSSL_X509_REQ_verify() had nothing to verify against and failed. Raw bytes carry no length prefix, so the whole input is the key. --- wolfcrypt/src/evp_pk.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index d4e3e06b6ed..771b1d12713 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -1225,6 +1225,8 @@ static int d2iTryMlDsaKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, } if (rc == 0) { isMlDsa = 1; + /* Raw bytes carry no length prefix; the whole input is the key. */ + keyIdx = inSz; } } From e0fcedd89721ed8272c74ce416c83ebc3be83652 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 11 Sep 2026 05:36:49 +0000 Subject: [PATCH 3/9] Address review: publish the cached key OID first, make get0_pubkey const Set x509->key.pubKeyOID before the compare-and-exchange publishes x509->key.pkey, so a concurrent X509_PUBKEY_get0_param() never sees a key next to an OID of 0. X509_get0_pubkey() takes a const X509* in OpenSSL. Take a const WOLFSSL_X509* and cast for the lazy cache, the same way wolfSSL_X509_get_X509_PUBKEY() does. --- doc/dox_comments/header_files/ssl.h | 2 +- src/x509.c | 9 ++++++--- wolfssl/ssl.h | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 55a6f847de3..23ed10a4032 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -5470,7 +5470,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509); \sa wolfSSL_X509_get_pubkey */ -WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509); +WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(const WOLFSSL_X509* x509); /*! \ingroup CertsKeys diff --git a/src/x509.c b/src/x509.c index 1478a4b3e8a..4367cc91f50 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6559,6 +6559,9 @@ static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) if (key == NULL) { key = X509DecodePubKey(x509); if (key != NULL) { + /* Set before publishing the pointer, so a reader that sees the + * key never sees an OID of 0 next to it. */ + x509->key.pubKeyOID = x509->pubKeyOID; #ifdef WOLFSSL_ATOMIC_OPS WOLFSSL_EVP_PKEY* current = NULL; if (!wolfSSL_Atomic_Ptr_CompareExchange( @@ -6569,7 +6572,6 @@ static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) #else x509->key.pkey = key; #endif - x509->key.pubKeyOID = x509->pubKeyOID; } } return key; @@ -6602,10 +6604,11 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) * returns a pointer to the WOLFSSL_EVP_PKEY on success and NULL on fail. * The key is valid for the lifetime of x509 and must not be freed. */ -WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509) +WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(const WOLFSSL_X509* x509) { WOLFSSL_ENTER("wolfSSL_X509_get0_pubkey"); - return X509CachedPubKey(x509); + /* The cache is the only thing written, like X509_get_X509_PUBKEY(). */ + return X509CachedPubKey((WOLFSSL_X509*)x509); } #endif /* OPENSSL_EXTRA_X509_SMALL */ diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index ad39bee999c..472b56d1a82 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2518,7 +2518,8 @@ WOLFSSL_API WOLFSSL_ASN1_TIME* wolfSSL_X509_CRL_get_nextUpdate(WOLFSSL_X509_CRL* WOLFSSL_API int wolfSSL_X509_CRL_set_nextUpdate(WOLFSSL_X509_CRL* crl, const WOLFSSL_ASN1_TIME* time); WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509); -WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(WOLFSSL_X509* x509); +WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey( + const WOLFSSL_X509* x509); WOLFSSL_API int wolfSSL_X509_CRL_verify(WOLFSSL_X509_CRL* crl, WOLFSSL_EVP_PKEY* pkey); WOLFSSL_API void wolfSSL_X509_OBJECT_free_contents(WOLFSSL_X509_OBJECT* obj); WOLFSSL_API WOLFSSL_PKCS8_PRIV_KEY_INFO* wolfSSL_d2i_PKCS8_PKEY_bio( From ecb575e0d4d7e155327c5f3bd491283d098cee89 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Mon, 14 Sep 2026 05:21:05 +0000 Subject: [PATCH 4/9] Fix CI: hoist the atomic CAS temporary to the top of X509CachedPubKey C89 forbids mixed declarations and code. Windows (C2275) and the -Wdeclaration-after-statement Jenkins configs both rejected the declaration inside the WOLFSSL_ATOMIC_OPS block. --- src/x509.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/x509.c b/src/x509.c index 4367cc91f50..932411366b8 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6552,6 +6552,9 @@ static WOLFSSL_EVP_PKEY* X509DecodePubKey(WOLFSSL_X509* x509) static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) { WOLFSSL_EVP_PKEY* key; +#ifdef WOLFSSL_ATOMIC_OPS + WOLFSSL_EVP_PKEY* current = NULL; +#endif if (x509 == NULL) return NULL; @@ -6563,7 +6566,6 @@ static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) * key never sees an OID of 0 next to it. */ x509->key.pubKeyOID = x509->pubKeyOID; #ifdef WOLFSSL_ATOMIC_OPS - WOLFSSL_EVP_PKEY* current = NULL; if (!wolfSSL_Atomic_Ptr_CompareExchange( (void* volatile*)&x509->key.pkey, (void**)¤t, key)) { wolfSSL_EVP_PKEY_free(key); From d644d975eb267fec87829ab08a14d758ff0c64f7 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 2 Oct 2026 06:18:45 +0000 Subject: [PATCH 5/9] Address review: const X509_PUBKEY_get0, explain the const casts X509_PUBKEY_get0() takes a const X509_PUBKEY* in OpenSSL; match it. Note why X509_get_X509_PUBKEY() and X509_get0_pubkey() cast away const: OpenSSL takes a const X509 in both, and only the lazily decoded key cache is written. Document that the borrowed key lasts until the certificate is freed or its public key is changed. --- doc/dox_comments/header_files/ssl.h | 7 ++++--- src/x509.c | 10 ++++++---- wolfssl/ssl.h | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 23ed10a4032..f04449222eb 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -5447,8 +5447,9 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509); \ingroup CertsKeys \brief This function returns the public key of the certificate without a - new reference. The key is owned by the certificate, stays valid for its - lifetime, and must not be freed. Treat it as read only. + new reference. The key is owned by the certificate and must not be freed. + It stays valid until the certificate is freed or its public key is + changed. Treat it as read only. \return pointer to the WOLFSSL_EVP_PKEY on success. \return NULL if x509 is NULL, has no public key, or the key cannot be @@ -5494,7 +5495,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(const WOLFSSL_X509* x509); \sa wolfSSL_X509_PUBKEY_get \sa wolfSSL_X509_get_X509_PUBKEY */ -WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(WOLFSSL_X509_PUBKEY* key); +WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(const WOLFSSL_X509_PUBKEY* key); /*! \ingroup CertsKeys diff --git a/src/x509.c b/src/x509.c index 932411366b8..0615aa57eb0 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6604,12 +6604,13 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_get_pubkey(WOLFSSL_X509* x509) /* Returns the public key of x509 without a new reference. * * returns a pointer to the WOLFSSL_EVP_PKEY on success and NULL on fail. - * The key is valid for the lifetime of x509 and must not be freed. + * The key must not be freed. It is valid until x509 is freed or its public + * key is changed. */ WOLFSSL_EVP_PKEY* wolfSSL_X509_get0_pubkey(const WOLFSSL_X509* x509) { WOLFSSL_ENTER("wolfSSL_X509_get0_pubkey"); - /* The cache is the only thing written, like X509_get_X509_PUBKEY(). */ + /* Only the lazily decoded key cache is written. */ return X509CachedPubKey((WOLFSSL_X509*)x509); } #endif /* OPENSSL_EXTRA_X509_SMALL */ @@ -11647,7 +11648,8 @@ WOLFSSL_X509_PUBKEY* wolfSSL_X509_get_X509_PUBKEY(const WOLFSSL_X509* x509) return NULL; } - /* Decode the key so pkey is usable through the returned object. */ + /* OpenSSL also takes a const X509 and returns a mutable X509_PUBKEY. + * Only the lazily decoded key cache is written here. */ (void)X509CachedPubKey((WOLFSSL_X509*)x509); return (WOLFSSL_X509_PUBKEY*)&x509->key; @@ -11711,7 +11713,7 @@ WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get(WOLFSSL_X509_PUBKEY* key) } /* Returns the pkey without a new reference. */ -WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(WOLFSSL_X509_PUBKEY* key) +WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(const WOLFSSL_X509_PUBKEY* key) { WOLFSSL_ENTER("wolfSSL_X509_PUBKEY_get0"); if (key == NULL) diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 472b56d1a82..a94ea75d420 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -6329,7 +6329,8 @@ WOLFSSL_API void wolfSSL_X509_PUBKEY_free(WOLFSSL_X509_PUBKEY *x); WOLFSSL_API WOLFSSL_X509_PUBKEY *wolfSSL_X509_get_X509_PUBKEY(const WOLFSSL_X509* x509); WOLFSSL_API int wolfSSL_X509_PUBKEY_get0_param(WOLFSSL_ASN1_OBJECT **ppkalg, const unsigned char **pk, int *ppklen, WOLFSSL_X509_ALGOR **pa, WOLFSSL_X509_PUBKEY *pub); WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get(WOLFSSL_X509_PUBKEY* key); -WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0(WOLFSSL_X509_PUBKEY* key); +WOLFSSL_API WOLFSSL_EVP_PKEY* wolfSSL_X509_PUBKEY_get0( + const WOLFSSL_X509_PUBKEY* key); WOLFSSL_API int wolfSSL_X509_PUBKEY_set(WOLFSSL_X509_PUBKEY **x, WOLFSSL_EVP_PKEY *key); WOLFSSL_API int wolfSSL_i2t_ASN1_OBJECT(char *buf, int buf_len, WOLFSSL_ASN1_OBJECT *a); WOLFSSL_API WOLFSSL_ASN1_OBJECT *wolfSSL_d2i_ASN1_OBJECT(WOLFSSL_ASN1_OBJECT **a, From 187b101e87ece87284a76ab5f6f98279e0951850 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 2 Oct 2026 06:18:45 +0000 Subject: [PATCH 6/9] Address review: build the X509_PUBKEY algorithm before storing it X509_PUBKEY_get0_param() stored the new X509_ALGOR in pub->algor before filling in its algorithm. When creating the object failed, later calls found the half built algor and returned a NULL algorithm with success. Build it locally, free it on failure, and store it only when complete. --- src/x509.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/x509.c b/src/x509.c index 0615aa57eb0..fe70719d95a 100644 --- a/src/x509.c +++ b/src/x509.c @@ -11673,15 +11673,20 @@ int wolfSSL_X509_PUBKEY_get0_param(WOLFSSL_ASN1_OBJECT **ppkalg, } if (!pub->algor) { - if (!(pub->algor = wolfSSL_X509_ALGOR_new())) { + /* Build the algorithm fully before storing it, so a failure + * never leaves a half built one behind. */ + WOLFSSL_X509_ALGOR* algor = wolfSSL_X509_ALGOR_new(); + if (algor == NULL) { return WOLFSSL_FAILURE; } - pub->algor->algorithm = wolfSSL_OBJ_nid2obj( + algor->algorithm = wolfSSL_OBJ_nid2obj( oid2nid((word32)pub->pubKeyOID, oidKeyType)); - if (pub->algor->algorithm == NULL) { + if (algor->algorithm == NULL) { WOLFSSL_MSG("Failed to create object from NID"); + wolfSSL_X509_ALGOR_free(algor); return WOLFSSL_FAILURE; } + pub->algor = algor; } if (pa) From a7f3c5cc916ae9cc87c0298e7071e791a3f86ebf Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 2 Oct 2026 06:18:45 +0000 Subject: [PATCH 7/9] Address review: test d2i_PUBKEY with a raw ML-DSA public key Decode the raw ML-DSA-44 public key and check that the EVP PKEY holds all of it. Without the keyIdx fix the key had no material. --- tests/api/test_evp_pkey.c | 48 +++++++++++++++++++++++++++++++++++++++ tests/api/test_evp_pkey.h | 2 ++ 2 files changed, 50 insertions(+) diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index dff8fab086e..2d0fb9a4968 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -3182,6 +3182,54 @@ int test_wolfSSL_d2i_PUBKEY_mldsa_reuse(void) return EXPECT_RESULT(); } +/* Raw ML-DSA public key bytes have no length prefix: the decoded EVP_PKEY + * must hold the whole input. */ +int test_wolfSSL_d2i_PUBKEY_mldsa_raw(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_HAVE_MLDSA) && \ + defined(WOLFSSL_MLDSA_PUBLIC_KEY) && !defined(WOLFSSL_NO_ML_DSA_44) && \ + !defined(NO_FILESYSTEM) + WOLFSSL_EVP_PKEY* pkey = NULL; + const unsigned char* p; + const unsigned char* raw = NULL; + unsigned char* der = NULL; + int derSz = 0; + 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); + } + /* The raw key is the tail of the SPKI BIT STRING. */ + ExpectIntGT(derSz, WC_MLDSA_44_PUB_KEY_SIZE); + if (EXPECT_SUCCESS()) { + raw = der + derSz - WC_MLDSA_44_PUB_KEY_SIZE; + } + + p = raw; + ExpectNotNull(pkey = wolfSSL_d2i_PUBKEY(NULL, &p, + (long)WC_MLDSA_44_PUB_KEY_SIZE)); + ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), WC_EVP_PKEY_DILITHIUM); + if (pkey != NULL) { + ExpectIntEQ(pkey->pkey_sz, WC_MLDSA_44_PUB_KEY_SIZE); + ExpectNotNull(pkey->pkey.ptr); + if (pkey->pkey.ptr != NULL) { + ExpectIntEQ(XMEMCMP(pkey->pkey.ptr, raw, + WC_MLDSA_44_PUB_KEY_SIZE), 0); + } + } + + wolfSSL_EVP_PKEY_free(pkey); + XFREE(der, 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. */ diff --git a/tests/api/test_evp_pkey.h b/tests/api/test_evp_pkey.h index 8af6d24712e..fd8c75e866c 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_PUBKEY_mldsa_raw(void); int test_wolfSSL_d2i_PrivateKey_mldsa(void); int test_wolfSSL_EVP_PKEY_x25519(void); int test_wolfSSL_EVP_PKEY_x448(void); @@ -129,6 +130,7 @@ int test_wolfSSL_CTX_use_PrivateKey_pkcs8_repopulate(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_PUBKEY_mldsa_raw), \ 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), \ From 6bf0c44d986e41f609ce943d8cbb4ff457dc5e98 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Mon, 5 Oct 2026 12:35:47 +0000 Subject: [PATCH 8/9] Address review: use plain assignment for the cached X509 key X509 objects are not designed for concurrent use. Their refcount manages lifetime, not thread sharing. Store the lazily decoded key with plain assignment instead of compare-and-exchange. --- src/x509.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/x509.c b/src/x509.c index fe70719d95a..bd3df2fae3b 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6545,16 +6545,10 @@ static WOLFSSL_EVP_PKEY* X509DecodePubKey(WOLFSSL_X509* x509) } /* Returns the public key cached in x509, decoding it on first use. - * - * The key is owned by x509 and freed with it. Concurrent first calls each - * decode; the first to publish wins and the others free their copy. - */ + * The key is owned by x509 and freed with it. */ static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) { WOLFSSL_EVP_PKEY* key; -#ifdef WOLFSSL_ATOMIC_OPS - WOLFSSL_EVP_PKEY* current = NULL; -#endif if (x509 == NULL) return NULL; @@ -6562,18 +6556,8 @@ static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) if (key == NULL) { key = X509DecodePubKey(x509); if (key != NULL) { - /* Set before publishing the pointer, so a reader that sees the - * key never sees an OID of 0 next to it. */ x509->key.pubKeyOID = x509->pubKeyOID; - #ifdef WOLFSSL_ATOMIC_OPS - if (!wolfSSL_Atomic_Ptr_CompareExchange( - (void* volatile*)&x509->key.pkey, (void**)¤t, key)) { - wolfSSL_EVP_PKEY_free(key); - key = current; - } - #else x509->key.pkey = key; - #endif } } return key; From 38df5de4f5b1719334073fb2991eec7a3e213332 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 6 Oct 2026 05:29:19 +0000 Subject: [PATCH 9/9] Fix CI: drop the ML-DSA raw keyIdx override after master's SPKI caching Master now encodes a raw ML-DSA key to DER in d2iTryMlDsaKey() and uses keyIdx == 0 to detect the raw import path. Setting keyIdx to the input size skipped that encoding, so the EVP PKEY cached raw bytes and i2d_PUBKEY()/d2i_PrivateKey() round trips failed in test_wolfSSL_d2i_PUBKEY and test_wolfSSL_d2i_PrivateKeys_bio. The raw ML-DSA test now expects the cached key to be the SPKI DER. --- tests/api/test_evp_pkey.c | 12 +++++++----- wolfcrypt/src/evp_pk.c | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 2d0fb9a4968..42e77bf8ca9 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -3182,8 +3182,8 @@ int test_wolfSSL_d2i_PUBKEY_mldsa_reuse(void) return EXPECT_RESULT(); } -/* Raw ML-DSA public key bytes have no length prefix: the decoded EVP_PKEY - * must hold the whole input. */ +/* Raw ML-DSA public key bytes must decode into an EVP_PKEY holding the + * whole key. */ int test_wolfSSL_d2i_PUBKEY_mldsa_raw(void) { EXPECT_DECLS; @@ -3215,14 +3215,16 @@ int test_wolfSSL_d2i_PUBKEY_mldsa_raw(void) ExpectNotNull(pkey = wolfSSL_d2i_PUBKEY(NULL, &p, (long)WC_MLDSA_44_PUB_KEY_SIZE)); ExpectIntEQ(wolfSSL_EVP_PKEY_id(pkey), WC_EVP_PKEY_DILITHIUM); +#if defined(WC_ENABLE_ASYM_KEY_EXPORT) && !defined(WOLFSSL_MLDSA_NO_ASN1) + /* Raw input is cached as SPKI DER. */ if (pkey != NULL) { - ExpectIntEQ(pkey->pkey_sz, WC_MLDSA_44_PUB_KEY_SIZE); + ExpectIntEQ(pkey->pkey_sz, derSz); ExpectNotNull(pkey->pkey.ptr); if (pkey->pkey.ptr != NULL) { - ExpectIntEQ(XMEMCMP(pkey->pkey.ptr, raw, - WC_MLDSA_44_PUB_KEY_SIZE), 0); + ExpectIntEQ(XMEMCMP(pkey->pkey.ptr, der, (size_t)derSz), 0); } } +#endif wolfSSL_EVP_PKEY_free(pkey); XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfcrypt/src/evp_pk.c b/wolfcrypt/src/evp_pk.c index 771b1d12713..d4e3e06b6ed 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -1225,8 +1225,6 @@ static int d2iTryMlDsaKey(WOLFSSL_EVP_PKEY** out, const unsigned char* mem, } if (rc == 0) { isMlDsa = 1; - /* Raw bytes carry no length prefix; the whole input is the key. */ - keyIdx = inSz; } }