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"
21static const char*
TAG =
"PinManager";
42 if (pinLoaded_)
return true;
44 if (!loadFromStorage()) {
45 LOG_I(
TAG,
"Loading default PINs (storage empty or unreadable)");
51 badgeRetries_ = badgeLocked_ ? 0 : 1;
53 LOG_I(
TAG,
"Badge state after init: locked=%d retries=%u pinSet=%d",
54 badgeLocked_, badgeRetries_, badgePinIsSet_);
61void PinManager::loadDefaults() {
64 badgeRetries_ = MAX_RETRIES;
68 generateSalt(pw1Salt_);
69 generateSalt(pw3Salt_);
76 pw1Retries_ = MAX_RETRIES;
77 pw3Retries_ = MAX_RETRIES;
78 badgePinIsSet_ =
false;
82 memset(duressSalt_, 0,
sizeof(duressSalt_));
83 memset(duressHash_, 0,
sizeof(duressHash_));
92void PinManager::generateSalt(uint8_t* salt) {
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;
134 LOG_W(
TAG,
"Attestation pubkey read failed");
138 LOG_W(
TAG,
"Attestation key is not P-256");
142 uint8_t pub_sec1[65];
144 memcpy(pub_sec1 + 1, pub_raw, 64);
147 mbedtls_sha256(payload, payload_len,
hash, 0);
149 mbedtls_ecp_group grp;
152 mbedtls_ecp_group_init(&grp);
153 mbedtls_ecp_point_init(&Q);
154 mbedtls_mpi_init(&r);
155 mbedtls_mpi_init(&s);
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;
166 mbedtls_mpi_free(&r);
167 mbedtls_mpi_free(&s);
168 mbedtls_ecp_point_free(&Q);
169 mbedtls_ecp_group_free(&grp);
173bool PinManager::loadFromStorage() {
176 LOG_W(
TAG,
"SE session not active");
180 uint8_t data[STORAGE_SIZE];
181 uint16_t actualLen = 0;
185 LOG_D(
TAG,
"No PIN data in R-Memory (read err=%d)",
186 static_cast<int>(result));
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);
199 data + PAYLOAD_SIZE, SIGNATURE_SIZE)) {
200 LOG_W(
TAG,
"PIN storage signature invalid - re-initializing");
211 badgeLocked_ = (data[pos++] != 0);
217 iterations_ = (data[pos] << 24) | (data[pos+1] << 16) | (data[pos+2] << 8) | data[pos+3];
233 pw1Retries_ = data[pos++];
234 pw3Retries_ = data[pos++];
237 duressSet_ = (data[pos++] != 0);
238 memcpy(duressSalt_, &data[pos],
SALT_SIZE);
244 persistedBadgeLocked_ = badgeLocked_;
245 persistedPw1Retries_ = pw1Retries_;
246 persistedPw3Retries_ = pw3Retries_;
251 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash,
BADGE_HASH_SIZE);
253 LOG_I(
TAG,
"Loaded PINs from R-Memory (Badge locked=%s, PW1=%d, PW3=%d retries)",
254 badgeLocked_ ?
"yes" :
"no", pw1Retries_, pw3Retries_);
262bool PinManager::saveToStorage() {
264 if (!se || !se->isSessionActive()) {
265 LOG_E(
TAG,
"SE session not active");
269 uint8_t data[STORAGE_SIZE];
279 data[pos++] = badgeLocked_ ? 0x01 : 0x00;
286 data[pos++] = (iterations_ >> 24) & 0xFF;
287 data[pos++] = (iterations_ >> 16) & 0xFF;
288 data[pos++] = (iterations_ >> 8) & 0xFF;
289 data[pos++] = iterations_ & 0xFF;
304 data[pos++] = pw1Retries_;
305 data[pos++] = pw3Retries_;
308 data[pos++] = duressSet_ ? 0x01 : 0x00;
309 memcpy(&data[pos], duressSalt_,
SALT_SIZE);
319 if (pos != PAYLOAD_SIZE) {
320 LOG_E(
TAG,
"Payload size mismatch (built=%zu, expected=%u)", pos, PAYLOAD_SIZE);
323 size_t sig_len = SIGNATURE_SIZE;
326 data + PAYLOAD_SIZE, &sig_len);
328 LOG_E(
TAG,
"Attestation sign failed (%d)",
static_cast<int>(sign_res));
336 LOG_E(
TAG,
"R-Memory write failed");
340 persistedBadgeLocked_ = badgeLocked_;
341 persistedPw1Retries_ = pw1Retries_;
342 persistedPw3Retries_ = pw3Retries_;
344 LOG_D(
TAG,
"PINs saved to R-Memory slot %d (signed, %u bytes)",
355bool PinManager::computeBadgeHash(
const char* pin, uint8_t* hashOut) {
356 if (!pin || !hashOut)
return false;
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);
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);
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;
387 if (len > 64)
return false;
392 size_t totalBytes = iterations_;
398 mbedtls_sha256_context ctx;
399 mbedtls_sha256_init(&ctx);
400 mbedtls_sha256_starts(&ctx, 0);
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);
409 mbedtls_sha256_finish(&ctx, hashOut);
410 mbedtls_sha256_free(&ctx);
411 mbedtls_platform_zeroize(buffer,
sizeof(buffer));
423bool PinManager::compareHash(
const uint8_t* h1,
const uint8_t* h2,
size_t len)
const {
425 for (
size_t i = 0; i < len; i++) {
426 diff |= h1[i] ^ h2[i];
446bool PinManager::verifyPin(PinSlot slot,
const char* pin) {
447 if (!pin)
return false;
448 if (!pinLoaded_)
init();
450 if (slot == PinSlot::BADGE) {
452 if (badgeRetries_ == 0) {
457 if (!computeBadgeHash(pin, inputHash))
return false;
462 badgeRetries_ = MAX_RETRIES;
463 lockoutActive_ =
false;
464 if (persistedBadgeLocked_) {
465 badgeLocked_ =
false;
472 LOG_W(
TAG,
"Wrong Badge PIN, %d retries left", badgeRetries_);
473 if (badgeRetries_ == 0) {
482 return verifyPinRaw(slot,
reinterpret_cast<const uint8_t*
>(pin), strlen(pin));
485bool PinManager::verifyPinRaw(PinSlot slot,
const uint8_t* data,
size_t len) {
486 if (!data)
return false;
487 if (!pinLoaded_)
init();
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;
501 retries = &pw1Retries_;
502 storedHash = pw1Hash_;
504 mirror = &persistedPw1Retries_;
508 retries = &pw3Retries_;
509 storedHash = pw3Hash_;
511 mirror = &persistedPw3Retries_;
523 if (!computeKdfHash(data, len, salt, inputHash))
return false;
525 const uint8_t before = *retries;
527 if (*retries < *mirror) {
528 if (!saveToStorage()) {
535 *retries = MAX_RETRIES;
536 if (*mirror != MAX_RETRIES) {
543 LOG_W(
TAG,
"Wrong %s, %d retries left", label, *retries);
553 return verifyPin(PinSlot::BADGE, pin);
575 minPinFloor_ = minLen;
579 if (!newPin)
return false;
580 size_t len = strlen(newPin);
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");
594 LOG_E(
TAG,
"Badge PIN must differ from duress PIN");
598 computeBadgeHash(newPin, badgeHash_);
599 badgeRetries_ = MAX_RETRIES;
600 badgeLocked_ =
false;
601 lockoutActive_ =
false;
605 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash,
BADGE_HASH_SIZE);
616 badgeRetries_ = MAX_RETRIES;
617 lockoutActive_ =
false;
619 badgeLocked_ =
false;
630 if (!hashOut)
return false;
641 if (!hashIn)
return false;
660 if (!pin)
return false;
661 if (!pinLoaded_)
init();
663 size_t len = strlen(pin);
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");
678 if (!computeBadgeHash(pin, candidateBadgeHash))
return false;
680 LOG_E(
TAG,
"Duress PIN must differ from badge PIN");
684 generateSalt(duressSalt_);
685 if (!computeKdfHash(pin, duressSalt_, duressHash_))
return false;
698 if (!pinLoaded_)
init();
699 if (!duressSet_)
return true;
702 memset(duressSalt_, 0,
sizeof(duressSalt_));
703 memset(duressHash_, 0,
sizeof(duressHash_));
716 if (!duressSet_ || !pin)
return false;
717 size_t len = strlen(pin);
718 if (len < BADGE_PIN_MIN || len >
BADGE_PIN_MAX)
return false;
721 if (!computeKdfHash(pin, duressSalt_, inputHash)) {
737 return verifyPin(PinSlot::PW1, pin);
747 if (!
verifyPW1(currentPin))
return false;
757 if (!newPin)
return false;
758 size_t len = strlen(newPin);
759 if (len < PW1_MIN || len >
PIN_MAX) {
765 generateSalt(pw1Salt_);
766 computeKdfHash(newPin, pw1Salt_, pw1Hash_);
767 pw1Retries_ = MAX_RETRIES;
775 return verifyPinRaw(PinSlot::PW1, data, 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;
784 LOG_I(
TAG,
"PW1 set from KDF reference");
794 if (!hashOut)
return false;
805 if (!saltOut)
return false;
814 if (pw1Retries_ < MAX_RETRIES) {
815 pw1Retries_ = MAX_RETRIES;
830 return verifyPin(PinSlot::PW3, pin);
840 if (!
verifyPW3(currentPin))
return false;
850 if (!newPin)
return false;
851 size_t len = strlen(newPin);
852 if (len < PW3_MIN || len >
PIN_MAX) {
857 generateSalt(pw3Salt_);
858 computeKdfHash(newPin, pw3Salt_, pw3Hash_);
859 pw3Retries_ = MAX_RETRIES;
867 return verifyPinRaw(PinSlot::PW3, data, 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;
876 LOG_I(
TAG,
"PW3 set from KDF reference");
886 if (!hashOut)
return false;
897 if (!saltOut)
return false;
906 if (pw3Retries_ < MAX_RETRIES) {
907 pw3Retries_ = MAX_RETRIES;
921 return badgeRetries_ == 0;
928 lockoutStartMs_ = esp_timer_get_time() / 1000;
929 lockoutActive_ =
true;
938 if (!lockoutActive_) {
942 uint32_t nowMs = esp_timer_get_time() / 1000;
943 uint32_t elapsed = nowMs - lockoutStartMs_;
956 if (!lockoutActive_) {
970 if (!lockoutActive_)
return;
973 lockoutActive_ =
false;
974 badgeRetries_ = MAX_RETRIES;
976 badgeLocked_ =
false;
979 LOG_I(
TAG,
"Badge recovery timer expired, retries restored to %u", MAX_RETRIES);
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_D(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
static constexpr uint8_t PIN_MAX
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
bool getPW1Hash(uint8_t *hashOut) const
Copies stored PW1 hash into caller buffer.
static constexpr uint32_t DEFAULT_ITERATIONS
bool setPW3Raw(const uint8_t *data, size_t len)
void resetPW1Retries()
Resets PW1 retry counter to maximum.
static constexpr uint32_t LOCKOUT_DURATION_MS
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
static constexpr uint8_t HASH_SHA256
bool isDuressPin(const char *pin) const
Constant-time check whether a candidate matches the duress PIN.
static constexpr uint8_t BADGE_PIN_MAX
bool getPW1Salt(uint8_t *saltOut) const
Copies stored PW1 salt into caller buffer.
static constexpr const char * DEFAULT_BADGE_PIN
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
static constexpr const char * DEFAULT_PW1
bool changePW1(const char *currentPin, const char *newPin)
Changes PW1 after validating the current value.
static constexpr uint8_t PW3_MIN
static constexpr uint8_t PW1_MIN
void resetPW3Retries()
Resets PW3 retry counter to maximum.
bool isBadgeBlocked() const
Lockout timer handling.
static constexpr uint8_t BADGE_PIN_MIN
static constexpr uint8_t KDF_ITERSALTED_S2K
static constexpr const char * DEFAULT_PW3
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
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
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).
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.