diff options
Diffstat (limited to 'src/irmd')
42 files changed, 6331 insertions, 1024 deletions
diff --git a/src/irmd/CMakeLists.txt b/src/irmd/CMakeLists.txt index c9c2e553..d65635af 100644 --- a/src/irmd/CMakeLists.txt +++ b/src/irmd/CMakeLists.txt @@ -1,95 +1,65 @@ -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) - -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_DIR /etc/ouroboros/ CACHE STRING - "Configuration directory") - 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) - mark_as_advanced(LIBTOML_LIBRARIES) - else () - message(STATUS "Configuration file support disabled by user") - unset(OUROBOROS_CONFIG_FILE CACHE) - unset(OUROBOROS_CONFIG_DIR 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(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 20000 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") +# 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/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}) +target_include_directories(irmd PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include) -target_link_libraries (irmd LINK_PUBLIC ouroboros-common - ${LIBTOML_LIBRARIES}) +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}) -# Enable once irmd has tests -#add_subdirectory(tests) -add_subdirectory(reg) +if(BUILD_TESTS) + add_subdirectory(oap/tests) + add_subdirectory(reg/tests) +endif() diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in index fa1156b9..e1072193 100644 --- a/src/irmd/config.h.in +++ b/src/irmd/config.h.in @@ -21,7 +21,8 @@ */ -#define IPCP_UDP_EXEC "@IPCP_UDP_TARGET@" +#define IPCP_UDP4_EXEC "@IPCP_UDP4_TARGET@" +#define IPCP_UDP6_EXEC "@IPCP_UDP6_TARGET@" #define IPCP_ETH_LLC_EXEC "@IPCP_ETH_LLC_TARGET@" #define IPCP_ETH_DIX_EXEC "@IPCP_ETH_DIX_TARGET@" #define IPCP_UNICAST_EXEC "@IPCP_UNICAST_TARGET@" @@ -40,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@ @@ -50,6 +53,7 @@ #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 @@ -62,36 +66,37 @@ #define OUROBOROS_CONFIG_FILE "@OUROBOROS_CONFIG_FILE@" #endif +#define OUROBOROS_SECURITY_DIR "@OUROBOROS_SECURITY_DIR@" +#define OUROBOROS_CA_CRT_DIR "@OUROBOROS_CA_CRT_DIR@" +#define OUROBOROS_SRV_CRT_DIR "@OUROBOROS_SRV_CRT_DIR@" +#define OUROBOROS_CLI_CRT_DIR "@OUROBOROS_CLI_CRT_DIR@" +#define OUROBOROS_CHAIN_DIR "@OUROBOROS_UNTRUSTED_DIR@" + #define IRMD_PKILL_TIMEOUT @IRMD_PKILL_TIMEOUT@ #cmakedefine IRMD_KILL_ALL_PROCESSES #cmakedefine HAVE_LIBGCRYPT +#cmakedefine HAVE_OPENSSL +#ifdef HAVE_OPENSSL +#cmakedefine HAVE_OPENSSL_PQC +#endif +#define IRMD_SECMEM_MAX @IRMD_SECMEM_MAX@ +#ifdef CONFIG_OUROBOROS_DEBUG +#cmakedefine DEBUG_PROTO_OAP +#endif + +#define _B "[38;5;4m" +#define _G "[38;5;8m" +#define RST "[0m" -#define O7S_ASCII_ART \ -"[0m\n" \ -" [38;5;4m▄▄█████▄▄▄[38;5;7m\n" \ -" [38;5;4m▄█▀▀[38;5;7m [38;5;4m▀▀███▄[38;5;7m " \ -"[38;5;8m█[38;5;7m\n" \ -" [38;5;4m██[38;5;7m [38;5;4m▄▄▄[38;5;7m [38;5;4m▄███▄[38;5;7m " \ -"[38;5;8m▄[38;5;7m [38;5;8m▄[38;5;7m [38;5;8m▄[38;5;7m [38;5;8m▄▄" \ -"[38;5;7m [38;5;8m▄▄[38;5;7m [38;5;8m█[38;5;7m [38;5;8m▄▄[38;5;7m " \ -" [38;5;8m▄▄[38;5;7m [38;5;8m▄[38;5;7m [38;5;8m▄▄[38;5;7m [38;5;8m" \ -"▄▄[38;5;7m [38;5;8m▄▄[38;5;7m\n" \ -" [38;5;4m██[38;5;7m [38;5;4m█[38;5;7m [38;5;4m█[38;5;7m " \ -"[38;5;4m█████[38;5;7m [38;5;8m█[38;5;7m [38;5;8m█[38;5;7m " \ -"[38;5;8m█▀[38;5;7m [38;5;8m▀[38;5;7m [38;5;8m█[38;5;7m [38;5;8m█" \ -"[38;5;7m [38;5;8m█▀[38;5;7m [38;5;8m█[38;5;7m [38;5;8m█[38;5;7m " \ -"[38;5;8m█[38;5;7m [38;5;8m█▀[38;5;7m [38;5;8m▀[38;5;7m [38;5;8m█" \ -"[38;5;7m [38;5;8m█[38;5;7m [38;5;8m▀▄[38;5;7m [38;5;8m▀[38;5;7m\n" \ -" [38;5;4m██[38;5;7m [38;5;4m▀▄▄▄▀[38;5;7m [38;5;4m▀█▀[38;5;7m " \ -"[38;5;8m█[38;5;7m [38;5;8m█[38;5;7m [38;5;8m█[38;5;7m [38;5;8m" \ -"█[38;5;7m [38;5;8m█[38;5;7m [38;5;8m█▄[38;5;7m [38;5;8m█[38;5;7m " \ -"[38;5;8m█[38;5;7m [38;5;8m█[38;5;7m [38;5;8m█[38;5;7m [38;5;8m█" \ -"[38;5;7m [38;5;8m█[38;5;7m [38;5;8m▄[38;5;7m [38;5;8m▀▄[38;5;7m\n" \ -" [38;5;4m█▄[38;5;7m [38;5;4m█[38;5;7m [38;5;8m▀▀▀" \ -"[38;5;7m [38;5;8m▀[38;5;7m [38;5;8m▀[38;5;7m [38;5;8m▀▀[38;5;7m" \ -" [38;5;8m▀[38;5;7m [38;5;8m▀▀[38;5;7m [38;5;8m▀▀[38;5;7m " \ -"[38;5;8m▀[38;5;7m [38;5;8m▀▀[38;5;7m [38;5;8m▀▀[38;5;7m\n" \ -" [38;5;4m▀█▄▄▄▄▄▄▄▄▀[38;5;7m\n" \ -" [38;5;4m▀▀▀▀▀▀[38;5;7m\n" \ -"\n" +#define O7S_ASCII_ART \ +RST "\n" \ +_B " ▄▄█████▄▄▄ \n" \ +_B " ▄█▀▀ ▀▀███▄ " _G " █ \n" \ +_B " ██ ▄▄▄ ▄███▄ " _G "▄ ▄ ▄ ▄ ▄▄ █▄▄ ▄▄ ▄ ▄ ▄▄ ▄▄ \n" \ +_B " ██ █ █ █████ " _G "█ █ █▀ ▀ █ █ █ █ █ █ █▀ ▀ █ █ ▀▄ ▀\n" \ +_B " ██ ▀▄▄▄▀ ▀█▀ " _G "█ █ █ █ █ █ █ █ █ █ █ █ ▄ ▀▄\n" \ +_B " █▄ █ " _G " ▀▀ ▀ ▀ ▀▀ ▀▀▀ ▀▀ ▀ ▀▀ ▀▀ \n" \ +_B " ▀█▄▄▄▄▄▄▄▄▀ \n" \ +_B " ▀▀▀▀▀▀ \n" \ +RST "\n" diff --git a/src/irmd/configfile.c b/src/irmd/configfile.c index 688c4ade..ce9fc8fc 100644 --- a/src/irmd/configfile.c +++ b/src/irmd/configfile.c @@ -47,8 +47,12 @@ #include <string.h> #include <toml.h> #include <arpa/inet.h> +#ifdef __FreeBSD__ +#include <sys/socket.h> +#endif #define ERRBUFSZ 200 +#define DATUMSZ 256 static int toml_hash(toml_table_t * table, struct layer_info * info) @@ -157,14 +161,66 @@ static int toml_eth_dix(toml_table_t * table, return 0; } -static int toml_udp(toml_table_t * table, - struct ipcp_config * conf) +static int toml_udp4(toml_table_t * table, + struct ipcp_config * conf) +{ + struct udp4_config * udp4; + toml_datum_t ip; + toml_datum_t port; + toml_datum_t dns; + + *conf = udp4_default_conf; + udp4 = &conf->udp4; + + ip = toml_string_in(table, "ip"); + if (!ip.ok) { + log_err("No IP address specified!"); + goto fail_ip; + } + + if (inet_pton (AF_INET, ip.u.s, &udp4->ip_addr.s_addr) != 1) { + log_err("Failed to parse IPv4 address %s.", ip.u.s); + goto fail_addr; + } + + port = toml_int_in(table, "port"); + if (port.ok) + udp4->port = port.u.i; + + dns = toml_string_in(table, "dns"); + if (dns.ok) { + if (inet_pton(AF_INET, dns.u.s, &udp4->dns_addr.s_addr) < 0) { + log_err("Failed to parse DNS address %s.", ip.u.s); + goto fail_dns; + } + + free(dns.u.s); + } + + free(ip.u.s); + + return 0; + + fail_dns: + free(dns.u.s); + fail_addr: + free(ip.u.s); + fail_ip: + return -1; +} + +static int toml_udp6(toml_table_t * table, + struct ipcp_config * conf) { + struct in6_addr ip6; + struct in6_addr dns6; toml_datum_t ip; toml_datum_t port; toml_datum_t dns; - *conf = udp_default_conf; + *conf = udp6_default_conf; + ip6 = conf->udp6.ip_addr; + dns6 = conf->udp6.dns_addr; ip = toml_string_in(table, "ip"); if (!ip.ok) { @@ -172,18 +228,18 @@ static int toml_udp(toml_table_t * table, goto fail_ip; } - if (inet_pton (AF_INET, ip.u.s, &conf->udp.ip_addr) != 1) { + if (inet_pton (AF_INET6, ip.u.s, &ip6.s6_addr) != 1) { log_err("Failed to parse IPv4 address %s.", ip.u.s); goto fail_addr; } port = toml_int_in(table, "port"); if (port.ok) - conf->udp.port = port.u.i; + conf->udp6.port = port.u.i; dns = toml_string_in(table, "dns"); if (dns.ok) { - if (inet_pton(AF_INET, dns.u.s, &conf->udp.dns_addr) < 0) { + if (inet_pton(AF_INET6, dns.u.s, &dns6.s6_addr) < 0) { log_err("Failed to parse DNS address %s.", ip.u.s); goto fail_dns; } @@ -214,26 +270,160 @@ static int toml_broadcast(toml_table_t * table, return 0; } +#define BETWEEN(a, b, c) ((a) >= (b) && (a) <= (c)) +#define DHT(conf, x) (conf)->dht.params.x +static int toml_dir(toml_table_t * table, + struct dir_config * conf) +{ + toml_datum_t dir; + toml_datum_t alpha; + toml_datum_t t_expire; + toml_datum_t t_refresh; + toml_datum_t t_replicate; + toml_datum_t k; + + dir = toml_string_in(table, "directory"); + if (dir.ok) { + log_dbg("Found directory type: %s", dir.u.s); + if (strlen(dir.u.s) > DATUMSZ) { + log_err("Directory name too long: %s", dir.u.s); + free(dir.u.s); + return -1; + } + if (strcmp(dir.u.s, "DHT") == 0) + conf->pol = DIR_DHT; + else if (strcmp(dir.u.s, "dht") == 0) + conf->pol = DIR_DHT; + else { + log_err("Unknown directory type: %s", dir.u.s); + free(dir.u.s); + return -EINVAL; + } + free(dir.u.s); + } + + switch(conf->pol) { + case DIR_DHT: + log_info("Using DHT directory policy."); + alpha = toml_int_in(table, "dht_alpha"); + if (alpha.ok) { + if (!BETWEEN(alpha.u.i, + DHT_ALPHA_MIN, DHT_ALPHA_MAX)) { + log_err("Invalid alpha value: %ld", + (long) alpha.u.i); + return -EINVAL; + } + DHT(conf, alpha) = alpha.u.i; + } + t_expire = toml_int_in(table, "dht_t_expire"); + if (t_expire.ok) { + if (!BETWEEN(t_expire.u.i, + DHT_T_EXPIRE_MIN, DHT_T_EXPIRE_MAX)) { + log_err("Invalid expire time: %ld", + (long) t_expire.u.i); + return -EINVAL; + } + DHT(conf, t_expire) = t_expire.u.i; + } + t_refresh = toml_int_in(table, "dht_t_refresh"); + if (t_refresh.ok) { + if (!BETWEEN(t_refresh.u.i, + DHT_T_REFRESH_MIN, DHT_T_REFRESH_MAX)) { + log_err("Invalid refresh time: %ld", + (long) t_refresh.u.i); + return -EINVAL; + } + DHT(conf, t_refresh) = t_refresh.u.i; + } + t_replicate = toml_int_in(table, "dht_t_replicate"); + if (t_replicate.ok) { + if (!BETWEEN(t_replicate.u.i, + DHT_T_REPLICATE_MIN, DHT_T_REPLICATE_MAX)) { + log_err("Invalid replication time: %ld", + (long) t_replicate.u.i); + return -EINVAL; + } + DHT(conf, t_replicate) = t_replicate.u.i; + } + k = toml_int_in(table, "dht_k"); + if (k.ok) { + if (!BETWEEN(k.u.i, DHT_K_MIN, DHT_K_MAX)) { + log_err("Invalid replication factor: %ld", + (long) k.u.i); + return -EINVAL; + } + DHT(conf, k) = k.u.i; + } + break; + default: + assert(false); + break; + } + + return 0; +} + static int toml_routing(toml_table_t * table, struct dt_config * conf) { toml_datum_t routing; + toml_datum_t t_recalc; + toml_datum_t t_update; + toml_datum_t t_timeo; routing = toml_string_in(table, "routing"); if (routing.ok) { - if (strcmp(routing.u.s, "link-state") == 0) - conf->routing_type = ROUTING_LINK_STATE; - else if (strcmp(routing.u.s, "lfa") == 0) - conf->routing_type = ROUTING_LINK_STATE_LFA; - else if (strcmp(routing.u.s, "ecmp") == 0) - conf->routing_type = ROUTING_LINK_STATE_ECMP; - else - conf->routing_type = ROUTING_INVALID; + if (strcmp(routing.u.s, "link-state") == 0) { + conf->routing.pol = ROUTING_LINK_STATE; + conf->routing.ls.pol = LS_SIMPLE; + } else if (strcmp(routing.u.s, "lfa") == 0) { + conf->routing.pol = ROUTING_LINK_STATE; + conf->routing.ls.pol = LS_LFA; + } else if (strcmp(routing.u.s, "ecmp") == 0) { + conf->routing.pol = ROUTING_LINK_STATE; + conf->routing.ls.pol = LS_ECMP; + } else { + conf->routing.pol = ROUTING_INVALID; + return -EINVAL; + } free(routing.u.s); } - if (conf->routing_type == ROUTING_INVALID) - return -1; + switch (conf->routing.pol) { + case ROUTING_LINK_STATE: + log_info("Using Link State routing policy."); + t_recalc = toml_int_in(table, "ls_t_recalc"); + if (t_recalc.ok) { + if (t_recalc.u.i < 1) { + log_err("Invalid ls_t_recalc value: %ld", + (long) t_recalc.u.i); + return -EINVAL; + } + conf->routing.ls.t_recalc = t_recalc.u.i; + } + t_update = toml_int_in(table, "ls_t_update"); + if (t_update.ok) { + if (t_update.u.i < 1) { + log_err("Invalid ls_t_update value: %ld", + (long) t_update.u.i); + return -EINVAL; + } + conf->routing.ls.t_update = t_update.u.i; + } + t_timeo = toml_int_in(table, "ls_t_timeo"); + if (t_timeo.ok) { + if (t_timeo.u.i < 1) { + log_err("Invalid ls_t_timeo value: %ld", + (long) t_timeo.u.i); + return -EINVAL; + } + conf->routing.ls.t_timeo = t_timeo.u.i; + } + break; + default: + log_err("Invalid routing policy: %d", conf->routing.pol); + return -EINVAL; + } return 0; } @@ -311,12 +501,12 @@ static int toml_dt(toml_table_t * table, static int toml_unicast(toml_table_t * table, struct ipcp_config * conf) { - - *conf = uni_default_conf; - if (toml_hash(table, &conf->layer_info) < 0) + if (toml_dir(table, &conf->unicast.dir) < 0) { + log_err("Invalid directory configuration."); return -1; + } if (toml_dt(table, &conf->unicast.dt) < 0) { log_err("Invalid DT configuration."); @@ -333,6 +523,7 @@ static int toml_unicast(toml_table_t * table, return -1; } + return 0; } @@ -504,8 +695,11 @@ static int toml_ipcp(toml_table_t * table, case IPCP_ETH_LLC: ret = toml_eth_llc(table, conf); break; - case IPCP_UDP: - ret = toml_udp(table, conf); + case IPCP_UDP4: + ret = toml_udp4(table, conf); + break; + case IPCP_UDP6: + ret = toml_udp6(table, conf); break; case IPCP_BROADCAST: ret = toml_broadcast(table, conf); @@ -563,7 +757,7 @@ static int toml_ipcp_list(toml_table_t * table, } info.type = type; - strcpy(info.name,key); + strcpy(info.name, key); conf.type = type; ret = toml_ipcp(toml_table_in(table, key), &info, &conf); @@ -691,18 +885,63 @@ static int toml_prog_list(toml_array_t * progs, return ret; } +static int cp_chk_path(char * buf, + char * path) +{ + char * rp; + + assert(path != NULL); + + rp = realpath(path, NULL); + if (rp == NULL) { + log_err("Failed to check path %s: %s.", path, strerror(errno)); + goto fail_rp; + } + + if (strlen(rp) > NAME_PATH_SIZE) { + log_err("File path too long: %s.", rp); + goto fail_len; + } + + strcpy(buf, rp); + free(rp); + free(path); + + return 0; + + fail_len: + free(rp); + fail_rp: + free(path); + return -1; +} + static int toml_name(toml_table_t * table, const char * name) { - toml_array_t * progs; - toml_array_t * args; - toml_datum_t lb; + toml_array_t * progs; + toml_array_t * args; + toml_datum_t lb; + toml_datum_t senc; + toml_datum_t scrt; + toml_datum_t skey; + toml_datum_t cenc; + toml_datum_t ccrt; + toml_datum_t ckey; + struct name_info info = { .pol_lb = LB_SPILL }; log_dbg("Found service name %s in configuration file.", name); + if (strlen(name) > NAME_SIZE) { + log_err("Name too long: %s", name); + return -1; + } + + strcpy(info.name, name); + lb = toml_string_in(table, "lb"); if (lb.ok) { if (strcmp(lb.u.s, "spill") == 0) @@ -718,8 +957,29 @@ static int toml_name(toml_table_t * table, log_err("Invalid load-balancing policy for %s.", name); return -1; } + senc = toml_string_in(table, "server_enc_file"); + if (senc.ok && cp_chk_path(info.s.enc, senc.u.s) < 0) + return -1; - strcpy(info.name, name); + scrt = toml_string_in(table, "server_crt_file"); + if (scrt.ok && cp_chk_path(info.s.crt, scrt.u.s) < 0) + return -1; + + skey = toml_string_in(table, "server_key_file"); + if (skey.ok && cp_chk_path(info.s.key, skey.u.s) < 0) + return -1; + + cenc = toml_string_in(table, "client_enc_file"); + if (cenc.ok && cp_chk_path(info.c.enc, cenc.u.s) < 0) + return -1; + + ccrt = toml_string_in(table, "client_crt_file"); + if (ccrt.ok && cp_chk_path(info.c.crt, ccrt.u.s) < 0) + return -1; + + ckey = toml_string_in(table, "client_key_file"); + if (ckey.ok && cp_chk_path(info.c.key, ckey.u.s) < 0) + return -1; if (name_create(&info) < 0) { log_err("Failed to create name %s.", name); @@ -762,23 +1022,24 @@ static int toml_toplevel(toml_table_t * table, toml_table_t * subtable; subtable = toml_table_in(table, key); - - if (strcmp(key, "local") == 0) + if (strcmp(key, "name") == 0) + return toml_name_list(subtable); + else if (strcmp(key, "local") == 0) return toml_ipcp_list(subtable, IPCP_LOCAL); else if (strcmp(key, "eth-dix") == 0) return toml_ipcp_list(subtable, IPCP_ETH_DIX); else if (strcmp(key, "eth-llc") == 0) return toml_ipcp_list(subtable, IPCP_ETH_LLC); - else if (strcmp(key, "udp") == 0) - return toml_ipcp_list(subtable, IPCP_UDP); + else if (strcmp(key, "udp4") == 0) + return toml_ipcp_list(subtable, IPCP_UDP4); + else if (strcmp(key, "udp6") == 0) + return toml_ipcp_list(subtable, IPCP_UDP6); else if (strcmp(key, "broadcast") == 0) return toml_ipcp_list(subtable, IPCP_BROADCAST); else if (strcmp(key, "unicast") == 0) return toml_ipcp_list(subtable, IPCP_UNICAST); - else if (strcmp(key, "name") == 0) - return toml_name_list(subtable); - - log_err("Unkown toplevel key: %s.", key); + else + log_err("Unkown toplevel key: %s.", key); return -1; } @@ -838,7 +1099,8 @@ int irm_configure(const char * path) rp = realpath(path, NULL); if (rp == NULL) { - log_err("Failed to resolve path for %s", path); + log_err("Failed to check path for %s: %s.", + path, strerror(errno)); goto fail_resolve; } diff --git a/src/irmd/ipcp.c b/src/irmd/ipcp.c index 5a9a79d3..d261fc57 100644 --- a/src/irmd/ipcp.c +++ b/src/irmd/ipcp.c @@ -83,28 +83,30 @@ ipcp_msg_t * send_recv_ipcp_msg(pid_t pid, { int sockfd; uint8_t buf[SOCK_BUF_SIZE]; - char * sock_path; + char * spath; ssize_t len; - ipcp_msg_t * recv_msg; struct timeval tv; struct timespec tic; struct timespec toc; - bool dealloc = false; + bool may_fail = false; if (kill(pid, 0) < 0) return NULL; - sock_path = ipcp_sock_path(pid); - if (sock_path == NULL) + spath = sock_path(pid, IPCP_SOCK_PATH_PREFIX); + if (spath == NULL) { + log_err("Failed to get IPCP socket path for pid %d.", pid); return NULL; + } - sockfd = client_socket_open(sock_path); + sockfd = client_socket_open(spath); if (sockfd < 0) { - free(sock_path); + log_err("Failed to open client socket at %s.", spath); + free(spath); return NULL; } - free(sock_path); + free(spath); len = ipcp_msg__get_packed_size(msg); if (len == 0 || len >= SOCK_BUF_SIZE) { @@ -127,6 +129,7 @@ ipcp_msg_t * send_recv_ipcp_msg(pid_t pid, tv.tv_usec = (REG_TIMEOUT % 1000) * 1000; break; case IPCP_MSG_CODE__IPCP_QUERY: + may_fail = true; /* name not always in Layer */ tv.tv_sec = QUERY_TIMEOUT / 1000; tv.tv_usec = (QUERY_TIMEOUT % 1000) * 1000; break; @@ -139,7 +142,7 @@ ipcp_msg_t * send_recv_ipcp_msg(pid_t pid, tv.tv_usec = (FLOW_ALLOC_TIMEOUT % 1000) * 1000; break; case IPCP_MSG_CODE__IPCP_FLOW_DEALLOC: - dealloc = true; + may_fail = true; tv.tv_sec = 0; /* FIX DEALLOC: don't wait for dealloc */ tv.tv_usec = 500; break; @@ -167,17 +170,15 @@ ipcp_msg_t * send_recv_ipcp_msg(pid_t pid, pthread_cleanup_pop(true); /* close socket */ if (len > 0) - recv_msg = ipcp_msg__unpack(NULL, len, buf); - else { - if (errno == EAGAIN && !dealloc) { - int diff = ts_diff_ms(&tic, &toc); - log_warn("IPCP %s timed out after %d ms.", - str_ipcp_cmd(msg->code), diff); - } - return NULL; + return ipcp_msg__unpack(NULL, len, buf); + + if (errno == EAGAIN && !may_fail) { + int diff = ts_diff_ms(&toc, &tic); + log_warn("IPCP %s timed out after %d ms.", + str_ipcp_cmd(msg->code), diff); } - return recv_msg; + return NULL; } int ipcp_bootstrap(pid_t pid, @@ -420,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; @@ -454,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; @@ -494,9 +499,11 @@ 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 = true; + msg.has_pk = response == 0; msg.pk.data = data.data; msg.pk.len = data.len; diff --git a/src/irmd/irmd.h b/src/irmd/irmd.h index cf8f6953..3e54904a 100644 --- a/src/irmd/irmd.h +++ b/src/irmd/irmd.h @@ -39,7 +39,7 @@ int connect_ipcp(pid_t pid, const char * component, qosspec_t qs); -int name_create(const struct name_info * info); +int name_create(struct name_info * info); int name_reg(const char * name, pid_t pid); diff --git a/src/irmd/main.c b/src/irmd/main.c index bc13fa7c..ccb16017 100644 --- a/src/irmd/main.c +++ b/src/irmd/main.c @@ -22,6 +22,7 @@ #if defined(__linux__) || defined(__CYGWIN__) #define _DEFAULT_SOURCE +#define _GNU_SOURCE #else #define _POSIX_C_SOURCE 200809L #endif @@ -39,9 +40,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> @@ -50,9 +53,13 @@ #include "irmd.h" #include "ipcp.h" +#include "oap.h" #include "reg/reg.h" #include "configfile.h" +#include <dirent.h> +#include <grp.h> +#include <pwd.h> #include <sys/socket.h> #include <sys/un.h> #include <signal.h> @@ -71,11 +78,13 @@ #define SHM_SAN_HOLDOFF 1000 /* ms */ #define IPCP_HASH_LEN(p) hash_len((p)->dir_hash_algo) #define BIND_TIMEOUT 10 /* ms */ +#define TIMESYNC_SLACK 100 /* ms */ +#define OAP_SEEN_TIMER 20 /* s */ #define DEALLOC_TIME 300 /* s */ -#define MSGBUFSZ 2048 enum irm_state { IRMD_NULL = 0, + IRMD_INIT, IRMD_RUNNING, IRMD_SHUTDOWN }; @@ -94,7 +103,7 @@ struct { char * cfg_file; /* configuration file path */ #endif 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 */ @@ -173,8 +182,11 @@ static pid_t spawn_ipcp(struct ipcp_info * info) case IPCP_BROADCAST: exec_name = IPCP_BROADCAST_EXEC; break; - case IPCP_UDP: - exec_name = IPCP_UDP_EXEC; + case IPCP_UDP4: + exec_name = IPCP_UDP4_EXEC; + break; + case IPCP_UDP6: + exec_name = IPCP_UDP6_EXEC; break; case IPCP_ETH_LLC: exec_name = IPCP_ETH_LLC_EXEC; @@ -217,7 +229,7 @@ static pid_t spawn_ipcp(struct ipcp_info * info) } info->pid = pid; - info->state = IPCP_BOOT; + info->state = IPCP_INIT; return 0; } @@ -312,7 +324,7 @@ int bootstrap_ipcp(pid_t pid, goto fail; } - if (conf->type == IPCP_UDP) + if (conf->type == IPCP_UDP4 || conf->type == IPCP_UDP6) conf->layer_info.dir_hash_algo = (enum pol_dir_hash) HASH_MD5; if (ipcp_bootstrap(pid, conf, &layer)) { @@ -320,7 +332,7 @@ int bootstrap_ipcp(pid_t pid, goto fail; } - info.state = IPCP_BOOTSTRAPPED; + info.state = IPCP_BOOT; if (reg_set_layer_for_ipcp(&info, &layer) < 0) { log_err("Failed to set layer info for IPCP."); @@ -352,6 +364,8 @@ int enroll_ipcp(pid_t pid, goto fail; } + info.state = IPCP_BOOT; + if (reg_set_layer_for_ipcp(&info, &layer) < 0) { log_err("Failed to set layer info for IPCP."); goto fail; @@ -425,6 +439,71 @@ static int disconnect_ipcp(pid_t pid, return 0; } +static void name_update_sec_paths(struct name_info * info) +{ + char * srv_dir = OUROBOROS_SRV_CRT_DIR; + char * cli_dir = OUROBOROS_CLI_CRT_DIR; + + assert(info != NULL); + + if (strlen(info->s.enc) == 0) + 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); + + if (strlen(info->s.key) == 0) + 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.conf", cli_dir, info->name); + + if (strlen(info->c.crt) == 0) + sprintf(info->c.crt, "%s/%s/crt.pem", cli_dir, info->name); + + if (strlen(info->c.key) == 0) + sprintf(info->c.key, "%s/%s/key.pem", cli_dir, info->name); +} + +int name_create(struct name_info * info) +{ + int ret; + + assert(info != NULL); + + name_update_sec_paths(info); + + ret = reg_create_name(info); + if (ret == -EEXIST) { + log_info("Name %s already exists.", info->name); + return 0; + } + + if (ret < 0) { + log_err("Failed to create name %s.", info->name); + return -1; + } + + log_info("Created new name: %s.", info->name); + + return 0; +} + +static int name_destroy(const char * name) +{ + + assert(name != NULL); + + if (reg_destroy_name(name) < 0) { + log_err("Failed to destroy name %s.", name); + return -1; + } + + log_info("Destroyed name: %s.", name); + + return 0; +} + int bind_program(char ** exec, const char * name, uint8_t flags) @@ -448,10 +527,8 @@ int bind_program(char ** exec, if (!reg_has_name(name)) { ni.pol_lb = LB_SPILL; strcpy(ni.name, name); - if (reg_create_name(&ni) < 0) { - log_err("Failed to create name %s.", name); + if (name_create(&ni) < 0) goto fail_name; - } } if (reg_bind_prog(name, exec, flags) < 0) { @@ -497,10 +574,8 @@ int bind_process(pid_t pid, if (!reg_has_name(name)) { ni.pol_lb = LB_SPILL; strcpy(ni.name, name); - if (reg_create_name(&ni) < 0) { - log_err("Failed to create name %s.", name); + if (name_create(&ni) < 0) goto fail; - } } if (reg_bind_proc(name, pid) < 0) { @@ -529,18 +604,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, @@ -549,18 +627,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, @@ -581,43 +662,6 @@ static int list_ipcps(ipcp_list_msg_t *** ipcps, return -1; } -int name_create(const struct name_info * info) -{ - int ret; - - assert(info != NULL); - - ret = reg_create_name(info); - if (ret == -EEXIST) { - log_info("Name %s already exists.", info->name); - return 0; - } - - if (ret < 0) { - log_err("Failed to create name %s.", info->name); - return -1; - } - - log_info("Created new name: %s.", info->name); - - return 0; -} - -static int name_destroy(const char * name) -{ - - assert(name != NULL); - - if (reg_destroy_name(name) < 0) { - log_err("Failed to destroy name %s.", name); - return -1; - } - - log_info("Destroyed name: %s.", name); - - return 0; -} - static int list_names(name_info_msg_t *** names, size_t * n_names) { @@ -669,8 +713,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 ".", @@ -679,6 +722,10 @@ int name_reg(const char * name, freebuf(hash); return 0; + + fail_hash: + freebuf(hash); + return -1; } static int name_unreg(const char * name, @@ -713,8 +760,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); @@ -722,20 +768,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; } @@ -749,12 +828,6 @@ 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); @@ -762,65 +835,43 @@ static void __cleanup_flow(void * flow) static int flow_accept(struct flow_info * flow, buffer_t * data, - struct timespec * abstime) + struct timespec * abstime, + struct crypt_sk * sk) { - uint8_t buf[MSGBUFSZ]; - buffer_t lpk; /* local public key */ - buffer_t rpk; /* remote public key */ - void * pkp; /* my public/private key pair */ - ssize_t key_len; - uint8_t * s; - int err; + 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 && data->len == 0 && data->data == NULL); + assert(data != NULL && BUF_IS_EMPTY(data)); + + clrbuf(req_hdr); + clrbuf(resp_hdr); if (!reg_has_proc(flow->n_pid)) { log_err("Unknown process %d calling accept.", flow->n_pid); err = -EINVAL; - goto fail; - } - - s = malloc(SYMMKEYSZ); - if (s == NULL) { - log_err("Failed to malloc symmkey."); - err = -ENOMEM; - goto fail; - } - - key_len = crypt_dh_pkp_create(&pkp, buf); - if (key_len < 0) { - log_err("Failed to generate key pair."); - err = -ECRYPT; - goto fail_pkp; + goto fail_flow; } - lpk.data = buf; - lpk.len = (size_t) key_len; - - log_dbg("Generated ephemeral keys for %d.", flow->n_pid); - if (reg_create_flow(flow) < 0) { log_err("Failed to create flow."); err = -EBADF; goto fail_flow; } - if (reg_prepare_flow_accept(flow, &lpk) < 0) { + if (reg_prepare_flow_accept(flow) < 0) { log_err("Failed to prepare accept."); err = -EBADF; goto fail_wait; } pthread_cleanup_push(__cleanup_flow, flow); - pthread_cleanup_push(__cleanup_pkp, pkp); - pthread_cleanup_push(free, s); - err = reg_wait_flow_accepted(flow, &rpk, abstime); + err = reg_wait_flow_accepted(flow, &req_hdr, abstime); pthread_cleanup_pop(false); - pthread_cleanup_pop(false); - pthread_cleanup_pop(false); if (err == -ETIMEDOUT) { log_err("Flow accept timed out."); @@ -835,45 +886,55 @@ static int flow_accept(struct flow_info * flow, assert(err == 0); - if (flow->qs.cypher_s != 0) { /* crypto requested */ - if (crypt_dh_derive(pkp, rpk, s) < 0) { - log_err("Failed to derive secret for %d.", flow->id); - err = -ECRYPT; - goto fail_derive; - } - freebuf(rpk); - data->data = s; - data->len = SYMMKEYSZ; - s= NULL; - } else { - clrbuf(lpk); + 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_oap; + } + + if (reg_get_name_info(name, &info) < 0) { + log_err("Failed to get name info for %s.", name); + err = -ENAME; + goto fail_oap; } - if (ipcp_flow_alloc_resp(flow, 0, lpk) < 0) { + log_dbg("IPCP %d accepting flow %d for %s.", + flow->n_pid, flow->id, name); + + flow->uid = reg_get_proc_uid(flow->n_pid); + + 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 (ipcp_flow_alloc_resp(flow, 0, resp_hdr) < 0) { log_err("Failed to respond to flow allocation."); - err = -EIPCP; - goto fail_alloc_resp; + goto fail_resp; } - crypt_dh_pkp_destroy(pkp); - free(s); + log_info("Flow %d accepted by %d for %s (uid %d).", + flow->id, flow->n_pid, name, flow->uid); + + freebuf(req_hdr); + freebuf(resp_hdr); return 0; - fail_derive: - freebuf(rpk); - clrbuf(lpk); - ipcp_flow_alloc_resp(flow, err, lpk); - fail_alloc_resp: - flow->state = FLOW_NULL; + fail_oap: + ipcp_flow_alloc_resp(flow, err, resp_hdr); fail_wait: reg_destroy_flow(flow->id); fail_flow: - crypt_dh_pkp_destroy(pkp); - fail_pkp: - free(s); - fail: return err; + + fail_resp: + flow->state = FLOW_NULL; + freebuf(req_hdr); + freebuf(resp_hdr); + reg_destroy_flow(flow->id); + return -EIPCP; } static int flow_join(struct flow_info * flow, @@ -883,17 +944,20 @@ static int flow_join(struct flow_info * flow, struct ipcp_info ipcp; struct layer_info layer; buffer_t hash; - buffer_t pbuf = {NULL, 0}; /* nothing to piggyback */ + 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); @@ -901,6 +965,8 @@ static int flow_join(struct flow_info * flow, goto fail_ipcp; } + flow->n_1_pid = ipcp.pid; + hash.len = hash_len((enum hash_algo) layer.dir_hash_algo); hash.data = malloc(hash.len); if (hash.data == NULL) { @@ -909,6 +975,8 @@ static int flow_join(struct flow_info * flow, goto fail_ipcp; } + str_hash((enum hash_algo) layer.dir_hash_algo, hash.data, dst); + reg_prepare_flow_alloc(flow); if (ipcp_flow_join(flow, hash)) { @@ -936,6 +1004,7 @@ static int flow_join(struct flow_info * flow, goto fail_alloc; } + assert(pbuf.data == NULL && pbuf.len == 0); assert(err == 0); freebuf(hash); @@ -1007,45 +1076,27 @@ static int get_ipcp_by_dst(const char * dst, return err; } -static int flow_alloc(struct flow_info * flow, - const char * dst, +static int flow_alloc(const char * dst, + struct flow_info * flow, buffer_t * data, - struct timespec * abstime) + struct timespec * abstime, + struct crypt_sk * sk) { - uint8_t buf[MSGBUFSZ]; - buffer_t lpk ={NULL, 0}; /* local public key */ - buffer_t rpk; /* remote public key */ - void * pkp = NULL; /* my public/private key pair */ - uint8_t * s = NULL; - buffer_t hash; - int err; - /* piggyback of user data not yet implemented */ - assert(data != NULL && data->len == 0 && data->data == NULL); - - log_info("Allocating flow for %d to %s.", flow->n_pid, dst); - - - if (flow->qs.cypher_s > 0) { - ssize_t key_len; + 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; - 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; + /* piggyback of user data not yet implemented */ + assert(data != NULL && BUF_IS_EMPTY(data)); - log_dbg("Generated ephemeral keys for %d.", flow->n_pid); + /* Look up name_info for dst */ + if (reg_get_name_info(dst, &info) < 0) { + log_err("Failed to get name info for %s.", dst); + err = -ENAME; + goto fail_flow; } if (reg_create_flow(flow) < 0) { @@ -1054,81 +1105,85 @@ static int flow_alloc(struct flow_info * flow, 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); + 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 (reg_prepare_flow_alloc(flow) < 0) { + log_err("Failed to prepare flow allocation."); + err = -EBADF; + goto fail_prepare; + } - if (ipcp_flow_alloc(flow, hash, lpk)) { + if (oap_cli_prepare(&ctx, &info, &req_hdr, *data) < 0) { + log_err("Failed to prepare OAP request for %s.", dst); + err = -EBADF; + goto fail_prepare; + } + + if (ipcp_flow_alloc(flow, hash, req_hdr)) { log_err("Flow allocation %d failed.", flow->id); - err = -ENOTALLOC; + err = -EIPCP; goto fail_alloc; } pthread_cleanup_push(__cleanup_flow, flow); - pthread_cleanup_push(__cleanup_pkp, pkp); pthread_cleanup_push(free, hash.data); - pthread_cleanup_push(free, s); - err = reg_wait_flow_allocated(flow, &rpk, abstime); + err = reg_wait_flow_allocated(flow, &resp_hdr, abstime); pthread_cleanup_pop(false); pthread_cleanup_pop(false); - pthread_cleanup_pop(false); - pthread_cleanup_pop(false); if (err == -ETIMEDOUT) { log_err("Flow allocation timed out."); - goto fail_alloc; + goto fail_wait; } - if (err == -1) { - log_dbg("Flow allocation terminated."); - err = -EPIPE; - goto fail_alloc; - } + log_dbg("Response for flow %d to %s.", flow->id, dst); - assert(err == 0); + if (err < 0) { + log_warn("Allocation rejected: %s (%d).", dst, err); + goto fail_peer; + } - if (flow->qs.cypher_s != 0) { /* crypto requested */ - if (crypt_dh_derive(pkp, rpk, s) < 0) { - log_err("Failed to derive secret for %d.", flow->id); - err = -ECRYPT; - goto fail_derive; - } - crypt_dh_pkp_destroy(pkp); - freebuf(rpk); - data->data = s; - data->len = SYMMKEYSZ; - s = NULL; + err = oap_cli_complete(ctx, &info, resp_hdr, data, sk); + if (err < 0) { + log_err("OAP completion failed for %s.", dst); + goto fail_complete; } + freebuf(req_hdr); + freebuf(resp_hdr); freebuf(hash); - free(s); return 0; - fail_derive: - freebuf(rpk); + 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: - if (flow->qs.cypher_s > 0) - crypt_dh_pkp_destroy(pkp); - fail_pkp: - free(s); - fail_malloc: return err; } -static int wait_for_accept(enum hash_algo algo, - const uint8_t * hash) +static int wait_for_accept(const char * name) { struct timespec timeo = TIMESPEC_INIT_MS(IRMD_REQ_ARR_TIMEOUT); struct timespec abstime; @@ -1138,25 +1193,23 @@ static int wait_for_accept(enum hash_algo algo, clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - ret = reg_wait_flow_accepting(algo, hash, &abstime); + ret = reg_wait_flow_accepting(name, &abstime); if (ret == -ETIMEDOUT) { - if (reg_get_exec(algo, hash, &exec) < 0) { - log_dbg("No program bound to " HASH_FMT32 ".", - HASH_VAL32(hash)); + if (reg_get_exec(name, &exec) < 0) { + log_dbg("No program bound for %s.", name); goto fail; } - log_info("Autostarting %s.", exec[0]); - if (spawn_program(exec) < 0) { - log_dbg("Failed to autostart " HASH_FMT32 ".", - HASH_VAL32(hash)); + log_err("Failed to start %s for %s.", exec[0], name); goto fail_spawn; } + log_info("Starting %s for %s.", exec[0], name); + ts_add(&abstime, &timeo, &abstime); - ret = reg_wait_flow_accepting(algo, hash, &abstime); + ret = reg_wait_flow_accepting(name, &abstime); if (ret == -ETIMEDOUT) goto fail_spawn; @@ -1179,10 +1232,11 @@ static int flow_req_arr(struct flow_info * flow, struct layer_info layer; enum hash_algo algo; int ret; + char name[NAME_SIZE + 1]; info.pid = flow->n_1_pid; - log_info("Flow req arrived from IPCP %d for " HASH_FMT32 ".", + log_dbg("Flow req arrived from IPCP %d for " HASH_FMT32 ".", info.pid, HASH_VAL32(hash)); if (reg_get_ipcp(&info, &layer) < 0) { @@ -1193,10 +1247,17 @@ static int flow_req_arr(struct flow_info * flow, algo = (enum hash_algo) layer.dir_hash_algo; - ret = wait_for_accept(algo, hash); + if (reg_get_name_for_hash(name, algo, hash) < 0) { + log_warn("No name for " HASH_FMT32 ".", HASH_VAL32(hash)); + ret = -ENAME; + goto fail; + } + + log_info("Flow request arrived for %s.", name); + + ret = wait_for_accept(name); if (ret < 0) { - log_err("No activeprocess for " HASH_FMT32 ".", - HASH_VAL32(hash)); + log_err("No active process for %s.", name); goto fail; } @@ -1218,9 +1279,9 @@ static int flow_alloc_reply(struct flow_info * flow, int response, buffer_t * data) { - flow->state = response ? FLOW_DEALLOCATED : FLOW_ALLOCATED; + flow->state = response != 0 ? FLOW_DEALLOCATED : FLOW_ALLOCATED; - if (reg_respond_alloc(flow, data) < 0) { + if (reg_respond_alloc(flow, data, response) < 0) { log_err("Failed to reply to flow %d.", flow->id); flow->state = FLOW_DEALLOCATED; return -EBADF; @@ -1232,7 +1293,7 @@ static int flow_alloc_reply(struct flow_info * flow, static int flow_dealloc(struct flow_info * flow, struct timespec * ts) { - log_info("Deallocating flow %d for process %d (timeout: %zd s).", + log_info("Deallocating flow %d for process %d (timeout: %ld s).", flow->id, flow->n_pid, ts->tv_sec); reg_dealloc_flow(flow); @@ -1308,25 +1369,29 @@ static void * acceptloop(void * o) return (void *) 0; } -static void free_msg(void * o) +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; + 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)); @@ -1351,7 +1416,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) ret_msg->code = IRM_MSG_CODE__IRM_REPLY; - pthread_cleanup_push(free_msg, ret_msg); + pthread_cleanup_push(__cleanup_irm_msg, ret_msg); switch (msg->code) { case IRM_MSG_CODE__IRM_CREATE_IPCP: @@ -1380,7 +1445,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) res = disconnect_ipcp(msg->pid, msg->dst, msg->comp); break; case IRM_MSG_CODE__IRM_BIND_PROGRAM: - /* Make exec NULL terminated instead of empty string terminated */ + /* Terminate with NULL instead of "" */ free(msg->exec[msg->n_exec - 1]); msg->exec[msg->n_exec - 1] = NULL; res = bind_program(msg->exec, msg->name, msg->opts); @@ -1391,7 +1456,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); @@ -1406,8 +1475,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) res = list_ipcps(&ret_msg->ipcps, &ret_msg->n_ipcps); break; case IRM_MSG_CODE__IRM_CREATE_NAME: - strcpy(name.name, msg->names[0]->name); - name.pol_lb = msg->names[0]->pol_lb; + name = name_info_msg_to_s(msg->name_info); res = name_create(&name); break; case IRM_MSG_CODE__IRM_DESTROY_NAME: @@ -1423,17 +1491,33 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) res = name_unreg(msg->name, msg->pid); break; case IRM_MSG_CODE__IRM_FLOW_ACCEPT: + tpm_wait_work(irmd.tpm); data.len = msg->pk.len; data.data = msg->pk.data; 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, &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 = data.len != 0; - ret_msg->symmkey.data = data.data; - ret_msg->symmkey.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: @@ -1443,12 +1527,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, &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 = data.len != 0; - ret_msg->symmkey.data = data.data; - ret_msg->symmkey.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: @@ -1457,7 +1555,7 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg) abstime = abstime == NULL ? &max : abstime; res = flow_join(&flow, msg->dst, abstime); if (res == 0) - ret_msg->flow_info = flow_info_s_to_msg(&flow); + ret_msg->flow_info = flow_info_s_to_msg(&flow); break; case IRM_MSG_CODE__IRM_FLOW_DEALLOC: flow = flow_info_msg_to_s(msg->flow_info); @@ -1502,14 +1600,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; @@ -1540,12 +1640,12 @@ static void * mainloop(void * o) continue; } - tpm_dec(irmd.tpm); + tpm_begin_work(irmd.tpm); pthread_cleanup_push(__cleanup_close_ptr, &sfd); - pthread_cleanup_push(free_msg, msg); + 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); @@ -1556,12 +1656,12 @@ static void * mainloop(void * o) } if (ret_msg->result == -EPIPE) { - log_dbg("Terminated command: application closed socket."); + log_dbg("Terminated command: remote closed socket."); goto fail; } if (ret_msg->result == -EIRMD) { - log_dbg("Terminated command: IRMd not in running state."); + log_dbg("Terminated command: IRMd not running."); goto fail; } @@ -1596,54 +1696,20 @@ static void * mainloop(void * o) pthread_cleanup_pop(true); pthread_cleanup_pop(true); - tpm_inc(irmd.tpm); + tpm_end_work(irmd.tpm); continue; fail: irm_msg__free_unpacked(ret_msg, NULL); fail_msg: close(sfd); - tpm_inc(irmd.tpm); + tpm_end_work(irmd.tpm); continue; } return (void *) 0; } -static void irm_fini(void) -{ -#ifdef HAVE_FUSE - struct timespec wait = TIMESPEC_INIT_MS(1); - int retries = 5; -#endif - if (irmd_get_state() != IRMD_NULL) - log_warn("Unsafe destroy."); - - tpm_destroy(irmd.tpm); - - close(irmd.sockfd); - - if (unlink(IRM_SOCK_PATH)) - log_dbg("Failed to unlink %s.", IRM_SOCK_PATH); - - if (irmd.rdrb != NULL) - shm_rdrbuff_destroy(irmd.rdrb); - - if (irmd.lf != NULL) - lockfile_destroy(irmd.lf); - - pthread_mutex_destroy(&irmd.cmd_lock); - pthread_cond_destroy(&irmd.cmd_cond); - pthread_rwlock_destroy(&irmd.state_lock); - -#ifdef HAVE_FUSE - while (rmdir(FUSE_PREFIX) < 0 && retries-- > 0) - nanosleep(&wait, NULL); - if (retries < 0) - log_err("Failed to remove " FUSE_PREFIX); -#endif -} - #ifdef HAVE_FUSE static void destroy_mount(char * mnt) { @@ -1672,7 +1738,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; @@ -1693,10 +1759,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) @@ -1719,10 +1783,85 @@ void * irm_sanitize(void * o) return (void *) 0; } +static int irm_load_store(char * dpath) +{ + struct stat st; + struct dirent * dent; + DIR * dir; + void * crt; + + if (stat(dpath, &st) == -1) { + log_dbg("Store directory %s not found.", dpath); + return 0; + } + + if (!S_ISDIR(st.st_mode)) { + log_err("%s is not a directory.", dpath); + goto fail_dir; + } + + /* loop through files in directory and load certificates */ + dir = opendir(dpath); + if (dir == NULL) { + log_err("Failed to open %s.", dpath); + goto fail_dir; + } + + while ((dent = readdir(dir)) != NULL) { + char path[NAME_PATH_SIZE + 1]; + + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + + snprintf(path, sizeof(path), "%s/%s", dpath, + dent->d_name); + + if (stat(path, &st) == -1) { + log_dbg("Failed to stat %s.", path); + continue; + } + + if (!S_ISREG(st.st_mode)) { + log_dbg("%s is not a regular file.", path); + goto fail_file; + } + + if (crypt_load_crt_file(path, &crt) < 0) { + log_err("Failed to load certificate from %s.", path); + goto fail_file; + } + + if (oap_auth_add_ca_crt(crt) < 0) { + log_err("Failed to add certificate from %s to store.", + path); + goto fail_crt_add; + } + + log_dbg("Loaded certificate: %s.", path); + + crypt_free_crt(crt); + } + + closedir(dir); + + log_info("Loaded certificates from %s.", dpath); + + return 0; + + fail_crt_add: + crypt_free_crt(crt); + fail_file: + closedir(dir); + fail_dir: + return -1; +} static int irm_init(void) { struct stat st; + struct group * grp; + gid_t gid; pthread_condattr_t cattr; #ifdef HAVE_FUSE mode_t mask; @@ -1808,17 +1947,45 @@ 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) { log_err("Failed to greate thread pool."); goto fail_tpm_create; } + + if (oap_auth_init() < 0) { + log_err("Failed to initialize OAP module."); + goto fail_oap; + } + + if (irm_load_store(OUROBOROS_CA_CRT_DIR) < 0) { + log_err("Failed to load CA certificates."); + goto fail_load_store; + } + + if (irm_load_store(OUROBOROS_CHAIN_DIR) < 0) { + log_err("Failed to load intermediate certificates."); + goto fail_load_store; + } + #ifdef HAVE_FUSE mask = umask(0); @@ -1844,6 +2011,8 @@ static int irm_init(void) gcry_control(GCRYCTL_INITIALIZATION_FINISHED); #endif + irmd_set_state(IRMD_INIT); + return 0; #ifdef HAVE_LIBGCRYPT @@ -1851,11 +2020,14 @@ static int irm_init(void) #ifdef HAVE_FUSE rmdir(FUSE_PREFIX); #endif - tpm_destroy(irmd.tpm); #endif + 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); @@ -1872,6 +2044,57 @@ static int irm_init(void) return -1; } +static void irm_fini(void) +{ + struct list_head * p; + struct list_head * h; +#ifdef HAVE_FUSE + struct timespec wait = TIMESPEC_INIT_MS(1); + int retries = 5; +#endif + if (irmd_get_state() != IRMD_INIT) + log_warn("Unsafe destroy."); + + oap_auth_fini(); + + tpm_destroy(irmd.tpm); + + close(irmd.sockfd); + + if (unlink(IRM_SOCK_PATH)) + log_dbg("Failed to unlink %s.", IRM_SOCK_PATH); + + ssm_pool_destroy(irmd.gspp); + + if (irmd.lf != NULL) + lockfile_destroy(irmd.lf); + + pthread_mutex_lock(&irmd.cmd_lock); + + list_for_each_safe(p, h, &irmd.cmds) { + struct cmd * cmd = list_entry(p, struct cmd, next); + list_del(&cmd->next); + close(cmd->fd); + free(cmd); + } + + pthread_mutex_unlock(&irmd.cmd_lock); + + pthread_mutex_destroy(&irmd.cmd_lock); + pthread_cond_destroy(&irmd.cmd_cond); + pthread_rwlock_destroy(&irmd.state_lock); + +#ifdef HAVE_FUSE + while (rmdir(FUSE_PREFIX) < 0 && retries-- > 0) + nanosleep(&wait, NULL); + if (retries < 0) + log_err("Failed to remove " FUSE_PREFIX); +#endif + assert(list_is_empty(&irmd.cmds)); + + irmd.state = IRMD_NULL; +} + static void usage(void) { printf("Usage: irmd \n" @@ -1885,11 +2108,11 @@ static void usage(void) static int irm_start(void) { + irmd_set_state(IRMD_RUNNING); + if (tpm_start(irmd.tpm)) goto fail_tpm_start; - irmd_set_state(IRMD_RUNNING); - if (pthread_create(&irmd.irm_sanitize, NULL, irm_sanitize, NULL)) goto fail_irm_sanitize; @@ -1904,9 +2127,9 @@ static int irm_start(void) pthread_cancel(irmd.irm_sanitize); pthread_join(irmd.irm_sanitize, NULL); fail_irm_sanitize: - irmd_set_state(IRMD_NULL); tpm_stop(irmd.tpm); fail_tpm_start: + irmd_set_state(IRMD_INIT); return -1; } @@ -1947,7 +2170,7 @@ static void irm_stop(void) tpm_stop(irmd.tpm); - irmd_set_state(IRMD_NULL); + irmd_set_state(IRMD_INIT); } static void irm_argparse(int argc, @@ -1998,8 +2221,8 @@ static void * kill_dash_nine(void * o) slept += intv; } - log_dbg("I am become Death, destroyer of hung processes."); - + log_dbg("I guess I’ll have to shut you down for good this time,"); + log_dbg("already tried a SIGQUIT, so now it’s KILL DASH 9."); #ifdef IRMD_KILL_ALL_PROCESSES reg_kill_all_proc(SIGKILL); nanosleep(&ts, NULL); @@ -2058,7 +2281,7 @@ int main(int argc, if (geteuid() != 0) { printf("IPC Resource Manager must be run as root.\n"); - exit(EXIT_FAILURE); + goto fail_irm_init; } if (irm_init() < 0) @@ -2069,6 +2292,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) @@ -2076,7 +2304,7 @@ int main(int argc, #ifdef HAVE_TOML if (irm_configure(irmd.cfg_file) < 0) { - irmd_set_state(IRMD_NULL); + irmd_set_state(IRMD_SHUTDOWN); ret = EXIT_FAILURE; } #endif @@ -2088,6 +2316,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 new file mode 100644 index 00000000..085e06a3 --- /dev/null +++ b/src/irmd/oap.c @@ -0,0 +1,130 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Shared credential and configuration loading + * + * 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 <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; + } + + if (cfg->c.nid == NID_undef || crypt_nid_to_str(cfg->c.nid) == NULL) { + log_err("Invalid cipher NID %d for %s.", cfg->c.nid, name); + return -ECRYPT; + } + + log_info("Encryption enabled for %s.", name); + + return 0; +} + +#endif /* OAP_TEST_MODE */ diff --git a/src/irmd/oap.h b/src/irmd/oap.h new file mode 100644 index 00000000..25c07408 --- /dev/null +++ b/src/irmd/oap.h @@ -0,0 +1,67 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Ouroboros Allocation Protocol (OAP) Component + * + * 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_H +#define OUROBOROS_IRMD_OAP_H + +#include <ouroboros/crypt.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <ouroboros/utils.h> + +/* OAP authentication state (in oap/auth.c) */ +int oap_auth_init(void); + +void oap_auth_fini(void); + +int oap_auth_add_ca_crt(void * crt); + +/* +* 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); + +/* + * 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); + +/* 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); + +/* Free OAP state (on failure before complete) */ +void oap_ctx_free(void * ctx); + +#endif /* OUROBOROS_IRMD_OAP_H */ diff --git a/src/irmd/oap/auth.c b/src/irmd/oap/auth.c new file mode 100644 index 00000000..cea7b7a0 --- /dev/null +++ b/src/irmd/oap/auth.c @@ -0,0 +1,252 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Authentication, replay detection, and validation + * + * Dimitri Staessens <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/oap/auth.h b/src/irmd/oap/auth.h new file mode 100644 index 00000000..07c33a23 --- /dev/null +++ b/src/irmd/oap/auth.h @@ -0,0 +1,35 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Authentication functions + * + * Dimitri Staessens <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_AUTH_H +#define OUROBOROS_IRMD_OAP_AUTH_H + +#include "hdr.h" + +int oap_check_hdr(const struct oap_hdr * hdr); + +/* name is updated with the peer's certificate name if available */ +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr); + +#endif /* OUROBOROS_IRMD_OAP_AUTH_H */ diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c new file mode 100644 index 00000000..ea2a25d1 --- /dev/null +++ b/src/irmd/oap/cli.c @@ -0,0 +1,553 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * 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; + } + + log_dbg_id(s->id.data, "KEX config: algo=%s, mode=%s, cipher=%s.", + s->kcfg.x.str != NULL ? s->kcfg.x.str : "none", + s->kcfg.x.mode == KEM_MODE_CLIENT_ENCAP ? "client-encap" : + s->kcfg.x.mode == KEM_MODE_SERVER_ENCAP ? "server-encap" : + "none", + s->kcfg.c.str != NULL ? s->kcfg.c.str : "none"); + + oap_hdr_init(&s->local_hdr, s->id, s->kex_buf, data, s->kcfg.c.nid); + + if (do_client_kex_prepare(info->name, s) < 0) { + log_err_id(s->id.data, "Failed to prepare client KEX."); + goto fail_kex; + } + + if (oap_hdr_encode(&s->local_hdr, pkp, crt, &s->kcfg, + (buffer_t) BUF_INIT, NID_undef)) { + log_err_id(s->id.data, "Failed to create OAP request header."); + goto fail_hdr; + } + + debug_oap_hdr_snd(&s->local_hdr); + + /* Compute and store hash of request for verification in complete */ + s->req_md_nid = s->kcfg.d.nid != NID_undef ? s->kcfg.d.nid : NID_sha384; + ret = md_digest(s->req_md_nid, s->local_hdr.hdr, s->req_hash); + if (ret < 0) { + log_err_id(s->id.data, "Failed to hash request."); + goto fail_hash; + } + s->req_hash_len = (size_t) ret; + + /* Transfer ownership of request buffer */ + *req_buf = s->local_hdr.hdr; + clrbuf(s->local_hdr.hdr); + + crypt_free_crt(crt); + crypt_free_key(pkp); + + *ctx = s; + + log_dbg_id(s->id.data, "OAP request prepared for %s.", info->name); + + return 0; + + fail_hash: + fail_hdr: + crypt_secure_free(s->key, SYMMKEYSZ); + crypt_free_key(s->pkp); + fail_kex: + crypt_free_crt(crt); + crypt_free_key(pkp); + fail_id: + free(s); + return -ECRYPT; +} + +void oap_ctx_free(void * ctx) +{ + struct oap_cli_ctx * s = ctx; + + if (s == NULL) + return; + + oap_hdr_fini(&s->local_hdr); + + if (s->pkp != NULL) + crypt_free_key(s->pkp); + + if (s->key != NULL) + crypt_secure_free(s->key, SYMMKEYSZ); + + memset(s, 0, sizeof(*s)); + free(s); +} + +static int do_client_kex_complete_kem(struct oap_cli_ctx * s, + const struct oap_hdr * peer_hdr, + struct crypt_sk * sk) +{ + struct sec_config * kcfg = &s->kcfg; + uint8_t * id = s->id.data; + uint8_t key_buf[SYMMKEYSZ]; + + if (kcfg->x.mode == KEM_MODE_SERVER_ENCAP) { + buffer_t ct; + + if (peer_hdr->kex.len == 0) { + log_err_id(id, "Server did not send KEM CT."); + return -ECRYPT; + } + + ct.data = peer_hdr->kex.data; + ct.len = peer_hdr->kex.len; + + if (kex_kem_decap(s->pkp, ct, kcfg->k.nid, key_buf) < 0) { + log_err_id(id, "Failed to decapsulate KEM."); + return -ECRYPT; + } + + log_dbg_id(id, "Client decapsulated server CT."); + + } else if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) { + /* Key already derived during prepare */ + memcpy(sk->key, s->key, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, + kcfg->c.str); + return 0; + } + + memcpy(sk->key, key_buf, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + 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; + + if (!IS_KEX_ALGO_SET(kcfg)) + return 0; + + /* Accept server's cipher choice */ + if (peer_hdr->cipher_str == NULL) { + log_err_id(id, "Server did not provide cipher."); + return -ECRYPT; + } + + SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str); + if (crypt_validate_nid(kcfg->c.nid) < 0) { + log_err_id(id, "Server cipher '%s' not supported.", + peer_hdr->cipher_str); + return -ENOTSUP; + } + + log_dbg_id(id, "Accepted server cipher %s.", peer_hdr->cipher_str); + + /* Derive shared secret */ + if (IS_KEM_ALGORITHM(kcfg->x.str)) + return do_client_kex_complete_kem(s, peer_hdr, sk); + + return do_client_kex_complete_dhe(s, peer_hdr, sk); +} + +int oap_cli_complete(void * ctx, + const struct name_info * info, + buffer_t rsp_buf, + buffer_t * data, + struct crypt_sk * sk) +{ + struct oap_cli_ctx * s = ctx; + struct oap_hdr peer_hdr; + char peer[NAME_SIZE + 1]; + uint8_t * id; + + assert(ctx != NULL); + assert(info != NULL); + assert(data != NULL); + assert(sk != NULL); + + sk->nid = NID_undef; + + clrbuf(*data); + + memset(&peer_hdr, 0, sizeof(peer_hdr)); + + id = s->id.data; + + log_dbg_id(id, "Completing OAP for %s.", info->name); + + /* Decode response header using client's md_nid for hash length */ + if (oap_hdr_decode(&peer_hdr, rsp_buf, s->req_md_nid) < 0) { + log_err_id(id, "Failed to decode OAP response header."); + goto fail_oap; + } + + debug_oap_hdr_rcv(&peer_hdr); + + /* Verify response ID matches request */ + if (memcmp(peer_hdr.id.data, id, OAP_ID_SIZE) != 0) { + log_err_id(id, "OAP response ID mismatch."); + goto fail_oap; + } + + /* Authenticate server */ + if (oap_auth_peer(peer, &s->local_hdr, &peer_hdr) < 0) { + log_err_id(id, "Failed to authenticate server."); + goto fail_oap; + } + + /* Verify request hash in authenticated response */ + if (peer_hdr.req_hash.len == 0) { + log_err_id(id, "Response missing req_hash."); + goto fail_oap; + } + + if (memcmp(peer_hdr.req_hash.data, s->req_hash, s->req_hash_len) != 0) { + log_err_id(id, "Response req_hash mismatch."); + goto fail_oap; + } + + /* Verify peer certificate name matches expected destination */ + if (peer_hdr.crt.len > 0 && strcmp(peer, info->name) != 0) { + log_err_id(id, "Peer crt for '%s' does not match '%s'.", + peer, info->name); + goto fail_oap; + } + + /* Complete key exchange */ + if (do_client_kex_complete(s, &peer_hdr, sk) < 0) { + log_err_id(id, "Failed to complete key exchange."); + goto fail_oap; + } + + /* Copy piggybacked data from server response */ + if (oap_hdr_copy_data(&peer_hdr, data) < 0) { + log_err_id(id, "Failed to copy server data."); + goto fail_oap; + } + + log_info_id(id, "OAP completed for %s.", info->name); + + oap_ctx_free(s); + + return 0; + + fail_oap: + oap_ctx_free(s); + return -ECRYPT; +} diff --git a/src/irmd/oap/hdr.c b/src/irmd/oap/hdr.c new file mode 100644 index 00000000..cdff7ab6 --- /dev/null +++ b/src/irmd/oap/hdr.c @@ -0,0 +1,456 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Header encoding, decoding, and debugging + * + * Dimitri Staessens <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 +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) + log_proto(" Key Exchange Data: [%zu bytes] [%s]", + hdr->kex.len, hdr->kex_flags.role ? + "Client encaps" : "Server encaps"); + else + log_proto(" Ephemeral Public Key: <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..f603b169 --- /dev/null +++ b/src/irmd/oap/hdr.h @@ -0,0 +1,159 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Header definitions and functions + * + * Dimitri Staessens <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..8363e3a2 --- /dev/null +++ b/src/irmd/oap/internal.h @@ -0,0 +1,133 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * 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> + +/* + * Authentication functions (auth.c) + */ +int oap_check_hdr(const struct oap_hdr * hdr); + +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr); + +/* + * Key exchange functions (kex.c) + */ +int oap_negotiate_cipher(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg); + +/* + * Credential loading (oap.c) - shared between client and server + */ +#ifndef OAP_TEST_MODE +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt); + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg); +#endif + +/* + * Server functions (srv.c) + */ +#ifndef OAP_TEST_MODE +int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); + +int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); + +int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp); +#else +extern int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp); +#endif + +int do_server_kex(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk); + +/* + * Client functions (cli.c) + */ +#ifndef OAP_TEST_MODE +int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); + +int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); + +int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk); +#else +extern int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk); +#endif + +int oap_client_kex_prepare(struct sec_config * kcfg, + buffer_t server_pk, + buffer_t * kex, + uint8_t * key, + void ** ephemeral_pkp); + +int oap_client_kex_complete(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + void * pkp, + uint8_t * key); + +#endif /* OUROBOROS_IRMD_OAP_INTERNAL_H */ diff --git a/src/irmd/oap/io.c b/src/irmd/oap/io.c new file mode 100644 index 00000000..e4189d4d --- /dev/null +++ b/src/irmd/oap/io.c @@ -0,0 +1,132 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - File I/O for credentials and configuration + * + * Dimitri Staessens <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; + } + + if (cfg->c.nid == NID_undef || crypt_nid_to_str(cfg->c.nid) == NULL) { + log_err("Invalid cipher NID %d for %s.", cfg->c.nid, name); + return -ECRYPT; + } + + log_info("Encryption enabled for %s.", name); + + return 0; +} + +#endif /* OAP_TEST_MODE */ diff --git a/src/irmd/oap/io.h b/src/irmd/oap/io.h new file mode 100644 index 00000000..a31ddf85 --- /dev/null +++ b/src/irmd/oap/io.h @@ -0,0 +1,40 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Credential and configuration file I/O + * + * Dimitri Staessens <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..c5a4453f --- /dev/null +++ b/src/irmd/oap/srv.c @@ -0,0 +1,462 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * 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_kex(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg) +{ + uint8_t * id = peer_hdr->id.data; + + if (kcfg->c.nid == NID_undef) { + if (peer_hdr->cipher_str != NULL) { + SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str); + if (kcfg->c.nid == NID_undef) { + log_err_id(id, "Unsupported cipher '%s'.", + peer_hdr->cipher_str); + return -ENOTSUP; + } + log_dbg_id(id, "Peer requested cipher %s.", + peer_hdr->cipher_str); + } else { + log_err_id(id, "Encryption requested, no cipher."); + return -ECRYPT; + } + } else { + log_dbg_id(id, "Using local cipher %s.", kcfg->c.str); + } + + /* Negotiate KDF - server overrides client if configured */ + if (kcfg->k.nid != NID_undef) { + log_dbg_id(id, "Using local KDF %s.", + md_nid_to_str(kcfg->k.nid)); + } else if (peer_hdr->kdf_nid != NID_undef) { + if (md_validate_nid(peer_hdr->kdf_nid) == 0) { + kcfg->k.nid = peer_hdr->kdf_nid; + log_dbg_id(id, "Using peer KDF %s.", + md_nid_to_str(peer_hdr->kdf_nid)); + } else { + log_err_id(id, "Unsupported KDF NID %d.", + peer_hdr->kdf_nid); + return -ENOTSUP; + } + } + + if (IS_KEX_ALGO_SET(kcfg)) + log_info_id(id, "Negotiated %s + %s.", + kcfg->x.str, kcfg->c.str); + else + log_info_id(id, "No key exchange."); + + return 0; +} + +static int do_server_kem_decap(const struct name_info * info, + const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + struct crypt_sk * sk) +{ + buffer_t ct; + void * server_pkp = NULL; + int ret; + uint8_t * id = peer_hdr->id.data; + + ret = load_server_kem_keypair(info->name, + peer_hdr->kex_flags.fmt, + &server_pkp); + if (ret < 0) + return ret; + + ct.data = peer_hdr->kex.data; + ct.len = peer_hdr->kex.len; + + ret = kex_kem_decap(server_pkp, ct, kcfg->k.nid, sk->key); + + crypt_free_key(server_pkp); + + if (ret < 0) { + log_err_id(id, "Failed to decapsulate KEM."); + return -ECRYPT; + } + + log_dbg_id(id, "Client encaps: decapsulated CT."); + + return 0; +} + +static int do_server_kem_encap(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + buffer_t client_pk; + ssize_t ct_len; + uint8_t * id = peer_hdr->id.data; + + client_pk.data = peer_hdr->kex.data; + client_pk.len = peer_hdr->kex.len; + + if (IS_HYBRID_KEM(kcfg->x.str)) + ct_len = kex_kem_encap_raw(client_pk, kex->data, + kcfg->k.nid, sk->key); + else + ct_len = kex_kem_encap(client_pk, kex->data, + kcfg->k.nid, sk->key); + + if (ct_len < 0) { + log_err_id(id, "Failed to encapsulate KEM."); + return -ECRYPT; + } + + kex->len = (size_t) ct_len; + + log_dbg_id(id, "Server encaps: generated CT, len=%zd.", ct_len); + + return 0; +} + +static int do_server_kex_kem(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + int ret; + + kcfg->x.mode = peer_hdr->kex_flags.role; + + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) { + ret = do_server_kem_decap(info, peer_hdr, kcfg, sk); + kex->len = 0; + } else { + ret = do_server_kem_encap(peer_hdr, kcfg, kex, sk); + } + + return ret; +} + +static int do_server_kex_dhe(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + ssize_t key_len; + void * epkp; + int ret; + uint8_t * id = peer_hdr->id.data; + + key_len = kex_pkp_create(kcfg, &epkp, kex->data); + if (key_len < 0) { + log_err_id(id, "Failed to generate key pair."); + return -ECRYPT; + } + + kex->len = (size_t) key_len; + + log_dbg_id(id, "Generated %s ephemeral keys.", kcfg->x.str); + + ret = kex_dhe_derive(kcfg, epkp, peer_hdr->kex, sk->key); + if (ret < 0) { + log_err_id(id, "Failed to derive secret."); + kex_pkp_destroy(epkp); + return -ECRYPT; + } + + kex_pkp_destroy(epkp); + + return 0; +} + +int do_server_kex(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + char algo_buf[KEX_ALGO_BUFSZ]; + uint8_t * id; + + id = peer_hdr->id.data; + + /* No KEX data from client */ + if (peer_hdr->kex.len == 0) { + if (IS_KEX_ALGO_SET(kcfg)) { + log_warn_id(id, "KEX requested without info."); + return -ECRYPT; + } + return 0; + } + + if (negotiate_kex(peer_hdr, kcfg) < 0) + return -ECRYPT; + + if (OAP_KEX_ROLE(peer_hdr) != KEM_MODE_CLIENT_ENCAP) { + /* Server encapsulation or DHE: extract algo from DER PK */ + if (get_algo_from_peer_key(peer_hdr, algo_buf) < 0) + return -ECRYPT; + + SET_KEX_ALGO(kcfg, algo_buf); + } + + /* Dispatch based on algorithm type */ + if (IS_KEM_ALGORITHM(kcfg->x.str)) + return do_server_kex_kem(info, peer_hdr, kcfg, kex, sk); + else + return do_server_kex_dhe(peer_hdr, kcfg, kex, sk); +} + +int oap_srv_process(const struct name_info * info, + buffer_t req_buf, + buffer_t * rsp_buf, + buffer_t * data, + struct crypt_sk * sk) +{ + struct oap_hdr peer_hdr; + struct oap_hdr local_hdr; + struct sec_config kcfg; + uint8_t kex_buf[MSGBUFSZ]; + uint8_t hash_buf[MAX_HASH_SIZE]; + buffer_t req_hash = BUF_INIT; + ssize_t hash_ret; + char cli_name[NAME_SIZE + 1]; /* TODO */ + uint8_t * id; + void * pkp = NULL; + void * crt = NULL; + int req_md_nid; + + assert(info != NULL); + assert(rsp_buf != NULL); + assert(data != NULL); + assert(sk != NULL); + + sk->nid = NID_undef; + + memset(&peer_hdr, 0, sizeof(peer_hdr)); + memset(&local_hdr, 0, sizeof(local_hdr)); + clrbuf(*rsp_buf); + + log_dbg("Processing OAP request for %s.", info->name); + + /* Load server credentials */ + if (load_srv_credentials(info, &pkp, &crt) < 0) { + log_err("Failed to load security keys for %s.", info->name); + goto fail_cred; + } + + /* Load KEX config */ + if (load_srv_kex_config(info, &kcfg) < 0) { + log_err("Failed to load KEX config for %s.", info->name); + goto fail_kex; + } + + sk->nid = kcfg.c.nid; + + /* Decode incoming header (NID_undef = request, no hash) */ + if (oap_hdr_decode(&peer_hdr, req_buf, NID_undef) < 0) { + log_err("Failed to decode OAP header."); + goto fail_auth; + } + + debug_oap_hdr_rcv(&peer_hdr); + + id = peer_hdr.id.data; /* Logging */ + + /* Check for replay */ + if (oap_check_hdr(&peer_hdr) < 0) { + log_err_id(id, "OAP header failed replay check."); + goto fail_auth; + } + + /* Authenticate client before processing KEX data */ + oap_hdr_init(&local_hdr, peer_hdr.id, kex_buf, *data, NID_undef); + + if (oap_auth_peer(cli_name, &local_hdr, &peer_hdr) < 0) { + log_err_id(id, "Failed to authenticate client."); + goto fail_auth; + } + + if (do_server_kex(info, &peer_hdr, &kcfg, &local_hdr.kex, sk) < 0) + goto fail_kex; + + /* Build response header with hash of client request */ + local_hdr.nid = sk->nid; + + /* Use client's md_nid, defaulting to SHA-384 for PQC */ + req_md_nid = peer_hdr.md_nid != NID_undef ? peer_hdr.md_nid : NID_sha384; + + /* Compute request hash using client's md_nid */ + hash_ret = md_digest(req_md_nid, req_buf, hash_buf); + if (hash_ret < 0) { + log_err_id(id, "Failed to hash request."); + goto fail_auth; + } + req_hash.data = hash_buf; + req_hash.len = (size_t) hash_ret; + + if (oap_hdr_encode(&local_hdr, pkp, crt, &kcfg, + req_hash, req_md_nid) < 0) { + log_err_id(id, "Failed to create OAP response header."); + goto fail_auth; + } + + debug_oap_hdr_snd(&local_hdr); + + if (oap_hdr_copy_data(&peer_hdr, data) < 0) { + log_err_id(id, "Failed to copy client data."); + goto fail_data; + } + + /* Transfer ownership of response buffer */ + *rsp_buf = local_hdr.hdr; + + log_info_id(id, "OAP request processed for %s.", info->name); + + crypt_free_crt(crt); + crypt_free_key(pkp); + + return 0; + + fail_data: + oap_hdr_fini(&local_hdr); + fail_auth: + crypt_free_crt(crt); + crypt_free_key(pkp); + fail_cred: + return -EAUTH; + + fail_kex: + crypt_free_crt(crt); + crypt_free_key(pkp); + return -ECRYPT; +} diff --git a/src/irmd/oap/tests/CMakeLists.txt b/src/irmd/oap/tests/CMakeLists.txt new file mode 100644 index 00000000..2bf23821 --- /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}_pqc_tests test_suite_pqc.c + # PQC-specific tests + oap_test_pqc.c +) + +# OAP test needs io.c compiled with OAP_TEST_MODE +set(OAP_TEST_SOURCES + ${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} +) + +# PQC test executable (ML-DSA) +add_executable(${PARENT_DIR}_pqc_test ${${PARENT_DIR}_pqc_tests} ${OAP_TEST_SOURCES}) +set_source_files_properties(${OAP_TEST_SOURCES} + TARGET_DIRECTORY ${PARENT_DIR}_pqc_test + PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" +) + +disable_test_logging_for_target(${PARENT_DIR}_pqc_test) +target_link_libraries(${PARENT_DIR}_pqc_test ouroboros-irm) +target_include_directories(${PARENT_DIR}_pqc_test PRIVATE + ${IRMD_SOURCE_DIR} + ${IRMD_BINARY_DIR} +) + +add_dependencies(build_tests ${PARENT_DIR}_test ${PARENT_DIR}_pqc_test) + +# Regular tests +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) + +# PQC tests +ouroboros_register_tests(TARGET ${PARENT_DIR}_pqc_test TESTS ${${PARENT_DIR}_pqc_tests}) 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..70f0a248 --- /dev/null +++ b/src/irmd/oap/tests/oap_test.c @@ -0,0 +1,951 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * 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.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 - they're tested in oap_test_pqc */ + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_roundtrip(kex_supported_nids[i]); + } + + return ret; +} + +/* Cipher negotiation - client should accept server's chosen cipher */ +static int test_oap_cipher_mismatch(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: ChaCha20-Poly1305, SHA3-256, SHA-384 */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_chacha20_poly1305; + test_cfg.srv.kdf = NID_sha3_256; + test_cfg.srv.md = NID_sha384; + test_cfg.srv.auth = AUTH; + + /* Client: AES-256-GCM, SHA-256, SHA-256 */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + /* Verify: both should have the server's chosen cipher and KDF */ + if (ctx.srv.nid != test_cfg.srv.cipher) { + printf("Server cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.srv.nid)); + goto fail_cleanup; + } + + if (ctx.cli.nid != test_cfg.srv.cipher) { + printf("Client cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.cli.nid)); + goto fail_cleanup; + } + + /* Parse response header to check negotiated KDF */ + if (ctx.resp_hdr.len > 26) { + uint16_t resp_kdf_nid; + /* KDF NID at offset 26: ID(16) + ts(8) + cipher(2) */ + resp_kdf_nid = ntoh16(*(uint16_t *)(ctx.resp_hdr.data + 26)); + + if (resp_kdf_nid != test_cfg.srv.kdf) { + printf("Response KDF mismatch: expected %s, got %s\n", + md_nid_to_str(test_cfg.srv.kdf), + md_nid_to_str(resp_kdf_nid)); + goto fail_cleanup; + } + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test roundtrip with different signature digest algorithms */ +static int test_oap_roundtrip_md(int md) +{ + struct oap_test_ctx ctx; + const char * md_str = md_nid_to_str(md); + + TEST_START("(%s)", md_str ? md_str : "default"); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth + KEX with specified md */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = md; + test_cfg.srv.auth = AUTH; + + /* Client: no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = md; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS("(%s)", md_str ? md_str : "default"); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL("(%s)", md_str ? md_str : "default"); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_md_all(void) +{ + int ret = 0; + int i; + + /* Test with default (0) */ + ret |= test_oap_roundtrip_md(0); + + /* Test with all supported digest NIDs */ + for (i = 0; md_supported_nids[i] != NID_undef; i++) + ret |= test_oap_roundtrip_md(md_supported_nids[i]); + + return ret; +} + +/* Timestamp is at offset 16 (after the 16-byte ID) */ +#define OAP_TIMESTAMP_OFFSET 16 +/* Test that packets with outdated timestamps are rejected */ +static int test_oap_outdated_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec old_ts; + uint64_t old_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 30 seconds in the past (> 20s replay timer) */ + clock_gettime(CLOCK_REALTIME, &old_ts); + old_ts.tv_sec -= OAP_REPLAY_TIMER + 10; + old_stamp = hton64(TS_TO_UINT64(old_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &old_stamp, + sizeof(old_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject outdated packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that packets from the future are rejected */ +static int test_oap_future_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec future_ts; + uint64_t future_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 1 second in the future (> 100ms slack) */ + clock_gettime(CLOCK_REALTIME, &future_ts); + future_ts.tv_sec += 1; + future_stamp = hton64(TS_TO_UINT64(future_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &future_stamp, + sizeof(future_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject future packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that replayed packets (same ID + timestamp) are rejected */ +static int test_oap_replay_packet(void) +{ + struct oap_test_ctx ctx; + buffer_t saved_req; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Save the request for replay */ + saved_req.len = ctx.req_hdr.len; + saved_req.data = malloc(saved_req.len); + if (saved_req.data == NULL) { + printf("Failed to allocate saved request.\n"); + goto fail_cleanup; + } + memcpy(saved_req.data, ctx.req_hdr.data, saved_req.len); + + /* First request should succeed */ + if (oap_srv_process_ctx(&ctx) < 0) { + printf("First request should succeed.\n"); + free(saved_req.data); + goto fail_cleanup; + } + + /* Free response from first request before replay */ + freebuf(ctx.resp_hdr); + + /* Restore the saved request for replay */ + freebuf(ctx.req_hdr); + ctx.req_hdr = saved_req; + + /* Replayed request should fail */ + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject replayed packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that client rejects server with wrong certificate name */ +static int test_oap_server_name_mismatch(void) +{ + struct oap_test_ctx ctx; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + /* Set client's expected name to something different from cert name */ + strcpy(ctx.cli.info.name, "wrong.server.name"); + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + /* Client should reject due to name mismatch */ + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client should reject server with wrong cert name.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int oap_test(int argc, + char **argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_oap_auth_init_fini(); + +#ifdef HAVE_OPENSSL + ret |= test_oap_roundtrip_auth_only(); + ret |= test_oap_roundtrip_kex_only(); + ret |= test_oap_piggyback_data(); + + ret |= test_oap_roundtrip_all(); + ret |= test_oap_roundtrip_md_all(); + + ret |= test_oap_corrupted_request(); + ret |= test_oap_corrupted_response(); + ret |= test_oap_truncated_request(); + ret |= test_oap_inflated_length_field(); + ret |= test_oap_deflated_length_field(); + ret |= test_oap_nid_without_kex(); + ret |= test_oap_unsupported_nid(); + + ret |= test_oap_cipher_mismatch(); + + ret |= test_oap_outdated_packet(); + ret |= test_oap_future_packet(); + ret |= test_oap_replay_packet(); + ret |= test_oap_server_name_mismatch(); +#else + (void) test_oap_roundtrip_auth_only; + (void) test_oap_roundtrip_kex_only; + (void) test_oap_piggyback_data; + (void) test_oap_roundtrip; + (void) test_oap_roundtrip_all; + (void) test_oap_roundtrip_md; + (void) test_oap_roundtrip_md_all; + (void) test_oap_corrupted_request; + (void) test_oap_corrupted_response; + (void) test_oap_truncated_request; + (void) test_oap_inflated_length_field; + (void) test_oap_deflated_length_field; + (void) test_oap_nid_without_kex; + (void) test_oap_unsupported_nid; + (void) test_oap_cipher_mismatch; + (void) test_oap_outdated_packet; + (void) test_oap_future_packet; + (void) test_oap_replay_packet; + (void) test_oap_server_name_mismatch; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/irmd/oap/tests/oap_test_pqc.c b/src/irmd/oap/tests/oap_test_pqc.c new file mode 100644 index 00000000..ed51a6b4 --- /dev/null +++ b/src/irmd/oap/tests/oap_test_pqc.c @@ -0,0 +1,363 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests of OAP post-quantum key exchange + * + * Dimitri Staessens <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_pqc.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; +} + +int oap_test_pqc(int argc, + char **argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_PQC + ret |= test_oap_roundtrip_auth_only(); + + ret |= test_oap_roundtrip_kem_all(); + + ret |= test_oap_corrupted_request(); + ret |= test_oap_corrupted_response(); + ret |= test_oap_truncated_request(); +#else + (void) test_oap_roundtrip_auth_only; + (void) test_oap_roundtrip_kem; + (void) test_oap_roundtrip_kem_all; + (void) test_oap_corrupted_request; + (void) test_oap_corrupted_response; + (void) test_oap_truncated_request; + + ret = TEST_RC_SKIP; +#endif + + return ret; +} diff --git a/src/irmd/reg/CMakeLists.txt b/src/irmd/reg/CMakeLists.txt deleted file mode 100644 index ff9d2e99..00000000 --- a/src/irmd/reg/CMakeLists.txt +++ /dev/null @@ -1,7 +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) - -add_subdirectory(tests) diff --git a/src/irmd/reg/flow.c b/src/irmd/reg/flow.c index 4d091b23..52b03e61 100644 --- a/src/irmd/reg/flow.c +++ b/src/irmd/reg/flow.c @@ -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; } @@ -103,22 +103,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 +178,7 @@ int reg_flow_update(struct reg_flow * flow, } flow->info.state = info->state; + flow->info.uid = info->uid; *info = flow->info; diff --git a/src/irmd/reg/flow.h b/src/irmd/reg/flow.h index 75ada971..b671d486 100644 --- a/src/irmd/reg/flow.h +++ b/src/irmd/reg/flow.h @@ -25,24 +25,28 @@ #include <ouroboros/list.h> #include <ouroboros/flow.h> +#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 <sys/types.h> #include <time.h> struct reg_flow { - struct list_head next; + struct list_head next; - struct flow_info info; + struct flow_info info; + int response; - buffer_t data; - struct timespec t0; + buffer_t data; + struct timespec t0; - struct shm_rbuff * n_rb; - struct shm_rbuff * n_1_rb; + char name[NAME_SIZE + 1]; + + struct ssm_rbuff * n_rb; + struct ssm_rbuff * n_1_rb; }; struct reg_flow * reg_flow_create(const struct flow_info * info); diff --git a/src/irmd/reg/ipcp.c b/src/irmd/reg/ipcp.c index 6580cb5b..74ec4939 100644 --- a/src/irmd/reg/ipcp.c +++ b/src/irmd/reg/ipcp.c @@ -40,7 +40,7 @@ struct reg_ipcp * reg_ipcp_create(const struct ipcp_info * info) struct reg_ipcp * ipcp; assert(info != NULL); - assert(info->state == IPCP_BOOT); + assert(info->state == IPCP_INIT); ipcp = malloc(sizeof(*ipcp)); if (ipcp == NULL) { @@ -54,7 +54,7 @@ struct reg_ipcp * reg_ipcp_create(const struct ipcp_info * info) list_head_init(&ipcp->next); ipcp->info = *info; - ipcp->info.state = IPCP_BOOT; + ipcp->info.state = IPCP_INIT; strcpy(ipcp->layer.name, "Not enrolled."); @@ -77,7 +77,6 @@ void reg_ipcp_update(struct reg_ipcp * ipcp, const struct ipcp_info * info) { assert(ipcp != NULL); - assert(info->state != IPCP_INIT); ipcp->info = *info; } @@ -86,7 +85,7 @@ void reg_ipcp_set_layer(struct reg_ipcp * ipcp, const struct layer_info * info) { assert(ipcp != NULL); - assert(ipcp->info.state == IPCP_OPERATIONAL); + assert(ipcp->info.state == IPCP_BOOT); ipcp->layer = *info; } diff --git a/src/irmd/reg/name.c b/src/irmd/reg/name.c index 1ac939a5..4e609711 100644 --- a/src/irmd/reg/name.c +++ b/src/irmd/reg/name.c @@ -66,15 +66,14 @@ struct reg_name * reg_name_create(const struct name_info * info) goto fail_malloc; } + memset(name, 0, sizeof(*name)); + list_head_init(&name->next); - list_head_init(&name->progs); - list_head_init(&name->procs); - list_head_init(&name->active); + list_head_init(&name->progs.list); + list_head_init(&name->procs.list); + list_head_init(&name->active.list); - name->info = *info; - name->n_progs = 0; - name->n_procs = 0; - name->n_active = 0; + name->info = *info; return name; @@ -88,13 +87,13 @@ void reg_name_destroy(struct reg_name * name) assert(list_is_empty(&name->next)); - assert(name->n_progs == 0); - assert(name->n_procs == 0); - assert(name->n_active == 0); + assert(name->progs.len == 0); + assert(name->procs.len == 0); + assert(name->active.len == 0); - assert(list_is_empty(&name->progs)); - assert(list_is_empty(&name->procs)); - assert(list_is_empty(&name->active)); + assert(list_is_empty(&name->progs.list)); + assert(list_is_empty(&name->procs.list)); + assert(list_is_empty(&name->active.list)); free(name); } @@ -107,7 +106,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_for_each(p, &name->active.list) { struct proc_entry * entry; entry = list_entry(p, struct proc_entry, next); if (entry->pid == pid) @@ -123,13 +122,13 @@ 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_for_each_safe(p, h, &name->active.list) { struct proc_entry * entry; entry = list_entry(p, struct proc_entry, next); if (entry->pid == pid) { list_del(&entry->next); free(entry); - name->n_active--; + --name->active.len; } } } @@ -142,7 +141,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_for_each(p, &name->procs.list) { struct proc_entry * entry; entry = list_entry(p, struct proc_entry, next); if (entry->pid == pid) @@ -160,7 +159,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_for_each(p, &name->progs.list) { struct prog_entry * entry; entry = list_entry(p, struct prog_entry, next); if (strcmp(entry->exec[0], prog) == 0) @@ -195,16 +194,16 @@ 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_add_tail(&entry->next, &name->active.list); break; case LB_SPILL: /* Keep accepting flows on the current process */ - list_add(&entry->next, &name->active); + list_add(&entry->next, &name->active.list); break; default: goto fail_unreachable; } - name->n_active++; + ++name->active.len; return 0; @@ -226,19 +225,23 @@ void reg_name_del_active(struct reg_name * name, list_del(&entry->next); - name->n_active--; + --name->active.len; free(entry); } pid_t reg_name_get_active(struct reg_name * name) { + struct proc_entry * e; + assert(name != NULL); - if (list_is_empty(&name->active)) + if (list_is_empty(&name->active.list)) return -1; - return list_first_entry(&name->active, struct proc_entry, next)->pid; + e = list_first_entry(&name->active.list, struct proc_entry, next); + + return e->pid; } int reg_name_add_proc(struct reg_name * name, @@ -259,9 +262,9 @@ int reg_name_add_proc(struct reg_name * name, entry->pid = pid; - list_add(&entry->next, &name->procs); + list_add(&entry->next, &name->procs.list); - name->n_procs++; + ++name->procs.len; return 0; @@ -287,7 +290,7 @@ void reg_name_del_proc(struct reg_name * name, free(entry); - name->n_procs--; + --name->procs.len; assert(__reg_name_get_proc(name, pid) == NULL); } @@ -296,8 +299,7 @@ bool reg_name_has_proc(const struct reg_name * name, pid_t pid) { return __reg_name_get_proc(name, pid) != NULL; -} char ** exec; - +} int reg_name_add_prog(struct reg_name * name, char ** exec) @@ -322,11 +324,11 @@ int reg_name_add_prog(struct reg_name * name, goto fail_exec; } - list_add(&entry->next, &name->progs); + list_add(&entry->next, &name->progs.list); log_dbg("Add prog %s to name %s.", exec[0], name->info.name); - name->n_progs++; + ++name->progs.len; return 0; @@ -352,7 +354,7 @@ void reg_name_del_prog(struct reg_name * name, __free_prog_entry(entry); - name->n_progs--; + --name->progs.len; assert(__reg_name_get_prog(name, prog) == NULL); } @@ -368,8 +370,12 @@ bool reg_name_has_prog(const struct reg_name * name, char ** reg_name_get_exec(const struct reg_name * name) { - if (list_is_empty(&name->progs)) + struct prog_entry * e; + + if (list_is_empty(&name->progs.list)) return NULL; - return list_first_entry(&name->progs, struct prog_entry, next)->exec; + e = list_first_entry(&name->progs.list, struct prog_entry, next); + + return e->exec; } diff --git a/src/irmd/reg/name.h b/src/irmd/reg/name.h index 97ca7f04..30a64e1c 100644 --- a/src/irmd/reg/name.h +++ b/src/irmd/reg/name.h @@ -33,14 +33,25 @@ struct reg_name { struct name_info info; - struct list_head progs; /* autostart programs for this name */ - size_t n_progs; /* number of programs */ - - struct list_head procs; /* processes bound to this name */ - size_t n_procs; /* number of processes */ - - struct list_head active; /* processes actively calling accept */ - size_t n_active; /* number of processes accepting */ + struct { + void * key; + void * crt; + } cache; + + struct { + struct list_head list; + size_t len; + } progs; /* autostart programs for this name */ + + struct { + struct list_head list; + size_t len; + } procs; /* processes bound to this name */ + + struct { + struct list_head list; + size_t len; + } active; /* processes actively calling accept */ }; struct reg_name * reg_name_create(const struct name_info * info); @@ -74,5 +85,4 @@ pid_t reg_name_get_active(struct reg_name * name); void reg_name_del_active(struct reg_name * name, pid_t proc); - #endif /* OUROBOROS_IRMD_REG_NAME_H */ diff --git a/src/irmd/reg/pool.c b/src/irmd/reg/pool.c new file mode 100644 index 00000000..fd983db8 --- /dev/null +++ b/src/irmd/reg/pool.c @@ -0,0 +1,101 @@ +/* + * 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++; + + log_dbg("PUP uid %d refcount++ -> %zu.", pool->uid, pool->refcount); +} + +int reg_pool_unref(struct reg_pool * pool) +{ + assert(pool != NULL); + assert(pool->refcount > 0); + + pool->refcount--; + + log_dbg("PUP uid %d refcount-- -> %zu.", pool->uid, 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..b97dcf2d 100644 --- a/src/irmd/reg/proc.c +++ b/src/irmd/reg/proc.c @@ -25,6 +25,7 @@ #define OUROBOROS_PREFIX "reg/proc" #include <ouroboros/logs.h> +#include <ouroboros/utils.h> #include "proc.h" @@ -75,7 +76,9 @@ 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; @@ -99,7 +102,7 @@ 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); @@ -181,3 +184,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 99f74fef..be4c1161 100644 --- a/src/irmd/reg/proc.h +++ b/src/irmd/reg/proc.h @@ -25,17 +25,17 @@ #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; /* names for which process accepts flows */ - size_t n_names; /* number of names */ + struct list_head names; /* process accepts flows for names */ + size_t n_names; /* number of names */ - struct shm_flow_set * set; + struct ssm_flow_set * set; }; struct reg_proc * reg_proc_create(const struct proc_info * info); @@ -53,4 +53,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/reg.c b/src/irmd/reg/reg.c index d95a4722..e89b492b 100644 --- a/src/irmd/reg/reg.c +++ b/src/irmd/reg/reg.c @@ -28,12 +28,14 @@ The IPC Resource Manager - Registry #include <ouroboros/errno.h> #include <ouroboros/list.h> #include <ouroboros/logs.h> +#include <ouroboros/protobuf.h> #include <ouroboros/pthread.h> #include "reg.h" #include "flow.h" #include "ipcp.h" #include "name.h" +#include "pool.h" #include "proc.h" #include "prog.h" @@ -46,6 +48,7 @@ The IPC Resource Manager - Registry struct { struct bmp * flow_ids; /* flow_ids for flows */ + struct list_head flows; /* flow information */ size_t n_flows; /* number of flows */ @@ -55,6 +58,9 @@ struct { struct list_head names; /* registered names known */ size_t n_names; /* number of names */ + struct list_head pools; /* per-user pools */ + size_t n_pools; /* number of pools */ + struct list_head procs; /* processes */ size_t n_procs; /* number of processes */ @@ -151,16 +157,23 @@ static struct reg_ipcp * __reg_get_ipcp_by_layer(const char * layer) return NULL; } -static struct list_head * __reg_after_ipcp(pid_t pid) + +static struct list_head * __reg_after_ipcp(const struct ipcp_info * info) { struct list_head * p; - assert(pid > 0); + assert(info != NULL); list_for_each(p, ®.ipcps) { struct reg_ipcp * entry; entry = list_entry(p, struct reg_ipcp, next); - if (entry->info.pid > pid) + if (entry->info.type < info->type) + continue; + + if (entry->info.type > info->type) + break; + + if (entry->info.pid > info->pid) break; } @@ -183,41 +196,17 @@ static struct reg_name * __reg_get_name(const char * name) return NULL; } -static struct reg_name * __reg_get_name_by_hash(enum hash_algo algo, - const uint8_t * hash) -{ - struct list_head * p; - uint8_t * thash; - size_t len; - - len = hash_len(algo); - - thash = malloc(len); - if (thash == NULL) - return NULL; - - list_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) { - free(thash); - return n; - } - } - - free(thash); - - return NULL; -} - -static int __reg_get_pending_flow_id_for_hash(enum hash_algo algo, - const uint8_t * hash) +static int __reg_get_pending_flow_id(const char * name) { struct reg_name * entry; struct reg_flow * flow; pid_t pid; - entry =__reg_get_name_by_hash(algo, hash); + assert(name != NULL); + assert(strlen(name) > 0); + assert(strlen(name) < NAME_SIZE + 1); + + entry =__reg_get_name(name); if (entry == NULL) return -ENAME; @@ -226,7 +215,10 @@ static int __reg_get_pending_flow_id_for_hash(enum hash_algo algo, return -EAGAIN; flow = __reg_get_accept_flow(pid); - assert(flow != NULL); + if (flow == NULL) /* compiler barks, this can't be NULL */ + return -EAGAIN; + + strcpy(flow->name, name); return flow->info.id; } @@ -247,6 +239,20 @@ 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; + + list_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; @@ -388,30 +394,17 @@ static struct reg_prog * __reg_get_prog(const char * name) return NULL; } -static char ** __reg_get_exec(enum hash_algo algo, - const uint8_t * hash) +static char ** __reg_get_exec(const char * name) { struct list_head * p; - uint8_t * buf; - - buf = malloc(hash_len(algo)); - if (buf == NULL) { - log_err("Failed to malloc hash buffer."); - return NULL; - } list_for_each(p, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); - str_hash(algo, buf, entry->info.name); - if (memcmp(buf, hash, hash_len(algo)) == 0) { - free(buf); + if (strcmp(entry->info.name, name) == 0) return reg_name_get_exec(entry); - } } - free(buf); - return NULL; } @@ -565,6 +558,7 @@ int reg_init(void) list_head_init(®.flows); list_head_init(®.ipcps); list_head_init(®.names); + list_head_init(®.pools); list_head_init(®.procs); list_head_init(®.progs); list_head_init(®.spawned); @@ -614,6 +608,23 @@ void reg_clear(void) reg.n_procs--; } + list_for_each_safe(p, h, ®.pools) { + struct reg_pool * entry; + entry = list_entry(p, struct reg_pool, next); + list_del(&entry->next); + entry->refcount = 0; /* Force destroy during cleanup */ + reg_pool_destroy(entry); + reg.n_pools--; + } + + list_for_each_safe(p, h, ®.flows) { + struct reg_flow * entry; + entry = list_entry(p, struct reg_flow, next); + list_del(&entry->next); + reg_flow_destroy(entry); + reg.n_flows--; + } + list_for_each_safe(p, h, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); @@ -630,14 +641,6 @@ void reg_clear(void) reg.n_ipcps--; } - list_for_each_safe(p, h, ®.flows) { - struct reg_flow * entry; - entry = list_entry(p, struct reg_flow, next); - list_del(&entry->next); - reg_flow_destroy(entry); - reg.n_flows--; - } - pthread_mutex_unlock(®.mtx); } @@ -646,6 +649,7 @@ void reg_fini(void) assert(list_is_empty(®.spawned)); assert(list_is_empty(®.progs)); assert(list_is_empty(®.procs)); + assert(list_is_empty(®.pools)); assert(list_is_empty(®.names)); assert(list_is_empty(®.ipcps)); assert(list_is_empty(®.flows)); @@ -653,6 +657,7 @@ void reg_fini(void) assert(reg.n_spawned == 0); assert(reg.n_progs == 0); assert(reg.n_procs == 0); + assert(reg.n_pools == 0); assert(reg.n_names == 0); assert(reg.n_ipcps == 0); assert(reg.n_flows == 0); @@ -757,7 +762,7 @@ int reg_create_ipcp(const struct ipcp_info * info) assert(info != NULL); assert(info->pid != 0); - assert(info->state == IPCP_BOOT); + assert(info->state == IPCP_INIT); pthread_mutex_lock(®.mtx); @@ -780,7 +785,7 @@ int reg_create_ipcp(const struct ipcp_info * info) entry->pid = info->pid; - list_add(&ipcp->next, __reg_after_ipcp(info->pid)); + list_add_tail(&ipcp->next, __reg_after_ipcp(info)); list_add(&entry->next, __reg_after_spawned(info->pid)); reg.n_ipcps++; @@ -848,11 +853,11 @@ static int __get_ipcp_info(ipcp_list_msg_t ** msg, (*msg)->name = strdup(ipcp->info.name); if ((*msg)->name == NULL) - goto fail_name; + goto fail_msg; (*msg)->layer = strdup(ipcp->layer.name); if ((*msg)->layer == NULL) - goto fail_layer; + goto fail_msg; (*msg)->pid = ipcp->info.pid; (*msg)->type = ipcp->info.type; @@ -860,10 +865,8 @@ static int __get_ipcp_info(ipcp_list_msg_t ** msg, return 0; - fail_layer: - free((*msg)->name); - fail_name: - free(*msg); + fail_msg: + ipcp_list_msg__free_unpacked(*msg, NULL); *msg = NULL; fail: return -1; @@ -876,10 +879,8 @@ int reg_list_ipcps(ipcp_list_msg_t *** ipcps) pthread_mutex_lock(®.mtx); - if (reg.n_ipcps == 0) { - *ipcps = NULL; + if (reg.n_ipcps == 0) goto finish; - } *ipcps = malloc(reg.n_ipcps * sizeof(**ipcps)); if (*ipcps == NULL) { @@ -890,24 +891,19 @@ int reg_list_ipcps(ipcp_list_msg_t *** ipcps) list_for_each(p, ®.ipcps) { struct reg_ipcp * entry; entry = list_entry(p, struct reg_ipcp, next); - if (__get_ipcp_info(&((*ipcps)[i]), entry) < 0) { - log_err("Failed to create ipcp list info."); + if (__get_ipcp_info(&(*ipcps)[i], entry) < 0) goto fail; - } - ++i; + i++; } - - assert(i == (int) reg.n_ipcps); finish: pthread_mutex_unlock(®.mtx); return i; fail: - while (i > 0) - ipcp_list_msg__free_unpacked((*ipcps)[--i], NULL); - + while (i-- > 0) + ipcp_list_msg__free_unpacked((*ipcps)[i], NULL); free(*ipcps); fail_malloc: pthread_mutex_unlock(®.mtx); @@ -993,28 +989,84 @@ bool reg_has_name(const char * name) return ret; } -static int __get_name_info(name_info_msg_t ** msg, - struct reg_name * n) +int reg_get_name_info(const char * name, + struct name_info * info) { - *msg = malloc(sizeof(**msg)); - if (*msg == NULL) - goto fail; + struct reg_name * n; - name_info_msg__init(*msg); + assert(name != NULL); + assert(info != NULL); - (*msg)->name = strdup(n->info.name); - if ((*msg)->name == NULL) - goto fail_name; + pthread_mutex_lock(®.mtx); + + n = __reg_get_name(name); + if (n == NULL) { + log_err("Name %s does not exist.", name); + goto no_name; + } - (*msg)->pol_lb = n->info.pol_lb; + *info = n->info; + + pthread_mutex_unlock(®.mtx); return 0; - fail_name: - free(*msg); - *msg = NULL; - fail: - return -1; + no_name: + pthread_mutex_unlock(®.mtx); + return -ENOENT; + +} + +int reg_get_name_for_hash(char * buf, + enum hash_algo algo, + const uint8_t * hash) +{ + struct list_head * p; + uint8_t * thash; + size_t len; + char * name = NULL; + + len = hash_len(algo); + + thash = malloc(len); + if (thash == NULL) + return -ENOMEM; + + pthread_mutex_lock(®.mtx); + + list_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) { + name = n->info.name; + break; + } + } + + if (name != NULL) + strcpy(buf, name); + + pthread_mutex_unlock(®.mtx); + + free(thash); + + return name == NULL ? -ENOENT : 0; +} + +int reg_get_name_for_flow_id(char * buf, + int flow_id) +{ + struct reg_flow * f; + + pthread_mutex_lock(®.mtx); + + f = __reg_get_flow(flow_id); + if (f != NULL) + strcpy(buf, f->name); + + pthread_mutex_unlock(®.mtx); + + return f == NULL ? -ENOENT : 0; } int reg_list_names(name_info_msg_t *** names) @@ -1036,24 +1088,31 @@ int reg_list_names(name_info_msg_t *** names) list_for_each(p, ®.names) { struct reg_name * entry; entry = list_entry(p, struct reg_name, next); - if (__get_name_info(&((*names)[i]), entry) < 0) { + (*names)[i] = name_info_s_to_msg(&entry->info); + if ((*names)[i] == NULL) { log_err("Failed to create name list info."); goto fail; } - - ++i; + /* wipe security info to avoid huge messages */ + free((*names)[i]->scrt); + (*names)[i]->scrt = NULL; + free((*names)[i]->skey); + (*names)[i]->skey = NULL; + free((*names)[i]->ccrt); + (*names)[i]->ccrt = NULL; + free((*names)[i]->ckey); + (*names)[i]->ckey = NULL; + + i++; } - - assert(i == (int) reg.n_names); finish: pthread_mutex_unlock(®.mtx); return i; fail: - while (i > 0) - name_info_msg__free_unpacked((*names)[--i], NULL); - + while (i-- > 0) + name_info_msg__free_unpacked((*names)[i], NULL); free(*names); fail_malloc: pthread_mutex_unlock(®.mtx); @@ -1061,6 +1120,35 @@ 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; + } + list_add(&pool->next, ®.pools); + reg.n_pools++; + } + + reg_pool_ref(pool); + + pthread_mutex_unlock(®.mtx); + + return 0; +} + int reg_create_proc(const struct proc_info * info) { struct reg_proc * proc; @@ -1071,13 +1159,13 @@ 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); @@ -1092,7 +1180,7 @@ int reg_create_proc(const struct proc_info * info) return 0; - fail_proc: + fail: pthread_mutex_unlock(®.mtx); return -1; } @@ -1100,6 +1188,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; @@ -1107,11 +1196,18 @@ int reg_destroy_proc(pid_t pid) proc = __reg_get_proc(pid); if (proc != NULL) { + if (!is_ouroboros_member_uid(proc->info.uid)) + pool = __reg_get_pool(proc->info.uid); list_del(&proc->next); reg.n_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) { + list_del(&pool->next); + reg.n_pools--; + reg_pool_destroy(pool); + } } spawn = __reg_get_spawned(pid); @@ -1146,6 +1242,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); @@ -1419,19 +1547,18 @@ bool reg_has_prog(const char * name) return ret; } -int reg_get_exec(enum hash_algo algo, - const uint8_t * hash, - char *** prog) +int reg_get_exec(const char * name, + char *** prog) { char ** exec; int ret = 0; - assert(hash != NULL); + assert(name != NULL); assert(prog != NULL); pthread_mutex_lock(®.mtx); - exec = __reg_get_exec(algo, hash); + exec = __reg_get_exec(name); if (exec == NULL) { ret = -EPERM; goto finish; @@ -1444,12 +1571,9 @@ int reg_get_exec(enum hash_algo algo, goto finish; } - pthread_mutex_unlock(®.mtx); - - return 0; - finish: pthread_mutex_unlock(®.mtx); + return ret; } @@ -1557,8 +1681,7 @@ int reg_set_layer_for_ipcp(struct ipcp_info * info, struct reg_ipcp * ipcp; assert(info != NULL); - assert(info->state > IPCP_BOOT); - assert(info->state < IPCP_SHUTDOWN); + assert(info->state == IPCP_BOOT); pthread_mutex_lock(®.mtx); @@ -1690,7 +1813,7 @@ int reg_wait_flow_allocated(struct flow_info * info, stop = true; break; case FLOW_DEALLOCATED: - ret = -1; + ret = flow->response; stop = true; break; default: @@ -1722,7 +1845,8 @@ int reg_wait_flow_allocated(struct flow_info * info, } int reg_respond_alloc(struct flow_info * info, - buffer_t * pbuf) + buffer_t * pbuf, + int response) { struct reg_flow * flow; @@ -1755,7 +1879,9 @@ int reg_respond_alloc(struct flow_info * info, if (reg_flow_update(flow, info) < 0) { log_err("Failed to create flow structs."); goto fail_flow; - }; + } + + flow->response = response; if (info->state == FLOW_ALLOCATED) reg_flow_set_data(flow, pbuf); @@ -1771,8 +1897,7 @@ int reg_respond_alloc(struct flow_info * info, return -1; } -int reg_prepare_flow_accept(struct flow_info * info, - buffer_t * pbuf) +int reg_prepare_flow_accept(struct flow_info * info) { struct reg_flow * flow; int ret; @@ -1790,7 +1915,7 @@ int reg_prepare_flow_accept(struct flow_info * info, ret = reg_flow_update(flow, info); - reg_flow_set_data(flow, pbuf); + pthread_cond_broadcast(®.cond); pthread_mutex_unlock(®.mtx); @@ -1824,8 +1949,6 @@ int reg_wait_flow_accepted(struct flow_info * info, assert(flow != NULL); assert(info->id == flow->info.id); assert(info->n_pid == flow->info.n_pid); - assert(info->state == flow->info.state); - assert(flow->info.state == FLOW_ACCEPT_PENDING); if (__reg_add_active_proc(info->n_pid) < 0) { log_err("Failed to mark pid %d active.", info->n_pid); @@ -1883,13 +2006,12 @@ int reg_wait_flow_accepted(struct flow_info * info, return -1; } -int reg_wait_flow_accepting(enum hash_algo algo, - const uint8_t * hash, +int reg_wait_flow_accepting(const char * name, const struct timespec * abstime) { int ret; - assert(hash != NULL); + assert(name != NULL); assert(abstime != NULL); pthread_mutex_lock(®.mtx); @@ -1897,7 +2019,7 @@ int reg_wait_flow_accepting(enum hash_algo algo, pthread_cleanup_push(__cleanup_mutex_unlock, ®.mtx); while (true) { - ret = __reg_get_pending_flow_id_for_hash(algo, hash); + ret = __reg_get_pending_flow_id(name); if (ret != -EAGAIN) break; @@ -1915,7 +2037,6 @@ int reg_respond_accept(struct flow_info * info, buffer_t * pbuf) { struct reg_flow * flow; - buffer_t temp; assert(info != NULL); assert(info->state == FLOW_ALLOCATED); @@ -1933,11 +2054,8 @@ int reg_respond_accept(struct flow_info * info, info->n_pid = flow->info.n_pid; - if (info->qs.cypher_s > 0) { - reg_flow_get_data(flow, &temp); - reg_flow_set_data(flow, pbuf); - *pbuf = temp; - } + reg_flow_set_data(flow, pbuf); + clrbuf(pbuf); if (reg_flow_update(flow, info) < 0) { log_err("Failed to create flow structs."); @@ -1970,12 +2088,14 @@ void reg_dealloc_flow(struct flow_info * info) assert(flow != NULL); assert(flow->data.data == NULL); assert(flow->data.len == 0); - 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; + memset(flow->name, 0, sizeof(flow->name)); + reg_flow_update(flow, info); pthread_mutex_unlock(®.mtx); @@ -2043,7 +2163,7 @@ int reg_wait_ipcp_boot(struct ipcp_info * info, int ret; bool stop = false; - assert(info->state == IPCP_BOOT); + assert(info->state == IPCP_INIT); pthread_mutex_lock(®.mtx); @@ -2063,16 +2183,18 @@ int reg_wait_ipcp_boot(struct ipcp_info * info, ret = -1; stop = true; break; + case IPCP_BOOT: + /* FALLTHRU*/ case IPCP_OPERATIONAL: ret = 0; stop = true; break; - case IPCP_BOOT: + case IPCP_INIT: ret = -__timedwait(®.cond, ®.mtx, abstime); break; default: assert(false); - continue; /* Shut up static analyzer. */ + break; /* Shut up static analyzer. */ } ipcp = __reg_get_ipcp(info->pid); diff --git a/src/irmd/reg/reg.h b/src/irmd/reg/reg.h index 17dfcc32..77264fde 100644 --- a/src/irmd/reg/reg.h +++ b/src/irmd/reg/reg.h @@ -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); @@ -90,6 +99,16 @@ int reg_destroy_name(const char * name); bool reg_has_name(const char * name); +int reg_get_name_info(const char * name, + struct name_info * info); + +int reg_get_name_for_hash(char * buf, + enum hash_algo algo, + const uint8_t * hash); + +int reg_get_name_for_flow_id(char * buf, + int flow_id); + /* TODO don't rely on protobuf here */ int reg_list_names(name_info_msg_t *** names); @@ -99,9 +118,8 @@ int reg_destroy_prog(const char * name); bool reg_has_prog(const char * name); -int reg_get_exec(enum hash_algo algo, - const uint8_t * hash, - char *** exec); +int reg_get_exec(const char * name, + char *** exec); int reg_bind_prog(const char * name, char ** exec, @@ -117,17 +135,16 @@ int reg_wait_flow_allocated(struct flow_info * info, const struct timespec * abstime); int reg_respond_alloc(struct flow_info * info, - buffer_t * pbuf); + buffer_t * pbuf, + int response); -int reg_prepare_flow_accept(struct flow_info * info, - buffer_t * pbuf); +int reg_prepare_flow_accept(struct flow_info * info); int reg_wait_flow_accepted(struct flow_info * info, buffer_t * pbuf, const struct timespec * abstime); -int reg_wait_flow_accepting(enum hash_algo algo, - const uint8_t * hash, +int reg_wait_flow_accepting(const char * name, const struct timespec * abstime); int reg_respond_accept(struct flow_info * info, diff --git a/src/irmd/reg/tests/CMakeLists.txt b/src/irmd/reg/tests/CMakeLists.txt index bc1354ed..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,19 +13,21 @@ create_test_sourcelist(${src_folder}_tests test_suite.c reg_test.c ) -add_executable(${src_folder}_test EXCLUDE_FROM_ALL ${${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}) -remove(tests_to_run test_suite.c) +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 f9d23fd1..2066c811 100644 --- a/src/irmd/reg/tests/flow_test.c +++ b/src/irmd/reg/tests/flow_test.c @@ -22,13 +22,13 @@ #include "../flow.c" -#include <ouroboros/test.h> +#include <test/test.h> #include <string.h> #define TEST_DATA "testpiggybackdata" -static int test_reg_flow_create(void) +static int test_reg_flow_create_destroy(void) { struct reg_flow * f; @@ -51,10 +51,10 @@ static int test_reg_flow_create(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_flow_create_no_id(void) { @@ -67,7 +67,7 @@ static int test_reg_flow_create_no_id(void) { reg_flow_create(&info); /* assert fail */ - return 0; + return TEST_RC_SUCCESS; } static int test_reg_flow_create_no_pid(void) { @@ -80,7 +80,7 @@ static int test_reg_flow_create_no_pid(void) { reg_flow_create(&info); /* assert fail */ - return 0; + return TEST_RC_SUCCESS; } static int test_reg_flow_create_has_n_1_pid(void) { @@ -94,7 +94,7 @@ static int test_reg_flow_create_has_n_1_pid(void) { reg_flow_create(&info); /* assert fail */ - return 0; + return TEST_RC_SUCCESS; } static int test_reg_flow_create_wrong_state(void) { @@ -108,7 +108,7 @@ static int test_reg_flow_create_wrong_state(void) { reg_flow_create(&info); /* assert fail */ - return 0; + return TEST_RC_SUCCESS; } static int test_reg_flow_create_has_mpl(void) { @@ -123,7 +123,7 @@ static int test_reg_flow_create_has_mpl(void) { reg_flow_create(&info); /* assert fail */ - return 0; + return TEST_RC_SUCCESS; } static int test_reg_flow_update(void) @@ -163,10 +163,10 @@ static int test_reg_flow_update(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_flow_update_wrong_id(void) @@ -199,10 +199,10 @@ static int test_reg_flow_update_wrong_id(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_flow_assert_fails(void) @@ -210,15 +210,10 @@ static int test_reg_flow_assert_fails(void) int ret = 0; ret |= test_assert_fail(test_reg_flow_create_no_id); - ret |= test_assert_fail(test_reg_flow_create_no_pid); - ret |= test_assert_fail(test_reg_flow_create_has_n_1_pid); - ret |= test_assert_fail(test_reg_flow_create_wrong_state); - ret |= test_assert_fail(test_reg_flow_create_has_mpl); - ret |= test_assert_fail(test_reg_flow_update_wrong_id); return ret; @@ -237,7 +232,7 @@ static int test_flow_data(void) char * data; buffer_t buf; - buffer_t rcv = {NULL, 0}; + buffer_t rcv = {0, NULL}; TEST_START(); @@ -267,11 +262,11 @@ static int test_flow_data(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: free(data); TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } int flow_test(int argc, @@ -282,12 +277,9 @@ int flow_test(int argc, (void) argc; (void) argv; - ret |= test_reg_flow_create(); - + 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 fb8ba71b..6ab6443d 100644 --- a/src/irmd/reg/tests/ipcp_test.c +++ b/src/irmd/reg/tests/ipcp_test.c @@ -20,7 +20,7 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#include <ouroboros/test.h> +#include <test/test.h> #include "../ipcp.c" @@ -31,7 +31,7 @@ static int test_reg_ipcp_create(void) struct reg_ipcp * ipcp; struct ipcp_info info = { .pid = TEST_PID, - .state = IPCP_BOOT + .state = IPCP_INIT }; struct layer_info layer = { .name = "testlayer", @@ -51,7 +51,7 @@ static int test_reg_ipcp_create(void) goto fail; } - ipcp->info.state = IPCP_OPERATIONAL; + ipcp->info.state = IPCP_BOOT; reg_ipcp_set_layer(ipcp, &layer); @@ -60,11 +60,6 @@ static int test_reg_ipcp_create(void) goto fail; } - if (ipcp->info.state != IPCP_OPERATIONAL) { - printf("IPCP state was not set.\n"); - goto fail; - } - reg_ipcp_destroy(ipcp); TEST_SUCCESS(); diff --git a/src/irmd/reg/tests/name_test.c b/src/irmd/reg/tests/name_test.c index 48f132e9..5b42875e 100644 --- a/src/irmd/reg/tests/name_test.c +++ b/src/irmd/reg/tests/name_test.c @@ -20,8 +20,11 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ + #include "../name.c" +#include <test/test.h> + #define TEST_PID 65534 #define TEST_PROG "/usr/bin/testprog" #define TEST_NAME "testservicename" @@ -34,6 +37,8 @@ static int test_reg_name_create(void) .pol_lb = LB_RR, }; + TEST_START(); + n = reg_name_create(&info); if (n == NULL) { printf("Failed to create name %s.\n", info.name); @@ -42,9 +47,12 @@ static int test_reg_name_create(void) reg_name_destroy(n); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } static int test_reg_name_add_proc(void) @@ -55,6 +63,8 @@ static int test_reg_name_add_proc(void) .pol_lb = LB_RR, }; + TEST_START(); + n = reg_name_create(&info); if (n == NULL) { printf("Failed to create name %s.\n", info.name); @@ -66,8 +76,8 @@ static int test_reg_name_add_proc(void) goto fail; } - if (n->n_procs != 1) { - printf("n_procs not updated.\n"); + if (n->procs.len != 1) { + printf("Proc not added to list.\n"); goto fail; } @@ -78,16 +88,19 @@ static int test_reg_name_add_proc(void) reg_name_del_proc(n, TEST_PID); - if (n->n_procs != 0) { - printf("n_procs not updated.\n"); + if (n->procs.len != 0) { + printf("Proc not removed from list.\n"); goto fail; } reg_name_destroy(n); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } static int test_reg_name_add_prog(void) @@ -100,6 +113,8 @@ static int test_reg_name_add_prog(void) char * exec[] = { TEST_PROG, "--argswitch", "argvalue", NULL}; + TEST_START(); + n = reg_name_create(&info); if (n == NULL) { printf("Failed to create name %s.\n", info.name); @@ -111,8 +126,8 @@ static int test_reg_name_add_prog(void) goto fail; } - if (n->n_progs != 1) { - printf("n_progs not updated.\n"); + if (n->progs.len != 1) { + printf("Prog not added to list.\n"); goto fail; } @@ -123,16 +138,19 @@ static int test_reg_name_add_prog(void) reg_name_del_prog(n, TEST_PROG); - if (n->n_progs != 0) { - printf("n_progs not updated.\n"); + if (n->progs.len != 0) { + printf("Prog not removed from list.\n"); goto fail; } reg_name_destroy(n); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } static int test_reg_name_add_active(enum pol_balance lb) @@ -144,6 +162,8 @@ static int test_reg_name_add_active(enum pol_balance lb) .pol_lb = lb, }; + TEST_START(); + n = reg_name_create(&info); if (n == NULL) { printf("Failed to create name %s.\n", info.name); @@ -175,8 +195,8 @@ static int test_reg_name_add_active(enum pol_balance lb) goto fail; } - if (n->n_active != 1) { - printf("n_active not updated.\n"); + if (n->active.len != 1) { + printf("Active list not updated.\n"); goto fail; } @@ -206,13 +226,13 @@ static int test_reg_name_add_active(enum pol_balance lb) goto fail; } - if (n->n_procs != 3) { - printf("n_procs not updated.\n"); + if (n->procs.len != 3) { + printf("Procs list not updated.\n"); goto fail; } - if (n->n_active != 4) { - printf("n_active not updated.\n"); + if (n->active.len != 4) { + printf("Active list not updated.\n"); goto fail; } @@ -243,41 +263,39 @@ static int test_reg_name_add_active(enum pol_balance lb) reg_name_del_proc(n, TEST_PID); - if (n->n_procs != 0) { - printf("n_procs not updated.\n"); + if (n->procs.len != 0) { + printf("Procs list not cleared.\n"); goto fail; } - if (n->n_active != 0) { - printf("n_active not updated.\n"); + if (n->active.len != 0) { + printf("Active list not cleared.\n"); goto fail; } reg_name_destroy(n); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } - int name_test(int argc, char ** argv) { - int res = 0; + int rc = 0; (void) argc; (void) argv; - res |= test_reg_name_create(); - - res |= test_reg_name_add_proc(); - - res |= test_reg_name_add_prog(); - - res |= test_reg_name_add_active(LB_RR); - - res |= test_reg_name_add_active(LB_SPILL); + rc |= test_reg_name_create(); + rc |= test_reg_name_add_proc(); + rc |= test_reg_name_add_prog(); + rc |= test_reg_name_add_active(LB_RR); + rc |= test_reg_name_add_active(LB_SPILL); - return res; + return rc; } diff --git a/src/irmd/reg/tests/proc_test.c b/src/irmd/reg/tests/proc_test.c index 5c9dd865..c4e689f0 100644 --- a/src/irmd/reg/tests/proc_test.c +++ b/src/irmd/reg/tests/proc_test.c @@ -22,16 +22,24 @@ #include "../proc.c" +#include <test/test.h> + #define TEST_PID 65534 #define TEST_PROG "usr/bin/testprog" -static int test_reg_proc_create(void) +#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(); proc = reg_proc_create(&info); if (proc == NULL) { @@ -41,21 +49,23 @@ static int test_reg_proc_create(void) reg_proc_destroy(proc); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } 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"; + TEST_START(); + proc = reg_proc_create(&info); if (proc == NULL) { printf("Failed to create proc.\n"); @@ -86,9 +96,12 @@ static int test_reg_proc_add_name(void) reg_proc_destroy(proc); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } int proc_test(int argc, @@ -99,8 +112,7 @@ int proc_test(int argc, (void) argc; (void) argv; - res |= test_reg_proc_create(); - + res |= test_reg_proc_create_destroy(); res |= test_reg_proc_add_name(); return res; diff --git a/src/irmd/reg/tests/prog_test.c b/src/irmd/reg/tests/prog_test.c index 5e6931d8..3900e7d7 100644 --- a/src/irmd/reg/tests/prog_test.c +++ b/src/irmd/reg/tests/prog_test.c @@ -22,8 +22,9 @@ #include "../prog.c" -#define TEST_PROG "usr/bin/testprog" +#include <test/test.h> +#define TEST_PROG "usr/bin/testprog" static int test_reg_prog_create(void) { @@ -32,6 +33,8 @@ static int test_reg_prog_create(void) .name = TEST_PROG }; + TEST_START(); + prog = reg_prog_create(&info); if (prog == NULL) { printf("Failed to create prog.\n"); @@ -40,9 +43,12 @@ static int test_reg_prog_create(void) reg_prog_destroy(prog); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } static int test_reg_prog_add_name(void) @@ -54,6 +60,8 @@ static int test_reg_prog_add_name(void) char * name = "testname"; + TEST_START(); + prog = reg_prog_create(&info); if (prog == NULL) { printf("Failed to create prog.\n"); @@ -84,9 +92,12 @@ static int test_reg_prog_add_name(void) reg_prog_destroy(prog); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: - return -1; + TEST_FAIL(); + return TEST_RC_FAIL; } int prog_test(int argc, @@ -98,7 +109,6 @@ int prog_test(int argc, (void) argv; ret |= test_reg_prog_create(); - ret |= test_reg_prog_add_name(); return ret; diff --git a/src/irmd/reg/tests/reg_test.c b/src/irmd/reg/tests/reg_test.c index c341c297..f7a4de8e 100644 --- a/src/irmd/reg/tests/reg_test.c +++ b/src/irmd/reg/tests/reg_test.c @@ -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)); } while(0) + do { TEST_FAIL(); reg_clear(); return TEST_RC_FAIL;} while(0) static int test_reg_init(void) { @@ -51,10 +59,10 @@ static int test_reg_init(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_create_flow(void) @@ -105,18 +113,17 @@ static int test_reg_create_flow(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_allocate_flow_timeout(void) { struct timespec abstime; struct timespec timeo = TIMESPEC_INIT_MS(1); - buffer_t pbuf; - buffer_t rbuf = {NULL, 0}; + buffer_t rbuf = BUF_INIT; struct flow_info info = { .n_pid = TEST_PID, @@ -125,14 +132,6 @@ static int test_reg_allocate_flow_timeout(void) TEST_START(); - pbuf.data = (uint8_t *) strdup(TEST_DATA);; - if (pbuf.data == NULL) { - printf("Failed to strdup data.\n"); - goto fail; - } - - pbuf.len = strlen((char *) pbuf.data) + 1; - clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); @@ -147,7 +146,7 @@ static int test_reg_allocate_flow_timeout(void) goto fail; } - if (reg_prepare_flow_accept(&info, &pbuf) < 0) { + if (reg_prepare_flow_accept(&info) < 0) { printf("Failed to prepare flow for accept.\n"); goto fail; } @@ -162,12 +161,6 @@ static int test_reg_allocate_flow_timeout(void) goto fail; } - if (pbuf.data == NULL) { - printf("Flow data was updated on timeout."); - goto fail; - } - - freebuf(pbuf); reg_destroy_flow(info.id); if (reg.n_flows != 0) { @@ -179,16 +172,19 @@ static int test_reg_allocate_flow_timeout(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static void * test_flow_respond_alloc(void * o) { struct flow_info * info = (struct flow_info *) o; - buffer_t pbuf = {NULL, 0}; + buffer_t pbuf = BUF_INIT; + int response; + + response = (info->state == FLOW_ALLOCATED) ? 0 : -1; if (info->state == FLOW_ALLOCATED) { pbuf.data = (uint8_t *) strdup(TEST_DATA2); @@ -199,7 +195,7 @@ static void * test_flow_respond_alloc(void * o) pbuf.len = strlen((char *) pbuf.data) + 1; } - reg_respond_alloc(info, &pbuf); + reg_respond_alloc(info, &pbuf, response); return (void *) 0; fail: @@ -220,13 +216,6 @@ static void * test_flow_respond_accept(void * o) reg_respond_accept(info, &pbuf); - if (info->qs.cypher_s == 0) { - freebuf(pbuf); - } else if (strcmp((char *) pbuf.data, TEST_DATA) != 0) { - printf("Data was not passed correctly.\n"); - goto fail; - } - return (void *) 0; fail: return (void *) -1; @@ -237,8 +226,7 @@ static int test_reg_accept_flow_success(void) pthread_t thr; struct timespec abstime; struct timespec timeo = TIMESPEC_INIT_S(1); - buffer_t pbuf = {(uint8_t *) TEST_DATA, strlen(TEST_DATA)}; - buffer_t rbuf = {NULL, 0}; + buffer_t rbuf = BUF_INIT; struct flow_info info = { .n_pid = TEST_PID, @@ -247,7 +235,7 @@ static int test_reg_accept_flow_success(void) struct flow_info n_1_info = { .n_1_pid = TEST_N_1_PID, - .qs = qos_data_crypt, + .qs = qos_data, .state = FLOW_ALLOCATED /* RESPONSE SUCCESS */ }; @@ -267,7 +255,7 @@ static int test_reg_accept_flow_success(void) goto fail; } - if (reg_prepare_flow_accept(&info, &pbuf) < 0) { + if (reg_prepare_flow_accept(&info) < 0) { printf("Failed to prepare flow for accept.\n"); goto fail; } @@ -277,8 +265,11 @@ static int test_reg_accept_flow_success(void) pthread_create(&thr, NULL, test_flow_respond_accept, &n_1_info); - if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0 ) { + 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; } @@ -321,10 +312,10 @@ static int test_reg_accept_flow_success(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_accept_flow_success_no_crypt(void) @@ -332,8 +323,7 @@ static int test_reg_accept_flow_success_no_crypt(void) pthread_t thr; struct timespec abstime; struct timespec timeo = TIMESPEC_INIT_S(1); - buffer_t pbuf = {(uint8_t *) TEST_DATA, strlen(TEST_DATA)}; - buffer_t rbuf = {NULL, 0}; + buffer_t rbuf = BUF_INIT; struct flow_info info = { .n_pid = TEST_PID, @@ -362,7 +352,7 @@ static int test_reg_accept_flow_success_no_crypt(void) goto fail; } - if (reg_prepare_flow_accept(&info, &pbuf) < 0) { + if (reg_prepare_flow_accept(&info) < 0) { printf("Failed to prepare flow for accept.\n"); goto fail; } @@ -374,6 +364,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; } @@ -389,10 +382,7 @@ static int test_reg_accept_flow_success_no_crypt(void) goto fail; } - if (strcmp((char *) rbuf.data, TEST_DATA) != 0) { - printf("Data was updated.\n"); - goto fail; - } + freebuf(rbuf); n_1_info.state = FLOW_DEALLOCATED; @@ -416,16 +406,16 @@ static int test_reg_accept_flow_success_no_crypt(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_allocate_flow_fail(void) { - buffer_t buf = {NULL, 0}; + buffer_t buf = BUF_INIT; pthread_t thr; struct timespec abstime; struct timespec timeo = TIMESPEC_INIT_S(1); @@ -470,6 +460,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; } @@ -486,26 +479,22 @@ static int test_reg_allocate_flow_fail(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_flow(void) { - int ret = 0; - - ret |= test_reg_create_flow(); + int rc = 0; - ret |= test_reg_allocate_flow_timeout(); + rc |= test_reg_create_flow(); + rc |= test_reg_allocate_flow_timeout(); + rc |= test_reg_accept_flow_success(); + rc |= test_reg_accept_flow_success_no_crypt(); + rc |= test_reg_allocate_flow_fail(); - ret |= test_reg_accept_flow_success(); - - ret |= test_reg_accept_flow_success_no_crypt(); - - ret |= test_reg_allocate_flow_fail(); - - return ret; + return rc; } static int test_reg_create_ipcp(void) @@ -513,7 +502,7 @@ static int test_reg_create_ipcp(void) struct ipcp_info info = { .name = TEST_IPCP, .pid = TEST_PID, - .state = IPCP_BOOT /* set by spawn_ipcp */ + .state = IPCP_INIT /* set by spawn_ipcp */ }; TEST_START(); @@ -552,10 +541,130 @@ static int test_reg_create_ipcp(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_list_ipcps(void) +{ + ipcp_list_msg_t ** ipcps; + int i; + ssize_t len; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + for (i = 0; i < 10; i++) { + struct ipcp_info info = { + .pid = TEST_PID + i, + .state = IPCP_INIT /* set by spawn_ipcp */ + }; + + sprintf(info.name, "%s%d", TEST_IPCP, i); + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp %d.\n", i); + goto fail; + } + } + + len = reg_list_ipcps(&ipcps); + if (len < 0) { + printf("Failed to list ipcps.\n"); + goto fail; + } + + if (len != 10) { + printf("Failed to list all ipcps.\n"); + goto fail; + } + + while (len-- > 0) + ipcp_list_msg__free_unpacked(ipcps[len], NULL); + free(ipcps); + + for (i = 0; i < 10; i++) + reg_destroy_proc(TEST_PID + i); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; +} + +static int test_insert_ipcps(void) +{ + ipcp_list_msg_t ** ipcps; + struct ipcp_info info; + size_t i; + size_t len; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + for (i = 0; i < 100; i++) { + sprintf(info.name, "%s-%zd", TEST_IPCP, i); + info.pid = TEST_PID + rand() % 10000; + info.type = rand() % IPCP_INVALID; + info.state = IPCP_INIT; /* set by spawn_ipcp */ + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp %s.\n", info.name); + goto fail; + } + } + + len = reg_list_ipcps(&ipcps); + if (len != 100) { + printf("Failed to list all ipcps.\n"); + goto fail; + } + + for (i = 1; i < len; i++) { + if (ipcps[i]->type < ipcps[i - 1]->type) { + printf("IPCPS not sorted by type.\n"); + goto fail; + } + + if (ipcps[i]->type != ipcps[i - 1]->type) + continue; + + /* allow occasional duplicate PID in test */ + if (ipcps[i]->pid < ipcps[i - 1]->pid) { + printf("IPCPS not sorted by pid.\n"); + goto fail; + } + } + + while (len-- > 0) + ipcp_list_msg__free_unpacked(ipcps[len], NULL); + free(ipcps); + + reg_clear(); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; +fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; } static int test_set_layer(void) @@ -564,7 +673,7 @@ static int test_set_layer(void) struct ipcp_info info = { .name = TEST_IPCP, .pid = TEST_PID, - .state = IPCP_BOOT /* set by spawn_ipcp */ + .state = IPCP_INIT /* set by spawn_ipcp */ }; struct layer_info layer = { .name = TEST_LAYER, @@ -588,8 +697,9 @@ static int test_set_layer(void) } ipcp = __reg_get_ipcp(info.pid); - ipcp->info.state = IPCP_OPERATIONAL; - info.state = IPCP_ENROLLED; + + ipcp->info.state = IPCP_BOOT; + info.state = IPCP_BOOT; reg_set_layer_for_ipcp(&info, &layer); @@ -614,21 +724,22 @@ static int test_set_layer(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_ipcp(void) { - int ret = 0; + int rc = 0; - ret |= test_reg_create_ipcp(); + rc |= test_reg_create_ipcp(); + rc |= test_reg_list_ipcps(); + rc |= test_insert_ipcps(); + rc |= test_set_layer(); - ret |= test_set_layer(); - - return ret; + return rc; } static int test_reg_create_name(void) @@ -674,27 +785,82 @@ static int test_reg_create_name(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_list_names(void) +{ + name_info_msg_t ** names; + int i; + ssize_t len; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + for (i = 0; i < 10; i++) { + struct name_info info = { + .pol_lb = LB_RR + }; + + sprintf(info.name, "%s%d", TEST_NAME, i); + + if (reg_create_name(&info) < 0) { + printf("Failed to create name %d.\n", i); + goto fail; + } + } + + len = reg_list_names(&names); + if (len < 0) { + printf("Failed to list names.\n"); + goto fail; + } + + if (len != 10) { + printf("Failed to list all names.\n"); + goto fail; + } + + for (i = 0; i < len; i++) + name_info_msg__free_unpacked(names[i], NULL); + free(names); + + for (i = 0; i < 10; i++) { + char name[NAME_MAX]; + sprintf(name, "%s%d", TEST_NAME, i); + reg_destroy_name(name); + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_name(void) { - int ret = 0; + int rc = 0; - ret |= test_reg_create_name(); + rc |= test_reg_create_name(); + rc |= test_reg_list_names(); - return ret; + return rc; } 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(); @@ -732,19 +898,19 @@ static int test_reg_create_proc(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_proc(void) { - int ret = 0; + int rc = 0; - ret |= test_reg_create_proc(); + rc |= test_reg_create_proc(); - return ret; + return rc; } static int test_reg_spawned(void) @@ -785,10 +951,10 @@ static int test_reg_spawned(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_create_prog(void) @@ -833,27 +999,24 @@ static int test_reg_create_prog(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_reg_prog(void) { - int ret = 0; + int rc = 0; - ret |= test_reg_create_prog(); + rc |= test_reg_create_prog(); - return ret; + return rc; } 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, @@ -900,10 +1063,10 @@ static int test_bind_proc(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_bind_prog(void) @@ -989,10 +1152,10 @@ static int test_bind_prog(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_inherit_prog(void) @@ -1006,10 +1169,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}; @@ -1060,10 +1220,10 @@ static int test_inherit_prog(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_wait_accepting_timeout(void) @@ -1071,7 +1231,6 @@ static int test_wait_accepting_timeout(void) struct timespec abstime; struct timespec timeo = TIMESPEC_INIT_MS(1); int flow_id; - uint8_t hash[64]; struct name_info ninfo = { .name = TEST_NAME, .pol_lb = LB_RR @@ -1089,12 +1248,10 @@ static int test_wait_accepting_timeout(void) goto fail; } - str_hash(HASH_SHA3_256, hash, ninfo.name); - clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - flow_id = reg_wait_flow_accepting(HASH_SHA3_256, hash, &abstime); + flow_id = reg_wait_flow_accepting(ninfo.name, &abstime); if (flow_id != -ETIMEDOUT) { printf("Wait accept did not time out: %d.\n", flow_id); goto fail; @@ -1106,10 +1263,10 @@ static int test_wait_accepting_timeout(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_wait_accepting_fail_name(void) @@ -1117,7 +1274,6 @@ static int test_wait_accepting_fail_name(void) struct timespec abstime; struct timespec timeo = TIMESPEC_INIT_S(1); int flow_id; - uint8_t hash[64]; TEST_START(); @@ -1128,11 +1284,10 @@ static int test_wait_accepting_fail_name(void) clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - str_hash(HASH_SHA3_256, hash, "C0FF33"); - flow_id = reg_wait_flow_accepting(HASH_SHA3_256, hash, &abstime); + flow_id = reg_wait_flow_accepting(TEST_NAME, &abstime); if (flow_id != -ENAME) { - printf("Wait accept did not fail on name: %d.\n", flow_id); + printf("Wait accept did not fail: %d.\n", flow_id); goto fail; } @@ -1140,22 +1295,19 @@ static int test_wait_accepting_fail_name(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static void * test_call_flow_accept(void * o) { struct timespec abstime; - struct timespec timeo = TIMESPEC_INIT_MS(1); - buffer_t pbuf = {NULL, 0}; + 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, @@ -1179,16 +1331,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, &pbuf); - 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); @@ -1200,15 +1357,15 @@ 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); - int flow_id; + struct timespec timeo = TIMESPEC_INIT_S(10); pthread_t thr; - uint8_t hash[64]; + int flow_id; struct name_info ninfo = { .name = TEST_NAME, .pol_lb = LB_RR }; + TEST_START(); if (reg_init()) { @@ -1226,11 +1383,12 @@ static int test_wait_accepting_success(void) clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - str_hash(HASH_SHA3_256, hash, ninfo.name); - - flow_id = reg_wait_flow_accepting(HASH_SHA3_256, hash, &abstime); + 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; } @@ -1242,23 +1400,21 @@ static int test_wait_accepting_success(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_wait_accepting(void) { - int ret = 0; - - ret |= test_wait_accepting_timeout(); - - ret |= test_wait_accepting_fail_name(); + int rc = 0; - ret |= test_wait_accepting_success(); + rc |= test_wait_accepting_timeout(); + rc |= test_wait_accepting_fail_name(); + rc |= test_wait_accepting_success(); - return ret; + return rc; } static int test_wait_ipcp_boot_timeout(void) @@ -1268,7 +1424,7 @@ static int test_wait_ipcp_boot_timeout(void) struct ipcp_info info = { .name = TEST_IPCP, .pid = TEST_PID, - .state = IPCP_BOOT /* set by spawn_ipcp */ + .state = IPCP_INIT /* set by spawn_ipcp */ }; TEST_START(); @@ -1300,10 +1456,10 @@ static int test_wait_ipcp_boot_timeout(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static void * test_ipcp_respond(void * o) @@ -1318,12 +1474,12 @@ 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, .pid = TEST_PID, - .state = IPCP_BOOT /* set by spawn_ipcp */ + .state = IPCP_INIT /* set by spawn_ipcp */ }; struct ipcp_info resp_info = { .name = TEST_IPCP, @@ -1348,10 +1504,13 @@ static int test_wait_ipcp_boot_fail(void) clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - info.state = IPCP_BOOT; + info.state = IPCP_INIT; 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; } @@ -1371,21 +1530,21 @@ static int test_wait_ipcp_boot_fail(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } 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, - .state = IPCP_BOOT /* set by spawn_ipcp */ + .state = IPCP_INIT /* set by spawn_ipcp */ }; struct ipcp_info resp_info = { .name = TEST_IPCP, @@ -1410,10 +1569,13 @@ static int test_wait_ipcp_boot_success(void) clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeo, &abstime); - info.state = IPCP_BOOT; + info.state = IPCP_INIT; 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; } @@ -1421,6 +1583,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; } @@ -1433,23 +1597,21 @@ static int test_wait_ipcp_boot_success(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_wait_ipcp_boot(void) { - int ret = 0; - - ret |= test_wait_ipcp_boot_timeout(); + int rc = 0; - ret |= test_wait_ipcp_boot_fail(); + rc |= test_wait_ipcp_boot_timeout(); + rc |= test_wait_ipcp_boot_fail(); + rc |= test_wait_ipcp_boot_success(); - ret |= test_wait_ipcp_boot_success(); - - return ret; + return rc; } static int test_wait_proc_timeout(void) @@ -1477,10 +1639,10 @@ static int test_wait_proc_timeout(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static void * test_proc(void * o) @@ -1495,12 +1657,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(); @@ -1516,6 +1675,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; } @@ -1527,57 +1689,43 @@ static int test_wait_proc_success(void) TEST_SUCCESS(); - return 0; + return TEST_RC_SUCCESS; fail: REG_TEST_FAIL(); - return -1; + return TEST_RC_FAIL; } static int test_wait_proc(void) { - int ret = 0; + int rc = 0; - ret |= test_wait_proc_timeout(); + rc |= test_wait_proc_timeout(); + rc |= test_wait_proc_success(); - ret |= test_wait_proc_success(); - - return ret; + return rc; } - int reg_test(int argc, char ** argv) { - int ret = 0; + int rc = 0; (void) argc; (void) argv; - ret |= test_reg_init(); - - ret |= test_reg_flow(); - - ret |= test_reg_ipcp(); - - ret |= test_reg_name(); - - ret |= test_reg_proc(); - - ret |= test_reg_prog(); - - ret |= test_reg_spawned(); - - ret |= test_bind_proc(); - - ret |= test_bind_prog(); - - ret |= test_inherit_prog(); - - ret |= test_wait_accepting(); - - ret |= test_wait_ipcp_boot(); - - ret |= test_wait_proc(); - - return ret; + rc |= test_reg_init(); + rc |= test_reg_flow(); + rc |= test_reg_ipcp(); + rc |= test_reg_name(); + rc |= test_reg_proc(); + rc |= test_reg_prog(); + rc |= test_reg_spawned(); + rc |= test_bind_proc(); + rc |= test_bind_prog(); + rc |= test_inherit_prog(); + rc |= test_wait_accepting(); + rc |= test_wait_ipcp_boot(); + rc |= test_wait_proc(); + + return rc; } diff --git a/src/irmd/tests/CMakeLists.txt b/src/irmd/tests/CMakeLists.txt deleted file mode 100644 index e005d194..00000000 --- a/src/irmd/tests/CMakeLists.txt +++ /dev/null @@ -1,19 +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 -) - -add_executable(${src_folder}_test EXCLUDE_FROM_ALL ${${src_folder}_tests}) -target_link_libraries(${src_folder}_test ouroboros-common) - -add_dependencies(check ${src_folder}_test) - -set(tests_to_run ${${src_folder}_tests}) -remove(tests_to_run test_suite.c) - -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) |
