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"
12#include "mod_fido2/ctaphid.h"
13#include "mod_fido2/u2f.h"
14#include "cdc_log.h"
15#include "cdc_core/PinManager.h"
17#include <esp_system.h>
18#include <esp_random.h>
19#include <mbedtls/ecdsa.h>
20#include <mbedtls/ecp.h>
21#include <mbedtls/ecdh.h>
22#include <mbedtls/md.h>
23#include <mbedtls/sha256.h>
24#include <mbedtls/aes.h>
26#include <freertos/FreeRTOS.h>
27#include <freertos/task.h>
28#include <esp_attr.h>
29#include <string.h>
30
33
34static const char* TAG = "CTAP2";
35static const char* TAG_PIN = "PIN";
36
38#ifndef CTAP2_DEBUG
39#define CTAP2_DEBUG 0 // Verbose CBOR/response dumps
40#endif
41#ifndef CTAP2_DEBUG_COMMANDS
42#define CTAP2_DEBUG_COMMANDS 0 // Command logging
43#endif
44
46static const uint8_t AAGUID[16] = {
47 0xCD, 0xCB, 0xAD, 0x6E, // "CDCBAD6E"
48 0x39, 0xC3, // 39C3
49 0x00, 0x01, // Version 1
50 0xBA, 0xD6, 0xE0, 0x01, // "BADGE01"
51 0x00, 0x00, 0x00, 0x01 // Device type
52};
53
55static const char *INFO_TRANSPORTS[] = {"usb"};
56
57#define USER_PRESENCE_TIMEOUT_MS 30000 // 30 seconds for user to respond
58
60
61static struct {
65
66 // For getNextAssertion
75} g_ctap2 = {};
76
78
79#define PIN_PROTOCOL_VERSION 2
80#define PIN_TOKEN_SIZE 32
81#define PIN_RETRIES_MAX 8
82#define PIN_UV_RETRIES_MAX 3
83
85#define PIN_CMD_GET_RETRIES 0x01
86#define PIN_CMD_GET_KEY_AGREEMENT 0x02
87#define PIN_CMD_SET_PIN 0x03
88#define PIN_CMD_CHANGE_PIN 0x04
89#define PIN_CMD_GET_PIN_TOKEN 0x05
90#define PIN_CMD_GET_PIN_UV_TOKEN 0x09
91
93#define PIN_PERM_MAKE_CREDENTIAL 0x01 // mc
94#define PIN_PERM_GET_ASSERTION 0x02 // ga
95#define PIN_PERM_CRED_MGMT 0x04 // cm
96#define PIN_PERM_BIO_ENROLLMENT 0x08 // be
97#define PIN_PERM_LARGE_BLOB_WRITE 0x10 // lbw
98#define PIN_PERM_AUTHN_CONFIG 0x20 // acfg
99
100static struct {
101 bool initialized;
102
103 // ECDH key pair (generated on init, regenerated on reset)
104 mbedtls_ecp_keypair ecdh_key;
106
107 // PIN token (regenerated on each getPinToken)
110
111 // Token permissions (CTAP 2.1) - 0 means all permissions (legacy)
113 uint8_t token_rp_id_hash[32]; // RP restriction (if any)
115
116 // Retry counters
117 uint8_t pin_retries;
118 uint8_t uv_retries;
120
122#define CRED_MGMT_GET_CREDS_METADATA 0x01
123#define CRED_MGMT_ENUMERATE_RPS_BEGIN 0x02
124#define CRED_MGMT_ENUMERATE_RPS_GET_NEXT 0x03
125#define CRED_MGMT_ENUMERATE_CREDS_BEGIN 0x04
126#define CRED_MGMT_ENUMERATE_CREDS_GET_NEXT 0x05
127#define CRED_MGMT_DELETE_CREDENTIAL 0x06
128
129static struct {
130 // RP enumeration state
131 uint8_t rp_slots[FIDO2_MAX_CREDENTIALS]; // Slots with unique RPs
132 uint8_t rp_count; // Number of unique RPs
133 uint8_t rp_index; // Current enumeration index
134
135 // Credential enumeration state
136 uint8_t cred_slots[FIDO2_MAX_CREDENTIALS]; // Slots for current RP
137 uint8_t cred_count; // Number of credentials for RP
138 uint8_t cred_index; // Current enumeration index
139 uint8_t current_rp_id_hash[32]; // RP being enumerated
141
147static void secure_random_fill(uint8_t* out, size_t len) {
149 if (se && se->isSessionActive() && se->getRandom(out, static_cast<uint16_t>(len))) {
150 return;
151 }
152 esp_fill_random(out, len);
153}
154
155static uint8_t build_authenticator_data(
156 const uint8_t *rp_id_hash,
157 uint8_t flags,
158 uint32_t sign_count,
159 const uint8_t *attested_cred_data,
160 uint16_t attested_cred_len,
161 const uint8_t *ext_data,
162 uint16_t ext_len,
163 uint8_t *out,
164 uint16_t *out_len
165);
166
174static int ctap2_random(void *ctx, unsigned char *out, size_t len) {
175 (void)ctx;
176 // Use TROPIC01 TRNG (with ESP32 fallback)
177 secure_random_fill(out, len);
178 return 0;
179}
180
192static bool ctap2_build_attested_cred(const uint8_t *cred_id,
193 uint16_t cred_id_len,
194 const uint8_t *pubkey,
195 uint8_t curve,
196 uint8_t *out,
197 size_t out_size,
198 uint16_t *out_len) {
199 if (!out || !out_len) return false;
200
201 const size_t fixed_prefix = 16 + 2 + cred_id_len;
202 if (out_size < fixed_prefix) return false;
203
204 uint16_t off = 0;
205
206 memcpy(out + off, AAGUID, 16);
207 off += 16;
208
209 out[off++] = (cred_id_len >> 8) & 0xFF;
210 out[off++] = cred_id_len & 0xFF;
211
212 memcpy(out + off, cred_id, cred_id_len);
213 off += cred_id_len;
214
215 cbor_writer_t cose_w;
216 cbor_writer_init(&cose_w, out + off, out_size - off);
217 if (curve == CDC_CURVE_ED25519) {
218 cbor_encode_cose_key_ed25519(&cose_w, pubkey);
219 } else {
220 cbor_encode_cose_key_p256(&cose_w, pubkey, pubkey + 32);
221 }
222 off += cbor_writer_length(&cose_w);
223
224 *out_len = off;
225 return !cbor_writer_error(&cose_w);
226}
227
235static uint16_t ctap2_build_cred_protect_extension(uint8_t level, uint8_t *out, size_t out_size) {
236 if (level == 0 || !out || out_size < 20) return 0;
237 cbor_writer_t w;
238 cbor_writer_init(&w, out, out_size);
239 cbor_encode_map(&w, 1);
240 cbor_encode_text(&w, "credProtect");
241 cbor_encode_uint(&w, level);
242 if (cbor_writer_error(&w)) {
243 return 0;
244 }
245 return (uint16_t)cbor_writer_length(&w);
246}
247
259 const uint8_t *attested_cred,
260 uint16_t attested_len,
261 uint8_t cred_protect,
262 uint8_t *auth_data,
263 uint16_t *auth_data_len) {
264 // Flags: UP=0x01, UV=0x04, AT=0x40, ED=0x80
265 uint8_t flags = 0x01 | 0x40; // UP=1, AT=1
267 LOG_I(TAG, "Building authData: pin_verified=%d, cred_protect=%u", pin_verified, cred_protect);
268 if (pin_verified) {
269 flags |= 0x04; // UV=1 when PIN was verified
270 LOG_I(TAG, "UV flag SET -> flags=0x%02X", flags);
271 }
272
273 // Build credProtect extension if requested
274 uint8_t ext_data[32];
275 uint16_t ext_len = 0;
276 if (cred_protect > 0) {
277 ext_len = ctap2_build_cred_protect_extension(cred_protect, ext_data, sizeof(ext_data));
278 if (ext_len > 0) {
279 LOG_I(TAG, "Including credProtect extension (level=%u, %u bytes)", cred_protect, ext_len);
280 }
281 }
282
284 attested_cred, attested_len,
285 ext_len > 0 ? ext_data : NULL, ext_len,
286 auth_data, auth_data_len) == CTAP2_OK;
287}
288
295static uint16_t ctap2_build_appid_extension(uint8_t *out, size_t out_size) {
296 cbor_writer_t w;
297 cbor_writer_init(&w, out, out_size);
298 cbor_encode_map(&w, 1);
299 cbor_encode_text(&w, "appid");
300 cbor_encode_bool(&w, true);
301 if (cbor_writer_error(&w)) {
302 return 0;
303 }
304 return (uint16_t)cbor_writer_length(&w);
305}
306
319static uint8_t ctap2_build_make_credential_response_packed(const uint8_t *auth_data,
320 uint16_t auth_data_len,
321 const uint8_t *sig,
322 uint8_t sig_len,
323 const uint8_t *cert,
324 uint16_t cert_len,
325 uint8_t *response,
326 uint16_t *response_len) {
327 cbor_writer_t w;
328 cbor_writer_init(&w, response + 1, *response_len - 1);
329
330 cbor_encode_map(&w, 3);
331
332 // fmt
334 if (sig_len == 0 && (cert == NULL || cert_len == 0)) {
335 // None attestation
336 cbor_encode_text(&w, "none");
337 } else {
338 cbor_encode_text(&w, "packed");
339 }
340
341 // authData
343 cbor_encode_bytes(&w, auth_data, auth_data_len);
344
345 // attStmt
347 if (sig_len == 0 && (cert == NULL || cert_len == 0)) {
348 // None attestation - empty map
349 cbor_encode_map(&w, 0);
350 } else if (cert && cert_len > 0) {
351 // Basic attestation with certificate
352 cbor_encode_map(&w, 3);
353 cbor_encode_text(&w, "alg");
355 cbor_encode_text(&w, "sig");
356 cbor_encode_bytes(&w, sig, sig_len);
357 cbor_encode_text(&w, "x5c");
358 cbor_encode_array(&w, 1); // Array with single certificate
359 cbor_encode_bytes(&w, cert, cert_len);
360 } else {
361 // Self attestation (no certificate)
362 cbor_encode_map(&w, 2);
363 cbor_encode_text(&w, "alg");
365 cbor_encode_text(&w, "sig");
366 cbor_encode_bytes(&w, sig, sig_len);
367 }
368
369 if (cbor_writer_error(&w)) {
370 response[0] = CTAP2_ERR_OTHER;
371 *response_len = 1;
372 return CTAP2_ERR_OTHER;
373 }
374
375 response[0] = CTAP2_OK;
376 *response_len = 1 + cbor_writer_length(&w);
377 return CTAP2_OK;
378}
379
386static bool ctap2_generate_ephemeral_keypair(mbedtls_ecp_keypair *key, uint8_t pubkey[64]) {
387 if (!key || !pubkey) return false;
388 mbedtls_ecp_keypair_init(key);
389
390 int rc = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, key, ctap2_random, NULL);
391 if (rc != 0) {
392 mbedtls_ecp_keypair_free(key);
393 return false;
394 }
395
396#if defined(MBEDTLS_PRIVATE)
397#define CTAP2_ECP_GRP(k) (k).MBEDTLS_PRIVATE(grp)
398#define CTAP2_ECP_Q(k) (k).MBEDTLS_PRIVATE(Q)
399#else
400#define CTAP2_ECP_GRP(k) (k).grp
401#define CTAP2_ECP_Q(k) (k).Q
402#endif
403
404 uint8_t buf[65];
405 size_t olen = 0;
406 rc = mbedtls_ecp_point_write_binary(&CTAP2_ECP_GRP((*key)), &CTAP2_ECP_Q((*key)),
407 MBEDTLS_ECP_PF_UNCOMPRESSED,
408 &olen, buf, sizeof(buf));
409#undef CTAP2_ECP_GRP
410#undef CTAP2_ECP_Q
411 if (rc != 0 || olen != sizeof(buf)) {
412 mbedtls_ecp_keypair_free(key);
413 return false;
414 }
415 memcpy(pubkey, buf + 1, 64);
416
417 return true;
418}
419
430static bool ctap2_sign_with_keypair(mbedtls_ecp_keypair *key,
431 const uint8_t *msg, size_t msg_len,
432 uint8_t *sig, size_t sig_size, size_t *sig_len) {
433 if (!key || !msg || !sig || !sig_len) return false;
434 uint8_t hash[32];
435 sha256(msg, msg_len, hash);
436
437 mbedtls_ecdsa_context ecdsa;
438 mbedtls_ecdsa_init(&ecdsa);
439 int rc = mbedtls_ecdsa_from_keypair(&ecdsa, key);
440 if (rc != 0) {
441 mbedtls_ecdsa_free(&ecdsa);
442 return false;
443 }
444
445 rc = mbedtls_ecdsa_write_signature(&ecdsa, MBEDTLS_MD_SHA256,
446 hash, sizeof(hash),
447 sig, sig_size, sig_len,
448 ctap2_random, NULL);
449 mbedtls_ecdsa_free(&ecdsa);
450
451 return rc == 0;
452}
453
468 const uint8_t *rp_id_hash,
469 uint8_t flags,
470 uint32_t sign_count,
471 const uint8_t *attested_cred_data,
472 uint16_t attested_cred_len,
473 const uint8_t *ext_data,
474 uint16_t ext_len,
475 uint8_t *out,
476 uint16_t *out_len
477) {
478 uint16_t offset = 0;
479
480 // RP ID hash (32 bytes)
481 memcpy(out + offset, rp_id_hash, 32);
482 offset += 32;
483
484 // Flags (1 byte)
485 if (ext_data && ext_len > 0) {
486 flags |= 0x80; // ED
487 }
488 out[offset++] = flags;
489
490 // Sign count (4 bytes, big endian)
491 out[offset++] = (sign_count >> 24) & 0xFF;
492 out[offset++] = (sign_count >> 16) & 0xFF;
493 out[offset++] = (sign_count >> 8) & 0xFF;
494 out[offset++] = sign_count & 0xFF;
495
496 // Attested credential data (if present)
497 if (attested_cred_data && attested_cred_len > 0) {
498 memcpy(out + offset, attested_cred_data, attested_cred_len);
499 offset += attested_cred_len;
500 }
501
502 if (ext_data && ext_len > 0) {
503 memcpy(out + offset, ext_data, ext_len);
504 offset += ext_len;
505 }
506
507 *out_len = offset;
508 return CTAP2_OK;
509}
510
518static bool wait_for_user_presence(const char *rp_id, fido2_action_t action, const char *user_name) {
519 LOG_I(TAG, "User presence required for %s at %s",
520 action == FIDO2_ACTION_REGISTER ? "registration" : "authentication",
521 rp_id ? rp_id : "unknown");
522
523 // Send keepalive to signal user presence is needed
524 uint32_t cid = ctaphid_get_current_cid();
526
527 // Request user presence via callback
529
530 switch (result) {
532 LOG_I(TAG, "User presence approved");
533 return true;
534 case FIDO2_UP_DENIED:
535 LOG_I(TAG, "User presence denied");
536 return false;
537 case FIDO2_UP_TIMEOUT:
538 LOG_I(TAG, "User presence timeout");
539 return false;
540 default:
541 LOG_W(TAG, "User presence unknown state: %d", result);
542 return false;
543 }
544}
545
547static constexpr uint64_t CTAP2_INFO_MAX_MSG_SIZE_VALUE = 1200;
549static constexpr uint64_t CTAP2_INFO_PIN_UV_AUTH_PROTOCOL_VALUE = 2;
551static constexpr uint64_t CTAP2_INFO_MAX_CRED_LIST_COUNT_VALUE = 8;
552
554static void encode_info_versions(cbor_writer_t *w) {
556 cbor_encode_array(w, 3);
557 cbor_encode_text(w, "FIDO_2_0");
558 cbor_encode_text(w, "FIDO_2_1");
559 cbor_encode_text(w, "U2F_V2");
560}
561
563static void encode_info_extensions(cbor_writer_t *w) {
565 cbor_encode_array(w, 3);
566 cbor_encode_text(w, "appid"); // 5 chars
567 cbor_encode_text(w, "credProtect"); // 11 chars - required for resident keys
568 cbor_encode_text(w, "appidExclude"); // 12 chars
569}
570
572static void encode_info_aaguid(cbor_writer_t *w) {
575}
576
578static void encode_info_options(cbor_writer_t *w) {
580 cbor_encode_map(w, 12);
581 cbor_encode_text(w, "rk"); // 2 chars
582 cbor_encode_bool(w, true);
583 cbor_encode_text(w, "up"); // 2 chars
584 cbor_encode_bool(w, true);
585 cbor_encode_text(w, "uv"); // 2 chars
586 cbor_encode_bool(w, false);
587 cbor_encode_text(w, "plat"); // 4 chars
588 cbor_encode_bool(w, false);
589 cbor_encode_text(w, "alwaysUv"); // 8 chars
591 cbor_encode_text(w, "credMgmt"); // 8 chars
592 cbor_encode_bool(w, true);
593 cbor_encode_text(w, "authnrCfg"); // 9 chars
594 cbor_encode_bool(w, true);
595 cbor_encode_text(w, "clientPin"); // 9 chars
596 cbor_encode_bool(w, true);
597 cbor_encode_text(w, "largeBlobs"); // 10 chars
598 cbor_encode_bool(w, true);
599 cbor_encode_text(w, "pinUvAuthToken"); // 14 chars
600 cbor_encode_bool(w, true);
601 cbor_encode_text(w, "setMinPINLength"); // 15 chars
602 cbor_encode_bool(w, true);
603 cbor_encode_text(w, "makeCredUvNotRqd"); // 16 chars
604 cbor_encode_bool(w, true);
605}
606
612
619
625
631
633static void encode_info_transports(cbor_writer_t *w) {
635 cbor_encode_array(w, 1);
637}
638
640static void encode_info_algorithms(cbor_writer_t *w) {
642 cbor_encode_array(w, 2);
643 // ES256 (P-256/ECDSA) - keys sorted by length: "alg" (3) < "type" (4)
644 cbor_encode_map(w, 2);
645 cbor_encode_text(w, "alg");
647 cbor_encode_text(w, "type");
648 cbor_encode_text(w, "public-key");
649 // EdDSA (Ed25519)
650 cbor_encode_map(w, 2);
651 cbor_encode_text(w, "alg");
653 cbor_encode_text(w, "type");
654 cbor_encode_text(w, "public-key");
655}
656
662
664static void encode_info_min_pin_length(cbor_writer_t *w) {
666 uint8_t floor = fido2_storage_get_min_pin_len();
669 }
670 cbor_encode_uint(w, floor);
671}
672
673#if CTAP2_DEBUG
675static void dump_get_info_response(const uint8_t *response, uint16_t len) {
676 LOG_I(TAG, "getInfo response len=%u", len);
677 for (uint16_t offset = 0; offset < len; offset += 16) {
678 char hex[50] = {0};
679 int dump_len = ((len - offset) < 16) ? (len - offset) : 16;
680 for (int i = 0; i < dump_len; i++) {
681 sprintf(hex + (i * 3), "%02X ", response[offset + i]);
682 }
683 LOG_D(TAG, "%03u: %s", offset, hex);
684 }
685}
686#endif
687
694uint8_t ctap2_get_info(uint8_t *response, uint16_t *response_len) {
695 cbor_writer_t w;
696 cbor_writer_init(&w, response + 1, *response_len - 1);
697
698 // Response is a map of 12 entries (CTAP 2.1 getInfo)
699 cbor_encode_map(&w, 12);
710 encode_info_max_large_blob(&w); // 0x0B
711 encode_info_min_pin_length(&w); // 0x0D
712
713 if (cbor_writer_error(&w)) {
714 response[0] = CTAP2_ERR_OTHER;
715 *response_len = 1;
716 return CTAP2_ERR_OTHER;
717 }
718
719 response[0] = CTAP2_OK;
720 *response_len = 1 + cbor_writer_length(&w);
721
722#if CTAP2_DEBUG
723 dump_get_info_response(response, *response_len);
724#endif
725
726 return CTAP2_OK;
727}
728
729#ifdef __DOXYGEN__
730namespace cdc::mod_fido2 {
731#endif
732
735 uint8_t client_data_hash[32];
737 uint8_t rp_id_hash[32];
739 uint8_t user_id_len;
741 bool rk;
743 int alg;
746 char appid_exclude[256];
748 uint8_t pin_uv_auth_param[64];
752 bool has_rp;
755
756 void clear() {
757 memset(this, 0, sizeof(*this));
758 option_up = true; // Default: UP required
759 }
760};
761
769 int rp_count = cbor_read_map(r);
770 if (rp_count < 0) return false;
771
772 for (int j = 0; j < rp_count; j++) {
773 char rp_key[16];
774 size_t key_len;
775 if (!cbor_read_text(r, rp_key, sizeof(rp_key), &key_len)) {
777 continue;
778 }
779 if (strcmp(rp_key, "id") == 0) {
780 size_t id_len;
781 cbor_read_text(r, p->rp_id, sizeof(p->rp_id), &id_len);
783 p->has_rp = true;
784 } else {
786 }
787 }
788 return true;
789}
790
798 int user_count = cbor_read_map(r);
799 if (user_count < 0) return false;
800
801 for (int j = 0; j < user_count; j++) {
802 char user_key[16];
803 size_t key_len;
804 if (!cbor_read_text(r, user_key, sizeof(user_key), &key_len)) {
806 continue;
807 }
808 if (strcmp(user_key, "id") == 0) {
809 size_t id_len;
810 cbor_read_bytes(r, p->user_id, sizeof(p->user_id), &id_len);
811 p->user_id_len = id_len;
812 p->has_user = true;
813 } else if (strcmp(user_key, "name") == 0) {
814 size_t name_len;
815 cbor_read_text(r, p->user_name, sizeof(p->user_name), &name_len);
816 } else {
818 }
819 }
820 return true;
821}
822
830 int params_count = cbor_read_array(r);
831 if (params_count < 0) return false;
832
833 for (int j = 0; j < params_count; j++) {
834 int param_count = cbor_read_map(r);
835 int64_t param_alg = 0;
836 for (int k = 0; k < param_count; k++) {
837 char param_key[8];
838 size_t key_len;
839 if (!cbor_read_text(r, param_key, sizeof(param_key), &key_len)) {
841 continue;
842 }
843 if (strcmp(param_key, "alg") == 0) {
844 cbor_read_int(r, &param_alg);
845 } else {
847 }
848 }
849 // We support ES256 (P-256/ECDSA) and EdDSA (Ed25519)
850 if (!p->has_alg && (param_alg == COSE_ALG_ES256 || param_alg == COSE_ALG_EDDSA)) {
851 p->alg = param_alg;
852 p->has_alg = true;
853 }
854 }
855 return true;
856}
857
865 int ext_count = cbor_read_map(r);
866 if (ext_count < 0) return false;
867
868 for (int j = 0; j < ext_count; j++) {
869 char ext_key[16];
870 size_t key_len;
871 if (!cbor_read_text(r, ext_key, sizeof(ext_key), &key_len)) {
873 continue;
874 }
875 if (strcmp(ext_key, "appidExclude") == 0) {
876 size_t len;
877 if (cbor_read_text(r, p->appid_exclude, sizeof(p->appid_exclude), &len)) {
878 p->has_appid_exclude = (len > 0);
879 }
880 } else if (strcmp(ext_key, "credProtect") == 0) {
881 uint64_t level;
882 if (cbor_read_uint(r, &level) && level >= 1 && level <= 3) {
883 p->cred_protect = (uint8_t)level;
884 LOG_I(TAG, "credProtect requested: level=%u", p->cred_protect);
885 }
886 } else {
888 }
889 }
890 return true;
891}
892
900 int opt_count = cbor_read_map(r);
901 if (opt_count < 0) return false;
902
903 for (int j = 0; j < opt_count; j++) {
904 char opt_key[8];
905 size_t key_len;
906 if (!cbor_read_text(r, opt_key, sizeof(opt_key), &key_len)) {
908 continue;
909 }
910 if (strcmp(opt_key, "rk") == 0) {
911 cbor_read_bool(r, &p->rk);
912 } else if (strcmp(opt_key, "uv") == 0) {
914 } else if (strcmp(opt_key, "up") == 0) {
916 } else {
918 }
919 }
920 return true;
921}
922
930static uint8_t parse_make_credential_params(const uint8_t *data, uint16_t data_len,
933 cbor_reader_init(&r, data, data_len);
934 p->clear();
935
936 int map_count = cbor_read_map(&r);
937 if (map_count < 0) {
939 }
940
941 for (int i = 0; i < map_count; i++) {
942 uint64_t key;
943 if (!cbor_read_uint(&r, &key)) {
945 }
946
947 switch (key) {
949 size_t len;
950 if (!cbor_read_bytes(&r, p->client_data_hash, 32, &len) || len != 32) {
952 }
953 p->has_client_data = true;
954 break;
955 }
956 case CTAP2_MC_RP:
957 if (!parse_rp_map(&r, p)) return CTAP2_ERR_INVALID_CBOR;
958 break;
959 case CTAP2_MC_USER:
960 if (!parse_user_map(&r, p)) return CTAP2_ERR_INVALID_CBOR;
961 break;
964 break;
967 break;
968 case CTAP2_MC_OPTIONS:
969 if (!parse_options_map(&r, p)) return CTAP2_ERR_INVALID_CBOR;
970 break;
974 break;
976 uint64_t proto;
977 if (cbor_read_uint(&r, &proto)) {
978 p->pin_uv_auth_protocol = (uint8_t)proto;
979 }
980 break;
981 }
982 default:
983 cbor_skip_item(&r);
984 break;
985 }
986 }
987
988 // Validate required parameters
989 if (!p->has_client_data || !p->has_rp || !p->has_user || !p->has_alg) {
991 }
992
993 return CTAP2_OK;
994}
995
1002 LOG_D(TAG, "pinToken valid=%d", g_client_pin.pin_token_valid);
1003
1004 if (p->pin_uv_auth_param_len == 0) {
1005 return CTAP2_OK; // No auth param provided, skip verification
1006 }
1007
1008 if (!g_client_pin.pin_token_valid) {
1009 LOG_W(TAG, "makeCredential: pinUvAuthParam provided but no valid pinToken");
1011 }
1012
1013 // Verify HMAC-SHA-256(pinToken, clientDataHash)
1014 uint8_t expected_hmac[32];
1015 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
1016 g_client_pin.pin_token, sizeof(g_client_pin.pin_token),
1017 p->client_data_hash, 32,
1018 expected_hmac);
1019
1020 // Protocol 2 uses first 32 bytes of HMAC
1021 size_t compare_len = (p->pin_uv_auth_protocol == 2) ? 32 : 16;
1022 if (p->pin_uv_auth_param_len < compare_len ||
1023 memcmp(p->pin_uv_auth_param, expected_hmac, compare_len) != 0) {
1024 LOG_W(TAG, "makeCredential: pinUvAuthParam verification failed");
1026 }
1027
1028 LOG_I(TAG, "makeCredential: pinUvAuthParam verified - UV=1");
1030 return CTAP2_OK;
1031}
1032
1039 if (!p->has_appid_exclude || p->appid_exclude[0] == '\0') {
1040 return CTAP2_OK;
1041 }
1042
1043 uint8_t appid_hash[32];
1044 sha256_str(p->appid_exclude, appid_hash);
1045 if (fido2_storage_find_by_rp(appid_hash, g_ctap2.assertion_creds, FIDO2_MAX_CREDENTIALS) > 0) {
1047 }
1048 return CTAP2_OK;
1049}
1050
1059 uint8_t *response, uint16_t *response_len) {
1060 LOG_I(TAG, "Browser probe request (%s) - waiting for user selection", p->rp_id);
1061
1062 // Wait for user to confirm this authenticator (no PIN needed)
1064 response[0] = CTAP2_ERR_OPERATION_DENIED;
1065 *response_len = 1;
1067 }
1068
1069 // Generate dummy credential for probe response
1070 uint8_t dummy_pubkey[64];
1071 uint8_t dummy_cred_id[FIDO2_CRED_ID_LEN];
1072 if (ctap2_random(NULL, dummy_cred_id, sizeof(dummy_cred_id)) != 0) {
1073 response[0] = CTAP2_ERR_OTHER;
1074 *response_len = 1;
1075 return CTAP2_ERR_OTHER;
1076 }
1077
1078 uint8_t attested_cred[256];
1079 uint16_t attested_len = 0;
1080 uint8_t auth_data[256];
1081 uint16_t auth_data_len = 0;
1082
1083 // Large buffer in PSRAM to save stack space
1084 EXT_RAM_BSS_ATTR static uint8_t to_sign[512];
1085 uint8_t signature[128];
1086 size_t sig_len = 0;
1087
1088 mbedtls_ecp_keypair ephemeral_key;
1089 if (!ctap2_generate_ephemeral_keypair(&ephemeral_key, dummy_pubkey)) {
1090 response[0] = CTAP2_ERR_OTHER;
1091 *response_len = 1;
1092 return CTAP2_ERR_OTHER;
1093 }
1094
1095 if (!ctap2_build_attested_cred(dummy_cred_id, FIDO2_CRED_ID_LEN, dummy_pubkey,
1096 CDC_CURVE_P256, attested_cred, sizeof(attested_cred),
1097 &attested_len)) {
1098 mbedtls_ecp_keypair_free(&ephemeral_key);
1099 response[0] = CTAP2_ERR_OTHER;
1100 *response_len = 1;
1101 return CTAP2_ERR_OTHER;
1102 }
1103
1104 if (!ctap2_build_auth_data_for_cred(p->rp_id_hash, attested_cred, attested_len,
1105 0, auth_data, &auth_data_len)) {
1106 mbedtls_ecp_keypair_free(&ephemeral_key);
1107 response[0] = CTAP2_ERR_OTHER;
1108 *response_len = 1;
1109 return CTAP2_ERR_OTHER;
1110 }
1111
1112 if (auth_data_len + 32 > sizeof(to_sign)) {
1113 mbedtls_ecp_keypair_free(&ephemeral_key);
1114 response[0] = CTAP2_ERR_OTHER;
1115 *response_len = 1;
1116 return CTAP2_ERR_OTHER;
1117 }
1118
1119 memcpy(to_sign, auth_data, auth_data_len);
1120 memcpy(to_sign + auth_data_len, p->client_data_hash, 32);
1121 uint16_t to_sign_len = auth_data_len + 32;
1122
1123 if (!ctap2_sign_with_keypair(&ephemeral_key, to_sign, to_sign_len,
1124 signature, sizeof(signature), &sig_len)) {
1125 mbedtls_ecp_keypair_free(&ephemeral_key);
1126 response[0] = CTAP2_ERR_OTHER;
1127 *response_len = 1;
1128 return CTAP2_ERR_OTHER;
1129 }
1130 mbedtls_ecp_keypair_free(&ephemeral_key);
1131
1132 LOG_I(TAG, "User selected this authenticator");
1134 auth_data, auth_data_len, signature, (uint8_t)sig_len,
1135 NULL, 0, response, response_len);
1136 LOG_I(TAG, "Probe makeCredential status=0x%02X resp_len=%u", status, *response_len);
1137 return status;
1138}
1139
1145static bool is_browser_probe(const char *rp_id) {
1146 return strcmp(rp_id, "make.me.blink") == 0 || strcmp(rp_id, ".dummy") == 0;
1147}
1148
1156static uint8_t mc_rollback_credential(uint8_t slot, uint8_t *response,
1157 uint16_t *response_len) {
1159 response[0] = CTAP2_ERR_OTHER;
1160 *response_len = 1;
1161 return CTAP2_ERR_OTHER;
1162}
1163
1173 uint8_t curve,
1174 uint8_t *response, uint16_t *response_len) {
1175 // Send KEEPALIVE before long operation (key generation takes ~200-500ms)
1177
1178 // Create credential
1179 uint8_t slot;
1180 uint8_t cred_id[FIDO2_CRED_ID_LEN];
1181 uint8_t pubkey[64]; // P-256: X||Y, Ed25519: 32 bytes (only first half used)
1182
1183 LOG_I(TAG, "Calling fido2_storage_create_credential...");
1185 p->rp_id, p->rp_id_hash, p->user_id, p->user_id_len, p->user_name,
1186 p->rk, p->cred_protect, curve, &slot, cred_id, pubkey)) {
1187 response[0] = CTAP2_ERR_KEY_STORE_FULL;
1188 *response_len = 1;
1190 }
1191
1192 uint8_t attested_cred[256];
1193 uint16_t attested_len = 0;
1194 uint8_t auth_data[256];
1195 uint16_t auth_data_len = 0;
1196
1197 if (!ctap2_build_attested_cred(cred_id, FIDO2_CRED_ID_LEN, pubkey, curve,
1198 attested_cred, sizeof(attested_cred),
1199 &attested_len) ||
1200 !ctap2_build_auth_data_for_cred(p->rp_id_hash, attested_cred, attested_len,
1201 p->cred_protect, auth_data, &auth_data_len)) {
1202 return mc_rollback_credential(slot, response, response_len);
1203 }
1204
1205 // Large buffer in PSRAM to save stack space
1206 EXT_RAM_BSS_ATTR static uint8_t mc_to_sign[512];
1207 if (auth_data_len + 32 > sizeof(mc_to_sign)) {
1208 return mc_rollback_credential(slot, response, response_len);
1209 }
1210 memcpy(mc_to_sign, auth_data, auth_data_len);
1211 memcpy(mc_to_sign + auth_data_len, p->client_data_hash, 32);
1212 uint16_t to_sign_len = auth_data_len + 32;
1213
1214 // Sign with attestation key for basic attestation
1215 uint8_t signature[128];
1216 uint8_t sig_len = 0;
1217 const uint8_t *att_cert = NULL;
1218 uint16_t att_cert_len = 0;
1219
1220 LOG_I(TAG, "PIN state: pinToken_valid=%d, is_pin_verified=%d",
1221 g_client_pin.pin_token_valid, fido2_is_pin_verified());
1222
1223 // Use packed attestation with FIDO2-compliant certificate
1224 if (!u2f_get_attestation_cert(&att_cert, &att_cert_len)) {
1225 LOG_E(TAG, "Attestation certificate not initialized");
1226 return mc_rollback_credential(slot, response, response_len);
1227 }
1228
1229 // Send KEEPALIVE before signing (TROPIC01 ECDSA takes ~100ms)
1231
1232 if (!u2f_attestation_sign(mc_to_sign, to_sign_len, signature, &sig_len)) {
1233 LOG_E(TAG, "Attestation signing failed");
1234 return mc_rollback_credential(slot, response, response_len);
1235 }
1236 LOG_I(TAG, "Using basic attestation (cert=%u, sig=%u)", att_cert_len, sig_len);
1237
1239 auth_data, auth_data_len, signature, sig_len,
1240 att_cert, att_cert_len, response, response_len);
1241
1242 if (status != CTAP2_OK) {
1243 return mc_rollback_credential(slot, response, response_len);
1244 }
1245 LOG_I(TAG, "Created credential for %s (slot %d)", p->rp_id, slot);
1246
1247#if CTAP2_DEBUG
1248 LOG_I(TAG, "makeCredential status=0x%02X resp_len=%u", status, *response_len);
1249 for (uint16_t offset = 0; offset < *response_len; offset += 16) {
1250 char hex[50] = {0};
1251 int dump_len = ((*response_len - offset) < 16) ? (*response_len - offset) : 16;
1252 for (int i = 0; i < dump_len; i++) {
1253 sprintf(hex + (i * 3), "%02X ", response[offset + i]);
1254 }
1255 LOG_D(TAG, "%03u: %s", offset, hex);
1256 }
1257#endif
1258
1259 return status;
1260}
1261
1270uint8_t ctap2_make_credential(const uint8_t *params, uint16_t params_len,
1271 uint8_t *response, uint16_t *response_len) {
1273
1274 // Step 1: Parse all CBOR parameters
1275 uint8_t status = parse_make_credential_params(params, params_len, &p);
1276 if (status != CTAP2_OK) {
1277 response[0] = status;
1278 *response_len = 1;
1279 return status;
1280 }
1281
1282 LOG_I(TAG, "makeCredential rp_id=%s rk=%d uv=%d up=%d alg=%d pinProto=%d pinAuthLen=%zu",
1283 p.rp_id[0] ? p.rp_id : "(none)", p.rk, p.option_uv, p.option_up, p.alg,
1285
1286 // Step 2: Check appidExclude extension
1287 status = check_appid_exclude(&p);
1288 if (status != CTAP2_OK) {
1289 response[0] = status;
1290 *response_len = 1;
1291 return status;
1292 }
1293
1294 // Step 3: Verify PIN/UV auth parameter
1295 status = verify_pin_uv_auth(&p);
1296 if (status != CTAP2_OK) {
1297 response[0] = status;
1298 *response_len = 1;
1299 return status;
1300 }
1301
1302 // alwaysUv (authenticatorConfig): require a verified pinUvAuthParam for
1303 // every makeCredential when the policy flag is set.
1305 response[0] = CTAP2_ERR_PIN_REQUIRED;
1306 *response_len = 1;
1308 }
1309
1310 // Step 4: Handle browser probe requests
1311 if (is_browser_probe(p.rp_id)) {
1312 return handle_browser_probe(&p, response, response_len);
1313 }
1314
1315 // Step 5: Determine curve from algorithm
1316 uint8_t curve;
1317 if (p.alg == COSE_ALG_ES256) {
1319 } else if (p.alg == COSE_ALG_EDDSA) {
1321 } else {
1322 response[0] = CTAP2_ERR_UNSUPPORTED_ALGORITHM;
1323 *response_len = 1;
1325 }
1326
1327 // Step 6: Validate options
1328 if (p.option_uv) {
1329 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
1330 *response_len = 1;
1332 }
1333 if (!p.option_up) {
1334 response[0] = CTAP2_ERR_INVALID_OPTION;
1335 *response_len = 1;
1337 }
1338
1341 up_action = FIDO2_ACTION_OVERWRITE;
1342 }
1343
1344 if (!wait_for_user_presence(p.rp_id, up_action, p.user_name)) {
1345 response[0] = CTAP2_ERR_OPERATION_DENIED;
1346 *response_len = 1;
1348 }
1349
1350 LOG_I(TAG, "User presence OK, creating credential (curve=%d)...", curve);
1351
1352 // Step 8: Create credential and build response
1353 return create_credential_and_respond(&p, curve, response, response_len);
1354}
1355
1359 uint8_t rp_id_hash[32];
1360 uint8_t client_data_hash[32];
1363
1364 // Allow list
1368
1369 // Options
1372
1373 // Extensions
1374 char appid[256];
1376 uint8_t appid_hash[32];
1377
1378 // PIN/UV auth
1382};
1383
1387 uint8_t count;
1390 uint8_t* hash_in_use; // Points to rp_id_hash or appid_hash
1391};
1392
1393#ifdef __DOXYGEN__
1394} // namespace cdc::mod_fido2
1395#endif
1396
1404static bool ga_parse_allow_list_credential(cbor_reader_t *r, uint8_t *cred_id,
1405 size_t *cred_id_len) {
1406 int cred_map = cbor_read_map(r);
1407 if (cred_map < 0) {
1408 return false;
1409 }
1410
1411 *cred_id_len = 0;
1412 bool have_id = false;
1413
1414 for (int k = 0; k < cred_map; k++) {
1415 char cred_key[16];
1416 size_t key_len;
1417 if (!cbor_read_text(r, cred_key, sizeof(cred_key), &key_len)) {
1418 cbor_skip_item(r);
1419 continue;
1420 }
1421 if (strcmp(cred_key, "id") == 0) {
1422 if (!cbor_read_bytes(r, cred_id, FIDO2_CRED_ID_LEN, cred_id_len)) {
1423 return false;
1424 }
1425 have_id = true;
1426 } else {
1427 cbor_skip_item(r);
1428 }
1429 }
1430
1431 return have_id && *cred_id_len == FIDO2_CRED_ID_LEN;
1432}
1433
1440static uint8_t ga_parse_allow_list(cbor_reader_t *r, GetAssertionParams *p) {
1441 int list_count = cbor_read_array(r);
1442 if (list_count < 0) {
1444 }
1445
1446 p->allow_list_present = true;
1447
1448 for (int j = 0; j < list_count; j++) {
1449 uint8_t cred_id[FIDO2_CRED_ID_LEN];
1450 size_t cred_id_len = 0;
1451
1452 if (!ga_parse_allow_list_credential(r, cred_id, &cred_id_len)) {
1453 continue;
1454 }
1455
1456 int8_t slot = fido2_storage_find_slot_by_cred_id(cred_id, cred_id_len);
1457 if (slot < 0) {
1458 continue;
1459 }
1460
1461 // Avoid duplicates
1462 bool exists = false;
1463 for (uint8_t m = 0; m < p->allow_list_count; m++) {
1464 if (p->allow_list_slots[m] == (uint8_t)slot) {
1465 exists = true;
1466 break;
1467 }
1468 }
1469 if (!exists && p->allow_list_count < FIDO2_MAX_CREDENTIALS) {
1470 p->allow_list_slots[p->allow_list_count++] = (uint8_t)slot;
1471 }
1472 }
1473
1474 return CTAP2_OK;
1475}
1476
1483static uint8_t ga_parse_extensions(cbor_reader_t *r, GetAssertionParams *p) {
1484 int ext_count = cbor_read_map(r);
1485 if (ext_count < 0) {
1487 }
1488
1489 for (int j = 0; j < ext_count; j++) {
1490 char ext_key[16];
1491 size_t key_len;
1492 if (!cbor_read_text(r, ext_key, sizeof(ext_key), &key_len)) {
1493 cbor_skip_item(r);
1494 continue;
1495 }
1496 if (strcmp(ext_key, "appid") == 0) {
1497 size_t len;
1498 if (cbor_read_text(r, p->appid, sizeof(p->appid), &len)) {
1499 p->has_appid = (len > 0);
1500 if (p->has_appid) {
1501 sha256_str(p->appid, p->appid_hash);
1502 }
1503 }
1504 } else {
1505 cbor_skip_item(r);
1506 }
1507 }
1508
1509 return CTAP2_OK;
1510}
1511
1518static uint8_t ga_parse_options(cbor_reader_t *r, GetAssertionParams *p) {
1519 int opt_count = cbor_read_map(r);
1520 if (opt_count < 0) {
1522 }
1523
1524 for (int j = 0; j < opt_count; j++) {
1525 char opt_key[8];
1526 size_t key_len;
1527 if (!cbor_read_text(r, opt_key, sizeof(opt_key), &key_len)) {
1528 cbor_skip_item(r);
1529 continue;
1530 }
1531 if (strcmp(opt_key, "uv") == 0) {
1532 cbor_read_bool(r, &p->option_uv);
1533 } else if (strcmp(opt_key, "up") == 0) {
1534 cbor_read_bool(r, &p->option_up);
1535 } else {
1536 cbor_skip_item(r);
1537 }
1538 }
1539
1540 return CTAP2_OK;
1541}
1542
1550static uint8_t ga_parse_params(const uint8_t *params, uint16_t params_len,
1551 GetAssertionParams *p) {
1552 memset(p, 0, sizeof(*p));
1553 p->option_up = true; // Default: user presence required
1554
1555 cbor_reader_t r;
1556 cbor_reader_init(&r, params, params_len);
1557
1558 int map_count = cbor_read_map(&r);
1559 if (map_count < 0) {
1561 }
1562
1563 for (int i = 0; i < map_count; i++) {
1564 uint64_t key;
1565 if (!cbor_read_uint(&r, &key)) {
1567 }
1568
1569 uint8_t status = CTAP2_OK;
1570
1571 switch (key) {
1572 case CTAP2_GA_RP_ID:
1573 {
1574 size_t len;
1575 cbor_read_text(&r, p->rp_id, sizeof(p->rp_id), &len);
1576 sha256_str(p->rp_id, p->rp_id_hash);
1577 p->has_rp = true;
1578 }
1579 break;
1580
1582 {
1583 size_t len;
1584 if (!cbor_read_bytes(&r, p->client_data_hash, 32, &len) || len != 32) {
1586 }
1587 p->has_client_data = true;
1588 }
1589 break;
1590
1592 status = ga_parse_allow_list(&r, p);
1593 if (status != CTAP2_OK) return status;
1594 break;
1595
1597 status = ga_parse_extensions(&r, p);
1598 if (status != CTAP2_OK) return status;
1599 break;
1600
1601 case CTAP2_GA_OPTIONS:
1602 status = ga_parse_options(&r, p);
1603 if (status != CTAP2_OK) return status;
1604 break;
1605
1607 cbor_read_bytes(&r, p->pin_uv_auth_param, sizeof(p->pin_uv_auth_param),
1608 &p->pin_uv_auth_param_len);
1609 break;
1610
1612 {
1613 uint64_t proto;
1614 if (cbor_read_uint(&r, &proto)) {
1615 p->pin_uv_auth_protocol = (uint8_t)proto;
1616 }
1617 }
1618 break;
1619
1620 default:
1621 cbor_skip_item(&r);
1622 break;
1623 }
1624 }
1625
1626 return CTAP2_OK;
1627}
1628
1635static uint8_t ga_verify_pin_auth(const GetAssertionParams *p, bool *uv_verified) {
1636 *uv_verified = false;
1637
1638 if (p->pin_uv_auth_param_len == 0) {
1639 return CTAP2_OK; // No PIN auth provided, continue without UV
1640 }
1641
1642 if (!g_client_pin.pin_token_valid) {
1643 LOG_W(TAG, "pinUvAuthParam provided but no valid pinToken");
1645 }
1646
1647 // Compute HMAC-SHA256(pinToken, clientDataHash)
1648 uint8_t expected_hmac[32];
1649 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
1650 g_client_pin.pin_token, PIN_TOKEN_SIZE,
1651 p->client_data_hash, 32,
1652 expected_hmac);
1653
1654 // Protocol 2 uses first 32 bytes of HMAC, protocol 1 uses 16
1655 size_t compare_len = (p->pin_uv_auth_protocol == 2) ? 32 : 16;
1656 if (p->pin_uv_auth_param_len < compare_len) {
1657 LOG_W(TAG, "pinUvAuthParam too short: %zu < %zu",
1658 p->pin_uv_auth_param_len, compare_len);
1660 }
1661
1662#if DEBUG_MODE
1663 LOG_D(TAG, "pinUvAuthParam received (%zu bytes):", p->pin_uv_auth_param_len);
1664 LOG_D(TAG, " %02X%02X%02X%02X %02X%02X%02X%02X...",
1665 p->pin_uv_auth_param[0], p->pin_uv_auth_param[1],
1666 p->pin_uv_auth_param[2], p->pin_uv_auth_param[3],
1667 p->pin_uv_auth_param[4], p->pin_uv_auth_param[5],
1668 p->pin_uv_auth_param[6], p->pin_uv_auth_param[7]);
1669 LOG_D(TAG, "Expected HMAC (first %zu bytes):", compare_len);
1670 LOG_D(TAG, " %02X%02X%02X%02X %02X%02X%02X%02X...",
1671 expected_hmac[0], expected_hmac[1], expected_hmac[2], expected_hmac[3],
1672 expected_hmac[4], expected_hmac[5], expected_hmac[6], expected_hmac[7]);
1673#endif
1674
1675 if (memcmp(p->pin_uv_auth_param, expected_hmac, compare_len) != 0) {
1676 LOG_W(TAG, "pinUvAuthParam verification failed");
1678 }
1679
1680 LOG_I(TAG, "pinUvAuthParam verified - UV=1");
1681 *uv_verified = true;
1683
1684 return CTAP2_OK;
1685}
1686
1693static void ga_find_credentials(GetAssertionParams *p, AssertionCredentials *creds) {
1694 memset(creds, 0, sizeof(*creds));
1695 creds->hash_in_use = p->rp_id_hash;
1696
1697 uint8_t temp_slots[FIDO2_MAX_CREDENTIALS] = {0};
1698 uint8_t temp_count = 0;
1699
1700 if (p->allow_list_present && p->allow_list_count > 0) {
1701 // Filter allowList by RP ID hash
1702 uint8_t filtered = 0;
1703 for (uint8_t i = 0; i < p->allow_list_count; i++) {
1704 fido2_credential_info_t info;
1705 if (fido2_storage_get_credential(p->allow_list_slots[i], &info) &&
1706 memcmp(info.rp_id_hash, p->rp_id_hash, 32) == 0) {
1707 creds->slots[filtered++] = p->allow_list_slots[i];
1708 }
1709 }
1710 creds->count = filtered;
1711 creds->include_user = false;
1712
1713 LOG_I(TAG, "getAssertion using allowList, matches=%u", creds->count);
1714
1715 // Try appid extension if present
1716 if (p->has_appid) {
1717 uint8_t filtered_appid = 0;
1718 for (uint8_t i = 0; i < p->allow_list_count; i++) {
1719 fido2_credential_info_t info;
1720 if (fido2_storage_get_credential(p->allow_list_slots[i], &info) &&
1721 memcmp(info.rp_id_hash, p->appid_hash, 32) == 0) {
1722 temp_slots[filtered_appid++] = p->allow_list_slots[i];
1723 }
1724 }
1725 if (filtered_appid > 0) {
1726 memcpy(creds->slots, temp_slots, filtered_appid);
1727 creds->count = filtered_appid;
1728 creds->appid_used = true;
1729 creds->hash_in_use = p->appid_hash;
1730 }
1731 }
1732 } else {
1733 // No allowList - search all credentials for this RP
1734 creds->count = fido2_storage_find_by_rp(p->rp_id_hash, creds->slots,
1736 creds->include_user = true;
1737
1738 LOG_I(TAG, "getAssertion using all RP creds, matches=%u", creds->count);
1739
1740 // Try appid extension if present
1741 if (p->has_appid) {
1742 temp_count = fido2_storage_find_by_rp(p->appid_hash, temp_slots,
1744 if (temp_count > 0) {
1745 memcpy(creds->slots, temp_slots, temp_count);
1746 creds->count = temp_count;
1747 creds->appid_used = true;
1748 creds->hash_in_use = p->appid_hash;
1749 }
1750 }
1751 }
1752}
1753
1764static uint8_t ga_sign_assertion(uint8_t slot, const uint8_t *auth_data,
1765 uint16_t auth_data_len, const uint8_t *client_data_hash,
1766 uint8_t *signature, uint8_t *sig_len) {
1767 // Prepare data to sign: authData || clientDataHash
1768 uint8_t to_sign[96]; // Max 64 bytes auth_data + 32 bytes clientDataHash
1769 if (auth_data_len > sizeof(to_sign) - 32) {
1770 LOG_E(TAG, "auth_data too large: %u", auth_data_len);
1771 return CTAP2_ERR_OTHER;
1772 }
1773
1774 memcpy(to_sign, auth_data, auth_data_len);
1775 memcpy(to_sign + auth_data_len, client_data_hash, 32);
1776 uint16_t to_sign_len = auth_data_len + 32;
1777
1778 if (!fido2_storage_sign_raw(slot, to_sign, to_sign_len, signature, sig_len)) {
1779 return CTAP2_ERR_OTHER;
1780 }
1781
1782 return CTAP2_OK;
1783}
1784
1799static uint8_t ga_build_response(const uint8_t *cred_id, const uint8_t *auth_data,
1800 uint16_t auth_data_len, const uint8_t *signature,
1801 uint8_t sig_len, const fido2_credential_info_t *cred,
1802 bool include_user, uint8_t total_creds,
1803 uint8_t *response, uint16_t *response_len) {
1804 cbor_writer_t w;
1805 cbor_writer_init(&w, response + 1, *response_len - 1);
1806
1807 int resp_fields = 3; // credential, authData, signature
1808 if (include_user) resp_fields++;
1809 if (total_creds > 1) resp_fields++;
1810
1811 cbor_encode_map(&w, resp_fields);
1812
1813 // credential descriptor
1815 cbor_encode_map(&w, 2);
1816 // Canonical order: "id" (len 2) before "type" (len 4)
1817 cbor_encode_text(&w, "id");
1819 cbor_encode_text(&w, "type");
1820 cbor_encode_text(&w, "public-key");
1821
1822 // authData
1824 cbor_encode_bytes(&w, auth_data, auth_data_len);
1825
1826 // signature
1828 cbor_encode_bytes(&w, signature, sig_len);
1829
1830 // user (only for discoverable credentials)
1831 if (include_user) {
1833 int user_fields = 1;
1834 if (cred->user_name[0] != '\0') user_fields++;
1835 cbor_encode_map(&w, user_fields);
1836
1837 cbor_encode_text(&w, "id");
1838 cbor_encode_bytes(&w, cred->user_id, cred->user_id_len);
1839
1840 if (cred->user_name[0] != '\0') {
1841 cbor_encode_text(&w, "name");
1842 cbor_encode_text(&w, cred->user_name);
1843 }
1844 }
1845
1846 // numberOfCredentials (if multiple)
1847 if (total_creds > 1) {
1849 cbor_encode_uint(&w, total_creds);
1850 }
1851
1852 if (cbor_writer_error(&w)) {
1853 return CTAP2_ERR_OTHER;
1854 }
1855
1856 response[0] = CTAP2_OK;
1857 *response_len = 1 + cbor_writer_length(&w);
1858 return CTAP2_OK;
1859}
1860
1869uint8_t ctap2_get_assertion(const uint8_t *params, uint16_t params_len,
1870 uint8_t *response, uint16_t *response_len) {
1871 // Step 1: Parse CBOR parameters
1872 GetAssertionParams p;
1873 uint8_t status = ga_parse_params(params, params_len, &p);
1874 if (status != CTAP2_OK) {
1875 response[0] = status;
1876 *response_len = 1;
1877 return status;
1878 }
1879
1880 LOG_I(TAG, "getAssertion rp_id=%s allowList=%d count=%u uv=%d up=%d appid=%s",
1881 p.rp_id[0] ? p.rp_id : "(none)", p.allow_list_present ? 1 : 0,
1882 p.allow_list_count, p.option_uv, p.option_up, p.has_appid ? p.appid : "(none)");
1883
1884 // Step 2: Validate required parameters
1885 if (!p.has_rp || !p.has_client_data) {
1886 response[0] = CTAP2_ERR_MISSING_PARAMETER;
1887 *response_len = 1;
1889 }
1890
1891 if (p.option_uv) {
1892 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
1893 *response_len = 1;
1895 }
1896
1897 // Step 3: Verify PIN/UV auth if provided
1898 bool uv_verified = false;
1899 status = ga_verify_pin_auth(&p, &uv_verified);
1900 if (status != CTAP2_OK) {
1901 response[0] = status;
1902 *response_len = 1;
1903 return status;
1904 }
1905
1906 // alwaysUv (authenticatorConfig): require user verification for every
1907 // getAssertion when the policy flag is set.
1908 if (fido2_storage_get_always_uv() && !uv_verified) {
1909 response[0] = CTAP2_ERR_PIN_REQUIRED;
1910 *response_len = 1;
1912 }
1913
1914 // Step 4: Find matching credentials
1915 AssertionCredentials creds;
1917
1918 if (creds.count == 0) {
1919 response[0] = CTAP2_ERR_NO_CREDENTIALS;
1920 *response_len = 1;
1922 }
1923
1924 // Step 5: Request user presence (if required)
1925 if (p.option_up) {
1927 response[0] = CTAP2_ERR_OPERATION_DENIED;
1928 *response_len = 1;
1930 }
1931 }
1932
1933 // Step 6: Save state for getNextAssertion
1934 memcpy(g_ctap2.assertion_creds, creds.slots, creds.count);
1935 g_ctap2.assertion_count = creds.count;
1936 g_ctap2.assertion_include_user = creds.include_user;
1937 memcpy(g_ctap2.assertion_rp_id_hash, creds.hash_in_use, 32);
1938 memcpy(g_ctap2.assertion_client_data_hash, p.client_data_hash, 32);
1939 g_ctap2.assertion_index = 0;
1940 g_ctap2.assertion_up_done = p.option_up;
1941 g_ctap2.assertion_appid_used = creds.appid_used;
1942
1943 // Step 7: Get first credential
1944 uint8_t slot = g_ctap2.assertion_creds[0];
1945 fido2_credential_info_t cred;
1946 if (!fido2_storage_get_credential(slot, &cred)) {
1947 response[0] = CTAP2_ERR_OTHER;
1948 *response_len = 1;
1949 return CTAP2_ERR_OTHER;
1950 }
1951
1952 // Step 8: Build authenticator data
1954 if (sign_count == 0) {
1955 response[0] = CTAP2_ERR_OTHER;
1956 *response_len = 1;
1957 return CTAP2_ERR_OTHER;
1958 }
1959
1960 uint8_t flags = p.option_up ? 0x01 : 0x00; // UP=1 only if user presence was requested
1961 if (uv_verified) {
1962 flags |= 0x04; // UV=1
1963 }
1964
1965 uint8_t ext_data[32];
1966 uint16_t ext_len = 0;
1967 if (creds.appid_used) {
1968 ext_len = ctap2_build_appid_extension(ext_data, sizeof(ext_data));
1969 }
1970
1971 uint8_t auth_data[128];
1972 uint16_t auth_data_len;
1973 build_authenticator_data(creds.hash_in_use, flags, sign_count, NULL, 0,
1974 ext_data, ext_len, auth_data, &auth_data_len);
1975
1976 // Step 9: Generate signature
1977 uint8_t signature[128];
1978 uint8_t sig_len;
1979 status = ga_sign_assertion(slot, auth_data, auth_data_len, p.client_data_hash,
1980 signature, &sig_len);
1981 if (status != CTAP2_OK) {
1982 response[0] = status;
1983 *response_len = 1;
1984 return status;
1985 }
1986
1987 // Step 10: Get credential ID
1988 uint8_t cred_id[FIDO2_CRED_ID_LEN];
1989 if (!fido2_storage_get_cred_id(slot, cred_id)) {
1990 LOG_E(TAG, "Failed to get credential ID for slot %d", slot);
1991 response[0] = CTAP2_ERR_OTHER;
1992 *response_len = 1;
1993 return CTAP2_ERR_OTHER;
1994 }
1995
1996 // Step 11: Build CBOR response
1997 status = ga_build_response(cred_id, auth_data, auth_data_len, signature, sig_len,
1998 &cred, g_ctap2.assertion_include_user,
1999 g_ctap2.assertion_count, response, response_len);
2000 if (status != CTAP2_OK) {
2001 response[0] = status;
2002 *response_len = 1;
2003 return status;
2004 }
2005
2007
2008 LOG_I(TAG, "Assertion for %s (slot %d)", p.rp_id, slot);
2009 return CTAP2_OK;
2010}
2011
2018uint8_t ctap2_get_next_assertion(uint8_t *response, uint16_t *response_len) {
2019 if (g_ctap2.assertion_count == 0) {
2020 response[0] = CTAP2_ERR_NOT_ALLOWED;
2021 *response_len = 1;
2022 return CTAP2_ERR_NOT_ALLOWED;
2023 }
2024
2025 g_ctap2.assertion_index++;
2026 if (g_ctap2.assertion_index >= g_ctap2.assertion_count) {
2027 response[0] = CTAP2_ERR_NOT_ALLOWED;
2028 *response_len = 1;
2029 return CTAP2_ERR_NOT_ALLOWED;
2030 }
2031
2032 // Similar to getAssertion but without user presence check
2033 uint8_t slot = g_ctap2.assertion_creds[g_ctap2.assertion_index];
2034 fido2_credential_info_t cred;
2035 if (!fido2_storage_get_credential(slot, &cred)) {
2036 response[0] = CTAP2_ERR_OTHER;
2037 *response_len = 1;
2038 return CTAP2_ERR_OTHER;
2039 }
2040
2042 if (sign_count == 0) {
2043 response[0] = CTAP2_ERR_OTHER;
2044 *response_len = 1;
2045 return CTAP2_ERR_OTHER;
2046 }
2047
2048 uint8_t auth_data[128];
2049 uint16_t auth_data_len;
2050 uint8_t ext_data[32];
2051 uint16_t ext_len = 0;
2052 if (g_ctap2.assertion_appid_used) {
2053 ext_len = ctap2_build_appid_extension(ext_data, sizeof(ext_data));
2054 }
2055 build_authenticator_data(g_ctap2.assertion_rp_id_hash, 0x01, sign_count,
2056 NULL, 0, ext_data, ext_len, auth_data, &auth_data_len);
2057
2058 // Sign: authData || clientDataHash (TROPIC01 hashes internally)
2059 uint8_t to_sign[96];
2060 if (auth_data_len > sizeof(to_sign) - 32) {
2061 LOG_E(TAG, "auth_data too large: %u", auth_data_len);
2062 response[0] = CTAP2_ERR_OTHER;
2063 *response_len = 1;
2064 return CTAP2_ERR_OTHER;
2065 }
2066 memcpy(to_sign, auth_data, auth_data_len);
2067 memcpy(to_sign + auth_data_len, g_ctap2.assertion_client_data_hash, 32);
2068 uint16_t to_sign_len = auth_data_len + 32;
2069
2070 uint8_t signature[128];
2071 uint8_t sig_len;
2072 if (!fido2_storage_sign_raw(slot, to_sign, to_sign_len, signature, &sig_len)) {
2073 response[0] = CTAP2_ERR_OTHER;
2074 *response_len = 1;
2075 return CTAP2_ERR_OTHER;
2076 }
2077
2078 // Get credential ID
2079 uint8_t cred_id[FIDO2_CRED_ID_LEN];
2080 if (!fido2_storage_get_cred_id(slot, cred_id)) {
2081 LOG_E(TAG, "Failed to get credential ID for slot %d", slot);
2082 response[0] = CTAP2_ERR_OTHER;
2083 *response_len = 1;
2084 return CTAP2_ERR_OTHER;
2085 }
2086
2087 cbor_writer_t w;
2088 cbor_writer_init(&w, response + 1, *response_len - 1);
2089
2090 cbor_encode_map(&w, 3);
2091
2092 // credential descriptor (type + id)
2094 cbor_encode_map(&w, 2);
2095 // Canonical order: "id" (len 2) before "type" (len 4)
2096 cbor_encode_text(&w, "id");
2098 cbor_encode_text(&w, "type");
2099 cbor_encode_text(&w, "public-key");
2100
2101 // authData
2103 cbor_encode_bytes(&w, auth_data, auth_data_len);
2104
2105 // signature
2107 cbor_encode_bytes(&w, signature, sig_len);
2108
2109 response[0] = CTAP2_OK;
2110 *response_len = 1 + cbor_writer_length(&w);
2111 return CTAP2_OK;
2112}
2113
2119static bool client_pin_init_ecdh(void) {
2120 if (g_client_pin.ecdh_valid) return true;
2121
2122 mbedtls_ecp_keypair_init(&g_client_pin.ecdh_key);
2123
2124 int ret = mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1,
2125 &g_client_pin.ecdh_key,
2126 ctap2_random, NULL);
2127 if (ret != 0) {
2128 LOG_E(TAG_PIN, "ECDH key generation failed: %d", ret);
2129 return false;
2130 }
2131
2132 g_client_pin.ecdh_valid = true;
2133 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2134 g_client_pin.uv_retries = PIN_UV_RETRIES_MAX;
2135 LOG_I(TAG_PIN, "ECDH key pair generated");
2136 return true;
2137}
2138
2147static bool client_pin_compute_shared_secret(const uint8_t *platform_key_x,
2148 const uint8_t *platform_key_y,
2149 uint8_t pin_protocol,
2150 uint8_t *shared_secret) {
2151 if (!g_client_pin.ecdh_valid) return false;
2152
2153 mbedtls_ecp_point platform_point;
2154 mbedtls_mpi shared_x;
2155
2156 mbedtls_ecp_point_init(&platform_point);
2157 mbedtls_mpi_init(&shared_x);
2158
2159 int ret = 0;
2160
2161 // Load platform public key
2162 ret = mbedtls_mpi_read_binary(&platform_point.MBEDTLS_PRIVATE(X), platform_key_x, 32);
2163 if (ret != 0) goto cleanup;
2164
2165 ret = mbedtls_mpi_read_binary(&platform_point.MBEDTLS_PRIVATE(Y), platform_key_y, 32);
2166 if (ret != 0) goto cleanup;
2167
2168 ret = mbedtls_mpi_lset(&platform_point.MBEDTLS_PRIVATE(Z), 1);
2169 if (ret != 0) goto cleanup;
2170
2171 // Compute ECDH: shared_x = (platformPubKey * authenticatorPrivKey).x
2172 ret = mbedtls_ecdh_compute_shared(&g_client_pin.ecdh_key.MBEDTLS_PRIVATE(grp),
2173 &shared_x,
2174 &platform_point,
2175 &g_client_pin.ecdh_key.MBEDTLS_PRIVATE(d),
2176 ctap2_random, NULL);
2177 if (ret != 0) {
2178 LOG_E(TAG_PIN, "ECDH compute failed: %d", ret);
2179 goto cleanup;
2180 }
2181
2182 // Extract x coordinate as bytes (Z = ECDH shared secret)
2183 uint8_t ecdh_z[32];
2184 ret = mbedtls_mpi_write_binary(&shared_x, ecdh_z, 32);
2185 if (ret != 0) goto cleanup;
2186
2187#if DEBUG_MODE
2188 // Full debug output for manual verification
2189 LOG_I(TAG_PIN, "=== ECDH DEBUG (full 32-byte values) ===");
2190 LOG_I(TAG_PIN, "Z (ECDH x-coord):");
2191 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2192 ecdh_z[0], ecdh_z[1], ecdh_z[2], ecdh_z[3], ecdh_z[4], ecdh_z[5], ecdh_z[6], ecdh_z[7],
2193 ecdh_z[8], ecdh_z[9], ecdh_z[10], ecdh_z[11], ecdh_z[12], ecdh_z[13], ecdh_z[14], ecdh_z[15]);
2194 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2195 ecdh_z[16], ecdh_z[17], ecdh_z[18], ecdh_z[19], ecdh_z[20], ecdh_z[21], ecdh_z[22], ecdh_z[23],
2196 ecdh_z[24], ecdh_z[25], ecdh_z[26], ecdh_z[27], ecdh_z[28], ecdh_z[29], ecdh_z[30], ecdh_z[31]);
2197#endif // DEBUG_MODE
2198
2199 if (pin_protocol == 1) {
2200 // Protocol 1: sharedSecret = SHA256(Z)
2201 LOG_D(TAG_PIN, "Using Protocol 1: SHA256(Z)");
2202 mbedtls_sha256(ecdh_z, 32, shared_secret, 0); // 0 = SHA256 (not SHA224)
2203 } else {
2204 // Protocol 2: Use HKDF-SHA256 to derive AES key
2205 // AES_key = HKDF-SHA256(salt=32zeros, IKM=Z, L=32, info="CTAP2 AES key")
2206 LOG_D(TAG_PIN, "Using Protocol 2: HKDF(Z)");
2207 const char *info = "CTAP2 AES key";
2208 uint8_t prk[32];
2209 uint8_t zero_salt[32] = {0};
2210
2211 // HKDF Extract: PRK = HMAC-SHA256(salt, IKM=Z)
2212 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
2213 zero_salt, 32, ecdh_z, 32, prk);
2214
2215 // HKDF Expand: OKM = HMAC-SHA256(PRK, info || 0x01)
2216 uint8_t expand_input[32];
2217 size_t info_len = strlen(info);
2218 memcpy(expand_input, info, info_len);
2219 expand_input[info_len] = 0x01;
2220
2221#if DEBUG_MODE
2222 LOG_I(TAG_PIN, "HKDF PRK (HMAC-SHA256(salt=0, IKM=Z)):");
2223 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2224 prk[0], prk[1], prk[2], prk[3], prk[4], prk[5], prk[6], prk[7],
2225 prk[8], prk[9], prk[10], prk[11], prk[12], prk[13], prk[14], prk[15]);
2226 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2227 prk[16], prk[17], prk[18], prk[19], prk[20], prk[21], prk[22], prk[23],
2228 prk[24], prk[25], prk[26], prk[27], prk[28], prk[29], prk[30], prk[31]);
2229 LOG_I(TAG_PIN, "HKDF info: '%s' || 0x01 (len=%zu)", info, info_len + 1);
2230#endif
2231
2232 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
2233 prk, 32, expand_input, info_len + 1, shared_secret);
2234 }
2235
2236#if DEBUG_MODE
2237 LOG_I(TAG_PIN, "AES key (shared secret):");
2238 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2239 shared_secret[0], shared_secret[1], shared_secret[2], shared_secret[3],
2240 shared_secret[4], shared_secret[5], shared_secret[6], shared_secret[7],
2241 shared_secret[8], shared_secret[9], shared_secret[10], shared_secret[11],
2242 shared_secret[12], shared_secret[13], shared_secret[14], shared_secret[15]);
2243 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2244 shared_secret[16], shared_secret[17], shared_secret[18], shared_secret[19],
2245 shared_secret[20], shared_secret[21], shared_secret[22], shared_secret[23],
2246 shared_secret[24], shared_secret[25], shared_secret[26], shared_secret[27],
2247 shared_secret[28], shared_secret[29], shared_secret[30], shared_secret[31]);
2248 LOG_I(TAG_PIN, "=== END ECDH DEBUG ===");
2249#endif
2250
2251cleanup:
2252 mbedtls_ecp_point_free(&platform_point);
2253 mbedtls_mpi_free(&shared_x);
2254 return ret == 0;
2255}
2256
2266static bool aes_256_cbc_decrypt_iv(const uint8_t *key, const uint8_t *iv,
2267 const uint8_t *input, size_t len, uint8_t *output) {
2268 mbedtls_aes_context aes;
2269 mbedtls_aes_init(&aes);
2270
2271 uint8_t iv_copy[16];
2272 memcpy(iv_copy, iv, 16); // mbedtls modifies IV during decrypt
2273
2274 int ret = mbedtls_aes_setkey_dec(&aes, key, 256);
2275 if (ret != 0) {
2276 mbedtls_aes_free(&aes);
2277 return false;
2278 }
2279
2280 ret = mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_DECRYPT, len, iv_copy, input, output);
2281 mbedtls_aes_free(&aes);
2282 return ret == 0;
2283}
2284
2293static bool aes_256_cbc_decrypt(const uint8_t *key, const uint8_t *input,
2294 size_t len, uint8_t *output) {
2295 uint8_t iv[16] = {0};
2296 return aes_256_cbc_decrypt_iv(key, iv, input, len, output);
2297}
2298
2307static bool aes_256_cbc_encrypt(const uint8_t *key, const uint8_t *input,
2308 size_t len, uint8_t *output) {
2309 mbedtls_aes_context aes;
2310 mbedtls_aes_init(&aes);
2311
2312 uint8_t iv[16] = {0}; // IV is all zeros for PIN protocol 1
2313
2314 int ret = mbedtls_aes_setkey_enc(&aes, key, 256);
2315 if (ret != 0) {
2316 mbedtls_aes_free(&aes);
2317 return false;
2318 }
2319
2320 ret = mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, len, iv, input, output);
2321 mbedtls_aes_free(&aes);
2322 return ret == 0;
2323}
2324
2333static bool aes_256_cbc_encrypt_p2(const uint8_t *key, const uint8_t *input,
2334 size_t len, uint8_t *output) {
2335 mbedtls_aes_context aes;
2336 mbedtls_aes_init(&aes);
2337
2338 // Generate random IV using TROPIC01 TRNG
2339 uint8_t iv[16];
2340 secure_random_fill(iv, 16);
2341
2342 // Copy IV to output first
2343 memcpy(output, iv, 16);
2344
2345 int ret = mbedtls_aes_setkey_enc(&aes, key, 256);
2346 if (ret != 0) {
2347 mbedtls_aes_free(&aes);
2348 return false;
2349 }
2350
2351 // Encrypt after the IV prefix
2352 ret = mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, len, iv, input, output + 16);
2353 mbedtls_aes_free(&aes);
2354 return ret == 0;
2355}
2356
2363static uint8_t client_pin_get_retries(uint8_t *response, uint16_t *response_len) {
2364 cbor_writer_t w;
2365 cbor_writer_init(&w, response + 1, *response_len - 1);
2366
2367 cbor_encode_map(&w, 2);
2368
2369 // pinRetries
2371 cbor_encode_uint(&w, g_client_pin.pin_retries);
2372
2373 // uvRetries (powerCycleState is optional and not used)
2375 cbor_encode_uint(&w, g_client_pin.uv_retries);
2376
2377 response[0] = CTAP2_OK;
2378 *response_len = 1 + cbor_writer_length(&w);
2379 return CTAP2_OK;
2380}
2381
2388static uint8_t client_pin_get_key_agreement(uint8_t *response, uint16_t *response_len) {
2389 if (!client_pin_init_ecdh()) {
2390 response[0] = CTAP2_ERR_OTHER;
2391 *response_len = 1;
2392 return CTAP2_ERR_OTHER;
2393 }
2394
2395 // Extract public key coordinates
2396 uint8_t pub_x[32], pub_y[32];
2397 mbedtls_mpi_write_binary(&g_client_pin.ecdh_key.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), pub_x, 32);
2398 mbedtls_mpi_write_binary(&g_client_pin.ecdh_key.MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), pub_y, 32);
2399
2400#if DEBUG_MODE
2401 LOG_I(TAG_PIN, "=== Our ECDH public key ===");
2402 LOG_I(TAG_PIN, "X: %02X%02X%02X%02X %02X%02X%02X%02X...",
2403 pub_x[0], pub_x[1], pub_x[2], pub_x[3], pub_x[4], pub_x[5], pub_x[6], pub_x[7]);
2404 LOG_I(TAG_PIN, "Y: %02X%02X%02X%02X %02X%02X%02X%02X...",
2405 pub_y[0], pub_y[1], pub_y[2], pub_y[3], pub_y[4], pub_y[5], pub_y[6], pub_y[7]);
2406#endif
2407
2408 cbor_writer_t w;
2409 cbor_writer_init(&w, response + 1, *response_len - 1);
2410
2411 cbor_encode_map(&w, 1);
2412
2413 // keyAgreement (COSE_Key, RFC 8152)
2415 cbor_encode_map(&w, 5);
2416
2417 // kty: EC2
2420
2421 // alg: ECDH-ES + HKDF-256
2424
2425 // crv: P-256
2428
2429 // x coordinate
2431 cbor_encode_bytes(&w, pub_x, 32);
2432
2433 // y coordinate
2435 cbor_encode_bytes(&w, pub_y, 32);
2436
2437 response[0] = CTAP2_OK;
2438 *response_len = 1 + cbor_writer_length(&w);
2439 LOG_I(TAG_PIN, "Sent key agreement");
2440 return CTAP2_OK;
2441}
2442
2451static uint8_t client_pin_get_pin_token(const uint8_t *params, uint16_t params_len,
2452 uint8_t *response, uint16_t *response_len) {
2453 // Check if PIN is blocked
2454 if (g_client_pin.pin_retries == 0) {
2455 response[0] = CTAP2_ERR_PIN_BLOCKED;
2456 *response_len = 1;
2457 return CTAP2_ERR_PIN_BLOCKED;
2458 }
2459
2460 // Check if FIDO2 PIN hash is available
2462 LOG_E(TAG_PIN, "FIDO2 hash not available - user must reset PIN");
2463 response[0] = CTAP2_ERR_PIN_NOT_SET;
2464 *response_len = 1;
2465 return CTAP2_ERR_PIN_NOT_SET;
2466 }
2467
2468 // Parse parameters
2469 cbor_reader_t r;
2470 cbor_reader_init(&r, params, params_len);
2471
2472 uint8_t platform_key_x[32] = {0};
2473 uint8_t platform_key_y[32] = {0};
2474 uint8_t pin_hash_enc[64] = {0}; // Can be 16 or 32 bytes (with padding)
2475 size_t pin_hash_enc_len = 0;
2476 uint8_t pin_protocol = 2; // Default to Protocol 2
2477 bool has_key = false, has_pin = false;
2478
2479 int map_size = cbor_read_map(&r);
2480 if (map_size < 0) {
2481 response[0] = CTAP2_ERR_INVALID_CBOR;
2482 *response_len = 1;
2484 }
2485
2486 for (int i = 0; i < map_size; i++) {
2487 // Read key - could be positive or negative, so use cbor_read_item
2488 cbor_item_t item;
2489 if (!cbor_read_item(&r, &item)) {
2490 LOG_E(TAG_PIN, "Failed to read map key %d", i);
2491 break;
2492 }
2493
2494 int64_t key;
2495 if (item.type == CBOR_UNSIGNED) {
2496 key = (int64_t)item.value;
2497 } else if (item.type == CBOR_NEGATIVE) {
2498 key = -1 - (int64_t)item.value;
2499 } else {
2500 LOG_E(TAG_PIN, "Unexpected key type: %d", item.type);
2501 cbor_skip_item(&r);
2502 continue;
2503 }
2504
2505 LOG_D(TAG_PIN, "Parsing key: %lld", key);
2506
2507 switch (key) {
2508 case CTAP2_PIN_PROTOCOL: {
2509 uint64_t proto;
2510 if (cbor_read_uint(&r, &proto)) {
2511 pin_protocol = (uint8_t)proto;
2512 LOG_I(TAG_PIN, "Client requested protocol: %d", pin_protocol);
2513 }
2514 break;
2515 }
2517 int cose_size = cbor_read_map(&r);
2518 LOG_D(TAG_PIN, "COSE_Key map size: %d", cose_size);
2519 if (cose_size < 0) break;
2520 for (int j = 0; j < cose_size; j++) {
2521 // COSE keys can be positive (kty=1, alg=3) or negative (crv=-1, x=-2, y=-3)
2522 cbor_item_t cose_item;
2523 if (!cbor_read_item(&r, &cose_item)) {
2524 LOG_E(TAG_PIN, "Failed to read COSE key %d", j);
2525 break;
2526 }
2527
2528 int64_t cose_key;
2529 if (cose_item.type == CBOR_UNSIGNED) {
2530 cose_key = (int64_t)cose_item.value;
2531 } else if (cose_item.type == CBOR_NEGATIVE) {
2532 cose_key = -1 - (int64_t)cose_item.value;
2533 } else {
2534 LOG_E(TAG_PIN, "Unexpected COSE key type: %d", cose_item.type);
2535 cbor_skip_item(&r);
2536 continue;
2537 }
2538
2539 LOG_D(TAG_PIN, "COSE key: %lld", cose_key);
2540
2541 if (cose_key == COSE_KEY_LABEL_X) { // x coordinate
2542 size_t x_len;
2543 if (cbor_read_bytes(&r, platform_key_x, 32, &x_len) && x_len == 32) {
2544 has_key = true;
2545 LOG_D(TAG_PIN, "Got x coordinate");
2546 }
2547 } else if (cose_key == COSE_KEY_LABEL_Y) { // y coordinate
2548 size_t y_len;
2549 cbor_read_bytes(&r, platform_key_y, 32, &y_len);
2550 LOG_D(TAG_PIN, "Got y coordinate");
2551 } else {
2552 cbor_skip_item(&r);
2553 }
2554 }
2555 break;
2556 }
2557 case CTAP2_PIN_HASH_ENC: {
2558 if (cbor_read_bytes(&r, pin_hash_enc, sizeof(pin_hash_enc), &pin_hash_enc_len)) {
2559 LOG_D(TAG_PIN, "pinHashEnc read OK, len=%zu", pin_hash_enc_len);
2560 if (pin_hash_enc_len == 16 || pin_hash_enc_len == 32 || pin_hash_enc_len == 64) {
2561 has_pin = true;
2562 LOG_D(TAG_PIN, "Got pinHashEnc (%zu bytes)", pin_hash_enc_len);
2563 } else {
2564 LOG_E(TAG_PIN, "pinHashEnc unexpected size: %zu", pin_hash_enc_len);
2565 }
2566 } else {
2567 LOG_E(TAG_PIN, "Failed to read pinHashEnc bytes");
2568 }
2569 break;
2570 }
2571 default:
2572 cbor_skip_item(&r);
2573 break;
2574 }
2575 }
2576
2577 if (!has_key || !has_pin) {
2578 LOG_E(TAG_PIN, "Missing keyAgreement or pinHashEnc");
2579 response[0] = CTAP2_ERR_MISSING_PARAMETER;
2580 *response_len = 1;
2582 }
2583
2584#if DEBUG_MODE
2585 // Log received pinHashEnc for debugging
2586 LOG_I(TAG_PIN, "Received pinHashEnc (%zu bytes):", pin_hash_enc_len);
2587 for (size_t i = 0; i < pin_hash_enc_len; i += 16) {
2588 size_t row_len = (pin_hash_enc_len - i < 16) ? (pin_hash_enc_len - i) : 16;
2589 char hex[64];
2590 char *p = hex;
2591 for (size_t j = 0; j < row_len; j++) {
2592 p += sprintf(p, "%02X ", pin_hash_enc[i + j]);
2593 }
2594 LOG_I(TAG_PIN, " %s", hex);
2595 }
2596#endif
2597
2598 // Compute shared secret
2599 uint8_t shared_secret[32];
2600 if (!client_pin_compute_shared_secret(platform_key_x, platform_key_y, pin_protocol, shared_secret)) {
2601 response[0] = CTAP2_ERR_OTHER;
2602 *response_len = 1;
2603 return CTAP2_ERR_OTHER;
2604 }
2605
2606#if DEBUG_MODE
2607 // Full platform key for verification
2608 LOG_I(TAG_PIN, "Platform (Chrome) public key X:");
2609 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2610 platform_key_x[0], platform_key_x[1], platform_key_x[2], platform_key_x[3],
2611 platform_key_x[4], platform_key_x[5], platform_key_x[6], platform_key_x[7],
2612 platform_key_x[8], platform_key_x[9], platform_key_x[10], platform_key_x[11],
2613 platform_key_x[12], platform_key_x[13], platform_key_x[14], platform_key_x[15]);
2614 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2615 platform_key_x[16], platform_key_x[17], platform_key_x[18], platform_key_x[19],
2616 platform_key_x[20], platform_key_x[21], platform_key_x[22], platform_key_x[23],
2617 platform_key_x[24], platform_key_x[25], platform_key_x[26], platform_key_x[27],
2618 platform_key_x[28], platform_key_x[29], platform_key_x[30], platform_key_x[31]);
2619 LOG_I(TAG_PIN, "Platform (Chrome) public key Y:");
2620 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2621 platform_key_y[0], platform_key_y[1], platform_key_y[2], platform_key_y[3],
2622 platform_key_y[4], platform_key_y[5], platform_key_y[6], platform_key_y[7],
2623 platform_key_y[8], platform_key_y[9], platform_key_y[10], platform_key_y[11],
2624 platform_key_y[12], platform_key_y[13], platform_key_y[14], platform_key_y[15]);
2625 LOG_I(TAG_PIN, " %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2626 platform_key_y[16], platform_key_y[17], platform_key_y[18], platform_key_y[19],
2627 platform_key_y[20], platform_key_y[21], platform_key_y[22], platform_key_y[23],
2628 platform_key_y[24], platform_key_y[25], platform_key_y[26], platform_key_y[27],
2629 platform_key_y[28], platform_key_y[29], platform_key_y[30], platform_key_y[31]);
2630#endif
2631
2632 // Decrypt pinHashEnc
2633 // Protocol 1: 16 bytes ciphertext with IV=0
2634 // Protocol 2: 32 bytes = IV (16) || ciphertext (16)
2635 uint8_t decrypted_pin_hash[16];
2636
2637 if (pin_protocol == 2 && pin_hash_enc_len == 32) {
2638 // Protocol 2: first 16 bytes are IV, next 16 are ciphertext
2639 const uint8_t *iv = pin_hash_enc;
2640 const uint8_t *ciphertext = pin_hash_enc + 16;
2641
2642 LOG_D(TAG_PIN, "Protocol 2 IV: %02X%02X%02X%02X %02X%02X%02X%02X...",
2643 iv[0], iv[1], iv[2], iv[3], iv[4], iv[5], iv[6], iv[7]);
2644 LOG_D(TAG_PIN, "Ciphertext: %02X%02X%02X%02X %02X%02X%02X%02X...",
2645 ciphertext[0], ciphertext[1], ciphertext[2], ciphertext[3],
2646 ciphertext[4], ciphertext[5], ciphertext[6], ciphertext[7]);
2647
2648 if (!aes_256_cbc_decrypt_iv(shared_secret, iv, ciphertext, 16, decrypted_pin_hash)) {
2649 LOG_E(TAG_PIN, "PIN decryption failed");
2650 response[0] = CTAP2_ERR_OTHER;
2651 *response_len = 1;
2652 return CTAP2_ERR_OTHER;
2653 }
2654 } else {
2655 // Protocol 1: IV is all zeros
2656 uint8_t decrypted[64];
2657 if (!aes_256_cbc_decrypt(shared_secret, pin_hash_enc, pin_hash_enc_len, decrypted)) {
2658 LOG_E(TAG_PIN, "PIN decryption failed");
2659 response[0] = CTAP2_ERR_OTHER;
2660 *response_len = 1;
2661 return CTAP2_ERR_OTHER;
2662 }
2663 memcpy(decrypted_pin_hash, decrypted, 16);
2664 }
2665
2666#if DEBUG_MODE
2667 LOG_D(TAG_PIN, "Decrypted PIN hash: %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2668 decrypted_pin_hash[0], decrypted_pin_hash[1], decrypted_pin_hash[2], decrypted_pin_hash[3],
2669 decrypted_pin_hash[4], decrypted_pin_hash[5], decrypted_pin_hash[6], decrypted_pin_hash[7],
2670 decrypted_pin_hash[8], decrypted_pin_hash[9], decrypted_pin_hash[10], decrypted_pin_hash[11],
2671 decrypted_pin_hash[12], decrypted_pin_hash[13], decrypted_pin_hash[14], decrypted_pin_hash[15]);
2672
2673 // Get stored hash for comparison
2674 uint8_t stored_hash[16];
2675 pin_storage_get_fido2_hash(stored_hash);
2676 LOG_D(TAG_PIN, "Stored FIDO2 hash: %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2677 stored_hash[0], stored_hash[1], stored_hash[2], stored_hash[3],
2678 stored_hash[4], stored_hash[5], stored_hash[6], stored_hash[7],
2679 stored_hash[8], stored_hash[9], stored_hash[10], stored_hash[11],
2680 stored_hash[12], stored_hash[13], stored_hash[14], stored_hash[15]);
2681
2682 // Debug: compute expected hash for "0000"
2683 uint8_t test_full[32];
2684 sha256((const uint8_t*)"0000", 4, test_full);
2685 LOG_D(TAG_PIN, "Expected for 0000: %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X",
2686 test_full[0], test_full[1], test_full[2], test_full[3],
2687 test_full[4], test_full[5], test_full[6], test_full[7],
2688 test_full[8], test_full[9], test_full[10], test_full[11],
2689 test_full[12], test_full[13], test_full[14], test_full[15]);
2690#endif
2691
2692 // Verify PIN hash
2693 if (!pin_storage_verify_fido2_hash(decrypted_pin_hash)) {
2694 g_client_pin.pin_retries--;
2695 LOG_W(TAG_PIN, "Invalid PIN, retries left: %d", g_client_pin.pin_retries);
2696
2697 if (g_client_pin.pin_retries == 0) {
2698 response[0] = CTAP2_ERR_PIN_BLOCKED;
2699 } else {
2700 response[0] = CTAP2_ERR_PIN_INVALID;
2701 }
2702 *response_len = 1;
2703 return response[0];
2704 }
2705
2706 // PIN correct - reset retries and generate pinToken
2707 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2709 g_client_pin.pin_token_valid = true;
2710
2711 // Encrypt pinToken with shared secret
2712 // Protocol 1: IV=0, returns ciphertext only (32 bytes)
2713 // Protocol 2: returns IV || ciphertext (16 + 32 = 48 bytes)
2714 uint8_t encrypted_token[PIN_TOKEN_SIZE + 16]; // Extra space for IV in Protocol 2
2715 size_t encrypted_len;
2716
2717 if (pin_protocol == 2) {
2718 if (!aes_256_cbc_encrypt_p2(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2719 response[0] = CTAP2_ERR_OTHER;
2720 *response_len = 1;
2721 return CTAP2_ERR_OTHER;
2722 }
2723 encrypted_len = PIN_TOKEN_SIZE + 16; // IV + ciphertext
2724 LOG_D(TAG_PIN, "Encrypted pinToken (Protocol 2, %zu bytes with IV)", encrypted_len);
2725 } else {
2726 if (!aes_256_cbc_encrypt(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2727 response[0] = CTAP2_ERR_OTHER;
2728 *response_len = 1;
2729 return CTAP2_ERR_OTHER;
2730 }
2731 encrypted_len = PIN_TOKEN_SIZE;
2732 LOG_D(TAG_PIN, "Encrypted pinToken (Protocol 1, %zu bytes)", encrypted_len);
2733 }
2734
2735 // Build response
2736 cbor_writer_t w;
2737 cbor_writer_init(&w, response + 1, *response_len - 1);
2738
2739 cbor_encode_map(&w, 1);
2740
2741 // pinUvAuthToken (encrypted)
2743 cbor_encode_bytes(&w, encrypted_token, encrypted_len);
2744
2745 response[0] = CTAP2_OK;
2746 *response_len = 1 + cbor_writer_length(&w);
2747
2748 // Legacy token (0x05) has all permissions
2749 g_client_pin.token_permissions = 0xFF;
2750 g_client_pin.token_rp_id_set = false;
2751
2752 LOG_I(TAG_PIN, "PIN verified, token issued (legacy, all permissions)");
2753 return CTAP2_OK;
2754}
2755
2764static uint8_t client_pin_get_pin_uv_auth_token(const uint8_t *params, uint16_t params_len,
2765 uint8_t *response, uint16_t *response_len) {
2766 // Check if PIN is blocked
2767 if (g_client_pin.pin_retries == 0) {
2768 response[0] = CTAP2_ERR_PIN_BLOCKED;
2769 *response_len = 1;
2770 return CTAP2_ERR_PIN_BLOCKED;
2771 }
2772
2773 // Check if FIDO2 PIN hash is available
2775 LOG_E(TAG_PIN, "FIDO2 hash not available - user must reset PIN");
2776 response[0] = CTAP2_ERR_PIN_NOT_SET;
2777 *response_len = 1;
2778 return CTAP2_ERR_PIN_NOT_SET;
2779 }
2780
2781 // Parse parameters
2782 cbor_reader_t r;
2783 cbor_reader_init(&r, params, params_len);
2784
2785 uint8_t platform_key_x[32] = {0};
2786 uint8_t platform_key_y[32] = {0};
2787 uint8_t pin_hash_enc[64] = {0};
2788 size_t pin_hash_enc_len = 0;
2789 uint8_t pin_protocol = 2;
2790 uint8_t permissions = 0;
2791 char rp_id[64] = {0};
2792 bool has_key = false, has_pin = false, has_permissions = false;
2793
2794 int map_size = cbor_read_map(&r);
2795 if (map_size < 0) {
2796 response[0] = CTAP2_ERR_INVALID_CBOR;
2797 *response_len = 1;
2799 }
2800
2801 for (int i = 0; i < map_size; i++) {
2802 cbor_item_t item;
2803 if (!cbor_read_item(&r, &item)) break;
2804
2805 int64_t key;
2806 if (item.type == CBOR_UNSIGNED) {
2807 key = (int64_t)item.value;
2808 } else if (item.type == CBOR_NEGATIVE) {
2809 key = -1 - (int64_t)item.value;
2810 } else {
2811 cbor_skip_item(&r);
2812 continue;
2813 }
2814
2815 switch (key) {
2816 case CTAP2_PIN_PROTOCOL: {
2817 uint64_t proto;
2818 if (cbor_read_uint(&r, &proto)) {
2819 pin_protocol = (uint8_t)proto;
2820 }
2821 break;
2822 }
2824 int cose_size = cbor_read_map(&r);
2825 if (cose_size < 0) break;
2826 for (int j = 0; j < cose_size; j++) {
2827 cbor_item_t cose_item;
2828 if (!cbor_read_item(&r, &cose_item)) break;
2829
2830 int64_t cose_key;
2831 if (cose_item.type == CBOR_UNSIGNED) {
2832 cose_key = (int64_t)cose_item.value;
2833 } else if (cose_item.type == CBOR_NEGATIVE) {
2834 cose_key = -1 - (int64_t)cose_item.value;
2835 } else {
2836 cbor_skip_item(&r);
2837 continue;
2838 }
2839
2840 if (cose_key == COSE_KEY_LABEL_X) { // x coordinate
2841 size_t x_len;
2842 if (cbor_read_bytes(&r, platform_key_x, 32, &x_len) && x_len == 32) {
2843 has_key = true;
2844 }
2845 } else if (cose_key == COSE_KEY_LABEL_Y) { // y coordinate
2846 size_t y_len;
2847 cbor_read_bytes(&r, platform_key_y, 32, &y_len);
2848 } else {
2849 cbor_skip_item(&r);
2850 }
2851 }
2852 break;
2853 }
2854 case CTAP2_PIN_HASH_ENC: {
2855 if (cbor_read_bytes(&r, pin_hash_enc, sizeof(pin_hash_enc), &pin_hash_enc_len)) {
2856 if (pin_hash_enc_len == 16 || pin_hash_enc_len == 32 || pin_hash_enc_len == 64) {
2857 has_pin = true;
2858 }
2859 }
2860 break;
2861 }
2862 case CTAP2_PIN_PERMISSIONS: {
2863 uint64_t perm;
2864 if (cbor_read_uint(&r, &perm)) {
2865 permissions = (uint8_t)perm;
2866 has_permissions = true;
2867 LOG_I(TAG_PIN, "Requested permissions: 0x%02X", permissions);
2868 }
2869 break;
2870 }
2872 size_t rp_len;
2873 if (cbor_read_text(&r, rp_id, sizeof(rp_id) - 1, &rp_len)) {
2874 LOG_I(TAG_PIN, "Requested rpId: %s", rp_id);
2875 }
2876 break;
2877 }
2878 default:
2879 cbor_skip_item(&r);
2880 break;
2881 }
2882 }
2883
2884 if (!has_key || !has_pin) {
2885 LOG_E(TAG_PIN, "Missing keyAgreement or pinHashEnc");
2886 response[0] = CTAP2_ERR_MISSING_PARAMETER;
2887 *response_len = 1;
2889 }
2890
2891 if (!has_permissions) {
2892 LOG_E(TAG_PIN, "Missing permissions parameter");
2893 response[0] = CTAP2_ERR_MISSING_PARAMETER;
2894 *response_len = 1;
2896 }
2897
2898 // Compute shared secret
2899 uint8_t shared_secret[32];
2900 if (!client_pin_compute_shared_secret(platform_key_x, platform_key_y, pin_protocol, shared_secret)) {
2901 response[0] = CTAP2_ERR_OTHER;
2902 *response_len = 1;
2903 return CTAP2_ERR_OTHER;
2904 }
2905
2906 // Decrypt pinHashEnc
2907 uint8_t decrypted_pin_hash[16];
2908 if (pin_protocol == 2 && pin_hash_enc_len == 32) {
2909 const uint8_t *iv = pin_hash_enc;
2910 const uint8_t *ciphertext = pin_hash_enc + 16;
2911 if (!aes_256_cbc_decrypt_iv(shared_secret, iv, ciphertext, 16, decrypted_pin_hash)) {
2912 LOG_E(TAG_PIN, "PIN decryption failed");
2913 response[0] = CTAP2_ERR_OTHER;
2914 *response_len = 1;
2915 return CTAP2_ERR_OTHER;
2916 }
2917 } else {
2918 uint8_t decrypted[64];
2919 if (!aes_256_cbc_decrypt(shared_secret, pin_hash_enc, pin_hash_enc_len, decrypted)) {
2920 LOG_E(TAG_PIN, "PIN decryption failed");
2921 response[0] = CTAP2_ERR_OTHER;
2922 *response_len = 1;
2923 return CTAP2_ERR_OTHER;
2924 }
2925 memcpy(decrypted_pin_hash, decrypted, 16);
2926 }
2927
2928 // Verify PIN hash
2929 if (!pin_storage_verify_fido2_hash(decrypted_pin_hash)) {
2930 g_client_pin.pin_retries--;
2931 LOG_W(TAG_PIN, "Invalid PIN, retries left: %d", g_client_pin.pin_retries);
2932 response[0] = (g_client_pin.pin_retries == 0) ? CTAP2_ERR_PIN_BLOCKED : CTAP2_ERR_PIN_INVALID;
2933 *response_len = 1;
2934 return response[0];
2935 }
2936
2937 // PIN correct - reset retries and generate pinToken
2938 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2940 g_client_pin.pin_token_valid = true;
2941
2942 // Store permissions
2943 g_client_pin.token_permissions = permissions;
2944 if (rp_id[0]) {
2945 sha256_str(rp_id, g_client_pin.token_rp_id_hash);
2946 g_client_pin.token_rp_id_set = true;
2947 } else {
2948 g_client_pin.token_rp_id_set = false;
2949 }
2950
2951 // Encrypt pinToken
2952 uint8_t encrypted_token[PIN_TOKEN_SIZE + 16];
2953 size_t encrypted_len;
2954
2955 if (pin_protocol == 2) {
2956 if (!aes_256_cbc_encrypt_p2(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2957 response[0] = CTAP2_ERR_OTHER;
2958 *response_len = 1;
2959 return CTAP2_ERR_OTHER;
2960 }
2961 encrypted_len = PIN_TOKEN_SIZE + 16;
2962 } else {
2963 if (!aes_256_cbc_encrypt(shared_secret, g_client_pin.pin_token, PIN_TOKEN_SIZE, encrypted_token)) {
2964 response[0] = CTAP2_ERR_OTHER;
2965 *response_len = 1;
2966 return CTAP2_ERR_OTHER;
2967 }
2968 encrypted_len = PIN_TOKEN_SIZE;
2969 }
2970
2971 // Build response
2972 cbor_writer_t w;
2973 cbor_writer_init(&w, response + 1, *response_len - 1);
2974
2975 cbor_encode_map(&w, 1);
2977 cbor_encode_bytes(&w, encrypted_token, encrypted_len);
2978
2979 response[0] = CTAP2_OK;
2980 *response_len = 1 + cbor_writer_length(&w);
2981 LOG_I(TAG_PIN, "PIN verified, token issued with permissions=0x%02X", permissions);
2982 return CTAP2_OK;
2983}
2984
2993uint8_t ctap2_client_pin(const uint8_t *params, uint16_t params_len,
2994 uint8_t *response, uint16_t *response_len) {
2995 // Initialize if needed
2996 if (!g_client_pin.initialized) {
2997 g_client_pin.pin_retries = PIN_RETRIES_MAX;
2998 g_client_pin.uv_retries = PIN_UV_RETRIES_MAX;
2999 g_client_pin.initialized = true;
3000 }
3001
3002 // Parse subCommand
3003 cbor_reader_t r;
3004 cbor_reader_init(&r, params, params_len);
3005
3006 int map_size = cbor_read_map(&r);
3007 if (map_size < 0) {
3008 response[0] = CTAP2_ERR_INVALID_CBOR;
3009 *response_len = 1;
3011 }
3012
3013 uint64_t pin_protocol = 0;
3014 uint64_t sub_command = 0;
3015
3016 for (int i = 0; i < map_size; i++) {
3017 uint64_t key;
3018 if (!cbor_read_uint(&r, &key)) break;
3019
3020 if (key == 0x01) { // pinUvAuthProtocol
3021 cbor_read_uint(&r, &pin_protocol);
3022 } else if (key == 0x02) { // subCommand
3023 cbor_read_uint(&r, &sub_command);
3024 } else {
3025 cbor_skip_item(&r);
3026 }
3027 }
3028
3029 LOG_I(TAG_PIN, "ClientPIN: protocol=%llu, subCommand=0x%02llx", pin_protocol, sub_command);
3030
3031 // We only support protocol 2
3032 if (pin_protocol != 0 && pin_protocol != PIN_PROTOCOL_VERSION) {
3033 response[0] = CTAP1_ERR_INVALID_PARAMETER;
3034 *response_len = 1;
3036 }
3037
3038 switch (sub_command) {
3040 return client_pin_get_retries(response, response_len);
3041
3043 return client_pin_get_key_agreement(response, response_len);
3044
3046 return client_pin_get_pin_token(params, params_len, response, response_len);
3047
3049 return client_pin_get_pin_uv_auth_token(params, params_len, response, response_len);
3050
3051 case PIN_CMD_SET_PIN:
3052 case PIN_CMD_CHANGE_PIN:
3053 // Not supported - PIN is set via badge UI
3054 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
3055 *response_len = 1;
3057
3058 default:
3059 LOG_W(TAG_PIN, "Unknown subCommand: 0x%02lx", sub_command);
3060 response[0] = CTAP1_ERR_INVALID_COMMAND;
3061 *response_len = 1;
3063 }
3064}
3065
3072uint8_t ctap2_reset(uint8_t *response, uint16_t *response_len) {
3073 // CTAP2.1 6.4: reset requires explicit user presence.
3075 response[0] = CTAP2_ERR_OPERATION_DENIED;
3076 *response_len = 1;
3078 }
3079 if (!fido2_factory_reset()) {
3080 response[0] = CTAP2_ERR_OTHER;
3081 *response_len = 1;
3082 return CTAP2_ERR_OTHER;
3083 }
3084
3085 response[0] = CTAP2_OK;
3086 *response_len = 1;
3087 LOG_I(TAG, "Factory reset complete");
3088 return CTAP2_OK;
3089}
3090
3097static bool cred_mgmt_slot_has_key(uint8_t slot) {
3098 uint8_t pubkey[64];
3099 if (!fido2_storage_get_pubkey(slot, pubkey)) {
3100 LOG_W(TAG, "credMgmt: skipping slot %d (no SE key)", slot);
3101 return false;
3102 }
3103 return true;
3104}
3105
3110static uint8_t cred_mgmt_count_unique_rps(void) {
3111 // Large buffer in PSRAM (32 * 32 = 1024 bytes)
3112 EXT_RAM_BSS_ATTR static uint8_t unique_hashes[FIDO2_MAX_CREDENTIALS][32];
3113 uint8_t count = 0;
3114
3115 for (uint8_t slot = 0; slot < FIDO2_MAX_CREDENTIALS; slot++) {
3116 if (!fido2_storage_is_resident(slot)) continue;
3117
3118 fido2_credential_info_t info;
3119 if (!fido2_storage_get_credential(slot, &info)) continue;
3120 if (!cred_mgmt_slot_has_key(slot)) continue;
3121
3122 // Check if this RP hash is already in our list
3123 bool found = false;
3124 for (uint8_t j = 0; j < count; j++) {
3125 if (memcmp(unique_hashes[j], info.rp_id_hash, 32) == 0) {
3126 found = true;
3127 break;
3128 }
3129 }
3130
3131 if (!found && count < FIDO2_MAX_CREDENTIALS) {
3132 memcpy(unique_hashes[count], info.rp_id_hash, 32);
3133 g_cred_mgmt.rp_slots[count] = slot; // Store a representative slot
3134 count++;
3135 }
3136 }
3137
3138 return count;
3139}
3140
3146static uint8_t cred_mgmt_find_creds_for_rp(const uint8_t *rp_id_hash) {
3147 uint8_t count = 0;
3148
3149 for (uint8_t slot = 0; slot < FIDO2_MAX_CREDENTIALS && count < FIDO2_MAX_CREDENTIALS; slot++) {
3150 if (!fido2_storage_is_resident(slot)) continue;
3151
3152 fido2_credential_info_t info;
3153 if (!fido2_storage_get_credential(slot, &info)) continue;
3154
3155 if (memcmp(info.rp_id_hash, rp_id_hash, 32) != 0) continue;
3156 if (!cred_mgmt_slot_has_key(slot)) continue;
3157
3158 g_cred_mgmt.cred_slots[count++] = slot;
3159 }
3160
3161 return count;
3162}
3163
3171static bool cred_mgmt_encode_rp(cbor_writer_t *w, uint8_t slot, bool include_total) {
3172 fido2_credential_info_t info;
3173 if (!fido2_storage_get_credential(slot, &info)) return false;
3174
3175 // Map with 2 or 3 entries
3176 cbor_encode_map(w, include_total ? 3 : 2);
3177
3178 // rp (map with id)
3180 cbor_encode_map(w, 1);
3181 cbor_encode_text(w, "id");
3182 cbor_encode_text(w, info.rp_id);
3183
3184 // rpIDHash
3186 cbor_encode_bytes(w, info.rp_id_hash, 32);
3187
3188 // totalRPs (only in first response)
3189 if (include_total) {
3191 cbor_encode_uint(w, g_cred_mgmt.rp_count);
3192 }
3193
3194 return true;
3195}
3196
3204static bool cred_mgmt_encode_credential(cbor_writer_t *w, uint8_t slot, bool include_total) {
3205 fido2_credential_info_t info;
3206 if (!fido2_storage_get_credential(slot, &info)) return false;
3207
3208 uint8_t cred_id[FIDO2_CRED_ID_LEN];
3209 if (!fido2_storage_get_cred_id(slot, cred_id)) return false;
3210
3211 uint8_t pubkey[64];
3212 if (!fido2_storage_get_pubkey(slot, pubkey)) return false;
3213
3214 // Map with 4 or 5 entries
3215 cbor_encode_map(w, include_total ? 5 : 4);
3216
3217 // user
3219 cbor_encode_map(w, info.user_name[0] ? 2 : 1);
3220 cbor_encode_text(w, "id");
3221 cbor_encode_bytes(w, info.user_id, info.user_id_len);
3222 if (info.user_name[0]) {
3223 cbor_encode_text(w, "name");
3224 cbor_encode_text(w, info.user_name);
3225 }
3226
3227 // credentialID (PublicKeyCredentialDescriptor)
3229 cbor_encode_map(w, 2);
3230 // Canonical order: "id" (len 2) before "type" (len 4)
3231 cbor_encode_text(w, "id");
3233 cbor_encode_text(w, "type");
3234 cbor_encode_text(w, "public-key");
3235
3236 // publicKey (COSE_Key, RFC 8152)
3238 if (info.curve == CDC_CURVE_ED25519) {
3240 } else {
3241 cbor_encode_cose_key_p256(w, pubkey, pubkey + 32);
3242 }
3243
3244 // totalCredentials (only in first response)
3245 if (include_total) {
3247 cbor_encode_uint(w, g_cred_mgmt.cred_count);
3248 }
3249
3250 // credProtect
3252 cbor_encode_uint(w, info.cred_protect ? info.cred_protect : 1);
3253
3254 return true;
3255}
3256
3265uint8_t ctap2_cred_management(const uint8_t *params, uint16_t params_len,
3266 uint8_t *response, uint16_t *response_len) {
3267 // Parse parameters
3268 if (params_len < 1) {
3269 response[0] = CTAP2_ERR_INVALID_CBOR;
3270 *response_len = 1;
3272 }
3273
3274 cbor_reader_t r;
3275 cbor_reader_init(&r, params, params_len);
3276
3277 int map_count = cbor_read_map(&r);
3278 if (map_count < 1) {
3279 response[0] = CTAP2_ERR_INVALID_CBOR;
3280 *response_len = 1;
3282 }
3283
3284 uint8_t subcommand = 0;
3285 uint8_t rp_id_hash[32] = {0};
3286 bool has_rp_id_hash = false;
3287 uint8_t cred_id[FIDO2_CRED_ID_LEN] = {0};
3288 uint16_t cred_id_len = 0;
3289 bool has_cred_id = false;
3290
3291 // Parse map entries
3292 for (int i = 0; i < map_count; i++) {
3293 uint64_t key;
3294 if (!cbor_read_uint(&r, &key)) {
3295 cbor_skip_item(&r);
3296 continue;
3297 }
3298
3299 switch (key) {
3301 {
3302 uint64_t cmd;
3303 if (cbor_read_uint(&r, &cmd)) {
3304 subcommand = (uint8_t)cmd;
3305 }
3306 }
3307 break;
3308
3310 {
3311 int sub_count = cbor_read_map(&r);
3312 for (int j = 0; j < sub_count; j++) {
3313 uint64_t sub_key;
3314 if (!cbor_read_uint(&r, &sub_key)) {
3315 cbor_skip_item(&r);
3316 cbor_skip_item(&r);
3317 continue;
3318 }
3319
3320 if (sub_key == CTAP2_CM_SUB_RP_ID_HASH) {
3321 size_t len;
3322 if (cbor_read_bytes(&r, rp_id_hash, 32, &len) && len == 32) {
3323 has_rp_id_hash = true;
3324 }
3325 } else if (sub_key == CTAP2_CM_SUB_CREDENTIAL_ID) {
3326 int cred_map = cbor_read_map(&r);
3327 for (int k = 0; k < cred_map; k++) {
3328 char cred_key[16];
3329 size_t key_len;
3330 if (cbor_read_text(&r, cred_key, sizeof(cred_key), &key_len)) {
3331 if (strcmp(cred_key, "id") == 0) {
3332 size_t len;
3333 if (cbor_read_bytes(&r, cred_id, FIDO2_CRED_ID_LEN, &len)) {
3334 cred_id_len = len;
3335 has_cred_id = true;
3336 }
3337 } else {
3338 cbor_skip_item(&r);
3339 }
3340 } else {
3341 cbor_skip_item(&r);
3342 cbor_skip_item(&r);
3343 }
3344 }
3345 } else {
3346 cbor_skip_item(&r);
3347 }
3348 }
3349 }
3350 break;
3351
3354 // We skip PIN auth verification for now
3355 // In production, should verify pinUvAuthParam
3356 cbor_skip_item(&r);
3357 break;
3358
3359 default:
3360 cbor_skip_item(&r);
3361 break;
3362 }
3363 }
3364
3365 LOG_I(TAG, "credMgmt subCmd=0x%02X", subcommand);
3366
3367 // CTAP2.1 6.8: credentialManagement requires a valid pinUvAuthToken. Block
3368 // unauthenticated enumeration/deletion of resident credentials.
3369 if (!g_client_pin.pin_token_valid) {
3370 response[0] = CTAP2_ERR_PIN_AUTH_INVALID;
3371 *response_len = 1;
3373 }
3374
3375 cbor_writer_t w;
3376 cbor_writer_init(&w, response + 1, *response_len - 1);
3377
3378 switch (subcommand) {
3380 {
3381 // Count resident credentials
3382 uint8_t existing = 0;
3383 for (uint8_t slot = 0; slot < FIDO2_MAX_CREDENTIALS; slot++) {
3384 if (fido2_storage_is_resident(slot)) existing++;
3385 }
3386
3387 cbor_encode_map(&w, 2);
3388
3389 // existingResidentCredentialsCount
3391 cbor_encode_uint(&w, existing);
3392
3393 // maxPossibleRemainingResidentCredentialsCount
3396
3397 LOG_I(TAG, "credMgmt metadata: %d existing, %d remaining",
3398 existing, FIDO2_MAX_CREDENTIALS - existing);
3399 }
3400 break;
3401
3403 {
3405 g_cred_mgmt.rp_index = 0;
3406
3407 if (g_cred_mgmt.rp_count == 0) {
3408 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3409 *response_len = 1;
3411 }
3412
3413 if (!cred_mgmt_encode_rp(&w, g_cred_mgmt.rp_slots[0], true)) {
3414 response[0] = CTAP2_ERR_OTHER;
3415 *response_len = 1;
3416 return CTAP2_ERR_OTHER;
3417 }
3418 g_cred_mgmt.rp_index = 1;
3419
3420 LOG_I(TAG, "credMgmt enumerateRPs: %d unique RPs", g_cred_mgmt.rp_count);
3421 }
3422 break;
3423
3425 {
3426 if (g_cred_mgmt.rp_index >= g_cred_mgmt.rp_count) {
3427 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3428 *response_len = 1;
3430 }
3431
3432 if (!cred_mgmt_encode_rp(&w, g_cred_mgmt.rp_slots[g_cred_mgmt.rp_index], false)) {
3433 response[0] = CTAP2_ERR_OTHER;
3434 *response_len = 1;
3435 return CTAP2_ERR_OTHER;
3436 }
3437 g_cred_mgmt.rp_index++;
3438 }
3439 break;
3440
3442 {
3443 if (!has_rp_id_hash) {
3444 response[0] = CTAP2_ERR_MISSING_PARAMETER;
3445 *response_len = 1;
3447 }
3448
3449 memcpy(g_cred_mgmt.current_rp_id_hash, rp_id_hash, 32);
3451 g_cred_mgmt.cred_index = 0;
3452
3453 if (g_cred_mgmt.cred_count == 0) {
3454 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3455 *response_len = 1;
3457 }
3458
3459 if (!cred_mgmt_encode_credential(&w, g_cred_mgmt.cred_slots[0], true)) {
3460 response[0] = CTAP2_ERR_OTHER;
3461 *response_len = 1;
3462 return CTAP2_ERR_OTHER;
3463 }
3464 g_cred_mgmt.cred_index = 1;
3465
3466 LOG_I(TAG, "credMgmt enumerateCreds: %d credentials for RP", g_cred_mgmt.cred_count);
3467 }
3468 break;
3469
3471 {
3472 if (g_cred_mgmt.cred_index >= g_cred_mgmt.cred_count) {
3473 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3474 *response_len = 1;
3476 }
3477
3478 if (!cred_mgmt_encode_credential(&w, g_cred_mgmt.cred_slots[g_cred_mgmt.cred_index], false)) {
3479 response[0] = CTAP2_ERR_OTHER;
3480 *response_len = 1;
3481 return CTAP2_ERR_OTHER;
3482 }
3483 g_cred_mgmt.cred_index++;
3484 }
3485 break;
3486
3488 {
3489 if (!has_cred_id) {
3490 response[0] = CTAP2_ERR_MISSING_PARAMETER;
3491 *response_len = 1;
3493 }
3494
3495 // Find credential by ID
3496 int8_t slot = fido2_storage_find_slot_by_cred_id(cred_id, cred_id_len);
3497 if (slot < 0) {
3498 response[0] = CTAP2_ERR_NO_CREDENTIALS;
3499 *response_len = 1;
3501 }
3502
3503 // Delete it
3505 response[0] = CTAP2_ERR_OTHER;
3506 *response_len = 1;
3507 return CTAP2_ERR_OTHER;
3508 }
3509
3510 LOG_I(TAG, "credMgmt deleted credential slot %d", slot);
3511
3512 // Success - empty response
3513 response[0] = CTAP2_OK;
3514 *response_len = 1;
3515 return CTAP2_OK;
3516 }
3517
3518 default:
3519 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION;
3520 *response_len = 1;
3522 }
3523
3524 if (cbor_writer_error(&w)) {
3525 response[0] = CTAP2_ERR_OTHER;
3526 *response_len = 1;
3527 return CTAP2_ERR_OTHER;
3528 }
3529
3530 response[0] = CTAP2_OK;
3531 *response_len = 1 + cbor_writer_length(&w);
3532 return CTAP2_OK;
3533}
3534
3541uint8_t ctap2_selection(uint8_t *response, uint16_t *response_len) {
3542 // authenticatorSelection carries no RP context and requires user presence only
3543 if (!wait_for_user_presence(NULL, FIDO2_ACTION_SELECT, NULL)) {
3544 response[0] = CTAP2_ERR_OPERATION_DENIED;
3545 *response_len = 1;
3547 }
3548
3549 response[0] = CTAP2_OK;
3550 *response_len = 1;
3551 return CTAP2_OK;
3552}
3553
3564static uint8_t verify_token_over_message(const uint8_t *msg, size_t msg_len,
3565 uint8_t protocol, const uint8_t *param,
3566 size_t param_len, uint8_t required_perm) {
3567 if (!g_client_pin.pin_token_valid) {
3569 }
3570 // token_permissions == 0 denotes a legacy token that carries all permissions.
3571 if (required_perm && g_client_pin.token_permissions != 0 &&
3572 !(g_client_pin.token_permissions & required_perm)) {
3574 }
3575 uint8_t expected[32];
3576 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
3577 g_client_pin.pin_token, PIN_TOKEN_SIZE,
3578 msg, msg_len, expected);
3579 size_t compare_len = (protocol == 2) ? 32 : 16;
3580 if (param_len < compare_len || memcmp(param, expected, compare_len) != 0) {
3582 }
3583 return CTAP2_OK;
3584}
3585
3586// In-progress largeBlobs write session (small) plus its PSRAM accumulation
3587// buffer and a separate per-fragment CBOR scratch buffer.
3591
3602
3606uint8_t ctap2_large_blobs(const uint8_t *params, uint16_t params_len,
3607 uint8_t *response, uint16_t *response_len) {
3608 cbor_reader_t r;
3609 cbor_reader_init(&r, params, params_len);
3610 int n = cbor_read_map(&r);
3611 if (n < 0) {
3612 response[0] = CTAP2_ERR_INVALID_CBOR; *response_len = 1; return CTAP2_ERR_INVALID_CBOR;
3613 }
3614
3615 bool has_get = false, has_set = false, has_offset = false, has_length = false;
3616 uint64_t get_len = 0, total_len = 0, offset = 0, pin_proto = 0;
3617 size_t set_len = 0;
3618 uint8_t pin_auth[64];
3619 size_t pin_auth_len = 0;
3620
3621 for (int i = 0; i < n; i++) {
3622 uint64_t key = 0;
3623 if (!cbor_read_uint(&r, &key)) {
3624 response[0] = CTAP2_ERR_INVALID_CBOR; *response_len = 1; return CTAP2_ERR_INVALID_CBOR;
3625 }
3626 switch (key) {
3627 case CTAP2_LB_GET:
3628 cbor_read_uint(&r, &get_len); has_get = true; break;
3629 case CTAP2_LB_SET: {
3630 size_t l = 0;
3632 set_len = l; has_set = true; break;
3633 }
3634 case CTAP2_LB_OFFSET:
3635 cbor_read_uint(&r, &offset); has_offset = true; break;
3636 case CTAP2_LB_LENGTH:
3637 cbor_read_uint(&r, &total_len); has_length = true; break;
3639 size_t l = 0;
3640 cbor_read_bytes(&r, pin_auth, sizeof(pin_auth), &l);
3641 pin_auth_len = l; break;
3642 }
3644 cbor_read_uint(&r, &pin_proto); break;
3645 default:
3646 cbor_skip_item(&r); break;
3647 }
3648 }
3649
3650 // Exactly one of get/set must be present, and offset is mandatory.
3651 if (has_get == has_set || !has_offset) {
3652 response[0] = CTAP1_ERR_INVALID_PARAMETER; *response_len = 1; return CTAP1_ERR_INVALID_PARAMETER;
3653 }
3654
3655 if (has_get) {
3656 uint16_t stored_len = 0;
3658 response[0] = CTAP2_ERR_OTHER; *response_len = 1; return CTAP2_ERR_OTHER;
3659 }
3660 if (offset > stored_len) {
3661 response[0] = CTAP1_ERR_INVALID_LENGTH; *response_len = 1; return CTAP1_ERR_INVALID_LENGTH;
3662 }
3663 uint16_t avail = stored_len - static_cast<uint16_t>(offset);
3664 uint16_t chunk = (get_len < avail) ? static_cast<uint16_t>(get_len) : avail;
3665 cbor_writer_t w;
3666 cbor_writer_init(&w, response + 1, *response_len - 1);
3667 cbor_encode_map(&w, 1);
3669 cbor_encode_bytes(&w, g_large_blob_buf + offset, chunk);
3670 if (cbor_writer_error(&w)) {
3671 response[0] = CTAP2_ERR_OTHER; *response_len = 1; return CTAP2_ERR_OTHER;
3672 }
3673 response[0] = CTAP2_OK;
3674 *response_len = 1 + cbor_writer_length(&w);
3675 return CTAP2_OK;
3676 }
3677
3678 // --- set path ---
3679 if (offset == 0) {
3680 if (!has_length) {
3681 response[0] = CTAP1_ERR_INVALID_PARAMETER; *response_len = 1; return CTAP1_ERR_INVALID_PARAMETER;
3682 }
3683 uint8_t st = lb_status(g_large_blob_session.begin(g_large_blob_buf, sizeof(g_large_blob_buf), total_len));
3684 if (st != CTAP2_OK) {
3685 g_large_blob_session.reset();
3686 response[0] = st; *response_len = 1; return st;
3687 }
3688 } else if (!g_large_blob_session.active() || offset != g_large_blob_session.nextOffset()) {
3689 response[0] = CTAP1_ERR_INVALID_SEQ; *response_len = 1; return CTAP1_ERR_INVALID_SEQ;
3690 }
3691
3692 // A write requires pinUvAuth with the largeBlobWrite permission when a PIN is set.
3694 uint8_t msg[32 + 1 + 1 + 4 + 32];
3695 memset(msg, 0xff, 32);
3696 msg[32] = CTAP2_CMD_LARGE_BLOBS; // 0x0c
3697 msg[33] = 0x00;
3698 msg[34] = static_cast<uint8_t>(offset & 0xff);
3699 msg[35] = static_cast<uint8_t>((offset >> 8) & 0xff);
3700 msg[36] = static_cast<uint8_t>((offset >> 16) & 0xff);
3701 msg[37] = static_cast<uint8_t>((offset >> 24) & 0xff);
3702 sha256(g_large_blob_chunk, set_len, msg + 38);
3703 uint8_t st = verify_token_over_message(msg, sizeof(msg), static_cast<uint8_t>(pin_proto),
3704 pin_auth, pin_auth_len, PIN_PERM_LARGE_BLOB_WRITE);
3705 if (st != CTAP2_OK) {
3706 g_large_blob_session.reset();
3707 response[0] = st; *response_len = 1; return st;
3708 }
3709 }
3710
3711 uint8_t st = lb_status(g_large_blob_session.append(static_cast<uint32_t>(offset),
3713 static_cast<uint16_t>(set_len)));
3714 if (st != CTAP2_OK) {
3715 g_large_blob_session.reset();
3716 response[0] = st; *response_len = 1; return st;
3717 }
3718
3719 if (g_large_blob_session.complete()) {
3720 const uint8_t *blob = g_large_blob_session.data();
3721 uint16_t blob_len = g_large_blob_session.length();
3722 uint8_t sum[32];
3723 sha256(blob, blob_len - 16, sum);
3724 if (memcmp(sum, blob + blob_len - 16, 16) != 0) {
3725 g_large_blob_session.reset();
3726 response[0] = CTAP2_ERR_INTEGRITY_FAILURE; *response_len = 1; return CTAP2_ERR_INTEGRITY_FAILURE;
3727 }
3728 bool ok = fido2_storage_largeblob_set(blob, blob_len);
3729 g_large_blob_session.reset();
3730 if (!ok) {
3731 response[0] = CTAP2_ERR_OTHER; *response_len = 1; return CTAP2_ERR_OTHER;
3732 }
3733 }
3734
3735 response[0] = CTAP2_OK;
3736 *response_len = 1;
3737 return CTAP2_OK;
3738}
3739
3746uint8_t ctap2_config(const uint8_t *params, uint16_t params_len,
3747 uint8_t *response, uint16_t *response_len) {
3748 cbor_reader_t r;
3749 cbor_reader_init(&r, params, params_len);
3750 int n = cbor_read_map(&r);
3751 if (n < 0) {
3752 response[0] = CTAP2_ERR_INVALID_CBOR; *response_len = 1; return CTAP2_ERR_INVALID_CBOR;
3753 }
3754
3755 uint64_t subcmd = 0, pin_proto = 0;
3756 bool has_sub = false;
3757 const uint8_t *sub_params = nullptr;
3758 size_t sub_params_len = 0;
3759 uint8_t pin_auth[64];
3760 size_t pin_auth_len = 0;
3761
3762 for (int i = 0; i < n; i++) {
3763 uint64_t key = 0;
3764 if (!cbor_read_uint(&r, &key)) {
3765 response[0] = CTAP2_ERR_INVALID_CBOR; *response_len = 1; return CTAP2_ERR_INVALID_CBOR;
3766 }
3767 switch (key) {
3769 cbor_read_uint(&r, &subcmd); has_sub = true; break;
3771 size_t start = r.offset;
3772 cbor_skip_item(&r);
3773 sub_params = params + start;
3774 sub_params_len = r.offset - start;
3775 break;
3776 }
3778 cbor_read_uint(&r, &pin_proto); break;
3780 size_t l = 0;
3781 cbor_read_bytes(&r, pin_auth, sizeof(pin_auth), &l);
3782 pin_auth_len = l; break;
3783 }
3784 default:
3785 cbor_skip_item(&r); break;
3786 }
3787 }
3788
3789 if (!has_sub) {
3790 response[0] = CTAP2_ERR_MISSING_PARAMETER; *response_len = 1; return CTAP2_ERR_MISSING_PARAMETER;
3791 }
3793 response[0] = CTAP2_ERR_UNSUPPORTED_OPTION; *response_len = 1; return CTAP2_ERR_UNSUPPORTED_OPTION;
3794 }
3795 if (subcmd != CTAP2_CONFIG_SUB_TOGGLE_ALWAYS_UV &&
3797 response[0] = CTAP1_ERR_INVALID_PARAMETER; *response_len = 1; return CTAP1_ERR_INVALID_PARAMETER;
3798 }
3799
3800 // pinUvAuth message: 0xff*32 || 0x0d || subCommand || subCommandParams.
3802 if (sub_params_len > 256) {
3803 response[0] = CTAP1_ERR_INVALID_LENGTH; *response_len = 1; return CTAP1_ERR_INVALID_LENGTH;
3804 }
3805 uint8_t msg[32 + 1 + 1 + 256];
3806 memset(msg, 0xff, 32);
3807 msg[32] = CTAP2_CMD_CONFIG; // 0x0d
3808 msg[33] = static_cast<uint8_t>(subcmd);
3809 if (sub_params_len) memcpy(msg + 34, sub_params, sub_params_len);
3810 uint8_t st = verify_token_over_message(msg, 34 + sub_params_len, static_cast<uint8_t>(pin_proto),
3811 pin_auth, pin_auth_len, PIN_PERM_AUTHN_CONFIG);
3812 if (st != CTAP2_OK) {
3813 response[0] = st; *response_len = 1; return st;
3814 }
3815 }
3816
3817 if (subcmd == CTAP2_CONFIG_SUB_TOGGLE_ALWAYS_UV) {
3818 bool cur = fido2_storage_get_always_uv();
3819 if (!fido2_storage_set_always_uv(!cur)) {
3820 response[0] = CTAP2_ERR_OTHER; *response_len = 1; return CTAP2_ERR_OTHER;
3821 }
3822 response[0] = CTAP2_OK; *response_len = 1; return CTAP2_OK;
3823 }
3824
3825 // setMinPINLength: parse newMinPINLength; the RP-ID list and forceChangePin
3826 // are accepted (covered by the auth) but not acted upon.
3827 uint64_t new_min = 0;
3828 bool has_new_min = false;
3829 if (sub_params && sub_params_len) {
3830 cbor_reader_t sr;
3831 cbor_reader_init(&sr, sub_params, sub_params_len);
3832 int sn = cbor_read_map(&sr);
3833 for (int i = 0; i < sn; i++) {
3834 uint64_t k = 0;
3835 if (!cbor_read_uint(&sr, &k)) break;
3837 cbor_read_uint(&sr, &new_min); has_new_min = true;
3838 } else {
3839 cbor_skip_item(&sr);
3840 }
3841 }
3842 }
3843 if (!has_new_min) {
3844 response[0] = CTAP1_ERR_INVALID_PARAMETER; *response_len = 1; return CTAP1_ERR_INVALID_PARAMETER;
3845 }
3846 uint8_t current = fido2_storage_get_min_pin_len();
3849 }
3850 // The floor may only increase and cannot exceed the badge PIN maximum.
3851 if (new_min < current || new_min > cdc::core::PinManager::BADGE_PIN_MAX) {
3852 response[0] = CTAP1_ERR_INVALID_PARAMETER; *response_len = 1; return CTAP1_ERR_INVALID_PARAMETER;
3853 }
3854 if (!fido2_storage_set_min_pin_len(static_cast<uint8_t>(new_min))) {
3855 response[0] = CTAP2_ERR_OTHER; *response_len = 1; return CTAP2_ERR_OTHER;
3856 }
3857 pin_storage_set_min_pin_floor(static_cast<uint8_t>(new_min));
3858 response[0] = CTAP2_OK; *response_len = 1; return CTAP2_OK;
3859}
3860
3865bool ctap2_init(void) {
3866 LOG_I(TAG, "Initializing...");
3867 memset(&g_ctap2, 0, sizeof(g_ctap2));
3868 g_ctap2.initialized = true;
3869 LOG_I(TAG, "Initialized");
3870 return true;
3871}
3872
3881uint8_t ctap2_process_command(const uint8_t *cmd, uint16_t cmd_len,
3882 uint8_t *response, uint16_t *response_len) {
3883 if (!g_ctap2.initialized || cmd_len < 1) {
3884 response[0] = CTAP1_ERR_INVALID_COMMAND;
3885 *response_len = 1;
3887 }
3888
3889 uint8_t command = cmd[0];
3890 const uint8_t *params = cmd + 1;
3891 uint16_t params_len = cmd_len - 1;
3892
3893 // Always log command type (helpful for debugging protocol issues)
3894 const char *cmd_name = "?";
3895 switch (command) {
3896 case CTAP2_CMD_MAKE_CREDENTIAL: cmd_name = "makeCredential"; break;
3897 case CTAP2_CMD_GET_ASSERTION: cmd_name = "getAssertion"; break;
3898 case CTAP2_CMD_GET_INFO: cmd_name = "getInfo"; break;
3899 case CTAP2_CMD_CLIENT_PIN: cmd_name = "clientPIN"; break;
3900 case CTAP2_CMD_RESET: cmd_name = "reset"; break;
3901 case CTAP2_CMD_GET_NEXT_ASSERTION: cmd_name = "getNextAssertion"; break;
3902 case CTAP2_CMD_CRED_MANAGEMENT: cmd_name = "credMgmt"; break;
3903 case CTAP2_CMD_SELECTION: cmd_name = "selection"; break;
3904 case CTAP2_CMD_LARGE_BLOBS: cmd_name = "largeBlobs"; break;
3905 case CTAP2_CMD_CONFIG: cmd_name = "config"; break;
3906 }
3907 LOG_I(TAG, "CMD 0x%02X (%s) %d bytes", command, cmd_name, params_len);
3908
3909 g_ctap2.operation_pending = true;
3910 g_ctap2.cancelled = false;
3911
3912 uint8_t status;
3913 switch (command) {
3914 case CTAP2_CMD_GET_INFO:
3915 status = ctap2_get_info(response, response_len);
3916 break;
3917
3919 status = ctap2_make_credential(params, params_len, response, response_len);
3920 break;
3921
3923 status = ctap2_get_assertion(params, params_len, response, response_len);
3924 break;
3925
3927 status = ctap2_get_next_assertion(response, response_len);
3928 break;
3929
3931 status = ctap2_client_pin(params, params_len, response, response_len);
3932 break;
3933
3934 case CTAP2_CMD_RESET:
3935 status = ctap2_reset(response, response_len);
3936 break;
3937
3939 status = ctap2_cred_management(params, params_len, response, response_len);
3940 break;
3941
3943 status = ctap2_selection(response, response_len);
3944 break;
3945
3947 status = ctap2_large_blobs(params, params_len, response, response_len);
3948 break;
3949
3950 case CTAP2_CMD_CONFIG:
3951 status = ctap2_config(params, params_len, response, response_len);
3952 break;
3953
3954 default:
3955 response[0] = CTAP1_ERR_INVALID_COMMAND;
3956 *response_len = 1;
3958 break;
3959 }
3960
3961 g_ctap2.operation_pending = false;
3962 return status;
3963}
3964
3969void ctap2_send_keepalive(uint8_t status) {
3970 uint32_t cid = ctaphid_get_current_cid();
3971 if (cid != 0) {
3972 ctaphid_send_keepalive(cid, status);
3973 }
3974}
3975
3979void ctap2_cancel(void) {
3980 g_ctap2.cancelled = true;
3981 if (CTAP2_DEBUG_COMMANDS) LOG_D(TAG, "Operation cancelled");
3982}
3983
3990 g_ctap2.cancelled = false;
3991}
3992
3997 return g_ctap2.cancelled;
3998}
static const char * TAG
Portable authenticatorLargeBlobs write-session accumulator and the canonical empty large-blob array c...
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
static constexpr uint8_t BADGE_PIN_MAX
Definition PinManager.h:50
static constexpr uint8_t BADGE_PIN_MIN
Definition PinManager.h:49
Accumulates an offset-chunked authenticatorLargeBlobs write into a caller-owned buffer.
#define PIN_CMD_GET_PIN_TOKEN
Definition ctap2.cpp:89
void sha256_str(const char *str, uint8_t out[32])
bool pin_token_valid
Definition ctap2.cpp:109
uint8_t assertion_count
Definition ctap2.cpp:68
#define PIN_PERM_LARGE_BLOB_WRITE
Definition ctap2.cpp:97
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:3171
uint8_t ctap2_reset(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorReset (0x07).
Definition ctap2.cpp:3072
bool assertion_appid_used
Definition ctap2.cpp:74
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:235
static void ga_find_credentials(GetAssertionParams *p, AssertionCredentials *creds)
Finds credentials matching RP/allowList and appid extension rules.
Definition ctap2.cpp:1693
uint8_t rp_slots[32]
Definition ctap2.cpp:131
static constexpr uint64_t CTAP2_INFO_PIN_UV_AUTH_PROTOCOL_VALUE
Reported PIN/UV auth protocol version (Protocol Two).
Definition ctap2.cpp:549
uint8_t cred_index
Definition ctap2.cpp:138
#define PIN_CMD_GET_PIN_UV_TOKEN
Definition ctap2.cpp:90
bool assertion_up_done
Definition ctap2.cpp:72
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:2993
static bool cred_mgmt_slot_has_key(uint8_t slot)
Credential-management helper and command implementation.
Definition ctap2.cpp:3097
static void encode_info_transports(cbor_writer_t *w)
Encodes the supported transports list.
Definition ctap2.cpp:633
uint8_t rp_count
Definition ctap2.cpp:132
void ctap2_cancel(void)
Marks current CTAP2 operation as cancelled.
Definition ctap2.cpp:3979
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:3881
mbedtls_ecp_keypair ecdh_key
Definition ctap2.cpp:104
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:3989
uint8_t cred_slots[32]
Definition ctap2.cpp:136
#define PIN_CMD_GET_KEY_AGREEMENT
Definition ctap2.cpp:86
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:1799
static const uint8_t AAGUID[16]
Authenticator Attestation GUID for this authenticator model.
Definition ctap2.cpp:46
static void encode_info_min_pin_length(cbor_writer_t *w)
Encodes the current minPINLength policy floor.
Definition ctap2.cpp:664
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:2147
static uint8_t g_large_blob_buf[cdc::mod_fido2::kLargeBlobMaxArray]
Definition ctap2.cpp:3589
static void encode_info_versions(cbor_writer_t *w)
Encodes the supported FIDO/U2F versions into the getInfo CBOR map.
Definition ctap2.cpp:554
uint8_t assertion_creds[32]
Definition ctap2.cpp:67
static void encode_info_options(cbor_writer_t *w)
Encodes the supported authenticator options, keys sorted by length.
Definition ctap2.cpp:578
static void encode_info_max_msg_size(cbor_writer_t *w)
Encodes the maxMsgSize entry into the getInfo CBOR map.
Definition ctap2.cpp:608
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:518
uint8_t pin_token[32]
Definition ctap2.cpp:108
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:467
static void secure_random_fill(uint8_t *out, size_t len)
Fills a buffer with cryptographically secure random bytes.
Definition ctap2.cpp:147
static uint8_t ga_parse_extensions(cbor_reader_t *r, GetAssertionParams *p)
Parses getAssertion extensions (map key 0x04).
Definition ctap2.cpp:1483
#define PIN_PERM_AUTHN_CONFIG
Definition ctap2.cpp:98
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:3146
static uint8_t client_pin_get_retries(uint8_t *response, uint16_t *response_len)
Handles ClientPIN subcommand getPINRetries (0x01).
Definition ctap2.cpp:2363
bool ctap2_init(void)
Initializes CTAP2 runtime state.
Definition ctap2.cpp:3865
static void encode_info_max_large_blob(cbor_writer_t *w)
Encodes the maxSerializedLargeBlobArray entry.
Definition ctap2.cpp:658
uint8_t token_permissions
Definition ctap2.cpp:112
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:258
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:2451
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:2307
#define CRED_MGMT_ENUMERATE_RPS_GET_NEXT
Definition ctap2.cpp:124
#define PIN_PROTOCOL_VERSION
ClientPIN constants and state for PIN protocol support.
Definition ctap2.cpp:79
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:2764
uint8_t ctap2_selection(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorSelection (0x0B).
Definition ctap2.cpp:3541
bool token_rp_id_set
Definition ctap2.cpp:114
uint8_t rp_index
Definition ctap2.cpp:133
#define PIN_TOKEN_SIZE
Definition ctap2.cpp:80
uint8_t pin_retries
Definition ctap2.cpp:117
uint8_t cred_count
Definition ctap2.cpp:137
bool ctap2_is_cancelled(void)
Returns true if the current CTAP2 operation has been cancelled.
Definition ctap2.cpp:3996
static constexpr uint64_t CTAP2_INFO_MAX_CRED_LIST_COUNT_VALUE
Reported maxCredentialCountInList for authenticatorGetInfo.
Definition ctap2.cpp:551
uint8_t assertion_rp_id_hash[32]
Definition ctap2.cpp:70
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:295
uint8_t ctap2_large_blobs(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorLargeBlobs (0x0C).
Definition ctap2.cpp:3606
static bool client_pin_init_ecdh(void)
ClientPIN command implementation helpers.
Definition ctap2.cpp:2119
uint8_t ctap2_config(const uint8_t *params, uint16_t params_len, uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorConfig (0x0D).
Definition ctap2.cpp:3746
#define CRED_MGMT_GET_CREDS_METADATA
Credential management constants and enumeration state.
Definition ctap2.cpp:122
uint8_t current_rp_id_hash[32]
Definition ctap2.cpp:139
static uint8_t lb_status(cdc::mod_fido2::LbResult r)
Maps a LargeBlobWriteSession result to a CTAP status.
Definition ctap2.cpp:3593
static uint8_t cred_mgmt_count_unique_rps(void)
Counts unique RP IDs among resident credentials.
Definition ctap2.cpp:3110
#define PIN_UV_RETRIES_MAX
Definition ctap2.cpp:82
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:1440
static struct @074350050112271276332254352137370356012162354162 g_cred_mgmt
#define CRED_MGMT_ENUMERATE_CREDS_BEGIN
Definition ctap2.cpp:125
bool ecdh_valid
Definition ctap2.cpp:105
#define PIN_RETRIES_MAX
Definition ctap2.cpp:81
static constexpr uint64_t CTAP2_INFO_MAX_MSG_SIZE_VALUE
Reported maximum message size for authenticatorGetInfo.
Definition ctap2.cpp:547
static int ctap2_random(void *ctx, unsigned char *out, size_t len)
mbedTLS RNG callback backed by secure random source.
Definition ctap2.cpp:174
#define CRED_MGMT_ENUMERATE_CREDS_GET_NEXT
Definition ctap2.cpp:126
static const char * TAG_PIN
Definition ctap2.cpp:35
uint8_t token_rp_id_hash[32]
Definition ctap2.cpp:113
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:192
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:2333
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:1764
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:1550
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:3204
static void encode_info_pin_uv_auth_protocols(cbor_writer_t *w)
Encodes the supported pinUvAuthProtocols list.
Definition ctap2.cpp:614
static void encode_info_aaguid(cbor_writer_t *w)
Encodes the authenticator AAGUID into the getInfo CBOR map.
Definition ctap2.cpp:572
void ctap2_send_keepalive(uint8_t status)
Sends CTAPHID keepalive for currently active channel.
Definition ctap2.cpp:3969
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:1869
#define PIN_CMD_GET_RETRIES
ClientPIN subcommand identifiers.
Definition ctap2.cpp:85
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:430
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:3265
uint8_t assertion_index
Definition ctap2.cpp:69
static void encode_info_algorithms(cbor_writer_t *w)
Encodes the supported algorithms array (PublicKeyCredentialParameters).
Definition ctap2.cpp:640
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:2266
#define CRED_MGMT_DELETE_CREDENTIAL
Definition ctap2.cpp:127
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:386
#define CTAP2_DEBUG_COMMANDS
Definition ctap2.cpp:42
uint8_t assertion_client_data_hash[32]
Definition ctap2.cpp:71
static uint8_t ga_verify_pin_auth(const GetAssertionParams *p, bool *uv_verified)
Verifies getAssertion pinUvAuthParam via HMAC.
Definition ctap2.cpp:1635
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:319
#define PIN_CMD_CHANGE_PIN
Definition ctap2.cpp:88
#define CRED_MGMT_ENUMERATE_RPS_BEGIN
Definition ctap2.cpp:123
static void encode_info_extensions(cbor_writer_t *w)
Encodes the supported CTAP extensions, sorted for CBOR canonical form.
Definition ctap2.cpp:563
static uint8_t client_pin_get_key_agreement(uint8_t *response, uint16_t *response_len)
Handles ClientPIN subcommand getKeyAgreement (0x02).
Definition ctap2.cpp:2388
static void encode_info_max_cred_id_length(cbor_writer_t *w)
Encodes the maxCredentialIdLength entry.
Definition ctap2.cpp:627
bool cancelled
Definition ctap2.cpp:64
#define CTAP2_ECP_Q(k)
uint8_t uv_retries
Definition ctap2.cpp:118
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:2293
static uint8_t verify_token_over_message(const uint8_t *msg, size_t msg_len, uint8_t protocol, const uint8_t *param, size_t param_len, uint8_t required_perm)
Verifies a pinUvAuthToken HMAC over an arbitrary message.
Definition ctap2.cpp:3564
static void encode_info_max_cred_count(cbor_writer_t *w)
Encodes the maxCredentialCountInList entry.
Definition ctap2.cpp:621
uint8_t ctap2_get_next_assertion(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorGetNextAssertion (0x08).
Definition ctap2.cpp:2018
bool initialized
Definition ctap2.cpp:62
bool assertion_include_user
Definition ctap2.cpp:73
#define PIN_CMD_SET_PIN
Definition ctap2.cpp:87
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:1404
uint8_t ctap2_get_info(uint8_t *response, uint16_t *response_len)
Handles CTAP2 authenticatorGetInfo (0x04).
Definition ctap2.cpp:694
#define CTAP2_ECP_GRP(k)
static const char * INFO_TRANSPORTS[]
Device info strings reported by authenticatorGetInfo.
Definition ctap2.cpp:55
static cdc::mod_fido2::LargeBlobWriteSession g_large_blob_session
Definition ctap2.cpp:3588
static uint8_t ga_parse_options(cbor_reader_t *r, GetAssertionParams *p)
Parses getAssertion options (map key 0x05).
Definition ctap2.cpp:1518
bool operation_pending
Definition ctap2.cpp:63
static uint8_t g_large_blob_chunk[cdc::mod_fido2::kLargeBlobMaxArray]
Definition ctap2.cpp:3590
#define CTAP2_MC_RESP_AUTH_DATA
Definition ctap2.h:154
#define CTAP1_ERR_INVALID_PARAMETER
Definition ctap2.h:38
#define CTAP2_PIN_PROTOCOL
Definition ctap2.h:174
#define CTAP2_INFO_ALGORITHMS
Definition ctap2.h:137
#define CTAP2_CMD_GET_NEXT_ASSERTION
Definition ctap2.h:22
#define CTAP2_MC_PUB_KEY_CRED_PARAMS
Definition ctap2.h:145
#define CTAP2_INFO_MAX_CRED_ID_LENGTH
Definition ctap2.h:135
#define CTAP2_GA_ALLOW_LIST
Definition ctap2.h:160
#define CTAP2_LB_OFFSET
Definition ctap2.h:215
#define CTAP2_CM_RESP_RP
Definition ctap2.h:203
#define CTAP2_CMD_RESET
Definition ctap2.h:21
#define CTAP2_OK
Definition ctap2.h:36
#define CTAP2_ERR_PIN_REQUIRED
Definition ctap2.h:72
#define CTAP2_GA_RESP_AUTH_DATA
Definition ctap2.h:168
#define CTAP2_CMD_GET_INFO
Definition ctap2.h:19
#define CTAP2_CM_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:193
#define CTAP2_GA_RESP_USER
Definition ctap2.h:170
#define COSE_KEY_LABEL_ALG
Definition ctap2.h:100
#define CTAP1_ERR_INVALID_LENGTH
Definition ctap2.h:39
#define CTAP2_MC_RP
Definition ctap2.h:143
#define CTAP2_CMD_CLIENT_PIN
Definition ctap2.h:20
#define CTAP2_MC_CLIENT_DATA_HASH
Definition ctap2.h:142
#define CTAP2_LB_RESP_CONFIG
Definition ctap2.h:219
#define CTAP2_CMD_CONFIG
Definition ctap2.h:26
#define CTAP2_ERR_UNSUPPORTED_OPTION
Definition ctap2.h:61
#define CTAP2_INFO_MAX_MSG_SIZE
Definition ctap2.h:132
#define CTAP2_ERR_KEY_STORE_FULL
Definition ctap2.h:59
#define CTAP2_CM_SUB_RP_ID_HASH
Definition ctap2.h:197
#define CTAP2_INFO_PIN_UV_AUTH_PROTOCOLS
Definition ctap2.h:133
#define CTAP2_CM_RESP_PUBLIC_KEY
Definition ctap2.h:208
#define CTAP2_MC_RESP_FMT
Definition ctap2.h:153
#define COSE_ALG_ECDH_ES_HKDF_256
Definition ctap2.h:89
#define COSE_KEY_LABEL_CRV
Definition ctap2.h:103
#define CTAP2_MC_EXTENSIONS
Definition ctap2.h:147
#define CTAP2_PIN_RESP_PIN_RETRIES
Definition ctap2.h:186
#define CTAP2_CONFIG_SUBCOMMAND
Definition ctap2.h:222
#define CTAP2_LB_SET
Definition ctap2.h:214
#define CTAP2_INFO_MIN_PIN_LENGTH
Definition ctap2.h:139
#define CTAP2_GA_OPTIONS
Definition ctap2.h:162
#define CTAP2_LB_GET
Definition ctap2.h:213
#define CTAP2_ERR_PIN_NOT_SET
Definition ctap2.h:71
#define CTAP2_GA_CLIENT_DATA_HASH
Definition ctap2.h:159
#define CTAP2_LB_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:218
#define CTAP2_CONFIG_SUB_TOGGLE_ALWAYS_UV
Definition ctap2.h:229
#define CTAP2_MC_OPTIONS
Definition ctap2.h:148
#define CTAP2_INFO_EXTENSIONS
Definition ctap2.h:129
#define CTAP2_CM_RESP_CREDENTIAL_ID
Definition ctap2.h:207
#define CTAP2_CM_RESP_EXISTING_CRED_COUNT
Definition ctap2.h:201
#define CTAP2_PIN_PERMISSIONS_RPID
Definition ctap2.h:181
#define CTAP2_GA_RP_ID
Definition ctap2.h:158
#define CTAP2_CM_RESP_USER
Definition ctap2.h:206
#define CTAP2_PIN_RESP_UV_RETRIES
Definition ctap2.h:188
#define CTAP2_ERR_OPERATION_DENIED
Definition ctap2.h:58
#define CTAP2_CONFIG_SUB_ENABLE_EP
Definition ctap2.h:228
#define CTAP2_LB_LENGTH
Definition ctap2.h:216
#define CTAP2_ERR_PIN_BLOCKED
Definition ctap2.h:68
#define CTAP2_CMD_GET_ASSERTION
Definition ctap2.h:18
#define CTAP1_ERR_INVALID_SEQ
Definition ctap2.h:40
#define CTAP2_CMD_CRED_MANAGEMENT
Definition ctap2.h:23
#define CTAP2_INFO_TRANSPORTS
Definition ctap2.h:136
#define CTAP2_CONFIG_SUB_SET_MIN_PIN_LENGTH
Definition ctap2.h:230
#define COSE_CRV_P256
Definition ctap2.h:114
#define CTAP2_CM_RESP_REMAINING_CRED_COUNT
Definition ctap2.h:202
#define CTAP2_CM_RESP_TOTAL_CREDENTIALS
Definition ctap2.h:209
#define CTAP1_ERR_INVALID_COMMAND
Definition ctap2.h:37
#define COSE_KEY_TYPE_EC2
Definition ctap2.h:110
#define CTAP2_CMD_LARGE_BLOBS
Definition ctap2.h:25
#define CTAP2_ERR_PIN_AUTH_INVALID
Definition ctap2.h:69
#define CTAP2_ERR_INTEGRITY_FAILURE
Definition ctap2.h:79
#define CTAP2_INFO_MAX_CRED_COUNT_IN_LIST
Definition ctap2.h:134
#define CTAP2_INFO_AAGUID
Definition ctap2.h:130
#define CTAP2_ERR_INVALID_OPTION
Definition ctap2.h:62
#define CTAP2_CMD_MAKE_CREDENTIAL
Definition ctap2.h:17
#define CTAP2_ERR_NO_CREDENTIALS
Definition ctap2.h:64
#define CTAP2_GA_PIN_UV_AUTH_PARAM
Definition ctap2.h:163
#define CTAP2_CM_SUBCOMMAND
Definition ctap2.h:191
#define CTAP2_ERR_PIN_INVALID
Definition ctap2.h:67
#define CTAP2_PIN_HASH_ENC
Definition ctap2.h:179
#define CTAP2_CM_PIN_UV_AUTH_PARAM
Definition ctap2.h:194
#define CTAP2_ERR_CREDENTIAL_EXCLUDED
Definition ctap2.h:51
#define CTAP2_CONFIG_PARAM_NEW_MIN_PIN_LEN
Definition ctap2.h:234
#define CTAP2_LB_PIN_UV_AUTH_PARAM
Definition ctap2.h:217
#define CTAP2_CM_RESP_TOTAL_RPS
Definition ctap2.h:205
#define CTAP2_CM_SUB_CREDENTIAL_ID
Definition ctap2.h:198
#define CTAP2_ERR_OTHER
Definition ctap2.h:80
#define COSE_KEY_LABEL_X
Definition ctap2.h:104
void ctap2_send_keepalive(uint8_t status)
Sends CTAPHID keepalive for currently active channel.
Definition ctap2.cpp:3969
#define CTAP2_INFO_MAX_SERIALIZED_LARGE_BLOB_ARRAY
Definition ctap2.h:138
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:192
#define CTAP2_ERR_MISSING_PARAMETER
Definition ctap2.h:47
#define CTAP2_CONFIG_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:224
#define CTAP2_CM_RESP_RP_ID_HASH
Definition ctap2.h:204
#define CTAP2_PIN_PERMISSIONS
Definition ctap2.h:180
#define CTAP2_GA_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:164
#define CTAP2_CONFIG_PIN_UV_AUTH_PARAM
Definition ctap2.h:225
#define CTAP2_MC_RESP_ATT_STMT
Definition ctap2.h:155
#define CTAP2_GA_EXTENSIONS
Definition ctap2.h:161
#define CTAP2_ERR_INVALID_CBOR
Definition ctap2.h:46
#define CTAP2_MC_USER
Definition ctap2.h:144
#define CTAP2_CM_RESP_CRED_PROTECT
Definition ctap2.h:210
#define CTAP2_INFO_OPTIONS
Definition ctap2.h:131
#define CTAP2_CONFIG_SUB_VENDOR_PROTOTYPE
Definition ctap2.h:231
#define CTAP2_PIN_RESP_KEY_AGREEMENT
Definition ctap2.h:184
#define CTAP2_MC_PIN_UV_AUTH_PROTOCOL
Definition ctap2.h:150
#define CTAP2_PIN_RESP_PIN_TOKEN
Definition ctap2.h:185
#define CTAP2_GA_RESP_NUMBER_OF_CREDS
Definition ctap2.h:171
#define CTAP2_ERR_LARGE_BLOB_STORAGE_FULL
Definition ctap2.h:50
#define COSE_ALG_ES256
Definition ctap2.h:86
#define CTAP2_ERR_NOT_ALLOWED
Definition ctap2.h:66
#define CTAP2_INFO_VERSIONS
Definition ctap2.h:128
#define CTAP2_GA_RESP_CREDENTIAL
Definition ctap2.h:167
#define CTAP2_CMD_SELECTION
Definition ctap2.h:24
#define CTAP2_PIN_KEY_AGREEMENT
Definition ctap2.h:176
#define COSE_KEY_LABEL_Y
Definition ctap2.h:105
#define CTAP2_GA_RESP_SIGNATURE
Definition ctap2.h:169
#define CTAP2_ERR_UNSUPPORTED_ALGORITHM
Definition ctap2.h:57
#define COSE_KEY_LABEL_KTY
Definition ctap2.h:98
#define COSE_ALG_EDDSA
Definition ctap2.h:87
#define CTAP2_CONFIG_SUBCOMMAND_PARAMS
Definition ctap2.h:223
#define CTAP2_MC_PIN_UV_AUTH_PARAM
Definition ctap2.h:149
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:35
#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:195
#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:295
fido2_user_presence_result_t fido2_request_user_presence(const char *rp_id, fido2_action_t action, const char *user_name)
Requests user presence from host/application callback.
Definition fido2.cpp:178
fido2_user_presence_result_t
Definition fido2.h:30
@ FIDO2_UP_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:266
bool fido2_is_pin_verified(void)
Returns current PIN-verified state.
Definition fido2.cpp:206
#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_largeblob_get(uint8_t *out, uint16_t max_len, uint16_t *out_len)
bool fido2_storage_get_always_uv(void)
bool fido2_storage_set_always_uv(bool enabled)
bool fido2_storage_largeblob_set(const uint8_t *data, uint16_t len)
bool fido2_storage_set_min_pin_len(uint8_t min_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.
uint8_t fido2_storage_get_min_pin_len(void)
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:930
static bool parse_options_map(cbor_reader_t *r, MakeCredentialParams *p)
Parses makeCredential options map from CBOR.
Definition ctap2.cpp:899
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:1156
LbResult
Outcome of a write-session step, mapped to a CTAP status by the caller.
@ StorageFull
declared total length exceeds the buffer / kLargeBlobMaxArray
@ BadSeq
fragment offset out of order, or no active session
@ BadLength
declared total too small, or a fragment overruns the total
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:1270
static bool is_browser_probe(const char *rp_id)
Detects known browser probe RP IDs.
Definition ctap2.cpp:1145
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:1172
static bool parse_rp_map(cbor_reader_t *r, MakeCredentialParams *p)
Parses the RP map from a makeCredential CBOR request.
Definition ctap2.cpp:768
static bool parse_user_map(cbor_reader_t *r, MakeCredentialParams *p)
Parses the user map from a makeCredential CBOR request.
Definition ctap2.cpp:797
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:864
constexpr uint16_t kLargeBlobMaxArray
Largest serialized large-blob array, advertised as maxSerializedLargeBlobArray.
static bool parse_pubkey_cred_params(cbor_reader_t *r, MakeCredentialParams *p)
Parses pubKeyCredParams and selects a supported algorithm.
Definition ctap2.cpp:829
static uint8_t verify_pin_uv_auth(const MakeCredentialParams *p)
Verifies pinUvAuthParam for makeCredential.
Definition ctap2.cpp:1001
static uint8_t check_appid_exclude(const MakeCredentialParams *p)
Validates the appidExclude extension against existing credentials.
Definition ctap2.cpp:1038
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:1058
bool pin_storage_verify_fido2_hash(const uint8_t *hash_in)
bool pin_storage_fido2_available(void)
void pin_storage_set_min_pin_floor(uint8_t min_len)
bool pin_storage_get_fido2_hash(uint8_t *hash_out)
Credential-selection result used to build assertion responses.
Definition ctap2.cpp:1385
Parsed parameters for authenticatorGetAssertion.
Definition ctap2.cpp:1357
Parsed parameters for authenticatorMakeCredential.
Definition ctap2.cpp:734
bool u2f_get_attestation_cert(const uint8_t **cert, uint16_t *cert_len)
Returns attestation certificate pointer and length, initializing attestation on demand if the boot-ti...
Definition u2f.cpp:401
bool u2f_attestation_sign(const uint8_t *data, size_t data_len, uint8_t *signature, uint8_t *sig_len)
Signs payload using the attestation key, initializing attestation on demand if the boot-time init did...
Definition u2f.cpp:461