Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions rtl8735b/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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)
Expand All @@ -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 ===
```

Expand Down
185 changes: 177 additions & 8 deletions rtl8735b/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -785,22 +937,24 @@ 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)));
byte iv[16] __attribute__((aligned(32))); /* 16 bytes, not the supported 12 */
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);
Expand All @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
Loading