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 <mbedtls/x509_crt.h>
15#include <mbedtls/pk.h>
16#include <nvs.h>
17#include <esp_attr.h>
18#include <string.h>
19#include <stdio.h>
20
22
23static const char* TAG = "U2F";
24
26static constexpr uint8_t DER_SEQUENCE_TAG = 0x30;
27static constexpr uint8_t DER_INTEGER_TAG = 0x02;
28static constexpr uint8_t DER_BIT_STRING_TAG = 0x03;
29static constexpr uint8_t DER_EXPLICIT_TAG_0 = 0xA0; // [0] EXPLICIT
30static constexpr uint8_t DER_EXPLICIT_TAG_3 = 0xA3; // [3] EXPLICIT
31static constexpr uint8_t DER_LENGTH_TWO_BYTES = 0x82; // Length uses 2 following bytes
32
34static constexpr uint8_t EC_POINT_UNCOMPRESSED = 0x04; // Uncompressed EC point prefix
35static constexpr uint8_t DER_INTEGER_NEGATIVE_MASK = 0x80; // MSB set = negative in DER
36static constexpr uint8_t DER_ENSURE_POSITIVE_MASK = 0x7F; // Mask to ensure positive
37static constexpr int RAW_SIGNATURE_COMPONENT_SIZE = 32; // Size of R or S in raw signature
38
40
41#define U2F_ATTEST_SLOT 0 // ECC slot 0 reserved for attestation
42
44EXT_RAM_BSS_ATTR static uint8_t g_attest_cert[U2F_MAX_ATT_CERT_SIZE];
45static uint16_t g_attest_cert_len = 0;
46static uint8_t g_attest_pubkey[65]; // 0x04 || X || Y
47static bool g_attest_initialized = false;
48
50static constexpr const char* ATTEST_NVS_NS = "attest";
51static constexpr const char* ATTEST_NVS_CERT = "cert";
52
60static bool cert_matches_attest_key(const uint8_t* der, size_t der_len) {
61 mbedtls_x509_crt crt;
62 mbedtls_x509_crt_init(&crt);
63 bool ok = false;
64 if (mbedtls_x509_crt_parse_der(&crt, der, der_len) == 0) {
65 uint8_t spki[256];
66 int n = mbedtls_pk_write_pubkey_der(&crt.pk, spki, sizeof(spki));
67 if (n >= 65) {
68 // mbedtls writes the DER at the end of the buffer; the uncompressed
69 // EC point (0x04 || X || Y) is its trailing 65 bytes.
70 ok = memcmp(spki + sizeof(spki) - 65, g_attest_pubkey, 65) == 0;
71 }
72 }
73 mbedtls_x509_crt_free(&crt);
74 return ok;
75}
76
84static uint16_t u2f_load_imported_cert(uint8_t* out, size_t out_size) {
85 nvs_handle_t nvs;
86 if (nvs_open(ATTEST_NVS_NS, NVS_READONLY, &nvs) != ESP_OK) return 0;
87 size_t len = out_size;
88 esp_err_t err = nvs_get_blob(nvs, ATTEST_NVS_CERT, out, &len);
89 nvs_close(nvs);
90 if (err != ESP_OK || len == 0 || len > out_size) return 0;
91 if (!cert_matches_attest_key(out, len)) {
92 LOG_W(TAG, "Imported attestation cert does not match slot 0, ignoring");
93 return 0;
94 }
95 return static_cast<uint16_t>(len);
96}
97
108static uint8_t* encode_der_integer(uint8_t *p, const uint8_t *mpi, size_t len) {
109 // Skip leading zero bytes but keep at least one byte.
110 size_t start = 0;
111 while (start + 1 < len && mpi[start] == 0) {
112 start++;
113 }
114 size_t actual_len = len - start;
115
116 // Prepend padding byte if MSB is set (DER INTEGERs are signed, two's complement).
117 int pad = (mpi[start] & DER_INTEGER_NEGATIVE_MASK) ? 1 : 0;
118
119 *p++ = DER_INTEGER_TAG;
120 *p++ = static_cast<uint8_t>(pad + actual_len);
121 if (pad) {
122 *p++ = 0x00;
123 }
124 memcpy(p, mpi + start, actual_len);
125 return p + actual_len;
126}
127
136static bool u2f_attest_sign(const uint8_t *data, size_t data_len,
137 uint8_t *signature, uint8_t *sig_len) {
139 if (!se) {
140 return false;
141 }
142
143 uint8_t raw_sig[64]; // R || S (each 32 bytes)
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");
149 return false;
150 }
151
152 // DER: SEQUENCE { INTEGER R, INTEGER S }
153 // Encode R and S into a scratch buffer first so we can compute the SEQUENCE length.
154 uint8_t body[2 * (2 + 1 + RAW_SIGNATURE_COMPONENT_SIZE)]; // tag + len + pad + magnitude, twice
155 uint8_t *body_end = encode_der_integer(body, raw_sig, RAW_SIGNATURE_COMPONENT_SIZE);
156 body_end = encode_der_integer(body_end, raw_sig + RAW_SIGNATURE_COMPONENT_SIZE,
158 size_t body_len = static_cast<size_t>(body_end - body);
159
160 uint8_t *p = signature;
161 *p++ = DER_SEQUENCE_TAG;
162 *p++ = static_cast<uint8_t>(body_len);
163 memcpy(p, body, body_len);
164 p += body_len;
165
166 *sig_len = static_cast<uint8_t>(p - signature);
167 return true;
168}
169
176 return true;
177 }
178
179 LOG_I(TAG, "Initializing attestation...");
180
182 if (!se) {
183 return false;
184 }
185
186 // Check if attestation key exists in slot 0
187 uint8_t pubkey[65];
189 if (!se->eccSlotUsed(U2F_ATTEST_SLOT)) {
190 LOG_E(TAG, "Attestation key missing in slot %d", U2F_ATTEST_SLOT);
191 return false;
192 }
193
194 if (se->eccGetPublicKey(U2F_ATTEST_SLOT, pubkey, &curve) != cdc::hal::SeResult::OK) {
195 LOG_E(TAG, "Failed to read attestation public key");
196 return false;
197 }
198
200 LOG_E(TAG, "Attestation key has invalid curve");
201 return false;
202 }
203
204 LOG_I(TAG, "Attestation key ready (curve=P256)");
205
206 // Store public key (with uncompressed point prefix)
208 memcpy(g_attest_pubkey + 1, pubkey, 64);
209
210 // Prefer a CA-signed certificate imported for this device; fall back to the
211 // self-signed certificate built below.
212 uint16_t imported = u2f_load_imported_cert(g_attest_cert, sizeof(g_attest_cert));
213 if (imported > 0) {
214 g_attest_cert_len = imported;
216 LOG_I(TAG, "Using imported attestation certificate (%u bytes)",
217 static_cast<unsigned>(imported));
218 return true;
219 }
220
221 // Build self-signed X.509 certificate manually (DER encoded)
222 // This is a minimal certificate structure for U2F
223 uint8_t *cert = g_attest_cert;
224 uint8_t *p = cert;
225
226 // We'll build the TBS (To Be Signed) certificate, sign it, then wrap
227
228 // TBS Certificate structure - use static PSRAM buffer (only called once at init)
229 EXT_RAM_BSS_ATTR static uint8_t tbs[512];
230 uint8_t *t = tbs;
231
232 // Version [0] EXPLICIT INTEGER = 2 (v3)
233 *t++ = DER_EXPLICIT_TAG_0; *t++ = 0x03; // [0] EXPLICIT
234 *t++ = DER_INTEGER_TAG; *t++ = 0x01; *t++ = 0x02; // INTEGER 2
235
236 // Serial number - random
237 uint8_t serial[8];
238 if (!se->getRandom(serial, sizeof(serial))) {
239 LOG_E(TAG, "Failed to get random serial");
240 return false;
241 }
242 serial[0] &= DER_ENSURE_POSITIVE_MASK; // Ensure positive
243 *t++ = DER_INTEGER_TAG; *t++ = 0x08; // INTEGER
244 memcpy(t, serial, 8);
245 t += 8;
246
247 // Signature algorithm: ecdsa-with-SHA256 (1.2.840.10045.4.3.2)
248 static const uint8_t ecdsa_sha256_oid[] = {
249 0x30, 0x0A, // SEQUENCE
250 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 // OID
251 };
252 memcpy(t, ecdsa_sha256_oid, sizeof(ecdsa_sha256_oid));
253 t += sizeof(ecdsa_sha256_oid);
254
255 // FIDO2-konformer Subject: C=DE, O=CDC, OU=Authenticator Attestation, CN=CDC Badge FIDO2
256 // Total: 13 + 14 + 36 + 26 = 89 bytes content
257 static const uint8_t fido2_subject[] = {
258 0x30, 0x59, // SEQUENCE (89 bytes)
259 // C=DE (13 bytes: SET(11) = SEQ(9) = OID(5) + PrintableString(2+2))
260 0x31, 0x0B, 0x30, 0x09,
261 0x06, 0x03, 0x55, 0x04, 0x06, // OID: C (2.5.4.6)
262 0x13, 0x02, 'D', 'E',
263 // O=CDC (14 bytes: SET(12) = SEQ(10) = OID(5) + UTF8String(2+3))
264 0x31, 0x0C, 0x30, 0x0A,
265 0x06, 0x03, 0x55, 0x04, 0x0A, // OID: O (2.5.4.10)
266 0x0C, 0x03, 'C', 'D', 'C',
267 // OU=Authenticator Attestation (36 bytes: SET(34) = SEQ(32) = OID(5) + UTF8String(2+25))
268 0x31, 0x22, 0x30, 0x20,
269 0x06, 0x03, 0x55, 0x04, 0x0B, // OID: OU (2.5.4.11)
270 0x0C, 0x19,
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',
273 // CN=CDC Badge FIDO2 (26 bytes: SET(24) = SEQ(22) = OID(5) + UTF8String(2+15))
274 0x31, 0x18, 0x30, 0x16,
275 0x06, 0x03, 0x55, 0x04, 0x03, // OID: CN (2.5.4.3)
276 0x0C, 0x0F,
277 'C', 'D', 'C', ' ', 'B', 'a', 'd', 'g', 'e', ' ', 'F', 'I', 'D', 'O', '2'
278 };
279 memcpy(t, fido2_subject, sizeof(fido2_subject));
280 t += sizeof(fido2_subject);
281
282 // Validity (2024-01-01 to 2049-12-31)
283 // UTCTime: 00-49 = 2000-2049, 50-99 = 1950-1999
284 static const uint8_t validity[] = {
285 0x30, 0x1E, // SEQUENCE
286 0x17, 0x0D, '2', '4', '0', '1', '0', '1', '0', '0', '0', '0', '0', '0', 'Z', // notBefore: 2024-01-01
287 0x17, 0x0D, '4', '9', '1', '2', '3', '1', '2', '3', '5', '9', '5', '9', 'Z' // notAfter: 2049-12-31
288 };
289 memcpy(t, validity, sizeof(validity));
290 t += sizeof(validity);
291
292 // Subject: same as issuer
293 memcpy(t, fido2_subject, sizeof(fido2_subject));
294 t += sizeof(fido2_subject);
295
296 // Subject Public Key Info
297 // AlgorithmIdentifier: ecPublicKey + prime256v1
298 static const uint8_t spki_prefix[] = {
299 0x30, 0x59, // SEQUENCE (89 bytes total)
300 0x30, 0x13, // SEQUENCE (algorithm)
301 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, // OID: ecPublicKey
302 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, // OID: prime256v1
303 0x03, 0x42, 0x00 // BIT STRING (66 bytes, 0 unused bits)
304 };
305 memcpy(t, spki_prefix, sizeof(spki_prefix));
306 t += sizeof(spki_prefix);
307
308 // Public key (0x04 || X || Y)
309 memcpy(t, g_attest_pubkey, 65);
310 t += 65;
311
312 // FIDO2 Extensions: basicConstraints (critical, CA:FALSE) + keyUsage (digitalSignature)
313 static const uint8_t fido2_extensions[] = {
314 0xA3, 0x1D, // [3] EXPLICIT (29 bytes)
315 0x30, 0x1B, // SEQUENCE (27 bytes)
316 // basicConstraints: critical, CA:FALSE
317 0x30, 0x0C,
318 0x06, 0x03, 0x55, 0x1D, 0x13, // OID 2.5.29.19
319 0x01, 0x01, 0xFF, // critical=TRUE
320 0x04, 0x02, 0x30, 0x00, // CA:FALSE
321 // keyUsage: digitalSignature
322 0x30, 0x0B,
323 0x06, 0x03, 0x55, 0x1D, 0x0F, // OID 2.5.29.15
324 0x04, 0x04,
325 0x03, 0x02, 0x07, 0x80 // digitalSignature bit
326 };
327 memcpy(t, fido2_extensions, sizeof(fido2_extensions));
328 t += sizeof(fido2_extensions);
329
330 size_t tbs_len = t - tbs;
331
332 // Now wrap TBS in SEQUENCE - use static PSRAM buffer (only called once at init)
333 EXT_RAM_BSS_ATTR static uint8_t tbs_wrapped[600];
334 uint8_t *tw = tbs_wrapped;
335
336 *tw++ = DER_SEQUENCE_TAG;
337 if (tbs_len < 128) {
338 *tw++ = tbs_len;
339 } else {
340 *tw++ = DER_LENGTH_TWO_BYTES;
341 *tw++ = (tbs_len >> 8) & 0xFF;
342 *tw++ = tbs_len & 0xFF;
343 }
344 memcpy(tw, tbs, tbs_len);
345 tw += tbs_len;
346
347 size_t tbs_wrapped_len = tw - tbs_wrapped;
348
349 // Sign the TBS
350 uint8_t sig[U2F_MAX_EC_SIG_SIZE];
351 uint8_t sig_len = 0;
352
353 if (!u2f_attest_sign(tbs_wrapped, tbs_wrapped_len, sig, &sig_len)) {
354 LOG_E(TAG, "Failed to sign certificate");
355 return false;
356 }
357
358 // Build complete certificate:
359 // SEQUENCE { TBS, SignatureAlgorithm, Signature }
360 size_t cert_content_len = tbs_wrapped_len + sizeof(ecdsa_sha256_oid) + 2 + 1 + sig_len;
361
362 *p++ = DER_SEQUENCE_TAG;
363 if (cert_content_len < 128) {
364 *p++ = cert_content_len;
365 } else {
367 *p++ = (cert_content_len >> 8) & 0xFF;
368 *p++ = cert_content_len & 0xFF;
369 }
370
371 // TBS Certificate (wrapped)
372 memcpy(p, tbs_wrapped, tbs_wrapped_len);
373 p += tbs_wrapped_len;
374
375 // Signature Algorithm
376 memcpy(p, ecdsa_sha256_oid, sizeof(ecdsa_sha256_oid));
377 p += sizeof(ecdsa_sha256_oid);
378
379 // Signature BIT STRING
380 *p++ = DER_BIT_STRING_TAG;
381 *p++ = sig_len + 1; // length (signature + unused bits byte)
382 *p++ = 0x00; // unused bits
383 memcpy(p, sig, sig_len);
384 p += sig_len;
385
386 g_attest_cert_len = p - cert;
388
389 LOG_I(TAG, "FIDO2 attestation certificate generated (%d bytes)", g_attest_cert_len);
390
391 return true;
392}
393
401bool u2f_get_attestation_cert(const uint8_t **cert, uint16_t *cert_len) {
402 if (!cert || !cert_len) {
403 return false;
404 }
405 if (!u2f_init_attestation()) {
406 return false;
407 }
408 *cert = g_attest_cert;
409 *cert_len = g_attest_cert_len;
410 return true;
411}
412
413bool u2f_get_attestation_pubkey(uint8_t out[65]) {
414 if (!out) return false;
415 if (!u2f_init_attestation()) return false;
416 memcpy(out, g_attest_pubkey, 65);
417 return true;
418}
419
420bool u2f_import_attestation_cert(const uint8_t *der, size_t len) {
421 if (!der || len == 0 || len > U2F_MAX_ATT_CERT_SIZE) return false;
422 // Ensure g_attest_pubkey reflects the live slot-0 key before validating.
423 if (!u2f_init_attestation()) return false;
424 if (!cert_matches_attest_key(der, len)) {
425 LOG_W(TAG, "Rejecting attestation cert: public key mismatch");
426 return false;
427 }
428
429 nvs_handle_t nvs;
430 if (nvs_open(ATTEST_NVS_NS, NVS_READWRITE, &nvs) != ESP_OK) return false;
431 esp_err_t err = nvs_set_blob(nvs, ATTEST_NVS_CERT, der, len);
432 if (err == ESP_OK) err = nvs_commit(nvs);
433 nvs_close(nvs);
434 if (err != ESP_OK) return false;
435
436 // Reload so subsequent makeCredential responses use the imported cert.
437 g_attest_initialized = false;
438 return u2f_init_attestation();
439}
440
442 nvs_handle_t nvs;
443 if (nvs_open(ATTEST_NVS_NS, NVS_READWRITE, &nvs) == ESP_OK) {
444 nvs_erase_key(nvs, ATTEST_NVS_CERT);
445 nvs_commit(nvs);
446 nvs_close(nvs);
447 }
448 g_attest_initialized = false;
449 return u2f_init_attestation();
450}
451
461bool u2f_attestation_sign(const uint8_t *data, size_t data_len,
462 uint8_t *signature, uint8_t *sig_len) {
463 if (!u2f_init_attestation()) {
464 return false;
465 }
466 return u2f_attest_sign(data, data_len, signature, sig_len);
467}
468
475static uint16_t u2f_response_sw(uint8_t *response, uint16_t sw) {
476 response[0] = (sw >> 8) & 0xFF;
477 response[1] = sw & 0xFF;
478 return 2;
479}
480
487static uint16_t u2f_response_error(uint8_t *response, uint16_t sw) {
488 return u2f_response_sw(response, sw);
489}
490
497static uint16_t u2f_version(uint8_t *response, uint16_t response_max) {
498 const char *version = "U2F_V2";
499 size_t len = strlen(version);
500
501 if (response_max < len + 2) {
502 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
503 }
504
505 memcpy(response, version, len);
506 response[len] = 0x90;
507 response[len + 1] = 0x00;
508
509 LOG_I(TAG, "Version request: U2F_V2");
510 return len + 2;
511}
512
519static bool is_dummy_application(const uint8_t *application) {
520 uint8_t first = application[0];
521 // Check if all 32 bytes are the same (dummy pattern)
522 for (int i = 1; i < 32; i++) {
523 if (application[i] != first) {
524 return false;
525 }
526 }
527 LOG_I(TAG, "Detected dummy/blink request (app=0x%02x...)", first);
528 return true;
529}
530
539static uint16_t u2f_register(const uint8_t *challenge, const uint8_t *application,
540 uint8_t *response, uint16_t response_max) {
541 LOG_I(TAG, "Register request");
542
543 bool is_dummy = is_dummy_application(application);
544
545 // For dummy/blink requests (app hash = 0x41414141... or similar),
546 // Chrome is probing for device presence before real registration.
547 // We need to wait for actual user touch, then return a valid-looking response.
548 // Chrome will discard the result but recognize the touch happened.
549 if (is_dummy) {
550 // Show identifier based on the dummy byte pattern
551 char dummy_id[16];
552 snprintf(dummy_id, sizeof(dummy_id), "U2F:%02x%02x%02x%02x",
553 application[0], application[1], application[2], application[3]);
554
555 // Request user presence - SELECT action for device selection
557 dummy_id, FIDO2_ACTION_SELECT, NULL);
558
559 if (up_result != FIDO2_UP_APPROVED) {
560 LOG_D(TAG, "Dummy: no user presence yet");
562 }
563
564 // User touched - generate a dummy response (random data, not stored)
565 LOG_I(TAG, "Dummy: user touched - generating response");
566 uint8_t dummy_cred[U2F_KEY_HANDLE_SIZE];
567 uint8_t dummy_pubkey[64];
569 if (!se || !se->getRandom(dummy_cred, U2F_KEY_HANDLE_SIZE) ||
570 !se->getRandom(dummy_pubkey, 64)) {
571 LOG_E(TAG, "Failed to get random for dummy response");
572 return U2F_SW_WTF;
573 }
574
575 // Build minimal response: 0x05 || pubkey || kh_len || kh || cert || sig
576 uint16_t offset = 0;
577 response[offset++] = U2F_REGISTER_ID;
578 response[offset++] = EC_POINT_UNCOMPRESSED;
579 memcpy(response + offset, dummy_pubkey, 64);
580 offset += 64;
581 response[offset++] = U2F_KEY_HANDLE_SIZE;
582 memcpy(response + offset, dummy_cred, U2F_KEY_HANDLE_SIZE);
583 offset += U2F_KEY_HANDLE_SIZE;
584
585 // Add attestation cert
586 if (offset + g_attest_cert_len + U2F_MAX_EC_SIG_SIZE + 2 > response_max) {
587 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
588 }
589 memcpy(response + offset, g_attest_cert, g_attest_cert_len);
590 offset += g_attest_cert_len;
591
592 // Sign with attestation key
593 uint8_t to_sign[1 + 32 + 32 + U2F_KEY_HANDLE_SIZE + 65];
594 size_t to_sign_len = 0;
595 to_sign[to_sign_len++] = 0x00;
596 memcpy(to_sign + to_sign_len, application, 32);
597 to_sign_len += 32;
598 memcpy(to_sign + to_sign_len, challenge, 32);
599 to_sign_len += 32;
600 memcpy(to_sign + to_sign_len, dummy_cred, U2F_KEY_HANDLE_SIZE);
601 to_sign_len += U2F_KEY_HANDLE_SIZE;
602 to_sign[to_sign_len++] = EC_POINT_UNCOMPRESSED;
603 memcpy(to_sign + to_sign_len, dummy_pubkey, 64);
604 to_sign_len += 64;
605
606 uint8_t signature[U2F_MAX_EC_SIG_SIZE];
607 uint8_t sig_len = 0;
608 if (!u2f_attest_sign(to_sign, to_sign_len, signature, &sig_len)) {
609 return u2f_response_error(response, U2F_SW_WRONG_DATA);
610 }
611 memcpy(response + offset, signature, sig_len);
612 offset += sig_len;
613
614 response[offset++] = 0x90;
615 response[offset++] = 0x00;
616
617 LOG_I(TAG, "Dummy response complete, len=%u", offset);
618 return offset;
619 }
620
621 // Create unique identifier from application hash (first 4 bytes as hex)
622 char rp_id[16];
623 snprintf(rp_id, sizeof(rp_id), "U2F:%02x%02x%02x%02x",
624 application[0], application[1], application[2], application[3]);
625
626 // Request user presence for real registration
629
630 if (up_result != FIDO2_UP_APPROVED) {
631 LOG_I(TAG, "User presence denied");
633 }
634
635 uint8_t cred_id[U2F_KEY_HANDLE_SIZE];
636 uint8_t pubkey[64]; // X || Y (no uncompressed prefix)
637 uint8_t slot = 0;
638
639 {
640 // Real registration - create and store credential
641 uint8_t user_id[1] = {0};
642
644 rp_id, application, user_id, 1, "U2F",
645 false, 0, CDC_CURVE_P256, &slot, cred_id, pubkey)) {
646 LOG_E(TAG, "Failed to create credential");
647 return u2f_response_error(response, U2F_SW_WRONG_DATA);
648 }
649 LOG_I(TAG, "Created credential in slot %d", slot);
650 }
651
652 // Build registration response:
653 // 0x05 || pubkey (65) || keyHandleLen (1) || keyHandle || attestation cert || signature
654 uint16_t offset = 0;
655
656 // Reserved byte
657 response[offset++] = U2F_REGISTER_ID;
658
659 // Public key (uncompressed: 0x04 || X || Y)
660 response[offset++] = EC_POINT_UNCOMPRESSED;
661 memcpy(response + offset, pubkey, 64);
662 offset += 64;
663
664 // Key handle length
665 response[offset++] = U2F_KEY_HANDLE_SIZE;
666
667 // Key handle (credential ID)
668 memcpy(response + offset, cred_id, U2F_KEY_HANDLE_SIZE);
669 offset += U2F_KEY_HANDLE_SIZE;
670
671 // Attestation certificate
672 if (!u2f_init_attestation()) {
673 LOG_E(TAG, "Attestation not initialized");
675 return u2f_response_error(response, U2F_SW_WRONG_DATA);
676 }
677
678 if (offset + g_attest_cert_len + U2F_MAX_EC_SIG_SIZE + 2 > response_max) {
679 LOG_E(TAG, "Response buffer too small");
681 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
682 }
683
684 memcpy(response + offset, g_attest_cert, g_attest_cert_len);
685 offset += g_attest_cert_len;
686
687 // Build data to sign: 0x00 || appParam || challenge || keyHandle || pubkey
688 // This is signed with the ATTESTATION key, not the credential key
689 uint8_t to_sign[1 + 32 + 32 + U2F_KEY_HANDLE_SIZE + 65];
690 size_t to_sign_len = 0;
691
692 to_sign[to_sign_len++] = 0x00; // Reserved
693 memcpy(to_sign + to_sign_len, application, 32);
694 to_sign_len += 32;
695 memcpy(to_sign + to_sign_len, challenge, 32);
696 to_sign_len += 32;
697 memcpy(to_sign + to_sign_len, cred_id, U2F_KEY_HANDLE_SIZE);
698 to_sign_len += U2F_KEY_HANDLE_SIZE;
699 to_sign[to_sign_len++] = EC_POINT_UNCOMPRESSED;
700 memcpy(to_sign + to_sign_len, pubkey, 64);
701 to_sign_len += 64;
702
703 // Sign with attestation key (slot 0)
704 uint8_t signature[U2F_MAX_EC_SIG_SIZE];
705 uint8_t sig_len = 0;
706
707 if (!u2f_attest_sign(to_sign, to_sign_len, signature, &sig_len)) {
708 LOG_E(TAG, "Attestation signing failed");
710 return u2f_response_error(response, U2F_SW_WRONG_DATA);
711 }
712
713 memcpy(response + offset, signature, sig_len);
714 offset += sig_len;
715
716 // Status word
717 response[offset++] = 0x90;
718 response[offset++] = 0x00;
719
720 LOG_I(TAG, "Register complete, response len=%u", offset);
721 return offset;
722}
723
735static uint16_t u2f_authenticate(uint8_t p1, const uint8_t *challenge,
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);
740
741 if (key_handle_len != U2F_KEY_HANDLE_SIZE) {
742 LOG_W(TAG, "Invalid key handle length: %d", key_handle_len);
743 return u2f_response_error(response, U2F_SW_WRONG_DATA);
744 }
745
746 // Find credential by key handle
747 int8_t slot = fido2_storage_find_slot_by_cred_id(key_handle, key_handle_len);
748 if (slot < 0) {
749 LOG_W(TAG, "Key handle not found");
750 return u2f_response_error(response, U2F_SW_WRONG_DATA);
751 }
752
753 // Verify RP ID hash matches
754 fido2_credential_info_t cred;
755 if (!fido2_storage_get_credential(slot, &cred)) {
756 LOG_E(TAG, "Failed to get credential info");
757 return u2f_response_error(response, U2F_SW_WRONG_DATA);
758 }
759
760 if (memcmp(cred.rp_id_hash, application, 32) != 0) {
761 LOG_W(TAG, "Application hash mismatch");
762 return u2f_response_error(response, U2F_SW_WRONG_DATA);
763 }
764
765 // Check-only mode - just verify key handle is valid
766 if (p1 == U2F_AUTH_CHECK_ONLY) {
767 LOG_I(TAG, "Check-only: key handle valid");
769 }
770
771 // Request user presence (unless dont-enforce)
772 if (p1 == U2F_AUTH_ENFORCE) {
774 cred.rp_id, FIDO2_ACTION_AUTHENTICATE, cred.user_name);
775
776 if (up_result != FIDO2_UP_APPROVED) {
777 LOG_I(TAG, "User presence denied");
779 }
780 }
781
782 // Increment counter
783 uint32_t counter = fido2_storage_increment_sign_count(slot);
784 if (counter == 0) {
786 }
788
789 // Build authentication response:
790 // userPresence (1) || counter (4) || signature
791 uint16_t offset = 0;
792
793 // User presence flag
794 response[offset++] = 0x01; // UP=1
795
796 // Counter (big-endian)
797 cdc::core::writeBe32(&response[offset], counter);
798 offset += 4;
799
800 // Build data to sign: appParam || userPresence || counter || challenge
801 uint8_t to_sign[32 + 1 + 4 + 32];
802 size_t to_sign_len = 0;
803
804 memcpy(to_sign + to_sign_len, application, 32);
805 to_sign_len += 32;
806 to_sign[to_sign_len++] = 0x01; // User presence
807 cdc::core::writeBe32(&to_sign[to_sign_len], counter);
808 to_sign_len += 4;
809 memcpy(to_sign + to_sign_len, challenge, 32);
810 to_sign_len += 32;
811
812 // Sign with TROPIC01
813 uint8_t signature[U2F_MAX_EC_SIG_SIZE];
814 uint8_t sig_len = 0;
815
816 if (!fido2_storage_sign_raw(slot, to_sign, to_sign_len, signature, &sig_len)) {
817 LOG_E(TAG, "Signing failed");
818 return u2f_response_error(response, U2F_SW_WRONG_DATA);
819 }
820
821 memcpy(response + offset, signature, sig_len);
822 offset += sig_len;
823
824 // Status word
825 response[offset++] = 0x90;
826 response[offset++] = 0x00;
827
828 LOG_I(TAG, "Authenticate complete, counter=%u, response len=%u", counter, offset);
829 return offset;
830}
831
840uint16_t u2f_process_apdu(const uint8_t *apdu, uint16_t apdu_len,
841 uint8_t *response, uint16_t response_max) {
842 if (apdu_len < 4) {
843 LOG_W(TAG, "APDU too short: %d", apdu_len);
844 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
845 }
846
847 uint8_t cla = apdu[0];
848 uint8_t ins = apdu[1];
849 uint8_t p1 = apdu[2];
850 uint8_t p2 = apdu[3];
851
852 // Only support CLA=0x00
853 if (cla != 0x00) {
854 LOG_W(TAG, "Unsupported CLA: 0x%02X", cla);
856 }
857
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);
860
861 // Parse extended length APDU
862 // Format: CLA INS P1 P2 [Lc(3)] [DATA] [Le(2)]
863 uint32_t data_len = 0;
864 const uint8_t *data = NULL;
865
866 if (apdu_len > 4) {
867 if (apdu[4] == 0x00 && apdu_len > 6) {
868 // Extended length: 00 Lc1 Lc2
869 data_len = (apdu[5] << 8) | apdu[6];
870 data = apdu + 7;
871 if (data_len + 7 > apdu_len) {
872 data_len = apdu_len - 7;
873 }
874 } else {
875 // Short length: Lc
876 data_len = apdu[4];
877 data = apdu + 5;
878 if (data_len + 5 > apdu_len) {
879 data_len = apdu_len - 5;
880 }
881 }
882 }
883
884 switch (ins) {
885 case U2F_INS_VERSION:
886 return u2f_version(response, response_max);
887
888 case U2F_INS_REGISTER:
889 if (data_len < U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE) {
890 LOG_W(TAG, "Register: insufficient data: %u", data_len);
891 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
892 }
893 return u2f_register(data, data + U2F_CHALLENGE_SIZE,
894 response, response_max);
895
897 if (data_len < U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE + 1) {
898 LOG_W(TAG, "Authenticate: insufficient data: %u", data_len);
899 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
900 }
901 {
902 uint8_t kh_len = data[U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE];
903 const uint8_t *kh = data + U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE + 1;
904
905 if (data_len < U2F_CHALLENGE_SIZE + U2F_APPLICATION_SIZE + 1 + kh_len) {
906 LOG_W(TAG, "Authenticate: key handle truncated");
907 return u2f_response_error(response, U2F_SW_WRONG_LENGTH);
908 }
909
910 return u2f_authenticate(p1, data, data + U2F_CHALLENGE_SIZE,
911 kh, kh_len, response, response_max);
912 }
913
914 default:
915 LOG_W(TAG, "Unsupported INS: 0x%02X", ins);
917 }
918}
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:295
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:178
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 const char * ATTEST_NVS_NS
NVS location of an optionally imported (CA-signed) attestation cert.
Definition u2f.cpp:50
static constexpr uint8_t EC_POINT_UNCOMPRESSED
ECDSA and EC-point encoding constants.
Definition u2f.cpp:34
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:735
bool u2f_init_attestation(void)
Initializes attestation key material and builds self-signed attestation certificate.
Definition u2f.cpp:174
static constexpr uint8_t DER_BIT_STRING_TAG
Definition u2f.cpp:28
static constexpr uint8_t DER_EXPLICIT_TAG_3
Definition u2f.cpp:30
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...
Definition u2f.cpp:84
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:401
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:840
bool u2f_import_attestation_cert(const uint8_t *der, size_t len)
Definition u2f.cpp:420
static constexpr int RAW_SIGNATURE_COMPONENT_SIZE
Definition u2f.cpp:37
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...
Definition u2f.cpp:60
static uint8_t g_attest_pubkey[65]
Definition u2f.cpp:46
static constexpr uint8_t DER_ENSURE_POSITIVE_MASK
Definition u2f.cpp:36
static constexpr uint8_t DER_LENGTH_TWO_BYTES
Definition u2f.cpp:31
static constexpr const char * ATTEST_NVS_CERT
Definition u2f.cpp:51
static bool g_attest_initialized
Definition u2f.cpp:47
static uint16_t u2f_response_error(uint8_t *response, uint16_t sw)
Writes a U2F error status word to response buffer.
Definition u2f.cpp:487
static bool is_dummy_application(const uint8_t *application)
U2F register/authenticate command helpers.
Definition u2f.cpp:519
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:136
static constexpr uint8_t DER_SEQUENCE_TAG
DER encoding helper constants for X.509/signature generation.
Definition u2f.cpp:26
static uint16_t u2f_response_sw(uint8_t *response, uint16_t sw)
Writes a U2F status word to response buffer.
Definition u2f.cpp:475
#define U2F_ATTEST_SLOT
Attestation certificate constants and cached buffers.
Definition u2f.cpp:41
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:461
static uint16_t g_attest_cert_len
Definition u2f.cpp:45
static uint16_t u2f_version(uint8_t *response, uint16_t response_max)
Handles U2F VERSION instruction (INS=0x03).
Definition u2f.cpp:497
static constexpr uint8_t DER_INTEGER_TAG
Definition u2f.cpp:27
bool u2f_clear_attestation_cert(void)
Definition u2f.cpp:441
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:108
static constexpr uint8_t DER_EXPLICIT_TAG_0
Definition u2f.cpp:29
bool u2f_get_attestation_pubkey(uint8_t out[65])
Definition u2f.cpp:413
static constexpr uint8_t DER_INTEGER_NEGATIVE_MASK
Definition u2f.cpp:35
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:539
static uint8_t g_attest_cert[U2F_MAX_ATT_CERT_SIZE]
Cached DER attestation certificate and associated state.
Definition u2f.cpp:44
#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