mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
feat(hal): introduce RS-Key(0.3.x) support and FirmwareTrait architecture
This commit significantly refactors the HAL layer to introduce proper support for the RS-Key firmware and abstraction across different hardware profiles
(PicoFido, RSKey, LkOne).
Key changes include:
- **Firmware Abstraction**: Added `FirmwareTrait` and `AnyFirmware` in `src/hal/firmwares/mod.rs` with specific implementations for `PicoFidoFirmware` and
`RSKeyFirmware` to decouple hardware-specific logic from the high-level `io.rs` layer.
- **RS-Key FIDO Support**: Implemented RS-Key payload logic in `src/hal/fido/mod.rs` to support reading/writing LED configuration and DEV_CONF directly over
the CTAPHID FIDO transport using TLV and custom command targets.
- **Constants Cleanup**: Stripped out and consolidated 200+ lines of raw CTAP2 constants and vendor opcodes from `src/hal/fido/constants.rs` to clean up the
module.
- **UI & PIN Dialogs**: Updated `src/ui/screens/config/view_model.rs` to route `DeviceMethod` correctly when applying configurations. Added
`StatusDialogHandle` integration to prompt the user for their FIDO PIN and instruct them to "Please touch your device if it flashes" when performing hardware
config writes over FIDO.
- **LkOne Type**: Added the `LkOne` AAGUID and firmware types to the hardware definition tree.
- **Home View Check**: Fixed a UI issue in `home/view.rs` to conditionally display the LED config card based on whether the `FirmwareType` actually supports
FIDO config modifications (like RS-Key).
This lays the architectural foundation needed to handle FIDO configurations dynamically based on the discovered firmware type.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[repr(i32)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(dead_code)]
|
||||
use std::fmt;
|
||||
|
||||
use std::fmt;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct FirmwareVersion {
|
||||
pub major: u16,
|
||||
|
||||
+35
-176
@@ -27,6 +27,8 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub use crate::hal::common::cose::{CoseAlgorithm, CoseCurve, CoseKeyParam};
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// CTAP2 STANDARD — FIDO Alliance specification §8.1
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -343,169 +345,6 @@ bitflags::bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
// ── COSE key types (RFC 8152) ───────────────────────────────────────────────
|
||||
|
||||
/// COSE algorithm identifiers (IANA COSE Algorithms registry).
|
||||
///
|
||||
/// Used in `pubKeyCredParams` to specify which signature algorithms
|
||||
/// the platform supports. The authenticator picks the first match.
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CoseAlgorithm {
|
||||
/// ECDSA with P-256 and SHA-256 (most common for WebAuthn).
|
||||
ES256 = -7,
|
||||
/// EdDSA with Ed25519.
|
||||
EdDSA = -8,
|
||||
/// ECDSA with P-256 (alternate ID, same as ES256).
|
||||
ESP256 = -9,
|
||||
/// EdDSA with Ed25519 (alternate ID).
|
||||
Ed25519 = -19,
|
||||
/// ECDH-ES with HKDF-256 key agreement.
|
||||
EcdhEsHkdf256 = -25,
|
||||
/// ECDSA with P-384 and SHA-384.
|
||||
ES384 = -35,
|
||||
/// ECDSA with P-521 and SHA-512.
|
||||
ES512 = -36,
|
||||
/// ECDSA with secp256k1 and SHA-256 (Bitcoin curve).
|
||||
ES256K = -47,
|
||||
/// ECDSA with P-384 (alternate ID).
|
||||
ESP384 = -51,
|
||||
/// ECDSA with P-521 (alternate ID).
|
||||
ESP512 = -52,
|
||||
/// EdDSA with Ed448.
|
||||
Ed448 = -53,
|
||||
/// RSASSA-PKCS1-v1_5 with SHA-256.
|
||||
RS256 = -257,
|
||||
/// RSASSA-PKCS1-v1_5 with SHA-384.
|
||||
RS384 = -258,
|
||||
/// RSASSA-PKCS1-v1_5 with SHA-512.
|
||||
RS512 = -259,
|
||||
/// ECDSA with brainpool256r1 and SHA-256.
|
||||
ESB256 = -265,
|
||||
/// ECDSA with brainpool384r1 and SHA-384.
|
||||
ESB384 = -267,
|
||||
/// ECDSA with brainpool512r1 and SHA-512.
|
||||
ESB512 = -268,
|
||||
/// ML-DSA-44 (FIPS 204, Level 2) — post-quantum signing.
|
||||
///
|
||||
/// RS-Key specific. Uses COSE key type AKP (7) instead of EC2/OKP.
|
||||
MLDSA44 = -48,
|
||||
/// ML-DSA-65 (FIPS 204, Level 3) — declared in getInfo but may be
|
||||
/// unsupported for credential creation.
|
||||
MLDSA65 = -49,
|
||||
/// ML-DSA-87 (FIPS 204, Level 5) — declared in getInfo but may be
|
||||
/// unsupported for credential creation.
|
||||
MLDSA87 = -50,
|
||||
}
|
||||
|
||||
impl CoseAlgorithm {
|
||||
/// Convert a raw i128 (from CBOR) to a [`CoseAlgorithm`].
|
||||
pub fn from_i128(val: i128) -> Option<Self> {
|
||||
match val as i32 {
|
||||
-7 => Some(Self::ES256),
|
||||
-8 => Some(Self::EdDSA),
|
||||
-9 => Some(Self::ESP256),
|
||||
-19 => Some(Self::Ed25519),
|
||||
-25 => Some(Self::EcdhEsHkdf256),
|
||||
-35 => Some(Self::ES384),
|
||||
-36 => Some(Self::ES512),
|
||||
-47 => Some(Self::ES256K),
|
||||
-51 => Some(Self::ESP384),
|
||||
-52 => Some(Self::ESP512),
|
||||
-53 => Some(Self::Ed448),
|
||||
-257 => Some(Self::RS256),
|
||||
-258 => Some(Self::RS384),
|
||||
-259 => Some(Self::RS512),
|
||||
-265 => Some(Self::ESB256),
|
||||
-267 => Some(Self::ESB384),
|
||||
-268 => Some(Self::ESB512),
|
||||
-48 => Some(Self::MLDSA44),
|
||||
-49 => Some(Self::MLDSA65),
|
||||
-50 => Some(Self::MLDSA87),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CoseAlgorithm {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::ES256 => write!(f, "ES256"),
|
||||
Self::EdDSA => write!(f, "EdDSA"),
|
||||
Self::ESP256 => write!(f, "ESP256"),
|
||||
Self::Ed25519 => write!(f, "Ed25519"),
|
||||
Self::EcdhEsHkdf256 => write!(f, "ECDH-ES-HKDF-256"),
|
||||
Self::ES384 => write!(f, "ES384"),
|
||||
Self::ES512 => write!(f, "ES512"),
|
||||
Self::ES256K => write!(f, "ES256K"),
|
||||
Self::ESP384 => write!(f, "ESP384"),
|
||||
Self::ESP512 => write!(f, "ESP512"),
|
||||
Self::Ed448 => write!(f, "Ed448"),
|
||||
Self::RS256 => write!(f, "RS256"),
|
||||
Self::RS384 => write!(f, "RS384"),
|
||||
Self::RS512 => write!(f, "RS512"),
|
||||
Self::ESB256 => write!(f, "ESB256"),
|
||||
Self::ESB384 => write!(f, "ESB384"),
|
||||
Self::ESB512 => write!(f, "ESB512"),
|
||||
Self::MLDSA44 => write!(f, "ML-DSA-44"),
|
||||
Self::MLDSA65 => write!(f, "ML-DSA-65"),
|
||||
Self::MLDSA87 => write!(f, "ML-DSA-87"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// COSE elliptic curve identifiers (RFC 8152 §13.1.1).
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CoseCurve {
|
||||
/// NIST P-256 (secp256r1, prime256v1).
|
||||
P256 = 1,
|
||||
/// NIST P-384 (secp384r1).
|
||||
P384 = 2,
|
||||
/// NIST P-521 (secp521r1).
|
||||
P521 = 3,
|
||||
/// X25519 for key agreement.
|
||||
X25519 = 4,
|
||||
/// X448 for key agreement.
|
||||
X448 = 5,
|
||||
/// Ed25519 for signing.
|
||||
Ed25519 = 6,
|
||||
/// Ed448 for signing.
|
||||
Ed448 = 7,
|
||||
/// secp256k1 (Bitcoin/Ethereum curve).
|
||||
P256K1 = 8,
|
||||
/// BrainpoolP256R1.
|
||||
BP256R1 = 9,
|
||||
/// BrainpoolP384R1.
|
||||
BP384R1 = 10,
|
||||
/// BrainpoolP512R1.
|
||||
BP512R1 = 11,
|
||||
}
|
||||
|
||||
/// COSE key parameter identifiers (RFC 8152 §7.1).
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CoseKeyParam {
|
||||
/// Key type (OKP, EC2, RSA, etc.).
|
||||
Kty = 1,
|
||||
/// Key identifier.
|
||||
Kid = 2,
|
||||
/// Algorithm identifier.
|
||||
Alg = 3,
|
||||
/// Key operations (sign, verify, encrypt, etc.).
|
||||
KeyOps = 4,
|
||||
/// Base IV for symmetric operations.
|
||||
BaseIV = 5,
|
||||
/// Elliptic curve identifier.
|
||||
Crv = -1,
|
||||
/// X coordinate (EC2) or public key bytes (OKP).
|
||||
X = -2,
|
||||
/// Y coordinate (EC2).
|
||||
Y = -3,
|
||||
/// Private key (EC2 or OKP).
|
||||
D = -4,
|
||||
}
|
||||
|
||||
// ── CTAP2 errors (§8.2) ────────────────────────────────────────────────────
|
||||
|
||||
/// CTAP2 error codes (§8.2).
|
||||
@@ -609,7 +448,7 @@ pub enum Ctap2Error {
|
||||
/// - **All versions**: Backup(0x01), MSE(0x02), Unlock(0x03), EA(0x04)
|
||||
/// - **≤ v7.2**: PhysicalOptions(0x05), Memory(0x06) — removed in later
|
||||
/// releases. PicoForge keeps them for legacy device support.
|
||||
/// - **Current**: AdminPin(0x08) added.
|
||||
/// - **≥ v7.6**: AdminPin(0x08) added.
|
||||
///
|
||||
/// RS-Key uses a different vendor command scheme (CTAPHID 0x41 with
|
||||
/// 64-bit sub-command IDs) — this enum does NOT apply to RS-Key.
|
||||
@@ -632,6 +471,8 @@ pub enum VendorCommand {
|
||||
///
|
||||
/// **Legacy** (pico-fido ≤ v7.2 only). Removed in current firmware.
|
||||
Memory = 0x06,
|
||||
/// Admin PIN operations (added in pico-fido v7.6).
|
||||
AdminPin = 0x08,
|
||||
}
|
||||
|
||||
/// Pico-fido vendor config command IDs (64-bit).
|
||||
@@ -869,11 +710,31 @@ pub const CTAP_VENDOR_CONFIG_CMD: u8 = 0xC2;
|
||||
/// RS-Key CTAPHID vendor command (0x41).
|
||||
///
|
||||
/// Carries CBOR-encoded sub-commands for seed backup, attestation,
|
||||
/// and audit operations. This is RS-Key specific and not part of pico-fido.
|
||||
/// audit operations, and PicoForge hardware config.
|
||||
/// This is RS-Key specific and not part of pico-fido.
|
||||
///
|
||||
/// See [RS-Key protocol §9](https://themaxmur.github.io/RS-Key/develop/) for details.
|
||||
pub const RSKEY_CTAPHID_VENDOR_CMD: u8 = 0x41;
|
||||
|
||||
/// RS-Key CONFIG_READ sub-command ID (0x0D).
|
||||
///
|
||||
/// Reads device configuration over FIDO. Supports DEV_CONF (0x00),
|
||||
/// PHY (0x01), and LED (0x02) targets. Ungated — no PIN needed.
|
||||
pub const RSKEY_CONFIG_READ: u8 = 0x0D;
|
||||
|
||||
/// RS-Key CONFIG_WRITE sub-command ID (0x0C).
|
||||
///
|
||||
/// Writes device configuration over FIDO. Supports the same targets
|
||||
/// as CONFIG_READ. Requires ACFG-gated PIN token.
|
||||
pub const RSKEY_CONFIG_WRITE: u8 = 0x0C;
|
||||
|
||||
/// RS-Key config target: device configuration (VID, PID, serial, product name).
|
||||
pub const RSKEY_CFG_TARGET_DEV_CONF: u8 = 0x00;
|
||||
/// RS-Key config target: physical config (LED GPIO, brightness, options).
|
||||
pub const RSKEY_CFG_TARGET_PHY: u8 = 0x01;
|
||||
/// RS-Key config target: LED status config.
|
||||
pub const RSKEY_CFG_TARGET_LED: u8 = 0x02;
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// SHARED PROTOCOL CONSTANTS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1237,6 +1098,7 @@ mod tests {
|
||||
// PhysicalOptions(0x05) and Memory(0x06) are legacy <=v7.2
|
||||
assert_eq!(VendorCommand::PhysicalOptions as u8, 0x05);
|
||||
assert_eq!(VendorCommand::Memory as u8, 0x06);
|
||||
assert_eq!(VendorCommand::AdminPin as u8, 0x08);
|
||||
}
|
||||
|
||||
// ── RS-Key vendor command ────────────────────────────────────────────────
|
||||
@@ -1271,20 +1133,17 @@ mod tests {
|
||||
}
|
||||
|
||||
// ── Vendor config command IDs ────────────────────────────────────────────
|
||||
// Reference: pico-fido src/fido/ctap.h (for auth/enable/disable/EA/PIN)
|
||||
// Reference: pico-fido src/fido/ctap.h (tagged releases v3.0–v7.6)
|
||||
// RS-Key protocol docs §11 (for physical config commands)
|
||||
//
|
||||
// NOTE: The auth encryption and PIN policy IDs in PicoForge do NOT match the
|
||||
// current pico-fido ctap.h values. The ctap.h values are documented below
|
||||
// for reference but the PicoForge values may target an older firmware version.
|
||||
// PicoForge values match all tagged releases (v3.0–v7.6). The `main`
|
||||
// branch restructured these to a 0x000X... prefix (unreleased) — not
|
||||
// relevant for current version targeting.
|
||||
//
|
||||
// Firmware ctap.h values:
|
||||
// AuthEncryptionEnable: 0x00043f56b34285e2
|
||||
// AuthEncryptionDisable: 0x0001a40f04a25ed9
|
||||
// EnterpriseAttestationUpload: 0x0002a674c29a8dcf
|
||||
// PinComplexityPolicy: 0x0007d70fe96c3897
|
||||
//
|
||||
// PicoForge values (verified against RS-Key protocol for physical ones):
|
||||
// v3.0+: AuthEncryptionEnable(0x03e4...), AuthEncryptionDisable(0x1831...)
|
||||
// v7.0+: EnterpriseAttestationUpload(0x66f2...), PinComplexityPolicy(0x6c07...)
|
||||
// v7.0+: PhysicalOptions changed from 0x969f... (v6.0–6.4) to 0x269f... (v7.0+)
|
||||
// main (unreleased): all changed to 0x000X... prefix, PHY options removed
|
||||
|
||||
#[test]
|
||||
fn test_vendor_config_command_from_u64() {
|
||||
|
||||
@@ -1882,6 +1882,80 @@ impl HidTransport {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read physical configuration from an RS-Key via CTAPHID 0x41 CONFIG_READ.
|
||||
///
|
||||
/// Sends `{1: 0x0D, 2: {1: target}}` CBOR payload to the RS-Key vendor
|
||||
/// command handler inside a CTAPHID_CBOR message with the vendor sub-command
|
||||
/// prefix. Returns raw TLV bytes for the requested target.
|
||||
/// Ungated — no PIN needed.
|
||||
///
|
||||
/// Targets: `RSKEY_CFG_TARGET_DEV_CONF` (0x00), `RSKEY_CFG_TARGET_PHY` (0x01),
|
||||
/// `RSKEY_CFG_TARGET_LED` (0x02).
|
||||
pub fn rs_key_config_read(&self, target: u8) -> Result<Vec<u8>, PFError> {
|
||||
let mut params = BTreeMap::new();
|
||||
params.insert(Value::Integer(1), Value::Integer(RSKEY_CONFIG_READ as i128));
|
||||
|
||||
let mut target_map = BTreeMap::new();
|
||||
target_map.insert(Value::Integer(1), Value::Integer(target as i128));
|
||||
params.insert(Value::Integer(2), Value::Map(target_map));
|
||||
|
||||
let inner = to_vec(&Value::Map(params)).map_err(|e| PFError::Io(e.to_string()))?;
|
||||
|
||||
let mut full_payload = vec![RSKEY_CTAPHID_VENDOR_CMD];
|
||||
full_payload.extend(inner);
|
||||
self.send_cbor(CTAPHID_CBOR, &full_payload)
|
||||
}
|
||||
|
||||
/// Write physical configuration to an RS-Key via CTAPHID 0x41 CONFIG_WRITE.
|
||||
///
|
||||
/// Sends `{1: 0x0C, 2: {1: target, 2: blob}, 3: protocol, 4: mac}` CBOR
|
||||
/// to the RS-Key vendor command handler. Requires a PIN token obtained with
|
||||
/// `AUTHENTICATOR_CONFIG` permission.
|
||||
///
|
||||
/// The MAC is computed as `HMAC-SHA256(pin_token, 0xFF*32 || 0x41 || 0x0C || cbor_params)[..16]`
|
||||
/// per the RS-Key protocol spec.
|
||||
pub fn rs_key_config_write(
|
||||
&self,
|
||||
pin_token: &[u8],
|
||||
target: u8,
|
||||
blob: &[u8],
|
||||
) -> Result<(), PFError> {
|
||||
let mut params_map = BTreeMap::new();
|
||||
params_map.insert(Value::Integer(1), Value::Integer(target as i128));
|
||||
params_map.insert(Value::Integer(2), Value::Bytes(blob.to_vec()));
|
||||
let params = Value::Map(params_map);
|
||||
let params_bytes = to_vec(¶ms).map_err(|e| PFError::Io(e.to_string()))?;
|
||||
|
||||
// MAC = HMAC-SHA256(pin_token, 0xFF*32 || vendor_cmd || sub_cmd || cbor_params)[..16]
|
||||
let mac = {
|
||||
let mut input = vec![0xFFu8; 32];
|
||||
input.push(RSKEY_CTAPHID_VENDOR_CMD);
|
||||
input.push(RSKEY_CONFIG_WRITE);
|
||||
input.extend(¶ms_bytes);
|
||||
let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, pin_token);
|
||||
hmac::sign(&hmac_key, &input).as_ref()[..16].to_vec()
|
||||
};
|
||||
|
||||
let mut outer = BTreeMap::new();
|
||||
outer.insert(
|
||||
Value::Integer(1),
|
||||
Value::Integer(RSKEY_CONFIG_WRITE as i128),
|
||||
);
|
||||
outer.insert(Value::Integer(2), params);
|
||||
outer.insert(Value::Integer(3), Value::Integer(1)); // PIN protocol v1
|
||||
outer.insert(Value::Integer(4), Value::Bytes(mac));
|
||||
|
||||
let inner = to_vec(&Value::Map(outer)).map_err(|e| PFError::Io(e.to_string()))?;
|
||||
|
||||
let mut full_payload = vec![RSKEY_CTAPHID_VENDOR_CMD];
|
||||
full_payload.extend(inner);
|
||||
// CONFIG_WRITE can involve flash erasure/write which takes
|
||||
// several seconds on RP2040 — use a generous timeout.
|
||||
const CONFIG_WRITE_TIMEOUT_MS: i32 = 30_000;
|
||||
self.send_cbor_with_timeout(CTAPHID_CBOR, &full_payload, CONFIG_WRITE_TIMEOUT_MS)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Sign a credential management command using HMAC-SHA-256.
|
||||
///
|
||||
/// Uses pico-fido's non-standard signing scheme: for sub-commands 0x01
|
||||
|
||||
+559
-74
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ pub use picofido::*;
|
||||
pub use rskey::*;
|
||||
|
||||
use crate::hal::common::FirmwareVersion;
|
||||
use crate::hal::types::FirmwareType;
|
||||
use crate::hal::types::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AnyFirmware {
|
||||
@@ -25,6 +25,7 @@ pub trait FirmwareTrait {
|
||||
}
|
||||
|
||||
fn supports_legacy_fido_hardware_config(&self) -> bool;
|
||||
fn supports_fido_config_write(&self) -> bool;
|
||||
fn supports_rs_key_vendor_command(&self) -> bool;
|
||||
fn supports_rescue_channel(&self) -> bool;
|
||||
}
|
||||
@@ -33,7 +34,9 @@ impl AnyFirmware {
|
||||
pub fn detect_by_aaguid(aaguid: &str) -> FirmwareType {
|
||||
if aaguid == crate::hal::types::RSKEY_AAGUID {
|
||||
FirmwareType::RSKey
|
||||
} else if aaguid == crate::hal::types::PICOFIDO_AAGUID {
|
||||
} else if aaguid == crate::hal::types::PICOFIDO_AAGUID
|
||||
|| aaguid == crate::hal::types::LKONE_AAGUID
|
||||
{
|
||||
FirmwareType::PicoFido
|
||||
} else {
|
||||
FirmwareType::Unknown
|
||||
@@ -45,7 +48,22 @@ impl AnyFirmware {
|
||||
match fw_type {
|
||||
FirmwareType::PicoFido => Self::PicoFido(PicoFidoFirmware::new(ver)),
|
||||
FirmwareType::RSKey => Self::RSKey(RSKeyFirmware::new(ver)),
|
||||
FirmwareType::Unknown => Self::PicoFido(PicoFidoFirmware::new(ver)),
|
||||
FirmwareType::LkOne | FirmwareType::Unknown => {
|
||||
Self::PicoFido(PicoFidoFirmware::new(ver))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_legacy(fw_type: FirmwareType, version: &str, has_legacy_vendor: bool) -> Self {
|
||||
let ver = FirmwareVersion::parse(version).unwrap_or_default();
|
||||
match fw_type {
|
||||
FirmwareType::PicoFido => {
|
||||
Self::PicoFido(PicoFidoFirmware::new(ver).with_legacy_vendor(has_legacy_vendor))
|
||||
}
|
||||
FirmwareType::RSKey => Self::RSKey(RSKeyFirmware::new(ver)),
|
||||
FirmwareType::LkOne | FirmwareType::Unknown => {
|
||||
Self::PicoFido(PicoFidoFirmware::new(ver))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +88,13 @@ impl AnyFirmware {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_fido_config_write(&self) -> bool {
|
||||
match self {
|
||||
Self::PicoFido(fw) => fw.supports_fido_config_write(),
|
||||
Self::RSKey(fw) => fw.supports_fido_config_write(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_new_fido_hardware_config(&self) -> bool {
|
||||
match self {
|
||||
Self::PicoFido(fw) => !fw.supports_legacy_fido_hardware_config(),
|
||||
|
||||
@@ -5,11 +5,22 @@ use crate::hal::types::FirmwareType;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PicoFidoFirmware {
|
||||
version: FirmwareVersion,
|
||||
/// Whether the device responded positively to the legacy
|
||||
/// VendorPrototype 0xFF probe (PicoForge CONFIG_PHY_* commands).
|
||||
has_legacy_vendor: bool,
|
||||
}
|
||||
|
||||
impl PicoFidoFirmware {
|
||||
pub fn new(version: FirmwareVersion) -> Self {
|
||||
Self { version }
|
||||
Self {
|
||||
version,
|
||||
has_legacy_vendor: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_legacy_vendor(mut self, legacy: bool) -> Self {
|
||||
self.has_legacy_vendor = legacy;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +34,13 @@ impl FirmwareTrait for PicoFidoFirmware {
|
||||
}
|
||||
|
||||
fn supports_legacy_fido_hardware_config(&self) -> bool {
|
||||
self.version.major < 7 || (self.version.major == 7 && self.version.minor <= 2)
|
||||
self.has_legacy_vendor
|
||||
|| self.version.major < 7
|
||||
|| (self.version.major == 7 && self.version.minor <= 2)
|
||||
}
|
||||
|
||||
fn supports_fido_config_write(&self) -> bool {
|
||||
self.has_legacy_vendor || self.version.major >= 7
|
||||
}
|
||||
|
||||
fn supports_rs_key_vendor_command(&self) -> bool {
|
||||
|
||||
@@ -22,12 +22,26 @@ impl FirmwareTrait for RSKeyFirmware {
|
||||
&self.version
|
||||
}
|
||||
|
||||
/// RS-Key reports firmware 5.x (< 7) per the SDK version scheme.
|
||||
/// Per the protocol integration notes, this version range triggers
|
||||
/// PicoForge's legacy hardware-config path (authenticatorConfig +
|
||||
/// vendorPrototype) which RS-Key supports for writes, and for reads
|
||||
/// it tries the 0x41 CONFIG_READ path instead.
|
||||
fn supports_legacy_fido_hardware_config(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// RS-Key supports FIDO config write via CTAPHID 0x41 CONFIG_WRITE
|
||||
/// on v0.3.1+. The CTAP firmware version from GET_INFO reports the SDK
|
||||
/// version (e.g., 5.7) which does not map to the RS-Key release version,
|
||||
/// so we cannot version-gate here. Actual support is determined via a
|
||||
/// runtime CONFIG_READ probe in write_rskey_config().
|
||||
fn supports_fido_config_write(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_rs_key_vendor_command(&self) -> bool {
|
||||
self.version.is_at_least(0, 1)
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_rescue_channel(&self) -> bool {
|
||||
|
||||
+92
-25
@@ -1,26 +1,40 @@
|
||||
use crate::{
|
||||
error::PFError,
|
||||
hal::{fido, rescue, types::*},
|
||||
hal::{fido, rescue, transport::DeviceHandle, types::*},
|
||||
};
|
||||
|
||||
pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
|
||||
let mut fido_status: Option<FullDeviceStatus> = None;
|
||||
let mut rescue_status: Option<FullDeviceStatus> = None;
|
||||
let mut rescue_fw_type: Option<FirmwareType> = None;
|
||||
|
||||
match fido::read_device_details() {
|
||||
Ok(status) => {
|
||||
log::info!("FIDO device details read successfully");
|
||||
fido_status = Some(status);
|
||||
}
|
||||
Err(e) => log::warn!("FIDO read_device_details failed: {}", e),
|
||||
// Discover via FIDO/HID transport
|
||||
match DeviceHandle::try_fido() {
|
||||
Ok(Some((_handle, _identity))) => match fido::read_device_details() {
|
||||
Ok(status) => {
|
||||
log::info!("FIDO device details read successfully");
|
||||
fido_status = Some(status);
|
||||
}
|
||||
Err(e) => log::warn!("FIDO read_device_details failed: {}", e),
|
||||
},
|
||||
Ok(None) => log::info!("No FIDO HID device found"),
|
||||
Err(e) => log::warn!("FIDO HID discovery error: {}", e),
|
||||
}
|
||||
|
||||
match rescue::read_device_details() {
|
||||
Ok(status) => {
|
||||
log::info!("Rescue device details read successfully");
|
||||
rescue_status = Some(status);
|
||||
// Discover via Rescue/PC/SC transport
|
||||
match DeviceHandle::try_rescue() {
|
||||
Ok(Some((handle, _identity))) => {
|
||||
rescue_fw_type = Some(handle.firmware_type());
|
||||
match rescue::read_device_details() {
|
||||
Ok(status) => {
|
||||
log::info!("Rescue device details read successfully");
|
||||
rescue_status = Some(status);
|
||||
}
|
||||
Err(e) => log::warn!("Rescue read_device_details failed: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => log::warn!("Rescue read_device_details failed: {}", e),
|
||||
Ok(None) => log::info!("No Rescue PC/SC device found"),
|
||||
Err(e) => log::warn!("Rescue PC/SC discovery error: {}", e),
|
||||
}
|
||||
|
||||
match (fido_status, rescue_status) {
|
||||
@@ -79,7 +93,17 @@ pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
|
||||
}
|
||||
(None, Some(rescue)) => {
|
||||
log::info!("Using Rescue-only device details");
|
||||
Ok(rescue)
|
||||
let ft = rescue_fw_type.and_then(|ft| {
|
||||
if rescue.firmware_type == FirmwareType::Unknown {
|
||||
Some(ft)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
Ok(FullDeviceStatus {
|
||||
firmware_type: ft.unwrap_or(rescue.firmware_type),
|
||||
..rescue
|
||||
})
|
||||
}
|
||||
(None, None) => {
|
||||
log::error!("Failed to read device details via both FIDO and Rescue");
|
||||
@@ -110,25 +134,68 @@ pub fn write_config(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_led_config() -> Result<LedStatusConfig, PFError> {
|
||||
rescue::read_led_config()
|
||||
pub fn read_led_config(method: DeviceMethod) -> Result<LedStatusConfig, PFError> {
|
||||
match method {
|
||||
DeviceMethod::Fido => {
|
||||
let transport = crate::hal::fido::hid::HidTransport::open()?;
|
||||
fido::read_rskey_led_config(&transport)
|
||||
}
|
||||
DeviceMethod::Rescue => rescue::read_led_config(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_led_status(
|
||||
status: u8,
|
||||
color: u8,
|
||||
brightness: u8,
|
||||
steady: bool,
|
||||
pub fn write_led_config(
|
||||
method: DeviceMethod,
|
||||
config: LedStatusConfig,
|
||||
pin: Option<String>,
|
||||
) -> Result<String, PFError> {
|
||||
rescue::write_led_status(status, color, brightness, steady)
|
||||
match method {
|
||||
DeviceMethod::Fido => {
|
||||
let pin = pin.ok_or_else(|| {
|
||||
PFError::Device("PIN is required for FIDO LED config write".into())
|
||||
})?;
|
||||
let transport = crate::hal::fido::hid::HidTransport::open()?;
|
||||
fido::write_rskey_led_config(&transport, &config, &pin)
|
||||
}
|
||||
DeviceMethod::Rescue => {
|
||||
for i in 0..4 {
|
||||
let (color, brightness) = config.statuses[i];
|
||||
rescue::write_led_status(i as u8, color, brightness, config.steady)?;
|
||||
}
|
||||
Ok("LED configuration applied successfully.".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_management_config() -> Result<ManagementAppConfig, PFError> {
|
||||
rescue::read_management_config()
|
||||
pub fn read_management_config(method: DeviceMethod) -> Result<ManagementAppConfig, PFError> {
|
||||
match method {
|
||||
DeviceMethod::Fido => {
|
||||
let transport = crate::hal::fido::hid::HidTransport::open()?;
|
||||
let info = fido::read_rskey_management_info(&transport)?;
|
||||
Ok(ManagementAppConfig {
|
||||
usb_supported: info.usb_supported.unwrap_or(0),
|
||||
usb_enabled: info.usb_enabled.unwrap_or(0),
|
||||
})
|
||||
}
|
||||
DeviceMethod::Rescue => rescue::read_management_config(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_management_config(enabled_mask: u16) -> Result<String, PFError> {
|
||||
rescue::write_management_config(enabled_mask)
|
||||
pub fn write_management_config(
|
||||
method: DeviceMethod,
|
||||
enabled_mask: u16,
|
||||
pin: Option<String>,
|
||||
) -> Result<String, PFError> {
|
||||
match method {
|
||||
DeviceMethod::Fido => {
|
||||
let pin = pin.ok_or_else(|| {
|
||||
PFError::Device("PIN is required for FIDO management config write".into())
|
||||
})?;
|
||||
let transport = crate::hal::fido::hid::HidTransport::open()?;
|
||||
fido::write_rskey_dev_config(&transport, enabled_mask, &pin)
|
||||
}
|
||||
DeviceMethod::Rescue => rescue::write_management_config(enabled_mask),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_fido_info() -> Result<FidoDeviceInfo, String> {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#![allow(dead_code)]
|
||||
use std::fmt;
|
||||
|
||||
use crate::error::PFError;
|
||||
@@ -7,19 +6,20 @@ use crate::hal::types::FirmwareType;
|
||||
|
||||
pub enum DeviceHandle {
|
||||
Fido(HidTransport),
|
||||
Rescue(pcsc::Card, FirmwareType),
|
||||
Rescue(FirmwareType),
|
||||
}
|
||||
|
||||
impl fmt::Debug for DeviceHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Fido(t) => f.debug_tuple("Fido").field(t).finish(),
|
||||
Self::Rescue(_, ft) => f.debug_tuple("Rescue").field(ft).finish(),
|
||||
Self::Rescue(ft) => f.debug_tuple("Rescue").field(ft).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct DeviceIdentity {
|
||||
pub vid: u16,
|
||||
pub pid: u16,
|
||||
@@ -28,6 +28,24 @@ pub struct DeviceIdentity {
|
||||
}
|
||||
|
||||
impl DeviceHandle {
|
||||
pub fn firmware_type(&self) -> FirmwareType {
|
||||
match self {
|
||||
Self::Fido(_) => FirmwareType::Unknown,
|
||||
Self::Rescue(ft) => ft.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the inner FIDO transport, consuming the handle.
|
||||
#[allow(dead_code)]
|
||||
pub fn into_fido(self) -> Option<HidTransport> {
|
||||
match self {
|
||||
Self::Fido(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to discover a device via FIDO HID first, falling back to Rescue PC/SC.
|
||||
#[allow(dead_code)]
|
||||
pub fn discover() -> Result<(Self, DeviceIdentity), PFError> {
|
||||
match Self::try_fido() {
|
||||
Ok(Some((handle, identity))) => {
|
||||
@@ -50,7 +68,8 @@ impl DeviceHandle {
|
||||
Err(PFError::NoDevice)
|
||||
}
|
||||
|
||||
fn try_fido() -> Result<Option<(Self, DeviceIdentity)>, PFError> {
|
||||
/// Try to connect via FIDO HID transport.
|
||||
pub fn try_fido() -> Result<Option<(Self, DeviceIdentity)>, PFError> {
|
||||
let transport = HidTransport::open()?;
|
||||
let identity = DeviceIdentity {
|
||||
vid: transport.vid,
|
||||
@@ -61,7 +80,8 @@ impl DeviceHandle {
|
||||
Ok(Some((Self::Fido(transport), identity)))
|
||||
}
|
||||
|
||||
fn try_rescue() -> Result<Option<(Self, DeviceIdentity)>, PFError> {
|
||||
/// Try to connect via Rescue PC/SC transport.
|
||||
pub fn try_rescue() -> Result<Option<(Self, DeviceIdentity)>, PFError> {
|
||||
let ctx = pcsc::Context::establish(pcsc::Scope::User).map_err(PFError::Pcsc)?;
|
||||
let mut readers_buf = [0; 2048];
|
||||
let mut readers = ctx.list_readers(&mut readers_buf).map_err(PFError::Pcsc)?;
|
||||
@@ -75,15 +95,18 @@ impl DeviceHandle {
|
||||
} else {
|
||||
FirmwareType::Unknown
|
||||
};
|
||||
// Connection opened just to verify the reader is responsive;
|
||||
// actual rescue operations open their own PC/SC connections.
|
||||
let card = ctx
|
||||
.connect(reader, pcsc::ShareMode::Shared, pcsc::Protocols::ANY)
|
||||
.map_err(PFError::Pcsc)?;
|
||||
drop(card);
|
||||
let identity = DeviceIdentity {
|
||||
vid: 0,
|
||||
pid: 0,
|
||||
product_name: reader_name.to_string(),
|
||||
firmware_type: fw_type.clone(),
|
||||
};
|
||||
Ok(Some((Self::Rescue(card, fw_type), identity)))
|
||||
Ok(Some((Self::Rescue(fw_type), identity)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ pub enum DeviceMethod {
|
||||
pub enum FirmwareType {
|
||||
PicoFido,
|
||||
RSKey,
|
||||
LkOne,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
@@ -109,6 +110,7 @@ impl fmt::Display for FirmwareType {
|
||||
match self {
|
||||
Self::PicoFido => write!(f, "pico-fido"),
|
||||
Self::RSKey => write!(f, "RS-Key"),
|
||||
Self::LkOne => write!(f, "LK-ONE"),
|
||||
Self::Unknown => write!(f, "Unknown"),
|
||||
}
|
||||
}
|
||||
@@ -179,3 +181,8 @@ pub struct StoredCredential {
|
||||
pub const RSKEY_AAGUID: &str = "2479C7BF6B3056839EC80E8171A918B7";
|
||||
/// AAGUID assigned to Pico-Fido hardware.
|
||||
pub const PICOFIDO_AAGUID: &str = "89FB94B706C936739B7E30526D968145";
|
||||
/// AAGUID assigned to LibreKeys LK-ONE hardware (same as pico-fido fork).
|
||||
pub const LKONE_AAGUID: &str = "89FB94B706C936739B7E30526D968145";
|
||||
/// LibreKeys USB VID:PID allocated by OpenMoko.
|
||||
pub const LKONE_VID: u16 = 0x1D50;
|
||||
pub const LKONE_PID: u16 = 0x619B;
|
||||
|
||||
+28
-17
@@ -36,6 +36,11 @@ pub struct PinPromptContent {
|
||||
}
|
||||
|
||||
impl PinPromptContent {
|
||||
pub fn set_loading_msg(&mut self, msg: impl Into<String>, cx: &mut Context<Self>) {
|
||||
self.phase = DialogPhase::LoadingWithMessage(msg.into());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_loading(&mut self, cx: &mut Context<Self>) {
|
||||
self.phase = DialogPhase::Loading;
|
||||
cx.notify();
|
||||
@@ -95,23 +100,29 @@ impl Render for PinPromptContent {
|
||||
)
|
||||
.into_any_element(),
|
||||
|
||||
DialogPhase::Loading | DialogPhase::LoadingWithMessage(_) => v_flex()
|
||||
.gap_4()
|
||||
.child(self.description.clone())
|
||||
.child(Input::new(&self.pin_input).disabled(true))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.gap_2()
|
||||
.child(Button::new("cancel").label("Cancel").disabled(true))
|
||||
.child(
|
||||
Button::new("confirm")
|
||||
.primary()
|
||||
.label("Loading...")
|
||||
.loading(true),
|
||||
),
|
||||
)
|
||||
.into_any_element(),
|
||||
DialogPhase::Loading | DialogPhase::LoadingWithMessage(_) => {
|
||||
let text = match &self.phase {
|
||||
DialogPhase::LoadingWithMessage(msg) => msg.clone(),
|
||||
_ => self.description.to_string(),
|
||||
};
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child(text)
|
||||
.child(Input::new(&self.pin_input).disabled(true))
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.gap_2()
|
||||
.child(Button::new("cancel").label("Cancel").disabled(true))
|
||||
.child(
|
||||
Button::new("confirm")
|
||||
.primary()
|
||||
.label("Loading...")
|
||||
.loading(true),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
DialogPhase::Error(err_msg) => {
|
||||
let pin_input = self.pin_input.clone();
|
||||
|
||||
+22
-20
@@ -14,6 +14,7 @@
|
||||
//! - **`apply_fresh_state()`** lets ViewModels push post-write HAL results
|
||||
//! back into the repo so subscribers get the event.
|
||||
|
||||
use crate::hal::firmwares::AnyFirmware;
|
||||
use crate::hal::io;
|
||||
use crate::hal::types;
|
||||
use gpui::*;
|
||||
@@ -23,7 +24,8 @@ pub use crate::hal::rescue::constants::{
|
||||
USB_CAP_U2F,
|
||||
};
|
||||
pub use types::{
|
||||
AppConfigInput, DeviceMethod, FidoDeviceInfo, FirmwareType, FullDeviceStatus, StoredCredential,
|
||||
AppConfigInput, DeviceMethod, FidoDeviceInfo, FirmwareType, FullDeviceStatus, LedStatusConfig,
|
||||
StoredCredential,
|
||||
};
|
||||
|
||||
// ── Events ──────────────────────────────────────────────────────────────────
|
||||
@@ -70,18 +72,19 @@ impl DeviceRepo {
|
||||
|
||||
// ── HAL static methods (blocking — call from background executor) ──────
|
||||
|
||||
pub fn firmware_supports_legacy_fido_config(version: &str) -> bool {
|
||||
crate::hal::fido::firmware_supports_legacy_fido_hardware_config(version)
|
||||
pub fn firmware_supports_legacy_fido_config(
|
||||
fw_type: &types::FirmwareType,
|
||||
version: &str,
|
||||
) -> bool {
|
||||
AnyFirmware::new(fw_type.clone(), version).supports_legacy_fido_hardware_config()
|
||||
}
|
||||
|
||||
pub fn read_device_state_blocking() -> Result<FreshDeviceState, crate::error::PFError> {
|
||||
let status = io::read_device_details()?;
|
||||
let (led_status, management_apps) = if status.firmware_type == types::FirmwareType::RSKey
|
||||
&& status.method == types::DeviceMethod::Rescue
|
||||
{
|
||||
let (led_status, management_apps) = if status.firmware_type == types::FirmwareType::RSKey {
|
||||
(
|
||||
io::read_led_config().ok(),
|
||||
io::read_management_config().ok(),
|
||||
io::read_led_config(status.method.clone()).ok(),
|
||||
io::read_management_config(status.method.clone()).ok(),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
@@ -101,19 +104,20 @@ impl DeviceRepo {
|
||||
io::write_config(config, method, pin)
|
||||
}
|
||||
|
||||
pub fn write_led_status_blocking(
|
||||
status_idx: u8,
|
||||
color: u8,
|
||||
brightness: u8,
|
||||
steady: bool,
|
||||
pub fn write_led_config_blocking(
|
||||
method: DeviceMethod,
|
||||
config: LedStatusConfig,
|
||||
pin: Option<String>,
|
||||
) -> Result<String, crate::error::PFError> {
|
||||
io::write_led_status(status_idx, color, brightness, steady)
|
||||
io::write_led_config(method, config, pin)
|
||||
}
|
||||
|
||||
pub fn write_management_config_blocking(
|
||||
method: DeviceMethod,
|
||||
enabled_mask: u16,
|
||||
pin: Option<String>,
|
||||
) -> Result<String, crate::error::PFError> {
|
||||
io::write_management_config(enabled_mask)
|
||||
io::write_management_config(method, enabled_mask, pin)
|
||||
}
|
||||
|
||||
pub fn get_fido_info_blocking() -> Result<types::FidoDeviceInfo, String> {
|
||||
@@ -223,11 +227,9 @@ impl DeviceRepo {
|
||||
}
|
||||
}
|
||||
|
||||
if status.firmware_type == types::FirmwareType::RSKey
|
||||
&& status.method == types::DeviceMethod::Rescue
|
||||
{
|
||||
self.led_status = io::read_led_config().ok();
|
||||
self.management_apps = io::read_management_config().ok();
|
||||
if status.firmware_type == types::FirmwareType::RSKey {
|
||||
self.led_status = io::read_led_config(status.method.clone()).ok();
|
||||
self.management_apps = io::read_management_config(status.method.clone()).ok();
|
||||
} else {
|
||||
self.led_status = None;
|
||||
self.management_apps = None;
|
||||
|
||||
@@ -578,39 +578,44 @@ impl Render for ConfigViewModel {
|
||||
let device = self.device.read(cx);
|
||||
let status = device.status.clone();
|
||||
let is_fido = status.as_ref().map(|s| s.method.clone()) == Some(DeviceMethod::Fido);
|
||||
let is_rskey = status.as_ref().map(|s| &s.firmware_type) == Some(&FirmwareType::RSKey);
|
||||
|
||||
let supports_legacy_fido_config = status
|
||||
.as_ref()
|
||||
.map(ConfigViewModel::status_supports_legacy_fido_config)
|
||||
.unwrap_or(false);
|
||||
let hardware_config_disabled = is_fido && !supports_legacy_fido_config;
|
||||
|
||||
let hardware_config_disabled = is_fido && !supports_legacy_fido_config && !is_rskey;
|
||||
|
||||
// RS-Key supports full config read/write over FIDO via CONFIG_READ/CONFIG_WRITE.
|
||||
// Other firmwares (pico-fido) don't: product name, LED driver, curves, etc.
|
||||
let fido_no_rskey = is_fido && !is_rskey;
|
||||
|
||||
let led_card = self
|
||||
.render_led_card(cx, is_fido, hardware_config_disabled)
|
||||
.render_led_card(cx, fido_no_rskey, hardware_config_disabled)
|
||||
.into_any_element();
|
||||
let options_card = self
|
||||
.render_options_card(cx, is_fido, hardware_config_disabled)
|
||||
.render_options_card(cx, fido_no_rskey, hardware_config_disabled)
|
||||
.into_any_element();
|
||||
|
||||
let identity_card = self
|
||||
.render_identity_card(cx.theme(), is_fido, hardware_config_disabled)
|
||||
.render_identity_card(cx.theme(), fido_no_rskey, hardware_config_disabled)
|
||||
.into_any_element();
|
||||
let touch_card = self
|
||||
.render_touch_card(cx.theme(), is_fido)
|
||||
.render_touch_card(cx.theme(), fido_no_rskey)
|
||||
.into_any_element();
|
||||
|
||||
let is_wide = window.bounds().size.width > px(1100.0);
|
||||
let columns = if is_wide { 2 } else { 1 };
|
||||
|
||||
let is_rskey = status.as_ref().map(|s| &s.firmware_type) == Some(&FirmwareType::RSKey);
|
||||
|
||||
let mut grid_children = vec![identity_card, led_card, touch_card, options_card];
|
||||
|
||||
if is_rskey {
|
||||
let rskey_led = self.render_rskey_led_card(cx, is_fido).into_any_element();
|
||||
let rskey_apps = self.render_rskey_apps_card(cx, is_fido).into_any_element();
|
||||
let rskey_usb_itf = self
|
||||
.render_rskey_usb_itf_card(cx, is_fido)
|
||||
.into_any_element();
|
||||
// RS-Key cards always enabled in FIDO mode — they work via
|
||||
// CONFIG_WRITE with PIN token.
|
||||
let rskey_led = self.render_rskey_led_card(cx, false).into_any_element();
|
||||
let rskey_apps = self.render_rskey_apps_card(cx, false).into_any_element();
|
||||
let rskey_usb_itf = self.render_rskey_usb_itf_card(cx, false).into_any_element();
|
||||
grid_children.push(rskey_led);
|
||||
grid_children.push(rskey_apps);
|
||||
grid_children.push(rskey_usb_itf);
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::ui::app::AppModels;
|
||||
use crate::ui::components::dialog::PinPromptContent;
|
||||
use crate::ui::components::{dialog, dialog::StatusContent};
|
||||
use crate::ui::models::device::{
|
||||
AppConfigInput, DeviceEvent, DeviceMethod, DeviceRepo, FullDeviceStatus,
|
||||
AppConfigInput, DeviceEvent, DeviceMethod, DeviceRepo, FullDeviceStatus, LedStatusConfig,
|
||||
};
|
||||
use gpui::*;
|
||||
use gpui_component::input::InputState;
|
||||
@@ -446,11 +446,33 @@ impl ConfigViewModel {
|
||||
return;
|
||||
}
|
||||
|
||||
let dialog = dialog_handle;
|
||||
|
||||
// Tell the user to look at their key!
|
||||
cx.update(|cx| {
|
||||
match &dialog {
|
||||
StatusDialogHandle::Pin(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_loading_msg("Applying configuration... Please touch your device if it flashes.", cx);
|
||||
});
|
||||
}
|
||||
StatusDialogHandle::Status(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_loading("Applying configuration... Please touch your device if it flashes.", cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}).ok();
|
||||
|
||||
let result = cx
|
||||
.background_executor()
|
||||
.spawn(async move { DeviceRepo::write_config_blocking(changes, method_clone, pin) })
|
||||
.spawn(async move {
|
||||
DeviceRepo::write_config_blocking(changes, method_clone, pin)
|
||||
})
|
||||
.await;
|
||||
|
||||
let dialog_handle = dialog;
|
||||
|
||||
let fresh_state = if result.is_ok() {
|
||||
cx.background_executor()
|
||||
.spawn(async move { DeviceRepo::read_device_state_blocking().ok() })
|
||||
@@ -516,6 +538,8 @@ impl ConfigViewModel {
|
||||
|
||||
if method == DeviceMethod::Fido && err_msg.contains("0x3E") {
|
||||
err_msg = "The device firmware does not support being configured in fido only communication mode. \nHave a look at the troubleshooting guide to fix this".to_string();
|
||||
} else if method == DeviceMethod::Fido && err_msg.contains("0x27") {
|
||||
err_msg = "Configuration denied (Status: 0x27). This usually means the operation timed out waiting for you to touch the device's button, or the PIN token was rejected.".to_string();
|
||||
}
|
||||
|
||||
match &dialog_handle {
|
||||
@@ -681,7 +705,8 @@ impl ConfigViewModel {
|
||||
};
|
||||
|
||||
if method == DeviceMethod::Fido {
|
||||
if Self::status_supports_legacy_fido_config(status) {
|
||||
let is_rskey = status.firmware_type == crate::ui::models::device::FirmwareType::RSKey;
|
||||
if Self::status_supports_legacy_fido_config(status) || is_rskey {
|
||||
self.open_pin_dialog(changes, window, cx);
|
||||
} else {
|
||||
let handle =
|
||||
@@ -708,7 +733,10 @@ impl ConfigViewModel {
|
||||
|
||||
pub(super) fn status_supports_legacy_fido_config(status: &FullDeviceStatus) -> bool {
|
||||
status.method == DeviceMethod::Fido
|
||||
&& DeviceRepo::firmware_supports_legacy_fido_config(&status.info.firmware_version)
|
||||
&& DeviceRepo::firmware_supports_legacy_fido_config(
|
||||
&status.firmware_type,
|
||||
&status.info.firmware_version,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -799,27 +827,194 @@ impl ConfigViewModel {
|
||||
}
|
||||
|
||||
pub(super) fn apply_rskey_led_settings(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let steady = self.led_status_steady;
|
||||
let colors = self.led_status_colors;
|
||||
let brightnesses = self.led_status_brightness;
|
||||
let config = LedStatusConfig {
|
||||
steady: self.led_status_steady,
|
||||
statuses: [
|
||||
(self.led_status_colors[0], self.led_status_brightness[0]),
|
||||
(self.led_status_colors[1], self.led_status_brightness[1]),
|
||||
(self.led_status_colors[2], self.led_status_brightness[2]),
|
||||
(self.led_status_colors[3], self.led_status_brightness[3]),
|
||||
],
|
||||
};
|
||||
|
||||
let method = self
|
||||
.device
|
||||
.read(cx)
|
||||
.status
|
||||
.as_ref()
|
||||
.map(|s| s.method.clone());
|
||||
|
||||
if method == Some(DeviceMethod::Fido) {
|
||||
let view_handle = cx.entity().downgrade();
|
||||
dialog::open_pin_prompt(
|
||||
"Authentication Required",
|
||||
"Enter your device PIN to update LED configuration.",
|
||||
None,
|
||||
"Confirm",
|
||||
window,
|
||||
cx,
|
||||
move |pin, dialog_handle, cx| {
|
||||
let _ = view_handle.update(cx, |this, cx| {
|
||||
this.do_write_led_config(
|
||||
config.clone(),
|
||||
DeviceMethod::Fido,
|
||||
Some(pin),
|
||||
StatusDialogHandle::Pin(dialog_handle),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let handle = dialog::open_status_dialog("Applying LED Configuration...", window, cx);
|
||||
self.do_write_led_config(
|
||||
config,
|
||||
DeviceMethod::Rescue,
|
||||
None,
|
||||
StatusDialogHandle::Status(handle),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_write_led_config(
|
||||
&mut self,
|
||||
config: LedStatusConfig,
|
||||
method: DeviceMethod,
|
||||
pin: Option<String>,
|
||||
dialog_handle: StatusDialogHandle,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.loading = true;
|
||||
let handle = dialog::open_status_dialog("Applying LED Configuration...", window, cx);
|
||||
cx.notify();
|
||||
|
||||
let entity = cx.entity().downgrade();
|
||||
|
||||
self._task = Some(cx.spawn(async move |_, cx| {
|
||||
let result = cx
|
||||
.background_executor()
|
||||
.spawn(async move { DeviceRepo::write_led_config_blocking(method, config, pin) })
|
||||
.await;
|
||||
|
||||
let fresh_state = if result.is_ok() {
|
||||
cx.background_executor()
|
||||
.spawn(async move { DeviceRepo::read_device_state_blocking().ok() })
|
||||
.await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let _ = entity.update(cx, |this, cx| {
|
||||
this.loading = false;
|
||||
match result {
|
||||
Ok(_) => {
|
||||
if let Some(fs) = fresh_state {
|
||||
this.device.update(cx, |repo, repo_cx| {
|
||||
repo.apply_fresh_state(fs, repo_cx);
|
||||
});
|
||||
}
|
||||
match &dialog_handle {
|
||||
StatusDialogHandle::Pin(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_success(
|
||||
"LED configuration applied successfully.".to_string(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
StatusDialogHandle::Status(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_success(
|
||||
"LED configuration applied successfully.".to_string(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => match &dialog_handle {
|
||||
StatusDialogHandle::Pin(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_error(format!("Failed to apply LED config: {}", e), cx);
|
||||
});
|
||||
}
|
||||
StatusDialogHandle::Status(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_error(format!("Failed to apply LED config: {}", e), cx);
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
pub(super) fn apply_rskey_apps_settings(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mask = self.usb_apps_enabled;
|
||||
|
||||
let method = self
|
||||
.device
|
||||
.read(cx)
|
||||
.status
|
||||
.as_ref()
|
||||
.map(|s| s.method.clone());
|
||||
|
||||
if method == Some(DeviceMethod::Fido) {
|
||||
let view_handle = cx.entity().downgrade();
|
||||
dialog::open_pin_prompt(
|
||||
"Authentication Required",
|
||||
"Enter your device PIN to update USB application configuration.",
|
||||
None,
|
||||
"Confirm",
|
||||
window,
|
||||
cx,
|
||||
move |pin, dialog_handle, cx| {
|
||||
let _ = view_handle.update(cx, |this, cx| {
|
||||
this.do_write_management_config(
|
||||
mask,
|
||||
DeviceMethod::Fido,
|
||||
Some(pin),
|
||||
StatusDialogHandle::Pin(dialog_handle),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
let handle = dialog::open_status_dialog("Applying USB Applications...", window, cx);
|
||||
self.do_write_management_config(
|
||||
mask,
|
||||
DeviceMethod::Rescue,
|
||||
None,
|
||||
StatusDialogHandle::Status(handle),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_write_management_config(
|
||||
&mut self,
|
||||
mask: u16,
|
||||
method: DeviceMethod,
|
||||
pin: Option<String>,
|
||||
dialog_handle: StatusDialogHandle,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.loading = true;
|
||||
cx.notify();
|
||||
|
||||
let entity = cx.entity().downgrade();
|
||||
|
||||
self._task = Some(cx.spawn(async move |_, cx| {
|
||||
let result = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
for i in 0..4 {
|
||||
DeviceRepo::write_led_status_blocking(
|
||||
i as u8,
|
||||
colors[i],
|
||||
brightnesses[i],
|
||||
steady,
|
||||
)?;
|
||||
}
|
||||
Ok::<_, crate::error::PFError>(())
|
||||
DeviceRepo::write_management_config_blocking(method, mask, pin)
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -840,70 +1035,38 @@ impl ConfigViewModel {
|
||||
repo.apply_fresh_state(fs, repo_cx);
|
||||
});
|
||||
}
|
||||
let _ = handle.update(cx, |d, cx| {
|
||||
d.set_success(
|
||||
"LED configuration applied successfully.".to_string(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = handle.update(cx, |d, cx| {
|
||||
d.set_error(format!("Failed to apply LED config: {}", e), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
pub(super) fn apply_rskey_apps_settings(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mask = self.usb_apps_enabled;
|
||||
|
||||
self.loading = true;
|
||||
let handle = dialog::open_status_dialog("Applying USB Applications...", window, cx);
|
||||
let entity = cx.entity().downgrade();
|
||||
|
||||
self._task = Some(cx.spawn(async move |_, cx| {
|
||||
let result = cx
|
||||
.background_executor()
|
||||
.spawn(async move { DeviceRepo::write_management_config_blocking(mask) })
|
||||
.await;
|
||||
|
||||
let fresh_state = if result.is_ok() {
|
||||
cx.background_executor()
|
||||
.spawn(async move { DeviceRepo::read_device_state_blocking().ok() })
|
||||
.await
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let _ = entity.update(cx, |this, cx| {
|
||||
this.loading = false;
|
||||
match result {
|
||||
Ok(_) => {
|
||||
if let Some(fs) = fresh_state {
|
||||
this.device.update(cx, |repo, repo_cx| {
|
||||
repo.apply_fresh_state(fs, repo_cx);
|
||||
});
|
||||
match &dialog_handle {
|
||||
StatusDialogHandle::Pin(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_success(
|
||||
"USB applications updated successfully. Please re-plug the device.".to_string(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
StatusDialogHandle::Status(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_success(
|
||||
"USB applications updated successfully. Please re-plug the device.".to_string(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
let _ = handle.update(cx, |d, cx| {
|
||||
d.set_success(
|
||||
"USB applications updated successfully. Please re-plug the device."
|
||||
.to_string(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
let _ = handle.update(cx, |d, cx| {
|
||||
d.set_error(format!("Failed to apply USB applications: {}", e), cx);
|
||||
});
|
||||
match &dialog_handle {
|
||||
StatusDialogHandle::Pin(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_error(format!("Failed to apply USB applications: {}", e), cx);
|
||||
});
|
||||
}
|
||||
StatusDialogHandle::Status(dh) => {
|
||||
let _ = dh.update(cx, |d, cx| {
|
||||
d.set_error(format!("Failed to apply USB applications: {}", e), cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::ui::components::{card::Card, page_view::PageView, tag::Tag};
|
||||
use crate::ui::models::device::{DeviceMethod, FidoDeviceInfo, FullDeviceStatus};
|
||||
use crate::ui::models::device::{DeviceMethod, FidoDeviceInfo, FirmwareType, FullDeviceStatus};
|
||||
use crate::ui::screens::home::view_model::HomeViewModel;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::*;
|
||||
@@ -245,10 +245,12 @@ impl HomeViewModel {
|
||||
|
||||
fn render_led_config(status: &FullDeviceStatus, theme: &Theme) -> impl IntoElement {
|
||||
let config = &status.config;
|
||||
let has_fido_config =
|
||||
status.firmware_type == FirmwareType::RSKey || status.method != DeviceMethod::Fido;
|
||||
Card::new()
|
||||
.title("LED Configuration")
|
||||
.icon(Icon::default().path("icons/microchip.svg"))
|
||||
.child(if status.method == DeviceMethod::Fido {
|
||||
.child(if !has_fido_config {
|
||||
v_flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
|
||||
Reference in New Issue
Block a user