From 3114723cb20060e2021fe42dcd3404268875aeab Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 3 Sep 2026 17:47:22 +0000 Subject: [PATCH 1/4] Add chain verify callback example A TLS client whose root of trust lives outside wolfSSL. No CA is loaded into the context; a chain verify callback hands the server's certificates to a trust service on its own thread, standing in for an HSM that holds the anchors, and defers the handshake with CHAIN_VERIFY_WANT_E until the service has a verdict. Switches show the handshake failing with no callback installed and with the service rejecting the chain. Requires wolfSSL built with --enable-chain-verify-cb. --- .gitignore | 1 + tls/Makefile | 1 + tls/README.md | 91 ++++++ tls/client-tls-chainverifycb.c | 493 +++++++++++++++++++++++++++++++++ 4 files changed, 586 insertions(+) create mode 100644 tls/client-tls-chainverifycb.c diff --git a/.gitignore b/.gitignore index 145f1b5f0..9f9ce5e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -91,6 +91,7 @@ android/wolfssljni-ndk-sample/proguard-project.txt /tls/server-tls13-earlydata /tls/client-tls-bio /tls/client-tls-cacb +/tls/client-tls-chainverifycb /tls/client-tls-callback /tls/client-tls-cryptocb /tls/client-tls-ecdhe diff --git a/tls/Makefile b/tls/Makefile index 28b8060d1..51ee8ee97 100644 --- a/tls/Makefile +++ b/tls/Makefile @@ -99,6 +99,7 @@ debug: all %-threaded: CFLAGS+=-pthread %-writedup: CFLAGS+=-pthread memory-tls: CFLAGS+=-pthread +client-tls-chainverifycb: CFLAGS+=-pthread # compile tcp examples without the LIBS variable %-tcp: LIBS= diff --git a/tls/README.md b/tls/README.md index 5b0513d6d..a74de7306 100644 --- a/tls/README.md +++ b/tls/README.md @@ -119,6 +119,7 @@ into. 3. [Running](#run-ecc) 6. [Encrypted Client Hello](#ech) +7. [Chain Verify Callback](#chainverifycb) @@ -182,6 +183,9 @@ For `client-tls-writedup` and `server-tls-writedup`, it is required that wolfSSL be configured with the `--enable-writedup` flag. Remember to build and install wolfSSL after configuring it with this flag. +For `client-tls-chainverifycb`, it is required that wolfSSL be configured +with the `--enable-chain-verify-cb` flag. + ## A simple TCP client/server pair @@ -1596,3 +1600,90 @@ Expected behavior: Please contact wolfSSL at support@wolfssl.com with any questions, bug fixes, or suggested feature additions. + + +## Chain Verify Callback + +A chain verify callback replaces wolfSSL's peer certificate verification +completely, so an external root of trust - an HSM or secure element holding +the anchors - can make the trust decision instead. + +To run this example build wolfSSL with the feature: + +```sh +./configure --enable-chain-verify-cb && make && sudo make install +``` + +`client-tls-chainverifycb` is a TLS client that loads no CA certificate at +all. It installs a chain verify callback, which wolfSSL calls with the +server's certificates as raw DER once it has decoded them. wolfSSL builds no +chain, verifies no signature and checks no date or host name: the trust +decision is entirely the application's. + +Here the application is a small "trust service" on its own thread, standing +in for the HSM. The callback copies the chain, hands it to the service and +returns `CHAIN_VERIFY_WANT_E`, which suspends the handshake. The client keeps +re-entering `wolfSSL_connect()`; each time the callback is asked again, and +once the service has a verdict the handshake completes or fails. + +What to look for: + +* `wolfSSL_CTX_SetChainVerifyCb()` is the only verification-related setup. + There is no `wolfSSL_CTX_load_verify_locations()`. +* `chain_verify_cb()` verifies nothing itself. On the first call it copies the + DER buffers, which are only valid for the duration of the call, and defers. +* `service_verify()` is what the root of trust does with the chain: walk it + from the certificate nearest the anchor down to the server's own. It uses a + CertManager the SSL object knows nothing about; an HSM would use its own + store. +* The connect loop treats `CHAIN_VERIFY_WANT_E` like `WANT_READ`: something to + wait for, then call `wolfSSL_connect()` again. + +Start a server presenting this repository's `certs/server-cert.pem`, for +example the one in the wolfSSL source tree. `-d` stops it asking for a client +certificate, and it serves one connection and exits: + +```sh +cd wolfssl +./examples/server/server -d -p 11111 \ + -c ../wolfssl-examples/certs/server-cert.pem \ + -k ../wolfssl-examples/certs/server-key.pem +``` + +Then, from this directory: + +```sh +make client-tls-chainverifycb +./client-tls-chainverifycb +trust service: anchors loaded from ../certs/ca-cert.pem, running on its own thread +client: no CA loaded, chain verify callback installed +callback: 2 certificate(s) handed to the trust service, deferring +callback: no verdict yet, deferring again +callback: trust service accepted the chain +client: handshake done, TLSv1.2 +server: I hear you fa shizzle! +``` + +Two switches show the other outcomes: + +* `-n` installs no callback. With no CA loaded the handshake fails with + `ASN_NO_SIGNER_E` (-188): wolfSSL's own verification has nothing to trust, + which is exactly why the callback exists. +* `-x` makes the service reject every chain. The handshake fails with + `CHAIN_VERIFY_CB_E` (-524) and the server receives a single + `bad_certificate` alert; the reason the service gave is never sent to it. + +`-a anchor.pem` points the service at a different trust anchor, and the host +and port default to `127.0.0.1 11111`. + +wolfSSL still decodes every certificate before the callback runs, so malformed +DER fails the handshake without the callback seeing it, and it still enforces +the minimum key sizes on the server's own key. Everything about trust is the +callback's. It is consulted even under `WOLFSSL_VERIFY_NONE`. + +DTLS, raw public keys and verifying a stapled OCSP response are not supported +together with the callback: the setters refuse them, and a connection that +uses one of them anyway fails with `CHAIN_VERIFY_UNSUPPORTED_E` before the +callback is called. + +See `ChainVerifyCb` in `wolfssl/ssl.h` for the full contract. diff --git a/tls/client-tls-chainverifycb.c b/tls/client-tls-chainverifycb.c new file mode 100644 index 000000000..210620cd2 --- /dev/null +++ b/tls/client-tls-chainverifycb.c @@ -0,0 +1,493 @@ +/* client-tls-chainverifycb.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* A TLS client whose root of trust lives outside wolfSSL. + * + * No CA is loaded into the WOLFSSL_CTX. A chain verify callback hands the + * server's certificates, as raw DER, to a "trust service" that runs on its + * own thread and owns the trust anchors. It stands in for an HSM or secure + * element that holds the root of trust and does the chain verification. The + * callback defers with CHAIN_VERIFY_WANT_E while the service works, and the + * handshake resumes when wolfSSL_connect() is called again with a verdict + * available. + * + * Requires wolfSSL built with --enable-chain-verify-cb. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DEFAULT_HOST "127.0.0.1" +#define DEFAULT_PORT 11111 +#define DEFAULT_ANCHOR "../certs/ca-cert.pem" +#define MAX_CHAIN 8 +#define SERVICE_DELAY_MS 300 /* pretend the anchors are slow to reach */ +#define POLL_MS 100 /* how often the handshake is re-entered */ +#define MAXDATASIZE 4096 + +#ifdef WOLFSSL_CHAIN_VERIFY_CB + +/* The root of trust. The SSL object never sees these anchors; the service + * verifies chains against them on its own thread. One job at a time is + * enough for one connection. */ +typedef struct TrustService { + pthread_t thread; + pthread_mutex_t lock; + pthread_cond_t cond; + unsigned char* anchor; /* PEM trust anchor(s) */ + long anchorSz; + int rejectAll; /* demo switch: refuse every chain */ + int stop; + + enum { JOB_NONE, JOB_PENDING, JOB_DONE } state; + unsigned char* der[MAX_CHAIN]; + unsigned int derSz[MAX_CHAIN]; + int count; + int verdict; /* 0 accepted, otherwise a wolfSSL error */ +} TrustService; + +static void job_clear(TrustService* svc) +{ + int i; + for (i = 0; i < svc->count; i++) { + free(svc->der[i]); + svc->der[i] = NULL; + } + svc->count = 0; + svc->state = JOB_NONE; +} + +/* Verify the chain the way an HSM holding the anchors would: walk from the + * certificate nearest the anchors down to the server's own, each one checked + * against what is trusted so far and then trusted for the next step. */ +static int service_verify(const TrustService* svc) +{ + WOLFSSL_CERT_MANAGER* cm; + int i; + int ret; + + cm = wolfSSL_CertManagerNew(); + if (cm == NULL) + return MEMORY_E; + + ret = wolfSSL_CertManagerLoadCABuffer(cm, svc->anchor, svc->anchorSz, + WOLFSSL_FILETYPE_PEM); + for (i = svc->count - 1; (ret == WOLFSSL_SUCCESS) && (i >= 1); i--) { + ret = wolfSSL_CertManagerVerifyBuffer(cm, svc->der[i], svc->derSz[i], + WOLFSSL_FILETYPE_ASN1); + if (ret == WOLFSSL_SUCCESS) { + ret = wolfSSL_CertManagerLoadCABuffer(cm, svc->der[i], + svc->derSz[i], WOLFSSL_FILETYPE_ASN1); + } + } + if (ret == WOLFSSL_SUCCESS) { + ret = wolfSSL_CertManagerVerifyBuffer(cm, svc->der[0], svc->derSz[0], + WOLFSSL_FILETYPE_ASN1); + } + wolfSSL_CertManagerFree(cm); + + return (ret == WOLFSSL_SUCCESS) ? 0 : ret; +} + +static void* service_thread(void* arg) +{ + TrustService* svc = (TrustService*)arg; + int verdict; + + pthread_mutex_lock(&svc->lock); + while (!svc->stop) { + if (svc->state != JOB_PENDING) { + pthread_cond_wait(&svc->cond, &svc->lock); + continue; + } + pthread_mutex_unlock(&svc->lock); + + usleep(SERVICE_DELAY_MS * 1000); + verdict = svc->rejectAll ? ASN_NO_SIGNER_E : service_verify(svc); + + pthread_mutex_lock(&svc->lock); + svc->verdict = verdict; + svc->state = JOB_DONE; + pthread_cond_broadcast(&svc->cond); + } + pthread_mutex_unlock(&svc->lock); + return NULL; +} + +static int service_start(TrustService* svc, const char* anchorFile, + int rejectAll) +{ + FILE* f; + long sz; + + memset(svc, 0, sizeof(*svc)); + svc->rejectAll = rejectAll; + + f = fopen(anchorFile, "rb"); + if (f == NULL) { + fprintf(stderr, "trust service: cannot open %s: %s\n", anchorFile, + strerror(errno)); + return -1; + } + fseek(f, 0, SEEK_END); + sz = ftell(f); + fseek(f, 0, SEEK_SET); + svc->anchor = (unsigned char*)malloc((size_t)sz); + if ((sz <= 0) || (svc->anchor == NULL) || + (fread(svc->anchor, 1, (size_t)sz, f) != (size_t)sz)) { + fprintf(stderr, "trust service: cannot read %s\n", anchorFile); + fclose(f); + return -1; + } + fclose(f); + svc->anchorSz = sz; + + pthread_mutex_init(&svc->lock, NULL); + pthread_cond_init(&svc->cond, NULL); + if (pthread_create(&svc->thread, NULL, service_thread, svc) != 0) { + fprintf(stderr, "trust service: cannot start thread\n"); + return -1; + } + printf("trust service: anchors loaded from %s, running on its own thread\n", + anchorFile); + return 0; +} + +static void service_stop(TrustService* svc) +{ + pthread_mutex_lock(&svc->lock); + svc->stop = 1; + pthread_cond_broadcast(&svc->cond); + pthread_mutex_unlock(&svc->lock); + pthread_join(svc->thread, NULL); + job_clear(svc); + free(svc->anchor); + pthread_cond_destroy(&svc->cond); + pthread_mutex_destroy(&svc->lock); +} + +/* Give the service a little time to answer. A real application would go + * back to its event loop instead and re-enter the handshake when told to. */ +static void service_poll(TrustService* svc) +{ + struct timespec until; + + clock_gettime(CLOCK_REALTIME, &until); + until.tv_nsec += POLL_MS * 1000000L; + if (until.tv_nsec >= 1000000000L) { + until.tv_sec++; + until.tv_nsec -= 1000000000L; + } + pthread_mutex_lock(&svc->lock); + if (svc->state == JOB_PENDING) + pthread_cond_timedwait(&svc->cond, &svc->lock, &until); + pthread_mutex_unlock(&svc->lock); +} + +/* The chain verify callback. wolfSSL has already decoded the certificates; + * it has built no chain and verified nothing. certs[0] is the server's own + * certificate, the rest is the chain it sent. The buffers are only valid for + * the duration of the call, so they are copied before the service gets them. + */ +static int chain_verify_cb(WOLFSSL* ssl, const WOLFSSL_BUFFER_INFO* certs, + int certsSz, void* ctx) +{ + TrustService* svc = (TrustService*)ctx; + int ret = 0; + int i; + + (void)ssl; + + pthread_mutex_lock(&svc->lock); + switch (svc->state) { + case JOB_NONE: + if (certsSz > MAX_CHAIN) { + ret = MAX_CHAIN_ERROR; + break; + } + for (i = 0; i < certsSz; i++) { + svc->der[i] = (unsigned char*)malloc(certs[i].length); + if (svc->der[i] == NULL) { + ret = MEMORY_E; + break; + } + memcpy(svc->der[i], certs[i].buffer, certs[i].length); + svc->derSz[i] = certs[i].length; + svc->count = i + 1; + } + if (ret != 0) { + job_clear(svc); + break; + } + svc->state = JOB_PENDING; + pthread_cond_broadcast(&svc->cond); + printf("callback: %d certificate(s) handed to the trust service, " + "deferring\n", certsSz); + ret = CHAIN_VERIFY_WANT_E; + break; + + case JOB_PENDING: + printf("callback: no verdict yet, deferring again\n"); + ret = CHAIN_VERIFY_WANT_E; + break; + + case JOB_DONE: + ret = svc->verdict; + if (ret == 0) { + printf("callback: trust service accepted the chain\n"); + } + else { + printf("callback: trust service rejected the chain: %s\n", + wolfSSL_ERR_reason_error_string((unsigned long)ret)); + } + job_clear(svc); + break; + } + pthread_mutex_unlock(&svc->lock); + + return ret; +} + +static int wait_socket(int sock, int forRead) +{ + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + FD_SET(sock, &fds); + tv.tv_sec = 5; + tv.tv_usec = 0; + return select(sock + 1, forRead ? &fds : NULL, forRead ? NULL : &fds, + NULL, &tv); +} + +static int tcp_connect(const char* host, int port) +{ + struct sockaddr_in addr; + int sock; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + fprintf(stderr, "socket: %s\n", strerror(errno)); + return -1; + } + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons((unsigned short)port); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + fprintf(stderr, "invalid address %s\n", host); + close(sock); + return -1; + } + if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + fprintf(stderr, "connect to %s:%d: %s\n", host, port, strerror(errno)); + close(sock); + return -1; + } + /* Non-blocking from here on, so the handshake returns to us whenever it + * waits for the network or for the trust service. */ + fcntl(sock, F_SETFL, O_NONBLOCK); + return sock; +} + +static void usage(void) +{ + printf("client-tls-chainverifycb [-n] [-x] [-a anchor.pem] [host] [port]\n" + " -n install no callback: with no CA loaded the handshake must " + "fail\n" + " -x make the trust service reject every chain\n" + " -a trust anchor file for the service (default %s)\n", + DEFAULT_ANCHOR); +} + +int main(int argc, char** argv) +{ + TrustService svc; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + const char* host = DEFAULT_HOST; + const char* anchorFile = DEFAULT_ANCHOR; + int port = DEFAULT_PORT; + int noCallback = 0; + int rejectAll = 0; + int hostSet = 0; + int sock = -1; + int ret = 0; + int err = 0; + int exitCode = EXIT_FAILURE; + int i; + char buf[MAXDATASIZE]; + const char msg[] = "Hello from client-tls-chainverifycb"; + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-n") == 0) + noCallback = 1; + else if (strcmp(argv[i], "-x") == 0) + rejectAll = 1; + else if ((strcmp(argv[i], "-a") == 0) && (i + 1 < argc)) + anchorFile = argv[++i]; + else if (strcmp(argv[i], "-h") == 0) { + usage(); + return EXIT_SUCCESS; + } + else if (argv[i][0] == '-') { + usage(); + return EXIT_FAILURE; + } + else if (!hostSet) { + host = argv[i]; + hostSet = 1; + } + else + port = atoi(argv[i]); + } + + /* A server that gives up on the handshake may close the socket while we + * are still writing; report that as an error instead of dying. */ + signal(SIGPIPE, SIG_IGN); + + if (service_start(&svc, anchorFile, rejectAll) != 0) + return EXIT_FAILURE; + + wolfSSL_Init(); + + ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()); + if (ctx == NULL) { + fprintf(stderr, "wolfSSL_CTX_new failed\n"); + goto cleanup; + } + + /* Deliberately no wolfSSL_CTX_load_verify_locations(): the SSL object + * holds no trust anchors at all. */ + if (!noCallback) { + ret = wolfSSL_CTX_SetChainVerifyCb(ctx, chain_verify_cb); + if (ret != WOLFSSL_SUCCESS) { + fprintf(stderr, "wolfSSL_CTX_SetChainVerifyCb failed: %s\n", + wolfSSL_ERR_reason_error_string((unsigned long)ret)); + goto cleanup; + } + printf("client: no CA loaded, chain verify callback installed\n"); + } + else { + printf("client: no CA loaded, no callback: expecting the handshake " + "to fail\n"); + } + + sock = tcp_connect(host, port); + if (sock < 0) + goto cleanup; + + ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + fprintf(stderr, "wolfSSL_new failed\n"); + goto cleanup; + } + wolfSSL_set_fd(ssl, sock); + wolfSSL_SetChainVerifyCtx(ssl, &svc); + + /* Drive the handshake. Three things make wolfSSL_connect() return early: + * waiting for the socket, and the callback waiting for the service. */ + for (;;) { + ret = wolfSSL_connect(ssl); + if (ret == WOLFSSL_SUCCESS) + break; + err = wolfSSL_get_error(ssl, ret); + if (err == WOLFSSL_ERROR_WANT_READ) + wait_socket(sock, 1); + else if (err == WOLFSSL_ERROR_WANT_WRITE) + wait_socket(sock, 0); + else if (err == CHAIN_VERIFY_WANT_E) + service_poll(&svc); + else + break; + } + if (ret != WOLFSSL_SUCCESS) { + printf("client: handshake failed: %d (%s)\n", err, + wolfSSL_ERR_reason_error_string((unsigned long)err)); + if (noCallback && (err == ASN_NO_SIGNER_E)) + exitCode = EXIT_SUCCESS; /* the expected outcome of -n */ + if (rejectAll && (err == CHAIN_VERIFY_CB_E)) + exitCode = EXIT_SUCCESS; /* the expected outcome of -x */ + goto cleanup; + } + printf("client: handshake done, %s\n", wolfSSL_get_version(ssl)); + + do { + ret = wolfSSL_write(ssl, msg, (int)strlen(msg)); + err = wolfSSL_get_error(ssl, ret); + } while ((ret <= 0) && (err == WOLFSSL_ERROR_WANT_WRITE)); + if (ret <= 0) { + fprintf(stderr, "wolfSSL_write failed: %d\n", err); + goto cleanup; + } + + do { + ret = wolfSSL_read(ssl, buf, sizeof(buf) - 1); + err = wolfSSL_get_error(ssl, ret); + if ((ret <= 0) && (err == WOLFSSL_ERROR_WANT_READ)) + wait_socket(sock, 1); + } while ((ret <= 0) && (err == WOLFSSL_ERROR_WANT_READ)); + if (ret <= 0) { + fprintf(stderr, "wolfSSL_read failed: %d\n", err); + goto cleanup; + } + buf[ret] = '\0'; + printf("server: %s\n", buf); + exitCode = EXIT_SUCCESS; + +cleanup: + if (ssl != NULL) { + wolfSSL_shutdown(ssl); + wolfSSL_free(ssl); + } + if (sock >= 0) + close(sock); + if (ctx != NULL) + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + service_stop(&svc); + return exitCode; +} + +#else + +int main(void) +{ + fprintf(stderr, "wolfSSL was built without --enable-chain-verify-cb\n"); + return EXIT_FAILURE; +} + +#endif /* WOLFSSL_CHAIN_VERIFY_CB */ From eb6d9c9208ce84b8e97d7baff532911f2aca764c Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 8 Sep 2026 12:04:59 +0000 Subject: [PATCH 2/4] Run the chain verify callback example in CI The example needs --enable-chain-verify-cb, which the tls profile did not carry, so client-tls-chainverifycb built as the stub that prints "wolfSSL was built without --enable-chain-verify-cb" and nothing ran it. Add the flag, and a pair entry that runs it against server-tls. The flag only defines WOLFSSL_CHAIN_VERIFY_CB. WOLFSSL_ASYNC_IO is already on in this profile, so nothing else in tls/ is affected. server-tls presents ../certs/server-cert.pem on port 11111, which the client's default anchor ../certs/ca-cert.pem issued, so the pair needs no arguments beyond the host. --- .github/examples-manifest.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/examples-manifest.yml b/.github/examples-manifest.yml index 1f0950d82..5d4f52f3d 100644 --- a/.github/examples-manifest.yml +++ b/.github/examples-manifest.yml @@ -131,7 +131,8 @@ profiles: flags: >- --enable-tls13 --enable-ech --enable-writedup --enable-pkcallbacks --enable-postauth --enable-cryptocb --enable-opensslall --enable-session-ticket - --enable-earlydata --enable-keygen --enable-des3 --enable-static --enable-shared + --enable-earlydata --enable-keygen --enable-des3 --enable-chain-verify-cb + --enable-static --enable-shared cflags: "-DHAVE_SECRET_CALLBACK" dtls: @@ -955,6 +956,13 @@ examples: stdin: "hello\nshutdown\n" server_exit: killed expect: "Successful resume" + # Sends a fixed message rather than reading stdin, so no stdin here and + # the server never sees "shutdown". + - pair: + server: [./server-tls] + client: [./client-tls-chainverifycb, 127.0.0.1] + server_exit: killed + expect: "server: I hear ya fa shizzle" # Deliberately not run: client-ech (external crypto.cloudflare.com:443), # {client,server}-tls-uart (needs /dev/ttyUSB0), and the four *-perf # benchmarks. All still build. From 42173d0bba97d6f2524b671a6a914968e7b0331b Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 8 Sep 2026 12:59:45 +0000 Subject: [PATCH 3/4] Fix cleanup ordering and error paths in the chain verify example The trust service thread calls wolfSSL, so joining it after wolfSSL_Cleanup() could tear down library state the thread was still using. Stop the service first. Three error paths alongside it: - Size the anchor file before allocating for it. A ftell() of -1 became a SIZE_MAX malloc, and the read failure path returned without freeing. - Destroy the mutex and condition variable and free the anchor when pthread_create() fails, so a service that never started leaves nothing behind. - Keep the socket's existing flags when setting O_NONBLOCK, and fail if either fcntl() does, rather than running blocking without saying so. --- tls/client-tls-chainverifycb.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tls/client-tls-chainverifycb.c b/tls/client-tls-chainverifycb.c index 210620cd2..4e4fc7096 100644 --- a/tls/client-tls-chainverifycb.c +++ b/tls/client-tls-chainverifycb.c @@ -162,13 +162,18 @@ static int service_start(TrustService* svc, const char* anchorFile, strerror(errno)); return -1; } - fseek(f, 0, SEEK_END); - sz = ftell(f); - fseek(f, 0, SEEK_SET); + if ((fseek(f, 0, SEEK_END) != 0) || ((sz = ftell(f)) <= 0) || + (fseek(f, 0, SEEK_SET) != 0)) { + fprintf(stderr, "trust service: cannot size %s\n", anchorFile); + fclose(f); + return -1; + } svc->anchor = (unsigned char*)malloc((size_t)sz); - if ((sz <= 0) || (svc->anchor == NULL) || + if ((svc->anchor == NULL) || (fread(svc->anchor, 1, (size_t)sz, f) != (size_t)sz)) { fprintf(stderr, "trust service: cannot read %s\n", anchorFile); + free(svc->anchor); + svc->anchor = NULL; fclose(f); return -1; } @@ -179,6 +184,10 @@ static int service_start(TrustService* svc, const char* anchorFile, pthread_cond_init(&svc->cond, NULL); if (pthread_create(&svc->thread, NULL, service_thread, svc) != 0) { fprintf(stderr, "trust service: cannot start thread\n"); + pthread_cond_destroy(&svc->cond); + pthread_mutex_destroy(&svc->lock); + free(svc->anchor); + svc->anchor = NULL; return -1; } printf("trust service: anchors loaded from %s, running on its own thread\n", @@ -298,6 +307,7 @@ static int tcp_connect(const char* host, int port) { struct sockaddr_in addr; int sock; + int flags; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { @@ -319,7 +329,12 @@ static int tcp_connect(const char* host, int port) } /* Non-blocking from here on, so the handshake returns to us whenever it * waits for the network or for the trust service. */ - fcntl(sock, F_SETFL, O_NONBLOCK); + flags = fcntl(sock, F_GETFL, 0); + if ((flags == -1) || (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1)) { + fprintf(stderr, "cannot set non-blocking: %s\n", strerror(errno)); + close(sock); + return -1; + } return sock; } @@ -477,8 +492,8 @@ int main(int argc, char** argv) close(sock); if (ctx != NULL) wolfSSL_CTX_free(ctx); - wolfSSL_Cleanup(); service_stop(&svc); + wolfSSL_Cleanup(); return exitCode; } From dc62bf4bca86750a13c231654ce27988f8b91ebc Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 8 Sep 2026 13:14:50 +0000 Subject: [PATCH 4/4] Run the chain verify callback example against master only --enable-chain-verify-cb reached wolfSSL master in wolfSSL/wolfssl#11367 and no release carries it. configure exits 1 on an unrecognized --enable-*, so carrying the flag in the shared tls profile failed that whole directory on the stable tag rather than just skipping the one example. Give it its own profile and its own entry, pinned to master with wolfssl_ref, the way pkcs7-signeddata-stream already is. The other 22 tls targets keep testing the released library. The entry builds only the two binaries its pair needs, since the tls entry builds the rest against both refs already. wolfssl-matrix built every profile for every ref and then seeded whatever pins were missing, so a profile no unpinned entry uses was still built for refs its flags predate. Build the (profile, ref) pairs entries actually ask for instead, which covers the pinned ones and drops the impossible ones. --- .github/examples-manifest.yml | 30 +++++++++++++++++++++++++++--- .github/scripts/manifest.py | 32 ++++++++++---------------------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/.github/examples-manifest.yml b/.github/examples-manifest.yml index 5d4f52f3d..119a84756 100644 --- a/.github/examples-manifest.yml +++ b/.github/examples-manifest.yml @@ -128,6 +128,16 @@ profiles: # absent: it needs the separate wolfAsyncCrypt repo patched in. # keygen + des3: client-tls-pkcs12 needs both (PKCS12 default PBE is 3DES) or # it prints "not configured with ..." and exits 0. + flags: >- + --enable-tls13 --enable-ech --enable-writedup --enable-pkcallbacks + --enable-postauth --enable-cryptocb --enable-opensslall --enable-session-ticket + --enable-earlydata --enable-keygen --enable-des3 --enable-static --enable-shared + cflags: "-DHAVE_SECRET_CALLBACK" + + tlschainverifycb: + # tls plus --enable-chain-verify-cb, which no release carries yet. Kept + # separate because configure exits 1 on an unrecognized --enable-*, so + # putting it in `tls` would fail that whole dir on the stable tag. flags: >- --enable-tls13 --enable-ech --enable-writedup --enable-pkcallbacks --enable-postauth --enable-cryptocb --enable-opensslall --enable-session-ticket @@ -956,6 +966,23 @@ examples: stdin: "hello\nshutdown\n" server_exit: killed expect: "Successful resume" + # Deliberately not run: client-ech (external crypto.cloudflare.com:443), + # {client,server}-tls-uart (needs /dev/ttyUSB0), and the four *-perf + # benchmarks. All still build. + + # Split out of tls above purely so the other 22 targets keep testing the + # released library. --enable-chain-verify-cb reached wolfSSL master in + # wolfSSL/wolfssl#11367 and no release contains it yet; configure exits 1 on + # an unrecognized --enable-*, so on a stable tag this profile cannot even be + # built. Drop the pin and fold this back into tls once a release has it. + - id: tls-chainverifycb + path: tls + profile: tlschainverifycb + wolfssl_ref: master + # Only the pair, not the whole dir: everything else here is already built + # and run by the tls entry against both refs. + build: [make, client-tls-chainverifycb, server-tls] + run: # Sends a fixed message rather than reading stdin, so no stdin here and # the server never sees "shutdown". - pair: @@ -963,9 +990,6 @@ examples: client: [./client-tls-chainverifycb, 127.0.0.1] server_exit: killed expect: "server: I hear ya fa shizzle" - # Deliberately not run: client-ech (external crypto.cloudflare.com:443), - # {client,server}-tls-uart (needs /dev/ttyUSB0), and the four *-perf - # benchmarks. All still build. - id: dtls path: dtls diff --git a/.github/scripts/manifest.py b/.github/scripts/manifest.py index 52f0a5748..0de35c70c 100644 --- a/.github/scripts/manifest.py +++ b/.github/scripts/manifest.py @@ -340,7 +340,15 @@ def cmd_wolfssl_matrix(data, refs, tier, shas=None): rebuild -- otherwise every example pays a full wolfSSL build. """ pinned = dict(zip(refs, shas)) if shas else {} - profiles = sorted({e["profile"] for e in live_entries(data, tier)}) + # Exactly the (profile, ref) pairs some entry asks for. A pinned entry must + # seed its own pair, and a profile only pinned entries use must NOT be built + # for the refs they exclude: configure exits 1 on an unrecognized + # --enable-*, so a profile naming a flag a ref predates fails to build. + wanted = { + (e["profile"], ref) + for e in live_entries(data, tier) + for ref in entry_refs(e, refs) + } out = [ { "profile": name, @@ -350,28 +358,8 @@ def cmd_wolfssl_matrix(data, refs, tier, shas=None): "cflags": data["profiles"][name].get("cflags", ""), "overlay": data["profiles"][name].get("overlay", ""), } - for name in profiles - for ref in refs + for name, ref in sorted(wanted) ] - # A pinned example needs its (profile, ref) seeded too, or its job pays a - # full wolfSSL build on every run. - for e in live_entries(data, tier): - ref = e.get("wolfssl_ref") - if ref and not any( - o["profile"] == e["profile"] and o["wolfssl_ref"] == ref for o in out - ): - out.append( - { - "profile": e["profile"], - "wolfssl_ref": ref, - "wolfssl_sha": pinned.get(ref, ref), - "flags": " ".join( - data["profiles"][e["profile"]].get("flags", "").split() - ), - "cflags": data["profiles"][e["profile"]].get("cflags", ""), - "overlay": data["profiles"][e["profile"]].get("overlay", ""), - } - ) print(json.dumps(out))