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/lib/crypt.c | 647 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 623 insertions(+), 24 deletions(-) (limited to 'src/lib/crypt.c') diff --git a/src/lib/crypt.c b/src/lib/crypt.c index 8b18140e..a050fe38 100644 --- a/src/lib/crypt.c +++ b/src/lib/crypt.c @@ -22,32 +22,244 @@ #include -#include #include +#include +#include + #ifdef HAVE_OPENSSL - #include "crypt/openssl.h" -#endif /* HAVE_OPENSSL */ +#include +#include "crypt/openssl.h" +#endif #include +#include #include +#include + +struct nid_map { + uint16_t nid; + const char * name; +}; + +static const struct nid_map cipher_nid_map[] = { + {NID_aes_128_gcm, "aes-128-gcm"}, + {NID_aes_192_gcm, "aes-192-gcm"}, + {NID_aes_256_gcm, "aes-256-gcm"}, + {NID_chacha20_poly1305, "chacha20-poly1305"}, + {NID_aes_128_ctr, "aes-128-ctr"}, + {NID_aes_192_ctr, "aes-192-ctr"}, + {NID_aes_256_ctr, "aes-256-ctr"}, + {NID_undef, NULL} +}; + +const uint16_t crypt_supported_nids[] = { +#ifdef HAVE_OPENSSL + NID_aes_128_gcm, + NID_aes_192_gcm, + NID_aes_256_gcm, + NID_chacha20_poly1305, + NID_aes_128_ctr, + NID_aes_192_ctr, + NID_aes_256_ctr, +#endif + NID_undef +}; + +static const struct nid_map kex_nid_map[] = { + {NID_X9_62_prime256v1, "prime256v1"}, + {NID_secp384r1, "secp384r1"}, + {NID_secp521r1, "secp521r1"}, + {NID_X25519, "X25519"}, + {NID_X448, "X448"}, + {NID_ffdhe2048, "ffdhe2048"}, + {NID_ffdhe3072, "ffdhe3072"}, + {NID_ffdhe4096, "ffdhe4096"}, + {NID_MLKEM512, "ML-KEM-512"}, + {NID_MLKEM768, "ML-KEM-768"}, + {NID_MLKEM1024, "ML-KEM-1024"}, + {NID_X25519MLKEM768, "X25519MLKEM768"}, + {NID_X448MLKEM1024, "X448MLKEM1024"}, + {NID_undef, NULL} +}; + +const uint16_t kex_supported_nids[] = { +#ifdef HAVE_OPENSSL + NID_X9_62_prime256v1, + NID_secp384r1, + NID_secp521r1, + NID_X25519, + NID_X448, + NID_ffdhe2048, + NID_ffdhe3072, + NID_ffdhe4096, +#ifdef HAVE_OPENSSL_PQC + NID_MLKEM512, + NID_MLKEM768, + NID_MLKEM1024, + NID_X25519MLKEM768, + NID_X448MLKEM1024, +#endif +#endif + NID_undef +}; + +static const struct nid_map md_nid_map[] = { + {NID_sha256, "sha256"}, + {NID_sha384, "sha384"}, + {NID_sha512, "sha512"}, + {NID_sha3_256, "sha3-256"}, + {NID_sha3_384, "sha3-384"}, + {NID_sha3_512, "sha3-512"}, + {NID_blake2b512, "blake2b512"}, + {NID_blake2s256, "blake2s256"}, + {NID_undef, NULL} +}; + +const uint16_t md_supported_nids[] = { +#ifdef HAVE_OPENSSL + NID_sha256, + NID_sha384, + NID_sha512, + NID_sha3_256, + NID_sha3_384, + NID_sha3_512, + NID_blake2b512, + NID_blake2s256, +#endif + NID_undef +}; struct crypt_ctx { - void * ctx; - uint8_t key[SYMMKEYSZ]; + void * ctx; /* Encryption context */ }; struct auth_ctx { void * store; }; -int crypt_dh_pkp_create(void ** pkp, - uint8_t * pk) +static int parse_kex_value(const char * value, + struct sec_config * cfg) +{ + SET_KEX_ALGO(cfg, value); + if (cfg->x.nid == NID_undef) + return -ENOTSUP; + + return 0; +} + +/* not in header, but non-static for unit testing */ +int parse_sec_config(struct sec_config * cfg, + FILE * fp) +{ + char line[256]; + char * equals; + char * key; + char * value; + + assert(cfg != NULL); + assert(fp != NULL); + + /* Set defaults */ + SET_KEX_ALGO_NID(cfg, NID_X9_62_prime256v1); + cfg->x.mode = KEM_MODE_SERVER_ENCAP; + SET_KEX_KDF_NID(cfg, NID_sha256); + SET_KEX_CIPHER_NID(cfg, NID_aes_256_gcm); + SET_KEX_DIGEST_NID(cfg, NID_sha256); + + while (fgets(line, sizeof(line), fp) != NULL) { + char * trimmed; + + /* Skip comments and empty lines */ + if (line[0] == '#' || line[0] == '\n') + continue; + + /* Check for 'none' keyword */ + trimmed = trim_whitespace(line); + if (strcmp(trimmed, "none") == 0) { + memset(cfg, 0, sizeof(*cfg)); + return 0; + } + + /* Find the = separator */ + equals = strchr(line, '='); + if (equals == NULL) + continue; + + /* Split into key and value */ + *equals = '\0'; + key = trim_whitespace(line); + value = trim_whitespace(equals + 1); + + /* Parse key exchange field */ + if (strcmp(key, "kex") == 0) { + if (parse_kex_value(value, cfg) < 0) + return -EINVAL; + } else if (strcmp(key, "cipher") == 0) { + SET_KEX_CIPHER(cfg, value); + if (cfg->c.nid == NID_undef) + return -EINVAL; + } else if (strcmp(key, "kdf") == 0) { + SET_KEX_KDF(cfg, value); + if (cfg->k.nid == NID_undef) + return -EINVAL; + } else if (strcmp(key, "digest") == 0) { + SET_KEX_DIGEST(cfg, value); + if (cfg->d.nid == NID_undef) + return -EINVAL; + } else if (strcmp(key, "kem_mode") == 0) { + if (strcmp(value, "server") == 0) { + cfg->x.mode = KEM_MODE_SERVER_ENCAP; + } else if (strcmp(value, "client") == 0) { + cfg->x.mode = KEM_MODE_CLIENT_ENCAP; + } else { + return -EINVAL; + } + } + } + + return 0; +} + +/* Parse key exchange config from file */ +int load_sec_config_file(struct sec_config * cfg, + const char * path) +{ + FILE * fp; + int ret; + + assert(cfg != NULL); + assert(path != NULL); + + fp = fopen(path, "r"); + if (fp == NULL) { + /* File doesn't exist - disable encryption */ + CLEAR_KEX_ALGO(cfg); + return 0; + } + + ret = parse_sec_config(cfg, fp); + + fclose(fp); + + return ret; +} + +int kex_pkp_create(struct sec_config * cfg, + void ** pkp, + uint8_t * pk) { #ifdef HAVE_OPENSSL + assert(cfg != NULL); assert(pkp != NULL); + *pkp = NULL; - return openssl_ecdh_pkp_create(pkp, pk); + + if (cfg->x.str == NULL || kex_validate_nid(cfg->x.nid) < 0) + return -ENOTSUP; + + return openssl_pkp_create(cfg->x.str, (EVP_PKEY **) pkp, pk); #else + (void) cfg; (void) pkp; (void) pk; @@ -57,12 +269,12 @@ int crypt_dh_pkp_create(void ** pkp, #endif } -void crypt_dh_pkp_destroy(void * pkp) +void kex_pkp_destroy(void * pkp) { if (pkp == NULL) return; #ifdef HAVE_OPENSSL - openssl_ecdh_pkp_destroy(pkp); + openssl_pkp_destroy((EVP_PKEY *) pkp); #else (void) pkp; @@ -70,12 +282,18 @@ void crypt_dh_pkp_destroy(void * pkp) #endif } -int crypt_dh_derive(void * pkp, - buffer_t pk, - uint8_t * s) +int kex_dhe_derive(struct sec_config * cfg, + void * pkp, + buffer_t pk, + uint8_t * s) { + assert(cfg != NULL); + + if (kex_validate_nid(cfg->x.nid) < 0) + return -ENOTSUP; + #ifdef HAVE_OPENSSL - return openssl_ecdh_derive(pkp, pk, s); + return openssl_dhe_derive((EVP_PKEY *) pkp, pk, cfg->k.nid, s); #else (void) pkp; (void) pk; @@ -86,6 +304,244 @@ int crypt_dh_derive(void * pkp, #endif } +ssize_t kex_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ +#ifdef HAVE_OPENSSL + return openssl_kem_encap(pk, ct, kdf, s); +#else + (void) pk; + (void) ct; + (void) kdf; + + memset(s, 0, SYMMKEYSZ); + + return -ECRYPT; +#endif +} + +ssize_t kex_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ +#ifdef HAVE_OPENSSL + return openssl_kem_encap_raw(pk, ct, kdf, s); +#else + (void) pk; + (void) ct; + (void) kdf; + + memset(s, 0, SYMMKEYSZ); + + return -ECRYPT; +#endif +} + +int kex_kem_decap(void * pkp, + buffer_t ct, + int kdf, + uint8_t * s) +{ +#ifdef HAVE_OPENSSL + return openssl_kem_decap((EVP_PKEY *) pkp, ct, kdf, s); +#else + (void) pkp; + (void) ct; + (void) kdf; + + memset(s, 0, SYMMKEYSZ); + + return -ECRYPT; +#endif +} + +int kex_get_algo_from_pk_der(buffer_t pk, + char * algo) +{ +#ifdef HAVE_OPENSSL + return openssl_get_algo_from_pk_der(pk, algo); +#else + (void) pk; + algo[0] = '\0'; + + return -ECRYPT; +#endif +} + +int kex_get_algo_from_pk_raw(buffer_t pk, + char * algo) +{ +#ifdef HAVE_OPENSSL + return openssl_get_algo_from_pk_raw(pk, algo); +#else + (void) pk; + algo[0] = '\0'; + + return -ECRYPT; +#endif +} + +int kex_validate_algo(const char * algo) +{ + if (algo == NULL) + return -EINVAL; + + /* Use NID validation instead of string array */ + return kex_validate_nid(kex_str_to_nid(algo)); +} + +int crypt_validate_nid(int nid) +{ + const struct nid_map * p; + + if (nid == NID_undef) + return -EINVAL; + + for (p = cipher_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return 0; + } + + return -ENOTSUP; +} + + +const char * crypt_nid_to_str(uint16_t nid) +{ + const struct nid_map * p; + + for (p = cipher_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return p->name; + } + + return NULL; +} + +uint16_t crypt_str_to_nid(const char * cipher) +{ + const struct nid_map * p; + + if (cipher == NULL) + return NID_undef; + + /* fast, check if cipher pointer is in the map */ + for (p = cipher_nid_map; p->name != NULL; p++) { + if (cipher == p->name) + return p->nid; + } + + for (p = cipher_nid_map; p->name != NULL; p++) { + if (strcmp(p->name, cipher) == 0) + return p->nid; + } + + return NID_undef; +} + +const char * kex_nid_to_str(uint16_t nid) +{ + const struct nid_map * p; + + for (p = kex_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return p->name; + } + + return NULL; +} + +uint16_t kex_str_to_nid(const char * algo) +{ + const struct nid_map * p; + + if (algo == NULL) + return NID_undef; + + /* Fast path: check if algo pointer is in the map */ + for (p = kex_nid_map; p->name != NULL; p++) { + if (algo == p->name) + return p->nid; + } + + /* Slow path: string comparison */ + for (p = kex_nid_map; p->name != NULL; p++) { + if (strcmp(p->name, algo) == 0) + return p->nid; + } + + return NID_undef; +} + +int kex_validate_nid(int nid) +{ + const struct nid_map * p; + + if (nid == NID_undef) + return -EINVAL; + + for (p = kex_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return 0; + } + + return -ENOTSUP; +} + +const char * md_nid_to_str(uint16_t nid) +{ + const struct nid_map * p; + + for (p = md_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return p->name; + } + + return NULL; +} + +uint16_t md_str_to_nid(const char * kdf) +{ + const struct nid_map * p; + + if (kdf == NULL) + return NID_undef; + + /* Fast path: check if kdf pointer is in the map */ + for (p = md_nid_map; p->name != NULL; p++) { + if (kdf == p->name) + return p->nid; + } + + /* Slow path: string comparison */ + for (p = md_nid_map; p->name != NULL; p++) { + if (strcmp(p->name, kdf) == 0) + return p->nid; + } + + return NID_undef; +} + +int md_validate_nid(int nid) +{ + const struct nid_map * p; + + if (nid == NID_undef) + return -EINVAL; + + for (p = md_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return 0; + } + + return -ENOTSUP; +} + +/* Hash length now returned by md_digest() */ + int crypt_encrypt(struct crypt_ctx * ctx, buffer_t in, buffer_t * out) @@ -94,7 +550,7 @@ int crypt_encrypt(struct crypt_ctx * ctx, assert(ctx->ctx != NULL); #ifdef HAVE_OPENSSL - return openssl_encrypt(ctx->ctx, ctx->key, in, out); + return openssl_encrypt(ctx->ctx, in, out); #else (void) ctx; (void) in; @@ -112,7 +568,7 @@ int crypt_decrypt(struct crypt_ctx * ctx, assert(ctx->ctx != NULL); #ifdef HAVE_OPENSSL - return openssl_decrypt(ctx->ctx, ctx->key, in, out); + return openssl_decrypt(ctx->ctx, in, out); #else (void) ctx; (void) in; @@ -122,20 +578,21 @@ int crypt_decrypt(struct crypt_ctx * ctx, #endif } -struct crypt_ctx * crypt_create_ctx(const uint8_t * key) +struct crypt_ctx * crypt_create_ctx(struct crypt_sk * sk) { struct crypt_ctx * crypt; + if (crypt_validate_nid(sk->nid) != 0) + return NULL; + crypt = malloc(sizeof(*crypt)); if (crypt == NULL) goto fail_crypt; memset(crypt, 0, sizeof(*crypt)); - if (key != NULL) - memcpy(crypt->key, key, SYMMKEYSZ); #ifdef HAVE_OPENSSL - crypt->ctx=openssl_crypt_create_ctx(); + crypt->ctx = openssl_crypt_create_ctx(sk); if (crypt->ctx == NULL) goto fail_ctx; #endif @@ -204,11 +661,72 @@ int crypt_load_pubkey_str(const char * str, #endif } +int crypt_load_pubkey_file(const char * path, + void ** key) +{ + *key = NULL; + +#ifdef HAVE_OPENSSL + return openssl_load_pubkey_file(path, key); +#else + (void) path; + + return 0; +#endif +} + +int crypt_load_pubkey_file_to_der(const char * path, + buffer_t * buf) +{ + assert(buf != NULL); + +#ifdef HAVE_OPENSSL + return openssl_load_pubkey_file_to_der(path, buf); +#else + (void) path; + + buf->data = NULL; + buf->len = 0; + return 0; +#endif +} + +int crypt_load_pubkey_raw_file(const char * path, + buffer_t * buf) +{ + assert(buf != NULL); + +#ifdef HAVE_OPENSSL + return openssl_load_pubkey_raw_file(path, buf); +#else + (void) path; + + buf->data = NULL; + buf->len = 0; + return 0; +#endif +} + +int crypt_load_privkey_raw_file(const char * path, + void ** key) +{ + *key = NULL; + +#ifdef HAVE_OPENSSL + return openssl_load_privkey_raw_file(path, key); +#else + (void) path; + + return 0; +#endif +} + int crypt_cmp_key(const void * key1, const void * key2) { #ifdef HAVE_OPENSSL - return openssl_cmp_key(key1, key2); + return openssl_cmp_key((const EVP_PKEY *) key1, + (const EVP_PKEY *) key2); #else (void) key1; (void) key2; @@ -223,7 +741,7 @@ void crypt_free_key(void * key) return; #ifdef HAVE_OPENSSL - openssl_free_key(key); + openssl_free_key((EVP_PKEY *) key); #endif } @@ -343,6 +861,19 @@ int crypt_check_crt_name(void * crt, #endif } +int crypt_get_crt_name(void * crt, + char * name) +{ +#ifdef HAVE_OPENSSL + return openssl_get_crt_name(crt, name); +#else + (void) crt; + (void) name; + + return 0; +#endif +} + struct auth_ctx * auth_create_ctx(void) { struct auth_ctx * ctx; @@ -406,13 +937,15 @@ int auth_verify_crt(struct auth_ctx * ctx, } int auth_sign(void * pkp, + int md_nid, buffer_t msg, buffer_t * sig) { #ifdef HAVE_OPENSSL - return openssl_sign(pkp, msg, sig); + return openssl_sign((EVP_PKEY *) pkp, md_nid, msg, sig); #else (void) pkp; + (void) md_nid; (void) msg; (void) sig; @@ -423,16 +956,82 @@ int auth_sign(void * pkp, } int auth_verify_sig(void * pk, + int md_nid, buffer_t msg, buffer_t sig) { #ifdef HAVE_OPENSSL - return openssl_verify_sig(pk, msg, sig); + return openssl_verify_sig((EVP_PKEY *) pk, md_nid, msg, sig); #else (void) pk; + (void) md_nid; (void) msg; (void) sig; return 0; #endif } + +ssize_t md_digest(int md_nid, + buffer_t in, + uint8_t * out) +{ +#ifdef HAVE_OPENSSL + return openssl_md_digest(md_nid, in, out); +#else + (void) md_nid; + (void) in; + (void) out; + + return -1; +#endif +} + +ssize_t md_len(int md_nid) +{ +#ifdef HAVE_OPENSSL + return openssl_md_len(md_nid); +#else + (void) md_nid; + return -1; +#endif +} + +int crypt_secure_malloc_init(size_t max) +{ +#ifdef HAVE_OPENSSL + return openssl_secure_malloc_init(max, SECMEM_GUARD); +#else + return 0; +#endif +} + +void crypt_secure_malloc_fini(void) +{ +#ifdef HAVE_OPENSSL + openssl_secure_malloc_fini(); +#endif +} + +void * crypt_secure_malloc(size_t size) +{ +#ifdef HAVE_OPENSSL + return openssl_secure_malloc(size); +#else + return malloc(size); +#endif +} + +void crypt_secure_free(void * ptr, + size_t size) +{ + if (ptr == NULL) + return; + +#ifdef HAVE_OPENSSL + openssl_secure_free(ptr, size); +#else + memset(ptr, 0, size); + free(ptr); +#endif +} -- cgit v1.2.3