From 46d016e10011272f3eb16169ff2a6a6c1aee2a84 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 6 Nov 2026 14:57:58 -0800 Subject: [PATCH] Accept ECB-wrapped ECC scalars in the RTL8735B HUK sign path --- wolfcrypt/src/port/realtek/README.md | 16 ++ wolfcrypt/src/port/realtek/rtl8735b.c | 174 ++++++++++++++++++---- wolfssl/wolfcrypt/port/realtek/rtl8735b.h | 33 ++-- 3 files changed, 183 insertions(+), 40 deletions(-) diff --git a/wolfcrypt/src/port/realtek/README.md b/wolfcrypt/src/port/realtek/README.md index 5cfbf0e831d..eb3e19eb7d5 100644 --- a/wolfcrypt/src/port/realtek/README.md +++ b/wolfcrypt/src/port/realtek/README.md @@ -133,6 +133,22 @@ scalar is briefly in software during the sign; an OTP-resident model through the HW ECDSA engine (`hal_ecdsa`) are follow-ons. (The GCM unwrap makes the ECDSA sign path require `HAVE_AESGCM`.) +The scalar can instead be AES-ECB-wrapped, the same blob layout as the STM32 +DHUK `wc_ecc_import_wrapped_private()` uses, so one wrap format serves both +parts. +Leave `iv`, `ivSz`, `tag` and `tagSz` all NULL/0, and pass the ECB ciphertext of +the scalar zero-padded to a multiple of 16 (`wrappedLen` up to +`WC_RTL8735B_MAX_WRAPPED`, the scalar in the first `plainLen` bytes): + +```c +wc_Rtl8735b_EccKey hk = { seed, 32, wrapped, 32, 32 }; /* ECB, P-256 */ +``` + +ECB has no integrity check: a damaged or wrong-device blob signs with a wrong +scalar and the signature fails to verify, so prefer GCM where only RTL8735B is +involved. A partial `iv`/`tag` set is rejected with `BAD_FUNC_ARG`. The ECB +format needs `HAVE_AES_ECB` or `WOLFSSL_AES_DIRECT`. + ### Additional HUK operations HMAC-SHA256 under the HUK (the 32-byte key is the HKDF seed; the MAC runs over diff --git a/wolfcrypt/src/port/realtek/rtl8735b.c b/wolfcrypt/src/port/realtek/rtl8735b.c index a991e26d664..756fa50304a 100644 --- a/wolfcrypt/src/port/realtek/rtl8735b.c +++ b/wolfcrypt/src/port/realtek/rtl8735b.c @@ -881,8 +881,9 @@ static int Rtl8735bHuk_Gcm(int enc, const byte* seed, const byte* in, /* AES-ECB under a HUK-derived slot key. sz must be a multiple of the block. * Unaligned caller in/out are bounced through 32-byte-aligned temporaries (the * HAL DMAs its buffers on cache-line boundaries), so callers need not align. - * Guarded to match its only call site (the WC_CIPHER_AES_ECB dispatch case); - * CBC/CTR drive hal_crypto_aes_ecb_* directly, not this helper. */ + * Guarded to match its call sites (the WC_CIPHER_AES_ECB dispatch case and the + * ECB-wrapped ECC scalar unwrap); CBC/CTR drive hal_crypto_aes_ecb_* directly, + * not this helper. */ static int Rtl8735bHuk_Ecb(int enc, const byte* seed, const byte* in, word32 sz, byte* out) { @@ -1497,9 +1498,9 @@ static int Rtl8735bHuk_HmacCb(struct wc_CryptoInfo* info) #endif /* !NO_HMAC && !NO_SHA256 */ #if defined(HAVE_ECC) && defined(HAVE_ECC_SIGN) -/* The HUK-wrapped ECDSA scalar is AES-GCM decrypt-verified at unwrap - * (Rtl8735bHuk_UnwrapScalar reuses Rtl8735bHuk_Gcm), so the ECDSA sign path - * requires AES-GCM. */ +/* The default HUK-wrapped ECDSA scalar format is AES-GCM decrypt-verified at + * unwrap (Rtl8735bHuk_UnwrapScalar reuses Rtl8735bHuk_Gcm), so the ECDSA sign + * path requires AES-GCM. */ #ifndef HAVE_AESGCM #error "RTL8735B HUK ECDSA needs HAVE_AESGCM (GCM-authenticated scalar unwrap)" #endif @@ -1904,22 +1905,92 @@ static int Rtl8735bHuk_EccVerifyHw(struct wc_CryptoInfo* info) } #endif /* HAVE_ECC_VERIFY */ -/* Unwrap the HUK-wrapped private scalar (hk->plainLen bytes). AES-GCM decrypt- - * verify under the HUK-derived key (reusing the GCM path): a tampered/wrong blob - * fails here with AES_GCM_AUTH_E, not silently as a garbage scalar downstream. */ -static int Rtl8735bHuk_UnwrapScalar(const wc_Rtl8735b_EccKey* hk, - byte* scalar, word32 scalarSz) +/* Validate the wrapped-scalar fields of a HUK ECC key. iv, ivSz, tag and tagSz + * all set selects AES-GCM (wrappedLen == plainLen); all four NULL/0 selects + * AES-ECB, the STM32 DHUK blob layout (wrappedLen a multiple of 16, scalar in + * the first plainLen bytes). A partial set is rejected rather than guessed. */ +static int Rtl8735bHuk_EccKeyCheck(const wc_Rtl8735b_EccKey* hk, int* isEcb) { - (void)scalarSz; /* out length is hk->plainLen (GCM: ciphertext == plaintext) */ + int gcm; + int ecb; + + if (hk->seed == NULL || hk->seedSz != WC_RTL8735B_KEYLEN || + hk->wrapped == NULL) { + return CRYPTOCB_UNAVAILABLE; + } + gcm = (hk->iv != NULL && hk->ivSz != 0 && + hk->tag != NULL && hk->tagSz != 0); + ecb = (hk->iv == NULL && hk->ivSz == 0 && + hk->tag == NULL && hk->tagSz == 0); + if (!gcm && !ecb) { + return BAD_FUNC_ARG; + } + if (hk->plainLen == 0 || + hk->plainLen > WC_RTL8735B_MAX_WRAPPED || + hk->plainLen > (word32)MAX_ECC_BYTES) { + return BAD_FUNC_ARG; + } + if (gcm) { + if (hk->wrappedLen != hk->plainLen) { /* ciphertext == plaintext */ + return BAD_FUNC_ARG; + } + } + else if (hk->wrappedLen < hk->plainLen || + hk->wrappedLen > WC_RTL8735B_MAX_WRAPPED || + (hk->wrappedLen % WC_AES_BLOCK_SIZE) != 0) { + return BAD_FUNC_ARG; + } + *isEcb = ecb; + return 0; +} + +/* Unwrap the HUK-wrapped private scalar (hk->plainLen bytes) under the HUK- + * derived key. GCM is decrypt-verified: a tampered/wrong blob fails here with + * AES_GCM_AUTH_E. ECB has no integrity check: a wrong blob yields a wrong + * scalar and the signature fails to verify. */ +static int Rtl8735bHuk_UnwrapScalar(const wc_Rtl8735b_EccKey* hk, int isEcb, + byte* scalar) +{ + if (isEcb) { +#if defined(HAVE_AES_ECB) || defined(WOLFSSL_AES_DIRECT) || \ + defined(WOLF_CRYPTO_CB_ONLY_AES) + int ret; + #ifdef WOLFSSL_SMALL_STACK + byte* buf; + + buf = (byte*)XMALLOC(WC_RTL8735B_MAX_WRAPPED, NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (buf == NULL) { + return MEMORY_E; + } + #else + byte buf[WC_RTL8735B_MAX_WRAPPED]; + #endif + + ret = Rtl8735bHuk_Ecb(0, hk->seed, hk->wrapped, hk->wrappedLen, buf); + if (ret == 0) { + XMEMCPY(scalar, buf, hk->plainLen); + } + ForceZero(buf, WC_RTL8735B_MAX_WRAPPED); + #ifdef WOLFSSL_SMALL_STACK + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + #endif + return ret; +#else + /* Not CRYPTOCB_UNAVAILABLE: that would retry a software sign on the + * keyless HUK device key. */ + return ALGO_ID_E; +#endif + } return Rtl8735bHuk_Gcm(0, hk->seed, hk->wrapped, hk->plainLen, scalar, hk->iv, hk->ivSz, NULL, 0, (byte*)hk->tag, hk->tagSz); } /* Route an ECDSA sign request to the HUK backend (wc_Rtl8735b_EccKey via - * key->devCtx). Modes: 1 OTP-resident (scalar stays in OTP); 2 GCM-unwrap the - * wrapped scalar then HW sign; 3 GCM-unwrap then software sign. The unwrap is - * AES-GCM-authenticated (needs HAVE_AESGCM). A plain ecc_key with no devCtx is a - * general HW offload. The wrapped blob unwraps only on its origin HUK. */ + * key->devCtx). Modes: 1 OTP-resident (scalar stays in OTP); 2 unwrap the + * wrapped scalar (GCM or ECB) then HW sign; 3 unwrap then software sign. A + * plain ecc_key with no devCtx is a general HW offload. The wrapped blob + * unwraps only on its origin HUK. */ static int Rtl8735bHuk_PkSign(struct wc_CryptoInfo* info) { ecc_key* key = info->pk.eccsign.key; @@ -1927,6 +1998,7 @@ static int Rtl8735bHuk_PkSign(struct wc_CryptoInfo* info) ecc_key* tmp = NULL; int ret; int curveId; + int isEcb = 0; word32 scalarSz; #ifdef WOLFSSL_SMALL_STACK byte* scalar = NULL; @@ -1972,17 +2044,9 @@ static int Rtl8735bHuk_PkSign(struct wc_CryptoInfo* info) goto cleanup; } - /* Modes 2/3 need the seed + GCM-wrapped scalar (ciphertext + iv + tag). */ - if (hk->seed == NULL || hk->seedSz != WC_RTL8735B_KEYLEN || - hk->wrapped == NULL || hk->iv == NULL || hk->tag == NULL) { - ret = CRYPTOCB_UNAVAILABLE; - goto cleanup; - } - if (hk->plainLen == 0 || - hk->wrappedLen != hk->plainLen || /* GCM: ciphertext == plaintext */ - hk->plainLen > WC_RTL8735B_MAX_WRAPPED || - hk->plainLen > (word32)MAX_ECC_BYTES) { - ret = BAD_FUNC_ARG; + /* Modes 2/3 need the seed + wrapped scalar (GCM: plus iv + tag). */ + ret = Rtl8735bHuk_EccKeyCheck(hk, &isEcb); + if (ret != 0) { goto cleanup; } /* The caller must set the curve (wc_ecc_set_curve): a wrapped 32-byte scalar @@ -1995,7 +2059,7 @@ static int Rtl8735bHuk_PkSign(struct wc_CryptoInfo* info) curveId = key->dp->id; scalarSz = hk->plainLen; - ret = Rtl8735bHuk_UnwrapScalar(hk, scalar, scalarSz); + ret = Rtl8735bHuk_UnwrapScalar(hk, isEcb, scalar); if (ret != 0) { goto cleanup; } @@ -2198,8 +2262,9 @@ int wc_Rtl8735b_HukUnRegister(int devId) #ifdef WOLFSSL_RTL8735B_HOST_TEST /* Host self-test for the silicon-independent helpers (no HAL crypto needed): - * BE<->LE word conversion, the AES-CTR counter increment, the HMAC accumulator - * growth/overflow/cap logic, and the bounce-buffer alignment helpers. The + * BE<->LE word conversion, the wrapped-scalar format checks, the AES-CTR + * counter increment, the HMAC accumulator growth/overflow/cap logic, and the + * bounce-buffer alignment helpers. The * --enable-rtl8735b build is a compile + these-KATs gate; the cipher / GCM / * ECDSA crypto correctness is validated on RTL8735B hardware, not here. Returns * 0 on success, or a negative code identifying the first failing check. */ @@ -2214,6 +2279,9 @@ int wc_Rtl8735b_HukSelfTest(void) }; word32 w[WC_RTL8735B_ECC_WORDS]; byte be2[WC_RTL8735B_ECC_BYTES]; + byte blob[WC_RTL8735B_MAX_WRAPPED]; + wc_Rtl8735b_EccKey hk; + int isEcb; #endif #ifdef WOLFSSL_AES_COUNTER byte ctr[WC_AES_BLOCK_SIZE]; @@ -2243,6 +2311,54 @@ int wc_Rtl8735b_HukSelfTest(void) if (XMEMCMP(kBe, be2, sizeof(kBe)) != 0) { return -2; } + + /* Wrapped-scalar format selection: iv/tag all set = GCM, all unset = ECB + * (block-padded), anything in between is rejected. */ + XMEMSET(blob, 0, sizeof(blob)); + XMEMSET(&hk, 0, sizeof(hk)); + hk.seed = kBe; hk.seedSz = WC_RTL8735B_KEYLEN; + hk.wrapped = blob; hk.wrappedLen = 32; hk.plainLen = 32; + isEcb = 0; + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != 0 || isEcb != 1) { + return -30; + } + hk.wrappedLen = 48; /* padded ECB blob */ + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != 0) { + return -31; + } + hk.wrappedLen = 40; /* not a block multiple */ + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != BAD_FUNC_ARG) { + return -32; + } + hk.wrappedLen = 16; /* shorter than the scalar */ + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != BAD_FUNC_ARG) { + return -33; + } + hk.wrappedLen = WC_RTL8735B_MAX_WRAPPED + WC_AES_BLOCK_SIZE; + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != BAD_FUNC_ARG) { + return -34; + } + hk.wrappedLen = 32; + hk.iv = blob; hk.ivSz = 12; /* iv without a tag */ + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != BAD_FUNC_ARG) { + return -35; + } + hk.iv = NULL; hk.tagSz = 16; /* a size without a pointer */ + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != BAD_FUNC_ARG) { + return -36; + } + hk.iv = blob; hk.tag = blob; /* full GCM set */ + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != 0 || isEcb != 0) { + return -37; + } + hk.wrappedLen = 48; /* GCM blob is not padded */ + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != BAD_FUNC_ARG) { + return -38; + } + hk.seed = NULL; + if (Rtl8735bHuk_EccKeyCheck(&hk, &isEcb) != CRYPTOCB_UNAVAILABLE) { + return -39; + } #endif #ifdef WOLFSSL_AES_COUNTER diff --git a/wolfssl/wolfcrypt/port/realtek/rtl8735b.h b/wolfssl/wolfcrypt/port/realtek/rtl8735b.h index c2bc14d752d..5192948d362 100644 --- a/wolfssl/wolfcrypt/port/realtek/rtl8735b.h +++ b/wolfssl/wolfcrypt/port/realtek/rtl8735b.h @@ -154,15 +154,25 @@ * wc_ecc_sign_hash(...); (unwraps + signs under the HUK) * * The pointed-at buffers must stay valid for the key's lifetime (borrowed, not - * copied). seed is the 256-bit HKDF input. The private scalar is AUTHENTICATED- - * wrapped with AES-GCM under the HUK-derived key: wrapped is the ciphertext - * (wrappedLen == plainLen, no block padding, <= WC_RTL8735B_MAX_WRAPPED), iv the - * 12-byte nonce, tag the 16-byte auth tag; plainLen is the scalar size (32 for - * P-256). GCM means a tampered, corrupted, or wrong-device blob fails at unwrap - * (AES_GCM_AUTH_E) instead of silently yielding a garbage scalar. Requires - * HAVE_AESGCM. + * copied). seed is the 256-bit HKDF input; plainLen is the scalar size (32 for + * P-256). The private scalar is wrapped under the HUK-derived key in one of two + * formats, selected by the iv/tag fields: * - * By default the sign runs in software after the HUK GCM-unwrap. Set useHwEngine + * AES-GCM (iv, ivSz, tag, tagSz all set; recommended): wrapped is the + * ciphertext (wrappedLen == plainLen, no block padding, + * <= WC_RTL8735B_MAX_WRAPPED), iv the 12-byte nonce, tag the 16-byte auth + * tag. A tampered, corrupted, or wrong-device blob fails at unwrap + * (AES_GCM_AUTH_E) instead of yielding a garbage scalar. + * AES-ECB (iv, ivSz, tag, tagSz all NULL/0): wrapped is the ECB ciphertext of + * the scalar zero-padded to a multiple of 16 (plainLen <= wrappedLen <= + * WC_RTL8735B_MAX_WRAPPED), the same layout as the STM32 DHUK blob for + * wc_ecc_import_wrapped_private(), so one wrap format serves both. ECB has + * no integrity check: a wrong blob signs with a wrong scalar and the + * signature fails to verify. Needs HAVE_AES_ECB or WOLFSSL_AES_DIRECT. + * + * A partial iv/tag set is rejected with BAD_FUNC_ARG. HAVE_AESGCM is required. + * + * By default the sign runs in software after the HUK unwrap. Set useHwEngine * to route the sign through the HW ECDSA engine (hal_ecdsa, P-256 only). With * otpPrkSel != 0 the private scalar is sourced from OTP via hal_ecdsa_select_prk * and never enters software -- seed/wrapped/iv/tag/plainLen are then unused and @@ -171,7 +181,7 @@ typedef struct wc_Rtl8735b_EccKey { const byte* seed; word32 seedSz; - const byte* wrapped; /* AES-GCM ciphertext of the scalar (== plainLen) */ + const byte* wrapped; /* GCM or ECB ciphertext of the scalar */ word32 wrappedLen; word32 plainLen; /* --- HW ECDSA engine extensions (appended; zero keeps legacy behavior) --- */ @@ -179,7 +189,7 @@ typedef struct wc_Rtl8735b_EccKey { byte otpPrkSel; /* HW private-key source: 0 = unwrapped INPUT scalar, * 1 = ECDSA_OTP_PRK_1, 2 = ECDSA_OTP_PRK_2 (scalar * never in software; seed/wrapped may be NULL) */ - /* --- AES-GCM wrap parameters (authenticated unwrap) --- */ + /* --- AES-GCM wrap parameters (all NULL/0 selects the ECB format) --- */ const byte* iv; /* 12-byte GCM nonce for the wrapped scalar */ word32 ivSz; const byte* tag; /* 16-byte GCM auth tag */ @@ -213,7 +223,8 @@ WOLFSSL_API int wc_Rtl8735b_AesUnRegister(int devId); #if defined(WOLFSSL_RTL8735B_HUK) && defined(WOLFSSL_RTL8735B_HOST_TEST) /* Host-only KAT of the port's silicon-independent helpers (BE/LE word conversion, - * CTR counter increment, HMAC accumulator growth/overflow/cap, bounce alignment). + * wrapped-scalar format checks, CTR counter increment, HMAC accumulator + * growth/overflow/cap, bounce alignment). * A build/CI aid for the --enable-rtl8735b compile-test -- NOT shipped production * API. Returns 0 on success, negative on the first failing check. */ WOLFSSL_API int wc_Rtl8735b_HukSelfTest(void);