diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-06-21 14:07:00 +0200 |
|---|---|---|
| committer | Sander Vrijders <sander@ouroboros.rocks> | 2026-06-29 08:33:00 +0200 |
| commit | 55a8136859d82d9bdb8f85abb25290177ca7e561 (patch) | |
| tree | e3b87e09322867245a49fe11c51b621efcff2730 /src/irmd/oap/tests | |
| parent | 552a4c4469db1cedacc02eb4f9969afe73e0fb42 (diff) | |
| download | ouroboros-55a8136859d82d9bdb8f85abb25290177ca7e561.tar.gz ouroboros-55a8136859d82d9bdb8f85abb25290177ca7e561.zip | |
irmd: Harden OAP handshake and add cert-less re-key
Adds support for:
Server key confirmation: the session key is bound to the
negotiated algorithm via the HKDF info. The server returns a
key-confirmation tag (rsp_tag, replacing the bare request-hash echo),
so a cipher downgrade or key desync is detected. The cleartext path
keeps a request echo, compared in constant time.
Sealed server identity: AEAD-seal the certificate, signature and
piggybacked data in the encrypted response (kex and rsp_tag move
ahead as AAD), hiding the server identity and response sizes.
Cert-less re-key: let the client omit its certificate, verifying the
peer against the cached certificate. On PQC flows, ephemeral
server-encap KEX (preserving forward secrecy) is used, even if the
original flow allocation was client-encap.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
Diffstat (limited to 'src/irmd/oap/tests')
| -rw-r--r-- | src/irmd/oap/tests/common.c | 168 | ||||
| -rw-r--r-- | src/irmd/oap/tests/common.h | 11 | ||||
| -rw-r--r-- | src/irmd/oap/tests/oap_test.c | 354 | ||||
| -rw-r--r-- | src/irmd/oap/tests/oap_test_ml_dsa.c | 22 |
4 files changed, 455 insertions, 100 deletions
diff --git a/src/irmd/oap/tests/common.c b/src/irmd/oap/tests/common.c index 8c271b2e..e9ac82ed 100644 --- a/src/irmd/oap/tests/common.c +++ b/src/irmd/oap/tests/common.c @@ -164,13 +164,15 @@ void oap_test_teardown(struct oap_test_ctx * ctx) if (ctx->cli.state != NULL) { res.key = ctx->cli.key; oap_cli_complete(ctx->cli.state, &ctx->cli.info, dummy, - &ctx->data, &res); + &ctx->data, &res, NULL, NULL); ctx->cli.state = NULL; } freebuf(ctx->data); freebuf(ctx->resp_hdr); freebuf(ctx->req_hdr); + freebuf(ctx->srv_crt); + freebuf(ctx->cli_crt); crypt_free_crt(ctx->im_ca); crypt_free_crt(ctx->root_ca); @@ -182,7 +184,7 @@ void oap_test_teardown(struct oap_test_ctx * 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); + ctx->data, ctx->rekey); } int oap_srv_process_ctx(struct oap_test_ctx * ctx) @@ -191,7 +193,9 @@ int oap_srv_process_ctx(struct oap_test_ctx * ctx) int ret; ret = oap_srv_process(&ctx->srv.info, ctx->req_hdr, - &ctx->resp_hdr, &ctx->data, &res); + &ctx->resp_hdr, &ctx->data, &res, ctx->rekey, + ctx->rekey ? &ctx->srv_crt : NULL, + ctx->rekey ? NULL : &ctx->srv_crt); if (ret == 0) ctx->srv.nid = res.nid; @@ -204,7 +208,9 @@ int oap_cli_complete_ctx(struct oap_test_ctx * ctx) int ret; ret = oap_cli_complete(ctx->cli.state, &ctx->cli.info, ctx->resp_hdr, - &ctx->data, &res); + &ctx->data, &res, + ctx->rekey ? &ctx->cli_crt : NULL, + ctx->rekey ? NULL : &ctx->cli_crt); ctx->cli.state = NULL; if (ret == 0) @@ -255,6 +261,147 @@ int roundtrip_auth_only(const char * root_ca, return TEST_RC_FAIL; } +int roundtrip_rekey(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; + + /* Initial handshake: the client caches the server cert. */ + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Initial client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Initial server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Initial client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Initial keys do not match.\n"); + goto fail_cleanup; + } + + if (ctx.cli_crt.len == 0) { + printf("Server cert was not cached for re-key.\n"); + goto fail_cleanup; + } + + /* Re-key: cert dropped on the wire, verified against the cache. */ + freebuf(ctx.req_hdr); + freebuf(ctx.resp_hdr); + freebuf(ctx.data); + + ctx.rekey = true; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Re-key client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Re-key server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Re-key client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Re-key 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 roundtrip_rekey_badcache(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("Initial client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Initial server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Initial client complete failed.\n"); + goto fail_cleanup; + } + + if (ctx.cli_crt.len == 0) { + printf("Server cert was not cached.\n"); + goto fail_cleanup; + } + + /* Corrupt the cached cert: the re-key must fail closed. */ + ctx.cli_crt.data[ctx.cli_crt.len / 2] ^= 0xFF; + + freebuf(ctx.req_hdr); + freebuf(ctx.resp_hdr); + freebuf(ctx.data); + + ctx.rekey = true; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Re-key client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Re-key server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Re-key accepted a corrupted cached cert.\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; @@ -283,14 +430,15 @@ int roundtrip_kex_only(void) } if (oap_cli_prepare(&cli_state, &cli_info, &req_hdr, - data) < 0) { + data, false) < 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) { + if (oap_srv_process(&srv_info, req_hdr, &resp_hdr, &data, &res, + false, NULL, NULL) < 0) { printf("Server process failed.\n"); goto fail_cleanup; } @@ -299,7 +447,8 @@ int roundtrip_kex_only(void) res.key = cli_key; - if (oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res) < 0) { + if (oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res, + NULL, NULL) < 0) { printf("Client complete failed.\n"); cli_state = NULL; goto fail_cleanup; @@ -328,7 +477,8 @@ int roundtrip_kex_only(void) fail_cleanup: if (cli_state != NULL) { res.key = cli_key; - oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res); + oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, + &res, NULL, NULL); } freebuf(resp_hdr); freebuf(req_hdr); @@ -408,7 +558,7 @@ int corrupted_response(const char * root_ca, res.key = ctx.cli.key; if (oap_cli_complete(ctx.cli.state, &ctx.cli.info, ctx.resp_hdr, - &ctx.data, &res) == 0) { + &ctx.data, &res, NULL, NULL) == 0) { printf("Client should reject corrupted response.\n"); ctx.cli.state = NULL; goto fail_cleanup; diff --git a/src/irmd/oap/tests/common.h b/src/irmd/oap/tests/common.h index 4fe2f779..c47096fb 100644 --- a/src/irmd/oap/tests/common.h +++ b/src/irmd/oap/tests/common.h @@ -71,6 +71,11 @@ struct oap_test_ctx { buffer_t data; void * root_ca; void * im_ca; + + /* Re-key (tier iii): drop the cert, verify against the cache. */ + bool rekey; + buffer_t srv_crt; /* client cert cached by server */ + buffer_t cli_crt; /* server cert cached by client */ }; int oap_test_setup(struct oap_test_ctx * ctx, @@ -88,6 +93,12 @@ 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_rekey(const char * root_ca, + const char * im_ca_str); + +int roundtrip_rekey_badcache(const char * root_ca, + const char * im_ca_str); + int roundtrip_kex_only(void); int corrupted_request(const char * root_ca, diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c index 53b525a7..fc10150b 100644 --- a/src/irmd/oap/tests/oap_test.c +++ b/src/irmd/oap/tests/oap_test.c @@ -49,7 +49,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <unistd.h> #ifdef HAVE_OPENSSL #include <openssl/evp.h> @@ -179,6 +178,7 @@ static int test_oap_roundtrip(int kex) oap_test_teardown(&ctx); TEST_SUCCESS("(%s)", kex_str); + return TEST_RC_SUCCESS; fail_cleanup: @@ -203,6 +203,20 @@ static int test_oap_roundtrip_auth_only(void) return roundtrip_auth_only(root_ca_crt_ec, im_ca_crt_ec); } +static int test_oap_rekey(void) +{ + test_default_cfg(); + + return roundtrip_rekey(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_rekey_badcache(void) +{ + test_default_cfg(); + + return roundtrip_rekey_badcache(root_ca_crt_ec, im_ca_crt_ec); +} + static int test_oap_roundtrip_kex_only(void) { memset(&test_cfg, 0, sizeof(test_cfg)); @@ -243,6 +257,7 @@ static int test_oap_piggyback_data(void) 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) @@ -293,6 +308,7 @@ static int test_oap_piggyback_data(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -361,6 +377,7 @@ static int test_oap_inflated_length_field(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -405,6 +422,7 @@ static int test_oap_deflated_length_field(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -463,6 +481,7 @@ static int test_oap_nid_without_kex(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -514,6 +533,61 @@ static int test_oap_unsupported_nid(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Client rejects a response whose key-confirmation tag is tampered */ +static int test_oap_key_confirm_mismatch(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + /* Unauthenticated + encrypted: response unsigned, KC is the gate */ + 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; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + /* The key-confirm tag is the last field of an unsigned response */ + ctx.resp_hdr.data[ctx.resp_hdr.len - 1] ^= 0xFF; + + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client accepted a bad key-confirmation tag.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -614,6 +688,7 @@ static int test_oap_cipher_mismatch(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -660,6 +735,7 @@ static int test_oap_srv_enc_cli_none(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -729,6 +805,7 @@ static int test_oap_cli_enc_srv_none(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -738,7 +815,7 @@ static int test_oap_cli_enc_srv_none(void) return TEST_RC_FAIL; } -/* Client rejects server response with downgraded cipher */ +/* Unauthenticated server: client floor-rejects a downgraded cipher */ static int test_oap_cli_rejects_downgrade(void) { struct oap_test_ctx ctx; @@ -752,7 +829,7 @@ static int test_oap_cli_rejects_downgrade(void) test_cfg.srv.cipher = NID_aes_256_gcm; test_cfg.srv.kdf = NID_sha256; test_cfg.srv.md = NID_sha256; - test_cfg.srv.auth = AUTH; + test_cfg.srv.auth = NO_AUTH; test_cfg.cli.kex = NID_X25519; test_cfg.cli.cipher = NID_aes_256_gcm; @@ -774,7 +851,7 @@ static int test_oap_cli_rejects_downgrade(void) } /* Tamper: replace cipher NID with weaker one */ - weak = hton16(NID_aes_128_ctr); + weak = hton16(NID_aes_128_gcm); memcpy(ctx.resp_hdr.data + OAP_CIPHER_NID_OFFSET, &weak, sizeof(weak)); @@ -787,6 +864,69 @@ static int test_oap_cli_rejects_downgrade(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * Suite binding: a cipher swapped to a higher rank clears the client floor + * check, but the bound key commits to the negotiated suite, so the swap must + * still fail key confirmation. + */ +static int test_oap_cli_rejects_suite_swap(void) +{ + struct oap_test_ctx ctx; + uint16_t swap; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Both AES-128-GCM: a swap to AES-256 outranks the client floor */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_128_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_128_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; + } + + /* Swap the response cipher to a higher-ranked one */ + swap = hton16(NID_aes_256_gcm); + memcpy(ctx.resp_hdr.data + OAP_CIPHER_NID_OFFSET, + &swap, sizeof(swap)); + + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client accepted a swapped cipher suite.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -836,6 +976,7 @@ static int test_oap_srv_rejects_weak_kex(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -895,6 +1036,7 @@ static int test_oap_roundtrip_md(int md) oap_test_teardown(&ctx); TEST_SUCCESS("(%s)", md_str ? md_str : "default"); + return TEST_RC_SUCCESS; fail_cleanup: @@ -960,6 +1102,7 @@ static int test_oap_outdated_packet(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1008,6 +1151,7 @@ static int test_oap_future_packet(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1067,6 +1211,7 @@ static int test_oap_replay_packet(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1274,6 +1419,7 @@ static int test_oap_missing_root_ca(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_teardown: @@ -1322,6 +1468,7 @@ static int test_oap_server_name_mismatch(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1363,6 +1510,7 @@ static int test_oap_cli_requires_srv_auth(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1398,6 +1546,7 @@ static int test_oap_srv_requires_cli_auth(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1445,6 +1594,7 @@ static int test_oap_mutual_req_auth(void) oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1454,59 +1604,18 @@ static int test_oap_mutual_req_auth(void) return TEST_RC_FAIL; } -/* Write a PEM cert to a temp file for cacert= pinning */ -static int write_tmp_crt(const char * pem, - char * path) -{ - FILE * fp; - int fd; - - strcpy(path, "/tmp/oap_test_pin_XXXXXX"); - - fd = mkstemp(path); - if (fd < 0) - return -1; - - fp = fdopen(fd, "w"); - if (fp == NULL) { - close(fd); - goto fail_file; - } - - if (fputs(pem, fp) == EOF) { - fclose(fp); - goto fail_file; - } - - fclose(fp); - - return 0; - - fail_file: - unlink(path); - return -1; -} - -/* Client pins the server CA: in-chain accepted, out-of-chain rejected */ -static int test_oap_cli_pin_ca(const char * pem, - bool expected) +/* Client rejects a server signature with a different digest */ +static int test_oap_cli_rejects_md_mismatch(void) { struct oap_test_ctx ctx; - char path[32]; test_default_cfg(); + test_cfg.srv.md = NID_sha384; - TEST_START("(%s)", expected ? "match" : "mismatch"); - - if (write_tmp_crt(pem, path) < 0) { - printf("Failed to write pinned CA file.\n"); - goto fail; - } - - test_cfg.cli.cacert = path; + TEST_START(); if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) - goto fail_unlink; + goto fail; if (oap_cli_prepare_ctx(&ctx) < 0) { printf("Client prepare failed.\n"); @@ -1518,80 +1627,88 @@ static int test_oap_cli_pin_ca(const char * pem, goto fail_cleanup; } - if ((oap_cli_complete_ctx(&ctx) == 0) != expected) { - printf("Pinned CA gave wrong verdict.\n"); + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client should reject digest mismatch.\n"); goto fail_cleanup; } - unlink(path); oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: oap_test_teardown(&ctx); - fail_unlink: - unlink(path); fail: TEST_FAIL(); return TEST_RC_FAIL; } -/* Server pins the client CA: in-chain accepted, out-of-chain rejected */ -static int test_oap_srv_pin_ca(const char * pem, - bool expected) +/* Server rejects a client signature with a different digest */ +static int test_oap_srv_rejects_md_mismatch(void) { struct oap_test_ctx ctx; - char path[32]; test_default_cfg(); test_cfg.cli.auth = AUTH; + test_cfg.cli.md = NID_sha384; - TEST_START("(%s)", expected ? "match" : "mismatch"); - - if (write_tmp_crt(pem, path) < 0) { - printf("Failed to write pinned CA file.\n"); - goto fail; - } - - test_cfg.srv.cacert = path; + TEST_START(); if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) - goto fail_unlink; + goto fail; if (oap_cli_prepare_ctx(&ctx) < 0) { printf("Client prepare failed.\n"); goto fail_cleanup; } - if ((oap_srv_process_ctx(&ctx) == 0) != expected) { - printf("Pinned CA gave wrong verdict.\n"); + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject digest mismatch.\n"); goto fail_cleanup; } - unlink(path); oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: oap_test_teardown(&ctx); - fail_unlink: - unlink(path); fail: TEST_FAIL(); return TEST_RC_FAIL; } -/* Client rejects a server signature with a different digest */ -static int test_oap_cli_rejects_md_mismatch(void) +/* Naive substring search over raw bytes (memmem is not portable here). */ +static bool buf_contains(const uint8_t * hay, + size_t hlen, + const uint8_t * needle, + size_t nlen) +{ + size_t i; + + if (nlen == 0 || nlen > hlen) + return false; + + for (i = 0; i + nlen <= hlen; i++) { + if (memcmp(hay + i, needle, nlen) == 0) + return true; + } + + return false; +} + +/* The server certificate must not appear in cleartext on the wire */ +static int test_oap_server_cert_hidden(void) { struct oap_test_ctx ctx; + void * crt = NULL; + buffer_t der = BUF_INIT; test_default_cfg(); - test_cfg.srv.md = NID_sha384; TEST_START(); @@ -1608,16 +1725,50 @@ static int test_oap_cli_rejects_md_mismatch(void) goto fail_cleanup; } - if (oap_cli_complete_ctx(&ctx) == 0) { - printf("Client should reject digest mismatch.\n"); + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { + printf("Failed to load server crt.\n"); goto fail_cleanup; } + if (crypt_crt_der(crt, &der) < 0) { + printf("Failed to DER-encode server crt.\n"); + goto fail_crt; + } + + if (der.len == 0 || der.len > ctx.resp_hdr.len) { + printf("Unexpected cert/response sizes.\n"); + goto fail_der; + } + + if (buf_contains(ctx.resp_hdr.data, ctx.resp_hdr.len, + der.data, der.len)) { + printf("Server certificate found in cleartext.\n"); + goto fail_der; + } + + /* The handshake must still complete and agree on a key */ + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_der; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_der; + } + + freebuf(der); + crypt_free_crt(crt); oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; + fail_der: + freebuf(der); + fail_crt: + crypt_free_crt(crt); fail_cleanup: oap_test_teardown(&ctx); fail: @@ -1625,14 +1776,13 @@ static int test_oap_cli_rejects_md_mismatch(void) return TEST_RC_FAIL; } -/* Server rejects a client signature with a different digest */ -static int test_oap_srv_rejects_md_mismatch(void) +/* Tampering the sealed identity block fails the handshake */ +static int test_oap_sealed_tamper(void) { struct oap_test_ctx ctx; + size_t pos; test_default_cfg(); - test_cfg.cli.auth = AUTH; - test_cfg.cli.md = NID_sha384; TEST_START(); @@ -1644,14 +1794,29 @@ static int test_oap_srv_rejects_md_mismatch(void) goto fail_cleanup; } - if (oap_srv_process_ctx(&ctx) == 0) { - printf("Server should reject digest mismatch.\n"); + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (ctx.resp_hdr.len < 64) { + printf("Response too short for test.\n"); + goto fail_cleanup; + } + + /* Flip a byte inside the sealed ciphertext, before the AEAD tag */ + pos = ctx.resp_hdr.len - 32; + ctx.resp_hdr.data[pos] ^= 0xFF; + + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client accepted a tampered identity block.\n"); goto fail_cleanup; } oap_test_teardown(&ctx); TEST_SUCCESS(); + return TEST_RC_SUCCESS; fail_cleanup: @@ -1677,12 +1842,15 @@ int oap_test(int argc, ret |= test_oap_roundtrip_auth_only(); ret |= test_oap_roundtrip_kex_only(); ret |= test_oap_piggyback_data(); + ret |= test_oap_rekey(); + ret |= test_oap_rekey_badcache(); ret |= test_oap_roundtrip_all(); ret |= test_oap_roundtrip_md_all(); ret |= test_oap_corrupted_request(); ret |= test_oap_corrupted_response(); + ret |= test_oap_key_confirm_mismatch(); ret |= test_oap_truncated_request(); ret |= test_oap_inflated_length_field(); ret |= test_oap_deflated_length_field(); @@ -1693,6 +1861,7 @@ int oap_test(int argc, ret |= test_oap_srv_enc_cli_none(); ret |= test_oap_cli_enc_srv_none(); ret |= test_oap_cli_rejects_downgrade(); + ret |= test_oap_cli_rejects_suite_swap(); ret |= test_oap_srv_rejects_weak_kex(); ret |= test_oap_outdated_packet(); @@ -1705,14 +1874,12 @@ int oap_test(int argc, ret |= test_oap_srv_requires_cli_auth(); ret |= test_oap_mutual_req_auth(); - ret |= test_oap_cli_pin_ca(im_ca_crt_ec, true); - ret |= test_oap_cli_pin_ca(root_ca_crt_ec, true); - ret |= test_oap_cli_pin_ca(other_ca_crt_ec, false); - ret |= test_oap_srv_pin_ca(im_ca_crt_ec, true); - ret |= test_oap_srv_pin_ca(other_ca_crt_ec, false); ret |= test_oap_cli_rejects_md_mismatch(); ret |= test_oap_srv_rejects_md_mismatch(); + + ret |= test_oap_server_cert_hidden(); + ret |= test_oap_sealed_tamper(); #else (void) test_oap_roundtrip_auth_only; (void) test_oap_roundtrip_kex_only; @@ -1723,6 +1890,7 @@ int oap_test(int argc, (void) test_oap_roundtrip_md_all; (void) test_oap_corrupted_request; (void) test_oap_corrupted_response; + (void) test_oap_key_confirm_mismatch; (void) test_oap_truncated_request; (void) test_oap_inflated_length_field; (void) test_oap_deflated_length_field; @@ -1732,19 +1900,23 @@ int oap_test(int argc, (void) test_oap_srv_enc_cli_none; (void) test_oap_cli_enc_srv_none; (void) test_oap_cli_rejects_downgrade; + (void) test_oap_cli_rejects_suite_swap; (void) test_oap_srv_rejects_weak_kex; (void) test_oap_outdated_packet; (void) test_oap_future_packet; (void) test_oap_replay_packet; + (void) test_oap_replay_generations; (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; - (void) test_oap_cli_pin_ca; - (void) test_oap_srv_pin_ca; (void) test_oap_cli_rejects_md_mismatch; (void) test_oap_srv_rejects_md_mismatch; + (void) test_oap_server_cert_hidden; + (void) test_oap_sealed_tamper; + (void) test_oap_rekey; + (void) test_oap_rekey_badcache; ret = TEST_RC_SKIP; #endif diff --git a/src/irmd/oap/tests/oap_test_ml_dsa.c b/src/irmd/oap/tests/oap_test_ml_dsa.c index 19be7400..8691aa00 100644 --- a/src/irmd/oap/tests/oap_test_ml_dsa.c +++ b/src/irmd/oap/tests/oap_test_ml_dsa.c @@ -179,6 +179,7 @@ int load_server_kem_pk(const char * name, 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; @@ -254,6 +255,22 @@ static int test_oap_srv_md_pin_exempts_pqc(void) return roundtrip_auth_only(root_ca_crt_ml, im_ca_crt_ml); } +static int test_oap_rekey(void) +{ + test_cfg_init(NID_X25519, NID_aes_256_gcm, NID_sha256, + 0, NO_CLI_AUTH); + + return roundtrip_rekey(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_rekey_badcache(void) +{ + test_cfg_init(NID_X25519, NID_aes_256_gcm, NID_sha256, + 0, NO_CLI_AUTH); + + return roundtrip_rekey_badcache(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(), @@ -449,10 +466,15 @@ int oap_test_ml_dsa(int argc, ret |= test_oap_corrupted_request(); ret |= test_oap_corrupted_response(); ret |= test_oap_truncated_request(); + + ret |= test_oap_rekey(); + ret |= test_oap_rekey_badcache(); #else (void) test_oap_roundtrip_auth_only; (void) test_oap_cli_md_pin_exempts_pqc; (void) test_oap_srv_md_pin_exempts_pqc; + (void) test_oap_rekey; + (void) test_oap_rekey_badcache; (void) test_oap_roundtrip_kem; (void) test_oap_roundtrip_kem_all; (void) test_oap_kem_srv_uncfg; |
