From 1fc1481637eef3f7ff228f6ba2c7d58adf2ca642 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Tue, 3 Nov 2026 13:40:30 -0800 Subject: [PATCH] Add MbedTLS example --- .github/examples-manifest.yml | 14 +++ .github/workflows/examples.yml | 3 + .gitignore | 4 + README.md | 19 ++++ mbedtls-compat/Makefile | 38 +++++++ mbedtls-compat/README.md | 87 +++++++++++++++ mbedtls-compat/check.sh | 57 ++++++++++ mbedtls-compat/client-tls-mbedtls.c | 161 ++++++++++++++++++++++++++++ 8 files changed, 383 insertions(+) create mode 100644 mbedtls-compat/Makefile create mode 100644 mbedtls-compat/README.md create mode 100755 mbedtls-compat/check.sh create mode 100644 mbedtls-compat/client-tls-mbedtls.c diff --git a/.github/examples-manifest.yml b/.github/examples-manifest.yml index 0bee06a2b..31b28529e 100644 --- a/.github/examples-manifest.yml +++ b/.github/examples-manifest.yml @@ -987,6 +987,20 @@ examples: # {client,server}-tls-uart (needs /dev/ttyUSB0), and the four *-perf # benchmarks. All still build. + - id: mbedtls-compat + path: mbedtls-compat + # default is only for the servers. The client links wolfPSA, built below. + profile: default + # wolfPSA master until a release has MBEDTLS_COMPAT, then pin that tag + fetch: + - [sh, -c, "test -d ../../wolfssl || git clone -q --depth 1 --branch \"${WOLFSSL_REF:-master}\" https://github.com/wolfSSL/wolfssl ../../wolfssl"] + - [sh, -c, "test -d ../../wolfpsa || git clone -q --depth 1 --branch master https://github.com/wolfSSL/wolfpsa ../../wolfpsa"] + - [sh, -c, "cd ../../wolfpsa && make -j\"$(nproc)\" MBEDTLS_COMPAT=1 WOLFSSL_PATH=../wolfssl"] + # make check builds ../tls/server-tls.c for TLS 1.2 and 1.3 and runs the + # client against it, one server per case. tls/server-tls13 is not used: it + # requires a client certificate, which this client does not send. + mode: check + - id: dtls path: dtls profile: dtls diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index f54bcc07d..35b245f5d 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -155,6 +155,9 @@ jobs: # Before the run: run_example.py execs itself into a netns with only # loopback, so an example needing a third-party tree has to get it now. - name: Fetch third-party sources for ${{ matrix.id }} + env: + # for a third-party tree that builds the wolfSSL sources under test + WOLFSSL_REF: ${{ matrix.wolfssl_ref }} run: | python3 .github/scripts/run_example.py --only '${{ matrix.id }}' --fetch diff --git a/.gitignore b/.gitignore index e8e65d672..10e65c43d 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,10 @@ android/wolfssljni-ndk-sample/proguard-project.txt /psk/server-psk /psk/server-tcp +/mbedtls-compat/client-tls-mbedtls +/mbedtls-compat/check-server-tls12 +/mbedtls-compat/check-server-tls13 + /tls/client-async /tls/client-tcp /tls/client-tls diff --git a/README.md b/README.md index 662146757..6f7cc828c 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,25 @@ and example keystores. Please see the [java/README.md](java/README.md) for further usage and details. +
+ +#### mbedtls-compat (Mbed TLS Compatibility) + +This directory contains a client written for the Mbed TLS 4.x API, built on +wolfSSL through the Mbed TLS compatibility layer in wolfPSA. + +The client links wolfPSA, built with the compatibility layer, in place of the +Mbed TLS libraries: + +``` +cd wolfpsa +make MBEDTLS_COMPAT=1 WOLFSSL_PATH=../wolfssl +``` + +Please see the [mbedtls-compat/README.md](mbedtls-compat/README.md) for further +usage and details. + +
#### mynewt (Apache Mynewt Examples) diff --git a/mbedtls-compat/Makefile b/mbedtls-compat/Makefile new file mode 100644 index 000000000..899bacf8c --- /dev/null +++ b/mbedtls-compat/Makefile @@ -0,0 +1,38 @@ +# Mbed TLS Compatibility Example Makefile +CC = gcc +WOLFSSL_INSTALL_DIR = /usr/local +# wolfPSA built with MBEDTLS_COMPAT=1 +WOLFPSA_DIR = ../../wolfpsa +CFLAGS = -Wall -I$(WOLFSSL_INSTALL_DIR)/include +LIBS = -L$(WOLFSSL_INSTALL_DIR)/lib -lwolfssl -lm +# The client uses only wolfPSA, so resolves to wolfPSA +PSA_CFLAGS = -Wall -I$(WOLFPSA_DIR)/wolfpsa -I$(WOLFPSA_DIR)/build/include +PSA_LIBS = $(WOLFPSA_DIR)/libwolfpsa.a -lm + +TARGETS = client-tls-mbedtls +# ../tls/server-tls13 requires a client certificate, which this client does +# not send, so make check runs ../tls/server-tls built for each version +CHECK_SERVERS = check-server-tls12 check-server-tls13 + +all: $(TARGETS) + +$(WOLFPSA_DIR)/libwolfpsa.a: + @echo "$@ not found, build wolfPSA with make MBEDTLS_COMPAT=1" + @false + +%: %.c $(WOLFPSA_DIR)/libwolfpsa.a + $(CC) -o $@ $< $(PSA_CFLAGS) $(PSA_LIBS) + +check-server-tls12: ../tls/server-tls.c + $(CC) -o $@ $< $(CFLAGS) $(LIBS) + +check-server-tls13: ../tls/server-tls.c + $(CC) -o $@ $< -DUSE_TLSV13 $(CFLAGS) $(LIBS) + +check: $(TARGETS) $(CHECK_SERVERS) + ./check.sh + +clean: + rm -f $(TARGETS) $(CHECK_SERVERS) + +.PHONY: all check clean diff --git a/mbedtls-compat/README.md b/mbedtls-compat/README.md new file mode 100644 index 000000000..587102dff --- /dev/null +++ b/mbedtls-compat/README.md @@ -0,0 +1,87 @@ +# Mbed TLS Compatibility Example + +`client-tls-mbedtls.c` is an application written for the Mbed TLS 4.x API, +built on wolfSSL through the Mbed TLS compatibility layer in +[wolfPSA](https://github.com/wolfSSL/wolfpsa). Its `tls_get()` function is the +client from the "Porting a TLS Client" chapter of the Mbed TLS to wolfSSL +migration guide, unchanged. + +wolfPSA provides the PSA Crypto API on wolfCrypt, as TF-PSA-Crypto does for +Mbed TLS. Built with `MBEDTLS_COMPAT=1`, it also provides the Mbed TLS TLS, +X.509 and network API on wolfSSL, so the client links `libwolfpsa.a` in place +of `libmbedtls`, `libmbedx509` and `libtfpsacrypto`. The calls mapped so far +and the known differences from Mbed TLS are in wolfPSA's README, section +"Mbed TLS compatibility". + +## Building + +Build wolfPSA with the compatibility layer, next to `wolfssl-examples`. It +compiles wolfCrypt and the wolfSSL TLS sources from `WOLFSSL_PATH` into +`libwolfpsa.a` with its own settings, so the client needs no wolfSSL install: + +``` +cd wolfpsa +make MBEDTLS_COMPAT=1 WOLFSSL_PATH=../wolfssl +``` + +Then build the example (set `WOLFPSA_DIR` if wolfPSA is elsewhere): + +``` +make +``` + +The Makefile puts wolfPSA's headers on the include path, so `` +resolves to wolfPSA even if Mbed TLS is installed too. + +## Running + +The servers are wolfSSL programs, built from `../tls/server-tls.c` against an +installed wolfSSL, as for the other examples: + +``` +cd wolfssl +./configure +make +sudo make install +sudo ldconfig +``` + +Start a TLS 1.3 server, then run the client: + +``` +make check-server-tls13 +./check-server-tls13 & +./client-tls-mbedtls 127.0.0.1 +``` + +``` +tls_get() returned 0 +``` + +The client checks the server certificate against `../certs/ca-cert.pem` and +the name `example.com`. Options: + +* `-A ` CA certificate file, PEM or DER +* `-S ` server name to send and check +* `-N` use a non-blocking socket + +## Testing + +`make check` builds `../tls/server-tls.c` as `check-server-tls12` and +`check-server-tls13` and runs the client against them for TLS 1.2 and 1.3, +blocking and non-blocking, and for each failure it should report: wrong server +name, wrong CA, missing CA file and a CA file that is not a certificate. +`../tls/server-tls13` is not used because it requires a client certificate, +which this client does not send. + +A wrong server name or CA fails with `MBEDTLS_ERR_X509_CERT_VERIFY_FAILED` +(`-9984`), and a CA file that is not a certificate with +`MBEDTLS_ERR_X509_INVALID_FORMAT` (`-8576`). The compatibility layer returns +only Mbed TLS codes: a wolfSSL error with no Mbed TLS match returns a general +code for the call, such as `MBEDTLS_ERR_SSL_INTERNAL_ERROR`. + +`check.sh` runs the client and servers from `BIN_DIR`, by default the current +directory. + +An IP address passed to `-S` fails with `MBEDTLS_ERR_SSL_BAD_INPUT_DATA` +(`-135`), because the compatibility layer does not map IP address checks yet. diff --git a/mbedtls-compat/check.sh b/mbedtls-compat/check.sh new file mode 100755 index 000000000..dcbc2433f --- /dev/null +++ b/mbedtls-compat/check.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +# Run client-tls-mbedtls against the check servers. Each case gets a new +# server because they exit after a failed handshake. WRAP prefixes the client. +# BIN_DIR is where they are built, by default the current directory. + +BIN_DIR=${BIN_DIR:-.} +FAILS=0 + +# Retry while the server is not listening yet. A probe connection would end +# the server, since it exits when a handshake fails. +run_client() { + i=0 + while :; do + out=$($WRAP "$BIN_DIR/client-tls-mbedtls" $opts 127.0.0.1 2>&1) + rc=$? + [ "$out" != "ERROR: failed to connect" ] && return + i=$((i + 1)) + [ $i -ge 50 ] && return + sleep 0.1 + done +} + +# name|server|client options|expected tls_get() return +while IFS='|' read -r name server opts expect; do + [ -z "$name" ] && continue + "$BIN_DIR/$server" >/dev/null 2>&1 & + srv=$! + run_client + want=1 + [ "$expect" -eq 0 ] && want=0 + if [ "$out" = "tls_get() returned $expect" ] && [ $rc -eq $want ]; then + echo "PASS: $name" + else + echo "FAIL: $name: $out" + FAILS=$((FAILS + 1)) + fi + kill $srv 2>/dev/null + wait $srv 2>/dev/null +done < +#include +#include + +/* socket includes */ +#include +#include +#include +#include +#include + +/* Mbed TLS, provided by wolfPSA */ +#include +#include +#include +#include + +#define DEFAULT_PORT 11111 + +#define CA_FILE "../certs/ca-cert.pem" +/* DNS name in the subjectAltName of ../certs/server-cert.pem */ +#define HOST_NAME "example.com" + +int tls_get(int fd, const char* host, const char* caFile) +{ + int ret; + mbedtls_net_context net; + mbedtls_ssl_context ssl; + mbedtls_ssl_config conf; + mbedtls_x509_crt ca; + unsigned char buf[256]; + const char req[] = "GET / HTTP/1.0\r\n\r\n"; + + mbedtls_net_init(&net); + net.fd = fd; + mbedtls_ssl_init(&ssl); + mbedtls_ssl_config_init(&conf); + mbedtls_x509_crt_init(&ca); + + ret = (int)psa_crypto_init(); /* 4.x: before any parsing */ + if (ret != PSA_SUCCESS) + goto out; + if ((ret = mbedtls_x509_crt_parse_file(&ca, caFile)) != 0) + goto out; + if ((ret = mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) + goto out; + mbedtls_ssl_conf_ca_chain(&conf, &ca, NULL); + /* 3.6 only: mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &drbg); */ + + if ((ret = mbedtls_ssl_setup(&ssl, &conf)) != 0 || + (ret = mbedtls_ssl_set_hostname(&ssl, host)) != 0) + goto out; + mbedtls_ssl_set_bio(&ssl, &net, mbedtls_net_send, mbedtls_net_recv, NULL); + + while ((ret = mbedtls_ssl_handshake(&ssl)) != 0) { + if (ret != MBEDTLS_ERR_SSL_WANT_READ && + ret != MBEDTLS_ERR_SSL_WANT_WRITE) + goto out; + } + ret = mbedtls_ssl_write(&ssl, (const unsigned char*)req, sizeof(req) - 1); + if (ret <= 0) + goto out; + do { + ret = mbedtls_ssl_read(&ssl, buf, sizeof(buf) - 1); + } while (ret == MBEDTLS_ERR_SSL_WANT_READ || + ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET); + if (ret > 0) + ret = 0; + mbedtls_ssl_close_notify(&ssl); + +out: + mbedtls_ssl_free(&ssl); + mbedtls_ssl_config_free(&conf); + mbedtls_x509_crt_free(&ca); + return ret; +} + +int main(int argc, char** argv) +{ + int sockfd; + struct sockaddr_in servAddr; + const char* caFile = CA_FILE; + const char* host = HOST_NAME; + int nonBlock = 0; + int opt; + int ret; + + while ((opt = getopt(argc, argv, "A:S:N")) != -1) { + if (opt == 'A') + caFile = optarg; + else if (opt == 'S') + host = optarg; + else if (opt == 'N') + nonBlock = 1; + else + break; + } + if (opt != -1 || optind != argc - 1) { + printf("usage: %s [-A ] [-S ] [-N] " + "\n", argv[0]); + return 1; + } + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(DEFAULT_PORT); + if (inet_pton(AF_INET, argv[optind], &servAddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid address\n"); + return 1; + } + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + return 1; + } + if (connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to connect\n"); + close(sockfd); + return 1; + } + /* tls_get() retries on MBEDTLS_ERR_SSL_WANT_READ/WANT_WRITE */ + if (nonBlock && + fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL) | O_NONBLOCK) != 0) { + fprintf(stderr, "ERROR: failed to set non-blocking\n"); + close(sockfd); + return 1; + } + + ret = tls_get(sockfd, host, caFile); + printf("tls_get() returned %d\n", ret); + + close(sockfd); + return (ret == 0) ? 0 : 1; +}