wolfssl-examples/rtl8735b/main.c

891 lines
32 KiB
C

/* wolfCrypt AmebaPro2 (RTL8735B) HUK example -- built inside the RealTek
* FreeRTOS SDK. Registers the wolfCrypt HUK crypto-callback device and runs
* AES-GCM (full payload) / AES-ECB / AES-CBC / AES-CTR, HMAC-SHA256, and ECDSA
* P-256 under a key derived from the silicon Hardware Unique Key; the working
* key never enters software. The 256-bit "seed" passed as the key is HKDF input
* diversifying the HUK. It also runs regression checks: unaligned-buffer GCM
* (port bounce path), in-place multi-call CBC, and a non-12-byte GCM IV that
* must hard-fail (no silent software fallback).
*
* With WOLFSSL_RTL8735B_AES also enabled it registers a second crypto-callback
* 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.
*
* Build: configure with -DEXAMPLE=wolfcrypt_huk (see wolfcrypt_huk.cmake).
*/
#include <string.h>
#include "platform_stdlib.h"
#include "FreeRTOS.h"
#include "task.h"
#include "device_lock.h"
#include "hal_trng_sec.h"
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/wc_port.h>
#include <wolfssl/wolfcrypt/aes.h>
#include <wolfssl/wolfcrypt/hmac.h>
#include <wolfssl/wolfcrypt/ecc.h>
#include <wolfssl/wolfcrypt/random.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/port/realtek/rtl8735b.h>
/* ForceZero (used to scrub key material below). Inline the misc helpers the same
* way wolfCrypt's own test.c/benchmark.c do; misc.c provides ForceZero only as an
* inline, so a plain extern reference would not link. */
#ifdef NO_INLINE
#include <wolfssl/wolfcrypt/misc.h>
#else
#define WOLFSSL_MISC_INCLUDED
#include <wolfcrypt/src/misc.c>
#endif
#define STACKSIZE 8192
#define CHECK(label, cond) \
dbg_printf("[%s] %s\r\n", (cond) ? "PASS" : "FAIL", (label))
/* wolfCrypt RNG seed hook (user_settings: CUSTOM_RAND_GENERATE_SEED). Fills from
* the AmebaPro2 secure (self-tested) hardware TRNG -- the same source the HUK
* port exposes as a crypto-callback SEED. (Using the secure TRNG directly also
* avoids the non-secure hal_trng wrapper, whose C-cut path references a ROM
* symbol not resolvable in a from-source SDK build.) */
int rtl8735b_rand_seed(unsigned char* output, unsigned int sz)
{
static int inited = 0;
unsigned int i, n;
u32 r;
if (inited == 0) {
if (hal_trng_sec_init() != 0) {
return -1;
}
inited = 1;
}
for (i = 0; i < sz; ) {
r = hal_trng_sec_get_rand();
n = (sz - i) < 4u ? (sz - i) : 4u;
memcpy(output + i, &r, n);
i += n;
}
return 0;
}
static void huk_gcm_test(void)
{
Aes aes;
/* HAL crypto engine requires 32-byte-aligned key/iv/aad/tag buffers. */
byte seed[32] __attribute__((aligned(32)));
byte iv[12] __attribute__((aligned(32)));
byte aad[16] __attribute__((aligned(32)));
byte pt[32] __attribute__((aligned(32)));
byte ct[32] __attribute__((aligned(32)));
byte dec[32] __attribute__((aligned(32)));
byte tag[16] __attribute__((aligned(32)));
byte tag2[16] __attribute__((aligned(32)));
int ret;
memset(seed, 0xA5, sizeof(seed)); memset(iv, 0x11, sizeof(iv));
memset(aad, 0x22, sizeof(aad)); memset(pt, 0x33, sizeof(pt));
dbg_printf("\r\n== AES-GCM (full payload) under HUK-derived key ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesGcmSetKey(&aes, seed, sizeof(seed));
CHECK("AesGcmSetKey(seed,32)", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
ret = wc_AesGcmEncrypt(&aes, ct, pt, sizeof(pt), iv, sizeof(iv),
tag, sizeof(tag), aad, sizeof(aad));
CHECK("AesGcmEncrypt", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
memset(tag2, 0, sizeof(tag2));
ret = wc_AesGcmEncrypt(&aes, ct, pt, sizeof(pt), iv, sizeof(iv),
tag2, sizeof(tag2), aad, sizeof(aad));
CHECK("deterministic tag", ret == 0 && memcmp(tag, tag2, 16) == 0);
ret = wc_AesGcmDecrypt(&aes, dec, ct, sizeof(ct), iv, sizeof(iv),
tag, sizeof(tag), aad, sizeof(aad));
CHECK("AesGcmDecrypt verifies", ret == 0);
CHECK("plaintext round-trips", memcmp(dec, pt, sizeof(pt)) == 0);
seed[0] ^= 0xFF;
ret = wc_AesGcmSetKey(&aes, seed, sizeof(seed));
CHECK("AesGcmSetKey(wrong seed)", ret == 0);
ret = wc_AesGcmDecrypt(&aes, dec, ct, sizeof(ct), iv, sizeof(iv),
tag, sizeof(tag), aad, sizeof(aad));
CHECK("wrong seed -> AES_GCM_AUTH_E", ret == AES_GCM_AUTH_E);
wc_AesFree(&aes);
ForceZero(seed, sizeof(seed)); /* scrub key material before return */
}
static void huk_ecb_cbc_test(void)
{
Aes aes;
byte seed[32] __attribute__((aligned(32)));
byte iv[16] __attribute__((aligned(32)));
byte pt[32] __attribute__((aligned(32)));
byte ct[32] __attribute__((aligned(32)));
byte dec[32] __attribute__((aligned(32)));
int ret;
memset(seed, 0x5A, sizeof(seed)); memset(iv, 0x44, sizeof(iv));
memset(pt, 0x77, sizeof(pt));
dbg_printf("\r\n== AES-ECB under HUK-derived key ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesSetKey(&aes, seed, sizeof(seed), NULL, AES_ENCRYPTION);
CHECK("AesSetKey(ECB enc)", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
ret = wc_AesEcbEncrypt(&aes, ct, pt, sizeof(pt));
CHECK("AesEcbEncrypt", ret == 0);
ret = wc_AesSetKey(&aes, seed, sizeof(seed), NULL, AES_DECRYPTION);
CHECK("AesSetKey(ECB dec)", ret == 0);
ret = wc_AesEcbDecrypt(&aes, dec, ct, sizeof(ct));
CHECK("AesEcb round-trip", ret == 0 && memcmp(dec, pt, sizeof(pt)) == 0);
wc_AesFree(&aes);
dbg_printf("\r\n== AES-CBC under HUK-derived key ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesSetKey(&aes, seed, sizeof(seed), iv, AES_ENCRYPTION);
CHECK("AesSetKey(CBC enc)", ret == 0);
ret = wc_AesCbcEncrypt(&aes, ct, pt, sizeof(pt));
CHECK("AesCbcEncrypt", ret == 0);
ret = wc_AesSetKey(&aes, seed, sizeof(seed), iv, AES_DECRYPTION);
CHECK("AesSetKey(CBC dec)", ret == 0);
ret = wc_AesCbcDecrypt(&aes, dec, ct, sizeof(ct));
CHECK("AesCbc round-trip", ret == 0 && memcmp(dec, pt, sizeof(pt)) == 0);
wc_AesFree(&aes);
ForceZero(seed, sizeof(seed)); /* scrub key material before return */
}
static void huk_ctr_test(void)
{
Aes aes;
byte seed[32] __attribute__((aligned(32)));
byte iv[16] __attribute__((aligned(32)));
byte pt[20] __attribute__((aligned(32))); /* non-block-multiple: partial */
byte ct[20] __attribute__((aligned(32)));
byte dec[20] __attribute__((aligned(32)));
int ret;
memset(seed, 0x5A, sizeof(seed)); memset(iv, 0x66, sizeof(iv));
memset(pt, 0x99, sizeof(pt));
dbg_printf("\r\n== AES-CTR under HUK-derived key ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesSetKeyDirect(&aes, seed, sizeof(seed), iv, AES_ENCRYPTION);
CHECK("AesSetKeyDirect(CTR)", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
ret = wc_AesCtrEncrypt(&aes, ct, pt, sizeof(pt));
CHECK("AesCtrEncrypt", ret == 0);
CHECK("CTR ciphertext != plaintext", memcmp(ct, pt, sizeof(pt)) != 0);
wc_AesFree(&aes);
/* CTR is its own inverse with the same key+IV */
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesSetKeyDirect(&aes, seed, sizeof(seed), iv, AES_ENCRYPTION);
CHECK("AesSetKeyDirect(CTR round-trip)", ret == 0);
ret = wc_AesCtrEncrypt(&aes, dec, ct, sizeof(ct));
CHECK("AesCtr round-trip", ret == 0 && memcmp(dec, pt, sizeof(pt)) == 0);
wc_AesFree(&aes);
ForceZero(seed, sizeof(seed)); /* scrub key material before return */
}
static void huk_gcm_unaligned_test(void)
{
Aes aes;
/* 32-byte-aligned backing; the +1 views below are deliberately unaligned so
* the port's bounce-to-aligned path is exercised. */
byte buf[7][48] __attribute__((aligned(32)));
byte* seed = buf[0] + 1;
byte* iv = buf[1] + 1;
byte* aad = buf[2] + 1;
byte* pt = buf[3] + 1;
byte* ct = buf[4] + 1;
byte* dec = buf[5] + 1;
byte* tag = buf[6] + 1;
int ret;
memset(seed, 0xA5, 32); memset(iv, 0x11, 12);
memset(aad, 0x22, 16); memset(pt, 0x33, 32);
dbg_printf("\r\n== AES-GCM with UNALIGNED buffers (port bounces) ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesGcmSetKey(&aes, seed, 32);
CHECK("AesGcmSetKey (unaligned seed)", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
ret = wc_AesGcmEncrypt(&aes, ct, pt, 32, iv, 12, tag, 16, aad, 16);
CHECK("AesGcmEncrypt (unaligned)", ret == 0);
ret = wc_AesGcmDecrypt(&aes, dec, ct, 32, iv, 12, tag, 16, aad, 16);
CHECK("AesGcmDecrypt (unaligned) verifies", ret == 0);
CHECK("unaligned round-trip", memcmp(dec, pt, 32) == 0);
wc_AesFree(&aes);
ForceZero(seed, 32); /* scrub key material before return */
}
/* In-place CBC decrypt (in == out) across two chained calls -- regression for
* the in-place chaining / IV-advance fix. */
static void huk_cbc_inplace_test(void)
{
Aes aes;
byte seed[32] __attribute__((aligned(32)));
byte iv[16] __attribute__((aligned(32)));
byte pt[48] __attribute__((aligned(32))); /* 3 blocks */
byte buf[48] __attribute__((aligned(32))); /* encrypted then decrypted in place */
int ret, i;
memset(seed, 0x5A, sizeof(seed)); memset(iv, 0x44, sizeof(iv));
for (i = 0; i < (int)sizeof(pt); i++) {
pt[i] = (byte)i;
}
memcpy(buf, pt, sizeof(pt));
dbg_printf("\r\n== AES-CBC in-place + multi-call under HUK-derived key ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesSetKey(&aes, seed, sizeof(seed), iv, AES_ENCRYPTION);
CHECK("AesSetKey(CBC enc)", ret == 0);
ret = wc_AesCbcEncrypt(&aes, buf, buf, sizeof(buf)); /* in == out */
CHECK("CBC in-place encrypt", ret == 0);
wc_AesFree(&aes);
/* Decrypt in place across two calls (32 then 16 bytes) -- the 2nd call relies
* on aes->reg having been advanced to the correct last-ciphertext block. */
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesSetKey(&aes, seed, sizeof(seed), iv, AES_DECRYPTION);
CHECK("AesSetKey(CBC dec)", ret == 0);
ret = wc_AesCbcDecrypt(&aes, buf, buf, 32);
if (ret == 0) {
ret = wc_AesCbcDecrypt(&aes, buf + 32, buf + 32, 16);
}
CHECK("CBC in-place multi-call decrypt", ret == 0);
CHECK("CBC in-place round-trip", memcmp(buf, pt, sizeof(pt)) == 0);
wc_AesFree(&aes);
ForceZero(seed, sizeof(seed)); /* scrub key material before return */
}
/* A non-12-byte GCM IV must hard-fail, NOT silently fall back to software GCM
* keyed on the seed -- regression for the IV hard-error fix. */
static void huk_gcm_badiv_test(void)
{
Aes aes;
byte seed[32] __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)));
int ret;
memset(seed, 0xA5, sizeof(seed)); memset(iv, 0x11, sizeof(iv));
memset(pt, 0x33, sizeof(pt));
dbg_printf("\r\n== AES-GCM non-12-byte IV must hard-fail ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesGcmSetKey(&aes, seed, sizeof(seed));
CHECK("AesGcmSetKey(seed,32)", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
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);
wc_AesFree(&aes);
ForceZero(seed, sizeof(seed)); /* scrub key material before return */
}
/* HMAC-SHA256 under the HUK-derived key: same seed -> same MAC (deterministic),
* a different seed -> different MAC. The 32-byte seed is the HMAC key; the port
* runs the MAC over the HUK-derived secure-key slot (key never in software). */
static void huk_hmac_test(void)
{
Hmac hmac;
byte seed[32] __attribute__((aligned(32)));
byte msg[40] __attribute__((aligned(32)));
byte mac1[WC_SHA256_DIGEST_SIZE];
byte mac2[WC_SHA256_DIGEST_SIZE];
byte mac3[WC_SHA256_DIGEST_SIZE];
int ret;
memset(seed, 0x5A, sizeof(seed));
memset(msg, 0x33, sizeof(msg));
dbg_printf("\r\n== HMAC-SHA256 under HUK-derived key ==\r\n");
ret = wc_HmacInit(&hmac, NULL, WC_HUK_DEVID);
CHECK("HmacInit(devId=WC_HUK_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_HmacSetKey(&hmac, WC_SHA256, seed, sizeof(seed));
CHECK("HmacSetKey(seed,32)", ret == 0);
if (ret == 0) {
ret = wc_HmacUpdate(&hmac, msg, sizeof(msg));
}
if (ret == 0) {
ret = wc_HmacFinal(&hmac, mac1);
}
CHECK("HmacFinal", ret == 0);
wc_HmacFree(&hmac);
if (ret != 0) {
return;
}
/* Same seed + message -> identical MAC. */
ret = wc_HmacInit(&hmac, NULL, WC_HUK_DEVID);
if (ret == 0) {
ret = wc_HmacSetKey(&hmac, WC_SHA256, seed, sizeof(seed));
}
if (ret == 0) {
ret = wc_HmacUpdate(&hmac, msg, sizeof(msg));
}
if (ret == 0) {
ret = wc_HmacFinal(&hmac, mac2);
}
wc_HmacFree(&hmac);
CHECK("deterministic MAC", ret == 0 &&
memcmp(mac1, mac2, sizeof(mac1)) == 0);
/* Different seed -> different MAC. */
seed[0] ^= 0xFF;
ret = wc_HmacInit(&hmac, NULL, WC_HUK_DEVID);
if (ret == 0) {
ret = wc_HmacSetKey(&hmac, WC_SHA256, seed, sizeof(seed));
}
if (ret == 0) {
ret = wc_HmacUpdate(&hmac, msg, sizeof(msg));
}
if (ret == 0) {
ret = wc_HmacFinal(&hmac, mac3);
}
wc_HmacFree(&hmac);
CHECK("wrong seed -> different MAC", ret == 0 &&
memcmp(mac1, mac3, sizeof(mac1)) != 0);
ForceZero(seed, sizeof(seed)); /* scrub key material before return */
}
/* 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
* 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
* hk.useHwEngine = 1 (requires on-silicon validation). */
static void huk_ecdsa_test(void)
{
Aes aes;
ecc_key key;
ecc_key huk;
ecc_key hwpub;
WC_RNG rng;
wc_Rtl8735b_EccKey hk;
byte seed[32] __attribute__((aligned(32)));
byte d[32] __attribute__((aligned(32)));
byte wrapped[32] __attribute__((aligned(32)));
byte wrapIv[12] __attribute__((aligned(32))); /* GCM nonce for the wrap */
byte wrapTag[16] __attribute__((aligned(32))); /* GCM auth tag */
byte hash[32] __attribute__((aligned(32)));
byte qx[32];
byte qy[32];
byte sig[80];
word32 dLen = sizeof(d);
word32 qxLen = sizeof(qx);
word32 qyLen = sizeof(qy);
word32 sigLen = sizeof(sig);
int ret;
int verified = 0;
int hwVerified = 0;
int rngOk = 0;
memset(seed, 0x6B, sizeof(seed));
memset(hash, 0x42, sizeof(hash));
memset(d, 0, sizeof(d));
/* Zero the key structs up front so the single cleanup can wc_ecc_free them
* safely even if an earlier step fails before they are initialized. */
memset(&key, 0, sizeof(key));
memset(&huk, 0, sizeof(huk));
memset(&hwpub, 0, sizeof(hwpub));
dbg_printf("\r\n== ECDSA P-256 under HUK-derived key ==\r\n");
ret = wc_InitRng(&rng);
CHECK("InitRng", ret == 0);
if (ret != 0) {
goto cleanup;
}
rngOk = 1;
ret = wc_ecc_init(&key);
if (ret == 0) {
ret = wc_ecc_make_key(&rng, 32, &key);
}
CHECK("ecc_make_key (P-256)", ret == 0);
if (ret != 0) {
goto cleanup;
}
ret = wc_ecc_export_private_only(&key, d, &dLen);
CHECK("export private scalar (32B)", ret == 0 && dLen == 32);
if (ret != 0 || dLen != 32) {
goto cleanup;
}
/* Wrap the scalar under the HUK: AES-GCM-encrypt with the HUK device + seed
* so the blob is authenticated (a tampered/wrong blob fails at unwrap). */
memset(wrapIv, 0x24, sizeof(wrapIv)); /* demo nonce; use a fresh one/provision */
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
if (ret == 0) {
ret = wc_AesGcmSetKey(&aes, seed, sizeof(seed));
}
if (ret == 0) {
ret = wc_AesGcmEncrypt(&aes, wrapped, d, sizeof(d), wrapIv, sizeof(wrapIv),
wrapTag, sizeof(wrapTag), NULL, 0);
}
wc_AesFree(&aes);
CHECK("wrap scalar under HUK (GCM)", ret == 0);
if (ret != 0) {
goto cleanup;
}
/* Sign via the HUK-bound key (GCM-unwraps under the HUK, then signs). */
memset(&hk, 0, sizeof(hk));
hk.seed = seed; hk.seedSz = sizeof(seed);
hk.wrapped = wrapped; hk.wrappedLen = 32;
hk.plainLen = 32;
hk.iv = wrapIv; hk.ivSz = sizeof(wrapIv);
hk.tag = wrapTag; hk.tagSz = sizeof(wrapTag);
/* Route the sign through the HW ECDSA engine (hal_ecdsa, validated on the
* RTL8735B). Set 0 for the software-after-unwrap path (the port default).
* hk.otpPrkSel can instead select an OTP-resident key (scalar never in
* software); see wolfcrypt/src/port/realtek/README.md. */
hk.useHwEngine = 1;
ret = wc_ecc_init_ex(&huk, NULL, WC_HUK_DEVID);
if (ret == 0) {
ret = wc_ecc_set_curve(&huk, 32, ECC_SECP256R1);
}
if (ret == 0) {
huk.devCtx = &hk;
ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &sigLen, &rng, &huk);
}
CHECK("HUK ECDSA sign", ret == 0);
if (ret == 0) {
ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &verified,
&key);
}
CHECK("verify with software public key", ret == 0 && verified == 1);
/* Verify the same signature through the HW ECDSA engine (general verify
* offload): import the public point into a WC_HUK_DEVID key so the verify
* dispatches to hal_ecdsa. A good signature -> res = 1; a tampered hash
* must -> res = 0 (and still return 0, since a bad signature is not an
* error). No HUK context is needed for verify -- any P-256 public key. */
ret = wc_ecc_export_public_raw(&key, qx, &qxLen, qy, &qyLen);
CHECK("export public point", ret == 0);
if (ret == 0) {
ret = wc_ecc_init_ex(&hwpub, NULL, WC_HUK_DEVID);
}
if (ret == 0) {
ret = wc_ecc_import_unsigned(&hwpub, qx, qy, NULL, ECC_SECP256R1);
}
if (ret == 0) {
hwVerified = 0;
ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &hwVerified,
&hwpub);
}
CHECK("HW ECDSA verify (good sig)", ret == 0 && hwVerified == 1);
if (ret == 0) {
hash[0] ^= 0xFF; /* tamper the digest */
hwVerified = 1;
ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &hwVerified,
&hwpub);
hash[0] ^= 0xFF; /* restore */
}
CHECK("HW ECDSA verify (tampered -> reject)", ret == 0 && hwVerified == 0);
cleanup:
wc_ecc_free(&hwpub);
wc_ecc_free(&huk);
wc_ecc_free(&key);
if (rngOk != 0) {
wc_FreeRng(&rng);
}
/* Scrub the plaintext private scalar, its wrapped form, and the HKDF seed
* (zero secrets with ForceZero, not memset, per wolfSSL convention). */
ForceZero(d, sizeof(d));
ForceZero(wrapped, sizeof(wrapped));
ForceZero(seed, sizeof(seed));
}
#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
* matches a published GCM vector. Then the same 32 key bytes are run through
* both devices to show they coexist and are selected per Aes by devId: the
* plaintext-key ciphertext differs from the HUK device-bound ciphertext. */
static void plain_gcm_test(void)
{
Aes aes;
/* McGrew & Viega GCM test case 3: 128-bit key, 64-byte payload, no AAD. */
static const byte katKey[16] = {
0xfe,0xff,0xe9,0x92,0x86,0x65,0x73,0x1c,
0x6d,0x6a,0x8f,0x94,0x67,0x30,0x83,0x08
};
static const byte katIv[12] = {
0xca,0xfe,0xba,0xbe,0xfa,0xce,0xdb,0xad,0xde,0xca,0xf8,0x88
};
static const byte katPt[64] = {
0xd9,0x31,0x32,0x25,0xf8,0x84,0x06,0xe5,
0xa5,0x59,0x09,0xc5,0xaf,0xf5,0x26,0x9a,
0x86,0xa7,0xa9,0x53,0x15,0x34,0xf7,0xda,
0x2e,0x4c,0x30,0x3d,0x8a,0x31,0x8a,0x72,
0x1c,0x3c,0x0c,0x95,0x95,0x68,0x09,0x53,
0x2f,0xcf,0x0e,0x24,0x49,0xa6,0xb5,0x25,
0xb1,0x6a,0xed,0xf5,0xaa,0x0d,0xe6,0x57,
0xba,0x63,0x7b,0x39,0x1a,0xaf,0xd2,0x55
};
static const byte katCt[64] = {
0x42,0x83,0x1e,0xc2,0x21,0x77,0x74,0x24,
0x4b,0x72,0x21,0xb7,0x84,0xd0,0xd4,0x9c,
0xe3,0xaa,0x21,0x2f,0x2c,0x02,0xa4,0xe0,
0x35,0xc1,0x7e,0x23,0x29,0xac,0xa1,0x2e,
0x21,0xd5,0x14,0xb2,0x54,0x66,0x93,0x1c,
0x7d,0x8f,0x6a,0x5a,0xac,0x84,0xaa,0x05,
0x1b,0xa3,0x0b,0x39,0x6a,0x0a,0xac,0x97,
0x3d,0x58,0xe0,0x91,0x47,0x3f,0x59,0x85
};
static const byte katTag[16] = {
0x4d,0x5c,0x2a,0xf3,0x27,0xcd,0x64,0xa6,
0x2c,0xf3,0x5a,0xbd,0x2b,0xa6,0xfa,0xb4
};
/* HAL crypto engine requires 32-byte-aligned key/iv/buffers. */
byte key[32] __attribute__((aligned(32)));
byte iv[12] __attribute__((aligned(32)));
byte pt[64] __attribute__((aligned(32)));
byte ct[64] __attribute__((aligned(32)));
byte ctHuk[64] __attribute__((aligned(32)));
byte dec[64] __attribute__((aligned(32)));
byte tag[16] __attribute__((aligned(32)));
byte tagHuk[16] __attribute__((aligned(32)));
int ret;
dbg_printf("\r\n== AES-GCM with a plaintext key "
"(devId=WC_RTL8735B_AES_DEVID) ==\r\n");
/* Known-answer test: the plaintext key must reproduce the published vector.
* Stage the vector key into the 32-byte-aligned buffer the HAL DMA wants. */
memcpy(key, katKey, sizeof(katKey));
memcpy(iv, katIv, sizeof(iv));
memcpy(pt, katPt, sizeof(pt));
ret = wc_AesInit(&aes, NULL, WC_RTL8735B_AES_DEVID);
CHECK("AesInit(devId=WC_RTL8735B_AES_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesGcmSetKey(&aes, key, sizeof(katKey));
CHECK("AesGcmSetKey(plaintext 128-bit key)", ret == 0);
if (ret == 0) {
ret = wc_AesGcmEncrypt(&aes, ct, pt, sizeof(pt), iv, sizeof(iv),
tag, sizeof(tag), NULL, 0);
CHECK("AesGcmEncrypt", ret == 0);
}
CHECK("ciphertext matches published vector",
ret == 0 && memcmp(ct, katCt, sizeof(katCt)) == 0);
CHECK("tag matches published vector",
ret == 0 && memcmp(tag, katTag, sizeof(katTag)) == 0);
if (ret == 0) {
ret = wc_AesGcmDecrypt(&aes, dec, ct, sizeof(ct), iv, sizeof(iv),
tag, sizeof(tag), NULL, 0);
CHECK("AesGcmDecrypt verifies", ret == 0);
CHECK("plaintext round-trips", memcmp(dec, pt, sizeof(pt)) == 0);
/* Negative: a tampered tag must be rejected by the HW decrypt-verify. */
tag[0] ^= 0xFFu;
ret = wc_AesGcmDecrypt(&aes, dec, ct, sizeof(ct), iv, sizeof(iv),
tag, sizeof(tag), NULL, 0);
CHECK("tampered tag -> AES_GCM_AUTH_E", ret == AES_GCM_AUTH_E);
}
wc_AesFree(&aes);
/* Coexistence: same 32 key bytes -> plaintext device vs HUK device produce
* different ciphertext (one uses the key verbatim, the other as a seed). */
memset(key, 0x5A, sizeof(key));
ret = wc_AesInit(&aes, NULL, WC_RTL8735B_AES_DEVID);
CHECK("AesInit(plaintext devId)", ret == 0);
if (ret == 0) {
ret = wc_AesGcmSetKey(&aes, key, sizeof(key));
CHECK("AesGcmSetKey(plaintext 256-bit key)", ret == 0);
}
if (ret == 0) {
ret = wc_AesGcmEncrypt(&aes, ct, pt, sizeof(pt), iv, sizeof(iv),
tag, sizeof(tag), NULL, 0);
}
wc_AesFree(&aes);
CHECK("plaintext-key encrypt (256-bit)", ret == 0);
if (ret == 0) {
ret = wc_AesInit(&aes, NULL, WC_HUK_DEVID);
CHECK("AesInit(HUK devId)", ret == 0);
if (ret == 0) {
ret = wc_AesGcmSetKey(&aes, key, sizeof(key));
CHECK("AesGcmSetKey(HUK seed, same 32 bytes)", ret == 0);
}
if (ret == 0) {
ret = wc_AesGcmEncrypt(&aes, ctHuk, pt, sizeof(pt), iv, sizeof(iv),
tagHuk, sizeof(tagHuk), NULL, 0);
}
wc_AesFree(&aes);
CHECK("HUK-seed encrypt (same 32 bytes)", ret == 0);
CHECK("plaintext CT != HUK CT (distinct keys per devId)",
ret == 0 && memcmp(ct, ctHuk, sizeof(ct)) != 0);
}
ForceZero(key, sizeof(key)); /* scrub key material before return */
}
/* AES-ECB with a plaintext key: FIPS-197 appendix C.1 known-answer vector. The
* plaintext device runs the key verbatim, so the HW output must match the
* published ciphertext exactly (the HUK device cannot -- its key is derived). */
static void plain_ecb_test(void)
{
Aes aes;
static const byte katKey[16] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
};
static const byte katPt[16] = {
0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,
0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff
};
static const byte katCt[16] = {
0x69,0xc4,0xe0,0xd8,0x6a,0x7b,0x04,0x30,
0xd8,0xcd,0xb7,0x80,0x70,0xb4,0xc5,0x5a
};
byte key[16] __attribute__((aligned(32)));
byte pt[16] __attribute__((aligned(32)));
byte ct[16] __attribute__((aligned(32)));
byte dec[16] __attribute__((aligned(32)));
int ret;
memcpy(key, katKey, sizeof(key));
memcpy(pt, katPt, sizeof(pt));
dbg_printf("\r\n== AES-ECB with a plaintext key (FIPS-197 KAT) ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_RTL8735B_AES_DEVID);
CHECK("AesInit(devId=WC_RTL8735B_AES_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_ENCRYPTION);
CHECK("AesSetKey(ECB enc)", ret == 0);
if (ret == 0) {
ret = wc_AesEcbEncrypt(&aes, ct, pt, sizeof(pt));
CHECK("AesEcbEncrypt", ret == 0);
}
CHECK("ciphertext matches FIPS-197 vector",
ret == 0 && memcmp(ct, katCt, sizeof(katCt)) == 0);
ret = wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_DECRYPTION);
CHECK("AesSetKey(ECB dec)", ret == 0);
if (ret == 0) {
ret = wc_AesEcbDecrypt(&aes, dec, ct, sizeof(ct));
}
CHECK("AesEcb round-trip", ret == 0 && memcmp(dec, pt, sizeof(pt)) == 0);
wc_AesFree(&aes);
ForceZero(key, sizeof(key)); /* scrub key material before return */
}
/* AES-GCM with a plaintext key over deliberately-unaligned caller buffers, so
* the port's bounce-to-aligned path is exercised for the second device too. */
static void plain_gcm_unaligned_test(void)
{
Aes aes;
/* 32-byte-aligned backing; the +1 views below are deliberately unaligned. */
byte buf[6][48] __attribute__((aligned(32)));
byte* key = buf[0] + 1;
byte* iv = buf[1] + 1;
byte* pt = buf[2] + 1;
byte* ct = buf[3] + 1;
byte* dec = buf[4] + 1;
byte* tag = buf[5] + 1;
int ret;
memset(key, 0x5A, 16); memset(iv, 0x11, 12);
memset(pt, 0x33, 32);
dbg_printf("\r\n== AES-GCM plaintext key, UNALIGNED buffers "
"(port bounces) ==\r\n");
ret = wc_AesInit(&aes, NULL, WC_RTL8735B_AES_DEVID);
CHECK("AesInit(devId=WC_RTL8735B_AES_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesGcmSetKey(&aes, key, 16);
CHECK("AesGcmSetKey (unaligned key)", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
ret = wc_AesGcmEncrypt(&aes, ct, pt, 32, iv, 12, tag, 16, NULL, 0);
CHECK("AesGcmEncrypt (unaligned)", ret == 0);
ret = wc_AesGcmDecrypt(&aes, dec, ct, 32, iv, 12, tag, 16, NULL, 0);
CHECK("AesGcmDecrypt (unaligned) verifies", ret == 0);
CHECK("unaligned round-trip", memcmp(dec, pt, 32) == 0);
wc_AesFree(&aes);
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)
{
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)));
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 "
"==\r\n");
ret = wc_AesInit(&aes, NULL, WC_RTL8735B_AES_DEVID);
CHECK("AesInit(devId=WC_RTL8735B_AES_DEVID)", ret == 0);
if (ret != 0) {
return;
}
ret = wc_AesGcmSetKey(&aes, key, sizeof(key));
CHECK("AesGcmSetKey(key,16)", ret == 0);
if (ret != 0) {
wc_AesFree(&aes);
return;
}
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);
wc_AesFree(&aes);
ForceZero(key, sizeof(key)); /* scrub key material before return */
}
#endif /* WOLFSSL_RTL8735B_AES */
static void wolf_huk_thread(void* param)
{
int ret;
#ifdef WOLFSSL_RTL8735B_AES
int aesRet = 0;
#endif
(void)param;
dbg_printf("\r\n=== wolfCrypt AmebaPro2 (RTL8735B) HUK example ===\r\n");
device_mutex_lock(RT_DEV_LOCK_CRYPTO);
ret = wolfCrypt_Init();
CHECK("wolfCrypt_Init", ret == 0);
if (ret == 0) {
ret = wc_Rtl8735b_HukRegister(WC_HUK_DEVID);
CHECK("wc_Rtl8735b_HukRegister", ret == 0);
}
#ifdef WOLFSSL_RTL8735B_AES
/* The plaintext-key AES device is an optional add-on; track its registration
* in its own result so a failure only skips its demo -- it must never
* suppress the HUK suite or leak the HUK device registration. */
if (ret == 0) {
aesRet = wc_Rtl8735b_AesRegister(WC_RTL8735B_AES_DEVID);
CHECK("wc_Rtl8735b_AesRegister", aesRet == 0);
}
#endif
if (ret == 0) {
huk_gcm_test();
huk_ecb_cbc_test();
huk_ctr_test();
huk_gcm_unaligned_test();
huk_cbc_inplace_test();
huk_gcm_badiv_test();
huk_hmac_test();
huk_ecdsa_test();
#ifdef WOLFSSL_RTL8735B_AES
if (aesRet == 0) {
plain_gcm_test();
plain_ecb_test();
plain_gcm_unaligned_test();
plain_gcm_badiv_test();
wc_Rtl8735b_AesUnRegister(WC_RTL8735B_AES_DEVID);
}
#endif
wc_Rtl8735b_HukUnRegister(WC_HUK_DEVID);
}
wolfCrypt_Cleanup();
device_mutex_unlock(RT_DEV_LOCK_CRYPTO);
dbg_printf("\r\n=== done ===\r\n");
vTaskDelete(NULL);
}
int main(void)
{
if (xTaskCreate(wolf_huk_thread, "wolf_huk", STACKSIZE, NULL,
tskIDLE_PRIORITY + 1, NULL) != pdPASS) {
dbg_printf("xTaskCreate failed\r\n");
}
else {
vTaskStartScheduler();
}
while (1) {
}
}