From ff4026f64a99be1a96f18d98164da06e78ceb06d Mon Sep 17 00:00:00 2001 From: Fabrice Bellamy Date: Fri, 23 Jan 2026 00:02:39 +0100 Subject: [PATCH 1/4] Enable the feature to chnage min pin length when a pin is defined --- shell.nix | 5 +++++ src/lib/views/configView.svelte | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/shell.nix b/shell.nix index 2f82655..2f20d81 100644 --- a/shell.nix +++ b/shell.nix @@ -15,6 +15,7 @@ let webkitgtk_4_1 pcsclite hidapi + mesa ]; packages = with pkgs; [ @@ -58,6 +59,10 @@ pkgs.mkShell { export LD_LIBRARY_PATH=${pkgs.lib.makeLibraryPath libraries}:$LD_LIBRARY_PATH export XDG_DATA_DIRS=$GSETTINGS_SCHEMAS_PATH:$XDG_DATA_DIRS + # Try to uncomment the following lines if you encounter EGL_BAD_PARAMETER errors: + # export LIBGL_ALWAYS_SOFTWARE=1 + # export WEBKIT_DISABLE_COMPOSITING_MODE=1 + echo "Nix development environment loaded!" echo "Available tools: rustc, cargo, deno, node, tauri" ''; diff --git a/src/lib/views/configView.svelte b/src/lib/views/configView.svelte index 9b6d310..79b89f7 100644 --- a/src/lib/views/configView.svelte +++ b/src/lib/views/configView.svelte @@ -49,14 +49,14 @@ -
+

Minimum PIN Length

Current: {device.fidoInfo?.minPinLength || 4} characters

- +
From abf93f6e55e84dc7b5e288df40d008f803f504d0 Mon Sep 17 00:00:00 2001 From: Fabrice Bellamy Date: Fri, 23 Jan 2026 00:15:32 +0100 Subject: [PATCH 2/4] fix minPinDialog submit button onclick handler --- src/lib/components/dialogs/minPinDialog.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/dialogs/minPinDialog.svelte b/src/lib/components/dialogs/minPinDialog.svelte index 5932590..1a64d13 100644 --- a/src/lib/components/dialogs/minPinDialog.svelte +++ b/src/lib/components/dialogs/minPinDialog.svelte @@ -72,7 +72,7 @@ (configState.minPinDialogOpen = false)} >Cancel - configState.handleMinPinChange()} >Update From ecae9ee5afa78c7ee67408c9f2e8a31b70af4d8e Mon Sep 17 00:00:00 2001 From: Fabrice Bellamy Date: Fri, 23 Jan 2026 00:34:16 +0100 Subject: [PATCH 3/4] implement custom HidTransport to send set_min_pin_length command because ctap-hid-fido2 set_min_pin_length has a bug --- src-tauri/src/fido/hid.rs | 78 +++++++++++++++++++++++++++++++++++++++ src-tauri/src/fido/mod.rs | 38 ++++++++++++++++--- 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src-tauri/src/fido/hid.rs b/src-tauri/src/fido/hid.rs index e54b7ac..b653a86 100644 --- a/src-tauri/src/fido/hid.rs +++ b/src-tauri/src/fido/hid.rs @@ -372,4 +372,82 @@ impl HidTransport { Ok(()) } + + /// Send authenticatorConfig command to set minimum PIN length. + /// + /// This bypasses the ctap-hid-fido2 library which has a bug where it sends + /// CBOR map keys out of order (0x01, 0x03, 0x04, 0x02) instead of the required + /// ascending order (0x01, 0x02, 0x03, 0x04). The pico-fido firmware strictly + /// enforces canonical CBOR ordering per CTAP2 spec. + pub fn send_config_set_min_pin_length( + &self, + pin_token: &[u8], + new_min_pin_length: u8, + ) -> Result<(), PFError> { + log::debug!( + "Sending setMinPINLength config command (new length: {})...", + new_min_pin_length + ); + + // Build subCommandParams (Key 0x02): { 0x01: newMinPINLength } + let mut sub_params_map = BTreeMap::new(); + sub_params_map.insert( + Value::Integer(ConfigSubCommandParam::NewMinPinLength as i128), + Value::Integer(new_min_pin_length as i128), + ); + let sub_params = Value::Map(sub_params_map); + let sub_params_bytes = to_vec(&sub_params).map_err(|e| PFError::Io(e.to_string()))?; + + // Build HMAC message for signing + // Per FIDO 2.1 spec: authenticate(pinUvAuthToken, 32×0xff || 0x0d || uint8(subCommand) || subCommandParams) + let mut message = vec![0xff; 32]; + message.push(CtapCommand::Config as u8); // 0x0d + message.push(ConfigSubCommand::SetMinPinLength as u8); // 0x03 + message.extend(&sub_params_bytes); + + // Sign using provided PIN token (Protocol 1 uses HMAC-SHA256, first 16 bytes) + use ring::hmac; + let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, pin_token); + let sig = hmac::sign(&hmac_key, &message); + let pin_auth = sig.as_ref()[0..16].to_vec(); + + // Build full authenticatorConfig map with keys in ASCENDING ORDER + // This is critical - the firmware parser rejects out-of-order keys with CTAP2_ERR_INVALID_CBOR + let mut config_map = BTreeMap::new(); + config_map.insert( + Value::Integer(ConfigParam::SubCommand as i128), // 0x01 + Value::Integer(ConfigSubCommand::SetMinPinLength as i128), // 0x03 + ); + config_map.insert( + Value::Integer(ConfigParam::SubCommandParams as i128), // 0x02 + sub_params, + ); + config_map.insert( + Value::Integer(ConfigParam::PinUvAuthProtocol as i128), // 0x03 + Value::Integer(1), // PIN protocol version 1 + ); + config_map.insert( + Value::Integer(ConfigParam::PinUvAuthParam as i128), // 0x04 + Value::Bytes(pin_auth), + ); + + let config_payload_cbor = + to_vec(&Value::Map(config_map)).map_err(|e| PFError::Io(e.to_string()))?; + + // Prepend CTAP command byte + let mut payload = vec![CtapCommand::Config as u8]; + payload.extend(config_payload_cbor); + + // Send via HID + self.send_cbor(CTAPHID_CBOR, &payload).map_err(|e| { + log::error!("Failed to send setMinPINLength config: {}", e); + PFError::Device(format!("setMinPINLength failed: {}", e)) + })?; + + log::info!( + "Successfully set minimum PIN length to {}", + new_min_pin_length + ); + Ok(()) + } } diff --git a/src-tauri/src/fido/mod.rs b/src-tauri/src/fido/mod.rs index 6c746e4..0c68d1b 100644 --- a/src-tauri/src/fido/mod.rs +++ b/src-tauri/src/fido/mod.rs @@ -79,13 +79,39 @@ pub(crate) fn set_min_pin_length( current_pin: String, min_pin_length: u8, ) -> Result { - let cfg = Cfg::init(); - let device = FidoKeyHidFactory::create(&cfg) - .map_err(|e| format!("Failed to connect to FIDO device: {:?}", e))?; + log::info!("Starting set_min_pin_length (custom implementation)..."); - device - .set_min_pin_length(min_pin_length, Some(¤t_pin)) - .map_err(|e| format!("Failed to set minimum PIN length: {:?}", e))?; + // 1. Obtain PIN token using the library handle + let pin_token = { + let cfg = Cfg::init(); + let device = FidoKeyHidFactory::create(&cfg) + .map_err(|e| format!("Could not connect to FIDO device: {:?}", e))?; + + use ctap_hid_fido2::fidokey::pin::Permission; + // Obtain a token with AuthenticatorConfiguration permission (CTAP 2.1) + match device.get_pinuv_auth_token_with_permission( + ¤t_pin, + Permission::AuthenticatorConfiguration, + ) { + Ok(token) => { + log::debug!("Successfully obtained PIN token with ACFG permission."); + token.key + } + Err(e) => { + log::error!("Failed to get PIN token with ACFG permission: {:?}", e); + return Err(format!("Failed to obtain PIN token: {:?}", e)); + } + } + // Library handle 'device' is dropped here, closing the HID session. + }; + + // 2. Open custom HidTransport and send command using the token because ctap-hid-fido2 has a bug where it sends CBOR map keys out of order (0x01, 0x03, 0x04, 0x02) instead of the required ascending order (0x01, 0x02, 0x03, 0x04). The pico-fido firmware strictly requires ascending order. + let transport = + HidTransport::open().map_err(|e| format!("Could not open HID transport: {}", e))?; + + transport + .send_config_set_min_pin_length(&pin_token, min_pin_length) + .map_err(|e| format!("Failed to set minimum PIN length: {}", e))?; Ok(format!( "Minimum PIN length successfully set to {}", From 894d243cf56581db9498cb7376c122d680c55726 Mon Sep 17 00:00:00 2001 From: Fabrice Bellamy Date: Fri, 23 Jan 2026 01:19:45 +0100 Subject: [PATCH 4/4] better error message when trying to decrease min pin length --- src-tauri/src/fido/hid.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src-tauri/src/fido/hid.rs b/src-tauri/src/fido/hid.rs index b653a86..5ea7ead 100644 --- a/src-tauri/src/fido/hid.rs +++ b/src-tauri/src/fido/hid.rs @@ -439,15 +439,27 @@ impl HidTransport { payload.extend(config_payload_cbor); // Send via HID - self.send_cbor(CTAPHID_CBOR, &payload).map_err(|e| { - log::error!("Failed to send setMinPINLength config: {}", e); - PFError::Device(format!("setMinPINLength failed: {}", e)) - })?; + match self.send_cbor(CTAPHID_CBOR, &payload) { + Ok(_) => { + log::info!( + "Successfully set minimum PIN length to {}", + new_min_pin_length + ); + Ok(()) + } + Err(e) => { + let err_str = e.to_string(); + log::error!("Failed to send setMinPINLength config: {}", err_str); - log::info!( - "Successfully set minimum PIN length to {}", - new_min_pin_length - ); - Ok(()) + // Check for PIN policy violation (0x37) - cannot decrease min PIN length + if err_str.contains("0x37") { + return Err(PFError::Device( + "Cannot decrease minimum PIN length. The FIDO2 security policy only allows increasing the minimum PIN length, not decreasing it. A device reset is required to lower the minimum.".into() + )); + } + + Err(PFError::Device(format!("setMinPINLength failed: {}", e))) + } + } } }