Skip to content

Commit 3ba188e

Browse files
committed
Add STM32 CCB and STM32C5 HW PKA ECDSA support
1 parent ba4fd0b commit 3ba188e

6 files changed

Lines changed: 1511 additions & 20 deletions

File tree

.wolfssl_known_macro_extras

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,13 +520,15 @@ QAT_ENABLE_RNG
520520
QAT_USE_POLLING_CHECK
521521
RCC_AHB1ENR_PKAEN
522522
RCC_AHB2ENR1_AESEN
523+
RCC_AHB2ENR1_CCBEN
523524
RCC_AHB2ENR1_HASHEN
524525
RCC_AHB2ENR1_PKAEN
525526
RCC_AHB2ENR1_SAESEN
526527
RCC_AHB2ENR_AESEN
527528
RCC_AHB2ENR_HASHEN
528529
RCC_AHB2ENR_PKAEN
529530
RCC_AHB2ENR_SAESEN
531+
RCC_AHB2RSTR1_CCBRST
530532
RCC_AHB2RSTR_PKARST
531533
RCC_AHB3ENR_AESEN
532534
RCC_AHB3ENR_CRYPEN
@@ -985,6 +987,7 @@ WOLFSSL_STM32C5
985987
WOLFSSL_STM32F3
986988
WOLFSSL_STM32F427_RNG
987989
WOLFSSL_STM32U0
990+
WOLFSSL_STM32_CCB
988991
WOLFSSL_STM32_DHUK_UNWRAP
989992
WOLFSSL_STM32_USE_SAES
990993
WOLFSSL_STRONGEST_HASH_SIG

wolfcrypt/src/ecc.c

Lines changed: 168 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,9 @@ ECC Curve Sizes:
246246
#include <wolfssl/wolfcrypt/port/nxp/ksdk_port.h>
247247
#endif
248248

249-
#if defined(WOLFSSL_STM32_PKA)
249+
#if defined(WOLFSSL_STM32_PKA) || defined(WOLFSSL_STM32_CCB)
250+
/* CCB without PKA still needs stm32.h so its consistency #errors (e.g. CCB
251+
* requires DHUK + BARE/CUBEMX) are visible to this translation unit. */
250252
#include <wolfssl/wolfcrypt/port/st/stm32.h>
251253
#endif
252254

@@ -280,8 +282,15 @@ ECC Curve Sizes:
280282
!defined(WOLFSSL_CRYPTOCELL) && !defined(WOLFSSL_SILABS_SE_ACCEL) && \
281283
!defined(WOLFSSL_KCAPI_ECC) && !defined(WOLFSSL_SE050) && \
282284
!defined(WOLFSSL_XILINX_CRYPT_VERSAL) && \
283-
!defined(WOLFSSL_STM32_PKA) && \
285+
(!defined(WOLFSSL_STM32_PKA) || defined(WC_STM32_PKA_SIGN_ONLY)) && \
284286
!defined(WOLFSSL_PSOC6_CRYPTO)
287+
/* STM32 sign-only (e.g. C5): the HW PKA cannot run the integrated ECDSA
288+
* verify (mode 0x26) correctly, so use the SW verify helper rather than the
289+
* HW-accelerator sigRS path. Note the helper's scalar multiplications still
290+
* run on the HW PKA generic-mul (stm32.c provides wc_ecc_mulmod_ex under
291+
* !WC_STM32_PKA_VERIFY_ONLY) -- the C5 issue is specifically the verify-mode
292+
* wrapper, not the point math, which is exercised and correct (the lead for
293+
* a future real HW verify). */
285294
#undef HAVE_ECC_VERIFY_HELPER
286295
#define HAVE_ECC_VERIFY_HELPER
287296
#endif
@@ -8099,6 +8108,158 @@ int wc_ecc_sign_set_k(const byte* k, word32 klen, ecc_key* key)
80998108
#endif /* WOLFSSL_ECDSA_SET_K || WOLFSSL_ECDSA_SET_K_ONE_LOOP */
81008109
#endif /* WOLFSSL_ATECC508A && WOLFSSL_CRYPTOCELL */
81018110

