Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/src/5-Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,10 @@ The two tiers keep one large key (e.g. an an ML-DSA-87 private key) from dictati

Each cached key carries its full `whNvmMetadata` record alongside the key bytes, plus an internal **committed flag** marking whether a copy also exists in NVM. This flag is what makes the cache a true working set: when it needs a slot and none is free, the keystore evicts a key that is already committed (and can be reloaded later), but never an uncommitted one. Uncommitted keys are RAM-only, so the caller must commit them to survive eviction or reset; if the cache fills with uncommitted keys, the next cache operation returns `WH_ERROR_NOSPACE` instead of silently dropping material.

A client drives this tier with five operations:
A client drives this tier with six operations:

- **Cache**: write key bytes and metadata into a server cache slot. The key is usable immediately but RAM-only.
- **Cache Random**: have the server generate a random key from its own RNG and cache it into a server cache slot.
- **Commit**: copy a cached key into NVM as a durable object. The cache copy is marked committed and becomes a candidate for eviction.
Comment thread
AlexLanzano marked this conversation as resolved.
- **Evict**: drop the cache copy. If the key was committed, the NVM copy remains and reloads on the next reference; if it was uncommitted, the key is gone.
- **Export**: read a cached key's bytes back to the client, subject to `WH_NVM_FLAGS_NONEXPORTABLE`.
Expand Down
92 changes: 92 additions & 0 deletions src/wh_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,98 @@ int wh_Client_KeyCache(whClientContext* c, uint32_t flags, uint8_t* label,
return ret;
}

int wh_Client_KeyCacheRandomRequest(whClientContext* c, uint32_t flags,
uint8_t* label, uint16_t labelSz,
uint16_t keySz, uint16_t keyId)
{
whMessageKeystore_CacheRandomRequest* req = NULL;
uint16_t capSz;

if (c == NULL || keySz == 0) {
return WH_ERROR_BADARGS;
}

req = (whMessageKeystore_CacheRandomRequest*)wh_CommClient_GetDataPtr(
c->comm);
if (req == NULL) {
return WH_ERROR_BADARGS;
}
memset(req, 0, sizeof(*req));
req->id = keyId;
req->flags = flags;
req->sz = keySz;

if (label == NULL) {
req->labelSz = 0;
}
else {
/* write label */
capSz = (labelSz > WH_NVM_LABEL_LEN) ? WH_NVM_LABEL_LEN : labelSz;
req->labelSz = capSz;
memcpy(req->label, label, capSz);
}

/* write request (no key material is sent) */
return wh_Client_SendRequest(c, WH_MESSAGE_GROUP_KEY, WH_KEY_CACHE_RANDOM,
sizeof(*req), (uint8_t*)req);
}

int wh_Client_KeyCacheRandomResponse(whClientContext* c, uint16_t* keyId)
{
uint16_t group;
uint16_t action;
uint16_t size;
int ret;
whMessageKeystore_CacheRandomResponse *resp = NULL;

if (c == NULL || keyId == NULL) {
return WH_ERROR_BADARGS;
}

resp = (whMessageKeystore_CacheRandomResponse*)wh_CommClient_GetDataPtr(
c->comm);
if (resp == NULL) {
return WH_ERROR_BADARGS;
}

ret = wh_Client_RecvResponse(c, &group, &action, &size, (uint8_t*)resp);
if (ret == WH_ERROR_OK) {
if (resp->rc != 0) {
ret = resp->rc;
}
else {
*keyId = resp->id;
}
}

return ret;
}

int wh_Client_KeyCacheRandom(whClientContext* c, uint32_t flags,
uint8_t* label, uint16_t labelSz,
uint16_t keySz, uint16_t* keyId)
{
int ret = WH_ERROR_OK;

if (keyId == NULL) {
return WH_ERROR_BADARGS;
}

ret = wh_Client_KeyCacheRandomRequest(c, flags, label, labelSz, keySz,
*keyId);

if (ret == 0) {
do {
ret = wh_Client_KeyCacheRandomResponse(c, keyId);
} while (ret == WH_ERROR_NOTREADY);
}

WH_DEBUG_CLIENT_VERBOSE("label:%.*s key_id:%x ret:%d \n",
(label != NULL) ? (int)labelSz : 0,
(label != NULL) ? (const char*)label : "", *keyId, ret);
return ret;
}

