diff --git a/rtl8735b/README.md b/rtl8735b/README.md index 6a04053d8..266325717 100644 --- a/rtl8735b/README.md +++ b/rtl8735b/README.md @@ -11,7 +11,8 @@ It is built inside the RealTek AmebaPro2 FreeRTOS SDK (which provides the startu - The key is device-bound and deterministic: the same seed yields the same key (so GMAC verifies and AES round-trips), and a different seed yields a different key (GCM decrypt returns `AES_GCM_AUTH_E`). - The port tolerates unaligned caller buffers (it bounces them to satisfy the HAL's 32-byte alignment); the example includes an explicitly-unaligned GCM check. - HMAC-SHA256 under the HUK (`wc_HmacInit` with `devId = WC_HUK_DEVID`, then `wc_HmacSetKey`/`wc_HmacUpdate`/`wc_HmacFinal`): the MAC is deterministic for a given seed and differs for a wrong seed. -- HUK-bound ECDSA P-256 sign: a key is generated in software, its private scalar is wrapped under the HUK (ECB-encrypted with the HUK device), and signing then goes through the HUK device (unwrap + sign); the signature verifies against the software public key. +- HUK-bound ECDSA P-256 sign: a key is generated in software, its private scalar is wrapped under the HUK (AES-GCM-encrypted with the HUK device), and signing then goes through the HUK device (unwrap + sign); the signature verifies against the software public key. +- HUK-wrapped key storage: `huk_blob_create()` and `huk_blob_load()` in `main.c` show how to wrap a P-256 key, keep the seed, IV, tag, wrapped scalar and public key as a stored blob, and sign with a key set up from that blob alone. The blob only unwraps on the device that wrapped it, and a damaged blob fails with `AES_GCM_AUTH_E`. - HW ECDSA P-256 verify offload: the same signature is then verified through the HW engine (the public point is imported into a `WC_HUK_DEVID` key so `wc_ecc_verify_hash` dispatches to `hal_ecdsa`), and a tampered digest is shown to be rejected. No HUK context is needed to verify -- any P-256 public key. - **Plaintext-key AES (second device):** with `WOLFSSL_RTL8735B_AES` also enabled (see `user_settings.h`), the example registers a second crypto-callback device, `wc_Rtl8735b_AesRegister(WC_RTL8735B_AES_DEVID)`, that runs a caller-supplied AES key directly on the HW engine with no HUK binding. It reproduces a published AES-GCM test vector (proving the key is used verbatim), then encrypts the same 32 key bytes through both devices to show the plaintext-key ciphertext differs from the HUK device-bound ciphertext -- the two devices coexist and are selected per `Aes` by `devId`. @@ -78,6 +79,12 @@ Remove the J27 jumper and reset to boot. The console (UART, 115200) prints: [PASS] verify with software public key [PASS] HW ECDSA verify (good sig) [PASS] HW ECDSA verify (tampered -> reject) +== HUK-wrapped ECDSA key: wrap, store, reload, sign == +[PASS] InitRng +[PASS] create wrapped key blob +[PASS] sign with reloaded blob +[PASS] verify with stored public key +[PASS] tampered blob -> AES_GCM_AUTH_E == AES-GCM with a plaintext key (devId=WC_RTL8735B_AES_DEVID) == [PASS] AesInit(devId=WC_RTL8735B_AES_DEVID) [PASS] AesGcmSetKey(plaintext 128-bit key) @@ -94,7 +101,7 @@ Remove the J27 jumper and reset to boot. The console (UART, 115200) prints: [PASS] ciphertext matches FIPS-197 vector [PASS] AesEcb round-trip == AES-GCM plaintext key, UNALIGNED buffers (port bounces) == ... round-trip PASS -== AES-GCM plaintext key, non-12-byte IV must hard-fail == ... 16-byte IV rejected PASS +== AES-GCM plaintext key, non-12-byte IV uses software GCM == ... matches software GCM PASS === done === ``` diff --git a/rtl8735b/main.c b/rtl8735b/main.c index 395cdd19e..d60f41819 100644 --- a/rtl8735b/main.c +++ b/rtl8735b/main.c @@ -11,7 +11,7 @@ * device (WC_RTL8735B_AES_DEVID) that runs a caller-supplied plaintext AES key * directly on the HW engine (no HUK binding), selected per Aes by devId, and * exercises it with AES-GCM (published KAT + coexistence), an AES-ECB FIPS-197 - * KAT, the unaligned-buffer bounce path, and non-12-byte-IV rejection. + * KAT, the unaligned-buffer bounce path, and software GCM for a non-12-byte IV. * * Build: configure with -DEXAMPLE=wolfcrypt_huk (see wolfcrypt_huk.cmake). */ @@ -419,7 +419,7 @@ static void huk_hmac_test(void) } /* HUK-bound ECDSA P-256 sign. A key is generated in software, its private - * scalar wrapped under the HUK (ECB-encrypted with the HUK device using the + * scalar wrapped under the HUK (AES-GCM-encrypted with the HUK device using the * seed), then signing goes through the HUK device (which unwraps under the HUK * and signs). The signature is checked against the software public key. To * exercise the HW ECDSA engine instead of the software-after-unwrap path, set @@ -570,6 +570,158 @@ static void huk_ecdsa_test(void) ForceZero(seed, sizeof(seed)); } +/* A HUK-wrapped P-256 key in the form an application stores it, for example in + * flash. The seed is not secret, and only the device that wrapped the scalar can + * unwrap it. */ +typedef struct huk_key_blob { + byte seed[32]; + byte iv[12]; + byte tag[16]; + byte wrapped[32]; + byte qx[32]; + byte qy[32]; +} huk_key_blob; + +/* Generate a P-256 key and wrap its scalar under the HUK with a fresh seed + IV. */ +static int huk_blob_create(WC_RNG* rng, huk_key_blob* blob) +{ + Aes aes; + ecc_key key; + byte d[32] __attribute__((aligned(32))); + word32 dLen = sizeof(d); + word32 qxLen = sizeof(blob->qx); + word32 qyLen = sizeof(blob->qy); + int ret; + + ret = wc_ecc_init(&key); + if (ret != 0) { + return ret; + } + ret = wc_ecc_make_key(rng, sizeof(d), &key); + if (ret == 0) { + ret = wc_ecc_export_private_only(&key, d, &dLen); + } + if (ret == 0 && dLen != sizeof(d)) { + ret = BUFFER_E; + } + if (ret == 0) { + ret = wc_ecc_export_public_raw(&key, blob->qx, &qxLen, blob->qy, &qyLen); + } + if (ret == 0) { + ret = wc_RNG_GenerateBlock(rng, blob->seed, sizeof(blob->seed)); + } + if (ret == 0) { + ret = wc_RNG_GenerateBlock(rng, blob->iv, sizeof(blob->iv)); + } + if (ret == 0) { + ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(&aes, blob->seed, sizeof(blob->seed)); + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, blob->wrapped, d, sizeof(d), + blob->iv, sizeof(blob->iv), blob->tag, + sizeof(blob->tag), NULL, 0); + } + wc_AesFree(&aes); + } + } + wc_ecc_free(&key); + ForceZero(d, sizeof(d)); + return ret; +} + +/* Set up a signing key from a stored blob. The key borrows hk and the blob, so + * both must stay valid until wc_ecc_free. */ +static int huk_blob_load(const huk_key_blob* blob, wc_Rtl8735b_EccKey* hk, + ecc_key* key) +{ + int ret; + + memset(hk, 0, sizeof(*hk)); + hk->seed = blob->seed; hk->seedSz = sizeof(blob->seed); + hk->wrapped = blob->wrapped; hk->wrappedLen = sizeof(blob->wrapped); + hk->plainLen = sizeof(blob->wrapped); + hk->iv = blob->iv; hk->ivSz = sizeof(blob->iv); + hk->tag = blob->tag; hk->tagSz = sizeof(blob->tag); + + ret = wc_ecc_init_ex(key, NULL, WC_HUK_DEVID); + if (ret == 0) { + ret = wc_ecc_set_curve(key, sizeof(blob->wrapped), ECC_SECP256R1); + } + if (ret == 0) { + key->devCtx = hk; + } + return ret; +} + +/* Sign with a blob, using a key object set up only from what was stored. */ +static int huk_blob_sign(const huk_key_blob* blob, WC_RNG* rng, const byte* hash, + word32 hashSz, byte* sig, word32* sigLen) +{ + wc_Rtl8735b_EccKey hk; + ecc_key key; + int ret; + + ret = huk_blob_load(blob, &hk, &key); + if (ret == 0) { + ret = wc_ecc_sign_hash(hash, hashSz, sig, sigLen, rng, &key); + } + wc_ecc_free(&key); + return ret; +} + +/* Wrap a key, reload it from the stored blob, and sign with it. A real + * application writes the blob to flash and reloads it after a reset. */ +static void huk_ecdsa_reload_test(void) +{ + static huk_key_blob blob; + ecc_key pub; + WC_RNG rng; + byte hash[32]; + byte sig[80]; + word32 sigLen = sizeof(sig); + int verified = 0; + int ret; + + dbg_printf("\r\n== HUK-wrapped ECDSA key: wrap, store, reload, sign ==\r\n"); + + memset(hash, 0x42, sizeof(hash)); + ret = wc_InitRng(&rng); + CHECK("InitRng", ret == 0); + if (ret != 0) { + return; + } + ret = huk_blob_create(&rng, &blob); + CHECK("create wrapped key blob", ret == 0); + if (ret != 0) { + wc_FreeRng(&rng); + return; + } + + ret = huk_blob_sign(&blob, &rng, hash, sizeof(hash), sig, &sigLen); + CHECK("sign with reloaded blob", ret == 0); + if (ret == 0) { + ret = wc_ecc_init(&pub); + if (ret == 0) { + ret = wc_ecc_import_unsigned(&pub, blob.qx, blob.qy, NULL, + ECC_SECP256R1); + if (ret == 0) { + ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), + &verified, &pub); + } + wc_ecc_free(&pub); + } + CHECK("verify with stored public key", ret == 0 && verified == 1); + } + + /* The unwrap is authenticated, so a damaged blob cannot produce a key. */ + blob.tag[0] ^= 0x01; + sigLen = sizeof(sig); + ret = huk_blob_sign(&blob, &rng, hash, sizeof(hash), sig, &sigLen); + CHECK("tampered blob -> AES_GCM_AUTH_E", ret == AES_GCM_AUTH_E); + wc_FreeRng(&rng); +} + #ifdef WOLFSSL_RTL8735B_AES /* AES-GCM with a plaintext key through the second device (WC_RTL8735B_AES_DEVID). * Unlike the HUK device, the key bytes are the actual AES key, so the output @@ -785,9 +937,9 @@ static void plain_gcm_unaligned_test(void) ForceZero(key, 16); /* scrub key material before return */ } -/* A non-12-byte GCM IV must hard-fail on the plaintext device too, NOT silently - * fall back to software GCM (regression for the IV hard-error contract). */ -static void plain_gcm_badiv_test(void) +/* The HW path only takes a 12-byte IV, so the plaintext device declines any other + * length and software GCM serves it with the same literal key. */ +static void plain_gcm_long_iv_test(void) { Aes aes; byte key[16] __attribute__((aligned(32))); @@ -795,12 +947,14 @@ static void plain_gcm_badiv_test(void) byte pt[16] __attribute__((aligned(32))); byte ct[16] __attribute__((aligned(32))); byte tag[16] __attribute__((aligned(32))); + byte swCt[16]; + byte swTag[16]; int ret; memset(key, 0x5A, sizeof(key)); memset(iv, 0x11, sizeof(iv)); memset(pt, 0x33, sizeof(pt)); - dbg_printf("\r\n== AES-GCM plaintext key, non-12-byte IV must hard-fail " + dbg_printf("\r\n== AES-GCM plaintext key, non-12-byte IV uses software GCM " "==\r\n"); ret = wc_AesInit(&aes, NULL, WC_RTL8735B_AES_DEVID); CHECK("AesInit(devId=WC_RTL8735B_AES_DEVID)", ret == 0); @@ -815,8 +969,22 @@ static void plain_gcm_badiv_test(void) } ret = wc_AesGcmEncrypt(&aes, ct, pt, sizeof(pt), iv, sizeof(iv), tag, sizeof(tag), NULL, 0); - CHECK("16-byte IV rejected (no silent SW fallback)", ret != 0); + CHECK("AesGcmEncrypt (16-byte IV)", ret == 0); wc_AesFree(&aes); + if (ret == 0) { + ret = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_AesGcmSetKey(&aes, key, sizeof(key)); + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, swCt, pt, sizeof(pt), iv, sizeof(iv), + swTag, sizeof(swTag), NULL, 0); + } + wc_AesFree(&aes); + } + CHECK("16-byte IV matches software GCM with the same key", + ret == 0 && memcmp(ct, swCt, sizeof(ct)) == 0 && + memcmp(tag, swTag, sizeof(tag)) == 0); + } ForceZero(key, sizeof(key)); /* scrub key material before return */ } #endif /* WOLFSSL_RTL8735B_AES */ @@ -858,12 +1026,13 @@ static void wolf_huk_thread(void* param) huk_gcm_badiv_test(); huk_hmac_test(); huk_ecdsa_test(); + huk_ecdsa_reload_test(); #ifdef WOLFSSL_RTL8735B_AES if (aesRet == 0) { plain_gcm_test(); plain_ecb_test(); plain_gcm_unaligned_test(); - plain_gcm_badiv_test(); + plain_gcm_long_iv_test(); wc_Rtl8735b_AesUnRegister(WC_RTL8735B_AES_DEVID); } #endif