diff options
Diffstat (limited to 'src/irmd')
| -rw-r--r-- | src/irmd/main.c | 14 | ||||
| -rw-r--r-- | src/irmd/oap.c | 130 | ||||
| -rw-r--r-- | src/irmd/oap/auth.c | 5 | ||||
| -rw-r--r-- | src/irmd/oap/cli.c | 2 | ||||
| -rw-r--r-- | src/irmd/oap/srv.c | 25 | ||||
| -rw-r--r-- | src/irmd/oap/tests/oap_test.c | 79 | ||||
| -rw-r--r-- | src/irmd/oap/tests/oap_test_ml_dsa.c | 1 | ||||
| -rw-r--r-- | src/irmd/reg/flow.c | 2 | ||||
| -rw-r--r-- | src/irmd/reg/reg.c | 6 | ||||
| -rw-r--r-- | src/irmd/reg/tests/flow_test.c | 20 | ||||
| -rw-r--r-- | src/irmd/reg/tests/reg_test.c | 111 |
11 files changed, 241 insertions, 154 deletions
diff --git a/src/irmd/main.c b/src/irmd/main.c index e610a015..4808934f 100644 --- a/src/irmd/main.c +++ b/src/irmd/main.c @@ -86,7 +86,9 @@ #define TIMESYNC_SLACK 100 /* ms */ #define OAP_SEEN_TIMER 20 /* s */ #define DEALLOC_TIME 300 /* s */ -#define DIRECT_MPL 1 /* s */ +#define DIRECT_MPL 20 /* ms */ +/* bytes; in-process, bounded only by PUP/GSPP. */ +#define DIRECT_MTU 65000 enum irm_state { IRMD_NULL = 0, @@ -910,6 +912,10 @@ static int flow_accept(struct flow_info * flow, flow->uid = reg_get_proc_uid(flow->n_pid); err = oap_srv_process(&info, req_hdr, &resp_hdr, data, sk); + if (err == -EREPLAY) { + log_warn("Dropping replayed alloc request for %s.", name); + goto fail_replay; + } if (err < 0) { log_err("OAP processing failed for %s.", name); goto fail_oap; @@ -938,6 +944,9 @@ static int flow_accept(struct flow_info * flow, fail_oap: if (!reg_flow_is_direct(flow->id)) ipcp_flow_alloc_resp(flow, err, resp_hdr); + fail_replay: + freebuf(req_hdr); + freebuf(resp_hdr); fail_wait: reg_destroy_flow(flow->id); fail_flow: @@ -1209,6 +1218,7 @@ static int flow_alloc_direct(const char * dst, acc.n_1_pid = flow->n_pid; acc.mpl = DIRECT_MPL; + acc.mtu = DIRECT_MTU; acc.qs = flow->qs; acc.state = FLOW_ALLOCATED; @@ -1244,6 +1254,7 @@ static int flow_alloc_direct(const char * dst, flow->id = acc.id; flow->n_1_pid = acc.n_pid; flow->mpl = DIRECT_MPL; + flow->mtu = DIRECT_MTU; flow->state = FLOW_ALLOCATED; log_info("Flow %d allocated (direct) for %d to %s.", @@ -2416,6 +2427,7 @@ int main(int argc, pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); crypt_secure_malloc_fini(); + crypt_cleanup(); reg_clear(); diff --git a/src/irmd/oap.c b/src/irmd/oap.c deleted file mode 100644 index 1831f533..00000000 --- a/src/irmd/oap.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2026 - * - * 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/auth.c b/src/irmd/oap/auth.c index a11ab158..d165de73 100644 --- a/src/irmd/oap/auth.c +++ b/src/irmd/oap/auth.c @@ -174,6 +174,7 @@ int oap_check_hdr(const struct oap_hdr * hdr) fail_replay: pthread_mutex_unlock(&oap_auth.replay.mtx); free(new); + return -EREPLAY; fail_stamp: return -EAUTH; } @@ -183,7 +184,7 @@ int oap_auth_peer(char * name, const struct oap_hdr * peer_hdr) { void * crt; - void * pk; + void * pk = NULL; buffer_t sign; /* Signed region */ uint8_t * id = peer_hdr->id.data; @@ -244,8 +245,8 @@ int oap_auth_peer(char * name, return 0; fail_check_sig: - crypt_free_key(pk); fail_crt: + crypt_free_key(pk); crypt_free_crt(crt); fail_check: return -EAUTH; diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c index 8ecd317d..7a202da7 100644 --- a/src/irmd/oap/cli.c +++ b/src/irmd/oap/cli.c @@ -50,7 +50,7 @@ struct oap_cli_ctx { uint8_t __id[OAP_ID_SIZE]; buffer_t id; - uint8_t kex_buf[MSGBUFSZ]; + uint8_t kex_buf[CRYPT_KEY_BUFSZ]; uint8_t req_hash[MAX_HASH_SIZE]; size_t req_hash_len; int req_md_nid; diff --git a/src/irmd/oap/srv.c b/src/irmd/oap/srv.c index 36391e50..587a8f9f 100644 --- a/src/irmd/oap/srv.c +++ b/src/irmd/oap/srv.c @@ -180,11 +180,7 @@ static int negotiate_cipher(const struct oap_hdr * peer_hdr, cli_rank = crypt_kdf_rank(peer_hdr->kdf_nid); srv_rank = crypt_kdf_rank(kcfg->k.nid); - /* - * For client-encap KEM, the KDF is baked into - * the ciphertext. The server must use the client's - * KDF and can only verify the minimum. - */ + /* Client-encap KEM bakes KDF into ciphertext; verify min. */ if (OAP_KEX_ROLE(peer_hdr) == KEM_MODE_CLIENT_ENCAP) { if (srv_rank > cli_rank) { log_err_id(id, "Client KDF too weak."); @@ -384,15 +380,16 @@ int oap_srv_process(const struct name_info * info, struct oap_hdr peer_hdr; struct oap_hdr local_hdr; struct sec_config kcfg; - uint8_t kex_buf[MSGBUFSZ]; + uint8_t kex_buf[CRYPT_KEY_BUFSZ]; uint8_t hash_buf[MAX_HASH_SIZE]; buffer_t req_hash = BUF_INIT; ssize_t hash_ret; - char cli_name[NAME_SIZE + 1]; /* TODO */ + char cli_name[NAME_SIZE + 1]; uint8_t * id; void * pkp = NULL; void * crt = NULL; int req_md_nid; + int ret; assert(info != NULL); assert(rsp_buf != NULL); @@ -427,8 +424,13 @@ int oap_srv_process(const struct name_info * info, id = peer_hdr.id.data; /* Logging */ - if (oap_check_hdr(&peer_hdr) < 0) { - log_err_id(id, "OAP header failed replay check."); + ret = oap_check_hdr(&peer_hdr); + if (ret == -EREPLAY) { + log_warn_id(id, "OAP header failed replay check."); + goto fail_replay; + } + if (ret < 0) { + log_err_id(id, "OAP header check failed."); goto fail_auth; } @@ -491,6 +493,11 @@ int oap_srv_process(const struct name_info * info, fail_cred: return -EAUTH; + fail_replay: + crypt_free_crt(crt); + crypt_free_key(pkp); + return -EREPLAY; + fail_kex: crypt_free_crt(crt); crypt_free_key(pkp); diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c index 2f0f0b4d..a525d988 100644 --- a/src/irmd/oap/tests/oap_test.c +++ b/src/irmd/oap/tests/oap_test.c @@ -32,6 +32,7 @@ #include <ouroboros/crypt.h> #include <ouroboros/endian.h> +#include <ouroboros/errno.h> #include <ouroboros/flow.h> #include <ouroboros/name.h> #include <ouroboros/random.h> @@ -1053,9 +1054,9 @@ static int test_oap_replay_packet(void) 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"); + /* Replay must return -EREPLAY so callers can drop silently. */ + if (oap_srv_process_ctx(&ctx) != -EREPLAY) { + printf("Replayed packet rejection != -EREPLAY.\n"); goto fail_cleanup; } @@ -1071,6 +1072,74 @@ static int test_oap_replay_packet(void) return TEST_RC_FAIL; } +/* Server rejects client certificate when root CA is missing from store */ +static int test_oap_missing_root_ca(void) +{ + struct oap_test_ctx ctx; + void * im_ca = NULL; + + test_default_cfg(); + + TEST_START(); + + 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; + } + + /* Load intermediate CA but intentionally omit the root CA */ + if (crypt_load_crt_str(im_ca_crt_ec, &im_ca) < 0) { + printf("Failed to load intermediate CA cert.\n"); + goto fail_fini; + } + + ctx.im_ca = im_ca; + + if (oap_auth_add_ca_crt(im_ca) < 0) { + printf("Failed to add intermediate CA cert to store.\n"); + goto fail_fini; + } + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_fini; + } + + /* Server processes and signs response - succeeds without root CA */ + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_teardown; + } + + /* Client verifies server certificate against trust store: + * should reject because root CA is not in the store */ + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client should reject without root CA.\n"); + goto fail_teardown; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_teardown: + oap_test_teardown(&ctx); + TEST_FAIL(); + return TEST_RC_FAIL; + fail_fini: + crypt_free_crt(im_ca); + oap_auth_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + /* Test that client rejects server with wrong certificate name */ static int test_oap_server_name_mismatch(void) { @@ -1149,6 +1218,7 @@ int oap_test(int argc, ret |= test_oap_outdated_packet(); ret |= test_oap_future_packet(); ret |= test_oap_replay_packet(); + ret |= test_oap_missing_root_ca(); ret |= test_oap_server_name_mismatch(); #else (void) test_oap_roundtrip_auth_only; @@ -1173,9 +1243,12 @@ int oap_test(int argc, (void) test_oap_outdated_packet; (void) test_oap_future_packet; (void) test_oap_replay_packet; + (void) test_oap_missing_root_ca; (void) test_oap_server_name_mismatch; ret = TEST_RC_SKIP; #endif + crypt_cleanup(); + return ret; } diff --git a/src/irmd/oap/tests/oap_test_ml_dsa.c b/src/irmd/oap/tests/oap_test_ml_dsa.c index f9e6bdb2..81b307ab 100644 --- a/src/irmd/oap/tests/oap_test_ml_dsa.c +++ b/src/irmd/oap/tests/oap_test_ml_dsa.c @@ -442,6 +442,7 @@ int oap_test_ml_dsa(int argc, ret = TEST_RC_SKIP; #endif + crypt_cleanup(); return ret; } diff --git a/src/irmd/reg/flow.c b/src/irmd/reg/flow.c index 93c3e128..5c709dea 100644 --- a/src/irmd/reg/flow.c +++ b/src/irmd/reg/flow.c @@ -42,6 +42,7 @@ struct reg_flow * reg_flow_create(const struct flow_info * info) assert(info->n_pid != 0); assert(info->n_1_pid == 0); assert(info->mpl == 0); + assert(info->mtu == 0); assert(info->state == FLOW_INIT); flow = malloc(sizeof(*flow)); @@ -160,6 +161,7 @@ int reg_flow_update(struct reg_flow * flow, assert(info->mpl != 0); flow->info.mpl = info->mpl; + flow->info.mtu = info->mtu; if (flow->info.state == FLOW_ALLOC_PENDING) break; diff --git a/src/irmd/reg/reg.c b/src/irmd/reg/reg.c index 0025f695..365064e5 100644 --- a/src/irmd/reg/reg.c +++ b/src/irmd/reg/reg.c @@ -1820,7 +1820,11 @@ int reg_respond_alloc(struct flow_info * info, goto fail_flow; } - assert(flow->info.state == FLOW_ALLOC_PENDING); + if (flow->info.state != FLOW_ALLOC_PENDING) { + log_warn("Flow %d already responded.", info->id); + goto fail_flow; + } + assert(flow->rsp_data.len == 0); assert(flow->rsp_data.data == NULL); diff --git a/src/irmd/reg/tests/flow_test.c b/src/irmd/reg/tests/flow_test.c index 7e1c1360..18214078 100644 --- a/src/irmd/reg/tests/flow_test.c +++ b/src/irmd/reg/tests/flow_test.c @@ -122,6 +122,21 @@ static int test_reg_flow_create_has_mpl(void) { return TEST_RC_SUCCESS; } +static int test_reg_flow_create_has_mtu(void) { + struct flow_info info = { + .id = 1, + .n_pid = 1, + .n_1_pid = 0, + .mtu = 1400, + .qs = qos_raw, + .state = FLOW_ALLOC_PENDING + }; + + reg_flow_create(&info); /* assert fail */ + + return TEST_RC_SUCCESS; +} + static int test_reg_flow_update(void) { struct reg_flow * f; @@ -136,7 +151,7 @@ static int test_reg_flow_update(void) struct flow_info upd = { .id = 1, .n_pid = 1, - .qs = qos_data, + .qs = qos_msg, .state = FLOW_DEALLOCATED }; @@ -179,7 +194,7 @@ static int test_reg_flow_update_wrong_id(void) struct flow_info upd = { .id = 2, .n_pid = 1, - .qs = qos_data, + .qs = qos_msg, .state = FLOW_DEALLOCATED }; @@ -210,6 +225,7 @@ static int test_reg_flow_assert_fails(void) 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_create_has_mtu); ret |= test_assert_fail(test_reg_flow_update_wrong_id); return ret; diff --git a/src/irmd/reg/tests/reg_test.c b/src/irmd/reg/tests/reg_test.c index b426c0dd..0b1014f9 100644 --- a/src/irmd/reg/tests/reg_test.c +++ b/src/irmd/reg/tests/reg_test.c @@ -31,6 +31,7 @@ #define TEST_N_1_PID 3999 #define TEST_FAKE_ID 9128349 #define TEST_MPL 5 +#define TEST_MTU 1400 #define TEST_PROG "reg_test" /* own binary for binary check */ #define TEST_IPCP "testipcp" #define TEST_NAME "testname" @@ -239,7 +240,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, + .qs = qos_msg, .state = FLOW_ALLOCATED /* RESPONSE SUCCESS */ }; @@ -266,6 +267,7 @@ static int test_reg_accept_flow_success(void) n_1_info.id = info.id; n_1_info.mpl = 1; + n_1_info.mtu = TEST_MTU; pthread_create(&thr, NULL, test_flow_respond_accept, &n_1_info); @@ -284,6 +286,11 @@ static int test_reg_accept_flow_success(void) goto fail; } + if (info.mtu != TEST_MTU) { + printf("MTU not propagated.\n"); + goto fail; + } + if (rbuf.data == NULL) { printf("rbuf data not returned.\n"); goto fail; @@ -336,7 +343,7 @@ static int test_reg_accept_flow_success_no_crypt(void) struct flow_info n_1_info = { .n_1_pid = TEST_N_1_PID, - .qs = qos_data, + .qs = qos_msg, .state = FLOW_ALLOCATED /* RESPONSE SUCCESS */ }; @@ -363,6 +370,7 @@ static int test_reg_accept_flow_success_no_crypt(void) n_1_info.id = info.id; n_1_info.mpl = 1; + n_1_info.mtu = TEST_MTU; pthread_create(&thr, NULL, test_flow_respond_accept, &n_1_info); @@ -381,6 +389,11 @@ static int test_reg_accept_flow_success_no_crypt(void) goto fail; } + if (info.mtu != TEST_MTU) { + printf("MTU not propagated.\n"); + goto fail; + } + if (rbuf.data == NULL) { printf("rbuf data was not returned.\n"); goto fail; @@ -431,7 +444,7 @@ static int test_reg_allocate_flow_fail(void) struct flow_info n_1_info = { .n_1_pid = TEST_N_1_PID, - .qs = qos_data, + .qs = qos_msg, .state = FLOW_DEALLOCATED /* RESPONSE FAIL */ }; @@ -489,6 +502,93 @@ static int test_reg_allocate_flow_fail(void) return TEST_RC_FAIL; } +static int test_reg_respond_alloc_duplicate(void) +{ + pthread_t thr; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(1); + buffer_t rbuf = BUF_INIT; + buffer_t empty = BUF_INIT; + struct flow_info dup_info; + + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw + }; + + struct flow_info n_1_info = { + .n_1_pid = TEST_N_1_PID, + .qs = qos_msg, + .state = FLOW_ALLOCATED /* RESPONSE SUCCESS */ + }; + + TEST_START(); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to add flow.\n"); + goto fail; + } + + info.n_1_pid = TEST_N_1_PID; + + if (reg_prepare_flow_alloc(&info) < 0) { + printf("Failed to prepare flow for alloc.\n"); + goto fail; + } + + n_1_info.id = info.id; + n_1_info.mpl = 1; + n_1_info.mtu = TEST_MTU; + + pthread_create(&thr, NULL, test_flow_respond_alloc, &n_1_info); + + if (reg_wait_flow_allocated(&info, &rbuf, &abstime) < 0) { + printf("Flow allocation failed.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + freebuf(rbuf); + + if (info.mtu != TEST_MTU) { + printf("MTU not propagated.\n"); + goto fail; + } + + /* Duplicate reply on an already-ALLOCATED flow must not assert. */ + dup_info = n_1_info; + dup_info.state = FLOW_DEALLOCATED; + + if (reg_respond_alloc(&dup_info, &empty, -EREPLAY) != -1) { + printf("Duplicate respond_alloc should return -1.\n"); + goto fail; + } + + reg_dealloc_flow(&info); + reg_dealloc_flow_resp(&info); + reg_destroy_flow(n_1_info.id); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + struct direct_alloc_info { struct flow_info info; buffer_t rsp; @@ -564,7 +664,7 @@ static int test_reg_direct_flow_success(void) dai.info.id = info.id; dai.info.n_1_pid = TEST_N_1_PID; dai.info.mpl = TEST_MPL; - dai.info.qs = qos_data; + dai.info.qs = qos_msg; dai.info.state = FLOW_ALLOCATED; dai.rsp.len = 0; dai.rsp.data = NULL; @@ -679,6 +779,7 @@ static int test_reg_flow(void) { rc |= test_reg_accept_flow_success(); rc |= test_reg_accept_flow_success_no_crypt(); rc |= test_reg_allocate_flow_fail(); + rc |= test_reg_respond_alloc_duplicate(); rc |= test_reg_direct_flow_success(); return rc; @@ -1491,7 +1592,7 @@ static int test_wait_accepting_fail_name(void) static void * test_call_flow_accept(void * o) { struct timespec abstime; - struct timespec timeo = TIMESPEC_INIT_MS(10); + struct timespec timeo = TIMESPEC_INIT_MS(30); buffer_t pbuf = BUF_INIT; struct proc_info pinfo = TEST_PROC_INFO; |
