From 669f6041bd616f6ce9fad930620fc528b68e0a93 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:34:05 +0200 Subject: [PATCH 01/41] 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() -- 2.34.1 From 5facbf61cdc922071c66c9ffe189867169c950c4 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:34:34 +0200 Subject: [PATCH 02/41] 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 } }; -- 2.34.1 From 81e03cefda8d5e695bf60a1e11cc2ff6103484f8 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:39:41 +0200 Subject: [PATCH 03/41] 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 -- 2.34.1 From bf1072781b5d80a92a9bbf21b96f1b6ffc0aedd7 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 25 Aug 2025 01:42:24 +0200 Subject: [PATCH 04/41] 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 -- 2.34.1 From 2b640a5c36bdb1c612947b03113b861e38552b90 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 27 Aug 2025 12:51:34 +0200 Subject: [PATCH 05/41] 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)); -- 2.34.1 From 73a785686666777cd286db985f20af9281b775bb Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 28 Aug 2025 00:17:57 +0200 Subject: [PATCH 06/41] 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 -- 2.34.1 From 292a9e8d8a3ef48e06d0943dfd6d15c6d55eac00 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 28 Aug 2025 01:04:09 +0200 Subject: [PATCH 07/41] 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); } -- 2.34.1 From 66ecd6a7fc74be20e432c9c341ae122a09c44f54 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:17:40 +0200 Subject: [PATCH 08/41] 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 -- 2.34.1 From f7ba3eec38afd76c8deac9ba7e67bd92c005c4d9 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:19:54 +0200 Subject: [PATCH 09/41] 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(); -- 2.34.1 From d30ebde4f0b8660700a5a754cbc8335ceaad14b2 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:20:12 +0200 Subject: [PATCH 10/41] 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 -- 2.34.1 From fdf97f54692de568fd547e896400024e1165f8ad Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:20:31 +0200 Subject: [PATCH 11/41] 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 -- 2.34.1 From a5fd31a5d6cf36578271061e68a1d85123c9ad1a Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 29 Aug 2025 01:32:22 +0200 Subject: [PATCH 12/41] 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 -- 2.34.1 From 44c5ad4adbce485cc6358ce1fb250a2e6170c653 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 20:38:07 +0200 Subject: [PATCH 13/41] 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) { -- 2.34.1 From 35a043f2615da87eb8390adbe277c94e5a490352 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 20:41:23 +0200 Subject: [PATCH 14/41] 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; -- 2.34.1 From 3fe3a9d2ec025652272334663b8211007b02d47f Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 20:50:44 +0200 Subject: [PATCH 15/41] 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) { -- 2.34.1 From 351242d377ad9bf1838e62cbfca0485c1069a1ee Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 21:27:53 +0200 Subject: [PATCH 16/41] 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); -- 2.34.1 From d1c61536e01f3538eb21a503c5f2edbc83210070 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 21:28:09 +0200 Subject: [PATCH 17/41] 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 -- 2.34.1 From 6836ffaf02286194b3a831c422d9a8e1502ebb37 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 1 Sep 2025 22:02:13 +0200 Subject: [PATCH 18/41] 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 -- 2.34.1 From 2919b37e9cb1722ff186b7d1f5c69050b4bf5b69 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 2 Sep 2025 01:20:15 +0200 Subject: [PATCH 19/41] 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 -- 2.34.1 From 48cc417546daa7d305ca2c70dece8eec8912dc37 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 2 Sep 2025 15:49:39 +0200 Subject: [PATCH 20/41] 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 -- 2.34.1 From 1ac628d2419f2f0935e5e16be5d3d7567413fb78 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 4 Sep 2025 21:57:53 +0200 Subject: [PATCH 21/41] 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) -- 2.34.1 From 56d5c61044358c33bdd596f54923f31cc3861a02 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sat, 6 Sep 2025 19:14:27 +0200 Subject: [PATCH 22/41] 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_ -- 2.34.1 From 54fb02995f51395b6d03d038512cfbe326830c11 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 11:31:45 +0200 Subject: [PATCH 23/41] 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 -- 2.34.1 From 6b636d0bf4f6e28d9e52d3ed033b398c5cff7763 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:13:44 +0200 Subject: [PATCH 24/41] 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 -- 2.34.1 From bf484d8663fed8226153c046d9d63124b8c1fe36 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:16:14 +0200 Subject: [PATCH 25/41] 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 -- 2.34.1 From b3b3a5eecc60f2e1bfc8e82c40b2d35190eabad9 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:23:45 +0200 Subject: [PATCH 26/41] 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) -- 2.34.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/41] 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}%)') -- 2.34.1 From e4f8caa1ba2b1830715075afcfb56c0155ff9771 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 18:20:36 +0200 Subject: [PATCH 28/41] 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)); -- 2.34.1 From 9b254a07386d37f302769c1c8089dd6337575a53 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 19:20:20 +0200 Subject: [PATCH 29/41] 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() -- 2.34.1 From 56b6b4a8b90e0f4f969d3094b54c94aab2c0627a Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 21 Sep 2025 01:23:02 +0200 Subject: [PATCH 30/41] 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): -- 2.34.1 From b25e4bed6c8234de08c7cfe2fead170c6e927e04 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 22 Sep 2025 23:35:55 +0200 Subject: [PATCH 31/41] 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 -- 2.34.1 From 78de56f0a99f5a3e28c691585f3dc06c76d3ea5e Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 22 Sep 2025 23:36:05 +0200 Subject: [PATCH 32/41] 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 -- 2.34.1 From 665f02959306b958465262343cfe6fcbf9fcf77f Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 22 Sep 2025 23:41:55 +0200 Subject: [PATCH 33/41] 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 -- 2.34.1 From 7d97b21ca40931c9fe4cf2e6f256e05e01bb7f71 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 23 Sep 2025 17:00:10 +0200 Subject: [PATCH 34/41] 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 -- 2.34.1 From 1b8ee2fc87c0b7bd8ba325631a3c420cdd7af605 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 23 Sep 2025 17:03:53 +0200 Subject: [PATCH 35/41] 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 -- 2.34.1 From eae22a97fbc38b02aea6fc4a59312b9dc85ccba4 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 23 Sep 2025 17:17:01 +0200 Subject: [PATCH 36/41] 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 -- 2.34.1 From 105cf618666d6927ce77fde54a4b7dd041b8d632 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Sat, 27 Sep 2025 10:42:13 +0200 Subject: [PATCH 37/41] Bump autobuild to esp-idf 5.5.1 and add pico parallel build --- workflows/autobuild.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflows/autobuild.sh b/workflows/autobuild.sh index 57e8967..f925b60 100755 --- a/workflows/autobuild.sh +++ b/workflows/autobuild.sh @@ -22,13 +22,13 @@ cd ../.. mkdir build_pico cd build_pico cmake -DPICO_SDK_PATH=../pico-sdk .. -make +make -j`nproc` cd .. elif [[ $1 == "esp32" ]]; then sudo apt install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 git clone --recursive https://github.com/espressif/esp-idf.git cd esp-idf -git checkout tags/v5.5 +git checkout tags/v5.5.1 ./install.sh esp32s3 . ./export.sh cd .. -- 2.34.1 From c6dba5df4385bdd095caa7b1262e77df63d51290 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sat, 27 Sep 2025 23:52:08 +0200 Subject: [PATCH 38/41] 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++; + } } } } -- 2.34.1 From 3e9d1a4eb40bec724e6c744a71283132528a3741 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 28 Sep 2025 00:05:25 +0200 Subject: [PATCH 39/41] 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 { -- 2.34.1 From 6c85421eca094d83a655c8622255cceb88f38607 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 28 Sep 2025 20:28:04 +0200 Subject: [PATCH 40/41] 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)); -- 2.34.1 From 85423fed859ade81df08d14bc91226190dd4ecd9 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 28 Sep 2025 20:29:06 +0200 Subject: [PATCH 41/41] 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 -- 2.34.1