summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/irmd/oap/tests/common.c128
-rw-r--r--src/irmd/oap/tests/common.h12
-rw-r--r--src/irmd/oap/tests/oap_test.c418
-rw-r--r--src/irmd/oap/tests/oap_test_ml_dsa.c178
4 files changed, 554 insertions, 182 deletions
diff --git a/src/irmd/oap/tests/common.c b/src/irmd/oap/tests/common.c
index 49ea9187..16d52c63 100644
--- a/src/irmd/oap/tests/common.c
+++ b/src/irmd/oap/tests/common.c
@@ -37,8 +37,6 @@ int load_srv_sec_config(const struct name_info * info,
memset(cfg, 0, sizeof(*cfg));
cfg->a.req = test_cfg.srv.req_auth;
- if (test_cfg.srv.cacert != NULL)
- strcpy(cfg->a.cacert, test_cfg.srv.cacert);
/* Digest is kept without kex, as in parse_sec_config */
SET_KEX_DIGEST_NID(cfg, test_cfg.srv.md);
@@ -62,8 +60,6 @@ int load_cli_sec_config(const struct name_info * info,
memset(cfg, 0, sizeof(*cfg));
cfg->a.req = test_cfg.cli.req_auth;
- if (test_cfg.cli.cacert != NULL)
- strcpy(cfg->a.cacert, test_cfg.cli.cacert);
/* Digest is kept without kex, as in parse_sec_config */
SET_KEX_DIGEST_NID(cfg, test_cfg.cli.md);
@@ -261,17 +257,35 @@ int roundtrip_auth_only(const char * root_ca,
return TEST_RC_FAIL;
}
+static const char * rekey_mode(bool srv_auth,
+ bool cli_auth)
+{
+ if (srv_auth && cli_auth)
+ return "mutual";
+
+ if (srv_auth)
+ return "srv-only";
+
+ if (cli_auth)
+ return "cli-only";
+
+ return "none";
+}
+
int roundtrip_rekey(const char * root_ca,
- const char * im_ca_str)
+ const char * im_ca_str,
+ bool srv_auth,
+ bool cli_auth)
{
struct oap_test_ctx ctx;
+ uint8_t key0[SYMMKEYSZ];
+ const char * mode = rekey_mode(srv_auth, cli_auth);
- TEST_START();
+ TEST_START("(%s)", mode);
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;
@@ -292,11 +306,20 @@ int roundtrip_rekey(const char * root_ca,
goto fail_cleanup;
}
- if (ctx.cli_crt.len == 0) {
+ /* The client caches the server cert only if the server authed. */
+ if (srv_auth && ctx.cli_crt.len == 0) {
printf("Server cert was not cached for re-key.\n");
goto fail_cleanup;
}
+ /* The server caches the client cert only if the client authed. */
+ if (cli_auth && ctx.srv_crt.len == 0) {
+ printf("Client cert was not cached by the server.\n");
+ goto fail_cleanup;
+ }
+
+ memcpy(key0, ctx.cli.key, SYMMKEYSZ);
+
/* Re-key: cert dropped on the wire, verified against the cache. */
freebuf(ctx.req_hdr);
freebuf(ctx.resp_hdr);
@@ -324,24 +347,36 @@ int roundtrip_rekey(const char * root_ca,
goto fail_cleanup;
}
+ if (memcmp(ctx.cli.key, key0, SYMMKEYSZ) == 0) {
+ printf("Re-key did not produce a fresh key.\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.cli.nid == NID_undef || ctx.srv.nid == NID_undef) {
+ printf("Cipher not set after re-key.\n");
+ goto fail_cleanup;
+ }
+
oap_test_teardown(&ctx);
- TEST_SUCCESS();
+ TEST_SUCCESS("(%s)", mode);
return TEST_RC_SUCCESS;
fail_cleanup:
oap_test_teardown(&ctx);
fail:
- TEST_FAIL();
+ TEST_FAIL("(%s)", mode);
return TEST_RC_FAIL;
}
int roundtrip_rekey_badcache(const char * root_ca,
- const char * im_ca_str)
+ const char * im_ca_str,
+ bool cli_auth)
{
struct oap_test_ctx ctx;
+ const char * mode = rekey_mode(true, cli_auth);
- TEST_START();
+ TEST_START("(%s)", mode);
if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0)
goto fail;
@@ -366,7 +401,7 @@ int roundtrip_rekey_badcache(const char * root_ca,
goto fail_cleanup;
}
- /* Corrupt the cached cert: the re-key must fail closed. */
+ /* Corrupt the client's cached server cert: re-key must fail closed. */
ctx.cli_crt.data[ctx.cli_crt.len / 2] ^= 0xFF;
freebuf(ctx.req_hdr);
@@ -392,13 +427,76 @@ int roundtrip_rekey_badcache(const char * root_ca,
oap_test_teardown(&ctx);
- TEST_SUCCESS();
+ TEST_SUCCESS("(%s)", mode);
return TEST_RC_SUCCESS;
fail_cleanup:
oap_test_teardown(&ctx);
fail:
- TEST_FAIL();
+ TEST_FAIL("(%s)", mode);
+ return TEST_RC_FAIL;
+}
+
+int roundtrip_rekey_srv_badcache(const char * root_ca,
+ const char * im_ca_str,
+ bool srv_auth)
+{
+ struct oap_test_ctx ctx;
+ const char * mode = rekey_mode(srv_auth, true);
+
+ TEST_START("(%s)", mode);
+
+ 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.srv_crt.len == 0) {
+ printf("Client cert was not cached by the server.\n");
+ goto fail_cleanup;
+ }
+
+ /* Corrupt the server's cached client cert: re-key must fail closed. */
+ ctx.srv_crt.data[ctx.srv_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("Server accepted a corrupted cached client cert.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS("(%s)", mode);
+
+ return TEST_RC_SUCCESS;
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL("(%s)", mode);
return TEST_RC_FAIL;
}
diff --git a/src/irmd/oap/tests/common.h b/src/irmd/oap/tests/common.h
index c47096fb..5e32bff0 100644
--- a/src/irmd/oap/tests/common.h
+++ b/src/irmd/oap/tests/common.h
@@ -39,7 +39,6 @@ struct test_sec_cfg {
int kem_mode; /* KEM encapsulation mode (0 for ECDH) */
bool auth; /* Use authentication (certificates) */
bool req_auth; /* Require peer authentication */
- const char * cacert; /* Pinned issuing CA path */
};
/* Test configuration - set by each test before running roundtrip */
@@ -94,10 +93,17 @@ 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);
+ const char * im_ca_str,
+ bool srv_auth,
+ bool cli_auth);
int roundtrip_rekey_badcache(const char * root_ca,
- const char * im_ca_str);
+ const char * im_ca_str,
+ bool cli_auth);
+
+int roundtrip_rekey_srv_badcache(const char * root_ca,
+ const char * im_ca_str,
+ bool srv_auth);
int roundtrip_kex_only(void);
diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c
index fc10150b..99c0fffe 100644
--- a/src/irmd/oap/tests/oap_test.c
+++ b/src/irmd/oap/tests/oap_test.c
@@ -117,6 +117,13 @@ static void test_default_cfg(void)
test_cfg.cli.auth = NO_AUTH;
}
+/* Encrypted, unauthenticated on both sides. */
+static void test_enc_noauth_cfg(void)
+{
+ test_default_cfg();
+ test_cfg.srv.auth = NO_AUTH;
+}
+
static int test_oap_auth_init_fini(void)
{
TEST_START();
@@ -203,37 +210,71 @@ 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)
+static int test_oap_rekey(bool srv_auth,
+ bool cli_auth)
{
test_default_cfg();
+ test_cfg.srv.auth = srv_auth;
+ test_cfg.cli.auth = cli_auth;
+
+ return roundtrip_rekey(root_ca_crt_ec, im_ca_crt_ec,
+ srv_auth, cli_auth);
+}
+
+static int test_oap_rekey_all(void)
+{
+ int ret = 0;
+
+ ret |= test_oap_rekey(AUTH, NO_AUTH);
+ ret |= test_oap_rekey(AUTH, AUTH);
+ ret |= test_oap_rekey(NO_AUTH, AUTH);
+ ret |= test_oap_rekey(NO_AUTH, NO_AUTH);
- return roundtrip_rekey(root_ca_crt_ec, im_ca_crt_ec);
+ return ret;
}
-static int test_oap_rekey_badcache(void)
+static int test_oap_rekey_srv_badcache(bool srv_auth)
{
test_default_cfg();
+ test_cfg.srv.auth = srv_auth;
+ test_cfg.cli.auth = AUTH;
- return roundtrip_rekey_badcache(root_ca_crt_ec, im_ca_crt_ec);
+ return roundtrip_rekey_srv_badcache(root_ca_crt_ec, im_ca_crt_ec,
+ srv_auth);
}
-static int test_oap_roundtrip_kex_only(void)
+static int test_oap_rekey_srv_badcache_all(void)
{
- memset(&test_cfg, 0, sizeof(test_cfg));
+ int ret = 0;
- /* 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;
+ ret |= test_oap_rekey_srv_badcache(AUTH);
+ ret |= test_oap_rekey_srv_badcache(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 ret;
+}
+
+static int test_oap_rekey_badcache(bool cli_auth)
+{
+ test_default_cfg();
+ test_cfg.cli.auth = cli_auth;
+
+ return roundtrip_rekey_badcache(root_ca_crt_ec, im_ca_crt_ec,
+ cli_auth);
+}
+
+static int test_oap_rekey_badcache_all(void)
+{
+ int ret = 0;
+
+ ret |= test_oap_rekey_badcache(NO_AUTH);
+ ret |= test_oap_rekey_badcache(AUTH);
+
+ return ret;
+}
+
+static int test_oap_roundtrip_kex_only(void)
+{
+ test_enc_noauth_cfg();
return roundtrip_kex_only();
}
@@ -434,8 +475,13 @@ static int test_oap_deflated_length_field(void)
/* Header field offsets for byte manipulation */
#define OAP_CIPHER_NID_OFFSET 24
+#define OAP_KDF_NID_OFFSET 26
+#define OAP_MD_NID_OFFSET 28
#define OAP_KEX_LEN_OFFSET 32
+/* A NID the crypto backend does not recognise; guarded by a test below. */
+#define UNSUPPORTED_NID 9999
+
/* Server rejects request when cipher NID set but no KEX data provided */
static int test_oap_nid_without_kex(void)
{
@@ -443,20 +489,9 @@ static int test_oap_nid_without_kex(void)
uint16_t cipher_nid;
uint16_t zero = 0;
- TEST_START();
+ test_enc_noauth_cfg();
- /* 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;
+ TEST_START();
if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
goto fail;
@@ -491,26 +526,44 @@ static int test_oap_nid_without_kex(void)
return TEST_RC_FAIL;
}
-/* Server rejects OAP request with unsupported cipher NID */
-static int test_oap_unsupported_nid(void)
+/* Guard: the tamper tests below rely on UNSUPPORTED_NID being invalid. */
+static int test_oap_unsupported_nid_undefined(void)
+{
+ TEST_START();
+
+ if (crypt_cipher_rank(UNSUPPORTED_NID) >= 0) {
+ printf("UNSUPPORTED_NID is a valid cipher NID.\n");
+ goto fail;
+ }
+
+ if (crypt_kdf_rank(UNSUPPORTED_NID) >= 0) {
+ printf("UNSUPPORTED_NID is a valid KDF NID.\n");
+ goto fail;
+ }
+
+ if (md_validate_nid(UNSUPPORTED_NID) >= 0) {
+ printf("UNSUPPORTED_NID is a valid digest NID.\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Server rejects a request whose cipher/kdf/digest NID is unsupported */
+static int test_oap_unsupported_nid(size_t offset,
+ const char * label)
{
struct oap_test_ctx ctx;
uint16_t bad_nid;
- TEST_START();
+ test_enc_noauth_cfg();
- /* 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;
+ TEST_START("(%s)", label);
if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
goto fail;
@@ -520,48 +573,47 @@ static int test_oap_unsupported_nid(void)
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));
+ bad_nid = hton16(UNSUPPORTED_NID);
+ memcpy(ctx.req_hdr.data + offset, &bad_nid, sizeof(bad_nid));
if (oap_srv_process_ctx(&ctx) == 0) {
- printf("Server should reject unsupported cipher NID.\n");
+ printf("Server should reject unsupported %s NID.\n", label);
goto fail_cleanup;
}
oap_test_teardown(&ctx);
- TEST_SUCCESS();
+ TEST_SUCCESS("(%s)", label);
return TEST_RC_SUCCESS;
fail_cleanup:
oap_test_teardown(&ctx);
fail:
- TEST_FAIL();
+ TEST_FAIL("(%s)", label);
return TEST_RC_FAIL;
}
+static int test_oap_unsupported_nid_all(void)
+{
+ int ret = 0;
+
+ ret |= test_oap_unsupported_nid(OAP_CIPHER_NID_OFFSET, "cipher");
+ ret |= test_oap_unsupported_nid(OAP_KDF_NID_OFFSET, "kdf");
+ ret |= test_oap_unsupported_nid(OAP_MD_NID_OFFSET, "digest");
+
+ return ret;
+}
+
/* 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;
+ test_enc_noauth_cfg();
+
+ TEST_START();
if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
goto fail;
@@ -821,21 +873,9 @@ static int test_oap_cli_rejects_downgrade(void)
struct oap_test_ctx ctx;
uint16_t weak;
- TEST_START();
-
- memset(&test_cfg, 0, sizeof(test_cfg));
+ test_enc_noauth_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;
+ TEST_START();
if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
goto fail;
@@ -884,21 +924,12 @@ 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_enc_noauth_cfg();
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;
+
+ TEST_START();
if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
goto fail;
@@ -1064,15 +1095,17 @@ static int test_oap_roundtrip_md_all(void)
/* 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)
+/* Server rejects a request whose timestamp is outside the replay window */
+static int test_oap_ts_reject(int delta_sec,
+ const char * label)
{
struct oap_test_ctx ctx;
- struct timespec old_ts;
- uint64_t old_stamp;
+ struct timespec ts;
+ uint64_t stamp;
test_default_cfg();
- TEST_START();
+ TEST_START("(%s)", label);
if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
goto fail;
@@ -1087,78 +1120,39 @@ static int test_oap_outdated_packet(void)
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));
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += delta_sec;
+ stamp = hton64(TS_TO_UINT64(ts));
+ memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &stamp,
+ sizeof(stamp));
if (oap_srv_process_ctx(&ctx) == 0) {
- printf("Server should reject outdated packet.\n");
+ printf("Server should reject %s packet.\n", label);
goto fail_cleanup;
}
oap_test_teardown(&ctx);
- TEST_SUCCESS();
+ TEST_SUCCESS("(%s)", label);
return TEST_RC_SUCCESS;
fail_cleanup:
oap_test_teardown(&ctx);
fail:
- TEST_FAIL();
+ TEST_FAIL("(%s)", label);
return TEST_RC_FAIL;
}
-/* Test that packets from the future are rejected */
-static int test_oap_future_packet(void)
+static int test_oap_ts_reject_all(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();
+ int ret = 0;
- return TEST_RC_SUCCESS;
+ /* Past the 20s replay window, and past the 100ms future slack. */
+ ret |= test_oap_ts_reject(-(OAP_REPLAY_TIMER + 10), "outdated");
+ ret |= test_oap_ts_reject(1, "future");
- fail_cleanup:
- oap_test_teardown(&ctx);
- fail:
- TEST_FAIL();
- return TEST_RC_FAIL;
+ return ret;
}
/* Test that replayed packets (same ID + timestamp) are rejected */
@@ -1300,9 +1294,12 @@ static int test_oap_replay_generations(void)
{
struct oap_hdr h;
struct timespec now;
+ struct timespec wait;
uint8_t id[OAP_ID_SIZE];
uint64_t cur;
uint64_t gen_ns;
+ uint64_t off;
+ uint64_t wait_ns;
uint64_t stamp_a;
uint64_t stamp_b;
@@ -1313,10 +1310,21 @@ static int test_oap_replay_generations(void)
goto fail;
}
- clock_gettime(CLOCK_REALTIME, &now);
- cur = TS_TO_UINT64(now);
gen_ns = (uint64_t) OAP_REPLAY_TIMER * BILLION;
+ /* Prev-gen stamp flakes on staleness near a generation top. */
+ clock_gettime(CLOCK_REALTIME, &now);
+ cur = TS_TO_UINT64(now);
+ off = cur % gen_ns;
+ if (gen_ns - off < BILLION) {
+ wait_ns = gen_ns - off + MILLION;
+ wait.tv_sec = (time_t) (wait_ns / BILLION);
+ wait.tv_nsec = (long) (wait_ns % BILLION);
+ nanosleep(&wait, NULL);
+ clock_gettime(CLOCK_REALTIME, &now);
+ cur = TS_TO_UINT64(now);
+ }
+
/* stamp_a in the current generation, stamp_b one generation older */
stamp_a = cur;
stamp_b = (cur / gen_ns) * gen_ns - 1;
@@ -1826,6 +1834,101 @@ static int test_oap_sealed_tamper(void)
return TEST_RC_FAIL;
}
+/* Cleartext md-only: rsp_tag echoes H(request) and is the sole gate */
+static int test_oap_cleartext_echo_tamper(void)
+{
+ struct oap_test_ctx ctx;
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = NO_AUTH;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ 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;
+ }
+
+ /* rsp_tag is the trailing field of an unsealed, unsigned response */
+ ctx.resp_hdr.data[ctx.resp_hdr.len - 1] ^= 0xFF;
+
+ if (oap_cli_complete_ctx(&ctx) == 0) {
+ printf("Client accepted a tampered request echo.\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;
+}
+
+/* Client rejects a response whose session ID does not match the request */
+static int test_oap_response_id_tamper(void)
+{
+ struct oap_test_ctx ctx;
+
+ /* Cleartext md-only: no seal, so the ID check itself must reject. */
+ memset(&test_cfg, 0, sizeof(test_cfg));
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = NO_AUTH;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ 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;
+ }
+
+ /* The session ID is the first field of the fixed header. */
+ ctx.resp_hdr.data[0] ^= 0xFF;
+
+ if (oap_cli_complete_ctx(&ctx) == 0) {
+ printf("Client accepted a mismatched response ID.\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)
{
@@ -1842,8 +1945,9 @@ 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_rekey_all();
+ ret |= test_oap_rekey_badcache_all();
+ ret |= test_oap_rekey_srv_badcache_all();
ret |= test_oap_roundtrip_all();
ret |= test_oap_roundtrip_md_all();
@@ -1855,7 +1959,8 @@ int oap_test(int argc,
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_unsupported_nid_undefined();
+ ret |= test_oap_unsupported_nid_all();
ret |= test_oap_cipher_mismatch();
ret |= test_oap_srv_enc_cli_none();
@@ -1864,8 +1969,7 @@ int oap_test(int argc,
ret |= test_oap_cli_rejects_suite_swap();
ret |= test_oap_srv_rejects_weak_kex();
- ret |= test_oap_outdated_packet();
- ret |= test_oap_future_packet();
+ ret |= test_oap_ts_reject_all();
ret |= test_oap_replay_packet();
ret |= test_oap_missing_root_ca();
ret |= test_oap_server_name_mismatch();
@@ -1880,6 +1984,8 @@ int oap_test(int argc,
ret |= test_oap_server_cert_hidden();
ret |= test_oap_sealed_tamper();
+ ret |= test_oap_cleartext_echo_tamper();
+ ret |= test_oap_response_id_tamper();
#else
(void) test_oap_roundtrip_auth_only;
(void) test_oap_roundtrip_kex_only;
diff --git a/src/irmd/oap/tests/oap_test_ml_dsa.c b/src/irmd/oap/tests/oap_test_ml_dsa.c
index 21f2440b..dc7842ac 100644
--- a/src/irmd/oap/tests/oap_test_ml_dsa.c
+++ b/src/irmd/oap/tests/oap_test_ml_dsa.c
@@ -255,20 +255,66 @@ 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)
+static int test_oap_rekey(bool srv_auth,
+ bool cli_auth)
{
test_cfg_init(NID_X25519, NID_aes_256_gcm, NID_sha256,
- 0, NO_CLI_AUTH);
+ 0, cli_auth);
+ test_cfg.srv.auth = srv_auth;
+
+ return roundtrip_rekey(root_ca_crt_ml, im_ca_crt_ml,
+ srv_auth, cli_auth);
+}
+
+static int test_oap_rekey_all(void)
+{
+ int ret = 0;
+
+ ret |= test_oap_rekey(CLI_AUTH, NO_CLI_AUTH);
+ ret |= test_oap_rekey(CLI_AUTH, CLI_AUTH);
+ ret |= test_oap_rekey(NO_CLI_AUTH, CLI_AUTH);
+ ret |= test_oap_rekey(NO_CLI_AUTH, NO_CLI_AUTH);
- return roundtrip_rekey(root_ca_crt_ml, im_ca_crt_ml);
+ return ret;
}
-static int test_oap_rekey_badcache(void)
+static int test_oap_rekey_badcache(bool cli_auth)
{
test_cfg_init(NID_X25519, NID_aes_256_gcm, NID_sha256,
- 0, NO_CLI_AUTH);
+ 0, cli_auth);
+
+ return roundtrip_rekey_badcache(root_ca_crt_ml, im_ca_crt_ml,
+ cli_auth);
+}
+
+static int test_oap_rekey_badcache_all(void)
+{
+ int ret = 0;
+
+ ret |= test_oap_rekey_badcache(NO_CLI_AUTH);
+ ret |= test_oap_rekey_badcache(CLI_AUTH);
- return roundtrip_rekey_badcache(root_ca_crt_ml, im_ca_crt_ml);
+ return ret;
+}
+
+static int test_oap_rekey_srv_badcache(bool srv_auth)
+{
+ test_cfg_init(NID_X25519, NID_aes_256_gcm, NID_sha256,
+ 0, CLI_AUTH);
+ test_cfg.srv.auth = srv_auth;
+
+ return roundtrip_rekey_srv_badcache(root_ca_crt_ml, im_ca_crt_ml,
+ srv_auth);
+}
+
+static int test_oap_rekey_srv_badcache_all(void)
+{
+ int ret = 0;
+
+ ret |= test_oap_rekey_srv_badcache(CLI_AUTH);
+ ret |= test_oap_rekey_srv_badcache(NO_CLI_AUTH);
+
+ return ret;
}
static int test_oap_corrupted_request(void)
@@ -366,6 +412,114 @@ static int test_oap_roundtrip_kem_all(void)
return ret;
}
+/* Re-key over a KEM KEX: forced ephemeral server-encap + cert-drop cache. */
+static int test_oap_rekey_kem(int kex,
+ int kem_mode)
+{
+ struct oap_test_ctx ctx;
+ const char * kex_str = kex_nid_to_str(kex);
+ const char * mode_str = "srv";
+ uint8_t key0[SYMMKEYSZ];
+
+ if (kem_mode == CLI_ENCAP)
+ mode_str = "cli";
+
+ 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("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;
+ }
+
+ memcpy(key0, ctx.cli.key, SYMMKEYSZ);
+
+ 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;
+ }
+
+ if (memcmp(ctx.cli.key, key0, SYMMKEYSZ) == 0) {
+ printf("Re-key did not produce a fresh key.\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_rekey_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_rekey_kem(kex_supported_nids[i], SRV_ENCAP);
+ ret |= test_oap_rekey_kem(kex_supported_nids[i], CLI_ENCAP);
+ }
+
+ return ret;
+}
+
static int test_oap_kem_srv_uncfg(int kex)
{
struct oap_test_ctx ctx;
@@ -467,16 +621,24 @@ int oap_test_ml_dsa(int argc,
ret |= test_oap_corrupted_response();
ret |= test_oap_truncated_request();
- ret |= test_oap_rekey();
- ret |= test_oap_rekey_badcache();
+ ret |= test_oap_rekey_all();
+ ret |= test_oap_rekey_badcache_all();
+ ret |= test_oap_rekey_srv_badcache_all();
+ ret |= test_oap_rekey_kem_all();
#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_all;
(void) test_oap_rekey_badcache;
+ (void) test_oap_rekey_badcache_all;
+ (void) test_oap_rekey_srv_badcache;
+ (void) test_oap_rekey_srv_badcache_all;
(void) test_oap_roundtrip_kem;
(void) test_oap_roundtrip_kem_all;
+ (void) test_oap_rekey_kem;
+ (void) test_oap_rekey_kem_all;
(void) test_oap_kem_srv_uncfg;
(void) test_oap_kem_srv_uncfg_all;
(void) test_oap_corrupted_request;