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;
+}