diff --git a/src/ssl.c b/src/ssl.c index efbbf3074e9..c5019d85e38 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -10997,6 +10997,44 @@ 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. + * + * 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 888c5783349..b05beb3530e 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -5220,6 +5220,8 @@ int wolfSSL_add0_chain_cert(WOLFSSL* ssl, WOLFSSL_X509* x509) if (ret == 1) { /* We now own cert chain. */ ssl->buffers.weOwnCertChain = 1; + /* Account for the certificate just added to the chain. */ + ssl->buffers.certChainCnt++; /* Create a stack to put certificate into. */ if (ssl->ourCertChain == NULL) { ssl->ourCertChain = wolfSSL_sk_X509_new_null(); @@ -5270,6 +5272,42 @@ 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; + ssl->buffers.certChainCnt = 0; + + /* 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..716671bc684 100644 --- a/src/ssl_sk.c +++ b/src/ssl_sk.c @@ -1205,6 +1205,44 @@ 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. + * + * @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_CIPHER* cipher; + + WOLFSSL_ENTER("wolfSSL_sk_SSL_CIPHER_delete"); + + if (sk == NULL || idx < 0) + return NULL; + + /* Capture the inline cipher value before the pop_node call frees the + * underlying memory. */ + cipher = wolfSSL_sk_SSL_CIPHER_value(sk, idx); + if (cipher == NULL) + return NULL; + + ret = (WOLFSSL_CIPHER*)XMALLOC(sizeof(WOLFSSL_CIPHER), NULL, + DYNAMIC_TYPE_OPENSSL); + if (ret == NULL) + return NULL; + + *ret = *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 fe92bdee85e..7825e96a357 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3694,6 +3694,138 @@ 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); + /* Like OpenSSL, the chain is emptied (NULL) after a clear. */ + ExpectNull(chain); + + /* 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(); +} + +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && defined(OPENSSL_EXTRA) && \ + defined(KEEP_OUR_CERT) && !defined(NO_RSA) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_SERVER) +/* Server-side ssl_ready hook: add chain certs then clear them, so the + * handshake runs against a freshly-cleared chain state. */ +static int test_wolfSSL_clear_chain_certs_handshake_ssl_ready(WOLFSSL* ssl) +{ + EXPECT_DECLS; + WOLFSSL_X509* x509 = NULL; + + 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); + + /* Drop the chain again; connection must still complete afterwards. */ + ExpectIntEQ(SSL_clear_chain_certs(ssl), 1); + + return EXPECT_RESULT(); +} +#endif + +/* Test that a connection still completes after SSL_clear_chain_certs. */ +static int test_wolfSSL_clear_chain_certs_handshake(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_SERVER) && !defined(NO_WOLFSSL_CLIENT) + test_ssl_cbf client_cbs; + test_ssl_cbf server_cbs; + + XMEMSET(&client_cbs, 0, sizeof(client_cbs)); + XMEMSET(&server_cbs, 0, sizeof(server_cbs)); + + client_cbs.method = wolfTLS_client_method; + server_cbs.method = wolfTLS_server_method; + + server_cbs.ssl_ready = test_wolfSSL_clear_chain_certs_handshake_ssl_ready; + + /* nofail_memio runs the full handshake and a read/write exchange, so a + * successful return proves the connection completed after the clear. */ + ExpectIntEQ(test_wolfSSL_client_server_nofail_memio(&client_cbs, + &server_cbs, NULL), TEST_SUCCESS); +#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) @@ -17178,6 +17310,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 */ @@ -17189,6 +17341,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; @@ -40343,6 +40551,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), @@ -40646,6 +40855,8 @@ 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_clear_chain_certs_handshake), 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), 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 2281bb2f264..d795fea6b00 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -3010,6 +3010,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); @@ -5428,6 +5433,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); @@ -6079,6 +6085,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);