diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 3495d3667..23f22ceac 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -672,10 +672,11 @@ 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. In addition to the spec commands, wolfHSM exposes two non-standard helpers that fill gaps left by the spec's assumption of dedicated hardware: -- `wh_Client_SheSetUid`: explicitly programs the 15-byte ECU UID that the key update protocol binds against. The AUTOSAR spec assumes this value is hardware-fused; wolfHSM needs a software path to install it, and rejects most SHE operations until it has been set. +- `wh_Client_SheSetUid`: explicitly programs the 15-byte ECU UID that the key update protocol binds against. The AUTOSAR spec assumes this value is hardware-fused; wolfHSM needs a software path to install it, and rejects most SHE operations until it has been set. Where the UID really does live in hardware or in NVM, the server can be pointed at it instead with [UID storage callbacks](#she-uid-storage), in which case `CMD_SET_UID` returns `WH_SHE_ERC_WRITE_PROTECTED` on a read-only store. - `wh_Client_ShePreProgramKey`: writes a key directly into a SHE NVM slot, bypassing the encrypted M1–M5 protocol. This exists to support initial provisioning on a blank device — once a `MASTER_ECU_KEY` exists, all subsequent updates can go through the spec-compliant protocol. All SHE commands return one of the spec's `WH_SHE_ERC_*` error codes (`SEQUENCE_ERROR`, `KEY_NOT_AVAILABLE`, `WRITE_PROTECTED`, `KEY_UPDATE_ERROR`, etc.) alongside the wolfHSM transport return code, so applications can distinguish protocol-level failures from communication failures. @@ -698,6 +699,30 @@ The SHE spec also requires every key to carry a 28-bit monotonic update counter `RAM_KEY` is the one exception to NVM-backed storage. The spec defines it as volatile, so the server caches the loaded key in its [key cache](#key-cache-key-ids-and-nvm-backing-store) but never calls into the NVM layer for it; eviction or reset clears it. All other slots, including `PRNG_SEED`, persist. +### SHE UID Storage + +A pair of optional callbacks determines where the 15-byte ECU UID lives. Install them and the server reads it from the integrator's store (fuses, OTP, NVM); leave them unset and it stays in the caller-owned `whServerSheContext`, re-provisioned with `CMD_SET_UID` after every reset. + +```c +typedef int (*whServerSheGetUidCb)(whServerContext* server, void* ctx, + uint8_t* outUid); +typedef int (*whServerSheSetUidCb)(whServerContext* server, void* ctx, + const uint8_t* uid); +``` + +The getter fills `WH_SHE_UID_SZ` bytes and returns `0`, `WH_ERROR_NOTFOUND` if no UID has been provisioned, or any other wolfHSM error to report a backend failure. The setter persists a UID that arrived over the wire via `CMD_SET_UID`; leaving it `NULL` marks the UID read-only, so provisioning attempts are answered with `WH_SHE_ERC_WRITE_PROTECTED` rather than being silently dropped. + +Callbacks are supplied at initialization through the optional `whServerConfig.sheConfig` field, or registered later with `wh_Server_SheSetUidCb`: + +```c +whServerSheConfig sheConfig = { + .getUidCb = myGetUid, + .setUidCb = NULL, /* fused UID, read-only */ + .uidCtx = &myPlatform, +}; +whServerConfig serverConfig = { /* ... */ .she = she, .sheConfig = &sheConfig }; +``` + ### Global SHE Keys By default every SHE keyId carries the connecting client's ID in its USER field, so each client gets its own private set of sixteen SHE slots. That is convenient when clients are mutually distrusting, but it does not match the AUTOSAR model, where SHE is a single physical device with one fixed set of slots shared by every host core. Defining `WOLFHSM_CFG_SHE_GLOBAL_KEYS` (which requires both `WOLFHSM_CFG_GLOBAL_KEYS` and `WOLFHSM_CFG_SHE_EXTENSION`) switches to that model: **all** SHE slots — `SECRET_KEY` through `RAM_KEY` and `PRNG_SEED` — are built in the [global-keys](#global-keys) namespace (USER = `WH_KEYUSER_GLOBAL`, 0) and are shared by every client. @@ -736,6 +761,8 @@ SHE secure boot is implemented as a three-phase state machine that the client dr While the state machine is in any state other than `SUCCESS`, the SHE handler refuses every non-boot command except `CMD_GET_STATUS` and `CMD_SET_UID`, returning `WH_SHE_ERC_SEQUENCE_ERROR`. This is what allows the SHE module to gate cryptographic services on a successful boot measurement: once boot has succeeded, the rest of the SHE command set unlocks; on a boot failure the keys remain inaccessible and only status queries are honored. +The same stateful gate also enforces that a UID has been provisioned. When [UID storage callbacks](#she-uid-storage) are installed, that check queries the integrator's store, and a store that reports a failure causes every command except `CMD_GET_STATUS` to return `WH_SHE_ERC_MEMORY_FAILURE`. + The bootloader bytes are supplied through the standard message buffer in chunks of up to `WOLFHSM_CFG_COMM_DATA_LEN`. For large bootloaders this is the natural place to opt into [DMA](#dma-support) — a future variant of the secure boot handler could read the bootloader image directly out of flash using the DMA address-translation path — but the current implementation is purely buffer-based. ### Deterministic PRNG diff --git a/src/wh_client_she.c b/src/wh_client_she.c index 48fd6c5e8..9daaad7a8 100644 --- a/src/wh_client_she.c +++ b/src/wh_client_she.c @@ -293,6 +293,70 @@ 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, + WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)resp); + if (ret == WH_ERROR_OK && dataSz < sizeof(*resp)) { + ret = WH_ERROR_ABORTED; + } + 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.c b/src/wh_server.c index 3a4c11553..134f36dda 100644 --- a/src/wh_server.c +++ b/src/wh_server.c @@ -91,6 +91,18 @@ int wh_Server_Init(whServerContext* server, whServerConfig* config) server->devId = config->devId; #ifdef WOLFHSM_CFG_SHE_EXTENSION server->she = config->she; + if (server->she != NULL) { + if (config->sheConfig != NULL) { + server->she->getUidCb = config->sheConfig->getUidCb; + server->she->setUidCb = config->sheConfig->setUidCb; + server->she->uidCtx = config->sheConfig->uidCtx; + } + else { + server->she->getUidCb = NULL; + server->she->setUidCb = NULL; + server->she->uidCtx = NULL; + } + } #endif #endif diff --git a/src/wh_server_she.c b/src/wh_server_she.c index 6a5e99057..06331f00e 100644 --- a/src/wh_server_she.c +++ b/src/wh_server_she.c @@ -125,7 +125,14 @@ 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 _GetUid(whServerContext* server, uint8_t* outUid); +static int _StoreUid(whServerContext* server, const uint8_t* uid); +static int _UidIsProvisioned(whServerContext* server); static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, uint16_t action, uint16_t req_size, const void* req_packet, @@ -159,6 +166,66 @@ static int _TranslateSheReturnCode(int ret) return ret; } +/* Reads the UID into outUid. Returns WH_ERROR_NOTFOUND if unprovisioned. */ +static int _GetUid(whServerContext* server, uint8_t* outUid) +{ + whServerSheContext* she = server->she; + int ret; + + if (she->getUidCb != NULL) { + ret = she->getUidCb(server, she->uidCtx, outUid); + if (ret != 0) { + memset(outUid, 0, WH_SHE_UID_SZ); + } + return ret; + } + + if (she->uidSet == 0) { + memset(outUid, 0, WH_SHE_UID_SZ); + return WH_ERROR_NOTFOUND; + } + memcpy(outUid, she->uid, WH_SHE_UID_SZ); + return 0; +} + +/* Stores a UID. Returns WH_ERROR_NOTIMPL if the UID is read-only. */ +static int _StoreUid(whServerContext* server, const uint8_t* uid) +{ + whServerSheContext* she = server->she; + + if (she->getUidCb != NULL) { + if (she->setUidCb == NULL) { + return WH_ERROR_NOTIMPL; + } + return she->setUidCb(server, she->uidCtx, uid); + } + + memcpy(she->uid, uid, WH_SHE_UID_SZ); + she->uidSet = 1; + return 0; +} + +/* Returns 1 if a UID is provisioned, 0 if not, or a negative error. */ +static int _UidIsProvisioned(whServerContext* server) +{ + uint8_t uid[WH_SHE_UID_SZ]; + int ret; + + if (server->she->getUidCb == NULL) { + return (server->she->uidSet != 0) ? 1 : 0; + } + + ret = _GetUid(server, uid); + memset(uid, 0, sizeof(uid)); + if (ret == 0) { + return 1; + } + if (ret == WH_ERROR_NOTFOUND) { + return 0; + } + return ret; +} + /* kdf function based on the Miyaguchi-Preneel one-way compression function */ static int _AesMp16(whServerContext* server, uint8_t* in, word32 inSz, uint8_t* out) @@ -207,13 +274,24 @@ static int _SetUid(whServerContext* server, uint16_t magic, uint16_t req_size, magic, (whMessageShe_SetUidRequest*)req_packet, &req); } - if ((ret == 0) && (server->she->uidSet == 1)) { - ret = WH_SHE_ERC_SEQUENCE_ERROR; + if (ret == 0) { + int provisioned = _UidIsProvisioned(server); + if (provisioned < 0) { + ret = WH_SHE_ERC_MEMORY_FAILURE; + } + else if (provisioned != 0) { + ret = WH_SHE_ERC_SEQUENCE_ERROR; + } } if (ret == WH_SHE_ERC_NO_ERROR) { - memcpy(server->she->uid, req.uid, sizeof(req.uid)); - server->she->uidSet = 1; + ret = _StoreUid(server, req.uid); + if (ret == WH_ERROR_NOTIMPL) { + ret = WH_SHE_ERC_WRITE_PROTECTED; + } + else if (ret != 0) { + ret = WH_SHE_ERC_MEMORY_FAILURE; + } } resp.rc = _TranslateSheReturnCode(ret); @@ -430,6 +508,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) @@ -444,26 +549,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); @@ -484,6 +570,7 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, uint8_t kdfInput[WH_SHE_KEY_SZ * 2]; uint8_t cmacOutput[AES_BLOCK_SIZE]; uint8_t tmpKey[WH_SHE_KEY_SZ]; + uint8_t uid[WH_SHE_UID_SZ]; whNvmMetadata meta[1] = {0}; uint32_t she_meta_count = 0; uint32_t she_meta_flags = 0; @@ -597,6 +684,13 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, } } } + /* fetch the UID once for the M1 comparison and the M4 response */ + if (ret == 0) { + ret = _GetUid(server, uid); + if (ret != 0) { + ret = WH_SHE_ERC_MEMORY_FAILURE; + } + } /* check UID == 0 */ if (ret == 0 && wh_Utils_memeqzero(req.messageOne, WH_SHE_UID_SZ) == 1) { /* check wildcard */ @@ -605,8 +699,8 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, } } /* compare to UID */ - else if (ret == 0 && wh_Utils_ConstantCompare(req.messageOne, server->she->uid, - sizeof(server->she->uid)) != 0) { + else if (ret == 0 && wh_Utils_ConstantCompare(req.messageOne, uid, + WH_SHE_UID_SZ) != 0) { ret = WH_SHE_ERC_KEY_UPDATE_ERROR; } /* verify msg_counter_val is greater than stored value */ @@ -688,7 +782,7 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, counter_buffer[3] |= 0x08; /* First copy UID into messageFour */ - memcpy(resp.messageFour, server->she->uid, sizeof(server->she->uid)); + memcpy(resp.messageFour, uid, WH_SHE_UID_SZ); /* Set ID and AuthID in last byte */ resp.messageFour[15] = ((_PopId(req.messageOne) << 4) | _PopAuthId(req.messageOne)); @@ -786,6 +880,7 @@ static int _ExportRamKey(whServerContext* server, uint16_t magic, uint8_t kdfInput[WH_SHE_KEY_SZ * 2]; uint8_t cmacOutput[AES_BLOCK_SIZE]; uint8_t tmpKey[WH_SHE_KEY_SZ]; + uint8_t uid[WH_SHE_UID_SZ]; whNvmMetadata meta[1]; uint32_t counter_val; whMessageShe_ExportRamKeyResponse resp = {0}; @@ -809,9 +904,16 @@ static int _ExportRamKey(whServerContext* server, uint16_t magic, ret = WH_SHE_ERC_KEY_NOT_AVAILABLE; } } + /* fetch the UID once for the M1 and M4 responses */ + if (ret == 0) { + ret = _GetUid(server, uid); + if (ret != 0) { + ret = WH_SHE_ERC_MEMORY_FAILURE; + } + } if (ret == 0) { /* set UID, key id and authId */ - memcpy(resp.messageOne, server->she->uid, sizeof(server->she->uid)); + memcpy(resp.messageOne, uid, WH_SHE_UID_SZ); resp.messageOne[15] = ((WH_SHE_RAM_KEY_ID << 4) | (WH_SHE_SECRET_KEY_ID)); /* add WH_SHE_KEY_UPDATE_ENC_C to the input */ @@ -908,7 +1010,7 @@ static int _ExportRamKey(whServerContext* server, uint16_t magic, wc_AesFree(server->she->sheAes); if (ret == 0) { /* set UID, key id and authId */ - memcpy(resp.messageFour, server->she->uid, sizeof(server->she->uid)); + memcpy(resp.messageFour, uid, WH_SHE_UID_SZ); resp.messageFour[15] = ((WH_SHE_RAM_KEY_ID << 4) | (WH_SHE_SECRET_KEY_ID)); /* add WH_SHE_KEY_UPDATE_MAC_C to the input */ @@ -1638,6 +1740,80 @@ 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]; + uint8_t uid[WH_SHE_UID_SZ]; + 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) { + ret = _GetUid(server, uid); + if (ret != 0) { + ret = WH_SHE_ERC_MEMORY_FAILURE; + } + } + + 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, uid, WH_SHE_UID_SZ); + macIn[WH_SHE_KEY_SZ + WH_SHE_UID_SZ] = sreg; + + keySz = WH_SHE_KEY_SZ; + ret = wh_Server_KeystoreReadKey( + server, + WH_SHE_MAKE_KEYID(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, uid, WH_SHE_UID_SZ); + 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 @@ -1652,25 +1828,43 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, (void)req_size; if (action == WH_SHE_GET_STATUS) { - /* Status read is always permitted per AUTOSAR spec, even before boot - *or UID setup. */ + /* Status read is always permitted per AUTOSAR spec, even before boot + * or UID setup. The UID store is deliberately not consulted so a + * failing backend still leaves status readable. */ } - else if (action == WH_SHE_SET_UID) { - /* Provisioning is one-shot: reject once the UID is already set. */ - if (server->she->uidSet != 0) { + else { + int provisioned = _UidIsProvisioned(server); + + if (provisioned < 0) { + /* Fail closed on a UID store error, distinct from a sequence + * error. */ + ret = WH_SHE_ERC_MEMORY_FAILURE; + } + else if (action == WH_SHE_SET_UID) { + /* Provisioning is one-shot: reject once the UID is already set. */ + if (provisioned != 0) { + ret = WH_SHE_ERC_SEQUENCE_ERROR; + } + else if ((server->she->getUidCb != NULL) && + (server->she->setUidCb == NULL)) { + /* A read-only UID store can never accept provisioning. */ + ret = WH_SHE_ERC_WRITE_PROTECTED; + } + } + else if (provisioned == 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 && + action != WH_SHE_GET_ID && + server->she->sbState != WH_SHE_SB_SUCCESS) { + /* 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 check above. */ 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) { @@ -1678,7 +1872,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, switch (action) { case WH_SHE_SET_UID: { whMessageShe_SetUidResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateSetUidResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1686,7 +1880,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_SECURE_BOOT_INIT: { whMessageShe_SecureBootInitResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateSecureBootInitResponse( magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1694,7 +1888,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_SECURE_BOOT_UPDATE: { whMessageShe_SecureBootUpdateResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateSecureBootUpdateResponse( magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1702,7 +1896,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_SECURE_BOOT_FINISH: { whMessageShe_SecureBootFinishResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateSecureBootFinishResponse( magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1719,7 +1913,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_LOAD_KEY: { whMessageShe_LoadKeyResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateLoadKeyResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1727,7 +1921,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_LOAD_PLAIN_KEY: { whMessageShe_LoadPlainKeyResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateLoadPlainKeyResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1735,7 +1929,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_EXPORT_RAM_KEY: { whMessageShe_ExportRamKeyResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateExportRamKeyResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1743,7 +1937,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_INIT_RND: { whMessageShe_InitRngResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateInitRngResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1751,7 +1945,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_RND: { whMessageShe_RndResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateRndResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1759,7 +1953,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_EXTEND_SEED: { whMessageShe_ExtendSeedResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateExtendSeedResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1767,7 +1961,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_ENC_ECB: { whMessageShe_EncEcbResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateEncEcbResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1775,7 +1969,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_ENC_CBC: { whMessageShe_EncCbcResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateEncCbcResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1783,7 +1977,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_DEC_ECB: { whMessageShe_DecEcbResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateDecEcbResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1791,7 +1985,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_DEC_CBC: { whMessageShe_DecCbcResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateDecCbcResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1799,7 +1993,7 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_GEN_MAC: { whMessageShe_GenMacResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); (void)wh_MessageShe_TranslateGenMacResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); @@ -1807,13 +2001,21 @@ static int _ReportInvalidSheState(whServerContext* server, uint16_t magic, } case WH_SHE_VERIFY_MAC: { whMessageShe_VerifyMacResponse resp; - resp.rc = WH_SHE_ERC_SEQUENCE_ERROR; + resp.rc = _TranslateSheReturnCode(ret); resp.status = 1; /* Verification failed */ (void)wh_MessageShe_TranslateVerifyMacResponse(magic, &resp, resp_packet); *out_resp_size = sizeof(resp); break; } + case WH_SHE_GET_ID: { + whMessageShe_GetIdResponse resp = {0}; + resp.rc = _TranslateSheReturnCode(ret); + (void)wh_MessageShe_TranslateGetIdResponse(magic, &resp, + resp_packet); + *out_resp_size = sizeof(resp); + break; + } } } @@ -1964,6 +2166,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; @@ -1985,5 +2195,21 @@ int wh_Server_HandleSheRequest(whServerContext* server, uint16_t magic, return (*out_resp_size > 0) ? 0 : ret; } +int wh_Server_SheSetUidCb(whServerContext* server, whServerSheGetUidCb getCb, + whServerSheSetUidCb setCb, void* ctx) +{ + /* No NULL check on the callbacks, since both are optional and always NULL + * checked before they are called */ + if ((server == NULL) || (server->she == NULL)) { + return WH_ERROR_BADARGS; + } + + server->she->getUidCb = getCb; + server->she->setUidCb = setCb; + server->she->uidCtx = ctx; + + return WH_ERROR_OK; +} + #endif /* WOLFHSM_CFG_SHE_EXTENSION */ #endif /* !WOLFHSM_CFG_NO_CRYPTO && WOLFHSM_CFG_ENABLE_SERVER */ diff --git a/test-refactor/client-server/wh_test_she.c b/test-refactor/client-server/wh_test_she.c index 06fb7a15c..93ad3111b 100644 --- a/test-refactor/client-server/wh_test_she.c +++ b/test-refactor/client-server/wh_test_she.c @@ -147,6 +147,13 @@ int whTest_She(whClientContext* client) uint8_t oversizeKey[WH_SHE_KEY_SZ * 2] = {0}; uint8_t oversizeLabel[WH_NVM_LABEL_LEN] = {0}; int32_t sheMetaRc = 0; + 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; const uint32_t SHE_SIZE_CHECK_KEY_ID = 7; @@ -322,6 +329,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 403c93d1d..699d503ca 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -207,6 +207,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/misc/wh_test_she_uid_cb.c b/test-refactor/misc/wh_test_she_uid_cb.c new file mode 100644 index 000000000..3abda8738 --- /dev/null +++ b/test-refactor/misc/wh_test_she_uid_cb.c @@ -0,0 +1,445 @@ +/* + * 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/misc/wh_test_she_uid_cb.c + * + * SHE UID storage callback tests. Lives in the misc group because it needs to + * build its own whServerConfig to exercise the sheConfig init path, which the + * port's shared server cannot provide. The server is driven directly through + * wh_Server_HandleSheRequest, so no client or transport pumping is needed. + */ + +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_SHE_EXTENSION) && !defined(WOLFHSM_CFG_NO_CRYPTO) && \ + defined(WOLFHSM_CFG_ENABLE_SERVER) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/error-crypt.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_utils.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_message.h" +#include "wolfhsm/wh_message_she.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_server_she.h" +#include "wolfhsm/wh_she_common.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +/* Value 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_SUCCESS 3 + +enum { + BUFFER_SIZE = sizeof(whTransportMemCsr) + sizeof(whCommHeader) + + WOLFHSM_CFG_COMM_DATA_LEN, +}; + +/* UID reported by the read-only backing store */ +static const uint8_t s_fusedUid[WH_SHE_UID_SZ] = { + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE}; + +/* UID a client provisions over the wire */ +static const uint8_t s_wireUid[WH_SHE_UID_SZ] = {0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xAA, + 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}; + +/* Integrator-supplied UID store, standing in for NVM, fuses or static RAM */ +typedef struct { + uint8_t uid[WH_SHE_UID_SZ]; + int provisioned; + int readOnly; + int getErr; /* forced error from the get callback */ + int getCount; + int setCount; +} TestUidStore; + +typedef struct { + whServerContext server[1]; + whServerCryptoContext crypto[1]; + whServerSheContext she[1]; + whServerSheConfig sheConfig[1]; + whServerConfig s_conf[1]; + /* Transport, server side only */ + uint8_t reqBuf[BUFFER_SIZE]; + uint8_t respBuf[BUFFER_SIZE]; + whTransportMemConfig tmcf[1]; + whTransportServerCb tscb[1]; + whTransportMemServerContext tmsc[1]; + whCommServerConfig cs_conf[1]; +} TestCtx; + +/* Static to keep the misc group's stack footprint small */ +static TestCtx _testCtx; +static TestUidStore _uidStore; + +static int _TestGetUid(struct whServerContext_t* server, void* ctx, + uint8_t* outUid) +{ + TestUidStore* store = (TestUidStore*)ctx; + (void)server; + + store->getCount++; + + if (store->getErr != 0) { + return store->getErr; + } + if (store->provisioned == 0) { + return WH_ERROR_NOTFOUND; + } + memcpy(outUid, store->uid, WH_SHE_UID_SZ); + return WH_ERROR_OK; +} + +static int _TestSetUid(struct whServerContext_t* server, void* ctx, + const uint8_t* uid) +{ + TestUidStore* store = (TestUidStore*)ctx; + (void)server; + + store->setCount++; + + memcpy(store->uid, uid, WH_SHE_UID_SZ); + store->provisioned = 1; + return WH_ERROR_OK; +} + +/* Send one SHE action through the server and return its response rc. */ +static int32_t _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); +} + +/* Bring up a server whose SHE UID is backed by _uidStore. When useConfig is + * set the callbacks arrive via whServerConfig.sheConfig, otherwise the server + * starts with in-context storage and the caller registers them later. */ +static int _SetupServer(TestCtx* t, int useConfig, int readOnly) +{ + memset(t, 0, sizeof(*t)); + + t->tmcf[0] = (whTransportMemConfig){ + .req = (whTransportMemCsr*)t->reqBuf, + .req_size = sizeof(t->reqBuf), + .resp = (whTransportMemCsr*)t->respBuf, + .resp_size = sizeof(t->respBuf), + }; + t->tscb[0] = (whTransportServerCb)WH_TRANSPORT_MEM_SERVER_CB; + t->cs_conf[0] = (whCommServerConfig){ + .transport_cb = t->tscb, + .transport_context = (void*)t->tmsc, + .transport_config = (void*)t->tmcf, + .server_id = 124, + }; + + t->sheConfig[0] = (whServerSheConfig){ + .getUidCb = _TestGetUid, + .setUidCb = (readOnly != 0) ? NULL : _TestSetUid, + .uidCtx = &_uidStore, + }; + + t->s_conf[0] = (whServerConfig){ + .comm_config = t->cs_conf, + .nvm = NULL, + .crypto = t->crypto, + .she = t->she, + .sheConfig = (useConfig != 0) ? t->sheConfig : NULL, + .devId = INVALID_DEVID, + }; + + WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(t->crypto->rng, NULL, INVALID_DEVID)); + WH_TEST_RETURN_ON_FAIL(wh_Server_Init(t->server, t->s_conf)); + + return WH_ERROR_OK; +} + +static void _CleanupServer(TestCtx* t) +{ + (void)wh_Server_Cleanup(t->server); + (void)wc_FreeRng(t->crypto->rng); + (void)wolfCrypt_Cleanup(); +} + +/* Run GET_ID and check that the response carries the expected UID. */ +static int _CheckGetIdUid(whServerContext* server, const uint8_t* expectUid, + uint8_t* req_packet, uint8_t* resp_packet) +{ + whMessageShe_GetIdRequest* req = (whMessageShe_GetIdRequest*)req_packet; + whMessageShe_GetIdResponse* resp = (whMessageShe_GetIdResponse*)resp_packet; + int32_t rc; + + memset(req, 0, sizeof(*req)); + memset(req->challenge, 0xA5, sizeof(req->challenge)); + + rc = _SheActionRc(server, WH_SHE_GET_ID, req_packet, sizeof(*req), + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + WH_TEST_ASSERT_RETURN(memcmp(resp->uid, expectUid, WH_SHE_UID_SZ) == 0); + + return WH_ERROR_OK; +} + +/* A read-only store rejects provisioning and serves its UID to GET_ID. */ +static int _TestReadOnlyUid(TestCtx* t, uint8_t* req_packet, + uint8_t* resp_packet) +{ + whServerContext* server = t->server; + int32_t rc; + int getCount; + + /* The UID is already provisioned, so SET_UID hits the one-shot rule. */ + { + whMessageShe_SetUidRequest* uidReq = + (whMessageShe_SetUidRequest*)req_packet; + memset(uidReq, 0, sizeof(*uidReq)); + memcpy(uidReq->uid, s_wireUid, WH_SHE_UID_SZ); + rc = _SheActionRc(server, WH_SHE_SET_UID, req_packet, sizeof(*uidReq), + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + } + WH_TEST_ASSERT_RETURN(_uidStore.setCount == 0); + WH_TEST_ASSERT_RETURN( + memcmp(_uidStore.uid, s_fusedUid, WH_SHE_UID_SZ) == 0); + + /* GET_STATUS is answered without consulting the UID store. */ + getCount = _uidStore.getCount; + rc = _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(_uidStore.getCount == getCount); + + /* GET_ID reports the store's UID and reads it back from the store. */ + server->she->sbState = TEST_SHE_SB_STATE_SUCCESS; + getCount = _uidStore.getCount; + WH_TEST_RETURN_ON_FAIL( + _CheckGetIdUid(server, s_fusedUid, req_packet, resp_packet)); + WH_TEST_ASSERT_RETURN(_uidStore.getCount > getCount); + + /* Nothing was cached in the SHE context. */ + WH_TEST_ASSERT_RETURN(server->she->uidSet == 0); + WH_TEST_ASSERT_RETURN( + wh_Utils_memeqzero(server->she->uid, WH_SHE_UID_SZ) == 1); + + return WH_ERROR_OK; +} + +/* A writable store accepts one provisioning round and then serves the UID. */ +static int _TestWritableUid(TestCtx* t, uint8_t* req_packet, + uint8_t* resp_packet) +{ + whServerContext* server = t->server; + whMessageShe_SetUidRequest* uidReq = + (whMessageShe_SetUidRequest*)req_packet; + int32_t rc; + + /* Before provisioning, protected commands are refused. */ + memset(req_packet, 0, WOLFHSM_CFG_COMM_DATA_LEN); + rc = _SheActionRc(server, WH_SHE_LOAD_PLAIN_KEY, req_packet, + sizeof(whMessageShe_LoadPlainKeyRequest), resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + rc = _SheActionRc(server, WH_SHE_GET_ID, req_packet, + sizeof(whMessageShe_GetIdRequest), resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + + /* SET_UID reaches the store. */ + memset(uidReq, 0, sizeof(*uidReq)); + memcpy(uidReq->uid, s_wireUid, WH_SHE_UID_SZ); + rc = _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(_uidStore.setCount == 1); + WH_TEST_ASSERT_RETURN(_uidStore.provisioned == 1); + WH_TEST_ASSERT_RETURN(memcmp(_uidStore.uid, s_wireUid, WH_SHE_UID_SZ) == 0); + + /* Provisioning stays one-shot when it is the store that remembers. */ + memset(uidReq, 0, sizeof(*uidReq)); + memcpy(uidReq->uid, s_fusedUid, WH_SHE_UID_SZ); + rc = _SheActionRc(server, WH_SHE_SET_UID, req_packet, sizeof(*uidReq), + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_SEQUENCE_ERROR); + WH_TEST_ASSERT_RETURN(_uidStore.setCount == 1); + WH_TEST_ASSERT_RETURN(memcmp(_uidStore.uid, s_wireUid, WH_SHE_UID_SZ) == 0); + + /* GET_ID echoes the provisioned UID, still with nothing cached. */ + server->she->sbState = TEST_SHE_SB_STATE_SUCCESS; + WH_TEST_RETURN_ON_FAIL( + _CheckGetIdUid(server, s_wireUid, req_packet, resp_packet)); + WH_TEST_ASSERT_RETURN(server->she->uidSet == 0); + WH_TEST_ASSERT_RETURN( + wh_Utils_memeqzero(server->she->uid, WH_SHE_UID_SZ) == 1); + + return WH_ERROR_OK; +} + +/* A read-only store that has nothing to serve can never be provisioned. */ +static int _TestReadOnlyUnprovisioned(TestCtx* t, uint8_t* req_packet, + uint8_t* resp_packet) +{ + whMessageShe_SetUidRequest* uidReq = + (whMessageShe_SetUidRequest*)req_packet; + int32_t rc; + + memset(uidReq, 0, sizeof(*uidReq)); + memcpy(uidReq->uid, s_wireUid, WH_SHE_UID_SZ); + rc = _SheActionRc(t->server, WH_SHE_SET_UID, req_packet, sizeof(*uidReq), + resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_WRITE_PROTECTED); + WH_TEST_ASSERT_RETURN(_uidStore.provisioned == 0); + + return WH_ERROR_OK; +} + +/* A failing store fails every command closed, except GET_STATUS. */ +static int _TestFailingUid(TestCtx* t, uint8_t* req_packet, + uint8_t* resp_packet) +{ + whServerContext* server = t->server; + int32_t rc; + uint32_t i; + + const struct { + uint16_t action; + uint16_t req_size; + } actions[] = { + {WH_SHE_SET_UID, sizeof(whMessageShe_SetUidRequest)}, + {WH_SHE_GET_ID, sizeof(whMessageShe_GetIdRequest)}, + {WH_SHE_LOAD_PLAIN_KEY, sizeof(whMessageShe_LoadPlainKeyRequest)}, + {WH_SHE_INIT_RND, 0}, + {WH_SHE_ENC_ECB, sizeof(whMessageShe_EncEcbRequest)}, + }; + const uint32_t actionCount = sizeof(actions) / sizeof(actions[0]); + + memset(req_packet, 0, WOLFHSM_CFG_COMM_DATA_LEN); + server->she->sbState = TEST_SHE_SB_STATE_SUCCESS; + _uidStore.getErr = WH_ERROR_ABORTED; + + for (i = 0; i < actionCount; i++) { + rc = _SheActionRc(server, actions[i].action, req_packet, + actions[i].req_size, resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_MEMORY_FAILURE); + } + + /* Status stays readable even with the store broken. */ + rc = _SheActionRc(server, WH_SHE_GET_STATUS, req_packet, 0, resp_packet); + WH_TEST_ASSERT_RETURN(rc == WH_SHE_ERC_NO_ERROR); + + _uidStore.getErr = 0; + + return WH_ERROR_OK; +} + +int whTest_SheUidCb(void* ctx) +{ + TestCtx* t = &_testCtx; + /* Buffers for request and response packets */ + static uint8_t req_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + static uint8_t resp_packet[WOLFHSM_CFG_COMM_DATA_LEN]; + + (void)ctx; + + /* Read-only store, callbacks installed through whServerConfig.sheConfig */ + memset(&_uidStore, 0, sizeof(_uidStore)); + memcpy(_uidStore.uid, s_fusedUid, WH_SHE_UID_SZ); + _uidStore.provisioned = 1; + WH_TEST_RETURN_ON_FAIL(_SetupServer(t, 1, 1)); + WH_TEST_RETURN_ON_FAIL(_TestReadOnlyUid(t, req_packet, resp_packet)); + _CleanupServer(t); + + /* Read-only store with no UID yet: provisioning is refused outright */ + memset(&_uidStore, 0, sizeof(_uidStore)); + WH_TEST_RETURN_ON_FAIL(_SetupServer(t, 1, 1)); + WH_TEST_RETURN_ON_FAIL( + _TestReadOnlyUnprovisioned(t, req_packet, resp_packet)); + _CleanupServer(t); + + /* Writable store, starting unprovisioned */ + memset(&_uidStore, 0, sizeof(_uidStore)); + WH_TEST_RETURN_ON_FAIL(_SetupServer(t, 1, 0)); + WH_TEST_RETURN_ON_FAIL(_TestWritableUid(t, req_packet, resp_packet)); + _CleanupServer(t); + + /* Failing store */ + memset(&_uidStore, 0, sizeof(_uidStore)); + _uidStore.provisioned = 1; + WH_TEST_RETURN_ON_FAIL(_SetupServer(t, 1, 0)); + WH_TEST_RETURN_ON_FAIL(_TestFailingUid(t, req_packet, resp_packet)); + _CleanupServer(t); + + /* Late registration reaches the same behavior as the config path */ + memset(&_uidStore, 0, sizeof(_uidStore)); + memcpy(_uidStore.uid, s_fusedUid, WH_SHE_UID_SZ); + _uidStore.provisioned = 1; + WH_TEST_RETURN_ON_FAIL(_SetupServer(t, 0, 1)); + WH_TEST_ASSERT_RETURN(t->server->she->getUidCb == NULL); + WH_TEST_RETURN_ON_FAIL( + wh_Server_SheSetUidCb(t->server, _TestGetUid, NULL, &_uidStore)); + WH_TEST_RETURN_ON_FAIL(_TestReadOnlyUid(t, req_packet, resp_packet)); + + /* Clearing the callbacks restores in-context storage */ + WH_TEST_RETURN_ON_FAIL(wh_Server_SheSetUidCb(t->server, NULL, NULL, NULL)); + { + whMessageShe_SetUidRequest* uidReq = + (whMessageShe_SetUidRequest*)req_packet; + memset(uidReq, 0, sizeof(*uidReq)); + memcpy(uidReq->uid, s_wireUid, WH_SHE_UID_SZ); + WH_TEST_ASSERT_RETURN(_SheActionRc(t->server, WH_SHE_SET_UID, + req_packet, sizeof(*uidReq), + resp_packet) == + WH_SHE_ERC_NO_ERROR); + } + WH_TEST_ASSERT_RETURN(t->server->she->uidSet == 1); + WH_TEST_ASSERT_RETURN( + memcmp(t->server->she->uid, s_wireUid, WH_SHE_UID_SZ) == 0); + WH_TEST_ASSERT_RETURN(_uidStore.setCount == 0); + WH_TEST_RETURN_ON_FAIL( + _CheckGetIdUid(t->server, s_wireUid, req_packet, resp_packet)); + + WH_TEST_ASSERT_RETURN(wh_Server_SheSetUidCb(NULL, _TestGetUid, NULL, + &_uidStore) == + WH_ERROR_BADARGS); + _CleanupServer(t); + + WH_TEST_PRINT("SHE UID callback test SUCCESS\n"); + + return WH_ERROR_OK; +} + +#endif /* WOLFHSM_CFG_SHE_EXTENSION && !WOLFHSM_CFG_NO_CRYPTO && + * WOLFHSM_CFG_ENABLE_SERVER */ diff --git a/test-refactor/server/wh_test_she_server.c b/test-refactor/server/wh_test_she_server.c index 8ef1aa201..58277f7a7 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-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 02e121ebe..08558eda0 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -89,6 +89,7 @@ WH_TEST_DECL(whTest_SheMasterEcuKeyFallback); WH_TEST_DECL(whTest_SheNoNvm); WH_TEST_DECL(whTest_SheReqSizeChecking); WH_TEST_DECL(whTest_SheStateGate); +WH_TEST_DECL(whTest_SheUidCb); WH_TEST_DECL(whTest_Echo); WH_TEST_DECL(whTest_NvmDma); WH_TEST_DECL(whTest_NvmOps); @@ -117,6 +118,7 @@ const whTestCase whTestsMisc[] = { { "whTest_Log", whTest_Log }, { "whTest_SheKeywrapInterop", whTest_SheKeywrapInterop }, { "whTest_SheNoNvm", whTest_SheNoNvm }, + { "whTest_SheUidCb", whTest_SheUidCb }, }; const size_t whTestsMiscCount = ARRAY_SIZE(whTestsMisc); diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index bf29822a6..f716566b6 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -213,6 +213,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 c04423d2c..5ea176a4e 100644 --- a/test/wh_test_she.c +++ b/test/wh_test_she.c @@ -173,6 +173,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; @@ -329,6 +336,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 @@ -2033,6 +2071,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); @@ -2229,6 +2287,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) && \ @@ -2506,6 +2731,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 7528c2c50..b587f37d3 100644 --- a/wolfhsm/wh_client_she.h +++ b/wolfhsm/wh_client_she.h @@ -205,6 +205,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 1c343aa57..1c9f7cba3 100644 --- a/wolfhsm/wh_message.h +++ b/wolfhsm/wh_message.h @@ -95,6 +95,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_ */ diff --git a/wolfhsm/wh_server.h b/wolfhsm/wh_server.h index 4611d4440..a3da0ef3a 100644 --- a/wolfhsm/wh_server.h +++ b/wolfhsm/wh_server.h @@ -156,6 +156,7 @@ typedef struct whServerConfig_t { whServerCryptoContext* crypto; #ifdef WOLFHSM_CFG_SHE_EXTENSION whServerSheContext* she; + whServerSheConfig* sheConfig; /* optional; NULL = in-context UID storage */ #endif /* WOLFHSM_CFG_SHE_EXTENSION */ #if defined WOLF_CRYPTO_CB int devId; diff --git a/wolfhsm/wh_server_she.h b/wolfhsm/wh_server_she.h index 854b7a343..10b7d1ef6 100644 --- a/wolfhsm/wh_server_she.h +++ b/wolfhsm/wh_server_she.h @@ -40,6 +40,24 @@ #if defined(WOLFHSM_CFG_SHE_EXTENSION) +struct whServerContext_t; + +/* Reads WH_SHE_UID_SZ bytes into outUid. Returns 0, WH_ERROR_NOTFOUND if no UID + * is provisioned, or another wolfHSM error. Called on every gated SHE request, + * so it must be cheap and idempotent. */ +typedef int (*whServerSheGetUidCb)(struct whServerContext_t* server, void* ctx, + uint8_t* outUid); + +/* Persists the WH_SHE_UID_SZ byte UID provisioned by WH_SHE_SET_UID. */ +typedef int (*whServerSheSetUidCb)(struct whServerContext_t* server, void* ctx, + const uint8_t* uid); + +typedef struct { + whServerSheGetUidCb getUidCb; /* NULL = use in-context uid[]/uidSet */ + whServerSheSetUidCb setUidCb; /* NULL = UID is read-only */ + void* uidCtx; /* opaque, passed back to both callbacks */ +} whServerSheConfig; + typedef struct { uint8_t sbState; uint8_t cmacKeyFound; @@ -61,12 +79,34 @@ typedef struct { uint8_t prngState[WH_SHE_KEY_SZ]; uint8_t prngKey[WH_SHE_KEY_SZ]; uint8_t uid[WH_SHE_UID_SZ]; + + /* When getUidCb is set, the uid[] and uidSet fields above are unused. */ + whServerSheGetUidCb getUidCb; + whServerSheSetUidCb setUidCb; + void* uidCtx; } whServerSheContext; int wh_Server_HandleSheRequest(whServerContext* server, uint16_t magic, uint16_t action, uint16_t req_size, const void* req_packet, uint16_t* out_resp_size, void* resp_packet); + +/** + * @brief Register SHE UID storage callbacks at runtime. + * + * Replaces callbacks previously set via whServerConfig.sheConfig or by a prior + * call to this function. + * + * @param server Server context. + * @param getCb UID read callback, or NULL to use in-context uid[]/uidSet. + * @param setCb UID write callback, or NULL for a read-only UID, which makes + * WH_SHE_SET_UID return WH_SHE_ERC_WRITE_PROTECTED. + * @param ctx Opaque context passed to both callbacks. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS if server or server->she is + * NULL. + */ +int wh_Server_SheSetUidCb(whServerContext* server, whServerSheGetUidCb getCb, + whServerSheSetUidCb setCb, void* ctx); #endif /* WOLFHSM_CFG_SHE_EXTENSION */ #endif /* !WOLFHSM_WH_SERVER_SHE_H */