From ef6f406962d056a9948768f91b610acb199a52f1 Mon Sep 17 00:00:00 2001 From: Roy Carter Date: Fri, 8 May 2026 19:29:35 +0300 Subject: [PATCH 1/2] Implement needed wolfSSL_SSL_CIPHER_find - find cipher by 2 bytes in wired like openssl wolfSSL_sk_SSL_CIPHER_delete - remove cipher at given index SSL_clear_chain_certs --- src/ssl.c | 43 ++++++++++++++++++++++++ src/ssl_load.c | 35 ++++++++++++++++++++ src/ssl_sk.c | 57 ++++++++++++++++++++++++++++++++ tests/api.c | 77 +++++++++++++++++++++++++++++++++++++++++++ wolfssl/openssl/ssl.h | 3 ++ wolfssl/ssl.h | 8 +++++ 6 files changed, 223 insertions(+) diff --git a/src/ssl.c b/src/ssl.c index 1bf913b18f1..7b21b5ff892 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -10992,6 +10992,49 @@ const WOLFSSL_CIPHER* wolfSSL_get_cipher_by_value(word16 value) return cipher; } +#if defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) || \ + defined(WOLFSSL_HAPROXY) || defined(OPENSSL_EXTRA) || defined(HAVE_LIGHTY) +/* Locate a cipher in the SSL's cipher list by 2-byte wire-format suite id. + * + * Mirrors OpenSSL's SSL_CIPHER_find(ssl, ptr). The two bytes pointed to by + * ptr are the on-the-wire cipher suite identifier (ptr[0] is the high byte, + * ptr[1] is the low byte). Lookup is restricted to ciphers in ssl's cipher + * list, matching OpenSSL semantics. + * + * Returned pointer references storage owned by the SSL object's internal + * cipher list; callers must not free it. It remains valid until SSL_free. + * + * @param [in] ssl SSL/TLS object whose cipher list is searched. + * @param [in] ptr Pointer to a 2-byte cipher suite identifier. + * @return Matching cipher on success. + * @return NULL if ssl or ptr is NULL, or no cipher matches. + */ +const WOLFSSL_CIPHER* wolfSSL_SSL_CIPHER_find(WOLFSSL* ssl, + const unsigned char* ptr) +{ + WOLF_STACK_OF(WOLFSSL_CIPHER)* sk; + WOLFSSL_STACK* node; + + WOLFSSL_ENTER("wolfSSL_SSL_CIPHER_find"); + + if (ssl == NULL || ptr == NULL) + return NULL; + + sk = wolfSSL_get_ciphers_compat(ssl); + if (sk == NULL) + return NULL; + + for (node = sk; node != NULL; node = node->next) { + if (node->data.cipher.cipherSuite0 == ptr[0] && + node->data.cipher.cipherSuite == ptr[1]) { + return &node->data.cipher; + } + } + + return NULL; +} +#endif + #if defined(HAVE_ECC) || defined(HAVE_CURVE25519) || defined(HAVE_CURVE448) || \ !defined(NO_DH) || (defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM)) diff --git a/src/ssl_load.c b/src/ssl_load.c index f71ccf3467e..f75b24bfb2b 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -5270,6 +5270,41 @@ int wolfSSL_add1_chain_cert(WOLFSSL* ssl, WOLFSSL_X509* x509) return ret; } + +/* Clear all extra chain certificates set on the SSL object. + * + * Mirrors OpenSSL's SSL_clear_chain_certs(): frees any chain certificates + * previously added via SSL_add0_chain_cert / SSL_add1_chain_cert (or set via + * SSL_set0_chain / SSL_set1_chain) on this SSL. Does not affect the leaf + * certificate, the private key, or chain certificates inherited from the + * WOLFSSL_CTX. + * + * @param [in, out] ssl SSL object. + * @return 1 on success. + * @return 0 when ssl is NULL. + */ +int wolfSSL_clear_chain_certs(WOLFSSL* ssl) +{ + WOLFSSL_ENTER("wolfSSL_clear_chain_certs"); + + if (ssl == NULL) + return 0; + + /* Free the DER-encoded chain buffer if this SSL owns it. */ + if (ssl->buffers.weOwnCertChain) { + FreeDer(&ssl->buffers.certChain); + ssl->buffers.weOwnCertChain = 0; + } + ssl->buffers.certChain = NULL; + + /* Free the X509 stack used to track ownership of added chain certs. */ + if (ssl->ourCertChain != NULL) { + wolfSSL_sk_X509_pop_free(ssl->ourCertChain, NULL); + ssl->ourCertChain = NULL; + } + + return 1; +} #endif /* KEEP_OUR_CERT */ #endif /* OPENSSL_EXTRA, HAVE_LIGHTY, WOLFSSL_MYSQL_COMPATIBLE, HAVE_STUNNEL, WOLFSSL_NGINX, HAVE_POCO_LIB, WOLFSSL_HAPROXY */ diff --git a/src/ssl_sk.c b/src/ssl_sk.c index e362af9e12b..45de00eadba 100644 --- a/src/ssl_sk.c +++ b/src/ssl_sk.c @@ -1205,6 +1205,63 @@ void wolfSSL_sk_SSL_CIPHER_free(WOLF_STACK_OF(WOLFSSL_CIPHER)* sk) WOLFSSL_ENTER("wolfSSL_sk_SSL_CIPHER_free"); wolfSSL_sk_free(sk); } + +/* Remove the cipher at the given index from the stack. + * + * Mirrors OpenSSL's sk_SSL_CIPHER_delete(sk, idx). The node is unlinked and + * freed. Because wolfSSL stores WOLFSSL_CIPHER inline within the stack node, + * the returned pointer is a heap-allocated copy of the removed cipher's + * value so that it remains valid after the underlying node is freed. + * + * Ownership of the returned pointer is transferred to the caller; free with + * XFREE(..., NULL, DYNAMIC_TYPE_OPENSSL) when no longer needed. NULL is + * returned when sk is NULL, idx is out of range, or allocation fails. + * + * @param [in,out] sk Stack of ciphers. + * @param [in] idx Index of cipher to remove. + * @return Heap copy of removed cipher on success. + * @return NULL on failure. + */ +WOLFSSL_CIPHER* wolfSSL_sk_SSL_CIPHER_delete( + WOLF_STACK_OF(WOLFSSL_CIPHER)* sk, int idx) +{ + WOLFSSL_CIPHER* ret = NULL; + WOLFSSL_STACK* node; + int num; + + WOLFSSL_ENTER("wolfSSL_sk_SSL_CIPHER_delete"); + + if (sk == NULL || idx < 0) + return NULL; + + num = wolfSSL_sk_SSL_CIPHER_num(sk); + if (idx >= num) + return NULL; + + /* Walk to the node so we can capture its inline cipher value before the + * pop_node call frees the underlying memory. */ + node = sk; + { + int i; + for (i = 0; i < idx && node != NULL; i++) + node = node->next; + } + if (node == NULL) + return NULL; + + ret = (WOLFSSL_CIPHER*)XMALLOC(sizeof(WOLFSSL_CIPHER), NULL, + DYNAMIC_TYPE_OPENSSL); + if (ret == NULL) + return NULL; + + *ret = node->data.cipher; + + /* pop_node returns NULL for STACK_TYPE_CIPHER (data is static/inline), + * but it still performs the unlink and node free that we need. */ + (void)wolfSSL_sk_pop_node(sk, idx); + + return ret; +} #endif /* OPENSSL_ALL || OPENSSL_EXTRA */ /******************************************************************************* diff --git a/tests/api.c b/tests/api.c index ecbcc4eed3e..b3c450b0637 100644 --- a/tests/api.c +++ b/tests/api.c @@ -17028,6 +17028,26 @@ static int test_wolfSSL_sk_SSL_CIPHER(void) /* error case because connection has not been established yet */ ExpectIntEQ(sk_SSL_CIPHER_find(sk, SSL_get_current_cipher(ssl)), -1); + + /* Exercise sk_SSL_CIPHER_delete on the duplicated stack so we don't + * disturb the SSL object's internal cipher list. */ + { + int dupNum = sk_SSL_CIPHER_num(dupSk); + if (dupNum > 0) { + SSL_CIPHER* removed = NULL; + + /* Out-of-range and negative idx should return NULL. */ + ExpectNull(sk_SSL_CIPHER_delete(dupSk, -1)); + ExpectNull(sk_SSL_CIPHER_delete(dupSk, dupNum)); + ExpectNull(sk_SSL_CIPHER_delete(NULL, 0)); + + /* Delete the head element and verify count decreased. */ + ExpectNotNull(removed = sk_SSL_CIPHER_delete(dupSk, 0)); + ExpectIntEQ(sk_SSL_CIPHER_num(dupSk), dupNum - 1); + XFREE(removed, NULL, DYNAMIC_TYPE_OPENSSL); + } + } + sk_SSL_CIPHER_free(dupSk); /* sk is pointer to internal struct that should be free'd in SSL_free */ @@ -17039,6 +17059,62 @@ static int test_wolfSSL_sk_SSL_CIPHER(void) return EXPECT_RESULT(); } +static int test_wolfSSL_SSL_CIPHER_find(void) +{ + EXPECT_DECLS; +#if (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA) || \ + defined(WOLFSSL_NGINX) || defined(WOLFSSL_HAPROXY) || \ + defined(HAVE_LIGHTY)) && \ + !defined(NO_CERTS) && !defined(NO_TLS) && !defined(NO_FILESYSTEM) && \ + !defined(NO_RSA) && \ + (!defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER)) + SSL* ssl = NULL; + SSL_CTX* ctx = NULL; + STACK_OF(SSL_CIPHER)* sk = NULL; + const SSL_CIPHER* found = NULL; + unsigned char id[2]; + const unsigned char bogus[2] = { 0xFF, 0xFF }; + +#ifndef NO_WOLFSSL_SERVER + ExpectNotNull(ctx = SSL_CTX_new(wolfSSLv23_server_method())); +#else + ExpectNotNull(ctx = SSL_CTX_new(wolfSSLv23_client_method())); +#endif + ExpectTrue(SSL_CTX_use_certificate_file(ctx, svrCertFile, SSL_FILETYPE_PEM)); + ExpectTrue(SSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, SSL_FILETYPE_PEM)); + ExpectNotNull(ssl = SSL_new(ctx)); + ExpectNotNull(sk = SSL_get_ciphers(ssl)); + ExpectIntGT(sk_SSL_CIPHER_num(sk), 0); + + /* Pick the first cipher in ssl's list and round-trip via SSL_CIPHER_find. */ + if (sk != NULL && sk_SSL_CIPHER_num(sk) > 0) { + const WOLFSSL_CIPHER* first = sk_SSL_CIPHER_value(sk, 0); + ExpectNotNull(first); + if (first != NULL) { + id[0] = first->cipherSuite0; + id[1] = first->cipherSuite; + + ExpectNotNull(found = SSL_CIPHER_find(ssl, id)); + if (found != NULL) { + ExpectIntEQ(found->cipherSuite0, id[0]); + ExpectIntEQ(found->cipherSuite, id[1]); + } + } + } + + /* NULL arg handling. */ + ExpectNull(SSL_CIPHER_find(NULL, id)); + ExpectNull(SSL_CIPHER_find(ssl, NULL)); + + /* Suite not in ssl's cipher list. */ + ExpectNull(SSL_CIPHER_find(ssl, bogus)); + + SSL_free(ssl); + SSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_set1_curves_list(void) { EXPECT_DECLS; @@ -40180,6 +40256,7 @@ TEST_CASE testCases[] = { #endif TEST_DECL(test_wolfSSL_configure_args), TEST_DECL(test_wolfSSL_sk_SSL_CIPHER), + TEST_DECL(test_wolfSSL_SSL_CIPHER_find), TEST_DECL(test_wolfSSL_set1_curves_list), TEST_DECL(test_wolfSSL_curves_mismatch), TEST_DECL(test_wolfSSL_set1_sigalgs_list), diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 063500675e1..5e688a44dc5 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -1367,6 +1367,8 @@ typedef WOLFSSL_SRTP_PROTECTION_PROFILE SRTP_PROTECTION_PROFILE; #define sk_SSL_CIPHER_dup wolfSSL_shallow_sk_dup #define sk_SSL_CIPHER_free wolfSSL_sk_SSL_CIPHER_free #define sk_SSL_CIPHER_find wolfSSL_sk_SSL_CIPHER_find +#define sk_SSL_CIPHER_delete wolfSSL_sk_SSL_CIPHER_delete +#define SSL_CIPHER_find wolfSSL_SSL_CIPHER_find #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) #define SSL_get0_peername wolfSSL_get0_peername @@ -1383,6 +1385,7 @@ typedef WOLFSSL_SRTP_PROTECTION_PROFILE SRTP_PROTECTION_PROFILE; #define SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS 83 #define SSL_CTX_clear_chain_certs(ctx) SSL_CTX_set0_chain(ctx,NULL) +#define SSL_clear_chain_certs wolfSSL_clear_chain_certs #define d2i_RSAPrivateKey_bio wolfSSL_d2i_RSAPrivateKey_bio #define SSL_CTX_use_RSAPrivateKey wolfSSL_CTX_use_RSAPrivateKey #define d2i_PrivateKey_bio wolfSSL_d2i_PrivateKey_bio diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index fad6fe6ed3f..c1156a2f69e 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -2985,6 +2985,11 @@ WOLFSSL_API int wolfSSL_CIPHER_get_digest_nid(const WOLFSSL_CIPHER* cipher); WOLFSSL_API int wolfSSL_CIPHER_get_kx_nid(const WOLFSSL_CIPHER* cipher); WOLFSSL_API int wolfSSL_CIPHER_is_aead(const WOLFSSL_CIPHER* cipher); WOLFSSL_API const WOLFSSL_CIPHER* wolfSSL_get_cipher_by_value(word16 value); +#if defined(OPENSSL_ALL) || defined(WOLFSSL_NGINX) || \ + defined(WOLFSSL_HAPROXY) || defined(OPENSSL_EXTRA) || defined(HAVE_LIGHTY) +WOLFSSL_API const WOLFSSL_CIPHER* wolfSSL_SSL_CIPHER_find(WOLFSSL* ssl, + const unsigned char* ptr); +#endif WOLFSSL_API const char* wolfSSL_SESSION_CIPHER_get_name(const WOLFSSL_SESSION* session); WOLFSSL_API const char* wolfSSL_get_cipher(WOLFSSL* ssl); WOLFSSL_API void wolfSSL_sk_CIPHER_free(WOLF_STACK_OF(WOLFSSL_CIPHER)* sk); @@ -5403,6 +5408,7 @@ WOLFSSL_API int wolfSSL_CTX_add0_chain_cert(WOLFSSL_CTX* ctx, WOLFSSL_X509* x509 WOLFSSL_API int wolfSSL_CTX_add1_chain_cert(WOLFSSL_CTX* ctx, WOLFSSL_X509* x509); WOLFSSL_API int wolfSSL_add0_chain_cert(WOLFSSL* ssl, WOLFSSL_X509* x509); WOLFSSL_API int wolfSSL_add1_chain_cert(WOLFSSL* ssl, WOLFSSL_X509* x509); +WOLFSSL_API int wolfSSL_clear_chain_certs(WOLFSSL* ssl); WOLFSSL_API int wolfSSL_BIO_read_filename(WOLFSSL_BIO *b, const char *name); /* These are to be merged shortly */ WOLFSSL_API void wolfSSL_set_verify_depth(WOLFSSL *ssl,int depth); @@ -6054,6 +6060,8 @@ WOLFSSL_API int wolfSSL_sk_SSL_CIPHER_num(const WOLF_STACK_OF(WOLFSSL_CIPHER)* p WOLFSSL_API int wolfSSL_sk_SSL_CIPHER_find( WOLF_STACK_OF(WOLFSSL_CIPHER)* sk, const WOLFSSL_CIPHER* toFind); WOLFSSL_API void wolfSSL_sk_SSL_CIPHER_free(WOLF_STACK_OF(WOLFSSL_CIPHER)* sk); +WOLFSSL_API WOLFSSL_CIPHER* wolfSSL_sk_SSL_CIPHER_delete( + WOLF_STACK_OF(WOLFSSL_CIPHER)* sk, int idx); WOLFSSL_API int wolfSSL_sk_SSL_COMP_zero(WOLFSSL_STACK* st); WOLFSSL_API int wolfSSL_sk_SSL_COMP_num(WOLF_STACK_OF(WOLFSSL_COMP)* sk); WOLFSSL_API WOLFSSL_CIPHER* wolfSSL_sk_SSL_CIPHER_value(WOLFSSL_STACK* sk, int i); From 3e105d708026f9f43a273a70a8ef75e736a1c92b Mon Sep 17 00:00:00 2001 From: Roy Carter Date: Fri, 22 May 2026 19:48:31 +0300 Subject: [PATCH 2/2] Remove self document + create a test --- src/ssl_sk.c | 9 ------ tests/api.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/src/ssl_sk.c b/src/ssl_sk.c index 45de00eadba..d053b519a4a 100644 --- a/src/ssl_sk.c +++ b/src/ssl_sk.c @@ -1207,15 +1207,6 @@ void wolfSSL_sk_SSL_CIPHER_free(WOLF_STACK_OF(WOLFSSL_CIPHER)* sk) } /* Remove the cipher at the given index from the stack. - * - * Mirrors OpenSSL's sk_SSL_CIPHER_delete(sk, idx). The node is unlinked and - * freed. Because wolfSSL stores WOLFSSL_CIPHER inline within the stack node, - * the returned pointer is a heap-allocated copy of the removed cipher's - * value so that it remains valid after the underlying node is freed. - * - * Ownership of the returned pointer is transferred to the caller; free with - * XFREE(..., NULL, DYNAMIC_TYPE_OPENSSL) when no longer needed. NULL is - * returned when sk is NULL, idx is out of range, or allocation fails. * * @param [in,out] sk Stack of ciphers. * @param [in] idx Index of cipher to remove. diff --git a/tests/api.c b/tests/api.c index b3c450b0637..4b9c76eb82d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3693,6 +3693,89 @@ static int test_wolfSSL_CTX_add1_chain_cert(void) return EXPECT_RESULT(); } +/* Test SSL_clear_chain_certs: must drop chain certs added via add0/add1, + * leave leaf certificate intact, and tolerate repeated calls / NULL input. */ +static int test_wolfSSL_clear_chain_certs(void) +{ + EXPECT_DECLS; +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && defined(OPENSSL_EXTRA) && \ + defined(KEEP_OUR_CERT) && !defined(NO_RSA) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL_X509* x509 = NULL; + WOLF_STACK_OF(X509)* chain = NULL; + const char* chainCerts[] = { + "./certs/intermediate/ca-int2-cert.pem", + "./certs/intermediate/ca-int-cert.pem", + NULL + }; + const char** cert; + + /* NULL arg. */ + ExpectIntEQ(SSL_clear_chain_certs(NULL), 0); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Clear on an SSL with no chain is a no-op success. */ + ExpectIntEQ(SSL_clear_chain_certs(ssl), 1); + + /* Set leaf so subsequent adds go to the chain. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file( + "./certs/intermediate/client-int-cert.pem", WOLFSSL_FILETYPE_PEM)); + ExpectIntEQ(SSL_add1_chain_cert(ssl, x509), 1); + wolfSSL_X509_free(x509); + x509 = NULL; + + for (cert = chainCerts; EXPECT_SUCCESS() && *cert != NULL; cert++) { + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file(*cert, + WOLFSSL_FILETYPE_PEM)); + ExpectIntEQ(SSL_add1_chain_cert(ssl, x509), 1); + wolfSSL_X509_free(x509); + x509 = NULL; + } + + /* Chain populated with the 2 intermediates. */ + ExpectIntEQ(SSL_get0_chain_certs(ssl, &chain), 1); + ExpectIntEQ(sk_X509_num(chain), 2); + if (ssl != NULL) { + ExpectIntEQ(ssl->buffers.certChainCnt, 2); + ExpectNotNull(ssl->buffers.certChain); + ExpectNotNull(ssl->ourCertChain); + } + + /* Clear. */ + ExpectIntEQ(SSL_clear_chain_certs(ssl), 1); + if (ssl != NULL) { + ExpectNull(ssl->buffers.certChain); + ExpectNull(ssl->ourCertChain); + ExpectIntEQ(ssl->buffers.weOwnCertChain, 0); + /* Leaf untouched. */ + ExpectNotNull(ssl->ourCert); + } + chain = NULL; + ExpectIntEQ(SSL_get0_chain_certs(ssl, &chain), 1); + ExpectIntEQ(sk_X509_num(chain), 0); + + /* Idempotent: clearing again still succeeds. */ + ExpectIntEQ(SSL_clear_chain_certs(ssl), 1); + + /* Re-adding after clear works. */ + ExpectNotNull(x509 = wolfSSL_X509_load_certificate_file( + "./certs/intermediate/ca-int2-cert.pem", WOLFSSL_FILETYPE_PEM)); + ExpectIntEQ(SSL_add1_chain_cert(ssl, x509), 1); + wolfSSL_X509_free(x509); + chain = NULL; + ExpectIntEQ(SSL_get0_chain_certs(ssl, &chain), 1); + ExpectIntEQ(sk_X509_num(chain), 1); + + SSL_free(ssl); + SSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* Test that wolfssl_add_to_chain rejects sizes that would overflow word32. * ZD #21241 */ static int test_wolfSSL_add_to_chain_overflow(void) @@ -40560,6 +40643,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_CTX_load_verify_buffer_ex), TEST_DECL(test_wolfSSL_CTX_load_verify_chain_buffer_format), TEST_DECL(test_wolfSSL_CTX_add1_chain_cert), + TEST_DECL(test_wolfSSL_clear_chain_certs), TEST_DECL(test_wolfSSL_add_to_chain_overflow), TEST_DECL(test_wolfSSL_CTX_use_certificate_chain_buffer_format), TEST_DECL(test_wolfSSL_CTX_use_certificate_chain_file_format),