CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
Tropic01Element.cpp
Go to the documentation of this file.
1
17
20#include "cdc_hal/hw_config.h"
22#include "cdc_core/SystemLock.h"
23#include "cdc_spi_lock.h"
24#include "cdc_log.h"
25#include "esp_random.h"
26#include "esp_timer.h"
27#include "freertos/FreeRTOS.h"
28#include "freertos/task.h"
29#include "driver/spi_master.h"
30#include "psa/crypto.h"
31#include <atomic>
32#include <cstring>
33
35#include "libtropic.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"
42
43static const char* TAG = "TR01";
44static constexpr uint8_t RMEM_HEADER_MAGIC = 0xCD;
45
46// PAIRING_KEY_PRIV / PAIRING_KEY_PUB / PAIRING_KEY_SLOT come from
47// cdc_hal/pairing_key_config.h (build-time selectable, default production key).
48
49namespace cdc::hal {
50
55public:
56 Tropic01Element() = default;
57
58 // IService implementation
59 bool init() override;
60 bool start() override;
61 void stop() override;
62 core::ServiceState getState() const override { return state_; }
63 const char* getName() const override { return "secure_element"; }
64
65 // Session Management
66 bool sessionStart() override;
67 void sessionEnd() override;
68 bool isSessionActive() const override { return sessionActive_.load(std::memory_order_acquire); }
69 void sleep() override;
70
71 // ECC Operations
72 SeResult eccGenerate(uint8_t slot, EccCurve curve) override;
73 SeResult eccImport(uint8_t slot, const uint8_t* privKey, EccCurve curve) override;
74 SeResult eccGetPublicKey(uint8_t slot, uint8_t* pubKey, EccCurve* curve) override;
75 SeResult eccDelete(uint8_t slot) override;
76 bool eccSlotUsed(uint8_t slot) const override;
77
78 bool getFwVersion(uint8_t riscvVer[4], uint8_t spectVer[4]) override;
79 uint16_t getRmemSlotSize() const override { return rmemSlotSize_; }
80
81 // Signing
82 SeResult ecdsaSign(uint8_t slot, const uint8_t* msg, size_t msgLen,
83 uint8_t* sig, size_t* sigLen) override;
84 SeResult eddsaSign(uint8_t slot, const uint8_t* msg, size_t msgLen,
85 uint8_t* sig) override;
86
87 // R-Memory
88 SeResult rmemRead(uint16_t slot, uint8_t* data, uint16_t maxLen,
89 uint16_t* actualLen) override;
90 SeResult rmemWrite(uint16_t slot, const uint8_t* data, uint16_t len) override;
91 SeResult rmemErase(uint16_t slot) override;
92 bool rmemSlotUsed(uint16_t slot) const override;
93 SeResult rmemWriteWithHeader(uint16_t slot, uint8_t moduleId,
94 const char* name, uint8_t flags,
95 const uint8_t* payload, uint16_t payloadLen) override;
96 SeResult rmemReadWithHeader(uint16_t slot, RMemHeader* headerOut,
97 uint8_t* payloadOut, uint16_t payloadMax,
98 uint16_t* payloadLenOut) override;
99
100 // Random
101 bool getRandom(uint8_t* buffer, uint16_t size) override;
102 bool getRandomStrict(uint8_t* buffer, uint16_t size) override;
103
104 // Diagnostics
105 bool getChipId(uint8_t* serialNum, uint8_t size) override;
106
107private:
108 // Public-API helpers
109 bool acquireBus();
110 void releaseBus();
111
112 // Bus-locked workers (caller must hold the bus)
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);
118 SeResult eccGetPublicKey_unlocked(uint8_t slot, uint8_t* pubKey, EccCurve* curve);
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);
122
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;
127
129 std::atomic<bool> sessionActive_{false};
130
131 // libtropic handles
132 lt_handle_t handle_ = {};
133 lt_ctx_mbedtls_v4_t cryptoCtx_ = {};
134 lt_dev_esp32_t device_ = {};
135
136 // ECC slot-usage cache. Mutated under the SPI bus lock; read lock-free
137 // from the const accessor, hence atomic.
138 mutable std::atomic<uint32_t> eccSlotCache_{0};
139 mutable std::atomic<bool> eccCacheValid_{false};
140
141 // Populated once during init() from libtropic's runtime FW-attribute
142 // query. Const after init() returns, so no synchronization needed.
143 uint16_t rmemSlotSize_ = ISecureElement::RMEM_SLOT_SIZE;
144};
145
151 if (state_ != core::ServiceState::UNINITIALIZED) {
152 return state_ == core::ServiceState::INITIALIZED ||
154 }
155
156 LOG_I(TAG, "Initializing TROPIC01...");
157
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");
165 return false;
166 }
167 LOG_I(TAG, "PSA crypto initialized");
168
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_;
174
175 lt_ret_t ret = lt_init(&handle_);
176 if (ret != LT_OK) {
177 LOG_E(TAG, "lt_init failed (%s)", lt_ret_verbose(ret));
180 ret == LT_L1_CHIP_ALARM_MODE
183 lt_ret_verbose(ret));
184 return false;
185 }
186
187 uint16_t slotSize = handle_.tr01_attrs.r_mem_udata_slot_size_max;
188 if (slotSize >= ISecureElement::RMEM_SLOT_SIZE &&
190 rmemSlotSize_ = slotSize;
191 }
192
194 LOG_I(TAG, "TROPIC01 initialized (CS=GPIO%d, R-Mem slot=%u B)",
195 TR01_CS_PIN, rmemSlotSize_);
196 return true;
197}
198
203 if (state_ == core::ServiceState::INITIALIZED ||
204 state_ == core::ServiceState::STOPPED) {
206 return true;
207 }
208 return state_ == core::ServiceState::STARTED;
209}
210
215 if (state_ == core::ServiceState::STARTED) {
216 if (sessionActive_.load(std::memory_order_acquire)) {
217 sessionEnd();
218 }
220 }
221}
222
227bool Tropic01Element::acquireBus() {
228 if (!device_.spi) {
229 LOG_E(TAG, "acquireBus: SPI device not initialized");
232 "SPI device handle null");
233 return false;
234 }
235 // Serialize the WHOLE secure-element operation against the e-paper display,
236 // which shares this SPI bus. A TROPIC01 op is many CS-framed transfers; the
237 // display (CalEPD) takes the same recursive lock around each of its
238 // transfers, so a display transfer can never slip between two TR01 frames
239 // (CS asserted) and corrupt the transaction (which would latch a chip
240 // tamper alarm). Held for the whole op, released in releaseBus().
241 SemaphoreHandle_t spiLock = cdc::hal::sharedSpiLock();
242 if (spiLock) {
243 xSemaphoreTakeRecursive(spiLock, portMAX_DELAY);
244 }
245 // ESP-IDF requires portMAX_DELAY for spi_device_acquire_bus; other timeouts
246 // return ESP_ERR_INVALID_ARG. Operations run under a busy display+TR01 bus
247 // are short, so blocking is acceptable here.
248 esp_err_t err = spi_device_acquire_bus(device_.spi, portMAX_DELAY);
249 if (err != ESP_OK) {
250 LOG_E(TAG, "acquireBus failed: %d", err);
251 if (spiLock) {
252 xSemaphoreGiveRecursive(spiLock);
253 }
256 "SPI bus acquire failed");
257 return false;
258 }
259 return true;
260}
261
265void Tropic01Element::releaseBus() {
266 if (device_.spi) {
267 spi_device_release_bus(device_.spi);
268 }
269 SemaphoreHandle_t spiLock = cdc::hal::sharedSpiLock();
270 if (spiLock) {
271 xSemaphoreGiveRecursive(spiLock);
272 }
273}
274
279 if (core::SystemLock::instance().isLocked()) {
280 return false;
281 }
282 if (sessionActive_.load(std::memory_order_acquire)) {
283 return true;
284 }
285 if (!acquireBus()) {
286 return false;
287 }
288 bool ok = sessionStart_unlocked();
289 releaseBus();
290 return ok;
291}
292
304bool Tropic01Element::ensureRConfigBits(lt_config_obj_addr_t addr, uint32_t mask, bool set) {
305 uint32_t cfg = 0;
306 if (lt_r_config_read(&handle_, addr, &cfg) != LT_OK) {
307 return false;
308 }
309 const uint32_t desired = set ? (cfg | mask) : (cfg & ~mask);
310 if (desired == cfg) {
311 return false;
312 }
313 return lt_r_config_write(&handle_, addr, desired) == LT_OK;
314}
315
319bool Tropic01Element::sessionStart_unlocked() {
320 if (sessionActive_.load(std::memory_order_acquire)) {
321 return true;
322 }
323 LOG_I(TAG, "Starting secure session...");
324
325 lt_ret_t ret = lt_verify_chip_and_start_secure_session(
327
328 if (ret != LT_OK) {
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) {
333 lastDumpUs = nowUs;
334 dumpChipStatus_unlocked("sessionStart");
335 }
336 // Fail-closed: any session establishment failure latches lockdown.
337 // The secure element is unusable without a verified session, so the
338 // OS must not continue running modules that depend on it.
340 ret == LT_L1_CHIP_ALARM_MODE
343 lt_ret_verbose(ret));
344 sessionActive_.store(false, std::memory_order_release);
345 return false;
346 }
347
348 sessionActive_.store(true, std::memory_order_release);
349 eccCacheValid_.store(false, std::memory_order_release);
350
351 if (ensureRConfigBits(TR01_CFG_SLEEP_MODE_ADDR, 0x01u, true)) {
352 LOG_I(TAG, "Auto-sleep enabled");
353 }
354 // Disable maintenance mode (start-up R-Config bit) so the chip rejects
355 // start-up firmware uploads. Reversible R-Config, re-applied automatically
356 // after an R-Memory wipe. Ref: Tropic Square advisory ODR_TR01_SA_2026012900.
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)");
360 }
361
362 LOG_I(TAG, "Secure session active");
363 return true;
364}
365
370 if (!sessionActive_.load(std::memory_order_acquire)) {
371 return;
372 }
373 if (!acquireBus()) {
374 sessionActive_.store(false, std::memory_order_release);
375 return;
376 }
377 lt_session_abort(&handle_);
378 sessionActive_.store(false, std::memory_order_release);
379 LOG_I(TAG, "Session ended");
380 releaseBus();
381}
382
388bool Tropic01Element::ensureSession_unlocked(const char* op) {
389 if (sessionActive_.load(std::memory_order_acquire)) {
390 return true;
391 }
392 LOG_W(TAG, "Session inactive for %s - restarting", op);
393 return sessionStart_unlocked();
394}
395
400 if (core::SystemLock::instance().isLocked()) {
401 return;
402 }
403 if (!sessionActive_.load(std::memory_order_acquire)) {
404 return;
405 }
406 if (!acquireBus()) {
407 return;
408 }
409 lt_ret_t ret = lt_sleep(&handle_, TR01_L2_SLEEP_KIND_SLEEP);
410 if (ret != LT_OK) {
411 LOG_E(TAG, "Sleep failed (%s)", lt_ret_verbose(ret));
412 handleSessionError(ret);
413 } else {
414 sessionActive_.store(false, std::memory_order_release);
415 LOG_I(TAG, "Entered sleep mode");
416 }
417 releaseBus();
418}
419
423void Tropic01Element::handleSessionError(lt_ret_t ret) {
424 switch (ret) {
425 case LT_L1_CHIP_ALARM_MODE:
426 sessionActive_.store(false, std::memory_order_release);
429 lt_ret_verbose(ret));
430 break;
431 case LT_L2_HSK_ERR:
432 case LT_L2_NO_SESSION:
433 case LT_L2_TAG_ERR:
434 sessionActive_.store(false, std::memory_order_release);
435 break;
436 default:
437 break;
438 }
439}
440
444SeResult Tropic01Element::mapResult(lt_ret_t ret) const {
445 switch (ret) {
446 case LT_OK:
447 return SeResult::OK;
448 case LT_PARAM_ERR:
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:
460 default:
461 return SeResult::ERROR;
462 }
463}
464
469void Tropic01Element::dumpChipStatus_unlocked(const char* context) {
470 if (!device_.spi) {
471 LOG_E(TAG, "[%s] CHIP_STATUS read skipped: SPI not initialized", context);
472 return;
473 }
474
475 handle_.l2.buff[0] = 0xAA; // TR01_L1_GET_RESPONSE_REQ_ID
476
477 lt_ret_t ret = lt_port_spi_csn_low(&handle_.l2);
478 if (ret != LT_OK) {
479 LOG_E(TAG, "[%s] CHIP_STATUS read: CS-low failed (%d)", context, (int)ret);
480 return;
481 }
482
483 ret = lt_port_spi_transfer(&handle_.l2, 0, 1, 100);
484 lt_port_spi_csn_high(&handle_.l2);
485
486 if (ret != LT_OK) {
487 LOG_E(TAG, "[%s] CHIP_STATUS read: SPI transfer failed (%d)", context, (int)ret);
488 return;
489 }
490
491 uint8_t status = handle_.l2.buff[0];
492 LOG_E(TAG, "[%s] CHIP_STATUS=0x%02X (READY=%d ALARM=%d STARTUP=%d)",
493 context, status,
494 (status & 0x01) ? 1 : 0,
495 (status & 0x02) ? 1 : 0,
496 (status & 0x04) ? 1 : 0);
497
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);
504 }
505}
506
511 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
512 if (slot >= ECC_SLOT_COUNT) {
514 }
515 if (!acquireBus()) return SeResult::ERROR;
516
517 SeResult result;
518 if (!ensureSession_unlocked("eccGenerate")) {
520 } else {
521 lt_ecc_curve_type_t ltCurve = (curve == EccCurve::ED25519) ?
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);
524 if (ret == LT_OK) {
525 eccSlotCache_.fetch_or(1u << slot, std::memory_order_release);
526 }
527 handleSessionError(ret);
528 result = mapResult(ret);
529 }
530 releaseBus();
531 return result;
532}
533
537SeResult Tropic01Element::eccImport(uint8_t slot, const uint8_t* privKey, EccCurve curve) {
538 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
539 if (slot >= ECC_SLOT_COUNT || !privKey) {
541 }
542 if (!acquireBus()) return SeResult::ERROR;
543
544 SeResult result;
545 if (!ensureSession_unlocked("eccImport")) {
547 } else {
548 lt_ecc_curve_type_t ltCurve = (curve == EccCurve::ED25519) ?
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);
551 if (ret == LT_OK) {
552 eccSlotCache_.fetch_or(1u << slot, std::memory_order_release);
553 }
554 handleSessionError(ret);
555 result = mapResult(ret);
556 }
557 releaseBus();
558 return result;
559}
560
564SeResult Tropic01Element::eccGetPublicKey_unlocked(uint8_t slot, uint8_t* pubKey, EccCurve* curve) {
565 if (!ensureSession_unlocked("eccGetPublicKey")) {
567 }
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, &ltCurve, &ltOrigin);
572 if (ret == LT_OK && curve) {
573 *curve = (ltCurve == TR01_CURVE_ED25519) ? EccCurve::ED25519 : EccCurve::P256;
574 }
575 handleSessionError(ret);
576 if (ret == LT_L3_INVALID_KEY) {
578 }
579 return mapResult(ret);
580}
581
585SeResult Tropic01Element::eccGetPublicKey(uint8_t slot, uint8_t* pubKey, EccCurve* curve) {
586 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
587 if (slot >= ECC_SLOT_COUNT || !pubKey) {
589 }
590 if (!acquireBus()) return SeResult::ERROR;
591 SeResult result = eccGetPublicKey_unlocked(slot, pubKey, curve);
592 releaseBus();
593 return result;
594}
595
600 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
601 if (slot >= ECC_SLOT_COUNT) {
603 }
604 if (!acquireBus()) return SeResult::ERROR;
605
606 SeResult result;
607 if (!ensureSession_unlocked("eccDelete")) {
609 } else {
610 lt_ret_t ret = lt_ecc_key_erase(&handle_, static_cast<lt_ecc_slot_t>(slot));
611 if (ret == LT_OK) {
612 eccSlotCache_.fetch_and(~(1u << slot), std::memory_order_release);
613 }
614 handleSessionError(ret);
615 result = mapResult(ret);
616 }
617 releaseBus();
618 return result;
619}
620
624bool Tropic01Element::eccSlotUsed(uint8_t slot) const {
625 if (slot >= ECC_SLOT_COUNT) {
626 return false;
627 }
628 if (eccCacheValid_.load(std::memory_order_acquire)) {
629 return (eccSlotCache_.load(std::memory_order_acquire) & (1u << slot)) != 0;
630 }
631 auto* self = const_cast<Tropic01Element*>(this);
632 uint8_t tempKey[65];
633 return self->eccGetPublicKey(slot, tempKey, nullptr) == SeResult::OK;
634}
635
642SeResult Tropic01Element::ecdsaSign(uint8_t slot, const uint8_t* msg, size_t msgLen,
643 uint8_t* sig, size_t* sigLen) {
644 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
645 if (slot >= ECC_SLOT_COUNT || !msg || msgLen == 0 || !sig || !sigLen) {
647 }
648
649 uint8_t digest[32];
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)) {
654 return SeResult::ERROR;
655 }
656
657 if (!acquireBus()) return SeResult::ERROR;
658
659 SeResult result;
660 if (!ensureSession_unlocked("ecdsaSign")) {
662 } else {
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);
665 if (ret == LT_OK) {
666 *sigLen = TR01_ECDSA_EDDSA_SIGNATURE_LENGTH;
667 }
668 handleSessionError(ret);
669 result = mapResult(ret);
670 }
671 releaseBus();
672 return result;
673}
674
678SeResult Tropic01Element::eddsaSign(uint8_t slot, const uint8_t* msg, size_t msgLen,
679 uint8_t* sig) {
680 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
681 if (slot >= ECC_SLOT_COUNT || !msg || msgLen == 0 || !sig) {
683 }
684 if (!acquireBus()) return SeResult::ERROR;
685
686 SeResult result;
687 if (!ensureSession_unlocked("eddsaSign")) {
689 } else {
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);
694 }
695 releaseBus();
696 return result;
697}
698
702SeResult Tropic01Element::rmemRead_unlocked(uint16_t slot, uint8_t* data, uint16_t maxLen,
703 uint16_t* actualLen) {
704 if (!ensureSession_unlocked("rmemRead")) {
706 }
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;
711 }
712 handleSessionError(ret);
713 if (ret == LT_L3_R_MEM_DATA_READ_SLOT_EMPTY) {
714 if (actualLen) *actualLen = 0;
716 }
717 return mapResult(ret);
718}
719
723SeResult Tropic01Element::rmemRead(uint16_t slot, uint8_t* data, uint16_t maxLen,
724 uint16_t* actualLen) {
725 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
726 if (slot >= RMEM_SLOT_COUNT || !data || maxLen == 0) {
728 }
729 if (!acquireBus()) return SeResult::ERROR;
730 SeResult result = rmemRead_unlocked(slot, data, maxLen, actualLen);
731 releaseBus();
732 return result;
733}
734
738SeResult Tropic01Element::rmemWrite_unlocked(uint16_t slot, const uint8_t* data, uint16_t len) {
739 if (!ensureSession_unlocked("rmemWrite")) {
741 }
742 lt_ret_t ret = lt_r_mem_data_write(&handle_, slot, data, len);
743 handleSessionError(ret);
744 return mapResult(ret);
745}
746
750SeResult Tropic01Element::rmemWrite(uint16_t slot, const uint8_t* data, uint16_t len) {
751 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
752 if (slot >= RMEM_SLOT_COUNT || !data || len == 0 || len > rmemSlotSize_) {
754 }
755 if (!acquireBus()) return SeResult::ERROR;
756 SeResult result = rmemWrite_unlocked(slot, data, len);
757 releaseBus();
758 return result;
759}
760
764SeResult Tropic01Element::rmemErase_unlocked(uint16_t slot) {
765 if (!ensureSession_unlocked("rmemErase")) {
767 }
768 lt_ret_t ret = lt_r_mem_data_erase(&handle_, slot);
769 handleSessionError(ret);
770 return mapResult(ret);
771}
772
777 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
778 if (slot >= RMEM_SLOT_COUNT) {
780 }
781 if (!acquireBus()) return SeResult::ERROR;
782 SeResult result = rmemErase_unlocked(slot);
783 releaseBus();
784 return result;
785}
786
790bool Tropic01Element::rmemSlotUsed(uint16_t slot) const {
791 if (slot >= RMEM_SLOT_COUNT) {
792 return false;
793 }
794 auto* self = const_cast<Tropic01Element*>(this);
795 uint8_t tempBuf[4];
796 uint16_t actualLen = 0;
797 SeResult res = self->rmemRead(slot, tempBuf, sizeof(tempBuf), &actualLen);
798 return res == SeResult::OK && actualLen > 0;
799}
800
804uint8_t Tropic01Element::computeHeaderChecksum(const RMemHeader& header) const {
805 uint16_t sum = 0;
806 sum += header.moduleId;
807 sum += header.flags;
808 for (size_t i = 0; i < sizeof(header.name); i++) {
809 sum += static_cast<uint8_t>(header.name[i]);
810 }
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);
814}
815
819bool Tropic01Element::validateHeader(const RMemHeader& header) const {
820 if (header.magic != RMEM_HEADER_MAGIC) {
821 return false;
822 }
823 return header.checksum == computeHeaderChecksum(header);
824}
825
830 const char* name, uint8_t flags,
831 const uint8_t* payload, uint16_t payloadLen) {
832 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
833 if (slot >= RMEM_SLOT_COUNT) {
835 }
836 if (payloadLen > (rmemSlotSize_ - sizeof(RMemHeader))) {
838 }
839 if (!acquireBus()) return SeResult::ERROR;
840
841 SeResult result;
842 SeResult eraseRes = rmemErase_unlocked(slot);
843 if (eraseRes != SeResult::OK && eraseRes != SeResult::SLOT_EMPTY) {
844 result = eraseRes;
845 } else {
846 RMemHeader header = {};
847 header.magic = RMEM_HEADER_MAGIC;
848 header.moduleId = moduleId;
849 header.flags = flags;
850 header.payloadLen = payloadLen;
851 if (name) {
852 strncpy(header.name, name, sizeof(header.name) - 1);
853 header.name[sizeof(header.name) - 1] = '\0';
854 }
855 header.checksum = computeHeaderChecksum(header);
856
857 uint8_t buffer[RMEM_SLOT_SIZE_MAX] = {};
858 memcpy(buffer, &header, sizeof(header));
859 if (payloadLen > 0 && payload) {
860 memcpy(buffer + sizeof(header), payload, payloadLen);
861 }
862
863 result = rmemWrite_unlocked(slot, buffer, static_cast<uint16_t>(sizeof(header) + payloadLen));
864 }
865 releaseBus();
866 return result;
867}
868
872SeResult Tropic01Element::rmemReadWithHeader(uint16_t slot, RMemHeader* headerOut,
873 uint8_t* payloadOut, uint16_t payloadMax,
874 uint16_t* payloadLenOut) {
875 if (core::SystemLock::instance().isLocked()) return SeResult::ALARM_MODE;
876 if (slot >= RMEM_SLOT_COUNT) {
878 }
879 if (!acquireBus()) return SeResult::ERROR;
880
881 SeResult result;
882 uint8_t buffer[RMEM_SLOT_SIZE_MAX] = {};
883 uint16_t actualLen = 0;
884 SeResult res = rmemRead_unlocked(slot, buffer, sizeof(buffer), &actualLen);
885 if (res != SeResult::OK) {
886 result = res;
887 } else if (actualLen < sizeof(RMemHeader)) {
888 result = SeResult::ERROR;
889 } else {
890 RMemHeader header = {};
891 memcpy(&header, buffer, sizeof(header));
892 if (!validateHeader(header)) {
893 result = SeResult::ERROR;
894 } else if (header.payloadLen > (rmemSlotSize_ - sizeof(RMemHeader))) {
895 result = SeResult::ERROR;
896 } else if (actualLen < static_cast<uint16_t>(sizeof(RMemHeader) + header.payloadLen)) {
897 result = SeResult::ERROR;
898 } else if (payloadOut && header.payloadLen > 0 && payloadMax < header.payloadLen) {
900 } else {
901 if (headerOut) {
902 *headerOut = header;
903 }
904 if (payloadLenOut) {
905 *payloadLenOut = header.payloadLen;
906 }
907 if (payloadOut && header.payloadLen > 0) {
908 memcpy(payloadOut, buffer + sizeof(RMemHeader), header.payloadLen);
909 }
910 result = SeResult::OK;
911 }
912 }
913 releaseBus();
914 return result;
915}
916
924lt_ret_t Tropic01Element::randomChunked_unlocked(uint8_t* buffer, uint16_t size) {
925 uint16_t offset = 0;
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);
930 if (ret != LT_OK) {
931 return ret;
932 }
933 offset += chunk;
934 }
935 return LT_OK;
936}
937
945bool Tropic01Element::getRandom(uint8_t* buffer, uint16_t size) {
946 if (!buffer || size == 0) {
947 return false;
948 }
949 if (core::SystemLock::instance().isLocked()) {
950 LOG_W(TAG, "SystemLock active, ESP32 TRNG fallback (size=%u)", size);
951 esp_fill_random(buffer, size);
952 return true;
953 }
954 if (!acquireBus()) {
955 LOG_W(TAG, "Bus unavailable, ESP32 TRNG fallback (size=%u)", size);
956 esp_fill_random(buffer, size);
957 return true;
958 }
959
960 if (!ensureSession_unlocked("getRandom")) {
961 LOG_W(TAG, "No SE session, ESP32 TRNG fallback (size=%u)", size);
962 esp_fill_random(buffer, size);
963 } else {
964 lt_ret_t ret = randomChunked_unlocked(buffer, size);
965 handleSessionError(ret);
966 if (ret != LT_OK) {
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);
970 }
971 }
972 releaseBus();
973 return true;
974}
975
979bool Tropic01Element::getRandomStrict(uint8_t* buffer, uint16_t size) {
980 if (!buffer || size == 0) {
981 return false;
982 }
983 if (core::SystemLock::instance().isLocked()) {
984 return false;
985 }
986 if (!acquireBus()) {
987 return false;
988 }
989 bool ok = false;
990 if (ensureSession_unlocked("getRandomStrict")) {
991 lt_ret_t ret = randomChunked_unlocked(buffer, size);
992 handleSessionError(ret);
993 ok = (ret == LT_OK);
994 }
995 releaseBus();
996 return ok;
997}
998
1002bool Tropic01Element::getChipId(uint8_t* serialNum, uint8_t size) {
1003 if (core::SystemLock::instance().isLocked()) return false;
1004 if (!serialNum || size < 8) {
1005 return false;
1006 }
1007 if (!acquireBus()) return false;
1008
1009 bool ok = 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);
1014 if (ret == LT_OK) {
1015 uint8_t copyLen = (size < sizeof(chipId.ser_num)) ? size : sizeof(chipId.ser_num);
1016 memcpy(serialNum, &chipId.ser_num, copyLen);
1017 ok = true;
1018 }
1019 handleSessionError(ret);
1020 }
1021 releaseBus();
1022 return ok;
1023}
1024
1028bool Tropic01Element::getFwVersion(uint8_t riscvVer[4], uint8_t spectVer[4]) {
1029 if (core::SystemLock::instance().isLocked()) return false;
1030 if (!riscvVer || !spectVer) {
1031 return false;
1032 }
1033 if (!acquireBus()) return false;
1034
1035 bool ok = 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);
1039 if (ret == LT_OK) {
1040 for (int i = 0; i < 4; i++) riscvVer[i] = riscvFw[i];
1041 }
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];
1046 }
1047 handleSessionError(ret == LT_OK ? ret2 : ret);
1048 ok = (ret == LT_OK) && (ret2 == LT_OK);
1049 }
1050 releaseBus();
1051 return ok;
1052}
1053
1056
1061 return &g_secureElement;
1062}
1063
1064} // namespace cdc::hal
static const char * TAG
static constexpr uint8_t RMEM_HEADER_MAGIC
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
uint8_t flags
uint8_t moduleId
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
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.
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.
uint8_t curve
SeResult
EccCurve
#define TR01_CS_PIN
Definition hw_config.h:40
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.
#define PAIRING_KEY_PUB
#define PAIRING_KEY_PRIV
#define PAIRING_KEY_SLOT