From 6203d6a57e9e6e93eda52dd6878cedb305045743 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 20 Dec 2024 23:31:50 +0100 Subject: [PATCH] Added TLS support + tests --- .forgejo/workflows/linux.yml | 12 +- Makefile | 30 +++ src/port/wolfssl_io.c | 51 +++++ src/test/test_native_wolfssl.c | 369 +++++++++++++++++++++++++++++++++ tools/certs/mkcerts.sh | 61 ++++++ 5 files changed, 517 insertions(+), 6 deletions(-) create mode 100644 src/port/wolfssl_io.c create mode 100644 src/test/test_native_wolfssl.c create mode 100755 tools/certs/mkcerts.sh diff --git a/.forgejo/workflows/linux.yml b/.forgejo/workflows/linux.yml index c33734c..c2f84a9 100644 --- a/.forgejo/workflows/linux.yml +++ b/.forgejo/workflows/linux.yml @@ -19,15 +19,15 @@ jobs: - name: Update repo run: | sudo apt-get update + sudo modprobe tun - name: Build linux tests run: | mkdir -p build/port make -# - name: Run interop tests -# run: | -# sudo build/test -# -# -# Build only for now: no tuntap support in container + - name: Run interop tests + run: | + sudo build/test + + diff --git a/Makefile b/Makefile index 8e8b1e2..57da3e5 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,14 @@ build/tcp_netcat_select: $(OBJ) build/port/posix/bsd_socket.o build/test/tcp_net @echo "[LD] $@" @$(CC) $(CFLAGS) $(LDFLAGS) -o $@ -Wl,--start-group $(^) -Wl,--end-group + +build/test-wolfssl:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG + + +build/test-wolfssl: $(OBJ) build/test/test_native_wolfssl.o build/port/wolfssl_io.o build/certs/server_key.o build/certs/ca_cert.o build/certs/server_cert.o + @echo "[LD] $@" + @$(CC) $(CFLAGS) $(LDFLAGS) -o $@ -Wl,--start-group $(^) -lwolfssl -Wl,--end-group + build/%.o: src/%.c @mkdir -p `dirname $@` || true @echo "[CC] $<" @@ -73,6 +81,28 @@ build/pie/%.o: src/%.c @echo "[CC] $<" @$(CC) $(CFLAGS) -c $< -o $@ +build/certs/%.o: build/certs/%.c + @mkdir -p `dirname $@` || true + @echo "[CC] $<" + @$(CC) $(CFLAGS) -c $< -o $@ + +build/certs/ca_cert.c: + @echo "[MKCERTS] `dirname $@`" + @tools/certs/mkcerts.sh + +build/certs/server_key.c: + @echo "[MKCERTS] `dirname $@`" + @tools/certs/mkcerts.sh + +build/certs/server_cert.c: + @echo "[MKCERTS] `dirname $@`" + @tools/certs/mkcerts.sh + +build/certs/server_key.o: build/certs/server_key.c + @mkdir -p `dirname $@` || true + @echo "[CC] $<" + @$(CC) $(CFLAGS) -c $< -o $@ + unit: build/test/unit build/test/unit: diff --git a/src/port/wolfssl_io.c b/src/port/wolfssl_io.c new file mode 100644 index 0000000..70f347c --- /dev/null +++ b/src/port/wolfssl_io.c @@ -0,0 +1,51 @@ +#include "femtotcp.h" +#include +#include + +static struct ipstack *ref_ipstack = NULL; + +static int ipstack_io_recv(WOLFSSL* ssl, char* buf, int sz, void* ctx) +{ + int ret = 0; + int fd = (intptr_t)ctx; + (void)ssl; + if (!ref_ipstack) + return -1; + ret = ft_recv(ref_ipstack, fd, buf, sz, 0); + if (ret == -11) + return WOLFSSL_CBIO_ERR_WANT_READ; + else if (ret <= 0) + return WOLFSSL_CBIO_ERR_CONN_CLOSE; + return ret; +} + +static int ipstack_io_send(WOLFSSL* ssl, char* buf, int sz, void* ctx) +{ + int ret = 0; + int fd = (intptr_t)ctx; + (void)ssl; + if (!ref_ipstack) + return -1; + ret = ft_send(ref_ipstack, fd, buf, sz, 0); + if (ret == -11) + return WOLFSSL_CBIO_ERR_WANT_WRITE; + else if (ret <= 0) + return WOLFSSL_CBIO_ERR_CONN_CLOSE; + return ret; +} + +int wolfSSL_SetIO_FT_CTX(WOLFSSL_CTX* ctx, struct ipstack *s) +{ + wolfSSL_SetIORecv(ctx, ipstack_io_recv); + wolfSSL_SetIOSend(ctx, ipstack_io_send); + ref_ipstack = s; + return 0; +} + +int wolfSSL_SetIO_FT(WOLFSSL* ssl, int fd) +{ + wolfSSL_SetIOReadCtx(ssl, (void*)(intptr_t)fd); + wolfSSL_SetIOWriteCtx(ssl, (void*)(intptr_t)fd); + return 0; +} + diff --git a/src/test/test_native_wolfssl.c b/src/test/test_native_wolfssl.c new file mode 100644 index 0000000..ff391a4 --- /dev/null +++ b/src/test/test_native_wolfssl.c @@ -0,0 +1,369 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "femtotcp.h" +#include +#include +#include + +#define TEST_SIZE (8 * 1024) + +#define BUFFER_SIZE TEST_SIZE + +static int listen_fd = -1, client_fd = -1; +static int exit_ok = 0, exit_count = 0; +static uint8_t buf[TEST_SIZE]; +static int tot_sent = 0; +static int tot_recv = 0; +static int femtotcp_closing = 0; +static int closed = 0; +static const uint8_t test_pattern[16] = "Test pattern - -"; + +static WOLFSSL_CTX *server_ctx = NULL; /* Used by femtoTCP */ +static WOLFSSL_CTX *client_ctx = NULL; /* Used by Linux */ +static WOLFSSL *client_ssl = NULL; +static WOLFSSL *server_ssl = NULL; + + +/* Defined in wolfssl_io.c */ +int wolfSSL_SetIO_FT(WOLFSSL* ssl, int fd); +int wolfSSL_SetIO_FT_CTX(WOLFSSL_CTX *ctx, struct ipstack *s); + +/* FemtoTCP: server side callback. */ +static void server_cb(int fd, uint16_t event, void *arg) +{ + int ret = 0; + if ((fd == listen_fd) && (event & CB_EVENT_READABLE) && (client_fd == -1)) { + client_fd = ft_accept((struct ipstack *)arg, listen_fd, NULL, NULL); + if (client_fd > 0) { + printf("accept: Client FD is 0x%04x\n", client_fd); + /* Create the wolfSSL object */ + server_ssl = wolfSSL_new(server_ctx); + if (!server_ssl) { + printf("Failed to create server SSL object\n"); + return; + } + wolfSSL_SetIO_FT(server_ssl, client_fd); + /* Accepting the TLS session is not necessary here, as the + * first read will trigger the handshake. + */ + printf("Server: TCP connection established\n"); + } + } else if ((fd == client_fd) && (event & CB_EVENT_READABLE )) { + ret = wolfSSL_read(server_ssl, buf, sizeof(buf)); + if (ret < 0) { + ret = wolfSSL_get_error(server_ssl, 0); + if (ret != WOLFSSL_ERROR_WANT_READ) { + printf("Recv error: %d\n", ret); + ft_close((struct ipstack *)arg, client_fd); + } + } else if (ret == 0) { + printf("Client side closed the connection.\n"); + ft_close((struct ipstack *)arg, client_fd); + printf("Server: Exiting.\n"); + exit_ok = 1; + } else if (ret > 0) { + printf("recv: %d, echoing back\n", ret); + tot_recv += ret; + } + } + if ((event & CB_EVENT_WRITABLE) || ((ret > 0) && !closed)) { + int snd_ret; + if ((tot_sent >= 4096) && femtotcp_closing) { + ft_close((struct ipstack *)arg, client_fd); + printf("Server: I closed the connection.\n"); + closed = 1; + exit_ok = 1; + } + if ((!closed) && (tot_sent < tot_recv)) { + snd_ret = wolfSSL_write(server_ssl, buf + tot_sent, tot_recv - tot_sent); + if (snd_ret != WANT_WRITE) { + if (snd_ret < 0) { + printf("Send error: %d\n", snd_ret); + wolfSSL_free(server_ssl); + ft_close((struct ipstack *)arg, client_fd); + } else { + tot_sent += snd_ret; + printf("sent %d bytes\n", snd_ret); + if (tot_recv == tot_sent) { + tot_sent = 0; + tot_recv = 0; + } + } + } + } + } + if (event & CB_EVENT_CLOSED) { + printf("Closing %d, client fd: %d\n", fd, client_fd); + wolfSSL_free(server_ssl); + server_ssl = NULL; + } + if ((fd == client_fd) && (event & CB_EVENT_CLOSED)) { + printf("Client side closed the connection (EVENT_CLOSED)\n"); + wolfSSL_free(server_ssl); + ft_close((struct ipstack *)arg, client_fd); + client_fd = -1; + printf("Server: Exiting.\n"); + exit_ok = 1; + } +} + + +/* FemtoTCP side: main loop of the stack under test. */ +static int test_loop(struct ipstack *s, int active_close) +{ + exit_ok = 0; + exit_count = 0; + tot_sent = 0; + femtotcp_closing = active_close; + closed = 0; + + while(1) { + uint32_t ms_next; + struct timeval tv; + gettimeofday(&tv, NULL); + ms_next = ipstack_poll(s, tv.tv_sec * 1000 + tv.tv_usec / 1000); + usleep(ms_next * 1000); + if (exit_ok > 0) { + if (exit_count++ < 10) + continue; + else break; + } + } + return 0; +} + +/* Test code (Linux side). + * Thread with client to test the echoserver. + */ +extern const unsigned char ca_der[]; +extern const unsigned long ca_der_len; + +void *pt_echoclient(void *arg) +{ + int fd, ret; + unsigned total_r = 0; + unsigned i; + uint8_t buf[BUFFER_SIZE]; + uint32_t *srv_addr = (uint32_t *)arg; + struct sockaddr_in remote_sock = { + .sin_family = AF_INET, + .sin_port = ntohs(8), /* Echo */ + }; + + client_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (!client_ctx) { + printf("Failed to create client context\n"); + return (void *)-1; + } + + client_ssl = wolfSSL_new(client_ctx); + if (!client_ssl) { + printf("Failed to create client SSL object\n"); + return (void *)-1; + } + + wolfSSL_CTX_load_verify_buffer(client_ctx, ca_der, ca_der_len, SSL_FILETYPE_ASN1); + + remote_sock.sin_addr.s_addr = *srv_addr; + fd = socket(AF_INET, IPSTACK_SOCK_STREAM, 0); + if (fd < 0) { + printf("test client socket: %d\n", fd); + return (void *)-1; + } + wolfSSL_set_fd(client_ssl, fd); + sleep(1); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); + printf("Connecting to echo server\n"); + ret = connect(fd, (struct sockaddr *)&remote_sock, sizeof(remote_sock)); + if (ret < 0) { + printf("test client connect: %d\n", ret); + perror("connect"); + return (void *)-1; + } + printf("Linux client: TCP connection established\n"); + ret = wolfSSL_connect(client_ssl); + if (ret != SSL_SUCCESS) { + printf("Linux client: Failed to connect to TLS server, err: %d\n", ret); + return (void *)-1; + } + for (i = 0; i < sizeof(buf); i += sizeof(test_pattern)) { + memcpy(buf + i, test_pattern, sizeof(test_pattern)); + } + ret = wolfSSL_write(client_ssl, buf, sizeof(buf)); + if (ret < 0) { + printf("test client write: %d\n", ret); + return (void *)-1; + } + while (total_r < sizeof(buf)) { + ret = wolfSSL_read(client_ssl, buf + total_r, sizeof(buf) - total_r); + if (ret < 0) { + printf("failed test client read: %d\n", ret); + return (void *)-1; + } + if (ret == 0) { + printf("test client read: server has closed the connection.\n"); + if (femtotcp_closing) + return (void *)0; + else + return (void *)-1; + } + total_r += ret; + } + for (i = 0; i < sizeof(buf); i += sizeof(test_pattern)) { + if (memcmp(buf + i, test_pattern, sizeof(test_pattern))) { + printf("test client: pattern mismatch\n"); + printf("at position %d\n", i); + buf[i + 16] = 0; + printf("%s\n", &buf[i]); + return (void *)-1; + } + } + client_ssl = NULL; + close(fd); + printf("Test client: success\n"); + wolfSSL_free(client_ssl); + return (void *)0; +} + + +/* Catch-all function to initialize a new tap device as the network interface. + * This is defined in port/linux.c + * */ +extern int tap_init(struct ll *dev, const char *name, uint32_t host_ip); + +/* Test cases */ + +extern const unsigned char server_der[]; +extern const unsigned long server_der_len; +extern const unsigned char server_key_der[]; +extern const unsigned long server_key_der_len; + + +void test_femtotcp_echoserver(struct ipstack *s, uint32_t srv_ip) +{ + int ret, test_ret = 0; + pthread_t pt; + struct ipstack_sockaddr_in local_sock = { + .sin_family = AF_INET, + .sin_port = ee16(8), /* Echo */ + .sin_addr.s_addr = 0 + }; + printf("TCP server tests\n"); + + printf("Creating TLS server context\n"); + server_ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + if (!server_ctx) { + printf("Failed to create server context\n"); + return; + } + printf("Associating server context with femtoTCP\n"); + wolfSSL_SetIO_FT_CTX(server_ctx, s); + + printf("Importing server certificate\n"); + ret = wolfSSL_CTX_use_certificate_buffer(server_ctx, server_der, + server_der_len, SSL_FILETYPE_ASN1); + if (ret != SSL_SUCCESS) { + printf("Failed to import server certificate\n"); + return; + } + printf("Importing server private key\n"); + ret = wolfSSL_CTX_use_PrivateKey_buffer(server_ctx, server_key_der, + server_key_der_len, SSL_FILETYPE_ASN1); + if (ret != SSL_SUCCESS) { + printf("Failed to import server private key\n"); + return; + } + + listen_fd = ft_socket(s, AF_INET, IPSTACK_SOCK_STREAM, 0); + printf("socket: %04x\n", listen_fd); + ipstack_register_callback(s, listen_fd, server_cb, s); + + pthread_create(&pt, NULL, pt_echoclient, &srv_ip); + printf("Starting test: echo server close-wait\n"); + ret = ft_bind(s, listen_fd, (struct ipstack_sockaddr *)&local_sock, sizeof(local_sock)); + printf("bind: %d\n", ret); + ret = ft_listen(s, listen_fd, 1); + printf("listen: %d\n", ret); + ret = test_loop(s, 0); + pthread_join(pt, (void **)&test_ret); + printf("Test echo server close-wait: %d\n", ret); + printf("Test linux client: %d\n", test_ret); + sleep(1); + + pthread_create(&pt, NULL, pt_echoclient, &srv_ip); + printf("Starting test: echo server active close\n"); + ret = test_loop(s, 1); + printf("Test echo server close-wait: %d\n", ret); + pthread_join(pt, (void **)&test_ret); + printf("Test linux client: %d\n", test_ret); + sleep(1); + + ft_close(s, listen_fd); +} + +/* Main test function. */ +int main(int argc, char **argv) +{ + struct ipstack *s; + struct ll *tapdev; + struct timeval tv; + struct in_addr linux_ip; + uint32_t srv_ip; + ip4 ip = 0, nm = 0, gw = 0; + + wolfSSL_Init(); + wolfSSL_Debugging_OFF(); + + (void)argc; + (void)argv; + (void)ip; + (void)nm; + (void)gw; + (void)tv; + ipstack_init_static(&s); + tapdev = ipstack_getdev(s); + if (!tapdev) + return 1; + inet_aton(LINUX_IP, &linux_ip); + if (tap_init(tapdev, "femt0", linux_ip.s_addr) < 0) { + perror("tap init"); + return 2; + } + system("tcpdump -i femt0 -w test.pcap &"); + +#ifdef DHCP + gettimeofday(&tv, NULL); + ipstack_poll(s, tv.tv_sec * 1000 + tv.tv_usec / 1000); + dhcp_client_init(s); + do { + gettimeofday(&tv, NULL); + ipstack_poll(s, tv.tv_sec * 1000 + tv.tv_usec / 1000); + usleep(1000); + ipstack_ipconfig_get(s, &ip, &nm, &gw); + } while (!dhcp_bound(s)); + printf("DHCP: obtained IP address.\n"); + ipstack_ipconfig_get(s, &ip, &nm, &gw); + srv_ip = htonl(ip); +#else + ipstack_ipconfig_set(s, atoip4(FEMTOTCP_IP), atoip4("255.255.255.0"), + atoip4(LINUX_IP)); + printf("IP: manually configured\n"); + inet_pton(AF_INET, FEMTOTCP_IP, &srv_ip); +#endif + + /* Server side test */ + test_femtotcp_echoserver(s, srv_ip); + sleep(2); + sync(); + system("killall tcpdump"); + return 0; +} + diff --git a/tools/certs/mkcerts.sh b/tools/certs/mkcerts.sh new file mode 100755 index 0000000..5c78fd0 --- /dev/null +++ b/tools/certs/mkcerts.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# +OUT_DIR=build/certs + +: "${COUNTRY:=US}" +: "${STATE:=State}" +: "${CITY:=City}" +: "${ORG:=ExampleOrg}" +: "${ORG_UNIT:=IT}" +: "${CA_COMMON_NAME:=Example CA}" +: "${SERVER_COMMON_NAME:=example.com}" +: "${DAYS_CA:=3650}" # CA certificate validity in days (10 years) +: "${DAYS_SERVER:=825}" # Server certificate validity in days (2 years) +: "${ECC_CURVE:=secp384r1}" # ECC curve to use + +# Create the output directory if it doesn't exist +mkdir -p "$OUT_DIR" + +# 1. Generate CA private key +openssl ecparam -name "$ECC_CURVE" -genkey -noout -out "$OUT_DIR/ca.key" + +# 2. Generate the CA self-signed certificate (PEM format) +openssl req -x509 -new -key "$OUT_DIR/ca.key" -sha256 -days "$DAYS_CA" -out "$OUT_DIR/ca.crt" \ + -subj "/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=$ORG_UNIT/CN=$CA_COMMON_NAME" + +# 3. Convert CA certificate to DER format +openssl x509 -in "$OUT_DIR/ca.crt" -outform DER -out "$OUT_DIR/ca.der" + +xxd -i "$OUT_DIR/ca.der" |sed -e "s/unsigned/const unsigned/g" | sed -e "s/build_certs_//g" > "$OUT_DIR/ca_cert.c" + + +echo "==== Generating server private key ====" + +# 4. Generate server private key +openssl ecparam -name "$ECC_CURVE" -genkey -noout -out "$OUT_DIR/server.key" + +# 5. Convert server private key to DER format +openssl pkcs8 -topk8 -nocrypt -in "$OUT_DIR/server.key" -outform DER -out "$OUT_DIR/server.key.der" + +xxd -i "$OUT_DIR/server.key.der" |sed -e "s/unsigned/const unsigned/g" | sed -e "s/build_certs_//g" > "$OUT_DIR/server_key.c" + + +echo "==== Generating server Certificate Signing Request (CSR) ====" + +# 6. Generate server Certificate Signing Request (CSR) +openssl req -new -key "$OUT_DIR/server.key" -out "$OUT_DIR/server.csr" \ + -subj "/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORG/OU=$ORG_UNIT/CN=$SERVER_COMMON_NAME" + +echo "==== Signing server certificate with the CA ====" + +# 7. Sign the server CSR with the CA to create a server certificate (PEM format) +openssl x509 -req -in "$OUT_DIR/server.csr" -CA "$OUT_DIR/ca.crt" -CAkey "$OUT_DIR/ca.key" \ + -CAcreateserial -out "$OUT_DIR/server.crt" -days "$DAYS_SERVER" -sha256 + +# 8. Convert server certificate to DER format +openssl x509 -in "$OUT_DIR/server.crt" -outform DER -out "$OUT_DIR/server.der" + +xxd -i "$OUT_DIR/server.der" |sed -e "s/unsigned/const unsigned/g" | sed -e "s/build_certs_//g" > "$OUT_DIR/server_cert.c" + +echo "==== Done ====" +