ecc: take the curve id in wc_ecc_import_wrapped_private so an imported DHUK key is ready to sign

pull/11497/head
David Garske 2026-09-17 18:37:25 -07:00
parent 31b9da2c8d
commit 1aa693da3b
4 changed files with 51 additions and 17 deletions

View File

@ -1326,31 +1326,35 @@ int wc_ecc_import_private_key(const byte* priv, word32 privSz, const byte* pub,
a chip-bound wrapped blob together with the 256-bit derivation seed; the
plaintext scalar is never imported. The key must be bound to the STM32 DHUK
crypto-callback device (init with wc_ecc_init_ex(&key, heap, WC_DHUK_DEVID)
after registering the device with wc_Stm32_DhukRegister). Available only on
after registering the device with wc_Stm32_DhukRegister). The curve is set
from curve_id, so the key is ready to sign on return. Available only on
STM32 builds with WOLFSSL_DHUK and a DHUK-capable SAES (WC_STM32_HAS_DHUK).
\return 0 Returned on success.
\return BAD_FUNC_ARG Returned if key, seed, or wrapped is NULL; if seedSz is
not 32; if wrappedLen is zero or not a multiple of the AES block size; if
wrappedLen exceeds the on-key blob buffer; if plainLen is zero or larger
than wrappedLen; or if wrappedLen is larger than plainLen padded to a full
AES block.
than wrappedLen; if wrappedLen is larger than plainLen padded to a full
AES block; or if plainLen does not match the scalar size of curve_id.
\return <0 An error from the curve lookup if curve_id is not supported.
\param key pointer to the ecc_key (bound to WC_DHUK_DEVID) to import into.
\param curve_id curve the scalar belongs to, e.g. ECC_SECP256R1.
\param seed pointer to the 256-bit (32-byte) per-key DHUK derivation seed.
\param seedSz length of seed in bytes; must be 32.
\param wrapped pointer to the DHUK-wrapped private scalar blob.
\param wrappedLen length of the wrapped blob; a non-zero multiple of the AES
block size, no larger than the on-key buffer.
\param plainLen length in bytes of the plaintext scalar inside the blob.
\param plainLen length in bytes of the plaintext scalar inside the blob;
must equal the scalar size of curve_id (32 for P-256, 48 for P-384).
_Example_
\code
ecc_key key;
wc_Stm32_DhukRegister(WC_DHUK_DEVID);
wc_ecc_init_ex(&key, NULL, WC_DHUK_DEVID);
if (wc_ecc_import_wrapped_private(&key, seed, 32, wrapped, wrappedLen,
plainLen) == 0) {
if (wc_ecc_import_wrapped_private(&key, ECC_SECP256R1, seed, 32, wrapped,
wrappedLen, plainLen) == 0) {
wc_ecc_sign_hash(hash, hashLen, sig, &sigLen, &rng, &key);
}
wc_ecc_free(&key);
@ -1360,7 +1364,8 @@ int wc_ecc_import_private_key(const byte* priv, word32 privSz, const byte* pub,
\sa wc_ecc_sign_hash
\sa wc_ecc_init_ex
*/
int wc_ecc_import_wrapped_private(ecc_key* key, const byte* seed, word32 seedSz,
int wc_ecc_import_wrapped_private(ecc_key* key, int curve_id,
const byte* seed, word32 seedSz,
const byte* wrapped, word32 wrappedLen,
word32 plainLen);

View File

@ -8307,10 +8307,13 @@ int wc_ecc_sign_set_k(const byte* k, word32 klen, ecc_key* key)
* at sign time it is decrypted into a short-lived buffer. The devId is NOT set
* here -- enable the device by setting devId at init
* (wc_ecc_init_ex(&key, heap, WC_DHUK_DEVID)). See ecc.h for the contract. */
int wc_ecc_import_wrapped_private(ecc_key* key, const byte* seed, word32 seedSz,
int wc_ecc_import_wrapped_private(ecc_key* key, int curve_id,
const byte* seed, word32 seedSz,
const byte* wrapped, word32 wrappedLen,
word32 plainLen)
{
int ret;
if (key == NULL || seed == NULL || wrapped == NULL) {
return BAD_FUNC_ARG;
}
@ -8335,6 +8338,25 @@ int wc_ecc_import_wrapped_private(ecc_key* key, const byte* seed, word32 seedSz,
if (wrappedLen > ((plainLen + 15u) & ~15u)) {
return BAD_FUNC_ARG;
}
/* Validate the scalar size against the curve before touching the key, so a
* rejected import leaves no curve behind on a key that has no blob. */
ret = wc_ecc_get_curve_size_from_id(curve_id);
if (ret < 0) {
return ret;
}
if ((word32)ret != plainLen) {
return BAD_FUNC_ARG;
}
/* The sign path needs the domain parameters to drive the PKA and returns
* ECC_BAD_ARG_E without them, and a caller starting from a bare
* wc_ecc_init() has no other way to supply them for a key that never holds
* its scalar in software. */
ret = wc_ecc_set_curve(key, (int)plainLen, curve_id);
if (ret != 0) {
return ret;
}
XMEMCPY(key->dhuk_wrapped_priv, wrapped, wrappedLen);
XMEMCPY(key->dhuk_seed, seed, seedSz);
key->dhuk_wrapped_priv_len = wrappedLen;

View File

@ -174,7 +174,7 @@ wc_AesFree(&aes);
wc_Stm32_DhukUnRegister(WC_DHUK_DEVID);
```
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.
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, ECC_SECP256R1, seed, seedSz, wrapped, wrappedLen, plainLen)`, then call the normal `wc_ecc_sign_hash()`. The curve argument is required: it is what gives the sign path the domain parameters to drive the PKA, and the key is ready to sign on return. 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.
Worked example: [`STM32_Bare_Test/src/main_dhuk.c`](https://github.com/wolfSSL/wolfssl-examples-stm32/blob/master/STM32_Bare_Test/src/main_dhuk.c) drives `wc_Stm32_DhukRegister` through transparent GMAC, AES-ECB, and ECDSA, and exercises the `wc_ecc_import_wrapped_private` argument validation in its `test_ecc_dhuk_setter()` block.

View File

@ -801,22 +801,29 @@ int wc_ecc_sign_hash_ex(const byte* in, word32 inlen, WC_RNG* rng,
#if defined(WOLFSSL_DHUK) && defined(WC_STM32_HAS_DHUK) && \
(defined(WOLFSSL_STM32_BARE) || defined(WOLFSSL_STM32_CUBEMX))
/* DHUK ECC sign: import a hardware-wrapped ECC private scalar + its derivation
* seed onto the ecc_key for the crypto-callback sign path. The caller MUST also
* populate key->pubkey (via wc_ecc_import_x963) so verify can use the
* in-clear public counterpart, and enable the device by setting devId at init
* (wc_ecc_init_ex(&key, heap, WC_DHUK_DEVID)).
* seed onto the ecc_key for the crypto-callback sign path. Sets the curve, so
* the key is ready to sign on return. Enable the device by setting devId at
* init (wc_ecc_init_ex(&key, heap, WC_DHUK_DEVID)). To verify with this same
* key, also populate key->pubkey (via wc_ecc_import_x963) -- verify uses the
* in-clear public counterpart and does not touch the wrapped scalar.
* curve_id -- curve the scalar belongs to (e.g. ECC_SECP256R1); without
* it the sign path has no parameters to drive the PKA and
* returns ECC_BAD_ARG_E
* seed -- 256-bit derivation seed (mixed with the silicon DHUK to
* derive the key that unwraps the scalar)
* seedSz -- seed length, must be 32
* wrapped -- ECC scalar AES-encrypted with the SAES-derived device key;
* length is a multiple of 16, <= 96
* wrappedLen -- length of the wrapped blob
* plainLen -- actual scalar size (e.g. 32 for P-256)
* plainLen -- actual scalar size, and must match the curve (32 for
* P-256, 48 for P-384)
*
* On success: stores seed + blob + lengths, returns 0 (does NOT set devId).
* On failure: BAD_FUNC_ARG. */
* On success: sets the curve, stores seed + blob + lengths, returns 0 (does
* NOT set devId).
* On failure: BAD_FUNC_ARG, or an error from the curve lookup. */
WOLFSSL_API
int wc_ecc_import_wrapped_private(ecc_key* key, const byte* seed, word32 seedSz,
int wc_ecc_import_wrapped_private(ecc_key* key, int curve_id,
const byte* seed, word32 seedSz,
const byte* wrapped, word32 wrappedLen,
word32 plainLen);
#endif