13#include <mbedtls/sha256.h>
20static const char*
TAG =
"U2F";
38#define U2F_ATTEST_SLOT 0
59 while (start + 1 < len && mpi[start] == 0) {
62 size_t actual_len = len - start;
68 *p++ =
static_cast<uint8_t
>(pad + actual_len);
72 memcpy(p, mpi + start, actual_len);
73 return p + actual_len;
85 uint8_t *signature, uint8_t *sig_len) {
92 size_t raw_len =
sizeof(raw_sig);
93 if (se->ecdsaSign(
U2F_ATTEST_SLOT, data, data_len, raw_sig, &raw_len) !=
95 raw_len !=
sizeof(raw_sig)) {
96 LOG_E(
TAG,
"Attestation signing failed");
106 size_t body_len =
static_cast<size_t>(body_end - body);
108 uint8_t *p = signature;
110 *p++ =
static_cast<uint8_t
>(body_len);
111 memcpy(p, body, body_len);
114 *sig_len =
static_cast<uint8_t
>(p - signature);
127 LOG_I(
TAG,
"Initializing attestation...");
143 LOG_E(
TAG,
"Failed to read attestation public key");
148 LOG_E(
TAG,
"Attestation key has invalid curve");
152 LOG_I(
TAG,
"Attestation key ready (curve=P256)");
166 EXT_RAM_BSS_ATTR
static uint8_t tbs[512];
175 if (!se->getRandom(serial,
sizeof(serial))) {
176 LOG_E(
TAG,
"Failed to get random serial");
181 memcpy(t, serial, 8);
185 static const uint8_t ecdsa_sha256_oid[] = {
187 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02
189 memcpy(t, ecdsa_sha256_oid,
sizeof(ecdsa_sha256_oid));
190 t +=
sizeof(ecdsa_sha256_oid);
194 static const uint8_t fido2_subject[] = {
197 0x31, 0x0B, 0x30, 0x09,
198 0x06, 0x03, 0x55, 0x04, 0x06,
199 0x13, 0x02,
'D',
'E',
201 0x31, 0x0C, 0x30, 0x0A,
202 0x06, 0x03, 0x55, 0x04, 0x0A,
203 0x0C, 0x03,
'C',
'D',
'C',
205 0x31, 0x22, 0x30, 0x20,
206 0x06, 0x03, 0x55, 0x04, 0x0B,
208 'A',
'u',
't',
'h',
'e',
'n',
't',
'i',
'c',
'a',
't',
'o',
'r',
' ',
209 'A',
't',
't',
'e',
's',
't',
'a',
't',
'i',
'o',
'n',
211 0x31, 0x18, 0x30, 0x16,
212 0x06, 0x03, 0x55, 0x04, 0x03,
214 'C',
'D',
'C',
' ',
'B',
'a',
'd',
'g',
'e',
' ',
'F',
'I',
'D',
'O',
'2'
216 memcpy(t, fido2_subject,
sizeof(fido2_subject));
217 t +=
sizeof(fido2_subject);
221 static const uint8_t validity[] = {
223 0x17, 0x0D,
'2',
'4',
'0',
'1',
'0',
'1',
'0',
'0',
'0',
'0',
'0',
'0',
'Z',
224 0x17, 0x0D,
'4',
'9',
'1',
'2',
'3',
'1',
'2',
'3',
'5',
'9',
'5',
'9',
'Z'
226 memcpy(t, validity,
sizeof(validity));
227 t +=
sizeof(validity);
230 memcpy(t, fido2_subject,
sizeof(fido2_subject));
231 t +=
sizeof(fido2_subject);
235 static const uint8_t spki_prefix[] = {
238 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
239 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
242 memcpy(t, spki_prefix,
sizeof(spki_prefix));
243 t +=
sizeof(spki_prefix);
250 static const uint8_t fido2_extensions[] = {
255 0x06, 0x03, 0x55, 0x1D, 0x13,
257 0x04, 0x02, 0x30, 0x00,
260 0x06, 0x03, 0x55, 0x1D, 0x0F,
262 0x03, 0x02, 0x07, 0x80
264 memcpy(t, fido2_extensions,
sizeof(fido2_extensions));
265 t +=
sizeof(fido2_extensions);
267 size_t tbs_len = t - tbs;
270 EXT_RAM_BSS_ATTR
static uint8_t tbs_wrapped[600];
271 uint8_t *tw = tbs_wrapped;
278 *tw++ = (tbs_len >> 8) & 0xFF;
279 *tw++ = tbs_len & 0xFF;
281 memcpy(tw, tbs, tbs_len);
284 size_t tbs_wrapped_len = tw - tbs_wrapped;
291 LOG_E(
TAG,
"Failed to sign certificate");
297 size_t cert_content_len = tbs_wrapped_len +
sizeof(ecdsa_sha256_oid) + 2 + 1 + sig_len;
300 if (cert_content_len < 128) {
301 *p++ = cert_content_len;
304 *p++ = (cert_content_len >> 8) & 0xFF;
305 *p++ = cert_content_len & 0xFF;
309 memcpy(p, tbs_wrapped, tbs_wrapped_len);
310 p += tbs_wrapped_len;
313 memcpy(p, ecdsa_sha256_oid,
sizeof(ecdsa_sha256_oid));
314 p +=
sizeof(ecdsa_sha256_oid);
320 memcpy(p, sig, sig_len);
339 if (!cert || !cert_len) {
360 uint8_t *signature, uint8_t *sig_len) {
374 response[0] = (sw >> 8) & 0xFF;
375 response[1] = sw & 0xFF;
395static uint16_t
u2f_version(uint8_t *response, uint16_t response_max) {
396 const char *
version =
"U2F_V2";
399 if (response_max < len + 2) {
403 memcpy(response,
version, len);
404 response[len] = 0x90;
405 response[len + 1] = 0x00;
407 LOG_I(
TAG,
"Version request: U2F_V2");
418 uint8_t first = application[0];
420 for (
int i = 1; i < 32; i++) {
421 if (application[i] != first) {
425 LOG_I(
TAG,
"Detected dummy/blink request (app=0x%02x...)", first);
437static uint16_t
u2f_register(
const uint8_t *challenge,
const uint8_t *application,
438 uint8_t *response, uint16_t response_max) {
450 snprintf(dummy_id,
sizeof(dummy_id),
"U2F:%02x%02x%02x%02x",
451 application[0], application[1], application[2], application[3]);
458 LOG_D(
TAG,
"Dummy: no user presence yet");
463 LOG_I(
TAG,
"Dummy: user touched - generating response");
465 uint8_t dummy_pubkey[64];
468 !se->getRandom(dummy_pubkey, 64)) {
469 LOG_E(
TAG,
"Failed to get random for dummy response");
477 memcpy(response + offset, dummy_pubkey, 64);
492 size_t to_sign_len = 0;
493 to_sign[to_sign_len++] = 0x00;
494 memcpy(to_sign + to_sign_len, application, 32);
496 memcpy(to_sign + to_sign_len, challenge, 32);
501 memcpy(to_sign + to_sign_len, dummy_pubkey, 64);
509 memcpy(response + offset, signature, sig_len);
512 response[offset++] = 0x90;
513 response[offset++] = 0x00;
515 LOG_I(
TAG,
"Dummy response complete, len=%u", offset);
521 snprintf(
rp_id,
sizeof(
rp_id),
"U2F:%02x%02x%02x%02x",
522 application[0], application[1], application[2], application[3]);
544 LOG_E(
TAG,
"Failed to create credential");
547 LOG_I(
TAG,
"Created credential in slot %d", slot);
559 memcpy(response + offset, pubkey, 64);
571 LOG_E(
TAG,
"Attestation not initialized");
577 LOG_E(
TAG,
"Response buffer too small");
588 size_t to_sign_len = 0;
590 to_sign[to_sign_len++] = 0x00;
591 memcpy(to_sign + to_sign_len, application, 32);
593 memcpy(to_sign + to_sign_len, challenge, 32);
598 memcpy(to_sign + to_sign_len, pubkey, 64);
606 LOG_E(
TAG,
"Attestation signing failed");
611 memcpy(response + offset, signature, sig_len);
615 response[offset++] = 0x90;
616 response[offset++] = 0x00;
618 LOG_I(
TAG,
"Register complete, response len=%u", offset);
634 const uint8_t *application,
635 const uint8_t *key_handle, uint8_t key_handle_len,
636 uint8_t *response, uint16_t response_max) {
637 LOG_I(
TAG,
"Authenticate request, p1=0x%02X, kh_len=%d", p1, key_handle_len);
640 LOG_W(
TAG,
"Invalid key handle length: %d", key_handle_len);
652 fido2_credential_info_t cred;
654 LOG_E(
TAG,
"Failed to get credential info");
658 if (memcmp(cred.rp_id_hash, application, 32) != 0) {
659 LOG_W(
TAG,
"Application hash mismatch");
665 LOG_I(
TAG,
"Check-only: key handle valid");
692 response[offset++] = 0x01;
699 uint8_t to_sign[32 + 1 + 4 + 32];
700 size_t to_sign_len = 0;
702 memcpy(to_sign + to_sign_len, application, 32);
704 to_sign[to_sign_len++] = 0x01;
707 memcpy(to_sign + to_sign_len, challenge, 32);
719 memcpy(response + offset, signature, sig_len);
723 response[offset++] = 0x90;
724 response[offset++] = 0x00;
726 LOG_I(
TAG,
"Authenticate complete, counter=%u, response len=%u", counter, offset);
739 uint8_t *response, uint16_t response_max) {
741 LOG_W(
TAG,
"APDU too short: %d", apdu_len);
745 uint8_t cla = apdu[0];
746 uint8_t ins = apdu[1];
747 uint8_t p1 = apdu[2];
748 uint8_t p2 = apdu[3];
752 LOG_W(
TAG,
"Unsupported CLA: 0x%02X", cla);
756 LOG_I(
TAG,
"APDU: CLA=0x%02X INS=0x%02X P1=0x%02X P2=0x%02X len=%d",
757 cla, ins, p1, p2, apdu_len);
761 uint32_t data_len = 0;
762 const uint8_t *data = NULL;
765 if (apdu[4] == 0x00 && apdu_len > 6) {
767 data_len = (apdu[5] << 8) | apdu[6];
769 if (data_len + 7 > apdu_len) {
770 data_len = apdu_len - 7;
776 if (data_len + 5 > apdu_len) {
777 data_len = apdu_len - 5;
788 LOG_W(
TAG,
"Register: insufficient data: %u", data_len);
792 response, response_max);
796 LOG_W(
TAG,
"Authenticate: insufficient data: %u", data_len);
804 LOG_W(
TAG,
"Authenticate: key handle truncated");
809 kh, kh_len, response, response_max);
813 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 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
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.
static constexpr int RAW_SIGNATURE_COMPONENT_SIZE
static uint8_t g_attest_pubkey[65]
static constexpr uint8_t DER_ENSURE_POSITIVE_MASK
static constexpr uint8_t DER_LENGTH_TWO_BYTES
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
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
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