From 08d21105aa4afabd5fcb0cfe8d4037b7f4bedd1e Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Mon, 12 Oct 2026 19:07:08 -0600 Subject: [PATCH 01/23] WIP: HW keystore improvements --- src/wh_client.c | 221 +++++++++++++++++ src/wh_hwkeystore.c | 363 ++++++++++++++++++++++++++++ src/wh_message_keystore.c | 72 ++++++ src/wh_server.c | 6 + src/wh_server_crypto.c | 357 +++++++++++++++++++++------ src/wh_server_keystore.c | 202 +++++++++++++++- src/wh_server_she.c | 18 +- src/wh_she_crypto.c | 12 +- test/wh_test_check_struct_padding.c | 6 + wolfhsm/wh_client.h | 98 ++++++++ wolfhsm/wh_common.h | 8 + wolfhsm/wh_hwkeystore.h | 130 ++++++++++ wolfhsm/wh_message.h | 3 + wolfhsm/wh_message_keystore.h | 65 +++++ wolfhsm/wh_server_keystore.h | 20 ++ wolfhsm/wh_settings.h | 31 +++ 16 files changed, 1517 insertions(+), 95 deletions(-) diff --git a/src/wh_client.c b/src/wh_client.c index b859e40d8..e357c3c92 100644 --- a/src/wh_client.c +++ b/src/wh_client.c @@ -1450,6 +1450,227 @@ int wh_Client_KeyRevoke(whClientContext* c, whKeyId keyId) return ret; } +int wh_Client_HwKeystoreLoadFromKeystoreRequest(whClientContext* c, + whKeyId hwKeyId, + whKeyId srcKeyId) +{ + whMessageKeystore_HwLoadRequest* req = NULL; + + if ((c == NULL) || WH_KEYID_ISERASED(srcKeyId)) { + return WH_ERROR_BADARGS; + } + + req = (whMessageKeystore_HwLoadRequest*)wh_CommClient_GetDataPtr(c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + memset(req, 0, sizeof(*req)); + req->hwId = hwKeyId; + req->srcId = srcKeyId; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, WH_KEY_HW_LOAD, + sizeof(*req), (uint8_t*)req); +} + +int wh_Client_HwKeystoreLoadFromKeystoreResponse(whClientContext* c, + whKeyId* outHwKeyId) +{ + uint16_t group; + uint16_t action; + uint16_t size; + int ret; + whMessageKeystore_HwLoadResponse* resp = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + resp = (whMessageKeystore_HwLoadResponse*)wh_CommClient_GetDataPtr(c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(c, &group, &action, &size, + WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)resp); + if (ret == 0) { + if (size < sizeof(*resp)) { + ret = WH_ERROR_ABORTED; + } + else if (resp->rc != 0) { + ret = resp->rc; + } + else if (outHwKeyId != NULL) { + *outHwKeyId = resp->hwId; + } + } + return ret; +} + +int wh_Client_HwKeystoreLoadFromKeystore(whClientContext* c, whKeyId hwKeyId, + whKeyId srcKeyId, whKeyId* outHwKeyId) +{ + int ret; + ret = wh_Client_HwKeystoreLoadFromKeystoreRequest(c, hwKeyId, srcKeyId); + if (ret == 0) { + do { + ret = wh_Client_HwKeystoreLoadFromKeystoreResponse(c, outHwKeyId); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + +/* ID 0 clears every key the client loaded */ +static int _HwKeystoreClearRequest(whClientContext* c, whKeyId hwKeyId) +{ + whMessageKeystore_HwClearRequest* req = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageKeystore_HwClearRequest*)wh_CommClient_GetDataPtr(c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + memset(req, 0, sizeof(*req)); + req->hwId = hwKeyId; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, WH_KEY_HW_CLEAR, + sizeof(*req), (uint8_t*)req); +} + +int wh_Client_HwKeystoreClearRequest(whClientContext* c, whKeyId hwKeyId) +{ + if (WH_KEYID_ISERASED(hwKeyId)) { + return WH_ERROR_BADARGS; + } + return _HwKeystoreClearRequest(c, hwKeyId); +} + +int wh_Client_HwKeystoreClearResponse(whClientContext* c) +{ + uint16_t group; + uint16_t action; + uint16_t size; + int ret; + whMessageKeystore_HwClearResponse* resp = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + resp = + (whMessageKeystore_HwClearResponse*)wh_CommClient_GetDataPtr(c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(c, &group, &action, &size, + WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)resp); + if (ret == 0) { + if (size < sizeof(*resp)) { + ret = WH_ERROR_ABORTED; + } + else if (resp->rc != 0) { + ret = resp->rc; + } + } + return ret; +} + +int wh_Client_HwKeystoreClear(whClientContext* c, whKeyId hwKeyId) +{ + int ret; + ret = wh_Client_HwKeystoreClearRequest(c, hwKeyId); + if (ret == 0) { + do { + ret = wh_Client_HwKeystoreClearResponse(c); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + +int wh_Client_HwKeystoreClearAll(whClientContext* c) +{ + int ret; + ret = _HwKeystoreClearRequest(c, WH_KEYID_ERASED); + if (ret == 0) { + do { + ret = wh_Client_HwKeystoreClearResponse(c); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + +int wh_Client_HwKeystoreGetInfoRequest(whClientContext* c, whKeyId hwKeyId) +{ + whMessageKeystore_HwInfoRequest* req = NULL; + + if (c == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageKeystore_HwInfoRequest*)wh_CommClient_GetDataPtr(c->comm); + if (req == NULL) { + return WH_ERROR_BADARGS; + } + memset(req, 0, sizeof(*req)); + req->hwId = hwKeyId; + + return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, WH_KEY_HW_INFO, + sizeof(*req), (uint8_t*)req); +} + +int wh_Client_HwKeystoreGetInfoResponse(whClientContext* c, + whHwKeystoreInfo* info) +{ + uint16_t group; + uint16_t action; + uint16_t size; + int ret; + whMessageKeystore_HwInfoResponse* resp = NULL; + + if ((c == NULL) || (info == NULL)) { + return WH_ERROR_BADARGS; + } + + resp = (whMessageKeystore_HwInfoResponse*)wh_CommClient_GetDataPtr(c->comm); + if (resp == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(c, &group, &action, &size, + WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)resp); + if (ret == 0) { + if (size < sizeof(*resp)) { + ret = WH_ERROR_ABORTED; + } + else if (resp->rc != 0) { + ret = resp->rc; + } + else { + info->keyType = resp->keyType; + info->keyBits = resp->keyBits; + info->flags = resp->flags; + info->owner = resp->owner; + } + } + return ret; +} + +int wh_Client_HwKeystoreGetInfo(whClientContext* c, whKeyId hwKeyId, + whHwKeystoreInfo* info) +{ + int ret; + ret = wh_Client_HwKeystoreGetInfoRequest(c, hwKeyId); + if (ret == 0) { + do { + ret = wh_Client_HwKeystoreGetInfoResponse(c, info); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + int wh_Client_CounterInitRequest(whClientContext* c, whNvmId counterId, uint32_t counter) { diff --git a/src/wh_hwkeystore.c b/src/wh_hwkeystore.c index 69f1b04ae..5557270bc 100644 --- a/src/wh_hwkeystore.c +++ b/src/wh_hwkeystore.c @@ -36,6 +36,12 @@ #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_hwkeystore.h" +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/cryptocb.h" +#include "wolfssl/wolfcrypt/wc_keystore.h" +#endif + int wh_HwKeystore_Init(whHwKeystoreContext* context, const whHwKeystoreConfig* config) { @@ -143,4 +149,361 @@ int wh_HwKeystore_GetKey(whHwKeystoreContext* context, whKeyId keyId, return rc; } +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO + +static int _Lock(whHwKeystoreContext* context) +{ +#ifdef WOLFHSM_CFG_THREADSAFE + return wh_Lock_Acquire(&context->lock); +#else + (void)context; + return WH_ERROR_OK; +#endif +} + +static void _Unlock(whHwKeystoreContext* context) +{ +#ifdef WOLFHSM_CFG_THREADSAFE + (void)wh_Lock_Release(&context->lock); +#else + (void)context; +#endif +} + +/* A hardware keyId the front end can act on: TYPE=HW and a non-zero ID */ +static int _Valid(whHwKeystoreContext* context, whKeyId keyId) +{ + return (context != NULL) && (context->initialized != 0) && + WH_KEYID_ISHW(keyId) && !WH_KEYID_ISERASED(keyId); +} + +/* Table entry for the hardware key with the same ID field, or NULL */ +static whHwKeystoreKey* _Find(whHwKeystoreContext* context, whKeyId keyId) +{ + int i; + + for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { + if ((context->keys[i].id != WH_KEYID_ERASED) && + (WH_KEYID_ID(context->keys[i].id) == WH_KEYID_ID(keyId))) { + return &context->keys[i]; + } + } + return NULL; +} + +static whHwKeystoreKey* _FindFree(whHwKeystoreContext* context) +{ + int i; + + for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { + if (context->keys[i].id == WH_KEYID_ERASED) { + return &context->keys[i]; + } + } + return NULL; +} + +static int _PortImport(int devId, whKeyId keyId, const uint8_t* key, + uint16_t keySz) +{ + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + + wh_HwKeystore_KeyRef(keyId, ref); + return wc_KeyStore_ImportPlain(devId, ref, sizeof(ref), WC_KEYSTORE_KEY_AES, + key, keySz, 0, NULL); +} + +static int _PortInfo(int devId, whKeyId keyId, word32* keyType, word32* keyBits) +{ + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + word32 attrs = 0; + + wh_HwKeystore_KeyRef(keyId, ref); + return wc_KeyStore_GetInfo(devId, ref, sizeof(ref), keyType, keyBits, + &attrs, NULL); +} + +/* Clear the key in hardware, then free its entry */ +static int _Remove(int devId, whHwKeystoreKey* entry) +{ + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + int ret; + + wh_HwKeystore_KeyRef(entry->id, ref); + ret = wc_KeyStore_Delete(devId, ref, sizeof(ref), NULL); + if (ret == 0) { + memset(entry, 0, sizeof(*entry)); + } + return ret; +} + +void wh_HwKeystore_KeyRef(whKeyId keyId, uint8_t* ref) +{ + ref[0] = (uint8_t)(keyId >> 8); + ref[1] = (uint8_t)keyId; +} + +int wh_HwKeystore_Register(whHwKeystoreContext* context, whKeyId keyId, + whNvmFlags flags, uint16_t len) +{ + whHwKeystoreKey* entry; + int ret; + + if (!_Valid(context, keyId)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret != WH_ERROR_OK) { + return ret; + } + if (_Find(context, keyId) != NULL) { + ret = WH_ERROR_ACCESS; + } + else { + entry = _FindFree(context); + if (entry == NULL) { + ret = WH_ERROR_NOSPACE; + } + else { + entry->id = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, + WH_KEYID_ID(keyId)); + entry->srcId = WH_KEYID_ERASED; + entry->flags = + (flags & WH_NVM_FLAGS_USAGE_ANY) | WH_NVM_FLAGS_NONDESTROYABLE; + entry->len = len; + } + } + _Unlock(context); + return ret; +} + +int wh_HwKeystore_Load(whHwKeystoreContext* context, int devId, + whKeyId* inout_keyId, whKeyId srcId, whNvmFlags flags, + const uint8_t* key, uint16_t keySz) +{ + whHwKeystoreKey* entry = NULL; + whKeyId keyId; + whKeyId cand; + word32 keyType; + word32 keyBits; + int id; + int ret; + + if ((context == NULL) || (context->initialized == 0) || + (inout_keyId == NULL) || !WH_KEYID_ISHW(*inout_keyId) || + (key == NULL) || (keySz == 0)) { + return WH_ERROR_BADARGS; + } + keyId = *inout_keyId; + + ret = _Lock(context); + if (ret != WH_ERROR_OK) { + return ret; + } + + if (WH_KEYID_ISERASED(keyId)) { + /* Try each hardware key the port reports empty, in ID order */ + entry = _FindFree(context); + ret = WH_ERROR_NOSPACE; + for (id = 1; (entry != NULL) && (id <= WH_KEYID_IDMAX); id++) { + cand = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYID_USER(keyId), id); + if ((_Find(context, cand) != NULL) || + (_PortInfo(devId, cand, &keyType, &keyBits) != 0) || + (keyType != WC_KEYSTORE_KEY_NONE)) { + continue; + } + ret = _PortImport(devId, cand, key, keySz); + if (ret == 0) { + keyId = cand; + break; + } + } + } + else { + entry = _Find(context, keyId); + if (entry != NULL) { + /* Only the owner may replace a key, and never a port key */ + if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + } + else { + entry = _FindFree(context); + if (entry == NULL) { + ret = WH_ERROR_NOSPACE; + } + } + if (ret == WH_ERROR_OK) { + ret = _PortImport(devId, keyId, key, keySz); + } + } + + if (ret == 0) { + entry->id = keyId; + entry->srcId = srcId; + entry->flags = flags & WH_NVM_FLAGS_USAGE_ANY; + entry->len = keySz; + *inout_keyId = keyId; + } + _Unlock(context); + return ret; +} + +int wh_HwKeystore_Clear(whHwKeystoreContext* context, int devId, whKeyId keyId) +{ + whHwKeystoreKey* entry; + int ret; + + if (!_Valid(context, keyId)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret != WH_ERROR_OK) { + return ret; + } + entry = _Find(context, keyId); + if (entry == NULL) { + ret = WH_ERROR_NOTFOUND; + } + else if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + else { + ret = _Remove(devId, entry); + } + _Unlock(context); + return ret; +} + +int wh_HwKeystore_ClearAll(whHwKeystoreContext* context, int devId, + uint16_t user) +{ + whHwKeystoreKey* entry; + int i; + int rc; + int ret; + + if ((context == NULL) || (context->initialized == 0)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret != WH_ERROR_OK) { + return ret; + } + for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { + entry = &context->keys[i]; + if ((entry->id != WH_KEYID_ERASED) && + ((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) == 0) && + (WH_KEYID_USER(entry->id) == user)) { + rc = _Remove(devId, entry); + if (ret == WH_ERROR_OK) { + ret = rc; + } + } + } + _Unlock(context); + return ret; +} + +int wh_HwKeystore_ClearSource(whHwKeystoreContext* context, int devId, + whKeyId srcId) +{ + whHwKeystoreKey* entry; + int i; + int rc; + int ret; + + if ((context == NULL) || (context->initialized == 0) || + (srcId == WH_KEYID_ERASED)) { + return WH_ERROR_BADARGS; + } + + ret = _Lock(context); + if (ret != WH_ERROR_OK) { + return ret; + } + for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { + entry = &context->keys[i]; + if ((entry->id != WH_KEYID_ERASED) && (entry->srcId == srcId)) { + rc = _Remove(devId, entry); + if (ret == WH_ERROR_OK) { + ret = rc; + } + } + } + _Unlock(context); + return ret; +} + +int wh_HwKeystore_CheckUse(whHwKeystoreContext* context, whKeyId keyId, + whNvmFlags usage, whKeyId* outKeyId) +{ + whHwKeystoreKey* entry; + int ret; + + if (!_Valid(context, keyId) || (outKeyId == NULL)) { + return WH_ERROR_BADARGS; + } + usage &= WH_NVM_FLAGS_USAGE_ANY; + + ret = _Lock(context); + if (ret != WH_ERROR_OK) { + return ret; + } + entry = _Find(context, keyId); + if (entry == NULL) { + ret = WH_ERROR_NOTFOUND; + } + else if ((WH_KEYID_USER(entry->id) != WH_KEYUSER_GLOBAL) && + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + else if ((usage == 0) || ((entry->flags & usage) != usage)) { + ret = WH_ERROR_USAGE; + } + else { + *outKeyId = entry->id; + } + _Unlock(context); + return ret; +} + +int wh_HwKeystore_GetInfo(whHwKeystoreContext* context, int devId, + whKeyId keyId, whHwKeystoreInfo* info) +{ + whHwKeystoreKey* entry; + word32 keyType = 0; + word32 keyBits = 0; + int ret; + + if (!_Valid(context, keyId) || (info == NULL)) { + return WH_ERROR_BADARGS; + } + memset(info, 0, sizeof(*info)); + + ret = _Lock(context); + if (ret != WH_ERROR_OK) { + return ret; + } + ret = _PortInfo(devId, keyId, &keyType, &keyBits); + if (ret == 0) { + info->keyType = keyType; + info->keyBits = keyBits; + entry = _Find(context, keyId); + if (entry != NULL) { + info->flags = entry->flags & WH_NVM_FLAGS_USAGE_ANY; + info->owner = WH_KEYID_USER(entry->id); + } + } + _Unlock(context); + return ret; +} + +#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ + #endif /* WOLFHSM_CFG_HWKEYSTORE */ diff --git a/src/wh_message_keystore.c b/src/wh_message_keystore.c index 5aa467c12..57e974b97 100644 --- a/src/wh_message_keystore.c +++ b/src/wh_message_keystore.c @@ -249,6 +249,78 @@ int wh_MessageKeystore_TranslateRevokeResponse( return 0; } +int wh_MessageKeystore_TranslateHwLoadRequest( + uint16_t magic, const whMessageKeystore_HwLoadRequest* src, + whMessageKeystore_HwLoadRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T16(magic, dest, src, hwId); + WH_T16(magic, dest, src, srcId); + return 0; +} + +int wh_MessageKeystore_TranslateHwLoadResponse( + uint16_t magic, const whMessageKeystore_HwLoadResponse* src, + whMessageKeystore_HwLoadResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + WH_T16(magic, dest, src, hwId); + return 0; +} + +int wh_MessageKeystore_TranslateHwClearRequest( + uint16_t magic, const whMessageKeystore_HwClearRequest* src, + whMessageKeystore_HwClearRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T16(magic, dest, src, hwId); + return 0; +} + +int wh_MessageKeystore_TranslateHwClearResponse( + uint16_t magic, const whMessageKeystore_HwClearResponse* src, + whMessageKeystore_HwClearResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + return 0; +} + +int wh_MessageKeystore_TranslateHwInfoRequest( + uint16_t magic, const whMessageKeystore_HwInfoRequest* src, + whMessageKeystore_HwInfoRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T16(magic, dest, src, hwId); + return 0; +} + +int wh_MessageKeystore_TranslateHwInfoResponse( + uint16_t magic, const whMessageKeystore_HwInfoResponse* src, + whMessageKeystore_HwInfoResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, rc); + WH_T32(magic, dest, src, keyType); + WH_T32(magic, dest, src, keyBits); + WH_T16(magic, dest, src, flags); + WH_T16(magic, dest, src, owner); + return 0; +} + #ifdef WOLFHSM_CFG_DMA /* * DMA-based keystore operations diff --git a/src/wh_server.c b/src/wh_server.c index b2296ed9f..7b1ad2dac 100644 --- a/src/wh_server.c +++ b/src/wh_server.c @@ -688,6 +688,12 @@ static uint16_t _FormatErrorResponse(uint16_t magic, uint16_t group, size = sizeof(whMessageKeystore_DataUnwrapResponse); break; #endif /* WOLFHSM_CFG_KEYWRAP */ + case WH_KEY_HW_LOAD: + case WH_KEY_HW_CLEAR: + case WH_KEY_HW_INFO: + /* rc comes first in all three, so the largest fits all */ + size = sizeof(whMessageKeystore_HwInfoResponse); + break; default: size = sizeof(int32_t); break; diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index ee1dbd2ec..8feddaebc 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -3173,13 +3173,52 @@ static int _HandleEd25519VerifyDma(whServerContext* ctx, uint16_t magic, #endif /* HAVE_ED25519 */ #ifndef NO_AES +/* Check that the client may use a hardware key for this request. On success + * *outHwKey is the key to pass to wolfCrypt by reference */ +static int _HwKeyCheckUse(whServerContext* ctx, int devId, whKeyId keyId, + whNvmFlags usage, whKeyId* outHwKey) +{ +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO + if ((ctx->hwKeystore == NULL) || (devId == INVALID_DEVID)) { + return WH_ERROR_NOTFOUND; + } + return wh_HwKeystore_CheckUse(ctx->hwKeystore, keyId, usage, outHwKey); +#else + /* Without it, hardware keys are only key encryption keys */ + (void)ctx; + (void)devId; + (void)keyId; + (void)usage; + (void)outHwKey; + return WH_ERROR_ACCESS; +#endif +} + +/* Init aes for a request. A hardware key goes to wolfCrypt by reference: no + * key is set, and the crypto callback runs every operation */ +static int _AesInitForKey(Aes* aes, int devId, whKeyId hwKey) +{ +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO + if (hwKey != WH_KEYID_ERASED) { + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + + wh_HwKeystore_KeyRef(hwKey, ref); + return wc_AesInit_Id(aes, ref, (int)sizeof(ref), NULL, devId); + } +#else + (void)hwKey; +#endif + return wc_AesInit(aes, NULL, devId); +} + #ifdef WOLFSSL_AES_COUNTER static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t inSize, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId hwKey = WH_KEYID_ERASED; whMessageCrypto_AesCtrRequest req; whMessageCrypto_AesCtrResponse res; uint8_t cachedKey[AES_256_KEY_SIZE]; @@ -3224,7 +3263,14 @@ static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCtr] IV ", iv, AES_BLOCK_SIZE); WH_DEBUG_VERBOSE_HEXDUMP("[AesCtr] tmp ", tmp, AES_BLOCK_SIZE); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISHW(key_id)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -3244,19 +3290,26 @@ static int _HandleAesCtr(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCtr] Key ", key, key_len); } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCtr] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesSetKeyDirect(aes, (byte*)key, (word32)key_len, (byte*)iv, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. A hardware key only needs the counter */ + if (hwKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = + wc_AesSetKeyDirect(aes, (byte*)key, (word32)key_len, (byte*)iv, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* Reject client-supplied left values outside the valid range. * wc_AesCtrEncrypt indexes aes->tmp via AES_BLOCK_SIZE - aes->left; @@ -3318,7 +3371,9 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesCtrDmaRequest req; whMessageCrypto_AesCtrDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId hwKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -3378,7 +3433,14 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISHW(keyId)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -3396,8 +3458,9 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCtr] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -3425,13 +3488,20 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); + aesInit = (ret == WH_ERROR_OK); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesSetKeyDirect(aes, (byte*)key, (word32)keyLen, (byte*)iv, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. A hardware key only needs the counter */ + if (hwKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = + wc_AesSetKeyDirect(aes, (byte*)key, (word32)keyLen, (byte*)iv, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* Reject client-supplied left values outside the valid range. * wc_AesCtrEncrypt indexes aes->tmp via AES_BLOCK_SIZE - aes->left; @@ -3491,7 +3561,9 @@ static int _HandleAesCtrDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -3515,7 +3587,8 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId hwKey = WH_KEYID_ERASED; whMessageCrypto_AesEcbRequest req; whMessageCrypto_AesEcbResponse res; uint8_t cachedKey[AES_256_KEY_SIZE]; @@ -3556,7 +3629,14 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesEcb] Input data", in, len); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISHW(key_id)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -3576,19 +3656,23 @@ static int _HandleAesEcb(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesEcb] Key ", key, key_len); } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesEcb] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); } if (ret == WH_ERROR_OK) { - /* load the key. AES-ECB does not use IV */ - ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, NULL, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. AES-ECB does not use IV. A hardware key needs no + * setup */ + if (hwKey == WH_KEYID_ERASED) { + ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, NULL, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* do the crypto operation */ if (enc != 0) { @@ -3631,7 +3715,9 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesEcbDmaRequest req; whMessageCrypto_AesEcbDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId hwKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -3680,7 +3766,14 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISHW(keyId)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -3698,8 +3791,9 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesEcb] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -3725,10 +3819,11 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); + aesInit = (ret == WH_ERROR_OK); } - if (ret == WH_ERROR_OK) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED) { ret = wc_AesSetKey(aes, (byte*)key, (word32)keyLen, NULL, req.enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); } @@ -3778,7 +3873,9 @@ static int _HandleAesEcbDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -3800,7 +3897,8 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId hwKey = WH_KEYID_ERASED; whMessageCrypto_AesCbcRequest req; whMessageCrypto_AesCbcResponse res; uint8_t cachedKey[AES_256_KEY_SIZE]; @@ -3846,7 +3944,14 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCbc] Input data", in, len); WH_DEBUG_VERBOSE_HEXDUMP("[AesCbc] IV", iv, AES_BLOCK_SIZE); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISHW(key_id)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -3866,19 +3971,25 @@ static int _HandleAesCbc(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_VERBOSE_HEXDUMP("[AesCbc] Key ", key, key_len); } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCbc] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, (byte*)iv, - enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* load the key. A hardware key only needs the IV */ + if (hwKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = wc_AesSetKey(aes, (byte*)key, (word32)key_len, (byte*)iv, + enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } if (ret == WH_ERROR_OK) { /* do the crypto operation */ if (enc != 0) { @@ -3922,7 +4033,9 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesCbcDmaRequest req; whMessageCrypto_AesCbcDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId hwKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -3977,7 +4090,14 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISHW(keyId)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -3995,8 +4115,9 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesCbc] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -4022,12 +4143,19 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); + aesInit = (ret == WH_ERROR_OK); } if (ret == WH_ERROR_OK) { - ret = wc_AesSetKey(aes, (byte*)key, keyLen, (byte*)iv, - req.enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + /* A hardware key only needs the IV */ + if (hwKey != WH_KEYID_ERASED) { + ret = wc_AesSetIV(aes, (byte*)iv); + } + else { + ret = wc_AesSetKey(aes, (byte*)key, keyLen, (byte*)iv, + req.enc != 0 ? AES_ENCRYPTION : AES_DECRYPTION); + } } if (ret == WH_ERROR_OK) { @@ -4075,7 +4203,9 @@ static int _HandleAesCbcDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -4097,7 +4227,8 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId, void* cryptoDataOut, uint16_t* outSize) { int ret = WH_ERROR_OK; - Aes aes[1] = {0}; + Aes aes[1]; + whKeyId hwKey = WH_KEYID_ERASED; uint8_t cachedKey[AES_256_KEY_SIZE]; whNvmMetadata keyMeta[1]; @@ -4166,7 +4297,14 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId, (uint32_t)needed_size); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(key_id)) { + if (WH_KEYID_ISHW(key_id)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, key_id, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(key_id)) { /* Copy the key + metadata into private buffers, enforcing the usage * policy against the same locked snapshot that is read. The crypto * below then runs entirely on the private copy. */ @@ -4184,18 +4322,21 @@ static int _HandleAesGcm(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && key_len != AES_128_KEY_SIZE && - key_len != AES_192_KEY_SIZE && key_len != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + key_len != AES_128_KEY_SIZE && key_len != AES_192_KEY_SIZE && + key_len != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesGcm] Invalid key size: %d", key_len); ret = WH_ERROR_BADARGS; } if (ret == WH_ERROR_OK) { /* init key with possible hardware */ - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); } if (ret == WH_ERROR_OK) { - /* load the key */ - ret = wc_AesGcmSetKey(aes, (byte*)key, (word32)key_len); + /* load the key. A hardware key needs no setup */ + if (hwKey == WH_KEYID_ERASED) { + ret = wc_AesGcmSetKey(aes, (byte*)key, (word32)key_len); + } WH_DEBUG_SERVER_VERBOSE("AesGcmSetKey key_id:%u key_len:%u ret:%d\n", key_id, key_len, ret); WH_DEBUG_VERBOSE_HEXDUMP("[server] key: ", key, key_len); @@ -4258,7 +4399,9 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, int ret = WH_ERROR_OK; whMessageCrypto_AesGcmDmaRequest req; whMessageCrypto_AesGcmDmaResponse res; - Aes aes[1] = {0}; + Aes aes[1]; + int aesInit = 0; + whKeyId hwKey = WH_KEYID_ERASED; void* inAddr = NULL; void* outAddr = NULL; @@ -4332,7 +4475,14 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, ctx->comm->client_id, req.keyId); /* Freshen key and validate usage policy if key is not erased */ - if (!WH_KEYID_ISERASED(keyId)) { + if (WH_KEYID_ISHW(keyId)) { + /* Hardware key: used by reference, never read */ + ret = _HwKeyCheckUse(ctx, devId, keyId, + req.enc != 0 ? WH_NVM_FLAGS_USAGE_ENCRYPT + : WH_NVM_FLAGS_USAGE_DECRYPT, + &hwKey); + } + else if (!WH_KEYID_ISERASED(keyId)) { /* Copy the key + metadata into private buffers, enforcing the * usage policy against the same locked snapshot that is read. The * crypto below then runs entirely on the private copy. */ @@ -4350,8 +4500,9 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, } } /* Verify key size is valid for AES */ - if (ret == WH_ERROR_OK && keyLen != AES_128_KEY_SIZE && - keyLen != AES_192_KEY_SIZE && keyLen != AES_256_KEY_SIZE) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED && + keyLen != AES_128_KEY_SIZE && keyLen != AES_192_KEY_SIZE && + keyLen != AES_256_KEY_SIZE) { WH_DEBUG_SERVER("[AesGcm] Invalid key size: %d", keyLen); ret = WH_ERROR_BADARGS; } @@ -4397,10 +4548,11 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, } if (ret == WH_ERROR_OK) { - ret = wc_AesInit(aes, NULL, devId); + ret = _AesInitForKey(aes, devId, hwKey); + aesInit = (ret == WH_ERROR_OK); } - if (ret == WH_ERROR_OK) { + if (ret == WH_ERROR_OK && hwKey == WH_KEYID_ERASED) { ret = wc_AesGcmSetKey(aes, (byte*)key, (word32)keyLen); } @@ -4453,7 +4605,9 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, } } - wc_AesFree(aes); + if (aesInit) { + wc_AesFree(aes); + } /* Set response */ res.outSz = outSz; @@ -4474,13 +4628,17 @@ static int _HandleAesGcmDma(whServerContext* ctx, uint16_t magic, int devId, #if defined(WOLFSSL_CMAC) && !defined(NO_AES) && defined(WOLFSSL_AES_DIRECT) /* Resolve CMAC key from request (inline key or keystore ID). - * outKey must be at least AES_256_KEY_SIZE bytes. */ -static int _CmacResolveKey(whServerContext* ctx, const uint8_t* requestKey, - uint32_t requestKeySz, whKeyId clientKeyId, - uint8_t* outKey, uint32_t* outKeyLen) + * outKey must be at least AES_256_KEY_SIZE bytes. A hardware key is returned + * in *outHwKey instead, to use by reference */ +static int _CmacResolveKey(whServerContext* ctx, int devId, + const uint8_t* requestKey, uint32_t requestKeySz, + whKeyId clientKeyId, uint8_t* outKey, + uint32_t* outKeyLen, whKeyId* outHwKey) { int ret = WH_ERROR_OK; + *outHwKey = WH_KEYID_ERASED; + if (requestKeySz != 0) { /* Client provided the key directly in the request */ memcpy(outKey, requestKey, requestKeySz); @@ -4491,6 +4649,18 @@ static int _CmacResolveKey(whServerContext* ctx, const uint8_t* requestKey, whKeyId keyId = wh_KeyId_TranslateFromClient( WH_KEYTYPE_CRYPTO, ctx->comm->client_id, clientKeyId); + if (WH_KEYID_ISHW(keyId)) { + /* Hardware key: used by reference, never read. CMAC accepts sign + * or verify usage */ + ret = _HwKeyCheckUse(ctx, devId, keyId, WH_NVM_FLAGS_USAGE_SIGN, + outHwKey); + if (ret == WH_ERROR_USAGE) { + ret = _HwKeyCheckUse(ctx, devId, keyId, + WH_NVM_FLAGS_USAGE_VERIFY, outHwKey); + } + *outKeyLen = 0; + return ret; + } /* Copy the key into the caller's private buffer, enforcing the usage * policy against the same locked snapshot that is read. CMAC accepts * sign or verify usage, so retry with verify on a usage failure; each @@ -4521,6 +4691,25 @@ static int _CmacResolveKey(whServerContext* ctx, const uint8_t* requestKey, return ret; } +/* Init cmac with key bytes, or with a hardware key by reference */ +static int _CmacInitForKey(Cmac* cmac, const uint8_t* key, uint32_t keyLen, + whKeyId hwKey, int devId) +{ +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO + if (hwKey != WH_KEYID_ERASED) { + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + + wh_HwKeystore_KeyRef(hwKey, ref); + return wc_InitCmac_Id(cmac, NULL, 0, WC_CMAC_AES, NULL, ref, + (int)sizeof(ref), NULL, devId); + } +#else + (void)hwKey; +#endif + return wc_InitCmac_ex(cmac, key, (word32)keyLen, WC_CMAC_AES, NULL, NULL, + devId); +} + static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, uint16_t seq, const void* cryptoDataIn, uint16_t inSize, void* cryptoDataOut, uint16_t* outSize) @@ -4568,9 +4757,11 @@ static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, uint8_t tmpKey[AES_256_KEY_SIZE]; uint32_t tmpKeyLen = sizeof(tmpKey); Cmac cmac[1]; + whKeyId hwKey = WH_KEYID_ERASED; /* Resolve the key to use */ - ret = _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, &tmpKeyLen); + ret = _CmacResolveKey(ctx, devId, key, req.keySz, req.keyId, tmpKey, + &tmpKeyLen, &hwKey); /* Oneshot: input and output are both present */ if (ret == 0 && req.inSz != 0 && req.outSz != 0) { @@ -4578,8 +4769,18 @@ static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_SERVER_VERBOSE("cmac generate oneshot\n"); - ret = wc_AesCmacGenerate_ex(cmac, out, &len, in, req.inSz, tmpKey, - (word32)tmpKeyLen, NULL, devId); + if (hwKey != WH_KEYID_ERASED) { + /* A hardware key is set up by reference first */ + ret = _CmacInitForKey(cmac, NULL, 0, hwKey, devId); + if (ret == 0) { + ret = wc_AesCmacGenerate_ex(cmac, out, &len, in, req.inSz, NULL, + 0, NULL, devId); + } + } + else { + ret = wc_AesCmacGenerate_ex(cmac, out, &len, in, req.inSz, tmpKey, + (word32)tmpKeyLen, NULL, devId); + } if (ret == 0) { res.outSz = len; @@ -4593,8 +4794,7 @@ static int _HandleCmac(whServerContext* ctx, uint16_t magic, int devId, req.inSz, req.outSz, req.keyId); /* Initialize CMAC context with key (re-derives k1/k2 subkeys) */ - ret = wc_InitCmac_ex(cmac, tmpKey, tmpKeyLen, WC_CMAC_AES, NULL, NULL, - devId); + ret = _CmacInitForKey(cmac, tmpKey, tmpKeyLen, hwKey, devId); WH_DEBUG_SERVER_VERBOSE("cmac init with keylen:%d ret:%d\n", tmpKeyLen, ret); @@ -9019,6 +9219,7 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, uint8_t tmpKey[AES_256_KEY_SIZE]; uint32_t tmpKeyLen = sizeof(tmpKey); Cmac cmac[1]; + whKeyId hwKey = WH_KEYID_ERASED; /* Oneshot fast path: DMA input only (no inline), output requested. The * streaming protocol never produces outSz>0 with DMA input (Final is @@ -9036,8 +9237,8 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, /* Resolve key */ if (ret == WH_ERROR_OK) { - ret = _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, - &tmpKeyLen); + ret = _CmacResolveKey(ctx, devId, key, req.keySz, req.keyId, tmpKey, + &tmpKeyLen, &hwKey); } if (ret == WH_ERROR_OK && req.keySz != 0) { @@ -9052,8 +9253,7 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, WH_DEBUG_SERVER_VERBOSE("dma cmac generate oneshot with keyId:%x\n", req.keyId); - ret = wc_InitCmac_ex(cmac, tmpKey, (word32)tmpKeyLen, WC_CMAC_AES, - NULL, NULL, devId); + ret = _CmacInitForKey(cmac, tmpKey, tmpKeyLen, hwKey, devId); if (ret == WH_ERROR_OK) { ret = wc_AesCmacGenerate_ex(cmac, out, &len, inAddr, @@ -9077,13 +9277,12 @@ static int _HandleCmacDma(whServerContext* ctx, uint16_t magic, int devId, (int)req.outSz, req.keyId); /* Resolve key */ - ret = - _CmacResolveKey(ctx, key, req.keySz, req.keyId, tmpKey, &tmpKeyLen); + ret = _CmacResolveKey(ctx, devId, key, req.keySz, req.keyId, tmpKey, + &tmpKeyLen, &hwKey); /* Initialize CMAC context with key (re-derives k1/k2 subkeys) */ if (ret == 0) { - ret = wc_InitCmac_ex(cmac, tmpKey, (word32)tmpKeyLen, WC_CMAC_AES, - NULL, NULL, devId); + ret = _CmacInitForKey(cmac, tmpKey, tmpKeyLen, hwKey, devId); WH_DEBUG_SERVER_VERBOSE("dma cmac init with keylen:%d ret:%d\n", tmpKeyLen, ret); } diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index e520469b2..94cc61881 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -125,6 +125,9 @@ typedef enum { /* Exporting only the public half of a public-key object is considered * non-sensitive and is intentionally not gated by NONEXPORTABLE. */ WH_KS_OP_EXPORT_PUBLIC, + /* Copying a key into the hardware keystore. The key never goes back to a + * client, so NONEXPORTABLE does not apply */ + WH_KS_OP_HW_LOAD, } whKsOp; static int _KeyIsCommitted(whServerContext* server, whKeyId keyId) @@ -247,6 +250,7 @@ static int _KeystoreCheckPolicy(whServerContext* server, whKsOp op, case WH_KS_OP_COMMIT: case WH_KS_OP_REVOKE: + case WH_KS_OP_HW_LOAD: /* Always allowed */ break; default: @@ -1294,8 +1298,24 @@ int wh_Server_KeystoreCommitKeyChecked(whServerContext* server, whNvmId keyId) return wh_Server_KeystoreCommitKey(server, keyId); } +/* An erased or revoked key must not stay usable through a hardware copy */ +static void _ClearHwCopies(whServerContext* server, whKeyId keyId) +{ +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO + if (server->hwKeystore != NULL) { + (void)wh_HwKeystore_ClearSource(server->hwKeystore, server->devId, + keyId); + } +#else + (void)server; + (void)keyId; +#endif +} + int wh_Server_KeystoreEraseKey(whServerContext* server, whNvmId keyId) { + int ret; + if ((server == NULL) || (WH_KEYID_IS_UNASSIGNED(keyId))) { return WH_ERROR_BADARGS; } @@ -1317,11 +1337,16 @@ int wh_Server_KeystoreEraseKey(whServerContext* server, whNvmId keyId) /* No NVM means there is nothing to destroy, same as erasing a key that * was never there. */ if (server->nvm == NULL) { + _ClearHwCopies(server, keyId); return WH_ERROR_OK; } /* destroy the object */ - return wh_Nvm_DestroyObjects(server->nvm, 1, &keyId); + ret = wh_Nvm_DestroyObjects(server->nvm, 1, &keyId); + if (ret == WH_ERROR_OK) { + _ClearHwCopies(server, keyId); + } + return ret; } int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId) @@ -1354,10 +1379,15 @@ int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId) /* Nothing left to destroy is a successful erase, matching * wh_Server_KeystoreEraseKey */ if (server->nvm == NULL) { + _ClearHwCopies(server, keyId); return WH_ERROR_OK; } - return wh_Nvm_DestroyObjectsChecked(server->nvm, 1, &keyId); + ret = wh_Nvm_DestroyObjectsChecked(server->nvm, 1, &keyId); + if (ret == WH_ERROR_OK) { + _ClearHwCopies(server, keyId); + } + return ret; } static void _revokeKey(whNvmMetadata* meta) @@ -1432,8 +1462,53 @@ int wh_Server_KeystoreRevokeKey(whServerContext* server, whNvmId keyId) _revokeKey(cacheMeta); } + if (ret == WH_ERROR_OK) { + _ClearHwCopies(server, keyId); + } + return ret; +} + +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +int wh_Server_KeystoreHwLoad(whServerContext* server, whKeyId* inout_hwId, + whKeyId srcId) +{ + uint8_t key[WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE]; + uint32_t keySz = sizeof(key); + whNvmMetadata meta[1]; + int ret; + + if ((server == NULL) || (inout_hwId == NULL) || + (WH_KEYID_TYPE(srcId) != WH_KEYTYPE_CRYPTO)) { + return WH_ERROR_BADARGS; + } + if (server->hwKeystore == NULL) { + return WH_ERROR_NOTFOUND; + } + + /* Read the source with the policy checks every client keystore operation + * gets, which refuse trusted keys */ + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + ret = _KeystoreCheckPolicy(server, WH_KS_OP_HW_LOAD, srcId); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreReadKey(server, srcId, meta, key, &keySz); + } + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + + /* The hardware copy keeps the usage of the source. A key with none (a + * revoked key, for example) could not be used */ + if ((ret == WH_ERROR_OK) && ((meta->flags & WH_NVM_FLAGS_USAGE_ANY) == 0)) { + ret = WH_ERROR_USAGE; + } + if (ret == WH_ERROR_OK) { + ret = wh_HwKeystore_Load(server->hwKeystore, server->devId, inout_hwId, + srcId, meta->flags, key, (uint16_t)keySz); + } + wh_Utils_ForceZero(key, sizeof(key)); return ret; } +#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ #ifdef WOLFHSM_CFG_KEYWRAP @@ -3645,6 +3720,129 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, #endif /* WOLFHSM_CFG_KEYWRAP */ +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO + case WH_KEY_HW_LOAD: { + whMessageKeystore_HwLoadRequest req = {0}; + whMessageKeystore_HwLoadResponse resp = {0}; + whKeyId hwId; + + if (req_size < sizeof(req)) { + ret = WH_ERROR_BADARGS; + } + + if (ret == WH_ERROR_OK) { + (void)wh_MessageKeystore_TranslateHwLoadRequest( + magic, (whMessageKeystore_HwLoadRequest*)req_packet, &req); + + hwId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_HW, server->comm->client_id, + req.hwId | WH_KEYID_CLIENT_HW_FLAG); + ret = wh_Server_KeystoreHwLoad( + server, &hwId, + wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, server->comm->client_id, req.srcId)); + if (ret == WH_ERROR_OK) { + resp.hwId = wh_KeyId_TranslateToClient(hwId); + } + } + resp.rc = ret; + + (void)wh_MessageKeystore_TranslateHwLoadResponse( + magic, &resp, (whMessageKeystore_HwLoadResponse*)resp_packet); + + *out_resp_size = sizeof(resp); + } break; + + case WH_KEY_HW_CLEAR: { + whMessageKeystore_HwClearRequest req = {0}; + whMessageKeystore_HwClearResponse resp = {0}; + whKeyId hwId; + + if (req_size < sizeof(req)) { + ret = WH_ERROR_BADARGS; + } + else if (server->hwKeystore == NULL) { + ret = WH_ERROR_NOTFOUND; + } + + if (ret == WH_ERROR_OK) { + (void)wh_MessageKeystore_TranslateHwClearRequest( + magic, (whMessageKeystore_HwClearRequest*)req_packet, &req); + + hwId = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_HW, server->comm->client_id, + req.hwId | WH_KEYID_CLIENT_HW_FLAG); + if (WH_KEYID_ISERASED(hwId)) { + /* ID 0 clears every key the client loaded */ + ret = wh_HwKeystore_ClearAll(server->hwKeystore, + server->devId, + server->comm->client_id); + } + else { + ret = wh_HwKeystore_Clear(server->hwKeystore, server->devId, + hwId); + } + } + resp.rc = ret; + + (void)wh_MessageKeystore_TranslateHwClearResponse( + magic, &resp, (whMessageKeystore_HwClearResponse*)resp_packet); + + *out_resp_size = sizeof(resp); + } break; + + case WH_KEY_HW_INFO: { + whMessageKeystore_HwInfoRequest req = {0}; + whMessageKeystore_HwInfoResponse resp = {0}; + whHwKeystoreInfo info = {0}; + + if (req_size < sizeof(req)) { + ret = WH_ERROR_BADARGS; + } + else if (server->hwKeystore == NULL) { + ret = WH_ERROR_NOTFOUND; + } + + if (ret == WH_ERROR_OK) { + (void)wh_MessageKeystore_TranslateHwInfoRequest( + magic, (whMessageKeystore_HwInfoRequest*)req_packet, &req); + + ret = wh_HwKeystore_GetInfo( + server->hwKeystore, server->devId, + wh_KeyId_TranslateFromClient( + WH_KEYTYPE_HW, server->comm->client_id, + req.hwId | WH_KEYID_CLIENT_HW_FLAG), + &info); + if (ret == WH_ERROR_OK) { + resp.keyType = info.keyType; + resp.keyBits = info.keyBits; + resp.flags = info.flags; + resp.owner = info.owner; + } + } + resp.rc = ret; + + (void)wh_MessageKeystore_TranslateHwInfoResponse( + magic, &resp, (whMessageKeystore_HwInfoResponse*)resp_packet); + + *out_resp_size = sizeof(resp); + } break; +#else + case WH_KEY_HW_LOAD: + case WH_KEY_HW_CLEAR: + case WH_KEY_HW_INFO: { + /* Not built in. rc comes first in all three responses, so the + * largest fits all */ + whMessageKeystore_HwInfoResponse resp = {0}; + + ret = WH_ERROR_NOTIMPL; + resp.rc = ret; + (void)wh_MessageKeystore_TranslateHwInfoResponse( + magic, &resp, (whMessageKeystore_HwInfoResponse*)resp_packet); + *out_resp_size = sizeof(resp); + } break; +#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ + default: ret = WH_ERROR_NOHANDLER; break; diff --git a/src/wh_server_she.c b/src/wh_server_she.c index 88b025c63..c5ebbb8ea 100644 --- a/src/wh_server_she.c +++ b/src/wh_server_she.c @@ -861,10 +861,12 @@ static int _LoadKey(whServerContext* server, uint16_t magic, uint16_t req_size, resp.messageFour[15] = ((_PopId(req.messageOne) << 4) | _PopAuthId(req.messageOne)); - /* Then encrypt counter into second half of messageFour */ - ret = wc_AesEncryptDirect(server->she->sheAes, - resp.messageFour + WH_SHE_KEY_SZ, - counter_buffer); + /* Then encrypt counter into second half of messageFour. One-block + * ECB, not AES-direct, which fails when a crypto callback owns the + * key */ + ret = wc_AesEcbEncrypt(server->she->sheAes, + resp.messageFour + WH_SHE_KEY_SZ, counter_buffer, + AES_BLOCK_SIZE); } /* free aes for protection */ wc_AesFree(server->she->sheAes); @@ -1075,10 +1077,10 @@ static int _ExportRamKey(whServerContext* server, uint16_t magic, memcpy(resp.messageFour + WH_SHE_KEY_SZ, &counter_val, sizeof(uint32_t)); resp.messageFour[WH_SHE_KEY_SZ + 3] |= 0x08; - /* encrypt the new counter */ - ret = wc_AesEncryptDirect(server->she->sheAes, - resp.messageFour + WH_SHE_KEY_SZ, - resp.messageFour + WH_SHE_KEY_SZ); + /* encrypt the new counter (one-block ECB, see above) */ + ret = wc_AesEcbEncrypt( + server->she->sheAes, resp.messageFour + WH_SHE_KEY_SZ, + resp.messageFour + WH_SHE_KEY_SZ, AES_BLOCK_SIZE); } /* free aes for protection */ wc_AesFree(server->she->sheAes); diff --git a/src/wh_she_crypto.c b/src/wh_she_crypto.c index 56cc2b4fd..5852f69cc 100644 --- a/src/wh_she_crypto.c +++ b/src/wh_she_crypto.c @@ -64,8 +64,8 @@ int wh_She_AesMp16_ex(Aes* aes, void* heap, int devid, ret = wc_AesInit(aes, heap, devid); /* do the first block with messageZero as the key */ if (ret == 0) { - ret = wc_AesSetKeyDirect(aes, messageZero, AES_BLOCK_SIZE, NULL, - AES_ENCRYPTION); + ret = wc_AesSetKey(aes, messageZero, AES_BLOCK_SIZE, NULL, + AES_ENCRYPTION); } while (ret == 0 && i < (int)inSz) { /* copy a block and pad it if we're short */ @@ -75,8 +75,9 @@ int wh_She_AesMp16_ex(Aes* aes, void* heap, int devid, } else memcpy(paddedInput, in + i, AES_BLOCK_SIZE); - /* encrypt this block */ - ret = wc_AesEncryptDirect(aes, out, paddedInput); + /* encrypt this block. One-block ECB, not AES-direct, which fails + * when a crypto callback owns the key */ + ret = wc_AesEcbEncrypt(aes, out, paddedInput, AES_BLOCK_SIZE); /* xor with the original message and then the previous block */ for (j = 0; j < (int)AES_BLOCK_SIZE; j++) { out[j] ^= paddedInput[j]; @@ -85,8 +86,7 @@ int wh_She_AesMp16_ex(Aes* aes, void* heap, int devid, } /* set the key for the next block */ if (ret == 0) { - ret = wc_AesSetKeyDirect(aes, out, AES_BLOCK_SIZE, - NULL, AES_ENCRYPTION); + ret = wc_AesSetKey(aes, out, AES_BLOCK_SIZE, NULL, AES_ENCRYPTION); } if (ret == 0) { /* store previous output in messageZero */ diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index f716566b6..7a2235205 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -70,6 +70,12 @@ whMessageKeystore_ExportResponse keyExportRes; whMessageKeystore_EraseResponse keyEraseRes; whMessageKeystore_ExportPublicRequest keyExportPublicReq; whMessageKeystore_ExportPublicResponse keyExportPublicRes; +whMessageKeystore_HwLoadRequest keyHwLoadReq; +whMessageKeystore_HwLoadResponse keyHwLoadRes; +whMessageKeystore_HwClearRequest keyHwClearReq; +whMessageKeystore_HwClearResponse keyHwClearRes; +whMessageKeystore_HwInfoRequest keyHwInfoReq; +whMessageKeystore_HwInfoResponse keyHwInfoRes; /* Include counter message header for new counter message structures */ #include "wolfhsm/wh_message_counter.h" diff --git a/wolfhsm/wh_client.h b/wolfhsm/wh_client.h index 69a507451..c9e1d33bf 100644 --- a/wolfhsm/wh_client.h +++ b/wolfhsm/wh_client.h @@ -1044,6 +1044,104 @@ int wh_Client_KeyRevokeResponse(whClientContext* c); */ int wh_Client_KeyRevoke(whClientContext* c, whKeyId keyId); +/** + * @brief Sends a request to copy a keystore key into a hardware key. + * + * The hardware key keeps the source key's usage flags and can then be used in + * AES and CMAC operations through wh_Client_AesSetKeyId() and + * wh_Client_CmacSetKeyId(). Which hardware keyIds exist and what they can hold + * depends on the port. The key belongs to this client unless hwKeyId has the + * global flag. Erasing or revoking a key clears every hardware key loaded from + * that key ID; evicting it does not. Hardware keys are lost when the HSM + * resets. + * Requires a server built with WOLFHSM_CFG_HWKEYSTORE_CRYPTO. + * + * @param[in] c Pointer to the client context. + * @param[in] hwKeyId Hardware key to load, from WH_CLIENT_KEYID_MAKE_HW(). An + * ID of 0 lets the server pick a free one. + * @param[in] srcKeyId Key ID of the keystore key to copy. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_HwKeystoreLoadFromKeystoreRequest(whClientContext* c, + whKeyId hwKeyId, + whKeyId srcKeyId); + +/** + * @brief Receives the response to a hardware key load request. + * + * @param[in] c Pointer to the client context. + * @param[out] outHwKeyId Optional. Hardware key that was loaded, ready for + * wh_Client_AesSetKeyId(). + * @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_HwKeystoreLoadFromKeystoreResponse(whClientContext* c, + whKeyId* outHwKeyId); + +/** + * @brief Copies a keystore key into a hardware key, blocking until done. + * + * See wh_Client_HwKeystoreLoadFromKeystoreRequest(). + */ +int wh_Client_HwKeystoreLoadFromKeystore(whClientContext* c, whKeyId hwKeyId, + whKeyId srcKeyId, whKeyId* outHwKeyId); + +/** + * @brief Sends a request to clear a hardware key this client loaded. + * + * @param[in] c Pointer to the client context. + * @param[in] hwKeyId Hardware key to clear. The ID must not be 0. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_HwKeystoreClearRequest(whClientContext* c, whKeyId hwKeyId); + +/** + * @brief Receives the response to a hardware key clear request. + * + * @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_HwKeystoreClearResponse(whClientContext* c); + +/** + * @brief Clears a hardware key this client loaded, blocking until done. + */ +int wh_Client_HwKeystoreClear(whClientContext* c, whKeyId hwKeyId); + +/** + * @brief Clears every hardware key this client loaded, blocking until done. + * + * Keys loaded as global and keys the port loaded are not cleared. + */ +int wh_Client_HwKeystoreClearAll(whClientContext* c); + +/** + * @brief Sends a request to describe a hardware key. + * + * @param[in] c Pointer to the client context. + * @param[in] hwKeyId Hardware key to describe. + * @return int Returns 0 on success, or a negative error code on failure. + */ +int wh_Client_HwKeystoreGetInfoRequest(whClientContext* c, whKeyId hwKeyId); + +/** + * @brief Receives the response to a hardware key info request. + * + * @param[in] c Pointer to the client context. + * @param[out] info What the key holds. keyType is 0 if it is empty. + * @return int Returns 0 on success, WH_ERROR_NOTREADY if no response is + * available yet, or a negative error code on failure (such as a + * hardware keyId the port does not have). + */ +int wh_Client_HwKeystoreGetInfoResponse(whClientContext* c, + whHwKeystoreInfo* info); + +/** + * @brief Describes a hardware key, blocking until done. + */ +int wh_Client_HwKeystoreGetInfo(whClientContext* c, whKeyId hwKeyId, + whHwKeystoreInfo* info); + #ifdef WOLFHSM_CFG_DMA /** diff --git a/wolfhsm/wh_common.h b/wolfhsm/wh_common.h index e3cb98f21..b7c65c580 100644 --- a/wolfhsm/wh_common.h +++ b/wolfhsm/wh_common.h @@ -134,6 +134,14 @@ typedef struct { uint8_t label[WH_NVM_LABEL_LEN]; } whNvmMetadata; +/* What a hardware key holds, as reported by the server */ +typedef struct { + uint32_t keyType; /* enum wc_KeyStoreKeyType from the port, 0 if empty */ + uint32_t keyBits; /* Key size in bits */ + whNvmFlags flags; /* WH_NVM_FLAGS_USAGE_* the server allows */ + uint16_t owner; /* Client that loaded it, 0 if shared */ +} whHwKeystoreInfo; + /* Certificate management flags */ typedef uint16_t whCertFlags; #define WH_CERT_FLAGS_NONE ((whCertFlags)0) diff --git a/wolfhsm/wh_hwkeystore.h b/wolfhsm/wh_hwkeystore.h index 273d71f95..c65c68128 100644 --- a/wolfhsm/wh_hwkeystore.h +++ b/wolfhsm/wh_hwkeystore.h @@ -32,6 +32,15 @@ * (stack) buffer, uses it, and zeroizes it. Hardware-only keys are only * usable as KEKs in the keywrap API. * + * With WOLFHSM_CFG_HWKEYSTORE_CRYPTO, clients can also load keys into the + * hardware keystore and use hardware keys in AES and CMAC requests. The + * hardware keeps the material: the server passes the key to wolfCrypt by + * reference and the port's crypto callback runs the operation. The port + * implements the WC_ALGO_TYPE_KEYSTORE callback to load, clear and describe + * keys, and decides which hardware keyIds exist and what they can hold. The + * front end records the owner and allowed usage of each key, and the server + * checks them on every request. + * * A backend provides a whHwKeystoreCb table (GetKey required; Init and Cleanup * optional) plus optional opaque context/config pointers for backend-specific * state. A whHwKeystoreContext is owned by the application, initialized once @@ -52,6 +61,7 @@ #include #include "wolfhsm/wh_keyid.h" +#include "wolfhsm/wh_common.h" #ifdef WOLFHSM_CFG_THREADSAFE #include "wolfhsm/wh_lock.h" @@ -114,12 +124,29 @@ typedef struct whHwKeystoreCb_t { whHwKeystoreGetKeyCb GetKey; /* Fetch key material (required) */ } whHwKeystoreCb; +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +/* Size of the key reference wolfCrypt passes to the port for a hardware key: + * the server keyId, big-endian */ +#define WH_HWKEYSTORE_REF_SIZE 2 + +/* A hardware key the server allows in crypto requests */ +typedef struct whHwKeystoreKey_t { + whKeyId id; /* TYPE=HW, USER=owner (0 if shared), or ERASED if free */ + whKeyId srcId; /* Keystore key it was loaded from, or ERASED */ + whNvmFlags flags; /* WH_NVM_FLAGS_USAGE_*. NONDESTROYABLE marks port keys */ + uint16_t len; /* Key size in bytes */ +} whHwKeystoreKey; +#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ + /* Context structure associated with a hardware keystore instance */ typedef struct whHwKeystoreContext_t { const whHwKeystoreCb* cb; /* Backend callback table */ void* context; /* Opaque backend context */ #ifdef WOLFHSM_CFG_THREADSAFE whLock lock; /* Lock serializing callback invocations */ +#endif +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO + whHwKeystoreKey keys[WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS]; #endif int initialized; /* 1 if initialized, 0 otherwise */ uint8_t WH_PAD[4]; @@ -180,6 +207,109 @@ int wh_HwKeystore_Cleanup(whHwKeystoreContext* context); int wh_HwKeystore_GetKey(whHwKeystoreContext* context, whKeyId keyId, uint8_t* out, uint16_t* inout_len); +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +/* + * Hardware keys for crypto requests. keyId arguments are server keyIds with + * TYPE=HW: the ID field names the hardware key and the USER field is the + * client making the call (0 for a shared key). A key loaded by a client can + * be used and cleared only by that client, unless it was loaded as shared. + * Keys the port registers are shared and clients cannot clear them. devId is + * the wolfCrypt device whose crypto callback implements WC_ALGO_TYPE_KEYSTORE. + */ + +/** + * @brief Write the wolfCrypt key reference for a hardware key. + * + * @param[in] keyId Server keyId of the hardware key + * @param[out] ref Buffer of WH_HWKEYSTORE_REF_SIZE bytes + */ +void wh_HwKeystore_KeyRef(whKeyId keyId, uint8_t* ref); + +/** + * @brief Record a key the port loaded itself, for example at boot. + * + * The key is shared by all clients and clients cannot clear it. + * + * @param[in] context Pointer to an initialized hardware keystore context + * @param[in] keyId Server keyId of the hardware key + * @param[in] flags WH_NVM_FLAGS_USAGE_* the key allows + * @param[in] len Key size in bytes + * @return int WH_ERROR_OK on success, WH_ERROR_ACCESS if the key is already + * in use, WH_ERROR_NOSPACE if the table is full + */ +int wh_HwKeystore_Register(whHwKeystoreContext* context, whKeyId keyId, + whNvmFlags flags, uint16_t len); + +/** + * @brief Load key material into a hardware key through the port. + * + * The owner can load again to replace its key. If the ID field of + * *inout_keyId is 0, the first free hardware key the port accepts is used + * and returned in *inout_keyId. + * + * @param[in] context Pointer to an initialized hardware keystore context + * @param[in] devId wolfCrypt device that implements the keystore callback + * @param[in,out] inout_keyId Hardware key to load, and the key loaded + * @param[in] srcId Keystore key the material came from, or WH_KEYID_ERASED + * @param[in] flags WH_NVM_FLAGS_USAGE_* the key allows + * @param[in] key Key material + * @param[in] keySz Key size in bytes + * @return int WH_ERROR_OK on success, WH_ERROR_ACCESS if another client owns + * the key, WH_ERROR_NOSPACE if no key is free, or the port's error + */ +int wh_HwKeystore_Load(whHwKeystoreContext* context, int devId, + whKeyId* inout_keyId, whKeyId srcId, whNvmFlags flags, + const uint8_t* key, uint16_t keySz); + +/** + * @brief Clear a hardware key the caller owns. + * + * @return int WH_ERROR_OK on success, WH_ERROR_NOTFOUND if the key is not + * loaded, WH_ERROR_ACCESS if the caller does not own it + */ +int wh_HwKeystore_Clear(whHwKeystoreContext* context, int devId, whKeyId keyId); + +/** + * @brief Clear every hardware key owned by a client. + * + * @param[in] user Client ID. Port keys are never cleared + */ +int wh_HwKeystore_ClearAll(whHwKeystoreContext* context, int devId, + uint16_t user); + +/** + * @brief Clear every hardware key loaded from a keystore key, for example + * when that key is erased or revoked. + */ +int wh_HwKeystore_ClearSource(whHwKeystoreContext* context, int devId, + whKeyId srcId); + +/** + * @brief Check that the caller may use a hardware key. + * + * @param[in] keyId Hardware key, with the caller in the USER field + * @param[in] usage WH_NVM_FLAGS_USAGE_* the operation needs (all of them) + * @param[out] outKeyId Recorded keyId, for wh_HwKeystore_KeyRef() + * @return int WH_ERROR_OK on success, WH_ERROR_NOTFOUND if the key is not + * loaded, WH_ERROR_ACCESS if another client owns it, WH_ERROR_USAGE + * if it does not allow the usage + */ +int wh_HwKeystore_CheckUse(whHwKeystoreContext* context, whKeyId keyId, + whNvmFlags usage, whKeyId* outKeyId); + +/** + * @brief Describe a hardware key. + * + * Reports what the port says the key holds, plus its owner and usage when + * the server tracks it. An empty key reports keyType 0. + * + * @return int WH_ERROR_OK on success, or the port's error for a keyId it + * does not have + */ +int wh_HwKeystore_GetInfo(whHwKeystoreContext* context, int devId, + whKeyId keyId, whHwKeystoreInfo* info); +#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ + #endif /* WOLFHSM_CFG_HWKEYSTORE */ #endif /* !WOLFHSM_WH_HWKEYSTORE_H_ */ diff --git a/wolfhsm/wh_message.h b/wolfhsm/wh_message.h index 9518110f6..a6dd477cf 100644 --- a/wolfhsm/wh_message.h +++ b/wolfhsm/wh_message.h @@ -74,6 +74,9 @@ enum WH_KEY_ENUM { WH_KEY_EXPORT_PUBLIC_DMA, WH_KEY_CACHE_RANDOM, WH_KEY_KEYWRAPEXPORT, + WH_KEY_HW_LOAD, + WH_KEY_HW_CLEAR, + WH_KEY_HW_INFO, }; /* SHE actions */ diff --git a/wolfhsm/wh_message_keystore.h b/wolfhsm/wh_message_keystore.h index 1283a3a29..932aba5f5 100644 --- a/wolfhsm/wh_message_keystore.h +++ b/wolfhsm/wh_message_keystore.h @@ -235,6 +235,71 @@ int wh_MessageKeystore_TranslateRevokeResponse( uint16_t magic, const whMessageKeystore_RevokeResponse* src, whMessageKeystore_RevokeResponse* dest); +/* Hardware key load request: copy a keystore key into a hardware key */ +typedef struct { + uint16_t hwId; /* Client HW keyId. ID 0 lets the server pick one */ + uint16_t srcId; /* Client keyId of the keystore key */ + uint8_t WH_PAD[4]; +} whMessageKeystore_HwLoadRequest; + +/* Hardware key load response */ +typedef struct { + uint32_t rc; + uint16_t hwId; /* Client HW keyId that was loaded */ + uint8_t WH_PAD[2]; +} whMessageKeystore_HwLoadResponse; + +int wh_MessageKeystore_TranslateHwLoadRequest( + uint16_t magic, const whMessageKeystore_HwLoadRequest* src, + whMessageKeystore_HwLoadRequest* dest); + +int wh_MessageKeystore_TranslateHwLoadResponse( + uint16_t magic, const whMessageKeystore_HwLoadResponse* src, + whMessageKeystore_HwLoadResponse* dest); + +/* Hardware key clear request */ +typedef struct { + uint16_t hwId; /* Client HW keyId. ID 0 clears all of the client's keys */ + uint8_t WH_PAD[6]; +} whMessageKeystore_HwClearRequest; + +/* Hardware key clear response */ +typedef struct { + uint32_t rc; + uint8_t WH_PAD[4]; +} whMessageKeystore_HwClearResponse; + +int wh_MessageKeystore_TranslateHwClearRequest( + uint16_t magic, const whMessageKeystore_HwClearRequest* src, + whMessageKeystore_HwClearRequest* dest); + +int wh_MessageKeystore_TranslateHwClearResponse( + uint16_t magic, const whMessageKeystore_HwClearResponse* src, + whMessageKeystore_HwClearResponse* dest); + +/* Hardware key info request */ +typedef struct { + uint16_t hwId; /* Client HW keyId */ + uint8_t WH_PAD[6]; +} whMessageKeystore_HwInfoRequest; + +/* Hardware key info response */ +typedef struct { + uint32_t rc; + uint32_t keyType; /* enum wc_KeyStoreKeyType from the port, 0 if empty */ + uint32_t keyBits; + uint16_t flags; /* WH_NVM_FLAGS_USAGE_* the server allows */ + uint16_t owner; /* Client that loaded it, 0 if shared */ +} whMessageKeystore_HwInfoResponse; + +int wh_MessageKeystore_TranslateHwInfoRequest( + uint16_t magic, const whMessageKeystore_HwInfoRequest* src, + whMessageKeystore_HwInfoRequest* dest); + +int wh_MessageKeystore_TranslateHwInfoResponse( + uint16_t magic, const whMessageKeystore_HwInfoResponse* src, + whMessageKeystore_HwInfoResponse* dest); + /* * DMA-based keystore operations */ diff --git a/wolfhsm/wh_server_keystore.h b/wolfhsm/wh_server_keystore.h index 89f153c13..e7352d8c6 100644 --- a/wolfhsm/wh_server_keystore.h +++ b/wolfhsm/wh_server_keystore.h @@ -232,6 +232,26 @@ int wh_Server_KeystoreEraseKeyChecked(whServerContext* server, whNvmId keyId); */ int wh_Server_KeystoreRevokeKey(whServerContext* server, whKeyId keyId); +#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +/** + * @brief Copy a keystore key into a hardware key + * + * Reads the source key with the usual keystore policy checks (trusted keys + * are refused) and loads it through the hardware keystore. The hardware key + * keeps the usage flags of the source. Erasing or revoking the source later + * clears the hardware copy. + * + * @param[in] server Server context + * @param[in,out] inout_hwId Hardware key to load, with the owner in the USER + * field. An ID field of 0 picks a free key and returns it + * @param[in] srcId Keystore key to copy (TYPE=CRYPTO) + * @return WH_ERROR_OK on success, WH_ERROR_USAGE if the source has no usage + * flags, or an error from the keystore or the hardware keystore + */ +int wh_Server_KeystoreHwLoad(whServerContext* server, whKeyId* inout_hwId, + whKeyId srcId); +#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ + /** * @brief Handle key management requests from clients * diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 7e4d16b7e..789087929 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -86,6 +86,18 @@ * served by a hardware keystore backend * Default: 32 * + * WOLFHSM_CFG_HWKEYSTORE_CRYPTO - If defined, clients can load keys into + * the hardware keystore and use hardware keys in AES and CMAC requests. The + * server passes a hardware key to wolfCrypt by reference, so the crypto + * callback must handle every operation on it. Requires + * WOLFHSM_CFG_HWKEYSTORE, and WOLF_CRYPTO_CB_KEYSTORE and WOLF_PRIVATE_KEY_ID + * in wolfCrypt + * Default: Not defined + * + * WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS - The number of hardware keys the server + * tracks for crypto use + * Default: 8 + * * WOLFHSM_CFG_HEXDUMP - If defined, include wh_Utils_HexDump functionality * using stdio.h * Default: Not defined @@ -615,6 +627,12 @@ #error "WOLFSSL_VALIDATE_ECC_IMPORT is not supported in wolfHSM client builds" #endif +#if defined(WOLFHSM_CFG_HWKEYSTORE_CRYPTO) && \ + (!defined(WOLF_CRYPTO_CB_KEYSTORE) || !defined(WOLF_PRIVATE_KEY_ID)) +#error \ + "WOLFHSM_CFG_HWKEYSTORE_CRYPTO requires WOLF_CRYPTO_CB_KEYSTORE and WOLF_PRIVATE_KEY_ID" +#endif + #if defined(WOLFHSM_CFG_SHE_EXTENSION) #if defined(NO_AES) || \ !defined(WOLFSSL_CMAC) || \ @@ -667,6 +685,19 @@ #endif #endif /* WOLFHSM_CFG_HWKEYSTORE */ +#if defined(WOLFHSM_CFG_HWKEYSTORE_CRYPTO) +#if !defined(WOLFHSM_CFG_HWKEYSTORE) +#error "WOLFHSM_CFG_HWKEYSTORE_CRYPTO requires WOLFHSM_CFG_HWKEYSTORE" +#endif +#if defined(WOLFHSM_CFG_NO_CRYPTO) +#error \ + "WOLFHSM_CFG_HWKEYSTORE_CRYPTO is incompatible with WOLFHSM_CFG_NO_CRYPTO" +#endif +#ifndef WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS +#define WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS 8 +#endif +#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ + /* Trusted cert verify cache requires the certificate manager and crypto. * Enforce here so downstream code can gate on * WOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE alone instead of repeating the full From 9ce8968f5442cb5f23e08ea72121b47fa56988a5 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 13 Oct 2026 12:56:09 -0600 Subject: [PATCH 02/23] concurrency fix: relocate load to within lock --- src/wh_server_keystore.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index 94cc61881..53baf3154 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -1486,25 +1486,28 @@ int wh_Server_KeystoreHwLoad(whServerContext* server, whKeyId* inout_hwId, } /* Read the source with the policy checks every client keystore operation - * gets, which refuse trusted keys */ + * gets, which refuse trusted keys. Hold the NVM lock until the copy is + * loaded, so erasing or revoking the source can't miss it */ ret = WH_SERVER_NVM_LOCK(server); if (ret == WH_ERROR_OK) { ret = _KeystoreCheckPolicy(server, WH_KS_OP_HW_LOAD, srcId); if (ret == WH_ERROR_OK) { ret = wh_Server_KeystoreReadKey(server, srcId, meta, key, &keySz); } + + /* The hardware copy keeps the usage of the source. A key with none (a + * revoked key, for example) could not be used */ + if ((ret == WH_ERROR_OK) && + ((meta->flags & WH_NVM_FLAGS_USAGE_ANY) == 0)) { + ret = WH_ERROR_USAGE; + } + if (ret == WH_ERROR_OK) { + ret = wh_HwKeystore_Load(server->hwKeystore, server->devId, + inout_hwId, srcId, meta->flags, key, + (uint16_t)keySz); + } (void)WH_SERVER_NVM_UNLOCK(server); } /* WH_SERVER_NVM_LOCK() */ - - /* The hardware copy keeps the usage of the source. A key with none (a - * revoked key, for example) could not be used */ - if ((ret == WH_ERROR_OK) && ((meta->flags & WH_NVM_FLAGS_USAGE_ANY) == 0)) { - ret = WH_ERROR_USAGE; - } - if (ret == WH_ERROR_OK) { - ret = wh_HwKeystore_Load(server->hwKeystore, server->devId, inout_hwId, - srcId, meta->flags, key, (uint16_t)keySz); - } wh_Utils_ForceZero(key, sizeof(key)); return ret; } From f994b48791c0950218108854f3815ce4b4707f3e Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:11:55 -0600 Subject: [PATCH 03/23] preserve user field in HW loadKey --- src/wh_hwkeystore.c | 3 +-- wolfhsm/wh_common.h | 2 +- wolfhsm/wh_hwkeystore.h | 10 ++++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/wh_hwkeystore.c b/src/wh_hwkeystore.c index 5557270bc..c29de9b44 100644 --- a/src/wh_hwkeystore.c +++ b/src/wh_hwkeystore.c @@ -266,8 +266,7 @@ int wh_HwKeystore_Register(whHwKeystoreContext* context, whKeyId keyId, ret = WH_ERROR_NOSPACE; } else { - entry->id = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, - WH_KEYID_ID(keyId)); + entry->id = keyId; entry->srcId = WH_KEYID_ERASED; entry->flags = (flags & WH_NVM_FLAGS_USAGE_ANY) | WH_NVM_FLAGS_NONDESTROYABLE; diff --git a/wolfhsm/wh_common.h b/wolfhsm/wh_common.h index b7c65c580..f02fafd8b 100644 --- a/wolfhsm/wh_common.h +++ b/wolfhsm/wh_common.h @@ -139,7 +139,7 @@ typedef struct { uint32_t keyType; /* enum wc_KeyStoreKeyType from the port, 0 if empty */ uint32_t keyBits; /* Key size in bits */ whNvmFlags flags; /* WH_NVM_FLAGS_USAGE_* the server allows */ - uint16_t owner; /* Client that loaded it, 0 if shared */ + uint16_t owner; /* Client that owns it, 0 if shared */ } whHwKeystoreInfo; /* Certificate management flags */ diff --git a/wolfhsm/wh_hwkeystore.h b/wolfhsm/wh_hwkeystore.h index c65c68128..c7ab416ff 100644 --- a/wolfhsm/wh_hwkeystore.h +++ b/wolfhsm/wh_hwkeystore.h @@ -213,8 +213,9 @@ int wh_HwKeystore_GetKey(whHwKeystoreContext* context, whKeyId keyId, * TYPE=HW: the ID field names the hardware key and the USER field is the * client making the call (0 for a shared key). A key loaded by a client can * be used and cleared only by that client, unless it was loaded as shared. - * Keys the port registers are shared and clients cannot clear them. devId is - * the wolfCrypt device whose crypto callback implements WC_ALGO_TYPE_KEYSTORE. + * A key the port registers is shared if its USER field is 0, otherwise only + * that client can use it, and clients cannot clear it. devId is the wolfCrypt + * device whose crypto callback implements WC_ALGO_TYPE_KEYSTORE. */ /** @@ -228,10 +229,11 @@ void wh_HwKeystore_KeyRef(whKeyId keyId, uint8_t* ref); /** * @brief Record a key the port loaded itself, for example at boot. * - * The key is shared by all clients and clients cannot clear it. + * Clients cannot clear or replace the key. * * @param[in] context Pointer to an initialized hardware keystore context - * @param[in] keyId Server keyId of the hardware key + * @param[in] keyId Server keyId of the hardware key. USER is the client that + * may use it, or 0 to share it with all clients * @param[in] flags WH_NVM_FLAGS_USAGE_* the key allows * @param[in] len Key size in bytes * @return int WH_ERROR_OK on success, WH_ERROR_ACCESS if the key is already From 496303f257e39daef7c762abaeceac7aa8040fcb Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 13 Oct 2026 13:12:17 -0600 Subject: [PATCH 04/23] fix mutex locking formatting --- src/wh_hwkeystore.c | 259 +++++++++++++++++++++----------------------- 1 file changed, 126 insertions(+), 133 deletions(-) diff --git a/src/wh_hwkeystore.c b/src/wh_hwkeystore.c index c29de9b44..d334ae89d 100644 --- a/src/wh_hwkeystore.c +++ b/src/wh_hwkeystore.c @@ -161,12 +161,13 @@ static int _Lock(whHwKeystoreContext* context) #endif } -static void _Unlock(whHwKeystoreContext* context) +static int _Unlock(whHwKeystoreContext* context) { #ifdef WOLFHSM_CFG_THREADSAFE - (void)wh_Lock_Release(&context->lock); + return wh_Lock_Release(&context->lock); #else (void)context; + return WH_ERROR_OK; #endif } @@ -254,26 +255,25 @@ int wh_HwKeystore_Register(whHwKeystoreContext* context, whKeyId keyId, } ret = _Lock(context); - if (ret != WH_ERROR_OK) { - return ret; - } - if (_Find(context, keyId) != NULL) { - ret = WH_ERROR_ACCESS; - } - else { - entry = _FindFree(context); - if (entry == NULL) { - ret = WH_ERROR_NOSPACE; + if (ret == WH_ERROR_OK) { + if (_Find(context, keyId) != NULL) { + ret = WH_ERROR_ACCESS; } else { - entry->id = keyId; - entry->srcId = WH_KEYID_ERASED; - entry->flags = - (flags & WH_NVM_FLAGS_USAGE_ANY) | WH_NVM_FLAGS_NONDESTROYABLE; - entry->len = len; + entry = _FindFree(context); + if (entry == NULL) { + ret = WH_ERROR_NOSPACE; + } + else { + entry->id = keyId; + entry->srcId = WH_KEYID_ERASED; + entry->flags = (flags & WH_NVM_FLAGS_USAGE_ANY) | + WH_NVM_FLAGS_NONDESTROYABLE; + entry->len = len; + } } - } - _Unlock(context); + (void)_Unlock(context); + } /* _Lock() */ return ret; } @@ -297,56 +297,54 @@ int wh_HwKeystore_Load(whHwKeystoreContext* context, int devId, keyId = *inout_keyId; ret = _Lock(context); - if (ret != WH_ERROR_OK) { - return ret; - } - - if (WH_KEYID_ISERASED(keyId)) { - /* Try each hardware key the port reports empty, in ID order */ - entry = _FindFree(context); - ret = WH_ERROR_NOSPACE; - for (id = 1; (entry != NULL) && (id <= WH_KEYID_IDMAX); id++) { - cand = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYID_USER(keyId), id); - if ((_Find(context, cand) != NULL) || - (_PortInfo(devId, cand, &keyType, &keyBits) != 0) || - (keyType != WC_KEYSTORE_KEY_NONE)) { - continue; - } - ret = _PortImport(devId, cand, key, keySz); - if (ret == 0) { - keyId = cand; - break; - } - } - } - else { - entry = _Find(context, keyId); - if (entry != NULL) { - /* Only the owner may replace a key, and never a port key */ - if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || - (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { - ret = WH_ERROR_ACCESS; + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(keyId)) { + /* Try each hardware key the port reports empty, in ID order */ + entry = _FindFree(context); + ret = WH_ERROR_NOSPACE; + for (id = 1; (entry != NULL) && (id <= WH_KEYID_IDMAX); id++) { + cand = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYID_USER(keyId), id); + if ((_Find(context, cand) != NULL) || + (_PortInfo(devId, cand, &keyType, &keyBits) != 0) || + (keyType != WC_KEYSTORE_KEY_NONE)) { + continue; + } + ret = _PortImport(devId, cand, key, keySz); + if (ret == 0) { + keyId = cand; + break; + } } } else { - entry = _FindFree(context); - if (entry == NULL) { - ret = WH_ERROR_NOSPACE; + entry = _Find(context, keyId); + if (entry != NULL) { + /* Only the owner may replace a key, and never a port key */ + if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + } + else { + entry = _FindFree(context); + if (entry == NULL) { + ret = WH_ERROR_NOSPACE; + } + } + if (ret == WH_ERROR_OK) { + ret = _PortImport(devId, keyId, key, keySz); } } - if (ret == WH_ERROR_OK) { - ret = _PortImport(devId, keyId, key, keySz); - } - } - if (ret == 0) { - entry->id = keyId; - entry->srcId = srcId; - entry->flags = flags & WH_NVM_FLAGS_USAGE_ANY; - entry->len = keySz; - *inout_keyId = keyId; - } - _Unlock(context); + if (ret == 0) { + entry->id = keyId; + entry->srcId = srcId; + entry->flags = flags & WH_NVM_FLAGS_USAGE_ANY; + entry->len = keySz; + *inout_keyId = keyId; + } + (void)_Unlock(context); + } /* _Lock() */ return ret; } @@ -360,21 +358,20 @@ int wh_HwKeystore_Clear(whHwKeystoreContext* context, int devId, whKeyId keyId) } ret = _Lock(context); - if (ret != WH_ERROR_OK) { - return ret; - } - entry = _Find(context, keyId); - if (entry == NULL) { - ret = WH_ERROR_NOTFOUND; - } - else if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || - (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { - ret = WH_ERROR_ACCESS; - } - else { - ret = _Remove(devId, entry); - } - _Unlock(context); + if (ret == WH_ERROR_OK) { + entry = _Find(context, keyId); + if (entry == NULL) { + ret = WH_ERROR_NOTFOUND; + } + else if (((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) != 0) || + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + else { + ret = _Remove(devId, entry); + } + (void)_Unlock(context); + } /* _Lock() */ return ret; } @@ -391,21 +388,20 @@ int wh_HwKeystore_ClearAll(whHwKeystoreContext* context, int devId, } ret = _Lock(context); - if (ret != WH_ERROR_OK) { - return ret; - } - for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { - entry = &context->keys[i]; - if ((entry->id != WH_KEYID_ERASED) && - ((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) == 0) && - (WH_KEYID_USER(entry->id) == user)) { - rc = _Remove(devId, entry); - if (ret == WH_ERROR_OK) { - ret = rc; + if (ret == WH_ERROR_OK) { + for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { + entry = &context->keys[i]; + if ((entry->id != WH_KEYID_ERASED) && + ((entry->flags & WH_NVM_FLAGS_NONDESTROYABLE) == 0) && + (WH_KEYID_USER(entry->id) == user)) { + rc = _Remove(devId, entry); + if (ret == WH_ERROR_OK) { + ret = rc; + } } } - } - _Unlock(context); + (void)_Unlock(context); + } /* _Lock() */ return ret; } @@ -423,19 +419,18 @@ int wh_HwKeystore_ClearSource(whHwKeystoreContext* context, int devId, } ret = _Lock(context); - if (ret != WH_ERROR_OK) { - return ret; - } - for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { - entry = &context->keys[i]; - if ((entry->id != WH_KEYID_ERASED) && (entry->srcId == srcId)) { - rc = _Remove(devId, entry); - if (ret == WH_ERROR_OK) { - ret = rc; + if (ret == WH_ERROR_OK) { + for (i = 0; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { + entry = &context->keys[i]; + if ((entry->id != WH_KEYID_ERASED) && (entry->srcId == srcId)) { + rc = _Remove(devId, entry); + if (ret == WH_ERROR_OK) { + ret = rc; + } } } - } - _Unlock(context); + (void)_Unlock(context); + } /* _Lock() */ return ret; } @@ -451,24 +446,23 @@ int wh_HwKeystore_CheckUse(whHwKeystoreContext* context, whKeyId keyId, usage &= WH_NVM_FLAGS_USAGE_ANY; ret = _Lock(context); - if (ret != WH_ERROR_OK) { - return ret; - } - entry = _Find(context, keyId); - if (entry == NULL) { - ret = WH_ERROR_NOTFOUND; - } - else if ((WH_KEYID_USER(entry->id) != WH_KEYUSER_GLOBAL) && - (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { - ret = WH_ERROR_ACCESS; - } - else if ((usage == 0) || ((entry->flags & usage) != usage)) { - ret = WH_ERROR_USAGE; - } - else { - *outKeyId = entry->id; - } - _Unlock(context); + if (ret == WH_ERROR_OK) { + entry = _Find(context, keyId); + if (entry == NULL) { + ret = WH_ERROR_NOTFOUND; + } + else if ((WH_KEYID_USER(entry->id) != WH_KEYUSER_GLOBAL) && + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + else if ((usage == 0) || ((entry->flags & usage) != usage)) { + ret = WH_ERROR_USAGE; + } + else { + *outKeyId = entry->id; + } + (void)_Unlock(context); + } /* _Lock() */ return ret; } @@ -486,20 +480,19 @@ int wh_HwKeystore_GetInfo(whHwKeystoreContext* context, int devId, memset(info, 0, sizeof(*info)); ret = _Lock(context); - if (ret != WH_ERROR_OK) { - return ret; - } - ret = _PortInfo(devId, keyId, &keyType, &keyBits); - if (ret == 0) { - info->keyType = keyType; - info->keyBits = keyBits; - entry = _Find(context, keyId); - if (entry != NULL) { - info->flags = entry->flags & WH_NVM_FLAGS_USAGE_ANY; - info->owner = WH_KEYID_USER(entry->id); + if (ret == WH_ERROR_OK) { + ret = _PortInfo(devId, keyId, &keyType, &keyBits); + if (ret == 0) { + info->keyType = keyType; + info->keyBits = keyBits; + entry = _Find(context, keyId); + if (entry != NULL) { + info->flags = entry->flags & WH_NVM_FLAGS_USAGE_ANY; + info->owner = WH_KEYID_USER(entry->id); + } } - } - _Unlock(context); + (void)_Unlock(context); + } /* _Lock() */ return ret; } From c39e9edb499b8ae8a3bf54fed0c3cf4546e87557 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:38:51 -0600 Subject: [PATCH 05/23] WIP: add mock hardware accelerator to test --- .github/workflows/build-and-test-refactor.yml | 12 + .../misc/wh_test_check_struct_padding.c | 6 + .../misc/wh_test_hwkeystore_crypto.c | 1649 +++++++++++++++++ test-refactor/posix/Makefile | 6 + test-refactor/wh_test_list.c | 2 + 5 files changed, 1675 insertions(+) create mode 100644 test-refactor/misc/wh_test_hwkeystore_crypto.c diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index ff3fbccd6..685172298 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -152,6 +152,18 @@ jobs: if: matrix.group == 'wolfcrypt' run: cd test-refactor/posix && make clean && make -j CRYPTO_AFFINITY=1 DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test with hardware keys in crypto requests (runs the hardware + # keystore crypto test, gated behind WOLFHSM_CFG_HWKEYSTORE_CRYPTO) + - name: Build and test refactor HWKEYSTORE_CRYPTO ASAN + if: matrix.group == 'wolfcrypt' + run: cd test-refactor/posix && make clean && make -j HWKEYSTORE_CRYPTO=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run + + # Build and test hardware keys alongside DMA (exercises the DMA handlers) + # LMS/XMSS verify-only; full PQ runs in DMA ASAN and THREADSAFE everything + - name: Build and test refactor HWKEYSTORE_CRYPTO DMA ASAN + if: matrix.group == 'wolfcrypt' + run: cd test-refactor/posix && make clean && make -j HWKEYSTORE_CRYPTO=1 DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + # Build and test with global SHE keys (all SHE slots shared across clients) - name: Build and test refactor ASAN SHE_GLOBAL if: matrix.group == 'wolfcrypt' diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c index 699d503ca..c61ceca81 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -72,6 +72,12 @@ whMessageKeystore_ExportResponse keyExportRes; whMessageKeystore_EraseResponse keyEraseRes; whMessageKeystore_ExportPublicRequest keyExportPublicReq; whMessageKeystore_ExportPublicResponse keyExportPublicRes; +whMessageKeystore_HwLoadRequest keyHwLoadReq; +whMessageKeystore_HwLoadResponse keyHwLoadRes; +whMessageKeystore_HwClearRequest keyHwClearReq; +whMessageKeystore_HwClearResponse keyHwClearRes; +whMessageKeystore_HwInfoRequest keyHwInfoReq; +whMessageKeystore_HwInfoResponse keyHwInfoRes; /* Include counter message header for new counter message structures */ #include "wolfhsm/wh_message_counter.h" diff --git a/test-refactor/misc/wh_test_hwkeystore_crypto.c b/test-refactor/misc/wh_test_hwkeystore_crypto.c new file mode 100644 index 000000000..a0ee43555 --- /dev/null +++ b/test-refactor/misc/wh_test_hwkeystore_crypto.c @@ -0,0 +1,1649 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/misc/wh_test_hwkeystore_crypto.c + * + * End-to-end test of hardware keys in AES and CMAC requests + * (WOLFHSM_CFG_HWKEYSTORE_CRYPTO). Two clients talk to two servers that share + * one NVM and one hardware keystore, like one HSM serving two cores. + * + * The servers' crypto callback is a mock AES engine modeled on the TC3xx HSM. + * It has eight write-only AES-128 key slots: + * K0, K1 device keys that no client can name + * K2 a key the port loads at boot. Clients can use it but not set or + * clear it + * K3, K4 loadable by clients + * K5-K7 used by the port for other things + * The mock runs each operation with wolfCrypt in software, using the key in + * the slot, and implements the wolfCrypt keystore callback that loads, clears + * and describes slots. + * + * Each client's transport runs its server whenever the client waits for a + * reply, so the blocking client APIs work without threads. + * + * _whTest_HwKeyMap - GetInfo for every hardware key ID + * _whTest_HwKeyPortKey - both clients use K2 but can't set or clear it + * _whTest_HwKeyLoadAndUse - load a keystore key into a hardware key and use + * it in every AES mode and CMAC, standard and DMA + * _whTest_HwKeyAllocation - picking a free key, explicit IDs, running out, + * reloading, and another client's keys + * _whTest_HwKeyUsage - the copy keeps the source key's usage flags + * _whTest_HwKeySource - evict keeps the copy, erase and revoke clear it + * _whTest_HwKeyRefused - sources and targets the server or port refuses + * _whTest_HwKeyClear - Clear and ClearAll + * _whTest_HwKeyShared - keys loaded with the global flag + * _whTest_HwKeyNoFallback - a key the hardware lost fails instead of + * running in software + * _whTest_HwKeyPortKeyOwner - a port key registered for one client + */ + +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ + defined(WOLFHSM_CFG_ENABLE_SERVER) && \ + defined(WOLFHSM_CFG_HWKEYSTORE_CRYPTO) && defined(HAVE_AES_ECB) && \ + defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_COUNTER) && \ + defined(HAVE_AESGCM) && defined(WOLFSSL_CMAC) && \ + defined(WOLFSSL_AES_DIRECT) + +#include +#include + +#include "wolfssl/wolfcrypt/settings.h" +#include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/error-crypt.h" +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/cmac.h" +#include "wolfssl/wolfcrypt/cryptocb.h" +#include "wolfssl/wolfcrypt/random.h" + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_common.h" +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_keyid.h" +#include "wolfhsm/wh_utils.h" +#include "wolfhsm/wh_hwkeystore.h" +#include "wolfhsm/wh_nvm.h" +#include "wolfhsm/wh_nvm_flash.h" +#include "wolfhsm/wh_flash_ramsim.h" +#include "wolfhsm/wh_transport_mem.h" +#include "wolfhsm/wh_client.h" +#include "wolfhsm/wh_client_crypto.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_server_keystore.h" + +#include "wh_test_common.h" +#include "wh_test_list.h" + +#define BUFFER_SIZE 4096 +#define FLASH_RAM_SIZE (1024 * 1024) +#define FLASH_SECTOR_SIZE (128 * 1024) +#define FLASH_PAGE_SIZE 8 + +/* wolfCrypt devIds: the mock engine, and one per client */ +#define MOCK_DEVID 0x4D4B4553 +#define CLIENT1_DEVID 0x484B4331 +#define CLIENT2_DEVID 0x484B4332 + +#define CLIENT1_ID WH_TEST_DEFAULT_CLIENT_ID +#define CLIENT2_ID (WH_TEST_DEFAULT_CLIENT_ID + 1) + +/* Mock engine slots */ +#define MOCK_SLOTS 8 +#define MOCK_PORT_SLOT 2 +#define MOCK_LOAD_FIRST 3 +#define MOCK_LOAD_LAST 4 + +/* Client keyIds of the hardware keys */ +#define HW(id) ((whKeyId)WH_CLIENT_KEYID_MAKE_HW(id)) + +#define USAGE_AES_CMAC \ + (WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT | \ + WH_NVM_FLAGS_USAGE_SIGN | WH_NVM_FLAGS_USAGE_VERIFY) + +/* Test data sizes */ +#define TEST_MSG_SZ 80 +#define TEST_BLOCKS_SZ (3 * AES_BLOCK_SIZE) /* ECB and CBC */ +#define TEST_CTR_SZ 37 +#define TEST_GCM_SZ 37 +#define TEST_CMAC_SZ 70 +#define TEST_GCM_IV_SZ 12 + +/* Keystore IDs used by the tests */ +#define SRC_SHARED_ID 0x20 +#define SRC_TRUSTED_ID 0x21 +#define SRC_MISSING_ID 0x22 + +/* ------------------------------------------------------------------------ + * Mock AES engine + * ------------------------------------------------------------------------ */ + +/* What each slot may hold */ +enum { + MOCK_NONE = 0, /* Not a hardware key */ + MOCK_LOAD, /* Loaded and cleared by clients */ + MOCK_PORT, /* Loaded by the port at boot */ +}; + +static const uint8_t _mockRole[MOCK_SLOTS] = { + MOCK_NONE, /* K0 */ + MOCK_NONE, /* K1 */ + MOCK_PORT, /* K2 */ + MOCK_LOAD, /* K3 */ + MOCK_LOAD, /* K4 */ + MOCK_NONE, /* K5 */ + MOCK_NONE, /* K6 */ + MOCK_NONE, /* K7 */ +}; + +typedef struct { + uint8_t key[MOCK_SLOTS][AES_128_KEY_SIZE]; + uint8_t loaded[MOCK_SLOTS]; + int ops; /* Operations run with a slot key */ +} MockEngine; + +static MockEngine _mock; + +/* Test data */ +static uint8_t _portKey[AES_128_KEY_SIZE]; +static uint8_t _keyA[AES_128_KEY_SIZE]; +static uint8_t _keyB[AES_128_KEY_SIZE]; +static uint8_t _keyC[AES_128_KEY_SIZE]; +static uint8_t _key256[AES_256_KEY_SIZE]; +static uint8_t _msg[TEST_MSG_SZ]; +static uint8_t _iv[AES_BLOCK_SIZE]; +static uint8_t _aad[20]; + +/* Slot named by a key reference (the server keyId, big-endian), or -1 */ +static int _MockRefSlot(const byte* ref, word32 refSz) +{ + whKeyId keyId; + + if ((ref == NULL) || (refSz != WH_HWKEYSTORE_REF_SIZE)) { + return -1; + } + keyId = (whKeyId)(((whKeyId)ref[0] << 8) | ref[1]); + if (!WH_KEYID_ISHW(keyId) || (WH_KEYID_ID(keyId) >= MOCK_SLOTS) || + (_mockRole[WH_KEYID_ID(keyId)] == MOCK_NONE)) { + return -1; + } + return (int)WH_KEYID_ID(keyId); +} + +/* Slot of a key set up by reference. A key wolfCrypt holds itself runs in + * software. A reference to a slot with no key fails, since software has no + * copy of it */ +static int _MockKeySlot(const unsigned char* id, int idLen, int* slot) +{ + if (idLen <= 0) { + return CRYPTOCB_UNAVAILABLE; + } + *slot = _MockRefSlot(id, (word32)idLen); + if ((*slot < 0) || (_mock.loaded[*slot] == 0)) { + return WC_HW_E; + } + return 0; +} + +static int _MockKeystore(wc_CryptoInfo* info) +{ + int slot; + + switch (info->keystore.type) { + case WC_KEYSTORE_IMPORT_PLAIN: + slot = _MockRefSlot(info->keystore.op.importPlain.keyRef, + info->keystore.op.importPlain.keyRefSz); + /* Only the loadable slots, and only AES-128 keys */ + if ((slot < 0) || (_mockRole[slot] != MOCK_LOAD) || + ((info->keystore.op.importPlain.keyType != + WC_KEYSTORE_KEY_AES) && + (info->keystore.op.importPlain.keyType != + WC_KEYSTORE_KEY_CMAC)) || + (info->keystore.op.importPlain.keySz != AES_128_KEY_SIZE)) { + return BAD_FUNC_ARG; + } + memcpy(_mock.key[slot], info->keystore.op.importPlain.key, + AES_128_KEY_SIZE); + _mock.loaded[slot] = 1; + return 0; + + case WC_KEYSTORE_DELETE: + slot = _MockRefSlot(info->keystore.op.deleteKey.keyRef, + info->keystore.op.deleteKey.keyRefSz); + if ((slot < 0) || (_mockRole[slot] != MOCK_LOAD)) { + return BAD_FUNC_ARG; + } + wh_Utils_ForceZero(_mock.key[slot], AES_128_KEY_SIZE); + _mock.loaded[slot] = 0; + return 0; + + case WC_KEYSTORE_GET_INFO: + slot = _MockRefSlot(info->keystore.op.getInfo.keyRef, + info->keystore.op.getInfo.keyRefSz); + if (slot < 0) { + return BAD_FUNC_ARG; + } + if (info->keystore.op.getInfo.keyType != NULL) { + *info->keystore.op.getInfo.keyType = _mock.loaded[slot] + ? WC_KEYSTORE_KEY_AES + : WC_KEYSTORE_KEY_NONE; + } + if (info->keystore.op.getInfo.keyBits != NULL) { + *info->keystore.op.getInfo.keyBits = + _mock.loaded[slot] ? 128 : 0; + } + if (info->keystore.op.getInfo.attrs != NULL) { + *info->keystore.op.getInfo.attrs = 0; + } + return 0; + + default: + return CRYPTOCB_UNAVAILABLE; + } +} + +static int _MockCipher(wc_CryptoInfo* info) +{ + Aes sw[1]; + Aes* aes; + int slot; + int ret; + + switch (info->cipher.type) { + case WC_CIPHER_AES_ECB: + aes = info->cipher.aesecb.aes; + break; + case WC_CIPHER_AES_CBC: + aes = info->cipher.aescbc.aes; + break; + case WC_CIPHER_AES_CTR: + aes = info->cipher.aesctr.aes; + break; + case WC_CIPHER_AES_GCM: + aes = (info->cipher.enc != 0) ? info->cipher.aesgcm_enc.aes + : info->cipher.aesgcm_dec.aes; + break; + default: + return CRYPTOCB_UNAVAILABLE; + } + + ret = _MockKeySlot(aes->id, aes->idLen, &slot); + if (ret != 0) { + return ret; + } + + ret = wc_AesInit(sw, NULL, INVALID_DEVID); + if (ret == 0) { + switch (info->cipher.type) { + case WC_CIPHER_AES_ECB: + ret = wc_AesSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE, NULL, + (info->cipher.enc != 0) ? AES_ENCRYPTION + : AES_DECRYPTION); + if ((ret == 0) && (info->cipher.enc != 0)) { + ret = wc_AesEcbEncrypt(sw, info->cipher.aesecb.out, + info->cipher.aesecb.in, + info->cipher.aesecb.sz); + } + else if (ret == 0) { + ret = wc_AesEcbDecrypt(sw, info->cipher.aesecb.out, + info->cipher.aesecb.in, + info->cipher.aesecb.sz); + } + break; + + case WC_CIPHER_AES_CBC: + ret = wc_AesSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE, + (const byte*)aes->reg, + (info->cipher.enc != 0) ? AES_ENCRYPTION + : AES_DECRYPTION); + if ((ret == 0) && (info->cipher.enc != 0)) { + ret = wc_AesCbcEncrypt(sw, info->cipher.aescbc.out, + info->cipher.aescbc.in, + info->cipher.aescbc.sz); + } + else if (ret == 0) { + ret = wc_AesCbcDecrypt(sw, info->cipher.aescbc.out, + info->cipher.aescbc.in, + info->cipher.aescbc.sz); + } + /* Hand back the chaining value */ + memcpy(aes->reg, sw->reg, AES_BLOCK_SIZE); + break; + + case WC_CIPHER_AES_CTR: + ret = wc_AesSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE, + (const byte*)aes->reg, AES_ENCRYPTION); + if (ret == 0) { + /* Continue from the caller's counter and unused + * keystream, and hand back the new state */ + memcpy(sw->tmp, aes->tmp, AES_BLOCK_SIZE); + sw->left = aes->left; + ret = wc_AesCtrEncrypt(sw, info->cipher.aesctr.out, + info->cipher.aesctr.in, + info->cipher.aesctr.sz); + memcpy(aes->reg, sw->reg, AES_BLOCK_SIZE); + memcpy(aes->tmp, sw->tmp, AES_BLOCK_SIZE); + aes->left = sw->left; + } + break; + + case WC_CIPHER_AES_GCM: + ret = wc_AesGcmSetKey(sw, _mock.key[slot], AES_128_KEY_SIZE); + if ((ret == 0) && (info->cipher.enc != 0)) { + wc_CryptoCb_AesAuthEnc* e = &info->cipher.aesgcm_enc; + ret = wc_AesGcmEncrypt(sw, e->out, e->in, e->sz, e->iv, + e->ivSz, e->authTag, e->authTagSz, + e->authIn, e->authInSz); + } + else if (ret == 0) { + wc_CryptoCb_AesAuthDec* d = &info->cipher.aesgcm_dec; + ret = wc_AesGcmDecrypt(sw, d->out, d->in, d->sz, d->iv, + d->ivSz, d->authTag, d->authTagSz, + d->authIn, d->authInSz); + } + break; + + default: + break; + } + wc_AesFree(sw); + } + wh_Utils_ForceZero(sw, sizeof(sw)); + + if (ret == 0) { + _mock.ops++; + } + return ret; +} + +static int _MockCmac(wc_CryptoInfo* info) +{ + Cmac* cmac = info->cmac.cmac; + Cmac sw[1]; + int slot; + int ret; + + /* Key bytes passed in are a software key */ + if (info->cmac.key != NULL) { + return CRYPTOCB_UNAVAILABLE; + } + ret = _MockKeySlot(cmac->id, cmac->idLen, &slot); + if (ret != 0) { + return ret; + } + /* Init has nothing to set up: the running state lives in cmac */ + if ((info->cmac.in == NULL) && (info->cmac.out == NULL)) { + return 0; + } + + /* Run the step on a software copy keyed from the slot */ + ret = wc_InitCmac_ex(sw, _mock.key[slot], AES_128_KEY_SIZE, WC_CMAC_AES, + NULL, NULL, INVALID_DEVID); + if (ret == 0) { + memcpy(sw->buffer, cmac->buffer, sizeof(sw->buffer)); + memcpy(sw->digest, cmac->digest, sizeof(sw->digest)); + sw->bufferSz = cmac->bufferSz; + sw->totalSz = cmac->totalSz; + + if (info->cmac.in != NULL) { + ret = wc_CmacUpdate(sw, info->cmac.in, info->cmac.inSz); + } + if ((ret == 0) && (info->cmac.out != NULL)) { + ret = wc_CmacFinalNoFree(sw, info->cmac.out, info->cmac.outSz); + } + + memcpy(cmac->buffer, sw->buffer, sizeof(cmac->buffer)); + memcpy(cmac->digest, sw->digest, sizeof(cmac->digest)); + cmac->bufferSz = sw->bufferSz; + cmac->totalSz = sw->totalSz; + (void)wc_CmacFree(sw); + } + wh_Utils_ForceZero(sw, sizeof(sw)); + + if (ret == 0) { + _mock.ops++; + } + return ret; +} + +static int _MockCryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)devId; + (void)ctx; + + switch (info->algo_type) { + case WC_ALGO_TYPE_KEYSTORE: + return _MockKeystore(info); + case WC_ALGO_TYPE_CIPHER: + return _MockCipher(info); + case WC_ALGO_TYPE_CMAC: + return _MockCmac(info); + default: + return CRYPTOCB_UNAVAILABLE; + } +} + +/* Slots can't be read, so hardware keys can't be key encryption keys */ +static int _MockGetKey(void* context, whKeyId keyId, uint8_t* out, + uint16_t* inout_len) +{ + (void)context; + (void)keyId; + (void)out; + (void)inout_len; + return WH_ERROR_ACCESS; +} + +/* clang-format off */ +static const whHwKeystoreCb _mockHwKeystoreCb = { + .Init = NULL, + .Cleanup = NULL, + .GetKey = _MockGetKey, +}; +/* clang-format on */ + +/* Boot the mock engine: empty every slot, load the port key into K2 and record + * it with the hardware keystore. owner is the client that may use it, or 0 + * for every client */ +static int _MockBoot(whHwKeystoreContext* hwKeystore, uint16_t owner) +{ + memset(&_mock, 0, sizeof(_mock)); + memcpy(_mock.key[MOCK_PORT_SLOT], _portKey, sizeof(_portKey)); + _mock.loaded[MOCK_PORT_SLOT] = 1; + + return wh_HwKeystore_Register( + hwKeystore, WH_MAKE_KEYID(WH_KEYTYPE_HW, owner, MOCK_PORT_SLOT), + USAGE_AES_CMAC, AES_128_KEY_SIZE); +} + +/* ------------------------------------------------------------------------ + * Client/server setup + * ------------------------------------------------------------------------ */ + +/* Client transport that runs the server when the client waits for a reply */ +typedef struct { + whTransportMemClientContext mem[1]; + whServerContext* server; +} PumpClientContext; + +static int _PumpInit(void* c, const void* cf, whCommSetConnectedCb connectcb, + void* connectcb_arg) +{ + return wh_TransportMem_InitClear(((PumpClientContext*)c)->mem, cf, + connectcb, connectcb_arg); +} + +static int _PumpSend(void* c, uint16_t len, const void* data) +{ + return wh_TransportMem_SendRequest(((PumpClientContext*)c)->mem, len, data); +} + +static int _PumpRecv(void* c, uint16_t* out_len, void* data) +{ + PumpClientContext* pump = (PumpClientContext*)c; + int ret; + int serverRet; + + ret = wh_TransportMem_RecvResponse(pump->mem, out_len, data); + if (ret == WH_ERROR_NOTREADY) { + serverRet = wh_Server_HandleRequestMessage(pump->server); + ret = wh_TransportMem_RecvResponse(pump->mem, out_len, data); + /* The server replies to each request at once, so still waiting + * means it failed. Stop instead of waiting forever */ + if (ret == WH_ERROR_NOTREADY) { + ret = (serverRet != WH_ERROR_OK) ? serverRet : WH_ERROR_ABORTED; + } + } + return ret; +} + +static int _PumpCleanup(void* c) +{ + return wh_TransportMem_Cleanup(((PumpClientContext*)c)->mem); +} + +/* clang-format off */ +static const whTransportClientCb _pumpClientCb = { + .Init = _PumpInit, + .Send = _PumpSend, + .Recv = _PumpRecv, + .Cleanup = _PumpCleanup, +}; +/* clang-format on */ + +/* One client and the server it talks to */ +typedef struct { + whServerContext server[1]; + whClientContext client[1]; + whServerCryptoContext crypto[1]; + uint8_t reqBuf[BUFFER_SIZE]; + uint8_t respBuf[BUFFER_SIZE]; + whTransportMemConfig tmcf[1]; + whTransportServerCb tscb[1]; + whTransportMemServerContext tmsc[1]; + whCommServerConfig cs_conf[1]; + PumpClientContext pump[1]; + whCommClientConfig cc_conf[1]; + whClientConfig c_conf[1]; + whServerConfig s_conf[1]; +} TestPair; + +typedef struct { + TestPair pair[2]; + /* Shared by both servers */ + whNvmContext nvm[1]; + whHwKeystoreContext hwKeystore[1]; + whHwKeystoreConfig hwks_conf[1]; + whFlashRamsimCtx fc[1]; + whFlashRamsimCfg fc_conf[1]; + whFlashCb fcb[1]; + whNvmFlashConfig nf_conf[1]; + whNvmFlashContext nfc[1]; + whNvmCb nfcb[1]; + whNvmConfig n_conf[1]; +} TestCtx; + +/* Static to keep the misc group's stack footprint small */ +static TestCtx _testCtx; +static uint8_t _flashMemory[FLASH_RAM_SIZE]; + +static void _Pattern(uint8_t* buf, uint32_t sz, uint8_t seed) +{ + uint32_t i; + + for (i = 0; i < sz; i++) { + buf[i] = (uint8_t)(seed ^ (i * 0x1D)); + } +} + +static int _SetupPair(TestCtx* t, TestPair* p, uint16_t clientId, + int clientDevId, uint16_t serverId) +{ + uint32_t outClientId = 0; + uint32_t outServerId = 0; + + p->tmcf[0] = (whTransportMemConfig){ + .req = (whTransportMemCsr*)p->reqBuf, + .req_size = sizeof(p->reqBuf), + .resp = (whTransportMemCsr*)p->respBuf, + .resp_size = sizeof(p->respBuf), + }; + p->tscb[0] = (whTransportServerCb)WH_TRANSPORT_MEM_SERVER_CB; + p->cs_conf[0] = (whCommServerConfig){ + .transport_cb = p->tscb, + .transport_context = (void*)p->tmsc, + .transport_config = (void*)p->tmcf, + .server_id = serverId, + }; + p->pump->server = p->server; + p->cc_conf[0] = (whCommClientConfig){ + .transport_cb = &_pumpClientCb, + .transport_context = (void*)p->pump, + .transport_config = (void*)p->tmcf, + .client_id = clientId, + }; + p->c_conf[0] = (whClientConfig){ + .comm = p->cc_conf, + .devId = clientDevId, + }; + p->s_conf[0] = (whServerConfig){ + .comm_config = p->cs_conf, + .nvm = t->nvm, + .crypto = p->crypto, + .devId = MOCK_DEVID, + .hwKeystore = t->hwKeystore, + }; + + WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(p->crypto->rng, NULL, INVALID_DEVID)); + WH_TEST_RETURN_ON_FAIL(wh_Server_Init(p->server, p->s_conf)); + WH_TEST_RETURN_ON_FAIL( + wh_Server_SetConnected(p->server, WH_COMM_CONNECTED)); + WH_TEST_RETURN_ON_FAIL(wh_Client_Init(p->client, p->c_conf)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_CommInit(p->client, &outClientId, &outServerId)); + WH_TEST_ASSERT_RETURN(outClientId == clientId); + return WH_ERROR_OK; +} + +static int _Setup(TestCtx* t) +{ + memset(t, 0, sizeof(*t)); + memset(_flashMemory, 0, sizeof(_flashMemory)); + + _Pattern(_portKey, sizeof(_portKey), 0x2B); + _Pattern(_keyA, sizeof(_keyA), 0xA5); + _Pattern(_keyB, sizeof(_keyB), 0x5A); + _Pattern(_keyC, sizeof(_keyC), 0xC3); + _Pattern(_key256, sizeof(_key256), 0x77); + _Pattern(_msg, sizeof(_msg), 0x11); + _Pattern(_iv, sizeof(_iv), 0xE0); + _Pattern(_aad, sizeof(_aad), 0x42); + + t->fc_conf[0] = (whFlashRamsimCfg){ + .size = FLASH_RAM_SIZE, + .sectorSize = FLASH_SECTOR_SIZE, + .pageSize = FLASH_PAGE_SIZE, + .erasedByte = ~(uint8_t)0, + .memory = _flashMemory, + }; + t->fcb[0] = (whFlashCb)WH_FLASH_RAMSIM_CB; + t->nf_conf[0] = (whNvmFlashConfig){ + .cb = t->fcb, + .context = t->fc, + .config = t->fc_conf, + }; + t->nfcb[0] = (whNvmCb)WH_NVM_FLASH_CB; + t->n_conf[0] = (whNvmConfig){ + .cb = t->nfcb, + .context = t->nfc, + .config = t->nf_conf, + }; + t->hwks_conf[0] = (whHwKeystoreConfig){ + .cb = &_mockHwKeystoreCb, + }; + + WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL( + wc_CryptoCb_RegisterDevice(MOCK_DEVID, _MockCryptoCb, NULL)); + WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(t->nvm, t->n_conf)); + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(t->hwKeystore, t->hwks_conf)); + WH_TEST_RETURN_ON_FAIL(_MockBoot(t->hwKeystore, WH_KEYUSER_GLOBAL)); + WH_TEST_RETURN_ON_FAIL( + _SetupPair(t, &t->pair[0], CLIENT1_ID, CLIENT1_DEVID, 201)); + WH_TEST_RETURN_ON_FAIL( + _SetupPair(t, &t->pair[1], CLIENT2_ID, CLIENT2_DEVID, 202)); + return WH_ERROR_OK; +} + +static void _Cleanup(TestCtx* t) +{ + int i; + + for (i = 0; i < 2; i++) { + (void)wh_Client_Cleanup(t->pair[i].client); + (void)wh_Server_Cleanup(t->pair[i].server); + (void)wc_FreeRng(t->pair[i].crypto->rng); + } + (void)wh_HwKeystore_Cleanup(t->hwKeystore); + (void)wh_Nvm_Cleanup(t->nvm); + wc_CryptoCb_UnRegisterDevice(MOCK_DEVID); + (void)wolfCrypt_Cleanup(); + wh_Utils_ForceZero(&_mock, sizeof(_mock)); +} + +/* Simulate an HSM reset: the slots and the hardware keystore start empty and + * the port registers its key again, this time for owner */ +static int _Reboot(TestCtx* t, uint16_t owner) +{ + (void)wh_HwKeystore_Cleanup(t->hwKeystore); + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(t->hwKeystore, t->hwks_conf)); + return _MockBoot(t->hwKeystore, owner); +} + +/* ------------------------------------------------------------------------ + * Helpers + * ------------------------------------------------------------------------ */ + +/* Cache an AES key in the client's keystore. *inout_id 0 picks a free ID */ +static int _Cache(whClientContext* c, whNvmFlags flags, const uint8_t* key, + uint16_t keySz, whKeyId* inout_id) +{ + uint8_t label[WH_NVM_LABEL_LEN] = "hwkey source"; + uint16_t id = *inout_id; + int ret; + + ret = wh_Client_KeyCache(c, flags, label, sizeof(label), (uint8_t*)key, + keySz, &id); + if (ret == WH_ERROR_OK) { + *inout_id = id; + } + return ret; +} + +static int _Load(whClientContext* c, whKeyId hwId, whKeyId srcId, + whKeyId* outHwId) +{ + return wh_Client_HwKeystoreLoadFromKeystore(c, hwId, srcId, outHwId); +} + +/* Check what GetInfo reports for a hardware key */ +static int _CheckInfo(whClientContext* c, int id, int loaded, whNvmFlags flags, + uint16_t owner) +{ + whHwKeystoreInfo info; + + memset(&info, 0xFF, sizeof(info)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreGetInfo(c, HW(id), &info)); + if (loaded) { + WH_TEST_ASSERT_RETURN(info.keyType == WC_KEYSTORE_KEY_AES); + WH_TEST_ASSERT_RETURN(info.keyBits == 128); + WH_TEST_ASSERT_RETURN(info.flags == flags); + WH_TEST_ASSERT_RETURN(info.owner == owner); + } + else { + WH_TEST_ASSERT_RETURN(info.keyType == WC_KEYSTORE_KEY_NONE); + WH_TEST_ASSERT_RETURN(info.keyBits == 0); + WH_TEST_ASSERT_RETURN(info.flags == 0); + WH_TEST_ASSERT_RETURN(info.owner == 0); + } + return WH_ERROR_OK; +} + +/* Clear every key both clients loaded, and check the loadable slots are + * empty again */ +static int _ClearAll(TestCtx* t) +{ + int id; + + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearAll(t->pair[0].client)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearAll(t->pair[1].client)); + for (id = MOCK_LOAD_FIRST; id <= MOCK_LOAD_LAST; id++) { + WH_TEST_RETURN_ON_FAIL(_CheckInfo(t->pair[0].client, id, 0, 0, 0)); + } + return WH_ERROR_OK; +} + +/* Expected results, from software */ +typedef struct { + uint8_t ecb[TEST_BLOCKS_SZ]; + uint8_t cbc[TEST_BLOCKS_SZ]; + uint8_t ctr[TEST_CTR_SZ]; + uint8_t gcm[TEST_GCM_SZ]; + uint8_t gcmTag[AES_BLOCK_SIZE]; + uint8_t gcm16[TEST_GCM_SZ]; /* With a 16-byte IV */ + uint8_t gcm16Tag[AES_BLOCK_SIZE]; + uint8_t cmac[AES_BLOCK_SIZE]; +} Expected; + +static int _SwExpected(const uint8_t* key, Expected* e) +{ + Aes aes[1]; + word32 macSz = AES_BLOCK_SIZE; + int ret; + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesSetKey(aes, key, AES_128_KEY_SIZE, NULL, AES_ENCRYPTION); + } + if (ret == 0) { + ret = wc_AesEcbEncrypt(aes, e->ecb, _msg, TEST_BLOCKS_SZ); + } + if (ret == 0) { + ret = wc_AesSetKey(aes, key, AES_128_KEY_SIZE, _iv, AES_ENCRYPTION); + } + if (ret == 0) { + ret = wc_AesCbcEncrypt(aes, e->cbc, _msg, TEST_BLOCKS_SZ); + } + if (ret == 0) { + ret = wc_AesSetKey(aes, key, AES_128_KEY_SIZE, _iv, AES_ENCRYPTION); + } + if (ret == 0) { + ret = wc_AesCtrEncrypt(aes, e->ctr, _msg, TEST_CTR_SZ); + } + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, key, AES_128_KEY_SIZE); + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(aes, e->gcm, _msg, TEST_GCM_SZ, _iv, + TEST_GCM_IV_SZ, e->gcmTag, AES_BLOCK_SIZE, _aad, + sizeof(_aad)); + } + if (ret == 0) { + ret = + wc_AesGcmEncrypt(aes, e->gcm16, _msg, TEST_GCM_SZ, _iv, sizeof(_iv), + e->gcm16Tag, AES_BLOCK_SIZE, _aad, sizeof(_aad)); + } + wc_AesFree(aes); + if (ret == 0) { + ret = wc_AesCmacGenerate(e->cmac, &macSz, _msg, TEST_CMAC_SZ, key, + AES_128_KEY_SIZE); + } + return ret; +} + +/* Fresh Aes that names keyId, with iv if not NULL */ +static int _AesStart(Aes* aes, whKeyId keyId, const uint8_t* iv) +{ + int ret; + + ret = wc_AesInit(aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_AesSetKeyId(aes, keyId); + } + if ((ret == 0) && (iv != NULL)) { + ret = wc_AesSetIV(aes, iv); + } + return ret; +} + +static int _Ecb(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesEcbDma(c, aes, enc, in, sz, out); + } +#endif + (void)dma; + return wh_Client_AesEcb(c, aes, enc, in, sz, out); +} + +static int _Cbc(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesCbcDma(c, aes, enc, in, sz, out); + } +#endif + (void)dma; + return wh_Client_AesCbc(c, aes, enc, in, sz, out); +} + +static int _Ctr(whClientContext* c, Aes* aes, int dma, const uint8_t* in, + uint8_t* out, uint32_t sz) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesCtrDma(c, aes, 1, in, sz, out); + } +#endif + (void)dma; + return wh_Client_AesCtr(c, aes, 1, in, sz, out); +} + +static int _Gcm(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz, uint32_t ivSz, + const uint8_t* decTag, uint8_t* encTag) +{ +#ifdef WOLFHSM_CFG_DMA + if (dma) { + return wh_Client_AesGcmDma(c, aes, enc, in, sz, _iv, ivSz, _aad, + sizeof(_aad), decTag, encTag, AES_BLOCK_SIZE, + out); + } +#endif + (void)dma; + return wh_Client_AesGcm(c, aes, enc, in, sz, _iv, ivSz, _aad, sizeof(_aad), + decTag, encTag, AES_BLOCK_SIZE, out); +} + +/* One-shot CMAC of the test message */ +static int _Cmac(whClientContext* c, whKeyId keyId, int dma, uint8_t* tag) +{ + Cmac cmac[1]; + uint32_t tagSz = AES_BLOCK_SIZE; + int ret; + + ret = wc_InitCmac_ex(cmac, NULL, 0, WC_CMAC_AES, NULL, NULL, c->devId); + if (ret == 0) { + ret = wh_Client_CmacSetKeyId(cmac, keyId); + } + if (ret == 0) { +#ifdef WOLFHSM_CFG_DMA + if (dma) { + ret = wh_Client_CmacDma(c, cmac, WC_CMAC_AES, NULL, 0, _msg, + TEST_CMAC_SZ, tag, &tagSz); + } + else +#endif + { + ret = wh_Client_Cmac(c, cmac, WC_CMAC_AES, NULL, 0, _msg, + TEST_CMAC_SZ, tag, &tagSz); + } + } + (void)dma; + (void)wc_CmacFree(cmac); + return ret; +} + +/* CMAC of the test message over several requests, so the server picks up the + * running state each time */ +static int _CmacSteps(whClientContext* c, whKeyId keyId, uint8_t* tag) +{ + Cmac cmac[1]; + uint32_t tagSz = AES_BLOCK_SIZE; + bool sent = false; + int ret; + + ret = wc_InitCmac_ex(cmac, NULL, 0, WC_CMAC_AES, NULL, NULL, c->devId); + if (ret == 0) { + ret = wh_Client_CmacSetKeyId(cmac, keyId); + } + if (ret == 0) { + ret = wh_Client_CmacUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, _msg, + 40, &sent); + } + if ((ret == 0) && sent) { + ret = wh_Client_CmacUpdateResponse(c, cmac); + } + if (ret == 0) { + ret = wh_Client_CmacUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, + _msg + 40, TEST_CMAC_SZ - 40, &sent); + } + if ((ret == 0) && sent) { + ret = wh_Client_CmacUpdateResponse(c, cmac); + } + if (ret == 0) { + ret = wh_Client_CmacFinalRequest(c, cmac); + } + if (ret == 0) { + ret = wh_Client_CmacFinalResponse(c, cmac, tag, &tagSz); + } + (void)wc_CmacFree(cmac); + return ret; +} + +/* Result of a one-block ECB request with keyId */ +static int _TryEcb(whClientContext* c, whKeyId keyId, int enc) +{ + Aes aes[1]; + uint8_t out[AES_BLOCK_SIZE]; + int ret; + + ret = _AesStart(aes, keyId, NULL); + if (ret == 0) { + ret = wh_Client_AesEcb(c, aes, enc, _msg, AES_BLOCK_SIZE, out); + } + wc_AesFree(aes); + return ret; +} + +/* Result of a one-shot CMAC request with keyId */ +static int _TryCmac(whClientContext* c, whKeyId keyId) +{ + uint8_t tag[AES_BLOCK_SIZE]; + + return _Cmac(c, keyId, 0, tag); +} + +/* One-block ECB with keyId must match software with key */ +static int _EcbMatches(whClientContext* c, whKeyId keyId, const uint8_t* key) +{ + Expected e; + Aes aes[1]; + uint8_t out[AES_BLOCK_SIZE]; + + WH_TEST_RETURN_ON_FAIL(_SwExpected(key, &e)); + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, NULL)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_AesEcb(c, aes, 1, _msg, AES_BLOCK_SIZE, out)); + wc_AesFree(aes); + WH_TEST_ASSERT_RETURN(memcmp(out, e.ecb, AES_BLOCK_SIZE) == 0); + return WH_ERROR_OK; +} + +/* The mock ran an operation since before (hardware key), or didn't (key held + * in software) */ +#define CHECK_RAN(before, hw) \ + WH_TEST_ASSERT_RETURN((hw) ? (_mock.ops > (before)) \ + : (_mock.ops == (before))) + +/* Run every AES mode and CMAC with keyId and compare with software results + * for key. hw says whether the mock engine must run them */ +static int _CheckModes(whClientContext* c, whKeyId keyId, const uint8_t* key, + int dma, int hw) +{ + Expected e; + Aes aes[1]; + uint8_t out[TEST_MSG_SZ]; + uint8_t back[TEST_MSG_SZ]; + uint8_t tag[AES_BLOCK_SIZE]; + int ops; + + WH_TEST_RETURN_ON_FAIL(_SwExpected(key, &e)); + + /* ECB */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, NULL)); + WH_TEST_RETURN_ON_FAIL(_Ecb(c, aes, dma, 1, _msg, out, TEST_BLOCKS_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.ecb, TEST_BLOCKS_SZ) == 0); + WH_TEST_RETURN_ON_FAIL(_Ecb(c, aes, dma, 0, out, back, TEST_BLOCKS_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_BLOCKS_SZ) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* CBC, encrypted in two requests to check the chaining value */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Cbc(c, aes, dma, 1, _msg, out, 32)); + WH_TEST_RETURN_ON_FAIL( + _Cbc(c, aes, dma, 1, _msg + 32, out + 32, TEST_BLOCKS_SZ - 32)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.cbc, TEST_BLOCKS_SZ) == 0); + wc_AesFree(aes); + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Cbc(c, aes, dma, 0, out, back, TEST_BLOCKS_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_BLOCKS_SZ) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* CTR, in two requests that split a block */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, _msg, out, 20)); + WH_TEST_RETURN_ON_FAIL( + _Ctr(c, aes, dma, _msg + 20, out + 20, TEST_CTR_SZ - 20)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.ctr, TEST_CTR_SZ) == 0); + wc_AesFree(aes); + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); + WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, out, back, TEST_CTR_SZ)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_CTR_SZ) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* GCM with a 12-byte IV, a 16-byte IV, and a bad tag */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, NULL)); + WH_TEST_RETURN_ON_FAIL(_Gcm(c, aes, dma, 1, _msg, out, TEST_GCM_SZ, + TEST_GCM_IV_SZ, NULL, tag)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.gcm, TEST_GCM_SZ) == 0); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.gcmTag, AES_BLOCK_SIZE) == 0); + WH_TEST_RETURN_ON_FAIL(_Gcm(c, aes, dma, 0, out, back, TEST_GCM_SZ, + TEST_GCM_IV_SZ, tag, NULL)); + WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_GCM_SZ) == 0); + tag[0] ^= 0x01; + WH_TEST_ASSERT_RETURN(AES_GCM_AUTH_E == _Gcm(c, aes, dma, 0, out, back, + TEST_GCM_SZ, TEST_GCM_IV_SZ, + tag, NULL)); + WH_TEST_RETURN_ON_FAIL( + _Gcm(c, aes, dma, 1, _msg, out, TEST_GCM_SZ, sizeof(_iv), NULL, tag)); + WH_TEST_ASSERT_RETURN(memcmp(out, e.gcm16, TEST_GCM_SZ) == 0); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.gcm16Tag, AES_BLOCK_SIZE) == 0); + wc_AesFree(aes); + CHECK_RAN(ops, hw); + + /* CMAC in one request, and over several */ + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_Cmac(c, keyId, dma, tag)); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); + if (!dma) { + memset(tag, 0, sizeof(tag)); + WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, keyId, tag)); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); + } + CHECK_RAN(ops, hw); + + return WH_ERROR_OK; +} + +/* ------------------------------------------------------------------------ + * Tests + * ------------------------------------------------------------------------ */ + +/* GetInfo for every hardware key ID */ +static int _whTest_HwKeyMap(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whHwKeystoreInfo info; + int id; + + for (id = 1; id <= MOCK_SLOTS; id++) { + if (id == MOCK_PORT_SLOT) { + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c, id, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + } + else if ((id >= MOCK_LOAD_FIRST) && (id <= MOCK_LOAD_LAST)) { + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, id, 0, 0, 0)); + } + else { + /* The port has no hardware key with this ID */ + WH_TEST_ASSERT_RETURN( + BAD_FUNC_ARG == wh_Client_HwKeystoreGetInfo(c, HW(id), &info)); + } + } + + /* ID 0 names no key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_Client_HwKeystoreGetInfo(c, HW(0), &info)); + + /* The split request and response */ + memset(&info, 0, sizeof(info)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_HwKeystoreGetInfoRequest(c, HW(MOCK_PORT_SLOT))); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreGetInfoResponse(c, &info)); + WH_TEST_ASSERT_RETURN(info.keyType == WC_KEYSTORE_KEY_AES); + WH_TEST_ASSERT_RETURN(info.flags == USAGE_AES_CMAC); + + return WH_ERROR_OK; +} + +/* Both clients can use the port key in K2, but neither can set or clear it */ +static int _whTest_HwKeyPortKey(TestCtx* t) +{ + whClientContext* c; + whKeyId src; + whKeyId hwId; + int i; + int dma; + + for (i = 0; i < 2; i++) { + c = t->pair[i].client; + + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL( + _CheckModes(c, HW(MOCK_PORT_SLOT), _portKey, dma, 1)); + } + + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c, HW(MOCK_PORT_SLOT), src, &hwId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_HwKeystoreClear(c, HW(MOCK_PORT_SLOT))); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearAll(c)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_PORT_SLOT, 1, USAGE_AES_CMAC, + WH_KEYUSER_GLOBAL)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + } + + return WH_ERROR_OK; +} + +/* Load a keystore key into a hardware key and use it in every mode */ +static int _whTest_HwKeyLoadAndUse(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId hwId; + Expected e; + Aes aes[1]; + uint8_t out[TEST_GCM_SZ]; + uint8_t tag[AES_BLOCK_SIZE]; + int dma; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + + /* The keystore key itself runs in software */ + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL(_CheckModes(c, src, _keyA, dma, 0)); + } + + /* ID 0 takes the first free hardware key */ + hwId = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + wh_Client_HwKeystoreLoadFromKeystoreRequest(c, HW(0), src)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_HwKeystoreLoadFromKeystoreResponse(c, &hwId)); + WH_TEST_ASSERT_RETURN(hwId == HW(MOCK_LOAD_FIRST)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(t->pair[1].client, MOCK_LOAD_FIRST, 1, + USAGE_AES_CMAC, CLIENT1_ID)); + + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL(_CheckModes(c, hwId, _keyA, dma, 1)); + } + + /* The wolfCrypt API works too: the client's crypto callback sends the + * hardware keyId */ + WH_TEST_RETURN_ON_FAIL(_SwExpected(_keyA, &e)); + WH_TEST_RETURN_ON_FAIL(wc_AesInit(aes, NULL, c->devId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_AesSetKeyId(aes, hwId)); + WH_TEST_RETURN_ON_FAIL(wc_AesGcmEncrypt(aes, out, _msg, TEST_GCM_SZ, _iv, + TEST_GCM_IV_SZ, tag, sizeof(tag), + _aad, sizeof(_aad))); + wc_AesFree(aes); + WH_TEST_ASSERT_RETURN(memcmp(out, e.gcm, TEST_GCM_SZ) == 0); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.gcmTag, AES_BLOCK_SIZE) == 0); + + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c, hwId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + return WH_ERROR_OK; +} + +/* Picking a free key, explicit IDs, running out, reloading, and keys that + * belong to the other client */ +static int _whTest_HwKeyAllocation(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src1 = WH_KEYID_ERASED; + whKeyId src2 = WH_KEYID_ERASED; + whKeyId src3 = WH_KEYID_ERASED; + whKeyId hw1; + whKeyId hw2; + whKeyId tmp; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c2, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &src2)); + + /* Each client gets the next free key */ + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src1, &hw1)); + WH_TEST_ASSERT_RETURN(hw1 == HW(MOCK_LOAD_FIRST)); + WH_TEST_RETURN_ON_FAIL(_Load(c2, HW(0), src2, &hw2)); + WH_TEST_ASSERT_RETURN(hw2 == HW(MOCK_LOAD_LAST)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, hw1, _keyA)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, hw2, _keyB)); + + /* No loadable key is left */ + WH_TEST_ASSERT_RETURN(WH_ERROR_NOSPACE == _Load(c1, HW(0), src1, &tmp)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOSPACE == _Load(c2, HW(0), src2, &tmp)); + + /* A client can't use, replace or clear the other client's key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _TryEcb(c2, hw1, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _TryCmac(c2, hw1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _TryEcb(c1, hw2, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _Load(c2, hw1, src2, &tmp)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_HwKeystoreClear(c2, hw1)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c2, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, CLIENT1_ID)); + + /* The owner can load its key again. The copy then follows the new + * source, so erasing the old one leaves it alone */ + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, WH_NVM_FLAGS_USAGE_ENCRYPT, _keyC, sizeof(_keyC), &src3)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, hw1, src3, &tmp)); + WH_TEST_ASSERT_RETURN(tmp == hw1); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, hw1, _keyC)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, MOCK_LOAD_FIRST, 1, + WH_NVM_FLAGS_USAGE_ENCRYPT, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyErase(c1, src1)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, hw1, _keyC)); + + /* ID 0 names no key, and ID 9 isn't loaded */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == _TryEcb(c1, HW(0), 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, HW(9), 1)); + + WH_TEST_RETURN_ON_FAIL(_ClearAll(t)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src3)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c2, src2)); + return WH_ERROR_OK; +} + +/* The copy keeps the source key's usage flags */ +static int _whTest_HwKeyUsage(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whKeyId src; + whKeyId hwId; + + /* Encrypt only: no decrypt and no CMAC */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_ENCRYPT, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, WH_KEYID_ID(hwId), 1, + WH_NVM_FLAGS_USAGE_ENCRYPT, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(_TryEcb(c, hwId, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryEcb(c, hwId, 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryCmac(c, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + /* Verify only: CMAC but no AES */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_VERIFY, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(_TryCmac(c, hwId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryEcb(c, hwId, 1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + /* A key with no usage could never be used, so it isn't loaded */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_NONE, _keyA, sizeof(_keyA), &src)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _Load(c, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + /* A non-exportable key can be loaded: the key stays in the HSM */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL(_Cache(c, + USAGE_AES_CMAC | WH_NVM_FLAGS_NONEXPORTABLE, + _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c, hwId, _keyA)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + return WH_ERROR_OK; +} + +/* Evicting the source keeps the copy. Erasing or revoking it clears every + * copy, but only copies of that client's key */ +static int _whTest_HwKeySource(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src; + whKeyId other; + whKeyId hwId; + + /* Evict */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, hwId, _keyA)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c1, hwId)); + + /* Erase */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyErase(c1, src)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(hwId), 0, 0, 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, hwId, 1)); + + /* Revoke. A revoked key has no usage, so it can't be loaded again */ + src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyRevoke(c1, src)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(hwId), 0, 0, 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, hwId, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _Load(c1, HW(0), src, &hwId)); + + /* The other client's key with the same ID is a different key */ + src = SRC_SHARED_ID; + other = SRC_SHARED_ID; + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c2, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &other)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyErase(c2, other)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, hwId, _keyA)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c1, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + + return WH_ERROR_OK; +} + +/* Sources and targets the server or the port refuses */ +static int _whTest_HwKeyRefused(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whServerContext* server = t->pair[0].server; + whKeyId src = WH_KEYID_ERASED; + whKeyId src256 = WH_KEYID_ERASED; + whKeyId hwId; + whNvmMetadata meta[1]; + int ret; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + + /* Sources: a hardware key, a missing key, and no key at all */ + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + _Load(c, HW(0), HW(MOCK_PORT_SLOT), &hwId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + _Load(c, HW(0), SRC_MISSING_ID, &hwId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + _Load(c, HW(0), WH_KEYID_ERASED, &hwId)); + + /* The port takes only AES-128 keys */ + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _key256, sizeof(_key256), &src256)); + WH_TEST_ASSERT_RETURN(BAD_FUNC_ARG == + _Load(c, HW(MOCK_LOAD_FIRST), src256, &hwId)); + WH_TEST_ASSERT_RETURN(BAD_FUNC_ARG == _Load(c, HW(0), src256, &hwId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src256)); + + /* Targets: IDs that aren't loadable hardware keys, and the port key */ + WH_TEST_ASSERT_RETURN(BAD_FUNC_ARG == _Load(c, HW(1), src, &hwId)); + WH_TEST_ASSERT_RETURN(BAD_FUNC_ARG == _Load(c, HW(5), src, &hwId)); + WH_TEST_ASSERT_RETURN(BAD_FUNC_ARG == _Load(c, HW(9), src, &hwId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c, HW(MOCK_PORT_SLOT), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_FIRST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, MOCK_LOAD_LAST, 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + + /* A trusted key is refused like for every client keystore operation. Only + * the server can make one */ + memset(meta, 0, sizeof(meta)); + meta->id = WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, CLIENT1_ID, SRC_TRUSTED_ID); + meta->flags = WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_TRUSTED; + meta->len = AES_128_KEY_SIZE; + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreCacheKey(server, meta, _keyA); + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + WH_TEST_RETURN_ON_FAIL(ret); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c, HW(0), SRC_TRUSTED_ID, &hwId)); + ret = WH_SERVER_NVM_LOCK(server); + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeystoreEvictKey(server, meta->id); + (void)WH_SERVER_NVM_UNLOCK(server); + } /* WH_SERVER_NVM_LOCK() */ + WH_TEST_RETURN_ON_FAIL(ret); + + return WH_ERROR_OK; +} + +/* Clear and ClearAll */ +static int _whTest_HwKeyClear(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src1 = WH_KEYID_ERASED; + whKeyId src2 = WH_KEYID_ERASED; + whKeyId hw1; + whKeyId hw2; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c2, USAGE_AES_CMAC, _keyB, sizeof(_keyB), &src2)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src1, &hw1)); + WH_TEST_RETURN_ON_FAIL(_Load(c2, HW(0), src2, &hw2)); + + /* Only the owner can clear a key, nobody can clear the port key, and ID 0 + * isn't a key to clear */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_HwKeystoreClear(c1, hw2)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_HwKeystoreClear(c1, HW(MOCK_PORT_SLOT))); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Client_HwKeystoreClear(c1, HW(9))); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_Client_HwKeystoreClearRequest(c1, HW(0))); + + /* The split request and response. The key is then gone */ + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearRequest(c1, hw1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearResponse(c1)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(hw1), 0, 0, 0)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == _TryEcb(c1, hw1, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_NOTFOUND == + wh_Client_HwKeystoreClear(c1, hw1)); + + /* ClearAll clears only the caller's keys */ + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src1, &hw1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearAll(c1)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(hw1), 0, 0, 0)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, WH_KEYID_ID(hw2), 1, USAGE_AES_CMAC, CLIENT2_ID)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, MOCK_PORT_SLOT, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, hw2, _keyB)); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearAll(c2)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, WH_KEYID_ID(hw2), 0, 0, 0)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c2, src2)); + return WH_ERROR_OK; +} + +#ifdef WOLFHSM_CFG_GLOBAL_KEYS +/* A key loaded with the global flag is shared: every client can use it, and + * clearing it takes the global flag too */ +static int _whTest_HwKeyShared(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId hwId; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL( + _Load(c1, WH_CLIENT_KEYID_MAKE_GLOBAL(HW(0)), src, &hwId)); + WH_TEST_ASSERT_RETURN(hwId == + WH_CLIENT_KEYID_MAKE_GLOBAL(HW(MOCK_LOAD_FIRST))); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c2, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + + /* Both clients can use it, by either form of the ID */ + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c1, hwId, _keyA)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, hwId, _keyA)); + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c2, HW(MOCK_LOAD_FIRST), _keyA)); + WH_TEST_RETURN_ON_FAIL(_TryCmac(c2, hwId)); + + /* ClearAll and a clear without the global flag leave it */ + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearAll(c1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_HwKeystoreClear(c2, HW(MOCK_LOAD_FIRST))); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, MOCK_LOAD_FIRST, 1, USAGE_AES_CMAC, WH_KEYUSER_GLOBAL)); + + /* Any client can clear it with the global flag */ + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c2, hwId)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c1, MOCK_LOAD_FIRST, 0, 0, 0)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + return WH_ERROR_OK; +} +#endif /* WOLFHSM_CFG_GLOBAL_KEYS */ + +/* If the hardware loses a key the server still records, requests fail rather + * than run in software without the key */ +static int _whTest_HwKeyNoFallback(TestCtx* t) +{ + whClientContext* c = t->pair[0].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId hwId; + int slot; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src, &hwId)); + slot = (int)WH_KEYID_ID(hwId); + + _mock.loaded[slot] = 0; + WH_TEST_ASSERT_RETURN(WC_HW_E == _TryEcb(c, hwId, 1)); + WH_TEST_ASSERT_RETURN(WC_HW_E == _TryCmac(c, hwId)); + _mock.loaded[slot] = 1; + WH_TEST_RETURN_ON_FAIL(_EcbMatches(c, hwId, _keyA)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + return WH_ERROR_OK; +} + +/* A port key registered for one client: only that client can use it, and no + * client can set or clear it */ +static int _whTest_HwKeyPortKeyOwner(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId hwId; + + WH_TEST_RETURN_ON_FAIL(_Reboot(t, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c2, MOCK_PORT_SLOT, 1, USAGE_AES_CMAC, CLIENT1_ID)); + + WH_TEST_RETURN_ON_FAIL(_CheckModes(c1, HW(MOCK_PORT_SLOT), _portKey, 0, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _TryEcb(c2, HW(MOCK_PORT_SLOT), 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _TryCmac(c2, HW(MOCK_PORT_SLOT))); + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &src)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + _Load(c1, HW(MOCK_PORT_SLOT), src, &hwId)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_HwKeystoreClear(c1, HW(MOCK_PORT_SLOT))); + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClearAll(c1)); + WH_TEST_RETURN_ON_FAIL( + _CheckInfo(c1, MOCK_PORT_SLOT, 1, USAGE_AES_CMAC, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + + /* Back to a shared port key */ + return _Reboot(t, WH_KEYUSER_GLOBAL); +} + +int whTest_HwKeystoreCrypto(void* ctx) +{ + TestCtx* t = &_testCtx; + int ret; + + (void)ctx; + + ret = _Setup(t); + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyMap(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyPortKey(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyLoadAndUse(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyAllocation(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyUsage(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeySource(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyRefused(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyClear(t); + } +#ifdef WOLFHSM_CFG_GLOBAL_KEYS + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyShared(t); + } +#endif + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyNoFallback(t); + } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyPortKeyOwner(t); + } + + _Cleanup(t); + + return ret; +} + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER && \ + WOLFHSM_CFG_HWKEYSTORE_CRYPTO && HAVE_AES_ECB && HAVE_AES_CBC && \ + WOLFSSL_AES_COUNTER && HAVE_AESGCM && WOLFSSL_CMAC && \ + WOLFSSL_AES_DIRECT */ diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index f0217de3c..2a180767c 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -202,6 +202,12 @@ ifeq ($(CERT_VERIFY_CACHE_FULLCHAIN),1) DEF += -DWOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_FULLCHAIN endif +# Enable hardware keys in AES and CMAC requests and their unit test +ifeq ($(HWKEYSTORE_CRYPTO),1) + DEF += -DWOLFHSM_CFG_HWKEYSTORE_CRYPTO + DEF += -DWOLF_CRYPTO_CB_KEYSTORE -DWOLF_PRIVATE_KEY_ID +endif + # Build the wolfCrypt test suite as a wolfHSM client ifeq ($(TESTWOLFCRYPT),1) DEF += -DWOLFHSM_CFG_TEST_WOLFCRYPTTEST diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index 731fd3000..f9d93f47a 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -49,6 +49,7 @@ WH_TEST_DECL(whTest_CryptoAesGcmDmaAad); WH_TEST_DECL(whTest_Dma); WH_TEST_DECL(whTest_DmaNonCoherent); WH_TEST_DECL(whTest_HwKeystore); +WH_TEST_DECL(whTest_HwKeystoreCrypto); WH_TEST_DECL(whTest_KeystoreReqSize); WH_TEST_DECL(whTest_MessageNvmTranslate); WH_TEST_DECL(whTest_MultiClient); @@ -130,6 +131,7 @@ const whTestCase whTestsMisc[] = { {"whTest_MessageNvmTranslate", whTest_MessageNvmTranslate}, {"whTest_MultiClient", whTest_MultiClient}, {"whTest_HwKeystore", whTest_HwKeystore}, + {"whTest_HwKeystoreCrypto", whTest_HwKeystoreCrypto}, {"whTest_Lock", whTest_Lock}, {"whTest_Log", whTest_Log}, {"whTest_SheKeywrapInterop", whTest_SheKeywrapInterop}, From a1a8dd7c7d4887ee3547b0600ddca41d96ca1b21 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:39:12 -0600 Subject: [PATCH 06/23] clang-format alignment fixes --- .clang-format | 1 + test-refactor/wh_test_list.c | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.clang-format b/.clang-format index 67f5f699e..601a9404c 100755 --- a/.clang-format +++ b/.clang-format @@ -2,6 +2,7 @@ Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align +AlignArrayOfStructures: Left AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: true AlignEscapedNewlinesLeft: true diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c index f9d93f47a..02be48cfe 100644 --- a/test-refactor/wh_test_list.c +++ b/test-refactor/wh_test_list.c @@ -119,24 +119,24 @@ WH_TEST_DECL(whTest_AuthSetCredentials); WH_TEST_DECL(whTest_AuthRequestAuthorization); const whTestCase whTestsMisc[] = { - {"whTest_CacheHelpers", whTest_CacheHelpers}, - {"whTest_ClientDevId", whTest_ClientDevId}, - {"whTest_ClientIdBinding", whTest_ClientIdBinding}, - {"whTest_Comm", whTest_Comm}, - {"whTest_Dma", whTest_Dma}, - {"whTest_DmaNonCoherent", whTest_DmaNonCoherent}, - {"whTest_CryptoAffinity", whTest_CryptoAffinity}, - {"whTest_CryptoAesGcmDmaAad", whTest_CryptoAesGcmDmaAad}, - {"whTest_KeystoreReqSize", whTest_KeystoreReqSize}, + {"whTest_CacheHelpers", whTest_CacheHelpers }, + {"whTest_ClientDevId", whTest_ClientDevId }, + {"whTest_ClientIdBinding", whTest_ClientIdBinding }, + {"whTest_Comm", whTest_Comm }, + {"whTest_Dma", whTest_Dma }, + {"whTest_DmaNonCoherent", whTest_DmaNonCoherent }, + {"whTest_CryptoAffinity", whTest_CryptoAffinity }, + {"whTest_CryptoAesGcmDmaAad", whTest_CryptoAesGcmDmaAad }, + {"whTest_KeystoreReqSize", whTest_KeystoreReqSize }, {"whTest_MessageNvmTranslate", whTest_MessageNvmTranslate}, - {"whTest_MultiClient", whTest_MultiClient}, - {"whTest_HwKeystore", whTest_HwKeystore}, - {"whTest_HwKeystoreCrypto", whTest_HwKeystoreCrypto}, - {"whTest_Lock", whTest_Lock}, - {"whTest_Log", whTest_Log}, - {"whTest_SheKeywrapInterop", whTest_SheKeywrapInterop}, - {"whTest_SheNoNvm", whTest_SheNoNvm}, - {"whTest_SheUidCb", whTest_SheUidCb}, + {"whTest_MultiClient", whTest_MultiClient }, + {"whTest_HwKeystore", whTest_HwKeystore }, + {"whTest_HwKeystoreCrypto", whTest_HwKeystoreCrypto }, + {"whTest_Lock", whTest_Lock }, + {"whTest_Log", whTest_Log }, + {"whTest_SheKeywrapInterop", whTest_SheKeywrapInterop }, + {"whTest_SheNoNvm", whTest_SheNoNvm }, + {"whTest_SheUidCb", whTest_SheUidCb }, }; const size_t whTestsMiscCount = ARRAY_SIZE(whTestsMisc); From 635c108820f5c241482412857149cb85627ef6ca Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Wed, 14 Oct 2026 11:42:01 -0600 Subject: [PATCH 07/23] split out autopoll transport so it is useable by other single threaded tests in test-refactor/misc --- .../misc/wh_test_hwkeystore_crypto.c | 91 ++++------------ .../misc/wh_test_transport_mem_autopoll.c | 102 ++++++++++++++++++ .../misc/wh_test_transport_mem_autopoll.h | 66 ++++++++++++ 3 files changed, 190 insertions(+), 69 deletions(-) create mode 100644 test-refactor/misc/wh_test_transport_mem_autopoll.c create mode 100644 test-refactor/misc/wh_test_transport_mem_autopoll.h diff --git a/test-refactor/misc/wh_test_hwkeystore_crypto.c b/test-refactor/misc/wh_test_hwkeystore_crypto.c index a0ee43555..b9fffdc59 100644 --- a/test-refactor/misc/wh_test_hwkeystore_crypto.c +++ b/test-refactor/misc/wh_test_hwkeystore_crypto.c @@ -34,8 +34,9 @@ * the slot, and implements the wolfCrypt keystore callback that loads, clears * and describes slots. * - * Each client's transport runs its server whenever the client waits for a - * reply, so the blocking client APIs work without threads. + * Each client talks to its server over the autopoll mem transport + * (wh_test_transport_mem_autopoll.h), so the blocking client APIs work without + * threads. * * _whTest_HwKeyMap - GetInfo for every hardware key ID * _whTest_HwKeyPortKey - both clients use K2 but can't set or clear it @@ -90,6 +91,7 @@ #include "wh_test_common.h" #include "wh_test_list.h" +#include "wh_test_transport_mem_autopoll.h" #define BUFFER_SIZE 4096 #define FLASH_RAM_SIZE (1024 * 1024) @@ -476,72 +478,22 @@ static int _MockBoot(whHwKeystoreContext* hwKeystore, uint16_t owner) * Client/server setup * ------------------------------------------------------------------------ */ -/* Client transport that runs the server when the client waits for a reply */ -typedef struct { - whTransportMemClientContext mem[1]; - whServerContext* server; -} PumpClientContext; - -static int _PumpInit(void* c, const void* cf, whCommSetConnectedCb connectcb, - void* connectcb_arg) -{ - return wh_TransportMem_InitClear(((PumpClientContext*)c)->mem, cf, - connectcb, connectcb_arg); -} - -static int _PumpSend(void* c, uint16_t len, const void* data) -{ - return wh_TransportMem_SendRequest(((PumpClientContext*)c)->mem, len, data); -} - -static int _PumpRecv(void* c, uint16_t* out_len, void* data) -{ - PumpClientContext* pump = (PumpClientContext*)c; - int ret; - int serverRet; - - ret = wh_TransportMem_RecvResponse(pump->mem, out_len, data); - if (ret == WH_ERROR_NOTREADY) { - serverRet = wh_Server_HandleRequestMessage(pump->server); - ret = wh_TransportMem_RecvResponse(pump->mem, out_len, data); - /* The server replies to each request at once, so still waiting - * means it failed. Stop instead of waiting forever */ - if (ret == WH_ERROR_NOTREADY) { - ret = (serverRet != WH_ERROR_OK) ? serverRet : WH_ERROR_ABORTED; - } - } - return ret; -} - -static int _PumpCleanup(void* c) -{ - return wh_TransportMem_Cleanup(((PumpClientContext*)c)->mem); -} - -/* clang-format off */ -static const whTransportClientCb _pumpClientCb = { - .Init = _PumpInit, - .Send = _PumpSend, - .Recv = _PumpRecv, - .Cleanup = _PumpCleanup, -}; -/* clang-format on */ - /* One client and the server it talks to */ typedef struct { - whServerContext server[1]; - whClientContext client[1]; - whServerCryptoContext crypto[1]; - uint8_t reqBuf[BUFFER_SIZE]; - uint8_t respBuf[BUFFER_SIZE]; - whTransportMemConfig tmcf[1]; - whTransportServerCb tscb[1]; - whTransportMemServerContext tmsc[1]; - whCommServerConfig cs_conf[1]; - PumpClientContext pump[1]; - whCommClientConfig cc_conf[1]; - whClientConfig c_conf[1]; - whServerConfig s_conf[1]; + whServerContext server[1]; + whClientContext client[1]; + whServerCryptoContext crypto[1]; + uint8_t reqBuf[BUFFER_SIZE]; + uint8_t respBuf[BUFFER_SIZE]; + whTransportMemConfig tmcf[1]; + whTransportServerCb tscb[1]; + whTransportMemServerContext tmsc[1]; + whCommServerConfig cs_conf[1]; + whTransportClientCb tccb[1]; + whTransportMemAutopollClientContext tmcc[1]; + whCommClientConfig cc_conf[1]; + whClientConfig c_conf[1]; + whServerConfig s_conf[1]; } TestPair; typedef struct { @@ -591,10 +543,11 @@ static int _SetupPair(TestCtx* t, TestPair* p, uint16_t clientId, .transport_config = (void*)p->tmcf, .server_id = serverId, }; - p->pump->server = p->server; + p->tccb[0] = (whTransportClientCb)WH_TRANSPORT_MEM_AUTOPOLL_CLIENT_CB; + p->tmcc->server = p->server; p->cc_conf[0] = (whCommClientConfig){ - .transport_cb = &_pumpClientCb, - .transport_context = (void*)p->pump, + .transport_cb = p->tccb, + .transport_context = (void*)p->tmcc, .transport_config = (void*)p->tmcf, .client_id = clientId, }; diff --git a/test-refactor/misc/wh_test_transport_mem_autopoll.c b/test-refactor/misc/wh_test_transport_mem_autopoll.c new file mode 100644 index 000000000..1164d4873 --- /dev/null +++ b/test-refactor/misc/wh_test_transport_mem_autopoll.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/misc/wh_test_transport_mem_autopoll.c + * + * Client mem transport that has the server handle each request while the + * client waits for the reply. See wh_test_transport_mem_autopoll.h + */ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) + +#include +#include /* For NULL */ + +#include "wolfhsm/wh_error.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_transport_mem.h" + +#include "wh_test_transport_mem_autopoll.h" + +int whTransportMemAutopoll_Init(void* c, const void* cf, + whCommSetConnectedCb connectcb, + void* connectcb_arg) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + return wh_TransportMem_InitClear(ctx->mem, cf, connectcb, connectcb_arg); +} + +int whTransportMemAutopoll_Send(void* c, uint16_t len, const void* data) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + return wh_TransportMem_SendRequest(ctx->mem, len, data); +} + +int whTransportMemAutopoll_Recv(void* c, uint16_t* out_len, void* data) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + int ret; + int serverRet; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_TransportMem_RecvResponse(ctx->mem, out_len, data); + if ((ret == WH_ERROR_NOTREADY) && (ctx->server != NULL)) { + serverRet = wh_Server_HandleRequestMessage(ctx->server); + ret = wh_TransportMem_RecvResponse(ctx->mem, out_len, data); + /* The server replies to a request at once, so no reply means it failed + * or had no request to handle. Stop instead of waiting forever */ + if (ret == WH_ERROR_NOTREADY) { + ret = + ((serverRet != WH_ERROR_OK) && (serverRet != WH_ERROR_NOTREADY)) + ? serverRet + : WH_ERROR_ABORTED; + } + } + return ret; +} + +int whTransportMemAutopoll_Cleanup(void* c) +{ + whTransportMemAutopollClientContext* ctx = + (whTransportMemAutopollClientContext*)c; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + return wh_TransportMem_Cleanup(ctx->mem); +} + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ diff --git a/test-refactor/misc/wh_test_transport_mem_autopoll.h b/test-refactor/misc/wh_test_transport_mem_autopoll.h new file mode 100644 index 000000000..33e25a079 --- /dev/null +++ b/test-refactor/misc/wh_test_transport_mem_autopoll.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfHSM. + * + * wolfHSM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfHSM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wolfHSM. If not, see . + */ +/* + * test-refactor/misc/wh_test_transport_mem_autopoll.h + * + * Client mem transport for misc tests that run a client and its server in one + * thread. While the client waits for a reply, the transport has the server + * handle the request, so blocking client APIs work without a server thread. + */ +#ifndef WH_TEST_TRANSPORT_MEM_AUTOPOLL_H_ +#define WH_TEST_TRANSPORT_MEM_AUTOPOLL_H_ + +/* Pick up compile-time configuration */ +#include "wolfhsm/wh_settings.h" + +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFHSM_CFG_ENABLE_SERVER) + +#include + +#include "wolfhsm/wh_comm.h" +#include "wolfhsm/wh_server.h" +#include "wolfhsm/wh_transport_mem.h" + +typedef struct { + whTransportMemClientContext mem[1]; + /* Server to run while waiting for a reply. Set it before the client's + * first request. NULL makes this a plain mem transport */ + whServerContext* server; +} whTransportMemAutopollClientContext; + +int whTransportMemAutopoll_Init(void* c, const void* cf, + whCommSetConnectedCb connectcb, + void* connectcb_arg); +int whTransportMemAutopoll_Send(void* c, uint16_t len, const void* data); +int whTransportMemAutopoll_Recv(void* c, uint16_t* out_len, void* data); +int whTransportMemAutopoll_Cleanup(void* c); + +/* clang-format off */ +#define WH_TRANSPORT_MEM_AUTOPOLL_CLIENT_CB \ + { \ + .Init = whTransportMemAutopoll_Init, \ + .Send = whTransportMemAutopoll_Send, \ + .Recv = whTransportMemAutopoll_Recv, \ + .Cleanup = whTransportMemAutopoll_Cleanup, \ + } +/* clang-format on */ + +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER */ + +#endif /* !WH_TEST_TRANSPORT_MEM_AUTOPOLL_H_ */ From f14c503fbe521c0f706b95bb1d821869953d0ec1 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Wed, 14 Oct 2026 14:06:19 -0600 Subject: [PATCH 08/23] add missing test coverage --- .../misc/wh_test_hwkeystore_crypto.c | 238 +++++++++++++----- 1 file changed, 180 insertions(+), 58 deletions(-) diff --git a/test-refactor/misc/wh_test_hwkeystore_crypto.c b/test-refactor/misc/wh_test_hwkeystore_crypto.c index b9fffdc59..cf5f32ab6 100644 --- a/test-refactor/misc/wh_test_hwkeystore_crypto.c +++ b/test-refactor/misc/wh_test_hwkeystore_crypto.c @@ -23,13 +23,13 @@ * (WOLFHSM_CFG_HWKEYSTORE_CRYPTO). Two clients talk to two servers that share * one NVM and one hardware keystore, like one HSM serving two cores. * - * The servers' crypto callback is a mock AES engine modeled on the TC3xx HSM. - * It has eight write-only AES-128 key slots: + * The servers' crypto callback is a mock AES engine with eight write-only + * AES-128 key slots with some arbitrary restrictions: * K0, K1 device keys that no client can name * K2 a key the port loads at boot. Clients can use it but not set or * clear it - * K3, K4 loadable by clients - * K5-K7 used by the port for other things + * K3, K4 loadable by clients for generic use + * K5-K7 used as scratch keys for port-internal functionality * The mock runs each operation with wolfCrypt in software, using the key in * the slot, and implements the wolfCrypt keystore callback that loads, clears * and describes slots. @@ -44,7 +44,8 @@ * it in every AES mode and CMAC, standard and DMA * _whTest_HwKeyAllocation - picking a free key, explicit IDs, running out, * reloading, and another client's keys - * _whTest_HwKeyUsage - the copy keeps the source key's usage flags + * _whTest_HwKeyUsage - the copy keeps the source key's usage flags, + * and every AES and CMAC request checks them * _whTest_HwKeySource - evict keeps the copy, erase and revoke clear it * _whTest_HwKeyRefused - sources and targets the server or port refuses * _whTest_HwKeyClear - Clear and ClearAll @@ -511,7 +512,6 @@ typedef struct { whNvmConfig n_conf[1]; } TestCtx; -/* Static to keep the misc group's stack footprint small */ static TestCtx _testCtx; static uint8_t _flashMemory[FLASH_RAM_SIZE]; @@ -809,16 +809,16 @@ static int _Cbc(whClientContext* c, Aes* aes, int dma, int enc, return wh_Client_AesCbc(c, aes, enc, in, sz, out); } -static int _Ctr(whClientContext* c, Aes* aes, int dma, const uint8_t* in, - uint8_t* out, uint32_t sz) +static int _Ctr(whClientContext* c, Aes* aes, int dma, int enc, + const uint8_t* in, uint8_t* out, uint32_t sz) { #ifdef WOLFHSM_CFG_DMA if (dma) { - return wh_Client_AesCtrDma(c, aes, 1, in, sz, out); + return wh_Client_AesCtrDma(c, aes, enc, in, sz, out); } #endif (void)dma; - return wh_Client_AesCtr(c, aes, 1, in, sz, out); + return wh_Client_AesCtr(c, aes, enc, in, sz, out); } static int _Gcm(whClientContext* c, Aes* aes, int dma, int enc, @@ -866,13 +866,61 @@ static int _Cmac(whClientContext* c, whKeyId keyId, int dma, uint8_t* tag) return ret; } +/* One CMAC update request, standard or DMA */ +static int _CmacUpdate(whClientContext* c, Cmac* cmac, int dma, + const uint8_t* in, uint32_t sz) +{ + bool sent = false; + int ret; + +#ifdef WOLFHSM_CFG_DMA + if (dma) { + ret = wh_Client_CmacDmaUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, in, + sz, &sent); + if ((ret == 0) && sent) { + ret = wh_Client_CmacDmaUpdateResponse(c, cmac); + } + return ret; + } +#endif + (void)dma; + ret = wh_Client_CmacUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, in, sz, + &sent); + if ((ret == 0) && sent) { + ret = wh_Client_CmacUpdateResponse(c, cmac); + } + return ret; +} + +/* CMAC final request, standard or DMA */ +static int _CmacFinal(whClientContext* c, Cmac* cmac, int dma, uint8_t* tag, + uint32_t* tagSz) +{ + int ret; + +#ifdef WOLFHSM_CFG_DMA + if (dma) { + ret = wh_Client_CmacDmaFinalRequest(c, cmac); + if (ret == 0) { + ret = wh_Client_CmacDmaFinalResponse(c, cmac, tag, tagSz); + } + return ret; + } +#endif + (void)dma; + ret = wh_Client_CmacFinalRequest(c, cmac); + if (ret == 0) { + ret = wh_Client_CmacFinalResponse(c, cmac, tag, tagSz); + } + return ret; +} + /* CMAC of the test message over several requests, so the server picks up the * running state each time */ -static int _CmacSteps(whClientContext* c, whKeyId keyId, uint8_t* tag) +static int _CmacSteps(whClientContext* c, whKeyId keyId, int dma, uint8_t* tag) { Cmac cmac[1]; uint32_t tagSz = AES_BLOCK_SIZE; - bool sent = false; int ret; ret = wc_InitCmac_ex(cmac, NULL, 0, WC_CMAC_AES, NULL, NULL, c->devId); @@ -880,44 +928,75 @@ static int _CmacSteps(whClientContext* c, whKeyId keyId, uint8_t* tag) ret = wh_Client_CmacSetKeyId(cmac, keyId); } if (ret == 0) { - ret = wh_Client_CmacUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, _msg, - 40, &sent); - } - if ((ret == 0) && sent) { - ret = wh_Client_CmacUpdateResponse(c, cmac); - } - if (ret == 0) { - ret = wh_Client_CmacUpdateRequest(c, cmac, WC_CMAC_AES, NULL, 0, - _msg + 40, TEST_CMAC_SZ - 40, &sent); - } - if ((ret == 0) && sent) { - ret = wh_Client_CmacUpdateResponse(c, cmac); + ret = _CmacUpdate(c, cmac, dma, _msg, 40); } if (ret == 0) { - ret = wh_Client_CmacFinalRequest(c, cmac); + ret = _CmacUpdate(c, cmac, dma, _msg + 40, TEST_CMAC_SZ - 40); } if (ret == 0) { - ret = wh_Client_CmacFinalResponse(c, cmac, tag, &tagSz); + ret = _CmacFinal(c, cmac, dma, tag, &tagSz); } (void)wc_CmacFree(cmac); return ret; } -/* Result of a one-block ECB request with keyId */ -static int _TryEcb(whClientContext* c, whKeyId keyId, int enc) +/* AES modes for _TryAes */ +enum { + MODE_ECB = 0, + MODE_CBC, + MODE_CTR, + MODE_GCM, + MODE_COUNT, +}; + +/* Result of one request in mode with keyId. A GCM decrypt uses the software + * ciphertext and tag in e, so it succeeds with the right key */ +static int _TryAes(whClientContext* c, whKeyId keyId, int mode, int dma, + int enc, const Expected* e) { Aes aes[1]; - uint8_t out[AES_BLOCK_SIZE]; + uint8_t out[TEST_GCM_SZ]; + uint8_t tag[AES_BLOCK_SIZE]; int ret; - ret = _AesStart(aes, keyId, NULL); + ret = _AesStart(aes, keyId, + ((mode == MODE_CBC) || (mode == MODE_CTR)) ? _iv : NULL); if (ret == 0) { - ret = wh_Client_AesEcb(c, aes, enc, _msg, AES_BLOCK_SIZE, out); + switch (mode) { + case MODE_ECB: + ret = _Ecb(c, aes, dma, enc, _msg, out, AES_BLOCK_SIZE); + break; + case MODE_CBC: + ret = _Cbc(c, aes, dma, enc, _msg, out, AES_BLOCK_SIZE); + break; + case MODE_CTR: + ret = _Ctr(c, aes, dma, enc, _msg, out, AES_BLOCK_SIZE); + break; + case MODE_GCM: + if (enc) { + ret = _Gcm(c, aes, dma, 1, _msg, out, TEST_GCM_SZ, + TEST_GCM_IV_SZ, NULL, tag); + } + else { + ret = _Gcm(c, aes, dma, 0, e->gcm, out, TEST_GCM_SZ, + TEST_GCM_IV_SZ, e->gcmTag, NULL); + } + break; + default: + ret = WH_ERROR_BADARGS; + break; + } } wc_AesFree(aes); return ret; } +/* Result of a one-block ECB request with keyId */ +static int _TryEcb(whClientContext* c, whKeyId keyId, int enc) +{ + return _TryAes(c, keyId, MODE_ECB, 0, enc, NULL); +} + /* Result of a one-shot CMAC request with keyId */ static int _TryCmac(whClientContext* c, whKeyId keyId) { @@ -989,13 +1068,13 @@ static int _CheckModes(whClientContext* c, whKeyId keyId, const uint8_t* key, /* CTR, in two requests that split a block */ ops = _mock.ops; WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); - WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, _msg, out, 20)); + WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, 1, _msg, out, 20)); WH_TEST_RETURN_ON_FAIL( - _Ctr(c, aes, dma, _msg + 20, out + 20, TEST_CTR_SZ - 20)); + _Ctr(c, aes, dma, 1, _msg + 20, out + 20, TEST_CTR_SZ - 20)); WH_TEST_ASSERT_RETURN(memcmp(out, e.ctr, TEST_CTR_SZ) == 0); wc_AesFree(aes); WH_TEST_RETURN_ON_FAIL(_AesStart(aes, keyId, _iv)); - WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, out, back, TEST_CTR_SZ)); + WH_TEST_RETURN_ON_FAIL(_Ctr(c, aes, dma, 1, out, back, TEST_CTR_SZ)); WH_TEST_ASSERT_RETURN(memcmp(back, _msg, TEST_CTR_SZ) == 0); wc_AesFree(aes); CHECK_RAN(ops, hw); @@ -1025,11 +1104,11 @@ static int _CheckModes(whClientContext* c, whKeyId keyId, const uint8_t* key, ops = _mock.ops; WH_TEST_RETURN_ON_FAIL(_Cmac(c, keyId, dma, tag)); WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); - if (!dma) { - memset(tag, 0, sizeof(tag)); - WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, keyId, tag)); - WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); - } + CHECK_RAN(ops, hw); + ops = _mock.ops; + memset(tag, 0, sizeof(tag)); + WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, keyId, dma, tag)); + WH_TEST_ASSERT_RETURN(memcmp(tag, e.cmac, AES_BLOCK_SIZE) == 0); CHECK_RAN(ops, hw); return WH_ERROR_OK; @@ -1225,35 +1304,78 @@ static int _whTest_HwKeyAllocation(TestCtx* t) return WH_ERROR_OK; } -/* The copy keeps the source key's usage flags */ +/* The copy keeps the source key's usage flags. Each AES request needs the + * usage for its direction and CMAC needs sign or verify, standard and DMA */ static int _whTest_HwKeyUsage(TestCtx* t) { whClientContext* c = t->pair[0].client; + Expected e; + uint8_t tag[AES_BLOCK_SIZE]; + whKeyId src1; + whKeyId src2; + whKeyId hw1; + whKeyId hw2; whKeyId src; whKeyId hwId; + int mode; + int dma; - /* Encrypt only: no decrypt and no CMAC */ - src = WH_KEYID_ERASED; + WH_TEST_RETURN_ON_FAIL(_SwExpected(_keyA, &e)); + + /* Encrypt only and decrypt only: each works one way in every AES mode, and + * neither can do CMAC */ + src1 = WH_KEYID_ERASED; + src2 = WH_KEYID_ERASED; WH_TEST_RETURN_ON_FAIL( - _Cache(c, WH_NVM_FLAGS_USAGE_ENCRYPT, _keyA, sizeof(_keyA), &src)); - WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src, &hwId)); - WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, WH_KEYID_ID(hwId), 1, + _Cache(c, WH_NVM_FLAGS_USAGE_ENCRYPT, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_DECRYPT, _keyA, sizeof(_keyA), &src2)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src1, &hw1)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src2, &hw2)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, WH_KEYID_ID(hw1), 1, WH_NVM_FLAGS_USAGE_ENCRYPT, CLIENT1_ID)); - WH_TEST_RETURN_ON_FAIL(_TryEcb(c, hwId, 1)); - WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryEcb(c, hwId, 0)); - WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryCmac(c, hwId)); - WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c, hwId)); - WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + WH_TEST_RETURN_ON_FAIL(_CheckInfo(c, WH_KEYID_ID(hw2), 1, + WH_NVM_FLAGS_USAGE_DECRYPT, CLIENT1_ID)); + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + for (mode = 0; mode < MODE_COUNT; mode++) { + WH_TEST_RETURN_ON_FAIL(_TryAes(c, hw1, mode, dma, 1, &e)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, hw1, mode, dma, 0, &e)); + WH_TEST_RETURN_ON_FAIL(_TryAes(c, hw2, mode, dma, 0, &e)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, hw2, mode, dma, 1, &e)); + } + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _Cmac(c, hw1, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _CmacSteps(c, hw1, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _Cmac(c, hw2, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _CmacSteps(c, hw2, dma, tag)); + } + WH_TEST_RETURN_ON_FAIL(_ClearAll(t)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src2)); - /* Verify only: CMAC but no AES */ - src = WH_KEYID_ERASED; + /* Sign only and verify only: either can do CMAC but not AES */ + src1 = WH_KEYID_ERASED; + src2 = WH_KEYID_ERASED; WH_TEST_RETURN_ON_FAIL( - _Cache(c, WH_NVM_FLAGS_USAGE_VERIFY, _keyA, sizeof(_keyA), &src)); - WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src, &hwId)); - WH_TEST_RETURN_ON_FAIL(_TryCmac(c, hwId)); - WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryEcb(c, hwId, 1)); - WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c, hwId)); - WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src)); + _Cache(c, WH_NVM_FLAGS_USAGE_SIGN, _keyA, sizeof(_keyA), &src1)); + WH_TEST_RETURN_ON_FAIL( + _Cache(c, WH_NVM_FLAGS_USAGE_VERIFY, _keyA, sizeof(_keyA), &src2)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src1, &hw1)); + WH_TEST_RETURN_ON_FAIL(_Load(c, HW(0), src2, &hw2)); + for (dma = 0; dma < WH_TEST_DMA_MODE_CNT; dma++) { + WH_TEST_RETURN_ON_FAIL(_Cmac(c, hw1, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, hw1, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_Cmac(c, hw2, dma, tag)); + WH_TEST_RETURN_ON_FAIL(_CmacSteps(c, hw2, dma, tag)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, hw1, MODE_ECB, dma, 1, &e)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _TryAes(c, hw2, MODE_ECB, dma, 0, &e)); + } + WH_TEST_RETURN_ON_FAIL(_ClearAll(t)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src1)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c, src2)); /* A key with no usage could never be used, so it isn't loaded */ src = WH_KEYID_ERASED; From dcf03a686ea5f0f173c63530e2964df439feb083 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Wed, 14 Oct 2026 19:21:17 -0600 Subject: [PATCH 09/23] WIP: keystore doc updates --- docs/src/5-Features.md | 354 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 334 insertions(+), 20 deletions(-) diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 29699cc14..63dcd3672 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -23,7 +23,11 @@ This chapter provides a detailed overview of the high level features that wolfHS - [Global Keys](#global-keys) - [Wrapped Keys](#wrapped-keys) - [Hardware-Only Keys](#hardware-only-keys) - - [Supported Components](#supported-components) + - [Hardware Key Overview](#hardware-key-overview) + - [Supported Functionality](#supported-functionality) + - [Using Hardware Keys from a Client](#using-hardware-keys-from-a-client) + - [Server Architecture for Hardware Keys](#server-architecture-for-hardware-keys) + - [Port and Core Library Responsibilities](#port-and-core-library-responsibilities) - [Key Usage Policies](#key-usage-policies) - [Certificate Management](#certificate-management) - [Trusted Root Storage](#trusted-root-storage) @@ -140,12 +144,12 @@ The same ID can name a key the client just cached, one provisioned into NVM at t ### 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. +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. With [hardware keys](#hardware-only-keys), the key can also stay in the accelerator: the server passes it to the port by reference and never reads it. Clients control whether a given crypto request should prefer hardware or software execution through the **crypto affinity** API. This feature is compiled in only when `WOLFHSM_CFG_CRYPTO_AFFINITY` is defined; when it is not, the server always uses its configured device ID and the client API below is unavailable. 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. +- `WH_CRYPTO_AFFINITY_SW`: the server always executes the operation using wolfCrypt's software implementation, bypassing any registered hardware device. Requests that use a [hardware key](#hardware-only-keys) fail with `WH_ERROR_NOTFOUND`, since software has no copy of the key. 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. @@ -433,7 +437,7 @@ See the `whnvmtool` README for the concrete flag value and provisioning mechanic **Never grant a KEK any usage beyond `WRAP`** — in particular not `WH_NVM_FLAGS_USAGE_ENCRYPT` or `WH_NVM_FLAGS_USAGE_DECRYPT`, and not the catch-all `WH_NVM_FLAGS_USAGE_ANY`. The server enforces usage flags on every wolfCrypt request through the crypto callback (see [Key Usage Policies](#key-usage-policies)), and that check is the only thing stopping a client from using a KEK as an ordinary cipher key. A wrap blob is just AES-GCM ciphertext under the KEK, so a KEK that also carries `USAGE_DECRYPT` can be fed to a raw AES-GCM decrypt under its keyId — returning the wrapped key's plaintext directly and defeating wrapped-key confidentiality regardless of every other control. A KEK never needs encrypt or decrypt usage to function, so `WRAP`-only is both fully functional and the only safe choice. -**Give each key one role.** A key should be a KEK *or* a general-purpose encryption/signing/derivation key, never both; provision separate keys if an application needs both. Sharing usage across roles is exactly what the crypto callback's usage enforcement exists to prevent. A [hardware KEK](#hardware-only-keys) sidesteps this class of mistake by construction: hardware-only keys carry no usage metadata and are rejected in every operation except keywrap, so they can never be used as a cipher key regardless of configuration. +**Give each key one role.** A key should be a KEK *or* a general-purpose encryption/signing/derivation key, never both; provision separate keys if an application needs both. Sharing usage across roles is exactly what the crypto callback's usage enforcement exists to prevent. A [hardware KEK](#hardware-only-keys) sidesteps this class of mistake by construction: only the keywrap operations read a KEK, through the port's `GetKey` callback, so it can never be used as a cipher key. With `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`, where hardware keys can also be cipher keys, this holds as long as the port keeps KEK numbers apart from the numbers it accepts as cipher keys (see [Port and Core Library Responsibilities](#port-and-core-library-responsibilities)). #### Cryptographic Domain Separation @@ -445,28 +449,338 @@ The tag is not stored in or transmitted with the blob; the server and every blob ### Hardware-Only Keys -Some platforms provide key material that the HSM core can read but that never lives in the wolfHSM keystore at all — KEKs burned into OTP or fuses, keys held by an SoC key-management block, or other hardware-provisioned secrets. The optional **hardware keystore front-end** (`WOLFHSM_CFG_HWKEYSTORE`) lets clients reference such keys as KEKs in [wrapped-key](#wrapped-keys) operations while guaranteeing the material never enters the key cache, never touches NVM, and is never returned to a client. +Many HSM platforms keep keys inside the crypto hardware itself: key slots in an AES accelerator, KEKs burned into OTP or fuses, or keys held by an SoC key manager. wolfHSM uses the hardware key abstraction to use these **hardware keys** without requiring server or client ownership of the key material. A client can name a hardware key by ID, just like any other key. The server will check the request, then dispatch to port-specific code to do useful things with the key. -The front-end (`wolfhsm/wh_hwkeystore.h`) is a platform-agnostic, fully configurable abstraction over the hardware keystore, following the same backend-callback-table paradigm as other elements of the core library. A backend provides a `whHwKeystoreCb` callback table whose required `GetKey` callback copies a requested key's bytes into a caller-provided buffer on demand, plus optional `Init` and `Cleanup` callbacks for backend setup/teardown. The callback table, an opaque backend context and config (for backend-specific state), and a lock (serializing callback invocations when the backing hardware is shared across server threads under `WOLFHSM_CFG_THREADSAFE`) are described by a `whHwKeystoreConfig` and held in a `whHwKeystoreContext`. The server application initializes the context once with `wh_HwKeystore_Init()` — which binds the callback table and invokes the backend `Init` callback if present — and binds it to one or more server contexts through the optional `hwKeystore` member of `whServerConfig`. +Two build options enable the feature: -Clients designate a hardware-only key by setting `WH_KEYID_CLIENT_HW_FLAG` (bit 10) in the request keyId, normally via the `WH_CLIENT_KEYID_MAKE_HW()` macro in `wolfhsm/wh_client.h`. The server translates the flag to the internal key type `WH_KEYTYPE_HW` and routes accordingly: +- `WOLFHSM_CFG_HWKEYSTORE` adds the hardware keystore front end and lets a hardware key be the KEK in the [key wrap](#wrapped-keys) operations. +- `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` lets clients use hardware keys in crypto operations, and copy their own keystore keys into hardware keys. It needs `WOLFHSM_CFG_HWKEYSTORE`, plus `WOLF_CRYPTO_CB_KEYSTORE` and `WOLF_PRIVATE_KEY_ID` in the wolfCrypt build. -- In the keywrap operations (key wrap, wrap-export, unwrap-and-export, unwrap-and-cache, and data wrap/unwrap), a hardware-only KEK id causes the server to fetch the KEK from the hardware keystore into a local stack buffer via `wh_HwKeystore_GetKey()`, perform the AES-GCM operation, and zeroize the buffer before returning. The KEK is never cached and never appears in any response. An unwrap-and-cache under a hardware KEK still caches the unwrapped payload since the payload is an ordinary key; only the KEK is hardware-resident. -- Hardware-only keyIds are rejected in every other operation with `WH_ERROR_ACCESS`. +[Supported Functionality](#supported-functionality) lists which wolfHSM operations accept hardware keys under each option. -Because hardware-only keys carry no `whNvmMetadata`, the [usage-flag policy machinery](#key-usage-policies) does not apply to them. The largest key the keywrap path will fetch from the backend is bounded by `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` (default 32 bytes, sized for an AES-256 KEK), which should be respected (or enforced), by the port layer. +#### Hardware Key Overview -#### Supported Components +A hardware key ID follows the same client-facing naming scheme as other keystore key IDs: it combines a key number (1-255) with flag bits that identify the key type. For a hardware key, the hardware flag (`WH_KEYID_CLIENT_HW_FLAG`) is set, normally by using the `WH_CLIENT_KEYID_MAKE_HW(n)` helper macro. -Hardware-only keys can be consumed by exactly one wolfHSM component today — the **[keywrap](#wrapped-keys) API**, where a `WH_CLIENT_KEYID_MAKE_HW()` id names the KEK. The supported operations are: +The port defines what each hardware key ID represents. It may identify a fixed key, such as one stored in OTP memory or fuses, or a logical key-storage location whose contents can be loaded or changed, such as a "key slot" or input register for a hardware accelerator. Unlike keystore key IDs, hardware key numbers are shared across clients: hardware key `n` refers to the same hardware key for every client, while the server tracks which client owns it. -- **Key wrap** (`wh_Client_KeyWrap`) — wrap a client-supplied key under the hardware-resident KEK. -- **Key wrap-export** (`wh_Client_KeyWrapExport`) — wrap a server-resident key named by id under the hardware KEK, without exposing its plaintext. A hardware KEK is inherently a [trusted KEK](#trusted-keks), which this operation requires. -- **Key unwrap-and-export** (`wh_Client_KeyUnwrapAndExport`) — unwrap a blob and return the recovered key to the client. -- **Key unwrap-and-cache** (`wh_Client_KeyUnwrapAndCache`) — unwrap a blob into the key cache; only the KEK is hardware-resident, while the unwrapped payload becomes an ordinary cached key. -- **Data wrap and unwrap** (`wh_Client_DataWrap`, `wh_Client_DataUnwrap`) — apply the same authenticated encryption to arbitrary application data. +To control which client may use which hardware key, and for what, the server keeps its own **record** of each hardware key that may be used in crypto operations. A record holds the key's owner (a client ID, or 0 if every client may use it) and the usage flags it allows. For a key copied from the keystore, it also holds the keystore key it came from. The server checks the record on every crypto request that names a hardware key: a client can't use another client's key, every operation needs the matching usage flag, and a hardware key with no record can't be used in crypto operations, whatever the hardware holds. Records are kept in server RAM, up to `WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS` of them (default 8). [Server Architecture for Hardware Keys](#server-architecture-for-hardware-keys) describes the runtime key record in more detail. -No other component can name a hardware-only key: a direct wolfCrypt operation through the crypto callback, or any keystore lifecycle operation (cache, export, commit, evict, erase, revoke), is rejected with `WH_ERROR_ACCESS`. **Support is limited to keywrap KEK usage for now** — broader use, such as referencing a hardware-only key directly in a crypto operation, may be added in a future release. +In general, there are three usage paterns for hardware keys: + +- **Client-loaded keys.** A client *loads* a hardware key by asking the server to copy one of its keystore keys into it, with `wh_Client_HwKeystoreLoadFromKeystore()`. The server writes the key into the hardware through the port and creates the record. This is the only way a client can put a key into hardware. A key loaded with the [global flag](#global-keys) added to its hardware key ID is shared and useable across clients. +- **Port keys.** The port can put a key into the hardware itself, for example a device-specific key derived at boot, and then *registers* it with the server, which creates the record. Clients can use a port-loaded key but can never replace or clear it. +- **Hardware KEKs.** Keys fixed in the hardware, such as keys in OTP or fuses, that the port offers as KEKs for the [key wrap](#wrapped-keys) operations. The server keeps no record of them, and they can only be used as KEKs. + +| | Client-loaded key | Port key | Hardware KEK | +|---|---|---|---| +| Used as | a key in crypto operations | a key in crypto operations | a KEK in key wrap operations | +| Usable by | the loading client, or every client if it is shared | every client, or one client, as the port registered it | the clients the port's `GetKey` callback allows | +| Usage flags | copied from the source keystore key | set by the port when it registers the key | none; the port decides | +| Lifetime | until its owner clears it (any client can clear a shared key), its source key is erased or revoked, or the HSM resets | until the HSM resets; the port registers it again at every boot | as long as the hardware holds it | + +The layers stack up like this: + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ Client application │ +│ wolfCrypt or wh_Client_* calls, key named WH_CLIENT_KEYID_MAKE_HW(n) │ +└─────────────────────────────┬────────────────────────────────────────────┘ + │ requests carry key IDs, not key bytes +┌─────────────────────────────▼────────────────────────────────────────────┐ +│ wolfHSM server (core library) │ +│ keystore ── load (copy) ──▶ hardware keystore front end: │ +│ (cache, NVM) one record per key: owner, usage, source │ +│ crypto handlers: check the key's record, then hand the key │ +│ to wolfCrypt by reference. Key wrap: fetch the KEK through GetKey │ +└─────────────────────────────┬────────────────────────────────────────────┘ + │ wolfCrypt crypto callback (server devId) + │ and GetKey callback (KEKs) +┌─────────────────────────────▼────────────────────────────────────────────┐ +│ Server Port │ +│ keystore callbacks: write, clear and describe a hardware key │ +│ algorithm callbacks: run the operation with the hardware key │ +│ GetKey callback: copy out a KEK for key wrap, or refuse │ +│ at boot: place keys into the hardware and register them │ +└─────────────────────────────┬────────────────────────────────────────────┘ + │ +┌─────────────────────────────▼────────────────────────────────────────────┐ +│ Hardware: crypto accelerator and its key slots, OTP or fuse KEKs │ +└──────────────────────────────────────────────────────────────────────────┘ +``` + +Client applications simply name keys. The wolfHSM server (core library) decides who may use which key and how. The port-specific logic, through crypto callbacks, drives the hardware and decides what the hardware can hold. [Port and Core Library Responsibilities](#port-and-core-library-responsibilities) lists the split in full. + +#### Supported Functionality + +Clients name a hardware key in the same calls as any other key, through the wolfCrypt API (via the client's crypto callback) or the native client API. These wolfHSM operations accept hardware keys: + +| Functionality | Role of the hardware key | Usage flag the key needs | Build option | +|---|---|---|---| +| AES-ECB, AES-CBC, AES-CTR and AES-GCM | the cipher key, in standard and DMA requests | `WH_NVM_FLAGS_USAGE_ENCRYPT` to encrypt, `WH_NVM_FLAGS_USAGE_DECRYPT` to decrypt | `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` | +| AES-CMAC, one-shot or multi-step | the MAC key, in standard and DMA requests | `WH_NVM_FLAGS_USAGE_SIGN` or `WH_NVM_FLAGS_USAGE_VERIFY` | `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` | +| Key wrap: key wrap, wrap-export, unwrap-and-export, unwrap-and-cache, data wrap and data unwrap | the KEK | none; the port decides which keys may be KEKs | `WOLFHSM_CFG_HWKEYSTORE`, with `WOLFHSM_CFG_KEYWRAP` | + +- The crypto operations take client-loaded keys and port keys. +- Key wrap takes hardware KEKs. A hardware KEK is always a [trusted KEK](#trusted-keks), so it can be used for wrap-export and unwrap-and-cache, which require one. +- All other wolfHSM functionality refuses hardware key IDs with `WH_ERROR_ACCESS`. This includes the algorithms that don't support hardware keys yet, such as RSA and ECC, and the keystore operations (cache, export, commit, evict, erase, revoke). + +#### Using Hardware Keys from a Client + +Hardware key IDs can be used like any other key ID. Bind it to a wolfCrypt object with the algorithm's `SetKeyId` function, such as `wh_Client_AesSetKeyId()`, and call wolfCrypt as usual, or pass the object to the native client functions, including their DMA forms. [Supported Functionality](#supported-functionality) lists the operations that accept hardware keys; all others refuse them with `WH_ERROR_ACCESS`. + +Usage patterns through the client API depend on if the key is intended to be loaded from the keystore or if it is loaded by the port. + +##### Example: AES accelerator using a keystore key + +Take an AES accelerator with one input key slot. Assume the port documentation defines that slot as hardware key 1, so clients can name it. A client holds an AES key in the keystore, here keystore key 5, provisioned in NVM with `WH_NVM_FLAGS_USAGE_ENCRYPT`. To use that key on the accelerator, the client loads it into the slot, then names the slot in the AES operation: + +```c +#define AES_KEY_SLOT WH_CLIENT_KEYID_MAKE_HW(1) /* defined by the port */ +#define APP_AES_KEY 5 /* keystore key, in NVM */ + +Aes aes; +uint8_t iv[AES_BLOCK_SIZE]; +uint8_t plain[2 * AES_BLOCK_SIZE]; +uint8_t cipher[sizeof(plain)]; + +/* Copy the keystore key into the accelerator's key slot */ +wh_Client_HwKeystoreLoadFromKeystore(client, AES_KEY_SLOT, APP_AES_KEY, NULL); + +/* Name the slot like any key ID. The request carries only the ID, and the + * accelerator runs the operation with the key in its slot */ +wc_AesInit(&aes, NULL, WH_CLIENT_DEVID(client)); +wh_Client_AesSetKeyId(&aes, AES_KEY_SLOT); +wc_AesSetIV(&aes, iv); +wc_AesCbcEncrypt(&aes, cipher, plain, sizeof(plain)); +wc_AesFree(&aes); + +/* Clear the slot when done, so other clients can load it */ +wh_Client_HwKeystoreClear(client, AES_KEY_SLOT); +``` + +`wh_Client_HwKeystoreLoadFromKeystore()` takes the hardware key to load and the keystore key to copy. The server reads the keystore key, has the port write it into the slot, and records the client as the slot's owner. Until the owner clears it, no other client can use or load the slot, and the owner can load it again to replace its key. The key bytes stay inside the server, going from the keystore directly to the port's hardware logic and nowhere else. + +If a port offers several interchangeable "scratch" slots, a client can pass ID 0 (`WH_CLIENT_KEYID_MAKE_HW(0)`) instead of a fixed slot. The server then picks the first empty slot the port accepts and returns its ID in the last argument, for the client to use in its operations. + +If the port instead has a fixed read-only or use-only keyslot that can be used with the accelerator, either because the key itself is fixed in hardware, or because the port installs the key at startup, then the entire keystore loading API call can be ommitted, and the AES operation can directly use the HW keyId in question. The nuances of what functionality extends to which keys is entirely port-specific and should be detailed in the port's documentation. + +##### Shared Hardware Keys + +Two kinds of hardware keys can be used by every client: + +- A key loaded with the global flag added: `WH_CLIENT_KEYID_MAKE_GLOBAL(WH_CLIENT_KEYID_MAKE_HW(n))`. This needs [global keys](#global-keys) (`WOLFHSM_CFG_GLOBAL_KEYS`). Any client can use it, with or without the global flag, and any client can clear it using the global flag. +- A port key that the port shares with all clients. A port can also register a key for one client, and only that client can use it. No client can replace or clear a port key. + +##### Inspecting and Sharing Hardware Keys +`wh_Client_HwKeystoreGetInfo()` fills a `whHwKeystoreInfo` for a hardware key: + +- `keyType` and `keyBits`: what the port says the key holds. `keyType` is a wolfCrypt `enum wc_KeyStoreKeyType` value, such as `WC_KEYSTORE_KEY_AES`, and is `WC_KEYSTORE_KEY_NONE` (0) when the key is empty. +- `flags` and `owner`: the usage flags and owning client the server recorded. The owner is 0 for a shared key. + +Any client can describe any hardware key the port has, including other clients' keys. `wh_Client_HwKeystoreClear()` clears one key the client owns. `wh_Client_HwKeystoreClearAll()` clears every key the client loaded without the global flag. Neither clears a port key. Each of these calls blocks until the server replies. All except `wh_Client_HwKeystoreClearAll()` also come as `Request`/`Response` pairs for [non-blocking use](#blocking-and-non-blocking-interfaces). + +##### Usage Restrictions + +- The hardware key gets the source key's usage flags, and the server checks them on every request, as it does for keystore keys. [Supported Functionality](#supported-functionality) lists the flag each operation needs. A source key with no usage flags, such as a revoked key, can't be loaded. +- Erasing or revoking the source key clears every hardware key loaded from it. Evicting it does not, so a client can evict the source from the cache once the hardware key is loaded. +- The source must be a keystore key the client can reach: its own, or a [global key](#global-keys). A `WH_NVM_FLAGS_NONEXPORTABLE` key can be loaded, because its bytes go to the hardware and not to a client. A [trusted KEK](#trusted-keks) can't be loaded. The key must fit in `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` bytes, and the port may accept only some key sizes. + + +##### Hardware Key Errors +Hardware key requests can return: + +| Error | Meaning | +|---|---| +| `WH_ERROR_ACCESS` | The key belongs to another client, or is a port key that can't be replaced or cleared. Or the source is a trusted KEK. Or the operation doesn't support hardware keys, which includes every crypto operation on a server built without `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`. | +| `WH_ERROR_USAGE` | The key's usage flags don't allow the operation, or the source key has no usage flags. | +| `WH_ERROR_NOTFOUND` | The server has no record of the hardware key (no client loaded it and the port didn't register it), or the source key doesn't exist. Also returned when the server has no hardware keystore, and for crypto requests when it has no crypto device for them, including requests sent with `WH_CRYPTO_AFFINITY_SW`. | +| `WH_ERROR_NOSPACE` | No empty hardware key for ID 0, the server's table of hardware keys is full, or the source key is larger than `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE`. | +| `WH_ERROR_BADARGS` | ID 0 where a specific key is needed, or a source ID with the hardware or wrapped flag. | +| `WH_ERROR_NOTIMPL` | The server was built without `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` (load, clear and info requests). | +| a port error | The port refused the key number or the key, for example a size the slot can't hold, or the hardware failed. The port's error code is returned as is. | + +##### Hardware keys and key-wrapping + +Pass `WH_CLIENT_KEYID_MAKE_HW(n)` as the KEK ID to any [key wrap](#wrapped-keys) operation. The server reads such a KEK through the port's `GetKey` callback, and this needs only `WOLFHSM_CFG_HWKEYSTORE`. Hardware KEKs carry no `whNvmMetadata`, so the [usage flags](#key-usage-policies) don't apply to them; the port decides which keys may be KEKs. Only the KEK is in hardware: a key recovered by unwrap-and-cache becomes an ordinary cached key. + +#### Server Architecture for Hardware Keys + +On the server, a request with a hardware key passes through the core library (the request handlers, then the hardware keystore front end), then wolfCrypt, then the port callbacks, which drive the hardware. The diagram below shows the three paths through these layers. Each box is marked with the code that owns it. + +``` + manage hardware keys use a hardware key key wrap with a + HW_LOAD, HW_CLEAR, in a crypto request hardware KEK + HW_INFO; erase or (standard or DMA) + revoke of a source + │ │ │ +┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ +│ [core] request handlers │ +│ client keyId -> server keyId: TYPE=HW, USER=client (0 if shared), ID=n │ +│ HW_LOAD reads the source key under the NVM lock, with the keystore's │ +│ policy checks │ +└───────────┬───────────────────────────────┬───────────────────────────────┬──────────────┘ + │ │ │ +┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ +│ [core] hardware keystore front end (one context, shared by all servers) │ +│ Load, Clear, ClearAll, CheckUse: owner and usage, GetKey │ +│ ClearSource, GetInfo KeyRef: the key reference │ +│ record table: { keyId with owner, source keyId, usage, size } x MAX_KEYS │ +└───────────┬───────────────────────────────┬───────────────────────────────┬──────────────┘ + │ wc_KeyStore_ImportPlain │ init by reference, e.g. │ + │ wc_KeyStore_Delete │ wc_AesInit_Id(ref), then the │ + │ wc_KeyStore_GetInfo │ usual wolfCrypt call │ +┌───────────▼───────────────────────────────▼─────────────────────┐ │ +│ [wolfCrypt] crypto callback dispatch to whServerConfig.devId │ │ +└───────────┬───────────────────────────────┬─────────────────────┘ │ + │ WC_ALGO_TYPE_KEYSTORE │ algorithm request, such as │ cb->GetKey + │ │ WC_ALGO_TYPE_CIPHER │ +┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ +│ [port] crypto callback [port] GetKey callback │ +│ IMPORT_PLAIN: write a slot reference -> slot, run the op copy a KEK's bytes, │ +│ DELETE: clear a slot with the slot's key, update or refuse │ +│ GET_INFO: type and size the running state │ +└───────────┬───────────────────────────────┬───────────────────────────────┬──────────────┘ + │ │ │ +┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ +│ Hardware: crypto accelerator and its key slots; OTP or fuse KEKs │ +└──────────────────────────────────────────────────────────────────────────────────────────┘ +``` + +**Request handlers.** When a request arrives, the server turns the client key ID into a full server key ID (see [Key Cache, Key IDs, and NVM Backing Store](#key-cache-key-ids-and-nvm-backing-store)). The hardware flag selects TYPE `WH_KEYTYPE_HW`, USER is the calling client (or 0 with the global flag), and ID is the hardware key number. Keystore operations refuse this type outright, so a hardware key ID can only reach the code paths in the diagram. + +**The front end and its record table.** The front end (`wolfhsm/wh_hwkeystore.h`) is a `whHwKeystoreContext` that the server application creates, initializes once with `wh_HwKeystore_Init()`, and passes to every server context through `whServerConfig.hwKeystore`. All server contexts that share it, typically one per client connection, see the same hardware keys. The context holds the port's `whHwKeystoreCb` callback table and, with `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`, the table of [records](#hardware-key-overview) (`whHwKeystoreKey`) for the hardware keys clients may use in crypto requests. There are `WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS` records (default 8), each with these fields: + +- `id`: the server key ID. USER is the owning client (0 if shared) and ID is the hardware key number. There is at most one record per number, whoever owns it. +- `srcId`: the keystore key the hardware key was loaded from, so erase and revoke can find its copies. Port keys have none. +- `flags`: the usage flags the key allows. Port keys are also marked `WH_NVM_FLAGS_NONDESTROYABLE`, which stops clients from replacing or clearing them. +- `len`: the key size in bytes. + +The table holds no key material; It is simply a directory enumerating the keys that the port is aware of and able to serve. It lives in RAM, so it is cleared after every reset. A crypto request with a hardware key passes `wh_HwKeystore_CheckUse()` only if a record exists for the number, its owner is the caller or 0, and its flags include every usage the operation needs. + +**Key references: how wolfCrypt is told which key to use.** wolfCrypt's `WOLF_PRIVATE_KEY_ID` feature lets a key object name a key held by a device instead of holding the key bytes. wolfCrypt functions like `wc_AesInit_Id()` and `wc_InitCmac_Id()` store an opaque ID in the object (`aes->id`, `cmac->id`) and leave the software key schedule empty, so wolfCrypt passes every operation on the object to the crypto callback registered for its `devId`. wolfHSM uses the server key ID, big-endian, as this ID: a 2-byte **key reference** (`WH_HWKEYSTORE_REF_SIZE`) written by `wh_HwKeystore_KeyRef()`. The same reference names the key in the keystore callbacks below. The port looks up the hardware key from the ID field alone (`WH_KEYID_ID()`), as the server front end has already checked the owner, and the USER field can differ between requests for the same key. + +Taking AES as the example, a crypto request with a hardware key runs like this: + +1. The handler first translates the keyId, and then checks ownership and key usage with `wh_HwKeystore_CheckUse()`. +2. It sets up the `Aes` with `wc_AesInit_Id(aes, ref, WH_HWKEYSTORE_REF_SIZE, NULL, devId)`, where `devId` is `whServerConfig.devId` and `ref` is the reference for the recorded key. +3. For CBC and CTR it sets only the IV, with `wc_AesSetIV()`. It skips the key setup a software key would get. +4. It calls the usual wolfCrypt function, such as `wc_AesGcmEncrypt()`. wolfCrypt passes it to the port's callback as a `WC_ALGO_TYPE_CIPHER` request with the reference in `aes->id`. +5. It returns the output to the client. For CBC and CTR it also returns the next IV or counter state that the callback left in the `Aes`, so the client can continue the stream in a later request. + +CMAC works the same way, with `wc_InitCmac_Id()` and `WC_ALGO_TYPE_CMAC` requests, and accepts a key with either `WH_NVM_FLAGS_USAGE_SIGN` or `WH_NVM_FLAGS_USAGE_VERIFY`. For multi-step CMAC, the server rebuilds the `Cmac` on every request from the running state the client sends back, and returns the new state. The port's callback must therefore keep that state in the `Cmac` fields and not in the port. + +**Keystore callbacks: how keys get into the hardware.** wolfCrypt's `WOLF_CRYPTO_CB_KEYSTORE` feature adds key lifecycle requests (`WC_ALGO_TYPE_KEYSTORE`) that reach the same crypto callback as the algorithm requests. The front end uses three of them, always with the server's `devId`: + +| Front end call | wolfCrypt call | Request the port's callback gets | Port's job | +|---|---|---|---| +| `wh_HwKeystore_Load()` | `wc_KeyStore_ImportPlain()`, key type `WC_KEYSTORE_KEY_AES` | `WC_KEYSTORE_IMPORT_PLAIN` | write the key into the hardware key, or refuse it | +| `wh_HwKeystore_Clear()`, `wh_HwKeystore_ClearAll()`, `wh_HwKeystore_ClearSource()` | `wc_KeyStore_Delete()` | `WC_KEYSTORE_DELETE` | clear the hardware key | +| `wh_HwKeystore_GetInfo()`, and `wh_HwKeystore_Load()` with ID 0 | `wc_KeyStore_GetInfo()` | `WC_KEYSTORE_GET_INFO` | report the key type and size (`WC_KEYSTORE_KEY_NONE` when empty), or fail for a number the hardware doesn't have | + +wolfHSM doesn't use the other keystore requests (plain export, wrapped import and export, derive). + +A load request runs like this: + +1. The handler takes the NVM lock and reads the source key with the keystore's usual policy checks, which refuse trusted keys. The key goes into a local buffer of `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` bytes. A source with no usage flags is refused. +2. `wh_HwKeystore_Load()` takes the front end lock and checks the target. The caller's own key can be replaced; a port key or another client's key can't (`WH_ERROR_ACCESS`); a new key needs a free record. With ID 0, it tries each number that has no record and that the port reports empty, in order. +3. `wc_KeyStore_ImportPlain()` hands the key to the port, which writes it to the hardware or refuses it. +4. On success, the front end fills in the record. Both locks are released and the local buffer is zeroized. + +Holding the NVM lock until the copy is recorded means that an erase or revoke of the source, which takes the same lock, either runs before the load (so the load fails) or after it (so it finds the copy). Erase and revoke call `wh_HwKeystore_ClearSource()` once the keystore change succeeds, which clears every hardware key whose `srcId` matches. + +**Hardware KEKs.** The key wrap path uses neither the record table nor the keystore callbacks. It calls `wh_HwKeystore_GetKey()`, which calls the port's `GetKey` callback to copy the KEK into a stack buffer. The server then runs AES-GCM with the KEK as an ordinary key and zeroizes the buffer. The port alone decides which numbers are KEKs. `GetKey` must return an error for every other number, and must not write more than the buffer size it is given, `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` (default 32 bytes). `GetKey` is required in the callback table; a port with no KEKs returns `WH_ERROR_ACCESS` for every number. + +**Concurrency.** With `WOLFHSM_CFG_THREADSAFE`, the front end's lock (`whHwKeystoreConfig.lockConfig`) serializes the record table and every call into the port's keystore requests and `GetKey`. A load takes the NVM lock first, then the front end lock, the same order erase and revoke use. The crypto operations themselves run outside these locks, like any other crypto request. If several server threads share an accelerator, the port must serialize access to it (see [Crypto Under Concurrency](#crypto-under-concurrency)). + +#### Port and Core Library Responsibilities + +The core library (wolfHSM) is responsible for: + +- Translating client key IDs, and keeping the record table: owner, usage flags, source key and size of every hardware key. +- Policy: checking owner and usage on every crypto request that names a hardware key, refusing trusted source keys and source keys without usage flags, protecting port keys, and clearing copies when a source key is erased or revoked. +- Key bytes: reading the source key, handing it to the port once, and zeroizing its own copy. It also zeroizes a KEK after each key wrap operation. +- Setting up wolfCrypt objects by reference with the server's `devId`, and carrying streaming state between requests, such as AES-CBC and AES-CTR IVs and CMAC state. + +The port is responsible for: + +- Deciding what each hardware key number means and which numbers clients may load. In its crypto callback, it answers `WC_KEYSTORE_GET_INFO` for the numbers clients may see, with `WC_KEYSTORE_KEY_NONE` for an empty one; the server's ID 0 search only tries numbers reported empty. It refuses `WC_KEYSTORE_IMPORT_PLAIN` for numbers clients must not set, such as device keys, slots the port uses internally and KEKs, and for keys the hardware can't hold. It clears the key on `WC_KEYSTORE_DELETE`. +- Running every operation on a wolfCrypt key object that carries a key reference (a nonzero `idLen`, such as `aes->idLen`), using the hardware key the reference names. It reads and updates the running state the object carries, such as `aes->reg`, `aes->tmp` and `aes->left` for AES, and the buffer and digest fields of a `Cmac`. Software keys (no reference) reach the same callback; the port can run them in hardware or return `CRYPTOCB_UNAVAILABLE` to leave them to wolfCrypt. +- Putting its own keys into the hardware, for example by deriving a device key at boot, and registering each one with `wh_HwKeystore_Register()` after every `wh_HwKeystore_Init()`. USER 0 in the key ID shares the key with every client; a client ID limits it to that client. +- Serving KEKs through `GetKey` and refusing every other number there, so a key a client loaded can never become a KEK. KEK numbers must also be numbers that `WC_KEYSTORE_IMPORT_PLAIN` refuses, so a client can't replace a KEK with a key it knows. +- Registering its crypto callback with `wc_CryptoCb_RegisterDevice()` and passing the same device ID in `whServerConfig.devId`. +- Serializing the accelerator if several server threads share it. + +A server application wires the pieces together at startup: + +```c +/* Port: algorithm and keystore requests for hardware keys */ +static int Port_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx); + +/* Port: KEKs for key wrap. This port has none */ +static int Port_GetKey(void* ctx, whKeyId keyId, uint8_t* out, uint16_t* len) +{ + (void)ctx; (void)keyId; (void)out; (void)len; + return WH_ERROR_ACCESS; +} + +static const whHwKeystoreCb portHwKeystoreCb = {.GetKey = Port_GetKey}; +static whHwKeystoreContext hwKeystore; + +whHwKeystoreConfig hwCfg = {.cb = &portHwKeystoreCb}; + +wc_CryptoCb_RegisterDevice(PORT_DEVID, Port_CryptoCb, NULL); +wh_HwKeystore_Init(&hwKeystore, &hwCfg); + +/* The port wrote a device key into hardware key 2 at boot. Register it so + * clients can use it. USER 0 shares it with every client */ +wh_HwKeystore_Register(&hwKeystore, + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, 2), + WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, + AES_128_KEY_SIZE); + +whServerConfig serverCfg = { + /* ... comm_config, nvm, crypto ... */ + .devId = PORT_DEVID, /* runs every hardware key operation */ + .hwKeystore = &hwKeystore, /* shared by every server context */ +}; +``` + +The port's crypto callback dispatches on the request type, and each handler maps the key reference back to a hardware key: + +```c +/* Hardware key number named by a key reference, or -1 */ +static int Port_RefToSlot(const byte* ref, word32 refSz) +{ + whKeyId keyId; + + if ((ref == NULL) || (refSz != WH_HWKEYSTORE_REF_SIZE)) { + return -1; + } + keyId = (whKeyId)((ref[0] << 8) | ref[1]); /* server keyId, big-endian */ + return WH_KEYID_ISHW(keyId) ? (int)WH_KEYID_ID(keyId) : -1; +} + +static int Port_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + switch (info->algo_type) { + case WC_ALGO_TYPE_KEYSTORE: + /* IMPORT_PLAIN, DELETE or GET_INFO on the slot named by + * info->keystore.op..keyRef */ + return Port_Keystore(info); + case WC_ALGO_TYPE_CIPHER: + /* aes->idLen != 0: run with the slot named by aes->id */ + return Port_Aes(info); + case WC_ALGO_TYPE_CMAC: + /* cmac->idLen != 0: run with the slot named by cmac->id */ + return Port_Cmac(info); + default: + return CRYPTOCB_UNAVAILABLE; + } +} +``` + +For a complete example, see the mock port in `test-refactor/misc/wh_test_hwkeystore_crypto.c`. It is an eight-slot AES engine with a port key, client-loadable slots and reserved slots, and it implements every callback described here. ### Key Usage Policies @@ -485,11 +799,11 @@ The usage flags constrain which cryptographic operations a given key may partici 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. +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. Requests that use a [hardware key](#hardware-only-keys) are checked the same way, against the usage flags the hardware key took from its source key. 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. +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. With `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`, revoking or erasing a key also clears every [hardware key](#hardware-only-keys) loaded from it. Concrete examples of policy-driven scenarios that this machinery supports: From ad753529600092c78c2adb8dc806c8e83b85d8c0 Mon Sep 17 00:00:00 2001 From: Brett Nicholas <7547222+bigbrett@users.noreply.github.com> Date: Fri, 16 Oct 2026 13:12:00 -0600 Subject: [PATCH 10/23] unify hardware key functionality across crypto and keywrap KEK --- .github/workflows/build-and-test-refactor.yml | 12 +- docs/src/5-Features.md | 192 ++++++------ docs/src/9-Configuration.md | 6 +- src/wh_hwkeystore.c | 193 +++++------- src/wh_server_crypto.c | 8 +- src/wh_server_keystore.c | 266 +++++++--------- test-refactor/README.md | 2 +- test-refactor/misc/wh_test_hwkeystore.c | 226 ++++++++++---- .../misc/wh_test_hwkeystore_crypto.c | 198 +++++++++--- test-refactor/posix/Makefile | 7 +- .../server/wh_test_hwkeystore_server.c | 287 ++++++++++-------- test/config/user_settings.h | 5 + test/config/wolfhsm_cfg.h | 2 +- tools/whnvmtool/README.md | 4 +- wolfhsm/wh_client.h | 32 +- wolfhsm/wh_common.h | 5 +- wolfhsm/wh_hwkeystore.h | 214 +++++-------- wolfhsm/wh_keyid.h | 8 +- wolfhsm/wh_server_keystore.h | 4 +- wolfhsm/wh_settings.h | 52 ++-- 20 files changed, 924 insertions(+), 799 deletions(-) diff --git a/.github/workflows/build-and-test-refactor.yml b/.github/workflows/build-and-test-refactor.yml index 685172298..0cc35ef42 100644 --- a/.github/workflows/build-and-test-refactor.yml +++ b/.github/workflows/build-and-test-refactor.yml @@ -152,17 +152,17 @@ jobs: if: matrix.group == 'wolfcrypt' run: cd test-refactor/posix && make clean && make -j CRYPTO_AFFINITY=1 DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run - # Build and test with hardware keys in crypto requests (runs the hardware - # keystore crypto test, gated behind WOLFHSM_CFG_HWKEYSTORE_CRYPTO) - - name: Build and test refactor HWKEYSTORE_CRYPTO ASAN + # Build and test with clients loading hardware keys (runs the hardware + # keystore crypto test, gated behind WOLFHSM_CFG_HWKEYSTORE_LOAD) + - name: Build and test refactor HWKEYSTORE_LOAD ASAN if: matrix.group == 'wolfcrypt' - run: cd test-refactor/posix && make clean && make -j HWKEYSTORE_CRYPTO=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run + run: cd test-refactor/posix && make clean && make -j HWKEYSTORE_LOAD=1 ASAN=1 WOLFSSL_DIR=../../wolfssl && make run # Build and test hardware keys alongside DMA (exercises the DMA handlers) # LMS/XMSS verify-only; full PQ runs in DMA ASAN and THREADSAFE everything - - name: Build and test refactor HWKEYSTORE_CRYPTO DMA ASAN + - name: Build and test refactor HWKEYSTORE_LOAD DMA ASAN if: matrix.group == 'wolfcrypt' - run: cd test-refactor/posix && make clean && make -j HWKEYSTORE_CRYPTO=1 DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run + run: cd test-refactor/posix && make clean && make -j HWKEYSTORE_LOAD=1 DMA=1 ASAN=1 LMS_VERIFY_ONLY=1 XMSS_VERIFY_ONLY=1 WOLFSSL_DIR=../../wolfssl && make run # Build and test with global SHE keys (all SHE slots shared across clients) - name: Build and test refactor ASAN SHE_GLOBAL diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 63dcd3672..e84aac0a7 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -404,7 +404,7 @@ The lifecycle exposed to clients consists of four primary operations: - **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. Unwrapped keys are cache-only and cannot be committed to NVM. Because this injects a key into the server keystore, the KEK must be a **trusted KEK** (see below). -In every operation the KEK is identified by its existing keyId in the keystore. A software (keystore-resident) KEK must carry the `WH_NVM_FLAGS_USAGE_WRAP` usage flag, enforced server-side by the keystore policy machinery: a key without the `WRAP` usage flag cannot wrap or unwrap — key material or data — regardless of any client request. This usage-flag check does not apply to [hardware-only KEKs](#hardware-only-keys), which carry no `whNvmMetadata`; for them the hardware keystore backend is the policy authority instead. +In every operation the KEK is identified by its existing keyId: a key in the keystore, or a [hardware key](#hardware-only-keys). A software (keystore-resident) KEK must carry the `WH_NVM_FLAGS_USAGE_WRAP` usage flag, enforced server-side by the keystore policy machinery: a key without the `WRAP` usage flag cannot wrap or unwrap — key material or data — regardless of any client request. A hardware KEK needs the same flag, in the server's record of the hardware key. 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. Like the key operations, data wrap and unwrap require the KEK to carry `WH_NVM_FLAGS_USAGE_WRAP`; unlike *wrap-export* and *unwrap-and-cache*, they do not require a **trusted KEK**, since no server-held secret crosses the boundary. Data blobs are cryptographically domain-separated from key blobs, so *data unwrap* cannot recover key material wrapped by *wrap* or *wrap-export*, and a *data wrap* blob cannot be injected as a key through *unwrap-and-cache* (see [Cryptographic Domain Separation](#cryptographic-domain-separation)). @@ -414,14 +414,16 @@ A parallel pair of APIs — `wh_Client_DataWrap` and `wh_Client_DataUnwrap` — Two of the operations — **wrap-export** and **unwrap-and-cache** — additionally require the KEK to be a **trusted KEK**, because each moves a *server-held* secret across the client trust boundary: wrap-export extracts one, and unwrap-and-cache injects one. If a client could name a KEK whose plaintext it knows (for example an AES key it cached itself), it could decrypt an extracted blob to recover the target key, or forge a blob to inject a key of its choosing, defeating the wrapped-key confidentiality guarantee. A trusted KEK is one that the client can neither read nor set, and is one of the following: -- a **[hardware-only key](#hardware-only-keys)**: A key stored in hardware in a port-specific manner (see next section) +- a **port key**: a [hardware key](#hardware-only-keys) the port put in place, which no client can set or read - a software key carrying the server-only **`WH_NVM_FLAGS_TRUSTED`** flag. -For a software KEK, the `WH_NVM_FLAGS_TRUSTED` flag will be stripped by the server from *every* client request that supplies key or object metadata such that a client can never cause a key or object to carry it. The strip lives in the policy-checked entry points that all client requests funnel through. It can be set only by calling the unchecked keystore/NVM API directly, which is not exposed via the client API but remains usable for server-side code or offline provisioning via `whnvmtool`. A key carrying the flag is treated as unreadable, immutable, non-evictable, and non-exportable through the client API, and is the only cache/NVM key permitted to act as a KEK for wrap-export and unwrap-and-cache. +A hardware key that a client loaded is never trusted, since the client may know its bytes. + +For a software KEK, the `WH_NVM_FLAGS_TRUSTED` flag will be stripped by the server from *every* client request that supplies key or object metadata such that a client can never cause a key or object to carry it. The strip lives in the policy-checked entry points that all client requests funnel through. It can be set only by calling the unchecked keystore/NVM API directly, which is not exposed via the client API but remains usable for server-side code or offline provisioning via `whnvmtool`. A key carrying the flag is treated as unreadable, immutable, non-evictable, and non-exportable through the client API, and a client can't load it into a hardware key. It is the only cache/NVM key permitted to act as a KEK for wrap-export and unwrap-and-cache. The ordinary **wrap**, **unwrap-and-export**, and **data wrap/unwrap** operations are not affected by the trusted-KEK requirement, as no server secret crosses the boundary in those; they accept any client-chosen KEK that carries `WH_NVM_FLAGS_USAGE_WRAP`. -For deployment guidance, an NVM-backed system that wishes to use software KEKs for keywrap export should provision the software KEK in an offline manner (e.g. using `whnvmtool` or building the NVM image manually). An NVM-less system has no secure persistent source for software-KEK bytes other than the firmware image, so a **hardware KEK is required** in this case. +For deployment guidance, an NVM-backed system that wishes to use software KEKs for keywrap export should provision the software KEK in an offline manner (e.g. using `whnvmtool` or building the NVM image manually). An NVM-less system has no secure persistent source for software-KEK bytes other than the firmware image, so its trusted KEK **must be a port key**, such as a key fixed in OTP or fuses, or one the port derives in hardware at boot. #### Server KEK Best Practices @@ -437,7 +439,7 @@ See the `whnvmtool` README for the concrete flag value and provisioning mechanic **Never grant a KEK any usage beyond `WRAP`** — in particular not `WH_NVM_FLAGS_USAGE_ENCRYPT` or `WH_NVM_FLAGS_USAGE_DECRYPT`, and not the catch-all `WH_NVM_FLAGS_USAGE_ANY`. The server enforces usage flags on every wolfCrypt request through the crypto callback (see [Key Usage Policies](#key-usage-policies)), and that check is the only thing stopping a client from using a KEK as an ordinary cipher key. A wrap blob is just AES-GCM ciphertext under the KEK, so a KEK that also carries `USAGE_DECRYPT` can be fed to a raw AES-GCM decrypt under its keyId — returning the wrapped key's plaintext directly and defeating wrapped-key confidentiality regardless of every other control. A KEK never needs encrypt or decrypt usage to function, so `WRAP`-only is both fully functional and the only safe choice. -**Give each key one role.** A key should be a KEK *or* a general-purpose encryption/signing/derivation key, never both; provision separate keys if an application needs both. Sharing usage across roles is exactly what the crypto callback's usage enforcement exists to prevent. A [hardware KEK](#hardware-only-keys) sidesteps this class of mistake by construction: only the keywrap operations read a KEK, through the port's `GetKey` callback, so it can never be used as a cipher key. With `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`, where hardware keys can also be cipher keys, this holds as long as the port keeps KEK numbers apart from the numbers it accepts as cipher keys (see [Port and Core Library Responsibilities](#port-and-core-library-responsibilities)). +**Give each key one role.** A key should be a KEK *or* a general-purpose encryption/signing/derivation key, never both; provision separate keys if an application needs both. Sharing usage across roles is exactly what the crypto callback's usage enforcement exists to prevent. The same rule applies to a [hardware KEK](#hardware-only-keys): the port should register it with `WH_NVM_FLAGS_USAGE_WRAP` only, and the server then refuses it as a cipher key. `wh_HwKeystore_Register()` doesn't check this, and every port key is trusted, so a port KEK that also allows `WH_NVM_FLAGS_USAGE_DECRYPT` is the same decryption oracle. #### Cryptographic Domain Separation @@ -453,8 +455,8 @@ Many HSM platforms keep keys inside the crypto hardware itself: key slots in an Two build options enable the feature: -- `WOLFHSM_CFG_HWKEYSTORE` adds the hardware keystore front end and lets a hardware key be the KEK in the [key wrap](#wrapped-keys) operations. -- `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` lets clients use hardware keys in crypto operations, and copy their own keystore keys into hardware keys. It needs `WOLFHSM_CFG_HWKEYSTORE`, plus `WOLF_CRYPTO_CB_KEYSTORE` and `WOLF_PRIVATE_KEY_ID` in the wolfCrypt build. +- `WOLFHSM_CFG_HWKEYSTORE` adds the hardware keystore front end: the port registers hardware keys, and clients use them in crypto operations and as [key wrap](#wrapped-keys) KEKs. It needs `WOLF_PRIVATE_KEY_ID` in the wolfCrypt build. +- `WOLFHSM_CFG_HWKEYSTORE_LOAD` also lets clients copy their own keystore keys into hardware keys, clear them, and describe hardware keys. It needs `WOLFHSM_CFG_HWKEYSTORE`, plus `WOLF_CRYPTO_CB_KEYSTORE` in the wolfCrypt build. [Supported Functionality](#supported-functionality) lists which wolfHSM operations accept hardware keys under each option. @@ -464,20 +466,19 @@ A hardware key ID follows the same client-facing naming scheme as other keystore The port defines what each hardware key ID represents. It may identify a fixed key, such as one stored in OTP memory or fuses, or a logical key-storage location whose contents can be loaded or changed, such as a "key slot" or input register for a hardware accelerator. Unlike keystore key IDs, hardware key numbers are shared across clients: hardware key `n` refers to the same hardware key for every client, while the server tracks which client owns it. -To control which client may use which hardware key, and for what, the server keeps its own **record** of each hardware key that may be used in crypto operations. A record holds the key's owner (a client ID, or 0 if every client may use it) and the usage flags it allows. For a key copied from the keystore, it also holds the keystore key it came from. The server checks the record on every crypto request that names a hardware key: a client can't use another client's key, every operation needs the matching usage flag, and a hardware key with no record can't be used in crypto operations, whatever the hardware holds. Records are kept in server RAM, up to `WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS` of them (default 8). [Server Architecture for Hardware Keys](#server-architecture-for-hardware-keys) describes the runtime key record in more detail. +To control which client may use which hardware key, and for what, the server keeps its own **record** of each hardware key clients may use. A record holds the key's owner (a client ID, or 0 if every client may use it) and the usage flags it allows. For a key copied from the keystore, it also holds the keystore key it came from. The server checks the record on every request that names a hardware key: a client can't use another client's key, every operation needs the matching usage flag, and a hardware key with no record can't be used at all, whatever the hardware holds. Records are kept in server RAM, up to `WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS` of them (default 8). [Server Architecture for Hardware Keys](#server-architecture-for-hardware-keys) describes the runtime key record in more detail. -In general, there are three usage paterns for hardware keys: +In general, there are two usage patterns for hardware keys: -- **Client-loaded keys.** A client *loads* a hardware key by asking the server to copy one of its keystore keys into it, with `wh_Client_HwKeystoreLoadFromKeystore()`. The server writes the key into the hardware through the port and creates the record. This is the only way a client can put a key into hardware. A key loaded with the [global flag](#global-keys) added to its hardware key ID is shared and useable across clients. -- **Port keys.** The port can put a key into the hardware itself, for example a device-specific key derived at boot, and then *registers* it with the server, which creates the record. Clients can use a port-loaded key but can never replace or clear it. -- **Hardware KEKs.** Keys fixed in the hardware, such as keys in OTP or fuses, that the port offers as KEKs for the [key wrap](#wrapped-keys) operations. The server keeps no record of them, and they can only be used as KEKs. +- **Client-loaded keys.** A client *loads* a hardware key by asking the server to copy one of its keystore keys into it, with `wh_Client_HwKeystoreLoadFromKeystore()`. The server writes the key into the hardware through the port and creates the record. This is the only way a client can put a key into hardware, and needs `WOLFHSM_CFG_HWKEYSTORE_LOAD`. A key loaded with the [global flag](#global-keys) added to its hardware key ID is shared and useable across clients. +- **Port keys.** The port can put a key into the hardware itself, for example a device-specific key derived at boot, or offer a key fixed in the hardware, such as a KEK in OTP or fuses. It then *registers* the key with the server, which creates the record. Clients can use a port key but can never replace or clear it. -| | Client-loaded key | Port key | Hardware KEK | -|---|---|---|---| -| Used as | a key in crypto operations | a key in crypto operations | a KEK in key wrap operations | -| Usable by | the loading client, or every client if it is shared | every client, or one client, as the port registered it | the clients the port's `GetKey` callback allows | -| Usage flags | copied from the source keystore key | set by the port when it registers the key | none; the port decides | -| Lifetime | until its owner clears it (any client can clear a shared key), its source key is erased or revoked, or the HSM resets | until the HSM resets; the port registers it again at every boot | as long as the hardware holds it | +| | Client-loaded key | Port key | +|---|---|---| +| Usable by | the loading client, or every client if it is shared | every client, or one client, as the port registered it | +| Usage flags | copied from the source keystore key | set by the port when it registers the key | +| [Trusted KEK](#trusted-keks) | no: the client may know its bytes | yes: no client can set or read it | +| Lifetime | until its owner clears it (any client can clear a shared key), its source key is erased or revoked, or the HSM resets | until the HSM resets; the port registers it again at every boot | The layers stack up like this: @@ -491,21 +492,19 @@ The layers stack up like this: │ wolfHSM server (core library) │ │ keystore ── load (copy) ──▶ hardware keystore front end: │ │ (cache, NVM) one record per key: owner, usage, source │ -│ crypto handlers: check the key's record, then hand the key │ -│ to wolfCrypt by reference. Key wrap: fetch the KEK through GetKey │ +│ crypto and key wrap handlers: check the key's record, then hand │ +│ the key to wolfCrypt by reference │ └─────────────────────────────┬────────────────────────────────────────────┘ │ wolfCrypt crypto callback (server devId) - │ and GetKey callback (KEKs) ┌─────────────────────────────▼────────────────────────────────────────────┐ -│ Server Port │ +│ Port │ │ keystore callbacks: write, clear and describe a hardware key │ │ algorithm callbacks: run the operation with the hardware key │ -│ GetKey callback: copy out a KEK for key wrap, or refuse │ -│ at boot: place keys into the hardware and register them │ +│ at boot: put its own keys into the hardware and register them │ └─────────────────────────────┬────────────────────────────────────────────┘ │ ┌─────────────────────────────▼────────────────────────────────────────────┐ -│ Hardware: crypto accelerator and its key slots, OTP or fuse KEKs │ +│ Hardware: crypto accelerator and its key slots, keys in OTP or fuses │ └──────────────────────────────────────────────────────────────────────────┘ ``` @@ -517,12 +516,12 @@ Clients name a hardware key in the same calls as any other key, through the wolf | Functionality | Role of the hardware key | Usage flag the key needs | Build option | |---|---|---|---| -| AES-ECB, AES-CBC, AES-CTR and AES-GCM | the cipher key, in standard and DMA requests | `WH_NVM_FLAGS_USAGE_ENCRYPT` to encrypt, `WH_NVM_FLAGS_USAGE_DECRYPT` to decrypt | `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` | -| AES-CMAC, one-shot or multi-step | the MAC key, in standard and DMA requests | `WH_NVM_FLAGS_USAGE_SIGN` or `WH_NVM_FLAGS_USAGE_VERIFY` | `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` | -| Key wrap: key wrap, wrap-export, unwrap-and-export, unwrap-and-cache, data wrap and data unwrap | the KEK | none; the port decides which keys may be KEKs | `WOLFHSM_CFG_HWKEYSTORE`, with `WOLFHSM_CFG_KEYWRAP` | +| AES-ECB, AES-CBC, AES-CTR and AES-GCM | the cipher key, in standard and DMA requests | `WH_NVM_FLAGS_USAGE_ENCRYPT` to encrypt, `WH_NVM_FLAGS_USAGE_DECRYPT` to decrypt | `WOLFHSM_CFG_HWKEYSTORE` | +| AES-CMAC, one-shot or multi-step | the MAC key, in standard and DMA requests | `WH_NVM_FLAGS_USAGE_SIGN` or `WH_NVM_FLAGS_USAGE_VERIFY` | `WOLFHSM_CFG_HWKEYSTORE` | +| Key wrap: key wrap, wrap-export, unwrap-and-export, unwrap-and-cache, data wrap and data unwrap | the KEK | `WH_NVM_FLAGS_USAGE_WRAP` | `WOLFHSM_CFG_HWKEYSTORE`, with `WOLFHSM_CFG_KEYWRAP` | -- The crypto operations take client-loaded keys and port keys. -- Key wrap takes hardware KEKs. A hardware KEK is always a [trusted KEK](#trusted-keks), so it can be used for wrap-export and unwrap-and-cache, which require one. +- Every operation takes client-loaded keys and port keys. +- Wrap-export and unwrap-and-cache need a [trusted KEK](#trusted-keks). A port key qualifies; a client-loaded key doesn't, since the client may know its bytes. - All other wolfHSM functionality refuses hardware key IDs with `WH_ERROR_ACCESS`. This includes the algorithms that don't support hardware keys yet, such as RSA and ECC, and the keystore operations (cache, export, commit, evict, erase, revoke). #### Using Hardware Keys from a Client @@ -563,7 +562,7 @@ wh_Client_HwKeystoreClear(client, AES_KEY_SLOT); If a port offers several interchangeable "scratch" slots, a client can pass ID 0 (`WH_CLIENT_KEYID_MAKE_HW(0)`) instead of a fixed slot. The server then picks the first empty slot the port accepts and returns its ID in the last argument, for the client to use in its operations. -If the port instead has a fixed read-only or use-only keyslot that can be used with the accelerator, either because the key itself is fixed in hardware, or because the port installs the key at startup, then the entire keystore loading API call can be ommitted, and the AES operation can directly use the HW keyId in question. The nuances of what functionality extends to which keys is entirely port-specific and should be detailed in the port's documentation. +If the port instead has a fixed read-only or use-only keyslot that can be used with the accelerator, either because the key itself is fixed in hardware, or because the port installs the key at startup, then the entire keystore loading API call can be omitted, and the AES operation can directly use the HW keyId in question. The nuances of what functionality extends to which keys is entirely port-specific and should be detailed in the port's documentation. ##### Shared Hardware Keys @@ -572,7 +571,7 @@ Two kinds of hardware keys can be used by every client: - A key loaded with the global flag added: `WH_CLIENT_KEYID_MAKE_GLOBAL(WH_CLIENT_KEYID_MAKE_HW(n))`. This needs [global keys](#global-keys) (`WOLFHSM_CFG_GLOBAL_KEYS`). Any client can use it, with or without the global flag, and any client can clear it using the global flag. - A port key that the port shares with all clients. A port can also register a key for one client, and only that client can use it. No client can replace or clear a port key. -##### Inspecting and Sharing Hardware Keys +##### Inspecting and Clearing Hardware Keys `wh_Client_HwKeystoreGetInfo()` fills a `whHwKeystoreInfo` for a hardware key: - `keyType` and `keyBits`: what the port says the key holds. `keyType` is a wolfCrypt `enum wc_KeyStoreKeyType` value, such as `WC_KEYSTORE_KEY_AES`, and is `WC_KEYSTORE_KEY_NONE` (0) when the key is empty. @@ -584,7 +583,7 @@ Any client can describe any hardware key the port has, including other clients' - The hardware key gets the source key's usage flags, and the server checks them on every request, as it does for keystore keys. [Supported Functionality](#supported-functionality) lists the flag each operation needs. A source key with no usage flags, such as a revoked key, can't be loaded. - Erasing or revoking the source key clears every hardware key loaded from it. Evicting it does not, so a client can evict the source from the cache once the hardware key is loaded. -- The source must be a keystore key the client can reach: its own, or a [global key](#global-keys). A `WH_NVM_FLAGS_NONEXPORTABLE` key can be loaded, because its bytes go to the hardware and not to a client. A [trusted KEK](#trusted-keks) can't be loaded. The key must fit in `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` bytes, and the port may accept only some key sizes. +- The source must be a keystore key the client can reach: its own, or a [global key](#global-keys). A `WH_NVM_FLAGS_NONEXPORTABLE` key can be loaded, because its bytes go to the hardware and not to a client. A key carrying `WH_NVM_FLAGS_TRUSTED`, such as a [trusted](#trusted-keks) software KEK, can't be loaded, and a loaded key is never trusted. To keep a trusted KEK in hardware, the port registers it as a port key. The key must fit in `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` bytes, and the port may accept only some key sizes. ##### Hardware Key Errors @@ -592,71 +591,71 @@ Hardware key requests can return: | Error | Meaning | |---|---| -| `WH_ERROR_ACCESS` | The key belongs to another client, or is a port key that can't be replaced or cleared. Or the source is a trusted KEK. Or the operation doesn't support hardware keys, which includes every crypto operation on a server built without `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`. | +| `WH_ERROR_ACCESS` | The key belongs to another client, or is a port key that can't be replaced or cleared. Or the source key carries `WH_NVM_FLAGS_TRUSTED`. Or the operation doesn't support hardware keys. Or wrap-export or unwrap-and-cache named a client-loaded KEK, which is not trusted. | | `WH_ERROR_USAGE` | The key's usage flags don't allow the operation, or the source key has no usage flags. | | `WH_ERROR_NOTFOUND` | The server has no record of the hardware key (no client loaded it and the port didn't register it), or the source key doesn't exist. Also returned when the server has no hardware keystore, and for crypto requests when it has no crypto device for them, including requests sent with `WH_CRYPTO_AFFINITY_SW`. | | `WH_ERROR_NOSPACE` | No empty hardware key for ID 0, the server's table of hardware keys is full, or the source key is larger than `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE`. | | `WH_ERROR_BADARGS` | ID 0 where a specific key is needed, or a source ID with the hardware or wrapped flag. | -| `WH_ERROR_NOTIMPL` | The server was built without `WOLFHSM_CFG_HWKEYSTORE_CRYPTO` (load, clear and info requests). | +| `WH_ERROR_NOTIMPL` | The server was built without `WOLFHSM_CFG_HWKEYSTORE_LOAD` (load, clear and info requests). | | a port error | The port refused the key number or the key, for example a size the slot can't hold, or the hardware failed. The port's error code is returned as is. | ##### Hardware keys and key-wrapping -Pass `WH_CLIENT_KEYID_MAKE_HW(n)` as the KEK ID to any [key wrap](#wrapped-keys) operation. The server reads such a KEK through the port's `GetKey` callback, and this needs only `WOLFHSM_CFG_HWKEYSTORE`. Hardware KEKs carry no `whNvmMetadata`, so the [usage flags](#key-usage-policies) don't apply to them; the port decides which keys may be KEKs. Only the KEK is in hardware: a key recovered by unwrap-and-cache becomes an ordinary cached key. +Pass `WH_CLIENT_KEYID_MAKE_HW(n)` as the KEK ID to any [key wrap](#wrapped-keys) operation. The server checks the KEK's record like any other use of a hardware key: it needs `WH_NVM_FLAGS_USAGE_WRAP`. The port then runs the AES-GCM with the key in hardware. Wrap-export and unwrap-and-cache also need a [trusted KEK](#trusted-keks): a port key qualifies, but a client-loaded key doesn't. Only the KEK is in hardware: a key recovered by unwrap-and-cache becomes an ordinary cached key. #### Server Architecture for Hardware Keys -On the server, a request with a hardware key passes through the core library (the request handlers, then the hardware keystore front end), then wolfCrypt, then the port callbacks, which drive the hardware. The diagram below shows the three paths through these layers. Each box is marked with the code that owns it. +On the server, a request with a hardware key passes through the core library (the request handlers, then the hardware keystore front end), then wolfCrypt, then the port callbacks, which drive the hardware. The diagram below shows the two paths through these layers. Each box is marked with the code that owns it. ``` - manage hardware keys use a hardware key key wrap with a - HW_LOAD, HW_CLEAR, in a crypto request hardware KEK - HW_INFO; erase or (standard or DMA) - revoke of a source - │ │ │ -┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ -│ [core] request handlers │ -│ client keyId -> server keyId: TYPE=HW, USER=client (0 if shared), ID=n │ -│ HW_LOAD reads the source key under the NVM lock, with the keystore's │ -│ policy checks │ -└───────────┬───────────────────────────────┬───────────────────────────────┬──────────────┘ - │ │ │ -┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ -│ [core] hardware keystore front end (one context, shared by all servers) │ -│ Load, Clear, ClearAll, CheckUse: owner and usage, GetKey │ -│ ClearSource, GetInfo KeyRef: the key reference │ -│ record table: { keyId with owner, source keyId, usage, size } x MAX_KEYS │ -└───────────┬───────────────────────────────┬───────────────────────────────┬──────────────┘ - │ wc_KeyStore_ImportPlain │ init by reference, e.g. │ - │ wc_KeyStore_Delete │ wc_AesInit_Id(ref), then the │ - │ wc_KeyStore_GetInfo │ usual wolfCrypt call │ -┌───────────▼───────────────────────────────▼─────────────────────┐ │ -│ [wolfCrypt] crypto callback dispatch to whServerConfig.devId │ │ -└───────────┬───────────────────────────────┬─────────────────────┘ │ - │ WC_ALGO_TYPE_KEYSTORE │ algorithm request, such as │ cb->GetKey - │ │ WC_ALGO_TYPE_CIPHER │ -┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ -│ [port] crypto callback [port] GetKey callback │ -│ IMPORT_PLAIN: write a slot reference -> slot, run the op copy a KEK's bytes, │ -│ DELETE: clear a slot with the slot's key, update or refuse │ -│ GET_INFO: type and size the running state │ -└───────────┬───────────────────────────────┬───────────────────────────────┬──────────────┘ - │ │ │ -┌───────────▼───────────────────────────────▼───────────────────────────────▼──────────────┐ -│ Hardware: crypto accelerator and its key slots; OTP or fuse KEKs │ -└──────────────────────────────────────────────────────────────────────────────────────────┘ + manage hardware keys use a hardware key + HW_LOAD, HW_CLEAR, HW_INFO; in a crypto request or as a + erase or revoke of a source key wrap KEK (standard or DMA) + (WOLFHSM_CFG_HWKEYSTORE_LOAD) + │ │ +┌───────────────▼───────────────────────────────────▼──────────────────────────────┐ +│ [core] request handlers │ +│ client keyId -> server keyId: TYPE=HW, USER=client (0 if shared), ID=n │ +│ HW_LOAD reads the source key under the NVM lock, with the keystore's │ +│ policy checks │ +└───────────────┬───────────────────────────────────┬──────────────────────────────┘ + │ │ +┌───────────────▼───────────────────────────────────▼──────────────────────────────┐ +│ [core] hardware keystore front end (one context, shared by all servers) │ +│ Load, Clear, ClearAll, CheckUse: owner, usage and │ +│ ClearSource, GetInfo trust; KeyRef: key reference │ +│ records: { keyId with owner, source keyId, usage, size } x MAX_KEYS │ +└───────────────┬───────────────────────────────────┬──────────────────────────────┘ + │ wc_KeyStore_ImportPlain │ init by reference, e.g. + │ wc_KeyStore_Delete │ wc_AesInit_Id(ref), then the + │ wc_KeyStore_GetInfo │ usual wolfCrypt call +┌───────────────▼───────────────────────────────────▼──────────────────────────────┐ +│ [wolfCrypt] crypto callback dispatch to whServerConfig.devId │ +└───────────────┬───────────────────────────────────┬──────────────────────────────┘ + │ WC_ALGO_TYPE_KEYSTORE │ algorithm request, such as + │ │ WC_ALGO_TYPE_CIPHER +┌───────────────▼───────────────────────────────────▼──────────────────────────────┐ +│ [port] crypto callback │ +│ IMPORT_PLAIN: write a slot reference -> slot, run the op │ +│ DELETE: clear a slot with the slot's key, update │ +│ GET_INFO: type and size the running state │ +└───────────────┬───────────────────────────────────┬──────────────────────────────┘ + │ │ +┌───────────────▼───────────────────────────────────▼──────────────────────────────┐ +│ Hardware: crypto accelerator and its key slots; keys in OTP or fuses │ +└──────────────────────────────────────────────────────────────────────────────────┘ ``` **Request handlers.** When a request arrives, the server turns the client key ID into a full server key ID (see [Key Cache, Key IDs, and NVM Backing Store](#key-cache-key-ids-and-nvm-backing-store)). The hardware flag selects TYPE `WH_KEYTYPE_HW`, USER is the calling client (or 0 with the global flag), and ID is the hardware key number. Keystore operations refuse this type outright, so a hardware key ID can only reach the code paths in the diagram. -**The front end and its record table.** The front end (`wolfhsm/wh_hwkeystore.h`) is a `whHwKeystoreContext` that the server application creates, initializes once with `wh_HwKeystore_Init()`, and passes to every server context through `whServerConfig.hwKeystore`. All server contexts that share it, typically one per client connection, see the same hardware keys. The context holds the port's `whHwKeystoreCb` callback table and, with `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`, the table of [records](#hardware-key-overview) (`whHwKeystoreKey`) for the hardware keys clients may use in crypto requests. There are `WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS` records (default 8), each with these fields: +**The front end and its record table.** The front end (`wolfhsm/wh_hwkeystore.h`) is a `whHwKeystoreContext` that the server application creates, initializes once with `wh_HwKeystore_Init()`, and passes to every server context through `whServerConfig.hwKeystore`. All server contexts that share it, typically one per client connection, see the same hardware keys. The context holds the optional backend hooks (`whHwKeystoreCb`) and the table of [records](#hardware-key-overview) (`whHwKeystoreKey`) for the hardware keys clients may use. There are `WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS` records (default 8), each with these fields: - `id`: the server key ID. USER is the owning client (0 if shared) and ID is the hardware key number. There is at most one record per number, whoever owns it. - `srcId`: the keystore key the hardware key was loaded from, so erase and revoke can find its copies. Port keys have none. -- `flags`: the usage flags the key allows. Port keys are also marked `WH_NVM_FLAGS_NONDESTROYABLE`, which stops clients from replacing or clearing them. +- `flags`: the usage flags the key allows. Port keys are also marked `WH_NVM_FLAGS_NONDESTROYABLE`, which stops clients from replacing or clearing them, and `WH_NVM_FLAGS_TRUSTED`, which makes them trusted KEKs. - `len`: the key size in bytes. -The table holds no key material; It is simply a directory enumerating the keys that the port is aware of and able to serve. It lives in RAM, so it is cleared after every reset. A crypto request with a hardware key passes `wh_HwKeystore_CheckUse()` only if a record exists for the number, its owner is the caller or 0, and its flags include every usage the operation needs. +The table holds no key material; it is simply a directory enumerating the keys that the port is aware of and able to serve. It lives in RAM, so it is cleared after every reset. A request with a hardware key passes `wh_HwKeystore_CheckUse()` only if a record exists for the number, its owner is the caller or 0, and its flags include every usage the operation needs, plus `WH_NVM_FLAGS_TRUSTED` when it needs a trusted KEK. **Key references: how wolfCrypt is told which key to use.** wolfCrypt's `WOLF_PRIVATE_KEY_ID` feature lets a key object name a key held by a device instead of holding the key bytes. wolfCrypt functions like `wc_AesInit_Id()` and `wc_InitCmac_Id()` store an opaque ID in the object (`aes->id`, `cmac->id`) and leave the software key schedule empty, so wolfCrypt passes every operation on the object to the crypto callback registered for its `devId`. wolfHSM uses the server key ID, big-endian, as this ID: a 2-byte **key reference** (`WH_HWKEYSTORE_REF_SIZE`) written by `wh_HwKeystore_KeyRef()`. The same reference names the key in the keystore callbacks below. The port looks up the hardware key from the ID field alone (`WH_KEYID_ID()`), as the server front end has already checked the owner, and the USER field can differ between requests for the same key. @@ -670,7 +669,9 @@ Taking AES as the example, a crypto request with a hardware key runs like this: CMAC works the same way, with `wc_InitCmac_Id()` and `WC_ALGO_TYPE_CMAC` requests, and accepts a key with either `WH_NVM_FLAGS_USAGE_SIGN` or `WH_NVM_FLAGS_USAGE_VERIFY`. For multi-step CMAC, the server rebuilds the `Cmac` on every request from the running state the client sends back, and returns the new state. The port's callback must therefore keep that state in the `Cmac` fields and not in the port. -**Keystore callbacks: how keys get into the hardware.** wolfCrypt's `WOLF_CRYPTO_CB_KEYSTORE` feature adds key lifecycle requests (`WC_ALGO_TYPE_KEYSTORE`) that reach the same crypto callback as the algorithm requests. The front end uses three of them, always with the server's `devId`: +Key wrap works the same way. The KEK's record must allow `WH_NVM_FLAGS_USAGE_WRAP`, and also be `WH_NVM_FLAGS_TRUSTED` for wrap-export and unwrap-and-cache. The server then runs AES-GCM with the KEK through `wc_AesInit_Id()`, so the KEK never leaves the hardware either. + +**Keystore callbacks: how keys get into the hardware.** For client loading (`WOLFHSM_CFG_HWKEYSTORE_LOAD`), wolfCrypt's `WOLF_CRYPTO_CB_KEYSTORE` feature adds key lifecycle requests (`WC_ALGO_TYPE_KEYSTORE`) that reach the same crypto callback as the algorithm requests. The front end uses three of them, always with the server's `devId`: | Front end call | wolfCrypt call | Request the port's callback gets | Port's job | |---|---|---|---| @@ -689,25 +690,22 @@ A load request runs like this: Holding the NVM lock until the copy is recorded means that an erase or revoke of the source, which takes the same lock, either runs before the load (so the load fails) or after it (so it finds the copy). Erase and revoke call `wh_HwKeystore_ClearSource()` once the keystore change succeeds, which clears every hardware key whose `srcId` matches. -**Hardware KEKs.** The key wrap path uses neither the record table nor the keystore callbacks. It calls `wh_HwKeystore_GetKey()`, which calls the port's `GetKey` callback to copy the KEK into a stack buffer. The server then runs AES-GCM with the KEK as an ordinary key and zeroizes the buffer. The port alone decides which numbers are KEKs. `GetKey` must return an error for every other number, and must not write more than the buffer size it is given, `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` (default 32 bytes). `GetKey` is required in the callback table; a port with no KEKs returns `WH_ERROR_ACCESS` for every number. - -**Concurrency.** With `WOLFHSM_CFG_THREADSAFE`, the front end's lock (`whHwKeystoreConfig.lockConfig`) serializes the record table and every call into the port's keystore requests and `GetKey`. A load takes the NVM lock first, then the front end lock, the same order erase and revoke use. The crypto operations themselves run outside these locks, like any other crypto request. If several server threads share an accelerator, the port must serialize access to it (see [Crypto Under Concurrency](#crypto-under-concurrency)). +**Concurrency.** With `WOLFHSM_CFG_THREADSAFE`, the front end's lock (`whHwKeystoreConfig.lockConfig`) serializes the record table and every call into the port's keystore requests. A load takes the NVM lock first, then the front end lock, the same order erase and revoke use. The crypto operations themselves run outside these locks, like any other crypto request. If several server threads share an accelerator, the port must serialize access to it (see [Crypto Under Concurrency](#crypto-under-concurrency)). #### Port and Core Library Responsibilities The core library (wolfHSM) is responsible for: - Translating client key IDs, and keeping the record table: owner, usage flags, source key and size of every hardware key. -- Policy: checking owner and usage on every crypto request that names a hardware key, refusing trusted source keys and source keys without usage flags, protecting port keys, and clearing copies when a source key is erased or revoked. -- Key bytes: reading the source key, handing it to the port once, and zeroizing its own copy. It also zeroizes a KEK after each key wrap operation. +- Policy: checking owner and usage on every request that names a hardware key, allowing only port keys as trusted KEKs, refusing trusted source keys and source keys without usage flags, protecting port keys, and clearing copies when a source key is erased or revoked. +- Key bytes: reading the source key, handing it to the port once, and zeroizing its own copy. - Setting up wolfCrypt objects by reference with the server's `devId`, and carrying streaming state between requests, such as AES-CBC and AES-CTR IVs and CMAC state. The port is responsible for: -- Deciding what each hardware key number means and which numbers clients may load. In its crypto callback, it answers `WC_KEYSTORE_GET_INFO` for the numbers clients may see, with `WC_KEYSTORE_KEY_NONE` for an empty one; the server's ID 0 search only tries numbers reported empty. It refuses `WC_KEYSTORE_IMPORT_PLAIN` for numbers clients must not set, such as device keys, slots the port uses internally and KEKs, and for keys the hardware can't hold. It clears the key on `WC_KEYSTORE_DELETE`. +- Deciding what each hardware key number means and which numbers clients may load. In its crypto callback, it answers `WC_KEYSTORE_GET_INFO` for the numbers clients may see, with `WC_KEYSTORE_KEY_NONE` for an empty one; the server's ID 0 search only tries numbers reported empty. It refuses `WC_KEYSTORE_IMPORT_PLAIN` for numbers clients must not set, such as device keys and slots the port uses internally, and for keys the hardware can't hold. It clears the key on `WC_KEYSTORE_DELETE`. - Running every operation on a wolfCrypt key object that carries a key reference (a nonzero `idLen`, such as `aes->idLen`), using the hardware key the reference names. It reads and updates the running state the object carries, such as `aes->reg`, `aes->tmp` and `aes->left` for AES, and the buffer and digest fields of a `Cmac`. Software keys (no reference) reach the same callback; the port can run them in hardware or return `CRYPTOCB_UNAVAILABLE` to leave them to wolfCrypt. -- Putting its own keys into the hardware, for example by deriving a device key at boot, and registering each one with `wh_HwKeystore_Register()` after every `wh_HwKeystore_Init()`. USER 0 in the key ID shares the key with every client; a client ID limits it to that client. -- Serving KEKs through `GetKey` and refusing every other number there, so a key a client loaded can never become a KEK. KEK numbers must also be numbers that `WC_KEYSTORE_IMPORT_PLAIN` refuses, so a client can't replace a KEK with a key it knows. +- Putting its own keys into the hardware, for example by deriving a device key at boot, or using keys fixed in it, and registering each one with `wh_HwKeystore_Register()` after every `wh_HwKeystore_Init()`. The port writes these keys into the hardware itself, not with `wh_HwKeystore_Load()`, whose keys are never trusted. The flags are the usage the key allows. Every port key is trusted, so a KEK gets `WH_NVM_FLAGS_USAGE_WRAP` alone; the server doesn't check this (see [Server KEK Best Practices](#server-kek-best-practices)). USER 0 in the key ID shares the key with every client; a client ID limits it to that client. - Registering its crypto callback with `wc_CryptoCb_RegisterDevice()` and passing the same device ID in `whServerConfig.devId`. - Serializing the accelerator if several server threads share it. @@ -717,17 +715,9 @@ A server application wires the pieces together at startup: /* Port: algorithm and keystore requests for hardware keys */ static int Port_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx); -/* Port: KEKs for key wrap. This port has none */ -static int Port_GetKey(void* ctx, whKeyId keyId, uint8_t* out, uint16_t* len) -{ - (void)ctx; (void)keyId; (void)out; (void)len; - return WH_ERROR_ACCESS; -} - -static const whHwKeystoreCb portHwKeystoreCb = {.GetKey = Port_GetKey}; -static whHwKeystoreContext hwKeystore; +static whHwKeystoreContext hwKeystore; -whHwKeystoreConfig hwCfg = {.cb = &portHwKeystoreCb}; +whHwKeystoreConfig hwCfg = {.cb = NULL}; /* no backend hooks */ wc_CryptoCb_RegisterDevice(PORT_DEVID, Port_CryptoCb, NULL); wh_HwKeystore_Init(&hwKeystore, &hwCfg); @@ -739,6 +729,11 @@ wh_HwKeystore_Register(&hwKeystore, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, AES_128_KEY_SIZE); +/* Hardware key 1 is a KEK fixed in OTP. Register it for key wrap only */ +wh_HwKeystore_Register(&hwKeystore, + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, 1), + WH_NVM_FLAGS_USAGE_WRAP, AES_256_KEY_SIZE); + whServerConfig serverCfg = { /* ... comm_config, nvm, crypto ... */ .devId = PORT_DEVID, /* runs every hardware key operation */ @@ -769,7 +764,8 @@ static int Port_CryptoCb(int devId, wc_CryptoInfo* info, void* ctx) * info->keystore.op..keyRef */ return Port_Keystore(info); case WC_ALGO_TYPE_CIPHER: - /* aes->idLen != 0: run with the slot named by aes->id */ + /* aes->idLen != 0: run with the slot named by aes->id. Key + * wrap's AES-GCM arrives here too */ return Port_Aes(info); case WC_ALGO_TYPE_CMAC: /* cmac->idLen != 0: run with the slot named by cmac->id */ @@ -799,11 +795,11 @@ The usage flags constrain which cryptographic operations a given key may partici 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. Requests that use a [hardware key](#hardware-only-keys) are checked the same way, against the usage flags the hardware key took from its source key. +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. Requests that use a [hardware key](#hardware-only-keys) are checked the same way, against the usage flags in the server's record of the key: those of the source key for a key a client loaded, or those the port registered for a port key. 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. With `WOLFHSM_CFG_HWKEYSTORE_CRYPTO`, revoking or erasing a key also clears every [hardware key](#hardware-only-keys) loaded from it. +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. With `WOLFHSM_CFG_HWKEYSTORE_LOAD`, revoking or erasing a key also clears every [hardware key](#hardware-only-keys) loaded from it. Concrete examples of policy-driven scenarios that this machinery supports: diff --git a/docs/src/9-Configuration.md b/docs/src/9-Configuration.md index 91ed0b00b..63f95a8c6 100644 --- a/docs/src/9-Configuration.md +++ b/docs/src/9-Configuration.md @@ -76,8 +76,10 @@ These macros enable or tune optional cryptographic subsystems built on top of wo | `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`, or `WOLFHSM_CFG_COMM_DATA_LEN` minus the request overhead when that is smaller | 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. The key plus its request header and metadata must fit within `WOLFHSM_CFG_COMM_DATA_LEN`; the default is derived so that it always does, and an explicit value that does not fit fails to build. | | `WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE` | `2000`, or `WOLFHSM_CFG_COMM_DATA_LEN` minus the request overhead when that is smaller | 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. The payload plus its request header must fit within `WOLFHSM_CFG_COMM_DATA_LEN`; the default is derived so that it always does, and an explicit value that does not fit fails to build. | -| `WOLFHSM_CFG_HWKEYSTORE` | Undefined | If defined, compile the hardware keystore front-end (`wh_HwKeystore_*`) and hardware-only key support (`WH_KEYTYPE_HW`, `WH_CLIENT_KEYID_MAKE_HW()`). Hardware-only keys are served on demand by a user-supplied callback and are usable only as keywrap KEKs; they never enter the key cache or NVM and are never exported. See [Hardware-Only Keys](5-Features.md#hardware-only-keys). | -| `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` | `32` | Maximum size, in bytes, of a key served by the hardware keystore backend; sizes the local buffer that holds a hardware KEK for the duration of a keywrap operation. Only consulted when `WOLFHSM_CFG_HWKEYSTORE` is defined. | +| `WOLFHSM_CFG_HWKEYSTORE` | Undefined | If defined, compile the hardware keystore front end (`wh_HwKeystore_*`) and hardware key support (`WH_KEYTYPE_HW`, `WH_CLIENT_KEYID_MAKE_HW()`). The port registers hardware keys, and clients use them in crypto operations and as key wrap KEKs (see [Supported Functionality](5-Features.md#supported-functionality)). The server passes a hardware key to wolfCrypt by reference, so the port's crypto callback runs every operation on it; the key never enters the key cache or NVM and is never exported. Requires `WOLF_PRIVATE_KEY_ID` in wolfCrypt. See [Hardware-Only Keys](5-Features.md#hardware-only-keys). | +| `WOLFHSM_CFG_HWKEYSTORE_LOAD` | Undefined | If defined, clients can also load keystore keys into hardware keys, clear them, and describe hardware keys. Requires `WOLFHSM_CFG_HWKEYSTORE`, and `WOLF_CRYPTO_CB_KEYSTORE` in wolfCrypt. Incompatible with `WOLFHSM_CFG_NO_CRYPTO`. | +| `WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS` | `8` | Number of hardware keys the server can keep records for, counting port keys and keys clients load. Only consulted when `WOLFHSM_CFG_HWKEYSTORE` is defined. | +| `WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE` | `32` | Largest keystore key, in bytes, a client can load into a hardware key; sizes the local buffer that holds the key during the load. Only consulted when `WOLFHSM_CFG_HWKEYSTORE_LOAD` 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. | | `WOLFHSM_CFG_SHE_GLOBAL_KEYS` | Undefined | If defined, all sixteen AUTOSAR SHE key slots live in the global-keys namespace (USER=0) so every connected client shares a single SHE device view, matching AUTOSAR's one-physical-SHE model rather than wolfHSM's default per-client SHE slots. Requires `WOLFHSM_CFG_GLOBAL_KEYS` and `WOLFHSM_CFG_SHE_EXTENSION` (enforced by `#error`). See [Global SHE Keys](5-Features.md#global-she-keys). | | `WH_DEV_ID` | `0x5748534D` (`"WHSM"`) | Value of the process-global crypto device ID registered by every `wh_Client_Init()` with the unified client crypto callback. Also the device ID bound to a client whose `whClientConfig.devId` is left `0`. Override it if the default collides with another crypto-callback device ID in the application. See [Transparent Offload via Crypto Callbacks](5-Features.md#transparent-offload-via-crypto-callbacks) for registration lifetime, multi-client rules, and wolfCrypt callback-table sizing (`MAX_CRYPTO_DEVID_CALLBACKS`, default 8). | diff --git a/src/wh_hwkeystore.c b/src/wh_hwkeystore.c index d334ae89d..97ebbe18d 100644 --- a/src/wh_hwkeystore.c +++ b/src/wh_hwkeystore.c @@ -19,8 +19,8 @@ /* * src/wh_hwkeystore.c * - * Hardware keystore front-end implementation. Dispatches to a backend callback - * table (whHwKeystoreCb) with argument validation and lock serialization. See + * Hardware keystore front end: the records of the hardware keys clients may + * use, and the calls into the port's keystore callback. See * wolfhsm/wh_hwkeystore.h for the module description. */ @@ -36,7 +36,7 @@ #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_hwkeystore.h" -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#ifdef WOLFHSM_CFG_HWKEYSTORE_LOAD #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/cryptocb.h" #include "wolfssl/wolfcrypt/wc_keystore.h" @@ -45,8 +45,7 @@ int wh_HwKeystore_Init(whHwKeystoreContext* context, const whHwKeystoreConfig* config) { - if ((context == NULL) || (config == NULL) || (config->cb == NULL) || - (config->cb->GetKey == NULL)) { + if ((context == NULL) || (config == NULL)) { return WH_ERROR_BADARGS; } @@ -71,7 +70,7 @@ int wh_HwKeystore_Init(whHwKeystoreContext* context, /* Initialize the backend if it provides an Init callback. Done last so it * is the only fallible step after the lock: on failure, undo the lock so a * successful backend Init is never left without a paired Cleanup */ - if (context->cb->Init != NULL) { + if ((context->cb != NULL) && (context->cb->Init != NULL)) { int rc = context->cb->Init(context->context, config->config); if (rc != WH_ERROR_OK) { #ifdef WOLFHSM_CFG_THREADSAFE @@ -115,42 +114,6 @@ int wh_HwKeystore_Cleanup(whHwKeystoreContext* context) return rc; } -int wh_HwKeystore_GetKey(whHwKeystoreContext* context, whKeyId keyId, - uint8_t* out, uint16_t* inout_len) -{ - int rc; - - if ((context == NULL) || (context->initialized == 0) || - (context->cb == NULL) || (context->cb->GetKey == NULL) || - (out == NULL) || (inout_len == NULL)) { - return WH_ERROR_BADARGS; - } - -#ifdef WOLFHSM_CFG_THREADSAFE - rc = wh_Lock_Acquire(&context->lock); - if (rc != WH_ERROR_OK) { - return rc; - } -#endif /* WOLFHSM_CFG_THREADSAFE */ - - rc = context->cb->GetKey(context->context, keyId, out, inout_len); - -#ifdef WOLFHSM_CFG_THREADSAFE - { - /* Preserve the backend result; only surface a release failure when the - * GetKey call itself succeeded so a real lock error is not hidden */ - int relRc = wh_Lock_Release(&context->lock); - if (rc == WH_ERROR_OK) { - rc = relRc; - } - } -#endif /* WOLFHSM_CFG_THREADSAFE */ - - return rc; -} - -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO - static int _Lock(whHwKeystoreContext* context) { #ifdef WOLFHSM_CFG_THREADSAFE @@ -204,40 +167,6 @@ static whHwKeystoreKey* _FindFree(whHwKeystoreContext* context) return NULL; } -static int _PortImport(int devId, whKeyId keyId, const uint8_t* key, - uint16_t keySz) -{ - uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; - - wh_HwKeystore_KeyRef(keyId, ref); - return wc_KeyStore_ImportPlain(devId, ref, sizeof(ref), WC_KEYSTORE_KEY_AES, - key, keySz, 0, NULL); -} - -static int _PortInfo(int devId, whKeyId keyId, word32* keyType, word32* keyBits) -{ - uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; - word32 attrs = 0; - - wh_HwKeystore_KeyRef(keyId, ref); - return wc_KeyStore_GetInfo(devId, ref, sizeof(ref), keyType, keyBits, - &attrs, NULL); -} - -/* Clear the key in hardware, then free its entry */ -static int _Remove(int devId, whHwKeystoreKey* entry) -{ - uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; - int ret; - - wh_HwKeystore_KeyRef(entry->id, ref); - ret = wc_KeyStore_Delete(devId, ref, sizeof(ref), NULL); - if (ret == 0) { - memset(entry, 0, sizeof(*entry)); - } - return ret; -} - void wh_HwKeystore_KeyRef(whKeyId keyId, uint8_t* ref) { ref[0] = (uint8_t)(keyId >> 8); @@ -267,8 +196,11 @@ int wh_HwKeystore_Register(whHwKeystoreContext* context, whKeyId keyId, else { entry->id = keyId; entry->srcId = WH_KEYID_ERASED; + /* Clients can't clear or replace a port key, or set or + * read it, so it is also a trusted KEK */ entry->flags = (flags & WH_NVM_FLAGS_USAGE_ANY) | - WH_NVM_FLAGS_NONDESTROYABLE; + WH_NVM_FLAGS_NONDESTROYABLE | + WH_NVM_FLAGS_TRUSTED; entry->len = len; } } @@ -277,6 +209,79 @@ int wh_HwKeystore_Register(whHwKeystoreContext* context, whKeyId keyId, return ret; } +int wh_HwKeystore_CheckUse(whHwKeystoreContext* context, whKeyId keyId, + whNvmFlags flags, whKeyId* outKeyId) +{ + whHwKeystoreKey* entry; + whNvmFlags usage; + int ret; + + if (!_Valid(context, keyId) || (outKeyId == NULL)) { + return WH_ERROR_BADARGS; + } + usage = flags & WH_NVM_FLAGS_USAGE_ANY; + + ret = _Lock(context); + if (ret == WH_ERROR_OK) { + entry = _Find(context, keyId); + if (entry == NULL) { + ret = WH_ERROR_NOTFOUND; + } + else if ((WH_KEYID_USER(entry->id) != WH_KEYUSER_GLOBAL) && + (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { + ret = WH_ERROR_ACCESS; + } + else if (((flags & WH_NVM_FLAGS_TRUSTED) != 0) && + ((entry->flags & WH_NVM_FLAGS_TRUSTED) == 0)) { + ret = WH_ERROR_ACCESS; + } + else if ((usage == 0) || ((entry->flags & usage) != usage)) { + ret = WH_ERROR_USAGE; + } + else { + *outKeyId = entry->id; + } + (void)_Unlock(context); + } /* _Lock() */ + return ret; +} + +#ifdef WOLFHSM_CFG_HWKEYSTORE_LOAD + +static int _PortImport(int devId, whKeyId keyId, const uint8_t* key, + uint16_t keySz) +{ + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + + wh_HwKeystore_KeyRef(keyId, ref); + return wc_KeyStore_ImportPlain(devId, ref, sizeof(ref), WC_KEYSTORE_KEY_AES, + key, keySz, 0, NULL); +} + +static int _PortInfo(int devId, whKeyId keyId, word32* keyType, word32* keyBits) +{ + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + word32 attrs = 0; + + wh_HwKeystore_KeyRef(keyId, ref); + return wc_KeyStore_GetInfo(devId, ref, sizeof(ref), keyType, keyBits, + &attrs, NULL); +} + +/* Clear the key in hardware, then free its entry */ +static int _Remove(int devId, whHwKeystoreKey* entry) +{ + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + int ret; + + wh_HwKeystore_KeyRef(entry->id, ref); + ret = wc_KeyStore_Delete(devId, ref, sizeof(ref), NULL); + if (ret == 0) { + memset(entry, 0, sizeof(*entry)); + } + return ret; +} + int wh_HwKeystore_Load(whHwKeystoreContext* context, int devId, whKeyId* inout_keyId, whKeyId srcId, whNvmFlags flags, const uint8_t* key, uint16_t keySz) @@ -434,38 +439,6 @@ int wh_HwKeystore_ClearSource(whHwKeystoreContext* context, int devId, return ret; } -int wh_HwKeystore_CheckUse(whHwKeystoreContext* context, whKeyId keyId, - whNvmFlags usage, whKeyId* outKeyId) -{ - whHwKeystoreKey* entry; - int ret; - - if (!_Valid(context, keyId) || (outKeyId == NULL)) { - return WH_ERROR_BADARGS; - } - usage &= WH_NVM_FLAGS_USAGE_ANY; - - ret = _Lock(context); - if (ret == WH_ERROR_OK) { - entry = _Find(context, keyId); - if (entry == NULL) { - ret = WH_ERROR_NOTFOUND; - } - else if ((WH_KEYID_USER(entry->id) != WH_KEYUSER_GLOBAL) && - (WH_KEYID_USER(entry->id) != WH_KEYID_USER(keyId))) { - ret = WH_ERROR_ACCESS; - } - else if ((usage == 0) || ((entry->flags & usage) != usage)) { - ret = WH_ERROR_USAGE; - } - else { - *outKeyId = entry->id; - } - (void)_Unlock(context); - } /* _Lock() */ - return ret; -} - int wh_HwKeystore_GetInfo(whHwKeystoreContext* context, int devId, whKeyId keyId, whHwKeystoreInfo* info) { @@ -496,6 +469,6 @@ int wh_HwKeystore_GetInfo(whHwKeystoreContext* context, int devId, return ret; } -#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ +#endif /* WOLFHSM_CFG_HWKEYSTORE_LOAD */ #endif /* WOLFHSM_CFG_HWKEYSTORE */ diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 8feddaebc..4ae820e78 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -3178,13 +3178,13 @@ static int _HandleEd25519VerifyDma(whServerContext* ctx, uint16_t magic, static int _HwKeyCheckUse(whServerContext* ctx, int devId, whKeyId keyId, whNvmFlags usage, whKeyId* outHwKey) { -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#ifdef WOLFHSM_CFG_HWKEYSTORE if ((ctx->hwKeystore == NULL) || (devId == INVALID_DEVID)) { return WH_ERROR_NOTFOUND; } return wh_HwKeystore_CheckUse(ctx->hwKeystore, keyId, usage, outHwKey); #else - /* Without it, hardware keys are only key encryption keys */ + /* No hardware keystore is built in */ (void)ctx; (void)devId; (void)keyId; @@ -3198,7 +3198,7 @@ static int _HwKeyCheckUse(whServerContext* ctx, int devId, whKeyId keyId, * key is set, and the crypto callback runs every operation */ static int _AesInitForKey(Aes* aes, int devId, whKeyId hwKey) { -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#ifdef WOLFHSM_CFG_HWKEYSTORE if (hwKey != WH_KEYID_ERASED) { uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; @@ -4695,7 +4695,7 @@ static int _CmacResolveKey(whServerContext* ctx, int devId, static int _CmacInitForKey(Cmac* cmac, const uint8_t* key, uint32_t keyLen, whKeyId hwKey, int devId) { -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#ifdef WOLFHSM_CFG_HWKEYSTORE if (hwKey != WH_KEYID_ERASED) { uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index 53baf3154..6d2df04ca 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -745,7 +745,7 @@ int wh_Server_KeystoreGetUniqueId(whServerContext* server, whNvmId* inout_id) } #ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only key ids are assigned by the hardware backend */ + /* The port defines hardware key ids, so the keystore never assigns one */ if (type == WH_KEYTYPE_HW) { return WH_ERROR_BADARGS; } @@ -999,9 +999,9 @@ int wh_Server_KeystoreFreshenKey(whServerContext* server, whKeyId keyId, } #ifdef WOLFHSM_CFG_HWKEYSTORE - /* Hardware-only keys are never cached and are not usable through the - * keystore. The keywrap KEK path fetches them directly from the hardware - * keystore instead */ + /* Hardware keys are never cached and are not usable through the + * keystore. Key wrap and the crypto handlers use them by reference + * instead */ if (WH_KEYID_ISHW(keyId)) { return WH_ERROR_ACCESS; } @@ -1301,7 +1301,7 @@ int wh_Server_KeystoreCommitKeyChecked(whServerContext* server, whNvmId keyId) /* An erased or revoked key must not stay usable through a hardware copy */ static void _ClearHwCopies(whServerContext* server, whKeyId keyId) { -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#ifdef WOLFHSM_CFG_HWKEYSTORE_LOAD if (server->hwKeystore != NULL) { (void)wh_HwKeystore_ClearSource(server->hwKeystore, server->devId, keyId); @@ -1468,7 +1468,7 @@ int wh_Server_KeystoreRevokeKey(whServerContext* server, whNvmId keyId) return ret; } -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#ifdef WOLFHSM_CFG_HWKEYSTORE_LOAD int wh_Server_KeystoreHwLoad(whServerContext* server, whKeyId* inout_hwId, whKeyId srcId) { @@ -1511,52 +1511,56 @@ int wh_Server_KeystoreHwLoad(whServerContext* server, whKeyId* inout_hwId, wh_Utils_ForceZero(key, sizeof(key)); return ret; } -#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ +#endif /* WOLFHSM_CFG_HWKEYSTORE_LOAD */ #ifdef WOLFHSM_CFG_KEYWRAP #ifndef NO_AES #ifdef HAVE_AESGCM -/* Resolve the KEK for a keywrap operation. Hardware-only KEKs (TYPE=HW) - * are fetched from the server's hardware keystore into hwKekBuf, which the - * caller must keep local and zeroize after use; they carry no NVM metadata, - * so usage policy is delegated to the hardware keystore backend. All other - * KEKs are freshened into the key cache and must carry WH_NVM_FLAGS_USAGE_WRAP. +/* The KEK of a keywrap operation: key bytes from the keystore, or a hardware + * key the port uses by reference */ +typedef struct { + const uint8_t* key; + uint32_t keySz; + whKeyId hwKey; /* WH_KEYID_ERASED unless a hardware key */ +} whKeywrapKek; + +/* Resolve the KEK for a keywrap operation. A hardware KEK (TYPE=HW) needs a + * record allowing WH_NVM_FLAGS_USAGE_WRAP and is used by reference, so its + * bytes never leave the hardware. All other KEKs are freshened into the key + * cache and must carry WH_NVM_FLAGS_USAGE_WRAP. * * When enforceTrustedKek is nonzero the KEK must be one the client cannot know - * or set: a hardware key (returned above) or a software key carrying - * WH_NVM_FLAGS_TRUSTED. This is the unified eligibility predicate - * isTrustedKek = WH_KEYID_ISHW(id) || (flags & WH_NVM_FLAGS_TRUSTED), required - * by the ops that move a server secret across the client boundary - * (KeyWrapExport, KeyUnwrapAndCache). */ + * or set: a port key (its hardware record is WH_NVM_FLAGS_TRUSTED) or a + * software key carrying WH_NVM_FLAGS_TRUSTED. The ops that move a server + * secret across the client boundary (KeyWrapExport, KeyUnwrapAndCache) + * require this. */ static int _KeywrapResolveKek(whServerContext* server, whKeyId serverKeyId, - int enforceTrustedKek, uint8_t* hwKekBuf, - uint16_t hwKekBufSz, const uint8_t** outKek, - uint32_t* outKekSz) + int enforceTrustedKek, whKeywrapKek* outKek) { int ret; whNvmMetadata* kekMeta = NULL; uint8_t* kek = NULL; + outKek->key = NULL; + outKek->keySz = 0; + outKek->hwKey = WH_KEYID_ERASED; + #ifdef WOLFHSM_CFG_HWKEYSTORE if (WH_KEYID_ISHW(serverKeyId)) { - uint16_t hwKekSz = hwKekBufSz; - if (server->hwKeystore == NULL) { - /* No hardware keystore bound to this server */ + whNvmFlags need = WH_NVM_FLAGS_USAGE_WRAP; + + if ((server->hwKeystore == NULL) || (server->devId == INVALID_DEVID)) { + /* No hardware keystore, or no device to run the key */ return WH_ERROR_NOTFOUND; } - ret = wh_HwKeystore_GetKey(server->hwKeystore, serverKeyId, hwKekBuf, - &hwKekSz); - if (ret == WH_ERROR_OK) { - *outKek = hwKekBuf; - *outKekSz = hwKekSz; + if (enforceTrustedKek) { + need |= WH_NVM_FLAGS_TRUSTED; } - return ret; + return wh_HwKeystore_CheckUse(server->hwKeystore, serverKeyId, need, + &outKek->hwKey); } -#else - (void)hwKekBuf; - (void)hwKekBufSz; #endif /* WOLFHSM_CFG_HWKEYSTORE */ ret = wh_Server_KeystoreFreshenKey(server, serverKeyId, &kek, &kekMeta); @@ -1570,34 +1574,49 @@ static int _KeywrapResolveKek(whServerContext* server, whKeyId serverKeyId, return ret; } - /* A hardware KEK already returned above and is inherently trusted; a - * software KEK qualifies only if it was provisioned with - * WH_NVM_FLAGS_TRUSTED (which a client can never set). */ + /* A software KEK is trusted only if it was provisioned with + * WH_NVM_FLAGS_TRUSTED (which a client can never set) */ if (enforceTrustedKek && !(kekMeta->flags & WH_NVM_FLAGS_TRUSTED)) { return WH_ERROR_ACCESS; } - *outKek = kek; - *outKekSz = kekMeta->len; + outKek->key = kek; + outKek->keySz = kekMeta->len; return WH_ERROR_OK; } -/* Size of the local storage each keywrap helper provides for a hardware-only - * KEK. Minimal when no hardware keystore is configured, since the resolver - * then never writes to it */ +/* Set up aes with the KEK: by reference for a hardware key, else with its key + * bytes. Nothing needs freeing on failure */ +static int _KeywrapAesInit(whServerContext* server, Aes* aes, + const whKeywrapKek* kek) +{ + int ret; + #ifdef WOLFHSM_CFG_HWKEYSTORE -#define WH_KEYWRAP_HWKEK_BUF_SIZE WOLFHSM_CFG_HWKEYSTORE_MAX_KEY_SIZE -#else -#define WH_KEYWRAP_HWKEK_BUF_SIZE 1 + if (kek->hwKey != WH_KEYID_ERASED) { + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + + wh_HwKeystore_KeyRef(kek->hwKey, ref); + return wc_AesInit_Id(aes, ref, (int)sizeof(ref), NULL, server->devId); + } #endif /* WOLFHSM_CFG_HWKEYSTORE */ + ret = wc_AesInit(aes, NULL, server->devId); + if (ret == 0) { + ret = wc_AesGcmSetKey(aes, kek->key, kek->keySz); + if (ret != 0) { + wc_AesFree(aes); + } + } + return ret; +} + static const uint8_t WH_KEYWRAP_AAD_KEY[] = WH_KEYWRAP_AAD_KEY_STR; static const uint8_t WH_KEYWRAP_AAD_DATA[] = WH_KEYWRAP_AAD_DATA_STR; -static int _AesGcmKeyWrapWithKek(whServerContext* server, - const uint8_t* serverKey, uint32_t serverKeySz, - uint8_t* keyIn, uint16_t keySz, - whNvmMetadata* metadataIn, +static int _AesGcmKeyWrapWithKek(whServerContext* server, + const whKeywrapKek* kek, uint8_t* keyIn, + uint16_t keySz, whNvmMetadata* metadataIn, uint8_t* wrappedKeyOut, uint16_t wrappedKeySz) { int ret = 0; @@ -1608,7 +1627,7 @@ static int _AesGcmKeyWrapWithKek(whServerContext* server, uint32_t plainBlobSz = sizeof(*metadataIn) + keySz; uint8_t* encBlob; - if (server == NULL || keyIn == NULL || metadataIn == NULL || + if (server == NULL || kek == NULL || keyIn == NULL || metadataIn == NULL || wrappedKeyOut == NULL || plainBlobSz > sizeof(plainBlob)) { return WH_ERROR_BADARGS; } @@ -1619,15 +1638,9 @@ static int _AesGcmKeyWrapWithKek(whServerContext* server, return WH_ERROR_BUFFER_SIZE; } - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); - if (ret != 0) { - return ret; - } - - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { - wc_AesFree(aes); return ret; } @@ -1668,33 +1681,25 @@ static int _AesGcmKeyWrap(whServerContext* server, whKeyId serverKeyId, whNvmMetadata* metadataIn, uint8_t* wrappedKeyOut, uint16_t wrappedKeySz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; } /* Get the server side key (KEK) */ - ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, &kek); if (ret == WH_ERROR_OK) { - ret = - _AesGcmKeyWrapWithKek(server, serverKey, serverKeySz, keyIn, keySz, - metadataIn, wrappedKeyOut, wrappedKeySz); + ret = _AesGcmKeyWrapWithKek(server, &kek, keyIn, keySz, metadataIn, + wrappedKeyOut, wrappedKeySz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - return ret; } -static int _AesGcmKeyUnwrapWithKek(whServerContext* server, - const uint8_t* serverKey, - uint32_t serverKeySz, void* wrappedKeyIn, +static int _AesGcmKeyUnwrapWithKek(whServerContext* server, + const whKeywrapKek* kek, void* wrappedKeyIn, uint16_t wrappedKeySz, whNvmMetadata* metadataOut, void* keyOut, uint16_t keySz) @@ -1707,8 +1712,9 @@ static int _AesGcmKeyUnwrapWithKek(whServerContext* server, uint16_t encBlobSz; uint8_t plainBlob[sizeof(*metadataOut) + WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE]; - if (server == NULL || wrappedKeyIn == NULL || metadataOut == NULL || - keyOut == NULL || keySz > WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE) { + if (server == NULL || kek == NULL || wrappedKeyIn == NULL || + metadataOut == NULL || keyOut == NULL || + keySz > WOLFHSM_CFG_KEYWRAP_MAX_KEY_SIZE) { return WH_ERROR_BADARGS; } @@ -1719,18 +1725,12 @@ static int _AesGcmKeyUnwrapWithKek(whServerContext* server, encBlob = (uint8_t*)wrappedKeyIn + sizeof(iv) + sizeof(authTag); encBlobSz = wrappedKeySz - sizeof(iv) - sizeof(authTag); - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { return ret; } - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); - if (ret != 0) { - wc_AesFree(aes); - return ret; - } - /* Extract IV and authTag from wrappedKeyIn */ memcpy(iv, wrappedKeyIn, sizeof(iv)); memcpy(authTag, (const uint8_t*)wrappedKeyIn + sizeof(iv), sizeof(authTag)); @@ -1758,33 +1758,25 @@ static int _AesGcmKeyUnwrap(whServerContext* server, uint16_t serverKeyId, uint16_t wrappedKeySz, whNvmMetadata* metadataOut, void* keyOut, uint16_t keySz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; } /* Get the server side key (KEK) */ - ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, requireTrustedKek, &kek); if (ret == WH_ERROR_OK) { - ret = _AesGcmKeyUnwrapWithKek(server, serverKey, serverKeySz, - wrappedKeyIn, wrappedKeySz, metadataOut, - keyOut, keySz); + ret = _AesGcmKeyUnwrapWithKek(server, &kek, wrappedKeyIn, wrappedKeySz, + metadataOut, keyOut, keySz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - return ret; } -static int _AesGcmDataWrapWithKek(whServerContext* server, - const uint8_t* serverKey, - uint32_t serverKeySz, uint8_t* dataIn, +static int _AesGcmDataWrapWithKek(whServerContext* server, + const whKeywrapKek* kek, uint8_t* dataIn, uint16_t dataSz, uint8_t* wrappedDataOut, uint16_t wrappedDataSz) { @@ -1794,8 +1786,8 @@ static int _AesGcmDataWrapWithKek(whServerContext* server, uint8_t iv[WH_KEYWRAP_AES_GCM_IV_SIZE]; uint8_t* encBlob; - if (server == NULL || dataIn == NULL || wrappedDataOut == NULL || - dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { + if (server == NULL || kek == NULL || dataIn == NULL || + wrappedDataOut == NULL || dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { return WH_ERROR_BADARGS; } @@ -1804,18 +1796,12 @@ static int _AesGcmDataWrapWithKek(whServerContext* server, return WH_ERROR_BUFFER_SIZE; } - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { return ret; } - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); - if (ret != 0) { - wc_AesFree(aes); - return ret; - } - /* Generate the IV */ ret = wc_RNG_GenerateBlock(server->crypto->rng, iv, sizeof(iv)); if (ret != 0) { @@ -1845,10 +1831,8 @@ static int _AesGcmDataWrap(whServerContext* server, whKeyId serverKeyId, uint8_t* dataIn, uint16_t dataSz, uint8_t* wrappedDataOut, uint16_t wrappedDataSz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; @@ -1857,24 +1841,19 @@ static int _AesGcmDataWrap(whServerContext* server, whKeyId serverKeyId, /* Get the server side key (KEK). Data wrap requires the KEK to carry * USAGE_WRAP, but not to be a trusted KEK (no server secret crosses the * boundary). */ - ret = _KeywrapResolveKek(server, serverKeyId, 0, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, 0, &kek); if (ret == WH_ERROR_OK) { - ret = _AesGcmDataWrapWithKek(server, serverKey, serverKeySz, dataIn, - dataSz, wrappedDataOut, wrappedDataSz); + ret = _AesGcmDataWrapWithKek(server, &kek, dataIn, dataSz, + wrappedDataOut, wrappedDataSz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - return ret; } -static int _AesGcmDataUnwrapWithKek(whServerContext* server, - const uint8_t* serverKey, - uint32_t serverKeySz, void* wrappedDataIn, - uint16_t wrappedDataSz, void* dataOut, - uint16_t dataSz) +static int _AesGcmDataUnwrapWithKek(whServerContext* server, + const whKeywrapKek* kek, + void* wrappedDataIn, uint16_t wrappedDataSz, + void* dataOut, uint16_t dataSz) { int ret = 0; Aes aes[1]; @@ -1883,8 +1862,8 @@ static int _AesGcmDataUnwrapWithKek(whServerContext* server, uint8_t* encBlob; uint16_t encBlobSz; - if (server == NULL || wrappedDataIn == NULL || dataOut == NULL || - dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { + if (server == NULL || kek == NULL || wrappedDataIn == NULL || + dataOut == NULL || dataSz > WOLFHSM_CFG_KEYWRAP_MAX_DATA_SIZE) { return WH_ERROR_BADARGS; } @@ -1899,15 +1878,9 @@ static int _AesGcmDataUnwrapWithKek(whServerContext* server, return WH_ERROR_BUFFER_SIZE; } - /* Initialize AES context and set it to use the server side key */ - ret = wc_AesInit(aes, NULL, server->devId); - if (ret != 0) { - return ret; - } - - ret = wc_AesGcmSetKey(aes, serverKey, serverKeySz); + /* Initialize AES context with the KEK */ + ret = _KeywrapAesInit(server, aes, kek); if (ret != 0) { - wc_AesFree(aes); return ret; } @@ -1928,10 +1901,8 @@ static int _AesGcmDataUnwrap(whServerContext* server, uint16_t serverKeyId, void* wrappedDataIn, uint16_t wrappedDataSz, void* dataOut, uint16_t dataSz) { - int ret; - const uint8_t* serverKey = NULL; - uint32_t serverKeySz = 0; - uint8_t hwKek[WH_KEYWRAP_HWKEK_BUF_SIZE]; + int ret; + whKeywrapKek kek; if (server == NULL) { return WH_ERROR_BADARGS; @@ -1940,17 +1911,12 @@ static int _AesGcmDataUnwrap(whServerContext* server, uint16_t serverKeyId, /* Get the server side key (KEK). Data unwrap requires the KEK to carry * USAGE_WRAP, but not to be a trusted KEK (no server secret crosses the * boundary). */ - ret = _KeywrapResolveKek(server, serverKeyId, 0, hwKek, - (uint16_t)sizeof(hwKek), &serverKey, &serverKeySz); + ret = _KeywrapResolveKek(server, serverKeyId, 0, &kek); if (ret == WH_ERROR_OK) { - ret = _AesGcmDataUnwrapWithKek(server, serverKey, serverKeySz, - wrappedDataIn, wrappedDataSz, dataOut, - dataSz); + ret = _AesGcmDataUnwrapWithKek(server, &kek, wrappedDataIn, + wrappedDataSz, dataOut, dataSz); } - /* Wipe any hardware KEK material from local storage */ - wh_Utils_ForceZero(hwKek, sizeof(hwKek)); - return ret; } @@ -2153,8 +2119,8 @@ _HandleKeyWrapExportRequest(whServerContext* server, } /* Wrap the key with its real metadata. This extracts a server-held - * secret to the client, so the KEK must be a trusted (HW or - * WH_NVM_FLAGS_TRUSTED) key the client cannot know. */ + * secret to the client, so the KEK must be a trusted key (a port + * key or WH_NVM_FLAGS_TRUSTED) the client cannot know. */ ret = _AesGcmKeyWrap(server, serverKeyId, /*requireTrustedKek=*/1, key, (uint16_t)keySz, &metadata, wrappedKeyStage, wrappedKeySz); @@ -2399,8 +2365,8 @@ static int _HandleKeyUnwrapAndCacheRequest( } /* Unwrap-and-cache injects a key into the server keystore, so the - * KEK must be a trusted (HW or WH_NVM_FLAGS_TRUSTED) key, else a - * client could forge a blob under a KEK it knows. */ + * KEK must be a trusted key (a port key or WH_NVM_FLAGS_TRUSTED), + * else a client could forge a blob under a KEK it knows. */ ret = _AesGcmKeyUnwrap(server, serverKeyId, /*requireTrustedKek=*/1, wrappedKey, req->wrappedKeySz, &metadata, keyStage, @@ -3723,7 +3689,7 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, #endif /* WOLFHSM_CFG_KEYWRAP */ -#ifdef WOLFHSM_CFG_HWKEYSTORE_CRYPTO +#ifdef WOLFHSM_CFG_HWKEYSTORE_LOAD case WH_KEY_HW_LOAD: { whMessageKeystore_HwLoadRequest req = {0}; whMessageKeystore_HwLoadResponse resp = {0}; @@ -3844,7 +3810,7 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic, magic, &resp, (whMessageKeystore_HwInfoResponse*)resp_packet); *out_resp_size = sizeof(resp); } break; -#endif /* WOLFHSM_CFG_HWKEYSTORE_CRYPTO */ +#endif /* WOLFHSM_CFG_HWKEYSTORE_LOAD */ default: ret = WH_ERROR_NOHANDLER; diff --git a/test-refactor/README.md b/test-refactor/README.md index 6dadc3f33..984e9f461 100644 --- a/test-refactor/README.md +++ b/test-refactor/README.md @@ -116,7 +116,7 @@ Not yet migrated (still live in `wolfHSM/test/`): | `wh_test_comm.c::whTest_Comm` | Pthread mem/tcp/shmem variants only; sequential mem variant has been ported | | `wh_test_clientserver.c::whTest_ClientServer` | Pthread variant: remaining coverage is the custom-callback round-trip (`_testCallbacks`) and the server-side DMA register/copy/allowlist exercise (`_testDma`). The sequential test is dropped, as is the FLASH_LOG NVM matrix variant. | | `wh_test_crypto.c::whTest_Crypto` | Remaining crypto coverage not yet split out: the AES async family (comm-buffer `whTest_CryptoAesAsync`/`AesAsyncKat` + DMA `whTest_CryptoAesDmaAsync`/`AesDmaAsyncKat`, round-trip & KAT). ECC DMA export-public and the ML-DSA wolfCrypt-API path are now migrated. | -| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | Policy coverage (sw-KEK round trip, trusted-KEK negatives, data-wrap usage) and the key/data unwrap underflow negatives are ported to `client-server/wh_test_keywrap.c`; the remaining legacy coverage (hardware-KEK cryptocb use of a cached wrapped key, unwrap oversize negatives) is not | +| `wh_test_keywrap.c::whTest_KeyWrapClientConfig` | Policy coverage (sw-KEK round trip, trusted-KEK negatives, data-wrap usage) and the key/data unwrap underflow negatives are ported to `client-server/wh_test_keywrap.c`; the unwrap oversize negatives are not. The legacy hardware-KEK tests, including cryptocb use of a cached wrapped key, no longer build: legacy `test/` does not enable `WOLFHSM_CFG_HWKEYSTORE` | | `wh_test_timeout.c::whTest_TimeoutPosix` | | | `wh_test_nvmflags.c::whTest_NvmFlags` | | | `wh_test_flash_fault_inject.c` | | diff --git a/test-refactor/misc/wh_test_hwkeystore.c b/test-refactor/misc/wh_test_hwkeystore.c index 1a1c31c91..c5c1516bc 100644 --- a/test-refactor/misc/wh_test_hwkeystore.c +++ b/test-refactor/misc/wh_test_hwkeystore.c @@ -19,18 +19,19 @@ /* * test-refactor/misc/wh_test_hwkeystore.c * - * End-to-end hardware-only key (WH_KEYTYPE_HW) coverage. Lives in the - * misc group because it requires a server with a known hardware keystore - * backend bound, which the port's shared server does not guarantee: this - * test spins up its own client/server pair over the mem transport, binds an - * emulated hardware keystore serving a single AES-256 KEK, and pumps the - * server inline between the split (non-blocking) client request/response - * calls so no threading is needed. + * End-to-end coverage of hardware keys (WH_KEYTYPE_HW) as key wrap KEKs. Lives + * in the misc group because it needs a server with a known hardware keystore, + * which the port's shared server does not guarantee: this test spins up its + * own client/server pair over the mem transport and pumps the server inline + * between the split (non-blocking) client request/response calls, so no + * threading is needed. A mock wolfCrypt device holds one AES-256 KEK, which + * the port registers as hardware key WH_TEST_HWKEK_ID, and runs AES-GCM with + * it when the server passes the key by reference. * * _whTest_HwKeystoreKeyWrap - key wrap / unwrap-export / unwrap-and-cache - * with a hardware-only KEK; wrapped+hwonly - * flag precedence; unserved-id and wrong-KEK - * negative paths + * with the hardware KEK; wrapped+hardware + * flag precedence; unregistered-id and + * wrong-KEK negative paths * _whTest_HwKeystoreWrappedKeyNoNvm - an unwrapped key is cache-only, so * commit and erase must reject a wrapped key * id while eviction still works @@ -38,14 +39,17 @@ * (never presenting plaintext) under the * hardware KEK, round-trip it, and enforce * NONEXPORTABLE - * _whTest_HwKeystoreDataWrap - data wrap/unwrap roundtrip with a - * hardware-only KEK - * _whTest_HwKeystoreRejections - keystore operations on a hardware-only id + * _whTest_HwKeystoreDataWrap - data wrap/unwrap roundtrip with the + * hardware KEK + * _whTest_HwKeystoreRejections - keystore operations on a hardware key id * must fail with WH_ERROR_ACCESS + * _whTest_HwKeystoreKekPolicy - a hardware key without WRAP usage, or + * another client's, is refused as a KEK + * _whTest_HwKeystoreUnbound - a hardware KEK fails with NOTFOUND on a + * server with no hardware keystore bound * - * Crypto key use of a hardware-only id (rejected at the keystore freshen - * choke point) is covered server-side in server/wh_test_hwkeystore_server.c, - * since the blocking wolfCrypt callback cannot be pumped single-threaded. + * AES and CMAC with hardware keys, and client-loaded keys as KEKs, are covered + * in misc/wh_test_hwkeystore_crypto.c. */ #include "wolfhsm/wh_settings.h" @@ -60,6 +64,9 @@ #include "wolfssl/wolfcrypt/settings.h" #include "wolfssl/wolfcrypt/types.h" +#include "wolfssl/wolfcrypt/error-crypt.h" +#include "wolfssl/wolfcrypt/aes.h" +#include "wolfssl/wolfcrypt/cryptocb.h" #include "wolfhsm/wh_error.h" #include "wolfhsm/wh_common.h" @@ -81,7 +88,7 @@ #define FLASH_SECTOR_SIZE (128 * 1024) #define FLASH_PAGE_SIZE 8 -/* Id and material of the only KEK served by the emulated hardware keystore */ +/* Id and size of the mock device's KEK, which the port registers */ #define WH_TEST_HWKEK_ID 3 #define WH_TEST_HWKEK_SIZE 32 @@ -98,39 +105,77 @@ static const uint8_t _hwKekMaterial[WH_TEST_HWKEK_SIZE] = { 0x59, 0xee, 0x07, 0xb1, 0x2c, 0x48, 0x95, 0x3a, 0xc6, 0x71, 0x0d, 0xb8, 0xe5, 0x12, 0x6a, 0xf9, 0x84, 0x2f, 0xd0, 0x5b, 0xa7}; -static int _HwKeystoreGetKey(void* context, whKeyId keyId, uint8_t* out, - uint16_t* inout_len) +/* wolfCrypt device of the mock hardware */ +#define WH_TEST_HW_DEVID 0x4D4B454B + +/* Hardware keys _whTest_HwKeystoreKekPolicy registers: one without WRAP usage, + * and one that belongs to another client */ +#define WH_TEST_HW_NOWRAP_ID (WH_TEST_HWKEK_ID + 2) +#define WH_TEST_HW_OTHER_ID (WH_TEST_HWKEK_ID + 3) + +/* AES-GCM operations the mock device ran with the KEK */ +static int _hwKekOps; + +/* Hardware key number named by a key reference, or -1 */ +static int _MockRefId(const unsigned char* ref, int refSz) { - (void)context; + whKeyId keyId; - /* Only hardware-only keyIds should ever reach a hardware keystore */ - if (WH_KEYID_TYPE(keyId) != WH_KEYTYPE_HW) { - return WH_ERROR_ACCESS; + if (refSz != WH_HWKEYSTORE_REF_SIZE) { + return -1; } + keyId = (whKeyId)(((whKeyId)ref[0] << 8) | ref[1]); + return WH_KEYID_ISHW(keyId) ? (int)WH_KEYID_ID(keyId) : -1; +} - /* Serve only the known test KEK id, refuse everything else */ - if (WH_KEYID_ID(keyId) != WH_TEST_HWKEK_ID) { - return WH_ERROR_NOTFOUND; - } +/* Runs AES-GCM with the KEK when the server passes it by reference. Software + * keys are left to wolfCrypt */ +static int _MockCryptoCb(int devId, wc_CryptoInfo* info, void* ctx) +{ + Aes* aes; + Aes sw[1]; + int ret; + + (void)devId; + (void)ctx; - if ((out == NULL) || (inout_len == NULL) || - (*inout_len < sizeof(_hwKekMaterial))) { - return WH_ERROR_BUFFER_SIZE; + if ((info->algo_type != WC_ALGO_TYPE_CIPHER) || + (info->cipher.type != WC_CIPHER_AES_GCM)) { + return CRYPTOCB_UNAVAILABLE; + } + aes = (info->cipher.enc != 0) ? info->cipher.aesgcm_enc.aes + : info->cipher.aesgcm_dec.aes; + if (aes->idLen == 0) { + return CRYPTOCB_UNAVAILABLE; + } + /* No other hardware key holds a KEK */ + if (_MockRefId(aes->id, aes->idLen) != WH_TEST_HWKEK_ID) { + return WC_HW_E; } - memcpy(out, _hwKekMaterial, sizeof(_hwKekMaterial)); - *inout_len = sizeof(_hwKekMaterial); - return WH_ERROR_OK; + ret = wc_AesInit(sw, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(sw, _hwKekMaterial, sizeof(_hwKekMaterial)); + if ((ret == 0) && (info->cipher.enc != 0)) { + wc_CryptoCb_AesAuthEnc* e = &info->cipher.aesgcm_enc; + ret = wc_AesGcmEncrypt(sw, e->out, e->in, e->sz, e->iv, e->ivSz, + e->authTag, e->authTagSz, e->authIn, + e->authInSz); + } + else if (ret == 0) { + wc_CryptoCb_AesAuthDec* d = &info->cipher.aesgcm_dec; + ret = wc_AesGcmDecrypt(sw, d->out, d->in, d->sz, d->iv, d->ivSz, + d->authTag, d->authTagSz, d->authIn, + d->authInSz); + } + wc_AesFree(sw); + } + if (ret == 0) { + _hwKekOps++; + } + return ret; } -/* clang-format off */ -static const whHwKeystoreCb _hwKeystoreCb = { - .Init = NULL, - .Cleanup = NULL, - .GetKey = _HwKeystoreGetKey, -}; -/* clang-format on */ - /* Self-contained client/server pair over the mem transport. The server is * pumped inline (wh_Server_HandleRequestMessage) between each split client * request/response pair */ @@ -224,10 +269,9 @@ static int _SetupClientServer(TestCtx* t) .config = t->nf_conf, }; - /* Hardware keystore front-end backed by the test getKey callback */ + /* Hardware keystore with no backend hooks */ t->hwks_conf[0] = (whHwKeystoreConfig){ - .cb = &_hwKeystoreCb, - .context = NULL, + .cb = NULL, }; /* Server config */ @@ -236,14 +280,21 @@ static int _SetupClientServer(TestCtx* t) .nvm = t->nvm, #ifndef WOLFHSM_CFG_NO_CRYPTO .crypto = t->crypto, - .devId = INVALID_DEVID, + .devId = WH_TEST_HW_DEVID, #endif .hwKeystore = t->hwKeystore, }; WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(t->nvm, t->n_conf)); WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(t->hwKeystore, t->hwks_conf)); + /* The port's KEK, shared by all clients */ + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Register( + t->hwKeystore, + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, WH_TEST_HWKEK_ID), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_HWKEK_SIZE)); WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); + WH_TEST_RETURN_ON_FAIL( + wc_CryptoCb_RegisterDevice(WH_TEST_HW_DEVID, _MockCryptoCb, NULL)); WH_TEST_RETURN_ON_FAIL(wc_InitRng_ex(t->crypto->rng, NULL, INVALID_DEVID)); WH_TEST_RETURN_ON_FAIL(wh_Server_Init(t->server, t->s_conf)); WH_TEST_RETURN_ON_FAIL( @@ -267,6 +318,7 @@ static void _CleanupClientServer(TestCtx* t) (void)wh_Nvm_Cleanup(t->nvm); (void)wh_HwKeystore_Cleanup(t->hwKeystore); (void)wc_FreeRng(t->crypto->rng); + wc_CryptoCb_UnRegisterDevice(WH_TEST_HW_DEVID); (void)wolfCrypt_Cleanup(); } @@ -396,6 +448,7 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) uint16_t unwrappedKeyId = WH_KEYID_ERASED; whNvmMetadata metadata = {0}; whNvmMetadata tmpMetadata = {0}; + int ops = _hwKekOps; size_t i; metadata.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(t->client->comm->client_id, @@ -409,7 +462,7 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) plainKey[i] = (uint8_t)(0xA0 ^ i); } - /* Wrap a key using the hardware-only KEK */ + /* Wrap a key using the hardware KEK */ ret = _KeyWrap(t, hwKekId, plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_OK) { @@ -417,7 +470,7 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) return ret; } - /* Unwrap and export with the hardware-only KEK, check the roundtrip */ + /* Unwrap and export with the hardware KEK, check the roundtrip */ ret = _KeyUnwrapAndExport(t, hwKekId, wrappedKey, wrappedKeySz, &tmpMetadata, tmpPlainKey, &tmpPlainKeySz); if (ret != WH_ERROR_OK) { @@ -425,6 +478,9 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) return ret; } + /* The mock device ran both operations with the KEK, by reference */ + WH_TEST_ASSERT_RETURN(_hwKekOps == ops + 2); + if (memcmp(plainKey, tmpPlainKey, sizeof(plainKey)) != 0) { WH_ERROR_PRINT("HW KEK wrap/unwrap key failed to match\n"); return WH_ERROR_ABORTED; @@ -435,7 +491,7 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) return WH_ERROR_ABORTED; } - /* Unwrap-and-cache with the hardware-only KEK: the wrapped payload is an + /* Unwrap-and-cache with the hardware KEK: the wrapped payload is an * ordinary key and may enter the cache; only the KEK itself is * hardware-resident */ ret = _KeyUnwrapAndCache(t, hwKekId, wrappedKey, wrappedKeySz, @@ -446,8 +502,8 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) } WH_TEST_RETURN_ON_FAIL(_KeyEvict(t, unwrappedKeyId)); - /* A wrapped+hardware-only KEK id must behave as hardware-only (the - * hardware-only flag takes precedence) */ + /* A KEK id with both the wrapped and hardware flags is a hardware key + * (the hardware flag takes precedence) */ wrappedKeySz = sizeof(wrappedKey); ret = _KeyWrap(t, hwKekId | WH_KEYID_CLIENT_WRAPPED_FLAG, plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); @@ -481,12 +537,12 @@ static int _whTest_HwKeystoreKeyWrap(TestCtx* t) } } - /* A hardware KEK id the backend does not serve must fail */ + /* A hardware key the server has no record of must fail */ wrappedKeySz = sizeof(wrappedKey); ret = _KeyWrap(t, WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HWKEK_ID + 1), plainKey, sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz); if (ret != WH_ERROR_NOTFOUND) { - WH_ERROR_PRINT("KeyWrap with unserved HW KEK expected NOTFOUND, " + WH_ERROR_PRINT("KeyWrap with unregistered HW KEK expected NOTFOUND, " "got %d\n", ret); return WH_ERROR_ABORTED; @@ -689,9 +745,8 @@ static int _whTest_HwKeystoreDataWrap(TestCtx* t) return WH_ERROR_OK; } -/* Hardware-only keys must be rejected by every keystore operation; only the - * keywrap KEK paths may resolve them. Crypto key use is rejected at the same - * keystore choke points, covered in server/wh_test_hwkeystore_server.c */ +/* Hardware keys must be rejected by every keystore operation. Key wrap and + * the crypto handlers use them through the hardware keystore instead */ static int _whTest_HwKeystoreRejections(TestCtx* t) { int ret; @@ -701,7 +756,7 @@ static int _whTest_HwKeystoreRejections(TestCtx* t) uint8_t label[WH_NVM_LABEL_LEN] = "hwonly reject"; uint16_t cacheKeyId = hwKeyId; - /* Caching key material under a hardware-only id must be rejected */ + /* Caching key material under a hardware key id must be rejected */ ret = _KeyCache(t, WH_NVM_FLAGS_NONE, label, sizeof(label), buf, sizeof(buf), &cacheKeyId); if (ret != WH_ERROR_ACCESS) { @@ -710,7 +765,7 @@ static int _whTest_HwKeystoreRejections(TestCtx* t) } #ifdef WOLFHSM_CFG_DMA - /* The DMA cache path must reject hardware-only ids as well */ + /* The DMA cache path must reject hardware key ids as well */ cacheKeyId = hwKeyId; WH_TEST_RETURN_ON_FAIL(wh_Client_KeyCacheDmaRequest( t->client, WH_NVM_FLAGS_NONE, label, sizeof(label), buf, sizeof(buf), @@ -724,7 +779,7 @@ static int _whTest_HwKeystoreRejections(TestCtx* t) } #endif - /* Exporting a hardware-only key must be rejected */ + /* Exporting a hardware key must be rejected */ ret = _KeyExport(t, hwKeyId, label, sizeof(label), buf, &bufSz); if (ret != WH_ERROR_ACCESS) { WH_ERROR_PRINT("KeyExport of HW-only id expected ACCESS, got %d\n", @@ -732,7 +787,7 @@ static int _whTest_HwKeystoreRejections(TestCtx* t) return WH_ERROR_ABORTED; } - /* Commit/evict/erase/revoke of a hardware-only key must be rejected */ + /* Commit/evict/erase/revoke of a hardware key must be rejected */ ret = _KeyCommit(t, hwKeyId); if (ret != WH_ERROR_ACCESS) { WH_ERROR_PRINT("KeyCommit of HW-only id expected ACCESS, got %d\n", @@ -762,7 +817,55 @@ static int _whTest_HwKeystoreRejections(TestCtx* t) return WH_ERROR_OK; } -/* A hardware-only KEK requested against a server that has no hardware keystore +/* A KEK must allow WRAP usage and belong to the caller or be shared, the same + * policy the server applies to every other use of a hardware key */ +static int _whTest_HwKeystoreKekPolicy(TestCtx* t) +{ + uint8_t plainKey[WH_TEST_AES_KEYSIZE] = {0}; + uint8_t wrappedKey[WH_TEST_WRAPPED_KEYSIZE]; + uint16_t wrappedKeySz = sizeof(wrappedKey); + uint8_t data[] = "Example data!"; + uint8_t wrappedData[sizeof(data) + WH_KEYWRAP_AES_GCM_HEADER_SIZE]; + uint32_t wrappedDataSz = sizeof(wrappedData); + whNvmMetadata metadata = {0}; + uint16_t otherUser = (uint16_t)(t->client->comm->client_id + 1); + + metadata.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(t->client->comm->client_id, + WH_TEST_WRAPPED_KEYID); + metadata.len = WH_TEST_AES_KEYSIZE; + metadata.flags = WH_NVM_FLAGS_USAGE_ANY; + + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Register( + t->hwKeystore, + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, WH_TEST_HW_NOWRAP_ID), + WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, + WH_TEST_HWKEK_SIZE)); + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Register( + t->hwKeystore, + WH_MAKE_KEYID(WH_KEYTYPE_HW, otherUser, WH_TEST_HW_OTHER_ID), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_HWKEK_SIZE)); + + /* No WRAP usage */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_USAGE == + _KeyWrap(t, WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HW_NOWRAP_ID), plainKey, + sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_USAGE == + _DataWrap(t, WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HW_NOWRAP_ID), data, + sizeof(data), wrappedData, &wrappedDataSz)); + + /* Another client's key */ + wrappedKeySz = sizeof(wrappedKey); + WH_TEST_ASSERT_RETURN( + WH_ERROR_ACCESS == + _KeyWrap(t, WH_CLIENT_KEYID_MAKE_HW(WH_TEST_HW_OTHER_ID), plainKey, + sizeof(plainKey), &metadata, wrappedKey, &wrappedKeySz)); + + return WH_ERROR_OK; +} + +/* A hardware KEK requested against a server that has no hardware keystore * bound (HWKEYSTORE compiled in, whServerConfig.hwKeystore left NULL) must fail * cleanly with NOTFOUND from the keywrap KEK resolver. Simulate the unbound * configuration by detaching the server's keystore for the duration of the @@ -838,6 +941,9 @@ int whTest_HwKeystore(void* ctx) if (ret == WH_ERROR_OK) { ret = _whTest_HwKeystoreRejections(t); } + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeystoreKekPolicy(t); + } if (ret == WH_ERROR_OK) { ret = _whTest_HwKeystoreUnbound(t); } diff --git a/test-refactor/misc/wh_test_hwkeystore_crypto.c b/test-refactor/misc/wh_test_hwkeystore_crypto.c index cf5f32ab6..c499c325d 100644 --- a/test-refactor/misc/wh_test_hwkeystore_crypto.c +++ b/test-refactor/misc/wh_test_hwkeystore_crypto.c @@ -19,8 +19,9 @@ /* * test-refactor/misc/wh_test_hwkeystore_crypto.c * - * End-to-end test of hardware keys in AES and CMAC requests - * (WOLFHSM_CFG_HWKEYSTORE_CRYPTO). Two clients talk to two servers that share + * End-to-end test of hardware keys in AES and CMAC requests and as key wrap + * KEKs, with clients loading keys (WOLFHSM_CFG_HWKEYSTORE_LOAD). Two clients + * talk to two servers that share * one NVM and one hardware keystore, like one HSM serving two cores. * * The servers' crypto callback is a mock AES engine with eight write-only @@ -52,16 +53,18 @@ * _whTest_HwKeyShared - keys loaded with the global flag * _whTest_HwKeyNoFallback - a key the hardware lost fails instead of * running in software + * _whTest_HwKeyKek - the port key and client-loaded keys as key wrap + * KEKs: only port keys are trusted * _whTest_HwKeyPortKeyOwner - a port key registered for one client */ #include "wolfhsm/wh_settings.h" -#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ - defined(WOLFHSM_CFG_ENABLE_SERVER) && \ - defined(WOLFHSM_CFG_HWKEYSTORE_CRYPTO) && defined(HAVE_AES_ECB) && \ - defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_COUNTER) && \ - defined(HAVE_AESGCM) && defined(WOLFSSL_CMAC) && \ +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && \ + defined(WOLFHSM_CFG_ENABLE_SERVER) && \ + defined(WOLFHSM_CFG_HWKEYSTORE_LOAD) && defined(HAVE_AES_ECB) && \ + defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_COUNTER) && \ + defined(HAVE_AESGCM) && defined(WOLFSSL_CMAC) && \ defined(WOLFSSL_AES_DIRECT) #include @@ -442,37 +445,19 @@ static int _MockCryptoCb(int devId, wc_CryptoInfo* info, void* ctx) } } -/* Slots can't be read, so hardware keys can't be key encryption keys */ -static int _MockGetKey(void* context, whKeyId keyId, uint8_t* out, - uint16_t* inout_len) -{ - (void)context; - (void)keyId; - (void)out; - (void)inout_len; - return WH_ERROR_ACCESS; -} - -/* clang-format off */ -static const whHwKeystoreCb _mockHwKeystoreCb = { - .Init = NULL, - .Cleanup = NULL, - .GetKey = _MockGetKey, -}; -/* clang-format on */ - /* Boot the mock engine: empty every slot, load the port key into K2 and record * it with the hardware keystore. owner is the client that may use it, or 0 - * for every client */ -static int _MockBoot(whHwKeystoreContext* hwKeystore, uint16_t owner) + * for every client, and flags the usage it allows */ +static int _MockBoot(whHwKeystoreContext* hwKeystore, uint16_t owner, + whNvmFlags flags) { memset(&_mock, 0, sizeof(_mock)); memcpy(_mock.key[MOCK_PORT_SLOT], _portKey, sizeof(_portKey)); _mock.loaded[MOCK_PORT_SLOT] = 1; return wh_HwKeystore_Register( - hwKeystore, WH_MAKE_KEYID(WH_KEYTYPE_HW, owner, MOCK_PORT_SLOT), - USAGE_AES_CMAC, AES_128_KEY_SIZE); + hwKeystore, WH_MAKE_KEYID(WH_KEYTYPE_HW, owner, MOCK_PORT_SLOT), flags, + AES_128_KEY_SIZE); } /* ------------------------------------------------------------------------ @@ -608,7 +593,7 @@ static int _Setup(TestCtx* t) .config = t->nf_conf, }; t->hwks_conf[0] = (whHwKeystoreConfig){ - .cb = &_mockHwKeystoreCb, + .cb = NULL, }; WH_TEST_RETURN_ON_FAIL(wolfCrypt_Init()); @@ -616,7 +601,8 @@ static int _Setup(TestCtx* t) wc_CryptoCb_RegisterDevice(MOCK_DEVID, _MockCryptoCb, NULL)); WH_TEST_RETURN_ON_FAIL(wh_Nvm_Init(t->nvm, t->n_conf)); WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(t->hwKeystore, t->hwks_conf)); - WH_TEST_RETURN_ON_FAIL(_MockBoot(t->hwKeystore, WH_KEYUSER_GLOBAL)); + WH_TEST_RETURN_ON_FAIL( + _MockBoot(t->hwKeystore, WH_KEYUSER_GLOBAL, USAGE_AES_CMAC)); WH_TEST_RETURN_ON_FAIL( _SetupPair(t, &t->pair[0], CLIENT1_ID, CLIENT1_DEVID, 201)); WH_TEST_RETURN_ON_FAIL( @@ -641,12 +627,12 @@ static void _Cleanup(TestCtx* t) } /* Simulate an HSM reset: the slots and the hardware keystore start empty and - * the port registers its key again, this time for owner */ -static int _Reboot(TestCtx* t, uint16_t owner) + * the port registers its key again, this time for owner with flags */ +static int _Reboot(TestCtx* t, uint16_t owner, whNvmFlags flags) { (void)wh_HwKeystore_Cleanup(t->hwKeystore); WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(t->hwKeystore, t->hwks_conf)); - return _MockBoot(t->hwKeystore, owner); + return _MockBoot(t->hwKeystore, owner, flags); } /* ------------------------------------------------------------------------ @@ -1636,6 +1622,133 @@ static int _whTest_HwKeyNoFallback(TestCtx* t) return WH_ERROR_OK; } +#ifdef WOLFHSM_CFG_KEYWRAP +/* Wrapped size of an AES-128 key */ +#define TEST_WRAPPED_SZ \ + (WH_KEYWRAP_AES_GCM_HEADER_SIZE + sizeof(whNvmMetadata) + AES_128_KEY_SIZE) + +/* Wrap _keyB under kekId, then unwrap it and check the round trip */ +static int _WrapRoundTrip(whClientContext* c, whKeyId kekId) +{ + whNvmMetadata meta; + whNvmMetadata outMeta; + uint8_t wrapped[TEST_WRAPPED_SZ]; + uint16_t wrappedSz = sizeof(wrapped); + uint8_t key[AES_128_KEY_SIZE]; + uint16_t keySz = sizeof(key); + int ret; + + memset(&meta, 0, sizeof(meta)); + meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(c->comm->client_id, 0x30); + meta.len = AES_128_KEY_SIZE; + meta.flags = WH_NVM_FLAGS_USAGE_ENCRYPT; + + ret = wh_Client_KeyWrap(c, WC_CIPHER_AES_GCM, kekId, _keyB, sizeof(_keyB), + &meta, wrapped, &wrappedSz); + if (ret == WH_ERROR_OK) { + ret = wh_Client_KeyUnwrapAndExport(c, WC_CIPHER_AES_GCM, kekId, wrapped, + wrappedSz, &outMeta, key, &keySz); + } + if (ret == WH_ERROR_OK) { + WH_TEST_ASSERT_RETURN(keySz == sizeof(_keyB)); + WH_TEST_ASSERT_RETURN(memcmp(key, _keyB, sizeof(_keyB)) == 0); + } + return ret; +} + +/* Hardware keys as key wrap KEKs. The port key is trusted, since no client can + * set or read it, so it also works for wrap-export and unwrap-and-cache. A + * client-loaded key is an ordinary KEK, since the client may know its bytes. + * Either needs WRAP usage, and a WRAP-only key is not a cipher key */ +static int _whTest_HwKeyKek(TestCtx* t) +{ + whClientContext* c1 = t->pair[0].client; + whClientContext* c2 = t->pair[1].client; + whKeyId src = WH_KEYID_ERASED; + whKeyId plainSrc = WH_KEYID_ERASED; + whKeyId cachedId = WH_KEYID_ERASED; + whKeyId hwId; + whNvmMetadata meta; + uint8_t wrapped[TEST_WRAPPED_SZ]; + uint16_t wrappedSz = sizeof(wrapped); + uint8_t data[] = "hardware KEK data"; + uint8_t wrappedData[WH_KEYWRAP_AES_GCM_HEADER_SIZE + sizeof(data)]; + uint32_t wrappedDataSz = sizeof(wrappedData); + uint8_t dataOut[sizeof(data)]; + uint32_t dataOutSz = sizeof(dataOut); + int ops; + + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, USAGE_AES_CMAC, _keyA, sizeof(_keyA), &plainSrc)); + + /* The port key as a WRAP-only KEK. The engine runs every wrap step with + * the key in its slot */ + WH_TEST_RETURN_ON_FAIL( + _Reboot(t, WH_KEYUSER_GLOBAL, WH_NVM_FLAGS_USAGE_WRAP)); + ops = _mock.ops; + WH_TEST_RETURN_ON_FAIL(_WrapRoundTrip(c2, HW(MOCK_PORT_SLOT))); + WH_TEST_ASSERT_RETURN(_mock.ops == ops + 2); + + /* It is trusted: wrap-export a server-held key, then cache it back */ + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrapExport( + c1, WC_CIPHER_AES_GCM, plainSrc, WH_KEYTYPE_CRYPTO, HW(MOCK_PORT_SLOT), + wrapped, &wrappedSz)); + WH_TEST_RETURN_ON_FAIL( + wh_Client_KeyUnwrapAndCache(c1, WC_CIPHER_AES_GCM, HW(MOCK_PORT_SLOT), + wrapped, wrappedSz, &cachedId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, cachedId)); + + /* WRAP-only, so not a cipher key */ + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryEcb(c1, HW(MOCK_PORT_SLOT), 1)); + + /* The usual port key has no WRAP usage */ + WH_TEST_RETURN_ON_FAIL(_Reboot(t, WH_KEYUSER_GLOBAL, USAGE_AES_CMAC)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + _WrapRoundTrip(c1, HW(MOCK_PORT_SLOT))); + + /* A client-loaded WRAP key is an ordinary KEK */ + WH_TEST_RETURN_ON_FAIL( + _Cache(c1, WH_NVM_FLAGS_USAGE_WRAP, _keyC, sizeof(_keyC), &src)); + WH_TEST_RETURN_ON_FAIL(_Load(c1, HW(0), src, &hwId)); + WH_TEST_RETURN_ON_FAIL(_WrapRoundTrip(c1, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_DataWrap(c1, WC_CIPHER_AES_GCM, hwId, data, + sizeof(data), wrappedData, + &wrappedDataSz)); + WH_TEST_RETURN_ON_FAIL(wh_Client_DataUnwrap(c1, WC_CIPHER_AES_GCM, hwId, + wrappedData, wrappedDataSz, + dataOut, &dataOutSz)); + WH_TEST_ASSERT_RETURN((dataOutSz == sizeof(data)) && + (memcmp(dataOut, data, sizeof(data)) == 0)); + + /* Not trusted: the client may know its bytes */ + wrappedSz = sizeof(wrapped); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_KeyWrapExport(c1, WC_CIPHER_AES_GCM, + plainSrc, WH_KEYTYPE_CRYPTO, + hwId, wrapped, &wrappedSz)); + memset(&meta, 0, sizeof(meta)); + meta.id = WH_CLIENT_KEYID_MAKE_WRAPPED_META(c1->comm->client_id, 0x30); + meta.len = AES_128_KEY_SIZE; + wrappedSz = sizeof(wrapped); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyWrap(c1, WC_CIPHER_AES_GCM, hwId, _keyB, + sizeof(_keyB), &meta, wrapped, + &wrappedSz)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_Client_KeyUnwrapAndCache(c1, WC_CIPHER_AES_GCM, + hwId, wrapped, wrappedSz, + &cachedId)); + + /* WRAP-only, and only for its owner */ + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == _TryEcb(c1, hwId, 1)); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == _WrapRoundTrip(c2, hwId)); + + WH_TEST_RETURN_ON_FAIL(wh_Client_HwKeystoreClear(c1, hwId)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); + WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, plainSrc)); + return WH_ERROR_OK; +} +#endif /* WOLFHSM_CFG_KEYWRAP */ + /* A port key registered for one client: only that client can use it, and no * client can set or clear it */ static int _whTest_HwKeyPortKeyOwner(TestCtx* t) @@ -1645,7 +1758,7 @@ static int _whTest_HwKeyPortKeyOwner(TestCtx* t) whKeyId src = WH_KEYID_ERASED; whKeyId hwId; - WH_TEST_RETURN_ON_FAIL(_Reboot(t, CLIENT1_ID)); + WH_TEST_RETURN_ON_FAIL(_Reboot(t, CLIENT1_ID, USAGE_AES_CMAC)); WH_TEST_RETURN_ON_FAIL( _CheckInfo(c2, MOCK_PORT_SLOT, 1, USAGE_AES_CMAC, CLIENT1_ID)); @@ -1666,7 +1779,7 @@ static int _whTest_HwKeyPortKeyOwner(TestCtx* t) WH_TEST_RETURN_ON_FAIL(wh_Client_KeyEvict(c1, src)); /* Back to a shared port key */ - return _Reboot(t, WH_KEYUSER_GLOBAL); + return _Reboot(t, WH_KEYUSER_GLOBAL, USAGE_AES_CMAC); } int whTest_HwKeystoreCrypto(void* ctx) @@ -1709,6 +1822,11 @@ int whTest_HwKeystoreCrypto(void* ctx) if (ret == WH_ERROR_OK) { ret = _whTest_HwKeyNoFallback(t); } +#ifdef WOLFHSM_CFG_KEYWRAP + if (ret == WH_ERROR_OK) { + ret = _whTest_HwKeyKek(t); + } +#endif if (ret == WH_ERROR_OK) { ret = _whTest_HwKeyPortKeyOwner(t); } @@ -1718,7 +1836,7 @@ int whTest_HwKeystoreCrypto(void* ctx) return ret; } -#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER && \ - WOLFHSM_CFG_HWKEYSTORE_CRYPTO && HAVE_AES_ECB && HAVE_AES_CBC && \ - WOLFSSL_AES_COUNTER && HAVE_AESGCM && WOLFSSL_CMAC && \ +#endif /* WOLFHSM_CFG_ENABLE_CLIENT && WOLFHSM_CFG_ENABLE_SERVER && \ + WOLFHSM_CFG_HWKEYSTORE_LOAD && HAVE_AES_ECB && HAVE_AES_CBC && \ + WOLFSSL_AES_COUNTER && HAVE_AESGCM && WOLFSSL_CMAC && \ WOLFSSL_AES_DIRECT */ diff --git a/test-refactor/posix/Makefile b/test-refactor/posix/Makefile index 2a180767c..4fc3b8289 100644 --- a/test-refactor/posix/Makefile +++ b/test-refactor/posix/Makefile @@ -202,10 +202,9 @@ ifeq ($(CERT_VERIFY_CACHE_FULLCHAIN),1) DEF += -DWOLFHSM_CFG_CERTIFICATE_VERIFY_CACHE_FULLCHAIN endif -# Enable hardware keys in AES and CMAC requests and their unit test -ifeq ($(HWKEYSTORE_CRYPTO),1) - DEF += -DWOLFHSM_CFG_HWKEYSTORE_CRYPTO - DEF += -DWOLF_CRYPTO_CB_KEYSTORE -DWOLF_PRIVATE_KEY_ID +# Let clients load keys into hardware keys, and enable its unit test +ifeq ($(HWKEYSTORE_LOAD),1) + DEF += -DWOLFHSM_CFG_HWKEYSTORE_LOAD -DWOLF_CRYPTO_CB_KEYSTORE endif # Build the wolfCrypt test suite as a wolfHSM client diff --git a/test-refactor/server/wh_test_hwkeystore_server.c b/test-refactor/server/wh_test_hwkeystore_server.c index 5a642fd03..dd99f8816 100644 --- a/test-refactor/server/wh_test_hwkeystore_server.c +++ b/test-refactor/server/wh_test_hwkeystore_server.c @@ -21,25 +21,23 @@ * * Server-side hardware keystore coverage that needs no client: * - * _whTest_HwKeystoreModule - wh_HwKeystore_Init/GetKey/Cleanup argument - * validation and GetKey dispatch, using a - * local context (the shared server context - * is not touched) - * _whTest_HwKeystoreLifecycle - optional backend Init/Cleanup callback - * dispatch and the Init-failure return path, - * using a backend that records its calls + * _whTest_HwKeystoreModule - wh_HwKeystore_Init/Register/CheckUse/ + * KeyRef/Cleanup: argument validation, the + * records, and the owner, usage and trust + * checks, using a local context (the shared + * server context is not touched) + * _whTest_HwKeystoreLifecycle - optional backend Init/Cleanup hook dispatch + * and the Init-failure return path, using a + * backend that records its calls * _whTest_KeystoreHwOnlyReject - every public keystore entry point must - * reject a hardware-only keyId with - * WH_ERROR_ACCESS before reaching the cache, - * NVM, or any hardware backend (none is - * bound to the shared server). FreshenKey is - * also the choke point through which all - * crypto handlers resolve keys, so this - * covers crypto key use of hardware-only ids + * reject a hardware keyId with + * WH_ERROR_ACCESS before reaching the cache + * or NVM * - * End-to-end keywrap use of hardware-only KEKs is covered by the misc group - * test misc/wh_test_hwkeystore.c, which binds a hardware keystore to its own - * private client/server pair. + * End-to-end use of hardware keys is covered by the misc group tests + * misc/wh_test_hwkeystore.c (key wrap KEKs) and + * misc/wh_test_hwkeystore_crypto.c (AES, CMAC and client loading), which bind + * a hardware keystore to their own client/server pairs. */ #include "wolfhsm/wh_settings.h" @@ -60,53 +58,15 @@ #include "wh_test_common.h" #include "wh_test_list.h" -/* Id and material of the only key served by the local test backend */ +/* ID and size of the hardware key the module tests register first */ #define WH_TEST_HWKEK_ID 3 #define WH_TEST_HWKEK_SIZE 32 -/* USER field value for constructing server-internal hardware-only keyIds */ +/* USER field value for constructing server-internal hardware keyIds */ #define WH_TEST_HW_USER 1 -static const uint8_t _hwKekMaterial[WH_TEST_HWKEK_SIZE] = { - 0x9a, 0x4e, 0x21, 0xc7, 0x5d, 0x10, 0xfb, 0x33, 0x6f, 0x82, 0xd4, - 0x59, 0xee, 0x07, 0xb1, 0x2c, 0x48, 0x95, 0x3a, 0xc6, 0x71, 0x0d, - 0xb8, 0xe5, 0x12, 0x6a, 0xf9, 0x84, 0x2f, 0xd0, 0x5b, 0xa7}; - -static int _HwKeystoreGetKey(void* context, whKeyId keyId, uint8_t* out, - uint16_t* inout_len) -{ - (void)context; - - /* Only hardware-only keyIds should ever reach a hardware keystore */ - if (WH_KEYID_TYPE(keyId) != WH_KEYTYPE_HW) { - return WH_ERROR_ACCESS; - } - - /* Serve only the known test KEK id, refuse everything else */ - if (WH_KEYID_ID(keyId) != WH_TEST_HWKEK_ID) { - return WH_ERROR_NOTFOUND; - } - - if ((out == NULL) || (inout_len == NULL) || - (*inout_len < sizeof(_hwKekMaterial))) { - return WH_ERROR_BUFFER_SIZE; - } - - memcpy(out, _hwKekMaterial, sizeof(_hwKekMaterial)); - *inout_len = sizeof(_hwKekMaterial); - return WH_ERROR_OK; -} - -/* clang-format off */ -static const whHwKeystoreCb _hwKeystoreCb = { - .Init = NULL, - .Cleanup = NULL, - .GetKey = _HwKeystoreGetKey, -}; -/* clang-format on */ - -/* Backend context recording optional Init/Cleanup callback dispatch. initRet - * and cleanupRet let a test force the Init/Cleanup failure paths; initConfig +/* Backend context recording optional Init/Cleanup hook dispatch. initRet and + * cleanupRet let a test force the Init/Cleanup failure paths; initConfig * captures the config pointer forwarded to Init to prove it is plumbed * through */ typedef struct { @@ -142,108 +102,181 @@ static int _HwKeystoreCleanup(void* context) static const whHwKeystoreCb _hwKeystoreLifecycleCb = { .Init = _HwKeystoreInit, .Cleanup = _HwKeystoreCleanup, - .GetKey = _HwKeystoreGetKey, }; /* clang-format on */ -/* wh_HwKeystore module front-end in isolation: argument validation, callback - * dispatch, and lifecycle */ +/* wh_HwKeystore front end in isolation: argument validation, the records, and + * the checks the server makes on every use of a hardware key */ static int _whTest_HwKeystoreModule(void) { - int ret; - whHwKeystoreContext hwks[1] = {{0}}; - whHwKeystoreConfig conf[1] = {{0}}; - whHwKeystoreConfig badConf[1] = {{0}}; - whKeyId servedId = + whHwKeystoreContext hwks[1] = {{0}}; + whHwKeystoreConfig conf[1] = {{0}}; + /* A shared port key, and a port key for one client */ + whKeyId shared = + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, WH_TEST_HWKEK_ID); + whKeyId owned = + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID + 1); + /* The same keys as the client and another client name them */ + whKeyId sharedByUser = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID); - uint8_t out[WH_TEST_HWKEK_SIZE] = {0}; - uint16_t outLen = sizeof(out); + whKeyId ownedByOther = + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER + 1, WH_TEST_HWKEK_ID + 1); + whKeyId out = WH_KEYID_ERASED; + uint8_t ref[WH_HWKEYSTORE_REF_SIZE]; + int i; - conf->cb = &_hwKeystoreCb; - - /* Init argument validation: NULL context/config/getKey are rejected */ + /* Init needs a context and a config; the hook table is optional */ WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_HwKeystore_Init(NULL, conf)); WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_HwKeystore_Init(hwks, NULL)); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_Init(hwks, badConf)); - /* GetKey must reject NULL and uninitialized contexts */ + /* An uninitialized context is refused */ WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(NULL, servedId, out, &outLen)); + wh_HwKeystore_Register(hwks, shared, + WH_NVM_FLAGS_USAGE_WRAP, + WH_TEST_HWKEK_SIZE)); WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); + wh_HwKeystore_CheckUse(hwks, sharedByUser, + WH_NVM_FLAGS_USAGE_WRAP, + &out)); WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(hwks, conf)); - /* GetKey argument validation on an initialized context */ - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, NULL, &outLen)); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, NULL)); + /* The key reference is the server keyId, big-endian */ + wh_HwKeystore_KeyRef(shared, ref); + WH_TEST_ASSERT_RETURN((ref[0] == (uint8_t)(shared >> 8)) && + (ref[1] == (uint8_t)shared)); - /* Served id: material and reported length must match the backend's */ - ret = wh_HwKeystore_GetKey(hwks, servedId, out, &outLen); - WH_TEST_ASSERT_RETURN(WH_ERROR_OK == ret); - WH_TEST_ASSERT_RETURN(outLen == sizeof(_hwKekMaterial)); - WH_TEST_ASSERT_RETURN(0 == - memcmp(out, _hwKekMaterial, sizeof(_hwKekMaterial))); + /* Only hardware keyIds with a nonzero ID can be registered */ + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_HwKeystore_Register(hwks, + WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, + WH_KEYUSER_GLOBAL, + WH_TEST_HWKEK_ID), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_HWKEK_SIZE)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_HwKeystore_Register( + hwks, WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, 0), + WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_HWKEK_SIZE)); + + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Register( + hwks, shared, WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_HWKEK_SIZE)); + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Register( + hwks, owned, WH_NVM_FLAGS_USAGE_ENCRYPT | WH_NVM_FLAGS_USAGE_DECRYPT, + 16)); + + /* One record per hardware key number, whoever owns it */ + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_HwKeystore_Register(hwks, sharedByUser, + WH_NVM_FLAGS_USAGE_WRAP, + WH_TEST_HWKEK_SIZE)); + + /* A shared key: any client can use it, and the recorded keyId comes back + * for the key reference */ + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_CheckUse( + hwks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP, &out)); + WH_TEST_ASSERT_RETURN(out == shared); + + /* Port keys are trusted */ + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_CheckUse( + hwks, sharedByUser, WH_NVM_FLAGS_USAGE_WRAP | WH_NVM_FLAGS_TRUSTED, + &out)); + + /* The usage must match, and some usage is needed */ + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + wh_HwKeystore_CheckUse(hwks, sharedByUser, + WH_NVM_FLAGS_USAGE_ENCRYPT, + &out)); + WH_TEST_ASSERT_RETURN(WH_ERROR_USAGE == + wh_HwKeystore_CheckUse(hwks, sharedByUser, 0, &out)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_USAGE == + wh_HwKeystore_CheckUse(hwks, sharedByUser, WH_NVM_FLAGS_TRUSTED, &out)); + + /* A key for one client: only that client can use it */ + WH_TEST_RETURN_ON_FAIL( + wh_HwKeystore_CheckUse(hwks, owned, WH_NVM_FLAGS_USAGE_ENCRYPT, &out)); + WH_TEST_ASSERT_RETURN(out == owned); + WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == + wh_HwKeystore_CheckUse(hwks, ownedByOther, + WH_NVM_FLAGS_USAGE_ENCRYPT, + &out)); - /* Backend policy: unserved id, undersized buffer, non-HW type */ - outLen = sizeof(out); + /* No record, a non-hardware keyId, ID 0, and no output */ WH_TEST_ASSERT_RETURN( WH_ERROR_NOTFOUND == - wh_HwKeystore_GetKey( - hwks, - WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID + 1), - out, &outLen)); - outLen = WH_TEST_HWKEK_SIZE - 1; - WH_TEST_ASSERT_RETURN(WH_ERROR_BUFFER_SIZE == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - outLen = sizeof(out); + wh_HwKeystore_CheckUse(hwks, + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, 9), + WH_NVM_FLAGS_USAGE_ENCRYPT, &out)); WH_TEST_ASSERT_RETURN( - WH_ERROR_ACCESS == - wh_HwKeystore_GetKey( + WH_ERROR_BADARGS == + wh_HwKeystore_CheckUse( hwks, WH_MAKE_KEYID(WH_KEYTYPE_CRYPTO, WH_TEST_HW_USER, WH_TEST_HWKEK_ID), - out, &outLen)); + WH_NVM_FLAGS_USAGE_WRAP, &out)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_HwKeystore_CheckUse(hwks, + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, 0), + WH_NVM_FLAGS_USAGE_WRAP, &out)); + WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == + wh_HwKeystore_CheckUse(hwks, sharedByUser, + WH_NVM_FLAGS_USAGE_WRAP, + NULL)); + + /* The records run out */ + for (i = 2; i < WOLFHSM_CFG_HWKEYSTORE_MAX_KEYS; i++) { + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Register( + hwks, WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, 10 + i), + WH_NVM_FLAGS_USAGE_ENCRYPT, 16)); + } + WH_TEST_ASSERT_RETURN( + WH_ERROR_NOSPACE == + wh_HwKeystore_Register( + hwks, WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, 9), + WH_NVM_FLAGS_USAGE_ENCRYPT, 16)); - /* Cleanup zeroizes the context, after which GetKey must reject it */ + /* Cleanup zeroizes the context, after which it is refused */ WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_HwKeystore_Cleanup(NULL)); WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Cleanup(hwks)); - outLen = sizeof(out); WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); + wh_HwKeystore_CheckUse(hwks, sharedByUser, + WH_NVM_FLAGS_USAGE_WRAP, + &out)); return WH_ERROR_OK; } -/* Optional backend Init/Cleanup dispatch and the Init-failure return path, - * using a backend that records its callback invocations */ +/* Optional backend Init/Cleanup hook dispatch and the Init-failure return + * path, using a backend that records its hook calls */ static int _whTest_HwKeystoreLifecycle(void) { HwKeystoreLifecycle lc = {0}; whHwKeystoreContext hwks[1] = {{0}}; whHwKeystoreConfig conf[1] = {{0}}; const int backendConfig = 0; /* sentinel forwarded to Init */ - whKeyId servedId = - WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, WH_TEST_HWKEK_ID); - uint8_t out[WH_TEST_HWKEK_SIZE] = {0}; - uint16_t outLen = sizeof(out); + whKeyId keyId = + WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_KEYUSER_GLOBAL, WH_TEST_HWKEK_ID); + whKeyId out = WH_KEYID_ERASED; conf->cb = &_hwKeystoreLifecycleCb; conf->context = &lc; conf->config = &backendConfig; /* Init-failure path: the backend Init rc is propagated, the config pointer - * was forwarded, and the context is left uninitialized (GetKey rejects it). - * Cleanup must NOT have run for a backend whose Init never succeeded */ + * was forwarded, and the context is left uninitialized (Register refuses + * it). Cleanup must NOT have run for a backend whose Init never + * succeeded */ lc.initRet = WH_ERROR_ABORTED; WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == wh_HwKeystore_Init(hwks, conf)); WH_TEST_ASSERT_RETURN(lc.initCalls == 1); WH_TEST_ASSERT_RETURN(lc.initConfig == &backendConfig); WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 0); WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); + wh_HwKeystore_Register(hwks, keyId, + WH_NVM_FLAGS_USAGE_WRAP, + WH_TEST_HWKEK_SIZE)); /* Init-success path: the backend Init is dispatched exactly once and the * context becomes usable */ @@ -251,17 +284,15 @@ static int _whTest_HwKeystoreLifecycle(void) lc.initCalls = 0; WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(hwks, conf)); WH_TEST_ASSERT_RETURN(lc.initCalls == 1); - - outLen = sizeof(out); - WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); - WH_TEST_ASSERT_RETURN(outLen == sizeof(_hwKekMaterial)); + WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Register( + hwks, keyId, WH_NVM_FLAGS_USAGE_WRAP, WH_TEST_HWKEK_SIZE)); /* Cleanup dispatches the backend Cleanup exactly once */ WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Cleanup(hwks)); WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 1); /* Cleanup-failure path: a backend Cleanup error is surfaced to the caller, - * yet the context is still torn down (a subsequent GetKey rejects it) */ + * yet the context is still torn down (it is refused afterwards) */ lc.initRet = WH_ERROR_OK; lc.initCalls = 0; lc.cleanupCalls = 0; @@ -269,14 +300,14 @@ static int _whTest_HwKeystoreLifecycle(void) WH_TEST_RETURN_ON_FAIL(wh_HwKeystore_Init(hwks, conf)); WH_TEST_ASSERT_RETURN(WH_ERROR_ABORTED == wh_HwKeystore_Cleanup(hwks)); WH_TEST_ASSERT_RETURN(lc.cleanupCalls == 1); - outLen = sizeof(out); - WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == - wh_HwKeystore_GetKey(hwks, servedId, out, &outLen)); + WH_TEST_ASSERT_RETURN( + WH_ERROR_BADARGS == + wh_HwKeystore_CheckUse(hwks, keyId, WH_NVM_FLAGS_USAGE_WRAP, &out)); return WH_ERROR_OK; } -/* Hardware-only keyIds must be rejected by every public keystore entry point +/* Hardware keyIds must be rejected by every public keystore entry point * with WH_ERROR_ACCESS (or WH_ERROR_BADARGS for id generation). These checks * are pure keyId-type checks: they fire whether or not a hardware keystore * is bound to the server, and before the cache or NVM is consulted */ @@ -291,8 +322,8 @@ static int _whTest_KeystoreHwOnlyReject(whServerContext* server) uint32_t outSz = sizeof(keyBuf); whNvmId uniqueId; - /* Id generation: hardware-only ids are assigned by the backend, never by - * the keystore */ + /* Id generation: the port defines hardware key ids, so the keystore never + * assigns one */ uniqueId = WH_MAKE_KEYID(WH_KEYTYPE_HW, WH_TEST_HW_USER, 0); WH_TEST_ASSERT_RETURN(WH_ERROR_BADARGS == wh_Server_KeystoreGetUniqueId(server, &uniqueId)); @@ -302,18 +333,18 @@ static int _whTest_KeystoreHwOnlyReject(whServerContext* server) wh_Server_KeystoreGetCacheSlot( server, hwId, sizeof(keyBuf), &outBuf, &outMeta)); - /* Caching key material under a hardware-only id */ + /* Caching key material under a hardware key id */ meta.id = hwId; meta.len = sizeof(keyBuf); WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == wh_Server_KeystoreCacheKey(server, &meta, keyBuf)); - /* Freshen: the resolution path used by all crypto handlers, so this also - * proves crypto operations cannot use hardware-only keys */ + /* Freshen: how crypto handlers resolve keystore keys. A hardware key never + * resolves here; the handlers use it by reference instead */ WH_TEST_ASSERT_RETURN(WH_ERROR_ACCESS == wh_Server_KeystoreFreshenKey( server, hwId, NULL, NULL)); - /* Reading hardware-only key material out of the keystore */ + /* Reading hardware key material out of the keystore */ WH_TEST_ASSERT_RETURN( WH_ERROR_ACCESS == wh_Server_KeystoreReadKey(server, hwId, NULL, keyBuf, &outSz)); diff --git a/test/config/user_settings.h b/test/config/user_settings.h index 669706220..91af69182 100644 --- a/test/config/user_settings.h +++ b/test/config/user_settings.h @@ -37,6 +37,11 @@ /** wolfHSM required settings for wolfCrypt */ #define WOLF_CRYPTO_CB + +/* Hardware keys are used by reference (WOLFHSM_CFG_HWKEYSTORE) */ +#ifndef WOLF_PRIVATE_KEY_ID +#define WOLF_PRIVATE_KEY_ID +#endif /* Allow test makefile to turn keygen on/off */ #ifndef WOLFHSM_CFG_TEST_NO_KEY_GEN #define WOLFSSL_KEY_GEN diff --git a/test/config/wolfhsm_cfg.h b/test/config/wolfhsm_cfg.h index 64d2d39d4..16c09e1b9 100644 --- a/test/config/wolfhsm_cfg.h +++ b/test/config/wolfhsm_cfg.h @@ -55,9 +55,9 @@ /* Enable Image Manager feature */ #define WOLFHSM_CFG_SERVER_IMG_MGR +/* Hardware keys (WOLFHSM_CFG_HWKEYSTORE) are tested in test-refactor */ #ifndef WOLFHSM_CFG_NO_CRYPTO #define WOLFHSM_CFG_KEYWRAP -#define WOLFHSM_CFG_HWKEYSTORE #endif /* Test log-based NVM flash backend */ diff --git a/tools/whnvmtool/README.md b/tools/whnvmtool/README.md index ae70cc7b0..f3dd6ea03 100644 --- a/tools/whnvmtool/README.md +++ b/tools/whnvmtool/README.md @@ -82,9 +82,9 @@ SHE entries take no `