From 0d837a19b4083e8ada3fcbd5b3b3c20f302bb888 Mon Sep 17 00:00:00 2001 From: Suyog Tandel Date: Wed, 28 Jan 2026 17:29:07 +0530 Subject: [PATCH] chore: change code formatting, use space for tabs and indents instead of tabs --- Cargo.lock | 31 -- Cargo.toml | 4 +- rustfmt.toml | 2 +- src/device/error.rs | 68 +-- src/device/fido/constants.rs | 446 +++++++-------- src/device/fido/hid.rs | 868 ++++++++++++++--------------- src/device/fido/mod.rs | 792 +++++++++++++-------------- src/device/io.rs | 52 +- src/device/logging.rs | 8 +- src/device/rescue/constants.rs | 98 ++-- src/device/rescue/mod.rs | 684 +++++++++++------------ src/device/types.rs | 102 ++-- src/main.rs | 91 ++-- src/ui/assets.rs | 26 +- src/ui/colors.rs | 2 +- src/ui/mod.rs | 4 +- src/ui/rootview.rs | 244 ++++----- src/ui/views/about.rs | 6 +- src/ui/views/config.rs | 12 +- src/ui/views/home.rs | 962 ++++++++++++++++----------------- src/ui/views/logs.rs | 6 +- src/ui/views/passkeys.rs | 12 +- src/ui/views/security.rs | 12 +- 23 files changed, 2248 insertions(+), 2284 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be4e9dd..772b466 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1469,15 +1469,6 @@ dependencies = [ "dirs-sys 0.4.1", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys 0.5.0", -] - [[package]] name = "dirs-sys" version = "0.3.7" @@ -2416,17 +2407,6 @@ dependencies = [ "zed-sum-tree", ] -[[package]] -name = "gpui-component-assets" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5842ea3f1e596fbc611894dcc0266df7125bc226f603ca313f5460b700503564" -dependencies = [ - "anyhow", - "gpui", - "rust-embed", -] - [[package]] name = "gpui-component-macros" version = "0.5.0" @@ -4304,7 +4284,6 @@ dependencies = [ "directories", "gpui", "gpui-component", - "gpui-component-assets", "hex", "hidapi", "log", @@ -4986,7 +4965,6 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "shellexpand", "syn 2.0.114", "walkdir", ] @@ -5503,15 +5481,6 @@ dependencies = [ "digest", ] -[[package]] -name = "shellexpand" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb" -dependencies = [ - "dirs 6.0.0", -] - [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index bf8285f..055b1fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ license = "AGPL-3.0" edition = "2024" [dependencies] -tokio = { version = "1.0", features = ["full"] } +tokio = { version = "1.49", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" log = "0.4" # Logging facade @@ -30,8 +30,6 @@ ring = "0.17" # For signing fido2 messages with pin token # For Application UI: gpui = "0.2.2" gpui-component = "0.5.0" -# Optional, for default bundled assets -gpui-component-assets = "0.5.0" rust-embed = "8.11.0" [profile.dev] diff --git a/rustfmt.toml b/rustfmt.toml index 69d92fe..a3a65db 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ edition = "2024" -hard_tabs = true +hard_tabs = false tab_spaces = 4 max_width = 100 diff --git a/src/device/error.rs b/src/device/error.rs index 1dee0ed..539e561 100644 --- a/src/device/error.rs +++ b/src/device/error.rs @@ -1,44 +1,44 @@ /// Custom error types for Pico Forge application. #[derive(Debug, thiserror::Error)] pub enum PFError { - #[error("No device found")] - NoDevice, - #[error("PCSC Error: {0}")] - Pcsc(#[from] pcsc::Error), - #[error("IO/Hex Error: {0}")] - Io(String), - #[error("Device Error: {0}")] - Device(String), + #[error("No device found")] + NoDevice, + #[error("PCSC Error: {0}")] + Pcsc(#[from] pcsc::Error), + #[error("IO/Hex Error: {0}")] + Io(String), + #[error("Device Error: {0}")] + Device(String), } // Allow error to be serialized to string for Tauri impl serde::Serialize for PFError { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut state = serializer.serialize_struct("PFError", 2)?; - match self { - PFError::NoDevice => { - state.serialize_field("type", "NoDevice")?; - state.serialize_field("message", "No device found")?; - } - PFError::Pcsc(err) => { - state.serialize_field("type", "Pcsc")?; - state.serialize_field("message", &err.to_string())?; - } - PFError::Io(msg) => { - state.serialize_field("type", "Io")?; - state.serialize_field("message", msg)?; - } - PFError::Device(msg) => { - state.serialize_field("type", "Device")?; - state.serialize_field("message", msg)?; - } - } - state.end() - } + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut state = serializer.serialize_struct("PFError", 2)?; + match self { + PFError::NoDevice => { + state.serialize_field("type", "NoDevice")?; + state.serialize_field("message", "No device found")?; + } + PFError::Pcsc(err) => { + state.serialize_field("type", "Pcsc")?; + state.serialize_field("message", &err.to_string())?; + } + PFError::Io(msg) => { + state.serialize_field("type", "Io")?; + state.serialize_field("message", msg)?; + } + PFError::Device(msg) => { + state.serialize_field("type", "Device")?; + state.serialize_field("message", msg)?; + } + } + state.end() + } } // pub type Result = std::result::Result; diff --git a/src/device/fido/constants.rs b/src/device/fido/constants.rs index 5be640c..0c4801d 100644 --- a/src/device/fido/constants.rs +++ b/src/device/fido/constants.rs @@ -6,358 +6,358 @@ use std::fmt; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CtapCommand { - MakeCredential = 0x01, - GetAssertion = 0x02, - GetInfo = 0x04, - ClientPin = 0x06, - Reset = 0x07, - GetNextAssertion = 0x08, - CredentialMgmt = 0x0A, - Selection = 0x0B, - LargeBlobs = 0x0C, - Config = 0x0D, + MakeCredential = 0x01, + GetAssertion = 0x02, + GetInfo = 0x04, + ClientPin = 0x06, + Reset = 0x07, + GetNextAssertion = 0x08, + CredentialMgmt = 0x0A, + Selection = 0x0B, + LargeBlobs = 0x0C, + Config = 0x0D, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum U2fCommand { - Register = 0x01, - Authenticate = 0x02, - Version = 0x03, + Register = 0x01, + Authenticate = 0x02, + Version = 0x03, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VendorCommand { - Backup = 0x01, - ManageSecurityEnvironment = 0x02, - Unlock = 0x03, - EnterpriseAttestation = 0x04, - PhysicalOptions = 0x05, - Memory = 0x06, + Backup = 0x01, + ManageSecurityEnvironment = 0x02, + Unlock = 0x03, + EnterpriseAttestation = 0x04, + PhysicalOptions = 0x05, + Memory = 0x06, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AuthenticateControl { - EnforceUserPresence = 0x03, - CheckOnly = 0x07, + EnforceUserPresence = 0x03, + CheckOnly = 0x07, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ClientPinSubCommand { - GetPinRetries = 0x01, - GetKeyAgreement = 0x02, - SetPin = 0x03, - ChangePin = 0x04, - GetPinToken = 0x05, - GetPinUvAuthTokenUsingUvWithPermissions = 0x06, - GetUvRetries = 0x07, - GetPinUvAuthTokenUsingPinWithPermissions = 0x08, + GetPinRetries = 0x01, + GetKeyAgreement = 0x02, + SetPin = 0x03, + ChangePin = 0x04, + GetPinToken = 0x05, + GetPinUvAuthTokenUsingUvWithPermissions = 0x06, + GetUvRetries = 0x07, + GetPinUvAuthTokenUsingPinWithPermissions = 0x08, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MakeCredentialParam { - ClientDataHash = 0x01, - Rp = 0x02, - User = 0x03, - PubKeyCredParams = 0x04, - ExcludeList = 0x05, - Extensions = 0x06, - Options = 0x07, - PinUvAuthParam = 0x08, - PinUvAuthProtocol = 0x09, - EnterpriseAttestation = 0x0A, + ClientDataHash = 0x01, + Rp = 0x02, + User = 0x03, + PubKeyCredParams = 0x04, + ExcludeList = 0x05, + Extensions = 0x06, + Options = 0x07, + PinUvAuthParam = 0x08, + PinUvAuthProtocol = 0x09, + EnterpriseAttestation = 0x0A, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum GetAssertionParam { - RpId = 0x01, - ClientDataHash = 0x02, - AllowList = 0x03, - Extensions = 0x04, - Options = 0x05, - PinUvAuthParam = 0x06, - PinUvAuthProtocol = 0x07, + RpId = 0x01, + ClientDataHash = 0x02, + AllowList = 0x03, + Extensions = 0x04, + Options = 0x05, + PinUvAuthParam = 0x06, + PinUvAuthProtocol = 0x07, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ClientPinParam { - PinUvAuthProtocol = 0x01, - SubCommand = 0x02, - KeyAgreement = 0x03, - PinUvAuthParam = 0x04, - NewPinEnc = 0x05, - PinHashEnc = 0x06, - Permissions = 0x09, - PermissionsRpId = 0x0A, + PinUvAuthProtocol = 0x01, + SubCommand = 0x02, + KeyAgreement = 0x03, + PinUvAuthParam = 0x04, + NewPinEnc = 0x05, + PinHashEnc = 0x06, + Permissions = 0x09, + PermissionsRpId = 0x0A, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConfigParam { - SubCommand = 0x01, - SubCommandParams = 0x02, - PinUvAuthProtocol = 0x03, - PinUvAuthParam = 0x04, + SubCommand = 0x01, + SubCommandParams = 0x02, + PinUvAuthProtocol = 0x03, + PinUvAuthParam = 0x04, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConfigSubCommand { - EnableEnterpriseAttestation = 0x01, - ToggleAlwaysUv = 0x02, - SetMinPinLength = 0x03, - VendorPrototype = 0xFF, + EnableEnterpriseAttestation = 0x01, + ToggleAlwaysUv = 0x02, + SetMinPinLength = 0x03, + VendorPrototype = 0xFF, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VendorParam { - VendorCommand = 0x01, - VendorSubParams = 0x02, - PinUvAuthProtocol = 0x03, - PinUvAuthParam = 0x04, + VendorCommand = 0x01, + VendorSubParams = 0x02, + PinUvAuthProtocol = 0x03, + PinUvAuthParam = 0x04, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VendorSubParam { - VendorParam = 0x01, - CoseKey = 0x02, - VendorParamInt = 0x03, - VendorParamText = 0x04, + VendorParam = 0x01, + CoseKey = 0x02, + VendorParamInt = 0x03, + VendorParamText = 0x04, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConfigSubCommandParam { - NewMinPinLength = 0x01, - MinPinLengthRPIDs = 0x02, - ForceChangePin = 0x03, + NewMinPinLength = 0x01, + MinPinLengthRPIDs = 0x02, + ForceChangePin = 0x03, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VendorConfigCommand { - AuthEncryptionEnable, - AuthEncryptionDisable, - EnterpriseAttestationUpload, - PinComplexityPolicy, - PhysicalVidPid, - PhysicalLedBrightness, - PhysicalLedGpio, - PhysicalOptions, + AuthEncryptionEnable, + AuthEncryptionDisable, + EnterpriseAttestationUpload, + PinComplexityPolicy, + PhysicalVidPid, + PhysicalLedBrightness, + PhysicalLedGpio, + PhysicalOptions, } impl VendorConfigCommand { - pub fn to_u64(self) -> u64 { - match self { - Self::AuthEncryptionEnable => 0x03e43f56b34285e2, - Self::AuthEncryptionDisable => 0x1831a40f04a25ed9, - Self::EnterpriseAttestationUpload => 0x66f2a674c29a8dcf, - Self::PinComplexityPolicy => 0x6c07d70fe96c3897, - Self::PhysicalVidPid => 0x6fcb19b0cbe3acfa, - Self::PhysicalLedBrightness => 0x76a85945985d02fd, - Self::PhysicalLedGpio => 0x7b392a394de9f948, - Self::PhysicalOptions => 0x269f3b09eceb805f, - } - } + pub fn to_u64(self) -> u64 { + match self { + Self::AuthEncryptionEnable => 0x03e43f56b34285e2, + Self::AuthEncryptionDisable => 0x1831a40f04a25ed9, + Self::EnterpriseAttestationUpload => 0x66f2a674c29a8dcf, + Self::PinComplexityPolicy => 0x6c07d70fe96c3897, + Self::PhysicalVidPid => 0x6fcb19b0cbe3acfa, + Self::PhysicalLedBrightness => 0x76a85945985d02fd, + Self::PhysicalLedGpio => 0x7b392a394de9f948, + Self::PhysicalOptions => 0x269f3b09eceb805f, + } + } - pub fn from_u64(val: u64) -> Option { - match val { - 0x03e43f56b34285e2 => Some(Self::AuthEncryptionEnable), - 0x1831a40f04a25ed9 => Some(Self::AuthEncryptionDisable), - 0x66f2a674c29a8dcf => Some(Self::EnterpriseAttestationUpload), - 0x6c07d70fe96c3897 => Some(Self::PinComplexityPolicy), - 0x6fcb19b0cbe3acfa => Some(Self::PhysicalVidPid), - 0x76a85945985d02fd => Some(Self::PhysicalLedBrightness), - 0x7b392a394de9f948 => Some(Self::PhysicalLedGpio), - 0x269f3b09eceb805f => Some(Self::PhysicalOptions), - _ => None, - } - } + pub fn from_u64(val: u64) -> Option { + match val { + 0x03e43f56b34285e2 => Some(Self::AuthEncryptionEnable), + 0x1831a40f04a25ed9 => Some(Self::AuthEncryptionDisable), + 0x66f2a674c29a8dcf => Some(Self::EnterpriseAttestationUpload), + 0x6c07d70fe96c3897 => Some(Self::PinComplexityPolicy), + 0x6fcb19b0cbe3acfa => Some(Self::PhysicalVidPid), + 0x76a85945985d02fd => Some(Self::PhysicalLedBrightness), + 0x7b392a394de9f948 => Some(Self::PhysicalLedGpio), + 0x269f3b09eceb805f => Some(Self::PhysicalOptions), + _ => None, + } + } } impl fmt::Display for VendorConfigCommand { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::AuthEncryptionEnable => write!(f, "AuthEncryptionEnable"), - Self::AuthEncryptionDisable => write!(f, "AuthEncryptionDisable"), - Self::EnterpriseAttestationUpload => write!(f, "EnterpriseAttestationUpload"), - Self::PinComplexityPolicy => write!(f, "PinComplexityPolicy"), - Self::PhysicalVidPid => write!(f, "PhysicalVidPid"), - Self::PhysicalLedBrightness => write!(f, "PhysicalLedBrightness"), - Self::PhysicalLedGpio => write!(f, "PhysicalLedGpio"), - Self::PhysicalOptions => write!(f, "PhysicalOptions"), - } - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::AuthEncryptionEnable => write!(f, "AuthEncryptionEnable"), + Self::AuthEncryptionDisable => write!(f, "AuthEncryptionDisable"), + Self::EnterpriseAttestationUpload => write!(f, "EnterpriseAttestationUpload"), + Self::PinComplexityPolicy => write!(f, "PinComplexityPolicy"), + Self::PhysicalVidPid => write!(f, "PhysicalVidPid"), + Self::PhysicalLedBrightness => write!(f, "PhysicalLedBrightness"), + Self::PhysicalLedGpio => write!(f, "PhysicalLedGpio"), + Self::PhysicalOptions => write!(f, "PhysicalOptions"), + } + } } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BackupSubCommand { - GetEncryptedBackup = 0x01, - RestoreEncryptedBackup = 0x02, + GetEncryptedBackup = 0x01, + RestoreEncryptedBackup = 0x02, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MseSubCommand { - KeyAgreement = 0x01, + KeyAgreement = 0x01, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EnterpriseAttestationSubCommand { - GenerateCsr = 0x01, + GenerateCsr = 0x01, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PhysicalOptionsSubCommand { - GetOptions = 0x01, + GetOptions = 0x01, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MemorySubCommand { - GetStats = 0x01, + GetStats = 0x01, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MemoryResponseKey { - FreeSpace = 0x01, - UsedSpace = 0x02, - TotalSpace = 0x03, - NumFiles = 0x04, - FlashSize = 0x05, + FreeSpace = 0x01, + UsedSpace = 0x02, + TotalSpace = 0x03, + NumFiles = 0x04, + FlashSize = 0x05, } bitflags::bitflags! { - pub struct PinUvAuthTokenPermissions: u8 { - const MAKE_CREDENTIAL = 0x01; - const GET_ASSERTION = 0x02; - const CREDENTIAL_MANAGEMENT = 0x04; - const BIO_ENROLLMENT = 0x08; - const LARGE_BLOB_WRITE = 0x10; - const AUTHENTICATOR_CONFIG = 0x20; - const PER_CREDENTIAL_MGMT_READONLY = 0x40; - } + pub struct PinUvAuthTokenPermissions: u8 { + const MAKE_CREDENTIAL = 0x01; + const GET_ASSERTION = 0x02; + const CREDENTIAL_MANAGEMENT = 0x04; + const BIO_ENROLLMENT = 0x08; + const LARGE_BLOB_WRITE = 0x10; + const AUTHENTICATOR_CONFIG = 0x20; + const PER_CREDENTIAL_MGMT_READONLY = 0x40; + } } bitflags::bitflags! { - pub struct AuthenticatorFlags: u8 { - const USER_PRESENT = 0x01; - const USER_VERIFIED = 0x04; - const ATTESTED_CREDENTIAL_DATA = 0x40; - const EXTENSION_DATA = 0x80; - } + pub struct AuthenticatorFlags: u8 { + const USER_PRESENT = 0x01; + const USER_VERIFIED = 0x04; + const ATTESTED_CREDENTIAL_DATA = 0x40; + const EXTENSION_DATA = 0x80; + } } bitflags::bitflags! { - pub struct AuthenticatorOptions: u8 { - const ENTERPRISE_ATTESTATION = 0x01; - const USER_VERIFICATION = 0x02; - } + pub struct AuthenticatorOptions: u8 { + const ENTERPRISE_ATTESTATION = 0x01; + const USER_VERIFICATION = 0x02; + } } #[repr(i32)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CoseAlgorithm { - ES256 = -7, - EdDSA = -8, - ESP256 = -9, - Ed25519 = -19, - EcdhEsHkdf256 = -25, - ES384 = -35, - ES512 = -36, - ES256K = -47, - ESP384 = -51, - ESP512 = -52, - Ed448 = -53, - RS256 = -257, - RS384 = -258, - RS512 = -259, - ESB256 = -265, - ESB384 = -267, - ESB512 = -268, + ES256 = -7, + EdDSA = -8, + ESP256 = -9, + Ed25519 = -19, + EcdhEsHkdf256 = -25, + ES384 = -35, + ES512 = -36, + ES256K = -47, + ESP384 = -51, + ESP512 = -52, + Ed448 = -53, + RS256 = -257, + RS384 = -258, + RS512 = -259, + ESB256 = -265, + ESB384 = -267, + ESB512 = -268, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CoseCurve { - P256 = 1, - P384 = 2, - P521 = 3, - X25519 = 4, - X448 = 5, - Ed25519 = 6, - Ed448 = 7, - P256K1 = 8, - BP256R1 = 9, - BP384R1 = 10, - BP512R1 = 11, + P256 = 1, + P384 = 2, + P521 = 3, + X25519 = 4, + X448 = 5, + Ed25519 = 6, + Ed448 = 7, + P256K1 = 8, + BP256R1 = 9, + BP384R1 = 10, + BP512R1 = 11, } #[repr(i32)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CoseKeyParam { - Kty = 1, - Kid = 2, - Alg = 3, - KeyOps = 4, - BaseIV = 5, - Crv = -1, - X = -2, - Y = -3, - D = -4, + Kty = 1, + Kid = 2, + Alg = 3, + KeyOps = 4, + BaseIV = 5, + Crv = -1, + X = -2, + Y = -3, + D = -4, } #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Ctap2Error { - Success = 0x00, - CborUnexpectedType = 0x11, - InvalidCbor = 0x12, - MissingParameter = 0x14, - LimitExceeded = 0x15, - FpDatabaseFull = 0x17, - LargeBlobStorageFull = 0x18, - CredentialExcluded = 0x19, - Processing = 0x21, - InvalidCredential = 0x22, - UserActionPending = 0x23, - OperationPending = 0x24, - NoOperations = 0x25, - UnsupportedAlgorithm = 0x26, - OperationDenied = 0x27, - KeyStoreFull = 0x28, - UnsupportedOption = 0x2B, - InvalidOption = 0x2C, - KeepaliveCancel = 0x2D, - NoCredentials = 0x2E, - UserActionTimeout = 0x2F, - NotAllowed = 0x30, - PinInvalid = 0x31, - PinBlocked = 0x32, - PinAuthInvalid = 0x33, - PinAuthBlocked = 0x34, - PinNotSet = 0x35, - PuatRequired = 0x36, - PinPolicyViolation = 0x37, - RequestTooLarge = 0x39, - ActionTimeout = 0x3A, - UpRequired = 0x3B, - UvBlocked = 0x3C, - IntegrityFailure = 0x3D, - InvalidSubcommand = 0x3E, - UvInvalid = 0x3F, - UnauthorizedPermission = 0x40, + Success = 0x00, + CborUnexpectedType = 0x11, + InvalidCbor = 0x12, + MissingParameter = 0x14, + LimitExceeded = 0x15, + FpDatabaseFull = 0x17, + LargeBlobStorageFull = 0x18, + CredentialExcluded = 0x19, + Processing = 0x21, + InvalidCredential = 0x22, + UserActionPending = 0x23, + OperationPending = 0x24, + NoOperations = 0x25, + UnsupportedAlgorithm = 0x26, + OperationDenied = 0x27, + KeyStoreFull = 0x28, + UnsupportedOption = 0x2B, + InvalidOption = 0x2C, + KeepaliveCancel = 0x2D, + NoCredentials = 0x2E, + UserActionTimeout = 0x2F, + NotAllowed = 0x30, + PinInvalid = 0x31, + PinBlocked = 0x32, + PinAuthInvalid = 0x33, + PinAuthBlocked = 0x34, + PinNotSet = 0x35, + PuatRequired = 0x36, + PinPolicyViolation = 0x37, + RequestTooLarge = 0x39, + ActionTimeout = 0x3A, + UpRequired = 0x3B, + UvBlocked = 0x3C, + IntegrityFailure = 0x3D, + InvalidSubcommand = 0x3E, + UvInvalid = 0x3F, + UnauthorizedPermission = 0x40, } pub const CTAP_VENDOR_CBOR_CMD: u8 = 0xC1; @@ -382,5 +382,5 @@ pub const MAX_FRAGMENT_LENGTH: usize = MAX_MSG_SIZE - 64; pub const MAX_LARGE_BLOB_SIZE: usize = 2048; pub const AAGUID: [u8; 16] = [ - 0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45, + 0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45, ]; diff --git a/src/device/fido/hid.rs b/src/device/fido/hid.rs index 0eaeab2..2a33f6d 100644 --- a/src/device/fido/hid.rs +++ b/src/device/fido/hid.rs @@ -22,500 +22,500 @@ const HID_RESP_READ_TIMEOUT_MS: i32 = 2000; const HID_CONT_READ_TIMEOUT_MS: i32 = 500; pub struct HidTransport { - device: hidapi::HidDevice, - cid: u32, - pub vid: u16, - pub pid: u16, - pub product_name: String, + device: hidapi::HidDevice, + cid: u32, + pub vid: u16, + pub pid: u16, + pub product_name: String, } impl HidTransport { - pub fn open() -> Result { - log::info!("Attempting to open HID transport for FIDO device..."); - let api = hidapi::HidApi::new().map_err(|e| { - log::error!("Failed to initialize HidApi: {}", e); - PFError::Device(format!("Failed to initialize HidApi: {}", e)) - })?; + pub fn open() -> Result { + log::info!("Attempting to open HID transport for FIDO device..."); + let api = hidapi::HidApi::new().map_err(|e| { + log::error!("Failed to initialize HidApi: {}", e); + PFError::Device(format!("Failed to initialize HidApi: {}", e)) + })?; - // Find device with FIDO Usage Page (0xF1D0) - let info = api - .device_list() - .find(|d| d.usage_page() == HID_USAGE_PAGE_FIDO) - .ok_or_else(|| { - log::warn!("No FIDO device found with Usage Page 0xF1D0."); - PFError::NoDevice - })?; + // Find device with FIDO Usage Page (0xF1D0) + let info = api + .device_list() + .find(|d| d.usage_page() == HID_USAGE_PAGE_FIDO) + .ok_or_else(|| { + log::warn!("No FIDO device found with Usage Page 0xF1D0."); + PFError::NoDevice + })?; - log::debug!( - "Found FIDO device: VendorID=0x{:04X}, ProductID=0x{:04X}", - info.vendor_id(), - info.product_id() - ); + log::debug!( + "Found FIDO device: VendorID=0x{:04X}, ProductID=0x{:04X}", + info.vendor_id(), + info.product_id() + ); - let vid = info.vendor_id(); - let pid = info.product_id(); - let product_name = info - .product_string() - .unwrap_or("Unknown FIDO Device") - .to_string(); + let vid = info.vendor_id(); + let pid = info.product_id(); + let product_name = info + .product_string() + .unwrap_or("Unknown FIDO Device") + .to_string(); - let device = info.open_device(&api).map_err(|e| { - log::error!("Failed to open HID device: {}", e); - PFError::Device(format!("Failed to open HID device: {}", e)) - })?; + let device = info.open_device(&api).map_err(|e| { + log::error!("Failed to open HID device: {}", e); + PFError::Device(format!("Failed to open HID device: {}", e)) + })?; - // Negotiate Channel ID (CID) - let cid = Self::init_channel(&device).map_err(|e| { - log::error!("Failed to negotiate Channel ID: {}", e); - PFError::Device(format!("Failed to negotiate Channel ID: {}", e)) - })?; + // Negotiate Channel ID (CID) + let cid = Self::init_channel(&device).map_err(|e| { + log::error!("Failed to negotiate Channel ID: {}", e); + PFError::Device(format!("Failed to negotiate Channel ID: {}", e)) + })?; - log::info!("HID Transport established successfully. CID: 0x{:08X}", cid); - Ok(Self { - device, - cid, - vid, - pid, - product_name, - }) - } + log::info!("HID Transport established successfully. CID: 0x{:08X}", cid); + Ok(Self { + device, + cid, + vid, + pid, + product_name, + }) + } - fn init_channel(device: &hidapi::HidDevice) -> Result { - log::debug!("Initializing CTAPHID channel..."); + fn init_channel(device: &hidapi::HidDevice) -> Result { + log::debug!("Initializing CTAPHID channel..."); - // --- Drain Step --- - // Read and discard any pending packets to avoid using a stale response for CID negotiation. - let mut drain_buf = [0u8; HID_REPORT_SIZE]; - while let Ok(n) = device.read_timeout(&mut drain_buf[..], HID_READ_TIMEOUT_MS) { - if n == 0 { - break; - } - log::trace!("Drained stale HID packet: {:02X?}", &drain_buf[0..16]); - } + // --- Drain Step --- + // Read and discard any pending packets to avoid using a stale response for CID negotiation. + let mut drain_buf = [0u8; HID_REPORT_SIZE]; + while let Ok(n) = device.read_timeout(&mut drain_buf[..], HID_READ_TIMEOUT_MS) { + if n == 0 { + break; + } + log::trace!("Drained stale HID packet: {:02X?}", &drain_buf[0..16]); + } - let mut nonce = [0u8; 8]; - rand::rng().fill(&mut nonce); + let mut nonce = [0u8; 8]; + rand::rng().fill(&mut nonce); - // Construct Init Packet: [CID(4) | CMD(1) | LEN(2) | NONCE(8)] - let mut report = [0u8; HID_REPORT_SIZE + 1]; // +1 for Report ID (always 0) - report[1..5].copy_from_slice(&CTAPHID_CID_BROADCAST.to_be_bytes()); - report[5] = CTAPHID_INIT; - report[6] = 0; // Len MSB - report[7] = 8; // Len LSB - report[8..16].copy_from_slice(&nonce); + // Construct Init Packet: [CID(4) | CMD(1) | LEN(2) | NONCE(8)] + let mut report = [0u8; HID_REPORT_SIZE + 1]; // +1 for Report ID (always 0) + report[1..5].copy_from_slice(&CTAPHID_CID_BROADCAST.to_be_bytes()); + report[5] = CTAPHID_INIT; + report[6] = 0; // Len MSB + report[7] = 8; // Len LSB + report[8..16].copy_from_slice(&nonce); - log::trace!("Sending CTAPHID_INIT broadcast with nonce: {:02X?}", nonce); - device.write(&report[..]).map_err(|e| { - log::error!("Failed to write INIT packet: {}", e); - PFError::Io(format!("Failed to write INIT packet: {}", e)) - })?; + log::trace!("Sending CTAPHID_INIT broadcast with nonce: {:02X?}", nonce); + device.write(&report[..]).map_err(|e| { + log::error!("Failed to write INIT packet: {}", e); + PFError::Io(format!("Failed to write INIT packet: {}", e)) + })?; - // Read Response until we find our nonce - let start = std::time::Instant::now(); - while start.elapsed() < Duration::from_secs(1) { - let mut buf = [0u8; HID_REPORT_SIZE]; - if device - .read_timeout(&mut buf[..], HID_INIT_READ_TIMEOUT_MS) - .is_ok() - { - // Check if response matches our broadcast and nonce - if buf[0..4] == CTAPHID_CID_BROADCAST.to_be_bytes() - && buf[4] == CTAPHID_INIT - && buf[7..15] == nonce - { - // New CID is at bytes 16..20 - let new_cid = u32::from_be_bytes([buf[15], buf[16], buf[17], buf[18]]); - log::debug!("Channel negotiation successful. New CID: 0x{:08X}", new_cid); - return Ok(new_cid); - } else { - log::trace!( - "Received ignoreable HID packet during CID negotiation: {:02X?}", - &buf[0..16] - ); - } - } - } - log::error!("Timeout waiting for CTAPHID_INIT response."); - Err(PFError::Device( - "Timeout waiting for FIDO Init response".into(), - )) - } + // Read Response until we find our nonce + let start = std::time::Instant::now(); + while start.elapsed() < Duration::from_secs(1) { + let mut buf = [0u8; HID_REPORT_SIZE]; + if device + .read_timeout(&mut buf[..], HID_INIT_READ_TIMEOUT_MS) + .is_ok() + { + // Check if response matches our broadcast and nonce + if buf[0..4] == CTAPHID_CID_BROADCAST.to_be_bytes() + && buf[4] == CTAPHID_INIT + && buf[7..15] == nonce + { + // New CID is at bytes 16..20 + let new_cid = u32::from_be_bytes([buf[15], buf[16], buf[17], buf[18]]); + log::debug!("Channel negotiation successful. New CID: 0x{:08X}", new_cid); + return Ok(new_cid); + } else { + log::trace!( + "Received ignoreable HID packet during CID negotiation: {:02X?}", + &buf[0..16] + ); + } + } + } + log::error!("Timeout waiting for CTAPHID_INIT response."); + Err(PFError::Device( + "Timeout waiting for FIDO Init response".into(), + )) + } - pub fn send_cbor(&self, cmd: u8, payload: &[u8]) -> Result, PFError> { - self.write_cbor_request(cmd, payload)?; - self.read_cbor_response(cmd) - } + pub fn send_cbor(&self, cmd: u8, payload: &[u8]) -> Result, PFError> { + self.write_cbor_request(cmd, payload)?; + self.read_cbor_response(cmd) + } - fn write_cbor_request(&self, cmd: u8, payload: &[u8]) -> Result<(), PFError> { - log::debug!( - "Sending CBOR Command: 0x{:02X}, Payload Size: {} bytes", - cmd, - payload.len() - ); + fn write_cbor_request(&self, cmd: u8, payload: &[u8]) -> Result<(), PFError> { + log::debug!( + "Sending CBOR Command: 0x{:02X}, Payload Size: {} bytes", + cmd, + payload.len() + ); - let total_len = payload.len(); - let mut sent = 0; - let mut sequence = 0u8; + let total_len = payload.len(); + let mut sent = 0; + let mut sequence = 0u8; - // 1. Init Packet - let mut report = [0u8; HID_REPORT_SIZE + 1]; - report[1..5].copy_from_slice(&self.cid.to_be_bytes()); - report[5] = cmd; - report[6] = (total_len >> 8) as u8; - report[7] = (total_len & 0xFF) as u8; + // 1. Init Packet + let mut report = [0u8; HID_REPORT_SIZE + 1]; + report[1..5].copy_from_slice(&self.cid.to_be_bytes()); + report[5] = cmd; + report[6] = (total_len >> 8) as u8; + report[7] = (total_len & 0xFF) as u8; - let to_copy = std::cmp::min(total_len, HID_REPORT_SIZE - 7); - report[8..8 + to_copy].copy_from_slice(&payload[0..to_copy]); - sent += to_copy; + let to_copy = std::cmp::min(total_len, HID_REPORT_SIZE - 7); + report[8..8 + to_copy].copy_from_slice(&payload[0..to_copy]); + sent += to_copy; - // log::trace!("Writing Init Packet (Sent: {}/{})", sent, total_len); - if let Err(e) = self.device.write(&report[..]) { - log::error!("Failed to write initial HID packet: {}", e); - return Err(PFError::Io(format!( - "Failed to write initial HID packet: {}", - e, - ))); - } else { - log::trace!("Successfully sent initial HID packet"); - } + // log::trace!("Writing Init Packet (Sent: {}/{})", sent, total_len); + if let Err(e) = self.device.write(&report[..]) { + log::error!("Failed to write initial HID packet: {}", e); + return Err(PFError::Io(format!( + "Failed to write initial HID packet: {}", + e, + ))); + } else { + log::trace!("Successfully sent initial HID packet"); + } - // 2. Continuation Packets - while sent < total_len { - let mut report = [0u8; HID_REPORT_SIZE + 1]; - report[1..5].copy_from_slice(&self.cid.to_be_bytes()); - report[5] = 0x7F & sequence; // SEQ - sequence += 1; + // 2. Continuation Packets + while sent < total_len { + let mut report = [0u8; HID_REPORT_SIZE + 1]; + report[1..5].copy_from_slice(&self.cid.to_be_bytes()); + report[5] = 0x7F & sequence; // SEQ + sequence += 1; - let to_copy = std::cmp::min(total_len - sent, HID_REPORT_SIZE - 5); - report[6..6 + to_copy].copy_from_slice(&payload[sent..sent + to_copy]); - sent += to_copy; + let to_copy = std::cmp::min(total_len - sent, HID_REPORT_SIZE - 5); + report[6..6 + to_copy].copy_from_slice(&payload[sent..sent + to_copy]); + sent += to_copy; - // log::trace!("Writing Cont Packet Seq {} (Sent: {}/{})", sequence - 1, sent, total_len); - if let Err(e) = self.device.write(&report[..]) { - log::error!( - "Failed to write continuation HID packet (Seq {}): {}", - sequence - 1, - e - ); - return Err(PFError::Io(format!( - "Failed to write continuation HID packet: {}", - e, - ))); - } else { - log::trace!( - "Successfully sent continuation HID packet (Seq {})", - sequence - 1 - ); - } - } + // log::trace!("Writing Cont Packet Seq {} (Sent: {}/{})", sequence - 1, sent, total_len); + if let Err(e) = self.device.write(&report[..]) { + log::error!( + "Failed to write continuation HID packet (Seq {}): {}", + sequence - 1, + e + ); + return Err(PFError::Io(format!( + "Failed to write continuation HID packet: {}", + e, + ))); + } else { + log::trace!( + "Successfully sent continuation HID packet (Seq {})", + sequence - 1 + ); + } + } - Ok(()) - } + Ok(()) + } - fn read_cbor_response(&self, cmd: u8) -> Result, PFError> { - log::debug!("Waiting for response..."); + fn read_cbor_response(&self, cmd: u8) -> Result, PFError> { + log::debug!("Waiting for response..."); - let mut buf = [0u8; HID_REPORT_SIZE]; - let mut response_data = Vec::new(); - let expected_len: usize; - let mut read_len = 0; - let mut last_seq = 0; + let mut buf = [0u8; HID_REPORT_SIZE]; + let mut response_data = Vec::new(); + let expected_len: usize; + let mut read_len = 0; + let mut last_seq = 0; - // 1. Read First Packet (Loop to handle Keepalives) - loop { - if let Err(e) = self - .device - .read_timeout(&mut buf[..], HID_RESP_READ_TIMEOUT_MS) - { - log::error!("Timeout reading response packet: {}", e); - return Err(PFError::Io(format!( - "Timeout reading response packet: {}", - e - ))); - } + // 1. Read First Packet (Loop to handle Keepalives) + loop { + if let Err(e) = self + .device + .read_timeout(&mut buf[..], HID_RESP_READ_TIMEOUT_MS) + { + log::error!("Timeout reading response packet: {}", e); + return Err(PFError::Io(format!( + "Timeout reading response packet: {}", + e + ))); + } - // Check CID mismatch - if u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) != self.cid { - log::warn!("Received packet from different CID, ignoring..."); - continue; - } + // Check CID mismatch + if u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) != self.cid { + log::warn!("Received packet from different CID, ignoring..."); + continue; + } - // Check for KEEPALIVE (0xBB) - if buf[4] == CTAPHID_KEEPALIVE { - let status = buf[5]; // Keepalive status byte - log::debug!( - "Device sent KEEPALIVE (Status: 0x{:02X}), waiting...", - status - ); - continue; // Go back to start of loop and read again - } + // Check for KEEPALIVE (0xBB) + if buf[4] == CTAPHID_KEEPALIVE { + let status = buf[5]; // Keepalive status byte + log::debug!( + "Device sent KEEPALIVE (Status: 0x{:02X}), waiting...", + status + ); + continue; // Go back to start of loop and read again + } - // If we are here, it's a real response - break; - } + // If we are here, it's a real response + break; + } - if buf[4] == CTAPHID_ERROR { - log::error!("Device returned CTAP Error code: 0x{:02X}", buf[5]); - return Err(PFError::Device(format!( - "Device returned CTAP Error: 0x{:02X}", - buf[5], - ))); - } else { - log::trace!("Packet received is not a CTAP Error"); - } + if buf[4] == CTAPHID_ERROR { + log::error!("Device returned CTAP Error code: 0x{:02X}", buf[5]); + return Err(PFError::Device(format!( + "Device returned CTAP Error: 0x{:02X}", + buf[5], + ))); + } else { + log::trace!("Packet received is not a CTAP Error"); + } - if buf[4] == cmd { - expected_len = u16::from_be_bytes([buf[5], buf[6]]) as usize; - let in_pkt = std::cmp::min(expected_len, HID_REPORT_SIZE - 7); - response_data.extend_from_slice(&buf[7..7 + in_pkt]); - read_len += in_pkt; - // log::trace!("Received Init Response. Expecting {} bytes total.", expected_len); - } else { - log::error!( - "Unexpected command response: 0x{:02X} (Expected 0x{:02X})", - buf[4], - cmd - ); - return Err(PFError::Device(format!( - "Unexpected command response: 0x{:02X} (Expected 0x{:02X})", - buf[4], cmd - ))); - } + if buf[4] == cmd { + expected_len = u16::from_be_bytes([buf[5], buf[6]]) as usize; + let in_pkt = std::cmp::min(expected_len, HID_REPORT_SIZE - 7); + response_data.extend_from_slice(&buf[7..7 + in_pkt]); + read_len += in_pkt; + // log::trace!("Received Init Response. Expecting {} bytes total.", expected_len); + } else { + log::error!( + "Unexpected command response: 0x{:02X} (Expected 0x{:02X})", + buf[4], + cmd + ); + return Err(PFError::Device(format!( + "Unexpected command response: 0x{:02X} (Expected 0x{:02X})", + buf[4], cmd + ))); + } - // 2. Read Continuation Packets - while read_len < expected_len { - if let Err(e) = self - .device - .read_timeout(&mut buf[..], HID_CONT_READ_TIMEOUT_MS) - { - log::error!("Timeout reading continuation packet: {}", e); - return Err(PFError::Io(format!( - "Timeout reading continuation packet: {}", - e - ))); - } + // 2. Read Continuation Packets + while read_len < expected_len { + if let Err(e) = self + .device + .read_timeout(&mut buf[..], HID_CONT_READ_TIMEOUT_MS) + { + log::error!("Timeout reading continuation packet: {}", e); + return Err(PFError::Io(format!( + "Timeout reading continuation packet: {}", + e + ))); + } - if u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) != self.cid { - continue; // Ignore packets from other channels - } + if u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) != self.cid { + continue; // Ignore packets from other channels + } - let seq = buf[4]; - if seq != last_seq { - log::error!( - "Sequence mismatch in response. Expected {}, got {}", - last_seq, - seq - ); - return Err(PFError::Device("Sequence mismatch".into())); - } - last_seq += 1; + let seq = buf[4]; + if seq != last_seq { + log::error!( + "Sequence mismatch in response. Expected {}, got {}", + last_seq, + seq + ); + return Err(PFError::Device("Sequence mismatch".into())); + } + last_seq += 1; - let in_pkt = std::cmp::min(expected_len - read_len, HID_REPORT_SIZE - 5); - response_data.extend_from_slice(&buf[5..5 + in_pkt]); - read_len += in_pkt; - } + let in_pkt = std::cmp::min(expected_len - read_len, HID_REPORT_SIZE - 5); + response_data.extend_from_slice(&buf[5..5 + in_pkt]); + read_len += in_pkt; + } - // 3. Check CTAP Status Byte (First byte of payload) - if response_data.is_empty() { - log::error!("Device sent empty payload response."); - return Err(PFError::Device("Empty response".into())); - } - let status = response_data[0]; - if status != 0x00 { - log::error!("FIDO Operation returned failure status: 0x{:02X}", status); - return Err(PFError::Device(format!( - "FIDO Operation Failed with Status: 0x{:02X}", - status - ))); - } + // 3. Check CTAP Status Byte (First byte of payload) + if response_data.is_empty() { + log::error!("Device sent empty payload response."); + return Err(PFError::Device("Empty response".into())); + } + let status = response_data[0]; + if status != 0x00 { + log::error!("FIDO Operation returned failure status: 0x{:02X}", status); + return Err(PFError::Device(format!( + "FIDO Operation Failed with Status: 0x{:02X}", + status + ))); + } - log::debug!( - "Command 0x{:02X} successful. Response payload len: {}", - cmd, - response_data.len() - 1 - ); - // Return payload without status byte - Ok(response_data[1..].to_vec()) - } + log::debug!( + "Command 0x{:02X} successful. Response payload len: {}", + cmd, + response_data.len() - 1 + ); + // Return payload without status byte + Ok(response_data[1..].to_vec()) + } - pub fn send_vendor_config( - &self, - pin_token: &[u8], - vendor_cmd: VendorConfigCommand, - param: Value, - ) -> Result<(), PFError> { - log::debug!("Sending vendor config command: {}...", vendor_cmd); + pub fn send_vendor_config( + &self, + pin_token: &[u8], + vendor_cmd: VendorConfigCommand, + param: Value, + ) -> Result<(), PFError> { + log::debug!("Sending vendor config command: {}...", vendor_cmd); - // Build subCommandParams (Key 0x02) - // This map contains: - // 0x01: vendorCommandId (u64) - // 0x02/0x03/0x04: param - let mut sub_params_inner = BTreeMap::new(); - sub_params_inner.insert( - Value::Integer(0x01), - Value::Integer(vendor_cmd.to_u64() as i128), - ); + // Build subCommandParams (Key 0x02) + // This map contains: + // 0x01: vendorCommandId (u64) + // 0x02/0x03/0x04: param + let mut sub_params_inner = BTreeMap::new(); + sub_params_inner.insert( + Value::Integer(0x01), + Value::Integer(vendor_cmd.to_u64() as i128), + ); - match param { - Value::Bytes(_) => { - sub_params_inner.insert(Value::Integer(0x02), param.clone()); - } - Value::Integer(_) => { - sub_params_inner.insert(Value::Integer(0x03), param.clone()); - } - Value::Text(_) => { - sub_params_inner.insert(Value::Integer(0x04), param.clone()); - } - _ => return Err(PFError::Io("Unsupported parameter type".into())), - } + match param { + Value::Bytes(_) => { + sub_params_inner.insert(Value::Integer(0x02), param.clone()); + } + Value::Integer(_) => { + sub_params_inner.insert(Value::Integer(0x03), param.clone()); + } + Value::Text(_) => { + sub_params_inner.insert(Value::Integer(0x04), param.clone()); + } + _ => return Err(PFError::Io("Unsupported parameter type".into())), + } - let sub_params = Value::Map(sub_params_inner); - let sub_params_bytes = to_vec(&sub_params).map_err(|e| PFError::Io(e.to_string()))?; + let sub_params = Value::Map(sub_params_inner); + let sub_params_bytes = to_vec(&sub_params).map_err(|e| PFError::Io(e.to_string()))?; - // Calculate PIN Auth - let pin_auth = self.sign_config_command( - pin_token, - ConfigSubCommand::VendorPrototype as u8, - &sub_params_bytes, - ); + // Calculate PIN Auth + let pin_auth = self.sign_config_command( + pin_token, + ConfigSubCommand::VendorPrototype as u8, + &sub_params_bytes, + ); - // Build full authenticatorConfig map - let mut config_map = BTreeMap::new(); - config_map.insert( - Value::Integer(ConfigParam::SubCommand as i128), - Value::Integer(ConfigSubCommand::VendorPrototype as i128), - ); - config_map.insert( - Value::Integer(ConfigParam::SubCommandParams as i128), - sub_params, - ); - config_map.insert( - Value::Integer(ConfigParam::PinUvAuthProtocol as i128), - Value::Integer(1), - ); - config_map.insert( - Value::Integer(ConfigParam::PinUvAuthParam as i128), - Value::Bytes(pin_auth), - ); + // Build full authenticatorConfig map + let mut config_map = BTreeMap::new(); + config_map.insert( + Value::Integer(ConfigParam::SubCommand as i128), + Value::Integer(ConfigSubCommand::VendorPrototype as i128), + ); + config_map.insert( + Value::Integer(ConfigParam::SubCommandParams as i128), + sub_params, + ); + config_map.insert( + Value::Integer(ConfigParam::PinUvAuthProtocol as i128), + Value::Integer(1), + ); + config_map.insert( + Value::Integer(ConfigParam::PinUvAuthParam as i128), + Value::Bytes(pin_auth), + ); - let config_payload_cbor = - to_vec(&Value::Map(config_map)).map_err(|e| PFError::Io(e.to_string()))?; + let config_payload_cbor = + to_vec(&Value::Map(config_map)).map_err(|e| PFError::Io(e.to_string()))?; - // Encapsulate for CTAP - let mut payload = vec![CtapCommand::Config as u8]; - payload.extend(config_payload_cbor); + // Encapsulate for CTAP + 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 FIDO config: {}", e); - PFError::Device(format!("FIDO config failed: {}", e)) - })?; + // Send via HID + self.send_cbor(CTAPHID_CBOR, &payload).map_err(|e| { + log::error!("Failed to send FIDO config: {}", e); + PFError::Device(format!("FIDO config failed: {}", e)) + })?; - Ok(()) - } + 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 - ); + /// 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 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()))?; - // Calculate PIN Auth - let pin_auth = self.sign_config_command( - pin_token, - ConfigSubCommand::SetMinPinLength as u8, - &sub_params_bytes, - ); + // Calculate PIN Auth + let pin_auth = self.sign_config_command( + pin_token, + ConfigSubCommand::SetMinPinLength as u8, + &sub_params_bytes, + ); - // Build full authenticatorConfig map with keys in ASCENDING ORDER - // Keeping the map item in the correct order 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), - ); + // Build full authenticatorConfig map with keys in ASCENDING ORDER + // Keeping the map item in the correct order 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()))?; + 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); + // Prepend CTAP command byte + let mut payload = vec![CtapCommand::Config as u8]; + payload.extend(config_payload_cbor); - // Send via HID - 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); + // Send via HID + 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); - // Check for PIN policy violation (0x37) - cannot decrease min PIN length - if err_str.contains("0x37") { - return Err(PFError::Device( + // 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))) - } - } - } + Err(PFError::Device(format!("setMinPINLength failed: {}", e))) + } + } + } - /// Helper to sign the authenticatorConfig command - fn sign_config_command( - &self, - pin_token: &[u8], - sub_cmd: u8, - sub_params_bytes: &[u8], - ) -> Vec { - // Build HMAC message for signing - // According to FIDO 2.1: authenticate(pinUvAuthToken, 32×0xff || 0x0d || uint8(subCommand) || subCommandParams) - let mut message = vec![0xff; 32]; - message.push(CtapCommand::Config as u8); - message.push(sub_cmd); - message.extend(sub_params_bytes); + /// Helper to sign the authenticatorConfig command + fn sign_config_command( + &self, + pin_token: &[u8], + sub_cmd: u8, + sub_params_bytes: &[u8], + ) -> Vec { + // Build HMAC message for signing + // According to FIDO 2.1: authenticate(pinUvAuthToken, 32×0xff || 0x0d || uint8(subCommand) || subCommandParams) + let mut message = vec![0xff; 32]; + message.push(CtapCommand::Config as u8); + message.push(sub_cmd); + message.extend(sub_params_bytes); - // Sign using provided PIN token - use ring::hmac; - let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, pin_token); - let sig = hmac::sign(&hmac_key, &message); - sig.as_ref()[0..16].to_vec() - } + // Sign using provided PIN token + use ring::hmac; + let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, pin_token); + let sig = hmac::sign(&hmac_key, &message); + sig.as_ref()[0..16].to_vec() + } } diff --git a/src/device/fido/mod.rs b/src/device/fido/mod.rs index a7ac22b..d9bf364 100644 --- a/src/device/fido/mod.rs +++ b/src/device/fido/mod.rs @@ -2,17 +2,17 @@ pub mod constants; pub mod hid; use crate::{ - device::error::PFError, - device::types::{ - AppConfig, AppConfigInput, DeviceInfo, DeviceMethod, FidoDeviceInfo, FullDeviceStatus, - StoredCredential, - }, + device::error::PFError, + device::types::{ + AppConfig, AppConfigInput, DeviceInfo, DeviceMethod, FidoDeviceInfo, FullDeviceStatus, + StoredCredential, + }, }; use constants::*; use ctap_hid_fido2::{ - Cfg, FidoKeyHidFactory, - fidokey::{FidoKeyHid, pin::Permission}, - public_key_credential_descriptor::PublicKeyCredentialDescriptor, + Cfg, FidoKeyHidFactory, + fidokey::{FidoKeyHid, pin::Permission}, + public_key_credential_descriptor::PublicKeyCredentialDescriptor, }; use hid::*; use serde_cbor_2::{Value, from_slice, to_vec}; @@ -21,482 +21,482 @@ use std::collections::{BTreeMap, HashMap}; // Fido functions that require pin: ( Uses ctap_hid_fido2 crate) fn get_device() -> Result { - let cfg = Cfg::init(); - FidoKeyHidFactory::create(&cfg).map_err(|e| { - format!( - "Could not connect to FIDO device. Is it plugged in? Error: {:?}", - e - ) - }) + let cfg = Cfg::init(); + FidoKeyHidFactory::create(&cfg).map_err(|e| { + format!( + "Could not connect to FIDO device. Is it plugged in? Error: {:?}", + e + ) + }) } pub(crate) fn get_fido_info() -> Result { - let device = get_device()?; + let device = get_device()?; - let info = device - .get_info() - .map_err(|e| format!("Error reading device info: {:?}", e))?; + let info = device + .get_info() + .map_err(|e| format!("Error reading device info: {:?}", e))?; - let options_map: HashMap = info.options.into_iter().collect(); + let options_map: HashMap = info.options.into_iter().collect(); - Ok(FidoDeviceInfo { - versions: info.versions, - extensions: info.extensions, - aaguid: hex::encode_upper(info.aaguid), - options: options_map, - max_msg_size: info.max_msg_size, - pin_protocols: info.pin_uv_auth_protocols, - min_pin_length: info.min_pin_length, - firmware_version: format!( - "{}.{}", - (info.firmware_version >> 8) & 0xFF, - info.firmware_version & 0xFF - ), - }) + Ok(FidoDeviceInfo { + versions: info.versions, + extensions: info.extensions, + aaguid: hex::encode_upper(info.aaguid), + options: options_map, + max_msg_size: info.max_msg_size, + pin_protocols: info.pin_uv_auth_protocols, + min_pin_length: info.min_pin_length, + firmware_version: format!( + "{}.{}", + (info.firmware_version >> 8) & 0xFF, + info.firmware_version & 0xFF + ), + }) } pub(crate) fn change_fido_pin( - current_pin: Option, - new_pin: String, + current_pin: Option, + new_pin: String, ) -> Result { - let device = get_device()?; + let device = get_device()?; - match current_pin { - Some(old) => { - device - .change_pin(&old, &new_pin) - .map_err(|e| format!("Failed to change PIN: {:?}", e))?; - Ok("PIN Changed Successfully".into()) - } - None => { - device - .set_new_pin(&new_pin) - .map_err(|e| format!("Failed to set PIN: {:?}", e))?; - Ok("PIN Set Successfully".into()) - } - } + match current_pin { + Some(old) => { + device + .change_pin(&old, &new_pin) + .map_err(|e| format!("Failed to change PIN: {:?}", e))?; + Ok("PIN Changed Successfully".into()) + } + None => { + device + .set_new_pin(&new_pin) + .map_err(|e| format!("Failed to set PIN: {:?}", e))?; + Ok("PIN Set Successfully".into()) + } + } } pub(crate) fn set_min_pin_length( - current_pin: String, - min_pin_length: u8, + current_pin: String, + min_pin_length: u8, ) -> Result { - log::info!("Starting set_min_pin_length (custom implementation)..."); + log::info!("Starting set_min_pin_length (custom implementation)..."); - // 1. Obtain PIN token using the library handle - let pin_token = { - let device = get_device()?; + // 1. Obtain PIN token using the library handle + let pin_token = { + let device = get_device()?; - // 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. - }; + // 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))?; + // 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))?; + 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 {}", - min_pin_length - )) + Ok(format!( + "Minimum PIN length successfully set to {}", + min_pin_length + )) } pub(crate) fn get_credentials(pin: String) -> Result, String> { - let device = get_device()?; + let device = get_device()?; - let rps = match device.credential_management_enumerate_rps(Some(&pin)) { - Ok(rps) => rps, - Err(e) => { - // CTAP2_ERR_NO_CREDENTIALS (0x2E) means no credentials exist - return empty list - let err_str = format!("{:?}", e); - if err_str.contains("0x2E") || err_str.contains("NO_CREDENTIALS") { - log::info!("No credentials stored on device (CTAP2_ERR_NO_CREDENTIALS)"); - return Ok(Vec::new()); - } - return Err(format!("Failed to enumerate Relying Parties: {:?}", e)); - } - }; + let rps = match device.credential_management_enumerate_rps(Some(&pin)) { + Ok(rps) => rps, + Err(e) => { + // CTAP2_ERR_NO_CREDENTIALS (0x2E) means no credentials exist - return empty list + let err_str = format!("{:?}", e); + if err_str.contains("0x2E") || err_str.contains("NO_CREDENTIALS") { + log::info!("No credentials stored on device (CTAP2_ERR_NO_CREDENTIALS)"); + return Ok(Vec::new()); + } + return Err(format!("Failed to enumerate Relying Parties: {:?}", e)); + } + }; - let mut all_credentials = Vec::new(); + let mut all_credentials = Vec::new(); - for rp in rps { - let creds = device - .credential_management_enumerate_credentials(Some(&pin), &rp.rpid_hash) - .map_err(|e| { - format!( - "Failed to enumerate credentials for RP {}: {:?}", - rp.public_key_credential_rp_entity.id, e - ) - })?; + for rp in rps { + let creds = device + .credential_management_enumerate_credentials(Some(&pin), &rp.rpid_hash) + .map_err(|e| { + format!( + "Failed to enumerate credentials for RP {}: {:?}", + rp.public_key_credential_rp_entity.id, e + ) + })?; - for cred in creds { - all_credentials.push(StoredCredential { - credential_id: hex::encode(&cred.public_key_credential_descriptor.id), - rp_id: rp.public_key_credential_rp_entity.id.clone(), - rp_name: rp.public_key_credential_rp_entity.name.clone(), - user_name: cred.public_key_credential_user_entity.name.clone(), - user_display_name: cred.public_key_credential_user_entity.display_name.clone(), - user_id: hex::encode(&cred.public_key_credential_user_entity.id).clone(), - }); - } - } + for cred in creds { + all_credentials.push(StoredCredential { + credential_id: hex::encode(&cred.public_key_credential_descriptor.id), + rp_id: rp.public_key_credential_rp_entity.id.clone(), + rp_name: rp.public_key_credential_rp_entity.name.clone(), + user_name: cred.public_key_credential_user_entity.name.clone(), + user_display_name: cred.public_key_credential_user_entity.display_name.clone(), + user_id: hex::encode(&cred.public_key_credential_user_entity.id).clone(), + }); + } + } - Ok(all_credentials) + Ok(all_credentials) } pub(crate) fn delete_credential(pin: String, credential_id_hex: String) -> Result { - let device = get_device()?; + let device = get_device()?; - let cred_id_bytes = hex::decode(&credential_id_hex) - .map_err(|_| "Invalid Credential ID Hex string".to_string())?; + let cred_id_bytes = hex::decode(&credential_id_hex) + .map_err(|_| "Invalid Credential ID Hex string".to_string())?; - let descriptor = PublicKeyCredentialDescriptor { - ctype: "public-key".to_string(), - id: cred_id_bytes, - }; + let descriptor = PublicKeyCredentialDescriptor { + ctype: "public-key".to_string(), + id: cred_id_bytes, + }; - device - .credential_management_delete_credential(Some(&pin), descriptor) - .map_err(|e| format!("Failed to delete credential: {:?}", e))?; + device + .credential_management_delete_credential(Some(&pin), descriptor) + .map_err(|e| format!("Failed to delete credential: {:?}", e))?; - Ok("Credential deleted successfully".into()) + Ok("Credential deleted successfully".into()) } // Custom Fido functions ( works only with pico-fido firmware ) pub fn read_device_details() -> Result { - log::info!("Starting FIDO device details read..."); + log::info!("Starting FIDO device details read..."); - let transport = HidTransport::open().map_err(|e| { - if matches!(e, PFError::NoDevice) { - PFError::NoDevice - } else { - log::error!("Failed to open HID transport: {}", e); - PFError::Device(e.to_string()) - } - })?; + let transport = HidTransport::open().map_err(|e| { + if matches!(e, PFError::NoDevice) { + PFError::NoDevice + } else { + log::error!("Failed to open HID transport: {}", e); + PFError::Device(e.to_string()) + } + })?; - let (aaguid_str, fw_version) = read_device_info(&transport)?; + let (aaguid_str, fw_version) = read_device_info(&transport)?; - log::info!( - "Device identified: AAGUID={}, FW={}", - aaguid_str, - fw_version - ); + log::info!( + "Device identified: AAGUID={}, FW={}", + aaguid_str, + fw_version + ); - let (used, total) = read_memory_stats(&transport)?; - log::debug!( - "Memory Stats: Used={}KB, Total={}KB", - used / 1024, - total / 1024 - ); + let (used, total) = read_memory_stats(&transport)?; + log::debug!( + "Memory Stats: Used={}KB, Total={}KB", + used / 1024, + total / 1024 + ); - let config = read_physical_config(&transport)?; + let config = read_physical_config(&transport)?; - log::info!("Successfully read all device details."); + log::info!("Successfully read all device details."); - Ok(FullDeviceStatus { - info: DeviceInfo { - serial: "?".to_string(), // Serial number is not available through fido - flash_used: used / 1024, - flash_total: total / 1024, - firmware_version: fw_version, - }, - config, - secure_boot: false, - secure_lock: false, - method: DeviceMethod::Fido, - }) + Ok(FullDeviceStatus { + info: DeviceInfo { + serial: "?".to_string(), // Serial number is not available through fido + flash_used: used / 1024, + flash_total: total / 1024, + firmware_version: fw_version, + }, + config, + secure_boot: false, + secure_lock: false, + method: DeviceMethod::Fido, + }) } fn read_device_info(transport: &HidTransport) -> Result<(String, String), PFError> { - log::debug!("Sending GetInfo command (0x04)..."); - let info_payload = [CtapCommand::GetInfo as u8]; - let info_res = transport - .send_cbor(CTAPHID_CBOR, &info_payload) - .map_err(|e| { - log::error!("GetInfo CTAP command failed: {}", e); - PFError::Device(format!("GetInfo failed: {}", e)) - })?; + log::debug!("Sending GetInfo command (0x04)..."); + let info_payload = [CtapCommand::GetInfo as u8]; + let info_res = transport + .send_cbor(CTAPHID_CBOR, &info_payload) + .map_err(|e| { + log::error!("GetInfo CTAP command failed: {}", e); + PFError::Device(format!("GetInfo failed: {}", e)) + })?; - log::debug!("GetInfo response received ({} bytes)", info_res.len()); + log::debug!("GetInfo response received ({} bytes)", info_res.len()); - let info_val: Value = from_slice(&info_res).map_err(|e| { - log::error!("Failed to parse GetInfo CBOR: {}", e); - PFError::Io(e.to_string()) - })?; + let info_val: Value = from_slice(&info_res).map_err(|e| { + log::error!("Failed to parse GetInfo CBOR: {}", e); + PFError::Io(e.to_string()) + })?; - // NOTE: Key 0x03 is AAGUID, not the unique device Serial. - let aaguid_str = if let Value::Map(m) = &info_val { - m.get(&Value::Integer(0x03)) - .and_then(|v| { - if let Value::Bytes(b) = v { - Some(hex::encode_upper(b)) - } else { - None - } - }) - .unwrap_or_else(|| { - log::warn!("AAGUID not found in GetInfo response"); - "Unknown".into() - }) - } else { - "Unknown".into() - }; + // NOTE: Key 0x03 is AAGUID, not the unique device Serial. + let aaguid_str = if let Value::Map(m) = &info_val { + m.get(&Value::Integer(0x03)) + .and_then(|v| { + if let Value::Bytes(b) = v { + Some(hex::encode_upper(b)) + } else { + None + } + }) + .unwrap_or_else(|| { + log::warn!("AAGUID not found in GetInfo response"); + "Unknown".into() + }) + } else { + "Unknown".into() + }; - let fw_version = if let Value::Map(m) = &info_val { - m.get(&Value::Integer(0x0E)) - .and_then(|v| { - if let Value::Integer(i) = v { - Some(format!("{}.{}", (i >> 8) & 0xFF, i & 0xFF)) - } else { - None - } - }) - .unwrap_or_else(|| { - log::warn!("Firmware version not found in GetInfo response"); - "Unknown".into() - }) - } else { - "Unknown".into() - }; + let fw_version = if let Value::Map(m) = &info_val { + m.get(&Value::Integer(0x0E)) + .and_then(|v| { + if let Value::Integer(i) = v { + Some(format!("{}.{}", (i >> 8) & 0xFF, i & 0xFF)) + } else { + None + } + }) + .unwrap_or_else(|| { + log::warn!("Firmware version not found in GetInfo response"); + "Unknown".into() + }) + } else { + "Unknown".into() + }; - Ok((aaguid_str, fw_version)) + Ok((aaguid_str, fw_version)) } fn read_memory_stats(transport: &HidTransport) -> Result<(u32, u32), PFError> { - log::debug!("Preparing Memory Stats vendor command..."); + log::debug!("Preparing Memory Stats vendor command..."); - let mut mem_req = BTreeMap::new(); - mem_req.insert( - Value::Integer(1), // Sub-command key (usually 1) - Value::Integer(MemorySubCommand::GetStats as i128), - ); + let mut mem_req = BTreeMap::new(); + mem_req.insert( + Value::Integer(1), // Sub-command key (usually 1) + Value::Integer(MemorySubCommand::GetStats as i128), + ); - let mem_cbor = to_vec(&Value::Map(mem_req)).map_err(|e| { - log::error!("Failed to encode Memory Stats CBOR: {}", e); - PFError::Io(format!("CBOR encode error: {}", e)) - })?; + let mem_cbor = to_vec(&Value::Map(mem_req)).map_err(|e| { + log::error!("Failed to encode Memory Stats CBOR: {}", e); + PFError::Io(format!("CBOR encode error: {}", e)) + })?; - let mut mem_payload = vec![VendorCommand::Memory as u8]; - mem_payload.extend(mem_cbor); + let mut mem_payload = vec![VendorCommand::Memory as u8]; + mem_payload.extend(mem_cbor); - log::debug!("Sending Memory Stats command..."); - let mem_res = transport - .send_cbor(CTAP_VENDOR_CBOR_CMD, &mem_payload) - .map_err(|e| { - log::warn!("Failed to fetch memory stats (Vendor Cmd): {}", e); - PFError::Device(format!("Failed to fetch memory stats: {}", e)) - })?; + log::debug!("Sending Memory Stats command..."); + let mem_res = transport + .send_cbor(CTAP_VENDOR_CBOR_CMD, &mem_payload) + .map_err(|e| { + log::warn!("Failed to fetch memory stats (Vendor Cmd): {}", e); + PFError::Device(format!("Failed to fetch memory stats: {}", e)) + })?; - let mem_map: BTreeMap = if !mem_res.is_empty() { - from_slice(&mem_res).map_err(|e| { - log::error!("Failed to parse Memory Stats CBOR response: {}", e); - PFError::Io(format!("Failed to parse Memory Stats CBOR: {}", e)) - })? - } else { - BTreeMap::new() - }; + let mem_map: BTreeMap = if !mem_res.is_empty() { + from_slice(&mem_res).map_err(|e| { + log::error!("Failed to parse Memory Stats CBOR response: {}", e); + PFError::Io(format!("Failed to parse Memory Stats CBOR: {}", e)) + })? + } else { + BTreeMap::new() + }; - let used = mem_map - .get(&(MemoryResponseKey::UsedSpace as i128)) - .cloned() - .unwrap_or(0) as u32; - let total = mem_map - .get(&(MemoryResponseKey::TotalSpace as i128)) - .cloned() - .unwrap_or(0) as u32; + let used = mem_map + .get(&(MemoryResponseKey::UsedSpace as i128)) + .cloned() + .unwrap_or(0) as u32; + let total = mem_map + .get(&(MemoryResponseKey::TotalSpace as i128)) + .cloned() + .unwrap_or(0) as u32; - Ok((used, total)) + Ok((used, total)) } fn read_physical_config(transport: &HidTransport) -> Result { - log::debug!("Preparing Physical Config vendor command..."); + log::debug!("Preparing Physical Config vendor command..."); - // FIX: Only arguments in CBOR map - let mut phy_params = BTreeMap::new(); - phy_params.insert( - Value::Integer(1), // Sub-command key - Value::Integer(PhysicalOptionsSubCommand::GetOptions as i128), - ); + // FIX: Only arguments in CBOR map + let mut phy_params = BTreeMap::new(); + phy_params.insert( + Value::Integer(1), // Sub-command key + Value::Integer(PhysicalOptionsSubCommand::GetOptions as i128), + ); - let phy_cbor = to_vec(&Value::Map(phy_params)).map_err(|e| { - log::error!("Failed to encode Physical Config CBOR: {}", e); - PFError::Io(format!("CBOR encode error: {}", e)) - })?; + let phy_cbor = to_vec(&Value::Map(phy_params)).map_err(|e| { + log::error!("Failed to encode Physical Config CBOR: {}", e); + PFError::Io(format!("CBOR encode error: {}", e)) + })?; - let mut phy_payload = vec![VendorCommand::PhysicalOptions as u8]; - phy_payload.extend(phy_cbor); + let mut phy_payload = vec![VendorCommand::PhysicalOptions as u8]; + phy_payload.extend(phy_cbor); - log::debug!("Sending Physical Config command..."); - let phy_res = transport - .send_cbor(CTAP_VENDOR_CBOR_CMD, &phy_payload) - .unwrap_or_else(|e| { - log::warn!("Failed to fetch physical config (Vendor Cmd): {}", e); - Vec::new() - }); + log::debug!("Sending Physical Config command..."); + let phy_res = transport + .send_cbor(CTAP_VENDOR_CBOR_CMD, &phy_payload) + .unwrap_or_else(|e| { + log::warn!("Failed to fetch physical config (Vendor Cmd): {}", e); + Vec::new() + }); - let mut config = AppConfig { - vid: format!("{:04X}", transport.vid), - pid: format!("{:04X}", transport.pid), - product_name: transport.product_name.clone(), - ..Default::default() - }; + let mut config = AppConfig { + vid: format!("{:04X}", transport.vid), + pid: format!("{:04X}", transport.pid), + product_name: transport.product_name.clone(), + ..Default::default() + }; - if let Ok(Value::Map(m)) = from_slice(&phy_res) { - log::debug!("Parsed Physical Config map successfully"); - if let Some(Value::Integer(v)) = m.get(&Value::Text("gpio".into())) { - config.led_gpio = *v as u8; - } else { - log::warn!("No led_gpio in CBOR map"); - } + if let Ok(Value::Map(m)) = from_slice(&phy_res) { + log::debug!("Parsed Physical Config map successfully"); + if let Some(Value::Integer(v)) = m.get(&Value::Text("gpio".into())) { + config.led_gpio = *v as u8; + } else { + log::warn!("No led_gpio in CBOR map"); + } - if let Some(Value::Integer(v)) = m.get(&Value::Text("brightness".into())) { - config.led_brightness = *v as u8; - } else { - log::warn!("No led_brightness in CBOR map"); - } - } else if !phy_res.is_empty() { - log::warn!("Physical config response was not a valid CBOR map"); - } else { - log::debug!("Physical config response was empty or already handled."); - } + if let Some(Value::Integer(v)) = m.get(&Value::Text("brightness".into())) { + config.led_brightness = *v as u8; + } else { + log::warn!("No led_brightness in CBOR map"); + } + } else if !phy_res.is_empty() { + log::warn!("Physical config response was not a valid CBOR map"); + } else { + log::debug!("Physical config response was empty or already handled."); + } - Ok(config) + Ok(config) } pub fn write_config(config: AppConfigInput, pin: Option) -> Result { - log::info!("Starting FIDO write_config..."); + log::info!("Starting FIDO write_config..."); - let pin_val = pin.as_deref().ok_or_else(|| { - log::error!("write_config called without any security PIN provided"); - PFError::Device( - "A security PIN is required to be set to change the configuration in fido mode".into(), - ) - })?; + let pin_val = pin.as_deref().ok_or_else(|| { + log::error!("write_config called without any security PIN provided"); + PFError::Device( + "A security PIN is required to be set to change the configuration in fido mode".into(), + ) + })?; - // 1. Obtain PIN token using the library handle - let pin_token = { - let device = get_device().map_err(PFError::Device)?; + // 1. Obtain PIN token using the library handle + let pin_token = { + let device = get_device().map_err(PFError::Device)?; - // Try to obtain a token with AuthenticatorConfiguration permission (CTAP 2.1) - match device - .get_pinuv_auth_token_with_permission(pin_val, Permission::AuthenticatorConfiguration) - { - Ok(token) => { - log::debug!("Successfully obtained PIN token with ACFG permission."); - token.key - } - Err(e) => { - log::warn!( - "Failed to get PIN token with ACFG permission (Error: {:?}). Falling back to standard token.", - e - ); - // Fallback to standard PIN token (Subcommand 0x05) - let token = device.get_pin_token(pin_val).map_err(|e2| { - log::error!("Failed to obtain even a standard PIN token: {:?}", e2); - PFError::Device(format!("PIN token acquisition failed: {:?}", e2)) - })?; - log::debug!("Successfully obtained standard PIN token (fallback)."); - token.key - } - } - // Library handle 'device' is dropped here, closing the HID session. - }; + // Try to obtain a token with AuthenticatorConfiguration permission (CTAP 2.1) + match device + .get_pinuv_auth_token_with_permission(pin_val, Permission::AuthenticatorConfiguration) + { + Ok(token) => { + log::debug!("Successfully obtained PIN token with ACFG permission."); + token.key + } + Err(e) => { + log::warn!( + "Failed to get PIN token with ACFG permission (Error: {:?}). Falling back to standard token.", + e + ); + // Fallback to standard PIN token (Subcommand 0x05) + let token = device.get_pin_token(pin_val).map_err(|e2| { + log::error!("Failed to obtain even a standard PIN token: {:?}", e2); + PFError::Device(format!("PIN token acquisition failed: {:?}", e2)) + })?; + log::debug!("Successfully obtained standard PIN token (fallback)."); + token.key + } + } + // Library handle 'device' is dropped here, closing the HID session. + }; - // 2. Open custom HidTransport and send vendor commands using the token - let transport = HidTransport::open().map_err(|e| { - log::error!("Failed to open HID transport: {}", e); - PFError::Device(format!("Could not open HID transport: {}", e)) - })?; + // 2. Open custom HidTransport and send vendor commands using the token + let transport = HidTransport::open().map_err(|e| { + log::error!("Failed to open HID transport: {}", e); + PFError::Device(format!("Could not open HID transport: {}", e)) + })?; - // VID/PID config - if let (Some(vid_str), Some(pid_str)) = (&config.vid, &config.pid) { - let vid = u16::from_str_radix(vid_str, 16).map_err(|e| PFError::Io(e.to_string()))?; - let pid = u16::from_str_radix(pid_str, 16).map_err(|e| PFError::Io(e.to_string()))?; - let vidpid = ((vid as u32) << 16) | (pid as u32); - transport.send_vendor_config( - &pin_token, - VendorConfigCommand::PhysicalVidPid, - Value::Integer(vidpid as i128), - )?; - } else { - log::info!("VID/PID configuration not provided, skipping update."); - } + // VID/PID config + if let (Some(vid_str), Some(pid_str)) = (&config.vid, &config.pid) { + let vid = u16::from_str_radix(vid_str, 16).map_err(|e| PFError::Io(e.to_string()))?; + let pid = u16::from_str_radix(pid_str, 16).map_err(|e| PFError::Io(e.to_string()))?; + let vidpid = ((vid as u32) << 16) | (pid as u32); + transport.send_vendor_config( + &pin_token, + VendorConfigCommand::PhysicalVidPid, + Value::Integer(vidpid as i128), + )?; + } else { + log::info!("VID/PID configuration not provided, skipping update."); + } - // LED GPIO config - if let Some(gpio) = config.led_gpio { - transport.send_vendor_config( - &pin_token, - VendorConfigCommand::PhysicalLedGpio, - Value::Integer(gpio as i128), - )?; - } else { - log::info!("LED GPIO configuration not provided, skipping update."); - } + // LED GPIO config + if let Some(gpio) = config.led_gpio { + transport.send_vendor_config( + &pin_token, + VendorConfigCommand::PhysicalLedGpio, + Value::Integer(gpio as i128), + )?; + } else { + log::info!("LED GPIO configuration not provided, skipping update."); + } - // LED brightness config - if let Some(brightness) = config.led_brightness { - transport.send_vendor_config( - &pin_token, - VendorConfigCommand::PhysicalLedBrightness, - Value::Integer(brightness as i128), - )?; - } else { - log::info!("LED brightness configuration not provided, skipping update."); - } + // LED brightness config + if let Some(brightness) = config.led_brightness { + transport.send_vendor_config( + &pin_token, + VendorConfigCommand::PhysicalLedBrightness, + Value::Integer(brightness as i128), + )?; + } else { + log::info!("LED brightness configuration not provided, skipping update."); + } - // Options config - let mut opts = 0u16; - if config.led_dimmable.unwrap_or(false) { - opts |= 0x02; // PHY_OPT_DIMM - } - if !config.power_cycle_on_reset.unwrap_or(true) { - opts |= 0x04; // PHY_OPT_DISABLE_POWER_RESET - } - if config.led_steady.unwrap_or(false) { - opts |= 0x08; // PHY_OPT_LED_STEADY - } - // Touch_timeout config - if let Some(timeout) = config.touch_timeout { - transport - .send_vendor_config( - &pin_token, - VendorConfigCommand::PhysicalOptions, - Value::Integer(timeout as i128), - ) - .ok(); - } else { - log::info!("Touch timeout configuration not provided, skipping update."); - } + // Options config + let mut opts = 0u16; + if config.led_dimmable.unwrap_or(false) { + opts |= 0x02; // PHY_OPT_DIMM + } + if !config.power_cycle_on_reset.unwrap_or(true) { + opts |= 0x04; // PHY_OPT_DISABLE_POWER_RESET + } + if config.led_steady.unwrap_or(false) { + opts |= 0x08; // PHY_OPT_LED_STEADY + } + // Touch_timeout config + if let Some(timeout) = config.touch_timeout { + transport + .send_vendor_config( + &pin_token, + VendorConfigCommand::PhysicalOptions, + Value::Integer(timeout as i128), + ) + .ok(); + } else { + log::info!("Touch timeout configuration not provided, skipping update."); + } - transport.send_vendor_config( - &pin_token, - VendorConfigCommand::PhysicalOptions, - Value::Integer(opts as i128), - )?; + transport.send_vendor_config( + &pin_token, + VendorConfigCommand::PhysicalOptions, + Value::Integer(opts as i128), + )?; - // ToDo : Product name configuration is not implemented in pico-fido firmware (cbor_config.c)? + // ToDo : Product name configuration is not implemented in pico-fido firmware (cbor_config.c)? - Ok( - "Configuration updated successfully! Unplug and re-plug the device to apply VID/PID changes." - .to_string(), - ) + Ok( + "Configuration updated successfully! Unplug and re-plug the device to apply VID/PID changes." + .to_string(), + ) } diff --git a/src/device/io.rs b/src/device/io.rs index 1f77cba..6cc2a44 100644 --- a/src/device/io.rs +++ b/src/device/io.rs @@ -2,57 +2,57 @@ use crate::{device::error::PFError, device::fido, device::rescue, device::types::*}; pub fn read_device_details() -> Result { - match rescue::read_device_details() { - Ok(status) => Ok(status), - Err(e) => { - log::warn!("Rescue method failed: {}. Falling back to FIDO...", e); - fido::read_device_details() - } - } + match rescue::read_device_details() { + Ok(status) => Ok(status), + Err(e) => { + log::warn!("Rescue method failed: {}. Falling back to FIDO...", e); + fido::read_device_details() + } + } } pub fn write_config( - config: AppConfigInput, - method: DeviceMethod, - pin: Option, + config: AppConfigInput, + method: DeviceMethod, + pin: Option, ) -> Result { - if method == DeviceMethod::Fido { - fido::write_config(config, pin) - } else { - rescue::write_config(config) - } + if method == DeviceMethod::Fido { + fido::write_config(config, pin) + } else { + rescue::write_config(config) + } } pub fn enable_secure_boot(lock: bool) -> Result { - rescue::enable_secure_boot(lock) + rescue::enable_secure_boot(lock) } pub(crate) fn get_fido_info() -> Result { - fido::get_fido_info() + fido::get_fido_info() } pub(crate) fn change_fido_pin( - current_pin: Option, - new_pin: String, + current_pin: Option, + new_pin: String, ) -> Result { - fido::change_fido_pin(current_pin, new_pin) + fido::change_fido_pin(current_pin, new_pin) } pub(crate) fn set_min_pin_length( - current_pin: String, - min_pin_length: u8, + current_pin: String, + min_pin_length: u8, ) -> Result { - fido::set_min_pin_length(current_pin, min_pin_length) + fido::set_min_pin_length(current_pin, min_pin_length) } pub fn reboot(to_bootsel: bool) -> Result { - rescue::reboot_device(to_bootsel) + rescue::reboot_device(to_bootsel) } pub fn get_credentials(pin: String) -> Result, String> { - fido::get_credentials(pin) + fido::get_credentials(pin) } pub fn delete_credential(pin: String, credential_id: String) -> Result { - fido::delete_credential(pin, credential_id) + fido::delete_credential(pin, credential_id) } diff --git a/src/device/logging.rs b/src/device/logging.rs index f4a759f..9602637 100644 --- a/src/device/logging.rs +++ b/src/device/logging.rs @@ -1,19 +1,19 @@ +use directories::ProjectDirs; use log::LevelFilter; use log4rs::{ append::{ console::{ConsoleAppender, Target}, rolling_file::{ - policy::compound::{ - roll::delete::DeleteRoller, trigger::size::SizeTrigger, CompoundPolicy, - }, RollingFileAppender, + policy::compound::{ + CompoundPolicy, roll::delete::DeleteRoller, trigger::size::SizeTrigger, + }, }, }, config::{Appender, Logger, Root}, encode::pattern::PatternEncoder, }; use std::fs; -use directories::ProjectDirs; /// Initializes log4rs with custom configuration for stdout and file logging. pub fn logger_init() { diff --git a/src/device/rescue/constants.rs b/src/device/rescue/constants.rs index 039e03e..9f4c9ed 100644 --- a/src/device/rescue/constants.rs +++ b/src/device/rescue/constants.rs @@ -29,53 +29,53 @@ pub const RESCUE_AID: &[u8] = &[0xA0, 0x58, 0x3F, 0xC1, 0x9B, 0x7E, 0x4F, 0x21]; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RescueInstruction { - KeyDevSign = 0x10, - Write = 0x1C, - Secure = 0x1D, - Read = 0x1E, - Reboot = 0x1F, + KeyDevSign = 0x10, + Write = 0x1C, + Secure = 0x1D, + Read = 0x1E, + Reboot = 0x1F, } /// P1 Parameters for RescueInstruction::Read (0x1E) #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReadParam { - PhyConfig = 0x01, - FlashInfo = 0x02, - SecureBootStatus = 0x03, + PhyConfig = 0x01, + FlashInfo = 0x02, + SecureBootStatus = 0x03, } /// P1 Parameters for WRITE (0x1C) #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WriteParam { - PhyConfig = 0x01, + PhyConfig = 0x01, } /// P1 Parameters for RescueInstruction::KeyDevSign (0x10) #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SignParam { - SignData = 0x01, - GetPublicKey = 0x02, - UploadCert = 0x03, + SignData = 0x01, + GetPublicKey = 0x02, + UploadCert = 0x03, } /// P1 Parameters for RescueInstruction::Reboot (0x1F) #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RebootParam { - Normal = 0x00, - Bootsel = 0x01, + Normal = 0x00, + Bootsel = 0x01, } /// P2 Parameters for SECURE (0x1D) #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum SecureLockParam { - #[default] - Unlock = 0x00, - Lock = 0x01, + #[default] + Unlock = 0x00, + Lock = 0x01, } /// Default P2 value when not used @@ -87,45 +87,45 @@ pub const P2_UNUSED: u8 = 0x00; #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PhyTag { - VidPid = 0x00, - LedGpio = 0x04, - LedBrightness = 0x05, - Opts = 0x06, - PresenceTimeout = 0x08, // Previously TAG_UP_BTN - UsbProduct = 0x09, - Curves = 0x0A, - LedDriver = 0x0C, + VidPid = 0x00, + LedGpio = 0x04, + LedBrightness = 0x05, + Opts = 0x06, + PresenceTimeout = 0x08, // Previously TAG_UP_BTN + UsbProduct = 0x09, + Curves = 0x0A, + LedDriver = 0x0C, } impl PhyTag { - /// Helper to convert raw u8 from device back to Enum - pub fn from_u8(val: u8) -> Option { - match val { - 0x00 => Some(Self::VidPid), - 0x04 => Some(Self::LedGpio), - 0x05 => Some(Self::LedBrightness), - 0x06 => Some(Self::Opts), - 0x08 => Some(Self::PresenceTimeout), - 0x09 => Some(Self::UsbProduct), - 0x0A => Some(Self::Curves), - 0x0C => Some(Self::LedDriver), - _ => None, - } - } + /// Helper to convert raw u8 from device back to Enum + pub fn from_u8(val: u8) -> Option { + match val { + 0x00 => Some(Self::VidPid), + 0x04 => Some(Self::LedGpio), + 0x05 => Some(Self::LedBrightness), + 0x06 => Some(Self::Opts), + 0x08 => Some(Self::PresenceTimeout), + 0x09 => Some(Self::UsbProduct), + 0x0A => Some(Self::Curves), + 0x0C => Some(Self::LedDriver), + _ => None, + } + } } bitflags::bitflags! { - /// Configuration options for TAG_OPTS (Tag 0x06) - pub struct RescueOptions: u16 { - const LED_DIMMABLE = 0x02; - const DISABLE_POWER_RESET = 0x04; - const LED_STEADY = 0x08; - } + /// Configuration options for TAG_OPTS (Tag 0x06) + pub struct RescueOptions: u16 { + const LED_DIMMABLE = 0x02; + const DISABLE_POWER_RESET = 0x04; + const LED_STEADY = 0x08; + } } bitflags::bitflags! { - /// Enabled curves for TAG_CURVES (Tag 0x0A) - pub struct RescueCurves: u32 { - const SECP256K1 = 0x08; - } + /// Enabled curves for TAG_CURVES (Tag 0x0A) + pub struct RescueCurves: u32 { + const SECP256K1 = 0x08; + } } diff --git a/src/device/rescue/mod.rs b/src/device/rescue/mod.rs index 0953a31..cf835bd 100644 --- a/src/device/rescue/mod.rs +++ b/src/device/rescue/mod.rs @@ -11,410 +11,410 @@ use std::io::Cursor; /// Connects to the first available reader and selects the Rescue Applet fn connect_and_select() -> Result<(pcsc::Card, Vec), PFError> { - let ctx = Context::establish(Scope::User).map_err(|e| { - log::error!("Failed to establish PCSC context: {}", e); - PFError::Pcsc(e) - })?; + let ctx = Context::establish(Scope::User).map_err(|e| { + log::error!("Failed to establish PCSC context: {}", e); + PFError::Pcsc(e) + })?; - let mut readers_buf = [0; 2048]; - let mut readers = ctx.list_readers(&mut readers_buf)?; + let mut readers_buf = [0; 2048]; + let mut readers = ctx.list_readers(&mut readers_buf)?; - // Use the first reader found - let reader = readers.next().ok_or_else(|| { - log::info!("No Smart Card Reader found"); - PFError::NoDevice - })?; + // Use the first reader found + let reader = readers.next().ok_or_else(|| { + log::info!("No Smart Card Reader found"); + PFError::NoDevice + })?; - let card = ctx.connect(reader, ShareMode::Shared, Protocols::ANY)?; + let card = ctx.connect(reader, ShareMode::Shared, Protocols::ANY)?; - // Select Applet APDU: 00 A4 04 04 [Len] [AID] - let mut apdu = vec![ - APDU_CLA_ISO, - APDU_INS_SELECT, - APDU_P1_SELECT_BY_DF_NAME, - APDU_P2_RETURN_FCI, - RESCUE_AID.len() as u8, - ]; - apdu.extend_from_slice(RESCUE_AID); + // Select Applet APDU: 00 A4 04 04 [Len] [AID] + let mut apdu = vec![ + APDU_CLA_ISO, + APDU_INS_SELECT, + APDU_P1_SELECT_BY_DF_NAME, + APDU_P2_RETURN_FCI, + RESCUE_AID.len() as u8, + ]; + apdu.extend_from_slice(RESCUE_AID); - let mut rx_buf = [0; 256]; - let rx = card.transmit(&apdu, &mut rx_buf)?; + let mut rx_buf = [0; 256]; + let rx = card.transmit(&apdu, &mut rx_buf)?; - // Check Success (0x90 0x00) - if !rx.ends_with(&[0x90, 0x00]) { - log::error!("Rescue Applet not found on the device!"); - return Err(PFError::Device( - // There is no such mode as fido, i tink the rescue applet stays active and at the same time fido mode works? - // Need to study this more. - "Rescue Applet not found on device. Is it in FIDO mode?".into(), - )); - } + // Check Success (0x90 0x00) + if !rx.ends_with(&[0x90, 0x00]) { + log::error!("Rescue Applet not found on the device!"); + return Err(PFError::Device( + // There is no such mode as fido, i tink the rescue applet stays active and at the same time fido mode works? + // Need to study this more. + "Rescue Applet not found on device. Is it in FIDO mode?".into(), + )); + } - log::info!("Successfully connected to Rescue Applet"); - Ok((card, rx.to_vec())) + log::info!("Successfully connected to Rescue Applet"); + Ok((card, rx.to_vec())) } pub fn read_device_details() -> Result { - log::info!("Reading full device details"); - let (card, select_resp) = connect_and_select()?; + log::info!("Reading full device details"); + let (card, select_resp) = connect_and_select()?; - log::info!("Select Response: {:?}", select_resp); + log::info!("Select Response: {:?}", select_resp); - // FIX: Relax the length check. - // Minimum valid response is 4 bytes data + 2 bytes SW = 6 bytes. - if select_resp.len() < 6 { - log::error!("Invalid select response length: {}", select_resp.len()); - return Err(PFError::Device("Invalid select response".into())); - } + // FIX: Relax the length check. + // Minimum valid response is 4 bytes data + 2 bytes SW = 6 bytes. + if select_resp.len() < 6 { + log::error!("Invalid select response length: {}", select_resp.len()); + return Err(PFError::Device("Invalid select response".into())); + } - let version_major = select_resp[2]; - let version_minor = select_resp[3]; + let version_major = select_resp[2]; + let version_minor = select_resp[3]; - // FIX: Handle missing Serial Number safely - // If the firmware sends 14 bytes, we have a serial. If it sends 6, we don't. - let serial_str = if select_resp.len() >= 14 { - hex::encode_upper(&select_resp[4..12]) - } else { - log::warn!( - "Device did not return a Serial Number (Firmware mismatch?). Using placeholder." - ); - "00000000".to_string() - }; + // FIX: Handle missing Serial Number safely + // If the firmware sends 14 bytes, we have a serial. If it sends 6, we don't. + let serial_str = if select_resp.len() >= 14 { + hex::encode_upper(&select_resp[4..12]) + } else { + log::warn!( + "Device did not return a Serial Number (Firmware mismatch?). Using placeholder." + ); + "00000000".to_string() + }; - log::info!("Device Version: {}.{}", version_major, version_minor); - log::info!("Device Serial: {}", serial_str); + log::info!("Device Version: {}.{}", version_major, version_minor); + log::info!("Device Serial: {}", serial_str); - // 2. Read Flash Info - let mut rx_buf = [0; 256]; - let rx_flash = card.transmit( - &[ - APDU_CLA_PROPRIETARY, - RescueInstruction::Read as u8, - ReadParam::FlashInfo as u8, - P2_UNUSED, - 0x00, // Le - ], - &mut rx_buf, - )?; + // 2. Read Flash Info + let mut rx_buf = [0; 256]; + let rx_flash = card.transmit( + &[ + APDU_CLA_PROPRIETARY, + RescueInstruction::Read as u8, + ReadParam::FlashInfo as u8, + P2_UNUSED, + 0x00, // Le + ], + &mut rx_buf, + )?; - if !rx_flash.ends_with(&SW_SUCCESS) { - return Err(PFError::Device("Failed to read flash".into())); - } + if !rx_flash.ends_with(&SW_SUCCESS) { + return Err(PFError::Device("Failed to read flash".into())); + } - let mut rdr = Cursor::new(&rx_flash[..rx_flash.len() - 2]); - let _free = rdr.read_u32::().unwrap_or(0); - let used = rdr.read_u32::().unwrap_or(0); - let total = rdr.read_u32::().unwrap_or(0); + let mut rdr = Cursor::new(&rx_flash[..rx_flash.len() - 2]); + let _free = rdr.read_u32::().unwrap_or(0); + let used = rdr.read_u32::().unwrap_or(0); + let total = rdr.read_u32::().unwrap_or(0); - // NOTE: captured but currently unused variables - let _nfiles = rdr.read_u32::().unwrap_or(0); - let _chip_size = rdr.read_u32::().unwrap_or(0); + // NOTE: captured but currently unused variables + let _nfiles = rdr.read_u32::().unwrap_or(0); + let _chip_size = rdr.read_u32::().unwrap_or(0); - // --- Read Secure Boot Status --- - let rx_secure = card.transmit( - &[ - APDU_CLA_PROPRIETARY, - RescueInstruction::Read as u8, - ReadParam::SecureBootStatus as u8, - P2_UNUSED, - 0x00, - ], - &mut rx_buf, - )?; + // --- Read Secure Boot Status --- + let rx_secure = card.transmit( + &[ + APDU_CLA_PROPRIETARY, + RescueInstruction::Read as u8, + ReadParam::SecureBootStatus as u8, + P2_UNUSED, + 0x00, + ], + &mut rx_buf, + )?; - let (sb_enabled, sb_locked) = if rx_secure.ends_with(&[0x90, 0x00]) && rx_secure.len() >= 4 { - (rx_secure[0] != 0, rx_secure[1] != 0) - } else { - (false, false) - }; // --- Read PHY Config --- - let rx_phy = card.transmit( - &[ - APDU_CLA_PROPRIETARY, - RescueInstruction::Read as u8, - ReadParam::PhyConfig as u8, - 0x01, - 0x00, - ], - &mut rx_buf, - )?; + let (sb_enabled, sb_locked) = if rx_secure.ends_with(&[0x90, 0x00]) && rx_secure.len() >= 4 { + (rx_secure[0] != 0, rx_secure[1] != 0) + } else { + (false, false) + }; // --- Read PHY Config --- + let rx_phy = card.transmit( + &[ + APDU_CLA_PROPRIETARY, + RescueInstruction::Read as u8, + ReadParam::PhyConfig as u8, + 0x01, + 0x00, + ], + &mut rx_buf, + )?; - if !rx_phy.ends_with(&[0x90, 0x00]) { - return Err(PFError::Device("Failed to read config".into())); - } + if !rx_phy.ends_with(&[0x90, 0x00]) { + return Err(PFError::Device("Failed to read config".into())); + } - // Parse TLV - let mut config = AppConfig::default(); - let data = &rx_phy[..rx_phy.len() - 2]; - let mut i = 0; - while i < data.len() { - if i + 2 > data.len() { - break; - } - let tag_byte = data[i]; - let len = data[i + 1] as usize; - i += 2; - if i + len > data.len() { - break; - } - let val = &data[i..i + len]; + // Parse TLV + let mut config = AppConfig::default(); + let data = &rx_phy[..rx_phy.len() - 2]; + let mut i = 0; + while i < data.len() { + if i + 2 > data.len() { + break; + } + let tag_byte = data[i]; + let len = data[i + 1] as usize; + i += 2; + if i + len > data.len() { + break; + } + let val = &data[i..i + len]; - if let Some(tag) = PhyTag::from_u8(tag_byte) { - match tag { - PhyTag::VidPid => { - if val.len() == 4 { - let vid = u16::from_be_bytes([val[0], val[1]]); - let pid = u16::from_be_bytes([val[2], val[3]]); - config.vid = format!("{:04X}", vid); - config.pid = format!("{:04X}", pid); - } - } - PhyTag::LedGpio => { - if !val.is_empty() { - config.led_gpio = val[0]; - } - } - PhyTag::LedBrightness => { - if !val.is_empty() { - config.led_brightness = val[0]; - } - } - PhyTag::PresenceTimeout => { - if !val.is_empty() { - config.touch_timeout = val[0]; - } - } - PhyTag::UsbProduct => { - let s = std::str::from_utf8(val) - .unwrap_or("") - .trim_matches(char::from(0)); - config.product_name = s.to_string(); - } - PhyTag::Opts => { - if val.len() >= 2 { - let opts_val = u16::from_be_bytes([val[0], val[1]]); - let opts = RescueOptions::from_bits_truncate(opts_val); + if let Some(tag) = PhyTag::from_u8(tag_byte) { + match tag { + PhyTag::VidPid => { + if val.len() == 4 { + let vid = u16::from_be_bytes([val[0], val[1]]); + let pid = u16::from_be_bytes([val[2], val[3]]); + config.vid = format!("{:04X}", vid); + config.pid = format!("{:04X}", pid); + } + } + PhyTag::LedGpio => { + if !val.is_empty() { + config.led_gpio = val[0]; + } + } + PhyTag::LedBrightness => { + if !val.is_empty() { + config.led_brightness = val[0]; + } + } + PhyTag::PresenceTimeout => { + if !val.is_empty() { + config.touch_timeout = val[0]; + } + } + PhyTag::UsbProduct => { + let s = std::str::from_utf8(val) + .unwrap_or("") + .trim_matches(char::from(0)); + config.product_name = s.to_string(); + } + PhyTag::Opts => { + if val.len() >= 2 { + let opts_val = u16::from_be_bytes([val[0], val[1]]); + let opts = RescueOptions::from_bits_truncate(opts_val); - config.led_dimmable = opts.contains(RescueOptions::LED_DIMMABLE); - config.power_cycle_on_reset = - !opts.contains(RescueOptions::DISABLE_POWER_RESET); - config.led_steady = opts.contains(RescueOptions::LED_STEADY); - } - } - PhyTag::Curves => { - if val.len() >= 4 { - let curves_val = u32::from_be_bytes([val[0], val[1], val[2], val[3]]); - let curves = RescueCurves::from_bits_truncate(curves_val); - config.enable_secp256k1 = curves.contains(RescueCurves::SECP256K1); - } - } - PhyTag::LedDriver => { - if !val.is_empty() { - config.led_driver = Some(val[0]); - } - } - } - } - i += len; - } + config.led_dimmable = opts.contains(RescueOptions::LED_DIMMABLE); + config.power_cycle_on_reset = + !opts.contains(RescueOptions::DISABLE_POWER_RESET); + config.led_steady = opts.contains(RescueOptions::LED_STEADY); + } + } + PhyTag::Curves => { + if val.len() >= 4 { + let curves_val = u32::from_be_bytes([val[0], val[1], val[2], val[3]]); + let curves = RescueCurves::from_bits_truncate(curves_val); + config.enable_secp256k1 = curves.contains(RescueCurves::SECP256K1); + } + } + PhyTag::LedDriver => { + if !val.is_empty() { + config.led_driver = Some(val[0]); + } + } + } + } + i += len; + } - log::info!( - "Successfully read device details - Serial: {}, Firmware: {}.{}", - serial_str, - version_major, - version_minor - ); + log::info!( + "Successfully read device details - Serial: {}, Firmware: {}.{}", + serial_str, + version_major, + version_minor + ); - Ok(FullDeviceStatus { - info: DeviceInfo { - serial: serial_str, - flash_used: used / 1024, - flash_total: total / 1024, - firmware_version: format!("{}.{}", version_major, version_minor), - }, - config, - secure_boot: sb_enabled, - secure_lock: sb_locked, - method: DeviceMethod::Rescue, - }) + Ok(FullDeviceStatus { + info: DeviceInfo { + serial: serial_str, + flash_used: used / 1024, + flash_total: total / 1024, + firmware_version: format!("{}.{}", version_major, version_minor), + }, + config, + secure_boot: sb_enabled, + secure_lock: sb_locked, + method: DeviceMethod::Rescue, + }) } pub fn write_config(config: AppConfigInput) -> Result { - log::info!("Writing configuration to device"); - log::debug!("Config input: {:?}", config); + log::info!("Writing configuration to device"); + log::debug!("Config input: {:?}", config); - // 1. Construct TLV Blob - let mut tlv = Vec::new(); + // 1. Construct TLV Blob + let mut tlv = Vec::new(); - // VID:PID (Tag 0x00) - if let (Some(vid_str), Some(pid_str)) = (&config.vid, &config.pid) { - let vid = - u16::from_str_radix(vid_str, 16).map_err(|_| PFError::Io("Invalid VID".into()))?; - let pid = - u16::from_str_radix(pid_str, 16).map_err(|_| PFError::Io("Invalid PID".into()))?; + // VID:PID (Tag 0x00) + if let (Some(vid_str), Some(pid_str)) = (&config.vid, &config.pid) { + let vid = + u16::from_str_radix(vid_str, 16).map_err(|_| PFError::Io("Invalid VID".into()))?; + let pid = + u16::from_str_radix(pid_str, 16).map_err(|_| PFError::Io("Invalid PID".into()))?; - tlv.push(PhyTag::VidPid as u8); - tlv.push(0x04); - tlv.write_u16::(vid).unwrap(); - tlv.write_u16::(pid).unwrap(); - } + tlv.push(PhyTag::VidPid as u8); + tlv.push(0x04); + tlv.write_u16::(vid).unwrap(); + tlv.write_u16::(pid).unwrap(); + } - // LED GPIO (Tag 0x04) - if let Some(val) = config.led_gpio { - tlv.push(PhyTag::LedGpio as u8); - tlv.push(0x01); - tlv.push(val); - } + // LED GPIO (Tag 0x04) + if let Some(val) = config.led_gpio { + tlv.push(PhyTag::LedGpio as u8); + tlv.push(0x01); + tlv.push(val); + } - // LED Brightness (Tag 0x05) - if let Some(val) = config.led_brightness { - tlv.push(PhyTag::LedBrightness as u8); - tlv.push(0x01); - tlv.push(val); - } + // LED Brightness (Tag 0x05) + if let Some(val) = config.led_brightness { + tlv.push(PhyTag::LedBrightness as u8); + tlv.push(0x01); + tlv.push(val); + } - // Touch Timeout (Tag 0x08) - if let Some(val) = config.touch_timeout { - tlv.push(PhyTag::PresenceTimeout as u8); - tlv.push(0x01); - tlv.push(val); - } + // Touch Timeout (Tag 0x08) + if let Some(val) = config.touch_timeout { + tlv.push(PhyTag::PresenceTimeout as u8); + tlv.push(0x01); + tlv.push(val); + } - // Options - if let (Some(dim), Some(cycle), Some(steady)) = ( - config.led_dimmable, - config.power_cycle_on_reset, - config.led_steady, - ) { - let mut opts = RescueOptions::empty(); - if dim { - opts.insert(RescueOptions::LED_DIMMABLE); - } - if !cycle { - opts.insert(RescueOptions::DISABLE_POWER_RESET); - } - if steady { - opts.insert(RescueOptions::LED_STEADY); - } + // Options + if let (Some(dim), Some(cycle), Some(steady)) = ( + config.led_dimmable, + config.power_cycle_on_reset, + config.led_steady, + ) { + let mut opts = RescueOptions::empty(); + if dim { + opts.insert(RescueOptions::LED_DIMMABLE); + } + if !cycle { + opts.insert(RescueOptions::DISABLE_POWER_RESET); + } + if steady { + opts.insert(RescueOptions::LED_STEADY); + } - tlv.push(PhyTag::Opts as u8); - tlv.push(0x02); - tlv.write_u16::(opts.bits()).unwrap(); - } + tlv.push(PhyTag::Opts as u8); + tlv.push(0x02); + tlv.write_u16::(opts.bits()).unwrap(); + } - // Curves - if let Some(enabled) = config.enable_secp256k1 { - let mut curves = RescueCurves::empty(); - if enabled { - curves.insert(RescueCurves::SECP256K1); - } + // Curves + if let Some(enabled) = config.enable_secp256k1 { + let mut curves = RescueCurves::empty(); + if enabled { + curves.insert(RescueCurves::SECP256K1); + } - tlv.push(PhyTag::Curves as u8); - tlv.push(0x04); - tlv.write_u32::(curves.bits()).unwrap(); - } + tlv.push(PhyTag::Curves as u8); + tlv.push(0x04); + tlv.write_u32::(curves.bits()).unwrap(); + } - // LED Driver (Tag 0x0C) - if let Some(val) = config.led_driver { - tlv.push(PhyTag::LedDriver as u8); - tlv.push(0x01); - tlv.push(val); - } + // LED Driver (Tag 0x0C) + if let Some(val) = config.led_driver { + tlv.push(PhyTag::LedDriver as u8); + tlv.push(0x01); + tlv.push(val); + } - // Product Name (Tag 0x09) - if let Some(name) = config.product_name.filter(|n| !n.is_empty()) { - let name_bytes = name.as_bytes(); - let len = name_bytes.len() + 1; - if len > 32 { - return Err(PFError::Io("Product name too long".into())); - } + // Product Name (Tag 0x09) + if let Some(name) = config.product_name.filter(|n| !n.is_empty()) { + let name_bytes = name.as_bytes(); + let len = name_bytes.len() + 1; + if len > 32 { + return Err(PFError::Io("Product name too long".into())); + } - tlv.push(PhyTag::UsbProduct as u8); - tlv.push(len as u8); - tlv.extend_from_slice(name_bytes); - tlv.push(0x00); // Null terminator - } + tlv.push(PhyTag::UsbProduct as u8); + tlv.push(len as u8); + tlv.extend_from_slice(name_bytes); + tlv.push(0x00); // Null terminator + } - // 2. Connect and Send - if tlv.is_empty() { - log::warn!("No configuration changes to apply"); - return Ok("No changes to apply".into()); - } + // 2. Connect and Send + if tlv.is_empty() { + log::warn!("No configuration changes to apply"); + return Ok("No changes to apply".into()); + } - log::debug!("TLV payload size: {} bytes", tlv.len()); + log::debug!("TLV payload size: {} bytes", tlv.len()); - let (card, _) = connect_and_select()?; + let (card, _) = connect_and_select()?; - // APDU: 80 1C 01 00 [Lc] [Data] - let mut apdu = vec![ - APDU_CLA_PROPRIETARY, - RescueInstruction::Write as u8, - WriteParam::PhyConfig as u8, - P2_UNUSED, - tlv.len() as u8, // Lc - ]; - apdu.extend_from_slice(&tlv); + // APDU: 80 1C 01 00 [Lc] [Data] + let mut apdu = vec![ + APDU_CLA_PROPRIETARY, + RescueInstruction::Write as u8, + WriteParam::PhyConfig as u8, + P2_UNUSED, + tlv.len() as u8, // Lc + ]; + apdu.extend_from_slice(&tlv); - let mut rx_buf = [0; 256]; - let rx = card.transmit(&apdu, &mut rx_buf)?; + let mut rx_buf = [0; 256]; + let rx = card.transmit(&apdu, &mut rx_buf)?; - if rx.ends_with(&[0x90, 0x00]) { - log::info!("Configuration applied successfully"); - Ok("Configuration Applied Successfully".into()) - } else { - log::error!("Configuration write failed: {:02X?}", rx); - Err(PFError::Device(format!("Write failed: {:02X?}", rx))) - } + if rx.ends_with(&[0x90, 0x00]) { + log::info!("Configuration applied successfully"); + Ok("Configuration Applied Successfully".into()) + } else { + log::error!("Configuration write failed: {:02X?}", rx); + Err(PFError::Device(format!("Write failed: {:02X?}", rx))) + } } pub fn reboot_device(to_bootsel: bool) -> Result { - let (card, _) = connect_and_select()?; + let (card, _) = connect_and_select()?; - let param = if to_bootsel { - RebootParam::Bootsel - } else { - RebootParam::Normal - }; + let param = if to_bootsel { + RebootParam::Bootsel + } else { + RebootParam::Normal + }; - let apdu = [ - APDU_CLA_PROPRIETARY, - RescueInstruction::Reboot as u8, - param as u8, - P2_UNUSED, - 0x00, - ]; + let apdu = [ + APDU_CLA_PROPRIETARY, + RescueInstruction::Reboot as u8, + param as u8, + P2_UNUSED, + 0x00, + ]; - let mut rx_buf = [0; 256]; - let rx = card.transmit(&apdu, &mut rx_buf)?; + let mut rx_buf = [0; 256]; + let rx = card.transmit(&apdu, &mut rx_buf)?; - if rx.ends_with(&SW_SUCCESS) { - Ok("Reboot command sent".into()) - } else { - Err(PFError::Device(format!("Reboot failed: {:02X?}", rx))) - } + if rx.ends_with(&SW_SUCCESS) { + Ok("Reboot command sent".into()) + } else { + Err(PFError::Device(format!("Reboot failed: {:02X?}", rx))) + } } /// UNSTABLE! (WIP) pub fn enable_secure_boot(lock: bool) -> Result { - let (card, _) = connect_and_select()?; + let (card, _) = connect_and_select()?; - // APDU: 80 1D [KeyIndex] [LockBool] 00 - // KeyIndex = 0 (Default), LockBool = 1 if true - let lock_byte = if lock { 0x01 } else { 0x00 }; + // APDU: 80 1D [KeyIndex] [LockBool] 00 + // KeyIndex = 0 (Default), LockBool = 1 if true + let lock_byte = if lock { 0x01 } else { 0x00 }; - let apdu = [ - APDU_CLA_PROPRIETARY, - RescueInstruction::Secure as u8, - 0x00, // Boot Key Index (0 = Default) - lock_byte as u8, - 0x00, - ]; + let apdu = [ + APDU_CLA_PROPRIETARY, + RescueInstruction::Secure as u8, + 0x00, // Boot Key Index (0 = Default) + lock_byte as u8, + 0x00, + ]; - let mut rx_buf = [0; 256]; - let rx = card.transmit(&apdu, &mut rx_buf)?; + let mut rx_buf = [0; 256]; + let rx = card.transmit(&apdu, &mut rx_buf)?; - if rx.ends_with(&[0x90, 0x00]) { - Ok("Secure Boot Enabled".into()) - } else { - Err(PFError::Device(format!("Secure Boot failed: {:02X?}", rx))) - } + if rx.ends_with(&[0x90, 0x00]) { + Ok("Secure Boot Enabled".into()) + } else { + Err(PFError::Device(format!("Secure Boot failed: {:02X?}", rx))) + } } diff --git a/src/device/types.rs b/src/device/types.rs index cbff91e..fbe4386 100644 --- a/src/device/types.rs +++ b/src/device/types.rs @@ -3,66 +3,66 @@ use serde::{Deserialize, Serialize}; struct PForgeState { - device_info: DeviceInfo, + device_info: DeviceInfo, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct DeviceInfo { - pub serial: String, - pub flash_used: u32, - pub flash_total: u32, - pub firmware_version: String, + pub serial: String, + pub flash_used: u32, + pub flash_total: u32, + pub firmware_version: String, } #[derive(Serialize, Deserialize, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct AppConfig { - pub vid: String, - pub pid: String, - pub product_name: String, - pub led_gpio: u8, - pub led_brightness: u8, - pub touch_timeout: u8, - #[serde(skip_serializing_if = "Option::is_none")] - pub led_driver: Option, - pub led_dimmable: bool, - pub power_cycle_on_reset: bool, - pub led_steady: bool, - pub enable_secp256k1: bool, + pub vid: String, + pub pid: String, + pub product_name: String, + pub led_gpio: u8, + pub led_brightness: u8, + pub touch_timeout: u8, + #[serde(skip_serializing_if = "Option::is_none")] + pub led_driver: Option, + pub led_dimmable: bool, + pub power_cycle_on_reset: bool, + pub led_steady: bool, + pub enable_secp256k1: bool, } #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct AppConfigInput { - pub vid: Option, - pub pid: Option, - pub product_name: Option, - pub led_gpio: Option, - pub led_brightness: Option, - pub touch_timeout: Option, - pub led_driver: Option, - pub led_dimmable: Option, - pub power_cycle_on_reset: Option, - pub led_steady: Option, - pub enable_secp256k1: Option, + pub vid: Option, + pub pid: Option, + pub product_name: Option, + pub led_gpio: Option, + pub led_brightness: Option, + pub touch_timeout: Option, + pub led_driver: Option, + pub led_dimmable: Option, + pub power_cycle_on_reset: Option, + pub led_steady: Option, + pub enable_secp256k1: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct FullDeviceStatus { - pub info: DeviceInfo, - pub config: AppConfig, - pub secure_boot: bool, - pub secure_lock: bool, - pub method: DeviceMethod, + pub info: DeviceInfo, + pub config: AppConfig, + pub secure_boot: bool, + pub secure_lock: bool, + pub method: DeviceMethod, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum DeviceMethod { - #[serde(rename = "FIDO")] - Fido, - Rescue, + #[serde(rename = "FIDO")] + Fido, + Rescue, } // Fido stuff: @@ -70,24 +70,24 @@ pub enum DeviceMethod { #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct FidoDeviceInfo { - pub versions: Vec, - pub extensions: Vec, - pub aaguid: String, - pub options: std::collections::HashMap, - pub max_msg_size: i32, - pub pin_protocols: Vec, - // pub remaining_disc_creds: u32, - pub min_pin_length: u32, - pub firmware_version: String, + pub versions: Vec, + pub extensions: Vec, + pub aaguid: String, + pub options: std::collections::HashMap, + pub max_msg_size: i32, + pub pin_protocols: Vec, + // pub remaining_disc_creds: u32, + pub min_pin_length: u32, + pub firmware_version: String, } #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct StoredCredential { - pub rp_id: String, - pub rp_name: String, - pub user_name: String, - pub user_display_name: String, - pub user_id: String, - pub credential_id: String, + pub rp_id: String, + pub rp_name: String, + pub user_name: String, + pub user_display_name: String, + pub user_id: String, + pub credential_id: String, } diff --git a/src/main.rs b/src/main.rs index 04e1188..b7a26e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,69 +2,66 @@ use gpui::*; use gpui_component::Root; use gpui_component::{Theme, ThemeMode}; use ui::rootview::ApplicationRoot; -// use crate::ui::assets::Assets; mod device; mod ui; fn main() { - // TODO: Configure and add custom assets. - // let app = Application::new().with_assets(gpui_component_assets::Assets); - let app = Application::new().with_assets(ui::assets::Assets); + let app = Application::new().with_assets(ui::assets::Assets); - app.run(move |cx| { - gpui_component::init(cx); - Theme::change(ThemeMode::Dark, None, cx); - // Theme::change(ThemeMode::Dark, Some(ui::theme::dark_theme()), cx); + app.run(move |cx| { + gpui_component::init(cx); + Theme::change(ThemeMode::Dark, None, cx); + // Theme::change(ThemeMode::Dark, Some(ui::theme::dark_theme()), cx); - cx.activate(true); + cx.activate(true); - let mut window_size = size(px(1280.0), px(720.0)); + let mut window_size = size(px(1280.0), px(720.0)); - // Basically, make sure that the window is max to max 85 percent size of the actual monitor/display, - // so the window does not get too big on small monitors. - if let Some(display) = cx.primary_display() { - let display_size = display.bounds().size; + // Basically, make sure that the window is max to max 85 percent size of the actual + // monitor/display, so the window does not get too big on small monitors. + if let Some(display) = cx.primary_display() { + let display_size = display.bounds().size; - window_size.width = window_size.width.min(display_size.width * 0.85); - window_size.height = window_size.height.min(display_size.height * 0.85); - } + window_size.width = window_size.width.min(display_size.width * 0.85); + window_size.height = window_size.height.min(display_size.height * 0.85); + } - let window_bounds = Bounds::centered(None, window_size, cx); + let window_bounds = Bounds::centered(None, window_size, cx); - cx.spawn(async move |cx| { - let window_options = WindowOptions { - app_id: Some("in.suyogtandel.picoforge".into()), + cx.spawn(async move |cx| { + let window_options = WindowOptions { + app_id: Some("in.suyogtandel.picoforge".into()), - window_bounds: Some(WindowBounds::Windowed(window_bounds)), + window_bounds: Some(WindowBounds::Windowed(window_bounds)), - titlebar: Some(TitlebarOptions { - title: Some("PicoForge".into()), - appears_transparent: true, - // TODO: This option needs to be tested and adjusted on macos - traffic_light_position: Some(gpui::point(px(12.0), px(12.0))), - }), + titlebar: Some(TitlebarOptions { + title: Some("PicoForge".into()), + appears_transparent: true, + // TODO: This option needs to be tested and adjusted on macos + traffic_light_position: Some(gpui::point(px(12.0), px(12.0))), + }), - #[cfg(any(target_os = "linux", target_os = "freebsd"))] - window_background: gpui::WindowBackgroundAppearance::Transparent, - #[cfg(any(target_os = "linux", target_os = "freebsd"))] - window_decorations: Some(gpui::WindowDecorations::Client), + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + window_background: gpui::WindowBackgroundAppearance::Transparent, + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + window_decorations: Some(gpui::WindowDecorations::Client), - window_min_size: Some(gpui::Size { - width: px(650.), - height: px(300.), - }), - kind: WindowKind::Normal, - ..Default::default() - }; + window_min_size: Some(gpui::Size { + width: px(650.), + height: px(300.), + }), + kind: WindowKind::Normal, + ..Default::default() + }; - cx.open_window(window_options, |window, cx| { - let view = cx.new(|_| ApplicationRoot::new()); - cx.new(|cx| Root::new(view, window, cx)) - })?; + cx.open_window(window_options, |window, cx| { + let view = cx.new(|_| ApplicationRoot::new()); + cx.new(|cx| Root::new(view, window, cx)) + })?; - Ok::<_, anyhow::Error>(()) - }) - .detach(); - }); + Ok::<_, anyhow::Error>(()) + }) + .detach(); + }); } diff --git a/src/ui/assets.rs b/src/ui/assets.rs index 5f02101..bff2b52 100644 --- a/src/ui/assets.rs +++ b/src/ui/assets.rs @@ -11,19 +11,19 @@ use std::borrow::Cow; pub struct Assets; impl AssetSource for Assets { - fn load(&self, path: &str) -> Result>> { - if path.is_empty() { - return Ok(None); - } + fn load(&self, path: &str) -> Result>> { + if path.is_empty() { + return Ok(None); + } - Self::get(path) - .map(|f| Some(f.data)) - .ok_or_else(|| anyhow!("could not find asset at path \"{path}\"")) - } + Self::get(path) + .map(|f| Some(f.data)) + .ok_or_else(|| anyhow!("could not find asset at path \"{path}\"")) + } - fn list(&self, path: &str) -> Result> { - Ok(Self::iter() - .filter_map(|p| p.starts_with(path).then(|| p.into())) - .collect()) - } + fn list(&self, path: &str) -> Result> { + Ok(Self::iter() + .filter_map(|p| p.starts_with(path).then(|| p.into())) + .collect()) + } } diff --git a/src/ui/colors.rs b/src/ui/colors.rs index 218f207..9a38649 100644 --- a/src/ui/colors.rs +++ b/src/ui/colors.rs @@ -11,4 +11,4 @@ pub mod zinc { pub const ZINC800: u32 = 0x27272a; pub const ZINC900: u32 = 0x18181b; pub const ZINC950: u32 = 0x09090b; -} \ No newline at end of file +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 43ff877..d14489d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,4 +1,4 @@ -pub mod views; pub mod assets; -pub mod rootview; pub mod colors; +pub mod rootview; +pub mod views; diff --git a/src/ui/rootview.rs b/src/ui/rootview.rs index d52105e..e7d2bd6 100644 --- a/src/ui/rootview.rs +++ b/src/ui/rootview.rs @@ -1,141 +1,141 @@ +use crate::ui::colors; use crate::ui::views::{ - about::AboutView, config::ConfigView, home::HomeView, logs::LogsView, passkeys::PasskeysView, - security::SecurityView, + about::AboutView, config::ConfigView, home::HomeView, logs::LogsView, passkeys::PasskeysView, + security::SecurityView, }; use gpui::*; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::scroll::ScrollableElement; -use gpui_component::{ActiveTheme, Icon, IconName, TitleBar, h_flex, v_flex}; +use gpui_component::{ActiveTheme, Icon, IconName, StyledExt, TitleBar, h_flex, v_flex}; use gpui_component::{Side, sidebar::*}; #[derive(Clone, Copy, PartialEq)] enum ActiveView { - Home, - Passkeys, - Configuration, - Security, - Logs, - About, + Home, + Passkeys, + Configuration, + Security, + Logs, + About, } pub struct ApplicationRoot { - active_view: ActiveView, - collapsed: bool, + active_view: ActiveView, + collapsed: bool, } impl ApplicationRoot { - pub fn new() -> Self { - Self { - active_view: ActiveView::Home, - collapsed: false, - } - } + pub fn new() -> Self { + Self { + active_view: ActiveView::Home, + collapsed: false, + } + } } impl Render for ApplicationRoot { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - h_flex() - .size_full() - .child( - Sidebar::new(Side::Left) - .collapsed(self.collapsed) - .collapsible(true) - .h_full() - .bg(rgb(0x18181b)) - // .header(SidebarHeader::new().child("PicoForge")) - .child( - SidebarGroup::new("Menu").child( - SidebarMenu::new() - .child( - SidebarMenuItem::new("Home") - .icon(Icon::default().path("icons/house.svg")) - .active(self.active_view == ActiveView::Home) - .on_click(cx.listener(|this, _, _, _| { - this.active_view = ActiveView::Home; - })), - ) - .child( - SidebarMenuItem::new("Passkeys") - .icon(Icon::default().path("icons/key-round.svg")) - .active(self.active_view == ActiveView::Passkeys) - .on_click(cx.listener(|this, _, _, _| { - this.active_view = ActiveView::Passkeys; - })), - ) - .child( - SidebarMenuItem::new("Configuration") - .icon(Icon::default().path("icons/settings.svg")) - .active(self.active_view == ActiveView::Configuration) - .on_click(cx.listener(|this, _, _, _| { - this.active_view = ActiveView::Configuration; - })), - ) - // TODO: Replace these icons with correct ones from lucide - .child( - SidebarMenuItem::new("Security") - .icon(Icon::default().path("icons/shield-check.svg")) - .active(self.active_view == ActiveView::Security) - .on_click(cx.listener(|this, _, _, _| { - this.active_view = ActiveView::Security; - })), - ) - .child( - SidebarMenuItem::new("Logs") - .icon(Icon::default().path("icons/scroll-text.svg")) - .active(self.active_view == ActiveView::Logs) - .on_click(cx.listener(|this, _, _, _| { - this.active_view = ActiveView::Logs; - })), - ) - .child( - SidebarMenuItem::new("About") - .icon(Icon::default().path("icons/shield-check.svg")) - .active(self.active_view == ActiveView::About) - .on_click(cx.listener(|this, _, _, _| { - this.active_view = ActiveView::About; - })), - ), - ), - ), // .footer(SidebarFooter::new().child("Device Status")), - ) - .child( - v_flex() - .size_full() - .child( - TitleBar::new().child( - h_flex() - .w_full() - .justify_between() - // .px_4() - .items_center() - .cursor(gpui::CursorStyle::OpenHand) - .child( - Button::new("sidebar_toggle") - .ghost() - .icon(IconName::PanelLeft) - .on_click(cx.listener(|this, _, _, _| { - this.collapsed = !this.collapsed; - })) - .tooltip("Toggle Sidebar"), - ), - ), - ) - .child( - v_flex() - .min_h(px(0.)) - .min_w(px(0.)) - .overflow_y_scrollbar() - .flex_grow() - .bg(cx.theme().background) - .child(match self.active_view { - ActiveView::Home => HomeView::build(cx.theme()).into_any_element(), - ActiveView::Passkeys => PasskeysView::build().into_any_element(), - ActiveView::Configuration => ConfigView::build().into_any_element(), - ActiveView::Security => SecurityView::build().into_any_element(), - ActiveView::Logs => LogsView::build().into_any_element(), - ActiveView::About => AboutView::build().into_any_element(), - }), - ), - ) - } + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + h_flex() + .size_full() + .child( + Sidebar::new(Side::Left) + .collapsed(self.collapsed) + .collapsible(true) + .h_full() + .bg(rgb(0x18181b)) + .child( + SidebarGroup::new("Menu").child( + SidebarMenu::new() + .child( + SidebarMenuItem::new("Home") + .icon(Icon::default().path("icons/house.svg")) + .active(self.active_view == ActiveView::Home) + .on_click(cx.listener(|this, _, _, _| { + this.active_view = ActiveView::Home; + })), + ) + .child( + SidebarMenuItem::new("Passkeys") + .icon(Icon::default().path("icons/key-round.svg")) + .active(self.active_view == ActiveView::Passkeys) + .on_click(cx.listener(|this, _, _, _| { + this.active_view = ActiveView::Passkeys; + })), + ) + .child( + SidebarMenuItem::new("Configuration") + .icon(Icon::default().path("icons/settings.svg")) + .active(self.active_view == ActiveView::Configuration) + .on_click(cx.listener(|this, _, _, _| { + this.active_view = ActiveView::Configuration; + })), + ) + // TODO: Replace these icons with correct ones from lucide + .child( + SidebarMenuItem::new("Security") + .icon(Icon::default().path("icons/shield-check.svg")) + .active(self.active_view == ActiveView::Security) + .on_click(cx.listener(|this, _, _, _| { + this.active_view = ActiveView::Security; + })), + ) + .child( + SidebarMenuItem::new("Logs") + .icon(Icon::default().path("icons/scroll-text.svg")) + .active(self.active_view == ActiveView::Logs) + .on_click(cx.listener(|this, _, _, _| { + this.active_view = ActiveView::Logs; + })), + ) + .child( + SidebarMenuItem::new("About") + .icon(IconName::Info) + .active(self.active_view == ActiveView::About) + .on_click(cx.listener(|this, _, _, _| { + this.active_view = ActiveView::About; + })), + ), + ), + ), + ) + .child( + v_flex() + .size_full() + .child( + TitleBar::new().bg(rgba(colors::zinc::ZINC900)).child( + h_flex() + .w_full() + .justify_between() + .bg(rgba(colors::zinc::ZINC900)) + .items_center() + .cursor(gpui::CursorStyle::OpenHand) + .child( + Button::new("sidebar_toggle") + .ghost() + .icon(IconName::PanelLeft) + .on_click(cx.listener(|this, _, _, _| { + this.collapsed = !this.collapsed; + })) + .tooltip("Toggle Sidebar"), + ), + ), + ) + .child( + v_flex() + .min_h(px(0.)) + .min_w(px(0.)) + .overflow_y_scrollbar() + .flex_grow() + .bg(cx.theme().background) + .child(match self.active_view { + ActiveView::Home => HomeView::build(cx.theme()).into_any_element(), + ActiveView::Passkeys => PasskeysView::build().into_any_element(), + ActiveView::Configuration => ConfigView::build().into_any_element(), + ActiveView::Security => SecurityView::build().into_any_element(), + ActiveView::Logs => LogsView::build().into_any_element(), + ActiveView::About => AboutView::build().into_any_element(), + }), + ), + ) + } } diff --git a/src/ui/views/about.rs b/src/ui/views/about.rs index afd8642..49be916 100644 --- a/src/ui/views/about.rs +++ b/src/ui/views/about.rs @@ -4,7 +4,7 @@ use gpui::*; pub struct AboutView; impl AboutView { - pub fn build() -> impl IntoElement { - div().size_full().p_8().child("About goes here...") - } + pub fn build() -> impl IntoElement { + div().size_full().p_8().child("About goes here...") + } } diff --git a/src/ui/views/config.rs b/src/ui/views/config.rs index 9014001..94c155f 100644 --- a/src/ui/views/config.rs +++ b/src/ui/views/config.rs @@ -4,10 +4,10 @@ use gpui::*; pub struct ConfigView; impl ConfigView { - pub fn build() -> impl IntoElement { - div() - .size_full() - .p_8() - .child("Passkey Management List goes here...") - } + pub fn build() -> impl IntoElement { + div() + .size_full() + .p_8() + .child("Passkey Management List goes here...") + } } diff --git a/src/ui/views/home.rs b/src/ui/views/home.rs index 78a192b..228a683 100644 --- a/src/ui/views/home.rs +++ b/src/ui/views/home.rs @@ -5,510 +5,510 @@ use gpui_component::{Icon, IconName, Theme, badge::Badge, h_flex, progress::Prog // These will be replaced/added in types.rs struct DeviceInfo { - serial: String, - firmware_version: String, - flash_used: f32, - flash_total: f32, + serial: String, + firmware_version: String, + flash_used: f32, + flash_total: f32, } struct DeviceConfig { - vid: u16, - pid: u16, - product_name: String, - led_gpio: u8, - led_brightness: u8, - touch_timeout: u8, - led_dimmable: bool, - led_steady: bool, + vid: u16, + pid: u16, + product_name: String, + led_gpio: u8, + led_brightness: u8, + touch_timeout: u8, + led_dimmable: bool, + led_steady: bool, } struct FidoInfo { - versions: Vec, - client_pin: bool, - min_pin_length: u8, - resident_keys: bool, - aaguid: String, + versions: Vec, + client_pin: bool, + min_pin_length: u8, + resident_keys: bool, + aaguid: String, } struct DeviceSecurity { - secure_boot: bool, - secure_lock: bool, - confirmed: bool, + secure_boot: bool, + secure_lock: bool, + confirmed: bool, } struct DeviceState { - connected: bool, - method: String, - info: DeviceInfo, - config: DeviceConfig, - fido_info: Option, - security: DeviceSecurity, + connected: bool, + method: String, + info: DeviceInfo, + config: DeviceConfig, + fido_info: Option, + security: DeviceSecurity, } pub struct HomeView; impl HomeView { - pub fn build(theme: &Theme) -> impl IntoElement { - // Mock Data, I will replace this with fetching of actual data later, kinda bored rn. - let device = DeviceState { - connected: true, - method: "HID".to_string(), - info: DeviceInfo { - serial: "A1B2C3D4E5".to_string(), - firmware_version: "1.2.0".to_string(), - flash_used: 128.0, - flash_total: 2048.0, - }, - config: DeviceConfig { - vid: 0x0000, - pid: 0x0000, - product_name: "Pico FIDO Key".to_string(), - led_gpio: 25, - led_brightness: 128, - touch_timeout: 10, - led_dimmable: true, - led_steady: false, - }, - fido_info: Some(FidoInfo { - versions: vec!["FIDO2_1".to_string(), "U2F_V2".to_string()], - client_pin: true, - min_pin_length: 4, - resident_keys: true, - aaguid: "00000000-0000-0000-0000-000000000000".to_string(), - }), - security: DeviceSecurity { - secure_boot: true, - secure_lock: false, - confirmed: true, - }, - }; + pub fn build(theme: &Theme) -> impl IntoElement { + // Mock Data, I will replace this with fetching of actual data later, kinda bored rn. + let device = DeviceState { + connected: true, + method: "HID".to_string(), + info: DeviceInfo { + serial: "A1B2C3D4E5".to_string(), + firmware_version: "1.2.0".to_string(), + flash_used: 128.0, + flash_total: 2048.0, + }, + config: DeviceConfig { + vid: 0x0000, + pid: 0x0000, + product_name: "Pico FIDO Key".to_string(), + led_gpio: 25, + led_brightness: 128, + touch_timeout: 10, + led_dimmable: true, + led_steady: false, + }, + fido_info: Some(FidoInfo { + versions: vec!["FIDO2_1".to_string(), "U2F_V2".to_string()], + client_pin: true, + min_pin_length: 4, + resident_keys: true, + aaguid: "00000000-0000-0000-0000-000000000000".to_string(), + }), + security: DeviceSecurity { + secure_boot: true, + secure_lock: false, + confirmed: true, + }, + }; - div() - .size_full() - .bg(theme.background) - .flex() - .flex_col() - .items_center() - .child( - div().w_full().max_w(px(1400.0)).px_10().py_5().child( - v_flex() - .gap_8() - .child( - v_flex() - .child( - div() - .text_3xl() - .font_extrabold() - .text_color(theme.foreground) - .child("Device Overview"), - ) - .child( - div().text_sm().text_color(theme.muted_foreground).child( - "Quick view of your device status and specifications.", - ), - ), - ) - // Content Section - .child(if !device.connected { - // No Device Status Placeholder - div() - .flex() - .items_center() - .justify_center() - .h_64() - .border_1() - .border_color(theme.border) - .rounded_xl() - .child( - div() - .text_color(theme.muted_foreground) - .child("No Device Connected"), - ) - .into_any_element() - } else { - // 4 Card Grid - div() - .grid() - .grid_cols(2) - .gap_6() - .child(Self::render_device_info(&device, theme)) - .child(Self::render_fido_info(&device, theme)) - .child(Self::render_led_config(&device, theme)) - .child(Self::render_security_status(&device, theme)) - .into_any_element() - }), - ), - ) - } + div() + .size_full() + .bg(theme.background) + .flex() + .flex_col() + .items_center() + .child( + div().w_full().max_w(px(1400.0)).px_10().py_5().child( + v_flex() + .gap_8() + .child( + v_flex() + .child( + div() + .text_3xl() + .font_extrabold() + .text_color(theme.foreground) + .child("Device Overview"), + ) + .child( + div().text_sm().text_color(theme.muted_foreground).child( + "Quick view of your device status and specifications.", + ), + ), + ) + // Content Section + .child(if !device.connected { + // No Device Status Placeholder + div() + .flex() + .items_center() + .justify_center() + .h_64() + .border_1() + .border_color(theme.border) + .rounded_xl() + .child( + div() + .text_color(theme.muted_foreground) + .child("No Device Connected"), + ) + .into_any_element() + } else { + // 4 Card Grid + div() + .grid() + .grid_cols(2) + .gap_6() + .child(Self::render_device_info(&device, theme)) + .child(Self::render_fido_info(&device, theme)) + .child(Self::render_led_config(&device, theme)) + .child(Self::render_security_status(&device, theme)) + .into_any_element() + }), + ), + ) + } - fn home_card( - title: &str, - icon: Icon, - content: impl IntoElement, - theme: &Theme, - ) -> impl IntoElement { - div() - .w_full() - // TODO: REPLACE with a constant or modify default theme - .bg(rgb(0x18181b)) - .border_1() - .border_color(theme.border) - .rounded_xl() - .p_6() - .child( - v_flex() - .gap_6() - .child( - h_flex() - .items_center() - .gap_2() - .child(Icon::new(icon).size_5().text_color(theme.foreground)) - .child( - div() - .font_bold() - .text_color(theme.foreground) - .child(title.to_string()), - ), - ) - .child(content), - ) - } + fn home_card( + title: &str, + icon: Icon, + content: impl IntoElement, + theme: &Theme, + ) -> impl IntoElement { + div() + .w_full() + // TODO: REPLACE with a constant or modify default theme + .bg(rgb(0x18181b)) + .border_1() + .border_color(theme.border) + .rounded_xl() + .p_6() + .child( + v_flex() + .gap_6() + .child( + h_flex() + .items_center() + .gap_2() + .child(Icon::new(icon).size_5().text_color(theme.foreground)) + .child( + div() + .font_bold() + .text_color(theme.foreground) + .child(title.to_string()), + ), + ) + .child(content), + ) + } - // --- Helper for Key-Value pairs --- - fn render_kv( - label: &str, - value: impl IntoElement, - theme: &Theme, - font_mono: bool, - ) -> impl IntoElement { - v_flex() - .gap_1() - .child( - div() - .text_sm() - .text_color(theme.muted_foreground) - .child(label.to_string()), - ) - .child( - div() - .text_sm() - .font_weight(if font_mono { - FontWeight::NORMAL - } else { - FontWeight::MEDIUM - }) - .font_family(if font_mono { "Mono" } else { "Sans" }) - .text_color(theme.foreground) - .child(value), - ) - } + // --- Helper for Key-Value pairs --- + fn render_kv( + label: &str, + value: impl IntoElement, + theme: &Theme, + font_mono: bool, + ) -> impl IntoElement { + v_flex() + .gap_1() + .child( + div() + .text_sm() + .text_color(theme.muted_foreground) + .child(label.to_string()), + ) + .child( + div() + .text_sm() + .font_weight(if font_mono { + FontWeight::NORMAL + } else { + FontWeight::MEDIUM + }) + .font_family(if font_mono { "Mono" } else { "Sans" }) + .text_color(theme.foreground) + .child(value), + ) + } - fn render_device_info(device: &DeviceState, theme: &Theme) -> impl IntoElement { - let flash_percent = (device.info.flash_used / device.info.flash_total) * 100.0; + fn render_device_info(device: &DeviceState, theme: &Theme) -> impl IntoElement { + let flash_percent = (device.info.flash_used / device.info.flash_total) * 100.0; - Self::home_card( - "Device Information", - Icon::default().path("icons/cpu.svg"), - v_flex() - .gap_6() - .child( - div() - .grid() - .grid_cols(2) - .gap_4() - .child(Self::render_kv( - "Serial Number", - device.info.serial.clone(), - theme, - true, - )) - .child(Self::render_kv( - "Firmware Version", - format!("v{}", device.info.firmware_version), - theme, - true, - )) - .child(Self::render_kv( - "VID:PID", - format!("{:04x}:{:04x}", device.config.vid, device.config.pid), - theme, - true, - )) - .child(Self::render_kv( - "Product Name", - device.config.product_name.clone(), - theme, - false, - )), - ) - .child( - div().h_px().bg(theme.border), // Separator - ) - .child( - v_flex() - .gap_2() - .child( - h_flex() - .justify_between() - .text_sm() - .child( - div() - .text_color(theme.muted_foreground) - .child("Flash Memory"), - ) - .child(div().text_color(theme.foreground).child(format!( - "{:.0} / {:.0} KB", - device.info.flash_used, device.info.flash_total - ))), - ) - .child(Progress::new().value(flash_percent)), - ), - theme, - ) - } + Self::home_card( + "Device Information", + Icon::default().path("icons/cpu.svg"), + v_flex() + .gap_6() + .child( + div() + .grid() + .grid_cols(2) + .gap_4() + .child(Self::render_kv( + "Serial Number", + device.info.serial.clone(), + theme, + true, + )) + .child(Self::render_kv( + "Firmware Version", + format!("v{}", device.info.firmware_version), + theme, + true, + )) + .child(Self::render_kv( + "VID:PID", + format!("{:04x}:{:04x}", device.config.vid, device.config.pid), + theme, + true, + )) + .child(Self::render_kv( + "Product Name", + device.config.product_name.clone(), + theme, + false, + )), + ) + .child(div().h_px().bg(theme.border)) + .child( + v_flex() + .gap_2() + .child( + h_flex() + .justify_between() + .text_sm() + .child( + div() + .text_color(theme.muted_foreground) + .child("Flash Memory"), + ) + .child(div().text_color(theme.foreground).child(format!( + "{:.0} / {:.0} KB", + device.info.flash_used, device.info.flash_total + ))), + ) + .child(Progress::new().value(flash_percent)), + ), + theme, + ) + } - fn render_fido_info(device: &DeviceState, theme: &Theme) -> impl IntoElement { - Self::home_card( - "FIDO2 Information", - Icon::default().path("icons/shield.svg"), - if let Some(fido) = &device.fido_info { - v_flex() - .gap_6() - .child( - div() - .grid() - .grid_cols(2) - .gap_4() - .child(Self::render_kv( - "FIDO Version", - fido.versions.first().cloned().unwrap_or("N/A".into()), - theme, - false, - )) - .child(Self::render_kv( - "PIN Set", - if fido.client_pin { "Yes" } else { "No" }, - theme, - false, - )) - .child(Self::render_kv( - "Min PIN Length", - fido.min_pin_length.to_string(), - theme, - false, - )) - .child(Self::render_kv( - "Resident Keys", - if fido.resident_keys { - "Supported" - } else { - "Not Supported" - }, - theme, - false, - )), - ) - .child(div().h_px().bg(theme.border)) - .child(Self::render_kv("AAGUID", fido.aaguid.clone(), theme, true)) - .into_any_element() - } else { - div() - .text_sm() - .text_color(theme.muted_foreground) - .child("FIDO information not available") - .into_any_element() - }, - theme, - ) - } + fn render_fido_info(device: &DeviceState, theme: &Theme) -> impl IntoElement { + Self::home_card( + "FIDO2 Information", + Icon::default().path("icons/shield.svg"), + if let Some(fido) = &device.fido_info { + v_flex() + .gap_6() + .child( + div() + .grid() + .grid_cols(2) + .gap_4() + .child(Self::render_kv( + "FIDO Version", + fido.versions.first().cloned().unwrap_or("N/A".into()), + theme, + false, + )) + .child(Self::render_kv( + "PIN Set", + if fido.client_pin { "Yes" } else { "No" }, + theme, + false, + )) + .child(Self::render_kv( + "Min PIN Length", + fido.min_pin_length.to_string(), + theme, + false, + )) + .child(Self::render_kv( + "Resident Keys", + if fido.resident_keys { + "Supported" + } else { + "Not Supported" + }, + theme, + false, + )), + ) + .child(div().h_px().bg(theme.border)) + .child(Self::render_kv("AAGUID", fido.aaguid.clone(), theme, true)) + .into_any_element() + } else { + div() + .text_sm() + .text_color(theme.muted_foreground) + .child("FIDO information not available") + .into_any_element() + }, + theme, + ) + } - fn render_led_config(device: &DeviceState, theme: &Theme) -> impl IntoElement { - Self::home_card( - "LED Configuration", - Icon::default().path("icons/microchip.svg"), - if device.method == "FIDO" { - v_flex() - .items_center() - .justify_center() - .py_4() - .gap_2() - .child( - Icon::new(IconName::TriangleAlert) - .size_8() - .text_color(gpui::yellow()), - ) - .child( - div() - .text_sm() - .text_color(theme.muted_foreground) - .child("Information is not available in Fido only communication mode."), - ) - .into_any_element() - } else { - v_flex() - .gap_3() - .text_sm() - .child( - h_flex() - .justify_between() - .child( - div() - .text_color(theme.muted_foreground) - .child("LED GPIO Pin"), - ) - .child(format!("GPIO {}", device.config.led_gpio)), - ) - .child( - h_flex() - .justify_between() - .child( - div() - .text_color(theme.muted_foreground) - .child("LED Brightness"), - ) - .child(device.config.led_brightness.to_string()), - ) - .child( - h_flex() - .justify_between() - .child( - div() - .text_color(theme.muted_foreground) - .child("Presence Touch Timeout"), - ) - .child(format!("{}s", device.config.touch_timeout)), - ) - .child( - h_flex() - .justify_between() - .child( - div() - .text_color(theme.muted_foreground) - .child("LED Dimmable"), - ) - .child( - Badge::new() - .child(if device.config.led_dimmable { - "Yes" - } else { - "No" - }) - .color(if device.config.led_dimmable { - theme.primary - } else { - theme.secondary - }), - ), - ) - .child( - h_flex() - .justify_between() - .child( - div() - .text_color(theme.muted_foreground) - .child("LED Steady Mode"), - ) - .child( - Badge::new() - .child(if device.config.led_steady { - "On" - } else { - "Off" - }) - .color(if device.config.led_steady { - theme.primary - } else { - theme.secondary - }), - ), - ) - .into_any_element() - }, - theme, - ) - } + fn render_led_config(device: &DeviceState, theme: &Theme) -> impl IntoElement { + Self::home_card( + "LED Configuration", + Icon::default().path("icons/microchip.svg"), + if device.method == "FIDO" { + v_flex() + .items_center() + .justify_center() + .py_4() + .gap_2() + .child( + Icon::new(IconName::TriangleAlert) + .size_8() + .text_color(gpui::yellow()), + ) + .child( + div() + .text_sm() + .text_color(theme.muted_foreground) + .child("Information is not available in Fido only communication mode."), + ) + .into_any_element() + } else { + v_flex() + .gap_3() + .text_sm() + .child( + h_flex() + .justify_between() + .child( + div() + .text_color(theme.muted_foreground) + .child("LED GPIO Pin"), + ) + .child(format!("GPIO {}", device.config.led_gpio)), + ) + .child( + h_flex() + .justify_between() + .child( + div() + .text_color(theme.muted_foreground) + .child("LED Brightness"), + ) + .child(device.config.led_brightness.to_string()), + ) + .child( + h_flex() + .justify_between() + .child( + div() + .text_color(theme.muted_foreground) + .child("Presence Touch Timeout"), + ) + .child(format!("{}s", device.config.touch_timeout)), + ) + .child( + h_flex() + .justify_between() + .child( + div() + .text_color(theme.muted_foreground) + .child("LED Dimmable"), + ) + .child( + Badge::new() + .child(if device.config.led_dimmable { + "Yes" + } else { + "No" + }) + .color(if device.config.led_dimmable { + theme.primary + } else { + theme.secondary + }), + ), + ) + .child( + h_flex() + .justify_between() + .child( + div() + .text_color(theme.muted_foreground) + .child("LED Steady Mode"), + ) + .child( + Badge::new() + .child(if device.config.led_steady { + "On" + } else { + "Off" + }) + .color(if device.config.led_steady { + theme.primary + } else { + theme.secondary + }), + ), + ) + .into_any_element() + }, + theme, + ) + } - fn render_security_status(device: &DeviceState, theme: &Theme) -> impl IntoElement { - Self::home_card( - "Security Status", - Icon::default().path("icons/shield-check.svg"), - v_flex() - .gap_3() - .text_sm() - .child( - h_flex() - .justify_between() - .items_center() - .child(div().text_color(theme.muted_foreground).child("Boot Mode")) - .child( - h_flex() - .gap_2() - .items_center() - .child(if device.security.secure_boot { - Icon::new(IconName::FolderClosed) - .size_3p5() - .text_color(gpui::green()) - } else { - Icon::new(IconName::FolderOpen) - .size_3p5() - .text_color(gpui::yellow()) - }) - .child( - Badge::new() - .child(if device.security.secure_boot { - "Secure Boot" - } else { - "Development" - }) - .color(if device.security.secure_boot { - theme.primary - } else { - theme.secondary - }), - ), - ), - ) - .child( - h_flex() - .justify_between() - .items_center() - .child( - div() - .text_color(theme.muted_foreground) - .child("Debug Interface"), - ) - .child(div().font_medium().text_color(theme.foreground).child( - if device.security.secure_lock { - "Read-out Locked" - } else { - "Debug Enabled" - }, - )), - ) - .child( - h_flex() - .justify_between() - .items_center() - .child( - div() - .text_color(theme.muted_foreground) - .child("Secure Lock"), - ) - .child( - Badge::new() - .child(if device.security.confirmed { - "Acknowledged" - } else { - "Pending" - }) - .color(if device.security.confirmed { - gpui::red() - } else { - theme.secondary - }), - ), - ), - theme, - ) - } + fn render_security_status(device: &DeviceState, theme: &Theme) -> impl IntoElement { + Self::home_card( + "Security Status", + Icon::default().path("icons/shield-check.svg"), + v_flex() + .gap_3() + .text_sm() + .child( + h_flex() + .justify_between() + .items_center() + .child(div().text_color(theme.muted_foreground).child("Boot Mode")) + .child( + h_flex() + .gap_2() + .items_center() + .child(if device.security.secure_boot { + Icon::default() + .path("icons/lock.svg") + .size_3p5() + .text_color(gpui::green()) + } else { + Icon::default() + .path("icons/lock-open.svg") + .size_3p5() + .text_color(gpui::yellow()) + }) + .child( + Badge::new() + .child(if device.security.secure_boot { + "Secure Boot" + } else { + "Development" + }) + .color(if device.security.secure_boot { + theme.primary + } else { + theme.secondary + }), + ), + ), + ) + .child( + h_flex() + .justify_between() + .items_center() + .child( + div() + .text_color(theme.muted_foreground) + .child("Debug Interface"), + ) + .child(div().font_medium().text_color(theme.foreground).child( + if device.security.secure_lock { + "Read-out Locked" + } else { + "Debug Enabled" + }, + )), + ) + .child( + h_flex() + .justify_between() + .items_center() + .child( + div() + .text_color(theme.muted_foreground) + .child("Secure Lock"), + ) + .child( + Badge::new() + .child(if device.security.confirmed { + "Acknowledged" + } else { + "Pending" + }) + .color(if device.security.confirmed { + gpui::red() + } else { + theme.secondary + }), + ), + ), + theme, + ) + } } diff --git a/src/ui/views/logs.rs b/src/ui/views/logs.rs index fa08a6c..504105e 100644 --- a/src/ui/views/logs.rs +++ b/src/ui/views/logs.rs @@ -4,7 +4,7 @@ use gpui::*; pub struct LogsView; impl LogsView { - pub fn build() -> impl IntoElement { - div().size_full().p_8().child("Logs goes here...") - } + pub fn build() -> impl IntoElement { + div().size_full().p_8().child("Logs goes here...") + } } diff --git a/src/ui/views/passkeys.rs b/src/ui/views/passkeys.rs index ac581b5..aac79ce 100644 --- a/src/ui/views/passkeys.rs +++ b/src/ui/views/passkeys.rs @@ -4,10 +4,10 @@ use gpui::*; pub struct PasskeysView; impl PasskeysView { - pub fn build() -> impl IntoElement { - div() - .size_full() - .p_8() - .child("Passkey Management List goes here...") - } + pub fn build() -> impl IntoElement { + div() + .size_full() + .p_8() + .child("Passkey Management List goes here...") + } } diff --git a/src/ui/views/security.rs b/src/ui/views/security.rs index d96ce44..3e458ba 100644 --- a/src/ui/views/security.rs +++ b/src/ui/views/security.rs @@ -4,10 +4,10 @@ use gpui::*; pub struct SecurityView; impl SecurityView { - pub fn build() -> impl IntoElement { - div() - .size_full() - .p_8() - .child("Security Management List goes here...") - } + pub fn build() -> impl IntoElement { + div() + .size_full() + .p_8() + .child("Security Management List goes here...") + } }