8111+
/* Guard must match the ecc.h prototype and the ccb_ / dhuk_ ecc_key struct
8112+
* members (both WOLFSSL_DHUK && WOLFSSL_STM32_CCB) -- the implementation must
8113+
* not be broader than the members it dereferences. */
8114+
#if defined(WOLFSSL_DHUK) && defined(WOLFSSL_STM32_CCB)
8115+
/* Load a previously provisioned device-protected ECDSA blob (wrapped scalar +
8116+
* AES-GCM iv/tag) and its public key onto the ecc_key. Sets the curve so the
8117+
* sign path can derive the parameters, and marks the key for the device
8118+
* crypto-callback. The caller enables the device with
8119+
* wc_ecc_init_ex(&key, heap, WC_DHUK_DEVID). Generic name -- the wrapping is
8120+
* the STM32 CCB, but the surface is not CCB-specific. */
8121+
int wc_ecc_import_wrapped_private_ex(ecc_key* key, int curve_id,
8122+
const byte* wrapped, word32 wrappedLen,
8123+
const byte* iv, word32 ivLen,
8124+
const byte* tag, word32 tagLen,
8125+
const byte* pub, word32 pubLen)
8126+
{
8127+
int modSz;
8128+
int ret;
8129+
8130+
if (key == NULL || wrapped == NULL || iv == NULL || tag == NULL ||
8131+
pub == NULL) {
8132+
return BAD_FUNC_ARG;
8133+
}
8134+
/* Fixed 16-byte AES-GCM iv/tag -- validate explicitly (no over-read). */
8135+
if (ivLen != sizeof(key->ccb_iv) || tagLen != sizeof(key->ccb_tag)) {
8136+
return BAD_FUNC_ARG;
8137+
}
8138+
modSz = wc_ecc_get_curve_size_from_id(curve_id);
8139+
if (modSz <= 0) {
8140+
return BAD_FUNC_ARG;
8141+
}
8142+
if (wrappedLen == 0u || wrappedLen > sizeof(key->dhuk_wrapped_priv)) {
8143+
return BAD_FUNC_ARG;
8144+
}
8145+
if (pubLen != (word32)(2 * modSz)) {
8146+
return BAD_FUNC_ARG;
8147+
}
8148+
/* Import public key (qx||qy) + set the curve (key->dp). */
8149+
ret = wc_ecc_import_unsigned(key, pub, pub + modSz, NULL, curve_id);
8150+
if (ret != 0) {
8151+
return ret;
8152+
}
8153+
XMEMCPY(key->dhuk_wrapped_priv, wrapped, wrappedLen);
8154+
key->dhuk_wrapped_priv_len = wrappedLen;
8155+
XMEMCPY(key->ccb_iv, iv, sizeof(key->ccb_iv));
8156+
XMEMCPY(key->ccb_tag, tag, sizeof(key->ccb_tag));
8157+
key->dhuk_is_ccb = 1;
8158+
return 0;
8159+
}
8160+
8161+
/* Crypto-callback keygen handler (WC_PK_TYPE_EC_KEYGEN). Provisions a fresh
8162+
* device-protected key: generate a scalar, have the CCB wrap it into a
8163+
* device-bound blob and derive its public key, and store both on the ecc_key
8164+
* (the scalar is zeroized and never leaves this call / the hardware). The
8165+
* scalar is generated in software with the supplied rng on a throwaway key with
8166+
* no devId (so no callback recursion). Returns CRYPTOCB_UNAVAILABLE for curves
8167+
* the CCB cannot wrap so keygen falls back to software. Not a public entry
8168+
* point -- reached via wc_ecc_make_key() on a WC_DHUK_DEVID key. */
8169+
int wc_ecc_dev_make_key(WC_RNG* rng, int keysize, ecc_key* key, int curve_id)
8170+
{
8171+
#ifdef WOLFSSL_SMALL_STACK
8172+
ecc_key* tmp = NULL; /* ecc_key is ~1-2KB -- heap it on the
8173+
* small-stack embedded targets this port
8174+
* runs on (repo convention). */
8175+
#else
8176+
ecc_key tmp[1];
8177+
#endif
8178+
#ifdef WOLFSSL_SMALL_STACK
8179+
/* d || pub || wrapped in one heap scratch buffer (~326 B) -- keep the
8180+
* fixed byte buffers off the stack too, like tmp above. */
8181+
byte* scratch = NULL;
8182+
byte* d;
8183+
byte* pub; /* qx || qy, contiguous */
8184+
byte* wrapped;
8185+
#else
8186+
byte d[MAX_ECC_BYTES];
8187+
byte pub[2 * MAX_ECC_BYTES]; /* qx || qy, contiguous */
8188+
byte wrapped[96];
8189+
#endif
8190+
byte iv[16];
8191+
byte tag[16];
8192+
word32 dLen;
8193+
word32 wrappedSz = 0;
8194+
int modSz;
8195+
int ret;
8196+
int tmpInit = 0;
8197+
8198+
(void)keysize;
8199+
if (rng == NULL || key == NULL) {
8200+
return BAD_FUNC_ARG;
8201+
}
8202+
/* wc_ecc_set_curve() ran before the callback, so key->dp is resolved even
8203+
* when curve_id came in as the default. */
8204+
if (curve_id <= 0 && key->dp != NULL) {
8205+
curve_id = key->dp->id;
8206+
}
8207+
modSz = wc_ecc_get_curve_size_from_id(curve_id);
8208+
if (modSz <= 0 || (word32)modSz > MAX_ECC_BYTES) {
8209+
return CRYPTOCB_UNAVAILABLE; /* unsupported -> software keygen */
8210+
}
8211+
8212+
#ifdef WOLFSSL_SMALL_STACK
8213+
tmp = (ecc_key*)XMALLOC(sizeof(ecc_key), key->heap, DYNAMIC_TYPE_ECC);
8214+
if (tmp == NULL) {
8215+
return MEMORY_E;
8216+
}
8217+
scratch = (byte*)XMALLOC((3 * MAX_ECC_BYTES) + 96, key->heap,
8218+
DYNAMIC_TYPE_TMP_BUFFER);
8219+
if (scratch == NULL) {
8220+
XFREE(tmp, key->heap, DYNAMIC_TYPE_ECC);
8221+
return MEMORY_E;
8222+
}
8223+
d = scratch;
8224+
pub = scratch + MAX_ECC_BYTES;
8225+
wrapped = scratch + (3 * MAX_ECC_BYTES);
8226+
#endif
8227+
8228+
ret = wc_ecc_init_ex(tmp, key->heap, INVALID_DEVID);
8229+
if (ret == 0) {
8230+
tmpInit = 1;
8231+
ret = wc_ecc_make_key_ex(rng, modSz, tmp, curve_id);
8232+
}
8233+
if (ret == 0) {
8234+
dLen = (word32)modSz;
8235+
ret = wc_ecc_export_private_only(tmp, d, &dLen);
8236+
}
8237+
if (ret == 0) {
8238+
ret = wc_Stm32_Ccb_EccMakeBlob(curve_id, d, dLen, iv, tag, wrapped,
8239+
&wrappedSz, pub, pub + modSz);
8240+
if (ret != 0) {
8241+
ret = CRYPTOCB_UNAVAILABLE; /* curve not CCB-wrappable -> SW */
8242+
}
8243+
}
8244+
if (ret == 0) {
8245+
ret = wc_ecc_import_wrapped_private_ex(key, curve_id, wrapped, wrappedSz,
8246+
iv, (word32)sizeof(iv), tag,
8247+
(word32)sizeof(tag), pub,
8248+
(word32)(2 * modSz));
8249+
}
8250+
8251+
ForceZero(d, MAX_ECC_BYTES);
8252+
if (tmpInit) {
8253+
wc_ecc_free(tmp);
8254+
}
8255+
#ifdef WOLFSSL_SMALL_STACK
8256+
XFREE(scratch, key->heap, DYNAMIC_TYPE_TMP_BUFFER);
8257+
XFREE(tmp, key->heap, DYNAMIC_TYPE_ECC);
8258+
#endif
8259+
return ret;
8260+
}
8261+
#endif /* WOLFSSL_DHUK && WOLFSSL_STM32_CCB */
8262+
81028263
#endif /* !HAVE_ECC_SIGN */
81038264

