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 "mbedtls/platform_util.h"
17#include "esp_random.h"
18#include "esp_timer.h"
19#include <cstring>
20
21static const char* TAG = "PinManager";
22
24static constexpr size_t SHA256_DIGEST_SIZE = 32;
25
26namespace cdc::core {
27
32PinManager& PinManager::instance() {
33 static PinManager instance;
34 return instance;
35}
36
42 if (pinLoaded_) return true;
43
44 if (!loadFromStorage()) {
45 LOG_I(TAG, "Loading default PINs (storage empty or unreadable)");
46 loadDefaults();
47 saveToStorage();
48 }
49 pinLoaded_ = true;
50
51 badgeRetries_ = badgeLocked_ ? 0 : 1;
53 LOG_I(TAG, "Badge state after init: locked=%d retries=%u pinSet=%d",
54 badgeLocked_, badgeRetries_, badgePinIsSet_);
55 return true;
56}
57
61void PinManager::loadDefaults() {
62 // Badge/FIDO2 hash
63 computeBadgeHash(DEFAULT_BADGE_PIN, badgeHash_);
64 badgeRetries_ = MAX_RETRIES;
65 badgeLocked_ = false;
66
67 // Generate random salts
68 generateSalt(pw1Salt_);
69 generateSalt(pw3Salt_);
70
71 // Compute KDF hashes with salts
72 computeKdfHash(DEFAULT_PW1, pw1Salt_, pw1Hash_);
73 computeKdfHash(DEFAULT_PW3, pw3Salt_, pw3Hash_);
74
75 iterations_ = DEFAULT_ITERATIONS;
76 pw1Retries_ = MAX_RETRIES;
77 pw3Retries_ = MAX_RETRIES;
78 badgePinIsSet_ = false;
79
80 // Duress PIN is opt-in: defaults leave it disarmed.
81 duressSet_ = false;
82 memset(duressSalt_, 0, sizeof(duressSalt_));
83 memset(duressHash_, 0, sizeof(duressHash_));
84
85 LOG_I(TAG, "Loaded default PINs");
86}
87
92void PinManager::generateSalt(uint8_t* salt) {
93 // Try to get random from SE, fallback to ESP random
95 if (se && se->isSessionActive() && se->getRandom(salt, SALT_SIZE)) {
96 return;
97 }
98 // Fallback to ESP32 RNG
99 esp_fill_random(salt, SALT_SIZE);
100}
101
110
128 const uint8_t* payload, size_t payload_len,
129 const uint8_t* sig, size_t sig_len) {
130 if (sig_len != 64) return false;
131 uint8_t pub_raw[64];
134 LOG_W(TAG, "Attestation pubkey read failed");
135 return false;
136 }
137 if (curve != hal::EccCurve::P256) {
138 LOG_W(TAG, "Attestation key is not P-256");
139 return false;
140 }
141
142 uint8_t pub_sec1[65];
143 pub_sec1[0] = 0x04;
144 memcpy(pub_sec1 + 1, pub_raw, 64);
145
146 uint8_t hash[SHA256_DIGEST_SIZE];
147 mbedtls_sha256(payload, payload_len, hash, 0);
148
149 mbedtls_ecp_group grp;
150 mbedtls_ecp_point Q;
151 mbedtls_mpi r, s;
152 mbedtls_ecp_group_init(&grp);
153 mbedtls_ecp_point_init(&Q);
154 mbedtls_mpi_init(&r);
155 mbedtls_mpi_init(&s);
156
157 bool ok = false;
158 do {
159 if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1) != 0) break;
160 if (mbedtls_ecp_point_read_binary(&grp, &Q, pub_sec1, sizeof(pub_sec1)) != 0) break;
161 if (mbedtls_mpi_read_binary(&r, sig + 0, 32) != 0) break;
162 if (mbedtls_mpi_read_binary(&s, sig + 32, 32) != 0) break;
163 ok = (mbedtls_ecdsa_verify(&grp, hash, SHA256_DIGEST_SIZE, &Q, &r, &s) == 0);
164 } while (0);
165
166 mbedtls_mpi_free(&r);
167 mbedtls_mpi_free(&s);
168 mbedtls_ecp_point_free(&Q);
169 mbedtls_ecp_group_free(&grp);
170 return ok;
171}
172
173bool PinManager::loadFromStorage() {
175 if (!se || !se->isSessionActive()) {
176 LOG_W(TAG, "SE session not active");
177 return false;
178 }
179
180 uint8_t data[STORAGE_SIZE];
181 uint16_t actualLen = 0;
182
183 hal::SeResult result = se->rmemRead(RMEM_SLOT_PIN, data, STORAGE_SIZE, &actualLen);
184 if (result != hal::SeResult::OK) {
185 LOG_D(TAG, "No PIN data in R-Memory (read err=%d)",
186 static_cast<int>(result));
187 return false;
188 }
189
190 // Only the signed format is accepted. Any other state (wrong magic,
191 // wrong length, or invalid signature) falls back to defaults so the
192 // slot ends up freshly signed by the chip-bound attestation key.
193 if (actualLen != STORAGE_SIZE || data[0] != MAGIC) {
194 LOG_W(TAG, "PIN storage unrecognised (len=%u magic=0x%02X) - using defaults",
195 actualLen, actualLen > 0 ? data[0] : 0);
196 return false;
197 }
198 if (!verify_payload_signature(se, data, PAYLOAD_SIZE,
199 data + PAYLOAD_SIZE, SIGNATURE_SIZE)) {
200 LOG_W(TAG, "PIN storage signature invalid - re-initializing");
201 return false;
202 }
203
204 size_t pos = 1;
205
206 // Badge hash
207 memcpy(badgeHash_, &data[pos], BADGE_HASH_SIZE);
208 pos += BADGE_HASH_SIZE;
209
210 // Badge locked flag (counter itself is RAM-only)
211 badgeLocked_ = (data[pos++] != 0);
212
213 // KDF params (skip algorithm bytes, we know them)
214 pos += 2; // KDF algo + Hash algo
215
216 // Iteration count (big endian)
217 iterations_ = (data[pos] << 24) | (data[pos+1] << 16) | (data[pos+2] << 8) | data[pos+3];
218 pos += 4;
219
220 // Salts
221 memcpy(pw1Salt_, &data[pos], SALT_SIZE);
222 pos += SALT_SIZE;
223 memcpy(pw3Salt_, &data[pos], SALT_SIZE);
224 pos += SALT_SIZE;
225
226 // Hashes
227 memcpy(pw1Hash_, &data[pos], KDF_HASH_SIZE);
228 pos += KDF_HASH_SIZE;
229 memcpy(pw3Hash_, &data[pos], KDF_HASH_SIZE);
230 pos += KDF_HASH_SIZE;
231
232 // Retries
233 pw1Retries_ = data[pos++];
234 pw3Retries_ = data[pos++];
235
236 // Duress / self-destruct PIN
237 duressSet_ = (data[pos++] != 0);
238 memcpy(duressSalt_, &data[pos], SALT_SIZE);
239 pos += SALT_SIZE;
240 memcpy(duressHash_, &data[pos], KDF_HASH_SIZE);
241 pos += KDF_HASH_SIZE;
242
243 // Mirror starts in sync with whatever is on the chip.
244 persistedBadgeLocked_ = badgeLocked_;
245 persistedPw1Retries_ = pw1Retries_;
246 persistedPw3Retries_ = pw3Retries_;
247
248 // Check if badge PIN differs from default
249 uint8_t defaultHash[BADGE_HASH_SIZE];
250 computeBadgeHash(DEFAULT_BADGE_PIN, defaultHash);
251 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash, BADGE_HASH_SIZE);
252
253 LOG_I(TAG, "Loaded PINs from R-Memory (Badge locked=%s, PW1=%d, PW3=%d retries)",
254 badgeLocked_ ? "yes" : "no", pw1Retries_, pw3Retries_);
255 return true;
256}
257
262bool PinManager::saveToStorage() {
263 hal::ISecureElement* se = hal::getSecureElementInstance();
264 if (!se || !se->isSessionActive()) {
265 LOG_E(TAG, "SE session not active");
266 return false;
267 }
268
269 uint8_t data[STORAGE_SIZE];
270 size_t pos = 0;
271
272 data[pos++] = MAGIC;
273
274 // Badge hash
275 memcpy(&data[pos], badgeHash_, BADGE_HASH_SIZE);
276 pos += BADGE_HASH_SIZE;
277
278 // Badge locked flag (retry counter is RAM-only)
279 data[pos++] = badgeLocked_ ? 0x01 : 0x00;
280
281 // KDF params
282 data[pos++] = KDF_ITERSALTED_S2K;
283 data[pos++] = HASH_SHA256;
284
285 // Iteration count (big endian)
286 data[pos++] = (iterations_ >> 24) & 0xFF;
287 data[pos++] = (iterations_ >> 16) & 0xFF;
288 data[pos++] = (iterations_ >> 8) & 0xFF;
289 data[pos++] = iterations_ & 0xFF;
290
291 // Salts
292 memcpy(&data[pos], pw1Salt_, SALT_SIZE);
293 pos += SALT_SIZE;
294 memcpy(&data[pos], pw3Salt_, SALT_SIZE);
295 pos += SALT_SIZE;
296
297 // Hashes
298 memcpy(&data[pos], pw1Hash_, KDF_HASH_SIZE);
299 pos += KDF_HASH_SIZE;
300 memcpy(&data[pos], pw3Hash_, KDF_HASH_SIZE);
301 pos += KDF_HASH_SIZE;
302
303 // Retries
304 data[pos++] = pw1Retries_;
305 data[pos++] = pw3Retries_;
306
307 // Duress / self-destruct PIN
308 data[pos++] = duressSet_ ? 0x01 : 0x00;
309 memcpy(&data[pos], duressSalt_, SALT_SIZE);
310 pos += SALT_SIZE;
311 memcpy(&data[pos], duressHash_, KDF_HASH_SIZE);
312 pos += KDF_HASH_SIZE;
313
314 // pos must now equal PAYLOAD_SIZE — append a P-256 ECDSA signature over
315 // bytes [0..PAYLOAD_SIZE) using the chip-bound attestation key in slot 0.
316 // A subsequent load that finds the signature invalid (because the slot 0
317 // key was regenerated or the payload was tampered with) will silently
318 // re-initialize the storage with defaults, exactly as requested by spec.
319 if (pos != PAYLOAD_SIZE) {
320 LOG_E(TAG, "Payload size mismatch (built=%zu, expected=%u)", pos, PAYLOAD_SIZE);
321 return false;
322 }
323 size_t sig_len = SIGNATURE_SIZE;
324 hal::SeResult sign_res = se->ecdsaSign(ATTESTATION_ECC_SLOT,
325 data, PAYLOAD_SIZE,
326 data + PAYLOAD_SIZE, &sig_len);
327 if (sign_res != hal::SeResult::OK || sig_len != SIGNATURE_SIZE) {
328 LOG_E(TAG, "Attestation sign failed (%d)", static_cast<int>(sign_res));
329 return false;
330 }
331
332 se->rmemErase(RMEM_SLOT_PIN);
333
334 hal::SeResult result = se->rmemWrite(RMEM_SLOT_PIN, data, STORAGE_SIZE);
335 if (result != hal::SeResult::OK) {
336 LOG_E(TAG, "R-Memory write failed");
337 return false;
338 }
339
340 persistedBadgeLocked_ = badgeLocked_;
341 persistedPw1Retries_ = pw1Retries_;
342 persistedPw3Retries_ = pw3Retries_;
343
344 LOG_D(TAG, "PINs saved to R-Memory slot %d (signed, %u bytes)",
345 RMEM_SLOT_PIN, STORAGE_SIZE);
346 return true;
347}
348
355bool PinManager::computeBadgeHash(const char* pin, uint8_t* hashOut) {
356 if (!pin || !hashOut) return false;
357
358 uint8_t fullHash[SHA256_DIGEST_SIZE];
359 mbedtls_sha256_context ctx;
360 mbedtls_sha256_init(&ctx);
361 mbedtls_sha256_starts(&ctx, 0);
362 mbedtls_sha256_update(&ctx, (const uint8_t*)pin, strlen(pin));
363 mbedtls_sha256_finish(&ctx, fullHash);
364 mbedtls_sha256_free(&ctx);
365
366 memcpy(hashOut, fullHash, BADGE_HASH_SIZE);
367 return true;
368}
369
377bool PinManager::computeKdfHash(const char* pin, const uint8_t* salt, uint8_t* hashOut) const {
378 if (!pin) return false;
379 return computeKdfHash(reinterpret_cast<const uint8_t*>(pin), strlen(pin), salt, hashOut);
380}
381
382bool PinManager::computeKdfHash(const uint8_t* data, size_t len, const uint8_t* salt,
383 uint8_t* hashOut) const {
384 if (!data || !salt || !hashOut) return false;
385 // Salt + input must fit the iteration buffer; the KDF-DO path supplies a
386 // pre-hash of up to 64 bytes, the cleartext path a PIN of up to PIN_MAX.
387 if (len > 64) return false;
388
389 // OpenPGP Iterated+Salted S2K (RFC 4880): hash iteration-count bytes of
390 // (salt + input) repeated.
391 size_t combined = SALT_SIZE + len;
392 size_t totalBytes = iterations_;
393
394 uint8_t buffer[SALT_SIZE + 64];
395 memcpy(buffer, salt, SALT_SIZE);
396 memcpy(buffer + SALT_SIZE, data, len);
397
398 mbedtls_sha256_context ctx;
399 mbedtls_sha256_init(&ctx);
400 mbedtls_sha256_starts(&ctx, 0);
401
402 size_t processed = 0;
403 while (processed < totalBytes) {
404 size_t chunk = (totalBytes - processed < combined) ? (totalBytes - processed) : combined;
405 mbedtls_sha256_update(&ctx, buffer, chunk);
406 processed += chunk;
407 }
408
409 mbedtls_sha256_finish(&ctx, hashOut);
410 mbedtls_sha256_free(&ctx);
411 mbedtls_platform_zeroize(buffer, sizeof(buffer));
412
413 return true;
414}
415
423bool PinManager::compareHash(const uint8_t* h1, const uint8_t* h2, size_t len) const {
424 uint8_t diff = 0;
425 for (size_t i = 0; i < len; i++) {
426 diff |= h1[i] ^ h2[i];
427 }
428 return diff == 0;
429}
430
434
446bool PinManager::verifyPin(PinSlot slot, const char* pin) {
447 if (!pin) return false;
448 if (!pinLoaded_) init();
449
450 if (slot == PinSlot::BADGE) {
452 if (badgeRetries_ == 0) {
453 LOG_W(TAG, "Badge PIN blocked");
454 return false;
455 }
456 uint8_t inputHash[BADGE_HASH_SIZE];
457 if (!computeBadgeHash(pin, inputHash)) return false;
458
459 badgeRetries_--;
460
461 if (compareHash(badgeHash_, inputHash, BADGE_HASH_SIZE)) {
462 badgeRetries_ = MAX_RETRIES;
463 lockoutActive_ = false;
464 if (persistedBadgeLocked_) {
465 badgeLocked_ = false;
466 saveToStorage();
467 }
468 LOG_I(TAG, "Badge PIN verified");
469 return true;
470 }
471
472 LOG_W(TAG, "Wrong Badge PIN, %d retries left", badgeRetries_);
473 if (badgeRetries_ == 0) {
474 badgeLocked_ = true;
475 saveToStorage();
476 startLockout();
477 }
478 return false;
479 }
480
481 // PW1/PW3 use the binary-capable path; the cleartext PIN is just its bytes.
482 return verifyPinRaw(slot, reinterpret_cast<const uint8_t*>(pin), strlen(pin));
483}
484
485bool PinManager::verifyPinRaw(PinSlot slot, const uint8_t* data, size_t len) {
486 if (!data) return false;
487 if (!pinLoaded_) init();
488
489 // PW1/PW3: smartcard semantics. Pre-decrement is persisted synchronously
490 // before the verify so a power-cycle between hash and persist cannot
491 // resurrect the counter. Reaching zero is terminal until an admin reset.
492 const char* label = nullptr;
493 uint8_t* retries = nullptr;
494 uint8_t* storedHash = nullptr;
495 uint8_t* salt = nullptr;
496 uint8_t* mirror = nullptr;
497
498 switch (slot) {
499 case PinSlot::PW1:
500 label = "PW1";
501 retries = &pw1Retries_;
502 storedHash = pw1Hash_;
503 salt = pw1Salt_;
504 mirror = &persistedPw1Retries_;
505 break;
506 case PinSlot::PW3:
507 label = "PW3";
508 retries = &pw3Retries_;
509 storedHash = pw3Hash_;
510 salt = pw3Salt_;
511 mirror = &persistedPw3Retries_;
512 break;
513 case PinSlot::BADGE:
514 return false; // unreachable
515 }
516
517 if (*retries == 0) {
518 LOG_W(TAG, "%s blocked", label);
519 return false;
520 }
521
522 uint8_t inputHash[KDF_HASH_SIZE];
523 if (!computeKdfHash(data, len, salt, inputHash)) return false;
524
525 const uint8_t before = *retries;
526 (*retries)--;
527 if (*retries < *mirror) {
528 if (!saveToStorage()) {
529 *retries = before;
530 return false;
531 }
532 }
533
534 if (compareHash(storedHash, inputHash, KDF_HASH_SIZE)) {
535 *retries = MAX_RETRIES;
536 if (*mirror != MAX_RETRIES) {
537 saveToStorage();
538 }
539 LOG_I(TAG, "%s verified", label);
540 return true;
541 }
542
543 LOG_W(TAG, "Wrong %s, %d retries left", label, *retries);
544 return false;
545}
546
552bool PinManager::verifyBadgePin(const char* pin) {
553 return verifyPin(PinSlot::BADGE, pin);
554}
555
562bool PinManager::changeBadgePin(const char* currentPin, const char* newPin) {
563 if (!verifyBadgePin(currentPin)) return false;
564 return setBadgePin(newPin);
565}
566
573 if (minLen > BADGE_PIN_MAX) minLen = BADGE_PIN_MAX;
574 if (minLen < BADGE_PIN_MIN) minLen = BADGE_PIN_MIN;
575 minPinFloor_ = minLen;
576}
577
578bool PinManager::setBadgePin(const char* newPin) {
579 if (!newPin) return false;
580 size_t len = strlen(newPin);
581 uint8_t minLen = minPinFloor_ > BADGE_PIN_MIN ? minPinFloor_ : BADGE_PIN_MIN;
582 if (len < minLen || len > BADGE_PIN_MAX) {
583 LOG_E(TAG, "Badge PIN must be %d-%d digits", minLen, BADGE_PIN_MAX);
584 return false;
585 }
586 for (size_t i = 0; i < len; i++) {
587 if (newPin[i] < '0' || newPin[i] > '9') {
588 LOG_E(TAG, "PIN must contain only digits");
589 return false;
590 }
591 }
592
593 if (duressSet_ && isDuressPin(newPin)) {
594 LOG_E(TAG, "Badge PIN must differ from duress PIN");
595 return false;
596 }
597
598 computeBadgeHash(newPin, badgeHash_);
599 badgeRetries_ = MAX_RETRIES;
600 badgeLocked_ = false;
601 lockoutActive_ = false;
602
603 uint8_t defaultHash[BADGE_HASH_SIZE];
604 computeBadgeHash(DEFAULT_BADGE_PIN, defaultHash);
605 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash, BADGE_HASH_SIZE);
606
607 saveToStorage();
608 LOG_I(TAG, "Badge PIN changed");
609 return true;
610}
611
616 badgeRetries_ = MAX_RETRIES;
617 lockoutActive_ = false;
618 if (badgeLocked_) {
619 badgeLocked_ = false;
620 saveToStorage();
621 }
622}
623
629bool PinManager::getBadgePinHash(uint8_t* hashOut) const {
630 if (!hashOut) return false;
631 memcpy(hashOut, badgeHash_, BADGE_HASH_SIZE);
632 return true;
633}
634
640bool PinManager::verifyBadgePinHash(const uint8_t* hashIn) const {
641 if (!hashIn) return false;
642 return compareHash(badgeHash_, hashIn, BADGE_HASH_SIZE);
643}
644
648
659bool PinManager::setDuressPin(const char* pin) {
660 if (!pin) return false;
661 if (!pinLoaded_) init();
662
663 size_t len = strlen(pin);
664 if (len < BADGE_PIN_MIN || len > BADGE_PIN_MAX) {
665 LOG_E(TAG, "Duress PIN must be %d-%d digits", BADGE_PIN_MIN, BADGE_PIN_MAX);
666 return false;
667 }
668 for (size_t i = 0; i < len; i++) {
669 if (pin[i] < '0' || pin[i] > '9') {
670 LOG_E(TAG, "Duress PIN must contain only digits");
671 return false;
672 }
673 }
674
675 // Must be distinct from the badge PIN: an ambiguous match would make the
676 // unlock outcome non-deterministic.
677 uint8_t candidateBadgeHash[BADGE_HASH_SIZE];
678 if (!computeBadgeHash(pin, candidateBadgeHash)) return false;
679 if (compareHash(badgeHash_, candidateBadgeHash, BADGE_HASH_SIZE)) {
680 LOG_E(TAG, "Duress PIN must differ from badge PIN");
681 return false;
682 }
683
684 generateSalt(duressSalt_);
685 if (!computeKdfHash(pin, duressSalt_, duressHash_)) return false;
686 duressSet_ = true;
687
688 saveToStorage();
689 LOG_I(TAG, "Duress PIN set");
690 return true;
691}
692
698 if (!pinLoaded_) init();
699 if (!duressSet_) return true;
700
701 duressSet_ = false;
702 memset(duressSalt_, 0, sizeof(duressSalt_));
703 memset(duressHash_, 0, sizeof(duressHash_));
704
705 saveToStorage();
706 LOG_I(TAG, "Duress PIN cleared");
707 return true;
708}
709
715bool PinManager::isDuressPin(const char* pin) const {
716 if (!duressSet_ || !pin) return false;
717 size_t len = strlen(pin);
718 if (len < BADGE_PIN_MIN || len > BADGE_PIN_MAX) return false;
719
720 uint8_t inputHash[KDF_HASH_SIZE];
721 if (!computeKdfHash(pin, duressSalt_, inputHash)) {
722 return false;
723 }
724 return compareHash(duressHash_, inputHash, KDF_HASH_SIZE);
725}
726
730
736bool PinManager::verifyPW1(const char* pin) {
737 return verifyPin(PinSlot::PW1, pin);
738}
739
746bool PinManager::changePW1(const char* currentPin, const char* newPin) {
747 if (!verifyPW1(currentPin)) return false;
748 return setPW1(newPin);
749}
750
756bool PinManager::setPW1(const char* newPin) {
757 if (!newPin) return false;
758 size_t len = strlen(newPin);
759 if (len < PW1_MIN || len > PIN_MAX) {
760 LOG_E(TAG, "PW1 must be %d-%d digits", PW1_MIN, PIN_MAX);
761 return false;
762 }
763
764 // Generate new salt
765 generateSalt(pw1Salt_);
766 computeKdfHash(newPin, pw1Salt_, pw1Hash_);
767 pw1Retries_ = MAX_RETRIES;
768
769 saveToStorage();
770 LOG_I(TAG, "PW1 changed");
771 return true;
772}
773
774bool PinManager::verifyPW1Raw(const uint8_t* data, size_t len) {
775 return verifyPinRaw(PinSlot::PW1, data, len);
776}
777
778bool PinManager::setPW1Raw(const uint8_t* data, size_t len) {
779 if (!data || (len != 32 && len != 64)) return false;
780 generateSalt(pw1Salt_);
781 if (!computeKdfHash(data, len, pw1Salt_, pw1Hash_)) return false;
782 pw1Retries_ = MAX_RETRIES;
783 saveToStorage();
784 LOG_I(TAG, "PW1 set from KDF reference");
785 return true;
786}
787
793bool PinManager::getPW1Hash(uint8_t* hashOut) const {
794 if (!hashOut) return false;
795 memcpy(hashOut, pw1Hash_, KDF_HASH_SIZE);
796 return true;
797}
798
804bool PinManager::getPW1Salt(uint8_t* saltOut) const {
805 if (!saltOut) return false;
806 memcpy(saltOut, pw1Salt_, SALT_SIZE);
807 return true;
808}
809
814 if (pw1Retries_ < MAX_RETRIES) {
815 pw1Retries_ = MAX_RETRIES;
816 saveToStorage();
817 }
818}
819
823
829bool PinManager::verifyPW3(const char* pin) {
830 return verifyPin(PinSlot::PW3, pin);
831}
832
839bool PinManager::changePW3(const char* currentPin, const char* newPin) {
840 if (!verifyPW3(currentPin)) return false;
841 return setPW3(newPin);
842}
843
849bool PinManager::setPW3(const char* newPin) {
850 if (!newPin) return false;
851 size_t len = strlen(newPin);
852 if (len < PW3_MIN || len > PIN_MAX) {
853 LOG_E(TAG, "PW3 must be %d-%d digits", PW3_MIN, PIN_MAX);
854 return false;
855 }
856
857 generateSalt(pw3Salt_);
858 computeKdfHash(newPin, pw3Salt_, pw3Hash_);
859 pw3Retries_ = MAX_RETRIES;
860
861 saveToStorage();
862 LOG_I(TAG, "PW3 changed");
863 return true;
864}
865
866bool PinManager::verifyPW3Raw(const uint8_t* data, size_t len) {
867 return verifyPinRaw(PinSlot::PW3, data, len);
868}
869
870bool PinManager::setPW3Raw(const uint8_t* data, size_t len) {
871 if (!data || (len != 32 && len != 64)) return false;
872 generateSalt(pw3Salt_);
873 if (!computeKdfHash(data, len, pw3Salt_, pw3Hash_)) return false;
874 pw3Retries_ = MAX_RETRIES;
875 saveToStorage();
876 LOG_I(TAG, "PW3 set from KDF reference");
877 return true;
878}
879
885bool PinManager::getPW3Hash(uint8_t* hashOut) const {
886 if (!hashOut) return false;
887 memcpy(hashOut, pw3Hash_, KDF_HASH_SIZE);
888 return true;
889}
890
896bool PinManager::getPW3Salt(uint8_t* saltOut) const {
897 if (!saltOut) return false;
898 memcpy(saltOut, pw3Salt_, SALT_SIZE);
899 return true;
900}
901
906 if (pw3Retries_ < MAX_RETRIES) {
907 pw3Retries_ = MAX_RETRIES;
908 saveToStorage();
909 }
910}
911
915
921 return badgeRetries_ == 0;
922}
923
928 lockoutStartMs_ = esp_timer_get_time() / 1000;
929 lockoutActive_ = true;
930 LOG_I(TAG, "Badge recovery timer started (%lu ms)", LOCKOUT_DURATION_MS);
931}
932
938 if (!lockoutActive_) {
939 return 0;
940 }
941
942 uint32_t nowMs = esp_timer_get_time() / 1000;
943 uint32_t elapsed = nowMs - lockoutStartMs_;
944
945 if (elapsed >= LOCKOUT_DURATION_MS) {
946 return 0;
947 }
948 return LOCKOUT_DURATION_MS - elapsed;
949}
950
956 if (!lockoutActive_) {
957 return false;
958 }
959 return getLockoutRemainingMs() > 0;
960}
961
970 if (!lockoutActive_) return;
971 if (getLockoutRemainingMs() > 0) return;
972
973 lockoutActive_ = false;
974 badgeRetries_ = MAX_RETRIES;
975 if (badgeLocked_) {
976 badgeLocked_ = false;
977 saveToStorage();
978 }
979 LOG_I(TAG, "Badge recovery timer expired, retries restored to %u", MAX_RETRIES);
980}
981
982} // 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
bool setPW3Raw(const uint8_t *data, size_t len)
void resetPW1Retries()
Resets PW1 retry counter to maximum.
static constexpr uint32_t LOCKOUT_DURATION_MS
Definition PinManager.h:100
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)
bool clearDuressPin()
Clears the duress PIN, disarming the self-destruct trigger.
void setMinPinLengthFloor(uint8_t minLen)
Sets the minimum badge-PIN length floor enforced on changes.
bool verifyPW3Raw(const uint8_t *data, size_t len)
OpenPGP KDF-DO path: PW3 reference is a host-supplied pre-hash.
void startLockout()
Starts the badge recovery timer.
bool setPW1Raw(const uint8_t *data, size_t len)
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 verifyPW1Raw(const uint8_t *data, size_t len)
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:48
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.