From 2e4f73a8241b30671c6e624bcb684c65a7e9b5d5 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:31:16 -0800 Subject: [PATCH 1/5] Documentation overhaul --- docs/draft/auth.md.bak | 482 ++++++++++++++ docs/src/0-Index.md | 72 +++ docs/src/1-Overview.md | 80 +++ docs/src/10-API-docs-server.md | 9 + docs/src/11-Configuration.md | 220 +++++++ docs/src/2-FAQs.md | 115 ++++ docs/src/3-Quickstart.md | 255 ++++++++ docs/src/4-Architecture.md | 161 +++++ docs/src/5-Features.md | 1085 ++++++++++++++++++++++++++++++++ docs/src/6-Utilities.md | 105 ++++ docs/src/7-Examples.md | 95 +++ docs/src/8-Integration.md | 68 ++ docs/src/9-API-docs-client.md | 7 + docs/src/appendix01.md | 93 --- docs/src/chapter01.md | 17 - docs/src/chapter02.md | 90 --- docs/src/chapter03.md | 199 ------ docs/src/chapter04.md | 303 --------- docs/src/chapter05.md | 600 ------------------ docs/src/chapter06.md | 24 - docs/src/chapter07.md | 431 ------------- docs/src/chapter08.md | 78 --- docs/src/chapter09.md | 219 ------- wolfhsm/wh_client_she.h | 657 +++++++++++++++++++ 24 files changed, 3411 insertions(+), 2054 deletions(-) create mode 100644 docs/draft/auth.md.bak create mode 100644 docs/src/0-Index.md create mode 100644 docs/src/1-Overview.md create mode 100644 docs/src/10-API-docs-server.md create mode 100644 docs/src/11-Configuration.md create mode 100644 docs/src/2-FAQs.md create mode 100644 docs/src/3-Quickstart.md create mode 100644 docs/src/4-Architecture.md create mode 100644 docs/src/5-Features.md create mode 100644 docs/src/6-Utilities.md create mode 100644 docs/src/7-Examples.md create mode 100644 docs/src/8-Integration.md create mode 100644 docs/src/9-API-docs-client.md delete mode 100644 docs/src/appendix01.md delete mode 100644 docs/src/chapter01.md delete mode 100644 docs/src/chapter02.md delete mode 100644 docs/src/chapter03.md delete mode 100644 docs/src/chapter04.md delete mode 100644 docs/src/chapter05.md delete mode 100644 docs/src/chapter06.md delete mode 100644 docs/src/chapter07.md delete mode 100644 docs/src/chapter08.md delete mode 100644 docs/src/chapter09.md diff --git a/docs/draft/auth.md.bak b/docs/draft/auth.md.bak new file mode 100644 index 000000000..d5a0e0a19 --- /dev/null +++ b/docs/draft/auth.md.bak @@ -0,0 +1,482 @@ +# wolfHSM Authentication Manager — PR #270 Overview (v2) + +--- + +## 1. TL;DR + +PR #270 introduces a **PKCS11-flavored Authentication/Authorization Manager** to wolfHSM. It provides: + +- **Login/logout** with two credential methods: **PIN** (SHA-256 hashed) or **X.509 certificate**. +- A **user database** managed via add/delete/get and set-permissions / set-credentials APIs. +- A **permission model** of (admin flag) + (per-group allow boolean) + (per-group bitmap of 256 allowed actions) + (a small per-user list of accessible key IDs — not yet wired into crypto paths). +- A **server-side request gate** that, on every incoming request, consults the Auth Manager and rejects messages the current session is not permitted to run. +- A **message group** `WH_MESSAGE_GROUP_AUTH = 0x0D00` with 7 new actions (login, logout, user add/delete/get, set-permissions, set-credentials), complete with endian/magic translation functions. +- A **pluggable backend**: everything goes through a `whAuthCb` callback vtable. A default in-memory backend lives in `src/wh_auth_base.c` (up to 5 users, credential storage up to 2 KiB per user, used by examples and tests). +- The feature is **opt-in**: entire subsystem is guarded by `WOLFHSM_CFG_ENABLE_AUTHENTICATION`. With it compiled in but no context configured (`server->auth == NULL`), the server logs a SECEVENT and processes all requests without any authorization check — preserving backwards compatibility. + +Design notes called out by the author: +- The "check key use" callback (`CheckKeyAuthorization`) is wired into the interface but **not yet invoked** on the key paths — it's a TODO placeholder. +- The base user list is in RAM, not NVM — deliberate for an initial cut. +- Logging of auth events (login success/failure, crypto actions) is another TODO, though authorization failures already log via `WH_LOG_ON_ERROR_F`. + +--- + +## 2. High-level architecture + +``` + ┌──────────────────────┐ + │ Client App │ + │ wh_Client_Auth*() │ client-side request/response helpers + └──────────┬───────────┘ + │ WH_MESSAGE_GROUP_AUTH (0x0D00) + │ + ┌──────────────▼────────────────────────────────────────────┐ + │ Server dispatch │ + │ wh_Server_HandleRequestMessage() (src/wh_server.c) │ + │ 1. Recv packet -> extract (group, action) │ + │ 2. wh_Auth_CheckRequestAuthorization(group, action) │ <-- the gate + │ 3. Dispatch by group │ + └──────────────┬─────────────────────────┬──────────────────┘ + │ │ + WH_MESSAGE_GROUP_AUTH any other group + │ │ + ▼ ▼ + ┌─────────────────────────┐ (NVM/key/crypto/SHE/etc. handlers; + │ wh_Server_HandleAuth- │ they do not re-check auth — the gate + │ Request() │ above has already vetted the call) + │ (src/wh_server_auth.c) │ + └───────┬─────────────────┘ + │ wh_Auth_Login / _Logout / _UserAdd / _UserDelete / + │ _UserGet / _UserSetPermissions / _UserSetCredentials + ▼ + ┌─────────────────────────┐ + │ Auth Manager core │ transport/protocol-agnostic wrappers + │ src/wh_auth.c │ that take the lock and delegate to cb + └───────┬─────────────────┘ + │ whAuthCb->Login / ->UserAdd / ... + ▼ + ┌─────────────────────────┐ + │ Pluggable backend │ + │ default: wh_auth_base │ in-memory user db, SHA-256 PIN hashing, + │ (src/wh_auth_base.c) │ optional wolfSSL cert verification + └─────────────────────────┘ +``` + +Key separation of concerns: + +1. `wh_auth.h` / `wh_auth.c` — **the "front end":** public API, session state, locking, policy decisions (the default group+action bitmap check). Always compiled when auth is on; does not depend on any specific user-store format. +2. `wh_auth_base.h` / `wh_auth_base.c` — **reference backend:** owns the user list, hashes PINs, verifies certificates, stores permissions. Can be swapped for a custom backend by registering a different `whAuthCb` vtable. +3. `wh_message_auth.h` / `wh_message_auth.c` — wire format and endian translation for all 7 auth messages (plus a flatten/unflatten pair for the permissions struct, which is too large and array-heavy for the usual `WH_T*()` helpers). +4. `wh_server_auth.c` / `wh_client_auth.c` — the message handlers on each side, each of which lives under both `WOLFHSM_CFG_ENABLE_{SERVER,CLIENT}` and `WOLFHSM_CFG_ENABLE_AUTHENTICATION` guards. + +--- + +## 3. Files touched (grouped) + +| Area | Files | +|------|-------| +| New public headers | `wolfhsm/wh_auth.h`, `wolfhsm/wh_auth_base.h`, `wolfhsm/wh_message_auth.h`, `wolfhsm/wh_server_auth.h` | +| Core & base impl | `src/wh_auth.c`, `src/wh_auth_base.c`, `src/wh_message_auth.c` | +| Server integration | `src/wh_server.c`, `src/wh_server_auth.c`, `wolfhsm/wh_server.h` | +| Client integration | `src/wh_client.c`, `src/wh_client_auth.c`, `wolfhsm/wh_client.h` | +| Error / message enums | `wolfhsm/wh_error.h` (3 new codes), `wolfhsm/wh_message.h` (new group + actions) | +| Examples | `examples/posix/wh_posix_server/wh_posix_server_cfg.c`, `wh_posix_server_cfg.h`, `wh_posix_server.c`; `examples/posix/wh_posix_client/Makefile`; `examples/posix/wh_posix_server/Makefile`; `examples/demo/client/wh_demo_client_auth.{c,h}`; `examples/demo/client/wh_demo_client_all.c` | +| Tests | `test/wh_test_auth.{c,h}` (1440 LOC), hook-ins in `test/wh_test.c`, `wh_test_clientserver.c`, `wh_test_crypto.c`, `wh_test_keywrap.c`, `wh_test_she.c`, `wh_test_log.c`, `wh_test_posix_threadsafe_stress.c`, `wh_test_common.h`, `test/Makefile` | +| Misc | `src/wh_server_she.c` (SHE tests now log in as admin), `src/wh_utils.c` + `wolfhsm/wh_utils.h` (new `wh_Utils_ForceZero` and `wh_Utils_ConstantCompare`), `port/posix/posix_transport_tls.c`, CI workflows, `docs/src/chapter09.md` (new docs chapter) | + +--- + +## 4. Data model (what an "individual user" looks like) + +### 4.1 Identity: `whUserId` + +`whUserId` is a `uint16_t`. Zero is reserved as `WH_USER_ID_INVALID`. The base backend assigns IDs 1..`WH_AUTH_BASE_MAX_USERS` (5 by default), where the ID is literally the 1-based slot in the static users array (`id = slot_index + 1`). + +### 4.2 Credentials: `whAuthMethod` + +```c +typedef enum { + WH_AUTH_METHOD_NONE = 0, + WH_AUTH_METHOD_PIN, // SHA-256 hashed when crypto is enabled + WH_AUTH_METHOD_CERTIFICATE, // wolfSSL cert verification; gated on WOLFHSM_CFG_CERTIFICATE_MANAGER +} whAuthMethod; +``` + +- PIN: the base backend stores the 32-byte SHA-256 of the PIN (falls back to a direct copy when `WOLFHSM_CFG_NO_CRYPTO` is set). Comparison uses `wh_Utils_ConstantCompare` — a new utility added by this PR. +- Certificate: the user's stored "credential" is a CA in DER; login presents a leaf cert, which the base backend feeds through `wolfSSL_CertManagerLoadCABuffer` + `wolfSSL_CertManagerVerifyBuffer`. + +### 4.3 Permissions: `whAuthPermissions` + +#### 4.3.1 The two-tier concept + +Every wolfHSM request on the wire is identified by a 16-bit `kind` that splits into: + +- **Group** (high byte): the category of operation. `WH_MESSAGE_GROUP_*` defines 13 groups today (`wolfhsm/wh_message.h:38-50`): `COMM=0x0100`, `NVM=0x0200`, `KEY=0x0300`, `CRYPTO=0x0400`, `IMAGE=0x0500`, `PKCS11=0x0600`, `SHE=0x0700`, `COUNTER=0x0800`, `CUSTOM=0x0A00`, `CRYPTO_DMA=0x0B00`, `CERT=0x0C00`, `AUTH=0x0D00`. +- **Action** (low byte): the specific operation within that group. Action enums are *group-local* — `KEY_CACHE` and `CRYPTO_SIGN` may both be value `0`, but they live in different groups so they're unambiguous in context. + +The auth manager mirrors that split in the user's `whAuthPermissions` struct as **two independent filters that both must pass** before a request is admitted to its handler: + +```c +typedef struct { + uint8_t groupPermissions[WH_NUMBER_OF_GROUPS + 1]; // boolean allow per group; last byte = admin flag + uint32_t actionPermissions[WH_NUMBER_OF_GROUPS][WH_AUTH_ACTION_WORDS]; // 256 bits per group (8 x uint32_t) + uint16_t keyIdCount; + uint32_t keyIds[WH_AUTH_MAX_KEY_IDS]; // small allowlist; default WH_AUTH_MAX_KEY_IDS = 2 +} whAuthPermissions; +``` + +#### 4.3.2 Filter 1 — group boolean + +`groupPermissions[groupIndex]` (where `groupIndex = (group >> 8) & 0xFF`) is a single byte: nonzero means "this user is allowed to talk to this group at all." If it's 0, the request is denied without ever looking at the bitmap. It's a fast reject path *and* a coarse on/off switch — useful for "this user only ever uses NVM, never crypto." + +The `+1` slot at the end (`groupPermissions[WH_NUMBER_OF_GROUPS]`) is reused as the **admin flag** — `WH_AUTH_IS_ADMIN(p)` reads it. Admin isn't a group; it's a separate capability that gates things like `UserAdd` of another admin and cross-user logout. + +#### 4.3.3 Filter 2 — action bitmap + +If the group passes, the gate then checks the per-group **256-bit bitmap** stored as 8 × `uint32_t`. The mapping is straightforward (`wolfhsm/wh_auth.h:79`): + +```c +wordIdx = action / 32 +bitMask = 1U << (action % 32) +allowed = actionPermissions[groupIndex][wordIdx] & bitMask +``` + +Wire actions are `uint16_t`, so 65 536 are theoretically possible — the model caps at 256 and rejects anything beyond. That's a deliberate trade: 256 bits per group keeps the struct flat and copy-friendly (the whole `whAuthPermissions` flattens to ~473 bytes for the wire) at the cost of an upper limit on actions per group. Today no group comes close. + +#### 4.3.4 Derived constants and byte shape + +- `WH_NUMBER_OF_GROUPS = (WH_MESSAGE_GROUP_MAX >> 8) + 1` — currently 14, since `WH_MESSAGE_GROUP_MAX = WH_MESSAGE_GROUP_AUTH = 0x0D00`. +- `WH_AUTH_ACTIONS_PER_GROUP = 256`, `WH_AUTH_ACTION_WORDS = 8`. + +Shape in bytes (exactly what gets flattened on the wire, `WH_FLAT_PERMISSIONS_LEN`): +``` +(WH_NUMBER_OF_GROUPS + 1) // group booleans + admin ++ 4 * WH_NUMBER_OF_GROUPS * WH_AUTH_ACTION_WORDS // action bitmap (per-group) ++ 2 // keyIdCount ++ 4 * WH_AUTH_MAX_KEY_IDS // keyIds += 15 + (4 * 14 * 8) + 2 + 8 = 473 bytes // with current defaults +``` + +#### 4.3.5 Helper macros + +The two filters compose through the macros in `wolfhsm/wh_auth.h:86-125`: + +| Macro | Group byte | Action bitmap | +|-------|------------|---------------| +| `WH_AUTH_IS_ADMIN(p)` | reads admin slot (byte index `WH_NUMBER_OF_GROUPS`) | — | +| `WH_AUTH_SET_IS_ADMIN(p, v)` | writes admin slot (`v ? 1 : 0`) | — | +| `WH_AUTH_SET_ALLOWED_GROUP(p, group)` | set to 1 | set all 256 bits | +| `WH_AUTH_SET_ALLOWED_ACTION(p, group, action)` | set to 1 | OR in one bit (existing bits preserved — Copilot flagged a mismatch with the header comment that says "only the given action bit") | +| `WH_AUTH_CLEAR_ALLOWED_GROUP(p, group)` | set to 0 | zero all bits | +| `WH_AUTH_CLEAR_ALLOWED_ACTION(p, group, action)` | left alone | clear one bit | + +So enabling a single action also implicitly enables its group, but disabling a single action leaves the group enabled (you can still use *other* actions in it). Disabling the group nukes everything. + +#### 4.3.6 Worked examples + +**Crypto-only signer:** group byte set for `CRYPTO`, only the `SIGN` action bit set in the CRYPTO bitmap, every other group byte = 0. Any NVM/KEY/IMAGE/etc. request hits filter 1 and is denied; any CRYPTO request other than `SIGN` passes filter 1 but fails filter 2. + +**Admin everything:** the example POSIX server seeds admin with `memset(&permissions, 0xFF, sizeof(permissions))` (`examples/posix/wh_posix_server/wh_posix_server_cfg.c:719`). That sets every group byte, every action bit, and the admin slot in one shot. + +#### 4.3.7 Exceptions to the bitmap + +The gate (`src/wh_auth.c:206`) hard-codes a few unconditional allows that bypass both filters: + +- Unauthenticated session → all of group `COMM`, plus `(AUTH, LOGIN)`. Without these, no one could ever open a connection or log in. +- Authenticated session → `(AUTH, LOGOUT)` always succeeds. You can always log yourself out regardless of permission state. + +Everything else flows through the two-tier filter described above, optionally followed by the backend's `cb->CheckRequestAuthorization` override (see §7.1) which sees the tentative verdict and can flip it either way. + +### 4.4 Session: `whAuthUser` / `whAuthContext` + +```c +typedef struct { + whUserId user_id; + char username[32]; + whAuthPermissions permissions; + bool is_active; +} whAuthUser; + +struct whAuthContext_t { + whAuthCb* cb; // backend vtable + whAuthUser user; // *the* currently-logged-in user for this connection + void* context; // opaque backend state +#ifdef WOLFHSM_CFG_THREADSAFE + whLock lock; +#endif +}; +``` + +A single `whAuthContext` holds **one** logged-in user at a time (src/wh_auth.c:139: "allowing only one user logged in to an open connection at a time"). A second login attempt while someone is already logged in returns success at the protocol level with `loggedIn=0` set — i.e. the call was processed fine, authentication simply didn't happen. + +--- + +## 5. The plugin contract: `whAuthCb` + +The core wraps every operation, acquires the lock, and then calls into this vtable: + +```c +typedef struct { + int (*Init)(void* ctx, const void* cfg); + int (*Cleanup)(void* ctx); + + int (*Login)(void* ctx, uint8_t client_id, whAuthMethod method, + const char* username, const void* auth_data, uint16_t auth_data_len, + whUserId* out_user_id, whAuthPermissions* out_permissions, + int* loggedIn); + int (*Logout)(void* ctx, whUserId current_user_id, whUserId user_id); + + /* Optional authorization-decision overrides */ + int (*CheckRequestAuthorization)(void* ctx, int err, uint16_t user_id, + uint16_t group, uint16_t action); + int (*CheckKeyAuthorization)(void* ctx, int err, uint16_t user_id, + uint32_t key_id, uint16_t action); + + /* User management */ + int (*UserAdd)(void* ctx, const char* username, whUserId* out_user_id, + whAuthPermissions permissions, whAuthMethod method, + const void* credentials, uint16_t credentials_len); + int (*UserDelete)(void* ctx, whUserId current_user_id, whUserId user_id); + int (*UserSetPermissions)(void* ctx, whUserId current_user_id, + whUserId user_id, whAuthPermissions permissions); + int (*UserGet)(void* ctx, const char* username, + whUserId* out_user_id, whAuthPermissions* out_permissions); + int (*UserSetCredentials)(void* ctx, whUserId user_id, whAuthMethod method, + const void* current_credentials, uint16_t current_credentials_len, + const void* new_credentials, uint16_t new_credentials_len); +} whAuthCb; +``` + +The two "Check*" callbacks are **overrides, not gates** — see §7 below for exactly how they're layered over the default decision. + +--- + +## 6. Default backend (`wh_auth_base.c`) + +- **Storage:** `static whAuthBase_User users[WH_AUTH_BASE_MAX_USERS]` (=5). Each slot has the public `whAuthUser`, the chosen method, and a `credentials[2048]` byte buffer (+ length). The author notes this is intentionally simple and not yet NVM-backed. +- **Thread safety:** explicitly documented (src/wh_auth_base.c:54) — the global array is protected by the auth context's lock which the core `wh_Auth_*` wrappers acquire before calling any backend entry. The backend itself does no locking. +- **PIN path:** `wh_Auth_BaseCheckPin` hashes the incoming PIN with `wc_Sha256Hash_ex` and compares to the stored digest using `wh_Utils_ConstantCompare`. Hash buffer is `wh_Utils_ForceZero`d on exit whether the compare succeeded or not. When `WOLFHSM_CFG_NO_CRYPTO` is set, the PIN is stored verbatim (bounded by `WH_AUTH_BASE_MAX_CREDENTIALS_LEN`). +- **Certificate path:** guarded by `WOLFHSM_CFG_CERTIFICATE_MANAGER && !WOLFHSM_CFG_NO_CRYPTO`. Uses a per-call `WOLFSSL_CERT_MANAGER` seeded with the user's stored DER as a CA and then verifies the supplied leaf. +- **Admin enforcement:** `wh_Auth_BaseUserDelete` and `wh_Auth_BaseUserSetPermissions` both require `current_user_id` (the caller session) to have the admin flag. `wh_Auth_BaseLogout` allows logging out someone *other* than yourself only if you're admin. +- **Set-credentials:** if the target user already has credentials, the old ones must be presented and match (constant-time compare, PIN hashed first); otherwise `current_credentials` must be NULL. PINs are rehashed before replacement, and intermediate hash buffers are force-zeroed. +- **User ID policy:** 1-based indexes into `users[]`; 0 reserved. Duplicate usernames are rejected by `wh_Auth_BaseUserAdd` with `WH_ERROR_BADARGS`. `keyIdCount` is clamped to `WH_AUTH_MAX_KEY_IDS` and unused `keyIds` entries are zeroed (done both on add and on set-permissions). + +--- + +## 7. Authorization — how the server enforces it on *every* request + +The integration point is **`wh_Server_HandleRequestMessage`** in `src/wh_server.c`. After a packet is received and the `(group, action)` are extracted, before the switch on `group`: + +```c +#ifdef WOLFHSM_CFG_ENABLE_AUTHENTICATION + if (server->auth != NULL) { + rc = wh_Auth_CheckRequestAuthorization(server->auth, group, action); + if (rc != WH_ERROR_OK) { + int32_t error_code = (int32_t)WH_AUTH_PERMISSION_ERROR; + uint16_t resp_size = _FormatAuthErrorResponse(magic, group, action, + error_code, data); + do { rc = wh_CommServer_SendResponse(server->comm, magic, kind, + seq, resp_size, data); + } while (rc == WH_ERROR_NOTREADY); + WH_LOG_ON_ERROR_F(&server->log, WH_LOG_LEVEL_ERROR, + WH_AUTH_PERMISSION_ERROR, + "Authorization failed for (group=%d, action=%d, seq=%d)", + group, action, seq); + return rc; + } + } +#endif +``` + +Two deliberate design points here: + +1. **The check happens once per request**, up-front, on the front end — as explicitly requested by @bigbrett in review ("the actual authorization check … should be part of the generic 'front end' and not delegated to the back-end"). Backends only affect authorization through the optional override callback. +2. **When `server->auth == NULL`** (auth compiled in but not configured) the check is skipped entirely and a SECEVENT log line is emitted at init time to announce this. That keeps existing code/tests working without having to introduce logins everywhere. + +### 7.1 The default decision (`wh_Auth_CheckRequestAuthorization`) + +In `src/wh_auth.c` the flow inside the lock is: + +1. Read `user_id = context->user.user_id`. +2. **If no user is logged in** (`user_id == WH_USER_ID_INVALID`): + - Allow `WH_MESSAGE_GROUP_COMM` (so a client can still perform comm handshakes/echo/close). + - Allow `WH_MESSAGE_GROUP_AUTH` + `WH_MESSAGE_AUTH_ACTION_LOGIN` (so a client can actually log in). + - Deny everything else → `WH_ERROR_ACCESS`. +3. **If a user is logged in:** + - Always allow `WH_MESSAGE_GROUP_AUTH` + `WH_MESSAGE_AUTH_ACTION_LOGOUT` (you can always log yourself out). + - Otherwise, look up `groupIndex = (group >> 8) & 0xFF`: + - Bounds-check `groupIndex < WH_NUMBER_OF_GROUPS`. + - If `permissions.groupPermissions[groupIndex] == 0` → deny. + - If `action >= WH_AUTH_ACTIONS_PER_GROUP` → deny. + - Map `action → (wordIdx, bitMask)` via `WH_AUTH_ACTION_TO_WORD_AND_BITMASK`. Allow iff the bit is set in `permissions.actionPermissions[groupIndex][wordIdx]`. +4. **Override hook:** if `cb->CheckRequestAuthorization != NULL`, invoke it with the preliminary `rc`, the user id, and the (group, action). Its return becomes the final decision. This is what `test/wh_test_auth.c` exercises to confirm backends can see the result and flip it either direction. + +If this stage denies the request, the server synthesizes a per-group/per-action error response via `_FormatAuthErrorResponse` (new helper) so the client always gets a well-formed reply carrying `WH_AUTH_PERMISSION_ERROR`. The helper handles the three auth responses that are bigger than `SimpleResponse` (Login / UserAdd / UserGet), the oversized NVM ones, the Cert group, and falls back to writing just a translated `int32_t` rc for everything else. + +### 7.2 Key-level authorization — deferred + +`wh_Auth_CheckKeyAuthorization` and the `CheckKeyAuthorization` callback are defined and tested (presence-of-callback), but **no current request handler calls it**. The PR author called this out explicitly: "I added a callback function framework for checking authorization of key use based on key ID and user permissions but did not tie in that check yet." The reviewer should confirm no crypto/key handler was modified to call it — otherwise callers silently skip that layer today. + +### 7.3 Admin gating in auth operations + +Two additional checks live above the backend in `wh_Auth_UserAdd` (src/wh_auth.c): + +- `WH_AUTH_IS_ADMIN(permissions_to_assign) && !WH_AUTH_IS_ADMIN(current_session_permissions)` → `WH_AUTH_PERMISSION_ERROR`. That is, **a non-admin session can never promote another user to admin**. This is enforced in the core, not the backend, so any custom backend inherits it. + +The backend `wh_Auth_BaseUserDelete` and `wh_Auth_BaseUserSetPermissions` additionally require the caller to be admin. `wh_Auth_BaseLogout` requires admin for cross-user logouts. + +### 7.4 Auto-logout on disconnect + +The COMM group's `CLOSE` action handler (src/wh_server.c:270) now logs the current user out when the comm channel is torn down. This prevents a stale session from persisting across client reconnects on the same server. + +--- + +## 8. Wire protocol — the auth message group + +Added to `wolfhsm/wh_message.h`: + +```c +WH_MESSAGE_GROUP_AUTH = 0x0D00 +WH_MESSAGE_GROUP_MAX = 0x0D00 // bumped so WH_NUMBER_OF_GROUPS reflects it + +enum { + WH_MESSAGE_AUTH_ACTION_LOGIN, + WH_MESSAGE_AUTH_ACTION_LOGOUT, + WH_MESSAGE_AUTH_ACTION_USER_ADD, + WH_MESSAGE_AUTH_ACTION_USER_DELETE, + WH_MESSAGE_AUTH_ACTION_USER_GET, + WH_MESSAGE_AUTH_ACTION_USER_SET_PERMISSIONS, + WH_MESSAGE_AUTH_ACTION_USER_SET_CREDENTIALS, +}; +``` + +The 7 request/response pairs live in `wh_message_auth.h`. Three of them carry **variable-length payloads** after a fixed header (login auth data, user-add credentials, set-credentials' two credential buffers). Those use `Translate*Request(void* src_packet, uint16_t src_size, ...)` helpers that validate `src_size` against the header-plus-declared-payload length (returning `WH_ERROR_BUFFER_SIZE` on mismatch), and each type has its own cap: + +```c +WH_MESSAGE_AUTH_LOGIN_MAX_AUTH_DATA_LEN + = COMM_DATA_LEN - sizeof(LoginRequest) +WH_MESSAGE_AUTH_USERADD_MAX_CREDENTIALS_LEN + = COMM_DATA_LEN - sizeof(UserAddRequest) +WH_MESSAGE_AUTH_SETCREDS_MAX_CREDENTIALS_LEN + = (COMM_DATA_LEN - sizeof(UserSetCredentialsRequest)) / 2 +``` + +`whAuthPermissions` is large and contains nested arrays, so the PR adds `wh_MessageAuth_FlattenPermissions` / `_Unflatten…` to marshal it into a fixed-size little-endian byte buffer (`WH_FLAT_PERMISSIONS_LEN` bytes) that's embedded in the UserAdd, UserGet, and UserSetPermissions messages. Everything else uses the standard `WH_T16`/`WH_T32` magic-aware translation helpers. + +Responses either use a dedicated type (Login → user_id, UserAdd → user_id, UserGet → user_id + flat permissions) or the shared `whMessageAuth_SimpleResponse { int32_t rc; }`. + +--- + +## 9. Client API (`src/wh_client_auth.c`) + +Every action comes in the wolfHSM-standard three flavors — a non-blocking send, a non-blocking receive, and a blocking loop on `WH_ERROR_NOTREADY`: + +```c +/* One-shot helpers */ +int wh_Client_AuthLogin(whClientContext* c, whAuthMethod method, + const char* username, const void* auth_data, + uint16_t auth_data_len, + int32_t* out_rc, whUserId* out_user_id); +int wh_Client_AuthLogout(whClientContext* c, whUserId user_id, int32_t* out_rc); +int wh_Client_AuthUserAdd(whClientContext* c, const char* username, + whAuthPermissions permissions, whAuthMethod method, + const void* credentials, uint16_t credentials_len, + int32_t* out_rc, whUserId* out_user_id); +int wh_Client_AuthUserDelete(whClientContext*, whUserId, int32_t* out_rc); +int wh_Client_AuthUserGet(whClientContext*, const char* username, + int32_t* out_rc, whUserId* out_user_id, + whAuthPermissions* out_permissions); +int wh_Client_AuthUserSetPermissions(whClientContext*, whUserId, + whAuthPermissions, int32_t* out_rc); +int wh_Client_AuthUserSetCredentials(whClientContext*, whUserId, whAuthMethod, + const void* current, uint16_t current_len, + const void* new, uint16_t new_len, + int32_t* out_rc); +``` + +Client-side defensive behavior to notice during review: + +- **Username validity** (`_UserNameIsValid` in wh_client_auth.c) requires non-NULL, non-empty, `< WH_MESSAGE_AUTH_MAX_USERNAME_LEN` (32) chars. +- The client stages all credential-carrying requests in a **stack buffer of size `WOLFHSM_CFG_COMM_DATA_LEN`** and calls `wh_Utils_ForceZero(buffer, sizeof(buffer))` before returning. This is the client-side mirror of the zeroization the server does after processing. +- `wh_Client_AuthLoginResponse` tolerates a server that responds with a `SimpleResponse` instead of a `LoginResponse` — that's how the server signals `WH_AUTH_NOT_ENABLED` to older/simpler clients; the demo (`wh_demo_client_auth.c`) keys off this to skip the demo cleanly. +- Every response handler validates `(resp_group, resp_action, resp_size)` before trusting the buffer. This is defense-in-depth against a desynchronized server — important since responses are returned in the same memory the request was written to. + +--- + +## 10. A concrete end-to-end: "user logs in and does a crypto op" + +To make the per-request/per-user flow concrete, here's what happens when a client does a `Login` followed by, say, a cached-key crypto call. Assume the server has been seeded with an admin `admin/1234` and a non-admin `demo` with `CRYPTO` group access (the exact setup in the POSIX example and the demo): + +1. **Client sends `Login("demo", PIN="…")`.** + - `wh_Client_AuthLogin` packs `whMessageAuth_LoginRequest` + PIN bytes into the comm buffer and sends with `(group=AUTH, action=LOGIN)`. + - The buffer is `ForceZero`d before return. +2. **Server front end** (`wh_Server_HandleRequestMessage`) receives the packet, extracts `(AUTH, LOGIN)`, and calls `wh_Auth_CheckRequestAuthorization`. No one is logged in yet, but the gate explicitly whitelists `(AUTH, LOGIN)` — passes. +3. **`wh_Server_HandleAuthRequest`** is dispatched, which for `LOGIN`: + - Translates the header (endian/magic) via `wh_MessageAuth_TranslateLoginRequest`. + - Calls `wh_Auth_Login(server->auth, comm->client_id, method, username, auth_data, auth_data_len, &loggedIn)`. +4. **`wh_Auth_Login`** acquires the auth lock. If someone is already logged in on this context, it returns `WH_ERROR_OK` with `loggedIn=0` (the slot is "busy"). Otherwise it calls the backend `Login`. On success it stashes `user.user_id`, `user.permissions`, and `user.is_active = true` inside the `whAuthContext`. +5. **`wh_Auth_BaseLogin`** (PIN path) hashes the provided PIN with `wc_Sha256Hash_ex`, looks the username up in the static array, constant-time compares the digests, sets `loggedIn=1` and copies out the user id and permissions on match. The hash scratch buffer is `ForceZero`d on every exit path. +6. **Server sends `LoginResponse`** containing either the new `user_id` or `WH_AUTH_LOGIN_FAILED`. The request packet's `auth_data` region is `ForceZero`d before the server returns. +7. **Client receives**, pulls out `out_rc` and `out_user_id` for later `Logout`. +8. **Client now does a crypto call**, e.g. `wc_…` which goes through the crypto-callback layer and ultimately sends `(group=CRYPTO, action=)`. +9. **Server front end** runs `wh_Auth_CheckRequestAuthorization(CRYPTO, op)`. Inside: + - `user_id` is non-invalid. + - `groupIndex = (WH_MESSAGE_GROUP_CRYPTO >> 8) & 0xFF = 4`. + - `permissions.groupPermissions[4]` is 1 → proceed to action bitmap. + - `(wordIdx, bitMask)` is computed from the action enum; allowed iff the bit is set. + - If a `CheckRequestAuthorization` callback is registered, it gets the tentative verdict and can flip it. In the example server it is `NULL`, so the callback step is skipped. +10. **If allowed** — the normal crypto handler runs; **no additional auth check** is performed today, even when the operation names a specific `keyId`. That's the TODO: the `CheckKeyAuthorization` callback and per-user `keyIds` allowlist exist in the data model and public API but the PR does not wire them into the crypto path. +11. **If denied** — `_FormatAuthErrorResponse` writes a group-appropriate error response carrying `WH_AUTH_PERMISSION_ERROR` (-2301), `wh_CommServer_SendResponse` ships it, and `WH_LOG_ON_ERROR_F` logs "Authorization failed for (group=%d, action=%d, seq=%d)". The request never reaches the crypto handler. +12. **Eventually the client sends `Close` on the comm channel.** The server's COMM close handler detects a live user, calls `wh_Auth_Logout`, which clears the `user` field inside `whAuthContext`. This happens even if the client forgets to call `AuthLogout` explicitly. + +--- + +## 11. Noteworthy security posture + +Things that are present and worth confirming during review: + +- **Constant-time credential compare** (`wh_Utils_ConstantCompare`, new utility) used for PIN hashes and cert buffers in the base backend. +- **Force-zero of sensitive buffers** (`wh_Utils_ForceZero`, new utility) on both client and server: PIN hash scratch, request-packet credential regions after processing, response packets with credentials, entire staging buffer on the client before return, and the user array on base-backend cleanup. Several of the later commits in the PR were exactly to add more of these. +- **Single-session-per-connection** semantics reduce the attack surface for cross-user confusion inside one comm channel. +- **Admin promotion guard** sits in the generic front end (`wh_Auth_UserAdd`), so backends can't accidentally allow it even if their `UserAdd` doesn't check. +- **Lock discipline**: all `wh_Auth_*` wrappers take the lock before calling into the backend; `wh_Auth_BaseLogin` and friends document that they expect to be called under the lock. Reviewer should verify any new call sites honor this. +- **Graceful fallback responses**: clients that get `WH_AUTH_NOT_ENABLED` when auth isn't configured server-side still see a well-formed message, not a malformed/oversized frame. + +Things that are **explicit open items** (per PR body and code comments): + +- No NVM backing for the user list — the base backend is RAM-only and losses on reboot. +- `CheckKeyAuthorization` is wired but not called anywhere in the request-handling paths in this PR. +- Logging of login attempts (successes and failures) is a TODO — only authorization denials are logged today. +- The `WH_AUTH_SET_ALLOWED_ACTION` macro comment says "and only the given action bit," but the implementation ORs (Copilot raised this during review). Either the comment or the semantics should change. + +--- + +## 12. Review checklist (suggested focus areas) + +1. **Front-end gate placement** — confirm every server-side request path goes through `wh_Server_HandleRequestMessage` before reaching a group handler. In particular, check DMA, SHE, PKCS11, CERT, and custom handler dispatch paths; anything that inserts a second dispatch could bypass the gate. +2. **`server->auth == NULL` semantics** — tests should confirm (a) auth-compiled + no context lets all non-auth requests through and (b) auth requests in that state return `WH_AUTH_NOT_ENABLED`, not `WH_ERROR_BADARGS`. +3. **Cross-user logout semantics** — `wh_Auth_Logout` in the core still wipes local session memory only if `user_id == context->user.user_id`, but the backend `wh_Auth_BaseLogout` can also deactivate a different user if the caller is admin. That asymmetry is intentional but worth double-checking against the test cases. +4. **Message validation against `WOLFHSM_CFG_COMM_DATA_LEN`** — particularly UserSetCredentials, which packs *two* variable-length buffers back to back. The per-message caps exist (`WH_MESSAGE_AUTH_SETCREDS_MAX_CREDENTIALS_LEN`) but verify both client and server reject an aggregate-size overrun. +5. **Macro behavior vs docs** on `WH_AUTH_SET_ALLOWED_ACTION` (OR vs assign) — the project should decide intended semantics since callers (including the demo) rely on OR behavior. +6. **`keyIdCount` clamping + unused slot zeroing** is duplicated between `UserAdd` and `UserSetPermissions` — worth a small helper to keep these in sync. +7. **Thread-safety contract** — backend docs say "protected by the auth context lock"; confirm any future backend author can't easily step outside the lock by, e.g., calling `wh_Auth_BaseFindUser` directly. +8. **Auto-logout on close** — confirm tests cover a client that crashes mid-session and reconnects; `CLOSE` isn't the only path to disconnection. +9. **`CheckKeyAuthorization` TODO** — decide whether merging without at least a scaffolded call site in the key/crypto handlers is acceptable, or whether it should be added (even if defaulting to allow) before merge so customers don't build against an interface that changes behavior later. +10. **Force-zero coverage** — spot-check that every function that stages credentials or PIN digests on the stack `ForceZero`s before return, even on error paths. The commit log shows several late additions here, suggesting it's easy to miss. + +--- + +## 13. Build & try it locally + +Per `docs/src/chapter09.md`, enable the feature via `WOLFHSM_CFG_ENABLE_AUTHENTICATION` and use the `AUTH=1` Make flag in the POSIX examples/tests: + +```bash +# Tests +cd test && make clean && make -j AUTH=1 && make run + +# Example server + demo client +cd examples/posix/wh_posix_server && make AUTH=1 +cd examples/posix/wh_posix_client && make AUTH=1 +``` + +The example server seeds an `admin/1234` user with everything-allowed permissions at startup (see `wh_PosixServer_ExampleAuthConfig` in `examples/posix/wh_posix_server/wh_posix_server_cfg.c`). The demo client (`examples/demo/client/wh_demo_client_auth.c`) logs in as admin, adds `demo/1234` with USER_SET_CREDENTIALS permission, rotates the `demo` PIN to `5678`, verifies the old PIN fails and the new PIN works, then logs out. Running the demo against an auth-disabled server returns `WH_AUTH_NOT_ENABLED` and the demo prints "Authentication not enabled on server, skipping …" and returns `WH_ERROR_OK` — useful for CI matrix coverage without separate test binaries. diff --git a/docs/src/0-Index.md b/docs/src/0-Index.md new file mode 100644 index 000000000..8269fee88 --- /dev/null +++ b/docs/src/0-Index.md @@ -0,0 +1,72 @@ +# wolfHSM Documentation + +A portable, open-source client-server framework for hardware cryptography, non-volatile memory, and secure processing. + +## Table of Contents + +### [1. Overview](1-Overview.md) +Introduction to wolfHSM, its feature set, and supported hardware platforms. + +- Overview +- Features +- Supported Platforms + +### [2. FAQs](2-FAQs.md) +Answers to common questions about wolfHSM's purpose, scope, dependencies, and capabilities. + +### [3. Quickstart](3-Quickstart.md) +Step-by-step guide to getting a client and server up and running. + +- Client Quickstart +- Server Quickstart +- Deep Dive: Transport, Comm Layer, NVM, wolfCrypt init, server context, request processing + +### [4. Architecture](4-Architecture.md) +Architectural overview of the client/server libraries and communication stack. + +- Client/Server libraries +- Code and API organization (Client API, Server API, source modules, compilation) +- Client/Server communication and communication stack +- Library configuration +- Internals deep dive: modular architecture + +### [5. Features](5-Features.md) +Detailed reference for each major wolfHSM subsystem. + +- Cryptography and wolfCrypt integration (offload, algorithms, hardware acceleration, blocking/non-blocking, request timeouts) +- Non-Volatile Memory (NVM interface, metadata, backends, flash abstraction) +- Keystore (key cache, key IDs, global keys, wrapped keys, usage policies) +- Certificate management (trusted roots, chain verification, verify cache, Acert support) +- Communication layer and transport backends +- DMA support (DMA crypto device, callbacks, allowlisting, 32/64-bit addressing) +- AUTOSAR SHE subsystem +- Non-volatile monotonic counters +- Image manager (image/firmware verification, verify methods and actions, wolfBoot images) +- Custom callbacks (application-defined server operations) +- Concurrency support (per-context threading model, lock abstraction) +- Authentication manager (PIN/certificate login, permissions; experimental) + +### [6. Utilities](6-Utilities.md) +Tools shipped with wolfHSM for provisioning and validation. + +- NVM Provisioning Tool (`whnvmtool`) +- Benchmark Suite (POSIX, real hardware) +- Test Suite (POSIX, real hardware) + +### [7. Examples](7-Examples.md) +Reference applications demonstrating wolfHSM usage. + +- POSIX example server and client (building, transport selection, NVM init) +- Demo client library (philosophy, demo categories) + +### [8. Integration](8-Integration.md) +Guides for integrating wolfHSM with the wider wolfSSL ecosystem. + +### [9. Client API Reference](9-API-docs-client.md) +Reference documentation for the wolfHSM client-side API. + +### [10. Server API Reference](10-API-docs-server.md) +Reference documentation for the wolfHSM server-side API. + +### [11. Configuration](11-Configuration.md) +Build-time and runtime configuration options for wolfHSM. diff --git a/docs/src/1-Overview.md b/docs/src/1-Overview.md new file mode 100644 index 000000000..7f661139d --- /dev/null +++ b/docs/src/1-Overview.md @@ -0,0 +1,80 @@ +# wolfHSM Documentation + +## Table of Contents + +- [Overview](#overview) +- [Features](#features) +- [Supported Platforms](#supported-platforms) + +## Overview + +wolfHSM is a software framework that provides a portable and open-source client-server +abstraction for hardware cryptography, non-volatile memory, and isolated secure processing +that maximizes security and performance. It consists of a client-server library architecture, +where the wolfHSM server application runs in a trusted environment, and client applications +communicate with the server through the wolfHSM client library. wolfHSM is intended to simplify +the challenge of moving between hardware with enhanced security features without being tied to +any vendor-specific library calls. wolfHSM also dramatically simplifies client HSM applications +by allowing direct use of wolfCrypt APIs, with the framework automatically offloading all sensitive +cryptographic operations to the wolfHSM server as remote procedure calls with no additional logic +required by the client app. + +Although initially targeted to automotive-style HSM-enabled microcontrollers, +wolfHSM can run on any platform that provides a secure/trusted execution environment +for the server. wolfHSM provides an extensible solution to support future capabilities +of any platform while still supporting standardized interfaces and protocols such as AUTOSAR SHE. + +## Features + +* Unified client and server library APIs abstracting common HSM operations +* Transport-agnostic architecture with reference implementations for shared memory buffers, POSIX TCP sockets, wolfSSL TLS on top of TCP, and POSIX inter-process shared memory +* Non-volatile object store with fail-safe atomic updates and fine-grained access control policies +* Per-client and global key stores supporting non-volatile and RAM-backed key slots, configurable usage policies, and encrypted (wrapped) keys +* Tight integration with the wolfCrypt cryptography library + * Client applications may use the wolfCrypt API directly, with supported algorithms and key storage transparently offloaded to the HSM server + * Server applications gain access to all wolfCrypt software algorithms, with optional device-specific hardware acceleration where available + * Support for Post-Quantum Cryptography (PQC) algorithms +* DMA support for shared-memory systems, including address allowlisting and configurable pre- and post-access callbacks for cache synchronization and address remapping +* X.509 certificate chain verification, including support for RFC 5755 attribute and authorization certificates +* Server image manager for authenticating firmware images or arbitrary regions of memory with customizable post-verification actions +* Extensible server behavior via user-defined callbacks for runtime functionality and message handling +* Non-volatile monotonic counters +* AUTOSAR Secure Hardware Extension (SHE) interface support +* Support for user authentication with full roll-based access control + +## Supported Platforms + +### Infineon + +- TC2xx +- TC3xx +- TC4xx + +### ST Microelectronics + +- Stellar SR6G +- SPC58N Bernina +- STM32H5 Trust Zone + +### Texas Instruments + +- TDA4VH + +### Renesas + +- Renesas RH850 + +### Microchip + +- PIC32CZ + +### AMD/Xilinx + +- Zynq UltraScale+ + +### Planned Ports + +- Microchip PIC32CN +- NXP S32K3 and S32G +- AMD/Xilinx Versal + diff --git a/docs/src/10-API-docs-server.md b/docs/src/10-API-docs-server.md new file mode 100644 index 000000000..ef04667a3 --- /dev/null +++ b/docs/src/10-API-docs-server.md @@ -0,0 +1,9 @@ +# Server API Reference + +This chapter is the complete reference for the wolfHSM **server** API. It is generated directly from the documentation comments in the public server headers ([`wolfhsm/wh_server.h`](../../wolfhsm/wh_server.h), [`wolfhsm/wh_server_keystore.h`](../../wolfhsm/wh_server_keystore.h), [`wolfhsm/wh_server_img_mgr.h`](../../wolfhsm/wh_server_img_mgr.h), [`wolfhsm/wh_server_cert.h`](../../wolfhsm/wh_server_cert.h), and [`wolfhsm/wh_server_cert_cache.h`](../../wolfhsm/wh_server_cert_cache.h)), so it always tracks the source. For a conceptual, feature-oriented walkthrough of what these functions are for, see [Features](5-Features.md); this chapter documents the precise signatures, parameters, and return values. + +- **[Server API](wh__server_8h.md)** — server context lifecycle, configuration, and top-level request dispatch (`wolfhsm/wh_server.h`). +- **[Server Keystore API](wh__server__keystore_8h.md)** — server-side key cache and keystore operations: cache and evict slots, NVM commit, key export, and metadata access (`wolfhsm/wh_server_keystore.h`). +- **[Server Image Manager API](wh__server__img__mgr_8h.md)** — image manager configuration, verification, and verify-state queries (`wolfhsm/wh_server_img_mgr.h`). +- **[Server Cert API](wh__server__cert_8h.md)** — server-side certificate manager: trusted root storage, chain verification, attribute certificate support, and the user-injectable verify callback (`wolfhsm/wh_server_cert.h`). +- **[Server Cert Cache API](wh__server__cert__cache_8h.md)** — trusted-cert verify-result cache: hash-based result lookup and invalidation (`wolfhsm/wh_server_cert_cache.h`). diff --git a/docs/src/11-Configuration.md b/docs/src/11-Configuration.md new file mode 100644 index 000000000..88d36bb95 --- /dev/null +++ b/docs/src/11-Configuration.md @@ -0,0 +1,220 @@ +# Configuration + +wolfHSM is configured entirely at build time through a set of `WOLFHSM_CFG_XXX` preprocessor macros. This chapter is an exhaustive reference for every supported configuration macro: what it does, what its default is, and which subsystem it affects. For an overview of *how* the configuration system is wired together, see [Library Configuration](4-Architecture.md#library-configuration); this chapter focuses on the macros themselves. + +## Table of Contents + +- [How to Override Configuration Values](#how-to-override-configuration-values) +- [Core Library and Roles](#core-library-and-roles) +- [Communication and Protocol Sizing](#communication-and-protocol-sizing) +- [Time and System Services](#time-and-system-services) +- [Cryptography Features](#cryptography-features) +- [Keystore and Key Cache](#keystore-and-key-cache) +- [NVM Storage](#nvm-storage) +- [Certificate Manager](#certificate-manager) +- [Image Manager](#image-manager) +- [Custom Server Callbacks](#custom-server-callbacks) +- [DMA Support](#dma-support) +- [Authentication](#authentication) +- [Concurrency and Thread Safety](#concurrency-and-thread-safety) +- [Transports](#transports) +- [Logging Subsystem](#logging-subsystem) +- [Debug and Print Configuration](#debug-and-print-configuration) +- [Benchmark Suite](#benchmark-suite) +- [Test Harness](#test-harness) +- [Memory and Cache Porting Macros](#memory-and-cache-porting-macros) + +## How to Override Configuration Values + +There are two supported ways to override a `WOLFHSM_CFG_XXX` value: + +1. **Compiler command line**. Pass `-DWOLFHSM_CFG_XXX=value` (or `-DWOLFHSM_CFG_XXX` for boolean-style flags) when invoking the compiler. This is the simplest approach when only one or two values need to change, and is what the wolfHSM `test/` and `benchmark/` Makefiles use to map their `make` variables (`DMA=1`, `SHE=1`, `THREADSAFE=1`, etc.) onto the corresponding macros. + +2. **User configuration header (`wolfhsm_cfg.h`)**. Create a header named `wolfhsm_cfg.h` containing `#define WOLFHSM_CFG_XXX value` statements, place it on the compiler's include search path, and define the top-level `WOLFHSM_CFG` macro when invoking the compiler (`-DWOLFHSM_CFG`). When `WOLFHSM_CFG` is defined, the central `wolfhsm/wh_settings.h` header includes `wolfhsm_cfg.h` first, so user-supplied values override the internal defaults. This is the recommended approach when more than a handful of options are being customized. The reference examples under `examples/posix/` and `test/config/` follow this pattern. + +Every wolfHSM source file includes `wolfhsm/wh_settings.h` first. The header walks each `WOLFHSM_CFG_XXX` macro, supplies a default if the user has not defined one, and performs a small amount of cross-checking (for example, refusing to build `WOLFHSM_CFG_KEYWRAP` together with `WOLFHSM_CFG_NO_CRYPTO`). Every option listed in this chapter has a sensible default; the only macro that the user **must** supply is either `WOLFHSM_CFG_PORT_GETTIME` or `WOLFHSM_CFG_NO_SYS_TIME` (see [Time and System Services](#time-and-system-services)). + +## Core Library and Roles + +These macros select which halves of the wolfHSM library are compiled in, and gate features that are shared by both the client and server. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_ENABLE_CLIENT` | Undefined | If defined, compile client-side functionality (`wh_Client_*` APIs, crypto callback, message marshalling for client requests). Define this in client-only builds and in combined client/server builds. | +| `WOLFHSM_CFG_ENABLE_SERVER` | Undefined | If defined, compile server-side functionality (`wh_Server_*` APIs, request dispatcher, server-side keystore, NVM, crypto, etc.). Define this in server-only and combined builds. | +| `WOLFHSM_CFG_ENABLE_TIMEOUT` | Undefined | If defined, compile the client-side support for blocking request timeouts (`wh_Client_SetRecvTimeout` and the timeout-aware variants of the blocking client APIs). Requires a working `WH_GETTIME_US()`. | +| `WOLFHSM_CFG_NO_CRYPTO` | Undefined | If defined, build wolfHSM without any wolfCrypt dependency. All crypto-related code, message types, and key-cache crypto paths are excluded; the resulting build is useful for porting and for purely transport/NVM-focused integrations. Incompatible with `WOLFHSM_CFG_KEYWRAP`. | +| `WOLFHSM_CFG_INFOVERSION` | `"01.01.01"` | String reported by the server in response to the version-info request. Override to embed a build- or vendor-specific version stamp. | +| `WOLFHSM_CFG_INFOBUILD` | `"12345678"` | String (typically a short git/build hash) reported by the server in response to the build-info request. | + +## Communication and Protocol Sizing + +These macros control the over-the-wire sizing of the request/response protocol shared between the client and server. The same value must be used on both ends of any given client/server pair. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_COMM_DATA_LEN` | `1280` | Maximum length, in bytes, of the data payload portion of a single request or response message. This sets the upper bound on how much data a single non-DMA request (key cache, certificate verify, large block crypto, etc.) can carry; messages larger than this must be split or use a DMA variant. Larger values raise per-context RAM usage and the size of any transport-side buffers. | +| `WOLFHSM_CFG_CUSTOMCB_LEN` | `256` | Maximum size, in bytes, of a single custom-callback message payload (see [Custom Server Callbacks](#custom-server-callbacks)). Independent of `WOLFHSM_CFG_COMM_DATA_LEN`. | + +## Time and System Services + +wolfHSM relies on a microsecond-resolution system time for benchmark measurements, log timestamps, and (when enabled) request timeouts. Exactly one of the two macros below **must** be supplied by the port. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_PORT_GETTIME` | None — port must supply | Function-like macro that returns the current system time as a `uint64_t` count of microseconds. wolfHSM wraps it as `WH_GETTIME_US()` and uses it for timestamps and elapsed-time accounting. The POSIX port supplies `posixGetTime`; new ports must provide an equivalent. | +| `WOLFHSM_CFG_NO_SYS_TIME` | Undefined | If defined, all internal calls to obtain the system time return zero, removing the need for the port to supply `WOLFHSM_CFG_PORT_GETTIME`. Disables meaningful benchmark output and log timestamps; intended for very early porting work. | + +## Cryptography Features + +These macros enable or tune optional cryptographic subsystems built on top of wolfCrypt. All of them are silently ignored when `WOLFHSM_CFG_NO_CRYPTO` is defined. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_SHE_EXTENSION` | Undefined | If defined, compile the AUTOSAR SHE subsystem (SHE message types, SHE key slots, M1-M5 update protocol, SHE-specific RNG and SREG handling). Requires wolfCrypt built with AES, `WOLFSSL_CMAC`, `WOLFSSL_AES_DIRECT`, and `HAVE_AES_ECB`. | +| `WOLFHSM_CFG_KEYWRAP` | Undefined | If defined, compile the key-wrap subsystem (`wh_Client_KeyWrap*` / server counterparts). Uses AES-GCM internally and therefore requires wolfCrypt built with AES and `HAVE_AESGCM`. Incompatible with `WOLFHSM_CFG_NO_CRYPTO`. | +| `WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE` | `2000` | Maximum size, in bytes, of a key that can be wrapped or unwrapped in a single operation. Only consulted when `WOLFHSM_CFG_KEYWRAP` is defined. | +| `WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE` | `2000` | Maximum size, in bytes, of the plaintext or wrapped payload carried by a single key-wrap request. Only consulted when `WOLFHSM_CFG_KEYWRAP` is defined. | +| `WOLFHSM_CFG_GLOBAL_KEYS` | Undefined | If defined, enable the global-keys feature, allowing keys to be cached so that they are visible to every client rather than scoped to the caching client. See [Global Keys](5-Features.md#global-keys) for a full discussion of the API and security implications. | + +## Keystore and Key Cache + +These macros size the server-side key cache. The cache is split into "regular" slots (sized for common symmetric and EC keys) and "big" slots (sized for RSA-class keys); both are statically allocated. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_SERVER_KEYCACHE_COUNT` | `8` | Number of regular RAM key-cache slots on the server. | +| `WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE` | `256` | Size, in bytes, of each regular key-cache slot. | +| `WOLFHSM_CFG_SERVER_KEYCACHE_BIG_COUNT` | `1` | Number of "big" RAM key-cache slots on the server, used for large keys (e.g. RSA, ML-DSA). | +| `WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE` | `1200` | Size, in bytes, of each big key-cache slot. Should be at least the largest key the server is expected to hold (e.g. ~1024 bytes for an RSA-4096 private key). | + +## NVM Storage + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_NVM_OBJECT_COUNT` | `32` | Maximum number of objects the NVM directory can hold simultaneously (RAM directory cache *and* the on-disk directory it mirrors). Determines the upper bound on the number of keys, certificates, counters, and user objects that can coexist in NVM at one time. | +| `WOLFHSM_CFG_SERVER_NVM_FLASH_LOG` | Undefined | If defined, compile the log-structured NVM flash backend (`wh_nvm_flash_log`). When enabled it can be selected at runtime as an alternative to the regular flash backend; useful for flash parts that tolerate fewer erases or that prefer append-only update patterns. | + +## Certificate Manager + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_CERTIFICATE_MANAGER` | Undefined | If defined, compile the server-side certificate manager (trusted-root storage, chain verification, optional leaf-public-key caching). Required by `WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT`. | +| `WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT` | Undefined | If defined, also compile attribute-certificate (RFC 5755) support into the certificate manager. Requires wolfSSL built with `WOLFSSL_ACERT` and `WOLFSSL_ASN_TEMPLATE`. | +| `WOLFHSM_CFG_MAX_CERT_SIZE` | `WOLFHSM_CFG_COMM_DATA_LEN`, or `4096` when `WOLFHSM_CFG_DMA` is defined | Maximum size, in bytes, of a certificate that the manager will accept. The DMA default is larger because certificate verification requests no longer have to fit inside a single comm-buffer-sized message. | +| `WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS` | `8` | Maximum number of trusted-root NVM IDs accepted in a single `wh_Server_CertVerifyMultiRoot` request. Bounded so that the non-DMA wire request still fits within `WOLFHSM_CFG_COMM_DATA_LEN` alongside the candidate chain, and so the inline DMA request struct remains a fixed-size POD. | + +## Image Manager + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_SERVER_IMG_MGR` | Undefined | If defined, compile the server-side image manager (manifest-driven boot/runtime image verification). | +| `WOLFHSM_CFG_SERVER_IMG_MGR_MAX_IMG_COUNT` | `4` | Maximum number of images that a single image-manager configuration can track at one time. | +| `WOLFHSM_CFG_SERVER_IMG_MGR_MAX_SIG_SIZE` | `512` | Maximum signature size, in bytes, that the image manager will allocate buffer space for. The default accommodates RSA-4096; raise it when using signature schemes with larger signatures. | + +## Custom Server Callbacks + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_SERVER_CUSTOMCB_COUNT` | `8` | Number of custom-callback dispatch slots reserved on the server. Each registered callback occupies one slot; sets the upper bound on the number of distinct custom callback IDs an application can register. | + +## DMA Support + +These macros gate and tune DMA-mode crypto and large-buffer operations. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_DMA` | Undefined | If defined, compile the DMA-capable code paths: the `WH_DEV_ID_DMA` crypto device, DMA message types, pre/post access callbacks, and the address allowlist machinery. Without this macro, DMA APIs are stubbed out. | +| `WOLFHSM_CFG_DMAADDR_COUNT` | `10` | Number of entries in the DMA address allowlist used by the server to validate client-supplied DMA buffers. | +| `WOLFHSM_CFG_DMA_PTR_SIZE` | Compiler-detected (`__SIZEOF_POINTER__`) | Override the assumed DMA pointer size, in bytes (must be `4` or `8`). Auto-detection works for GCC/Clang and IAR; define this explicitly for any toolchain that does not provide `__SIZEOF_POINTER__`. | +| `WOLFHSM_CFG_DMA_ALT_PTR_SIZE` | Undefined | If defined, allows the DMA pointer size to differ from the native CPU pointer size (e.g. a 32-bit-pointer server reachable from a 64-bit-pointer client). When undefined, wh_settings.h refuses to build with a mismatched `WOLFHSM_CFG_DMA_PTR_SIZE`. | +| `WOLFHSM_CFG_DMA_CUSTOM_CLIENT_COPY` | Undefined | If defined, expose hooks that let the integrator override the client-to-server and server-to-client memory copy used during DMA requests. Useful when DMA buffers live in shared memory that requires custom invalidation or cache maintenance beyond the standard `XCACHE*` macros. | + +## Authentication + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_ENABLE_AUTHENTICATION` | Undefined | If defined, compile the authentication manager on both client and server: session establishment, the authorization gate, per-client permissions, and the pluggable auth backend (`wh_Auth_*`). All authenticated message types and the in-request session header are conditional on this macro. | + +## Concurrency and Thread Safety + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_THREADSAFE` | Undefined | If defined, compile the lock abstraction (`wh_Lock_*`) into shared server resources: the global key cache, NVM operations, the authentication manager, and any port-supplied shared crypto hardware. Requires the port to supply lock callbacks via `whLockConfig`. When undefined, all lock operations expand to no-ops with zero runtime overhead. See [Concurrency Support](5-Features.md#concurrency-support). | + +## Transports + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_TLS` | Undefined | If defined, compile the POSIX TLS transport (`posix_transport_tls`). Used by the POSIX example applications to wrap their client/server connection in wolfSSL TLS or PSK. Requires a wolfSSL build that includes the relevant TLS features (`!NO_PSK` for PSK mode, etc.). | + +## Logging Subsystem + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_LOGGING` | Undefined | If defined, compile the server-side logging subsystem (`wh_log_*`): structured log records, the ring-buffer backend, and the optional printf-style sink. Without this macro, the logging APIs are stubbed out. | +| `WOLFHSM_CFG_LOG_MSG_MAX` | `256` | Maximum size, in bytes, of a single log-message buffer, including the null terminator. Formatted log messages longer than this are truncated. | + +## Debug and Print Configuration + +These macros control the diagnostic output that wolfHSM emits at runtime. They are independent of `WOLFHSM_CFG_LOGGING`, which is a structured-event channel. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_DEBUG` | Undefined | If defined, enable the base debug print macros (`WH_DEBUG_PRINT`, `WH_DEBUG_CLIENT`, `WH_DEBUG_SERVER`). Without it, every debug print expands to `do {} while (0)`. | +| `WOLFHSM_CFG_DEBUG_VERBOSE` | Undefined | If defined, additionally enable the verbose debug macros (`WH_DEBUG_CLIENT_VERBOSE`, `WH_DEBUG_SERVER_VERBOSE`) which include function name and line number, plus the verbose hexdump helper. Implies the prerequisites of `WOLFHSM_CFG_DEBUG`. | +| `WOLFHSM_CFG_HEXDUMP` | Auto-enabled when either debug macro is set | If defined, compile `wh_Utils_Hexdump`. Pulled in implicitly by either `WOLFHSM_CFG_DEBUG` or `WOLFHSM_CFG_DEBUG_VERBOSE` (and by the verbose hexdump helper), or can be defined manually for use by integrator code. Brings in ``. | +| `WOLFHSM_CFG_PRINTF` | `printf` (``) | Function or function-like macro used as the underlying print primitive for every debug print. Must match the signature `int func(const char* fmt, ...)`. Override this for targets without a working `printf`, or to redirect debug output through a vendor logging API. | + +## Benchmark Suite + +These macros are consumed only when building the wolfHSM benchmark suite (`benchmark/`). They are documented in more detail in [6-Utilities.md](6-Utilities.md#benchmark-suite) and in `benchmark/README.md`. + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_BENCH_ENABLE` | Undefined | If defined, compile the benchmark suite into the build. Required to use any of the other `BENCH_*` macros. | +| `WOLFHSM_CFG_BENCH_MAIN` | Undefined | If defined together with `WOLFHSM_CFG_BENCH_ENABLE`, also compile the standalone benchmark `main()` (`benchmark/wh_bench_main.c`). | +| `WOLFHSM_CFG_BENCH_CRYPT_ITERS` | `100` | Number of iterations executed per symmetric-crypto benchmark (AES, HMAC, RNG, echo). | +| `WOLFHSM_CFG_BENCH_KG_ITERS` | `10` | Number of iterations executed per key-generation benchmark. | +| `WOLFHSM_CFG_BENCH_PK_ITERS` | `10` | Number of iterations executed per public-key benchmark (ECC sign/verify, RSA sign/verify, ML-DSA, Curve25519 KA). | +| `WOLFHSM_CFG_BENCH_DATA_BUFFER_SIZE` | `0x400` (`1024`) | Size, in bytes, of each of the two static input/output buffers used by the data-plane benchmarks. | +| `WOLFHSM_CFG_BENCH_DMA_BUFFER_SIZE` | `0x8000` (`32 KiB`) | Size, in bytes, of the static DMA buffer used by DMA benchmarks. Only consulted when `WOLFHSM_CFG_DMA` is defined. | +| `WOLFHSM_CFG_BENCH_CUSTOM_DATA_BUFFERS` | Undefined | If defined, the benchmark suite will not allocate its own input/output buffers and will instead use the addresses supplied by `WOLFHSM_CFG_BENCH_CUSTOM_DATA_IN_BUFFER` and `WOLFHSM_CFG_BENCH_CUSTOM_DATA_OUT_BUFFER`. Useful for placing buffers in a specific memory region (TCM, shared RAM, etc.). | +| `WOLFHSM_CFG_BENCH_CUSTOM_DATA_IN_BUFFER` | None | Address (cast to `void*`) of the user-provided input buffer. Consulted only when `WOLFHSM_CFG_BENCH_CUSTOM_DATA_BUFFERS` is defined. | +| `WOLFHSM_CFG_BENCH_CUSTOM_DATA_OUT_BUFFER` | None | Address of the user-provided output buffer; same conditions as above. | +| `WOLFHSM_CFG_BENCH_CUSTOM_DMA_BUFFER` | Undefined | If defined, evaluates to the address of a user-supplied DMA buffer used in place of the static benchmark DMA buffer. | +| `WOLFHSM_CFG_BENCH_INIT_DATA_BUFFERS` | Undefined | If defined, benchmark modules re-initialize their input/output buffers with deterministic content before each measurement. Slightly slower per iteration, but produces more reproducible numbers. | +| `WOLFHSM_CFG_BENCH_CUSTOM_PRINTF` | Undefined | If defined, overrides the benchmark suite's print primitive with the named function or macro. Required on targets that do not provide a working `printf`. Independent of `WOLFHSM_CFG_PRINTF`. | +| `WOLFHSM_CFG_BENCH_CUSTOM_TIME_FUNC` | Undefined | If defined, overrides the benchmark suite's microsecond timer. The named function must have the signature `uint64_t func(void)`. Required on non-POSIX targets, where the suite has no portable way to obtain a wall-clock time. | + +## Test Harness + +These macros are consumed only by the wolfHSM test suite (`test/`) and its supporting infrastructure. They have no effect on a release build of the library. Most are toggled by `make` variables in `test/Makefile` (e.g. `make DMA=1 THREADSAFE=1 STRESS=1`). + +| Macro | Default | Description | +|---|---|---| +| `WOLFHSM_CFG_TEST_POSIX` | Undefined | If defined, compile tests and benchmark scaffolding that depend on POSIX APIs (pthreads, sockets, file-based flash, etc.). Set automatically by the POSIX test/benchmark builds. | +| `WOLFHSM_CFG_TEST_CLIENT_ONLY` | Undefined | If defined, build the unit tests as a client-only driver that expects to connect to an externally running server (e.g. one started by another process). Set by the `CLIENT_ONLY=1` make variable. | +| `WOLFHSM_CFG_TEST_CLIENT_ONLY_TCP` | Undefined | Variant of the above that constrains the client-only build to the TCP transport, used by the integration harness that pairs a local client driver with a remote TCP server. | +| `WOLFHSM_CFG_TEST_CLIENT_LARGE_DATA_DMA_ONLY` | Undefined | If defined, tests that exercise large data payloads only run the DMA variant; the non-DMA equivalents are compiled out. Used on targets where the comm-buffer sizing cannot accommodate the large-data non-DMA path. | +| `WOLFHSM_CFG_TEST_WOLFCRYPTTEST` | Undefined | If defined, integrate the upstream `wolfcrypt/test/test.c` suite into the wolfHSM test driver and run it through the wolfHSM crypto callback. Set by the `TESTWOLFCRYPT=1` make variable. | +| `WOLFHSM_CFG_TEST_UNIT_NO_MAIN` | Undefined | If defined, suppress the default `main()` provided by the test harness, allowing the test functions to be linked into an application that supplies its own entry point. | +| `WOLFHSM_CFG_TEST_STRESS` | Undefined | If defined, compile and run the POSIX thread-safety stress test (`test/wh_test_posix_threadsafe_stress.c`). Requires `WOLFHSM_CFG_THREADSAFE` and `WOLFHSM_CFG_TEST_POSIX`. Set by the `STRESS=1` make variable. | +| `WOLFHSM_CFG_TEST_STRESS_TSAN` | Undefined | If defined, force the stress test to emit additional ThreadSanitizer annotations. Set automatically by the `make TSAN=1 STRESS=1` combination. | +| `WOLFHSM_CFG_TEST_STRESS_PHASE_ITERATIONS` | `800` | Number of iterations executed within a single phase of the threadsafe stress test. | +| `WOLFHSM_CFG_TEST_STRESS_PHASE_TIMEOUT_SEC` | Undefined (no timeout) | If defined, the stress test bails out of any single phase that has not finished within this many wall-clock seconds, reporting a timeout failure. | +| `WOLFHSM_CFG_TEST_ASSERT_FUNC` | stdlib `assert()` | If defined, overrides the macro used by the test harness to evaluate `WH_TEST_ASSERT(...)`. Useful for redirecting assertion failures into a target-specific failure reporter. | +| `WOLFHSM_CFG_TEST_ALLOW_PERSISTENT_NVM_ARTIFACTS` | Undefined | If defined, NVM-touching tests are allowed to leave persistent artifacts behind (objects, counters, keys) between runs, which lets a separate test phase verify them. Otherwise NVM is reset between tests. Used by the POSIX test build. | +| `WOLFHSM_CFG_TEST_CRYPTSVR_CFG` | Implicitly defined | The crypto-server test pulls in its standard wolfCrypt test configuration unless `NO_WOLFHSM_CFG_TEST_CRYPTSVR_CFG` is defined at build time, in which case the integrator must supply their own configuration. | +| `WOLFHSM_CFG_IS_TEST_SERVER` | Undefined | If defined, the client-side unit tests assume they are talking to a server that is running additional test-only instrumentation (exercising edge cases that cannot be triggered against a standard server). Set automatically for the combined client/server POSIX test build; should not be defined outside of that harness. | + +## Memory and Cache Porting Macros + +For completeness, wolfHSM also relies on a small number of `X*` porting macros that are normally supplied alongside the `WOLFHSM_CFG_*` macros in `wolfhsm_cfg.h` even though they do not share the prefix: + +- `XMEMFENCE()` — sequential memory fence (defaults to `__atomic_thread_fence(__ATOMIC_SEQ_CST)` on GCC/Clang; otherwise a no-op with a build warning). +- `XCACHELINE` — cache-line size in bytes (default `32`). +- `XCACHEFLUSH(p)` / `XCACHEFLUSHBLK(p, n)` — flush one line / a range; defaults are no-op and `wh_Utils_CacheFlush` respectively. +- `XCACHEINVLD(p)` / `XCACHEINVLDBLK(p, n)` — invalidate one line / a range; defaults are no-op and `wh_Utils_CacheInvalidate` respectively. + +These are the only knobs needed to make the shared-memory transports and DMA crypto paths safe on systems with separate I-cache/D-cache and on multicore SoCs without hardware cache coherency. diff --git a/docs/src/2-FAQs.md b/docs/src/2-FAQs.md new file mode 100644 index 000000000..5b1232b97 --- /dev/null +++ b/docs/src/2-FAQs.md @@ -0,0 +1,115 @@ +# FAQ + +## What is wolfHSM? + +wolfHSM is a portable, open-source client-server framework for cryptography, +non-volatile memory (NVM), and isolated secure processing. A server application runs in a trusted environment (physical HSM core, trust zone secure wordl, remote server, etc.) while client applications use a library API that can offload cryptographic and storage operations to the server. The core library exposes a client and server API to help developers stitch together their own HSM applications using a curated set of ready-to-use and secure +components. + +## Why would I need wolfHSM? + +wolfHSM was initially designed for developers targeting automotive-style SoCs that include a dedicated HSM core with secure resources such as protected memory regions and hardware cryptographic accelerators, however can generalize to any application scenario where operations must be delegated to a secure programmable secure environment. It enables applications running in an untrusted environment to securely leverage the HSM without requiring a custom, ground-up implementation, while still allowing flexibility to tailor HSM functionality to specific product requirements. + +When building security-sensitive products on these platforms, developers typically face two common approaches: + +The first option is to build everything from scratch. This is a daunting undertaking that involves developing a custom application to run on the HSM core, managing non-volatile memory, implementing cryptographic algorithms in software or writing hardware accelerator drivers, and creating a bespoke messaging or RPC framework to communicate between cores. In many cases, silicon vendors provide minimal reference code, limited documentation, and little direct support for HSM software development. + +The second option is to rely on vendor-provided, fixed-function HSM binaries. These solutions are typically expensive and expose only a narrow, predefined API. While convenient for simple use cases, they are often inflexible and difficult to adapt. Algorithm support is usually fixed or constrained to available hardware accelerators, large chunks of system memory are reserved for opaque internal use, and feature sets are limited by the vendor’s design choices. + +wolfHSM provides a practical middle ground. It offers a flexible, open-source framework that implements commonly required HSM functionality while empowering users to build their own HSM applications. The core library is hardware agnostic, extensible, auditable, and modular, exposing essential building blocks that can be composed to meet specific security and system requirements. wolfHSM is built on top of wolfCrypt, leveraging a mature, well-tested cryptographic foundation while adding key management, policy enforcement, transport abstraction, and secure storage capabilities. + +Building a custom HSM application sounds scary if you are used to a "black box" HSM solutions, but fear not! We took care of the hard part! Once you realize the freedom and flexibility that full control over the HSM-side provides, you will never want to go back. wolfHSM empowers your team to build a bespoke HSM solution that is right for YOU! + +## What is an HSM client and server? + +The HSM server is an application that runs in a trusted or secure execution environment, typically on a dedicated HSM core. It is responsible for performing cryptographic operations, managing keys, and accessing non-volatile memory (NVM) on behalf of clients. + +An HSM client is an application that consumes these services from outside of the secure environment. Clients communicate with the server using a linked client library that marshals requests over a defined transport and returns responses. This architecture allows applications to use the wolfHSM and wolfCrypt APIs transparently, without requiring direct interaction with or management of the secure execution environment. + +## How is wolfHSM different from other vendor solutions? + +wolfHSM is a library framework rather than a proprietary, fixed-function firmware. It is designed to enable users to build their own custom HSM applications instead of being constrained to a predefined feature set. The library is vendor-agnostic and open-source, with a clean abstraction layer and pluggable transports. For users who just want an out-of-the-box solution, each wolfHSM port ships with a "reference server" with default functionality that can be directly loaded onto HSM hardware and used by client applications. + +wolfHSM also differs from other solutions due to its inherent portability. Client applications can use wolfHSM and wolfCrypt APIs directly, with sensitive operations transparently offloaded to the HSM, avoiding proprietary interfaces and simplifying portability across HSM-enabled platforms. When migrating between platforms, the same high-level application logic can be retained, requiring only the registration of platform-specific transport and non-volatile storage drivers. + +## What is the difference between wolfHSM and wolfSSL/wolfCrypt + +wolfSSL/wolfCrypt is a standalone library that provides a TLS stack (wolfSSL) alongside an integrated cryptography library (wolfCrypt). wolfHSM is a separate library that is built on top of wolfSSL/wolfCrypt, using it internally as its cryptographic provider. Think about it like a remote procedure call framework for wolfCrypt with some extra HSM-specific functionality on top. Put more formally: wolfHSM wraps wolfCrypt within a client–server framework, allowing applications to offload cryptographic operations to a secure server while adding key management, non-volatile memory (NVM) management, transport abstraction, and policy enforcement layers on top of wolfSSL/wolfCrypt's standard cryptographic functionality. + +## What is the difference between wolfHSM and wolfBoot? + +wolfBoot is a secure bootloader focused on authenticated boot and firmware updates. wolfHSM is an HSM framework for runtime cryptography, key storage, and secure processing. However, wolfBoot and wolfHSM are best friends! wolfBoot has deep integration with wolfHSM and can be configured to run both as a wolfHSM client to accelerate and offload secure boot crypto and key storage, or to run on the HSM server core to verify the HSM server application. wolfHSM also knows how to verify wolfBoot images for scenarios where the HSM controls the boot flow on a multicore SoC. + +## Is wolfHSM open source? + +Yes. wolfHSM is open source under wolfSSL's dual licensing terms. See `LICENSING` for details. + +## Does wolfHSM have any external dependencies? + +The core wolfHSM library depends only on wolfSSL/wolfCrypt. Platform-specific reference ports may additionally rely on operating system or vendor libraries for startup code, configuration, transport mechanisms, and storage drivers, but no other third-party dependencies are required by the core library. + +Due to wolfHSM’s modular design, reference platform code can be easily replaced with user-provided transport or storage drivers if the supplied implementations do not meet specific system requirements. + +## Does wolfHSM require dynamic memory allocation? + +No. wolfHSM is designed to avoid dynamic memory allocation. The library itself does not ever allocate from the heap. wolfCrypt, a dependency of wolfHSM, **does** require allocation for *some* functionality, however this can be configured to use statically allocated fixed-size memory pools, eliminating the nead for a runtime heap. See [chapter04](https://www.wolfssl.com/documentation/manuals/wolfssl/chapter04.html#static-buffer-allocation-option) of the wolfSSL manual for more details on wolfCrypt static memory. When wolfCrypt is configured to use the static memory feature, wolfHSM applications are guaranteed to never allocate from the heap. + +## What is a wolfHSM "port"? + +A wolfHSM "port" contains the platform-specific **reference code** that demonstrates how to bring up and run the wolfHSM library on a given device or execution environment. While the core wolfHSM library itself is written in OS-agnostic C and contains no direct hardware or operating system dependencies, the port is what supplies the concrete implementations and drivers required to operate on real hardware. + +A typical wolfHSM port includes reference implementations for low-level services such as client–server transport mechanisms, non-volatile storage access, startup and initialization logic, and any required platform or OS configuration. On platforms with hardware cryptographic accelerators or dedicated secure resources, the port may also include example drivers or integration logic to expose those capabilities to the wolfHSM server. + +These ports are intentionally modular and illustrative rather than prescriptive. They are provided to show how wolfHSM can be integrated on a specific hardware platform, not to dictate a required architecture. Due to wolfHSM’s modular design, all port components can be easily replaced or customized with user-provided implementations while retaining the full functionality of the core wolfHSM library. + +Most wolfHSM port implementations are not open source because they interact directly with silicon vendor–specific security hardware and reference device-specific registers, peripherals, and SDK components that are typically covered by the silicon vendor's security-related non-disclosure agreements. This limitation is a practical consequence of working with secure hardware platforms and is not a policy choice by wolfSSL, Inc. For most platforms, wolfSSL only needs to verify that you hold a valid NDA for the device in question in order to share the corresponding port. If you are interested in obtaining a wolfHSM port for a specific platform, please contact facts@wolfssl.com + +## I looked at wolfHSM and don't see my specific hardware platform, where is it? + +Some platform ports are public (for example, POSIX and STM32H5). The majority of hardware ports are vendor-NDA restricted and are not in this repository. If you do not see your platform, you can either implement a new port using the transport/NVM/flash abstractions or contact wolfSSL for access to restricted ports. + +Most wolfHSM port implementations are not open source because they interact directly with silicon vendor–specific security hardware and reference device-specific registers, peripherals, and SDK components that are typically covered by the silicon vendor's security-related non-disclosure agreements. This limitation is a practical consequence of working with secure hardware platforms and is not a policy choice by wolfSSL, Inc. For most platforms, wolfSSL only needs to verify that you hold a valid NDA for the device in question in order to share the corresponding port. If you are interested in obtaining a wolfHSM port for a specific platform, please contact facts@wolfssl.com + + +## Does wolfHSM support operating system X? + +The core wolfHSM library does not rely on operating system primitives and is written in portable C99. It can run in bare-metal environments as well as on operating systems in 32-bit and larger architectures. Nothing OS-specific should prohibit usage of wolfHSM on a given platform as long as the appropriate transport and storage drivers are supplied. + +## Does wolfHSM support compiler X? + +wolfHSM (and wolfCrypt) is written in portable C and is designed to build with a wide range of embedded and cross-compilation toolchains. Official support is provided for common, flagship toolchains associated with each device port, and most additional toolchains can be enabled with minimal changes. If you would like to see a specific toolchain officially supported, please contact facts@wolfssl.com + +## Does wolfHSM support device/platform X? + +In general, yes. Reference ports currently exist for the listed [supported platforms](1-Overview.md#supported-platforms). Most platform ports are NDA-restricted by the silicon vendor and must distributed separately as their own bundle. If a port does not exist for your platform, adding support can typically be accomplished by in a matter of weeks, depending on the complexity of the device and desired use case. wolfSSL also routinely adds ports to new devices on request as part of a consulting engagment. + +If you are interested in obtaining a restricted port for a platform, or want to see a new device supported, contact facts@wolfssl.com. + +## Does wolfHSM support concurrency or multithreading? + +The core wolfHSM library does not internally use any threading or parallelism. + +The wolfHSM client API is safe to use in a multithreaded environment as long as access to each client context is properly serialized. The client context is not meant to be shared across threads without caller serialization. + +The wolfHSM server API is safe to use in a multithreaded environment as long as access to each server context is properly serialized. The server context is not meant to be shared across threads without caller serialization. Global shared resources accessible across server contexts through the server API ARE safe to use in concurrent scenarios as the server library will properly serialize access internally using the internal port-specific mutex abstraction. + +For example, two threads must NOT use the server API on one server context shared between the threads. However, it is perfectly acceptable for two threads to poll their own server contexts in parallel, even if the two threads are handling requests that reference the same keys or NVM objects. + +## What is the bare minimum my client application needs to do in order to use wolfHSM? + +At a minimum, a client application must link against the wolfHSM client library and provide a platform-specific transport that enables communication with the wolfHSM server running in a secure environment. The transport is configured and bound to the client context through a series of configuration structures. Once initialized, the application can call wolfHSM APIs or supported wolfCrypt APIs directly, with cryptographic operations, key access, and secure storage transparently offloaded to the HSM server. The client does not need to manage keys, hardware resources, or the secure execution environment. + +See [3-Quickstart.md](3-Quickstart.md) for a concise quickstart example + +## What is the bare minimum my server application needs to do in order to use wolfHSM? + +At a minimum, a server application must link against the wolfHSM server library, initialize the server context by registering the platform-specific drivers for transport, non-volatile storage, and optionally hardware crypto, then start the request-processing loop, calling `wh_Server_HandleRequestMessage` on a server context to poll for requests from the corresponding client. + +Once initialized, the server handles all cryptographic operations, key management, and secure storage on behalf of connected clients. No application-specific logic is required beyond initialization and transport handling, although the server can be extended with custom memory access patterns, callbacks, or services as needed. + +## How do I do X with wolfHSM? + +First, checkout the quickstart guide in [3-Quickstart.md](3-Quickstart.md) and the descriptions of each high level feature in [5-Features.md](5-Features.md). You can also look at the example server and client applications in `examples/posix/wh_posix_server` and `examples/posix/wh_posix_client` as well as the individual feature demo examples in `examples/demo/client`. + +If you still have questions, reach out to our engineers for direct support at support@wolfssl.com. + diff --git a/docs/src/3-Quickstart.md b/docs/src/3-Quickstart.md new file mode 100644 index 000000000..6521517f8 --- /dev/null +++ b/docs/src/3-Quickstart.md @@ -0,0 +1,255 @@ +# Quickstart + +This quickstart example demonstrates a minimal, end-to-end overview of how to bring up wolfHSM and begin using it from both the client and server sides. It walks through the essential initialization steps required to initialize and run the wolfHSM server and process requests, as well as how to connect a client application to it using a supported transport. + +This quickstart uses the built-in shared memory transport for communication, and the NVM flash implementation for NVM object storage. The actual flash drivers bound to the NVM flash layer are not defined, as they would be hardware specific. + +The client and server are shown as two separate programs, mirroring a typical deployment where the server runs on a trusted HSM core and the client runs on an application core. Because the shared memory transport exchanges data through two shared buffers, both programs must reference the *same* physical memory: on real hardware these buffers live in a shared SRAM region placed at addresses agreed upon by both cores. Fully runnable POSIX versions (using the TCP and POSIX shared memory transports) are provided in [Examples and Demos](7-Examples.md). + +Enough with the high level concepts and jargon, let's dive straight into the code... + + +## Client Quickstart + +```c +#include /* for printf() */ +#include /* for strlen(), memcmp() */ + +#include "wolfhsm/wh_error.h" /* WH_ERROR_OK and friends */ +#include "wolfhsm/wh_client.h" /* Client API (includes comm config) */ +#include "wolfhsm/wh_transport_mem.h" /* Shared-memory transport */ + +#define SHARED_BUFFER_SIZE 4096 + +/* Request and response buffers shared with the server core. The client writes + * requests to gReqBuffer and reads responses from gRespBuffer. On real hardware + * these must be placed (e.g. via the linker) in a memory region shared with the + * server, so that both cores reference the identical buffers. */ +uint8_t gReqBuffer[SHARED_BUFFER_SIZE]; +uint8_t gRespBuffer[SHARED_BUFFER_SIZE]; + +int main(void) +{ + int rc; + + /* 1. Describe the shared-memory transport and its callback table */ + whTransportMemConfig transportCfg[1] = {{ + .req = gReqBuffer, + .req_size = SHARED_BUFFER_SIZE, + .resp = gRespBuffer, + .resp_size = SHARED_BUFFER_SIZE, + }}; + whTransportClientCb transportCb[1] = {WH_TRANSPORT_MEM_CLIENT_CB}; + whTransportMemClientContext transportCtx[1] = {0}; + + /* 2. Bind the transport to the client comm configuration */ + whCommClientConfig commCfg[1] = {{ + .transport_cb = transportCb, + .transport_context = (void*)transportCtx, + .transport_config = (void*)transportCfg, + .client_id = 1, /* unique client identifier (1-15) */ + }}; + + /* 3. Assemble the client configuration */ + whClientConfig clientCfg[1] = {{ + .comm = commCfg, + }}; + + /* 4. Initialize the client context */ + whClientContext client[1] = {0}; + rc = wh_Client_Init(client, clientCfg); + if (rc != WH_ERROR_OK) { + printf("wh_Client_Init failed: %d\n", rc); + return 1; + } + + /* 5. Connect to the server (exchanges client and server IDs) */ + rc = wh_Client_CommInit(client, NULL, NULL); + if (rc != WH_ERROR_OK) { + printf("wh_Client_CommInit failed: %d\n", rc); + return 1; + } + + /* 6. Use the client API. Here we send a blocking echo request. */ + { + const char sendBuffer[] = "Hello, wolfHSM!"; + uint16_t sendLen = (uint16_t)strlen(sendBuffer); + char recvBuffer[WOLFHSM_CFG_COMM_DATA_LEN] = {0}; + uint16_t recvLen = 0; + + rc = wh_Client_Echo(client, sendLen, sendBuffer, &recvLen, recvBuffer); + if (rc == WH_ERROR_OK && recvLen == sendLen && + memcmp(sendBuffer, recvBuffer, sendLen) == 0) { + printf("Server echoed: %.*s\n", recvLen, recvBuffer); + } + else { + /* Error: we weren't echoed back exactly what we sent */ + printf("Echo failed: rc=%d\n", rc); + } + } + + /* 7. Close the connection and release the client context */ + (void)wh_Client_CommClose(client); + (void)wh_Client_Cleanup(client); + + return 0; +} +``` + +## Server Quickstart + +```c +#include /* for printf() */ + +#include "wolfhsm/wh_error.h" /* WH_ERROR_OK and friends */ +#include "wolfhsm/wh_server.h" /* Server API */ +#include "wolfhsm/wh_transport_mem.h" /* Shared-memory transport */ +#include "wolfhsm/wh_nvm.h" /* NVM abstraction */ +#include "wolfhsm/wh_nvm_flash.h" /* NVM-on-flash implementation */ +#include "wolfhsm/wh_flash.h" /* whFlashCb interface */ + +#define SHARED_BUFFER_SIZE 4096 + +/* The same shared buffers referenced by the client (see the client example). + * The server reads requests from gReqBuffer and writes responses to + * gRespBuffer. Both programs must reference the identical physical memory. */ +uint8_t gReqBuffer[SHARED_BUFFER_SIZE]; +uint8_t gRespBuffer[SHARED_BUFFER_SIZE]; + +/* The low-level flash driver is supplied by the platform port: a whFlashCb + * callback table plus port-defined context and configuration structures. The + * actual driver is hardware-specific and omitted here. On a host you can drop + * in the bundled RAM simulator (wolfhsm/wh_flash_ramsim.h, WH_FLASH_RAMSIM_CB) + * to run this example without real flash. */ +extern const whFlashCb myFlashCb; +extern void* myFlashContext; +extern const void* myFlashConfig; + +int main(void) +{ + int rc; + + /* 1. Describe the shared-memory transport and its callback table */ + whTransportMemConfig transportCfg[1] = {{ + .req = gReqBuffer, + .req_size = SHARED_BUFFER_SIZE, + .resp = gRespBuffer, + .resp_size = SHARED_BUFFER_SIZE, + }}; + whTransportServerCb transportCb[1] = {WH_TRANSPORT_MEM_SERVER_CB}; + whTransportMemServerContext transportCtx[1] = {0}; + + /* 2. Bind the transport to the server comm configuration */ + whCommServerConfig commCfg[1] = {{ + .transport_cb = transportCb, + .transport_context = (void*)transportCtx, + .transport_config = (void*)transportCfg, + .server_id = 1, /* server identifier */ + }}; + + /* 3. Build the NVM context on top of the NVM-flash layer and flash driver */ + whNvmFlashConfig nvmFlashCfg[1] = {{ + .cb = &myFlashCb, + .context = myFlashContext, + .config = myFlashConfig, + }}; + whNvmFlashContext nvmFlashCtx[1] = {0}; + whNvmCb nvmCb[1] = {WH_NVM_FLASH_CB}; + + whNvmConfig nvmCfg[1] = {{ + .cb = nvmCb, + .context = nvmFlashCtx, + .config = nvmFlashCfg, + }}; + whNvmContext nvm[1] = {0}; + + rc = wh_Nvm_Init(nvm, nvmCfg); + if (rc != WH_ERROR_OK) { + printf("wh_Nvm_Init failed: %d\n", rc); + return 1; + } + + /* 4. Initialize wolfCrypt and seed the server's crypto context RNG */ + whServerCryptoContext crypto[1] = {0}; + wolfCrypt_Init(); + wc_InitRng_ex(crypto->rng, NULL, INVALID_DEVID); + + /* 5. Assemble the server configuration */ + whServerConfig serverCfg[1] = {{ + .comm_config = commCfg, + .nvm = nvm, + .crypto = crypto, + .devId = INVALID_DEVID, /* software crypto; use a crypto-callback + * devId to offload to hardware */ + }}; + + /* 6. Initialize the server context */ + whServerContext server[1] = {0}; + rc = wh_Server_Init(server, serverCfg); + if (rc != WH_ERROR_OK) { + printf("wh_Server_Init failed: %d\n", rc); + return 1; + } + + /* 7. Mark the transport connected once the shared memory is ready */ + wh_Server_SetConnected(server, WH_COMM_CONNECTED); + + /* 8. Service client requests. HandleRequestMessage is non-blocking and + * returns WH_ERROR_NOTREADY when no request is pending. */ + while (1) { + rc = wh_Server_HandleRequestMessage(server); + if (rc != WH_ERROR_OK && rc != WH_ERROR_NOTREADY) { + break; /* fatal transport error */ + } + } + + wh_Server_Cleanup(server); + return 0; +} +``` + +## Deep Dive + +Now that you have seen the code, lets dive a little deeper and explain what is going on. We will use the server-side code as an example unless otherwise noted, as it also contains the majority of the steps necessary to initialize the client side. + +### Transport Configuration + +The transport is responsible for moving raw bytes between the client and the server. This example uses the built-in two-buffer shared-memory transport declared in `wolfhsm/wh_transport_mem.h`: the client writes requests into one buffer and reads responses from the other, while the server does the reverse. + +`whTransportMemConfig` binds the two shared buffers and their sizes, while the `WH_TRANSPORT_MEM_SERVER_CB` macro (and `WH_TRANSPORT_MEM_CLIENT_CB` on the client) populates a callback table that adapts this concrete transport to the abstract transport interface that the comm layer consumes. Because both sides operate on the same two buffers, the memory referenced by `.req` and `.resp` must be physically shared between the client and server. wolfHSM ships with additional transports (TCP and POSIX shared memory) that plug in the exact same way by swapping the config, context, and callback structures. See [Communication Layer and Transports](5-Features.md#communication-layer-and-transports) for the full list and details. + +### Comm Layer Configuration + +The communication layer sits directly above the transport and implements the request/response protocol: message framing, sequence numbers, and endianness handling. The server binds its transport into a `whCommServerConfig`, providing the callback table, the transport context, the transport config, and a `server_id`. The client's `whCommClientConfig` is analogous but carries a `client_id` instead. + +The `client_id` is significant: the server learns each client's identifier during the connection handshake, and uses it to keep per-client key caches and other resources isolated from one another. The request/response protocol these structures drive is the **split-transaction, non-blocking** model described in [Client/Server Communication](4-Architecture.md#clientserver-communication). + +### NVM Configuration + +Non-volatile storage is layered. From the bottom up: + +1. A platform **flash driver** implements the `whFlashCb` callbacks (read/program/erase) for a specific device. This is the hardware-specific piece omitted from the example above; wolfHSM also bundles a RAM-backed simulator (`wolfhsm/wh_flash_ramsim.h`, `WH_FLASH_RAMSIM_CB`) that is handy for running on a host. +2. The **NVM flash** implementation (`whNvmFlashConfig` + `WH_NVM_FLASH_CB`) turns that raw flash into an object store with wear-aware, power-fail-safe semantics. It is bound to the flash driver via the `.cb`, `.context`, and `.config` fields. +3. The generic **NVM context** (`whNvmConfig` + `whNvmContext`) presents the high-level object API to the rest of the server. `wh_Nvm_Init()` initializes it from the configuration before the server is started. + +This is the same callback-driven layering used throughout wolfHSM, so the NVM flash backend can be swapped for another NVM implementation without touching the server code. See [Non-Volatile Memory (NVM)](5-Features.md#non-volatile-memory-nvm) and [Flash Abstraction](5-Features.md#flash-abstraction). + +### wolfCrypt Initialization + +The server performs cryptographic operations with wolfCrypt, so `wolfCrypt_Init()` must be called before `wh_Server_Init()`. The server's `whServerCryptoContext` owns a wolfCrypt random number generator that must be seeded with `wc_InitRng_ex()`. + +Passing `INVALID_DEVID` makes the server perform crypto in software. To offload to a hardware accelerator instead, register a wolfCrypt crypto callback and pass its device ID both to `wc_InitRng_ex()` and to the `.devId` field of the server configuration. Note that the client does *not* initialize wolfCrypt for offloaded operations: it transparently routes wolfCrypt API calls to the server by using the `WH_DEV_ID` device ID. See [Cryptography and wolfCrypt Integration](5-Features.md#cryptography-and-wolfcrypt-integration). + +### Initializing the Server Context + +`whServerConfig` aggregates the three pieces configured above — the comm config (`.comm_config`), the initialized NVM context (`.nvm`), and the crypto context (`.crypto`) — into a single configuration. `wh_Server_Init()` wires them into the `whServerContext`, which from then on serves as the handle for all server operations. + +After initialization, the server must be told when the underlying transport is actually ready for communication by calling `wh_Server_SetConnected(server, WH_COMM_CONNECTED)`. Until the server is connected, `wh_Server_HandleRequestMessage()` returns `WH_ERROR_NOTREADY`. On the client side, the corresponding steps are simply `wh_Client_Init()` followed by `wh_Client_CommInit()`. + +### Processing Requests + +Once connected, the server services requests by repeatedly calling `wh_Server_HandleRequestMessage()`. This function is non-blocking: it returns `WH_ERROR_NOTREADY` when no request is pending, processes and responds to a request when one is available, and only returns a hard error when the underlying transport fails. Errors that occur while *servicing* a request are reported back to the client inside the response message rather than failing the call, so the server loop only needs to break on fatal transport errors. + +In this example the loop polls in a busy wait, which is fine for illustration; on real hardware this poll is typically driven by an inter-core interrupt so the HSM core can sleep between requests. + +The client side mirrors this. `wh_Client_Echo()` used above is a blocking convenience helper that sends a request and polls for the matching response internally. For finer control, every client operation also exposes the underlying non-blocking split-transaction API as a `wh_Client_*Request()` / `wh_Client_*Response()` pair (for example, `wh_Client_EchoRequest()` and `wh_Client_EchoResponse()`), letting the caller send a request and poll for its response separately. diff --git a/docs/src/4-Architecture.md b/docs/src/4-Architecture.md new file mode 100644 index 000000000..6045f9fee --- /dev/null +++ b/docs/src/4-Architecture.md @@ -0,0 +1,161 @@ +# Architecture + +## Client/server libraries + +wolfHSM is built as two cooperating libraries: + +- Client library: linked into a client application and exposes an API for key management, NVM object storage, certificate chain verification, and other core functionality. It also integrates with wolfCrypt and allows client applications to use the wolfCrypt cryptography API directly with transparent offload to the HSM server. +- Server library: linked into a server application running in a trusted environment, typically on an isolated CPU core, and owns the actual cryptographic material, key caches, NVM storage, and hardware integration points. + +## Code and API organization + +The codebase has the following organizational layout: + +- `wolfhsm/`: Header files, both external and internal +- `src/`: Core library source code +- `test/`: Test suite +- `benchmark/`: Benchmark suite +- `examples/`: Example POSIX server and client applications, as well as client-facing demo functions for core features +- `port/`: Public platform port code. Currently only contains POSIX port, as all hardware platforms are behind vendor NDA +- `tools/`: Tools and utilities +- `docs/`: Documentation + +### Client API + +The core client API is defined in `wolfhsm/wh_client.h`. Public client functions are prefixed with `wh_Client_`. Most client applications only need the interfaces provided by this header and the wolfCrypt API. The AUTOSAR SHE client compatibility interface is defined separately in `wolfhsm/wh_client_she.h`. + +A client’s connection to a server is represented by the `whClientContext` structure. After initialization with the appropriate transport configuration, this context serves as the handle for all client-side wolfHSM operations, ensuring that requests and responses are routed through the correct communication interface. For most operations, state is held by the client, unless the state is cryptographically sensitive. + +### Server API + +The server API is divided across multiple header files in `wolfhsm/wh_server*.h` and may be consumed on a per-module basis. Core server functionality like initialization, teardown, and request processing are defined in `wolfhsm/wh_server.h`. Additional capabilities are exposed through module-specific headers. + +A server’s connection to a client is represented by the `whServerContext` structure. Unlike the client context, which primarily encapsulates a single communication interface, the server context aggregates a wider set of stateful and platform-specific resources. This includes runtime-initialized callbacks for module implementations, persistent and transient state required to service client requests, and integrations with platform services such as NVM, DMA access, and logging. Some of these resources and state are per-client and others are global. + +Once initialized with the appropriate use-case-specific configuration, the server context serves as the central handle for all server-side wolfHSM operations, ensuring that requests and responses are routed through the correct communication interface and processed using the appropriate module implementations and platform resources. + +### Important Source Modules + +- `src/wh_client*.c`: client-side API, request construction, and crypto callback integration. +- `src/wh_server*.c`: server-side functionality including request processing, crypto, key cache control, etc. +- `src/wh_comm.c`: high level communication layer abstraction for server and client. +- `src/wh_transport*.c`: low level transport abstraction with pluggable back-ends (memory buffers, TCP, POSIX shared memory). +- `src/wh_message*.c`: protocol encoding/decoding, fixed-size message framing, and command identifiers. +- `src/wh_nvm*.c`: non-volatile storage abstraction with pluggable back-ends. + +### Compiling Source Files + +When building wolfHSM for the client or server, it is safe to wildcard include all C source files in the `src/` directory and pass them to your compiler. All wolfHSM code is internally protected by library configuration macros ensuring that only relevant code will be conditionally compiled based on the supplied configuration. For more information see [Library Configuration](#library-configuration). + +## Client/Server Communication + +All client/server communication follows a strict request/response model. A request is a message sent from a client to a server, and a response is the corresponding reply from the server to that specific request. Each response uniquely maps to a single outstanding request. + +### Client Communication + +The client API uses a **split-transaction, non-blocking** communication model composed of two explicit phases: + +1. **Send Request**: The client invokes an API function to transmit a request to the server. The function returns immediately and may report a “not ready” condition if the underlying transport is unable to accept the request, in which case the caller is expected to retry. + +2. **Receive Response**: The client invokes the corresponding receive API to poll for the server’s response to a previously sent request. This operation is non-blocking and may return “not ready” if the response has not yet arrived. + +Each request is tagged with a sequence number that is internally validated against the received response, ensuring that the client processes the correct reply and does not consume a stale or mismatched message. + +### Server Communication + +The server is designed to operate within an event-driven loop for each connected client, continuously polling for pending requests. + +After initialization, the server repeatedly calls `wh_Server_HandleRequestMessage()` on a given `whServerContext` to check for incoming requests in a non-blocking manner. The function returns “not ready” when no request is pending. When a request is available, it is processed internally and dispatched to the appropriate module handler, which generates the corresponding response message that is sent back to the client. + +Errors that occur while servicing a client request are treated as non-fatal and are reported to the client as part of the response. The `wh_Server_HandleRequestMessage()` function itself only fails when an error occurs in the underlying communication stack while receiving or transmitting a request. + +### Communication Stack + +wolfHSM's communication is structured as a two-layer stack that sits beneath every client and server API. The **comm layer** owns the request/response framing described above: the versioned packet header, sequence numbering, and the send/receive primitives that the higher-level APIs build on. Beneath it, the **transport layer** is a thin, pluggable interface whose only responsibility is to move a single packet between the two endpoints. The comm layer reaches the underlying medium exclusively through this interface, so the same client and server code runs unchanged whether packets travel over shared memory on a multi-core SoC, a hardware mailbox, TCP, or anything else — supporting a new medium means supplying a transport, not modifying the core library. + +The stack is symmetric across the client/server boundary: + +``` + client server + ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ + │ Client API │<──>│ Comm layer │<──>│ Transport │<══════>│ Transport │<──>│ Comm layer │<──>│ Server API │ + └────────────┘ └────────────┘ └────────────┘ │ └────────────┘ └────────────┘ └────────────┘ + │ + underlying transport medium + (shared memory, TCP, mailbox, …) +``` + +For the packet header layout, sequence-numbering rules, and the reference transports wolfHSM ships with, see [Communication Layer and Transports](5-Features.md#communication-layer-and-transports). + +## Library Configuration + +wolfHSM is configured at build time using a set of `WOLFHSM_CFG_XXX` preprocessor macros. These configuration macros are used to: + +- Enable or disable library features +- Control sizing and resource limits (e.g. message buffer size, NVM object count, key cache size, etc.) +- Specify user-supplied override functions for platform-specific behavior (e.g. cache operations, system time retrieval, `printf` and stdout functionality, etc.) + +Each configuration option provides a sane default that allows the library to build and operate in its default configuration without user intervention. All options may be overridden at build time to customize the library for a specific use case or platform. + +The only configuration that **must** be supplied by the user is a mechanism to obtain the system time, either by defining `WOLFHSM_CFG_PORT_GETTIME` or by explicitly disabling system time support with `WOLFHSM_CFG_NO_SYS_TIME`. In practice, this requirement is typically handled by the port layer provided with a given platform and is only relevant when porting wolfHSM to a new environment. + +Configuration macros may be provided directly on the compiler command line, or more commonly, via a user-defined configuration header named `wolfhsm_cfg.h`. When using a configuration header (recommended when more than a small number of options are being customized), the user must: + +1. Create a file named `wolfhsm_cfg.h` containing `#define` statements for the desired `WOLFHSM_CFG_XXX` values +2. Ensure the file resides in a directory included in the compiler’s header search path (for example, via `-I`) +3. Define the `WOLFHSM_CFG` macro when invoking the compiler (for example, `-DWOLFHSM_CFG`) + +Once these steps are completed, wolfHSM will use the user-defined configuration values in place of the internal defaults. + +Note that the default base configuration and global library settings are defined in `wolfhsm/wh_settings.h`. Every wolfHSM source file includes this header first to establish the configuration environment. When `WOLFHSM_CFG` is defined, `wh_settings.h` conditionally includes `wolfhsm_cfg.h`, ensuring that user-supplied overrides are applied before any internal defaults are used. + +For an exhaustive list of all wolfHSM config macros, see [11-Configuration.md](11-Configuration.md). + +## Internals Deep Dive: Modular Architecture + +wolfHSM is highly modular and built around an internal architecture that uses generic interfaces for core subsystems such as communications/transport, non-volatile memory (NVM) storage, and logging. Concrete implementations of these interfaces are selected and bound at runtime through a configuration structure supplied upon initialization. This design enables independent selection of both software and hardware implementations for each subsystem. + +Put another way: Instead of binding to a single implementation or hardware driver stack, each system-facing module exposes an interface that is implemented via callbacks. The core library depends only on these interfaces, not on any platform specifics. + +The following snippet is an abstract example of how this looks for an arbitrary wolfHSM "component": + +```c +#include "wolfhsm/component.h" /* wolfHSM abstract API reference for a component */ +#include "port/vendor/mycomponent.h" /* Platform specific definitions of configuration + * and context structures, as well as declarations of + * callback functions */ + +/* Provide the lookup table (vtable) for function callbacks for mycomponent. + *Note the type is the abstract type provided in wolfhsm/component.h */ +whComponentCb my_cb = {MY_COMPONENT_CB}; + +/* Fixed configuration data. Note that pertinent data is copied out of the structure + * during init() */ +const myComponentConfig my_config = { + .my_number = 3, + .my_string = "This is a string", +} + +/* Static allocation of the dynamic state of the myComponent. */ +myComponentContext my_context = {0}; + +/* Initialization of the component using platform-specific callbacks */ +const whComponentConfig comp_config = { + .cb = my_cb, + .context = my_context, + .config = my_config + }; +whComponentContext comp_context = {0}; +int rc = wh_Component_Init(comp_context, comp_config); + +rc = wh_Component_DoSomething(comp_context, 1, 2, 3); +rc = wh_Component_CleanUp(comp_context); +``` + +Modules in wolHSM that are currently implemented in this way are: + +- Transports: define how bytes move between client and server. The core library only cares about send/receive semantics and state management. +- NVM: define how persistent storage on the server is formatted and accessed. +- Logging: Internal logging uses configurable logging backends that implement their own schema and read/write/erase/export operations. +- Mutex abstraction: Internal resource serialization is written against a generic "lock" interface that uses port-specific mutex abstractions. + diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md new file mode 100644 index 000000000..8f71f38f8 --- /dev/null +++ b/docs/src/5-Features.md @@ -0,0 +1,1085 @@ +# Features + +This chapter provides a detailed overview of the high level features that wolfHSM provides. Each section is intended to convey *what* a given feature does, what functionality it exposes, and what a developer can build with it. Concrete API usage and signatures are deferred to the client and server API references in [9-API-docs-client.md](9-API-docs-client.md) and [10-API-docs-server.md](10-API-docs-server.md). + +## Table of Contents + +- [Cryptography and wolfCrypt Integration](#cryptography-and-wolfcrypt-integration) + - [Transparent Offload via Crypto Callbacks](#transparent-offload-via-crypto-callbacks) + - [Supported Algorithms](#supported-algorithms) + - [Referencing Keys by ID](#referencing-keys-by-id) + - [Hardware Acceleration and Crypto Affinity](#hardware-acceleration-and-crypto-affinity) + - [Blocking and Non-Blocking Interfaces](#blocking-and-non-blocking-interfaces) + - [Crypto Operation Timeouts](#crypto-operation-timeouts) +- [Non-Volatile Memory (NVM)](#non-volatile-memory-nvm) + - [High Level NVM Interface](#high-level-nvm-interface) + - [Object Metadata and Access Attributes](#object-metadata-and-access-attributes) + - [NVM Backends](#nvm-backends) + - [Flash Abstraction](#flash-abstraction) +- [Keystore](#keystore) + - [Key Cache, Key IDs, and NVM Backing Store](#key-cache-key-ids-and-nvm-backing-store) + - [Global Keys](#global-keys) + - [Wrapped Keys](#wrapped-keys) + - [Key Usage Policies](#key-usage-policies) +- [Certificate Management](#certificate-management) + - [Trusted Root Storage](#trusted-root-storage) + - [Chain Verification](#chain-verification) + - [Caching the Leaf Public Key](#caching-the-leaf-public-key) + - [DMA Variants](#dma-variants) + - [Trusted Certificate Verify Cache](#trusted-certificate-verify-cache) + - [Attribute Certificate (Acert) Support](#attribute-certificate-acert-support) +- [Communication Layer and Transports](#communication-layer-and-transports) + - [Communication Layer](#communication-layer) + - [Transport Backends](#transport-backends) +- [DMA Support](#dma-support) + - [The DMA Crypto Device (`WH_DEV_ID_DMA`)](#the-dma-crypto-device-wh_dev_id_dma) + - [Pre-Access and Post-Access Callbacks](#pre-access-and-post-access-callbacks) + - [Address Allowlisting](#address-allowlisting) + - [32-bit vs. 64-bit Address Handling](#32-bit-vs-64-bit-address-handling) +- [AUTOSAR SHE Subsystem](#autosar-she-subsystem) + - [Client API and Command Set](#client-api-and-command-set) + - [SHE Key Slots and the wolfHSM Keystore](#she-key-slots-and-the-wolfhsm-keystore) + - [Encrypted Key Update Protocol (M1–M5)](#encrypted-key-update-protocol-m1m5) + - [Secure Boot](#secure-boot) + - [Deterministic PRNG](#deterministic-prng) + - [Status Register (SREG)](#status-register-sreg) + - [Integration with the Rest of wolfHSM](#integration-with-the-rest-of-wolfhsm) +- [Non-Volatile Monotonic Counters](#non-volatile-monotonic-counters) + - [Counter Semantics](#counter-semantics) + - [Counter Identifiers and Storage](#counter-identifiers-and-storage) + - [Client API](#client-api) +- [Image Manager](#image-manager) + - [Image Configuration](#image-configuration) + - [Verify Methods](#verify-methods) + - [Verify Actions](#verify-actions) + - [wolfBoot Image Support](#wolfboot-image-support) + - [In-Place Access via DMA](#in-place-access-via-dma) +- [Custom Callbacks](#custom-callbacks) + - [Server-Side Registration and Dispatch](#server-side-registration-and-dispatch) + - [Client-Side Invocation](#client-side-invocation) + - [Request and Response Messages](#request-and-response-messages) + - [Constraints](#constraints) + - [Example](#example) +- [Concurrency Support](#concurrency-support) + - [Per-Context Threading Model](#per-context-threading-model) + - [The Lock Abstraction](#the-lock-abstraction) + - [Concurrent Server Pattern](#concurrent-server-pattern) + - [Transports and Concurrency](#transports-and-concurrency) + - [Crypto Under Concurrency](#crypto-under-concurrency) +- [Authentication Manager](#authentication-manager) + - [Authentication Methods](#authentication-methods) + - [Sessions and the Authorization Gate](#sessions-and-the-authorization-gate) + - [Permissions](#permissions) + - [Pluggable Backend](#pluggable-backend) + +## Cryptography and wolfCrypt Integration + +wolfHSM uses wolfCrypt as its cryptographic provider on both sides of the client/server boundary. On the client, applications call the standard wolfCrypt API directly and the operation runs transparently on the server. On the server, the full set of wolfCrypt's software algorithms is available out of the box, with optional acceleration from port-supplied hardware drivers. + +### Transparent Offload via Crypto Callbacks + +Clients can use the wolfCrypt API directly because of wolfCrypt's [crypto callback (cryptoCb)](https://www.wolfssl.com/documentation/manuals/wolfssl/chapter06.html#crypto-callbacks-cryptocb) framework. Crypto callbacks let you override selected algorithms at runtime by registering a callback against a device identifier (`devId`). Most wolfCrypt functions take a `devId`, and when it matches a registered device the call is dispatched through that callback instead of running locally. + +The wolfHSM client library registers a crypto callback that turns each supported wolfCrypt call into a request/response exchange with the server. The same wolfCrypt source can be retargeted to the HSM by changing only the `devId` — nothing else in the application changes. wolfHSM defines `WH_DEV_ID` for the server crypto device, and `WH_DEV_ID_DMA` when DMA support is compiled in; passing either to a wolfCrypt function routes the operation to the server. + +In effect the callback layer is a transparent RPC framework for wolfCrypt: clients write ordinary wolfCrypt code, and wolfHSM handles request marshaling, transport, dispatch, and response delivery underneath. It also makes prototyping easy — develop against a local wolfCrypt instance, then switch to the HSM by toggling one parameter once the server is available. + +### Supported Algorithms + +The wolfHSM server exposes the full set of wolfCrypt software algorithms, and the client crypto callback supports transparent offload for the most commonly used algorithm families: + +- **Symmetric ciphers**: AES in CBC, CTR, ECB, GCM, and CCM modes; AES key wrap +- **Hashing**: SHA-1, SHA-2 (SHA-224, SHA-256, SHA-384, SHA-512), SHA-3 +- **Message authentication**: HMAC (over the supported hash functions) and CMAC +- **Asymmetric**: RSA (encryption, signing, key generation), ECC (ECDSA, ECDH), Ed25519, Curve25519 +- **Random number generation**: DRBG/RNG backed by the server's entropy source +- **Post-quantum cryptography**: ML-DSA (FIPS 204) and ML-KEM (FIPS 203) + +For the authoritative list of algorithms, parameter ranges, and options, see the [wolfCrypt API reference](https://www.wolfssl.com/documentation/manuals/wolfssl/index.html). An algorithm not yet wired through the crypto callback can still be used locally against the client's own wolfCrypt instance — only operations dispatched to `WH_DEV_ID` are offloaded. + +### Referencing Keys by ID + +When a client offloads an operation, it usually does *not* send the key with the request. The key lives in the server [keystore](#keystore) under a numeric **key ID**, and the client refers to it by that ID alone. The bytes never cross the client/server boundary — the client holds only the ID, and the server looks up the material when it runs the operation. This is what lets an HSM guard a private key while still letting a client sign or decrypt with it. + +A wolfCrypt key object is tied to a server-side key ID with a per-algorithm `SetKeyId` call. Every offloaded algorithm has one — `wh_Client_RsaSetKeyId`, `wh_Client_EccSetKeyId`, `wh_Client_AesSetKeyId`, `wh_Client_Ed25519SetKeyId`, `wh_Client_Curve25519SetKeyId`, `wh_Client_CmacSetKeyId`, `wh_Client_MlDsaSetKeyId`, and so on (each with a matching `GetKeyId`). You initialize an ordinary wolfCrypt key struct with `WH_DEV_ID`, associate it with a key ID instead of loading key bytes, and call wolfCrypt as usual: + +```c +RsaKey rsa; +whKeyId keyId = 4; /* keyId 4 must be resident on the server */ + +/* Initialize the RSA key context to use wolfHSM offload via WH_DEV_ID */ +wc_InitRsaKey_ex(&rsa, NULL, WH_DEV_ID); + +/* Bind the key object to the server-side key */ +wh_Client_RsaSetKeyId(&rsa, keyId); + +/* Use wolfCrypt as normal — signing runs on the server and the + * private key never leaves the HSM */ +sigLen = wc_RsaSSL_Sign(msg, msgLen, sig, sizeof(sig), &rsa, &rng); +``` + +The same ID can name a key the client just cached, one provisioned into NVM at the factory, or one the server generated and never exported — the client uses it the same way in every case. How IDs are assigned and structured is covered under [Keystore](#keystore). + +### Hardware Acceleration and Crypto Affinity + +Many of the platforms wolfHSM targets ship a dedicated crypto accelerator alongside their secure core. The server can use these accelerators per-algorithm through the same crypto callback mechanism: a port-supplied callback, registered at server init, redirects supported operations to the vendor's hardware driver, and anything not implemented in hardware falls back to wolfCrypt software. Which algorithms are accelerated depends on the silicon and is documented in each platform's port. + +Clients control whether a given crypto request should prefer hardware or software execution through the **crypto affinity** API. Affinity is a per-client setting with two values: + +- `WH_CRYPTO_AFFINITY_HW` (default): the server attempts to execute the operation using the configured hardware crypto device. If the server was not configured with a valid hardware device ID, or if the requested algorithm is not implemented in hardware, the request transparently falls back to wolfCrypt's software implementation. +- `WH_CRYPTO_AFFINITY_SW`: the server always executes the operation using wolfCrypt's software implementation, bypassing any registered hardware device. + +Affinity is stored on the client and sent in the header of every crypto request, so a change takes effect on the next operation with no extra round-trip. + +The affinity is set and queried using `wh_Client_SetCryptoAffinity` and `wh_Client_GetCryptoAffinity`. See the [client API reference](9-API-docs-client.md) for the precise signatures. + +### Blocking and Non-Blocking Interfaces + +Operations invoked through the standard wolfCrypt API are **blocking**: the call does not return until the server responds (or the transport fails). This matches what applications already expect from wolfCrypt and is the simplest way to port existing code to wolfHSM. + +For non-blocking, split-transaction behavior, wolfHSM also exposes native client crypto APIs in `wolfhsm/wh_client_crypto.h` that follow the same send-request / receive-response pattern as the rest of the client API. These come as paired `wh_Client_Request` and `wh_Client_Response` calls, so a caller can issue a request, do other work, and poll for the result later. They cover a subset of algorithms (see the [client API reference](9-API-docs-client.md)), and blocking and non-blocking calls must not be interleaved on the same `whClientContext` while a request is outstanding. + +> **Note**: Because the standard wolfCrypt API is blocking on the client side, applications that need to overlap crypto work with other activity should either use the native non-blocking client API, or run their wolfCrypt calls from a dedicated thread with its own `whClientContext`. See [Concurrency Support](#concurrency-support) for guidance on multi-threaded usage. + +### Crypto Operation Timeouts + +A blocking call waits until the server responds (see [Blocking and Non-Blocking Interfaces](#blocking-and-non-blocking-interfaces)). If the server never answers — it crashed, or the transport stalled — the call would otherwise wait forever. The optional request-timeout feature (`WOLFHSM_CFG_ENABLE_TIMEOUT`) bounds that wait: once the timeout elapses the call returns `WH_ERROR_TIMEOUT` instead of hanging, and the client context stays usable so the application can recover or retry. It is purely client-side — the server is unaware of it — and applies to any blocking request/response, offloaded crypto being the main case. + +A timeout is configured per client through the `respTimeoutConfig` field of the `whCommClientConfig`. The client starts the timer when it sends a request, checks it while polling for the response, and stops it once the response arrives. Setting `respTimeoutConfig` to `NULL` disables the timeout (the client waits indefinitely), so the feature can be compiled in but left off for individual clients. + +wolfHSM has no built-in notion of time, so the actual time measurement is supplied by the platform through a small callback table (`whTimeoutCb`) of init, set, start, stop, and check-expired functions. This keeps the core free of OS dependencies. The POSIX port ships a ready-made implementation (`POSIX_TIMEOUT_CB`, based on `CLOCK_MONOTONIC`); other platforms supply their own. Durations are expressed in microseconds, with `WH_MSEC_TO_USEC()`, `WH_SEC_TO_USEC()`, and `WH_MIN_TO_USEC()` helper macros for readability. An application may also register an optional expired callback that runs when the timer elapses and can override the expiration — for example, restart the timer to grant a legitimately slow operation more time. + +## Non-Volatile Memory (NVM) + +wolfHSM exposes persistent storage to the rest of the server as an object store rather than as raw bytes. Keys, certificates, monotonic counters, and any other long-lived state that the server needs to survive a reset are stored as discrete objects in this store, each addressed by a stable identifier and carrying its own metadata. The object store sits on top of a pluggable backend, which in turn talks to platform flash through a thin abstraction provided by the port. This layered design keeps the higher-level subsystems (keystore, certificate manager, image manager, counters) independent of the specifics of any particular flash device. + +The NVM library is engineered for fail-safe operation: every mutating operation is structured so that interruption at any point — including loss of power partway through a write or erase — leaves the store in a recoverable state on the next initialization. This property is what allows the rest of wolfHSM to treat NVM as a reliable substrate for security-critical state. + +### High Level NVM Interface + +The NVM library presents non-volatile storage as a collection of opaque, variable-sized **objects**. Each object has three parts: + +- A unique 16-bit identifier (`whNvmId`) that the rest of the server uses to refer to it +- A fixed-size **metadata** record describing the object (see [Object Metadata and Access Attributes](#object-metadata-and-access-attributes)) +- A variable-length **payload** of arbitrary bytes + +Applications and higher-level subsystems do not address NVM by byte offset; they create, read, enumerate, and destroy whole objects through the `wh_Nvm_*` API. This object orientation is what allows the keystore, certificate manager, and counter subsystems to share a single backing store without colliding: each subsystem owns a range of identifiers and a set of metadata flags, and the underlying NVM layer is unaware of what the objects mean. + +The core operations exposed by the interface are: + +- **Add**: write a new object with caller-supplied metadata and payload. Duplicate identifiers are accepted at the NVM layer; the most recently written instance is the one returned on subsequent reads, which provides an in-place update semantic at higher layers. +- **Read**: retrieve all or part of an object's payload by identifier and byte offset, allowing large objects (firmware images, certificate chains) to be streamed out incrementally. +- **List / GetMetadata**: enumerate objects matching an access/flags filter and retrieve metadata for a specific identifier without touching the payload, which is what the server uses to drive directory-style operations and policy enforcement. +- **Destroy**: remove one or more objects. Removal is implemented as a regeneration of the partition with the listed objects omitted, which both deletes the entries and reclaims their space in a single atomic operation. +- **GetAvailable**: query free space and the amount of space that would be reclaimed by a compaction, so callers can make informed decisions before issuing large writes. + +All mutating operations are **atomic and power-loss tolerant**: any interruption either leaves the store as it was before the operation or, if the operation completed past its commit point, as it was after. The NVM library does not return success until the new state is durably committed to flash. This guarantee is what makes it safe for the server to commit a key, increment a counter, or update a certificate without an intervening cleanup pass if the system is reset mid-operation. + +The library also exposes an explicit **compaction / reclamation** model. Because objects are added by writing into free space rather than overwriting in place (a property of the underlying flash semantics), space occupied by destroyed or superseded objects is not immediately reusable. Compaction can be triggered implicitly by `wh_Nvm_AddObjectWithReclaim()` when the next add would otherwise fail for lack of space, or explicitly by calling `wh_Nvm_DestroyObjects()` with an empty list. Either path regenerates the active partition with only the live objects present and reclaims everything else. + +When wolfHSM is built with `WOLFHSM_CFG_THREADSAFE`, each NVM context carries an embedded lock. The lock's lifecycle is managed by `wh_Nvm_Init()` and `wh_Nvm_Cleanup()`, but acquiring and releasing it around operations is the caller's responsibility — the NVM API functions deliberately do not lock internally so that multiple operations can be grouped under a single critical section. See [Concurrency Support](#concurrency-support) for the broader threading model. + +### Object Metadata and Access Attributes + +Every NVM object is described by a `whNvmMetadata` record carrying: + +- `id`: the unique `whNvmId` identifier +- `len`: the payload length in bytes +- `label`: a fixed-size (`WH_NVM_LABEL_LEN`) byte array that callers may use as a user-defined name or tag for the object +- `access`: a bitfield of access permissions (`whNvmAccess`) describing who may interact with the object +- `flags`: a bitfield of `whNvmFlags` describing policy and behavior + +The metadata is written alongside the payload and travels with the object for its entire lifetime. Higher-level subsystems use these fields both to identify objects (the keystore, for example, decodes the structure of `id` to distinguish keys from certificates and counters) and to enforce policy on every access. + +The flags field carries the policy attributes that subsystems use to gate operations: + +- `WH_NVM_FLAGS_NONMODIFIABLE`: the object cannot be overwritten or destroyed through the policy-checked APIs +- `WH_NVM_FLAGS_NONDESTROYABLE`: the object cannot be destroyed (but may still be modified) +- `WH_NVM_FLAGS_NONEXPORTABLE`: the object's payload cannot be read back out through the policy-checked APIs +- `WH_NVM_FLAGS_SENSITIVE`: marks the object as holding secret material, so subsystems can apply zeroization and audit behavior accordingly +- `WH_NVM_FLAGS_EPHEMERAL`: the object should not be cached or committed +- `WH_NVM_FLAGS_LOCAL`: the object was generated locally on the server (as opposed to imported) +- `WH_NVM_FLAGS_USAGE_*`: key usage policy bits (`ENCRYPT`, `DECRYPT`, `SIGN`, `VERIFY`, `WRAP`, `DERIVE`) consumed by the keystore to constrain how a key may be used; see [Key Usage Policies](#key-usage-policies) + +The NVM library exposes both a raw and a policy-checked variant of the mutating and reading APIs (`wh_Nvm_AddObject` vs. `wh_Nvm_AddObjectChecked`, `wh_Nvm_DestroyObjects` vs. `wh_Nvm_DestroyObjectsChecked`, `wh_Nvm_Read` vs. `wh_Nvm_ReadChecked`). The checked variants honor the flags above and return `WH_ERROR_ACCESS` when the requested operation would violate them; the unchecked variants are used by server-internal code paths that need to manage the state itself (for example, to clear `NONMODIFIABLE` during a controlled revocation flow). Because policy enforcement happens server-side at the NVM layer, no client request can bypass it. + +The access field is used to express coarser-grained permissions (owner / other / user buckets, with read/write/exec/special bits) that higher layers may consult, and is the primary filter used by `wh_Nvm_List()` when enumerating objects. + +### NVM Backends + +The `wh_Nvm_*` API is implemented against a backend callback table (`whNvmCb`) that abstracts the details of how objects are actually laid out on storage. The core library does not depend on any particular backend — selecting a backend is part of server configuration, and ports or applications can supply their own implementations against the same interface. wolfHSM ships with two reference backends, both built on top of the [flash abstraction](#flash-abstraction): + +- **`nvm_flash`** (`wh_nvm_flash.c`): the default backend, suitable for flash devices with small write granularity (8 bytes or less). It manages two equal-sized partitions in flash, with one designated as active at any time. New objects are added by programming directly into free space at the end of the active partition, which keeps write amplification low for read-heavy and append-dominated workloads. A directory of object state is cached in RAM and rebuilt from flash at initialization. Destruction of objects (and explicit compaction) is performed by regenerating the inactive partition with only the surviving objects, then atomically switching the active partition pointer and erasing the old one. An interruption before the switch leaves the previous partition intact; an interruption after the switch is recovered by completing the erase of the now-inactive partition on the next boot. +- **`nvm_flash_log`** (`wh_nvm_flash_log.c`): an alternative backend designed for flash devices with **large write granularity** (e.g. 64 bytes) where every program operation must be aligned and padded to that boundary. It also uses a two-partition layout, but caches the entire active partition in RAM and rewrites the whole inactive partition on every mutation. Each partition header carries a monotonic epoch counter, and the partition with the highest epoch is treated as authoritative on the next initialization. The implementation favors simplicity and a uniform write pattern at the cost of higher write amplification, which is acceptable on the read-heavy workloads it is intended for. Selected at build time via `WOLFHSM_CFG_SERVER_NVM_FLASH_LOG`. + +Both backends bind to a `whFlashCb` flash driver supplied by the port; the choice between them is a function of the underlying flash device's program granularity and the application's write profile, not of any user-facing feature. Ports targeting microcontrollers with conventional NOR flash typically use `nvm_flash`; ports targeting devices whose program operation is fundamentally a 32- or 64-byte page write are better served by `nvm_flash_log`. + +### Flash Abstraction + +The lowest layer of the NVM stack is the `whFlashCb` interface, a small callback table that the port supplies to describe how to read, program, erase, and verify the platform's flash. The NVM backends — and any user-supplied backend — speak only through this interface, which keeps them entirely portable across flash devices. + +The interface comprises: + +- `Init` / `Cleanup`: lifecycle management for the underlying driver +- `PartitionSize`: returns the partition size, which is also the minimum erase granularity and the alignment used by the NVM backends +- `Read`: copy bytes out of flash at a given offset +- `Program`: write bytes into previously-erased flash at a given offset +- `Erase`: erase one or more partitions back to their blank state +- `Verify`: compare flash contents against a buffer, used after programming to confirm the write succeeded +- `BlankCheck`: confirm that a region is in the erased state, used during recovery and partition selection +- `WriteLock` / `WriteUnlock`: optional protection against accidental programming or erasure of a region + +wolfHSM ships with two reference flash drivers usable on host platforms and in testing: + +- **POSIX file-backed flash** (`port/posix/posix_flash_file.c`): persists flash contents to a host file, suitable for development, simulation, and the POSIX server example +- **RAM-backed flash simulator** (`wolfhsm/wh_flash_ramsim.h`): emulates flash semantics (erase-then-program, partition alignment, configurable erased-byte value) entirely in RAM, used by the test suite and useful when bringing up a new port + +Vendor-supplied flash drivers ship with the platform ports under `port//`. New platforms are integrated into wolfHSM by implementing the `whFlashCb` callback set against the device's flash controller; nothing in the NVM library above this layer needs to change. + +## Keystore + +The keystore manages the lifecycle of cryptographic key material on the server. It sits on top of [NVM](#non-volatile-memory-nvm) and is the layer every crypto operation goes through to reference a key. Clients refer to keys by a stable 16-bit identifier, not by the key bytes; the material stays server-side and is only returned through explicit, policy-checked operations. This is what lets the server enforce per-key usage policy, isolate keys between clients, and offload bulk crypto to hardware without exposing key bytes outside the trust boundary. + +The keystore has three responsibilities that the rest of this section covers in turn: + +- Managing a fast working set of keys in RAM (the **key cache**) layered over the slower NVM object store, with explicit commit and load operations between the two +- Enforcing **isolation between clients**, so that a key cached by one client is not visible to another — and, optionally, relaxing this isolation for explicitly designated [global keys](#global-keys) +- Implementing **wrapped keys** that can leave the server under the protection of a server-resident key encryption key, and **usage policies** that constrain how every individual key may be used at every crypto request + +### Key Cache, Key IDs, and NVM Backing Store + +The keystore is two-tier: a fixed-size **key cache** in server RAM holds the working set of keys, and persistent **NVM** behind it holds the keys that must survive a reset. Every key in use lives in the cache; NVM is the durable copy, loaded into a cache slot on demand. + +Keys are named by a 16-bit identifier (`whKeyId`), which has two forms — a simple one the client uses and a fuller one the server uses internally: + +- **Client-side**: Each client gets a dedicated namespace of 255 key identifiers that are specific to and only accessible by that client. These IDs range from `[1, 255]`, where `0` is the reserved sentinel value `WH_KEYID_ERASED` used internally to mark empty key slots (this sentinel value is also used to request a dynamically assigned ID for a key cache operation — see the keystore API documentation for more information). The client can also set a flag bit in the keyId top byte to ask for special handling — bit 8 for a [global key](#global-keys), bit 9 for a [wrapped key](#wrapped-keys). That is all a client ever deals with, and the `WH_CLIENT_KEYID_MAKE_*` macros in `wolfhsm/wh_client.h` set those flags for it. +- **Server-side**: internally every key has a globally unique id that also encodes *what* the key is and *who* owns it. When a request arrives, the server expands the client's provided keyId number into this full form, and collapses it back on the way out (`wh_KeyId_TranslateFromClient()` and its inverse). Client code never touches the internal fields. + +The server-side `whKeyId` packs three fields into its 16 bits: + +- **TYPE** (top 4 bits): the kind of object — `WH_KEYTYPE_CRYPTO` for ordinary crypto keys, `WH_KEYTYPE_SHE` for AUTOSAR SHE keys, `WH_KEYTYPE_COUNTER` for monotonic counters, `WH_KEYTYPE_WRAPPED` for wrapped-key metadata, and `WH_KEYTYPE_NVM` for non-key NVM objects that share the same id space. +- **USER** (middle 4 bits): the owning client. Value `0` is reserved for the global-key namespace when `WOLFHSM_CFG_GLOBAL_KEYS` is enabled. +- **ID** (low 8 bits): the number the client chose. + +The USER field is what gives each client its own private key space. The server fills it with the connection's client id (assigned at init and checked against `WH_CLIENT_ID_MAX`), so when two clients both use "key 5" they map to different `whKeyId` values and cannot touch each other's key. Every client sees the same `[1, 255]` range, and the ranges never overlap. + +Turning to the cache itself: it is statically allocated inside the server context (wolfHSM uses no dynamic memory) and sized at build time by two pairs of macros: + +- `WOLFHSM_CFG_SERVER_KEYCACHE_COUNT` × `WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE`: the number of *regular* slots and the largest key that fits in one +- `WOLFHSM_CFG_SERVER_KEYCACHE_BIG_COUNT` × `WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE`: the number of *big* slots and the largest key that fits in one + +The two tiers keep one large key (e.g. an an ML-DSA-87 private key) from dictating the slot size for every smaller key. A cached key goes to the regular cache if it fits and the big cache otherwise; both follow the same eviction and policy rules. + +Each cached key carries its full `whNvmMetadata` record alongside the key bytes, plus an internal **committed flag** marking whether a copy also exists in NVM. This flag is what makes the cache a true working set: when it needs a slot and none is free, the keystore evicts a key that is already committed (and can be reloaded later), but never an uncommitted one. Uncommitted keys are RAM-only, so the caller must commit them to survive eviction or reset; if the cache fills with uncommitted keys, the next cache operation returns `WH_ERROR_NOSPACE` instead of silently dropping material. + +A client drives this tier with five operations: + +- **Cache**: write key bytes and metadata into a server cache slot. The key is usable immediately but RAM-only. +- **Commit**: copy a cached key into NVM as a durable object. The cache copy is marked committed and becomes a candidate for eviction. +- **Evict**: drop the cache copy. If the key was committed, the NVM copy remains and reloads on the next reference; if it was uncommitted, the key is gone. +- **Export**: read a cached key's bytes back to the client, subject to `WH_NVM_FLAGS_NONEXPORTABLE`. +- **Erase**: remove the key from both cache and NVM in one operation. + +Operations that take a `whKeyId` don't care whether the key is in the cache or only in NVM: on first use the server loads it from NVM into a cache slot — the implementation calls this *freshening* — and serves later operations from the cache until eviction. + +Two more behaviors round out the cache model: + +- A key whose metadata carries `WH_NVM_FLAGS_EPHEMERAL` is never committed to NVM, no matter what the caller requests. This suits short-lived material (session keys, transient keypairs) where an NVM write is never warranted. +- The 24-byte `label` field travels with the key for its whole lifetime in both cache and NVM. It is opaque to the keystore — a caller-supplied name or tag — and is returned alongside the key on export, so applications can tell keys apart without keeping their own mapping. The exception to this is in the SHE layer, where it is used to store SHE-specific metadata. + +### Global Keys + +By default a key cached by one client is invisible to every other client, thanks to the USER field in `whKeyId`. But sometimes clients on a single HSM genuinely need to share key material. Copying such a key into every client's namespace would waste cache and NVM and complicate provisioning. + +The optional **global keys** feature (`WOLFHSM_CFG_GLOBAL_KEYS`) adds a parallel keystore namespace shared by all clients. Global keys live in their own cache and their own NVM id range, but are otherwise used exactly like local keys: any operation that takes a `whKeyId` — including the wolfCrypt crypto callback — accepts a global keyId unchanged. + +Internally, global keys reserve USER field value `0` (`WH_KEYUSER_GLOBAL`) — a value no client can hold if the global key feature is enabled. The global cache lives in the NVM context (`whNvmContext::globalCache`), not a server context, because server contexts are per-connection while NVM and global keys are shared across them. On each keystore operation the server checks the keyId: USER `0` routes to the global cache, anything else to the connection's local cache. There is no separate global API — `wh_Client_KeyCache`, `wh_Client_KeyCommit`, `wh_Client_KeyExport`, and the crypto callbacks all work transparently with global keyIds. + +Clients designate a key as global by setting the `WH_KEYID_CLIENT_GLOBAL_FLAG` bit (bit 8) in the request keyId. The recommended way to do this is via the `WH_CLIENT_KEYID_MAKE_GLOBAL()` macro: + +- `whKeyId k = WH_CLIENT_KEYID_MAKE_GLOBAL(5)` constructs a client-facing global keyId for numeric ID 5 +- Passing this keyId to `wh_Client_KeyCache` causes the server to store the key in the global cache and (on commit) in the global NVM range +- Passing the same keyId from any other connected client retrieves the same key + +Global keys interact with the cache and NVM tiers in the same way as local keys, including the commit/evict/freshen flow, eviction of committed-only slots, and policy enforcement at the NVM layer. The only practical differences are the cache they occupy and the visibility they grant. + +> **Security note**: Because a global key is reachable by every client connected to the server, the security boundary it provides is the server itself, not any particular client. Global keys should be reserved for material that is genuinely shared across the trust domains of the connected clients — typically vendor-provisioned roots and shared symmetric keys for inter-client communication — and should not be used as a workaround for per-client key management. Per-key usage flags (see [Key Usage Policies](#key-usage-policies)) apply to global keys exactly as they do to local keys, and should be used to constrain how a shared key may be used regardless of which client invokes the operation. + +### Wrapped Keys + +A key that lives entirely inside the server is protected by its trust boundary: only the server can read its bytes, and policy is enforced before every use. Some workflows still need to move key material outside that boundary — to back keys up to off-device storage, to transport a key between systems during provisioning, or even to support wolfHSM on an HSM platform without dedicated NVM. wolfHSM's **wrapped keys** feature (`WOLFHSM_CFG_KEYWRAP`) does this safely, with the server mediating every step. + +A *wrapped key* is a key whose payload — the key bytes and its `whNvmMetadata` — has been encrypted and authenticated under another key resident on the server, the **key encryption key (KEK)**. Because the KEK never leaves the server, the wrapped blob can be handed to the client, stored on a host filesystem, sent over an untrusted channel, or pushed to off-device storage, and only the server (with the same KEK) can recover the original key. + +The wrap format used by wolfHSM is a length-prefixed, authenticated encryption blob. AES-GCM is the currently supported wrap cipher and is requested by passing `WC_CIPHER_AES_GCM` to the wrap APIs; the on-wire layout is: + +``` +[ IV (12 bytes) | AuthTag (16 bytes) | AES-GCM( metadata || key ) ] +``` + +The metadata is bound into the authenticated plaintext so that the wrapped blob carries not only the key bytes but also its policy, label, and identifier — a recipient cannot strip or substitute metadata without invalidating the authentication tag. The on-wire constants `WH_KEYWRAP_AES_GCM_IV_SIZE`, `WH_KEYWRAP_AES_GCM_TAG_SIZE`, and `WH_KEYWRAP_AES_GCM_HEADER_SIZE` are defined in `wolfhsm/wh_common.h` and may be used by callers to size wrap output buffers. The maximum wrappable key size is controlled by `WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE`. + +The lifecycle exposed to clients consists of three primary operations: + +- **Wrap**: the client supplies plaintext key bytes, a metadata template, and the keyId of a server-resident KEK; the server encrypts the (metadata || key) blob with the KEK and returns the wrapped blob to the client. The plaintext is not written to NVM as part of this operation. +- **Unwrap-and-export**: the client supplies a wrapped blob and the KEK's keyId; the server decrypts the blob, authenticates the tag, and returns the recovered metadata and key bytes to the client. This is the operation used by host-side workflows that need to consume the key off-device, for example to inject it into a non-HSM peer. +- **Unwrap-and-cache**: the client supplies a wrapped blob and the KEK's keyId; the server decrypts the blob and installs the recovered key directly into the keystore cache as if `wh_Client_KeyCache` had been called locally with the recovered bytes. This is the more common operation in production deployments, since it lets a key live on disk in encrypted form and be hydrated into the HSM at runtime without the plaintext ever transiting the client. Clients can then commit the unwrapped key to NVM if they wish. + +In all three operations the KEK is identified by its existing keyId in the keystore, must carry the `WH_NVM_FLAGS_USAGE_WRAP` usage flag, and is enforced server-side by the keystore policy machinery. A key without the `WRAP` usage flag cannot be used to wrap or unwrap regardless of any client request. + +A parallel pair of APIs — `wh_Client_DataWrap` and `wh_Client_DataUnwrap` — applies the same construction to arbitrary application data rather than key material. These are useful when a client needs the same authenticated-encryption guarantee for non-key payloads using a key resident in the HSM. + +> **Note**: Wrapped key identifiers are signaled on the wire by setting `WH_KEYID_CLIENT_WRAPPED_FLAG` (bit 9) in the request keyId, which the server translates internally to `WH_KEYTYPE_WRAPPED`. Clients construct wrapped-key identifiers using `WH_CLIENT_KEYID_MAKE_WRAPPED()`, and the combined wrapped-and-global form using `WH_CLIENT_KEYID_MAKE_WRAPPED_GLOBAL()`; both are defined in `wolfhsm/wh_client.h`. + +### Key Usage Policies + +A key's `whNvmMetadata` carries a `flags` field that the keystore checks on every server-side operation to constrain how the key may be used. The flags fall into two groups: **lifecycle flags** that govern whether a key may be modified, destroyed, exported, or cached at all, and **usage flags** that govern which cryptographic operations it may take part in. + +The lifecycle flags are the same `whNvmFlags` bits described in [Object Metadata and Access Attributes](#object-metadata-and-access-attributes) and are enforced uniformly across NVM objects: `WH_NVM_FLAGS_NONMODIFIABLE`, `WH_NVM_FLAGS_NONDESTROYABLE`, `WH_NVM_FLAGS_NONEXPORTABLE`, `WH_NVM_FLAGS_SENSITIVE`, and `WH_NVM_FLAGS_EPHEMERAL`. For keys specifically, the most consequential of these is `NONEXPORTABLE`, which prevents `wh_Client_KeyExport` and the full per-algorithm export helpers from returning the key bytes to the client. The public-only export path is deliberately exempt from this flag because public key material is non-sensitive; the corollary is that a key marked `NONEXPORTABLE` can still be made useful for client-side verification by exporting only its public half. + +The usage flags constrain which cryptographic operations a given key may participate in: + +- `WH_NVM_FLAGS_USAGE_ENCRYPT`: the key may be used to encrypt +- `WH_NVM_FLAGS_USAGE_DECRYPT`: the key may be used to decrypt +- `WH_NVM_FLAGS_USAGE_SIGN`: the key may be used to produce signatures or MACs +- `WH_NVM_FLAGS_USAGE_VERIFY`: the key may be used to verify signatures or MACs +- `WH_NVM_FLAGS_USAGE_WRAP`: the key may be used as a KEK for [wrapped keys](#wrapped-keys) or for data wrapping +- `WH_NVM_FLAGS_USAGE_DERIVE`: the key may be used as input to a key derivation function + +Multiple usage flags may be combined, and `WH_NVM_FLAGS_USAGE_ANY` is a convenience constant equal to the bitwise OR of all USAGE bits. A key whose metadata carries no USAGE bits at all is treated as not permitted for any cryptographic use — attempting to use it returns `WH_ERROR_USAGE`. This is intentional: a default-zero metadata does not silently grant access; the application must explicitly opt in to each operation a key may perform. + +Policy enforcement happens server-side on every relevant operation. Every wolfCrypt request that flows through the crypto callback routes through the keystore's usage check before the underlying primitive runs, and the operation is rejected with `WH_ERROR_USAGE` if the flag for that operation is not set. Because enforcement is server-side, and because the metadata is bound into the wrapped blob for wrapped keys, no client request can bypass it. + +Lifecycle and usage flags are bound to a key at the moment it is first cached or generated and travel with the key into NVM on commit. They cannot be edited in place through the standard client API — once a key has been created with `USAGE_SIGN` only, the only way to also grant `USAGE_VERIFY` is to erase the key and recreate it (or, for a public verification key, to extract the public half through the public-only export path and use it as an independent key). This non-editability is what makes the policy useful as a security control: an attacker who compromises a client cannot loosen the policy on an already-provisioned key. + +The keystore additionally provides a **revocation** operation (`wh_Client_KeyRevoke`) that clears all `USAGE_*` bits and sets `WH_NVM_FLAGS_NONMODIFIABLE` on a key without destroying the underlying storage. After revocation, every cryptographic use of the key returns `WH_ERROR_USAGE`, and the key cannot be re-enabled. Revocation is persisted to NVM for committed keys and survives reset; for cache-only keys, eviction has the same effect since the key cannot be reloaded. This makes revocation useful for emergency rotation (taking a compromised signing key out of circulation without immediately reclaiming its NVM slot) and for staged decommissioning, while still leaving the key bytes in place for audit or forensic recovery. + +Concrete examples of policy-driven scenarios that this machinery supports: + +- A **signing-only** key for code signing: `USAGE_SIGN | NONEXPORTABLE | NONMODIFIABLE`. The key can produce signatures but never leaves the HSM and cannot be silently replaced. +- A **verification-only** public key: `USAGE_VERIFY`. Attempting to use it for signing returns `WH_ERROR_USAGE` even though the key object is cryptographically capable of either. +- A **KEK** for wrapped-key workflows: `USAGE_WRAP | NONEXPORTABLE`. The key can wrap and unwrap other keys but cannot itself be exported or used for general encryption. +- A **derivation root** for session keys: `USAGE_DERIVE | NONEXPORTABLE | SENSITIVE`. The root is bound to the HSM and is consumed only by KDF operations that produce shorter-lived material. + +## Certificate Management + +wolfHSM provides a server-resident **certificate manager** that handles the storage of trusted root certificates and the verification of X.509 certificate chains against them. Clients submit a candidate chain and the NVM id of a trusted root — or a list of root ids, with the multi-root API — and the server validates the chain and returns a single yes/no answer, optionally extracting the leaf's public key into the keystore for later crypto operations. The feature is enabled with `WOLFHSM_CFG_CERTIFICATE_MANAGER`. Two opt-ins extend it: `WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT` adds [RFC 5755 attribute certificates](#attribute-certificate-acert-support), and `WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE` adds the [trusted certificate verify cache](#trusted-certificate-verify-cache). + +Under the hood, chain verification is delegated to wolfSSL's `WOLFSSL_CERT_MANAGER`, which the server instantiates per request and populates with the requested root(s). This means that the full set of X.509 validation behaviors that wolfSSL implements can be leveraged through wolfHSM. + +### Trusted Root Storage + +Trusted root certificates are stored as ordinary NVM objects (see [Non-Volatile Memory](#non-volatile-memory-nvm)). Each root is a DER-encoded X.509 certificate written into NVM under a caller-chosen `whNvmId` with full `whNvmMetadata` — access bits, flags, and label — so that the same access-control machinery that applies to keys also applies to roots. + +The lifecycle operations exposed to clients are: + +- **Add trusted**: write a root certificate into NVM under a caller-supplied id with the requested metadata. Roots are commonly added with `WH_NVM_FLAGS_NONMODIFIABLE` (and optionally `WH_NVM_FLAGS_NONEXPORTABLE`) so that a compromised client cannot tamper with the trust anchor after provisioning. +- **Erase trusted**: destroy a previously installed root. Subject to the same policy bits as other NVM destroy operations, so a root marked `NONDESTROYABLE` will not be removed. +- **Read trusted**: read a stored root back out to the client. Read access is gated on `WH_NVM_FLAGS_NONEXPORTABLE`: if the root was provisioned non-exportable, the read request returns `WH_ERROR_ACCESS` regardless of who issued it. + +The maximum size of an individual root is bounded by `WOLFHSM_CFG_MAX_CERT_SIZE`. + +### Chain Verification + +A candidate certificate chain is presented to the server as a single buffer of concatenated DER-encoded X.509 certificates ordered from CA-down-to-leaf, such that each certificate appears in the buffer after the certificate that signed it. The server walks the buffer one ASN.1 SEQUENCE at a time, verifies each certificate against the current trust store, and if the certificate is itself a CA, promotes it into the trust store so the next certificate in the chain can chain to it. The single certificate that is *not* marked as a CA is treated as the leaf, and the verification succeeds only if the leaf chains all the way back to a trusted root that was installed in NVM. + +Two verification variants are exposed: + +- **Single-root verify** (`wh_Client_CertVerify`): the client gives the NVM id of one trusted root. The chain must anchor to that root. +- **Multi-root verify** (`wh_Client_CertVerifyMultiRoot`): the client gives a list of up to `WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS` root ids. The chain is accepted if it anchors to *any* of them. Ids not present in NVM are skipped, and the order does not matter. + +Multi-root verify lets a client send one fixed list of acceptable roots and run unchanged on any device, no matter which of those roots that device actually has. For example, every device in a fleet can be shipped the same root list while each holds only its own subset. The matching server-side functions are `wh_Server_CertVerify` and `wh_Server_CertVerifyMultiRoot`. + +Verification returns a single status code: `WH_ERROR_OK` on a trusted chain, `WH_ERROR_CERT_VERIFY` if no anchor matches, `WH_ERROR_NOTFOUND` if the multi-root call found none of the supplied root ids in NVM, or a more specific error for malformed input. + +#### Caching the Leaf Public Key + +A common pattern in HSM-mediated workflows is to verify a peer's certificate chain and then use the leaf's public key for subsequent cryptographic operations (signature verification, key exchange, etc.). To support this without round-tripping the public key through the client, every verify call accepts a `WH_CERT_FLAGS_CACHE_LEAF_PUBKEY` flag and an associated keyId: + +- If the flag is set and the supplied keyId is `WH_KEYID_ERASED`, the server allocates a fresh unique keyId, extracts the leaf's SubjectPublicKeyInfo into the keystore's "big" cache slot under that id, and returns the keyId to the client. +- If the flag is set and the supplied keyId is a concrete value, the server caches the public key under that id (subject to keystore policy). +- The metadata applied to the cached key is supplied by the caller via the `cachedKeyFlags` argument, so the leaf key inherits an appropriate usage policy (e.g. `WH_NVM_FLAGS_USAGE_VERIFY | WH_NVM_FLAGS_NONEXPORTABLE`) as soon as it is materialized. + +The cached key behaves like any other key in the [keystore](#keystore) from that point on: it can be used in wolfCrypt operations, committed to NVM by the client, evicted, and so on. The plaintext key bytes never leave the server during this flow — the chain enters and the keyId comes out. Both variants offer a convenience call that sets the flag for you: `wh_Client_CertVerifyAndCacheLeafPubKey` and `wh_Client_CertVerifyMultiRootAndCacheLeafPubKey` (each with a DMA form). + +#### DMA Variants + +When `WOLFHSM_CFG_DMA` is enabled, parallel DMA variants of all certificate operations let the server read the chain (or write the root, in the case of add/read trusted) directly from client memory rather than copying it through the message buffer. This lifts the per-chain size ceiling above what `WOLFHSM_CFG_COMM_DATA_LEN` would otherwise allow and is the recommended path for verifying long chains or large root certificates. The DMA allowlist applies as it does for any DMA-backed feature — the server will refuse to read a client buffer outside its configured allowed regions. See [DMA Support](#dma-support). + +### Trusted Certificate Verify Cache + +Every verify runs a signature check at each link in the chain. When the same CA certificates keep showing up — many clients chaining through one issuing CA, or one client re-checking the same peer over and over — that work repeats even though the result never changes. The optional **trusted certificate verify cache** (`WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE`) remembers CAs that have already verified and skips the signature check when they reappear. + +Each cache entry is the SHA-256 hash of a verified CA, tagged with the set of trusted-root ids that were loaded at the time. As the server walks a chain, it hashes each certificate and checks the cache first. A hit skips the signature check; everything else proceeds normally (the CA is still added to the trust store, the leaf key is still extracted if asked). A miss verifies the certificate the usual way, then caches it if it is a CA. + +**Only CAs are cached, never leaf certificates.** Caching a leaf would be unsafe: a later request that sends the leaf on its own could get a cache hit and pass, even though its issuer isn't loaded and the signature check would have failed. CAs are safe to cache because each one is verified as a link in a full chain before it is trusted. + +The root-id tag is what makes a cached result safe to reuse. A lookup hits only if the entry's roots are all among the roots the current caller has loaded. This is safe because adding trusted roots can never undo a verify that already passed — so if the original roots are still trusted, the cached result still holds. Single-root verifies make the most reusable entries (one root, easy to match); multi-root verifies make narrower ones that need more roots to match. Both kinds share the same cache. + +When a trusted root is added or erased, the server automatically drops every entry that referenced that id. It drops the whole entry rather than just that one id, because the removed root may have been the anchor that made the chain pass. This stops a reused id from producing a stale hit under a root that is no longer there. + +The cache is on by default once compiled in, and clients can manage it at runtime: + +- **Clear** (`wh_Client_CertVerifyCacheClear`): empty the cache. The next verify re-checks and re-caches. +- **Enable/disable** (`wh_Client_CertVerifyCacheSetEnabled`): turn caching off (which also empties it) or back on. Disable it when you want every verify to run the full signature check. + +When full, the cache overwrites the oldest entry first. It holds `WOLFHSM_CFG_CERT_VERIFY_CACHE_COUNT` entries, and each entry tracks up to `WOLFHSM_CFG_CERT_MAX_VERIFY_ROOTS` roots. + +By default each client has its own cache. Defining `WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_GLOBAL` makes one shared cache (with its own lock), so a CA verified by one client can hit for another, and clear/disable then apply to every client. This stays safe: each hit is still checked against the caller's own loaded roots, so no client can borrow another's trust anchors. + +> **Note**: A cache hit skips wolfSSL's verify path, so it also skips any verify callback you registered (`whServerCertConfig.verifyCb`, or `wh_Server_CertSetVerifyCb` at runtime). If your callback must run on every chain, leave the cache disabled. + +### Attribute Certificate (Acert) Support + +When built with `WOLFHSM_CFG_CERTIFICATE_MANAGER_ACERT`, wolfHSM also accepts and verifies [RFC 5755](https://www.rfc-editor.org/rfc/rfc5755) **attribute certificates**. An attribute certificate (acert) is a short-lived, separately signed assertion of *attributes* — typically roles, group memberships, or authorization claims — bound to a holder identified by a conventional X.509 identity certificate. + +The server-side verification model for acerts is deliberately narrower than for ordinary X.509 chains: an acert is verified directly against the public key of a single trusted root certificate stored in NVM, rather than being walked through a chain. The server reads the trusted root by id, extracts its public key and algorithm, and calls `wc_VerifyX509Acert` to validate the acert's signature. Successful verification means the acert was signed by the holder of the named root, with the validity period, holder binding, and other RFC 5755 fields enforced by wolfCrypt's acert parser; semantic interpretation of the contained attributes is left to the application. + +## Communication Layer and Transports + +The communication layer is the substrate that carries every client request to the server and every server response back. It sits between the high-level client/server APIs and the platform-specific medium that actually moves bytes between them, and is responsible for everything that has to be true for those APIs to behave as a coherent request/response protocol regardless of where the two sides are physically located. Concretely, it provides a fixed-MTU packet framing with a versioned header, a sequence-numbering scheme that lets the client match each response to its outstanding request, and a pluggable transport interface that the platform implements once and the rest of the library never has to know about. + +The stack is two layers: + +- The **comm layer** (`whCommClient` and `whCommServer`, declared in `wolfhsm/wh_comm.h`) provides packet framing, sequence numbering, and the public send/receive functions that the higher-level APIs build on. +- The **transport layer** (`whTransportClientCb` and `whTransportServerCb`) is a small callback table that delivers complete packets between the two endpoints. The comm layer speaks to it through this interface and never touches the underlying medium directly. + +Higher-level wolfHSM APIs — the keystore client, the wolfCrypt callback, the NVM client, and every other client subsystem — all build their requests and responses on top of `whCommClient`/`whCommServer` and are agnostic to which transport is in use underneath. + +### Communication Layer + +Each request or response is a single packet composed of an 8-byte `whCommHeader` followed by up to `WOLFHSM_CFG_COMM_DATA_LEN` bytes of payload. The header carries: + +- A **magic** field that combines a 1-byte endianness marker and a 1-byte protocol version. The endianness marker lets the receiving side detect a mismatched byte order and use the protocol's translation helpers (`wh_Translate16`/`32`/`64`) to byte-swap multi-byte fields as it parses them, so heterogeneous client/server pairings on a single SoC do not need a separate framing layer to agree on byte order. +- A **kind** field that identifies the message: an 8-bit group naming the subsystem (`COMM`, `NVM`, `KEY`, `CRYPTO`, `CERT`, `SHE`, `COUNTER`, `AUTH`, `CUSTOM`, …) and an 8-bit action within that group. The server uses the group to dispatch the packet to the correct subsystem handler and the action to invoke the specific operation. This is what allows a single transport connection to be multiplexed across every wolfHSM feature. +- A **seq** field, incremented on each client request and copied verbatim onto the matching response. The client validates the sequence number on receipt and rejects mismatched or stale packets, which is what makes the split-transaction client API safe in the face of late or duplicated responses. +- An **aux** field that conveys a session identifier (or `NORESP` for fire-and-forget requests) on the way out and a coarse outcome code (`OK`, `ERROR`, `FATAL`, `UNSUPP`) on the way back, separately from any payload-carried return code. + +The comm layer itself is **stateless** in the protocol sense: each request/response transaction stands alone, no session state is required for the server to interpret it, and the only client-side state that persists between calls is the next sequence number and whether a request is currently outstanding. Higher-level features that do require session state (authentication, monotonic counters, etc.) layer it on top of the comm packets rather than embedding it in the protocol itself. The practical consequence is that a single server can serve multiple independent clients over multiple independent transport instances without those clients sharing any state at the comm layer. + +The comm layer is also **non-blocking and split-transaction** end-to-end: `wh_CommClient_SendRequest` and `wh_CommClient_RecvResponse` return `WH_ERROR_NOTREADY` rather than waiting on the transport, and the same convention is propagated up through every higher-level client API. Transport errors below this layer surface as either `WH_ERROR_NOTREADY` (retry) or `WH_ERROR_ABORTED` (fatal — clean up and reinitialize the connection), and the higher-level APIs distinguish between the two so applications can implement appropriate retry or recovery logic. See [Client/Server Communication](4-Architecture.md#clientserver-communication) for the broader request/response model that this layer implements. + +### Transport Backends + +The transport interface — `whTransportClientCb` on the client side and `whTransportServerCb` on the server side — is a four-function callback table (`Init`, `Send`, `Recv`, `Cleanup`) that delivers complete, MTU-sized packets between client and server. The comm layer speaks only through this interface, so wolfHSM is integrated into a new platform by supplying a transport implementation rather than by modifying the core library. Transports are expected to deliver packets reliably and in order, one at a time and up to `WH_COMM_MTU` bytes; framing, sequencing, dispatch, and policy enforcement are all handled above. + +wolfHSM ships with several reference transports that between them cover the most common system topologies: + +- **Memory buffer transport** (`wolfhsm/wh_transport_mem.h`): the canonical transport for systems where the client and server share memory — typically a multi-core SoC where the server runs on a secure core and clients run on application cores. Two pre-allocated buffers, one for requests and one for responses, hold the active packet alongside a small control/status header used for flow control. The implementation is small and dependency-free so that ports can use it directly or layer hardware notification (mailbox, interrupt) on top. +- **POSIX TCP transport** (`port/posix/posix_transport_tcp.h`): carries the packet exchange over a length-prefixed TCP stream. The server listens on a configured port and the client connects to it. This is the default transport for the POSIX server example and the recommended development transport, because the client and server can live in separate host processes — or on separate machines — without any additional platform integration. +- **POSIX shared memory transport** (`port/posix/posix_transport_shm.h`): hosts the same request/response buffer layout used by the memory buffer transport in a named POSIX shared memory object, with optional space for DMA-style buffers alongside it. This lets the client and server run as independent processes on the same host while exercising the shared-memory code paths a multi-core SoC would use in production. +- **TLS-over-TCP transport** (`port/posix/posix_transport_tls.h`): wraps the POSIX TCP transport in a wolfSSL-secured channel, with support for certificate-based authentication and PSK. It is intended for deployments where the client and server are physically separated and the link between them cannot be trusted. The packet framing above the TLS session is identical to the plain TCP transport, so higher-level code does not change between the two. + +Beyond the reference transports, platform ports for embedded targets typically supply hardware-specific transports — silicon mailboxes, interrupt-driven inter-core channels, vendor IPC blocks — by implementing the same callback interface. The comm-layer contract is purely "deliver one packet, in order," so a transport need only marshal bytes between the two sides. + +Choosing a transport is primarily a function of system topology: + +- **Single SoC, server on a secure core**: the memory buffer transport (or a port-supplied hardware mailbox transport built on the same packet model) is the natural choice. +- **Development against a host-side server, or remote production access**: the POSIX TCP transport for trusted links and the TLS-over-TCP transport when confidentiality or authentication on the link is required. +- **Multi-process workloads on a single host**: the POSIX shared memory transport keeps the client and server in separate address spaces while preserving shared-memory packet semantics, which is useful both for testing port-supplied shared-memory transports and for validating client integrations against an example server without modifying either. + +The choice of transport does not affect any other part of the system — the same client code, the same wolfCrypt calls, and the same server initialization sequence work across every transport. + +## DMA Support + +Every request that travels through the [communication layer](#communication-layer-and-transports) is bounded by `WOLFHSM_CFG_COMM_DATA_LEN`, and operations that exceed that bound must otherwise be split across multiple round-trips. For workloads that have direct memory-sharing pathways between client and server — most commonly a multi-core SoC where the secure core can address the application core's RAM, or a host environment with shared memory — wolfHSM provides an optional **DMA** mode in which the server reads and writes the client's buffers in place rather than marshaling them through the message buffer. The feature is enabled with `WOLFHSM_CFG_DMA` and is layered onto the existing client/server protocol: a parallel set of DMA-aware request kinds carry pointers and lengths into the client's address space instead of inline data, and the server resolves those pointers under server-enforced policy before touching the underlying memory. + +The motivating use cases all involve payloads that are either too large or too inconveniently placed to copy through the comm buffer: + +- **Bulk symmetric crypto** over multi-kilobyte messages (AES-CBC/CTR/GCM/ECB, CMAC, streaming SHA-2 updates) where the cost of two copies dominates the cost of the cryptographic primitive. +- **Bulk asymmetric crypto** over large messages or signature buffers (ML-DSA sign/verify, Ed25519 sign/verify) and the associated key import/export paths. +- **Large NVM objects** (`wh_Client_NvmAddObjectDma`, `wh_Client_NvmReadDma`) where the object payload is larger than `WOLFHSM_CFG_COMM_DATA_LEN`. +- **Certificate chain verification** (see [DMA Variants](#dma-variants)) where the chain itself may be several kilobytes and the application already holds it in its own memory. +- **In-place image verification** by the [image manager](#image-manager), which is the canonical case: the image being authenticated is already mapped in flash or RAM, and copying it through the comm buffer would defeat the purpose. + +### The DMA Crypto Device (`WH_DEV_ID_DMA`) + +For wolfCrypt-mediated operations, opt-in to DMA is a single change at the call site: instead of passing `WH_DEV_ID` as the device identifier, the client passes `WH_DEV_ID_DMA`. Both device IDs route to the wolfHSM client crypto callback, but the DMA device tells the callback to construct a DMA-flavored request whose payload carries pointers and lengths into the client's address space rather than inline data. The server-side dispatcher recognizes the DMA request kind and, for each referenced buffer, hands the pointer to the server's DMA address-processing path (described below) before invoking the underlying wolfCrypt primitive. The set of algorithms that have a DMA path mirrors the most performance-sensitive subset of the supported algorithms; algorithms without a DMA path return an unsupported error if invoked with `WH_DEV_ID_DMA`, and the application can fall back to the standard `WH_DEV_ID` for those. + +For the non-crypto subsystems (NVM, certificate manager, image manager, key cache/export, and the data-wrap API) the DMA-aware request kinds are exposed as `*Dma` variants of the corresponding client API functions — `wh_Client_NvmAddObjectDma`, `wh_Client_KeyCacheDma`, `wh_Client_KeyExportDma`, `wh_Client_CertVerifyDma`, and so on. See the [client API reference](9-API-docs-client.md) for the full set. + +### Pre-Access and Post-Access Callbacks + +A pointer that is valid in the client's address space is not necessarily valid in the server's. On a multi-core SoC the two cores may have distinct memory maps, distinct cache hierarchies that need to be synchronized before and after a shared-buffer access, or both. wolfHSM does not bake any particular assumption about this relationship into the server; instead, it exposes a port-supplied callback that the server invokes around every client-memory access, paired with optional flags to control cache behavior. The callback is registered at server initialization via `wh_Server_DmaRegisterCb` (or as part of `whServerDmaConfig`). + +The callback signature is: + +```c +int whServerDmaClientMemCb(whServerContext* server, + uintptr_t clientAddr, + void** serverPtr, + size_t len, + whServerDmaOper oper, + whServerDmaFlags flags); +``` + +It is called twice per access — once before, once after — with the `oper` argument distinguishing the four phases: + +- `WH_DMA_OPER_CLIENT_READ_PRE` — the server is about to read `len` bytes from `clientAddr`. The callback should translate the address into a server-accessible pointer in `*serverPtr` and, on a system with caches, invalidate or flush the corresponding cache lines so the subsequent read sees up-to-date client memory. +- `WH_DMA_OPER_CLIENT_READ_POST` — the read has completed. Tear-down hook for any state the PRE callback set up. +- `WH_DMA_OPER_CLIENT_WRITE_PRE` — the server is about to write `len` bytes to `clientAddr`. The callback translates the address and may perform whatever cache preparation the platform requires (flushing dirty lines from the server cache, for example, so the subsequent write is the authoritative copy). +- `WH_DMA_OPER_CLIENT_WRITE_POST` — the write has completed. On a system where the client must observe the write through its own cache, this is the hook that invalidates the client-visible cache lines. + +If no callback is registered, the server uses the client address directly as `*serverPtr`, which is the right behavior for a system with a flat shared address space and coherent caches. Ports that need either address translation or cache maintenance supply a callback that handles both; the callback is the single extension point for both concerns. + +For platforms where the client buffer is not directly memcpy-able even after address translation — for example, when the only path to client memory is through a hardware FIFO or register window — wolfHSM additionally exposes a `whServerDmaMemCopyCb` callback under `WOLFHSM_CFG_DMA_CUSTOM_CLIENT_COPY`. When registered (via `wh_Server_DmaRegisterMemCopyCb`), this callback replaces the internal `memcpy` between server and client memory entirely, and is the only operation that touches the client side of the transfer. + +The same PRE/POST callback model is also available on the **client side** through `wh_Client_DmaRegisterCb`, with an identical `whClientDmaClientMemCb` signature. The client callback is invoked before the request is sent and after the response is received, and is the right place for any work that has to happen in the client's address space before the server is ever told about a buffer — pinning pages, flushing the client's view of a cache line, or substituting the application's pointer with one that lives in a region the server can actually reach. The POSIX shared-memory transport illustrates the last case: an application buffer allocated from the process's ordinary heap is not visible to the server because it lies outside the mapped shared-memory segment, so the transport's client callback (`posixTransportShm_ClientStaticMemDmaCallback`) detects that the supplied address falls outside the DMA region, allocates a bounce buffer inside the shared segment on `*_READ_PRE`/`*_WRITE_PRE`, copies the application data into it for the read direction, and reports the in-segment offset as the address the server should use. The matching POST phase copies any server-written bytes back to the original application buffer and frees the bounce buffer. From the application's perspective the original wolfCrypt call is unchanged; the client callback transparently bridges the gap between the application's address space and the address space the server can address. Client-side and server-side callbacks are independent — a port may register either, both, or neither, depending on which side needs the translation. + +The `whDmaFlags` argument carries per-request hints supplied by the client. Currently the only defined flag is `cacheForceInvalidate`, which the client sets when it has reason to believe the server should not trust any cached view of the buffer (after a DMA write by another agent, for example). Additional flags are reserved for future protocol extensions. + +### Address Allowlisting + +Because DMA gives the server the ability to read or write any address the client passes, an unconstrained DMA path would let a compromised or buggy client direct the server to access memory it has no business touching — kernel memory, other processes' buffers, peripherals mapped into the address space. wolfHSM accordingly enforces, in the server, an **allowlist** of address ranges that DMA requests are permitted to reference. The allowlist is supplied by the port at server initialization (`wh_Server_DmaRegisterAllowList` or `whServerDmaConfig::dmaAddrAllowList`) and is consulted on every PRE phase of every DMA operation. + +The allowlist is two parallel tables — one for client reads (server reads from client memory) and one for client writes (server writes to client memory) — so that a region can be made readable without being writable. Each entry is a `{addr, size}` pair, and a DMA request is accepted only if the requested range is **fully contained** within at least one entry in the relevant table. Partial overlap is treated as a failure, and a request that fails the check is rejected with `WH_ERROR_ACCESS` before any access actually occurs. POST phases skip the check on the assumption that the address was already validated at PRE. + +When the allowlist is not registered the server allows every address, which is appropriate only for tightly trusted single-application systems and for development. **Production deployments should always supply an allowlist** that covers exactly the shared-memory regions the application is permitted to use; the allowlist is the primary mechanism that bounds the blast radius of a misbehaving client and is the difference between DMA being a performance feature and DMA being an exfiltration channel. The size of the allowlist is bounded at compile time by `WOLFHSM_CFG_DMAADDR_COUNT` entries per direction; a zero-sized entry is treated as unused. + +A symmetric `whDmaAddrAllowList` is also available on the client side for clients that wish to validate addresses locally before issuing a DMA request (`whClientDmaConfig::dmaAddrAllowList`). Client-side validation is advisory — the server's check is the authoritative one — but it lets a client fail fast when its own code accidentally constructs an out-of-range pointer. + +### 32-bit vs. 64-bit Address Handling + +The DMA path is designed so that a 32-bit client can interoperate with a 64-bit server, and vice versa, without either side losing information. All DMA pointers on the wire are 64-bit (`uint64_t`) regardless of the native pointer size on either endpoint: the client zero-extends its address into the request and the server narrows on receipt if its pointers are 32-bit (with an overflow check that rejects out-of-range values). The runtime callback signatures use `uintptr_t` so the platform's native pointer width drives address translation. The build distinguishes the two cases via `WH_DMA_IS_32BIT` and `WH_DMA_IS_64BIT`, derived automatically from the system pointer size or set explicitly via `WOLFHSM_CFG_DMA_PTR_SIZE` when the build needs to override that default. + +This wire-format choice is what makes asymmetric topologies — a 64-bit secure host serving a 32-bit application core, for example — work without special-case message structures. + +## AUTOSAR SHE Subsystem + +The AUTOSAR Secure Hardware Extension (SHE) is an automotive industry specification for a small, fixed-function security module embedded alongside an ECU's application core. It defines a set of 128-bit AES key slots, an encrypted key update protocol, a deterministic PRNG, and a CMAC-based secure boot mechanism — together intended to give an ECU the minimum trusted-crypto surface needed for in-vehicle networks. wolfHSM ships an optional SHE implementation, enabled with `WOLFHSM_CFG_SHE_EXTENSION`, that layers a spec-compliant SHE server and client API on top of the existing wolfHSM stack. The intent is that an application written against the SHE command set can use wolfHSM as its SHE module without modification, while still benefiting from the broader wolfHSM keystore, NVM, and transport infrastructure underneath. + +The SHE extension is independent of the rest of the crypto API: a server can be built with both SHE and the generic wolfCrypt offload enabled, and clients can mix SHE commands with ordinary wolfCrypt calls over the same connection. SHE keys and native crypto keys share the underlying NVM store but live in disjoint namespaces in the keystore's [type field](#key-cache-key-ids-and-nvm-backing-store), so the two subsystems cannot collide. The primary use case is automotive ECU firmware that needs to remain conformant with AUTOSAR's SHE expectations — secure boot, MAC-authenticated CAN traffic, encrypted key provisioning — while running on hardware (or a software emulation) that does not provide a dedicated SHE peripheral. + +### Client API and Command Set + +The SHE client API is declared in `wolfhsm/wh_client_she.h` and maps one-to-one onto the AUTOSAR SHE command set. Each spec command is exposed as a `wh_Client_She*` function, with the same `Request` / `Response` split-transaction variants that the rest of the wolfHSM client API uses (see [Blocking and Non-Blocking Interfaces](#blocking-and-non-blocking-interfaces)). The full set comprises: + +- **Secure boot**: `wh_Client_SheSecureBoot` (`CMD_SECURE_BOOT`) — drives the three-phase INIT/UPDATE/FINISH state machine and reports the boot result through the status register +- **Key update**: `wh_Client_SheLoadKey` (`CMD_LOAD_KEY`) — performs the encrypted M1–M5 key update protocol against any slot other than `RAM_KEY` +- **Plain key update**: `wh_Client_SheLoadPlainKey` (`CMD_LOAD_PLAIN_KEY`) and `wh_Client_SheExportRamKey` (`CMD_EXPORT_RAM_KEY`) — load the volatile `RAM_KEY` directly, and export it as an M1–M5 blob bound to the master ECU key for transfer to a peer +- **PRNG**: `wh_Client_SheInitRnd` (`CMD_INIT_RNG`), `wh_Client_SheRnd` (`CMD_RND`), and `wh_Client_SheExtendSeed` (`CMD_EXTEND_SEED`) — initialize, draw from, and reseed the spec's deterministic PRNG +- **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) + +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_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. + +### SHE Key Slots and the wolfHSM Keystore + +The AUTOSAR SHE specification defines sixteen 128-bit AES key slots, identified by IDs 0 through 15, with fixed roles for several of them: + +- **`SECRET_KEY`** (ID 0): the master secret consumed by the PRNG derivation +- **`MASTER_ECU_KEY`** (ID 1): the ECU's identity key, used as the authorization key for key updates +- **`BOOT_MAC_KEY`** (ID 2): the key used to compute the bootloader CMAC during secure boot +- **`BOOT_MAC`** (ID 3): the expected CMAC digest of the bootloader, compared against during secure boot +- **`KEY_4`…`KEY_13`** (IDs 4–13): ten general-purpose user key slots +- **`RAM_KEY`** (ID 14): a volatile slot that lives only in the cache and is lost on power cycle +- **`PRNG_SEED`** (ID 15): the persistent PRNG seed state + +wolfHSM does not implement these slots as a parallel storage layer; they are stored as ordinary objects in the [NVM and keystore](#keystore), with the SHE-specific roles encoded in the keyId. Each SHE key is given a `whKeyId` constructed with the `WH_KEYTYPE_SHE` type field, the connection's client ID in the USER field, and the SHE slot number in the ID field. The `WH_KEYTYPE_SHE` type means SHE objects are distinct from `WH_KEYTYPE_CRYPTO` keys even when they share the same numeric slot number, and the USER field gives every connected client its own independent set of sixteen SHE slots — a property that follows directly from the keystore's per-client isolation model and that the SHE spec itself does not require but that wolfHSM provides for free. + +The SHE spec also requires every key to carry a 28-bit monotonic update counter and a 5-bit set of protection flags (`WRITE_PROTECT`, `BOOT_PROTECT`, `DEBUGGER_PROTECTION`, `USAGE`, `WILDCARD`). wolfHSM stores these by repurposing the first eight bytes of the NVM `label` field as a `whSheMetadata` record holding the counter and flags in big-endian order; conversion is done by `wh_She_Meta2Label` / `wh_She_Label2Meta`. This means SHE keys need no additional NVM machinery beyond what the generic object store already provides: the counter and protection flags survive resets exactly like the key payload itself, and the SHE-side update logic — counter strictly-increasing checks, `WRITE_PROTECT` enforcement, and the rest — is implemented on top of the existing metadata round-trip. + +`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. + +### Encrypted Key Update Protocol (M1–M5) + +The most intricate piece of the SHE spec is the encrypted key update protocol that runs underneath `CMD_LOAD_KEY`. wolfHSM implements it as specified: the client constructs three input messages (M1, M2, M3) by encrypting and CMACing the new key and its metadata under keys derived from a chosen authorization key, sends them to the server, and receives two response messages (M4, M5) that prove the server stored the new key correctly. + +The protocol uses a key derivation function based on the **Miyaguchi-Preneel** one-way compression construction (`wh_She_AesMp16` in `wh_she_crypto.c`), with the exact derivation constants — `KEY_UPDATE_ENC_C`, `KEY_UPDATE_MAC_C`, `PRNG_KEY_C`, `PRNG_SEED_KEY_C` — that the AUTOSAR specification mandates. From the authorization key the server derives an encryption key (K1) and a MAC key (K2) for verifying the request, and from the *new* key it derives a separate pair (K3, K4) for proving storage in the response. + +The server-side handler enforces all of the spec's update constraints in addition to verifying M3 and decrypting M2: the new counter must be strictly greater than the previous counter for that slot (rollback protection), the existing `WRITE_PROTECT` flag must not be set, the UID in M1 must match the ECU's configured UID (unless the existing key has `WILDCARD` set and M1 carries the all-zero UID), and the authorization key referenced by the AID field in M1 must exist. Only after all of these pass is the new key written into NVM and the response constructed. + +### Secure Boot + +SHE secure boot is implemented as a three-phase state machine that the client drives via `CMD_SECURE_BOOT`: + +1. **INIT**: the client supplies the total bootloader length; the server reads `BOOT_MAC_KEY` from NVM, initializes a CMAC with a 12-byte zero prefix followed by the length, and transitions to the UPDATE state. +2. **UPDATE**: the client streams the bootloader into the CMAC in arbitrary-sized chunks; the server feeds each chunk into the running CMAC and stays in UPDATE until the cumulative length matches the value declared in INIT. +3. **FINISH**: the server finalizes the CMAC and compares it byte-for-byte against the stored `BOOT_MAC` (slot ID 3). A match sets `WH_SHE_SREG_BOOT_OK` in the status register; a mismatch leaves `BOOT_OK` clear. Either outcome sets `BOOT_FINISHED` and transitions the state machine to a terminal state. + +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 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 + +The SHE PRNG is deterministic, seeded from the master `SECRET_KEY` and a persisted `PRNG_SEED`, and is meant to be used both for spec-defined operations (the M5 verification path, internal nonce generation) and as a standards-compliant entropy source for the application: + +- `CMD_INIT_RNG`: the server reads `SECRET_KEY` and `PRNG_SEED` from NVM, derives `PRNG_KEY` and a new state via the spec's Miyaguchi-Preneel construction, advances the seed, and writes the new seed back to NVM. After this completes `WH_SHE_SREG_RND_INIT` is set in the status register. +- `CMD_RND`: each invocation runs an AES-CBC encryption of the current PRNG state under `PRNG_KEY` to produce 16 bytes of output and advance the state. +- `CMD_EXTEND_SEED`: mixes 16 bytes of caller-supplied entropy into the state via the same Miyaguchi-Preneel compression and writes the updated seed back to NVM, so reseeding survives reboots. + +The PRNG's `state` and derived `prngKey` live in the per-connection `whServerSheContext`, while the persisted `PRNG_SEED` (slot ID 15) lives in NVM exactly like any other SHE key. This is the only piece of SHE that maintains live cryptographic state in the server context rather than in the keystore. + +### Status Register (SREG) + +The SHE status register is an 8-bit field that reports the module's current secure-boot and PRNG state. The wolfHSM implementation maps the spec's bits as follows: + +- `SECURE_BOOT` (bit 1): set if a `BOOT_MAC_KEY` has been provisioned for the connected client +- `BOOT_FINISHED` (bit 3): set after secure boot has completed, regardless of outcome +- `BOOT_OK` (bit 4): set only if secure boot succeeded +- `RND_INIT` (bit 5): set after `CMD_INIT_RNG` has succeeded for the current session + +The `BUSY` (bit 0), `BOOT_INIT` (bit 2), `EXT_DEBUGGER` (bit 6), and `INT_DEBUGGER` (bit 7) positions are reserved and not currently driven by the implementation; the corresponding spec-defined behaviors (debugger-presence interlocks, asynchronous busy reporting) are intentionally out of scope for the software implementation and would be supplied by a hardware-backed port if needed. + +### Integration with the Rest of wolfHSM + +The SHE extension is built on top of the same infrastructure as every other wolfHSM feature, with a few specific touchpoints worth calling out: + +- **NVM**: SHE keys are ordinary NVM objects under the `WH_KEYTYPE_SHE` namespace; they inherit fail-safe atomicity, partition compaction, and the rest of the [NVM](#non-volatile-memory-nvm) guarantees. The 24-byte `label` field carries SHE-specific counter and flag metadata. +- **Keystore**: SHE keys live in the same per-client `whKeyId` space as crypto keys, with the TYPE field disambiguating the two. SHE keys do not currently consume the per-key [usage flag policy](#key-usage-policies) machinery — usage constraints are expressed through the SHE-spec flag set in the label instead — but lifecycle flags like `NONMODIFIABLE` apply at the NVM layer just as they do for any other object. +- **Communication layer**: every SHE command is a packet under the `WH_MESSAGE_GROUP_SHE` group and is dispatched through the [comm layer](#communication-layer-and-transports) like any other request. SHE clients work over every available transport without modification. +- **Global keys** and **wrapped keys**: not currently supported for SHE keys — the SHE keyId namespace uses the per-client USER field and does not interpret `WH_KEYUSER_GLOBAL` or the wrapped flag. Applications that need to share a key across clients must do so by provisioning it into each client's SHE namespace separately. + +A typical automotive deployment uses the SHE extension end-to-end: the bootloader and `BOOT_MAC` are programmed into NVM at production using `wh_Client_ShePreProgramKey`, the device's UID is set on first boot with `wh_Client_SheSetUid`, secure boot is run on every reset via `wh_Client_SheSecureBoot`, in-field key updates flow through the encrypted `CMD_LOAD_KEY` protocol, and CAN message authentication uses `wh_Client_SheGenerateMac` / `wh_Client_SheVerifyMac` against pre-provisioned user-slot keys. + +## Non-Volatile Monotonic Counters + +wolfHSM provides **non-volatile monotonic counters**: server-resident 32-bit values that are guaranteed never to decrease across resets and power cycles. They are the building block for anti-rollback checks on firmware versions, replay protection, audit tallies, and unique-per-boot nonces — anywhere an application needs a persistent, strictly-increasing value that a client cannot rewind. + +### Counter Semantics + +A counter is a 32-bit unsigned value supporting four operations: + +- **Init**: create the counter with a caller-supplied starting value, or overwrite an existing counter. **Reset** is the same call with value zero. This is the only path that can lower a counter, intended for provisioning rather than runtime use. +- **Increment**: atomically read, add one, and write back. Returns the new value. +- **Read**: return the current value without modifying it. +- **Destroy**: remove the counter from NVM. Subsequent reads and increments return `WH_ERROR_NOTFOUND` until it is re-initialized. + +Increment **saturates at `UINT32_MAX`** rather than rolling over: once the counter reaches the maximum it stays there, and no NVM write is performed. Silent rollover would defeat the monotonicity guarantee, so the subsystem refuses to wrap even at the cost of losing further increments. Applications approaching saturation should treat the counter as exhausted and rotate to a new identifier. + +Every mutating operation is committed by the NVM layer before the response is returned, so a power loss leaves the counter at either its pre- or post-increment value but never in between. The implementation uses `wh_Nvm_AddObjectWithReclaim`, so the partition is compacted in place as needed and a frequently-incremented counter does not accumulate dead entries. + +### Counter Identifiers and Storage + +A counter is referenced by a 16-bit `whNvmId` supplied by the caller, with `WH_KEYID_ERASED` (0) reserved as invalid. Internally the server encodes it as a `whKeyId` with TYPE = `WH_KEYTYPE_COUNTER`, USER = the connection's client id, and ID = the supplied value. This means counters inherit the keystore's [per-client isolation](#key-cache-key-ids-and-nvm-backing-store) — each client has its own counter namespace — and that counter id 5 and key id 5 are distinct objects in the same NVM store. + +The 32-bit value is stored in the **`label` field of the object's `whNvmMetadata`** with a zero-length payload. A counter therefore lives entirely in the metadata that the NVM layer already reads on every directory operation, so an increment is a single metadata write and a read is satisfied by `wh_Nvm_GetMetadata` alone. The remainder of the label and the access/flags fields are unused by the counter subsystem. Counters share the `WOLFHSM_CFG_NVM_OBJECT_COUNT` object budget with keys and other NVM objects. + +### Client API + +The operations are exposed in `wolfhsm/wh_client.h`: + +- `wh_Client_CounterInit(ctx, counterId, &value)` — create or overwrite with the supplied initial value +- `wh_Client_CounterReset(ctx, counterId, &value)` — initialize to zero +- `wh_Client_CounterIncrement(ctx, counterId, &value)` — atomically increment, return the new value +- `wh_Client_CounterRead(ctx, counterId, &value)` — read without modifying +- `wh_Client_CounterDestroy(ctx, counterId)` — remove from NVM + +Each function has split-transaction `Request` / `Response` counterparts for non-blocking use. Requests are dispatched under `WH_MESSAGE_GROUP_COUNTER` through the standard [communication layer](#communication-layer-and-transports). + +## Image Manager + +The image manager is a server-side facility for authenticating an arbitrary region of memory — typically a firmware image, but equally a data blob, a configuration record, or any other contiguous payload — against a cryptographic signature or MAC using a key resident in the server. It is enabled with `WOLFHSM_CFG_SERVER_IMG_MGR` and is the canonical mechanism by which a wolfHSM-equipped system can drive HSM-mediated secure boot of an application core, gate execution of a dynamically loaded image, or perform a periodic runtime integrity check of a code or data region without ever exposing the verifying key or the signature to the client. + +The verification model is straightforward: each managed image is described by a pointer and length into the memory the server can address, plus the [keyId](#key-cache-key-ids-and-nvm-backing-store) of the verification key and either an [NVM id](#non-volatile-memory-nvm) for the signature or an indication that the signature is embedded in the image itself. At verification time, the server reads the image (in place, via [DMA](#dma-support), when available), loads the key from the keystore, retrieves the signature, runs a verify method against the configured algorithm, and invokes an application-supplied action callback with the result. + +### Image Configuration + +Images are registered at server initialization through a `whServerImgMgrConfig` that points to an array of `whServerImgMgrImg` records. The maximum number of managed images is bounded at compile time by `WOLFHSM_CFG_SERVER_IMG_MGR_MAX_IMG_COUNT`. Each `whServerImgMgrImg` carries: + +- `addr` / `size`: the location and size of the image payload in server-addressable memory +- `hdrAddr` / `hdrSize`: for image formats whose signature is embedded in the image itself (such as wolfBoot), the location of the header from which the signature and ancillary metadata are extracted +- `keyId`: the [keyId](#key-cache-key-ids-and-nvm-backing-store) of the verification key +- `sigNvmId`: for image types whose signature lives in NVM, the `whNvmId` of the signature object; for cert-chain image types, the `whNvmId` of the trusted root certificate +- `imgType`: one of `WH_IMG_MGR_IMG_TYPE_RAW`, `WH_IMG_MGR_IMG_TYPE_WOLFBOOT`, or `WH_IMG_MGR_IMG_TYPE_WOLFBOOT_CERT`, which tells the framework how to load the key and signature +- `verifyMethod`: the verify callback that runs the actual cryptographic check +- `verifyAction`: the post-verification callback invoked with the verify result + +Once registered, an image can be verified individually by reference (`wh_Server_ImgMgrVerifyImg`), by index into the registered array (`wh_Server_ImgMgrVerifyImgIdx`), or in bulk against every registered image (`wh_Server_ImgMgrVerifyAll`). All three calls return both the cryptographic outcome and the action callback's return value through a `whServerImgMgrVerifyResult` so the caller can distinguish a verification failure from an action failure. + +### Verify Methods + +A *verify method* is the callback that performs the cryptographic check against a `(image, key, signature)` triple. The signature is: + +```c +int verifyMethod(whServerImgMgrContext* ctx, + const whServerImgMgrImg* img, + const uint8_t* key, size_t keySz, + const uint8_t* sig, size_t sigSz); +``` + +A verify method returns `WH_ERROR_OK` on a successful verification, `WH_ERROR_NOTVERIFIED` when the signature does not match, or a negative error code for an operational failure (DMA error, malformed input, missing key). wolfHSM ships with several built-in verify methods: + +- `wh_Server_ImgMgrVerifyMethodEccWithSha256`: ECDSA P-256 signature over the SHA-256 hash of the image +- `wh_Server_ImgMgrVerifyMethodRsaSslWithSha256`: RSA PKCS#1 v1.5 signature over the SHA-256 hash of the image +- `wh_Server_ImgMgrVerifyMethodAesCmac`: AES-128 CMAC over the image bytes +- `wh_Server_ImgMgrVerifyMethodWolfBootRsa4096WithSha256`: RSA-4096 verification of a wolfBoot-formatted image (see [wolfBoot Image Support](#wolfboot-image-support)) +- `wh_Server_ImgMgrVerifyMethodWolfBootCertChainRsa4096WithSha256`: cert-chain-based RSA-4096 verification of a wolfBoot image + +Applications can supply their own verify method to support algorithms not represented in the built-in set, or to layer additional checks on top of an existing one — for example, validating a monotonic counter against a [non-volatile counter](#non-volatile-monotonic-counters) inside a wrapper verify method to add anti-rollback protection. The maximum signature size handled by the framework is `WOLFHSM_CFG_SERVER_IMG_MGR_MAX_SIG_SIZE`, whose default accommodates RSA-4096. + +### Verify Actions + +The *verify action* is the application-supplied callback invoked after the verify method completes, with the result of verification as an argument. Its signature is: + +```c +int verifyAction(whServerImgMgrContext* ctx, + const whServerImgMgrImg* img, + int verifyResult); +``` + +The action callback is the extension point through which the image manager produces an externally visible effect. Typical actions include releasing reset on the application core whose image was just verified, jumping to the verified image's entry point, latching a hardware "verified" signal, or simply logging the outcome. Because the action receives the verify result, it is the right place to implement both success and failure handling: a boot-time action might release reset on success and assert a fault pin on failure, while a periodic-integrity action might do nothing on success and force a reset on failure. + +A default no-op action, `wh_Server_ImgMgrVerifyActionDefault`, is provided for cases where the caller wants the verification result back but does not need any side effect. + +### wolfBoot Image Support + +wolfHSM understands the [wolfBoot](https://github.com/wolfSSL/wolfBoot) image header format natively so that a wolfHSM-equipped system can serve as the verifier for a wolfBoot-staged image without the client having to parse the header itself. Two wolfBoot image types are recognized: + +- `WH_IMG_MGR_IMG_TYPE_WOLFBOOT`: the signature is extracted from the wolfBoot TLV header and verified against a key resident in the server's keystore (identified by `keyId`). This corresponds to the standard wolfBoot signing model where the signing key is known in advance and provisioned into the HSM. +- `WH_IMG_MGR_IMG_TYPE_WOLFBOOT_CERT`: the image carries a certificate chain inside its wolfBoot header; the chain is verified against a trusted root in NVM (identified by `sigNvmId`) using the [certificate manager](#certificate-management), and the leaf certificate's public key is then used to verify the image signature. This matches the wolfBoot cert-chain mode and is the right choice when the signing key is rotated independently of the on-device trust anchor. + +In both cases the framework parses the header at `hdrAddr`, locates the signature TLV, validates the wolfBoot magic and public key hint, and feeds the appropriate `(image, key, signature)` triple into the wolfBoot verify method. The application's `verifyAction` is invoked exactly as for a raw image. + +### In-Place Access via DMA + +The image manager is fully [DMA](#dma-support)-aware. When `WOLFHSM_CFG_DMA` is enabled, the server reads the image header and payload directly from the address the caller registered, rather than having to copy the image through the comm message buffer. This is essential in practice because firmware images are typically megabytes in size, are already mapped into flash or RAM by the client, and would be impractical to ship over the comm protocol. + +The DMA path runs the standard [pre- and post-access callbacks](#pre-access-and-post-access-callbacks) around every read of the image, so any cache maintenance and address translation the platform requires is handled by the same port-supplied hook that DMA crypto uses. The DMA [address allowlist](#address-allowlisting) is consulted on every access, so the image manager cannot be coerced into reading from a region the port has not explicitly permitted. + +The image manager is not gated on `WOLFHSM_CFG_DMA`, and the same verification flow works in builds without DMA. In that configuration the server dereferences the registered image and header addresses directly instead of routing them through the pre- and post-access callbacks, so no address translation, cache maintenance, or allowlist check is performed. This mode is appropriate when the image already lives in memory the server can address natively — for example, an image resident in the server's own flash, or a single-address-space build where client and server share a memory map — and the caller is responsible for ensuring that `img->addr` and `img->hdrAddr` (and the lengths derived from them) refer to memory the server is permitted to read. Systems with a real client/server address-space boundary should enable DMA so the allowlist and translation callbacks apply. + +## Custom Callbacks + +wolfHSM's built-in feature set covers the common HSM workload, but it cannot anticipate every application-specific service that an embedded system may need to run in its secure environment. The **custom callback** feature is wolfHSM's extension point for these cases: an application registers one or more callback functions in the server's dispatch table, and clients invoke them by ID through the same request/response pipeline that carries every other wolfHSM operation. The wire framing, dispatch, and byte-order translation are handled by the library; the contents of the request and response payload are defined entirely by the application. + +The mechanism is intended for operations that naturally belong on the secure side of the trust boundary but are not part of the standard HSM API. Typical uses include proprietary key derivation routines that consume on-device material, application-specific authentication or monitoring protocols that need to run alongside the standard crypto offload, and anything else specific to hardware or proprietary application functionality. Because custom callbacks reuse the standard [communication layer](#communication-layer-and-transports), the same callback works over every supported transport, and the server's normal polling loop dispatches custom requests interchangeably with built-in ones. + +### Server-Side Registration and Dispatch + +A custom callback is a function of type `whServerCustomCb`, defined in `wolfhsm/wh_server.h`: + +```c +typedef int (*whServerCustomCb)( + whServerContext* server, + const whMessageCustomCb_Request* req, + whMessageCustomCb_Response* resp +); +``` + +The callback receives the dispatching server context, a translated request structure populated from the client's message, and an output response structure that wolfHSM will marshal back to the client when the callback returns. The callback populates `resp->data` with any output payload and may set `resp->err` to a wolfHSM error code; the callback's own return value is reported to the client as `resp->rc`. The library does not interpret either field — they are simply propagated to the client unchanged, with the convention that `rc` is invalid whenever `err` is non-zero. + +Callbacks are registered against a 16-bit **action ID** in the server's dispatch table: + +```c +int wh_Server_RegisterCustomCb(whServerContext* server, uint16_t action, + whServerCustomCb handler); +``` + +The table is statically sized at build time by `WOLFHSM_CFG_SERVER_CUSTOMCB_COUNT`, and the action ID is the index into it. Action IDs are application-defined: they have no meaning to wolfHSM beyond identifying which slot in the table to dispatch to. Registration may occur at any point in the server's lifetime — there is no requirement to install all callbacks before the server starts handling requests — so a server application can register or replace handlers dynamically based on its own state. A request that arrives for an action ID with no registered handler is reported back to the client with `resp->err == WH_ERROR_NOHANDLER`. + +### Client-Side Invocation + +A client invokes a registered callback by populating a `whMessageCustomCb_Request` and sending it through the standard split-transaction client API: + +- `wh_Client_CustomCbRequest()` dispatches the request to the server +- `wh_Client_CustomCbResponse()` polls for the matching response + +The two functions follow the same non-blocking pattern as the rest of the client API. Because dispatch is by action ID, the client must agree with the server on the meaning of each ID — application code typically defines a shared header that names every action ID and the data layout for each. + +To allow a client to query the server before invoking a callback whose presence it is unsure of, `wh_Client_CustomCbCheckRegistered()` returns `WH_ERROR_OK` if the supplied action ID has a handler installed and `WH_ERROR_NOHANDLER` if it does not. + +### Request and Response Messages + +The request and response messages are declared in `wolfhsm/wh_message_customcb.h`: + +```c +typedef struct { + uint32_t id; + uint32_t type; + whMessageCustomCb_Data data; +} whMessageCustomCb_Request; + +typedef struct { + uint32_t id; + uint32_t type; + int32_t rc; + int32_t err; + whMessageCustomCb_Data data; +} whMessageCustomCb_Response; +``` + +The `id` field carries the action ID, echoed back unchanged on the response for client-side bookkeeping. The `type` field is a hint to the callback describing how to interpret `data`. The response additionally carries `rc` (the callback's own return value) and `err` (a wolfHSM-defined error code populated by the framework or by the callback). + +The `data` field is a union that exposes three pre-defined shapes plus a raw buffer for application-specific schemas: + +```c +typedef union { + struct { uint32_t client_addr, client_sz, server_addr, server_sz; } dma32; + struct { uint64_t client_addr, client_sz, server_addr, server_sz; } dma64; + struct { uint8_t data[WOLFHSM_CFG_CUSTOMCB_LEN]; } buffer; +} whMessageCustomCb_Data; +``` + +The `dma32` and `dma64` variants carry pointer-and-length pairs for systems where the client wants the server to operate directly on its memory; the `buffer` variant is a fixed-size opaque payload, sized by `WOLFHSM_CFG_CUSTOMCB_LEN`, into which the application encodes whatever structure it likes. Custom callbacks do **not** automatically run the server's [DMA address-translation or allowlist machinery](#dma-support) for the `dma*` shapes — they are simply a convention for passing addresses, and the callback is responsible for any address handling or policy enforcement it needs. + +The `type` field is an enum (`whMessageCustomCb_Type`) whose first eight values are reserved by wolfHSM (`WH_MESSAGE_CUSTOM_CB_TYPE_DMA32`, `WH_MESSAGE_CUSTOM_CB_TYPE_DMA64`, the internal `WH_MESSAGE_CUSTOM_CB_TYPE_QUERY`, and several reserved slots), with application-defined types beginning at `WH_MESSAGE_CUSTOM_CB_TYPE_USER_DEFINED_START` (value 8). The framework recognizes the reserved types for byte-order translation of the corresponding `data` union variant, but **user-defined types are passed through unmodified, so a callback that needs to interoperate between endpoints of different endianness must perform its own translation**. + +### Constraints + +- The maximum number of registered callbacks is fixed at build time by `WOLFHSM_CFG_SERVER_CUSTOMCB_COUNT`. Valid action IDs are in the range `[0, WOLFHSM_CFG_SERVER_CUSTOMCB_COUNT)`. +- The data payload in either direction is bounded by `WOLFHSM_CFG_CUSTOMCB_LEN`. Operations whose payload exceeds this limit must either split across multiple requests or use the `dma32` / `dma64` shapes to point at a larger buffer in shared memory. +- The callback's return value is reported to the client through `resp->rc`; wolfHSM error semantics are conveyed by the library separately through `resp->err`. Callbacks should reserve `err` for genuine wolfHSM-defined failures (the `WH_ERROR_*` set) and use `rc` for application-defined results. + +### Example + +This example registers a single callback under action ID 0 that handles three kinds of request: a DMA32 payload describing a buffer in client memory, and two application-defined types each carrying a different struct in the `buffer` variant. A shared header defines the action ID, the user-defined type values, and the per-type payload structs: + +```c +/* my_custom_cb.h - shared between client and server */ +#include "wolfhsm/wh_message_customcb.h" + +#define MY_CUSTOM_CB_ID 0 + +enum { + MY_TYPE_A = WH_MESSAGE_CUSTOM_CB_TYPE_USER_DEFINED_START, + MY_TYPE_B, +}; + +typedef struct { int foo; int bar; } myCustomCbDataA; +typedef struct { int noo; int baz; } myCustomCbDataB; +``` + +The server registers the callback and then enters its standard request-handling loop: + +```c +#include "wolfhsm/wh_server.h" +#include "my_custom_cb.h" + +static int myCustomCb(whServerContext* server, + const whMessageCustomCb_Request* req, + whMessageCustomCb_Response* resp) +{ + int rc = 0; + resp->err = WH_ERROR_OK; + + switch (req->type) { + case WH_MESSAGE_CUSTOM_CB_TYPE_DMA32: { + uint8_t* ptr = (uint8_t*)((uintptr_t)req->data.dma32.client_addr); + rc = doWorkOnClientAddr(ptr, req->data.dma32.client_sz); + break; + } + case MY_TYPE_A: + rc = doWorkWithTypeA((myCustomCbDataA*)req->data.buffer.data); + break; + case MY_TYPE_B: + rc = doWorkWithTypeB((myCustomCbDataB*)req->data.buffer.data); + break; + default: + resp->err = WH_ERROR_BADARGS; + break; + } + return rc; +} + +int main(void) { + whServerContext serverCtx; + whServerConfig serverCfg = { /* server config */ }; + + wh_Server_Init(&serverCtx, &serverCfg); + wh_Server_RegisterCustomCb(&serverCtx, MY_CUSTOM_CB_ID, myCustomCb); + + while (1) { + wh_Server_HandleRequestMessage(&serverCtx); + } +} +``` + +The client verifies the callback is registered, then issues requests against the supported types: + +```c +#include "wolfhsm/wh_client.h" +#include "my_custom_cb.h" + +int main(void) { + whClientContext clientCtx; + whClientConfig clientCfg = { /* client config */ }; + int err = 0; + + wh_Client_Init(&clientCtx, &clientCfg); + + if (wh_Client_CustomCbCheckRegistered(&clientCtx, MY_CUSTOM_CB_ID, &err) + != WH_ERROR_OK) { + return -1; /* callback not registered on this server */ + } + + whMessageCustomCb_Request req = {0}; + whMessageCustomCb_Response resp = {0}; + + /* DMA-style invocation: hand the server a pointer in our address space */ + uint8_t buf[LARGE_SIZE] = { /* ... */ }; + req.id = MY_CUSTOM_CB_ID; + req.type = WH_MESSAGE_CUSTOM_CB_TYPE_DMA32; + req.data.dma32.client_addr = (uint32_t)((uintptr_t)buf); + req.data.dma32.client_sz = sizeof(buf); + wh_Client_CustomCbRequest(&clientCtx, &req); + wh_Client_CustomCbResponse(&clientCtx, &resp); + + /* Application-defined invocation: pass a struct in the buffer payload */ + myCustomCbDataA a = { /* ... */ }; + memset(&req, 0, sizeof(req)); + req.id = MY_CUSTOM_CB_ID; + req.type = MY_TYPE_A; + memcpy(req.data.buffer.data, &a, sizeof(a)); + wh_Client_CustomCbRequest(&clientCtx, &req); + wh_Client_CustomCbResponse(&clientCtx, &resp); +} +``` + +The same pattern scales naturally: each action ID can dispatch to a different callback, and a single callback can multiplex any number of `type` values to handle distinct sub-operations behind one ID. + +## Concurrency Support + +Many systems that integrate an HSM have multiple threads, cores, or subsystems issuing cryptographic operations at the same time. wolfHSM is designed to support these workloads while keeping the request/response protocol simple and predictable. Concurrency is achieved by the server processing requests from multiple **independent client sessions in parallel**: each session still handles requests sequentially, but the sessions themselves can be scheduled concurrently. wolfHSM v1.4.0 introduced the locking infrastructure that makes this safe; everything described in this section applies from that version onward. + +The library itself never spawns threads or assumes a particular runtime — the embedding application controls scheduling. wolfHSM provides the building blocks (per-session context structures, an internal lock abstraction, a transport interface that does not constrain dispatch) and lets the application choose between a simple round-robin loop, an event-driven dispatcher, or one thread per session. + +### Per-Context Threading Model + +The unit of concurrency on both sides of the connection is the context: + +- A `whClientContext` represents a single client session with the server. It must be used by **one thread at a time**: there is at most one in-flight request per session, and the matching response must be received before the next request can be issued. Applications that want to issue requests in parallel create multiple client contexts — typically one per worker thread — each connected to its own server session. +- A `whServerContext` represents the server side of a single client session. It is likewise **single-threaded from the caller's perspective**: the application must not call `wh_Server_HandleRequestMessage()` for the same context from two threads at once. Concurrency across sessions is achieved by creating multiple server contexts and dispatching them independently. + +Shared server-side state — the [NVM](#non-volatile-memory-nvm) object store, the [global key cache](#global-keys), the [authentication](#authentication-manager) state, and anything else that lives outside a single session — is protected by the internal lock infrastructure described below, so multiple server contexts can safely operate against the same backing store at the same time. + +### The Lock Abstraction + +To keep the core library free of OS dependencies, wolfHSM serializes access to shared resources through a generic lock abstraction declared in `wolfhsm/wh_lock.h`. Each shared resource embeds its own `whLock` instance, and the platform port supplies a callback table (`whLockCb`) that implements the four lifecycle operations against the native synchronization primitive of choice: + +- `init` / `cleanup` — set up and tear down the platform-specific lock state +- `acquire` / `release` — blocking exclusive lock and unlock + +Reference implementations cover the common cases: + +- **POSIX pthread mutexes** (`port/posix/posix_lock.h`), used by the POSIX server example and the threadsafe stress tests +- Ports for embedded RTOS targets are expected to register a callback table backed by FreeRTOS mutexes, atomic spinlocks, or any other primitive the platform already provides + +The feature is gated by `WOLFHSM_CFG_THREADSAFE`. When the macro is undefined, all locking calls compile to no-ops and the build has zero overhead from the concurrency machinery; Single-threaded deployments pay nothing for the abstraction. When the macro is defined but no callback table is registered for a given resource, the runtime also degrades to no-op locking; this is the right behavior for a thread-safe build that nonetheless drives a particular subsystem from a single thread. + +Locking is scoped per resource rather than global, so the topology is flexible: a deployment can share one mutex across all subsystems, give each shared resource its own mutex, or anywhere in between. + +### Concurrent Server Pattern + +The reference servers shipped with most platform ports use a single-threaded round-robin loop: one thread iterates over each registered `whServerContext` and processes at most one request per pass. This is the simplest model and is sufficient for many embedded deployments. + +When more concurrency is needed, a common pattern is to dedicate one thread to each server context: + +```c +int main(void) +{ + whServerContext serverA; + whServerContext serverB; + + whServerConfig serverConfigA = { /* server configuration */ }; + whServerConfig serverConfigB = { /* server configuration */ }; + + ThreadType threadA; + ThreadType threadB; + + /* Bind server configuration to the transports, NVM, and platform locks + * for shared resources - omitted for clarity */ + + /* Initialize server contexts, binding to configuration */ + wh_Server_Init(&serverA, &serverConfigA); + wh_Server_Init(&serverB, &serverConfigB); + + /* Create one processing thread per client connection */ + threadA = thread_create(serverThread, &serverA); + threadB = thread_create(serverThread, &serverB); + + thread_join(threadA); + thread_join(threadB); + + return 0; +} + +/* Blocking request-processing loop for a single client */ +void* serverThread(void* arg) +{ + whServerContext* server = (whServerContext*)arg; + int ret = WH_ERROR_OK; + + while (ret == WH_ERROR_OK) { + ret = wh_Server_HandleRequestMessage(server); + } + return NULL; +} +``` + +Each client is serviced by a dedicated thread, so requests from different clients can execute in parallel; scheduling priority is left entirely to the underlying OS or runtime. Production systems may replace the tight loop with a blocking wait on a transport-specific event or interrupt, calling `wh_Server_HandleRequestMessage()` only when work is available. Other valid strategies include dispatching from a transport interrupt, an event-driven reactor, or a worker pool — the API does not impose a scheduling model. + +### Transports and Concurrency + +Server-side concurrency is independent of the transport layer. The [transport](#communication-layer-and-transports) only moves bytes between client and server; it does not determine how the server schedules request handling. + +Because each client/server pair allows only one in-flight request, concurrency comes from running **multiple clients** in parallel, not from pipelining requests within a single session. Transports that serialize messages from many clients through a single shared channel — a hardware mailbox or MPSC ring buffer, for example — therefore do not increase server concurrency on their own; they would require an additional dispatch layer to fan messages back out to per-client server contexts. The highest practical concurrency on these platforms is typically achieved with the shared-memory transport, where each client owns a dedicated request/response buffer and the per-client server contexts can execute truly in parallel. + +### Crypto Under Concurrency + +How a cryptographic operation behaves under concurrency depends on whether it is served by software or hardware: + +- **Software crypto** runs entirely inside wolfCrypt using ephemeral per-request operation contexts, so it works naturally across concurrent server threads without any additional coordination. +- **Hardware crypto** can be approached in several ways. The most common is to rely on wolfCrypt's hardware abstraction layer, which serializes accelerator access using its own mutex mechanisms. Alternatively, the server application can restrict hardware access to a single privileged client by registering the hardware crypto callback only for that session — useful in safety-critical or real-time deployments where one client needs deterministic uncontended access. A third option is to use the [crypto affinity](#hardware-acceleration-and-crypto-affinity) feature and let clients themselves coordinate hardware use, which fits trusted-client environments with a cooperative allocation policy. + +The right choice depends on the platform, the accelerator, and the application's contention profile; wolfHSM intentionally supports all three. + +## Authentication Manager + +> **Note**: The authentication manager is currently **experimental** and has known issues. It is not yet suitable as a production security boundary. + +wolfHSM provides an optional **authentication manager**, enabled with `WOLFHSM_CFG_ENABLE_AUTHENTICATION`, that authenticates clients to the server and checks every incoming request against a per-user permission model. It is transport-agnostic: the same login flow and authorization check apply over every supported transport. + +The subsystem has three responsibilities: + +- **Authentication**: verify a client's identity using a PIN or an X.509 certificate +- **Session tracking**: bind the authenticated identity to the client's connection so that subsequent requests carry that identity automatically +- **Authorization**: on every request, check that the active identity is permitted to invoke the requested operation, and reject the request if not + +### Authentication Methods + +A client authenticates by calling `wh_Client_AuthLogin` with a username and one of two credential methods: + +- **PIN** (`WH_AUTH_METHOD_PIN`): the client supplies a PIN; the server hashes it with SHA-256 and compares the digest in constant time against the stored hash. +- **Certificate** (`WH_AUTH_METHOD_CERTIFICATE`): the client supplies a DER-encoded X.509 certificate; the server verifies it against the trusted CA stored as that user's credential using the [certificate manager](#certificate-management). Requires `WOLFHSM_CFG_CERTIFICATE_MANAGER`. + +A successful login returns a `whUserId` and records that identity as the active session for the connection. A failed login leaves the connection unauthenticated. `wh_Client_AuthLogout` clears the session, and the server also clears it automatically when the comm channel is closed so that a reconnecting client cannot inherit a stale identity. + +### Sessions and the Authorization Gate + +Each server context carries at most one authenticated session at a time — the `whAuthContext` embedded in the server context holds the active `whUserId` and the user's permissions. A client that needs to operate as a different user must log out and log back in. + +Every request received by the server is checked against the active session before it is dispatched to the corresponding subsystem handler: + +- If no user is logged in, only [comm-layer](#communication-layer-and-transports) requests and the `LOGIN` action are permitted; every other request returns `WH_AUTH_PERMISSION_ERROR`. +- If a user is logged in, `LOGOUT` is always permitted, and every other request is gated against the user's permissions. + +The gate lives in the server's front-end request handler, so individual subsystems do not need to perform their own auth checks. When the auth manager is compiled in but no auth context is configured at server initialization, the gate is skipped entirely and the server processes all requests without enforcement; this preserves compatibility with builds that do not need authentication. + +### Permissions + +A user's permissions are described by a `whAuthPermissions` record with three pieces: + +- **Group bitmap**: a per-group allow boolean. A request whose [message group](#communication-layer-and-transports) is not allowed is rejected without further checks. +- **Action bitmap**: for each allowed group, a 256-bit mask of which actions within that group are permitted. A request is allowed only if both its group and its action bit are set. +- **Admin flag**: a separate capability that gates user-management operations (`UserAdd`, `UserDelete`, `UserSetPermissions`) and cross-user logout. The core forbids non-admin sessions from promoting another user to admin regardless of backend behavior. + +The helper macros `WH_AUTH_SET_ALLOWED_GROUP`, `WH_AUTH_SET_ALLOWED_ACTION`, `WH_AUTH_CLEAR_ALLOWED_GROUP`, and `WH_AUTH_CLEAR_ALLOWED_ACTION` build permission sets at provisioning time, and `WH_AUTH_SET_IS_ADMIN` toggles the admin flag. + +`whAuthPermissions` also carries a small per-user `keyIds` allowlist and the data model includes a `CheckKeyAuthorization` callback intended to constrain which keys a user may exercise. Per-key authorization is a placeholder in the current implementation — the callback is defined but no crypto or key handler invokes it yet. + +### Pluggable Backend + +The authentication manager does not own the user database itself. All operations that read or modify user state — login, user add/delete, permission updates, credential updates — are dispatched through a `whAuthCb` callback table that the application supplies at server initialization. The storage backend is therefore a port-time decision: an in-memory table for development, an NVM-backed store for production, or a connector to an external identity service. + +wolfHSM ships with a default in-memory backend (`wh_auth_base.c`) used by the POSIX server example and the test suite. It holds up to `WH_AUTH_BASE_MAX_USERS` users in a static array, hashes PINs with SHA-256, and runs certificate verification against the user's stored CA when the certificate manager is built in. The base backend is intentionally simple and is **not** persisted to NVM — deployments that need user records to survive reset must supply their own backend. + +Custom backends implement the `whAuthCb` vtable and register their context through `whAuthConfig`. The core handles locking, session state, and the request-time authorization gate; the backend is responsible for storage, credential verification, and any backend-specific overrides through the optional `CheckRequestAuthorization` and `CheckKeyAuthorization` callbacks. These overrides see the core's preliminary decision and can flip it either way, which lets a backend layer additional policy (time-of-day restrictions, audit hooks, per-key allowlists) on top of the default group/action check. + diff --git a/docs/src/6-Utilities.md b/docs/src/6-Utilities.md new file mode 100644 index 000000000..c8f56924d --- /dev/null +++ b/docs/src/6-Utilities.md @@ -0,0 +1,105 @@ +# Utilities + +This chapter describes the auxiliary tools that ship alongside the wolfHSM client and server libraries. These utilities are not part of the runtime library but support the workflows around it — provisioning an NVM image for a device, measuring the performance of a configured server, and validating that a port or build configuration behaves correctly. Each section describes *what* the utility does and *how* to drive it on the supported platforms; the underlying subsystems exercised by these tools are covered in [5-Features.md](5-Features.md) and the API references. + +## Table of Contents + +- [NVM Provisioning Tool](#nvm-provisioning-tool) +- [Benchmark Suite](#benchmark-suite) + - [Benchmark Suite Overview](#benchmark-suite-overview) + - [Running Benchmarks on POSIX](#running-benchmarks-on-posix) + - [Running Benchmarks on Real Hardware](#running-benchmarks-on-real-hardware) +- [Test Suite](#test-suite) + - [Test Suite Overview](#test-suite-overview) + - [Running Tests on POSIX](#running-tests-on-posix) + - [Running Tests on Real Hardware](#running-tests-on-real-hardware) + +## NVM Provisioning Tool + +The NVM provisioning tool (`tools/whnvmtool/`) is a host-side utility that builds a pre-populated wolfHSM NVM image from a configuration file. It is intended for device provisioning: rather than having the server populate its NVM at runtime, the integrator describes the desired initial contents — a set of NVM objects and keys, each with its metadata ID, access permissions, flags, label, and a path to the binary payload — and the tool produces a single image file that can be programmed into the device's flash at manufacture or used in place to back a `whNvmFlash` provider in simulation. Currently the tool targets the `whNvmFlash` provider; the generated image is binary, and can be converted to Intel HEX with the standard `objcopy` workflow for use with automated programmers. + +Because the on-flash layout depends on build-time configuration, the tool must be compiled against the same wolfHSM version as the target server and with a matching `WOLFHSM_CFG_NVM_OBJECT_COUNT`, and the `--size` argument must match the server's `whNvmFlash` partition size. For the full configuration file schema, command-line options, hex conversion recipe, and test workflow, see [`tools/whnvmtool/README.md`](../tools/whnvmtool/README.md). + +## Benchmark Suite + +### Benchmark Suite Overview + +The benchmark suite (`benchmark/`) is a standalone wolfHSM client application that measures the round-trip cost of cryptographic operations against a configured wolfHSM server, from the perspective of a client. The numbers it reports therefore reflect the end-to-end performance a real client would observe in the same runtime environment: server-side computation, transport overhead, and any port-specific acceleration all rolled together. + +The benchmark app consists of individual modules that each measure the various cryptographic algorithms that wolfHSM exposes. Each module runs its operation a configurable number of iterations, and the framework reports either operations per second or throughput in bytes per second, depending on the algorithm class. + +The same client application builds and runs against any supported port: on POSIX the client and server run in separate threads of the host process, and on embedded targets the application links into the port's runtime alongside a board-specific timer and `printf`. Iteration counts, data buffer sizes, DMA buffers, and timing/printing hooks are all overridable through `WOLFHSM_CFG_BENCH_*` macros so the suite can be tuned to the constraints of the target. + +For the full list of configuration macros, the module interface, instructions for adding a new benchmark, and the internal layout of the framework, see [`benchmark/README.md`](../benchmark/README.md). + +### Running Benchmarks on POSIX + +To compile and run the benchmark application on a POSIX host system using the POSIX server port: + +```sh +cd benchmark +make clean +make +make run +``` + +To use the DMA versions of algorithms that support it, pass the `DMA=1` environment variable to the build command: + +``` +make clean +make DMA=1 +make run +``` + + +### Running Benchmarks on Real Hardware + +Each hardware port ships with its own instructions for compiling and running the benchmark application on the target — refer to the README under the corresponding `port///` directory for board-specific details such as toolchain setup, linker scripts, and timer configuration. At a high level, integrating the suite into a port-specific application reduces to three steps. + +**1. Compile the framework sources alongside the application.** Add `benchmark/wh_bench.c`, `benchmark/wh_bench_ops.c`, and `benchmark/wh_bench_data.c` to the build, together with the per-algorithm modules under `benchmark/bench_modules/`. The standalone CLI wrapper in `benchmark/wh_bench_main.c` assumes a POSIX command-line environment and is typically omitted on embedded targets, where the application invokes the benchmark entry points directly. + +**2. Define the required configuration macros.** At a minimum, define `WOLFHSM_CFG_BENCH_ENABLE` to compile the suite in. On non-POSIX targets the framework has no portable way to obtain wall-clock time, so the port must supply a microsecond timer with the signature `uint64_t timer(void)` and point `WOLFHSM_CFG_BENCH_CUSTOM_TIME_FUNC` at its name. If the platform lacks a working `printf`, define `WOLFHSM_CFG_BENCH_CUSTOM_PRINTF` to a port-supplied formatted-print routine. Iteration counts (`WOLFHSM_CFG_BENCH_CRYPT_ITERS`, `WOLFHSM_CFG_BENCH_KG_ITERS`, `WOLFHSM_CFG_BENCH_PK_ITERS`), buffer sizes (`WOLFHSM_CFG_BENCH_DATA_BUFFER_SIZE`, `WOLFHSM_CFG_BENCH_DMA_BUFFER_SIZE`), and buffer placement (`WOLFHSM_CFG_BENCH_CUSTOM_DATA_BUFFERS`, `WOLFHSM_CFG_BENCH_CUSTOM_DMA_BUFFER`) are all optional overrides used to fit the suite into the target's resource budget. + +**3. Drive the benchmark client from the application.** The benchmark utility is purely client-side: it issues requests to a wolfHSM server and times the responses. The server it talks to can be any wolfHSM server instance that is listening on the same transport — most commonly the application's own production server, which exercises the exact configuration whose performance you are trying to measure. For convenience the suite also exposes `wh_Bench_ServerCfgLoop(whServerConfig*)`, a minimal server processing loop that dispatches incoming client requests until the client disconnects, which can be used in lieu of a production server when one is not yet available. On the client side, `wh_Bench_ClientCfg(whClientConfig*, int transport)` initializes a client, runs the full suite, and tears the client down; `wh_Bench_ClientCtx(whClientContext*, int transport)` is the equivalent entry point when the application already manages the client context lifecycle. How the client and the server are scheduled relative to each other — separate cores, separate tasks, or cooperatively from a main loop — is determined by the port. + +## Test Suite + +### Test Suite Overview + +The test suite (`test/`) is a standalone wolfHSM application that exercises the library's unit and integration tests against a configured client and server. Tests are grouped one-per-component (NVM, comm, crypto, keystore, certificates, SHE, image manager, authentication, etc.), each in its own `wh_test_*.c` source file, and are wired together by the top-level driver in `wh_test.c`. The suite validates wolfHSM itself rather than wolfCrypt; the full wolfCrypt test suite can additionally be run as a wolfHSM client by enabling `WOLFHSM_CFG_TEST_WOLFCRYPTTEST`, which exercises the crypto callback path end-to-end. + +The same test sources build for POSIX hosts and for embedded targets. Tests that depend on POSIX facilities (sockets, pthreads, file-backed flash) are compiled in only when `WOLFHSM_CFG_TEST_POSIX` is defined, so an embedded port pulls in just the portable subset and selects whichever modules its configuration supports. Output goes through `WOLFHSM_CFG_PRINTF` and assertions go through `WOLFHSM_CFG_TEST_ASSERT_FUNC`, so both can be redirected to port-supplied implementations. + +For the full list of test modules, supported build options, and code coverage workflow, see [`test/README.md`](../test/README.md). + +### Running Tests on POSIX + +To compile and run the full test suite on a POSIX host system using the POSIX server port: + +```sh +cd test +make clean +make +make run +``` + +Feature-specific builds are selected with the same makefile variables documented in [11-Configuration.md](11-Configuration.md) — for example `DMA=1`, `SHE=1`, `AUTH=1`, `TLS=1`, `THREADSAFE=1`, `TESTWOLFCRYPT=1`. Development and CI builds also commonly set `ASAN=1` (address sanitizer), `TSAN=1` (thread sanitizer, mutually exclusive with `ASAN`), `DEBUG=1`, or `COVERAGE=1` (instruments the build for `gcovr`; see `make coverage`). + +To build a client-only driver that connects to an already-running server over TCP (or TLS when `TLS=1` is set), pass `CLIENT_ONLY=1`: + +```sh +make clean +make CLIENT_ONLY=1 +make run +``` + + +### Running Tests on Real Hardware + +Each hardware port ships with its own instructions for compiling and running the test suite on the target — refer to the README under the corresponding `port///` directory for board-specific details. At a high level, integrating the suite into a port-specific application reduces to three steps. + +**1. Compile the framework sources alongside the application.** Add `test/wh_test.c`, `test/wh_test_common.c`, and the per-module `test/wh_test_*.c` files for the components you wish to validate. Each module is independent, so an embedded port can pick a subset (e.g. crypto, keystore, certificates) and omit modules whose features the build does not enable. Leave `WOLFHSM_CFG_TEST_POSIX` undefined so the POSIX-only paths (sockets, pthreads, file-backed flash) are excluded. + +**2. Define the required configuration macros.** Define `WOLFHSM_CFG_TEST_UNIT_NO_MAIN` to suppress the default `main()` so the application can call the test entry points itself. If the platform lacks a working stdlib `assert()`, define `WOLFHSM_CFG_TEST_ASSERT_FUNC` to a port-supplied assertion routine; output is already routed through `WOLFHSM_CFG_PRINTF` (see [11-Configuration.md](11-Configuration.md)). Enable individual test categories by defining the same feature macros that gate them in the library (e.g. `WOLFHSM_CFG_DMA`, `WOLFHSM_CFG_SHE_EXTENSION`, `WOLFHSM_CFG_TEST_WOLFCRYPTTEST`). + +**3. Drive the test entry points from the application.** Like the benchmark suite, the tests are purely client-side: they issue requests to a wolfHSM server and validate the responses. The server can be any wolfHSM server listening on the same transport — typically the application's own production server. To run the full client-side test set against a client the application has already configured, call `whTest_ClientConfig(whClientConfig*)`; individual modules expose their own client entry points (`whTest_CryptoClientConfig`, `whTest_SheClientConfig`, `whTest_TimeoutClientConfig`, etc.) when only a subset is desired. The suite also exposes `whTest_ServerCfgLoop(whServerConfig*)`, a minimal server processing loop suitable for in-process or co-resident-core test runs when no production server is available. How the client and server are scheduled relative to each other — separate cores, separate tasks, or cooperatively from a main loop — is determined by the port. diff --git a/docs/src/7-Examples.md b/docs/src/7-Examples.md new file mode 100644 index 000000000..89858cae3 --- /dev/null +++ b/docs/src/7-Examples.md @@ -0,0 +1,95 @@ +# Examples + +This chapter describes the example applications and demo code that ship with wolfHSM. These are *not* part of the runtime library — they live under `examples/` and exist so that an integrator can see a complete, end-to-end wolfHSM client and server running on a host, and so that a developer can find a working, runnable answer to the question "how do I do *X* in wolfHSM?" The example applications wire together the transport, comm, NVM, and crypto subsystems described in [5-Features.md](5-Features.md); the demos are the port-agnostic exercises those applications run. + +## Table of Contents + +- [POSIX Example Server and Client](#posix-example-server-and-client) + - [Building and Running](#building-and-running) + - [Transport Selection](#transport-selection) + - [Server NVM Initialization](#server-nvm-initialization) +- [Demo Client Library](#demo-client-library) + - [Philosophy](#philosophy) + - [Demo Categories](#demo-categories) + +## POSIX Example Server and Client + +The POSIX example consists of two simple host applications — a server (`examples/posix/wh_posix_server/`) and a client (`examples/posix/wh_posix_client/`) — that talk to each other over a transport of the user's choosing. They are intentionally minimal: each is a single `main()` that configures the appropriate context, calls the wolfHSM init routines, and then either services requests (server) or issues them (client). They are the smallest complete demonstration of a wolfHSM deployment on a system with a real OS, and they double as the reference for what a port-specific server and client application need to do at startup. + +The server runs as a foreground process that waits for a client to connect on the selected transport, dispatches incoming requests through `wh_Server_HandleRequestMessage()`, and exits when the client disconnects. The client connects to a running server, sends a fixed sequence of `Echo` messages to verify the transport is alive, and — if invoked with `--test` — runs the full demo client library against the server before disconnecting. Both applications share a small set of helper configuration sources (`wh_posix_*_cfg.c`) that build the per-transport `whClientConfig` / `whServerConfig` structures; the configuration helpers are deliberately separated from the `main()` driver so the same transport plumbing can be lifted into an integrator's own application. + +### Building and Running + +The applications expect to find wolfHSM and wolfSSL as sibling directories. From a clean checkout: + +```sh +cd examples/posix/wh_posix_server && make +cd examples/posix/wh_posix_client && make +``` + +This produces `wh_posix_server.elf` and `wh_posix_client.elf` under each directory's `Build/`. Launch the server in one shell and the client in another: + +```sh +./examples/posix/wh_posix_server/Build/wh_posix_server.elf +./examples/posix/wh_posix_client/Build/wh_posix_client.elf +``` + +To exercise the demo client library against the running server, pass `--test` to the client: + +```sh +./wh_posix_client.elf --test +``` + +The full set of supported build options for the example applications is documented in [11-Configuration.md](11-Configuration.md); the same `DMA=1`, `SHE=1`, `AUTH=1`, `TLS=1` knobs that gate features in the test and benchmark suites apply here. + +### Transport Selection + +Both applications accept a `--type ` argument that selects which configuration helper builds the comm context. The supported types depend on how the example was compiled: + +- `tcp` — POSIX TCP socket on `127.0.0.1:23456` (the default). +- `shm` — POSIX inter-process shared memory. +- `dma` — Shared memory plus a wolfSSL static-memory DMA buffer, where requests pass DMA offsets instead of inline payloads. Requires `WOLFSSL_STATIC_MEMORY`. +- `tls` — wolfSSL TLS over TCP. Requires `WOLFHSM_CFG_TLS`. +- `psk` — TLS with a pre-shared key. Requires `WOLFHSM_CFG_TLS` and `!NO_PSK`. + +The server and client must be started with the same `--type`. Each transport is defined by a corresponding `wh_PosixServer_ExampleConfig()` / `wh_PosixClient_ExampleConfig()` helper that an integrator can copy verbatim into their own application. + +### Server NVM Initialization + +By default the server starts with an empty NVM. Two arguments let the example pre-populate it before the dispatch loop begins: + +- `--key --id [--client ]` loads a single DER-encoded key from disk and caches it under the specified key ID for the specified client (default client 12). +- `--nvminit ` reads a [whnvmtool](6-Utilities.md#nvm-provisioning-tool)-style configuration file and loads every key and object it lists. + +Both forms are conveniences specific to the POSIX example — in a real deployment, NVM contents typically come from a pre-built image programmed at manufacture (see [6-Utilities.md](6-Utilities.md)). They are included here so the demo client library can exercise paths that assume keys or objects already exist on the server. + +## Demo Client Library + +The demo client library (`examples/demo/client/`) is a collection of self-contained C functions, one file per feature area, that drive the wolfHSM client API through a representative workflow for each subsystem. The library is port-agnostic: it depends only on a fully initialized `whClientContext*` and the wolfHSM client headers. The POSIX example client is one consumer of this library, but the same code compiles and runs from any port that can hand it an initialized client context — and that is the point. + +### Philosophy + +The demos are **living documentation expressed as code**. They are written to answer "how do I do *X* in wolfHSM?" by being the shortest complete program that does *X*, with the surrounding setup and teardown spelled out so the reader can copy and adapt without guessing. Because the library is checked into the same repository as the runtime code, it cannot fall out of sync with the API the way prose documentation can: if a function signature changes, the demos break the build, and the demos are updated as part of the same change. + +Two consequences of that philosophy are worth calling out explicitly, because they make the demo code look different from the rest of the codebase: + +- **Aggressive inline commentary at the expense of error checking.** A production caller would propagate every return code, free every allocation on every path, and handle every edge case. The demos generally check return codes only enough to bail out cleanly at the top level and instead spend their lines explaining *why* the next call is shaped the way it is. The goal is for the reader to walk away understanding the *intent* of the sequence, not a robust template they can paste into production. Production code should treat the demos as a starting point and add the missing rigor. +- **Clarity over efficiency.** The demos preference straight-line code, fixed-size local buffers, and explicit step-by-step sequences over the more compact or efficient idioms a production integration would use. Where there is a tension between "how a developer learns this" and "how a developer should ship this," the demos pick the former. + +Each demo function takes a `whClientContext*` and returns `0` on success or a wolfHSM error code on failure, so the same set of demos can be wired into an integrator's own application — not just the POSIX example client — by calling them after their `wh_Client_Init()` succeeds. The top-level `wh_DemoClient_All()` in `wh_demo_client_all.c` runs the full suite in order and is what the POSIX client invokes when launched with `--test`. + +### Demo Categories + +The demos are organized by wolfHSM feature area, with one source/header pair per area. Each category lives in `examples/demo/client/wh_demo_client_.c` and is gated by the same build-time configuration macros as the underlying feature, so a demo for a feature that is not compiled in is simply elided from the suite. + +- **NVM** (`wh_demo_client_nvm.c`) — Adding, reading back, enumerating, and reclaiming non-volatile objects through the client NVM API. Shows the full lifecycle of an NVM object including metadata, access flags, and reclamation of freed space. +- **Keystore** (`wh_demo_client_keystore.c`) — Caching raw key material in the server, querying cached keys by ID and label, committing cached keys to NVM, and using a cached key from a wolfCrypt operation. The reference for the basic key-cache / NVM-backing-store flow described in [5-Features.md](5-Features.md#keystore). +- **Key Wrapping** (`wh_demo_client_keywrap.c`) — Importing wrapped key blobs, unwrapping them on the server, and using the resulting cached key without ever exposing plaintext key material on the client. Gated by `WOLFHSM_CFG_KEYWRAP`. +- **Cryptography** (`wh_demo_client_crypto.c`) — The largest demo file: end-to-end signing, verification, key agreement, symmetric encryption, KDF, and MAC examples for each algorithm wolfHSM supports through the crypto callback path. Covers RSA, ECC, Curve25519, AES-CBC, AES-GCM, HKDF, CMAC, and CMAC-KDF, each in both an "import a key as part of the call" form and a "use a key already in the cache" form so the reader can see the two shapes of the API side-by-side. +- **Secure Boot** (`wh_demo_client_secboot.c`) — A complete provisioning-and-boot workflow: generating a server-side signing keypair, hashing an image with SHA-256, signing the hash, and later re-verifying it on boot. Acts as a worked example of how the keystore, NVM, and crypto subsystems compose for an image-authentication use case. +- **Authentication** (`wh_demo_client_auth.c`) — Logging in as a user, exercising role-based access controls on protected operations, and logging out. Gated by `WOLFHSM_CFG_ENABLE_AUTHENTICATION`. The POSIX example client logs in as an `admin` user after running the auth demos so subsequent demos run with full privileges. +- **Counters** (`wh_demo_client_counter.c`) — Reading, incrementing, and resetting non-volatile monotonic counters through the client counter API. +- **wolfCrypt Test Passthrough** (`wh_demo_client_wctest.c`) — Runs the standard wolfCrypt unit test suite as a wolfHSM client, exercising every supported algorithm through the crypto callback path. Gated by `WH_DEMO_WCTEST`. This is the same surface validated by the test utility in [6-Utilities.md](6-Utilities.md#test-suite), but invoked from inside an example application rather than the standalone test runner. +- **wolfCrypt Benchmark Passthrough** (`wh_demo_client_wcbench.c`) — Runs the standard wolfCrypt benchmark as a wolfHSM client. Useful for sanity-checking that an integration produces sensible numbers; for thorough measurement use the dedicated [benchmark suite](6-Utilities.md#benchmark-suite). + +To add a demo for a new feature, drop a new `wh_demo_client_.c/.h` pair into `examples/demo/client/`, gate it on the appropriate `WOLFHSM_CFG_*` macro, and call it from `wh_DemoClient_All()` under the same guard. The new demo is then automatically picked up by every port-specific example application that builds against the demo library. diff --git a/docs/src/8-Integration.md b/docs/src/8-Integration.md new file mode 100644 index 000000000..417fb307c --- /dev/null +++ b/docs/src/8-Integration.md @@ -0,0 +1,68 @@ +# Integration + +This chapter describes how wolfHSM integrates with other wolfSSL libraries and products. wolfHSM is designed to slot into an existing wolfSSL-based stack rather than replace it: the same wolfCrypt API drives the cryptographic work, the same TLS stack secures network links, and the same firmware authentication tooling produces signed images. The sections below summarize the integration surface with each library, and refer the reader to the documentation for that library when the details live there. + +## Table of Contents + +- [wolfSSL and wolfCrypt](#wolfssl-and-wolfcrypt) +- [wolfBoot](#wolfboot) + - [wolfBoot as a wolfHSM Client](#wolfboot-as-a-wolfhsm-client) + - [wolfBoot on the wolfHSM Server](#wolfboot-on-the-wolfhsm-server) + - [Verifying wolfBoot Images from a Server Application](#verifying-wolfboot-images-from-a-server-application) +- [wolfIP](#wolfip) +- [wolfGuard (WireGuard Transport)](#wolfguard-wireguard-transport) +- [wolfSentry](#wolfsentry) +- [wolfTPM](#wolftpm) + +## wolfSSL and wolfCrypt + +wolfSSL/wolfCrypt is a hard dependency of wolfHSM and is used in three distinct roles, all of which are covered in detail elsewhere in this manual: + +- **Cryptographic provider** on both sides of the client/server boundary. The client crypto callback routes wolfCrypt API calls to the server, and the server uses wolfCrypt to perform the actual cryptographic work. See [Cryptography and wolfCrypt Integration](5-Features.md#cryptography-and-wolfcrypt-integration) and the broader discussion in [Architecture](4-Architecture.md). +- **Certificate and ASN.1 handling** behind the certificate manager — chain verification, leaf public key extraction, and acert parsing are all delegated to wolfSSL. See [Certificate Management](5-Features.md#certificate-management). +- **TLS transport** for client/server links that cross an untrusted network. The `posix_transport_tls` reference transport wraps the plain POSIX TCP transport in a wolfSSL-secured session (with optional PSK), with no change to the framing above. See [Transport Backends](5-Features.md#transport-backends). + +Because wolfCrypt is already covered exhaustively in the architecture and feature chapters, this chapter does not repeat the integration details. Treat wolfSSL/wolfCrypt as a prerequisite of every wolfHSM build, and refer to those chapters when configuring it. + +## wolfBoot + +[wolfBoot](https://github.com/wolfSSL/wolfBoot) is wolfSSL's portable secure bootloader. wolfBoot and wolfHSM are designed to work together: when both are present on a platform, wolfBoot can use wolfHSM for all of the cryptographic work and key storage it performs during firmware authentication, eliminating the need for wolfBoot to handle key material directly. The integration is bidirectional — wolfBoot can act as a wolfHSM *client*, or it can host an embedded wolfHSM *server* — and a wolfHSM server application can independently verify wolfBoot-formatted images on behalf of its own clients. This section summarizes the integration surface; For more information, refer to [wolfBoot's `docs/wolfHSM.md`](https://github.com/wolfSSL/wolfBoot/blob/master/docs/wolfHSM.md), which covers per-platform configuration, build options, and HAL requirements in detail. + +### wolfBoot as a wolfHSM Client + +In **client mode**, wolfBoot is a wolfHSM client like any other application: it links the wolfHSM client library, opens a transport to a separate wolfHSM server, and offloads firmware signature verification (and the hashing that feeds it) through the standard wolfCrypt crypto callback. Image signing keys are provisioned onto the server in advance, and wolfBoot references them by `keyId` rather than holding the key material itself. The build option `WOLFBOOT_ENABLE_WOLFHSM_CLIENT` selects this mode, and `WOLFBOOT_USE_WOLFHSM_PUBKEY_ID` together with the keygen `--nolocalkeys` option produces a keystore that contains only key metadata, with the actual public key resident on the HSM. + +The wolfBoot HAL supplies the wolfHSM client context, the transport configuration, and the device/key identifiers (`hsmClientCtx`, `hsmDevIdHash`, `hsmDevIdPubKey`, `hsmKeyIdPubKey`) that wolfBoot uses to direct crypto callback calls at the right wolfHSM resources. wolfBoot's algorithm support over wolfHSM covers RSA-2048/3072/4096, ECDSA P-256/P-384/P-521, ML-DSA at security levels 2/3/5, and SHA-256, with the actual set available on a given target gated by the HAL. + +This mode is the natural fit for a multi-core SoC where wolfBoot runs on the application core and the wolfHSM server runs on a separate secure core, communicating over the platform's shared-memory or mailbox transport. It is also the mode used by the wolfBoot simulator, which talks to the example POSIX TCP server over loopback. + +### wolfBoot on the wolfHSM Server + +In **server mode**, wolfBoot links the wolfHSM *server* library and runs an embedded wolfHSM server inside the bootloader itself, using the wolfHSM server API directly rather than over a transport. There is no external HSM in this configuration; wolfBoot owns the NVM, the keystore, and the crypto subsystem, and any wolfHSM features it needs (notably certificate chain verification) are invoked locally. The build option `WOLFBOOT_ENABLE_WOLFHSM_SERVER` selects this mode, and is mutually exclusive with the client-mode option. + +Server mode is the right choice when the bootloader has no separate secure core to delegate to but still benefits from wolfHSM's keystore, certificate manager, and NVM abstraction — for example, when the application that runs after boot is itself a wolfHSM server and the bootloader needs to share its provisioning. The HAL supplies the server context, NVM initialization, and the NVM IDs of any pre-provisioned root CA certificates used for certificate chain verification. + +### Verifying wolfBoot Images from a Server Application + +Independently of which mode wolfBoot itself is running in, a wolfHSM **server application** can verify wolfBoot-formatted images on behalf of its clients using the [image manager](5-Features.md#image-manager). The image manager understands the wolfBoot TLV header natively and exposes two verify methods specifically for wolfBoot images: + +- `WH_IMG_MGR_IMG_TYPE_WOLFBOOT` — verifies the image against a key resident in the server's keystore, matching wolfBoot's standard signing model. +- `WH_IMG_MGR_IMG_TYPE_WOLFBOOT_CERT` — verifies a certificate chain embedded in the wolfBoot header against a trusted root in NVM and then uses the leaf public key to verify the image, matching wolfBoot's cert-chain signing mode. + +The full mechanism — header parsing, signature TLV extraction, public-key-hint validation, and the DMA-aware payload reads that make verifying multi-megabyte images practical — is documented in [wolfBoot Image Support](5-Features.md#wolfboot-image-support). The practical upshot is that a wolfBoot client and a wolfHSM-equipped system can share a single image format and a single trust anchor: the same `.bin` that wolfBoot would verify locally can be verified by a wolfHSM server through the image manager, and the same root CA provisioned on the HSM works for both flows. + +## wolfIP + +Coming soon ;-) + +## wolfGuard (WireGuard Transport) + +Coming soon ;-) + +## wolfSentry + +Coming soon ;-) + +## wolfTPM + +Coming soon ;-) diff --git a/docs/src/9-API-docs-client.md b/docs/src/9-API-docs-client.md new file mode 100644 index 000000000..6728ac283 --- /dev/null +++ b/docs/src/9-API-docs-client.md @@ -0,0 +1,7 @@ +# Client API Reference + +This chapter is the complete reference for the wolfHSM **client** API. It is generated directly from the documentation comments in the public client headers ([`wolfhsm/wh_client.h`](../../wolfhsm/wh_client.h), [`wolfhsm/wh_client_crypto.h`](../../wolfhsm/wh_client_crypto.h), and [`wolfhsm/wh_client_she.h`](../../wolfhsm/wh_client_she.h)), so it always tracks the source. For a conceptual, feature-oriented walkthrough of what these functions are for, see [Features](5-Features.md); this chapter documents the precise signatures, parameters, and return values. + +- **[Client API](wh__client_8h.md)** — client context lifecycle, communication, NVM, keystore, certificate, image-manager, and counter operations (`wolfhsm/wh_client.h`). +- **[Client Crypto API](wh__client__crypto_8h.md)** — split-transaction, non-blocking crypto request/response calls (`wolfhsm/wh_client_crypto.h`). +- **[Client SHE API](wh__client__she_8h.md)** — AUTOSAR SHE (Secure Hardware Extension) client interface: key update protocol (M1–M5), encrypted message handling, secure boot, deterministic PRNG, and status register access (`wolfhsm/wh_client_she.h`). diff --git a/docs/src/appendix01.md b/docs/src/appendix01.md deleted file mode 100644 index 72ef50d59..000000000 --- a/docs/src/appendix01.md +++ /dev/null @@ -1,93 +0,0 @@ -# wolfHSM API reference - -## Key Revocation - -### wh_Client_KeyRevokeRequest - -Send a key revocation request to the server (non-blocking). - -This function prepares and sends a revoke request for the specified key ID. It -returns after the request is sent; use `wh_Client_KeyRevokeResponse()` to -retrieve the result. - -Parameters: - -- `c`: Client context. -- `keyId`: Key ID to revoke. - -Return values: - -- `WH_ERROR_OK` on successful request send. -- A negative error code on failure. - -Error codes: - -- `WH_ERROR_BADARGS` if `c` is NULL or `keyId` is invalid. -- Propagates comm layer errors on send failure. - -### wh_Client_KeyRevokeResponse - -Receive a key revocation response. - -This function polls for the revoke response and returns `WH_ERROR_NOTREADY` -until the server reply is available. - -Parameters: - -- `c`: Client context. - -Return values: - -- `WH_ERROR_OK` on success. -- `WH_ERROR_NOTREADY` if the response has not arrived. -- A negative error code on failure. - -Error codes: - -- `WH_ERROR_BADARGS` if `c` is NULL. -- Server error codes such as `WH_ERROR_NOTFOUND`. - -### wh_Client_KeyRevoke - -Revoke a key using a blocking request/response. - -This helper sends a revoke request and waits for the response. - -Parameters: - -- `c`: Client context. -- `keyId`: Key ID to revoke. - -Return values: - -- `WH_ERROR_OK` on success. -- A negative error code on failure. - -Error codes: - -- Any error code returned by `wh_Client_KeyRevokeRequest()` or - `wh_Client_KeyRevokeResponse()`. - -### wh_Server_KeystoreRevokeKey - -Revoke a key by updating its metadata. - -This server-side function marks a key as non-modifiable and clears all usage -flags. If the key exists in NVM, the metadata update is committed so the revoke -state persists. - -Parameters: - -- `server`: Server context. -- `keyId`: Key ID to revoke. - -Return values: - -- `WH_ERROR_OK` on success. -- A negative error code on failure. - -Error codes: - -- `WH_ERROR_BADARGS` if parameters are invalid. -- `WH_ERROR_NOTFOUND` if the key is missing. -- Propagates NVM/storage errors (for example `WH_ERROR_NOSPACE`). diff --git a/docs/src/chapter01.md b/docs/src/chapter01.md deleted file mode 100644 index d35610c29..000000000 --- a/docs/src/chapter01.md +++ /dev/null @@ -1,17 +0,0 @@ -# Introduction - -This manual is written as a technical guide to the wolfHSM embedded hardware security module library. It -will explain how to build and get started with wolfHSM, provide an overview of build -options, features, portability enhancements, support, and much more. - -You can find the PDF version of this document [here](https://www.wolfssl.com/documentation/manuals/wolfhsm/wolfHSM-Manual.pdf). - -## Why Choose wolfHSM? - -Automotive HSMs (Hardware Security Modules) dramatically improve the security of cryptographic keys and processing. They achieve this by isolating signature verification and cryptographic execution, the very foundations of security, into physically independent processors. These HSMs are not just recommended, but often mandatory for ECUs that demand robust security. In line with this, wolfSSL has seamlessly integrated our popular, rigorously tested, and industry-leading cryptographic library to operate in widely used Automotive HSMs such as Aurix Tricore TC3XX. WolfHSM, with its sole dependency on wolfCrypt, ensures portability across almost any runtime environment. It also facilitates a user-friendly client interface, allowing direct utilization of wolfCrypt APIs. - -wolfHSM provides a portable and open-source abstraction to hardware cryptography, non-volatile memory, and isolated secure processing, maximizing security and performance for ECUs. By integrating the wolfCrypt software crypto engine on hardware HSMs like Infineon Aurix Tricore TC3XX, Chinese-mandated government algorithms like SM2, SM3, and SM4 are available. Additionally, Post Quantum Cryptography algos like Kyber, LMS, XMSS, and others are easily made available to automotive users to meet customer requirements. At the same time, when hardware cryptographic processing is available on the HSM, we consume it to enhance performance. - -wolfBoot is a mature, portable, secure bootloader solution designed for bare-metal bootloaders and equipped with failsafe NVM controls. It offers comprehensive firmware authentication and update mechanisms, leveraging a minimalistic design and a tiny HAL API, which makes it fully independent from any operating system or bare-metal application. wolfBoot efficiently manages the flash interface and pre-boot environment, accurately measures and authenticates applications, and utilizes low-level hardware cryptography as needed. wolfBoot can use the wolfHSM client to support HSM-assisted application core secure boot. Additionally, wolfBoot can run on the HSM core to ensure the HSM server is intact, offering a secondary layer of protection. This setup ensures a secure boot sequence, aligning well with the booting processes of HSM cores that rely on NVM support. - - diff --git a/docs/src/chapter02.md b/docs/src/chapter02.md deleted file mode 100644 index a6810c026..000000000 --- a/docs/src/chapter02.md +++ /dev/null @@ -1,90 +0,0 @@ -# Overview - -wolfHSM is a software framework that provides a unified API for HSM operations -such as cryptographic operations, key management, and non-volatile storage. -It is designed to improve portability of code related to HSM applications, -easing the challenge of moving between hardware with enhanced security -features without being tied to any vendor-specific library calls. It -dramatically simplifies client applications by allowing direct use of wolfCrypt -APIs, with the library automatically offloading all sensitive cryptographic -operations to the HSM core as remote procedure calls with no additional logic -required by the client app. - -Although initially targeted to automotive-style HSM-enabled microcontrollers, -wolfHSM provides an extensible solution to support future capabilities of -platforms while still supporting standardized interfaces and protocols such as -PKCS11 and AUTOSAR SHE. It has no external dependencies other than wolfCrypt and -is portable to almost any runtime environment. - -## Features - -- Secure non-volatile object storage with user-based permissions -- Cryptographic key management with support for hardware keys -- Hardware cryptographic support for compatible devices -- Fully asynchronous client API -- Flexible callback architecture enables custom use cases without modifying library -- Use wolfCrypt APIs directly on client, with automatic offload to HSM core -- Image manager to support chain of trust -- Integration with AUTOSAR -- Integration with SHE+ -- PKCS11 interface available -- TPM 2.0 interface available -- Secure OnBoard Communication (SecOC) module integration available -- Certificate handling -- Symmetric and Asymmetric keys and cryptography -- Supports "crypto agility" by providing every algorithm implemented in wolfCrypt, not just those implemented by your silicon vendor -- FIPS 140-3 available - -## Architecture - -wolfHSM employs a client-server architecture where the server runs in a trusted -and secure environment (typically on a secure coprocessor) and the client is a library -that can be linked against user applications. This architecture ensures that -sensitive cryptographic operations and key management are handled securely - within the server, while the client library abstracts away the lower level -communication with the server. - -- Server: The server component of wolfHSM is a standalone application that runs - on the HSM core. It handles cryptographic operations, key management, and - non-volatile storage within a secure environment. The server is responsible - for processing requests from clients and returning the results. - -- Client: The client component of wolfHSM is a library that can be linked - against user applications. It provides APIs for sending requests to the - server and receiving responses. The client abstracts the complexities of - communication and ensures that the application can interact with the HSM - securely and efficiently. - -## Ports - -wolfHSM itself is not executable and it does not contain any code to interact with any specific hardware. In order for wolfHSM to run on a specific device, the library must be configured with the necessary hardware drivers and abstraction layers so that the server application can run and communicate with the client. Specifically, getting wolfHSM to run on real hardware requires the implementation of the following: - -- Server application startup and hardware initialization -- Server wolfCrypt configuration -- Server non-volatile memory configuration -- Server and client transport configuration -- Server and client connection handling - -The code that provides these requirements and wraps the server API into a bootable application is collectively referred to as a wolfHSM "port". - -Official ports of wolfHSM are provided for various supported architectures, with each port providing the implementation of the wolfHSM abstractions tailored to the specific device. Each port contains: - -- Standalone Reference Server Application: This application is meant to run on the HSM core and handle all secure operations. It comes fully functional out-of-the-box but can also be customized by the end user to support additional use cases -- Client Library: This library can be linked against user applications to facilitate communication with the server - -### Supported Ports - -wolfHSM has supported ports for the following devices/environments: - -- POSIX runtime -- ST Micro SPC58N\* -- Infineon Aurix TC3xx\* -- Infineon Aurix TC4xx\* (coming soon) -- Infineon Traveo T2G\* (coming soon) -- Renesas RH850\* (coming soon) -- Renesas RL78\* (coming soon) -- NXP S32\* (coming soon) - -With additional ports on the way. - -\* These ports, unfortunately, require an NDA with the silicon vendor to obtain any information about the HSM core. Therefore, the wolfHSM ports for these platforms are not public and are only available to qualified customers. If you wish to access a restricted wolfHSM port, please contact us at support@wolfssl.com. diff --git a/docs/src/chapter03.md b/docs/src/chapter03.md deleted file mode 100644 index 4f1699505..000000000 --- a/docs/src/chapter03.md +++ /dev/null @@ -1,199 +0,0 @@ -# Getting Started With wolfHSM - -The most common use case for wolfHSM is adding HSM-enabled functionality to an existing application that runs on one of the application cores of a multi-core device with an HSM coprocessor. - -The first step required to run wolfHSM on a device is to follow the steps in the specific wolfHSM port to get the reference server running on the HSM core. Once the wolfHSM server app is loaded on the device and boots, client applications can link against the wolfHSM client library, configure an instance of the wolfHSM client structure, and interact with the HSM through the wolfHSM client API and through the wolfCrypt API. - -Each wolfHSM port contains a client demo app showing how to set up the default communication channel and interact with the server. The server reference implementation can also be customized through [server callbacks](./chapter07.md) to extend its functionality, which can be invoked through client requests. - -## Basic Client Configuration - -Configuring a wolfHSM client involves allocating a client context structure and initializing it with a valid client configuration that enables it to communicate with a server. - -The client context structure `whClientContext` holds the internal state of the client and its communication with the server. All client APIs take a pointer to the client context. - -The client configuration structure holds the communication layer configuration (`whCommClientConfig`) that will be used to configure and initialize the context for the server communication. The `whCommClientConfig` structure binds an actual transport implementation (either built-in or custom) to the abstract comms interface for the client to use. - -The general steps to configure a client are: - -1. Allocate and initialize a transport configuration structure, context, and callback implementation for the desired transport -2. Allocate comm client configuration structure and bind it to the transport configuration from step 1 so it can be used by the client -3. Allocate and initialize a client configuration structure using the comm client configuration in step 2 -4. Allocate a client context structure -5. Initialize the client with the client configuration by calling `wh_Client_Init()` -6. Use the client APIs to interact with the server - -Here is a bare-minimum example of configuring a client application to use the built-in shared memory transport to send an echo request to the server. - -```c -#include /* for memcmp() */ -#include "wolfhsm/client.h" /* Client API (includes comm config) */ -#include "wolfhsm/wh_transport_mem.h" /* transport implementation */ - -/* Step 1: Allocate and initialize the shared memory transport configuration */ -/* Shared memory transport configuration */ -static whTransportMemConfig transportMemCfg = { /* shared memory config */ }; -/* Shared memory transport context (state) */ -whTransportMemClientContext transportMemClientCtx = {0}; -/* Callback structure that binds the abstract comm transport interface to - * our concrete implementation */ -whTransportClientCb transportMemClientCb = {WH_TRANSPORT_MEM_CLIENT_CB}; - -/* Step 2: Allocate client comm configuration and bind to the transport */ -/* Configure the client comms to use the selected transport configuration */ -whCommClientConfig commClientCfg = { - .transport_cb = transportMemClientCb, - .transport_context = (void*)transportMemClientCtx, - .transport_config = (void*)transportMemCfg, - .client_id = 1, /* unique client identifier, 1-15 (or 0-15 without WOLFHSM_CFG_GLOBAL_KEYS) */ -}; - -/* Step 3: Allocate and initialize the client configuration */ -whClientConfig clientCfg= { - .comm = commClientCfg, -}; - -/* Step 4: Allocate the client context */ -whClientContext clientCtx = {0}; - -/* Step 5: Initialize the client with the provided configuration */ -wh_Client_Init(&clientCtx, &clientCfg); - -/* Step 6: Use the client APIs to interact with the server */ - -/* Buffers to hold sent and received data */ -char recvBuffer[WH_COMM_DATA_LEN] = {0}; -char sendBuffer[WH_COMM_DATA_LEN] = {0}; - -uint16_t sendLen = snprintf(&sendBuffer, - sizeof(sendBuffer), - "Hello World!\n"); -uint16_t recvLen = 0; - -/* Send an echo request and block on receiving a response */ -wh_Client_Echo(client, sendLen, &sendBuffer, &recvLen, &recvBuffer); - -if ((recvLen != sendLen ) || - (0 != memcmp(sendBuffer, recvBuffer, sendLen))) { - /* Error, we weren't echoed back what we sent */ -} -``` - -For more information, refer to [Chapter 5: Client Library](./chapter05.md). - -## Basic Server Configuration - -*Note: A wolfHSM port comes with a reference server application that is already configured to run on the HSM core and so manual server configuration is not required.* - -Configuring a wolfHSM server involves allocating a server context structure and initializing it with a valid client configuration that enables it to perform the requested operations. These operations usually include client communication, cryptographic operations, managing keys, and non-volatile object storage. Depending on the required functionality, not all of these configuration components need to be initialized. - - -The steps required to configure a server that supports client communication, NVM object storage using the NVM flash configuration, and local crypto (software only) are: - -1. Initialize the server comms configuration - 1\. Allocate and initialize a transport configuration structure, context, and callback implementation for the desired transport - 2\. Allocate and initialize a comm server configuration structure using the transport configuration from step 1.1 -2. Initialize the server NVM context - 1\. Allocate and initialize a config, context, and callback structure for the low-level flash storage drivers (the implementation of these structures is provided by the port) - 2\. Allocate and initialize an NVM flash config, context, and callback strucure and bind the port flash configuration from step 2.1 to them - 3\. Allocate an NVM context structure and initialize it with the configuration from step 2.2 using `wh_Nvm_Init()` -3. Allocate and initialize a crypto context structure for the server -4. Initialize wolfCrypt (before initializing the server) -5. Allocate and initialize a server config structure and bind the comm server configuration, NVM context, and crypto context to it -6. Allocate a server context structure and initialize it with the server configuration using `wh_Server_Init()` -7. Set the server connection state to connected using `wh_Server_SetConnected()` when the underlying transport is ready to be used for client communication (see [wolfHSM Examples](https://github.com/wolfSSL/wolfHSM/tree/main/examples) for more information) -8. Process client requests using `wh_Server_HandleRequestMessage()` - -The server may be configured to support NVM object storage using NVM flash configuration. Include the steps to [initialize NVM](./chapter04.md#NVM-Architecture) on the server after step 1. - -```c -#include /* for memcmp() */ -#include "wolfhsm/server.h" /* Server API (includes comm config) */ -#include "wolfhsm/wh_transport_mem.h" /* transport implementation */ - -/* Step 1.1: Allocate and initialize the shared memory transport configuration */ -/* Shared memory transport configuration */ -static whTransportMemConfig transportMemCfg = { /* shared memory config */ }; - -/* Shared memory transport context (state) */ -whTransportMemServerContext transportMemServerCtx = {0}; - -/* Callback structure that binds the abstract comm transport interface to - * our concrete implementation */ -whTransportServerCb transportMemServerCb = {WH_TRANSPORT_MEM_SERVER_CB}; - -/* Step 1.2: Allocate a comm server configuration structure and bind to the - * transport */ -/* Configure the server comms to use the selected transport configuration*/ -whCommServerConfig commServerCfg = { - .transport_cb = transportMemServerCb, - .transport_context = (void*)transportMemServerCtx, - .transport_config = (void*)transportMemCfg, - .server_id = 456, /* unique server identifier */ -}; - -/* Initialize the server NVM context */ - -/* Step 2.1: Allocate and initialize context and config for port-specific - * flash storage drivers */ - -/* Port Flash context (structure names are port-specific) */ -MyPortFlashContext portFlashCtx = {0} - -/* Port Flash config */ -MyPortFlashConfig portFlashCfg = { /* port specific configuration */ }; - -/* NVM Flash callback implementation for Port Flash */ -whFlashCb portFlashCb = { /* port flash implementation of NVM Flash callbacks */ - -/* Step 2.2: Allocate and initialize NVM flash config structure and bind to port - * configuration from step 2.1 */ -whNvmFlashConfig nvmFlashCfg = { - .cb = portFlashCb, - .context = portFlashCtx, - .config = portFlashCfg, -}; -whNvmFlashContext nfc = {0}; - -/* Step 2.3: Allocate NVM context, config, and callback structure and initialize - * with NVM flash configuration from step 2.2 */ -whNvmCb nvmFlashCb = {WH_NVM_FLASH_CB}; - -whNvmConfig nvmConf = { - .cb = nvmFlashCb; - .context = nfc; - .config = nvmFlashCfg, -}; -whNvmContext nvmCtx = {0}; - -wh_Nvm_Init(&nvmCtx, &whNvmConfig); - -/* Step 3: Allocate and initialize a crypto context structure */ -whServerCryptoContext cryptoCtx = {0}; - -/* Allocate and initialize the Server configuration*/ -whServerConfig serverCfg = { - .comm = commServerCfg, - .nvm = nvmCtx, - .crypto = &cryptoCtx, - .devId = INVALID_DEVID, /* or set to custom crypto callback devID */ -}; - -/* Step 4: Initialize wolfCrypt*/ -wolfCrypt_Init(); - -/* Step 5: Allocate and initialize server config structure and bind the comm - * server configuration and crypto context to it*/ -whServerContext server = {0}; -wh_Server_Init(&server, &serverCfg); - -/* Set server connection state to connected when transport is ready (e.g. - * shared memory buffers cleared) */ -wh_Server_SetConnected(&server, WH_COMM_CONNECTED); - -/* Process client requests*/ -while (1) { - wh_Server_HandleRequestMessage(&server); -} -``` - diff --git a/docs/src/chapter04.md b/docs/src/chapter04.md deleted file mode 100644 index cfecdfc9f..000000000 --- a/docs/src/chapter04.md +++ /dev/null @@ -1,303 +0,0 @@ -# Library Design / wolfHSM Internals - -wolfHSM is a modular and extensible library designed to provide a secure and efficient hardware security module (HSM) API for embedded systems. The library is built around a set of functional components that can be easily configured and combined to meet the specific requirements of a given application. This chapter provides an overview of the key functional components of wolfHSM, including the component architecture, communications layer, non-volatile memory (NVM), key management, cryptographic operations, and hardware security module (HSM) support. - -## Table of Contents: - -- [Generic Component Architecture](#generic-component-architecture) -- [Communications](#communications) - - [Key Components](#key-components) - - [Client/Server APIs](#clientserver-apis) - - [Comms Layer](#comms-layer) -- [Non Volatile Memory](#non-volatile-memory) - - [NVM Metadata](#nvm-metadata) - - [NVM Access and Flags](#nvm-access-and-flags) - - [NVM Architecture](#nvm-architecture) - - [NVM Back-Ends](#nvm-back-ends) -- [Key Management](#key-management) - - [Key Revocation](#key-revocation) -- [Cryptographic Operations](#cryptographic-operations) - - [Hardware Cryptography Support](#hardware-cryptography-support) - - -## Generic Component Architecture - -To support easily porting wolfHSM to different hardware platforms and build -environments, each component of wolfHSM is designed to have a common initialization, -configuration, and context storage architecture to allow compile-time, link- -time, and/or run-time selection of functional components. Hardware specifics -are abstracted from the logical operations by associating callback functions -with untyped context structures, referenced as a `void*`. - -### Example component initialization - -The prototypical compile-time static instance configuration and initialization -sequence of a wolfHSM component is: - -```c -#include "wolfhsm/component.h" /* wolfHSM abstract API reference for a component */ -#include "port/vendor/mycomponent.h" /* Platform specific definitions of configuration - * and context structures, as well as declarations of - * callback functions */ - -/* Provide the lookup table for function callbacks for mycomponent. Note the type -is the abstract type provided in wolfhsm/component.h */ -whComponentCb my_cb[1] = {MY_COMPONENT_CB}; - -/* Fixed configuration data. Note that pertinent data is copied out of the structure - * during init() */ -const myComponentConfig my_config = { - .my_number = 3, - .my_string = "This is a string", -} - -/* Static allocation of the dynamic state of the myComponent. */ -myComponentContext my_context[1] = {0}; - -/* Initialization of the component using platform-specific callbacks */ -const whComponentConfig comp_config[1] = { - .cb = my_cb, - .context = my_context, - .config = my_config - }; -whComponentContext comp_context[1] = {0}; -int rc = wh_Component_Init(comp_context, comp_config); - -rc = wh_Component_DoSomething(comp_context, 1, 2, 3); -rc = wh_Component_CleanUp(comp_context); -``` - -## Communications - -The communication layer of wolfHSM is designed to provide reliable, bidirectional, and packet-based communication between clients and servers. This layer abstracts the underlying transport mechanisms, allowing for flexibility and modularity. A key aspect of wolfHSM communication is split request and response functions for both client and server, enabling synchronous polling of message reception or asynchronous handling based on interrupt/event support. - -### Key Components - -- Client/Server APIs: Main interface for communicating between client and server. These are the APIs that are directly used by user applications. -- Comms layer: Defines the format and structure of messages exchanged between clients and servers, and provides an abstract interface to the underlying transport layer implementation, exposing a consistent interface for sending and receiving messages. -- Transport Layer: Concrete implementations of the underlying transport. Defines how data is actually transported between client and server. - -### Client/Server APIs - -High-level client and server APIs (defined in `wolfhsm/wh_client.h` and `wolfhsm/wh_server.h`) are the primary interface for communication. These functions abstract the low level communications details from the caller, providing a simple split transaction interface for logical operations. - -For example, using the client API to send an echo request to the server: - -```c -/* send the echo request */ -wh_Client_EchoRequest(&clientCtx, sendLen, &sendBuffer)); - -/* optionally do stuff */ - -/* poll for the server response */ -while (WH_ERROR_NOTREADY == wh_Client_EchoResponse(client, &recv_len, recv_buffer)); -``` - -### Comms Layer - -The comms layer encapsulates the messaging structure and control logic to send and receive data from lower level transports. The comms layer is directly invoked by the higher level client and server APIs. The comms layer provides comm client and comm server abstractions that hold communication state and provide the abstract interface functions to interact with lower level transports. The comms layer API consists of send and receive functions for requests and responses, where the requests and responses pertain to messages rather than high level operations. - -Each client is only allowed a single outstanding request to the server at a time. -The server will process a single request at a time to ensure client isolation. - -#### Messages - -Messages comprise a header with a variable length payload. The header indicates -the sequence id, and type of a request or response. The header also provides -additional fields to provide auxiliary flags or session information. - -```c -/* wolfhsm/wh_comm.h */ - -typedef struct { - uint16_t magic; - uint16_t kind; - uint16_t seq; - uint16_t size; -} whCommHeader; -``` - -Messages are used to encapsulate the request data necessary for the server to -execute the desired function and for the response to provide the results of the -function execution back to the client. Message types are grouped based on the -component that is performing the function and uniquely identify which of the -enumerated functions is being performed. To ensure compatibility (endianness -and version), messages include a Magic field which has known values used to -indicate what operations are necessary to demarshall data passed within the -payload for native processing. Each functional component has a "remote" -implementation that converts between native values and the "on-the-wire" message -formats. The servers ensures the response format matches the request format. - -In addition to passing data contents within messages, certain message types also -support passing shared or mapped memory pointers, especially for performance- -critical operations where the server component may be able to directly access -the data in a DMA fashion. To avoid integer pointer size (IPS) and `size_t` -differences, all pointers and sizes should be sent as `uint64_t` when -possible. - -Messages are encoded in the "on-the-wire" format using the Magic field of the -header indicating the specified endianness of structure members as well as the -version of the communications header (currently 0x01). Server components that -process request messages translate the provided values into native format, -perform the task, and then reencode the result into the format of the request. -Client response handling is not required to process messages that do not match -the request format. Encoded messages assume the same size and layout as the -native structure, with the endianness specified by the Magic field. - -Here is an example of how the client comm layer sends a request: - -```c -uint16_t req_magic = wh_COMM_MAGIC_NATIVE; -uint16_t req_type = 123; -uint16_t request_id; -char* req_data = "RequestData"; -rc = wh_CommClient_SendRequest(context, req_magic, req_type, &request_id, - sizeof(req_data), req_data); -/* Do other work */ - -uint16_t resp_magic, resp_type, resp_id, resp_size; -char response_data[20]; -while((rc = wh_CommClient_RecvResponse(context,&resp_magic, &resp_type, &resp_id, - &resp_size, resp_data)) == WH_ERROR_NOTREADY) { - /* Do other work or yield */ -} -``` - -Note that transport errors passed into the message layer are expected to be fatal and the -client/server should Cleanup any context as a result. - -### Transports - -Transports provide intact packets (byte sequences) of variable size (up to a -maximum MTU), to the messaging layer for the library to process as a request or -response. Transports implement the abstract interface defined by `whTransportClientCb` -and are invoked directly by the commClient/commServer when needing to send and -receive data. - -Custom transport modules that implement the `whTransportClientCb` interface -can be registered with the server and client and then are automatically used -via the standard server and client request/response functions. - -Examples of a memory buffer transport module and a POSIX TCP socket transport can be found in wolfHSM's supported transports. - -#### Supported Transports - -wolfHSM ships with two built-in transports: a memory buffer transport (`wh_transport_mem.c`) and a POSIX TCP socket transport (`port/posix_transport_tcp.c`). - -The memory transport is the default transport for most embedded wolfHSM ports, and is part of the core wolfHSM library. It provides a concrete implementation of the transport callbacks using shared memory blocks between client and server. The shared memory transport mechanism works by allocating two blocks of memory, one for incoming requests and one for outgoing responses. The client writes requests to the incoming memory block and reads responses from the outgoing memory block. The server reads requests from the incoming memory block and writes responses to the outgoing memory block. Each block contains control and status flags signaling to the consumer when it is ready for use. This mechanism is designed to be fast and efficient, as it avoids the need for system calls or network communication. - - -The POSIX TCP transport is part of the wolfHSM POSIX port. It uses TCP sockets as the transport medium for data between client and server. The sockets are IPv4 only and non-blocking. - -## Non Volatile Memory - -Non-Volatile Memory (NVM) in the context of wolfHSM is used to manage persistent objects with metadata and data blocks. The NVM library ensures reliable, atomic operations to ensure transactions are fully committed before returning success. Key operations include adding, listing, reading, and destroying objects, as well as obtaining associated metadata. - -High level NVM features include: - -- API’s to associate metadata (ID, Label, Length, Access, Flags) with variable-sized data within accessible NVM -- Always recoverable using 2 erasable partitions with status flags -- Objects are added by using the next entry and programmed into free space -- Duplicated id’s are allowed but only the latest is readable -- Objects are destroyed by copying the entire space to the inactive partition without the listed objects -- Internal epoch counters used to identify the later objects during recovery - - -### NVM Metadata - -In the wolfHSM library, Non-Volatile Memory (NVM) metadata is used to manage and describe objects stored in NVM. This metadata provides essential information about each object, such as its identifier, access permissions, flags, and other attributes. The metadata ensures that objects can be reliably managed, accessed, and manipulated within the NVM. - -``` -/* User-specified metadata for an NVM object */ -typedef struct { - whNvmId id; /* Unique identifier */ - whNvmAccess access; /* Access Permissions */ - whNvmFlags flags; /* Additional flags */ - whNvmSize len; /* Length of data in bytes */ - uint8_t label[WOLFHSM_NVM_LABEL_LEN]; -} whNvmMetadata; -``` - -- ID (`whNvmId id`): A unique identifier for the NVM object. This ID is used to reference and access the specific object within the NVM. It allows for operations like reading, writing, and deleting the object. -- Access (`whNvmAccess access`): Defines the access permissions for the object. This field specifies who can access the object and under what conditions. It helps enforce security policies and ensures that only authorized entities can interact with the object. -- Flags (`whNvmFlags flags`): Additional flags that provide extra information or modify the behavior of the object. Flags can be used to mark objects with special attributes or states, such as whether the object is read-only, temporary, or has other specific properties. -Length (whNvmSize len): The length of the data associated with the object, in bytes. -- Label (`uint8_t label[]`): A human-readable label or name for the object. - -### NVM Access and Flags - -NVM access controls are stored in the metadata for all objects and are returned by `wh_Nvm_GetMetadata()` and related client APIs. - -NVM flags provide object and key policy hints that are enforced by the NVM library and keystore. Relevant flags include: - -- `WH_NVM_FLAGS_NONMODIFIABLE`: Object cannot be modified and/or destroyed. -- `WH_NVM_FLAGS_NONDESTROYABLE`: Object cannot be destroyed. -- `WH_NVM_FLAGS_NONEXPORTABLE`: Object data cannot be read/exported. -- `WH_NVM_FLAGS_USAGE_*`: Key usage policy flags for encrypt/decrypt/sign/verify/wrap/derive. -- `WH_NVM_FLAGS_USAGE_ANY`: Allow all usage flags. - -### NVM Architecture - -The wolfHSM server follows the generic component architecture approach to handle Non-Volatile Memory (NVM) operations. The configuration is divided into generic and specific parts, allowing for flexibility and customization. - -1. **Generic Configuration (wh_nvm.h):** This header file defines the generic interface for NVM operations. It includes function pointers for NVM operations like `nvm_Read`, `nvm_Write`, `nvm_Erase`, and `nvm_Init`. These function pointers are part of the `whNvmConfig` structure, which is used to bind an actual NVM implementation to the abstract NVM interface. - -2. **Specific Configuration (wh_nvm_flash.c, wh_nvm_flash.h):** These files provide a specific implementation of the NVM interface for flash memory. The functions defined here adhere to the function signatures defined in the generic interface, allowing them to be used as the actual implementation for the NVM operations. - -The `whServerContext` structure includes a `whNvmConfig` member. This is used to bind the NVM operations to the server context, allowing the server to perform NVM operations using the configured NVM interface. - -Steps required to initialize NVM on the server are: - -1. Allocate and initialize a `whNvmConfig` structure, providing bindings to a specific NVM back-end (e.g., from `wh_nvm_flash.c`). -2. Allocate and initialize a `whServerConfig` structure, and set its `nvmConfig` member to the `whNvmConfig` structure initialized in step 1. -3. Allocate a `whServerContext` structure. -4. Initialize the server with the `whServerConfig` structure by calling `wh_Server_Init()`. - -This allows the server to use the configured NVM operations on the given backing store, which can be easily swapped out by providing a different implementation in the `whNvmConfig` structure. - -### NVM Back-Ends - -Currently, wolfHSM only supports one NVM back-end provider: the NVM flash module (`wh_nvm_flash.c`). This module provides a concrete implementation of the NMV interface functions (`wh_nvm.h`), mapping the NVM data store to a flash memory device. The low-level flash drivers are device-specific and themselves specified as generic components (`wh_flash.h`) that can be swapped out depending on the target hardware. - -## Key Management - -The wolfHSM library provides comprehensive key management capabilities, including storing, loading, and exporting keys from non-volatile memory, caching of frequently used keys in RAM for fast access, and interacting with hardware-exclusive device keys. Keys are stored in non-volatile memory along side other NVM objects with corresponding access protections. wolfHSM will automatically load keys into the necessary cryptographic hardware when the key is selected for use with a specific consumer. More information on the key management API can be found in the [client library](./chapter05.md) and [API documentation](./appendix01.md) sections. - -### Key Revocation - -Key revocation provides a lightweight mechanism to invalidate a key without destroying its storage. When a key is revoked on the server, its metadata is updated by setting `WH_NVM_FLAGS_NONMODIFIABLE` and clearing all `WH_NVM_FLAGS_USAGE_*` bits. The key remains present in cache/NVM, but is no longer eligible for cryptographic operations that enforce usage flags. - -Runtime behavior for revoked keys: - -- Cryptographic operations that enforce usage flags return `WH_ERROR_USAGE` when the required usage bit is no longer set. -- The `WH_NVM_FLAGS_NONMODIFIABLE` setting prevents further key metadata changes and blocks NVM modification or destruction checks. -- Revocation does not automatically set `WH_NVM_FLAGS_NONEXPORTABLE`; export behavior remains controlled by those flags. - -Persistence behavior: - -- Revocation is committed to NVM for keys that already exist in NVM, so the revoked state persists across resets or power cycles. -- If a key exists only in cache and has not been committed, the revoked state is limited to the cache lifetime. - -## Cryptographic Operations - -One of the defining features of wolfHSM is that it enables the client application to use the wolfCrypt API directly, but with the underlying cryptographic operations actually being executed on the HSM core. This is an incredibly powerful feature for a number of reasons: - -- client applications are dramatically simpler as they do not need to set up the complicated communication transactions required to pass data back and forth between the HSM -- local and remote HSM implementations can be easily switched between by changing a single parameter to the wolfCrypt call, enabling maximum flexibility of implementation and ease of development. Client application development can be prototyped with local instances of wolfCrypt before the HSM core is even brought on-line -- the wolfHSM API is simple, stable, well documented, and battle tested - -The ability to easily redirect wolfCrypt API calls to the wolfHSM server is based on the "crypto callback" (a.k.a cryptocb) of wolfCrypt. - - -The wolfHSM client is able to redirect wolfCrypt API calls to the wolfHSM server by implementing the remote procedure call logic as a [crypto callback](https://www.wolfssl.com/documentation/manuals/wolfssl/chapter06.html#crypto-callbacks-cryptocb). The Crypto callback framework in wolfCrypt enables users to override the default implementation of select cryptographic algorithms and provide their own custom implementations at runtime. The wolfHSM client library registers a crypto callback with wolfCrypt that transforms each wolfCrypt crypto API function into a remote procedure call to the HSM server to be executed in a secure environment. Crypto callbacks are selected for use based on the device ID (`devId`) parameter accepted by most wolfCrypt API calls. - -wolfHSM defines the `WOLFHSM_DEV_ID` value to represent the wolfHSM server crypto device, which can be passed to any wolfCrypt function as the `devId` parameter. wolfCrypt APIs that support the `devId` parameter can be passed `WOLFHSM_DEV_ID` and, if supported, the cryptographic operation will be automatically exectued by the wolfHSM server. - -### Hardware Cryptography Support - -Many HSM devices also have hardware acceleration capabilities for select algorithms available. In these cases, the wolfHSM server may also support offloading the HSM server-side cryptography to device hardware. If supported, the wolfHSM server can be configured to do this automatically with no input required from the user. Any port-specific hardware acceleration capabilities will be documented in the wolfHSM port for that device. - - -## AUTOSAR SHE - -TODO diff --git a/docs/src/chapter05.md b/docs/src/chapter05.md deleted file mode 100644 index 6f43dc9ae..000000000 --- a/docs/src/chapter05.md +++ /dev/null @@ -1,600 +0,0 @@ -# wolfHSM Client Library - -The client library API is the primary mechanism through which users will interact with wolfHSM. Refer to the [API documentation](appendix01.md) for a full list of available functions and their descriptions. - -## Table of Contents - -- [API Return Codes](#api-return-codes) -- [Split Transaction Processing](#split-transaction-processing) -- [The Client Context](#the-client-context) - - [Initializing the client context](#initializing-the-client-context) -- [NVM Operations](#nvm-operations) -- [NVM Access and Flags](#nvm-access-and-flags) -- [Key Management](#key-management) -- [Key Revocation](#key-revocation) -- [Cryptography](#cryptography) -- [AUTOSAR SHE API](#autosar-she-api) - - -## API Return Codes - -All client API functions return a wolfHSM error code indicating success or the type of failure. Some failures are critical errors, while others may simply indicate an action is required from the caller (e.g. `WH_ERROR_NOTREADY` in the case of a non-blocking operation). Many client APIs also propagate a server error code (and in some cases an additional status) to the caller, allowing for the case where the underlying request transaction succeeded but the server was unable to perform the operation. Examples of this include requesting an NVM object from the server that doesn't exist, attempting to add an object when NVM is full, or trying to use a cryptographic algorithm that the server is not configured to support. - -Error codes are defined in `wolfhsm/wh_error.h`. Refer to the API documentation for more details. - -## Split Transaction Processing - -Most client APIs are fully asynchronous and decomposed into split transactions, meaning there is a separate function for the operation request and response. The request function sends the request to the server and immediately returns without blocking. The response function polls the underlying transport for a response, processing it if it exists, and immediately returning if it has not yet arrived. This allows for the client to request long running operations from the server without wasting client CPU cycles. The following example shows an example asynchronous request and response invocation using the "echo" message: - -```c -int rc; - -/* send an echo request */ -rc = wh_Client_EchoRequest(&clientCtx, sendLen, &sendBuffer); -if (rc != WH_ERROR_OK) { - /* handle error */ -} - -/* do work... */ - -/* poll for a server response */ -while ((rc = wh_Client_EchoResponse(client, &recv_len, recv_buffer)) == WH_ERROR_NOTREADY) { - /* do work or yield */ -} - -if (rc != WH_ERROR_OK) { - /* handle error */ -} - - -``` - -## The Client Context - -The client context structure (`whClientContext`) holds the runtime state of the client and represents the endpoint of the connection with the server. There is a one-to-one relationship between client and server contexts, meaning an application that interacts with multiple servers will need multiple client contexts - one for each server. Each client API function takes a client context as an argument, indicating which server connection the operations will correspond to. If familiar with wolfSSL, the client context structure is analogous to the `WOLFSSL` connection context structure. - -### Initializing the client context - -Before using any client APIs on a client context, the structure must be configured and initialized using the `whClientConfig` configuration structure and the `wh_Client_Init()` function. - -The client configuration structure holds the communication layer configuration (`whCommClientConfig`) that will be used to configure and initialize the context for the server communication. The `whCommClientConfig` structure binds an actual transport implementation (either built-in or custom) to the abstract comms interface for the client to use. - -The general steps to configure a client are: - -1. Allocate and initialize a transport configuration structure, context, and callback implementation for the desired transport -2. Allocate and comm client configuration structure and bind it to the transport configuration from step 1 so it can be used by the client -3. Allocate and initialize a client configuration structure using the comm client configuration in step 2 -4. Allocate a client context structure -5. Initialize the client with the client configuration by calling `wh_Client_Init()` -6. Use the client APIs to interact with the server - -Here is a bare-minimum example of configuring a client application to use the built-in shared memory transport: - -```c -#include /* for memcmp() */ -#include "wolfhsm/client.h" /* Client API (includes comm config) */ -#include "wolfhsm/wh_transport_mem.h" /* transport implementation */ - -/* Step 1: Allocate and initialize the shared memory transport configuration */ -/* Shared memory transport configuration */ -static whTransportMemConfig transportMemCfg = { /* shared memory config */ }; -/* Shared memory transport context (state) */ -whTransportMemClientContext transportMemClientCtx = {0}; -/* Callback structure that binds the abstract comm transport interface to - * our concrete implementation */ -whTransportClientCb transportMemClientCb = {WH_TRANSPORT_MEM_CLIENT_CB}; - -/* Step 2: Allocate client comm configuration and bind to the transport */ -/* Configure the client comms to use the selected transport configuration */ -whCommClientConfig commClientCfg[1] = {{ - .transport_cb = transportMemClientCb, - .transport_context = (void*)transportMemClientCtx, - .transport_config = (void*)transportMemCfg, - .client_id = 1, /* unique client identifier, 1-15 (or 0-15 without WOLFHSM_CFG_GLOBAL_KEYS) */ -}}; - -/* Step 3: Allocate and initialize the client configuration */ -whClientConfig clientCfg= { - .comm = commClientCfg, -}; - -/* Step 4: Allocate the client context */ -whClientContext clientCtx = {0}; - -/* Step 5: Initialize the client with the provided configuration */ -wh_Client_Init(&clientCtx, &clientCfg); -``` - -The client context is now initialized and can be used with the client library API functions in order to do work. Here is an example of sending an echo request to the server: - -```c -/* Step 6: Use the client APIs to interact with the server */ - -/* Buffers to hold sent and received data */ -char recvBuffer[WH_COMM_DATA_LEN] = {0}; -char sendBuffer[WH_COMM_DATA_LEN] = {0}; - -uint16_t sendLen = snprintf(&sendBuffer, - sizeof(sendBuffer), - "Hello World!\n"); -uint16_t recvLen = 0; - -/* Send an echo request and block on receiving a response */ -wh_Client_Echo(client, sendLen, &sendBuffer, &recvLen, &recvBuffer); - -if ((recvLen != sendLen ) || - (0 != memcmp(sendBuffer, recvBuffer, sendLen))) { - /* Error, we weren't echoed back what we sent */ -} -``` - -While there are indeed a large number of nested configurations and structures to set up, designing wolfHSM this way allowed for different transport implementations to be swapped in and out easily without changing the client code. For example, in order to switch from the shared memory transport to a TCP transport, only the transport configuration and callback structures need to be changed, and the rest of the client code remains the same (everything after step 2 in the sequence above). - -```c -#include /* for memcmp() */ -#include "wolfhsm/client.h" /* Client API (includes comm config) */ -#include "port/posix_transport_tcp.h" /* transport implementation */ - -/* Step 1: Allocate and initialize the posix TCP transport configuration */ -/* Client configuration/contexts */ -whTransportClientCb posixTransportTcpCb = {PTT_CLIENT_CB}; -posixTransportTcpClientContext posixTranportTcpCtx = {0}; -posixTransportTcpConfig posixTransportTcpCfg = { - /* IP and port configuration */ -}; - -/* Step 2: Allocate client comm configuration and bind to the transport */ -/* Configure the client comms to use the selected transport configuration */ -whCommClientConfig commClientCfg = {{ - .transport_cb = posixTransportTcpCb, - .transport_context = (void*)posixTransportTcpCtx, - .transport_config = (void*)posixTransportTcpCfg, - .client_id = 1, /* unique client identifier, 1-15 (or 0-15 without WOLFHSM_CFG_GLOBAL_KEYS) */ -}}; - -/* Subsequent steps remain the same... */ -``` - -Note that the echo request in step 6 is just a simple usage example. Once the connection to the server is set up, any of the client APIs are available for use. - -## NVM Operations - -This section provides examples of how to use the client NVM API. Blocking APIs are used for simplicity, though the split transaction APIs can be used in a similar manner. - -Client usage of the server NVM storage first requires sending an initialization request to the server. This currently does not trigger any action on the server side but it may in the future and so it is recommended to include in client applications. - -```c -int rc; -int serverRc; -uint32_t clientId; /* unused for now */ -uint32_t serverId; - -rc = wh_Client_NvmInit(&clientCtx, &serverRc, &clientId, &serverId); - -/* error check both local and remote error codes */ -/* serverId holds unique ID of server */ -``` - -Once initialized, the client can create and add an object using the `NvmAddObject` functions. Note that a metadata entry must be created for every object. - -```c -int serverRc; - -whNvmId id = 123; -whNvmAccess access = WOLFHSM_NVM_ACCESS_ANY; -whNvmFlags flags = WOLFHSM_NVM_FLAGS_ANY; -uint8_t label[] = “My Label”; - -uint8_t myData[] = “This is my data.” - -whClient_NvmAddObject(&clientCtx, id, access, flags, strlen(label), &label, sizeof(myData), &myData, &serverRc); -``` - -Data corresponding to an existing objects can be updated in place: - -```c -byte myUpdate[] = “This is my update.” - -whClient_NvmAddObject(&clientCtx, &myMeta, sizeof(myUpdate), myUpdate); -``` - -For objects that should not be copied and sent over the transport, there exist DMA versions of the `NvmAddObject` functions. These pass the data to the server by reference rather than by value, allowing the server to access the data in memory directly. Note that if your platform requires custom address translation or cache invalidation before the server may access client addresses, you will need to implement a [DMA callback](./chapter07#DMA-Callbacks). - -``` -whNvmMetadata myMeta = { - .id = 123, - .access = WOLFHSM_NVM_ACCESS_ANY, - .flags = WOLFHSM_NVM_FLAGS_ANY, - .label = “My Label” -}; - - -uint8_t myData[] = “This is my data.” - -wh_Client_NvmAddObjectDma(client, &myMeta, sizeof(myData), &myData), &serverRc); -``` - -NVM Object data can be read using the `NvmRead` functions. There also exist DMA versions of `NvmRead` functions that can be used identically to their `AddbjectDma` counterparts. - -```c -const whNvmId myId = 123; /* ID of the object we want to read */ -const whNvmSize offset = 0; /* byte offset into the object data */ - -whNvmSize outLen; /* will hold length in bytes of the requested data */ -int outRc; /* will hold server return code */ - -byte myBuffer[BIG_SIZE]; - -whClient_NvmRead(&clientCtx, myId, offset, sizeof(myData), &serverRc, outLen, &myBuffer) -/* or via DMA */ -whClient_NvmReadDma(&clientCtx -iint wh_Client_NvmReadDma(&clientCtx, myid, offset, sizeof(myData), &myBuffer, &serverRc); -``` - -Objects can be deleted/destroyed using the `NvmDestroy` functions. These functions take a list (array) of object IDs to be deleted. IDs in the list that are not present in NVM do not cause an error. - -```c -whNvmId idList[] = {123, 456}; -whNvmSize count = sizeof(myIds)/ sizeof(myIds[0]); -int serverRc; - -wh_Client_NvmDestroyObjectsRequest(&clientCtx, count, &idList); -wh_Client_NvmDestroyObjectsResponse(&clientCtx, &serverRc); -``` - -The objects in NVM can also be enumerated using the `NvmList` functions. These functions retrieve the next matching id in the NVM list starting at `start_id`, and sets `out_count` to the total number of IDs that match `access` and `flags`: - -```c -int wh_Client_NvmList(whClientContext* c, - whNvmAccess access, whNvmFlags flags, whNvmId start_id, - int32_t *out_rc, whNvmId *out_count, whNvmId *out_id); -``` - -For a full description of all the NVM API functions, please refer to the [API documentation](./appendix01.md). - -## NVM Flags - -NVM objects include flags in their metadata. Flags (such as `WH_NVM_FLAGS_NONMODIFIABLE`, `WH_NVM_FLAGS_NONDESTROYABLE`, and `WH_NVM_FLAGS_NONEXPORTABLE`) are enforced by the server NVM policy checks. Key usage flags (`WH_NVM_FLAGS_USAGE_*`) are enforced by the keystore during cryptographic operations. - -## Key Management - -Keys meant for use with wolfCrypt can be loaded into the HSM's keystore and optionally saved to NVM with the following APIs: - -```c -#include "wolfhsm/wh_client.h" - -uint16_t keyId = WOLFHSM_KEYID_ERASED; -uint32_t keyLen; -byte key[AES_128_KEY_SIZE] = { /* AES key */ }; -byte label[WOLFHSM_NVM_LABEL_LEN] = { /* Key label */ }; - -whClientContext clientCtx; -whClientCfg clientCfg = { /* config */ }; - -wh_Client_Init(&clientCtx, &clientCfg); - -wh_Client_KeyCache(clientCtx, 0, label, sizeof(label), key, sizeof(key), &keyId); -wh_Client_KeyCommit(clientCtx, keyId); -wh_Client_KeyEvict(clientCtx, keyId); -keyLen = sizeof(key); -wh_Client_KeyExport(clientCtx, keyId, label, sizeof(label), key, &keyLen); -wh_Client_KeyErase(clientCtx, keyId); -``` - -`wh_Client_KeyCache` will store the key and label in the HSM's ram cache and correlate it with the `keyId` passed in. Using a `keyId` of `WOLFHSM_KEYID_ERASED` will make wolfHSM assign a new, unique `keyId` that will be returned through the keyId parameter. wolfHSM has a limited number of cache slots, configured by `WOLFHSM_NUM_RAMKEYS`, and will return `WH_ERROR_NOSPACE` if all keyslots are full. Keys that are in cache and NVM will be removed from the cache to make room for more keys since they're backed up in NVM. -`wh_Client_KeyCommit` will save a cached key to NVM with the key indicated by it's keyId. -`wh_Client_KeyEvict` will evict a key from the cache but will leave it in NVM if it's been commited. -`wh_Client_KeyExport` will read the key contents out of the HSM back to the client. -`wh_Client_KeyErase` will remove the indicated key from cache and erase it from NVM. - -## Exporting only the public half of a cached key - -`wh_Client_KeyExport` returns the raw cached key bytes. For a cached public-key -keypair this includes the private material, which defeats the security benefit -of leaving the key inside the HSM when the caller only needs the public half -(for example, to verify a signature on the client side or to package the key -into a certificate). - -wolfHSM provides a dedicated public-only export path that, given a key ID for -a cached public-key keypair, re-emits only the public portion on the server -and deserializes it into a wolfCrypt key object on the client. The private -key never leaves the HSM. - -Per-algorithm helpers wrap the transport layer: - -```c -int wh_Client_RsaExportPublicKey(whClientContext* ctx, whKeyId keyId, - RsaKey* key, uint32_t label_len, uint8_t* label); -int wh_Client_EccExportPublicKey(whClientContext* ctx, whKeyId keyId, - ecc_key* key, uint16_t label_len, uint8_t* label); -int wh_Client_Ed25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, - ed25519_key* key, uint16_t label_len, uint8_t* label); -int wh_Client_Curve25519ExportPublicKey(whClientContext* ctx, whKeyId keyId, - curve25519_key* key, uint16_t label_len, uint8_t* label); -int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId, - wc_MlDsaKey* key, uint16_t label_len, uint8_t* label); -int wh_Client_MlKemExportPublicKey(whClientContext* ctx, whKeyId keyId, - MlKemKey* key, uint16_t label_len, uint8_t* label); -``` - -Unlike the DER-based helpers, ML-KEM emits the raw wire-format public key -bytes (sized by `WC_ML_KEM_MAX_PUBLIC_KEY_SIZE`); the client deserializer -probes the supported levels. `WH_KEY_ALGO_MLKEM` is the matching algo -selector when calling the generic `wh_Client_KeyExportPublic[Dma]` API -directly. - -Example: generate an RSA keypair on the HSM with the private key marked -`NONEXPORTABLE`, obtain the public key on the client, and verify a signature -the HSM produced using the cached private key. - -```c -whKeyId keyId = WH_KEYID_ERASED; -RsaKey pub; - -/* 1. Generate keypair inside the HSM. NONEXPORTABLE blocks full export. */ -wh_Client_RsaMakeCacheKey(&clientCtx, 2048, 0x10001, &keyId, - WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY | - WH_NVM_FLAGS_NONEXPORTABLE, 0, NULL); - -/* 2. Fetch only the public half. NONEXPORTABLE does NOT block this. */ -wc_InitRsaKey_ex(&pub, NULL, INVALID_DEVID); -wh_Client_RsaExportPublicKey(&clientCtx, keyId, &pub, 0, NULL); -/* pub.type == RSA_PUBLIC; usable for client-side verification */ -``` - -### Interaction with `WH_NVM_FLAGS_NONEXPORTABLE` - -`NONEXPORTABLE` blocks `wh_Client_KeyExport` and the per-algorithm full-export -helpers. It does **not** block the public-only export path, because public key -material is non-sensitive — preventing it from ever leaving the HSM would make -the cached key unusable for any external verification or key-transport use -case. If you want to block even public extraction, do not generate or import -the key in the first place, or remove it with `wh_Client_KeyErase` after it -has served its purpose. - -### DMA variant - -For builds with `WOLFHSM_CFG_DMA` enabled, parallel APIs let the server -write the public DER directly into a client-provided buffer using the -existing DMA transport, avoiding the comm-buffer copy. The semantics -(NONEXPORTABLE carve-out, algo selector, error handling) are identical to -the non-DMA path. - -```c -int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId, - uint16_t algo, const void* keyAddr, uint16_t keySz, - uint8_t* label, uint16_t labelSz, uint16_t* outSz); - -int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, - wc_MlDsaKey* key, uint16_t label_len, uint8_t* label); -int wh_Client_MlKemExportPublicKeyDma(whClientContext* ctx, whKeyId keyId, - MlKemKey* key, uint16_t label_len, uint8_t* label); -``` - -`wh_Client_KeyExportPublicDma` is the generic transport — callers receive -raw public DER and deserialize it themselves. `wh_Client_MlDsaExportPublicKeyDma` -mirrors the existing `wh_Client_MlDsaExportKeyDma` per-algorithm helper for -the case where ML-DSA's large public DER benefits most from skipping the -comm-buffer staging. - -### Wire protocol - -The public-only export uses dedicated keystore actions, -`WH_KEY_EXPORT_PUBLIC` and `WH_KEY_EXPORT_PUBLIC_DMA`, with a per-request -algorithm selector (`WH_KEY_ALGO_*` in `wolfhsm/wh_common.h`) because -cached keys are stored as opaque DER bytes and the server needs to know -how to decode them. Integrators implementing custom transports should -route these actions the same way they route `WH_KEY_EXPORT` and -`WH_KEY_EXPORT_DMA`. - -## Key Revocation - -Key revocation updates key metadata to prevent further cryptographic use without destroying storage. Revocation clears all `WH_NVM_FLAGS_USAGE_*` bits and sets `WH_NVM_FLAGS_NONMODIFIABLE`. The revoked state is persisted when the key is already committed to NVM. - -Creating a key with NVM usage flags: - -```c -int rc; -uint16_t keyId = WH_KEYID_ERASED; -byte label[WH_NVM_LABEL_LEN] = "app-signing"; -byte key[AES_128_KEY_SIZE] = { /* key bytes */ }; -whNvmFlags flags = WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_NONEXPORTABLE; - -rc = wh_Client_KeyCache(&clientCtx, flags, label, sizeof(label), - key, sizeof(key), &keyId); -if (rc == WH_ERROR_OK) { - rc = wh_Client_KeyCommit(&clientCtx, keyId); -} -``` - -Revoking a key: - -```c -int rc; - -rc = wh_Client_KeyRevoke(&clientCtx, keyId); -if (rc != WH_ERROR_OK) { - /* handle error */ -} -``` - -Attempting to use a revoked key and handling failure: - -```c -int rc; -Aes aes; -byte iv[AES_BLOCK_SIZE] = {0}; -byte plain[AES_BLOCK_SIZE] = {0}; -byte cipher[AES_BLOCK_SIZE] = {0}; - -wc_AesInit(&aes, NULL, WOLFHSM_DEV_ID); -wh_Client_AesSetKeyId(&aes, keyId); -wc_AesSetIV(&aes, iv); - -rc = wh_Client_AesCbc(&clientCtx, &aes, AES_ENCRYPTION, - plain, sizeof(plain), cipher); -if (rc == WH_ERROR_USAGE) { - /* key revoked or usage not permitted */ -} -wc_AesFree(&aes); -``` - -Compatibility notes: - -- Keys stored with `WH_NVM_FLAGS_NONE` (no usage flags) are treated as not permitted for cryptographic use and will return `WH_ERROR_USAGE`. -- Keys committed to NVM retain revocation state across resets; cached-only keys do not persist after reset or eviction. - -### Key revocation client API - -#### wh_Client_KeyRevokeRequest - -Send a key revocation request to the server (non-blocking). - -This function prepares and sends a revoke request for the specified key ID. It -returns after the request is sent; use `wh_Client_KeyRevokeResponse()` to -retrieve the result. - -Parameters: - -- `c`: Client context. -- `keyId`: Key ID to revoke. - -Return values: - -- `WH_ERROR_OK` on successful request send. -- A negative error code on failure. - -Error codes: - -- `WH_ERROR_BADARGS` if `c` is NULL or `keyId` is invalid. -- Propagates comm layer errors on send failure. - -#### wh_Client_KeyRevokeResponse - -Receive a key revocation response. - -This function polls for the revoke response and returns `WH_ERROR_NOTREADY` -until the server reply is available. - -Parameters: - -- `c`: Client context. - -Return values: - -- `WH_ERROR_OK` on success. -- `WH_ERROR_NOTREADY` if the response has not arrived. -- A negative error code on failure. - -Error codes: - -- `WH_ERROR_BADARGS` if `c` is NULL. -- Server error codes such as `WH_ERROR_NOTFOUND`. - -#### wh_Client_KeyRevoke - -Revoke a key using a blocking request/response. - -This helper sends a revoke request and waits for the response. - -Parameters: - -- `c`: Client context. -- `keyId`: Key ID to revoke. - -Return values: - -- `WH_ERROR_OK` on success. -- A negative error code on failure. - -Error codes: - -- Any error code returned by `wh_Client_KeyRevokeRequest()` or - `wh_Client_KeyRevokeResponse()`. - -## Cryptography - -When using wolfCrypt in the client application, compatible crypto operations can be executed on the wolfHSM server by passing `WOLFHSM_DEV_ID` as the `devId` argument. The wolfHSM client must be initialized before using any wolfHSM remote crypto. - -If wolfHSM does not yet support that algorithm, the API call will return `CRYPTOCB_UNAVAILABLE`. - -Here is an example of how a client application would perform an `AES CBC` encryption operation on the wolfHSM server: - -```c -#include "wolfhsm/client.h" -#include "wolfssl/wolfcrypt/aes.h" - -whClientContext clientCtx; -whClientCfg clientCfg = { /* config */ }; - -wh_Client_Init(&clientCtx, &clientCfg); - -Aes aes; -byte key[AES_128_KEY_SIZE] = { /* AES key */ }; -byte iv[AES_BLOCK_SIZE] = { /* AES IV */ }; - -byte plainText[AES_BLOCK_SIZE] = { /* plaintext */ }; -byte cipherText[AES_BLOCK_SIZE]; - -wc_AesInit(&aes, NULL, WOLFHSM_DEV_ID); - -wc_AesSetKey(&aes, &key, AES_BLOCK_SIZE, &iv, AES_ENCRYPTION); - -wc_AesCbcEncrypt(&aes, &cipherText, &plainText, sizeof(plainText)); - -wc_AesFree(&aes); -``` - -If it is necessary to use an HSM-owned key instead of a client-owned key (e.g. a HSM hardware key), client API functions such as `wh_Client_SetKeyAes` (or similar for other crypto algorithms) will make wolfHSM use the indicated HSM key for the subsequent cryptographic operation instead of requiring a client-supplied key: - -```c -#include "wolfhsm/client.h" -#include "wolfssl/wolfcrypt/aes.h" - -whClientContext clientCtx; -whClientCfg clientCfg = { /* config */ }; - -wh_Client_Init(&clientCtx, &clientCfg); - -uint16_t keyId; -Aes aes; -byte key[AES_128_KEY_SIZE] = { /* AES key */ }; -byte label[WOLFHSM_NVM_LABEL_LEN] = { /* Key label */ }; -byte iv[AES_BLOCK_SIZE] = { /* AES IV */ }; - -byte plainText[AES_BLOCK_SIZE] = { /* plaintext */ }; -byte cipherText[AES_BLOCK_SIZE]; - -wc_AesInit(&aes, NULL, WOLFHSM_DEV_ID); - -/* IV needs to be set seperate from the key */ -wc_AesSetIV(&aes, iv); - -/* this key can be cached at any time before use, done here for the sake of example */ -wh_Client_KeyCache(clientCtx, 0, label, sizeof(label), key, sizeof(key), &keyId); - -wh_Client_SetKeyAes(&aes, keyId); - -wc_AesCbcEncrypt(&aes, &cipherText, &plainText, sizeof(plainText)); - -/* key eviction is optional, the key can be stored in cache or NVM and used with wolfCrypt */ -wh_Client_KeyEvict(clientCtx, keyId); - -wc_AesFree(&aes); -``` - -If it is desired to run the crypto locally on the client, all that is necessary is to pass `INVALID_DEVID` to `wc_AesInit()`: - -```c -wc_AesInit(&aes, NULL, INVALID_DEVID); -``` - -Outside of the steps mentioned above, the usage of the wolfHSM API should be otherwise unchanged. Please consult the wolfCrypt API reference inside the [wolfSSL manual](https://www.wolfssl.com/documentation/manuals/wolfssl/index.html) for further usage instructions and the extensive list of supported cryptographic algorithms. - -### CMAC - -For CMAC operations that need to use cached keys, seperate wolfHSM specific functions must be called to do the CMAC hash and verify operation in one function call. The normal `wc_AesCmacGenerate_ex` and `wc_AesCmacVerify_ex` are acceptable to use if the client can supply a key when the functions are invoked, but in order to use a pre-cached key, `wh_Client_AesCmacGenerate` and `wh_Client_AesCmacVerify` must be used. The non-oneshot functions `wc_InitCmac_ex`, `wc_CmacUpdate` and `wc_CmacFinal` can be used with either a client-side key or a pre-cached key. To use a cached key for these functions, the caller should pass a `NULL` key parameter and use `wh_Client_SetKeyCmac` to set the appropriate keyId. - - -## AUTOSAR SHE API - diff --git a/docs/src/chapter06.md b/docs/src/chapter06.md deleted file mode 100644 index 7513d37d3..000000000 --- a/docs/src/chapter06.md +++ /dev/null @@ -1,24 +0,0 @@ -# wolfHSM Server Library - -The wolfHSM server library is a server-side implementation of the wolfCrypt cryptography library. -It provides an interface for applications to offload cryptographic operations to a -dedicated server, which runs the wolfHSM server software. This allows the application to perform -cryptographic operations without having to manage the cryptographic keys or perform the -operations locally. - -## Getting Started -TODO - -## Architecture -TODO - -## API Reference -TODO - -## Key Management -TODO - -## Cryptographic - -wolfHSM uses wolfCrypt for all cryptographic operations, which means wolfHSM can offload any algorithm supported by wolfCypt to run on the wolfHSM server. This includes the Chinese government mandated ShāngMì ciphers (SM2, SM3, SM4), as well as post-quantum algorithms such as Kyber, LMS, XMSS, and more! - diff --git a/docs/src/chapter07.md b/docs/src/chapter07.md deleted file mode 100644 index bf22477e1..000000000 --- a/docs/src/chapter07.md +++ /dev/null @@ -1,431 +0,0 @@ -# Customizing wolfHSM - -wolfHSM provides multiple points of customization via build time options and user-supplied callbacks, enabling it to to be tailored to a wide range of use cases and environments without requiring changes to the core library code. This chapter provides an overview of the customization options available in wolfHSM, including: - -- [Library Configuration](#library-configuration): Compile-time options that can be used to enable or disable specific features in the library. -- [DMA Callbacks](#dma-callbacks): Custom callbacks that can be registered with the server to perform operations before and after accessing client memory directly. -- [DMA Address Allow List](#dma-address-allow-list): A mechanism for the server to restrict the client's access to specific memory regions. -- [Custom Callbacks](#custom-callbacks): Custom callbacks that can be registered with the server and invoked by the client to perform specific operations that are not covered by the default HSM capabilities. - -## Library Configuration - -The wolfHSM library has a number of build options that can be turned on or off though compile time definitions. The library expects these configuration macros to be defined in a configuration header named `wh_config.h`. This file should be defined by applications using wolfHSM and located in a directory in the compilers include path. - -An example `wh_config.h` is distributed with every wolfHSM port providing a known good configuration. - -For a full list of wolfHSM configuration settings that can be defined in `wh_config.h`, refer to the [API documentation](appendix01.md). - -## DMA Callbacks - -The Direct Memory Access (DMA) callback feature in wolfHSM provides hooks on the server side for custom operations before and after accessing client memory directly. This is often required when porting to a new shared memory architecture. The feature is particularly useful for scenarios where the server needs to perform specific actions such as cache invalidation, address translation, or other custom memory manipulations to ensure coherency between client and server memory. - -Callbacks can be registered with a server using the `wh_Server_DmaRegisterCb32()` and `wh_Server_DmaRegisterCb64()` functions, which bind the supplied callback to all DMA operations on the server context. - -Separate callback functions for handling 32 and 64-bit addresses are required, corresponding to the distinct 32 and 64-bit client DMA API functions. Callback functions are of type `whServerDmaClientMem32Cb` and `whServerDmaClientMem64Cb`, respectively, defined as: - -```c -typedef int (*whServerDmaClientMem32Cb)(struct whServerContext_t* server, - uint32_t clientAddr, void** serverPtr, - uint32_t len, whServerDmaOper oper, - whServerDmaFlags flags); -typedef int (*whServerDmaClientMem64Cb)(struct whServerContext_t* server, - uint64_t clientAddr, void** serverPtr, - uint64_t len, whServerDmaOper oper, - whServerDmaFlags flags); -``` - -The DMA callback functions receive the following arguments: - -- `server`: A pointer to the server context. -- `clientAddr`: The client memory address to be accessed. -- `serverPtr`: A pointer to a the server memory address (also a pointer), which the callback will set after applying any necessary transformations/remappings -- `len`: The length of the requested memory operation in bytes -- `oper`: The type of memory operation (injection point in the next section) that is about to be performed on the transformed server address -- `flags`: Additional flags for the memory operation. Right now these are reserved for future use and should be ignored. - -The callback should return `WH_ERROR_OK` on success, or an error code if an error occurs. The server will propagate the error code back to the client if the callback fails. - -### Callback Locations - -The DMA callbacks are at four distinct points around the server's memory access: - -- Pre-Read: Callback is invoked before reading data from the client memory. The server should use the callback to perform any necessary pre-read operations, such as address translation or cache invalidation. -- Post-Read: Callback is invoked after reading data from the client memory. The server should use the callback to perform any necessary post-read operations, such as cache synchronization. -- Pre-Write: Callback is invoked before writing data to the client memory. The server should use the callback to perform any necessary pre-write operations, such as address translation or cache invalidation. -- Post-write: Callback is invoked after writing data to the client memory. The server should use the callback to perform any necessary post-write operations, such as cache synchronization. - -The point at which the callback is invoked is passed into the callback through the `oper` argument, which can take the following values: - -```c -typedef enum { - WH_SERVER_DMA_OPER_PRE_READ, /* Pre-read operation */ - WH_SERVER_DMA_OPER_POST_READ, /* Post-read operation */ - WH_SERVER_DMA_OPER_PRE_WRITE, /* Pre-write operation */ - WH_SERVER_DMA_OPER_POST_WRITE /* Post-write operation */ -} whServerDmaOper; -``` - -This enables the callback to `switch` on the `oper` value and perform custom logic based on the type of memory operation being performed. An example DMA callback implementation is shown below: - -```c -#include "wolfhsm/wh_server.h" -#include "wolfhsm/wh_error.h" - -/* Example DMA callback for 32-bit client addresses */ -int myDmaCallback32(whServerContext* server, uint32_t clientAddr, - void** xformedCliAddr, uint32_t len, - whServerDmaOper oper, whServerDmaFlags flags) -{ - /* Optionally transform client address to server address space, e.g. memmap() */ - *xformedCliAddr = (void*)clientAddr; /* do transformation */ - - switch (oper) { - case WH_DMA_OPER_CLIENT_READ_PRE: - /* Pre-Read Operation here, e.g. cache invalidation */ - break; - case WH_DMA_OPER_CLIENT_READ_POST: - /* Post-Read Operation here */ - break; - case WH_DMA_OPER_CLIENT_WRITE_PRE: - /* Pre-Write Operation here */ - break; - case WH_DMA_OPER_CLIENT_WRITE_POST: - /* Post-Write Operation here, e.g. cache flush */ - break; - default: - return WH_ERROR_BADARGS; - } - - return WH_ERROR_OK; -} -``` - -### Callback Registration - -The callback can be registered with the server context, either at initialization through the server configuration structure, or at any time after initialization using the callback registration functions. - -To register the callback at initialization, the callback function should be included in the DMA configuration structure within the server configuration structure. Note that the callback functions are optional, so unused callbacks can be set to `NULL`. - -```c -#include "wolfhsm/wh_server.h" - -/* Example of initializing a server config structr with a DMA32 callback then initializing the server */ -int main(void) -{ - whServerDmaConfig dmaCfg = {0}; - dmaCfg.dma32Cb = myDmaCallback32; - - - whServerConfig serverCfg = { - .dmaCfg = dmaCfg, - - /* other configuration omitted for brevity */ - }; - - whServerContext serverCtx; - - wh_Server_Init(&serverCtx, &serverCfg); - - /* server app logic */ -} -``` - -To register the callback after initialization, first initialize the server context with the desired configuration, then call the appropriate registration function. - -```c -#include "wolfhsm/wh_server.h" - -int main(void) -{ - - whServerConfig serverCfg = { /* server config */ }; - - whServerContext serverCtx; - - wh_Server_Init(&serverCtx, &serverCfg); - - /* register the callback defined above */ - wh_Server_DmaRegisterCb32(&serverCtx, myDmaCallback32); - - /* server app logic */ -} -``` - -## DMA Address Allow List - -wolfHSM also exposes an "allow list" for client DMA addresses, providing a mechanism for the server to restrict the client's access to a pre-configured list of specific memory regions. This feature is particularly useful in scenarios where the server needs to limit the client's access to certain memory regions to prevent unauthorized access or to ensure that the client only accesses memory that is safe to access. For example, in a multicore system with one client running per-core, it is most likely that clients should not be able to access each others memory regions, nor read out server memory which could contain sensitive information like cryptographic keys. - -It is important to note that the software allow list feature is meant to work as a second layer of protection on top of device-specific memory protection mechanisms, and should not be considered a first line of defense in preventing unauthorized memory accesses. It is imperative that the user configure the device-specific memory protection mechanisms required to enforce the isolation of their applications and segment the HSM core and associated memory from the rest of the system. - -### Registering an Allow List - -Similar to the DMA callbacks, the allow list can be registered with the server context, either at initialization through the server configuration structure, or at any time after initialization using the allow list registration functions. - -To register the list at initialization, the list should be populated in the DMA configuration structure inside the server configuration structure. - -```c -#include "wolfhsm/wh_server.h" -#include "wolfhsm/wh_error.h" - -/* Define the allowed memory regions */ -const whServerDmaAddrAllowList allowList = { - .readList = { - {(void*)0x20001000, 0x100}, /* Allow read from 0x20001000 to 0x200010FF */ - {(void*)0x20002000, 0x200}, /* Allow read from 0x20002000 to 0x200021FF */ - }, - .writeList = { - {(void*)0x20003000, 0x100}, /* Allow write from 0x20003000 to 0x200030FF */ - {(void*)0x20004000, 0x200}, /* Allow write from 0x20004000 to 0x200041FF */ - }, -}; - -int main() -{ - whServerConfig config; - - - whServerDmaConfig dmaCfg = {0}; - dmaCfg.allowList = &allowList; - - whServerConfig serverCfg = { - .dmaCfg = dmaCfg, - /* other configuration omitted for brevity */ - }; - - whServerContext server; - - wh_Server_Init(&server, &config); - - /* Server is now configured with the allowlist */ - /* Perform other server operations */ - - /* Allow list can also be registered after initialization if the - * list is not present in the server configuration struct using: - * - * wh_Server_DmaRegisterAllowList(&server, &allowList); - */ -} -``` - -Once registered, all DMA operations requested of the server by the client will be checked against the allow list. If the client attempts to access a memory region that is not in the allow list, the server will return an error to the client, and the operation will not be performed. - -## Custom Callbacks - -The custom callback feature in wolfHSM allows developers to extend the functionality of the library by registering custom callback functions on the server. These callbacks can then be invoked by clients to perform specific operations that are not covered by the default HSM capabilities such as enabling or disabling peripheral hardware, implementing custom monitoring or authentication routines, staging secure boot for an additional core, etc. - -### Server side - -The server can register custom callback functions that define specific operations. These functions must be of type `whServerCustomCb`. - -```c -/* wh_server.h */ - -/* Type definition for a custom server callback */ -typedef int (*whServerCustomCb)( - whServerContext* server, /* points to dispatching server ctx */ - const whMessageCustomCb_Request* req, /* request from client to callback */ - whMessageCustomCb_Response* resp /* response from callback to client */ -); - -``` - -Custom server callback functions are associated with unique identifiers (IDs), which correspond to indices into the server's custom callback dispatch table. The client will refer to the callback by it's ID when it requests invocation. - -The custom callback has access to data passed from the client by value or by reference (useful in a shared memory system) through the `whMessageCustomCb_Request` argument passed into the callback function. The callback can act on the input data and produce output data that can be passed back to the client through th e`whMessageCustomCb_Response` argument. The custom callback does not need to handle sending or receiving any of the input / output client data, as this is handled externally by wolfHSM. The response structure also contains fields for an error code and return code to propagate back to the client. The error code should be populated by the callback, and the return code will be set the return value from the custom callback. - -### Client Side - -Clients can send requests to the server to invoke these custom callbacks. The API provides a request and response function similar to the other functions in the client API. The client should declare an instance of a custom request structure, populate it with its custom data, and then send it to the server using `wh_Client_CustomCbRequest()`. The server response can then be polled using `wh_Client_CustomCbResponse()`, and the response data will populate the output `whMessageCustomCb_Response()` on successful receipt. - -The client can also check the registration status of a given callback ID using the `wh_Client_CustomCheckRegistered()` family of functions. This function queries the server for whether a given callback ID is registered in its internal callback table. The server responds with a true or false indicating the registration status. - -### Custom Messaging - -The client is able to pass data in and receive data from the custom callbacks through the custom request and response message data structures. -These custom request and response messages are structured to include a unique ID, a type indicator, and a data payload. The ID corresponds to the index in the server's callback table. The type field indicating to the custom callback how the data payload should be interpreted. -The data payload is a fixed size data buffer that the client can use in any way it wishes. The response structure contains additional error code values described above. - -```c -/* request message to the custom server callback */ -typedef struct { - uint32_t id; /* identifier of registered callback */ - uint32_t type; /* whMessageCustomCb_Type */ - whMessageCustomCb_Data data; -} whMessageCustomCb_Request; - -/* response message from the custom server callback */ -typedef struct { - uint32_t id; /* identifier of registered callback */ - uint32_t type; /* whMessageCustomCb_Type */ - int32_t rc; /* Return code from custom callback. Invalid if err != 0 */ - int32_t err; /* wolfHSM-specific error. If err != 0, rc is invalid */ - whMessageCustomCb_Data data; -} whMessageCustomCb_Response; -``` - -### Defining Custom Data Types - -Custom data types can be defined using the `whMessageCustomCb_Data` union, which provides several helpful predefined structures for common data types (e.g., dma32, dma64) and a raw data buffer (buffer) for user-defined schemas. Clients can indicate to the server callback how it should interpret the data in the union through the `type` field in the request. wolfHSM reserves the first few type indices for internal use, with the remainder of the type values available for custom client types. - - -### Custom Callback Example - -In this example, a custom callback is implemented that is able to process three types of client requests, one using the built-in DMA-style addressing type, and two that use custom user defined types. - -First, common messages shared between the client and server should be defined: - -```c -/* my_custom_cb.h */ - -#include "wolfhsm/wh_message_customcb.h" - -#define MY_CUSTOM_CB_ID 0 - -enum { - MY_TYPE_A = WH_MESSAGE_CUSTOM_CB_TYPE_USER_DEFINED_START, - MY_TYPE_B, -} myUserDefinedTypes; - -typedef struct { - int foo; - int bar; -} myCustomCbDataA; - -typedef struct { - int noo; - int baz; -} myCustomCbDataB; -``` - -On the server side, the callback must be defined and then registered with the server context before processing requests. Note that the callback can be registered at any time, not necessarily before processing the first request. - -```c -#include "wolfhsm/wh_server.h" -#include "my_custom_cb.h" - -int doWorkOnClientAddr(uint8_t* addr, uint32_t size) { - /* do work */ -} - -int doWorkWithTypeA(myCustomTypeA* typeA) { - /* do work */ -} - -int doWorkWithTypeB(myCustomTypeB* typeB) { - /* do work */ -} - -static int customServerCb(whServerContext* server, - const whMessageCustomCb_Request* req, - whMessageCustomCb_Response* resp) -{ - int rc; - - resp->err = WH_ERROR_OK; - - /* detect and handle DMA request */ - if (req->type == WH_MESSAGE_CUSTOM_CB_TYPE_DMA32) { - uint8_t* clientPtr = (uint8_t*)((uintptr_t)req->data.dma32.client_addr); - size_t clientSz = req->data.dma32.client_sz; - - if (clientPtr == NULL) { - resp->err = WH_ERROR_BADARGS; - } - else { - rc = doWorkOnClientAddr(clientPtr, clientSz); - } - } - else if (req->type == MY_TYPE_A) { - myCustomCbDataA *data = (myCustomCbDataA*)((uintptr_t)req->data.data); - rc = doWorkWithTypeA(data); - /* optionally set error code of your choice */ - if (/* error condition */) { - resp->err = WH_ERROR_ABORTED; - } - } - else if (req->type == MY_TYPE_B) { - myCustomCbDataB *data = (myCustomCbDataB)((uintptr_t)req->data.data); - rc = doWorkWithTypeB(data); - /* optionally set error code of your choice */ - if (/* error condition */) { - resp->err = WH_ERROR_ABORTED; - } - } - - return rc; -} - - -int main(void) { - - whServerContext serverCtx; - - whServerConfig serverCfg = { - /* your server configuration */ - }; - - wh_Server_Init(&serverCtx, &serverCfg); - - wh_Server_RegisterCustomCb(&serverCtx, MY_CUSTOM_CB_ID, customServerCb)); - - /* process server requests (simplified) */ - while (1) { - wh_Server_HandleRequestMessage(&serverCtx); - } - -} -``` - -Now the client is able to check the registration of the custom callback, as well as invoke it remotely: - -```c -#include "wh_client.h" -#include "my_custom_cb.h" - -whClientContext clientCtx; -whClientConfig clientCfg = { - /* your client configuration */ -}; - -whClient_Init(&clientCtx, &clientCfg); - -bool isRegistered = wh_Client_CustomCheckRegistered(&client, MY_CUSTOM_CB_ID); - -if (isRegistered) { - uint8_t buffer[LARGE_SIZE] = {/* data*/}; - myCustomCbDataA typeA = {/* data */}; - myCustomCbDataB typeB = {/* data */}; - - whMessageCustomCb_Request req = {0}; - whMessageCustomCb_Request resp = {0}; - - /* send custom request with built-in DMA type */ - req.id = MY_CUSTOM_CB_ID; - req.type = WH_MESSAGE_CUSTOM_CB_TYPE_DMA32; - req.data.dma32.client_addr = (uint32_t)((uintptr_t)&data); - req.data.dma32.client_sz = sizeof(data); - wh_Client_CustomCbRequest(clientCtx, &req); - wh_Client_CustomCbResponse(clientCtx, &resp); - /* do stuff with response */ - - /* send custom request with a user defined type */ - memset(req, 0, sizeof(req)); - req.id = MY_CUSTOM_CB_ID; - req.type = MY_TYPE_A; - memcpy(&req.data.data, typeA, sizeof(typeA)); - wh_Client_CustomCbRequest(clientCtx, &req); - wh_Client_CustomCbResponse(clientCtx, &resp); - /* do stuff with response */ - - /* send custom request with a different user defined type */ - memset(req, 0, sizeof(req)); - req.id = MY_CUSTOM_CB_ID; - req.type = MY_TYPE_B; - memcpy(&req.data.data, typeA, sizeof(typeB)); - wh_Client_CustomCbRequest(clientCtx, &req); - wh_Client_CustomCbResponse(clientCtx, &resp); - /* do stuff with response */ -} - -``` - diff --git a/docs/src/chapter08.md b/docs/src/chapter08.md deleted file mode 100644 index 095e7b7be..000000000 --- a/docs/src/chapter08.md +++ /dev/null @@ -1,78 +0,0 @@ -# WolfHSM Porting - -This porting section aims to provide you with wolfHSM porting-related material and information. -We will cover the following: - -- WolfHSM Porting Overview -- WolfHSM Ports -- WolfHSM Porting Interface - -## WolfHSM Porting Overview - -wolfHSM itself is not executable and it does not contain any code to interact with any specific hardware. In order for wolfHSM to run on a specific device, the library must be configured with the necessary hardware drivers and abstraction layers so that the server application can run and communicate with the client. Specifically, getting wolfHSM to run on real hardware requires the implementation of the following: - -- Server application startup and hardware initialization -- Server wolfCrypt configuration -- Server non-volatile memory configuration -- Server and client transport configuration -- Server and client connection handling - -The code that provides these requirements and wraps the server API into a bootable application is collectively referred to as a wolfHSM "port". - -Official ports of wolfHSM are provided for various supported architectures, with each port providing the implementation of the wolfHSM abstractions tailored to the specific device. Each port contains: - -- Standalone Reference Server Application: This application is meant to run on the HSM core and handle all secure operations. It comes fully functional out-of-the-box but can also be customized by the end user to support additional use cases -- Client Library: This library can be linked against user applications to facilitate communication with the server - -## WolfHSM Ports - -### Infineon Aurix TC3XX - -The distribution of this port is restricted by the vendor. Please contact support@wolfssl.com for access. - -**Infineon Aurix TC3xx** - -- Up to 6x 300MHz TriCore application cores -- 1x 100MHz ARM Cortex M3 HSM core -- Crypto offload: TRNG, AES128, ECDSA, ED25519, SHA - -### ST SPC58NN - -The distribution of this port is restricted by the vendor. Please contact support@wolfssl.com for access. - -** ST SPC58NN** - -- 3x 200MHz e200z4256 PowerPC application cores -- 1x 100MHz e200z0 PowerPC HSM core with NVM -- Crypto offload: TRNG, AES128 - -### POSIX - -The POSIX port provides multiple and fully functional implementations of different wolfHSM abstractions that can be used to better understand the exact functionality expected for different hardware abstractions. - -The POSIX port provides: -- Memory buffer transport -- TCP transport -- Unix domain transport -- RAM-based and file-based NVM flash simulators - -### Skeleton - -The Skeleton port source code provides a non-functioning layout to be used as a starting point for future hardware/platform ports. Each function provides the basic description and expected flow with error cases explained so that ports can be used interchangeably with consistent results. - -The Skeleton port provides stub implementations of: -- Transport callbacks -- NVM Flash callbacks -- Crypto callbacks - -## WolfHSM Porting Interface - -Ports must implement hardware-specific interfaces: -- NVM flash interface - -Crypto Hardware -- TRNG, Keys, symmetric/asymmetric crypto - -Platform Interface -- Boot sequence, application core reset, memory limitations -- Port and configuration are selected at compile time diff --git a/docs/src/chapter09.md b/docs/src/chapter09.md deleted file mode 100644 index c3796900f..000000000 --- a/docs/src/chapter09.md +++ /dev/null @@ -1,219 +0,0 @@ -# **WARNING** Experimental! Authentication Manager - -The wolfHSM Authentication Manager is still in development and has **known security issues**. It is a transport-agnostic component that provides authentication (PIN and certificate verification), session management, and authorization for wolfHSM operations. It is configured via a callback structure (`whAuthCb`) and can use the default in-memory implementation in `wh_auth_base.c`, or a custom backend that implements the same interface. - -## Table of Contents - -- [Enabling and Configuring the Authentication Manager](#enabling-and-configuring-the-authentication-manager) -- [WH_AUTH_* Macro Helpers](#wh_auth_-macro-helpers) -- [Default User Database (wh_auth_base.c)](#default-user-database-wh_auth_basec) -- [Admin vs Non-Admin Users and Restrictions](#admin-vs-non-admin-users-and-restrictions) -- [Auth Message Group and Actions](#auth-message-group-and-actions) -- [Authorization Callbacks (Override)](#authorization-callbacks-override) -- [Error Codes](#error-codes) -- [Thread Safety and Locking](#thread-safety-and-locking) - -## Enabling and Configuring the Authentication Manager - -### Build-time - -The Authentication Manager feature is off by default. To enable it, define `WOLFHSM_CFG_ENABLE_AUTHENTICATION` when building wolfHSM (e.g., in `wh_config.h` or via compiler flags). Without this macro, auth-related code is excluded from the build and auth requests return `WH_AUTH_NOT_ENABLED`. - -### Building examples with auth - -The POSIX server, POSIX client, and test Makefiles support an authentication-capable build via `AUTH=1`. Pass `AUTH=1` to `make` when building these targets: - -```bash -# From the examples/posix/wh_posix_server or wh_posix_client directory -make AUTH=1 - -# From the test directory -make AUTH=1 - -# From the top-level directory (exports AUTH to subdirectories) -make AUTH=1 examples -``` - -The auth demo client (`wh_demo_client_auth.c`) and related examples require this build to function. - -### Runtime configuration - -Even when auth is compiled in, the server must have an auth context configured. The auth context is set via `whServerConfig.auth` and stored in `server->auth`. If `server->auth == NULL`, no authentication is attempted: - -- Auth group requests (LOGIN, USER_ADD, USER_DELETE, etc.) return `WH_AUTH_NOT_ENABLED` -- Authorization checks for other message groups (NVM, key, crypto, etc.) are skipped entirely - -The server will process requests without requiring login. To enable authentication, the application must initialize an auth context and pass it in the server configuration. - -## WH_AUTH_* Macro Helpers - -The following macros in `wolfhsm/wh_auth.h` simplify setting and checking permissions in a `whAuthPermissions` structure: - -| Macro | Purpose | -|-------|---------| -| `WH_AUTH_IS_ADMIN(permissions)` | Returns non-zero if admin flag is set | -| `WH_AUTH_SET_IS_ADMIN(permissions, value)` | Sets admin flag (0 = non-admin, non-zero = admin) | -| `WH_AUTH_ACTION_TO_WORD_AND_BITMASK(action, wordIdx, bitMask)` | Internal: maps action (0-255) to word index and bitmask | -| `WH_AUTH_SET_ALLOWED_GROUP(permissions, group)` | Enables a message group and allows all actions in that group | -| `WH_AUTH_SET_ALLOWED_ACTION(permissions, group, action)` | Enables group and only the specified action bit | -| `WH_AUTH_CLEAR_ALLOWED_GROUP(permissions, group)` | Disables group and clears all action bits | -| `WH_AUTH_CLEAR_ALLOWED_ACTION(permissions, group, action)` | Clears permission for a specific action | - -Related constants: - -- `WH_AUTH_MAX_KEY_IDS` (2): Maximum number of key IDs a user can have access to -- `WH_AUTH_ACTIONS_PER_GROUP` (256): Support for up to 256 actions per group -- `WH_AUTH_ACTION_WORDS`: Number of `uint32_t` words used for the action bit array per group - -Example: creating a non-admin user with permission to add users but not perform other auth operations: - -```c -#include "wolfhsm/wh_auth.h" -#include "wolfhsm/wh_message.h" - -whAuthPermissions perms; - -memset(&perms, 0, sizeof(perms)); -WH_AUTH_SET_ALLOWED_ACTION(perms, WH_MESSAGE_GROUP_AUTH, - WH_MESSAGE_AUTH_ACTION_USER_ADD); -WH_AUTH_SET_IS_ADMIN(perms, 0); -perms.keyIdCount = 0; - -/* Use perms when adding the user via wh_Auth_UserAdd or in UserAdd request */ -``` - -## Default User Database (wh_auth_base.c) - -The in-memory implementation in `src/wh_auth_base.c` provides a simple user database suitable for development and testing. It can be used as the auth backend by registering the `wh_Auth_Base*` callbacks in `whAuthCb`. - -### Storage - -- Static array of `whAuthBase_User` structures (max 5 users by default via `WH_AUTH_BASE_MAX_USERS`) -- Each entry holds `whAuthUser`, authentication method, and credentials (max 2048 bytes via `WH_AUTH_BASE_MAX_CREDENTIALS_LEN`) - -### Init and Cleanup - -- `wh_Auth_BaseInit` zeros the user array -- `wh_Auth_BaseCleanup` force-zeros sensitive data in memory - -### Login - -- `wh_Auth_BaseLogin` supports: - - `WH_AUTH_METHOD_PIN`: Credentials are SHA256-hashed when crypto is enabled; stored as plain copy when `WOLFHSM_CFG_NO_CRYPTO` is defined - - `WH_AUTH_METHOD_CERTIFICATE`: When `WOLFHSM_CFG_CERTIFICATE_MANAGER` is defined - -### Operations - -- `wh_Auth_BaseUserAdd`, `wh_Auth_BaseUserDelete`, `wh_Auth_BaseUserSetPermissions`, `wh_Auth_BaseUserGet`, `wh_Auth_BaseUserSetCredentials` - -### Lookup - -- `wh_Auth_BaseFindUser` looks up users by username -- User IDs are 1-based (0 reserved for `WH_USER_ID_INVALID`) - -### Usernames - -The default user database does not support multiple users with the same username. Duplicate usernames are rejected in `wh_Auth_BaseUserAdd` with `WH_ERROR_BADARGS`. - -### Example: seeding a default admin user - -The POSIX server example in `examples/posix/wh_posix_server/wh_posix_server_cfg.c` seeds a default admin user at configuration time: - -```c -/* Add an admin user with permissions for everything */ -memset(&permissions, 0xFF, sizeof(whAuthPermissions)); -permissions.keyIdCount = 0; -for (i = 0; i < WH_AUTH_MAX_KEY_IDS; i++) { - permissions.keyIds[i] = 0; -} -rc = wh_Auth_BaseUserAdd(&auth_ctx, "admin", &out_user_id, permissions, - WH_AUTH_METHOD_PIN, "1234", 4); -``` - -## Admin vs Non-Admin Users and Restrictions - -### Admin users - -- Identified by `WH_AUTH_IS_ADMIN(permissions)` returning non-zero (stored in `groupPermissions[WH_NUMBER_OF_GROUPS]`) -- Can add users (including other admins), delete users, set permissions, and set credentials for any user -- Can log out other users (in addition to themselves) - -### Non-admin users - -- Identified by `WH_AUTH_SET_IS_ADMIN(permissions, 0)` or admin flag cleared -- **Key restriction**: Cannot add a user with admin permissions. If a non-admin attempts to add a user whose permissions include the admin flag, the operation fails with `WH_AUTH_PERMISSION_ERROR` (-2301). This is enforced in `wh_Auth_UserAdd` in `src/wh_auth.c` before the backend callback is invoked. -- Cannot delete users (enforced in `wh_Auth_BaseUserDelete`: only admin may delete) -- Cannot set permissions for other users (enforced in `wh_Auth_BaseUserSetPermissions`: only admin may change permissions) -- Can log out only themselves (enforced in `wh_Auth_BaseLogout`: non-admin cannot log out another user) -- Can add non-admin users if they have `WH_MESSAGE_AUTH_ACTION_USER_ADD` in the auth group - -Example: a non-admin user with user-add permission can add other non-admin users but will fail when attempting to add an admin: - -```c -/* Create non-admin with only USER_ADD permission */ -whAuthPermissions nonadmin_perms; -memset(&nonadmin_perms, 0, sizeof(nonadmin_perms)); -WH_AUTH_SET_ALLOWED_ACTION(nonadmin_perms, WH_MESSAGE_GROUP_AUTH, - WH_MESSAGE_AUTH_ACTION_USER_ADD); -WH_AUTH_SET_IS_ADMIN(nonadmin_perms, 0); - -/* After logging in as this user: adding a non-admin succeeds, - * but adding a user with admin permissions (e.g. memset(&perms, 0xFF, ...)) - * returns WH_AUTH_PERMISSION_ERROR. */ -``` - -## Auth Message Group and Actions - -The auth message group is `WH_MESSAGE_GROUP_AUTH` (0x0D00). Available actions in `wolfhsm/wh_message.h`: - -- `WH_MESSAGE_AUTH_ACTION_LOGIN` -- `WH_MESSAGE_AUTH_ACTION_LOGOUT` -- `WH_MESSAGE_AUTH_ACTION_USER_ADD` -- `WH_MESSAGE_AUTH_ACTION_USER_DELETE` -- `WH_MESSAGE_AUTH_ACTION_USER_GET` -- `WH_MESSAGE_AUTH_ACTION_USER_SET_PERMISSIONS` -- `WH_MESSAGE_AUTH_ACTION_USER_SET_CREDENTIALS` - -Unauthenticated users can only perform LOGIN and communications (comm) operations. Logout is always allowed for logged-in users. All other auth actions require the corresponding permission bits to be set in the user's permissions for the auth group. - -## Authorization Callbacks (Override) - -The `whAuthCb` structure defines optional callbacks that allow the auth backend to override default authorization results: - -### CheckRequestAuthorization - -Allows the auth backend to override the default authorization result for a given group and action. After the Auth Manager computes the default result (allowed or denied based on the user's permissions), if this callback is set, it is invoked with the preliminary result. The callback may change the result (e.g., grant access that would otherwise be denied, or deny access that would otherwise be allowed). - -The callback is invoked from `wh_Auth_CheckRequestAuthorization` in `src/wh_auth.c`. Parameters: context, preliminary err, user_id, group, action. Returns the final authorization result. - -### CheckKeyAuthorization - -Placeholder for checking whether a user is authorized to use a specific key ID. This callback is defined in the interface but wolfHSM currently does not invoke it before key use; it is a TODO for future integration. When implemented, it would allow the backend to override key-access decisions (e.g., based on the user's `keyIds` array in permissions). - -## Error Codes - -Auth-related error codes in `wolfhsm/wh_error.h`: - -| Code | Value | Description | -|------|-------|-------------| -| `WH_AUTH_LOGIN_FAILED` | -2300 | User login attempt failed | -| `WH_AUTH_PERMISSION_ERROR` | -2301 | User attempted an action not allowed | -| `WH_AUTH_NOT_ENABLED` | -2302 | Server does not have auth feature | - -## Thread Safety and Locking - -### Conditional compilation - -When `WOLFHSM_CFG_THREADSAFE` is defined, the Auth Manager uses a lock (`whLock`) stored in `whAuthContext` to serialize auth operations. When undefined, locking is disabled and `WH_AUTH_LOCK`/`WH_AUTH_UNLOCK` expand to no-ops (return `WH_ERROR_OK`). - -### Lock acquisition - -All public Auth Manager API functions in `src/wh_auth.c` acquire the lock via `WH_AUTH_LOCK` at entry and release via `WH_AUTH_UNLOCK` before return. Callbacks (Login, Logout, UserAdd, etc.) are invoked while holding the lock. - -### Base implementation - -The default user database in `wh_auth_base.c` uses a static global users array. When `WOLFHSM_CFG_THREADSAFE` is defined, this array is protected by the auth context lock; locking is performed by the `wh_Auth_*` wrapper functions, not by the base implementation itself. - -### Configuration - -`whAuthConfig` includes an optional `lockConfig` (of type `whLockConfig`) when `WOLFHSM_CFG_THREADSAFE` is defined; this is passed to `wh_Lock_Init` during `wh_Auth_Init`. Custom backends that maintain shared state must either rely on this lock or implement their own synchronization. diff --git a/wolfhsm/wh_client_she.h b/wolfhsm/wh_client_she.h index 6bfc66d2c..778bfb47a 100644 --- a/wolfhsm/wh_client_she.h +++ b/wolfhsm/wh_client_she.h @@ -19,6 +19,37 @@ /* * wolfhsm/wh_client_she.h * + * Client API for the AUTOSAR SHE (Secure Hardware Extension) subsystem. + * + * This header declares the client-side interface to the optional wolfHSM SHE + * extension, which is compiled only when WOLFHSM_CFG_SHE_EXTENSION is defined. + * The API maps one-to-one onto the AUTOSAR SHE command set (CMD_SECURE_BOOT, + * CMD_LOAD_KEY, CMD_RND, ...): each spec command is exposed as a + * wh_Client_She* function that marshals the request to the wolfHSM server, + * which implements the spec-compliant SHE behavior on top of the wolfHSM + * keystore, NVM, and crypto infrastructure. + * + * AUTOSAR SHE defines a small, fixed-function security module for automotive + * ECUs: a set of 128-bit AES key slots with fixed roles, an encrypted key + * update protocol (the M1-M5 messages), a CMAC-based secure boot measurement, + * a deterministic PRNG, and an 8-bit status register (SREG). See the AUTOSAR + * "Specification of Secure Hardware Extensions" for the authoritative command + * and protocol definitions. + * + * SHE key slots have fixed roles (defined in wolfhsm/wh_she_common.h): + * ID 0 SECRET_KEY master secret consumed by the PRNG derivation + * ID 1 MASTER_ECU_KEY ECU identity / key-update authorization key + * ID 2 BOOT_MAC_KEY key used to CMAC the bootloader at secure boot + * ID 3 BOOT_MAC expected bootloader CMAC compared at secure boot + * ID 4-13 user key slots general-purpose keys + * ID 14 RAM_KEY volatile slot, lost on power cycle + * ID 15 PRNG_SEED persistent PRNG seed state + * + * Client SHE commands return WH_SHE_ERC_NO_ERROR (defined to WH_ERROR_OK, + * i.e. 0) on success. On a SHE protocol failure they return one of the + * WH_SHE_ERC_* status codes from wolfhsm/wh_error.h. On a transport or argument + * failure they return a negative wolfHSM error code such as WH_ERROR_BADARGS. + * */ #ifndef WOLFHSM_WH_CLIENT_SHE_H_ @@ -36,75 +67,701 @@ #include "wolfhsm/wh_common.h" #include "wolfhsm/wh_client.h" +/** SHE provisioning and identity functions */ + +/** + * @brief Pre-programs a SHE key directly into NVM, bypassing the key update + * protocol. + * + * This is a wolfHSM-specific provisioning helper that has no equivalent in the + * AUTOSAR SHE command set. It writes @p key straight into the SHE NVM slot + * @p keyId with an update counter of zero, skipping the encrypted M1-M5 + * CMD_LOAD_KEY protocol. It is intended for the initial provisioning of a + * blank device (for example installing the MASTER_ECU_KEY or BOOT_MAC at + * production) before any key-update authorization key exists; subsequent + * in-field updates should use the spec-compliant wh_Client_SheLoadKey(). The + * key is scoped to the calling client via the keyId USER field. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to write (0-15, e.g. WH_SHE_MASTER_ECU_KEY_ID). + * @param[in] flags SHE key protection flags to store with the key + * (WH_SHE_FLAG_WRITE_PROTECT, WH_SHE_FLAG_BOOT_PROTECT, etc.). + * @param[in] key Pointer to the key material to store. + * @param[in] keySz Length of the key material in bytes (WH_SHE_KEY_SZ, 16). + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_ShePreProgramKey(whClientContext* c, whNvmId keyId, whNvmFlags flags, uint8_t* key, whNvmSize keySz); + +/** + * @brief Sends a request to set the ECU UID (wolfHSM-specific). + * + * Sends a request carrying the 15-byte unique identifier. This + * command has no AUTOSAR SHE equivalent: the spec assumes the UID is fused in + * hardware, but a software SHE module must be told its UID. The server rejects + * most SHE commands until the UID has been set, and the key update protocol + * binds the M1/M4 messages against this value. + * + * @param[in] c Pointer to the client context. + * @param[in] uid Pointer to the UID bytes to install. + * @param[in] uidSz Length of @p uid; must be at least WH_SHE_UID_SZ (15). + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheSetUidRequest(whClientContext* c, uint8_t* uid, uint32_t uidSz); + +/** + * @brief Receives the response to a set UID request. + * + * @param[in] c Pointer to the client context. + * @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_SheSetUidResponse(whClientContext* c); + +/** + * @brief Sets the ECU UID with a blocking call (wolfHSM-specific). + * + * Sends a set UID request and busy-polls for the response. See + * wh_Client_SheSetUidRequest() for why this command exists outside the AUTOSAR + * SHE command set. The UID must be set before most other SHE commands will be + * accepted by the server. + * + * @param[in] c Pointer to the client context. + * @param[in] uid Pointer to the UID bytes to install. + * @param[in] uidSz Length of @p uid; must be at least WH_SHE_UID_SZ (15). + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheSetUid(whClientContext* c, uint8_t* uid, uint32_t uidSz); + +/** SHE secure boot functions */ + +/** + * @brief Runs the SHE secure boot measurement with a blocking call + * (CMD_SECURE_BOOT). + * + * Implements the AUTOSAR SHE CMD_SECURE_BOOT command by driving the + * three-phase INIT/UPDATE/FINISH state machine in a single blocking call. The + * server initializes a CMAC with the bootloader length, streams @p bootloader + * into it in chunks of up to WOLFHSM_CFG_COMM_DATA_LEN (repeating the UPDATE + * phase as needed), then finalizes the CMAC using BOOT_MAC_KEY (slot 2) and + * compares it against the stored BOOT_MAC (slot 3). The outcome is reported + * through the status register: a match sets WH_SHE_SREG_BOOT_OK while a + * mismatch leaves it clear; either way WH_SHE_SREG_BOOT_FINISHED is set. Until + * secure boot succeeds the server refuses most non-boot SHE commands. + * + * @param[in] c Pointer to the client context. + * @param[in] bootloader Pointer to the bootloader image to measure. + * @param[in] bootloaderLen Length of the bootloader image in bytes. + * @return int Returns 0 on success, or a negative error code on failure. A + * boot measurement mismatch is reported via the status register + * (wh_Client_SheGetStatus), not as an error return. + */ int wh_Client_SheSecureBoot(whClientContext* c, uint8_t* bootloader, uint32_t bootloaderLen); + +/** SHE status functions */ + +/** + * @brief Sends a request to read the SHE status register (CMD_GET_STATUS). + * + * Sends an AUTOSAR SHE CMD_GET_STATUS request. This is a + * zero-length message. + * + * @param[in] c Pointer to the client context. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheGetStatusRequest(whClientContext* c); + +/** + * @brief Receives the SHE status register response (CMD_GET_STATUS). + * + * Consumes a CMD_GET_STATUS response and writes the 8-bit status register + * (SREG) to @p sreg. The SREG bits are defined in wolfhsm/wh_she_common.h + * (WH_SHE_SREG_SECURE_BOOT, WH_SHE_SREG_BOOT_FINISHED, WH_SHE_SREG_BOOT_OK, + * WH_SHE_SREG_RND_INIT, ...). + * + * @param[in] c Pointer to the client context. + * @param[out] sreg Pointer to a byte that receives the status register value. + * @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_SheGetStatusResponse(whClientContext* c, uint8_t* sreg); + +/** + * @brief Reads the SHE status register with a blocking call (CMD_GET_STATUS). + * + * Sends a CMD_GET_STATUS request and busy-polls for the response, writing the + * 8-bit status register (SREG) to @p sreg. See wolfhsm/wh_she_common.h for the + * SREG bit definitions. + * + * @param[in] c Pointer to the client context. + * @param[out] sreg Pointer to a byte that receives the status register value. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheGetStatus(whClientContext* c, uint8_t* sreg); + +/** SHE key management functions */ + +/** + * @brief Sends an encrypted key update request (CMD_LOAD_KEY). + * + * Sends the three AUTOSAR SHE key update messages. M1, M2, and M3 + * encode the target slot, authorization key, new key, update counter, and + * protection flags, encrypted and CMAC-protected under keys derived from the + * authorization key using the spec's Miyaguchi-Preneel construction. + * + * @param[in] c Pointer to the client context. + * @param[in] messageOne M1: WH_SHE_M1_SZ (16) bytes - UID, key ID, auth ID. + * @param[in] messageTwo M2: WH_SHE_M2_SZ (32) bytes - encrypted counter, flags, + * and the new key. + * @param[in] messageThree M3: WH_SHE_M3_SZ (16) bytes - CMAC over M1|M2. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheLoadKeyRequest(whClientContext* c, uint8_t* messageOne, uint8_t* messageTwo, uint8_t* messageThree); + +/** + * @brief Receives the encrypted key update response (CMD_LOAD_KEY). + * + * Consumes the M4 and M5 verification messages the server returns after + * storing the new key. M4 proves the new key and counter were stored, and M5 + * is a CMAC over M4; both are derived from the newly loaded key so the client + * can confirm the update was applied correctly. + * + * @param[in] c Pointer to the client context. + * @param[out] messageFour M4: WH_SHE_M4_SZ (32) bytes - proof of storage. + * @param[out] messageFive M5: WH_SHE_M5_SZ (16) bytes - CMAC over M4. + * @return int Returns 0 on success, WH_ERROR_NOTREADY if no response is + * available yet, or a negative error code on failure (e.g. + * WH_SHE_ERC_KEY_UPDATE_ERROR, WH_SHE_ERC_WRITE_PROTECTED). + */ int wh_Client_SheLoadKeyResponse(whClientContext* c, uint8_t* messageFour, uint8_t* messageFive); + +/** + * @brief Performs an encrypted SHE key update with a blocking call + * (CMD_LOAD_KEY). + * + * Sends the M1/M2/M3 key update messages and busy-polls for the M4/M5 + * verification response. This is the AUTOSAR SHE protocol for installing or + * updating any key slot other than RAM_KEY: the server derives the + * authorization keys, verifies M3, decrypts M2, enforces the strictly + * increasing update counter and the WRITE_PROTECT / UID / WILDCARD + * constraints, stores the new key, and returns M4/M5. Use + * wh_Client_SheLoadPlainKey() to load the volatile RAM_KEY in plaintext. + * + * @param[in] c Pointer to the client context. + * @param[in] messageOne M1: WH_SHE_M1_SZ (16) bytes. + * @param[in] messageTwo M2: WH_SHE_M2_SZ (32) bytes. + * @param[in] messageThree M3: WH_SHE_M3_SZ (16) bytes. + * @param[out] messageFour M4: WH_SHE_M4_SZ (32) bytes. + * @param[out] messageFive M5: WH_SHE_M5_SZ (16) bytes. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheLoadKey(whClientContext* c, uint8_t* messageOne, uint8_t* messageTwo, uint8_t* messageThree, uint8_t* messageFour, uint8_t* messageFive); + +/** + * @brief Sends a request to load the RAM key in plaintext (CMD_LOAD_PLAIN_KEY). + * + * Sends an AUTOSAR SHE CMD_LOAD_PLAIN_KEY request, which installs + * a plaintext key directly into the volatile RAM_KEY slot (ID 14) without the + * encrypted M1-M5 protocol. RAM_KEY lives only in the server key cache and is + * lost on power cycle. + * + * @param[in] c Pointer to the client context. + * @param[in] key Pointer to the plaintext key material. + * @param[in] keySz Length of @p key; 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_SheLoadPlainKeyRequest(whClientContext* c, uint8_t* key, uint32_t keySz); + +/** + * @brief Receives the response to a load plain key request + * (CMD_LOAD_PLAIN_KEY). + * + * @param[in] c Pointer to the client context. + * @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_SheLoadPlainKeyResponse(whClientContext* c); + +/** + * @brief Loads the RAM key in plaintext with a blocking call + * (CMD_LOAD_PLAIN_KEY). + * + * Sends a CMD_LOAD_PLAIN_KEY request and busy-polls for the response, + * installing @p key into the volatile RAM_KEY slot (ID 14), which is lost on + * power cycle. + * + * @param[in] c Pointer to the client context. + * @param[in] key Pointer to the plaintext key material. + * @param[in] keySz Length of @p key; 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_SheLoadPlainKey(whClientContext* c, uint8_t* key, uint32_t keySz); + +/** + * @brief Sends a request to export the RAM key (CMD_EXPORT_RAM_KEY). + * + * Sends an AUTOSAR SHE CMD_EXPORT_RAM_KEY request. This is a + * zero-length message. + * + * @param[in] c Pointer to the client context. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheExportRamKeyRequest(whClientContext* c); + +/** + * @brief Receives the exported RAM key as an M1-M5 blob (CMD_EXPORT_RAM_KEY). + * + * Consumes the CMD_EXPORT_RAM_KEY response, which returns the current RAM_KEY + * (ID 14) packaged as the five key update messages bound to the + * MASTER_ECU_KEY. The resulting M1-M5 blob can be transferred to a peer SHE + * module and installed there via the CMD_LOAD_KEY protocol. + * + * @param[in] c Pointer to the client context. + * @param[out] messageOne M1: WH_SHE_M1_SZ (16) bytes. + * @param[out] messageTwo M2: WH_SHE_M2_SZ (32) bytes. + * @param[out] messageThree M3: WH_SHE_M3_SZ (16) bytes. + * @param[out] messageFour M4: WH_SHE_M4_SZ (32) bytes. + * @param[out] messageFive M5: WH_SHE_M5_SZ (16) bytes. + * @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_SheExportRamKeyResponse(whClientContext* c, uint8_t* messageOne, uint8_t* messageTwo, uint8_t* messageThree, uint8_t* messageFour, uint8_t* messageFive); + +/** + * @brief Exports the RAM key as an M1-M5 blob with a blocking call + * (CMD_EXPORT_RAM_KEY). + * + * Sends a CMD_EXPORT_RAM_KEY request and busy-polls for the response. The + * RAM_KEY is returned as the five key update messages (M1-M5) bound to the + * MASTER_ECU_KEY, suitable for loading into a peer with wh_Client_SheLoadKey(). + * + * @param[in] c Pointer to the client context. + * @param[out] messageOne M1: WH_SHE_M1_SZ (16) bytes. + * @param[out] messageTwo M2: WH_SHE_M2_SZ (32) bytes. + * @param[out] messageThree M3: WH_SHE_M3_SZ (16) bytes. + * @param[out] messageFour M4: WH_SHE_M4_SZ (32) bytes. + * @param[out] messageFive M5: WH_SHE_M5_SZ (16) bytes. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheExportRamKey(whClientContext* c, uint8_t* messageOne, uint8_t* messageTwo, uint8_t* messageThree, uint8_t* messageFour, uint8_t* messageFive); + +/** SHE PRNG functions */ + +/** + * @brief Sends a request to initialize the SHE PRNG (CMD_INIT_RNG). + * + * Sends an AUTOSAR SHE CMD_INIT_RNG request. This is a + * zero-length message. + * + * @param[in] c Pointer to the client context. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheInitRndRequest(whClientContext* c); + +/** + * @brief Receives the response to a PRNG initialization request + * (CMD_INIT_RNG). + * + * On success the server has derived the PRNG key and state from SECRET_KEY and + * PRNG_SEED, advanced and persisted the seed, and set WH_SHE_SREG_RND_INIT in + * the status register. + * + * @param[in] c Pointer to the client context. + * @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_SheInitRndResponse(whClientContext* c); + +/** + * @brief Initializes the SHE PRNG with a blocking call (CMD_INIT_RNG). + * + * Sends a CMD_INIT_RNG request and busy-polls for the response. The + * deterministic PRNG must be initialized with this command before + * wh_Client_SheRnd() can draw random data. + * + * @param[in] c Pointer to the client context. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheInitRnd(whClientContext* c); + +/** + * @brief Sends a request to draw random bytes from the SHE PRNG (CMD_RND). + * + * Sends an AUTOSAR SHE CMD_RND request. This is a zero-length + * message. The PRNG must have been initialized with wh_Client_SheInitRnd(). + * + * @param[in] c Pointer to the client context. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheRndRequest(whClientContext* c); + +/** + * @brief Receives random bytes from the SHE PRNG (CMD_RND). + * + * Consumes a CMD_RND response and copies the generated random block into + * @p out. SHE produces exactly WH_SHE_KEY_SZ (16) bytes per call. + * + * @param[in] c Pointer to the client context. + * @param[out] out Buffer that receives the random bytes. + * @param[in,out] outSz On input, the size of @p out (must be at least + * WH_SHE_KEY_SZ); on output, the number of bytes written + * (WH_SHE_KEY_SZ). + * @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_SheRndResponse(whClientContext* c, uint8_t* out, uint32_t* outSz); + +/** + * @brief Draws random bytes from the SHE PRNG with a blocking call (CMD_RND). + * + * Sends a CMD_RND request and busy-polls for the response, copying + * WH_SHE_KEY_SZ (16) bytes of PRNG output into @p out. wh_Client_SheInitRnd() + * must have been called first. + * + * @param[in] c Pointer to the client context. + * @param[out] out Buffer that receives the random bytes. + * @param[in,out] outSz On input, the size of @p out (must be at least + * WH_SHE_KEY_SZ); on output, the number of bytes written. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheRnd(whClientContext* c, uint8_t* out, uint32_t* outSz); + +/** + * @brief Sends a request to reseed the SHE PRNG (CMD_EXTEND_SEED). + * + * Sends an AUTOSAR SHE CMD_EXTEND_SEED request, mixing + * caller-supplied entropy into the PRNG state. + * + * @param[in] c Pointer to the client context. + * @param[in] entropy Pointer to the additional entropy to mix in. + * @param[in] entropySz Length of @p entropy; must equal WH_SHE_KEY_SZ (16). + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheExtendSeedRequest(whClientContext* c, uint8_t* entropy, uint32_t entropySz); + +/** + * @brief Receives the response to a PRNG reseed request (CMD_EXTEND_SEED). + * + * On success the server has folded the supplied entropy into the PRNG state + * and written the updated PRNG_SEED back to NVM so the reseed survives reboots. + * + * @param[in] c Pointer to the client context. + * @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_SheExtendSeedResponse(whClientContext* c); + +/** + * @brief Reseeds the SHE PRNG with a blocking call (CMD_EXTEND_SEED). + * + * Sends a CMD_EXTEND_SEED request and busy-polls for the response, mixing + * WH_SHE_KEY_SZ (16) bytes of caller-supplied entropy into the PRNG state and + * persisting the updated seed. + * + * @param[in] c Pointer to the client context. + * @param[in] entropy Pointer to the additional entropy to mix in. + * @param[in] entropySz Length of @p entropy; must equal WH_SHE_KEY_SZ (16). + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheExtendSeed(whClientContext* c, uint8_t* entropy, uint32_t entropySz); + +/** SHE cipher functions */ + +/** + * @brief Sends an AES-ECB encryption request (CMD_ENC_ECB). + * + * Sends an AUTOSAR SHE CMD_ENC_ECB request to encrypt @p in under + * the key in slot @p keyId. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to encrypt with. + * @param[in] in Pointer to the plaintext. + * @param[in] sz Length of the plaintext in bytes; must be at least + * WH_SHE_KEY_SZ (16) and fit within WOLFHSM_CFG_COMM_DATA_LEN. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheEncEcbRequest(whClientContext* c, uint8_t keyId, uint8_t* in, uint32_t sz); + +/** + * @brief Receives the AES-ECB ciphertext (CMD_ENC_ECB). + * + * @param[in] c Pointer to the client context. + * @param[out] out Buffer that receives the ciphertext. + * @param[in] sz Size of @p out; must be at least the input size. + * @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_SheEncEcbResponse(whClientContext* c, uint8_t* out, uint32_t sz); + +/** + * @brief Encrypts data with AES-ECB using a blocking call (CMD_ENC_ECB). + * + * Sends a CMD_ENC_ECB request and busy-polls for the response, encrypting + * @p in under the key in slot @p keyId and writing the ciphertext to @p out. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to encrypt with. + * @param[in] in Pointer to the plaintext. + * @param[out] out Buffer that receives the ciphertext (at least @p sz bytes). + * @param[in] sz Length of the data in bytes; must be at least WH_SHE_KEY_SZ. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheEncEcb(whClientContext* c, uint8_t keyId, uint8_t* in, uint8_t* out, uint32_t sz); + +/** + * @brief Sends an AES-CBC encryption request (CMD_ENC_CBC). + * + * Sends an AUTOSAR SHE CMD_ENC_CBC request to encrypt @p in under + * the key in slot @p keyId using the supplied initialization vector. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to encrypt with. + * @param[in] iv Pointer to the initialization vector. + * @param[in] ivSz Length of @p iv; must be at least WH_SHE_KEY_SZ (16). + * @param[in] in Pointer to the plaintext. + * @param[in] sz Length of the plaintext in bytes; must be at least + * WH_SHE_KEY_SZ (16) and fit within WOLFHSM_CFG_COMM_DATA_LEN. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheEncCbcRequest(whClientContext* c, uint8_t keyId, uint8_t* iv, uint32_t ivSz, uint8_t* in, uint32_t sz); + +/** + * @brief Receives the AES-CBC ciphertext (CMD_ENC_CBC). + * + * @param[in] c Pointer to the client context. + * @param[out] out Buffer that receives the ciphertext. + * @param[in] sz Size of @p out; must be at least the input size. + * @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_SheEncCbcResponse(whClientContext* c, uint8_t* out, uint32_t sz); + +/** + * @brief Encrypts data with AES-CBC using a blocking call (CMD_ENC_CBC). + * + * Sends a CMD_ENC_CBC request and busy-polls for the response, encrypting + * @p in under the key in slot @p keyId with the supplied @p iv. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to encrypt with. + * @param[in] iv Pointer to the initialization vector. + * @param[in] ivSz Length of @p iv; must be at least WH_SHE_KEY_SZ (16). + * @param[in] in Pointer to the plaintext. + * @param[out] out Buffer that receives the ciphertext (at least @p sz bytes). + * @param[in] sz Length of the data in bytes; must be at least WH_SHE_KEY_SZ. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheEncCbc(whClientContext* c, uint8_t keyId, uint8_t* iv, uint32_t ivSz, uint8_t* in, uint8_t* out, uint32_t sz); + +/** + * @brief Sends an AES-ECB decryption request (CMD_DEC_ECB). + * + * Sends an AUTOSAR SHE CMD_DEC_ECB request to decrypt @p in under + * the key in slot @p keyId. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to decrypt with. + * @param[in] in Pointer to the ciphertext. + * @param[in] sz Length of the ciphertext in bytes; must be at least + * WH_SHE_KEY_SZ (16) and fit within WOLFHSM_CFG_COMM_DATA_LEN. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheDecEcbRequest(whClientContext* c, uint8_t keyId, uint8_t* in, uint32_t sz); + +/** + * @brief Receives the AES-ECB plaintext (CMD_DEC_ECB). + * + * @param[in] c Pointer to the client context. + * @param[out] out Buffer that receives the plaintext. + * @param[in] sz Size of @p out; must be at least the input size. + * @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_SheDecEcbResponse(whClientContext* c, uint8_t* out, uint32_t sz); + +/** + * @brief Decrypts data with AES-ECB using a blocking call (CMD_DEC_ECB). + * + * Sends a CMD_DEC_ECB request and busy-polls for the response, decrypting + * @p in under the key in slot @p keyId and writing the plaintext to @p out. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to decrypt with. + * @param[in] in Pointer to the ciphertext. + * @param[out] out Buffer that receives the plaintext (at least @p sz bytes). + * @param[in] sz Length of the data in bytes; must be at least WH_SHE_KEY_SZ. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheDecEcb(whClientContext* c, uint8_t keyId, uint8_t* in, uint8_t* out, uint32_t sz); + +/** + * @brief Sends an AES-CBC decryption request (CMD_DEC_CBC). + * + * Sends an AUTOSAR SHE CMD_DEC_CBC request to decrypt @p in under + * the key in slot @p keyId using the supplied initialization vector. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to decrypt with. + * @param[in] iv Pointer to the initialization vector. + * @param[in] ivSz Length of @p iv; must be at least WH_SHE_KEY_SZ (16). + * @param[in] in Pointer to the ciphertext. + * @param[in] sz Length of the ciphertext in bytes; must be at least + * WH_SHE_KEY_SZ (16) and fit within WOLFHSM_CFG_COMM_DATA_LEN. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheDecCbcRequest(whClientContext* c, uint8_t keyId, uint8_t* iv, uint32_t ivSz, uint8_t* in, uint32_t sz); + +/** + * @brief Receives the AES-CBC plaintext (CMD_DEC_CBC). + * + * @param[in] c Pointer to the client context. + * @param[out] out Buffer that receives the plaintext. + * @param[in] sz Size of @p out; must be at least the input size. + * @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_SheDecCbcResponse(whClientContext* c, uint8_t* out, uint32_t sz); + +/** + * @brief Decrypts data with AES-CBC using a blocking call (CMD_DEC_CBC). + * + * Sends a CMD_DEC_CBC request and busy-polls for the response, decrypting + * @p in under the key in slot @p keyId with the supplied @p iv. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to decrypt with. + * @param[in] iv Pointer to the initialization vector. + * @param[in] ivSz Length of @p iv; must be at least WH_SHE_KEY_SZ (16). + * @param[in] in Pointer to the ciphertext. + * @param[out] out Buffer that receives the plaintext (at least @p sz bytes). + * @param[in] sz Length of the data in bytes; must be at least WH_SHE_KEY_SZ. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheDecCbc(whClientContext* c, uint8_t keyId, uint8_t* iv, uint32_t ivSz, uint8_t* in, uint8_t* out, uint32_t sz); + +/** SHE MAC functions */ + +/** + * @brief Sends a CMAC generation request (CMD_GENERATE_MAC). + * + * Sends an AUTOSAR SHE CMD_GENERATE_MAC request to compute a CMAC + * over @p in using the key in slot @p keyId. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to MAC with. + * @param[in] in Pointer to the message to authenticate. + * @param[in] sz Length of the message in bytes; must be at least WH_SHE_KEY_SZ + * (16) and fit within WOLFHSM_CFG_COMM_DATA_LEN. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheGenerateMacRequest(whClientContext* c, uint8_t keyId, uint8_t* in, uint32_t sz); + +/** + * @brief Receives the generated CMAC (CMD_GENERATE_MAC). + * + * @param[in] c Pointer to the client context. + * @param[out] out Buffer that receives the WH_SHE_KEY_SZ (16) byte CMAC. + * @param[in] sz Size of @p out; must be at least WH_SHE_KEY_SZ (16). + * @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_SheGenerateMacResponse(whClientContext* c, uint8_t* out, uint32_t sz); + +/** + * @brief Generates a CMAC with a blocking call (CMD_GENERATE_MAC). + * + * Sends a CMD_GENERATE_MAC request and busy-polls for the response, computing + * a CMAC over @p in under the key in slot @p keyId and writing the 16-byte tag + * to @p out. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to MAC with. + * @param[in] in Pointer to the message to authenticate. + * @param[in] inSz Length of the message in bytes; must be at least + * WH_SHE_KEY_SZ (16). + * @param[out] out Buffer that receives the CMAC. + * @param[in] outSz Size of @p out; 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_SheGenerateMac(whClientContext* c, uint8_t keyId, uint8_t* in, uint32_t inSz, uint8_t* out, uint32_t outSz); + +/** + * @brief Sends a CMAC verification request (CMD_VERIFY_MAC). + * + * Sends an AUTOSAR SHE CMD_VERIFY_MAC request to verify @p mac + * against a freshly computed CMAC of @p message using the key in slot @p keyId. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to verify with. + * @param[in] message Pointer to the message that was authenticated. + * @param[in] messageLen Length of @p message; must be at least WH_SHE_KEY_SZ + * (16). The message plus MAC must fit within + * WOLFHSM_CFG_COMM_DATA_LEN. + * @param[in] mac Pointer to the CMAC to verify. + * @param[in] macLen Length of @p mac; 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_SheVerifyMacRequest(whClientContext* c, uint8_t keyId, uint8_t* message, uint32_t messageLen, uint8_t* mac, uint32_t macLen); + +/** + * @brief Receives the CMAC verification result (CMD_VERIFY_MAC). + * + * Consumes a CMD_VERIFY_MAC response and reports whether the MAC matched + * through @p outStatus. Per the SHE spec a verification mismatch is a normal + * result rather than an error: the function still returns success and signals + * the mismatch via @p outStatus. + * + * @param[in] c Pointer to the client context. + * @param[out] outStatus Set to 0 if the MAC verified successfully, or 1 if it + * did not match. + * @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_SheVerifyMacResponse(whClientContext* c, uint8_t* outStatus); + +/** + * @brief Verifies a CMAC with a blocking call (CMD_VERIFY_MAC). + * + * Sends a CMD_VERIFY_MAC request and busy-polls for the response, verifying + * @p mac against a CMAC of @p message under the key in slot @p keyId. A + * verification mismatch is reported through @p outStatus, not as an error + * return. + * + * @param[in] c Pointer to the client context. + * @param[in] keyId SHE key slot to verify with. + * @param[in] message Pointer to the message that was authenticated. + * @param[in] messageLen Length of @p message; must be at least WH_SHE_KEY_SZ. + * @param[in] mac Pointer to the CMAC to verify. + * @param[in] macLen Length of @p mac; must be at least WH_SHE_KEY_SZ (16). + * @param[out] outStatus Set to 0 if the MAC verified successfully, or 1 if it + * did not match. + * @return int Returns 0 on success, or a negative error code on failure. + */ int wh_Client_SheVerifyMac(whClientContext* c, uint8_t keyId, uint8_t* message, uint32_t messageLen, uint8_t* mac, uint32_t macLen, uint8_t* outStatus); From 369484ca56e025b322c61019c01dd6a24ce0c77f Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Fri, 29 May 2026 09:39:41 -0600 Subject: [PATCH 2/5] undo rename of auth.md --- docs/draft/{auth.md.bak => auth.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/draft/{auth.md.bak => auth.md} (100%) diff --git a/docs/draft/auth.md.bak b/docs/draft/auth.md similarity index 100% rename from docs/draft/auth.md.bak rename to docs/draft/auth.md From 8425e83b77e567677e21886cb08ec03bdeaad95c Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Fri, 29 May 2026 09:39:52 -0600 Subject: [PATCH 3/5] fix typos and links --- docs/src/1-Overview.md | 2 +- docs/src/2-FAQs.md | 6 +++--- docs/src/4-Architecture.md | 2 +- docs/src/6-Utilities.md | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/1-Overview.md b/docs/src/1-Overview.md index 7f661139d..93b655385 100644 --- a/docs/src/1-Overview.md +++ b/docs/src/1-Overview.md @@ -40,7 +40,7 @@ of any platform while still supporting standardized interfaces and protocols suc * Extensible server behavior via user-defined callbacks for runtime functionality and message handling * Non-volatile monotonic counters * AUTOSAR Secure Hardware Extension (SHE) interface support -* Support for user authentication with full roll-based access control +* Support for user authentication with full role-based access control ## Supported Platforms diff --git a/docs/src/2-FAQs.md b/docs/src/2-FAQs.md index 5b1232b97..c26fb10c8 100644 --- a/docs/src/2-FAQs.md +++ b/docs/src/2-FAQs.md @@ -3,7 +3,7 @@ ## What is wolfHSM? wolfHSM is a portable, open-source client-server framework for cryptography, -non-volatile memory (NVM), and isolated secure processing. A server application runs in a trusted environment (physical HSM core, trust zone secure wordl, remote server, etc.) while client applications use a library API that can offload cryptographic and storage operations to the server. The core library exposes a client and server API to help developers stitch together their own HSM applications using a curated set of ready-to-use and secure +non-volatile memory (NVM), and isolated secure processing. A server application runs in a trusted environment (physical HSM core, trust zone secure world, remote server, etc.) while client applications use a library API that can offload cryptographic and storage operations to the server. The core library exposes a client and server API to help developers stitch together their own HSM applications using a curated set of ready-to-use and secure components. ## Why would I need wolfHSM? @@ -52,7 +52,7 @@ Due to wolfHSM’s modular design, reference platform code can be easily replace ## Does wolfHSM require dynamic memory allocation? -No. wolfHSM is designed to avoid dynamic memory allocation. The library itself does not ever allocate from the heap. wolfCrypt, a dependency of wolfHSM, **does** require allocation for *some* functionality, however this can be configured to use statically allocated fixed-size memory pools, eliminating the nead for a runtime heap. See [chapter04](https://www.wolfssl.com/documentation/manuals/wolfssl/chapter04.html#static-buffer-allocation-option) of the wolfSSL manual for more details on wolfCrypt static memory. When wolfCrypt is configured to use the static memory feature, wolfHSM applications are guaranteed to never allocate from the heap. +No. wolfHSM is designed to avoid dynamic memory allocation. The library itself does not ever allocate from the heap. wolfCrypt, a dependency of wolfHSM, **does** require allocation for *some* functionality, however this can be configured to use statically allocated fixed-size memory pools, eliminating the need for a runtime heap. See [chapter04](https://www.wolfssl.com/documentation/manuals/wolfssl/chapter04.html#static-buffer-allocation-option) of the wolfSSL manual for more details on wolfCrypt static memory. When wolfCrypt is configured to use the static memory feature, wolfHSM applications are guaranteed to never allocate from the heap. ## What is a wolfHSM "port"? @@ -81,7 +81,7 @@ wolfHSM (and wolfCrypt) is written in portable C and is designed to build with a ## Does wolfHSM support device/platform X? -In general, yes. Reference ports currently exist for the listed [supported platforms](1-Overview.md#supported-platforms). Most platform ports are NDA-restricted by the silicon vendor and must distributed separately as their own bundle. If a port does not exist for your platform, adding support can typically be accomplished by in a matter of weeks, depending on the complexity of the device and desired use case. wolfSSL also routinely adds ports to new devices on request as part of a consulting engagment. +In general, yes. Reference ports currently exist for the listed [supported platforms](1-Overview.md#supported-platforms). Most platform ports are NDA-restricted by the silicon vendor and must be distributed separately as their own bundle. If a port does not exist for your platform, adding support can typically be accomplished in a matter of weeks, depending on the complexity of the device and desired use case. wolfSSL also routinely adds ports to new devices on request as part of a consulting engagement. If you are interested in obtaining a restricted port for a platform, or want to see a new device supported, contact facts@wolfssl.com. diff --git a/docs/src/4-Architecture.md b/docs/src/4-Architecture.md index 6045f9fee..146e3b0e1 100644 --- a/docs/src/4-Architecture.md +++ b/docs/src/4-Architecture.md @@ -152,7 +152,7 @@ rc = wh_Component_DoSomething(comp_context, 1, 2, 3); rc = wh_Component_CleanUp(comp_context); ``` -Modules in wolHSM that are currently implemented in this way are: +Modules in wolfHSM that are currently implemented in this way are: - Transports: define how bytes move between client and server. The core library only cares about send/receive semantics and state management. - NVM: define how persistent storage on the server is formatted and accessed. diff --git a/docs/src/6-Utilities.md b/docs/src/6-Utilities.md index c8f56924d..6d8f0c054 100644 --- a/docs/src/6-Utilities.md +++ b/docs/src/6-Utilities.md @@ -18,7 +18,7 @@ This chapter describes the auxiliary tools that ship alongside the wolfHSM clien The NVM provisioning tool (`tools/whnvmtool/`) is a host-side utility that builds a pre-populated wolfHSM NVM image from a configuration file. It is intended for device provisioning: rather than having the server populate its NVM at runtime, the integrator describes the desired initial contents — a set of NVM objects and keys, each with its metadata ID, access permissions, flags, label, and a path to the binary payload — and the tool produces a single image file that can be programmed into the device's flash at manufacture or used in place to back a `whNvmFlash` provider in simulation. Currently the tool targets the `whNvmFlash` provider; the generated image is binary, and can be converted to Intel HEX with the standard `objcopy` workflow for use with automated programmers. -Because the on-flash layout depends on build-time configuration, the tool must be compiled against the same wolfHSM version as the target server and with a matching `WOLFHSM_CFG_NVM_OBJECT_COUNT`, and the `--size` argument must match the server's `whNvmFlash` partition size. For the full configuration file schema, command-line options, hex conversion recipe, and test workflow, see [`tools/whnvmtool/README.md`](../tools/whnvmtool/README.md). +Because the on-flash layout depends on build-time configuration, the tool must be compiled against the same wolfHSM version as the target server and with a matching `WOLFHSM_CFG_NVM_OBJECT_COUNT`, and the `--size` argument must match the server's `whNvmFlash` partition size. For the full configuration file schema, command-line options, hex conversion recipe, and test workflow, see [`tools/whnvmtool/README.md`](https://github.com/wolfSSL/wolfHSM/blob/main/tools/whnvmtool/README.md). ## Benchmark Suite @@ -30,7 +30,7 @@ The benchmark app consists of individual modules that each measure the various c The same client application builds and runs against any supported port: on POSIX the client and server run in separate threads of the host process, and on embedded targets the application links into the port's runtime alongside a board-specific timer and `printf`. Iteration counts, data buffer sizes, DMA buffers, and timing/printing hooks are all overridable through `WOLFHSM_CFG_BENCH_*` macros so the suite can be tuned to the constraints of the target. -For the full list of configuration macros, the module interface, instructions for adding a new benchmark, and the internal layout of the framework, see [`benchmark/README.md`](../benchmark/README.md). +For the full list of configuration macros, the module interface, instructions for adding a new benchmark, and the internal layout of the framework, see [`benchmark/README.md`](https://github.com/wolfSSL/wolfHSM/blob/main/benchmark/README.md). ### Running Benchmarks on POSIX @@ -70,7 +70,7 @@ The test suite (`test/`) is a standalone wolfHSM application that exercises the The same test sources build for POSIX hosts and for embedded targets. Tests that depend on POSIX facilities (sockets, pthreads, file-backed flash) are compiled in only when `WOLFHSM_CFG_TEST_POSIX` is defined, so an embedded port pulls in just the portable subset and selects whichever modules its configuration supports. Output goes through `WOLFHSM_CFG_PRINTF` and assertions go through `WOLFHSM_CFG_TEST_ASSERT_FUNC`, so both can be redirected to port-supplied implementations. -For the full list of test modules, supported build options, and code coverage workflow, see [`test/README.md`](../test/README.md). +For the full list of test modules, supported build options, and code coverage workflow, see [`test/README.md`](https://github.com/wolfSSL/wolfHSM/blob/main/test/README.md). ### Running Tests on POSIX From 842f588568fcf5c9bb4efea9f11c30ec18bd966b Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Fri, 29 May 2026 10:34:55 -0600 Subject: [PATCH 4/5] strip hyperlinks --- docs/src/10-API-docs-server.md | 2 +- docs/src/9-API-docs-client.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/10-API-docs-server.md b/docs/src/10-API-docs-server.md index ef04667a3..ff933a37c 100644 --- a/docs/src/10-API-docs-server.md +++ b/docs/src/10-API-docs-server.md @@ -1,6 +1,6 @@ # Server API Reference -This chapter is the complete reference for the wolfHSM **server** API. It is generated directly from the documentation comments in the public server headers ([`wolfhsm/wh_server.h`](../../wolfhsm/wh_server.h), [`wolfhsm/wh_server_keystore.h`](../../wolfhsm/wh_server_keystore.h), [`wolfhsm/wh_server_img_mgr.h`](../../wolfhsm/wh_server_img_mgr.h), [`wolfhsm/wh_server_cert.h`](../../wolfhsm/wh_server_cert.h), and [`wolfhsm/wh_server_cert_cache.h`](../../wolfhsm/wh_server_cert_cache.h)), so it always tracks the source. For a conceptual, feature-oriented walkthrough of what these functions are for, see [Features](5-Features.md); this chapter documents the precise signatures, parameters, and return values. +This chapter is the complete reference for the wolfHSM **server** API. It is generated directly from the documentation comments in the public server headers (`wolfhsm/wh_server.h`, `wolfhsm/wh_server_keystore.h`, `wolfhsm/wh_server_img_mgr.h`, `wolfhsm/wh_server_cert.h`, and `wolfhsm/wh_server_cert_cache.h`), so it always tracks the source. For a conceptual, feature-oriented walkthrough of what these functions are for, see [Features](5-Features.md); this chapter documents the precise signatures, parameters, and return values. - **[Server API](wh__server_8h.md)** — server context lifecycle, configuration, and top-level request dispatch (`wolfhsm/wh_server.h`). - **[Server Keystore API](wh__server__keystore_8h.md)** — server-side key cache and keystore operations: cache and evict slots, NVM commit, key export, and metadata access (`wolfhsm/wh_server_keystore.h`). diff --git a/docs/src/9-API-docs-client.md b/docs/src/9-API-docs-client.md index 6728ac283..77b9dbef1 100644 --- a/docs/src/9-API-docs-client.md +++ b/docs/src/9-API-docs-client.md @@ -1,6 +1,6 @@ # Client API Reference -This chapter is the complete reference for the wolfHSM **client** API. It is generated directly from the documentation comments in the public client headers ([`wolfhsm/wh_client.h`](../../wolfhsm/wh_client.h), [`wolfhsm/wh_client_crypto.h`](../../wolfhsm/wh_client_crypto.h), and [`wolfhsm/wh_client_she.h`](../../wolfhsm/wh_client_she.h)), so it always tracks the source. For a conceptual, feature-oriented walkthrough of what these functions are for, see [Features](5-Features.md); this chapter documents the precise signatures, parameters, and return values. +This chapter is the complete reference for the wolfHSM **client** API. It is generated directly from the documentation comments in the public client headers (`wolfhsm/wh_client.h`, `wolfhsm/wh_client_crypto.h`, and `wolfhsm/wh_client_she.h`), so it always tracks the source. For a conceptual, feature-oriented walkthrough of what these functions are for, see [Features](5-Features.md); this chapter documents the precise signatures, parameters, and return values. - **[Client API](wh__client_8h.md)** — client context lifecycle, communication, NVM, keystore, certificate, image-manager, and counter operations (`wolfhsm/wh_client.h`). - **[Client Crypto API](wh__client__crypto_8h.md)** — split-transaction, non-blocking crypto request/response calls (`wolfhsm/wh_client_crypto.h`). From ee862934e9eac59b05ba35ebc8bd95e0211a374f Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Fri, 29 May 2026 10:59:04 -0600 Subject: [PATCH 5/5] reorder docs --- docs/src/0-Index.md | 10 +++++----- .../{9-API-docs-client.md => 10-API-docs-client.md} | 0 .../{10-API-docs-server.md => 11-API-docs-server.md} | 0 docs/src/4-Architecture.md | 2 +- docs/src/5-Features.md | 8 ++++---- docs/src/6-Utilities.md | 4 ++-- docs/src/7-Examples.md | 2 +- docs/src/{11-Configuration.md => 9-Configuration.md} | 0 8 files changed, 13 insertions(+), 13 deletions(-) rename docs/src/{9-API-docs-client.md => 10-API-docs-client.md} (100%) rename docs/src/{10-API-docs-server.md => 11-API-docs-server.md} (100%) rename docs/src/{11-Configuration.md => 9-Configuration.md} (100%) diff --git a/docs/src/0-Index.md b/docs/src/0-Index.md index 8269fee88..613d80da8 100644 --- a/docs/src/0-Index.md +++ b/docs/src/0-Index.md @@ -62,11 +62,11 @@ Reference applications demonstrating wolfHSM usage. ### [8. Integration](8-Integration.md) Guides for integrating wolfHSM with the wider wolfSSL ecosystem. -### [9. Client API Reference](9-API-docs-client.md) +### [9. Configuration](9-Configuration.md) +Build-time and runtime configuration options for wolfHSM. + +### [10. Client API Reference](10-API-docs-client.md) Reference documentation for the wolfHSM client-side API. -### [10. Server API Reference](10-API-docs-server.md) +### [11. Server API Reference](11-API-docs-server.md) Reference documentation for the wolfHSM server-side API. - -### [11. Configuration](11-Configuration.md) -Build-time and runtime configuration options for wolfHSM. diff --git a/docs/src/9-API-docs-client.md b/docs/src/10-API-docs-client.md similarity index 100% rename from docs/src/9-API-docs-client.md rename to docs/src/10-API-docs-client.md diff --git a/docs/src/10-API-docs-server.md b/docs/src/11-API-docs-server.md similarity index 100% rename from docs/src/10-API-docs-server.md rename to docs/src/11-API-docs-server.md diff --git a/docs/src/4-Architecture.md b/docs/src/4-Architecture.md index 146e3b0e1..131e65d7a 100644 --- a/docs/src/4-Architecture.md +++ b/docs/src/4-Architecture.md @@ -109,7 +109,7 @@ Once these steps are completed, wolfHSM will use the user-defined configuration Note that the default base configuration and global library settings are defined in `wolfhsm/wh_settings.h`. Every wolfHSM source file includes this header first to establish the configuration environment. When `WOLFHSM_CFG` is defined, `wh_settings.h` conditionally includes `wolfhsm_cfg.h`, ensuring that user-supplied overrides are applied before any internal defaults are used. -For an exhaustive list of all wolfHSM config macros, see [11-Configuration.md](11-Configuration.md). +For an exhaustive list of all wolfHSM config macros, see [9-Configuration.md](9-Configuration.md). ## Internals Deep Dive: Modular Architecture diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 8f71f38f8..6e06d1d1c 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -1,6 +1,6 @@ # Features -This chapter provides a detailed overview of the high level features that wolfHSM provides. Each section is intended to convey *what* a given feature does, what functionality it exposes, and what a developer can build with it. Concrete API usage and signatures are deferred to the client and server API references in [9-API-docs-client.md](9-API-docs-client.md) and [10-API-docs-server.md](10-API-docs-server.md). +This chapter provides a detailed overview of the high level features that wolfHSM provides. Each section is intended to convey *what* a given feature does, what functionality it exposes, and what a developer can build with it. Concrete API usage and signatures are deferred to the client and server API references in [10-API-docs-client.md](10-API-docs-client.md) and [11-API-docs-server.md](11-API-docs-server.md). ## Table of Contents @@ -131,13 +131,13 @@ Clients control whether a given crypto request should prefer hardware or softwar Affinity is stored on the client and sent in the header of every crypto request, so a change takes effect on the next operation with no extra round-trip. -The affinity is set and queried using `wh_Client_SetCryptoAffinity` and `wh_Client_GetCryptoAffinity`. See the [client API reference](9-API-docs-client.md) for the precise signatures. +The affinity is set and queried using `wh_Client_SetCryptoAffinity` and `wh_Client_GetCryptoAffinity`. See the [client API reference](10-API-docs-client.md) for the precise signatures. ### Blocking and Non-Blocking Interfaces Operations invoked through the standard wolfCrypt API are **blocking**: the call does not return until the server responds (or the transport fails). This matches what applications already expect from wolfCrypt and is the simplest way to port existing code to wolfHSM. -For non-blocking, split-transaction behavior, wolfHSM also exposes native client crypto APIs in `wolfhsm/wh_client_crypto.h` that follow the same send-request / receive-response pattern as the rest of the client API. These come as paired `wh_Client_Request` and `wh_Client_Response` calls, so a caller can issue a request, do other work, and poll for the result later. They cover a subset of algorithms (see the [client API reference](9-API-docs-client.md)), and blocking and non-blocking calls must not be interleaved on the same `whClientContext` while a request is outstanding. +For non-blocking, split-transaction behavior, wolfHSM also exposes native client crypto APIs in `wolfhsm/wh_client_crypto.h` that follow the same send-request / receive-response pattern as the rest of the client API. These come as paired `wh_Client_Request` and `wh_Client_Response` calls, so a caller can issue a request, do other work, and poll for the result later. They cover a subset of algorithms (see the [client API reference](10-API-docs-client.md)), and blocking and non-blocking calls must not be interleaved on the same `whClientContext` while a request is outstanding. > **Note**: Because the standard wolfCrypt API is blocking on the client side, applications that need to overlap crypto work with other activity should either use the native non-blocking client API, or run their wolfCrypt calls from a dedicated thread with its own `whClientContext`. See [Concurrency Support](#concurrency-support) for guidance on multi-threaded usage. @@ -496,7 +496,7 @@ The motivating use cases all involve payloads that are either too large or too i For wolfCrypt-mediated operations, opt-in to DMA is a single change at the call site: instead of passing `WH_DEV_ID` as the device identifier, the client passes `WH_DEV_ID_DMA`. Both device IDs route to the wolfHSM client crypto callback, but the DMA device tells the callback to construct a DMA-flavored request whose payload carries pointers and lengths into the client's address space rather than inline data. The server-side dispatcher recognizes the DMA request kind and, for each referenced buffer, hands the pointer to the server's DMA address-processing path (described below) before invoking the underlying wolfCrypt primitive. The set of algorithms that have a DMA path mirrors the most performance-sensitive subset of the supported algorithms; algorithms without a DMA path return an unsupported error if invoked with `WH_DEV_ID_DMA`, and the application can fall back to the standard `WH_DEV_ID` for those. -For the non-crypto subsystems (NVM, certificate manager, image manager, key cache/export, and the data-wrap API) the DMA-aware request kinds are exposed as `*Dma` variants of the corresponding client API functions — `wh_Client_NvmAddObjectDma`, `wh_Client_KeyCacheDma`, `wh_Client_KeyExportDma`, `wh_Client_CertVerifyDma`, and so on. See the [client API reference](9-API-docs-client.md) for the full set. +For the non-crypto subsystems (NVM, certificate manager, image manager, key cache/export, and the data-wrap API) the DMA-aware request kinds are exposed as `*Dma` variants of the corresponding client API functions — `wh_Client_NvmAddObjectDma`, `wh_Client_KeyCacheDma`, `wh_Client_KeyExportDma`, `wh_Client_CertVerifyDma`, and so on. See the [client API reference](10-API-docs-client.md) for the full set. ### Pre-Access and Post-Access Callbacks diff --git a/docs/src/6-Utilities.md b/docs/src/6-Utilities.md index 6d8f0c054..42eb2368f 100644 --- a/docs/src/6-Utilities.md +++ b/docs/src/6-Utilities.md @@ -83,7 +83,7 @@ make make run ``` -Feature-specific builds are selected with the same makefile variables documented in [11-Configuration.md](11-Configuration.md) — for example `DMA=1`, `SHE=1`, `AUTH=1`, `TLS=1`, `THREADSAFE=1`, `TESTWOLFCRYPT=1`. Development and CI builds also commonly set `ASAN=1` (address sanitizer), `TSAN=1` (thread sanitizer, mutually exclusive with `ASAN`), `DEBUG=1`, or `COVERAGE=1` (instruments the build for `gcovr`; see `make coverage`). +Feature-specific builds are selected with the same makefile variables documented in [9-Configuration.md](9-Configuration.md) — for example `DMA=1`, `SHE=1`, `AUTH=1`, `TLS=1`, `THREADSAFE=1`, `TESTWOLFCRYPT=1`. Development and CI builds also commonly set `ASAN=1` (address sanitizer), `TSAN=1` (thread sanitizer, mutually exclusive with `ASAN`), `DEBUG=1`, or `COVERAGE=1` (instruments the build for `gcovr`; see `make coverage`). To build a client-only driver that connects to an already-running server over TCP (or TLS when `TLS=1` is set), pass `CLIENT_ONLY=1`: @@ -100,6 +100,6 @@ Each hardware port ships with its own instructions for compiling and running the **1. Compile the framework sources alongside the application.** Add `test/wh_test.c`, `test/wh_test_common.c`, and the per-module `test/wh_test_*.c` files for the components you wish to validate. Each module is independent, so an embedded port can pick a subset (e.g. crypto, keystore, certificates) and omit modules whose features the build does not enable. Leave `WOLFHSM_CFG_TEST_POSIX` undefined so the POSIX-only paths (sockets, pthreads, file-backed flash) are excluded. -**2. Define the required configuration macros.** Define `WOLFHSM_CFG_TEST_UNIT_NO_MAIN` to suppress the default `main()` so the application can call the test entry points itself. If the platform lacks a working stdlib `assert()`, define `WOLFHSM_CFG_TEST_ASSERT_FUNC` to a port-supplied assertion routine; output is already routed through `WOLFHSM_CFG_PRINTF` (see [11-Configuration.md](11-Configuration.md)). Enable individual test categories by defining the same feature macros that gate them in the library (e.g. `WOLFHSM_CFG_DMA`, `WOLFHSM_CFG_SHE_EXTENSION`, `WOLFHSM_CFG_TEST_WOLFCRYPTTEST`). +**2. Define the required configuration macros.** Define `WOLFHSM_CFG_TEST_UNIT_NO_MAIN` to suppress the default `main()` so the application can call the test entry points itself. If the platform lacks a working stdlib `assert()`, define `WOLFHSM_CFG_TEST_ASSERT_FUNC` to a port-supplied assertion routine; output is already routed through `WOLFHSM_CFG_PRINTF` (see [9-Configuration.md](9-Configuration.md)). Enable individual test categories by defining the same feature macros that gate them in the library (e.g. `WOLFHSM_CFG_DMA`, `WOLFHSM_CFG_SHE_EXTENSION`, `WOLFHSM_CFG_TEST_WOLFCRYPTTEST`). **3. Drive the test entry points from the application.** Like the benchmark suite, the tests are purely client-side: they issue requests to a wolfHSM server and validate the responses. The server can be any wolfHSM server listening on the same transport — typically the application's own production server. To run the full client-side test set against a client the application has already configured, call `whTest_ClientConfig(whClientConfig*)`; individual modules expose their own client entry points (`whTest_CryptoClientConfig`, `whTest_SheClientConfig`, `whTest_TimeoutClientConfig`, etc.) when only a subset is desired. The suite also exposes `whTest_ServerCfgLoop(whServerConfig*)`, a minimal server processing loop suitable for in-process or co-resident-core test runs when no production server is available. How the client and server are scheduled relative to each other — separate cores, separate tasks, or cooperatively from a main loop — is determined by the port. diff --git a/docs/src/7-Examples.md b/docs/src/7-Examples.md index 89858cae3..9864a4310 100644 --- a/docs/src/7-Examples.md +++ b/docs/src/7-Examples.md @@ -40,7 +40,7 @@ To exercise the demo client library against the running server, pass `--test` to ./wh_posix_client.elf --test ``` -The full set of supported build options for the example applications is documented in [11-Configuration.md](11-Configuration.md); the same `DMA=1`, `SHE=1`, `AUTH=1`, `TLS=1` knobs that gate features in the test and benchmark suites apply here. +The full set of supported build options for the example applications is documented in [9-Configuration.md](9-Configuration.md); the same `DMA=1`, `SHE=1`, `AUTH=1`, `TLS=1` knobs that gate features in the test and benchmark suites apply here. ### Transport Selection diff --git a/docs/src/11-Configuration.md b/docs/src/9-Configuration.md similarity index 100% rename from docs/src/11-Configuration.md rename to docs/src/9-Configuration.md