Skip to content

Commit

Permalink
Merge branch 'main' into eddsa
Browse files Browse the repository at this point in the history
Signed-off-by: Pol Henarejos <[email protected]>
  • Loading branch information
polhenarejos committed Aug 20, 2024
2 parents 21765a6 + 910fb66 commit d2c25b6
Show file tree
Hide file tree
Showing 28 changed files with 341 additions and 322 deletions.
14 changes: 11 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@

cmake_minimum_required(VERSION 3.13)

if(ESP_PLATFORM)
set(EXTRA_COMPONENT_DIRS src pico-keys-sdk/src)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
else()

if(ENABLE_EMULATION)
else()
include(pico_sdk_import.cmake)
Expand All @@ -33,7 +38,7 @@ pico_sdk_init()
endif()

add_executable(pico_fido)

endif()
option(ENABLE_UP_BUTTON "Enable/disable user presence button" ON)
if(ENABLE_UP_BUTTON)
add_definitions(-DENABLE_UP_BUTTON=1)
Expand Down Expand Up @@ -110,11 +115,13 @@ endif()

set(USB_ITF_HID 1)
include(pico-keys-sdk/pico_keys_sdk_import.cmake)

if(ESP_PLATFORM)
project(pico_fido)
endif()
set(INCLUDES ${INCLUDES}
${CMAKE_CURRENT_LIST_DIR}/src/fido
)

if(NOT ESP_PLATFORM)
target_sources(pico_fido PUBLIC ${SOURCES})
target_include_directories(pico_fido PUBLIC ${INCLUDES})

Expand Down Expand Up @@ -149,3 +156,4 @@ else()
pico_add_extra_outputs(pico_fido)
target_link_libraries(pico_fido PRIVATE pico_keys_sdk pico_stdlib pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id hardware_rtc tinyusb_device tinyusb_board)
endif()
endif()
77 changes: 41 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,76 +1,80 @@
# Pico FIDO
This project aims at transforming your Raspberry Pico into a FIDO key integrated. The Pico works as a FIDO key, like a normal USB key for authentication.
This project transforms your Raspberry Pi Pico into an integrated FIDO key, functioning like a standard USB key for authentication.

## Features
Pico FIDO has implemented the following features:
Pico FIDO includes the following features:

- CTAP 2.1 / CTAP 1
- WebAuthn
- U2F
- HMAC-Secret extension
- CredProtect extension
- User presence enforcement through physical button
- User Verification with PIN
- User verification with PIN
- Discoverable credentials
- Credential management
- ECDSA and EDDSA authentication
- Authentication with SECP256R1, SECP384R1, SECP521R1, SECP256K1 and Ed25519 curves
- Support for SECP256R1, SECP384R1, SECP521R1, SECP256K1 and Ed25519 curves
- App registration and login
- Device selection
- Support for vendor Config
- Support for vendor configuration
- Backup with 24 words
- Secure lock to protect the device from flash dumpings
- Secure lock to protect the device from flash dumps
- Permissions support (MC, GA, CM, ACFG, LBW)
- Authenticator configuration
- minPinLength extension
- Self attestation
- Enterprise attestation
- credBlobs extension
- largeBlobKey extension
- largeBlobs support (2048 bytes máx.)
- Large blobs support (2048 bytes max)
- OATH (based on YKOATH protocol specification)
- TOTP / HOTP
- Yubikey OTP
- Challenge-response generation
- Emulated keyboard interface
- Button press generates an OTP that is written directly is it was typed
- Button press generates an OTP that is directly typed
- Yubico YKMAN compatible
- Nitrokey nitropy and nitroapp compatible

All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue.
All features comply with the specifications. If you encounter unexpected behavior or deviations from the specifications, please open an issue.

## Security considerations
Pico FIDO is an open platform so be careful. The contents in the flash memory may be easily dumpled and obtain the private/master keys. Therefore, it is not possible to encrypt the content. At least, one key (the master, the supreme key) must be stored in clear text.
## Security Considerations

If the Pico is stolen the contents of private and secret keys can be read.
Pico FIDO is an open platform, so exercise caution. The flash memory contents can be easily dumped, potentially revealing private/master keys. It is not feasible to encrypt the content, meaning at least one key (the master key) must be stored in clear text.

