summaryrefslogtreecommitdiff
path: root/src/irmd/oap/tests/oap_test.c
diff options
context:
space:
mode:
authorDimitri Staessens <dimitri@ouroboros.rocks>2026-06-12 19:34:27 +0200
committerSander Vrijders <sander@ouroboros.rocks>2026-06-29 08:32:58 +0200
commit977bcac2d56a8793ed93b4aac7016ef36b51a07f (patch)
tree7e26553a57cbdc75d9c33b25fe228631dea36142 /src/irmd/oap/tests/oap_test.c
parent67c55d5869d5473e5139614637f31ea37746181d (diff)
downloadouroboros-977bcac2d56a8793ed93b4aac7016ef36b51a07f.tar.gz
ouroboros-977bcac2d56a8793ed93b4aac7016ef36b51a07f.zip
irmd: Add issuer and digest pinning to OAP
A peer certificate that verifies against the CA store could have been issued by any trusted CA, and a peer could pick any supported digest for its signature. Tighten the authentication contract with two local policies. cacert= pins the issuing CA: a peer certificate, if presented, must chain through the pinned CA. Whether a certificate is mandatory at all remains controlled by auth= alone. digest= now also pins the signature digest: a classical peer must sign with the locally configured digest, and may not omit the digest NID to fall back to the key's default digest. PQC signatures (ML-DSA, SLH-DSA) have an intrinsic digest and may be NID_undef. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
Diffstat (limited to 'src/irmd/oap/tests/oap_test.c')
-rw-r--r--src/irmd/oap/tests/oap_test.c223
1 files changed, 223 insertions, 0 deletions
diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c
index fd2c5629..311177b7 100644
--- a/src/irmd/oap/tests/oap_test.c
+++ b/src/irmd/oap/tests/oap_test.c
@@ -45,7 +45,10 @@
#include "common.h"
#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#ifdef HAVE_OPENSSL
#include <openssl/evp.h>
@@ -1306,6 +1309,213 @@ 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)
+{
+ struct oap_test_ctx ctx;
+ char path[32];
+
+ test_default_cfg();
+
+ 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;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail_unlink;
+
+ 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) != expected) {
+ printf("Pinned CA gave wrong verdict.\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)
+{
+ struct oap_test_ctx ctx;
+ char path[32];
+
+ test_default_cfg();
+ test_cfg.cli.auth = AUTH;
+
+ 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;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail_unlink;
+
+ 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");
+ 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)
+{
+ struct oap_test_ctx ctx;
+
+ test_default_cfg();
+ test_cfg.srv.md = NID_sha384;
+
+ 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 digest mismatch.\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 rejects a client signature with a different digest */
+static int test_oap_srv_rejects_md_mismatch(void)
+{
+ struct oap_test_ctx ctx;
+
+ test_default_cfg();
+ test_cfg.cli.auth = AUTH;
+ test_cfg.cli.md = NID_sha384;
+
+ 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 digest mismatch.\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)
{
@@ -1347,6 +1557,15 @@ int oap_test(int argc,
ret |= test_oap_cli_requires_srv_auth();
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();
#else
(void) test_oap_roundtrip_auth_only;
(void) test_oap_roundtrip_kex_only;
@@ -1375,6 +1594,10 @@ int oap_test(int argc,
(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;
ret = TEST_RC_SKIP;
#endif