Skip to content

Commit 9b9eb3e

Browse files
committed
Add STM32 DHUK (Device Hardware Unique Key) support via crypto callbacks
Transparent DHUK on SAES+DHUK families (WC_STM32_HAS_DHUK): enable by setting an Aes/ecc_key devId to the registered STM32 crypto-callback device (wc_Stm32_DhukRegister), supply the 256-bit seed as the key (wc_AesGcmSetKey) or via wc_ecc_import_wrapped_private. Requires WOLF_CRYPTO_CB. The derived key never enters software.
1 parent 238d323 commit 9b9eb3e

7 files changed

Lines changed: 1427 additions & 0 deletions

File tree

.wolfssl_known_macro_extras

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ WOLFSSL_STM32C5
967967
WOLFSSL_STM32F3
968968
WOLFSSL_STM32F427_RNG
969969
WOLFSSL_STM32U0
970+
WOLFSSL_STM32_DHUK_UNWRAP
970971
WOLFSSL_STRONGEST_HASH_SIG
971972
WOLFSSL_STSAFE_TAKES_SLOT
972973
WOLFSSL_TELIT_M2MB

wolfcrypt/src/aes.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4128,6 +4128,14 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock,
41284128
aes->keylen = keylen;
41294129
aes->rounds = keylen/4 + 6;
41304130
XMEMCPY(rk, userKey, keylen);
4131+
#ifdef WOLF_CRYPTO_CB
4132+
/* Keep a raw (non-reversed) copy for crypto-callback offload, e.g. the
4133+
* DHUK device reads the seed from devKey. Mirrors the generic
4134+
* wc_AesSetKey cryptocb path. */
4135+
if (keylen <= sizeof(aes->devKey)) {
4136+
XMEMCPY(aes->devKey, userKey, keylen);
4137+
}
4138+
#endif
41314139
#if !defined(WOLFSSL_STM32_CUBEMX) || defined(STM32_HAL_V2)
41324140
ByteReverseWords(rk, rk, keylen);
41334141
#endif

wolfcrypt/src/ecc.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7058,6 +7058,50 @@ int wc_ecc_sign_hash_ex(const byte* in, word32 inlen, WC_RNG* rng,
70587058
return stm32_ecc_sign_hash_ex(in, inlen, rng, key, r, s);
70597059
}
70607060

7061+
#if defined(WOLFSSL_DHUK) && defined(WOLFSSL_STM32_BARE) && \
7062+
defined(WC_STM32_HAS_DHUK)
7063+
/* Import a hardware-wrapped ECC private scalar + its derivation seed onto the
7064+
* ecc_key for the DHUK crypto-callback sign path. The scalar is AES-encrypted
7065+
* (offline or on-chip) with the device key that the SAES derives from the seed;
7066+
* at sign time it is decrypted into a short-lived buffer. The devId is NOT set
7067+
* here -- enable the device by setting devId at init
7068+
* (wc_ecc_init_ex(&key, heap, WC_DHUK_DEVID)). See ecc.h for the contract. */
7069+
int wc_ecc_import_wrapped_private(ecc_key* key, const byte* seed, word32 seedSz,
7070+
const byte* wrapped, word32 wrappedLen,
7071+
word32 plainLen)
7072+
{
7073+
if (key == NULL || seed == NULL || wrapped == NULL) {
7074+
return BAD_FUNC_ARG;
7075+
}
7076+
/* Seed is the 256-bit DHUK derivation secret. */
7077+
if (seedSz != sizeof(key->dhuk_seed)) {
7078+
return BAD_FUNC_ARG;
7079+
}
7080+
/* Wrapped scalar blob must be a non-zero multiple of one AES block. */
7081+
if (wrappedLen == 0u || (wrappedLen % 16u) != 0u) {
7082+
return BAD_FUNC_ARG;
7083+
}
7084+
if (wrappedLen > sizeof(key->dhuk_wrapped_priv)) {
7085+
return BAD_FUNC_ARG;
7086+
}
7087+
/* Plain length must fit inside the wrapped blob and be non-zero. */
7088+
if (plainLen == 0u || plainLen > wrappedLen) {
7089+
return BAD_FUNC_ARG;
7090+
}
7091+
/* Wrapped blob must be no larger than the plaintext padded up to a full
7092+
* AES block; a larger blob is malformed and would overrun the fixed-size
7093+
* unwrap buffer used during signing. */
7094+
if (wrappedLen > ((plainLen + 15u) & ~15u)) {
7095+
return BAD_FUNC_ARG;
7096+
}
7097+
XMEMCPY(key->dhuk_wrapped_priv, wrapped, wrappedLen);
7098+
XMEMCPY(key->dhuk_seed, seed, seedSz);
7099+
key->dhuk_wrapped_priv_len = wrappedLen;
7100+
key->dhuk_plain_priv_len = plainLen;
7101+
key->dhuk_seed_sz = seedSz;
7102+
return 0;
7103+
}
7104+
#endif /* WOLFSSL_DHUK && WOLFSSL_STM32_BARE && WC_STM32_HAS_DHUK */
70617105

70627106
#elif !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
70637107
!defined(WOLFSSL_MICROCHIP_TA100) && \
@@ -8149,6 +8193,15 @@ int wc_ecc_free(ecc_key* key)
81498193
wc_MAXQ10XX_EccFree(key);
81508194
#endif
81518195

