From 60b04305d70614580b4f883c0a147507edef3779 Mon Sep 17 00:00:00 2001 From: Dimitri Staessens Date: Wed, 7 Jan 2026 16:44:34 +0100 Subject: lib: Add post-quantum cryptography support This adds initial support for runtime-configurable encryption and post-quantum Key Encapsulation Mechanisms (KEMs) and authentication (ML-DSA). Supported key exchange algorithms: ECDH: prime256v1, secp384r1, secp521r1, X25519, X448 Finite Field DH: ffdhe2048, ffdhe3072, ffdhe4096 ML-KEM (FIPS 203): ML-KEM-512, ML-KEM-768, ML-KEM-1024 Hybrid KEMs: X25519MLKEM768, X448MLKEM1024 Supported ciphers: AEAD: aes-128-gcm, aes-192-gcm, aes-256-gcm, chacha20-poly1305 CTR: aes-128-ctr, aes-192-ctr, aes-256-ctr Supported HKDFs: sha256, sha384, sha512, sha3-256, sha3-384, sha3-512, blake2b512, blake2s256 Supported Digests for DSA: sha256, sha384, sha512, sha3-256, sha3-384, sha3-512, blake2b512, blake2s256 PQC support requires OpenSSL 3.4.0+ and is detected automatically via CMake. A DISABLE_PQC option allows building without PQC even when available. KEMs differ from traditional DH in that they require asymmetric roles: one party encapsulates to the other's public key. This creates a coordination problem during simultaneous reconnection attempts. The kem_mode configuration parameter resolves this by pre-assigning roles: kem_mode=server # Server encapsulates (1-RTT, full forward secrecy) kem_mode=client # Client encapsulates (0-RTT, cached server key) The enc.conf file format supports: kex= # Key exchange algorithm cipher= # Symmetric cipher kdf= # Key derivation function digest= # Digest for DSA kem_mode= # Server (default) or client none # Disable encryption The OAP protocol is extended to negotiate algorithms and exchange KEX data. All KEX messages are signed using existing authentication infrastructure for integrity and replay protection. Tests are split into base and _pqc variants to handle conditional PQC compilation (kex_test.c/kex_test_pqc.c, oap_test.c/oap_test_pqc.c). Bumped minimum required OpenSSL version for encryption to 3.0 (required for HKDF API). 1.1.1 is long time EOL. Signed-off-by: Dimitri Staessens Signed-off-by: Sander Vrijders --- src/irmd/oap.c | 290 +++++++++++++-------------------------------------------- 1 file changed, 66 insertions(+), 224 deletions(-) (limited to 'src/irmd/oap.c') diff --git a/src/irmd/oap.c b/src/irmd/oap.c index 500da6f1..085e06a3 100644 --- a/src/irmd/oap.c +++ b/src/irmd/oap.c @@ -1,7 +1,7 @@ /* * Ouroboros - Copyright (C) 2016 - 2024 * - * Ouroboros flow allocation protocol header + * OAP - Shared credential and configuration loading * * Dimitri Staessens * Sander Vrijders @@ -29,260 +29,102 @@ #define OUROBOROS_PREFIX "irmd/oap" #include -#include +#include #include -#include -#include #include "config.h" -#include "oap.h" - #include +#include +#include -int oap_hdr_init(buffer_t id, - void * pkp, - void * pubcrt, - buffer_t ephkey, - buffer_t data, - struct oap_hdr * oap_hdr) -{ - struct timespec now; - uint64_t stamp; - buffer_t hdr; - buffer_t der = BUF_INIT; - buffer_t sig = BUF_INIT; - buffer_t sign; - uint16_t len; - off_t offset; - - assert(id.data != NULL && id.len == OAP_ID_SIZE); - assert(oap_hdr != NULL); - memset(oap_hdr, 0, sizeof(*oap_hdr)); - - clock_gettime(CLOCK_REALTIME, &now); - stamp = hton64(TS_TO_UINT64(now)); - - if (pubcrt != NULL && crypt_crt_der(pubcrt, &der) < 0) - goto fail_der; - - hdr.len = id.len + - sizeof(stamp) + - sizeof(len) + der.len + - sizeof(len) + ephkey.len + - sizeof(len) + data.len + - sizeof(len); /* sig len */ - - hdr.data = malloc(hdr.len); - if (hdr.data == NULL) - goto fail_hdr; - - offset = 0; - - memcpy(hdr.data, id.data, id.len); - offset += id.len; - - memcpy(hdr.data + offset, &stamp, sizeof(stamp)); - offset += sizeof(stamp); - - /* pubcrt */ - len = hton16((uint16_t) der.len); - memcpy(hdr.data + offset, &len, sizeof(len)); - offset += sizeof(len); - if (der.len != 0) - memcpy(hdr.data + offset, der.data, der.len); - offset += der.len; - - /* ephkey */ - len = hton16((uint16_t) ephkey.len); - memcpy(hdr.data + offset, &len, sizeof(len)); - offset += sizeof(len); - if (ephkey.len != 0) - memcpy(hdr.data + offset, ephkey.data, ephkey.len); - offset += ephkey.len; - - /* data */ - len = hton16((uint16_t) data.len); - memcpy(hdr.data + offset, &len, sizeof(len)); - offset += sizeof(len); - if (data.len != 0) - memcpy(hdr.data + offset, data.data, data.len); - offset += data.len; - - sign.data = hdr.data; - sign.len = hdr.len - sizeof(len); - - if (pkp != NULL && auth_sign(pkp, sign, &sig) < 0) - goto fail_sig; - - len = hton16((uint16_t) sig.len); - memcpy(hdr.data + offset, &len, sizeof(len)); - offset += sizeof(len); - - oap_hdr->hdr = hdr; +/* + * Shared credential and configuration loading helpers + */ - assert((size_t) offset == hdr.len); +#ifndef OAP_TEST_MODE - if (sig.len > 0) { - oap_hdr->hdr.len += sig.len; - oap_hdr->hdr.data = realloc(hdr.data, oap_hdr->hdr.len); - if (oap_hdr->hdr.data == NULL) - goto fail_oap_hdr; +static bool file_exists(const char * path) +{ + struct stat s; - memcpy(oap_hdr->hdr.data + offset, sig.data, sig.len); - clrbuf(hdr); + if (stat(path, &s) < 0 && errno == ENOENT) { + log_dbg("File %s does not exist.", path); + return false; } - if (oap_hdr_decode(oap_hdr->hdr, oap_hdr) < 0) - goto fail_decode; - - freebuf(der); - freebuf(sig); - - return 0; - - fail_decode: - oap_hdr_fini(oap_hdr); - fail_oap_hdr: - freebuf(sig); - fail_sig: - freebuf(hdr); - fail_hdr: - freebuf(der); - fail_der: - memset(oap_hdr, 0, sizeof(*oap_hdr)); - return -1; + return true; } -void oap_hdr_fini(struct oap_hdr * oap_hdr) +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt) { - assert(oap_hdr != NULL); - - freebuf(oap_hdr->hdr); - memset(oap_hdr, 0, sizeof(*oap_hdr)); -} - -int oap_hdr_decode(buffer_t hdr, - struct oap_hdr * oap_hdr) -{ - off_t offset; - - assert(oap_hdr != NULL); - memset(oap_hdr, 0, sizeof(*oap_hdr)); - - if (hdr.len < OAP_HDR_MIN_SIZE) - goto fail_decode; - - oap_hdr->id.data = hdr.data; - oap_hdr->id.len = OAP_ID_SIZE; + assert(paths != NULL); + assert(pkp != NULL); + assert(crt != NULL); - offset = OAP_ID_SIZE; + *pkp = NULL; + *crt = NULL; - oap_hdr->timestamp = ntoh64(*(uint64_t *)(hdr.data + offset)); - - offset += sizeof(uint64_t); - - oap_hdr->crt.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); - oap_hdr->crt.data = hdr.data + offset + sizeof(uint16_t); - - offset += sizeof(uint16_t) + oap_hdr->crt.len; - - if ((size_t) offset + sizeof(uint16_t) >= hdr.len) - goto fail_decode; - - oap_hdr->eph.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); - oap_hdr->eph.data = hdr.data + offset + sizeof(uint16_t); - - offset += sizeof(uint16_t) + oap_hdr->eph.len; - - if ((size_t) offset + sizeof(uint16_t) >= hdr.len) - goto fail_decode; - - oap_hdr->data.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); - oap_hdr->data.data = hdr.data + offset + sizeof(uint16_t); - - offset += sizeof(uint16_t) + oap_hdr->data.len; - - if ((size_t) offset + sizeof(uint16_t) > hdr.len) - goto fail_decode; - - oap_hdr->sig.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); - oap_hdr->sig.data = hdr.data + offset + sizeof(uint16_t); + if (!file_exists(paths->crt) || !file_exists(paths->key)) { + log_info("No authentication certificates for %s.", name); + return 0; + } - offset += sizeof(uint16_t) + oap_hdr->sig.len; + if (crypt_load_crt_file(paths->crt, crt) < 0) { + log_err("Failed to load %s for %s.", paths->crt, name); + goto fail_crt; + } - if ((size_t) offset != hdr.len) - goto fail_decode; + if (crypt_load_privkey_file(paths->key, pkp) < 0) { + log_err("Failed to load %s for %s.", paths->key, name); + goto fail_key; + } - oap_hdr->hdr = hdr; + log_info("Loaded authentication certificates for %s.", name); return 0; - fail_decode: - memset(oap_hdr, 0, sizeof(*oap_hdr)); - return -1; -} - -#ifdef DEBUG_PROTO_OAP -static void debug_oap_hdr(const struct oap_hdr * hdr) -{ - assert(hdr); - - if (hdr->crt.len > 0) - log_proto(" Certificate: [%zu bytes]", hdr->crt.len); - else - log_proto(" Certificate: "); - - if (hdr->eph.len > 0) - log_proto(" Ephemeral Public Key: [%zu bytes]", hdr->eph.len); - else - log_proto(" Ephemeral Public Key: "); - if (hdr->data.len > 0) - log_proto(" Data: [%zu bytes]", hdr->data.len); - else - log_proto(" Data: "); - if (hdr->sig.len > 0) - log_proto(" Signature: [%zu bytes]", hdr->sig.len); - else - log_proto(" Signature: "); + fail_key: + crypt_free_crt(*crt); + *crt = NULL; + fail_crt: + return -EAUTH; } -void debug_oap_hdr_rcv(const struct oap_hdr * hdr) +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg) { - struct tm * tm; - char tmstr[RIB_TM_STRLEN]; - time_t stamp; + assert(name != NULL); + assert(cfg != NULL); - assert(hdr); + memset(cfg, 0, sizeof(*cfg)); - stamp = (time_t) hdr->timestamp / BILLION; - - tm = gmtime(&stamp); - strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - - log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] <--", - HASH_VAL64(hdr->id.data), tmstr); - - debug_oap_hdr(hdr); -} + /* Load encryption config */ + if (!file_exists(path)) + log_dbg("No encryption %s for %s.", path, name); -void debug_oap_hdr_snd(const struct oap_hdr * hdr) -{ - struct tm * tm; - char tmstr[RIB_TM_STRLEN]; - time_t stamp; - - assert(hdr); + if (load_sec_config_file(cfg, path) < 0) { + log_warn("Failed to load %s for %s.", path, name); + return -1; + } - stamp = (time_t) hdr->timestamp / BILLION; + if (!IS_KEX_ALGO_SET(cfg)) { + log_info("Key exchange not configured for %s.", name); + return 0; + } - tm = gmtime(&stamp); - strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); + if (cfg->c.nid == NID_undef || crypt_nid_to_str(cfg->c.nid) == NULL) { + log_err("Invalid cipher NID %d for %s.", cfg->c.nid, name); + return -ECRYPT; + } - log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] -->", - HASH_VAL64(hdr->id.data), tmstr); + log_info("Encryption enabled for %s.", name); - debug_oap_hdr(hdr); + return 0; } -#endif +#endif /* OAP_TEST_MODE */ -- cgit v1.2.3