support pico-fido version 7.4

This commit is contained in:
Fabrice Bellamy
2026-03-01 14:15:11 +01:00
parent c0412aff74
commit 081c45b3ee
7 changed files with 632 additions and 54 deletions
+97
View File
@@ -182,6 +182,54 @@ impl VendorConfigCommand {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FidoCertification {
AuthEncryption,
AuthEncryptionLock,
EnterpriseAttestation,
PinComplexity,
PhysicalVidPid,
LedBrightness,
LedGpio,
PhysicalOptions,
}
impl FidoCertification {
pub fn from_u64(val: u64) -> Option<Self> {
match val {
0x03E43F56B34285E2 => Some(Self::AuthEncryption),
0x1831A40F04A25ED9 => Some(Self::AuthEncryptionLock),
0x66F2A674C29A8DCF => Some(Self::EnterpriseAttestation),
0x6C07D70FE96C3897 => Some(Self::PinComplexity),
0x6FCB19B0CBE3ACFA => Some(Self::PhysicalVidPid),
0x76A85945985D02FD => Some(Self::LedBrightness),
0x7B392A394DE9F948 => Some(Self::LedGpio),
0x269F3B09ECEB805F => Some(Self::PhysicalOptions),
_ => None,
}
}
pub fn from_str(val: &str) -> Option<Self> {
let val = val.strip_prefix("0x").unwrap_or(val);
u64::from_str_radix(val, 16).ok().and_then(Self::from_u64)
}
}
impl fmt::Display for FidoCertification {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AuthEncryption => write!(f, "Auth Encryption"),
Self::AuthEncryptionLock => write!(f, "Auth Encryption (Lock)"),
Self::EnterpriseAttestation => write!(f, "Enterprise Attestation"),
Self::PinComplexity => write!(f, "PIN Complexity"),
Self::PhysicalVidPid => write!(f, "Physical VID/PID"),
Self::LedBrightness => write!(f, "LED Brightness"),
Self::LedGpio => write!(f, "LED GPIO"),
Self::PhysicalOptions => write!(f, "Physical Options"),
}
}
}
impl fmt::Display for VendorConfigCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -288,6 +336,55 @@ pub enum CoseAlgorithm {
ESB512 = -268,
}
impl CoseAlgorithm {
pub fn from_i128(val: i128) -> Option<Self> {
match val as i32 {
-7 => Some(Self::ES256),
-8 => Some(Self::EdDSA),
-9 => Some(Self::ESP256),
-19 => Some(Self::Ed25519),
-25 => Some(Self::EcdhEsHkdf256),
-35 => Some(Self::ES384),
-36 => Some(Self::ES512),
-47 => Some(Self::ES256K),
-51 => Some(Self::ESP384),
-52 => Some(Self::ESP512),
-53 => Some(Self::Ed448),
-257 => Some(Self::RS256),
-258 => Some(Self::RS384),
-259 => Some(Self::RS512),
-265 => Some(Self::ESB256),
-267 => Some(Self::ESB384),
-268 => Some(Self::ESB512),
_ => None,
}
}
}
impl fmt::Display for CoseAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ES256 => write!(f, "ES256"),
Self::EdDSA => write!(f, "EdDSA"),
Self::ESP256 => write!(f, "ESP256"),
Self::Ed25519 => write!(f, "Ed25519"),
Self::EcdhEsHkdf256 => write!(f, "ECDH-ES-HKDF-256"),
Self::ES384 => write!(f, "ES384"),
Self::ES512 => write!(f, "ES512"),
Self::ES256K => write!(f, "ES256K"),
Self::ESP384 => write!(f, "ESP384"),
Self::ESP512 => write!(f, "ESP512"),
Self::Ed448 => write!(f, "Ed448"),
Self::RS256 => write!(f, "RS256"),
Self::RS384 => write!(f, "RS384"),
Self::RS512 => write!(f, "RS512"),
Self::ESB256 => write!(f, "ESB256"),
Self::ESB384 => write!(f, "ESB384"),
Self::ESB512 => write!(f, "ESB512"),
}
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoseCurve {
+406 -31
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -228,8 +228,8 @@ pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
Ok(FullDeviceStatus {
info: DeviceInfo {
serial: serial_str,
flash_used: used / 1024,
flash_total: total / 1024,
flash_used: Some(used / 1024),
flash_total: Some(total / 1024),
firmware_version: format!("{}.{}", version_major, version_minor),
},
config,
+15 -5
View File
@@ -10,8 +10,8 @@ struct PForgeState {
#[serde(rename_all = "camelCase")]
pub struct DeviceInfo {
pub serial: String,
pub flash_used: u32,
pub flash_total: u32,
pub flash_used: Option<u32>,
pub flash_total: Option<u32>,
pub firmware_version: String,
}
@@ -74,11 +74,21 @@ pub struct FidoDeviceInfo {
pub extensions: Vec<String>,
pub aaguid: String,
pub options: std::collections::HashMap<String, bool>,
pub max_msg_size: i32,
pub max_msg_size: i128,
pub pin_protocols: Vec<u32>,
// pub remaining_disc_creds: u32,
pub min_pin_length: u32,
pub remaining_discoverable_credentials: Option<i128>,
pub min_pin_length: i128,
pub firmware_version: String,
/// Supported vendor config commands (human-readable names), parsed from CTAP GetInfo key 0x13
pub vendor_config_commands: Vec<String>,
/// Device certifications, parsed from CTAP GetInfo key 0x15
pub certifications: std::collections::HashMap<String, bool>,
pub max_credential_count_in_list: Option<i128>,
pub max_credential_id_length: Option<i128>,
pub algorithms: Vec<String>,
pub max_serialized_large_blob_array: Option<i128>,
pub force_pin_change: Option<bool>,
pub max_cred_blob_length: Option<i128>,
}
#[derive(Debug, Clone, Serialize)]
+32 -5
View File
@@ -15,6 +15,33 @@ enum DialogPhase {
Error(String),
}
fn render_error_message(msg: String) -> impl IntoElement {
let troubleshooting_phrase = "troubleshooting guide";
let url = "https://github.com/librekeys/picoforge/wiki/Troubleshooting#1-my-key-is-not-detected-by-picoforge-or-picoforge-displays-a-device-status-of-online---fido-and-there-are-some-settings-that-i-cannot-configure";
if msg.contains(troubleshooting_phrase) {
v_flex()
.child("The device firmware does not support being configured in fido only communication mode.")
.child(
h_flex()
.gap_1()
.child("Have a look at the")
.child(
div()
.text_color(rgb(0x3b82f6))
.cursor_pointer()
.on_mouse_down(MouseButton::Left, move |_, _, cx| {
cx.open_url(url);
})
.child(troubleshooting_phrase.to_string()),
)
.child("to fix this"),
)
} else {
div().child(msg)
}
}
pub struct PinPromptContent {
phase: DialogPhase,
title: SharedString,
@@ -120,7 +147,7 @@ impl Render for PinPromptContent {
.bg(cx.theme().danger.opacity(0.1))
.text_color(cx.theme().danger)
.text_sm()
.child(err_msg.clone()),
.child(render_error_message(err_msg.clone())),
)
.child(Input::new(&pin_input))
.child(
@@ -329,7 +356,7 @@ impl Render for ConfirmContent {
.bg(cx.theme().danger.opacity(0.1))
.text_color(cx.theme().danger)
.text_sm()
.child(err_msg.clone()),
.child(render_error_message(err_msg.clone())),
)
.child(
h_flex()
@@ -551,7 +578,7 @@ impl Render for ChangePinContent {
.bg(cx.theme().danger.opacity(0.1))
.text_color(cx.theme().danger)
.text_sm()
.child(err_msg.clone()),
.child(render_error_message(err_msg.clone())),
)
.child(
v_flex()
@@ -859,7 +886,7 @@ impl Render for SetPinContent {
.bg(cx.theme().danger.opacity(0.1))
.text_color(cx.theme().danger)
.text_sm()
.child(err_msg.clone()),
.child(render_error_message(err_msg.clone())),
)
.child(
v_flex()
@@ -1094,7 +1121,7 @@ impl Render for StatusContent {
.bg(cx.theme().danger.opacity(0.1))
.text_color(cx.theme().danger)
.text_sm()
.child(err_msg.clone()),
.child(render_error_message(err_msg.clone())),
)
.child(
h_flex()
+14 -3
View File
@@ -228,11 +228,12 @@ impl ConfigView {
cx.notify();
let entity = cx.entity().downgrade();
let method_clone = method.clone();
self._task = Some(cx.spawn(async move |_, cx| {
let result = cx
.background_executor()
.spawn(async move { io::write_config(changes, method, pin) })
.spawn(async move { io::write_config(changes, method_clone, pin) })
.await;
let new_status_result = if result.is_ok() {
@@ -289,15 +290,25 @@ impl ConfigView {
}
Err(e) => {
log::error!("Error saving config: {}", e);
let mut err_msg = format!("Failed to apply configuration: {}", e);
// Special case for FIDO 0x3E error (Invalid Subcommand)
// This happens when the firmware is too old to support config over FIDO
if method == crate::device::types::DeviceMethod::Fido && err_msg.contains("0x3E")
{
err_msg = "The device firmware does not support being configured in fido only communication mode. \nHave a look at the troubleshooting guide to fix this".to_string();
}
match &dialog_handle {
StatusDialogHandle::Pin(dh) => {
let _ = dh.update(cx, |d, cx| {
d.set_error(format!("Failed to apply: {}", e), cx);
d.set_error(err_msg, cx);
});
}
StatusDialogHandle::Status(dh) => {
let _ = dh.update(cx, |d, cx| {
d.set_error(format!("Failed to apply: {}", e), cx);
d.set_error(err_msg, cx);
});
}
}
+66 -8
View File
@@ -1,6 +1,7 @@
use crate::device::types::DeviceMethod;
use crate::ui::components::{card::Card, page_view::PageView, tag::Tag};
use crate::ui::types::GlobalDeviceState;
use gpui::prelude::FluentBuilder;
use gpui::*;
use gpui_component::StyledExt;
use gpui_component::{Icon, IconName, Theme, h_flex, progress::Progress, v_flex};
@@ -86,8 +87,6 @@ impl HomeView {
let info = &status.info;
let config = &status.config;
let flash_percent = (info.flash_used as f32 / info.flash_total as f32) * 100.0;
Card::new()
.title("Device Information")
.icon(Icon::default().path("icons/cpu.svg"))
@@ -137,12 +136,25 @@ impl HomeView {
.text_color(theme.muted_foreground)
.child("Flash Memory"),
)
.child(div().text_color(theme.foreground).child(format!(
"{:.0} / {:.0} KB",
info.flash_used, info.flash_total
))),
.child(div().text_color(theme.foreground).child(
if let (Some(used), Some(total)) =
(info.flash_used, info.flash_total)
{
format!("{:.0} / {:.0} KB", used, total)
} else {
"Not Available".to_string()
},
)),
)
.child(Progress::new().value(flash_percent)),
.when(
info.flash_used.is_some() && info.flash_total.is_some(),
|this| {
let used = info.flash_used.unwrap();
let total = info.flash_total.unwrap();
let flash_percent = (used as f32 / total as f32) * 100.0;
this.child(Progress::new().value(flash_percent))
},
),
),
)
}
@@ -190,10 +202,56 @@ impl HomeView {
},
theme,
false,
)),
))
.when(fido.remaining_discoverable_credentials.is_some(), |this| {
this.child(Self::render_kv(
"Remaining Credentials",
fido.remaining_discoverable_credentials
.unwrap_or(0)
.to_string(),
theme,
false,
))
}),
)
.child(div().h_px().bg(theme.border))
.child(Self::render_kv("AAGUID", fido.aaguid.clone(), theme, true))
// .when(!fido.vendor_config_commands.is_empty(), |this| {
// this.child(div().h_px().bg(theme.border)).child(
// v_flex()
// .gap_2()
// .child(
// div()
// .text_sm()
// .text_color(theme.muted_foreground)
// .child("Vendor Config Commands"),
// )
// .child(
// h_flex().gap_2().flex_wrap().children(
// fido.vendor_config_commands
// .iter()
// .map(|cmd| Tag::new(cmd.clone()).active(true)),
// ),
// ),
// )
// })
// .when(!fido.certifications.is_empty(), |this| {
// this.child(div().h_px().bg(theme.border)).child(
// v_flex()
// .gap_2()
// .child(
// div()
// .text_sm()
// .text_color(theme.muted_foreground)
// .child("Certifications"),
// )
// .child(h_flex().gap_2().flex_wrap().children(
// fido.certifications.iter().map(|(name, _enabled)| {
// Tag::new(name.clone()).active(true)
// }),
// )),
// )
// })
.into_any_element()
} else {
div()