8196+
#ifdef WOLFSSL_DHUK
8197+
/* Scrub the DHUK derivation seed and wrapped scalar (both secret). */
8198+
ForceZero(key->dhuk_seed, sizeof(key->dhuk_seed));
8199+
ForceZero(key->dhuk_wrapped_priv, sizeof(key->dhuk_wrapped_priv));
8200+
key->dhuk_seed_sz = 0;
8201+
key->dhuk_wrapped_priv_len = 0;
8202+
key->dhuk_plain_priv_len = 0;
8203+
#endif
8204+
81528205
mp_clear(key->pubkey.x);
81538206
mp_clear(key->pubkey.y);
81548207
mp_clear(key->pubkey.z);

wolfcrypt/src/port/st/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,54 @@ The STM32 PKA peripheral accelerates ECC scalar multiplication and ECDSA sign/ve
9696
- BARE-metal V2 PKA ECDSA sign/verify is work-in-progress -- the single-curve P-256 path is functional but multi-curve sweeps in `wolfcrypt_test` hit a -248 result on some boards. Track this in the wolfssl-examples-stm32 STM32_Bare_Test/README.
9797

9898

99+
## STM32 DHUK (Device Hardware Unique Key)
100+
101+
Newer STM32 silicon (U3/U5/WBA/H5/C5/N6; the `WC_STM32_HAS_DHUK` family gate) carries a chip-unique 256-bit key (DHUK) burned into the SAES key-derivation path. wolfSSL exposes it through the standard crypto-callback (`WOLF_CRYPTO_CB`) framework: register the STM32 DHUK device once, init a normal `Aes` / `ecc_key` with its `devId`, then perform NORMAL wolfCrypt operations (AES, AES-GCM/GMAC, ECDSA sign) transparently. There is no separate DHUK module -- the STM32 crypto callback lives in `wolfcrypt/src/port/st/stm32.c`.
102+
103+
A DHUK-protected key is driven by a per-key 256-bit seed. The SAES derives the device-bound working key from (seed, DHUK) inside the hardware; for symmetric operations the derived key never appears in software. For ECDSA the derived key decrypts a wrapped private scalar into a short-lived buffer only.
104+
105+
### Enabling
106+
107+
```
108+
#define WOLFSSL_DHUK /* enable DHUK */
109+
#define WOLF_CRYPTO_CB /* required -- DHUK routes through crypto callbacks */
110+
```
111+
112+
`WC_STM32_HAS_DHUK` is auto-defined for the SAES+DHUK families when `WOLFSSL_DHUK` is set; other families compile out the DHUK code. `WOLFSSL_STM32_BARE` selects the bare-metal SAES backend.
113+
114+
### API
115+
116+
```c
117+
/* one-time: register the STM32 DHUK crypto-callback device */
118+
wc_Stm32_DhukRegister(WC_DHUK_DEVID);
119+
120+
/* AES / GMAC: enable via devId at init, then pass the 256-bit seed as the key */
121+
Aes aes;
122+
wc_AesInit(&aes, NULL, WC_DHUK_DEVID);
123+
wc_AesGcmSetKey(&aes, seed, 32);
124+
wc_AesGcmEncrypt(&aes, NULL, NULL, 0, iv, ivSz, tag, tagSz, aad, aadSz); /* GMAC */
125+
wc_AesFree(&aes);
126+
127+
wc_Stm32_DhukUnRegister(WC_DHUK_DEVID);
128+
```
129+
130+
ECDSA mirrors this: init the key with `wc_ecc_init_ex(&key, NULL, WC_DHUK_DEVID)`, import the wrapped private scalar plus its derivation seed with `wc_ecc_import_wrapped_private(&key, seed, seedSz, wrapped, wrappedLen, plainLen)`, then call the normal `wc_ecc_sign_hash()`; verification uses the in-clear public key unchanged. The seed reaches the device as the AES key bytes (`aes->devKey`, set by the normal `wc_AesSetKey` / `wc_AesGcmSetKey`) or, for ECC, on the `ecc_key`; the STM32 callback reads it and derives the working key inside SAES.
131+
132+
### Provisioning helper
133+
134+
`wc_Stm32_Aes_Wrap()` performs a chip-bound DHUK wrap (KEYSEL=HW, deterministic output) and is retained for provisioning wrapped key material. `WOLFSSL_DHUK_DEVID` (808) / `WOLFSSL_SAES_DEVID` (807) select its wrap-key source.
135+
136+
### Current state
137+
138+
- Validated on STM32U385 (TZEN=0): transparent GMAC, AES-ECB, and ECDSA sign all run through the crypto-callback path; the derived key is deterministic, AES round-trips, and ECDSA verifies with the public counterpart.
139+
- The SAES key-derivation/unwrap passes complete via `SR.BUSY` clearing plus `SR.KEYVALID`, NOT via `CCF` (which is only raised for data-output passes). Waiting on `CCF` for the key path was the original `WC_TIMEOUT_E`; the BUSY/KEYVALID completion is the fix.
140+
- STM32U585 under TZEN=1 secure state: the derive currently stalls (`SR.BUSY` does not clear) -- a secure-context concern (SAES RNG / GTZC) that is open work. DHUK does not otherwise require secure state.
141+
142+
### Optional exact-key import (off by default)
143+
144+
`wc_Stm32_Aes_DhukOp[_ex]()` unwraps a previously DHUK-wrapped key into SAES KEYR and runs AES ECB/CBC with it (importing an externally-chosen key, vs deriving one from a seed). It is compiled only with `WOLFSSL_STM32_DHUK_UNWRAP`, is called explicitly (not auto-routed), and is not re-validated on current hardware.
145+
146+
99147
## STM32 BARE-metal port
100148
101149
`WOLFSSL_STM32_BARE` selects a direct-register integration with zero HAL or StdPeriLib dependency. Use this for:

0 commit comments

Comments
 (0)