23#include "cdc_spi_lock.h"
25#include "esp_random.h"
27#include "freertos/FreeRTOS.h"
28#include "freertos/task.h"
29#include "driver/spi_master.h"
30#include "psa/crypto.h"
36#include "libtropic_common.h"
37#include "libtropic_l2.h"
38#include "libtropic_l3.h"
39#include "libtropic_port.h"
40#include "lt_l2_api_structs.h"
41#include "libtropic_mbedtls_v4.h"
43static const char*
TAG =
"TR01";
60 bool start()
override;
63 const char*
getName()
const override {
return "secure_element"; }
68 bool isSessionActive()
const override {
return sessionActive_.load(std::memory_order_acquire); }
69 void sleep()
override;
78 bool getFwVersion(uint8_t riscvVer[4], uint8_t spectVer[4])
override;
83 uint8_t* sig,
size_t* sigLen)
override;
85 uint8_t* sig)
override;
89 uint16_t* actualLen)
override;
95 const uint8_t* payload, uint16_t payloadLen)
override;
97 uint8_t* payloadOut, uint16_t payloadMax,
98 uint16_t* payloadLenOut)
override;
101 bool getRandom(uint8_t* buffer, uint16_t size)
override;
105 bool getChipId(uint8_t* serialNum, uint8_t size)
override;
113 bool sessionStart_unlocked();
114 bool ensureSession_unlocked(
const char* op);
115 SeResult rmemRead_unlocked(uint16_t slot, uint8_t* data, uint16_t maxLen, uint16_t* actualLen);
116 SeResult rmemWrite_unlocked(uint16_t slot,
const uint8_t* data, uint16_t len);
117 SeResult rmemErase_unlocked(uint16_t slot);
119 void dumpChipStatus_unlocked(
const char* context);
120 bool ensureRConfigBits(lt_config_obj_addr_t addr, uint32_t mask,
bool set);
121 lt_ret_t randomChunked_unlocked(uint8_t* buffer, uint16_t size);
123 SeResult mapResult(lt_ret_t ret)
const;
124 void handleSessionError(lt_ret_t ret);
125 uint8_t computeHeaderChecksum(
const RMemHeader& header)
const;
126 bool validateHeader(
const RMemHeader& header)
const;
129 std::atomic<bool> sessionActive_{
false};
132 lt_handle_t handle_ = {};
133 lt_ctx_mbedtls_v4_t cryptoCtx_ = {};
134 lt_dev_esp32_t device_ = {};
138 mutable std::atomic<uint32_t> eccSlotCache_{0};
139 mutable std::atomic<bool> eccCacheValid_{
false};
156 LOG_I(
TAG,
"Initializing TROPIC01...");
158 psa_status_t psaStatus = psa_crypto_init();
159 if (psaStatus != PSA_SUCCESS && psaStatus != PSA_ERROR_BAD_STATE) {
160 LOG_E(
TAG,
"PSA crypto init failed (status=%ld)", (
long)psaStatus);
164 "PSA crypto init failed");
167 LOG_I(
TAG,
"PSA crypto initialized");
169 memset(&handle_, 0,
sizeof(handle_));
170 device_.cs_pin =
static_cast<gpio_num_t
>(
TR01_CS_PIN);
171 device_.spi =
nullptr;
172 handle_.l2.device = &device_;
173 handle_.l3.crypto_ctx = &cryptoCtx_;
175 lt_ret_t ret = lt_init(&handle_);
177 LOG_E(
TAG,
"lt_init failed (%s)", lt_ret_verbose(ret));
180 ret == LT_L1_CHIP_ALARM_MODE
183 lt_ret_verbose(ret));
187 uint16_t slotSize = handle_.tr01_attrs.r_mem_udata_slot_size_max;
190 rmemSlotSize_ = slotSize;
194 LOG_I(
TAG,
"TROPIC01 initialized (CS=GPIO%d, R-Mem slot=%u B)",
216 if (sessionActive_.load(std::memory_order_acquire)) {
227bool Tropic01Element::acquireBus() {
229 LOG_E(
TAG,
"acquireBus: SPI device not initialized");
232 "SPI device handle null");
241 SemaphoreHandle_t spiLock = cdc::hal::sharedSpiLock();
243 xSemaphoreTakeRecursive(spiLock, portMAX_DELAY);
248 esp_err_t err = spi_device_acquire_bus(device_.
spi, portMAX_DELAY);
250 LOG_E(
TAG,
"acquireBus failed: %d", err);
252 xSemaphoreGiveRecursive(spiLock);
256 "SPI bus acquire failed");
265void Tropic01Element::releaseBus() {
267 spi_device_release_bus(device_.spi);
269 SemaphoreHandle_t spiLock = cdc::hal::sharedSpiLock();
271 xSemaphoreGiveRecursive(spiLock);
282 if (sessionActive_.load(std::memory_order_acquire)) {
288 bool ok = sessionStart_unlocked();
304bool Tropic01Element::ensureRConfigBits(lt_config_obj_addr_t addr, uint32_t mask,
bool set) {
306 if (lt_r_config_read(&handle_, addr, &cfg) != LT_OK) {
309 const uint32_t desired = set ? (cfg | mask) : (cfg & ~mask);
310 if (desired == cfg) {
313 return lt_r_config_write(&handle_, addr, desired) == LT_OK;
319bool Tropic01Element::sessionStart_unlocked() {
320 if (sessionActive_.load(std::memory_order_acquire)) {
323 LOG_I(
TAG,
"Starting secure session...");
325 lt_ret_t ret = lt_verify_chip_and_start_secure_session(
329 LOG_E(
TAG,
"Secure session failed (%s)", lt_ret_verbose(ret));
330 static int64_t lastDumpUs = 0;
331 int64_t nowUs = esp_timer_get_time();
332 if (nowUs - lastDumpUs > 1000000) {
334 dumpChipStatus_unlocked(
"sessionStart");
340 ret == LT_L1_CHIP_ALARM_MODE
343 lt_ret_verbose(ret));
344 sessionActive_.store(
false, std::memory_order_release);
348 sessionActive_.store(
true, std::memory_order_release);
349 eccCacheValid_.store(
false, std::memory_order_release);
351 if (ensureRConfigBits(TR01_CFG_SLEEP_MODE_ADDR, 0x01u,
true)) {
357 if (ensureRConfigBits(TR01_CFG_START_UP_ADDR,
358 BOOTLOADER_CO_CFG_START_UP_MAINTENANCE_ENA_MASK,
false)) {
359 LOG_I(
TAG,
"TROPIC01 maintenance mode disabled (R-Config)");
362 LOG_I(
TAG,
"Secure session active");
370 if (!sessionActive_.load(std::memory_order_acquire)) {
374 sessionActive_.store(
false, std::memory_order_release);
377 lt_session_abort(&handle_);
378 sessionActive_.store(
false, std::memory_order_release);
388bool Tropic01Element::ensureSession_unlocked(
const char* op) {
389 if (sessionActive_.load(std::memory_order_acquire)) {
392 LOG_W(
TAG,
"Session inactive for %s - restarting", op);
393 return sessionStart_unlocked();
403 if (!sessionActive_.load(std::memory_order_acquire)) {
409 lt_ret_t ret = lt_sleep(&handle_, TR01_L2_SLEEP_KIND_SLEEP);
411 LOG_E(
TAG,
"Sleep failed (%s)", lt_ret_verbose(ret));
412 handleSessionError(ret);
414 sessionActive_.store(
false, std::memory_order_release);
423void Tropic01Element::handleSessionError(lt_ret_t ret) {
425 case LT_L1_CHIP_ALARM_MODE:
426 sessionActive_.store(
false, std::memory_order_release);
429 lt_ret_verbose(ret));
432 case LT_L2_NO_SESSION:
434 sessionActive_.store(
false, std::memory_order_release);
444SeResult Tropic01Element::mapResult(lt_ret_t ret)
const {
450 case LT_L3_R_MEM_DATA_READ_SLOT_EMPTY:
451 case LT_L3_SLOT_EMPTY:
453 case LT_L3_SLOT_NOT_EMPTY:
455 case LT_HOST_NO_SESSION:
456 case LT_L2_NO_SESSION:
458 case LT_L1_CHIP_ALARM_MODE:
469void Tropic01Element::dumpChipStatus_unlocked(
const char* context) {
471 LOG_E(
TAG,
"[%s] CHIP_STATUS read skipped: SPI not initialized", context);
475 handle_.l2.buff[0] = 0xAA;
479 LOG_E(
TAG,
"[%s] CHIP_STATUS read: CS-low failed (%d)", context, (
int)ret);
487 LOG_E(
TAG,
"[%s] CHIP_STATUS read: SPI transfer failed (%d)", context, (
int)ret);
491 uint8_t status = handle_.l2.buff[0];
492 LOG_E(
TAG,
"[%s] CHIP_STATUS=0x%02X (READY=%d ALARM=%d STARTUP=%d)",
494 (status & 0x01) ? 1 : 0,
495 (status & 0x02) ? 1 : 0,
496 (status & 0x04) ? 1 : 0);
498 if (status == 0xFF) {
499 LOG_E(
TAG,
"[%s] All-ones MISO: chip not responding (wiring/power/CS)", context);
500 }
else if (status == 0x00) {
501 LOG_E(
TAG,
"[%s] All-zeros MISO: chip not driving (no power or stuck)", context);
502 }
else if (status & 0x02) {
503 LOG_E(
TAG,
"[%s] Real ALARM mode: chip set ALARM bit (tamper/violation)", context);
518 if (!ensureSession_unlocked(
"eccGenerate")) {
522 TR01_CURVE_ED25519 : TR01_CURVE_P256;
523 lt_ret_t ret = lt_ecc_key_generate(&handle_,
static_cast<lt_ecc_slot_t
>(slot), ltCurve);
525 eccSlotCache_.fetch_or(1u << slot, std::memory_order_release);
527 handleSessionError(ret);
528 result = mapResult(ret);
545 if (!ensureSession_unlocked(
"eccImport")) {
549 TR01_CURVE_ED25519 : TR01_CURVE_P256;
550 lt_ret_t ret = lt_ecc_key_store(&handle_,
static_cast<lt_ecc_slot_t
>(slot), ltCurve, privKey);
552 eccSlotCache_.fetch_or(1u << slot, std::memory_order_release);
554 handleSessionError(ret);
555 result = mapResult(ret);
565 if (!ensureSession_unlocked(
"eccGetPublicKey")) {
568 lt_ecc_curve_type_t ltCurve;
569 lt_ecc_key_origin_t ltOrigin;
570 lt_ret_t ret = lt_ecc_key_read(&handle_,
static_cast<lt_ecc_slot_t
>(slot),
571 pubKey, 64, <Curve, <Origin);
572 if (ret == LT_OK &&
curve) {
575 handleSessionError(ret);
576 if (ret == LT_L3_INVALID_KEY) {
579 return mapResult(ret);
591 SeResult result = eccGetPublicKey_unlocked(slot, pubKey,
curve);
607 if (!ensureSession_unlocked(
"eccDelete")) {
610 lt_ret_t ret = lt_ecc_key_erase(&handle_,
static_cast<lt_ecc_slot_t
>(slot));
612 eccSlotCache_.fetch_and(~(1u << slot), std::memory_order_release);
614 handleSessionError(ret);
615 result = mapResult(ret);
628 if (eccCacheValid_.load(std::memory_order_acquire)) {
629 return (eccSlotCache_.load(std::memory_order_acquire) & (1u << slot)) != 0;
633 return self->eccGetPublicKey(slot, tempKey,
nullptr) ==
SeResult::OK;
643 uint8_t* sig,
size_t* sigLen) {
645 if (slot >=
ECC_SLOT_COUNT || !msg || msgLen == 0 || !sig || !sigLen) {
650 size_t digestLen = 0;
651 if (psa_hash_compute(PSA_ALG_SHA_256, msg, msgLen, digest,
sizeof(digest),
652 &digestLen) != PSA_SUCCESS ||
653 digestLen !=
sizeof(digest)) {
660 if (!ensureSession_unlocked(
"ecdsaSign")) {
663 lt_ret_t ret = lt_ecc_ecdsa_sign(&handle_,
static_cast<lt_ecc_slot_t
>(slot),
664 digest,
static_cast<uint32_t
>(
sizeof(digest)), sig);
666 *sigLen = TR01_ECDSA_EDDSA_SIGNATURE_LENGTH;
668 handleSessionError(ret);
669 result = mapResult(ret);
687 if (!ensureSession_unlocked(
"eddsaSign")) {
690 lt_ret_t ret = lt_ecc_eddsa_sign(&handle_,
static_cast<lt_ecc_slot_t
>(slot),
691 msg,
static_cast<uint16_t
>(msgLen), sig);
692 handleSessionError(ret);
693 result = mapResult(ret);
702SeResult Tropic01Element::rmemRead_unlocked(uint16_t slot, uint8_t* data, uint16_t maxLen,
703 uint16_t* actualLen) {
704 if (!ensureSession_unlocked(
"rmemRead")) {
707 uint16_t bytesRead = 0;
708 lt_ret_t ret = lt_r_mem_data_read(&handle_, slot, data, maxLen, &bytesRead);
709 if (ret == LT_OK && actualLen) {
710 *actualLen = bytesRead;
712 handleSessionError(ret);
713 if (ret == LT_L3_R_MEM_DATA_READ_SLOT_EMPTY) {
714 if (actualLen) *actualLen = 0;
717 return mapResult(ret);
724 uint16_t* actualLen) {
730 SeResult result = rmemRead_unlocked(slot, data, maxLen, actualLen);
738SeResult Tropic01Element::rmemWrite_unlocked(uint16_t slot,
const uint8_t* data, uint16_t len) {
739 if (!ensureSession_unlocked(
"rmemWrite")) {
742 lt_ret_t ret = lt_r_mem_data_write(&handle_, slot, data, len);
743 handleSessionError(ret);
744 return mapResult(ret);
752 if (slot >=
RMEM_SLOT_COUNT || !data || len == 0 || len > rmemSlotSize_) {
756 SeResult result = rmemWrite_unlocked(slot, data, len);
764SeResult Tropic01Element::rmemErase_unlocked(uint16_t slot) {
765 if (!ensureSession_unlocked(
"rmemErase")) {
768 lt_ret_t ret = lt_r_mem_data_erase(&handle_, slot);
769 handleSessionError(ret);
770 return mapResult(ret);
782 SeResult result = rmemErase_unlocked(slot);
796 uint16_t actualLen = 0;
797 SeResult res = self->rmemRead(slot, tempBuf,
sizeof(tempBuf), &actualLen);
804uint8_t Tropic01Element::computeHeaderChecksum(
const RMemHeader& header)
const {
806 sum += header.moduleId;
808 for (
size_t i = 0; i <
sizeof(header.name); i++) {
809 sum +=
static_cast<uint8_t
>(header.name[i]);
811 sum +=
static_cast<uint8_t
>(header.payloadLen & 0xFF);
812 sum +=
static_cast<uint8_t
>((header.payloadLen >> 8) & 0xFF);
813 return static_cast<uint8_t
>(sum & 0xFF);
819bool Tropic01Element::validateHeader(
const RMemHeader& header)
const {
823 return header.checksum == computeHeaderChecksum(header);
831 const uint8_t* payload, uint16_t payloadLen) {
836 if (payloadLen > (rmemSlotSize_ -
sizeof(RMemHeader))) {
842 SeResult eraseRes = rmemErase_unlocked(slot);
846 RMemHeader header = {};
849 header.flags =
flags;
850 header.payloadLen = payloadLen;
852 strncpy(header.name,
name,
sizeof(header.name) - 1);
853 header.name[
sizeof(header.name) - 1] =
'\0';
855 header.checksum = computeHeaderChecksum(header);
858 memcpy(buffer, &header,
sizeof(header));
859 if (payloadLen > 0 && payload) {
860 memcpy(buffer +
sizeof(header), payload, payloadLen);
863 result = rmemWrite_unlocked(slot, buffer,
static_cast<uint16_t
>(
sizeof(header) + payloadLen));
873 uint8_t* payloadOut, uint16_t payloadMax,
874 uint16_t* payloadLenOut) {
883 uint16_t actualLen = 0;
884 SeResult res = rmemRead_unlocked(slot, buffer,
sizeof(buffer), &actualLen);
887 }
else if (actualLen <
sizeof(RMemHeader)) {
890 RMemHeader header = {};
891 memcpy(&header, buffer,
sizeof(header));
892 if (!validateHeader(header)) {
894 }
else if (header.payloadLen > (rmemSlotSize_ -
sizeof(RMemHeader))) {
896 }
else if (actualLen <
static_cast<uint16_t
>(
sizeof(RMemHeader) + header.payloadLen)) {
898 }
else if (payloadOut && header.payloadLen > 0 && payloadMax < header.payloadLen) {
905 *payloadLenOut = header.payloadLen;
907 if (payloadOut && header.payloadLen > 0) {
908 memcpy(payloadOut, buffer +
sizeof(RMemHeader), header.payloadLen);
924lt_ret_t Tropic01Element::randomChunked_unlocked(uint8_t* buffer, uint16_t size) {
926 while (offset < size) {
927 const uint16_t remaining = size - offset;
928 const uint8_t chunk =
static_cast<uint8_t
>(remaining > 255 ? 255 : remaining);
929 lt_ret_t ret = lt_random_value_get(&handle_, buffer + offset, chunk);
946 if (!buffer || size == 0) {
950 LOG_W(
TAG,
"SystemLock active, ESP32 TRNG fallback (size=%u)", size);
951 esp_fill_random(buffer, size);
955 LOG_W(
TAG,
"Bus unavailable, ESP32 TRNG fallback (size=%u)", size);
956 esp_fill_random(buffer, size);
960 if (!ensureSession_unlocked(
"getRandom")) {
961 LOG_W(
TAG,
"No SE session, ESP32 TRNG fallback (size=%u)", size);
962 esp_fill_random(buffer, size);
964 lt_ret_t ret = randomChunked_unlocked(buffer, size);
965 handleSessionError(ret);
967 LOG_W(
TAG,
"TROPIC01 TRNG failed (%s), ESP32 TRNG fallback (size=%u)",
968 lt_ret_verbose(ret), size);
969 esp_fill_random(buffer, size);
980 if (!buffer || size == 0) {
990 if (ensureSession_unlocked(
"getRandomStrict")) {
991 lt_ret_t ret = randomChunked_unlocked(buffer, size);
992 handleSessionError(ret);
1004 if (!serialNum || size < 8) {
1007 if (!acquireBus())
return false;
1010 if (ensureSession_unlocked(
"getChipId")) {
1011 struct lt_chip_id_t chipId;
1012 memset(&chipId, 0,
sizeof(chipId));
1013 lt_ret_t ret = lt_get_info_chip_id(&handle_, &chipId);
1015 uint8_t copyLen = (size <
sizeof(chipId.ser_num)) ? size :
sizeof(chipId.ser_num);
1016 memcpy(serialNum, &chipId.ser_num, copyLen);
1019 handleSessionError(ret);
1030 if (!riscvVer || !spectVer) {
1033 if (!acquireBus())
return false;
1036 if (ensureSession_unlocked(
"getFwVersion")) {
1037 uint8_t riscvFw[TR01_L2_GET_INFO_RISCV_FW_SIZE] = {0};
1038 lt_ret_t ret = lt_get_info_riscv_fw_ver(&handle_, riscvFw);
1040 for (
int i = 0; i < 4; i++) riscvVer[i] = riscvFw[i];
1042 uint8_t spectFw[TR01_L2_GET_INFO_SPECT_FW_SIZE] = {0};
1043 lt_ret_t ret2 = lt_get_info_spect_fw_ver(&handle_, spectFw);
1044 if (ret2 == LT_OK) {
1045 for (
int i = 0; i < 4; i++) spectVer[i] = spectFw[i];
1047 handleSessionError(ret == LT_OK ? ret2 : ret);
1048 ok = (ret == LT_OK) && (ret2 == LT_OK);
static constexpr uint8_t RMEM_HEADER_MAGIC
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
void triggerLockdown(LockdownReason reason, const char *detail=nullptr)
Latches the lockdown flag. Idempotent and ISR-safe.
static SystemLock & instance()
Returns the process-wide lockdown latch singleton.
static constexpr uint16_t RMEM_SLOT_SIZE_MAX
static constexpr uint16_t RMEM_SLOT_COUNT
static constexpr uint16_t RMEM_SLOT_SIZE
static constexpr uint8_t ECC_SLOT_COUNT
Secure-element implementation backed by libtropic.
SeResult rmemReadWithHeader(uint16_t slot, RMemHeader *headerOut, uint8_t *payloadOut, uint16_t payloadMax, uint16_t *payloadLenOut) override
Reads and validates headered R-memory record.
SeResult rmemErase(uint16_t slot) override
Erases one R-memory slot.
void stop() override
Stops secure-element service and closes any active session.
bool getRandomStrict(uint8_t *buffer, uint16_t size) override
Fills buffer with random bytes from TROPIC TRNG only; no fallback.
Tropic01Element()=default
bool getChipId(uint8_t *serialNum, uint8_t size) override
Reads chip serial identifier.
SeResult ecdsaSign(uint8_t slot, const uint8_t *msg, size_t msgLen, uint8_t *sig, size_t *sigLen) override
Signs a message using ECDSA key in slot.
bool sessionStart() override
Opens a secure session with the TROPIC01 chip.
void sessionEnd() override
Aborts the active secure session.
uint16_t getRmemSlotSize() const override
bool getFwVersion(uint8_t riscvVer[4], uint8_t spectVer[4]) override
Reads RISC-V and SPECT firmware major version bytes.
bool rmemSlotUsed(uint16_t slot) const override
Checks whether R-memory slot contains data.
SeResult eccGenerate(uint8_t slot, EccCurve curve) override
Generates an ECC key pair in the requested slot.
SeResult rmemWrite(uint16_t slot, const uint8_t *data, uint16_t len) override
Writes raw data to an R-memory slot.
bool isSessionActive() const override
SeResult eccDelete(uint8_t slot) override
Erases ECC key material from slot.
SeResult eddsaSign(uint8_t slot, const uint8_t *msg, size_t msgLen, uint8_t *sig) override
Signs message using EdDSA key in slot.
SeResult rmemWriteWithHeader(uint16_t slot, uint8_t moduleId, const char *name, uint8_t flags, const uint8_t *payload, uint16_t payloadLen) override
Writes payload to R-memory slot with metadata header.
bool eccSlotUsed(uint8_t slot) const override
Checks whether ECC slot currently contains a key.
SeResult rmemRead(uint16_t slot, uint8_t *data, uint16_t maxLen, uint16_t *actualLen) override
Reads raw R-memory slot data.
void sleep() override
Requests secure-element sleep mode and marks session inactive.
const char * getName() const override
bool init() override
Initializes PSA crypto and the libtropic device context.
SeResult eccImport(uint8_t slot, const uint8_t *privKey, EccCurve curve) override
Imports an ECC private key into the requested slot.
bool getRandom(uint8_t *buffer, uint16_t size) override
Fills buffer with random bytes from TROPIC TRNG with ESP fallback.
SeResult eccGetPublicKey(uint8_t slot, uint8_t *pubKey, EccCurve *curve) override
Reads public key from ECC slot.
core::ServiceState getState() const override
bool start() override
Starts secure-element service when initialized.
lt_ret_t lt_port_spi_csn_high(lt_l2_state_t *s2)
lt_ret_t lt_port_spi_transfer(lt_l2_state_t *s2, uint8_t offset, uint16_t tx_len, uint32_t timeout_ms)
lt_ret_t lt_port_spi_csn_low(lt_l2_state_t *s2)
static Tropic01Element g_secureElement
Global singleton instance of TROPIC secure-element implementation.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
Build-time selection of the TROPIC01 SH0 pairing key and slot.