From 6ba3b3b7eff80a783d3b8d6c1fcd724879d348dd Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Thu, 25 Jun 2026 20:48:22 +1000 Subject: [PATCH] PQC proxy Proxies that sit in front of a client and server that don't support PQC crypto algorithms. --- .gitignore | 8 + pq/pqc_proxy/Makefile | 36 ++++ pq/pqc_proxy/README.md | 208 ++++++++++++++++++++ pq/pqc_proxy/legacy-client.c | 159 +++++++++++++++ pq/pqc_proxy/legacy-server.c | 211 ++++++++++++++++++++ pq/pqc_proxy/pq-client.c | 174 ++++++++++++++++ pq/pqc_proxy/pq-proxy.c | 365 ++++++++++++++++++++++++++++++++++ pq/pqc_proxy/pq-server.c | 225 +++++++++++++++++++++ pq/pqc_proxy/proxy-common.h | 120 ++++++++++++ pq/pqc_proxy/upgrade-proxy.c | 370 +++++++++++++++++++++++++++++++++++ 10 files changed, 1876 insertions(+) create mode 100644 pq/pqc_proxy/Makefile create mode 100644 pq/pqc_proxy/README.md create mode 100644 pq/pqc_proxy/legacy-client.c create mode 100644 pq/pqc_proxy/legacy-server.c create mode 100644 pq/pqc_proxy/pq-client.c create mode 100644 pq/pqc_proxy/pq-proxy.c create mode 100644 pq/pqc_proxy/pq-server.c create mode 100644 pq/pqc_proxy/proxy-common.h create mode 100644 pq/pqc_proxy/upgrade-proxy.c diff --git a/.gitignore b/.gitignore index dd369b303..1a31a0765 100644 --- a/.gitignore +++ b/.gitignore @@ -287,6 +287,14 @@ pq/ml_dsa/ml_dsa_test pq/ml_dsa/*.bin pq/ml_dsa/*.key +# PQC crypto-proxy +pq/pqc_proxy/pq-proxy +pq/pqc_proxy/pq-client +pq/pqc_proxy/legacy-server +pq/pqc_proxy/upgrade-proxy +pq/pqc_proxy/legacy-client +pq/pqc_proxy/pq-server + embedded/tls-client-server embedded/tls-server-size embedded/tls-sock-client diff --git a/pq/pqc_proxy/Makefile b/pq/pqc_proxy/Makefile new file mode 100644 index 000000000..fb271150e --- /dev/null +++ b/pq/pqc_proxy/Makefile @@ -0,0 +1,36 @@ +# PQC crypto-proxy examples Makefile +CC = gcc +WOLFSSL_INSTALL_DIR = /usr/local +CFLAGS = -Wall -I$(WOLFSSL_INSTALL_DIR)/include +LIBS = -L$(WOLFSSL_INSTALL_DIR)/lib -lm + +# option variables +DYN_LIB = -lwolfssl +STATIC_LIB = $(WOLFSSL_INSTALL_DIR)/lib/libwolfssl.a +DEBUG_FLAGS = -g -DDEBUG +OPTIMIZE = -Os + +# Options +#CFLAGS+=$(DEBUG_FLAGS) +CFLAGS+=$(OPTIMIZE) +#LIBS+=$(STATIC_LIB) +LIBS+=$(DYN_LIB) + +# build targets +SRC=$(wildcard *.c) +TARGETS=$(patsubst %.c, %, $(SRC)) +DEPS=proxy-common.h + +.PHONY: clean all + +all: $(TARGETS) + +debug: CFLAGS+=$(DEBUG_FLAGS) +debug: all + +# build template +%: %.c $(DEPS) + $(CC) -o $@ $< $(CFLAGS) $(LIBS) + +clean: + rm -f $(TARGETS) diff --git a/pq/pqc_proxy/README.md b/pq/pqc_proxy/README.md new file mode 100644 index 000000000..9897b7cef --- /dev/null +++ b/pq/pqc_proxy/README.md @@ -0,0 +1,208 @@ +# wolfSSL Post-Quantum Crypto-Proxy Examples + +This directory shows two ways to use a small TLS terminating proxy to add a +**post-quantum (hybrid ML-KEM) key exchange** to a connection where one of the +two endpoints can only speak **legacy, classical TLS**. In both cases the proxy +decrypts on one side and re-encrypts on the other, so the post-quantum +protection is added transparently and neither legacy endpoint has to change. + +### Direction 1 — quantum-safe front door (`pq-proxy`) + +A modern client reaches a legacy origin that cannot be upgraded. The +client-facing leg is post-quantum; the origin-facing leg stays classical. + +``` + ┌────────────┐ PQC / hybrid TLS 1.3 ┌────────────┐ legacy TLS 1.2 ┌───────────────┐ + │ pq-client │ ──────────────────────► │ pq-proxy │ ────────────────► │ legacy-server │ + │ (modern) │ P256_ML_KEM_768 │ (terminates│ ECDHE-RSA-AES128 │ (origin) │ + └────────────┘ │ + re-TLS) │ -GCM-SHA256 └───────────────┘ + └────────────┘ +``` + +### Direction 2 — quantum-safe upgrade (`upgrade-proxy`) + +A legacy client that cannot speak PQC reaches a modern origin. The proxy +upgrades the connection so the leg crossing the untrusted network to the server +is post-quantum. + +``` + ┌───────────────┐ legacy TLS 1.2 ┌──────────────┐ PQC / hybrid TLS 1.3 ┌───────────┐ + │ legacy-client │ ────────────────► │ upgrade-proxy│ ──────────────────────► │ pq-server │ + │ (legacy) │ ECDHE-RSA-AES128 │ (terminates │ P256_ML_KEM_768 │ (modern) │ + └───────────────┘ -GCM-SHA256 │ + re-TLS) │ └───────────┘ + └──────────────┘ +``` + +Each proxy is simultaneously a TLS **server** on one leg and a TLS **client** +on the other. + +## Why this is useful + +A "harvest now, decrypt later" attacker can record TLS traffic today and break +the (classical) key exchange once a quantum computer exists. Putting a +post-quantum **hybrid** key exchange on the leg that crosses the public network +removes that risk, even when one of the endpoints behind the proxy cannot yet +be upgraded. + +## Files + +| File | Role | Crypto | +|------|------|--------| +| `pq-proxy.c` | Direction 1 proxy: PQC frontend, legacy backend | frontend: TLS 1.3 hybrid ML-KEM, backend: TLS 1.2 ECDHE-RSA | +| `legacy-server.c` | Direction 1 legacy origin | TLS 1.2 ECDHE-RSA-AES128-GCM-SHA256 | +| `pq-client.c` | Direction 1 modern client | TLS 1.3 hybrid ML-KEM | +| `upgrade-proxy.c` | Direction 2 proxy: legacy frontend, PQC backend | frontend: TLS 1.2 ECDHE-RSA, backend: TLS 1.3 hybrid ML-KEM | +| `pq-server.c` | Direction 2 modern origin | TLS 1.3 hybrid ML-KEM | +| `legacy-client.c` | Direction 2 legacy client | TLS 1.2 ECDHE-RSA-AES128-GCM-SHA256 | +| `proxy-common.h` | Shared ports, groups, cert paths | — | + +## Prerequisites + +Build and install wolfSSL with ML-KEM (Kyber) and TLS 1.3: + +```sh +./configure --enable-mlkem --enable-tls13 +make +sudo make install +sudo ldconfig +``` + +If you also build with `--enable-mldsa`, the PQC leg automatically upgrades to a +**fully** post-quantum handshake (ML-DSA certificate + ML-KEM key exchange). +Otherwise the PQC leg uses post-quantum key exchange with a classical RSA +certificate for authentication — which is the most common real-world PQC +deployment today, because public CAs do not yet issue PQC certificates. + +## Building + +```sh +make +``` + +## Running direction 1 (quantum-safe front door) + +Use three terminals. + +1. Start the legacy origin server (classical TLS 1.2): + + ```sh + ./legacy-server + ``` + +2. Start the crypto-proxy: + + ```sh + ./pq-proxy + ``` + + ``` + PQC crypto-proxy ready + frontend : 0.0.0.0:11111 TLS 1.3 kex=P256_ML_KEM_768 auth=RSA-2048 (classical) + backend : 127.0.0.1:11112 TLS 1.2 ECDHE-RSA-AES128-GCM-SHA256 + ``` + +3. Connect with the modern client and type a message: + + ```sh + ./pq-client 127.0.0.1 + ``` + + ``` + Connected to proxy: TLSv1.3 TLS_AES_256_GCM_SHA384 kex=SecP256r1MLKEM768 + Message for server: hello + Server: [origin] legacy service reached over TLS 1.2 + ``` + +The proxy reports both halves of the bridge: + +``` +Bridging connection: + frontend TLSv1.3 TLS_AES_256_GCM_SHA384 kex=SecP256r1MLKEM768 + backend TLSv1.2 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 kex=SECP256R1 +``` + +## Running direction 2 (quantum-safe upgrade) + +Use three terminals. + +1. Start the modern PQC origin server (TLS 1.3, hybrid ML-KEM): + + ```sh + ./pq-server + ``` + +2. Start the upgrade-proxy: + + ```sh + ./upgrade-proxy + ``` + + ``` + PQC upgrade-proxy ready + frontend : 0.0.0.0:22221 TLS 1.2 ECDHE-RSA-AES128-GCM-SHA256 + backend : 127.0.0.1:22222 TLS 1.3 kex=P256_ML_KEM_768 auth=RSA-2048 (classical) + ``` + +3. Connect with the legacy client and type a message: + + ```sh + ./legacy-client 127.0.0.1 + ``` + + ``` + Connected to proxy: TLSv1.2 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + Message for server: hello + Server: [pq-server] reached over a quantum-safe TLS 1.3 link + ``` + +The proxy reports both halves of the bridge: + +``` +Bridging connection: + frontend TLSv1.2 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 kex=SECP256R1 + backend TLSv1.3 TLS_AES_256_GCM_SHA384 kex=SecP256r1MLKEM768 +``` + +In either direction, send the message `shutdown` to stop the origin server. + +You can verify a proxy really requires PQC on its quantum-safe leg: it only ever +negotiates the `P256_ML_KEM_768` group there (via `wolfSSL_CTX_set_groups()`), +so a classical-only peer on that leg is rejected. + +## Command-line options + +```sh +./legacy-server [] +./pq-proxy [ [ []]] +./pq-client [ []] + +./pq-server [] +./upgrade-proxy [ [ []]] +./legacy-client [ []] +``` + +The two directions use different default ports (11111/11112 and 22221/22222), +so both demos can run at the same time. + +## Customising the cryptography + +All algorithm selection lives in `proxy-common.h`: + +- **PQC group** — change `FRONTEND_GROUP` to raise the security level + (e.g. `WOLFSSL_SECP384R1MLKEM1024`) or to use a non-hybrid, pure-PQC group + (`WOLFSSL_ML_KEM_768`). Both proxies restrict negotiation on their PQC leg to + this single group, so only quantum-safe peers are accepted there. +- **Legacy cipher suite** — change `BACKEND_CIPHERS` to model whatever legacy + suite your real endpoint requires. + +## Notes and limitations + +- For clarity each proxy handles **one client connection at a time**. A + production proxy would `fork()` or spawn a thread per accepted connection (or + use the non-blocking / event-loop patterns in `tls/server-tls-epoll-*.c`), + reusing the two `WOLFSSL_CTX` objects across connections as these examples + already do. +- Data is relayed with a `select()` loop over the two socket descriptors, + draining any records wolfSSL has already buffered with `wolfSSL_pending()`. +- The example certificates come from the repository's `certs/` directory and + are for testing only. diff --git a/pq/pqc_proxy/legacy-client.c b/pq/pqc_proxy/legacy-client.c new file mode 100644 index 000000000..711a0fc77 --- /dev/null +++ b/pq/pqc_proxy/legacy-client.c @@ -0,0 +1,159 @@ +/* legacy-client.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * 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 stand-in for a legacy client that can only speak classical TLS 1.2. It + * connects to the upgrade-proxy, which transparently re-establishes the request + * to the modern pq-server over a quantum-safe (hybrid ML-KEM) TLS 1.3 link. */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "proxy-common.h" + +int main(int argc, char** argv) +{ + int ret = 0; + int sockfd = SOCKET_INVALID; + int port = UPGRADE_PORT; + const char* host = "127.0.0.1"; + struct sockaddr_in servAddr; + char buff[256]; + size_t len; + + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + if (argc >= 2) host = argv[1]; + if (argc >= 3) port = atoi(argv[2]); + if (argc > 3) { + printf("usage: %s [ []]\n", argv[0]); + printf("Defaults: 127.0.0.1 %d\n", UPGRADE_PORT); + return 0; + } + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto exit; + } + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(port); + if (inet_pton(AF_INET, host, &servAddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid address %s\n", host); + ret = -1; + goto exit; + } + + if (connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to connect\n"); + ret = -1; + goto exit; + } + + wolfSSL_Init(); + + /* Deliberately pin to classical TLS 1.2 - this client cannot do PQC. */ + if ((ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + + if ((ret = wolfSSL_CTX_set_cipher_list(ctx, LEGACY_CIPHERS)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set cipher list %s\n", + LEGACY_CIPHERS); + goto exit; + } + + /* Trust the proxy's frontend (legacy) certificate. */ + if ((ret = wolfSSL_CTX_load_verify_locations(ctx, LEGACY_CA, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", LEGACY_CA); + goto exit; + } + + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; + goto exit; + } + wolfSSL_set_fd(ssl, sockfd); + + if (wolfSSL_connect(ssl) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: handshake failed: %d\n", + wolfSSL_get_error(ssl, 0)); + ret = -1; + goto exit; + } + + printf("Connected to proxy: %s %s\n", + wolfSSL_get_version(ssl), wolfSSL_get_cipher(ssl)); + + printf("Message for server: "); + memset(buff, 0, sizeof(buff)); + if (fgets(buff, sizeof(buff), stdin) == NULL) { + fprintf(stderr, "ERROR: failed to get message\n"); + ret = -1; + goto exit; + } + len = strnlen(buff, sizeof(buff)); + + if (wolfSSL_write(ssl, buff, (int)len) != (int)len) { + fprintf(stderr, "ERROR: failed to write\n"); + ret = -1; + goto exit; + } + + memset(buff, 0, sizeof(buff)); + if (wolfSSL_read(ssl, buff, sizeof(buff) - 1) < 0) { + fprintf(stderr, "ERROR: failed to read\n"); + ret = -1; + goto exit; + } + printf("Server: %s", buff); + + ret = 0; + +exit: + if (ssl) { + wolfSSL_shutdown(ssl); + wolfSSL_free(ssl); + } + if (sockfd != SOCKET_INVALID) + close(sockfd); + if (ctx) + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + + return ret; +} diff --git a/pq/pqc_proxy/legacy-server.c b/pq/pqc_proxy/legacy-server.c new file mode 100644 index 000000000..02171bc0b --- /dev/null +++ b/pq/pqc_proxy/legacy-server.c @@ -0,0 +1,211 @@ +/* legacy-server.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * 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 stand-in for a legacy origin service: it only speaks classical TLS 1.2. + * The PQC proxy (pq-proxy) terminates the quantum-safe client connection and + * forwards the plaintext to this server over a legacy cipher suite. */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "proxy-common.h" + +static int mSockfd = SOCKET_INVALID; +static int mConnd = SOCKET_INVALID; +static int mShutdown = 0; + +static void sig_handler(const int sig) +{ + (void)sig; + mShutdown = 1; + if (mConnd != SOCKET_INVALID) { + close(mConnd); + mConnd = SOCKET_INVALID; + } + if (mSockfd != SOCKET_INVALID) { + close(mSockfd); + mSockfd = SOCKET_INVALID; + } +} + +/* Accept and service client connections until a shutdown is requested. */ +static int accept_loop(WOLFSSL_CTX* ctx) +{ + int ret = 0; + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + char buff[256]; + const char* reply = "[origin] legacy service reached over TLS 1.2\n"; + WOLFSSL* ssl = NULL; + + while (!mShutdown) { + int n; + + printf("Waiting for a connection...\n"); + if ((mConnd = accept(mSockfd, (struct sockaddr*)&clientAddr, &size)) + == -1) { + if (mShutdown) + break; + fprintf(stderr, "ERROR: failed to accept the connection\n"); + ret = -1; + break; + } + + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; + break; + } + wolfSSL_set_fd(ssl, mConnd); + + if (wolfSSL_accept(ssl) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: wolfSSL_accept error %d\n", + wolfSSL_get_error(ssl, 0)); + goto conn_cleanup; + } + + printf("Proxy connected: %s %s\n", + wolfSSL_get_version(ssl), wolfSSL_get_cipher(ssl)); + + memset(buff, 0, sizeof(buff)); + if ((n = wolfSSL_read(ssl, buff, sizeof(buff) - 1)) > 0) { + printf("Received: %s", buff); + if (strncmp(buff, "shutdown", 8) == 0) { + printf("Shutdown command issued!\n"); + mShutdown = 1; + } + } + + if (wolfSSL_write(ssl, reply, (int)strlen(reply)) < 0) + fprintf(stderr, "ERROR: failed to write\n"); + + wolfSSL_shutdown(ssl); + +conn_cleanup: + wolfSSL_free(ssl); + ssl = NULL; + if (mConnd != SOCKET_INVALID) { + close(mConnd); + mConnd = SOCKET_INVALID; + } + } + + return ret; +} + +int main(int argc, char** argv) +{ + int ret = 0; + int port = BACKEND_PORT; + struct sockaddr_in servAddr; + + WOLFSSL_CTX* ctx = NULL; + + if (argc == 2) { + port = atoi(argv[1]); + } + else if (argc != 1) { + printf("usage: %s []\n", argv[0]); + printf("Default listen port: %d\n", BACKEND_PORT); + return 0; + } + + signal(SIGINT, sig_handler); + + wolfSSL_Init(); + + if ((mSockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto exit; + } + + /* Deliberately pin to classical TLS 1.2 - cannot be upgraded. */ + if ((ctx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + + if ((ret = wolfSSL_CTX_set_cipher_list(ctx, BACKEND_CIPHERS)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set cipher list %s\n", + BACKEND_CIPHERS); + goto exit; + } + + if ((ret = wolfSSL_CTX_use_certificate_file(ctx, BACKEND_CERT, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", BACKEND_CERT); + goto exit; + } + + if ((ret = wolfSSL_CTX_use_PrivateKey_file(ctx, BACKEND_KEY, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", BACKEND_KEY); + goto exit; + } + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(port); + servAddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(mSockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + ret = -1; + goto exit; + } + + if (listen(mSockfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + ret = -1; + goto exit; + } + + printf("Legacy origin server listening on port %d (%s)\n", + port, BACKEND_CIPHERS); + + ret = accept_loop(ctx); + if (ret == 0) + printf("Shutdown complete\n"); + +exit: + if (mConnd != SOCKET_INVALID) + close(mConnd); + if (mSockfd != SOCKET_INVALID) + close(mSockfd); + if (ctx) + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + + return ret; +} diff --git a/pq/pqc_proxy/pq-client.c b/pq/pqc_proxy/pq-client.c new file mode 100644 index 000000000..3c402124b --- /dev/null +++ b/pq/pqc_proxy/pq-client.c @@ -0,0 +1,174 @@ +/* pq-client.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * 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 modern client that connects to the PQC crypto-proxy using a post-quantum + * (hybrid ML-KEM) TLS 1.3 handshake. It neither knows nor cares that the proxy + * forwards its request to a legacy TLS 1.2 origin behind the scenes. */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "proxy-common.h" + +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) + +int main(int argc, char** argv) +{ + int ret = 0; + int sockfd = SOCKET_INVALID; + int port = FRONTEND_PORT; + const char* host = "127.0.0.1"; + struct sockaddr_in servAddr; + char buff[256]; + size_t len; + + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + if (argc >= 2) host = argv[1]; + if (argc >= 3) port = atoi(argv[2]); + if (argc > 3) { + printf("usage: %s [ []]\n", argv[0]); + printf("Defaults: 127.0.0.1 %d\n", FRONTEND_PORT); + return 0; + } + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto exit; + } + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(port); + if (inet_pton(AF_INET, host, &servAddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid address %s\n", host); + ret = -1; + goto exit; + } + + if (connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to connect\n"); + ret = -1; + goto exit; + } + + wolfSSL_Init(); + + if ((ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + + /* Trust the proxy's frontend certificate. */ + if ((ret = wolfSSL_CTX_load_verify_locations(ctx, FRONTEND_CA, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", FRONTEND_CA); + goto exit; + } + + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; + goto exit; + } + + /* Offer the post-quantum (hybrid) group for key establishment. */ + if ((ret = wolfSSL_UseKeyShare(ssl, FRONTEND_GROUP)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set PQC key share (%s)\n", + FRONTEND_GROUP_NAME); + goto exit; + } + + wolfSSL_set_fd(ssl, sockfd); + + if (wolfSSL_connect(ssl) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: handshake failed: %d\n", + wolfSSL_get_error(ssl, 0)); + ret = -1; + goto exit; + } + + printf("Connected to proxy: %s %s kex=%s\n", + wolfSSL_get_version(ssl), wolfSSL_get_cipher(ssl), + wolfSSL_get_curve_name(ssl)); + + printf("Message for server: "); + memset(buff, 0, sizeof(buff)); + if (fgets(buff, sizeof(buff), stdin) == NULL) { + fprintf(stderr, "ERROR: failed to get message\n"); + ret = -1; + goto exit; + } + len = strnlen(buff, sizeof(buff)); + + if (wolfSSL_write(ssl, buff, (int)len) != (int)len) { + fprintf(stderr, "ERROR: failed to write\n"); + ret = -1; + goto exit; + } + + memset(buff, 0, sizeof(buff)); + if (wolfSSL_read(ssl, buff, sizeof(buff) - 1) < 0) { + fprintf(stderr, "ERROR: failed to read\n"); + ret = -1; + goto exit; + } + printf("Server: %s", buff); + + ret = 0; + +exit: + if (ssl) { + wolfSSL_shutdown(ssl); + wolfSSL_free(ssl); + } + if (sockfd != SOCKET_INVALID) + close(sockfd); + if (ctx) + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + + return ret; +} + +#else + +int main(void) +{ + printf("This example requires TLS 1.3 and ML-KEM (Kyber).\n"); + printf("Configure wolfSSL like this:\n"); + printf(" ./configure --enable-kyber --enable-tls13\n"); + return 0; +} + +#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM */ diff --git a/pq/pqc_proxy/pq-proxy.c b/pq/pqc_proxy/pq-proxy.c new file mode 100644 index 000000000..33ec1066b --- /dev/null +++ b/pq/pqc_proxy/pq-proxy.c @@ -0,0 +1,365 @@ +/* pq-proxy.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * 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 + */ + +/* pq-proxy: a TLS terminating crypto-proxy. + * + * client =(PQC/hybrid TLS 1.3)=> pq-proxy =(legacy TLS 1.2)=> origin + * + * The proxy is simultaneously: + * - a TLS *server* on the frontend, accepting a quantum-safe (hybrid ML-KEM) + * handshake from modern clients; + * - a TLS *client* on the backend, opening a classical TLS 1.2 connection to + * a legacy origin server that cannot be upgraded. + * + * Application data is decrypted on one side and re-encrypted on the other, so + * the post-quantum protection is added transparently in front of the legacy + * service. Connections are handled one at a time to keep the example readable; + * see the README for notes on serving clients concurrently. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "proxy-common.h" + +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) + +static int mListenfd = SOCKET_INVALID; +static int mShutdown = 0; + +static void sig_handler(const int sig) +{ + (void)sig; + mShutdown = 1; + if (mListenfd != SOCKET_INVALID) { + close(mListenfd); + mListenfd = SOCKET_INVALID; + } +} + +/* Open a TCP connection to the legacy origin server. */ +static int connect_backend(const char* host, int port) +{ + int sockfd; + struct sockaddr_in addr; + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + close(sockfd); + return -1; + } + + if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + close(sockfd); + return -1; + } + + return sockfd; +} + +/* Pump all currently available plaintext from src to dst. + * Returns 1 on success, 0 if the source closed, -1 on error. */ +static int pump(WOLFSSL* src, WOLFSSL* dst) +{ + char buff[PROXY_BUFFER_SZ]; + int n; + + do { + n = wolfSSL_read(src, buff, sizeof(buff)); + if (n <= 0) { + int err = wolfSSL_get_error(src, n); + if (err == WOLFSSL_ERROR_WANT_READ || + err == WOLFSSL_ERROR_WANT_WRITE) + return 1; + return (n == 0 || err == WOLFSSL_ERROR_ZERO_RETURN) ? 0 : -1; + } + + if (wolfSSL_write(dst, buff, n) != n) + return -1; + + /* wolfSSL may already hold further decrypted records that the socket + * select() would not report; drain while data remains buffered. */ + } while (wolfSSL_pending(src) > 0); + + return 1; +} + +/* Shuttle application data between the frontend and backend TLS sessions until + * either side closes the connection. */ +static void relay(WOLFSSL* front, int frontFd, WOLFSSL* back, int backFd) +{ + int maxFd = (frontFd > backFd ? frontFd : backFd) + 1; + + while (!mShutdown) { + fd_set readfds; + int rc; + + FD_ZERO(&readfds); + FD_SET(frontFd, &readfds); + FD_SET(backFd, &readfds); + + rc = select(maxFd, &readfds, NULL, NULL, NULL); + if (rc < 0) + break; + + if (FD_ISSET(frontFd, &readfds) || wolfSSL_pending(front) > 0) { + if (pump(front, back) <= 0) + break; + } + if (FD_ISSET(backFd, &readfds) || wolfSSL_pending(back) > 0) { + if (pump(back, front) <= 0) + break; + } + } +} + +static void print_leg(const char* label, WOLFSSL* ssl) +{ + const char* group = wolfSSL_get_curve_name(ssl); + printf(" %-9s %s %s kex=%s\n", label, + wolfSSL_get_version(ssl), wolfSSL_get_cipher(ssl), + group ? group : "(classical)"); +} + +/* Accept clients on the frontend and bridge each one to a fresh backend + * connection until a shutdown is requested. */ +static int accept_loop(WOLFSSL_CTX* frontCtx, WOLFSSL_CTX* backCtx, + const char* backHost, int backPort) +{ + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + + while (!mShutdown) { + int frontFd = SOCKET_INVALID; + int backFd = SOCKET_INVALID; + WOLFSSL* front = NULL; + WOLFSSL* back = NULL; + + printf("\nWaiting for a client...\n"); + frontFd = accept(mListenfd, (struct sockaddr*)&clientAddr, &size); + if (frontFd == -1) { + if (mShutdown) + break; + fprintf(stderr, "ERROR: failed to accept\n"); + continue; + } + + /* Terminate the quantum-safe client connection. */ + if ((front = wolfSSL_new(frontCtx)) == NULL) { + fprintf(stderr, "ERROR: failed to create frontend WOLFSSL\n"); + goto conn_cleanup; + } + wolfSSL_set_fd(front, frontFd); + if (wolfSSL_accept(front) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: frontend handshake failed: %d\n", + wolfSSL_get_error(front, 0)); + goto conn_cleanup; + } + + /* Open the legacy connection to the origin. */ + backFd = connect_backend(backHost, backPort); + if (backFd < 0) { + fprintf(stderr, "ERROR: failed to reach backend %s:%d\n", + backHost, backPort); + goto conn_cleanup; + } + if ((back = wolfSSL_new(backCtx)) == NULL) { + fprintf(stderr, "ERROR: failed to create backend WOLFSSL\n"); + goto conn_cleanup; + } + wolfSSL_set_fd(back, backFd); + if (wolfSSL_connect(back) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: backend handshake failed: %d\n", + wolfSSL_get_error(back, 0)); + goto conn_cleanup; + } + + printf("Bridging connection:\n"); + print_leg("frontend", front); + print_leg("backend", back); + + relay(front, frontFd, back, backFd); + printf("Connection closed\n"); + +conn_cleanup: + if (front) { + wolfSSL_shutdown(front); + wolfSSL_free(front); + } + if (back) { + wolfSSL_shutdown(back); + wolfSSL_free(back); + } + if (frontFd != SOCKET_INVALID) + close(frontFd); + if (backFd != SOCKET_INVALID) + close(backFd); + } + + return 0; +} + +int main(int argc, char** argv) +{ + int ret = 0; + int frontPort = FRONTEND_PORT; + const char* backHost = BACKEND_HOST; + int backPort = BACKEND_PORT; + int on = 1; + + struct sockaddr_in servAddr; + + /* The frontend group is restricted to a single post-quantum (hybrid) group + * so that the proxy will only accept quantum-safe key exchanges. */ + int frontGroups[] = { FRONTEND_GROUP }; + + WOLFSSL_CTX* frontCtx = NULL; /* frontend: PQC TLS 1.3 server */ + WOLFSSL_CTX* backCtx = NULL; /* backend: legacy TLS 1.2 client */ + + if (argc >= 2) frontPort = atoi(argv[1]); + if (argc >= 3) backHost = argv[2]; + if (argc >= 4) backPort = atoi(argv[3]); + if (argc > 4) { + printf("usage: %s [ [ " + "[]]]\n", argv[0]); + printf("Defaults: %d %s %d\n", FRONTEND_PORT, BACKEND_HOST, + BACKEND_PORT); + return 0; + } + + signal(SIGINT, sig_handler); + + wolfSSL_Init(); + + /* ---- Frontend context: post-quantum / hybrid TLS 1.3 server ---------- */ + if ((frontCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create frontend WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + if ((ret = wolfSSL_CTX_use_certificate_file(frontCtx, FRONTEND_CERT, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", FRONTEND_CERT); + goto exit; + } + if ((ret = wolfSSL_CTX_use_PrivateKey_file(frontCtx, FRONTEND_KEY, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", FRONTEND_KEY); + goto exit; + } + /* Only negotiate the hybrid PQC group - reject classical-only clients. */ + if ((ret = wolfSSL_CTX_set_groups(frontCtx, frontGroups, + sizeof(frontGroups) / sizeof(frontGroups[0]))) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set frontend PQC group\n"); + goto exit; + } + + /* ---- Backend context: legacy classical TLS 1.2 client ---------------- */ + if ((backCtx = wolfSSL_CTX_new(wolfTLSv1_2_client_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create backend WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + if ((ret = wolfSSL_CTX_set_cipher_list(backCtx, BACKEND_CIPHERS)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set backend cipher list\n"); + goto exit; + } + if ((ret = wolfSSL_CTX_load_verify_locations(backCtx, BACKEND_CA, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", BACKEND_CA); + goto exit; + } + + /* ---- Listen on the frontend port ------------------------------------- */ + if ((mListenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto exit; + } + setsockopt(mListenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(frontPort); + servAddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(mListenfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + ret = -1; + goto exit; + } + if (listen(mListenfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + ret = -1; + goto exit; + } + + printf("PQC crypto-proxy ready\n"); + printf(" frontend : 0.0.0.0:%d TLS 1.3 kex=%s auth=%s\n", + frontPort, FRONTEND_GROUP_NAME, FRONTEND_AUTH); + printf(" backend : %s:%d TLS 1.2 %s\n", + backHost, backPort, BACKEND_CIPHERS); + + ret = accept_loop(frontCtx, backCtx, backHost, backPort); + + printf("Shutdown complete\n"); + +exit: + if (mListenfd != SOCKET_INVALID) + close(mListenfd); + if (frontCtx) + wolfSSL_CTX_free(frontCtx); + if (backCtx) + wolfSSL_CTX_free(backCtx); + wolfSSL_Cleanup(); + + return ret; +} + +#else + +int main(void) +{ + printf("This example requires TLS 1.3 and ML-KEM (Kyber).\n"); + printf("Configure wolfSSL like this:\n"); + printf(" ./configure --enable-kyber --enable-tls13\n"); + printf("Add --enable-dilithium for post-quantum authentication too.\n"); + return 0; +} + +#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM */ diff --git a/pq/pqc_proxy/pq-server.c b/pq/pqc_proxy/pq-server.c new file mode 100644 index 000000000..1cb9a0e57 --- /dev/null +++ b/pq/pqc_proxy/pq-server.c @@ -0,0 +1,225 @@ +/* pq-server.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * 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 modern origin service that only accepts post-quantum (hybrid ML-KEM) + * TLS 1.3 connections. The upgrade-proxy (upgrade-proxy) connects here on + * behalf of legacy clients that cannot speak PQC themselves. */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "proxy-common.h" + +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) + +static int mSockfd = SOCKET_INVALID; +static int mConnd = SOCKET_INVALID; +static int mShutdown = 0; + +static void sig_handler(const int sig) +{ + (void)sig; + mShutdown = 1; + if (mConnd != SOCKET_INVALID) { + close(mConnd); + mConnd = SOCKET_INVALID; + } + if (mSockfd != SOCKET_INVALID) { + close(mSockfd); + mSockfd = SOCKET_INVALID; + } +} + +/* Accept and service client connections until a shutdown is requested. */ +static int accept_loop(WOLFSSL_CTX* ctx) +{ + int ret = 0; + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + char buff[256]; + const char* reply = + "[pq-server] reached over a quantum-safe TLS 1.3 link\n"; + WOLFSSL* ssl = NULL; + + while (!mShutdown) { + int n; + + printf("Waiting for a connection...\n"); + if ((mConnd = accept(mSockfd, (struct sockaddr*)&clientAddr, &size)) + == -1) { + if (mShutdown) + break; + fprintf(stderr, "ERROR: failed to accept the connection\n"); + ret = -1; + break; + } + + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; + break; + } + wolfSSL_set_fd(ssl, mConnd); + + if (wolfSSL_accept(ssl) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: wolfSSL_accept error %d\n", + wolfSSL_get_error(ssl, 0)); + goto conn_cleanup; + } + + printf("Proxy connected: %s %s kex=%s\n", + wolfSSL_get_version(ssl), wolfSSL_get_cipher(ssl), + wolfSSL_get_curve_name(ssl)); + + memset(buff, 0, sizeof(buff)); + if ((n = wolfSSL_read(ssl, buff, sizeof(buff) - 1)) > 0) { + printf("Received: %s", buff); + if (strncmp(buff, "shutdown", 8) == 0) { + printf("Shutdown command issued!\n"); + mShutdown = 1; + } + } + + if (wolfSSL_write(ssl, reply, (int)strlen(reply)) < 0) + fprintf(stderr, "ERROR: failed to write\n"); + + wolfSSL_shutdown(ssl); + +conn_cleanup: + wolfSSL_free(ssl); + ssl = NULL; + if (mConnd != SOCKET_INVALID) { + close(mConnd); + mConnd = SOCKET_INVALID; + } + } + + return ret; +} + +int main(int argc, char** argv) +{ + int ret = 0; + int port = PQSERVER_PORT; + struct sockaddr_in servAddr; + + /* Only negotiate the hybrid PQC group - reject classical-only peers. */ + int groups[] = { PQC_GROUP }; + + WOLFSSL_CTX* ctx = NULL; + + if (argc == 2) { + port = atoi(argv[1]); + } + else if (argc != 1) { + printf("usage: %s []\n", argv[0]); + printf("Default listen port: %d\n", PQSERVER_PORT); + return 0; + } + + signal(SIGINT, sig_handler); + + wolfSSL_Init(); + + if ((mSockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto exit; + } + + if ((ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + + if ((ret = wolfSSL_CTX_use_certificate_file(ctx, PQC_CERT, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", PQC_CERT); + goto exit; + } + if ((ret = wolfSSL_CTX_use_PrivateKey_file(ctx, PQC_KEY, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", PQC_KEY); + goto exit; + } + if ((ret = wolfSSL_CTX_set_groups(ctx, groups, + sizeof(groups) / sizeof(groups[0]))) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set PQC group\n"); + goto exit; + } + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(port); + servAddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(mSockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + ret = -1; + goto exit; + } + if (listen(mSockfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + ret = -1; + goto exit; + } + + printf("PQC origin server listening on port %d\n", port); + printf(" TLS 1.3 kex=%s auth=%s\n", PQC_GROUP_NAME, PQC_AUTH); + + ret = accept_loop(ctx); + if (ret == 0) + printf("Shutdown complete\n"); + +exit: + if (mConnd != SOCKET_INVALID) + close(mConnd); + if (mSockfd != SOCKET_INVALID) + close(mSockfd); + if (ctx) + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + + return ret; +} + +#else + +int main(void) +{ + printf("This example requires TLS 1.3 and ML-KEM (Kyber).\n"); + printf("Configure wolfSSL like this:\n"); + printf(" ./configure --enable-mlkem --enable-tls13\n"); + return 0; +} + +#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM */ diff --git a/pq/pqc_proxy/proxy-common.h b/pq/pqc_proxy/proxy-common.h new file mode 100644 index 000000000..c1527db43 --- /dev/null +++ b/pq/pqc_proxy/proxy-common.h @@ -0,0 +1,120 @@ +/* proxy-common.h + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * 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 + */ + +/* Shared configuration for the PQC crypto-proxy examples. + * + * Two proxy directions are demonstrated: + * + * 1. Quantum-safe front door (pq-proxy): + * pq-client =(PQC TLS 1.3)=> pq-proxy =(legacy TLS 1.2)=> legacy-server + * + * 2. Quantum-safe upgrade (upgrade-proxy): + * legacy-client =(legacy TLS 1.2)=> upgrade-proxy =(PQC TLS 1.3)=> pq-server + * + * In direction 1 the modern client gets a post-quantum key exchange while the + * legacy origin is left untouched. In direction 2 a legacy client that cannot + * speak PQC is transparently upgraded to a quantum-safe connection to a modern + * server. Either way the post-quantum (hybrid) key exchange protects the leg + * crossing the untrusted network against "harvest now, decrypt later" attacks. + * + * For direction 1 the macros below are named by position (FRONTEND_* is the + * PQC leg, BACKEND_* is the legacy leg). The neutral PQC_* / LEGACY_* aliases + * near the end of this file let direction 2 refer to each leg by its + * cryptography instead of its position. + */ + +#ifndef PROXY_COMMON_H +#define PROXY_COMMON_H + +#include +#include + +/* Ports used by the examples. */ +#define FRONTEND_PORT 11111 /* dir 1: clients connect to pq-proxy */ +#define BACKEND_PORT 11112 /* dir 1: the legacy origin listens here */ +#define BACKEND_HOST "127.0.0.1" + +#define UPGRADE_PORT 22221 /* dir 2: legacy clients connect here */ +#define PQSERVER_PORT 22222 /* dir 2: the PQC origin listens here */ +#define PQSERVER_HOST "127.0.0.1" + +/* ---- Frontend: post-quantum / hybrid TLS 1.3 ----------------------------- */ + +/* Hybrid key exchange: X25519-style ECDHE (here NIST P-256) combined with + * ML-KEM-768. The classical half keeps interop guarantees while the ML-KEM + * half provides the post-quantum protection. Override at build time with + * -DFRONTEND_GROUP=WOLFSSL_SECP384R1MLKEM1024 etc. for a higher security + * level, or a pure WOLFSSL_ML_KEM_768 group for non-hybrid PQC. */ +#ifndef FRONTEND_GROUP +#define FRONTEND_GROUP WOLFSSL_SECP256R1MLKEM768 +#define FRONTEND_GROUP_NAME "P256_ML_KEM_768" +#endif +#ifndef FRONTEND_GROUP_NAME +#define FRONTEND_GROUP_NAME "(custom)" +#endif + +/* Server authentication on the frontend. + * + * If the wolfSSL library was built with ML-DSA (Dilithium) support we use a + * post-quantum certificate chain so that BOTH key exchange and authentication + * are quantum-safe. Otherwise we fall back to a classical RSA certificate, + * which still gives a post-quantum *key exchange* - the most common real-world + * PQC deployment today, since CAs do not yet issue PQC certificates. */ +#ifdef HAVE_DILITHIUM + #define FRONTEND_CERT "../../certs/mldsa65_entity_cert.pem" + #define FRONTEND_KEY "../../certs/mldsa65_entity_key.pem" + #define FRONTEND_CA "../../certs/mldsa65_root_cert.pem" + #define FRONTEND_AUTH "ML-DSA-65 (post-quantum)" +#else + #define FRONTEND_CERT "../../certs/server-cert.pem" + #define FRONTEND_KEY "../../certs/server-key.pem" + #define FRONTEND_CA "../../certs/ca-cert.pem" + #define FRONTEND_AUTH "RSA-2048 (classical)" +#endif + +/* ---- Backend: legacy classical TLS 1.2 ----------------------------------- */ + +/* A deliberately old-school, non-PQC cipher suite. ECDHE-RSA still gives + * forward secrecy but offers no protection against a quantum adversary. */ +#define BACKEND_CIPHERS "ECDHE-RSA-AES128-GCM-SHA256" +#define BACKEND_CA "../../certs/ca-cert.pem" +#define BACKEND_CERT "../../certs/server-cert.pem" +#define BACKEND_KEY "../../certs/server-key.pem" + +/* ---- Neutral aliases ----------------------------------------------------- */ + +/* Refer to each leg by its cryptography rather than its position, so the + * upgrade-proxy (legacy frontend, PQC backend) reads naturally too. */ +#define PQC_GROUP FRONTEND_GROUP +#define PQC_GROUP_NAME FRONTEND_GROUP_NAME +#define PQC_CERT FRONTEND_CERT +#define PQC_KEY FRONTEND_KEY +#define PQC_CA FRONTEND_CA +#define PQC_AUTH FRONTEND_AUTH + +#define LEGACY_CIPHERS BACKEND_CIPHERS +#define LEGACY_CERT BACKEND_CERT +#define LEGACY_KEY BACKEND_KEY +#define LEGACY_CA BACKEND_CA + +#define PROXY_BUFFER_SZ 4096 + +#endif /* PROXY_COMMON_H */ diff --git a/pq/pqc_proxy/upgrade-proxy.c b/pq/pqc_proxy/upgrade-proxy.c new file mode 100644 index 000000000..745059216 --- /dev/null +++ b/pq/pqc_proxy/upgrade-proxy.c @@ -0,0 +1,370 @@ +/* upgrade-proxy.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * 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 + */ + +/* upgrade-proxy: a TLS terminating crypto-proxy that *upgrades* legacy clients. + * + * legacy-client =(legacy TLS 1.2)=> upgrade-proxy =(PQC TLS 1.3)=> pq-server + * + * This is the mirror image of pq-proxy. Here the proxy is: + * - a TLS *server* on the frontend, accepting a classical TLS 1.2 handshake + * from a legacy client that cannot speak PQC; + * - a TLS *client* on the backend, opening a quantum-safe (hybrid ML-KEM) + * TLS 1.3 connection to a modern origin server. + * + * It lets an un-upgradeable client still reach a service over a quantum-safe + * link, with the post-quantum key exchange protecting the proxy-to-server leg. + * Connections are handled one at a time to keep the example readable. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "proxy-common.h" + +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_HAVE_MLKEM) + +static int mListenfd = SOCKET_INVALID; +static int mShutdown = 0; + +static void sig_handler(const int sig) +{ + (void)sig; + mShutdown = 1; + if (mListenfd != SOCKET_INVALID) { + close(mListenfd); + mListenfd = SOCKET_INVALID; + } +} + +/* Open a TCP connection to the PQC origin server. */ +static int connect_backend(const char* host, int port) +{ + int sockfd; + struct sockaddr_in addr; + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + close(sockfd); + return -1; + } + + if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + close(sockfd); + return -1; + } + + return sockfd; +} + +/* Pump all currently available plaintext from src to dst. + * Returns 1 on success, 0 if the source closed, -1 on error. */ +static int pump(WOLFSSL* src, WOLFSSL* dst) +{ + char buff[PROXY_BUFFER_SZ]; + int n; + + do { + n = wolfSSL_read(src, buff, sizeof(buff)); + if (n <= 0) { + int err = wolfSSL_get_error(src, n); + if (err == WOLFSSL_ERROR_WANT_READ || + err == WOLFSSL_ERROR_WANT_WRITE) + return 1; + return (n == 0 || err == WOLFSSL_ERROR_ZERO_RETURN) ? 0 : -1; + } + + if (wolfSSL_write(dst, buff, n) != n) + return -1; + + /* wolfSSL may already hold further decrypted records that the socket + * select() would not report; drain while data remains buffered. */ + } while (wolfSSL_pending(src) > 0); + + return 1; +} + +/* Shuttle application data between the frontend and backend TLS sessions until + * either side closes the connection. */ +static void relay(WOLFSSL* front, int frontFd, WOLFSSL* back, int backFd) +{ + int maxFd = (frontFd > backFd ? frontFd : backFd) + 1; + + while (!mShutdown) { + fd_set readfds; + int rc; + + FD_ZERO(&readfds); + FD_SET(frontFd, &readfds); + FD_SET(backFd, &readfds); + + rc = select(maxFd, &readfds, NULL, NULL, NULL); + if (rc < 0) + break; + + if (FD_ISSET(frontFd, &readfds) || wolfSSL_pending(front) > 0) { + if (pump(front, back) <= 0) + break; + } + if (FD_ISSET(backFd, &readfds) || wolfSSL_pending(back) > 0) { + if (pump(back, front) <= 0) + break; + } + } +} + +static void print_leg(const char* label, WOLFSSL* ssl) +{ + const char* group = wolfSSL_get_curve_name(ssl); + printf(" %-9s %s %s kex=%s\n", label, + wolfSSL_get_version(ssl), wolfSSL_get_cipher(ssl), + group ? group : "(classical)"); +} + +/* Accept legacy clients on the frontend and bridge each one to a fresh + * quantum-safe backend connection until a shutdown is requested. */ +static int accept_loop(WOLFSSL_CTX* frontCtx, WOLFSSL_CTX* backCtx, + const char* backHost, int backPort) +{ + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + + while (!mShutdown) { + int frontFd = SOCKET_INVALID; + int backFd = SOCKET_INVALID; + WOLFSSL* front = NULL; + WOLFSSL* back = NULL; + + printf("\nWaiting for a client...\n"); + frontFd = accept(mListenfd, (struct sockaddr*)&clientAddr, &size); + if (frontFd == -1) { + if (mShutdown) + break; + fprintf(stderr, "ERROR: failed to accept\n"); + continue; + } + + /* Terminate the legacy client connection. */ + if ((front = wolfSSL_new(frontCtx)) == NULL) { + fprintf(stderr, "ERROR: failed to create frontend WOLFSSL\n"); + goto conn_cleanup; + } + wolfSSL_set_fd(front, frontFd); + if (wolfSSL_accept(front) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: frontend handshake failed: %d\n", + wolfSSL_get_error(front, 0)); + goto conn_cleanup; + } + + /* Open the quantum-safe connection to the origin. */ + backFd = connect_backend(backHost, backPort); + if (backFd < 0) { + fprintf(stderr, "ERROR: failed to reach backend %s:%d\n", + backHost, backPort); + goto conn_cleanup; + } + if ((back = wolfSSL_new(backCtx)) == NULL) { + fprintf(stderr, "ERROR: failed to create backend WOLFSSL\n"); + goto conn_cleanup; + } + wolfSSL_set_fd(back, backFd); + /* Pre-generate the hybrid key share so the very first ClientHello to + * the origin already carries the post-quantum group. */ + if (wolfSSL_UseKeyShare(back, PQC_GROUP) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set backend key share\n"); + goto conn_cleanup; + } + if (wolfSSL_connect(back) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: backend handshake failed: %d\n", + wolfSSL_get_error(back, 0)); + goto conn_cleanup; + } + + printf("Bridging connection:\n"); + print_leg("frontend", front); + print_leg("backend", back); + + relay(front, frontFd, back, backFd); + printf("Connection closed\n"); + +conn_cleanup: + if (front) { + wolfSSL_shutdown(front); + wolfSSL_free(front); + } + if (back) { + wolfSSL_shutdown(back); + wolfSSL_free(back); + } + if (frontFd != SOCKET_INVALID) + close(frontFd); + if (backFd != SOCKET_INVALID) + close(backFd); + } + + return 0; +} + +int main(int argc, char** argv) +{ + int ret = 0; + int frontPort = UPGRADE_PORT; + const char* backHost = PQSERVER_HOST; + int backPort = PQSERVER_PORT; + int on = 1; + + struct sockaddr_in servAddr; + + /* The backend group is restricted to a single post-quantum (hybrid) group + * so that the proxy-to-server leg is always quantum-safe. */ + int backGroups[] = { PQC_GROUP }; + + WOLFSSL_CTX* frontCtx = NULL; /* frontend: legacy TLS 1.2 server */ + WOLFSSL_CTX* backCtx = NULL; /* backend: PQC TLS 1.3 client */ + + if (argc >= 2) frontPort = atoi(argv[1]); + if (argc >= 3) backHost = argv[2]; + if (argc >= 4) backPort = atoi(argv[3]); + if (argc > 4) { + printf("usage: %s [ [ " + "[]]]\n", argv[0]); + printf("Defaults: %d %s %d\n", UPGRADE_PORT, PQSERVER_HOST, + PQSERVER_PORT); + return 0; + } + + signal(SIGINT, sig_handler); + + wolfSSL_Init(); + + /* ---- Frontend context: legacy classical TLS 1.2 server --------------- */ + if ((frontCtx = wolfSSL_CTX_new(wolfTLSv1_2_server_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create frontend WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + if ((ret = wolfSSL_CTX_set_cipher_list(frontCtx, LEGACY_CIPHERS)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set frontend cipher list\n"); + goto exit; + } + if ((ret = wolfSSL_CTX_use_certificate_file(frontCtx, LEGACY_CERT, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", LEGACY_CERT); + goto exit; + } + if ((ret = wolfSSL_CTX_use_PrivateKey_file(frontCtx, LEGACY_KEY, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", LEGACY_KEY); + goto exit; + } + + /* ---- Backend context: post-quantum / hybrid TLS 1.3 client ----------- */ + if ((backCtx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create backend WOLFSSL_CTX\n"); + ret = -1; + goto exit; + } + if ((ret = wolfSSL_CTX_load_verify_locations(backCtx, PQC_CA, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", PQC_CA); + goto exit; + } + /* Only negotiate the hybrid PQC group with the origin. */ + if ((ret = wolfSSL_CTX_set_groups(backCtx, backGroups, + sizeof(backGroups) / sizeof(backGroups[0]))) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set backend PQC group\n"); + goto exit; + } + + /* ---- Listen on the frontend port ------------------------------------- */ + if ((mListenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; + goto exit; + } + setsockopt(mListenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(frontPort); + servAddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(mListenfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + ret = -1; + goto exit; + } + if (listen(mListenfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + ret = -1; + goto exit; + } + + printf("PQC upgrade-proxy ready\n"); + printf(" frontend : 0.0.0.0:%d TLS 1.2 %s\n", + frontPort, LEGACY_CIPHERS); + printf(" backend : %s:%d TLS 1.3 kex=%s auth=%s\n", + backHost, backPort, PQC_GROUP_NAME, PQC_AUTH); + + ret = accept_loop(frontCtx, backCtx, backHost, backPort); + + printf("Shutdown complete\n"); + +exit: + if (mListenfd != SOCKET_INVALID) + close(mListenfd); + if (frontCtx) + wolfSSL_CTX_free(frontCtx); + if (backCtx) + wolfSSL_CTX_free(backCtx); + wolfSSL_Cleanup(); + + return ret; +} + +#else + +int main(void) +{ + printf("This example requires TLS 1.3 and ML-KEM (Kyber).\n"); + printf("Configure wolfSSL like this:\n"); + printf(" ./configure --enable-mlkem --enable-tls13\n"); + printf("Add --enable-mldsa for post-quantum authentication too.\n"); + return 0; +} + +#endif /* WOLFSSL_TLS13 && WOLFSSL_HAVE_MLKEM */