diff --git a/docs/draft/async-crypto.md b/docs/draft/async-crypto.md
index 92fdb3caf..9a57b81b3 100644
--- a/docs/draft/async-crypto.md
+++ b/docs/draft/async-crypto.md
@@ -128,7 +128,7 @@ is:
| Sha256Request / Sha512Request | resumeState + control fields
| resumeState.hiLen (4 bytes) |
| resumeState.loLen (4 bytes) |
-| resumeState.hash (32 or 64 bytes) | intermediate digest
+| resumeState.hash (32 or 64 bytes) | intermediate digest (see below)
| [resumeState.hashType (4 bytes)] | SHA-512 family only
| isLastBlock (4 bytes) |
| inSz (4 bytes) |
@@ -346,6 +346,22 @@ reaches the server:
The hash state (`resumeState`) always travels **inline**, not via DMA, for
cross-architecture concerns (endian translation, etc.)
+### Endianness of `resumeState.hash`
+
+`resumeState.hash` is not an opaque byte array. wolfCrypt keeps the SHA-2
+chaining variables in `wc_Sha256.digest` / `wc_Sha512.digest` as **host-order**
+`word32` / `word64`, and the client copies them to the wire verbatim, so the
+field carries eight chaining words in the *client's* byte order. The server
+byte-swaps each word when the client's `magic` reports the opposite endianness,
+exactly as it does for the SHA-3 Keccak state (`whMessageCrypto_Sha3State`).
+
+The response field is context-dependent: on a non-final update it carries the
+same host-order chaining words, but on `isLastBlock` it carries the finalized
+digest, which wolfCrypt has already emitted in canonical big-endian byte order
+and which must not be swapped. The response translation helpers therefore take
+the chaining word size (4, 8, or 0 for a finalized digest) from the caller —
+see `wh_MessageCrypto_TranslateSha2Response_ex()`.
+
DMA async functions require the client to stash the translated DMA address
across the Request/Response boundary for POST cleanup. This context is stored
in `whClientContext.dma.asyncCtx.sha`:
diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c
index 8012252ec..937f42c88 100644
--- a/src/wh_message_crypto.c
+++ b/src/wh_message_crypto.c
@@ -639,6 +639,43 @@ int wh_MessageCrypto_TranslateEd25519VerifyResponse(
return 0;
}
+/* Number of chaining words in a SHA-2 intermediate state, for every variant */
+#define WH_MESSAGE_CRYPTO_SHA2_STATE_WORDS 8
+
+/* Swap the host-order chaining words of a serialized SHA-2 state, as the SHA3
+ * Keccak state does. wordSize is 4 (SHA224/256) or 8 (SHA384/512); any other
+ * value, including 0 for a finalized digest, copies the array verbatim. */
+static void _TranslateSha2State(uint16_t magic, uint8_t* dest,
+ const uint8_t* src, uint32_t hashSz,
+ uint32_t wordSize)
+{
+ uint32_t i;
+ uint32_t stateSz = WH_MESSAGE_CRYPTO_SHA2_STATE_WORDS * wordSize;
+ uint32_t w32;
+ uint64_t w64;
+
+ if (dest != src) {
+ memcpy(dest, src, hashSz);
+ }
+ if (stateSz > hashSz) {
+ return;
+ }
+ if (wordSize == sizeof(uint32_t)) {
+ for (i = 0; i < stateSz; i += wordSize) {
+ memcpy(&w32, dest + i, sizeof(w32));
+ w32 = wh_Translate32(magic, w32);
+ memcpy(dest + i, &w32, sizeof(w32));
+ }
+ }
+ else if (wordSize == sizeof(uint64_t)) {
+ for (i = 0; i < stateSz; i += wordSize) {
+ memcpy(&w64, dest + i, sizeof(w64));
+ w64 = wh_Translate64(magic, w64);
+ memcpy(dest + i, &w64, sizeof(w64));
+ }
+ }
+}
+
/* SHA256 Request translation. Only the fixed-size header is translated; any
* trailing variable-length input bytes (uint8_t in[inSz]) are raw bytes that
* do not need endian translation. */
@@ -651,11 +688,10 @@ int wh_MessageCrypto_TranslateSha256Request(
}
WH_T32(magic, dest, src, resumeState.hiLen);
WH_T32(magic, dest, src, resumeState.loLen);
- /* Hash value is just a byte array, no translation needed */
- if (src != dest) {
- memcpy(dest->resumeState.hash, src->resumeState.hash,
- sizeof(src->resumeState.hash));
- }
+ /* A request always carries intermediate state, even on the last block */
+ _TranslateSha2State(magic, dest->resumeState.hash, src->resumeState.hash,
+ (uint32_t)sizeof(src->resumeState.hash),
+ (uint32_t)sizeof(uint32_t));
WH_T32(magic, dest, src, isLastBlock);
WH_T32(magic, dest, src, inSz);
return 0;
@@ -675,21 +711,29 @@ int wh_MessageCrypto_TranslateSha512Request(
WH_T32(magic, dest, src, resumeState.hiLen);
WH_T32(magic, dest, src, resumeState.loLen);
WH_T32(magic, dest, src, resumeState.hashType);
- /* Hash value is just a byte array, no translation needed */
- if (src != dest) {
- memcpy(dest->resumeState.hash, src->resumeState.hash,
- sizeof(src->resumeState.hash));
- }
+ /* A request always carries intermediate state, even on the last block */
+ _TranslateSha2State(magic, dest->resumeState.hash, src->resumeState.hash,
+ (uint32_t)sizeof(src->resumeState.hash),
+ (uint32_t)sizeof(uint64_t));
WH_T32(magic, dest, src, isLastBlock);
WH_T32(magic, dest, src, inSz);
return 0;
}
#endif /* WOLFSSL_SHA512 || WOLFSSL_SHA384 */
-/* SHA2 Response translation */
+/* SHA2 Response translation, treating hash as a finalized digest */
int wh_MessageCrypto_TranslateSha2Response(
uint16_t magic, const whMessageCrypto_Sha2Response* src,
whMessageCrypto_Sha2Response* dest)
+{
+ return wh_MessageCrypto_TranslateSha2Response_ex(magic, src, dest, 0);
+}
+
+/* SHA2 Response translation. stateWordSize is the chaining word size when hash
+ * carries intermediate state, or 0 when it carries a finalized digest. */
+int wh_MessageCrypto_TranslateSha2Response_ex(
+ uint16_t magic, const whMessageCrypto_Sha2Response* src,
+ whMessageCrypto_Sha2Response* dest, uint32_t stateWordSize)
{
if ((src == NULL) || (dest == NULL)) {
return WH_ERROR_BADARGS;
@@ -697,10 +741,8 @@ int wh_MessageCrypto_TranslateSha2Response(
WH_T32(magic, dest, src, hiLen);
WH_T32(magic, dest, src, loLen);
WH_T32(magic, dest, src, hashType);
- /* Hash value is just a byte array, no translation needed */
- if (src != dest) {
- memcpy(dest->hash, src->hash, sizeof(src->hash));
- }
+ _TranslateSha2State(magic, dest->hash, src->hash,
+ (uint32_t)sizeof(src->hash), stateWordSize);
return 0;
}
@@ -1024,10 +1066,9 @@ int wh_MessageCrypto_TranslateSha256DmaRequest(
WH_T32(magic, dest, src, resumeState.hiLen);
WH_T32(magic, dest, src, resumeState.loLen);
- if (src != dest) {
- memcpy(dest->resumeState.hash, src->resumeState.hash,
- sizeof(src->resumeState.hash));
- }
+ _TranslateSha2State(magic, dest->resumeState.hash, src->resumeState.hash,
+ (uint32_t)sizeof(src->resumeState.hash),
+ (uint32_t)sizeof(uint32_t));
ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->input, &dest->input);
if (ret != 0) {
@@ -1053,10 +1094,9 @@ int wh_MessageCrypto_TranslateSha512DmaRequest(
WH_T32(magic, dest, src, resumeState.hiLen);
WH_T32(magic, dest, src, resumeState.loLen);
- if (src != dest) {
- memcpy(dest->resumeState.hash, src->resumeState.hash,
- sizeof(src->resumeState.hash));
- }
+ _TranslateSha2State(magic, dest->resumeState.hash, src->resumeState.hash,
+ (uint32_t)sizeof(src->resumeState.hash),
+ (uint32_t)sizeof(uint64_t));
WH_T32(magic, dest, src, resumeState.hashType);
ret = wh_MessageCrypto_TranslateDmaBuffer(magic, &src->input, &dest->input);
@@ -1070,10 +1110,19 @@ int wh_MessageCrypto_TranslateSha512DmaRequest(
return 0;
}
-/* SHA2 DMA Response translation */
+/* SHA2 DMA Response translation, treating hash as a finalized digest */
int wh_MessageCrypto_TranslateSha2DmaResponse(
uint16_t magic, const whMessageCrypto_Sha2DmaResponse* src,
whMessageCrypto_Sha2DmaResponse* dest)
+{
+ return wh_MessageCrypto_TranslateSha2DmaResponse_ex(magic, src, dest, 0);
+}
+
+/* SHA2 DMA Response translation. stateWordSize is the chaining word size when
+ * hash carries intermediate state, or 0 when it carries a finalized digest. */
+int wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ uint16_t magic, const whMessageCrypto_Sha2DmaResponse* src,
+ whMessageCrypto_Sha2DmaResponse* dest, uint32_t stateWordSize)
{
if ((src == NULL) || (dest == NULL)) {
return WH_ERROR_BADARGS;
@@ -1081,9 +1130,8 @@ int wh_MessageCrypto_TranslateSha2DmaResponse(
WH_T32(magic, dest, src, hiLen);
WH_T32(magic, dest, src, loLen);
- if (src != dest) {
- memcpy(dest->hash, src->hash, sizeof(src->hash));
- }
+ _TranslateSha2State(magic, dest->hash, src->hash,
+ (uint32_t)sizeof(src->hash), stateWordSize);
WH_T32(magic, dest, src, hashType);
return wh_MessageCrypto_TranslateDmaAddrStatus(magic, &src->dmaAddrStatus,
diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c
index f83224dc4..d719f9f84 100644
--- a/src/wh_server_crypto.c
+++ b/src/wh_server_crypto.c
@@ -4234,8 +4234,9 @@ static int _HandleSha256(whServerContext* ctx, uint16_t magic, int devId,
/* Translate the response */
if (ret == 0) {
- ret =
- wh_MessageCrypto_TranslateSha2Response(magic, &res, cryptoDataOut);
+ ret = wh_MessageCrypto_TranslateSha2Response_ex(
+ magic, &res, cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint32_t));
if (ret == 0) {
*outSize = sizeof(res);
}
@@ -4325,8 +4326,9 @@ static int _HandleSha224(whServerContext* ctx, uint16_t magic, int devId,
/* Translate the response */
if (ret == 0) {
- ret =
- wh_MessageCrypto_TranslateSha2Response(magic, &res, cryptoDataOut);
+ ret = wh_MessageCrypto_TranslateSha2Response_ex(
+ magic, &res, cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint32_t));
if (ret == 0) {
*outSize = sizeof(res);
}
@@ -4421,8 +4423,9 @@ static int _HandleSha384(whServerContext* ctx, uint16_t magic, int devId,
/* Translate the response */
if (ret == 0) {
- ret =
- wh_MessageCrypto_TranslateSha2Response(magic, &res, cryptoDataOut);
+ ret = wh_MessageCrypto_TranslateSha2Response_ex(
+ magic, &res, cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint64_t));
if (ret == 0) {
*outSize = sizeof(res);
}
@@ -4545,8 +4548,9 @@ static int _HandleSha512(whServerContext* ctx, uint16_t magic, int devId,
/* Translate the response */
if (ret == 0) {
- ret =
- wh_MessageCrypto_TranslateSha2Response(magic, &res, cryptoDataOut);
+ ret = wh_MessageCrypto_TranslateSha2Response_ex(
+ magic, &res, cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint64_t));
if (ret == 0) {
*outSize = sizeof(res);
}
@@ -5920,8 +5924,9 @@ static int _HandleSha256Dma(whServerContext* ctx, uint16_t magic, int devId,
}
}
- (void)wh_MessageCrypto_TranslateSha2DmaResponse(
- magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut);
+ (void)wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint32_t));
*outSize = sizeof(res);
return ret;
@@ -6024,8 +6029,9 @@ static int _HandleSha224Dma(whServerContext* ctx, uint16_t magic, int devId,
}
}
- (void)wh_MessageCrypto_TranslateSha2DmaResponse(
- magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut);
+ (void)wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint32_t));
*outSize = sizeof(res);
return ret;
@@ -6128,8 +6134,9 @@ static int _HandleSha384Dma(whServerContext* ctx, uint16_t magic, int devId,
}
}
- (void)wh_MessageCrypto_TranslateSha2DmaResponse(
- magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut);
+ (void)wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint64_t));
*outSize = sizeof(res);
return ret;
@@ -6266,8 +6273,9 @@ static int _HandleSha512Dma(whServerContext* ctx, uint16_t magic, int devId,
}
}
- (void)wh_MessageCrypto_TranslateSha2DmaResponse(
- magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut);
+ (void)wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ magic, &res, (whMessageCrypto_Sha2DmaResponse*)cryptoDataOut,
+ req.isLastBlock ? 0 : (uint32_t)sizeof(uint64_t));
*outSize = sizeof(res);
return ret;
diff --git a/test-refactor/misc/wh_test_message_crypto.c b/test-refactor/misc/wh_test_message_crypto.c
new file mode 100644
index 000000000..e37d97834
--- /dev/null
+++ b/test-refactor/misc/wh_test_message_crypto.c
@@ -0,0 +1,384 @@
+/*
+ * 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 .
+ */
+
+/* Endian-translation coverage for the crypto 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
+#include
+
+#include "wolfhsm/wh_comm.h"
+#include "wolfhsm/wh_error.h"
+#include "wolfhsm/wh_message_crypto.h"
+
+#include "wh_test_common.h"
+#include "wh_test_list.h"
+
+/* Number of chaining words a SHA-2 intermediate state carries */
+#define WH_TEST_SHA2_STATE_WORDS 8
+
+/* 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)
+
+/* 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);
+ }
+}
+
+/* Return 1 if each word of dest is the matching word of src, byte-reversed */
+static int _whTest_WordsSwapped(const uint8_t* dest, const uint8_t* src,
+ uint32_t wordSize, uint32_t words)
+{
+ uint32_t i;
+ uint32_t j;
+
+ for (i = 0; i < words; i++) {
+ for (j = 0; j < wordSize; j++) {
+ if (dest[(i * wordSize) + j] !=
+ src[(i * wordSize) + (wordSize - 1u - j)]) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+static int _whTest_MessageCryptoSha256Request(void)
+{
+ whMessageCrypto_Sha256Request src;
+ whMessageCrypto_Sha256Request dest;
+ whMessageCrypto_Sha256Request back;
+
+ memset(&src, 0, sizeof(src));
+ memset(&dest, 0, sizeof(dest));
+ memset(&back, 0, sizeof(back));
+
+ src.resumeState.hiLen = 0x01020304u;
+ src.resumeState.loLen = 0x05060708u;
+ src.isLastBlock = 1;
+ src.inSz = 0x090a0b0cu;
+ _whTest_FillPattern(src.resumeState.hash,
+ (uint32_t)sizeof(src.resumeState.hash), 0x10);
+
+ /* A same-endian peer must see the message unchanged */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha256Request(WH_TEST_MAGIC_LOCAL, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(memcmp(&dest, &src, sizeof(src)) == 0);
+
+ /* A cross-endian peer swaps every chaining word. A request carries
+ * intermediate state even on the last block, so isLastBlock is set here. */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha256Request(WH_TEST_MAGIC_FOREIGN, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(
+ _whTest_WordsSwapped(dest.resumeState.hash, src.resumeState.hash,
+ (uint32_t)sizeof(uint32_t),
+ WH_TEST_SHA2_STATE_WORDS));
+ WH_TEST_ASSERT_RETURN(dest.resumeState.hiLen ==
+ wh_Translate32(WH_TEST_MAGIC_FOREIGN,
+ src.resumeState.hiLen));
+
+ /* Translation is its own inverse */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha256Request(WH_TEST_MAGIC_FOREIGN, &dest,
+ &back));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);
+
+ /* In-place translation must give the same result */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha256Request(WH_TEST_MAGIC_FOREIGN, &back,
+ &back));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &dest, sizeof(src)) == 0);
+
+ return WH_TEST_SUCCESS;
+}
+
+#if defined(WOLFSSL_SHA512) || defined(WOLFSSL_SHA384)
+static int _whTest_MessageCryptoSha512Request(void)
+{
+ whMessageCrypto_Sha512Request src;
+ whMessageCrypto_Sha512Request dest;
+ whMessageCrypto_Sha512Request back;
+
+ memset(&src, 0, sizeof(src));
+ memset(&dest, 0, sizeof(dest));
+ memset(&back, 0, sizeof(back));
+
+ src.resumeState.hiLen = 0x01020304u;
+ src.resumeState.loLen = 0x05060708u;
+ src.resumeState.hashType = 0x090a0b0cu;
+ src.isLastBlock = 0;
+ src.inSz = 128;
+ _whTest_FillPattern(src.resumeState.hash,
+ (uint32_t)sizeof(src.resumeState.hash), 0x20);
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha512Request(WH_TEST_MAGIC_LOCAL, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(memcmp(&dest, &src, sizeof(src)) == 0);
+
+ /* The SHA512 family chains 64-bit words */
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha512Request(WH_TEST_MAGIC_FOREIGN, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(
+ _whTest_WordsSwapped(dest.resumeState.hash, src.resumeState.hash,
+ (uint32_t)sizeof(uint64_t),
+ WH_TEST_SHA2_STATE_WORDS));
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha512Request(WH_TEST_MAGIC_FOREIGN, &dest,
+ &back));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);
+
+ return WH_TEST_SUCCESS;
+}
+#endif /* WOLFSSL_SHA512 || WOLFSSL_SHA384 */
+
+static int _whTest_MessageCryptoSha2Response(void)
+{
+ whMessageCrypto_Sha2Response src;
+ whMessageCrypto_Sha2Response dest;
+ whMessageCrypto_Sha2Response back;
+
+ memset(&src, 0, sizeof(src));
+ memset(&dest, 0, sizeof(dest));
+ memset(&back, 0, sizeof(back));
+
+ src.hiLen = 0x01020304u;
+ src.loLen = 0x05060708u;
+ src.hashType = 0x090a0b0cu;
+ _whTest_FillPattern(src.hash, (uint32_t)sizeof(src.hash), 0x30);
+
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2Response_ex(
+ WH_TEST_MAGIC_LOCAL, &src, &dest, (uint32_t)sizeof(uint32_t)));
+ WH_TEST_ASSERT_RETURN(memcmp(&dest, &src, sizeof(src)) == 0);
+
+ /* SHA224/256 state: the first 8 words swap, the unused tail is copied */
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2Response_ex(
+ WH_TEST_MAGIC_FOREIGN, &src, &dest, (uint32_t)sizeof(uint32_t)));
+ WH_TEST_ASSERT_RETURN(_whTest_WordsSwapped(dest.hash, src.hash,
+ (uint32_t)sizeof(uint32_t),
+ WH_TEST_SHA2_STATE_WORDS));
+ WH_TEST_ASSERT_RETURN(memcmp(&dest.hash[32], &src.hash[32], 32) == 0);
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2Response_ex(
+ WH_TEST_MAGIC_FOREIGN, &dest, &back, (uint32_t)sizeof(uint32_t)));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);
+
+ /* SHA384/512 state: all 8 words are 64 bits wide */
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2Response_ex(
+ WH_TEST_MAGIC_FOREIGN, &src, &dest, (uint32_t)sizeof(uint64_t)));
+ WH_TEST_ASSERT_RETURN(_whTest_WordsSwapped(dest.hash, src.hash,
+ (uint32_t)sizeof(uint64_t),
+ WH_TEST_SHA2_STATE_WORDS));
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2Response_ex(
+ WH_TEST_MAGIC_FOREIGN, &dest, &back, (uint32_t)sizeof(uint64_t)));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);
+
+ /* A finalized digest is already in canonical order and must not move,
+ * which is what the plain (non _ex) helper is for */
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2Response_ex(
+ WH_TEST_MAGIC_FOREIGN, &src, &dest, 0));
+ WH_TEST_ASSERT_RETURN(memcmp(dest.hash, src.hash, sizeof(src.hash)) == 0);
+ WH_TEST_ASSERT_RETURN(dest.loLen ==
+ wh_Translate32(WH_TEST_MAGIC_FOREIGN, src.loLen));
+
+ memset(&dest, 0, sizeof(dest));
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2Response(
+ WH_TEST_MAGIC_FOREIGN, &src, &dest));
+ WH_TEST_ASSERT_RETURN(memcmp(dest.hash, src.hash, sizeof(src.hash)) == 0);
+
+ return WH_TEST_SUCCESS;
+}
+
+#if defined(WOLFHSM_CFG_DMA)
+static int _whTest_MessageCryptoSha256DmaRequest(void)
+{
+ whMessageCrypto_Sha256DmaRequest src;
+ whMessageCrypto_Sha256DmaRequest dest;
+ whMessageCrypto_Sha256DmaRequest back;
+
+ memset(&src, 0, sizeof(src));
+ memset(&dest, 0, sizeof(dest));
+ memset(&back, 0, sizeof(back));
+
+ src.resumeState.hiLen = 0x01020304u;
+ src.resumeState.loLen = 0x05060708u;
+ src.input.addr = 0x1122334455667788ull;
+ src.input.sz = 64;
+ src.isLastBlock = 0;
+ src.inSz = 0;
+ _whTest_FillPattern(src.resumeState.hash,
+ (uint32_t)sizeof(src.resumeState.hash), 0x40);
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha256DmaRequest(WH_TEST_MAGIC_LOCAL, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(memcmp(&dest, &src, sizeof(src)) == 0);
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha256DmaRequest(WH_TEST_MAGIC_FOREIGN, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(
+ _whTest_WordsSwapped(dest.resumeState.hash, src.resumeState.hash,
+ (uint32_t)sizeof(uint32_t),
+ WH_TEST_SHA2_STATE_WORDS));
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha256DmaRequest(WH_TEST_MAGIC_FOREIGN, &dest,
+ &back));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);
+
+ return WH_TEST_SUCCESS;
+}
+
+static int _whTest_MessageCryptoSha512DmaRequest(void)
+{
+ whMessageCrypto_Sha512DmaRequest src;
+ whMessageCrypto_Sha512DmaRequest dest;
+ whMessageCrypto_Sha512DmaRequest back;
+
+ memset(&src, 0, sizeof(src));
+ memset(&dest, 0, sizeof(dest));
+ memset(&back, 0, sizeof(back));
+
+ src.resumeState.hiLen = 0x01020304u;
+ src.resumeState.loLen = 0x05060708u;
+ src.resumeState.hashType = 0x090a0b0cu;
+ src.input.addr = 0x1122334455667788ull;
+ src.input.sz = 128;
+ _whTest_FillPattern(src.resumeState.hash,
+ (uint32_t)sizeof(src.resumeState.hash), 0x50);
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha512DmaRequest(WH_TEST_MAGIC_LOCAL, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(memcmp(&dest, &src, sizeof(src)) == 0);
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha512DmaRequest(WH_TEST_MAGIC_FOREIGN, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(
+ _whTest_WordsSwapped(dest.resumeState.hash, src.resumeState.hash,
+ (uint32_t)sizeof(uint64_t),
+ WH_TEST_SHA2_STATE_WORDS));
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha512DmaRequest(WH_TEST_MAGIC_FOREIGN, &dest,
+ &back));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);
+
+ return WH_TEST_SUCCESS;
+}
+
+static int _whTest_MessageCryptoSha2DmaResponse(void)
+{
+ whMessageCrypto_Sha2DmaResponse src;
+ whMessageCrypto_Sha2DmaResponse dest;
+ whMessageCrypto_Sha2DmaResponse back;
+
+ memset(&src, 0, sizeof(src));
+ memset(&dest, 0, sizeof(dest));
+ memset(&back, 0, sizeof(back));
+
+ src.hiLen = 0x01020304u;
+ src.loLen = 0x05060708u;
+ src.hashType = 0x090a0b0cu;
+ src.dmaAddrStatus.badAddr.addr = 0x1122334455667788ull;
+ _whTest_FillPattern(src.hash, (uint32_t)sizeof(src.hash), 0x60);
+
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ WH_TEST_MAGIC_FOREIGN, &src, &dest, (uint32_t)sizeof(uint32_t)));
+ WH_TEST_ASSERT_RETURN(_whTest_WordsSwapped(dest.hash, src.hash,
+ (uint32_t)sizeof(uint32_t),
+ WH_TEST_SHA2_STATE_WORDS));
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ WH_TEST_MAGIC_FOREIGN, &dest, &back, (uint32_t)sizeof(uint32_t)));
+ WH_TEST_ASSERT_RETURN(memcmp(&back, &src, sizeof(src)) == 0);
+
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ WH_TEST_MAGIC_FOREIGN, &src, &dest, (uint32_t)sizeof(uint64_t)));
+ WH_TEST_ASSERT_RETURN(_whTest_WordsSwapped(dest.hash, src.hash,
+ (uint32_t)sizeof(uint64_t),
+ WH_TEST_SHA2_STATE_WORDS));
+
+ /* Final block: the digest is canonical already */
+ WH_TEST_RETURN_ON_FAIL(wh_MessageCrypto_TranslateSha2DmaResponse(
+ WH_TEST_MAGIC_FOREIGN, &src, &dest));
+ WH_TEST_ASSERT_RETURN(memcmp(dest.hash, src.hash, sizeof(src.hash)) == 0);
+
+ return WH_TEST_SUCCESS;
+}
+#endif /* WOLFHSM_CFG_DMA */
+
+/* The SHA3 Keccak state has always been translated per word. Checking it with
+ * the same helper confirms the helper agrees with known-good behavior. */
+static int _whTest_MessageCryptoSha3State(void)
+{
+ whMessageCrypto_Sha3State src;
+ whMessageCrypto_Sha3State dest;
+ int i;
+
+ memset(&src, 0, sizeof(src));
+ memset(&dest, 0, sizeof(dest));
+
+ for (i = 0; i < 25; i++) {
+ src.s[i] = 0x0102030405060708ull + (uint64_t)i;
+ }
+
+ WH_TEST_RETURN_ON_FAIL(
+ wh_MessageCrypto_TranslateSha3State(WH_TEST_MAGIC_FOREIGN, &src,
+ &dest));
+ WH_TEST_ASSERT_RETURN(_whTest_WordsSwapped((const uint8_t*)dest.s,
+ (const uint8_t*)src.s,
+ (uint32_t)sizeof(uint64_t), 25));
+
+ return WH_TEST_SUCCESS;
+}
+
+int whTest_MessageCryptoTranslate(void* ctx)
+{
+ (void)ctx;
+
+ WH_TEST_PRINT("Testing crypto message translation...\n");
+
+ WH_TEST_RETURN_ON_FAIL(_whTest_MessageCryptoSha256Request());
+#if defined(WOLFSSL_SHA512) || defined(WOLFSSL_SHA384)
+ WH_TEST_RETURN_ON_FAIL(_whTest_MessageCryptoSha512Request());
+#endif
+ WH_TEST_RETURN_ON_FAIL(_whTest_MessageCryptoSha2Response());
+#if defined(WOLFHSM_CFG_DMA)
+ WH_TEST_RETURN_ON_FAIL(_whTest_MessageCryptoSha256DmaRequest());
+ WH_TEST_RETURN_ON_FAIL(_whTest_MessageCryptoSha512DmaRequest());
+ WH_TEST_RETURN_ON_FAIL(_whTest_MessageCryptoSha2DmaResponse());
+#endif
+ WH_TEST_RETURN_ON_FAIL(_whTest_MessageCryptoSha3State());
+
+ return WH_TEST_SUCCESS;
+}
diff --git a/test-refactor/wh_test_list.c b/test-refactor/wh_test_list.c
index 70f7d0396..88dc37aa9 100644
--- a/test-refactor/wh_test_list.c
+++ b/test-refactor/wh_test_list.c
@@ -45,6 +45,7 @@ WH_TEST_DECL(whTest_Comm);
WH_TEST_DECL(whTest_Dma);
WH_TEST_DECL(whTest_HwKeystore);
WH_TEST_DECL(whTest_KeystoreReqSize);
+WH_TEST_DECL(whTest_MessageCryptoTranslate);
WH_TEST_DECL(whTest_MultiClient);
WH_TEST_DECL(whTest_Lock);
WH_TEST_DECL(whTest_CertVerify);
@@ -102,6 +103,7 @@ const whTestCase whTestsMisc[] = {
{ "whTest_Comm", whTest_Comm },
{ "whTest_Dma", whTest_Dma },
{ "whTest_KeystoreReqSize", whTest_KeystoreReqSize },
+ { "whTest_MessageCryptoTranslate", whTest_MessageCryptoTranslate },
{ "whTest_MultiClient", whTest_MultiClient },
{ "whTest_HwKeystore", whTest_HwKeystore },
{ "whTest_Lock", whTest_Lock },
diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h
index f87cab564..442f12c4b 100644
--- a/wolfhsm/wh_message_crypto.h
+++ b/wolfhsm/wh_message_crypto.h
@@ -883,6 +883,13 @@ int wh_MessageCrypto_TranslateSha2Response(
uint16_t magic, const whMessageCrypto_Sha2Response* src,
whMessageCrypto_Sha2Response* dest);
+/* hash carries the host-order chaining state on a non-final update and a
+ * finalized digest on the last block, so the caller states which it is:
+ * stateWordSize is 4 (SHA224/256), 8 (SHA384/512), or 0 for a digest. */
+int wh_MessageCrypto_TranslateSha2Response_ex(
+ uint16_t magic, const whMessageCrypto_Sha2Response* src,
+ whMessageCrypto_Sha2Response* dest, uint32_t stateWordSize);
+
/*
* SHA3 (all variants: 224/256/384/512)
*
@@ -1330,6 +1337,11 @@ int wh_MessageCrypto_TranslateSha2DmaResponse(
uint16_t magic, const whMessageCrypto_Sha2DmaResponse* src,
whMessageCrypto_Sha2DmaResponse* dest);
+/* See wh_MessageCrypto_TranslateSha2Response_ex for stateWordSize */
+int wh_MessageCrypto_TranslateSha2DmaResponse_ex(
+ uint16_t magic, const whMessageCrypto_Sha2DmaResponse* src,
+ whMessageCrypto_Sha2DmaResponse* dest, uint32_t stateWordSize);
+
/* SHA3 DMA Request - state is passed inline (not via DMA) for
* cross-architecture safety. Only whole-block input data goes via DMA.
* Variant is conveyed in the generic request header's algoType field.