CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
OathStore.cpp
Go to the documentation of this file.
1#include "mod_2fa/OathStore.h"
4#include "cdc_log.h"
5#include <mbedtls/md.h>
6#include <cstring>
7#include <ctime>
8#include <cstdint>
9
10static const char* TAG = "2FA";
11
12namespace cdc::mod_2fa {
13
17static constexpr uint8_t OATH_DIGITS_MIN = 6;
18static constexpr uint8_t OATH_DIGITS_MAX = 8;
19
23static constexpr uint32_t TOTP_PERIOD_MIN = 15;
24static constexpr uint32_t TOTP_PERIOD_MAX = 300;
25
34static bool validateOathParams(uint8_t type, uint8_t& digits, uint32_t& period,
35 uint8_t& algorithm) {
36 if (type > static_cast<uint8_t>(OathType::CR)) {
37 LOG_W(TAG, "Invalid OATH type %u", type);
38 return false;
39 }
40
41 // CR produces a raw HMAC and ignores digits/period; only the algorithm
42 // matters, and it is restricted to the HMAC sizes the transports support.
43 if (type == static_cast<uint8_t>(OathType::CR)) {
44 if (algorithm != static_cast<uint8_t>(OathAlgorithm::SHA1) &&
45 algorithm != static_cast<uint8_t>(OathAlgorithm::SHA256)) {
46 LOG_W(TAG, "CR algorithm %u unsupported (expected SHA1 or SHA256)", algorithm);
47 return false;
48 }
49 return true;
50 }
51
52 if (digits == 0) {
54 } else if (digits < OATH_DIGITS_MIN || digits > OATH_DIGITS_MAX) {
55 LOG_W(TAG, "Invalid OATH digits %u, expected %u-%u",
57 return false;
58 }
59
60 // Period only constrains TOTP; HOTP ignores it.
61 if (type == static_cast<uint8_t>(OathType::TOTP)) {
62 if (period == 0) {
64 } else if (period < TOTP_PERIOD_MIN || period > TOTP_PERIOD_MAX) {
65 LOG_W(TAG, "Invalid TOTP period %lu, expected %u-%u",
66 static_cast<unsigned long>(period), TOTP_PERIOD_MIN, TOTP_PERIOD_MAX);
67 return false;
68 }
69 }
70
71 if (algorithm > static_cast<uint8_t>(OathAlgorithm::SHA512)) {
72 LOG_W(TAG, "Invalid OATH algorithm %u", algorithm);
73 return false;
74 }
75
76 return true;
77}
78
79#pragma pack(push, 1)
81 uint8_t type;
84 uint8_t secretLen;
85 uint8_t digits;
86 uint32_t period;
87 uint64_t counter;
88 uint8_t algorithm;
89 uint8_t flags;
90};
91#pragma pack(pop)
92
98static int base32CharValue(char c) {
99 if (c >= 'A' && c <= 'Z') return c - 'A';
100 if (c >= 'a' && c <= 'z') return c - 'a';
101 if (c >= '2' && c <= '7') return c - '2' + 26;
102 return -1;
103}
104
112static int base32Decode(const char* encoded, uint8_t* out, size_t outMax) {
113 if (!encoded || !out) return -1;
114
115 int buffer = 0;
116 int bitsLeft = 0;
117 size_t count = 0;
118
119 for (const char* p = encoded; *p; ++p) {
120 if (*p == '=' || *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
121 continue;
122 }
123 int value = base32CharValue(*p);
124 if (value < 0) {
125 return -1;
126 }
127 buffer = (buffer << 5) | value;
128 bitsLeft += 5;
129 if (bitsLeft >= 8) {
130 bitsLeft -= 8;
131 if (count >= outMax) {
132 return -1;
133 }
134 out[count++] = static_cast<uint8_t>((buffer >> bitsLeft) & 0xFF);
135 }
136 }
137
138 return static_cast<int>(count);
139}
140
141static const uint32_t POWERS_10[] = {
142 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000
143};
144
150 static OathStore inst;
151 return inst;
152}
153
159 slots_.setSlotRange(range);
160}
161
168bool OathStore::readAccount(uint16_t slot, OathEntry* out) {
169 if (!out) return false;
170 if (!slots_.hasSlotRange()) return false;
171 uint16_t physSlot = 0;
172 if (!toPhysicalSlot(slot, &physSlot)) return false;
173
175 if (!se) return false;
176
177 cdc::hal::ISecureElement::RMemHeader header = {};
178 uint8_t payloadBuf[sizeof(OathPayload)] = {};
179 uint16_t payloadLen = 0;
180
181 auto res = se->rmemReadWithHeader(physSlot, &header, payloadBuf, sizeof(payloadBuf), &payloadLen);
182 if (res != cdc::hal::SeResult::OK) {
183 return false;
184 }
185
186 if (header.moduleId != slots_.moduleId()) {
187 return false;
188 }
189
190 // Reject records that do not match the current record layout (NO-MIGRATION:
191 // legacy TOTP entries are simply not readable and get reinitialized).
192 if (payloadLen != sizeof(OathPayload)) {
193 LOG_W(TAG, "Slot %u payload size mismatch (%u != %u), rejecting",
194 physSlot, payloadLen, static_cast<unsigned>(sizeof(OathPayload)));
195 return false;
196 }
197
198 OathPayload payload = {};
199 memcpy(&payload, payloadBuf, sizeof(payload));
200
201 memset(out, 0, sizeof(*out));
202 header.name[cdc::hal::ISecureElement::RMEM_NAME_LEN - 1] = '\0';
203 payload.issuer[sizeof(payload.issuer) - 1] = '\0';
204 out->type = payload.type;
205 strncpy(out->name, header.name, sizeof(out->name) - 1);
206 strncpy(out->issuer, payload.issuer, sizeof(out->issuer) - 1);
207 memcpy(out->secret, payload.secret, sizeof(out->secret));
208 // Clamp the persisted length to the fixed buffer so a tampered/corrupt slot
209 // cannot drive an out-of-bounds read in later consumers (e.g. base32Encode).
210 out->secretLen = payload.secretLen > sizeof(out->secret)
211 ? static_cast<uint8_t>(sizeof(out->secret))
212 : payload.secretLen;
213 out->digits = payload.digits ? payload.digits : DEFAULT_DIGITS;
214 out->period = payload.period ? payload.period : DEFAULT_PERIOD;
215 out->counter = payload.counter;
216 out->algorithm = payload.algorithm;
217 out->flags = payload.flags;
218
219 return true;
220}
221
237static bool writePayload(uint16_t physSlot, const char* name, uint8_t type,
238 const char* issuer, const uint8_t* secret, uint8_t secretLen,
239 uint8_t digits, uint32_t period, uint64_t counter,
240 uint8_t algorithm, uint8_t flags) {
241 OathPayload payload = {};
242 payload.type = type;
243 if (issuer) {
244 strncpy(payload.issuer, issuer, sizeof(payload.issuer) - 1);
245 }
246 memcpy(payload.secret, secret, secretLen);
247 payload.secretLen = secretLen;
248 payload.digits = digits ? digits : OathStore::DEFAULT_DIGITS;
249 payload.period = period ? period : OathStore::DEFAULT_PERIOD;
250 payload.counter = counter;
251 payload.algorithm = algorithm;
252 payload.flags = flags;
253
255 if (!se) return false;
256
258 auto res = se->rmemWriteWithHeader(
259 physSlot,
260 moduleId,
261 name,
262 flags,
263 reinterpret_cast<const uint8_t*>(&payload),
264 sizeof(payload)
265 );
266
267 if (res != cdc::hal::SeResult::OK) {
268 LOG_E(TAG, "Failed to write slot %u", physSlot);
269 return false;
270 }
271
273 return true;
274}
275
289bool OathStore::addAccount(uint8_t type, const char* name, const char* issuer,
290 const char* secretBase32, uint8_t digits, uint32_t period,
291 uint8_t algorithm, uint64_t counter, uint8_t flags) {
292 if (!name || !secretBase32) return false;
293 if (!slots_.hasSlotRange()) return false;
294
296 LOG_W(TAG, "OATH name too long (max %u)",
298 return false;
299 }
300
301 if (!validateOathParams(type, digits, period, algorithm)) {
302 return false;
303 }
304
305 uint16_t slot = 0;
306 if (!slots_.findFreeSlot(&slot)) {
307 LOG_W(TAG, "No free OATH slots");
308 return false;
309 }
310
311 uint8_t secret[SECRET_LEN];
312 int secretLen = base32Decode(secretBase32, secret, SECRET_LEN);
313 if (secretLen <= 0) {
314 LOG_E(TAG, "Invalid Base32 secret");
315 return false;
316 }
317
318 return writePayload(slot, name, type, issuer, secret,
319 static_cast<uint8_t>(secretLen), digits, period,
320 counter, algorithm, flags);
321}
322
337bool OathStore::updateAccount(uint16_t slot, uint8_t type, const char* name, const char* issuer,
338 const char* secretBase32, uint8_t digits, uint32_t period,
339 uint8_t algorithm, uint64_t counter, uint8_t flags) {
340 if (!name || !secretBase32) return false;
341 if (!slots_.hasSlotRange()) return false;
343 LOG_W(TAG, "OATH name too long (max %u)",
345 return false;
346 }
347 if (!validateOathParams(type, digits, period, algorithm)) {
348 return false;
349 }
350 uint16_t physSlot = 0;
351 if (!toPhysicalSlot(slot, &physSlot)) return false;
352
353 uint8_t secret[SECRET_LEN];
354 int secretLen = base32Decode(secretBase32, secret, SECRET_LEN);
355 if (secretLen <= 0) {
356 LOG_E(TAG, "Invalid Base32 secret");
357 return false;
358 }
359
360 return writePayload(physSlot, name, type, issuer, secret,
361 static_cast<uint8_t>(secretLen), digits, period,
362 counter, algorithm, flags);
363}
364
370bool OathStore::deleteAccount(uint16_t slot) {
371 if (!slots_.hasSlotRange()) return false;
372 uint16_t physSlot = 0;
373 if (!toPhysicalSlot(slot, &physSlot)) return false;
375 if (!se) return false;
376
377 auto res = se->rmemErase(physSlot);
378 if (res != cdc::hal::SeResult::OK) {
379 return false;
380 }
381
382 cdc::core::TropicStorage::instance().eraseSlot(slots_.moduleId(), physSlot);
383
384 return true;
385}
386
401uint32_t OathStore::generate(const uint8_t* secret, size_t secretLen, uint64_t counter,
402 uint8_t digits, OathAlgorithm algorithm) const {
403 if (!secret || secretLen == 0 || secretLen > SECRET_LEN) {
404 return 0;
405 }
406
407 if (digits < OATH_DIGITS_MIN || digits > OATH_DIGITS_MAX) {
408 digits = DEFAULT_DIGITS;
409 }
410
411 uint8_t counterBytes[8];
412 uint64_t c = counter;
413 for (int i = 7; i >= 0; i--) {
414 counterBytes[i] = static_cast<uint8_t>(c & 0xFF);
415 c >>= 8;
416 }
417
418 uint8_t hmac[64] = {};
419 size_t hmacLen = 0;
420 if (!hmacCompute(algorithm, secret, secretLen, counterBytes, 8, hmac, &hmacLen)) {
421 return 0;
422 }
423
424 int offset = hmac[hmacLen - 1] & 0x0F;
425 uint32_t binary =
426 ((hmac[offset] & 0x7F) << 24) |
427 ((hmac[offset + 1] & 0xFF) << 16) |
428 ((hmac[offset + 2] & 0xFF) << 8) |
429 (hmac[offset + 3] & 0xFF);
430
431 return binary % POWERS_10[digits];
432}
433
445bool OathStore::hmacCompute(OathAlgorithm algo, const uint8_t* key, size_t keyLen,
446 const uint8_t* data, size_t dataLen,
447 uint8_t* output, size_t* outputLen) const {
448 mbedtls_md_type_t mdType;
449 size_t expectedLen;
450
451 switch (algo) {
453 mdType = MBEDTLS_MD_SHA256;
454 expectedLen = 32;
455 break;
457 mdType = MBEDTLS_MD_SHA512;
458 expectedLen = 64;
459 break;
460 default:
461 mdType = MBEDTLS_MD_SHA1;
462 expectedLen = 20;
463 break;
464 }
465
466 const mbedtls_md_info_t* mdInfo = mbedtls_md_info_from_type(mdType);
467 if (!mdInfo) {
468 return false;
469 }
470
471 int ret = mbedtls_md_hmac(mdInfo, key, keyLen, data, dataLen, output);
472 if (ret != 0) {
473 return false;
474 }
475
476 if (outputLen) {
477 *outputLen = expectedLen;
478 }
479 return true;
480}
481
489bool OathStore::persistCounter(uint16_t slot, const OathEntry& entry, uint64_t counter) {
490 uint16_t physSlot = 0;
491 if (!toPhysicalSlot(slot, &physSlot)) return false;
492 return writePayload(physSlot, entry.name, entry.type,
493 entry.issuer[0] ? entry.issuer : nullptr,
494 entry.secret, entry.secretLen, entry.digits, entry.period,
495 counter, entry.algorithm, entry.flags);
496}
497
505static void formatCode(uint32_t code, uint8_t digits, char* codeOut, size_t codeOutLen) {
506 char fmt[8];
507 snprintf(fmt, sizeof(fmt), "%%0%ulu",
508 digits >= OATH_DIGITS_MIN && digits <= OATH_DIGITS_MAX ? digits : OATH_DIGITS_MIN);
509 snprintf(codeOut, codeOutLen, fmt, static_cast<unsigned long>(code));
510}
511
520int8_t OathStore::generateCode(uint16_t slot, char* codeOut, size_t codeOutLen) {
521 if (!codeOut || codeOutLen == 0) return -1;
522 codeOut[0] = '\0';
523
524 OathEntry account = {};
525 if (!readAccount(slot, &account)) {
526 return -1;
527 }
528
529 if (account.type == static_cast<uint8_t>(OathType::HOTP)) {
530 uint64_t counter = account.counter;
531 uint32_t code = generate(account.secret, account.secretLen, counter,
532 account.digits,
533 static_cast<OathAlgorithm>(account.algorithm));
534 // Persist the incremented counter so the next code differs and survives
535 // a reboot (RFC 4226 moving factor).
536 if (!persistCounter(slot, account, counter + 1)) {
537 LOG_E(TAG, "Failed to persist HOTP counter for slot %u", slot);
538 return -1;
539 }
540 formatCode(code, account.digits, codeOut, codeOutLen);
541 return 0;
542 }
543
544 // TOTP path.
545 if (!isTimeValid()) {
546 snprintf(codeOut, codeOutLen, "------");
547 return -1;
548 }
549
550 uint32_t period = account.period ? account.period : DEFAULT_PERIOD;
551 uint64_t counter = static_cast<uint64_t>(time(nullptr)) / period;
552 uint32_t code = generate(account.secret, account.secretLen, counter,
553 account.digits,
554 static_cast<OathAlgorithm>(account.algorithm));
555 formatCode(code, account.digits, codeOut, codeOutLen);
556 return static_cast<int8_t>(timeRemaining(account.period));
557}
558
564uint8_t OathStore::timeRemaining(uint32_t period) const {
565 if (period == 0) period = DEFAULT_PERIOD;
566 return static_cast<uint8_t>(period - (time(nullptr) % period));
567}
568
574 time_t now = time(nullptr);
575 struct tm timeinfo;
576 localtime_r(&now, &timeinfo);
577 return timeinfo.tm_year >= 124; // 2024+
578}
579
586bool OathStore::findByName(const char* name, uint16_t* slotOut) const {
587 if (!name || !slotOut) return false;
588 if (!slots_.hasSlotRange()) return false;
589
590 struct Ctx {
591 const char* target;
592 uint16_t slot;
593 bool found;
594 } ctx = { name, 0, false };
595
596 auto cb = [](uint16_t slot, const cdc::core::TropicStorage::CacheEntry& entry, void* user) {
597 auto* c = static_cast<Ctx*>(user);
598 if (c->found) return;
599 if (strncmp(entry.name, c->target, cdc::hal::ISecureElement::RMEM_NAME_LEN) == 0) {
600 uint16_t logical = 0;
601 if (OathStore::instance().toLogicalSlot(slot, &logical)) {
602 c->slot = logical;
603 c->found = true;
604 }
605 }
606 };
607
609 slots_.moduleId(),
610 slots_.rmemStart(),
611 slots_.rmemEnd(),
612 cb, &ctx);
613
614 if (!ctx.found) return false;
615 *slotOut = ctx.slot;
616 return true;
617}
618
634int OathStore::challengeResponse(const char* entryName, const uint8_t* challenge, size_t clen,
635 uint8_t* out, bool* touchRequiredOut) {
636 if (!entryName || !out) return -1;
637 if (clen != 0 && !challenge) return -1;
638
639 uint16_t slot = 0;
640 if (!findByName(entryName, &slot)) {
641 return -1;
642 }
643
644 OathEntry entry = {};
645 if (!readAccount(slot, &entry)) {
646 return -1;
647 }
648
649 if (entry.type != static_cast<uint8_t>(OathType::CR)) {
650 LOG_W(TAG, "Entry '%s' is not a CR entry", entryName);
651 return -1;
652 }
653 if (entry.secretLen == 0) {
654 return -1;
655 }
656
657 auto algo = static_cast<OathAlgorithm>(entry.algorithm);
658 if (algo != OathAlgorithm::SHA1 && algo != OathAlgorithm::SHA256) {
659 LOG_W(TAG, "CR entry '%s' has unsupported algorithm %u", entryName, entry.algorithm);
660 return -1;
661 }
662
663 size_t outLen = 0;
664 if (!hmacCompute(algo, entry.secret, entry.secretLen, challenge, clen, out, &outLen)) {
665 return -1;
666 }
667
668 if (touchRequiredOut) {
669 *touchRequiredOut = (entry.flags & OathFlag::TOUCH_REQUIRED) != 0;
670 }
671 return static_cast<int>(outLen);
672}
673
679bool OathStore::findUsbCrSlot(uint16_t* slotOut) const {
680 if (!slotOut) return false;
681 if (!slots_.hasSlotRange()) return false;
682
683 struct Ctx {
684 uint16_t slot;
685 bool found;
686 } ctx = { 0, false };
687
688 auto cb = [](uint16_t slot, const cdc::core::TropicStorage::CacheEntry&, void* user) {
689 auto* c = static_cast<Ctx*>(user);
690 if (c->found) return;
691 uint16_t logical = 0;
692 if (!OathStore::instance().toLogicalSlot(slot, &logical)) return;
693 OathEntry entry = {};
694 if (!OathStore::instance().readAccount(logical, &entry)) return;
695 if (entry.type == static_cast<uint8_t>(OathType::CR) &&
696 (entry.flags & OathFlag::USB_CR_SLOT) != 0) {
697 c->slot = logical;
698 c->found = true;
699 }
700 };
701
703 slots_.moduleId(),
704 slots_.rmemStart(),
705 slots_.rmemEnd(),
706 cb, &ctx);
707
708 if (!ctx.found) return false;
709 *slotOut = ctx.slot;
710 return true;
711}
712
721int OathStore::challengeResponseUsbSlot(const uint8_t* challenge, size_t clen,
722 uint8_t* out, bool* touchRequiredOut) {
723 if (!out) return -1;
724
725 uint16_t slot = 0;
726 if (!findUsbCrSlot(&slot)) {
727 return -1;
728 }
729
730 OathEntry entry = {};
731 if (!readAccount(slot, &entry)) {
732 return -1;
733 }
734 return challengeResponse(entry.name, challenge, clen, out, touchRequiredOut);
735}
736
741void OathStore::clearUsbCrFlagExcept(uint16_t keepSlot) {
742 if (!slots_.hasSlotRange()) return;
743
744 struct Ctx {
745 uint16_t keep;
746 } ctx = { keepSlot };
747
748 auto cb = [](uint16_t slot, const cdc::core::TropicStorage::CacheEntry&, void* user) {
749 auto* c = static_cast<Ctx*>(user);
750 auto& store = OathStore::instance();
751 uint16_t logical = 0;
752 if (!store.toLogicalSlot(slot, &logical)) return;
753 if (logical == c->keep) return;
754 OathEntry entry = {};
755 if (!store.readAccount(logical, &entry)) return;
756 if ((entry.flags & OathFlag::USB_CR_SLOT) == 0) return;
757 uint8_t cleared = static_cast<uint8_t>(entry.flags & ~OathFlag::USB_CR_SLOT);
758 uint16_t physSlot = 0;
759 if (!store.toPhysicalSlot(logical, &physSlot)) return;
760 writePayload(physSlot, entry.name, entry.type,
761 entry.issuer[0] ? entry.issuer : nullptr,
762 entry.secret, entry.secretLen, entry.digits, entry.period,
763 entry.counter, entry.algorithm, cleared);
764 };
765
767 slots_.moduleId(),
768 slots_.rmemStart(),
769 slots_.rmemEnd(),
770 cb, &ctx);
771}
772
773} // namespace cdc::mod_2fa
static const char * TAG
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_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
int challengeResponseUsbSlot(const uint8_t *challenge, size_t clen, uint8_t *out, bool *touchRequiredOut=nullptr)
Computes the raw HMAC challenge-response for the USB-CR slot entry.
bool toPhysicalSlot(uint16_t logicalIndex, uint16_t *slotOut) const
Definition OathStore.h:154
bool isTimeValid() const
Returns whether system time is considered valid for TOTP.
void clearUsbCrFlagExcept(uint16_t keepSlot)
Clears the USB-CR-slot flag on every entry except keepSlot.
int challengeResponse(const char *entryName, const uint8_t *challenge, size_t clen, uint8_t *out, bool *touchRequiredOut=nullptr)
Computes the raw HMAC challenge-response for a CR entry by name.
static constexpr uint8_t SECRET_LEN
Definition OathStore.h:59
bool toLogicalSlot(uint16_t slot, uint16_t *logicalIndexOut) const
Definition OathStore.h:157
uint8_t moduleId() const
Definition OathStore.h:161
bool findUsbCrSlot(uint16_t *slotOut) const
Finds the logical slot of the CR entry flagged as the USB-CR slot.
bool addAccount(uint8_t type, const char *name, const char *issuer, const char *secretBase32, uint8_t digits, uint32_t period, uint8_t algorithm, uint64_t counter, uint8_t flags=0)
Adds a new OATH entry from a Base32 secret.
static OathStore & instance()
Returns singleton OATH store instance.
static constexpr uint8_t DEFAULT_DIGITS
Definition OathStore.h:60
int8_t generateCode(uint16_t slot, char *codeOut, size_t codeOutLen)
Renders the current code for an entry into codeOut.
bool updateAccount(uint16_t slot, uint8_t type, const char *name, const char *issuer, const char *secretBase32, uint8_t digits, uint32_t period, uint8_t algorithm, uint64_t counter, uint8_t flags=0)
Updates an existing OATH entry.
static constexpr uint32_t DEFAULT_PERIOD
Definition OathStore.h:61
uint8_t timeRemaining(uint32_t period) const
Returns seconds remaining in current TOTP time step.
void setSlotRange(const cdc::core::IModule::SlotRange &range)
Configures logical-to-physical slot mapping for OATH entries.
static constexpr uint8_t ISSUER_LEN
Definition OathStore.h:58
bool findByName(const char *name, uint16_t *slotOut) const
Finds a logical slot index by account name.
bool readAccount(uint16_t slot, OathEntry *out)
Reads one OATH entry from secure-element storage.
bool deleteAccount(uint16_t slot)
Deletes account in logical slot.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
Per-entry flag bits stored in OathEntry::flags.
Definition OathStore.h:32
constexpr uint8_t USB_CR_SLOT
Designate this CR entry as the USB OTP-HID slot-2 responder.
Definition OathStore.h:36
constexpr uint8_t TOUCH_REQUIRED
Require an on-device touch confirmation before answering a CR request.
Definition OathStore.h:34
static bool writePayload(uint16_t physSlot, const char *name, uint8_t type, const char *issuer, const uint8_t *secret, uint8_t secretLen, uint8_t digits, uint32_t period, uint64_t counter, uint8_t algorithm, uint8_t flags)
Builds and persists an OATH payload to a physical slot.
static bool validateOathParams(uint8_t type, uint8_t &digits, uint32_t &period, uint8_t &algorithm)
Validates OATH entry parameters and clamps to defaults when invalid.
Definition OathStore.cpp:34
static constexpr uint8_t OATH_DIGITS_MIN
Allowed OATH digit count range per RFC 4226 / RFC 6238.
Definition OathStore.cpp:17
static constexpr uint8_t OATH_DIGITS_MAX
Definition OathStore.cpp:18
static int base32CharValue(char c)
Converts one Base32 character into 5-bit value.
Definition OathStore.cpp:98
static constexpr uint32_t TOTP_PERIOD_MAX
Definition OathStore.cpp:24
static int base32Decode(const char *encoded, uint8_t *out, size_t outMax)
Decodes Base32 secret into raw bytes.
static constexpr uint32_t TOTP_PERIOD_MIN
Allowed TOTP period range in seconds.
Definition OathStore.cpp:23
static const uint32_t POWERS_10[]
static void formatCode(uint32_t code, uint8_t digits, char *codeOut, size_t codeOutLen)
Formats a numeric code into codeOut with leading zeros.
OathAlgorithm
Hash algorithm used by an OATH entry's HMAC engine.
Definition OathStore.h:14
Unified OATH credential record (TOTP, HOTP, and reserved CR).
Definition OathStore.h:42
uint8_t digits
Output digit count (TOTP/HOTP).
Definition OathStore.h:49
uint8_t secret[64]
Raw HMAC key.
Definition OathStore.h:46
uint8_t algorithm
OathAlgorithm value.
Definition OathStore.h:48
uint8_t secretLen
Valid bytes in secret.
Definition OathStore.h:47
uint64_t counter
Moving factor (HOTP only).
Definition OathStore.h:51
uint8_t type
OathType discriminator.
Definition OathStore.h:43
uint32_t period
TOTP step in seconds (TOTP only).
Definition OathStore.h:50
char issuer[32+1]
Optional issuer text.
Definition OathStore.h:45
char name[16+1]
Account label.
Definition OathStore.h:44
uint8_t flags
Reserved entry flags.
Definition OathStore.h:52
uint8_t secret[OathStore::SECRET_LEN]
Definition OathStore.cpp:83
char issuer[OathStore::ISSUER_LEN]
Definition OathStore.cpp:82