diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-01-07 16:44:34 +0100 |
|---|---|---|
| committer | Sander Vrijders <sander@ouroboros.rocks> | 2026-01-19 08:29:29 +0100 |
| commit | 60b04305d70614580b4f883c0a147507edef3779 (patch) | |
| tree | 08e0513f39a17cbd31712d09d32354a63acd5a24 /src/lib/crypt.c | |
| parent | 8aa6ab4d29df80adde0d512244d43d38264bf32e (diff) | |
| download | ouroboros-60b04305d70614580b4f883c0a147507edef3779.tar.gz ouroboros-60b04305d70614580b4f883c0a147507edef3779.zip | |
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=<algorithm> # Key exchange algorithm
cipher=<algorithm> # Symmetric cipher
kdf=<KDF> # Key derivation function
digest=<digest> # Digest for DSA
kem_mode=<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 <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
Diffstat (limited to 'src/lib/crypt.c')
| -rw-r--r-- | src/lib/crypt.c | 647 |
1 files changed, 623 insertions, 24 deletions
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 <config.h> -#include <ouroboros/crypt.h> #include <ouroboros/errno.h> +#include <ouroboros/random.h> +#include <ouroboros/crypt.h> + #ifdef HAVE_OPENSSL - #include "crypt/openssl.h" -#endif /* HAVE_OPENSSL */ +#include <openssl/evp.h> +#include "crypt/openssl.h" +#endif #include <assert.h> +#include <stdio.h> #include <string.h> +#include <sys/stat.h> + +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 +} |
