Skip to content

Commit e88805b

Browse files
committed
Add STM32 CCB support
1 parent 1418e16 commit e88805b

6 files changed

Lines changed: 1341 additions & 5 deletions

File tree

.wolfssl_known_macro_extras

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,13 +517,15 @@ QAT_ENABLE_RNG
517517
QAT_USE_POLLING_CHECK
518518
RCC_AHB1ENR_PKAEN
519519
RCC_AHB2ENR1_AESEN
520+
RCC_AHB2ENR1_CCBEN
520521
RCC_AHB2ENR1_HASHEN
521522
RCC_AHB2ENR1_PKAEN
522523
RCC_AHB2ENR1_SAESEN
523524
RCC_AHB2ENR_AESEN
524525
RCC_AHB2ENR_HASHEN
525526
RCC_AHB2ENR_PKAEN
526527
RCC_AHB2ENR_SAESEN
528+
RCC_AHB2RSTR1_CCBRST
527529
RCC_AHB2RSTR_PKARST
528530
RCC_AHB3ENR_AESEN
529531
RCC_AHB3ENR_CRYPEN
@@ -973,6 +975,7 @@ WOLFSSL_STM32C5
973975
WOLFSSL_STM32F3
974976
WOLFSSL_STM32F427_RNG
975977
WOLFSSL_STM32U0
978+
WOLFSSL_STM32_CCB
976979
WOLFSSL_STM32_DHUK_UNWRAP
977980
WOLFSSL_STM32_USE_SAES
978981
WOLFSSL_STRONGEST_HASH_SIG

wolfcrypt/src/ecc.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8065,6 +8065,113 @@ int wc_ecc_sign_set_k(const byte* k, word32 klen, ecc_key* key)
80658065
#endif /* WOLFSSL_ECDSA_SET_K || WOLFSSL_ECDSA_SET_K_ONE_LOOP */
80668066
#endif /* WOLFSSL_ATECC508A && WOLFSSL_CRYPTOCELL */
80678067

