11#include <mbedtls/sha256.h>
18static const char*
TAG =
"FIDO2";
22#define FIDO2_RMEM_MAGIC "FID2"
23#define FIDO2_RMEM_MAGIC_LEN 4
24#define NVS_NAMESPACE "fido2"
25#define NVS_KEY_COUNTER "auth_cnt"
52#define FIDO2_STORED_SIZE sizeof(fido2_stored_cred_t)
55#define FIDO2_FLAG_RESIDENT 0x01
59EXT_RAM_BSS_ATTR
static struct {
94 uint16_t rmem_start, uint16_t rmem_end) {
158 return count > 0 && slot < count;
177 uint16_t offset =
static_cast<uint16_t
>(slot);
190 if (!stored)
return false;
193 if (!se)
return false;
199 auto res = se->rmemRead(rmem_slot, data,
sizeof(data), &size);
204 auto* tmp =
reinterpret_cast<fido2_stored_cred_t*
>(data);
209 memcpy(stored, tmp,
sizeof(fido2_stored_cred_t));
223 memcpy(
g_storage.creds[slot].rp_id_hash, stored->rp_id_hash, 32);
226 g_storage.creds[slot].user_id_len = stored->user_id_len;
227 if (stored->user_id_len > 0) {
228 memcpy(
g_storage.creds[slot].user_id, stored->user_id, stored->user_id_len);
230 g_storage.creds[slot].sign_count = stored->sign_count;
231 g_storage.creds[slot].resident = is_resident;
232 g_storage.creds[slot].cred_protect = stored->cred_protect;
233 g_storage.creds[slot].curve = stored->curve;
246 se->eccDelete(phys_slot);
249 se->rmemErase(rmem_slot);
279 *p++ =
static_cast<uint8_t
>(pad + actual_len);
283 memcpy(p, mpi + skip, actual_len);
284 return p + actual_len;
295 uint8_t* p = der_sig;
299 uint8_t* len_pos = p++;
303 *len_pos =
static_cast<uint8_t
>(end - len_pos - 1);
304 return static_cast<uint8_t
>(end - der_sig);
316 if (!se)
return false;
321 se->rmemErase(rmem_slot);
323 if (se->rmemWrite(rmem_slot,
reinterpret_cast<const uint8_t*
>(stored),
325 LOG_E(
TAG,
"Failed to write credential metadata to slot %d", rmem_slot);
340 if (err != ESP_ERR_NVS_NOT_FOUND) {
341 LOG_W(
TAG,
"Failed to open NVS for counter: %s", esp_err_to_name(err));
350 if (err != ESP_ERR_NVS_NOT_FOUND) {
351 LOG_W(
TAG,
"Failed to read counter from NVS: %s", esp_err_to_name(err));
381 uint32_t new_value =
g_storage.auth_counter + 1;
384 esp_err_t err = nvs_open(
NVS_NAMESPACE, NVS_READWRITE, &nvs);
386 LOG_E(
TAG,
"Failed to open NVS for counter write: %s", esp_err_to_name(err));
392 LOG_E(
TAG,
"Failed to set counter in NVS: %s", esp_err_to_name(err));
397 err = nvs_commit(nvs);
399 LOG_E(
TAG,
"NVS commit failed for counter: %s", esp_err_to_name(err));
424 LOG_I(
TAG,
"Initializing storage...");
431 LOG_E(
TAG,
"No secure element available");
437 LOG_E(
TAG,
"Slot range not configured");
443 if (rcount < count) {
444 LOG_E(
TAG,
"R-Memory range smaller than ECC range");
449 fido2_stored_cred_t stored;
454 LOG_D(
TAG,
"Found credential %d: %s (curve=%d)", i, stored.rp_id, stored.curve);
505 uint8_t *out_slots, uint8_t max_slots) {
512 out_slots[count++] = i;
527 uint8_t *out_slots, uint8_t max_slots) {
535 LOG_D(
TAG,
"Slot %d: valid=%d resident=%d rp_match=%d rp=%s",
538 if (
g_storage.creds[i].resident && rp_match) {
539 out_slots[count++] = i;
580 LOG_D(
TAG,
"Found existing credential in slot %d (empty user_id)", i);
584 LOG_D(
TAG,
"Found existing credential in slot %d for replacement", i);
601 uint8_t slot = cred_id[0];
631 size_t user_name_max) {
636 fido2_stored_cred_t stored;
642 uint8_t len = stored.user_id_len;
644 memcpy(
user_id, stored.user_id, len);
650 if (copy_len >= user_name_max) copy_len = user_name_max - 1;
651 memcpy(
user_name, stored.user_name, copy_len);
665 if (!cred_id)
return false;
684 fido2_stored_cred_t stored;
686 LOG_E(
TAG,
"Failed to read credential %d", slot);
692 out_cred_id[0] = slot;
693 memcpy(out_cred_id + 1, stored.cred_id_nonce, 16);
711 memset(info, 0,
sizeof(*info));
713 memcpy(info->rp_id_hash,
g_storage.creds[slot].rp_id_hash, 32);
716 info->user_id_len = 0;
717 info->sign_count =
g_storage.creds[slot].sign_count;
718 info->resident_key =
g_storage.creds[slot].resident;
719 info->cred_protect =
g_storage.creds[slot].cred_protect;
720 info->curve =
g_storage.creds[slot].curve;
769 uint8_t *out_cred_id,
781 if (existing_slot >= 0) {
783 LOG_I(
TAG,
"Replacing existing credential in slot %d", existing_slot);
784 slot = existing_slot;
802 LOG_I(
TAG,
"Creating %s credential in slot %d for %s", curve_name, slot,
rp_id);
806 LOG_D(
TAG,
"Erasing slot %d before key generation", slot);
809 if (!se)
return false;
810 se->eccDelete(phys_slot);
817 LOG_E(
TAG,
"Failed to generate %s key in slot %d", curve_name, slot);
828 LOG_E(
TAG,
"Failed to read public key from slot %d", slot);
829 se->eccDelete(phys_slot);
835 if (!se->getRandom(nonce, 16)) {
836 LOG_E(
TAG,
"Failed to generate nonce");
837 se->eccDelete(phys_slot);
845 out_cred_id[0] = slot;
846 memcpy(out_cred_id + 1, nonce, 16);
849 fido2_stored_cred_t stored;
850 memset(&stored, 0,
sizeof(stored));
863 stored.sign_count = 0;
864 memcpy(stored.cred_id_nonce, nonce, 16);
867 stored.curve =
curve;
871 se->eccDelete(phys_slot);
880 memcpy(out_pubkey, pubkey, pubkey_size);
883 LOG_I(
TAG,
"Created %s credential in slot %d", curve_name, slot);
897 LOG_I(
TAG,
"Deleting credential in slot %d", slot);
906 LOG_I(
TAG,
"Deleted credential in slot %d", slot);
922 uint32_t new_count =
g_storage.creds[slot].sign_count;
925 fido2_stored_cred_t stored;
927 stored.sign_count = new_count;
929 LOG_E(
TAG,
"CRITICAL: Failed to persist sign count for slot %d!", slot);
948 uint8_t *signature, uint8_t *sig_len) {
954 if (!se)
return false;
957 size_t raw_len =
sizeof(raw_sig);
959 if (se->ecdsaSign(phys_slot, msg, msg_len, raw_sig, &raw_len) !=
962 LOG_E(
TAG,
"ECDSA sign failed for slot %d", slot);
969 LOG_D(
TAG,
"Signed with slot %d, sig_len=%d", slot, *sig_len);
983 uint8_t *signature, uint8_t *sig_len) {
993 if (!se)
return false;
997 LOG_E(
TAG,
"EdDSA sign failed for slot %d", slot);
1001 LOG_D(
TAG,
"EdDSA signed %d bytes with slot %d", msg_len, slot);
1004 if (!se)
return false;
1007 if (se->ecdsaSign(phys_slot, msg, msg_len, signature, &raw_len) !=
1010 LOG_E(
TAG,
"ECDSA sign failed for slot %d", slot);
1014 LOG_D(
TAG,
"ECDSA signed %d bytes with slot %d", msg_len, slot);
1031 uint8_t *signature, uint8_t *sig_len) {
1037 if (!se)
return false;
1040 size_t raw_len =
sizeof(raw_sig);
1042 if (se->ecdsaSign(phys_slot, msg, msg_len, raw_sig, &raw_len) !=
1045 LOG_E(
TAG,
"ECDSA sign failed for slot %d", slot);
1052 LOG_D(
TAG,
"Signed DER %d bytes with slot %d, sig_len=%d", msg_len, slot, *sig_len);
1073 if (!se->eccSlotUsed(phys_slot)) {
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
uint8_t fido2_storage_ecc_end(void)
Returns configured ECC end slot.
static uint8_t s_ecc_start
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.
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.
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.
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.
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.
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.
void sha256(const uint8_t *data, size_t len, uint8_t out[32])
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]