int wh_Client_KeyEvictRequest(whClientContext* c, uint16_t keyId)
{
whMessageKeystore_EvictRequest* req = NULL;
Expand Down
32 changes: 32 additions & 0 deletions src/wh_message_keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,38 @@ int wh_MessageKeystore_TranslateCacheResponse(
return 0;
}

/* Key Cache Random Request translation */
int wh_MessageKeystore_TranslateCacheRandomRequest(
uint16_t magic, const whMessageKeystore_CacheRandomRequest* src,
whMessageKeystore_CacheRandomRequest* dest)
{
if ((src == NULL) || (dest == NULL)) {
return WH_ERROR_BADARGS;
}
WH_T32(magic, dest, src, flags);
WH_T32(magic, dest, src, labelSz);
WH_T16(magic, dest, src, sz);
WH_T16(magic, dest, src, id);
/* Label is just a byte array, no translation needed */
if (src != dest) {
memcpy(dest->label, src->label, WH_NVM_LABEL_LEN);
}
return 0;
}

/* Key Cache Random Response translation */
int wh_MessageKeystore_TranslateCacheRandomResponse(
uint16_t magic, const whMessageKeystore_CacheRandomResponse* src,
whMessageKeystore_CacheRandomResponse* dest)
{
if ((src == NULL) || (dest == NULL)) {
return WH_ERROR_BADARGS;
}
WH_T32(magic, dest, src, rc);
WH_T16(magic, dest, src, id);
return 0;
}

/* Key Evict Request translation */
int wh_MessageKeystore_TranslateEvictRequest(
uint16_t magic, const whMessageKeystore_EvictRequest* src,
Expand Down
99 changes: 99 additions & 0 deletions src/wh_server_keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,43 @@ int wh_Server_KeystoreCacheKeyChecked(whServerContext* server,
return _KeystoreCacheKey(server, meta, in, 1);
}

#ifndef WC_NO_RNG
static int _KeystoreCacheRandomKey(whServerContext* server, whNvmMetadata* meta)
{
uint8_t* slotBuf;
whNvmMetadata* slotMeta;
int ret;

/* make sure id and length are valid */
if ((server == NULL) || (meta == NULL) || (meta->len == 0) ||
WH_KEYID_ISERASED(meta->id) ||
((meta->len > WOLFHSM_CFG_SERVER_KEYCACHE_BUFSIZE) &&
(meta->len > WOLFHSM_CFG_SERVER_KEYCACHE_BIG_BUFSIZE))) {
return WH_ERROR_BADARGS;
}

ret = wh_Server_KeystoreGetCacheSlotChecked(server, meta->id, meta->len,
&slotBuf, &slotMeta);
if (ret != WH_ERROR_OK) {
return ret;
}

/* Fill the slot directly from the server RNG */
ret = wc_RNG_GenerateBlock(server->crypto->rng, slotBuf, meta->len);
if (ret != 0) {
return ret;
}

memcpy((uint8_t*)slotMeta, (uint8_t*)meta, sizeof(whNvmMetadata));
_MarkKeyCommitted(_GetCacheContext(server, meta->id), meta->id, 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexLanzano Just noticed this. Why are we marking this as committed? Is it because we never want it to go to NVM and it is meant to be cache-only and auto-evictable? I'm wondering if we want to actually have this just be treated like any other key?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like Im setting it as uncommitted here, no? I used the same logic as the regular key cache handler


WH_DEBUG_SERVER_VERBOSE("hsmGenerateKey: cached keyid=0x%X, len=%u\n",
meta->id, meta->len);

return WH_ERROR_OK;
}
#endif /* !WC_NO_RNG */

static int _FindInCache(whServerContext* server, whKeyId keyId, int* out_index,
int* out_big, uint8_t** out_buffer,
whNvmMetadata** out_meta)
Expand Down Expand Up @@ -2272,6 +2309,68 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic,
*out_resp_size = sizeof(resp);
} break;

case WH_KEY_CACHE_RANDOM: {
whMessageKeystore_CacheRandomRequest req = {0};
whMessageKeystore_CacheRandomResponse resp = {0};

/* Validate req_size can hold the fixed request struct */
if (req_size < sizeof(req)) {
ret = WH_ERROR_BADARGS;
}

if (ret == WH_ERROR_OK) {
/* translate request */
(void)wh_MessageKeystore_TranslateCacheRandomRequest(
magic, (whMessageKeystore_CacheRandomRequest*)req_packet,
&req);

/* set the metadata fields (no key material is sent) */
meta->id = wh_KeyId_TranslateFromClient(
WH_KEYTYPE_CRYPTO, server->comm->client_id, req.id);
meta->access = WH_NVM_ACCESS_ANY;
meta->flags = req.flags;
meta->len = req.sz;
/* truncate label if it's too large */
if (req.labelSz > WH_NVM_LABEL_LEN) {
req.labelSz = WH_NVM_LABEL_LEN;
}
memcpy(meta->label, req.label, req.labelSz);
}

#ifndef WC_NO_RNG
if (ret == WH_ERROR_OK) {
ret = WH_SERVER_NVM_LOCK(server);
if (ret == WH_ERROR_OK) {
/* get a new id if one wasn't provided */
if (WH_KEYID_ISERASED(meta->id)) {
ret = wh_Server_KeystoreGetUniqueId(server, &meta->id);
}
/* generate the key from the server RNG and cache it */
if (ret == WH_ERROR_OK) {
ret = _KeystoreCacheRandomKey(server, meta);
}

(void)WH_SERVER_NVM_UNLOCK(server);
} /* WH_SERVER_NVM_LOCK() */
}
#else
if (ret == WH_ERROR_OK) {
ret = WH_ERROR_NOTIMPL;
}
#endif /* !WC_NO_RNG */

if (ret == WH_ERROR_OK) {
/* Translate server keyId back to client format with flags */
resp.id = wh_KeyId_TranslateToClient(meta->id);
}
resp.rc = ret;

(void)wh_MessageKeystore_TranslateCacheRandomResponse(
magic, &resp, (whMessageKeystore_CacheRandomResponse*)resp_packet);

*out_resp_size = sizeof(resp);
} break;

#ifdef WOLFHSM_CFG_DMA

case WH_KEY_CACHE_DMA: {
Expand Down
109 changes: 109 additions & 0 deletions test/wh_test_crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -6753,6 +6753,110 @@ static int whTest_CacheExportKeyDma(whClientContext* ctx, whKeyId* inout_key_id,
}
#endif /* WOLFHSM_CFG_DMA */

static int whTest_KeyCacheRandom(whClientContext* ctx, int devId,
WC_RNG* rng)
{
(void)devId;
(void)rng; /* Unused */

#define WH_TEST_KEYCACHERANDOM_KEYSIZE 32
int ret;
int i;
int isZero;
uint16_t outLen;
uint16_t keyId;
uint16_t keyId2;
uint8_t keyOut[WH_TEST_KEYCACHERANDOM_KEYSIZE] = {0};
uint8_t keyOut2[WH_TEST_KEYCACHERANDOM_KEYSIZE] = {0};
uint8_t labelIn[WH_NVM_LABEL_LEN] = "KeyCacheRandom Test";
uint8_t labelOut[WH_NVM_LABEL_LEN] = {0};

/* Generate a key from the server RNG and cache it */
keyId = WH_KEYID_ERASED;
ret = wh_Client_KeyCacheRandom(ctx, 0, labelIn, sizeof(labelIn),
WH_TEST_KEYCACHERANDOM_KEYSIZE, &keyId);
if (ret != 0) {
WH_ERROR_PRINT("Failed to wh_Client_KeyCacheRandom %d\n", ret);
}
else if (keyId == WH_KEYID_ERASED) {
WH_ERROR_PRINT("KeyCacheRandom returned erased keyId\n");
ret = -1;
}

/* Export it back and verify size, label, and that RNG actually ran */
if (ret == 0) {
outLen = sizeof(keyOut);
ret = wh_Client_KeyExport(ctx, keyId, labelOut, sizeof(labelOut),
keyOut, &outLen);
if (ret != 0) {
WH_ERROR_PRINT("Failed to wh_Client_KeyExport %d\n", ret);
}
else if (outLen != WH_TEST_KEYCACHERANDOM_KEYSIZE) {
WH_ERROR_PRINT("KeyCacheRandom bad length %u\n", outLen);
ret = -1;
}
else if (memcmp(labelIn, labelOut, sizeof(labelIn)) != 0) {
WH_ERROR_PRINT("KeyCacheRandom label mismatch\n");
ret = -1;
}
else {
/* sanity: generated key should not be all zeros */
isZero = 1;
for (i = 0; i < (int)outLen; i++) {
if (keyOut[i] != 0) {
isZero = 0;
break;
}
}
if (isZero) {
WH_ERROR_PRINT("KeyCacheRandom produced all-zero key\n");
ret = -1;
}
}
}

/* A second generation should yield a distinct auto-assigned keyId and
* distinct key material */
if (ret == 0) {
keyId2 = WH_KEYID_ERASED;
ret = wh_Client_KeyCacheRandom(ctx, 0, labelIn, sizeof(labelIn),
WH_TEST_KEYCACHERANDOM_KEYSIZE, &keyId2);
if (ret != 0) {
WH_ERROR_PRINT("Failed to wh_Client_KeyCacheRandom(2) %d\n", ret);
}
else if (keyId2 == keyId) {
WH_ERROR_PRINT("KeyCacheRandom reused keyId 0x%X\n", keyId);
ret = -1;
}
Comment thread
bigbrett marked this conversation as resolved.
else {
/* Export the second key and confirm its bytes differ from the
* first — two RNG generations must not produce the same key */
outLen = sizeof(keyOut2);
ret = wh_Client_KeyExport(ctx, keyId2, labelOut,
sizeof(labelOut), keyOut2, &outLen);
if (ret != 0) {
WH_ERROR_PRINT("Failed to wh_Client_KeyExport(2) %d\n", ret);
}
else if (outLen != WH_TEST_KEYCACHERANDOM_KEYSIZE) {
WH_ERROR_PRINT("KeyCacheRandom(2) bad length %u\n", outLen);
ret = -1;
}
else if (memcmp(keyOut, keyOut2, outLen) == 0) {
WH_ERROR_PRINT("KeyCacheRandom produced identical keys\n");
ret = -1;
}
}
(void)wh_Client_KeyEvict(ctx, keyId2);
}

(void)wh_Client_KeyEvict(ctx, keyId);

if (ret == 0) {
WH_TEST_PRINT("KEY CACHE RANDOM SUCCESS\n");
}
return ret;
}

static int whTest_KeyCache(whClientContext* ctx, int devId, WC_RNG* rng)
{
(void)devId; (void)rng; /* Unused */
Expand Down Expand Up @@ -15341,6 +15445,11 @@ int whTest_CryptoClientConfig(whClientConfig* config)
ret = whTest_KeyCache(client, WH_CLIENT_DEVID(client), rng);
}

if (ret == 0) {
/* Test server-side key generation from RNG */
ret = whTest_KeyCacheRandom(client, WH_DEV_ID, rng);
}

if (ret == 0) {
/* Test Non-Exportable Flag enforcement on keystore */
ret =
Expand Down
Loading
Loading