From 4fba304c3e6f20d13a4921c78865da65f3bd4ac4 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 10 Sep 2026 15:27:34 +0000 Subject: [PATCH 1/4] 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 | 8 ++ 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, 340 insertions(+), 15 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 936fc7bad5a..d9923a86a4d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -30,6 +30,14 @@ --disable-tlsv12` and `--enable-dtls --enable-dtls13 --enable-dtlscid --enable-session-ticket --disable-tlsv12` now build and test cleanly. +* **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. + * **Behavioral change (`wc_PufReadSram` health tests the raw SRAM readout)**: the raw readout is now health tested before the context accepts it, and a readout that cannot be SRAM power-on noise is rejected with `PUF_READ_E` diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 24c0c99ef60..01531dd9a61 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -5295,6 +5295,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 2f00929b753..51756a3cbf8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -5484,19 +5484,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); @@ -15249,6 +15257,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); @@ -15278,9 +15291,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 f6df291a89a..d23648af005 100644 --- a/src/pk_ec.c +++ b/src/pk_ec.c @@ -4383,6 +4383,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 514c474bd0f..fc9199be67c 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6397,19 +6397,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) { @@ -6554,6 +6554,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 @@ -11494,6 +11558,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; } @@ -11509,12 +11576,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; @@ -11549,6 +11621,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; @@ -16760,6 +16841,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 @@ -16877,6 +16959,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 84de675afb3..c8e01a45746 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 762eb5833fa..9ac593af620 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -566,8 +566,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 @@ -880,6 +881,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 69c7d802491..a602bec30c6 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2507,6 +2507,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( @@ -6248,6 +6249,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 b0529bf5f451d7c9cb51d55d69cbd38963644847 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 11 Sep 2026 05:33:04 +0000 Subject: [PATCH 2/4] 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 c0347f95b14..a28c18569a4 100644 --- a/wolfcrypt/src/evp_pk.c +++ b/wolfcrypt/src/evp_pk.c @@ -1113,6 +1113,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 9ced2c4bdc0761ae0cea6e7a4cd17641a426edb6 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 11 Sep 2026 05:36:49 +0000 Subject: [PATCH 3/4] 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 01531dd9a61..ab5225da150 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -5354,7 +5354,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 fc9199be67c..b64c192284d 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6570,6 +6570,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( @@ -6580,7 +6583,6 @@ static WOLFSSL_EVP_PKEY* X509CachedPubKey(WOLFSSL_X509* x509) #else x509->key.pkey = key; #endif - x509->key.pubKeyOID = x509->pubKeyOID; } } return key; @@ -6613,10 +6615,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 a602bec30c6..f522371352a 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2507,7 +2507,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 62567ec78282a9b644ad121b29619cdb969d205c Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Mon, 14 Sep 2026 05:21:05 +0000 Subject: [PATCH 4/4] 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 b64c192284d..2f2265c6340 100644 --- a/src/x509.c +++ b/src/x509.c @@ -6563,6 +6563,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; @@ -6574,7 +6577,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);