CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
ctap2.cpp
Go to the documentation of this file.
1
5
6#include "mod_fido2/ctap2.h"
8#include "mod_fido2/fido2.h"
11#include "mod_fido2/ctaphid.h"
12#include "mod_fido2/u2f.h"
13#include "cdc_log.h"
14#include "cdc_core/PinManager.h"
16#include <esp_system.h>
17#include <esp_random.h>
18#include <mbedtls/ecdsa.h>
19#include <mbedtls/ecp.h>
20#include <mbedtls/ecdh.h>
21#include <mbedtls/md.h>
22#include <mbedtls/sha256.h>
23#include <mbedtls/aes.h>
25#include <freertos/FreeRTOS.h>
26#include <freertos/task.h>
27#include <esp_attr.h>
28#include <string.h>
29
32
33static const char* TAG = "CTAP2";
34static const char* TAG_PIN = "PIN";
35
37#ifndef CTAP2_DEBUG
38#define CTAP2_DEBUG 0 // Verbose CBOR/response dumps
39#endif
40#ifndef CTAP2_DEBUG_COMMANDS
41#define CTAP2_DEBUG_COMMANDS 0 // Command logging
42#endif
43
45static const uint8_t AAGUID[16] = {
46 0xCD, 0xCB, 0xAD, 0x6E, // "CDCBAD6E"
47 0x39, 0xC3, // 39C3
48 0x00, 0x01, // Version 1
49 0xBA, 0xD6, 0xE0, 0x01, // "BADGE01"
50 0x00, 0x00, 0x00, 0x01 // Device type
51};
52
54static const char *INFO_TRANSPORTS[] = {"usb"};
55
56#define USER_PRESENCE_TIMEOUT_MS 30000 // 30 seconds for user to respond
57
59
60static struct {
64
65 // For getNextAssertion
74} g_ctap2 = {};
75
77
78#define PIN_PROTOCOL_VERSION 2
79#define PIN_TOKEN_SIZE 32
80#define PIN_RETRIES_MAX 8
81#define PIN_UV_RETRIES_MAX 3
82
84#define PIN_CMD_GET_RETRIES 0x01
85#define PIN_CMD_GET_KEY_AGREEMENT 0x02
86#define PIN_CMD_SET_PIN 0x03
87#define PIN_CMD_CHANGE_PIN 0x04
88#define PIN_CMD_GET_PIN_TOKEN 0x05
89#define PIN_CMD_GET_PIN_UV_TOKEN 0x09
90
92#define PIN_PERM_MAKE_CREDENTIAL 0x01 // mc
93#define PIN_PERM_GET_ASSERTION 0x02 // ga
94#define PIN_PERM_CRED_MGMT 0x04 // cm
95#define PIN_PERM_BIO_ENROLLMENT 0x08 // be
96#define PIN_PERM_LARGE_BLOB_WRITE 0x10 // lbw
97#define PIN_PERM_AUTHN_CONFIG 0x20 // acfg
98
99static struct {
100 bool initialized;
101
102 // ECDH key pair (generated on init, regenerated on reset)
103 mbedtls_ecp_keypair ecdh_key;
105
106 // PIN token (regenerated on each getPinToken)
109
110 // Token permissions (CTAP 2.1) - 0 means all permissions (legacy)
112 uint8_t token_rp_id_hash[32]; // RP restriction (if any)
114
115 // Retry counters
116 uint8_t pin_retries;
117 uint8_t uv_retries;
119
121#define CRED_MGMT_GET_CREDS_METADATA 0x01
122#define CRED_MGMT_ENUMERATE_RPS_BEGIN 0x02
123#define CRED_MGMT_ENUMERATE_RPS_GET_NEXT 0x03
124#define CRED_MGMT_ENUMERATE_CREDS_BEGIN 0x04
125#define CRED_MGMT_ENUMERATE_CREDS_GET_NEXT 0x05
126#define CRED_MGMT_DELETE_CREDENTIAL 0x06
127
128static struct {
129 // RP enumeration state
130 uint8_t rp_slots[FIDO2_MAX_CREDENTIALS]; // Slots with unique RPs
131 uint8_t rp_count; // Number of unique RPs
132 uint8_t rp_index; // Current enumeration index
133
134 // Credential enumeration state
135 uint8_t cred_slots[FIDO2_MAX_CREDENTIALS]; // Slots for current RP
136 uint8_t cred_count; // Number of credentials for RP
137 uint8_t cred_index; // Current enumeration index
138 uint8_t current_rp_id_hash[32]; // RP being enumerated
140
146static void secure_random_fill(uint8_t* out, size_t len) {
148 if (se && se->isSessionActive() && se->getRandom(out, static_cast<uint16_t>(len))) {
149 return;
150 }
151 esp_fill_random(out, len);
152}
153
154static uint8_t build_authenticator_data(
155 const uint8_t *rp_id_hash,
156 uint8_t flags,
157 uint32_t sign_count,
158 const uint8_t *attested_cred_data,
159 uint16_t attested_cred_len,
160 const uint8_t *ext_data,
161 uint16_t ext_len,
162 uint8_t *out,
163 uint16_t *out_len
164);
165
173static int ctap2_random(void *ctx, unsigned char *out, size_t len) {
174 (void)ctx;
175 // Use TROPIC01 TRNG (with ESP32 fallback)
176 secure_random_fill(out, len);
177 return 0;
178}
179
191static bool ctap2_build_attested_cred(const uint8_t *cred_id,
192 uint16_t cred_id_len,
193 const uint8_t *pubkey,
194 uint8_t curve,
195 uint8_t *out,
196 size_t out_size,
197 uint16_t *out_len) {
198 if (!out || !out_len) return false;
199
200 const size_t fixed_prefix = 16 + 2 + cred_id_len;
201 if (out_size < fixed_prefix) return false;
202
203 uint16_t off = 0;
204
205 memcpy(out + off, AAGUID, 16);
206 off += 16;
207
208 out[off++] = (cred_id_len >> 8) & 0xFF;
209 out[off++] = cred_id_len & 0xFF;
210
211 memcpy(out + off, cred_id, cred_id_len);
212 off += cred_id_len;
213
214 cbor_writer_t cose_w;
215 cbor_writer_init(&cose_w, out + off, out_size - off);
216 if (curve == CDC_CURVE_ED25519) {
217 cbor_encode_cose_key_ed25519(&cose_w, pubkey);
218 } else {
219 cbor_encode_cose_key_p256(&cose_w, pubkey, pubkey + 32);
220 }
221 off += cbor_writer_length(&cose_w);
222
223 *out_len = off;
224 return !cbor_writer_error(&cose_w);
225}
226
234static uint16_t ctap2_build_cred_protect_extension(uint8_t level, uint8_t *out, size_t out_size) {
235 if (level == 0 || !out || out_size < 20) return 0;
236 cbor_writer_t w;
237 cbor_writer_init(&w, out, out_size);
238 cbor_encode_map(&w, 1);
239 cbor_encode_text(&w, "credProtect");
240 cbor_encode_uint(&w, level);
241 if (cbor_writer_error(&w)) {
242 return 0;
243 }
244 return (uint16_t)cbor_writer_length(&w);
245}
246
258 const uint8_t *attested_cred,
259 uint16_t attested_len,
260 uint8_t cred_protect,
261 uint8_t *auth_data,
262 uint16_t *auth_data_len) {
263 // Flags: UP=0x01, UV=0x04, AT=0x40, ED=0x80
264 uint8_t flags = 0x01 | 0x40; // UP=1, AT=1
266 LOG_I(TAG, "Building authData: pin_verified=%d, cred_protect=%u", pin_verified, cred_protect);
267 if (pin_verified) {
268 flags |= 0x04; // UV=1 when PIN was verified
269 LOG_I(TAG, "UV flag SET -> flags=0x%02X", flags);
270 }
271
272 // Build credProtect extension if requested
273 uint8_t ext_data[32];
274 uint16_t ext_len = 0;
275 if (cred_protect > 0) {
276 ext_len = ctap2_build_cred_protect_extension(cred_protect, ext_data, sizeof(ext_data));
277 if (ext_len > 0) {
278 LOG_I(TAG, "Including credProtect extension (level=%u, %u bytes)", cred_protect, ext_len);
279 }
280 }
281
283 attested_cred, attested_len,
284 ext_len > 0 ? ext_data : NULL, ext_len,
285 auth_data, auth_data_len) == CTAP2_OK;
286}
287
294static uint16_t ctap2_build_appid_extension(uint8_t *out, size_t out_size) {
295 cbor_writer_t w;
296 cbor_writer_init(&w, out, out_size);
297 cbor_encode_map(&w, 1);
298 cbor_encode_text(&w, "appid");
299 cbor_encode_bool(&w, true);
300 if (cbor_writer_error(&w)) {
301 return 0;
302 }
303 return (uint16_t)cbor_writer_length(&w);
304}
305
318static uint8_t ctap2_build_make_credential_response_packed(const uint8_t *auth_data,
319 uint16_t auth_data_len,
320 const uint8_t *sig,
321 uint8_t sig_len,
322 const uint8_t *cert,
323 uint16_t cert_len,
324 uint8_t *response,
325 uint16_t *response_len) {
326 cbor_writer_t w;
327 cbor_writer_init(&w, response + 1, *response_len - 1);
328
329 cbor_encode_map(&w, 3);
330
331 // fmt
333 if (sig_len == 0 && (cert == NULL || cert_len == 0)) {
334 // None attestation
335 cbor_encode_text(&w, "none");
336 } else {
337 cbor_encode_text(&w, "packed");
338 }
339
340 // authData
342 cbor_encode_bytes(&w, auth_data, auth_data_len);
343
344 // attStmt
346 if (sig_len == 0 && (cert == NULL || cert_len == 0)) {
347 // None attestation - empty map
348 cbor_encode_map(&w, 0);
349 } else if (cert && cert_len > 0) {
350 // Basic attestation with certificate
351 cbor_encode_map(&w, 3);
352 cbor_encode_text(&w, "alg");
354 cbor_encode_text(&w, "sig");
355 cbor_encode_bytes(&w, sig, sig_len);
356 cbor_encode_text(&w, "x5c");
357 cbor_encode_array(&w, 1); // Array with single certificate
358 cbor_encode_bytes(&w, cert, cert_len);
359 } else {
360 // Self attestation (no certificate)
361 cbor_encode_map(&w, 2);
362 cbor_encode_text(&w, "alg");
364 cbor_encode_text(&w, "sig");
365 cbor_encode_bytes(&w, sig, sig_len);
366 }
367
368 if (cbor_writer_error(&w)) {
369 response[0] = CTAP2_ERR_OTHER;
370 *response_len = 1;
371 return CTAP2_ERR_OTHER;
372 }
373
374 response[0] = CTAP2_OK;
375 *response_len = 1 + cbor_writer_length(&w);
376 return CTAP2_OK;
377}
378
385static bool ctap2_generate_ephemeral_keypair(mbedtls_ecp_keypair *key, uint8_t pubkey[64]) {
386 if (!key || !pubkey) return false;
387 mbedtls_ecp_keypair_init(key);
388
389 int rc = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, key, ctap2_random, NULL);
390 if (rc != 0) {
391 mbedtls_ecp_keypair_free(key);
392 return false;
393 }
394
395#if defined(MBEDTLS_PRIVATE)
396#define CTAP2_ECP_GRP(k) (k).MBEDTLS_PRIVATE(grp)
397#define CTAP2_ECP_Q(k) (k).MBEDTLS_PRIVATE(Q)
398#else
399#define CTAP2_ECP_GRP(k) (k).grp
400#define CTAP2_ECP_Q(k) (k).Q
401#endif
402
403 uint8_t buf[65];
404 size_t olen = 0;
405 rc = mbedtls_ecp_point_write_binary(&CTAP2_ECP_GRP((*key)), &CTAP2_ECP_Q((*key)),
406 MBEDTLS_ECP_PF_UNCOMPRESSED,
407 &olen, buf, sizeof(buf));
408#undef CTAP2_ECP_GRP
409#undef CTAP2_ECP_Q
410 if (rc != 0 || olen != sizeof(buf)) {
411 mbedtls_ecp_keypair_free(key);
412 return false;
413 }
414 memcpy(pubkey, buf + 1, 64);
415
416 return true;
417}
418
429static bool ctap2_sign_with_keypair(mbedtls_ecp_keypair *key,
430 const uint8_t *msg, size_t msg_len,
431 uint8_t *sig, size_t sig_size, size_t *sig_len) {
432 if (!key || !msg || !sig || !sig_len) return false;
433 uint8_t hash[32];
434 sha256(msg, msg_len, hash);
435
436 mbedtls_ecdsa_context ecdsa;
437 mbedtls_ecdsa_init(&ecdsa);
438 int rc = mbedtls_ecdsa_from_keypair(&ecdsa, key);
439 if (rc != 0) {
440 mbedtls_ecdsa_free(&ecdsa);
441 return false;
442 }
443
444 rc = mbedtls_ecdsa_write_signature(&ecdsa, MBEDTLS_MD_SHA256,
445 hash, sizeof(hash),
446 sig, sig_size, sig_len,
447 ctap2_random, NULL);
448 mbedtls_ecdsa_free(&ecdsa);
449
450 return rc == 0;
451}
452
467 const uint8_t *rp_id_hash,
468 uint8_t flags,
469 uint32_t sign_count,
470 const uint8_t *attested_cred_data,
471 uint16_t attested_cred_len,
472 const uint8_t *ext_data,
473 uint16_t ext_len,
474 uint8_t *out,
475 uint16_t *out_len
476) {
477 uint16_t offset = 0;
478
479 // RP ID hash (32 bytes)
480 memcpy(out + offset, rp_id_hash, 32);
481 offset += 32;
482
483 // Flags (1 byte)
484 if (ext_data && ext_len > 0) {
485 flags |= 0x80; // ED
486 }
487 out[offset++] = flags;
488
489 // Sign count (4 bytes, big endian)
490 out[offset++] = (sign_count >> 24) & 0xFF;
491 out[offset++] = (sign_count >> 16) & 0xFF;
492 out[offset++] = (sign_count >> 8) & 0xFF;
493 out[offset++] = sign_count & 0xFF;
494
495 // Attested credential data (if present)
496 if (attested_cred_data && attested_cred_len > 0) {
497 memcpy(out + offset, attested_cred_data, attested_cred_len);
498 offset += attested_cred_len;
499 }
500
501 if (ext_data && ext_len > 0) {
502 memcpy(out + offset, ext_data, ext_len);
503 offset += ext_len;
504 }
505
506 *out_len = offset;
507 return CTAP2_OK;
508}
509
517static bool wait_for_user_presence(const char *rp_id, fido2_action_t action, const char *user_name) {
518 LOG_I(TAG, "User presence required for %s at %s",
519 action == FIDO2_ACTION_REGISTER ? "registration" : "authentication",
520 rp_id ? rp_id : "unknown");
521
522 // Send keepalive to signal user presence is needed
523 uint32_t cid = ctaphid_get_current_cid();
525
526 // Request user presence via callback
528
529 switch (result) {
531 LOG_I(TAG, "User presence approved");
532 return true;
533 case FIDO2_UP_DENIED:
534 LOG_I(TAG, "User presence denied");
535 return false;
536 case FIDO2_UP_TIMEOUT:
537 LOG_I(TAG, "User presence timeout");
538 return false;
539 default:
540 LOG_W(TAG, "User presence unknown state: %d", result);
541 return false;
542 }
543}
544
546static constexpr uint64_t CTAP2_INFO_MAX_MSG_SIZE_VALUE = 1200;
548static constexpr uint64_t CTAP2_INFO_PIN_UV_AUTH_PROTOCOL_VALUE = 2;
550static constexpr uint64_t CTAP2_INFO_MAX_CRED_LIST_COUNT_VALUE = 8;
551
553static void encode_info_versions(cbor_writer_t *w) {
555 cbor_encode_array(w, 3);
556 cbor_encode_text(w, "FIDO_2_0");
557 cbor_encode_text(w, "FIDO_2_1");
558 cbor_encode_text(w, "U2F_V2");
559}
560
562static void encode_info_extensions(cbor_writer_t *w) {
564 cbor_encode_array(w, 3);
565 cbor_encode_text(w, "appid"); // 5 chars
566 cbor_encode_text(w, "credProtect"); // 11 chars - required for resident keys
567 cbor_encode_text(w, "appidExclude"); // 12 chars
568}
569
571static void encode_info_aaguid(cbor_writer_t *w) {
574}
575
577static void encode_info_options(cbor_writer_t *w) {
579 cbor_encode_map(w, 7);
580 cbor_encode_text(w, "rk"); // 2 chars
581 cbor_encode_bool(w, true);
582 cbor_encode_text(w, "up"); // 2 chars
583 cbor_encode_bool(w, true);
584 cbor_encode_text(w, "uv"); // 2 chars
585 cbor_encode_bool(w, false);
586 cbor_encode_text(w, "plat"); // 4 chars
587 cbor_encode_bool(w, false);
588 cbor_encode_text(w, "credMgmt"); // 8 chars
589 cbor_encode_bool(w, true);
590 cbor_encode_text(w, "clientPin"); // 9 chars
591 cbor_encode_bool(w, true);
592 cbor_encode_text(w, "pinUvAuthToken"); // 14 chars
593 cbor_encode_bool(w, true);
594}
595
601
608
614
620
622static void encode_info_transports(cbor_writer_t *w) {
624 cbor_encode_array(w, 1);
626}
627
629static void encode_info_algorithms(cbor_writer_t *w) {
631 cbor_encode_array(w, 2);
632 // ES256 (P-256/ECDSA) - keys sorted by length: "alg" (3) < "type" (4)
633 cbor_encode_map(w, 2);
634 cbor_encode_text(w, "alg");
636 cbor_encode_text(w, "type");
637 cbor_encode_text(w, "public-key");
638 // EdDSA (Ed25519)
639 cbor_encode_map(w, 2);
640 cbor_encode_text(w, "alg");
642 cbor_encode_text(w, "type");
643 cbor_encode_text(w, "public-key");
644}
645
646#if CTAP2_DEBUG
648static void dump_get_info_response(const uint8_t *response, uint16_t len) {
649 LOG_I(TAG, "getInfo response len=%u", len);
650 for (uint16_t offset = 0; offset < len; offset += 16) {
651 char hex[50] = {0};
652 int dump_len = ((len - offset) < 16) ? (len - offset) : 16;
653 for (int i = 0; i < dump_len; i++) {
654 sprintf(hex + (i * 3), "%02X ", response[offset + i]);
655 }
656 LOG_D(TAG, "%03u: %s", offset, hex);
657 }
658}
659#endif
660
667uint8_t ctap2_get_info(uint8_t *response, uint16_t *response_len) {
668 cbor_writer_t w;
669 cbor_writer_init(&w, response + 1, *response_len - 1);
670
671 // Response is a map of 10 entries (CTAP 2.1 getInfo)
672 cbor_encode_map(&w, 10);
683
684 if (cbor_writer_error(&w)) {
685 response[0] = CTAP2_ERR_OTHER;
686 *response_len = 1;
687 return CTAP2_ERR_OTHER;
688 }
689
690 response[0] = CTAP2_OK;
691 *response_len = 1 + cbor_writer_length(&w);
692
693#if CTAP2_DEBUG
694 dump_get_info_response(response, *response_len);
695#endif
696
697 return CTAP2_OK;
698}
699
700#ifdef __DOXYGEN__
701namespace cdc::mod_fido2 {
702#endif
703
706 uint8_t client_data_hash[32];
708 uint8_t rp_id_hash[32];
710 uint8_t user_id_len;
712 bool rk;
714 int alg;
717 char appid_exclude[256];
719 uint8_t pin_uv_auth_param[64];
723 bool has_rp;
726
727 void clear() {
728 memset(this, 0, sizeof(*this));
729 option_up = true; // Default: UP required
730 }
731};
732
740 int rp_count = cbor_read_map(r);
741 if (rp_count < 0) return false;
742
743 for (int j = 0; j < rp_count; j++) {
744 char rp_key[16];
745 size_t key_len;
746 if (!cbor_read_text(r, rp_key, sizeof(rp_key), &key_len)) {
748 continue;
749 }
750 if (strcmp(rp_key, "id") == 0) {
751 size_t id_len;
752 cbor_read_text(r, p->rp_id, sizeof(p->rp_id), &id_len);
754 p->has_rp = true;
755 } else {
757 }
758 }
759 return true;
760}
761
769 int user_count = cbor_read_map(r);
770 if (user_count < 0) return false;
771
772 for (int j = 0; j < user_count; j++) {
773 char user_key[16];
774 size_t key_len;
775 if (!cbor_read_text(r, user_key, sizeof(user_key), &key_len)) {
777 continue;
778 }
779 if (strcmp(user_key, "id") == 0) {
780 size_t id_len;
781 cbor_read_bytes(r, p->user_id, sizeof(p->user_id), &id_len);
782 p->user_id_len = id_len;
783 p->has_user = true;
784 } else if (strcmp(user_key, "name") == 0) {
785 size_t name_len;
786 cbor_read_text(r, p->user_name, sizeof(p->user_name), &name_len);
787 } else {
789 }
790 }
791 return true;
792}
793
801 int params_count = cbor_read_array(r);
802 if (params_count < 0) return false;
803
804 for (int j = 0; j < params_count; j++) {
805 int param_count = cbor_read_map(r);
806 int64_t param_alg = 0;
807 for (int k = 0; k < param_count; k++) {
808 char param_key[8];
809 size_t key_len;
810 if (!cbor_read_text(r, param_key, sizeof(param_key), &key_len)) {
812 continue;
813 }
814 if (strcmp(param_key, "alg") == 0) {
815 cbor_read_int(r, &param_alg);
816 } else {
818 }
819 }
820 // We support ES256 (P-256/ECDSA) and EdDSA (Ed25519)
821 if (!p->has_alg && (param_alg == COSE_ALG_ES256 || param_alg == COSE_ALG_EDDSA)) {
822 p->alg = param_alg;
823 p->has_alg = true;
824 }
825 }
826 return true;
827}
828
836 int ext_count = cbor_read_map(r);
837 if (ext_count < 0) return false;
838
839 for (int j = 0; j < ext_count; j++) {
840 char ext_key[16];
841 size_t key_len;
842 if (!cbor_read_text(r, ext_key, sizeof(ext_key), &key_len)) {
844 continue;
845 }
846 if (strcmp(ext_key, "appidExclude") == 0) {
847 size_t len;
848 if (cbor_read_text(r, p->appid_exclude, sizeof(p->appid_exclude), &len)) {
849 p->has_appid_exclude = (len > 0);
850 }
851 } else if (strcmp(ext_key, "credProtect") == 0) {
852 uint64_t level;
853 if (cbor_read_uint(r, &level) && level >= 1 && level <= 3) {
854 p->cred_protect = (uint8_t)level;
855 LOG_I(TAG, "credProtect requested: level=%u", p->cred_protect);
856 }
857 } else {
859 }
860 }
861 return true;
862}
863
871 int opt_count = cbor_read_map(r);
872 if (opt_count < 0) return false;
873
874 for (int j = 0; j < opt_count; j++) {
875 char opt_key[8];
876 size_t key_len;
877 if (!cbor_read_text(r, opt_key, sizeof(opt_key), &key_len)) {
879 continue;
880 }
881 if (strcmp(opt_key, "rk") == 0) {
882 cbor_read_bool(r, &p->rk);
883 } else if (strcmp(opt_key, "uv") == 0) {
885 } else if (strcmp(opt_key, "up") == 0) {
887 } else {
889 }
890 }
891 return true;
892}
893
901static uint8_t parse_make_credential_params(const uint8_t *data, uint16_t data_len,
904 cbor_reader_init(&r, data, data_len);
905 p->clear();
906
907 int map_count = cbor_read_map(&r);
908 if (map_count < 0) {
910 }
911
912 for (int i = 0; i < map_count; i++) {
913 uint64_t key;
914 if (!cbor_read_uint(&r, &key)) {
916 }
917
918 switch (key) {
920 size_t len;
921 if (!cbor_read_bytes(&r, p->client_data_hash, 32, &len) || len != 32) {
923 }
924 p->has_client_data = true;
925 break;
926 }
927 case CTAP2_MC_RP:
928 if (!parse_rp_map(&r, p)) return CTAP2_ERR_INVALID_CBOR;
929 break;
930 case CTAP2_MC_USER:
931 if (!parse_user_map(&r, p)) return CTAP2_ERR_INVALID_CBOR;
932 break;
935 break;
938 break;
939 case CTAP2_MC_OPTIONS:
940 if (!parse_options_map(&r, p)) return CTAP2_ERR_INVALID_CBOR;
941 break;
945 break;
947 uint64_t proto;
948 if (cbor_read_uint(&r, &proto)) {
949 p->pin_uv_auth_protocol = (uint8_t)proto;
950 }
951 break;
952 }
953 default:
954 cbor_skip_item(&r);
955 break;
956 }
957 }
958
959 // Validate required parameters
960 if (!p->has_client_data || !p->has_rp || !p->has_user || !p->has_alg) {
962 }
963
964 return CTAP2_OK;
965}
966
972static uint8_t verify_pin_uv_auth(const MakeCredentialParams *p) {
973 LOG_D(TAG, "pinToken valid=%d", g_client_pin.pin_token_valid);
974
975 if (p->pin_uv_auth_param_len == 0) {
976 return CTAP2_OK; // No auth param provided, skip verification
977 }
978
979 if (!g_client_pin.pin_token_valid) {
980 LOG_W(TAG, "makeCredential: pinUvAuthParam provided but no valid pinToken");
982 }
983
984 // Verify HMAC-SHA-256(pinToken, clientDataHash)
985 uint8_t expected_hmac[32];
986 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
987 g_client_pin.pin_token, sizeof(g_client_pin.pin_token),
988 p->client_data_hash, 32,
989 expected_hmac);
990
991 // Protocol 2 uses first 32 bytes of HMAC
992 size_t compare_len = (p->pin_uv_auth_protocol == 2) ? 32 : 16;
993 if (p->pin_uv_auth_param_len < compare_len ||
994 memcmp(p->pin_uv_auth_param, expected_hmac, compare_len) != 0) {
995 LOG_W(TAG, "makeCredential: pinUvAuthParam verification failed");
997 }
998
999 LOG_I(TAG, "makeCredential: pinUvAuthParam verified - UV=1");
1001 return CTAP2_OK;
1002}
1003
1010 if (!p->has_appid_exclude || p->appid_exclude[0] == '\0') {
1011 return CTAP2_OK;
1012 }
1013
1014 uint8_t appid_hash[32];
1015 sha256_str(p->appid_exclude, appid_hash);
1016 if (fido2_storage_find_by_rp(appid_hash, g_ctap2.assertion_creds, FIDO2_MAX_CREDENTIALS) > 0) {
1018 }
1019 return CTAP2_OK;
1020}
1021
1030 uint8_t *response, uint16_t *response_len) {
1031 LOG_I(TAG, "Browser probe request (%s) - waiting for user selection", p->rp_id);
1032
1033 // Wait for user to confirm this authenticator (no PIN needed)
1035 response[0] = CTAP2_ERR_OPERATION_DENIED;
1036 *response_len = 1;
1038 }
1039
1040 // Generate dummy credential for probe response
1041 uint8_t dummy_pubkey[64];
1042 uint8_t dummy_cred_id[FIDO2_CRED_ID_LEN];
1043 if (ctap2_random(NULL, dummy_cred_id, sizeof(dummy_cred_id)) != 0) {
1044 response[0] = CTAP2_ERR_OTHER;
1045 *response_len = 1;
1046 return CTAP2_ERR_OTHER;
1047 }
1048
1049 uint8_t attested_cred[256];
1050 uint16_t attested_len = 0;
1051 uint8_t auth_data[256];
1052 uint16_t auth_data_len = 0;
1053
1054 // Large buffer in PSRAM to save stack space
1055 EXT_RAM_BSS_ATTR static uint8_t to_sign[512];
1056 uint8_t signature[128];
1057 size_t sig_len = 0;
1058
1059 mbedtls_ecp_keypair ephemeral_key;
1060 if (!ctap2_generate_ephemeral_keypair(&ephemeral_key, dummy_pubkey)) {
1061 response[0] = CTAP2_ERR_OTHER;
1062 *response_len = 1;
1063 return CTAP2_ERR_OTHER;
1064 }
1065
1066 if (!ctap2_build_attested_cred(dummy_cred_id, FIDO2_CRED_ID_LEN, dummy_pubkey,
1067 CDC_CURVE_P256, attested_cred, sizeof(attested_cred),
1068 &attested_len)) {
1069 mbedtls_ecp_keypair_free(&ephemeral_key);
1070 response[0] = CTAP2_ERR_OTHER;
1071 *response_len = 1;
1072 return CTAP2_ERR_OTHER;
1073 }
1074
1075 if (!ctap2_build_auth_data_for_cred(p->rp_id_hash, attested_cred, attested_len,
1076 0, auth_data, &auth_data_len)) {
1077 mbedtls_ecp_keypair_free(&ephemeral_key);
1078 response[0] = CTAP2_ERR_OTHER;
1079 *response_len = 1;
1080 return CTAP2_ERR_OTHER;
1081 }
1082
1083 if (auth_data_len + 32 > sizeof(to_sign)) {
1084 mbedtls_ecp_keypair_free(&ephemeral_key);
1085 response[0] = CTAP2_ERR_OTHER;
1086 *response_len = 1;
1087 return CTAP2_ERR_OTHER;
1088 }
1089
1090 memcpy(to_sign, auth_data, auth_data_len);
1091 memcpy(to_sign + auth_data_len, p->client_data_hash, 32);
1092 uint16_t to_sign_len = auth_data_len + 32;
1093
1094 if (!ctap2_sign_with_keypair(&ephemeral_key, to_sign, to_sign_len,
1095 signature, sizeof(signature), &sig_len)) {
1096 mbedtls_ecp_keypair_free(&ephemeral_key);
1097 response[0] = CTAP2_ERR_OTHER;
1098 *response_len = 1;
1099 return CTAP2_ERR_OTHER;
1100 }
1101 mbedtls_ecp_keypair_free(&ephemeral_key);
1102
1103 LOG_I(TAG, "User selected this authenticator");
1105 auth_data, auth_data_len, signature, (uint8_t)sig_len,
1106 NULL, 0, response, response_len);
1107 LOG_I(TAG, "Probe makeCredential status=0x%02X resp_len=%u", status, *response_len);
1108 return status;
1109}
1110
1116static bool is_browser_probe(const char *rp_id) {
1117 return strcmp(rp_id, "make.me.blink") == 0 || strcmp(rp_id, ".dummy") == 0;
1118}
1119
1127static uint8_t mc_rollback_credential(uint8_t slot, uint8_t *response,
1128 uint16_t *response_len) {
1130 response[0] = CTAP2_ERR_OTHER;
1131 *response_len = 1;
1132 return CTAP2_ERR_OTHER;
1133}
1134
1144 uint8_t curve,
1145 uint8_t *response, uint16_t *response_len) {
1146 // Send KEEPALIVE before long operation (key generation takes ~200-500ms)
1148
1149 // Create credential
1150 uint8_t slot;
1151 uint8_t cred_id[FIDO2_CRED_ID_LEN];
1152 uint8_t pubkey[64]; // P-256: X||Y, Ed25519: 32 bytes (only first half used)
1153
1154 LOG_I(TAG, "Calling fido2_storage_create_credential...");
1156 p->rp_id, p->rp_id_hash, p->user_id, p->user_id_len, p->user_name,
1157 p->rk, p->cred_protect, curve, &slot, cred_id, pubkey)) {
1158 response[0] = CTAP2_ERR_KEY_STORE_FULL;
1159 *response_len = 1;
1161 }
1162
1163 uint8_t attested_cred[256];
1164 uint16_t attested_len = 0;
1165 uint8_t auth_data[256];
1166 uint16_t auth_data_len = 0;
1167
1168 if (!ctap2_build_attested_cred(cred_id, FIDO2_CRED_ID_LEN, pubkey, curve,
1169 attested_cred, sizeof(attested_cred),
1170 &attested_len) ||
1171 !ctap2_build_auth_data_for_cred(p->rp_id_hash, attested_cred, attested_len,
1172 p->cred_protect, auth_data, &auth_data_len)) {
1173 return mc_rollback_credential(slot, response, response_len);
1174 }
1175
1176 // Large buffer in PSRAM to save stack space
1177 EXT_RAM_BSS_ATTR static uint8_t mc_to_sign[512];
1178 if (auth_data_len + 32 > sizeof(mc_to_sign)) {
1179 return mc_rollback_credential(slot, response, response_len);
1180 }
1181 memcpy(mc_to_sign, auth_data, auth_data_len);
1182 memcpy(mc_to_sign + auth_data_len, p->client_data_hash, 32);
1183 uint16_t to_sign_len = auth_data_len + 32;
1184
1185 // Sign with attestation key for basic attestation
1186 uint8_t signature[128];
1187 uint8_t sig_len = 0;
1188 const uint8_t *att_cert = NULL;
1189 uint16_t att_cert_len = 0;
1190
1191 LOG_I(TAG, "PIN state: pinToken_valid=%d, is_pin_verified=%d",
1192 g_client_pin.pin_token_valid, fido2_is_pin_verified());
1193
1194 // Use packed attestation with FIDO2-compliant certificate
1195 if (!u2f_get_attestation_cert(&att_cert, &att_cert_len)) {
1196 LOG_E(TAG, "Attestation certificate not initialized");
1197 return mc_rollback_credential(slot, response, response_len);
1198 }
1199
1200 // Send KEEPALIVE before signing (TROPIC01 ECDSA takes ~100ms)
1202
1203 if (!u2f_attestation_sign(mc_to_sign, to_sign_len, signature, &sig_len)) {
1204 LOG_E(TAG, "Attestation signing failed");
1205 return mc_rollback_credential(slot, response, response_len);
1206 }
1207 LOG_I(TAG, "Using basic attestation (cert=%u, sig=%u)", att_cert_len, sig_len);
1208
1210 auth_data, auth_data_len, signature, sig_len,
1211 att_cert, att_cert_len, response, response_len);
1212
1213 if (status != CTAP2_OK) {
1214 return mc_rollback_credential(slot, response, response_len);
1215 }
1216 LOG_I(TAG, "Created credential for %s (slot %d)", p->rp_id, slot);
1217
1218#if CTAP2_DEBUG
1219 LOG_I(TAG, "makeCredential status=0x%02X resp_len=%u", status, *response_len);
1220 for (uint16_t offset = 0; offset < *response_len; offset += 16) {
1221 char hex[50] = {0};
1222 int dump_len = ((*response_len - offset) < 16) ? (*response_len - offset) : 16;
1223 for (int i = 0; i < dump_len; i++) {
1224 sprintf(hex + (i * 3), "%02X ", response[offset + i]);
1225 }
1226 LOG_D(TAG, "%03u: %s", offset, hex);
1227 }
1228#endif
1229
1230 return status;
1231}
1232
1241uint8_t ctap2_make_credential(const uint8_t *params, uint16_t params_len,
1242 uint8_t *response, uint16_t *response_len) {
1244
1245 // Step 1: Parse all CBOR parameters
1246 uint8_t status = parse_make_credential_params(params, params_len, &p);
1247 if (status != CTAP2_OK) {
1248 response[0] = status;
1249 *response_len = 1;
1250 return status;
1251 }
1252
1253 LOG_I(TAG, "makeCredential rp_id=%s rk=%d uv=%d up=%d alg=%d pinProto=%d pinAuthLen=%zu",
1254 p.rp_id[0] ? p.rp_id : "(none)", p.rk, p.option_uv, p.option_up, p.alg,
1256
1257 // Step 2: Check appidExclude extension
1258 status = check_appid_exclude(&p);
1259 if (status != CTAP2_OK) {
1260 response[0] = status;
1261 *response_len = 1;
1262 return status;
1263 }
1264
1265 // Step 3: Verify PIN/UV auth parameter
1266 status = verify_pin_uv_auth(&p);
1267 if (status != CTAP2_OK) {
1268 response[0] = status;
1269 *response_len = 1;
1270 return status;
1271 }
1272
1273 // Step 4: Handle browser probe requests
1274 if (is_browser_probe(p.rp_id)) {
1275 return handle_browser_probe(&p, response, response_len);
1276 }
1277
1278 // Step 5: Determine curve from algorithm
1279 uint8_t curve;
1280 if (p.alg == COSE_ALG_ES256) {
1282 } else if (p.alg == COSE_ALG_EDDSA) {
1284 } else {
1285 response[0] = CTAP2_ERR_UNSUPPORTED_ALGORITHM;
1286 *response_len = 1;
1288 }
1289
1290 // Step 6: Validate options
1291 if (p.option_uv) {
1292 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
1293 *response_len = 1;
1295 }
1296 if (!p.option_up) {
1297 response[0] = CTAP2_ERR_INVALID_OPTION;
1298 *response_len = 1;
1300 }
1301
1304 up_action = FIDO2_ACTION_OVERWRITE;
1305 }
1306
1307 if (!wait_for_user_presence(p.rp_id, up_action, p.user_name)) {
1308 response[0] = CTAP2_ERR_OPERATION_DENIED;
1309 *response_len = 1;
1311 }
1312
1313 LOG_I(TAG, "User presence OK, creating credential (curve=%d)...", curve);
1314
1315 // Step 8: Create credential and build response
1316 return create_credential_and_respond(&p, curve, response, response_len);
1317}
1318
1322 uint8_t rp_id_hash[32];
1323 uint8_t client_data_hash[32];
1326
1327 // Allow list
1331
1332 // Options
1335
1336 // Extensions
1337 char appid[256];
1339 uint8_t appid_hash[32];
1340
1341 // PIN/UV auth
1345};
1346
1350 uint8_t count;
1353 uint8_t* hash_in_use; // Points to rp_id_hash or appid_hash
1354};
1355
1356#ifdef __DOXYGEN__
1357} // namespace cdc::mod_fido2
1358#endif
1359
1367static bool ga_parse_allow_list_credential(cbor_reader_t *r, uint8_t *cred_id,
1368 size_t *cred_id_len) {
1369 int cred_map = cbor_read_map(r);
1370 if (cred_map < 0) {
1371 return false;
1372 }
1373
1374 *cred_id_len = 0;
1375 bool have_id = false;
1376
1377 for (int k = 0; k < cred_map; k++) {
1378 char cred_key[16];
1379 size_t key_len;
1380 if (!cbor_read_text(r, cred_key, sizeof(cred_key), &key_len)) {
1381 cbor_skip_item(r);
1382 continue;
1383 }
1384 if (strcmp(cred_key, "id") == 0) {
1385 if (!cbor_read_bytes(r, cred_id, FIDO2_CRED_ID_LEN, cred_id_len)) {
1386 return false;
1387 }
1388 have_id = true;
1389 } else {
1390 cbor_skip_item(r);
1391 }
1392 }
1393
1394 return have_id && *cred_id_len == FIDO2_CRED_ID_LEN;
1395}
1396
1403static uint8_t ga_parse_allow_list(cbor_reader_t *r, GetAssertionParams *p) {
1404 int list_count = cbor_read_array(r);
1405 if (list_count < 0) {
1407 }
1408
1409 p->allow_list_present = true;
1410
1411 for (int j = 0; j < list_count; j++) {
1412 uint8_t cred_id[FIDO2_CRED_ID_LEN];
1413 size_t cred_id_len = 0;
1414
1415 if (!ga_parse_allow_list_credential(r, cred_id, &cred_id_len)) {
1416 continue;
1417 }
1418
1419 int8_t slot = fido2_storage_find_slot_by_cred_id(cred_id, cred_id_len);
1420 if (slot < 0) {
1421 continue;
1422 }
1423
1424 // Avoid duplicates
1425 bool exists = false;
1426 for (uint8_t m = 0; m < p->allow_list_count; m++) {
1427 if (p->allow_list_slots[m] == (uint8_t)slot) {
1428 exists = true;
1429 break;
1430 }
1431 }
1432 if (!exists && p->allow_list_count < FIDO2_MAX_CREDENTIALS) {
1433 p->allow_list_slots[p->allow_list_count++] = (uint8_t)slot;
1434 }
1435 }
1436
1437 return CTAP2_OK;
1438}
1439
1446static uint8_t ga_parse_extensions(cbor_reader_t *r, GetAssertionParams *p) {
1447 int ext_count = cbor_read_map(r);
1448 if (ext_count < 0) {
1450 }
1451
1452 for (int j = 0; j < ext_count; j++) {
1453 char ext_key[16];
1454 size_t key_len;
1455 if (!cbor_read_text(r, ext_key, sizeof(ext_key), &key_len)) {
1456 cbor_skip_item(r);
1457 continue;
1458 }
1459 if (strcmp(ext_key, "appid") == 0) {
1460 size_t len;
1461 if (cbor_read_text(r, p->appid, sizeof(p->appid), &len)) {
1462 p->has_appid = (len > 0);
1463 if (p->has_appid) {
1464 sha256_str(p->appid, p->appid_hash);
1465 }
1466 }
1467 } else {
1468 cbor_skip_item(r);
1469 }
1470 }
1471
1472 return CTAP2_OK;
1473}
1474
1481static uint8_t ga_parse_options(cbor_reader_t *r, GetAssertionParams *p) {
1482 int opt_count = cbor_read_map(r);
1483 if (opt_count < 0) {
1485 }
1486
1487 for (int j = 0; j < opt_count; j++) {
1488 char opt_key[8];
1489 size_t key_len;
1490 if (!cbor_read_text(r, opt_key, sizeof(opt_key), &key_len)) {
1491 cbor_skip_item(r);
1492 continue;
1493 }
1494 if (strcmp(opt_key, "uv") == 0) {
1495 cbor_read_bool(r, &p->option_uv);
1496 } else if (strcmp(opt_key, "up") == 0) {
1497 cbor_read_bool(r, &p->option_up);
1498 } else {
1499 cbor_skip_item(r);
1500 }
1501 }
1502
1503 return CTAP2_OK;
1504}
1505
1513static uint8_t ga_parse_params(const uint8_t *params, uint16_t params_len,
1514 GetAssertionParams *p) {
1515 memset(p, 0, sizeof(*p));
1516 p->option_up = true; // Default: user presence required
1517
1518 cbor_reader_t r;
1519 cbor_reader_init(&r, params, params_len);
1520
1521 int map_count = cbor_read_map(&r);
1522 if (map_count < 0) {
1524 }
1525
1526 for (int i = 0; i < map_count; i++) {
1527 uint64_t key;
1528 if (!cbor_read_uint(&r, &key)) {
1530 }
1531
1532 uint8_t status = CTAP2_OK;
1533
1534 switch (key) {
1535 case CTAP2_GA_RP_ID:
1536 {
1537 size_t len;
1538 cbor_read_text(&r, p->rp_id, sizeof(p->rp_id), &len);
1539 sha256_str(p->rp_id, p->rp_id_hash);
1540 p->has_rp = true;
1541 }
1542 break;
1543
1545 {
1546 size_t len;
1547 if (!cbor_read_bytes(&r, p->client_data_hash, 32, &len) || len != 32) {
1549 }
1550 p->has_client_data = true;
1551 }
1552 break;
1553
1555 status = ga_parse_allow_list(&r, p);
1556 if (status != CTAP2_OK) return status;
1557 break;
1558
1560 status = ga_parse_extensions(&r, p);
1561 if (status != CTAP2_OK) return status;
1562 break;
1563
1564 case CTAP2_GA_OPTIONS:
1565 status = ga_parse_options(&r, p);
1566 if (status != CTAP2_OK) return status;
1567 break;
1568
1570 cbor_read_bytes(&r, p->pin_uv_auth_param, sizeof(p->pin_uv_auth_param),
1571 &p->pin_uv_auth_param_len);
1572 break;
1573
1575 {
1576 uint64_t proto;
1577 if (cbor_read_uint(&r, &proto)) {
1578 p->pin_uv_auth_protocol = (uint8_t)proto;
1579 }
1580 }
1581 break;
1582
1583 default:
1584 cbor_skip_item(&r);
1585 break;
1586 }
1587 }
1588
1589 return CTAP2_OK;
1590}
1591
1598static uint8_t ga_verify_pin_auth(const GetAssertionParams *p, bool *uv_verified) {
1599 *uv_verified = false;
1600
1601 if (p->pin_uv_auth_param_len == 0) {
1602 return CTAP2_OK; // No PIN auth provided, continue without UV
1603 }
1604
1605 if (!g_client_pin.pin_token_valid) {
1606 LOG_W(TAG, "pinUvAuthParam provided but no valid pinToken");
1608 }
1609
1610 // Compute HMAC-SHA256(pinToken, clientDataHash)
1611 uint8_t expected_hmac[32];
1612 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
1613 g_client_pin.pin_token, PIN_TOKEN_SIZE,
1614 p->client_data_hash, 32,
1615 expected_hmac);
1616
1617 // Protocol 2 uses first 32 bytes of HMAC, protocol 1 uses 16
1618 size_t compare_len = (p->pin_uv_auth_protocol == 2) ? 32 : 16;
1619 if (p->pin_uv_auth_param_len < compare_len) {
1620 LOG_W(TAG, "pinUvAuthParam too short: %zu < %zu",
1621 p->pin_uv_auth_param_len, compare_len);
1623 }
1624
1625#if DEBUG_MODE
1626 LOG_D(TAG, "pinUvAuthParam received (%zu bytes):", p->pin_uv_auth_param_len);
1627 LOG_D(TAG, " %02X%02X%02X%02X %02X%02X%02X%02X...",
1628 p->pin_uv_auth_param[0], p->pin_uv_auth_param[1],
1629 p->pin_uv_auth_param[2], p->pin_uv_auth_param[3],
1630 p->pin_uv_auth_param[4], p->pin_uv_auth_param[5],
1631 p->pin_uv_auth_param[6], p->pin_uv_auth_param[7]);
1632 LOG_D(TAG, "Expected HMAC (first %zu bytes):", compare_len);
1633 LOG_D(TAG, " %02X%02X%02X%02X %02X%02X%02X%02X...",
1634 expected_hmac[0], expected_hmac[1], expected_hmac[2], expected_hmac[3],
1635 expected_hmac[4], expected_hmac[5], expected_hmac[6], expected_hmac[7]);
1636#endif
1637
1638 if (memcmp(p->pin_uv_auth_param, expected_hmac, compare_len) != 0) {
1639 LOG_W(TAG, "pinUvAuthParam verification failed");
1641 }
1642
1643 LOG_I(TAG, "pinUvAuthParam verified - UV=1");
1644 *uv_verified = true;
1646
1647 return CTAP2_OK;
1648}
1649
1656static void ga_find_credentials(GetAssertionParams *p, AssertionCredentials *creds) {
1657 memset(creds, 0, sizeof(*creds));
1658 creds->hash_in_use = p->rp_id_hash;
1659
1660 uint8_t temp_slots[FIDO2_MAX_CREDENTIALS] = {0};
1661 uint8_t temp_count = 0;
1662
1663 if (p->allow_list_present && p->allow_list_count > 0) {
1664 // Filter allowList by RP ID hash
1665 uint8_t filtered = 0;
1666 for (uint8_t i = 0; i < p->allow_list_count; i++) {
1667 fido2_credential_info_t info;
1668 if (fido2_storage_get_credential(p->allow_list_slots[i], &info) &&
1669 memcmp(info.rp_id_hash, p->rp_id_hash, 32) == 0) {
1670 creds->slots[filtered++] = p->allow_list_slots[i];
1671 }
1672 }
1673 creds->count = filtered;
1674 creds->include_user = false;
1675
1676 LOG_I(TAG, "getAssertion using allowList, matches=%u", creds->count);
1677
1678 // Try appid extension if present
1679 if (p->has_appid) {
1680 uint8_t filtered_appid = 0;
1681 for (uint8_t i = 0; i < p->allow_list_count; i++) {
1682 fido2_credential_info_t info;
1683 if (fido2_storage_get_credential(p->allow_list_slots[i], &info) &&
1684 memcmp(info.rp_id_hash, p->appid_hash, 32) == 0) {
1685 temp_slots[filtered_appid++] = p->allow_list_slots[i];
1686 }
1687 }
1688 if (filtered_appid > 0) {
1689 memcpy(creds->slots, temp_slots, filtered_appid);
1690 creds->count = filtered_appid;
1691 creds->appid_used = true;
1692 creds->hash_in_use = p->appid_hash;
1693 }
1694 }
1695 } else {
1696 // No allowList - search all credentials for this RP
1697 creds->count = fido2_storage_find_by_rp(p->rp_id_hash, creds->slots,
1699 creds->include_user = true;
1700
1701 LOG_I(TAG, "getAssertion using all RP creds, matches=%u", creds->count);
1702
1703 // Try appid extension if present
1704 if (p->has_appid) {
1705 temp_count = fido2_storage_find_by_rp(p->appid_hash, temp_slots,
1707 if (temp_count > 0) {
1708 memcpy(creds->slots, temp_slots, temp_count);
1709 creds->count = temp_count;
1710 creds->appid_used = true;
1711 creds->hash_in_use = p->appid_hash;
1712 }
1713 }
1714 }
1715}
1716
1727static uint8_t ga_sign_assertion(uint8_t slot, const uint8_t *auth_data,
1728 uint16_t auth_data_len, const uint8_t *client_data_hash,
1729 uint8_t *signature, uint8_t *sig_len) {
1730 // Prepare data to sign: authData || clientDataHash
1731 uint8_t to_sign[96]; // Max 64 bytes auth_data + 32 bytes clientDataHash
1732 if (auth_data_len > sizeof(to_sign) - 32) {
1733 LOG_E(TAG, "auth_data too large: %u", auth_data_len);
1734 return CTAP2_ERR_OTHER;
1735 }
1736
1737 memcpy(to_sign, auth_data, auth_data_len);
1738 memcpy(to_sign + auth_data_len, client_data_hash, 32);
1739 uint16_t to_sign_len = auth_data_len + 32;
1740
1741 if (!fido2_storage_sign_raw(slot, to_sign, to_sign_len, signature, sig_len)) {
1742 return CTAP2_ERR_OTHER;
1743 }
1744
1745 return CTAP2_OK;
1746}
1747
1762static uint8_t ga_build_response(const uint8_t *cred_id, const uint8_t *auth_data,
1763 uint16_t auth_data_len, const uint8_t *signature,
1764 uint8_t sig_len, const fido2_credential_info_t *cred,
1765 bool include_user, uint8_t total_creds,
1766 uint8_t *response, uint16_t *response_len) {
1767 cbor_writer_t w;
1768 cbor_writer_init(&w, response + 1, *response_len - 1);
1769
1770 int resp_fields = 3; // credential, authData, signature
1771 if (include_user) resp_fields++;
1772 if (total_creds > 1) resp_fields++;
1773
1774 cbor_encode_map(&w, resp_fields);
1775
1776 // credential descriptor
1778 cbor_encode_map(&w, 2);
1779 // Canonical order: "id" (len 2) before "type" (len 4)
1780 cbor_encode_text(&w, "id");
1782 cbor_encode_text(&w, "type");
1783 cbor_encode_text(&w, "public-key");
1784
1785 // authData
1787 cbor_encode_bytes(&w, auth_data, auth_data_len);
1788
1789 // signature
1791 cbor_encode_bytes(&w, signature, sig_len);
1792
1793 // user (only for discoverable credentials)
1794 if (include_user) {
1796 int user_fields = 1;
1797 if (cred->user_name[0] != '\0') user_fields++;
1798 cbor_encode_map(&w, user_fields);
1799
1800 cbor_encode_text(&w, "id");
1801 cbor_encode_bytes(&w, cred->user_id, cred->user_id_len);
1802
1803 if (cred->user_name[0] != '\0') {
1804 cbor_encode_text(&w, "name");
1805 cbor_encode_text(&w, cred->user_name);
1806 }
1807 }
1808
1809 // numberOfCredentials (if multiple)
1810 if (total_creds > 1) {
1812 cbor_encode_uint(&w, total_creds);
1813 }
1814
1815 if (cbor_writer_error(&w)) {
1816 return CTAP2_ERR_OTHER;
1817 }
1818
1819 response[0] = CTAP2_OK;
1820 *response_len = 1 + cbor_writer_length(&w);
1821 return CTAP2_OK;
1822}
1823
1832uint8_t ctap2_get_assertion(const uint8_t *params, uint16_t params_len,
1833 uint8_t *response, uint16_t *response_len) {
1834 // Step 1: Parse CBOR parameters
1835 GetAssertionParams p;
1836 uint8_t status = ga_parse_params(params, params_len, &p);
1837 if (status != CTAP2_OK) {
1838 response[0] = status;
1839 *response_len = 1;
1840 return status;
1841 }
1842
1843 LOG_I(TAG, "getAssertion rp_id=%s allowList=%d count=%u uv=%d up=%d appid=%s",
1844 p.rp_id[0] ? p.rp_id : "(none)", p.allow_list_present ? 1 : 0,
1845 p.allow_list_count, p.option_uv, p.option_up, p.has_appid ? p.appid : "(none)");
1846
1847 // Step 2: Validate required parameters
1848 if (!p.has_rp || !p.has_client_data) {
1849 response[0] = CTAP2_ERR_MISSING_PARAMETER;
1850 *response_len = 1;
1852 }
1853
1854 if (p.option_uv) {
1855 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
1856 *response_len = 1;
1858 }
1859
1860 // Step 3: Verify PIN/UV auth if provided
1861 bool uv_verified = false;
1862 status = ga_verify_pin_auth(&p, &uv_verified);
1863 if (status != CTAP2_OK) {
1864 response[0] = status;
1865 *response_len = 1;
1866 return status;
1867 }
1868
1869 // Step 4: Find matching credentials
1870 AssertionCredentials creds;
1872
1873 if (creds.count == 0) {
1874 response[0] = CTAP2_ERR_NO_CREDENTIALS;
1875 *response_len = 1;
1877 }
1878
1879 // Step 5: Request user presence (if required)
1880 if (p.option_up) {
1882 response[0] = CTAP2_ERR_OPERATION_DENIED;
1883 *response_len = 1;
1885 }
1886 }
1887
1888 // Step 6: Save state for getNextAssertion
1889 memcpy(g_ctap2.assertion_creds, creds.slots, creds.count);
1890 g_ctap2.assertion_count = creds.count;
1891 g_ctap2.assertion_include_user = creds.include_user;
1892 memcpy(g_ctap2.assertion_rp_id_hash, creds.hash_in_use, 32);
1893 memcpy(g_ctap2.assertion_client_data_hash, p.client_data_hash, 32);
1894 g_ctap2.assertion_index = 0;
1895 g_ctap2.assertion_up_done = p.option_up;
1896 g_ctap2.assertion_appid_used = creds.appid_used;
1897
1898 // Step 7: Get first credential
1899 uint8_t slot = g_ctap2.assertion_creds[0];
1900 fido2_credential_info_t cred;
1901 if (!fido2_storage_get_credential(slot, &cred)) {
1902 response[0] = CTAP2_ERR_OTHER;
1903 *response_len = 1;
1904 return CTAP2_ERR_OTHER;
1905 }
1906
1907 // Step 8: Build authenticator data
1909 if (sign_count == 0) {
1910 response[0] = CTAP2_ERR_OTHER;
1911 *response_len = 1;
1912 return CTAP2_ERR_OTHER;
1913 }
1914
1915 uint8_t flags = p.option_up ? 0x01 : 0x00; // UP=1 only if user presence was requested
1916 if (uv_verified) {
1917 flags |= 0x04; // UV=1
1918 }
1919
1920 uint8_t ext_data[32];
1921 uint16_t ext_len = 0;
1922 if (creds.appid_used) {
1923 ext_len = ctap2_build_appid_extension(ext_data, sizeof(ext_data));
1924 }
1925
1926 uint8_t auth_data[128];
1927 uint16_t auth_data_len;
1928 build_authenticator_data(creds.hash_in_use, flags, sign_count, NULL, 0,
1929 ext_data, ext_len, auth_data, &auth_data_len);
1930
1931 // Step 9: Generate signature
1932 uint8_t signature[128];
1933 uint8_t sig_len;
1934 status = ga_sign_assertion(slot, auth_data, auth_data_len, p.client_data_hash,
1935 signature, &sig_len);
1936 if (status != CTAP2_OK) {
1937 response[0] = status;
1938 *response_len = 1;
1939 return status;
1940 }
1941
1942 // Step 10: Get credential ID
1943 uint8_t cred_id[FIDO2_CRED_ID_LEN];
1944 if (!fido2_storage_get_cred_id(slot, cred_id)) {
1945 LOG_E(TAG, "Failed to get credential ID for slot %d", slot);
1946 response[0] = CTAP2_ERR_OTHER;
1947 *response_len = 1;
1948 return CTAP2_ERR_OTHER;
1949 }
1950
1951 // Step 11: Build CBOR response
1952 status = ga_build_response(cred_id, auth_data, auth_data_len, signature, sig_len,
1953 &cred, g_ctap2.assertion_include_user,
1954 g_ctap2.assertion_count, response, response_len);
1955 if (status != CTAP2_OK) {
1956 response[0] = status;
1957 *response_len = 1;
1958 return status;
1959 }
1960
1962
1963 LOG_I(TAG, "Assertion for %s (slot %d)", p.rp_id, slot);
1964 return CTAP2_OK;
1965}
1966
1973uint8_t ctap2_get_next_assertion(uint8_t *response, uint16_t *response_len) {
1974 if (g_ctap2.assertion_count == 0) {
1975 response[0] = CTAP2_ERR_NOT_ALLOWED;
1976 *response_len = 1;
1977 return CTAP2_ERR_NOT_ALLOWED;
1978 }
1979
1980 g_ctap2.assertion_index++;
1981 if (g_ctap2.assertion_index >= g_ctap2.assertion_count) {
1982 response[0] = CTAP2_ERR_NOT_ALLOWED;
1983 *response_len = 1;
1984 return CTAP2_ERR_NOT_ALLOWED;
1985 }
1986
1987 // Similar to getAssertion but without user presence check
1988 uint8_t slot = g_ctap2.assertion_creds[g_ctap2.assertion_index];
1989 fido2_credential_info_t cred;
1990 if (!fido2_storage_get_credential(slot, &cred)) {
1991 response[0] = CTAP2_ERR_OTHER;
1992 *response_len = 1;
1993 return CTAP2_ERR_OTHER;
1994 }
1995
1997 if (sign_count == 0) {
1998 response[0] = CTAP2_ERR_OTHER;
1999 *response_len = 1;
2000 return CTAP2_ERR_OTHER;
2001 }
2002
2003 uint8_t auth_data[128];
2004 uint16_t auth_data_len;
2005 uint8_t ext_data[32];
2006 uint16_t ext_len = 0;
2007 if (g_ctap2.assertion_appid_used) {
2008 ext_len = ctap2_build_appid_extension(ext_data, sizeof(ext_data));
2009 }
2010 build_authenticator_data(g_ctap2.assertion_rp_id_hash, 0x01, sign_count,
2011 NULL, 0, ext_data, ext_len, auth_data, &auth_data_len);
2012
2013 // Sign: authData || clientDataHash (TROPIC01 hashes internally)
2014 uint8_t to_sign[96];
2015 if (auth_data_len > sizeof(to_sign) - 32) {
2016 LOG_E(TAG, "auth_data too large: %u", auth_data_len);
2017 response[0] = CTAP2_ERR_OTHER;
2018 *response_len = 1;
2019 return CTAP2_ERR_OTHER;
2020 }
2021 memcpy(to_sign, auth_data, auth_data_len);
2022 memcpy(to_sign + auth_data_len, g_ctap2.assertion_client_data_hash, 32);
2023 uint16_t to_sign_len = auth_data_len + 32;
2024
2025 uint8_t signature[128];
2026 uint8_t sig_len;
2027 if (!fido2_storage_sign_raw(slot, to_sign, to_sign_len, signature, &sig_len)) {
2028 response[0] = CTAP2_ERR_OTHER;
2029 *response_len = 1;
2030 return CTAP2_ERR_OTHER;
2031 }
2032
2033 // Get credential ID
2034 uint8_t cred_id[FIDO2_CRED_ID_LEN];
2035 if (!fido2_storage_get_cred_id(slot, cred_id)) {
2036 LOG_E(TAG, "Failed to get credential ID for slot %d", slot);
2037 response[0] = CTAP2_ERR_OTHER;
2038 *response_len = 1;
2039 return CTAP2_ERR_OTHER;
2040 }
2041
2042 cbor_writer_t w;
2043 cbor_writer_init(&w, response + 1, *response_len - 1);
2044
2045 cbor_encode_map(&w, 3);
2046
2047 // credential descriptor (type + id)
2049 cbor_encode_map(&w, 2);
2050 // Canonical order: "id" (len 2) before "type" (len 4)
2051 cbor_encode_text(&w, "id");
2053 cbor_encode_text(&w, "type");
2054 cbor_encode_text(&w, "public-key");
2055
2056 // authData
2058 cbor_encode_bytes(&w, auth_data, auth_data_len);
2059
2060 // signature
2062 cbor_encode_bytes(&w, signature, sig_len);
2063
2064 response[0] = CTAP2_OK;
2065 *response_len = 1 + cbor_writer_length(&w);
2066 return CTAP2_OK;
2067}
2068
2074static bool client_pin_init_ecdh(void) {
2075 if (g_client_pin.ecdh_valid) return true;
2076
2077 mbedtls_ecp_keypair_init(&g_client_pin.ecdh_key);
2078
2079 int ret = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1,
2080 &g_client_pin.ecdh_key,
2081 ctap2_random, NULL);
2082 if (ret != 0) {
2083 LOG_E(TAG_PIN, "ECDH key generation failed: %d", ret);
2084 return false;
2085 }
2086
2087 g_client_pin.ecdh_valid = true;
2088 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2089 g_client_pin.uv_retries = PIN_UV_RETRIES_MAX;
2090 LOG_I(TAG_PIN, "ECDH key pair generated");
2091 return true;
2092}
2093
2102static bool client_pin_compute_shared_secret(const uint8_t *platform_key_x,
2103 const uint8_t *platform_key_y,
2104 uint8_t pin_protocol,
2105 uint8_t *shared_secret) {
2106 if (!g_client_pin.ecdh_valid) return false;
2107
2108 mbedtls_ecp_point platform_point;
2109 mbedtls_mpi shared_x;
2110
2111 mbedtls_ecp_point_init(&platform_point);
2112 mbedtls_mpi_init(&shared_x);
2113
2114 int ret = 0;
2115
2116 // Load platform public key
2117 ret = mbedtls_mpi_read_binary(&platform_point.MBEDTLS_PRIVATE(X), platform_key_x, 32);
2118 if (ret != 0) goto cleanup;
2119
2120 ret = mbedtls_mpi_read_binary(&platform_point.MBEDTLS_PRIVATE(Y), platform_key_y, 32);
2121 if (ret != 0) goto cleanup;
2122
2123 ret = mbedtls_mpi_lset(&platform_point.MBEDTLS_PRIVATE(Z), 1);
2124 if (ret != 0) goto cleanup;
2125
2126 // Compute ECDH: shared_x = (platformPubKey * authenticatorPrivKey).x
2127 ret = mbedtls_ecdh_compute_shared(&g_client_pin.ecdh_key.MBEDTLS_PRIVATE(grp),
2128 &shared_x,
2129 &platform_point,
2130 &g_client_pin.ecdh_key.MBEDTLS_PRIVATE(d),
2131 ctap2_random, NULL);
2132 if (ret != 0) {
2133 LOG_E(TAG_PIN, "ECDH compute failed: %d", ret);
2134 goto cleanup;
2135 }
2136
2137 // Extract x coordinate as bytes (Z = ECDH shared secret)
2138 uint8_t ecdh_z[32];
2139 ret = mbedtls_mpi_write_binary(&shared_x, ecdh_z, 32);
2140 if (ret != 0) goto cleanup;
2141
2142#if DEBUG_MODE
2143 // Full debug output for manual verification
2144 LOG_I(TAG_PIN, "=== ECDH DEBUG (full 32-byte values) ===");
2145 LOG_I(TAG_PIN, "Z (ECDH x-coord):");
2146 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2147 ecdh_z[0], ecdh_z[1], ecdh_z[2], ecdh_z[3], ecdh_z[4], ecdh_z[5], ecdh_z[6], ecdh_z[7],
2148 ecdh_z[8], ecdh_z[9], ecdh_z[10], ecdh_z[11], ecdh_z[12], ecdh_z[13], ecdh_z[14], ecdh_z[15]);
2149 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2150 ecdh_z[16], ecdh_z[17], ecdh_z[18], ecdh_z[19], ecdh_z[20], ecdh_z[21], ecdh_z[22], ecdh_z[23],
2151 ecdh_z[24], ecdh_z[25], ecdh_z[26], ecdh_z[27], ecdh_z[28], ecdh_z[29], ecdh_z[30], ecdh_z[31]);
2152#endif // DEBUG_MODE
2153
2154 if (pin_protocol == 1) {
2155 // Protocol 1: sharedSecret = SHA256(Z)
2156 LOG_D(TAG_PIN, "Using Protocol 1: SHA256(Z)");
2157 mbedtls_sha256(ecdh_z, 32, shared_secret, 0); // 0 = SHA256 (not SHA224)
2158 } else {
2159 // Protocol 2: Use HKDF-SHA256 to derive AES key
2160 // AES_key = HKDF-SHA256(salt=32zeros, IKM=Z, L=32, info="CTAP2 AES key")
2161 LOG_D(TAG_PIN, "Using Protocol 2: HKDF(Z)");
2162 const char *info = "CTAP2 AES key";
2163 uint8_t prk[32];
2164 uint8_t zero_salt[32] = {0};
2165
2166 // HKDF Extract: PRK = HMAC-SHA256(salt, IKM=Z)
2167 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
2168 zero_salt, 32, ecdh_z, 32, prk);
2169
2170 // HKDF Expand: OKM = HMAC-SHA256(PRK, info || 0x01)
2171 uint8_t expand_input[32];
2172 size_t info_len = strlen(info);
2173 memcpy(expand_input, info, info_len);
2174 expand_input[info_len] = 0x01;
2175
2176#if DEBUG_MODE
2177 LOG_I(TAG_PIN, "HKDF PRK (HMAC-SHA256(salt=0, IKM=Z)):");
2178 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2179 prk[0], prk[1], prk[2], prk[3], prk[4], prk[5], prk[6], prk[7],
2180 prk[8], prk[9], prk[10], prk[11], prk[12], prk[13], prk[14], prk[15]);
2181 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2182 prk[16], prk[17], prk[18], prk[19], prk[20], prk[21], prk[22], prk[23],
2183 prk[24], prk[25], prk[26], prk[27], prk[28], prk[29], prk[30], prk[31]);
2184 LOG_I(TAG_PIN, "HKDF info: '%s' || 0x01 (len=%zu)", info, info_len + 1);
2185#endif
2186
2187 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
2188 prk, 32, expand_input, info_len + 1, shared_secret);
2189 }
2190
2191#if DEBUG_MODE
2192 LOG_I(TAG_PIN, "AES key (shared secret):");
2193 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2194 shared_secret[0], shared_secret[1], shared_secret[2], shared_secret[3],
2195 shared_secret[4], shared_secret[5], shared_secret[6], shared_secret[7],
2196 shared_secret[8], shared_secret[9], shared_secret[10], shared_secret[11],
2197 shared_secret[12], shared_secret[13], shared_secret[14], shared_secret[15]);
2198 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2199 shared_secret[16], shared_secret[17], shared_secret[18], shared_secret[19],
2200 shared_secret[20], shared_secret[21], shared_secret[22], shared_secret[23],
2201 shared_secret[24], shared_secret[25], shared_secret[26], shared_secret[27],
2202 shared_secret[28], shared_secret[29], shared_secret[30], shared_secret[31]);
2203 LOG_I(TAG_PIN, "=== END ECDH DEBUG ===");
2204#endif
2205
2206cleanup:
2207 mbedtls_ecp_point_free(&platform_point);
2208 mbedtls_mpi_free(&shared_x);
2209 return ret == 0;
2210}
2211
2221static bool aes_256_cbc_decrypt_iv(const uint8_t *key, const uint8_t *iv,
2222 const uint8_t *input, size_t len, uint8_t *output) {
2223 mbedtls_aes_context aes;
2224 mbedtls_aes_init(&aes);
2225
2226 uint8_t iv_copy[16];
2227 memcpy(iv_copy, iv, 16); // mbedtls modifies IV during decrypt
2228
2229 int ret = mbedtls_aes_setkey_dec(&aes, key, 256);
2230 if (ret != 0) {
2231 mbedtls_aes_free(&aes);
2232 return false;
2233 }
2234
2235 ret = mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, len, iv_copy, input, output);
2236 mbedtls_aes_free(&aes);
2237 return ret == 0;
2238}
2239
2248static bool aes_256_cbc_decrypt(const uint8_t *key, const uint8_t *input,
2249 size_t len, uint8_t *output) {
2250 uint8_t iv[16] = {0};
2251 return aes_256_cbc_decrypt_iv(key, iv, input, len, output);
2252}
2253
2262static bool aes_256_cbc_encrypt(const uint8_t *key, const uint8_t *input,
2263 size_t len, uint8_t *output) {
2264 mbedtls_aes_context aes;
2265 mbedtls_aes_init(&aes);
2266
2267 uint8_t iv[16] = {0}; // IV is all zeros for PIN protocol 1
2268
2269 int ret = mbedtls_aes_setkey_enc(&aes, key, 256);
2270 if (ret != 0) {
2271 mbedtls_aes_free(&aes);
2272 return false;
2273 }
2274
2275 ret = mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, len, iv, input, output);
2276 mbedtls_aes_free(&aes);
2277 return ret == 0;
2278}
2279
2288static bool aes_256_cbc_encrypt_p2(const uint8_t *key, const uint8_t *input,
2289 size_t len, uint8_t *output) {
2290 mbedtls_aes_context aes;
2291 mbedtls_aes_init(&aes);
2292
2293 // Generate random IV using TROPIC01 TRNG
2294 uint8_t iv[16];
2295 secure_random_fill(iv, 16);
2296
2297 // Copy IV to output first
2298 memcpy(output, iv, 16);
2299
2300 int ret = mbedtls_aes_setkey_enc(&aes, key, 256);
2301 if (ret != 0) {
2302 mbedtls_aes_free(&aes);
2303 return false;
2304 }
2305
2306 // Encrypt after the IV prefix
2307 ret = mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, len, iv, input, output + 16);
2308 mbedtls_aes_free(&aes);
2309 return ret == 0;
2310}
2311
2318static uint8_t client_pin_get_retries(uint8_t *response, uint16_t *response_len) {
2319 cbor_writer_t w;
2320 cbor_writer_init(&w, response + 1, *response_len - 1);
2321
2322 cbor_encode_map(&w, 2);
2323
2324 // pinRetries
2326 cbor_encode_uint(&w, g_client_pin.pin_retries);
2327
2328 // uvRetries (powerCycleState is optional and not used)
2330 cbor_encode_uint(&w, g_client_pin.uv_retries);
2331
2332 response[0] = CTAP2_OK;
2333 *response_len = 1 + cbor_writer_length(&w);
2334 return CTAP2_OK;
2335}
2336
2343static uint8_t client_pin_get_key_agreement(uint8_t *response, uint16_t *response_len) {
2344 if (!client_pin_init_ecdh()) {
2345 response[0] = CTAP2_ERR_OTHER;
2346 *response_len = 1;
2347 return CTAP2_ERR_OTHER;
2348 }
2349
2350 // Extract public key coordinates
2351 uint8_t pub_x[32], pub_y[32];
2352 mbedtls_mpi_write_binary(&g_client_pin.ecdh_key.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), pub_x, 32);
2353 mbedtls_mpi_write_binary(&g_client_pin.ecdh_key.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), pub_y, 32);
2354
2355#if DEBUG_MODE
2356 LOG_I(TAG_PIN, "=== Our ECDH public key ===");
2357 LOG_I(TAG_PIN, "X: %02X%02X%02X%02X %02X%02X%02X%02X...",
2358 pub_x[0], pub_x[1], pub_x[2], pub_x[3], pub_x[4], pub_x[5], pub_x[6], pub_x[7]);
2359 LOG_I(TAG_PIN, "Y: %02X%02X%02X%02X %02X%02X%02X%02X...",
2360 pub_y[0], pub_y[1], pub_y[2], pub_y[3], pub_y[4], pub_y[5], pub_y[6], pub_y[7]);
2361#endif
2362
2363 cbor_writer_t w;
2364 cbor_writer_init(&w, response + 1, *response_len - 1);
2365
2366 cbor_encode_map(&w, 1);
2367
2368 // keyAgreement (COSE_Key, RFC 8152)
2370 cbor_encode_map(&w, 5);
2371
2372 // kty: EC2
2375
2376 // alg: ECDH-ES + HKDF-256
2379
2380 // crv: P-256
2383
2384 // x coordinate
2386 cbor_encode_bytes(&w, pub_x, 32);
2387
2388 // y coordinate
2390 cbor_encode_bytes(&w, pub_y, 32);
2391
2392 response[0] = CTAP2_OK;
2393 *response_len = 1 + cbor_writer_length(&w);
2394 LOG_I(TAG_PIN, "Sent key agreement");
2395 return CTAP2_OK;
2396}
2397
2406static uint8_t client_pin_get_pin_token(const uint8_t *params, uint16_t params_len,
2407 uint8_t *response, uint16_t *response_len) {
2408 // Check if PIN is blocked
2409 if (g_client_pin.pin_retries == 0) {
2410 response[0] = CTAP2_ERR_PIN_BLOCKED;
2411 *response_len = 1;
2412 return CTAP2_ERR_PIN_BLOCKED;
2413 }
2414
2415 // Check if FIDO2 PIN hash is available
2417 LOG_E(TAG_PIN, "FIDO2 hash not available - user must reset PIN");
2418 response[0] = CTAP2_ERR_PIN_NOT_SET;
2419 *response_len = 1;
2420 return CTAP2_ERR_PIN_NOT_SET;
2421 }
2422
2423 // Parse parameters
2424 cbor_reader_t r;
2425 cbor_reader_init(&r, params, params_len);
2426
2427 uint8_t platform_key_x[32] = {0};
2428 uint8_t platform_key_y[32] = {0};
2429 uint8_t pin_hash_enc[64] = {0}; // Can be 16 or 32 bytes (with padding)
2430 size_t pin_hash_enc_len = 0;
2431 uint8_t pin_protocol = 2; // Default to Protocol 2
2432 bool has_key = false, has_pin = false;
2433
2434 int map_size = cbor_read_map(&r);
2435 if (map_size < 0) {
2436 response[0] = CTAP2_ERR_INVALID_CBOR;
2437 *response_len = 1;
2439 }
2440
2441 for (int i = 0; i < map_size; i++) {
2442 // Read key - could be positive or negative, so use cbor_read_item
2443 cbor_item_t item;
2444 if (!cbor_read_item(&r, &item)) {
2445 LOG_E(TAG_PIN, "Failed to read map key %d", i);
2446 break;
2447 }
2448
2449 int64_t key;
2450 if (item.type == CBOR_UNSIGNED) {
2451 key = (int64_t)item.value;
2452 } else if (item.type == CBOR_NEGATIVE) {
2453 key = -1 - (int64_t)item.value;
2454 } else {
2455 LOG_E(TAG_PIN, "Unexpected key type: %d", item.type);
2456 cbor_skip_item(&r);
2457 continue;
2458 }
2459
2460 LOG_D(TAG_PIN, "Parsing key: %lld", key);
2461
2462 switch (key) {
2463 case CTAP2_PIN_PROTOCOL: {
2464 uint64_t proto;
2465 if (cbor_read_uint(&r, &proto)) {
2466 pin_protocol = (uint8_t)proto;
2467 LOG_I(TAG_PIN, "Client requested protocol: %d", pin_protocol);
2468 }
2469 break;
2470 }
2472 int cose_size = cbor_read_map(&r);
2473 LOG_D(TAG_PIN, "COSE_Key map size: %d", cose_size);
2474 if (cose_size < 0) break;
2475 for (int j = 0; j < cose_size; j++) {
2476 // COSE keys can be positive (kty=1, alg=3) or negative (crv=-1, x=-2, y=-3)
2477 cbor_item_t cose_item;
2478 if (!cbor_read_item(&r, &cose_item)) {
2479 LOG_E(TAG_PIN, "Failed to read COSE key %d", j);
2480 break;
2481 }
2482
2483 int64_t cose_key;
2484 if (cose_item.type == CBOR_UNSIGNED) {
2485 cose_key = (int64_t)cose_item.value;
2486 } else if (cose_item.type == CBOR_NEGATIVE) {
2487 cose_key = -1 - (int64_t)cose_item.value;
2488 } else {
2489 LOG_E(TAG_PIN, "Unexpected COSE key type: %d", cose_item.type);
2490 cbor_skip_item(&r);
2491 continue;
2492 }
2493
2494 LOG_D(TAG_PIN, "COSE key: %lld", cose_key);
2495
2496 if (cose_key == COSE_KEY_LABEL_X) { // x coordinate
2497 size_t x_len;
2498 if (cbor_read_bytes(&r, platform_key_x, 32, &x_len) && x_len == 32) {
2499 has_key = true;
2500 LOG_D(TAG_PIN, "Got x coordinate");
2501 }
2502 } else if (cose_key == COSE_KEY_LABEL_Y) { // y coordinate
2503 size_t y_len;
2504 cbor_read_bytes(&r, platform_key_y, 32, &y_len);
2505 LOG_D(TAG_PIN, "Got y coordinate");
2506 } else {
2507 cbor_skip_item(&r);
2508 }
2509 }
2510 break;
2511 }
2512 case CTAP2_PIN_HASH_ENC: {
2513 if (cbor_read_bytes(&r, pin_hash_enc, sizeof(pin_hash_enc), &pin_hash_enc_len)) {
2514 LOG_D(TAG_PIN, "pinHashEnc read OK, len=%zu", pin_hash_enc_len);
2515 if (pin_hash_enc_len == 16 || pin_hash_enc_len == 32 || pin_hash_enc_len == 64) {
2516 has_pin = true;
2517 LOG_D(TAG_PIN, "Got pinHashEnc (%zu bytes)", pin_hash_enc_len);
2518 } else {
2519 LOG_E(TAG_PIN, "pinHashEnc unexpected size: %zu", pin_hash_enc_len);
2520 }
2521 } else {
2522 LOG_E(TAG_PIN, "Failed to read pinHashEnc bytes");
2523 }
2524 break;
2525 }
2526 default:
2527 cbor_skip_item(&r);
2528 break;
2529 }
2530 }
2531
2532 if (!has_key || !has_pin) {
2533 LOG_E(TAG_PIN, "Missing keyAgreement or pinHashEnc");
2534 response[0] = CTAP2_ERR_MISSING_PARAMETER;
2535 *response_len = 1;
2537 }
2538
2539#if DEBUG_MODE
2540 // Log received pinHashEnc for debugging
2541 LOG_I(TAG_PIN, "Received pinHashEnc (%zu bytes):", pin_hash_enc_len);
2542 for (size_t i = 0; i < pin_hash_enc_len; i += 16) {
2543 size_t row_len = (pin_hash_enc_len - i < 16) ? (pin_hash_enc_len - i) : 16;
2544 char hex[64];
2545 char *p = hex;
2546 for (size_t j = 0; j < row_len; j++) {
2547 p += sprintf(p, "%02X ", pin_hash_enc[i + j]);
2548 }
2549 LOG_I(TAG_PIN, " %s", hex);
2550 }
2551#endif
2552
2553 // Compute shared secret
2554 uint8_t shared_secret[32];
2555 if (!client_pin_compute_shared_secret(platform_key_x, platform_key_y, pin_protocol, shared_secret)) {
2556 response[0] = CTAP2_ERR_OTHER;
2557 *response_len = 1;
2558 return CTAP2_ERR_OTHER;
2559 }
2560
2561#if DEBUG_MODE
2562 // Full platform key for verification
2563 LOG_I(TAG_PIN, "Platform (Chrome) public key X:");
2564 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2565 platform_key_x[0], platform_key_x[1], platform_key_x[2], platform_key_x[3],
2566 platform_key_x[4], platform_key_x[5], platform_key_x[6], platform_key_x[7],
2567 platform_key_x[8], platform_key_x[9], platform_key_x[10], platform_key_x[11],
2568 platform_key_x[12], platform_key_x[13], platform_key_x[14], platform_key_x[15]);
2569 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2570 platform_key_x[16], platform_key_x[17], platform_key_x[18], platform_key_x[19],
2571 platform_key_x[20], platform_key_x[21], platform_key_x[22], platform_key_x[23],
2572 platform_key_x[24], platform_key_x[25], platform_key_x[26], platform_key_x[27],
2573 platform_key_x[28], platform_key_x[29], platform_key_x[30], platform_key_x[31]);
2574 LOG_I(TAG_PIN, "Platform (Chrome) public key Y:");
2575 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2576 platform_key_y[0], platform_key_y[1], platform_key_y[2], platform_key_y[3],
2577 platform_key_y[4], platform_key_y[5], platform_key_y[6], platform_key_y[7],
2578 platform_key_y[8], platform_key_y[9], platform_key_y[10], platform_key_y[11],
2579 platform_key_y[12], platform_key_y[13], platform_key_y[14], platform_key_y[15]);
2580 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2581 platform_key_y[16], platform_key_y[17], platform_key_y[18], platform_key_y[19],
2582 platform_key_y[20], platform_key_y[21], platform_key_y[22], platform_key_y[23],
2583 platform_key_y[24], platform_key_y[25], platform_key_y[26], platform_key_y[27],
2584 platform_key_y[28], platform_key_y[29], platform_key_y[30], platform_key_y[31]);
2585#endif
2586
2587 // Decrypt pinHashEnc
2588 // Protocol 1: 16 bytes ciphertext with IV=0
2589 // Protocol 2: 32 bytes = IV (16) || ciphertext (16)
2590 uint8_t decrypted_pin_hash[16];
2591
2592 if (pin_protocol == 2 && pin_hash_enc_len == 32) {
2593 // Protocol 2: first 16 bytes are IV, next 16 are ciphertext
2594 const uint8_t *iv = pin_hash_enc;
2595 const uint8_t *ciphertext = pin_hash_enc + 16;
2596
2597 LOG_D(TAG_PIN, "Protocol 2 IV: %02X%02X%02X%02X %02X%02X%02X%02X...",
2598 iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7]);
2599 LOG_D(TAG_PIN, "Ciphertext: %02X%02X%02X%02X %02X%02X%02X%02X...",
2600 ciphertext[0], ciphertext[1], ciphertext[2], ciphertext[3],
2601 ciphertext[4], ciphertext[5], ciphertext[6], ciphertext[7]);
2602
2603 if (!aes_256_cbc_decrypt_iv(shared_secret, iv, ciphertext, 16, decrypted_pin_hash)) {
2604 LOG_E(TAG_PIN, "PIN decryption failed");
2605 response[0] = CTAP2_ERR_OTHER;
2606 *response_len = 1;
2607 return CTAP2_ERR_OTHER;
2608 }
2609 } else {
2610 // Protocol 1: IV is all zeros
2611 uint8_t decrypted[64];
2612 if (!aes_256_cbc_decrypt(shared_secret, pin_hash_enc, pin_hash_enc_len, decrypted)) {
2613 LOG_E(TAG_PIN, "PIN decryption failed");
2614 response[0] = CTAP2_ERR_OTHER;
2615 *response_len = 1;
2616 return CTAP2_ERR_OTHER;
2617 }
2618 memcpy(decrypted_pin_hash, decrypted, 16);
2619 }
2620
2621#if DEBUG_MODE
2622 LOG_D(TAG_PIN, "Decrypted PIN hash: %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2623 decrypted_pin_hash[0], decrypted_pin_hash[1], decrypted_pin_hash[2], decrypted_pin_hash[3],
2624 decrypted_pin_hash[4], decrypted_pin_hash[5], decrypted_pin_hash[6], decrypted_pin_hash[7],
2625 decrypted_pin_hash[8], decrypted_pin_hash[9], decrypted_pin_hash[10], decrypted_pin_hash[11],
2626 decrypted_pin_hash[12], decrypted_pin_hash[13], decrypted_pin_hash[14], decrypted_pin_hash[15]);
2627
2628 // Get stored hash for comparison
2629 uint8_t stored_hash[16];
2630 pin_storage_get_fido2_hash(stored_hash);
2631 LOG_D(TAG_PIN, "Stored FIDO2 hash: %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2632 stored_hash[0], stored_hash[1], stored_hash[2], stored_hash[3],
2633 stored_hash[4], stored_hash[5], stored_hash[6], stored_hash[7],
2634 stored_hash[8], stored_hash[9], stored_hash[10], stored_hash[11],
2635 stored_hash[12], stored_hash[13], stored_hash[14], stored_hash[15]);
2636
2637 // Debug: compute expected hash for "0000"
2638 uint8_t test_full[32];
2639 sha256((const uint8_t*)"0000", 4, test_full);
2640 LOG_D(TAG_PIN, "Expected for 0000: %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2641 test_full[0], test_full[1], test_full[2], test_full[3],
2642 test_full[4], test_full[5], test_full[6], test_full[7],
2643 test_full[8], test_full[9], test_full[10], test_full[11],
2644 test_full[12], test_full[13], test_full[14], test_full[15]);
2645#endif
2646
2647 // Verify PIN hash
2648 if (!pin_storage_verify_fido2_hash(decrypted_pin_hash)) {
2649 g_client_pin.pin_retries--;
2650 LOG_W(TAG_PIN, "Invalid PIN, retries left: %d", g_client_pin.pin_retries);
2651
2652 if (g_client_pin.pin_retries == 0) {
2653 response[0] = CTAP2_ERR_PIN_BLOCKED;
2654 } else {
2655 response[0] = CTAP2_ERR_PIN_INVALID;
2656 }
2657 *response_len = 1;
2658 return response[0];
2659 }
2660
2661 // PIN correct - reset retries and generate pinToken
2662 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2664 g_client_pin.pin_token_valid = true;
2665
2666 // Encrypt pinToken with shared secret
2667 // Protocol 1: IV=0, returns ciphertext only (32 bytes)
2668 // Protocol 2: returns IV || ciphertext (16 + 32 = 48 bytes)
2669 uint8_t encrypted_token[PIN_TOKEN_SIZE + 16]; // Extra space for IV in Protocol 2
2670 size_t encrypted_len;
2671
2672 if (pin_protocol == 2) {
2673 if (!aes_256_cbc_encrypt_p2(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2674 response[0] = CTAP2_ERR_OTHER;
2675 *response_len = 1;
2676 return CTAP2_ERR_OTHER;
2677 }
2678 encrypted_len = PIN_TOKEN_SIZE + 16; // IV + ciphertext
2679 LOG_D(TAG_PIN, "Encrypted pinToken (Protocol 2, %zu bytes with IV)", encrypted_len);
2680 } else {
2681 if (!aes_256_cbc_encrypt(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2682 response[0] = CTAP2_ERR_OTHER;
2683 *response_len = 1;
2684 return CTAP2_ERR_OTHER;
2685 }
2686 encrypted_len = PIN_TOKEN_SIZE;
2687 LOG_D(TAG_PIN, "Encrypted pinToken (Protocol 1, %zu bytes)", encrypted_len);
2688 }
2689
2690 // Build response
2691 cbor_writer_t w;
2692 cbor_writer_init(&w, response + 1, *response_len - 1);
2693
2694 cbor_encode_map(&w, 1);
2695
2696 // pinUvAuthToken (encrypted)
2698 cbor_encode_bytes(&w, encrypted_token, encrypted_len);
2699
2700 response[0] = CTAP2_OK;
2701 *response_len = 1 + cbor_writer_length(&w);
2702
2703 // Legacy token (0x05) has all permissions
2704 g_client_pin.token_permissions = 0xFF;
2705 g_client_pin.token_rp_id_set = false;
2706
2707 LOG_I(TAG_PIN, "PIN verified, token issued (legacy, all permissions)");
2708 return CTAP2_OK;
2709}
2710
2719static uint8_t client_pin_get_pin_uv_auth_token(const uint8_t *params, uint16_t params_len,
2720 uint8_t *response, uint16_t *response_len) {
2721 // Check if PIN is blocked
2722 if (g_client_pin.pin_retries == 0) {
2723 response[0] = CTAP2_ERR_PIN_BLOCKED;
2724 *response_len = 1;
2725 return CTAP2_ERR_PIN_BLOCKED;
2726 }
2727
2728 // Check if FIDO2 PIN hash is available
2730 LOG_E(TAG_PIN, "FIDO2 hash not available - user must reset PIN");
2731 response[0] = CTAP2_ERR_PIN_NOT_SET;
2732 *response_len = 1;
2733 return CTAP2_ERR_PIN_NOT_SET;
2734 }
2735
2736 // Parse parameters
2737 cbor_reader_t r;
2738 cbor_reader_init(&r, params, params_len);
2739
2740 uint8_t platform_key_x[32] = {0};
2741 uint8_t platform_key_y[32] = {0};
2742 uint8_t pin_hash_enc[64] = {0};
2743 size_t pin_hash_enc_len = 0;
2744 uint8_t pin_protocol = 2;
2745 uint8_t permissions = 0;
2746 char rp_id[64] = {0};
2747 bool has_key = false, has_pin = false, has_permissions = false;
2748
2749 int map_size = cbor_read_map(&r);
2750 if (map_size < 0) {
2751 response[0] = CTAP2_ERR_INVALID_CBOR;
2752 *response_len = 1;
2754 }
2755
2756 for (int i = 0; i < map_size; i++) {
2757 cbor_item_t item;
2758 if (!cbor_read_item(&r, &item)) break;
2759
2760 int64_t key;
2761 if (item.type == CBOR_UNSIGNED) {
2762 key = (int64_t)item.value;
2763 } else if (item.type == CBOR_NEGATIVE) {
2764 key = -1 - (int64_t)item.value;
2765 } else {
2766 cbor_skip_item(&r);
2767 continue;
2768 }
2769
2770 switch (key) {
2771 case CTAP2_PIN_PROTOCOL: {
2772 uint64_t proto;
2773 if (cbor_read_uint(&r, &proto)) {
2774 pin_protocol = (uint8_t)proto;
2775 }
2776 break;
2777 }
2779 int cose_size = cbor_read_map(&r);
2780 if (cose_size < 0) break;
2781 for (int j = 0; j < cose_size; j++) {
2782 cbor_item_t cose_item;
2783 if (!cbor_read_item(&r, &cose_item)) break;
2784
2785 int64_t cose_key;
2786 if (cose_item.type == CBOR_UNSIGNED) {
2787 cose_key = (int64_t)cose_item.value;
2788 } else if (cose_item.type == CBOR_NEGATIVE) {
2789 cose_key = -1 - (int64_t)cose_item.value;
2790 } else {
2791 cbor_skip_item(&r);
2792 continue;
2793 }
2794
2795 if (cose_key == COSE_KEY_LABEL_X) { // x coordinate
2796 size_t x_len;
2797 if (cbor_read_bytes(&r, platform_key_x, 32, &x_len) && x_len == 32) {
2798 has_key = true;
2799 }
2800 } else if (cose_key == COSE_KEY_LABEL_Y) { // y coordinate
2801 size_t y_len;
2802 cbor_read_bytes(&r, platform_key_y, 32, &y_len);
2803 } else {
2804 cbor_skip_item(&r);
2805 }
2806 }
2807 break;
2808 }
2809 case CTAP2_PIN_HASH_ENC: {
2810 if (cbor_read_bytes(&r, pin_hash_enc, sizeof(pin_hash_enc), &pin_hash_enc_len)) {
2811 if (pin_hash_enc_len == 16 || pin_hash_enc_len == 32 || pin_hash_enc_len == 64) {
2812 has_pin = true;
2813 }
2814 }
2815 break;
2816 }
2817 case CTAP2_PIN_PERMISSIONS: {
2818 uint64_t perm;
2819 if (cbor_read_uint(&r, &perm)) {
2820 permissions = (uint8_t)perm;
2821 has_permissions = true;
2822 LOG_I(TAG_PIN, "Requested permissions: 0x%02X", permissions);
2823 }
2824 break;
2825 }
2827 size_t rp_len;
2828 if (cbor_read_text(&r, rp_id, sizeof(rp_id) - 1, &rp_len)) {
2829 LOG_I(TAG_PIN, "Requested rpId: %s", rp_id);
2830 }
2831 break;
2832 }
2833 default:
2834 cbor_skip_item(&r);
2835 break;
2836 }
2837 }
2838
2839 if (!has_key || !has_pin) {
2840 LOG_E(TAG_PIN, "Missing keyAgreement or pinHashEnc");
2841 response[0] = CTAP2_ERR_MISSING_PARAMETER;
2842 *response_len = 1;
2844 }
2845
2846 if (!has_permissions) {
2847 LOG_E(TAG_PIN, "Missing permissions parameter");
2848 response[0] = CTAP2_ERR_MISSING_PARAMETER;
2849 *response_len = 1;
2851 }
2852
2853 // Compute shared secret
2854 uint8_t shared_secret[32];
2855 if (!client_pin_compute_shared_secret(platform_key_x, platform_key_y, pin_protocol, shared_secret)) {
2856 response[0] = CTAP2_ERR_OTHER;
2857 *response_len = 1;
2858 return CTAP2_ERR_OTHER;
2859 }
2860
2861 // Decrypt pinHashEnc
2862 uint8_t decrypted_pin_hash[16];
2863 if (pin_protocol == 2 && pin_hash_enc_len == 32) {
2864 const uint8_t *iv = pin_hash_enc;
2865 const uint8_t *ciphertext = pin_hash_enc + 16;
2866 if (!aes_256_cbc_decrypt_iv(shared_secret, iv, ciphertext, 16, decrypted_pin_hash)) {
2867 LOG_E(TAG_PIN, "PIN decryption failed");
2868 response[0] = CTAP2_ERR_OTHER;
2869 *response_len = 1;
2870 return CTAP2_ERR_OTHER;
2871 }
2872 } else {
2873 uint8_t decrypted[64];
2874 if (!aes_256_cbc_decrypt(shared_secret, pin_hash_enc, pin_hash_enc_len, decrypted)) {
2875 LOG_E(TAG_PIN, "PIN decryption failed");
2876 response[0] = CTAP2_ERR_OTHER;
2877 *response_len = 1;
2878 return CTAP2_ERR_OTHER;
2879 }
2880 memcpy(decrypted_pin_hash, decrypted, 16);
2881 }
2882
2883 // Verify PIN hash
2884 if (!pin_storage_verify_fido2_hash(decrypted_pin_hash)) {
2885 g_client_pin.pin_retries--;
2886 LOG_W(TAG_PIN, "Invalid PIN, retries left: %d", g_client_pin.pin_retries);
2887 response[0] = (g_client_pin.pin_retries == 0) ? CTAP2_ERR_PIN_BLOCKED : CTAP2_ERR_PIN_INVALID;
2888 *response_len = 1;
2889 return response[0];
2890 }
2891
2892 // PIN correct - reset retries and generate pinToken
2893 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2895 g_client_pin.pin_token_valid = true;
2896
2897 // Store permissions
2898 g_client_pin.token_permissions = permissions;
2899 if (rp_id[0]) {
2900 sha256_str(rp_id, g_client_pin.token_rp_id_hash);
2901 g_client_pin.token_rp_id_set = true;
2902 } else {
2903 g_client_pin.token_rp_id_set = false;
2904 }
2905
2906 // Encrypt pinToken
2907 uint8_t encrypted_token[PIN_TOKEN_SIZE + 16];
2908 size_t encrypted_len;
2909
2910 if (pin_protocol == 2) {
2911 if (!aes_256_cbc_encrypt_p2(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2912 response[0] = CTAP2_ERR_OTHER;
2913 *response_len = 1;
2914 return CTAP2_ERR_OTHER;
2915 }
2916 encrypted_len = PIN_TOKEN_SIZE + 16;
2917 } else {
2918 if (!aes_256_cbc_encrypt(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2919 response[0] = CTAP2_ERR_OTHER;
2920 *response_len = 1;
2921 return CTAP2_ERR_OTHER;
2922 }
2923 encrypted_len = PIN_TOKEN_SIZE;
2924 }
2925
2926 // Build response
2927 cbor_writer_t w;
2928 cbor_writer_init(&w, response + 1, *response_len - 1);
2929
2930 cbor_encode_map(&w, 1);
2932 cbor_encode_bytes(&w, encrypted_token, encrypted_len);
2933
2934 response[0] = CTAP2_OK;
2935 *response_len = 1 + cbor_writer_length(&w);
2936 LOG_I(TAG_PIN, "PIN verified, token issued with permissions=0x%02X", permissions);
2937 return CTAP2_OK;
2938}
2939
2948uint8_t ctap2_client_pin(const uint8_t *params, uint16_t params_len,
2949 uint8_t *response, uint16_t *response_len) {
2950 // Initialize if needed
2951 if (!g_client_pin.initialized) {
2952 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2953 g_client_pin.uv_retries = PIN_UV_RETRIES_MAX;
2954 g_client_pin.initialized = true;
2955 }
2956
2957 // Parse subCommand
2958 cbor_reader_t r;
2959 cbor_reader_init(&r, params, params_len);
2960
2961 int map_size = cbor_read_map(&r);
2962 if (map_size < 0) {
2963 response[0] = CTAP2_ERR_INVALID_CBOR;
2964 *response_len = 1;
2966 }
2967
2968 uint64_t pin_protocol = 0;
2969 uint64_t sub_command = 0;
2970
2971 for (int i = 0; i < map_size; i++) {
2972 uint64_t key;
2973 if (!cbor_read_uint(&r, &key)) break;
2974
2975 if (key == 0x01) { // pinUvAuthProtocol
2976 cbor_read_uint(&r, &pin_protocol);
2977 } else if (key == 0x02) { // subCommand
2978 cbor_read_uint(&r, &sub_command);
2979 } else {
2980 cbor_skip_item(&r);
2981 }
2982 }
2983
2984 LOG_I(TAG_PIN, "ClientPIN: protocol=%llu, subCommand=0x%02llx", pin_protocol, sub_command);
2985
2986 // We only support protocol 2
2987 if (pin_protocol != 0 && pin_protocol != PIN_PROTOCOL_VERSION) {
2988 response[0] = CTAP1_ERR_INVALID_PARAMETER;
2989 *response_len = 1;
2991 }
2992
2993 switch (sub_command) {
2995 return client_pin_get_retries(response, response_len);
2996
2998 return client_pin_get_key_agreement(response, response_len);
2999
3001 return client_pin_get_pin_token(params, params_len, response, response_len);
3002
3004 return client_pin_get_pin_uv_auth_token(params, params_len, response, response_len);
3005
3006 case PIN_CMD_SET_PIN:
3007 case PIN_CMD_CHANGE_PIN:
3008 // Not supported - PIN is set via badge UI
3009 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
3010 *response_len = 1;
3012
3013 default:
3014 LOG_W(TAG_PIN, "Unknown subCommand: 0x%02lx", sub_command);
3015 response[0] = CTAP1_ERR_INVALID_COMMAND;
3016 *response_len = 1;
3018 }
3019}
3020
3027uint8_t ctap2_reset(uint8_t *response, uint16_t *response_len) {
3028 // CTAP2.1 6.4: reset requires explicit user presence.
3030 response[0] = CTAP2_ERR_OPERATION_DENIED;
3031 *response_len = 1;
3033 }
3034 if (!fido2_factory_reset()) {
3035 response[0] = CTAP2_ERR_OTHER;
3036 *response_len = 1;
3037 return CTAP2_ERR_OTHER;
3038 }
3039
3040 response[0] = CTAP2_OK;
3041 *response_len = 1;
3042 LOG_I(TAG, "Factory reset complete");
3043 return CTAP2_OK;
3044}
3045
3052static bool cred_mgmt_slot_has_key(uint8_t slot) {
3053 uint8_t pubkey[64];
3054 if (!fido2_storage_get_pubkey(slot, pubkey)) {
3055 LOG_W(TAG, "credMgmt: skipping slot %d (no SE key)", slot);
3056 return false;
3057 }
3058 return true;
3059}
3060
3065static uint8_t cred_mgmt_count_unique_rps(void) {
3066 // Large buffer in PSRAM (32 * 32 = 1024 bytes)
3067 EXT_RAM_BSS_ATTR static uint8_t unique_hashes[FIDO2_MAX_CREDENTIALS][32];
3068 uint8_t count = 0;
3069
3070 for (uint8_t slot = 0; slot < FIDO2_MAX_CREDENTIALS; slot++) {
3071 if (!fido2_storage_is_resident(slot)) continue;
3072
3073 fido2_credential_info_t info;
3074 if (!fido2_storage_get_credential(slot, &info)) continue;
3075 if (!cred_mgmt_slot_has_key(slot)) continue;
3076
3077 // Check if this RP hash is already in our list
3078 bool found = false;
3079 for (uint8_t j = 0; j < count; j++) {
3080 if (memcmp(unique_hashes[j], info.rp_id_hash, 32) == 0) {
3081 found = true;
3082 break;
3083 }
3084 }
3085
3086 if (!found && count < FIDO2_MAX_CREDENTIALS) {
3087 memcpy(unique_hashes[count], info.rp_id_hash, 32);
3088 g_cred_mgmt.rp_slots[count] = slot; // Store a representative slot
3089 count++;
3090 }
3091 }
3092
3093 return count;
3094}
3095
3101static uint8_t cred_mgmt_find_creds_for_rp(const uint8_t *rp_id_hash) {
3102 uint8_t count = 0;
3103
3104 for (uint8_t slot = 0; slot < FIDO2_MAX_CREDENTIALS && count < FIDO2_MAX_CREDENTIALS; slot++) {
3105 if (!fido2_storage_is_resident(slot)) continue;
3106
3107 fido2_credential_info_t info;
3108 if (!fido2_storage_get_credential(slot, &info)) continue;
3109
3110 if (memcmp(info.rp_id_hash, rp_id_hash, 32) != 0) continue;
3111 if (!cred_mgmt_slot_has_key(slot)) continue;
3112
3113 g_cred_mgmt.cred_slots[count++] = slot;
3114 }
3115
3116 return count;
3117}
3118
3126static bool cred_mgmt_encode_rp(cbor_writer_t *w, uint8_t slot, bool include_total) {
3127 fido2_credential_info_t info;
3128 if (!fido2_storage_get_credential(slot, &info)) return false;
3129
3130 // Map with 2 or 3 entries
3131 cbor_encode_map(w, include_total ? 3 : 2);
3132
3133 // rp (map with id)
3135 cbor_encode_map(w, 1);
3136 cbor_encode_text(w, "id");
3137 cbor_encode_text(w, info.rp_id);
3138
3139 // rpIDHash
3141 cbor_encode_bytes(w, info.rp_id_hash, 32);
3142
3143 // totalRPs (only in first response)
3144 if (include_total) {
3146 cbor_encode_uint(w, g_cred_mgmt.rp_count);
3147 }
3148
3149 return true;
3150}
3151
3159static bool cred_mgmt_encode_credential(cbor_writer_t *w, uint8_t slot, bool include_total) {
3160 fido2_credential_info_t info;
3161 if (!fido2_storage_get_credential(slot, &info)) return false;
3162
3163 uint8_t cred_id[FIDO2_CRED_ID_LEN];
3164 if (!fido2_storage_get_cred_id(slot, cred_id)) return false;
3165
3166 uint8_t pubkey[64];
3167 if (!fido2_storage_get_pubkey(slot, pubkey)) return false;
3168
3169 // Map with 4 or 5 entries
3170 cbor_encode_map(w, include_total ? 5 : 4);
3171
3172 // user
3174 cbor_encode_map(w, info.user_name[0] ? 2 : 1);
3175 cbor_encode_text(w, "id");
3176 cbor_encode_bytes(w, info.user_id, info.user_id_len);
3177 if (info.user_name[0]) {
3178 cbor_encode_text(w, "name");
3179 cbor_encode_text(w, info.user_name);
3180 }
3181
3182 // credentialID (PublicKeyCredentialDescriptor)
3184 cbor_encode_map(w, 2);
3185 // Canonical order: "id" (len 2) before "type" (len 4)
3186 cbor_encode_text(w, "id");
3188 cbor_encode_text(w, "type");
3189 cbor_encode_text(w, "public-key");
3190
3191 // publicKey (COSE_Key, RFC 8152)
3193 if (info.curve == CDC_CURVE_ED25519) {
3195 } else {
3196 cbor_encode_cose_key_p256(w, pubkey, pubkey + 32);
3197 }
3198
3199 // totalCredentials (only in first response)
3200 if (include_total) {
3202 cbor_encode_uint(w, g_cred_mgmt.cred_count);
3203 }
3204
3205 // credProtect
3207 cbor_encode_uint(w, info.cred_protect ? info.cred_protect : 1);
3208
3209 return true;
3210}
3211
3220uint8_t ctap2_cred_management(const uint8_t *params, uint16_t params_len,
3221 uint8_t *response, uint16_t *response_len) {
3222 // Parse parameters
3223 if (params_len < 1) {
3224 response[0] = CTAP2_ERR_INVALID_CBOR;
3225 *response_len = 1;
3227 }
3228
3229 cbor_reader_t r;
3230 cbor_reader_init(&r, params, params_len);
3231
3232 int map_count = cbor_read_map(&r);
3233 if (map_count < 1) {
3234 response[0] = CTAP2_ERR_INVALID_CBOR;
3235 *response_len = 1;
3237 }
3238
3239 uint8_t subcommand = 0;
3240 uint8_t rp_id_hash[32] = {0};
3241 bool has_rp_id_hash = false;
3242 uint8_t cred_id[FIDO2_CRED_ID_LEN] = {0};
3243 uint16_t cred_id_len = 0;
3244 bool has_cred_id = false;
3245
3246 // Parse map entries
3247 for (int i = 0; i < map_count; i++) {
3248 uint64_t key;
3249 if (!cbor_read_uint(&r, &key)) {
3250 cbor_skip_item(&r);
3251 continue;
3252 }
3253
3254 switch (key) {
3256 {
3257 uint64_t cmd;
3258 if (cbor_read_uint(&r, &cmd)) {
3259 subcommand = (uint8_t)cmd;
3260 }
3261 }
3262 break;
3263
3265 {
3266 int sub_count = cbor_read_map(&r);
3267 for (int j = 0; j < sub_count; j++) {
3268 uint64_t sub_key;
3269 if (!cbor_read_uint(&r, &sub_key)) {
3270 cbor_skip_item(&r);
3271 cbor_skip_item(&r);
3272 continue;
3273 }
3274
3275 if (sub_key == CTAP2_CM_SUB_RP_ID_HASH) {
3276 size_t len;
3277 if (cbor_read_bytes(&r, rp_id_hash, 32, &len) && len == 32) {
3278 has_rp_id_hash = true;
3279 }
3280 } else if (sub_key == CTAP2_CM_SUB_CREDENTIAL_ID) {
3281 int cred_map = cbor_read_map(&r);
3282 for (int k = 0; k < cred_map; k++) {
3283 char cred_key[16];
3284 size_t key_len;
3285 if (cbor_read_text(&r, cred_key, sizeof(cred_key), &key_len)) {
3286 if (strcmp(cred_key, "id") == 0) {
3287 size_t len;
3288 if (cbor_read_bytes(&r, cred_id, FIDO2_CRED_ID_LEN, &len)) {
3289 cred_id_len = len;
3290 has_cred_id = true;
3291 }
3292 } else {
3293 cbor_skip_item(&r);
3294 }
3295 } else {
3296 cbor_skip_item(&r);
3297 cbor_skip_item(&r);
3298 }
3299 }
3300 } else {
3301 cbor_skip_item(&r);
3302 }
3303 }
3304 }
3305 break;
3306
3309 // We skip PIN auth verification for now
3310 // In production, should verify pinUvAuthParam
3311 cbor_skip_item(&r);
3312 break;
3313
3314 default:
3315 cbor_skip_item(&r);
3316 break;
3317 }
3318 }
3319
3320 LOG_I(TAG, "credMgmt subCmd=0x%02X", subcommand);
3321
3322 // CTAP2.1 6.8: credentialManagement requires a valid pinUvAuthToken. Block
3323 // unauthenticated enumeration/deletion of resident credentials.
3324 if (!g_client_pin.pin_token_valid) {
3325 response[0] = CTAP2_ERR_PIN_AUTH_INVALID;
3326 *response_len = 1;
3328 }
3329
3330 cbor_writer_t w;
3331 cbor_writer_init(&w, response + 1, *response_len - 1);
3332
3333 switch (subcommand) {
3335 {
3336 // Count resident credentials
3337 uint8_t existing = 0;
3338 for (uint8_t slot = 0; slot < FIDO2_MAX_CREDENTIALS; slot++) {
3339 if (fido2_storage_is_resident(slot)) existing++;
3340 }
3341
3342 cbor_encode_map(&w, 2);
3343
3344 // existingResidentCredentialsCount
3346 cbor_encode_uint(&w, existing);
3347
3348 // maxPossibleRemainingResidentCredentialsCount
3351
3352 LOG_I(TAG, "credMgmt metadata: %d existing, %d remaining",
3353 existing, FIDO2_MAX_CREDENTIALS - existing);
3354 }
3355 break;
3356
3358 {
3360 g_cred_mgmt.rp_index = 0;
3361
3362 if (g_cred_mgmt.rp_count == 0) {
3363 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3364 *response_len = 1;
3366 }
3367
3368 if (!cred_mgmt_encode_rp(&w, g_cred_mgmt.rp_slots[0], true)) {
3369 response[0] = CTAP2_ERR_OTHER;
3370 *response_len = 1;
3371 return CTAP2_ERR_OTHER;
3372 }
3373 g_cred_mgmt.rp_index = 1;
3374
3375 LOG_I(TAG, "credMgmt enumerateRPs: %d unique RPs", g_cred_mgmt.rp_count);
3376 }
3377 break;
3378
3380 {
3381 if (g_cred_mgmt.rp_index >= g_cred_mgmt.rp_count) {
3382 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3383 *response_len = 1;
3385 }
3386
3387 if (!cred_mgmt_encode_rp(&w, g_cred_mgmt.rp_slots[g_cred_mgmt.rp_index], false)) {
3388 response[0] = CTAP2_ERR_OTHER;
3389 *response_len = 1;
3390 return CTAP2_ERR_OTHER;
3391 }
3392 g_cred_mgmt.rp_index++;
3393 }
3394 break;
3395
3397 {
3398 if (!has_rp_id_hash) {
3399 response[0] = CTAP2_ERR_MISSING_PARAMETER;
3400 *response_len = 1;
3402 }
3403
3404 memcpy(g_cred_mgmt.current_rp_id_hash, rp_id_hash, 32);
3406 g_cred_mgmt.cred_index = 0;
3407
3408 if (g_cred_mgmt.cred_count == 0) {
3409 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3410 *response_len = 1;
3412 }
3413
3414 if (!cred_mgmt_encode_credential(&w, g_cred_mgmt.cred_slots[0], true)) {
3415 response[0] = CTAP2_ERR_OTHER;
3416 *response_len = 1;
3417 return CTAP2_ERR_OTHER;
3418 }
3419 g_cred_mgmt.cred_index = 1;
3420
3421 LOG_I(TAG, "credMgmt enumerateCreds: %d credentials for RP", g_cred_mgmt.cred_count);
3422 }
3423 break;
3424
3426 {
3427 if (g_cred_mgmt.cred_index >= g_cred_mgmt.cred_count) {
3428 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3429 *response_len = 1;
3431 }
3432
3433 if (!cred_mgmt_encode_credential(&w, g_cred_mgmt.cred_slots[g_cred_mgmt.cred_index], false)) {
3434 response[0] = CTAP2_ERR_OTHER;
3435 *response_len = 1;
3436 return CTAP2_ERR_OTHER;
3437 }
3438 g_cred_mgmt.cred_index++;
3439 }
3440 break;
3441
3443 {
3444 if (!has_cred_id) {
3445 response[0] = CTAP2_ERR_MISSING_PARAMETER;
3446 *response_len = 1;
3448 }
3449
3450 // Find credential by ID
3451 int8_t slot = fido2_storage_find_slot_by_cred_id(cred_id, cred_id_len);
3452 if (slot < 0) {
3453 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3454 *response_len = 1;
3456 }
3457
3458 // Delete it
3460 response[0] = CTAP2_ERR_OTHER;
3461 *response_len = 1;
3462 return CTAP2_ERR_OTHER;
3463 }
3464
3465 LOG_I(TAG, "credMgmt deleted credential slot %d", slot);
3466
3467 // Success - empty response
3468 response[0] = CTAP2_OK;
3469 *response_len = 1;
3470 return CTAP2_OK;
3471 }
3472
3473 default:
3474 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
3475 *response_len = 1;
3477 }
3478
3479 if (cbor_writer_error(&w)) {
3480 response[0] = CTAP2_ERR_OTHER;
3481 *response_len = 1;
3482 return CTAP2_ERR_OTHER;
3483 }
3484
3485 response[0] = CTAP2_OK;
3486 *response_len = 1 + cbor_writer_length(&w);
3487 return CTAP2_OK;
3488}
3489
3496uint8_t ctap2_selection(uint8_t *response, uint16_t *response_len) {
3497 // authenticatorSelection carries no RP context and requires user presence only
3498 if (!wait_for_user_presence(NULL, FIDO2_ACTION_SELECT, NULL)) {
3499 response[0] = CTAP2_ERR_OPERATION_DENIED;
3500 *response_len = 1;
3502 }
3503
3504 response[0] = CTAP2_OK;
3505 *response_len = 1;
3506 return CTAP2_OK;
3507}
3508
3513bool ctap2_init(void) {
3514 LOG_I(TAG, "Initializing...");
3515 memset(&g_ctap2, 0, sizeof(g_ctap2));
3516 g_ctap2.initialized = true;
3517 LOG_I(TAG, "Initialized");
3518 return true;
3519}
3520
3529uint8_t ctap2_process_command(const uint8_t *cmd, uint16_t cmd_len,
3530 uint8_t *response, uint16_t *response_len) {
3531 if (!g_ctap2.initialized || cmd_len < 1) {
3532 response[0] = CTAP1_ERR_INVALID_COMMAND;
3533 *response_len = 1;
3535 }
3536
3537 uint8_t command = cmd[0];
3538 const uint8_t *params = cmd + 1;
3539 uint16_t params_len = cmd_len - 1;
3540
3541 // Always log command type (helpful for debugging protocol issues)
3542 const char *cmd_name = "?";
3543 switch (command) {
3544 case CTAP2_CMD_MAKE_CREDENTIAL: cmd_name = "makeCredential"; break;
3545 case CTAP2_CMD_GET_ASSERTION: cmd_name = "getAssertion"; break;
3546 case CTAP2_CMD_GET_INFO: cmd_name = "getInfo"; break;
3547 case CTAP2_CMD_CLIENT_PIN: cmd_name = "clientPIN"; break;
3548 case CTAP2_CMD_RESET: cmd_name = "reset"; break;
3549 case CTAP2_CMD_GET_NEXT_ASSERTION: cmd_name = "getNextAssertion"; break;
3550 case CTAP2_CMD_CRED_MANAGEMENT: cmd_name = "credMgmt"; break;
3551 case CTAP2_CMD_SELECTION: cmd_name = "selection"; break;
3552 }
3553 LOG_I(TAG, "CMD 0x%02X (%s) %d bytes", command, cmd_name, params_len);
3554
3555 g_ctap2.operation_pending = true;
3556 g_ctap2.cancelled = false;
3557
3558 uint8_t status;
3559 switch (command) {
3560 case CTAP2_CMD_GET_INFO:
3561 status = ctap2_get_info(response, response_len);
3562 break;
3563
3565 status = ctap2_make_credential(params, params_len, response, response_len);
3566 break;
3567
3569 status = ctap2_get_assertion(params, params_len, response, response_len);
3570 break;
3571
3573 status = ctap2_get_next_assertion(response, response_len);
3574 break;
3575
3577 status = ctap2_client_pin(params, params_len, response, response_len);
3578 break;
3579
3580 case CTAP2_CMD_RESET:
3581 status = ctap2_reset(response, response_len);
3582 break;
3583
3585 status = ctap2_cred_management(params, params_len, response, response_len);
3586 break;
3587
3589 status = ctap2_selection(response, response_len);
3590 break;
3591
3593 case CTAP2_CMD_CONFIG:
3594 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
3595 *response_len = 1;
3597 break;
3598
3599 default:
3600 response[0] = CTAP1_ERR_INVALID_COMMAND;
3601 *response_len = 1;
3603 break;
3604 }
3605
3606 g_ctap2.operation_pending = false;
3607 return status;
3608}
3609
3614void ctap2_send_keepalive(uint8_t status) {
3615 uint32_t cid = ctaphid_get_current_cid();
3616 if (cid != 0) {
3617 ctaphid_send_keepalive(cid, status);
3618 }
3619}
3620
3624void ctap2_cancel(void) {
3625 g_ctap2.cancelled = true;
3626 if (CTAP2_DEBUG_COMMANDS) LOG_D(TAG, "Operation cancelled");
3627}
3628
3635 g_ctap2.cancelled = false;
3636}
3637
3642 return g_ctap2.cancelled;
3643}
static const char * TAG
uint8_t flags
void cbor_encode_cose_key_p256(cbor_writer_t *w, const uint8_t *x, const uint8_t *y)
Encodes COSE P-256 public key map.
void cbor_encode_uint(cbor_writer_t *w, uint64_t value)
Encodes CBOR unsigned integer.
void cbor_encode_bool(cbor_writer_t *w, bool value)
Encodes CBOR boolean.
void cbor_encode_cose_key_ed25519(cbor_writer_t *w, const uint8_t *pubkey)
Encodes COSE Ed25519 public key map.
void cbor_writer_init(cbor_writer_t *w, uint8_t *buffer, size_t size)
CBOR writer implementation.
void cbor_encode_text(cbor_writer_t *w, const char *str)
Encodes CBOR text string.
size_t cbor_writer_length(const cbor_writer_t *w)
Returns number of bytes written by CBOR writer.
bool cbor_writer_error(const cbor_writer_t *w)
Returns whether writer encountered an error.
void cbor_encode_bytes(cbor_writer_t *w, const uint8_t *data, size_t len)
Encodes CBOR byte-string.
void cbor_encode_array(cbor_writer_t *w, size_t count)
Encodes CBOR array header.
void cbor_encode_int(cbor_writer_t *w, int64_t value)
Encodes CBOR signed integer.
void cbor_encode_map(cbor_writer_t *w, size_t count)
Encodes CBOR map header.
void cbor_reader_init(cbor_reader_t *r, const uint8_t *data, size_t size)
CBOR reader implementation.
#define CBOR_UNSIGNED
bool cbor_read_text(cbor_reader_t *r, char *out, size_t max_len, size_t *out_len)
Reads CBOR text string into output buffer.
int cbor_read_map(cbor_reader_t *r)
Reads CBOR map header and returns pair count.
int cbor_read_array(cbor_reader_t *r)
Reads CBOR array header and returns element count.
bool cbor_skip_item(cbor_reader_t *r)
Skips one complete CBOR item including nested container content.
#define CBOR_NEGATIVE
bool cbor_read_bool(cbor_reader_t *r, bool *value)
Reads CBOR boolean simple value.
bool cbor_read_item(cbor_reader_t *r, cbor_item_t *item)
Reads next CBOR item metadata and optional inline payload pointer.
bool cbor_read_int(cbor_reader_t *r, int64_t *value)
Reads CBOR integer (positive or negative).
bool cbor_read_uint(cbor_reader_t *r, uint64_t *value)
Reads CBOR unsigned integer.
bool cbor_read_bytes(cbor_reader_t *r, uint8_t *out, size_t max_len, size_t *out_len)
Reads CBOR byte-string into optional output buffer.
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 PIN_CMD_GET_PIN_TOKEN
Definition ctap2.cpp:88
void sha256_str(const char *str, uint8_t out[32])
bool pin_token_valid
Definition ctap2.cpp:108
uint8_t assertion_count
Definition ctap2.cpp:67
static bool cred_mgmt_encode_rp(cbor_writer_t *w, uint8_t slot, bool include_total)
Encodes a credential-management RP response entry.
Definition ctap2.cpp:3126
uint8_t ctap2_reset(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorReset (0x07).
Definition ctap2.cpp:3027
bool assertion_appid_used
Definition ctap2.cpp:73
static uint16_t ctap2_build_cred_protect_extension(uint8_t level, uint8_t *out, size_t out_size)
Builds CBOR payload for the credProtect extension.
Definition ctap2.cpp:234
static void ga_find_credentials(GetAssertionParams *p, AssertionCredentials *creds)
Finds credentials matching RP/allowList and appid extension rules.
Definition ctap2.cpp:1656
uint8_t rp_slots[32]
Definition ctap2.cpp:130
static constexpr uint64_t CTAP2_INFO_PIN_UV_AUTH_PROTOCOL_VALUE
Reported PIN/UV auth protocol version (Protocol Two).
Definition ctap2.cpp:548
uint8_t cred_index
Definition ctap2.cpp:137
#define PIN_CMD_GET_PIN_UV_TOKEN
Definition ctap2.cpp:89
bool assertion_up_done
Definition ctap2.cpp:71
uint8_t ctap2_client_pin(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorClientPIN (0x06).
Definition ctap2.cpp:2948
static bool cred_mgmt_slot_has_key(uint8_t slot)
Credential-management helper and command implementation.
Definition ctap2.cpp:3052
static void encode_info_transports(cbor_writer_t *w)
Encodes the supported transports list.
Definition ctap2.cpp:622
uint8_t rp_count
Definition ctap2.cpp:131
void ctap2_cancel(void)
Marks current CTAP2 operation as cancelled.
Definition ctap2.cpp:3624
uint8_t ctap2_process_command(const uint8_t *cmd, uint16_t cmd_len, uint8_t *response, uint16_t *response_len)
Dispatches one CTAP2 command and writes response payload.
Definition ctap2.cpp:3529
mbedtls_ecp_keypair ecdh_key
Definition ctap2.cpp:103
void ctap2_clear_cancel(void)
Clears any latched cancel flag. Called at the start of a new CTAPHID channel (INIT) so a cancel from ...
Definition ctap2.cpp:3634
uint8_t cred_slots[32]
Definition ctap2.cpp:135
#define PIN_CMD_GET_KEY_AGREEMENT
Definition ctap2.cpp:85
static uint8_t ga_build_response(const uint8_t *cred_id, const uint8_t *auth_data, uint16_t auth_data_len, const uint8_t *signature, uint8_t sig_len, const fido2_credential_info_t *cred, bool include_user, uint8_t total_creds, uint8_t *response, uint16_t *response_len)
Builds CBOR response payload for getAssertion/getNextAssertion.
Definition ctap2.cpp:1762
static const uint8_t AAGUID[16]
Authenticator Attestation GUID for this authenticator model.
Definition ctap2.cpp:45
static bool client_pin_compute_shared_secret(const uint8_t *platform_key_x, const uint8_t *platform_key_y, uint8_t pin_protocol, uint8_t *shared_secret)
Computes ClientPIN shared secret from platform ECDH public key.
Definition ctap2.cpp:2102
static void encode_info_versions(cbor_writer_t *w)
Encodes the supported FIDO/U2F versions into the getInfo CBOR map.
Definition ctap2.cpp:553
uint8_t assertion_creds[32]
Definition ctap2.cpp:66
static void encode_info_options(cbor_writer_t *w)
Encodes the supported authenticator options, keys sorted by length.
Definition ctap2.cpp:577
static void encode_info_max_msg_size(cbor_writer_t *w)
Encodes the maxMsgSize entry into the getInfo CBOR map.
Definition ctap2.cpp:597
static bool wait_for_user_presence(const char *rp_id, fido2_action_t action, const char *user_name)
Requests user-presence confirmation through platform callback.
Definition ctap2.cpp:517
uint8_t pin_token[32]
Definition ctap2.cpp:107
static uint8_t build_authenticator_data(const uint8_t *rp_id_hash, uint8_t flags, uint32_t sign_count, const uint8_t *attested_cred_data, uint16_t attested_cred_len, const uint8_t *ext_data, uint16_t ext_len, uint8_t *out, uint16_t *out_len)
Builds raw authenticatorData structure.
Definition ctap2.cpp:466
static void secure_random_fill(uint8_t *out, size_t len)
Fills a buffer with cryptographically secure random bytes.
Definition ctap2.cpp:146
static uint8_t ga_parse_extensions(cbor_reader_t *r, GetAssertionParams *p)
Parses getAssertion extensions (map key 0x04).
Definition ctap2.cpp:1446
static uint8_t cred_mgmt_find_creds_for_rp(const uint8_t *rp_id_hash)
Collects resident credentials for the given RP ID hash.
Definition ctap2.cpp:3101
static uint8_t client_pin_get_retries(uint8_t *response, uint16_t *response_len)
Handles ClientPIN subcommand getPINRetries (0x01).
Definition ctap2.cpp:2318
bool ctap2_init(void)
Initializes CTAP2 runtime state.
Definition ctap2.cpp:3513
uint8_t token_permissions
Definition ctap2.cpp:111
static struct @345050366056176050043354151136135170030316236203 g_client_pin
static bool ctap2_build_auth_data_for_cred(const uint8_t *rp_id_hash, const uint8_t *attested_cred, uint16_t attested_len, uint8_t cred_protect, uint8_t *auth_data, uint16_t *auth_data_len)
Builds authenticator data for makeCredential with optional credProtect extension.
Definition ctap2.cpp:257
static uint8_t client_pin_get_pin_token(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles ClientPIN subcommand getPinToken (0x05).
Definition ctap2.cpp:2406
static bool aes_256_cbc_encrypt(const uint8_t *key, const uint8_t *input, size_t len, uint8_t *output)
Encrypts Protocol-1 PIN payload (AES-256-CBC with zero IV).
Definition ctap2.cpp:2262
#define CRED_MGMT_ENUMERATE_RPS_GET_NEXT
Definition ctap2.cpp:123
#define PIN_PROTOCOL_VERSION
ClientPIN constants and state for PIN protocol support.
Definition ctap2.cpp:78
static uint8_t client_pin_get_pin_uv_auth_token(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles ClientPIN subcommand getPinUvAuthTokenUsingPinWithPermissions (0x09).
Definition ctap2.cpp:2719
uint8_t ctap2_selection(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorSelection (0x0B).
Definition ctap2.cpp:3496
bool token_rp_id_set
Definition ctap2.cpp:113
uint8_t rp_index
Definition ctap2.cpp:132
#define PIN_TOKEN_SIZE
Definition ctap2.cpp:79
uint8_t pin_retries
Definition ctap2.cpp:116
uint8_t cred_count
Definition ctap2.cpp:136
bool ctap2_is_cancelled(void)
Returns true if the current CTAP2 operation has been cancelled.
Definition ctap2.cpp:3641
static constexpr uint64_t CTAP2_INFO_MAX_CRED_LIST_COUNT_VALUE
Reported maxCredentialCountInList for authenticatorGetInfo.
Definition ctap2.cpp:550
uint8_t assertion_rp_id_hash[32]
Definition ctap2.cpp:69
static uint16_t ctap2_build_appid_extension(uint8_t *out, size_t out_size)
Builds CBOR payload for appid extension in assertions.
Definition ctap2.cpp:294
static bool client_pin_init_ecdh(void)
ClientPIN command implementation helpers.
Definition ctap2.cpp:2074
#define CRED_MGMT_GET_CREDS_METADATA
Credential management constants and enumeration state.
Definition ctap2.cpp:121
uint8_t current_rp_id_hash[32]
Definition ctap2.cpp:138
static uint8_t cred_mgmt_count_unique_rps(void)
Counts unique RP IDs among resident credentials.
Definition ctap2.cpp:3065
#define PIN_UV_RETRIES_MAX
Definition ctap2.cpp:81
void sha256(const uint8_t *data, size_t len, uint8_t out[32])
static struct @363146237155063244362205253337222300366302103074 g_ctap2
Global CTAP2 runtime state.
static uint8_t ga_parse_allow_list(cbor_reader_t *r, GetAssertionParams *p)
Parses getAssertion allowList (map key 0x03).
Definition ctap2.cpp:1403
static struct @074350050112271276332254352137370356012162354162 g_cred_mgmt
#define CRED_MGMT_ENUMERATE_CREDS_BEGIN
Definition ctap2.cpp:124
bool ecdh_valid
Definition ctap2.cpp:104
#define PIN_RETRIES_MAX
Definition ctap2.cpp:80
static constexpr uint64_t CTAP2_INFO_MAX_MSG_SIZE_VALUE
Reported maximum message size for authenticatorGetInfo.
Definition ctap2.cpp:546
static int ctap2_random(void *ctx, unsigned char *out, size_t len)
mbedTLS RNG callback backed by secure random source.
Definition ctap2.cpp:173
#define CRED_MGMT_ENUMERATE_CREDS_GET_NEXT
Definition ctap2.cpp:125
static const char * TAG_PIN
Definition ctap2.cpp:34
uint8_t token_rp_id_hash[32]
Definition ctap2.cpp:112
static bool ctap2_build_attested_cred(const uint8_t *cred_id, uint16_t cred_id_len, const uint8_t *pubkey, uint8_t curve, uint8_t *out, size_t out_size, uint16_t *out_len)
Builds attested credential data (AAGUID, credential ID, COSE key).
Definition ctap2.cpp:191
static bool aes_256_cbc_encrypt_p2(const uint8_t *key, const uint8_t *input, size_t len, uint8_t *output)
Encrypts Protocol-2 PIN payload and prefixes random IV (IV || ciphertext).
Definition ctap2.cpp:2288
static uint8_t ga_sign_assertion(uint8_t slot, const uint8_t *auth_data, uint16_t auth_data_len, const uint8_t *client_data_hash, uint8_t *signature, uint8_t *sig_len)
Signs assertion message (authData || clientDataHash) for one credential slot.
Definition ctap2.cpp:1727
static uint8_t ga_parse_params(const uint8_t *params, uint16_t params_len, GetAssertionParams *p)
Parses complete getAssertion request map from CBOR payload.
Definition ctap2.cpp:1513
static bool cred_mgmt_encode_credential(cbor_writer_t *w, uint8_t slot, bool include_total)
Encodes a credential-management credential response entry.
Definition ctap2.cpp:3159
static void encode_info_pin_uv_auth_protocols(cbor_writer_t *w)
Encodes the supported pinUvAuthProtocols list.
Definition ctap2.cpp:603
static void encode_info_aaguid(cbor_writer_t *w)
Encodes the authenticator AAGUID into the getInfo CBOR map.
Definition ctap2.cpp:571
void ctap2_send_keepalive(uint8_t status)
Sends CTAPHID keepalive for currently active channel.
Definition ctap2.cpp:3614
uint8_t ctap2_get_assertion(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorGetAssertion (0x02).
Definition ctap2.cpp:1832
#define PIN_CMD_GET_RETRIES
ClientPIN subcommand identifiers.
Definition ctap2.cpp:84
static bool ctap2_sign_with_keypair(mbedtls_ecp_keypair *key, const uint8_t *msg, size_t msg_len, uint8_t *sig, size_t sig_size, size_t *sig_len)
Signs message using provided keypair (ECDSA over SHA-256).
Definition ctap2.cpp:429
uint8_t ctap2_cred_management(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorCredentialManagement (0x0A).
Definition ctap2.cpp:3220
uint8_t assertion_index
Definition ctap2.cpp:68
static void encode_info_algorithms(cbor_writer_t *w)
Encodes the supported algorithms array (PublicKeyCredentialParameters).
Definition ctap2.cpp:629
static bool aes_256_cbc_decrypt_iv(const uint8_t *key, const uint8_t *iv, const uint8_t *input, size_t len, uint8_t *output)
Decrypts data using AES-256-CBC with caller-provided IV.
Definition ctap2.cpp:2221
#define CRED_MGMT_DELETE_CREDENTIAL
Definition ctap2.cpp:126
static bool ctap2_generate_ephemeral_keypair(mbedtls_ecp_keypair *key, uint8_t pubkey[64])
Generates ephemeral P-256 key pair and exports 64-byte X||Y public key.
Definition ctap2.cpp:385
#define CTAP2_DEBUG_COMMANDS
Definition ctap2.cpp:41
uint8_t assertion_client_data_hash[32]
Definition ctap2.cpp:70
static uint8_t ga_verify_pin_auth(const GetAssertionParams *p, bool *uv_verified)
Verifies getAssertion pinUvAuthParam via HMAC.
Definition ctap2.cpp:1598
static uint8_t ctap2_build_make_credential_response_packed(const uint8_t *auth_data, uint16_t auth_data_len, const uint8_t *sig, uint8_t sig_len, const uint8_t *cert, uint16_t cert_len, uint8_t *response, uint16_t *response_len)
Builds packed-attestation makeCredential response CBOR payload.
Definition ctap2.cpp:318
#define PIN_CMD_CHANGE_PIN
Definition ctap2.cpp:87
#define CRED_MGMT_ENUMERATE_RPS_BEGIN
Definition ctap2.cpp:122
static void encode_info_extensions(cbor_writer_t *w)
Encodes the supported CTAP extensions, sorted for CBOR canonical form.
Definition ctap2.cpp:562
static uint8_t client_pin_get_key_agreement(uint8_t *response, uint16_t *response_len)
Handles ClientPIN subcommand getKeyAgreement (0x02).
Definition ctap2.cpp:2343
static void encode_info_max_cred_id_length(cbor_writer_t *w)
Encodes the maxCredentialIdLength entry.
Definition ctap2.cpp:616
bool cancelled
Definition ctap2.cpp:63
#define CTAP2_ECP_Q(k)
uint8_t uv_retries
Definition ctap2.cpp:117
static bool aes_256_cbc_decrypt(const uint8_t *key, const uint8_t *input, size_t len, uint8_t *output)
Decrypts Protocol-1 PIN payload (AES-256-CBC with zero IV).
Definition ctap2.cpp:2248
static void encode_info_max_cred_count(cbor_writer_t *w)
Encodes the maxCredentialCountInList entry.
Definition ctap2.cpp:610
uint8_t ctap2_get_next_assertion(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorGetNextAssertion (0x08).
Definition ctap2.cpp:1973
bool initialized
Definition ctap2.cpp:61
bool assertion_include_user
Definition ctap2.cpp:72
#define PIN_CMD_SET_PIN
Definition ctap2.cpp:86
static bool ga_parse_allow_list_credential(cbor_reader_t *r, uint8_t *cred_id, size_t *cred_id_len)
Parses one allowList credential descriptor and extracts credential ID.
Definition ctap2.cpp:1367
uint8_t ctap2_get_info(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorGetInfo (0x04).
Definition ctap2.cpp:667
#define CTAP2_ECP_GRP(k)
static const char * INFO_TRANSPORTS[]
Device info strings reported by authenticatorGetInfo.
Definition ctap2.cpp:54
static uint8_t ga_parse_options(cbor_reader_t *r, GetAssertionParams *p)
Parses getAssertion options (map key 0x05).
Definition ctap2.cpp:1481
bool operation_pending
Definition ctap2.cpp:62
#define CTAP2_MC_RESP_AUTH_DATA
Definition ctap2.h:150
#define CTAP1_ERR_INVALID_PARAMETER
Definition ctap2.h:38
#define CTAP2_PIN_PROTOCOL
Definition ctap2.h:170
#define CTAP2_INFO_ALGORITHMS
Definition ctap2.h:135
#define CTAP2_CMD_GET_NEXT_ASSERTION
Definition ctap2.h:22
#define CTAP2_MC_PUB_KEY_CRED_PARAMS
Definition ctap2.h:141
#define CTAP2_INFO_MAX_CRED_ID_LENGTH
Definition ctap2.h:133
#define CTAP2_GA_ALLOW_LIST
Definition ctap2.h:156
#define CTAP2_CM_RESP_RP
Definition ctap2.h:199
#define CTAP2_CMD_RESET
Definition ctap2.h:21
#define CTAP2_OK
Definition ctap2.h:36
#define CTAP2_GA_RESP_AUTH_DATA
Definition ctap2.h:164
#define CTAP2_CMD_GET_INFO
Definition ctap2.h:19
#define CTAP2_CM_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:189
#define CTAP2_GA_RESP_USER
Definition ctap2.h:166
#define COSE_KEY_LABEL_ALG
Definition ctap2.h:98
#define CTAP2_MC_RP
Definition ctap2.h:139
#define CTAP2_CMD_CLIENT_PIN
Definition ctap2.h:20
#define CTAP2_MC_CLIENT_DATA_HASH
Definition ctap2.h:138
#define CTAP2_CMD_CONFIG
Definition ctap2.h:26
#define CTAP2_ERR_UNSUPPORTED_OPTION
Definition ctap2.h:60
#define CTAP2_INFO_MAX_MSG_SIZE
Definition ctap2.h:130
#define CTAP2_ERR_KEY_STORE_FULL
Definition ctap2.h:58
#define CTAP2_CM_SUB_RP_ID_HASH
Definition ctap2.h:193
#define CTAP2_INFO_PIN_UV_AUTH_PROTOCOLS
Definition ctap2.h:131
#define CTAP2_CM_RESP_PUBLIC_KEY
Definition ctap2.h:204
#define CTAP2_MC_RESP_FMT
Definition ctap2.h:149
#define COSE_ALG_ECDH_ES_HKDF_256
Definition ctap2.h:87
#define COSE_KEY_LABEL_CRV
Definition ctap2.h:101
#define CTAP2_MC_EXTENSIONS
Definition ctap2.h:143
#define CTAP2_PIN_RESP_PIN_RETRIES
Definition ctap2.h:182
#define CTAP2_GA_OPTIONS
Definition ctap2.h:158
#define CTAP2_ERR_PIN_NOT_SET
Definition ctap2.h:70
#define CTAP2_GA_CLIENT_DATA_HASH
Definition ctap2.h:155
#define CTAP2_MC_OPTIONS
Definition ctap2.h:144
#define CTAP2_INFO_EXTENSIONS
Definition ctap2.h:127
#define CTAP2_CM_RESP_CREDENTIAL_ID
Definition ctap2.h:203
#define CTAP2_CM_RESP_EXISTING_CRED_COUNT
Definition ctap2.h:197
#define CTAP2_PIN_PERMISSIONS_RPID
Definition ctap2.h:177
#define CTAP2_GA_RP_ID
Definition ctap2.h:154
#define CTAP2_CM_RESP_USER
Definition ctap2.h:202
#define CTAP2_PIN_RESP_UV_RETRIES
Definition ctap2.h:184
#define CTAP2_ERR_OPERATION_DENIED
Definition ctap2.h:57
#define CTAP2_ERR_PIN_BLOCKED
Definition ctap2.h:67
#define CTAP2_CMD_GET_ASSERTION
Definition ctap2.h:18
#define CTAP2_CMD_CRED_MANAGEMENT
Definition ctap2.h:23
#define CTAP2_INFO_TRANSPORTS
Definition ctap2.h:134
#define COSE_CRV_P256
Definition ctap2.h:112
#define CTAP2_CM_RESP_REMAINING_CRED_COUNT
Definition ctap2.h:198
#define CTAP2_CM_RESP_TOTAL_CREDENTIALS
Definition ctap2.h:205
#define CTAP1_ERR_INVALID_COMMAND
Definition ctap2.h:37
#define COSE_KEY_TYPE_EC2
Definition ctap2.h:108
#define CTAP2_CMD_LARGE_BLOBS
Definition ctap2.h:25
#define CTAP2_ERR_PIN_AUTH_INVALID
Definition ctap2.h:68
#define CTAP2_INFO_MAX_CRED_COUNT_IN_LIST
Definition ctap2.h:132
#define CTAP2_INFO_AAGUID
Definition ctap2.h:128
#define CTAP2_ERR_INVALID_OPTION
Definition ctap2.h:61
#define CTAP2_CMD_MAKE_CREDENTIAL
Definition ctap2.h:17
#define CTAP2_ERR_NO_CREDENTIALS
Definition ctap2.h:63
#define CTAP2_GA_PIN_UV_AUTH_PARAM
Definition ctap2.h:159
#define CTAP2_CM_SUBCOMMAND
Definition ctap2.h:187
#define CTAP2_ERR_PIN_INVALID
Definition ctap2.h:66
#define CTAP2_PIN_HASH_ENC
Definition ctap2.h:175
#define CTAP2_CM_PIN_UV_AUTH_PARAM
Definition ctap2.h:190
#define CTAP2_ERR_CREDENTIAL_EXCLUDED
Definition ctap2.h:50
#define CTAP2_CM_RESP_TOTAL_RPS
Definition ctap2.h:201
#define CTAP2_CM_SUB_CREDENTIAL_ID
Definition ctap2.h:194
#define CTAP2_ERR_OTHER
Definition ctap2.h:78
#define COSE_KEY_LABEL_X
Definition ctap2.h:102
void ctap2_send_keepalive(uint8_t status)
Sends CTAPHID keepalive for currently active channel.
Definition ctap2.cpp:3614
uint8_t ctap2_make_credential(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
#define CTAP2_CM_SUBCOMMAND_PARAMS
Definition ctap2.h:188
#define CTAP2_ERR_MISSING_PARAMETER
Definition ctap2.h:47
#define CTAP2_CM_RESP_RP_ID_HASH
Definition ctap2.h:200
#define CTAP2_PIN_PERMISSIONS
Definition ctap2.h:176
#define CTAP2_GA_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:160
#define CTAP2_MC_RESP_ATT_STMT
Definition ctap2.h:151
#define CTAP2_GA_EXTENSIONS
Definition ctap2.h:157
#define CTAP2_ERR_INVALID_CBOR
Definition ctap2.h:46
#define CTAP2_MC_USER
Definition ctap2.h:140
#define CTAP2_CM_RESP_CRED_PROTECT
Definition ctap2.h:206
#define CTAP2_INFO_OPTIONS
Definition ctap2.h:129
#define CTAP2_PIN_RESP_KEY_AGREEMENT
Definition ctap2.h:180
#define CTAP2_MC_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:146
#define CTAP2_PIN_RESP_PIN_TOKEN
Definition ctap2.h:181
#define CTAP2_GA_RESP_NUMBER_OF_CREDS
Definition ctap2.h:167
#define COSE_ALG_ES256
Definition ctap2.h:84
#define CTAP2_ERR_NOT_ALLOWED
Definition ctap2.h:65
#define CTAP2_INFO_VERSIONS
Definition ctap2.h:126
#define CTAP2_GA_RESP_CREDENTIAL
Definition ctap2.h:163
#define CTAP2_CMD_SELECTION
Definition ctap2.h:24
#define CTAP2_PIN_KEY_AGREEMENT
Definition ctap2.h:172
#define COSE_KEY_LABEL_Y
Definition ctap2.h:103
#define CTAP2_GA_RESP_SIGNATURE
Definition ctap2.h:165
#define CTAP2_ERR_UNSUPPORTED_ALGORITHM
Definition ctap2.h:56
#define COSE_KEY_LABEL_KTY
Definition ctap2.h:96
#define COSE_ALG_EDDSA
Definition ctap2.h:85
#define CTAP2_MC_PIN_UV_AUTH_PARAM
Definition ctap2.h:145
uint32_t ctaphid_get_current_cid(void)
Returns the channel identifier of the currently processed request.
Definition ctaphid.cpp:753
#define CTAPHID_STATUS_PROCESSING
Definition ctaphid.h:47
#define CTAPHID_STATUS_UPNEEDED
Definition ctaphid.h:48
void ctaphid_send_keepalive(uint32_t cid, uint8_t status)
Sends a CTAPHID KEEPALIVE packet immediately over USB.
Definition ctaphid.cpp:689
bool pin_verified
Definition fido2.cpp:34
#define CDC_CURVE_ED25519
Definition fido2.h:23
#define FIDO2_MAX_CREDENTIALS
Definition fido2.h:16
#define CDC_CURVE_P256
Definition fido2.h:24
void fido2_set_pin_verified(bool verified)
Stores whether PIN verification was completed via ClientPIN.
Definition fido2.cpp:194
#define FIDO2_CRED_ID_LEN
Definition fido2.h:20
#define FIDO2_RP_ID_MAX_LEN
Definition fido2.h:17
#define FIDO2_USER_NAME_MAX_LEN
Definition fido2.h:19
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_DENIED
Definition fido2.h:33
@ FIDO2_UP_TIMEOUT
Definition fido2.h:34
@ FIDO2_UP_APPROVED
Definition fido2.h:32
bool fido2_factory_reset(void)
Removes all credentials and resets FIDO2 data.
Definition fido2.cpp:265
bool fido2_is_pin_verified(void)
Returns current PIN-verified state.
Definition fido2.cpp:205
#define FIDO2_USER_ID_MAX_LEN
Definition fido2.h:18
fido2_action_t
Definition fido2.h:37
@ FIDO2_ACTION_SELECT
Definition fido2.h:40
@ FIDO2_ACTION_REGISTER
Definition fido2.h:38
@ FIDO2_ACTION_OVERWRITE
Definition fido2.h:41
@ FIDO2_ACTION_AUTHENTICATE
Definition fido2.h:39
uint8_t cred_protect
uint8_t curve
struct @262231322003320050276064353325174062307231151161::@131310070117174352112321004206244146355206237313 creds[FIDO2_MAX_CREDENTIALS]
uint32_t sign_count
char rp_id[FIDO2_RP_ID_MAX_LEN]
uint8_t rp_id_hash[32]
char user_name[FIDO2_USER_NAME_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.
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.
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.
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_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.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
void sha256_str(const char *str, uint8_t out[32])
static uint8_t parse_make_credential_params(const uint8_t *data, uint16_t data_len, MakeCredentialParams *p)
Parses complete makeCredential request map from CBOR payload.
Definition ctap2.cpp:901
static bool parse_options_map(cbor_reader_t *r, MakeCredentialParams *p)
Parses makeCredential options map from CBOR.
Definition ctap2.cpp:870
static uint8_t mc_rollback_credential(uint8_t slot, uint8_t *response, uint16_t *response_len)
Deletes a just-created credential and reports CTAP2_ERR_OTHER.
Definition ctap2.cpp:1127
uint8_t ctap2_make_credential(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorMakeCredential (0x01).
Definition ctap2.cpp:1241
static bool is_browser_probe(const char *rp_id)
Detects known browser probe RP IDs.
Definition ctap2.cpp:1116
static uint8_t create_credential_and_respond(const MakeCredentialParams *p, uint8_t curve, uint8_t *response, uint16_t *response_len)
Creates credential, signs attestation statement, and builds response.
Definition ctap2.cpp:1143
static bool parse_rp_map(cbor_reader_t *r, MakeCredentialParams *p)
Parses the RP map from a makeCredential CBOR request.
Definition ctap2.cpp:739
static bool parse_user_map(cbor_reader_t *r, MakeCredentialParams *p)
Parses the user map from a makeCredential CBOR request.
Definition ctap2.cpp:768
void sha256(const uint8_t *data, size_t len, uint8_t out[32])
static bool parse_extensions_map(cbor_reader_t *r, MakeCredentialParams *p)
Parses makeCredential extensions map from CBOR.
Definition ctap2.cpp:835
static bool parse_pubkey_cred_params(cbor_reader_t *r, MakeCredentialParams *p)
Parses pubKeyCredParams and selects a supported algorithm.
Definition ctap2.cpp:800
static uint8_t verify_pin_uv_auth(const MakeCredentialParams *p)
Verifies pinUvAuthParam for makeCredential.
Definition ctap2.cpp:972
static uint8_t check_appid_exclude(const MakeCredentialParams *p)
Validates the appidExclude extension against existing credentials.
Definition ctap2.cpp:1009
static uint8_t handle_browser_probe(const MakeCredentialParams *p, uint8_t *response, uint16_t *response_len)
Handles browser probe RP IDs by returning a synthetic attested response.
Definition ctap2.cpp:1029
bool pin_storage_verify_fido2_hash(const uint8_t *hash_in)
bool pin_storage_fido2_available(void)
bool pin_storage_get_fido2_hash(uint8_t *hash_out)
Credential-selection result used to build assertion responses.
Definition ctap2.cpp:1348
Parsed parameters for authenticatorGetAssertion.
Definition ctap2.cpp:1320
Parsed parameters for authenticatorMakeCredential.
Definition ctap2.cpp:705
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
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