From def00ad02d728d76c6cdb00a3d725dbb31abc3bf Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 1 Sep 2026 15:12:03 -0700 Subject: [PATCH] TI C2000 example: init the software AES context, honour ENTROPY_NUM_SRC, report per-window raw entropy --- .../ti-c2000-f28p55x/Header/entropy_probe.h | 30 +++ embedded/ti-c2000-f28p55x/Makefile | 7 + embedded/ti-c2000-f28p55x/README.md | 2 +- .../ti-c2000-f28p55x/Source/entropy_probe.c | 34 +++- embedded/ti-c2000-f28p55x/Source/wolf_main.c | 180 ++++++++++++++---- .../ti-c2000-f28p55x/tools/entropy_analyze.py | 73 +++++-- embedded/ti-c2000-f28p55x/tools/gen_kat.c | 11 +- 7 files changed, 282 insertions(+), 55 deletions(-) create mode 100644 embedded/ti-c2000-f28p55x/Header/entropy_probe.h diff --git a/embedded/ti-c2000-f28p55x/Header/entropy_probe.h b/embedded/ti-c2000-f28p55x/Header/entropy_probe.h new file mode 100644 index 00000000..7eae181b --- /dev/null +++ b/embedded/ti-c2000-f28p55x/Header/entropy_probe.h @@ -0,0 +1,30 @@ +/* entropy_probe.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + + +#ifndef ENTROPY_PROBE_H +#define ENTROPY_PROBE_H + +/* Raw entropy-source characterization image (make ENTROPY_PROBE=1). + * Dumps unconditioned samples over SCI; never returns. */ +void entropy_probe_run(void); + +#endif /* ENTROPY_PROBE_H */ diff --git a/embedded/ti-c2000-f28p55x/Makefile b/embedded/ti-c2000-f28p55x/Makefile index bae26161..e979c6b6 100644 --- a/embedded/ti-c2000-f28p55x/Makefile +++ b/embedded/ti-c2000-f28p55x/Makefile @@ -310,6 +310,13 @@ endif # can estimate min-entropy. Measurement only - no crypto runs. ENTROPY_PROBE ?= 0 ifeq ($(ENTROPY_PROBE),1) +ifneq ($(SIGN)$(MLDSA)$(BENCH),000) +$(error ENTROPY_PROBE=1 is a standalone measurement image - build it on its own) +endif + # Nothing after entropy_probe_run() executes, so linking wolfcrypt_test and + # benchmark would only bloat the image. + CFLAGS += --define=NO_CRYPT_TEST --define=NO_CRYPT_BENCHMARK + HARNESS_SRCS := CFLAGS += --define=WOLF_ENTROPY_PROBE --define=NO_CRYPT_TEST \ --define=NO_CRYPT_BENCHMARK HARNESS_EXTRA += $(CURDIR)/Source/entropy_probe.c diff --git a/embedded/ti-c2000-f28p55x/README.md b/embedded/ti-c2000-f28p55x/README.md index d768dc48..af6c6131 100644 --- a/embedded/ti-c2000-f28p55x/README.md +++ b/embedded/ti-c2000-f28p55x/README.md @@ -117,7 +117,7 @@ ML-DSA-87 (asymmetric, @150 MHz): verify ~225 ms/op; keygen and signing also run The F28P550SJ has an on-chip AES accelerator ("AESA", a TI EIP-120t at `0x00042000`) that C2000Ware exposes through `driverlib/f28p55x/driverlib/aes.h`. `HWAES=1` offloads AES-ECB/CBC/CTR to it via the wolfCrypt crypto-callback framework (`wolfcrypt/src/port/ti/ti-c2000-aes.c` in the wolfSSL tree, gated on `WOLFSSL_C2000_AES`). `driverlib.lib` is already linked by this example, so no extra build plumbing is needed. -Software AES stays compiled in. A context opts into hardware with `wc_AesInit(&aes, NULL, WOLFSSL_C2000_DEVID)`; one initialised with `INVALID_DEVID` runs pure software. `wolf_aes_hw_test()` uses both and compares them, which is the point: on a 16-bit-byte target the octet marshalling into the accelerator's 32-bit registers is the highest-risk part of the port, and a mismatch is exactly what you want to see. The harness prints 13 lines covering ECB/CBC/CTR at 128/192/256 bits, multi-block, split calls, in-place decrypt and a non-block-aligned CTR split, each checked against software and (for the first block of each mode) against the published NIST SP800-38A vector. +Software AES stays compiled in. A context opts into hardware with `wc_AesInit(&aes, NULL, WOLFSSL_C2000_DEVID)`; one initialised with `INVALID_DEVID` runs pure software. `wolf_aes_hw_test()` uses both and compares them, which is the point: on a 16-bit-byte target the octet marshalling into the accelerator's 32-bit registers is the highest-risk part of the port, and a mismatch is exactly what you want to see. The harness prints 18 lines covering ECB/CBC/CTR at 128/192/256 bits, multi-block, split calls, in-place decrypt and a non-block-aligned CTR split, each checked against software and (for the first block of each mode) against the published NIST SP800-38A vector. `HWAES=1` also defines `WC_USE_DEVID=0x2000` so `wolfcrypt_test` and `benchmark` exercise the device too -- without it they init every context with `INVALID_DEVID` and silently measure software. diff --git a/embedded/ti-c2000-f28p55x/Source/entropy_probe.c b/embedded/ti-c2000-f28p55x/Source/entropy_probe.c index 2554f538..286ef5f8 100644 --- a/embedded/ti-c2000-f28p55x/Source/entropy_probe.c +++ b/embedded/ti-c2000-f28p55x/Source/entropy_probe.c @@ -48,6 +48,13 @@ /* Counter1 counts down from 0xFFFFF, capping the window near 34,900 INTOSC * cycles at 300 MHz PLL / 10 MHz INTOSC; keep well under that. */ #define PROBE_CNT1_SEED 0xFFFFFUL + +/* A sample taken after a DCC ERROR flag or a guard-loop timeout is not noise, + * it is a misconfigured clock mux or a stalled counter. Count both so a + * capture with any nonzero total can be rejected rather than analysed. */ +static uint32_t probeDccErrors; +static uint32_t probeDccTimeouts; +static uint32_t probeAdcTimeouts; #define PROBE_SAMPLES 1024 /* printf over SCI dominates; keep the ADC set smaller. */ #define PROBE_ADC_SAMPLES 1024 @@ -87,12 +94,19 @@ static uint32_t probe_dccSample(uint32_t base, DCC_Count0ClockSource src0, DCC_enableModule(base); - /* Bounded wait, scaled to the window, so a bad mux cannot hang. */ + /* Bounded wait, scaled to the window, so a bad mux cannot hang. Record + * why we stopped: only a clean done-signal yields a usable sample. */ for (guard = 0; guard < (window * 256UL) + 100000UL; guard++) { if (DCC_getSingleShotStatus(base) || DCC_getErrorStatus(base)) { break; } } + if (DCC_getErrorStatus(base)) { + probeDccErrors++; + } + else if (!DCC_getSingleShotStatus(base)) { + probeDccTimeouts++; + } return (PROBE_CNT1_SEED - (DCC_getCounter1Value(base) & PROBE_CNT1_SEED)); } @@ -140,6 +154,9 @@ static uint16_t probe_adcSample(void) break; } } + if (guard >= 1000000UL) { + probeAdcTimeouts++; + } return ADC_readResult(ADCCRESULT_BASE, ADC_SOC_NUMBER0); } @@ -191,7 +208,7 @@ static void probe_dumpPackedDcc(const char* tag, uint32_t base, } -static void probe_dumpPackedAdc(uint32_t nbytes) +static void probe_dumpPackedAdc(const char* tag, uint32_t nbytes) { uint32_t i; int b; @@ -199,7 +216,7 @@ static void probe_dumpPackedAdc(uint32_t nbytes) for (i = 0; i < nbytes; i++) { if ((i % 32U) == 0U) { - printf("\r\nE6 0 "); + printf("\r\n%s 0 ", tag); } acc = 0U; for (b = 0; b < 8; b++) { @@ -218,8 +235,10 @@ void entropy_probe_run(void) uint32_t i; printf("\r\n=== ENTROPY PROBE ===\r\n"); - printf("SYSCLK %lu Hz, samples/config %d\r\n", - (unsigned long)DEVICE_SYSCLK_FREQ, (int)PROBE_SAMPLES); + /* %lu, not %d: int is 16 bits here and PROBE_PACKED_BYTES is 32768. */ + printf("SYSCLK %lu Hz, samples/config %lu, packed stream %lu octets\r\n", + (unsigned long)DEVICE_SYSCLK_FREQ, (unsigned long)PROBE_SAMPLES, + (unsigned long)PROBE_PACKED_BYTES); probe_dccInit(); probe_adcInit(); @@ -244,8 +263,11 @@ void entropy_probe_run(void) DCC_COUNT1SRC_PLL, 256UL, PROBE_PACKED_BYTES); probe_dumpPackedDcc("E5", DCC0_BASE, DCC_COUNT0SRC_INTOSC2, DCC_COUNT1SRC_PLL, 256UL, PROBE_PACKED_BYTES); - probe_dumpPackedAdc(PROBE_PACKED_BYTES); + probe_dumpPackedAdc("E6", PROBE_PACKED_BYTES); + printf("\r\nDCC errors %lu, DCC timeouts %lu, ADC timeouts %lu\r\n", + (unsigned long)probeDccErrors, (unsigned long)probeDccTimeouts, + (unsigned long)probeAdcTimeouts); printf("\r\nPROBE DONE\r\n"); } diff --git a/embedded/ti-c2000-f28p55x/Source/wolf_main.c b/embedded/ti-c2000-f28p55x/Source/wolf_main.c index f6f22ed2..de0b8c3a 100644 --- a/embedded/ti-c2000-f28p55x/Source/wolf_main.c +++ b/embedded/ti-c2000-f28p55x/Source/wolf_main.c @@ -60,7 +60,9 @@ #endif #ifdef WOLF_ENTROPY #include -#include +#endif +#ifdef WOLF_ENTROPY_PROBE +#include "entropy_probe.h" #endif #ifdef WOLF_25519 #include @@ -978,7 +980,7 @@ static void wolf_mldsa87_verify_test(void) * signature that arrive PACKED verify once wc_UnpackOctets() expands them. * One key struct is shared - WOLFSSL_MLDSA_VERIFY_NO_MALLOC pins the verify * workspace inside it, far too big for the 16 KW C28x stack. */ -/* Static: an ML-DSA-65 signature is 3309 cells = 6618 octets of C28x RAM. */ +/* Static: an ML-DSA-65 signature is 3309 octets = 3309 cells = 6618 bytes. */ static byte mo_pub[WC_MLDSA_65_PUB_KEY_SIZE]; static byte mo_sig[WC_MLDSA_65_SIG_SIZE]; static wc_MlDsaKey mo_key; @@ -1030,12 +1032,23 @@ static void mo_pack_roundtrip(void) src[i] = (byte)((i * 7 + 1) & 0xFF); } ret = wc_PackOctets(packed, (word32)sizeof(packed), src, - (word32)sizeof(src)); + (word32)sizeof(src), (word32)sizeof(src)); if (ret == 0) { ret = wc_UnpackOctets(back, (word32)sizeof(back), packed, - (word32)sizeof(src)); + (word32)sizeof(packed), (word32)sizeof(src)); } + /* Round-trip alone would also pass for an identity implementation, so + * check the packed layout itself: cell 0 must carry the first + * WC_OCTETS_PER_BYTE octets, low octet first. */ ok = (ret == 0) && (XMEMCMP(src, back, sizeof(src)) == 0); + if (ok) { + word32 expect = 0; + word32 e; + for (e = 0; e < WC_OCTETS_PER_BYTE; e++) { + expect |= (word32)src[e] << (8 * e); + } + ok = ((word32)packed[0] == expect); + } printf("wc_Pack/UnpackOctets round-trip: %s (ret=%d)\r\n", ok ? "PASS" : "FAIL", ret); } @@ -1047,10 +1060,16 @@ static void mo_packed_verify(void) int ret; ret = wc_UnpackOctets(mo_pub, (word32)sizeof(mo_pub), - (const byte*)kat_mldsa65_pub_packed, (word32)sizeof(mo_pub)); + (const byte*)kat_mldsa65_pub_packed, + (word32)(sizeof(kat_mldsa65_pub_packed) / + sizeof(kat_mldsa65_pub_packed[0])), + (word32)sizeof(mo_pub)); if (ret == 0) { ret = wc_UnpackOctets(mo_sig, (word32)sizeof(mo_sig), - (const byte*)kat_mldsa65_sig_packed, (word32)sizeof(mo_sig)); + (const byte*)kat_mldsa65_sig_packed, + (word32)(sizeof(kat_mldsa65_sig_packed) / + sizeof(kat_mldsa65_sig_packed[0])), + (word32)sizeof(mo_sig)); } /* Expanded buffers must match the plain arrays octet for octet. */ if (ret == 0) { @@ -1098,8 +1117,9 @@ static void wolf_mldsa_octet_test(void) return; } - printf("octet model: CHAR_BIT=%d, %lu octet(s) per byte cell; " - "ML-DSA-65 sig = %lu octets = %lu cells = %lu bytes of RAM\r\n", + printf("octet model: CHAR_BIT=%d, one octet per byte cell, %lu octet(s) " + "per cell when packed; ML-DSA-65 sig = %lu octets = %lu cells = " + "%lu bytes of RAM\r\n", (int)CHAR_BIT, (unsigned long)WC_OCTETS_PER_BYTE, (unsigned long)WC_MLDSA_65_SIG_SIZE, (unsigned long)WC_MLDSA_65_SIG_SIZE, @@ -1134,6 +1154,36 @@ static void wolf_mldsa_octet_test(void) kat_mldsa87_sig_ph512, (word32)sizeof(kat_mldsa87_sig_ph512), WC_HASH_TYPE_SHA512, sha512, (word32)sizeof(sha512)); + /* Negative case: every check above is positive, so a verify that returned + * success unconditionally - or a w1 encoder that collapsed distinct + * commitments - would pass them all. Flip one octet of the level-44 + * signature and require a clean rejection. */ + { + static byte bad[WC_MLDSA_44_SIG_SIZE]; + int bres = 1; + int bret; + + XMEMCPY(bad, kat_mldsa44_sig, sizeof(bad)); + bad[sizeof(bad) / 2] ^= 0x01; + + bret = wc_MlDsaKey_Init(&mo_key, NULL, INVALID_DEVID); + if (bret == 0) { + bret = wc_MlDsaKey_SetParams(&mo_key, WC_ML_DSA_44); + } + if (bret == 0) { + bret = wc_MlDsaKey_ImportPubRaw(&mo_key, kat_mldsa44_pub, + (word32)sizeof(kat_mldsa44_pub)); + } + if (bret == 0) { + bret = wc_MlDsaKey_VerifyCtx(&mo_key, bad, (word32)sizeof(bad), + NULL, 0, mo_msg, (word32)sizeof(mo_msg), &bres); + } + /* A corrupt signature must be rejected, not error out. */ + printf("ML-DSA-44 corrupted-signature reject: %s (ret=%d res=%d)\r\n", + ((bret == 0) && (bres == 0)) ? "PASS" : "FAIL", bret, bres); + wc_MlDsaKey_Free(&mo_key); + } + mo_packed_verify(); } #endif /* WOLF_MLDSA_OCTETS */ @@ -1305,9 +1355,18 @@ static void wolf_entropy_test(void) printf("Entropy liveness self-test (raw): %s (ret=%d)\r\n", (ret == 0) ? "PASS" : "FAIL", ret); - /* Raw noise sanity per source: population count should sit near half. */ - for (src = 0; src < 2; src++) { + /* Raw noise sanity per source: population count should sit near half. + * Source 1 is optional - a build that needs DCC0 elsewhere sets + * WOLFSSL_C2000_ENTROPY_NUM_SRC to 1. */ + for (src = 0; src < WOLFSSL_C2000_ENTROPY_NUM_SRC; src++) { ret = wc_c2000_Entropy_GetRaw(raw, (word32)sizeof(raw), src); + if (ret != 0) { + /* raw[] holds stale data on failure, so counting it would report a + * meaningless balance. Report the read error instead. */ + printf("Entropy raw src%d bit balance: FAIL (read error %d)\r\n", + src, ret); + continue; + } ones = 0; for (i = 0; i < (word32)sizeof(raw); i++) { for (b = 0; b < 8; b++) { @@ -1319,7 +1378,7 @@ static void wolf_entropy_test(void) /* 2048 bits; accept 40%..60% ones, i.e. counts 820..1228. */ printf("Entropy raw src%d bit balance: %s (%lu/2048 ones)\r\n", src, - (ret == 0 && ones > 819UL && ones < 1229UL) ? "PASS" : "FAIL", + (ones > 819UL && ones < 1229UL) ? "PASS" : "FAIL", (unsigned long)ones); } @@ -1378,6 +1437,17 @@ static void wolf_aes_test(void) static byte o[16], o2[16], tag[16]; int r; + /* Must be initialised, and explicitly with INVALID_DEVID: a static Aes + * zero-fills devId to 0, which is a valid device id, so with WOLF_CRYPTO_CB + * built in (HWAES=1) every aes.c hook would attempt callback dispatch + * instead of skipping. This is the software reference for the HW-vs-SW + * cross-checks, so it must stay unambiguously software. */ + r = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (r != 0) { + printf("AES software test: FAIL (init ret=%d)\r\n", r); + return; + } + /* CBC */ r = wc_AesSetKey(&aes, k, 16, iv, AES_ENCRYPTION); if (r == 0) r = wc_AesCbcEncrypt(&aes, o, pt, 16); @@ -1432,11 +1502,17 @@ static void wolf_aes_test(void) * NIST SP800-38A vectors are asserted where we have them; multi-block, * split-call and in-place cases are checked hardware-against-software, since * software AES is already covered by wolfcrypt_test. */ +/* Set only when the AESA device actually registered. Without it the 'hw' + * context silently falls back to software and every cross-check would compare + * software against software and report PASS. */ +static int g_aesaReady = 0; + static void hw_report(const char* name, int r, const byte* a, const byte* b, word32 len) { - printf("HW %s: %s\r\n", name, - (r == 0 && XMEMCMP(a, b, len) == 0) ? "PASS" : "FAIL"); + int cmp = XMEMCMP(a, b, len); + printf("HW %s: %s (ret=%d cmp=%d)\r\n", name, + (r == 0 && cmp == 0) ? "PASS" : "FAIL", r, cmp); } static void wolf_aes_hw_test(void) @@ -1494,9 +1570,20 @@ static void wolf_aes_hw_test(void) static byte oh[64], os[64], dh[64]; int rh, rs; - if (wc_AesInit(&hw, NULL, WOLFSSL_C2000_DEVID) != 0 || - wc_AesInit(&sw, NULL, INVALID_DEVID) != 0) { - printf("HW AES init: FAIL\r\n"); + if (!g_aesaReady) { + printf("HW AES cross-checks: SKIP (AESA not registered)\r\n"); + return; + } + + rh = wc_AesInit(&hw, NULL, WOLFSSL_C2000_DEVID); + if (rh != 0) { + printf("HW AES init (hw ctx): FAIL (ret=%d)\r\n", rh); + return; + } + rs = wc_AesInit(&sw, NULL, INVALID_DEVID); + if (rs != 0) { + printf("HW AES init (sw ctx): FAIL (ret=%d)\r\n", rs); + wc_AesFree(&hw); return; } @@ -1506,7 +1593,7 @@ static void wolf_aes_hw_test(void) if (rh == 0) rh = wc_AesEcbEncrypt(&hw, oh, pt, 64); if (rs == 0) rs = wc_AesEcbEncrypt(&sw, os, pt, 64); hw_report("AES-128-ECB encrypt vs NIST", rh, oh, ecb_ct1, 16); - hw_report("AES-128-ECB encrypt vs SW", (rh | rs), oh, os, 64); + hw_report("AES-128-ECB encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); rh = wc_AesSetKey(&hw, k128, 16, NULL, AES_DECRYPTION); if (rh == 0) rh = wc_AesEcbDecrypt(&hw, dh, oh, 64); @@ -1518,7 +1605,7 @@ static void wolf_aes_hw_test(void) if (rh == 0) rh = wc_AesCbcEncrypt(&hw, oh, pt, 64); if (rs == 0) rs = wc_AesCbcEncrypt(&sw, os, pt, 64); hw_report("AES-128-CBC encrypt vs NIST", rh, oh, cbc_ct1, 16); - hw_report("AES-128-CBC encrypt vs SW", (rh | rs), oh, os, 64); + hw_report("AES-128-CBC encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); rh = wc_AesSetKey(&hw, k128, 16, iv, AES_DECRYPTION); if (rh == 0) rh = wc_AesCbcDecrypt(&hw, dh, oh, 64); @@ -1528,7 +1615,7 @@ static void wolf_aes_hw_test(void) rh = wc_AesSetKey(&hw, k128, 16, iv, AES_ENCRYPTION); if (rh == 0) rh = wc_AesCbcEncrypt(&hw, oh, pt, 16); if (rh == 0) rh = wc_AesCbcEncrypt(&hw, oh + 16, pt + 16, 48); - hw_report("AES-128-CBC split-call chain", (rh | rs), oh, os, 64); + hw_report("AES-128-CBC split-call chain", rh, oh, os, 64); /* ---- CBC in-place decrypt: proves the last-block save ---- */ XMEMCPY(dh, os, 64); @@ -1543,26 +1630,53 @@ static void wolf_aes_hw_test(void) if (rs == 0) rs = wc_AesCtrEncrypt(&sw, os, pt, 64); hw_report("AES-128-CTR vs NIST (64B)", rh, oh, ctr_ct, 64); hw_report("AES-128-CTR SW vs NIST (64B)", rs, os, ctr_ct, 64); - hw_report("AES-128-CTR vs SW", (rh | rs), oh, os, 64); + hw_report("AES-128-CTR vs SW", ((rh != 0) ? rh : rs), oh, os, 64); /* ---- CTR split at a non-block boundary: proves aes->left/aes->tmp ---- */ rh = wc_AesSetKey(&hw, k128, 16, ctr_iv, AES_ENCRYPTION); if (rh == 0) rh = wc_AesCtrEncrypt(&hw, oh, pt, 10); if (rh == 0) rh = wc_AesCtrEncrypt(&hw, oh + 10, pt + 10, 54); - hw_report("AES-128-CTR partial split", (rh | rs), oh, os, 64); + hw_report("AES-128-CTR partial split", rh, oh, os, 64); /* ---- 192- and 256-bit keys: the 6- and 8-word AES_setKey1 paths ---- */ rh = wc_AesSetKey(&hw, k192, 24, iv, AES_ENCRYPTION); rs = wc_AesSetKey(&sw, k192, 24, iv, AES_ENCRYPTION); if (rh == 0) rh = wc_AesCbcEncrypt(&hw, oh, pt, 64); if (rs == 0) rs = wc_AesCbcEncrypt(&sw, os, pt, 64); - hw_report("AES-192-CBC encrypt vs SW", (rh | rs), oh, os, 64); + hw_report("AES-192-CBC encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); rh = wc_AesSetKey(&hw, k256, 32, iv, AES_ENCRYPTION); rs = wc_AesSetKey(&sw, k256, 32, iv, AES_ENCRYPTION); if (rh == 0) rh = wc_AesCbcEncrypt(&hw, oh, pt, 64); if (rs == 0) rs = wc_AesCbcEncrypt(&sw, os, pt, 64); - hw_report("AES-256-CBC encrypt vs SW", (rh | rs), oh, os, 64); + hw_report("AES-256-CBC encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); + + /* ECB and CTR at 192/256 too: the accelerator's key schedule differs per + * key size, and marshalling the longer schedule into its 32-bit registers + * is exactly the octet handling this port is validating. */ + rh = wc_AesSetKey(&hw, k192, 24, NULL, AES_ENCRYPTION); + rs = wc_AesSetKey(&sw, k192, 24, NULL, AES_ENCRYPTION); + if (rh == 0) rh = wc_AesEcbEncrypt(&hw, oh, pt, 64); + if (rs == 0) rs = wc_AesEcbEncrypt(&sw, os, pt, 64); + hw_report("AES-192-ECB encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); + + rh = wc_AesSetKey(&hw, k256, 32, NULL, AES_ENCRYPTION); + rs = wc_AesSetKey(&sw, k256, 32, NULL, AES_ENCRYPTION); + if (rh == 0) rh = wc_AesEcbEncrypt(&hw, oh, pt, 64); + if (rs == 0) rs = wc_AesEcbEncrypt(&sw, os, pt, 64); + hw_report("AES-256-ECB encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); + + rh = wc_AesSetKey(&hw, k192, 24, ctr_iv, AES_ENCRYPTION); + rs = wc_AesSetKey(&sw, k192, 24, ctr_iv, AES_ENCRYPTION); + if (rh == 0) rh = wc_AesCtrEncrypt(&hw, oh, pt, 64); + if (rs == 0) rs = wc_AesCtrEncrypt(&sw, os, pt, 64); + hw_report("AES-192-CTR encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); + + rh = wc_AesSetKey(&hw, k256, 32, ctr_iv, AES_ENCRYPTION); + rs = wc_AesSetKey(&sw, k256, 32, ctr_iv, AES_ENCRYPTION); + if (rh == 0) rh = wc_AesCtrEncrypt(&hw, oh, pt, 64); + if (rs == 0) rs = wc_AesCtrEncrypt(&sw, os, pt, 64); + hw_report("AES-256-CTR encrypt vs SW", ((rh != 0) ? rh : rs), oh, os, 64); wc_AesFree(&hw); wc_AesFree(&sw); @@ -1964,12 +2078,13 @@ static void wolf_aes_modes_test(void) #ifdef WOLFSSL_AES_OFB { static Aes oaes; - r = wc_AesSetKey(&oaes, mk, 16, miv, AES_ENCRYPTION); + r = wc_AesInit(&oaes, NULL, INVALID_DEVID); + if (r == 0) r = wc_AesSetKey(&oaes, mk, 16, miv, AES_ENCRYPTION); if (r == 0) r = wc_AesOfbEncrypt(&oaes, mct, mpt, 32); if (r == 0) r = wc_AesSetKey(&oaes, mk, 16, miv, AES_ENCRYPTION); if (r == 0) r = wc_AesOfbDecrypt(&oaes, mdec, mct, 32); - printf("AES-128-OFB round-trip: %s\r\n", - (r == 0 && XMEMCMP(mdec, mpt, 32) == 0) ? "PASS":"FAIL"); + printf("AES-128-OFB round-trip: %s (ret=%d)\r\n", + (r == 0 && XMEMCMP(mdec, mpt, 32) == 0) ? "PASS":"FAIL", r); wc_AesFree(&oaes); } #endif @@ -2147,12 +2262,12 @@ int main(void) printf("=== wolfSSL wolfCrypt on TI C2000 LAUNCHXL-F28P55X ===\r\n"); #ifdef WOLF_ENTROPY_PROBE - /* Measurement-only image: dump raw entropy samples and stop. */ - { - extern void entropy_probe_run(void); - entropy_probe_run(); - } - while (1) { + /* Measurement-only image: dump raw entropy samples and stop. Nothing + * after this runs, which is why the Makefile rejects combining + * ENTROPY_PROBE=1 with the other image toggles. */ + entropy_probe_run(); + for (;;) { + /* spin */ } #endif @@ -2173,6 +2288,7 @@ int main(void) } else { printf("C2000 AESA init: PASS\r\n"); + g_aesaReady = 1; } #endif diff --git a/embedded/ti-c2000-f28p55x/tools/entropy_analyze.py b/embedded/ti-c2000-f28p55x/tools/entropy_analyze.py index 65493aa3..c113c13c 100755 --- a/embedded/ti-c2000-f28p55x/tools/entropy_analyze.py +++ b/embedded/ti-c2000-f28p55x/tools/entropy_analyze.py @@ -30,7 +30,6 @@ an upper bound, and low measured correlation is what makes it a reasonable one. Usage: python3 tools/entropy_analyze.py capture.log python3 tools/entropy_analyze.py --selftest - tail -f /tmp/uart-monitor/latest/ttyACMx.log | python3 tools/entropy_analyze.py - """ import math @@ -56,15 +55,22 @@ RAW = OrderedDict(( )) BANNER = "=== ENTROPY PROBE ===" +# The probe reports how many samples were taken after a DCC ERROR flag or a +# guard-loop timeout. Any nonzero count means the capture is not pure noise. +HEALTH_RE = re.compile(r"DCC errors (\d+), DCC timeouts (\d+), " + r"ADC timeouts (\d+)") DONE = "PROBE DONE" MAX_LAG = 64 +# Below this the MCV bound is too wide to be worth reporting. +MIN_PACKED_OCTETS = 256 Z_99 = 2.5758293035489004 # two-sided 99% normal quantile def first_pass(text): - """The board loops main(), so the probe output repeats. Return just the - first complete pass, so a long capture does not silently concatenate - several runs into one sample set.""" + """Return just the first complete probe pass. The probe image runs once + and then spins, so a normal capture holds one pass - but a capture that + spans a reset or a re-flash would otherwise concatenate several runs into + one sample set.""" start = text.find(BANNER) if start < 0: return text @@ -77,21 +83,41 @@ def first_pass(text): def parse(text): - """tag -> list of ints, in emission order.""" + """Returns (out, per_window). + + out tag -> list of ints, in emission order (all windows merged). + per_window tag -> {window: [ints]}. + + The raw DCC tags are emitted once per sweep window (256/1024/4096), and a + count scales with the window, so merging them would mix populations of + different magnitude. The packed streams the min-entropy estimate uses are + emitted once, at a single window, so out[] is correct for those.""" out = {} + per_window = {} # A console line is " ...". Match per line, and # never across a newline: the tags and window counts are themselves valid # hex, so a multi-line match would swallow the next line's header as data. # Tolerate any timestamp or prefix a log wrapper put ahead of the tag. line_re = re.compile(r"\b(E[0-9])[^\S\n]+(\d+)[^\S\n]+" r"((?:[0-9a-fA-F]+[^\S\n]*)+)$") + skipped = 0 for line in text.splitlines(): - m = line_re.search(line.rstrip()) + line = line.rstrip() + m = line_re.search(line) if m is None: + # A line that starts with a probe tag but does not parse means a + # corrupted capture, not unrelated console output - say so. + if re.match(r"\s*E[0-9]\b", line): + skipped += 1 continue - out.setdefault(m.group(1), []).extend( - int(t, 16) for t in m.group(3).split()) - return out + vals = [int(t, 16) for t in m.group(3).split()] + out.setdefault(m.group(1), []).extend(vals) + per_window.setdefault(m.group(1), {}).setdefault( + int(m.group(2)), []).extend(vals) + if skipped: + sys.stderr.write("warning: %d probe line(s) did not parse - capture may " + "be corrupted or truncated\n" % skipped) + return out, per_window def unpack_bits(octets): @@ -266,7 +292,16 @@ def main(): with open(path, errors="replace") as f: text = f.read() - data = parse(first_pass(text)) + health = HEALTH_RE.search(text) + if health is None: + sys.stderr.write("warning: no probe health line - old probe image, or " + "the capture is truncated\n") + elif any(int(g) for g in health.groups()): + sys.exit("probe reported %s DCC errors, %s DCC timeouts, %s ADC " + "timeouts - these samples are not noise, reject the capture" + % health.groups()) + + data, per_window = parse(first_pass(text)) if not data: sys.exit("no probe tags found - is this an ENTROPY_PROBE=1 capture?") @@ -276,6 +311,10 @@ def main(): if not octets: sys.stderr.write("warning: no %s samples (%s)\n" % (tag, label)) continue + if len(octets) < MIN_PACKED_OCTETS: + sys.stderr.write("warning: %s has only %d octets (< %d) - the " + "min-entropy estimate will be unreliable\n" + % (tag, len(octets), MIN_PACKED_OCTETS)) bad = [v for v in octets if v > 0xFF] if bad: sys.exit("%s: %d values exceed one octet - capture is corrupt" @@ -283,11 +322,15 @@ def main(): rows.append(report(tag, label, octets)) for tag, label in RAW.items(): - vals = data.get(tag) - if vals: - a = np.asarray(vals, dtype=np.int64) - print("%s %s: %d samples, min %d max %d mean %.1f, " - "%d distinct" % (tag, label, len(a), a.min(), a.max(), + wins = per_window.get(tag) + if not wins: + continue + # Per window: a raw count scales with the window, so pooling them + # would report a spread that is an artifact of the sweep. + for win in sorted(wins): + a = np.asarray(wins[win], dtype=np.int64) + print("%s %s [window %d]: %d samples, min %d max %d mean %.1f, " + "%d distinct" % (tag, label, win, len(a), a.min(), a.max(), a.mean(), len(np.unique(a)))) print() diff --git a/embedded/ti-c2000-f28p55x/tools/gen_kat.c b/embedded/ti-c2000-f28p55x/tools/gen_kat.c index 868a6a94..993d8009 100644 --- a/embedded/ti-c2000-f28p55x/tools/gen_kat.c +++ b/embedded/ti-c2000-f28p55x/tools/gen_kat.c @@ -32,7 +32,6 @@ #include #include #include -#include #define MSG_SZ 512 #define OUT_NAME "mldsa_octet_kat.h" @@ -167,17 +166,25 @@ int main(void) for (i = 0; i < MSG_SZ; i++) msg[i] = (byte)(i & 0xFF); + ret = wolfCrypt_Init(); + if (ret != 0) { + fprintf(stderr, "wolfCrypt_Init failed: %d\n", ret); + return 1; + } + ret = wc_Sha256Hash(msg, MSG_SZ, sha256); if (ret == 0) ret = wc_Sha512Hash(msg, MSG_SZ, sha512); if (ret != 0) { fprintf(stderr, "hash failed: %d\n", ret); + wolfCrypt_Cleanup(); return 1; } out = fopen(OUT_NAME, "w"); if (out == NULL) { fprintf(stderr, "cannot open output\n"); + wolfCrypt_Cleanup(); return 1; } @@ -205,7 +212,9 @@ int main(void) if (ret != 0) { /* Do not leave a truncated header behind for the build to pick up. */ remove(OUT_NAME); + wolfCrypt_Cleanup(); return 1; } + wolfCrypt_Cleanup(); return 0; }