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 --- cmake/dependencies/openssl.cmake | 21 +- cmake/ipcp/eth.cmake | 4 +- cmake/irmd.cmake | 12 +- cmake/tests.cmake | 3 +- cmake/utils/TestUtils.cmake | 6 + doc/man/ouroboros.8 | 6 +- enc.conf.in | 150 +++ include/ouroboros/crypt.h | 285 +++++- include/ouroboros/flow.h | 4 +- include/ouroboros/name.h | 4 +- include/ouroboros/serdes-irm.h | 4 +- include/ouroboros/test.h | 93 -- include/ouroboros/utils.h | 7 +- include/test/certs.h | 125 +++ include/test/certs_pqc.h | 656 +++++++++++++ include/test/test.h | 103 ++ irmd.conf.in | 4 +- src/ipcpd/config.h.in | 9 +- src/ipcpd/eth/eth.c | 8 +- src/ipcpd/unicast/dir/tests/CMakeLists.txt | 6 +- src/ipcpd/unicast/dir/tests/dht_test.c | 2 +- src/ipcpd/unicast/pff/tests/CMakeLists.txt | 4 +- src/ipcpd/unicast/routing/tests/CMakeLists.txt | 4 +- src/irmd/config.h.in | 6 + src/irmd/main.c | 746 ++++----------- src/irmd/oap.c | 290 ++---- src/irmd/oap.h | 93 +- src/irmd/oap/auth.c | 252 +++++ src/irmd/oap/auth.h | 35 + src/irmd/oap/cli.c | 553 +++++++++++ src/irmd/oap/hdr.c | 456 +++++++++ src/irmd/oap/hdr.h | 159 ++++ src/irmd/oap/internal.h | 133 +++ src/irmd/oap/io.c | 132 +++ src/irmd/oap/io.h | 40 + src/irmd/oap/srv.c | 462 +++++++++ src/irmd/oap/tests/CMakeLists.txt | 78 ++ src/irmd/oap/tests/common.c | 457 +++++++++ src/irmd/oap/tests/common.h | 100 ++ src/irmd/oap/tests/oap_test.c | 947 +++++++++++++++++++ src/irmd/oap/tests/oap_test_pqc.c | 363 ++++++++ src/irmd/reg/ipcp.c | 1 - src/irmd/reg/tests/CMakeLists.txt | 4 +- src/irmd/reg/tests/flow_test.c | 2 +- src/irmd/reg/tests/ipcp_test.c | 2 +- src/irmd/reg/tests/name_test.c | 2 +- src/irmd/reg/tests/proc_test.c | 2 +- src/irmd/reg/tests/prog_test.c | 2 +- src/irmd/reg/tests/reg_test.c | 4 +- src/irmd/tests/CMakeLists.txt | 28 - src/irmd/tests/irm_test.c | 33 - src/irmd/tests/oap_test.c | 948 +++++++++++++++---- src/lib/config.h.in | 4 +- src/lib/crypt.c | 647 ++++++++++++- src/lib/crypt/openssl.c | 1190 +++++++++++++++++++++--- src/lib/crypt/openssl.h | 99 +- src/lib/dev.c | 101 +- src/lib/pb/irm.proto | 9 +- src/lib/protobuf.c | 3 + src/lib/serdes-irm.c | 24 +- src/lib/tests/CMakeLists.txt | 14 +- src/lib/tests/auth_test.c | 157 +--- src/lib/tests/auth_test_pqc.c | 356 +++++++ src/lib/tests/crypt_test.c | 262 +++--- src/lib/tests/hash_test.c | 2 +- src/lib/tests/kex_test.c | 844 +++++++++++++++++ src/lib/tests/kex_test_pqc.c | 549 +++++++++++ src/lib/tests/sockets_test.c | 2 +- src/lib/tests/time_test.c | 2 +- src/lib/tests/tpm_test.c | 2 +- src/lib/utils.c | 19 + src/tools/irm/irm_name_create.c | 4 +- 72 files changed, 10400 insertions(+), 1740 deletions(-) create mode 100644 cmake/utils/TestUtils.cmake create mode 100644 enc.conf.in delete mode 100644 include/ouroboros/test.h create mode 100644 include/test/certs.h create mode 100644 include/test/certs_pqc.h create mode 100644 include/test/test.h create mode 100644 src/irmd/oap/auth.c create mode 100644 src/irmd/oap/auth.h create mode 100644 src/irmd/oap/cli.c create mode 100644 src/irmd/oap/hdr.c create mode 100644 src/irmd/oap/hdr.h create mode 100644 src/irmd/oap/internal.h create mode 100644 src/irmd/oap/io.c create mode 100644 src/irmd/oap/io.h create mode 100644 src/irmd/oap/srv.c create mode 100644 src/irmd/oap/tests/CMakeLists.txt create mode 100644 src/irmd/oap/tests/common.c create mode 100644 src/irmd/oap/tests/common.h create mode 100644 src/irmd/oap/tests/oap_test.c create mode 100644 src/irmd/oap/tests/oap_test_pqc.c delete mode 100644 src/irmd/tests/CMakeLists.txt delete mode 100644 src/irmd/tests/irm_test.c create mode 100644 src/lib/tests/auth_test_pqc.c create mode 100644 src/lib/tests/kex_test.c create mode 100644 src/lib/tests/kex_test_pqc.c diff --git a/cmake/dependencies/openssl.cmake b/cmake/dependencies/openssl.cmake index 604d5d99..19edfa2b 100644 --- a/cmake/dependencies/openssl.cmake +++ b/cmake/dependencies/openssl.cmake @@ -1,21 +1,36 @@ find_package(OpenSSL QUIET) if (OPENSSL_FOUND) set(HAVE_OPENSSL_RNG TRUE) - if (OPENSSL_VERSION VERSION_LESS "1.1.0") - message(STATUS "Install version >= \"1.1.0\" to enable OpenSSL support " + if (OPENSSL_VERSION VERSION_LESS "3.0.0") + message(STATUS "Install version >= 3.0.0 to enable OpenSSL support " "(found version \"${OPENSSL_VERSION}\")") else () set(DISABLE_OPENSSL FALSE CACHE BOOL "Disable OpenSSL support") if (NOT DISABLE_OPENSSL) message(STATUS "OpenSSL support enabled") set(HAVE_OPENSSL TRUE CACHE INTERNAL "") + set(IRMD_SECMEM_MAX 1048576 CACHE STRING "IRMd secure heap size") + set(PROC_SECMEM_MAX 1048576 CACHE STRING "Process secure heap size") + set(SECMEM_GUARD 32 CACHE STRING "Secure heap min size") + set(DISABLE_PQC FALSE CACHE BOOL "Disable post-quantum cryptography support") + if (OPENSSL_VERSION VERSION_GREATER_EQUAL "3.4.0") + if (NOT DISABLE_PQC) + set(HAVE_OPENSSL_PQC TRUE CACHE INTERNAL "") + message(STATUS "OpenSSL PQC support enabled") + else() + message(STATUS "OpenSSL PQC support disabled by user") + unset(HAVE_OPENSSL_PQC CACHE) + endif() + else() + message(STATUS "Install OpenSSL >= 3.4.0 for PQC support") + endif() else() message(STATUS "OpenSSL support disabled") unset(HAVE_OPENSSL CACHE) endif() endif () else() - message(STATUS "Install openSSL version >= \"1.1.0\" to enable OpenSSL support") + message(STATUS "Install OpenSSL version >= 3.0.0 to enable OpenSSL support") unset(HAVE_OPENSSL_RNG) unset(HAVE_OPENSSL CACHE) set(OPENSSL_INCLUDE_DIR "") diff --git a/cmake/ipcp/eth.cmake b/cmake/ipcp/eth.cmake index 0d37bf9b..c14a1d6e 100644 --- a/cmake/ipcp/eth.cmake +++ b/cmake/ipcp/eth.cmake @@ -9,8 +9,10 @@ set(IPCP_ETH_WR_THR 1 CACHE STRING "Number of writer threads in Ethernet IPCP") set(IPCP_ETH_QDISC_BYPASS false CACHE BOOL "Bypass the Qdisc in the kernel when using raw sockets") -set(IPCP_ETH_LO_MTU 1500 CACHE STRING +set(IPCP_ETH_LO_MTU 9000 CACHE STRING "Restrict Ethernet MTU over loopback interfaces") +set(IPCP_ETH_MGMT_FRAME_SIZE 9000 CACHE STRING + "Management frame buffer size for Ethernet IPCPs") set(IPCP_ETH_MPL 100 CACHE STRING "Default maximum packet lifetime for the Ethernet IPCPs, in ms") diff --git a/cmake/irmd.cmake b/cmake/irmd.cmake index d4b4808c..f02f37d6 100644 --- a/cmake/irmd.cmake +++ b/cmake/irmd.cmake @@ -31,6 +31,8 @@ set(CONNECT_TIMEOUT 20000 CACHE STRING "Timeout to connect an IPCP to another IPCP (ms)") set(FLOW_ALLOC_TIMEOUT 20000 CACHE STRING "Timeout for a flow allocation response (ms)") +set(OAP_REPLAY_TIMER 20 CACHE STRING + "OAP replay protection window (s)") set(IRMD_MIN_THREADS 8 CACHE STRING "Minimum number of worker threads in the IRMd") set(IRMD_ADD_THREADS 8 CACHE STRING @@ -55,8 +57,12 @@ if (LIBTOML_LIBRARIES) set(INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") configure_file("${CMAKE_SOURCE_DIR}/irmd.conf.in" "${CMAKE_BINARY_DIR}/${OUROBOROS_CONFIG_FILE}.example" @ONLY) + configure_file("${CMAKE_SOURCE_DIR}/enc.conf.in" + "${CMAKE_BINARY_DIR}/enc.conf.example" @ONLY) install(FILES "${CMAKE_BINARY_DIR}/${OUROBOROS_CONFIG_FILE}.example" DESTINATION "${OUROBOROS_CONFIG_DIR}") + install(FILES "${CMAKE_BINARY_DIR}/enc.conf.example" + DESTINATION "${OUROBOROS_CONFIG_DIR}") install(CODE " if (NOT EXISTS \"${OUROBOROS_CONFIG_DIR}/${OUROBOROS_CONFIG_FILE}\") file(WRITE \"${OUROBOROS_CONFIG_DIR}/${OUROBOROS_CONFIG_FILE}\" \"\") @@ -81,7 +87,11 @@ set(IRMD_SOURCES "${IRMD_SOURCE_DIR}/ipcp.c" "${IRMD_SOURCE_DIR}/configfile.c" "${IRMD_SOURCE_DIR}/main.c" - "${IRMD_SOURCE_DIR}/oap.c" + "${IRMD_SOURCE_DIR}/oap/io.c" + "${IRMD_SOURCE_DIR}/oap/hdr.c" + "${IRMD_SOURCE_DIR}/oap/auth.c" + "${IRMD_SOURCE_DIR}/oap/srv.c" + "${IRMD_SOURCE_DIR}/oap/cli.c" "${IRMD_SOURCE_DIR}/reg/flow.c" "${IRMD_SOURCE_DIR}/reg/ipcp.c" "${IRMD_SOURCE_DIR}/reg/proc.c" diff --git a/cmake/tests.cmake b/cmake/tests.cmake index 4c16171c..456ad120 100644 --- a/cmake/tests.cmake +++ b/cmake/tests.cmake @@ -1,4 +1,5 @@ include(CTest) # Sets BUILD_TESTING by default to on. +include(utils/TestUtils) include(utils/DisableTestLogging) @@ -16,7 +17,7 @@ if (BUILD_TESTS) # Add test subdirectories add_subdirectory(src/lib/tests) - add_subdirectory(src/irmd/tests) + add_subdirectory(src/irmd/oap/tests) add_subdirectory(src/ipcpd/unicast/pff/tests) add_subdirectory(src/ipcpd/unicast/routing/tests) add_subdirectory(src/ipcpd/unicast/dir/tests) diff --git a/cmake/utils/TestUtils.cmake b/cmake/utils/TestUtils.cmake new file mode 100644 index 00000000..e40bdda1 --- /dev/null +++ b/cmake/utils/TestUtils.cmake @@ -0,0 +1,6 @@ +# Compute test name prefix from directory structure +function(compute_test_prefix) + file(RELATIVE_PATH _prefix "${CMAKE_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}") + string(REGEX REPLACE "/tests$" "" _prefix "${_prefix}") + set(TEST_PREFIX "${_prefix}" PARENT_SCOPE) +endfunction(compute_test_prefix) diff --git a/doc/man/ouroboros.8 b/doc/man/ouroboros.8 index 5d3d9475..759b1433 100644 --- a/doc/man/ouroboros.8 +++ b/doc/man/ouroboros.8 @@ -389,11 +389,11 @@ not accept future flow allocation requests for \fIname\fR. .SH IRM NAME COMMANDS .PP \fBirm name create \fIname\fR lb \fIpolicy\fR -[sencpath \fI/path/to/server/enc.cfg\fR] +[sencpath \fI/path/to/server/enc.conf\fR] [scrtpath \fI/path/to/server/crt.pem\fR] [skeypath \fI/path/to/server/key.pem\fR] -[cencpath \fI/path/to/client/enc.cfg\fR] +[cencpath \fI/path/to/client/enc.conf\fR] [ccrtpath \fI/path/to/client/crt.pem\fR] [ckeypath \fI/path/to/client/key.pem\fR] .RS 4 @@ -401,7 +401,7 @@ Create a name \fIname\fR with a load-balancing policy and security credentials .br \fIpolicy\fR: round-robin, spillover .br -\fI/path/to/enc.cfg\fR: The path to the server and client encryption configuration. +\fI/path/to/enc.conf\fR: The path to the server and client encryption configuration. \fI/path/to/pem\fR: The path to the server and client certificates and private keys, in pem format. .br diff --git a/enc.conf.in b/enc.conf.in new file mode 100644 index 00000000..64502fbb --- /dev/null +++ b/enc.conf.in @@ -0,0 +1,150 @@ +### Example Ouroboros encryption configuration file +# +# This file specifies the key exchange (KEX) algorithm and cipher to use +# for encrypted flows. +# +# File Locations: +# --------------- +# +# This file should be placed at one of: +# @OUROBOROS_CONFIG_DIR@/security/server//enc.conf (server-side config) +# @OUROBOROS_CONFIG_DIR@/security/client//enc.conf (client-side config) +# +# Where is the service name registered with 'irm name create'. +# +# You can override the default paths using: +# irm name create sencpath cencpath +# +# Configuration Options: +# ---------------------- +# +# kex= Key exchange/encapsulation algorithm +# cipher= Symmetric cipher algorithm +# kdf= Key derivation function hash algorithm +# kem_mode= KEM encapsulation mode (server or client) +# none Explicitly disable encryption +# +# Supported KEX algorithms (kex=): +# -------------------------------- +# +# ECDH Curves: +# prime256v1 NIST P-256 (default) +# secp384r1 NIST P-384 +# secp521r1 NIST P-521 +# X25519 Curve25519 +# X448 Curve448 +# +# Finite Field Diffie-Hellman (RFC 7919): +# ffdhe2048 2048-bit MODP Group +# ffdhe3072 3072-bit MODP Group +# ffdhe4096 4096-bit MODP Group +# +# ML-KEM (FIPS 203): +# ML-KEM-512 CRYSTALS-Kyber-512 +# ML-KEM-768 CRYSTALS-Kyber-768 +# ML-KEM-1024 CRYSTALS-Kyber-1024 +# +# Hybrid KEMs: +# X25519MLKEM768 X25519 + ML-KEM-768 +# X448MLKEM1024 X448 + ML-KEM-1024 +# +# Supported cipher algorithms (cipher=): +# -------------------------------------- +# +# Authenticated encryption: +# aes-128-gcm AES-128 in GCM mode +# aes-192-gcm AES-192 in GCM mode +# aes-256-gcm AES-256 in GCM mode (default) +# chacha20-poly1305 ChaCha20-Poly1305 +# +# Stream ciphers (not recommended): +# aes-128-ctr AES-128 in CTR mode +# aes-192-ctr AES-192 in CTR mode +# aes-256-ctr AES-256 in CTR mode +# +# Key Derivation Functions (kdf=): +# --------------------------------- +# +# Hash algorithms for key derivation in KEX operations: +# +# sha256 SHA-256 (default) +# sha384 SHA-384 +# sha512 SHA-512 +# sha3-256 SHA3-256 +# sha3-384 SHA3-384 +# sha3-512 SHA3-512 +# blake2b512 BLAKE2b-512 (requires OpenSSL 1.1.0+) +# blake2s256 BLAKE2s-256 (requires OpenSSL 1.1.0+) +# +# KEM Mode (kem_mode=): +# --------------------- +# +# For KEM algorithms (ML-KEM-* and hybrid KEMs), specify which side +# performs the encapsulation operation: +# +# server Server encapsulates to client's ephemeral public key (default, matches TLS 1.3) +# - Client generates ephemeral keypair, sends public key in request +# - Server encapsulates and sends ciphertext in response +# - Client decapsulates with ephemeral private key +# - Standard approach, no pre-shared keys needed +# +# client Client encapsulates to server's static public key (alternative) +# - Requires cached server public key at: +# @OUROBOROS_CONFIG_DIR@/security/client//kex.srv.pub.[pem|raw] +# - Client encapsulates and sends ciphertext in initial request +# - Server decapsulates with its static private key from: +# @OUROBOROS_CONFIG_DIR@/security/server/kex.key.pem +# - More efficient (0 round-trip) but requires key distribution +# and forfeits forward secrecy +# +# Note: Both sides must use the same kem_mode setting. +# This option is ignored for ECDH/DH key exchange algorithms. +# +# Key Management for Client Mode: +# -------------------------------- +# +# For client encapsulation mode, you must: +# 1. Generate server KEM keypair: +# openssl genpkey -algorithm ML-KEM-768 \ +# -out @OUROBOROS_CONFIG_DIR@/security/server/kex.key.pem +# 2. Extract and distribute server public key: +# openssl pkey -in kex.key.pem -pubout -out kex.srv.pub.pem +# 3. Cache on clients at: +# @OUROBOROS_CONFIG_DIR@/security/client//kex.srv.pub.pem +# +# File formats: +# - Pure ML-KEM: PEM format (.pem extension) +# - Hybrid KEMs: Raw bytes (.raw extension) +# +# Examples: +# --------- +# +# Default configuration (NIST P-256 ECDH + AES-256-GCM): +kex=prime256v1 +cipher=aes-256-gcm +kdf=sha256 +# +# Post-quantum KEX with server encapsulation (default, like TLS 1.3): +# kex=ML-KEM-768 +# cipher=chacha20-poly1305 +# kdf=sha256 +# kem_mode=server +# +# Post-quantum KEX with client encapsulation (requires key distribution): +# kex=ML-KEM-768 +# cipher=chacha20-poly1305 +# kdf=sha256 +# kem_mode=client +# +# Hybrid KEX (quantum-resistant): +# kex=X25519MLKEM768 +# cipher=aes-256-gcm +# kdf=sha256 +# +# High security configuration: +# kex=secp521r1 +# cipher=aes-256-gcm +# kdf=sha512 +# +# Disable encryption: +# none diff --git a/include/ouroboros/crypt.h b/include/ouroboros/crypt.h index 2d7cda6d..c463d9a8 100644 --- a/include/ouroboros/crypt.h +++ b/include/ouroboros/crypt.h @@ -26,25 +26,255 @@ #include #include -#define IVSZ 16 -#define SYMMKEYSZ 32 -#define MSGBUFSZ 2048 +#include + +#define IVSZ 16 +#define SYMMKEYSZ 32 +#define MAX_HASH_SIZE 64 /* SHA-512/BLAKE2b max */ +#define KEX_ALGO_BUFSZ 32 +#define KEX_CIPHER_BUFSZ 32 +#define MSGBUFSZ 2048 + +/* Cipher NIDs (match OpenSSL values) */ +#define NID_undef 0 +#define NID_aes_128_gcm 895 +#define NID_aes_192_gcm 898 +#define NID_aes_256_gcm 901 +#define NID_aes_128_ctr 904 +#define NID_aes_192_ctr 905 +#define NID_aes_256_ctr 906 +#define NID_chacha20_poly1305 1018 + +/* KDF NIDs (match OpenSSL values) */ +#define NID_hkdf 1036 +#define NID_sha256 672 +#define NID_sha384 673 +#define NID_sha512 674 +#define NID_sha3_256 1096 +#define NID_sha3_384 1097 +#define NID_sha3_512 1098 +#define NID_blake2b512 1056 +#define NID_blake2s256 1057 + +/* KEX algorithm NIDs (match OpenSSL values) */ +#define NID_X9_62_prime256v1 415 +#define NID_secp384r1 715 +#define NID_secp521r1 716 +#define NID_X25519 1034 +#define NID_X448 1035 +#define NID_ffdhe2048 1126 +#define NID_ffdhe3072 1127 +#define NID_ffdhe4096 1128 +#define NID_MLKEM512 1454 +#define NID_MLKEM768 1455 +#define NID_MLKEM1024 1456 +#define NID_X25519MLKEM768 2053 /* !! not in OpenSSL */ +#define NID_X448MLKEM1024 2054 /* !! not in OpenSSL */ + +#define IS_KEM_ALGORITHM(algo) \ + (strstr(algo, "ML-KEM") != NULL || strstr(algo, "MLKEM") != NULL) + +#define IS_HYBRID_KEM(algo) \ + ((strstr(algo, "X25519") != NULL || strstr(algo, "X448") != NULL) && \ + strstr(algo, "MLKEM") != NULL) + +#define X25519MLKEM768_PKSZ 1216 /* 32 + 1184 */ +#define X25519MLKEM768_CTSZ 1120 /* 32 + 1088 */ +#define X25519MLKEM768_SKSZ 2432 /* 32 + 2400 */ +#define X448MLKEM1024_PKSZ 1624 /* 56 + 1568 */ +#define X448MLKEM1024_SKSZ 3224 /* 56 + 3168 */ + +#define KEM_MODE_SERVER_ENCAP 0 /* Server encapsulates (default) */ +#define KEM_MODE_CLIENT_ENCAP 1 /* Client encapsulates */ +#define IS_KEX_ALGO_SET(cfg) ((cfg)->x.nid != NID_undef) +#define IS_KEX_CIPHER_SET(cfg) ((cfg)->c.nid != NID_undef) + + +struct crypt_sk { + int nid; + uint8_t * key; +}; + +struct sec_config { + struct { + const char * str; + int nid; + int mode; + } x; /* key exchange */ + struct { + const char * str; + int nid; + } k; /* kdf */ + struct { + const char * str; + int nid; + } c; /* cipher */ + struct { + const char * str; + int nid; + } d; /* digest */ +}; + +/* Helper macros to set sec_config fields consistently */ +#define SET_KEX_ALGO(cfg, algo_str) do { \ + (cfg)->x.nid = kex_str_to_nid(algo_str); \ + (cfg)->x.str = kex_nid_to_str((cfg)->x.nid); \ + assert((cfg)->x.nid != NID_undef || (cfg)->x.str == NULL); \ +} while (0) + +#define SET_KEX_ALGO_NID(cfg, nid_val) do { \ + (cfg)->x.nid = (nid_val); \ + (cfg)->x.str = kex_nid_to_str((cfg)->x.nid); \ + assert((cfg)->x.nid != NID_undef || (cfg)->x.str == NULL); \ +} while (0) + +#define SET_KEX_KEM_MODE(cfg, mode_val) do { \ + (cfg)->x.mode = (mode_val); \ +} while (0) + +#define SET_KEX_KDF(cfg, kdf_str) do { \ + (cfg)->k.nid = md_str_to_nid(kdf_str); \ + (cfg)->k.str = md_nid_to_str((cfg)->k.nid); \ + assert((cfg)->k.nid != NID_undef || (cfg)->k.str == NULL); \ +} while (0) + +#define SET_KEX_KDF_NID(cfg, nid_val) do { \ + (cfg)->k.nid = (nid_val); \ + (cfg)->k.str = md_nid_to_str((cfg)->k.nid); \ + assert((cfg)->k.nid != NID_undef || (cfg)->k.str == NULL); \ +} while (0) + +#define SET_KEX_CIPHER(cfg, cipher_str) do { \ + (cfg)->c.nid = crypt_str_to_nid(cipher_str); \ + (cfg)->c.str = crypt_nid_to_str((cfg)->c.nid); \ + assert((cfg)->c.nid != NID_undef || (cfg)->c.str == NULL); \ +} while (0) + +#define SET_KEX_CIPHER_NID(cfg, nid_val) do { \ + (cfg)->c.nid = (nid_val); \ + (cfg)->c.str = crypt_nid_to_str((cfg)->c.nid); \ + assert((cfg)->c.nid != NID_undef || (cfg)->c.str == NULL); \ +} while (0) + +#define SET_KEX_DIGEST(cfg, digest_str) do { \ + (cfg)->d.nid = md_str_to_nid(digest_str); \ + (cfg)->d.str = md_nid_to_str((cfg)->d.nid); \ + assert((cfg)->d.nid != NID_undef || (cfg)->d.str == NULL); \ +} while (0) + +#define SET_KEX_DIGEST_NID(cfg, nid_val) do { \ + (cfg)->d.nid = (nid_val); \ + (cfg)->d.str = md_nid_to_str((cfg)->d.nid); \ + assert((cfg)->d.nid != NID_undef || (cfg)->d.str == NULL); \ +} while (0) + +#define CLEAR_KEX_ALGO(cfg) do { \ + (cfg)->x.nid = NID_undef; \ + (cfg)->x.str = NULL; \ +} while (0) + +#define CLEAR_KEX_KDF(cfg) do { \ + (cfg)->k.nid = NID_undef; \ + (cfg)->k.str = NULL; \ +} while (0) + +#define CLEAR_KEX_CIPHER(cfg) do { \ + (cfg)->c.nid = NID_undef; \ + (cfg)->c.str = NULL; \ +} while (0) + +#define CLEAR_KEX_DIGEST(cfg) do { \ + (cfg)->d.nid = NID_undef; \ + (cfg)->d.str = NULL; \ +} while (0) struct auth_ctx; struct crypt_ctx; -struct crypt_ctx * crypt_create_ctx(const uint8_t * key); +struct auth_ctx * auth_create_ctx(void); + +void auth_destroy_ctx(struct auth_ctx * ctx); + +int auth_add_crt_to_store(struct auth_ctx * ctx, + void * crt); + +int auth_verify_crt(struct auth_ctx * ctx, + void * crt); + +int auth_sign(void * pkp, + int md_nid, + buffer_t msg, + buffer_t * sig); + +int auth_verify_sig(void * pk, + int md_nid, + buffer_t msg, + buffer_t sig); + +int load_sec_config_file(struct sec_config * cfg, + const char * path); + +int kex_pkp_create(struct sec_config * cfg, + void ** pkp, + uint8_t * pk); + +void kex_pkp_destroy(void * pkp); + +int kex_dhe_derive(struct sec_config * cfg, + void * pkp, + buffer_t pk, + uint8_t * s); + +ssize_t kex_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); + +ssize_t kex_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); + +int kex_kem_decap(void * pkp, + buffer_t ct, + int kdf_nid, + uint8_t * s); + +int kex_get_algo_from_pk_der(buffer_t pk, + char * algo); + +int kex_get_algo_from_pk_raw(buffer_t pk, + char * algo); + +int kex_validate_algo(const char * algo); + +int kex_validate_nid(int nid); + +const char * kex_nid_to_str(uint16_t nid); + +uint16_t kex_str_to_nid(const char * algo); + +struct crypt_ctx * crypt_create_ctx(struct crypt_sk * sk); void crypt_destroy_ctx(struct crypt_ctx * ctx); -int crypt_dh_pkp_create(void ** pkp, - uint8_t * pk); +int crypt_validate_nid(int nid); + +const char * crypt_nid_to_str(uint16_t nid); + +uint16_t crypt_str_to_nid(const char * cipher); + +int md_validate_nid(int nid); + +const char * md_nid_to_str(uint16_t nid); -void crypt_dh_pkp_destroy(void * pkp); +uint16_t md_str_to_nid(const char * kdf); -int crypt_dh_derive(void * pkp, - buffer_t pk, - uint8_t * s); +ssize_t md_digest(int md_nid, + buffer_t in, + uint8_t * out); + +ssize_t md_len(int md_nid); int crypt_encrypt(struct crypt_ctx * ctx, buffer_t in, @@ -77,6 +307,18 @@ int crypt_load_privkey_str(const char * str, int crypt_load_pubkey_str(const char * str, void ** key); +int crypt_load_pubkey_file(const char * path, + void ** key); + +int crypt_load_pubkey_file_to_der(const char * path, + buffer_t * buf); + +int crypt_load_pubkey_raw_file(const char * path, + buffer_t * buf); + +int crypt_load_privkey_raw_file(const char * path, + void ** key); + int crypt_cmp_key(const void * key1, const void * key2); @@ -91,24 +333,17 @@ int crypt_crt_der(const void * crt, int crypt_check_crt_name(void * crt, const char * name); -struct auth_ctx * auth_create_ctx(void); - -void auth_destroy_ctx(struct auth_ctx * ctx); +int crypt_get_crt_name(void * crt, + char * name); -int auth_add_crt_to_store(struct auth_ctx * ctx, - void * crt); +/* Secure memory allocation for sensitive data (keys, secrets) */ +int crypt_secure_malloc_init(size_t max); -void auth_destroy_ctx(struct auth_ctx * ctx); - -int auth_verify_crt(struct auth_ctx * ctx, - void * crt); +void crypt_secure_malloc_fini(void); -int auth_sign(void * pkp, - buffer_t msg, - buffer_t * sig); +void * crypt_secure_malloc(size_t size); -int auth_verify_sig(void * pk, - buffer_t msg, - buffer_t sig); +void crypt_secure_free(void * ptr, + size_t size); #endif /* OUROBOROS_LIB_CRYPT_H */ diff --git a/include/ouroboros/flow.h b/include/ouroboros/flow.h index 77b7737e..f9aa0d83 100644 --- a/include/ouroboros/flow.h +++ b/include/ouroboros/flow.h @@ -27,7 +27,9 @@ #include - enum flow_state { /* DO NOT CHANGE ORDER! */ +#define SYMMKEYSZ 32 + +enum flow_state { /* DO NOT CHANGE ORDER! */ FLOW_INIT = 0, FLOW_ALLOC_PENDING, FLOW_ACCEPT_PENDING, diff --git a/include/ouroboros/name.h b/include/ouroboros/name.h index 14fdd504..892f2a1f 100644 --- a/include/ouroboros/name.h +++ b/include/ouroboros/name.h @@ -35,8 +35,8 @@ enum pol_balance { struct name_sec_paths { char enc[NAME_PATH_SIZE + 1]; /* path to crypt for this name */ - char key[NAME_PATH_SIZE + 1]; /* path to key for this name */ - char crt[NAME_PATH_SIZE + 1]; /* path to crt for this name */ + char key[NAME_PATH_SIZE + 1]; /* path to key for this name */ + char crt[NAME_PATH_SIZE + 1]; /* path to crt for this name */ }; struct name_info { diff --git a/include/ouroboros/serdes-irm.h b/include/ouroboros/serdes-irm.h index 1d041541..246db23d 100644 --- a/include/ouroboros/serdes-irm.h +++ b/include/ouroboros/serdes-irm.h @@ -23,6 +23,7 @@ #ifndef OUROBOROS_LIB_SERDES_IRM_H #define OUROBOROS_LIB_SERDES_IRM_H +#include #include #include #include @@ -54,10 +55,9 @@ int ipcp_flow_alloc_reply__irm_msg_ser(buffer_t * buf, int response, const buffer_t * data); -/* response to alloc / join / accept / flow_req_arr */ int flow__irm_result_des(buffer_t * buf, struct flow_info * flow, - buffer_t * sk); + struct crypt_sk * sk); int flow_dealloc__irm_req_ser(buffer_t * buf, const struct flow_info * flow, diff --git a/include/ouroboros/test.h b/include/ouroboros/test.h deleted file mode 100644 index bccf9ccd..00000000 --- a/include/ouroboros/test.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2024 - * - * Test macros - * - * Dimitri Staessens - * Sander Vrijders - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#ifndef OUROBOROS_LIB_TEST_H -#define OUROBOROS_LIB_TEST_H - -#include -#include -#include -#include -#include -#include - -#define TEST_RC_SUCCESS 0 -#define TEST_RC_SKIP 1 -#define TEST_RC_FAIL -1 - -#define TEST_START() \ - do { \ - printf("%s started.\n", __func__); \ - fflush(stdout); \ - } while (0) -#define TEST_SUCCESS() \ - do { \ - printf("\x1b[32m%s succeeded.\x1b[0m\n", __func__); \ - fflush(stdout); \ - } while (0) - -#define TEST_SKIPPED() \ - do { \ - printf("\x1b[33m%s skipped.\x1b[0m\n", __func__); \ - fflush(stdout); \ - } while (0) - -#define TEST_FAIL() \ - do { \ - printf("\x1b[31m%s failed.\x1b[0m\n", __func__); \ - fflush(stdout); \ - } while (0) - -#define TEST_END(result) \ - do { if (result == 0) TEST_SUCCESS(); else TEST_FAIL(); } while (0) - -static int __attribute__((unused)) test_assert_fail(int(* testfunc)(void)) -{ - pid_t pid; - int wstatus; - - pid = fork(); - if (pid == -1) { - printf("Failed to fork: %s.\n", strerror(errno)); - return TEST_RC_FAIL; - } - - if (pid == 0) - return testfunc(); /* should abort */ - - waitpid(pid, &wstatus, 0); -#ifdef CONFIG_OUROBOROS_DEBUG - if (WIFSIGNALED(wstatus) && (wstatus == 134 || wstatus == 6)) - return TEST_RC_SUCCESS; - - printf("Process did not abort, status: %d.\n", wstatus); -#else - if (WIFEXITED(wstatus) && wstatus == 0) - return TEST_RC_SUCCESS; - - printf("Process did not exit, status: %d.\n", wstatus); -#endif - - return TEST_RC_FAIL; -} - -#endif /* OUROBOROS_LIB_TEST_H */ diff --git a/include/ouroboros/utils.h b/include/ouroboros/utils.h index b93b345d..5d082d5c 100644 --- a/include/ouroboros/utils.h +++ b/include/ouroboros/utils.h @@ -31,8 +31,8 @@ #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #define MAX(a,b) (((a) > (b)) ? (a) : (b)) #define ABS(a) ((a) > 0 ? (a) : -(a)) -#define clrbuf(buf) do { memset(&(buf), 0, sizeof(buf)); } while (0); -#define freebuf(buf) do { free((buf).data); clrbuf(buf); } while (0); +#define clrbuf(buf) do { memset(&(buf), 0, sizeof(buf)); } while (0) +#define freebuf(buf) do { free((buf).data); clrbuf(buf); } while (0) #define BUF_INIT { 0, NULL } #define BUF_IS_EMPTY(buf) ((buf)->data == NULL && (buf)->len == 0) @@ -50,9 +50,10 @@ int bufcmp(const buffer_t * a, */ int n_digits(unsigned i); -/* gets the application name */ char * path_strip(const char * src); +char * trim_whitespace(char * str); + /* functions for copying and destroying arguments list */ size_t argvlen(const char ** argv); diff --git a/include/test/certs.h b/include/test/certs.h new file mode 100644 index 00000000..1b847406 --- /dev/null +++ b/include/test/certs.h @@ -0,0 +1,125 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test certificates - ECDSA/P-256 signed certificates + * + * Dimitri Staessens + * Sander Vrijders + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef TEST_CERTS_H +#define TEST_CERTS_H + +/* +* Certificates created following the guide +* Building an openssl certificate authority +* on +* https://community.f5.com/kb/technicalarticles/ +*/ + +/* Root certificate for CA ca.unittest.o7s */ +static const char * root_ca_crt_ec = \ +"-----BEGIN CERTIFICATE-----\n" +"MIICXTCCAgOgAwIBAgIURlENlCOy1OsA/AXFscPUQ2li8OYwCgYIKoZIzj0EAwIw\n" +"fDELMAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAK\n" +"BgNVBAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51\n" +"bml0dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASAwHhcNMjUwODAzMTg1MzE1WhcN\n" +"NDUwNzI5MTg1MzE1WjB8MQswCQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQ4wDAYD\n" +"VQQHDAVHaGVudDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n" +"GDAWBgNVBAMMD2NhLnVuaXR0ZXN0Lm83czEQMA4GCSqGSIb3DQEJARYBIDBZMBMG\n" +"ByqGSM49AgEGCCqGSM49AwEHA0IABEPMseCScbd/d5TlHmyYVszn/YGVeNdUCnFR\n" +"naOr95WlTNo3MyKKBuoiEFwHhjPASgXr/VDVjJLSyM3JUPebAcGjYzBhMB0GA1Ud\n" +"DgQWBBQkxjMILHH6lZ+rnCMnD/63GO3y1zAfBgNVHSMEGDAWgBQkxjMILHH6lZ+r\n" +"nCMnD/63GO3y1zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAKBggq\n" +"hkjOPQQDAgNIADBFAiEA1jVJWW4idkCgAYv0m2LT9C33Dq42aLyRkJ+9YdzDqLwC\n" +"IHT6MS4I0k52YP/hxoqWVBbpOW79PKYMRLyXTk1r7+Fa\n" +"-----END CERTIFICATE-----\n"; + +/* Certificate for intermediary im.unittest.o7s used for signing */ +static const char * im_ca_crt_ec = \ +"-----BEGIN CERTIFICATE-----\n" +"MIICbTCCAhOgAwIBAgICEAMwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCQkUxDDAK\n" +"BgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNVBAoMA283czEVMBMGA1UE\n" +"CwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0dGVzdC5vN3MxEDAOBgkq\n" +"hkiG9w0BCQEWASAwHhcNMjUwODAzMTkwMjU3WhcNNDUwNzI5MTkwMjU3WjBaMQsw\n" +"CQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQwwCgYDVQQKDANvN3MxFTATBgNVBAsM\n" +"DHVuaXR0ZXN0Lm83czEYMBYGA1UEAwwPaW0udW5pdHRlc3QubzdzMFkwEwYHKoZI\n" +"zj0CAQYIKoZIzj0DAQcDQgAEdlra08XItIPtVl5veaq4UF6LIcBXj2mZFqKNEXFh\n" +"l9uAz6UAbIc+FUPNfom6dwKbg/AjQ82a100eh6K/jCY7eKOBpjCBozAdBgNVHQ4E\n" +"FgQUy8Go8BIO6i0lJ+mgBr9lvh2L0eswHwYDVR0jBBgwFoAUJMYzCCxx+pWfq5wj\n" +"Jw/+txjt8tcwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEQYD\n" +"VR0fBAowCDAGoASgAoYAMCoGCCsGAQUFBwEBBB4wHDAMBggrBgEFBQcwAoYAMAwG\n" +"CCsGAQUFBzABhgAwCgYIKoZIzj0EAwIDSAAwRQIhAN3ZYhqu6mVLGidmONsbANk5\n" +"rzT6aHJcmvj19OxMusaXAiBKy0gBFCri/GLizi4wZo09wf31yZMqfr8IrApvPaLw\n" +"qA==\n" +"-----END CERTIFICATE-----\n"; + +/* Server test-1.unittest.o7s private-public key pair */ +static const char * server_pkp_ec = \ +"-----BEGIN EC PRIVATE KEY-----\n" +"MHcCAQEEIA4/bcmquVvGrY4+TtfnFSy1SpXs896r5xJjGuD6NmGRoAoGCCqGSM49\n" +"AwEHoUQDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2M0YzRKFKeV48tG5eD+MBaTrT\n" +"eoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n" +"-----END EC PRIVATE KEY-----\n"; + +/* Public key for the Private key */ +static __attribute__((unused)) const char * server_pk_ec = \ +"-----BEGIN PUBLIC KEY-----\n" +"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2\n" +"M0YzRKFKeV48tG5eD+MBaTrTeoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n" +"-----END PUBLIC KEY-----\n"; + +/* Valid signed server certificate for test-1.unittest.o7s */ +#define SSC_TEXT_SIZE 2295 /* size of cleartext certificate */ +static const char * signed_server_crt_ec = \ +"-----BEGIN CERTIFICATE-----\n" +"MIIDiTCCAy+gAwIBAgICEAUwCgYIKoZIzj0EAwIwWjELMAkGA1UEBhMCQkUxDDAK\n" +"BgNVBAgMA09WTDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n" +"GDAWBgNVBAMMD2ltLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDgxODQ4NTNaFw00NTA4\n" +"MDMxODQ4NTNaMG4xCzAJBgNVBAYTAkJFMQwwCgYDVQQIDANPVkwxDjAMBgNVBAcM\n" +"BUdoZW50MQwwCgYDVQQKDANvN3MxFTATBgNVBAsMDHVuaXR0ZXN0Lm83czEcMBoG\n" +"A1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czBZMBMGByqGSM49AgEGCCqGSM49AwEH\n" +"A0IABOAUjob9+quGwjC0pCWgr88Gd6T8tjNGM0ShSnlePLRuXg/jAWk603qB1HEX\n" +"6c9BDv4p6txVQ8xKAENjVqZ89JCjggHPMIIByzAJBgNVHRMEAjAAMBEGCWCGSAGG\n" +"+EIBAQQEAwIGQDA4BglghkgBhvhCAQ0EKxYpbzdzIHVuaXR0ZXN0IEdlbmVyYXRl\n" +"ZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFI+htsK0xxy6e1CqCyxn7mqi\n" +"wRrpMIGoBgNVHSMEgaAwgZ2AFMvBqPASDuotJSfpoAa/Zb4di9HroYGApH4wfDEL\n" +"MAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNV\n" +"BAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0\n" +"dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASCCAhADMA4GA1UdDwEB/wQEAwIFoDAT\n" +"BgNVHSUEDDAKBggrBgEFBQcDATAoBgNVHR8EITAfMB2gG6AZhhdodHRwczovL291\n" +"cm9ib3Jvcy5yb2NrczBYBggrBgEFBQcBAQRMMEowIwYIKwYBBQUHMAKGF2h0dHBz\n" +"Oi8vb3Vyb2Jvcm9zLnJvY2tzMCMGCCsGAQUFBzABhhdodHRwczovL291cm9ib3Jv\n" +"cy5yb2NrczAKBggqhkjOPQQDAgNIADBFAiBZuw/Yb2pq925H7pEiOXr4fMo0wknz\n" +"ktkxoHAFbjQEPQIhAMInHI7lvRmS0IMw1wBF/WlUZWKvhyU/TeMIZfk/JGCS\n" +"-----END CERTIFICATE-----\n"; + +/* Self-signed by server test-1.unittest.o7s using its key */ +static __attribute__((unused)) const char * server_crt_ec = \ +"-----BEGIN CERTIFICATE-----\n" +"MIIBfjCCASWgAwIBAgIUB5VYxp7i+sgYjvLiwfpf0W5NfqQwCgYIKoZIzj0EAwIw\n" +"HjEcMBoGA1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDMxOTI4MzVa\n" +"Fw00NTA3MjkxOTI4MzVaMB4xHDAaBgNVBAMME3Rlc3QtMS51bml0dGVzdC5vN3Mw\n" +"WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATgFI6G/fqrhsIwtKQloK/PBnek/LYz\n" +"RjNEoUp5Xjy0bl4P4wFpOtN6gdRxF+nPQQ7+KercVUPMSgBDY1amfPSQo0EwPzAe\n" +"BgNVHREEFzAVghN0ZXN0LTEudW5pdHRlc3QubzdzMB0GA1UdDgQWBBSPobbCtMcc\n" +"untQqgssZ+5qosEa6TAKBggqhkjOPQQDAgNHADBEAiAoFC/rqgrRXmMUx4y5cPbv\n" +"jOKpoL3FpehRgGkPatmL/QIgMRHc2TSGo6q1SG22Xt1dHAIBsaN2AlSfhjKULMH5\n" +"gRo=\n" +"-----END CERTIFICATE-----\n"; + +#endif /* TEST_CERTS_H */ + diff --git a/include/test/certs_pqc.h b/include/test/certs_pqc.h new file mode 100644 index 00000000..b533ca60 --- /dev/null +++ b/include/test/certs_pqc.h @@ -0,0 +1,656 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test certificates - ML-DSA-65 (post-quantum) signed certificates + * + * Dimitri Staessens + * Sander Vrijders + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef TEST_CERTS_PQC_H +#define TEST_CERTS_PQC_H + +/* + * ML-DSA-65 certificates for testing post-quantum cryptography + * Root CA: ca.unittest.o7s + * Intermediate CA: im.unittest.o7s (pathlen:0) + * Server: test-1.unittest.o7s + */ + +/* PEM certificate strings will go here */ +static const char * root_ca_crt_ml = \ +"-----BEGIN CERTIFICATE-----\n" +"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\n" +"-----END CERTIFICATE-----\n"; + +static const char * im_ca_crt_ml = \ +"-----BEGIN CERTIFICATE-----\n" +"MIIVyDCCCMWgAwIBAgICEAAwCwYJYIZIAWUDBAMSMFMxCzAJBgNVBAYTAkJFMQww\n" +"CgYDVQQIDANPVkwxDjAMBgNVBAcMBUdoZW50MQwwCgYDVQQKDANvN3MxGDAWBgNV\n" +"BAMMD2NhLnVuaXR0ZXN0Lm83czAeFw0yNjAxMDUxOTE1MjJaFw0zNjAxMDMxOTE1\n" +"MjJaMBoxGDAWBgNVBAMMD2ltLnVuaXR0ZXN0Lm83czCCB7IwCwYJYIZIAWUDBAMS\n" +"A4IHoQA5Wkc2Mz19rGlaczLiVz+WqRHexHpdzgKP/wXNiduzFZtYxbXVPJHPybkX\n" +"Jx8p18vxsTHBsUz1ST+nOqLKH0RAqqEQWBLmf2bg0To7aLkN53lg40xcZ8XszXBg\n" +"H0cjl/agLFSvPoSpLv5R25FeR5mK5pkpaCzY29Wg2xppeAFeMPL1YBUtsxiDoLWG\n" +"xnquhrDRWrFCHDcpcG+qFCrwSk30EJgIEFz8zAucZk++MWXINmm0rKzqAZHkh9zo\n" +"IXHTulI6zFGxFDP19eRoke4AzmC6xJyw9W+WIU84TyU9o4bSfQtJjLFn7qk410a2\n" +"/zm3mXS9fQiqqZm8M6mAk6UTFVHHr6/SzzwFutn652qz8xhSDkOlsd8jz9XixEpk\n" +"wXQiZZMaai9MTW9m5Hzi+8mFMcMmGwsIgTWo/G0os8d8hxPWcRXO4lXfuHa83dUP\n" +"KWHEcroEBjdN67R3qepCONhvq8VSHbw1/tkm8VjzTz2VCQUjFSkn/rxqJAi1cIoh\n" +"ZEBuDuJ2Ilx3DlFAQTrMl6L6HwIqetYYyYoMUSL09MtAA1EZTIJm6CHM+gxnef20\n" +"mLIvWDaXicm01FpBaxJwGqWD0fS+2Ii4+XwnxYh3kKLD1x1O4guIqPDPG3NVBdu5\n" +"pKxYLAgcE9OJc3I7SfgimQCweGuEUGwHSqMjcYKYWiAodlo2z4+GtAN08Np+Av0/\n" +"fVTOHW6aFMnvxfcpsZv0fBcwgOm+vvuxmKdIOTjJiQnLPqWWAykt3LM5AaoBT0v5\n" +"Pd3OU7z4p/XXwkYgQXFV58nOttFHHE0ANKiLUF7iBmu2Vz3+n8ewTW/nYABUEZlC\n" +"W+VfnhHwYhgmtg2krCBW9KNI1Vvfw9WutJxT/8+lAN3P/ZqyvF6vS9rECfn1zFIr\n" +"mbkYlopTV6H/pD2GQmYsrEJOK8nW4SpXU0XDa+545TWDbuj04TD80B8Ao193gndY\n" +"F97SzXgxLG4koxQzu7l2i8Fa3yr7sbjnh3xvW3abL4D7x1W2I81xVPyTfopRRUq7\n" +"/mJE621LWpbSlLAxsnlWpt8JGw4Q3vnkxOAox5T1Mh8LyZYi7f8qx7uCMxZyBqeP\n" +"EYu2/bHJCXv0EOZnKbr2gVy+5rnSS1hxOvRhiT3lE32/ab4Z/iNkG8oHnqkWPrdC\n" +"HyyRtzMuCXK/4T9P/gDi7DyXLl2uklBm6YE+zrn5dENmfjUOXOAkGZ2KQ2fmbob1\n" +"QJT14ry9lbZTkCxdC9MfOJcX8gvA3/Jm2sfOjpBJpj/r/QSmpAWV8AEE/qHfJhm0\n" +"e1qMxcCecbTyE/JoP42cBgVo8DKy4AUJsbBjtBY3/Aa6bRrkg4M3SFlPfUUzaoLK\n" +"AfnkVk85uqP1SV7dioChdPAz/8XvDPWUjLKxO4TXFZz2Q4SwL0OPwld8mnKaVNQI\n" +"9oO/WIXgXS3sKLRdnfwoI6HsslhyyM/tHwjR4kIt6UomMy2GemXwFk8VGU+dcezy\n" +"QnNHVbtw9xyjWV93JMYx+rSIAL0TVwDO/rIVLeIbp+nPDyh19TSwWG94JSTv0LZl\n" +"HEJp36/QZ6gtRu5ezHzqJnTEC79ovO34UIC1v8zGpL5Z05+4eRqSRZ7iE/PLJ7Jb\n" +"9+KNRvDJnwdOecjzkjKu+CJ04x8UTs0VixqXuU3CFQJ8PfT3Ed9lt5HyiT9Wlv5G\n" +"MQJBsZBanmafEhclzN3eeBptNcQKWyxJ2bJMDxjDGncIeQ+NbYA5M7f+RXv/PJ8/\n" +"ArZpf/OHyGQFKA81m/TiQZHaQBWHkDdjw72jMjim058VpC2OBqff6aOwXT4RKxo6\n" +"MQ7HSbbtm88YWaBrqj/U0ji90Vxzdp5X/a1KRRadsedvm05wvxpJ3AxwBi4KBy7I\n" +"BF00dBsXQqklG9AItqE1y0BsGakXOWD8lM7CpSMKeIfFBSXwWOHJSleM0EqGSjUD\n" +"DHU0F5mWxVzFfxg3C/5irDFpT61d02M3EEBcJLjWkHNHpziLgH7b8OrH+utce8Ve\n" +"5Oa4MMoDz/H7k118IP3Y1WMvymniD6f6Lv4Vja05/f8K8l0OvRIJgNGeW2lgcLfo\n" +"4EsKcYLC3h5Uaf2hVnEW4ADj/rFDBKRlCyLpl1loimDYpn7RyPUVoz3oTzkwxKbP\n" +"sITXRL5X0j5yLZVlJA+ibF+5+2slZSzBJqyM5EkbWTqE/rCVIWg9rnoofAFAtPlB\n" +"qk2PVCpQYipEKnRukG/DjxIStfKitMy5y6baKiVRIdebX+vRMVs9csPDIBMg20KA\n" +"wOuTvU4sxoPx2/2QGb9jXSoLLtjTyJEwOiGAB2O33XYkU87SPY0nLfIjyzcef3ni\n" +"lxu49W5iGCfAcDl4ZLgU8rQgQxk0wWNa8kCSjMkvm9UuXerurZgWHaoG25lE43r/\n" +"GxpqiUKfqW78BWOHd2MqFU4UTkzZmQdvvQ8/hSVQr8UGAo7j7KLpnSKkKVSIylwR\n" +"qvfVnxp4vrrMNh1EHJnWvHklxnYN+Wm72b/93tB3IyijQJuhrNQXKARbimy3o3SF\n" +"yHyOVQ5EwlQbE0w5Au6x/Q4jelSVutv4I9PRH3ahC+1Auh3VklsiFVuCfHUESUoc\n" +"gL7HwPEJpMRrSMt6siLSarrQyVbJl7Ci/9GJjQ+ujb44a+bXuqNmMGQwHQYDVR0O\n" +"BBYEFN227zIe99jSqqfjEh88cm1lhnstMB8GA1UdIwQYMBaAFMqHQbYKyHbMbnoX\n" +"U2Q8yK1KpzI9MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMAsG\n" +"CWCGSAFlAwQDEgOCDO4Azm59ev1Sf7AxeEOX/uW5iWvxURQZ7mFrOjnEa3DTdOke\n" +"47qObh0Guh5x4r1QFGPmNNG68XOAe9JqDQIJjgoeuXFKAYqcfIAJwQOiX+rQjuUE\n" +"Wm1dyejrj3N152c17Yxl/DLjxwA9cJ1XxOU57jNaqqjoUSgr3k5D2aH356cpIi+S\n" +"xXWKwZcSTD97T3/bJOyCKQWxSg0qmPYwAVIWX2jMLcPaIkDTtBFijuvgfjnCJ9yC\n" +"sBmSHEhOZmymiXocMvycYZtJTsNeTmOuxLhhcNIFzDr5+PZ4SgViSFkpmxKsPnYK\n" +"TLo+2RTYAys/G4e/BEgwH++XC9WXidSgdHbguD+86mh39jKcnV9YaZqMhGtPQNlx\n" +"Dv0vjrW+Ff1+HrwehpULU7Tq+DSRpIPtz3QpoKgQaFYZKiX6BG8eK5p9UQe8AZAK\n" +"X0v/zTWznAqevmm5t97QyrcHY8aZuu0ix5NWK+0SefqySaR6hew6E9E2EUSYFQJt\n" +"NcPQMXDr7EvGcKnNOZED5EEdxctC2VLC6jIxoIKkzgwG6iOh/mTrgvYfdzwdb/8d\n" +"K1x8NoPblRKKztDA5kcVisFDIvESe14V2FuIZPUv8MBITt5pDCR3oyyZEDP3odYc\n" +"f42ONFHx2rCCCfOV59Bgo3EadvGK+dzONf+j1Gv4x0lSJeTo8XcXNiSl1wa5ppvh\n" +"mVJCnWdxkCwEiCGIzMHogt9L36cQcR6LpnevrLEPx83yQU06isDjiumpEOnqmOrX\n" +"Gu6nSr60hPJdyurmphX+WXNONutzRJoRhwdEjZporYeg7HxQpIqO5QWeOuL4fbO5\n" +"hFCc+Myd1RBJoq8o74Yia2fkYXxQj36AVSvPXwH62hmjQwUxaCNAeYLht/eOnv4H\n" +"kBbP1RQKm34qRxkpG+PnXXganJsq4tyq8QVcV7htzmi1NbwMOjPR+ue/cjRfxm6s\n" +"/IffD+vGF3Uxq02t7n4ORtehg3ICq5nmsd8nQCZ6WOdvTUgrX4Lfm9WPOXOv55s9\n" +"O385JPT5qxWc8KNNUIzNam8VbRNZXCkSHddwDip1Nskbvbl6dSNp9X8ctEjwFI+k\n" +"CORFh0/DdwFAV+QQf9gfOJ/qq2ZnOhC8U5t+M5SR7+s2bB4pZQg3dAYk3rctTRwi\n" +"HKvnh6JbvELsXq78nImeJsCjHhhdnxLkEWAvT60fKnaxMpFYHVKgYY/PLGrvJ6On\n" +"eJWYdvjSawenSSkciNyP2TGcfQObBujB/3pcZ4Si2GlXf78LCHhz2E4cJ9XdNnxi\n" +"55wCwAHLmdxWjuxVRX58F7pT2dpGl2cVEiORv1z08zYe1X3rRa+mB+wtKui6FTOX\n" +"fRaUBT4ak8zOEg3L1RjNaXF67ieJDWCCCaPFeGBmEo8y4fTHHxoFncINA1LHLEwo\n" +"BnGCtoTV83C09my/x0PAdgFzISyz7yMJKtDbCV64Ru6KFsSIBc4MsEHhnl3C+npd\n" +"OaSOLmaEUUf/wtvTO2vzIs/DMW1JRy2x+VV9SwN7Nadjy7yKqbqF2w2GbG2uRhEh\n" +"pbDoekg/baH9sjfICetgVsDG4rpcrvJPHnWY1CvlJZ7T7KI6U6xSuhP663VJNe3a\n" +"YOul2eqKxas2UGQVIj0o5qEXaTIIQJh79giZuCOLTjCi6cLhEgMf0arazFrt+Iez\n" +"AcO3eWwIojjjan0WQ6dUK8ZV139XfQrGPrFOp7BWSDqdrr6jJyzPKMAJkQVBP0Fr\n" +"FFHLpvr1cJ+Usgew4ciosNqjsb3uVfhti4eKxAM6jo5M4o++on8FB0yp8sNAoG3u\n" +"HoWgxKAulZA/vcNLzEPKVo6fihxRQxmT8CjxAZe17actqC4CS+NBS6eBrKhJtJ/M\n" +"rHqEDOVhni8YTP90gxKn/h9qdS+eh6Haz4X+1Z3KeKgwqMVZmpfuRgWe1nR8Ue2d\n" +"ZD3XODV7K/3YLHEaJ/Pj3pxyA1nMLzFQAqKKJMsXvLKXlpLVWoKsbIiQ3t4yh1uE\n" +"IC723nyoSPdGqwgPMTSgxfve7O0XW26KSaSfscqg1zYwBJbkgDKLlR/tTnWmzll5\n" +"VBgoNth1mZ8+NyI5DboJqQUEgyDws4t6LC0pUx73ddMxaNkqpmujF0iIkuNwZPHm\n" +"yWYMfDSXkJDUx3cXWiOpS4hpt98H5NGCweZ7y0eV0xolFn5E1o3U6qAl6kMZQYi2\n" +"JktmQkPMkDXjvwE8ztVdfeGH0WEhXGB0L0RZIuLt+mpnYHU6xnhFOBxleAwfIawS\n" +"LzVZ0uaNlXNwwCM0eVGh9v8c5vuxzJOf8x5vj6SfFvxTF3RGd97bThOAbh2lrqza\n" +"3KiqSTxokRBLLubjMB2sUkYEf2OZIs7jOIf3tMRPafyVW5hlEDHd8IWAYMznKBvT\n" +"xpTJTis5ns+2y8aVRAet3Qiq7jfAexlNr3dx/MAY3zADneQ4PG6949NrP0eYqGKn\n" +"h0hHmAX2wBHc8CUByRfsDzsAkU6+3F68mJ37ACnobHPO9kBxHICX+RPspzTgTcZn\n" +"XDJBHp5Th7hmzlxCYn07qjE66jAo39Ity3YeZeXOMe2qXn5b59tgrBHIqQR0lmfM\n" +"1NbO4PxECUDhn2yKXLvj7tGS2St/J2e8j91jOACvPoq1C6rmLGFxvobg0QM83Bwg\n" +"V88VW5XJoCeTskqdR4+2v2YsdHAcZChSkglbhiuVWqmUDjZA1xOn4osLn1dJdmk/\n" +"pf/4ftoSytDJaFwhfJhjB5mwpAM3ftJfHSSqrGGOydvAxtcLKcBWMYPlUThRWKAI\n" +"yYwyapSb57iMAKnguGq0AWu9lqW9KvQXhzVN9LpJATYSkNddRiWCUyUF3OJnz+UK\n" +"DfNuWQbFXrfVsJtA4nFIqt9FnT1yX9f1OF4FshhmBG6Eh+PqfaASFzeUxatNlQad\n" +"Ryq6TVNESSvtNGJSRsRewM7Hpm9zmNEDcbUwQAhMHG8OHsXGUpmVR5fmwwpfwznB\n" +"M7mdvDf34DDd1nJAxiVExb/ZJIyK1+BCXH+T834iW0Aljo1bQ14Tz8nSu/S/0Fvo\n" +"Uy92oDYi4plVbB+Z2SmAiqh4fQfW3z/5lLiltZWlEUNeyzoPsdMBYGRAoaqFgSW7\n" +"a+7Yjqy7NbTUHzO+slS8rJNQe7b8MeTJpKtll488lE5+8/ikPv5G6aK3Q9g+4Vxi\n" +"cPvosUyVMxA++3BVjHI//1y3EITTQOqgwk4DBnYTy7dfNvCqZr/6F8EZCIarjKsi\n" +"XFP7kShZpgKz9DGbhP31Cz1ghr/BrRgTQgECd80OowAODslVLCCqCnu4oizmCRl5\n" +"s1tAOJAaW/4XKvK+PTrSv2EBt5t5IRzrjkxtYcEvgFUkC/5WuGlmGd/xW12QqNyu\n" +"xPkjBcS8UQY0/2ooQ+/Wk4/t09x0HqwkgF773VhDsOiwnrAuRWYATCyQnxz+quEQ\n" +"ROilgS4dKV7Byh2NZI8EFUz9RcY41yXp1QSIxwwqgvExNvSi86FQXZT444yssvcd\n" +"eTdOBIQNhhCK2MRGoZBRBBOJCydyM8UO064d4/jRd5hnuM8Zedki5lQk3hnEmqzo\n" +"Crx2xDfsvTEXir/3n67p3R/I7JyjZUYeWD5s00RutDAOcuqZ0+WoAtZSthTRwot9\n" +"23nH6LOyfXQKfRiVV7g1W8rQllREtbBNV380CY/yz80WcPXVFT62VtjKB3Gsqk7v\n" +"DcSwT6CTl6wtwJhBc3sdD6Z/KKTvhLZZFWxwhlNweBqkbBewW+/s6dB5+zYpOURv\n" +"LMmbtW2OHSig5MPx0FHB2/EeQHAqVOP43iBZ59nrRBIIJ/71pIqZHtcyOGn1uM81\n" +"Rie/3em6AF77+BNxcp85fmEXDm/DY4qbUszpmr5ZNqA+6OrCyj/DdRg1CEOVD/x5\n" +"SYFWwJg70njXa6I/xJeoK8lW8WZNod//brn4OfvgweF4qetZA5tVeIH4oT3TyGiv\n" +"9+QAitHefGtkiJe0AMYM4Ws3iD7Vb8HhHKwLGxWkln60cXiB9rPbSZJav9j3kwIf\n" +"45jCldkR3kgfx7TmG77ycXE1Eim0PaBBjANaYjkvpMlrl7P1RehcUl5jMy4mrGL3\n" +"Xx8rNJizus4SJw3JRcR32BkjYn/KhdbJaZxFIor62vevdcRS//uiVP1HZjgySiqT\n" +"2Sq/EydG6AhtmfVoUvLfDgSh3cvNWobpqbg3AE0VDJCKifahvyrYtvxJ1Y28dat4\n" +"Ame8w8rZh5kpI7rl/95nXj0v+hhk/YY9uC3yH6thPkAyOu90kKAx2O7oRNrJmB3q\n" +"FlLPE4oaNuw0q3hyYVRimC0G9R3nK7HUu/EYpvg/OGbH9C43pCYrcEBcW2fSnsjL\n" +"wqMknqM50KNLNh6elXXpM0Wi2Ny23uF/Ng8jT8V7n/1tKJAxmBO06GJ4vnVon887\n" +"P0d3/VZdy87yLnDCzvn+GBw0TleLpVl2f7O3vMbR7/f9DCwuNLbjAAAAAAAAAAAA\n" +"AAAAAAAABQoQFyIo\n" +"-----END CERTIFICATE-----\n"; + +static const char * server_pkp_ml = \ +"-----BEGIN PRIVATE KEY-----\n" +"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\n" +"DngTH/g89SEnGxqKXe7RxVof\n" +"-----END PRIVATE KEY-----\n"; + +static __attribute__((unused)) const char * server_pk_ml = \ +"-----BEGIN PUBLIC KEY-----\n" +"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\n" +"k1M8ksQo\n" +"-----END PUBLIC KEY-----\n"; + +static const char * signed_server_crt_ml = \ +"-----BEGIN CERTIFICATE-----\n" +"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\n" +"AAAAAAAAAAAAAAAABgkOExgc\n" +"-----END CERTIFICATE-----\n"; + +static __attribute__((unused)) const char * server_crt_ml = \ +"-----BEGIN CERTIFICATE-----\n" +"MIIVtjCCCLOgAwIBAgIUS/okpA0M+TarUc6SknL4NJuAwKAwCwYJYIZIAWUDBAMS\n" +"MB4xHDAaBgNVBAMME3Rlc3QtMS51bml0dGVzdC5vN3MwHhcNMjYwMTA1MTkyMDMz\n" +"WhcNNDUxMjMxMTkyMDMzWjAeMRwwGgYDVQQDDBN0ZXN0LTEudW5pdHRlc3Qubzdz\n" +"MIIHsjALBglghkgBZQMEAxIDggehAOum4NJL/Zbq+yMC5YbG/cLeS61RZLMw0rZ3\n" +"mBkdbUluk3cm3nnBOZHlKiM36m040cgrYqE/h7VqzgkeuupRrOYjq2OiI+Qb9hnX\n" +"rDM+++8tHNfHxbFEU8qMokOrilVlqAXVaUW/R92x5w3I10vnJVRHkVmpMOh68s4L\n" +"+rQtSZM4AtnYToJd4DBI0tzV6Id6Synvp7fPPbkU8EpUHbuAegjoEmLLQdDWF4H+\n" +"CAU0yzFtPT5yTl7S/Kz/XC8dZ2ie56jTTj10K9c1pfjqMdkfz+PGgL6Sprq5/G84\n" +"upK4DZqUobS/dVcYraOBcWXw5Rpiyjbrsd1zCiV+WQo5oIHBNjQ8dwaMARM2gfOp\n" +"PbSpIScrabla0w6aklyRTLrjkY4EoByJK964PFBrqvh3FTU+rrYogSo85EeZYKgR\n" +"MF6bbtmNPoFoaBhEW19S6HYnXbZ4HTfGe6ceGF5i/lmBkbWWDOH2i+TLsRoGubi4\n" +"hc3Fj7DFMK0TlNjB2z0yOnwLC1MZKxTktvZ3DDAW696Ke6YIg+cvchJy3G61kpKD\n" +"0plsaK23todSIGpZDAiH9Ohxep2Jv/LJs9aE+EJ9fNYkrolnDSKCqXU/FVuvup0n\n" +"k9tiWq5gEYGWAUt1Qaqv7I8PaMm7hlhVwmmjZXxElLtwKvlg22Krl6sXHIBmTQIx\n" +"DS/fFJbrHPkKOFHBhvrfmCFr+h2ihVXvZbq9fvCAik2V3Qtvmot552N5jq48WgXq\n" +"mnHs0BPscilVieg4I4/VBiCzff1uexLknLD/5Xoak60uw37djDUJe5GR+/REHHNu\n" +"vTcGjnBmWFKKAaDpnaje1IP7rLcJAC4PRdbuZ4hbjrxUEDR40MMlT2W3BBr7avE5\n" +"8dgjmtPrVhPqOV4jfzp5ySOMDdeN4iJJQS5giwzI5p6PRi32J1IMsQKHH8nJHZys\n" +"BxW9TjNomt8K6jU/bP0iH4Xvjz3FdV9M44Ppar8QyTJIlFC9mRw2CjbU7B7Lx9oE\n" +"2j92zCN6D5/cpM6WjgVNcDihbcYtO9RvhuBlr1RZ7KAhCGrq0Gtggd4pbbJmHoJk\n" +"7OJVWpocATKfWemo+3e704cYYXiYzQXCmKeZofFFhxsPrxfdWoS03lXfK/+6RR9W\n" +"tpZgK75OyG40S102UDvjArICbITkMad/hkQOrDixkuAQdE9ZzD2L6MOEjnIF+WUK\n" +"pUq+JvHbZYKT16RwN33jQ6V7RHtE7qNgziDbluH5RsGmEdO2QFfbAFRanY3CeIlW\n" +"G/AyQIJtxm5qDkQVNF72eK9jq/NzkA5eEt3uRcJR1B+DwimVD15Jn/22gLF8ETEi\n" +"E3zp3CxoCeI7paC3V5v0wRsVXlX/hOBWS3JJyBHgT7UXHIA19UT9vnZg9G53XRie\n" +"T6a1pUav4JFFYBz+6prcoz648uxbcVlUQMiQencVBnQy+yFP67uMhR9hjz2oQOiP\n" +"2tITIT6B1HNbqmXTvokoCR4eFNtranA3ARAmNW58zICPts3PrvZzZRQ/Wgi14Ao8\n" +"lvPx8MRDWR0HGchjukbqvS7RBPMsIWnbR+qAOySfBR5T7K/0nIScQ/crP/79JCG+\n" +"NmzEuJPOhIZDySK7cWoSHedHIx/6I6tTBceMumZVQXmCganZC+xz7MlKnUFeutxE\n" +"4j8RWeBxCjSB+W4a1XXy+J0WMebVgDyc/b2eQ/MlNL0jnEjpD9T7YPfYGGZd6qbl\n" +"+o0OqoTzxbJDXGmsoko0l0VYXFxCsoFfNYLWBkTvYS2lX59YCpUrtiftxlaccEYG\n" +"6ZTf/lkmveMUClBvLyFzjD6sHw0Wis2j1ySNbq72I83nP7QrL/d6xReR82dxrymU\n" +"zbg8Xuc6b+ZwrXhbO0CmXnyNuiX1Kq+JIv96l08CqY57MMOon7MahkXUJq+8hEQ9\n" +"AVelRhR851+15uNrtftaTNipWGRlqDxlJaBWOgj/wtuLzSDLcKMsytaprVrf+Ez/\n" +"EBaaKRhtZsAumtE+HUc7PwS+slgDsCCg0HD75h7g1UDtlYgGEiLHW9qqCgNBJ9xx\n" +"1ORDKvzVMJWnerECKk1afTNt2NaGXDHE6fe/AokP0lhHHIw90bplQbZn+LFFYohv\n" +"GbEnBoPIdRPUse1drJr0Lc5b7Jp+n7lDS9uQQg0Sys8o6gfNd7Nme1MlDh9O7mpW\n" +"0veeNg4LI+GVny7Zk7dSH6GtYZjl79DzhnHQ+n6tTl3I7dCMOgKtyZN51/LVyRZ+\n" +"8/3WwgkUDG1DMIH6D7beW7+4FlC4OZR9a3uFclPX+4X1b6Jl9VuN1zaXeIyC5lBY\n" +"kXcfXRT0yF2PauGQL+Qzv0J+kM9HqBO0U+p0uqkzbblIPMf1Gkff6MeM4I1RAiB7\n" +"RqsoHekxIWkNyhLenxaKJRDCO4BOoa7QKO2FGYaUdQSKTUKO9PWFRJFmXk4YlYmi\n" +"pFCS69VCns4mElOXm4ep/+E2tAxlxNyx2LyUVJkwySbTnSOLJuNplUf4j4EwAxid\n" +"3gGoznQMipLhYyemI/iQQ+TtaGSSz9STTjky3ajrNUI92Rr1n3M66z7hx8Zhh3dp\n" +"0WfJ47y3KBtFjN4pVHDs+RAhcF5f58vy1iFWdHqB1clV1SU6g40D0YXAlcdPN2cV\n" +"k1M8ksQoo3MwcTAdBgNVHQ4EFgQUJPWgYA8LLq4MCf1piahVdRM2L1kwHwYDVR0j\n" +"BBgwFoAUJPWgYA8LLq4MCf1piahVdRM2L1kwDwYDVR0TAQH/BAUwAwEB/zAeBgNV\n" +"HREEFzAVghN0ZXN0LTEudW5pdHRlc3QubzdzMAsGCWCGSAFlAwQDEgOCDO4AqzJ/\n" +"0OXl6WB18z5y1YM/8A7SM8JozXzq7Jdngg2GmFNiWLGW3TR3FI7KCVw1AT88gpK7\n" +"Fu7FMaZZ9gIGGzO+aUH0NcKpfXTTCjDISabzXc4+lAc/9jCNO3uL5lYbWuSJOCBf\n" +"5eAbvdv4y2kAnnP1uw2YYKFeBJLo/pZAm1jvXniOEIwan+Z1KUDNU3qFBG7I/zQW\n" +"rqzDFkdqyI+bHgMgWsOAvRu0rtoM4qOlbvdLlpULT+Nj25jCf2p18W19SN8MoQLZ\n" +"bQH+Y/F6i6yXv9CpzAT9RWRSJyI8mdh3n5F6QlNYrJt7xzeDxXiTgICxNpxWtTx9\n" +"+D+YEMhxGs2R6UAk+hn1P2nZgZ/HPIBgqGg8rXyDfAecRNbDUMJxRLhPGRdvidFy\n" +"UqfjWoasltAbEricq5wPWObNrOPHWI43JWQ5Efk4CqZORhIiiash1K2kMyYeiY3E\n" +"hteoCOUeOKMSu06pFtR6f7khGJUfiXIe9HPAJL4ZwnnisGvI7cnmM7JCMXSC/H86\n" +"h1z7d+XMvFTNnMBsOpAm1nBV+ZoIHzyQ2n770I31CBq+ZmS/bSAqjAlC4CJ8wjNH\n" +"LD7sB2Rek22wB518Jy8MJSShAeF8vHk7EGijukOdre0tVLDDPOcGjZL1bgi91XVW\n" +"VQcO7EsLIf/kdyfAUZQGc/6LMwRCp+1BeDFp0wT74rYUpn3+MOXjWoTou4N97xnd\n" +"w862gnU/e70wsBGgPuDgVlB9gnAnkoQB0C1s3ecBo7HrWRIeYDHr6JTUMrdCAYJx\n" +"aw88edY1rWAdUGgk0UlT7lm+MBBJIyBtVY6mYWZkL7IU3DSk0Z9yrXciHC1H4sOw\n" +"h9ahd1seUT4G4GobMM19t7q6ibAlgvVWWlVDi7TG/LPoySlIwdkmGt2PQn0pjmPj\n" +"jXfg4He4bTKbu8SPuH9r22nPKz/0ffw/SgP7F/F6nb6KVAZnxoyo9SWLQVC8EVDP\n" +"H/mhQ6GLfZEDfr0dGtFxCJgYF1F7E6+qUbEKC7BtqMvUBCkiLUO/2Cwa+xFiz8Py\n" +"SKrumacBRG+PD128wZDBhStM+U52cCKeh7h1ENDejL9IK8YsbQn514h03EhlrwRw\n" +"3xHKFGQKk/iv5tgU9nZ+tPj5J7b3nM8jdGYyFw9Dl4h+pf27DXAVzkiFONCbbedZ\n" +"zCazjwX8fXOOgEvI82y/60/SlX+679/HoYGjyJVctyLfBlGEavx6SerTZZsNaWe3\n" +"jIVDf0OWvF6fzK4/VWEr/iULJquQlxnTOvhtxFVuvK0DoTFSzPCgF7U+inrJ5i+3\n" +"31JHuoBYtfEa6SS7dQVrRG74L7aqmbzPLkAz/k07hLGW+6WhLtPHCqQVAt4v0DHe\n" +"iWVkMEjXdqEQqYyu0ObSenZLPDkcvJNNaFH4A7wvJJXWXtm5UfBq9RR1IR+VErf/\n" +"Da9hEOJP1n0wcKyE6sfLwNTn/wAWCc4BF+XRQFhdUcmMNzoMd3YNlRcLWBlFxvVI\n" +"FFd4/nbuwCplrD/H6Loq3McgOoDY6L1nXjfgnFSmzN/83yzaUJdp6dVaXMsrDGiT\n" +"Zda57l4oUcTJgoNtU6DGyvQoZlF11gBZVi7M+3YAPnnHmy8w4W6dN1tyCaxOcaht\n" +"I8eapuA+Gl0mYYcf8FGLXmFt8s8GvC/vP6snlxN0wxzEwzwEUS9zi+KNKokvahRn\n" +"M8wYMXMuKBgOGVv3nzicXLNOAoLe56Y5Hnm7a9FGNrRenUQeRQ7yA0/iWg1v2hNi\n" +"wJX1DQw89awbiv+d920Te0VJFrV39jdNE8pQItWKoBlPMvBpXOks6rBGo6wl1+lE\n" +"RXqZVgdxC9/b7gCK/DrRQPoixTZWyX/Gyr+r+cnduMAECDeAcoyghK6/OfAo0JF+\n" +"5kjb0xcMYwbRNoPEWVRwmO6Y/FMVVj4yURow2Pkdm/Ng9J19QpgJa5zpih+xawVb\n" +"U3Jz3SMvIIfoi1eUdwSklH2XPIS0qxz/aEtqaNX6FGUwbK8j2fVbRmxiy7buoJ0i\n" +"ya/EfXW+enc1z1mjKHKESy66JZwhaEXpDpllw6U3rNDGXDBIB3AlA1Zm7FbiDB2h\n" +"eq1jhee8ldHC1X/wMu6TTaP3JNPkJ2/BNVqUvtjLLVAIzJ2GJZEiWA5Fni415gWX\n" +"9IGq9HqG29LM86RMq0ElP9U0YGUsQrFmrQnB7m0jP+bcQLLJNI5A193GdT9GyGiq\n" +"oyoUptuAZ7e9gqbc8m9LCwhamZaRDLdknxYD7F3tDmOoawApE7wk5PVgSK1tKNUB\n" +"QkdILE6sxkglv10JhkwI5nrQTDJqDNW2gmpQlFOpJm35iJpaJtyz2iqS/mmGfNiZ\n" +"vXuxdDIHOraA7NRVuJadB+UX1+/w7CBzyPm3bAPRMgvfOLoU5ARcy1S0VFwjP72o\n" +"Rx/ccStVtStLFV4yEPa2+GIukRjYrKf4TQbQcHXdRfb7vh1mk8+33GHd6cYLYYIb\n" +"6gPBEdyDOvFliD5AZsib8/0K92ldD2uZoJuj1SgBP4T3FDlFlmxdzTydM+fzmvh8\n" +"PRYwY8ZsGfMbYkDzcqPrP6YY5m49toA2/PFscxP3ThJEmBh9GfU/4N1oY2cRqhnX\n" +"9BbWTMz/aHyj2t3C/CkcavOIH0R80w6dN5bumylsI0iK2w60DHfOIWG44rakc0qr\n" +"TR2Zx+sMWwlfgYOMGEc+XE5wjqLJqywLbobfE3bMjLjBTKNjSCyOaooh3UFOJO5k\n" +"iY7MxCtCASSMeU0lsvtWfHH9c5bMEvFYnF6WgJ05kUvUmHRJFTlMb3uhyXae8mDh\n" +"qrSyp8vMYQmZEZzVaG1D/cDlnNuG+kCPtxoqtnEnFNGH9zNO2NTOV70tNl9tS/7G\n" +"/abEGKZnrk0BZ+6ViE2V8F7bm0JpUpwdNOpohjiVp06PIKVHamlwa9TcAGVNo+vK\n" +"YD0MK4HMaFget5OsvqRCOv3J3d+Ukih6/LLYyAmgW/+04G567SSqhB5HL6qrV99G\n" +"lmXGeCNtZ4Ypdu1xzsfQRiDSJWT2a5GZdNVGFgoiZyI0LNI8hKudemYcUk0R61eO\n" +"vBRiny8U59ZMx3JwHZuJ88Wu2MXtzQxuIAfRgh1gXVnZ3YSRxa0xviWkLq4Z/MH4\n" +"o0VhJ7LqdfJNP/PvFapddi53pMbANvuou0OoKhDn0otwCCzgU/Ye2DMhU/2o1oRT\n" +"x5gRZPxp80r8tO1iNW9789dxw+Q6icof1vQJ6zeJVdQDQND2jtP15IZGOrG71Ohz\n" +"54mOu+eHMif4FJvPodYPgqBPFsn8wjow81lhU+nBPYzVpWjaQ1ZpbGwymLkqXR36\n" +"5Xol+wA8Xr1znB7wmhJVNOvhMbn/lHekzXuoST8HGy+RPUhZ+DPIkihWmEL1LSDB\n" +"IwdYM9TO1fRMXzCXQRL4KSoYObVWy8gE/GIKevivvlM/FNr1fNh/xyPE6OqGOchx\n" +"w2NAP6uBxnwPwk2dA7uqFQi18klen/b1JA5hKdhEttK/uOmcb+Scy4wjyw39YCSW\n" +"Ybf2r8lvqW55SfYBIfk59B7vSCttQgqLqs8CR6ynTybUsleb2EtfU/iCOqkKUFVu\n" +"CzrWzZEC54QrzE074nKQ/VVidrMXbFe/bWLd84a1CF+Lc2jlh3r2Vqp6K4QJOQS/\n" +"1nXWu+DolDhWubBjTHlpN0LuyvIRSaFaBKrHckYGTUi24YHHuk5PrVb+LNm14H2z\n" +"Slv/m+BWsB94Gs4EXOO0yxDWc2dY8s0+v7j40f4pxavE24ehFceD7SxouyegFA/x\n" +"OPGGYWYVi1A73X0B4SbUJuazhZa3J03iGNxdPQ3tW6GPT9Z2ewJOkS91e5iABbw9\n" +"DB0kJoZMY05a9X0SlrWDF/2MPRr7XasHzAaOiO8DO2TFp6gpCeSLzzbjw6dBa5Yh\n" +"86oKLxfSZPIQnF5jS84DhAHvQ6K7P9v7bqk5SD2kIOronCwEAQYulWADvByijGQy\n" +"+cx4CULZ2eO/Bsf0+d1EgsfpTG/7s/hCY3U/+1Gr2iKXQfiNIFUo8l0V4YKBon+0\n" +"3Eits0tHG1+WS/7QvgoB5+43md6ANnyyhH64WAR3wMFiG3/ThLRfND3DvkuFwdkJ\n" +"kPiJw1k6RwuCp7Pg4iaWpvNymeMnEl28ewB9yIjO8Cnp/olo8FuTCjrJtMIouxTY\n" +"2kyjB35id4d/NjxHYl257Fxh4XFRbfyG+U5g7f/fTB17OzzoY39kIOdINMulQB8y\n" +"jn2Fd9Kw3rWMUWgwqiEsVU2EoO/YWY1EmKCU5WF3bHXwXgGZLEZP3zIfmy/h2bAt\n" +"+EIJO/yec956WkxuFzMAu2GcMxya2I2ZQsR1lyYqe9uXKOIMu18OEQ+0I90haNSb\n" +"1mXUsGxiKDsXHBwkCX/eeJmVIQDPSJfj1D5nQ4gAO01RVVhdYXCH/Q4PLX6eyesH\n" +"b3t9ma4mb36DrMjKTFFWwtgya56fo+f6AAAAAAAAAAAAAAAACxIYHyQr\n" +"-----END CERTIFICATE-----\n"; + +#endif /* TEST_CERTS_PQC_H */ + diff --git a/include/test/test.h b/include/test/test.h new file mode 100644 index 00000000..b524d8ec --- /dev/null +++ b/include/test/test.h @@ -0,0 +1,103 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test macros + * + * Dimitri Staessens + * Sander Vrijders + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_LIB_TEST_H +#define OUROBOROS_LIB_TEST_H + +#include +#include +#include +#include +#include +#include + +#define TEST_RC_SUCCESS 0 +#define TEST_RC_SKIP 1 +#define TEST_RC_FAIL -1 + +#define TEST_START(...) \ + do { \ + printf("%s", __func__); \ + if (sizeof(#__VA_ARGS__) > 1) \ + printf(" " __VA_ARGS__); \ + printf(" started.\n"); \ + fflush(stdout); \ + } while (0) + +#define TEST_SUCCESS(...) \ + do { \ + printf("\x1b[32m%s", __func__); \ + if (sizeof(#__VA_ARGS__) > 1) \ + printf(" " __VA_ARGS__); \ + printf(" succeeded.\x1b[0m\n"); \ + fflush(stdout); \ + } while (0) + +#define TEST_SKIPPED() \ + do { \ + printf("\x1b[33m%s skipped.\x1b[0m\n", __func__); \ + fflush(stdout); \ + } while (0) + +#define TEST_FAIL(...) \ + do { \ + printf("\x1b[31m%s", __func__); \ + if (sizeof(#__VA_ARGS__) > 1) \ + printf(" " __VA_ARGS__); \ + printf(" failed.\x1b[0m\n"); \ + fflush(stdout); \ + } while (0) + +#define TEST_END(result) \ + do { if (result == 0) TEST_SUCCESS(); else TEST_FAIL(); } while (0) + +static int __attribute__((unused)) test_assert_fail(int(* testfunc)(void)) +{ + pid_t pid; + int wstatus; + + pid = fork(); + if (pid == -1) { + printf("Failed to fork: %s.\n", strerror(errno)); + return TEST_RC_FAIL; + } + + if (pid == 0) + return testfunc(); /* should abort */ + + waitpid(pid, &wstatus, 0); +#ifdef CONFIG_OUROBOROS_DEBUG + if (WIFSIGNALED(wstatus) && (wstatus == 134 || wstatus == 6)) + return TEST_RC_SUCCESS; + + printf("Process did not abort, status: %d.\n", wstatus); +#else + if (WIFEXITED(wstatus) && wstatus == 0) + return TEST_RC_SUCCESS; + + printf("Process did not exit, status: %d.\n", wstatus); +#endif + + return TEST_RC_FAIL; +} + +#endif /* OUROBOROS_LIB_TEST_H */ diff --git a/irmd.conf.in b/irmd.conf.in index da5a3288..dee88392 100644 --- a/irmd.conf.in +++ b/irmd.conf.in @@ -56,10 +56,10 @@ prog=["@INSTALL_DIR@/ovpn"] # Defaults to []. prog=["@INSTALL_DIR@/oping"] # Defaults to []. args=["--listen"] # Defaults to disabled. Autostart server with these args. lb="round-robin" # Defaults to spill (load-balancing options: spill, round-robin). -# server_enc_file=/path/to/enc.cfg Default: @OUROBOROS_SRV_CRT_DIR@//enc.cfg +# server_enc_file=/path/to/enc.conf Default: @OUROBOROS_SRV_CRT_DIR@//enc.conf # server_crt_file=/path/to/crt.pem Default: @OUROBOROS_SRV_CRT_DIR@//crt.pem # server_key_file=/path/to/key.pem Default: @OUROBOROS_SRV_CRT_DIR@//key.pem -# client_enc_file=/path/to/enc.cfg Default: @OUROBOROS_CLI_CRT_DIR@//enc.cfg +# client_enc_file=/path/to/enc.conf Default: @OUROBOROS_CLI_CRT_DIR@//enc.conf # client_crt_file=/path/to/crt.pem Default: @OUROBOROS_CLI_CRT_DIR@//crt.pem # client_key_file=/path/to/key.pem Default: @OUROBOROS_CLI_CRT_DIR@//key.pem diff --git a/src/ipcpd/config.h.in b/src/ipcpd/config.h.in index 61506196..a39789a5 100644 --- a/src/ipcpd/config.h.in +++ b/src/ipcpd/config.h.in @@ -72,10 +72,11 @@ #cmakedefine HAVE_BPF #cmakedefine HAVE_RAW_SOCKETS #cmakedefine IPCP_ETH_QDISC_BYPASS -#define IPCP_ETH_RD_THR @IPCP_ETH_RD_THR@ -#define IPCP_ETH_WR_THR @IPCP_ETH_WR_THR@ -#define IPCP_ETH_LO_MTU @IPCP_ETH_LO_MTU@ -#define IPCP_ETH_MPL @IPCP_ETH_MPL@ +#define IPCP_ETH_RD_THR @IPCP_ETH_RD_THR@ +#define IPCP_ETH_WR_THR @IPCP_ETH_WR_THR@ +#define IPCP_ETH_LO_MTU @IPCP_ETH_LO_MTU@ +#define IPCP_ETH_MGMT_FRAME_SIZE @IPCP_ETH_MGMT_FRAME_SIZE@ +#define IPCP_ETH_MPL @IPCP_ETH_MPL@ /* local */ #define IPCP_LOCAL_MPL @IPCP_LOCAL_MPL@ diff --git a/src/ipcpd/eth/eth.c b/src/ipcpd/eth/eth.c index d2e7a7c8..29c5ff4f 100644 --- a/src/ipcpd/eth/eth.c +++ b/src/ipcpd/eth/eth.c @@ -138,7 +138,7 @@ #define NAME_QUERY_TIMEO 2000 /* ms */ #define MGMT_TIMEO 100 /* ms */ -#define MGMT_FRAME_SIZE 2048 +#define MGMT_FRAME_SIZE IPCP_ETH_MGMT_FRAME_SIZE #define FLOW_REQ 0 #define FLOW_REPLY 1 @@ -937,6 +937,12 @@ static void * eth_ipcp_packet_reader(void * o) #endif ipcp_sdb_release(sdb); /* No need for the N+1 buffer. */ + if (length > MGMT_FRAME_SIZE) { + log_warn("Management frame size %u exceeds %u.", + length, MGMT_FRAME_SIZE); + goto fail_frame; + } + frame = malloc(sizeof(*frame)); if (frame == NULL) { log_err("Failed to allocate frame."); diff --git a/src/ipcpd/unicast/dir/tests/CMakeLists.txt b/src/ipcpd/unicast/dir/tests/CMakeLists.txt index 870603dc..3dda8104 100644 --- a/src/ipcpd/unicast/dir/tests/CMakeLists.txt +++ b/src/ipcpd/unicast/dir/tests/CMakeLists.txt @@ -21,6 +21,8 @@ include_directories(${CMAKE_BINARY_DIR}/src/ipcpd/unicast) get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here dht_test.c @@ -31,8 +33,8 @@ add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests} ${DHT_PROTO_SRCS}) disable_test_logging_for_target(${PARENT_DIR}_test) -target_link_libraries(${PARENT_DIR}_test ouroboros-common) +target_link_libraries(${PARENT_DIR}_test ouroboros-common) add_dependencies(build_tests ${PARENT_DIR}_test) set(tests_to_run ${${PARENT_DIR}_tests}) @@ -44,5 +46,5 @@ endif() foreach (test ${tests_to_run}) get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) + add_test(${TEST_PREFIX}/${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) endforeach (test) diff --git a/src/ipcpd/unicast/dir/tests/dht_test.c b/src/ipcpd/unicast/dir/tests/dht_test.c index cb6b0f9f..4f0ee00a 100644 --- a/src/ipcpd/unicast/dir/tests/dht_test.c +++ b/src/ipcpd/unicast/dir/tests/dht_test.c @@ -27,7 +27,7 @@ #define _POSIX_C_SOURCE 200112L #endif -#include +#include #include #include diff --git a/src/ipcpd/unicast/pff/tests/CMakeLists.txt b/src/ipcpd/unicast/pff/tests/CMakeLists.txt index 358da56e..ccca26a0 100644 --- a/src/ipcpd/unicast/pff/tests/CMakeLists.txt +++ b/src/ipcpd/unicast/pff/tests/CMakeLists.txt @@ -19,6 +19,8 @@ include_directories(${CMAKE_BINARY_DIR}/src/ipcpd) get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here pft_test.c @@ -39,5 +41,5 @@ endif() foreach (test ${tests_to_run}) get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${PARENT_DIR}_test ${test_name}) + add_test(${TEST_PREFIX}/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${PARENT_DIR}_test ${test_name}) endforeach (test) diff --git a/src/ipcpd/unicast/routing/tests/CMakeLists.txt b/src/ipcpd/unicast/routing/tests/CMakeLists.txt index e4168600..f97d03bb 100644 --- a/src/ipcpd/unicast/routing/tests/CMakeLists.txt +++ b/src/ipcpd/unicast/routing/tests/CMakeLists.txt @@ -18,6 +18,8 @@ include_directories(${CMAKE_BINARY_DIR}/src/ipcpd) get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here graph_test.c @@ -39,5 +41,5 @@ endif() foreach (test ${tests_to_run}) get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) + add_test(${TEST_PREFIX}/${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) endforeach (test) diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in index 527694c0..06d51ccd 100644 --- a/src/irmd/config.h.in +++ b/src/irmd/config.h.in @@ -41,6 +41,8 @@ #define FLOW_ALLOC_TIMEOUT @FLOW_ALLOC_TIMEOUT@ #define FLOW_DEALLOC_TIMEOUT @FLOW_DEALLOC_TIMEOUT@ +#define OAP_REPLAY_TIMER @OAP_REPLAY_TIMER@ + #define BOOTSTRAP_TIMEOUT @BOOTSTRAP_TIMEOUT@ #define ENROLL_TIMEOUT @ENROLL_TIMEOUT@ #define REG_TIMEOUT @REG_TIMEOUT@ @@ -73,6 +75,10 @@ #cmakedefine IRMD_KILL_ALL_PROCESSES #cmakedefine HAVE_LIBGCRYPT #cmakedefine HAVE_OPENSSL +#ifdef HAVE_OPENSSL +#define IRMD_SECMEM_MAX @IRMD_SECMEM_MAX@ +#cmakedefine HAVE_OPENSSL_PQC +#endif #ifdef CONFIG_OUROBOROS_DEBUG #cmakedefine DEBUG_PROTO_OAP #endif diff --git a/src/irmd/main.c b/src/irmd/main.c index daaf4129..57703254 100644 --- a/src/irmd/main.c +++ b/src/irmd/main.c @@ -85,13 +85,6 @@ enum irm_state { IRMD_SHUTDOWN }; -struct oaph { - struct list_head next; - - uint64_t stamp; - uint8_t id[OAP_ID_SIZE]; -}; - struct cmd { struct list_head next; @@ -105,12 +98,6 @@ struct { #ifdef HAVE_TOML char * cfg_file; /* configuration file path */ #endif - struct { - struct auth_ctx * ctx; /* default authentication ctx */ - struct list_head list; /* OAP headers seen before */ - pthread_mutex_t mtx; /* mutex for OAP headers */ - } auth; - struct lockfile * lf; /* single irmd per system */ struct shm_rdrbuff * rdrb; /* rdrbuff for packets */ @@ -456,7 +443,7 @@ static void name_update_sec_paths(struct name_info * info) assert(info != NULL); if (strlen(info->s.enc) == 0) - sprintf(info->s.enc, "%s/%s/enc.cfg", srv_dir, info->name); + sprintf(info->s.enc, "%s/%s/enc.conf", srv_dir, info->name); if (strlen(info->s.crt) == 0) sprintf(info->s.crt, "%s/%s/crt.pem", srv_dir, info->name); @@ -465,7 +452,7 @@ static void name_update_sec_paths(struct name_info * info) sprintf(info->s.key, "%s/%s/key.pem", srv_dir, info->name); if (strlen(info->c.enc) == 0) - sprintf(info->c.enc, "%s/%s/enc.cfg", cli_dir, info->name); + sprintf(info->c.enc, "%s/%s/enc.conf", cli_dir, info->name); if (strlen(info->c.crt) == 0) sprintf(info->c.crt, "%s/%s/crt.pem", cli_dir, info->name); @@ -613,18 +600,21 @@ static int unbind_program(const char * prog, if (name == NULL) { if (reg_destroy_prog(prog) < 0) { log_err("Failed to unbind %s.", prog); - return -1; + goto fail; } log_info("Program %s unbound.", prog); } else { if (reg_unbind_prog(name, prog) < 0) { log_err("Failed to unbind %s from %s", prog, name); - return -1; + goto fail; } log_info("Name %s unbound for %s.", name, prog); } return 0; + + fail: + return -1; } static int unbind_process(pid_t pid, @@ -633,18 +623,21 @@ static int unbind_process(pid_t pid, if (name == NULL) { if (reg_destroy_proc(pid) < 0) { log_err("Failed to unbind %d.", pid); - return -1; + goto fail; } log_info("Process %d unbound.", pid); } else { if (reg_unbind_proc(name, pid) < 0) { log_err("Failed to unbind %d from %s", pid, name); - return -1; + goto fail; } log_info("Name %s unbound for process %d.", name, pid); } return 0; + + fail: + return -1; } static int list_ipcps(ipcp_list_msg_t *** ipcps, @@ -716,8 +709,7 @@ int name_reg(const char * name, if (ipcp_reg(pid, hash)) { log_err("Could not register " HASH_FMT32 " with IPCP %d.", HASH_VAL32(hash.data), pid); - freebuf(hash); - return -1; + goto fail_hash; } log_info("Registered %s with IPCP %d as " HASH_FMT32 ".", @@ -726,6 +718,10 @@ int name_reg(const char * name, freebuf(hash); return 0; + + fail_hash: + freebuf(hash); + return -1; } static int name_unreg(const char * name, @@ -760,8 +756,7 @@ static int name_unreg(const char * name, if (ipcp_unreg(pid, hash)) { log_err("Could not unregister %s with IPCP %d.", name, pid); - freebuf(hash); - return -1; + goto fail_hash; } log_info("Unregistered %s from %d.", name, pid); @@ -769,6 +764,10 @@ static int name_unreg(const char * name, freebuf(hash); return 0; + + fail_hash: + freebuf(hash); + return -1; } static int proc_announce(const struct proc_info * info) @@ -796,266 +795,26 @@ static int proc_exit(pid_t pid) return 0; } -static void __cleanup_pkp(void * pkp) -{ - if (pkp != NULL) - crypt_dh_pkp_destroy(pkp); -} - static void __cleanup_flow(void * flow) { reg_destroy_flow(((struct flow_info *) flow)->id); } -static bool file_exists(const char * path) -{ - struct stat s; - - if (stat(path, &s) < 0 && errno == ENOENT) { - log_dbg("File %s does not exist.", path); - return false; - } - - return true; -} - -static int load_credentials(const char * name, - const struct name_sec_paths * paths, - void ** pkp, - void ** crt, - bool * crypt) -{ - assert(paths != NULL); - assert(pkp != NULL); - assert(crt != NULL); - - *pkp = NULL; - *crt = NULL; - - /* TODO: Allow configuration. For now, encrypt if path exists */ - *crypt = file_exists(paths->enc); - if (*crypt) - log_info("Encryption enabled for %s.", name); - - if (!file_exists(paths->crt) || !file_exists(paths->key)) { - log_info("No security info for %s.", name); - return 0; - } - - if (crypt_load_crt_file(paths->crt, crt) < 0) { - log_err("Failed to load %s for %s.", paths->crt, name); - goto fail_crt; - } - - if (crypt_load_privkey_file(paths->key, pkp) < 0) { - log_err("Failed to load %s for %s.", paths->key, name); - goto fail_key; - } - - log_info("Loaded security keys for %s.", name); - - return 0; - - fail_key: - crypt_free_crt(*crt); - *crt = NULL; - fail_crt: - return -EAUTH; -} - -static int load_srv_credentials(const char * name, - void ** pkp, - void ** crt, - bool * crypt) -{ - struct name_info info; - - assert(name != NULL); - assert(pkp != NULL); - assert(crt != NULL); - - if (reg_get_name_info(name, &info) < 0) { - log_err("Failed to get name info for %s.", name); - return -ENAME; - } - - return load_credentials(name, &info.s, pkp, crt, crypt); -} - -static int load_cli_credentials(const char * name, - void ** pkp, - void ** crt, - bool * crypt) -{ - struct name_info info; - - assert(name != NULL); - assert(pkp != NULL); - assert(crt != NULL); - - if (reg_get_name_info(name, &info) < 0) { - log_err("Failed to get name info for %s.", name); - return -ENAME; - } - - return load_credentials(name, &info.c, pkp, crt, crypt); -} - -#define ID_IS_EQUAL(id1, id2) (memcmp(id1, id2, OAP_ID_SIZE) == 0) -static int irm_check_oap_hdr(const struct oap_hdr * oap_hdr, - time_t mpl) -{ - struct list_head * p; - struct list_head * h; - struct timespec now; - struct oaph * new; - uint64_t stamp; - uint64_t cur; - uint8_t * id; - ssize_t delta; - - assert(oap_hdr != NULL); - - stamp = oap_hdr->timestamp; - id = oap_hdr->id.data; - - clock_gettime(CLOCK_REALTIME, &now); - - cur = TS_TO_UINT64(now); - - delta = (ssize_t)(cur - stamp) / MILLION; - if (delta > mpl) - log_warn("Transit time exceeds MPL by %zd ms.", delta); - if (delta < -TIMESYNC_SLACK) - log_warn("OAP header sent %zd ms from the future.", -delta); - - new = malloc(sizeof(*new)); - if (new == NULL) { - log_err("Failed to allocate memory for OAP element."); - return -ENOMEM; - } - - pthread_mutex_lock(&irmd.auth.mtx); - - list_for_each_safe(p, h, &irmd.auth.list) { - struct oaph * oaph = list_entry(p, struct oaph, next); - if (cur > oaph->stamp + OAP_SEEN_TIMER * BILLION) { - list_del(&oaph->next); - free(oaph); - continue; - } - - if (oaph->stamp == stamp && ID_IS_EQUAL(oaph->id, id)) { - log_warn("OAP header already known: " HASH_FMT64 ".", - HASH_VAL64(id)); - goto fail_replay; - } - } - - memcpy(new->id, id, OAP_ID_SIZE); - new->stamp = stamp; - - list_add_tail(&new->next, &irmd.auth.list); - - pthread_mutex_unlock(&irmd.auth.mtx); - - return 0; - - fail_replay: - pthread_mutex_unlock(&irmd.auth.mtx); - free(new); - return -EAUTH; -} - -static int irm_auth_peer(const char * name, - const struct oap_hdr * oap_hdr, - const struct oap_hdr * r_oap_hdr) -{ - void * crt; - void * pk; - buffer_t sign; - const char * n = name == NULL ? "" : name; - - if (memcmp(r_oap_hdr->id.data, oap_hdr->id.data, OAP_ID_SIZE) != 0) { - log_err("OAP ID mismatch in flow allocation."); - goto fail_check; - } - - if (r_oap_hdr->crt.len == 0) { - log_info("No certificate provided by %s.", n); - return 0; - } - - if (crypt_load_crt_der(r_oap_hdr->crt, &crt) < 0) { - log_err("Failed to load certificate from %s.", n); - goto fail_check; - } - - log_dbg("Loaded peer certificate for %s.", n); - - if (name != NULL) { - if (crypt_check_crt_name(crt, n) < 0) { - log_err("Certificate does not match %s.", n); - goto fail_crt; - } - log_dbg("Certificate matches name %s.", n); - } - - if (crypt_get_pubkey_crt(crt, &pk) < 0) { - log_err("Failed to get pubkey from certificate for %s.", n); - goto fail_crt; - } - - log_dbg("Got public key from certificate for %s.", n); - - if (auth_verify_crt(irmd.auth.ctx, crt) < 0) { - log_err("Failed to verify peer %s with CA store.", n); - goto fail_crt; - } - - log_info("Successfully verified peer certificate for %s.", n); - - sign = r_oap_hdr->hdr; - sign.len -= (r_oap_hdr->sig.len + sizeof(uint16_t)); - - if (auth_verify_sig(pk, sign, r_oap_hdr->sig) < 0) { - log_err("Failed to verify signature for peer %s.", n); - goto fail_check_sig; - } - - crypt_free_key(pk); - crypt_free_crt(crt); - - log_info("Successfully authenticated %s.", n); - - return 0; - - fail_check_sig: - crypt_free_key(pk); - fail_crt: - crypt_free_crt(crt); - fail_check: - return -1; -} - static int flow_accept(struct flow_info * flow, - buffer_t * symmkey, buffer_t * data, - struct timespec * abstime) + struct timespec * abstime, + struct crypt_sk * sk) { - struct oap_hdr oap_hdr; /* incoming request */ - struct oap_hdr r_oap_hdr; /* outgoing response */ - uint8_t buf[MSGBUFSZ]; /* buffer for local ephkey */ - buffer_t lpk = BUF_INIT; /* local ephemeral pubkey */ - char name[NAME_SIZE + 1]; /* name for flow */ - void * pkp = NULL; /* signing private key */ - void * crt = NULL; /* signing certificate */ - int err; - bool crypt; + buffer_t req_hdr; + buffer_t resp_hdr; + char name[NAME_SIZE + 1]; + struct name_info info; + int err; - /* piggyback of user data not yet implemented */ assert(data != NULL && BUF_IS_EMPTY(data)); - assert(symmkey != NULL && BUF_IS_EMPTY(symmkey)); + + clrbuf(req_hdr); + clrbuf(resp_hdr); if (!reg_has_proc(flow->n_pid)) { log_err("Unknown process %d calling accept.", flow->n_pid); @@ -1077,7 +836,7 @@ static int flow_accept(struct flow_info * flow, pthread_cleanup_push(__cleanup_flow, flow); - err = reg_wait_flow_accepted(flow, &oap_hdr.hdr, abstime); + err = reg_wait_flow_accepted(flow, &req_hdr, abstime); pthread_cleanup_pop(false); @@ -1097,119 +856,38 @@ static int flow_accept(struct flow_info * flow, if (reg_get_name_for_flow_id(name, flow->id) < 0) { log_err("Failed to get name for flow %d.", flow->id); err = -EIPCP; - goto fail_cred; - } - - log_dbg("IPCP %d accepting flow %d for %s.", - flow->n_pid, flow->id, name); - - if (load_srv_credentials(name, &pkp, &crt, &crypt) < 0) { - log_err("Failed to load security keys for %s.", name); - err = -EAUTH; - goto fail_cred; - } - - if (oap_hdr_decode(oap_hdr.hdr, &oap_hdr) < 0) { - log_err("Failed to decode OAP header from %s.", name); - err = -EIPCP; - goto fail_oap_hdr; - } -#ifdef DEBUG_PROTO_OAP - debug_oap_hdr_rcv(&oap_hdr); -#endif - if (irm_check_oap_hdr(&oap_hdr, flow->mpl) < 0) { - log_err("OAP header failed replay check."); - goto fail_oap_hdr; - } - - if (crypt && oap_hdr.eph.len == 0) { - log_warn("Encryption required but no key provided."); - err = -ECRYPT; - goto fail_oap_hdr; + goto fail_oap; } - if (oap_hdr.eph.len > 0) { /* crypto requested */ - uint8_t * s; /* symmetric encryption key */ - ssize_t key_len; /* length of local pubkey */ - void * pkp = NULL; /* ephemeral private key pair */ - - s = malloc(SYMMKEYSZ); - if (s == NULL) { - log_err("Failed to malloc symmkey."); - err = -ENOMEM; - goto fail_keys; - } - - key_len = crypt_dh_pkp_create(&pkp, buf); - if (key_len < 0) { - free(s); - log_err("Failed to generate key pair."); - err = -ECRYPT; - goto fail_keys; - } - - lpk.data = buf; - lpk.len = (size_t) key_len; - - log_dbg("Generated ephemeral keys for %d.", flow->n_pid); - - if (crypt_dh_derive(pkp, oap_hdr.eph, s) < 0) { - log_err("Failed to derive secret for %d.", flow->id); - crypt_dh_pkp_destroy(pkp); - free(s); - err = -ECRYPT; - goto fail_derive; - } - - symmkey->data = s; - symmkey->len = SYMMKEYSZ; - - crypt_dh_pkp_destroy(pkp); + if (reg_get_name_info(name, &info) < 0) { + log_err("Failed to get name info for %s.", name); + err = -ENAME; + goto fail_oap; } - if (oap_hdr_init(oap_hdr.id, pkp, crt, lpk, *data, &r_oap_hdr) < 0) { - log_err("Failed to create OAP header."); - err = -ENOMEM; - goto fail_r_oap_hdr; - } + log_dbg("IPCP %d accepting flow %d for %s.", + flow->n_pid, flow->id, name); - if (irm_auth_peer(NULL, &r_oap_hdr, &oap_hdr) < 0) { - log_err("Failed to auth %s client, flow %d.", name, flow->id); - err = -EAUTH; - goto fail_r_oap_hdr; + err = oap_srv_process(&info, req_hdr, &resp_hdr, data, sk); + if (err < 0) { + log_err("OAP processing failed for %s.", name); + goto fail_oap; } - crypt_free_crt(crt); - crypt_free_key(pkp); - -#ifdef DEBUG_PROTO_OAP - debug_oap_hdr_snd(&r_oap_hdr); -#endif - if (ipcp_flow_alloc_resp(flow, 0, r_oap_hdr.hdr) < 0) { + if (ipcp_flow_alloc_resp(flow, 0, resp_hdr) < 0) { log_err("Failed to respond to flow allocation."); goto fail_resp; } - log_info("Flow %d accepted by %d for %s.", - flow->id, flow->n_pid, name); + log_info("Flow %d accepted by %d for %s.", flow->id, flow->n_pid, name); - oap_hdr_fini(&oap_hdr); - oap_hdr_fini(&r_oap_hdr); + freebuf(req_hdr); + freebuf(resp_hdr); return 0; - fail_r_oap_hdr: - freebuf(*symmkey); - fail_derive: - clrbuf(lpk); - fail_keys: - oap_hdr_fini(&oap_hdr); - fail_oap_hdr: - crypt_free_crt(crt); - crypt_free_key(pkp); - fail_cred: - assert(lpk.data == NULL && lpk.len == 0); - ipcp_flow_alloc_resp(flow, err, lpk); + fail_oap: + ipcp_flow_alloc_resp(flow, err, resp_hdr); fail_wait: reg_destroy_flow(flow->id); fail_flow: @@ -1217,10 +895,8 @@ static int flow_accept(struct flow_info * flow, fail_resp: flow->state = FLOW_NULL; - oap_hdr_fini(&r_oap_hdr); - freebuf(*symmkey); - clrbuf(lpk); - oap_hdr_fini(&oap_hdr); + freebuf(req_hdr); + freebuf(resp_hdr); reg_destroy_flow(flow->id); return -EIPCP; } @@ -1361,78 +1037,31 @@ static int get_ipcp_by_dst(const char * dst, return err; } -static int flow_alloc(struct flow_info * flow, - const char * dst, - buffer_t * symmkey, +static int flow_alloc(const char * dst, + struct flow_info * flow, buffer_t * data, - struct timespec * abstime) + struct timespec * abstime, + struct crypt_sk * sk) { - struct oap_hdr oap_hdr; /* outgoing request */ - struct oap_hdr r_oap_hdr; /* incoming response */ - uint8_t buf[MSGBUFSZ]; /* buffer for local ephkey */ - buffer_t lpk = BUF_INIT; /* local ephemeral pubkey */ - void * pkp = NULL; /* ephemeral private key pair */ - uint8_t * s = NULL; /* symmetric key */ - void * cpkp = NULL; /* signing private key */ - void * ccrt = NULL; /* signing certificate */ - buffer_t hash; - uint8_t idbuf[OAP_ID_SIZE]; - buffer_t id; - int err; - bool crypt; + buffer_t req_hdr = BUF_INIT; + buffer_t resp_hdr = BUF_INIT; + buffer_t hash = BUF_INIT; + struct name_info info; + void * ctx; + int err; /* piggyback of user data not yet implemented */ assert(data != NULL && BUF_IS_EMPTY(data)); - assert(symmkey != NULL && BUF_IS_EMPTY(symmkey)); log_info("Allocating flow for %d to %s.", flow->n_pid, dst); - if (random_buffer(idbuf, OAP_ID_SIZE) < 0) { - log_err("Failed to generate ID."); - err = -EIRMD; - goto fail_id; - } - - id.data = idbuf; - id.len = OAP_ID_SIZE; - - if (load_cli_credentials(dst, &cpkp, &ccrt, &crypt) < 0) { - log_err("Failed to load security keys for %s.", dst); - err = -EAUTH; - goto fail_cred; - } - - if (crypt > 0) { - ssize_t key_len; - - s = malloc(SYMMKEYSZ); - if (s == NULL) { - log_err("Failed to malloc symmetric key"); - err = -ENOMEM; - goto fail_malloc; - } - - key_len = crypt_dh_pkp_create(&pkp, buf); - if (key_len < 0) { - log_err("Failed to generate key pair."); - err = -ECRYPT; - goto fail_pkp; - } - - lpk.data = buf; - lpk.len = (size_t) key_len; - - log_dbg("Generated ephemeral keys for %d.", flow->n_pid); + /* Look up name_info for dst */ + if (reg_get_name_info(dst, &info) < 0) { + log_err("Failed to get name info for %s.", dst); + err = -ENAME; + goto fail_flow; } - if (oap_hdr_init(id, cpkp, ccrt, lpk, *data, &oap_hdr) < 0) { - log_err("Failed to create OAP header."); - err = -ENOMEM; - goto fail_oap_hdr; - } -#ifdef DEBUG_PROTO_OAP - debug_oap_hdr_snd(&oap_hdr); -#endif if (reg_create_flow(flow) < 0) { log_err("Failed to create flow."); err = -EBADF; @@ -1445,109 +1074,70 @@ static int flow_alloc(struct flow_info * flow, goto fail_ipcp; } - reg_prepare_flow_alloc(flow); + if (reg_prepare_flow_alloc(flow) < 0) { + log_err("Failed to prepare flow allocation."); + err = -EBADF; + goto fail_prepare; + } - if (ipcp_flow_alloc(flow, hash, oap_hdr.hdr)) { + if (oap_cli_prepare(&ctx, &info, &req_hdr, *data) < 0) { + log_err("Failed to prepare OAP request for %s.", dst); + err = -EBADF; + goto fail_prepare; + } + + if (ipcp_flow_alloc(flow, hash, req_hdr)) { log_err("Flow allocation %d failed.", flow->id); - err = -ENOTALLOC; + err = -EIPCP; goto fail_alloc; } pthread_cleanup_push(__cleanup_flow, flow); - pthread_cleanup_push(__cleanup_pkp, pkp); pthread_cleanup_push(free, hash.data); - pthread_cleanup_push(free, s); - err = reg_wait_flow_allocated(flow, &r_oap_hdr.hdr, abstime); + err = reg_wait_flow_allocated(flow, &resp_hdr, abstime); - pthread_cleanup_pop(false); - pthread_cleanup_pop(false); pthread_cleanup_pop(false); pthread_cleanup_pop(false); if (err == -ETIMEDOUT) { log_err("Flow allocation timed out."); - goto fail_alloc; - } - - if (err == -1) { - log_dbg("Flow allocation terminated."); - err = -EIPCP; - goto fail_alloc; + goto fail_wait; } - log_dbg("Response received for flow %d to %s.", flow->id, dst); + log_dbg("Response for flow %d to %s.", flow->id, dst); if (err < 0) { - log_warn("Flow allocation rejected for %s: %d.", dst, err); - goto fail_alloc; + log_warn("Allocation rejected: %s (%d).", dst, err); + goto fail_peer; } - if (oap_hdr_decode(r_oap_hdr.hdr, &r_oap_hdr) < 0) { - log_err("Failed to decode OAP header."); - err = -EIPCP; - goto fail_r_oap_hdr; - } -#ifdef DEBUG_PROTO_OAP - debug_oap_hdr_rcv(&r_oap_hdr); -#endif - if (irm_check_oap_hdr(&r_oap_hdr, flow->mpl) < 0) { - log_err("OAP header failed replay check."); - err = -EAUTH; - goto fail_r_oap_hdr; - } - - if (irm_auth_peer(dst, &oap_hdr, &r_oap_hdr) < 0) { - log_err("Failed to authenticate %s (flow %d).", dst, flow->id); - err = -EAUTH; - goto fail_r_oap_hdr; - } - - if (lpk.len > 0) { /* crypto requested */ - if (crypt_dh_derive(pkp, r_oap_hdr.eph, s) < 0) { - log_err("Failed to derive secret for %d.", flow->id); - err = -ECRYPT; - goto fail_r_oap_hdr; - } - crypt_dh_pkp_destroy(pkp); - - symmkey->data = s; - symmkey->len = SYMMKEYSZ; - s = NULL; + err = oap_cli_complete(ctx, &info, resp_hdr, data, sk); + if (err < 0) { + log_err("OAP completion failed for %s.", dst); + goto fail_complete; } - oap_hdr_fini(&r_oap_hdr); - oap_hdr_fini(&oap_hdr); - - crypt_free_crt(ccrt); - crypt_free_key(cpkp); - - /* TODO: piggyback user data if needed */ - + freebuf(req_hdr); + freebuf(resp_hdr); freebuf(hash); - free(s); return 0; - fail_r_oap_hdr: + fail_complete: + ctx = NULL; /* freee'd on complete */ + fail_peer: flow->state = FLOW_DEALLOCATED; - oap_hdr_fini(&r_oap_hdr); + fail_wait: + freebuf(resp_hdr); fail_alloc: + freebuf(req_hdr); + oap_ctx_free(ctx); + fail_prepare: freebuf(hash); fail_ipcp: reg_destroy_flow(flow->id); fail_flow: - oap_hdr_fini(&oap_hdr); - fail_oap_hdr: - crypt_dh_pkp_destroy(pkp); - fail_pkp: - free(s); - fail_malloc: - crypt_free_crt(ccrt); - crypt_free_key(cpkp); - fail_cred: - clrbuf(id); - fail_id: return err; } @@ -1744,19 +1334,21 @@ static void __cleanup_irm_msg(void * o) static irm_msg_t * do_command_msg(irm_msg_t * msg) { - struct ipcp_config conf; - struct ipcp_info ipcp; - struct flow_info flow; - struct proc_info proc; - struct name_info name; - struct timespec * abstime; - struct timespec max = TIMESPEC_INIT_MS(FLOW_ALLOC_TIMEOUT); - struct timespec now; - struct timespec ts = TIMESPEC_INIT_S(0); /* static analysis */ - int res; - irm_msg_t * ret_msg; - buffer_t data; - buffer_t symmkey = BUF_INIT;; + struct ipcp_config conf; + struct ipcp_info ipcp; + struct flow_info flow; + struct proc_info proc; + struct name_info name; + struct crypt_sk sk; + uint8_t kbuf[SYMMKEYSZ]; /* stack buffer for OAP */ + uint8_t * hbuf = NULL; /* heap copy for response */ + struct timespec * abstime; + struct timespec max = TIMESPEC_INIT_MS(FLOW_ALLOC_TIMEOUT); + struct timespec now; + struct timespec ts = TIMESPEC_INIT_S(0); /* static analysis */ + int res; + irm_msg_t * ret_msg; + buffer_t data; memset(&flow, 0, sizeof(flow)); @@ -1858,15 +1450,27 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) msg->has_pk = false; assert(data.len > 0 ? data.data != NULL : data.data == NULL); flow = flow_info_msg_to_s(msg->flow_info); - res = flow_accept(&flow, &symmkey, &data, abstime); + sk.key = kbuf; + res = flow_accept(&flow, &data, abstime, &sk); if (res == 0) { - ret_msg->flow_info = flow_info_s_to_msg(&flow); - ret_msg->has_symmkey = symmkey.len != 0; - ret_msg->symmkey.data = symmkey.data; - ret_msg->symmkey.len = symmkey.len; - ret_msg->has_pk = data.len != 0; - ret_msg->pk.data = data.data; - ret_msg->pk.len = data.len; + ret_msg->flow_info = flow_info_s_to_msg(&flow); + ret_msg->has_pk = data.len != 0; + ret_msg->pk.data = data.data; + ret_msg->pk.len = data.len; + ret_msg->has_cipher_nid = true; + ret_msg->cipher_nid = sk.nid; + if (sk.nid != NID_undef) { + hbuf = malloc(SYMMKEYSZ); + if (hbuf == NULL) { + log_err("Failed to malloc key buf"); + return NULL; + } + + memcpy(hbuf, kbuf, SYMMKEYSZ); + ret_msg->sym_key.data = hbuf; + ret_msg->sym_key.len = SYMMKEYSZ; + ret_msg->has_sym_key = true; + } } break; case IRM_MSG_CODE__IRM_FLOW_ALLOC: @@ -1876,15 +1480,26 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) assert(data.len > 0 ? data.data != NULL : data.data == NULL); flow = flow_info_msg_to_s(msg->flow_info); abstime = abstime == NULL ? &max : abstime; - res = flow_alloc(&flow, msg->dst, &symmkey, &data, abstime); + sk.key = kbuf; + res = flow_alloc(msg->dst, &flow, &data, abstime, &sk); if (res == 0) { - ret_msg->flow_info = flow_info_s_to_msg(&flow); - ret_msg->has_symmkey = symmkey.len != 0; - ret_msg->symmkey.data = symmkey.data; - ret_msg->symmkey.len = symmkey.len; - ret_msg->has_pk = data.len != 0; - ret_msg->pk.data = data.data; - ret_msg->pk.len = data.len; + ret_msg->flow_info = flow_info_s_to_msg(&flow); + ret_msg->has_pk = data.len != 0; + ret_msg->pk.data = data.data; + ret_msg->pk.len = data.len; + ret_msg->has_cipher_nid = true; + ret_msg->cipher_nid = sk.nid; + if (sk.nid != NID_undef) { + hbuf = malloc(SYMMKEYSZ); + if (hbuf == NULL) { + log_err("Failed to malloc key buf"); + return NULL; + } + memcpy(hbuf, kbuf, SYMMKEYSZ); + ret_msg->sym_key.data = hbuf; + ret_msg->sym_key.len = SYMMKEYSZ; + ret_msg->has_sym_key = true; + } } break; case IRM_MSG_CODE__IRM_FLOW_JOIN: @@ -1938,14 +1553,16 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) else ret_msg->result = res; + explicit_bzero(kbuf, SYMMKEYSZ); + return ret_msg; } static void * mainloop(void * o) { - int sfd; - irm_msg_t * msg; - buffer_t buffer; + int sfd; + irm_msg_t * msg; + buffer_t buffer; (void) o; @@ -2170,7 +1787,7 @@ static int irm_load_store(char * dpath) goto fail_file; } - if (auth_add_crt_to_store(irmd.auth.ctx, crt) < 0) { + if (oap_auth_add_ca_crt(crt) < 0) { log_err("Failed to add certificate from %s to store.", path); goto fail_crt_add; @@ -2295,27 +1912,19 @@ static int irm_init(void) goto fail_tpm_create; } - if (pthread_mutex_init(&irmd.auth.mtx, NULL) < 0) { - log_err("Failed to initialize auth mutex."); - goto fail_auth_mtx; + if (oap_auth_init() < 0) { + log_err("Failed to initialize OAP module."); + goto fail_oap; } - irmd.auth.ctx = auth_create_ctx(); - if (irmd.auth.ctx == NULL) { - log_err("Failed to create auth store context."); - goto fail_auth_ctx; - } - - list_head_init(&irmd.auth.list); - if (irm_load_store(OUROBOROS_CA_CRT_DIR) < 0) { log_err("Failed to load CA certificates."); - goto fail_auth_ctx; + goto fail_load_store; } if (irm_load_store(OUROBOROS_CHAIN_DIR) < 0) { log_err("Failed to load intermediate certificates."); - goto fail_auth_ctx; + goto fail_load_store; } #ifdef HAVE_FUSE @@ -2352,11 +1961,10 @@ static int irm_init(void) #ifdef HAVE_FUSE rmdir(FUSE_PREFIX); #endif - auth_destroy_ctx(irmd.auth.ctx); #endif - fail_auth_ctx: - pthread_mutex_destroy(&irmd.auth.mtx); - fail_auth_mtx: + fail_load_store: + oap_auth_fini(); + fail_oap: tpm_destroy(irmd.tpm); fail_tpm_create: shm_rdrbuff_destroy(irmd.rdrb); @@ -2388,18 +1996,7 @@ static void irm_fini(void) if (irmd_get_state() != IRMD_INIT) log_warn("Unsafe destroy."); - pthread_mutex_lock(&irmd.auth.mtx); - - list_for_each_safe(p, h, &irmd.auth.list) { - struct oaph * oaph = list_entry(p, struct oaph, next); - list_del(&oaph->next); - free(oaph); - } - - pthread_mutex_unlock(&irmd.auth.mtx); - pthread_mutex_destroy(&irmd.auth.mtx); - - auth_destroy_ctx(irmd.auth.ctx); + oap_auth_fini(); tpm_destroy(irmd.tpm); @@ -2637,6 +2234,11 @@ int main(int argc, goto fail_reg; } + if (crypt_secure_malloc_init(IRMD_SECMEM_MAX) < 0) { + log_err("Failed to initialize secure memory allocation."); + goto fail_reg; + } + pthread_sigmask(SIG_BLOCK, &sigset, NULL); if (irm_start() < 0) @@ -2656,6 +2258,8 @@ int main(int argc, pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); + crypt_secure_malloc_fini(); + reg_clear(); reg_fini(); 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 */ diff --git a/src/irmd/oap.h b/src/irmd/oap.h index ccdfa804..25c07408 100644 --- a/src/irmd/oap.h +++ b/src/irmd/oap.h @@ -1,7 +1,7 @@ /* * Ouroboros - Copyright (C) 2016 - 2024 * - * Ouroboros flow allocation protocol header + * Ouroboros Allocation Protocol (OAP) Component * * Dimitri Staessens * Sander Vrijders @@ -23,72 +23,45 @@ #ifndef OUROBOROS_IRMD_OAP_H #define OUROBOROS_IRMD_OAP_H +#include +#include +#include #include -#define OAP_ID_SIZE (16) -#define OAP_HDR_MIN_SIZE (OAP_ID_SIZE + sizeof(uint64_t) + 4 * sizeof(uint16_t)) +/* OAP authentication state (in oap/auth.c) */ +int oap_auth_init(void); +void oap_auth_fini(void); -/* - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +---------------------------------------------------------------+ - * | | - * | id (128 bits) | - * | | - * | | - * +---------------------------------------------------------------+ - * | timestamp (64 bits) | - * | | - * +---------------------------------------------------------------+ - * | crt_len (16 bits) | | - * +-----------+-----------------+ | - * | certificate | - * | | - * +---------------------------------------------------------------+ - * | eph_len (16 bits) | | - * +-----------+-----------------+ | - * | public key for ECDHE | - * | | - * +---------------------------------------------------------------+ - * | data_len (16 bits) | | - * +-----------+-----------------+ | - * | piggy backed application data | - * | | - * +---------------------------------------------------------------+ - * | sig_len (16 bits) | | - * +-----------+-----------------+ | - * | signature | - * | | - * +---------------------------------------------------------------+ - */ - -struct oap_hdr { - uint64_t timestamp; - buffer_t id; - buffer_t crt; - buffer_t eph; - buffer_t data; - buffer_t sig; - buffer_t hdr; -}; - -int oap_hdr_init(buffer_t id, - void * pkp, - void * pubcrt, - buffer_t ephkey, - buffer_t data, - struct oap_hdr * oap_hdr); +int oap_auth_add_ca_crt(void * crt); -void oap_hdr_fini(struct oap_hdr * oap_hdr); +/* +* Prepare OAP request header for server, returns context +* Passes client data for srv, returns srv data for client +*/ +int oap_cli_prepare(void ** ctx, + const struct name_info * info, + buffer_t * req_buf, + buffer_t data); -int oap_hdr_decode(buffer_t hdr, - struct oap_hdr * oap_hdr); +/* + * Server processes header, creates response header, returns secret key. + * data is in/out: input=srv data to send, output=cli data received. + */ +int oap_srv_process(const struct name_info * info, + buffer_t req_buf, + buffer_t * rsp_buf, + buffer_t * data, + struct crypt_sk * sk); -#ifdef DEBUG_PROTO_OAP -void debug_oap_hdr_snd(const struct oap_hdr * hdr); +/* Complete OAP, returns secret key and server data, frees ctx */ +int oap_cli_complete(void * ctx, + const struct name_info * info, + buffer_t rsp_buf, + buffer_t * data, + struct crypt_sk * sk); -void debug_oap_hdr_rcv(const struct oap_hdr * hdr); -#endif /* DEBUG_PROTO_OAP */ +/* Free OAP state (on failure before complete) */ +void oap_ctx_free(void * ctx); #endif /* OUROBOROS_IRMD_OAP_H */ diff --git a/src/irmd/oap/auth.c b/src/irmd/oap/auth.c new file mode 100644 index 00000000..cea7b7a0 --- /dev/null +++ b/src/irmd/oap/auth.c @@ -0,0 +1,252 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Authentication, replay detection, and validation + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "auth.h" +#include "hdr.h" + +#include +#include +#include + +struct oap_replay_entry { + struct list_head next; + uint64_t timestamp; + uint8_t id[OAP_ID_SIZE]; +}; + +static struct { + struct auth_ctx * ca_ctx; + struct { + struct list_head list; + pthread_mutex_t mtx; + } replay; +} oap_auth; + +int oap_auth_init(void) +{ + oap_auth.ca_ctx = auth_create_ctx(); + if (oap_auth.ca_ctx == NULL) { + log_err("Failed to create OAP auth context."); + goto fail_ctx; + } + + list_head_init(&oap_auth.replay.list); + + if (pthread_mutex_init(&oap_auth.replay.mtx, NULL)) { + log_err("Failed to init OAP replay mutex."); + goto fail_mtx; + } + + return 0; + + fail_mtx: + auth_destroy_ctx(oap_auth.ca_ctx); + fail_ctx: + return -1; +} + +void oap_auth_fini(void) +{ + struct list_head * p; + struct list_head * h; + + pthread_mutex_lock(&oap_auth.replay.mtx); + + list_for_each_safe(p, h, &oap_auth.replay.list) { + struct oap_replay_entry * e; + e = list_entry(p, struct oap_replay_entry, next); + list_del(&e->next); + free(e); + } + + pthread_mutex_unlock(&oap_auth.replay.mtx); + pthread_mutex_destroy(&oap_auth.replay.mtx); + + auth_destroy_ctx(oap_auth.ca_ctx); +} + +int oap_auth_add_ca_crt(void * crt) +{ + return auth_add_crt_to_store(oap_auth.ca_ctx, crt); +} + +#define TIMESYNC_SLACK 100 /* ms */ +#define ID_IS_EQUAL(id1, id2) (memcmp(id1, id2, OAP_ID_SIZE) == 0) +int oap_check_hdr(const struct oap_hdr * hdr) +{ + struct list_head * p; + struct list_head * h; + struct timespec now; + struct oap_replay_entry * new; + uint64_t stamp; + uint64_t cur; + uint8_t * id; + ssize_t delta; + + assert(hdr != NULL); + + stamp = hdr->timestamp; + id = hdr->id.data; + + clock_gettime(CLOCK_REALTIME, &now); + + cur = TS_TO_UINT64(now); + + delta = (ssize_t)(cur - stamp) / MILLION; + if (delta < -TIMESYNC_SLACK) { + log_err_id(id, "OAP header from %zd ms into future.", -delta); + goto fail_stamp; + } + + if (delta > OAP_REPLAY_TIMER * 1000) { + log_err_id(id, "OAP header too old (%zd ms).", delta); + goto fail_stamp; + } + + new = malloc(sizeof(*new)); + if (new == NULL) { + log_err_id(id, "Failed to allocate memory for OAP element."); + goto fail_stamp; + } + + pthread_mutex_lock(&oap_auth.replay.mtx); + + list_for_each_safe(p, h, &oap_auth.replay.list) { + struct oap_replay_entry * e; + e = list_entry(p, struct oap_replay_entry, next); + if (cur > e->timestamp + OAP_REPLAY_TIMER * BILLION) { + list_del(&e->next); + free(e); + continue; + } + + if (e->timestamp == stamp && ID_IS_EQUAL(e->id, id)) { + log_warn_id(id, "OAP header already known."); + goto fail_replay; + } + } + + memcpy(new->id, id, OAP_ID_SIZE); + new->timestamp = stamp; + + list_add_tail(&new->next, &oap_auth.replay.list); + + pthread_mutex_unlock(&oap_auth.replay.mtx); + + return 0; + + fail_replay: + pthread_mutex_unlock(&oap_auth.replay.mtx); + free(new); + fail_stamp: + return -EAUTH; +} + +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr) +{ + void * crt; + void * pk; + buffer_t sign; /* Signed region */ + uint8_t * id = peer_hdr->id.data; + + assert(name != NULL); + assert(local_hdr != NULL); + assert(peer_hdr != NULL); + + if (memcmp(peer_hdr->id.data, local_hdr->id.data, OAP_ID_SIZE) != 0) { + log_err_id(id, "OAP ID mismatch in flow allocation."); + goto fail_check; + } + + if (peer_hdr->crt.len == 0) { + log_dbg_id(id, "No crt provided."); + name[0] = '\0'; + return 0; + } + + if (crypt_load_crt_der(peer_hdr->crt, &crt) < 0) { + log_err_id(id, "Failed to load crt."); + goto fail_check; + } + + log_dbg_id(id, "Loaded peer crt."); + + if (crypt_get_pubkey_crt(crt, &pk) < 0) { + log_err_id(id, "Failed to get pubkey from crt."); + goto fail_crt; + } + + log_dbg_id(id, "Got public key from crt."); + + if (auth_verify_crt(oap_auth.ca_ctx, crt) < 0) { + log_err_id(id, "Failed to verify peer with CA store."); + goto fail_crt; + } + + log_dbg_id(id, "Successfully verified peer crt."); + + sign = peer_hdr->hdr; + sign.len -= peer_hdr->sig.len; + + if (auth_verify_sig(pk, peer_hdr->md_nid, sign, peer_hdr->sig) < 0) { + log_err_id(id, "Failed to verify signature."); + goto fail_check_sig; + } + + if (crypt_get_crt_name(crt, name) < 0) { + log_warn_id(id, "Failed to extract name from certificate."); + name[0] = '\0'; + } + + crypt_free_key(pk); + crypt_free_crt(crt); + + log_dbg_id(id, "Successfully authenticated peer."); + + return 0; + + fail_check_sig: + crypt_free_key(pk); + fail_crt: + crypt_free_crt(crt); + fail_check: + return -EAUTH; +} diff --git a/src/irmd/oap/auth.h b/src/irmd/oap/auth.h new file mode 100644 index 00000000..07c33a23 --- /dev/null +++ b/src/irmd/oap/auth.h @@ -0,0 +1,35 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Authentication functions + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_AUTH_H +#define OUROBOROS_IRMD_OAP_AUTH_H + +#include "hdr.h" + +int oap_check_hdr(const struct oap_hdr * hdr); + +/* name is updated with the peer's certificate name if available */ +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr); + +#endif /* OUROBOROS_IRMD_OAP_AUTH_H */ diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c new file mode 100644 index 00000000..12660d7f --- /dev/null +++ b/src/irmd/oap/cli.c @@ -0,0 +1,553 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Client-side processing + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include +#include +#include +#include + +#include "config.h" + +#include "auth.h" +#include "hdr.h" +#include "io.h" +#include "../oap.h" + +#include +#include +#include +#include +#include + +/* Client context between oap_cli_prepare and oap_cli_complete */ +struct oap_cli_ctx { + uint8_t __id[OAP_ID_SIZE]; + buffer_t id; + uint8_t kex_buf[MSGBUFSZ]; + uint8_t req_hash[MAX_HASH_SIZE]; + size_t req_hash_len; + int req_md_nid; + struct sec_config kcfg; + struct oap_hdr local_hdr; + void * pkp; /* Ephemeral keypair */ + uint8_t * key; /* For client-encap KEM */ +}; + +#define OAP_CLI_CTX_INIT(s) \ + do { s->id.len = OAP_ID_SIZE; s->id.data = s->__id; } while (0) + +/* Client-side credential loading, mocked in tests */ + +#ifdef OAP_TEST_MODE +extern int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * buf); +#else + +int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + assert(info != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + return load_credentials(info->name, &info->c, pkp, crt); +} + +int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + assert(info != NULL); + assert(cfg != NULL); + + return load_kex_config(info->name, info->c.enc, cfg); +} + +int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk) +{ + char path[PATH_MAX]; + const char * ext; + + assert(name != NULL); + assert(cfg != NULL); + assert(pk != NULL); + + ext = IS_HYBRID_KEM(cfg->x.str) ? "raw" : "pem"; + + snprintf(path, sizeof(path), + OUROBOROS_CLI_CRT_DIR "/%s/kex.srv.pub.%s", name, ext); + + if (IS_HYBRID_KEM(cfg->x.str)) { + if (crypt_load_pubkey_raw_file(path, pk) < 0) { + log_err("Failed to load %s pubkey from %s.", ext, path); + return -1; + } + } else { + if (crypt_load_pubkey_file_to_der(path, pk) < 0) { + log_err("Failed to load %s pubkey from %s.", ext, path); + return -1; + } + } + + log_dbg("Loaded %s pubkey from %s (%zu bytes).", ext, path, pk->len); + + return 0; +} + +#endif /* OAP_TEST_MODE */ + +static int do_client_kex_prepare_dhe(struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + buffer_t * kex = &s->local_hdr.kex; + uint8_t * id = s->id.data; + ssize_t len; + + /* Generate ephemeral keypair, send PK */ + len = kex_pkp_create(kcfg, &s->pkp, kex->data); + if (len < 0) { + log_err_id(id, "Failed to generate DHE keypair."); + return -ECRYPT; + } + + kex->len = (size_t) len; + log_dbg_id(id, "Generated ephemeral %s keys (%zd bytes).", + kcfg->x.str, len); + + return 0; +} + +static int do_client_kex_prepare_kem_encap(const char * server_name, + struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + buffer_t * kex = &s->local_hdr.kex; + uint8_t * id = s->id.data; + buffer_t server_pk = BUF_INIT; + uint8_t key_buf[SYMMKEYSZ]; + ssize_t len; + + if (load_server_kem_pk(server_name, kcfg, &server_pk) < 0) { + log_err_id(id, "Failed to load server KEM pk."); + return -ECRYPT; + } + + if (IS_HYBRID_KEM(kcfg->x.str)) + len = kex_kem_encap_raw(server_pk, kex->data, + kcfg->k.nid, key_buf); + else + len = kex_kem_encap(server_pk, kex->data, + kcfg->k.nid, key_buf); + + freebuf(server_pk); + + if (len < 0) { + log_err_id(id, "Failed to encapsulate KEM."); + return -ECRYPT; + } + + kex->len = (size_t) len; + log_dbg_id(id, "Client encaps: CT len=%zd.", len); + + /* Store derived key */ + s->key = crypt_secure_malloc(SYMMKEYSZ); + if (s->key == NULL) { + log_err_id(id, "Failed to allocate secure key."); + return -ENOMEM; + } + memcpy(s->key, key_buf, SYMMKEYSZ); + explicit_bzero(key_buf, SYMMKEYSZ); + + return 0; +} + +static int do_client_kex_prepare_kem_decap(struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + buffer_t * kex = &s->local_hdr.kex; + uint8_t * id = s->id.data; + ssize_t len; + + /* Server encaps: generate keypair, send PK */ + len = kex_pkp_create(kcfg, &s->pkp, kex->data); + if (len < 0) { + log_err_id(id, "Failed to generate KEM keypair."); + return -ECRYPT; + } + + kex->len = (size_t) len; + log_dbg_id(id, "Client PK for server encaps (%zd bytes).", len); + + return 0; +} + +static int do_client_kex_prepare(const char * server_name, + struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + + if (!IS_KEX_ALGO_SET(kcfg)) + return 0; + + if (IS_KEM_ALGORITHM(kcfg->x.str)) { + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) + return do_client_kex_prepare_kem_encap(server_name, s); + else + return do_client_kex_prepare_kem_decap(s); + } + + return do_client_kex_prepare_dhe(s); +} + +int oap_cli_prepare(void ** ctx, + const struct name_info * info, + buffer_t * req_buf, + buffer_t data) +{ + struct oap_cli_ctx * s; + void * pkp = NULL; + void * crt = NULL; + ssize_t ret; + + assert(ctx != NULL); + assert(info != NULL); + assert(req_buf != NULL); + + clrbuf(*req_buf); + *ctx = NULL; + + /* Allocate ctx to carry between prepare and complete */ + s = malloc(sizeof(*s)); + if (s == NULL) { + log_err("Failed to allocate OAP client ctx."); + return -ENOMEM; + } + + memset(s, 0, sizeof(*s)); + OAP_CLI_CTX_INIT(s); + + /* Generate session ID */ + if (random_buffer(s->__id, OAP_ID_SIZE) < 0) { + log_err("Failed to generate OAP session ID."); + goto fail_id; + } + + log_dbg_id(s->id.data, "Preparing OAP request for %s.", info->name); + + /* Load client credentials */ + if (load_cli_credentials(info, &pkp, &crt) < 0) { + log_err_id(s->id.data, "Failed to load credentials for %s.", + info->name); + goto fail_id; + } + + /* Load KEX config */ + if (load_cli_kex_config(info, &s->kcfg) < 0) { + log_err_id(s->id.data, "Failed to load KEX config for %s.", + info->name); + goto fail_kex; + } + + log_dbg_id(s->id.data, "KEX config: algo=%s, mode=%s, cipher=%s.", + s->kcfg.x.str != NULL ? s->kcfg.x.str : "none", + s->kcfg.x.mode == KEM_MODE_CLIENT_ENCAP ? "client-encap" : + s->kcfg.x.mode == KEM_MODE_SERVER_ENCAP ? "server-encap" : + "none", + s->kcfg.c.str != NULL ? s->kcfg.c.str : "none"); + + oap_hdr_init(&s->local_hdr, s->id, s->kex_buf, data, s->kcfg.c.nid); + + if (do_client_kex_prepare(info->name, s) < 0) { + log_err_id(s->id.data, "Failed to prepare client KEX."); + goto fail_kex; + } + + if (oap_hdr_encode(&s->local_hdr, pkp, crt, &s->kcfg, + (buffer_t) BUF_INIT, NID_undef)) { + log_err_id(s->id.data, "Failed to create OAP request header."); + goto fail_hdr; + } + + debug_oap_hdr_snd(&s->local_hdr); + + /* Compute and store hash of request for verification in complete */ + s->req_md_nid = s->kcfg.d.nid != NID_undef ? s->kcfg.d.nid : NID_sha384; + ret = md_digest(s->req_md_nid, s->local_hdr.hdr, s->req_hash); + if (ret < 0) { + log_err_id(s->id.data, "Failed to hash request."); + goto fail_hash; + } + s->req_hash_len = (size_t) ret; + + /* Transfer ownership of request buffer */ + *req_buf = s->local_hdr.hdr; + clrbuf(s->local_hdr.hdr); + + crypt_free_crt(crt); + crypt_free_key(pkp); + + *ctx = s; + + log_dbg_id(s->id.data, "OAP request prepared for %s.", info->name); + + return 0; + + fail_hash: + fail_hdr: + crypt_secure_free(s->key, SYMMKEYSZ); + crypt_free_key(s->pkp); + fail_kex: + crypt_free_crt(crt); + crypt_free_key(pkp); + fail_id: + free(s); + return -ECRYPT; +} + +void oap_ctx_free(void * ctx) +{ + struct oap_cli_ctx * s = ctx; + + if (s == NULL) + return; + + oap_hdr_fini(&s->local_hdr); + + if (s->pkp != NULL) + crypt_free_key(s->pkp); + + if (s->key != NULL) + crypt_secure_free(s->key, SYMMKEYSZ); + + memset(s, 0, sizeof(*s)); + free(s); +} + +static int do_client_kex_complete_kem(struct oap_cli_ctx * s, + const struct oap_hdr * peer_hdr, + struct crypt_sk * sk) +{ + struct sec_config * kcfg = &s->kcfg; + uint8_t * id = s->id.data; + uint8_t key_buf[SYMMKEYSZ]; + + if (kcfg->x.mode == KEM_MODE_SERVER_ENCAP) { + buffer_t ct; + + if (peer_hdr->kex.len == 0) { + log_err_id(id, "Server did not send KEM CT."); + return -ECRYPT; + } + + ct.data = peer_hdr->kex.data; + ct.len = peer_hdr->kex.len; + + if (kex_kem_decap(s->pkp, ct, kcfg->k.nid, key_buf) < 0) { + log_err_id(id, "Failed to decapsulate KEM."); + return -ECRYPT; + } + + log_dbg_id(id, "Client decapsulated server CT."); + + } else if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) { + /* Key already derived during prepare */ + memcpy(sk->key, s->key, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, + kcfg->c.str); + return 0; + } + + memcpy(sk->key, key_buf, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + explicit_bzero(key_buf, SYMMKEYSZ); + + log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str); + + return 0; +} + +static int do_client_kex_complete_dhe(struct oap_cli_ctx * s, + const struct oap_hdr * peer_hdr, + struct crypt_sk * sk) +{ + struct sec_config * kcfg = &s->kcfg; + uint8_t * id = s->id.data; + uint8_t key_buf[SYMMKEYSZ]; + + /* DHE: derive from server's public key */ + if (peer_hdr->kex.len == 0) { + log_err_id(id, "Server did not send DHE public key."); + return -ECRYPT; + } + + if (kex_dhe_derive(kcfg, s->pkp, peer_hdr->kex, key_buf) < 0) { + log_err_id(id, "Failed to derive DHE secret."); + return -ECRYPT; + } + + log_dbg_id(id, "DHE: derived shared secret."); + + memcpy(sk->key, key_buf, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + explicit_bzero(key_buf, SYMMKEYSZ); + + log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str); + + return 0; +} + + +static int do_client_kex_complete(struct oap_cli_ctx * s, + const struct oap_hdr * peer_hdr, + struct crypt_sk * sk) +{ + struct sec_config * kcfg = &s->kcfg; + uint8_t * id = s->id.data; + + if (!IS_KEX_ALGO_SET(kcfg)) + return 0; + + /* Accept server's cipher choice */ + if (peer_hdr->cipher_str == NULL) { + log_err_id(id, "Server did not provide cipher."); + return -ECRYPT; + } + + SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str); + if (crypt_validate_nid(kcfg->c.nid) < 0) { + log_err_id(id, "Server cipher '%s' not supported.", + peer_hdr->cipher_str); + return -ENOTSUP; + } + + log_dbg_id(id, "Accepted server cipher %s.", peer_hdr->cipher_str); + + /* Derive shared secret */ + if (IS_KEM_ALGORITHM(kcfg->x.str)) + return do_client_kex_complete_kem(s, peer_hdr, sk); + + return do_client_kex_complete_dhe(s, peer_hdr, sk); +} + +int oap_cli_complete(void * ctx, + const struct name_info * info, + buffer_t rsp_buf, + buffer_t * data, + struct crypt_sk * sk) +{ + struct oap_cli_ctx * s = ctx; + struct oap_hdr peer_hdr; + char peer[NAME_SIZE + 1]; + uint8_t * id; + + assert(ctx != NULL); + assert(info != NULL); + assert(data != NULL); + assert(sk != NULL); + + sk->nid = NID_undef; + + clrbuf(*data); + + memset(&peer_hdr, 0, sizeof(peer_hdr)); + + id = s->id.data; + + log_dbg_id(id, "Completing OAP for %s.", info->name); + + /* Decode response header using client's md_nid for hash length */ + if (oap_hdr_decode(&peer_hdr, rsp_buf, s->req_md_nid) < 0) { + log_err_id(id, "Failed to decode OAP response header."); + goto fail_oap; + } + + debug_oap_hdr_rcv(&peer_hdr); + + /* Verify response ID matches request */ + if (memcmp(peer_hdr.id.data, id, OAP_ID_SIZE) != 0) { + log_err_id(id, "OAP response ID mismatch."); + goto fail_oap; + } + + /* Authenticate server */ + if (oap_auth_peer(peer, &s->local_hdr, &peer_hdr) < 0) { + log_err_id(id, "Failed to authenticate server."); + goto fail_oap; + } + + /* Verify request hash in authenticated response */ + if (peer_hdr.req_hash.len == 0) { + log_err_id(id, "Response missing req_hash."); + goto fail_oap; + } + + if (memcmp(peer_hdr.req_hash.data, s->req_hash, s->req_hash_len) != 0) { + log_err_id(id, "Response req_hash mismatch."); + goto fail_oap; + } + + /* Verify peer certificate name matches expected destination */ + if (peer_hdr.crt.len > 0 && strcmp(peer, info->name) != 0) { + log_err_id(id, "Peer crt for '%s' does not match '%s'.", + peer, info->name); + goto fail_oap; + } + + /* Complete key exchange */ + if (do_client_kex_complete(s, &peer_hdr, sk) < 0) { + log_err_id(id, "Failed to complete key exchange."); + goto fail_oap; + } + + /* Copy piggybacked data from server response */ + if (oap_hdr_copy_data(&peer_hdr, data) < 0) { + log_err_id(id, "Failed to copy server data."); + goto fail_oap; + } + + log_info_id(id, "OAP completed for %s.", info->name); + + oap_ctx_free(s); + + return 0; + + fail_oap: + oap_ctx_free(s); + return -ECRYPT; +} diff --git a/src/irmd/oap/hdr.c b/src/irmd/oap/hdr.c new file mode 100644 index 00000000..cdff7ab6 --- /dev/null +++ b/src/irmd/oap/hdr.c @@ -0,0 +1,456 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Header encoding, decoding, and debugging + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "hdr.h" + +#include +#include +#include +#include +#include + +int oap_hdr_decode(struct oap_hdr * oap_hdr, + buffer_t hdr, + int req_md_nid) +{ + off_t offset; + uint16_t kex_len; + uint16_t ciph_nid; + size_t crt_len; + size_t data_len; + size_t hash_len; + size_t sig_len; + + assert(oap_hdr != NULL); + memset(oap_hdr, 0, sizeof(*oap_hdr)); + + if (hdr.len < OAP_HDR_MIN_SIZE) + goto fail_decode; + + /* Parse fixed header (36 bytes) */ + oap_hdr->id.data = hdr.data; + oap_hdr->id.len = OAP_ID_SIZE; + + offset = OAP_ID_SIZE; + + oap_hdr->timestamp = ntoh64(*(uint64_t *)(hdr.data + offset)); + offset += sizeof(uint64_t); + + /* cipher NID */ + ciph_nid = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->nid = ciph_nid; + oap_hdr->cipher_str = crypt_nid_to_str(ciph_nid); + offset += sizeof(uint16_t); + + /* kdf NID */ + oap_hdr->kdf_nid = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->kdf_str = md_nid_to_str(oap_hdr->kdf_nid); + offset += sizeof(uint16_t); + + /* md NID (signature hash) */ + oap_hdr->md_nid = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->md_str = md_nid_to_str(oap_hdr->md_nid); + offset += sizeof(uint16_t); + + /* Validate NIDs: NID_undef is valid at parse time, else must be known. + * Note: md_nid=NID_undef only valid for PQC; enforced at sign/verify. + */ + if (ciph_nid != NID_undef && crypt_validate_nid(ciph_nid) < 0) + goto fail_decode; + if (oap_hdr->kdf_nid != NID_undef && + md_validate_nid(oap_hdr->kdf_nid) < 0) + goto fail_decode; + if (oap_hdr->md_nid != NID_undef && + md_validate_nid(oap_hdr->md_nid) < 0) + goto fail_decode; + + /* crt_len */ + crt_len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); + offset += sizeof(uint16_t); + + /* kex_len + flags */ + kex_len = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->kex.len = (size_t) (kex_len & OAP_KEX_LEN_MASK); + oap_hdr->kex_flags.fmt = (kex_len & OAP_KEX_FMT_BIT) ? 1 : 0; + oap_hdr->kex_flags.role = (kex_len & OAP_KEX_ROLE_BIT) ? 1 : 0; + offset += sizeof(uint16_t); + + /* data_len */ + data_len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); + offset += sizeof(uint16_t); + + /* Response includes req_hash when md_nid is set */ + hash_len = (req_md_nid != NID_undef) ? + (size_t) md_len(req_md_nid) : 0; + + /* Validate total length */ + if (hdr.len < (size_t) offset + crt_len + oap_hdr->kex.len + + data_len + hash_len) + goto fail_decode; + + /* Derive sig_len from remaining bytes */ + sig_len = hdr.len - offset - crt_len - oap_hdr->kex.len - + data_len - hash_len; + + /* Unsigned packets must not have trailing bytes */ + if (crt_len == 0 && sig_len != 0) + goto fail_decode; + + /* Parse variable fields */ + oap_hdr->crt.data = hdr.data + offset; + oap_hdr->crt.len = crt_len; + offset += crt_len; + + oap_hdr->kex.data = hdr.data + offset; + offset += oap_hdr->kex.len; + + oap_hdr->data.data = hdr.data + offset; + oap_hdr->data.len = data_len; + offset += data_len; + + oap_hdr->req_hash.data = hdr.data + offset; + oap_hdr->req_hash.len = hash_len; + offset += hash_len; + + oap_hdr->sig.data = hdr.data + offset; + oap_hdr->sig.len = sig_len; + + oap_hdr->hdr = hdr; + + return 0; + + fail_decode: + memset(oap_hdr, 0, sizeof(*oap_hdr)); + return -1; +} + +void oap_hdr_fini(struct oap_hdr * oap_hdr) +{ + assert(oap_hdr != NULL); + + freebuf(oap_hdr->hdr); + memset(oap_hdr, 0, sizeof(*oap_hdr)); +} + +int oap_hdr_copy_data(const struct oap_hdr * hdr, + buffer_t * out) +{ + assert(hdr != NULL); + assert(out != NULL); + + if (hdr->data.len == 0) { + clrbuf(*out); + return 0; + } + + out->data = malloc(hdr->data.len); + if (out->data == NULL) + return -ENOMEM; + + memcpy(out->data, hdr->data.data, hdr->data.len); + out->len = hdr->data.len; + + return 0; +} + +void oap_hdr_init(struct oap_hdr * hdr, + buffer_t id, + uint8_t * kex_buf, + buffer_t data, + uint16_t nid) +{ + assert(hdr != NULL); + assert(id.data != NULL && id.len == OAP_ID_SIZE); + + memset(hdr, 0, sizeof(*hdr)); + + hdr->id = id; + hdr->kex.data = kex_buf; + hdr->kex.len = 0; + hdr->data = data; + hdr->nid = nid; +} + +int oap_hdr_encode(struct oap_hdr * hdr, + void * pkp, + void * crt, + struct sec_config * kcfg, + buffer_t req_hash, + int req_md_nid) +{ + struct timespec now; + uint64_t stamp; + buffer_t out; + buffer_t der = BUF_INIT; + buffer_t sig = BUF_INIT; + buffer_t sign; + uint16_t len; + uint16_t ciph_nid; + uint16_t kdf_nid; + uint16_t md_nid; + uint16_t kex_len; + off_t offset; + + assert(hdr != NULL); + assert(hdr->id.data != NULL && hdr->id.len == OAP_ID_SIZE); + assert(kcfg != NULL); + + clock_gettime(CLOCK_REALTIME, &now); + stamp = hton64(TS_TO_UINT64(now)); + + if (crt != NULL && crypt_crt_der(crt, &der) < 0) + goto fail_der; + + ciph_nid = hton16(hdr->nid); + kdf_nid = hton16(kcfg->k.nid); + md_nid = hton16(kcfg->d.nid); + + /* Build kex_len with flags */ + kex_len = (uint16_t) hdr->kex.len; + if (hdr->kex.len > 0 && IS_KEM_ALGORITHM(kcfg->x.str)) { + if (IS_HYBRID_KEM(kcfg->x.str)) + kex_len |= OAP_KEX_FMT_BIT; + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) + kex_len |= OAP_KEX_ROLE_BIT; + } + kex_len = hton16(kex_len); + + /* Fixed header (36 bytes) + variable fields + req_hash (if auth) */ + out.len = OAP_HDR_MIN_SIZE + der.len + hdr->kex.len + hdr->data.len + + req_hash.len; + + out.data = malloc(out.len); + if (out.data == NULL) + goto fail_out; + + offset = 0; + + /* id (16 bytes) */ + memcpy(out.data + offset, hdr->id.data, hdr->id.len); + offset += hdr->id.len; + + /* timestamp (8 bytes) */ + memcpy(out.data + offset, &stamp, sizeof(stamp)); + offset += sizeof(stamp); + + /* cipher_nid (2 bytes) */ + memcpy(out.data + offset, &ciph_nid, sizeof(ciph_nid)); + offset += sizeof(ciph_nid); + + /* kdf_nid (2 bytes) */ + memcpy(out.data + offset, &kdf_nid, sizeof(kdf_nid)); + offset += sizeof(kdf_nid); + + /* md_nid (2 bytes) */ + memcpy(out.data + offset, &md_nid, sizeof(md_nid)); + offset += sizeof(md_nid); + + /* crt_len (2 bytes) */ + len = hton16((uint16_t) der.len); + memcpy(out.data + offset, &len, sizeof(len)); + offset += sizeof(len); + + /* kex_len + flags (2 bytes) */ + memcpy(out.data + offset, &kex_len, sizeof(kex_len)); + offset += sizeof(kex_len); + + /* data_len (2 bytes) */ + len = hton16((uint16_t) hdr->data.len); + memcpy(out.data + offset, &len, sizeof(len)); + offset += sizeof(len); + + /* Fixed header complete (36 bytes) */ + assert((size_t) offset == OAP_HDR_MIN_SIZE); + + /* certificate (variable) */ + if (der.len != 0) + memcpy(out.data + offset, der.data, der.len); + offset += der.len; + + /* kex data (variable) */ + if (hdr->kex.len != 0) + memcpy(out.data + offset, hdr->kex.data, hdr->kex.len); + offset += hdr->kex.len; + + /* data (variable) */ + if (hdr->data.len != 0) + memcpy(out.data + offset, hdr->data.data, hdr->data.len); + offset += hdr->data.len; + + /* req_hash (variable, only for authenticated responses) */ + if (req_hash.len != 0) + memcpy(out.data + offset, req_hash.data, req_hash.len); + offset += req_hash.len; + + assert((size_t) offset == out.len); + + /* Sign the entire header (fixed + variable, excluding signature) */ + sign.data = out.data; + sign.len = out.len; + + if (pkp != NULL && auth_sign(pkp, kcfg->d.nid, sign, &sig) < 0) + goto fail_sig; + + hdr->hdr = out; + + /* Append signature */ + if (sig.len > 0) { + hdr->hdr.len += sig.len; + hdr->hdr.data = realloc(out.data, hdr->hdr.len); + if (hdr->hdr.data == NULL) + goto fail_realloc; + + memcpy(hdr->hdr.data + offset, sig.data, sig.len); + clrbuf(out); + } + + if (oap_hdr_decode(hdr, hdr->hdr, req_md_nid) < 0) + goto fail_decode; + + freebuf(der); + freebuf(sig); + + return 0; + + fail_decode: + oap_hdr_fini(hdr); + fail_realloc: + freebuf(sig); + fail_sig: + freebuf(out); + fail_out: + freebuf(der); + fail_der: + 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(" crt: [%zu bytes]", hdr->crt.len); + else + log_proto(" crt: "); + + if (hdr->kex.len > 0) + log_proto(" Key Exchange Data: [%zu bytes] [%s]", + hdr->kex.len, hdr->kex_flags.role ? + "Client encaps" : "Server encaps"); + else + log_proto(" Ephemeral Public Key: "); + + if (hdr->cipher_str != NULL) + log_proto(" Cipher: %s", hdr->cipher_str); + else + log_proto(" Cipher: "); + + if (hdr->kdf_str != NULL) + log_proto(" KDF: HKDF-%s", hdr->kdf_str); + else + log_proto(" KDF: "); + + if (hdr->md_str != NULL) + log_proto(" Digest: %s", hdr->md_str); + else + log_proto(" Digest: "); + + if (hdr->data.len > 0) + log_proto(" Data: [%zu bytes]", hdr->data.len); + else + log_proto(" Data: "); + + if (hdr->req_hash.len > 0) + log_proto(" Req Hash: [%zu bytes]", hdr->req_hash.len); + else + log_proto(" Req Hash: "); + + if (hdr->sig.len > 0) + log_proto(" Signature: [%zu bytes]", hdr->sig.len); + else + log_proto(" Signature: "); +} +#endif + +void debug_oap_hdr_rcv(const struct oap_hdr * hdr) +{ +#ifdef DEBUG_PROTO_OAP + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + time_t stamp; + + assert(hdr); + + 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); +#else + (void) hdr; +#endif +} + +void debug_oap_hdr_snd(const struct oap_hdr * hdr) +{ +#ifdef DEBUG_PROTO_OAP + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + time_t stamp; + + assert(hdr); + + 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); +#else + (void) hdr; +#endif +} diff --git a/src/irmd/oap/hdr.h b/src/irmd/oap/hdr.h new file mode 100644 index 00000000..f603b169 --- /dev/null +++ b/src/irmd/oap/hdr.h @@ -0,0 +1,159 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Header definitions and functions + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_HDR_H +#define OUROBOROS_IRMD_OAP_HDR_H + +#include +#include + +#include +#include + +#define OAP_ID_SIZE (16) +#define OAP_HDR_MIN_SIZE (OAP_ID_SIZE + sizeof(uint64_t) + 6 * sizeof(uint16_t)) + +#define OAP_KEX_FMT_BIT 0x8000 /* bit 15: 0=X.509 DER, 1=Raw */ +#define OAP_KEX_ROLE_BIT 0x4000 /* bit 14: 0=Server encaps, 1=Client encaps */ +#define OAP_KEX_LEN_MASK 0x3FFF /* bits 0-13: Length (0-16383 bytes) */ + +#define OAP_KEX_ROLE(hdr) (hdr->kex_flags.role) +#define OAP_KEX_FMT(hdr) (hdr->kex_flags.fmt) + +#define OAP_KEX_IS_X509_FMT(hdr) (((hdr)->kex_flags.fmt) == 0) +#define OAP_KEX_IS_RAW_FMT(hdr) (((hdr)->kex_flags.fmt) == 1) + +/* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---+ + * | | | + * + + | + * | | | + * + id (128 bits) + | + * | Unique flow allocation ID | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + timestamp (64 bits) + | + * | UTC nanoseconds since epoch | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | cipher_nid (16 bits) | kdf_nid (16 bits) | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | md_nid (16 bits) | crt_len (16 bits) | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * |F|R| kex_len (14 bits) | data_len (16 bits) | | Signed + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Region + * | | | + * + certificate (variable) + | + * | X.509 certificate, DER encoded | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + kex_data (variable) + | + * | public key (DER/raw) or ciphertext (KEM) | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + data (variable) + | + * | Piggybacked application data | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + req_hash (variable, response only) + | + * | H(request) using req md_nid / sha384 | | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---+ + * | | + * + signature (variable) + + * | DSA signature over signed region | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * cipher_nid: NID value for symmetric cipher (0 = none) + * kdf_nid: NID value for KDF function (0 = none) + * md_nid: NID value for signature hash (0 = PQC/no signature) + * + * kex_len field bit layout: + * F (bit 15): Format - 0 = X.509 DER, 1 = Raw/Hybrid + * R (bit 14): Role - 0 = Server encaps, 1 = Client encaps + * (R is ignored for non-KEM algorithms) + * Bits 0-13: Length (0-16383 bytes) + * + * Request: sig_len = total - 36 - crt_len - kex_len - data_len + * Response: sig_len = total - 36 - crt_len - kex_len - data_len - hash_len + * where hash_len = md_len(req_md_nid / sha384) + */ + +/* Parsed OAP header - buffers pointing to a single memory region */ +struct oap_hdr { + const char * cipher_str; + const char * kdf_str; + const char * md_str; + uint64_t timestamp; + uint16_t nid; + uint16_t kdf_nid; + uint16_t md_nid; + struct { + bool fmt; /* Format */ + bool role; /* Role */ + } kex_flags; + buffer_t id; + buffer_t crt; + buffer_t kex; + buffer_t data; + buffer_t req_hash; /* H(request) - response only */ + buffer_t sig; + buffer_t hdr; +}; + + +void oap_hdr_init(struct oap_hdr * hdr, + buffer_t id, + uint8_t * kex_buf, + buffer_t data, + uint16_t nid); + +void oap_hdr_fini(struct oap_hdr * oap_hdr); + +int oap_hdr_encode(struct oap_hdr * hdr, + void * pkp, + void * crt, + struct sec_config * kcfg, + buffer_t req_hash, + int req_md_nid); + +int oap_hdr_decode(struct oap_hdr * hdr, + buffer_t buf, + int req_md_nid); + +void debug_oap_hdr_rcv(const struct oap_hdr * hdr); + +void debug_oap_hdr_snd(const struct oap_hdr * hdr); + +int oap_hdr_copy_data(const struct oap_hdr * hdr, + buffer_t * out); + +#endif /* OUROBOROS_IRMD_OAP_HDR_H */ diff --git a/src/irmd/oap/internal.h b/src/irmd/oap/internal.h new file mode 100644 index 00000000..8363e3a2 --- /dev/null +++ b/src/irmd/oap/internal.h @@ -0,0 +1,133 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP internal definitions + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_INTERNAL_H +#define OUROBOROS_IRMD_OAP_INTERNAL_H + +#include +#include +#include +#include +#include + +#include "hdr.h" + +#include +#include + +/* + * Authentication functions (auth.c) + */ +int oap_check_hdr(const struct oap_hdr * hdr); + +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr); + +/* + * Key exchange functions (kex.c) + */ +int oap_negotiate_cipher(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg); + +/* + * Credential loading (oap.c) - shared between client and server + */ +#ifndef OAP_TEST_MODE +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt); + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg); +#endif + +/* + * Server functions (srv.c) + */ +#ifndef OAP_TEST_MODE +int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); + +int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); + +int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp); +#else +extern int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp); +#endif + +int do_server_kex(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk); + +/* + * Client functions (cli.c) + */ +#ifndef OAP_TEST_MODE +int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); + +int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); + +int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk); +#else +extern int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk); +#endif + +int oap_client_kex_prepare(struct sec_config * kcfg, + buffer_t server_pk, + buffer_t * kex, + uint8_t * key, + void ** ephemeral_pkp); + +int oap_client_kex_complete(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + void * pkp, + uint8_t * key); + +#endif /* OUROBOROS_IRMD_OAP_INTERNAL_H */ diff --git a/src/irmd/oap/io.c b/src/irmd/oap/io.c new file mode 100644 index 00000000..e4189d4d --- /dev/null +++ b/src/irmd/oap/io.c @@ -0,0 +1,132 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - File I/O for credentials and configuration + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include +#include +#include + +#include "config.h" + +#include "io.h" + +#include +#include +#include + +/* + * Shared credential and configuration loading helpers + */ + +#ifndef OAP_TEST_MODE + +static bool file_exists(const char * path) +{ + struct stat s; + + if (stat(path, &s) < 0 && errno == ENOENT) { + log_dbg("File %s does not exist.", path); + return false; + } + + return true; +} + +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt) +{ + assert(paths != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + *pkp = NULL; + *crt = NULL; + + if (!file_exists(paths->crt) || !file_exists(paths->key)) { + log_info("No authentication certificates for %s.", name); + return 0; + } + + if (crypt_load_crt_file(paths->crt, crt) < 0) { + log_err("Failed to load %s for %s.", paths->crt, name); + goto fail_crt; + } + + if (crypt_load_privkey_file(paths->key, pkp) < 0) { + log_err("Failed to load %s for %s.", paths->key, name); + goto fail_key; + } + + log_info("Loaded authentication certificates for %s.", name); + + return 0; + + fail_key: + crypt_free_crt(*crt); + *crt = NULL; + fail_crt: + return -EAUTH; +} + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg) +{ + assert(name != NULL); + assert(cfg != NULL); + + memset(cfg, 0, sizeof(*cfg)); + + /* Load encryption config */ + if (!file_exists(path)) + log_dbg("No encryption %s for %s.", path, name); + + if (load_sec_config_file(cfg, path) < 0) { + log_warn("Failed to load %s for %s.", path, name); + return -1; + } + + if (!IS_KEX_ALGO_SET(cfg)) { + log_info("Key exchange not configured for %s.", name); + return 0; + } + + 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_info("Encryption enabled for %s.", name); + + return 0; +} + +#endif /* OAP_TEST_MODE */ diff --git a/src/irmd/oap/io.h b/src/irmd/oap/io.h new file mode 100644 index 00000000..a31ddf85 --- /dev/null +++ b/src/irmd/oap/io.h @@ -0,0 +1,40 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Credential and configuration file I/O + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_IO_H +#define OUROBOROS_IRMD_OAP_IO_H + +#include +#include + +#ifndef OAP_TEST_MODE +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt); + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg); +#endif + +#endif /* OUROBOROS_IRMD_OAP_IO_H */ diff --git a/src/irmd/oap/srv.c b/src/irmd/oap/srv.c new file mode 100644 index 00000000..c5a4453f --- /dev/null +++ b/src/irmd/oap/srv.c @@ -0,0 +1,462 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Server-side processing + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include +#include +#include + +#include "config.h" + +#include "auth.h" +#include "hdr.h" +#include "io.h" +#include "oap.h" + +#include +#include +#include +#include +#include + +#ifdef OAP_TEST_MODE +extern int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_keypair(const char * name, + bool raw_fmt, + void ** pkp); +#else + +int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + assert(info != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + return load_credentials(info->name, &info->s, pkp, crt); +} + +int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + assert(info != NULL); + assert(cfg != NULL); + + return load_kex_config(info->name, info->s.enc, cfg); +} + +int load_server_kem_keypair(const char * name, + bool raw_fmt, + void ** pkp) +{ + char path[PATH_MAX]; + const char * ext; + + assert(name != NULL); + assert(pkp != NULL); + + ext = raw_fmt ? "raw" : "pem"; + + snprintf(path, sizeof(path), + OUROBOROS_SRV_CRT_DIR "/%s/kex.key.%s", name, ext); + + if (raw_fmt) { + if (crypt_load_privkey_raw_file(path, pkp) < 0) { + log_err("Failed to load %s keypair from %s.", + ext, path); + return -ECRYPT; + } + } else { + if (crypt_load_privkey_file(path, pkp) < 0) { + log_err("Failed to load %s keypair from %s.", + ext, path); + return -ECRYPT; + } + } + + log_dbg("Loaded server KEM keypair from %s.", path); + return 0; +} + +#endif /* OAP_TEST_MODE */ + +static int get_algo_from_peer_key(const struct oap_hdr * peer_hdr, + char * algo_buf) +{ + uint8_t * id = peer_hdr->id.data; + int ret; + + if (OAP_KEX_IS_RAW_FMT(peer_hdr)) { + ret = kex_get_algo_from_pk_raw(peer_hdr->kex, algo_buf); + if (ret < 0) { + log_err_id(id, "Failed to get algo from raw key."); + return -ECRYPT; + } + } else { + ret = kex_get_algo_from_pk_der(peer_hdr->kex, algo_buf); + if (ret < 0) { + log_err_id(id, "Failed to get algo from DER key."); + return -ECRYPT; + } + } + + return 0; +} + +static int negotiate_kex(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg) +{ + uint8_t * id = peer_hdr->id.data; + + if (kcfg->c.nid == NID_undef) { + if (peer_hdr->cipher_str != NULL) { + SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str); + if (kcfg->c.nid == NID_undef) { + log_err_id(id, "Unsupported cipher '%s'.", + peer_hdr->cipher_str); + return -ENOTSUP; + } + log_dbg_id(id, "Peer requested cipher %s.", + peer_hdr->cipher_str); + } else { + log_err_id(id, "Encryption requested, no cipher."); + return -ECRYPT; + } + } else { + log_dbg_id(id, "Using local cipher %s.", kcfg->c.str); + } + + /* Negotiate KDF - server overrides client if configured */ + if (kcfg->k.nid != NID_undef) { + log_dbg_id(id, "Using local KDF %s.", + md_nid_to_str(kcfg->k.nid)); + } else if (peer_hdr->kdf_nid != NID_undef) { + if (md_validate_nid(peer_hdr->kdf_nid) == 0) { + kcfg->k.nid = peer_hdr->kdf_nid; + log_dbg_id(id, "Using peer KDF %s.", + md_nid_to_str(peer_hdr->kdf_nid)); + } else { + log_err_id(id, "Unsupported KDF NID %d.", + peer_hdr->kdf_nid); + return -ENOTSUP; + } + } + + if (IS_KEX_ALGO_SET(kcfg)) + log_info_id(id, "Negotiated %s + %s.", + kcfg->x.str, kcfg->c.str); + else + log_info_id(id, "No key exchange."); + + return 0; +} + +static int do_server_kem_decap(const struct name_info * info, + const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + struct crypt_sk * sk) +{ + buffer_t ct; + void * server_pkp = NULL; + int ret; + uint8_t * id = peer_hdr->id.data; + + ret = load_server_kem_keypair(info->name, + peer_hdr->kex_flags.fmt, + &server_pkp); + if (ret < 0) + return ret; + + ct.data = peer_hdr->kex.data; + ct.len = peer_hdr->kex.len; + + ret = kex_kem_decap(server_pkp, ct, kcfg->k.nid, sk->key); + + crypt_free_key(server_pkp); + + if (ret < 0) { + log_err_id(id, "Failed to decapsulate KEM."); + return -ECRYPT; + } + + log_dbg_id(id, "Client encaps: decapsulated CT."); + + return 0; +} + +static int do_server_kem_encap(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + buffer_t client_pk; + ssize_t ct_len; + uint8_t * id = peer_hdr->id.data; + + client_pk.data = peer_hdr->kex.data; + client_pk.len = peer_hdr->kex.len; + + if (IS_HYBRID_KEM(kcfg->x.str)) + ct_len = kex_kem_encap_raw(client_pk, kex->data, + kcfg->k.nid, sk->key); + else + ct_len = kex_kem_encap(client_pk, kex->data, + kcfg->k.nid, sk->key); + + if (ct_len < 0) { + log_err_id(id, "Failed to encapsulate KEM."); + return -ECRYPT; + } + + kex->len = (size_t) ct_len; + + log_dbg_id(id, "Server encaps: generated CT, len=%zd.", ct_len); + + return 0; +} + +static int do_server_kex_kem(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + int ret; + + kcfg->x.mode = peer_hdr->kex_flags.role; + + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) { + ret = do_server_kem_decap(info, peer_hdr, kcfg, sk); + kex->len = 0; + } else { + ret = do_server_kem_encap(peer_hdr, kcfg, kex, sk); + } + + return ret; +} + +static int do_server_kex_dhe(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + ssize_t key_len; + void * epkp; + int ret; + uint8_t * id = peer_hdr->id.data; + + key_len = kex_pkp_create(kcfg, &epkp, kex->data); + if (key_len < 0) { + log_err_id(id, "Failed to generate key pair."); + return -ECRYPT; + } + + kex->len = (size_t) key_len; + + log_dbg_id(id, "Generated %s ephemeral keys.", kcfg->x.str); + + ret = kex_dhe_derive(kcfg, epkp, peer_hdr->kex, sk->key); + if (ret < 0) { + log_err_id(id, "Failed to derive secret."); + kex_pkp_destroy(epkp); + return -ECRYPT; + } + + kex_pkp_destroy(epkp); + + return 0; +} + +int do_server_kex(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + char algo_buf[KEX_ALGO_BUFSZ]; + uint8_t * id; + + id = peer_hdr->id.data; + + /* No KEX data from client */ + if (peer_hdr->kex.len == 0) { + if (IS_KEX_ALGO_SET(kcfg)) { + log_warn_id(id, "KEX requested without info."); + return -ECRYPT; + } + return 0; + } + + if (negotiate_kex(peer_hdr, kcfg) < 0) + return -ECRYPT; + + if (OAP_KEX_ROLE(peer_hdr) != KEM_MODE_CLIENT_ENCAP) { + /* Server encapsulation or DHE: extract algo from DER PK */ + if (get_algo_from_peer_key(peer_hdr, algo_buf) < 0) + return -ECRYPT; + + SET_KEX_ALGO(kcfg, algo_buf); + } + + /* Dispatch based on algorithm type */ + if (IS_KEM_ALGORITHM(kcfg->x.str)) + return do_server_kex_kem(info, peer_hdr, kcfg, kex, sk); + else + return do_server_kex_dhe(peer_hdr, kcfg, kex, sk); +} + +int oap_srv_process(const struct name_info * info, + buffer_t req_buf, + buffer_t * rsp_buf, + buffer_t * data, + struct crypt_sk * sk) +{ + struct oap_hdr peer_hdr; + struct oap_hdr local_hdr; + struct sec_config kcfg; + uint8_t kex_buf[MSGBUFSZ]; + uint8_t hash_buf[MAX_HASH_SIZE]; + buffer_t req_hash = BUF_INIT; + ssize_t hash_ret; + char cli_name[NAME_SIZE + 1]; /* TODO */ + uint8_t * id; + void * pkp = NULL; + void * crt = NULL; + int req_md_nid; + + assert(info != NULL); + assert(rsp_buf != NULL); + assert(data != NULL); + assert(sk != NULL); + + sk->nid = NID_undef; + + memset(&peer_hdr, 0, sizeof(peer_hdr)); + memset(&local_hdr, 0, sizeof(local_hdr)); + clrbuf(*rsp_buf); + + log_dbg("Processing OAP request for %s.", info->name); + + /* Load server credentials */ + if (load_srv_credentials(info, &pkp, &crt) < 0) { + log_err("Failed to load security keys for %s.", info->name); + goto fail_cred; + } + + /* Load KEX config */ + if (load_srv_kex_config(info, &kcfg) < 0) { + log_err("Failed to load KEX config for %s.", info->name); + goto fail_kex; + } + + sk->nid = kcfg.c.nid; + + /* Decode incoming header (NID_undef = request, no hash) */ + if (oap_hdr_decode(&peer_hdr, req_buf, NID_undef) < 0) { + log_err("Failed to decode OAP header."); + goto fail_auth; + } + + debug_oap_hdr_rcv(&peer_hdr); + + id = peer_hdr.id.data; /* Logging */ + + /* Check for replay */ + if (oap_check_hdr(&peer_hdr) < 0) { + log_err_id(id, "OAP header failed replay check."); + goto fail_auth; + } + + /* Authenticate client before processing KEX data */ + oap_hdr_init(&local_hdr, peer_hdr.id, kex_buf, *data, NID_undef); + + if (oap_auth_peer(cli_name, &local_hdr, &peer_hdr) < 0) { + log_err_id(id, "Failed to authenticate client."); + goto fail_auth; + } + + if (do_server_kex(info, &peer_hdr, &kcfg, &local_hdr.kex, sk) < 0) + goto fail_kex; + + /* Build response header with hash of client request */ + local_hdr.nid = sk->nid; + + /* Use client's md_nid, defaulting to SHA-384 for PQC */ + req_md_nid = peer_hdr.md_nid != NID_undef ? peer_hdr.md_nid : NID_sha384; + + /* Compute request hash using client's md_nid */ + hash_ret = md_digest(req_md_nid, req_buf, hash_buf); + if (hash_ret < 0) { + log_err_id(id, "Failed to hash request."); + goto fail_auth; + } + req_hash.data = hash_buf; + req_hash.len = (size_t) hash_ret; + + if (oap_hdr_encode(&local_hdr, pkp, crt, &kcfg, + req_hash, req_md_nid) < 0) { + log_err_id(id, "Failed to create OAP response header."); + goto fail_auth; + } + + debug_oap_hdr_snd(&local_hdr); + + if (oap_hdr_copy_data(&peer_hdr, data) < 0) { + log_err_id(id, "Failed to copy client data."); + goto fail_data; + } + + /* Transfer ownership of response buffer */ + *rsp_buf = local_hdr.hdr; + + log_info_id(id, "OAP request processed for %s.", info->name); + + crypt_free_crt(crt); + crypt_free_key(pkp); + + return 0; + + fail_data: + oap_hdr_fini(&local_hdr); + fail_auth: + crypt_free_crt(crt); + crypt_free_key(pkp); + fail_cred: + return -EAUTH; + + fail_kex: + crypt_free_crt(crt); + crypt_free_key(pkp); + return -ECRYPT; +} diff --git a/src/irmd/oap/tests/CMakeLists.txt b/src/irmd/oap/tests/CMakeLists.txt new file mode 100644 index 00000000..861b3590 --- /dev/null +++ b/src/irmd/oap/tests/CMakeLists.txt @@ -0,0 +1,78 @@ +get_filename_component(tmp ".." ABSOLUTE) +get_filename_component(src_folder "${tmp}" NAME) + +compute_test_prefix() + +create_test_sourcelist(${src_folder}_tests test_suite.c + # Add new tests here + oap_test.c +) + +create_test_sourcelist(${src_folder}_pqc_tests test_suite_pqc.c + # PQC-specific tests + oap_test_pqc.c +) + +# OAP test needs io.c compiled with OAP_TEST_MODE +set(OAP_TEST_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/../io.c + ${CMAKE_CURRENT_SOURCE_DIR}/../hdr.c + ${CMAKE_CURRENT_SOURCE_DIR}/../auth.c + ${CMAKE_CURRENT_SOURCE_DIR}/../srv.c + ${CMAKE_CURRENT_SOURCE_DIR}/../cli.c + ${CMAKE_CURRENT_SOURCE_DIR}/common.c +) + +# Regular test executable (ECDSA) +add_executable(${src_folder}_test ${${src_folder}_tests} ${OAP_TEST_SOURCES}) +set_source_files_properties(${OAP_TEST_SOURCES} + PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" +) +target_link_libraries(${src_folder}_test ouroboros-irm) +target_include_directories(${src_folder}_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR}/../.. +) + +# PQC test executable (ML-DSA) +add_executable(${src_folder}_pqc_test ${${src_folder}_pqc_tests} ${OAP_TEST_SOURCES}) +set_source_files_properties(${OAP_TEST_SOURCES} + TARGET_DIRECTORY ${src_folder}_pqc_test + PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" +) +target_link_libraries(${src_folder}_pqc_test ouroboros-irm) +target_include_directories(${src_folder}_pqc_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../.. + ${CMAKE_CURRENT_BINARY_DIR}/../.. +) + +add_dependencies(build_tests ${src_folder}_test ${src_folder}_pqc_test) + +# Regular tests +set(tests_to_run ${${src_folder}_tests}) +if(CMAKE_VERSION VERSION_LESS "3.29.0") + remove(tests_to_run test_suite.c) +else () + list(POP_FRONT tests_to_run) +endif() + +foreach(test ${tests_to_run}) + get_filename_component(test_name ${test} NAME_WE) + add_test(${TEST_PREFIX}/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${src_folder}_test ${test_name}) +endforeach(test) + +# PQC tests +set(pqc_tests_to_run ${${src_folder}_pqc_tests}) +if(CMAKE_VERSION VERSION_LESS "3.29.0") + remove(pqc_tests_to_run test_suite_pqc.c) +else () + list(POP_FRONT pqc_tests_to_run) +endif() + +foreach(test ${pqc_tests_to_run}) + get_filename_component(test_name ${test} NAME_WE) + add_test(${TEST_PREFIX}/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${src_folder}_pqc_test ${test_name}) +endforeach(test) + +set_property(TEST ${TEST_PREFIX}/oap_test PROPERTY SKIP_RETURN_CODE 1) +set_property(TEST ${TEST_PREFIX}/oap_test_pqc PROPERTY SKIP_RETURN_CODE 1) diff --git a/src/irmd/oap/tests/common.c b/src/irmd/oap/tests/common.c new file mode 100644 index 00000000..0a1af100 --- /dev/null +++ b/src/irmd/oap/tests/common.c @@ -0,0 +1,457 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Common test helper functions for OAP tests + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "common.h" + +#include + +#include "oap.h" + +#include +#include + +int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + (void) info; + + memset(cfg, 0, sizeof(*cfg)); + + if (test_cfg.srv.kex == NID_undef) + return 0; + + SET_KEX_ALGO_NID(cfg, test_cfg.srv.kex); + SET_KEX_CIPHER_NID(cfg, test_cfg.srv.cipher); + SET_KEX_KDF_NID(cfg, test_cfg.srv.kdf); + SET_KEX_DIGEST_NID(cfg, test_cfg.srv.md); + SET_KEX_KEM_MODE(cfg, test_cfg.srv.kem_mode); + + return 0; +} + +int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + (void) info; + + memset(cfg, 0, sizeof(*cfg)); + + if (test_cfg.cli.kex == NID_undef) + return 0; + + SET_KEX_ALGO_NID(cfg, test_cfg.cli.kex); + SET_KEX_CIPHER_NID(cfg, test_cfg.cli.cipher); + SET_KEX_KDF_NID(cfg, test_cfg.cli.kdf); + SET_KEX_DIGEST_NID(cfg, test_cfg.cli.md); + SET_KEX_KEM_MODE(cfg, test_cfg.cli.kem_mode); + + return 0; +} + +int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + (void) info; + + *pkp = NULL; + *crt = NULL; + + if (!test_cfg.srv.auth) + return 0; + + return mock_load_credentials(pkp, crt); +} + +int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + (void) info; + + *pkp = NULL; + *crt = NULL; + + if (!test_cfg.cli.auth) + return 0; + + return mock_load_credentials(pkp, crt); +} + +int oap_test_setup(struct oap_test_ctx * ctx, + const char * root_ca_str, + const char * im_ca_str) +{ + memset(ctx, 0, sizeof(*ctx)); + + strcpy(ctx->srv.info.name, "test-1.unittest.o7s"); + strcpy(ctx->cli.info.name, "test-1.unittest.o7s"); + + if (oap_auth_init() < 0) { + printf("Failed to init OAP.\n"); + goto fail_init; + } + + if (crypt_load_crt_str(root_ca_str, &ctx->root_ca) < 0) { + printf("Failed to load root CA cert.\n"); + goto fail_root_ca; + } + + if (crypt_load_crt_str(im_ca_str, &ctx->im_ca) < 0) { + printf("Failed to load intermediate CA cert.\n"); + goto fail_im_ca; + } + + if (oap_auth_add_ca_crt(ctx->root_ca) < 0) { + printf("Failed to add root CA cert to store.\n"); + goto fail_add_ca; + } + + if (oap_auth_add_ca_crt(ctx->im_ca) < 0) { + printf("Failed to add intermediate CA cert to store.\n"); + goto fail_add_ca; + } + + return 0; + + fail_add_ca: + crypt_free_crt(ctx->im_ca); + fail_im_ca: + crypt_free_crt(ctx->root_ca); + fail_root_ca: + oap_auth_fini(); + fail_init: + memset(ctx, 0, sizeof(*ctx)); + return -1; +} + +void oap_test_teardown(struct oap_test_ctx * ctx) +{ + struct crypt_sk res; + buffer_t dummy = BUF_INIT; + + if (ctx->cli.state != NULL) { + res.key = ctx->cli.key; + oap_cli_complete(ctx->cli.state, &ctx->cli.info, dummy, + &ctx->data, &res); + ctx->cli.state = NULL; + } + + freebuf(ctx->data); + freebuf(ctx->resp_hdr); + freebuf(ctx->req_hdr); + + crypt_free_crt(ctx->im_ca); + crypt_free_crt(ctx->root_ca); + + oap_auth_fini(); + memset(ctx, 0, sizeof(*ctx)); +} + +int oap_cli_prepare_ctx(struct oap_test_ctx * ctx) +{ + return oap_cli_prepare(&ctx->cli.state, &ctx->cli.info, &ctx->req_hdr, + ctx->data); +} + +int oap_srv_process_ctx(struct oap_test_ctx * ctx) +{ + struct crypt_sk res = { .nid = NID_undef, .key = ctx->srv.key }; + int ret; + + ret = oap_srv_process(&ctx->srv.info, ctx->req_hdr, + &ctx->resp_hdr, &ctx->data, &res); + if (ret == 0) + ctx->srv.nid = res.nid; + + return ret; +} + +int oap_cli_complete_ctx(struct oap_test_ctx * ctx) +{ + struct crypt_sk res = { .nid = NID_undef, .key = ctx->cli.key }; + int ret; + + ret = oap_cli_complete(ctx->cli.state, &ctx->cli.info, ctx->resp_hdr, + &ctx->data, &res); + ctx->cli.state = NULL; + + if (ret == 0) + ctx->cli.nid = res.nid; + + return ret; +} + +int roundtrip_auth_only(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (ctx.cli.nid != NID_undef || ctx.srv.nid != NID_undef) { + printf("Cipher should not be set for auth-only.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int roundtrip_kex_only(void) +{ + struct name_info cli_info; + struct name_info srv_info; + struct crypt_sk res; + uint8_t cli_key[SYMMKEYSZ]; + uint8_t srv_key[SYMMKEYSZ]; + int cli_nid; + int srv_nid; + buffer_t req_hdr = BUF_INIT; + buffer_t resp_hdr = BUF_INIT; + buffer_t data = BUF_INIT; + void * cli_state = NULL; + + TEST_START(); + + memset(&cli_info, 0, sizeof(cli_info)); + memset(&srv_info, 0, sizeof(srv_info)); + + strcpy(cli_info.name, "test-1.unittest.o7s"); + strcpy(srv_info.name, "test-1.unittest.o7s"); + + if (oap_auth_init() < 0) { + printf("Failed to init OAP.\n"); + goto fail; + } + + if (oap_cli_prepare(&cli_state, &cli_info, &req_hdr, + data) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + res.key = srv_key; + + if (oap_srv_process(&srv_info, req_hdr, &resp_hdr, &data, &res) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + srv_nid = res.nid; + + res.key = cli_key; + + if (oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res) < 0) { + printf("Client complete failed.\n"); + cli_state = NULL; + goto fail_cleanup; + } + + cli_nid = res.nid; + cli_state = NULL; + + if (memcmp(cli_key, srv_key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + if (cli_nid == NID_undef || srv_nid == NID_undef) { + printf("Cipher should be set for kex-only.\n"); + goto fail_cleanup; + } + + freebuf(resp_hdr); + freebuf(req_hdr); + oap_auth_fini(); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + if (cli_state != NULL) { + res.key = cli_key; + oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res); + } + freebuf(resp_hdr); + freebuf(req_hdr); + oap_auth_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int corrupted_request(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Corrupt the request */ + if (ctx.req_hdr.len > 100) { + ctx.req_hdr.data[50] ^= 0xFF; + ctx.req_hdr.data[51] ^= 0xAA; + ctx.req_hdr.data[52] ^= 0x55; + } + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject corrupted request.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int corrupted_response(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + struct crypt_sk res; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + /* Corrupt the response */ + if (ctx.resp_hdr.len > 100) { + ctx.resp_hdr.data[50] ^= 0xFF; + ctx.resp_hdr.data[51] ^= 0xAA; + ctx.resp_hdr.data[52] ^= 0x55; + } + + res.key = ctx.cli.key; + + if (oap_cli_complete(ctx.cli.state, &ctx.cli.info, ctx.resp_hdr, + &ctx.data, &res) == 0) { + printf("Client should reject corrupted response.\n"); + ctx.cli.state = NULL; + goto fail_cleanup; + } + + ctx.cli.state = NULL; + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int truncated_request(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + size_t orig_len; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Truncate the request buffer */ + orig_len = ctx.req_hdr.len; + ctx.req_hdr.len = orig_len / 2; + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject truncated request.\n"); + ctx.req_hdr.len = orig_len; + goto fail_cleanup; + } + + ctx.req_hdr.len = orig_len; + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} diff --git a/src/irmd/oap/tests/common.h b/src/irmd/oap/tests/common.h new file mode 100644 index 00000000..d4b6733a --- /dev/null +++ b/src/irmd/oap/tests/common.h @@ -0,0 +1,100 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Common test helper functions for OAP tests + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef IRMD_TESTS_COMMON_H +#define IRMD_TESTS_COMMON_H + +#include +#include +#include +#include + +#include + +/* Per-side security configuration for tests */ +struct test_sec_cfg { + int kex; /* KEX algorithm NID */ + int cipher; /* Cipher NID for encryption */ + int kdf; /* KDF NID for key derivation */ + int md; /* Digest NID for signatures */ + int kem_mode; /* KEM encapsulation mode (0 for ECDH) */ + bool auth; /* Use authentication (certificates) */ +}; + +/* Test configuration - set by each test before running roundtrip */ +extern struct test_cfg { + struct test_sec_cfg srv; + struct test_sec_cfg cli; +} test_cfg; + +/* Each test file defines this with its own certificates */ +extern int mock_load_credentials(void ** pkp, + void ** crt); + +/* Per-side test context */ +struct oap_test_side { + struct name_info info; + struct flow_info flow; + uint8_t key[SYMMKEYSZ]; + int nid; + void * state; +}; + +/* Test context - holds all common state for OAP tests */ +struct oap_test_ctx { + struct oap_test_side srv; + struct oap_test_side cli; + + buffer_t req_hdr; + buffer_t resp_hdr; + buffer_t data; + void * root_ca; + void * im_ca; +}; + +int oap_test_setup(struct oap_test_ctx * ctx, + const char * root_ca_str, + const char * im_ca_str); + +void oap_test_teardown(struct oap_test_ctx * ctx); + +int oap_cli_prepare_ctx(struct oap_test_ctx * ctx); + +int oap_srv_process_ctx(struct oap_test_ctx * ctx); + +int oap_cli_complete_ctx(struct oap_test_ctx * ctx); + +int roundtrip_auth_only(const char * root_ca, + const char * im_ca_str); + +int roundtrip_kex_only(void); + +int corrupted_request(const char * root_ca, + const char * im_ca_str); + +int corrupted_response(const char * root_ca, + const char * im_ca_str); + +int truncated_request(const char * root_ca, + const char * im_ca_str); + +#endif /* IRMD_TESTS_COMMON_H */ diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c new file mode 100644 index 00000000..70943d7c --- /dev/null +++ b/src/irmd/oap/tests/oap_test.c @@ -0,0 +1,947 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Unit tests of Ouroboros Allocation Protocol (OAP) + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif +#else +#define _POSIX_C_SOURCE 200809L +#endif + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "oap.h" +#include "common.h" + +#include +#include + +#ifdef HAVE_OPENSSL +#include +#endif + +#define AUTH true +#define NO_AUTH false + +extern const uint16_t kex_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +struct test_cfg test_cfg; + +/* Mock load - called by load_*_credentials in common.c */ +int mock_load_credentials(void ** pkp, + void ** crt) +{ + *crt = NULL; + + if (crypt_load_privkey_str(server_pkp_ec, pkp) < 0) + goto fail_privkey; + + if (crypt_load_crt_str(signed_server_crt_ec, crt) < 0) + goto fail_crt; + + return 0; + + fail_crt: + crypt_free_key(*pkp); + fail_privkey: + *pkp = NULL; + return -1; +} + +/* Stub KEM functions - ECDSA tests don't use KEM */ +int load_server_kem_keypair(__attribute__((unused)) const char * name, + __attribute__((unused)) bool raw_fmt, + __attribute__((unused)) void ** pkp) +{ + return -1; +} + +int load_server_kem_pk(__attribute__((unused)) const char * name, + __attribute__((unused)) struct sec_config * cfg, + __attribute__((unused)) buffer_t * pk) +{ + return -1; +} + +static void test_default_cfg(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: X25519, AES-256-GCM, SHA-256, with auth */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: same KEX/cipher/kdf/md, no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; +} + +static int test_oap_auth_init_fini(void) +{ + TEST_START(); + + if (oap_auth_init() < 0) { + printf("Failed to init OAP.\n"); + goto fail; + } + + oap_auth_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip(int kex) +{ + struct oap_test_ctx ctx; + const char * kex_str = kex_nid_to_str(kex); + + TEST_START("(%s)", kex_str); + + test_default_cfg(); + test_cfg.srv.kex = kex; + test_cfg.cli.kex = kex; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + if (ctx.cli.nid == NID_undef || ctx.srv.nid == NID_undef) { + printf("Cipher not set in flow.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS("(%s)", kex_str); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL("(%s)", kex_str); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_auth_only(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth only, no encryption */ + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: no auth, no encryption */ + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + return roundtrip_auth_only(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_roundtrip_kex_only(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: KEX only, no auth */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + + /* Client: KEX only, no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + return roundtrip_kex_only(); +} + +static int test_oap_piggyback_data(void) +{ + struct oap_test_ctx ctx; + const char * cli_data_str = "client_data"; + const char * srv_data_str = "server_data"; + buffer_t srv_data = BUF_INIT; + + TEST_START(); + + test_default_cfg(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + /* Client prepares request with piggybacked data */ + ctx.data.len = strlen(cli_data_str); + ctx.data.data = malloc(ctx.data.len); + if (ctx.data.data == NULL) + goto fail_cleanup; + memcpy(ctx.data.data, cli_data_str, ctx.data.len); + + if (oap_cli_prepare_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Set server's response data (ctx.data will take cli data) */ + srv_data.len = strlen(srv_data_str); + srv_data.data = malloc(srv_data.len); + if (srv_data.data == NULL) + goto fail_cleanup; + memcpy(srv_data.data, srv_data_str, srv_data.len); + + freebuf(ctx.data); + ctx.data = srv_data; + clrbuf(srv_data); + + if (oap_srv_process_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Verify server received client's piggybacked data */ + if (ctx.data.len != strlen(cli_data_str) || + memcmp(ctx.data.data, cli_data_str, ctx.data.len) != 0) { + printf("Server did not receive correct client data.\n"); + goto fail_cleanup; + } + + freebuf(ctx.data); + + if (oap_cli_complete_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Verify client received server's piggybacked data */ + if (ctx.data.len != strlen(srv_data_str) || + memcmp(ctx.data.data, srv_data_str, ctx.data.len) != 0) { + printf("Client did not receive correct server data.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + freebuf(srv_data); + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_corrupted_request(void) +{ + test_default_cfg(); + test_cfg.cli.auth = AUTH; + + return corrupted_request(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_corrupted_response(void) +{ + test_default_cfg(); + + return corrupted_response(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_truncated_request(void) +{ + test_default_cfg(); + + return truncated_request(root_ca_crt_ec, im_ca_crt_ec); +} + +/* After ID (16), timestamp (8), cipher_nid (2), kdf_nid (2), md (2) */ +#define OAP_CERT_LEN_OFFSET 30 +static int test_oap_inflated_length_field(void) +{ + struct oap_test_ctx ctx; + uint16_t fake; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set cert length to claim more bytes than packet contains */ + fake = hton16(60000); + memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject inflated length field.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Attacker claims cert is smaller - causes misparse of subsequent fields */ +static int test_oap_deflated_length_field(void) +{ + struct oap_test_ctx ctx; + uint16_t fake; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set cert length to claim fewer bytes - will misparse rest */ + fake = hton16(1); + memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject deflated length field.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Header field offsets for byte manipulation */ +#define OAP_CIPHER_NID_OFFSET 24 +#define OAP_KEX_LEN_OFFSET 32 + +/* Server rejects request when cipher NID set but no KEX data provided */ +static int test_oap_nid_without_kex(void) +{ + struct oap_test_ctx ctx; + uint16_t cipher_nid; + uint16_t zero = 0; + + TEST_START(); + + /* Configure unsigned KEX-only mode */ + memset(&test_cfg, 0, sizeof(test_cfg)); + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Tamper: keep cipher_nid but set kex_len=0, truncate KEX data */ + cipher_nid = hton16(NID_aes_256_gcm); + memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &cipher_nid, + sizeof(cipher_nid)); + memcpy(ctx.req_hdr.data + OAP_KEX_LEN_OFFSET, &zero, sizeof(zero)); + ctx.req_hdr.len = 36; /* Fixed header only, no KEX data */ + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject cipher NID without KEX data.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Server rejects OAP request with unsupported cipher NID */ +static int test_oap_unsupported_nid(void) +{ + struct oap_test_ctx ctx; + uint16_t bad_nid; + + TEST_START(); + + /* Configure unsigned KEX-only mode */ + memset(&test_cfg, 0, sizeof(test_cfg)); + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Tamper: set cipher_nid to unsupported value */ + bad_nid = hton16(9999); + memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &bad_nid, + sizeof(bad_nid)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject unsupported cipher NID.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + /* Skip KEM algorithms - they're tested in oap_test_pqc */ + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_roundtrip(kex_supported_nids[i]); + } + + return ret; +} + +/* Cipher negotiation - client should accept server's chosen cipher */ +static int test_oap_cipher_mismatch(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: ChaCha20-Poly1305, SHA3-256, SHA-384 */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_chacha20_poly1305; + test_cfg.srv.kdf = NID_sha3_256; + test_cfg.srv.md = NID_sha384; + test_cfg.srv.auth = AUTH; + + /* Client: AES-256-GCM, SHA-256, SHA-256 */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + /* Verify: both should have the server's chosen cipher and KDF */ + if (ctx.srv.nid != test_cfg.srv.cipher) { + printf("Server cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.srv.nid)); + goto fail_cleanup; + } + + if (ctx.cli.nid != test_cfg.srv.cipher) { + printf("Client cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.cli.nid)); + goto fail_cleanup; + } + + /* Parse response header to check negotiated KDF */ + if (ctx.resp_hdr.len > 26) { + uint16_t resp_kdf_nid; + /* KDF NID at offset 26: ID(16) + ts(8) + cipher(2) */ + resp_kdf_nid = ntoh16(*(uint16_t *)(ctx.resp_hdr.data + 26)); + + if (resp_kdf_nid != test_cfg.srv.kdf) { + printf("Response KDF mismatch: expected %s, got %s\n", + md_nid_to_str(test_cfg.srv.kdf), + md_nid_to_str(resp_kdf_nid)); + goto fail_cleanup; + } + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test roundtrip with different signature digest algorithms */ +static int test_oap_roundtrip_md(int md) +{ + struct oap_test_ctx ctx; + const char * md_str = md_nid_to_str(md); + + TEST_START("(%s)", md_str ? md_str : "default"); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth + KEX with specified md */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = md; + test_cfg.srv.auth = AUTH; + + /* Client: no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = md; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS("(%s)", md_str ? md_str : "default"); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL("(%s)", md_str ? md_str : "default"); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_md_all(void) +{ + int ret = 0; + int i; + + /* Test with default (0) */ + ret |= test_oap_roundtrip_md(0); + + /* Test with all supported digest NIDs */ + for (i = 0; md_supported_nids[i] != NID_undef; i++) + ret |= test_oap_roundtrip_md(md_supported_nids[i]); + + return ret; +} + +/* Timestamp is at offset 16 (after the 16-byte ID) */ +#define OAP_TIMESTAMP_OFFSET 16 +/* Test that packets with outdated timestamps are rejected */ +static int test_oap_outdated_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec old_ts; + uint64_t old_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 30 seconds in the past (> 20s replay timer) */ + clock_gettime(CLOCK_REALTIME, &old_ts); + old_ts.tv_sec -= OAP_REPLAY_TIMER + 10; + old_stamp = hton64(TS_TO_UINT64(old_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &old_stamp, + sizeof(old_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject outdated packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that packets from the future are rejected */ +static int test_oap_future_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec future_ts; + uint64_t future_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 1 second in the future (> 100ms slack) */ + clock_gettime(CLOCK_REALTIME, &future_ts); + future_ts.tv_sec += 1; + future_stamp = hton64(TS_TO_UINT64(future_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &future_stamp, + sizeof(future_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject future packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that replayed packets (same ID + timestamp) are rejected */ +static int test_oap_replay_packet(void) +{ + struct oap_test_ctx ctx; + buffer_t saved_req; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Save the request for replay */ + saved_req.len = ctx.req_hdr.len; + saved_req.data = malloc(saved_req.len); + if (saved_req.data == NULL) { + printf("Failed to allocate saved request.\n"); + goto fail_cleanup; + } + memcpy(saved_req.data, ctx.req_hdr.data, saved_req.len); + + /* First request should succeed */ + if (oap_srv_process_ctx(&ctx) < 0) { + printf("First request should succeed.\n"); + free(saved_req.data); + goto fail_cleanup; + } + + /* Free response from first request before replay */ + freebuf(ctx.resp_hdr); + + /* Restore the saved request for replay */ + freebuf(ctx.req_hdr); + ctx.req_hdr = saved_req; + + /* Replayed request should fail */ + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject replayed packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that client rejects server with wrong certificate name */ +static int test_oap_server_name_mismatch(void) +{ + struct oap_test_ctx ctx; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + /* Set client's expected name to something different from cert name */ + strcpy(ctx.cli.info.name, "wrong.server.name"); + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + /* Client should reject due to name mismatch */ + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client should reject server with wrong cert name.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int oap_test(int argc, + char **argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_oap_auth_init_fini(); + +#ifdef HAVE_OPENSSL + ret |= test_oap_roundtrip_auth_only(); + ret |= test_oap_roundtrip_kex_only(); + ret |= test_oap_piggyback_data(); + + ret |= test_oap_roundtrip_all(); + ret |= test_oap_roundtrip_md_all(); + + ret |= test_oap_corrupted_request(); + ret |= test_oap_corrupted_response(); + ret |= test_oap_truncated_request(); + ret |= test_oap_inflated_length_field(); + ret |= test_oap_deflated_length_field(); + ret |= test_oap_nid_without_kex(); + ret |= test_oap_unsupported_nid(); + + ret |= test_oap_cipher_mismatch(); + + ret |= test_oap_outdated_packet(); + ret |= test_oap_future_packet(); + ret |= test_oap_replay_packet(); + ret |= test_oap_server_name_mismatch(); +#else + (void) test_oap_roundtrip_auth_only; + (void) test_oap_roundtrip_kex_only; + (void) test_oap_piggyback_data; + (void) test_oap_roundtrip; + (void) test_oap_roundtrip_all; + (void) test_oap_roundtrip_md; + (void) test_oap_roundtrip_md_all; + (void) test_oap_corrupted_request; + (void) test_oap_corrupted_response; + (void) test_oap_truncated_request; + (void) test_oap_inflated_length_field; + (void) test_oap_deflated_length_field; + (void) test_oap_nid_without_kex; + (void) test_oap_unsupported_nid; + (void) test_oap_cipher_mismatch; + (void) test_oap_outdated_packet; + (void) test_oap_future_packet; + (void) test_oap_replay_packet; + (void) test_oap_server_name_mismatch; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/irmd/oap/tests/oap_test_pqc.c b/src/irmd/oap/tests/oap_test_pqc.c new file mode 100644 index 00000000..ed51a6b4 --- /dev/null +++ b/src/irmd/oap/tests/oap_test_pqc.c @@ -0,0 +1,363 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests of OAP post-quantum key exchange + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200809L +#endif + +#include "config.h" + +#include +#include +#include +#include +#include + +#include + +#include "oap.h" +#include "common.h" + +#include +#include + +#ifdef HAVE_OPENSSL +#include +#endif + +#define CLI_AUTH 1 +#define NO_CLI_AUTH 0 +#define CLI_ENCAP KEM_MODE_CLIENT_ENCAP +#define SRV_ENCAP KEM_MODE_SERVER_ENCAP + +extern const uint16_t kex_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +static int get_random_kdf(void) +{ + static int idx = 0; + int count; + + if (md_supported_nids[0] == NID_undef) + return NID_undef; + + for (count = 0; md_supported_nids[count] != NID_undef; count++) + ; + + return md_supported_nids[(idx++) % count]; +} + +struct test_cfg test_cfg; + +/* KEM keypair storage for tests (server-side keypair for KEM modes) */ +static void * test_kem_pkp = NULL; /* Private key pair */ +static uint8_t test_kem_pk[4096]; /* Public key buffer */ +static size_t test_kem_pk_len = 0; + +/* Mock load - called by load_*_credentials in common.c */ +int mock_load_credentials(void ** pkp, + void ** crt) +{ + *pkp = NULL; + *crt = NULL; + + if (crypt_load_privkey_str(server_pkp_ml, pkp) < 0) + return -1; + + if (crypt_load_crt_str(signed_server_crt_ml, crt) < 0) { + crypt_free_key(*pkp); + *pkp = NULL; + return -1; + } + + return 0; +} + +int load_server_kem_keypair(const char * name, + bool raw_fmt, + void ** pkp) +{ +#ifdef HAVE_OPENSSL + struct sec_config local_cfg; + ssize_t pk_len; + + (void) name; + (void) raw_fmt; + + /* + * Uses reference counting. The caller will call + * EVP_PKEY_free which decrements the count. + */ + if (test_kem_pkp != NULL) { + if (EVP_PKEY_up_ref((EVP_PKEY *)test_kem_pkp) != 1) + return -1; + + *pkp = test_kem_pkp; + return 0; + } + + /* + * Generate a new KEM keypair from test_cfg.srv.kex. + */ + memset(&local_cfg, 0, sizeof(local_cfg)); + if (test_cfg.srv.kex == NID_undef) + goto fail; + + SET_KEX_ALGO_NID(&local_cfg, test_cfg.srv.kex); + + pk_len = kex_pkp_create(&local_cfg, &test_kem_pkp, test_kem_pk); + if (pk_len < 0) + goto fail; + + test_kem_pk_len = (size_t) pk_len; + + if (EVP_PKEY_up_ref((EVP_PKEY *)test_kem_pkp) != 1) + goto fail_ref; + + *pkp = test_kem_pkp; + + return 0; + fail_ref: + kex_pkp_destroy(test_kem_pkp); + test_kem_pkp = NULL; + test_kem_pk_len = 0; + fail: + return -1; + +#else + (void) name; + (void) raw_fmt; + (void) pkp; + return -1; +#endif +} + +int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk) +{ + ssize_t len; + + (void) name; + + if (test_kem_pk_len > 0) { + pk->data = malloc(test_kem_pk_len); + if (pk->data == NULL) + return -1; + memcpy(pk->data, test_kem_pk, test_kem_pk_len); + pk->len = test_kem_pk_len; + return 0; + } + + /* Generate keypair on demand if not already done */ + len = kex_pkp_create(cfg, &test_kem_pkp, test_kem_pk); + if (len < 0) + return -1; + + test_kem_pk_len = (size_t) len; + pk->data = malloc(test_kem_pk_len); + if (pk->data == NULL) + return -1; + memcpy(pk->data, test_kem_pk, test_kem_pk_len); + pk->len = test_kem_pk_len; + + return 0; +} + +static void reset_kem_state(void) +{ + if (test_kem_pkp != NULL) { + kex_pkp_destroy(test_kem_pkp); + test_kem_pkp = NULL; + } + test_kem_pk_len = 0; +} + +static void test_cfg_init(int kex, + int cipher, + int kdf, + int kem_mode, + bool cli_auth) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server config */ + test_cfg.srv.kex = kex; + test_cfg.srv.cipher = cipher; + test_cfg.srv.kdf = kdf; + test_cfg.srv.kem_mode = kem_mode; + test_cfg.srv.auth = true; + + /* Client config */ + test_cfg.cli.kex = kex; + test_cfg.cli.cipher = cipher; + test_cfg.cli.kdf = kdf; + test_cfg.cli.kem_mode = kem_mode; + test_cfg.cli.auth = cli_auth; +} + +static int oap_test_setup_kem(struct oap_test_ctx * ctx, + const char * root_ca, + const char * im_ca) +{ + reset_kem_state(); + return oap_test_setup(ctx, root_ca, im_ca); +} + +static void oap_test_teardown_kem(struct oap_test_ctx * ctx) +{ + oap_test_teardown(ctx); +} + +static int test_oap_roundtrip_auth_only(void) +{ + test_cfg_init(NID_undef, NID_undef, NID_undef, 0, false); + + return roundtrip_auth_only(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_corrupted_request(void) +{ + test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(), + SRV_ENCAP, CLI_AUTH); + + return corrupted_request(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_corrupted_response(void) +{ + test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(), + SRV_ENCAP, NO_CLI_AUTH); + + return corrupted_response(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_truncated_request(void) +{ + test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(), + SRV_ENCAP, NO_CLI_AUTH); + + return truncated_request(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_roundtrip_kem(int kex, + int kem_mode) +{ + struct oap_test_ctx ctx; + const char * kex_str = kex_nid_to_str(kex); + const char * mode_str = kem_mode == CLI_ENCAP ? "cli" : "srv"; + + test_cfg_init(kex, NID_aes_256_gcm, get_random_kdf(), + kem_mode, NO_CLI_AUTH); + + TEST_START("(%s, %s encaps)", kex_str, mode_str); + + if (oap_test_setup_kem(&ctx, root_ca_crt_ml, im_ca_crt_ml) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + if (ctx.cli.nid == NID_undef || + ctx.srv.nid == NID_undef) { + printf("Cipher not set in flow.\n"); + goto fail_cleanup; + } + + oap_test_teardown_kem(&ctx); + + TEST_SUCCESS("(%s, %s encaps)", kex_str, mode_str); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown_kem(&ctx); + fail: + TEST_FAIL("(%s, %s encaps)", kex_str, mode_str); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_kem_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_roundtrip_kem(kex_supported_nids[i], SRV_ENCAP); + ret |= test_oap_roundtrip_kem(kex_supported_nids[i], CLI_ENCAP); + } + + return ret; +} + +int oap_test_pqc(int argc, + char **argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_PQC + ret |= test_oap_roundtrip_auth_only(); + + ret |= test_oap_roundtrip_kem_all(); + + ret |= test_oap_corrupted_request(); + ret |= test_oap_corrupted_response(); + ret |= test_oap_truncated_request(); +#else + (void) test_oap_roundtrip_auth_only; + (void) test_oap_roundtrip_kem; + (void) test_oap_roundtrip_kem_all; + (void) test_oap_corrupted_request; + (void) test_oap_corrupted_response; + (void) test_oap_truncated_request; + + ret = TEST_RC_SKIP; +#endif + + return ret; +} diff --git a/src/irmd/reg/ipcp.c b/src/irmd/reg/ipcp.c index 474527a4..74ec4939 100644 --- a/src/irmd/reg/ipcp.c +++ b/src/irmd/reg/ipcp.c @@ -77,7 +77,6 @@ void reg_ipcp_update(struct reg_ipcp * ipcp, const struct ipcp_info * info) { assert(ipcp != NULL); - assert(info->state != IPCP_NULL); ipcp->info = *info; } diff --git a/src/irmd/reg/tests/CMakeLists.txt b/src/irmd/reg/tests/CMakeLists.txt index e33bd056..759d29b4 100644 --- a/src/irmd/reg/tests/CMakeLists.txt +++ b/src/irmd/reg/tests/CMakeLists.txt @@ -1,6 +1,8 @@ get_filename_component(tmp ".." ABSOLUTE) get_filename_component(src_folder "${tmp}" NAME) +compute_test_prefix() + # Set include directories for tests include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -39,5 +41,5 @@ endif() foreach(test ${tests_to_run}) get_filename_component(test_name ${test} NAME_WE) - add_test(irmd/reg/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${src_folder}_test ${test_name}) + add_test(${TEST_PREFIX}/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${src_folder}_test ${test_name}) endforeach(test) diff --git a/src/irmd/reg/tests/flow_test.c b/src/irmd/reg/tests/flow_test.c index 27fd61b0..2066c811 100644 --- a/src/irmd/reg/tests/flow_test.c +++ b/src/irmd/reg/tests/flow_test.c @@ -22,7 +22,7 @@ #include "../flow.c" -#include +#include #include diff --git a/src/irmd/reg/tests/ipcp_test.c b/src/irmd/reg/tests/ipcp_test.c index d7d8e524..6ab6443d 100644 --- a/src/irmd/reg/tests/ipcp_test.c +++ b/src/irmd/reg/tests/ipcp_test.c @@ -20,7 +20,7 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#include +#include #include "../ipcp.c" diff --git a/src/irmd/reg/tests/name_test.c b/src/irmd/reg/tests/name_test.c index 9071364b..5b42875e 100644 --- a/src/irmd/reg/tests/name_test.c +++ b/src/irmd/reg/tests/name_test.c @@ -23,7 +23,7 @@ #include "../name.c" -#include +#include #define TEST_PID 65534 #define TEST_PROG "/usr/bin/testprog" diff --git a/src/irmd/reg/tests/proc_test.c b/src/irmd/reg/tests/proc_test.c index df0527fb..d53f18ec 100644 --- a/src/irmd/reg/tests/proc_test.c +++ b/src/irmd/reg/tests/proc_test.c @@ -22,7 +22,7 @@ #include "../proc.c" -#include +#include #define TEST_PID 65534 #define TEST_PROG "usr/bin/testprog" diff --git a/src/irmd/reg/tests/prog_test.c b/src/irmd/reg/tests/prog_test.c index c394c222..3900e7d7 100644 --- a/src/irmd/reg/tests/prog_test.c +++ b/src/irmd/reg/tests/prog_test.c @@ -22,7 +22,7 @@ #include "../prog.c" -#include +#include #define TEST_PROG "usr/bin/testprog" diff --git a/src/irmd/reg/tests/reg_test.c b/src/irmd/reg/tests/reg_test.c index 4699beab..e7ae1a97 100644 --- a/src/irmd/reg/tests/reg_test.c +++ b/src/irmd/reg/tests/reg_test.c @@ -23,7 +23,7 @@ #include "../reg.c" -#include +#include #define TEST_PID 3666 #define TEST_N_1_PID 3999 @@ -1471,7 +1471,7 @@ static int test_wait_ipcp_boot_fail(void) struct ipcp_info resp_info = { .name = TEST_IPCP, .pid = TEST_PID, - .state = IPCP_INIT + .state = IPCP_NULL }; TEST_START(); diff --git a/src/irmd/tests/CMakeLists.txt b/src/irmd/tests/CMakeLists.txt deleted file mode 100644 index a255ca00..00000000 --- a/src/irmd/tests/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -get_filename_component(tmp ".." ABSOLUTE) -get_filename_component(src_folder "${tmp}" NAME) - -create_test_sourcelist(${src_folder}_tests test_suite.c - # Add new tests here - irm_test.c - oap_test.c -) - -add_executable(${src_folder}_test ${${src_folder}_tests}) -target_link_libraries(${src_folder}_test ouroboros-irm) -target_include_directories(${src_folder}_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) - -add_dependencies(build_tests ${src_folder}_test) - -set(tests_to_run ${${src_folder}_tests}) -if(CMAKE_VERSION VERSION_LESS "3.29.0") - remove(tests_to_run test_suite.c) -else () - list(POP_FRONT tests_to_run) -endif() - -foreach(test ${tests_to_run}) - get_filename_component(test_name ${test} NAME_WE) - add_test(irmd/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${src_folder}_test ${test_name}) -endforeach(test) - -set_property(TEST irmd/oap_test PROPERTY SKIP_RETURN_CODE 1) diff --git a/src/irmd/tests/irm_test.c b/src/irmd/tests/irm_test.c deleted file mode 100644 index d440289c..00000000 --- a/src/irmd/tests/irm_test.c +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2024 - * - * Unit tests of IRMd functions - * Dimitri Staessens - * Sander Vrijders - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - - - -int irm_test(int argc, - char **argv) -{ - int ret = 0; - - (void) argc; - (void) argv; - - return ret; -} diff --git a/src/irmd/tests/oap_test.c b/src/irmd/tests/oap_test.c index a5f9c706..0e7c11fc 100644 --- a/src/irmd/tests/oap_test.c +++ b/src/irmd/tests/oap_test.c @@ -1,7 +1,8 @@ /* * Ouroboros - Copyright (C) 2016 - 2024 * - * Unit tests of Ouroboros flow allocation protocol + * Unit tests of Ouroboros Allocation Protocol (OAP) + * * Dimitri Staessens * Sander Vrijders * @@ -19,245 +20,824 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#include "oap.c" +#if defined(__linux__) || defined(__CYGWIN__) + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif +#else +#define _POSIX_C_SOURCE 200809L +#endif + +#include "config.h" +#include +#include +#include +#include #include -#include - -static const char * pkp_str = \ -"-----BEGIN EC PRIVATE KEY-----\n" -"MHcCAQEEIC13y+5jdKe80HBJD7WITpQamcn3rrkTX1r0v+JwSk4NoAoGCCqGSM49\n" -"AwEHoUQDQgAEcC0yLAfUtufH8cdLybrdWPc6U+xRuhDhqqrEcBO5+eob2xyqEaNk\n" -"nIV/86724zPptGRahWz0rzW2PvNppJdNBg==\n" -"-----END EC PRIVATE KEY-----\n"; - -/* Valid signed server certificate for server-2.unittest.o7s */ -static const char * crt_str = \ -"-----BEGIN CERTIFICATE-----\n" -"MIIDgjCCAyigAwIBAgICEAIwCgYIKoZIzj0EAwIwWzELMAkGA1UEBhMCQkUxDDAK\n" -"BgNVBAgMA09WTDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n" -"GTAXBgNVBAMMEGltMi51bml0dGVzdC5vN3MwHhcNMjUwNzA0MTMxODI5WhcNMzUw\n" -"NzAyMTMxODI5WjBwMQswCQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQ4wDAYDVQQH\n" -"DAVHaGVudDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3MxHjAc\n" -"BgNVBAMMFXNlcnZlci0yLnVuaXR0ZXN0Lm83czBZMBMGByqGSM49AgEGCCqGSM49\n" -"AwEHA0IABHAtMiwH1Lbnx/HHS8m63Vj3OlPsUboQ4aqqxHATufnqG9scqhGjZJyF\n" -"f/Ou9uMz6bRkWoVs9K81tj7zaaSXTQajggHFMIIBwTAJBgNVHRMEAjAAMBEGCWCG\n" -"SAGG+EIBAQQEAwIGQDA6BglghkgBhvhCAQ0ELRYrR3JpbGxlZCBDaGVlc2UgR2Vu\n" -"ZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUTt3xHTwE9amoglxh\n" -"cEMqWv+PpDMwgb8GA1UdIwSBtzCBtIAUFfeZRx8QWWKQr7Aw8zjDu2shvcShgZek\n" -"gZQwgZExCzAJBgNVBAYTAkJFMQwwCgYDVQQIDANPVkwxDjAMBgNVBAcMBUdoZW50\n" -"MQwwCgYDVQQKDANvN3MxFTATBgNVBAsMDHVuaXR0ZXN0Lm83czEZMBcGA1UEAwwQ\n" -"Y2EyLnVuaXR0ZXN0Lm83czEkMCIGCSqGSIb3DQEJARYVZHVtbXlAb3Vyb2Jvcm9z\n" -"LnJvY2tzggIQAjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw\n" -"EQYDVR0fBAowCDAGoASgAoYAMCoGCCsGAQUFBwEBBB4wHDAMBggrBgEFBQcwAoYA\n" -"MAwGCCsGAQUFBzABhgAwIAYDVR0RBBkwF4IVc2VydmVyLTEudW5pdHRlc3Qubzdz\n" -"MAoGCCqGSM49BAMCA0gAMEUCIQDHuDb62w/Uah4nKwUFoJVkr4rgdNGh2Rn3SWaK\n" -"0FV/gAIgOLKorTwSgrTFdyOUkuPOhRs8BEMpah+dp8UTO8AnLvY=\n" -"-----END CERTIFICATE-----\n"; - -static int test_oap_hdr_init_fini(void) +#include + +#include +#include + +#include "oap.h" +#include "common.h" + +#include +#include + +#ifdef HAVE_OPENSSL +#include +#endif + +#define AUTH true +#define NO_AUTH false + +extern const uint16_t kex_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +struct test_cfg test_cfg; + +/* Mock load - called by load_*_credentials in common.c */ +int mock_load_credentials(void ** pkp, + void ** crt) { - struct oap_hdr oap_hdr; - struct timespec now; - uint64_t stamp; - buffer_t ephkey = BUF_INIT; - buffer_t data = BUF_INIT; - uint8_t buf[OAP_ID_SIZE]; - buffer_t id; - void * pkp = NULL; - void * pubcrt = NULL; + *crt = NULL; + + if (crypt_load_privkey_str(server_pkp_ec, pkp) < 0) + goto fail_privkey; + + if (crypt_load_crt_str(signed_server_crt_ec, crt) < 0) + goto fail_crt; + return 0; + + fail_crt: + crypt_free_key(*pkp); + fail_privkey: + *pkp = NULL; + return -1; +} + +/* Stub KEM functions - ECDSA tests don't use KEM */ +int load_server_kem_keypair(__attribute__((unused)) const char * name, + __attribute__((unused)) struct sec_config * cfg, + __attribute__((unused)) void ** pkp) +{ + return -1; +} + +int load_server_kem_pk(__attribute__((unused)) const char * name, + __attribute__((unused)) struct sec_config * cfg, + __attribute__((unused)) buffer_t * pk) +{ + return -1; +} + +static void test_default_cfg(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: X25519, AES-256-GCM, SHA-256, with auth */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: same KEX/cipher/kdf/md, no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; +} + +static int test_oap_init_fini(void) +{ TEST_START(); - random_buffer(buf, OAP_ID_SIZE); - id.data = buf; - id.len = OAP_ID_SIZE; + if (oap_init() < 0) { + printf("Failed to init OAP.\n"); + goto fail; + } - clock_gettime(CLOCK_REALTIME, &now); - stamp = TS_TO_UINT64(now); + oap_fini(); - if (oap_hdr_init(id, pkp, pubcrt, ephkey, data, &oap_hdr) < 0) { - printf("Failed to init OAP request header.\n"); - goto fail_req_hdr; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip(int kex) +{ + struct oap_test_ctx ctx; + const char * kex_str = kex_nid_to_str(kex); + + TEST_START("(%s)", kex_str); + + test_default_cfg(); + test_cfg.srv.kex = kex; + test_cfg.cli.kex = kex; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; } - if (oap_hdr.hdr.len != OAP_HDR_MIN_SIZE) { - printf("OAP request header wrong: %zu < %zu.\n", - oap_hdr.hdr.len, OAP_HDR_MIN_SIZE); - goto fail_req_hdr_chk; + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; } - if (oap_hdr.id.len != OAP_ID_SIZE) { - printf("OAP request header ID wrong size: %zu != %zu.\n", - oap_hdr.id.len, (size_t) OAP_ID_SIZE); - goto fail_req_hdr_chk; + if (ctx.cli.nid == NID_undef || ctx.srv.nid == NID_undef) { + printf("Cipher not set in flow.\n"); + goto fail_cleanup; } - if (memcmp(oap_hdr.id.data, id.data, OAP_ID_SIZE) != 0) { - printf("OAP request header ID mismatch.\n"); - goto fail_req_hdr_chk; + oap_test_teardown(&ctx); + + TEST_SUCCESS("(%s)", kex_str); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL("(%s)", kex_str); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_auth_only(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth only, no encryption */ + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: no auth, no encryption */ + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + return roundtrip_auth_only(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_roundtrip_kex_only(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: KEX only, no auth */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + + /* Client: KEX only, no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + return roundtrip_kex_only(); +} + +static int test_oap_piggyback_data(void) +{ + struct oap_test_ctx ctx; + const char * cli_data_str = "client_data"; + const char * srv_data_str = "server_data"; + buffer_t srv_data = BUF_INIT; + + TEST_START(); + + test_default_cfg(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + /* Client prepares request with piggybacked data */ + ctx.data.len = strlen(cli_data_str); + ctx.data.data = malloc(ctx.data.len); + if (ctx.data.data == NULL) + goto fail_cleanup; + memcpy(ctx.data.data, cli_data_str, ctx.data.len); + + if (oap_cli_prepare_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Set server's response data (ctx.data replaced with cli data) */ + srv_data.len = strlen(srv_data_str); + srv_data.data = malloc(srv_data.len); + if (srv_data.data == NULL) + goto fail_cleanup; + memcpy(srv_data.data, srv_data_str, srv_data.len); + + freebuf(ctx.data); + ctx.data = srv_data; + clrbuf(srv_data); + + if (oap_srv_process_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Verify server received client's piggybacked data */ + if (ctx.data.len != strlen(cli_data_str) || + memcmp(ctx.data.data, cli_data_str, ctx.data.len) != 0) { + printf("Server did not receive correct client data.\n"); + goto fail_cleanup; } - if (oap_hdr.timestamp < stamp) { - printf("OAP request header timestamp is too old.\n"); - goto fail_req_hdr_chk; + freebuf(ctx.data); + + if (oap_cli_complete_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Verify client received server's piggybacked data */ + if (ctx.data.len != strlen(srv_data_str) || + memcmp(ctx.data.data, srv_data_str, ctx.data.len) != 0) { + printf("Client did not receive correct server data.\n"); + goto fail_cleanup; } - if (oap_hdr.timestamp > stamp + 1 * BILLION) { - printf("OAP request header timestamp is too new.\n"); - goto fail_req_hdr_chk; + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; } - oap_hdr_fini(&oap_hdr); + oap_test_teardown(&ctx); TEST_SUCCESS(); - return TEST_RC_SUCCESS; - fail_req_hdr_chk: - oap_hdr_fini(&oap_hdr); - fail_req_hdr: + fail_cleanup: + freebuf(srv_data); + oap_test_teardown(&ctx); + fail: TEST_FAIL(); return TEST_RC_FAIL; } -static int test_oap_hdr_init_fini_data(void) +static int test_oap_corrupted_request(void) +{ + test_default_cfg(); + test_cfg.cli.auth = AUTH; + return corrupted_request(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_corrupted_response(void) +{ + test_default_cfg(); + + return corrupted_response(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_truncated_request(void) { - struct oap_hdr oap_hdr; - buffer_t data; - buffer_t ephkey = BUF_INIT; - uint8_t buf[OAP_ID_SIZE]; - buffer_t id; - void * pkp = NULL; - void * pubcrt = NULL; + test_default_cfg(); + + return truncated_request(root_ca_crt_ec, im_ca_crt_ec); +} + +/* After ID (16), timestamp (8), cipher_nid (2), kdf_nid (2), md (2) */ +#define OAP_CERT_LEN_OFFSET 30 +static int test_oap_inflated_length_field(void) +{ + struct oap_test_ctx ctx; + uint16_t fake; + + test_default_cfg(); TEST_START(); - random_buffer(buf, OAP_ID_SIZE); - id.data = buf; - id.len = OAP_ID_SIZE; + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } - data.len = 100; - data.data = malloc(data.len); - if (data.data == NULL) { - printf("Failed to allocate data buffer.\n"); - goto fail_data; + if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) { + printf("Request too short for test.\n"); + goto fail_cleanup; } - random_buffer(data.data, data.len); + /* Set cert length to claim more bytes than packet contains */ + fake = hton16(60000); + memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake)); - if (oap_hdr_init(id, pkp, pubcrt, ephkey, data, &oap_hdr) < 0) { - printf("Failed to create OAP request header.\n"); - goto fail_req_hdr; + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject inflated length field.\n"); + goto fail_cleanup; } - if (oap_hdr.hdr.len != OAP_HDR_MIN_SIZE + data.len) { - printf("OAP request header wrong: %zu < %zu.\n", - oap_hdr.hdr.len, OAP_HDR_MIN_SIZE + data.len); - goto fail_req_hdr_sz; + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Attacker claims cert is smaller - causes misparse of subsequent fields */ +static int test_oap_deflated_length_field(void) +{ + struct oap_test_ctx ctx; + uint16_t fake; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set cert length to claim fewer bytes - will misparse rest */ + fake = hton16(1); + memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject deflated length field.\n"); + goto fail_cleanup; } - freebuf(data); - oap_hdr_fini(&oap_hdr); + oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} +/* Header field offsets for byte manipulation */ +#define OAP_CIPHER_NID_OFFSET 24 +#define OAP_KEX_LEN_OFFSET 32 + +/* Server rejects request when cipher NID set but no KEX data provided */ +static int test_oap_nid_without_kex(void) +{ + struct oap_test_ctx ctx; + uint16_t cipher_nid; + uint16_t zero = 0; + + TEST_START(); + + /* Configure unsigned KEX-only mode */ + memset(&test_cfg, 0, sizeof(test_cfg)); + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Tamper: keep cipher_nid but set kex_len=0, truncate KEX data */ + cipher_nid = hton16(NID_aes_256_gcm); + memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &cipher_nid, + sizeof(cipher_nid)); + memcpy(ctx.req_hdr.data + OAP_KEX_LEN_OFFSET, &zero, sizeof(zero)); + ctx.req_hdr.len = 36; /* Fixed header only, no KEX data */ + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject cipher NID without KEX data.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); return TEST_RC_SUCCESS; - fail_req_hdr_sz: - oap_hdr_fini(&oap_hdr); - fail_req_hdr: - freebuf(data); - fail_data: + fail_cleanup: + oap_test_teardown(&ctx); + fail: TEST_FAIL(); return TEST_RC_FAIL; } -static int test_oap_hdr_init_fini_signed(void) +/* Server rejects OAP request with unsupported cipher NID */ +static int test_oap_unsupported_nid(void) { - struct oap_hdr oap_hdr; - buffer_t ephkey = BUF_INIT; - buffer_t data = BUF_INIT; - buffer_t sign; - buffer_t id; - uint8_t buf[OAP_ID_SIZE]; - void * pkp; - void * pk; - void * pubcrt; - void * pubcrt2; + struct oap_test_ctx ctx; + uint16_t bad_nid; TEST_START(); - random_buffer(buf, OAP_ID_SIZE); - id.data = buf; - id.len = OAP_ID_SIZE; + /* Configure unsigned KEX-only mode */ + memset(&test_cfg, 0, sizeof(test_cfg)); + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Tamper: set cipher_nid to unsupported value */ + bad_nid = hton16(9999); + memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &bad_nid, + sizeof(bad_nid)); - if (crypt_load_privkey_str(pkp_str, &pkp) < 0) { - printf("Failed to load private key.\n"); - goto fail_pkp; + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject unsupported cipher NID.\n"); + goto fail_cleanup; } - if (crypt_load_crt_str(crt_str, &pubcrt) < 0) { - printf("Failed to load public certificate.\n"); - goto fail_pubcrt; + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + /* Skip KEM algorithms - they're tested in oap_test_pqc */ + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_roundtrip(kex_supported_nids[i]); + } + + return ret; +} + +/* Cipher negotiation - client should accept server's chosen cipher */ +static int test_oap_cipher_mismatch(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: ChaCha20-Poly1305, SHA3-256, SHA-384 */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_chacha20_poly1305; + test_cfg.srv.kdf = NID_sha3_256; + test_cfg.srv.md = NID_sha384; + test_cfg.srv.auth = AUTH; + + /* Client: AES-256-GCM, SHA-256, SHA-256 */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; } - if (oap_hdr_init(id, pkp, pubcrt, ephkey, data, &oap_hdr) < 0) { - printf("Failed to create OAP request header.\n"); - goto fail_req_hdr; + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; } - if (oap_hdr.crt.len == 0) { - printf("OAP request header has no public certificate.\n"); - goto fail_req_hdr; + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; } - if (oap_hdr.sig.len == 0) { - printf("OAP request header no signature.\n"); - goto fail_req_hdr; + /* Verify: both should have the server's chosen cipher and KDF */ + if (ctx.srv.nid != test_cfg.srv.cipher) { + printf("Server cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.srv.nid)); + goto fail_cleanup; } - if (crypt_load_crt_der(oap_hdr.crt, &pubcrt2) < 0) { - printf("Failed to load public certificate from DER.\n"); - goto fail_crt_der; + if (ctx.cli.nid != test_cfg.srv.cipher) { + printf("Client cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.cli.nid)); + goto fail_cleanup; } - if (crypt_get_pubkey_crt(pubcrt2, &pk) < 0) { - printf("Failed to get public key from certificate.\n"); - goto fail_crt_pk; + /* Parse response header to check negotiated KDF */ + if (ctx.resp_hdr.len > 26) { + uint16_t resp_kdf_nid; + /* KDF NID at offset 26: ID(16) + ts(8) + cipher(2) */ + resp_kdf_nid = ntoh16(*(uint16_t *)(ctx.resp_hdr.data + 26)); + + if (resp_kdf_nid != test_cfg.srv.kdf) { + printf("Response KDF mismatch: expected %s, got %s\n", + md_nid_to_str(test_cfg.srv.kdf), + md_nid_to_str(resp_kdf_nid)); + goto fail_cleanup; + } } - sign = oap_hdr.hdr; - sign.len -= (oap_hdr.sig.len + sizeof(uint16_t)); + oap_test_teardown(&ctx); - if (auth_verify_sig(pk, sign, oap_hdr.sig) < 0) { - printf("Failed to verify OAP request header signature.\n"); - goto fail_check_sig; + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test roundtrip with different signature digest algorithms */ +static int test_oap_roundtrip_md(int md) +{ + struct oap_test_ctx ctx; + const char * md_str = md_nid_to_str(md); + + TEST_START("(%s)", md_str ? md_str : "default"); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth + KEX with specified md */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = md; + test_cfg.srv.auth = AUTH; + + /* Client: no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = md; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; } - oap_hdr_fini(&oap_hdr); + oap_test_teardown(&ctx); - crypt_free_crt(pubcrt2); - crypt_free_crt(pubcrt); - crypt_free_key(pk); - crypt_free_key(pkp); + TEST_SUCCESS("(%s)", md_str ? md_str : "default"); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL("(%s)", md_str ? md_str : "default"); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_md_all(void) +{ + int ret = 0; + int i; + + /* Test with default (0) */ + ret |= test_oap_roundtrip_md(0); + + /* Test with all supported digest NIDs */ + for (i = 0; md_supported_nids[i] != NID_undef; i++) + ret |= test_oap_roundtrip_md(md_supported_nids[i]); + + return ret; +} + +/* Timestamp is at offset 16 (after the 16-byte ID) */ +#define OAP_TIMESTAMP_OFFSET 16 +/* Test that packets with outdated timestamps are rejected */ +static int test_oap_outdated_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec old_ts; + uint64_t old_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 30 seconds in the past (> 20s replay timer) */ + clock_gettime(CLOCK_REALTIME, &old_ts); + old_ts.tv_sec -= OAP_REPLAY_TIMER + 10; + old_stamp = hton64(TS_TO_UINT64(old_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &old_stamp, + sizeof(old_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject outdated packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that packets from the future are rejected */ +static int test_oap_future_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec future_ts; + uint64_t future_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 1 second in the future (> 100ms slack) */ + clock_gettime(CLOCK_REALTIME, &future_ts); + future_ts.tv_sec += 1; + future_stamp = hton64(TS_TO_UINT64(future_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &future_stamp, + sizeof(future_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject future packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that replayed packets (same ID + timestamp) are rejected */ +static int test_oap_replay_packet(void) +{ + struct oap_test_ctx ctx; + buffer_t saved_req; + + test_default_cfg(); + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Save the request for replay */ + saved_req.len = ctx.req_hdr.len; + saved_req.data = malloc(saved_req.len); + if (saved_req.data == NULL) { + printf("Failed to allocate saved request.\n"); + goto fail_cleanup; + } + memcpy(saved_req.data, ctx.req_hdr.data, saved_req.len); + + /* First request should succeed */ + if (oap_srv_process_ctx(&ctx) < 0) { + printf("First request should succeed.\n"); + free(saved_req.data); + goto fail_cleanup; + } + + /* Restore the saved request for replay */ + freebuf(ctx.req_hdr); + ctx.req_hdr = saved_req; + + /* Replayed request should fail */ + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject replayed packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); return TEST_RC_SUCCESS; - fail_check_sig: - crypt_free_key(pk); - fail_crt_pk: - crypt_free_crt(pubcrt2); - fail_crt_der: - oap_hdr_fini(&oap_hdr); - fail_req_hdr: - crypt_free_crt(pubcrt); - fail_pubcrt: - crypt_free_key(pkp); - fail_pkp: + fail_cleanup: + oap_test_teardown(&ctx); + fail: TEST_FAIL(); return TEST_RC_FAIL; } @@ -270,12 +850,48 @@ int oap_test(int argc, (void) argc; (void) argv; - ret |= test_oap_hdr_init_fini(); - ret |= test_oap_hdr_init_fini_data(); + ret |= test_oap_init_fini(); + #ifdef HAVE_OPENSSL - ret |= test_oap_hdr_init_fini_signed(); + ret |= test_oap_roundtrip_auth_only(); + ret |= test_oap_roundtrip_kex_only(); + ret |= test_oap_piggyback_data(); + + ret |= test_oap_roundtrip_all(); + ret |= test_oap_roundtrip_md_all(); + + ret |= test_oap_corrupted_request(); + ret |= test_oap_corrupted_response(); + ret |= test_oap_truncated_request(); + ret |= test_oap_inflated_length_field(); + ret |= test_oap_deflated_length_field(); + ret |= test_oap_nid_without_kex(); + ret |= test_oap_unsupported_nid(); + + ret |= test_oap_cipher_mismatch(); + + ret |= test_oap_outdated_packet(); + ret |= test_oap_future_packet(); + ret |= test_oap_replay_packet(); #else - (void) test_oap_hdr_init_fini_signed; + (void) test_oap_roundtrip_auth_only; + (void) test_oap_roundtrip_kex_only; + (void) test_oap_piggyback_data; + (void) test_oap_roundtrip; + (void) test_oap_roundtrip_all; + (void) test_oap_roundtrip_md; + (void) test_oap_roundtrip_md_all; + (void) test_oap_corrupted_request; + (void) test_oap_corrupted_response; + (void) test_oap_truncated_request; + (void) test_oap_inflated_length_field; + (void) test_oap_deflated_length_field; + (void) test_oap_nid_without_kex; + (void) test_oap_unsupported_nid; + (void) test_oap_cipher_mismatch; + (void) test_oap_outdated_packet; + (void) test_oap_future_packet; + (void) test_oap_replay_packet; ret = TEST_RC_SKIP; #endif diff --git a/src/lib/config.h.in b/src/lib/config.h.in index 4533b00e..b34e6a7b 100644 --- a/src/lib/config.h.in +++ b/src/lib/config.h.in @@ -23,9 +23,11 @@ #cmakedefine HAVE_SYS_RANDOM #cmakedefine HAVE_LIBGCRYPT #cmakedefine HAVE_OPENSSL - #ifdef HAVE_OPENSSL +#cmakedefine HAVE_OPENSSL_PQC #define HAVE_ENCRYPTION +#define PROC_SECMEM_MAX @PROC_SECMEM_MAX@ +#define SECMEM_GUARD @SECMEM_GUARD@ #endif #define SYS_MAX_FLOWS @SYS_MAX_FLOWS@ 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 +} diff --git a/src/lib/crypt/openssl.c b/src/lib/crypt/openssl.c index 291a3418..71a69c1c 100644 --- a/src/lib/crypt/openssl.c +++ b/src/lib/crypt/openssl.c @@ -23,6 +23,10 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ +#define _POSIX_C_SOURCE 200809L + +#include + #include #include #include @@ -32,31 +36,239 @@ #include #include #include +#include +#include #include #include +#include #include #include #include +#include + +#define IS_EC_GROUP(str) (strcmp(str, "EC") == 0) +#define IS_DH_GROUP(str) (strcmp(str, "DH") == 0) + +#define HKDF_INFO_DHE "o7s-ossl-dhe" +#define HKDF_INFO_ENCAP "o7s-ossl-encap" +#define HKDF_SALT_LEN 32 /* SHA-256 output size */ + +struct ossl_crypt_ctx { + EVP_CIPHER_CTX * evp_ctx; + const EVP_CIPHER * cipher; + uint8_t * key; + int ivsz; + int tagsz; +}; + +struct kdf_info { + buffer_t secret; + int nid; + buffer_t salt; + buffer_t info; + buffer_t key; +}; + +/* Convert hash NID to OpenSSL digest name string for HKDF */ +static const char * hash_nid_to_digest_name(int nid) +{ + const EVP_MD * md; + const char * name; + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return "SHA256"; /* fallback to SHA-256 */ + + name = EVP_MD_get0_name(md); + if (name == NULL) + return "SHA256"; /* fallback to SHA-256 */ + + return name; +} + +/* Extract public key bytes from a key pair for salt derivation */ +static int get_pk_bytes_from_key(EVP_PKEY * key, + buffer_t * pk) +{ + const char * name; + int ret; + + assert(key != NULL); + assert(pk != NULL); + + name = EVP_PKEY_get0_type_name(key); + if (name == NULL) + goto fail_name; + + if (IS_HYBRID_KEM(name)) { + pk->len = EVP_PKEY_get1_encoded_public_key(key, &pk->data); + if (pk->len == 0) + goto fail_name; + } else { + /* Pure ML-KEM: use DER encoding to match encap */ + pk->data = NULL; + ret = i2d_PUBKEY(key, &pk->data); + if (ret <= 0) + goto fail_name; + pk->len = (size_t) ret; + } + + return 0; + fail_name: + return -ECRYPT; +} + +/* Derive salt from public key bytes by hashing them */ +static int derive_salt_from_pk_bytes(buffer_t pk, + uint8_t * salt, + size_t salt_len) +{ + uint8_t hash[EVP_MAX_MD_SIZE]; + unsigned hash_len; + + assert(pk.data != NULL); + assert(salt != NULL); + + if (EVP_Digest(pk.data, pk.len, hash, &hash_len, + EVP_sha256(), NULL) != 1) + goto fail_digest; + + memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len); + + return 0; + fail_digest: + return -ECRYPT; +} + +/* Derive salt from two public key byte buffers (DHE) in canonical order */ +static int derive_salt_from_pk_bytes_dhe(buffer_t local, + buffer_t remote, + uint8_t * salt, + size_t salt_len) +{ + uint8_t * concat; + size_t concat_len; + uint8_t hash[EVP_MAX_MD_SIZE]; + unsigned hash_len; + size_t min_len; + int cmp; + + assert(local.data != NULL); + assert(remote.data != NULL); + assert(salt != NULL); + + concat_len = local.len + remote.len; + concat = OPENSSL_malloc(concat_len); + if (concat == NULL) + goto fail_malloc; + + /* Canonical order: compare and concatenate smaller first */ + min_len = local.len < remote.len ? local.len : remote.len; + cmp = memcmp(local.data, remote.data, min_len); + if (cmp < 0 || (cmp == 0 && local.len < remote.len)) { + memcpy(concat, local.data, local.len); + memcpy(concat + local.len, remote.data, remote.len); + } else { + memcpy(concat, remote.data, remote.len); + memcpy(concat + remote.len, local.data, local.len); + } + + if (EVP_Digest(concat, concat_len, hash, &hash_len, + EVP_sha256(), NULL) != 1) + goto fail_digest; + + OPENSSL_free(concat); + + memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len); + + return 0; + fail_digest: + OPENSSL_free(concat); + fail_malloc: + return -ECRYPT; +} + +/* Derive key using HKDF */ +#define OPc_u_str OSSL_PARAM_construct_utf8_string +#define OPc_o_str OSSL_PARAM_construct_octet_string +static int derive_key_hkdf(struct kdf_info * ki) +{ + EVP_KDF * kdf; + EVP_KDF_CTX * kctx; + OSSL_PARAM params[5]; + const char * digest; + int idx; + + digest = hash_nid_to_digest_name(ki->nid); + + kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + if (kdf == NULL) + goto fail_fetch; + + kctx = EVP_KDF_CTX_new(kdf); + if (kctx == NULL) + goto fail_ctx; + + idx = 0; + params[idx++] = OPc_u_str("digest", (char *) digest, 0); + params[idx++] = OPc_o_str("key", ki->secret.data, ki->secret.len); + params[idx++] = OPc_o_str("salt", ki->salt.data, ki->salt.len); + params[idx++] = OPc_o_str("info", ki->info.data, ki->info.len); + + params[idx] = OSSL_PARAM_construct_end(); + + if (EVP_KDF_derive(kctx, ki->key.data, ki->key.len, params) != 1) + goto fail_derive; + + EVP_KDF_CTX_free(kctx); + EVP_KDF_free(kdf); + + return 0; + + fail_derive: + EVP_KDF_CTX_free(kctx); + fail_ctx: + EVP_KDF_free(kdf); + fail_fetch: + return -ECRYPT; +} /* * Derive the common secret from - * - your public key pair (kp) - * - the remote public key (pub). + * - your public key pair (pkp) + * - the remote public key bytes (remote_pk). * Store it in a preallocated buffer (s). */ -static int __openssl_ecdh_derive_secret(EVP_PKEY * kp, - EVP_PKEY * pub, - uint8_t * s) +static int __openssl_dhe_derive(EVP_PKEY * pkp, + EVP_PKEY * pub, + buffer_t remote_pk, + int kdf, + uint8_t * s) { - EVP_PKEY_CTX * ctx; - int ret; - uint8_t * secret; - size_t secret_len; - - ctx = EVP_PKEY_CTX_new(kp, NULL); + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + buffer_t local_pk; + int ret; + uint8_t * secret; + size_t secret_len; + uint8_t salt_buf[HKDF_SALT_LEN]; + + /* Extract local public key bytes */ + local_pk.data = NULL; + ret = i2d_PUBKEY(pkp, &local_pk.data); + if (ret <= 0) + goto fail_local; + local_pk.len = (size_t) ret; + + /* Derive salt from both public keys */ + if (derive_salt_from_pk_bytes_dhe(local_pk, remote_pk, salt_buf, + HKDF_SALT_LEN) < 0) + goto fail_salt; + + ctx = EVP_PKEY_CTX_new(pkp, NULL); if (ctx == NULL) - goto fail_new; + goto fail_salt; ret = EVP_PKEY_derive_init(ctx); if (ret != 1) @@ -81,37 +293,78 @@ static int __openssl_ecdh_derive_secret(EVP_PKEY * kp, if (ret != 1) goto fail_derive; - /* Hash the secret for use as AES key. */ - mem_hash(HASH_SHA3_256, s, secret, secret_len); + ki.nid = kdf; + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.info.len = strlen(HKDF_INFO_DHE); + ki.info.data = (uint8_t *) HKDF_INFO_DHE; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt_buf; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); OPENSSL_free(secret); EVP_PKEY_CTX_free(ctx); + OPENSSL_free(local_pk.data); + + if (ret != 0) + return ret; return 0; fail_derive: OPENSSL_free(secret); fail_ctx: EVP_PKEY_CTX_free(ctx); - fail_new: + fail_salt: + OPENSSL_free(local_pk.data); + fail_local: return -ECRYPT; } -static int __openssl_ecdh_gen_key(void ** kp) +static int __openssl_dhe_gen_key(const char * algo, + EVP_PKEY ** kp) { EVP_PKEY_CTX * ctx = NULL; EVP_PKEY_CTX * kctx = NULL; EVP_PKEY * params = NULL; + int nid; + int type; int ret; - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + assert(algo != NULL); + assert(kp != NULL); + + nid = OBJ_txt2nid(algo); + if (nid == NID_undef) + return -ECRYPT; + + /* X25519 and X448: direct keygen context */ + if (nid == EVP_PKEY_X25519 || nid == EVP_PKEY_X448) { + kctx = EVP_PKEY_CTX_new_id(nid, NULL); + if (kctx == NULL) + goto fail_kctx; + + goto keygen; + } + /* EC and FFDHE: parameter generation first */ + type = (strncmp(algo, "ffdhe", 5) == 0) ? EVP_PKEY_DH : EVP_PKEY_EC; + + ctx = EVP_PKEY_CTX_new_id(type, NULL); if (ctx == NULL) - goto fail_new_id; + goto fail_ctx; ret = EVP_PKEY_paramgen_init(ctx); if (ret != 1) goto fail_paramgen; - ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1); + if (type == EVP_PKEY_EC) + ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid); + else /* EVP_PKEY_DH */ + ret = EVP_PKEY_CTX_set_dh_nid(ctx, nid); + if (ret != 1) goto fail_paramgen; @@ -121,187 +374,556 @@ static int __openssl_ecdh_gen_key(void ** kp) kctx = EVP_PKEY_CTX_new(params, NULL); if (kctx == NULL) - goto fail_keygen_init; + goto fail_kctx; + EVP_PKEY_free(params); + EVP_PKEY_CTX_free(ctx); + keygen: ret = EVP_PKEY_keygen_init(kctx); if (ret != 1) goto fail_keygen; - ret = EVP_PKEY_keygen(kctx, (EVP_PKEY **) kp); + ret = EVP_PKEY_keygen(kctx, kp); if (ret != 1) goto fail_keygen; - EVP_PKEY_free(params); EVP_PKEY_CTX_free(kctx); - EVP_PKEY_CTX_free(ctx); return 0; + fail_keygen: EVP_PKEY_CTX_free(kctx); - fail_keygen_init: - EVP_PKEY_free(params); + return -ECRYPT; + fail_kctx: + if (params != NULL) + EVP_PKEY_free(params); fail_paramgen: - EVP_PKEY_CTX_free(ctx); - fail_new_id: + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + fail_ctx: + return -ECRYPT; +} + +static int __openssl_kem_gen_key(const char * algo, + EVP_PKEY ** kp) +{ + EVP_PKEY_CTX * kctx; + int ret; + + assert(algo != NULL); + assert(kp != NULL); + + /* PQC KEM (ML-KEM-512, ML-KEM-768, ML-KEM-1024) or hybrid */ + kctx = EVP_PKEY_CTX_new_from_name(NULL, algo, NULL); + if (kctx == NULL) + goto fail_kctx; + + ret = EVP_PKEY_keygen_init(kctx); + if (ret != 1) + goto fail_keygen; + + ret = EVP_PKEY_keygen(kctx, kp); + if (ret != 1) + goto fail_keygen; + + EVP_PKEY_CTX_free(kctx); + + return 0; + + fail_keygen: + EVP_PKEY_CTX_free(kctx); + fail_kctx: return -ECRYPT; } -ssize_t openssl_ecdh_pkp_create(void ** pkp, - uint8_t * pk) +/* Determine hybrid KEM algorithm from raw key/ciphertext length */ +static const char * __openssl_hybrid_algo_from_len(size_t len) +{ + switch(len) { + case X25519MLKEM768_PKSZ: + return "X25519MLKEM768"; + case X25519MLKEM768_CTSZ: + return "X25519MLKEM768"; + case X448MLKEM1024_PKSZ: + return "X448MLKEM1024"; + default: + break; + } + + return NULL; +} + +static int __openssl_kex_gen_key(const char * algo, + EVP_PKEY ** kp) { - uint8_t * pos; - ssize_t len; + assert(algo != NULL); + assert(kp != NULL); + + /* Dispatch based on algorithm name prefix */ + if (IS_KEM_ALGORITHM(algo)) + return __openssl_kem_gen_key(algo, kp); + + return __openssl_dhe_gen_key(algo, kp); +} +ssize_t openssl_pkp_create(const char * algo, + EVP_PKEY ** pkp, + uint8_t * pk) +{ + uint8_t * pos; + buffer_t raw; + ssize_t len; + + assert(algo != NULL); assert(pkp != NULL); assert(*pkp == NULL); assert(pk != NULL); - if (__openssl_ecdh_gen_key(pkp) < 0) + if (__openssl_kex_gen_key(algo, pkp) < 0) goto fail_key; - pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */ - len = i2d_PUBKEY(*pkp, &pos); - if (len < 0) - goto fail_pubkey; + if (IS_HYBRID_KEM(algo)) { /* Raw encode hybrid KEM */ + raw.len = EVP_PKEY_get1_encoded_public_key(*pkp, &raw.data); + if (raw.len == 0) + goto fail_pubkey; + + memcpy(pk, raw.data, raw.len); + OPENSSL_free(raw.data); + + return (ssize_t) raw.len; + } else { /* DER encode standard algorithms */ + pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */ + len = i2d_PUBKEY(*pkp, &pos); + if (len < 0) + goto fail_pubkey; - return len; + return len; + } fail_pubkey: EVP_PKEY_free(*pkp); fail_key: return -ECRYPT; } -void openssl_ecdh_pkp_destroy(void * pkp) +/* Common KEM encapsulation - pub key and salt already prepared */ +static ssize_t __openssl_kem_encap(EVP_PKEY * pub, + uint8_t * salt, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + uint8_t * secret; + size_t secret_len; + size_t ct_len; + int ret; + + ctx = EVP_PKEY_CTX_new(pub, NULL); + if (ctx == NULL) + goto fail_ctx; + + ret = EVP_PKEY_encapsulate_init(ctx, NULL); + if (ret != 1) + goto fail_encap; + + /* Get required lengths */ + ret = EVP_PKEY_encapsulate(ctx, NULL, &ct_len, NULL, &secret_len); + if (ret != 1 || ct_len > MSGBUFSZ) + goto fail_encap; + + /* Allocate buffer for secret */ + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_encap; + + /* Perform encapsulation */ + ret = EVP_PKEY_encapsulate(ctx, ct, &ct_len, secret, &secret_len); + if (ret != 1) + goto fail_secret; + + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.nid = kdf; + ki.info.len = strlen(HKDF_INFO_ENCAP); + ki.info.data = (uint8_t *) HKDF_INFO_ENCAP; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + + if (ret != 0) + return -ECRYPT; + + return (ssize_t) ct_len; + + fail_secret: + OPENSSL_free(secret); + fail_encap: + EVP_PKEY_CTX_free(ctx); + fail_ctx: + return -ECRYPT; +} + +/* ML-KEM encapsulation - DER-encoded public key */ +ssize_t openssl_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY * pub; + uint8_t * pos; + uint8_t salt[HKDF_SALT_LEN]; + ssize_t ret; + + assert(pk.data != NULL); + assert(ct != NULL); + assert(s != NULL); + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + pos = pk.data; + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + goto fail_salt; + + ret = __openssl_kem_encap(pub, salt, ct, kdf, s); + + EVP_PKEY_free(pub); + + return ret; + fail_salt: + return -ECRYPT; +} + +/* Hybrid KEM encapsulation: raw-encoded public key */ +ssize_t openssl_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY * pub; + const char * algo; + uint8_t salt[HKDF_SALT_LEN]; + ssize_t ret; + + assert(pk.data != NULL); + assert(ct != NULL); + assert(s != NULL); + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + algo = __openssl_hybrid_algo_from_len(pk.len); + if (algo == NULL) + goto fail_salt; + + pub = EVP_PKEY_new_raw_public_key_ex(NULL, algo, NULL, + pk.data, pk.len); + if (pub == NULL) + goto fail_salt; + + ret = __openssl_kem_encap(pub, salt, ct, kdf, s); + + EVP_PKEY_free(pub); + + return ret; + fail_salt: + return -ECRYPT; +} + +/* KEM decapsulation - used by party that generated the keypair */ +int openssl_kem_decap(EVP_PKEY * priv, + buffer_t ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + buffer_t pk; + uint8_t * secret; + size_t secret_len; + int ret; + uint8_t salt[HKDF_SALT_LEN]; + + /* Extract public key bytes from private key */ + if (get_pk_bytes_from_key(priv, &pk) < 0) + goto fail_pk; + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + ctx = EVP_PKEY_CTX_new(priv, NULL); + if (ctx == NULL) + goto fail_salt; + + ret = EVP_PKEY_decapsulate_init(ctx, NULL); + if (ret != 1) + goto fail_ctx; + + /* Get required secret length */ + ret = EVP_PKEY_decapsulate(ctx, NULL, &secret_len, ct.data, ct.len); + if (ret != 1) + goto fail_ctx; + + /* Allocate buffer for secret */ + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_ctx; + + /* Perform decapsulation */ + ret = EVP_PKEY_decapsulate(ctx, secret, &secret_len, ct.data, ct.len); + if (ret != 1) + goto fail_secret; + + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.nid = kdf; + ki.info.len = strlen(HKDF_INFO_ENCAP); + ki.info.data = (uint8_t *) HKDF_INFO_ENCAP; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + OPENSSL_free(pk.data); + + if (ret != 0) + return ret; + + return 0; + + fail_secret: + OPENSSL_free(secret); + fail_ctx: + EVP_PKEY_CTX_free(ctx); + fail_salt: + OPENSSL_free(pk.data); + fail_pk: + return -ECRYPT; +} + +void openssl_pkp_destroy(EVP_PKEY * pkp) +{ + EVP_PKEY_free(pkp); +} + +int __openssl_get_curve(EVP_PKEY * pub, + char * algo) +{ + int ret; + size_t len = KEX_ALGO_BUFSZ; + + ret = EVP_PKEY_get_utf8_string_param(pub, "group", algo, len, &len); + return ret == 1 ? 0 : -ECRYPT; +} + +int openssl_get_algo_from_pk_der(buffer_t pk, + char * algo) +{ + uint8_t * pos; + EVP_PKEY * pub; + char * type_str; + + assert(pk.data != NULL); + assert(algo != NULL); + + pos = pk.data; + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + goto fail_decode; + + type_str = (char *) EVP_PKEY_get0_type_name(pub); + if (type_str == NULL) + goto fail_pub; + + strcpy(algo, type_str); + + if ((IS_EC_GROUP(algo) || IS_DH_GROUP(algo)) && + __openssl_get_curve(pub, algo) < 0) + goto fail_pub; + + EVP_PKEY_free(pub); + return 0; + + fail_pub: + EVP_PKEY_free(pub); + fail_decode: + return -ECRYPT; +} + +int openssl_get_algo_from_pk_raw(buffer_t pk, + char * algo) { - EVP_PKEY_free((EVP_PKEY *) pkp); + const char * hybrid_algo; + + assert(pk.data != NULL); + assert(algo != NULL); + + hybrid_algo = __openssl_hybrid_algo_from_len(pk.len); + if (hybrid_algo == NULL) + return -ECRYPT; + + strcpy(algo, hybrid_algo); + + return 0; } -int openssl_ecdh_derive(void * pkp, - buffer_t pk, - uint8_t * s) +int openssl_dhe_derive(EVP_PKEY * pkp, + buffer_t pk, + int kdf, + uint8_t * s) { uint8_t * pos; EVP_PKEY * pub; + assert(pkp != NULL); + assert(pk.data != NULL); + assert(s != NULL); + + /* X.509 DER decoding for DHE */ pos = pk.data; /* d2i_PUBKEY increments pos, don't use key ptr! */ pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); if (pub == NULL) - goto fail_pubkey; + goto fail_decode; - if (__openssl_ecdh_derive_secret(pkp, pub, s) < 0) - goto fail_key; + if (__openssl_dhe_derive(pkp, pub, pk, kdf, s) < 0) + goto fail_derive; EVP_PKEY_free(pub); return 0; - fail_pubkey: + fail_derive: EVP_PKEY_free(pub); - fail_key: + fail_decode: return -ECRYPT; } -/* - * AES encryption calls. If FRCT is disabled, we should generate a - * 128-bit random IV and append it to the packet. If the flow is - * reliable, we could initialize the context once, and consider the - * stream a single encrypted message to avoid initializing the - * encryption context for each packet. - */ - -int openssl_encrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out) +int openssl_encrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out) { - uint8_t * ptr; - uint8_t * iv; - int in_sz; - int out_sz; - int tmp_sz; - int ret; + uint8_t * ptr; + uint8_t * iv; + int in_sz; + int out_sz; + int tmp_sz; + int ret; + + assert(ctx != NULL); in_sz = (int) in.len; - out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + IVSZ); + out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + \ + ctx->ivsz + ctx->tagsz); if (out->data == NULL) goto fail_malloc; iv = out->data; - ptr = out->data + IVSZ; + ptr = out->data + ctx->ivsz; - if (random_buffer(iv, IVSZ) < 0) - goto fail_iv; + if (random_buffer(iv, ctx->ivsz) < 0) + goto fail_encrypt; - EVP_CIPHER_CTX_reset(ctx); + EVP_CIPHER_CTX_reset(ctx->evp_ctx); - ret = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); + ret = EVP_EncryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, ctx->key, iv); if (ret != 1) - goto fail_iv; + goto fail_encrypt; - ret = EVP_EncryptUpdate(ctx, ptr, &tmp_sz, in.data, in_sz); + ret = EVP_EncryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, in.data, in_sz); if (ret != 1) goto fail_encrypt; out_sz = tmp_sz; - ret = EVP_EncryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz); + ret = EVP_EncryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz); if (ret != 1) goto fail_encrypt; out_sz += tmp_sz; - EVP_CIPHER_CTX_cleanup(ctx); + /* For AEAD ciphers, get and append the authentication tag */ + if (ctx->tagsz > 0) { + ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_GET_TAG, + ctx->tagsz, ptr + out_sz); + if (ret != 1) + goto fail_encrypt; + out_sz += ctx->tagsz; + } assert(out_sz >= in_sz); - out->len = (size_t) out_sz + IVSZ; + out->len = (size_t) out_sz + ctx->ivsz; return 0; fail_encrypt: - EVP_CIPHER_CTX_cleanup(ctx); - fail_iv: free(out->data); fail_malloc: clrbuf(*out); return -ECRYPT; } -int openssl_decrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out) +int openssl_decrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out) { - uint8_t * ptr; - uint8_t * iv; - uint8_t * input; - int ret; - int out_sz; - int in_sz; - int tmp_sz; - - in_sz = (int) in.len - IVSZ; - if (in_sz < 0) + uint8_t * ptr; + uint8_t * iv; + uint8_t * input; + int ret; + int out_sz; + int in_sz; + int tmp_sz; + + assert(ctx != NULL); + + in_sz = (int) in.len - ctx->ivsz; + if (in_sz < ctx->tagsz) return -ECRYPT; - out->data = malloc(in_sz); + in_sz -= ctx->tagsz; + + out->data = malloc(in_sz + EVP_MAX_BLOCK_LENGTH); if (out->data == NULL) goto fail_malloc; iv = in.data; ptr = out->data; - input = in.data + IVSZ; + input = in.data + ctx->ivsz; - EVP_CIPHER_CTX_reset(ctx); + EVP_CIPHER_CTX_reset(ctx->evp_ctx); - ret = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv); + ret = EVP_DecryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, ctx->key, iv); if (ret != 1) - goto fail_decrypt_init; + goto fail_decrypt; + + /* For AEAD ciphers, set the expected authentication tag */ + if (ctx->tagsz > 0) { + uint8_t * tag = input + in_sz; + ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_SET_TAG, + ctx->tagsz, tag); + if (ret != 1) + goto fail_decrypt; + } - ret = EVP_DecryptUpdate(ctx, ptr, &tmp_sz, input, in_sz); + ret = EVP_DecryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, input, in_sz); if (ret != 1) goto fail_decrypt; out_sz = tmp_sz; - ret = EVP_DecryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz); + ret = EVP_DecryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz); if (ret != 1) goto fail_decrypt; @@ -313,22 +935,65 @@ int openssl_decrypt(void * ctx, return 0; fail_decrypt: - EVP_CIPHER_CTX_cleanup(ctx); - fail_decrypt_init: free(out->data); fail_malloc: clrbuf(*out); return -ECRYPT; } -void * openssl_crypt_create_ctx(void) +struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk) { - return (void *) EVP_CIPHER_CTX_new(); + struct ossl_crypt_ctx * ctx; + + assert(sk != NULL); + assert(sk->key != NULL); + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) + goto fail_malloc; + + memset(ctx, 0, sizeof(*ctx)); + + ctx->key = OPENSSL_secure_malloc(SYMMKEYSZ); + if (ctx->key == NULL) + goto fail_key; + + memcpy(ctx->key, sk->key, SYMMKEYSZ); + + ctx->cipher = EVP_get_cipherbynid(sk->nid); + if (ctx->cipher == NULL) + goto fail_cipher; + + ctx->ivsz = EVP_CIPHER_iv_length(ctx->cipher); + + /* Set tag size for AEAD ciphers (GCM, CCM, OCB, ChaCha20-Poly1305) */ + if (EVP_CIPHER_flags(ctx->cipher) & EVP_CIPH_FLAG_AEAD_CIPHER) + ctx->tagsz = 16; /* Standard AEAD tag length (128 bits) */ + + ctx->evp_ctx = EVP_CIPHER_CTX_new(); + if (ctx->evp_ctx == NULL) + goto fail_cipher; + + return ctx; + + fail_cipher: + OPENSSL_secure_clear_free(ctx->key, SYMMKEYSZ); + fail_key: + free(ctx); + fail_malloc: + return NULL; } -void openssl_crypt_destroy_ctx(void * ctx) +void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx) { - EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *) ctx); + if (ctx == NULL) + return; + + if (ctx->key != NULL) + OPENSSL_secure_clear_free(ctx->key, SYMMKEYSZ); + + EVP_CIPHER_CTX_free(ctx->evp_ctx); + free(ctx); } /* AUTHENTICATION */ @@ -442,14 +1107,24 @@ int openssl_load_privkey_file(const char * path, { FILE * fp; EVP_PKEY * pkey; + unsigned long err; + char errbuf[256]; fp = fopen(path, "r"); - if (fp == NULL) + if (fp == NULL) { + fprintf(stderr, "Failed to open %s\n", path); goto fail_file; + } pkey = PEM_read_PrivateKey(fp, NULL, NULL, ""); - if (pkey == NULL) + if (pkey == NULL) { + err = ERR_get_error(); + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + fprintf(stderr, + "OpenSSL error loading privkey from %s: %s\n", + path, errbuf); goto fail_key; + } fclose(fp); @@ -518,6 +1193,48 @@ int openssl_load_pubkey_file(const char * path, return -1; } +int openssl_load_pubkey_file_to_der(const char * path, + buffer_t * buf) +{ + FILE * fp; + EVP_PKEY * pkey; + int ret; + + assert(path != NULL); + assert(buf != NULL); + + memset(buf, 0, sizeof(*buf)); + + fp = fopen(path, "r"); + if (fp == NULL) + goto fail_file; + + pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + if (pkey == NULL) + goto fail_key; + + fclose(fp); + + /* Extract public key bytes in DER format */ + ret = get_pk_bytes_from_key(pkey, buf); + + EVP_PKEY_free(pkey); + + if (ret < 0) + goto fail_extract; + + return 0; + + fail_extract: + clrbuf(*buf); + return -1; + fail_key: + fclose(fp); + fail_file: + clrbuf(*buf); + return -1; +} + int openssl_load_pubkey_str(const char * str, void ** key) { @@ -547,28 +1264,123 @@ int openssl_load_pubkey_str(const char * str, return -1; } -int openssl_cmp_key(const void * key1, - const void * key2) +int openssl_load_pubkey_raw_file(const char * path, + buffer_t * buf) +{ + FILE * fp; + uint8_t tmp_buf[MSGBUFSZ]; + size_t bytes_read; + const char * algo; + + assert(path != NULL); + assert(buf != NULL); + + fp = fopen(path, "rb"); + if (fp == NULL) + goto fail_file; + + bytes_read = fread(tmp_buf, 1, MSGBUFSZ, fp); + if (bytes_read == 0) + goto fail_read; + + /* Validate that this is a known hybrid KEM format */ + algo = __openssl_hybrid_algo_from_len(bytes_read); + if (algo == NULL) + goto fail_read; + + buf->data = malloc(bytes_read); + if (buf->data == NULL) + goto fail_malloc; + + memcpy(buf->data, tmp_buf, bytes_read); + buf->len = bytes_read; + + return 0; + + fail_malloc: + fail_read: + fclose(fp); + fail_file: + clrbuf(*buf); + return -1; +} + +/* Determine hybrid KEM algorithm from raw private key length */ +static const char * __openssl_hybrid_algo_from_sk_len(size_t len) +{ + switch(len) { + case X25519MLKEM768_SKSZ: + return "X25519MLKEM768"; + case X448MLKEM1024_SKSZ: + return "X448MLKEM1024"; + default: + break; + } + + return NULL; +} + +int openssl_load_privkey_raw_file(const char * path, + void ** key) { - EVP_PKEY * pkey1; - EVP_PKEY * pkey2; + FILE * fp; + uint8_t tmp_buf[4096]; + size_t bytes_read; + const char * algo; + EVP_PKEY * pkey; + + assert(path != NULL); + assert(key != NULL); + + fp = fopen(path, "rb"); + if (fp == NULL) + goto fail_file; + + bytes_read = fread(tmp_buf, 1, sizeof(tmp_buf), fp); + fclose(fp); + + if (bytes_read == 0) + goto fail_read; + + /* Determine algorithm from key size */ + algo = __openssl_hybrid_algo_from_sk_len(bytes_read); + if (algo == NULL) + goto fail_read; + + pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algo, NULL, + tmp_buf, bytes_read); + /* Clear sensitive data from stack */ + OPENSSL_cleanse(tmp_buf, bytes_read); + if (pkey == NULL) + goto fail_read; + + *key = (void *) pkey; + + return 0; + + fail_read: + fail_file: + *key = NULL; + return -1; +} + +int openssl_cmp_key(const EVP_PKEY * key1, + const EVP_PKEY * key2) +{ assert(key1 != NULL); assert(key2 != NULL); - pkey1 = (EVP_PKEY *) key1; - pkey2 = (EVP_PKEY *) key2; - #if OPENSSL_VERSION_NUMBER >= 0x30000000L - return EVP_PKEY_eq(pkey1, pkey2) == 1 ? 0 : -1; + return EVP_PKEY_eq(key1, key2) == 1 ? 0 : -1; #else - return EVP_PKEY_cmp(pkey1, pkey2) == 1 ? 0 : -1; + return EVP_PKEY_cmp(key1, key2) == 1 ? 0 : -1; #endif } -void openssl_free_key(void * key) +void openssl_free_key(EVP_PKEY * key) { - EVP_PKEY_free((EVP_PKEY *) key); + EVP_PKEY_free(key); } int openssl_check_crt_name(void * crt, @@ -600,6 +1412,41 @@ int openssl_check_crt_name(void * crt, return -1; } +int openssl_get_crt_name(void * crt, + char * name) +{ + char * subj; + char * cn; + char * end; + X509 * xcrt; + + xcrt = (X509 *) crt; + + subj = X509_NAME_oneline(X509_get_subject_name(xcrt), NULL, 0); + if (subj == NULL) + goto fail_subj; + + cn = strstr(subj, "CN="); + if (cn == NULL) + goto fail_cn; + + cn += 3; /* Skip "CN=" */ + + /* Find end of CN (comma or slash for next field) */ + end = strpbrk(cn, ",/"); + if (end != NULL) + *end = '\0'; + + strcpy(name, cn); + free(subj); + + return 0; + fail_cn: + free(subj); + fail_subj: + return -1; +} + int openssl_crt_str(const void * crt, char * str) { @@ -704,37 +1551,48 @@ int openssl_verify_crt(void * store, return -1; } -int openssl_sign(void * pkp, +static const EVP_MD * select_md(EVP_PKEY * pkey, + int nid) +{ + if (EVP_PKEY_get_id(pkey) < 0) + return NULL; /* Provider-based (PQC) */ + + if (nid == NID_undef) + return NULL; /* Classical requires explicit nid */ + + return EVP_get_digestbynid(nid); +} + +int openssl_sign(EVP_PKEY * pkp, + int nid, buffer_t msg, buffer_t * sig) { - EVP_PKEY * pkey; - EVP_MD_CTX * mdctx; - size_t required; + EVP_MD_CTX * mdctx; + const EVP_MD * md; + size_t required; assert(pkp != NULL); assert(sig != NULL); - pkey = (EVP_PKEY *) pkp; - mdctx = EVP_MD_CTX_new(); if (!mdctx) goto fail_ctx; - if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) - goto fail_digest; + md = select_md(pkp, nid); - if (EVP_DigestSignUpdate(mdctx, msg.data, msg.len) != 1) + if (EVP_DigestSignInit(mdctx, NULL, md, NULL, pkp) != 1) goto fail_digest; - if (EVP_DigestSignFinal(mdctx, NULL, &required) != 1) + /* Get required signature buffer size */ + if (EVP_DigestSign(mdctx, NULL, &required, msg.data, msg.len) != 1) goto fail_digest; sig->data = malloc(required); if (sig->data == NULL) goto fail_digest; - if (EVP_DigestSignFinal(mdctx, sig->data, &required) != 1) + if (EVP_DigestSign(mdctx, sig->data, &required, msg.data, msg.len) != 1) goto fail_sign; sig->len = required; @@ -751,29 +1609,27 @@ int openssl_sign(void * pkp, return -1; } -int openssl_verify_sig(void * pk, - buffer_t msg, - buffer_t sig) +int openssl_verify_sig(EVP_PKEY * pk, + int nid, + buffer_t msg, + buffer_t sig) { - EVP_PKEY * pkey; - EVP_MD_CTX * mdctx; - int ret; + EVP_MD_CTX * mdctx; + const EVP_MD * md; + int ret; assert(pk != NULL); - pkey = (EVP_PKEY *) pk; - mdctx = EVP_MD_CTX_new(); if (!mdctx) goto fail_ctx; - if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) - goto fail_digest; + md = select_md(pk, nid); - if (EVP_DigestVerifyUpdate(mdctx, msg.data, msg.len) != 1) + if (EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pk) != 1) goto fail_digest; - ret = EVP_DigestVerifyFinal(mdctx, sig.data, sig.len); + ret = EVP_DigestVerify(mdctx, sig.data, sig.len, msg.data, msg.len); if (ret != 1) goto fail_digest; @@ -786,3 +1642,55 @@ int openssl_verify_sig(void * pk, clrbuf(sig); return -1; } + +ssize_t openssl_md_digest(int nid, + buffer_t in, + uint8_t * out) +{ + const EVP_MD * md; + unsigned int len; + + assert(in.data != NULL); + assert(out != NULL); + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return -1; + + if (EVP_Digest(in.data, in.len, out, &len, md, NULL) != 1) + return -1; + + return (ssize_t) len; +} + +ssize_t openssl_md_len(int nid) +{ + const EVP_MD * md; + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return -1; + + return (ssize_t) EVP_MD_get_size(md); +} + +int openssl_secure_malloc_init(size_t max, + size_t guard) +{ + return CRYPTO_secure_malloc_init(max, guard) == 1 ? 0 : -1; +} + +void openssl_secure_malloc_fini(void) +{ + CRYPTO_secure_malloc_done(); +} + +void * openssl_secure_malloc(size_t size) +{ + return OPENSSL_secure_malloc(size); +} + +void openssl_secure_free(void * ptr) +{ + OPENSSL_secure_free(ptr); +} diff --git a/src/lib/crypt/openssl.h b/src/lib/crypt/openssl.h index d4ee73b9..c28d0b4d 100644 --- a/src/lib/crypt/openssl.h +++ b/src/lib/crypt/openssl.h @@ -26,28 +26,52 @@ #ifndef OUROBOROS_LIB_CRYPT_OPENSSL_H #define OUROBOROS_LIB_CRYPT_OPENSSL_H -ssize_t openssl_ecdh_pkp_create(void ** pkp, - uint8_t * pk); +struct ossl_crypt_ctx; -void openssl_ecdh_pkp_destroy(void * pkp); +ssize_t openssl_pkp_create(const char * algo, + EVP_PKEY ** pkp, + uint8_t * pk); -int openssl_ecdh_derive(void * pkp, - buffer_t pk, - uint8_t * s); +void openssl_pkp_destroy(EVP_PKEY * pkp); -int openssl_encrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out); +int openssl_dhe_derive(EVP_PKEY * pkp, + buffer_t pk, + int kdf_nid, + uint8_t * s); -int openssl_decrypt(void * ctx, - uint8_t * key, - buffer_t in, - buffer_t * out); +ssize_t openssl_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); -void * openssl_crypt_create_ctx(void); +/* no X509 DER support yet for DHKEM public keys */ +ssize_t openssl_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); -void openssl_crypt_destroy_ctx(void * ctx); +int openssl_kem_decap(EVP_PKEY * priv, + buffer_t ct, + int kdf_nid, + uint8_t * s); + +int openssl_get_algo_from_pk_der(buffer_t pk, + char * algo); + +int openssl_get_algo_from_pk_raw(buffer_t pk, + char * algo); + +int openssl_encrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out); + +int openssl_decrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out); + +struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk); + +void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx); /* AUTHENTICATION */ @@ -76,15 +100,25 @@ int openssl_load_pubkey_file(const char * path, int openssl_load_pubkey_str(const char * str, void ** key); +int openssl_load_pubkey_file_to_der(const char * path, + buffer_t * buf); +int openssl_load_pubkey_raw_file(const char * path, + buffer_t * buf); + +int openssl_load_privkey_raw_file(const char * path, + void ** key); -int openssl_cmp_key(const void * key1, - const void * key2); +int openssl_cmp_key(const EVP_PKEY * key1, + const EVP_PKEY * key2); -void openssl_free_key(void * key); +void openssl_free_key(EVP_PKEY * key); int openssl_check_crt_name(void * crt, const char * name); +int openssl_get_crt_name(void * crt, + char * name); + int openssl_crt_str(const void * crt, char * str); @@ -101,12 +135,31 @@ int openssl_auth_add_crt_to_store(void * store, int openssl_verify_crt(void * store, void * crt); -int openssl_sign(void * pkp, +int openssl_sign(EVP_PKEY * pkp, + int md_nid, buffer_t msg, buffer_t * sig); -int openssl_verify_sig(void * pk, - buffer_t msg, - buffer_t sig); +int openssl_verify_sig(EVP_PKEY * pk, + int md_nid, + buffer_t msg, + buffer_t sig); + +ssize_t openssl_md_digest(int md_nid, + buffer_t in, + uint8_t * out); + +ssize_t openssl_md_len(int md_nid); + +/* Secure memory allocation */ +int openssl_secure_malloc_init(size_t max, + size_t guard); + +void openssl_secure_malloc_fini(void); + +void * openssl_secure_malloc(size_t size); + +void openssl_secure_free(void * ptr, + size_t size); #endif /* OUROBOROS_LIB_CRYPT_OPENSSL_H */ diff --git a/src/lib/dev.c b/src/lib/dev.c index cb483aca..2c0dbf28 100644 --- a/src/lib/dev.c +++ b/src/lib/dev.c @@ -502,8 +502,10 @@ static void flow_fini(int fd) pthread_rwlock_unlock(&ai.lock); } +#define IS_ENCRYPTED(crypt) ((crypt)->nid != NID_undef) +#define IS_ORDERED(flow) (flow.qs.in_order != 0) static int flow_init(struct flow_info * info, - buffer_t * sk) + struct crypt_sk * sk) { struct timespec now; struct flow * flow; @@ -542,16 +544,15 @@ static int flow_init(struct flow_info * info, flow->rcv_act = now; flow->crypt = NULL; - if (sk!= NULL && sk->data != NULL) { - assert(sk->len == SYMMKEYSZ); - flow->crypt = crypt_create_ctx(sk->data); + if (IS_ENCRYPTED(sk)) { + flow->crypt = crypt_create_ctx(sk); if (flow->crypt == NULL) goto fail_crypt; } assert(flow->frcti == NULL); - if (info->qs.in_order != 0) { + if (IS_ORDERED(flow->info)) { flow->frcti = frcti_create(fd, DELT_A, DELT_R, info->mpl); if (flow->frcti == NULL) goto fail_frcti; @@ -708,6 +709,11 @@ static void init(int argc, goto fail_timerwheel; } + if (crypt_secure_malloc_init(PROC_SECMEM_MAX) < 0) { + fprintf(stderr, "FATAL: Could not init secure malloc.\n"); + goto fail_timerwheel; + } + #if defined PROC_FLOW_STATS if (strstr(argv[0], "ipcpd") == NULL) { sprintf(procstr, "proc.%d", getpid()); @@ -823,12 +829,13 @@ __attribute__((section(FINI_SECTION))) __typeof__(fini) * __fini = fini; int flow_accept(qosspec_t * qs, const struct timespec * timeo) { - struct flow_info flow; - uint8_t buf[SOCK_BUF_SIZE]; - buffer_t msg = {SOCK_BUF_SIZE, buf}; - buffer_t sk; - int fd; - int err; + struct flow_info flow; + struct crypt_sk crypt; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + uint8_t key[SYMMKEYSZ]; + int fd; + int err; #ifdef QOS_DISABLE_CRC if (qs != NULL) @@ -846,13 +853,15 @@ int flow_accept(qosspec_t * qs, if (err < 0) return err; - err = flow__irm_result_des(&msg, &flow, &sk); + crypt.key = key; + + err = flow__irm_result_des(&msg, &flow, &crypt); if (err < 0) return err; - fd = flow_init(&flow, &sk); + fd = flow_init(&flow, &crypt); - freebuf(sk); + explicit_bzero(key, SYMMKEYSZ); if (qs != NULL) *qs = flow.qs; @@ -864,12 +873,13 @@ int flow_alloc(const char * dst, qosspec_t * qs, const struct timespec * timeo) { - struct flow_info flow; - uint8_t buf[SOCK_BUF_SIZE]; - buffer_t msg = {SOCK_BUF_SIZE, buf}; - buffer_t sk; /* symmetric key */ - int fd; - int err; + struct flow_info flow; + struct crypt_sk crypt; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + uint8_t key[SYMMKEYSZ]; + int fd; + int err; #ifdef QOS_DISABLE_CRC if (qs != NULL) @@ -890,13 +900,15 @@ int flow_alloc(const char * dst, return err; } - err = flow__irm_result_des(&msg, &flow, &sk); + crypt.key = key; + + err = flow__irm_result_des(&msg, &flow, &crypt); if (err < 0) return err; - fd = flow_init(&flow, &sk); + fd = flow_init(&flow, &crypt); - freebuf(sk); + explicit_bzero(key, SYMMKEYSZ); if (qs != NULL) *qs = flow.qs; @@ -907,11 +919,13 @@ int flow_alloc(const char * dst, int flow_join(const char * dst, const struct timespec * timeo) { - struct flow_info flow; - uint8_t buf[SOCK_BUF_SIZE]; - buffer_t msg = {SOCK_BUF_SIZE, buf}; - int fd; - int err; + struct flow_info flow; + struct crypt_sk crypt; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + uint8_t key[SYMMKEYSZ]; + int fd; + int err; memset(&flow, 0, sizeof(flow)); @@ -925,11 +939,15 @@ int flow_join(const char * dst, if (err < 0) return err; - err = flow__irm_result_des(&msg, &flow, NULL); + crypt.key = key; + + err = flow__irm_result_des(&msg, &flow, &crypt); if (err < 0) return err; - fd = flow_init(&flow, NULL); + fd = flow_init(&flow, &crypt); + + explicit_bzero(key, SYMMKEYSZ); return fd; } @@ -1785,7 +1803,8 @@ ssize_t fevent(struct flow_set * set, int np1_flow_alloc(pid_t n_pid, int flow_id) { - struct flow_info flow; + struct flow_info flow; + struct crypt_sk crypt = { .nid = NID_undef, .key = NULL }; memset(&flow, 0, sizeof(flow)); @@ -1795,7 +1814,7 @@ int np1_flow_alloc(pid_t n_pid, flow.mpl = 0; flow.n_1_pid = n_pid; /* This "flow" is upside-down! */ - return flow_init(&flow, NULL); + return flow_init(&flow, &crypt); } int np1_flow_dealloc(int flow_id, @@ -1859,9 +1878,11 @@ int ipcp_flow_req_arr(const buffer_t * dst, const buffer_t * data) { struct flow_info flow; - uint8_t buf[SOCK_BUF_SIZE]; - buffer_t msg = {SOCK_BUF_SIZE, buf}; - int err; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + struct crypt_sk crypt; + uint8_t key[SYMMKEYSZ]; + int err; memset(&flow, 0, sizeof(flow)); @@ -1878,17 +1899,23 @@ int ipcp_flow_req_arr(const buffer_t * dst, if (err < 0) return err; - err = flow__irm_result_des(&msg, &flow, NULL); + crypt.key = key; + + err = flow__irm_result_des(&msg, &flow, &crypt); if (err < 0) return err; + assert(crypt.nid == NID_undef); /* np1 flows are not encrypted */ + /* inverted for np1_flow */ flow.n_1_pid = flow.n_pid; flow.n_pid = getpid(); flow.mpl = 0; flow.qs = qos_np1; - return flow_init(&flow, NULL); + crypt.nid = NID_undef; + + return flow_init(&flow, &crypt); } int ipcp_flow_alloc_reply(int fd, diff --git a/src/lib/pb/irm.proto b/src/lib/pb/irm.proto index 75f5f350..5d0ee611 100644 --- a/src/lib/pb/irm.proto +++ b/src/lib/pb/irm.proto @@ -91,8 +91,9 @@ message irm_msg { optional sint32 mpl = 20; optional string comp = 21; optional bytes pk = 22; /* piggyback */ - optional bytes symmkey = 23; - optional uint32 timeo_sec = 24; - optional uint32 timeo_nsec = 25; - optional sint32 result = 26; + optional uint32 timeo_sec = 23; + optional uint32 timeo_nsec = 24; + optional sint32 result = 25; + optional bytes sym_key = 26; /* symmetric encryption key */ + optional sint32 cipher_nid = 27; /* cipher NID */ } diff --git a/src/lib/protobuf.c b/src/lib/protobuf.c index 6df4e810..bd6c179e 100644 --- a/src/lib/protobuf.c +++ b/src/lib/protobuf.c @@ -23,6 +23,7 @@ #define _DEFAULT_SOURCE #include +#include #include #include @@ -96,6 +97,8 @@ struct flow_info flow_info_msg_to_s(const flow_info_msg_t * msg) assert(msg != NULL); + memset(&s, 0, sizeof(s)); + s.id = msg->id; s.n_pid = msg->n_pid; s.n_1_pid = msg->n_1_pid; diff --git a/src/lib/serdes-irm.c b/src/lib/serdes-irm.c index 3aea0617..a0fdbec2 100644 --- a/src/lib/serdes-irm.c +++ b/src/lib/serdes-irm.c @@ -24,6 +24,7 @@ #include "config.h" +#include #include #include #include @@ -133,16 +134,13 @@ int flow_join__irm_req_ser(buffer_t * buf, IRM_MSG_CODE__IRM_FLOW_JOIN); } -int flow__irm_result_des(buffer_t * buf, - struct flow_info * flow, - buffer_t * sk) +int flow__irm_result_des(buffer_t * buf, + struct flow_info * flow, + struct crypt_sk * sk) { irm_msg_t * msg; int err; - if (sk != NULL) - sk->data = NULL; - msg = irm_msg__unpack(NULL, buf->len, buf->data); if (msg == NULL) { err = -EIRMD; @@ -166,13 +164,15 @@ int flow__irm_result_des(buffer_t * buf, *flow = flow_info_msg_to_s(msg->flow_info); - if (sk != NULL) { - sk->len = msg->symmkey.len; - sk->data = msg->symmkey.data; + if (msg->has_cipher_nid) + sk->nid = msg->cipher_nid; + else + sk->nid = NID_undef; - msg->symmkey.data = NULL; - msg->symmkey.len = 0; - } + if (msg->sym_key.len == SYMMKEYSZ) + memcpy(sk->key, msg->sym_key.data, SYMMKEYSZ); + else + memset(sk->key, 0, SYMMKEYSZ); irm_msg__free_unpacked(msg, NULL); diff --git a/src/lib/tests/CMakeLists.txt b/src/lib/tests/CMakeLists.txt index 69fdf18b..6ab69bd1 100644 --- a/src/lib/tests/CMakeLists.txt +++ b/src/lib/tests/CMakeLists.txt @@ -1,14 +1,19 @@ get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here auth_test.c + auth_test_pqc.c bitmap_test.c btree_test.c crc32_test.c crypt_test.c hash_test.c + kex_test.c + kex_test_pqc.c md5_test.c sha3_test.c shm_rbuff_test.c @@ -33,8 +38,11 @@ endif() foreach (test ${tests_to_run}) get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${PARENT_DIR}_test ${test_name}) + add_test(${TEST_PREFIX}/${test_name} ${CMAKE_CURRENT_BINARY_DIR}/${PARENT_DIR}_test ${test_name}) endforeach (test) -set_property(TEST auth_test PROPERTY SKIP_RETURN_CODE 1) -set_property(TEST crypt_test PROPERTY SKIP_RETURN_CODE 1) +set_property(TEST ${TEST_PREFIX}/auth_test PROPERTY SKIP_RETURN_CODE 1) +set_property(TEST ${TEST_PREFIX}/auth_test_pqc PROPERTY SKIP_RETURN_CODE 1) +set_property(TEST ${TEST_PREFIX}/crypt_test PROPERTY SKIP_RETURN_CODE 1) +set_property(TEST ${TEST_PREFIX}/kex_test PROPERTY SKIP_RETURN_CODE 1) +set_property(TEST ${TEST_PREFIX}/kex_test_pqc PROPERTY SKIP_RETURN_CODE 1) diff --git a/src/lib/tests/auth_test.c b/src/lib/tests/auth_test.c index 896c42b0..16c88f31 100644 --- a/src/lib/tests/auth_test.c +++ b/src/lib/tests/auth_test.c @@ -22,111 +22,14 @@ #include "config.h" -#include +#include #include #include #include -#define TEST_MSG_SIZE 1500 +#include -/* -* Certificates created following the guide -* Building an openssl certificate authority -* on -* https://community.f5.com/kb/technicalarticles/ -*/ - -/* Root certificate for CA ca.unittest.o7s */ -static const char * root_ca_crt = \ -"-----BEGIN CERTIFICATE-----\n" -"MIICXTCCAgOgAwIBAgIURlENlCOy1OsA/AXFscPUQ2li8OYwCgYIKoZIzj0EAwIw\n" -"fDELMAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAK\n" -"BgNVBAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51\n" -"bml0dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASAwHhcNMjUwODAzMTg1MzE1WhcN\n" -"NDUwNzI5MTg1MzE1WjB8MQswCQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQ4wDAYD\n" -"VQQHDAVHaGVudDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n" -"GDAWBgNVBAMMD2NhLnVuaXR0ZXN0Lm83czEQMA4GCSqGSIb3DQEJARYBIDBZMBMG\n" -"ByqGSM49AgEGCCqGSM49AwEHA0IABEPMseCScbd/d5TlHmyYVszn/YGVeNdUCnFR\n" -"naOr95WlTNo3MyKKBuoiEFwHhjPASgXr/VDVjJLSyM3JUPebAcGjYzBhMB0GA1Ud\n" -"DgQWBBQkxjMILHH6lZ+rnCMnD/63GO3y1zAfBgNVHSMEGDAWgBQkxjMILHH6lZ+r\n" -"nCMnD/63GO3y1zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAKBggq\n" -"hkjOPQQDAgNIADBFAiEA1jVJWW4idkCgAYv0m2LT9C33Dq42aLyRkJ+9YdzDqLwC\n" -"IHT6MS4I0k52YP/hxoqWVBbpOW79PKYMRLyXTk1r7+Fa\n" -"-----END CERTIFICATE-----\n"; - - -/* Certificate for intermediary im.unittest.o7s used for signing */ -static const char * intermediate_ca_crt = \ -"-----BEGIN CERTIFICATE-----\n" -"MIICbTCCAhOgAwIBAgICEAMwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCQkUxDDAK\n" -"BgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNVBAoMA283czEVMBMGA1UE\n" -"CwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0dGVzdC5vN3MxEDAOBgkq\n" -"hkiG9w0BCQEWASAwHhcNMjUwODAzMTkwMjU3WhcNNDUwNzI5MTkwMjU3WjBaMQsw\n" -"CQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQwwCgYDVQQKDANvN3MxFTATBgNVBAsM\n" -"DHVuaXR0ZXN0Lm83czEYMBYGA1UEAwwPaW0udW5pdHRlc3QubzdzMFkwEwYHKoZI\n" -"zj0CAQYIKoZIzj0DAQcDQgAEdlra08XItIPtVl5veaq4UF6LIcBXj2mZFqKNEXFh\n" -"l9uAz6UAbIc+FUPNfom6dwKbg/AjQ82a100eh6K/jCY7eKOBpjCBozAdBgNVHQ4E\n" -"FgQUy8Go8BIO6i0lJ+mgBr9lvh2L0eswHwYDVR0jBBgwFoAUJMYzCCxx+pWfq5wj\n" -"Jw/+txjt8tcwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEQYD\n" -"VR0fBAowCDAGoASgAoYAMCoGCCsGAQUFBwEBBB4wHDAMBggrBgEFBQcwAoYAMAwG\n" -"CCsGAQUFBzABhgAwCgYIKoZIzj0EAwIDSAAwRQIhAN3ZYhqu6mVLGidmONsbANk5\n" -"rzT6aHJcmvj19OxMusaXAiBKy0gBFCri/GLizi4wZo09wf31yZMqfr8IrApvPaLw\n" -"qA==\n" -"-----END CERTIFICATE-----\n"; - -/* Server test-1.unittest.o7s private-public key pair */ -static const char * server_ec_pkp = \ -"-----BEGIN EC PRIVATE KEY-----\n" -"MHcCAQEEIA4/bcmquVvGrY4+TtfnFSy1SpXs896r5xJjGuD6NmGRoAoGCCqGSM49\n" -"AwEHoUQDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2M0YzRKFKeV48tG5eD+MBaTrT\n" -"eoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n" -"-----END EC PRIVATE KEY-----\n"; - -/* Public key for the Private key */ -static const char * server_ec_pk = \ -"-----BEGIN PUBLIC KEY-----\n" -"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2\n" -"M0YzRKFKeV48tG5eD+MBaTrTeoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n" -"-----END PUBLIC KEY-----\n"; - -/* Valid signed server certificate for test-1.unittest.o7s */ -#define SSC_TEXT_SIZE 2295 /* size of cleartext certificate */ -static const char * signed_server_crt = \ -"-----BEGIN CERTIFICATE-----\n" -"MIIDiTCCAy+gAwIBAgICEAUwCgYIKoZIzj0EAwIwWjELMAkGA1UEBhMCQkUxDDAK\n" -"BgNVBAgMA09WTDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n" -"GDAWBgNVBAMMD2ltLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDgxODQ4NTNaFw00NTA4\n" -"MDMxODQ4NTNaMG4xCzAJBgNVBAYTAkJFMQwwCgYDVQQIDANPVkwxDjAMBgNVBAcM\n" -"BUdoZW50MQwwCgYDVQQKDANvN3MxFTATBgNVBAsMDHVuaXR0ZXN0Lm83czEcMBoG\n" -"A1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czBZMBMGByqGSM49AgEGCCqGSM49AwEH\n" -"A0IABOAUjob9+quGwjC0pCWgr88Gd6T8tjNGM0ShSnlePLRuXg/jAWk603qB1HEX\n" -"6c9BDv4p6txVQ8xKAENjVqZ89JCjggHPMIIByzAJBgNVHRMEAjAAMBEGCWCGSAGG\n" -"+EIBAQQEAwIGQDA4BglghkgBhvhCAQ0EKxYpbzdzIHVuaXR0ZXN0IEdlbmVyYXRl\n" -"ZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFI+htsK0xxy6e1CqCyxn7mqi\n" -"wRrpMIGoBgNVHSMEgaAwgZ2AFMvBqPASDuotJSfpoAa/Zb4di9HroYGApH4wfDEL\n" -"MAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNV\n" -"BAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0\n" -"dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASCCAhADMA4GA1UdDwEB/wQEAwIFoDAT\n" -"BgNVHSUEDDAKBggrBgEFBQcDATAoBgNVHR8EITAfMB2gG6AZhhdodHRwczovL291\n" -"cm9ib3Jvcy5yb2NrczBYBggrBgEFBQcBAQRMMEowIwYIKwYBBQUHMAKGF2h0dHBz\n" -"Oi8vb3Vyb2Jvcm9zLnJvY2tzMCMGCCsGAQUFBzABhhdodHRwczovL291cm9ib3Jv\n" -"cy5yb2NrczAKBggqhkjOPQQDAgNIADBFAiBZuw/Yb2pq925H7pEiOXr4fMo0wknz\n" -"ktkxoHAFbjQEPQIhAMInHI7lvRmS0IMw1wBF/WlUZWKvhyU/TeMIZfk/JGCS\n" -"-----END CERTIFICATE-----\n"; - -/* Self-signed by server test-1.unittest.o7s using its key */ -static const char * server_crt = \ -"-----BEGIN CERTIFICATE-----\n" -"MIIBfjCCASWgAwIBAgIUB5VYxp7i+sgYjvLiwfpf0W5NfqQwCgYIKoZIzj0EAwIw\n" -"HjEcMBoGA1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDMxOTI4MzVa\n" -"Fw00NTA3MjkxOTI4MzVaMB4xHDAaBgNVBAMME3Rlc3QtMS51bml0dGVzdC5vN3Mw\n" -"WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATgFI6G/fqrhsIwtKQloK/PBnek/LYz\n" -"RjNEoUp5Xjy0bl4P4wFpOtN6gdRxF+nPQQ7+KercVUPMSgBDY1amfPSQo0EwPzAe\n" -"BgNVHREEFzAVghN0ZXN0LTEudW5pdHRlc3QubzdzMB0GA1UdDgQWBBSPobbCtMcc\n" -"untQqgssZ+5qosEa6TAKBggqhkjOPQQDAgNHADBEAiAoFC/rqgrRXmMUx4y5cPbv\n" -"jOKpoL3FpehRgGkPatmL/QIgMRHc2TSGo6q1SG22Xt1dHAIBsaN2AlSfhjKULMH5\n" -"gRo=\n" -"-----END CERTIFICATE-----\n"; +#define TEST_MSG_SIZE 1500 static int test_auth_create_destroy_ctx(void) { @@ -156,7 +59,7 @@ static int test_load_free_crt(void) TEST_START(); - if (crypt_load_crt_str(root_ca_crt, &crt) < 0) { + if (crypt_load_crt_str(root_ca_crt_ec, &crt) < 0) { printf("Failed to load certificate string.\n"); goto fail_load; } @@ -178,7 +81,7 @@ static int test_crypt_get_pubkey_crt(void) TEST_START(); - if (crypt_load_crt_str(signed_server_crt, &crt) < 0) { + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { printf("Failed to load server certificate from string.\n"); goto fail_load; } @@ -208,7 +111,7 @@ static int test_check_crt_name(void) TEST_START(); - if (crypt_load_crt_str(signed_server_crt, &crt) < 0) { + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { printf("Failed to load certificate from string.\n"); goto fail_load; } @@ -241,7 +144,7 @@ static int test_load_free_privkey(void) TEST_START(); - if (crypt_load_privkey_str(server_ec_pkp, &key) < 0) { + if (crypt_load_privkey_str(server_pkp_ec, &key) < 0) { printf("Failed to load server key pair from string.\n"); goto fail_load; } @@ -262,7 +165,7 @@ static int test_load_free_pubkey(void) TEST_START(); - if (crypt_load_pubkey_str(server_ec_pk, &key) < 0) { + if (crypt_load_pubkey_str(server_pk_ec, &key) < 0) { printf("Failed to load server public key from string.\n"); goto fail_load; } @@ -285,12 +188,12 @@ static int test_crypt_check_pubkey_crt(void) TEST_START(); - if (crypt_load_crt_str(signed_server_crt, &crt) < 0) { + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { printf("Failed to load public certificate from string.\n"); goto fail_crt; } - if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) { + if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) { printf("Failed to load public key from string.\n"); goto fail_pubkey; } @@ -337,7 +240,7 @@ static int test_store_add(void) goto fail_create; } - if (crypt_load_crt_str(root_ca_crt, &_root_ca_crt) < 0) { + if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) { printf("Failed to load root crt from string.\n"); goto fail_load; } @@ -369,7 +272,7 @@ static int test_verify_crt(void) void * _server_crt; void * _signed_server_crt; void * _root_ca_crt; - void * _intermediate_ca_crt; + void * _im_ca_crt; TEST_START(); @@ -379,24 +282,24 @@ static int test_verify_crt(void) goto fail_create_ctx; } - if (crypt_load_crt_str(server_crt, &_server_crt) < 0) { + if (crypt_load_crt_str(server_crt_ec, &_server_crt) < 0) { printf("Failed to load self-signed crt from string.\n"); goto fail_load_server_crt; } - if (crypt_load_crt_str(signed_server_crt, &_signed_server_crt) < 0) { + if (crypt_load_crt_str(signed_server_crt_ec, &_signed_server_crt) < 0) { printf("Failed to load signed crt from string.\n"); goto fail_load_signed_server_crt; } - if (crypt_load_crt_str(root_ca_crt, &_root_ca_crt) < 0) { + if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) { printf("Failed to load root crt from string.\n"); goto fail_load_root_ca_crt; } - if (crypt_load_crt_str(intermediate_ca_crt, &_intermediate_ca_crt) < 0) { + if (crypt_load_crt_str(im_ca_crt_ec, &_im_ca_crt) < 0) { printf("Failed to load intermediate crt from string.\n"); - goto fail_load_intermediate_ca_crt; + goto fail_load_im_ca_crt; } if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) { @@ -404,7 +307,7 @@ static int test_verify_crt(void) goto fail_verify; } - if (auth_add_crt_to_store(auth, _intermediate_ca_crt) < 0) { + if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) { printf("Failed to add intermediate ca crt to auth store.\n"); goto fail_verify; } @@ -419,7 +322,7 @@ static int test_verify_crt(void) goto fail_verify; } - crypt_free_crt(_intermediate_ca_crt); + crypt_free_crt(_im_ca_crt); crypt_free_crt(_root_ca_crt); crypt_free_crt(_signed_server_crt); crypt_free_crt(_server_crt); @@ -430,8 +333,8 @@ static int test_verify_crt(void) return TEST_RC_SUCCESS; fail_verify: - crypt_free_crt(_intermediate_ca_crt); - fail_load_intermediate_ca_crt: + crypt_free_crt(_im_ca_crt); + fail_load_im_ca_crt: crypt_free_crt(_root_ca_crt); fail_load_root_ca_crt: crypt_free_crt(_signed_server_crt); @@ -462,22 +365,22 @@ int test_auth_sign(void) goto fail_init; } - if (crypt_load_privkey_str(server_ec_pkp, &pkp) < 0) { + if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) { printf("Failed to load server key pair from string.\n"); goto fail_init; } - if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) { + if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) { printf("Failed to load public key.\n"); goto fail_pubkey; } - if (auth_sign(pkp, msg, &sig) < 0) { + if (auth_sign(pkp, 0, msg, &sig) < 0) { printf("Failed to sign message.\n"); goto fail_sign; } - if (auth_verify_sig(pk, msg, sig) < 0) { + if (auth_verify_sig(pk, 0, msg, sig) < 0) { printf("Failed to verify signature.\n"); goto fail_verify; } @@ -519,17 +422,17 @@ int test_auth_bad_signature(void) goto fail_init; } - if (crypt_load_privkey_str(server_ec_pkp, &pkp) < 0) { + if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) { printf("Failed to load server key pair from string.\n"); goto fail_init; } - if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) { + if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) { printf("Failed to load public key.\n"); goto fail_pubkey; } - if (auth_sign(pkp, msg, &sig) < 0) { + if (auth_sign(pkp, 0, msg, &sig) < 0) { printf("Failed to sign message.\n"); goto fail_sign; } @@ -546,7 +449,7 @@ int test_auth_bad_signature(void) goto fail_malloc; } - if (auth_verify_sig(pk, msg, fake_sig) == 0) { + if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) { printf("Failed to detect bad signature.\n"); goto fail_verify; } @@ -579,7 +482,7 @@ int test_crt_str(void) TEST_START(); - if (crypt_load_crt_str(signed_server_crt, &crt) < 0) { + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { printf("Failed to load certificate from string.\n"); goto fail_load; } diff --git a/src/lib/tests/auth_test_pqc.c b/src/lib/tests/auth_test_pqc.c new file mode 100644 index 00000000..349636d2 --- /dev/null +++ b/src/lib/tests/auth_test_pqc.c @@ -0,0 +1,356 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test of the PQC authentication functions (ML-DSA-65) + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include + +#define TEST_MSG_SIZE 1500 + +static int test_auth_create_destroy_ctx(void) +{ + struct auth_ctx * ctx; + + TEST_START(); + + ctx = auth_create_ctx(); + if (ctx == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create; + } + + auth_destroy_ctx(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_crt(void) +{ + void * crt; + + TEST_START(); + + if (crypt_load_crt_str(root_ca_crt_ml, &crt) < 0) { + printf("Failed to load root crt from string.\n"); + goto fail_load; + } + + crypt_free_crt(crt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_privkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_privkey_str(server_pkp_ml, &key) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_load; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_pubkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_pubkey_str(server_pk_ml, &key) < 0) { + printf("Failed to load server public key from string.\n"); + goto fail_load; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_verify_crt(void) +{ + struct auth_ctx * auth; + void * _server_crt; + void * _signed_server_crt; + void * _root_ca_crt; + void * _im_ca_crt; + + TEST_START(); + + auth = auth_create_ctx(); + if (auth == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create_ctx; + } + + if (crypt_load_crt_str(server_crt_ml, &_server_crt) < 0) { + printf("Failed to load self-signed crt from string.\n"); + goto fail_load_server_crt; + } + + if (crypt_load_crt_str(signed_server_crt_ml, &_signed_server_crt) < 0) { + printf("Failed to load signed crt from string.\n"); + goto fail_load_signed_server_crt; + } + + if (crypt_load_crt_str(root_ca_crt_ml, &_root_ca_crt) < 0) { + printf("Failed to load root crt from string.\n"); + goto fail_load_root_ca_crt; + } + + if (crypt_load_crt_str(im_ca_crt_ml, &_im_ca_crt) < 0) { + printf("Failed to load intermediate crt from string.\n"); + goto fail_load_im_ca_crt; + } + + if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) { + printf("Failed to add root ca crt to auth store.\n"); + goto fail_verify; + } + + if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) { + printf("Failed to add intermediate ca crt to auth store.\n"); + goto fail_verify; + } + + if (auth_verify_crt(auth, _signed_server_crt) < 0) { + printf("Failed to verify signed crt with ca crt.\n"); + goto fail_verify; + } + + if (auth_verify_crt(auth, _server_crt) == 0) { + printf("Failed to detect untrusted crt.\n"); + goto fail_verify; + } + + crypt_free_crt(_im_ca_crt); + crypt_free_crt(_root_ca_crt); + crypt_free_crt(_signed_server_crt); + crypt_free_crt(_server_crt); + + auth_destroy_ctx(auth); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + crypt_free_crt(_im_ca_crt); + fail_load_im_ca_crt: + crypt_free_crt(_root_ca_crt); + fail_load_root_ca_crt: + crypt_free_crt(_signed_server_crt); + fail_load_signed_server_crt: + crypt_free_crt(_server_crt); + fail_load_server_crt: + auth_destroy_ctx(auth); + fail_create_ctx: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_auth_sign(void) +{ + uint8_t buf[TEST_MSG_SIZE]; + void * pkp; + void * pk; + buffer_t msg; + buffer_t sig; + + TEST_START(); + + msg.data = buf; + msg.len = sizeof(buf); + + if (random_buffer(msg.data, msg.len) < 0) { + printf("Failed to generate random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) { + printf("Failed to load public key from string.\n"); + goto fail_pubkey; + } + + if (auth_sign(pkp, 0, msg, &sig) < 0) { + printf("Failed to sign message.\n"); + goto fail_sign; + } + + if (auth_verify_sig(pk, 0, msg, sig) < 0) { + printf("Failed to verify signature.\n"); + goto fail_verify; + } + + freebuf(sig); + + crypt_free_key(pk); + crypt_free_key(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + freebuf(sig); + fail_sign: + crypt_free_key(pk); + fail_pubkey: + crypt_free_key(pkp); + fail_init: + return TEST_RC_FAIL; +} + +static int test_auth_bad_signature(void) +{ + uint8_t buf[TEST_MSG_SIZE]; + void * pkp; + void * pk; + buffer_t msg; + buffer_t sig; + buffer_t fake_sig; + + TEST_START(); + + msg.data = buf; + msg.len = sizeof(buf); + + if (random_buffer(msg.data, msg.len) < 0) { + printf("Failed to generate random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) { + printf("Failed to load public key from string.\n"); + goto fail_pubkey; + } + + if (auth_sign(pkp, 0, msg, &sig) < 0) { + printf("Failed to sign message.\n"); + goto fail_sign; + } + + fake_sig.data = malloc(sig.len); + if (fake_sig.data == NULL) { + printf("Failed to allocate memory for fake signature.\n"); + goto fail_malloc; + } + + fake_sig.len = sig.len; + if (random_buffer(fake_sig.data, fake_sig.len) < 0) { + printf("Failed to generate random fake signature.\n"); + goto fail_malloc; + } + + if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) { + printf("Failed to detect bad ML-DSA-65 signature.\n"); + goto fail_verify; + } + + freebuf(fake_sig); + freebuf(sig); + + crypt_free_key(pk); + crypt_free_key(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + freebuf(fake_sig); + fail_malloc: + freebuf(sig); + fail_sign: + crypt_free_key(pk); + fail_pubkey: + crypt_free_key(pkp); + fail_init: + return TEST_RC_FAIL; +} + +int auth_test_pqc(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_PQC + ret |= test_auth_create_destroy_ctx(); + ret |= test_load_free_crt(); + ret |= test_load_free_privkey(); + ret |= test_load_free_pubkey(); + ret |= test_verify_crt(); + ret |= test_auth_sign(); + ret |= test_auth_bad_signature(); +#else + (void) test_auth_create_destroy_ctx; + (void) test_load_free_crt; + (void) test_load_free_privkey; + (void) test_load_free_pubkey; + (void) test_verify_crt; + (void) test_auth_sign; + (void) test_auth_bad_signature; + + ret = TEST_RC_SKIP; +#endif + return ret; +} 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 -#include +#include #include +#include #include -#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 - 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 +#include + +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; } diff --git a/src/lib/tests/hash_test.c b/src/lib/tests/hash_test.c index 970d9185..fb428b47 100644 --- a/src/lib/tests/hash_test.c +++ b/src/lib/tests/hash_test.c @@ -21,7 +21,7 @@ */ #include -#include +#include #include #include diff --git a/src/lib/tests/kex_test.c b/src/lib/tests/kex_test.c new file mode 100644 index 00000000..58cf8b43 --- /dev/null +++ b/src/lib/tests/kex_test.c @@ -0,0 +1,844 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the key exchange functions + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" + +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_OPENSSL +#include +#include +#endif + +/* Test configuration strings */ +#define KEX_CONFIG_CUSTOM \ + "kex=X25519\n" + +#define KEX_CONFIG_NONE \ + "none\n" + +#define KEX_CONFIG_WHITESPACE \ + "# Comment line\n" \ + "kex = X448" \ + "\n" \ + "# Another comment\n" + +#define KEX_CONFIG_CIPHER \ + "kex=X25519\n" \ + "cipher=chacha20-poly1305\n" + +#define KEX_CONFIG_DIGEST \ + "kex=X25519\n" \ + "digest=sha384\n" + +/* Test key material for key loading tests */ +#define X25519_PRIVKEY_PEM \ + "-----BEGIN PRIVATE KEY-----\n" \ + "MC4CAQAwBQYDK2VuBCIEIJDd3+/0k2IZlaH5sZ9Z2e5J8dV2U0nsXaSUm70ZaMhL\n" \ + "-----END PRIVATE KEY-----\n" + +#define X25519_PUBKEY_PEM \ + "-----BEGIN PUBLIC KEY-----\n" \ + "MCowBQYDK2VuAyEAKYLIycSZtLFlwAX07YWWgBAYhEnRxHfgK1TVw9+mtBs=\n" \ + "-----END PUBLIC KEY-----\n" + +/* Helper macro to open string constant as FILE stream */ +#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r") + +extern const uint16_t kex_supported_nids[]; + +int parse_sec_config(struct sec_config * cfg, + FILE * fp); + +static int test_kex_create_destroy(void) +{ + struct sec_config cfg; + + TEST_START(); + + memset(&cfg, 0, sizeof(cfg)); + cfg.x.nid = NID_X9_62_prime256v1; + cfg.x.str = kex_nid_to_str(cfg.x.nid); + cfg.c.nid = NID_aes_256_gcm; + cfg.c.str = crypt_nid_to_str(cfg.c.nid); + + if (cfg.x.nid == NID_undef || cfg.c.nid == NID_undef) { + printf("Failed to initialize kex config.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_dh_pkp_create_destroy(void) +{ + struct sec_config kex; + void * pkp; + uint8_t buf[MSGBUFSZ]; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, "prime256v1"); + + if (kex_pkp_create(&kex, &pkp, buf) < 0) { + printf("Failed to create DH PKP.\n"); + goto fail; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_get_algo_from_pk(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + ssize_t len; + uint8_t buf[MSGBUFSZ]; + char extracted_algo[256]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf; + + /* Use raw decode for hybrid KEMs, DER for others */ + if (IS_HYBRID_KEM(algo)) { + if (kex_get_algo_from_pk_raw(pk, extracted_algo) < 0) { + printf("Failed to extract algo from pk.\n"); + goto fail_pkp; + } + } else { + if (kex_get_algo_from_pk_der(pk, extracted_algo) < 0) { + printf("Failed to extract algo from pk.\n"); + goto fail_pkp; + } + } + + /* All algorithms should now return the specific group name */ + if (strcmp(extracted_algo, algo) != 0) { + printf("Algo mismatch: expected %s, got %s.\n", + algo, extracted_algo); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_get_algo_from_pk_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + ret |= test_kex_get_algo_from_pk(algo); + } + + return ret; +} + +static int test_kex_dhe_derive(const char * algo) +{ + struct sec_config kex; + 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("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp1, buf1); + if (len < 0) { + printf("Failed to create first key pair for %s.\n", algo); + goto fail; + } + + pk1.len = (size_t) len; + pk1.data = buf1; + + len = kex_pkp_create(&kex, &pkp2, buf2); + if (len < 0) { + printf("Failed to create second key pair for %s.\n", algo); + goto fail_pkp1; + } + + pk2.len = (size_t) len; + pk2.data = buf2; + + if (kex_dhe_derive(&kex, pkp1, pk2, s1) < 0) { + printf("Failed to derive first key for %s.\n", algo); + goto fail_pkp2; + } + + if (kex_dhe_derive(&kex, pkp2, pk1, s2) < 0) { + printf("Failed to derive second key for %s.\n", algo); + goto fail_pkp2; + } + + if (memcmp(s1, s2, SYMMKEYSZ) != 0) { + printf("Derived keys do not match for %s.\n", algo); + goto fail_pkp2; + } + + kex_pkp_destroy(pkp2); + kex_pkp_destroy(pkp1); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp2: + kex_pkp_destroy(pkp2); + fail_pkp1: + kex_pkp_destroy(pkp1); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_validate_algo(void) +{ + TEST_START(); + + if (kex_validate_algo("prime256v1") != 0) { + printf("prime256v1 should be valid.\n"); + goto fail; + } + + if (kex_validate_algo("X25519") != 0) { + printf("X25519 should be valid.\n"); + goto fail; + } + +#ifdef HAVE_OPENSSL_PQC + if (kex_validate_algo("ML-KEM-768") != 0) { + printf("ML-KEM-768 should be valid.\n"); + goto fail; + } +#endif + + if (kex_validate_algo("ffdhe2048") != 0) { + printf("ffdhe2048 should be valid.\n"); + goto fail; + } + + if (kex_validate_algo("invalid_algo") == 0) { + printf("invalid_algo should be rejected.\n"); + goto fail; + } + + if (kex_validate_algo("rsa2048") == 0) { + printf("rsa2048 should be rejected.\n"); + goto fail; + } + + if (kex_validate_algo(NULL) == 0) { + printf("NULL should be rejected.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_dhe_corrupted_pubkey(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + ssize_t len; + uint8_t buf[MSGBUFSZ]; + uint8_t s[SYMMKEYSZ]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf; + + /* Corrupt the public key */ + buf[0] ^= 0xFF; + buf[len - 1] ^= 0xFF; + + if (kex_dhe_derive(&kex, pkp, pk, s) == 0) { + printf("Should fail with corrupted public key.\n"); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_dhe_wrong_algo(void) +{ + struct sec_config kex1; + struct sec_config kex2; + void * pkp1; + void * pkp2; + buffer_t pk2; + ssize_t len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s[SYMMKEYSZ]; + const char * algo1 = "X25519"; + const char * algo2 = "X448"; + + TEST_START("(%s vs %s)", algo1, algo2); + + memset(&kex1, 0, sizeof(kex1)); + memset(&kex2, 0, sizeof(kex2)); + SET_KEX_ALGO(&kex1, algo1); + SET_KEX_ALGO(&kex2, algo2); + + if (kex_pkp_create(&kex1, &pkp1, buf1) < 0) { + printf("Failed to create first key pair.\n"); + goto fail; + } + + len = kex_pkp_create(&kex2, &pkp2, buf2); + if (len < 0) { + printf("Failed to create second key pair.\n"); + goto fail_pkp1; + } + + pk2.len = (size_t) len; + pk2.data = buf2; + + /* Try to derive with mismatched algorithms */ + if (kex_dhe_derive(&kex1, pkp1, pk2, s) == 0) { + printf("Should fail with mismatched algorithms.\n"); + goto fail_pkp2; + } + + kex_pkp_destroy(pkp2); + kex_pkp_destroy(pkp1); + + TEST_SUCCESS("(%s vs %s)", algo1, algo2); + + return TEST_RC_SUCCESS; + fail_pkp2: + kex_pkp_destroy(pkp2); + fail_pkp1: + kex_pkp_destroy(pkp1); + fail: + TEST_FAIL("(%s vs %s)", algo1, algo2); + return TEST_RC_FAIL; +} + +static int test_kex_load_dhe_privkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_privkey_str(X25519_PRIVKEY_PEM, &key) < 0) { + printf("Failed to load X25519 private key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_load_dhe_pubkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_pubkey_str(X25519_PUBKEY_PEM, &key) < 0) { + printf("Failed to load X25519 public key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +#ifdef HAVE_OPENSSL +#include + +static int test_kex_nid_values(void) +{ + int i; + + TEST_START(); + + /* Verify all KEX algorithm NIDs match OpenSSL's */ + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + uint16_t our_nid = kex_supported_nids[i]; + const char * kex_name; + int openssl_nid; + + kex_name = kex_nid_to_str(our_nid); + if (kex_name == NULL) { + printf("kex_nid_to_str failed for NID %u\n", our_nid); + goto fail; + } + + /* Test reverse conversion */ + if (kex_str_to_nid(kex_name) != our_nid) { + printf("kex_str_to_nid failed for '%s'\n", kex_name); + goto fail; + } + + /* Get OpenSSL's NID for this name */ + openssl_nid = OBJ_txt2nid(kex_name); + if (openssl_nid != NID_undef) { + /* OpenSSL recognizes this algorithm */ + if (our_nid != openssl_nid) { + printf("NID mismatch for '%s': " + "ours=%d, OpenSSL=%d\n", + kex_name, our_nid, openssl_nid); + goto fail; + } + } else { + /* Verify no NID collision with different algorithm */ + const char * ossl_name = OBJ_nid2sn(our_nid); + if (ossl_name != NULL && + strcmp(ossl_name, kex_name) != 0) { + printf("NID collision for '%d': " + "ours=%s, OpenSSL=%s\n", + our_nid, kex_name, ossl_name); + goto fail; + } + } + } + + /* Test error cases */ + if (kex_str_to_nid("invalid") != NID_undef) { + printf("kex_str_to_nid should return NID_undef for invalid\n"); + goto fail; + } + + if (kex_nid_to_str(9999) != NULL) { + printf("kex_nid_to_str should return NULL for invalid NID\n"); + goto fail; + } + + if (kex_str_to_nid(NULL) != NID_undef) { + printf("kex_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; +} +#endif + +static int test_kex_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + /* KEM tests are in kex_test_pqc.c */ + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_dhe_derive(algo); + } + + return ret; +} + +static int test_kex_dhe_corrupted_pubkey_all(void) +{ + int ret = 0; + int i; + + /* Test corruption for all DHE algorithms */ + /* KEM error injection tests are in kex_test_pqc.c */ + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_dhe_corrupted_pubkey(algo); + } + + return ret; +} + +static int test_kex_parse_config_empty(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(""); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse empty config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "prime256v1") != 0) { + printf("Empty config should use prime256v1.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_custom(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_CUSTOM); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse custom config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X25519") != 0) { + printf("Algorithm not set correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_none(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_NONE); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse 'none' config.\n"); + fclose(fp); + goto fail; + } + + if (kex.x.nid != NID_undef) { + printf("'none' keyword should disable encryption.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_whitespace(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_WHITESPACE); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse config with comments.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X448") != 0) { + printf("Algorithm with whitespace not parsed correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_cipher(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_CIPHER); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse cipher config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X25519") != 0) { + printf("Algorithm not set correctly.\n"); + fclose(fp); + goto fail; + } + + if (kex.c.nid != NID_chacha20_poly1305) { + printf("Cipher not set correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_digest(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_DIGEST); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse digest config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X25519") != 0) { + printf("Algorithm not set correctly.\n"); + fclose(fp); + goto fail; + } + + if (kex.d.nid != NID_sha384) { + printf("Digest not set correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int kex_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_kex_create_destroy(); + ret |= test_kex_parse_config_empty(); + ret |= test_kex_parse_config_none(); +#ifdef HAVE_OPENSSL + ret |= test_kex_parse_config_custom(); + ret |= test_kex_parse_config_whitespace(); + ret |= test_kex_parse_config_cipher(); + ret |= test_kex_parse_config_digest(); + ret |= test_kex_nid_values(); + ret |= test_kex_dh_pkp_create_destroy(); + ret |= test_kex_all(); + ret |= test_kex_validate_algo(); + ret |= test_kex_get_algo_from_pk_all(); + ret |= test_kex_dhe_wrong_algo(); + ret |= test_kex_dhe_corrupted_pubkey_all(); + ret |= test_kex_load_dhe_privkey(); + ret |= test_kex_load_dhe_pubkey(); +#else + (void) test_kex_parse_config_custom; + (void) test_kex_parse_config_whitespace; + (void) test_kex_parse_config_cipher; + (void) test_kex_parse_config_digest; + (void) test_kex_dh_pkp_create_destroy; + (void) test_kex_all; + (void) test_kex_validate_algo; + (void) test_kex_get_algo_from_pk_all; + (void) test_kex_dhe_wrong_algo(); + (void) test_kex_dhe_corrupted_pubkey_all; + (void) test_kex_load_dhe_privkey; + (void) test_kex_load_dhe_pubkey; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/lib/tests/kex_test_pqc.c b/src/lib/tests/kex_test_pqc.c new file mode 100644 index 00000000..d4579eca --- /dev/null +++ b/src/lib/tests/kex_test_pqc.c @@ -0,0 +1,549 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test of the post-quantum key exchange functions + * + * Dimitri Staessens + * Sander Vrijders + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_OPENSSL +#include +#include +#endif + +extern const uint16_t kex_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +static int get_random_kdf(void) +{ + static int idx = 0; + int count; + + if (md_supported_nids[0] == NID_undef) + return NID_undef; + + for (count = 0; md_supported_nids[count] != NID_undef; count++) + ; + + return md_supported_nids[(idx++) % count]; +} + +/* ML-KEM-768 test key material */ + +#define MLKEM768_PRIVKEY_PEM \ + "-----BEGIN PRIVATE KEY-----\n" \ + "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\n" \ + "108=\n" \ + "-----END PRIVATE KEY-----\n" + +#define MLKEM768_PUBKEY_PEM \ + "-----BEGIN PUBLIC KEY-----\n" \ + "MIIEsjALBglghkgBZQMEBAIDggShAMPIO3U2cWpYVzqVh5Vrp5FyZjN4BtdqGvHC\n" \ + "KJosh0dcP3lpn/RLr60oEaBDPCZmxvB1eGX6f7KoM0TpKjDZvC56qEPBlVagwK/l\n" \ + "Ir4cAY+1xntRT2hauqmIvTzmbsXkbj0olQRlp1W2Phn3OuR5CSJ1r7gju/djczOx\n" \ + "awYnDDtiFMMmmzHGZKbzlsfDH3yFldKpyQlMvhzacHOCsGW6QhP1aX6oi0y2FW8A\n" \ + "E/OnjAZmF2AqsQhGXfIKeGFSCkuVTFh6DBYMwSNnEqVJsjKXmn2UJLzDaWwHOxDY\n" \ + "UmIDL8xYtrnpoD7AWzcLyyZnoJ+nk1ZrxGb6Tz3wZYHFmK+EXTcIE5njZ27RhRl0\n" \ + "I+gDlXUoLpuHgnJYUjj1UVtAUwaDACpzQSsAfeIxqaYoL3vjpZZqCdQgbejwvKBm\n" \ + "u6y4Z5OiFcmFVRMkPDazSvM2FvDkjKiiSRjkFYQ0oUpMiB0HG9h6U4rBsYTxnfoU\n" \ + "RbQAGWbHtJEIpaU1WNIQAheYyJBUt1AXfdBVgP1FHlyGNrbnhW4kfimWj7knCIpb\n" \ + "PwuUIgGbsJWSJ9kwuh+YyTezcvixe0T4GF+bk+WTdGuAN2Ucf6EruCTrVcZ3KcaM\n" \ + "Njtou0IpnRIyxWyRIuibWlshHkwotixDU+x7iaF8M4UKRYUsjlNKEYy4iijWTtd4\n" \ + "gTqDxChSVXkRfJYceEhVoTjJYY8BWASnRSzAoV56jdKgIB4AMDdTzzKbGkEmnvfI\n" \ + "MiqiTl0zdKCSN89ZkOSCQAjxYewIop6qtI6zWSQTcle0TuRxMdPpcejXrZOaAaSo\n" \ + "xXA5DcSZHoVGljEmFm+5VuMmxLFiNbdJydHhmK+iLur3nKlyjffWkiUhmmpXQ3J1\n" \ + "SkzJBrPVyeg8pgOHXFrouFyoQorxrryJeNCDOCpAEMAGidXJs0O2UnmWdNO4qFqT\n" \ + "w8EUCaaGPUl2s+qgi9BEKYtpquQnv+r1Eu2TqRM4nSq0t7aqrALEnDKgx3ZDkXj7\n" \ + "FNZke73SWNF1ydh7BqupwrOgvbu1ylM6ZXPzwjWFhVQkRXtoHfpAyFNLV2q5Oolk\n" \ + "IPSEE9YAxOgSgPg4W8paZsWbNpbGsxzgQJh4MAApizZiqhdIt8CLEGZmNvpQMHrW\n" \ + "XYYDnykUlbRnA3xLm0CkDY1GL7ugL/OAyAEYd+3oTT45RVvwi3z2ljW3vJurn5+Q\n" \ + "G8SLEMh3d/WkreCpxpiob9iGgwkwlaSpjEI0aZ1nyAWjjpXor7jFlRuVBYrKk3r5\n" \ + "Fg7iQOHSSimlYYRQuOAGgIf5XwTsZygTxJF1BjUEgyhFVZvxSvxlghVTyWqBpk+V\n" \ + "YzehEKJaTSRHF7EUXSLlaUgsgr0ZPyxpf5uZF30pLLcmuk+FCfn0KkkFHHPxYp0n\n" \ + "fDC2oT/ZNvFCEuigRYdhWtkQbkRKQig6RQAlRH4LMssUMx1QTzDcJqwTjKz6Fyjs\n" \ + "ADlMCazbgDUVdzEyA1vpUcDRuLlzys8Jm+O5stdHgDT8DZRqWjhzLdQCv2TSttk4\n" \ + "meOqTx4TGR5rFvKHjZwmlTvmLg40tixFkmSD1uQKk+BkpvvTuw9k7vYJfeH+VB1d\n" \ + "WAaRntOp\n" \ + "-----END PUBLIC KEY-----\n" + +/* Helper macro to open string constant as FILE stream */ +#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r") + +static int test_kex_load_kem_privkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_privkey_str(MLKEM768_PRIVKEY_PEM, &key) < 0) { + printf("Failed to load ML-KEM-768 private key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_load_kem_pubkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_pubkey_str(MLKEM768_PUBKEY_PEM, &key) < 0) { + printf("Failed to load ML-KEM-768 public key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_kem(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + int kdf; + + TEST_START("(%s)", algo); + + kdf = get_random_kdf(); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf1); + if (len < 0) { + printf("Failed to create key pair for %s.\n", algo); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf1; + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1); + else + ct_len = kex_kem_encap(pk, buf2, kdf, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate for %s.\n", algo); + goto fail_pkp; + } + + ct.len = (size_t) ct_len; + ct.data = buf2; + + if (kex_kem_decap(pkp, ct, kdf, s2) < 0) { + printf("Failed to decapsulate for %s.\n", algo); + goto fail_pkp; + } + + if (memcmp(s1, s2, SYMMKEYSZ) != 0) { + printf("Shared secrets don't match for %s.\n", algo); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_corrupted_ciphertext(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + int kdf; + + TEST_START("(%s)", algo); + + kdf = get_random_kdf(); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf1); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf1; + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1); + else + ct_len = kex_kem_encap(pk, buf2, kdf, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate.\n"); + goto fail_pkp; + } + + ct.len = (size_t) ct_len; + ct.data = buf2; + + /* Corrupt the ciphertext */ + buf2[0] ^= 0xFF; + buf2[ct_len - 1] ^= 0xFF; + + /* ML-KEM uses implicit rejection */ + if (kex_kem_decap(pkp, ct, kdf, s2) < 0) { + printf("Decapsulation failed unexpectedly.\n"); + goto fail_pkp; + } + + /* The shared secrets should NOT match with corrupted CT */ + if (memcmp(s1, s2, SYMMKEYSZ) == 0) { + printf("Corrupted ciphertext produced same secret.\n"); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_wrong_keypair(const char * algo) +{ + struct sec_config kex; + void * pkp1; + void * pkp2; + buffer_t pk1; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t buf3[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp1, buf1); + if (len < 0) { + printf("Failed to create first key pair.\n"); + goto fail; + } + + pk1.len = (size_t) len; + pk1.data = buf1; + + if (kex_pkp_create(&kex, &pkp2, buf2) < 0) { + printf("Failed to create second key pair.\n"); + goto fail_pkp1; + } + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk1, buf3, NID_sha256, s1); + else + ct_len = kex_kem_encap(pk1, buf3, NID_sha256, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate.\n"); + goto fail_pkp2; + } + + ct.len = (size_t) ct_len; + ct.data = buf3; + + if (kex_kem_decap(pkp2, ct, NID_sha256, s2) == 0) { + if (memcmp(s1, s2, SYMMKEYSZ) == 0) { + printf("Wrong keypair produced same secret.\n"); + goto fail_pkp2; + } + } + + kex_pkp_destroy(pkp2); + kex_pkp_destroy(pkp1); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp2: + kex_pkp_destroy(pkp2); + fail_pkp1: + kex_pkp_destroy(pkp1); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_truncated_ciphertext(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf1); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf1; + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk, buf2, NID_sha256, s1); + else + ct_len = kex_kem_encap(pk, buf2, NID_sha256, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate.\n"); + goto fail_pkp; + } + + /* Truncate the ciphertext */ + ct.len = (size_t) ct_len / 2; + ct.data = buf2; + + if (kex_kem_decap(pkp, ct, NID_sha256, s2) == 0) { + printf("Should fail with truncated ciphertext.\n"); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem(algo); + } + + return ret; +} + +static int test_kex_kem_corrupted_ciphertext_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem_corrupted_ciphertext(algo); + } + + return ret; +} + +static int test_kex_kem_wrong_keypair_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem_wrong_keypair(algo); + } + + return ret; +} + +static int test_kex_kem_truncated_ciphertext_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem_truncated_ciphertext(algo); + } + + return ret; +} + +int kex_test_pqc(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_PQC + ret |= test_kex_load_kem_privkey(); + ret |= test_kex_load_kem_pubkey(); + ret |= test_kex_kem_all(); + ret |= test_kex_kem_corrupted_ciphertext_all(); + ret |= test_kex_kem_wrong_keypair_all(); + ret |= test_kex_kem_truncated_ciphertext_all(); +#else + (void) test_kex_load_kem_privkey; + (void) test_kex_load_kem_pubkey; + (void) test_kex_kem_all; + (void) test_kex_kem_corrupted_ciphertext_all; + (void) test_kex_kem_wrong_keypair_all; + (void) test_kex_kem_truncated_ciphertext_all; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/lib/tests/sockets_test.c b/src/lib/tests/sockets_test.c index bbf2323b..952f9529 100644 --- a/src/lib/tests/sockets_test.c +++ b/src/lib/tests/sockets_test.c @@ -23,7 +23,7 @@ #define _POSIX_C_SOURCE 200112L #include -#include +#include #include #include diff --git a/src/lib/tests/time_test.c b/src/lib/tests/time_test.c index 2b75b873..4685310b 100644 --- a/src/lib/tests/time_test.c +++ b/src/lib/tests/time_test.c @@ -22,7 +22,7 @@ #define _POSIX_C_SOURCE 200809L -#include +#include #include #include diff --git a/src/lib/tests/tpm_test.c b/src/lib/tests/tpm_test.c index 98d4fab3..41bce964 100644 --- a/src/lib/tests/tpm_test.c +++ b/src/lib/tests/tpm_test.c @@ -23,7 +23,7 @@ #include "tpm.c" -#include +#include static void * test_func(void * o) { diff --git a/src/lib/utils.c b/src/lib/utils.c index fd275f63..74f8ce4f 100644 --- a/src/lib/utils.c +++ b/src/lib/utils.c @@ -24,6 +24,7 @@ #include +#include #include #include @@ -67,6 +68,24 @@ char * path_strip(const char * src) return dst; } +char * trim_whitespace(char * str) +{ + char * end; + + while (isspace((unsigned char) *str)) + str++; + + if (*str == '\0') + return str; + + /* Trim trailing space */ + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) + *end-- = '\0'; + + return str; +} + size_t argvlen(const char ** argv) { size_t argc = 0; diff --git a/src/tools/irm/irm_name_create.c b/src/tools/irm/irm_name_create.c index 22341d2e..b132d355 100644 --- a/src/tools/irm/irm_name_create.c +++ b/src/tools/irm/irm_name_create.c @@ -51,10 +51,10 @@ #define RR "round-robin" #define SPILL "spillover" -#define SENC "/server//enc.cfg" +#define SENC "/server//enc.conf" #define SCRT "/server//crt.pem" #define SKEY "/server//key.pem" -#define CENC "/client//enc.cfg" +#define CENC "/client//enc.conf" #define CCRT "/client//crt.pem" #define CKEY "/client//key.pem" -- cgit v1.2.3