From ed1c15929d2eb2dbc6432986c26661bf1549964a Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Thu, 17 Sep 2026 21:26:20 +0200 Subject: Add native TLS transport, TLS clients and lego cert sidecar - bokfd: optional TLS listener (OpenSSL), certificate reload on change - clients: tls:host:port targets with chain and host verification - compose: port 8788 and an INWX/lego renewal sidecar - Makefile: header dependency tracking (-MMD -MP) --- tests/test_core.c | 127 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/tls_test_cert.pem | 11 +++++ tests/tls_test_key.pem | 5 ++ 3 files changed, 143 insertions(+) create mode 100644 tests/tls_test_cert.pem create mode 100644 tests/tls_test_key.pem (limited to 'tests') diff --git a/tests/test_core.c b/tests/test_core.c index db3bfb2..b2c828d 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -1,10 +1,17 @@ +#include +#include +#include +#include #include #include #include #include +#include #include +#include #include +#include "client.h" #include "config.h" #include "db.h" #include "protocol.h" @@ -170,6 +177,124 @@ static int login(const char *user, const char *pass) return ok; } +static void echo_child(int wfd, int tls) +{ + int sfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sfd < 0) + _exit(1); + struct sockaddr_in sa; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sa.sin_port = 0; + if (bind(sfd, (struct sockaddr *)&sa, sizeof sa) != 0 || + listen(sfd, 1) != 0) + _exit(1); + socklen_t slen = sizeof sa; + if (getsockname(sfd, (struct sockaddr *)&sa, &slen) != 0) + _exit(1); + int port = ntohs(sa.sin_port); + if (write(wfd, &port, sizeof port) != (ssize_t)sizeof port) + _exit(1); + close(wfd); + int cfd = accept(sfd, NULL, NULL); + if (cfd < 0) + _exit(1); + char buf[256]; + if (tls) { + SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); + if (!ctx || + SSL_CTX_use_certificate_chain_file(ctx, + "tests/tls_test_cert.pem") != 1 || + SSL_CTX_use_PrivateKey_file(ctx, "tests/tls_test_key.pem", + SSL_FILETYPE_PEM) != 1) + _exit(1); + SSL *ssl = SSL_new(ctx); + SSL_set_fd(ssl, cfd); + if (SSL_accept(ssl) != 1) + _exit(1); + size_t got = 0; + while (got < sizeof buf) { + int n = SSL_read(ssl, buf + got, (int)(sizeof buf - got)); + if (n <= 0) + break; + got += (size_t)n; + if (memchr(buf, '\n', got)) + break; + } + (void)SSL_write(ssl, "tls-ok", 6); + SSL_shutdown(ssl); + SSL_free(ssl); + SSL_CTX_free(ctx); + } else { + size_t got = 0; + while (got < sizeof buf) { + ssize_t n = read(cfd, buf + got, sizeof buf - got); + if (n <= 0) + break; + got += (size_t)n; + if (memchr(buf, '\n', got)) + break; + } + (void)write(cfd, "tcp-ok", 6); + } + close(cfd); + close(sfd); + _exit(0); +} + +/* variant 0: plaintext tcp; 1: TLS trusting the test CA; 2: TLS without the + CA, which must fail certificate verification. */ +static void test_transport(void) +{ + signal(SIGPIPE, SIG_IGN); + for (int variant = 0; variant < 3; variant++) { + int tls = variant > 0; + int pfd[2]; + if (pipe(pfd) != 0) { + CHECK(0 && "pipe"); + return; + } + pid_t pid = fork(); + if (pid == 0) { + close(pfd[0]); + echo_child(pfd[1], tls); + } + close(pfd[1]); + int port = 0; + if (read(pfd[0], &port, sizeof port) != (ssize_t)sizeof port) + port = 0; + close(pfd[0]); + if (port <= 0) { + CHECK(0 && "no test server port"); + waitpid(pid, NULL, 0); + continue; + } + if (variant == 1) + setenv("BOKFD_TLS_CA", "tests/tls_test_cert.pem", 1); + else + unsetenv("BOKFD_TLS_CA"); + char target[64]; + snprintf(target, sizeof target, "%s:localhost:%d", + tls ? "tls" : "tcp", port); + struct client_conn c; + if (variant == 2) { + CHECK(client_connect(target, &c) != 0); + waitpid(pid, NULL, 0); + continue; + } + CHECK(client_connect(target, &c) == 0); + CHECK(client_send_line(&c, "{\"v\":1}") == 0); + char *line = client_read_line(&c); + CHECK(line && strncmp(line, tls ? "tls-ok" : "tcp-ok", 6) == 0); + if (!line) + fprintf(stderr, "transport error: %s\n", client_last_error()); + free(line); + client_close(&c); + waitpid(pid, NULL, 0); + } +} + int main(void) { char tmpdir[] = "/tmp/bokf-test-XXXXXX"; @@ -1059,6 +1184,8 @@ int main(void) CHECK(jint(d, "result.checked") > 20); yyjson_doc_free(d); + test_transport(); + /* rate limiting must stay last: it blocks the login key */ for (int i = 0; i < 5; i++) CHECK(!login("admin", "wrong")); diff --git a/tests/tls_test_cert.pem b/tests/tls_test_cert.pem new file mode 100644 index 0000000..c559354 --- /dev/null +++ b/tests/tls_test_cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBmTCCAT+gAwIBAgIUGXc7xLB5t5mnvBH4vPYvo6ACfowwCgYIKoZIzj0EAwIw +FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDkxNzE4MjAzMloXDTM2MDkxNDE4 +MjAzMlowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAE66+gpPbkPDOANXFk3UfqpE3djA17Z/EzZPBgmuKpbNyZftyjESkSf0Ra +LG3sZ97J9SMFWJJwLtB70I3dZO+NxaNvMG0wHQYDVR0OBBYEFHv6wMxHetBdatyF +ydr8XLg+aIVQMB8GA1UdIwQYMBaAFHv6wMxHetBdatyFydr8XLg+aIVQMA8GA1Ud +EwEB/wQFMAMBAf8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAoGCCqGSM49 +BAMCA0gAMEUCIDACquqAztvZ4gMH5MlbuZ8Hkqie1txpmIl6yW9qCm9MAiEAllS3 +LDzwrm8g5KgLAgURVZcdBLth3HXjYgQQrn/uwEQ= +-----END CERTIFICATE----- diff --git a/tests/tls_test_key.pem b/tests/tls_test_key.pem new file mode 100644 index 0000000..b084830 --- /dev/null +++ b/tests/tls_test_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjif72KjB8IkhmHF2 +FFNqe3eSRHi5cXOIba+FC5/Jd4ehRANCAATrr6Ck9uQ8M4A1cWTdR+qkTd2MDXtn +8TNk8GCa4qls3Jl+3KMRKRJ/RFosbexn3sn1IwVYknAu0HvQjd1k743F +-----END PRIVATE KEY----- -- cgit v1.3