diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-06-11 10:03:14 +0000 |
|---|---|---|
| committer | Sander Vrijders <sander@ouroboros.rocks> | 2026-06-29 08:32:58 +0200 |
| commit | 67c55d5869d5473e5139614637f31ea37746181d (patch) | |
| tree | acc2ace032eca6eaac1110d323d6f809bb8eb364 /src/irmd | |
| parent | f5b15630d20acc893e3000f248f03185763f24b0 (diff) | |
| download | ouroboros-67c55d5869d5473e5139614637f31ea37746181d.tar.gz ouroboros-67c55d5869d5473e5139614637f31ea37746181d.zip | |
irmd: Specify peer authentication contract
OAP accepted requests and responses without a certificate even when
the peer was expected to authenticate. An on-path attacker could
strip the certificate and signature from a flow allocation response
and substitute its own key exchange, silently downgrading the
handshake to unauthenticated.
Add an auth=required|optional policy to enc.conf, enforced per role: a
client config requires the server to present a valid certificate, a
server config requires the same from the client. Default is required
for client side (https), optional server side. The client side default
can be changed via OAP_CLIENT_AUTH_DEFAULT for testing.
Replace the bare 'none' keyword with encryption=none, which disables
encryption only: the digest and the authentication policy are kept, so
authenticated but unencrypted flows can be configured. Configs using
bare 'none' are now rejected.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
Diffstat (limited to 'src/irmd')
| -rw-r--r-- | src/irmd/config.h.in | 1 | ||||
| -rw-r--r-- | src/irmd/oap/cli.c | 11 | ||||
| -rw-r--r-- | src/irmd/oap/io.c | 2 | ||||
| -rw-r--r-- | src/irmd/oap/srv.c | 8 | ||||
| -rw-r--r-- | src/irmd/oap/tests/common.c | 4 | ||||
| -rw-r--r-- | src/irmd/oap/tests/common.h | 1 | ||||
| -rw-r--r-- | src/irmd/oap/tests/oap_test.c | 130 |
7 files changed, 155 insertions, 2 deletions
diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in index df0cd718..0364e080 100644 --- a/src/irmd/config.h.in +++ b/src/irmd/config.h.in @@ -42,6 +42,7 @@ #define FLOW_DEALLOC_TIMEOUT @FLOW_DEALLOC_TIMEOUT@ #define OAP_REPLAY_TIMER @OAP_REPLAY_TIMER@ +#cmakedefine01 OAP_CLIENT_AUTH_DEFAULT #define BOOTSTRAP_TIMEOUT @BOOTSTRAP_TIMEOUT@ #define ENROLL_TIMEOUT @ENROLL_TIMEOUT@ diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c index 7a202da7..d38f38dd 100644 --- a/src/irmd/oap/cli.c +++ b/src/irmd/oap/cli.c @@ -93,6 +93,11 @@ int load_cli_kex_config(const struct name_info * info, assert(info != NULL); assert(cfg != NULL); + memset(cfg, 0, sizeof(*cfg)); + + /* A client authenticates the server by default, like an https client */ + cfg->req_auth = OAP_CLIENT_AUTH_DEFAULT; + return load_kex_config(info->name, info->c.enc, cfg); } @@ -534,6 +539,12 @@ int oap_cli_complete(void * ctx, goto fail_oap; } + /* Required peer auth makes sig and name binding mandatory */ + if (s->kcfg.req_auth && peer_hdr.crt.len == 0) { + log_err_id(id, "Server did not provide a certificate."); + goto fail_oap; + } + /* Verify request hash in authenticated response */ if (peer_hdr.req_hash.len == 0) { log_err_id(id, "Response missing req_hash."); diff --git a/src/irmd/oap/io.c b/src/irmd/oap/io.c index c2c91b91..24d33e60 100644 --- a/src/irmd/oap/io.c +++ b/src/irmd/oap/io.c @@ -103,8 +103,6 @@ int load_kex_config(const char * name, 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); diff --git a/src/irmd/oap/srv.c b/src/irmd/oap/srv.c index 587a8f9f..08b4d9d2 100644 --- a/src/irmd/oap/srv.c +++ b/src/irmd/oap/srv.c @@ -73,6 +73,9 @@ int load_srv_kex_config(const struct name_info * info, assert(info != NULL); assert(cfg != NULL); + memset(cfg, 0, sizeof(*cfg)); + + /* Client auth stays opt-in (mTLS); enable with auth=required */ return load_kex_config(info->name, info->s.enc, cfg); } @@ -441,6 +444,11 @@ int oap_srv_process(const struct name_info * info, goto fail_auth; } + if (kcfg.req_auth && peer_hdr.crt.len == 0) { + log_err_id(id, "Client did not provide a certificate."); + goto fail_auth; + } + if (do_server_kex(info, &peer_hdr, &kcfg, &local_hdr.kex, sk) < 0) goto fail_kex; diff --git a/src/irmd/oap/tests/common.c b/src/irmd/oap/tests/common.c index 0a1af100..c5000e48 100644 --- a/src/irmd/oap/tests/common.c +++ b/src/irmd/oap/tests/common.c @@ -36,6 +36,8 @@ int load_srv_kex_config(const struct name_info * info, memset(cfg, 0, sizeof(*cfg)); + cfg->req_auth = test_cfg.srv.req_auth; + if (test_cfg.srv.kex == NID_undef) return 0; @@ -55,6 +57,8 @@ int load_cli_kex_config(const struct name_info * info, memset(cfg, 0, sizeof(*cfg)); + cfg->req_auth = test_cfg.cli.req_auth; + if (test_cfg.cli.kex == NID_undef) return 0; diff --git a/src/irmd/oap/tests/common.h b/src/irmd/oap/tests/common.h index d4b6733a..fa500ffe 100644 --- a/src/irmd/oap/tests/common.h +++ b/src/irmd/oap/tests/common.h @@ -38,6 +38,7 @@ struct test_sec_cfg { int md; /* Digest NID for signatures */ int kem_mode; /* KEM encapsulation mode (0 for ECDH) */ bool auth; /* Use authentication (certificates) */ + bool req_auth; /* Require peer authentication */ }; /* Test configuration - set by each test before running roundtrip */ diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c index a525d988..fd2c5629 100644 --- a/src/irmd/oap/tests/oap_test.c +++ b/src/irmd/oap/tests/oap_test.c @@ -1183,6 +1183,129 @@ static int test_oap_server_name_mismatch(void) return TEST_RC_FAIL; } +/* Client requiring auth rejects a response without certificate */ +static int test_oap_cli_requires_srv_auth(void) +{ + struct oap_test_ctx ctx; + + test_default_cfg(); + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.req_auth = true; + + 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 (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client should reject unauthenticated server.\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 requiring auth rejects a request without certificate */ +static int test_oap_srv_requires_cli_auth(void) +{ + struct oap_test_ctx ctx; + + test_default_cfg(); + test_cfg.srv.req_auth = true; + + 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 (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject unauthenticated client.\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; +} + +/* Roundtrip succeeds when both sides require and provide auth */ +static int test_oap_mutual_req_auth(void) +{ + struct oap_test_ctx ctx; + + test_default_cfg(); + test_cfg.srv.req_auth = true; + test_cfg.cli.auth = AUTH; + test_cfg.cli.req_auth = true; + + 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 (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(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + int oap_test(int argc, char **argv) { @@ -1220,6 +1343,10 @@ int oap_test(int argc, ret |= test_oap_replay_packet(); ret |= test_oap_missing_root_ca(); ret |= test_oap_server_name_mismatch(); + + ret |= test_oap_cli_requires_srv_auth(); + ret |= test_oap_srv_requires_cli_auth(); + ret |= test_oap_mutual_req_auth(); #else (void) test_oap_roundtrip_auth_only; (void) test_oap_roundtrip_kex_only; @@ -1245,6 +1372,9 @@ int oap_test(int argc, (void) test_oap_replay_packet; (void) test_oap_missing_root_ca; (void) test_oap_server_name_mismatch; + (void) test_oap_cli_requires_srv_auth; + (void) test_oap_srv_requires_cli_auth; + (void) test_oap_mutual_req_auth; ret = TEST_RC_SKIP; #endif |