8068+
#if defined(WOLFSSL_STM32_CCB)
8069+
/* Load a previously provisioned device-protected ECDSA blob (wrapped scalar +
8070+
* AES-GCM iv/tag) and its public key onto the ecc_key. Sets the curve so the
8071+
* sign path can derive the parameters, and marks the key for the device
8072+
* crypto-callback. The caller enables the device with
8073+
* wc_ecc_init_ex(&key, heap, WC_DHUK_DEVID). Generic name -- the wrapping is
8074+
* the STM32 CCB, but the surface is not CCB-specific. */
8075+
int wc_ecc_import_wrapped_private_ex(ecc_key* key, int curve_id,
8076+
const byte* wrapped, word32 wrappedLen,
8077+
const byte* iv, const byte* tag,
8078+
const byte* pub, word32 pubLen)
8079+
{
8080+
int modSz;
8081+
int ret;
8082+
8083+
if (key == NULL || wrapped == NULL || iv == NULL || tag == NULL ||
8084+
pub == NULL) {
8085+
return BAD_FUNC_ARG;
8086+
}
8087+
modSz = wc_ecc_get_curve_size_from_id(curve_id);
8088+
if (modSz <= 0) {
8089+
return BAD_FUNC_ARG;
8090+
}
8091+
if (wrappedLen == 0u || wrappedLen > sizeof(key->dhuk_wrapped_priv)) {
8092+
return BAD_FUNC_ARG;
8093+
}
8094+
if (pubLen != (word32)(2 * modSz)) {
8095+
return BAD_FUNC_ARG;
8096+
}
8097+
/* Import public key (qx||qy) + set the curve (key->dp). */
8098+
ret = wc_ecc_import_unsigned(key, pub, pub + modSz, NULL, curve_id);
8099+
if (ret != 0) {
8100+
return ret;
8101+
}
8102+
XMEMCPY(key->dhuk_wrapped_priv, wrapped, wrappedLen);
8103+
key->dhuk_wrapped_priv_len = wrappedLen;
8104+
XMEMCPY(key->ccb_iv, iv, sizeof(key->ccb_iv));
8105+
XMEMCPY(key->ccb_tag, tag, sizeof(key->ccb_tag));
8106+
key->dhuk_is_ccb = 1;
8107+
return 0;
8108+
}
8109+
8110+
/* Crypto-callback keygen handler (WC_PK_TYPE_EC_KEYGEN). Provisions a fresh
8111+
* device-protected key: generate a scalar, have the CCB wrap it into a
8112+
* device-bound blob and derive its public key, and store both on the ecc_key
8113+
* (the scalar is zeroized and never leaves this call / the hardware). The
8114+
* scalar is generated in software with the supplied rng on a throwaway key with
8115+
* no devId (so no callback recursion). Returns CRYPTOCB_UNAVAILABLE for curves
8116+
* the CCB cannot wrap so keygen falls back to software. Not a public entry
8117+
* point -- reached via wc_ecc_make_key() on a WC_DHUK_DEVID key. */
8118+
int wc_ecc_dev_make_key(WC_RNG* rng, int keysize, ecc_key* key, int curve_id)
8119+
{
8120+
ecc_key tmp;
8121+
byte d[MAX_ECC_BYTES];
8122+
byte pub[2 * MAX_ECC_BYTES]; /* qx || qy, contiguous */
8123+
byte iv[16];
8124+
byte tag[16];
8125+
byte wrapped[96];
8126+
word32 dLen;
8127+
word32 wrappedSz = 0;
8128+
int modSz;
8129+
int ret;
8130+
int tmpInit = 0;
8131+
8132+
(void)keysize;
8133+
if (rng == NULL || key == NULL) {
8134+
return BAD_FUNC_ARG;
8135+
}
8136+
/* wc_ecc_set_curve() ran before the callback, so key->dp is resolved even
8137+
* when curve_id came in as the default. */
8138+
if (curve_id <= 0 && key->dp != NULL) {
8139+
curve_id = key->dp->id;
8140+
}
8141+
modSz = wc_ecc_get_curve_size_from_id(curve_id);
8142+
if (modSz <= 0 || (word32)modSz > sizeof(d)) {
8143+
return CRYPTOCB_UNAVAILABLE; /* unsupported -> software keygen */
8144+
}
8145+
8146+
ret = wc_ecc_init_ex(&tmp, key->heap, INVALID_DEVID);
8147+
if (ret == 0) {
8148+
tmpInit = 1;
8149+
ret = wc_ecc_make_key_ex(rng, modSz, &tmp, curve_id);
8150+
}
8151+
if (ret == 0) {
8152+
dLen = (word32)modSz;
8153+
ret = wc_ecc_export_private_only(&tmp, d, &dLen);
8154+
}
8155+
if (ret == 0) {
8156+
ret = wc_Stm32_Ccb_EccMakeBlob(curve_id, d, dLen, iv, tag, wrapped,
8157+
&wrappedSz, pub, pub + modSz);
8158+
if (ret != 0) {
8159+
ret = CRYPTOCB_UNAVAILABLE; /* curve not CCB-wrappable -> SW */
8160+
}
8161+
}
8162+
if (ret == 0) {
8163+
ret = wc_ecc_import_wrapped_private_ex(key, curve_id, wrapped, wrappedSz,
8164+
iv, tag, pub, (word32)(2 * modSz));
8165+
}
8166+
8167+
ForceZero(d, sizeof(d));
8168+
if (tmpInit) {
8169+
wc_ecc_free(&tmp);
8170+
}
8171+
return ret;
8172+
}
8173+
#endif /* WOLFSSL_STM32_CCB */
8174+
80688175
#endif /* !HAVE_ECC_SIGN */
80698176

80708177
#ifdef WOLFSSL_CUSTOM_CURVES

