From be4aca20962f88d2cb2d8d92f1e0462877c40543 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Mon, 4 May 2026 12:00:03 -0600 Subject: [PATCH 1/2] Add support for multi-root cert verification API --- src/wh_client_cert.c | 311 +++++++++++++++++++++++++ src/wh_message_cert.c | 35 +++ src/wh_server_cert.c | 219 +++++++++++++++--- test/wh_test_cert.c | 347 ++++++++++++++++++++++++++++ test/wh_test_check_struct_padding.c | 3 + wolfhsm/wh_client.h | 208 +++++++++++++++++ wolfhsm/wh_message_cert.h | 81 ++++++- wolfhsm/wh_server_cert.h | 36 +++ wolfhsm/wh_settings.h | 15 ++ 9 files changed, 1217 insertions(+), 38 deletions(-) diff --git a/src/wh_client_cert.c b/src/wh_client_cert.c index d233da5e8..26b2f3cae 100644 --- a/src/wh_client_cert.c +++ b/src/wh_client_cert.c @@ -507,6 +507,168 @@ int wh_Client_CertVerifyAndCacheLeafPubKey( inout_keyId, out_rc); } +/* Helper: send a multi-root verify request */ +static int _certVerifyMultiRootRequest(whClientContext* c, const uint8_t* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, uint16_t verifyFlags, + whNvmFlags cachedKeyFlags, whKeyId keyId) +{ + whMessageCert_VerifyMultiRootRequest req = {0}; + uint8_t buffer[WOLFHSM_CFG_COMM_DATA_LEN] = {0}; + uint16_t hdr_len = sizeof(req); + uint32_t roots_bytes = (uint32_t)numRoots * sizeof(whNvmId); + uint8_t* roots_dst; + uint8_t* cert_dst; + + if ((c == NULL) || (cert == NULL) || (cert_len == 0) || + (trustedRootNvmIds == NULL) || (numRoots == 0) || + (numRoots > WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS) || + (roots_bytes > sizeof(buffer) - hdr_len) || + (cert_len > sizeof(buffer) - hdr_len - roots_bytes)) { + return WH_ERROR_BADARGS; + } + + /* Prepare request */ + req.cert_len = cert_len; + req.numRoots = numRoots; + req.flags = verifyFlags; + req.cachedKeyFlags = cachedKeyFlags; + req.keyId = keyId; + + /* Pack header, root array, then certificate data */ + memcpy(buffer, &req, hdr_len); + roots_dst = buffer + hdr_len; + memcpy(roots_dst, trustedRootNvmIds, roots_bytes); + cert_dst = roots_dst + roots_bytes; + memcpy(cert_dst, cert, cert_len); + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_CERT, + WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT, + hdr_len + roots_bytes + cert_len, buffer); +} + +/* Helper: receive a multi-root verify response */ +static int _certVerifyMultiRootResponse(whClientContext* c, whKeyId* out_keyId, + int32_t* out_rc) +{ + int rc; + uint16_t group; + uint16_t action; + uint16_t size; + whMessageCert_VerifyResponse resp; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + rc = wh_Client_RecvResponse(c, &group, &action, &size, &resp); + if (rc == 0) { + if ((group != WH_MESSAGE_GROUP_CERT) || + (action != WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT) || + (size != sizeof(resp))) { + rc = WH_ERROR_ABORTED; + } + else { + if (out_rc != NULL) { + *out_rc = resp.rc; + } + if (out_keyId != NULL) { + *out_keyId = resp.keyId; + } + } + } + + return rc; +} + +/* Helper: blocking multi-root verify */ +static int _certVerifyMultiRoot(whClientContext* c, const uint8_t* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, uint16_t verifyFlags, + whNvmFlags cachedKeyFlags, whKeyId* inout_keyId, + int32_t* out_rc) +{ + int rc = 0; + whKeyId keyId = WH_KEYID_ERASED; + + if ((c == NULL) || (cert == NULL) || (cert_len == 0) || + (trustedRootNvmIds == NULL) || (numRoots == 0)) { + return WH_ERROR_BADARGS; + } + + if (inout_keyId != NULL) { + keyId = *inout_keyId; + } + + do { + rc = _certVerifyMultiRootRequest(c, cert, cert_len, trustedRootNvmIds, + numRoots, verifyFlags, cachedKeyFlags, + keyId); + } while (rc == WH_ERROR_NOTREADY); + + if (rc == 0) { + do { + rc = _certVerifyMultiRootResponse(c, inout_keyId, out_rc); + } while (rc == WH_ERROR_NOTREADY); + } + + return rc; +} + +int wh_Client_CertVerifyMultiRootRequest(whClientContext* c, + const uint8_t* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots) +{ + return _certVerifyMultiRootRequest(c, cert, cert_len, trustedRootNvmIds, + numRoots, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, WH_KEYID_ERASED); +} + +int wh_Client_CertVerifyMultiRootResponse(whClientContext* c, int32_t* out_rc) +{ + return _certVerifyMultiRootResponse(c, NULL, out_rc); +} + +int wh_Client_CertVerifyMultiRoot(whClientContext* c, const uint8_t* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, int32_t* out_rc) +{ + return _certVerifyMultiRoot(c, cert, cert_len, trustedRootNvmIds, numRoots, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, + NULL, out_rc); +} + +int wh_Client_CertVerifyMultiRootAndCacheLeafPubKeyRequest( + whClientContext* c, const uint8_t* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId keyId) +{ + return _certVerifyMultiRootRequest( + c, cert, cert_len, trustedRootNvmIds, numRoots, + WH_CERT_FLAGS_CACHE_LEAF_PUBKEY, cachedKeyFlags, keyId); +} + +int wh_Client_CertVerifyMultiRootAndCacheLeafPubKeyResponse(whClientContext* c, + whKeyId* out_keyId, + int32_t* out_rc) +{ + return _certVerifyMultiRootResponse(c, out_keyId, out_rc); +} + +int wh_Client_CertVerifyMultiRootAndCacheLeafPubKey( + whClientContext* c, const uint8_t* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId* inout_keyId, int32_t* out_rc) +{ + return _certVerifyMultiRoot(c, cert, cert_len, trustedRootNvmIds, numRoots, + WH_CERT_FLAGS_CACHE_LEAF_PUBKEY, cachedKeyFlags, + inout_keyId, out_rc); +} + #ifdef WOLFHSM_CFG_DMA int wh_Client_CertAddTrustedDmaRequest(whClientContext* c, whNvmId id, @@ -801,6 +963,155 @@ int wh_Client_CertVerifyDmaAndCacheLeafPubKey( inout_keyId, out_rc); } +/* Helper: send a multi-root DMA verify request */ +static int _certVerifyMultiRootDmaRequest( + whClientContext* c, const void* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, uint16_t verifyFlags, + whNvmFlags cachedKeyFlags, whKeyId keyId) +{ + whMessageCert_VerifyMultiRootDmaRequest req = {0}; + + if ((c == NULL) || (trustedRootNvmIds == NULL) || (numRoots == 0) || + (numRoots > WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS)) { + return WH_ERROR_BADARGS; + } + + req.cert_addr = (uint64_t)(uintptr_t)cert; + req.cert_len = cert_len; + req.numRoots = numRoots; + req.flags = verifyFlags; + req.cachedKeyFlags = cachedKeyFlags; + req.keyId = keyId; + /* Only the first numRoots entries are meaningful; remaining slots stay + * zeroed by the initializer above. */ + memcpy(req.trustedRootNvmIds, trustedRootNvmIds, + (size_t)numRoots * sizeof(whNvmId)); + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_CERT, + WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT_DMA, + sizeof(req), &req); +} + +/* Helper: receive a multi-root DMA verify response */ +static int _certVerifyMultiRootDmaResponse(whClientContext* c, + whKeyId* out_keyId, int32_t* out_rc) +{ + int rc; + uint16_t group; + uint16_t action; + uint16_t size; + whMessageCert_VerifyDmaResponse resp; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + rc = wh_Client_RecvResponse(c, &group, &action, &size, &resp); + if (rc == 0) { + if ((group != WH_MESSAGE_GROUP_CERT) || + (action != WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT_DMA) || + (size != sizeof(resp))) { + rc = WH_ERROR_ABORTED; + } + else { + if (out_rc != NULL) { + *out_rc = resp.rc; + } + if (out_keyId != NULL) { + *out_keyId = resp.keyId; + } + } + } + + return rc; +} + +/* Helper: blocking multi-root DMA verify */ +static int _certVerifyMultiRootDma(whClientContext* c, const void* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, uint16_t verifyFlags, + whNvmFlags cachedKeyFlags, + whKeyId* inout_keyId, int32_t* out_rc) +{ + int rc = 0; + whKeyId keyId = WH_KEYID_ERASED; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + if (inout_keyId != NULL) { + keyId = *inout_keyId; + } + + do { + rc = _certVerifyMultiRootDmaRequest(c, cert, cert_len, + trustedRootNvmIds, numRoots, + verifyFlags, cachedKeyFlags, keyId); + } while (rc == WH_ERROR_NOTREADY); + + if (rc == 0) { + do { + rc = _certVerifyMultiRootDmaResponse(c, inout_keyId, out_rc); + } while (rc == WH_ERROR_NOTREADY); + } + + return rc; +} + +int wh_Client_CertVerifyMultiRootDmaRequest(whClientContext* c, + const void* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots) +{ + return _certVerifyMultiRootDmaRequest( + c, cert, cert_len, trustedRootNvmIds, numRoots, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, WH_KEYID_ERASED); +} + +int wh_Client_CertVerifyMultiRootDmaResponse(whClientContext* c, + int32_t* out_rc) +{ + return _certVerifyMultiRootDmaResponse(c, NULL, out_rc); +} + +int wh_Client_CertVerifyMultiRootDma(whClientContext* c, const void* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, int32_t* out_rc) +{ + return _certVerifyMultiRootDma(c, cert, cert_len, trustedRootNvmIds, + numRoots, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL, out_rc); +} + +int wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKeyRequest( + whClientContext* c, const void* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId keyId) +{ + return _certVerifyMultiRootDmaRequest( + c, cert, cert_len, trustedRootNvmIds, numRoots, + WH_CERT_FLAGS_CACHE_LEAF_PUBKEY, cachedKeyFlags, keyId); +} + +int wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKeyResponse( + whClientContext* c, whKeyId* out_keyId, int32_t* out_rc) +{ + return _certVerifyMultiRootDmaResponse(c, out_keyId, out_rc); +} + +int wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKey( + whClientContext* c, const void* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId* inout_keyId, int32_t* out_rc) +{ + return _certVerifyMultiRootDma(c, cert, cert_len, trustedRootNvmIds, + numRoots, WH_CERT_FLAGS_CACHE_LEAF_PUBKEY, + cachedKeyFlags, inout_keyId, out_rc); +} + #endif /* WOLFHSM_CFG_DMA */ #ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT diff --git a/src/wh_message_cert.c b/src/wh_message_cert.c index 35d9b86ad..07617b72c 100644 --- a/src/wh_message_cert.c +++ b/src/wh_message_cert.c @@ -122,6 +122,21 @@ int wh_MessageCert_TranslateVerifyResponse( return 0; } +int wh_MessageCert_TranslateVerifyMultiRootRequest( + uint16_t magic, const whMessageCert_VerifyMultiRootRequest* src, + whMessageCert_VerifyMultiRootRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, cert_len); + WH_T16(magic, dest, src, numRoots); + WH_T16(magic, dest, src, flags); + WH_T16(magic, dest, src, cachedKeyFlags); + WH_T16(magic, dest, src, keyId); + return 0; +} + #ifdef WOLFHSM_CFG_DMA int wh_MessageCert_TranslateAddTrustedDmaRequest( @@ -181,6 +196,26 @@ int wh_MessageCert_TranslateVerifyDmaResponse( WH_T16(magic, dest, src, keyId); return 0; } + +int wh_MessageCert_TranslateVerifyMultiRootDmaRequest( + uint16_t magic, const whMessageCert_VerifyMultiRootDmaRequest* src, + whMessageCert_VerifyMultiRootDmaRequest* dest) +{ + int i; + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T64(magic, dest, src, cert_addr); + WH_T32(magic, dest, src, cert_len); + WH_T16(magic, dest, src, numRoots); + WH_T16(magic, dest, src, flags); + WH_T16(magic, dest, src, cachedKeyFlags); + WH_T16(magic, dest, src, keyId); + for (i = 0; i < WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS; i++) { + WH_T16(magic, dest, src, trustedRootNvmIds[i]); + } + return 0; +} #endif /* WOLFHSM_CFG_DMA */ #ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c index 44e6487ea..c43407b6e 100644 --- a/src/wh_server_cert.c +++ b/src/wh_server_cert.c @@ -257,20 +257,24 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, return wh_Nvm_Read(server->nvm, id, 0, meta.len, cert); } -/* Verify a certificate against trusted certificates */ -int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, - uint32_t cert_len, whNvmId trustedRootNvmId, - whCertFlags flags, whNvmFlags cachedKeyFlags, - whKeyId* inout_keyId) +/* Verify a certificate chain against a set of trusted root anchors */ +int wh_Server_CertVerifyMultiRoot(whServerContext* server, const uint8_t* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, whCertFlags flags, + whNvmFlags cachedKeyFlags, + whKeyId* inout_keyId) { WOLFSSL_CERT_MANAGER* cm = NULL; - - /* Stack-based buffer for root certificate */ - uint8_t root_cert[WOLFHSM_CFG_MAX_CERT_SIZE]; - uint32_t root_cert_len = sizeof(root_cert); - int rc; - - if ((server == NULL) || (cert == NULL) || (cert_len == 0)) { + uint8_t root_cert[WOLFHSM_CFG_MAX_CERT_SIZE]; + uint32_t root_cert_len; + int rc = WH_ERROR_OK; + int anchorsLoaded = 0; + uint16_t i; + + if ((server == NULL) || (cert == NULL) || (cert_len == 0) || + (trustedRootNvmIds == NULL) || (numRoots == 0) || + (numRoots > WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS)) { return WH_ERROR_BADARGS; } @@ -286,32 +290,60 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, return WH_ERROR_ABORTED; } - /* Get the trusted root certificate */ - rc = wh_Server_CertReadTrusted(server, trustedRootNvmId, root_cert, - &root_cert_len); - if (rc == WH_ERROR_OK) { - /* Load the trusted root certificate */ + /* Load each root anchor. Absent roots are silently skipped; any other + * read or load failure is fatal and reported. */ + for (i = 0; i < numRoots; i++) { + root_cert_len = sizeof(root_cert); + rc = wh_Server_CertReadTrusted(server, trustedRootNvmIds[i], root_cert, + &root_cert_len); + if (rc == WH_ERROR_NOTFOUND) { + continue; + } + if (rc != WH_ERROR_OK) { + (void)wolfSSL_CertManagerFree(cm); + return rc; + } + rc = wolfSSL_CertManagerLoadCABuffer(cm, root_cert, root_cert_len, WOLFSSL_FILETYPE_ASN1); - if (rc == WOLFSSL_SUCCESS) { - /* Verify the certificate */ - rc = _verifyChainAgainstCmStore(server, cm, cert, cert_len, flags, - cachedKeyFlags, inout_keyId); - if (rc != WH_ERROR_OK) { - rc = WH_ERROR_CERT_VERIFY; - } - } - else { - WH_DEBUG_SERVER_VERBOSE("Failed to load trusted root certificate: %d\n", rc); + if (rc != WOLFSSL_SUCCESS) { + WH_DEBUG_SERVER_VERBOSE( + "Failed to load trusted root certificate: %d\n", rc); + (void)wolfSSL_CertManagerFree(cm); + return WH_ERROR_ABORTED; } + anchorsLoaded++; + } + + /* If no anchors were loaded, the trust store is empty */ + if (anchorsLoaded == 0) { + (void)wolfSSL_CertManagerFree(cm); + return WH_ERROR_NOTFOUND; + } + + /* Verify the chain against the populated trust store */ + rc = _verifyChainAgainstCmStore(server, cm, cert, cert_len, flags, + cachedKeyFlags, inout_keyId); + if (rc != WH_ERROR_OK) { + rc = WH_ERROR_CERT_VERIFY; } - /* Clean up */ (void)wolfSSL_CertManagerFree(cm); return rc; } +/* Verify a certificate against a single trusted root certificate */ +int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, + uint32_t cert_len, whNvmId trustedRootNvmId, + whCertFlags flags, whNvmFlags cachedKeyFlags, + whKeyId* inout_keyId) +{ + return wh_Server_CertVerifyMultiRoot(server, cert, cert_len, + &trustedRootNvmId, 1, flags, + cachedKeyFlags, inout_keyId); +} + #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT) int wh_Server_CertVerifyAcert(whServerContext* server, const uint8_t* cert, uint32_t cert_len, whNvmId trustedRootNvmId) @@ -554,6 +586,83 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, *out_resp_size = sizeof(resp); }; break; + case WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT: { + whMessageCert_VerifyMultiRootRequest req = {0}; + whMessageCert_VerifyResponse resp = {0}; + const uint8_t* payload; + const whNvmId* root_ids_wire; + whNvmId root_ids[WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS]; + const uint8_t* cert_data; + uint32_t roots_bytes; + uint16_t i; + + if (req_size < sizeof(req)) { + /* Request is malformed */ + resp.rc = WH_ERROR_ABORTED; + } + else { + /* Convert request struct */ + wh_MessageCert_TranslateVerifyMultiRootRequest( + magic, (whMessageCert_VerifyMultiRootRequest*)req_packet, + &req); + + /* Validate numRoots range */ + if ((req.numRoots == 0) || + (req.numRoots > WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS)) { + resp.rc = WH_ERROR_BADARGS; + wh_MessageCert_TranslateVerifyResponse( + magic, &resp, + (whMessageCert_VerifyResponse*)resp_packet); + *out_resp_size = sizeof(resp); + break; + } + + roots_bytes = (uint32_t)req.numRoots * sizeof(whNvmId); + + /* Validate that the root array and certificate data fit + * within the request */ + if ((roots_bytes > req_size - sizeof(req)) || + (req.cert_len > req_size - sizeof(req) - roots_bytes)) { + resp.rc = WH_ERROR_BADARGS; + wh_MessageCert_TranslateVerifyResponse( + magic, &resp, + (whMessageCert_VerifyResponse*)resp_packet); + *out_resp_size = sizeof(resp); + break; + } + + /* Locate and translate the inline root id array */ + payload = (const uint8_t*)req_packet + sizeof(req); + root_ids_wire = (const whNvmId*)payload; + for (i = 0; i < req.numRoots; i++) { + root_ids[i] = wh_Translate16(magic, root_ids_wire[i]); + } + + /* Certificate data follows the root id array */ + cert_data = payload + roots_bytes; + + /* Map client keyId to server keyId space */ + whKeyId keyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, server->comm->client_id, req.keyId); + + rc = WH_SERVER_NVM_LOCK(server); + if (rc == WH_ERROR_OK) { + rc = wh_Server_CertVerifyMultiRoot( + server, cert_data, req.cert_len, root_ids, req.numRoots, + req.flags, req.cachedKeyFlags, &keyId); + resp.keyId = wh_KeyId_TranslateToClient(keyId); + + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + resp.rc = rc; + } + + /* Convert the response struct */ + wh_MessageCert_TranslateVerifyResponse( + magic, &resp, (whMessageCert_VerifyResponse*)resp_packet); + *out_resp_size = sizeof(resp); + }; break; + #ifdef WOLFHSM_CFG_DMA case WH_MESSAGE_CERT_ACTION_ADDTRUSTED_DMA: { whMessageCert_AddTrustedDmaRequest req = {0}; @@ -720,6 +829,60 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, magic, &resp, (whMessageCert_VerifyDmaResponse*)resp_packet); *out_resp_size = sizeof(resp); }; break; + + case WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT_DMA: { + whMessageCert_VerifyMultiRootDmaRequest req = {0}; + whMessageCert_VerifyDmaResponse resp = {0}; + void* cert_data = NULL; + whKeyId keyId = WH_KEYID_ERASED; + int cert_dma_pre_ok = 0; + + if (req_size != sizeof(req)) { + /* Request is malformed */ + resp.rc = WH_ERROR_ABORTED; + } + if (resp.rc == WH_ERROR_OK) { + /* Convert request struct */ + wh_MessageCert_TranslateVerifyMultiRootDmaRequest( + magic, (whMessageCert_VerifyMultiRootDmaRequest*)req_packet, + &req); + + /* Map client keyId to server keyId space */ + keyId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, server->comm->client_id, req.keyId); + + /* Process client address */ + resp.rc = wh_Server_DmaProcessClientAddress( + server, req.cert_addr, &cert_data, req.cert_len, + WH_DMA_OPER_CLIENT_READ_PRE, (whServerDmaFlags){0}); + if (resp.rc == WH_ERROR_OK) { + cert_dma_pre_ok = 1; + } + } + if (resp.rc == WH_ERROR_OK) { + resp.rc = WH_SERVER_NVM_LOCK(server); + if (resp.rc == WH_ERROR_OK) { + resp.rc = wh_Server_CertVerifyMultiRoot( + server, cert_data, req.cert_len, req.trustedRootNvmIds, + req.numRoots, req.flags, req.cachedKeyFlags, &keyId); + resp.keyId = wh_KeyId_TranslateToClient(keyId); + + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + } + /* Always call POST for successful PRE, regardless of operation + * result */ + if (cert_dma_pre_ok) { + (void)wh_Server_DmaProcessClientAddress( + server, req.cert_addr, &cert_data, req.cert_len, + WH_DMA_OPER_CLIENT_READ_POST, (whServerDmaFlags){0}); + } + + /* Convert the response struct */ + wh_MessageCert_TranslateVerifyDmaResponse( + magic, &resp, (whMessageCert_VerifyDmaResponse*)resp_packet); + *out_resp_size = sizeof(resp); + }; break; #endif /* WOLFHSM_CFG_DMA */ #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT) diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c index ede3a0b4a..8e6d7ea20 100644 --- a/test/wh_test_cert.c +++ b/test/wh_test_cert.c @@ -134,6 +134,117 @@ int whTest_CertServerCfg(whServerConfig* serverCfg) WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + /* ===== Multi-root verification tests ===== */ + { + const whNvmId rootCertC_absent = 99; + whNvmId roots_AB[2] = {rootCertA, rootCertB}; + whNvmId roots_BA[2] = {rootCertB, rootCertA}; + whNvmId roots_A_absent[2] = {rootCertA, rootCertC_absent}; + whNvmId roots_only_absent[2] = {rootCertC_absent, 100}; + whNvmId roots_max[WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS]; + uint16_t i; + + /* (1) Single root via multi-root path */ + WH_TEST_PRINT("Multi-root: single-element array, chain matches...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, &rootCertA, 1, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + WH_TEST_PRINT("Multi-root: single-element array, chain mismatch...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_CERT_VERIFY == + wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, &rootCertB, 1, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* (2) Two roots, chain matches first */ + WH_TEST_PRINT("Multi-root: two roots [A,B], chain anchors to A...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_AB, 2, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* (3) Two roots, chain matches second */ + WH_TEST_PRINT("Multi-root: two roots [A,B], chain anchors to B...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_AB, 2, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* (4) Two roots, chain matches neither */ + WH_TEST_PRINT( + "Multi-root: two roots [A,B], chain matches neither...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_CERT_VERIFY == + wh_Server_CertVerifyMultiRoot(server, INTERMEDIATE_B_CERT, + INTERMEDIATE_B_CERT_len, &rootCertA, + 1, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* (5) One present root, one absent root */ + WH_TEST_PRINT("Multi-root: roots [A, absent], chain anchors to A " + "succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_A_absent, 2, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + WH_TEST_PRINT("Multi-root: roots [A, absent], chain B fails with " + "CERT_VERIFY...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_CERT_VERIFY == + wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_A_absent, + 2, WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* (6) All supplied roots absent */ + WH_TEST_PRINT("Multi-root: all supplied roots absent → NOTFOUND...\n"); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, + RAW_CERT_CHAIN_A_len, roots_only_absent, 2, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, + NULL)); + + /* (7) Boundary on numRoots */ + WH_TEST_PRINT("Multi-root: numRoots == 0 → BADARGS...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, &rootCertA, 0, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + WH_TEST_PRINT("Multi-root: numRoots > MAX → BADARGS...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_max, + WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS + 1, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* numRoots == MAX, mostly absent ids plus rootCertA, succeeds */ + roots_max[0] = rootCertA; + for (i = 1; i < WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS; i++) { + roots_max[i] = (whNvmId)(200 + i); /* nonexistent */ + } + WH_TEST_PRINT( + "Multi-root: numRoots == MAX with mostly-absent succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_max, + WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS, WH_CERT_FLAGS_NONE, + WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* (12) Order independence: [A,B] and [B,A] both succeed for chain B */ + WH_TEST_PRINT("Multi-root: order independence [B,A] for chain B...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerifyMultiRoot( + server, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_BA, 2, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + + /* Equivalence with single-root entry point */ + WH_TEST_PRINT( + "Multi-root: equivalence with single-root entry point...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertVerify( + server, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, rootCertA, + WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); + } + /* remove trusted root certificate for chain A */ WH_TEST_PRINT("Removing trusted root certificates...\n"); WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(server, rootCertA)); @@ -246,6 +357,133 @@ int whTest_CertClient(whClientContext* client) WH_TEST_ASSERT_RETURN( 0 == memcmp(exportedPubKey, LEAF_A_PUBKEY, LEAF_A_PUBKEY_len)); + /* ===== Multi-root client tests ===== */ + { + whNvmId rootC_absent = 99; + whNvmId roots_AB[2] = {rootCertA_id, rootCertB_id}; + whNvmId roots_BA[2] = {rootCertB_id, rootCertA_id}; + whNvmId roots_A_absent[2] = {rootCertA_id, rootC_absent}; + whNvmId roots_only_absent[2] = {rootC_absent, 100}; + whKeyId mr_keyId = WH_KEYID_ERASED; + + /* (1) Single-root via multi-root path */ + WH_TEST_PRINT("Client multi-root: single root, chain matches...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, &rootCertA_id, 1, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + WH_TEST_PRINT("Client multi-root: single root, chain mismatch...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, &rootCertB_id, 1, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); + + /* (2-3) Two roots, chain matches first / second */ + WH_TEST_PRINT("Client multi-root: [A,B] anchors to A...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_AB, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + WH_TEST_PRINT("Client multi-root: [A,B] anchors to B...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_AB, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* (4) Two roots, neither matches */ + WH_TEST_PRINT( + "Client multi-root: [A,B] mismatch (intermediate B alone)...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, INTERMEDIATE_B_CERT, INTERMEDIATE_B_CERT_len, &rootCertA_id, + 1, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); + + /* (5) One present root + absent: success when chain matches present */ + WH_TEST_PRINT("Client multi-root: [A, absent] chain A succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_A_absent, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + WH_TEST_PRINT( + "Client multi-root: [A, absent] chain B → CERT_VERIFY...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_A_absent, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); + + /* (6) All supplied roots absent */ + WH_TEST_PRINT("Client multi-root: all absent → NOTFOUND...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_only_absent, + 2, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_NOTFOUND); + + /* (7) Boundary numRoots == 0 rejected client-side */ + WH_TEST_PRINT("Client multi-root: numRoots == 0 → BADARGS...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Client_CertVerifyMultiRoot(client, RAW_CERT_CHAIN_A, + RAW_CERT_CHAIN_A_len, &rootCertA_id, + 0, &out_rc)); + + /* (7b) Boundary numRoots > MAX_VERIFY_ROOTS rejected client-side */ + { + whNvmId oversized[WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS + 1] = {0}; + uint16_t over = (uint16_t)(WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS + 1); + WH_TEST_PRINT("Client multi-root: numRoots > MAX → BADARGS...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Client_CertVerifyMultiRoot(client, RAW_CERT_CHAIN_A, + RAW_CERT_CHAIN_A_len, oversized, + over, &out_rc)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Client_CertVerifyMultiRootAndCacheLeafPubKey( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, oversized, + over, WH_NVM_FLAGS_USAGE_ANY, &mr_keyId, &out_rc)); + } + + /* (12) Order independence */ + WH_TEST_PRINT("Client multi-root: [B,A] for chain B succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_BA, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* (11) Async split with case (3) */ + WH_TEST_PRINT( + "Client multi-root: async split, [A,B] chain B succeeds...\n"); + do { + rc = wh_Client_CertVerifyMultiRootRequest( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_AB, 2); + } while (rc == WH_ERROR_NOTREADY); + WH_TEST_RETURN_ON_FAIL(rc); + do { + rc = wh_Client_CertVerifyMultiRootResponse(client, &out_rc); + } while (rc == WH_ERROR_NOTREADY); + WH_TEST_RETURN_ON_FAIL(rc); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* (9) Leaf key caching variant: succeed on chain A and pull pubkey */ + WH_TEST_PRINT( + "Client multi-root: leaf cache [A,B] chain A succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootAndCacheLeafPubKey( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_AB, 2, + WH_NVM_FLAGS_USAGE_ANY, &mr_keyId, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + exportedPubKeyLen = sizeof(exportedPubKey); + rc = wh_Client_KeyExport(client, mr_keyId, NULL, 0, exportedPubKey, + &exportedPubKeyLen); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, mr_keyId)); + WH_TEST_ASSERT(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(exportedPubKeyLen == LEAF_A_PUBKEY_len); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(exportedPubKey, LEAF_A_PUBKEY, LEAF_A_PUBKEY_len)); + } + /* Clean up - delete the root certificates */ WH_TEST_PRINT("Deleting root certificates...\n"); WH_TEST_RETURN_ON_FAIL( @@ -432,6 +670,115 @@ int whTest_CertClientDma_ClientServerTestInternal(whClientContext* client) WH_TEST_ASSERT_RETURN( 0 == memcmp(exportedPubKey, LEAF_A_PUBKEY, LEAF_A_PUBKEY_len)); + /* ===== Multi-root DMA client tests ===== */ + { + whNvmId rootC_absent = 99; + whNvmId roots_AB[2] = {rootCertA_id, rootCertB_id}; + whNvmId roots_BA[2] = {rootCertB_id, rootCertA_id}; + whNvmId roots_A_absent[2] = {rootCertA_id, rootC_absent}; + whNvmId roots_only_absent[2] = {rootC_absent, 100}; + whKeyId mr_keyId = WH_KEYID_ERASED; + + /* (1) Single root via multi-root DMA path */ + WH_TEST_PRINT("Client multi-root DMA: single root, matches...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, &rootCertA_id, 1, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + WH_TEST_PRINT("Client multi-root DMA: single root, mismatch...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, &rootCertB_id, 1, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); + + /* (2-3) Two roots, chain matches first / second */ + WH_TEST_PRINT("Client multi-root DMA: [A,B] chain A succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_AB, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + WH_TEST_PRINT("Client multi-root DMA: [A,B] chain B succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_AB, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* (5) Present + absent: success when chain matches present */ + WH_TEST_PRINT( + "Client multi-root DMA: [A, absent] chain A succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_A_absent, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + WH_TEST_PRINT( + "Client multi-root DMA: [A, absent] chain B → CERT_VERIFY...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_A_absent, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); + + /* (6) All absent → NOTFOUND */ + WH_TEST_PRINT("Client multi-root DMA: all absent → NOTFOUND...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_only_absent, + 2, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_NOTFOUND); + + /* (12) Order independence */ + WH_TEST_PRINT("Client multi-root DMA: [B,A] chain B succeeds...\n"); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRootDma( + client, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_BA, 2, + &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + /* (7) Boundary numRoots == 0 rejected client-side */ + WH_TEST_PRINT("Client multi-root DMA: numRoots == 0 → BADARGS...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Client_CertVerifyMultiRootDma(client, RAW_CERT_CHAIN_A, + RAW_CERT_CHAIN_A_len, + &rootCertA_id, 0, &out_rc)); + + /* (7b) Boundary numRoots > MAX_VERIFY_ROOTS rejected client-side */ + { + whNvmId oversized[WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS + 1] = {0}; + uint16_t over = (uint16_t)(WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS + 1); + WH_TEST_PRINT( + "Client multi-root DMA: numRoots > MAX → BADARGS...\n"); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Client_CertVerifyMultiRootDma(client, RAW_CERT_CHAIN_A, + RAW_CERT_CHAIN_A_len, + oversized, over, &out_rc)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKey( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, oversized, + over, WH_NVM_FLAGS_USAGE_ANY, &mr_keyId, &out_rc)); + } + + /* (9) Leaf key caching variant: succeed on chain A and pull pubkey */ + WH_TEST_PRINT( + "Client multi-root DMA: leaf cache [A,B] chain A succeeds...\n"); + WH_TEST_RETURN_ON_FAIL( + wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKey( + client, RAW_CERT_CHAIN_A, RAW_CERT_CHAIN_A_len, roots_AB, 2, + WH_NVM_FLAGS_USAGE_ANY, &mr_keyId, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + exportedPubKeyLen = sizeof(exportedPubKey); + rc = wh_Client_KeyExportDma(client, mr_keyId, exportedPubKey, + sizeof(exportedPubKey), NULL, 0, + &exportedPubKeyLen); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(client, mr_keyId)); + WH_TEST_ASSERT(rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(exportedPubKeyLen == LEAF_A_PUBKEY_len); + WH_TEST_ASSERT_RETURN( + 0 == memcmp(exportedPubKey, LEAF_A_PUBKEY, LEAF_A_PUBKEY_len)); + } + /* Clean up - delete the root certificates */ WH_TEST_PRINT("Deleting root certificates...\n"); WH_TEST_RETURN_ON_FAIL( diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index dfc6d4913..fe224c1fa 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -183,12 +183,15 @@ whMessageCert_ReadTrustedRequest whMessageCert_ReadTrustedRequest_test; whMessageCert_ReadTrustedResponse whMessageCert_ReadTrustedResponse_test; whMessageCert_VerifyRequest whMessageCert_VerifyRequest_test; whMessageCert_VerifyResponse whMessageCert_VerifyResponse_test; +whMessageCert_VerifyMultiRootRequest whMessageCert_VerifyMultiRootRequest_test; #if defined(WOLFHSM_CFG_DMA) whMessageCert_AddTrustedDmaRequest whMessageCert_AddTrustedDmaRequest_test; whMessageCert_ReadTrustedDmaRequest whMessageCert_ReadTrustedDmaRequest_test; whMessageCert_VerifyDmaRequest whMessageCert_VerifyDmaRequest_test; whMessageCert_VerifyDmaResponse whMessageCert_VerifyDmaResponse_test; +whMessageCert_VerifyMultiRootDmaRequest + whMessageCert_VerifyMultiRootDmaRequest_test; #endif /* WOLFHSM_CFG_DMA */ #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT) diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index a71a788c1..ca4ece9eb 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -2571,6 +2571,110 @@ int wh_Client_CertVerifyAndCacheLeafPubKey( int32_t* out_rc); +/** + * @brief Sends a request to verify a certificate chain against a set of + * trusted root anchors. + * + * Generalizes wh_Client_CertVerifyRequest to accept an ordered list of + * trusted root NVM IDs. Verification succeeds if the chain anchors to any + * of the supplied roots. This function does not block; it returns + * immediately after sending the request. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds (1.. + * WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS). + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootRequest(whClientContext* c, + const uint8_t* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots); + +/** + * @brief Receives a response from the server after multi-root certificate + * verification. + * + * @param[in] c Pointer to the client context. + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootResponse(whClientContext* c, int32_t* out_rc); + +/** + * @brief Sends a request and receives a response to verify a certificate + * chain against a set of trusted root anchors. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds. + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRoot(whClientContext* c, const uint8_t* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, int32_t* out_rc); + +/** + * @brief Sends a request to verify a certificate chain against a set of + * trusted root anchors and cache the leaf certificate public key. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds. + * @param[in] cachedKeyFlags NVM usage flags for the cached leaf public key. + * @param[in] keyId The keyId to cache the leaf public key in. If set to + * WH_KEYID_ERASED, the server will pick a keyId. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootAndCacheLeafPubKeyRequest( + whClientContext* c, const uint8_t* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId keyId); + +/** + * @brief Receives a response from the server after multi-root certificate + * verification with leaf public key caching. + * + * @param[in] c Pointer to the client context. + * @param[out] out_keyId Pointer to store the key ID of the cached leaf public + * key. + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootAndCacheLeafPubKeyResponse(whClientContext* c, + whKeyId* out_keyId, + int32_t* out_rc); + +/** + * @brief Sends a request and receives a response to verify a certificate + * chain against a set of trusted root anchors and cache the leaf + * certificate public key. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds. + * @param[in] cachedKeyFlags NVM usage flags for the cached leaf public key. + * @param[in,out] inout_keyId Pointer to the desired key ID (in) / cached key + * ID (out). + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootAndCacheLeafPubKey( + whClientContext* c, const uint8_t* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId* inout_keyId, int32_t* out_rc); + + #ifdef WOLFHSM_CFG_DMA /** @@ -2801,6 +2905,110 @@ int wh_Client_CertVerifyDmaAndCacheLeafPubKey( int32_t* out_rc); +/** + * @brief Sends a DMA request to verify a certificate chain against a set + * of trusted root anchors. + * + * Generalizes wh_Client_CertVerifyDmaRequest to accept an ordered list of + * trusted root NVM IDs. Verification succeeds if the chain anchors to any + * of the supplied roots. This function does not block; it returns + * immediately after sending the request. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds (1.. + * WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS). + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootDmaRequest(whClientContext* c, + const void* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots); + +/** + * @brief Receives a response from the server after multi-root DMA + * certificate verification. + * + * @param[in] c Pointer to the client context. + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootDmaResponse(whClientContext* c, + int32_t* out_rc); + +/** + * @brief Sends a request and receives a response to verify a certificate + * chain via DMA against a set of trusted root anchors. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds. + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootDma(whClientContext* c, const void* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, int32_t* out_rc); + +/** + * @brief Sends a DMA request to verify a certificate chain against a set + * of trusted root anchors and cache the leaf certificate public key. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds. + * @param[in] cachedKeyFlags NVM usage flags for the cached leaf public key. + * @param[in] keyId The keyId to cache the leaf public key in. If set to + * WH_KEYID_ERASED, the server will pick a keyId. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKeyRequest( + whClientContext* c, const void* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId keyId); + +/** + * @brief Receives a response from the server after multi-root DMA + * certificate verification with leaf public key caching. + * + * @param[in] c Pointer to the client context. + * @param[out] out_keyId Pointer to store the key ID of the cached leaf public + * key. + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKeyResponse( + whClientContext* c, whKeyId* out_keyId, int32_t* out_rc); + +/** + * @brief Sends a request and receives a response to verify a certificate + * chain via DMA against a set of trusted root anchors and cache the leaf + * certificate public key. + * + * @param[in] c Pointer to the client context. + * @param[in] cert Pointer to the certificate data to verify. + * @param[in] cert_len Length of the certificate data. + * @param[in] trustedRootNvmIds Array of NVM IDs of trusted root certificates. + * @param[in] numRoots Number of entries in trustedRootNvmIds. + * @param[in] cachedKeyFlags NVM usage flags for the cached leaf public key. + * @param[in,out] inout_keyId Pointer to the desired key ID (in) / cached key + * ID (out). + * @param[out] out_rc Pointer to store the response code from the server. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_CertVerifyMultiRootDmaAndCacheLeafPubKey( + whClientContext* c, const void* cert, uint32_t cert_len, + const whNvmId* trustedRootNvmIds, uint16_t numRoots, + whNvmFlags cachedKeyFlags, whKeyId* inout_keyId, int32_t* out_rc); + + #endif /* WOLFHSM_CFG_DMA */ /** diff --git a/wolfhsm/wh_message_cert.h b/wolfhsm/wh_message_cert.h index c2370bd31..8dbb05886 100644 --- a/wolfhsm/wh_message_cert.h +++ b/wolfhsm/wh_message_cert.h @@ -32,18 +32,21 @@ #include "wolfhsm/wh_comm.h" #include "wolfhsm/wh_message.h" #include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_utils.h" enum WH_MESSAGE_CERT_ACTION_ENUM { - WH_MESSAGE_CERT_ACTION_INIT = 0x1, - WH_MESSAGE_CERT_ACTION_ADDTRUSTED = 0x2, - WH_MESSAGE_CERT_ACTION_ERASETRUSTED = 0x3, - WH_MESSAGE_CERT_ACTION_READTRUSTED = 0x4, - WH_MESSAGE_CERT_ACTION_VERIFY = 0x5, - WH_MESSAGE_CERT_ACTION_ADDTRUSTED_DMA = 0x22, - WH_MESSAGE_CERT_ACTION_READTRUSTED_DMA = 0x24, - WH_MESSAGE_CERT_ACTION_VERIFY_DMA = 0x25, - WH_MESSAGE_CERT_ACTION_VERIFY_ACERT = 0x26, - WH_MESSAGE_CERT_ACTION_VERIFY_ACERT_DMA = 0x27, + WH_MESSAGE_CERT_ACTION_INIT = 0x1, + WH_MESSAGE_CERT_ACTION_ADDTRUSTED = 0x2, + WH_MESSAGE_CERT_ACTION_ERASETRUSTED = 0x3, + WH_MESSAGE_CERT_ACTION_READTRUSTED = 0x4, + WH_MESSAGE_CERT_ACTION_VERIFY = 0x5, + WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT = 0x6, + WH_MESSAGE_CERT_ACTION_ADDTRUSTED_DMA = 0x22, + WH_MESSAGE_CERT_ACTION_READTRUSTED_DMA = 0x24, + WH_MESSAGE_CERT_ACTION_VERIFY_DMA = 0x25, + WH_MESSAGE_CERT_ACTION_VERIFY_ACERT = 0x26, + WH_MESSAGE_CERT_ACTION_VERIFY_ACERT_DMA = 0x27, + WH_MESSAGE_CERT_ACTION_VERIFY_MULTI_ROOT_DMA = 0x28, }; /* Simple reusable response message */ @@ -137,6 +140,29 @@ int wh_MessageCert_TranslateVerifyResponse( uint16_t magic, const whMessageCert_VerifyResponse* src, whMessageCert_VerifyResponse* dest); +/* VerifyMultiRoot Request + * + * Followed inline by: + * whNvmId trustedRootNvmIds[numRoots] (numRoots * sizeof(whNvmId)) + * uint8_t cert[cert_len] (the candidate chain) + * + * cert_len + numRoots * sizeof(whNvmId) plus sizeof this struct must not + * exceed WOLFHSM_CFG_COMM_DATA_LEN. */ +typedef struct { + uint32_t cert_len; + uint16_t numRoots; + uint16_t flags; + whNvmFlags cachedKeyFlags; + whKeyId keyId; + uint8_t WH_PAD[4]; +} whMessageCert_VerifyMultiRootRequest; + +int wh_MessageCert_TranslateVerifyMultiRootRequest( + uint16_t magic, const whMessageCert_VerifyMultiRootRequest* src, + whMessageCert_VerifyMultiRootRequest* dest); + +/* VerifyMultiRoot Response: reuse whMessageCert_VerifyResponse */ + #ifdef WOLFHSM_CFG_DMA /* AddTrusted DMA Request */ @@ -192,6 +218,41 @@ int wh_MessageCert_TranslateVerifyDmaResponse( uint16_t magic, const whMessageCert_VerifyDmaResponse* src, whMessageCert_VerifyDmaResponse* dest); +/* VerifyMultiRoot DMA Request + * + * The root array is inlined at fixed maximum size to keep the request a flat + * POD; only the first numRoots entries are meaningful. + * + * WH_PAD sized so that, with the default WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS + * (8), the struct total is a multiple of 8. Users overriding the bound to a + * non-multiple-of-4 may need to adjust WH_PAD to silence -Wpadded. */ +typedef struct { + uint64_t cert_addr; + uint32_t cert_len; + uint16_t numRoots; + uint16_t flags; + whNvmFlags cachedKeyFlags; + whKeyId keyId; + uint8_t WH_PAD[4]; + whNvmId trustedRootNvmIds[WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS]; +} whMessageCert_VerifyMultiRootDmaRequest; + +/* The fixed-size DMA request must fit on the wire. If a build overrides + * WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS high enough that this fails, the comm + * layer would otherwise reject every multi-root DMA call at runtime with + * BADARGS; surface the misconfiguration at compile time instead. */ +WH_UTILS_STATIC_ASSERT(sizeof(whMessageCert_VerifyMultiRootDmaRequest) <= + WOLFHSM_CFG_COMM_DATA_LEN, + "WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS too large: " + "whMessageCert_VerifyMultiRootDmaRequest exceeds " + "WOLFHSM_CFG_COMM_DATA_LEN"); + +int wh_MessageCert_TranslateVerifyMultiRootDmaRequest( + uint16_t magic, const whMessageCert_VerifyMultiRootDmaRequest* src, + whMessageCert_VerifyMultiRootDmaRequest* dest); + +/* VerifyMultiRoot DMA Response: reuse whMessageCert_VerifyDmaResponse */ + #endif /* WOLFHSM_CFG_DMA */ #ifdef WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT diff --git a/wolfhsm/wh_server_cert.h b/wolfhsm/wh_server_cert.h index 0d0be42b7..7b9396655 100644 --- a/wolfhsm/wh_server_cert.h +++ b/wolfhsm/wh_server_cert.h @@ -95,6 +95,42 @@ int wh_Server_CertVerify(whServerContext* server, const uint8_t* cert, whCertFlags flags, whNvmFlags cachedKeyFlags, whKeyId* inout_keyId); +/** + * @brief Verify a certificate chain against a set of trusted root anchors. + * + * Loads each available root identified by trustedRootNvmIds into a freshly + * allocated cert manager and verifies the supplied chain once. Succeeds if + * the chain anchors to any loaded root. Roots whose NVM objects are absent + * are skipped silently; non-absent failures to read or load any root are + * reported. + * + * @param[in] server Server context. + * @param[in] cert Candidate certificate chain (DER). + * @param[in] cert_len Length of cert in bytes. + * @param[in] trustedRootNvmIds Array of root NVM IDs to load as anchors. + * Order is informational only. + * @param[in] numRoots Number of entries in trustedRootNvmIds. + * Must + * be 1..WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS. + * @param[in] flags See WH_CERT_FLAGS_*. + * @param[in] cachedKeyFlags NVM flags applied to the cached leaf key + * (only used if + * WH_CERT_FLAGS_CACHE_LEAF_PUBKEY). + * @param[in,out] inout_keyId Cached leaf key id (only used if + * WH_CERT_FLAGS_CACHE_LEAF_PUBKEY). + * @return WH_ERROR_OK on chain trust success. + * WH_ERROR_CERT_VERIFY if no loaded anchor matches the chain. + * WH_ERROR_NOTFOUND if every supplied root id is absent from NVM. + * WH_ERROR_BADARGS / other negative codes on argument or environment + * errors. + */ +int wh_Server_CertVerifyMultiRoot(whServerContext* server, const uint8_t* cert, + uint32_t cert_len, + const whNvmId* trustedRootNvmIds, + uint16_t numRoots, whCertFlags flags, + whNvmFlags cachedKeyFlags, + whKeyId* inout_keyId); + #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT) /** * @brief Verifies an attribute certificate against a trusted root certificate diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index e9e54be88..18d23bd57 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -86,6 +86,13 @@ * operation in DMA requests. * Default: Not defined * + * WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS - Maximum number of trusted root NVM IDs + * accepted in a single wh_Server_CertVerifyMultiRoot request. Bounded so the + * non-DMA wire request fits within WOLFHSM_CFG_COMM_DATA_LEN alongside the + * candidate chain, and so the inline DMA request struct stays a fixed-size + * POD. + * Default: 8 + * * WOLFHSM_CFG_IS_TEST_SERVER - If defined, the client-side unit tests assume * the server will be running custom server-side test instrumentation meant to * test additional edge cases that could otherwise not be triggered when running @@ -272,6 +279,14 @@ #endif #endif +/* Maximum number of trusted root NVM IDs accepted in one multi-root verify + * request. Bounded so the request fits within WOLFHSM_CFG_COMM_DATA_LEN + * alongside the candidate chain, and so the inline DMA request struct stays + * a fixed-size POD. Default 8; overridable at build time. */ +#ifndef WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS +#define WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS 8 +#endif + /*----------------------------------------------------------------------------- * Debug and Print Configuration *---------------------------------------------------------------------------*/ From dbe4f126ca86abf4f9b30fbc27d0790d7d2fc212 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Wed, 6 May 2026 11:42:11 -0600 Subject: [PATCH 2/2] fix erroneous test case --- test/wh_test_cert.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/wh_test_cert.c b/test/wh_test_cert.c index 8e6d7ea20..80175ffaa 100644 --- a/test/wh_test_cert.c +++ b/test/wh_test_cert.c @@ -169,14 +169,14 @@ int whTest_CertServerCfg(whServerConfig* serverCfg) server, RAW_CERT_CHAIN_B, RAW_CERT_CHAIN_B_len, roots_AB, 2, WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); - /* (4) Two roots, chain matches neither */ + /* (4) Two roots, chain matches neither (incomplete chain: leaf + * without its intermediate cannot be anchored to A or B) */ WH_TEST_PRINT( "Multi-root: two roots [A,B], chain matches neither...\n"); WH_TEST_ASSERT_RETURN( WH_ERROR_CERT_VERIFY == - wh_Server_CertVerifyMultiRoot(server, INTERMEDIATE_B_CERT, - INTERMEDIATE_B_CERT_len, &rootCertA, - 1, WH_CERT_FLAGS_NONE, + wh_Server_CertVerifyMultiRoot(server, LEAF_A_CERT, LEAF_A_CERT_len, + roots_AB, 2, WH_CERT_FLAGS_NONE, WH_NVM_FLAGS_USAGE_ANY, NULL)); /* (5) One present root, one absent root */ @@ -392,12 +392,12 @@ int whTest_CertClient(whClientContext* client) &out_rc)); WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); - /* (4) Two roots, neither matches */ - WH_TEST_PRINT( - "Client multi-root: [A,B] mismatch (intermediate B alone)...\n"); + /* (4) Two roots, neither matches (incomplete chain: leaf without + * its intermediate cannot be anchored to A or B) */ + WH_TEST_PRINT("Client multi-root: [A,B] mismatch (leaf without " + "intermediate)...\n"); WH_TEST_RETURN_ON_FAIL(wh_Client_CertVerifyMultiRoot( - client, INTERMEDIATE_B_CERT, INTERMEDIATE_B_CERT_len, &rootCertA_id, - 1, &out_rc)); + client, LEAF_A_CERT, LEAF_A_CERT_len, roots_AB, 2, &out_rc)); WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_CERT_VERIFY); /* (5) One present root + absent: success when chain matches present */