diff options
Diffstat (limited to 'src/irmd')
48 files changed, 6044 insertions, 1823 deletions
diff --git a/src/irmd/CMakeLists.txt b/src/irmd/CMakeLists.txt index 32904d59..9aa747ca 100644 --- a/src/irmd/CMakeLists.txt +++ b/src/irmd/CMakeLists.txt @@ -1,114 +1,67 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +# IRMd (IPC Resource Manager daemon) build configuration +# Configuration options are in cmake/config/global.cmake and cmake/config/irmd.cmake -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -set(OUROBOROS_CONFIG_DIR /etc/ouroboros CACHE STRING - "Configuration directory") - -find_library(LIBTOML_LIBRARIES toml QUIET) -if (LIBTOML_LIBRARIES) - set(DISABLE_CONFIGFILE FALSE CACHE BOOL - "Disable configuration file support") - if (NOT DISABLE_CONFIGFILE) - set(OUROBOROS_CONFIG_FILE irmd.conf CACHE STRING - "Name of the IRMd configuration file") - set(HAVE_TOML TRUE) - message(STATUS "Found TOML C99 library: " ${LIBTOML_LIBRARIES}) - message(STATUS "Configuration file support enabled") - message(STATUS "Configuration directory: ${OUROBOROS_CONFIG_DIR}") - set(INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") - configure_file("${CMAKE_SOURCE_DIR}/irmd.conf.in" - "${CMAKE_BINARY_DIR}/irmd.conf.example" @ONLY) - install(FILES "${CMAKE_BINARY_DIR}/irmd.conf.example" - DESTINATION "${OUROBOROS_CONFIG_DIR}") - unset(INSTALL_DIR) - find_path(LIBTOML_INCLUDE toml.h) - mark_as_advanced(LIBTOML_LIBRARIES LIBTOML_INCLUDE) - else () - message(STATUS "Configuration file support disabled by user") - unset(OUROBOROS_CONFIG_FILE CACHE) - set(HAVE_TOML FALSE) - endif () -else () - message(STATUS "Install tomlc99 for config file support") - message(STATUS " https://github.com/cktan/tomlc99") - set(LIBTOML_LIBRARIES "") - unset(DISABLE_CONFIGFILE CACHE) - unset(HAVE_TOML) -endif () - -set(OUROBOROS_SECURITY_DIR "${OUROBOROS_CONFIG_DIR}/security" CACHE STRING - "Security directory holding authentication information") -set(OUROBOROS_CA_CRT_DIR "${OUROBOROS_SECURITY_DIR}/cacert" CACHE STRING - "Directory holding trusted CA certificates") -set(OUROBOROS_SRV_CRT_DIR "${OUROBOROS_SECURITY_DIR}/server" CACHE STRING - "Directory holding trusted CA certificates") -set(OUROBOROS_CLI_CRT_DIR "${OUROBOROS_SECURITY_DIR}/client" CACHE STRING - "Directory holding trusted CA certificates") -set(OUROBOROS_UNTRUSTED_DIR "${OUROBOROS_SECURITY_DIR}/untrusted" CACHE STRING - "Directory holding untrusted intermediate certificates") - -set(IRMD_REQ_ARR_TIMEOUT 1000 CACHE STRING - "Timeout for an application to respond to a new flow (ms)") -set(BOOTSTRAP_TIMEOUT 5000 CACHE STRING - "Timeout for an IPCP to bootstrap (ms)") -set(ENROLL_TIMEOUT 20000 CACHE STRING - "Timeout for an IPCP to enroll (ms)") -set(REG_TIMEOUT 20000 CACHE STRING - "Timeout for registering a name (ms)") -set(QUERY_TIMEOUT 200 CACHE STRING - "Timeout to query a name with an IPCP (ms)") -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(IRMD_MIN_THREADS 8 CACHE STRING - "Minimum number of worker threads in the IRMd") -set(IRMD_ADD_THREADS 8 CACHE STRING - "Number of extra threads to start when the IRMD faces thread starvation") -set(IRMD_PKILL_TIMEOUT 30 CACHE STRING - "Number of seconds to wait before sending SIGKILL to subprocesses on exit") -set(IRMD_KILL_ALL_PROCESSES TRUE CACHE BOOL - "Kill all processes on exit") -set(DEBUG_PROTO_OAP FALSE CACHE BOOL - "Add Flow allocation protocol message output to IRMd debug logging") +# Generate and install configuration files if TOML support available +# HAVE_TOML is set in cmake/dependencies/irmd/libtoml.cmake +if(HAVE_TOML) + 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}\" \"\") + endif() + ") + unset(INSTALL_DIR) +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) -set(SOURCE_FILES - # Add source files here +set(IRMD_SOURCES ipcp.c configfile.c main.c - oap.c + oap/io.c + oap/hdr.c + oap/auth.c + oap/srv.c + oap/cli.c reg/flow.c reg/ipcp.c + reg/pool.c reg/proc.c reg/prog.c reg/name.c reg/reg.c - ) +) + +add_executable(irmd ${IRMD_SOURCES}) -add_executable (irmd ${SOURCE_FILES}) +add_dependencies(irmd version_header) -target_link_libraries (irmd LINK_PUBLIC ouroboros-common - ${LIBTOML_LIBRARIES}) +target_include_directories(irmd PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include) -if (HAVE_TOML) - target_include_directories(irmd PUBLIC ${LIBTOML_INCLUDE}) -endif () +target_link_libraries(irmd PRIVATE ouroboros-common) +if(HAVE_TOML) + target_link_libraries(irmd PRIVATE toml::toml) +endif() -include(AddCompileFlags) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(irmd -DCONFIG_OUROBOROS_DEBUG) -endif () +ouroboros_target_debug_definitions(irmd) install(TARGETS irmd RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) -add_subdirectory(reg) if(BUILD_TESTS) - add_subdirectory(tests) -endif () + add_subdirectory(oap/tests) + add_subdirectory(reg/tests) +endif() diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in index 527694c0..df0cd718 100644 --- a/src/irmd/config.h.in +++ b/src/irmd/config.h.in @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Configuration for the IPC Resource Manager * @@ -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@ @@ -51,6 +53,8 @@ #define IRMD_MIN_THREADS @IRMD_MIN_THREADS@ #define IRMD_ADD_THREADS @IRMD_ADD_THREADS@ +#define SSM_PID_GSPP 0 + #cmakedefine HAVE_FUSE #ifdef HAVE_FUSE #define FUSE_PREFIX "@FUSE_PREFIX@" @@ -70,9 +74,15 @@ #define IRMD_PKILL_TIMEOUT @IRMD_PKILL_TIMEOUT@ +#cmakedefine DISABLE_DIRECT_IPC #cmakedefine IRMD_KILL_ALL_PROCESSES #cmakedefine HAVE_LIBGCRYPT #cmakedefine HAVE_OPENSSL +#ifdef HAVE_OPENSSL +#cmakedefine HAVE_OPENSSL_ML_KEM +#cmakedefine HAVE_OPENSSL_ML_DSA +#endif +#define IRMD_SECMEM_MAX @IRMD_SECMEM_MAX@ #ifdef CONFIG_OUROBOROS_DEBUG #cmakedefine DEBUG_PROTO_OAP #endif diff --git a/src/irmd/configfile.c b/src/irmd/configfile.c index ce9fc8fc..53608eee 100644 --- a/src/irmd/configfile.c +++ b/src/irmd/configfile.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager / Configuration from file * diff --git a/src/irmd/configfile.h b/src/irmd/configfile.h index 3ccf53fd..62f522a4 100644 --- a/src/irmd/configfile.h +++ b/src/irmd/configfile.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager / Configuration from file * diff --git a/src/irmd/ipcp.c b/src/irmd/ipcp.c index 6226aeda..a7da186c 100644 --- a/src/irmd/ipcp.c +++ b/src/irmd/ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The API to instruct IPCPs * @@ -421,6 +421,8 @@ int ipcp_flow_join(const struct flow_info * flow, msg.flow_id = flow->id; msg.has_pid = true; msg.pid = flow->n_pid; + msg.has_uid = true; + msg.uid = flow->uid; msg.has_hash = true; msg.hash.data = (uint8_t *) dst.data; msg.hash.len = dst.len; @@ -455,6 +457,8 @@ int ipcp_flow_alloc(const struct flow_info * flow, msg.flow_id = flow->id; msg.has_pid = true; msg.pid = flow->n_pid; + msg.has_uid = true; + msg.uid = flow->uid; msg.qosspec = qos_spec_s_to_msg(&flow->qs); msg.has_hash = true; msg.hash.data = (uint8_t *) dst.data; @@ -495,6 +499,8 @@ int ipcp_flow_alloc_resp(const struct flow_info * flow, msg.flow_id = flow->id; msg.has_pid = true; msg.pid = flow->n_pid; + msg.has_uid = true; + msg.uid = flow->uid; msg.has_response = true; msg.response = response; msg.has_pk = response == 0; diff --git a/src/irmd/ipcp.h b/src/irmd/ipcp.h index b7413cd2..f1025096 100644 --- a/src/irmd/ipcp.h +++ b/src/irmd/ipcp.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The API for the IRM to instruct IPCPs * diff --git a/src/irmd/irmd.h b/src/irmd/irmd.h index 3e54904a..f88378ad 100644 --- a/src/irmd/irmd.h +++ b/src/irmd/irmd.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager * diff --git a/src/irmd/main.c b/src/irmd/main.c index daaf4129..e610a015 100644 --- a/src/irmd/main.c +++ b/src/irmd/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager * @@ -22,8 +22,10 @@ #if defined(__linux__) || defined(__CYGWIN__) #define _DEFAULT_SOURCE +#define _GNU_SOURCE #else -#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE +#define _BSD_SOURCE #endif #include "config.h" @@ -39,10 +41,11 @@ #include <ouroboros/list.h> #include <ouroboros/lockfile.h> #include <ouroboros/logs.h> +#include <ouroboros/protobuf.h> #include <ouroboros/pthread.h> #include <ouroboros/random.h> #include <ouroboros/rib.h> -#include <ouroboros/shm_rdrbuff.h> +#include <ouroboros/ssm_pool.h> #include <ouroboros/sockets.h> #include <ouroboros/time.h> #include <ouroboros/tpm.h> @@ -56,15 +59,21 @@ #include "configfile.h" #include <dirent.h> -#include <sys/socket.h> -#include <sys/un.h> +#include <grp.h> +#include <pwd.h> #include <signal.h> +#include <spawn.h> #include <stdlib.h> #include <string.h> #include <limits.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/wait.h> -#include <spawn.h> +#include <sys/un.h> +#ifdef __APPLE__ +#include <sys/types.h> +#include <unistd.h> +#endif #ifdef HAVE_LIBGCRYPT #include <gcrypt.h> @@ -77,6 +86,7 @@ #define TIMESYNC_SLACK 100 /* ms */ #define OAP_SEEN_TIMER 20 /* s */ #define DEALLOC_TIME 300 /* s */ +#define DIRECT_MPL 1 /* s */ enum irm_state { IRMD_NULL = 0, @@ -85,13 +95,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,14 +108,8 @@ 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 */ + struct ssm_pool * gspp; /* pool for packets */ int sockfd; /* UNIX socket */ @@ -456,7 +453,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 +462,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 +610,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 +633,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 +719,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 +728,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 +766,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,20 +774,53 @@ static int name_unreg(const char * name, freebuf(hash); return 0; + + fail_hash: + freebuf(hash); + return -1; +} + +static int get_peer_ids(int fd, + uid_t * uid, + gid_t * gid) +{ +#if defined(__linux__) + struct ucred ucred; + socklen_t len; + + len = sizeof(ucred); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) + goto fail; + + *uid = ucred.uid; + *gid = ucred.gid; +#else + if (getpeereid(fd, uid, gid) < 0) + goto fail; +#endif + return 0; + fail: + return -1; } static int proc_announce(const struct proc_info * info) { + if (reg_prepare_pool(info->uid, info->gid) < 0) { + log_err("Failed to prepare pool for uid %d.", info->uid); + goto fail; + } + if (reg_create_proc(info) < 0) { log_err("Failed to add process %d.", info->pid); - goto fail_proc; + goto fail; } log_info("Process added: %d (%s).", info->pid, info->prog); return 0; - fail_proc: + fail: return -1; } @@ -796,266 +834,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 ? "<client>" : 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 +875,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 +895,49 @@ 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; + goto fail_oap; + } + + if (reg_get_name_info(name, &info) < 0) { + log_err("Failed to get name info for %s.", name); + err = -ENAME; + goto fail_oap; } 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; - } + flow->uid = reg_get_proc_uid(flow->n_pid); - if (crypt && oap_hdr.eph.len == 0) { - log_warn("Encryption required but no key provided."); - err = -ECRYPT; - goto fail_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; } - 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; + if (reg_flow_is_direct(flow->id)) { + if (reg_respond_flow_direct(flow->id, &resp_hdr) < 0) { + log_err("Failed to respond to direct flow."); + goto fail_resp; } - - 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 (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; - } - - 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; - } - - 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) { + log_info("Flow %d accepted (direct) by %d for %s.", + flow->id, flow->n_pid, name); + } else if (ipcp_flow_alloc_resp(flow, 0, resp_hdr) < 0) { log_err("Failed to respond to flow allocation."); goto fail_resp; + } else { + log_info("Flow %d accepted by %d for %s (uid %d).", + flow->id, flow->n_pid, name, flow->uid); } - 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: + if (!reg_flow_is_direct(flow->id)) + ipcp_flow_alloc_resp(flow, err, resp_hdr); fail_wait: reg_destroy_flow(flow->id); fail_flow: @@ -1217,10 +945,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; } @@ -1235,14 +961,17 @@ static int flow_join(struct flow_info * flow, buffer_t pbuf = BUF_INIT; /* nothing to piggyback */ int err; - log_info("Allocating flow for %d to %s.", flow->n_pid, dst); - if (reg_create_flow(flow) < 0) { log_err("Failed to create flow."); err = -EBADF; goto fail_flow; } + flow->uid = reg_get_proc_uid(flow->n_pid); + + log_info("Allocating flow for %d to %s (uid %d).", + flow->n_pid, dst, flow->uid); + strcpy(layer.name, dst); if (reg_get_ipcp_by_layer(&ipcp, &layer) < 0) { log_err("Failed to get IPCP for layer %s.", dst); @@ -1308,7 +1037,7 @@ static int get_ipcp_by_dst(const char * dst, pid_t * pid, buffer_t * hash) { - ipcp_list_msg_t ** ipcps; + ipcp_list_msg_t ** ipcps = NULL; int n; int i; int err = -EIPCP; @@ -1361,196 +1090,6 @@ 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, - buffer_t * data, - struct timespec * abstime) -{ - 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; - - /* 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); - } - - 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; - goto fail_flow; - } - - if (get_ipcp_by_dst(dst, &flow->n_1_pid, &hash) < 0) { - log_err("Failed to find IPCP for %s.", dst); - err = -EIPCP; - goto fail_ipcp; - } - - reg_prepare_flow_alloc(flow); - - if (ipcp_flow_alloc(flow, hash, oap_hdr.hdr)) { - log_err("Flow allocation %d failed.", flow->id); - err = -ENOTALLOC; - 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); - - 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; - } - - log_dbg("Response received for flow %d to %s.", flow->id, dst); - - if (err < 0) { - log_warn("Flow allocation rejected for %s: %d.", dst, err); - goto fail_alloc; - } - - 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; - } - - 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(hash); - free(s); - - return 0; - - fail_r_oap_hdr: - flow->state = FLOW_DEALLOCATED; - oap_hdr_fini(&r_oap_hdr); - fail_alloc: - 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; -} - static int wait_for_accept(const char * name) { struct timespec timeo = TIMESPEC_INIT_MS(IRMD_REQ_ARR_TIMEOUT); @@ -1643,6 +1182,194 @@ static int flow_req_arr(struct flow_info * flow, return ret; } +#ifndef DISABLE_DIRECT_IPC +static int flow_alloc_direct(const char * dst, + struct flow_info * flow, + buffer_t * data, + struct timespec * abstime, + struct crypt_sk * sk, + struct name_info * info) +{ + struct flow_info acc; /* server side flow */ + buffer_t req_hdr = BUF_INIT; + buffer_t resp_hdr = BUF_INIT; + void * ctx; + int err; + + acc.id = wait_for_accept(dst); + if (acc.id < 0) { + log_dbg("No accepting process for %s.", dst); + return -EAGAIN; + } + + if (oap_cli_prepare(&ctx, info, &req_hdr, *data) < 0) { + log_err("Failed to prepare OAP for %s.", dst); + return -EBADF; + } + + acc.n_1_pid = flow->n_pid; + acc.mpl = DIRECT_MPL; + acc.qs = flow->qs; + acc.state = FLOW_ALLOCATED; + + err = reg_prepare_flow_direct(&acc, &req_hdr, flow->uid); + if (err == -EPERM) { + log_dbg("UID mismatch, falling back."); + oap_ctx_free(ctx); + freebuf(req_hdr); + return -EPERM; + } + + if (err < 0) { + log_err("Failed to prepare direct flow."); + oap_ctx_free(ctx); + freebuf(req_hdr); + return -EBADF; + } + + err = reg_wait_flow_direct(acc.id, &resp_hdr, abstime); + if (err < 0) { + log_err("Timeout waiting for OAP response."); + oap_ctx_free(ctx); + return -ETIMEDOUT; + } + + err = oap_cli_complete(ctx, info, resp_hdr, data, sk); + if (err < 0) { + log_err("OAP completion failed for %s.", dst); + freebuf(resp_hdr); + return err; + } + + flow->id = acc.id; + flow->n_1_pid = acc.n_pid; + flow->mpl = DIRECT_MPL; + flow->state = FLOW_ALLOCATED; + + log_info("Flow %d allocated (direct) for %d to %s.", + flow->id, flow->n_pid, dst); + + freebuf(resp_hdr); + + return 0; +} +#endif /* DISABLE_DIRECT_IPC */ + +static int flow_alloc(const char * dst, + struct flow_info * flow, + buffer_t * data, + struct timespec * abstime, + struct crypt_sk * sk) +{ + 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)); + + /* 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; + } + + flow->uid = reg_get_proc_uid(flow->n_pid); + + log_info("Allocating flow for %d to %s (uid %d).", + flow->n_pid, dst, flow->uid); + +#ifndef DISABLE_DIRECT_IPC + err = flow_alloc_direct(dst, flow, data, abstime, sk, &info); + if (err == 0) + return 0; + + if (err != -EPERM && err != -EAGAIN) + goto fail_flow; +#endif + if (reg_create_flow(flow) < 0) { + log_err("Failed to create flow."); + err = -EBADF; + goto fail_flow; + } + + if (get_ipcp_by_dst(dst, &flow->n_1_pid, &hash) < 0) { + log_err("Failed to find IPCP for %s.", dst); + err = -EIPCP; + goto fail_ipcp; + } + + if (reg_prepare_flow_alloc(flow) < 0) { + log_err("Failed to prepare flow allocation."); + err = -EBADF; + goto fail_prepare; + } + + 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 = -EIPCP; + goto fail_alloc; + } + + pthread_cleanup_push(__cleanup_flow, flow); + pthread_cleanup_push(free, hash.data); + + err = reg_wait_flow_allocated(flow, &resp_hdr, abstime); + + pthread_cleanup_pop(false); + pthread_cleanup_pop(false); + + if (err == -ETIMEDOUT) { + log_err("Flow allocation timed out."); + goto fail_wait; + } + + log_dbg("Response for flow %d to %s.", flow->id, dst); + + if (err < 0) { + log_warn("Allocation rejected: %s (%d).", dst, err); + goto fail_peer; + } + + err = oap_cli_complete(ctx, &info, resp_hdr, data, sk); + if (err < 0) { + log_err("OAP completion failed for %s.", dst); + goto fail_complete; + } + + freebuf(req_hdr); + freebuf(resp_hdr); + freebuf(hash); + + return 0; + + fail_complete: + ctx = NULL; /* freee'd on complete */ + fail_peer: + flow->state = FLOW_DEALLOCATED; + 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: + return err; +} + static int flow_alloc_reply(struct flow_info * flow, int response, buffer_t * data) @@ -1666,6 +1393,12 @@ static int flow_dealloc(struct flow_info * flow, reg_dealloc_flow(flow); + if (reg_flow_is_direct(flow->id)) { + if (flow->state == FLOW_DEALLOCATED) + reg_destroy_flow(flow->id); + return 0; + } + if (ipcp_flow_dealloc(flow->n_1_pid, flow->id, ts->tv_sec) < 0) { log_err("Failed to request dealloc from %d.", flow->n_1_pid); return -EIPCP; @@ -1742,21 +1475,24 @@ static void __cleanup_irm_msg(void * o) irm_msg__free_unpacked((irm_msg_t *) o, NULL); } -static irm_msg_t * do_command_msg(irm_msg_t * msg) +static irm_msg_t * do_command_msg(irm_msg_t * msg, + int fd) { - 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)); @@ -1821,7 +1557,11 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) case IRM_MSG_CODE__IRM_PROC_ANNOUNCE: proc.pid = msg->pid; strcpy(proc.prog, msg->prog); - res = proc_announce(&proc); + res = get_peer_ids(fd, &proc.uid, &proc.gid); + if (res < 0) + log_err("Failed to get UID/GID for pid %d.", msg->pid); + else + res = proc_announce(&proc); break; case IRM_MSG_CODE__IRM_PROC_EXIT: res = proc_exit(msg->pid); @@ -1858,15 +1598,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 +1628,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 +1701,16 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) else ret_msg->result = res; + crypt_secure_clear(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; @@ -1981,7 +1746,7 @@ static void * mainloop(void * o) pthread_cleanup_push(__cleanup_close_ptr, &sfd); pthread_cleanup_push(__cleanup_irm_msg, msg); - ret_msg = do_command_msg(msg); + ret_msg = do_command_msg(msg, sfd); pthread_cleanup_pop(true); pthread_cleanup_pop(false); @@ -2074,7 +1839,7 @@ static void destroy_mount(char * mnt) static int ouroboros_reset(void) { - shm_rdrbuff_purge(); + ssm_pool_gspp_purge(); lockfile_destroy(irmd.lf); return 0; @@ -2095,10 +1860,8 @@ static void cleanup_pid(pid_t pid) } destroy_mount(mnt); - -#else - (void) pid; #endif + ssm_pool_reclaim_orphans(irmd.gspp, pid); } void * irm_sanitize(void * o) @@ -2170,7 +1933,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; @@ -2198,6 +1961,8 @@ static int irm_load_store(char * dpath) static int irm_init(void) { struct stat st; + struct group * grp; + gid_t gid; pthread_condattr_t cattr; #ifdef HAVE_FUSE mode_t mask; @@ -2283,11 +2048,23 @@ static int irm_init(void) goto fail_sock_path; } - if ((irmd.rdrb = shm_rdrbuff_create()) == NULL) { - log_err("Failed to create rdrbuff."); - goto fail_rdrbuff; + grp = getgrnam("ouroboros"); + if (grp == NULL) { + log_warn("ouroboros group not found, using gid %d.", getgid()); + gid = getgid(); + } else { + gid = grp->gr_gid; + } + + irmd.gspp = ssm_pool_create(getuid(), gid); + if (irmd.gspp == NULL) { + log_err("Failed to create GSPP."); + goto fail_pool; } + if (ssm_pool_mlock(irmd.gspp) < 0) + log_warn("Failed to mlock pool."); + irmd.tpm = tpm_create(IRMD_MIN_THREADS, IRMD_ADD_THREADS, mainloop, NULL); if (irmd.tpm == NULL) { @@ -2295,27 +2072,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; - } - - irmd.auth.ctx = auth_create_ctx(); - if (irmd.auth.ctx == NULL) { - log_err("Failed to create auth store context."); - goto fail_auth_ctx; + if (oap_auth_init() < 0) { + log_err("Failed to initialize OAP module."); + goto fail_oap; } - 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,15 +2121,14 @@ 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); - fail_rdrbuff: + ssm_pool_destroy(irmd.gspp); + fail_pool: close(irmd.sockfd); fail_sock_path: unlink(IRM_SOCK_PATH); @@ -2388,18 +2156,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); @@ -2408,8 +2165,7 @@ static void irm_fini(void) if (unlink(IRM_SOCK_PATH)) log_dbg("Failed to unlink %s.", IRM_SOCK_PATH); - if (irmd.rdrb != NULL) - shm_rdrbuff_destroy(irmd.rdrb); + ssm_pool_destroy(irmd.gspp); if (irmd.lf != NULL) lockfile_destroy(irmd.lf); @@ -2532,10 +2288,8 @@ static void irm_argparse(int argc, argc--; argv++; } else if (strcmp(*argv, "--version") == 0) { - printf("Ouroboros version %d.%d.%d\n", - OUROBOROS_VERSION_MAJOR, - OUROBOROS_VERSION_MINOR, - OUROBOROS_VERSION_PATCH); + printf("Ouroboros version %s\n", + OUROBOROS_VERSION_STRING); exit(EXIT_SUCCESS); #ifdef HAVE_TOML } else if (strcmp (*argv, "--config") == 0) { @@ -2637,6 +2391,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 +2415,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..1831f533 100644 --- a/src/irmd/oap.c +++ b/src/irmd/oap.c @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * - * Ouroboros flow allocation protocol header + * OAP - Shared credential and configuration loading * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -29,260 +29,102 @@ #define OUROBOROS_PREFIX "irmd/oap" #include <ouroboros/crypt.h> -#include <ouroboros/endian.h> +#include <ouroboros/errno.h> #include <ouroboros/logs.h> -#include <ouroboros/rib.h> -#include <ouroboros/time.h> #include "config.h" -#include "oap.h" - #include <assert.h> +#include <string.h> +#include <sys/stat.h> -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: <none>"); - - if (hdr->eph.len > 0) - log_proto(" Ephemeral Public Key: [%zu bytes]", hdr->eph.len); - else - log_proto(" Ephemeral Public Key: <none>"); - if (hdr->data.len > 0) - log_proto(" Data: [%zu bytes]", hdr->data.len); - else - log_proto(" Data: <none>"); - if (hdr->sig.len > 0) - log_proto(" Signature: [%zu bytes]", hdr->sig.len); - else - log_proto(" Signature: <none>"); + 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..d6d8dfe2 100644 --- a/src/irmd/oap.h +++ b/src/irmd/oap.h @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * - * Ouroboros flow allocation protocol header + * Ouroboros Allocation Protocol (OAP) Component * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -23,72 +23,45 @@ #ifndef OUROBOROS_IRMD_OAP_H #define OUROBOROS_IRMD_OAP_H +#include <ouroboros/crypt.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> #include <ouroboros/utils.h> -#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..a11ab158 --- /dev/null +++ b/src/irmd/oap/auth.c @@ -0,0 +1,252 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - Authentication, replay detection, and validation + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/list.h> +#include <ouroboros/logs.h> +#include <ouroboros/pthread.h> +#include <ouroboros/time.h> + +#include "config.h" + +#include "auth.h" +#include "hdr.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +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/tests/irm_test.c b/src/irmd/oap/auth.h index d440289c..4f748750 100644 --- a/src/irmd/tests/irm_test.c +++ b/src/irmd/oap/auth.h @@ -1,7 +1,8 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - Authentication functions * - * Unit tests of IRMd functions * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> * @@ -19,15 +20,16 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ +#ifndef OUROBOROS_IRMD_OAP_AUTH_H +#define OUROBOROS_IRMD_OAP_AUTH_H +#include "hdr.h" -int irm_test(int argc, - char **argv) -{ - int ret = 0; +int oap_check_hdr(const struct oap_hdr * hdr); - (void) argc; - (void) argv; +/* 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); - return ret; -} +#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..8ecd317d --- /dev/null +++ b/src/irmd/oap/cli.c @@ -0,0 +1,576 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - Client-side processing + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/logs.h> +#include <ouroboros/random.h> + +#include "config.h" + +#include "auth.h" +#include "hdr.h" +#include "io.h" +#include "../oap.h" + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* 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); + crypt_secure_clear(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; + } + + 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); + + /* oap_hdr_encode repoints id into hdr; restore to __id */ + s->local_hdr.id = s->id; + + 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; + crypt_secure_clear(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; + crypt_secure_clear(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; + int cipher_nid; + int kdf_nid; + + if (!IS_KEX_ALGO_SET(kcfg)) + return 0; + + /* Save client's configured minimums */ + cipher_nid = kcfg->c.nid; + kdf_nid = kcfg->k.nid; + + /* 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; + } + + /* Verify server cipher >= client's minimum */ + if (crypt_cipher_rank(kcfg->c.nid) < crypt_cipher_rank(cipher_nid)) { + log_err_id(id, "Server cipher %s too weak.", + peer_hdr->cipher_str); + return -ECRYPT; + } + + log_dbg_id(id, "Accepted server cipher %s.", + peer_hdr->cipher_str); + + /* Accept server's KDF for non-client-encap modes */ + if (kcfg->x.mode != KEM_MODE_CLIENT_ENCAP + && peer_hdr->kdf_nid != NID_undef) { + if (crypt_kdf_rank(peer_hdr->kdf_nid) + < crypt_kdf_rank(kdf_nid)) { + log_err_id(id, "Server KDF too weak."); + return -ECRYPT; + } + SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid); + log_dbg_id(id, "Accepted server KDF %s.", + md_nid_to_str(kcfg->k.nid)); + } + + /* 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..5465dd2a --- /dev/null +++ b/src/irmd/oap/hdr.c @@ -0,0 +1,465 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - Header encoding, decoding, and debugging + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/endian.h> +#include <ouroboros/hash.h> +#include <ouroboros/logs.h> +#include <ouroboros/rib.h> +#include <ouroboros/time.h> + +#include "config.h" + +#include "hdr.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +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 +#define OAP_KEX_IS_KEM(hdr) ((hdr)->kex_flags.role | (hdr)->kex_flags.fmt) +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: <none>"); + + if (hdr->kex.len > 0) { + if (OAP_KEX_IS_KEM(hdr)) + log_proto(" Key Exchange Data:" + " [%zu bytes] [%s]", + hdr->kex.len, + hdr->kex_flags.role ? + "Client encaps" : + "Server encaps"); + else + log_proto(" Key Exchange Data:" + " [%zu bytes]", + hdr->kex.len); + } else + log_proto(" Key Exchange Data: <none>"); + + if (hdr->cipher_str != NULL) + log_proto(" Cipher: %s", hdr->cipher_str); + else + log_proto(" Cipher: <none>"); + + if (hdr->kdf_str != NULL) + log_proto(" KDF: HKDF-%s", hdr->kdf_str); + else + log_proto(" KDF: <none>"); + + if (hdr->md_str != NULL) + log_proto(" Digest: %s", hdr->md_str); + else + log_proto(" Digest: <none>"); + + if (hdr->data.len > 0) + log_proto(" Data: [%zu bytes]", hdr->data.len); + else + log_proto(" Data: <none>"); + + if (hdr->req_hash.len > 0) + log_proto(" Req Hash: [%zu bytes]", hdr->req_hash.len); + else + log_proto(" Req Hash: <none>"); + + if (hdr->sig.len > 0) + log_proto(" Signature: [%zu bytes]", hdr->sig.len); + else + log_proto(" Signature: <none>"); +} +#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..6016452c --- /dev/null +++ b/src/irmd/oap/hdr.h @@ -0,0 +1,159 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - Header definitions and functions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/utils.h> + +#include <stdbool.h> +#include <stdint.h> + +#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..6dd44d56 --- /dev/null +++ b/src/irmd/oap/internal.h @@ -0,0 +1,118 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP internal definitions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/list.h> +#include <ouroboros/name.h> +#include <ouroboros/pthread.h> +#include <ouroboros/utils.h> + +#include "hdr.h" + +#include <stdbool.h> +#include <stdint.h> + +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); + +int oap_negotiate_cipher(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg); + +#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 + +#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); + +#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..c2c91b91 --- /dev/null +++ b/src/irmd/oap/io.c @@ -0,0 +1,138 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - File I/O for credentials and configuration + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/logs.h> + +#include "config.h" + +#include "io.h" + +#include <assert.h> +#include <string.h> +#include <sys/stat.h> + +/* + * 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; + } +#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); + return -ENOTSUP; + } +#endif + if (cfg->c.nid == NID_undef) { + log_err("Invalid cipher for %s.", 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..2d47c62f --- /dev/null +++ b/src/irmd/oap/io.h @@ -0,0 +1,40 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - Credential and configuration file I/O + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/name.h> + +#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..36391e50 --- /dev/null +++ b/src/irmd/oap/srv.c @@ -0,0 +1,498 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * OAP - Server-side processing + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/logs.h> + +#include "config.h" + +#include "auth.h" +#include "hdr.h" +#include "io.h" +#include "oap.h" + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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_cipher(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg) +{ + uint8_t * id = peer_hdr->id.data; + int cli_nid; + int cli_rank; + int srv_rank; + + /* Cipher: select the strongest of client and server */ + cli_nid = peer_hdr->cipher_str != NULL + ? (int) crypt_str_to_nid(peer_hdr->cipher_str) + : NID_undef; + + if (cli_nid != NID_undef + && crypt_cipher_rank(cli_nid) < 0) { + log_err_id(id, "Unsupported cipher '%s'.", + peer_hdr->cipher_str); + return -ENOTSUP; + } + + cli_rank = crypt_cipher_rank(cli_nid); + srv_rank = crypt_cipher_rank(kcfg->c.nid); + + if (cli_rank > srv_rank) { + SET_KEX_CIPHER_NID(kcfg, cli_nid); + log_dbg_id(id, "Selected client cipher %s.", + kcfg->c.str); + } else if (srv_rank > 0) { + log_dbg_id(id, "Selected server cipher %s.", + kcfg->c.str); + } else { + log_err_id(id, "Encryption requested, no cipher."); + return -ECRYPT; + } + + /* KDF: select the strongest of client and server */ + if (peer_hdr->kdf_nid != NID_undef + && crypt_kdf_rank(peer_hdr->kdf_nid) < 0) { + log_err_id(id, "Unsupported KDF NID %d.", + peer_hdr->kdf_nid); + return -ENOTSUP; + } + + cli_rank = crypt_kdf_rank(peer_hdr->kdf_nid); + srv_rank = crypt_kdf_rank(kcfg->k.nid); + + /* + * For client-encap KEM, the KDF is baked into + * the ciphertext. The server must use the client's + * KDF and can only verify the minimum. + */ + if (OAP_KEX_ROLE(peer_hdr) == KEM_MODE_CLIENT_ENCAP) { + if (srv_rank > cli_rank) { + log_err_id(id, "Client KDF too weak."); + return -ECRYPT; + } + SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid); + } else if (cli_rank > srv_rank) { + SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid); + log_dbg_id(id, "Selected client KDF %s.", + md_nid_to_str(kcfg->k.nid)); + } else if (srv_rank > 0) { + log_dbg_id(id, "Selected server KDF %s.", + md_nid_to_str(kcfg->k.nid)); + } + + 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]; + int srv_kex_nid; + 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_cipher(peer_hdr, kcfg) < 0) + return -ECRYPT; + + /* Save server's configured KEX before overwriting */ + srv_kex_nid = kcfg->x.nid; + + 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); + + /* Reject if client KEX is weaker than server's */ + if (crypt_kex_rank(kcfg->x.nid) + < crypt_kex_rank(srv_kex_nid)) { + log_err_id(id, "Client KEX %s too weak.", + kcfg->x.str); + return -ECRYPT; + } + } + + /* 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); + + if (load_srv_credentials(info, &pkp, &crt) < 0) { + log_err("Failed to load security keys for %s.", info->name); + goto fail_cred; + } + + if (load_srv_kex_config(info, &kcfg) < 0) { + log_err("Failed to load KEX config for %s.", info->name); + goto fail_kex; + } + + /* 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 */ + + if (oap_check_hdr(&peer_hdr) < 0) { + log_err_id(id, "OAP header failed replay check."); + goto fail_auth; + } + + 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; + + sk->nid = kcfg.c.nid; + + /* 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..b534cb72 --- /dev/null +++ b/src/irmd/oap/tests/CMakeLists.txt @@ -0,0 +1,64 @@ +get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) + +get_filename_component(OAP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" DIRECTORY) +get_filename_component(OAP_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" DIRECTORY) +get_filename_component(IRMD_SOURCE_DIR "${OAP_SOURCE_DIR}" DIRECTORY) +get_filename_component(IRMD_BINARY_DIR "${OAP_BINARY_DIR}" DIRECTORY) + +compute_test_prefix() + +create_test_sourcelist(${PARENT_DIR}_tests test_suite.c + # Add new tests here + oap_test.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 +set(OAP_TEST_SOURCES + ${OAP_SOURCE_DIR}/io.c + ${OAP_SOURCE_DIR}/hdr.c + ${OAP_SOURCE_DIR}/auth.c + ${OAP_SOURCE_DIR}/srv.c + ${OAP_SOURCE_DIR}/cli.c + ${CMAKE_CURRENT_SOURCE_DIR}/common.c +) + +# Regular test executable (ECDSA) +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests} ${OAP_TEST_SOURCES}) +set_source_files_properties(${OAP_TEST_SOURCES} + PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" +) + +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test ouroboros-irm) +target_include_directories(${PARENT_DIR}_test PRIVATE + ${IRMD_SOURCE_DIR} + ${IRMD_BINARY_DIR} +) + +# 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}_ml_dsa_test + PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" +) + +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}_ml_dsa_test) + +# Regular tests +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_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/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 <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> + +#include "oap.h" + +#include <string.h> +#include <stdio.h> + +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 <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/utils.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <test/test.h> + +#include <stdbool.h> + +/* 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..2f0f0b4d --- /dev/null +++ b/src/irmd/oap/tests/oap_test.c @@ -0,0 +1,1181 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests of Ouroboros Allocation Protocol (OAP) + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/endian.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <ouroboros/random.h> +#include <ouroboros/time.h> + +#include <test/test.h> +#include <test/certs/ecdsa.h> + +#include "oap.h" +#include "common.h" + +#include <stdbool.h> +#include <string.h> + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#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 = (uint8_t *) srv_data_str; + + freebuf(ctx.data); + ctx.data.data = srv_data.data; + ctx.data.len = srv_data.len; + srv_data.data = NULL; + srv_data.len = 0; + + 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; + } + + /* Free the copied data */ + free(ctx.data.data); + ctx.data.data = NULL; + ctx.data.len = 0; + + 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 - tested in oap_test_ml_dsa */ + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_roundtrip(kex_supported_nids[i]); + } + + return ret; +} + +/* Cipher negotiation - strongest cipher and KDF are selected */ +static int test_oap_cipher_mismatch(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: AES-128-GCM, SHA-256 */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_128_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: AES-256-GCM, SHA-512 */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha512; + 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 strongest cipher */ + if (ctx.srv.nid != NID_aes_256_gcm) { + printf("Server cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(NID_aes_256_gcm), + crypt_nid_to_str(ctx.srv.nid)); + goto fail_cleanup; + } + + if (ctx.cli.nid != NID_aes_256_gcm) { + printf("Client cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(NID_aes_256_gcm), + 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 != NID_sha512) { + printf("Response KDF mismatch: expected %s, got %s\n", + md_nid_to_str(NID_sha512), + 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; +} + +/* Server encryption, client none: server rejects (no KEX data) */ +static int test_oap_srv_enc_cli_none(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: encryption configured */ + 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: no encryption */ + 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; + } + + /* Server should reject: KEX required but client sent none */ + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should have rejected.\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; +} + +/* Client encryption, server none: use client settings */ +static int test_oap_cli_enc_srv_none(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: no encryption configured */ + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: encryption configured */ + 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; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Key mismatch.\n"); + goto fail_cleanup; + } + + if (ctx.cli.nid != NID_aes_256_gcm) { + printf("Expected %s, got %s.\n", + crypt_nid_to_str(NID_aes_256_gcm), + crypt_nid_to_str(ctx.cli.nid)); + goto fail_cleanup; + } + + if (ctx.srv.nid != NID_aes_256_gcm) { + printf("Expected %s, got %s.\n", + crypt_nid_to_str(NID_aes_256_gcm), + crypt_nid_to_str(ctx.srv.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; +} + +/* Client rejects server response with downgraded cipher */ +static int test_oap_cli_rejects_downgrade(void) +{ + struct oap_test_ctx ctx; + uint16_t weak; + + TEST_START(); + + 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 = 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; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + /* Tamper: replace cipher NID with weaker one */ + weak = hton16(NID_aes_128_ctr); + memcpy(ctx.resp_hdr.data + OAP_CIPHER_NID_OFFSET, + &weak, sizeof(weak)); + + /* Client should reject the downgraded cipher */ + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client accepted downgrade.\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 client with weaker KEX algorithm */ +static int test_oap_srv_rejects_weak_kex(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: secp521r1 (strong) */ + test_cfg.srv.kex = NID_secp521r1; + 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: ffdhe2048 (weakest) */ + test_cfg.cli.kex = NID_ffdhe2048; + 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; + } + + /* Server should reject: client KEX too weak */ + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject weak KEX.\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 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 */ + ret |= test_oap_roundtrip_md(NID_undef); + + /* 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_srv_enc_cli_none(); + ret |= test_oap_cli_enc_srv_none(); + ret |= test_oap_cli_rejects_downgrade(); + ret |= test_oap_srv_rejects_weak_kex(); + + 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_srv_enc_cli_none; + (void) test_oap_cli_enc_srv_none; + (void) test_oap_cli_rejects_downgrade; + (void) test_oap_srv_rejects_weak_kex; + (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_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 <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 <ouroboros/crypt.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <ouroboros/random.h> +#include <test/test.h> + +#include <test/certs/ml_dsa.h> + +#include "oap.h" +#include "common.h" + +#include <stdbool.h> +#include <string.h> + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#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/reg/CMakeLists.txt b/src/irmd/reg/CMakeLists.txt deleted file mode 100644 index d3844908..00000000 --- a/src/irmd/reg/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -if(BUILD_TESTS) - add_subdirectory(tests) -endif () diff --git a/src/irmd/reg/flow.c b/src/irmd/reg/flow.c index 4d091b23..93c3e128 100644 --- a/src/irmd/reg/flow.c +++ b/src/irmd/reg/flow.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Flows * @@ -66,11 +66,11 @@ struct reg_flow * reg_flow_create(const struct flow_info * info) static void destroy_rbuffs(struct reg_flow * flow) { if (flow->n_rb != NULL) - shm_rbuff_destroy(flow->n_rb); + ssm_rbuff_destroy(flow->n_rb); flow->n_rb = NULL; if (flow->n_1_rb != NULL) - shm_rbuff_destroy(flow->n_1_rb); + ssm_rbuff_destroy(flow->n_1_rb); flow->n_1_rb = NULL; } @@ -80,7 +80,7 @@ void reg_flow_destroy(struct reg_flow * flow) switch(flow->info.state) { case FLOW_ACCEPT_PENDING: - clrbuf(flow->data); + clrbuf(flow->req_data); /* FALLTHRU */ default: destroy_rbuffs(flow); @@ -89,8 +89,10 @@ void reg_flow_destroy(struct reg_flow * flow) assert(flow->n_rb == NULL); assert(flow->n_1_rb == NULL); - assert(flow->data.data == NULL); - assert(flow->data.len == 0); + assert(flow->req_data.data == NULL); + assert(flow->req_data.len == 0); + assert(flow->rsp_data.data == NULL); + assert(flow->rsp_data.len == 0); assert(list_is_empty(&flow->next)); @@ -103,22 +105,28 @@ static int create_rbuffs(struct reg_flow * flow, assert(flow != NULL); assert(info != NULL); - flow->n_rb = shm_rbuff_create(info->n_pid, info->id); + flow->n_rb = ssm_rbuff_create(info->n_pid, info->id); if (flow->n_rb == NULL) goto fail_n_rb; + if (ssm_rbuff_mlock(flow->n_rb) < 0) + log_warn("Failed to mlock n_rb for flow %d.", info->id); + assert(flow->info.n_1_pid == 0); assert(flow->n_1_rb == NULL); flow->info.n_1_pid = info->n_1_pid; - flow->n_1_rb = shm_rbuff_create(info->n_1_pid, info->id); + flow->n_1_rb = ssm_rbuff_create(info->n_1_pid, info->id); if (flow->n_1_rb == NULL) goto fail_n_1_rb; + if (ssm_rbuff_mlock(flow->n_1_rb) < 0) + log_warn("Failed to mlock n_1_rb for flow %d.", info->id); + return 0; fail_n_1_rb: - shm_rbuff_destroy(flow->n_rb); + ssm_rbuff_destroy(flow->n_rb); fail_n_rb: return -ENOMEM; } @@ -172,6 +180,7 @@ int reg_flow_update(struct reg_flow * flow, } flow->info.state = info->state; + flow->info.uid = info->uid; *info = flow->info; @@ -179,30 +188,3 @@ int reg_flow_update(struct reg_flow * flow, fail: return -ENOMEM; } - -void reg_flow_set_data(struct reg_flow * flow, - const buffer_t * buf) -{ - assert(flow != NULL); - assert(buf != NULL); - assert(flow->data.data == NULL); - assert(flow->data.len == 0); - - flow->data = *buf; -} - -void reg_flow_get_data(struct reg_flow * flow, - buffer_t * buf) -{ - assert(flow != NULL); - assert(buf != NULL); - - *buf = flow->data; - - clrbuf(flow->data); -} - -void reg_flow_free_data(struct reg_flow * flow) -{ - freebuf(flow->data); -} diff --git a/src/irmd/reg/flow.h b/src/irmd/reg/flow.h index d1e4811c..9a4046d3 100644 --- a/src/irmd/reg/flow.h +++ b/src/irmd/reg/flow.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Flows * @@ -28,9 +28,10 @@ #include <ouroboros/name.h> #include <ouroboros/pthread.h> #include <ouroboros/qos.h> -#include <ouroboros/shm_rbuff.h> +#include <ouroboros/ssm_rbuff.h> #include <ouroboros/utils.h> +#include <stdbool.h> #include <sys/types.h> #include <time.h> @@ -40,13 +41,16 @@ struct reg_flow { struct flow_info info; int response; - buffer_t data; + buffer_t req_data; + buffer_t rsp_data; struct timespec t0; char name[NAME_SIZE + 1]; - struct shm_rbuff * n_rb; - struct shm_rbuff * n_1_rb; + bool direct; + + struct ssm_rbuff * n_rb; + struct ssm_rbuff * n_1_rb; }; struct reg_flow * reg_flow_create(const struct flow_info * info); @@ -56,12 +60,4 @@ void reg_flow_destroy(struct reg_flow * flow); int reg_flow_update(struct reg_flow * flow, struct flow_info * info); -void reg_flow_set_data(struct reg_flow * flow, - const buffer_t * buf); - -void reg_flow_get_data(struct reg_flow * flow, - buffer_t * buf); - -void reg_flow_free_data(struct reg_flow * flow); - #endif /* OUROBOROS_IRMD_REG_FLOW_H */ diff --git a/src/irmd/reg/ipcp.c b/src/irmd/reg/ipcp.c index 474527a4..b193e28f 100644 --- a/src/irmd/reg/ipcp.c +++ b/src/irmd/reg/ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - IPCPs * @@ -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/ipcp.h b/src/irmd/reg/ipcp.h index 375973a7..fe9d3bf1 100644 --- a/src/irmd/reg/ipcp.h +++ b/src/irmd/reg/ipcp.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - IPCPs * diff --git a/src/irmd/reg/name.c b/src/irmd/reg/name.c index 4e609711..61a328ec 100644 --- a/src/irmd/reg/name.c +++ b/src/irmd/reg/name.c @@ -1,6 +1,6 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Names * @@ -69,9 +69,9 @@ struct reg_name * reg_name_create(const struct name_info * info) memset(name, 0, sizeof(*name)); list_head_init(&name->next); - list_head_init(&name->progs.list); - list_head_init(&name->procs.list); - list_head_init(&name->active.list); + llist_init(&name->progs); + llist_init(&name->procs); + llist_init(&name->active); name->info = *info; @@ -87,13 +87,9 @@ void reg_name_destroy(struct reg_name * name) assert(list_is_empty(&name->next)); - assert(name->progs.len == 0); - assert(name->procs.len == 0); - assert(name->active.len == 0); - - assert(list_is_empty(&name->progs.list)); - assert(list_is_empty(&name->procs.list)); - assert(list_is_empty(&name->active.list)); + assert(llist_is_empty(&name->progs)); + assert(llist_is_empty(&name->procs)); + assert(llist_is_empty(&name->active)); free(name); } @@ -106,7 +102,7 @@ static struct proc_entry * __reg_name_get_active(const struct reg_name * name, assert(name != NULL); assert(pid > 0); - list_for_each(p, &name->active.list) { + llist_for_each(p, &name->active) { struct proc_entry * entry; entry = list_entry(p, struct proc_entry, next); if (entry->pid == pid) @@ -122,13 +118,12 @@ static void __reg_name_del_all_active(struct reg_name * name, struct list_head * p; struct list_head * h; - list_for_each_safe(p, h, &name->active.list) { + llist_for_each_safe(p, h, &name->active) { struct proc_entry * entry; entry = list_entry(p, struct proc_entry, next); if (entry->pid == pid) { - list_del(&entry->next); + llist_del(&entry->next, &name->active); free(entry); - --name->active.len; } } } @@ -141,7 +136,7 @@ static struct proc_entry * __reg_name_get_proc(const struct reg_name * name, assert(name != NULL); assert(pid > 0); - list_for_each(p, &name->procs.list) { + llist_for_each(p, &name->procs) { struct proc_entry * entry; entry = list_entry(p, struct proc_entry, next); if (entry->pid == pid) @@ -159,7 +154,7 @@ static struct prog_entry * __reg_name_get_prog(const struct reg_name * name, assert(name != NULL); assert(prog != NULL); - list_for_each(p, &name->progs.list) { + llist_for_each(p, &name->progs) { struct prog_entry * entry; entry = list_entry(p, struct prog_entry, next); if (strcmp(entry->exec[0], prog) == 0) @@ -194,17 +189,15 @@ int reg_name_add_active(struct reg_name * name, switch (name->info.pol_lb) { case LB_RR: /* Round robin policy. */ - list_add_tail(&entry->next, &name->active.list); + llist_add_tail(&entry->next, &name->active); break; case LB_SPILL: /* Keep accepting flows on the current process */ - list_add(&entry->next, &name->active.list); + llist_add(&entry->next, &name->active); break; default: goto fail_unreachable; } - ++name->active.len; - return 0; fail_unreachable: @@ -223,9 +216,7 @@ void reg_name_del_active(struct reg_name * name, if (entry == NULL) return; - list_del(&entry->next); - - --name->active.len; + llist_del(&entry->next, &name->active); free(entry); } @@ -236,10 +227,10 @@ pid_t reg_name_get_active(struct reg_name * name) assert(name != NULL); - if (list_is_empty(&name->active.list)) + if (llist_is_empty(&name->active)) return -1; - e = list_first_entry(&name->active.list, struct proc_entry, next); + e = llist_first_entry(&name->active, struct proc_entry, next); return e->pid; } @@ -262,9 +253,7 @@ int reg_name_add_proc(struct reg_name * name, entry->pid = pid; - list_add(&entry->next, &name->procs.list); - - ++name->procs.len; + llist_add(&entry->next, &name->procs); return 0; @@ -286,12 +275,10 @@ void reg_name_del_proc(struct reg_name * name, __reg_name_del_all_active(name, pid); - list_del(&entry->next); + llist_del(&entry->next, &name->procs); free(entry); - --name->procs.len; - assert(__reg_name_get_proc(name, pid) == NULL); } @@ -324,12 +311,10 @@ int reg_name_add_prog(struct reg_name * name, goto fail_exec; } - list_add(&entry->next, &name->progs.list); + llist_add(&entry->next, &name->progs); log_dbg("Add prog %s to name %s.", exec[0], name->info.name); - ++name->progs.len; - return 0; fail_exec: @@ -350,12 +335,10 @@ void reg_name_del_prog(struct reg_name * name, if (entry == NULL) return; - list_del(&entry->next); + llist_del(&entry->next, &name->progs); __free_prog_entry(entry); - --name->progs.len; - assert(__reg_name_get_prog(name, prog) == NULL); } @@ -372,10 +355,10 @@ char ** reg_name_get_exec(const struct reg_name * name) { struct prog_entry * e; - if (list_is_empty(&name->progs.list)) + if (llist_is_empty(&name->progs)) return NULL; - e = list_first_entry(&name->progs.list, struct prog_entry, next); + e = llist_first_entry(&name->progs, struct prog_entry, next); return e->exec; } diff --git a/src/irmd/reg/name.h b/src/irmd/reg/name.h index 30a64e1c..59d6d9bd 100644 --- a/src/irmd/reg/name.h +++ b/src/irmd/reg/name.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Names * @@ -38,20 +38,11 @@ struct reg_name { void * crt; } cache; - struct { - struct list_head list; - size_t len; - } progs; /* autostart programs for this name */ + struct llist progs; /* autostart programs for this name */ - struct { - struct list_head list; - size_t len; - } procs; /* processes bound to this name */ + struct llist procs; /* processes bound to this name */ - struct { - struct list_head list; - size_t len; - } active; /* processes actively calling accept */ + struct llist active; /* processes actively calling accept */ }; struct reg_name * reg_name_create(const struct name_info * info); diff --git a/src/irmd/reg/pool.c b/src/irmd/reg/pool.c new file mode 100644 index 00000000..4b1486bb --- /dev/null +++ b/src/irmd/reg/pool.c @@ -0,0 +1,97 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * The IPC Resource Manager - Registry - Per-User Pools + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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 + +#define OUROBOROS_PREFIX "reg/pool" + +#include <ouroboros/logs.h> +#include <ouroboros/ssm_pool.h> + +#include "pool.h" + +#include <assert.h> +#include <stdlib.h> + +struct reg_pool * reg_pool_create(uid_t uid, + gid_t gid) +{ + struct reg_pool * pool; + + pool = malloc(sizeof(*pool)); + if (pool == NULL) { + log_err("Failed to malloc pool."); + goto fail_malloc; + } + + pool->ssm = ssm_pool_create(uid, gid); + if (pool->ssm == NULL) { + log_err("Failed to create PUP for uid %d.", uid); + goto fail_ssm; + } + + list_head_init(&pool->next); + pool->uid = uid; + pool->gid = gid; + pool->refcount = 1; + + log_dbg("Created PUP for uid %d gid %d.", uid, gid); + + return pool; + + fail_ssm: + free(pool); + fail_malloc: + return NULL; +} + +void reg_pool_destroy(struct reg_pool * pool) +{ + assert(pool != NULL); + assert(pool->refcount == 0); + + log_dbg("Destroying PUP for uid %d.", pool->uid); + + ssm_pool_destroy(pool->ssm); + + assert(list_is_empty(&pool->next)); + + free(pool); +} + +void reg_pool_ref(struct reg_pool * pool) +{ + assert(pool != NULL); + assert(pool->refcount > 0); + + pool->refcount++; +} + +int reg_pool_unref(struct reg_pool * pool) +{ + assert(pool != NULL); + assert(pool->refcount > 0); + + pool->refcount--; + + return pool->refcount == 0 ? 0 : 1; +} diff --git a/src/irmd/reg/pool.h b/src/irmd/reg/pool.h new file mode 100644 index 00000000..576f491c --- /dev/null +++ b/src/irmd/reg/pool.h @@ -0,0 +1,48 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * The IPC Resource Manager - Registry - Per-User Pools + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * 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_REG_POOL_H +#define OUROBOROS_IRMD_REG_POOL_H + +#include <ouroboros/list.h> +#include <ouroboros/ssm_pool.h> + +#include <sys/types.h> + +struct reg_pool { + struct list_head next; + uid_t uid; + gid_t gid; + size_t refcount; + struct ssm_pool * ssm; +}; + +struct reg_pool * reg_pool_create(uid_t uid, + gid_t gid); + +void reg_pool_destroy(struct reg_pool * pool); + +void reg_pool_ref(struct reg_pool * pool); + +int reg_pool_unref(struct reg_pool * pool); + +#endif /* OUROBOROS_IRMD_REG_POOL_H */ diff --git a/src/irmd/reg/proc.c b/src/irmd/reg/proc.c index 9bbdf0eb..8a7e24c9 100644 --- a/src/irmd/reg/proc.c +++ b/src/irmd/reg/proc.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Processes * @@ -25,6 +25,7 @@ #define OUROBOROS_PREFIX "reg/proc" #include <ouroboros/logs.h> +#include <ouroboros/utils.h> #include "proc.h" @@ -54,12 +55,11 @@ static void __reg_proc_clear_names(struct reg_proc * proc) assert(proc != NULL); - list_for_each_safe(p, h, &proc->names) { + llist_for_each_safe(p, h, &proc->names) { struct name_entry * entry; entry = list_entry(p, struct name_entry, next); - list_del(&entry->next); + llist_del(&entry->next, &proc->names); __free_name_entry(entry); - proc->n_names--; } } @@ -75,17 +75,18 @@ struct reg_proc * reg_proc_create(const struct proc_info * info) goto fail_malloc; } - proc->set = shm_flow_set_create(info->pid); + memset(proc, 0, sizeof(*proc)); + + proc->set = ssm_flow_set_create(info->pid); if (proc->set == NULL) { log_err("Failed to create flow set for %d.", info->pid); goto fail_set; } list_head_init(&proc->next); - list_head_init(&proc->names); + llist_init(&proc->names); proc->info = *info; - proc->n_names = 0; return proc; @@ -99,15 +100,13 @@ void reg_proc_destroy(struct reg_proc * proc) { assert(proc != NULL); - shm_flow_set_destroy(proc->set); + ssm_flow_set_destroy(proc->set); __reg_proc_clear_names(proc); assert(list_is_empty(&proc->next)); - assert(proc->n_names == 0); - - assert(list_is_empty(&proc->names)); + assert(llist_is_empty(&proc->names)); free(proc); } @@ -117,7 +116,7 @@ static struct name_entry * __reg_proc_get_name(const struct reg_proc * proc, { struct list_head * p; - list_for_each(p, &proc->names) { + llist_for_each(p, &proc->names) { struct name_entry * entry; entry = list_entry(p, struct name_entry, next); if (strcmp(entry->name, name) == 0) @@ -146,9 +145,7 @@ int reg_proc_add_name(struct reg_proc * proc, goto fail_name; } - list_add(&entry->next, &proc->names); - - proc->n_names++; + llist_add(&entry->next, &proc->names); return 0; @@ -167,12 +164,10 @@ void reg_proc_del_name(struct reg_proc * proc, if(entry == NULL) return; - list_del(&entry->next); + llist_del(&entry->next, &proc->names); __free_name_entry(entry); - proc->n_names--; - assert(__reg_proc_get_name(proc, name) == NULL); } @@ -181,3 +176,10 @@ bool reg_proc_has_name(const struct reg_proc * proc, { return __reg_proc_get_name(proc, name) != NULL; } + +bool reg_proc_is_privileged(const struct reg_proc * proc) +{ + assert(proc != NULL); + + return is_ouroboros_member_uid(proc->info.uid); +} diff --git a/src/irmd/reg/proc.h b/src/irmd/reg/proc.h index 499ecc72..18cc2803 100644 --- a/src/irmd/reg/proc.h +++ b/src/irmd/reg/proc.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Processes * @@ -25,17 +25,16 @@ #include <ouroboros/list.h> #include <ouroboros/proc.h> -#include <ouroboros/shm_flow_set.h> +#include <ouroboros/ssm_flow_set.h> struct reg_proc { struct list_head next; struct proc_info info; - struct list_head names; /* process accepts flows for names */ - size_t n_names; /* number of names */ + struct llist names; /* process accepts flows for names */ - struct shm_flow_set * set; + struct ssm_flow_set * set; }; struct reg_proc * reg_proc_create(const struct proc_info * info); @@ -53,4 +52,6 @@ void reg_proc_del_name(struct reg_proc * proc, bool reg_proc_has_name(const struct reg_proc * proc, const char * name); +bool reg_proc_is_privileged(const struct reg_proc * proc); + #endif /* OUROBOROS_IRMD_REG_PROC_H */ diff --git a/src/irmd/reg/prog.c b/src/irmd/reg/prog.c index 9b9e7510..2d7f9f8d 100644 --- a/src/irmd/reg/prog.c +++ b/src/irmd/reg/prog.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Programs * @@ -55,12 +55,11 @@ static void __reg_prog_clear_names(struct reg_prog * prog) assert(prog != NULL); - list_for_each_safe(p, h, &prog->names) { + llist_for_each_safe(p, h, &prog->names) { struct name_entry * entry; entry = list_entry(p, struct name_entry, next); - list_del(&entry->next); + llist_del(&entry->next, &prog->names); __free_name_entry(entry); - prog->n_names--; } } @@ -77,10 +76,9 @@ struct reg_prog * reg_prog_create(const struct prog_info * info) } list_head_init(&p->next); - list_head_init(&p->names); + llist_init(&p->names); p->info = *info; - p->n_names = 0; return p; @@ -96,9 +94,7 @@ void reg_prog_destroy(struct reg_prog * prog) assert(list_is_empty(&prog->next)); - assert(prog->n_names == 0); - - assert(list_is_empty(&prog->names)); + assert(llist_is_empty(&prog->names)); free(prog); } @@ -108,7 +104,7 @@ static struct name_entry * __reg_prog_get_name(const struct reg_prog * prog, { struct list_head * p; - list_for_each(p, &prog->names) { + llist_for_each(p, &prog->names) { struct name_entry * entry; entry = list_entry(p, struct name_entry, next); if (strcmp(entry->name, name) == 0) @@ -137,9 +133,7 @@ int reg_prog_add_name(struct reg_prog * prog, goto fail_name; } - list_add(&entry->next, &prog->names); - - prog->n_names++; + llist_add(&entry->next, &prog->names); return 0; @@ -158,12 +152,10 @@ void reg_prog_del_name(struct reg_prog * prog, if (entry == NULL) return; - list_del(&entry->next); + llist_del(&entry->next, &prog->names); __free_name_entry(entry); - prog->n_names--; - assert(__reg_prog_get_name(prog, name) == NULL); } diff --git a/src/irmd/reg/prog.h b/src/irmd/reg/prog.h index a98fc6a1..e52b8e15 100644 --- a/src/irmd/reg/prog.h +++ b/src/irmd/reg/prog.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Programs * @@ -33,8 +33,7 @@ struct reg_prog { struct prog_info info; - struct list_head names; /* names to listen for */ - size_t n_names; /* number of names in list */ + struct llist names; /* names to listen for */ }; struct reg_prog * reg_prog_create(const struct prog_info * info); diff --git a/src/irmd/reg/reg.c b/src/irmd/reg/reg.c index a24a9d1d..0025f695 100644 --- a/src/irmd/reg/reg.c +++ b/src/irmd/reg/reg.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * The IPC Resource Manager - Registry * @@ -35,6 +35,7 @@ The IPC Resource Manager - Registry #include "flow.h" #include "ipcp.h" #include "name.h" +#include "pool.h" #include "proc.h" #include "prog.h" @@ -46,28 +47,18 @@ The IPC Resource Manager - Registry #define ID_OFFT 1 /* reserve some flow_ids */ struct { - struct bmp * flow_ids; /* flow_ids for flows */ - - struct list_head flows; /* flow information */ - size_t n_flows; /* number of flows */ - - struct list_head ipcps; /* list of ipcps in system */ - size_t n_ipcps; /* number of ipcps */ - - struct list_head names; /* registered names known */ - size_t n_names; /* number of names */ - - struct list_head procs; /* processes */ - size_t n_procs; /* number of processes */ - - struct list_head progs; /* programs known */ - size_t n_progs; /* number of programs */ - - struct list_head spawned; /* child processes */ - size_t n_spawned; /* number of child processes */ - - pthread_mutex_t mtx; /* registry lock */ - pthread_cond_t cond; /* condvar for reg changes */ + struct bmp * ids; /* flow bitmap */ + + struct llist flows; /* list of flows */ + struct llist ipcps; /* list of ipcps in system */ + struct llist names; /* registered names known */ + struct llist pools; /* per-user pools */ + struct llist procs; /* processes known */ + struct llist progs; /* programs known */ + struct llist spawned; /* child processes */ + + pthread_mutex_t mtx; /* registry lock */ + pthread_cond_t cond; /* condvar for reg changes */ } reg; struct pid_entry { @@ -81,7 +72,7 @@ static struct reg_flow * __reg_get_flow(int flow_id) assert(flow_id >= ID_OFFT); - list_for_each(p, ®.flows) { + llist_for_each(p, ®.flows) { struct reg_flow * entry; entry = list_entry(p, struct reg_flow, next); if (entry->info.id == flow_id) @@ -95,7 +86,7 @@ static struct reg_flow * __reg_get_accept_flow(pid_t pid) { struct list_head * p; - list_for_each(p, ®.flows) { + llist_for_each(p, ®.flows) { struct reg_flow * entry; entry = list_entry(p, struct reg_flow, next); if (entry->info.state != FLOW_ACCEPT_PENDING) @@ -113,7 +104,7 @@ static struct list_head * __reg_after_flow(int flow_id) assert(flow_id >= ID_OFFT); - list_for_each(p, ®.flows) { + llist_for_each(p, ®.flows) { struct reg_flow * entry; entry = list_entry(p, struct reg_flow, next); if (entry->info.id > flow_id) @@ -129,7 +120,7 @@ static struct reg_ipcp * __reg_get_ipcp(pid_t pid) assert(pid > 0); - list_for_each(p, ®.ipcps) { + llist_for_each(p, ®.ipcps) { struct reg_ipcp * entry; entry = list_entry(p, struct reg_ipcp, next); if (entry->info.pid == pid) @@ -143,7 +134,7 @@ static struct reg_ipcp * __reg_get_ipcp_by_layer(const char * layer) { struct list_head * p; - list_for_each(p, ®.ipcps) { + llist_for_each(p, ®.ipcps) { struct reg_ipcp * entry; entry = list_entry(p, struct reg_ipcp, next); if (strcmp(entry->layer.name, layer) == 0) @@ -160,7 +151,7 @@ static struct list_head * __reg_after_ipcp(const struct ipcp_info * info) assert(info != NULL); - list_for_each(p, ®.ipcps) { + llist_for_each(p, ®.ipcps) { struct reg_ipcp * entry; entry = list_entry(p, struct reg_ipcp, next); if (entry->info.type < info->type) @@ -182,7 +173,7 @@ static struct reg_name * __reg_get_name(const char * name) assert(name != NULL); - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); if (strcmp(entry->info.name, name) == 0) @@ -225,7 +216,7 @@ static struct list_head * __reg_after_name(const char * name) assert(name != NULL); - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); if (strcmp(entry->info.name, name) > 0) @@ -235,11 +226,25 @@ static struct list_head * __reg_after_name(const char * name) return p; } +static struct reg_pool * __reg_get_pool(uid_t uid) +{ + struct list_head * p; + + llist_for_each(p, ®.pools) { + struct reg_pool * entry; + entry = list_entry(p, struct reg_pool, next); + if (entry->uid == uid) + return entry; + } + + return NULL; +} + static struct reg_proc * __reg_get_proc(pid_t pid) { struct list_head * p; - list_for_each(p, ®.procs) { + llist_for_each(p, ®.procs) { struct reg_proc * entry; entry = list_entry(p, struct reg_proc, next); if (entry->info.pid == pid) @@ -253,7 +258,7 @@ static struct list_head * __reg_after_proc(pid_t pid) { struct list_head * p; - list_for_each(p, ®.procs) { + llist_for_each(p, ®.procs) { struct reg_proc * entry; entry = list_entry(p, struct reg_proc, next); if (entry->info.pid > pid) @@ -267,7 +272,7 @@ static void __reg_kill_all_proc(int signal) { struct list_head * p; - list_for_each(p, ®.procs) { + llist_for_each(p, ®.procs) { struct reg_proc * entry; entry = list_entry(p, struct reg_proc, next); kill(entry->info.pid, signal); @@ -278,7 +283,7 @@ static pid_t __reg_get_dead_proc(void) { struct list_head * p; - list_for_each(p, ®.procs) { + llist_for_each(p, ®.procs) { struct reg_proc * entry; entry = list_entry(p, struct reg_proc, next); if (kill(entry->info.pid, 0) < 0) @@ -293,7 +298,7 @@ static void __reg_cancel_flows_for_proc(pid_t pid) struct list_head * p; bool changed = false; - list_for_each(p, ®.flows) { + llist_for_each(p, ®.flows) { struct reg_flow * entry; entry = list_entry(p, struct reg_flow, next); if (entry->info.n_pid != pid) @@ -319,7 +324,7 @@ static struct pid_entry * __reg_get_spawned(pid_t pid) { struct list_head * p; - list_for_each(p, ®.spawned) { + llist_for_each(p, ®.spawned) { struct pid_entry * entry; entry = list_entry(p, struct pid_entry, next); if (entry->pid == pid) @@ -333,7 +338,7 @@ static struct list_head * __reg_after_spawned(pid_t pid) { struct list_head * p; - list_for_each(p, ®.spawned) { + llist_for_each(p, ®.spawned) { struct pid_entry * entry; entry = list_entry(p, struct pid_entry, next); if (entry->pid > pid) @@ -347,7 +352,7 @@ static void __reg_kill_all_spawned(int signal) { struct list_head * p; - list_for_each(p, ®.spawned) { + llist_for_each(p, ®.spawned) { struct pid_entry * entry; entry = list_entry(p, struct pid_entry, next); kill(entry->pid, signal); @@ -356,17 +361,17 @@ static void __reg_kill_all_spawned(int signal) static pid_t __reg_first_spawned(void) { - if (list_is_empty(®.spawned)) + if (llist_is_empty(®.spawned)) return -1; - return list_first_entry(®.spawned, struct pid_entry, next)->pid; + return llist_first_entry(®.spawned, struct pid_entry, next)->pid; } static struct reg_prog * __reg_get_prog(const char * name) { struct list_head * p; - list_for_each(p, ®.progs) { + llist_for_each(p, ®.progs) { struct reg_prog * entry; entry = list_entry(p, struct reg_prog, next); if (strcmp(entry->info.name, name) == 0) @@ -380,7 +385,7 @@ static char ** __reg_get_exec(const char * name) { struct list_head * p; - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); if (strcmp(entry->info.name, name) == 0) @@ -394,7 +399,7 @@ static struct list_head * __reg_after_prog(const char * name) { struct list_head * p; - list_for_each(p, ®.progs) { + llist_for_each(p, ®.progs) { struct reg_prog * entry; entry = list_entry(p, struct reg_prog, next); if (strcmp(entry->info.name, name) > 0) @@ -408,7 +413,7 @@ static void __reg_del_name_from_procs(const char * name) { struct list_head * p; - list_for_each(p, ®.procs) { + llist_for_each(p, ®.procs) { struct reg_proc * proc; proc = list_entry(p, struct reg_proc, next); reg_proc_del_name(proc, name); @@ -419,7 +424,7 @@ static void __reg_del_name_from_progs(const char * name) { struct list_head * p; - list_for_each(p, ®.progs) { + llist_for_each(p, ®.progs) { struct reg_prog * prog; prog = list_entry(p, struct reg_prog, next); reg_prog_del_name(prog, name); @@ -431,13 +436,13 @@ static void __reg_proc_update_names(struct reg_proc * proc) struct list_head * p; struct reg_prog * prog; - assert(list_is_empty(&proc->names)); + assert(llist_is_empty(&proc->names)); prog = __reg_get_prog(proc->info.prog); if (prog == NULL) return; - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * name; name = list_entry(p, struct reg_name, next); assert(!reg_name_has_proc(name, proc->info.pid)); @@ -452,7 +457,7 @@ static void __reg_del_proc_from_names(pid_t pid) { struct list_head * p; - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * name; name = list_entry(p, struct reg_name, next); reg_name_del_proc(name, pid); @@ -463,7 +468,7 @@ static void __reg_del_prog_from_names(const char * prog) { struct list_head * p; - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * name; name = list_entry(p, struct reg_name, next); reg_name_del_prog(name, prog); @@ -478,7 +483,7 @@ static int __reg_add_active_proc(pid_t pid) assert(pid > 0); - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * name; name = list_entry(p, struct reg_name, next); if (reg_name_has_proc(name, pid)) { @@ -500,7 +505,7 @@ static void __reg_del_active_proc(pid_t pid) assert(pid > 0); - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * name; name = list_entry(p, struct reg_name, next); reg_name_del_active(name, pid); @@ -529,20 +534,21 @@ int reg_init(void) goto fail_cond; } - reg.flow_ids = bmp_create(SYS_MAX_FLOWS -ID_OFFT, ID_OFFT); - if (reg.flow_ids == NULL) { + reg.ids = bmp_create(SYS_MAX_FLOWS - ID_OFFT, ID_OFFT); + if (reg.ids == NULL) { log_err("Failed to create flow_ids bitmap."); goto fail_flow_ids; } pthread_condattr_destroy(&cattr); - list_head_init(®.flows); - list_head_init(®.ipcps); - list_head_init(®.names); - list_head_init(®.procs); - list_head_init(®.progs); - list_head_init(®.spawned); + llist_init(®.flows); + llist_init(®.ipcps); + llist_init(®.names); + llist_init(®.pools); + llist_init(®.procs); + llist_init(®.progs); + llist_init(®.spawned); return 0; @@ -563,54 +569,56 @@ void reg_clear(void) pthread_mutex_lock(®.mtx); - list_for_each_safe(p, h, ®.spawned) { + llist_for_each_safe(p, h, ®.spawned) { struct pid_entry * entry; entry = list_entry(p, struct pid_entry, next); - list_del(&entry->next); + llist_del(&entry->next, ®.spawned); free(entry); - reg.n_spawned--; } - list_for_each_safe(p, h, ®.progs) { + llist_for_each_safe(p, h, ®.progs) { struct reg_prog * entry; entry = list_entry(p, struct reg_prog, next); - list_del(&entry->next); + llist_del(&entry->next, ®.progs); __reg_del_prog_from_names(entry->info.path); reg_prog_destroy(entry); - reg.n_progs--; } - list_for_each_safe(p, h, ®.procs) { + llist_for_each_safe(p, h, ®.procs) { struct reg_proc * entry; entry = list_entry(p, struct reg_proc, next); - list_del(&entry->next); + llist_del(&entry->next, ®.procs); __reg_del_proc_from_names(entry->info.pid); reg_proc_destroy(entry); - reg.n_procs--; } - list_for_each_safe(p, h, ®.flows) { + llist_for_each_safe(p, h, ®.pools) { + struct reg_pool * entry; + entry = list_entry(p, struct reg_pool, next); + llist_del(&entry->next, ®.pools); + entry->refcount = 0; /* Force destroy during cleanup */ + reg_pool_destroy(entry); + } + + llist_for_each_safe(p, h, ®.flows) { struct reg_flow * entry; entry = list_entry(p, struct reg_flow, next); - list_del(&entry->next); + llist_del(&entry->next, ®.flows); reg_flow_destroy(entry); - reg.n_flows--; } - list_for_each_safe(p, h, ®.names) { + llist_for_each_safe(p, h, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); - list_del(&entry->next); + llist_del(&entry->next, ®.names); reg_name_destroy(entry); - reg.n_names--; } - list_for_each_safe(p, h, ®.ipcps) { + llist_for_each_safe(p, h, ®.ipcps) { struct reg_ipcp * entry; entry = list_entry(p, struct reg_ipcp, next); - list_del(&entry->next); + llist_del(&entry->next, ®.ipcps); reg_ipcp_destroy(entry); - reg.n_ipcps--; } pthread_mutex_unlock(®.mtx); @@ -618,21 +626,15 @@ void reg_clear(void) void reg_fini(void) { - assert(list_is_empty(®.spawned)); - assert(list_is_empty(®.progs)); - assert(list_is_empty(®.procs)); - assert(list_is_empty(®.names)); - assert(list_is_empty(®.ipcps)); - assert(list_is_empty(®.flows)); - - assert(reg.n_spawned == 0); - assert(reg.n_progs == 0); - assert(reg.n_procs == 0); - assert(reg.n_names == 0); - assert(reg.n_ipcps == 0); - assert(reg.n_flows == 0); + assert(llist_is_empty(®.spawned)); + assert(llist_is_empty(®.progs)); + assert(llist_is_empty(®.procs)); + assert(llist_is_empty(®.pools)); + assert(llist_is_empty(®.names)); + assert(llist_is_empty(®.ipcps)); + assert(llist_is_empty(®.flows)); - bmp_destroy(reg.flow_ids); + bmp_destroy(reg.ids); if (pthread_cond_destroy(®.cond) != 0) log_warn("Failed to destroy condvar."); @@ -652,8 +654,8 @@ int reg_create_flow(struct flow_info * info) pthread_mutex_lock(®.mtx); - info->id = bmp_allocate(reg.flow_ids); - if (!bmp_is_id_valid(reg.flow_ids, info->id)) { + info->id = bmp_allocate(reg.ids); + if (!bmp_is_id_valid(reg.ids, info->id)) { log_err("Failed to allocate flow id."); goto fail_id; } @@ -664,16 +666,14 @@ int reg_create_flow(struct flow_info * info) goto fail_flow; } - list_add(&f->next, __reg_after_flow(info->id)); - - reg.n_flows++; + llist_add_at(&f->next, __reg_after_flow(info->id), ®.flows); pthread_mutex_unlock(®.mtx); return 0; fail_flow: - bmp_release(reg.flow_ids, info->id); + bmp_release(reg.ids, info->id); info->id = 0; fail_id: pthread_mutex_unlock(®.mtx); @@ -692,11 +692,9 @@ int reg_destroy_flow(int flow_id) goto no_flow; } - list_del(&f->next); - - reg.n_flows--; + llist_del(&f->next, ®.flows); - bmp_release(reg.flow_ids, flow_id); + bmp_release(reg.ids, flow_id); pthread_mutex_unlock(®.mtx); @@ -755,11 +753,10 @@ int reg_create_ipcp(const struct ipcp_info * info) entry->pid = info->pid; - list_add_tail(&ipcp->next, __reg_after_ipcp(info)); - list_add(&entry->next, __reg_after_spawned(info->pid)); - - reg.n_ipcps++; - reg.n_spawned++; + llist_add_tail_at(&ipcp->next, __reg_after_ipcp(info), ®.ipcps); + llist_add_at(&entry->next, + __reg_after_spawned(info->pid), + ®.spawned); pthread_mutex_unlock(®.mtx); @@ -849,16 +846,16 @@ int reg_list_ipcps(ipcp_list_msg_t *** ipcps) pthread_mutex_lock(®.mtx); - if (reg.n_ipcps == 0) + if (llist_is_empty(®.ipcps)) goto finish; - *ipcps = malloc(reg.n_ipcps * sizeof(**ipcps)); + *ipcps = malloc(reg.ipcps.len * sizeof(**ipcps)); if (*ipcps == NULL) { log_err("Failed to malloc ipcps."); goto fail_malloc; } - list_for_each(p, ®.ipcps) { + llist_for_each(p, ®.ipcps) { struct reg_ipcp * entry; entry = list_entry(p, struct reg_ipcp, next); if (__get_ipcp_info(&(*ipcps)[i], entry) < 0) @@ -900,9 +897,7 @@ int reg_create_name(const struct name_info * info) goto fail_name; } - list_add(&n->next, __reg_after_name(info->name)); - - reg.n_names++; + llist_add_at(&n->next, __reg_after_name(info->name), ®.names); pthread_mutex_unlock(®.mtx); return 0; @@ -931,9 +926,7 @@ int reg_destroy_name(const char * name) __reg_del_name_from_procs(name); __reg_del_name_from_progs(name); - list_del(&n->next); - - reg.n_names--; + llist_del(&n->next, ®.names); pthread_mutex_unlock(®.mtx); @@ -1004,7 +997,7 @@ int reg_get_name_for_hash(char * buf, pthread_mutex_lock(®.mtx); - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * n = list_entry(p, struct reg_name, next); str_hash(algo, thash, n->info.name); if (memcmp(thash, hash, len) == 0) { @@ -1046,16 +1039,16 @@ int reg_list_names(name_info_msg_t *** names) pthread_mutex_lock(®.mtx); - if (reg.n_names == 0) + if (llist_is_empty(®.names)) goto finish; - *names = malloc(reg.n_names * sizeof(**names)); + *names = malloc(reg.names.len * sizeof(**names)); if (*names == NULL) { log_err("Failed to malloc names."); goto fail_malloc; } - list_for_each(p, ®.names) { + llist_for_each(p, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); (*names)[i] = name_info_s_to_msg(&entry->info); @@ -1090,6 +1083,34 @@ int reg_list_names(name_info_msg_t *** names) return -ENOMEM; } +int reg_prepare_pool(uid_t uid, + gid_t gid) +{ + struct reg_pool * pool; + + if (is_ouroboros_member_uid(uid)) + return 0; + + pthread_mutex_lock(®.mtx); + + pool = __reg_get_pool(uid); + if (pool == NULL) { + pool = reg_pool_create(uid, gid); + if (pool == NULL) { + log_err("Failed to create pool for uid %d.", uid); + pthread_mutex_unlock(®.mtx); + return -1; + } + llist_add(&pool->next, ®.pools); + } + + reg_pool_ref(pool); + + pthread_mutex_unlock(®.mtx); + + return 0; +} + int reg_create_proc(const struct proc_info * info) { struct reg_proc * proc; @@ -1100,20 +1121,18 @@ int reg_create_proc(const struct proc_info * info) if (__reg_get_proc(info->pid) != NULL) { log_err("Process %d already exists.", info->pid); - goto fail_proc; + goto fail; } proc = reg_proc_create(info); if (proc == NULL) { log_err("Failed to create process %d.", info->pid); - goto fail_proc; + goto fail; } __reg_proc_update_names(proc); - list_add(&proc->next, __reg_after_proc(info->pid)); - - reg.n_procs++; + llist_add_at(&proc->next, __reg_after_proc(info->pid), ®.procs); pthread_cond_broadcast(®.cond); @@ -1121,7 +1140,7 @@ int reg_create_proc(const struct proc_info * info) return 0; - fail_proc: + fail: pthread_mutex_unlock(®.mtx); return -1; } @@ -1129,6 +1148,7 @@ int reg_create_proc(const struct proc_info * info) int reg_destroy_proc(pid_t pid) { struct reg_proc * proc; + struct reg_pool * pool = NULL; struct pid_entry * spawn; struct reg_ipcp * ipcp; @@ -1136,24 +1156,27 @@ int reg_destroy_proc(pid_t pid) proc = __reg_get_proc(pid); if (proc != NULL) { - list_del(&proc->next); - reg.n_procs--; + if (!is_ouroboros_member_uid(proc->info.uid)) + pool = __reg_get_pool(proc->info.uid); + llist_del(&proc->next, ®.procs); reg_proc_destroy(proc); __reg_del_proc_from_names(pid); __reg_cancel_flows_for_proc(pid); + if (pool != NULL && reg_pool_unref(pool) == 0) { + llist_del(&pool->next, ®.pools); + reg_pool_destroy(pool); + } } spawn = __reg_get_spawned(pid); if (spawn != NULL) { - list_del(&spawn->next); - reg.n_spawned--; + llist_del(&spawn->next, ®.spawned); free(spawn); } ipcp = __reg_get_ipcp(pid); if (ipcp != NULL) { - list_del(&ipcp->next); - reg.n_ipcps--; + llist_del(&ipcp->next, ®.ipcps); reg_ipcp_destroy(ipcp); } @@ -1175,6 +1198,38 @@ bool reg_has_proc(pid_t pid) return ret; } +bool reg_is_proc_privileged(pid_t pid) +{ + struct reg_proc * proc; + bool ret = false; + + pthread_mutex_lock(®.mtx); + + proc = __reg_get_proc(pid); + if (proc != NULL) + ret = reg_proc_is_privileged(proc); + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +uid_t reg_get_proc_uid(pid_t pid) +{ + struct reg_proc * proc; + uid_t ret = 0; + + pthread_mutex_lock(®.mtx); + + proc = __reg_get_proc(pid); + if (proc != NULL && !is_ouroboros_member_uid(proc->info.uid)) + ret = proc->info.uid; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + void reg_kill_all_proc(int signal) { pthread_mutex_lock(®.mtx); @@ -1216,9 +1271,7 @@ int reg_create_spawned(pid_t pid) entry->pid = pid; - list_add(&entry->next, __reg_after_spawned(pid)); - - reg.n_spawned++; + llist_add_at(&entry->next, __reg_after_spawned(pid), ®.spawned); pthread_mutex_unlock(®.mtx); @@ -1388,9 +1441,7 @@ int reg_create_prog(const struct prog_info * info) goto fail_prog; } - list_add(&prog->next, __reg_after_prog(info->name)); - - reg.n_progs++; + llist_add_at(&prog->next, __reg_after_prog(info->name), ®.progs); exists: pthread_mutex_unlock(®.mtx); @@ -1418,9 +1469,7 @@ int reg_destroy_prog(const char * name) __reg_del_prog_from_names(prog->info.path); - list_del(&prog->next); - - reg.n_progs--; + llist_del(&prog->next, ®.progs); pthread_mutex_unlock(®.mtx); @@ -1736,7 +1785,8 @@ int reg_wait_flow_allocated(struct flow_info * info, } if (flow != NULL) { - reg_flow_get_data(flow, pbuf); + *pbuf = flow->rsp_data; + clrbuf(flow->rsp_data); *info = flow->info; } @@ -1771,8 +1821,8 @@ int reg_respond_alloc(struct flow_info * info, } assert(flow->info.state == FLOW_ALLOC_PENDING); - assert(flow->data.len == 0); - assert(flow->data.data == NULL); + assert(flow->rsp_data.len == 0); + assert(flow->rsp_data.data == NULL); info->n_pid = flow->info.n_pid; info->n_1_pid = flow->info.n_pid; @@ -1784,8 +1834,10 @@ int reg_respond_alloc(struct flow_info * info, flow->response = response; - if (info->state == FLOW_ALLOCATED) - reg_flow_set_data(flow, pbuf); + if (info->state == FLOW_ALLOCATED) { + flow->rsp_data = *pbuf; + clrbuf(*pbuf); + } pthread_cond_broadcast(®.cond); @@ -1816,6 +1868,8 @@ int reg_prepare_flow_accept(struct flow_info * info) ret = reg_flow_update(flow, info); + pthread_cond_broadcast(®.cond); + pthread_mutex_unlock(®.mtx); return ret; @@ -1893,7 +1947,8 @@ int reg_wait_flow_accepted(struct flow_info * info, pthread_cleanup_pop(true); /* __cleanup_wait_accept */ if (flow != NULL) { - reg_flow_get_data(flow, pbuf); + *pbuf = flow->req_data; + clrbuf(flow->req_data); *info = flow->info; } @@ -1953,8 +2008,63 @@ int reg_respond_accept(struct flow_info * info, info->n_pid = flow->info.n_pid; - reg_flow_set_data(flow, pbuf); - clrbuf(pbuf); + flow->req_data = *pbuf; + clrbuf(*pbuf); + + if (reg_flow_update(flow, info) < 0) { + log_err("Failed to create flow structs."); + goto fail_flow; + } + + pthread_cond_broadcast(®.cond); + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_flow: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_prepare_flow_direct(struct flow_info * info, + buffer_t * pbuf, + uid_t alloc_uid) +{ + struct reg_flow * flow; + struct reg_proc * proc; + uid_t accept_uid = 0; + + assert(info != NULL); + assert(info->state == FLOW_ALLOCATED); + assert(info->n_1_pid != 0); + assert(pbuf != NULL); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + if (flow == NULL) { + log_err("Flow not found: %d.", info->id); + goto fail_flow; + } + + assert(flow->info.state == FLOW_ACCEPT_PENDING); + + info->n_pid = flow->info.n_pid; + + proc = __reg_get_proc(info->n_pid); + if (proc != NULL && !is_ouroboros_member_uid(proc->info.uid)) + accept_uid = proc->info.uid; + + if (alloc_uid != accept_uid) { + pthread_mutex_unlock(®.mtx); + return -EPERM; + } + + flow->direct = true; + + flow->req_data = *pbuf; + clrbuf(*pbuf); if (reg_flow_update(flow, info) < 0) { log_err("Failed to create flow structs."); @@ -1972,6 +2082,109 @@ int reg_respond_accept(struct flow_info * info, return -1; } +bool reg_flow_is_direct(int flow_id) +{ + struct reg_flow * flow; + bool ret; + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + + ret = flow != NULL && flow->direct; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +int reg_respond_flow_direct(int flow_id, + buffer_t * pbuf) +{ + struct reg_flow * flow; + + assert(pbuf != NULL); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow == NULL) { + log_err("Flow %d not found.", flow_id); + goto fail; + } + + assert(flow->direct); + assert(flow->rsp_data.data == NULL); + + flow->rsp_data = *pbuf; + clrbuf(*pbuf); + + pthread_cond_broadcast(®.cond); + + pthread_mutex_unlock(®.mtx); + + return 0; + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_wait_flow_direct(int flow_id, + buffer_t * pbuf, + const struct timespec * abstime) +{ + struct reg_flow * flow; + int ret = -1; + + assert(pbuf != NULL); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(flow_id); + if (flow == NULL) + goto fail; + + assert(flow->direct); + + pthread_cleanup_push(__cleanup_mutex_unlock, ®.mtx); + + while (flow != NULL && flow->rsp_data.data == NULL) { + ret = -__timedwait(®.cond, ®.mtx, abstime); + if (ret == -ETIMEDOUT) + break; + flow = __reg_get_flow(flow_id); + } + + if (flow != NULL && flow->rsp_data.data != NULL) { + *pbuf = flow->rsp_data; + clrbuf(flow->rsp_data); + ret = 0; + } + + pthread_cleanup_pop(true); + + return ret; + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +static int direct_flow_dealloc(struct reg_flow * flow, + pid_t pid) +{ + if (!flow->direct) + return -1; + + if (pid == flow->info.n_pid && flow->info.n_pid != -1) + flow->info.n_pid = -1; + else if (pid == flow->info.n_1_pid && flow->info.n_1_pid != -1) + flow->info.n_1_pid = -1; + else + return -1; + + return 0; +} + void reg_dealloc_flow(struct flow_info * info) { struct reg_flow * flow; @@ -1985,13 +2198,32 @@ void reg_dealloc_flow(struct flow_info * info) flow = __reg_get_flow(info->id); assert(flow != NULL); - assert(flow->data.data == NULL); - assert(flow->data.len == 0); + assert(flow->req_data.data == NULL); + assert(flow->req_data.len == 0); + assert(flow->rsp_data.data == NULL); + assert(flow->rsp_data.len == 0); + + info->n_1_pid = flow->info.n_1_pid; + + if (flow->info.state == FLOW_DEALLOC_PENDING) { + if (direct_flow_dealloc(flow, info->n_pid) < 0) { + info->state = FLOW_DEALLOC_PENDING; + pthread_mutex_unlock(®.mtx); + return; + } + flow->info.state = FLOW_DEALLOCATED; + info->state = FLOW_DEALLOCATED; + reg_flow_update(flow, info); + pthread_mutex_unlock(®.mtx); + return; + } + assert(flow->info.state == FLOW_ALLOCATED); flow->info.state = FLOW_DEALLOC_PENDING; info->state = FLOW_DEALLOC_PENDING; - info->n_1_pid = flow->info.n_1_pid; + + direct_flow_dealloc(flow, info->n_pid); memset(flow->name, 0, sizeof(flow->name)); @@ -2013,8 +2245,10 @@ void reg_dealloc_flow_resp(struct flow_info * info) flow = __reg_get_flow(info->id); assert(flow != NULL); - assert(flow->data.data == NULL); - assert(flow->data.len == 0); + assert(flow->req_data.data == NULL); + assert(flow->req_data.len == 0); + assert(flow->rsp_data.data == NULL); + assert(flow->rsp_data.len == 0); assert(flow->info.state == FLOW_DEALLOC_PENDING); flow->info.state = FLOW_DEALLOCATED; diff --git a/src/irmd/reg/reg.h b/src/irmd/reg/reg.h index 7728c80f..6b576471 100644 --- a/src/irmd/reg/reg.h +++ b/src/irmd/reg/reg.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry * @@ -31,6 +31,8 @@ #include <ouroboros/time.h> #include <ouroboros/utils.h> +#include "pool.h" + int reg_init(void); void reg_clear(void); @@ -50,6 +52,13 @@ int reg_destroy_proc(pid_t pid); bool reg_has_proc(pid_t pid); +bool reg_is_proc_privileged(pid_t pid); + +int reg_prepare_pool(uid_t uid, + gid_t gid); + +uid_t reg_get_proc_uid(pid_t pid); + void reg_kill_all_proc(int signal); pid_t reg_get_dead_proc(void); @@ -141,6 +150,19 @@ int reg_wait_flow_accepting(const char * name, int reg_respond_accept(struct flow_info * info, buffer_t * pbuf); +int reg_prepare_flow_direct(struct flow_info * info, + buffer_t * pbuf, + uid_t alloc_uid); + +int reg_respond_flow_direct(int flow_id, + buffer_t * pbuf); + +int reg_wait_flow_direct(int flow_id, + buffer_t * pbuf, + const struct timespec * abstime); + +bool reg_flow_is_direct(int flow_id); + void reg_dealloc_flow(struct flow_info * info); void reg_dealloc_flow_resp(struct flow_info * info); diff --git a/src/irmd/reg/tests/CMakeLists.txt b/src/irmd/reg/tests/CMakeLists.txt index 73df911b..e8521545 100644 --- a/src/irmd/reg/tests/CMakeLists.txt +++ b/src/irmd/reg/tests/CMakeLists.txt @@ -1,7 +1,9 @@ -get_filename_component(tmp ".." ABSOLUTE) -get_filename_component(src_folder "${tmp}" NAME) +get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) -create_test_sourcelist(${src_folder}_tests test_suite.c +compute_test_prefix() + +create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here flow_test.c ipcp_test.c @@ -11,23 +13,21 @@ create_test_sourcelist(${src_folder}_tests test_suite.c reg_test.c ) -add_executable(${src_folder}_test ${${src_folder}_tests}) -target_link_libraries(${src_folder}_test ouroboros-common) +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests}) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(${src_folder}_test -DCONFIG_OUROBOROS_DEBUG) -endif () +target_include_directories(${PARENT_DIR}_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/src/irmd + ${CMAKE_BINARY_DIR}/src/irmd +) -add_dependencies(check ${src_folder}_test) +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common) +ouroboros_target_debug_definitions(${PARENT_DIR}_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() +add_dependencies(build_tests ${PARENT_DIR}_test) -foreach(test ${tests_to_run}) - get_filename_component(test_name ${test} NAME_WE) - add_test(irmd/reg/${test_name} ${C_TEST_PATH}/${src_folder}_test ${test_name}) -endforeach(test) +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) diff --git a/src/irmd/reg/tests/flow_test.c b/src/irmd/reg/tests/flow_test.c index 27fd61b0..7e1c1360 100644 --- a/src/irmd/reg/tests/flow_test.c +++ b/src/irmd/reg/tests/flow_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Flows - Unit Tests * @@ -22,11 +22,7 @@ #include "../flow.c" -#include <ouroboros/test.h> - -#include <string.h> - -#define TEST_DATA "testpiggybackdata" +#include <test/test.h> static int test_reg_flow_create_destroy(void) { @@ -219,56 +215,6 @@ static int test_reg_flow_assert_fails(void) return ret; } -static int test_flow_data(void) -{ - struct reg_flow * f; - - struct flow_info info = { - .id = 1, - .n_pid = 1, - .qs = qos_raw, - .state = FLOW_INIT - }; - - char * data; - buffer_t buf; - buffer_t rcv = {0, NULL}; - - TEST_START(); - - data = strdup(TEST_DATA); - if (data == NULL) { - printf("Failed to strdup data.\n"); - goto fail; - } - - buf.data = (uint8_t *) data; - buf.len = strlen(data); - - f = reg_flow_create(&info); - if (f == NULL) { - printf("Failed to create flow.\n"); - goto fail; - } - - reg_flow_set_data(f, &buf); - - reg_flow_get_data(f, &rcv); - - freebuf(buf); - clrbuf(rcv); - - reg_flow_destroy(f); - - TEST_SUCCESS(); - - return TEST_RC_SUCCESS; - fail: - free(data); - TEST_FAIL(); - return TEST_RC_FAIL; -} - int flow_test(int argc, char ** argv) { @@ -280,7 +226,6 @@ int flow_test(int argc, ret |= test_reg_flow_create_destroy(); ret |= test_reg_flow_update(); ret |= test_reg_flow_assert_fails(); - ret |= test_flow_data(); return ret; } diff --git a/src/irmd/reg/tests/ipcp_test.c b/src/irmd/reg/tests/ipcp_test.c index d7d8e524..5279283e 100644 --- a/src/irmd/reg/tests/ipcp_test.c +++ b/src/irmd/reg/tests/ipcp_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - IPCPs - Unit Tests * @@ -20,7 +20,7 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#include <ouroboros/test.h> +#include <test/test.h> #include "../ipcp.c" diff --git a/src/irmd/reg/tests/name_test.c b/src/irmd/reg/tests/name_test.c index 9071364b..403c8a6c 100644 --- a/src/irmd/reg/tests/name_test.c +++ b/src/irmd/reg/tests/name_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Names - Unit Tests * @@ -23,7 +23,7 @@ #include "../name.c" -#include <ouroboros/test.h> +#include <test/test.h> #define TEST_PID 65534 #define TEST_PROG "/usr/bin/testprog" @@ -88,7 +88,7 @@ static int test_reg_name_add_proc(void) reg_name_del_proc(n, TEST_PID); - if (n->procs.len != 0) { + if (!llist_is_empty(&n->procs)) { printf("Proc not removed from list.\n"); goto fail; } @@ -138,7 +138,7 @@ static int test_reg_name_add_prog(void) reg_name_del_prog(n, TEST_PROG); - if (n->progs.len != 0) { + if (!llist_is_empty(&n->progs)) { printf("Prog not removed from list.\n"); goto fail; } @@ -263,12 +263,12 @@ static int test_reg_name_add_active(enum pol_balance lb) reg_name_del_proc(n, TEST_PID); - if (n->procs.len != 0) { + if (!llist_is_empty(&n->procs)) { printf("Procs list not cleared.\n"); goto fail; } - if (n->active.len != 0) { + if (!llist_is_empty(&n->active)) { printf("Active list not cleared.\n"); goto fail; } diff --git a/src/irmd/reg/tests/proc_test.c b/src/irmd/reg/tests/proc_test.c index df0527fb..a85f4039 100644 --- a/src/irmd/reg/tests/proc_test.c +++ b/src/irmd/reg/tests/proc_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Processes - Unit Tests * @@ -22,18 +22,22 @@ #include "../proc.c" -#include <ouroboros/test.h> +#include <test/test.h> #define TEST_PID 65534 #define TEST_PROG "usr/bin/testprog" +#define TEST_PROC { \ + .pid = TEST_PID, \ + .prog = TEST_PROG, \ + .uid = getuid(), \ + .gid = getgid() \ +} + static int test_reg_proc_create_destroy(void) { struct reg_proc * proc; - struct proc_info info = { - .pid = TEST_PID, - .prog = TEST_PROG - }; + struct proc_info info = TEST_PROC; TEST_START(); @@ -56,10 +60,7 @@ static int test_reg_proc_create_destroy(void) static int test_reg_proc_add_name(void) { struct reg_proc * proc; - struct proc_info info = { - .pid = TEST_PID, - .prog = TEST_PROG - }; + struct proc_info info = TEST_PROC; char * name = "testname"; @@ -76,7 +77,7 @@ static int test_reg_proc_add_name(void) goto fail; } - if (proc->n_names != 1) { + if (proc->names.len != 1) { printf("n_names not updated.\n"); goto fail; } @@ -88,7 +89,7 @@ static int test_reg_proc_add_name(void) reg_proc_del_name(proc, name); - if (proc->n_names != 0) { + if (!llist_is_empty(&proc->names)) { printf("n_names not updated.\n"); goto fail; } diff --git a/src/irmd/reg/tests/prog_test.c b/src/irmd/reg/tests/prog_test.c index c394c222..91264aba 100644 --- a/src/irmd/reg/tests/prog_test.c +++ b/src/irmd/reg/tests/prog_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Programs - Unit Tests * @@ -22,7 +22,7 @@ #include "../prog.c" -#include <ouroboros/test.h> +#include <test/test.h> #define TEST_PROG "usr/bin/testprog" @@ -73,7 +73,7 @@ static int test_reg_prog_add_name(void) goto fail; } - if (prog->n_names != 1) { + if (prog->names.len != 1) { printf("n_names not updated.\n"); goto fail; } @@ -85,7 +85,7 @@ static int test_reg_prog_add_name(void) reg_prog_del_name(prog, name); - if (prog->n_names != 0) { + if (!llist_is_empty(&prog->names)) { printf("n_names not updated.\n"); goto fail; } diff --git a/src/irmd/reg/tests/reg_test.c b/src/irmd/reg/tests/reg_test.c index 4699beab..b426c0dd 100644 --- a/src/irmd/reg/tests/reg_test.c +++ b/src/irmd/reg/tests/reg_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * The IPC Resource Manager - Registry - Unit Tests * @@ -21,9 +21,11 @@ */ +#include "../pool.c" +#undef OUROBOROS_PREFIX #include "../reg.c" -#include <ouroboros/test.h> +#include <test/test.h> #define TEST_PID 3666 #define TEST_N_1_PID 3999 @@ -35,8 +37,14 @@ #define TEST_DATA "testpbufdata" #define TEST_DATA2 "testpbufdata2" #define TEST_LAYER "testlayer" +#define TEST_PROC_INFO { \ + .pid = TEST_PID, \ + .prog = TEST_PROG, \ + .uid = 0, \ + .gid = 0 \ +} #define REG_TEST_FAIL() \ - do { TEST_FAIL(); memset(®, 0, sizeof(reg)); abort();} while(0) + do { TEST_FAIL(); reg_clear(); return TEST_RC_FAIL;} while(0) static int test_reg_init(void) { @@ -81,7 +89,7 @@ static int test_reg_create_flow(void) goto fail; } - if (reg.n_flows != 1) { + if (reg.flows.len != 1) { printf("n_flows was not updated.\n"); goto fail; } @@ -96,8 +104,8 @@ static int test_reg_create_flow(void) goto fail; } - if (reg.n_flows != 0) { - printf("n_flows was not updated.\n"); + if (!llist_is_empty(®.flows)) { + printf("flows.len was not updated.\n"); goto fail; } @@ -155,7 +163,7 @@ static int test_reg_allocate_flow_timeout(void) reg_destroy_flow(info.id); - if (reg.n_flows != 0) { + if (!llist_is_empty(®.flows)) { printf("Flow did not destroy.\n"); goto fail; } @@ -189,6 +197,8 @@ static void * test_flow_respond_alloc(void * o) reg_respond_alloc(info, &pbuf, response); + freebuf(pbuf); + return (void *) 0; fail: return (void *) -1; @@ -208,6 +218,8 @@ static void * test_flow_respond_accept(void * o) reg_respond_accept(info, &pbuf); + freebuf(pbuf); + return (void *) 0; fail: return (void *) -1; @@ -259,6 +271,9 @@ static int test_reg_accept_flow_success(void) if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0) { printf("Flow allocation failed.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); goto fail; } @@ -353,6 +368,9 @@ static int test_reg_accept_flow_success_no_crypt(void) if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0 ) { printf("Flow allocation failed.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); goto fail; } @@ -446,6 +464,9 @@ static int test_reg_allocate_flow_fail(void) if (reg_wait_flow_allocated(&info, &buf, &abstime) == 0 ) { printf("Flow allocation succeeded.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); goto fail; } @@ -468,6 +489,188 @@ static int test_reg_allocate_flow_fail(void) return TEST_RC_FAIL; } +struct direct_alloc_info { + struct flow_info info; + buffer_t rsp; + struct timespec abstime; +}; + +static void * test_flow_alloc_direct(void * o) +{ + struct direct_alloc_info * dai; + buffer_t req; + + dai = (struct direct_alloc_info *) o; + + req.data = (uint8_t *) strdup(TEST_DATA); + if (req.data == NULL) { + printf("Failed to strdup req data.\n"); + goto fail; + } + req.len = strlen(TEST_DATA) + 1; + + if (reg_prepare_flow_direct(&dai->info, &req, 0) < 0) { + printf("Failed to prepare direct flow.\n"); + freebuf(req); + goto fail; + } + + if (reg_wait_flow_direct(dai->info.id, &dai->rsp, &dai->abstime) < 0) { + printf("Failed to wait direct flow.\n"); + goto fail; + } + + return (void *) 0; + fail: + return (void *) -1; +} + +static int test_reg_direct_flow_success(void) +{ + pthread_t thr; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(1); + buffer_t rbuf = BUF_INIT; + buffer_t rsp; + struct direct_alloc_info dai; + void * thr_ret; + + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw + }; + + TEST_START(); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + ts_add(&abstime, &timeo, &abstime); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to add flow.\n"); + goto fail; + } + + if (reg_prepare_flow_accept(&info) < 0) { + printf("Failed to prepare for accept.\n"); + goto fail; + } + + dai.info.id = info.id; + dai.info.n_1_pid = TEST_N_1_PID; + dai.info.mpl = TEST_MPL; + dai.info.qs = qos_data; + dai.info.state = FLOW_ALLOCATED; + dai.rsp.len = 0; + dai.rsp.data = NULL; + dai.abstime = abstime; + + pthread_create(&thr, NULL, test_flow_alloc_direct, &dai); + + if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0) { + printf("Flow accept failed.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); + goto fail; + } + + if (info.state != FLOW_ALLOCATED) { + printf("Flow not in allocated state.\n"); + goto fail; + } + + if (rbuf.data == NULL) { + printf("req_data not received.\n"); + goto fail; + } + + if (strcmp((char *) rbuf.data, TEST_DATA) != 0) { + printf("req_data content mismatch.\n"); + goto fail; + } + + freebuf(rbuf); + + if (!reg_flow_is_direct(info.id)) { + printf("Flow not marked direct.\n"); + goto fail; + } + + rsp.data = (uint8_t *) strdup(TEST_DATA2); + if (rsp.data == NULL) { + printf("Failed to strdup rsp data.\n"); + goto fail; + } + rsp.len = strlen(TEST_DATA2) + 1; + + if (reg_respond_flow_direct(info.id, &rsp) < 0) { + printf("Failed to respond direct.\n"); + freebuf(rsp); + goto fail; + } + + pthread_join(thr, &thr_ret); + + if (thr_ret != (void *) 0) { + printf("Allocator thread failed.\n"); + goto fail; + } + + if (dai.rsp.data == NULL) { + printf("rsp_data not received.\n"); + goto fail; + } + + if (strcmp((char *) dai.rsp.data, TEST_DATA2) != 0) { + printf("rsp_data content mismatch.\n"); + goto fail; + } + + freebuf(dai.rsp); + + reg_dealloc_flow(&info); + + if (info.state != FLOW_DEALLOC_PENDING) { + printf("Flow not in dealloc pending.\n"); + goto fail; + } + + info.n_pid = TEST_PID; + + reg_dealloc_flow(&info); + + if (info.state != FLOW_DEALLOC_PENDING) { + printf("Same endpoint dealloc changed state.\n"); + goto fail; + } + + info.n_pid = TEST_N_1_PID; + + reg_dealloc_flow(&info); + + if (info.state != FLOW_DEALLOCATED) { + printf("Flow not deallocated.\n"); + goto fail; + } + + reg_destroy_flow(info.id); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + static int test_reg_flow(void) { int rc = 0; @@ -476,6 +679,7 @@ static int test_reg_flow(void) { rc |= test_reg_accept_flow_success(); rc |= test_reg_accept_flow_success_no_crypt(); rc |= test_reg_allocate_flow_fail(); + rc |= test_reg_direct_flow_success(); return rc; } @@ -500,7 +704,7 @@ static int test_reg_create_ipcp(void) goto fail; } - if (reg.n_ipcps != 1) { + if (reg.ipcps.len != 1) { printf("n_ipcps was not updated.\n"); goto fail; } @@ -515,8 +719,8 @@ static int test_reg_create_ipcp(void) goto fail; } - if (reg.n_ipcps != 0) { - printf("n_ipcps was not updated.\n"); + if (reg.ipcps.len != 0) { + printf("ipcps.len was not updated.\n"); goto fail; } @@ -744,7 +948,7 @@ static int test_reg_create_name(void) goto fail; } - if (reg.n_names != 1) { + if (reg.names.len != 1) { printf("n_names was not updated.\n"); goto fail; } @@ -759,7 +963,7 @@ static int test_reg_create_name(void) goto fail; } - if (reg.n_names != 0) { + if (!llist_is_empty(®.names)) { printf("n_names was not updated.\n"); goto fail; } @@ -843,10 +1047,7 @@ static int test_reg_name(void) static int test_reg_create_proc(void) { - struct proc_info info = { - .pid = TEST_PID, - .prog = TEST_PROG - }; + struct proc_info info = TEST_PROC_INFO; TEST_START(); @@ -860,7 +1061,7 @@ static int test_reg_create_proc(void) goto fail; } - if (reg.n_procs != 1) { + if (reg.procs.len != 1) { printf("n_procs was not updated.\n"); goto fail; } @@ -875,7 +1076,7 @@ static int test_reg_create_proc(void) goto fail; } - if (reg.n_procs != 0) { + if (!llist_is_empty(®.procs)) { printf("n_procs was not updated.\n"); goto fail; } @@ -913,7 +1114,7 @@ static int test_reg_spawned(void) goto fail; } - if (reg.n_spawned != 1) { + if (reg.spawned.len != 1) { printf("n_spawned was not updated.\n"); goto fail; } @@ -928,7 +1129,7 @@ static int test_reg_spawned(void) goto fail; } - if (reg.n_spawned != 0) { + if (!llist_is_empty(®.spawned)) { printf("n_spawned was not updated.\n"); goto fail; } @@ -961,7 +1162,7 @@ static int test_reg_create_prog(void) goto fail; } - if (reg.n_progs != 1) { + if (reg.progs.len != 1) { printf("n_progs was not updated.\n"); goto fail; } @@ -976,7 +1177,7 @@ static int test_reg_create_prog(void) goto fail; } - if (reg.n_progs != 0) { + if (!llist_is_empty(®.progs)) { printf("n_progs was not updated.\n"); goto fail; } @@ -1002,10 +1203,7 @@ static int test_reg_prog(void) static int test_bind_proc(void) { - struct proc_info pinfo = { - .pid = TEST_PID, - .prog = TEST_PROG - }; + struct proc_info pinfo = TEST_PROC_INFO; struct name_info ninfo = { .name = TEST_NAME, @@ -1158,10 +1356,7 @@ static int test_inherit_prog(void) .name = TEST_PROG }; - struct proc_info procinfo = { - .pid = TEST_PID, - .prog = TEST_PROG - }; + struct proc_info procinfo = TEST_PROC_INFO; char * exec[] = { TEST_PROG, NULL}; @@ -1296,13 +1491,10 @@ static int test_wait_accepting_fail_name(void) static void * test_call_flow_accept(void * o) { struct timespec abstime; - struct timespec timeo = TIMESPEC_INIT_MS(1); + struct timespec timeo = TIMESPEC_INIT_MS(10); buffer_t pbuf = BUF_INIT; - struct proc_info pinfo = { - .pid = TEST_PID, - .prog = TEST_PROG - }; + struct proc_info pinfo = TEST_PROC_INFO; struct flow_info info = { .n_pid = pinfo.pid, @@ -1326,16 +1518,21 @@ static void * test_call_flow_accept(void * o) info.state = FLOW_ACCEPT_PENDING; + reg_prepare_flow_accept(&info); + clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - reg_prepare_flow_accept(&info); - if (reg_wait_flow_accepted(&info, &pbuf, &abstime) != -ETIMEDOUT) { printf("Wait allocated did not timeout.\n"); goto fail; } + if (reg_unbind_proc((char *) o, pinfo.pid) < 0) { + printf("Failed to unbind proc.\n"); + goto fail; + } + reg_destroy_flow(info.id); reg_destroy_proc(pinfo.pid); @@ -1347,7 +1544,7 @@ static void * test_call_flow_accept(void * o) static int test_wait_accepting_success(void) { struct timespec abstime; - struct timespec timeo = TIMESPEC_INIT_S(1); + struct timespec timeo = TIMESPEC_INIT_S(10); pthread_t thr; int flow_id; struct name_info ninfo = { @@ -1375,7 +1572,10 @@ static int test_wait_accepting_success(void) flow_id = reg_wait_flow_accepting(ninfo.name, &abstime); if (flow_id < 0) { - printf("Wait accept did not return a flow id: %d.", flow_id); + printf("Wait accept did not return a flow id: %d.\n", flow_id); + pthread_join(thr, NULL); + reg_destroy_name(TEST_NAME); + reg_fini(); goto fail; } @@ -1461,7 +1661,7 @@ static void * test_ipcp_respond(void * o) static int test_wait_ipcp_boot_fail(void) { struct timespec abstime; - struct timespec timeo = TIMESPEC_INIT_S(1); + struct timespec timeo = TIMESPEC_INIT_S(10); pthread_t thr; struct ipcp_info info = { .name = TEST_IPCP, @@ -1471,7 +1671,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(); @@ -1495,6 +1695,9 @@ static int test_wait_ipcp_boot_fail(void) if (reg_wait_ipcp_boot(&info, &abstime) == 0) { printf("IPCP boot reported success.\n"); + pthread_join(thr, NULL); + reg_destroy_proc(info.pid); + reg_fini(); goto fail; } @@ -1505,8 +1708,8 @@ static int test_wait_ipcp_boot_fail(void) goto fail; } - if (reg.n_ipcps != 0) { - printf("n_ipcps was not updated.\n"); + if (!llist_is_empty(®.ipcps)) { + printf("ipcps.len was not updated.\n"); goto fail; } @@ -1524,7 +1727,7 @@ static int test_wait_ipcp_boot_success(void) { pthread_t thr; struct timespec abstime; - struct timespec timeo = TIMESPEC_INIT_S(1); + struct timespec timeo = TIMESPEC_INIT_S(10); struct ipcp_info info = { .name = TEST_IPCP, .pid = TEST_PID, @@ -1557,6 +1760,9 @@ static int test_wait_ipcp_boot_success(void) if (reg_wait_ipcp_boot(&info, &abstime) < 0) { printf("IPCP boot failed.\n"); + pthread_join(thr, NULL); + reg_destroy_proc(info.pid); + reg_fini(); goto fail; } @@ -1564,6 +1770,8 @@ static int test_wait_ipcp_boot_success(void) if (info.state != IPCP_OPERATIONAL) { printf("IPCP boot succeeded in non-operational state.\n"); + reg_destroy_proc(info.pid); + reg_fini(); goto fail; } @@ -1636,12 +1844,9 @@ static void * test_proc(void * o) static int test_wait_proc_success(void) { struct timespec abstime; - struct timespec timeo = TIMESPEC_INIT_S(1); + struct timespec timeo = TIMESPEC_INIT_S(10); pthread_t thr; - struct proc_info info = { - .pid = TEST_PID, - .prog = TEST_PROG - }; + struct proc_info info = TEST_PROC_INFO; TEST_START(); @@ -1657,6 +1862,9 @@ static int test_wait_proc_success(void) if (reg_wait_proc(info.pid, &abstime) < 0) { printf("Waiting for proc failed.\n"); + pthread_join(thr, NULL); + reg_destroy_proc(info.pid); + reg_fini(); goto fail; } diff --git a/src/irmd/tests/CMakeLists.txt b/src/irmd/tests/CMakeLists.txt deleted file mode 100644 index 4b62261c..00000000 --- a/src/irmd/tests/CMakeLists.txt +++ /dev/null @@ -1,27 +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-common) - -add_dependencies(check ${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} ${C_TEST_PATH}/${src_folder}_test ${test_name}) -endforeach(test) - -set_property(TEST irmd/oap_test PROPERTY SKIP_RETURN_CODE 1) diff --git a/src/irmd/tests/oap_test.c b/src/irmd/tests/oap_test.c deleted file mode 100644 index 4e7fb2d1..00000000 --- a/src/irmd/tests/oap_test.c +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2024 - * - * Unit tests of Ouroboros flow allocation protocol - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * 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 "oap.c" - -#include <ouroboros/random.h> -#include <ouroboros/test.h> - -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) -{ - 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; - - TEST_START(); - - random_buffer(buf, OAP_ID_SIZE); - id.data = buf; - id.len = OAP_ID_SIZE; - - clock_gettime(CLOCK_REALTIME, &now); - stamp = TS_TO_UINT64(now); - - if (oap_hdr_init(id, pkp, pubcrt, ephkey, data, &oap_hdr) < 0) { - printf("Failed to init OAP request header.\n"); - goto fail_req_hdr; - } - - 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 (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 (memcmp(oap_hdr.id.data, id.data, OAP_ID_SIZE) != 0) { - printf("OAP request header ID mismatch.\n"); - goto fail_req_hdr_chk; - } - - if (oap_hdr.timestamp < stamp) { - printf("OAP request header timestamp is too old.\n"); - goto fail_req_hdr_chk; - } - - if (oap_hdr.timestamp > stamp + 1 * BILLION) { - printf("OAP request header timestamp is too new.\n"); - goto fail_req_hdr_chk; - } - - oap_hdr_fini(&oap_hdr); - - TEST_SUCCESS(); - - return TEST_RC_SUCCESS; - - fail_req_hdr_chk: - oap_hdr_fini(&oap_hdr); - fail_req_hdr: - TEST_FAIL(); - return TEST_RC_FAIL; -} - -static int test_oap_hdr_init_fini_data(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_START(); - - random_buffer(buf, OAP_ID_SIZE); - id.data = buf; - id.len = OAP_ID_SIZE; - - data.len = 100; - data.data = malloc(data.len); - if (data.data == NULL) { - printf("Failed to allocate data buffer.\n"); - goto fail_data; - } - - random_buffer(data.data, data.len); - - 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_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; - } - - freebuf(data); - oap_hdr_fini(&oap_hdr); - - TEST_SUCCESS(); - - return TEST_RC_SUCCESS; - - fail_req_hdr_sz: - oap_hdr_fini(&oap_hdr); - fail_req_hdr: - freebuf(data); - fail_data: - TEST_FAIL(); - return TEST_RC_FAIL; -} - -static int test_oap_hdr_init_fini_signed(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; - - TEST_START(); - - random_buffer(buf, OAP_ID_SIZE); - id.data = buf; - id.len = OAP_ID_SIZE; - - if (crypt_load_privkey_str(pkp_str, &pkp) < 0) { - printf("Failed to load private key.\n"); - goto fail_pkp; - } - - if (crypt_load_crt_str(crt_str, &pubcrt) < 0) { - printf("Failed to load public certificate.\n"); - goto fail_pubcrt; - } - - 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_hdr.crt.len == 0) { - printf("OAP request header has no public certificate.\n"); - goto fail_req_hdr; - } - - if (oap_hdr.sig.len == 0) { - printf("OAP request header no signature.\n"); - goto fail_req_hdr; - } - - if (crypt_load_crt_der(oap_hdr.crt, &pubcrt2) < 0) { - printf("Failed to load public certificate from DER.\n"); - goto fail_crt_der; - } - - if (crypt_get_pubkey_crt(pubcrt2, &pk) < 0) { - printf("Failed to get public key from certificate.\n"); - goto fail_crt_pk; - } - - sign = oap_hdr.hdr; - sign.len -= (oap_hdr.sig.len + sizeof(uint16_t)); - - if (auth_verify_sig(pk, sign, oap_hdr.sig) < 0) { - printf("Failed to verify OAP request header signature.\n"); - goto fail_check_sig; - } - - oap_hdr_fini(&oap_hdr); - - crypt_free_crt(pubcrt2); - crypt_free_crt(pubcrt); - crypt_free_key(pk); - crypt_free_key(pkp); - - 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: - TEST_FAIL(); - return TEST_RC_FAIL; -} - -int oap_test(int argc, - char **argv) -{ - int ret = 0; - - (void) argc; - (void) argv; - - ret |= test_oap_hdr_init_fini(); - ret |= test_oap_hdr_init_fini_data(); -#ifdef HAVE_OPENSSL - ret |= test_oap_hdr_init_fini_signed(); -#else - (void) test_oap_hdr_init_fini_signed; - - ret = TEST_RC_SKIP; -#endif - return ret; -} |
