Implement SHE GET_ID command with client side request, server side handler, and tests - #450
Implement SHE GET_ID command with client side request, server side handler, and tests#450AlexLanzano wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for the AUTOSAR SHE CMD_GET_ID command across the wolfHSM client/server messaging layer, exposing a client API to request module identity and implementing the server-side handler to return UID, status (SREG), and an identity CMAC.
Changes:
- Added
WH_SHE_GET_IDto the SHE action enum and introduced GET_ID request/response message types plus translation helpers. - Implemented client APIs (
wh_Client_SheGetId*) and server handler (_GetId) including SREG composition refactoring via_BuildSreg. - Added end-to-end and server-direct tests covering MAC verification, truncated request handling, empty
MASTER_ECU_KEYfallback-to-zero-key behavior, and pre-secure-boot allowance; updated docs accordingly.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| wolfhsm/wh_message.h | Adds WH_SHE_GET_ID action identifier. |
| wolfhsm/wh_message_she.h | Defines GET_ID request/response structs and translation function prototypes. |
| wolfhsm/wh_client_she.h | Adds public client API declarations and documentation for CMD_GET_ID. |
| src/wh_server_she.c | Implements server-side GET_ID handler, refactors SREG building into _BuildSreg, and updates state-gating/dispatch. |
| src/wh_message_she.c | Implements GET_ID request/response translation functions. |
| src/wh_client_she.c | Implements client request/response helpers and blocking wh_Client_SheGetId. |
| test/wh_test_she.c | Adds end-to-end GET_ID validation and server-direct GET_ID cases (empty key + pre-boot) plus req-size truncation test. |
| test/wh_test_check_struct_padding.c | Includes new GET_ID message structs in padding/struct compilation coverage. |
| test-refactor/server/wh_test_she_server.c | Adds GET_ID truncated request test in refactor suite. |
| test-refactor/misc/wh_test_check_struct_padding.c | Includes new GET_ID message structs in refactor padding coverage. |
| test-refactor/client-server/wh_test_she.c | Adds end-to-end GET_ID identity+MAC verification in refactor client-server test. |
| docs/src/5-Features.md | Documents wh_Client_SheGetId in the SHE client API feature list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (3)
test-refactor/misc/wh_test_she_uid_cb.c:148
- Casting the byte buffer resp_packet to int32_t* for writes/reads can cause unaligned accesses (UB) on architectures that require 32-bit alignment. Use memcpy to/from a local int32_t instead of pointer casts when seeding/reading the response rc.
*((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);
wolfhsm/wh_server_she.h:47
- The getUidCb comment notes it is called on every gated SHE request, but it doesn’t mention that this happens inside the state gate (before any NVM lock is taken). Callers implementing a backend in NVM/flash may need this detail to avoid unsafe assumptions about locking/thread-safety.
/* 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. */
docs/src/5-Features.md:766
- The secure-boot gate description just above still says only CMD_GET_STATUS and CMD_SET_UID are permitted before secure boot succeeds, but the server gate now also whitelists CMD_GET_ID (see wh_server_she.c). The docs should reflect that GET_ID is allowed in all states.
The same 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`. Status stays readable in every case, per the spec.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #450
Scan targets checked: wolfhsm-core-bugs, wolfhsm-crypto-bugs, wolfhsm-src
Findings: 1
Medium (1)
SHE UID callback invoked without NVM lock for SET_UID and the pre-dispatch state gate
File: src/wh_server_she.c:2057
Function: wh_Server_HandleSheRequest
Category: NV storage vulnerabilities
_ReportInvalidSheState calls _UidIsProvisioned/getUidCb for nearly every action before any lock is taken, and the WH_SHE_SET_UID case runs _SetUid (which can call setUidCb) with no WH_SERVER_NVM_LOCK, unlike every other handler that touches the keystore/NVM. Since docs explicitly endorse NVM-backed UID callbacks, this permits concurrent unsynchronized NVM access in WOLFHSM_CFG_THREADSAFE builds.
Recommendation: Wrap the _ReportInvalidSheState call and the WH_SHE_SET_UID case with WH_SERVER_NVM_LOCK/WH_SERVER_NVM_UNLOCK like the other handlers.
Referenced code: src/wh_server_she.c:2057-2063 (7 lines)
This review was generated automatically by Fenrir. Findings are non-blocking.
bigbrett
left a comment
There was a problem hiding this comment.
Run git-clang-format main to reformat the diff plz
| whServerConfig serverConfig = { /* ... */ .she = she, .sheConfig = &sheConfig }; | ||
| ``` | ||
|
|
||
| When a getter is installed the server reads the UID on demand at each point that needs it and keeps no copy in `whServerSheContext`, so the UID never outlives a single request in server RAM. The trade-off is that the getter is called on every gated SHE request, and more than once for commands that use the UID, so it must be cheap, idempotent, and free of side effects. Leaving `sheConfig` unset preserves the original in-context behavior exactly. |
|
|
||
| ### SHE UID Storage | ||
|
|
||
| By default the 15-byte ECU UID lives in the caller-owned `whServerSheContext`, which means it is RAM-resident and must be re-provisioned with `CMD_SET_UID` after every reset. Integrators whose UID is fused, held in OTP, or kept in NVM can instead point the server at that store with a pair of callbacks: |
There was a problem hiding this comment.
This reads like the default behavior is that the UID is RAM only, but really there isn't a default it just depends on if you register callbacks or now. I'd rephrase to make it clear that the callback field would be what dictates the behavior.
| memcpy(dest->uid, src->uid, WH_SHE_UID_SZ); | ||
| memcpy(dest->mac, src->mac, WH_SHE_KEY_SZ); |
There was a problem hiding this comment.
would these not need translation?
There was a problem hiding this comment.
These are accessed/used as byte arrays on both ends so there wouldn't be any need to translate
| /* The SHE context is caller-owned and not covered by the memset above, so | ||
| * the callbacks are assigned on both paths. */ |
There was a problem hiding this comment.
| /* The SHE context is caller-owned and not covered by the memset above, so | |
| * the callbacks are assigned on both paths. */ |
| 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; |
There was a problem hiding this comment.
could we not have the callbacks take a pointer to a whServerSheContext instead of whServerContext to avoid the impicit wh_server.h dependency? In my mind the only server-specific thing that it would need would be a pointer to the NVM instance, which could be passed through the callback-specific context pointer anyway?
There was a problem hiding this comment.
I feel like at that point we should just have the signature be:
int (*whServerSheGetUidCb)(void* ctx, uint8_t* outUid)
Thoughts?
| ret = _GetUid(server, uid); | ||
| if (ret != 0) { | ||
| ret = WH_SHE_ERC_MEMORY_FAILURE; | ||
| } |
There was a problem hiding this comment.
this pattern is repeated a lot, would it make more sense for _GetUid() to just return the right error codes so they don't need to be remuxed everywhere?
There was a problem hiding this comment.
I think it's best that the underling caller of the _GetUid determines the SHE error code else we will need to pass in some sort of state to the _GetUid function in order to determine the right SHE error code. I.E. take a look at _UidIsProvisioned which calls _GetUid and how it's caller determines the right SHE error code
| * 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. */ |
There was a problem hiding this comment.
| * 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. */ |
| } /* WH_SERVER_NVM_LOCK() */ | ||
| break; | ||
| case WH_SHE_GET_ID: | ||
| ret = WH_SERVER_NVM_LOCK(server); |
There was a problem hiding this comment.
why are we taking the lock here?
There was a problem hiding this comment.
To sync the KeyStoreRead of the MASTER_ECU_KEY.
| - **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. |
There was a problem hiding this comment.
| - **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. | |
| - **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. |
|
|
||
| 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 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`. Status stays readable in every case, per the spec. |
There was a problem hiding this comment.
| The same 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`. Status stays readable in every case, per the spec. | |
| 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`. |
No description provided.