Skip to content

Commit ba4fd0b

Browse files
committed
Add STM32 DHUK (Device Hardware Unique Key) support via crypto callbacks
1 parent 48eb842 commit ba4fd0b

7 files changed

Lines changed: 1456 additions & 0 deletions

File tree

.wolfssl_known_macro_extras

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ RCC_AHB3ENR_HASHEN
534534
RCC_AHB3ENR_PKAEN
535535
RCC_AHB3ENR_RNGEN
536536
RCC_AHB3ENR_SAESEN
537+
RCC_CR_SHSION
537538
RCC_MP_AHB5ENSETR_CRYP1EN
538539
RCC_MP_AHB5ENSETR_HASH1EN
539540
RCC_MP_AHB5ENSETR_RNG1EN
@@ -602,6 +603,7 @@ STM32F777xx
602603
STM32G071xx
603604
STM32G491xx
604605
STM32H563xx
606+
STM32H573xx
605607
STM32H723xx
606608
STM32H725xx
607609
STM32H743xx
@@ -983,6 +985,8 @@ WOLFSSL_STM32C5
983985
WOLFSSL_STM32F3
984986
WOLFSSL_STM32F427_RNG
985987
WOLFSSL_STM32U0
988+
WOLFSSL_STM32_DHUK_UNWRAP
989+
WOLFSSL_STM32_USE_SAES
986990
WOLFSSL_STRONGEST_HASH_SIG
987991
WOLFSSL_STSAFE_TAKES_SLOT
988992
WOLFSSL_TELIT_M2MB

wolfcrypt/src/aes.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4154,6 +4154,14 @@ static WARN_UNUSED_RESULT int wc_AesDecrypt(Aes* aes, const byte* inBlock,
41544154
aes->keylen = keylen;
41554155
aes->rounds = keylen/4 + 6;
41564156
XMEMCPY(rk, userKey, keylen);
4157+
#ifdef WOLF_CRYPTO_CB
4158+
/* Keep a raw (non-reversed) copy for crypto-callback offload, e.g. the
4159+
* DHUK device reads the seed from devKey. Mirrors the generic
4160+
* wc_AesSetKey cryptocb path. */
4161+
if (keylen <= sizeof(aes->devKey)) {
4162+
XMEMCPY(aes->devKey, userKey, keylen);
4163+
}
4164+
#endif
41574165
#if !defined(WOLFSSL_STM32_CUBEMX) || defined(STM32_HAL_V2)
41584166
ByteReverseWords(rk, rk, keylen);
41594167
#endif

wolfcrypt/src/ecc.c

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

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

70697113
#elif !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
70707114
!defined(WOLFSSL_MICROCHIP_TA100) && \
@@ -8161,6 +8205,15 @@ int wc_ecc_free(ecc_key* key)
81618205
wc_MAXQ10XX_EccFree(key);
81628206
#endif
81638207

8208+
#ifdef WOLFSSL_DHUK
8209+
/* Scrub the DHUK derivation seed and wrapped scalar (both secret). */
8210+
ForceZero(key->dhuk_seed, sizeof(key->dhuk_seed));
8211+
ForceZero(key->dhuk_wrapped_priv, sizeof(key->dhuk_wrapped_priv));
8212+
key->dhuk_seed_sz = 0;
8213+
key->dhuk_wrapped_priv_len = 0;
8214+
key->dhuk_plain_priv_len = 0;
8215+
#endif
8216+
81648217
mp_clear(key->pubkey.x);
81658218
mp_clear(key->pubkey.y);
81668219
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)