CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
u2f.cpp
Go to the documentation of this file.
1
5
6#include "mod_fido2/u2f.h"
7#include "mod_fido2/fido2.h"
11#include "cdc_core/Bytes.h"
12#include "cdc_log.h"
13#include <mbedtls/sha256.h>
14#include <esp_attr.h>
15#include <string.h>
16#include <stdio.h>
17
19
20static const char* TAG = "U2F";
21
23static constexpr uint8_t DER_SEQUENCE_TAG = 0x30;
24static constexpr uint8_t DER_INTEGER_TAG = 0x02;
25static constexpr uint8_t DER_BIT_STRING_TAG = 0x03;
26static constexpr uint8_t DER_EXPLICIT_TAG_0 = 0xA0; // [0] EXPLICIT
27static constexpr uint8_t DER_EXPLICIT_TAG_3 = 0xA3; // [3] EXPLICIT
28static constexpr uint8_t DER_LENGTH_TWO_BYTES = 0x82; // Length uses 2 following bytes
29
31static constexpr uint8_t EC_POINT_UNCOMPRESSED = 0x04; // Uncompressed EC point prefix
32static constexpr uint8_t DER_INTEGER_NEGATIVE_MASK = 0x80; // MSB set = negative in DER
33static constexpr uint8_t DER_ENSURE_POSITIVE_MASK = 0x7F; // Mask to ensure positive
34static constexpr int RAW_SIGNATURE_COMPONENT_SIZE = 32; // Size of R or S in raw signature
35
37
38#define U2F_ATTEST_SLOT 0 // ECC slot 0 reserved for attestation
39
41EXT_RAM_BSS_ATTR static uint8_t g_attest_cert[U2F_MAX_ATT_CERT_SIZE];
42static uint16_t g_attest_cert_len = 0;
43static uint8_t g_attest_pubkey[65]; // 0x04 || X || Y
44static bool g_attest_initialized = false;
45
56static uint8_t* encode_der_integer(uint8_t *p, const uint8_t *mpi, size_t len) {
57 // Skip leading zero bytes but keep at least one byte.
58 size_t start = 0;
59 while (start + 1 < len && mpi[start] == 0) {
60 start++;
61 }
62 size_t actual_len = len - start;
63
64 // Prepend padding byte if MSB is set (DER INTEGERs are signed, two's complement).
65 int pad = (mpi[start] & DER_INTEGER_NEGATIVE_MASK) ? 1 : 0;
66
67 *p++ = DER_INTEGER_TAG;
68 *p++ = static_cast<uint8_t>(pad + actual_len);
69 if (pad) {
70 *p++ = 0x00;
71 }
72 memcpy(p, mpi + start, actual_len);
73 return p + actual_len;
74}
75
84static bool u2f_attest_sign(const uint8_t *data, size_t data_len,
85 uint8_t *signature, uint8_t *sig_len) {
87 if (!se) {
88 return false;
89 }
90
91 uint8_t raw_sig[64]; // R || S (each 32 bytes)
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");
97 return false;
98 }
99
100 // DER: SEQUENCE { INTEGER R, INTEGER S }
101 // Encode R and S into a scratch buffer first so we can compute the SEQUENCE length.
102 uint8_t body[2 * (2 + 1 + RAW_SIGNATURE_COMPONENT_SIZE)]; // tag + len + pad + magnitude, twice
103 uint8_t *body_end = encode_der_integer(body, raw_sig, RAW_SIGNATURE_COMPONENT_SIZE);
104 body_end = encode_der_integer(body_end, raw_sig + RAW_SIGNATURE_COMPONENT_SIZE,
106 size_t body_len = static_cast<size_t>(body_end - body);
107
108 uint8_t *p = signature;
109 *p++ = DER_SEQUENCE_TAG;
110 *p++ = static_cast<uint8_t>(body_len);
111 memcpy(p, body, body_len);
112 p += body_len;
113
114 *sig_len = static_cast<uint8_t>(p - signature);
115 return true;
116}
117
124 return true;
125 }
126
127 LOG_I(TAG, "Initializing attestation...");
128
130 if (!se) {
131 return false;
132 }
133
134 // Check if attestation key exists in slot 0
135 uint8_t pubkey[65];
137 if (!se->eccSlotUsed(U2F_ATTEST_SLOT)) {
138 LOG_E(TAG, "Attestation key missing in slot %d", U2F_ATTEST_SLOT);
139 return false;
140 }
141
142 if (se->eccGetPublicKey(U2F_ATTEST_SLOT, pubkey, &curve) != cdc::hal::SeResult::OK) {
143 LOG_E(TAG, "Failed to read attestation public key");
144 return false;
145 }
146
148 LOG_E(TAG, "Attestation key has invalid curve");
149 return false;
150 }
151
152 LOG_I(TAG, "Attestation key ready (curve=P256)");
153
154 // Store public key (with uncompressed point prefix)
156 memcpy(g_attest_pubkey + 1, pubkey, 64);
157
158 // Build self-signed X.509 certificate manually (DER encoded)
159 // This is a minimal certificate structure for U2F
160 uint8_t *cert = g_attest_cert;
161 uint8_t *p = cert;
162
163 // We'll build the TBS (To Be Signed) certificate, sign it, then wrap
164
165 // TBS Certificate structure - use static PSRAM buffer (only called once at init)
166 EXT_RAM_BSS_ATTR static uint8_t tbs[512];
167 uint8_t *t = tbs;
168
169 // Version [0] EXPLICIT INTEGER = 2 (v3)
170 *t++ = DER_EXPLICIT_TAG_0; *t++ = 0x03; // [0] EXPLICIT
171 *t++ = DER_INTEGER_TAG; *t++ = 0x01; *t++ = 0x02; // INTEGER 2
172
173 // Serial number - random
174 uint8_t serial[8];
175 if (!se->getRandom(serial, sizeof(serial))) {
176 LOG_E(TAG, "Failed to get random serial");
177 return false;
178 }
179 serial[0] &= DER_ENSURE_POSITIVE_MASK; // Ensure positive
180 *t++ = DER_INTEGER_TAG; *t++ = 0x08; // INTEGER
181 memcpy(t, serial, 8);
182 t += 8;
183
184 // Signature algorithm: ecdsa-with-SHA256 (1.2.840.10045.4.3.2)
185 static const uint8_t ecdsa_sha256_oid[] = {
186 0x30, 0x0A, // SEQUENCE
187 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 // OID
188 };
189 memcpy(t, ecdsa_sha256_oid, sizeof(ecdsa_sha256_oid));
190 t += sizeof(ecdsa_sha256_oid);
191
192 // FIDO2-konformer Subject: C=DE, O=CDC, OU=Authenticator Attestation, CN=CDC Badge FIDO2
193 // Total: 13 + 14 + 36 + 26 = 89 bytes content
194 static const uint8_t fido2_subject[] = {
195 0x30, 0x59, // SEQUENCE (89 bytes)
196 // C=DE (13 bytes: SET(11) = SEQ(9) = OID(5) + PrintableString(2+2))
197 0x31, 0x0B, 0x30, 0x09,
198 0x06, 0x03, 0x55, 0x04, 0x06, // OID: C (2.5.4.6)
199 0x13, 0x02, 'D', 'E',
200 // O=CDC (14 bytes: SET(12) = SEQ(10) = OID(5) + UTF8String(2+3))
201 0x31, 0x0C, 0x30, 0x0A,
202 0x06, 0x03, 0x55, 0x04, 0x0A, // OID: O (2.5.4.10)
203 0x0C, 0x03, 'C', 'D', 'C',
204 // OU=Authenticator Attestation (36 bytes: SET(34) = SEQ(32) = OID(5) + UTF8String(2+25))
205 0x31, 0x22, 0x30, 0x20,
206 0x06, 0x03, 0x55, 0x04, 0x0B, // OID: OU (2.5.4.11)
207 0x0C, 0x19,
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',
210 // CN=CDC Badge FIDO2 (26 bytes: SET(24) = SEQ(22) = OID(5) + UTF8String(2+15))
211 0x31, 0x18, 0x30, 0x16,
212 0x06, 0x03, 0x55, 0x04, 0x03, // OID: CN (2.5.4.3)
213 0x0C, 0x0F,
214 'C', 'D', 'C', ' ', 'B', 'a', 'd', 'g', 'e', ' ', 'F', 'I', 'D', 'O', '2'
215 };
216 memcpy(t, fido2_subject, sizeof(fido2_subject));
217 t += sizeof(fido2_subject);
218
219 // Validity (2024-01-01 to 2049-12-31)
220 // UTCTime: 00-49 = 2000-2049, 50-99 = 1950-1999
221 static const uint8_t validity[] = {
222 0x30, 0x1E, // SEQUENCE
223 0x17, 0x0D, '2', '4', '0', '1', '0', '1', '0', '0', '0', '0', '0', '0', 'Z', // notBefore: 2024-01-01
224 0x17, 0x0D, '4', '9', '1', '2', '3', '1', '2', '3', '5', '9', '5', '9', 'Z' // notAfter: 2049-12-31
225 };
226 memcpy(t, validity, sizeof(validity));
227 t += sizeof(validity);
228
229 // Subject: same as issuer
230 memcpy(t, fido2_subject, sizeof(fido2_subject));
231 t += sizeof(fido2_subject);
232
233 // Subject Public Key Info
234 // AlgorithmIdentifier: ecPublicKey + prime256v1
235 static const uint8_t spki_prefix[] = {
236 0x30, 0x59, // SEQUENCE (89 bytes total)
237 0x30, 0x13, // SEQUENCE (algorithm)
238 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, // OID: ecPublicKey
239 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, // OID: prime256v1
240 0x03, 0x42, 0x00 // BIT STRING (66 bytes, 0 unused bits)
241 };
242 memcpy(t, spki_prefix, sizeof(spki_prefix));
243 t += sizeof(spki_prefix);
244
245 // Public key (0x04 || X || Y)
246 memcpy(t, g_attest_pubkey, 65);
247 t += 65;
248
249 // FIDO2 Extensions: basicConstraints (critical, CA:FALSE) + keyUsage (digitalSignature)
250 static const uint8_t fido2_extensions[] = {
251 0xA3, 0x1D, // [3] EXPLICIT (29 bytes)
252 0x30, 0x1B, // SEQUENCE (27 bytes)
253 // basicConstraints: critical, CA:FALSE
254 0x30, 0x0C,
255 0x06, 0x03, 0x55, 0x1D, 0x13, // OID 2.5.29.19
256 0x01, 0x01, 0xFF, // critical=TRUE
257 0x04, 0x02, 0x30, 0x00, // CA:FALSE
258 // keyUsage: digitalSignature
259 0x30, 0x0B,
260 0x06, 0x03, 0x55, 0x1D, 0x0F, // OID 2.5.29.15
261 0x04, 0x04,
262 0x03, 0x02, 0x07, 0x80 // digitalSignature bit
263 };
264 memcpy(t, fido2_extensions, sizeof(fido2_extensions));
265 t += sizeof(fido2_extensions);
266
267 size_t tbs_len = t - tbs;
268
269 // Now wrap TBS in SEQUENCE - use static PSRAM buffer (only called once at init)
270 EXT_RAM_BSS_ATTR static uint8_t tbs_wrapped[600];
271 uint8_t *tw = tbs_wrapped;
272
273 *tw++ = DER_SEQUENCE_TAG;
274 if (tbs_len < 128) {
275 *tw++ = tbs_len;
276 } else {
277 *tw++ = DER_LENGTH_TWO_BYTES;
278 *tw++ = (tbs_len >> 8) & 0xFF;
279 *tw++ = tbs_len & 0xFF;
280 }
281 memcpy(tw, tbs, tbs_len);
282 tw += tbs_len;
283
284 size_t tbs_wrapped_len = tw - tbs_wrapped;
285
286 // Sign the TBS
287 uint8_t sig[U2F_MAX_EC_SIG_SIZE];
288 uint8_t sig_len = 0;
289
290 if (!u2f_attest_sign(tbs_wrapped, tbs_wrapped_len, sig, &sig_len)) {
291 LOG_E(TAG, "Failed to sign certificate");
292 return false;
293 }
294
295 // Build complete certificate:
296 // SEQUENCE { TBS, SignatureAlgorithm, Signature }
297 size_t cert_content_len = tbs_wrapped_len + sizeof(ecdsa_sha256_oid) + 2 + 1 + sig_len;
298
299 *p++ = DER_SEQUENCE_TAG;
300 if (cert_content_len < 128) {
301 *p++ = cert_content_len;
302 } else {
304 *p++ = (cert_content_len >> 8) & 0xFF;
305 *p++ = cert_content_len & 0xFF;
306 }
307
308 // TBS Certificate (wrapped)
309 memcpy(p, tbs_wrapped, tbs_wrapped_len);
310 p += tbs_wrapped_len;
311
312 // Signature Algorithm
313 memcpy(p, ecdsa_sha256_oid, sizeof(ecdsa_sha256_oid));
314 p += sizeof(ecdsa_sha256_oid);
315
316 // Signature BIT STRING
317 *p++ = DER_BIT_STRING_TAG;
318 *p++ = sig_len + 1; // length (signature + unused bits byte)
319 *p++ = 0x00; // unused bits
320 memcpy(p, sig, sig_len);
321 p += sig_len;
322
323 g_attest_cert_len = p - cert;
325
326 LOG_I(TAG, "FIDO2 attestation certificate generated (%d bytes)", g_attest_cert_len);
327
328 return true;
329}
330
338bool u2f_get_attestation_cert(const uint8_t **cert, uint16_t *cert_len) {
339 if (!cert || !cert_len) {
340 return false;
341 }
342 if (!u2f_init_attestation()) {
343 return false;
344 }
345 *cert = g_attest_cert;
346 *cert_len = g_attest_cert_len;
347 return true;
348}
349
359bool u2f_attestation_sign(const uint8_t *data, size_t data_len,
360 uint8_t *signature, uint8_t *sig_len) {
361 if (!u2f_init_attestation()) {
362 return false;
363 }
364 return u2f_attest_sign(data, data_len, signature, sig_len);
365}
366
373static uint16_t u2f_response_sw(uint8_t *response, uint16_t sw) {
374 response[0] = (sw >> 8) & 0xFF;
375 response[1] = sw & 0xFF;
376 return 2;
377}
378
385static uint16_t u2f_response_error(uint8_t *response, uint16_t sw) {
386 return u2f_response_sw(response, sw);
387}
388
395static uint16_t u2f_version(uint8_t *response, uint16_t response_max) {
396 const char *version = "U2F_V2";
397 size_t len = strlen(version);
398
399 if (response_max < len + 2) {
400 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
401 }
402
403 memcpy(response, version, len);
404 response[len] = 0x90;
405 response[len + 1] = 0x00;
406
407 LOG_I(TAG, "Version request: U2F_V2");
408 return len + 2;
409}
410
417static bool is_dummy_application(const uint8_t *application) {
418 uint8_t first = application[0];
419 // Check if all 32 bytes are the same (dummy pattern)
420 for (int i = 1; i < 32; i++) {
421 if (application[i] != first) {
422 return false;
423 }
424 }
425 LOG_I(TAG, "Detected dummy/blink request (app=0x%02x...)", first);
426 return true;
427}
428
437static uint16_t u2f_register(const uint8_t *challenge, const uint8_t *application,
438 uint8_t *response, uint16_t response_max) {
439 LOG_I(TAG, "Register request");
440
441 bool is_dummy = is_dummy_application(application);
442
443 // For dummy/blink requests (app hash = 0x41414141... or similar),
444 // Chrome is probing for device presence before real registration.
445 // We need to wait for actual user touch, then return a valid-looking response.
446 // Chrome will discard the result but recognize the touch happened.
447 if (is_dummy) {
448 // Show identifier based on the dummy byte pattern
449 char dummy_id[16];
450 snprintf(dummy_id, sizeof(dummy_id), "U2F:%02x%02x%02x%02x",
451 application[0], application[1], application[2], application[3]);
452
453 // Request user presence - SELECT action for device selection
455 dummy_id, FIDO2_ACTION_SELECT, NULL);
456
457 if (up_result != FIDO2_UP_APPROVED) {
458 LOG_D(TAG, "Dummy: no user presence yet");
460 }
461
462 // User touched - generate a dummy response (random data, not stored)
463 LOG_I(TAG, "Dummy: user touched - generating response");
464 uint8_t dummy_cred[U2F_KEY_HANDLE_SIZE];
465 uint8_t dummy_pubkey[64];
467 if (!se || !se->getRandom(dummy_cred, U2F_KEY_HANDLE_SIZE) ||
468 !se->getRandom(dummy_pubkey, 64)) {
469 LOG_E(TAG, "Failed to get random for dummy response");
470 return U2F_SW_WTF;
471 }
472
473 // Build minimal response: 0x05 || pubkey || kh_len || kh || cert || sig
474 uint16_t offset = 0;
475 response[offset++] = U2F_REGISTER_ID;
476 response[offset++] = EC_POINT_UNCOMPRESSED;
477 memcpy(response + offset, dummy_pubkey, 64);
478 offset += 64;
479 response[offset++] = U2F_KEY_HANDLE_SIZE;
480 memcpy(response + offset, dummy_cred, U2F_KEY_HANDLE_SIZE);
481 offset += U2F_KEY_HANDLE_SIZE;
482
483 // Add attestation cert
484 if (offset + g_attest_cert_len + U2F_MAX_EC_SIG_SIZE + 2 > response_max) {
485 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
486 }
487 memcpy(response + offset, g_attest_cert, g_attest_cert_len);
488 offset += g_attest_cert_len;
489
490 // Sign with attestation key
491 uint8_t to_sign[1 + 32 + 32 + U2F_KEY_HANDLE_SIZE + 65];
492 size_t to_sign_len = 0;
493 to_sign[to_sign_len++] = 0x00;
494 memcpy(to_sign + to_sign_len, application, 32);
495 to_sign_len += 32;
496 memcpy(to_sign + to_sign_len, challenge, 32);
497 to_sign_len += 32;
498 memcpy(to_sign + to_sign_len, dummy_cred, U2F_KEY_HANDLE_SIZE);
499 to_sign_len += U2F_KEY_HANDLE_SIZE;
500 to_sign[to_sign_len++] = EC_POINT_UNCOMPRESSED;
501 memcpy(to_sign + to_sign_len, dummy_pubkey, 64);
502 to_sign_len += 64;
503
504 uint8_t signature[U2F_MAX_EC_SIG_SIZE];
505 uint8_t sig_len = 0;
506 if (!u2f_attest_sign(to_sign, to_sign_len, signature, &sig_len)) {
507 return u2f_response_error(response, U2F_SW_WRONG_DATA);
508 }
509 memcpy(response + offset, signature, sig_len);
510 offset += sig_len;
511
512 response[offset++] = 0x90;
513 response[offset++] = 0x00;
514
515 LOG_I(TAG, "Dummy response complete, len=%u", offset);
516 return offset;
517 }
518
519 // Create unique identifier from application hash (first 4 bytes as hex)
520 char rp_id[16];
521 snprintf(rp_id, sizeof(rp_id), "U2F:%02x%02x%02x%02x",
522 application[0], application[1], application[2], application[3]);
523
524 // Request user presence for real registration
527
528 if (up_result != FIDO2_UP_APPROVED) {
529 LOG_I(TAG, "User presence denied");
531 }
532
533 uint8_t cred_id[U2F_KEY_HANDLE_SIZE];
534 uint8_t pubkey[64]; // X || Y (no uncompressed prefix)
535 uint8_t slot = 0;
536
537 {
538 // Real registration - create and store credential
539 uint8_t user_id[1] = {0};
540
542 rp_id, application, user_id, 1, "U2F",
543 false, 0, CDC_CURVE_P256, &slot, cred_id, pubkey)) {
544 LOG_E(TAG, "Failed to create credential");
545 return u2f_response_error(response, U2F_SW_WRONG_DATA);
546 }
547 LOG_I(TAG, "Created credential in slot %d", slot);
548 }
549
550 // Build registration response:
551 // 0x05 || pubkey (65) || keyHandleLen (1) || keyHandle || attestation cert || signature
552 uint16_t offset = 0;
553
554 // Reserved byte
555 response[offset++] = U2F_REGISTER_ID;
556
557 // Public key (uncompressed: 0x04 || X || Y)
558 response[offset++] = EC_POINT_UNCOMPRESSED;
559 memcpy(response + offset, pubkey, 64);
560 offset += 64;
561
562 // Key handle length
563 response[offset++] = U2F_KEY_HANDLE_SIZE;
564
565 // Key handle (credential ID)
566 memcpy(response + offset, cred_id, U2F_KEY_HANDLE_SIZE);
567 offset += U2F_KEY_HANDLE_SIZE;
568
569 // Attestation certificate
570 if (!u2f_init_attestation()) {
571 LOG_E(TAG, "Attestation not initialized");
573 return u2f_response_error(response, U2F_SW_WRONG_DATA);
574 }
575
576 if (offset + g_attest_cert_len + U2F_MAX_EC_SIG_SIZE + 2 > response_max) {
577 LOG_E(TAG, "Response buffer too small");
579 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
580 }
581
582 memcpy(response + offset, g_attest_cert, g_attest_cert_len);
583 offset += g_attest_cert_len;
584
585 // Build data to sign: 0x00 || appParam || challenge || keyHandle || pubkey
586 // This is signed with the ATTESTATION key, not the credential key
587 uint8_t to_sign[1 + 32 + 32 + U2F_KEY_HANDLE_SIZE + 65];
588 size_t to_sign_len = 0;
589
590 to_sign[to_sign_len++] = 0x00; // Reserved
591 memcpy(to_sign + to_sign_len, application, 32);
592 to_sign_len += 32;
593 memcpy(to_sign + to_sign_len, challenge, 32);
594 to_sign_len += 32;
595 memcpy(to_sign + to_sign_len, cred_id, U2F_KEY_HANDLE_SIZE);
596 to_sign_len += U2F_KEY_HANDLE_SIZE;
597 to_sign[to_sign_len++] = EC_POINT_UNCOMPRESSED;
598 memcpy(to_sign + to_sign_len, pubkey, 64);
599 to_sign_len += 64;
600
601 // Sign with attestation key (slot 0)
602 uint8_t signature[U2F_MAX_EC_SIG_SIZE];
603 uint8_t sig_len = 0;
604
605 if (!u2f_attest_sign(to_sign, to_sign_len, signature, &sig_len)) {
606 LOG_E(TAG, "Attestation signing failed");
608 return u2f_response_error(response, U2F_SW_WRONG_DATA);
609 }
610
611 memcpy(response + offset, signature, sig_len);
612 offset += sig_len;
613
614 // Status word
615 response[offset++] = 0x90;
616 response[offset++] = 0x00;
617
618 LOG_I(TAG, "Register complete, response len=%u", offset);
619 return offset;
620}
621
633static uint16_t u2f_authenticate(uint8_t p1, const uint8_t *challenge,
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);
638
639 if (key_handle_len != U2F_KEY_HANDLE_SIZE) {
640 LOG_W(TAG, "Invalid key handle length: %d", key_handle_len);
641 return u2f_response_error(response, U2F_SW_WRONG_DATA);
642 }
643
644 // Find credential by key handle
645 int8_t slot = fido2_storage_find_slot_by_cred_id(key_handle, key_handle_len);
646 if (slot < 0) {
647 LOG_W(TAG, "Key handle not found");
648 return u2f_response_error(response, U2F_SW_WRONG_DATA);
649 }
650
651 // Verify RP ID hash matches
652 fido2_credential_info_t cred;
653 if (!fido2_storage_get_credential(slot, &cred)) {
654 LOG_E(TAG, "Failed to get credential info");
655 return u2f_response_error(response, U2F_SW_WRONG_DATA);
656 }
657
658 if (memcmp(cred.rp_id_hash, application, 32) != 0) {
659 LOG_W(TAG, "Application hash mismatch");
660 return u2f_response_error(response, U2F_SW_WRONG_DATA);
661 }
662
663 // Check-only mode - just verify key handle is valid
664 if (p1 == U2F_AUTH_CHECK_ONLY) {
665 LOG_I(TAG, "Check-only: key handle valid");
667 }
668
669 // Request user presence (unless dont-enforce)
670 if (p1 == U2F_AUTH_ENFORCE) {
672 cred.rp_id, FIDO2_ACTION_AUTHENTICATE, cred.user_name);
673
674 if (up_result != FIDO2_UP_APPROVED) {
675 LOG_I(TAG, "User presence denied");
677 }
678 }
679
680 // Increment counter
681 uint32_t counter = fido2_storage_increment_sign_count(slot);
682 if (counter == 0) {
684 }
686
687 // Build authentication response:
688 // userPresence (1) || counter (4) || signature
689 uint16_t offset = 0;
690
691 // User presence flag
692 response[offset++] = 0x01; // UP=1
693
694 // Counter (big-endian)
695 cdc::core::writeBe32(&response[offset], counter);
696 offset += 4;
697
698 // Build data to sign: appParam || userPresence || counter || challenge
699 uint8_t to_sign[32 + 1 + 4 + 32];
700 size_t to_sign_len = 0;
701
702 memcpy(to_sign + to_sign_len, application, 32);
703 to_sign_len += 32;
704 to_sign[to_sign_len++] = 0x01; // User presence
705 cdc::core::writeBe32(&to_sign[to_sign_len], counter);
706 to_sign_len += 4;
707 memcpy(to_sign + to_sign_len, challenge, 32);
708 to_sign_len += 32;
709
710 // Sign with TROPIC01
711 uint8_t signature[U2F_MAX_EC_SIG_SIZE];
712 uint8_t sig_len = 0;
713
714 if (!fido2_storage_sign_raw(slot, to_sign, to_sign_len, signature, &sig_len)) {
715 LOG_E(TAG, "Signing failed");
716 return u2f_response_error(response, U2F_SW_WRONG_DATA);
717 }
718
719 memcpy(response + offset, signature, sig_len);
720 offset += sig_len;
721
722 // Status word
723 response[offset++] = 0x90;
724 response[offset++] = 0x00;
725
726 LOG_I(TAG, "Authenticate complete, counter=%u, response len=%u", counter, offset);
727 return offset;
728}
729
738uint16_t u2f_process_apdu(const uint8_t *apdu, uint16_t apdu_len,
739 uint8_t *response, uint16_t response_max) {
740 if (apdu_len < 4) {
741 LOG_W(TAG, "APDU too short: %d", apdu_len);
742 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
743 }
744
745 uint8_t cla = apdu[0];
746 uint8_t ins = apdu[1];
747 uint8_t p1 = apdu[2];
748 uint8_t p2 = apdu[3];
749
750 // Only support CLA=0x00
751 if (cla != 0x00) {
752 LOG_W(TAG, "Unsupported CLA: 0x%02X", cla);
754 }
755
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);
758
759 // Parse extended length APDU
760 // Format: CLA INS P1 P2 [Lc(3)] [DATA] [Le(2)]
761 uint32_t data_len = 0;
762 const uint8_t *data = NULL;
763
764 if (apdu_len > 4) {
765 if (apdu[4] == 0x00 && apdu_len > 6) {
766 // Extended length: 00 Lc1 Lc2
767 data_len = (apdu[5] << 8) | apdu[6];
768 data = apdu + 7;
769 if (data_len + 7 > apdu_len) {
770 data_len = apdu_len - 7;
771 }
772 } else {
773 // Short length: Lc
774 data_len = apdu[4];
775 data = apdu + 5;
776 if (data_len + 5 > apdu_len) {
777 data_len = apdu_len - 5;
778 }
779 }
780 }
781
782 switch (ins) {
783 case U2F_INS_VERSION:
784 return u2f_version(response, response_max);
785
786 case U2F_INS_REGISTER:
787 if (data_len < U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE) {
788 LOG_W(TAG, "Register: insufficient data: %u", data_len);
789 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
790 }
791 return u2f_register(data, data + U2F_CHALLENGE_SIZE,
792 response, response_max);
793
795 if (data_len < U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE + 1) {
796 LOG_W(TAG, "Authenticate: insufficient data: %u", data_len);
797 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
798 }
799 {
800 uint8_t kh_len = data[U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE];
801 const uint8_t *kh = data + U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE + 1;
802
803 if (data_len < U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE + 1 + kh_len) {
804 LOG_W(TAG, "Authenticate: key handle truncated");
805 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
806 }
807
808 return u2f_authenticate(p1, data, data + U2F_CHALLENGE_SIZE,
809 kh, kh_len, response, response_max);
810 }
811
812 default:
813 LOG_W(TAG, "Unsupported INS: 0x%02X", ins);
815 }
816}
static const char * TAG
Big-endian byte-packing helpers.
uint8_t version
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_D(tag, fmt,...)
Definition cdc_log.h:148
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
#define CDC_CURVE_P256
Definition fido2.h:24
void fido2_increment_auth_counter(void)
Increments global authentication counter.
Definition fido2.cpp:290
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.
Definition fido2.cpp:177
fido2_user_presence_result_t
Definition fido2.h:30
@ FIDO2_UP_APPROVED
Definition fido2.h:32
@ FIDO2_ACTION_SELECT
Definition fido2.h:40
@ FIDO2_ACTION_REGISTER
Definition fido2.h:38
@ FIDO2_ACTION_AUTHENTICATE
Definition fido2.h:39
uint8_t curve
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.
Definition Bytes.h:17
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.
Definition u2f.cpp:31
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).
Definition u2f.cpp:633
bool u2f_init_attestation(void)
Initializes attestation key material and builds self-signed attestation certificate.
Definition u2f.cpp:122
static constexpr uint8_t DER_BIT_STRING_TAG
Definition u2f.cpp:25
static constexpr uint8_t DER_EXPLICIT_TAG_3
Definition u2f.cpp:27
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...
Definition u2f.cpp:338
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.
Definition u2f.cpp:738
static constexpr int RAW_SIGNATURE_COMPONENT_SIZE
Definition u2f.cpp:34
static uint8_t g_attest_pubkey[65]
Definition u2f.cpp:43
static constexpr uint8_t DER_ENSURE_POSITIVE_MASK
Definition u2f.cpp:33
static constexpr uint8_t DER_LENGTH_TWO_BYTES
Definition u2f.cpp:28
static bool g_attest_initialized
Definition u2f.cpp:44
static uint16_t u2f_response_error(uint8_t *response, uint16_t sw)
Writes a U2F error status word to response buffer.
Definition u2f.cpp:385
static bool is_dummy_application(const uint8_t *application)
U2F register/authenticate command helpers.
Definition u2f.cpp:417
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.
Definition u2f.cpp:84
static constexpr uint8_t DER_SEQUENCE_TAG
DER encoding helper constants for X.509/signature generation.
Definition u2f.cpp:23
static uint16_t u2f_response_sw(uint8_t *response, uint16_t sw)
Writes a U2F status word to response buffer.
Definition u2f.cpp:373
#define U2F_ATTEST_SLOT
Attestation certificate constants and cached buffers.
Definition u2f.cpp:38
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...
Definition u2f.cpp:359
static uint16_t g_attest_cert_len
Definition u2f.cpp:42
static uint16_t u2f_version(uint8_t *response, uint16_t response_max)
Handles U2F VERSION instruction (INS=0x03).
Definition u2f.cpp:395
static constexpr uint8_t DER_INTEGER_TAG
Definition u2f.cpp:24
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.
Definition u2f.cpp:56
static constexpr uint8_t DER_EXPLICIT_TAG_0
Definition u2f.cpp:26
static constexpr uint8_t DER_INTEGER_NEGATIVE_MASK
Definition u2f.cpp:32
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).
Definition u2f.cpp:437
static uint8_t g_attest_cert[U2F_MAX_ATT_CERT_SIZE]
Cached DER attestation certificate and associated state.
Definition u2f.cpp:41
#define U2F_REGISTER_ID
Definition u2f.h:41
#define U2F_SW_CLA_NOT_SUPPORTED
Definition u2f.h:32
#define U2F_CHALLENGE_SIZE
Definition u2f.h:38
#define U2F_INS_AUTHENTICATE
Definition u2f.h:19
#define U2F_INS_REGISTER
Definition u2f.h:18
#define U2F_SW_WTF
Definition u2f.h:35
#define U2F_APPLICATION_SIZE
Definition u2f.h:39
#define U2F_MAX_ATT_CERT_SIZE
Definition u2f.h:44
#define U2F_SW_WRONG_LENGTH
Definition u2f.h:31
#define U2F_SW_WRONG_DATA
Definition u2f.h:30
#define U2F_INS_VERSION
Definition u2f.h:20
#define U2F_SW_INS_NOT_SUPPORTED
Definition u2f.h:33
#define U2F_KEY_HANDLE_SIZE
Definition u2f.h:40
#define U2F_MAX_EC_SIG_SIZE
Definition u2f.h:45
#define U2F_AUTH_ENFORCE
Definition u2f.h:24
#define U2F_SW_CONDITIONS_NOT_SATISFIED
Definition u2f.h:29
#define U2F_AUTH_CHECK_ONLY
Definition u2f.h:23