diff --git a/CMakeLists.txt b/CMakeLists.txt index 52ca50b..93402f2 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() @@ -156,6 +151,12 @@ if(ENABLE_EMULATION) target_link_options(pico_fido PUBLIC -Wl,-dead_strip ) + if(DEBUG_APDU) + target_compile_options(pico_fido PUBLIC + -fsanitize=address -g -O1 -fno-omit-frame-pointer) + target_link_options(pico_fido PUBLIC + -fsanitize=address -g -O1 -fno-omit-frame-pointer) + endif() else() target_link_options(pico_fido PUBLIC -Wl,--gc-sections diff --git a/src/fido/cbor.c b/src/fido/cbor.c index c7ac7f6..29a1b98 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" @@ -104,7 +104,8 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) { return CTAP1_ERR_INVALID_CMD; } -void cbor_thread(void) { +void *cbor_thread(void *arg) { + (void)arg; card_init_core1(); while (1) { uint32_t m; @@ -115,17 +116,17 @@ void cbor_thread(void) { if (m == EV_EXIT) { break; } - apdu.sw = cbor_parse(cbor_cmd, cbor_data, cbor_len); + apdu.sw = (uint16_t)cbor_parse(cbor_cmd, cbor_data, cbor_len); if (apdu.sw == 0) { DEBUG_DATA(res_APDU, res_APDU_size); } else { if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) { - res_APDU[-1] = apdu.sw; + res_APDU[-1] = (uint8_t)apdu.sw; apdu.sw = 0; } else { - res_APDU[0] = apdu.sw; + res_APDU[0] = (uint8_t)apdu.sw; } } @@ -137,6 +138,7 @@ void cbor_thread(void) { #ifdef ESP_PLATFORM vTaskDelete(NULL); #endif + return NULL; } int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) { @@ -214,6 +216,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_client_pin.c b/src/fido/cbor_client_pin.c index 512c34b..75fcd62 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 @@ -27,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" @@ -35,7 +36,6 @@ #include "files.h" #include "random.h" #include "crypto_utils.h" -#include "pico_keys.h" #include "apdu.h" #include "kek.h" @@ -44,6 +44,7 @@ uint32_t max_usage_time_period = 600 * 1000; bool needs_power_cycle = false; static mbedtls_ecdh_context hkey; static bool hkey_init = false; +extern int encrypt_keydev_f1(const uint8_t keydev[32]); int beginUsingPinUvAuthToken(bool userIsPresent) { paut.user_present = userIsPresent; @@ -105,11 +106,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 +122,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 +138,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; } @@ -210,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in return -1; } -int authenticate(uint8_t protocol, - const uint8_t *key, - const uint8_t *data, - size_t len, - uint8_t *sign) { +int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) { uint8_t hmac[32]; int ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac); @@ -242,10 +228,10 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l return ret; } if (protocol == 1) { - return memcmp(sign, hmac, 16); + return ct_memcmp(sign, hmac, 16); } else if (protocol == 2) { - return memcmp(sign, hmac, 32); + return ct_memcmp(sign, hmac, 32); } return -1; } @@ -280,17 +266,15 @@ int pinUvAuthTokenUsageTimerObserver() { return 0; } -int check_mkek_encrypted(const uint8_t *dhash) { - if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) { - hash_multi(dhash, 16, session_pin); // Only for storing MKEK - uint8_t mkek[MKEK_SIZE] = {0}; - memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE); - int ret = store_mkek(mkek); - mbedtls_platform_zeroize(mkek, sizeof(mkek)); - mbedtls_platform_zeroize(session_pin, sizeof(session_pin)); - if (ret != PICOKEY_OK) { - return CTAP2_ERR_PIN_AUTH_INVALID; - } +int check_keydev_encrypted(const uint8_t pin_token[32]) { + if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) { + uint8_t tmp_keydev[61]; + tmp_keydev[0] = 0x02; // Change format to encrypted + encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1); + DEBUG_DATA(tmp_keydev, sizeof(tmp_keydev)); + file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev)); + mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev)); + low_flash_available(); } return PICOKEY_OK; } @@ -305,11 +289,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CborEncoder encoder, mapEncoder; CborValue map; CborError error = CborNoError; - CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, - kay = { 0 }; + CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 }; CborCharString rpId = { 0 }; CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map)); uint64_t val_c = 1; + uint8_t keydev[32] = {0}; if (hkey_init == false) { initialize(); } @@ -431,15 +415,17 @@ 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); + pin_derive_verifier(dhash, 16, hsh + 3); + file_put_data(ef_pin, hsh, sizeof(hsh)); low_flash_available(); - ret = check_mkek_encrypted(dhash); + pin_derive_session(dhash, 16, session_pin); + ret = check_keydev_encrypted(session_pin); if (ret != PICOKEY_OK) { CBOR_ERROR(ret); } @@ -486,10 +472,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]; @@ -498,9 +484,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 { + pin_derive_verifier(paddedNewPin, 16, dhash); + } + + if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { regenerate(); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); if (retries == 0) { @@ -514,10 +507,28 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PIN_INVALID); } } - hash_multi(paddedNewPin, 16, session_pin); + if (off == 2) { + // Upgrade pin file to new format + pin_data[2] = 1; // New format indicator + pin_derive_verifier(paddedNewPin, 16, pin_data + 3); + + hash_multi(paddedNewPin, 16, session_pin); + ret = load_keydev(keydev); + if (ret != PICOKEY_OK) { + CBOR_ERROR(CTAP2_ERR_PIN_INVALID); + } + encrypt_keydev_f1(keydev); + } + pin_derive_session(paddedNewPin, 16, session_pin); pin_data[0] = MAX_PIN_RETRIES; file_put_data(ef_pin, pin_data, sizeof(pin_data)); low_flash_available(); + + ret = check_keydev_encrypted(session_pin); + if (ret != PICOKEY_OK) { + CBOR_ERROR(ret); + } + new_pin_mismatches = 0; ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); @@ -539,35 +550,33 @@ 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; + + // New PIN is valid and verified + ret = load_keydev(keydev); + if (ret != PICOKEY_OK) { + CBOR_ERROR(CTAP2_ERR_PIN_INVALID); + } + encrypt_keydev_f1(keydev); + mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); - double_hash_pin(dhash, 16, hsh + 2); - if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && - memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) { + pin_derive_session(dhash, 16, session_pin); + ret = check_keydev_encrypted(session_pin); + if (ret != PICOKEY_OK) { + CBOR_ERROR(ret); + } + low_flash_available(); + + pin_data[0] = MAX_PIN_RETRIES; + pin_data[1] = pin_len; + pin_data[2] = 1; // New format indicator + pin_derive_verifier(dhash, 16, pin_data + 3); + + if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) { CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); } + file_put_data(ef_pin, pin_data, sizeof(pin_data)); - uint8_t mkek[MKEK_SIZE] = {0}; - ret = load_mkek(mkek); - if (ret != PICOKEY_OK) { - CBOR_ERROR(ret); - } - file_put_data(ef_pin, hsh, 2 + 32); - - ret = check_mkek_encrypted(dhash); - if (ret != PICOKEY_OK) { - CBOR_ERROR(ret); - } - - hash_multi(dhash, 16, session_pin); - ret = store_mkek(mkek); - mbedtls_platform_zeroize(mkek, sizeof(mkek)); - if (ret != PICOKEY_OK) { - CBOR_ERROR(ret); - } - mbedtls_platform_zeroize(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)); @@ -578,6 +587,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 +608,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); @@ -618,10 +630,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; @@ -630,11 +642,18 @@ 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 { + pin_derive_verifier(paddedNewPin, 16, dhash); + } + if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) { regenerate(); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); + mbedtls_platform_zeroize(dhash, sizeof(dhash)); if (retries == 0) { CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED); } @@ -646,39 +665,59 @@ int cbor_client_pin(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PIN_INVALID); } } + mbedtls_platform_zeroize(dhash, sizeof(dhash)); - ret = check_mkek_encrypted(paddedNewPin); + if (off == 2) { + // Upgrade pin file to new format + pin_data[2] = 1; // New format indicator + pin_derive_verifier(paddedNewPin, 16, pin_data + 3); + hash_multi(paddedNewPin, 16, session_pin); + ret = load_keydev(keydev); + if (ret != PICOKEY_OK) { + CBOR_ERROR(CTAP2_ERR_PIN_INVALID); + } + encrypt_keydev_f1(keydev); + } + + pin_derive_session(paddedNewPin, 16, session_pin); + ret = check_keydev_encrypted(session_pin); if (ret != PICOKEY_OK) { CBOR_ERROR(ret); } - hash_multi(paddedNewPin, 16, session_pin); pin_data[0] = MAX_PIN_RETRIES; new_pin_mismatches = 0; + file_put_data(ef_pin, pin_data, sizeof(pin_data)); mbedtls_platform_zeroize(pin_data, sizeof(pin_data)); - mbedtls_platform_zeroize(dhash, sizeof(dhash)); low_flash_available(); file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF); 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)); @@ -695,6 +734,7 @@ err: CBOR_FREE_BYTE_STRING(kax); CBOR_FREE_BYTE_STRING(kay); CBOR_FREE_BYTE_STRING(rpId); + mbedtls_platform_zeroize(keydev, sizeof(keydev)); if (error != CborNoError) { if (error == CborErrorImproperValue) { return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 89abcdf..2ee716d 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" @@ -31,14 +31,16 @@ 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; CborValue map; CborError error = CborNoError; - uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0; - CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 }; - CborCharString minPinLengthRPIDs[32] = { 0 }; + uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0; + CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 }; + CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 }; size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0; CborEncoder encoder; //CborEncoder mapEncoder; @@ -66,13 +68,19 @@ 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); } else if (subpara == 0x02) { - CBOR_FIELD_GET_BYTES(vendorAutCt, 2); + CBOR_FIELD_GET_BYTES(vendorParamByteString, 2); + } + 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 @@ -95,15 +103,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; @@ -122,9 +121,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); @@ -141,8 +143,14 @@ 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); } @@ -163,7 +171,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); } @@ -171,7 +179,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) { @@ -184,6 +192,56 @@ 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 = (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)vendorParamInt; + phy_data.led_gpio_present = true; + } + else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) { + 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)vendorParamInt; + } + else { + CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); + } + if (is_phy && phy_save() != PICOKEY_OK) { + 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 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 + (uint16_t)vendorParamByteString.len); + free(val); + } + } + low_flash_available(); + } else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } @@ -207,6 +265,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; @@ -222,35 +284,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); } @@ -259,7 +292,8 @@ 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); + 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_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 95d4f42..5d53db8 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; @@ -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; @@ -239,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_resident(cred_ef, rpIdHash.data, &cred) != 0) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } @@ -296,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)); @@ -352,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); @@ -394,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_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) { @@ -405,11 +427,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } uint8_t newcred[MAX_CRED_ID_LENGTH]; - size_t newcred_len = 0; + uint16_t newcred_len = 0; if (credential_create(&cred.rpId, &cred.userId, &user.parent.name, - &user.displayName, &cred.opts, &cred.extensions, - cred.use_sign_count, (int)cred.alg, - (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 a534a8d..ee25e79 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -15,16 +15,16 @@ * along with this program. If not, see . */ +#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" #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" @@ -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_resident(ef, 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_resident(ef, 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 && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) { credential_free(&creds[i]); } else { @@ -375,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++; + } } } } @@ -427,10 +463,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 +675,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 +709,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_get_info.c b/src/fido/cbor_get_info.c index 74643cd..182e33a 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" @@ -27,22 +28,36 @@ 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; +#ifndef ENABLE_EMULATION + if (phy_data.vid != 0x1050) { + lfields++; + } +#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)); - 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)); - 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)); @@ -150,13 +165,36 @@ 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)); + uint8_t enabled_cmds = 4; +#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)); + 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)); + 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 + } + 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)); + } - 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)); - +#endif CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); err: if (error != CborNoError) { diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c index 40e3dca..293a677 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; @@ -129,7 +129,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) { uint8_t verify_data[70] = { 0 }; memset(verify_data, 0xff, 32); verify_data[32] = 0x0C; - put_uint32_t_le(offset, verify_data + 34); + put_uint32_t_le((uint32_t)offset, verify_data + 34); mbedtls_sha256(set.data, set.len, verify_data + 38, 0); if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) { CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 5aea796..25a5d41 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,7 @@ #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) { CborParser parser; @@ -39,7 +40,11 @@ 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; + const bool *pin_complexity_policy = NULL; uint8_t *aut_data = NULL; size_t resp_size = 0; CredExtensions extensions = { 0 }; @@ -127,12 +132,39 @@ 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); 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); @@ -202,21 +234,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) @@ -227,11 +274,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 @@ -298,12 +350,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_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); + } + } + } + } + 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); } @@ -313,6 +379,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) { @@ -332,11 +402,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) { const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash); uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0}; - size_t cred_id_len = 0; + uint16_t cred_id_len = 0; - CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, - &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, - cred_id, &cred_id_len)); + 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; @@ -372,6 +440,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) { if (extensions.credBlob.present == true) { l++; } + 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) { @@ -383,15 +457,69 @@ 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)); } + 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)); + } + 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); @@ -417,15 +545,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) { @@ -436,14 +572,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 @@ -483,7 +619,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { #ifndef ENABLE_EMULATION uint8_t *p = (uint8_t *)user.parent.name.data + 5; if (memcmp(p, "CommissionProfile", 17) == 0) { - ret = phy_unserialize_data(user.id.data, user.id.len, &phy_data); + ret = phy_unserialize_data(user.id.data, (uint16_t)user.id.len, &phy_data); if (ret == PICOKEY_OK) { ret = phy_save(); } @@ -519,12 +655,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) { @@ -569,6 +705,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); } diff --git a/src/fido/cbor_reset.c b/src/fido/cbor_reset.c index c5ee2ab..c09d8c8 100644 --- a/src/fido/cbor_reset.c +++ b/src/fido/cbor_reset.c @@ -15,10 +15,11 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #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/cbor_selection.c b/src/fido/cbor_selection.c index 90ccdc1..079d4d6 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 b077a98..e102aab 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" @@ -237,25 +237,14 @@ 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) { if (vendorCmd == 0x01) { uint16_t opts = 0; if (file_has_data(ef_phy)) { - uint8_t *data = file_get_data(ef_phy); - opts = get_uint16_t_be(data + PHY_OPTS); + uint8_t *pdata = file_get_data(ef_phy); + opts = get_uint16_t_be(pdata + PHY_OPTS); } CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); diff --git a/src/fido/cmd_authenticate.c b/src/fido/cmd_authenticate.c index 024b373..2616f42 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 52f5c72..a545448 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 f55f80f..6bc479a 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -15,10 +15,11 @@ * along with this program. If not, see . */ +#include "pico_keys.h" #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" @@ -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 *); @@ -93,7 +93,7 @@ int credential_create(CborCharString *rpId, int alg, int curve, uint8_t *cred_id, - size_t *cred_id_len) { + uint16_t *cred_id_len) { CborEncoder encoder, mapEncoder, mapEncoder2; CborError error = CborNoError; uint8_t rp_id_hash[32]; @@ -150,7 +150,7 @@ int credential_create(CborCharString *rpId, } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id); - *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN; + *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN; uint8_t key[32] = {0}; credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO); uint8_t iv[CRED_IV_LEN] = {0}; @@ -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,33 @@ 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, 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); + return 0; +} + +bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) { + if (cred_id_len < 4 + CRED_PROTO_RESIDENT_LEN) { + return false; + } + return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0; +} + +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 d7235f9..a77b72f 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; @@ -58,6 +59,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 +68,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 + 6) +#define CRED_RESIDENT_LEN (CRED_RESIDENT_HEADER_LEN + 32) + typedef enum { CRED_PROTO_21 = 0x01, @@ -83,7 +90,7 @@ extern int credential_create(CborCharString *rpId, int alg, int curve, uint8_t *cred_id, - size_t *cred_id_len); + uint16_t *cred_id_len); extern void credential_free(Credential *cred); extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash); extern int credential_load(const uint8_t *cred_id, @@ -94,5 +101,8 @@ 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); +extern int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred); #endif // _CREDENTIAL_H_ diff --git a/src/fido/ctap.h b/src/fido/ctap.h index 2db44bf..15bb84d 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -114,10 +114,14 @@ 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_GPIO 0x7b392a394de9f948 #define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd -#define CTAP_CONFIG_PHY_OPTS 0x969f3b09eceb805f +#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948 +#define CTAP_CONFIG_PHY_OPTS 0x269f3b09eceb805f +#endif #define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1) @@ -134,6 +138,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/defs.c b/src/fido/defs.c index c8cdc0f..74713be 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 96fe65d..d6cb9d9 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,10 +25,10 @@ #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) +#if defined(PICO_PLATFORM) #include "bsp/board.h" #endif #include @@ -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; @@ -118,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) { @@ -158,7 +168,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) { uint8_t key_path[KEY_PATH_LEN]; memcpy(key_path, cred_id, KEY_PATH_LEN); *(uint32_t *) key_path = 0x80000000 | 10022; - for (int i = 1; i < KEY_PATH_ENTRIES; i++) { + for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) { *(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000; } return derive_key(NULL, false, key_path, mbedtls_curve, key); @@ -194,7 +204,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe return ret; } -int load_keydev(uint8_t *key) { +int load_keydev(uint8_t key[32]) { if (has_keydev_dec == false && !file_has_data(ef_keydev)) { return PICOKEY_ERR_MEMORY_FATAL; } @@ -203,13 +213,39 @@ int load_keydev(uint8_t *key) { memcpy(key, keydev_dec, sizeof(keydev_dec)); } else { - memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev)); - - if (mkek_decrypt(key, 32) != PICOKEY_OK) { - return PICOKEY_EXEC_ERROR; + uint16_t fid_size = file_get_size(ef_keydev); + if (fid_size == 32) { + memcpy(key, file_get_data(ef_keydev), 32); + if (mkek_decrypt(key, 32) != PICOKEY_OK) { + return PICOKEY_EXEC_ERROR; + } + if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) { + return PICOKEY_EXEC_ERROR; + } } - if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) { - return PICOKEY_EXEC_ERROR; + else if (fid_size == 33 || fid_size == 61) { + uint8_t format = *file_get_data(ef_keydev); + if (format == 0x01 || format == 0x02) { // Format indicator + if (format == 0x02) { + uint8_t tmp_key[61]; + memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key)); + int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, key); + if (ret != PICOKEY_OK) { + return PICOKEY_EXEC_ERROR; + } + } + else { + memcpy(key, file_get_data(ef_keydev) + 1, 32); + } + uint8_t kbase[32]; + derive_kbase(kbase); + int ret = aes_decrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32); + if (ret != PICOKEY_OK) { + mbedtls_platform_zeroize(kbase, sizeof(kbase)); + return PICOKEY_EXEC_ERROR; + } + mbedtls_platform_zeroize(kbase, sizeof(kbase)); + } } } @@ -217,7 +253,7 @@ int load_keydev(uint8_t *key) { } int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) { - for (int i = 0; i < KEY_PATH_ENTRIES; i++) { + for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) { uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)]; if (!(k & 0x80000000)) { return -1; @@ -258,7 +294,7 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur return r; } const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - for (int i = 0; i < KEY_PATH_ENTRIES; i++) { + for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) { if (new_key == true) { uint32_t val = 0; random_gen(NULL, (uint8_t *) &val, sizeof(val)); @@ -305,6 +341,23 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur return r; } +int encrypt_keydev_f1(const uint8_t keydev[32]) { + uint8_t kdata[33] = {0}; + kdata[0] = 0x01; // Format indicator + memcpy(kdata + 1, keydev, 32); + uint8_t kbase[32]; + derive_kbase(kbase); + int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata + 1, 32); + mbedtls_platform_zeroize(kbase, sizeof(kbase)); + if (ret != PICOKEY_OK) { + return ret; + } + ret = file_put_data(ef_keydev, kdata, 33); + mbedtls_platform_zeroize(kdata, sizeof(kdata)); + low_flash_available(); + return ret; +} + int scan_files_fido() { ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF); ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF); @@ -320,17 +373,16 @@ int scan_files_fido() { mbedtls_ecdsa_free(&ecdsa); return ret; } - uint8_t kdata[64]; + uint8_t keydev[32] = {0}; size_t key_size = 0; - ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata)); - if (ret != PICOKEY_OK) { - return ret; + ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev)); + if (ret != 0 || key_size != 32) { + mbedtls_platform_zeroize(keydev, sizeof(keydev)); + mbedtls_ecdsa_free(&ecdsa); + return ret != 0 ? ret : PICOKEY_EXEC_ERROR; } - if (otp_key_1) { - ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32); - } - ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size); - mbedtls_platform_zeroize(kdata, sizeof(kdata)); + encrypt_keydev_f1(keydev); + mbedtls_platform_zeroize(keydev, sizeof(keydev)); mbedtls_ecdsa_free(&ecdsa); if (ret != PICOKEY_OK) { return ret; @@ -341,21 +393,6 @@ int scan_files_fido() { else { printf("FATAL ERROR: KEY DEV not found in memory!\r\n"); } - if (ef_mkek) { // No encrypted MKEK - if (!file_has_data(ef_mkek)) { - uint8_t mkek[MKEK_IV_SIZE + MKEK_KEY_SIZE]; - random_gen(NULL, mkek, sizeof(mkek)); - file_put_data(ef_mkek, mkek, sizeof(mkek)); - int ret = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), file_get_data(ef_keydev), 32); - mbedtls_platform_zeroize(mkek, sizeof(mkek)); - if (ret != 0) { - printf("FATAL ERROR: MKEK encryption failed!\r\n"); - } - } - } - else { - printf("FATAL ERROR: MKEK not found in memory!\r\n"); - } ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF); if (ef_certdev) { if (!file_has_data(ef_certdev)) { @@ -399,13 +436,6 @@ int scan_files_fido() { printf("FATAL ERROR: Global counter not found in memory!\r\n"); } ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF); - if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage - uint8_t pin_data[34] = { 0 }, dhash[32]; - memcpy(pin_data, file_get_data(ef_pin), 18); - double_hash_pin(pin_data + 2, 16, dhash); - memcpy(pin_data + 2, dhash, 32); - file_put_data(ef_pin, pin_data, 34); - } ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF); if (ef_authtoken) { if (!file_has_data(ef_authtoken)) { @@ -419,6 +449,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); @@ -436,12 +479,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); @@ -485,11 +530,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(); diff --git a/src/fido/fido.h b/src/fido/fido.h index c69836e..481aa6c 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 @@ -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) @@ -55,20 +51,28 @@ extern void init_fido(); extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve); extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id); extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key); -extern int load_keydev(uint8_t *key); +extern int load_keydev(uint8_t key[32]); extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out); extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out); extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret); -#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 +82,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 @@ -129,9 +136,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 0f6b35b..bc278d5 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 8434136..1fe844b 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -29,7 +29,9 @@ #define EF_OPTS 0xC001 #define EF_PIN 0x1080 #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/src/fido/kek.c b/src/fido/kek.c index 1501060..a7b380f 100644 --- a/src/fido/kek.c +++ b/src/fido/kek.c @@ -15,10 +15,10 @@ * 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) +#if defined(PICO_PLATFORM) #include "pico/stdlib.h" #endif #include "kek.h" @@ -85,46 +85,6 @@ void release_mkek(uint8_t *mkek) { mbedtls_platform_zeroize(mkek, MKEK_SIZE); } -int store_mkek(const uint8_t *mkek) { - uint8_t tmp_mkek[MKEK_SIZE]; - if (mkek == NULL) { - const uint8_t *rd = random_bytes_get(MKEK_IV_SIZE + MKEK_KEY_SIZE); - memcpy(tmp_mkek, rd, MKEK_IV_SIZE + MKEK_KEY_SIZE); - } - else { - memcpy(tmp_mkek, mkek, MKEK_SIZE); - } - if (otp_key_1) { - mkek_masked(tmp_mkek, otp_key_1); - } - *(uint32_t *) MKEK_CHECKSUM(tmp_mkek) = crc32c(MKEK_KEY(tmp_mkek), MKEK_KEY_SIZE); - uint8_t tmp_mkek_pin[MKEK_SIZE]; - memcpy(tmp_mkek_pin, tmp_mkek, MKEK_SIZE); - file_t *tf = search_file(EF_MKEK); - if (!tf) { - release_mkek(tmp_mkek); - release_mkek(tmp_mkek_pin); - return PICOKEY_ERR_FILE_NOT_FOUND; - } - aes_encrypt_cfb_256(session_pin, MKEK_IV(tmp_mkek_pin), MKEK_KEY(tmp_mkek_pin), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE); - file_put_data(tf, tmp_mkek_pin, MKEK_SIZE); - release_mkek(tmp_mkek_pin); - low_flash_available(); - release_mkek(tmp_mkek); - return PICOKEY_OK; -} - -int mkek_encrypt(uint8_t *data, uint16_t len) { - int r; - uint8_t mkek[MKEK_SIZE + 4]; - if ((r = load_mkek(mkek)) != PICOKEY_OK) { - return r; - } - r = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len); - release_mkek(mkek); - return r; -} - int mkek_decrypt(uint8_t *data, uint16_t len) { int r; uint8_t mkek[MKEK_SIZE + 4]; diff --git a/src/fido/known_apps.c b/src/fido/known_apps.c index 9fc2c93..f724528 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 b3025b1..3a61178 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" @@ -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; } @@ -114,7 +116,7 @@ int man_get_config() { if (!file_has_data(ef)) { res_APDU[res_APDU_size++] = TAG_USB_ENABLED; res_APDU[res_APDU_size++] = 2; - uint16_t caps = 0; + caps = 0; if (cap_supported(CAP_FIDO2)) { caps |= CAP_FIDO2; } diff --git a/src/fido/management.h b/src/fido/management.h index e5d6bd1..4651c61 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 diff --git a/src/fido/oath.c b/src/fido/oath.c index fc4e592..0c293c7 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" @@ -24,6 +24,7 @@ #include "asn1.h" #include "crypto_utils.h" #include "management.h" +extern bool is_nk; #define MAX_OATH_CRED 255 #define CHALLENGE_LEN 8 @@ -44,6 +45,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 +61,7 @@ #define PROP_INC 0x01 #define PROP_TOUCH 0x02 +#define PROP_PIN 0x03 int oath_process_apdu(); int oath_unload(); @@ -99,6 +105,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 +282,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; + } } } } @@ -615,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); @@ -626,6 +649,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 +710,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 +727,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 } }; diff --git a/src/fido/otp.c b/src/fido/otp.c index 582f72c..29d7da8 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; @@ -218,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; @@ -259,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 @@ -317,19 +317,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 +486,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 +564,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 +665,3 @@ uint16_t otp_hid_get_report_cb(uint8_t itf, return reqlen; } - -#endif 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/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/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/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/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..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): @@ -85,7 +86,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 +117,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 +127,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 +141,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 +184,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 +198,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 +209,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 +228,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..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.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, 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)['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)['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) 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 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 diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index 963e70f..07c252f 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 @@ -73,21 +73,21 @@ def get_pki_data(url, data=None, method='GET'): return j class VendorConfig(Config): - class PARAM(IntEnum): VENDOR_COMMAND_ID = 0x01 - VENDOR_AUT_CT = 0x02 - VENDOR_PARAM = 0x02 + VENDOR_PARAM_BYTESTRING = 0x02 + VENDOR_PARAM_INT = 0x03 + VENDOR_PARAM_TEXTSTRING = 0x04 class CMD(IntEnum): CONFIG_AUT_ENABLE = 0x03e43f56b34285e2 CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9 - CONFIG_VENDOR_PROTOTYPE = 0x7f - CONFIG_VENDOR_PHY = 0x1b + 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 = 0x269f3b09eceb805f + CONFIG_PIN_POLICY = 0x6c07d70fe96c3897 class RESP(IntEnum): KEY_AGREEMENT = 0x01 @@ -97,16 +97,16 @@ class VendorConfig(Config): def enable_device_aut(self, ct): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE, + 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 }, ) def disable_device_aut(self): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE, + Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE }, @@ -114,40 +114,63 @@ class VendorConfig(Config): def vidpid(self, vid, pid): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + 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 }, ) def led_gpio(self, gpio): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + 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 }, ) def led_brightness(self, brightness): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + 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 }, ) def phy_opts(self, opts): self._call( - VendorConfig.CMD.CONFIG_VENDOR_PHY, + 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 + }, + ) + + 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) @@ -245,7 +268,6 @@ class Vendor: DISABLE = 0x02 KEY_AGREEMENT = 0x01 EA_CSR = 0x01 - EA_UPLOAD = 0x02 class RESP(IntEnum): PARAM = 0x01 @@ -433,13 +455,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) @@ -483,6 +499,21 @@ 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 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") @@ -495,7 +526,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,10 +544,15 @@ 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 -def secure(vdr, args): +def secure(vdr: Vendor, args): if (args.subcommand == 'enable'): vdr.enable_device_aut() elif (args.subcommand == 'unlock'): @@ -524,13 +560,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()) @@ -545,8 +581,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'): @@ -570,7 +608,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}%)') @@ -579,6 +617,14 @@ def memory(vdr, 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') @@ -602,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()