12#include <mbedtls/sha256.h>
19static const char*
TAG =
"FIDO2";
23#define FIDO2_RMEM_MAGIC "FID2"
24#define FIDO2_RMEM_MAGIC_LEN 4
25#define NVS_NAMESPACE "fido2"
26#define NVS_KEY_COUNTER "auth_cnt"
27#define NVS_KEY_LARGEBLOB "lblob"
28#define NVS_KEY_ALWAYS_UV "always_uv"
29#define NVS_KEY_MIN_PIN "min_pin"
56#define FIDO2_STORED_SIZE sizeof(fido2_stored_cred_t)
59#define FIDO2_FLAG_RESIDENT 0x01
63EXT_RAM_BSS_ATTR
static struct {
98 uint16_t rmem_start, uint16_t rmem_end) {
162 return count > 0 && slot < count;
181 uint16_t offset =
static_cast<uint16_t
>(slot);
194 if (!stored)
return false;
197 if (!se)
return false;
203 auto res = se->rmemRead(rmem_slot, data,
sizeof(data), &size);
208 auto* tmp =
reinterpret_cast<fido2_stored_cred_t*
>(data);
213 memcpy(stored, tmp,
sizeof(fido2_stored_cred_t));
227 memcpy(
g_storage.creds[slot].rp_id_hash, stored->rp_id_hash, 32);
230 g_storage.creds[slot].user_id_len = stored->user_id_len;
231 if (stored->user_id_len > 0) {
232 memcpy(
g_storage.creds[slot].user_id, stored->user_id, stored->user_id_len);
234 g_storage.creds[slot].sign_count = stored->sign_count;
235 g_storage.creds[slot].resident = is_resident;
236 g_storage.creds[slot].cred_protect = stored->cred_protect;
237 g_storage.creds[slot].curve = stored->curve;
250 se->eccDelete(phys_slot);
253 se->rmemErase(rmem_slot);
283 *p++ =
static_cast<uint8_t
>(pad + actual_len);
287 memcpy(p, mpi + skip, actual_len);
288 return p + actual_len;
299 uint8_t* p = der_sig;
303 uint8_t* len_pos = p++;
307 *len_pos =
static_cast<uint8_t
>(end - len_pos - 1);
308 return static_cast<uint8_t
>(end - der_sig);
320 if (!se)
return false;
325 se->rmemErase(rmem_slot);
327 if (se->rmemWrite(rmem_slot,
reinterpret_cast<const uint8_t*
>(stored),
329 LOG_E(
TAG,
"Failed to write credential metadata to slot %d", rmem_slot);
344 if (err != ESP_ERR_NVS_NOT_FOUND) {
345 LOG_W(
TAG,
"Failed to open NVS for counter: %s", esp_err_to_name(err));
354 if (err != ESP_ERR_NVS_NOT_FOUND) {
355 LOG_W(
TAG,
"Failed to read counter from NVS: %s", esp_err_to_name(err));
385 uint32_t new_value =
g_storage.auth_counter + 1;
388 esp_err_t err = nvs_open(
NVS_NAMESPACE, NVS_READWRITE, &nvs);
390 LOG_E(
TAG,
"Failed to open NVS for counter write: %s", esp_err_to_name(err));
396 LOG_E(
TAG,
"Failed to set counter in NVS: %s", esp_err_to_name(err));
401 err = nvs_commit(nvs);
403 LOG_E(
TAG,
"NVS commit failed for counter: %s", esp_err_to_name(err));
431 if (err != ESP_OK || sz == 0) {
434 return static_cast<uint16_t
>(sz);
438 if (!out || !out_len)
return false;
440 bool fall_back_to_empty =
false;
443 fall_back_to_empty =
true;
449 *out_len =
static_cast<uint16_t
>(sz);
452 if (err != ESP_ERR_NVS_NOT_FOUND) {
453 LOG_W(
TAG,
"largeblob read failed: %s", esp_err_to_name(err));
456 fall_back_to_empty =
true;
459 if (fall_back_to_empty) {
471 if (nvs_open(
NVS_NAMESPACE, NVS_READWRITE, &nvs) != ESP_OK)
return false;
473 if (err == ESP_OK) err = nvs_commit(nvs);
475 return err == ESP_OK;
480 if (nvs_open(
NVS_NAMESPACE, NVS_READONLY, &nvs) != ESP_OK)
return false;
484 return err == ESP_OK && v != 0;
489 if (nvs_open(
NVS_NAMESPACE, NVS_READWRITE, &nvs) != ESP_OK)
return false;
491 if (err == ESP_OK) err = nvs_commit(nvs);
493 return err == ESP_OK;
498 if (nvs_open(
NVS_NAMESPACE, NVS_READONLY, &nvs) != ESP_OK)
return 0;
502 return (err == ESP_OK) ? v : 0;
507 if (nvs_open(
NVS_NAMESPACE, NVS_READWRITE, &nvs) != ESP_OK)
return false;
509 if (err == ESP_OK) err = nvs_commit(nvs);
511 return err == ESP_OK;
516 if (nvs_open(
NVS_NAMESPACE, NVS_READWRITE, &nvs) != ESP_OK)
return;
531 LOG_I(
TAG,
"Initializing storage...");
538 LOG_E(
TAG,
"No secure element available");
544 LOG_E(
TAG,
"Slot range not configured");
550 if (rcount < count) {
551 LOG_E(
TAG,
"R-Memory range smaller than ECC range");
556 fido2_stored_cred_t stored;
561 LOG_D(
TAG,
"Found credential %d: %s (curve=%d)", i, stored.rp_id, stored.curve);
612 uint8_t *out_slots, uint8_t max_slots) {
619 out_slots[count++] = i;
634 uint8_t *out_slots, uint8_t max_slots) {
642 LOG_D(
TAG,
"Slot %d: valid=%d resident=%d rp_match=%d rp=%s",
645 if (
g_storage.creds[i].resident && rp_match) {
646 out_slots[count++] = i;
687 LOG_D(
TAG,
"Found existing credential in slot %d (empty user_id)", i);
691 LOG_D(
TAG,
"Found existing credential in slot %d for replacement", i);
708 uint8_t slot = cred_id[0];
738 size_t user_name_max) {
743 fido2_stored_cred_t stored;
749 uint8_t len = stored.user_id_len;
751 memcpy(
user_id, stored.user_id, len);
757 if (copy_len >= user_name_max) copy_len = user_name_max - 1;
758 memcpy(
user_name, stored.user_name, copy_len);
772 if (!cred_id)
return false;
791 fido2_stored_cred_t stored;
793 LOG_E(
TAG,
"Failed to read credential %d", slot);
799 out_cred_id[0] = slot;
800 memcpy(out_cred_id + 1, stored.cred_id_nonce, 16);
818 memset(info, 0,
sizeof(*info));
820 memcpy(info->rp_id_hash,
g_storage.creds[slot].rp_id_hash, 32);
823 info->user_id_len = 0;
824 info->sign_count =
g_storage.creds[slot].sign_count;
825 info->resident_key =
g_storage.creds[slot].resident;
826 info->cred_protect =
g_storage.creds[slot].cred_protect;
827 info->curve =
g_storage.creds[slot].curve;
876 uint8_t *out_cred_id,
888 if (existing_slot >= 0) {
890 LOG_I(
TAG,
"Replacing existing credential in slot %d", existing_slot);
891 slot = existing_slot;
909 LOG_I(
TAG,
"Creating %s credential in slot %d for %s", curve_name, slot,
rp_id);
913 LOG_D(
TAG,
"Erasing slot %d before key generation", slot);
916 if (!se)
return false;
917 se->eccDelete(phys_slot);
924 LOG_E(
TAG,
"Failed to generate %s key in slot %d", curve_name, slot);
935 LOG_E(
TAG,
"Failed to read public key from slot %d", slot);
936 se->eccDelete(phys_slot);
942 if (!se->getRandom(nonce, 16)) {
943 LOG_E(
TAG,
"Failed to generate nonce");
944 se->eccDelete(phys_slot);
952 out_cred_id[0] = slot;
953 memcpy(out_cred_id + 1, nonce, 16);
956 fido2_stored_cred_t stored;
957 memset(&stored, 0,
sizeof(stored));
970 stored.sign_count = 0;
971 memcpy(stored.cred_id_nonce, nonce, 16);
974 stored.curve =
curve;
978 se->eccDelete(phys_slot);
987 memcpy(out_pubkey, pubkey, pubkey_size);
990 LOG_I(
TAG,
"Created %s credential in slot %d", curve_name, slot);
1004 LOG_I(
TAG,
"Deleting credential in slot %d", slot);
1013 LOG_I(
TAG,
"Deleted credential in slot %d", slot);
1029 uint32_t new_count =
g_storage.creds[slot].sign_count;
1032 fido2_stored_cred_t stored;
1034 stored.sign_count = new_count;
1036 LOG_E(
TAG,
"CRITICAL: Failed to persist sign count for slot %d!", slot);
1055 uint8_t *signature, uint8_t *sig_len) {
1061 if (!se)
return false;
1064 size_t raw_len =
sizeof(raw_sig);
1066 if (se->ecdsaSign(phys_slot, msg, msg_len, raw_sig, &raw_len) !=
1069 LOG_E(
TAG,
"ECDSA sign failed for slot %d", slot);
1076 LOG_D(
TAG,
"Signed with slot %d, sig_len=%d", slot, *sig_len);
1090 uint8_t *signature, uint8_t *sig_len) {
1100 if (!se)
return false;
1104 LOG_E(
TAG,
"EdDSA sign failed for slot %d", slot);
1108 LOG_D(
TAG,
"EdDSA signed %d bytes with slot %d", msg_len, slot);
1111 if (!se)
return false;
1114 if (se->ecdsaSign(phys_slot, msg, msg_len, signature, &raw_len) !=
1117 LOG_E(
TAG,
"ECDSA sign failed for slot %d", slot);
1121 LOG_D(
TAG,
"ECDSA signed %d bytes with slot %d", msg_len, slot);
1138 uint8_t *signature, uint8_t *sig_len) {
1144 if (!se)
return false;
1147 size_t raw_len =
sizeof(raw_sig);
1149 if (se->ecdsaSign(phys_slot, msg, msg_len, raw_sig, &raw_len) !=
1152 LOG_E(
TAG,
"ECDSA sign failed for slot %d", slot);
1159 LOG_D(
TAG,
"Signed DER %d bytes with slot %d, sig_len=%d", msg_len, slot, *sig_len);
1180 if (!se->eccSlotUsed(phys_slot)) {
Portable authenticatorLargeBlobs write-session accumulator and the canonical empty large-blob array c...
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,...)
#define CDC_CURVE_ED25519
#define FIDO2_MAX_CREDENTIALS
#define FIDO2_CRED_ID_LEN
#define FIDO2_RP_ID_MAX_LEN
#define FIDO2_USER_NAME_MAX_LEN
#define FIDO2_USER_ID_MAX_LEN
#define FIDO2_SIG_COMPONENT_SIZE
bool fido2_storage_largeblob_get(uint8_t *out, uint16_t max_len, uint16_t *out_len)
bool fido2_storage_get_always_uv(void)
uint8_t fido2_storage_ecc_end(void)
Returns configured ECC end slot.
bool fido2_storage_set_always_uv(bool enabled)
static uint8_t s_ecc_start
bool fido2_storage_largeblob_set(const uint8_t *data, uint16_t len)
bool fido2_storage_counter_flush(void)
No-op flush retained for API stability; per-increment path commits.
static bool slot_logical_valid(uint8_t slot)
Checks whether logical slot index is within range.
#define FIDO2_RMEM_MAGIC_LEN
uint16_t fido2_storage_rmem_start(void)
Returns configured RMEM start slot.
static void erase_slot_data(uint8_t logical_slot)
Erases ECC key material and R-Memory data for a logical slot.
static bool write_rmem_credential(uint8_t logical_slot, const fido2_stored_cred_t *stored)
Writes credential metadata to R-Memory after erasing the destination slot.
bool fido2_storage_set_min_pin_len(uint8_t min_len)
uint8_t fido2_storage_count(void)
Credential lookup operations using in-memory cache only.
static uint16_t rmem_slot_for_logical(uint8_t slot)
Maps logical slot to physical RMEM slot.
bool fido2_storage_sign(uint8_t slot, const uint8_t *msg, uint16_t msg_len, uint8_t *signature, uint8_t *sig_len)
Signing operations requiring secure-element access.
static uint8_t raw_sig_to_der(const uint8_t raw_sig[FIDO2_SIG_SIZE], uint8_t *der_sig)
Converts raw 64-byte ECDSA signature (R||S) to DER sequence format.
uint16_t fido2_storage_largeblob_length(void)
authenticatorLargeBlobs and authenticatorConfig persistence (NVS).
static uint16_t s_rmem_start
#define FIDO2_FLAG_RESIDENT
Stored-credential flag bits.
static uint8_t * encode_der_integer(uint8_t *p, const uint8_t *mpi)
Encodes a single ECDSA P-256 component (R or S) as a DER INTEGER.
bool fido2_storage_sign_raw(uint8_t slot, const uint8_t *msg, uint16_t msg_len, uint8_t *signature, uint8_t *sig_len)
Signs message and returns raw signature (EdDSA/ECDSA).
struct @262231322003320050276064353325174062307231151161::@131310070117174352112321004206244146355206237313 creds[FIDO2_MAX_CREDENTIALS]
bool fido2_storage_get_user(uint8_t slot, uint8_t *user_id, uint8_t *user_id_len, char *user_name, size_t user_name_max)
Loads user handle and optional user name for a credential slot.
bool fido2_storage_sign_der(uint8_t slot, const uint8_t *msg, uint16_t msg_len, uint8_t *signature, uint8_t *sig_len)
Signs data and returns DER-encoded signature for U2F compatibility.
static uint16_t rmem_count(void)
Returns number of configured logical RMEM slots.
uint32_t fido2_storage_counter_get(void)
Returns current global authentication counter.
#define NVS_KEY_LARGEBLOB
uint8_t fido2_storage_ecc_start(void)
Returns configured ECC start slot.
bool fido2_storage_get_credential(uint8_t slot, fido2_credential_info_t *info)
Credential create/read/delete operations.
uint8_t fido2_storage_get_min_pin_len(void)
#define NVS_KEY_ALWAYS_UV
static bool read_rmem_credential(uint8_t logical_slot, fido2_stored_cred_t *stored)
Internal helper functions for slot and cache management.
static uint16_t s_rmem_end
static constexpr uint8_t DER_TAG_INTEGER
bool fido2_storage_verify_cred_id(uint8_t slot, const uint8_t *cred_id)
Verifies credential-id for logical slot.
bool fido2_storage_delete_credential(uint8_t slot)
Deletes credential and associated slot data.
void fido2_storage_config_reset(void)
uint8_t user_id[FIDO2_USER_ID_MAX_LEN]
char rp_id[FIDO2_RP_ID_MAX_LEN]
static uint16_t ecc_count(void)
Returns number of configured logical ECC slots.
static struct @262231322003320050276064353325174062307231151161 g_storage
Runtime storage/cache state.
static bool slot_range_valid(void)
Validates slot-range configuration.
bool fido2_storage_counter_increment(void)
Increments and persists global authentication counter.
uint8_t fido2_storage_find_by_rp_resident(const uint8_t *rp_id_hash, uint8_t *out_slots, uint8_t max_slots)
Finds resident credentials matching RP hash.
bool fido2_storage_get_cred_id(uint8_t slot, uint8_t *out_cred_id)
Builds credential-id blob for logical slot.
int8_t fido2_storage_find_slot_by_cred_id(const uint8_t *cred_id, uint16_t cred_id_len)
Resolves and verifies logical slot from credential-id blob.
bool fido2_storage_get_pubkey(uint8_t slot, uint8_t *pubkey)
Reads public key from secure-element slot.
void fido2_storage_set_slot_range(uint8_t ecc_start, uint8_t ecc_end, uint16_t rmem_start, uint16_t rmem_end)
Configures FIDO2 storage slot ranges.
#define FIDO2_RMEM_MAGIC
Persistent storage layout definitions.
uint8_t fido2_storage_get_curve(uint8_t slot)
Returns stored curve identifier for slot.
bool fido2_storage_create_credential(const char *rp_id, const uint8_t *rp_id_hash, const uint8_t *user_id, uint8_t user_id_len, const char *user_name, bool resident_key, uint8_t cred_protect, uint8_t curve, uint8_t *out_slot, uint8_t *out_cred_id, uint8_t *out_pubkey)
Creates or replaces credential in secure-element storage.
static constexpr uint8_t DER_INTEGER_MSB_MASK
MSB mask used to detect when DER INTEGER needs a 0x00 padding byte.
uint16_t fido2_storage_rmem_end(void)
Returns configured RMEM end slot.
static uint8_t ecc_slot_for_logical(uint8_t slot)
Maps logical slot to physical ECC slot.
uint8_t fido2_storage_find_by_rp(const uint8_t *rp_id_hash, uint8_t *out_slots, uint8_t max_slots)
Finds credentials matching RP hash.
uint32_t fido2_storage_increment_sign_count(uint8_t slot)
Increments per-credential sign counter and persists metadata.
bool fido2_storage_slot_used(uint8_t slot)
Checks whether logical slot is occupied.
uint8_t fido2_storage_init(void)
Initialization and cache rebuild routines.
int8_t fido2_storage_find_free_slot(void)
Finds first unused logical slot.
static constexpr uint8_t DER_TAG_SEQUENCE
DER ASN.1 tags used for ECDSA signature encoding.
bool fido2_storage_is_resident(uint8_t slot)
Returns resident-key flag for slot.
int8_t fido2_storage_find_by_rp_user(const uint8_t *rp_id_hash, const uint8_t *user_id, uint8_t user_id_len)
Finds credential by RP hash and user handle for replacement logic.
#define FIDO2_STORED_SIZE
void fido2_storage_counter_load(void)
NVS-backed global authentication counter operations.
static void update_cache_from_stored(uint8_t slot, const fido2_stored_cred_t *stored, bool is_resident)
Updates cache entry from stored credential payload.
char user_name[FIDO2_USER_NAME_MAX_LEN]
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
constexpr uint16_t kLargeBlobEmptyLen
void sha256(const uint8_t *data, size_t len, uint8_t out[32])
constexpr uint16_t kLargeBlobMaxArray
Largest serialized large-blob array, advertised as maxSerializedLargeBlobArray.
const uint8_t kLargeBlobEmpty[kLargeBlobEmptyLen]
Canonical empty large-blob array bytes.
char user_name[FIDO2_USER_NAME_MAX_LEN]
uint8_t cred_id_nonce[16]
char rp_id[FIDO2_RP_ID_MAX_LEN]
uint8_t user_id[FIDO2_USER_ID_MAX_LEN]