From 6d276be51ba89b04a60bb16b5179c015ad202c7c Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 27 Sep 2026 19:17:02 +0100 Subject: Seal in one session a wire, authenticated by the sender's key (088) A sealed request cost three X25519 operations (about 0.5 ms), so sealed sends drained at half the plain rate. A wire now keeps one HPKE context, encapsulated at its first seal, and seals each request as the context's next number (a failed seal or a restart starts a new session); the header carries the session's encapsulated key and the number, so a receiver opens a session once per connection thread (16 remembered, the least used replaced) and each seal by its number, also after it restarts, unasked. A sender with a key of its own seals in HPKE auth mode and names that key in the header; a receiver started with --sender NAME=KEY takes deliveries only from the senders it names, each sealed with its key (403 sender otherwise). The body is now the ciphertext and its tag. Sealed sends, sender authenticated, drain at 461-464k a second against 462-470k plain and 207-234k sealed a request at a time (Raspberry Pi 5). The check adds: a wire's requests share one session numbered from 0; a changed number or sender key does not open; a receiver restarted naming its sender opens the same session again; another node claiming that sender's name with its own key is refused and applies nothing. --- serve/crypto-openssl.c | 89 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 26 deletions(-) (limited to 'serve/crypto-openssl.c') diff --git a/serve/crypto-openssl.c b/serve/crypto-openssl.c index 38b4628..127ae38 100644 --- a/serve/crypto-openssl.c +++ b/serve/crypto-openssl.c @@ -56,11 +56,14 @@ static int tls_read(PureTls *tls, void *bytes, int size) { return SSL_read((SSL static int tls_write(PureTls *tls, const void *bytes, int size) { return SSL_write((SSL *)tls, bytes, size); } static void tls_close(PureTls *tls) { SSL_free((SSL *)tls); } -/* ---- Sealing: HPKE base mode (RFC 9180), X25519 and HKDF-SHA256, with the - * AEAD the suite names. */ +/* ---- Sealing: HPKE (RFC 9180), X25519 and HKDF-SHA256, with the AEAD the + * suite names; auth mode when the sender seals with a key of its own. A + * session is one encapsulation, its seals numbered by the context. */ struct PureSealKey { EVP_PKEY *key; }; +struct PureSealer { OSSL_HPKE_CTX *context; }; +struct PureOpener { OSSL_HPKE_CTX *context; }; -static const unsigned char info[] = "pure sealed send 1"; +static const unsigned char info[] = "pure sealed send 2"; static bool suite_named(const char *name, OSSL_HPKE_SUITE *out) { OSSL_HPKE_SUITE suite = {OSSL_HPKE_KEM_ID_X25519, OSSL_HPKE_KDF_ID_HKDF_SHA256, 0}; @@ -120,34 +123,68 @@ static void seal_key_free(PureSealKey *key) { free(key); } -static bool seal(const char *suite_name, const unsigned char peer[PURE_SEAL_KEY], const char *aad, - const unsigned char *plain, size_t size, unsigned char *out) { +static PureSealer *sealer(const char *suite_name, const unsigned char peer[PURE_SEAL_KEY], const PureSealKey *own, + unsigned char enc[PURE_SEAL_KEY]) { OSSL_HPKE_SUITE suite; - if (!suite_named(suite_name, &suite)) return false; - OSSL_HPKE_CTX *context = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, suite, OSSL_HPKE_ROLE_SENDER, NULL, NULL); - size_t encapsulated = PURE_SEAL_KEY, sealed = size + 16; - bool ok = context && OSSL_HPKE_encap(context, out, &encapsulated, peer, PURE_SEAL_KEY, info, sizeof(info) - 1) == 1 && - encapsulated == PURE_SEAL_KEY && - OSSL_HPKE_seal(context, out + PURE_SEAL_KEY, &sealed, (const unsigned char *)aad, strlen(aad), plain, size) == 1 && - sealed == size + 16; - OSSL_HPKE_CTX_free(context); - return ok; + PureSealer *made = suite_named(suite_name, &suite) ? calloc(1, sizeof(*made)) : NULL; + if (!made) return NULL; + made->context = OSSL_HPKE_CTX_new(own ? OSSL_HPKE_MODE_AUTH : OSSL_HPKE_MODE_BASE, suite, OSSL_HPKE_ROLE_SENDER, + NULL, NULL); + size_t length = PURE_SEAL_KEY; + if (!made->context || (own && OSSL_HPKE_CTX_set1_authpriv(made->context, own->key) != 1) || + OSSL_HPKE_encap(made->context, enc, &length, peer, PURE_SEAL_KEY, info, sizeof(info) - 1) != 1 || + length != PURE_SEAL_KEY) { + OSSL_HPKE_CTX_free(made->context); + free(made); + return NULL; + } + return made; +} + +static bool seal(PureSealer *s, const char *aad, const unsigned char *plain, size_t size, unsigned char *out, + uint64_t *n) { + size_t sealed = size + PURE_SEAL_OVERHEAD; + return OSSL_HPKE_CTX_get_seq(s->context, n) == 1 && + OSSL_HPKE_seal(s->context, out, &sealed, (const unsigned char *)aad, strlen(aad), plain, size) == 1 && + sealed == size + PURE_SEAL_OVERHEAD; +} + +static void sealer_free(PureSealer *s) { + if (!s) return; + OSSL_HPKE_CTX_free(s->context); + free(s); } -static bool unseal(const PureSealKey *key, const char *suite_name, const char *aad, const unsigned char *sealed, - size_t size, unsigned char *plain) { +static PureOpener *opener(const PureSealKey *key, const char *suite_name, const unsigned char enc[PURE_SEAL_KEY], + const unsigned char *from) { OSSL_HPKE_SUITE suite; - if (!key || size < PURE_SEAL_OVERHEAD || !suite_named(suite_name, &suite)) return false; - OSSL_HPKE_CTX *context = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, suite, OSSL_HPKE_ROLE_RECEIVER, NULL, NULL); + PureOpener *made = key && suite_named(suite_name, &suite) ? calloc(1, sizeof(*made)) : NULL; + if (!made) return NULL; + made->context = OSSL_HPKE_CTX_new(from ? OSSL_HPKE_MODE_AUTH : OSSL_HPKE_MODE_BASE, suite, OSSL_HPKE_ROLE_RECEIVER, + NULL, NULL); + if (!made->context || (from && OSSL_HPKE_CTX_set1_authpub(made->context, from, PURE_SEAL_KEY) != 1) || + OSSL_HPKE_decap(made->context, enc, PURE_SEAL_KEY, key->key, info, sizeof(info) - 1) != 1) { + OSSL_HPKE_CTX_free(made->context); + free(made); + return NULL; + } + return made; +} + +/* A receiver may set the number (a sender may not: it would reuse a nonce). */ +static bool unseal(PureOpener *o, uint64_t n, const char *aad, const unsigned char *sealed, size_t size, + unsigned char *plain) { size_t opened = size - PURE_SEAL_OVERHEAD; - bool ok = context && - OSSL_HPKE_decap(context, sealed, PURE_SEAL_KEY, key->key, info, sizeof(info) - 1) == 1 && - OSSL_HPKE_open(context, plain, &opened, (const unsigned char *)aad, strlen(aad), sealed + PURE_SEAL_KEY, - size - PURE_SEAL_KEY) == 1 && - opened == size - PURE_SEAL_OVERHEAD; - OSSL_HPKE_CTX_free(context); - return ok; + return size >= PURE_SEAL_OVERHEAD && OSSL_HPKE_CTX_set_seq(o->context, n) == 1 && + OSSL_HPKE_open(o->context, plain, &opened, (const unsigned char *)aad, strlen(aad), sealed, size) == 1 && + opened == size - PURE_SEAL_OVERHEAD; +} + +static void opener_free(PureOpener *o) { + if (!o) return; + OSSL_HPKE_CTX_free(o->context); + free(o); } const PureCrypto pure_crypto = {PURE_CRYPTO_VERSION, tls_start, tls_open, tls_read, tls_write, tls_close, - seal_key, seal_key_free, seal, unseal}; + seal_key, seal_key_free, sealer, seal, sealer_free, opener, unseal, opener_free}; -- cgit v1.2.3