From 96fb285077342e80fcaa97756c1e5cb136f644d8 Mon Sep 17 00:00:00 2001 From: Anthony Date: Fri, 24 Jul 2026 15:18:48 -0400 Subject: [PATCH] Add HPKE seal/open context example One KEM encapsulation protecting an ordered message sequence via wc_HpkeInitSealContext()/wc_HpkeContextSealBase() and the open equivalents, with out-of-order rejection. --- pk/hpke/Makefile | 12 ++- pk/hpke/README.md | 17 ++++- pk/hpke/hpke_context.c | 170 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 pk/hpke/hpke_context.c diff --git a/pk/hpke/Makefile b/pk/hpke/Makefile index e880cb38..3ccc59d7 100644 --- a/pk/hpke/Makefile +++ b/pk/hpke/Makefile @@ -3,14 +3,20 @@ WOLFSSL_INSTALL_DIR=/usr/local CFLAGS= -I$(WOLFSSL_INSTALL_DIR)/include -Wall LIBS= -L$(WOLFSSL_INSTALL_DIR)/lib -lwolfssl +all: hpke_test hpke_context + hpke_test: hpke_test.o $(CC) -o $@ $^ $(CFLAGS) $(LIBS) -.PHONY: clean check +hpke_context: hpke_context.o + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +.PHONY: clean check all clean: - rm -f *.o hpke_test + rm -f *.o hpke_test hpke_context -check: hpke_test +check: all out=$$(./hpke_test) && printf '%s' "$$out" | grep -q 'HPKE test success' + out=$$(./hpke_context) && printf '%s' "$$out" | grep -q 'HPKE context test success' @echo "PASS: pk-hpke checks" diff --git a/pk/hpke/README.md b/pk/hpke/README.md index a5fd384c..fef875ef 100644 --- a/pk/hpke/README.md +++ b/pk/hpke/README.md @@ -1,9 +1,22 @@ -# HPKE Example with all supported options +# HPKE Examples -To build wolfSSL for this example run `./configure --enable-hpke --enable-aesgcm --enable-curve25519 --enable-ecc && make && sudo make install` +Demonstrates HPKE (Hybrid Public Key Encryption, RFC 9180): public-key +encryption built from a key encapsulation mechanism (KEM), a key derivation +function (KDF), and an authenticated cipher (AEAD). + +To build wolfSSL for these examples run `./configure --enable-hpke --enable-aesgcm --enable-curve25519 --enable-ecc && make && sudo make install` + +* `hpke_test.c` - one-shot seal/open (`wc_HpkeSealBase()` / `wc_HpkeOpenBase()`) + with all supported KEM/KDF/AEAD combinations. +* `hpke_context.c` - seal/open contexts (`wc_HpkeInitSealContext()` / + `wc_HpkeContextSealBase()` and the open equivalents): one key encapsulation + protecting an ordered sequence of messages. ```sh make ./hpke_test HPKE test success +./hpke_context +... +HPKE context test success ``` diff --git a/pk/hpke/hpke_context.c b/pk/hpke/hpke_context.c new file mode 100644 index 00000000..d8105c3c --- /dev/null +++ b/pk/hpke/hpke_context.c @@ -0,0 +1,170 @@ +/* hpke_context.c + * + * Copyright (C) 2006-2026 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 + */ + +/* Example of HPKE (RFC 9180) context reuse: one KEM encapsulation protecting + * a sequence of messages. + * + * wc_HpkeSealBase() does a fresh key encapsulation per message. With a + * seal/open context the encapsulation happens once and each message gets the + * next AEAD nonce, so both sides must process messages in the same order. */ + +#include +#include + +#include +#include +#include +#include +#include + +#if defined(HAVE_HPKE) && (defined(HAVE_ECC) || defined(HAVE_CURVE25519)) && \ + defined(HAVE_AESGCM) + +#define NUM_MSGS 3 +#define TAG_SZ 16 +#define MAX_MSG 64 + +static const char* msgs[NUM_MSGS] = { + "first message", + "second message", + "third message" +}; + +int main(void) +{ + int ret; + int i; + Hpke hpke[1]; + HpkeBaseContext sealCtx[1]; + HpkeBaseContext openCtx[1]; + WC_RNG rng[1]; + int rngInit = 0; + void* receiverKey = NULL; + void* ephemeralKey = NULL; + byte pubKey[HPKE_Npk_MAX]; + word16 pubKeySz = (word16)sizeof(pubKey); + byte cipher[NUM_MSGS][MAX_MSG + TAG_SZ]; + byte plain[MAX_MSG]; + const char* info = "hpke context example"; + const char* aad = "message aad"; + +#if defined(HAVE_CURVE25519) + ret = wc_HpkeInit(hpke, DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, + HPKE_AES_128_GCM, NULL); +#else + ret = wc_HpkeInit(hpke, DHKEM_P256_HKDF_SHA256, HKDF_SHA256, + HPKE_AES_128_GCM, NULL); +#endif + if (ret != 0) + goto exit; + + ret = wc_InitRng(rng); + if (ret != 0) + goto exit; + rngInit = 1; + + ret = wc_HpkeGenerateKeyPair(hpke, &receiverKey, rng); + if (ret == 0) + ret = wc_HpkeGenerateKeyPair(hpke, &ephemeralKey, rng); + if (ret != 0) + goto exit; + + /* Sender: one encapsulation, then seal each message in order. */ + ret = wc_HpkeInitSealContext(hpke, sealCtx, ephemeralKey, receiverKey, + (byte*)info, (word32)strlen(info)); + if (ret != 0) + goto exit; + + for (i = 0; i < NUM_MSGS; i++) { + ret = wc_HpkeContextSealBase(hpke, sealCtx, (byte*)aad, + (word32)strlen(aad), (byte*)msgs[i], + (word32)strlen(msgs[i]), cipher[i]); + if (ret != 0) + goto exit; + printf("sealed message %d (%zu bytes)\n", i, strlen(msgs[i])); + } + + /* Only the ephemeral public key travels to the receiver. */ + ret = wc_HpkeSerializePublicKey(hpke, ephemeralKey, pubKey, &pubKeySz); + if (ret != 0) + goto exit; + + /* Receiver: decapsulate once, then open in the same order. */ + ret = wc_HpkeInitOpenContext(hpke, openCtx, receiverKey, pubKey, pubKeySz, + (byte*)info, (word32)strlen(info)); + if (ret != 0) + goto exit; + + for (i = 0; i < NUM_MSGS; i++) { + word32 msgSz = (word32)strlen(msgs[i]); + + memset(plain, 0, sizeof(plain)); + ret = wc_HpkeContextOpenBase(hpke, openCtx, (byte*)aad, + (word32)strlen(aad), cipher[i], msgSz, + plain); + if (ret != 0) + goto exit; + if (memcmp(plain, msgs[i], msgSz) != 0) { + printf("message %d mismatch\n", i); + ret = -1; + goto exit; + } + printf("opened message %d: %.*s\n", i, (int)msgSz, plain); + } + + /* Replaying a message out of sequence uses the wrong nonce and fails. */ + ret = wc_HpkeContextOpenBase(hpke, openCtx, (byte*)aad, + (word32)strlen(aad), cipher[0], + (word32)strlen(msgs[0]), plain); + if (ret == 0) { + printf("out-of-order open succeeded unexpectedly\n"); + ret = -1; + goto exit; + } + printf("out-of-order open rejected as expected\n"); + ret = 0; + + printf("HPKE context test success\n"); + +exit: + if (ret != 0) + printf("HPKE context test error %d: %s\n", ret, + wc_GetErrorString(ret)); + if (ephemeralKey != NULL) + wc_HpkeFreeKey(hpke, hpke->kem, ephemeralKey, NULL); + if (receiverKey != NULL) + wc_HpkeFreeKey(hpke, hpke->kem, receiverKey, NULL); + if (rngInit) + wc_FreeRng(rng); + + return ret == 0 ? 0 : 1; +} + +#else + +int main(void) +{ + printf("Please build wolfssl with ./configure --enable-hpke " + "--enable-aesgcm --enable-curve25519 --enable-ecc\n"); + return 0; +} + +#endif