## Download
Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") and download the UF2 file for your board.
If the Pico is stolen, the private and secret keys can be accessed.

Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you are planning to use it with OpenSC or similar, you should modify Info.plist of CCID driver to add these VID/PID or use the [Pico Patcher tool](https://www.picokeys.com/pico-patcher/).
## Download
Please visit the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") to download the UF2 file for your board.

Alternatively you can use the legacy VID/PID patcher as follows:
`./patch_vidpid.sh VID:PID input_hsm_file.uf2 output_hsm_file.uf2`
Note that UF2 files are shipped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you plan to use it with OpenSC or similar software, you will need to modify the Info.plist of the CCID driver to add these VID/PID values or use the [Pico Patcher tool](https://www.picokeys.com/pico-patcher/).

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.
Alternatively, you can use the legacy VID/PID patcher with the following command:
```sh
./patch_vidpid.sh VID:PID input_hsm_file.uf2 output_hsm_file.uf2
```
You can use any VID/PID (e.g., 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 Patcher tool](https://www.picokeys.com/pico-patcher/) is the most recommended.
For ease of use, the pure-browser option [Pico Patcher tool](https://www.picokeys.com/pico-patcher/) is highly recommended.

## Build
Before building, ensure you have installed the toolchain for the Pico and the Pico SDK is properly located in your drive.

git clone https://github.com/polhenarejos/pico-fido
cd pico-fido
mkdir build
cd build
PICO_SDK_PATH=/path/to/pico-sdk cmake .. -DPICO_BOARD=board_type -DUSB_VID=0x1234 -DUSB_PID=0x5678
make
Before building, ensure you have installed the toolchain for the Pico and that the Pico SDK is properly located on your drive.

```sh
git clone https://github.com/polhenarejos/pico-fido
cd pico-fido
mkdir build
cd build
PICO_SDK_PATH=/path/to/pico-sdk cmake .. -DPICO_BOARD=board_type -DUSB_VID=0x1234 -DUSB_PID=0x5678
make
```

Note that PICO_BOARD, USB_VID and USB_PID are optional. If not provided, pico board and VID/PID FEFF:FCFD will be used.
Note that `PICO_BOARD`, `USB_VID`, and `USB_PID` are optional. If not provided, the default Pico board and VID/PID `FEFF:FCFD` will be used.

After make ends, the binary file pico_fido.uf2 will be generated. Put your pico board into loading mode, by pushing BOOTSEL button while pluging on, and copy the UF2 to the new fresh usb mass storage Pico device. Once copied, the pico mass storage will be disconnected automatically and the pico board will reset with the new firmware. A blinking led will indicate the device is ready to work.
After `make` finishes, the binary file `pico_fido.uf2` will be generated. Put your Pico board into loading mode by holding the BOOTSEL button while plugging it in, then copy the UF2 file to the new USB mass storage Pico device. Once copied, the Pico mass storage will disconnect automatically, and the Pico board will reset with the new firmware. A blinking LED will indicate that the device is ready to work.

**Remark:** Pico Fido uses HID interface and thus, VID/PID values are irrelevant in terms of operativity. You can safely use any arbitrary value or the default ones.
**Remark:** Pico FIDO uses the HID interface, so VID/PID values are irrelevant in terms of operativity. You can safely use any arbitrary values or the default ones.

## Led blink
Pico FIDO uses the led to indicate the current status. Four states are available:
Expand All @@ -96,20 +100,21 @@ While processing, the Pico FIDO is busy and cannot receive additional commands u

## Driver

Pico FIDO uses the `HID` driver, present in all OS. It should be detected by all OS and browser/applications, like normal USB FIDO keys.
Pico FIDO uses the `HID` driver, which is present in all operating systems. It should be detected by all OS and browser/applications just like normal USB FIDO keys.

## Tests

Tests can be found at `tests` folder. It is based on [FIDO2 tests](https://github.com/solokeys/fido2-tests "FIDO2 tests") from Solokeys, but adapted to [python-fido2](https://github.com/Yubico/python-fido2 "python-fido2") v1.0 package, which is a major refactor from previous 0.8 version and includes latests improvements from CTAP 2.1.
Tests can be found in the `tests` folder. They are based on [FIDO2 tests](https://github.com/solokeys/fido2-tests "FIDO2 tests") from Solokeys but adapted to the [python-fido2](https://github.com/Yubico/python-fido2 "python-fido2") v1.0 package, which is a major refactor from the previous 0.8 version and includes the latest improvements from CTAP 2.1.

All tests can be run by
To run all tests, use:

```
```sh
pytest
```

or by selecting a subset with `-k <test>` flag:
```
To run a subset of tests, use the `-k <test>` flag:

```sh
pytest -k test_credprotect
```

Expand Down
2 changes: 1 addition & 1 deletion build_pico_fido.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash

VERSION_MAJOR="5"
VERSION_MINOR="8-eddsa1"
VERSION_MINOR="10-eddsa1"

rm -rf release/*
cd build_release
Expand Down
2 changes: 1 addition & 1 deletion pico-fido-patch-vidpid.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ fi
LITTLE_VID="\x${VID:2:2}\x${VID:0:2}"
LITTLE_PID="\x${PID:2:2}\x${PID:0:2}"

perl -pi -e "s/\xfe\xca\x31\x42\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01/$LITTLE_VID$LITTLE_PID\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01/" $UF2_FILE_OF
perl -pi -e "s/[\x00-\xff]{4}\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01\x00\x00/$LITTLE_VID$LITTLE_PID\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01\x00\x00/" $UF2_FILE_OF

echo "Done!"
echo ""
Expand Down
9 changes: 6 additions & 3 deletions src/fido/cbor.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef ENABLE_EMULATION
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "hid/ctap_hid.h"
Expand Down Expand Up @@ -122,14 +122,17 @@ void cbor_thread() {
}
else {
res_APDU[0] = apdu.sw;
apdu.sw = 0;
//apdu.sw = 0;
}

finished_data_size = res_APDU_size + 1;

uint32_t flag = EV_EXEC_FINISHED;
queue_add_blocking(&card_to_usb_q, &flag);
}
#ifdef ESP_PLATFORM
vTaskDelete(NULL);
#endif
}
#endif

Expand All @@ -139,7 +142,7 @@ int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
cmd = last_cmd;
res_APDU = ctap_resp->init.data + 1;
res_APDU_size = 0;
return 1;
return 2; // CBOR processing
}

CborError COSE_key_params(int crv,
Expand Down
26 changes: 15 additions & 11 deletions src/fido/cbor_client_pin.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef ESP_PLATFORM
#include "common.h"
#else
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#endif
#include "mbedtls/ecp.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/sha256.h"
#include "mbedtls/hkdf.h"
#include "cbor.h"
#include "ctap.h"
#include "ctap2_cbor.h"
#ifndef ENABLE_EMULATION
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
Expand Down Expand Up @@ -169,7 +173,7 @@ int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
int resetPinUvAuthToken() {
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
flash_write_data_to_file(ef_authtoken, t, sizeof(t));
file_put_data(ef_authtoken, t, sizeof(t));
paut.permissions = 0;
paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken);
Expand Down Expand Up @@ -332,7 +336,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
}
CBOR_PARSE_MAP_END(map, 1);

cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, res_APDU + 1, CTAP_MAX_PACKET_SIZE, 0);
if (subcommand == 0x0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
Expand Down Expand Up @@ -417,7 +421,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
hsh[0] = MAX_PIN_RETRIES;
hsh[1] = pin_len;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 2);
flash_write_data_to_file(ef_pin, hsh, 2 + 16);
file_put_data(ef_pin, hsh, 2 + 16);
low_flash_available();
goto err; //No return
}
Expand Down Expand Up @@ -464,7 +468,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
uint8_t pin_data[18];
memcpy(pin_data, file_get_data(ef_pin), 18);
pin_data[0] -= 1;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64];
Expand All @@ -489,7 +493,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
}
}
pin_data[0] = MAX_PIN_RETRIES;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
new_pin_mismatches = 0;
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
Expand Down Expand Up @@ -520,12 +524,12 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 16) == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
flash_write_data_to_file(ef_pin, hsh, 2 + 16);
file_put_data(ef_pin, hsh, 2 + 16);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
uint8_t *tmp = (uint8_t *) calloc(1, file_get_size(ef_minpin));
memcpy(tmp, file_get_data(ef_minpin), file_get_size(ef_minpin));
tmp[1] = 0;
flash_write_data_to_file(ef_minpin, tmp, file_get_size(ef_minpin));
file_put_data(ef_minpin, tmp, file_get_size(ef_minpin));
free(tmp);
}
low_flash_available();
Expand Down Expand Up @@ -573,7 +577,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
uint8_t pin_data[18];
memcpy(pin_data, file_get_data(ef_pin), 18);
pin_data[0] -= 1;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64], poff = (pinUvAuthProtocol - 1) * IV_SIZE;
Expand All @@ -599,7 +603,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
}
pin_data[0] = MAX_PIN_RETRIES;
new_pin_mismatches = 0;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, sizeof(pin_data));
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) {
Expand Down Expand Up @@ -628,7 +632,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
resp_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1);
err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(newPinEnc);
Expand Down
16 changes: 8 additions & 8 deletions src/fido/cbor_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ int cbor_config(const uint8_t *data, size_t len) {
}
CBOR_PARSE_MAP_END(map, 1);

cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, res_APDU + 1, CTAP_MAX_PACKET_SIZE, 0);

if (pinUvAuthParam.present == false) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
Expand Down Expand Up @@ -142,9 +142,9 @@ int cbor_config(const uint8_t *data, size_t len) {
if (has_keydev_dec == false) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
flash_write_data_to_file(ef_keydev, keydev_dec, sizeof(keydev_dec));
file_put_data(ef_keydev, keydev_dec, sizeof(keydev_dec));
mbedtls_platform_zeroize(keydev_dec, sizeof(keydev_dec));
flash_write_data_to_file(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
file_put_data(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
else if (vendorCommandId == CTAP_CONFIG_AUT_ENABLE) {
Expand Down Expand Up @@ -178,10 +178,10 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}

flash_write_data_to_file(ef_keydev_enc, key_dev_enc, sizeof(key_dev_enc));
file_put_data(ef_keydev_enc, key_dev_enc, sizeof(key_dev_enc));
mbedtls_platform_zeroize(key_dev_enc, sizeof(key_dev_enc));
flash_write_data_to_file(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
file_put_data(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
else {
Expand Down Expand Up @@ -216,7 +216,7 @@ int cbor_config(const uint8_t *data, size_t len) {
data + 2 + m * 32,
0);
}
flash_write_data_to_file(ef_minpin, data, 2 + minPinLengthRPIDs_len * 32);
file_put_data(ef_minpin, data, 2 + minPinLengthRPIDs_len * 32);
low_flash_available();
goto err; //No return
}
Expand All @@ -228,7 +228,7 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
resp_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1);

err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
Expand Down
Loading

0 comments on commit d2c25b6

Please sign in to comment.