From 040bdfb18684d809cb5edacf9867d3378b7e093b Mon Sep 17 00:00:00 2001 From: Dimitri Staessens Date: Tue, 17 Feb 2026 22:37:39 +0100 Subject: lib: Add SLH-DSA tests and per-algorithm PQC gating This replaces the single HAVE_OPENSSL_PQC/DISABLE_PQC with per-algorithm CMake variables (ML-KEM, ML-DSA, SLH-DSA), gated by the OpenSSL versions: ML-KEM and ML-DSA require >= 3.4, SLH-DSA >= 3.5. SLH-DSA was already working, but now added explicit authentication tests for it with a full certificate chain (root CA, intermediate CA, server) to show full support. Rename PQC test files and cert headers to use algorithm-specific names (ml_kem, ml_dsa, slh_dsa) and move cert headers to include/test/certs/. Signed-off-by: Dimitri Staessens Signed-off-by: Sander Vrijders --- src/ipcpd/ipcp.c | 2 +- src/irmd/config.h.in | 3 +- src/irmd/oap/io.c | 2 +- src/irmd/oap/tests/CMakeLists.txt | 24 +- src/irmd/oap/tests/oap_test.c | 4 +- src/irmd/oap/tests/oap_test_ml_dsa.c | 447 ++++++++++++++++++++++++++++ src/irmd/oap/tests/oap_test_pqc.c | 447 ---------------------------- src/lib/config.h.in | 4 +- src/lib/crypt.c | 2 +- src/lib/tests/CMakeLists.txt | 5 +- src/lib/tests/auth_test.c | 2 +- src/lib/tests/auth_test_ml_dsa.c | 356 +++++++++++++++++++++++ src/lib/tests/auth_test_pqc.c | 356 ----------------------- src/lib/tests/auth_test_slh_dsa.c | 367 +++++++++++++++++++++++ src/lib/tests/kex_test.c | 6 +- src/lib/tests/kex_test_ml_kem.c | 549 +++++++++++++++++++++++++++++++++++ src/lib/tests/kex_test_pqc.c | 549 ----------------------------------- 17 files changed, 1748 insertions(+), 1377 deletions(-) create mode 100644 src/irmd/oap/tests/oap_test_ml_dsa.c delete mode 100644 src/irmd/oap/tests/oap_test_pqc.c create mode 100644 src/lib/tests/auth_test_ml_dsa.c delete mode 100644 src/lib/tests/auth_test_pqc.c create mode 100644 src/lib/tests/auth_test_slh_dsa.c create mode 100644 src/lib/tests/kex_test_ml_kem.c delete mode 100644 src/lib/tests/kex_test_pqc.c (limited to 'src') diff --git a/src/ipcpd/ipcp.c b/src/ipcpd/ipcp.c index 3ea77da9..c5a5174d 100644 --- a/src/ipcpd/ipcp.c +++ b/src/ipcpd/ipcp.c @@ -208,7 +208,7 @@ static int ipcp_rib_read(const char * path, char * buf, size_t len) { - char * entry; + const char * entry; if (len < LAYER_NAME_SIZE + 2) /* trailing \n */ return 0; diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in index e1072193..6cbfc11f 100644 --- a/src/irmd/config.h.in +++ b/src/irmd/config.h.in @@ -78,7 +78,8 @@ #cmakedefine HAVE_LIBGCRYPT #cmakedefine HAVE_OPENSSL #ifdef HAVE_OPENSSL -#cmakedefine HAVE_OPENSSL_PQC +#cmakedefine HAVE_OPENSSL_ML_KEM +#cmakedefine HAVE_OPENSSL_ML_DSA #endif #define IRMD_SECMEM_MAX @IRMD_SECMEM_MAX@ #ifdef CONFIG_OUROBOROS_DEBUG diff --git a/src/irmd/oap/io.c b/src/irmd/oap/io.c index 8f75a8d8..c8d26147 100644 --- a/src/irmd/oap/io.c +++ b/src/irmd/oap/io.c @@ -118,7 +118,7 @@ int load_kex_config(const char * name, log_info("Key exchange not configured for %s.", name); return 0; } -#ifndef HAVE_OPENSSL_PQC +#ifndef HAVE_OPENSSL_ML_KEM if (IS_KEM_ALGORITHM(cfg->x.str)) { log_err("PQC not available, can't use %s for %s.", cfg->x.str, name); diff --git a/src/irmd/oap/tests/CMakeLists.txt b/src/irmd/oap/tests/CMakeLists.txt index 2bf23821..b534cb72 100644 --- a/src/irmd/oap/tests/CMakeLists.txt +++ b/src/irmd/oap/tests/CMakeLists.txt @@ -13,9 +13,9 @@ create_test_sourcelist(${PARENT_DIR}_tests test_suite.c oap_test.c ) -create_test_sourcelist(${PARENT_DIR}_pqc_tests test_suite_pqc.c - # PQC-specific tests - oap_test_pqc.c +create_test_sourcelist(${PARENT_DIR}_ml_dsa_tests test_suite_ml_dsa.c + # ML-DSA-specific tests + oap_test_ml_dsa.c ) # OAP test needs io.c compiled with OAP_TEST_MODE @@ -41,24 +41,24 @@ target_include_directories(${PARENT_DIR}_test PRIVATE ${IRMD_BINARY_DIR} ) -# PQC test executable (ML-DSA) -add_executable(${PARENT_DIR}_pqc_test ${${PARENT_DIR}_pqc_tests} ${OAP_TEST_SOURCES}) +# ML-DSA test executable +add_executable(${PARENT_DIR}_ml_dsa_test ${${PARENT_DIR}_ml_dsa_tests} ${OAP_TEST_SOURCES}) set_source_files_properties(${OAP_TEST_SOURCES} - TARGET_DIRECTORY ${PARENT_DIR}_pqc_test + TARGET_DIRECTORY ${PARENT_DIR}_ml_dsa_test PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" ) -disable_test_logging_for_target(${PARENT_DIR}_pqc_test) -target_link_libraries(${PARENT_DIR}_pqc_test ouroboros-irm) -target_include_directories(${PARENT_DIR}_pqc_test PRIVATE +disable_test_logging_for_target(${PARENT_DIR}_ml_dsa_test) +target_link_libraries(${PARENT_DIR}_ml_dsa_test ouroboros-irm) +target_include_directories(${PARENT_DIR}_ml_dsa_test PRIVATE ${IRMD_SOURCE_DIR} ${IRMD_BINARY_DIR} ) -add_dependencies(build_tests ${PARENT_DIR}_test ${PARENT_DIR}_pqc_test) +add_dependencies(build_tests ${PARENT_DIR}_test ${PARENT_DIR}_ml_dsa_test) # Regular tests ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) -# PQC tests -ouroboros_register_tests(TARGET ${PARENT_DIR}_pqc_test TESTS ${${PARENT_DIR}_pqc_tests}) +# ML-DSA tests +ouroboros_register_tests(TARGET ${PARENT_DIR}_ml_dsa_test TESTS ${${PARENT_DIR}_ml_dsa_tests}) diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c index fc78ed9a..169cfc14 100644 --- a/src/irmd/oap/tests/oap_test.c +++ b/src/irmd/oap/tests/oap_test.c @@ -38,7 +38,7 @@ #include #include -#include +#include #include "oap.h" #include "common.h" @@ -526,7 +526,7 @@ static int test_oap_roundtrip_all(void) 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 */ + /* Skip KEM algorithms - tested in oap_test_ml_dsa */ if (IS_KEM_ALGORITHM(algo)) continue; diff --git a/src/irmd/oap/tests/oap_test_ml_dsa.c b/src/irmd/oap/tests/oap_test_ml_dsa.c new file mode 100644 index 00000000..f9e6bdb2 --- /dev/null +++ b/src/irmd/oap/tests/oap_test_ml_dsa.c @@ -0,0 +1,447 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests of OAP ML-KEM/ML-DSA 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; +} + +static int test_oap_kem_srv_uncfg(int kex) +{ + struct oap_test_ctx ctx; + const char * kex_str = kex_nid_to_str(kex); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth only, no KEX configured */ + test_cfg.srv.auth = true; + + /* Client: requests KEM with server-side encapsulation */ + test_cfg.cli.kex = kex; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = get_random_kdf(); + test_cfg.cli.kem_mode = SRV_ENCAP; + test_cfg.cli.auth = false; + + TEST_START("(%s)", kex_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)", kex_str); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown_kem(&ctx); + fail: + TEST_FAIL("(%s)", kex_str); + return TEST_RC_FAIL; +} + +static int test_oap_kem_srv_uncfg_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo; + + algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_kem_srv_uncfg(kex_supported_nids[i]); + } + + return ret; +} + +int oap_test_ml_dsa(int argc, + char **argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_ML_KEM + ret |= test_oap_roundtrip_auth_only(); + + ret |= test_oap_roundtrip_kem_all(); + + ret |= test_oap_kem_srv_uncfg_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_kem_srv_uncfg; + (void) test_oap_kem_srv_uncfg_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/oap/tests/oap_test_pqc.c b/src/irmd/oap/tests/oap_test_pqc.c deleted file mode 100644 index bbaf9f7b..00000000 --- a/src/irmd/oap/tests/oap_test_pqc.c +++ /dev/null @@ -1,447 +0,0 @@ -/* - * 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; -} - -static int test_oap_kem_srv_uncfg(int kex) -{ - struct oap_test_ctx ctx; - const char * kex_str = kex_nid_to_str(kex); - - memset(&test_cfg, 0, sizeof(test_cfg)); - - /* Server: auth only, no KEX configured */ - test_cfg.srv.auth = true; - - /* Client: requests KEM with server-side encapsulation */ - test_cfg.cli.kex = kex; - test_cfg.cli.cipher = NID_aes_256_gcm; - test_cfg.cli.kdf = get_random_kdf(); - test_cfg.cli.kem_mode = SRV_ENCAP; - test_cfg.cli.auth = false; - - TEST_START("(%s)", kex_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)", kex_str); - return TEST_RC_SUCCESS; - - fail_cleanup: - oap_test_teardown_kem(&ctx); - fail: - TEST_FAIL("(%s)", kex_str); - return TEST_RC_FAIL; -} - -static int test_oap_kem_srv_uncfg_all(void) -{ - int ret = 0; - int i; - - for (i = 0; kex_supported_nids[i] != NID_undef; i++) { - const char * algo; - - algo = kex_nid_to_str(kex_supported_nids[i]); - - if (!IS_KEM_ALGORITHM(algo)) - continue; - - ret |= test_oap_kem_srv_uncfg(kex_supported_nids[i]); - } - - 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_kem_srv_uncfg_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_kem_srv_uncfg; - (void) test_oap_kem_srv_uncfg_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/lib/config.h.in b/src/lib/config.h.in index 6065ac41..2c3afc80 100644 --- a/src/lib/config.h.in +++ b/src/lib/config.h.in @@ -25,7 +25,9 @@ #cmakedefine HAVE_LIBGCRYPT #cmakedefine HAVE_OPENSSL #ifdef HAVE_OPENSSL -#cmakedefine HAVE_OPENSSL_PQC +#cmakedefine HAVE_OPENSSL_ML_KEM +#cmakedefine HAVE_OPENSSL_ML_DSA +#cmakedefine HAVE_OPENSSL_SLH_DSA #define HAVE_ENCRYPTION #define SECMEM_GUARD @SECMEM_GUARD@ #endif diff --git a/src/lib/crypt.c b/src/lib/crypt.c index 92da803d..a14fc46b 100644 --- a/src/lib/crypt.c +++ b/src/lib/crypt.c @@ -98,7 +98,7 @@ const uint16_t kex_supported_nids[] = { NID_ffdhe4096, NID_X448, NID_secp521r1, -#ifdef HAVE_OPENSSL_PQC +#ifdef HAVE_OPENSSL_ML_KEM NID_MLKEM512, NID_MLKEM768, NID_MLKEM1024, diff --git a/src/lib/tests/CMakeLists.txt b/src/lib/tests/CMakeLists.txt index 23d01f9b..5a2f2c52 100644 --- a/src/lib/tests/CMakeLists.txt +++ b/src/lib/tests/CMakeLists.txt @@ -6,14 +6,15 @@ compute_test_prefix() create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here auth_test.c - auth_test_pqc.c + auth_test_ml_dsa.c + auth_test_slh_dsa.c bitmap_test.c btree_test.c crc32_test.c crypt_test.c hash_test.c kex_test.c - kex_test_pqc.c + kex_test_ml_kem.c md5_test.c sha3_test.c sockets_test.c diff --git a/src/lib/tests/auth_test.c b/src/lib/tests/auth_test.c index b3f09277..0ea955f7 100644 --- a/src/lib/tests/auth_test.c +++ b/src/lib/tests/auth_test.c @@ -27,7 +27,7 @@ #include #include -#include +#include #define TEST_MSG_SIZE 1500 diff --git a/src/lib/tests/auth_test_ml_dsa.c b/src/lib/tests/auth_test_ml_dsa.c new file mode 100644 index 00000000..cc72e61b --- /dev/null +++ b/src/lib/tests/auth_test_ml_dsa.c @@ -0,0 +1,356 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test of the ML-DSA-65 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/. + */ + +#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_ml_dsa(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_ML_DSA + 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/auth_test_pqc.c b/src/lib/tests/auth_test_pqc.c deleted file mode 100644 index 349636d2..00000000 --- a/src/lib/tests/auth_test_pqc.c +++ /dev/null @@ -1,356 +0,0 @@ -/* - * 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/auth_test_slh_dsa.c b/src/lib/tests/auth_test_slh_dsa.c new file mode 100644 index 00000000..511d20fe --- /dev/null +++ b/src/lib/tests/auth_test_slh_dsa.c @@ -0,0 +1,367 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test of the SLH-DSA-SHA2-128s 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/. + */ + +#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_slh, &crt) < 0) { + printf("Failed to load root crt.\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_slh, &key) < 0) { + printf("Failed to load server key pair.\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_slh, &key) < 0) { + printf("Failed to load server public key.\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_slh, + &_server_crt) < 0) { + printf("Failed to load self-signed crt.\n"); + goto fail_load_server_crt; + } + + if (crypt_load_crt_str(signed_server_crt_slh, + &_signed_server_crt) < 0) { + printf("Failed to load signed crt.\n"); + goto fail_load_signed_server_crt; + } + + if (crypt_load_crt_str(root_ca_crt_slh, + &_root_ca_crt) < 0) { + printf("Failed to load root crt.\n"); + goto fail_load_root_ca_crt; + } + + if (crypt_load_crt_str(im_ca_crt_slh, + &_im_ca_crt) < 0) { + printf("Failed to load im crt.\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.\n"); + goto fail_verify; + } + + if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) { + printf("Failed to add im ca crt.\n"); + goto fail_verify; + } + + if (auth_verify_crt(auth, _signed_server_crt) < 0) { + printf("Failed to verify signed 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 gen random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_slh, + &pkp) < 0) { + printf("Failed to load server key pair.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_slh, + &pk) < 0) { + printf("Failed to load public key.\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: + TEST_FAIL(); + 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 gen random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_slh, + &pkp) < 0) { + printf("Failed to load server key pair.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_slh, + &pk) < 0) { + printf("Failed to load public key.\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 alloc fake sig buf.\n"); + goto fail_malloc; + } + + fake_sig.len = sig.len; + if (random_buffer(fake_sig.data, + fake_sig.len) < 0) { + printf("Failed to gen random fake sig.\n"); + goto fail_malloc; + } + + if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) { + printf("Failed to detect bad sig.\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: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int auth_test_slh_dsa(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_SLH_DSA + 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/kex_test.c b/src/lib/tests/kex_test.c index 0a588550..04200679 100644 --- a/src/lib/tests/kex_test.c +++ b/src/lib/tests/kex_test.c @@ -276,7 +276,7 @@ static int test_kex_validate_algo(void) goto fail; } -#ifdef HAVE_OPENSSL_PQC +#ifdef HAVE_OPENSSL_ML_KEM if (kex_validate_algo("ML-KEM-768") != 0) { printf("ML-KEM-768 should be valid.\n"); goto fail; @@ -536,7 +536,7 @@ static int test_kex_all(void) 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 */ + /* KEM tests are in kex_test_ml_kem.c */ if (IS_KEM_ALGORITHM(algo)) continue; @@ -552,7 +552,7 @@ static int test_kex_dhe_corrupted_pubkey_all(void) int i; /* Test corruption for all DHE algorithms */ - /* KEM error injection tests are in kex_test_pqc.c */ + /* KEM error injection tests are in kex_test_ml_kem.c */ for (i = 0; kex_supported_nids[i] != NID_undef; i++) { const char * algo = kex_nid_to_str(kex_supported_nids[i]); diff --git a/src/lib/tests/kex_test_ml_kem.c b/src/lib/tests/kex_test_ml_kem.c new file mode 100644 index 00000000..3bb9ae7c --- /dev/null +++ b/src/lib/tests/kex_test_ml_kem.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_ml_kem(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_ML_KEM + 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/kex_test_pqc.c b/src/lib/tests/kex_test_pqc.c deleted file mode 100644 index d4579eca..00000000 --- a/src/lib/tests/kex_test_pqc.c +++ /dev/null @@ -1,549 +0,0 @@ -/* - * 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; -} -- cgit v1.2.3