From f22107cac9baa1917b33a5ffa8ddff23ce5a43c1 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Thu, 5 Nov 2026 18:11:22 +0000 Subject: [PATCH 1/4] Keep only the peer certificate's encoding until it is asked for With WOLFSSL_DYNAMIC_PEER_CERT a connection now keeps only the DER of the peer's leaf certificate when it arrives, in ssl->peerCertDer. GetPeerCert() builds the WOLFSSL_X509 from it the first time it is used, for example by wolfSSL_get_peer_certificate(), and frees the encoding as the object has its own copy. A connection whose application never asks for the certificate no longer holds the decoded object, its two names and an ASN1_STRING per name entry. - SetPeerCertDer() keeps the encoding (without the flag it is SetPeerCert()) and is used when the leaf is kept in ProcessPeerCerts() and when the certificate is restored from a session ticket. - When a verify callback runs for the leaf, the object is built from the decoded leaf as before, so the callback's current certificate is still the kept object. - The object is built as d2i_X509() does. When memory runs out the encoding is kept for a later call. - GetPeerCertDer() gives the encoding without building the object; session ticket creation and wolfSSL_cmp_peer_cert_to_file() use it. - Only a leaf that parsed without error is kept as its encoding alone. One accepted despite an error, such as keyCertSign without being a CA cleared by the chain verify callback, is copied from the decoded leaf as before, as decoding it again would fail the same way. Established client connection with an ECC leaf on an STM32U585 (Cortex-M33, 160 MHz): resident heap 9409 -> 5697 bytes with OPENSSL_EXTRA and 11846 -> 6652 bytes with OPENSSL_ALL (3445 without the compat layer); handshake peak 14822 -> 12925 and 16304 -> 13294 bytes. Unchanged when a verify callback is set or once the certificate has been asked for; the first wolfSSL_get_peer_certificate() then takes about 1.2 ms. --- src/internal.c | 148 ++++++++++++++++-- src/ssl_api_cert.c | 32 ++-- tests/api/test_ssl_cert.c | 321 +++++++++++++++++++++++++++++++++++++- tests/api/test_ssl_cert.h | 4 + tests/api/test_tls13.c | 1 + wolfssl/internal.h | 40 +++-- 6 files changed, 510 insertions(+), 36 deletions(-) diff --git a/src/internal.c b/src/internal.c index 1c5196874d2..5ecf2752211 100644 --- a/src/internal.c +++ b/src/internal.c @@ -6182,6 +6182,7 @@ void FreePeerCert(WOLFSSL* ssl) wolfSSL_X509_free(ssl->peerCert); ssl->peerCert = NULL; } + FreeDer(&ssl->peerCertDer); #else FreeX509(&ssl->peerCert); InitX509(&ssl->peerCert, 0, ssl->heap); @@ -16629,6 +16630,116 @@ int CopyDecodedToX509(WOLFSSL_X509* x509, DecodedCert* dCert) return ret; } +#if defined(KEEP_PEER_CERT) && !defined(NO_ASN) +/* Make the certificate in dCert the kept peer certificate, keeping only its + * encoding when the X509 object can be built from it later. + * + * With WOLFSSL_DYNAMIC_PEER_CERT, GetPeerCert() builds the X509 object from + * the encoding on first use, so a connection whose application never asks for + * it does not hold the decoded object, its names and their entries. Otherwise + * the object is filled in now, as by SetPeerCert(). + * + * Only for a certificate that was parsed without error, so that decoding it + * again succeeds. Use SetPeerCert() for one accepted despite an error. + * + * @param [in, out] ssl SSL/TLS object. + * @param [in] dCert Decoded peer certificate, with its encoding. + * @return 0 on success. + * @return MEMORY_E when the encoding cannot be stored; no cert is kept. + * @return Other error from SetPeerCert(). + */ +int SetPeerCertDer(WOLFSSL* ssl, DecodedCert* dCert) +{ +#ifdef WOLFSSL_DYNAMIC_PEER_CERT + if ((dCert->source != NULL) && (dCert->maxIdx > 0)) { + FreePeerCert(ssl); + if (AllocCopyDecodedDer(&ssl->peerCertDer, dCert->source, + dCert->maxIdx, dCert->srcIdx, ssl->heap) != 0) { + return MEMORY_E; + } + return 0; + } +#endif + return SetPeerCert(ssl, dCert); +} + +#ifdef WOLFSSL_DYNAMIC_PEER_CERT +/* Build the kept peer certificate object from the kept encoding. + * + * Only the encoding of a certificate that parsed without error is kept, and + * the certificate was verified when it was received, so it is only decoded + * here, as by d2i_X509(). A copy that fails part way is still kept, as by + * SetPeerCert(). When memory runs out the encoding is kept instead, so a + * later call can try again. + * + * @param [in, out] ssl SSL/TLS object with only the encoding kept. + */ +static void BuildPeerCert(WOLFSSL* ssl) +{ + int ret = 0; + WOLFSSL_X509* x509 = NULL; + WC_DECLARE_VAR(dCert, DecodedCert, 1, ssl->heap); + + WC_ALLOC_VAR_EX(dCert, DecodedCert, 1, ssl->heap, DYNAMIC_TYPE_DCERT, + ret = MEMORY_E); + if (ret == 0) { + InitDecodedCert(dCert, ssl->peerCertDer->buffer, + ssl->peerCertDer->length, ssl->heap); + #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) + /* CopyDecodedToX509() adds the name entries to the X509 directly. */ + dCert->noX509Name = 1; + #endif + ret = ParseCertRelative(dCert, CERT_TYPE, NO_VERIFY, NULL, NULL); + if (ret == 0) { + x509 = wolfSSL_X509_new_ex(ssl->heap); + if (x509 == NULL) { + ret = MEMORY_E; + } + } + if (ret == 0) { + ret = CopyDecodedToX509(x509, dCert); + if (ret == WC_NO_ERR_TRACE(MEMORY_E)) { + wolfSSL_X509_free(x509); + x509 = NULL; + } + } + FreeDecodedCert(dCert); + WC_FREE_VAR_EX(dCert, ssl->heap, DYNAMIC_TYPE_DCERT); + } + + if (x509 != NULL) { + /* The object has its own copy of the encoding. */ + FreeDer(&ssl->peerCertDer); + ssl->peerCert = x509; + } + else { + WOLFSSL_MSG("Unable to build peer certificate from its encoding"); + } +} +#endif /* WOLFSSL_DYNAMIC_PEER_CERT */ +#endif /* KEEP_PEER_CERT && !NO_ASN */ + +#if defined(KEEP_PEER_CERT) && defined(WOLFSSL_DYNAMIC_PEER_CERT) +/* Get the kept peer certificate object. + * + * When only the certificate's encoding is kept, the object is built from it + * now and kept in its place. + * + * @param [in, out] ssl SSL/TLS object. + * @return Kept peer certificate object. + * @return NULL when no certificate is kept or the object cannot be built. + */ +WOLFSSL_X509* GetPeerCert(WOLFSSL* ssl) +{ +#ifndef NO_ASN + if ((ssl->peerCert == NULL) && (ssl->peerCertDer != NULL)) { + BuildPeerCert(ssl); + } +#endif + return ssl->peerCert; +} +#endif /* KEEP_PEER_CERT && WOLFSSL_DYNAMIC_PEER_CERT */ + #endif /* KEEP_PEER_CERT || SESSION_CERTS */ #if defined(WOLFSSL_ACERT) @@ -17247,8 +17358,14 @@ int SetupStoreCtxCallback(WOLFSSL_X509_STORE_CTX** store_pt, #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) #ifdef KEEP_PEER_CERT if (args->certIdx == 0) { - /* Copy the leaf once, here or in ProcessPeerCerts(). */ - if (!args->peerCertSet) { + /* Copy the leaf once, here or in ProcessPeerCerts(). When + * ProcessPeerCerts() kept only its encoding, build the object + * from the decoded leaf rather than decoding it again. */ + if (!args->peerCertSet + #ifdef WOLFSSL_DYNAMIC_PEER_CERT + || ((ssl->peerCert == NULL) && args->dCertInit) + #endif + ) { if (SetPeerCert(ssl, args->dCert) == 0) args->peerCertSet = 1; else @@ -17993,6 +18110,13 @@ PRAGMA_GCC_DIAG_POP #endif /* Parse Certificate */ ret = ParseCertRelative(args->dCert, certType, verify, SSL_CM(ssl), extraSigners); +#ifdef KEEP_PEER_CERT + /* An error here can still be overridden and the leaf accepted, while + * decoding it again later fails the same way. */ + if ((certType == CERT_TYPE) && (args->certIdx == 0)) { + args->leafDecodeErr = (ret != 0); + } +#endif #ifdef WOLFSSL_SMALL_CERT_VERIFY /* Self signed: the key that verified the signature is its own */ if (verify == VERIFY_NAME && sigRet == 0 && sigCaKey != NULL && @@ -20698,12 +20822,17 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx, #ifdef KEEP_PEER_CERT /* The verify callback may have copied the leaf already, on an - * error. Otherwise it is called later and uses this copy. */ + * error. Otherwise it is called later and uses what is kept + * here. Only a leaf that parsed without error is kept as its + * encoding alone, as only then can it be decoded again. */ if (args->fatal == 0 && !args->peerCertSet) { int copyRet = 0; /* replace old peer cert */ - copyRet = SetPeerCert(ssl, args->dCert); + if (args->leafDecodeErr) + copyRet = SetPeerCert(ssl, args->dCert); + else + copyRet = SetPeerCertDer(ssl, args->dCert); if (copyRet == 0) { args->peerCertSet = 1; } @@ -45469,13 +45598,12 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (!ssl->options.dtls) { const byte* certDer = NULL; word32 certDerSz = 0; - WOLFSSL_X509* peer = GetPeerCert(ssl); + const DerBuffer* peerDer = GetPeerCertDer(ssl); - if (peer != NULL && peer->derCert != NULL && - peer->derCert->length > 0) { + if (peerDer != NULL && peerDer->length > 0) { /* Use current peer certificate */ - certDer = peer->derCert->buffer; - certDerSz = peer->derCert->length; + certDer = peerDer->buffer; + certDerSz = peerDer->length; } #ifdef SESSION_CERTS else if (ssl->session->chain.count > 0) { @@ -46011,7 +46139,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) AddSessionCertToChain(&ssl->session->chain, it->peerCert, peerCertLen); #endif - ret = SetPeerCert(ssl, dCert); + ret = SetPeerCertDer(ssl, dCert); if (ret != 0) { FreePeerCert(ssl); } diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 15781db1732..4ceeac270dd 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -2227,12 +2227,12 @@ int wolfSSL_cmp_peer_cert_to_file(WOLFSSL* ssl, const char *fname) XFILE file = XBADFILE; long sz = 0; void* heap = ssl->ctx->heap; - WOLFSSL_X509* peer_cert = GetPeerCert(ssl); + const DerBuffer* peerDer = GetPeerCertDer(ssl); DerBuffer* fileDer = NULL; /* handle gracefully if called before handshake, or if peer did not * present a cert. */ - if ((peer_cert == NULL) || (peer_cert->derCert == NULL)) { + if (peerDer == NULL) { WOLFSSL_MSG("wolfSSL_cmp_peer_cert_to_file without peer cert"); ret = WOLFSSL_FATAL_ERROR; } @@ -2262,8 +2262,8 @@ int wolfSSL_cmp_peer_cert_to_file(WOLFSSL* ssl, const char *fname) } /* Peer certificate matches when the DER lengths and bytes are equal. */ if ((ret == 0) && ((fileDer->length == 0) || - (fileDer->length != peer_cert->derCert->length) || - (XMEMCMP(peer_cert->derCert->buffer, fileDer->buffer, + (fileDer->length != peerDer->length) || + (XMEMCMP(peerDer->buffer, fileDer->buffer, fileDer->length) != 0))) { ret = WOLFSSL_FATAL_ERROR; } @@ -3055,8 +3055,9 @@ static int DecodeToX509(WOLFSSL_X509* x509, const byte* in, int len) #ifdef KEEP_PEER_CERT /* Get the kept peer certificate, owned by the SSL/TLS object. * - * The certificate is decoded from the session chain, and kept, when not - * already available on the object. + * The certificate is built from its kept encoding on first use. Otherwise it + * is decoded from the session chain, and kept, when not already available on + * the object. * * @param [in, out] ssl SSL/TLS object. * @return Kept peer certificate on success. @@ -3215,7 +3216,8 @@ static int PushCAx509Chain(WOLFSSL_CERT_MANAGER* cm, * certificate, index 0, is taken from the copy kept on the SSL/TLS object * instead; any other is left out of the stack, as it was before it had a slot. * - * @param [in] ssl SSL/TLS object. + * @param [in, out] ssl SSL/TLS object. The kept peer certificate may be + * built from its encoding. * @param [in] idx Index of the certificate in the chain. * @param [in, out] sk Stack to add the certificate to. * @param [out] pushed Certificate added to the stack, NULL when the slot @@ -3225,7 +3227,7 @@ static int PushCAx509Chain(WOLFSSL_CERT_MANAGER* cm, * @return Other negative value when the certificate cannot be decoded or * stored. */ -static int PushPeerCertToChain(const WOLFSSL* ssl, int idx, WOLFSSL_STACK* sk, +static int PushPeerCertToChain(WOLFSSL* ssl, int idx, WOLFSSL_STACK* sk, WOLFSSL_X509** pushed) { int ret = 0; @@ -3235,9 +3237,10 @@ static int PushPeerCertToChain(const WOLFSSL* ssl, int idx, WOLFSSL_STACK* sk, if (ssl->session->chain.certs[idx].length == 0) { #ifdef KEEP_PEER_CERT - if ((idx == 0) && (GetPeerCert(ssl) != NULL) && - (GetPeerCert(ssl)->issuer.sz > 0)) { - x509 = wolfSSL_X509_dup(GetPeerCert(ssl)); + WOLFSSL_X509* peer = (idx == 0) ? GetPeerCert(ssl) : NULL; + + if ((peer != NULL) && (peer->issuer.sz > 0)) { + x509 = wolfSSL_X509_dup(peer); if (x509 == NULL) { WOLFSSL_MSG("Error duplicating peer certificate"); ret = MEMORY_E; @@ -3290,13 +3293,14 @@ static int PushPeerCertToChain(const WOLFSSL* ssl, int idx, WOLFSSL_STACK* sk, * For a verified chain the CA certificates known for the last certificate are * placed at the bottom of the stack. * - * @param [in] ssl SSL/TLS object. - * @param [in] verifiedFlag Whether to append the known CA chain. + * @param [in, out] ssl SSL/TLS object. The kept peer certificate + * may be built from its encoding. + * @param [in] verifiedFlag Whether to append the known CA chain. * @return Stack of the peer's certificates on success. * @return NULL when ssl is NULL, the session holds no chain, or a certificate * cannot be created, decoded or stored. */ -static WOLF_STACK_OF(WOLFSSL_X509)* CreatePeerCertChain(const WOLFSSL* ssl, +static WOLF_STACK_OF(WOLFSSL_X509)* CreatePeerCertChain(WOLFSSL* ssl, int verifiedFlag) { WOLFSSL_STACK* sk = NULL; diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 64c29841f44..a18e562c053 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -596,8 +596,12 @@ int test_wolfSSL_peer_cert_copy_oom(void) */ static int test_peer_cert_kept(const WOLFSSL* ssl) { - WOLFSSL_X509* peer = GetPeerCert(ssl); - return (peer != NULL) && (peer->issuer.sz > 0); +#ifdef WOLFSSL_DYNAMIC_PEER_CERT + /* The object, or only the encoding until the object is asked for. */ + return (ssl->peerCert != NULL) || (ssl->peerCertDer != NULL); +#else + return ssl->peerCert.issuer.sz > 0; +#endif } #if defined(HAVE_EX_DATA) && defined(HAVE_EX_DATA_CLEANUP_HOOKS) @@ -774,6 +778,319 @@ int test_wolfSSL_get_peer_certificate_ref(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + defined(WOLFSSL_DYNAMIC_PEER_CERT) && !defined(NO_RSA) && \ + !defined(NO_TLS) +/* Leaf certificate the verify callback was given. */ +static WOLFSSL_X509* peer_on_demand_cb_leaf = NULL; + +/* Record the leaf certificate the verify callback is given and accept it. + * + * @param [in] preverify Result of verification so far. + * @param [in] store Store context with the certificate being checked. + * @return 1 to accept the certificate. + */ +static int peer_on_demand_verify_cb(int preverify, + WOLFSSL_X509_STORE_CTX* store) +{ + (void)preverify; + if (wolfSSL_X509_STORE_CTX_get_error_depth(store) == 0) + peer_on_demand_cb_leaf = wolfSSL_X509_STORE_CTX_get_current_cert(store); + return 1; +} + +/* Check that a connection keeps only the peer certificate's encoding until + * the object is asked for, and that the object then built is the one sent. + * + * @param [in] method_c Client method. + * @param [in] method_s Server method. + * @return TEST_SUCCESS on success. + */ +static int test_peer_cert_on_demand(method_provider method_c, + method_provider method_s) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_X509* srv = NULL; + WOLFSSL_X509* peer = NULL; + const unsigned char* srvDer = NULL; + const unsigned char* der = NULL; + int srvDerSz = 0; + int derSz = 0; + + ExpectNotNull(srv = wolfSSL_X509_load_certificate_file(svrCertFile, + CERT_FILETYPE)); + ExpectNotNull(srvDer = wolfSSL_X509_get_der(srv, &srvDerSz)); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + method_c, method_s), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Only the encoding of the certificate the server sent is kept. */ + if (EXPECT_SUCCESS()) { + ExpectNull(ssl_c->peerCert); + ExpectNotNull(ssl_c->peerCertDer); + } + if (EXPECT_SUCCESS()) { + ExpectIntEQ((int)ssl_c->peerCertDer->length, srvDerSz); + ExpectBufEQ(ssl_c->peerCertDer->buffer, srvDer, srvDerSz); + } +#if defined(HAVE_EX_DATA) && !defined(NO_FILESYSTEM) && \ + defined(WOLFSSL_PEM_TO_DER) + /* Comparing with a file uses the encoding and builds nothing. */ + ExpectIntEQ(wolfSSL_cmp_peer_cert_to_file(ssl_c, svrCertFile), 0); + if (EXPECT_SUCCESS()) { + ExpectNull(ssl_c->peerCert); + } +#endif +#ifdef TEST_PEER_CERT_COPY_OOM + /* Out of memory, no object is returned and the encoding stays. */ + (void)wolfSSL_GetAllocators(&peer_copy_prev_malloc, + &peer_copy_prev_free, &peer_copy_prev_realloc); + ExpectIntEQ(wolfSSL_SetAllocators(peer_copy_fail_malloc, + peer_copy_fail_free, peer_copy_fail_realloc), 0); + peer_copy_fail_alloc = 1; + peer = wolfSSL_get_peer_certificate(ssl_c); + peer_copy_fail_alloc = 0; + (void)wolfSSL_SetAllocators(peer_copy_prev_malloc, peer_copy_prev_free, + peer_copy_prev_realloc); + ExpectNull(peer); + wolfSSL_X509_free(peer); + peer = NULL; + if (EXPECT_SUCCESS()) { + ExpectNull(ssl_c->peerCert); + ExpectNotNull(ssl_c->peerCertDer); + } +#endif + + /* Asking builds the object, which takes the place of the encoding. */ + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_c)); + if (EXPECT_SUCCESS()) { + ExpectPtrEq(peer, ssl_c->peerCert); + ExpectNull(ssl_c->peerCertDer); + } + ExpectNotNull(der = wolfSSL_X509_get_der(peer, &derSz)); + ExpectIntEQ(derSz, srvDerSz); + ExpectBufEQ(der, srvDer, srvDerSz); + ExpectIntEQ(wolfSSL_X509_NAME_cmp(wolfSSL_X509_get_subject_name(peer), + wolfSSL_X509_get_subject_name(srv)), 0); + ExpectIntEQ(wolfSSL_X509_NAME_cmp(wolfSSL_X509_get_issuer_name(peer), + wolfSSL_X509_get_issuer_name(srv)), 0); + ExpectIntEQ(wolfSSL_X509_NAME_entry_count( + wolfSSL_X509_get_subject_name(peer)), + wolfSSL_X509_NAME_entry_count(wolfSSL_X509_get_subject_name(srv))); + ExpectIntEQ(wolfSSL_X509_cmp(peer, srv), 0); + wolfSSL_X509_free(peer); + peer = NULL; + + /* Reusing the object for a new connection drops the encoding. */ + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ExpectNull(ssl_c->peerCert); + ExpectNull(ssl_c->peerCertDer); + } + + /* With a verify callback the object is built from the decoded leaf for + * the callback, and that object is the one kept. */ + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); +#ifndef NO_DH + SetDH(ssl_s); +#endif + test_memio_clear_buffer(&test_ctx, 0); + test_memio_clear_buffer(&test_ctx, 1); + peer_on_demand_cb_leaf = NULL; + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, peer_on_demand_verify_cb); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectNotNull(ssl_c->peerCert); + ExpectNull(ssl_c->peerCertDer); + ExpectPtrEq(peer_on_demand_cb_leaf, ssl_c->peerCert); + } + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_c)); + if (EXPECT_SUCCESS()) { + ExpectPtrEq(peer, ssl_c->peerCert); + } + ExpectIntEQ(wolfSSL_X509_cmp(peer, srv), 0); + peer_on_demand_cb_leaf = NULL; + + wolfSSL_X509_free(peer); + wolfSSL_X509_free(srv); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + + return EXPECT_RESULT(); +} +#endif + +/* Test that only the encoding of the peer's certificate is kept until the + * certificate object is asked for. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_peer_cert_on_demand(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + defined(WOLFSSL_DYNAMIC_PEER_CERT) && !defined(NO_RSA) && \ + !defined(NO_TLS) +#ifdef WOLFSSL_TLS13 + ExpectIntEQ(test_peer_cert_on_demand(wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method), TEST_SUCCESS); +#endif +#ifndef WOLFSSL_NO_TLS12 + ExpectIntEQ(test_peer_cert_on_demand(wolfTLSv1_2_client_method, + wolfTLSv1_2_server_method), TEST_SUCCESS); +#endif +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(OPENSSL_EXTRA) && \ + defined(KEEP_PEER_CERT) && defined(WOLFSSL_CHAIN_VERIFY_CB) && \ + !defined(NO_RSA) && !defined(NO_TLS) && !defined(NO_SHA256) && \ + !defined(NO_ASN_TIME) && defined(WOLFSSL_CERT_GEN) && \ + defined(WOLFSSL_CERT_EXT) && defined(USE_CERT_BUFFERS_2048) +#define TEST_PEER_CERT_DECODE_ERR + +/* Accept the peer's chain, whatever wolfSSL makes of its content. + * + * @return 0 to accept the chain. + */ +static int peer_cert_decode_err_chain_cb(WOLFSSL* ssl, + const WOLFSSL_BUFFER_INFO* certs, int certsSz, void* ctx) +{ + (void)ssl; + (void)certs; + (void)certsSz; + (void)ctx; + return 0; +} +#endif + +/* Test that a peer certificate accepted despite an error decoding it is still + * kept as an object. + * + * The leaf has keyCertSign without being a CA, which parsing reports however + * it is done, and the chain verify callback accepts it. Decoding the kept + * encoding again would fail, so the decoded leaf is kept as the object. + * + * @return TEST_SUCCESS on success. + */ +int test_wolfSSL_peer_cert_kept_decode_err(void) +{ + EXPECT_DECLS; +#ifdef TEST_PEER_CERT_DECODE_ERR + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + WOLFSSL_X509* peer = NULL; + const unsigned char* der = NULL; + byte* leafDer = NULL; + int leafDerSz = 0; + int derSz = 0; + RsaKey key; + WC_RNG rng; + Cert cert; + word32 idx = 0; + int keyInit = 0; + int rngInit = 0; + + ExpectNotNull(leafDer = (byte*)XMALLOC(FOURK_BUF, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectIntEQ(wc_InitRng(&rng), 0); + if (EXPECT_SUCCESS()) + rngInit = 1; + ExpectIntEQ(wc_InitRsaKey(&key, NULL), 0); + if (EXPECT_SUCCESS()) + keyInit = 1; + ExpectIntEQ(wc_RsaPrivateKeyDecode(server_key_der_2048, &idx, &key, + (word32)sizeof_server_key_der_2048), 0); + + /* Self-signed leaf with keyCertSign but not a CA. */ + ExpectIntEQ(wc_InitCert(&cert), 0); + if (EXPECT_SUCCESS()) { + cert.isCA = 0; + cert.sigType = CTC_SHA256wRSA; + XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.org, "wolfSSL_test", CTC_NAME_SIZE - 1); + XSTRNCPY(cert.subject.commonName, "keyCertSign leaf", + CTC_NAME_SIZE - 1); + } + ExpectIntEQ(wc_SetKeyUsage(&cert, + "digitalSignature,keyEncipherment,keyCertSign"), 0); + ExpectIntGT(wc_MakeCert(&cert, leafDer, FOURK_BUF, &key, NULL, &rng), 0); + ExpectIntGT(leafDerSz = wc_SignCert(cert.bodySz, cert.sigType, leafDer, + FOURK_BUF, &key, NULL, &rng), 0); +#ifdef WOLFSSL_CERT_GEN_CACHE + wc_SetCert_Free(&cert); +#endif + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); +#ifdef WOLFSSL_TLS13 + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); +#else + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); +#endif + ExpectIntEQ(wolfSSL_use_certificate_buffer(ssl_s, leafDer, leafDerSz, + WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_buffer(ssl_s, server_key_der_2048, + sizeof_server_key_der_2048, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(wolfSSL_SetChainVerifyCb(ssl_c, + peer_cert_decode_err_chain_cb), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + +#ifdef WOLFSSL_DYNAMIC_PEER_CERT + #ifndef ALLOW_INVALID_CERTSIGN + /* The decoded leaf is kept, not only its encoding. */ + if (EXPECT_SUCCESS()) { + ExpectNotNull(ssl_c->peerCert); + ExpectNull(ssl_c->peerCertDer); + } + #endif + /* The kept object is handed out with a reference. */ + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_c)); + ExpectNotNull(der = wolfSSL_X509_get_der(peer, &derSz)); +#else + /* A copy is handed out, made by decoding the certificate again, which + * fails for this one, so check the kept object itself. */ + if (EXPECT_SUCCESS()) { + ExpectIntGT(ssl_c->peerCert.issuer.sz, 0); + ExpectNotNull(ssl_c->peerCert.derCert); + } + if (EXPECT_SUCCESS()) { + der = ssl_c->peerCert.derCert->buffer; + derSz = (int)ssl_c->peerCert.derCert->length; + } +#endif + ExpectIntEQ(derSz, leafDerSz); + ExpectBufEQ(der, leafDer, leafDerSz); + + wolfSSL_X509_free(peer); + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + if (keyInit) + wc_FreeRsaKey(&key); + if (rngInit) + wc_FreeRng(&rng); + XFREE(leafDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + /* Guarded to match its only caller, test_wolfSSL_CTX_set_client_cert_cb(), * which needs OPENSSL_EXTRA for the ctx->CBClientCert field. */ #if defined(WOLFSSL_CERT_SETUP_CB) && defined(OPENSSL_EXTRA) && \ diff --git a/tests/api/test_ssl_cert.h b/tests/api/test_ssl_cert.h index 1f802590194..9b99a8036b7 100644 --- a/tests/api/test_ssl_cert.h +++ b/tests/api/test_ssl_cert.h @@ -43,6 +43,8 @@ int test_wolfSSL_get_chain_cert_pem(void); int test_wolfSSL_cmp_peer_cert_to_file(void); int test_wolfSSL_peer_cert_copy_oom(void); int test_wolfSSL_get_peer_certificate_ref(void); +int test_wolfSSL_peer_cert_on_demand(void); +int test_wolfSSL_peer_cert_kept_decode_err(void); int test_wolfSSL_CTX_set_client_cert_cb(void); int test_wolfSSL_CTX_set_cert_cb(void); int test_wolfSSL_cert_setup_cb_ret(void); @@ -92,6 +94,8 @@ int test_wolfSSL_SESSION_get0_peer_full_chain(void); TEST_DECL_GROUP("ssl_cert", test_wolfSSL_cmp_peer_cert_to_file), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_peer_cert_copy_oom), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_get_peer_certificate_ref), \ + TEST_DECL_GROUP("ssl_cert", test_wolfSSL_peer_cert_on_demand), \ + TEST_DECL_GROUP("ssl_cert", test_wolfSSL_peer_cert_kept_decode_err), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_CTX_set_client_cert_cb), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_CTX_set_cert_cb), \ TEST_DECL_GROUP("ssl_cert", test_wolfSSL_cert_setup_cb_ret), \ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 74982e5655e..be105f451fa 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -12945,6 +12945,7 @@ int test_tls13_ticket_peer_cert_reverify(void) * fallback that may see stale cache state. */ #ifdef WOLFSSL_DYNAMIC_PEER_CERT ExpectNull(ssl_s->peerCert); + ExpectNull(ssl_s->peerCertDer); #else ExpectIntEQ(ssl_s->peerCert.issuer.sz, 0); #endif diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 623df4f7569..94d4566ecbc 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3035,7 +3035,10 @@ typedef struct ProcPeerCertArgs { word16 csrAcked:1; /* survives the staple checks clearing status_request */ #endif #ifdef KEEP_PEER_CERT - word16 peerCertSet:1; /* leaf already copied into ssl->peerCert */ + word16 peerCertSet:1; /* leaf already kept in ssl->peerCert, or only its + * encoding in ssl->peerCertDer */ + word16 leafDecodeErr:1; /* parsing the leaf returned an error, so a later + * decode of it may not give the same result */ #endif } ProcPeerCertArgs; WOLFSSL_LOCAL int DoVerifyCallback(WOLFSSL_CERT_MANAGER* cm, WOLFSSL* ssl, @@ -7319,9 +7322,14 @@ struct WOLFSSL { #endif #ifdef KEEP_PEER_CERT #ifdef WOLFSSL_DYNAMIC_PEER_CERT - WOLFSSL_X509* peerCert; /* X509 peer cert, NULL when none. - Holds one reference; replaced, - never reused, by a new cert */ + WOLFSSL_X509* peerCert; /* X509 peer cert, NULL when none + or not built yet from + peerCertDer. Holds one reference; + replaced, never reused, by a new + cert */ + DerBuffer* peerCertDer; /* Encoding of the peer cert until + peerCert is built from it, then + NULL. See GetPeerCert() */ #else WOLFSSL_X509 peerCert; /* X509 peer cert */ #endif @@ -8218,19 +8226,31 @@ WOLFSSL_LOCAL enum wc_HashType HashAlgoToType(int hashAlgo); DecodedCert* dCert); #endif #ifdef KEEP_PEER_CERT - /* The kept peer certificate object. NULL only with - * WOLFSSL_DYNAMIC_PEER_CERT, when no certificate is kept. */ + #ifdef WOLFSSL_DYNAMIC_PEER_CERT + WOLFSSL_LOCAL WOLFSSL_X509* GetPeerCert(WOLFSSL* ssl); + #else + /* The kept peer certificate object. */ static WC_INLINE WOLFSSL_X509* GetPeerCert(const WOLFSSL* ssl) { - #ifdef WOLFSSL_DYNAMIC_PEER_CERT - return ssl->peerCert; - #else return (WOLFSSL_X509*)&ssl->peerCert; - #endif + } + #endif + /* The encoding of the kept peer certificate, without building the X509 + * object. NULL when no certificate is kept. */ + static WC_INLINE const DerBuffer* GetPeerCertDer(const WOLFSSL* ssl) + { + #ifdef WOLFSSL_DYNAMIC_PEER_CERT + if (ssl->peerCertDer != NULL) + return ssl->peerCertDer; + return (ssl->peerCert != NULL) ? ssl->peerCert->derCert : NULL; + #else + return ssl->peerCert.derCert; + #endif } WOLFSSL_LOCAL void FreePeerCert(WOLFSSL* ssl); #ifndef NO_ASN WOLFSSL_LOCAL int SetPeerCert(WOLFSSL* ssl, DecodedCert* dCert); + WOLFSSL_LOCAL int SetPeerCertDer(WOLFSSL* ssl, DecodedCert* dCert); #endif #endif #endif From bbd0042db61bad756911ec9bc4d143cd24b04dc8 Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Thu, 5 Nov 2026 18:20:30 +0000 Subject: [PATCH 2/4] Name the allocator API's guard where the on-demand test uses it check-api-guards.py cannot see that TEST_PEER_CERT_COPY_OOM is only defined with USE_WOLFSSL_MEMORY, so the out-of-memory part of test_wolfSSL_peer_cert_on_demand now names it next to the macro. --- tests/api/test_ssl_cert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index a18e562c053..66ad813e25e 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -846,7 +846,7 @@ static int test_peer_cert_on_demand(method_provider method_c, ExpectNull(ssl_c->peerCert); } #endif -#ifdef TEST_PEER_CERT_COPY_OOM +#if defined(TEST_PEER_CERT_COPY_OOM) && defined(USE_WOLFSSL_MEMORY) /* Out of memory, no object is returned and the encoding stays. */ (void)wolfSSL_GetAllocators(&peer_copy_prev_malloc, &peer_copy_prev_free, &peer_copy_prev_realloc); From 33da09ee99c9fc947f59896129adfefcb179e50d Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Thu, 5 Nov 2026 18:22:09 +0000 Subject: [PATCH 3/4] Test clearing a connection that kept only the peer encoding test_wolfSSL_peer_cert_on_demand cleared the connection only after the certificate object had been built from the encoding. A further handshake now clears it while only the encoding is kept, and checks that both the object and the encoding are gone. --- tests/api/test_ssl_cert.c | 54 +++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 66ad813e25e..cd76039f228 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -799,6 +799,32 @@ static int peer_on_demand_verify_cb(int preverify, return 1; } +/* Give the client a new server to handshake with. + * + * @param [in] ctx_s Server context. + * @param [in, out] ssl_s Server object, replaced by a new one. + * @param [in, out] test_ctx Memory I/O, emptied. + * @return TEST_SUCCESS on success. + */ +static int test_peer_cert_on_demand_new_server(WOLFSSL_CTX* ctx_s, + WOLFSSL** ssl_s, struct test_memio_ctx* test_ctx) +{ + EXPECT_DECLS; + + wolfSSL_free(*ssl_s); + *ssl_s = NULL; + ExpectNotNull(*ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOWriteCtx(*ssl_s, test_ctx); + wolfSSL_SetIOReadCtx(*ssl_s, test_ctx); +#ifndef NO_DH + SetDH(*ssl_s); +#endif + test_memio_clear_buffer(test_ctx, 0); + test_memio_clear_buffer(test_ctx, 1); + + return EXPECT_RESULT(); +} + /* Check that a connection keeps only the peer certificate's encoding until * the object is asked for, and that the object then built is the one sent. * @@ -886,7 +912,21 @@ static int test_peer_cert_on_demand(method_provider method_c, wolfSSL_X509_free(peer); peer = NULL; - /* Reusing the object for a new connection drops the encoding. */ + /* Reusing the object for a new connection drops the built object. */ + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ExpectNull(ssl_c->peerCert); + ExpectNull(ssl_c->peerCertDer); + } + + /* It also drops the encoding when no object was built from it. */ + ExpectIntEQ(test_peer_cert_on_demand_new_server(ctx_s, &ssl_s, &test_ctx), + TEST_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectNull(ssl_c->peerCert); + ExpectNotNull(ssl_c->peerCertDer); + } ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); if (EXPECT_SUCCESS()) { ExpectNull(ssl_c->peerCert); @@ -895,16 +935,8 @@ static int test_peer_cert_on_demand(method_provider method_c, /* With a verify callback the object is built from the decoded leaf for * the callback, and that object is the one kept. */ - wolfSSL_free(ssl_s); - ssl_s = NULL; - ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); - wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); - wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); -#ifndef NO_DH - SetDH(ssl_s); -#endif - test_memio_clear_buffer(&test_ctx, 0); - test_memio_clear_buffer(&test_ctx, 1); + ExpectIntEQ(test_peer_cert_on_demand_new_server(ctx_s, &ssl_s, &test_ctx), + TEST_SUCCESS); peer_on_demand_cb_leaf = NULL; wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_PEER, peer_on_demand_verify_cb); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); From cf64cd642a6d43f878cc7eb10949f2cce65b70cb Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Thu, 5 Nov 2026 18:51:08 +0000 Subject: [PATCH 4/4] Free the generated Cert only after it was initialized In test_wolfSSL_peer_cert_kept_decode_err, wc_InitCert() is skipped when an earlier expectation fails, and with WOLFSSL_CERT_GEN_CACHE wc_SetCert_Free() would then free an uninitialized Cert. A certInit flag now guards it, as keyInit and rngInit do for the key and RNG. --- tests/api/test_ssl_cert.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index cd76039f228..362f674a44a 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -1035,6 +1035,7 @@ int test_wolfSSL_peer_cert_kept_decode_err(void) word32 idx = 0; int keyInit = 0; int rngInit = 0; + int certInit = 0; ExpectNotNull(leafDer = (byte*)XMALLOC(FOURK_BUF, NULL, DYNAMIC_TYPE_TMP_BUFFER)); @@ -1050,6 +1051,7 @@ int test_wolfSSL_peer_cert_kept_decode_err(void) /* Self-signed leaf with keyCertSign but not a CA. */ ExpectIntEQ(wc_InitCert(&cert), 0); if (EXPECT_SUCCESS()) { + certInit = 1; cert.isCA = 0; cert.sigType = CTC_SHA256wRSA; XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE - 1); @@ -1063,8 +1065,10 @@ int test_wolfSSL_peer_cert_kept_decode_err(void) ExpectIntGT(leafDerSz = wc_SignCert(cert.bodySz, cert.sigType, leafDer, FOURK_BUF, &key, NULL, &rng), 0); #ifdef WOLFSSL_CERT_GEN_CACHE - wc_SetCert_Free(&cert); + if (certInit) + wc_SetCert_Free(&cert); #endif + (void)certInit; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); #ifdef WOLFSSL_TLS13