CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
PasswordStore.cpp
Go to the documentation of this file.
4#include "cdc_log.h"
5#include <algorithm>
6#include <cctype>
7#include <cstring>
8#include <memory>
9
10static const char* TAG = "PASSWORD";
11
12namespace cdc::mod_password {
13
14#pragma pack(push, 1)
23#pragma pack(pop)
24
25static_assert(sizeof(PasswordPayload) == PasswordStore::PAYLOAD_MAX, "Password payload size mismatch");
26
33static void copyText(char* dst, size_t dstSize, const char* src) {
34 if (!dst || dstSize == 0) return;
35 if (!src) {
36 dst[0] = '\0';
37 return;
38 }
39 strncpy(dst, src, dstSize - 1);
40 dst[dstSize - 1] = '\0';
41}
42
47PasswordStore& PasswordStore::instance() {
48 static PasswordStore inst;
49 return inst;
50}
51
59
66bool PasswordStore::readEntry(uint16_t slot, PasswordEntry* out) const {
67 if (!out) return false;
68 if (!slots_.hasSlotRange()) return false;
69 uint16_t physSlot = 0;
70 if (!toPhysicalSlot(slot, &physSlot)) return false;
71
73 if (!se) return false;
74
75 cdc::hal::ISecureElement::RMemHeader header = {};
76 PasswordPayload payload = {};
77 uint16_t payloadLen = 0;
78
79 auto res = se->rmemReadWithHeader(physSlot, &header,
80 reinterpret_cast<uint8_t*>(&payload),
81 sizeof(payload), &payloadLen);
82 if (res != cdc::hal::SeResult::OK) {
83 return false;
84 }
85
86 if (header.moduleId != slots_.moduleId()) {
87 return false;
88 }
89
90 memset(out, 0, sizeof(*out));
91 if (payload.title[0]) {
92 copyText(out->title, sizeof(out->title), payload.title);
93 } else {
94 copyText(out->title, sizeof(out->title), header.name);
95 }
96 copyText(out->username, sizeof(out->username), payload.username);
97 copyText(out->password, sizeof(out->password), payload.password);
98 copyText(out->url, sizeof(out->url), payload.url);
99 out->totpSlot = payload.totpSlot;
100 copyText(out->notes, sizeof(out->notes), payload.notes);
101
102 return true;
103}
104
111 if (!slots_.hasSlotRange()) return false;
112 uint16_t slot = 0;
113 if (!slots_.findFreeSlot(&slot)) {
114 LOG_W(TAG, "No free password slots");
115 return false;
116 }
117
118 PasswordPayload payload = {};
119 copyText(payload.title, sizeof(payload.title), entry.title);
120 copyText(payload.username, sizeof(payload.username), entry.username);
121 copyText(payload.password, sizeof(payload.password), entry.password);
122 copyText(payload.url, sizeof(payload.url), entry.url);
123 payload.totpSlot = entry.totpSlot;
124 copyText(payload.notes, sizeof(payload.notes), entry.notes);
125
126 char headerName[cdc::hal::ISecureElement::RMEM_NAME_LEN + 1] = {};
127 if (entry.title[0]) {
128 copyText(headerName, sizeof(headerName), entry.title);
129 } else {
130 copyText(headerName, sizeof(headerName), "Password");
131 }
132
134 if (!se) return false;
135
136 auto res = se->rmemWriteWithHeader(
137 slot,
138 slots_.moduleId(),
139 headerName,
140 0,
141 reinterpret_cast<const uint8_t*>(&payload),
142 sizeof(payload)
143 );
144
145 if (res != cdc::hal::SeResult::OK) {
146 LOG_E(TAG, "Failed to write slot %u (SeResult=%u, payload=%u bytes)",
147 slot, static_cast<unsigned>(res),
148 static_cast<unsigned>(sizeof(payload)));
149 return false;
150 }
151
152 cdc::core::TropicStorage::instance().writeSlot(slots_.moduleId(), slot, headerName, 0);
153
154 return true;
155}
156
163bool PasswordStore::updateEntry(uint16_t slot, const PasswordEntry& entry) {
164 if (!slots_.hasSlotRange()) return false;
165 uint16_t physSlot = 0;
166 if (!toPhysicalSlot(slot, &physSlot)) return false;
167
168 PasswordPayload payload = {};
169 copyText(payload.title, sizeof(payload.title), entry.title);
170 copyText(payload.username, sizeof(payload.username), entry.username);
171 copyText(payload.password, sizeof(payload.password), entry.password);
172 copyText(payload.url, sizeof(payload.url), entry.url);
173 payload.totpSlot = entry.totpSlot;
174 copyText(payload.notes, sizeof(payload.notes), entry.notes);
175
176 char headerName[cdc::hal::ISecureElement::RMEM_NAME_LEN + 1] = {};
177 if (entry.title[0]) {
178 copyText(headerName, sizeof(headerName), entry.title);
179 } else {
180 copyText(headerName, sizeof(headerName), "Password");
181 }
182
184 if (!se) return false;
185
186 auto res = se->rmemWriteWithHeader(
187 physSlot,
188 slots_.moduleId(),
189 headerName,
190 0,
191 reinterpret_cast<const uint8_t*>(&payload),
192 sizeof(payload)
193 );
194
195 if (res != cdc::hal::SeResult::OK) {
196 LOG_E(TAG, "Failed to write slot %u", slot);
197 return false;
198 }
199
200 cdc::core::TropicStorage::instance().writeSlot(slots_.moduleId(), physSlot, headerName, 0);
201
202 return true;
203}
204
210bool PasswordStore::deleteEntry(uint16_t slot) {
211 if (!slots_.hasSlotRange()) return false;
212 uint16_t physSlot = 0;
213 if (!toPhysicalSlot(slot, &physSlot)) return false;
214
216 if (!se) return false;
217
218 auto res = se->rmemErase(physSlot);
219 if (res != cdc::hal::SeResult::OK) {
220 return false;
221 }
222
223 cdc::core::TropicStorage::instance().eraseSlot(slots_.moduleId(), physSlot);
224 return true;
225}
226
233int PasswordStore::compareTitles(const char* a, const char* b) {
234 if (!a) return b ? -1 : 0;
235 if (!b) return 1;
236 while (*a || *b) {
237 int ca = *a ? std::tolower(static_cast<unsigned char>(*a)) : 0;
238 int cb = *b ? std::tolower(static_cast<unsigned char>(*b)) : 0;
239 if (ca != cb) return ca - cb;
240 if (*a) ++a;
241 if (*b) ++b;
242 }
243 return 0;
244}
245
253bool PasswordStore::listEntriesSorted(EntryIndex* entries, uint16_t maxEntries, uint16_t* countOut) const {
254 if (!entries || !countOut) return false;
255 if (!slots_.hasSlotRange()) return false;
256
257 struct Ctx {
258 EntryIndex* entries;
259 uint16_t* count;
260 uint16_t max;
261 } ctx = { entries, countOut, maxEntries };
262
263 *countOut = 0;
264 auto cb = [](uint16_t slot, const cdc::core::TropicStorage::CacheEntry& entry, void* user) {
265 auto* ctx = static_cast<Ctx*>(user);
266 if (!ctx || !ctx->entries || !ctx->count) return;
267 if (*ctx->count >= ctx->max) return;
268
269 uint16_t logical = 0;
270 if (!PasswordStore::instance().toLogicalSlot(slot, &logical)) return;
271
272 uint16_t idx = *ctx->count;
273 copyText(ctx->entries[idx].title, sizeof(ctx->entries[idx].title), entry.name);
274 ctx->entries[idx].slot = logical;
275 (*ctx->count)++;
276 };
277
279 slots_.moduleId(), slots_.rmemStart(), slots_.rmemEnd(), cb, &ctx);
280
281 std::sort(entries, entries + *countOut, [](const EntryIndex& a, const EntryIndex& b) {
282 return PasswordStore::compareTitles(a.title, b.title) < 0;
283 });
284
285 return true;
286}
287
298bool PasswordStore::findByTitle(const char* title, uint16_t* logicalSlotOut) const {
299 if (!title || !logicalSlotOut) return false;
300 if (!slots_.hasSlotRange()) return false;
301
302 struct Ctx {
303 const char* target;
304 uint16_t slot;
305 bool found;
306 } ctx = { title, 0, false };
307
308 auto cb = [](uint16_t slot, const cdc::core::TropicStorage::CacheEntry& entry, void* user) {
309 auto* c = static_cast<Ctx*>(user);
310 if (c->found) return;
311 if (PasswordStore::compareTitles(entry.name, c->target) == 0) {
312 uint16_t logical = 0;
313 if (PasswordStore::instance().toLogicalSlot(slot, &logical)) {
314 c->slot = logical;
315 c->found = true;
316 }
317 }
318 };
319
321 slots_.moduleId(), slots_.rmemStart(), slots_.rmemEnd(), cb, &ctx);
322
323 if (!ctx.found) return false;
324 *logicalSlotOut = ctx.slot;
325 return true;
326}
327
333bool PasswordStore::findFreeLogicalSlot(uint16_t* logicalSlotOut) const {
334 if (!logicalSlotOut) return false;
335 uint16_t physSlot = 0;
336 if (!slots_.findFreeSlot(&physSlot)) return false;
337 return toLogicalSlot(physSlot, logicalSlotOut);
338}
339
340} // namespace cdc::mod_password
static const char * TAG
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
bool writeSlot(uint8_t moduleId, uint16_t slot, const char *name, uint8_t flags)
Writes or updates cached metadata entry for one slot.
bool eraseSlot(uint8_t moduleId, uint16_t slot)
Clears cached metadata entry for one slot.
static TropicStorage & instance()
Returns singleton instance of TROPIC metadata cache manager.
bool forEachSlot(uint8_t moduleId, SlotCallback cb, void *ctx)
Iterates all cached slots for one module across its allowed range.
static constexpr uint8_t RMEM_NAME_LEN
bool toPhysicalSlot(uint16_t logicalIndex, uint16_t *slotOut) const
static constexpr uint8_t PASSWORD_LEN
bool updateEntry(uint16_t slot, const PasswordEntry &entry)
Updates existing password entry.
static constexpr uint8_t TITLE_LEN
bool addEntry(const PasswordEntry &entry)
Adds a new password entry into first free slot.
static constexpr size_t NOTES_LEN
bool findFreeLogicalSlot(uint16_t *logicalSlotOut) const
Finds first free logical slot in this module's range.
static constexpr uint8_t USERNAME_LEN
static PasswordStore & instance()
Returns singleton password store instance.
bool listEntriesSorted(EntryIndex *entries, uint16_t maxEntries, uint16_t *countOut) const
Lists entries sorted alphabetically by title.
bool deleteEntry(uint16_t slot)
Deletes entry at logical slot index.
static constexpr uint8_t URL_LEN
bool toLogicalSlot(uint16_t slot, uint16_t *logicalIndexOut) const
static constexpr size_t PAYLOAD_MAX
bool findByTitle(const char *title, uint16_t *logicalSlotOut) const
Finds the logical slot of an entry with a matching title.
bool readEntry(uint16_t slot, PasswordEntry *out) const
Reads one password entry from secure-element storage.
void setSlotRange(const cdc::core::IModule::SlotRange &range)
Configures logical-to-physical slot mapping for password entries.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
static void copyText(char *dst, size_t dstSize, const char *src)
Copies text into bounded destination buffer.
char password[PASSWORD_PASSWORD_LEN+1]
char url[PASSWORD_URL_LEN+1]
char title[PASSWORD_TITLE_LEN+1]
char notes[PASSWORD_NOTES_LEN+1]
char username[PASSWORD_USERNAME_LEN+1]
char url[PasswordStore::URL_LEN]
char password[PasswordStore::PASSWORD_LEN]
char notes[PasswordStore::NOTES_LEN]
char username[PasswordStore::USERNAME_LEN]
char title[PasswordStore::TITLE_LEN]