Skip to content
Merged
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
20 changes: 20 additions & 0 deletions src/wh_message_nvm.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@
#include "wolfhsm/wh_message_nvm.h"


/* Keep in step with wh_MessageNvm_TranslateAddObjectRequest */
int wh_MessageNvm_TranslateMetadata(uint16_t magic, const whNvmMetadata* src,
whNvmMetadata* dest)
{
if ((src == NULL) || (dest == NULL)) {
return WH_ERROR_BADARGS;
}
WH_T16(magic, dest, src, id);
WH_T16(magic, dest, src, access);
WH_T16(magic, dest, src, flags);
WH_T16(magic, dest, src, len);
/* Label is just a byte array, no translation needed */
if (src != dest) {
memcpy(dest->label, src->label, sizeof(dest->label));
}
return 0;
}

int wh_MessageNvm_TranslateSimpleResponse(uint16_t magic,
const whMessageNvm_SimpleResponse* src,
whMessageNvm_SimpleResponse* dest)
Expand Down Expand Up @@ -141,6 +159,8 @@ int wh_MessageNvm_TranslateGetMetadataResponse(uint16_t magic,
return 0;
}

/* The request mirrors whNvmMetadata: keep in step with
* wh_MessageNvm_TranslateMetadata */
int wh_MessageNvm_TranslateAddObjectRequest(uint16_t magic,
const whMessageNvm_AddObjectRequest* src,
whMessageNvm_AddObjectRequest* dest)
Expand Down
100 changes: 72 additions & 28 deletions src/wh_server_keystore.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "wolfhsm/wh_error.h"
#include "wolfhsm/wh_message.h"
#include "wolfhsm/wh_message_keystore.h"
#include "wolfhsm/wh_message_nvm.h" /* For wh_MessageNvm_TranslateMetadata */
#include "wolfhsm/wh_utils.h"
#include "wolfhsm/wh_server.h"
#include "wolfhsm/wh_log.h"
Expand Down Expand Up @@ -1830,6 +1831,7 @@ static int _AesGcmDataUnwrap(whServerContext* server, uint16_t serverKeyId,
#endif /* !NO_AES */

static int _HandleKeyWrapRequest(whServerContext* server,
uint16_t magic,
whMessageKeystore_KeyWrapRequest* req,
uint8_t* reqData, uint32_t reqDataSz,
whMessageKeystore_KeyWrapResponse* resp,
Expand All @@ -1849,20 +1851,24 @@ static int _HandleKeyWrapRequest(whServerContext* server,
return WH_ERROR_BADARGS;
}

/* Set before any failure exit: the client checks cipherType before rc */
resp->cipherType = req->cipherType;
resp->wrappedKeySz = 0;

/* Check if the reqData is big enough to hold the metadata and key */
if (reqDataSz < sizeof(metadata) + req->keySz) {
return WH_ERROR_BUFFER_SIZE;
}

/* Extract the metadata and key from reqData */
/* Extract the metadata and key. The metadata trailer arrives in the
* client's byte order, so translate before any field is used */
memcpy(&metadata, reqData, sizeof(metadata));
ret = wh_MessageNvm_TranslateMetadata(magic, &metadata, &metadata);
if (ret != WH_ERROR_OK) {
return ret;
}
memcpy(key, reqData + sizeof(metadata), req->keySz);

/* Ensure the cipher type in the response matches the request */
resp->cipherType = req->cipherType;
/* Wrapped key size is only passed back to the client on success */
resp->wrappedKeySz = 0;

/* Ensure the keyId in the wrapped metadata has the wrapped flag set */
if (!WH_KEYID_ISWRAPPED(metadata.id)) {
WH_LOG_F(&server->log, WH_LOG_LEVEL_ERROR,
Expand Down Expand Up @@ -2037,16 +2043,18 @@ _HandleKeyWrapExportRequest(whServerContext* server,
}

static int _HandleKeyUnwrapAndExportRequest(
whServerContext* server, whMessageKeystore_KeyUnwrapAndExportRequest* req,
uint8_t* reqData, uint32_t reqDataSz,
whMessageKeystore_KeyUnwrapAndExportResponse* resp, uint8_t* respData,
uint32_t respDataSz)
whServerContext* server, uint16_t magic,
whMessageKeystore_KeyUnwrapAndExportRequest* req, uint8_t* reqData,
uint32_t reqDataSz, whMessageKeystore_KeyUnwrapAndExportResponse* resp,
uint8_t* respData, uint32_t respDataSz)
{
int ret;
/* Defensive: a case that never assigns ret cannot report success */
int ret = WH_ERROR_BADARGS;
uint8_t* wrappedKey;
whNvmMetadata* metadata;
uint8_t* key;
whKeyId serverKeyId;
uint16_t keySz = 0;

if (server == NULL || req == NULL || reqData == NULL || resp == NULL ||
respData == NULL) {
Expand Down Expand Up @@ -2080,19 +2088,22 @@ static int _HandleKeyUnwrapAndExportRequest(
#ifndef NO_AES
#ifdef HAVE_AESGCM
case WC_CIPHER_AES_GCM: {
uint16_t keySz;
uint16_t wrappedKeyUser = 0;
uint16_t wrappedKeyType = 0;

if (req->wrappedKeySz < WH_KEYWRAP_AES_GCM_HEADER_SIZE +
sizeof(*metadata)) {
return WH_ERROR_BADARGS;
ret = WH_ERROR_BADARGS;
break;
}

keySz = req->wrappedKeySz -
WH_KEYWRAP_AES_GCM_HEADER_SIZE - sizeof(*metadata);

/* Check if the response data can fit the metadata + key */
if (respDataSz < sizeof(*metadata) + keySz) {
return WH_ERROR_BUFFER_SIZE;
ret = WH_ERROR_BUFFER_SIZE;
break;
}

/* Unwrap the key. The plaintext is handed back to the client, not
Expand All @@ -2101,28 +2112,31 @@ static int _HandleKeyUnwrapAndExportRequest(
/*requireTrustedKek=*/0, wrappedKey,
req->wrappedKeySz, metadata, key, keySz);
if (ret != WH_ERROR_OK) {
return ret;
break;
}

/* Dynamic keyId generation for wrapped keys is not allowed */
if (WH_KEYID_IS_UNASSIGNED(metadata->id)) {
/* Wrapped keys must use explicit identifiers */
return WH_ERROR_BADARGS;
ret = WH_ERROR_BADARGS;
break;
}

/* Extract ownership from unwrapped metadata (preserves original
* owner) */
uint16_t wrappedKeyUser = WH_KEYID_USER(metadata->id);
uint16_t wrappedKeyType = WH_KEYID_TYPE(metadata->id);
wrappedKeyUser = WH_KEYID_USER(metadata->id);
wrappedKeyType = WH_KEYID_TYPE(metadata->id);

/* Require explicit wrapped-key encoding */
if (wrappedKeyType != WH_KEYTYPE_WRAPPED) {
return WH_ERROR_ABORTED;
ret = WH_ERROR_ABORTED;
break;
}

/* Check if the key is exportable */
if (metadata->flags & WH_NVM_FLAGS_NONEXPORTABLE) {
return WH_ERROR_ACCESS;
ret = WH_ERROR_ACCESS;
break;
}

/* Validate ownership: USER field must match requesting client.
Expand All @@ -2131,12 +2145,14 @@ static int _HandleKeyUnwrapAndExportRequest(
/* Global keys (USER=0) can be exported by any client */
if (wrappedKeyUser != WH_KEYUSER_GLOBAL &&
wrappedKeyUser != server->comm->client_id) {
return WH_ERROR_ACCESS;
ret = WH_ERROR_ACCESS;
break;
}
#else
/* Without global keys, USER must match requesting client */
if (wrappedKeyUser != server->comm->client_id) {
return WH_ERROR_ACCESS;
ret = WH_ERROR_ACCESS;
break;
}
#endif /* WOLFHSM_CFG_GLOBAL_KEYS */

Expand All @@ -2147,7 +2163,26 @@ static int _HandleKeyUnwrapAndExportRequest(
#endif /* !NO_AES */

default:
return WH_ERROR_BADARGS;
ret = WH_ERROR_BADARGS;
break;
}

if (ret == WH_ERROR_OK) {
/* The blob stores metadata in server order, so every check above ran
* on native values. Convert to client order only on the way out */
ret = wh_MessageNvm_TranslateMetadata(magic, metadata, metadata);
}

/* Keyed off the final ret so a failed translation scrubs too. respData is
* the long-lived comm buffer, so wipe the trailer and any decrypted key */
if (ret != WH_ERROR_OK && respDataSz >= sizeof(*metadata)) {
uint32_t scrubSz = sizeof(*metadata) + keySz;

if (scrubSz > respDataSz) {
scrubSz = respDataSz;
}
resp->keySz = 0;
wh_Utils_ForceZero(metadata, scrubSz);
}

return ret;
Expand Down Expand Up @@ -2497,7 +2532,8 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic,

/* validate args, even though these functions are only supposed to be
* called by internal functions */
if ((server == NULL) || (req_packet == NULL) || (out_resp_size == NULL)) {
if ((server == NULL) || (req_packet == NULL) || (resp_packet == NULL) ||
(out_resp_size == NULL)) {
return WH_ERROR_BADARGS;
}

Expand Down Expand Up @@ -3225,8 +3261,8 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic,
if (ret == WH_ERROR_OK) {
ret = WH_SERVER_NVM_LOCK(server);
if (ret == WH_ERROR_OK) {
ret = _HandleKeyWrapRequest(server, &wrapReq, reqData,
reqDataSz, &wrapResp,
ret = _HandleKeyWrapRequest(server, magic, &wrapReq,
reqData, reqDataSz, &wrapResp,
respData, respDataSz);

(void)WH_SERVER_NVM_UNLOCK(server);
Expand Down Expand Up @@ -3327,14 +3363,22 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic,
ret = WH_SERVER_NVM_LOCK(server);
if (ret == WH_ERROR_OK) {
ret = _HandleKeyUnwrapAndExportRequest(
server, &unwrapReq, reqData, reqDataSz, &unwrapResp,
respData, respDataSz);
server, magic, &unwrapReq, reqData, reqDataSz,
&unwrapResp, respData, respDataSz);

(void)WH_SERVER_NVM_UNLOCK(server);
} /* WH_SERVER_NVM_LOCK() */
}
unwrapResp.rc = ret;

/* The size below always counts a trailer, so clear what a failure
* would otherwise ship out of the shared buffer */
if (ret != WH_ERROR_OK && respDataSz >= sizeof(whNvmMetadata)) {
wh_Utils_ForceZero((uint8_t*)resp_packet +
sizeof(unwrapResp),
sizeof(whNvmMetadata));
}

(void)wh_MessageKeystore_TranslateKeyUnwrapAndExportResponse(
magic, &unwrapResp, resp_packet);

Expand Down
2 changes: 2 additions & 0 deletions test-refactor/misc/wh_test_check_struct_padding.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ whMessageCustomCb_Request whMessageCustomCb_Request_test;
whMessageCustomCb_Response whMessageCustomCb_Response_test;

#include "wolfhsm/wh_message_nvm.h"
/* Raw wire struct: the key wrap trailers memcpy it across the boundary */
whNvmMetadata whNvmMetadata_test;
whMessageNvm_SimpleResponse whMessageNvm_SimpleResponse_test;
whMessageNvm_InitRequest whMessageNvm_InitRequest_test;
whMessageNvm_InitResponse whMessageNvm_InitResponse_test;
Expand Down
128 changes: 128 additions & 0 deletions test-refactor/misc/wh_test_message_nvm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

/* Endian-translation coverage for the NVM message structs. A loopback test
* cannot observe peers that disagree on byte order, so these call the
* translation helpers directly with the magic of a foreign-endian peer. */

#include "wolfhsm/wh_settings.h"

#include <stdint.h>
#include <string.h>

#include "wolfhsm/wh_common.h"
#include "wolfhsm/wh_comm.h"
#include "wolfhsm/wh_error.h"
#include "wolfhsm/wh_message_nvm.h"

#include "wh_test_common.h"
#include "wh_test_list.h"

/* Magic of a peer whose byte order is the opposite of ours */
#define WH_TEST_MAGIC_FOREIGN ((uint16_t)WH_COMM_MAGIC_SWAP)
#define WH_TEST_MAGIC_LOCAL ((uint16_t)WH_COMM_MAGIC_NATIVE)

/* Non-palindromic field values, and the same values byte-reversed */
#define WH_TEST_NVM_META_ID 0x0102u
#define WH_TEST_NVM_META_ACCESS 0x1234u
#define WH_TEST_NVM_META_FLAGS 0x5678u
#define WH_TEST_NVM_META_LEN 0x0a0bu

#define WH_TEST_NVM_META_ID_SWAPPED 0x0201u
#define WH_TEST_NVM_META_ACCESS_SWAPPED 0x3412u
#define WH_TEST_NVM_META_FLAGS_SWAPPED 0x7856u
#define WH_TEST_NVM_META_LEN_SWAPPED 0x0b0au

/* Fill with distinct byte values so any missed swap is visible */
static void _whTest_FillPattern(uint8_t* buf, uint32_t len, uint8_t seed)
{
uint32_t i;

for (i = 0; i < len; i++) {
buf[i] = (uint8_t)(seed + i);
}
}

/* The key wrap messages carry this struct raw, so every scalar swaps and the
* label, a plain byte array, must stay put */
static int _whTest_MessageNvmMetadata(void)
{
whNvmMetadata src;
whNvmMetadata dest;
whNvmMetadata back;

memset(&src, 0, sizeof(src));
memset(&dest, 0, sizeof(dest));
memset(&back, 0, sizeof(back));

src.id = (whNvmId)WH_TEST_NVM_META_ID;
src.access = (whNvmAccess)WH_TEST_NVM_META_ACCESS;
src.flags = (whNvmFlags)WH_TEST_NVM_META_FLAGS;
src.len = (whNvmSize)WH_TEST_NVM_META_LEN;
_whTest_FillPattern(src.label, (uint32_t)sizeof(src.label), 0x70);

/* A same-endian peer must see the metadata unchanged */
WH_TEST_RETURN_ON_FAIL(
wh_MessageNvm_TranslateMetadata(WH_TEST_MAGIC_LOCAL, &src, &dest));
WH_TEST_ASSERT_RETURN(memcmp(&dest, &src, sizeof(src)) == 0);

/* A cross-endian peer swaps every scalar. The expected values are written
* out rather than derived, so they do not share the code under test */
WH_TEST_RETURN_ON_FAIL(
wh_MessageNvm_TranslateMetadata(WH_TEST_MAGIC_FOREIGN, &src, &dest));
WH_TEST_ASSERT_RETURN(dest.id == (whNvmId)WH_TEST_NVM_META_ID_SWAPPED);
WH_TEST_ASSERT_RETURN(dest.access ==
(whNvmAccess)WH_TEST_NVM_META_ACCESS_SWAPPED);
WH_TEST_ASSERT_RETURN(dest.flags ==
(whNvmFlags)WH_TEST_NVM_META_FLAGS_SWAPPED);
WH_TEST_ASSERT_RETURN(dest.len == (whNvmSize)WH_TEST_NVM_META_LEN_SWAPPED);
WH_TEST_ASSERT_RETURN(memcmp(dest.label, src.label, sizeof(src.label)) ==
0);

/* Translation is its own inverse */
WH_TEST_RETURN_ON_FAIL(
wh_MessageNvm_TranslateMetadata(WH_TEST_MAGIC_FOREIGN, &dest, &back));
WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);

/* In-place translation must give the same result */
memcpy(&back, &src, sizeof(back));
WH_TEST_RETURN_ON_FAIL(
wh_MessageNvm_TranslateMetadata(WH_TEST_MAGIC_FOREIGN, &back, &back));
WH_TEST_ASSERT_RETURN(memcmp(&back, &dest, sizeof(src)) == 0);

WH_TEST_ASSERT_RETURN(wh_MessageNvm_TranslateMetadata(
WH_TEST_MAGIC_FOREIGN, NULL, &dest) ==
WH_ERROR_BADARGS);
WH_TEST_ASSERT_RETURN(wh_MessageNvm_TranslateMetadata(
WH_TEST_MAGIC_FOREIGN, &src, NULL) ==
WH_ERROR_BADARGS);

return WH_TEST_SUCCESS;
}

int whTest_MessageNvmTranslate(void* ctx)
{
(void)ctx;

WH_TEST_PRINT("Testing NVM message translation...\n");

WH_TEST_RETURN_ON_FAIL(_whTest_MessageNvmMetadata());

return WH_TEST_SUCCESS;
}
Loading
Loading