From cd6a59ef7c745af633f9d61a1da655ce7ba14974 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Fri, 26 Jun 2026 11:08:02 -0700 Subject: [PATCH] Add test case for SHE secure boot --- src/wh_server_she.c | 38 +++-- test-refactor/server/wh_test_she_server.c | 126 +++++++++++++- test-refactor/wh_test_list.c | 2 + test/wh_test_she.c | 192 +++++++++++++++++++++- 4 files changed, 345 insertions(+), 13 deletions(-) diff --git a/src/wh_server_she.c b/src/wh_server_she.c index 3615dce16..4fc90a59c 100644 --- a/src/wh_server_she.c +++ b/src/wh_server_she.c @@ -1617,16 +1617,32 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, { (void)req_packet; (void)req_size; + int ret = 0; - /* TODO does SHE specify what this error should be? */ - /* if we haven't secure booted, only allow secure boot requests */ - if ((server->she->sbState != WH_SHE_SB_SUCCESS && - (action != WH_SHE_SECURE_BOOT_INIT && - action != WH_SHE_SECURE_BOOT_UPDATE && - action != WH_SHE_SECURE_BOOT_FINISH && action != WH_SHE_GET_STATUS && - action != WH_SHE_SET_UID)) || - (action != WH_SHE_SET_UID && server->she->uidSet == 0)) { - /* Create an error response based on the action */ + if (action == WH_SHE_GET_STATUS) { + /* Status read is always permitted per AUTOSAR spec, even before boot + *or UID setup. */ + } + else if (action == WH_SHE_SET_UID) { + /* Provisioning is one-shot: reject once the UID is already set. */ + if (server->she->uidSet != 0) { + ret = WH_SHE_ERC_SEQUENCE_ERROR; + } + } + else if (server->she->uidSet == 0) { + /* Every remaining command needs a provisioned UID. */ + ret = WH_SHE_ERC_SEQUENCE_ERROR; + } + else if (action != WH_SHE_SECURE_BOOT_INIT && + action != WH_SHE_SECURE_BOOT_UPDATE && + action != WH_SHE_SECURE_BOOT_FINISH && + server->she->sbState != WH_SHE_SB_SUCCESS) { + /* Non-boot commands are blocked until secure boot succeeds. */ + ret = WH_SHE_ERC_SEQUENCE_ERROR; + } + + if (ret != 0) { + /* State is invalid, create an error response based on the action */ switch (action) { case WH_SHE_SET_UID: { whMessageShe_SetUidResponse resp; @@ -1767,9 +1783,9 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, break; } } - return WH_SHE_ERC_SEQUENCE_ERROR; } - return 0; + + return ret; } int wh_Server_HandleSheRequest(whServerContext* server, uint16_t magic, diff --git a/test-refactor/server/wh_test_she_server.c b/test-refactor/server/wh_test_she_server.c index c772f34fb..d417873e8 100644 --- a/test-refactor/server/wh_test_she_server.c +++ b/test-refactor/server/wh_test_she_server.c @@ -46,8 +46,9 @@ #include "wh_test_common.h" #include "wh_test_list.h" -/* Value of WH_SHE_SB_SUCCESS from the wh_server_she.c internal enum. +/* Values from the wh_server_she.c internal WH_SHE_SB_STATE enum. * Mirrored here since the enum is private to that translation unit. */ +#define TEST_SHE_SB_STATE_INIT 0 #define TEST_SHE_SB_STATE_SUCCESS 3 @@ -479,4 +480,127 @@ int whTest_SheReqSizeChecking(whServerContext* server) return 0; } + +/* Send one SHE action through the server and return its response rc. */ +static int32_t wh_She_SheActionRc(whServerContext* server, uint16_t action, + const void* req_packet, uint16_t req_size, + void* resp_packet) +{ + uint16_t resp_size = 0; + int ret; + + *((int32_t*)resp_packet) = WH_SHE_ERC_NO_ERROR; + ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE, action, + req_size, req_packet, &resp_size, + resp_packet); + if (ret != 0 || resp_size < sizeof(int32_t)) { + return WH_SHE_ERC_GENERAL_ERROR; + } + return *((const int32_t*)resp_packet); +} + + +/* Test that the SHE state gate rejects protected commands before the SHE + * state machine is initialized. */ +int whTest_SheStateGate(whServerContext* server) +{ + uint32_t i; + int32_t rc; + + /* Buffers for request and response packets */ + uint8_t req_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + uint8_t resp_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + + /* Protected actions that must not run before the state machine is up. + * req_size is the valid size for each, so a deleted gate would let the + * handler proceed rather than fail on size alone. */ + const struct { + uint16_t action; + uint16_t req_size; + } protectedActions[] = { + {WH_SHE_LOAD_PLAIN_KEY, sizeof(whMessageShe_LoadPlainKeyRequest)}, + {WH_SHE_INIT_RND, 0}, + {WH_SHE_ENC_ECB, sizeof(whMessageShe_EncEcbRequest)}, + }; + const uint32_t protectedCount = + sizeof(protectedActions) / sizeof(protectedActions[0]); + + if (server == NULL) { + return WH_ERROR_BADARGS; + } + + /* Start from a clean SHE state machine: uidSet=0, sbState=INIT. */ + memset(server->she, 0, sizeof(*server->she)); + memset(req_packet, 0, sizeof(req_packet)); + + /* + * Phase 1: before UID setup. Protected commands must be rejected with + * WH_SHE_ERC_SEQUENCE_ERROR, while GET_STATUS is allowed (per SHE spec). + */ + for (i = 0; i < protectedCount; i++) { + rc = wh_She_SheActionRc(server, protectedActions[i].action, req_packet, + protectedActions[i].req_size, resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + } + /* Secure boot itself also needs the UID set first. */ + rc = wh_She_SheActionRc(server, WH_SHE_SECURE_BOOT_INIT, req_packet, + sizeof(whMessageShe_SecureBootInitRequest), + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + /* GET_STATUS is callable at any time, even before UID setup. */ + rc = wh_She_SheActionRc(server, WH_SHE_GET_STATUS, req_packet, 0, + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + /* Rejected commands left no state behind. */ + WH_TEST_ASSERT_RETURN(server->she->uidSet == 0); + WH_TEST_ASSERT_RETURN(server->she->rndInited == 0); + + /* SET_UID passes the gate and provisions the UID. */ + { + whMessageShe_SetUidRequest* uidReq = + (whMessageShe_SetUidRequest*)req_packet; + memset(uidReq, 0, sizeof(*uidReq)); + memset(uidReq->uid, 0x5A, WH_SHE_UID_SZ); + rc = wh_She_SheActionRc(server, WH_SHE_SET_UID, req_packet, + sizeof(*uidReq), resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + WH_TEST_ASSERT_RETURN(server->she->uidSet == 1); + } + memset(req_packet, 0, sizeof(req_packet)); + + /* + * Phase 2: UID set but secure boot not yet successful. Isolates the + * secure-boot requirement from the UID check above. + */ + server->she->sbState = TEST_SHE_SB_STATE_INIT; + for (i = 0; i < protectedCount; i++) { + rc = wh_She_SheActionRc(server, protectedActions[i].action, req_packet, + protectedActions[i].req_size, resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + } + rc = wh_She_SheActionRc(server, WH_SHE_GET_STATUS, req_packet, 0, + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + WH_TEST_ASSERT_RETURN(server->she->rndInited == 0); + + /* + * Phase 3: UID set and secure boot successful. The gate must let a + * protected command reach its handler, proving it does not over-block. + * Keys are not provisioned here, so INIT_RND still fails inside the + * handler, but with a key error rather than a gate sequence error. + */ + server->she->sbState = TEST_SHE_SB_STATE_SUCCESS; + rc = wh_She_SheActionRc(server, WH_SHE_INIT_RND, req_packet, 0, + resp_packet); + WH_TEST_ASSERT_RETURN(rc != WH_SHE_ERC_SEQUENCE_ERROR); + + /* Restore a clean SHE context so the poked state doesn't leak into the + * live request loop the server enters next. */ + memset(server->she, 0, sizeof(*server->she)); + + WH_TEST_PRINT("SHE state gate test SUCCESS\n"); + + return 0; +} + #endif /* WOLFHSM_CFG_SHE_EXTENSION && !WOLFHSM_CFG_NO_CRYPTO */ diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 683555463..5ac2c3e39 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -71,6 +71,7 @@ WH_TEST_DECL(whTest_CryptoSha256); WH_TEST_DECL(whTest_She); WH_TEST_DECL(whTest_SheMasterEcuKeyFallback); WH_TEST_DECL(whTest_SheReqSizeChecking); +WH_TEST_DECL(whTest_SheStateGate); WH_TEST_DECL(whTest_Echo); WH_TEST_DECL(whTest_NvmDma); WH_TEST_DECL(whTest_NvmOps); @@ -99,6 +100,7 @@ const whTestCase whTestsServer[] = { { "whTest_NvmOptional", whTest_NvmOptional}, { "whTest_SheMasterEcuKeyFallback", whTest_SheMasterEcuKeyFallback }, { "whTest_SheReqSizeChecking", whTest_SheReqSizeChecking }, + { "whTest_SheStateGate", whTest_SheStateGate }, }; const size_t whTestsServerCount = ARRAY_SIZE(whTestsServer); diff --git a/test/wh_test_she.c b/test/wh_test_she.c index b15deddd4..5cc5eedc3 100644 --- a/test/wh_test_she.c +++ b/test/wh_test_she.c @@ -1185,7 +1185,8 @@ static int wh_She_TestMasterEcuKeyFallback(void) #endif /* WOLFHSM_CFG_ENABLE_SERVER */ #if defined(WOLFHSM_CFG_ENABLE_SERVER) -/* Value of WH_SHE_SB_SUCCESS from wh_server_she.c internal enum */ +/* Values from the wh_server_she.c internal WH_SHE_SB_STATE enum */ +#define TEST_SHE_SB_STATE_INIT 0 #define TEST_SHE_SB_STATE_SUCCESS 3 /** @@ -1645,6 +1646,193 @@ static int wh_She_TestReqSizeChecking(void) return 0; } + +/* Send one SHE action through the server and return its response rc. */ +static int32_t wh_She_SheActionRc(whServerContext* server, uint16_t action, + const void* req_packet, uint16_t req_size, + void* resp_packet) +{ + uint16_t resp_size = 0; + int ret; + + *((int32_t*)resp_packet) = WH_SHE_ERC_NO_ERROR; + ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE, action, + req_size, req_packet, &resp_size, + resp_packet); + if (ret != 0 || resp_size < sizeof(int32_t)) { + return WH_SHE_ERC_GENERAL_ERROR; + } + return *((const int32_t*)resp_packet); +} + +/* Test that the SHE state gate rejects protected commands before the SHE + * state machine is initialized. */ +static int wh_She_TestStateGate(void) +{ + int ret = 0; + uint32_t i; + int32_t rc; + whServerContext server[1] = {0}; + + /* Buffers for request and response packets */ + uint8_t req_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + uint8_t resp_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + + /* Protected actions that must not run before the state machine is up. + * req_size is the valid size for each, so a deleted gate would let the + * handler proceed rather than fail on size alone. */ + const struct { + uint16_t action; + uint16_t req_size; + } protectedActions[] = { + {WH_SHE_LOAD_PLAIN_KEY, sizeof(whMessageShe_LoadPlainKeyRequest)}, + {WH_SHE_INIT_RND, 0}, + {WH_SHE_ENC_ECB, sizeof(whMessageShe_EncEcbRequest)}, + }; + const uint32_t protectedCount = + sizeof(protectedActions) / sizeof(protectedActions[0]); + + /* Transport (not used, but required for server init) */ + uint8_t reqBuf[BUFFER_SIZE] = {0}; + uint8_t respBuf[BUFFER_SIZE] = {0}; + whTransportMemConfig tmcf[1] = {{ + .req = (whTransportMemCsr*)reqBuf, + .req_size = sizeof(reqBuf), + .resp = (whTransportMemCsr*)respBuf, + .resp_size = sizeof(respBuf), + }}; + whTransportServerCb tscb[1] = {WH_TRANSPORT_MEM_SERVER_CB}; + whTransportMemServerContext tmsc[1] = {0}; + whCommServerConfig cs_conf[1] = {{ + .transport_cb = tscb, + .transport_context = (void*)tmsc, + .transport_config = (void*)tmcf, + .server_id = 125, + }}; + + /* RamSim Flash state and configuration. + * memory[] is static to avoid 1MB stack allocation. */ + static uint8_t memory[FLASH_RAM_SIZE]; + whFlashRamsimCtx fc[1] = {0}; + whFlashRamsimCfg fc_conf[1] = {{0}}; + const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB}; + + /* NVM */ + whNvmFlashConfig nf_conf[1] = {{ + .cb = fcb, + .context = fc, + .config = fc_conf, + }}; + whNvmFlashContext nfc[1] = {0}; + whNvmCb nfcb[1] = {WH_NVM_FLASH_CB}; + whNvmConfig n_conf[1] = {{ + .cb = nfcb, + .context = nfc, + .config = nf_conf, + }}; + whNvmContext nvm[1] = {{0}}; + + /* Crypto context */ + whServerCryptoContext crypto[1] = {0}; + + whServerSheContext she[1]; + + whServerConfig s_conf[1] = {{ + .comm_config = cs_conf, + .nvm = nvm, + .crypto = crypto, + .she = she, + .devId = INVALID_DEVID, + }}; + + memset(she, 0, sizeof(she)); + memset(memory, 0, sizeof(memory)); + memset(req_packet, 0, sizeof(req_packet)); + + fc_conf->size = FLASH_RAM_SIZE; + fc_conf->sectorSize = FLASH_SECTOR_SIZE; + fc_conf->pageSize = FLASH_PAGE_SIZE; + fc_conf->erasedByte = ~(uint8_t)0; + fc_conf->memory = memory; + + WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(nvm, n_conf)); + WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(crypto->rng, NULL, s_conf->devId)); + WH_TEST_RETURN_ON_FAIL(wh_Server_Init(server, s_conf)); + WH_TEST_RETURN_ON_FAIL(wh_Server_SetConnected(server, WH_COMM_CONNECTED)); + + /* she is zeroed: uidSet == 0, sbState == WH_SHE_SB_INIT. */ + + /* + * Phase 1: before UID setup. Protected commands must be rejected with + * WH_SHE_ERC_SEQUENCE_ERROR, while GET_STATUS is allowed (per SHE spec). + */ + for (i = 0; i < protectedCount; i++) { + rc = wh_She_SheActionRc(server, protectedActions[i].action, req_packet, + protectedActions[i].req_size, resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + } + /* Secure boot itself also needs the UID set first. */ + rc = wh_She_SheActionRc(server, WH_SHE_SECURE_BOOT_INIT, req_packet, + sizeof(whMessageShe_SecureBootInitRequest), + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + /* GET_STATUS is callable at any time, even before UID setup. */ + rc = wh_She_SheActionRc(server, WH_SHE_GET_STATUS, req_packet, 0, + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + /* Rejected commands left no state behind. */ + WH_TEST_ASSERT_RETURN(server->she->uidSet == 0); + WH_TEST_ASSERT_RETURN(server->she->rndInited == 0); + + /* SET_UID passes the gate and provisions the UID. */ + { + whMessageShe_SetUidRequest* uidReq = + (whMessageShe_SetUidRequest*)req_packet; + memset(uidReq, 0, sizeof(*uidReq)); + memset(uidReq->uid, 0x5A, WH_SHE_UID_SZ); + rc = wh_She_SheActionRc(server, WH_SHE_SET_UID, req_packet, + sizeof(*uidReq), resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + WH_TEST_ASSERT_RETURN(server->she->uidSet == 1); + } + memset(req_packet, 0, sizeof(req_packet)); + + /* + * Phase 2: UID set but secure boot not yet successful. Isolates the + * secure-boot requirement from the UID check above. + */ + server->she->sbState = TEST_SHE_SB_STATE_INIT; + for (i = 0; i < protectedCount; i++) { + rc = wh_She_SheActionRc(server, protectedActions[i].action, req_packet, + protectedActions[i].req_size, resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + } + rc = wh_She_SheActionRc(server, WH_SHE_GET_STATUS, req_packet, 0, + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + WH_TEST_ASSERT_RETURN(server->she->rndInited == 0); + + /* + * Phase 3: UID set and secure boot successful. The gate must let a + * protected command reach its handler, proving it does not over-block. + * Keys are not provisioned here, so INIT_RND still fails inside the + * handler, but with a key error rather than a gate sequence error. + */ + server->she->sbState = TEST_SHE_SB_STATE_SUCCESS; + rc = wh_She_SheActionRc(server, WH_SHE_INIT_RND, req_packet, 0, + resp_packet); + WH_TEST_ASSERT_RETURN(rc != WH_SHE_ERC_SEQUENCE_ERROR); + + WH_TEST_PRINT("SHE state gate test SUCCESS\n"); + + wh_Server_Cleanup(server); + wh_Nvm_Cleanup(nvm); + wc_FreeRng(crypto->rng); + wolfCrypt_Cleanup(); + + return ret; +} #endif /* WOLFHSM_CFG_ENABLE_SERVER */ #if defined(WOLFHSM_CFG_TEST_POSIX) && defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ @@ -1656,6 +1844,8 @@ int whTest_She(void) WH_TEST_PRINT("Testing SHE: req_size checking...\n"); WH_TEST_RETURN_ON_FAIL(wh_She_TestReqSizeChecking()); + WH_TEST_PRINT("Testing SHE: state gate...\n"); + WH_TEST_RETURN_ON_FAIL(wh_She_TestStateGate()); WH_TEST_PRINT("Testing SHE: (pthread) mem core flow...\n"); WH_TEST_RETURN_ON_FAIL( wh_ClientServer_MemThreadTest(whTest_SheClientConfig));