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"
20static const char*
TAG =
"PinManager";
41 if (pinLoaded_)
return true;
43 if (!loadFromStorage()) {
44 LOG_I(
TAG,
"Loading default PINs (storage empty or unreadable)");
50 badgeRetries_ = badgeLocked_ ? 0 : 1;
52 LOG_I(
TAG,
"Badge state after init: locked=%d retries=%u pinSet=%d",
53 badgeLocked_, badgeRetries_, badgePinIsSet_);
60void PinManager::loadDefaults() {
63 badgeRetries_ = MAX_RETRIES;
67 generateSalt(pw1Salt_);
68 generateSalt(pw3Salt_);
75 pw1Retries_ = MAX_RETRIES;
76 pw3Retries_ = MAX_RETRIES;
77 badgePinIsSet_ =
false;
81 memset(duressSalt_, 0,
sizeof(duressSalt_));
82 memset(duressHash_, 0,
sizeof(duressHash_));
91void PinManager::generateSalt(uint8_t* salt) {
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;
133 LOG_W(
TAG,
"Attestation pubkey read failed");
137 LOG_W(
TAG,
"Attestation key is not P-256");
141 uint8_t pub_sec1[65];
143 memcpy(pub_sec1 + 1, pub_raw, 64);
146 mbedtls_sha256(payload, payload_len,
hash, 0);
148 mbedtls_ecp_group grp;
151 mbedtls_ecp_group_init(&grp);
152 mbedtls_ecp_point_init(&Q);
153 mbedtls_mpi_init(&r);
154 mbedtls_mpi_init(&s);
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;
165 mbedtls_mpi_free(&r);
166 mbedtls_mpi_free(&s);
167 mbedtls_ecp_point_free(&Q);
168 mbedtls_ecp_group_free(&grp);
172bool PinManager::loadFromStorage() {
175 LOG_W(
TAG,
"SE session not active");
179 uint8_t data[STORAGE_SIZE];
180 uint16_t actualLen = 0;
184 LOG_D(
TAG,
"No PIN data in R-Memory (read err=%d)",
185 static_cast<int>(result));
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);
198 data + PAYLOAD_SIZE, SIGNATURE_SIZE)) {
199 LOG_W(
TAG,
"PIN storage signature invalid - re-initializing");
210 badgeLocked_ = (data[pos++] != 0);
216 iterations_ = (data[pos] << 24) | (data[pos+1] << 16) | (data[pos+2] << 8) | data[pos+3];
232 pw1Retries_ = data[pos++];
233 pw3Retries_ = data[pos++];
236 duressSet_ = (data[pos++] != 0);
237 memcpy(duressSalt_, &data[pos],
SALT_SIZE);
243 persistedBadgeLocked_ = badgeLocked_;
244 persistedPw1Retries_ = pw1Retries_;
245 persistedPw3Retries_ = pw3Retries_;
250 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash,
BADGE_HASH_SIZE);
252 LOG_I(
TAG,
"Loaded PINs from R-Memory (Badge locked=%s, PW1=%d, PW3=%d retries)",
253 badgeLocked_ ?
"yes" :
"no", pw1Retries_, pw3Retries_);
261bool PinManager::saveToStorage() {
263 if (!se || !se->isSessionActive()) {
264 LOG_E(
TAG,
"SE session not active");
268 uint8_t data[STORAGE_SIZE];
278 data[pos++] = badgeLocked_ ? 0x01 : 0x00;
285 data[pos++] = (iterations_ >> 24) & 0xFF;
286 data[pos++] = (iterations_ >> 16) & 0xFF;
287 data[pos++] = (iterations_ >> 8) & 0xFF;
288 data[pos++] = iterations_ & 0xFF;
303 data[pos++] = pw1Retries_;
304 data[pos++] = pw3Retries_;
307 data[pos++] = duressSet_ ? 0x01 : 0x00;
308 memcpy(&data[pos], duressSalt_,
SALT_SIZE);
318 if (pos != PAYLOAD_SIZE) {
319 LOG_E(
TAG,
"Payload size mismatch (built=%zu, expected=%u)", pos, PAYLOAD_SIZE);
322 size_t sig_len = SIGNATURE_SIZE;
325 data + PAYLOAD_SIZE, &sig_len);
327 LOG_E(
TAG,
"Attestation sign failed (%d)",
static_cast<int>(sign_res));
335 LOG_E(
TAG,
"R-Memory write failed");
339 persistedBadgeLocked_ = badgeLocked_;
340 persistedPw1Retries_ = pw1Retries_;
341 persistedPw3Retries_ = pw3Retries_;
343 LOG_D(
TAG,
"PINs saved to R-Memory slot %d (signed, %u bytes)",
354bool PinManager::computeBadgeHash(
const char* pin, uint8_t* hashOut) {
355 if (!pin || !hashOut)
return false;
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);
376bool PinManager::computeKdfHash(
const char* pin,
const uint8_t* salt, uint8_t* hashOut)
const {
377 if (!pin || !salt || !hashOut)
return false;
381 size_t pinLen = strlen(pin);
386 size_t totalBytes = iterations_;
392 mbedtls_sha256_context ctx;
393 mbedtls_sha256_init(&ctx);
394 mbedtls_sha256_starts(&ctx, 0);
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);
403 mbedtls_sha256_finish(&ctx, hashOut);
404 mbedtls_sha256_free(&ctx);
416bool PinManager::compareHash(
const uint8_t* h1,
const uint8_t* h2,
size_t len)
const {
418 for (
size_t i = 0; i < len; i++) {
419 diff |= h1[i] ^ h2[i];
439bool PinManager::verifyPin(PinSlot slot,
const char* pin) {
440 if (!pin)
return false;
441 if (!pinLoaded_)
init();
443 if (slot == PinSlot::BADGE) {
445 if (badgeRetries_ == 0) {
450 if (!computeBadgeHash(pin, inputHash))
return false;
455 badgeRetries_ = MAX_RETRIES;
456 lockoutActive_ =
false;
457 if (persistedBadgeLocked_) {
458 badgeLocked_ =
false;
465 LOG_W(
TAG,
"Wrong Badge PIN, %d retries left", badgeRetries_);
466 if (badgeRetries_ == 0) {
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;
486 retries = &pw1Retries_;
487 storedHash = pw1Hash_;
489 mirror = &persistedPw1Retries_;
493 retries = &pw3Retries_;
494 storedHash = pw3Hash_;
496 mirror = &persistedPw3Retries_;
508 if (!computeKdfHash(pin, salt, inputHash))
return false;
510 const uint8_t before = *retries;
512 if (*retries < *mirror) {
513 if (!saveToStorage()) {
520 *retries = MAX_RETRIES;
521 if (*mirror != MAX_RETRIES) {
528 LOG_W(
TAG,
"Wrong %s, %d retries left", label, *retries);
538 return verifyPin(PinSlot::BADGE, pin);
558 if (!newPin)
return false;
559 size_t len = strlen(newPin);
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");
572 LOG_E(
TAG,
"Badge PIN must differ from duress PIN");
576 computeBadgeHash(newPin, badgeHash_);
577 badgeRetries_ = MAX_RETRIES;
578 badgeLocked_ =
false;
579 lockoutActive_ =
false;
583 badgePinIsSet_ = !compareHash(badgeHash_, defaultHash,
BADGE_HASH_SIZE);
594 badgeRetries_ = MAX_RETRIES;
595 lockoutActive_ =
false;
597 badgeLocked_ =
false;
608 if (!hashOut)
return false;
619 if (!hashIn)
return false;
638 if (!pin)
return false;
639 if (!pinLoaded_)
init();
641 size_t len = strlen(pin);
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");
656 if (!computeBadgeHash(pin, candidateBadgeHash))
return false;
658 LOG_E(
TAG,
"Duress PIN must differ from badge PIN");
662 generateSalt(duressSalt_);
663 if (!computeKdfHash(pin, duressSalt_, duressHash_))
return false;
676 if (!pinLoaded_)
init();
677 if (!duressSet_)
return true;
680 memset(duressSalt_, 0,
sizeof(duressSalt_));
681 memset(duressHash_, 0,
sizeof(duressHash_));
694 if (!duressSet_ || !pin)
return false;
695 size_t len = strlen(pin);
696 if (len < BADGE_PIN_MIN || len >
BADGE_PIN_MAX)
return false;
699 if (!computeKdfHash(pin, duressSalt_, inputHash)) {
715 return verifyPin(PinSlot::PW1, pin);
725 if (!
verifyPW1(currentPin))
return false;
735 if (!newPin)
return false;
736 size_t len = strlen(newPin);
737 if (len < PW1_MIN || len >
PIN_MAX) {
743 generateSalt(pw1Salt_);
744 computeKdfHash(newPin, pw1Salt_, pw1Hash_);
745 pw1Retries_ = MAX_RETRIES;
758 if (!hashOut)
return false;
769 if (!saltOut)
return false;
778 if (pw1Retries_ < MAX_RETRIES) {
779 pw1Retries_ = MAX_RETRIES;
794 return verifyPin(PinSlot::PW3, pin);
804 if (!
verifyPW3(currentPin))
return false;
814 if (!newPin)
return false;
815 size_t len = strlen(newPin);
816 if (len < PW3_MIN || len >
PIN_MAX) {
821 generateSalt(pw3Salt_);
822 computeKdfHash(newPin, pw3Salt_, pw3Hash_);
823 pw3Retries_ = MAX_RETRIES;
836 if (!hashOut)
return false;
847 if (!saltOut)
return false;
856 if (pw3Retries_ < MAX_RETRIES) {
857 pw3Retries_ = MAX_RETRIES;
871 return badgeRetries_ == 0;
878 lockoutStartMs_ = esp_timer_get_time() / 1000;
879 lockoutActive_ =
true;
888 if (!lockoutActive_) {
892 uint32_t nowMs = esp_timer_get_time() / 1000;
893 uint32_t elapsed = nowMs - lockoutStartMs_;
906 if (!lockoutActive_) {
920 if (!lockoutActive_)
return;
923 lockoutActive_ =
false;
924 badgeRetries_ = MAX_RETRIES;
926 badgeLocked_ =
false;
929 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
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)
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
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 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.