CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
host_api_se.cpp
Go to the documentation of this file.
1
5
11#include "nvs.h"
12
13#include <cstdio>
14#include <cstring>
15#include <string>
16
17extern "C" void* plg_get_active_plugin(void);
18
23
24namespace {
25
26// Plugin pool module id (matches MODULE_ID_PLUGIN_POOL in main/tropic_slot_map.h).
27// The ECC and RMEM slot RANGES are fetched from the central TropicSlotMap
28// (authority) via plugin_pool() below, never hardcoded here, so they cannot
29// drift from the map. Plugin ECC/RMEM keys are addressed by name; the ECC
30// name->slot mapping is persisted in NVS so a key keeps its slot across reboot.
31constexpr uint8_t PLG_POOL_MODULE_ID = 7;
32constexpr uint8_t RMEM_HEADER_MAGIC = 0xCD;
33constexpr uint8_t RMEM_NAME_FIELD_LEN = ISecureElement::RMEM_NAME_LEN;
34constexpr char PLG_ECC_MAP_NS[] = "plg_ecc_map"; // NVS namespace: name->slot
35
36// Plugin slot pool bounds, fetched from the central TropicSlotMap. An invalid
37// or missing range yields an empty pool (start > end) so every caller fails
38// safely (RMEM_FULL / NO_MEMORY) and never touches reserved slot 0.
39struct PluginPool { uint16_t start; uint16_t end; };
40
41PluginPool plugin_pool(cdc::core::TropicSlotMap::SlotType type) {
43 if (!cdc::core::TropicSlotMap::instance().getRangeByModuleId(PLG_POOL_MODULE_ID, type, &r)
44 || !r.valid) {
45 return { 1, 0 }; // empty: `slot <= end` is immediately false
46 }
47 return { r.start, r.end };
48}
49
51
54}
55
56bool active_declares_rmem(const std::string& name) {
57 auto* p = active();
58 if (!p) return false;
59 for (const std::string& n : p->manifest().capabilities.rmem) {
60 if (n == name) return true;
61 }
62 return false;
63}
64
65bool any_installed_declares_rmem(const std::string& name) {
67 for (const std::string& id : mgr.listInstalledIds()) {
68 auto man = mgr.getManifest(id);
69 if (!man) continue;
70 for (const std::string& n : man->capabilities.rmem) {
71 if (n == name) return true;
72 }
73 }
74 return false;
75}
76
77// Scan the plugin pool. Output `out_existing` is the slot holding `name`, or
78// -1 if none. Output `out_reclaimable` is a slot that is empty or stale (not
79// claimed by any installed plugin), or -1 if pool is fully populated by
80// live names.
81void scan_plugin_pool(const std::string& name, int& out_existing, int& out_reclaimable) {
82 out_existing = -1;
83 out_reclaimable = -1;
84 auto* s = se();
85 if (!s) return;
86
87 PluginPool pool = plugin_pool(cdc::core::TropicSlotMap::SlotType::RMEM);
88 for (uint16_t slot = pool.start; slot <= pool.end; ++slot) {
89 ISecureElement::RMemHeader hdr{};
90 SeResult rr = s->rmemReadWithHeader(slot, &hdr, nullptr, 0, nullptr);
91 if (rr != SeResult::OK || hdr.magic != RMEM_HEADER_MAGIC) {
92 if (out_reclaimable < 0) out_reclaimable = slot;
93 continue;
94 }
95 std::string hdr_name(hdr.name, ::strnlen(hdr.name, RMEM_NAME_FIELD_LEN));
96 if (hdr_name == name) {
97 out_existing = slot;
98 return;
99 }
100 if (out_reclaimable < 0 && !any_installed_declares_rmem(hdr_name)) {
101 out_reclaimable = slot;
102 }
103 }
104}
105
106bool active_declares_ecc(const std::string& name) {
107 auto* p = active();
108 if (!p) return false;
109 for (const std::string& n : p->manifest().capabilities.ecc) {
110 if (n == name) return true;
111 }
112 return false;
113}
114
115bool any_installed_declares_ecc(const std::string& name) {
117 for (const std::string& id : mgr.listInstalledIds()) {
118 auto man = mgr.getManifest(id);
119 if (!man) continue;
120 for (const std::string& n : man->capabilities.ecc) {
121 if (n == name) return true;
122 }
123 }
124 return false;
125}
126
127// Map an ECC key name to a physical pool slot. Returns the slot, or -1 if the
128// name is not mapped (and either assignment was not requested or the pool is
129// full). When `assign` is set, claims a free or reclaimable slot, persists the
130// name->slot mapping in NVS, and wipes any stale key on a reclaimed slot.
131int resolve_ecc_slot(const std::string& name, bool assign) {
132 nvs_handle_t h;
133 if (nvs_open(PLG_ECC_MAP_NS, assign ? NVS_READWRITE : NVS_READONLY, &h) != ESP_OK) {
134 return -1; // namespace absent (no mappings yet) or open failure
135 }
136
137 int existing = -1, reclaimable = -1;
138 char key[16];
139 char stored[HOST_ECC_NAME_MAX + 1];
140 PluginPool pool = plugin_pool(cdc::core::TropicSlotMap::SlotType::ECC);
141 for (uint16_t slot = pool.start; slot <= pool.end; ++slot) {
142 std::snprintf(key, sizeof(key), "s%u", slot);
143 size_t sz = sizeof(stored);
144 if (nvs_get_str(h, key, stored, &sz) != ESP_OK) {
145 if (reclaimable < 0) reclaimable = slot;
146 continue;
147 }
148 if (name == stored) { existing = slot; break; }
149 if (reclaimable < 0 && !any_installed_declares_ecc(stored)) reclaimable = slot;
150 }
151
152 int result = -1;
153 if (existing >= 0) {
154 result = existing;
155 } else if (assign && reclaimable >= 0) {
156 std::snprintf(key, sizeof(key), "s%u", reclaimable);
157 if (nvs_set_str(h, key, name.c_str()) == ESP_OK && nvs_commit(h) == ESP_OK) {
158 auto* s = se();
159 if (s) s->eccDelete(static_cast<uint8_t>(reclaimable)); // wipe stale owner's key
160 result = reclaimable;
161 }
162 }
163 nvs_close(h);
164 return result;
165}
166
167// Drop the name->slot mapping so the slot returns to the pool.
168void free_ecc_mapping(uint8_t slot) {
169 nvs_handle_t h;
170 if (nvs_open(PLG_ECC_MAP_NS, NVS_READWRITE, &h) != ESP_OK) return;
171 char key[16];
172 std::snprintf(key, sizeof(key), "s%u", slot);
173 nvs_erase_key(h, key);
174 nvs_commit(h);
175 nvs_close(h);
176}
177
178int se_rc(SeResult r) {
179 switch (r) {
180 case SeResult::OK: return HOST_OK;
181 case SeResult::INVALID_PARAM: return HOST_ERR_INVALID_ARG;
182 case SeResult::SLOT_EMPTY: return HOST_ERR_NOT_FOUND;
183 case SeResult::SESSION_REQUIRED: return HOST_ERR_BUSY;
184 case SeResult::NOT_SUPPORTED: return HOST_ERR_NOT_SUPPORTED;
185 default: return HOST_ERR_GENERIC;
186 }
187}
188
189EccCurve curve_for(uint8_t curve) {
190 return curve == ECC_CURVE_ED25519 ? EccCurve::ED25519 : EccCurve::P256;
191}
192
193} // namespace
194
195extern "C" {
196
197int host_rmem_read_named(const char* name, uint8_t* buf, size_t* len)
198{
199 if (!name || !len) return HOST_ERR_INVALID_ARG;
200 std::string name_s(name);
201 if (!active_declares_rmem(name_s)) return HOST_ERR_NO_CAPABILITY;
202 auto* s = se();
203 if (!s) return HOST_ERR_NOT_FOUND;
204
205 int existing = -1, reclaimable = -1;
206 scan_plugin_pool(name_s, existing, reclaimable);
207 if (existing < 0) return HOST_ERR_NOT_FOUND;
208
209 ISecureElement::RMemHeader hdr{};
210 uint16_t payload_len = 0;
211 SeResult rr = s->rmemReadWithHeader(static_cast<uint16_t>(existing), &hdr,
212 buf, static_cast<uint16_t>(*len), &payload_len);
213 if (rr != SeResult::OK) return se_rc(rr);
214 *len = payload_len;
215 return HOST_OK;
216}
217
218int host_rmem_write_named(const char* name, const uint8_t* buf, size_t len)
219{
220 if (!name || (!buf && len > 0)) return HOST_ERR_INVALID_ARG;
221 std::string name_s(name);
222 if (name_s.empty() || name_s.size() > HOST_RMEM_NAME_MAX) return HOST_ERR_INVALID_ARG;
223 if (!active_declares_rmem(name_s)) return HOST_ERR_NO_CAPABILITY;
224 auto* s = se();
225 if (!s) return HOST_ERR_NOT_FOUND;
226
227 int existing = -1, reclaimable = -1;
228 scan_plugin_pool(name_s, existing, reclaimable);
229 int target = (existing >= 0) ? existing : reclaimable;
230 if (target < 0) return HOST_ERR_RMEM_FULL;
231
232 return se_rc(s->rmemWriteWithHeader(static_cast<uint16_t>(target),
233 PLG_POOL_MODULE_ID,
234 name_s.c_str(),
235 /*flags=*/0,
236 buf, static_cast<uint16_t>(len)));
237}
238
240{
241 if (!name) return HOST_ERR_INVALID_ARG;
242 std::string name_s(name);
243 if (!active_declares_rmem(name_s)) return HOST_ERR_NO_CAPABILITY;
244 auto* s = se();
245 if (!s) return HOST_ERR_NOT_FOUND;
246
247 int existing = -1, reclaimable = -1;
248 scan_plugin_pool(name_s, existing, reclaimable);
249 if (existing < 0) return HOST_ERR_NOT_FOUND;
250
251 return se_rc(s->rmemErase(static_cast<uint16_t>(existing)));
252}
253
254bool host_rmem_name_used(const char* name)
255{
256 if (!name) return false;
257 std::string name_s(name);
258 if (!active_declares_rmem(name_s)) return false;
259 int existing = -1, reclaimable = -1;
260 scan_plugin_pool(name_s, existing, reclaimable);
261 return existing >= 0;
262}
263
265{
266 auto* s = se();
267 return s ? s->getRmemSlotSize() : 0;
268}
269
270int host_ecc_generate(const char* name, uint8_t curve)
271{
272 if (!name) return HOST_ERR_INVALID_ARG;
273 std::string n(name);
274 if (n.empty() || n.size() > HOST_ECC_NAME_MAX) return HOST_ERR_INVALID_ARG;
275 if (!active_declares_ecc(n)) return HOST_ERR_NO_CAPABILITY;
276 auto* s = se();
277 if (!s) return HOST_ERR_NOT_FOUND;
278 int slot = resolve_ecc_slot(n, /*assign=*/true);
279 if (slot < 0) return HOST_ERR_NO_MEMORY; // plugin ECC pool exhausted
280 return se_rc(s->eccGenerate(static_cast<uint8_t>(slot), curve_for(curve)));
281}
282
283int host_ecc_import(const char* /*name*/, const uint8_t* /*priv*/, uint8_t /*curve*/)
284{
285 return HOST_ERR_NOT_SUPPORTED; // intentionally not exposed
286}
287
288int host_ecc_pubkey(const char* name, uint8_t* pub, uint8_t /*curve*/)
289{
290 if (!name || !pub) return HOST_ERR_INVALID_ARG;
291 std::string n(name);
292 if (!active_declares_ecc(n)) return HOST_ERR_NO_CAPABILITY;
293 auto* s = se();
294 if (!s) return HOST_ERR_NOT_FOUND;
295 int slot = resolve_ecc_slot(n, /*assign=*/false);
296 if (slot < 0) return HOST_ERR_NOT_FOUND;
297 return se_rc(s->eccGetPublicKey(static_cast<uint8_t>(slot), pub, nullptr));
298}
299
300int host_ecc_delete(const char* name)
301{
302 if (!name) return HOST_ERR_INVALID_ARG;
303 std::string n(name);
304 if (!active_declares_ecc(n)) return HOST_ERR_NO_CAPABILITY;
305 auto* s = se();
306 if (!s) return HOST_ERR_NOT_FOUND;
307 int slot = resolve_ecc_slot(n, /*assign=*/false);
308 if (slot < 0) return HOST_ERR_NOT_FOUND;
309 int rc = se_rc(s->eccDelete(static_cast<uint8_t>(slot)));
310 free_ecc_mapping(static_cast<uint8_t>(slot)); // return the slot to the pool
311 return rc;
312}
313
314bool host_ecc_exists(const char* name)
315{
316 if (!name) return false;
317 std::string n(name);
318 if (!active_declares_ecc(n)) return false;
319 auto* s = se();
320 if (!s) return false;
321 int slot = resolve_ecc_slot(n, /*assign=*/false);
322 return slot >= 0 && s->eccSlotUsed(static_cast<uint8_t>(slot));
323}
324
325int host_ecdsa_sign(const char* name, const uint8_t* msg, size_t len, uint8_t sig[64])
326{
327 if (!name || !msg || !sig) return HOST_ERR_INVALID_ARG;
328 std::string n(name);
329 if (!active_declares_ecc(n)) return HOST_ERR_NO_CAPABILITY;
330 auto* s = se();
331 if (!s) return HOST_ERR_NOT_FOUND;
332 int slot = resolve_ecc_slot(n, /*assign=*/false);
333 if (slot < 0) return HOST_ERR_NOT_FOUND;
334 size_t sig_len = 64;
335 return se_rc(s->ecdsaSign(static_cast<uint8_t>(slot), msg, len, sig, &sig_len));
336}
337
338int host_eddsa_sign(const char* name, const uint8_t* msg, size_t len, uint8_t sig[64])
339{
340 if (!name || !msg || !sig) return HOST_ERR_INVALID_ARG;
341 std::string n(name);
342 if (!active_declares_ecc(n)) return HOST_ERR_NO_CAPABILITY;
343 auto* s = se();
344 if (!s) return HOST_ERR_NOT_FOUND;
345 int slot = resolve_ecc_slot(n, /*assign=*/false);
346 if (slot < 0) return HOST_ERR_NOT_FOUND;
347 return se_rc(s->eddsaSign(static_cast<uint8_t>(slot), msg, len, sig));
348}
349
350int host_se_chip_id(uint8_t* serial, size_t* len)
351{
352 if (!serial || !len) return HOST_ERR_INVALID_ARG;
353 auto* s = se();
354 if (!s) return HOST_ERR_NOT_FOUND;
355 // getChipId takes a uint8_t capacity; clamp so a large *len cannot wrap to a
356 // tiny size. It reports success/failure only, not the byte count written.
357 uint8_t cap = *len > 0xff ? 0xff : static_cast<uint8_t>(*len);
358 return s->getChipId(serial, cap) ? HOST_OK : HOST_ERR_GENERIC;
359}
360
361int host_se_fw_version(uint8_t* riscv, uint8_t* spect)
362{
363 if (!riscv || !spect) return HOST_ERR_INVALID_ARG;
364 auto* s = se();
365 if (!s) return HOST_ERR_NOT_FOUND;
366 return s->getFwVersion(riscv, spect) ? HOST_OK : HOST_ERR_GENERIC;
367}
368
369} // extern "C"
Discovers, loads, runs and unloads WASM plugins on the badge.
Owned WAMR module instance + per-plugin state.
static constexpr uint8_t RMEM_HEADER_MAGIC
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
static constexpr uint8_t RMEM_NAME_LEN
static TropicSlotMap & instance()
Returns singleton Tropic slot-map instance.
static PluginManager & instance() noexcept
uint8_t curve
#define HOST_ECC_NAME_MAX
Definition host_api.h:281
int host_ecc_delete(const char *name)
Erase the named ECC key and free its pool slot.
int host_se_fw_version(uint8_t *riscv, uint8_t *spect)
Read TROPIC01 firmware versions for the RISC-V CPU and SPECT core.
int host_se_chip_id(uint8_t *serial, size_t *len)
Read the TROPIC01 chip serial / identity blob.
int host_rmem_read_named(const char *name, uint8_t *buf, size_t *len)
Read a named retained-memory slot.
#define HOST_RMEM_NAME_MAX
Definition host_api.h:249
int host_rmem_write_named(const char *name, const uint8_t *buf, size_t len)
Write up to host_rmem_slot_size() bytes into a named rmem slot.
uint16_t host_rmem_slot_size(void)
Maximum payload bytes per rmem slot.
int host_ecc_pubkey(const char *name, uint8_t *pub, uint8_t)
Export the public key for the named slot.
#define ECC_CURVE_ED25519
Definition host_api.h:237
int host_ecdsa_sign(const char *name, const uint8_t *msg, size_t len, uint8_t sig[64])
ECDSA-sign msg with the P-256 named key; writes 64-byte raw sig.
bool host_rmem_name_used(const char *name)
True if the named rmem slot currently holds data.
int host_eddsa_sign(const char *name, const uint8_t *msg, size_t len, uint8_t sig[64])
Ed25519-sign msg with the named key; writes 64-byte signature.
int host_rmem_erase_named(const char *name)
Erase the contents of a named rmem slot.
int host_ecc_generate(const char *name, uint8_t curve)
Generate a fresh ECC key for the named slot.
int host_ecc_import(const char *, const uint8_t *, uint8_t)
Import an externally-generated private key for the named slot.
bool host_ecc_exists(const char *name)
True when the named ECC key currently holds a key.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_ERR_NO_CAPABILITY
Definition host_api.h:40
#define HOST_ERR_NOT_SUPPORTED
Definition host_api.h:45
#define HOST_OK
Definition host_api.h:37
#define HOST_ERR_INVALID_ARG
Definition host_api.h:39
#define HOST_ERR_NO_MEMORY
Definition host_api.h:43
#define HOST_ERR_NOT_FOUND
Definition host_api.h:41
#define HOST_ERR_RMEM_FULL
Definition host_api.h:46
#define HOST_ERR_GENERIC
Definition host_api.h:38
#define HOST_ERR_BUSY
Definition host_api.h:44
void * plg_get_active_plugin(void)
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.