diff --git a/src/wh_server_cert.c b/src/wh_server_cert.c index 8f7076679..ff6ff64b1 100644 --- a/src/wh_server_cert.c +++ b/src/wh_server_cert.c @@ -688,6 +688,7 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, uint8_t* cert, uint32_t* inout_cert_len) { int rc; + uint32_t buf_len; whNvmMetadata meta; if ((server == NULL) || (cert == NULL) || (inout_cert_len == NULL) || @@ -695,6 +696,7 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, return WH_ERROR_BADARGS; } + buf_len = *inout_cert_len; /* Get metadata to check the certificate size */ rc = wh_Nvm_GetMetadata(server->nvm, id, &meta); @@ -702,15 +704,15 @@ int wh_Server_CertReadTrusted(whServerContext* server, whNvmId id, return rc; } + /* Report the actual length even when the buffer is too small, so the + * caller learns the size it could not receive */ + *inout_cert_len = meta.len; + /* Check if the provided buffer is large enough */ - if (meta.len > *inout_cert_len) { + if (meta.len > buf_len) { return WH_ERROR_BUFFER_SIZE; } - /* Clamp the input length to the actual length of the certificate. This will - * be reflected back to the user on length mismatch failure */ - *inout_cert_len = meta.len; - return wh_Nvm_Read(server->nvm, id, 0, meta.len, cert); } @@ -1007,7 +1009,13 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, else { rc = wh_Server_CertReadTrusted(server, req.id, cert_data, &cert_len); - resp.cert_len = cert_len; + /* These are the only outcomes that resolve a length. + * Any other error staged no certificate, so there is + * no length to describe it with */ + if ((rc == WH_ERROR_OK) || + (rc == WH_ERROR_BUFFER_SIZE)) { + resp.cert_len = cert_len; + } } } @@ -1018,7 +1026,12 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, /* Convert the response struct */ wh_MessageCert_TranslateReadTrustedResponse( magic, &resp, (whMessageCert_ReadTrustedResponse*)resp_packet); - *out_resp_size = sizeof(resp) + resp.cert_len; + /* Certificate data is only staged on success. Sizing the response + * to cert_len otherwise would transmit unwritten packet bytes */ + *out_resp_size = sizeof(resp); + if (rc == WH_ERROR_OK) { + *out_resp_size += resp.cert_len; + } }; break; case WH_MESSAGE_CERT_ACTION_VERIFY: { @@ -1294,7 +1307,9 @@ int wh_Server_HandleCertRequest(whServerContext* server, uint16_t magic, resp.rc = WH_ERROR_ACCESS; } else { - /* Clamp cert_len to actual stored length */ + /* The callee reports the stored length back into + * cert_len, but SimpleResponse has no field to + * return it, so the client cannot learn it here */ cert_len = req.cert_len; resp.rc = wh_Server_CertReadTrusted( server, req.id, cert_data, &cert_len); diff --git a/test-refactor/client-server/wh_test_client_certs.c b/test-refactor/client-server/wh_test_client_certs.c index ea29c1789..4c672079c 100644 --- a/test-refactor/client-server/wh_test_client_certs.c +++ b/test-refactor/client-server/wh_test_client_certs.c @@ -29,9 +29,11 @@ && !defined(WOLFHSM_CFG_NO_CRYPTO) #include +#include #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_message_cert.h" #include "wh_test_common.h" #include "wh_test_list.h" @@ -83,9 +85,74 @@ static int _whTest_CertReadTrustedSmallBuffer(whClientContext* ctx) } +/* Only with DMA does the certificate limit drop below the comm buffer, which + * is what makes a stored-but-unreadable certificate possible */ +#if defined(WOLFHSM_CFG_DMA) + +/* Mirrors the staging clamp in the non-DMA READTRUSTED handler */ +#define WH_TEST_CERT_TRANSPORT_LEN \ + (WOLFHSM_CFG_COMM_DATA_LEN - sizeof(whMessageCert_ReadTrustedResponse)) +#define WH_TEST_CERT_STAGED_LEN \ + ((WOLFHSM_CFG_MAX_CERT_SIZE > WH_TEST_CERT_TRANSPORT_LEN) \ + ? WH_TEST_CERT_TRANSPORT_LEN \ + : WOLFHSM_CFG_MAX_CERT_SIZE) + +/* The add paths and the read clamp have different bounds, so a certificate + * can be stored that a non-DMA read cannot return */ +static int _whTest_CertReadTrustedOverClamp(whClientContext* ctx) +{ + static uint8_t big_cert[WH_TEST_CERT_STAGED_LEN + 1]; + static uint8_t read_buf[WOLFHSM_CFG_COMM_DATA_LEN]; + const whNvmId cert_id = 104; + const uint32_t stored_len = (uint32_t)WH_TEST_CERT_STAGED_LEN + 1; + const uint32_t inline_max = + WOLFHSM_CFG_COMM_DATA_LEN - sizeof(whMessageCert_AddTrustedRequest); + uint32_t cert_len = 0; + int32_t out_rc = 0; + + /* One of the two add paths must be able to hold it, or there is no window + * to probe and the config needs a different size than STAGED_LEN + 1 */ + WH_TEST_ASSERT_RETURN((stored_len <= inline_max) || + (stored_len <= WOLFHSM_CFG_MAX_CERT_SIZE)); + + memset(big_cert, 0x5A, sizeof(big_cert)); + + /* A large comm buffer lets the inline add outrun the clamp; otherwise the + * certificate limit is the larger bound and only DMA can */ + if (stored_len <= inline_max) { + WH_TEST_RETURN_ON_FAIL(wh_Client_CertAddTrusted( + ctx, cert_id, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, NULL, 0, + big_cert, stored_len, &out_rc)); + } + else { + WH_TEST_RETURN_ON_FAIL(wh_Client_CertAddTrustedDma( + ctx, cert_id, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, NULL, 0, + big_cert, stored_len, &out_rc)); + } + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + cert_len = sizeof(read_buf); + WH_TEST_RETURN_ON_FAIL(wh_Client_CertReadTrusted( + ctx, cert_id, read_buf, &cert_len, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_BUFFER_SIZE); + /* The stored size, not the clamp the server staged with */ + WH_TEST_ASSERT_RETURN(cert_len == stored_len); + + WH_TEST_RETURN_ON_FAIL(wh_Client_CertEraseTrusted(ctx, cert_id, &out_rc)); + WH_TEST_ASSERT_RETURN(out_rc == WH_ERROR_OK); + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_DMA */ + + int whTest_ClientCerts(whClientContext* ctx) { WH_TEST_RETURN_ON_FAIL(_whTest_CertReadTrustedSmallBuffer(ctx)); +#if defined(WOLFHSM_CFG_DMA) + WH_TEST_RETURN_ON_FAIL(_whTest_CertReadTrustedOverClamp(ctx)); +#endif return WH_ERROR_OK; } diff --git a/test-refactor/server/wh_test_cert_readtrusted.c b/test-refactor/server/wh_test_cert_readtrusted.c new file mode 100644 index 000000000..490887c82 --- /dev/null +++ b/test-refactor/server/wh_test_cert_readtrusted.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/server/wh_test_cert_readtrusted.c + * + * READTRUSTED response sizing over the comm buffer. Drives the + * request handler directly against the shared server context. + */ + +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) \ + && !defined(WOLFHSM_CFG_NO_CRYPTO) + +#include +#include + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_message_cert.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_server_cert.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +/* wh_test_cert_data.h defines rather than declares, so it can only be + * included once across the suite */ +extern const unsigned char ROOT_A_CERT[]; +extern const size_t ROOT_A_CERT_len; + +/* Mirrors the staging clamp in the non-DMA READTRUSTED handler. Reading at + * both STAGED_LEN and STAGED_LEN + 1 pins the clamp from either side */ +#define WH_TEST_CERT_TRANSPORT_LEN \ + (WOLFHSM_CFG_COMM_DATA_LEN - sizeof(whMessageCert_ReadTrustedResponse)) +#define WH_TEST_CERT_STAGED_LEN \ + ((WOLFHSM_CFG_MAX_CERT_SIZE > WH_TEST_CERT_TRANSPORT_LEN) \ + ? WH_TEST_CERT_TRANSPORT_LEN \ + : WOLFHSM_CFG_MAX_CERT_SIZE) + +/* An unstageable certificate must report the stored size and no payload */ +static int _whTest_CertReadTrustedOversized(whServerContext* server) +{ + whMessageCert_ReadTrustedRequest req[1] = {{0}}; + union { + whMessageCert_ReadTrustedResponse resp; + uint8_t bytes[WOLFHSM_CFG_COMM_DATA_LEN]; + } respPkt; + /* Static filler: an automatic copy would double this stack frame on the + * embedded targets these suites also run on */ + static uint8_t oversized_cert[WH_TEST_CERT_STAGED_LEN + 1]; + const whNvmId certId = 20; + const uint32_t oversized_len = (uint32_t)WH_TEST_CERT_STAGED_LEN + 1; + uint16_t resp_size = 0; + int handler_rc; + uint32_t i; + + /* One byte too large to stage */ + memset(oversized_cert, 0x5A, sizeof(oversized_cert)); + WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted( + server, certId, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, NULL, 0, + oversized_cert, oversized_len)); + + /* Poison makes bytes the handler never staged detectable */ + memset(respPkt.bytes, 0xA5, sizeof(respPkt.bytes)); + req->id = certId; + + /* The server transmits regardless of this return, so out_resp_size is + * what actually reaches the client */ + handler_rc = wh_Server_HandleCertRequest( + server, WH_COMM_MAGIC_NATIVE, WH_MESSAGE_CERT_ACTION_READTRUSTED, 0, + (uint16_t)sizeof(*req), req, &resp_size, respPkt.bytes); + + WH_TEST_ASSERT_RETURN(handler_rc == WH_ERROR_BUFFER_SIZE); + WH_TEST_ASSERT_RETURN(respPkt.resp.rc == WH_ERROR_BUFFER_SIZE); + /* Reports the true size, which no non-DMA response can carry */ + WH_TEST_ASSERT_RETURN(respPkt.resp.cert_len == oversized_len); + WH_TEST_ASSERT_RETURN(resp_size == sizeof(respPkt.resp)); + + for (i = sizeof(respPkt.resp); i < sizeof(respPkt.bytes); i++) { + WH_TEST_ASSERT_RETURN(respPkt.bytes[i] == 0xA5); + } + + WH_TEST_RETURN_ON_FAIL(wh_Server_CertEraseTrusted(server, certId)); + + /* The largest stageable certificate must still read back whole */ + WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted( + server, certId, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONE, NULL, 0, + oversized_cert, oversized_len - 1)); + + memset(respPkt.bytes, 0xA5, sizeof(respPkt.bytes)); + req->id = certId; + + WH_TEST_RETURN_ON_FAIL(wh_Server_HandleCertRequest( + server, WH_COMM_MAGIC_NATIVE, WH_MESSAGE_CERT_ACTION_READTRUSTED, 0, + (uint16_t)sizeof(*req), req, &resp_size, respPkt.bytes)); + + WH_TEST_ASSERT_RETURN(respPkt.resp.rc == WH_ERROR_OK); + WH_TEST_ASSERT_RETURN(respPkt.resp.cert_len == oversized_len - 1); + WH_TEST_ASSERT_RETURN(resp_size == sizeof(respPkt.resp) + oversized_len - 1); + + for (i = 0; i < oversized_len - 1; i++) { + WH_TEST_ASSERT_RETURN(respPkt.bytes[sizeof(respPkt.resp) + i] == 0x5A); + } + + return wh_Server_CertEraseTrusted(server, certId); +} + +/* A rejected read stages no certificate, so the response must carry no length + * and no payload */ +static int _whTest_CertReadTrustedDenied(whServerContext* server) +{ + whMessageCert_ReadTrustedRequest req[1] = {{0}}; + union { + whMessageCert_ReadTrustedResponse resp; + uint8_t bytes[WOLFHSM_CFG_COMM_DATA_LEN]; + } respPkt; + const whNvmId certId = 21; + uint16_t resp_size = 0; + int handler_rc; + + WH_TEST_RETURN_ON_FAIL(wh_Server_CertAddTrusted( + server, certId, WH_NVM_ACCESS_ANY, WH_NVM_FLAGS_NONEXPORTABLE, NULL, 0, + ROOT_A_CERT, ROOT_A_CERT_len)); + + memset(respPkt.bytes, 0xA5, sizeof(respPkt.bytes)); + req->id = certId; + + handler_rc = wh_Server_HandleCertRequest( + server, WH_COMM_MAGIC_NATIVE, WH_MESSAGE_CERT_ACTION_READTRUSTED, 0, + (uint16_t)sizeof(*req), req, &resp_size, respPkt.bytes); + + WH_TEST_ASSERT_RETURN(handler_rc == WH_ERROR_ACCESS); + WH_TEST_ASSERT_RETURN(respPkt.resp.rc == WH_ERROR_ACCESS); + WH_TEST_ASSERT_RETURN(respPkt.resp.cert_len == 0); + WH_TEST_ASSERT_RETURN(resp_size == sizeof(respPkt.resp)); + + return wh_Server_CertEraseTrusted(server, certId); +} + +/* READTRUSTED response sizing over the comm buffer */ +int whTest_CertReadTrusted(whServerContext* ctx) +{ + whServerContext* server = (whServerContext*)ctx; + + WH_TEST_RETURN_ON_FAIL(wh_Server_CertInit(server)); + WH_TEST_RETURN_ON_FAIL(_whTest_CertReadTrustedOversized(server)); + WH_TEST_RETURN_ON_FAIL(_whTest_CertReadTrustedDenied(server)); + + return 0; +} + + +#endif /* WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 70f7d0396..462d83aae 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -50,6 +50,7 @@ WH_TEST_DECL(whTest_Lock); WH_TEST_DECL(whTest_CertVerify); WH_TEST_DECL(whTest_CertNvmPolicy); WH_TEST_DECL(whTest_CertReadRejectsServerOnly); +WH_TEST_DECL(whTest_CertReadTrusted); WH_TEST_DECL(whTest_HwKeystoreServer); WH_TEST_DECL(whTest_ServerImgMgr); WH_TEST_DECL(whTest_NvmOptional); @@ -115,6 +116,7 @@ const whTestCase whTestsServer[] = { { "whTest_CertNvmPolicy", whTest_CertNvmPolicy }, { "whTest_CertReadRejectsServerOnly", whTest_CertReadRejectsServerOnly }, { "whTest_ServerImgMgr", whTest_ServerImgMgr }, + { "whTest_CertReadTrusted", whTest_CertReadTrusted }, { "whTest_NvmOptional", whTest_NvmOptional }, { "whTest_SheMasterEcuKeyFallback", whTest_SheMasterEcuKeyFallback }, { "whTest_SheReqSizeChecking", whTest_SheReqSizeChecking },