diff --git a/README.md b/README.md index 2bfd17d..c45d55d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Pico FIDO This project transforms your Raspberry Pi Pico or ESP32 microcontroller into an integrated FIDO Passkey, functioning like a standard USB Passkey for authentication. -If you are looking for a Fido + OpenPGP, see: https://github.com/polhenarejos/pico-fido2 +If you are looking for a OpenPGP + Fido, see: https://github.com/polhenarejos/pico-fido2. Available through [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App"). ## Features Pico FIDO includes the following features: @@ -36,12 +36,13 @@ Pico FIDO includes the following features: - Challenge-response generation - Emulated keyboard interface - Button press generates an OTP that is directly typed +- Yubico Authenticator app compatible - Yubico YKMAN compatible - Nitrokey nitropy and nitroapp compatible - Secure Boot and Secure Lock in RP2350 and ESP32-S3 MCUs - One Time Programming to store the master key that encrypts all resident keys and seeds. - Rescue interface to allow recovery of the device if it becomes unresponsive or undetectable. -- LED customization with Pico Commissioner. +- LED customization with PicoKey App. All features comply with the specifications. If you encounter unexpected behavior or deviations from the specifications, please open an issue. @@ -55,11 +56,11 @@ Microcontrollers RP2350 and ESP32-S3 are designed to support secure environments If you own a Raspberry Pico (RP2040 or RP2350), go to [Download page](https://www.picokeys.com/getting-started/), select your vendor and model and download the proper firmware; or go to [Release page](https://www.github.com/polhenarejos/pico-fido/releases/) and download the UF2 file for your board. -Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you plan to use it with other proprietary tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [Pico Commissioner](https://www.picokeys.com/pico-commissioner/ "Pico Commissioner"). +Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you plan to use it with OpenSC or similar tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App"). You can use whatever VID/PID (i.e., 234b:0000 from FISJ), but remember that you are not authorized to distribute the binary with a VID/PID that you do not own. -Note that the pure-browser option [Pico Commissioner](https://www.picokeys.com/pico-commissioner/ "Pico Commissioner") is the most recommended. +Note that the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App") is the most recommended. ## Build for Raspberry Pico Before building, ensure you have installed the toolchain for the Pico and that the Pico SDK is properly located on your drive. diff --git a/build_pico_fido.sh b/build_pico_fido.sh index ab83499..497d70d 100755 --- a/build_pico_fido.sh +++ b/build_pico_fido.sh @@ -1,7 +1,7 @@ #!/bin/bash VERSION_MAJOR="7" -VERSION_MINOR="0" +VERSION_MINOR="2" SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}" #if ! [[ -z "${GITHUB_SHA}" ]]; then # SUFFIX="${SUFFIX}.${GITHUB_SHA}" diff --git a/pico-keys-sdk b/pico-keys-sdk index 7e6e3c8..8a0ef0b 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 7e6e3c8f3c7d4f7e9aa5f7f94de4a3560fbf59cb +Subproject commit 8a0ef0b30cf4fa586b415a37578695efe35a04be diff --git a/src/fido/cbor.c b/src/fido/cbor.c index fec4d2f..f9ca3ed 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -41,6 +41,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len); int cbor_config(const uint8_t *data, size_t len); int cbor_vendor(const uint8_t *data, size_t len); int cbor_large_blobs(const uint8_t *data, size_t len); +extern void reset_gna_state(); extern int cmd_read_config(); @@ -59,6 +60,9 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) { } if (cap_supported(CAP_FIDO2)) { if (cmd == CTAPHID_CBOR) { + if (data[0] != CTAP_GET_NEXT_ASSERTION) { + reset_gna_state(); + } if (data[0] == CTAP_MAKE_CREDENTIAL) { return cbor_make_credential(data + 1, len - 1); } diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index ee25e79..fac8e01 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -42,6 +42,22 @@ uint32_t timerx = 0; uint8_t *datax = NULL; size_t lenx = 0; +void reset_gna_state() { + for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { + credential_free(&credsx[i]); + } + if (datax) { + free(datax); + datax = NULL; + } + lenx = 0; + residentx = false; + timerx = 0; + flagsx = 0; + credentialCounter = 0; + numberOfCredentialsx = 0; +} + int cbor_get_next_assertion(const uint8_t *data, size_t len) { (void) data; (void) len; @@ -57,19 +73,7 @@ int cbor_get_next_assertion(const uint8_t *data, size_t len) { credentialCounter++; err: if (error != CborNoError || credentialCounter == numberOfCredentialsx) { - for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { - credential_free(&credsx[i]); - } - if (datax) { - free(datax); - datax = NULL; - } - lenx = 0; - residentx = false; - timerx = 0; - flagsx = 0; - credentialCounter = 0; - numberOfCredentialsx = 0; + reset_gna_state(); if (error == CborErrorImproperValue) { return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; } diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 25a5d41..b53cd58 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -613,24 +613,6 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PROCESSING); } - if (user.id.len > 0 && user.parent.name.len > 0 && user.displayName.len > 0) { - if (memcmp(user.parent.name.data, "+pico", 5) == 0) { - options.rk = pfalse; -#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, (uint16_t)user.id.len, &phy_data); - if (ret == PICOKEY_OK) { - ret = phy_save(); - } - } -#endif - if (ret != PICOKEY_OK) { - CBOR_ERROR(CTAP2_ERR_PROCESSING); - } - } - } - uint8_t largeBlobKey[32] = {0}; if (extensions.largeBlobKey == ptrue && options.rk == ptrue) { ret = credential_derive_large_blob_key(cred_id, cred_id_len, largeBlobKey); diff --git a/src/fido/credential.c b/src/fido/credential.c index 6bc479a..645417d 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -319,7 +319,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t * credential_free(&rcred); continue; } - if (memcmp(rcred.userId.data, cred.userId.data, MIN(rcred.userId.len, cred.userId.len)) == 0) { + if (rcred.userId.len == cred.userId.len && memcmp(rcred.userId.data, cred.userId.data, rcred.userId.len) == 0) { sloti = i; credential_free(&rcred); new_record = false; diff --git a/src/fido/files.h b/src/fido/files.h index 1fe844b..715a306 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -40,6 +40,8 @@ #define EF_OATH_CODE 0xBAFF #define EF_OTP_SLOT1 0xBB00 #define EF_OTP_SLOT2 0xBB01 +#define EF_OTP_SLOT3 0xBB02 +#define EF_OTP_SLOT4 0xBB03 #define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN extern file_t *ef_keydev; diff --git a/src/fido/otp.c b/src/fido/otp.c index 29d7da8..3cc5811 100644 --- a/src/fido/otp.c +++ b/src/fido/otp.c @@ -49,6 +49,11 @@ void append_keyboard_buffer(const uint8_t *buf, size_t len) {} #define CONFIG_LED_INV 0x10 #define CONFIG_STATUS_MASK 0x1f +#define CONFIG3_VALID 0x01 +#define CONFIG4_VALID 0x02 +#define CONFIG3_TOUCH 0x04 +#define CONFIG4_TOUCH 0x08 + /* EXT Flags */ #define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press) #define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field @@ -161,7 +166,7 @@ extern void scan_all(); void init_otp() { if (scanned == false) { scan_all(); - for (uint8_t i = 0; i < 2; i++) { + for (uint8_t i = 0; i < 4; i++) { file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i); uint8_t *data = file_get_data(ef); otp_config_t *otp_config = (otp_config_t *) data; @@ -207,7 +212,8 @@ int otp_button_pressed(uint8_t slot) { if (!cap_supported(CAP_OTP)) { return 3; } - file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + uint16_t slot_ef = EF_OTP_SLOT1 + slot - 1; + file_t *ef = search_dynamic_file(slot_ef); const uint8_t *data = file_get_data(ef); otp_config_t *otp_config = (otp_config_t *) data; if (file_has_data(ef) == false) { @@ -333,6 +339,39 @@ int otp_unload() { } uint8_t status_byte = 0x0; +uint16_t otp_status_ext() { + for (int i = 0; i < 4; i++) { + file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i); + if (file_has_data(ef)) { + res_APDU[res_APDU_size++] = 0xB0 + i; + res_APDU[res_APDU_size++] = 0; // Filled later + uint8_t *p = res_APDU + res_APDU_size; + otp_config_t *otp_config = (otp_config_t *)file_get_data(ef); + *p++ = 0xA0; + *p++ = 2; + *p++ = otp_config->tkt_flags; + *p++ = otp_config->cfg_flags; + if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) { + + } + else if (otp_config->tkt_flags & OATH_HOTP) { + } + else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) { + } + else { + *p++ = 0xC0; + *p++ = 6; + memcpy(p, otp_config->fixed_data, 6); + p += 6; + } + uint8_t len = p - (res_APDU + res_APDU_size); + res_APDU[res_APDU_size - 1] = len; + res_APDU_size += len; + } + } + return SW_OK(); +} + uint16_t otp_status(bool is_otp) { if (scanned == false) { scan_all(); @@ -384,12 +423,13 @@ bool check_crc(const otp_config_t *data) { bool _is_otp = false; int cmd_otp() { uint8_t p1 = P1(apdu), p2 = P2(apdu); - if (p2 != 0x00) { - return SW_INCORRECT_P1P2(); - } if (p1 == 0x01 || p1 == 0x03) { // Configure slot otp_config_t *odata = (otp_config_t *) apdu.data; - file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + if (p1 == 0x03 && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; + file_t *ef = file_new(slot); if (file_has_data(ef)) { otp_config_t *otpc = (otp_config_t *) file_get_data(ef); if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) { @@ -415,10 +455,14 @@ int cmd_otp() { } else if (p1 == 0x04 || p1 == 0x05) { // Update slot otp_config_t *odata = (otp_config_t *) apdu.data; + if (p1 == 0x05 && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) { return SW_WRONG_DATA(); } - file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + file_t *ef = search_dynamic_file(slot); if (file_has_data(ef)) { otp_config_t *otpc = (otp_config_t *) file_get_data(ef); if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) { @@ -446,8 +490,16 @@ int cmd_otp() { else if (p1 == 0x06) { // Swap slots uint8_t tmp[otp_config_size + 8]; bool ef1_data = false; - file_t *ef1 = file_new(EF_OTP_SLOT1); - file_t *ef2 = file_new(EF_OTP_SLOT2); + uint16_t slot1 = EF_OTP_SLOT1, slot2 = EF_OTP_SLOT2; + if (apdu.ne > 0) { + if (apdu.ne != 2) { + return SW_WRONG_LENGTH(); + } + slot1 += apdu.data[0]; + slot2 += apdu.data[1]; + } + file_t *ef1 = file_new(slot1); + file_t *ef2 = file_new(slot2); if (file_has_data(ef1)) { memcpy(tmp, file_get_data(ef1), file_get_size(ef1)); ef1_data = true; @@ -458,7 +510,7 @@ int cmd_otp() { else { delete_file(ef1); // When a dynamic file is deleted, existing referenes are invalidated - ef2 = file_new(EF_OTP_SLOT2); + ef2 = file_new(slot2); } if (ef1_data) { file_put_data(ef2, tmp, sizeof(tmp)); @@ -478,8 +530,15 @@ int cmd_otp() { else if (p1 == 0x13) { // Get config man_get_config(); } + else if (p1 == 0x14) { + otp_status_ext(); + } else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) { // Calculate OTP - file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2); + if ((p1 == 0x38 || p1 == 0x28) && p2 != 0x0) { + return SW_INCORRECT_P1P2(); + } + uint16_t slot = (p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2; + file_t *ef = search_dynamic_file(slot); if (file_has_data(ef)) { otp_config_t *otp_config = (otp_config_t *) file_get_data(ef); if (!(otp_config->tkt_flags & CHAL_RESP)) { diff --git a/src/fido/version.h b/src/fido/version.h index 7417630..f8625ce 100644 --- a/src/fido/version.h +++ b/src/fido/version.h @@ -18,7 +18,7 @@ #ifndef __VERSION_H_ #define __VERSION_H_ -#define PICO_FIDO_VERSION 0x0700 +#define PICO_FIDO_VERSION 0x0702 #define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff) #define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff) diff --git a/tests/pico-fido/test_022_discoverable.py b/tests/pico-fido/test_022_discoverable.py index 36b7055..2faa2ed 100644 --- a/tests/pico-fido/test_022_discoverable.py +++ b/tests/pico-fido/test_022_discoverable.py @@ -202,10 +202,29 @@ def test_rk_with_allowlist_of_different_rp(resetdevice): assert e.value.code == CtapError.ERR.NO_CREDENTIALS +def test_same_prefix_userId(device): + """ + A make credential request with two different UserIds that share the same prefix should NOT overwrite. + """ + rp = {"id": "sameprefix.org", "name": "Example"} + user1 = {"id": b"user_12", "name": "A fixed name", "displayName": "A fixed display name"} + user2 = {"id": b"user_123", "name": "A fixed name", "displayName": "A fixed display name"} + + mc_res1 = device.MC(rp = rp, options={"rk":True}, user = user1) + + # Should not overwrite the first credential. + mc_res2 = device.MC(rp = rp, options={"rk":True}, user = user2) + + ga_res = device.GA(rp_id=rp['id'])['res'] + + assert ga_res.number_of_credentials == 2 + + def test_same_userId_overwrites_rk(resetdevice): """ A make credential request with a UserId & Rp that is the same as an existing one should overwrite. """ + resetdevice.reset() rp = {"id": "overwrite.org", "name": "Example"} user = generate_random_user()