81048265
#ifdef WOLFSSL_CUSTOM_CURVES
@@ -8945,7 +9106,7 @@ int wc_ecc_verify_hash(const byte* sig, word32 siglen, const byte* hash,
89459106

89469107
#ifndef WOLF_CRYPTO_CB_ONLY_ECC
89479108

8948-
#if !defined(WOLFSSL_STM32_PKA) && \
9109+
#if (!defined(WOLFSSL_STM32_PKA) || defined(WC_STM32_PKA_SIGN_ONLY)) && \
89499110
!defined(WOLFSSL_PSOC6_CRYPTO) && \
89509111
!defined(WOLF_CRYPTO_CB_ONLY_ECC)
89519112
static int wc_ecc_check_r_s_range(ecc_key* key, mp_int* r, mp_int* s)
@@ -9463,9 +9624,11 @@ static int ecc_verify_hash(mp_int *r, mp_int *s, const byte* hash,
94639624
int wc_ecc_verify_hash_ex(mp_int *r, mp_int *s, const byte* hash,
94649625
word32 hashlen, int* res, ecc_key* key)
94659626
{
9466-
#if defined(WOLFSSL_STM32_PKA)
9627+
#if defined(WOLFSSL_STM32_PKA) && !defined(WC_STM32_PKA_SIGN_ONLY)
94679628
/* HW ECDSA verify via STM32 PKA. Works under both the CubeMX-HAL
9468-
* and the bare-metal direct-register paths. Mirror the non-FIPS
9629+
* and the bare-metal direct-register paths. (Under WC_STM32_PKA_SIGN_ONLY
9630+
* -- e.g. STM32C5 -- verify falls through to the software body below.)
9631+
* Mirror the non-FIPS
94699632
* input-validation from the SW body below (length range, all-zero
94709633
* digest rejection) so HW + SW share the same input contract. */
94719634
#ifndef WC_ALLOW_ECC_ZERO_HASH

wolfcrypt/src/port/st/README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Support for STM32 on-chip crypto hardware acceleration across the following fami
2222
| `WOLFSSL_STM32WBA` | WBA52 (TinyAES / HASH / RNG / SAES / V2 PKA / DHUK) |
2323
| `WOLFSSL_STM32WL` | WL55 (TinyAES / RNG / V1 PKA) |
2424
| `WOLFSSL_STM32C0` | C0xx (not yet supported in settings.h; SW only) |
25-
| `WOLFSSL_STM32C5` | C5xx (TinyAES / HASH / RNG / SAES / V2 PKA / DHUK) |
25+
| `WOLFSSL_STM32C5` | C5xx (TinyAES / HASH / RNG / SAES / V2 PKA sign-only / DHUK / CCB) |
2626
| `WOLFSSL_STM32N6` | N6xx (TinyAES / HASH / RNG / SAES / V2 PKA / DHUK; M55 core) |
2727
| `WOLFSSL_STM32MP13` | MP13 (CRYP / HASH / RNG / PKA; Cortex-A7) |
2828
| `WOLFSSL_STM32MP25` | MP25 (not yet supported in settings.h; Cortex-A35 + M33) |
@@ -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)