Merge pull request #578 from SparkiDev/pqc_proxy_examples

PQC proxy
pull/318/merge
Paul Adelsbach 2026-07-10 16:06:09 -06:00 committed by GitHub
commit a627f04342
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1876 additions and 0 deletions

8
.gitignore vendored
View File

@ -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

View File

@ -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)

View File

@ -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 [<listen port>]
./pq-proxy [<frontend port> [<backend host> [<backend port>]]]
./pq-client [<proxy host> [<proxy port>]]
./pq-server [<listen port>]
./upgrade-proxy [<frontend port> [<backend host> [<backend port>]]]
./legacy-client [<proxy host> [<proxy port>]]
```
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.

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#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 [<proxy host> [<proxy port>]]\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;
}

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#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 [<listen port>]\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;
}

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#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 [<proxy host> [<proxy port>]]\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 */

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#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 [<frontend port> [<backend host> "
"[<backend port>]]]\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 */

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#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 [<listen port>]\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 */

View File

@ -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 <wolfssl/options.h>
#include <wolfssl/ssl.h>
/* 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 */

View File

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#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 [<frontend port> [<backend host> "
"[<backend port>]]]\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 */