summaryrefslogtreecommitdiff
path: root/src/irmd/oap/tests
diff options
context:
space:
mode:
authorDimitri Staessens <dimitri@ouroboros.rocks>2026-06-21 14:07:00 +0200
committerSander Vrijders <sander@ouroboros.rocks>2026-06-29 08:33:00 +0200
commit55a8136859d82d9bdb8f85abb25290177ca7e561 (patch)
treee3b87e09322867245a49fe11c51b621efcff2730 /src/irmd/oap/tests
parent552a4c4469db1cedacc02eb4f9969afe73e0fb42 (diff)
downloadouroboros-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.c168
-rw-r--r--src/irmd/oap/tests/common.h11
-rw-r--r--src/irmd/oap/tests/oap_test.c354
-rw-r--r--src/irmd/oap/tests/oap_test_ml_dsa.c22
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;