From f7dd12f03105f1cf41d3d1facce37578019f6cd7 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 23 Oct 2026 15:26:06 -0700 Subject: [PATCH] Add ML-KEM and ML-DSA crypto callback examples --- .github/examples-manifest.yml | 33 ++++ pq/ml_dsa_cryptocb/Makefile | 22 +++ pq/ml_dsa_cryptocb/README.md | 45 +++++ pq/ml_dsa_cryptocb/ml_dsa_cryptocb.c | 264 +++++++++++++++++++++++++++ pq/ml_kem_cryptocb/Makefile | 22 +++ pq/ml_kem_cryptocb/README.md | 51 ++++++ pq/ml_kem_cryptocb/ml_kem_cryptocb.c | 260 ++++++++++++++++++++++++++ pq/tls_cryptocb/Makefile | 23 +++ pq/tls_cryptocb/README.md | 60 ++++++ pq/tls_cryptocb/client-tls13-mlkem.c | 204 +++++++++++++++++++++ pq/tls_cryptocb/mlkem-cryptocb.c | 96 ++++++++++ pq/tls_cryptocb/mlkem-cryptocb.h | 57 ++++++ pq/tls_cryptocb/server-tls13-mlkem.c | 186 +++++++++++++++++++ 13 files changed, 1323 insertions(+) create mode 100644 pq/ml_dsa_cryptocb/Makefile create mode 100644 pq/ml_dsa_cryptocb/README.md create mode 100644 pq/ml_dsa_cryptocb/ml_dsa_cryptocb.c create mode 100644 pq/ml_kem_cryptocb/Makefile create mode 100644 pq/ml_kem_cryptocb/README.md create mode 100644 pq/ml_kem_cryptocb/ml_kem_cryptocb.c create mode 100644 pq/tls_cryptocb/Makefile create mode 100644 pq/tls_cryptocb/README.md create mode 100644 pq/tls_cryptocb/client-tls13-mlkem.c create mode 100644 pq/tls_cryptocb/mlkem-cryptocb.c create mode 100644 pq/tls_cryptocb/mlkem-cryptocb.h create mode 100644 pq/tls_cryptocb/server-tls13-mlkem.c diff --git a/.github/examples-manifest.yml b/.github/examples-manifest.yml index d36a76a5c..b41aa483d 100644 --- a/.github/examples-manifest.yml +++ b/.github/examples-manifest.yml @@ -162,6 +162,16 @@ profiles: --enable-extra-pqc-hybrids --enable-slhdsa=yes,sha2 --enable-experimental --enable-tls13 --enable-static --enable-shared + pq-cryptocb: + # pq/ml_kem_cryptocb, pq/ml_dsa_cryptocb and pq/tls_cryptocb register a + # crypto callback that offloads the ML-KEM / ML-DSA operations, so they add + # --enable-cryptocb to the mlkem/dilithium flags their READMEs document. + # tls13 and the default ML-KEM hybrid groups (WOLFSSL_PQC_HYBRIDS) are on by + # default, which is what pq/tls_cryptocb needs. + flags: >- + --enable-cryptocb --enable-mlkem --enable-dilithium + --enable-experimental --enable-static --enable-shared + acert: flags: "--enable-acert --enable-opensslextra --enable-rsapss --enable-static --enable-shared" @@ -623,6 +633,29 @@ examples: profile: pq mode: check + - id: pq-ml-kem-cryptocb + path: pq/ml_kem_cryptocb + profile: pq-cryptocb + mode: check + + - id: pq-ml-dsa-cryptocb + path: pq/ml_dsa_cryptocb + profile: pq-cryptocb + mode: check + + - id: pq-tls-cryptocb + path: pq/tls_cryptocb + profile: pq-cryptocb + run: + - pair: + server: [./server-tls13-mlkem] + client: [./client-tls13-mlkem, 127.0.0.1] + stdin: "hello mlkem cryptocb\n" + server_exit: clean + # the server replies with a fixed string once the SecP256r1MLKEM768 + # handshake completes, proving the ML-KEM key exchange carried data. + expect: "Server: I hear ya fa shizzle" + - id: pq-slh-dsa path: pq/slh_dsa profile: pq diff --git a/pq/ml_dsa_cryptocb/Makefile b/pq/ml_dsa_cryptocb/Makefile new file mode 100644 index 000000000..dd8c1304e --- /dev/null +++ b/pq/ml_dsa_cryptocb/Makefile @@ -0,0 +1,22 @@ +CC = gcc + +WOLFSSL_INSTALL_DIR = /usr/local + +WOLFSSL_CFLAGS = -Wextra -Werror -Wall -I$(WOLFSSL_INSTALL_DIR)/include +WOLFSSL_LIBS = -L$(WOLFSSL_INSTALL_DIR)/lib -lm -lwolfssl + +DEBUG_FLAGS = -g -DDEBUG + +all: ml_dsa_cryptocb + +ml_dsa_cryptocb: ml_dsa_cryptocb.c + $(CC) -o $@ $^ $(WOLFSSL_CFLAGS) $(WOLFSSL_LIBS) $(DEBUG_FLAGS) + +.PHONY: clean all check + +clean: + rm -f *.o ml_dsa_cryptocb + +check: ml_dsa_cryptocb + out=$$(./ml_dsa_cryptocb) && printf '%s' "$$out" | grep -q 'via crypto callback' + @echo "PASS: pq-ml-dsa-cryptocb checks" diff --git a/pq/ml_dsa_cryptocb/README.md b/pq/ml_dsa_cryptocb/README.md new file mode 100644 index 000000000..88e8b662b --- /dev/null +++ b/pq/ml_dsa_cryptocb/README.md @@ -0,0 +1,45 @@ +# Description + +ML-DSA key generation, signing and verification routed through the wolfCrypt +crypto callback (cryptocb) interface. wolfSSL dispatches the signature +operations under `WC_ALGO_TYPE_PK` to a registered callback, which selects +ML-DSA with `WC_PQC_SIG_TYPE_MLDSA`. + +The callback handles three operation types: + +- `WC_PK_TYPE_PQC_SIG_KEYGEN` (`info->pk.pqc_sig_kg`) - generate a key pair. +- `WC_PK_TYPE_PQC_SIG_SIGN` (`info->pk.pqc_sign`) - sign a message. +- `WC_PK_TYPE_PQC_SIG_VERIFY` (`info->pk.pqc_verify`) - verify a signature. + +The example signs and verifies with an empty context (FIPS 204 pure signing); +a pre-hash request falls back to software. The callback delegates back to the +wolfCrypt software implementation so it runs without hardware. A real device +replaces each case body with its own calls and keeps the private key on-chip. +It is registered with `wc_CryptoCb_RegisterDevice()`; for TLS, associate it +with a context using `wolfSSL_CTX_SetDevId()`. + +Requires wolfSSL built with: + +```sh +$ ./autogen.sh +$ ./configure --enable-cryptocb --enable-dilithium +$ make +$ sudo make install +$ sudo ldconfig +``` + +Build the example: + +```sh +$ make +``` + +# Usage + +``` +$ ./ml_dsa_cryptocb +``` + +The ML-DSA key is initialized with a callback device id, so key generation, +signing and verification all dispatch to the callback. The program prints the +per-operation callback counts and confirms the signature verified. diff --git a/pq/ml_dsa_cryptocb/ml_dsa_cryptocb.c b/pq/ml_dsa_cryptocb/ml_dsa_cryptocb.c new file mode 100644 index 000000000..c471f61a0 --- /dev/null +++ b/pq/ml_dsa_cryptocb/ml_dsa_cryptocb.c @@ -0,0 +1,264 @@ +/* ml_dsa_cryptocb.c + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * 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 + */ + +/* Offload ML-DSA key generation, signing and verification through the + * wolfCrypt crypto callback (cryptocb) interface. wolfSSL routes the signature + * operations to the registered callback under WC_ALGO_TYPE_PK, selecting + * ML-DSA with WC_PQC_SIG_TYPE_MLDSA. + * + * The callback here delegates back to the wolfCrypt software implementation so + * the example runs without hardware. A real device replaces each case body + * with its own calls and keeps the private key on-chip. + * + * Build wolfSSL with: ./configure --enable-cryptocb --enable-dilithium + */ + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include +#include +#include + +#include +#include +#include + +#if defined(WOLF_CRYPTO_CB) && defined(HAVE_DILITHIUM) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && !defined(WOLFSSL_MLDSA_NO_SIGN) && \ + !defined(WOLFSSL_MLDSA_NO_VERIFY) && !defined(WOLFSSL_MLDSA_NO_CTX) + +/* Any value except INVALID_DEVID (-2). */ +#define ML_DSA_CB_DEVID 1 + +/* FIPS 204 security category: 2 = ML-DSA-44, 3 = ML-DSA-65, 5 = ML-DSA-87. */ +#define ML_DSA_CB_LEVEL 2 + +typedef struct MyCryptoCbCtx { + int keygenCount; + int signCount; + int verifyCount; +} MyCryptoCbCtx; + +/* Crypto callback. Handles ML-DSA keygen/sign/verify (pure, empty-context) and + * returns CRYPTOCB_UNAVAILABLE for everything else so wolfCrypt falls back to + * software. + * + * Recursion is avoided by clearing key->devId before calling the public API + * and restoring it afterwards: the signature dispatch in wc_mldsa.c only fires + * when key->devId != INVALID_DEVID. */ +static int myPqcSigCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + int ret = CRYPTOCB_UNAVAILABLE; + MyCryptoCbCtx* myCtx = (MyCryptoCbCtx*)ctx; + MlDsaKey* key = NULL; + int savedId = INVALID_DEVID; + + (void)devId; + + if (info->algo_type != WC_ALGO_TYPE_PK) + return CRYPTOCB_UNAVAILABLE; + + switch (info->pk.type) { + case WC_PK_TYPE_PQC_SIG_KEYGEN: + if (info->pk.pqc_sig_kg.type != WC_PQC_SIG_TYPE_MLDSA) + break; + key = (MlDsaKey*)info->pk.pqc_sig_kg.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlDsaKey_MakeKey(key, info->pk.pqc_sig_kg.rng); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->keygenCount++; + break; + + case WC_PK_TYPE_PQC_SIG_SIGN: + if (info->pk.pqc_sign.type != WC_PQC_SIG_TYPE_MLDSA) + break; + /* Only the pure (non pre-hash) path is handled here; a pre-hash + * request falls back to software. */ + if ((enum wc_HashType)info->pk.pqc_sign.preHashType != + WC_HASH_TYPE_NONE) + break; + key = (MlDsaKey*)info->pk.pqc_sign.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlDsaKey_SignCtx(key, info->pk.pqc_sign.context, + info->pk.pqc_sign.contextLen, info->pk.pqc_sign.out, + info->pk.pqc_sign.outlen, info->pk.pqc_sign.in, + info->pk.pqc_sign.inlen, info->pk.pqc_sign.rng); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->signCount++; + break; + + case WC_PK_TYPE_PQC_SIG_VERIFY: + if (info->pk.pqc_verify.type != WC_PQC_SIG_TYPE_MLDSA) + break; + if ((enum wc_HashType)info->pk.pqc_verify.preHashType != + WC_HASH_TYPE_NONE) + break; + key = (MlDsaKey*)info->pk.pqc_verify.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + /* SIG_VERIFY_E passes back unchanged; it reports a bad signature. */ + ret = wc_MlDsaKey_VerifyCtx(key, info->pk.pqc_verify.sig, + info->pk.pqc_verify.siglen, info->pk.pqc_verify.context, + info->pk.pqc_verify.contextLen, info->pk.pqc_verify.msg, + info->pk.pqc_verify.msglen, info->pk.pqc_verify.res); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->verifyCount++; + break; + + default: + break; + } + + return ret; +} + +int main(void) +{ + MlDsaKey key; + WC_RNG rng; + MyCryptoCbCtx myCtx = { 0, 0, 0 }; + const char* msg = "wolfSSL ML-DSA crypto callback example"; + byte* sig = NULL; + word32 sigLen = 0; + int sigSz = 0; + int verifyRes = 0; + int ret; + int wcInit = 0; + int rngInit = 0; + int keyInit = 0; + int devRegistered = 0; + + printf("ML-DSA sign/verify through the wolfCrypt crypto callback\n\n"); + + /* Initializes the crypto callback device table. */ + ret = wolfCrypt_Init(); + if (ret == 0) + wcInit = 1; + else + printf("wolfCrypt_Init failed: %d\n", ret); + + if (ret == 0) { + if (wc_CryptoCb_RegisterDevice(ML_DSA_CB_DEVID, myPqcSigCb, &myCtx) == 0) + devRegistered = 1; + else { + printf("wc_CryptoCb_RegisterDevice failed\n"); + ret = -1; + } + } + + if (ret == 0) { + ret = wc_InitRng(&rng); + if (ret == 0) + rngInit = 1; + } + + /* Key initialized with the callback devId so every operation dispatches to + * myPqcSigCb. */ + if (ret == 0) { + ret = wc_MlDsaKey_Init(&key, NULL, ML_DSA_CB_DEVID); + if (ret == 0) + keyInit = 1; + } + + if (ret == 0) + ret = wc_MlDsaKey_SetParams(&key, ML_DSA_CB_LEVEL); + + /* Key generation (callback). */ + if (ret == 0) + ret = wc_MlDsaKey_MakeKey(&key, &rng); + + if (ret == 0) + ret = wc_MlDsaKey_GetSigLen(&key, &sigSz); + if (ret == 0 && sigSz > 0) { + sig = (byte*)malloc((size_t)sigSz); + if (sig == NULL) + ret = MEMORY_E; + } + + /* Sign (callback). ctx=NULL/ctxLen=0 is FIPS 204 signing with an empty + * context. */ + if (ret == 0) { + sigLen = (word32)sigSz; + ret = wc_MlDsaKey_SignCtx(&key, NULL, 0, sig, &sigLen, + (const byte*)msg, (word32)strlen(msg), &rng); + } + + /* Verify (callback). */ + if (ret == 0) + ret = wc_MlDsaKey_VerifyCtx(&key, sig, sigLen, NULL, 0, + (const byte*)msg, (word32)strlen(msg), &verifyRes); + + if (ret == 0 && verifyRes != 1) { + printf("Signature did not verify\n"); + ret = -1; + } + + /* Prove the operations actually went through the callback. */ + if (ret == 0 && (myCtx.keygenCount < 1 || myCtx.signCount < 1 || + myCtx.verifyCount < 1)) { + printf("Crypto callback was not exercised (keygen=%d sign=%d " + "verify=%d)\n", myCtx.keygenCount, myCtx.signCount, + myCtx.verifyCount); + ret = -1; + } + + if (ret == 0) { + printf("keygen=%d sign=%d verify=%d via crypto callback\n", + myCtx.keygenCount, myCtx.signCount, myCtx.verifyCount); + printf("Signature verified\n"); + } + else { + printf("An error occurred: %d\n", ret); + } + + if (sig != NULL) + free(sig); + if (keyInit) + wc_MlDsaKey_Free(&key); + if (rngInit) + wc_FreeRng(&rng); + if (devRegistered) + wc_CryptoCb_UnRegisterDevice(ML_DSA_CB_DEVID); + if (wcInit) + wolfCrypt_Cleanup(); + + /* not ret: an exit code is masked to 8 bits, so -256 would read as 0 */ + return (ret == 0) ? 0 : 1; +} + +#else + +int main(void) +{ + printf("Example requires wolfSSL built with " + "--enable-cryptocb --enable-dilithium\n"); + return 0; +} + +#endif /* WOLF_CRYPTO_CB && HAVE_DILITHIUM && sign/verify/ctx */ diff --git a/pq/ml_kem_cryptocb/Makefile b/pq/ml_kem_cryptocb/Makefile new file mode 100644 index 000000000..42b62b069 --- /dev/null +++ b/pq/ml_kem_cryptocb/Makefile @@ -0,0 +1,22 @@ +CC = gcc + +WOLFSSL_INSTALL_DIR = /usr/local + +WOLFSSL_CFLAGS = -Wextra -Werror -Wall -I$(WOLFSSL_INSTALL_DIR)/include +WOLFSSL_LIBS = -L$(WOLFSSL_INSTALL_DIR)/lib -lm -lwolfssl + +DEBUG_FLAGS = -g -DDEBUG + +all: ml_kem_cryptocb + +ml_kem_cryptocb: ml_kem_cryptocb.c + $(CC) -o $@ $^ $(WOLFSSL_CFLAGS) $(WOLFSSL_LIBS) $(DEBUG_FLAGS) + +.PHONY: clean all check + +clean: + rm -f *.o ml_kem_cryptocb + +check: ml_kem_cryptocb + out=$$(./ml_kem_cryptocb) && printf '%s' "$$out" | grep -q 'via crypto callback' + @echo "PASS: pq-ml-kem-cryptocb checks" diff --git a/pq/ml_kem_cryptocb/README.md b/pq/ml_kem_cryptocb/README.md new file mode 100644 index 000000000..65c731b3d --- /dev/null +++ b/pq/ml_kem_cryptocb/README.md @@ -0,0 +1,51 @@ +# Description + +ML-KEM key generation, encapsulation and decapsulation routed through the +wolfCrypt crypto callback (cryptocb) interface. This is how a secure element or +other offload target implements ML-KEM for TLS 1.3: wolfSSL dispatches the KEM +operations under `WC_ALGO_TYPE_PK` to a registered callback, which selects +ML-KEM with `WC_PQC_KEM_TYPE_MLKEM`. + +The callback handles three operation types: + +- `WC_PK_TYPE_PQC_KEM_KEYGEN` (`info->pk.pqc_kem_kg`) - generate a key pair. In + TLS 1.3 the client does this for its key_share. +- `WC_PK_TYPE_PQC_KEM_ENCAPS` (`info->pk.pqc_encaps`) - encapsulate to a public + key. In TLS 1.3 the server does this. +- `WC_PK_TYPE_PQC_KEM_DECAPS` (`info->pk.pqc_decaps`) - decapsulate a + ciphertext. In TLS 1.3 the client does this. + +A TLS 1.3 client therefore needs KEYGEN and DECAPS; a server needs ENCAPS. + +The example's callback delegates back to the wolfCrypt software implementation +so it runs without hardware. A real device replaces each case body with its own +calls and keeps the private key on-chip. It is registered with +`wc_CryptoCb_RegisterDevice()`; for TLS, associate it with a context using +`wolfSSL_CTX_SetDevId()`. + +Requires wolfSSL built with: + +```sh +$ ./autogen.sh +$ ./configure --enable-cryptocb --enable-mlkem +$ make +$ sudo make install +$ sudo ldconfig +``` + +Build the example: + +```sh +$ make +``` + +# Usage + +``` +$ ./ml_kem_cryptocb +``` + +The ML-KEM keys are initialized with a callback device id, so key generation, +encapsulation and decapsulation all dispatch to the callback. The program +prints the per-operation callback counts and confirms both sides derived the +same shared secret. diff --git a/pq/ml_kem_cryptocb/ml_kem_cryptocb.c b/pq/ml_kem_cryptocb/ml_kem_cryptocb.c new file mode 100644 index 000000000..a30fc6bd6 --- /dev/null +++ b/pq/ml_kem_cryptocb/ml_kem_cryptocb.c @@ -0,0 +1,260 @@ +/* ml_kem_cryptocb.c + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * 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 + */ + +/* Offload ML-KEM key generation, encapsulation and decapsulation through the + * wolfCrypt crypto callback (cryptocb) interface. This is the path a secure + * element such as a TLS 1.3 peer would use: wolfSSL routes the KEM operations + * to the registered callback under WC_ALGO_TYPE_PK, selecting ML-KEM with + * WC_PQC_KEM_TYPE_MLKEM. In TLS 1.3 a client needs KEYGEN and DECAPS; a server + * needs ENCAPS. + * + * The callback here delegates back to the wolfCrypt software implementation so + * the example runs without hardware. A real device replaces each case body + * with its own calls and keeps the private key on-chip. + * + * Build wolfSSL with: ./configure --enable-cryptocb --enable-mlkem + */ + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include +#include +#include + +#include + +#if defined(WOLF_CRYPTO_CB) && defined(WOLFSSL_HAVE_MLKEM) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + +/* Any value except INVALID_DEVID (-2). Passed to wc_MlKemKey_Init and matched + * by the registered callback. */ +#define ML_KEM_CB_DEVID 1 + +typedef struct MyCryptoCbCtx { + int keygenCount; + int encapCount; + int decapCount; +} MyCryptoCbCtx; + +/* Crypto callback. Handles the three ML-KEM KEM operations and returns + * CRYPTOCB_UNAVAILABLE for everything else so wolfCrypt falls back to + * software. + * + * Recursion is avoided by clearing key->devId before calling the public API + * and restoring it afterwards: the KEM dispatch in wc_mlkem.c only fires when + * key->devId != INVALID_DEVID, so the delegated call runs in software and the + * key still dispatches here on the next operation. */ +static int myPqcKemCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + int ret = CRYPTOCB_UNAVAILABLE; + MyCryptoCbCtx* myCtx = (MyCryptoCbCtx*)ctx; + MlKemKey* key = NULL; + int savedId = INVALID_DEVID; + + (void)devId; + + if (info->algo_type != WC_ALGO_TYPE_PK) + return CRYPTOCB_UNAVAILABLE; + + switch (info->pk.type) { + case WC_PK_TYPE_PQC_KEM_KEYGEN: + if (info->pk.pqc_kem_kg.type != WC_PQC_KEM_TYPE_MLKEM) + break; + key = (MlKemKey*)info->pk.pqc_kem_kg.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlKemKey_MakeKey(key, info->pk.pqc_kem_kg.rng); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->keygenCount++; + break; + + case WC_PK_TYPE_PQC_KEM_ENCAPS: + if (info->pk.pqc_encaps.type != WC_PQC_KEM_TYPE_MLKEM) + break; + key = (MlKemKey*)info->pk.pqc_encaps.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlKemKey_Encapsulate(key, info->pk.pqc_encaps.ciphertext, + info->pk.pqc_encaps.sharedSecret, info->pk.pqc_encaps.rng); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->encapCount++; + break; + + case WC_PK_TYPE_PQC_KEM_DECAPS: + if (info->pk.pqc_decaps.type != WC_PQC_KEM_TYPE_MLKEM) + break; + key = (MlKemKey*)info->pk.pqc_decaps.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlKemKey_Decapsulate(key, info->pk.pqc_decaps.sharedSecret, + info->pk.pqc_decaps.ciphertext, info->pk.pqc_decaps.ciphertextLen); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->decapCount++; + break; + + default: + break; + } + + return ret; +} + +int main(void) +{ + MlKemKey aliceKey; + MlKemKey bobKey; + WC_RNG rng; + MyCryptoCbCtx myCtx = { 0, 0, 0 }; + int ret; + int wcInit = 0; + int rngInit = 0; + int aliceInit = 0; + int bobInit = 0; + int devRegistered = 0; + int i; + byte diff = 0; + byte alicePub[WC_ML_KEM_512_PUBLIC_KEY_SIZE]; + byte bobCt[WC_ML_KEM_512_CIPHER_TEXT_SIZE]; + byte aliceSs[WC_ML_KEM_SS_SZ]; + byte bobSs[WC_ML_KEM_SS_SZ]; + + printf("ML-KEM-512 key exchange through the wolfCrypt crypto callback\n\n"); + + /* Initializes the crypto callback device table. */ + ret = wolfCrypt_Init(); + if (ret == 0) + wcInit = 1; + else + printf("wolfCrypt_Init failed: %d\n", ret); + + if (ret == 0) { + if (wc_CryptoCb_RegisterDevice(ML_KEM_CB_DEVID, myPqcKemCb, &myCtx) == 0) + devRegistered = 1; + else { + printf("wc_CryptoCb_RegisterDevice failed\n"); + ret = -1; + } + } + + if (ret == 0) { + ret = wc_InitRng(&rng); + if (ret == 0) + rngInit = 1; + } + + /* Keys are initialized with the callback devId so every KEM operation + * dispatches to myPqcKemCb. */ + if (ret == 0) { + ret = wc_MlKemKey_Init(&aliceKey, WC_ML_KEM_512, NULL, ML_KEM_CB_DEVID); + if (ret == 0) + aliceInit = 1; + } + + if (ret == 0) { + ret = wc_MlKemKey_Init(&bobKey, WC_ML_KEM_512, NULL, ML_KEM_CB_DEVID); + if (ret == 0) + bobInit = 1; + } + + /* Alice: key generation (callback). */ + if (ret == 0) + ret = wc_MlKemKey_MakeKey(&aliceKey, &rng); + if (ret == 0) + ret = wc_MlKemKey_EncodePublicKey(&aliceKey, alicePub, + WC_ML_KEM_512_PUBLIC_KEY_SIZE); + + /* Bob: encapsulate to Alice's public key (callback). */ + if (ret == 0) + ret = wc_MlKemKey_DecodePublicKey(&bobKey, alicePub, + WC_ML_KEM_512_PUBLIC_KEY_SIZE); + if (ret == 0) + ret = wc_MlKemKey_Encapsulate(&bobKey, bobCt, bobSs, &rng); + + /* Alice: decapsulate Bob's ciphertext (callback). */ + if (ret == 0) + ret = wc_MlKemKey_Decapsulate(&aliceKey, aliceSs, bobCt, + WC_ML_KEM_512_CIPHER_TEXT_SIZE); + + /* Both sides must derive the same secret. Accumulate rather than break + * early; the secrets are secret data. */ + if (ret == 0) { + for (i = 0; i < WC_ML_KEM_SS_SZ; i++) + diff |= (byte)(aliceSs[i] ^ bobSs[i]); + if (diff != 0) { + printf("Shared secrets do not match\n"); + ret = -1; + } + } + + /* Prove the operations actually went through the callback. */ + if (ret == 0 && (myCtx.keygenCount < 1 || myCtx.encapCount < 1 || + myCtx.decapCount < 1)) { + printf("Crypto callback was not exercised (keygen=%d encaps=%d " + "decaps=%d)\n", myCtx.keygenCount, myCtx.encapCount, + myCtx.decapCount); + ret = -1; + } + + if (ret == 0) { + printf("keygen=%d encapsulate=%d decapsulate=%d via crypto callback\n", + myCtx.keygenCount, myCtx.encapCount, myCtx.decapCount); + printf("Shared secrets match\n"); + } + else { + printf("An error occurred: %d\n", ret); + } + + wc_ForceZero(aliceSs, sizeof(aliceSs)); + wc_ForceZero(bobSs, sizeof(bobSs)); + + if (aliceInit) + wc_MlKemKey_Free(&aliceKey); + if (bobInit) + wc_MlKemKey_Free(&bobKey); + if (rngInit) + wc_FreeRng(&rng); + if (devRegistered) + wc_CryptoCb_UnRegisterDevice(ML_KEM_CB_DEVID); + if (wcInit) + wolfCrypt_Cleanup(); + + /* not ret: an exit code is masked to 8 bits, so -256 would read as 0 */ + return (ret == 0) ? 0 : 1; +} + +#else + +int main(void) +{ + printf("Example requires wolfSSL built with " + "--enable-cryptocb --enable-mlkem\n"); + return 0; +} + +#endif /* WOLF_CRYPTO_CB && WOLFSSL_HAVE_MLKEM && make/encaps/decaps */ diff --git a/pq/tls_cryptocb/Makefile b/pq/tls_cryptocb/Makefile new file mode 100644 index 000000000..bbe912930 --- /dev/null +++ b/pq/tls_cryptocb/Makefile @@ -0,0 +1,23 @@ +CC = gcc + +WOLFSSL_INSTALL_DIR = /usr/local + +WOLFSSL_CFLAGS = -Wextra -Werror -Wall -I$(WOLFSSL_INSTALL_DIR)/include +WOLFSSL_LIBS = -L$(WOLFSSL_INSTALL_DIR)/lib -lm -lwolfssl + +DEBUG_FLAGS = -g -DDEBUG + +COMMON = mlkem-cryptocb.c + +all: client-tls13-mlkem server-tls13-mlkem + +client-tls13-mlkem: client-tls13-mlkem.c $(COMMON) + $(CC) -o $@ $^ $(WOLFSSL_CFLAGS) $(WOLFSSL_LIBS) $(DEBUG_FLAGS) + +server-tls13-mlkem: server-tls13-mlkem.c $(COMMON) + $(CC) -o $@ $^ $(WOLFSSL_CFLAGS) $(WOLFSSL_LIBS) $(DEBUG_FLAGS) + +.PHONY: clean all + +clean: + rm -f *.o client-tls13-mlkem server-tls13-mlkem diff --git a/pq/tls_cryptocb/README.md b/pq/tls_cryptocb/README.md new file mode 100644 index 000000000..98bf78c8e --- /dev/null +++ b/pq/tls_cryptocb/README.md @@ -0,0 +1,60 @@ +# Description + +TLS 1.3 client and server that offload the ML-KEM key exchange (the key_share) +through the wolfCrypt crypto callback (cryptocb) interface. This is the path a +secure element uses to handle post-quantum key exchange for TLS 1.3. + +wolfSSL dispatches the KEM operations under `WC_ALGO_TYPE_PK` to the registered +callback (`pq/tls_cryptocb/mlkem-cryptocb.c`), selecting ML-KEM with +`WC_PQC_KEM_TYPE_MLKEM`: + +- the client generates its key pair (`WC_PK_TYPE_PQC_KEM_KEYGEN`) and recovers + the shared secret (`WC_PK_TYPE_PQC_KEM_DECAPS`); +- the server encapsulates to the client's public key + (`WC_PK_TYPE_PQC_KEM_ENCAPS`). + +The callback is shared by both programs. It is registered with +`wc_CryptoCb_RegisterDevice()` and bound to the context with +`wolfSSL_CTX_SetDevId()`; the devId then flows into the handshake's ML-KEM keys +automatically. The client requests the ML-KEM-768 hybrid group with +`wolfSSL_UseKeyShare(ssl, WOLFSSL_SECP256R1MLKEM768)` (ECC P-256 + ML-KEM-768); +its ML-KEM half is what dispatches to the callback. This hybrid is on by +default. For a pure, non-hybrid group such as `WOLFSSL_ML_KEM_768`, build +wolfSSL with `--enable-tls-mlkem-standalone` and use that group instead. + +The callback delegates back to the wolfCrypt software implementation so the +example runs without hardware. A real device replaces each case body with its +own calls and keeps the private key on-chip. + +Requires wolfSSL built with: + +```sh +$ ./autogen.sh +$ ./configure --enable-cryptocb --enable-mlkem +$ make +$ sudo make install +$ sudo ldconfig +``` + +Build the example: + +```sh +$ make +``` + +# Usage + +In one terminal start the server: + +```sh +$ ./server-tls13-mlkem +``` + +In another terminal run the client: + +```sh +$ ./client-tls13-mlkem 127.0.0.1 +``` + +Both print the negotiated group and the per-operation callback counts, proving +the ML-KEM key exchange was offloaded to the callback. diff --git a/pq/tls_cryptocb/client-tls13-mlkem.c b/pq/tls_cryptocb/client-tls13-mlkem.c new file mode 100644 index 000000000..374522b7a --- /dev/null +++ b/pq/tls_cryptocb/client-tls13-mlkem.c @@ -0,0 +1,204 @@ +/* client-tls13-mlkem.c + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * 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 + */ + +/* TLS 1.3 client that offloads its ML-KEM key_share through a crypto callback. + * The client generates the ML-KEM key pair (KEYGEN) and recovers the handshake + * shared secret (DECAPS) via the registered device. */ + +#include +#include +#include + +#include +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include + +#include "mlkem-cryptocb.h" + +#define DEFAULT_PORT 11111 + +#define CERT_FILE "../../certs/client-cert.pem" +#define KEY_FILE "../../certs/client-key.pem" +#define CA_FILE "../../certs/ca-cert.pem" + +int main(int argc, char** argv) +{ + int ret = 0; +#ifdef MLKEM_CRYPTOCB_ENABLED + int err = 0; + int sockfd = -1; + struct sockaddr_in servAddr; + char buff[256]; + size_t len; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + int devId = 1; /* anything besides -2 (INVALID_DEVID) */ + MlKemCryptoCbCtx myCtx; + + memset(&myCtx, 0, sizeof(myCtx)); + + if (argc != 2) { + printf("usage: %s \n", argv[0]); + return 0; + } + + /* Process exit status stays nonzero until every check below passes. Several + * TLS setup calls return WOLFSSL_FAILURE (0) on error, so the raw return + * value cannot be used as the exit status. */ + ret = 1; + + if ((err = wolfSSL_Init()) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to initialize the library\n"); + goto exit; + } + + err = wc_CryptoCb_RegisterDevice(devId, myMlKemCryptoCb, &myCtx); + if (err != 0) { + fprintf(stderr, "ERROR: wc_CryptoCb_RegisterDevice failed %d\n", err); + goto exit; + } + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + goto exit; + } + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(DEFAULT_PORT); + if (inet_pton(AF_INET, argv[1], &servAddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid address\n"); + goto exit; + } + + if (connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to connect\n"); + goto exit; + } + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (ctx == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + goto exit; + } + + if ((err = wolfSSL_CTX_use_certificate_file(ctx, CERT_FILE, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", CERT_FILE); + goto exit; + } + if ((err = wolfSSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", KEY_FILE); + goto exit; + } + if ((err = wolfSSL_CTX_load_verify_locations(ctx, CA_FILE, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", CA_FILE); + goto exit; + } + + /* Route the crypto callback device to this context. */ + wolfSSL_CTX_SetDevId(ctx, devId); + + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + goto exit; + } + + /* Request the ML-KEM-768 hybrid group (ECC P-256 + ML-KEM-768) for the key + * share. This hybrid is on by default with --enable-mlkem; its ML-KEM half + * is what dispatches to the crypto callback. For a pure (non-hybrid) group + * such as WOLFSSL_ML_KEM_768, build wolfSSL with + * --enable-tls-mlkem-standalone. */ + if ((err = wolfSSL_UseKeyShare(ssl, WOLFSSL_SECP256R1MLKEM768)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set ML-KEM key share group\n"); + goto exit; + } + + if ((err = wolfSSL_set_fd(ssl, sockfd)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to set the file descriptor\n"); + goto exit; + } + + if ((err = wolfSSL_connect(ssl)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to connect to wolfSSL\n"); + goto exit; + } + + printf("Negotiated group: %s\n", wolfSSL_get_curve_name(ssl)); + printf("ML-KEM via crypto callback: keygen=%d decapsulate=%d\n", + myCtx.keygenCount, myCtx.decapCount); + + printf("Message for server: "); + memset(buff, 0, sizeof(buff)); + if (fgets(buff, sizeof(buff), stdin) == NULL) { + fprintf(stderr, "ERROR: failed to get message for server\n"); + goto exit; + } + len = strnlen(buff, sizeof(buff)); + + if ((int)len != wolfSSL_write(ssl, buff, (int)len)) { + fprintf(stderr, "ERROR: failed to write entire message\n"); + goto exit; + } + + memset(buff, 0, sizeof(buff)); + if (wolfSSL_read(ssl, buff, sizeof(buff) - 1) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + goto exit; + } + printf("Server: %s\n", buff); + + /* The client must have generated and decapsulated through the callback. */ + if (myCtx.keygenCount < 1 || myCtx.decapCount < 1) { + fprintf(stderr, "ERROR: ML-KEM did not route through the callback\n"); + goto exit; + } + + /* Everything passed: report success. */ + ret = 0; + +exit: + if (ssl != NULL) + wolfSSL_free(ssl); + if (sockfd != -1) + close(sockfd); + if (ctx != NULL) + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + +#else + (void)argc; + (void)argv; + printf("Please configure wolfSSL with --enable-cryptocb --enable-mlkem " + "and TLS 1.3 enabled, and try again\n"); +#endif /* MLKEM_CRYPTOCB_ENABLED */ + + return ret; +} diff --git a/pq/tls_cryptocb/mlkem-cryptocb.c b/pq/tls_cryptocb/mlkem-cryptocb.c new file mode 100644 index 000000000..091e95949 --- /dev/null +++ b/pq/tls_cryptocb/mlkem-cryptocb.c @@ -0,0 +1,96 @@ +/* mlkem-cryptocb.c + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * 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 + */ + +/* ML-KEM crypto callback shared by the TLS 1.3 client and server. wolfSSL + * routes the key_share KEM operations here under WC_ALGO_TYPE_PK: the client + * generates a key pair (KEYGEN) and recovers the shared secret (DECAPS); the + * server encapsulates to the client's public key (ENCAPS). + * + * The callback delegates back to the wolfCrypt software implementation so the + * example runs without hardware. A real secure element replaces each case body + * with its own calls and keeps the private key on-chip. Recursion is avoided by + * clearing key->devId before calling the public API and restoring it after. */ + +#include "mlkem-cryptocb.h" + +#ifdef MLKEM_CRYPTOCB_ENABLED + +#include + +int myMlKemCryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + int ret = CRYPTOCB_UNAVAILABLE; + MlKemCryptoCbCtx* myCtx = (MlKemCryptoCbCtx*)ctx; + MlKemKey* key = NULL; + int savedId = INVALID_DEVID; + + (void)devId; + + if (info->algo_type != WC_ALGO_TYPE_PK) + return CRYPTOCB_UNAVAILABLE; + + switch (info->pk.type) { + case WC_PK_TYPE_PQC_KEM_KEYGEN: + if (info->pk.pqc_kem_kg.type != WC_PQC_KEM_TYPE_MLKEM) + break; + key = (MlKemKey*)info->pk.pqc_kem_kg.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlKemKey_MakeKey(key, info->pk.pqc_kem_kg.rng); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->keygenCount++; + break; + + case WC_PK_TYPE_PQC_KEM_ENCAPS: + if (info->pk.pqc_encaps.type != WC_PQC_KEM_TYPE_MLKEM) + break; + key = (MlKemKey*)info->pk.pqc_encaps.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlKemKey_Encapsulate(key, info->pk.pqc_encaps.ciphertext, + info->pk.pqc_encaps.sharedSecret, info->pk.pqc_encaps.rng); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->encapCount++; + break; + + case WC_PK_TYPE_PQC_KEM_DECAPS: + if (info->pk.pqc_decaps.type != WC_PQC_KEM_TYPE_MLKEM) + break; + key = (MlKemKey*)info->pk.pqc_decaps.key; + savedId = key->devId; + key->devId = INVALID_DEVID; + ret = wc_MlKemKey_Decapsulate(key, info->pk.pqc_decaps.sharedSecret, + info->pk.pqc_decaps.ciphertext, info->pk.pqc_decaps.ciphertextLen); + key->devId = savedId; + if (ret == 0 && myCtx != NULL) + myCtx->decapCount++; + break; + + default: + break; + } + + return ret; +} + +#endif /* MLKEM_CRYPTOCB_ENABLED */ diff --git a/pq/tls_cryptocb/mlkem-cryptocb.h b/pq/tls_cryptocb/mlkem-cryptocb.h new file mode 100644 index 000000000..b391b0d0d --- /dev/null +++ b/pq/tls_cryptocb/mlkem-cryptocb.h @@ -0,0 +1,57 @@ +/* mlkem-cryptocb.h + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * 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 + */ + +#ifndef MLKEM_CRYPTOCB_H +#define MLKEM_CRYPTOCB_H + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include + +/* The examples need the crypto callback framework, TLS 1.3, and all three + * ML-KEM KEM operations, since the shared callback implements keygen, + * encapsulate and decapsulate and both programs link it. */ +#if defined(WOLF_CRYPTO_CB) && defined(WOLFSSL_HAVE_MLKEM) && \ + defined(WOLFSSL_TLS13) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) + #define MLKEM_CRYPTOCB_ENABLED +#endif + +#ifdef MLKEM_CRYPTOCB_ENABLED + +/* Crypto callback context: counts the ML-KEM KEM operations the TLS 1.3 + * handshake routed to the device. */ +typedef struct MlKemCryptoCbCtx { + int keygenCount; + int encapCount; + int decapCount; +} MlKemCryptoCbCtx; + +int myMlKemCryptoCb(int devId, wc_CryptoInfo* info, void* ctx); + +#endif /* MLKEM_CRYPTOCB_ENABLED */ + +#endif /* MLKEM_CRYPTOCB_H */ diff --git a/pq/tls_cryptocb/server-tls13-mlkem.c b/pq/tls_cryptocb/server-tls13-mlkem.c new file mode 100644 index 000000000..2c81f81a1 --- /dev/null +++ b/pq/tls_cryptocb/server-tls13-mlkem.c @@ -0,0 +1,186 @@ +/* server-tls13-mlkem.c + * + * Copyright (C) 2025 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * 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 + */ + +/* TLS 1.3 server that offloads the ML-KEM key_share through a crypto callback. + * The server encapsulates to the client's ML-KEM public key (ENCAPS) via the + * registered device. It accepts a single connection, then exits. */ + +#include +#include +#include + +#include +#include +#include +#include + +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include + +#include "mlkem-cryptocb.h" + +#define DEFAULT_PORT 11111 + +#define CERT_FILE "../../certs/server-cert.pem" +#define KEY_FILE "../../certs/server-key.pem" + +int main(void) +{ + int ret = 0; +#ifdef MLKEM_CRYPTOCB_ENABLED + int err = 0; + int sockfd = -1; + int connd = -1; + struct sockaddr_in servAddr; + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + char buff[256]; + size_t len; + const char* reply = "I hear ya fa shizzle!\n"; + int on = 1; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + int devId = 1; /* anything besides -2 (INVALID_DEVID) */ + MlKemCryptoCbCtx myCtx; + + memset(&myCtx, 0, sizeof(myCtx)); + + /* Process exit status stays nonzero until every check below passes. Several + * TLS setup calls return WOLFSSL_FAILURE (0) on error, so the raw return + * value cannot be used as the exit status. */ + ret = 1; + + if ((err = wolfSSL_Init()) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to initialize the library\n"); + goto exit; + } + + err = wc_CryptoCb_RegisterDevice(devId, myMlKemCryptoCb, &myCtx); + if (err != 0) { + fprintf(stderr, "ERROR: wc_CryptoCb_RegisterDevice failed %d\n", err); + goto exit; + } + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + goto exit; + } + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + memset(&servAddr, 0, sizeof(servAddr)); + servAddr.sin_family = AF_INET; + servAddr.sin_port = htons(DEFAULT_PORT); + servAddr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + goto exit; + } + if (listen(sockfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + goto exit; + } + + ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + if (ctx == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + goto exit; + } + + if ((err = wolfSSL_CTX_use_certificate_file(ctx, CERT_FILE, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", CERT_FILE); + goto exit; + } + if ((err = wolfSSL_CTX_use_PrivateKey_file(ctx, KEY_FILE, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s\n", KEY_FILE); + goto exit; + } + + /* Route the crypto callback device to this context. */ + wolfSSL_CTX_SetDevId(ctx, devId); + + printf("Waiting for a connection...\n"); + if ((connd = accept(sockfd, (struct sockaddr*)&clientAddr, &size)) == -1) { + fprintf(stderr, "ERROR: failed to accept the connection\n"); + goto exit; + } + + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + goto exit; + } + wolfSSL_set_fd(ssl, connd); + + if ((err = wolfSSL_accept(ssl)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: wolfSSL_accept error %d\n", + wolfSSL_get_error(ssl, err)); + goto exit; + } + + printf("Negotiated group: %s\n", wolfSSL_get_curve_name(ssl)); + printf("ML-KEM via crypto callback: encapsulate=%d\n", myCtx.encapCount); + + memset(buff, 0, sizeof(buff)); + if (wolfSSL_read(ssl, buff, sizeof(buff) - 1) == -1) { + fprintf(stderr, "ERROR: failed to read\n"); + goto exit; + } + printf("Client: %s\n", buff); + + len = strlen(reply); + if ((int)len != wolfSSL_write(ssl, reply, (int)len)) { + fprintf(stderr, "ERROR: failed to write\n"); + goto exit; + } + + /* The server must have encapsulated through the callback. */ + if (myCtx.encapCount < 1) { + fprintf(stderr, "ERROR: ML-KEM did not route through the callback\n"); + goto exit; + } + + /* Everything passed: report success. */ + ret = 0; + +exit: + if (ssl != NULL) { + wolfSSL_shutdown(ssl); + wolfSSL_free(ssl); + } + if (connd != -1) + close(connd); + if (sockfd != -1) + close(sockfd); + if (ctx != NULL) + wolfSSL_CTX_free(ctx); + wolfSSL_Cleanup(); + +#else + printf("Please configure wolfSSL with --enable-cryptocb --enable-mlkem " + "and TLS 1.3 enabled, and try again\n"); +#endif /* MLKEM_CRYPTOCB_ENABLED */ + + return ret; +}