From 669f6041bd616f6ce9fad930620fc528b68e0a93 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:34:05 +0200 Subject: [PATCH 01/45] Do not call pico_sdk_init. Signed-off-by: Pol Henarejos --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52ca50b..be0feb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,11 +35,6 @@ project(pico_fido C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) -if(ENABLE_EMULATION) -else() -pico_sdk_init() -endif() - add_executable(pico_fido) endif() From 5facbf61cdc922071c66c9ffe189867169c950c4 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:34:34 +0200 Subject: [PATCH 02/45] NK compatibility improvements. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- src/fido/cbor_make_credential.c | 6 +-- src/fido/oath.c | 75 ++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 113e720..5984d1f 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 113e720fcaaa6b9ca74d114bee1923bb2619ba3b +Subproject commit 5984d1f72de82e44c18ed0bbbc953e1559a58af6 diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 6625532..bd9539d 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -519,12 +519,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03)); - CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nitrokey ? 3 : 2)); + CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nk ? 3 : 2)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg")); - CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nitrokey ? -alg : -FIDO2_ALG_ES256)); + CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nk ? -alg : -FIDO2_ALG_ES256)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig")); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen)); - if (self_attestation == false || is_nitrokey) { + if (self_attestation == false || is_nk) { CborEncoder arrEncoder; file_t *ef_cert = NULL; if (enterpriseAttestation == 2) { diff --git a/src/fido/oath.c b/src/fido/oath.c index f0a0adf..40e72a2 100644 --- a/src/fido/oath.c +++ b/src/fido/oath.c @@ -44,6 +44,10 @@ #define TAG_PASSWORD 0x80 #define TAG_NEW_PASSWORD 0x81 #define TAG_PIN_COUNTER 0x82 +#define TAG_PWS_LOGIN 0x83 +#define TAG_PWS_PASSWORD 0x84 +#define TAG_PWS_METADATA 0x85 +#define TAG_SERIAL_NUMBER 0x8F #define ALG_HMAC_SHA1 0x01 #define ALG_HMAC_SHA256 0x02 @@ -56,6 +60,7 @@ #define PROP_INC 0x01 #define PROP_TOUCH 0x02 +#define PROP_PIN 0x03 int oath_process_apdu(); int oath_unload(); @@ -99,6 +104,12 @@ int oath_select(app_t *a, uint8_t force) { res_APDU[res_APDU_size++] = TAG_ALGO; res_APDU[res_APDU_size++] = 1; res_APDU[res_APDU_size++] = ALG_HMAC_SHA1; + if (is_nk) { + res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER; + res_APDU[res_APDU_size++] = 8; + memcpy(res_APDU + res_APDU_size, pico_serial_str, 8); + res_APDU_size += 8; + } apdu.ne = res_APDU_size; return PICOKEY_OK; } @@ -270,16 +281,27 @@ int cmd_list() { if (validated == false) { return SW_SECURITY_STATUS_NOT_SATISFIED(); } + bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01); for (int i = 0; i < MAX_OATH_CRED; i++) { file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i)); if (file_has_data(ef)) { - asn1_ctx_t ctxi, key = { 0 }, name = { 0 }; + asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, pws = { 0 }; asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi); if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true && asn1_find_tag(&ctxi, TAG_KEY, &key) == true) { res_APDU[res_APDU_size++] = TAG_NAME_LIST; - res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1); + res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1 + (ext ? 1 : 0)); res_APDU[res_APDU_size++] = key.data[0]; memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len; + if (ext) { + uint8_t props = 0x0; + if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_METADATA, &pws) == true) { + props |= 0x4; + } + if (asn1_find_tag(&ctxi, TAG_PROPERTY, &pws) == true && (pws.data[0] & PROP_TOUCH)) { + props |= 0x1; + } + res_APDU[res_APDU_size++] = props; + } } } } @@ -626,6 +648,53 @@ int cmd_rename() { return SW_OK(); } +int cmd_get_credential() { + asn1_ctx_t ctxi, name = { 0 }; + if (apdu.nc < 3) { + return SW_INCORRECT_PARAMS(); + } + if (apdu.data[0] != TAG_NAME) { + return SW_WRONG_DATA(); + } + asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi); + if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) { + return SW_WRONG_DATA(); + } + file_t *ef = find_oath_cred(name.data, name.len); + if (file_has_data(ef) == false) { + return SW_DATA_INVALID(); + } + asn1_ctx_t login = { 0 }, pw = { 0 }, meta = { 0 }, prop = { 0 }; + asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi); + if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true) { + res_APDU[res_APDU_size++] = TAG_NAME; + res_APDU[res_APDU_size++] = (uint8_t)(name.len); + memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len; + } + if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &login) == true) { + res_APDU[res_APDU_size++] = TAG_PWS_LOGIN; + res_APDU[res_APDU_size++] = (uint8_t)(login.len); + memcpy(res_APDU + res_APDU_size, login.data, login.len); res_APDU_size += login.len; + } + if (asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pw) == true) { + res_APDU[res_APDU_size++] = TAG_PWS_PASSWORD; + res_APDU[res_APDU_size++] = (uint8_t)(pw.len); + memcpy(res_APDU + res_APDU_size, pw.data, pw.len); res_APDU_size += pw.len; + } + if (asn1_find_tag(&ctxi, TAG_PWS_METADATA, &meta) == true) { + res_APDU[res_APDU_size++] = TAG_PWS_METADATA; + res_APDU[res_APDU_size++] = (uint8_t)(meta.len); + memcpy(res_APDU + res_APDU_size, meta.data, meta.len); res_APDU_size += meta.len; + } + if (asn1_find_tag(&ctxi, TAG_PROPERTY, &prop) == true) { + res_APDU[res_APDU_size++] = TAG_PROPERTY; + res_APDU[res_APDU_size++] = (uint8_t)(prop.len); + memcpy(res_APDU + res_APDU_size, prop.data, prop.len); res_APDU_size += prop.len; + } + apdu.ne = res_APDU_size; + return SW_OK(); +} + #define INS_PUT 0x01 #define INS_DELETE 0x02 #define INS_SET_CODE 0x03 @@ -640,6 +709,7 @@ int cmd_rename() { #define INS_VERIFY_PIN 0xb2 #define INS_CHANGE_PIN 0xb3 #define INS_SET_PIN 0xb4 +#define INS_GET_CREDENTIAL 0xb5 static const cmd_t cmds[] = { { INS_PUT, cmd_put }, @@ -656,6 +726,7 @@ static const cmd_t cmds[] = { { INS_CHANGE_PIN, cmd_change_otp_pin }, { INS_VERIFY_PIN, cmd_verify_otp_pin }, { INS_VERIFY_CODE, cmd_verify_hotp }, + { INS_GET_CREDENTIAL, cmd_get_credential }, { 0x00, 0x0 } }; From 81e03cefda8d5e695bf60a1e11cc2ff6103484f8 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:39:41 +0200 Subject: [PATCH 03/45] Fix for rp2350 build. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 5984d1f..8321db1 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 5984d1f72de82e44c18ed0bbbc953e1559a58af6 +Subproject commit 8321db1f67d924dca7bbe063662ed535ed98086f From bf1072781b5d80a92a9bbf21b96f1b6ffc0aedd7 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:42:24 +0200 Subject: [PATCH 04/45] Fix build. Signed-off-by: Pol Henarejos --- src/fido/oath.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fido/oath.c b/src/fido/oath.c index 40e72a2..c9ebd2d 100644 --- a/src/fido/oath.c +++ b/src/fido/oath.c @@ -24,6 +24,7 @@ #include "asn1.h" #include "crypto_utils.h" #include "management.h" +#include "ctap_hid.h" #define MAX_OATH_CRED 255 #define CHALLENGE_LEN 8 From 2b640a5c36bdb1c612947b03113b861e38552b90 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 27 Aug 2025 12:51:34 +0200 Subject: [PATCH 05/45] Add support for FIDO 2.2 Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 853f1da..a2d58f1 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -30,10 +30,11 @@ int cbor_get_info() { CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3)); + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4)); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "U2F_V2")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_1")); + CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2")); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); From 73a785686666777cd286db985f20af9281b775bb Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 28 Aug 2025 00:17:57 +0200 Subject: [PATCH 06/45] Add support for persistentPinUvAuthToken. Signed-off-by: Pol Henarejos --- src/fido/cbor_client_pin.c | 98 +++++++++++++++++++------------------- src/fido/cbor_config.c | 6 +++ src/fido/cbor_cred_mgmt.c | 54 ++++++++++++++------- src/fido/ctap.h | 1 + src/fido/fido.c | 14 ++++++ src/fido/fido.h | 8 ++++ src/fido/files.c | 1 + src/fido/files.h | 1 + 8 files changed, 117 insertions(+), 66 deletions(-) diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index 1ceb701..ef2cd4d 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -105,11 +105,7 @@ int regenerate() { mbedtls_ecdh_init(&hkey); hkey_init = true; mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1); - int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, - &hkey.ctx.mbed_ecdh.d, - &hkey.ctx.mbed_ecdh.Q, - random_gen, - NULL); + int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL); mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1); if (ret != 0) { return ret; @@ -125,34 +121,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) { return ret; } if (protocol == 1) { - return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), - buf, - sizeof(buf), - sharedSecret); + return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret); } else if (protocol == 2) { const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - ret = mbedtls_hkdf(md_info, - NULL, - 0, - buf, - sizeof(buf), - (uint8_t *) "CTAP2 HMAC key", - 14, - sharedSecret, - 32); + ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32); if (ret != 0) { return ret; } - return mbedtls_hkdf(md_info, - NULL, - 0, - buf, - sizeof(buf), - (uint8_t *) "CTAP2 AES key", - 13, - sharedSecret + 32, - 32); + return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32); } return -1; } @@ -160,26 +137,38 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) { int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) { mbedtls_mpi z; mbedtls_mpi_init(&z); - int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, - &z, - Q, - &hkey.ctx.mbed_ecdh.d, - random_gen, - NULL); + int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL); ret = kdf(protocol, &z, sharedSecret); mbedtls_mpi_free(&z); return ret; } -int resetPinUvAuthToken() { +void resetAuthToken(bool persistent) { + uint16_t fid = EF_AUTHTOKEN; + if (persistent) { + fid = EF_PAUTHTOKEN; + } + file_t *ef = search_by_fid(fid, NULL, SPECIFY_EF); uint8_t t[32]; random_gen(NULL, t, sizeof(t)); - file_put_data(ef_authtoken, t, sizeof(t)); + file_put_data(ef, t, sizeof(t)); + low_flash_available(); +} + +int resetPinUvAuthToken() { + resetAuthToken(false); paut.permissions = 0; paut.data = file_get_data(ef_authtoken); paut.len = file_get_size(ef_authtoken); + return 0; +} - low_flash_available(); +int resetPersistentPinUvAuthToken() { + resetAuthToken(true); + file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF); + ppaut.permissions = 0; + ppaut.data = file_get_data(ef_pauthtoken); + ppaut.len = file_get_size(ef_pauthtoken); return 0; } @@ -578,6 +567,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) { } low_flash_available(); resetPinUvAuthToken(); + resetPersistentPinUvAuthToken(); goto err; // No return } else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions @@ -598,7 +588,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION); } - + if ((permissions & CTAP_PERMISSION_PCMR) && permissions != CTAP_PERMISSION_PCMR) { + CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION); + } } if (!file_has_data(ef_pin)) { CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET); @@ -664,21 +656,29 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) { CBOR_ERROR(CTAP2_ERR_PIN_INVALID); } - resetPinUvAuthToken(); - beginUsingPinUvAuthToken(false); - if (subcommand == 0x05) { - permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA; - } - paut.permissions = (uint8_t)permissions; - if (rpId.present == true) { - mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0); - paut.has_rp_id = true; + uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL; + ; + if (permissions & CTAP_PERMISSION_PCMR) { + ppaut.permissions = CTAP_PERMISSION_PCMR; + pdata = ppaut.data; } else { - paut.has_rp_id = false; + resetPinUvAuthToken(); + beginUsingPinUvAuthToken(false); + if (subcommand == 0x05) { + permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA; + } + paut.permissions = (uint8_t)permissions; + if (rpId.present == true) { + mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0); + paut.has_rp_id = true; + } + else { + paut.has_rp_id = false; + } + pdata = paut.data; } - uint8_t pinUvAuthToken_enc[32 + IV_SIZE]; - encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc); + encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pdata, 32, pinUvAuthToken_enc); CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff)); diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index a4afeaf..0661846 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -31,6 +31,8 @@ extern uint8_t keydev_dec[32]; extern bool has_keydev_dec; +extern void resetPersistentPinUvAuthToken(); +extern void resetPinUvAuthToken(); int cbor_config(const uint8_t *data, size_t len) { CborParser parser; @@ -207,6 +209,10 @@ int cbor_config(const uint8_t *data, size_t len) { if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) { forceChangePin = ptrue; } + if (forceChangePin) { + resetPersistentPinUvAuthToken(); + resetPinUvAuthToken(); + } uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32); dataf[0] = (uint8_t)newMinPinLength; dataf[1] = forceChangePin == ptrue ? 1 : 0; diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 5469326..5cd9f8a 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -79,8 +79,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { if (strcmp(_fd3, "transports") == 0) { CBOR_PARSE_ARRAY_START(_f3, 4) { - CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId. - transports_len], 4); + CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4); credentialId.transports_len++; } CBOR_PARSE_ARRAY_END(_f3, 4); @@ -122,12 +121,19 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0); if (subcommand == 0x01) { - if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) { - CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) { + if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } } - if (is_preview == false && - (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) { - CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + else { + if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } + if (is_preview == false && + (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } } uint8_t existing = 0; for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) { @@ -144,11 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { else if (subcommand == 0x02 || subcommand == 0x03) { file_t *rp_ef = NULL; if (subcommand == 0x02) { - if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) { - CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) { + if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } } - if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) { - CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + else { + if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } + if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } } rp_counter = 1; rp_total = 0; @@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { } if (subcommand == 0x04) { *(raw_subpara - 1) = 0x04; - if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) { - CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) { + if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } } - if (is_preview == false && - (!(paut.permissions & CTAP_PERMISSION_CM) || - (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) { - CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + else { + if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } + if (is_preview == false && + (!(paut.permissions & CTAP_PERMISSION_CM) || + (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) { + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + } } cred_counter = 1; cred_total = 0; diff --git a/src/fido/ctap.h b/src/fido/ctap.h index ce5617a..4c6aa31 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -134,6 +134,7 @@ typedef struct { #define CTAP_PERMISSION_BE 0x08 // BioEnrollment #define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite #define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration +#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly typedef struct mse { uint8_t Qpt[65]; diff --git a/src/fido/fido.c b/src/fido/fido.c index b4e1d1c..799edbd 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -42,6 +42,7 @@ int fido_process_apdu(); int fido_unload(); pinUvAuthToken_t paut = { 0 }; +persistentPinUvAuthToken_t ppaut = { 0 }; uint8_t keydev_dec[32]; bool has_keydev_dec = false; @@ -419,6 +420,19 @@ int scan_files_fido() { else { printf("FATAL ERROR: Auth Token not found in memory!\r\n"); } + file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF); + if (ef_pauthtoken) { + if (!file_has_data(ef_pauthtoken)) { + uint8_t t[32]; + random_gen(NULL, t, sizeof(t)); + file_put_data(ef_pauthtoken, t, sizeof(t)); + } + ppaut.data = file_get_data(ef_pauthtoken); + ppaut.len = file_get_size(ef_pauthtoken); + } + else { + printf("FATAL ERROR: Persistent Auth Token not found in memory!\r\n"); + } ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF); if (!file_has_data(ef_largeblob)) { file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17); diff --git a/src/fido/fido.h b/src/fido/fido.h index d2e2979..4bb2d9b 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -129,9 +129,17 @@ typedef struct pinUvAuthToken { bool user_verified; } pinUvAuthToken_t; +typedef struct persistentPinUvAuthToken { + uint8_t *data; + size_t len; + uint8_t permissions; +} persistentPinUvAuthToken_t; + extern uint32_t user_present_time_limit; extern pinUvAuthToken_t paut; +extern persistentPinUvAuthToken_t ppaut; + extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign); extern uint8_t session_pin[32]; diff --git a/src/fido/files.c b/src/fido/files.c index bf403bd..63f4d5c 100644 --- a/src/fido/files.c +++ b/src/fido/files.c @@ -27,6 +27,7 @@ file_t file_entries[] = { { .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter { .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN { .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN + { .fid = EF_PAUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PERSISTENT AUTH TOKEN { .fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH { .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options { .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob diff --git a/src/fido/files.h b/src/fido/files.h index 93ceec9..176f1d5 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -29,6 +29,7 @@ #define EF_OPTS 0xC001 #define EF_PIN 0x1080 #define EF_AUTHTOKEN 0x1090 +#define EF_PAUTHTOKEN 0x1091 #define EF_MINPINLEN 0x1100 #define EF_DEV_CONF 0x1122 #define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF From 292a9e8d8a3ef48e06d0943dfd6d15c6d55eac00 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 28 Aug 2025 01:04:09 +0200 Subject: [PATCH 07/45] Add support for hmac-secret-mc extension. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 3 +- src/fido/cbor_make_credential.c | 93 ++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index a2d58f1..a87314e 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -38,12 +38,13 @@ int cbor_get_info() { CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 6)); + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 7)); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength")); + CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret-mc")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "thirdPartyPayment")); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index bd9539d..ae82b45 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -26,6 +26,7 @@ #include "mbedtls/sha256.h" #include "random.h" #include "pico_keys.h" +#include "crypto_utils.h" int cbor_make_credential(const uint8_t *data, size_t len) { CborParser parser; @@ -39,7 +40,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) { PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 }; size_t excludeList_len = 0; CredOptions options = { 0 }; - uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0; + uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0, hmacSecretPinUvAuthProtocol = 1; + int64_t kty = 2, hmac_alg = 0, crv = 0; + CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 }; + bool hmac_secret_mc = false; uint8_t *aut_data = NULL; size_t resp_size = 0; CredExtensions extensions = { 0 }; @@ -127,6 +131,31 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_PARSE_MAP_START(_f1, 2) { CBOR_FIELD_GET_KEY_TEXT(2); + if (strcmp(_fd2, "hmac-secret-mc") == 0) { + hmac_secret_mc = true; + uint64_t ukey = 0; + CBOR_PARSE_MAP_START(_f2, 3) + { + CBOR_FIELD_GET_UINT(ukey, 3); + if (ukey == 0x01) { + CBOR_CHECK(COSE_read_key(&_f3, &kty, &hmac_alg, &crv, &kax, &kay)); + } + else if (ukey == 0x02) { + CBOR_FIELD_GET_BYTES(salt_enc, 3); + } + else if (ukey == 0x03) { + CBOR_FIELD_GET_BYTES(salt_auth, 3); + } + else if (ukey == 0x04) { + CBOR_FIELD_GET_UINT(hmacSecretPinUvAuthProtocol, 3); + } + else { + CBOR_ADVANCE(3); + } + } + CBOR_PARSE_MAP_END(_f2, 3); + continue; + } CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret); CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength); @@ -313,6 +342,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_INVALID_OPTION); } + if (hmac_secret_mc && extensions.hmac_secret != ptrue) { + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + } + if (options.up == ptrue || options.up == NULL) { //14.1 if (pinUvAuthParam.present == true) { if (getUserPresentFlagValue() == false) { @@ -372,6 +405,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) { if (extensions.credBlob.present == true) { l++; } + if (hmac_secret_mc) { + l++; + } if (l > 0) { CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); if (extensions.credBlob.present == true) { @@ -392,6 +428,57 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength")); CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen)); } + if (hmac_secret_mc) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret-mc")); + + uint8_t sharedSecret[64] = {0}; + mbedtls_ecp_point Qp; + mbedtls_ecp_point_init(&Qp); + mbedtls_mpi_lset(&Qp.Z, 1); + if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) { + mbedtls_ecp_point_free(&Qp); + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) { + mbedtls_ecp_point_free(&Qp); + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + int ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret); + mbedtls_ecp_point_free(&Qp); + if (ret != 0) { + mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) { + mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); + CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST); + } + uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE; + ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec); + if (ret != 0) { + mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + uint8_t cred_random[64] = {0}, *crd = NULL; + ret = credential_derive_hmac_key(cred_id, cred_id_len, cred_random); + if (ret != 0) { + mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + if (flags & FIDO2_AUT_FLAG_UV) { + crd = cred_random + 32; + } + else { + crd = cred_random; + } + uint8_t out1[64] = {0}, hmac_res[80] = {0}; + mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1); + if ((uint8_t)salt_enc.len == 64 + poff) { + mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32); + } + encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len)); + } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); ext_len = cbor_encoder_get_buffer_size(&encoder, ext); @@ -569,6 +656,10 @@ err: CBOR_FREE_BYTE_STRING(user.id); CBOR_FREE_BYTE_STRING(user.displayName); CBOR_FREE_BYTE_STRING(user.parent.name); + CBOR_FREE_BYTE_STRING(kax); + CBOR_FREE_BYTE_STRING(kay); + CBOR_FREE_BYTE_STRING(salt_enc); + CBOR_FREE_BYTE_STRING(salt_auth); if (extensions.present == true) { CBOR_FREE_BYTE_STRING(extensions.credBlob); } From 66ecd6a7fc74be20e432c9c341ae122a09c44f54 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:17:40 +0200 Subject: [PATCH 08/45] Fix uint16 endianness that affected chained RAPDU. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 8321db1..a340657 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 8321db1f67d924dca7bbe063662ed535ed98086f +Subproject commit a3406572cde8c5243de1ab2825d871a7d4a99f4a From f7ba3eec38afd76c8deac9ba7e67bd92c005c4d9 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:19:54 +0200 Subject: [PATCH 09/45] Fix crash APDU with CBOR. Signed-off-by: Pol Henarejos --- src/fido/fido.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fido/fido.c b/src/fido/fido.c index 799edbd..d59a870 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -499,11 +499,13 @@ extern int cmd_register(); extern int cmd_authenticate(); extern int cmd_version(); extern int cbor_parse(int, uint8_t *, size_t); +extern void driver_init_hid(); #define CTAP_CBOR 0x10 int cmd_cbor() { uint8_t *old_buf = res_APDU; + driver_init_hid(); int ret = cbor_parse(0x90, apdu.data, apdu.nc); if (ret != 0) { return SW_EXEC_ERROR(); From d30ebde4f0b8660700a5a754cbc8335ceaad14b2 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:20:12 +0200 Subject: [PATCH 10/45] Upgrade tinycbor to 0.6.1 Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index a340657..365567d 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit a3406572cde8c5243de1ab2825d871a7d4a99f4a +Subproject commit 365567d12b792b5e55ef32705fb3e948287f6999 From fdf97f54692de568fd547e896400024e1165f8ad Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:20:31 +0200 Subject: [PATCH 11/45] Upgrade tests to python-fido2 v2.0.0 Signed-off-by: Pol Henarejos --- tests/conftest.py | 104 +++++++++++++-------- tests/docker/bullseye/Dockerfile | 6 +- tests/docker/fido2/__init__.py | 61 +++++++----- tests/pico-fido/test_020_register.py | 48 ++++------ tests/pico-fido/test_021_authenticate.py | 24 ++--- tests/pico-fido/test_022_discoverable.py | 20 ++-- tests/pico-fido/test_031_blob.py | 32 ++++--- tests/pico-fido/test_033_credprotect.py | 6 +- tests/pico-fido/test_035_hmac_secret.py | 107 +++++++++++----------- tests/pico-fido/test_051_ctap1_interop.py | 2 +- 10 files changed, 217 insertions(+), 193 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8828e91..dce2795 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,12 +20,13 @@ from http import client from fido2.hid import CtapHidDevice -from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend +from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend, DefaultClientDataCollector from fido2.attestation import FidoU2FAttestation from fido2.ctap2.pin import ClientPin from fido2.server import Fido2Server from fido2.ctap import CtapError -from fido2.webauthn import CollectedClientData, PublicKeyCredentialParameters, PublicKeyCredentialType +from fido2.webauthn import PublicKeyCredentialParameters, PublicKeyCredentialType, PublicKeyCredentialCreationOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, UserVerificationRequirement, PublicKeyCredentialRequestOptions +from fido2.ctap2.extensions import HmacSecretExtension, LargeBlobExtension, CredBlobExtension, CredProtectExtension, MinPinLengthExtension, CredPropsExtension, ThirdPartyPaymentExtension from utils import * from fido2.cose import ES256 import sys @@ -70,11 +71,13 @@ class DeviceSelectCredential: pass class Device(): - def __init__(self, origin="https://example.com", user_interaction=CliInteraction(),uv="discouraged",rp={"id": "example.com", "name": "Example RP"}, attestation="direct"): + def __init__(self, origin="https://example.com", user_interaction=CliInteraction(), uv="discouraged", rp={"id": "example.com", "name": "Example RP"}, attestation="direct"): self.__user = None self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv) self.__set_server(rp=rp, attestation=attestation) + def __verify_rp(rp_id, origin): + return True def __set_client(self, origin, user_interaction, uv): self.__uv = uv @@ -101,14 +104,23 @@ class Device(): sys.exit(1) # Set up a FIDO 2 client using the origin https://example.com - self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction) + extensions = [ + HmacSecretExtension(allow_hmac_secret=True), + LargeBlobExtension(), + CredBlobExtension(), + CredProtectExtension(), + MinPinLengthExtension(), + CredPropsExtension(), + ThirdPartyPaymentExtension() + ] + self.__client = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction, extensions=extensions) # Prefer UV if supported and configured if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"): self.__uv = "preferred" print("Authenticator supports User Verification") - self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction) + self.__client1 = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction) self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction) self.ctap1 = self.__client1._backend.ctap1 @@ -117,7 +129,7 @@ class Device(): self.__attestation = attestation self.__server = Fido2Server(self.__rp, attestation=self.__attestation) self.__server.allowed_algorithms = [ - PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, p['alg']) + PublicKeyCredentialParameters(type=PublicKeyCredentialType.PUBLIC_KEY, alg=p['alg']) for p in self.__client._backend.info.algorithms ] @@ -216,9 +228,7 @@ class Device(): 'key_params':key_params}} def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False): - client_data = client_data if client_data is not Ellipsis else CollectedClientData.create( - type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32) - ) + client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp) rp = rp if rp is not Ellipsis else self.__rp user = user if user is not Ellipsis else self.user() key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms @@ -226,22 +236,31 @@ class Device(): client = self.__client1 else: client = self.__client - result = client._backend.do_make_credential( - client_data=client_data, - rp=rp, - user=user, - key_params=key_params, - exclude_list=exclude_list, + options=PublicKeyCredentialCreationOptions( + rp=PublicKeyCredentialRpEntity.from_dict(rp), + user=PublicKeyCredentialUserEntity.from_dict(user), + pub_key_cred_params=key_params, + exclude_credentials=exclude_list, extensions=extensions, - rk=rk, - user_verification=user_verification, - enterprise_attestation=enterprise_attestation, + challenge=os.urandom(32), + authenticator_selection=AuthenticatorSelectionCriteria( + require_resident_key=rk, + user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED + ), + attestation=enterprise_attestation + ) + client_data, rp_id = client_data.collect_client_data(options=options) + result = client._backend.do_make_credential( + options=options, + client_data=client_data, + rp_id=rp_id, + enterprise_rpid_list=None, event=event ) - return {'res':result,'req':{'client_data':client_data, + return {'res':result.response,'req':{'client_data':client_data, 'rp':rp, 'user':user, - 'key_params':key_params}} + 'key_params':key_params},'client_extension_results':result.client_extension_results} def try_make_credential(self, options=None): if (options is None): @@ -267,14 +286,14 @@ class Device(): # Complete registration auth_data = self.__server.register_complete( - state, result.client_data, result.attestation_object + state=state, response=result ) credentials = [auth_data.credential_data] print("New credential created!") - print("CLIENT DATA:", result.client_data) - print("ATTESTATION OBJECT:", result.attestation_object) + print("CLIENT DATA:", result.response.client_data) + print("ATTESTATION OBJECT:", result.response.attestation_object) print() print("CREDENTIAL DATA:", auth_data.credential_data) @@ -294,17 +313,14 @@ class Device(): self.__server.authenticate_complete( state, credentials, - result.credential_id, - result.client_data, - result.authenticator_data, - result.signature, + result ) print("Credential authenticated!") - print("CLIENT DATA:", result.client_data) + print("CLIENT DATA:", result.response.client_data) print() - print("AUTH DATA:", result.authenticator_data) + print("AUTH DATA:", result.response.authenticator_data) def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None): rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id'] @@ -325,21 +341,31 @@ class Device(): return self.__client._backend.ctap2.get_next_assertion() def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False): - client_data = client_data if client_data is not Ellipsis else CollectedClientData.create( - type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32) - ) + client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp) + if (ctap1 is True): + client = self.__client1 + else: + client = self.__client + rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id'] + options=PublicKeyCredentialRequestOptions( + challenge=os.urandom(32), + rp_id=rp_id, + allow_credentials=allow_list, + user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED, + extensions=extensions + ) + client_data, rp_id = client_data.collect_client_data(options=options) + if (ctap1 is True): client = self.__client1 else: client = self.__client try: result = client._backend.do_get_assertion( + options=options, client_data=client_data, rp_id=rp_id, - allow_list=allow_list, - extensions=extensions, - user_verification=user_verification, event=event ) except ClientError as e: @@ -347,11 +373,9 @@ class Device(): client_pin = ClientPin(self.__client._backend.ctap2) client_pin.set_pin(DEFAULT_PIN) result = client._backend.do_get_assertion( + options=options, client_data=client_data, rp_id=rp_id, - allow_list=allow_list, - extensions=extensions, - user_verification=user_verification, event=event ) else: @@ -416,8 +440,8 @@ def AuthRes(device, RegRes, *args): {"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ], *args) aut_data = res['res'].get_response(0) - m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash - ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature) + m = aut_data.response.authenticator_data.rp_id_hash + aut_data.response.authenticator_data.flags.to_bytes(1, 'big') + aut_data.response.authenticator_data.counter.to_bytes(4, 'big') + aut_data.response.client_data.hash + ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature) return aut_data @pytest.fixture(scope="class") diff --git a/tests/docker/bullseye/Dockerfile b/tests/docker/bullseye/Dockerfile index f4e20f8..b261379 100644 --- a/tests/docker/bullseye/Dockerfile +++ b/tests/docker/bullseye/Dockerfile @@ -22,11 +22,7 @@ RUN apt install -y libccid \ cmake \ libfuse-dev \ && rm -rf /var/lib/apt/lists/* -RUN pip3 install pytest pycvc cryptography pyscard inputimeout -RUN git clone https://github.com/polhenarejos/python-fido2.git -WORKDIR /python-fido2 -RUN git checkout development -RUN pip3 install . +RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0 WORKDIR / RUN git clone https://github.com/frankmorgner/vsmartcard.git WORKDIR /vsmartcard/virtualsmartcard diff --git a/tests/docker/fido2/__init__.py b/tests/docker/fido2/__init__.py index 83359b2..1755c76 100644 --- a/tests/docker/fido2/__init__.py +++ b/tests/docker/fido2/__init__.py @@ -27,16 +27,17 @@ from __future__ import annotations -from .base import HidDescriptor -from ..ctap import CtapDevice, CtapError, STATUS -from ..utils import LOG_LEVEL_TRAFFIC -from threading import Event -from enum import IntEnum, IntFlag, unique -from typing import Tuple, Optional, Callable, Iterator +import logging +import os import struct import sys -import os -import logging +from enum import IntEnum, IntFlag, unique +from threading import Event +from typing import Callable, Iterator + +from ..ctap import STATUS, CtapDevice, CtapError +from ..utils import LOG_LEVEL_TRAFFIC +from .base import HidDescriptor logger = logging.getLogger(__name__) @@ -55,6 +56,7 @@ elif sys.platform.startswith("openbsd"): from . import openbsd as backend else: raise Exception("Unsupported platform") + from . import emulation as backend list_descriptors = backend.list_descriptors @@ -62,6 +64,10 @@ get_descriptor = backend.get_descriptor open_connection = backend.open_connection +class ConnectionFailure(Exception): + """The CTAP connection failed or returned an invalid response.""" + + @unique class CTAPHID(IntEnum): PING = 0x01 @@ -109,7 +115,7 @@ class CtapHidDevice(CtapDevice): response = self.call(CTAPHID.INIT, nonce) r_nonce, response = response[:8], response[8:] if r_nonce != nonce: - raise Exception("Wrong nonce") + raise ConnectionFailure("Wrong nonce") ( self._channel_id, self._u2fhid_version, @@ -129,7 +135,7 @@ class CtapHidDevice(CtapDevice): return self._u2fhid_version @property - def device_version(self) -> Tuple[int, int, int]: + def device_version(self) -> tuple[int, int, int]: """Device version number.""" return self._device_version @@ -139,12 +145,12 @@ class CtapHidDevice(CtapDevice): return self._capabilities @property - def product_name(self) -> Optional[str]: + def product_name(self) -> str | None: """Product name of device.""" return self.descriptor.product_name @property - def serial_number(self) -> Optional[str]: + def serial_number(self) -> str | None: """Serial number of device.""" return self.descriptor.serial_number @@ -159,10 +165,22 @@ class CtapHidDevice(CtapDevice): self, cmd: int, data: bytes = b"", - event: Optional[Event] = None, - on_keepalive: Optional[Callable[[int], None]] = None, + event: Event | None = None, + on_keepalive: Callable[[STATUS], None] | None = None, ) -> bytes: event = event or Event() + + while True: + try: + return self._do_call(cmd, data, event, on_keepalive) + except CtapError as e: + if e.code == CtapError.ERR.CHANNEL_BUSY: + if not event.wait(0.1): + logger.warning("CTAP channel busy, trying again...") + continue # Keep retrying on BUSY while not cancelled + raise + + def _do_call(self, cmd, data, event, on_keepalive): remaining = data seq = 0 @@ -194,7 +212,7 @@ class CtapHidDevice(CtapDevice): r_channel = struct.unpack_from(">I", recv)[0] recv = recv[4:] if r_channel != self._channel_id: - raise Exception("Wrong channel") + raise ConnectionFailure("Wrong channel") if not response: # Initialization packet r_cmd, r_len = struct.unpack_from(">BH", recv) @@ -202,13 +220,12 @@ class CtapHidDevice(CtapDevice): if r_cmd == TYPE_INIT | cmd: pass # first data packet elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE: - ka_status = struct.unpack_from(">B", recv)[0] - logger.debug(f"Got keepalive status: {ka_status:02x}") + try: + ka_status = STATUS(struct.unpack_from(">B", recv)[0]) + logger.debug(f"Got keepalive status: {ka_status:02x}") + except ValueError: + raise ConnectionFailure("Invalid keepalive status") if on_keepalive and ka_status != last_ka: - try: - ka_status = STATUS(ka_status) - except ValueError: - pass # Unknown status value last_ka = ka_status on_keepalive(ka_status) continue @@ -220,7 +237,7 @@ class CtapHidDevice(CtapDevice): r_seq = struct.unpack_from(">B", recv)[0] recv = recv[1:] if r_seq != seq: - raise Exception("Wrong sequence number") + raise ConnectionFailure("Wrong sequence number") seq += 1 response += recv diff --git a/tests/pico-fido/test_020_register.py b/tests/pico-fido/test_020_register.py index 28c37e6..b43c4fe 100644 --- a/tests/pico-fido/test_020_register.py +++ b/tests/pico-fido/test_020_register.py @@ -20,8 +20,6 @@ from fido2.client import CtapError from fido2.cose import ES256, ES384, ES512, EdDSA -import fido2.features -fido2.features.webauthn_json_mapping.enabled = False from utils import ES256K import pytest @@ -51,13 +49,13 @@ def test_bad_type_cdh(device): def test_missing_user(device): with pytest.raises(CtapError) as e: - device.doMC(user=None) + device.MC(user=None) assert e.value.code == CtapError.ERR.MISSING_PARAMETER def test_bad_type_user_user(device): with pytest.raises(CtapError) as e: - device.doMC(user=b"12345678") + device.MC(user=b"12345678") def test_missing_rp(device): with pytest.raises(CtapError) as e: @@ -71,7 +69,7 @@ def test_bad_type_rp(device): def test_missing_pubKeyCredParams(device): with pytest.raises(CtapError) as e: - device.doMC(key_params=None) + device.MC(key_params=None) assert e.value.code == CtapError.ERR.MISSING_PARAMETER @@ -93,35 +91,23 @@ def test_bad_type_options(device): def test_bad_type_rp_name(device): with pytest.raises(CtapError) as e: - device.doMC(rp={"id": "test.org", "name": 8, "icon": "icon"}) + device.MC(rp={"id": "test.org", "name": 8, "icon": "icon"}) def test_bad_type_rp_id(device): with pytest.raises(CtapError) as e: - device.doMC(rp={"id": 8, "name": "name", "icon": "icon"}) - -def test_bad_type_rp_icon(device): - with pytest.raises(CtapError) as e: - device.doMC(rp={"id": "test.org", "name": "name", "icon": 8}) + device.MC(rp={"id": 8, "name": "name", "icon": "icon"}) def test_bad_type_user_name(device): with pytest.raises(CtapError) as e: - device.doMC(user={"id": b"user_id", "name": 8}) + device.MC(user={"id": b"user_id", "name": 8}) def test_bad_type_user_id(device): with pytest.raises(CtapError) as e: - device.doMC(user={"id": "user_id", "name": "name"}) + device.MC(user={"id": "user_id", "name": "name"}) def test_bad_type_user_displayName(device): with pytest.raises(CtapError) as e: - device.doMC(user={"id": "user_id", "name": "name", "displayName": 8}) - -def test_bad_type_user_icon(device): - with pytest.raises(CtapError) as e: - device.doMC(user={"id": "user_id", "name": "name", "icon": 8}) - -def test_bad_type_pubKeyCredParams(device): - with pytest.raises(CtapError) as e: - device.doMC(key_params=["wrong"]) + device.MC(user={"id": "user_id", "name": "name", "displayName": 8}) @pytest.mark.parametrize( "alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM] @@ -132,13 +118,13 @@ def test_algorithms(device, info, alg): def test_missing_pubKeyCredParams_type(device): with pytest.raises(CtapError) as e: - device.doMC(key_params=[{"alg": ES256.ALGORITHM}]) + device.MC(key_params=[{"alg": ES256.ALGORITHM}]) assert e.value.code == CtapError.ERR.INVALID_CBOR def test_missing_pubKeyCredParams_alg(device): with pytest.raises(CtapError) as e: - device.doMC(key_params=[{"type": "public-key"}]) + device.MC(key_params=[{"type": "public-key"}]) assert e.value.code in [ CtapError.ERR.INVALID_CBOR, @@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device): def test_bad_type_pubKeyCredParams_alg(device): with pytest.raises(CtapError) as e: - device.doMC(key_params=[{"alg": "7", "type": "public-key"}]) + device.MC(key_params=[{"alg": "7", "type": "public-key"}]) assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE @@ -158,26 +144,26 @@ def test_unsupported_algorithm(device): assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM def test_exclude_list(resetdevice): - resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}]) + resetdevice.MC(exclude_list=[{"id": b"1234", "type": "rot13"}]) def test_exclude_list2(resetdevice): - resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}]) + resetdevice.MC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}]) def test_bad_type_exclude_list(device): with pytest.raises(CtapError) as e: - device.doMC(exclude_list=["1234"]) + device.MC(exclude_list=["1234"]) def test_missing_exclude_list_type(device): with pytest.raises(CtapError) as e: - device.doMC(exclude_list=[{"id": b"1234"}]) + device.MC(exclude_list=[{"id": b"1234"}]) def test_missing_exclude_list_id(device): with pytest.raises(CtapError) as e: - device.doMC(exclude_list=[{"type": "public-key"}]) + device.MC(exclude_list=[{"type": "public-key"}]) def test_bad_type_exclude_list_id(device): with pytest.raises(CtapError) as e: - device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}]) + device.MC(exclude_list=[{"type": "public-key", "id": "1234"}]) def test_bad_type_exclude_list_type(device): with pytest.raises(CtapError) as e: diff --git a/tests/pico-fido/test_021_authenticate.py b/tests/pico-fido/test_021_authenticate.py index 14260b7..96e5338 100644 --- a/tests/pico-fido/test_021_authenticate.py +++ b/tests/pico-fido/test_021_authenticate.py @@ -31,10 +31,10 @@ def test_authenticate(device): AUTRes = device.authenticate(credentials) def test_assertion_auth_data(GARes): - assert len(GARes['res'].get_response(0).authenticator_data) == 37 + assert len(GARes['res'].get_response(0).response.authenticator_data) == 37 def test_Check_that_AT_flag_is_not_set(GARes): - assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0 + assert (GARes['res'].get_response(0).response.authenticator_data.flags & 0xF8) == 0 def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes): res = device.GA(allow_list=[ @@ -63,8 +63,8 @@ def test_get_assertion_allow_list_filtering_and_buffering(device): """ Check that authenticator filters and stores items in allow list correctly """ allow_list = [] - rp1 = {"id": "rp1.com", "name": "rp1.com"} - rp2 = {"id": "rp2.com", "name": "rp2.com"} + rp1 = {"id": "example.com", "name": "rp1.com"} + rp2 = {"id": "example.com", "name": "rp2.com"} rp1_registrations = [] rp2_registrations = [] @@ -127,7 +127,7 @@ def test_mismatched_rp(device, GARes): rp_id += ".com" with pytest.raises(CtapError) as e: - device.doGA(rp_id=rp_id) + device.GA(rp_id=rp_id) assert e.value.code == CtapError.ERR.NO_CREDENTIALS def test_missing_rp(device): @@ -137,7 +137,7 @@ def test_missing_rp(device): def test_bad_rp(device): with pytest.raises(CtapError) as e: - device.doGA(rp_id={"id": {"type": "wrong"}}) + device.GA(rp_id={"id": {"type": "wrong"}}) def test_missing_cdh(device): with pytest.raises(CtapError) as e: @@ -150,11 +150,11 @@ def test_bad_cdh(device): def test_bad_allow_list(device): with pytest.raises(CtapError) as e: - device.doGA(allow_list={"type": "wrong"}) + device.GA(allow_list={"type": "wrong"}) def test_bad_allow_list_item(device, MCRes): with pytest.raises(CtapError) as e: - device.doGA(allow_list=["wrong"] + [ + device.GA(allow_list=["wrong"] + [ {"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ] ) @@ -177,7 +177,7 @@ def test_option_up(device, info, GARes): assert res.auth_data.flags & (1 << 0) def test_allow_list_fake_item(device, MCRes): - device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}] + device.GA(allow_list=[{"type": "rot13", "id": b"1234"}] + [ {"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ], @@ -185,7 +185,7 @@ def test_allow_list_fake_item(device, MCRes): def test_allow_list_missing_field(device, MCRes): with pytest.raises(CtapError) as e: - device.doGA(allow_list=[{"id": b"1234"}] + [ + device.GA(allow_list=[{"id": b"1234"}] + [ {"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ] ) @@ -200,7 +200,7 @@ def test_allow_list_field_wrong_type(device, MCRes): def test_allow_list_id_wrong_type(device, MCRes): with pytest.raises(CtapError) as e: - device.doGA(allow_list=[{"type": "public-key", "id": 42}] + device.GA(allow_list=[{"type": "public-key", "id": 42}] + [ {"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ] @@ -208,7 +208,7 @@ def test_allow_list_id_wrong_type(device, MCRes): def test_allow_list_missing_id(device, MCRes): with pytest.raises(CtapError) as e: - device.doGA(allow_list=[{"type": "public-key"}] + [ + device.GA(allow_list=[{"type": "public-key"}] + [ {"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ] ) diff --git a/tests/pico-fido/test_022_discoverable.py b/tests/pico-fido/test_022_discoverable.py index d1d3c3f..83f8c37 100644 --- a/tests/pico-fido/test_022_discoverable.py +++ b/tests/pico-fido/test_022_discoverable.py @@ -85,7 +85,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC): auths = [] regs = [] # Use unique RP to not collide with other credentials - rp = {"id": f"unique-{random.random()}.com", "name": "Example"} + rp = {"id": "example.com", "name": "Example"} for i in range(0, 3): res = device.doMC(rp=rp, rk=True, user=generate_random_user()) regs.append(res) @@ -116,7 +116,7 @@ def test_rk_maximum_size_nodisplay(device): auths = resGA.get_assertions() user_max_GA = auths[0] - print(auths) + for y in ("name", "displayName", "id"): if (y in user_max_GA): assert user_max_GA.user[y] == user_max[y] @@ -126,7 +126,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC): """ Test maximum returned capacity of the RK for the given RP """ - + device.reset() # Try to determine from get_info, or default to 19. RK_CAPACITY_PER_RP = info.max_creds_in_list if not RK_CAPACITY_PER_RP: @@ -140,7 +140,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC): return user # Use unique RP to not collide with other credentials from other tests. - rp = {"id": f"unique-{random.random()}.com", "name": "Example"} + rp = {"id": "example.com", "name": "Example"} # req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp) # res = device.sendGA(*req.toGA()) @@ -183,10 +183,10 @@ def test_rk_with_allowlist_of_different_rp(resetdevice): """ rk_rp = {"id": "rk-cred.org", "name": "Example"} - rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object + rk_res = resetdevice.MC(rp = rk_rp, options={"rk":True})['res'] server_rp = {"id": "server-cred.com", "name": "Example"} - server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object + server_res = resetdevice.MC(rp = server_rp, options={"rk":True})['res'] allow_list_with_different_rp_cred = [ { @@ -197,7 +197,7 @@ def test_rk_with_allowlist_of_different_rp(resetdevice): with pytest.raises(CtapError) as e: - res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred) + res = resetdevice.GA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred) assert e.value.code == CtapError.ERR.NO_CREDENTIALS @@ -208,10 +208,10 @@ def test_same_userId_overwrites_rk(resetdevice): rp = {"id": "overwrite.org", "name": "Example"} user = generate_random_user() - mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user) + mc_res1 = resetdevice.MC(rp = rp, options={"rk":True}, user = user) # Should overwrite the first credential. - mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user) + mc_res2 = resetdevice.MC(rp = rp, options={"rk":True}, user = user) ga_res = resetdevice.GA(rp_id=rp['id'])['res'] @@ -227,7 +227,7 @@ def test_larger_icon_than_128(device): user = generate_random_user() user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128) - device.doMC(rp = rp, rk=True, user = user) + device.MC(rp = rp, options={"rk":True}, user = user) def test_returned_credential(device): diff --git a/tests/pico-fido/test_031_blob.py b/tests/pico-fido/test_031_blob.py index f5d2a73..6aba82d 100644 --- a/tests/pico-fido/test_031_blob.py +++ b/tests/pico-fido/test_031_blob.py @@ -21,6 +21,7 @@ import pytest from fido2.ctap import CtapError from fido2.ctap2.pin import PinProtocolV2, ClientPin +from fido2.utils import websafe_decode from utils import verify import os @@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob): @pytest.fixture(scope="function") def MCLBK(device): - res = device.doMC( + mc = device.doMC( rk=True, extensions={'largeBlob':{'support':'required'}} - )['res'] - return res + ) + res = mc['res'] + ext = mc['client_extension_results'] + return {'res': res, 'ext': ext} @pytest.fixture(scope="function") def GALBRead(device, MCLBK): res = device.doGA( allow_list=[ - {"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} + {"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ],extensions={'largeBlob':{'read': True}} ) assertions = res['res'].get_assertions() for a in assertions: - verify(MCLBK.attestation_object, a, res['req']['client_data'].hash) + verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash) return res['res'] @pytest.fixture(scope="function") @@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead): @pytest.fixture(scope="function") def GALBReadLB(GALBRead): + print(GALBRead.get_response(0)) return GALBRead.get_response(0) @pytest.fixture(scope="function") def GALBWrite(device, MCLBK): res = device.doGA( allow_list=[ - {"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} + {"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ],extensions={'largeBlob':{'write': LARGE_BLOB}} ) assertions = res['res'].get_assertions() for a in assertions: - verify(MCLBK.attestation_object, a, res['req']['client_data'].hash) + verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash) return res['res'].get_response(0) def test_supports_credblob(info): @@ -136,15 +140,17 @@ def test_supports_largeblobs(info): assert info.max_large_blob is None or (info.max_large_blob > 1024) def test_get_largeblobkey_mc(MCLBK): - assert 'supported' in MCLBK.extension_results - assert MCLBK.extension_results['supported'] is True + assert 'largeBlob' in MCLBK['ext'] + assert 'supported' in MCLBK['ext']['largeBlob'] + assert MCLBK['ext']['largeBlob']['supported'] is True def test_get_largeblobkey_ga(GALBReadLBK): assert GALBReadLBK.large_blob_key is not None def test_get_largeblob_rw(GALBWrite, GALBReadLB): - assert 'written' in GALBWrite.extension_results - assert GALBWrite.extension_results['written'] is True + assert 'largeBlob' in GALBWrite.client_extension_results + assert 'written' in GALBWrite.client_extension_results['largeBlob'] + assert GALBWrite.client_extension_results['largeBlob']['written'] is True - assert 'blob' in GALBReadLB.extension_results - assert GALBReadLB.extension_results['blob'] == LARGE_BLOB + assert 'blob' in GALBReadLB.client_extension_results['largeBlob'] + assert websafe_decode(GALBReadLB.client_extension_results['largeBlob']['blob']) == LARGE_BLOB diff --git a/tests/pico-fido/test_033_credprotect.py b/tests/pico-fido/test_033_credprotect.py index 83e02c8..65b7d2d 100644 --- a/tests/pico-fido/test_033_credprotect.py +++ b/tests/pico-fido/test_033_credprotect.py @@ -83,7 +83,7 @@ def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList): ] with pytest.raises(CtapError) as e: - device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list) + device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list) assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED @@ -123,10 +123,10 @@ def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional }, ] # works - res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0] + res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0] assert res1.number_of_credentials in (None, 2) - results = device.doGA(allow_list=allow_list)['res'].get_assertions() + results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions() # the required credProtect is not returned. for res in results: diff --git a/tests/pico-fido/test_035_hmac_secret.py b/tests/pico-fido/test_035_hmac_secret.py index ed2861c..ccd276c 100644 --- a/tests/pico-fido/test_035_hmac_secret.py +++ b/tests/pico-fido/test_035_hmac_secret.py @@ -24,6 +24,7 @@ from fido2.ctap2.extensions import HmacSecretExtension from fido2.utils import hmac_sha256 from fido2.ctap2.pin import PinProtocolV2 from fido2.webauthn import UserVerificationRequirement +from fido2.client import ClientError from utils import * salt1 = b"\xa5" * 32 @@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice): res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True) return res['res'].attestation_object -@pytest.fixture(scope="class") -def hmac(resetdevice): - return HmacSecretExtension(resetdevice.client()._backend.ctap2, pin_protocol=PinProtocolV2()) - def test_hmac_secret_make_credential(MCHmacSecret): assert MCHmacSecret.auth_data.extensions assert "hmac-secret" in MCHmacSecret.auth_data.extensions @@ -55,51 +52,51 @@ def test_fake_extension(device): @pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)]) -def test_hmac_secret_entropy(device, MCHmacSecret, hmac, salts +def test_hmac_secret_entropy(device, MCHmacSecret, salts ): hout = {'salt1':salts[0]} if (len(salts) > 1): hout['salt2'] = salts[1] auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0) - ext = auth.extension_results + ext = auth.client_extension_results assert ext assert "hmacGetSecret" in ext - assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16 + assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16 - #print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret'])) + #print(shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret'])) if len(salts) == 1: - assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5 - assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5 + assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5 + assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5 if len(salts) == 2: - assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4 - assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5 - assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.5 + assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4 + assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5 + assert shannon_entropy(ext.hmac_get_secret.output2) > 4.5 -def get_output(device, MCHmacSecret, hmac, salts): +def get_output(device, MCHmacSecret, salts): hout = {'salt1':salts[0]} if (len(salts) > 1): hout['salt2'] = salts[1] auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0) - ext = auth.extension_results + ext = auth.client_extension_results assert ext assert "hmacGetSecret" in ext - assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16 + assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16 if len(salts) == 2: - return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2'] + return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2 else: - return ext["hmacGetSecret"]['output1'] + return ext.hmac_get_secret.output1 -def test_hmac_secret_sanity(device, MCHmacSecret, hmac): - output1 = get_output(device, MCHmacSecret, hmac, (salt1,)) +def test_hmac_secret_sanity(device, MCHmacSecret): + output1 = get_output(device, MCHmacSecret, (salt1,)) output12 = get_output( - device, MCHmacSecret, hmac, (salt1, salt2) + device, MCHmacSecret, (salt1, salt2) ) output21 = get_output( - device, MCHmacSecret, hmac, (salt2, salt1) + device, MCHmacSecret, (salt2, salt1) ) assert output12[0] == output1 @@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac): assert output21[0] == output12[1] assert output12[0] != output12[1] -def test_missing_keyAgreement(device, hmac): - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}}) +def test_missing_keyAgreement(device): with pytest.raises(CtapError): - device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}}) + device.GA(extensions={"hmac-secret": {2: b'1234', 3: b'1234'}}) -def test_missing_saltAuth(device, hmac): - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}}) +def test_missing_saltAuth(device): with pytest.raises(CtapError) as e: - device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}}) + device.GA(extensions={"hmac-secret": {2: b'1234'}}) assert e.value.code == CtapError.ERR.MISSING_PARAMETER -def test_missing_saltEnc(device, hmac): - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}}) +def test_missing_saltEnc(device,): with pytest.raises(CtapError) as e: - device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}}) + device.GA(extensions={"hmac-secret": { 3: b'1234'}}) assert e.value.code == CtapError.ERR.MISSING_PARAMETER -def test_bad_auth(device, hmac, MCHmacSecret): +def test_bad_auth(device, MCHmacSecret): - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}}) - bad_auth = list(hout[3][:]) - bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1 - bad_auth = bytes(bad_auth) + key_agreement = { + 1: 2, + 3: -25, # Per the spec, "although this is NOT the algorithm actually used" + -1: 1, + -2: b'\x00'*32, + -3: b'\x00'*32, + } with pytest.raises(CtapError) as e: - device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 4: 2}}) + device.GA(extensions={"hmac-secret": {1: key_agreement, 2: b'\x00'*80, 3: b'\x00'*32, 4: 2}}) assert e.value.code == CtapError.ERR.EXTENSION_FIRST @pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)]) -def test_invalid_salt_length(device, hmac, salts): - with pytest.raises(ValueError) as e: +def test_invalid_salt_length(device, salts): + with pytest.raises((CtapError,ClientError)) as e: if (len(salts) == 2): - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}}) + hout = {"salt1":salts[0],"salt2":salts[1]} else: - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}}) + hout = {"salt1":salts[0]} device.doGA(extensions={"hmacGetSecret": hout}) @pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)]) def test_get_next_assertion_has_extension( - device, hmac, salts + device, salts ): """ Check that get_next_assertion properly returns extension information for multiple accounts. """ if (len(salts) == 2): - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}}) + hout = {"salt1":salts[0],"salt2":salts[1]} else: - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}}) + hout = {"salt1":salts[0]} accounts = 3 regs = [] auths = [] - rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"} + rp = {"id": f"example.com", "name": "ExampleRP_2"} fixed_users = [generate_random_user() for _ in range(accounts)] for i in range(accounts): res = device.doMC(extensions={"hmacCreateSecret": True}, @@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension( assert "hmac-secret" in ext assert isinstance(ext["hmac-secret"], bytes) assert len(ext["hmac-secret"]) == len(salts) * 32 + 16 - key = hmac.process_get_output(x) - -def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac): +def test_hmac_secret_different_with_uv(device, MCHmacSecret): salts = [salt1] if (len(salts) == 2): - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}}) + hout = {"salt1":salts[0],"salt2":salts[1]} else: - hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}}) + hout = {"salt1":salts[0]} - auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res'] - assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0 + auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0) + assert (auth_no_uv.response.authenticator_data.flags & (1 << 2)) == 0 - ext_no_uv = auth_no_uv.auth_data.extensions + ext_no_uv = auth_no_uv.response.authenticator_data.extensions assert ext_no_uv assert "hmac-secret" in ext_no_uv assert isinstance(ext_no_uv["hmac-secret"], bytes) @@ -209,11 +204,11 @@ def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac): hout['salt2'] = salts[1] auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0) - assert auth_uv.authenticator_data.flags & (1 << 2) - ext_uv = auth_uv.extension_results + assert auth_uv.response.authenticator_data.flags & (1 << 2) + ext_uv = auth_uv.client_extension_results assert ext_uv assert "hmacGetSecret" in ext_uv - assert len(ext_uv["hmacGetSecret"]) == len(salts) + assert len([p for p in ext_uv["hmacGetSecret"] if len(ext_uv["hmacGetSecret"][p]) > 0]) == len(salts) # Now see if the hmac-secrets are different assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1'] diff --git a/tests/pico-fido/test_051_ctap1_interop.py b/tests/pico-fido/test_051_ctap1_interop.py index da7e244..d8d3eab 100644 --- a/tests/pico-fido/test_051_ctap1_interop.py +++ b/tests/pico-fido/test_051_ctap1_interop.py @@ -29,7 +29,7 @@ def test_authenticate_ctap1_through_ctap2(device, RegRes): res = device.doGA(ctap1=False, allow_list=[ {"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} ]) - assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id + assert res['res'].get_response(0).raw_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id # Test FIDO2 register works with U2F auth From a5fd31a5d6cf36578271061e68a1d85123c9ad1a Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:32:22 +0200 Subject: [PATCH 12/45] Upgrade to bookworm CI for fido2 Signed-off-by: Pol Henarejos --- tests/docker/bookworm/Dockerfile | 32 ++++++++++++++++++++++++++++++++ tests/docker_env.sh | 2 +- tests/start-up-and-test.sh | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 tests/docker/bookworm/Dockerfile diff --git a/tests/docker/bookworm/Dockerfile b/tests/docker/bookworm/Dockerfile new file mode 100644 index 0000000..db2074a --- /dev/null +++ b/tests/docker/bookworm/Dockerfile @@ -0,0 +1,32 @@ +FROM debian:bookworm + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update && apt upgrade -y +RUN apt install -y apt-utils +RUN apt install -y libccid \ + libpcsclite-dev \ + git \ + autoconf \ + pkg-config \ + libtool \ + help2man \ + automake \ + gcc \ + make \ + build-essential \ + opensc \ + python3 \ + python3-pip \ + swig \ + cmake \ + libfuse-dev \ + && rm -rf /var/lib/apt/lists/* +RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0 --break-system-packages +WORKDIR / +RUN git clone https://github.com/frankmorgner/vsmartcard.git +WORKDIR /vsmartcard/virtualsmartcard +RUN autoreconf --verbose --install +RUN ./configure --sysconfdir=/etc +RUN make && make install +WORKDIR / diff --git a/tests/docker_env.sh b/tests/docker_env.sh index 384bc9c..7c8cebe 100644 --- a/tests/docker_env.sh +++ b/tests/docker_env.sh @@ -46,7 +46,7 @@ # default values, can be overridden by the environment -: ${MBEDTLS_DOCKER_GUEST:=bullseye} +: ${MBEDTLS_DOCKER_GUEST:=bookworm} DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}" diff --git a/tests/start-up-and-test.sh b/tests/start-up-and-test.sh index 1620c97..613b594 100755 --- a/tests/start-up-and-test.sh +++ b/tests/start-up-and-test.sh @@ -3,6 +3,6 @@ /usr/sbin/pcscd & sleep 2 rm -f memory.flash -cp -R tests/docker/fido2/* /usr/local/lib/python3.9/dist-packages/fido2/hid +cp -R tests/docker/fido2/* /usr/local/lib/python3.11/dist-packages/fido2/hid ./build_in_docker/pico_fido > /dev/null & pytest tests From 44c5ad4adbce485cc6358ce1fb250a2e6170c653 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 20:38:07 +0200 Subject: [PATCH 13/45] Some VIDs do not support VENDOR_CONFIG values. Fixes #172. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index a87314e..e0e7dc3 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -27,7 +27,11 @@ int cbor_get_info() { CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2; CborError error = CborNoError; cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0); - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15)); + uint8_t lfields = 14; + if (phy_data.vid != 0x1050) { + lfields++; + } + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4)); @@ -152,13 +156,13 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength - - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); - CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); - CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE)); - CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); - + if (phy_data.vid != 0x1050) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE)); + CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); + } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); err: if (error != CborNoError) { From 35a043f2615da87eb8390adbe277c94e5a490352 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 20:41:23 +0200 Subject: [PATCH 14/45] Fix automatic build. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index e0e7dc3..5b05188 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -22,6 +22,7 @@ #include "files.h" #include "apdu.h" #include "version.h" +#include "phy.h" int cbor_get_info() { CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2; From 3fe3a9d2ec025652272334663b8211007b02d47f Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 20:50:44 +0200 Subject: [PATCH 15/45] Fix build for emulation. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 5b05188..34c7af2 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -22,16 +22,19 @@ #include "files.h" #include "apdu.h" #include "version.h" -#include "phy.h" int cbor_get_info() { CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2; CborError error = CborNoError; cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0); uint8_t lfields = 14; +#ifndef ENABLE_EMULATION if (phy_data.vid != 0x1050) { lfields++; } +#else + lfields++; +#endif CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); @@ -157,13 +160,17 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength +#ifndef ENABLE_EMULATION if (phy_data.vid != 0x1050) { +#endif CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE)); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); +#ifndef ENABLE_EMULATION } +#endif CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); err: if (error != CborNoError) { From 351242d377ad9bf1838e62cbfca0485c1069a1ee Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 21:27:53 +0200 Subject: [PATCH 16/45] Fix build for ESP. Signed-off-by: Pol Henarejos --- src/fido/oath.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fido/oath.c b/src/fido/oath.c index c9ebd2d..9155727 100644 --- a/src/fido/oath.c +++ b/src/fido/oath.c @@ -24,7 +24,7 @@ #include "asn1.h" #include "crypto_utils.h" #include "management.h" -#include "ctap_hid.h" +extern bool is_nk; #define MAX_OATH_CRED 255 #define CHALLENGE_LEN 8 @@ -638,7 +638,7 @@ int cmd_rename() { if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) { return SW_WRONG_DATA(); } - uint8_t *new_data = (uint8_t *) calloc(sizeof(uint8_t), fsize + new_name.len - name.len); + uint8_t *new_data = (uint8_t *) calloc(fsize + new_name.len - name.len, sizeof(uint8_t)); memcpy(new_data, fdata, name.data - fdata); *(new_data + (name.data - fdata) - 1) = new_name.len; memcpy(new_data + (name.data - fdata), new_name.data, new_name.len); From d1c61536e01f3538eb21a503c5f2edbc83210070 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 21:28:09 +0200 Subject: [PATCH 17/45] Add support for dynamic led driver. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 365567d..2e2b784 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 365567d12b792b5e55ef32705fb3e948287f6999 +Subproject commit 2e2b78445cc163aaacc48cacf24820c00d0c4949 From 6836ffaf02286194b3a831c422d9a8e1502ebb37 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 22:02:13 +0200 Subject: [PATCH 18/45] Add dummy led driver to avoid crashes in case a non-supported board is built. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 2e2b784..95f02b6 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 2e2b78445cc163aaacc48cacf24820c00d0c4949 +Subproject commit 95f02b6ea7c3615b1d69cc43b6533cd566f988ac From 2919b37e9cb1722ff186b7d1f5c69050b4bf5b69 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 2 Sep 2025 01:20:15 +0200 Subject: [PATCH 19/45] Fix descriptor description when there are disabled interfaces. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 95f02b6..202d32d 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 95f02b6ea7c3615b1d69cc43b6533cd566f988ac +Subproject commit 202d32d13dc29e9bbb978de3f9ca95ac97cf5ca3 From 48cc417546daa7d305ca2c70dece8eec8912dc37 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 2 Sep 2025 15:49:39 +0200 Subject: [PATCH 20/45] Added support for Brainpool curves and Ed448. Signed-off-by: Pol Henarejos --- src/fido/cbor.c | 3 +++ src/fido/cbor_make_credential.c | 42 +++++++++++++++++++++++---------- src/fido/fido.c | 9 +++++++ src/fido/fido.h | 17 ++++++++++--- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/fido/cbor.c b/src/fido/cbor.c index ae09a0f..48f9a1b 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -214,6 +214,9 @@ CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent, else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) { alg = FIDO2_ALG_EDDSA; } + else if (key->grp.id == MBEDTLS_ECP_DP_ED448) { + alg = FIDO2_ALG_ED448; + } #endif return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder); } diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index ae82b45..a4c0b9a 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -231,21 +231,36 @@ int cbor_make_credential(const uint8_t *data, size_t len) { if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) { CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE); } - if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) { + if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP256) { if (curve <= 0) { curve = FIDO2_CURVE_P256; } } - else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384) { + else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP384) { if (curve <= 0) { curve = FIDO2_CURVE_P384; } } - else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512) { + else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP512) { if (curve <= 0) { curve = FIDO2_CURVE_P521; } } + else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB256) { + if (curve <= 0) { + curve = FIDO2_CURVE_BP256R1; + } + } + else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB384) { + if (curve <= 0) { + curve = FIDO2_CURVE_BP384R1; + } + } + else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB512) { + if (curve <= 0) { + curve = FIDO2_CURVE_BP512R1; + } + } else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K #ifndef ENABLE_EMULATION && (phy_data.enabled_curves & PHY_CURVE_SECP256K1) @@ -256,11 +271,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) { } } #ifdef MBEDTLS_EDDSA_C - else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) { + else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA || pubKeyCredParams[i].alg == FIDO2_ALG_ED25519) { if (curve <= 0) { curve = FIDO2_CURVE_ED25519; } } + else if (pubKeyCredParams[i].alg == FIDO2_ALG_ED448) { + if (curve <= 0) { + curve = FIDO2_CURVE_ED448; + } + } #endif else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) { // pass @@ -327,9 +347,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { continue; } Credential ecred = {0}; - if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, - &ecred) == 0 && - (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || + if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) { credential_free(&ecred); CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); @@ -367,9 +385,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0}; size_t cred_id_len = 0; - CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, - &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, - cred_id, &cred_id_len)); + CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len)); if (getUserVerifiedFlagValue()) { flags |= FIDO2_AUT_FLAG_UV; @@ -523,14 +539,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) { memcpy(pa, clientDataHash.data, clientDataHash.len); uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0}; const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) { + if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP384R1) { md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); } - else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) { + else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP512R1) { md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); } #ifdef MBEDTLS_EDDSA_C - else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) { + else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519 || ekey.grp.id == MBEDTLS_ECP_DP_ED448) { md = NULL; } #endif diff --git a/src/fido/fido.c b/src/fido/fido.c index d59a870..5e66815 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -119,6 +119,15 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) { return MBEDTLS_ECP_DP_ED448; } #endif + else if (curve == FIDO2_CURVE_BP256R1) { + return MBEDTLS_ECP_DP_BP256R1; + } + else if (curve == FIDO2_CURVE_BP384R1) { + return MBEDTLS_ECP_DP_BP384R1; + } + else if (curve == FIDO2_CURVE_BP512R1) { + return MBEDTLS_ECP_DP_BP512R1; + } return MBEDTLS_ECP_DP_NONE; } int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) { diff --git a/src/fido/fido.h b/src/fido/fido.h index 4bb2d9b..2a0ed2f 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -60,15 +60,23 @@ extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out); extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret); -#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256 +#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 #define FIDO2_ALG_EDDSA -8 //EdDSA -#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384 -#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521 +#define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256 +#define FIDO2_ALG_ED25519 -19 //EDDSA Ed25519 +#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 +#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 #define FIDO2_ALG_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256 #define FIDO2_ALG_ES256K -47 +#define FIDO2_ALG_ESP384 -51 //ECDSA-SHA384 P384 +#define FIDO2_ALG_ESP512 -52 //ECDSA-SHA512 P521 +#define FIDO2_ALG_ED448 -53 //EDDSA Ed448 #define FIDO2_ALG_RS256 -257 #define FIDO2_ALG_RS384 -258 #define FIDO2_ALG_RS512 -259 +#define FIDO2_ALG_ESB256 -265 //ECDSA-SHA256 BP256r1 +#define FIDO2_ALG_ESB384 -267 //ECDSA-SHA384 BP384r1 +#define FIDO2_ALG_ESB512 -268 //ECDSA-SHA512 BP512r1 #define FIDO2_CURVE_P256 1 #define FIDO2_CURVE_P384 2 @@ -78,6 +86,9 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec #define FIDO2_CURVE_ED25519 6 #define FIDO2_CURVE_ED448 7 #define FIDO2_CURVE_P256K1 8 +#define FIDO2_CURVE_BP256R1 9 +#define FIDO2_CURVE_BP384R1 10 +#define FIDO2_CURVE_BP512R1 11 #define FIDO2_AUT_FLAG_UP 0x1 #define FIDO2_AUT_FLAG_UV 0x4 From 1ac628d2419f2f0935e5e16be5d3d7567413fb78 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 4 Sep 2025 21:57:53 +0200 Subject: [PATCH 21/45] Major refactor on resident keys. Now, credential ids have shorter and fixed length (40) to avoid issues with some servers, which have maximum credential id length constraints. Fixes #184 Signed-off-by: Pol Henarejos --- src/fido/cbor_cred_mgmt.c | 18 +- src/fido/cbor_get_assertion.c | 76 ++++++--- src/fido/cbor_make_credential.c | 36 +++- src/fido/credential.c | 27 ++- src/fido/credential.h | 8 + tests/pico-fido/test_022_discoverable.py | 5 +- tests/pico-fido/test_033_credprotect.py | 204 +++++++++++------------ 7 files changed, 230 insertions(+), 144 deletions(-) diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 5cd9f8a..fe5c95a 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -259,7 +259,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { } Credential cred = { 0 }; - if (credential_load(file_get_data(cred_ef) + 32, file_get_size(cred_ef) - 32, rpIdHash.data, &cred) != 0) { + if (credential_load(file_get_data(cred_ef) + 32 + CRED_RESIDENT_LEN, file_get_size(cred_ef) - 32 - CRED_RESIDENT_LEN, rpIdHash.data, &cred) != 0) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } @@ -316,7 +316,9 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07)); CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id")); - CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len)); + uint8_t cred_idr[CRED_RESIDENT_LEN] = {0}; + credential_derive_resident(cred.id.data, cred.id.len, cred_idr); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr))); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type")); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key")); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); @@ -372,7 +374,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { } for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) { file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i)); - if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) { + if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) { uint8_t *rp_id_hash = file_get_data(ef); if (delete_file(ef) != 0) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); @@ -414,10 +416,10 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { } for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) { file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i)); - if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) { + if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) { Credential cred = { 0 }; uint8_t *rp_id_hash = file_get_data(ef); - if (credential_load(rp_id_hash + 32, file_get_size(ef) - 32, rp_id_hash, &cred) != 0) { + if (credential_load(rp_id_hash + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &cred) != 0) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) { @@ -427,9 +429,9 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { uint8_t newcred[MAX_CRED_ID_LENGTH]; size_t newcred_len = 0; if (credential_create(&cred.rpId, &cred.userId, &user.parent.name, - &user.displayName, &cred.opts, &cred.extensions, - cred.use_sign_count, (int)cred.alg, - (int)cred.curve, newcred, &newcred_len) != 0) { + &user.displayName, &cred.opts, &cred.extensions, + cred.use_sign_count, (int)cred.alg, + (int)cred.curve, newcred, &newcred_len) != 0) { credential_free(&cred); CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 1ace962..743e70f 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -295,27 +295,48 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { if (strcmp(allowList[e].type.data, "public-key") != 0) { continue; } - if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) { - credential_free(&creds[creds_len]); - } - else { - creds_len++; - silent = false; // If we are able to load a credential, we are not silent - // Even we provide allowList, we need to check if the credential is resident - if (!resident) { - for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { - file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i)); - if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) { + if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) { + for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { + file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i)); + if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) { + continue; + } + if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) { + if (credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &creds[creds_len]) != 0) { + // Should never happen + credential_free(&creds[creds_len]); continue; } - if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) { - resident = true; + resident = true; + creds_len++; + silent = false; // If we are able to load a credential, we are not silent + break; + } + } + } + else { + if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) { + credential_free(&creds[creds_len]); + } + else { + creds_len++; + silent = false; // If we are able to load a credential, we are not silent + // Even we provide allowList, we need to check if the credential is resident + if (!resident) { + for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { + file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i)); + if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) { + continue; + } + if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) { + resident = true; + break; + } + } + if (resident) { break; } } - if (resident) { - break; - } } } } @@ -326,7 +347,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) { continue; } - int ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &creds[creds_len]); + int ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &creds[creds_len]); if (ret != 0) { credential_free(&creds[creds_len]); } @@ -343,8 +364,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) { credential_free(&creds[i]); } - else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && - resident == true && !(flags & FIDO2_AUT_FLAG_UV)) { + else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && resident == true && !(flags & FIDO2_AUT_FLAG_UV)) { credential_free(&creds[i]); } else { @@ -427,10 +447,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } else { selcred = &creds[0]; + if (resident && allowList_len > 1) { + numberOfCredentials = 1; + } if (numberOfCredentials > 1) { asserted = true; residentx = resident; for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { + credential_free(&credsx[i]); + } + for (int i = 0; i < numberOfCredentials; i++) { credsx[i] = creds[i]; } numberOfCredentialsx = numberOfCredentials; @@ -633,7 +659,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id")); if (selcred) { - CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len)); + if (resident) { + uint8_t cred_idr[CRED_RESIDENT_LEN] = {0}; + credential_derive_resident(selcred->id.data, selcred->id.len, cred_idr); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr))); + } + else { + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len)); + } } else { CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16)); @@ -660,8 +693,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, lu)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id")); - CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, - selcred->userId.len)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len)); if (numberOfCredentials > 1 && allowList_len == 0) { if (selcred->userName.present == true) { CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name")); diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index a4c0b9a..75d87f6 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -347,10 +347,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) { continue; } Credential ecred = {0}; - if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || - (flags & FIDO2_AUT_FLAG_UV))) { + if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) { + for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) { + file_t *ef_cred = search_dynamic_file((uint16_t)(EF_CRED + i)); + if (!file_has_data(ef_cred) || memcmp(file_get_data(ef_cred), rp_id_hash, 32) != 0) { + continue; + } + uint8_t *cred_idr = file_get_data(ef_cred) + 32; + if (memcmp(cred_idr, excludeList[e].id.data, CRED_RESIDENT_LEN) == 0) { + if (credential_load(file_get_data(ef_cred) + 32 + CRED_RESIDENT_LEN, file_get_size(ef_cred) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) { + credential_free(&ecred); + CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); + } + } + } + } + else { + if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) { credential_free(&ecred); - CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); + CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); + } } credential_free(&ecred); } @@ -520,15 +536,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder)); size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf); - size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + cred_id_len + rs) + ext_len; + size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + (options.rk == ptrue ? CRED_RESIDENT_LEN : cred_id_len) + rs) + ext_len; aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len); uint8_t *pa = aut_data; memcpy(pa, rp_id_hash, 32); pa += 32; *pa++ = flags; pa += put_uint32_t_be(ctr, pa); memcpy(pa, aaguid, 16); pa += 16; - pa += put_uint16_t_be(cred_id_len, pa); - memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len; + if (options.rk == ptrue) { + uint8_t cred_idr[CRED_RESIDENT_LEN] = {0}; + pa += put_uint16_t_be(sizeof(cred_idr), pa); + credential_derive_resident(cred_id, cred_id_len, cred_idr); + memcpy(pa, cred_idr, sizeof(cred_idr)); pa += sizeof(cred_idr); + } + else { + pa += put_uint16_t_be(cred_id_len, pa); + memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len; + } memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs; memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len; if ((size_t)(pa - aut_data) != aut_data_len) { diff --git a/src/fido/credential.c b/src/fido/credential.c index e9edb44..e7c2ecd 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -314,7 +314,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t * if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) { continue; } - ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &rcred); + ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &rcred); if (ret != 0) { credential_free(&rcred); continue; @@ -330,11 +330,14 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t * if (sloti == -1) { return -1; } - uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32); + uint8_t cred_idr[CRED_RESIDENT_LEN] = {0}; + credential_derive_resident(cred_id, cred_id_len, cred_idr); + uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32 + CRED_RESIDENT_LEN); memcpy(data, rp_id_hash, 32); - memcpy(data + 32, cred_id, cred_id_len); + memcpy(data + 32, cred_idr, CRED_RESIDENT_LEN); + memcpy(data + 32 + CRED_RESIDENT_LEN, cred_id, cred_id_len); file_t *ef = file_new((uint16_t)(EF_CRED + sloti)); - file_put_data(ef, data, (uint16_t)cred_id_len + 32); + file_put_data(ef, data, (uint16_t)cred_id_len + 32 + CRED_RESIDENT_LEN); free(data); if (new_record == true) { //increase rps @@ -421,3 +424,19 @@ int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk); return 0; } + +int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) { + memset(outk, 0, CRED_RESIDENT_LEN); + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + uint8_t *cred_idr = outk + CRED_RESIDENT_HEADER_LEN; + mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "SLIP-0022", 9, cred_idr); + mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, cred_idr); + mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "resident", 8, cred_idr); + mbedtls_md_hmac(md_info, cred_idr, 32, cred_id, cred_id_len, cred_idr); + memcpy(outk, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN); + return 0; +} + +bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) { + return memcmp(cred_id, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0; +} diff --git a/src/fido/credential.h b/src/fido/credential.h index c5cfc93..e7a2792 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -58,6 +58,7 @@ typedef struct Credential { #define CRED_PROTO_21_S "\xf1\xd0\x02\x01" #define CRED_PROTO_22_S "\xf1\xd0\x02\x02" +#define CRED_PROTO_23_S "\xf1\xd0\x02\x03" #define CRED_PROTO CRED_PROTO_22_S @@ -66,6 +67,11 @@ typedef struct Credential { #define CRED_TAG_LEN 16 #define CRED_SILENT_TAG_LEN 16 +#define CRED_PROTO_RESIDENT CRED_PROTO_23_S +#define CRED_PROTO_RESIDENT_LEN 4 +#define CRED_RESIDENT_HEADER_LEN (CRED_PROTO_RESIDENT_LEN + 4) +#define CRED_RESIDENT_LEN (CRED_RESIDENT_HEADER_LEN + 32) + typedef enum { CRED_PROTO_21 = 0x01, @@ -94,5 +100,7 @@ extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len extern int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk); +extern int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk); +extern bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len); #endif // _CREDENTIAL_H_ diff --git a/tests/pico-fido/test_022_discoverable.py b/tests/pico-fido/test_022_discoverable.py index 83f8c37..36b7055 100644 --- a/tests/pico-fido/test_022_discoverable.py +++ b/tests/pico-fido/test_022_discoverable.py @@ -58,8 +58,9 @@ def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC): device.reset() # It returns a silent authentication - ga_res = device.doGA(allow_list=allow_list) - + with pytest.raises(CtapError) as e: + ga_res = device.doGA(allow_list=allow_list) + assert e.value.code == CtapError.ERR.NO_CREDENTIALS def test_resident_key(MCRes_DC, info): diff --git a/tests/pico-fido/test_033_credprotect.py b/tests/pico-fido/test_033_credprotect.py index 65b7d2d..b4f4438 100644 --- a/tests/pico-fido/test_033_credprotect.py +++ b/tests/pico-fido/test_033_credprotect.py @@ -22,6 +22,7 @@ import pytest from fido2.ctap2.extensions import CredProtectExtension from fido2.webauthn import UserVerificationRequirement from fido2.ctap import CtapError +from utils import generate_random_user class CredProtect: UserVerificationOptional = 1 @@ -30,140 +31,139 @@ class CredProtect: @pytest.fixture(scope="class") def MCCredProtectOptional(resetdevice): - res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object + res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object return res @pytest.fixture(scope="class") def MCCredProtectOptionalList(resetdevice): - res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object + res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object return res @pytest.fixture(scope="class") def MCCredProtectRequired(resetdevice): - res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object + res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object return res +class TestCredProtect(object): + def test_credprotect_make_credential_1(self, MCCredProtectOptional): + assert MCCredProtectOptional.auth_data.extensions + assert "credProtect" in MCCredProtectOptional.auth_data.extensions + assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1 -def test_credprotect_make_credential_1(MCCredProtectOptional): - assert MCCredProtectOptional.auth_data.extensions - assert "credProtect" in MCCredProtectOptional.auth_data.extensions - assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1 + def test_credprotect_make_credential_2(self, MCCredProtectOptionalList): + assert MCCredProtectOptionalList.auth_data.extensions + assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions + assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2 -def test_credprotect_make_credential_2(MCCredProtectOptionalList): - assert MCCredProtectOptionalList.auth_data.extensions - assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions - assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2 + def test_credprotect_make_credential_3(self, MCCredProtectRequired): + assert MCCredProtectRequired.auth_data.extensions + assert "credProtect" in MCCredProtectRequired.auth_data.extensions + assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3 -def test_credprotect_make_credential_3(MCCredProtectRequired): - assert MCCredProtectRequired.auth_data.extensions - assert "credProtect" in MCCredProtectRequired.auth_data.extensions - assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3 + def test_credprotect_optional_excluded(self, device, MCCredProtectOptional): + """ CredProtectOptional Cred should be visible to be excluded with no UV """ + exclude_list = [ + { + "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], + "type": "public-key", + } + ] -def test_credprotect_optional_excluded(device, MCCredProtectOptional): - """ CredProtectOptional Cred should be visible to be excluded with no UV """ - exclude_list = [ - { - "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], - "type": "public-key", - } - ] + with pytest.raises(CtapError) as e: + device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list) - with pytest.raises(CtapError) as e: - device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list) + assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED - assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED + def test_credprotect_optional_list_excluded(self, device, MCCredProtectOptionalList): + """ CredProtectOptionalList Cred should be visible to be excluded with no UV """ + exclude_list = [ + { + "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], + "type": "public-key", + } + ] -def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList): - """ CredProtectOptionalList Cred should be visible to be excluded with no UV """ - exclude_list = [ - { - "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], - "type": "public-key", - } - ] + with pytest.raises(CtapError) as e: + device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list) - with pytest.raises(CtapError) as e: - device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list) + assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED - assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED + def test_credprotect_required_not_excluded_with_no_uv(self, device, MCCredProtectRequired): + """ CredProtectRequired Cred should NOT be visible to be excluded with no UV """ + exclude_list = [ + { + "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], + "type": "public-key", + } + ] -def test_credprotect_required_not_excluded_with_no_uv(device, MCCredProtectRequired): - """ CredProtectRequired Cred should NOT be visible to be excluded with no UV """ - exclude_list = [ - { - "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], - "type": "public-key", - } - ] + # works + device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list) - # works - device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list) + def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional): -def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional): + # works + res = device.doGA()['res'].get_assertions()[0] - # works - res = device.doGA()['res'].get_assertions()[0] + # If there's only one credential, this is None + assert res.number_of_credentials == None - # If there's only one credential, this is None - assert res.number_of_credentials == None + def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired): + allow_list = [ + { + "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + ] + # works + res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0] + assert res1.number_of_credentials in (None, 2) -def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired): - allow_list = [ - { - "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], - "type": "public-key", - }, - { - "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], - "type": "public-key", - }, - { - "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], - "type": "public-key", - }, - ] - # works - res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0] - assert res1.number_of_credentials in (None, 2) + results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions() - results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions() + # the required credProtect is not returned. + for res in results: + assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:] - # the required credProtect is not returned. - for res in results: - assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:] + def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional): -def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional -): + res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object - res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object + for ext in ["credProtect", "hmac-secret"]: + assert res.auth_data.extensions + assert ext in res.auth_data.extensions + assert res.auth_data.extensions[ext] == True - for ext in ["credProtect", "hmac-secret"]: - assert res.auth_data.extensions - assert ext in res.auth_data.extensions - assert res.auth_data.extensions[ext] == True +class TestCredProtectUv: + def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin): + allow_list = [ + { + "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + ] + pin = "12345678" -def test_credprotect_all_with_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin): - allow_list = [ - { - "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], - "type": "public-key", - }, - { - "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], - "type": "public-key", - }, - { - "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], - "type": "public-key", - }, - ] + client_pin.set_pin(pin) - pin = "12345678" + res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0] - client_pin.set_pin(pin) - - res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0] - - assert res1.number_of_credentials in (None, 3) + assert res1.number_of_credentials in (None, 3) From 56d5c61044358c33bdd596f54923f31cc3861a02 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sat, 6 Sep 2025 19:14:27 +0200 Subject: [PATCH 22/45] Add compatibility of old resident key system with the new one. Related to #184. Signed-off-by: Pol Henarejos --- src/fido/cbor_cred_mgmt.c | 4 ++-- src/fido/cbor_get_assertion.c | 4 ++-- src/fido/cbor_make_credential.c | 2 +- src/fido/credential.c | 7 +++++++ src/fido/credential.h | 2 ++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index fe5c95a..901ec00 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -259,7 +259,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { } Credential cred = { 0 }; - if (credential_load(file_get_data(cred_ef) + 32 + CRED_RESIDENT_LEN, file_get_size(cred_ef) - 32 - CRED_RESIDENT_LEN, rpIdHash.data, &cred) != 0) { + if (credential_load_resident(cred_ef, rpIdHash.data, &cred) != 0) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } @@ -419,7 +419,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) { Credential cred = { 0 }; uint8_t *rp_id_hash = file_get_data(ef); - if (credential_load(rp_id_hash + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &cred) != 0) { + if (credential_load_resident(ef, rp_id_hash, &cred) != 0) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) { diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 743e70f..e2cbaab 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -302,7 +302,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { continue; } if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) { - if (credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &creds[creds_len]) != 0) { + if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) { // Should never happen credential_free(&creds[creds_len]); continue; @@ -347,7 +347,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) { continue; } - int ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &creds[creds_len]); + int ret = credential_load_resident(ef, rp_id_hash, &creds[creds_len]); if (ret != 0) { credential_free(&creds[creds_len]); } diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 75d87f6..08ef580 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -355,7 +355,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { } uint8_t *cred_idr = file_get_data(ef_cred) + 32; if (memcmp(cred_idr, excludeList[e].id.data, CRED_RESIDENT_LEN) == 0) { - if (credential_load(file_get_data(ef_cred) + 32 + CRED_RESIDENT_LEN, file_get_size(ef_cred) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) { + if (credential_load_resident(ef_cred, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) { credential_free(&ecred); CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); } diff --git a/src/fido/credential.c b/src/fido/credential.c index e7c2ecd..3ba18f6 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -440,3 +440,10 @@ int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8 bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) { return memcmp(cred_id, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0; } + +int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred) { + if (credential_is_resident(file_get_data(ef) + 32, file_get_size(ef) - 32)) { + return credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, cred); + } + return credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, cred); +} diff --git a/src/fido/credential.h b/src/fido/credential.h index e7a2792..3b3bbab 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -19,6 +19,7 @@ #define _CREDENTIAL_H_ #include "ctap2_cbor.h" +#include "file.h" typedef struct CredOptions { const bool *rk; @@ -102,5 +103,6 @@ extern int credential_derive_large_blob_key(const uint8_t *cred_id, uint8_t *outk); extern int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk); extern bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len); +extern int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred); #endif // _CREDENTIAL_H_ From 54fb02995f51395b6d03d038512cfbe326830c11 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 11:31:45 +0200 Subject: [PATCH 23/45] Add 4 pseudorandom bytes to allow indexing used by some RP entities. Fixes #185 Signed-off-by: Pol Henarejos --- src/fido/credential.c | 8 ++++++-- src/fido/credential.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/fido/credential.c b/src/fido/credential.c index 3ba18f6..5fca7d0 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -429,16 +429,20 @@ int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8 memset(outk, 0, CRED_RESIDENT_LEN); const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); uint8_t *cred_idr = outk + CRED_RESIDENT_HEADER_LEN; + mbedtls_md_hmac(md_info, cred_idr, 32, pico_serial.id, sizeof(pico_serial.id), outk); + memcpy(outk + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN); + outk[4 + CRED_PROTO_RESIDENT_LEN] = 0x00; + outk[4 + CRED_PROTO_RESIDENT_LEN + 1] = 0x00; + mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "SLIP-0022", 9, cred_idr); mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, cred_idr); mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "resident", 8, cred_idr); mbedtls_md_hmac(md_info, cred_idr, 32, cred_id, cred_id_len, cred_idr); - memcpy(outk, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN); return 0; } bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) { - return memcmp(cred_id, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0; + return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0; } int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred) { diff --git a/src/fido/credential.h b/src/fido/credential.h index 3b3bbab..8e140e4 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -70,7 +70,7 @@ typedef struct Credential { #define CRED_PROTO_RESIDENT CRED_PROTO_23_S #define CRED_PROTO_RESIDENT_LEN 4 -#define CRED_RESIDENT_HEADER_LEN (CRED_PROTO_RESIDENT_LEN + 4) +#define CRED_RESIDENT_HEADER_LEN (CRED_PROTO_RESIDENT_LEN + 6) #define CRED_RESIDENT_LEN (CRED_RESIDENT_HEADER_LEN + 32) typedef enum From 6b636d0bf4f6e28d9e52d3ed033b398c5cff7763 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:13:44 +0200 Subject: [PATCH 24/45] Fix CMD_CONFIG with VendorCmd. Signed-off-by: Pol Henarejos --- src/fido/cbor_config.c | 84 ++++++++++++++++++++--------------------- src/fido/cbor_vendor.c | 11 ------ tools/pico-fido-tool.py | 17 ++++----- 3 files changed, 50 insertions(+), 62 deletions(-) diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 0661846..1d145fa 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -68,7 +68,7 @@ int cbor_config(const uint8_t *data, size_t len) { raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1); CBOR_PARSE_MAP_START(_f1, 2) { - if (subcommand == 0x7f) { // Config Aut + if (subcommand == 0xFF) { // Vendor CBOR_FIELD_GET_UINT(subpara, 2); if (subpara == 0x01) { CBOR_FIELD_GET_UINT(vendorCommandId, 2); @@ -76,6 +76,9 @@ int cbor_config(const uint8_t *data, size_t len) { else if (subpara == 0x02) { CBOR_FIELD_GET_BYTES(vendorAutCt, 2); } + else if (subpara == 0x03) { + CBOR_FIELD_GET_UINT(vendorParam, 2); + } } else if (subcommand == 0x03) { // Extensions CBOR_FIELD_GET_UINT(subpara, 2); @@ -97,15 +100,6 @@ int cbor_config(const uint8_t *data, size_t len) { CBOR_FIELD_GET_BOOL(forceChangePin, 2); } } - else if (subcommand == 0x1B) { // PHY - CBOR_FIELD_GET_UINT(subpara, 2); - if (subpara == 0x01) { - CBOR_FIELD_GET_UINT(vendorCommandId, 2); - } - else if (subpara == 0x02) { - CBOR_FIELD_GET_UINT(vendorParam, 2); - } - } } CBOR_PARSE_MAP_END(_f1, 2); raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara; @@ -124,9 +118,12 @@ int cbor_config(const uint8_t *data, size_t len) { if (pinUvAuthParam.present == false) { CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED); } - if (pinUvAuthProtocol == 0) { + if (pinUvAuthProtocol == 0) { CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); } + if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) { + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len); memset(verify_payload, 0xff, 32); @@ -143,8 +140,15 @@ int cbor_config(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); } - if (subcommand == 0x7f) { - if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) { + if (subcommand == 0xFF) { +#ifndef ENABLE_EMULATION + const bool is_phy = (vendorCommandId == CTAP_CONFIG_PHY_VIDPID || + vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO || + vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS || + vendorCommandId == CTAP_CONFIG_PHY_OPTS); +#endif + if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) + { if (!file_has_data(ef_keydev_enc)) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } @@ -186,6 +190,31 @@ int cbor_config(const uint8_t *data, size_t len) { file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes low_flash_available(); } + +#ifndef ENABLE_EMULATION + else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) { + phy_data.vid = (vendorParam >> 16) & 0xFFFF; + phy_data.pid = vendorParam & 0xFFFF; + phy_data.vidpid_present = true; + } + else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) { + phy_data.led_gpio = (uint8_t)vendorParam; + phy_data.led_gpio_present = true; + } + else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) { + phy_data.led_brightness = (uint8_t)vendorParam; + phy_data.led_brightness_present = true; + } + else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) { + phy_data.opts = (uint16_t)vendorParam; + } + else { + CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); + } + if (is_phy && phy_save() != PICOKEY_OK) { + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } +#endif else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } @@ -228,35 +257,6 @@ int cbor_config(const uint8_t *data, size_t len) { set_opts(get_opts() | FIDO2_OPT_EA); goto err; } -#ifndef ENABLE_EMULATION - else if (subcommand == 0x1B) { - if (vendorParam == 0) { - CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); - } - if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) { - phy_data.vid = (vendorParam >> 16) & 0xFFFF; - phy_data.pid = vendorParam & 0xFFFF; - phy_data.vidpid_present = true; - } - else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) { - phy_data.led_gpio = (uint8_t)vendorParam; - phy_data.led_gpio_present = true; - } - else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) { - phy_data.led_brightness = (uint8_t)vendorParam; - phy_data.led_brightness_present = true; - } - else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) { - phy_data.opts = (uint16_t)vendorParam; - } - else { - CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); - } - if (phy_save() != PICOKEY_OK) { - CBOR_ERROR(CTAP2_ERR_PROCESSING); - } - } -#endif else { CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); } diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c index e8ff439..042140c 100644 --- a/src/fido/cbor_vendor.c +++ b/src/fido/cbor_vendor.c @@ -237,17 +237,6 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret)); } - else if (vendorCmd == 0x02) { - if (vendorParam.present == false) { - CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); - } - file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF); - if (ef_ee_ea) { - file_put_data(ef_ee_ea, vendorParam.data, (uint16_t)vendorParam.len); - } - low_flash_available(); - goto err; - } } #ifndef ENABLE_EMULATION else if (cmd == CTAP_VENDOR_PHY_OPTS) { diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index 963e70f..8b8ab57 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -74,16 +74,15 @@ def get_pki_data(url, data=None, method='GET'): class VendorConfig(Config): + CONFIG_VENDOR_PROTOTYPE = 0xFF class PARAM(IntEnum): VENDOR_COMMAND_ID = 0x01 VENDOR_AUT_CT = 0x02 - VENDOR_PARAM = 0x02 + VENDOR_PARAM = 0x03 class CMD(IntEnum): CONFIG_AUT_ENABLE = 0x03e43f56b34285e2 CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9 - CONFIG_VENDOR_PROTOTYPE = 0x7f - CONFIG_VENDOR_PHY = 0x1b CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa CONFIG_PHY_OPTS = 0x969f3b09eceb805f CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 @@ -97,7 +96,7 @@ class VendorConfig(Config): def enable_device_aut(self, ct): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE, + VendorConfig.CONFIG_VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE, VendorConfig.PARAM.VENDOR_AUT_CT: ct @@ -106,7 +105,7 @@ class VendorConfig(Config): def disable_device_aut(self): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE, + VendorConfig.CONFIG_VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE }, @@ -114,7 +113,7 @@ class VendorConfig(Config): def vidpid(self, vid, pid): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + VendorConfig.CONFIG_VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID, VendorConfig.PARAM.VENDOR_PARAM: (vid & 0xFFFF) << 16 | pid @@ -123,7 +122,7 @@ class VendorConfig(Config): def led_gpio(self, gpio): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + VendorConfig.CONFIG_VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO, VendorConfig.PARAM.VENDOR_PARAM: gpio @@ -132,7 +131,7 @@ class VendorConfig(Config): def led_brightness(self, brightness): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + VendorConfig.CONFIG_VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS, VendorConfig.PARAM.VENDOR_PARAM: brightness @@ -141,7 +140,7 @@ class VendorConfig(Config): def phy_opts(self, opts): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + VendorConfig.CONFIG_VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS, VendorConfig.PARAM.VENDOR_PARAM: opts From bf484d8663fed8226153c046d9d63124b8c1fe36 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:16:14 +0200 Subject: [PATCH 25/45] Use internal macro. Signed-off-by: Pol Henarejos --- tools/pico-fido-tool.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index 8b8ab57..a9b2387 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -73,8 +73,6 @@ def get_pki_data(url, data=None, method='GET'): return j class VendorConfig(Config): - - CONFIG_VENDOR_PROTOTYPE = 0xFF class PARAM(IntEnum): VENDOR_COMMAND_ID = 0x01 VENDOR_AUT_CT = 0x02 @@ -96,7 +94,7 @@ class VendorConfig(Config): def enable_device_aut(self, ct): self._call( - VendorConfig.CONFIG_VENDOR_PROTOTYPE, + Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE, VendorConfig.PARAM.VENDOR_AUT_CT: ct @@ -105,7 +103,7 @@ class VendorConfig(Config): def disable_device_aut(self): self._call( - VendorConfig.CONFIG_VENDOR_PROTOTYPE, + Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE }, @@ -113,7 +111,7 @@ class VendorConfig(Config): def vidpid(self, vid, pid): self._call( - VendorConfig.CONFIG_VENDOR_PROTOTYPE, + Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID, VendorConfig.PARAM.VENDOR_PARAM: (vid & 0xFFFF) << 16 | pid @@ -122,7 +120,7 @@ class VendorConfig(Config): def led_gpio(self, gpio): self._call( - VendorConfig.CONFIG_VENDOR_PROTOTYPE, + Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO, VendorConfig.PARAM.VENDOR_PARAM: gpio @@ -131,7 +129,7 @@ class VendorConfig(Config): def led_brightness(self, brightness): self._call( - VendorConfig.CONFIG_VENDOR_PROTOTYPE, + Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS, VendorConfig.PARAM.VENDOR_PARAM: brightness @@ -140,7 +138,7 @@ class VendorConfig(Config): def phy_opts(self, opts): self._call( - VendorConfig.CONFIG_VENDOR_PROTOTYPE, + Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS, VendorConfig.PARAM.VENDOR_PARAM: opts From b3b3a5eecc60f2e1bfc8e82c40b2d35190eabad9 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:23:45 +0200 Subject: [PATCH 26/45] Add other PHY commands to get_info(). Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 12 +++++++++++- src/fido/ctap.h | 4 +++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 34c7af2..3c6c7cd 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -164,9 +164,19 @@ int cbor_get_info() { if (phy_data.vid != 0x1050) { #endif CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); + uint8_t enabled_cmds = 2; +#ifndef ENABLE_EMULATION + enabled_cmds += 4; +#endif + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, enabled_cmds)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE)); +#ifndef ENABLE_EMULATION + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_GPIO)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_OPTS)); +#endif CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); #ifndef ENABLE_EMULATION } diff --git a/src/fido/ctap.h b/src/fido/ctap.h index 4c6aa31..46ad887 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -114,10 +114,12 @@ typedef struct { #define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2 #define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9 +#ifndef ENABLE_EMULATION #define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa -#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948 #define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd +#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948 #define CTAP_CONFIG_PHY_OPTS 0x969f3b09eceb805f +#endif #define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1) From 7e720e8c230b1177115caa937840638ba4d5acc5 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:56:02 +0200 Subject: [PATCH 27/45] Enable enterprise attestation through VendorConfig. Add a subcommand to enable through pico-tool. Signed-off-by: Pol Henarejos --- src/fido/cbor_config.c | 37 +++++++++++++++++----------- src/fido/ctap.h | 1 + tools/pico-fido-tool.py | 54 +++++++++++++++++++++++------------------ 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 1d145fa..554e9e3 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -38,8 +38,8 @@ int cbor_config(const uint8_t *data, size_t len) { CborParser parser; CborValue map; CborError error = CborNoError; - uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0; - CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 }; + uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0; + CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 }; CborCharString minPinLengthRPIDs[32] = { 0 }; size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0; CborEncoder encoder; @@ -74,10 +74,10 @@ int cbor_config(const uint8_t *data, size_t len) { CBOR_FIELD_GET_UINT(vendorCommandId, 2); } else if (subpara == 0x02) { - CBOR_FIELD_GET_BYTES(vendorAutCt, 2); + CBOR_FIELD_GET_BYTES(vendorParamByteString, 2); } else if (subpara == 0x03) { - CBOR_FIELD_GET_UINT(vendorParam, 2); + CBOR_FIELD_GET_UINT(vendorParamInt, 2); } } else if (subcommand == 0x03) { // Extensions @@ -147,8 +147,7 @@ int cbor_config(const uint8_t *data, size_t len) { vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS || vendorCommandId == CTAP_CONFIG_PHY_OPTS); #endif - if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) - { + if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){ if (!file_has_data(ef_keydev_enc)) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } @@ -169,7 +168,7 @@ int cbor_config(const uint8_t *data, size_t len) { } mbedtls_chachapoly_context chatx; - int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len); + int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len); if (ret != 0) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } @@ -177,7 +176,7 @@ int cbor_config(const uint8_t *data, size_t len) { uint8_t key_dev_enc[12 + 32 + 16]; random_gen(NULL, key_dev_enc, 12); mbedtls_chachapoly_init(&chatx); - mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data); + mbedtls_chachapoly_setkey(&chatx, vendorParamByteString.data); ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev)); mbedtls_chachapoly_free(&chatx); if (ret != 0) { @@ -193,20 +192,20 @@ int cbor_config(const uint8_t *data, size_t len) { #ifndef ENABLE_EMULATION else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) { - phy_data.vid = (vendorParam >> 16) & 0xFFFF; - phy_data.pid = vendorParam & 0xFFFF; + phy_data.vid = (vendorParamInt >> 16) & 0xFFFF; + phy_data.pid = vendorParamInt & 0xFFFF; phy_data.vidpid_present = true; } else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) { - phy_data.led_gpio = (uint8_t)vendorParam; + phy_data.led_gpio = (uint8_t)vendorParamInt; phy_data.led_gpio_present = true; } else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) { - phy_data.led_brightness = (uint8_t)vendorParam; + phy_data.led_brightness = (uint8_t)vendorParamInt; phy_data.led_brightness_present = true; } else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) { - phy_data.opts = (uint16_t)vendorParam; + phy_data.opts = (uint16_t)vendorParamInt; } else { CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); @@ -215,6 +214,16 @@ int cbor_config(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PROCESSING); } #endif + else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) { + if (vendorParamByteString.present == false) { + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + } + file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF); + if (ef_ee_ea) { + file_put_data(ef_ee_ea, vendorParamByteString.data, (uint16_t)vendorParamByteString.len); + } + low_flash_available(); + } else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } @@ -265,7 +274,7 @@ int cbor_config(const uint8_t *data, size_t len) { err: CBOR_FREE_BYTE_STRING(pinUvAuthParam); - CBOR_FREE_BYTE_STRING(vendorAutCt); + CBOR_FREE_BYTE_STRING(vendorParamByteString); for (size_t i = 0; i < minPinLengthRPIDs_len; i++) { CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]); } diff --git a/src/fido/ctap.h b/src/fido/ctap.h index 46ad887..ad248ba 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -114,6 +114,7 @@ typedef struct { #define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2 #define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9 +#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf #ifndef ENABLE_EMULATION #define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa #define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index a9b2387..88eb787 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -75,16 +75,17 @@ def get_pki_data(url, data=None, method='GET'): class VendorConfig(Config): class PARAM(IntEnum): VENDOR_COMMAND_ID = 0x01 - VENDOR_AUT_CT = 0x02 - VENDOR_PARAM = 0x03 + VENDOR_PARAM_BYTESTRING = 0x02 + VENDOR_PARAM_INT = 0x03 class CMD(IntEnum): CONFIG_AUT_ENABLE = 0x03e43f56b34285e2 CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9 + CONFIG_EA_UPLOAD = 0x66f2a674c29a8dcf CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa - CONFIG_PHY_OPTS = 0x969f3b09eceb805f - CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd + CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 + CONFIG_PHY_OPTS = 0x969f3b09eceb805f class RESP(IntEnum): KEY_AGREEMENT = 0x01 @@ -97,7 +98,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE, - VendorConfig.PARAM.VENDOR_AUT_CT: ct + VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: ct }, ) @@ -114,7 +115,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID, - VendorConfig.PARAM.VENDOR_PARAM: (vid & 0xFFFF) << 16 | pid + VendorConfig.PARAM.VENDOR_PARAM_INT: (vid & 0xFFFF) << 16 | pid }, ) @@ -123,7 +124,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO, - VendorConfig.PARAM.VENDOR_PARAM: gpio + VendorConfig.PARAM.VENDOR_PARAM_INT: gpio }, ) @@ -132,7 +133,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS, - VendorConfig.PARAM.VENDOR_PARAM: brightness + VendorConfig.PARAM.VENDOR_PARAM_INT: brightness }, ) @@ -141,7 +142,16 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS, - VendorConfig.PARAM.VENDOR_PARAM: opts + VendorConfig.PARAM.VENDOR_PARAM_INT: opts + }, + ) + + def upload_ea(self, der): + self._call( + Config.CMD.VENDOR_PROTOTYPE, + { + VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_EA_UPLOAD, + VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: der }, ) @@ -242,7 +252,6 @@ class Vendor: DISABLE = 0x02 KEY_AGREEMENT = 0x01 EA_CSR = 0x01 - EA_UPLOAD = 0x02 class RESP(IntEnum): PARAM = 0x01 @@ -430,13 +439,7 @@ class Vendor: )[Vendor.RESP.PARAM] def upload_ea(self, der): - self._call( - Vendor.CMD.VENDOR_EA, - Vendor.SUBCMD.EA_UPLOAD, - { - Vendor.PARAM.PARAM: der - } - ) + self.vcfg.upload_ea(der) def vidpid(self, vid, pid): return self.vcfg.vidpid(vid, pid) @@ -480,6 +483,9 @@ class Vendor: ) return { 'free': resp[1], 'used': resp[2], 'total': resp[3], 'files': resp[4], 'size': resp[5] } + def enable_enterprise_attestation(self): + self.vcfg.enable_enterprise_attestation() + def parse_args(): parser = argparse.ArgumentParser() subparser = parser.add_subparsers(title="commands", dest="command") @@ -492,7 +498,7 @@ def parse_args(): parser_backup.add_argument('filename', help='File to save or load the backup.') parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation') - parser_attestation.add_argument('subcommand', choices=['csr']) + parser_attestation.add_argument('subcommand', choices=['csr','enable']) parser_attestation.add_argument('--filename', help='Uploads the certificate filename to the device as enterprise attestation certificate. If not provided, it will generate an enterprise attestation certificate automatically.') parser_phy = subparser.add_parser('phy', help='Set PHY options.') @@ -513,7 +519,7 @@ def parse_args(): args = parser.parse_args() return args -def secure(vdr, args): +def secure(vdr: Vendor, args): if (args.subcommand == 'enable'): vdr.enable_device_aut() elif (args.subcommand == 'unlock'): @@ -521,13 +527,13 @@ def secure(vdr, args): elif (args.subcommand == 'disable'): vdr.disable_device_aut() -def backup(vdr, args): +def backup(vdr: Vendor, args): if (args.subcommand == 'save'): vdr.backup_save(args.filename) elif (args.subcommand == 'load'): vdr.backup_load(args.filename) -def attestation(vdr, args): +def attestation(vdr: Vendor, args): if (args.subcommand == 'csr'): if (args.filename is None): csr = x509.load_der_x509_csr(vdr.csr()) @@ -542,8 +548,10 @@ def attestation(vdr, args): except ValueError: cert = x509.load_pem_x509_certificate(dataf) vdr.upload_ea(cert.public_bytes(Encoding.DER)) + elif (args.subcommand == 'enable'): + vdr.enable_enterprise_attestation() -def phy(vdr, args): +def phy(vdr: Vendor, args): val = args.value if 'value' in args else None if (val): if (args.subcommand == 'vidpid'): @@ -567,7 +575,7 @@ def phy(vdr, args): else: print('Command executed successfully. Please, restart your Pico Key.') -def memory(vdr, args): +def memory(vdr: Vendor, args): mem = vdr.memory() print(f'Memory usage:') print(f'\tFree: {mem["free"]/1024:.2f} kilobytes ({mem["free"]*100/mem["total"]:.2f}%)') From e4f8caa1ba2b1830715075afcfb56c0155ff9771 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 18:20:36 +0200 Subject: [PATCH 28/45] Add VendorConfig upload EA command to get_info(). Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 3c6c7cd..6455290 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -164,13 +164,14 @@ int cbor_get_info() { if (phy_data.vid != 0x1050) { #endif CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); - uint8_t enabled_cmds = 2; + uint8_t enabled_cmds = 3; #ifndef ENABLE_EMULATION enabled_cmds += 4; #endif CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, enabled_cmds)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD)); #ifndef ENABLE_EMULATION CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS)); From 9b254a07386d37f302769c1c8089dd6337575a53 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 19:20:20 +0200 Subject: [PATCH 29/45] Add support to PIN POLICY URL via VendorConfig. Signed-off-by: Pol Henarejos --- src/fido/cbor_config.c | 21 ++++++++++++++- src/fido/cbor_get_info.c | 14 +++++++++- src/fido/cbor_make_credential.c | 13 ++++++++-- src/fido/ctap.h | 1 + src/fido/files.h | 1 + tools/pico-fido-tool.py | 46 ++++++++++++++++++++++++++++++++- 6 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 554e9e3..02bcd06 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -40,7 +40,7 @@ int cbor_config(const uint8_t *data, size_t len) { CborError error = CborNoError; uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0; CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 }; - CborCharString minPinLengthRPIDs[32] = { 0 }; + CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 }; size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0; CborEncoder encoder; //CborEncoder mapEncoder; @@ -79,6 +79,9 @@ int cbor_config(const uint8_t *data, size_t len) { else if (subpara == 0x03) { CBOR_FIELD_GET_UINT(vendorParamInt, 2); } + else if (subpara == 0x04) { + CBOR_FIELD_GET_TEXT(vendorParamTextString, 2); + } } else if (subcommand == 0x03) { // Extensions CBOR_FIELD_GET_UINT(subpara, 2); @@ -224,6 +227,21 @@ int cbor_config(const uint8_t *data, size_t len) { } low_flash_available(); } + else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) { + file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY); + if (ef_pin_policy) { + uint8_t *val = calloc(1, 2 + vendorParamByteString.len); + if (val) { + // Not ready yet for integer param + // val[0] = (uint8_t)(vendorParamInt >> 8); + // val[1] = (uint8_t)(vendorParamInt & 0xFF); + memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len); + file_put_data(ef_pin_policy, val, 2 + vendorParamByteString.len); + free(val); + } + } + low_flash_available(); + } else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } @@ -275,6 +293,7 @@ int cbor_config(const uint8_t *data, size_t len) { err: CBOR_FREE_BYTE_STRING(pinUvAuthParam); CBOR_FREE_BYTE_STRING(vendorParamByteString); + CBOR_FREE_BYTE_STRING(vendorParamTextString); for (size_t i = 0; i < minPinLengthRPIDs_len; i++) { CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]); } diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 6455290..216e7ee 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -35,6 +35,10 @@ int cbor_get_info() { #else lfields++; #endif + file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF); + if (file_has_data(ef_pin_policy)) { + lfields += 2; + } CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); @@ -164,7 +168,7 @@ int cbor_get_info() { if (phy_data.vid != 0x1050) { #endif CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); - uint8_t enabled_cmds = 3; + uint8_t enabled_cmds = 4; #ifndef ENABLE_EMULATION enabled_cmds += 4; #endif @@ -172,6 +176,7 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY)); #ifndef ENABLE_EMULATION CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS)); @@ -181,6 +186,13 @@ int cbor_get_info() { CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); #ifndef ENABLE_EMULATION } + if (file_has_data(ef_pin_policy)) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B)); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2)); + } + #endif CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); err: diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 08ef580..72e615b 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -44,6 +44,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { int64_t kty = 2, hmac_alg = 0, crv = 0; CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 }; bool hmac_secret_mc = false; + const bool *pin_complexity_policy = NULL; uint8_t *aut_data = NULL; size_t resp_size = 0; CredExtensions extensions = { 0 }; @@ -162,6 +163,8 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment); + CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy); + CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); @@ -440,6 +443,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) { if (hmac_secret_mc) { l++; } + if (pin_complexity_policy == ptrue) { + l++; + } if (l > 0) { CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); if (extensions.credBlob.present == true) { @@ -451,12 +457,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect)); } if (extensions.hmac_secret == ptrue) { - CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret")); CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true)); } if (minPinLen > 0) { - CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength")); CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen)); } @@ -511,6 +515,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) { encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len)); } + if (pin_complexity_policy == ptrue) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy")); + file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy))); + } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); ext_len = cbor_encoder_get_buffer_size(&encoder, ext); diff --git a/src/fido/ctap.h b/src/fido/ctap.h index ad248ba..c33b928 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -115,6 +115,7 @@ typedef struct { #define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2 #define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9 #define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf +#define CTAP_CONFIG_PIN_POLICY 0x6c07d70fe96c3897 #ifndef ENABLE_EMULATION #define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa #define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd diff --git a/src/fido/files.h b/src/fido/files.h index 176f1d5..d6590e5 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -31,6 +31,7 @@ #define EF_AUTHTOKEN 0x1090 #define EF_PAUTHTOKEN 0x1091 #define EF_MINPINLEN 0x1100 +#define EF_PIN_COMPLEXITY_POLICY 0x1102 #define EF_DEV_CONF 0x1122 #define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF #define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index 88eb787..b874742 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -24,7 +24,7 @@ import argparse import platform from binascii import hexlify from threading import Event -from typing import Mapping, Any, Optional, Callable +from typing import List, Mapping, Any, Optional, Callable import struct import urllib.request import json @@ -77,6 +77,7 @@ class VendorConfig(Config): VENDOR_COMMAND_ID = 0x01 VENDOR_PARAM_BYTESTRING = 0x02 VENDOR_PARAM_INT = 0x03 + VENDOR_PARAM_TEXTSTRING = 0x04 class CMD(IntEnum): CONFIG_AUT_ENABLE = 0x03e43f56b34285e2 @@ -86,6 +87,7 @@ class VendorConfig(Config): CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 CONFIG_PHY_OPTS = 0x969f3b09eceb805f + CONFIG_PIN_POLICY = 0x6c07d70fe96c3897 class RESP(IntEnum): KEY_AGREEMENT = 0x01 @@ -155,6 +157,20 @@ class VendorConfig(Config): }, ) + def pin_policy(self, url: bytes|str = None, policy: int = None): + if (url is not None or policy is not None): + params = { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PIN_POLICY } + if (url is not None): + if (isinstance(url, str)): + url = url.encode() + params[VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING] = url + if (policy is not None): + params[VendorConfig.PARAM.VENDOR_PARAM_INT] = policy + self._call( + Config.CMD.VENDOR_PROTOTYPE, + params, + ) + class Ctap2Vendor(Ctap2): def __init__(self, device: CtapDevice, strict_cbor: bool = True): super().__init__(device=device, strict_cbor=strict_cbor) @@ -486,6 +502,18 @@ class Vendor: def enable_enterprise_attestation(self): self.vcfg.enable_enterprise_attestation() + def set_min_pin_length(self, length, rpids: list[str] = None, url=None): + params = { + Config.PARAM.NEW_MIN_PIN_LENGTH: length, + Config.PARAM.MIN_PIN_LENGTH_RPIDS: rpids if rpids else None, + } + self.vcfg.set_min_pin_length( + min_pin_length=length, + rp_ids=rpids if rpids else None, + ) + self.vcfg.pin_policy(url=url.encode() if url else None) + + def parse_args(): parser = argparse.ArgumentParser() subparser = parser.add_subparsers(title="commands", dest="command") @@ -516,6 +544,11 @@ def parse_args(): parser_mem = subparser.add_parser('memory', help='Get current memory usage.') + parser_pin_policy = subparser.add_parser('pin_policy', help='Manage PIN policy.') + parser_pin_policy.add_argument('length', type=int, help='Minimum PIN length (4-63).') + parser_pin_policy.add_argument('--rpids', help='Comma separated list of Relying Party IDs that have authorization to receive minimum PIN length.') + parser_pin_policy.add_argument('--url', help='URL where the user can consult PIN policy.') + args = parser.parse_args() return args @@ -584,6 +617,14 @@ def memory(vdr: Vendor, args): print(f'\tFlash size: {mem["size"]/1024:.2f} kilobytes') print(f'\tFiles: {mem["files"]}') + +def pin_policy(vdr: Vendor, args): + rpids = None + if (args.rpids): + rpids = args.rpids.split(',') + vdr.set_min_pin_length(args.length, rpids, args.url) + + def main(args): print('Pico Fido Tool v1.10') print('Author: Pol Henarejos') @@ -607,6 +648,9 @@ def main(args): phy(vdr, args) elif (args.command == 'memory'): memory(vdr, args) + elif (args.command == 'pin_policy'): + pin_policy(vdr, args) + def run(): args = parse_args() From 56b6b4a8b90e0f4f969d3094b54c94aab2c0627a Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 21 Sep 2025 01:23:02 +0200 Subject: [PATCH 30/45] Vendor Config cmds have to be < 0x8000000000000000 Signed-off-by: Pol Henarejos --- src/fido/ctap.h | 2 +- tools/pico-fido-tool.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fido/ctap.h b/src/fido/ctap.h index c33b928..7b395b9 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -120,7 +120,7 @@ typedef struct { #define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa #define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd #define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948 -#define CTAP_CONFIG_PHY_OPTS 0x969f3b09eceb805f +#define CTAP_CONFIG_PHY_OPTS 0x269f3b09eceb805f #endif #define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1) diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index b874742..07c252f 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -86,7 +86,7 @@ class VendorConfig(Config): CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 - CONFIG_PHY_OPTS = 0x969f3b09eceb805f + CONFIG_PHY_OPTS = 0x269f3b09eceb805f CONFIG_PIN_POLICY = 0x6c07d70fe96c3897 class RESP(IntEnum): From b25e4bed6c8234de08c7cfe2fead170c6e927e04 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 22 Sep 2025 23:35:55 +0200 Subject: [PATCH 31/45] Fix build for non-pico boards. Signed-off-by: Pol Henarejos --- src/fido/cbor_client_pin.c | 2 +- src/fido/cbor_config.c | 2 +- src/fido/cbor_cred_mgmt.c | 2 +- src/fido/cbor_get_assertion.c | 2 +- src/fido/cbor_get_info.c | 1 + src/fido/cbor_large_blobs.c | 2 +- src/fido/cbor_make_credential.c | 2 +- src/fido/cbor_reset.c | 1 + src/fido/cbor_selection.c | 1 + src/fido/cbor_vendor.c | 2 +- src/fido/cmd_authenticate.c | 2 +- src/fido/cmd_register.c | 6 +----- src/fido/credential.c | 2 +- src/fido/defs.c | 3 ++- src/fido/fido.c | 4 ++-- src/fido/fido.h | 4 ---- src/fido/kek.c | 2 +- src/fido/known_apps.c | 1 + src/fido/management.c | 2 +- src/fido/oath.c | 2 +- src/fido/otp.c | 32 +++++++++++--------------------- 21 files changed, 32 insertions(+), 45 deletions(-) diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index ef2cd4d..0464129 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #ifndef ESP_PLATFORM #include "common.h" #else @@ -35,7 +36,6 @@ #include "files.h" #include "random.h" #include "crypto_utils.h" -#include "pico_keys.h" #include "apdu.h" #include "kek.h" diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 02bcd06..3da353d 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "ctap2_cbor.h" #include "fido.h" #include "ctap.h" @@ -22,7 +23,6 @@ #include "files.h" #include "apdu.h" #include "credential.h" -#include "pico_keys.h" #include "random.h" #include "mbedtls/ecdh.h" #include "mbedtls/chachapoly.h" diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 901ec00..3f90a37 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "fido.h" #include "ctap.h" #include "hid/ctap_hid.h" @@ -22,7 +23,6 @@ #include "files.h" #include "apdu.h" #include "credential.h" -#include "pico_keys.h" uint8_t rp_counter = 1; uint8_t rp_total = 0; diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index e2cbaab..7829378 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "cbor.h" #include "ctap.h" #if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) @@ -24,7 +25,6 @@ #include "fido.h" #include "files.h" #include "crypto_utils.h" -#include "pico_keys.h" #include "apdu.h" #include "cbor_make_credential.h" #include "credential.h" diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 216e7ee..63a98a6 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "ctap2_cbor.h" #include "hid/ctap_hid.h" #include "fido.h" diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c index e1b0aa5..d1078c4 100644 --- a/src/fido/cbor_large_blobs.c +++ b/src/fido/cbor_large_blobs.c @@ -15,13 +15,13 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "ctap2_cbor.h" #include "fido.h" #include "ctap.h" #include "hid/ctap_hid.h" #include "files.h" #include "apdu.h" -#include "pico_keys.h" #include "mbedtls/sha256.h" static uint64_t expectedLength = 0, expectedNextOffset = 0; diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 72e615b..359bf91 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "cbor_make_credential.h" #include "ctap2_cbor.h" #include "hid/ctap_hid.h" @@ -25,7 +26,6 @@ #include "credential.h" #include "mbedtls/sha256.h" #include "random.h" -#include "pico_keys.h" #include "crypto_utils.h" int cbor_make_credential(const uint8_t *data, size_t len) { diff --git a/src/fido/cbor_reset.c b/src/fido/cbor_reset.c index afc8298..cc18e9c 100644 --- a/src/fido/cbor_reset.c +++ b/src/fido/cbor_reset.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "file.h" #include "fido.h" #include "ctap.h" diff --git a/src/fido/cbor_selection.c b/src/fido/cbor_selection.c index 8a0e1c2..8e2c395 100644 --- a/src/fido/cbor_selection.c +++ b/src/fido/cbor_selection.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "fido.h" #include "ctap.h" diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c index 042140c..39e00bf 100644 --- a/src/fido/cbor_vendor.c +++ b/src/fido/cbor_vendor.c @@ -15,13 +15,13 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "ctap2_cbor.h" #include "fido.h" #include "ctap.h" #include "hid/ctap_hid.h" #include "files.h" #include "apdu.h" -#include "pico_keys.h" #include "random.h" #include "mbedtls/ecdh.h" #include "mbedtls/chachapoly.h" diff --git a/src/fido/cmd_authenticate.c b/src/fido/cmd_authenticate.c index 41aa729..db6d5da 100644 --- a/src/fido/cmd_authenticate.c +++ b/src/fido/cmd_authenticate.c @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#include "fido.h" #include "pico_keys.h" +#include "fido.h" #include "apdu.h" #include "ctap.h" #include "random.h" diff --git a/src/fido/cmd_register.c b/src/fido/cmd_register.c index b7f1ff3..643af3b 100644 --- a/src/fido/cmd_register.c +++ b/src/fido/cmd_register.c @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#include "fido.h" #include "pico_keys.h" +#include "fido.h" #include "apdu.h" #include "ctap.h" #include "random.h" @@ -69,11 +69,7 @@ int cmd_register() { } if (memcmp(req->appId, bogus_firefox, CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0) -#ifndef ENABLE_EMULATION { return ctap_error(CTAP1_ERR_CHANNEL_BUSY); } -#else - { return SW_DATA_INVALID(); } -#endif mbedtls_ecdsa_context key; mbedtls_ecdsa_init(&key); int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key); diff --git a/src/fido/credential.c b/src/fido/credential.c index 5fca7d0..75b88a5 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "mbedtls/chachapoly.h" #include "mbedtls/sha256.h" #include "credential.h" @@ -26,7 +27,6 @@ #include "ctap.h" #include "random.h" #include "files.h" -#include "pico_keys.h" #include "otp.h" int credential_derive_chacha_key(uint8_t *outk, const uint8_t *); diff --git a/src/fido/defs.c b/src/fido/defs.c index 4089fce..c5db7ae 100644 --- a/src/fido/defs.c +++ b/src/fido/defs.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ - #include "fido.h" +#include "pico_keys.h" +#include "fido.h" uint8_t PICO_PRODUCT = 2; // Pico FIDO diff --git a/src/fido/fido.c b/src/fido/fido.c index 5e66815..742dd62 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -15,9 +15,9 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "fido.h" #include "kek.h" -#include "pico_keys.h" #include "apdu.h" #include "ctap.h" #include "files.h" @@ -25,7 +25,7 @@ #include "random.h" #include "mbedtls/x509_crt.h" #include "mbedtls/hkdf.h" -#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION) +#if defined(USB_ITF_CCID) #include "ccid/ccid.h" #endif #if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) diff --git a/src/fido/fido.h b/src/fido/fido.h index 2a0ed2f..8af6977 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -31,11 +31,7 @@ #ifdef MBEDTLS_EDDSA_C #include "mbedtls/eddsa.h" #endif -#ifndef ENABLE_EMULATION #include "hid/ctap_hid.h" -#else -#include -#endif #define CTAP_PUBKEY_LEN (65) #define KEY_PATH_LEN (32) diff --git a/src/fido/kek.c b/src/fido/kek.c index 8608151..030e05c 100644 --- a/src/fido/kek.c +++ b/src/fido/kek.c @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#include "fido.h" #include "pico_keys.h" +#include "fido.h" #include "stdlib.h" #if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #include "pico/stdlib.h" diff --git a/src/fido/known_apps.c b/src/fido/known_apps.c index e5f1e7c..ffa1cc8 100644 --- a/src/fido/known_apps.c +++ b/src/fido/known_apps.c @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #include "fido.h" #include "ctap2_cbor.h" diff --git a/src/fido/management.c b/src/fido/management.c index 1827509..6f6360d 100644 --- a/src/fido/management.c +++ b/src/fido/management.c @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#include "fido.h" #include "pico_keys.h" +#include "fido.h" #include "apdu.h" #include "version.h" #include "files.h" diff --git a/src/fido/oath.c b/src/fido/oath.c index 9155727..ea6602d 100644 --- a/src/fido/oath.c +++ b/src/fido/oath.c @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#include "fido.h" #include "pico_keys.h" +#include "fido.h" #include "apdu.h" #include "files.h" #include "random.h" diff --git a/src/fido/otp.c b/src/fido/otp.c index e434d27..a9adf33 100644 --- a/src/fido/otp.c +++ b/src/fido/otp.c @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#include "fido.h" #include "pico_keys.h" +#include "fido.h" #include "apdu.h" #include "files.h" #include "random.h" @@ -24,14 +24,17 @@ #include "asn1.h" #include "hid/ctap_hid.h" #include "usb.h" -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "bsp/board.h" #endif +#ifdef ENABLE_EMULATION +void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {} +void append_keyboard_buffer(const uint8_t *buf, size_t len) {} +#else +#include "tusb.h" +#endif #include "mbedtls/aes.h" #include "management.h" -#ifndef ENABLE_EMULATION -#include "tusb.h" -#endif #define FIXED_SIZE 16 #define KEY_SIZE 16 @@ -116,12 +119,10 @@ uint16_t otp_status(bool is_otp); int otp_process_apdu(); int otp_unload(); -#ifndef ENABLE_EMULATION extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t); extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t); int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t); uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t); -#endif const uint8_t otp_aid[] = { 7, @@ -200,15 +201,12 @@ uint16_t calculate_crc(const uint8_t *data, size_t data_len) { return crc & 0xFFFF; } -#ifndef ENABLE_EMULATION static uint8_t session_counter[2] = { 0 }; -#endif int otp_button_pressed(uint8_t slot) { init_otp(); if (!cap_supported(CAP_OTP)) { return 3; } -#ifndef ENABLE_EMULATION file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); const uint8_t *data = file_get_data(ef); otp_config_t *otp_config = (otp_config_t *) data; @@ -317,19 +315,15 @@ int otp_button_pressed(uint8_t slot) { low_flash_available(); } } -#else - (void) slot; -#endif + return 0; } INITIALIZER( otp_ctor ) { register_app(otp_select, otp_aid); button_pressed_cb = otp_button_pressed; -#ifndef ENABLE_EMULATION hid_set_report_cb = otp_hid_set_report_cb; hid_get_report_cb = otp_hid_get_report_cb; -#endif } int otp_unload() { @@ -490,20 +484,20 @@ int cmd_otp() { return SW_WRONG_DATA(); } int ret = 0; -#ifndef ENABLE_EMULATION uint8_t *rdata_bk = apdu.rdata; if (otp_config->cfg_flags & CHAL_BTN_TRIG) { status_byte = 0x20; otp_status(_is_otp); +#ifndef ENABLE_EMULATION if (wait_button() == true) { status_byte = 0x00; otp_status(_is_otp); return SW_CONDITIONS_NOT_SATISFIED(); } +#endif status_byte = 0x10; apdu.rdata = rdata_bk; } -#endif if (p1 == 0x30 || p1 == 0x38) { if (!(otp_config->cfg_flags & CHAL_HMAC)) { return SW_WRONG_DATA(); @@ -568,8 +562,6 @@ int otp_process_apdu() { return SW_INS_NOT_SUPPORTED(); } -#ifndef ENABLE_EMULATION - uint8_t otp_frame_rx[70] = {0}; uint8_t otp_frame_tx[70] = {0}; uint8_t otp_exp_seq = 0, otp_curr_seq = 0; @@ -671,5 +663,3 @@ uint16_t otp_hid_get_report_cb(uint8_t itf, return reqlen; } - -#endif From 78de56f0a99f5a3e28c691585f3dc06c76d3ea5e Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 22 Sep 2025 23:36:05 +0200 Subject: [PATCH 32/45] Fix build for non-pico boards. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 202d32d..4edc506 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 202d32d13dc29e9bbb978de3f9ca95ac97cf5ca3 +Subproject commit 4edc5067593ea5596e27cd1d5ef8be6c6467df53 From 665f02959306b958465262343cfe6fcbf9fcf77f Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 22 Sep 2025 23:41:55 +0200 Subject: [PATCH 33/45] Fix build for non-pico boards. Signed-off-by: Pol Henarejos --- src/fido/cbor.c | 2 +- src/fido/cbor_client_pin.c | 2 +- src/fido/cbor_get_assertion.c | 2 +- src/fido/cbor_reset.c | 2 +- src/fido/credential.c | 2 +- src/fido/fido.c | 2 +- src/fido/fido.h | 2 +- src/fido/kek.c | 2 +- src/fido/management.h | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/fido/cbor.c b/src/fido/cbor.c index 48f9a1b..ab9faff 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -16,7 +16,7 @@ */ #include "pico_keys.h" -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "pico/stdlib.h" #endif #include "hid/ctap_hid.h" diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index 0464129..a06a536 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -28,7 +28,7 @@ #include "cbor.h" #include "ctap.h" #include "ctap2_cbor.h" -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "bsp/board.h" #endif #include "hid/ctap_hid.h" diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 7829378..4e3c288 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -18,7 +18,7 @@ #include "pico_keys.h" #include "cbor.h" #include "ctap.h" -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "bsp/board.h" #endif #include "hid/ctap_hid.h" diff --git a/src/fido/cbor_reset.c b/src/fido/cbor_reset.c index cc18e9c..0887684 100644 --- a/src/fido/cbor_reset.c +++ b/src/fido/cbor_reset.c @@ -19,7 +19,7 @@ #include "file.h" #include "fido.h" #include "ctap.h" -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "bsp/board.h" #endif #ifdef ESP_PLATFORM diff --git a/src/fido/credential.c b/src/fido/credential.c index 75b88a5..fa6b574 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -19,7 +19,7 @@ #include "mbedtls/chachapoly.h" #include "mbedtls/sha256.h" #include "credential.h" -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "bsp/board.h" #endif #include "hid/ctap_hid.h" diff --git a/src/fido/fido.c b/src/fido/fido.c index 742dd62..b14d6ab 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -28,7 +28,7 @@ #if defined(USB_ITF_CCID) #include "ccid/ccid.h" #endif -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "bsp/board.h" #endif #include diff --git a/src/fido/fido.h b/src/fido/fido.h index 8af6977..75e5928 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -18,7 +18,7 @@ #ifndef _FIDO_H_ #define _FIDO_H_ -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "pico/stdlib.h" #endif #ifndef ESP_PLATFORM diff --git a/src/fido/kek.c b/src/fido/kek.c index 030e05c..943217a 100644 --- a/src/fido/kek.c +++ b/src/fido/kek.c @@ -18,7 +18,7 @@ #include "pico_keys.h" #include "fido.h" #include "stdlib.h" -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "pico/stdlib.h" #endif #include "kek.h" diff --git a/src/fido/management.h b/src/fido/management.h index a8a6331..0e97f02 100644 --- a/src/fido/management.h +++ b/src/fido/management.h @@ -19,7 +19,7 @@ #define _MANAGEMENT_H_ #include -#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) +#if defined(PICO_PLATFORM) #include "pico/stdlib.h" #endif From 7d97b21ca40931c9fe4cf2e6f256e05e01bb7f71 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 23 Sep 2025 17:00:10 +0200 Subject: [PATCH 34/45] Update Pico Keys SDK. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 4edc506..70c0c1b 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 4edc5067593ea5596e27cd1d5ef8be6c6467df53 +Subproject commit 70c0c1bf81e430bdbd0f8700271970c1ba4a0159 From 1b8ee2fc87c0b7bd8ba325631a3c420cdd7af605 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 23 Sep 2025 17:03:53 +0200 Subject: [PATCH 35/45] Fix missing files. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 70c0c1b..809dc3d 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 70c0c1bf81e430bdbd0f8700271970c1ba4a0159 +Subproject commit 809dc3d16d9562987764aed6266731481576ae9c From eae22a97fbc38b02aea6fc4a59312b9dc85ccba4 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 23 Sep 2025 17:17:01 +0200 Subject: [PATCH 36/45] Fix conditional build. Signed-off-by: Pol Henarejos --- src/fido/fido.c | 4 +++- src/fido/management.c | 2 ++ src/fido/otp.c | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/fido/fido.c b/src/fido/fido.c index b14d6ab..b9a0ef8 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -459,12 +459,14 @@ void scan_all() { extern void init_otp(); void init_fido() { scan_all(); +#ifdef ENABLE_OTP_APP init_otp(); +#endif } bool wait_button_pressed() { uint32_t val = EV_PRESS_BUTTON; -#ifndef ENABLE_EMULATION +#if defined(PICO_PLATFORM) || defined(ESP_PLATFORM) queue_try_add(&card_to_usb_q, &val); do { queue_remove_blocking(&usb_to_card_q, &val); diff --git a/src/fido/management.c b/src/fido/management.c index 6f6360d..95b08f9 100644 --- a/src/fido/management.c +++ b/src/fido/management.c @@ -40,7 +40,9 @@ int man_select(app_t *a, uint8_t force) { apdu.ne = res_APDU_size; if (force) { scan_all(); +#ifdef ENABLE_OTP_APP init_otp(); +#endif } return PICOKEY_OK; } diff --git a/src/fido/otp.c b/src/fido/otp.c index a9adf33..f893b30 100644 --- a/src/fido/otp.c +++ b/src/fido/otp.c @@ -216,6 +216,7 @@ int otp_button_pressed(uint8_t slot) { if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) { return 2; } +#ifdef ENABLE_OATH_APP if (otp_config->tkt_flags & OATH_HOTP) { uint8_t tmp_key[KEY_SIZE + 2]; tmp_key[0] = 0x01; @@ -257,6 +258,7 @@ int otp_button_pressed(uint8_t slot) { append_keyboard_buffer((const uint8_t *) "\r", 1); } } +#endif else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) { uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE; if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET From c6dba5df4385bdd095caa7b1262e77df63d51290 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sat, 27 Sep 2025 23:52:08 +0200 Subject: [PATCH 37/45] Fix silent authentication with new resident key system. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 4e3c288..0714fe4 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -395,8 +395,24 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { if (strcmp(allowList[e].type.data, "public-key") != 0) { continue; } - if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) { - numberOfCredentials++; + if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) { + for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { + file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i)); + if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) { + continue; + } + if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) { + if (credential_verify(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, true) == 0) { + numberOfCredentials++; + } + break; + } + } + } + else { + if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) { + numberOfCredentials++; + } } } } From 3e9d1a4eb40bec724e6c744a71283132528a3741 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 28 Sep 2025 00:05:25 +0200 Subject: [PATCH 38/45] Fix silent authentication with resident keys. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 0714fe4..ce273b4 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -364,7 +364,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) { credential_free(&creds[i]); } - else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && resident == true && !(flags & FIDO2_AUT_FLAG_UV)) { + else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) { credential_free(&creds[i]); } else { From 6c85421eca094d83a655c8622255cceb88f38607 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 28 Sep 2025 20:28:04 +0200 Subject: [PATCH 39/45] Using new PIN format. Now, PIN uses OTP as a seed to avoid memory dumps, when available (RP2350 / ESP32). Related with #187. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- src/fido/cbor_client_pin.c | 71 ++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 809dc3d..47456dd 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 809dc3d16d9562987764aed6266731481576ae9c +Subproject commit 47456dda6b1d80c4cb086df32d691c3020956e57 diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index a06a536..703b156 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -420,12 +420,13 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (pin_len < minPin) { CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); } - uint8_t hsh[34], dhash[32]; + uint8_t hsh[35], dhash[32]; hsh[0] = MAX_PIN_RETRIES; hsh[1] = pin_len; + hsh[2] = 1; // New format indicator mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); - double_hash_pin(dhash, 16, hsh + 2); - file_put_data(ef_pin, hsh, 2 + 32); + double_hash_pin_otp(dhash, 16, hsh + 3); + file_put_data(ef_pin, hsh, sizeof(hsh)); low_flash_available(); ret = check_mkek_encrypted(dhash); @@ -475,10 +476,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) { mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); } - uint8_t pin_data[34]; - memcpy(pin_data, file_get_data(ef_pin), 34); + uint8_t pin_data[35]; + memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin)); pin_data[0] -= 1; - file_put_data(ef_pin, pin_data, sizeof(pin_data)); + file_put_data(ef_pin, pin_data, file_get_size(ef_pin)); low_flash_available(); uint8_t retries = pin_data[0]; uint8_t paddedNewPin[64]; @@ -487,9 +488,16 @@ int cbor_client_pin(const uint8_t *data, size_t len) { mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); } - uint8_t dhash[32]; - double_hash_pin(paddedNewPin, 16, dhash); - if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) { + uint8_t dhash[32], off = 3; + if (file_get_size(ef_pin) == 34) { + off = 2; + double_hash_pin(paddedNewPin, 16, dhash); + } + else { + double_hash_pin_otp(paddedNewPin, 16, dhash); + } + + if (memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { regenerate(); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); if (retries == 0) { @@ -503,6 +511,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PIN_INVALID); } } + if (off == 2) { + // Upgrade pin file to new format + pin_data[2] = 1; // New format indicator + double_hash_pin_otp(paddedNewPin, 16, pin_data + 3); + } hash_multi(paddedNewPin, 16, session_pin); pin_data[0] = MAX_PIN_RETRIES; file_put_data(ef_pin, pin_data, sizeof(pin_data)); @@ -528,13 +541,14 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (pin_len < minPin) { CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); } - uint8_t hsh[34]; - hsh[0] = MAX_PIN_RETRIES; - hsh[1] = pin_len; + pin_data[0] = MAX_PIN_RETRIES; + pin_data[1] = pin_len; + pin_data[2] = 1; // New format indicator mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); - double_hash_pin(dhash, 16, hsh + 2); + double_hash_pin_otp(dhash, 16, pin_data + 3); + if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && - memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) { + memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) { CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); } @@ -543,7 +557,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (ret != PICOKEY_OK) { CBOR_ERROR(ret); } - file_put_data(ef_pin, hsh, 2 + 32); + file_put_data(ef_pin, pin_data, sizeof(pin_data)); ret = check_mkek_encrypted(dhash); if (ret != PICOKEY_OK) { @@ -556,7 +570,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (ret != PICOKEY_OK) { CBOR_ERROR(ret); } - mbedtls_platform_zeroize(hsh, sizeof(hsh)); + mbedtls_platform_zeroize(pin_data, sizeof(pin_data)); mbedtls_platform_zeroize(dhash, sizeof(dhash)); if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) { uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin)); @@ -610,10 +624,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) { mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } - uint8_t pin_data[34]; - memcpy(pin_data, file_get_data(ef_pin), 34); + uint8_t pin_data[35]; + memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin)); pin_data[0] -= 1; - file_put_data(ef_pin, pin_data, sizeof(pin_data)); + file_put_data(ef_pin, pin_data, file_get_size(ef_pin)); low_flash_available(); uint8_t retries = pin_data[0]; uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE; @@ -622,9 +636,15 @@ int cbor_client_pin(const uint8_t *data, size_t len) { mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); } - uint8_t dhash[32]; - double_hash_pin(paddedNewPin, 16, dhash); - if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) { + uint8_t dhash[32], off = 3; + if (file_get_size(ef_pin) == 34) { + off = 2; + double_hash_pin(paddedNewPin, 16, dhash); + } + else { + double_hash_pin_otp(paddedNewPin, 16, dhash); + } + if (memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { regenerate(); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); if (retries == 0) { @@ -647,6 +667,13 @@ int cbor_client_pin(const uint8_t *data, size_t len) { hash_multi(paddedNewPin, 16, session_pin); pin_data[0] = MAX_PIN_RETRIES; new_pin_mismatches = 0; + + if (off == 2) { + // Upgrade pin file to new format + pin_data[2] = 1; // New format indicator + double_hash_pin_otp(paddedNewPin, 16, pin_data + 3); + } + file_put_data(ef_pin, pin_data, sizeof(pin_data)); mbedtls_platform_zeroize(pin_data, sizeof(pin_data)); mbedtls_platform_zeroize(dhash, sizeof(dhash)); From 85423fed859ade81df08d14bc91226190dd4ecd9 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 28 Sep 2025 20:29:06 +0200 Subject: [PATCH 40/45] Using new PIN format. Now, PIN uses OTP as a seed to avoid memory dumps, when available (RP2350 / ESP32). Related with #187. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 47456dd..5048e07 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 47456dda6b1d80c4cb086df32d691c3020956e57 +Subproject commit 5048e07f81b41de387e2b65d8bb3b8e2b6d53962 From de1bf3d2d44d16779ed65f0c6721c15c6eca22b9 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 6 Oct 2025 14:22:23 +0200 Subject: [PATCH 41/45] Add OTP security enhancements. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index 5048e07..b3b2b67 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 5048e07f81b41de387e2b65d8bb3b8e2b6d53962 +Subproject commit b3b2b67034334dfbd1030ed8dc6ac4d463faacd7 From d424f0dea7931393c63a30452c3c01b82b4f9c69 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 7 Oct 2025 21:11:50 +0200 Subject: [PATCH 42/45] Add sanity check. Signed-off-by: Pol Henarejos --- src/fido/credential.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fido/credential.c b/src/fido/credential.c index fa6b574..5c7bf53 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -442,6 +442,9 @@ int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8 } bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) { + if (cred_id_len < 4 + CRED_PROTO_RESIDENT_LEN) { + return false; + } return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0; } From 51c13b0f0b87cc48b6a14f30cdc7b023bf988a23 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 7 Oct 2025 23:41:58 +0200 Subject: [PATCH 43/45] Add memory leak checker. Signed-off-by: Pol Henarejos --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index be0feb2..93402f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,6 +151,12 @@ if(ENABLE_EMULATION) target_link_options(pico_fido PUBLIC -Wl,-dead_strip ) + if(DEBUG_APDU) + target_compile_options(pico_fido PUBLIC + -fsanitize=address -g -O1 -fno-omit-frame-pointer) + target_link_options(pico_fido PUBLIC + -fsanitize=address -g -O1 -fno-omit-frame-pointer) + endif() else() target_link_options(pico_fido PUBLIC -Wl,--gc-sections From 898c88dc6d9852667afc7a2f1837ed6ad1c43050 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 8 Oct 2025 00:33:23 +0200 Subject: [PATCH 44/45] Migration to the new system of secure functions to derive keys based on OTP, if available, and pico_serial as a fallback. PIN is also an input vector, which defines a separated domain. PIN is used to derive encryption key, derive session key and derive verifier. From session key is derived encryption key. As a consequence, MKEK functionalities are not necessary anymore, since key device is handled by this new set directly. Some MKEK functions are left for compatibility purposes and for the silent migration to new format. It also applies for double_hash_pin and hash_multi, which are deprecated. Signed-off-by: Pol Henarejos --- pico-keys-sdk | 2 +- src/fido/cbor_client_pin.c | 135 ++++++++++++++++++++----------------- src/fido/fido.c | 96 +++++++++++++++----------- src/fido/fido.h | 2 +- src/fido/kek.c | 40 ----------- 5 files changed, 134 insertions(+), 141 deletions(-) diff --git a/pico-keys-sdk b/pico-keys-sdk index b3b2b67..c165ae4 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit b3b2b67034334dfbd1030ed8dc6ac4d463faacd7 +Subproject commit c165ae4838bd2edcbac260cca3247979d9910edc diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index 703b156..78579a8 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -44,6 +44,7 @@ uint32_t max_usage_time_period = 600 * 1000; bool needs_power_cycle = false; static mbedtls_ecdh_context hkey; static bool hkey_init = false; +extern int encrypt_keydev_f1(const uint8_t keydev[32]); int beginUsingPinUvAuthToken(bool userIsPresent) { paut.user_present = userIsPresent; @@ -199,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in return -1; } -int authenticate(uint8_t protocol, - const uint8_t *key, - const uint8_t *data, - size_t len, - uint8_t *sign) { +int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) { uint8_t hmac[32]; int ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac); @@ -231,10 +228,10 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l return ret; } if (protocol == 1) { - return memcmp(sign, hmac, 16); + return ct_memcmp(sign, hmac, 16); } else if (protocol == 2) { - return memcmp(sign, hmac, 32); + return ct_memcmp(sign, hmac, 32); } return -1; } @@ -269,17 +266,15 @@ int pinUvAuthTokenUsageTimerObserver() { return 0; } -int check_mkek_encrypted(const uint8_t *dhash) { - if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) { - hash_multi(dhash, 16, session_pin); // Only for storing MKEK - uint8_t mkek[MKEK_SIZE] = {0}; - memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE); - int ret = store_mkek(mkek); - mbedtls_platform_zeroize(mkek, sizeof(mkek)); - mbedtls_platform_zeroize(session_pin, sizeof(session_pin)); - if (ret != PICOKEY_OK) { - return CTAP2_ERR_PIN_AUTH_INVALID; - } +int check_keydev_encrypted(const uint8_t pin_token[32]) { + if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) { + uint8_t tmp_keydev[61]; + tmp_keydev[0] = 0x02; // Change format to encrypted + encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1); + DEBUG_DATA(tmp_keydev, sizeof(tmp_keydev)); + file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev)); + mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev)); + low_flash_available(); } return PICOKEY_OK; } @@ -294,11 +289,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CborEncoder encoder, mapEncoder; CborValue map; CborError error = CborNoError; - CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, - kay = { 0 }; + CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 }; CborCharString rpId = { 0 }; CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map)); uint64_t val_c = 1; + uint8_t keydev[32] = {0}; if (hkey_init == false) { initialize(); } @@ -425,11 +420,12 @@ int cbor_client_pin(const uint8_t *data, size_t len) { hsh[1] = pin_len; hsh[2] = 1; // New format indicator mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); - double_hash_pin_otp(dhash, 16, hsh + 3); + pin_derive_verifier(dhash, 16, hsh + 3); file_put_data(ef_pin, hsh, sizeof(hsh)); low_flash_available(); - ret = check_mkek_encrypted(dhash); + pin_derive_session(dhash, 16, session_pin); + ret = check_keydev_encrypted(session_pin); if (ret != PICOKEY_OK) { CBOR_ERROR(ret); } @@ -494,10 +490,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) { double_hash_pin(paddedNewPin, 16, dhash); } else { - double_hash_pin_otp(paddedNewPin, 16, dhash); + pin_derive_verifier(paddedNewPin, 16, dhash); } - if (memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { + if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { regenerate(); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); if (retries == 0) { @@ -514,12 +510,25 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (off == 2) { // Upgrade pin file to new format pin_data[2] = 1; // New format indicator - double_hash_pin_otp(paddedNewPin, 16, pin_data + 3); + pin_derive_verifier(paddedNewPin, 16, pin_data + 3); + + hash_multi(paddedNewPin, 16, session_pin); + ret = load_keydev(keydev); + if (ret != PICOKEY_OK) { + CBOR_ERROR(CTAP2_ERR_PIN_INVALID); + } + encrypt_keydev_f1(keydev); } - hash_multi(paddedNewPin, 16, session_pin); + pin_derive_session(paddedNewPin, 16, session_pin); pin_data[0] = MAX_PIN_RETRIES; file_put_data(ef_pin, pin_data, sizeof(pin_data)); low_flash_available(); + + ret = check_keydev_encrypted(session_pin); + if (ret != PICOKEY_OK) { + CBOR_ERROR(ret); + } + new_pin_mismatches = 0; ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); @@ -541,35 +550,32 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (pin_len < minPin) { CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); } + + // New PIN is valid and verified + ret = load_keydev(keydev); + if (ret != PICOKEY_OK) { + CBOR_ERROR(CTAP2_ERR_PIN_INVALID); + } + encrypt_keydev_f1(keydev); + + mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); + pin_derive_session(dhash, 16, session_pin); + ret = check_keydev_encrypted(session_pin); + if (ret != PICOKEY_OK) { + CBOR_ERROR(ret); + } + low_flash_available(); + pin_data[0] = MAX_PIN_RETRIES; pin_data[1] = pin_len; pin_data[2] = 1; // New format indicator - mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); - double_hash_pin_otp(dhash, 16, pin_data + 3); + pin_derive_verifier(dhash, 16, pin_data + 3); - if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && - memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) { + if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) { CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); } - - uint8_t mkek[MKEK_SIZE] = {0}; - ret = load_mkek(mkek); - if (ret != PICOKEY_OK) { - CBOR_ERROR(ret); - } file_put_data(ef_pin, pin_data, sizeof(pin_data)); - ret = check_mkek_encrypted(dhash); - if (ret != PICOKEY_OK) { - CBOR_ERROR(ret); - } - - hash_multi(dhash, 16, session_pin); - ret = store_mkek(mkek); - mbedtls_platform_zeroize(mkek, sizeof(mkek)); - if (ret != PICOKEY_OK) { - CBOR_ERROR(ret); - } mbedtls_platform_zeroize(pin_data, sizeof(pin_data)); mbedtls_platform_zeroize(dhash, sizeof(dhash)); if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) { @@ -642,11 +648,12 @@ int cbor_client_pin(const uint8_t *data, size_t len) { double_hash_pin(paddedNewPin, 16, dhash); } else { - double_hash_pin_otp(paddedNewPin, 16, dhash); + pin_derive_verifier(paddedNewPin, 16, dhash); } - if (memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { + if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { regenerate(); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); + mbedtls_platform_zeroize(dhash, sizeof(dhash)); if (retries == 0) { CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED); } @@ -658,25 +665,31 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PIN_INVALID); } } - - ret = check_mkek_encrypted(paddedNewPin); - if (ret != PICOKEY_OK) { - CBOR_ERROR(ret); - } - - hash_multi(paddedNewPin, 16, session_pin); - pin_data[0] = MAX_PIN_RETRIES; - new_pin_mismatches = 0; + mbedtls_platform_zeroize(dhash, sizeof(dhash)); if (off == 2) { // Upgrade pin file to new format pin_data[2] = 1; // New format indicator - double_hash_pin_otp(paddedNewPin, 16, pin_data + 3); + pin_derive_verifier(paddedNewPin, 16, pin_data + 3); + hash_multi(paddedNewPin, 16, session_pin); + ret = load_keydev(keydev); + if (ret != PICOKEY_OK) { + CBOR_ERROR(CTAP2_ERR_PIN_INVALID); + } + encrypt_keydev_f1(keydev); } + pin_derive_session(paddedNewPin, 16, session_pin); + ret = check_keydev_encrypted(session_pin); + if (ret != PICOKEY_OK) { + CBOR_ERROR(ret); + } + + pin_data[0] = MAX_PIN_RETRIES; + new_pin_mismatches = 0; + file_put_data(ef_pin, pin_data, sizeof(pin_data)); mbedtls_platform_zeroize(pin_data, sizeof(pin_data)); - mbedtls_platform_zeroize(dhash, sizeof(dhash)); low_flash_available(); file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF); @@ -684,7 +697,6 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PIN_INVALID); } uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL; - ; if (permissions & CTAP_PERMISSION_PCMR) { ppaut.permissions = CTAP_PERMISSION_PCMR; pdata = ppaut.data; @@ -722,6 +734,7 @@ err: CBOR_FREE_BYTE_STRING(kax); CBOR_FREE_BYTE_STRING(kay); CBOR_FREE_BYTE_STRING(rpId); + mbedtls_platform_zeroize(keydev, sizeof(keydev)); if (error != CborNoError) { if (error == CborErrorImproperValue) { return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; diff --git a/src/fido/fido.c b/src/fido/fido.c index b9a0ef8..b05e1fb 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -204,7 +204,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe return ret; } -int load_keydev(uint8_t *key) { +int load_keydev(uint8_t key[32]) { if (has_keydev_dec == false && !file_has_data(ef_keydev)) { return PICOKEY_ERR_MEMORY_FATAL; } @@ -213,13 +213,39 @@ int load_keydev(uint8_t *key) { memcpy(key, keydev_dec, sizeof(keydev_dec)); } else { - memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev)); - - if (mkek_decrypt(key, 32) != PICOKEY_OK) { - return PICOKEY_EXEC_ERROR; + uint16_t fid_size = file_get_size(ef_keydev); + if (fid_size == 32) { + memcpy(key, file_get_data(ef_keydev), 32); + if (mkek_decrypt(key, 32) != PICOKEY_OK) { + return PICOKEY_EXEC_ERROR; + } + if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) { + return PICOKEY_EXEC_ERROR; + } } - if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) { - return PICOKEY_EXEC_ERROR; + else if (fid_size == 33 || fid_size == 61) { + uint8_t format = *file_get_data(ef_keydev); + if (format == 0x01 || format == 0x02) { // Format indicator + if (format == 0x02) { + uint8_t tmp_key[61]; + memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key)); + int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, key); + if (ret != PICOKEY_OK) { + return PICOKEY_EXEC_ERROR; + } + } + else { + memcpy(key, file_get_data(ef_keydev) + 1, 32); + } + uint8_t kbase[32]; + derive_kbase(kbase); + int ret = aes_decrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32); + if (ret != PICOKEY_OK) { + mbedtls_platform_zeroize(kbase, sizeof(kbase)); + return PICOKEY_EXEC_ERROR; + } + mbedtls_platform_zeroize(kbase, sizeof(kbase)); + } } } @@ -315,6 +341,23 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur return r; } +int encrypt_keydev_f1(const uint8_t keydev[32]) { + uint8_t kdata[33] = {0}; + kdata[0] = 0x01; // Format indicator + memcpy(kdata + 1, keydev, 32); + uint8_t kbase[32]; + derive_kbase(kbase); + int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata + 1, 32); + mbedtls_platform_zeroize(kbase, sizeof(kbase)); + if (ret != PICOKEY_OK) { + return ret; + } + ret = file_put_data(ef_keydev, kdata, 33); + mbedtls_platform_zeroize(kdata, sizeof(kdata)); + low_flash_available(); + return ret; +} + int scan_files_fido() { ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF); ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF); @@ -330,17 +373,16 @@ int scan_files_fido() { mbedtls_ecdsa_free(&ecdsa); return ret; } - uint8_t kdata[64]; + uint8_t keydev[32] = {0}; size_t key_size = 0; - ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata)); - if (ret != PICOKEY_OK) { - return ret; + ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev)); + if (ret != 0 || key_size != 32) { + mbedtls_platform_zeroize(keydev, sizeof(keydev)); + mbedtls_ecdsa_free(&ecdsa); + return ret != 0 ? ret : PICOKEY_EXEC_ERROR; } - if (otp_key_1) { - ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32); - } - ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size); - mbedtls_platform_zeroize(kdata, sizeof(kdata)); + encrypt_keydev_f1(keydev); + mbedtls_platform_zeroize(keydev, sizeof(keydev)); mbedtls_ecdsa_free(&ecdsa); if (ret != PICOKEY_OK) { return ret; @@ -351,21 +393,6 @@ int scan_files_fido() { else { printf("FATAL ERROR: KEY DEV not found in memory!\r\n"); } - if (ef_mkek) { // No encrypted MKEK - if (!file_has_data(ef_mkek)) { - uint8_t mkek[MKEK_IV_SIZE + MKEK_KEY_SIZE]; - random_gen(NULL, mkek, sizeof(mkek)); - file_put_data(ef_mkek, mkek, sizeof(mkek)); - int ret = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), file_get_data(ef_keydev), 32); - mbedtls_platform_zeroize(mkek, sizeof(mkek)); - if (ret != 0) { - printf("FATAL ERROR: MKEK encryption failed!\r\n"); - } - } - } - else { - printf("FATAL ERROR: MKEK not found in memory!\r\n"); - } ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF); if (ef_certdev) { if (!file_has_data(ef_certdev)) { @@ -409,13 +436,6 @@ int scan_files_fido() { printf("FATAL ERROR: Global counter not found in memory!\r\n"); } ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF); - if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage - uint8_t pin_data[34] = { 0 }, dhash[32]; - memcpy(pin_data, file_get_data(ef_pin), 18); - double_hash_pin(pin_data + 2, 16, dhash); - memcpy(pin_data + 2, dhash, 32); - file_put_data(ef_pin, pin_data, 34); - } ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF); if (ef_authtoken) { if (!file_has_data(ef_authtoken)) { diff --git a/src/fido/fido.h b/src/fido/fido.h index 75e5928..bc42f8e 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -51,7 +51,7 @@ extern void init_fido(); extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve); extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id); extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key); -extern int load_keydev(uint8_t *key); +extern int load_keydev(uint8_t key[32]); extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out); extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out); extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret); diff --git a/src/fido/kek.c b/src/fido/kek.c index 943217a..325dcca 100644 --- a/src/fido/kek.c +++ b/src/fido/kek.c @@ -85,46 +85,6 @@ void release_mkek(uint8_t *mkek) { mbedtls_platform_zeroize(mkek, MKEK_SIZE); } -int store_mkek(const uint8_t *mkek) { - uint8_t tmp_mkek[MKEK_SIZE]; - if (mkek == NULL) { - const uint8_t *rd = random_bytes_get(MKEK_IV_SIZE + MKEK_KEY_SIZE); - memcpy(tmp_mkek, rd, MKEK_IV_SIZE + MKEK_KEY_SIZE); - } - else { - memcpy(tmp_mkek, mkek, MKEK_SIZE); - } - if (otp_key_1) { - mkek_masked(tmp_mkek, otp_key_1); - } - *(uint32_t *) MKEK_CHECKSUM(tmp_mkek) = crc32c(MKEK_KEY(tmp_mkek), MKEK_KEY_SIZE); - uint8_t tmp_mkek_pin[MKEK_SIZE]; - memcpy(tmp_mkek_pin, tmp_mkek, MKEK_SIZE); - file_t *tf = search_file(EF_MKEK); - if (!tf) { - release_mkek(tmp_mkek); - release_mkek(tmp_mkek_pin); - return PICOKEY_ERR_FILE_NOT_FOUND; - } - aes_encrypt_cfb_256(session_pin, MKEK_IV(tmp_mkek_pin), MKEK_KEY(tmp_mkek_pin), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE); - file_put_data(tf, tmp_mkek_pin, MKEK_SIZE); - release_mkek(tmp_mkek_pin); - low_flash_available(); - release_mkek(tmp_mkek); - return PICOKEY_OK; -} - -int mkek_encrypt(uint8_t *data, uint16_t len) { - int r; - uint8_t mkek[MKEK_SIZE + 4]; - if ((r = load_mkek(mkek)) != PICOKEY_OK) { - return r; - } - r = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len); - release_mkek(mkek); - return r; -} - int mkek_decrypt(uint8_t *data, uint16_t len) { int r; uint8_t mkek[MKEK_SIZE + 4]; From 6b939380406ee8b5ba65ddffaa0a1a543905f046 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 12 Oct 2025 18:56:14 +0200 Subject: [PATCH 45/45] Fix warnings. Signed-off-by: Pol Henarejos --- src/fido/cbor.c | 10 ++++++---- src/fido/cbor_config.c | 2 +- src/fido/cbor_cred_mgmt.c | 2 +- src/fido/cbor_large_blobs.c | 2 +- src/fido/cbor_make_credential.c | 4 ++-- src/fido/cbor_vendor.c | 4 ++-- src/fido/credential.c | 4 ++-- src/fido/credential.h | 2 +- src/fido/fido.c | 6 +++--- src/fido/management.c | 2 +- 10 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/fido/cbor.c b/src/fido/cbor.c index ab9faff..57319b7 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -104,7 +104,8 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) { return CTAP1_ERR_INVALID_CMD; } -void cbor_thread(void) { +void *cbor_thread(void *arg) { + (void)arg; card_init_core1(); while (1) { uint32_t m; @@ -115,17 +116,17 @@ void cbor_thread(void) { if (m == EV_EXIT) { break; } - apdu.sw = cbor_parse(cbor_cmd, cbor_data, cbor_len); + apdu.sw = (uint16_t)cbor_parse(cbor_cmd, cbor_data, cbor_len); if (apdu.sw == 0) { DEBUG_DATA(res_APDU, res_APDU_size); } else { if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) { - res_APDU[-1] = apdu.sw; + res_APDU[-1] = (uint8_t)apdu.sw; apdu.sw = 0; } else { - res_APDU[0] = apdu.sw; + res_APDU[0] = (uint8_t)apdu.sw; } } @@ -137,6 +138,7 @@ void cbor_thread(void) { #ifdef ESP_PLATFORM vTaskDelete(NULL); #endif + return NULL; } int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) { diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 3da353d..7816923 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -236,7 +236,7 @@ int cbor_config(const uint8_t *data, size_t len) { // val[0] = (uint8_t)(vendorParamInt >> 8); // val[1] = (uint8_t)(vendorParamInt & 0xFF); memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len); - file_put_data(ef_pin_policy, val, 2 + vendorParamByteString.len); + file_put_data(ef_pin_policy, val, 2 + (uint16_t)vendorParamByteString.len); free(val); } } diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 3f90a37..8080fca 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -427,7 +427,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } uint8_t newcred[MAX_CRED_ID_LENGTH]; - size_t newcred_len = 0; + uint16_t newcred_len = 0; if (credential_create(&cred.rpId, &cred.userId, &user.parent.name, &user.displayName, &cred.opts, &cred.extensions, cred.use_sign_count, (int)cred.alg, diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c index d1078c4..688bdb0 100644 --- a/src/fido/cbor_large_blobs.c +++ b/src/fido/cbor_large_blobs.c @@ -129,7 +129,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) { uint8_t verify_data[70] = { 0 }; memset(verify_data, 0xff, 32); verify_data[32] = 0x0C; - put_uint32_t_le(offset, verify_data + 34); + put_uint32_t_le((uint32_t)offset, verify_data + 34); mbedtls_sha256(set.data, set.len, verify_data + 38, 0); if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) { CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 359bf91..aef2056 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -402,7 +402,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash); uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0}; - size_t cred_id_len = 0; + uint16_t cred_id_len = 0; CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len)); @@ -619,7 +619,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { #ifndef ENABLE_EMULATION uint8_t *p = (uint8_t *)user.parent.name.data + 5; if (memcmp(p, "CommissionProfile", 17) == 0) { - ret = phy_unserialize_data(user.id.data, user.id.len, &phy_data); + ret = phy_unserialize_data(user.id.data, (uint16_t)user.id.len, &phy_data); if (ret == PICOKEY_OK) { ret = phy_save(); } diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c index 39e00bf..1f3bfc7 100644 --- a/src/fido/cbor_vendor.c +++ b/src/fido/cbor_vendor.c @@ -243,8 +243,8 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { if (vendorCmd == 0x01) { uint16_t opts = 0; if (file_has_data(ef_phy)) { - uint8_t *data = file_get_data(ef_phy); - opts = get_uint16_t_be(data + PHY_OPTS); + uint8_t *pdata = file_get_data(ef_phy); + opts = get_uint16_t_be(pdata + PHY_OPTS); } CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); diff --git a/src/fido/credential.c b/src/fido/credential.c index 5c7bf53..64f93bd 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -93,7 +93,7 @@ int credential_create(CborCharString *rpId, int alg, int curve, uint8_t *cred_id, - size_t *cred_id_len) { + uint16_t *cred_id_len) { CborEncoder encoder, mapEncoder, mapEncoder2; CborError error = CborNoError; uint8_t rp_id_hash[32]; @@ -150,7 +150,7 @@ int credential_create(CborCharString *rpId, } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id); - *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN; + *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN; uint8_t key[32] = {0}; credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO); uint8_t iv[CRED_IV_LEN] = {0}; diff --git a/src/fido/credential.h b/src/fido/credential.h index 8e140e4..730459f 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -90,7 +90,7 @@ extern int credential_create(CborCharString *rpId, int alg, int curve, uint8_t *cred_id, - size_t *cred_id_len); + uint16_t *cred_id_len); extern void credential_free(Credential *cred); extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash); extern int credential_load(const uint8_t *cred_id, diff --git a/src/fido/fido.c b/src/fido/fido.c index b05e1fb..e292fc9 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -168,7 +168,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) { uint8_t key_path[KEY_PATH_LEN]; memcpy(key_path, cred_id, KEY_PATH_LEN); *(uint32_t *) key_path = 0x80000000 | 10022; - for (int i = 1; i < KEY_PATH_ENTRIES; i++) { + for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) { *(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000; } return derive_key(NULL, false, key_path, mbedtls_curve, key); @@ -253,7 +253,7 @@ int load_keydev(uint8_t key[32]) { } int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) { - for (int i = 0; i < KEY_PATH_ENTRIES; i++) { + for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) { uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)]; if (!(k & 0x80000000)) { return -1; @@ -294,7 +294,7 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur return r; } const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - for (int i = 0; i < KEY_PATH_ENTRIES; i++) { + for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) { if (new_key == true) { uint32_t val = 0; random_gen(NULL, (uint8_t *) &val, sizeof(val)); diff --git a/src/fido/management.c b/src/fido/management.c index 95b08f9..759a7ca 100644 --- a/src/fido/management.c +++ b/src/fido/management.c @@ -116,7 +116,7 @@ int man_get_config() { if (!file_has_data(ef)) { res_APDU[res_APDU_size++] = TAG_USB_ENABLED; res_APDU[res_APDU_size++] = 2; - uint16_t caps = 0; + caps = 0; if (cap_supported(CAP_FIDO2)) { caps |= CAP_FIDO2; }