13#include <mbedtls/sha256.h>
14#include <mbedtls/x509_crt.h>
15#include <mbedtls/pk.h>
23static const char*
TAG =
"U2F";
41#define U2F_ATTEST_SLOT 0
62 mbedtls_x509_crt_init(&crt);
64 if (mbedtls_x509_crt_parse_der(&crt, der, der_len) == 0) {
66 int n = mbedtls_pk_write_pubkey_der(&crt.pk, spki,
sizeof(spki));
73 mbedtls_x509_crt_free(&crt);
86 if (nvs_open(
ATTEST_NVS_NS, NVS_READONLY, &nvs) != ESP_OK)
return 0;
87 size_t len = out_size;
90 if (err != ESP_OK || len == 0 || len > out_size)
return 0;
92 LOG_W(
TAG,
"Imported attestation cert does not match slot 0, ignoring");
95 return static_cast<uint16_t
>(len);
111 while (start + 1 < len && mpi[start] == 0) {
114 size_t actual_len = len - start;
120 *p++ =
static_cast<uint8_t
>(pad + actual_len);
124 memcpy(p, mpi + start, actual_len);
125 return p + actual_len;
137 uint8_t *signature, uint8_t *sig_len) {
144 size_t raw_len =
sizeof(raw_sig);
145 if (se->ecdsaSign(
U2F_ATTEST_SLOT, data, data_len, raw_sig, &raw_len) !=
147 raw_len !=
sizeof(raw_sig)) {
148 LOG_E(
TAG,
"Attestation signing failed");
158 size_t body_len =
static_cast<size_t>(body_end - body);
160 uint8_t *p = signature;
162 *p++ =
static_cast<uint8_t
>(body_len);
163 memcpy(p, body, body_len);
166 *sig_len =
static_cast<uint8_t
>(p - signature);
179 LOG_I(
TAG,
"Initializing attestation...");
195 LOG_E(
TAG,
"Failed to read attestation public key");
200 LOG_E(
TAG,
"Attestation key has invalid curve");
204 LOG_I(
TAG,
"Attestation key ready (curve=P256)");
216 LOG_I(
TAG,
"Using imported attestation certificate (%u bytes)",
217 static_cast<unsigned>(imported));
229 EXT_RAM_BSS_ATTR
static uint8_t tbs[512];
238 if (!se->getRandom(serial,
sizeof(serial))) {
239 LOG_E(
TAG,
"Failed to get random serial");
244 memcpy(t, serial, 8);
248 static const uint8_t ecdsa_sha256_oid[] = {
250 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02
252 memcpy(t, ecdsa_sha256_oid,
sizeof(ecdsa_sha256_oid));
253 t +=
sizeof(ecdsa_sha256_oid);
257 static const uint8_t fido2_subject[] = {
260 0x31, 0x0B, 0x30, 0x09,
261 0x06, 0x03, 0x55, 0x04, 0x06,
262 0x13, 0x02,
'D',
'E',
264 0x31, 0x0C, 0x30, 0x0A,
265 0x06, 0x03, 0x55, 0x04, 0x0A,
266 0x0C, 0x03,
'C',
'D',
'C',
268 0x31, 0x22, 0x30, 0x20,
269 0x06, 0x03, 0x55, 0x04, 0x0B,
271 'A',
'u',
't',
'h',
'e',
'n',
't',
'i',
'c',
'a',
't',
'o',
'r',
' ',
272 'A',
't',
't',
'e',
's',
't',
'a',
't',
'i',
'o',
'n',
274 0x31, 0x18, 0x30, 0x16,
275 0x06, 0x03, 0x55, 0x04, 0x03,
277 'C',
'D',
'C',
' ',
'B',
'a',
'd',
'g',
'e',
' ',
'F',
'I',
'D',
'O',
'2'
279 memcpy(t, fido2_subject,
sizeof(fido2_subject));
280 t +=
sizeof(fido2_subject);
284 static const uint8_t validity[] = {
286 0x17, 0x0D,
'2',
'4',
'0',
'1',
'0',
'1',
'0',
'0',
'0',
'0',
'0',
'0',
'Z',
287 0x17, 0x0D,
'4',
'9',
'1',
'2',
'3',
'1',
'2',
'3',
'5',
'9',
'5',
'9',
'Z'
289 memcpy(t, validity,
sizeof(validity));
290 t +=
sizeof(validity);
293 memcpy(t, fido2_subject,
sizeof(fido2_subject));
294 t +=
sizeof(fido2_subject);
298 static const uint8_t spki_prefix[] = {
301 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
302 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
305 memcpy(t, spki_prefix,
sizeof(spki_prefix));
306 t +=
sizeof(spki_prefix);
313 static const uint8_t fido2_extensions[] = {
318 0x06, 0x03, 0x55, 0x1D, 0x13,
320 0x04, 0x02, 0x30, 0x00,
323 0x06, 0x03, 0x55, 0x1D, 0x0F,
325 0x03, 0x02, 0x07, 0x80
327 memcpy(t, fido2_extensions,
sizeof(fido2_extensions));
328 t +=
sizeof(fido2_extensions);
330 size_t tbs_len = t - tbs;
333 EXT_RAM_BSS_ATTR
static uint8_t tbs_wrapped[600];
334 uint8_t *tw = tbs_wrapped;
341 *tw++ = (tbs_len >> 8) & 0xFF;
342 *tw++ = tbs_len & 0xFF;
344 memcpy(tw, tbs, tbs_len);
347 size_t tbs_wrapped_len = tw - tbs_wrapped;
354 LOG_E(
TAG,
"Failed to sign certificate");
360 size_t cert_content_len = tbs_wrapped_len +
sizeof(ecdsa_sha256_oid) + 2 + 1 + sig_len;
363 if (cert_content_len < 128) {
364 *p++ = cert_content_len;
367 *p++ = (cert_content_len >> 8) & 0xFF;
368 *p++ = cert_content_len & 0xFF;
372 memcpy(p, tbs_wrapped, tbs_wrapped_len);
373 p += tbs_wrapped_len;
376 memcpy(p, ecdsa_sha256_oid,
sizeof(ecdsa_sha256_oid));
377 p +=
sizeof(ecdsa_sha256_oid);
383 memcpy(p, sig, sig_len);
402 if (!cert || !cert_len) {
414 if (!out)
return false;
425 LOG_W(
TAG,
"Rejecting attestation cert: public key mismatch");
430 if (nvs_open(
ATTEST_NVS_NS, NVS_READWRITE, &nvs) != ESP_OK)
return false;
432 if (err == ESP_OK) err = nvs_commit(nvs);
434 if (err != ESP_OK)
return false;
443 if (nvs_open(
ATTEST_NVS_NS, NVS_READWRITE, &nvs) == ESP_OK) {
462 uint8_t *signature, uint8_t *sig_len) {
476 response[0] = (sw >> 8) & 0xFF;
477 response[1] = sw & 0xFF;
497static uint16_t
u2f_version(uint8_t *response, uint16_t response_max) {
498 const char *
version =
"U2F_V2";
501 if (response_max < len + 2) {
505 memcpy(response,
version, len);
506 response[len] = 0x90;
507 response[len + 1] = 0x00;
509 LOG_I(
TAG,
"Version request: U2F_V2");
520 uint8_t first = application[0];
522 for (
int i = 1; i < 32; i++) {
523 if (application[i] != first) {
527 LOG_I(
TAG,
"Detected dummy/blink request (app=0x%02x...)", first);
539static uint16_t
u2f_register(
const uint8_t *challenge,
const uint8_t *application,
540 uint8_t *response, uint16_t response_max) {
552 snprintf(dummy_id,
sizeof(dummy_id),
"U2F:%02x%02x%02x%02x",
553 application[0], application[1], application[2], application[3]);
560 LOG_D(
TAG,
"Dummy: no user presence yet");
565 LOG_I(
TAG,
"Dummy: user touched - generating response");
567 uint8_t dummy_pubkey[64];
570 !se->getRandom(dummy_pubkey, 64)) {
571 LOG_E(
TAG,
"Failed to get random for dummy response");
579 memcpy(response + offset, dummy_pubkey, 64);
594 size_t to_sign_len = 0;
595 to_sign[to_sign_len++] = 0x00;
596 memcpy(to_sign + to_sign_len, application, 32);
598 memcpy(to_sign + to_sign_len, challenge, 32);
603 memcpy(to_sign + to_sign_len, dummy_pubkey, 64);
611 memcpy(response + offset, signature, sig_len);
614 response[offset++] = 0x90;
615 response[offset++] = 0x00;
617 LOG_I(
TAG,
"Dummy response complete, len=%u", offset);
623 snprintf(
rp_id,
sizeof(
rp_id),
"U2F:%02x%02x%02x%02x",
624 application[0], application[1], application[2], application[3]);
646 LOG_E(
TAG,
"Failed to create credential");
649 LOG_I(
TAG,
"Created credential in slot %d", slot);
661 memcpy(response + offset, pubkey, 64);
673 LOG_E(
TAG,
"Attestation not initialized");
679 LOG_E(
TAG,
"Response buffer too small");
690 size_t to_sign_len = 0;
692 to_sign[to_sign_len++] = 0x00;
693 memcpy(to_sign + to_sign_len, application, 32);
695 memcpy(to_sign + to_sign_len, challenge, 32);
700 memcpy(to_sign + to_sign_len, pubkey, 64);
708 LOG_E(
TAG,
"Attestation signing failed");
713 memcpy(response + offset, signature, sig_len);
717 response[offset++] = 0x90;
718 response[offset++] = 0x00;
720 LOG_I(
TAG,
"Register complete, response len=%u", offset);
736 const uint8_t *application,
737 const uint8_t *key_handle, uint8_t key_handle_len,
738 uint8_t *response, uint16_t response_max) {
739 LOG_I(
TAG,
"Authenticate request, p1=0x%02X, kh_len=%d", p1, key_handle_len);
742 LOG_W(
TAG,
"Invalid key handle length: %d", key_handle_len);
754 fido2_credential_info_t cred;
756 LOG_E(
TAG,
"Failed to get credential info");
760 if (memcmp(cred.rp_id_hash, application, 32) != 0) {
761 LOG_W(
TAG,
"Application hash mismatch");
767 LOG_I(
TAG,
"Check-only: key handle valid");
794 response[offset++] = 0x01;
801 uint8_t to_sign[32 + 1 + 4 + 32];
802 size_t to_sign_len = 0;
804 memcpy(to_sign + to_sign_len, application, 32);
806 to_sign[to_sign_len++] = 0x01;
809 memcpy(to_sign + to_sign_len, challenge, 32);
821 memcpy(response + offset, signature, sig_len);
825 response[offset++] = 0x90;
826 response[offset++] = 0x00;
828 LOG_I(
TAG,
"Authenticate complete, counter=%u, response len=%u", counter, offset);
841 uint8_t *response, uint16_t response_max) {
843 LOG_W(
TAG,
"APDU too short: %d", apdu_len);
847 uint8_t cla = apdu[0];
848 uint8_t ins = apdu[1];
849 uint8_t p1 = apdu[2];
850 uint8_t p2 = apdu[3];
854 LOG_W(
TAG,
"Unsupported CLA: 0x%02X", cla);
858 LOG_I(
TAG,
"APDU: CLA=0x%02X INS=0x%02X P1=0x%02X P2=0x%02X len=%d",
859 cla, ins, p1, p2, apdu_len);
863 uint32_t data_len = 0;
864 const uint8_t *data = NULL;
867 if (apdu[4] == 0x00 && apdu_len > 6) {
869 data_len = (apdu[5] << 8) | apdu[6];
871 if (data_len + 7 > apdu_len) {
872 data_len = apdu_len - 7;
878 if (data_len + 5 > apdu_len) {
879 data_len = apdu_len - 5;
890 LOG_W(
TAG,
"Register: insufficient data: %u", data_len);
894 response, response_max);
898 LOG_W(
TAG,
"Authenticate: insufficient data: %u", data_len);
906 LOG_W(
TAG,
"Authenticate: key handle truncated");
911 kh, kh_len, response, response_max);
915 LOG_W(
TAG,
"Unsupported INS: 0x%02X", ins);
Big-endian byte-packing helpers.
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,...)
void fido2_increment_auth_counter(void)
Increments global authentication counter.
fido2_user_presence_result_t fido2_request_user_presence(const char *rp_id, fido2_action_t action, const char *user_name)
Requests user presence from host/application callback.
fido2_user_presence_result_t
@ FIDO2_ACTION_AUTHENTICATE
uint8_t user_id[FIDO2_USER_ID_MAX_LEN]
char rp_id[FIDO2_RP_ID_MAX_LEN]
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).
bool fido2_storage_get_credential(uint8_t slot, fido2_credential_info_t *info)
Credential create/read/delete operations.
bool fido2_storage_delete_credential(uint8_t slot)
Deletes credential and associated slot data.
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_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.
uint32_t fido2_storage_increment_sign_count(uint8_t slot)
Increments per-credential sign counter and persists metadata.
void writeBe32(uint8_t *out, uint32_t v)
Writes a 32-bit value to a buffer in big-endian order.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
void sha256(const uint8_t *data, size_t len, uint8_t out[32])
static constexpr const char * ATTEST_NVS_NS
NVS location of an optionally imported (CA-signed) attestation cert.
static constexpr uint8_t EC_POINT_UNCOMPRESSED
ECDSA and EC-point encoding constants.
static uint16_t u2f_authenticate(uint8_t p1, const uint8_t *challenge, const uint8_t *application, const uint8_t *key_handle, uint8_t key_handle_len, uint8_t *response, uint16_t response_max)
Handles U2F AUTHENTICATE instruction (INS=0x02).
bool u2f_init_attestation(void)
Initializes attestation key material and builds self-signed attestation certificate.
static constexpr uint8_t DER_BIT_STRING_TAG
static constexpr uint8_t DER_EXPLICIT_TAG_3
static uint16_t u2f_load_imported_cert(uint8_t *out, size_t out_size)
Loads an imported attestation certificate from NVS when present and its public key still matches slot...
bool u2f_get_attestation_cert(const uint8_t **cert, uint16_t *cert_len)
Returns attestation certificate pointer and length, initializing attestation on demand if the boot-ti...
uint16_t u2f_process_apdu(const uint8_t *apdu, uint16_t apdu_len, uint8_t *response, uint16_t response_max)
Parses U2F APDU and dispatches to instruction handlers.
bool u2f_import_attestation_cert(const uint8_t *der, size_t len)
static constexpr int RAW_SIGNATURE_COMPONENT_SIZE
static bool cert_matches_attest_key(const uint8_t *der, size_t der_len)
Checks whether a DER certificate's public key matches the slot-0 attestation key. g_attest_pubkey mus...
static uint8_t g_attest_pubkey[65]
static constexpr uint8_t DER_ENSURE_POSITIVE_MASK
static constexpr uint8_t DER_LENGTH_TWO_BYTES
static constexpr const char * ATTEST_NVS_CERT
static bool g_attest_initialized
static uint16_t u2f_response_error(uint8_t *response, uint16_t sw)
Writes a U2F error status word to response buffer.
static bool is_dummy_application(const uint8_t *application)
U2F register/authenticate command helpers.
static bool u2f_attest_sign(const uint8_t *data, size_t data_len, uint8_t *signature, uint8_t *sig_len)
Signs payload hash with attestation key and encodes signature as DER.
static constexpr uint8_t DER_SEQUENCE_TAG
DER encoding helper constants for X.509/signature generation.
static uint16_t u2f_response_sw(uint8_t *response, uint16_t sw)
Writes a U2F status word to response buffer.
#define U2F_ATTEST_SLOT
Attestation certificate constants and cached buffers.
bool u2f_attestation_sign(const uint8_t *data, size_t data_len, uint8_t *signature, uint8_t *sig_len)
Signs payload using the attestation key, initializing attestation on demand if the boot-time init did...
static uint16_t g_attest_cert_len
static uint16_t u2f_version(uint8_t *response, uint16_t response_max)
Handles U2F VERSION instruction (INS=0x03).
static constexpr uint8_t DER_INTEGER_TAG
bool u2f_clear_attestation_cert(void)
static uint8_t * encode_der_integer(uint8_t *p, const uint8_t *mpi, size_t len)
Encodes a single big-endian unsigned integer as a DER INTEGER element.
static constexpr uint8_t DER_EXPLICIT_TAG_0
bool u2f_get_attestation_pubkey(uint8_t out[65])
static constexpr uint8_t DER_INTEGER_NEGATIVE_MASK
static uint16_t u2f_register(const uint8_t *challenge, const uint8_t *application, uint8_t *response, uint16_t response_max)
Handles U2F REGISTER instruction (INS=0x01).
static uint8_t g_attest_cert[U2F_MAX_ATT_CERT_SIZE]
Cached DER attestation certificate and associated state.
#define U2F_SW_CLA_NOT_SUPPORTED
#define U2F_CHALLENGE_SIZE
#define U2F_INS_AUTHENTICATE
#define U2F_APPLICATION_SIZE
#define U2F_MAX_ATT_CERT_SIZE
#define U2F_SW_WRONG_LENGTH
#define U2F_SW_WRONG_DATA
#define U2F_SW_INS_NOT_SUPPORTED
#define U2F_KEY_HANDLE_SIZE
#define U2F_MAX_EC_SIG_SIZE
#define U2F_SW_CONDITIONS_NOT_SATISFIED
#define U2F_AUTH_CHECK_ONLY