From 76953578aa216d81a120563a42bd0bbe26efe640 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Mon, 13 Jul 2026 12:35:19 -0400 Subject: [PATCH] Implement SHE GET_ID command with client side request, server side handler, and tests --- docs/src/5-Features.md | 1 + src/wh_client_she.c | 60 +++++ src/wh_message_she.c | 32 +++ src/wh_server_she.c | 145 +++++++++-- test-refactor/client-server/wh_test_she.c | 40 +++ .../misc/wh_test_check_struct_padding.c | 2 + test-refactor/server/wh_test_she_server.c | 20 ++ test/wh_test_check_struct_padding.c | 2 + test/wh_test_she.c | 227 ++++++++++++++++++ wolfhsm/wh_client_she.h | 57 +++++ wolfhsm/wh_message.h | 1 + wolfhsm/wh_message_she.h | 23 ++ 12 files changed, 589 insertions(+), 21 deletions(-) diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 0e0f7d3af..3d10138c5 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -629,6 +629,7 @@ The SHE client API is declared in `wolfhsm/wh_client_she.h` and maps one-to-one - **Bulk crypto**: `wh_Client_SheEncEcb` / `wh_Client_SheEncCbc` / `wh_Client_SheDecEcb` / `wh_Client_SheDecCbc` (`CMD_ENC_*` / `CMD_DEC_*`) — AES-ECB and AES-CBC encrypt and decrypt against a selected key slot - **MAC**: `wh_Client_SheGenerateMac` / `wh_Client_SheVerifyMac` (`CMD_GENERATE_MAC` / `CMD_VERIFY_MAC`) — CMAC generation and verification against a selected key slot - **Status**: `wh_Client_SheGetStatus` (`CMD_GET_STATUS`) — reads the SHE status register (SREG) +- **Module identity**: `wh_Client_SheGetId` (`CMD_GET_ID`) — returns the ECU UID, the status register, and a CMAC over the caller's challenge, UID, and status register computed under the `MASTER_ECU_KEY`, letting a party that holds that key verify the module's identity. If the `MASTER_ECU_KEY` slot is empty the MAC is computed with an all-zero key, per the spec. In addition to the spec commands, wolfHSM exposes two non-standard helpers that fill gaps left by the spec's assumption of dedicated hardware: diff --git a/src/wh_client_she.c b/src/wh_client_she.c index 716934f83..ba6b088d1 100644 --- a/src/wh_client_she.c +++ b/src/wh_client_she.c @@ -265,6 +265,66 @@ int wh_Client_SheGetStatus(whClientContext* c, uint8_t* sreg) return ret; } +int wh_Client_SheGetIdRequest(whClientContext* c, uint8_t* challenge, + uint32_t challengeSz) +{ + whMessageShe_GetIdRequest* req = NULL; + + if (c == NULL || challenge == NULL || challengeSz < WH_SHE_KEY_SZ) { + return WH_ERROR_BADARGS; + } + + req = (whMessageShe_GetIdRequest*)wh_CommClient_GetDataPtr(c->comm); + + memcpy(req->challenge, challenge, sizeof(req->challenge)); + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_SHE, WH_SHE_GET_ID, + sizeof(*req), (uint8_t*)req); +} + +int wh_Client_SheGetIdResponse(whClientContext* c, uint8_t* uid, uint8_t* sreg, + uint8_t* mac) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t dataSz; + whMessageShe_GetIdResponse* resp = NULL; + + if (c == NULL || uid == NULL || sreg == NULL || mac == NULL) { + return WH_ERROR_BADARGS; + } + + resp = (whMessageShe_GetIdResponse*)wh_CommClient_GetDataPtr(c->comm); + + ret = wh_Client_RecvResponse(c, &group, &action, &dataSz, (uint8_t*)resp); + if (ret == 0) { + if (resp->rc != WH_SHE_ERC_NO_ERROR) { + ret = resp->rc; + } + else { + memcpy(uid, resp->uid, sizeof(resp->uid)); + *sreg = resp->sreg; + memcpy(mac, resp->mac, sizeof(resp->mac)); + } + } + return ret; +} + +int wh_Client_SheGetId(whClientContext* c, uint8_t* challenge, + uint32_t challengeSz, uint8_t* uid, uint8_t* sreg, + uint8_t* mac) +{ + int ret; + ret = wh_Client_SheGetIdRequest(c, challenge, challengeSz); + if (ret == 0) { + do { + ret = wh_Client_SheGetIdResponse(c, uid, sreg, mac); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + int wh_Client_SheLoadKeyRequest(whClientContext* c, uint8_t* messageOne, uint8_t* messageTwo, uint8_t* messageThree) { diff --git a/src/wh_message_she.c b/src/wh_message_she.c index 659673128..9aa82d376 100644 --- a/src/wh_message_she.c +++ b/src/wh_message_she.c @@ -429,4 +429,36 @@ int wh_MessageShe_TranslateVerifyMacResponse( return 0; } +/* Get ID translation functions */ +int wh_MessageShe_TranslateGetIdRequest(uint16_t magic, + const whMessageShe_GetIdRequest* src, + whMessageShe_GetIdRequest* dest) +{ + (void)magic; + + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + if (src != dest) { + memcpy(dest->challenge, src->challenge, WH_SHE_KEY_SZ); + } + return 0; +} + +int wh_MessageShe_TranslateGetIdResponse( + uint16_t magic, const whMessageShe_GetIdResponse* src, + whMessageShe_GetIdResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + if (src != dest) { + memcpy(dest->uid, src->uid, WH_SHE_UID_SZ); + memcpy(dest->mac, src->mac, WH_SHE_KEY_SZ); + } + dest->sreg = src->sreg; + return 0; +} + #endif /* WOLFHSM_CFG_SHE_EXTENSION */ \ No newline at end of file diff --git a/src/wh_server_she.c b/src/wh_server_she.c index 79f778b2a..d0eed4cdd 100644 --- a/src/wh_server_she.c +++ b/src/wh_server_she.c @@ -125,6 +125,10 @@ static int _GenerateMac(whServerContext* server, uint16_t magic, static int _VerifyMac(whServerContext* server, uint16_t magic, uint16_t req_size, const void* req_packet, uint16_t* out_resp_size, void* resp_packet); +static int _GetId(whServerContext* server, uint16_t magic, uint16_t req_size, + const void* req_packet, uint16_t* out_resp_size, + void* resp_packet); +static uint8_t _BuildSreg(whServerContext* server); static int _TranslateSheReturnCode(int ret); static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, uint16_t action, uint16_t req_size, @@ -431,6 +435,33 @@ static int _SecureBootFinish(whServerContext* server, uint16_t magic, return ret; } +/* Compose the 8-bit SHE status register (SREG) from the current server state. + * TODO do we care about all the sreg fields? */ +static uint8_t _BuildSreg(whServerContext* server) +{ + uint8_t sreg = 0; + + /* SECURE_BOOT */ + if (server->she->cmacKeyFound) { + sreg |= WH_SHE_SREG_SECURE_BOOT; + } + /* BOOT_FINISHED */ + if (server->she->sbState == WH_SHE_SB_SUCCESS || + server->she->sbState == WH_SHE_SB_FAILURE) { + sreg |= WH_SHE_SREG_BOOT_FINISHED; + } + /* BOOT_OK */ + if (server->she->sbState == WH_SHE_SB_SUCCESS) { + sreg |= WH_SHE_SREG_BOOT_OK; + } + /* RND_INIT */ + if (server->she->rndInited == 1) { + sreg |= WH_SHE_SREG_RND_INIT; + } + + return sreg; +} + static int _GetStatus(whServerContext* server, uint16_t magic, uint16_t req_size, const void* req_packet, uint16_t* out_resp_size, void* resp_packet) @@ -445,26 +476,7 @@ static int _GetStatus(whServerContext* server, uint16_t magic, } if (ret == 0) { - /* TODO do we care about all the sreg fields? */ - resp.sreg = 0; - /* SECURE_BOOT */ - if (server->she->cmacKeyFound) { - resp.sreg |= WH_SHE_SREG_SECURE_BOOT; - } - - /* BOOT_FINISHED */ - if (server->she->sbState == WH_SHE_SB_SUCCESS || - server->she->sbState == WH_SHE_SB_FAILURE) { - resp.sreg |= WH_SHE_SREG_BOOT_FINISHED; - } - /* BOOT_OK */ - if (server->she->sbState == WH_SHE_SB_SUCCESS) { - resp.sreg |= WH_SHE_SREG_BOOT_OK; - } - /* RND_INIT */ - if (server->she->rndInited == 1) { - resp.sreg |= WH_SHE_SREG_RND_INIT; - } + resp.sreg = _BuildSreg(server); } *out_resp_size = sizeof(resp); @@ -1606,6 +1618,78 @@ static int _VerifyMac(whServerContext* server, uint16_t magic, return ret; } +static int _GetId(whServerContext* server, uint16_t magic, uint16_t req_size, + const void* req_packet, uint16_t* out_resp_size, + void* resp_packet) +{ + int ret = 0; + uint32_t field = AES_BLOCK_SIZE; + uint32_t keySz; + uint8_t tmpKey[WH_SHE_KEY_SZ]; + /* CMAC input: CHALLENGE || UID || SREG */ + uint8_t macIn[WH_SHE_KEY_SZ + WH_SHE_UID_SZ + 1]; + whMessageShe_GetIdRequest req = {0}; + whMessageShe_GetIdResponse resp = {0}; + + if (req_size < sizeof(req)) { + ret = WH_ERROR_BUFFER_SIZE; + } + + if (ret == 0) { + ret = wh_MessageShe_TranslateGetIdRequest(magic, req_packet, &req); + } + + if (ret == 0) { + /* Assemble the CMAC input: challenge || uid || sreg */ + uint8_t sreg = _BuildSreg(server); + memcpy(macIn, req.challenge, WH_SHE_KEY_SZ); + memcpy(macIn + WH_SHE_KEY_SZ, server->she->uid, WH_SHE_UID_SZ); + macIn[WH_SHE_KEY_SZ + WH_SHE_UID_SZ] = sreg; + + /* Load the MASTER_ECU_KEY. Per the SHE spec, if the slot is empty the + * MAC is computed with an all-zero key rather than returning an error. + * wh_Server_KeystoreReadKey already applies this substitution for the + * MASTER_ECU_KEY slot (returns a zero-filled key and rc 0), so the + * WH_ERROR_NOTFOUND branch below is only a defensive backstop for + * builds/paths where that substitution does not occur. */ + keySz = WH_SHE_KEY_SZ; + ret = wh_Server_KeystoreReadKey( + server, + WH_MAKE_KEYID(WH_KEYTYPE_SHE, server->comm->client_id, + WH_SHE_MASTER_ECU_KEY_ID), + NULL, tmpKey, &keySz); + if (ret == WH_ERROR_NOTFOUND) { + memset(tmpKey, 0, WH_SHE_KEY_SZ); + ret = 0; + } + else if (ret == 0 && keySz != WH_SHE_KEY_SZ) { + ret = WH_SHE_ERC_KEY_INVALID; + } + + /* Compute the identity MAC over challenge || uid || sreg */ + if (ret == 0) { + ret = wc_AesCmacGenerate_ex(server->she->sheCmac, resp.mac, + (word32*)&field, macIn, sizeof(macIn), + tmpKey, WH_SHE_KEY_SZ, NULL, + server->devId); + } + + /* Fill the remaining response fields */ + if (ret == 0) { + memcpy(resp.uid, server->she->uid, sizeof(resp.uid)); + resp.sreg = sreg; + } + } + + resp.rc = _TranslateSheReturnCode(ret); + (void)wh_MessageShe_TranslateGetIdResponse(magic, &resp, resp_packet); + *out_resp_size = sizeof(resp); + + wh_Utils_ForceZero(tmpKey, sizeof(tmpKey)); + + return ret; +} + /* TODO: This is terrible, but without implementing a SHE sub-protocol like we * do for crypto layer, there is no way to return non-request specific error @@ -1636,8 +1720,11 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, else if (action != WH_SHE_SECURE_BOOT_INIT && action != WH_SHE_SECURE_BOOT_UPDATE && action != WH_SHE_SECURE_BOOT_FINISH && + action != WH_SHE_GET_ID && server->she->sbState != WH_SHE_SB_SUCCESS) { - /* Non-boot commands are blocked until secure boot succeeds. */ + /* Non-boot commands are blocked until secure boot succeeds. GET_ID is + * exempt (the AUTOSAR spec permits it in every state), though it still + * requires a provisioned UID via the uidSet check above. */ ret = WH_SHE_ERC_SEQUENCE_ERROR; } @@ -1782,6 +1869,14 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, *out_resp_size = sizeof(resp); break; } + case WH_SHE_GET_ID: { + whMessageShe_GetIdResponse resp = {0}; + resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + (void)wh_MessageShe_TranslateGetIdResponse(magic, &resp, + resp_packet); + *out_resp_size = sizeof(resp); + break; + } } } @@ -1932,6 +2027,14 @@ int wh_Server_HandleSheRequest(whServerContext* server, uint16_t magic, (void)WH_SERVER_NVM_UNLOCK(server); } /* WH_SERVER_NVM_LOCK() */ break; + case WH_SHE_GET_ID: + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + ret = _GetId(server, magic, req_size, req_packet, out_resp_size, + resp_packet); + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + break; default: ret = WH_ERROR_BADARGS; break; diff --git a/test-refactor/client-server/wh_test_she.c b/test-refactor/client-server/wh_test_she.c index f5883e403..829af49f2 100644 --- a/test-refactor/client-server/wh_test_she.c +++ b/test-refactor/client-server/wh_test_she.c @@ -145,6 +145,13 @@ int whTest_She(whClientContext* client) uint8_t messageThree[WH_SHE_M3_SZ]; uint8_t messageFour[WH_SHE_M4_SZ]; uint8_t messageFive[WH_SHE_M5_SZ]; + uint8_t sheChallenge[WH_SHE_KEY_SZ] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; + uint8_t sheGetIdUid[WH_SHE_UID_SZ]; + uint8_t sheGetIdMac[WH_SHE_KEY_SZ]; + uint8_t expectedGetIdMac[WH_SHE_KEY_SZ]; + uint8_t getIdMacInput[WH_SHE_KEY_SZ + WH_SHE_UID_SZ + 1]; + word32 expectedGetIdMacSz = sizeof(expectedGetIdMac); const uint32_t SHE_TEST_VECTOR_KEY_ID = 4; const uint32_t SHE_WP_KEY_ID = 6; @@ -317,6 +324,39 @@ int whTest_She(whClientContext* client) } WH_TEST_PRINT("SHE LOAD KEY SUCCESS\n"); + /* === GET_ID identity + MAC verification === */ + + /* CMD_GET_ID: read the module identity and verify the identity MAC. The + * MASTER_ECU_KEY (slot 1) was loaded above with vectorMasterEcuKey, so we + * can recompute the expected CMAC over challenge || uid || sreg. */ + if ((ret = wh_Client_SheGetId(client, sheChallenge, sizeof(sheChallenge), + sheGetIdUid, &sreg, sheGetIdMac)) != 0) { + WH_ERROR_PRINT("Failed to wh_Client_SheGetId %d\n", ret); + goto exit; + } + if (memcmp(sheGetIdUid, sheUid, WH_SHE_UID_SZ) != 0) { + ret = WH_ERROR_ABORTED; + WH_ERROR_PRINT("SHE GET_ID returned an unexpected UID\n"); + goto exit; + } + /* expected MAC = CMAC(MASTER_ECU_KEY, challenge || uid || sreg) */ + memcpy(getIdMacInput, sheChallenge, WH_SHE_KEY_SZ); + memcpy(getIdMacInput + WH_SHE_KEY_SZ, sheGetIdUid, WH_SHE_UID_SZ); + getIdMacInput[WH_SHE_KEY_SZ + WH_SHE_UID_SZ] = sreg; + expectedGetIdMacSz = sizeof(expectedGetIdMac); + if ((ret = wc_AesCmacGenerate(expectedGetIdMac, &expectedGetIdMacSz, + getIdMacInput, sizeof(getIdMacInput), vectorMasterEcuKey, + sizeof(vectorMasterEcuKey))) != 0) { + WH_ERROR_PRINT("Failed to compute expected GET_ID MAC %d\n", ret); + goto exit; + } + if (memcmp(sheGetIdMac, expectedGetIdMac, WH_SHE_KEY_SZ) != 0) { + ret = WH_ERROR_ABORTED; + WH_ERROR_PRINT("SHE GET_ID MAC mismatch\n"); + goto exit; + } + WH_TEST_PRINT("SHE GET ID SUCCESS\n"); + /* === LoadKey UID handling === */ /* A non-matching UID must be rejected, an all-zero UID must be diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c index 1e6bf04b7..79e50bed2 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -194,6 +194,8 @@ whMessageShe_GenMacRequest sheGenMacReq; whMessageShe_GenMacResponse sheGenMacRes; whMessageShe_VerifyMacRequest sheVerifyMacReq; whMessageShe_VerifyMacResponse sheVerifyMacRes; +whMessageShe_GetIdRequest sheGetIdReq; +whMessageShe_GetIdResponse sheGetIdRes; #endif /* WOLFHSM_CFG_SHE_EXTENSION */ #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) diff --git a/test-refactor/server/wh_test_she_server.c b/test-refactor/server/wh_test_she_server.c index d417873e8..f4e8668a3 100644 --- a/test-refactor/server/wh_test_she_server.c +++ b/test-refactor/server/wh_test_she_server.c @@ -471,6 +471,26 @@ int whTest_SheReqSizeChecking(whServerContext* server) WH_TEST_ASSERT_RETURN(verifyMacResp->rc != WH_SHE_ERC_NO_ERROR); } + /* + * Test 18: WH_SHE_GET_ID with truncated request. + * Populate a valid challenge, but pass req_size one byte short. + */ + { + whMessageShe_GetIdRequest* req = + (whMessageShe_GetIdRequest*)req_packet; + whMessageShe_GetIdResponse* getIdResp = + (whMessageShe_GetIdResponse*)resp_packet; + memset(getIdResp, 0, sizeof(*getIdResp)); + memset(req->challenge, 0xAA, WH_SHE_KEY_SZ); + req_size = sizeof(whMessageShe_GetIdRequest) - 1; + ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE, + WH_SHE_GET_ID, req_size, + req_packet, &resp_size, resp_packet); + WH_TEST_ASSERT_RETURN(ret == 0); + WH_TEST_ASSERT_RETURN(resp_size == sizeof(*getIdResp)); + WH_TEST_ASSERT_RETURN(getIdResp->rc != WH_SHE_ERC_NO_ERROR); + } + /* Restore a clean SHE context so the poked uidSet/sbState don't * leak into the live request loop the server enters next. */ memset(server->she, 0, sizeof(*server->she)); diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index ba3d86295..49da27b64 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -202,6 +202,8 @@ whMessageShe_GenMacRequest sheGenMacReq; whMessageShe_GenMacResponse sheGenMacRes; whMessageShe_VerifyMacRequest sheVerifyMacReq; whMessageShe_VerifyMacResponse sheVerifyMacRes; +whMessageShe_GetIdRequest sheGetIdReq; +whMessageShe_GetIdResponse sheGetIdRes; #endif /* WOLFHSM_CFG_SHE_EXTENSION */ #if defined(WOLFHSM_CFG_CERTIFICATE_MANAGER) diff --git a/test/wh_test_she.c b/test/wh_test_she.c index 5cc5eedc3..ddbc9d2d9 100644 --- a/test/wh_test_she.c +++ b/test/wh_test_she.c @@ -164,6 +164,13 @@ int whTest_SheClientConfig(whClientConfig* config) uint8_t messageThree[WH_SHE_M3_SZ]; uint8_t messageFour[WH_SHE_M4_SZ]; uint8_t messageFive[WH_SHE_M5_SZ]; + uint8_t sheChallenge[WH_SHE_KEY_SZ] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; + uint8_t sheGetIdUid[WH_SHE_UID_SZ]; + uint8_t sheGetIdMac[WH_SHE_KEY_SZ]; + uint8_t expectedGetIdMac[WH_SHE_KEY_SZ]; + uint8_t getIdMacInput[WH_SHE_KEY_SZ + WH_SHE_UID_SZ + 1]; + word32 expectedGetIdMacSz = sizeof(expectedGetIdMac); uint32_t outClientId = 0; uint32_t outServerId = 0; const uint32_t SHE_TEST_VECTOR_KEY_ID = 4; @@ -320,6 +327,37 @@ int whTest_SheClientConfig(whClientConfig* config) } WH_TEST_PRINT("SHE LOAD KEY SUCCESS\n"); + /* CMD_GET_ID: read the module identity and verify the identity MAC. The + * MASTER_ECU_KEY (slot 1) was loaded above with vectorMasterEcuKey, so we + * can recompute the expected CMAC over challenge || uid || sreg. */ + if ((ret = wh_Client_SheGetId(client, sheChallenge, sizeof(sheChallenge), + sheGetIdUid, &sreg, sheGetIdMac)) != 0) { + WH_ERROR_PRINT("Failed to wh_Client_SheGetId %d\n", ret); + goto exit; + } + if (memcmp(sheGetIdUid, sheUid, WH_SHE_UID_SZ) != 0) { + ret = WH_ERROR_ABORTED; + WH_ERROR_PRINT("SHE GET_ID returned an unexpected UID\n"); + goto exit; + } + /* expected MAC = CMAC(MASTER_ECU_KEY, challenge || uid || sreg) */ + memcpy(getIdMacInput, sheChallenge, WH_SHE_KEY_SZ); + memcpy(getIdMacInput + WH_SHE_KEY_SZ, sheGetIdUid, WH_SHE_UID_SZ); + getIdMacInput[WH_SHE_KEY_SZ + WH_SHE_UID_SZ] = sreg; + expectedGetIdMacSz = sizeof(expectedGetIdMac); + if ((ret = wc_AesCmacGenerate(expectedGetIdMac, &expectedGetIdMacSz, + getIdMacInput, sizeof(getIdMacInput), vectorMasterEcuKey, + sizeof(vectorMasterEcuKey))) != 0) { + WH_ERROR_PRINT("Failed to compute expected GET_ID MAC %d\n", ret); + goto exit; + } + if (memcmp(sheGetIdMac, expectedGetIdMac, WH_SHE_KEY_SZ) != 0) { + ret = WH_ERROR_ABORTED; + WH_ERROR_PRINT("SHE GET_ID MAC mismatch\n"); + goto exit; + } + WH_TEST_PRINT("SHE GET ID SUCCESS\n"); + /* _LoadKey UID handling: a non-matching UID must be rejected, an * all-zero UID must be rejected unless the stored target key has * WH_SHE_FLAG_WILDCARD set. Use wh_She_GenerateLoadableKey with the @@ -1637,6 +1675,26 @@ static int wh_She_TestReqSizeChecking(void) WH_TEST_ASSERT_RETURN(verifyMacResp->rc != WH_SHE_ERC_NO_ERROR); } + /* + * Test 18: WH_SHE_GET_ID with truncated request. + * Populate a valid challenge, but pass req_size one byte short. + */ + { + whMessageShe_GetIdRequest* req = + (whMessageShe_GetIdRequest*)req_packet; + whMessageShe_GetIdResponse* getIdResp = + (whMessageShe_GetIdResponse*)resp_packet; + memset(getIdResp, 0, sizeof(*getIdResp)); + memset(req->challenge, 0xAA, WH_SHE_KEY_SZ); + req_size = sizeof(whMessageShe_GetIdRequest) - 1; + ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE, + WH_SHE_GET_ID, req_size, + req_packet, &resp_size, resp_packet); + WH_TEST_ASSERT_RETURN(ret == 0); + WH_TEST_ASSERT_RETURN(resp_size == sizeof(*getIdResp)); + WH_TEST_ASSERT_RETURN(getIdResp->rc != WH_SHE_ERC_NO_ERROR); + } + WH_TEST_PRINT("SHE req_size checking test SUCCESS\n"); wh_Server_Cleanup(server); @@ -1833,6 +1891,173 @@ static int wh_She_TestStateGate(void) return ret; } + +/** + * Server-direct GET_ID tests for two paths not covered by the end-to-end flow: + * 1. Empty MASTER_ECU_KEY slot: the identity MAC must be computed with an + * all-zero key (per the SHE spec), not error out. + * 2. GET_ID before secure boot: the command is whitelisted, so it must + * succeed even when sbState != SUCCESS (SREG reflects the un-booted state). + * Both are driven through wh_Server_HandleSheRequest() with no key ever loaded. + */ +static int wh_She_TestGetId(void) +{ + int ret = 0; + uint16_t resp_size = 0; + + uint8_t req_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + uint8_t resp_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + + 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, + }}; + + static uint8_t memory[FLASH_RAM_SIZE]; + whFlashRamsimCtx fc[1] = {0}; + whFlashRamsimCfg fc_conf[1] = {{0}}; + const whFlashCb fcb[1] = {WH_FLASH_RAMSIM_CB}; + + 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}}; + + whServerCryptoContext crypto[1] = {0}; + whServerSheContext she[1]; + whServerContext server[1] = {0}; + + whServerConfig s_conf[1] = {{ + .comm_config = cs_conf, + .nvm = nvm, + .crypto = crypto, + .she = she, + .devId = INVALID_DEVID, + }}; + + /* Known UID and challenge so the identity MAC can be recomputed. */ + uint8_t knownUid[WH_SHE_UID_SZ] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; + uint8_t challenge[WH_SHE_KEY_SZ] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; + uint8_t zeroKey[WH_SHE_KEY_SZ] = {0}; + uint8_t macInput[WH_SHE_KEY_SZ + WH_SHE_UID_SZ + 1]; + uint8_t expectedMac[WH_SHE_KEY_SZ]; + word32 expectedMacSz; + uint8_t sregBooted = 0; + uint8_t sregPreBoot = 0; + + memset(she, 0, sizeof(she)); + memset(memory, 0, sizeof(memory)); + + 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)); + + /* UID is set (GET_ID returns it), but NO MASTER_ECU_KEY is ever loaded, so + * the identity MAC must fall back to an all-zero key. */ + server->she->uidSet = 1; + memcpy(server->she->uid, knownUid, WH_SHE_UID_SZ); + + /* + * Case 1: empty MASTER_ECU_KEY, secure boot succeeded. + * Expect success, the stored UID echoed back, and the MAC computed under an + * all-zero key over challenge || uid || sreg. + */ + server->she->sbState = TEST_SHE_SB_STATE_SUCCESS; + { + whMessageShe_GetIdRequest* req = (whMessageShe_GetIdRequest*)req_packet; + whMessageShe_GetIdResponse* resp = + (whMessageShe_GetIdResponse*)resp_packet; + memset(resp, 0, sizeof(*resp)); + memcpy(req->challenge, challenge, WH_SHE_KEY_SZ); + ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE, + WH_SHE_GET_ID, sizeof(*req), req_packet, &resp_size, + resp_packet); + WH_TEST_ASSERT_RETURN(ret == 0); + WH_TEST_ASSERT_RETURN(resp_size == sizeof(*resp)); + WH_TEST_ASSERT_RETURN(resp->rc == WH_SHE_ERC_NO_ERROR); + WH_TEST_ASSERT_RETURN(memcmp(resp->uid, knownUid, WH_SHE_UID_SZ) == 0); + + sregBooted = resp->sreg; + memcpy(macInput, challenge, WH_SHE_KEY_SZ); + memcpy(macInput + WH_SHE_KEY_SZ, knownUid, WH_SHE_UID_SZ); + macInput[WH_SHE_KEY_SZ + WH_SHE_UID_SZ] = resp->sreg; + expectedMacSz = sizeof(expectedMac); + WH_TEST_RETURN_ON_FAIL(wc_AesCmacGenerate(expectedMac, &expectedMacSz, + macInput, sizeof(macInput), zeroKey, sizeof(zeroKey))); + WH_TEST_ASSERT_RETURN(memcmp(resp->mac, expectedMac, WH_SHE_KEY_SZ) == 0); + } + + /* + * Case 2: GET_ID before secure boot (sbState != SUCCESS). + * The command is whitelisted, so it must still succeed, and the SREG must + * reflect the un-booted state (differs from the booted SREG above). + */ + server->she->sbState = TEST_SHE_SB_STATE_INIT; + { + whMessageShe_GetIdRequest* req = (whMessageShe_GetIdRequest*)req_packet; + whMessageShe_GetIdResponse* resp = + (whMessageShe_GetIdResponse*)resp_packet; + memset(resp, 0, sizeof(*resp)); + memcpy(req->challenge, challenge, WH_SHE_KEY_SZ); + ret = wh_Server_HandleSheRequest(server, WH_COMM_MAGIC_NATIVE, + WH_SHE_GET_ID, sizeof(*req), req_packet, &resp_size, + resp_packet); + WH_TEST_ASSERT_RETURN(ret == 0); + WH_TEST_ASSERT_RETURN(resp->rc == WH_SHE_ERC_NO_ERROR); + WH_TEST_ASSERT_RETURN(memcmp(resp->uid, knownUid, WH_SHE_UID_SZ) == 0); + + sregPreBoot = resp->sreg; + macInput[WH_SHE_KEY_SZ + WH_SHE_UID_SZ] = resp->sreg; + expectedMacSz = sizeof(expectedMac); + WH_TEST_RETURN_ON_FAIL(wc_AesCmacGenerate(expectedMac, &expectedMacSz, + macInput, sizeof(macInput), zeroKey, sizeof(zeroKey))); + WH_TEST_ASSERT_RETURN(memcmp(resp->mac, expectedMac, WH_SHE_KEY_SZ) == 0); + } + + /* The booted and pre-boot status registers must differ, proving GET_ID's + * SREG reflects live server state rather than a fixed value. */ + WH_TEST_ASSERT_RETURN(sregBooted != sregPreBoot); + + WH_TEST_PRINT("SHE GET_ID empty-key / pre-secure-boot test SUCCESS\n"); + + wh_Server_Cleanup(server); + wh_Nvm_Cleanup(nvm); + wc_FreeRng(crypto->rng); + wolfCrypt_Cleanup(); + + return 0; +} #endif /* WOLFHSM_CFG_ENABLE_SERVER */ #if defined(WOLFHSM_CFG_TEST_POSIX) && defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ @@ -1846,6 +2071,8 @@ int whTest_She(void) 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: GET_ID empty-key / pre-secure-boot...\n"); + WH_TEST_RETURN_ON_FAIL(wh_She_TestGetId()); WH_TEST_PRINT("Testing SHE: (pthread) mem core flow...\n"); WH_TEST_RETURN_ON_FAIL( wh_ClientServer_MemThreadTest(whTest_SheClientConfig)); diff --git a/wolfhsm/wh_client_she.h b/wolfhsm/wh_client_she.h index 778bfb47a..f5855ee77 100644 --- a/wolfhsm/wh_client_she.h +++ b/wolfhsm/wh_client_she.h @@ -201,6 +201,63 @@ int wh_Client_SheGetStatusResponse(whClientContext* c, uint8_t* sreg); */ int wh_Client_SheGetStatus(whClientContext* c, uint8_t* sreg); +/** SHE identity functions */ + +/** + * @brief Sends a request to read the SHE module identity (CMD_GET_ID). + * + * Sends an AUTOSAR SHE CMD_GET_ID request carrying a 16-byte challenge. The + * server returns the ECU UID, the status register, and a CMAC over the + * challenge, UID, and status register computed under the MASTER_ECU_KEY + * (slot 1). If the MASTER_ECU_KEY slot is empty the MAC is computed with an + * all-zero key, per the SHE spec. + * + * @param[in] c Pointer to the client context. + * @param[in] challenge Pointer to the challenge bytes. + * @param[in] challengeSz Length of @p challenge; must be at least + * WH_SHE_KEY_SZ (16). + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_SheGetIdRequest(whClientContext* c, uint8_t* challenge, + uint32_t challengeSz); + +/** + * @brief Receives the SHE module identity response (CMD_GET_ID). + * + * Consumes a CMD_GET_ID response and writes out the ECU UID, status register, + * and identity MAC. + * + * @param[in] c Pointer to the client context. + * @param[out] uid Buffer that receives the WH_SHE_UID_SZ (15) byte UID. + * @param[out] sreg Pointer to a byte that receives the status register value. + * @param[out] mac Buffer that receives the WH_SHE_KEY_SZ (16) byte identity MAC. + * @return int Returns 0 on success, WH_ERROR_NOTREADY if no response is + * available yet, or a negative error code on failure. + */ +int wh_Client_SheGetIdResponse(whClientContext* c, uint8_t* uid, uint8_t* sreg, + uint8_t* mac); + +/** + * @brief Reads the SHE module identity with a blocking call (CMD_GET_ID). + * + * Sends a CMD_GET_ID request with the supplied @p challenge and busy-polls for + * the response, writing the ECU UID, status register, and identity MAC to + * @p uid, @p sreg, and @p mac respectively. The MAC is + * CMAC(MASTER_ECU_KEY, challenge || uid || sreg), allowing a tester to verify + * the module's identity. + * + * @param[in] c Pointer to the client context. + * @param[in] challenge Pointer to the challenge bytes. + * @param[in] challengeSz Length of @p challenge; must be at least + * WH_SHE_KEY_SZ (16). + * @param[out] uid Buffer that receives the WH_SHE_UID_SZ (15) byte UID. + * @param[out] sreg Pointer to a byte that receives the status register value. + * @param[out] mac Buffer that receives the WH_SHE_KEY_SZ (16) byte identity MAC. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_SheGetId(whClientContext* c, uint8_t* challenge, + uint32_t challengeSz, uint8_t* uid, uint8_t* sreg, uint8_t* mac); + /** SHE key management functions */ /** diff --git a/wolfhsm/wh_message.h b/wolfhsm/wh_message.h index c562c877f..843457c16 100644 --- a/wolfhsm/wh_message.h +++ b/wolfhsm/wh_message.h @@ -93,6 +93,7 @@ enum WH_SHE_ENUM { WH_SHE_DEC_CBC, WH_SHE_GEN_MAC, WH_SHE_VERIFY_MAC, + WH_SHE_GET_ID, }; /* counter actions */ diff --git a/wolfhsm/wh_message_she.h b/wolfhsm/wh_message_she.h index 0e28ee78e..f8dc0ad5d 100644 --- a/wolfhsm/wh_message_she.h +++ b/wolfhsm/wh_message_she.h @@ -389,6 +389,29 @@ int wh_MessageShe_TranslateVerifyMacResponse( uint16_t magic, const whMessageShe_VerifyMacResponse* src, whMessageShe_VerifyMacResponse* dest); +/* Get ID Request */ +typedef struct { + uint8_t challenge[WH_SHE_KEY_SZ]; +} whMessageShe_GetIdRequest; + +/* Get ID Response */ +typedef struct { + int32_t rc; + uint8_t uid[WH_SHE_UID_SZ]; + uint8_t sreg; + uint8_t mac[WH_SHE_KEY_SZ]; + uint8_t WH_PAD[4]; +} whMessageShe_GetIdResponse; + +/* Get ID translation functions */ +int wh_MessageShe_TranslateGetIdRequest(uint16_t magic, + const whMessageShe_GetIdRequest* src, + whMessageShe_GetIdRequest* dest); + +int wh_MessageShe_TranslateGetIdResponse( + uint16_t magic, const whMessageShe_GetIdResponse* src, + whMessageShe_GetIdResponse* dest); + #endif /* WOLFHSM_CFG_SHE_EXTENSION */ #endif /* !WOLFHSM_WH_MESSAGE_SHE_H_ */