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/tests/crypt_test.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/tests/crypt_test.c')
| -rw-r--r-- | src/lib/tests/crypt_test.c | 262 |
1 files changed, 138 insertions, 124 deletions
diff --git a/src/lib/tests/crypt_test.c b/src/lib/tests/crypt_test.c index e7a09e8f..906059be 100644 --- a/src/lib/tests/crypt_test.c +++ b/src/lib/tests/crypt_test.c @@ -22,45 +22,32 @@ #include "config.h" -#include <ouroboros/test.h> -#include <ouroboros/crypt.h> +#include <test/test.h> #include <ouroboros/random.h> +#include <ouroboros/crypt.h> #include <ouroboros/utils.h> -#define TEST_PACKET_SIZE 1500 - -static int test_crypt_create_destroy(void) -{ - struct crypt_ctx * ctx; - - TEST_START(); - - ctx = crypt_create_ctx(NULL); - if (ctx == NULL) { - printf("Failed to initialize cryptography.\n"); - goto fail; - } - - crypt_destroy_ctx(ctx); +#include <stdio.h> - TEST_SUCCESS(); +#define TEST_PACKET_SIZE 1500 - return TEST_RC_SUCCESS; - fail: - TEST_FAIL(); - return TEST_RC_FAIL; -} +extern const uint16_t crypt_supported_nids[]; +extern const uint16_t md_supported_nids[]; -static int test_crypt_create_destroy_with_key(void) +static int test_crypt_create_destroy(void) { struct crypt_ctx * ctx; uint8_t key[SYMMKEYSZ]; + struct crypt_sk sk = { + .nid = NID_aes_256_gcm, + .key = key + }; TEST_START(); memset(key, 0, sizeof(key)); - ctx = crypt_create_ctx(key); + ctx = crypt_create_ctx(&sk); if (ctx == NULL) { printf("Failed to initialize cryptography.\n"); goto fail; @@ -76,100 +63,22 @@ static int test_crypt_create_destroy_with_key(void) return TEST_RC_FAIL; } -static int test_crypt_dh_pkp_create_destroy(void) -{ - void * pkp; - uint8_t buf[MSGBUFSZ]; - - TEST_START(); - - if (crypt_dh_pkp_create(&pkp, buf) < 0) { - printf("Failed to create DH PKP."); - goto fail; - } - - crypt_dh_pkp_destroy(pkp); - - TEST_SUCCESS(); - - return TEST_RC_SUCCESS; - fail: - TEST_FAIL(); - return TEST_RC_FAIL; -} - -static int test_crypt_dh_derive(void) -{ - void * pkp1; - void * pkp2; - buffer_t pk1; - buffer_t pk2; - ssize_t len; - uint8_t buf1[MSGBUFSZ]; - uint8_t buf2[MSGBUFSZ]; - uint8_t s1[SYMMKEYSZ]; - uint8_t s2[SYMMKEYSZ]; - - TEST_START(); - - len = crypt_dh_pkp_create(&pkp1, buf1); - if (len < 0) { - printf("Failed to create first key pair."); - goto fail_pkp1; - } - - pk1.len = (size_t) len; - pk1.data = buf1; - - len = crypt_dh_pkp_create(&pkp2, buf2); - if (len < 0) { - printf("Failed to create second key pair."); - goto fail_pkp2; - } - - pk2.len = (size_t) len; - pk2.data = buf2; - - if (crypt_dh_derive(pkp1, pk2, s1) < 0) { - printf("Failed to derive first key."); - goto fail; - } - - if (crypt_dh_derive(pkp2, pk1, s2) < 0) { - printf("Failed to derive second key."); - goto fail; - } - - if (memcmp(s1, s2, SYMMKEYSZ) != 0) { - printf("Derived keys do not match."); - goto fail; - } - - crypt_dh_pkp_destroy(pkp2); - crypt_dh_pkp_destroy(pkp1); - - TEST_SUCCESS(); - - return TEST_RC_SUCCESS; - fail: - crypt_dh_pkp_destroy(pkp2); - fail_pkp2: - crypt_dh_pkp_destroy(pkp1); - fail_pkp1: - TEST_FAIL(); - return TEST_RC_FAIL; -} - -int test_crypt_encrypt_decrypt(void) +static int test_crypt_encrypt_decrypt(int nid) { uint8_t pkt[TEST_PACKET_SIZE]; - uint8_t key[SYMMKEYSZ]; struct crypt_ctx * ctx; + uint8_t key[SYMMKEYSZ]; + struct crypt_sk sk = { + .nid = NID_aes_256_gcm, + .key = key + }; buffer_t in; buffer_t out; buffer_t out2; + const char * cipher; - TEST_START(); + cipher = crypt_nid_to_str(nid); + TEST_START("(%s)", cipher); if (random_buffer(key, sizeof(key)) < 0) { printf("Failed to generate random key.\n"); @@ -181,7 +90,7 @@ int test_crypt_encrypt_decrypt(void) goto fail_init; } - ctx = crypt_create_ctx(key); + ctx = crypt_create_ctx(&sk); if (ctx == NULL) { printf("Failed to initialize cryptography.\n"); goto fail_init; @@ -219,7 +128,7 @@ int test_crypt_encrypt_decrypt(void) freebuf(out2); freebuf(out); - TEST_SUCCESS(); + TEST_SUCCESS("(%s)", cipher); return TEST_RC_SUCCESS; fail_chk: @@ -229,10 +138,122 @@ int test_crypt_encrypt_decrypt(void) fail_encrypt: crypt_destroy_ctx(ctx); fail_init: + TEST_FAIL("(%s)", cipher); + return TEST_RC_FAIL; +} + +static int test_encrypt_decrypt_all(void) +{ + int ret = 0; + int i; + + for (i = 0; crypt_supported_nids[i] != NID_undef; i++) + ret |= test_crypt_encrypt_decrypt(crypt_supported_nids[i]); + + return ret; +} + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#include <openssl/obj_mac.h> + +static int test_cipher_nid_values(void) +{ + int i; + + TEST_START(); + + /* Loop over all supported ciphers and verify NIDs match OpenSSL's */ + for (i = 0; crypt_supported_nids[i] != NID_undef; i++) { + uint16_t our_nid = crypt_supported_nids[i]; + const char * str = crypt_nid_to_str(our_nid); + const EVP_CIPHER * cipher; + int openssl_nid; + + if (str == NULL) { + printf("crypt_nid_to_str failed for NID %u\n", our_nid); + goto fail; + } + + cipher = EVP_get_cipherbyname(str); + if (cipher == NULL) { + printf("OpenSSL doesn't recognize cipher '%s'\n", str); + goto fail; + } + + openssl_nid = EVP_CIPHER_nid(cipher); + + if (our_nid != openssl_nid) { + printf("NID mismatch for '%s': ours=%u, OpenSSL=%d\n", + str, our_nid, openssl_nid); + goto fail; + } + + /* Test reverse conversion */ + if (crypt_str_to_nid(str) != our_nid) { + printf("crypt_str_to_nid failed for '%s'\n", str); + goto fail; + } + } + + /* Test error cases */ + if (crypt_str_to_nid("invalid") != NID_undef) { + printf("crypt_str_to_nid: no NID_undef for invalid.\n"); + goto fail; + } + + if (crypt_nid_to_str(9999) != NULL) { + printf("crypt_nid_to_str should return NULL for invalid NID\n"); + goto fail; + } + + if (crypt_str_to_nid(NULL) != NID_undef) { + printf("crypt_str_to_nid should return NID_undef for NULL\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: TEST_FAIL(); return TEST_RC_FAIL; } +static int test_md_nid_values(void) +{ + int i; + + TEST_START(); + + for (i = 0; md_supported_nids[i] != NID_undef; i++) { + uint16_t our_nid = md_supported_nids[i]; + const EVP_MD * md; + int openssl_nid; + + md = EVP_get_digestbynid(our_nid); + if (md == NULL) { + printf("OpenSSL doesn't recognize NID %u\n", our_nid); + goto fail; + } + + openssl_nid = EVP_MD_nid(md); + if (our_nid != openssl_nid) { + printf("NID mismatch: ours=%u, OpenSSL=%d\n", + our_nid, openssl_nid); + goto fail; + } + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} +#endif + int crypt_test(int argc, char ** argv) { @@ -242,17 +263,10 @@ int crypt_test(int argc, (void) argv; ret |= test_crypt_create_destroy(); - ret |= test_crypt_create_destroy_with_key(); + ret |= test_encrypt_decrypt_all(); #ifdef HAVE_OPENSSL - ret |= test_crypt_dh_pkp_create_destroy(); - ret |= test_crypt_dh_derive(); - ret |= test_crypt_encrypt_decrypt(); -#else - (void) test_crypt_dh_pkp_create_destroy; - (void) test_crypt_dh_derive; - (void) test_crypt_encrypt_decrypt; - - ret = TEST_RC_SKIP; + ret |= test_cipher_nid_values(); + ret |= test_md_nid_values(); #endif return ret; } |
