26 Commits

Author SHA1 Message Date
Pol Henarejos
804ee68e86 Remove non-standard MAKE CREDENTIAL step.
It may collide with other userName and the purpose is achieved cleaner via Rescue interface.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-08 10:46:51 +01:00
Pol Henarejos
fe49149d86 Update README with up-to-date info.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-06 21:20:04 +01:00
Pol Henarejos
81d97f1a18 Upgrade to v7.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:56:09 +01:00
Pol Henarejos
d16016cf1e Upgrade Pico Keys SDK to v8.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:51:11 +01:00
Pol Henarejos
2e0333677b Fix button logic.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:39:46 +01:00
Pol Henarejos
becdc94339 Disable button press by default since LED may not be properly configured until it is commissioned.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:37:30 +01:00
Pol Henarejos
bd499ae1d4 Remove print
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:37:27 +01:00
Pol Henarejos
5fc84d7097 Reset internal state of GA to avoid phantom requests on GNA.
When a previous GA had more than 1 credential, it stored the full list in the internal state. Later, if a GA had only 1 credential, subsequent GNA returned older state of previous non-related GA.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 12:36:44 +01:00
Pol Henarejos
ac7e34522a Fixed resident credential storage when two userId have the same prefix.
Added a specific test for this case.

Fixes #241.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 12:34:04 +01:00
Pol Henarejos
70dec5596a Fix build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-29 20:36:37 +01:00
Pol Henarejos
2331dcb3ec Blink led three times to acknowledge proper commissioning.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-29 20:16:22 +01:00
Pol Henarejos
42b8b0af5f Fix pimoroni led
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-27 22:04:03 +01:00
Pol Henarejos
ceb54d9a08 Move pointer
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-27 16:03:07 +01:00
Pol Henarejos
527943fbba Releaser is available up to 6.7.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-26 19:53:46 +01:00
Pol Henarejos
7dddfd971e Build only necessary boards
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-11 20:01:08 +01:00
Pol Henarejos
29f942dab9 Update pointer
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-11 15:42:47 +01:00
Pol Henarejos
aa9df892d3 Revert "Move EDDSA to another branch."
This reverts commit 1867f0330f.
2025-12-11 15:41:47 +01:00
Pol Henarejos
7ac2ce30f0 Revert "Move other curves to another branch."
This reverts commit 46720fb387.
2025-12-11 15:40:16 +01:00
Pol Henarejos
e86862033c Revert "Move enterprise attestation to another branch."
This reverts commit 1d21d93b74.
2025-12-11 15:40:10 +01:00
Pol Henarejos
ae36143498 Revert "Move Secure Boot to another branch."
This reverts commit d90dbb6c5f.
2025-12-11 15:39:57 +01:00
Pol Henarejos
d90dbb6c5f Move Secure Boot to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 21:39:14 +01:00
Pol Henarejos
1d21d93b74 Move enterprise attestation to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 21:39:10 +01:00
Pol Henarejos
8b9be258de Fix applet cmp
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 19:15:35 +01:00
Pol Henarejos
46720fb387 Move other curves to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 18:52:13 +01:00
Pol Henarejos
1867f0330f Move EDDSA to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 15:56:31 +01:00
Pol Henarejos
bb542e3b83 Add is_gpg flag for fido2.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-07 20:34:42 +01:00
11 changed files with 56 additions and 66 deletions

View File

@@ -34,7 +34,7 @@ jobs:
- name: Delete private key
run: rm private.pem
- name: Update nightly release
uses: pyTooling/Actions/releaser@main
uses: pyTooling/Actions/releaser@v6.7.0
with:
tag: nightly-${{ matrix.refs }}
rm: true

View File

@@ -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.

View File

@@ -1,48 +1,25 @@
#!/bin/bash
VERSION_MAJOR="7"
VERSION_MINOR="0"
NO_EDDSA=0
VERSION_MINOR="2"
SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}"
#if ! [[ -z "${GITHUB_SHA}" ]]; then
# SUFFIX="${SUFFIX}.${GITHUB_SHA}"
#fi
if [[ $1 == "--no-eddsa" ]]; then
NO_EDDSA=1
echo "Skipping EDDSA build"
fi
mkdir -p build_release
mkdir -p release
mkdir -p release_eddsa
rm -rf -- release/*
if [[ $NO_EDDSA -eq 0 ]]; then
rm -rf -- release_eddsa/*
fi
cd build_release
PICO_SDK_PATH="${PICO_SDK_PATH:-../../pico-sdk}"
SECURE_BOOT_PKEY="${SECURE_BOOT_PKEY:-../../ec_private_key.pem}"
board_dir=${PICO_SDK_PATH}/src/boards/include/boards
for board in "$board_dir"/*
boards=("pico" "pico2")
for board_name in "${boards[@]}"
do
board_name="$(basename -- "$board" .h)"
rm -rf -- ./*
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=${SECURE_BOOT_PKEY}
make -j`nproc`
mv pico_fido.uf2 ../release/pico_fido_$board_name-$SUFFIX.uf2
done
# Build with EDDSA
if [[ $NO_EDDSA -eq 0 ]]; then
for board in "$board_dir"/*
do
board_name="$(basename -- "$board" .h)"
rm -rf -- ./*
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=${SECURE_BOOT_PKEY} -DENABLE_EDDSA=1
make -j`nproc`
mv pico_fido.uf2 ../release_eddsa/pico_fido_$board_name-$SUFFIX-eddsa1.uf2
done
fi

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -23,6 +23,8 @@
#include "asn1.h"
#include "management.h"
bool is_gpg = true;
int man_process_apdu();
int man_unload();
@@ -44,6 +46,7 @@ int man_select(app_t *a, uint8_t force) {
init_otp();
#endif
}
is_gpg = false;
return PICOKEY_OK;
}

View File

@@ -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)

View File

@@ -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()