CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
PinManager.cpp
Go to the documentation of this file.
1
7
11#include "cdc_log.h"
12#include "mbedtls/sha256.h"
13#include "mbedtls/ecdsa.h"
14#include "mbedtls/ecp.h"
15#include "mbedtls/bignum.h"
16#include "esp_random.h"
17#include "esp_timer.h"
18#include <cstring>
19
20static const char* TAG = "PinManager";
21
23static constexpr size_t SHA256_DIGEST_SIZE = 32;
24
25namespace cdc::core {
26
31PinManager& PinManager::instance() {
32 static PinManager instance;
33 return instance;
34}
35
41 if (pinLoaded_) return true;
42
43 if (!loadFromStorage()) {
44 LOG_I(TAG, "Loading default PINs (storage empty or unreadable)");
45 loadDefaults();
46 saveToStorage();
47 }
48 pinLoaded_ = true;
49
50 badgeRetries_ = badgeLocked_ ? 0 : 1;
52 LOG_I(TAG, "Badge state after init: locked=%d retries=%u pinSet=%d",
53 badgeLocked_, badgeRetries_, badgePinIsSet_);
54 return true;
55}
56
60void PinManager::loadDefaults() {
61 // Badge/FIDO2 hash
62 computeBadgeHash(DEFAULT_BADGE_PIN, badgeHash_);
63 badgeRetries_ = MAX_RETRIES;
64 badgeLocked_ = false;
65
66 // Generate random salts
67 generateSalt(pw1Salt_);
68 generateSalt(pw3Salt_);
69
70 // Compute KDF hashes with salts
71 computeKdfHash(DEFAULT_PW1, pw1Salt_, pw1Hash_);
72 computeKdfHash(DEFAULT_PW3, pw3Salt_, pw3Hash_);
73
74 iterations_ = DEFAULT_ITERATIONS;
75 pw1Retries_ = MAX_RETRIES;
76 pw3Retries_ = MAX_RETRIES;
77 badgePinIsSet_ = false;
78
79 // Duress PIN is opt-in: defaults leave it disarmed.
80 duressSet_ = false;
81 memset(duressSalt_, 0, sizeof(duressSalt_));
82 memset(duressHash_, 0, sizeof(duressHash_));
83
84 LOG_I(TAG, "Loaded default PINs");
85}
86
91void PinManager::generateSalt(uint8_t* salt) {
92 // Try to get random from SE, fallback to ESP random
94 if (se && se->isSessionActive() && se->getRandom(salt, SALT_SIZE)) {
95 return;
96 }
97 // Fallback to ESP32 RNG
98 esp_fill_random(salt, SALT_SIZE);
99}
100
109
127 const uint8_t* payload, size_t payload_len,
128 const uint8_t* sig, size_t sig_len) {
129 if (sig_len != 64) return false;
130 uint8_t pub_raw[64];
133 LOG_W(TAG, "Attestation pubkey read failed");
134 return false;
135 }
136 if (curve != hal::EccCurve::P256) {
137 LOG_W(TAG, "Attestation key is not P-256");
138 return false;
139 }
140
141 uint8_t pub_sec1[65];
142 pub_sec1[0] = 0x04;
143 memcpy(pub_sec1 + 1, pub_raw, 64);
144
145 uint8_t hash[SHA256_DIGEST_SIZE];
146 mbedtls_sha256(payload, payload_len, hash, 0);
147
148 mbedtls_ecp_group grp;
149 mbedtls_ecp_point Q;
150 mbedtls_mpi r, s;
151 mbedtls_ecp_group_init(&grp);
152 mbedtls_ecp_point_init(&Q);
153 mbedtls_mpi_init(&r);
154 mbedtls_mpi_init(&s);
155
156 bool ok = false;
157 do {
158 if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) != 0) break;
159 if (mbedtls_ecp_point_read_binary(&grp, &Q, pub_sec1, sizeof(pub_sec1)) != 0) break;
160 if (mbedtls_mpi_read_binary(&r, sig + 0, 32) != 0) break;
161 if (mbedtls_mpi_read_binary(&s, sig + 32, 32) != 0) break;
162 ok = (mbedtls_ecdsa_verify(&grp, hash, SHA256_DIGEST_SIZE, &Q, &r, &s) == 0);
163 } while (0);
164
165 mbedtls_mpi_free(&r);
166 mbedtls_mpi_free(&s);
167 mbedtls_ecp_point_free(&Q);
168 mbedtls_ecp_group_free(&grp);
169 return ok;
170}
171
172bool PinManager::loadFromStorage() {
174 if (!se || !se->isSessionActive()) {
175 LOG_W(TAG, "SE session not active");
176 return false;
177 }
178
179 uint8_t data[STORAGE_SIZE];
180 uint16_t actualLen = 0;
181
182 hal::SeResult result = se->rmemRead(RMEM_SLOT_PIN, data, STORAGE_SIZE, &actualLen);
183 if (result != hal::SeResult::OK) {
184 LOG_D(TAG, "No PIN data in R-Memory (read err=%d)",
185 static_cast<int>(result));
186 return false;
187 }
188
189 // Only the signed format is accepted. Any other state (wrong magic,
190 // wrong length, or invalid signature) falls back to defaults so the
191 // slot ends up freshly signed by the chip-bound attestation key.
192 if (actualLen != STORAGE_SIZE || data[0] != MAGIC) {
193 LOG_W(TAG, "PIN storage unrecognised (len=%u magic=0x%02X) - using defaults",
194 actualLen, actualLen > 0 ? data[0] : 0);
195 return false;
196 }
197 if (!verify_payload_signature(se, data, PAYLOAD_SIZE,
198 data + PAYLOAD_SIZE, SIGNATURE_SIZE)) {
199 LOG_W(TAG, "PIN storage signature invalid - re-initializing");
200 return false;
201 }
202
203 size_t pos = 1;
204
205 // Badge hash
206 memcpy(badgeHash_, &data[pos], BADGE_HASH_SIZE);
207 pos += BADGE_HASH_SIZE;
208
209 // Badge locked flag (counter itself is RAM-only)
210 badgeLocked_ = (data[pos++] != 0);
211
212 // KDF params (skip algorithm bytes, we know them)
213 pos += 2; // KDF algo + Hash algo
214
215 // Iteration count (big endian)
216 iterations_ = (data[pos] << 24) | (data[pos+1] << 16) | (data[pos+2] << 8) | data[pos+3];
217 pos += 4;
218
219 // Salts
220 memcpy(pw1Salt_, &data[pos], SALT_SIZE);
221 pos += SALT_SIZE;
222 memcpy(pw3Salt_, &data[pos], SALT_SIZE);
223 pos += SALT_SIZE;
224
225 // Hashes
226 memcpy(pw1Hash_, &data[pos], KDF_HASH_SIZE);
227 pos += KDF_HASH_SIZE;
228 memcpy(pw3Hash_, &data[pos], KDF_HASH_SIZE);
229 pos += KDF_HASH_SIZE;
230
231 // Retries
232 pw1Retries_ = data[pos++];
233 pw3Retries_ = data[pos++];
234
235 // Duress / self-destruct PIN
236 duressSet_ = (data[pos++] != 0);
237 memcpy(duressSalt_, &data[pos], SALT_SIZE);
238 pos += SALT_SIZE;
239 memcpy(duressHash_, &data[pos], KDF_HASH_SIZE);
240 pos += KDF_HASH_SIZE;
241
242 // Mirror starts in sync with whatever is on the chip.
243 persistedBadgeLocked_ = badgeLocked_;
244 persistedPw1Retries_ = pw1Retries_;
245 persistedPw3Retries_ = pw3Retries_;
246
247 // Check if badge PIN differs from default
248 uint8_t defaultHash[BADGE_HASH_SIZE];
249 computeBadgeHash(DEFAULT_BADGE_PIN, defaultHash);
250 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash, BADGE_HASH_SIZE);
251
252 LOG_I(TAG, "Loaded PINs from R-Memory (Badge locked=%s, PW1=%d, PW3=%d retries)",
253 badgeLocked_ ? "yes" : "no", pw1Retries_, pw3Retries_);
254 return true;
255}
256
261bool PinManager::saveToStorage() {
262 hal::ISecureElement* se = hal::getSecureElementInstance();
263 if (!se || !se->isSessionActive()) {
264 LOG_E(TAG, "SE session not active");
265 return false;
266 }
267
268 uint8_t data[STORAGE_SIZE];
269 size_t pos = 0;
270
271 data[pos++] = MAGIC;
272
273 // Badge hash
274 memcpy(&data[pos], badgeHash_, BADGE_HASH_SIZE);
275 pos += BADGE_HASH_SIZE;
276
277 // Badge locked flag (retry counter is RAM-only)
278 data[pos++] = badgeLocked_ ? 0x01 : 0x00;
279
280 // KDF params
281 data[pos++] = KDF_ITERSALTED_S2K;
282 data[pos++] = HASH_SHA256;
283
284 // Iteration count (big endian)
285 data[pos++] = (iterations_ >> 24) & 0xFF;
286 data[pos++] = (iterations_ >> 16) & 0xFF;
287 data[pos++] = (iterations_ >> 8) & 0xFF;
288 data[pos++] = iterations_ & 0xFF;
289
290 // Salts
291 memcpy(&data[pos], pw1Salt_, SALT_SIZE);
292 pos += SALT_SIZE;
293 memcpy(&data[pos], pw3Salt_, SALT_SIZE);
294 pos += SALT_SIZE;
295
296 // Hashes
297 memcpy(&data[pos], pw1Hash_, KDF_HASH_SIZE);
298 pos += KDF_HASH_SIZE;
299 memcpy(&data[pos], pw3Hash_, KDF_HASH_SIZE);
300 pos += KDF_HASH_SIZE;
301
302 // Retries
303 data[pos++] = pw1Retries_;
304 data[pos++] = pw3Retries_;
305
306 // Duress / self-destruct PIN
307 data[pos++] = duressSet_ ? 0x01 : 0x00;
308 memcpy(&data[pos], duressSalt_, SALT_SIZE);
309 pos += SALT_SIZE;
310 memcpy(&data[pos], duressHash_, KDF_HASH_SIZE);
311 pos += KDF_HASH_SIZE;
312
313 // pos must now equal PAYLOAD_SIZE — append a P-256 ECDSA signature over
314 // bytes [0..PAYLOAD_SIZE) using the chip-bound attestation key in slot 0.
315 // A subsequent load that finds the signature invalid (because the slot 0
316 // key was regenerated or the payload was tampered with) will silently
317 // re-initialize the storage with defaults, exactly as requested by spec.
318 if (pos != PAYLOAD_SIZE) {
319 LOG_E(TAG, "Payload size mismatch (built=%zu, expected=%u)", pos, PAYLOAD_SIZE);
320 return false;
321 }
322 size_t sig_len = SIGNATURE_SIZE;
323 hal::SeResult sign_res = se->ecdsaSign(ATTESTATION_ECC_SLOT,
324 data, PAYLOAD_SIZE,
325 data + PAYLOAD_SIZE, &sig_len);
326 if (sign_res != hal::SeResult::OK || sig_len != SIGNATURE_SIZE) {
327 LOG_E(TAG, "Attestation sign failed (%d)", static_cast<int>(sign_res));
328 return false;
329 }
330
331 se->rmemErase(RMEM_SLOT_PIN);
332
333 hal::SeResult result = se->rmemWrite(RMEM_SLOT_PIN, data, STORAGE_SIZE);
334 if (result != hal::SeResult::OK) {
335 LOG_E(TAG, "R-Memory write failed");
336 return false;
337 }
338
339 persistedBadgeLocked_ = badgeLocked_;
340 persistedPw1Retries_ = pw1Retries_;
341 persistedPw3Retries_ = pw3Retries_;
342
343 LOG_D(TAG, "PINs saved to R-Memory slot %d (signed, %u bytes)",
344 RMEM_SLOT_PIN, STORAGE_SIZE);
345 return true;
346}
347
354bool PinManager::computeBadgeHash(const char* pin, uint8_t* hashOut) {
355 if (!pin || !hashOut) return false;
356
357 uint8_t fullHash[SHA256_DIGEST_SIZE];
358 mbedtls_sha256_context ctx;
359 mbedtls_sha256_init(&ctx);
360 mbedtls_sha256_starts(&ctx, 0);
361 mbedtls_sha256_update(&ctx, (const uint8_t*)pin, strlen(pin));
362 mbedtls_sha256_finish(&ctx, fullHash);
363 mbedtls_sha256_free(&ctx);
364
365 memcpy(hashOut, fullHash, BADGE_HASH_SIZE);
366 return true;
367}
368
376bool PinManager::computeKdfHash(const char* pin, const uint8_t* salt, uint8_t* hashOut) const {
377 if (!pin || !salt || !hashOut) return false;
378
379 // OpenPGP Iterated+Salted S2K (RFC 4880)
380 // Hash iteration count bytes of (salt + password) repeated
381 size_t pinLen = strlen(pin);
382 size_t combined = SALT_SIZE + pinLen;
383
384 // Calculate actual byte count from iteration count
385 // OpenPGP uses coded count, here we use direct iteration count
386 size_t totalBytes = iterations_;
387
388 uint8_t buffer[64]; // Salt + PIN (max 16)
389 memcpy(buffer, salt, SALT_SIZE);
390 memcpy(buffer + SALT_SIZE, pin, pinLen);
391
392 mbedtls_sha256_context ctx;
393 mbedtls_sha256_init(&ctx);
394 mbedtls_sha256_starts(&ctx, 0);
395
396 size_t processed = 0;
397 while (processed < totalBytes) {
398 size_t chunk = (totalBytes - processed < combined) ? (totalBytes - processed) : combined;
399 mbedtls_sha256_update(&ctx, buffer, chunk);
400 processed += chunk;
401 }
402
403 mbedtls_sha256_finish(&ctx, hashOut);
404 mbedtls_sha256_free(&ctx);
405
406 return true;
407}
408
416bool PinManager::compareHash(const uint8_t* h1, const uint8_t* h2, size_t len) const {
417 uint8_t diff = 0;
418 for (size_t i = 0; i < len; i++) {
419 diff |= h1[i] ^ h2[i];
420 }
421 return diff == 0;
422}
423
427
439bool PinManager::verifyPin(PinSlot slot, const char* pin) {
440 if (!pin) return false;
441 if (!pinLoaded_) init();
442
443 if (slot == PinSlot::BADGE) {
445 if (badgeRetries_ == 0) {
446 LOG_W(TAG, "Badge PIN blocked");
447 return false;
448 }
449 uint8_t inputHash[BADGE_HASH_SIZE];
450 if (!computeBadgeHash(pin, inputHash)) return false;
451
452 badgeRetries_--;
453
454 if (compareHash(badgeHash_, inputHash, BADGE_HASH_SIZE)) {
455 badgeRetries_ = MAX_RETRIES;
456 lockoutActive_ = false;
457 if (persistedBadgeLocked_) {
458 badgeLocked_ = false;
459 saveToStorage();
460 }
461 LOG_I(TAG, "Badge PIN verified");
462 return true;
463 }
464
465 LOG_W(TAG, "Wrong Badge PIN, %d retries left", badgeRetries_);
466 if (badgeRetries_ == 0) {
467 badgeLocked_ = true;
468 saveToStorage();
469 startLockout();
470 }
471 return false;
472 }
473
474 // PW1/PW3: smartcard semantics. Pre-decrement is persisted synchronously
475 // before the verify so a power-cycle between hash and persist cannot
476 // resurrect the counter. Reaching zero is terminal until an admin reset.
477 const char* label = nullptr;
478 uint8_t* retries = nullptr;
479 uint8_t* storedHash = nullptr;
480 uint8_t* salt = nullptr;
481 uint8_t* mirror = nullptr;
482
483 switch (slot) {
484 case PinSlot::PW1:
485 label = "PW1";
486 retries = &pw1Retries_;
487 storedHash = pw1Hash_;
488 salt = pw1Salt_;
489 mirror = &persistedPw1Retries_;
490 break;
491 case PinSlot::PW3:
492 label = "PW3";
493 retries = &pw3Retries_;
494 storedHash = pw3Hash_;
495 salt = pw3Salt_;
496 mirror = &persistedPw3Retries_;
497 break;
498 case PinSlot::BADGE:
499 return false; // unreachable
500 }
501
502 if (*retries == 0) {
503 LOG_W(TAG, "%s blocked", label);
504 return false;
505 }
506
507 uint8_t inputHash[KDF_HASH_SIZE];
508 if (!computeKdfHash(pin, salt, inputHash)) return false;
509
510 const uint8_t before = *retries;
511 (*retries)--;
512 if (*retries < *mirror) {
513 if (!saveToStorage()) {
514 *retries = before;
515 return false;
516 }
517 }
518
519 if (compareHash(storedHash, inputHash, KDF_HASH_SIZE)) {
520 *retries = MAX_RETRIES;
521 if (*mirror != MAX_RETRIES) {
522 saveToStorage();
523 }
524 LOG_I(TAG, "%s verified", label);
525 return true;
526 }
527
528 LOG_W(TAG, "Wrong %s, %d retries left", label, *retries);
529 return false;
530}
531
537bool PinManager::verifyBadgePin(const char* pin) {
538 return verifyPin(PinSlot::BADGE, pin);
539}
540
547bool PinManager::changeBadgePin(const char* currentPin, const char* newPin) {
548 if (!verifyBadgePin(currentPin)) return false;
549 return setBadgePin(newPin);
550}
551
557bool PinManager::setBadgePin(const char* newPin) {
558 if (!newPin) return false;
559 size_t len = strlen(newPin);
560 if (len < BADGE_PIN_MIN || len > BADGE_PIN_MAX) {
561 LOG_E(TAG, "Badge PIN must be %d-%d digits", BADGE_PIN_MIN, BADGE_PIN_MAX);
562 return false;
563 }
564 for (size_t i = 0; i < len; i++) {
565 if (newPin[i] < '0' || newPin[i] > '9') {
566 LOG_E(TAG, "PIN must contain only digits");
567 return false;
568 }
569 }
570
571 if (duressSet_ && isDuressPin(newPin)) {
572 LOG_E(TAG, "Badge PIN must differ from duress PIN");
573 return false;
574 }
575
576 computeBadgeHash(newPin, badgeHash_);
577 badgeRetries_ = MAX_RETRIES;
578 badgeLocked_ = false;
579 lockoutActive_ = false;
580
581 uint8_t defaultHash[BADGE_HASH_SIZE];
582 computeBadgeHash(DEFAULT_BADGE_PIN, defaultHash);
583 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash, BADGE_HASH_SIZE);
584
585 saveToStorage();
586 LOG_I(TAG, "Badge PIN changed");
587 return true;
588}
589
594 badgeRetries_ = MAX_RETRIES;
595 lockoutActive_ = false;
596 if (badgeLocked_) {
597 badgeLocked_ = false;
598 saveToStorage();
599 }
600}
601
607bool PinManager::getBadgePinHash(uint8_t* hashOut) const {
608 if (!hashOut) return false;
609 memcpy(hashOut, badgeHash_, BADGE_HASH_SIZE);
610 return true;
611}
612
618bool PinManager::verifyBadgePinHash(const uint8_t* hashIn) const {
619 if (!hashIn) return false;
620 return compareHash(badgeHash_, hashIn, BADGE_HASH_SIZE);
621}
622
626
637bool PinManager::setDuressPin(const char* pin) {
638 if (!pin) return false;
639 if (!pinLoaded_) init();
640
641 size_t len = strlen(pin);
642 if (len < BADGE_PIN_MIN || len > BADGE_PIN_MAX) {
643 LOG_E(TAG, "Duress PIN must be %d-%d digits", BADGE_PIN_MIN, BADGE_PIN_MAX);
644 return false;
645 }
646 for (size_t i = 0; i < len; i++) {
647 if (pin[i] < '0' || pin[i] > '9') {
648 LOG_E(TAG, "Duress PIN must contain only digits");
649 return false;
650 }
651 }
652
653 // Must be distinct from the badge PIN: an ambiguous match would make the
654 // unlock outcome non-deterministic.
655 uint8_t candidateBadgeHash[BADGE_HASH_SIZE];
656 if (!computeBadgeHash(pin, candidateBadgeHash)) return false;
657 if (compareHash(badgeHash_, candidateBadgeHash, BADGE_HASH_SIZE)) {
658 LOG_E(TAG, "Duress PIN must differ from badge PIN");
659 return false;
660 }
661
662 generateSalt(duressSalt_);
663 if (!computeKdfHash(pin, duressSalt_, duressHash_)) return false;
664 duressSet_ = true;
665
666 saveToStorage();
667 LOG_I(TAG, "Duress PIN set");
668 return true;
669}
670
676 if (!pinLoaded_) init();
677 if (!duressSet_) return true;
678
679 duressSet_ = false;
680 memset(duressSalt_, 0, sizeof(duressSalt_));
681 memset(duressHash_, 0, sizeof(duressHash_));
682
683 saveToStorage();
684 LOG_I(TAG, "Duress PIN cleared");
685 return true;
686}
687
693bool PinManager::isDuressPin(const char* pin) const {
694 if (!duressSet_ || !pin) return false;
695 size_t len = strlen(pin);
696 if (len < BADGE_PIN_MIN || len > BADGE_PIN_MAX) return false;
697
698 uint8_t inputHash[KDF_HASH_SIZE];
699 if (!computeKdfHash(pin, duressSalt_, inputHash)) {
700 return false;
701 }
702 return compareHash(duressHash_, inputHash, KDF_HASH_SIZE);
703}
704
708
714bool PinManager::verifyPW1(const char* pin) {
715 return verifyPin(PinSlot::PW1, pin);
716}
717
724bool PinManager::changePW1(const char* currentPin, const char* newPin) {
725 if (!verifyPW1(currentPin)) return false;
726 return setPW1(newPin);
727}
728
734bool PinManager::setPW1(const char* newPin) {
735 if (!newPin) return false;
736 size_t len = strlen(newPin);
737 if (len < PW1_MIN || len > PIN_MAX) {
738 LOG_E(TAG, "PW1 must be %d-%d digits", PW1_MIN, PIN_MAX);
739 return false;
740 }
741
742 // Generate new salt
743 generateSalt(pw1Salt_);
744 computeKdfHash(newPin, pw1Salt_, pw1Hash_);
745 pw1Retries_ = MAX_RETRIES;
746
747 saveToStorage();
748 LOG_I(TAG, "PW1 changed");
749 return true;
750}
751
757bool PinManager::getPW1Hash(uint8_t* hashOut) const {
758 if (!hashOut) return false;
759 memcpy(hashOut, pw1Hash_, KDF_HASH_SIZE);
760 return true;
761}
762
768bool PinManager::getPW1Salt(uint8_t* saltOut) const {
769 if (!saltOut) return false;
770 memcpy(saltOut, pw1Salt_, SALT_SIZE);
771 return true;
772}
773
778 if (pw1Retries_ < MAX_RETRIES) {
779 pw1Retries_ = MAX_RETRIES;
780 saveToStorage();
781 }
782}
783
787
793bool PinManager::verifyPW3(const char* pin) {
794 return verifyPin(PinSlot::PW3, pin);
795}
796
803bool PinManager::changePW3(const char* currentPin, const char* newPin) {
804 if (!verifyPW3(currentPin)) return false;
805 return setPW3(newPin);
806}
807
813bool PinManager::setPW3(const char* newPin) {
814 if (!newPin) return false;
815 size_t len = strlen(newPin);
816 if (len < PW3_MIN || len > PIN_MAX) {
817 LOG_E(TAG, "PW3 must be %d-%d digits", PW3_MIN, PIN_MAX);
818 return false;
819 }
820
821 generateSalt(pw3Salt_);
822 computeKdfHash(newPin, pw3Salt_, pw3Hash_);
823 pw3Retries_ = MAX_RETRIES;
824
825 saveToStorage();
826 LOG_I(TAG, "PW3 changed");
827 return true;
828}
829
835bool PinManager::getPW3Hash(uint8_t* hashOut) const {
836 if (!hashOut) return false;
837 memcpy(hashOut, pw3Hash_, KDF_HASH_SIZE);
838 return true;
839}
840
846bool PinManager::getPW3Salt(uint8_t* saltOut) const {
847 if (!saltOut) return false;
848 memcpy(saltOut, pw3Salt_, SALT_SIZE);
849 return true;
850}
851
856 if (pw3Retries_ < MAX_RETRIES) {
857 pw3Retries_ = MAX_RETRIES;
858 saveToStorage();
859 }
860}
861
865
871 return badgeRetries_ == 0;
872}
873
878 lockoutStartMs_ = esp_timer_get_time() / 1000;
879 lockoutActive_ = true;
880 LOG_I(TAG, "Badge recovery timer started (%lu ms)", LOCKOUT_DURATION_MS);
881}
882
888 if (!lockoutActive_) {
889 return 0;
890 }
891
892 uint32_t nowMs = esp_timer_get_time() / 1000;
893 uint32_t elapsed = nowMs - lockoutStartMs_;
894
895 if (elapsed >= LOCKOUT_DURATION_MS) {
896 return 0;
897 }
898 return LOCKOUT_DURATION_MS - elapsed;
899}
900
906 if (!lockoutActive_) {
907 return false;
908 }
909 return getLockoutRemainingMs() > 0;
910}
911
920 if (!lockoutActive_) return;
921 if (getLockoutRemainingMs() > 0) return;
922
923 lockoutActive_ = false;
924 badgeRetries_ = MAX_RETRIES;
925 if (badgeLocked_) {
926 badgeLocked_ = false;
927 saveToStorage();
928 }
929 LOG_I(TAG, "Badge recovery timer expired, retries restored to %u", MAX_RETRIES);
930}
931
932} // namespace cdc::core
static const char * TAG
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_D(tag, fmt,...)
Definition cdc_log.h:148
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
static constexpr uint8_t PIN_MAX
Definition PinManager.h:53
bool verifyPW1(const char *pin)
OpenPGP PW1 (user PIN) workflow.
bool changeBadgePin(const char *currentPin, const char *newPin)
Changes badge PIN after validating current PIN.
static constexpr uint8_t KDF_HASH_SIZE
Definition PinManager.h:65
bool getPW1Hash(uint8_t *hashOut) const
Copies stored PW1 hash into caller buffer.
static constexpr uint32_t DEFAULT_ITERATIONS
Definition PinManager.h:71
void resetPW1Retries()
Resets PW1 retry counter to maximum.
static constexpr uint32_t LOCKOUT_DURATION_MS
Definition PinManager.h:92
bool changePW3(const char *currentPin, const char *newPin)
Changes PW3 after validating the current value.
void resetBadgeRetries()
Resets badge retry counter to maximum.
static constexpr uint16_t RMEM_SLOT_PIN
Definition PinManager.h:56
static constexpr uint8_t HASH_SHA256
Definition PinManager.h:70
bool isDuressPin(const char *pin) const
Constant-time check whether a candidate matches the duress PIN.
static constexpr uint8_t BADGE_PIN_MAX
Definition PinManager.h:50
bool getPW1Salt(uint8_t *saltOut) const
Copies stored PW1 salt into caller buffer.
static constexpr const char * DEFAULT_BADGE_PIN
Definition PinManager.h:74
bool isStorageAvailable() const
Returns whether secure storage access is currently available.
bool setPW3(const char *newPin)
Sets PW3 directly and refreshes salt/hash material.
bool verifyBadgePin(const char *pin)
Verifies badge PIN, updates retries, and handles lockout transitions.
static constexpr uint8_t BADGE_HASH_SIZE
Definition PinManager.h:64
static constexpr const char * DEFAULT_PW1
Definition PinManager.h:75
bool changePW1(const char *currentPin, const char *newPin)
Changes PW1 after validating the current value.
static constexpr uint8_t PW3_MIN
Definition PinManager.h:52
static constexpr uint8_t PW1_MIN
Definition PinManager.h:51
void resetPW3Retries()
Resets PW3 retry counter to maximum.
bool isBadgeBlocked() const
Lockout timer handling.
static constexpr uint8_t BADGE_PIN_MIN
Definition PinManager.h:49
static constexpr uint8_t KDF_ITERSALTED_S2K
Definition PinManager.h:69
static constexpr const char * DEFAULT_PW3
Definition PinManager.h:76
bool setBadgePin(const char *newPin)
Sets badge PIN directly with format validation.
bool clearDuressPin()
Clears the duress PIN, disarming the self-destruct trigger.
void startLockout()
Starts the badge recovery timer.
bool isLockoutActive() const
Returns whether lockout is currently active without mutating state.
static PinManager & instance()
Returns singleton PIN manager instance.
static constexpr uint8_t ATTESTATION_ECC_SLOT
Definition PinManager.h:61
bool getPW3Hash(uint8_t *hashOut) const
Copies stored PW3 hash into caller buffer.
uint32_t getLockoutRemainingMs() const
Returns remaining badge lockout duration.
bool setPW1(const char *newPin)
Sets PW1 directly and refreshes salt/hash material.
bool setDuressPin(const char *pin)
Sets the duress PIN, arming the self-destruct trigger.
bool verifyBadgePinHash(const uint8_t *hashIn) const
Verifies provided hash against stored badge hash.
bool getBadgePinHash(uint8_t *hashOut) const
Copies stored badge PIN hash into caller buffer.
static constexpr uint8_t SALT_SIZE
Definition PinManager.h:66
bool init()
Initializes PIN state from secure storage or defaults.
bool verifyPW3(const char *pin)
OpenPGP PW3 (admin PIN) workflow.
void checkAndResetExpiredLockout()
Clears expired lockout state and resets retry counter.
bool getPW3Salt(uint8_t *saltOut) const
Copies stored PW3 salt into caller buffer.
virtual bool getRandom(uint8_t *buffer, uint16_t size)=0
virtual SeResult eccGetPublicKey(uint8_t slot, uint8_t *pubKey, EccCurve *curve=nullptr)=0
virtual bool isSessionActive() const =0
virtual SeResult rmemRead(uint16_t slot, uint8_t *data, uint16_t maxLen, uint16_t *actualLen)=0
#define SHA256_DIGEST_SIZE
SHA-256 digest output size in bytes (FIPS 180-4).
Definition constants.h:37
uint8_t curve
static bool verify_payload_signature(hal::ISecureElement *se, const uint8_t *payload, size_t payload_len, const uint8_t *sig, size_t sig_len)
Loads serialized PIN/KDF state from secure-element R-Memory.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.