wolfcrypt/src/port/st/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,58 @@ ECDSA mirrors this: init the key with `wc_ecc_init_ex(&key, NULL, WC_DHUK_DEVID)
144144
`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.
145145
146146
147+
## STM32 CCB (Coupling and Chaining Bridge)
148+
149+
STM32U3 silicon (e.g. U385; RM0487 ch 31, the `WC_STM32_HAS_CCB` gate) carries the CCB peripheral, which chains the PKA, SAES and RNG over a private local interconnect. This lets a DHUK-protected ECDSA private scalar be unwrapped by the SAES and consumed by the PKA entirely in hardware -- the scalar never crosses the system bus or enters software, not even into a short-lived buffer (unlike the generic DHUK ECDSA path above, which decrypts the scalar into a stack buffer). CCB builds on DHUK: the private key is held as a chip-bound AES-GCM blob (`iv` / `tag` / wrapped scalar) created under the silicon DHUK.
150+
151+
CCB is supported on both build paths: the bare-metal direct-register OPSTEP driver (`WOLFSSL_STM32_BARE`) and the CubeMX/HAL path (`WOLFSSL_STM32_CUBEMX`, via ST's `HAL_CCB_*` driver). It currently covers ECDSA over P-256.
152+
153+
### Enabling
154+
155+
```
156+
#define WOLFSSL_DHUK /* CCB is a DHUK feature */
157+
#define WOLF_CRYPTO_CB /* required -- transparent sign routes through crypto callbacks */
158+
#define WOLFSSL_STM32_CCB /* opt in to the CCB-protected ECDSA path */
159+
```
160+
161+
`WOLFSSL_STM32_CCB` requires CCB silicon (`WOLFSSL_STM32U3`) and either `WOLFSSL_STM32_BARE` or `WOLFSSL_STM32_CUBEMX` (a `#error` fires otherwise).
162+
163+
### API
164+
165+
The whole flow uses the **standard ECC API** -- there is no CCB-specific public API. Binding the key to `WC_DHUK_DEVID` routes keygen and sign through the STM32 crypto callback, which provisions and uses the CCB-protected key transparently (a drop-in for TLS and other consumers). The same flow works on both build paths.
166+
167+
```c
168+
ecc_key key;
169+
170+
/* one-time: register the STM32 DHUK/CCB crypto-callback device */
171+
wc_Stm32_DhukRegister(WC_DHUK_DEVID);
172+
173+
wc_ecc_init_ex(&key, NULL, WC_DHUK_DEVID);
174+
175+
/* provision a fresh device-bound key with the STANDARD keygen -- the crypto
176+
* callback intercepts it: the CCB generates the scalar, wraps it into a blob
177+
* and derives the public key, all in hardware. No CCB-specific API. */
178+
wc_ecc_make_key_ex(&rng, 32, &key, ECC_SECP256R1);
179+
180+
/* transparent sign -- the scalar is unwrapped SAES->PKA in HW and signed */
181+
wc_ecc_sign_hash(hash, hashLen, sig, &sigLen, &rng, &key);
182+
183+
/* verify with the in-clear public key, unchanged */
184+
wc_ecc_verify_hash(sig, sigLen, hash, hashLen, &verified, &key);
185+
186+
wc_ecc_free(&key);
187+
wc_Stm32_DhukUnRegister(WC_DHUK_DEVID);
188+
```
189+
190+
To reuse a key across resets, persist the blob from a provisioned key and reload it later with `wc_ecc_import_wrapped_private_ex(&key, curve_id, wrapped, wrappedLen, iv, tag, pub, pubLen)` (the public key in uncompressed `qx||qy` form), then sign as above. Both paths set `key->dhuk_is_ccb` and the device `devId`, so dispatch to the CCB happens automatically inside the crypto callback.
191+
192+
### Current state
193+
194+
- Validated on STM32U385 (NUCLEO-U385RG-Q, TZEN=0), P-256, on both the bare-metal and CubeMX/HAL build paths: `wc_ecc_make_key` -> `wc_ecc_sign_hash` -> `wc_ecc_verify_hash` round-trips, with the private scalar never present in software.
195+
- `Stm32Ccb_Init()` pulse-resets the PKA / SAES / RNG before each operation, so the first CCB op is robust even when prior standalone crypto (RNG seeding, ECC keygen) left an engine in a state that would otherwise stall the CCB's chained SAES GCM step. The family-specific reset register name is abstracted (`WC_STM32_CCB_RSTR`).
196+
- CCB requires the U3 at its full clock; the reference clock-tree bring-up (96 MHz) is in the bare example's `boards/u3/hw_init.c`.
197+
198+
147199
## STM32 BARE-metal port
148200

149201
`WOLFSSL_STM32_BARE` selects a direct-register integration with zero HAL or StdPeriLib dependency. Use this for:

0 commit comments

Comments
 (0)