docs: add documentation to src/device/module

This commit is contained in:
Suyog Tandel
2026-06-23 21:53:00 +05:30
parent 2852893187
commit e3dc8fc1c6
8 changed files with 1739 additions and 240 deletions
File diff suppressed because it is too large Load Diff
+287 -3
View File
@@ -1,3 +1,87 @@
//! USB HID transport for CTAP2/FIDO2 communication.
//!
//! # What is HID?
//!
//! USB HID (Human Interface Device) is a standard USB device class for input
//! devices like keyboards, mice, and gamepads. HID devices communicate through
//! *reports* — fixed-size packets sent/received on USB endpoints. The OS
//! auto-detects HID devices without requiring custom drivers, making it ideal
//! for FIDO2 security keys that need to work across platforms.
//!
//! # What is CTAPHID?
//!
//! CTAPHID is the [CTAP2] transport binding for USB HID. It layers the CTAP2
//! protocol on top of HID reports, allowing FIDO2 authenticators to
//! communicate with hosts through the standard HID driver stack. The
//! specification is defined in [CTAP2 §11.2](https://fidoalliance.org/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html#usb-human-interface-device-hid).
//!
//! # Framing protocol
//!
//! CTAPHID uses 64-byte HID reports. Messages that exceed 64 bytes are split
//! across multiple packets:
//!
//! ```text
//! Init Packet (64 bytes):
//! CID(4) | CMD(1) | BCNT_HI(1) | BCNT_LO(1) | payload[..57]
//!
//! Continuation Packets:
//! CID(4) | SEQ(1) | payload[..59]
//! ```
//!
//! - **CID** (Channel ID): 4-byte identifier negotiated via `CTAPHID_INIT`.
//! Multiplexes multiple logical channels on one HID device.
//! - **CMD**: Command byte (e.g., `0x90` for CBOR, `0x86` for INIT).
//! - **BCNT**: 16-bit big-endian payload length.
//! - **SEQ**: Sequence number for continuation packets (starts at 0).
//!
//! # Channel initialization
//!
//! Before any CTAP2 command can be sent, the host must negotiate a Channel ID:
//!
//! 1. Host sends `CTAPHID_INIT` to the broadcast CID (`0xFFFFFFFF`) with a
//! random 8-byte nonce.
//! 2. Device responds with the same nonce and a newly allocated CID.
//! 3. All subsequent communication uses this CID.
//!
//! This allows multiple CTAP2 sessions to coexist on one device (e.g., two
//! browsers open simultaneously).
//!
//! # Cryptographic operations
//!
//! PIN operations require ECDH key agreement and AES-256-CBC encryption:
//!
//! ```text
//! 1. Host → Device: GetKeyAgreement (returns device's P-256 public key)
//! 2. Host generates ephemeral P-256 key pair
//! 3. Host computes ECDH shared secret → SHA-256(shared_secret)
//! 4. PIN hash encrypted with AES-256-CBC (key = shared_secret, IV = 0)
//! 5. Token decrypted with same key
//! ```
//!
//! The shared secret is derived as `SHA-256(ECDH_x_coordinate)`.
//!
//! # Firmware compatibility
//!
//! Both [pico-fido] and [RS-Key] implement CTAPHID. This module handles:
//! - Standard CTAP2 commands (GetInfo, MakeCredential, GetAssertion, etc.)
//! - Pico-fido vendor commands (`0xC1`, `0xC2`) for hardware config
//! - RS-Key vendor command (`0x41`) for seed backup and attestation
//!
//! # File structure
//!
//! - [`HidTransport`] — main transport struct; opens HID device, negotiates
//! CID, sends/receives CBOR payloads
//! - [`EnumerateRpResponse`], [`EnumerateCredentialResponse`] — response
//! types for credential management enumeration
//! - PIN methods (`get_pin_token`, `set_pin`, `change_pin`) implement the
//! full ECDH + AES-CBC flow per CTAP2 §11.5.4
//! - Vendor methods (`send_vendor_config`, `get_enterprise_attestation_csr`)
//! handle pico-fido/RS-Key specific extensions
//!
//! [CTAP2]: https://fidoalliance.org/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html
//! [pico-fido]: https://github.com/polhenarejos/pico-fido
//! [RS-Key]: https://github.com/TheMaxMur/RS-Key
use cbc::cipher::{Block, BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::NoPadding};
use rand::RngExt;
use ring::{agreement, digest, hmac};
@@ -8,22 +92,70 @@ use std::time::Duration;
use crate::device::fido::constants::*;
use crate::error::PFError;
// HID Transport Constants
/// Size of a single USB HID report in bytes (CTAP2 §11.2 mandates 64-byte reports).
const HID_REPORT_SIZE: usize = 64;
/// FIDO Alliance HID Usage Page identifier.
///
/// Devices advertising this usage page in their HID descriptor are identified
/// as FIDO authenticators by the operating system's HID enumeration.
const HID_USAGE_PAGE_FIDO: u16 = 0xF1D0;
/// Broadcast Channel ID used for the initial CTAPHID_INIT handshake.
///
/// The host sends an INIT command to this CID to request a unique Channel ID
/// from the authenticator. All subsequent communication uses the negotiated CID.
const CTAPHID_CID_BROADCAST: u32 = 0xFFFFFFFF;
/// CTAPHID INIT command byte (0x86).
///
/// Initiates channel negotiation. The host sends a random 8-byte nonce; the
/// device responds with the same nonce and a newly allocated Channel ID.
const CTAPHID_INIT: u8 = 0x86;
/// CTAPHID CBOR command byte (0x90).
///
/// Wraps a CTAP2 CBOR-encoded command or response payload. The payload is
/// fragmented across one init packet and zero or more continuation packets.
pub const CTAPHID_CBOR: u8 = 0x90;
/// CTAPHID ERROR response byte (0xBF).
///
/// Indicates the authenticator encountered an error processing the command.
/// The next byte contains the CTAP2 error code.
const CTAPHID_ERROR: u8 = 0xBF;
/// CTAPHID KEEPALIVE status byte (0xBB).
///
/// Sent by the authenticator while processing a long-running operation (e.g.,
/// MakeCredential with user interaction). The host must continue reading
/// until it receives the final CBOR or ERROR response.
const CTAPHID_KEEPALIVE: u8 = 0xBB;
// Timeouts
/// Default timeout in milliseconds for draining stale HID packets.
const HID_READ_TIMEOUT_MS: i32 = 10;
/// Timeout in milliseconds for reading the CTAPHID_INIT response during channel negotiation.
const HID_INIT_READ_TIMEOUT_MS: i32 = 100;
/// Timeout in milliseconds for reading a single HID response packet (excluding keepalives).
const HID_RESP_READ_TIMEOUT_MS: i32 = 2000;
/// Timeout in milliseconds for reading CTAPHID continuation packets.
const HID_CONT_READ_TIMEOUT_MS: i32 = 500;
/// Maximum total time in milliseconds allowed for a complete CBOR command/response exchange.
const HID_TOTAL_TIMEOUT_MS: i32 = 5000;
/// USB HID transport for CTAP2/FIDO2 communication.
///
/// Wraps a `hidapi::HidDevice` and manages the CTAPHID framing layer:
/// channel negotiation (INIT), multi-packet CBOR send/receive, keepalive
/// handling, and all higher-level CTAP2 operations (PIN, credential management,
/// vendor commands).
///
/// Created via [`HidTransport::open`], which scans for a device with the FIDO
/// HID Usage Page (0xF1D0) and performs the INIT handshake to obtain a Channel ID.
pub struct HidTransport {
device: hidapi::HidDevice,
cid: u32,
@@ -32,6 +164,10 @@ pub struct HidTransport {
pub product_name: String,
}
/// Response from enumerating a Relying Party via credential management.
///
/// Returned by [`HidTransport::credential_management_enumerate_rps`]. Each entry
/// represents one RP stored on the authenticator.
#[derive(Debug, Clone)]
pub struct EnumerateRpResponse {
pub rp: Value,
@@ -40,6 +176,10 @@ pub struct EnumerateRpResponse {
pub total_rps: Option<usize>,
}
/// Response from enumerating a credential via credential management.
///
/// Returned by [`HidTransport::credential_management_enumerate_credentials`].
/// Each entry represents one credential (public key) registered under an RP.
#[derive(Debug, Clone)]
pub struct EnumerateCredentialResponse {
pub user: Value,
@@ -51,6 +191,11 @@ pub struct EnumerateCredentialResponse {
}
impl HidTransport {
/// Open the first available FIDO HID device and negotiate a Channel ID.
///
/// Scans for a device with HID Usage Page `0xF1D0`, opens it, and performs
/// the CTAPHID_INIT handshake. Returns an error if no device is found or
/// the INIT handshake times out.
pub fn open() -> Result<Self, PFError> {
log::info!("Attempting to open HID transport for FIDO device...");
let api = hidapi::HidApi::new().map_err(|e| {
@@ -101,6 +246,11 @@ impl HidTransport {
})
}
/// Negotiate a CTAPHID Channel ID via CTAPHID_INIT.
///
/// Sends an INIT command to the broadcast CID (`0xFFFFFFFF`) with a random
/// 8-byte nonce, then reads the response to extract the allocated CID.
/// Drains any stale packets before the handshake to avoid confusion.
fn init_channel(device: &hidapi::HidDevice) -> Result<u32, PFError> {
log::debug!("Initializing CTAPHID channel...");
@@ -162,10 +312,18 @@ impl HidTransport {
))
}
/// Send a CTAP2 CBOR command and wait for the response using the default timeout.
///
/// Convenience wrapper around [`send_cbor_with_timeout`](HidTransport::send_cbor_with_timeout).
pub fn send_cbor(&self, cmd: u8, payload: &[u8]) -> Result<Vec<u8>, PFError> {
self.send_cbor_with_timeout(cmd, payload, HID_TOTAL_TIMEOUT_MS)
}
/// Send a CTAP2 CBOR command and wait for the response with a custom timeout.
///
/// Fragments `payload` into CTAPHID init + continuation packets, then reads
/// and reassembles the response. The `timeout_ms` parameter overrides the
/// default for the read phase (useful for operations that require user interaction).
pub fn send_cbor_with_timeout(
&self,
cmd: u8,
@@ -176,11 +334,20 @@ impl HidTransport {
self.read_cbor_response(cmd, timeout_ms)
}
/// Send a CTAP2 CBOR command and return the raw HID response without status-byte parsing.
///
/// Unlike [`send_cbor`](HidTransport::send_cbor), this does not check the CTAP status byte
/// or strip it from the response. Useful for vendor commands that return non-standard payloads.
pub fn send_raw(&self, cmd: u8, payload: &[u8]) -> Result<Vec<u8>, PFError> {
self.write_cbor_request(cmd, payload)?;
self.read_hid_response(cmd, HID_TOTAL_TIMEOUT_MS)
}
/// Send the CTAP authenticatorReset command (0x07).
///
/// Resets the authenticator to its factory state: all credentials, PINs,
/// and configuration are erased. Uses a 30-second timeout to allow for
/// any required user interaction (e.g., touch confirmation).
pub fn reset(&self) -> Result<(), PFError> {
log::info!("Sending CTAP authenticatorReset (0x07)...");
self.write_cbor_request(CTAPHID_CBOR, &[0x07])?;
@@ -188,6 +355,11 @@ impl HidTransport {
Ok(())
}
/// Fragment and write a CTAPHID request to the device.
///
/// Encodes the command byte and payload into a CTAPHID init packet followed
/// by zero or more continuation packets, then writes each 65-byte HID report
/// (1 byte Report ID + 64 bytes payload) to the device.
fn write_cbor_request(&self, cmd: u8, payload: &[u8]) -> Result<(), PFError> {
log::debug!(
"Sending CBOR Command: 0x{:02X}, Payload Size: {} bytes",
@@ -254,6 +426,11 @@ impl HidTransport {
Ok(())
}
/// Read a CTAPHID response and verify the CTAP status byte.
///
/// Delegates to [`read_hid_response`](HidTransport::read_hid_response) for packet
/// reassembly, then checks the first byte for a non-zero CTAP status code and
/// strips it before returning the payload.
fn read_cbor_response(&self, cmd: u8, timeout_ms: i32) -> Result<Vec<u8>, PFError> {
let response_data = self.read_hid_response(cmd, timeout_ms)?;
@@ -280,6 +457,13 @@ impl HidTransport {
Ok(response_data[1..].to_vec())
}
/// Read and reassemble a CTAPHID response from the device.
///
/// Handles the full CTAPHID receive flow:
/// 1. Reads the init packet while skipping KEEPALIVE and mismatched-CID packets.
/// 2. Validates the command byte matches the expected response.
/// 3. Reads continuation packets in sequence order until the full payload is received.
/// 4. Enforces the `timeout_ms` deadline across the entire read.
fn read_hid_response(&self, cmd: u8, timeout_ms: i32) -> Result<Vec<u8>, PFError> {
log::debug!("Waiting for response...");
@@ -396,6 +580,12 @@ impl HidTransport {
Ok(response_data)
}
/// Send a pico-fido vendor-specific authenticatorConfig command.
///
/// Wraps the vendor command ID and parameter into the VendorPrototype
/// sub-command structure, signs it with the PIN token, and sends it as
/// a CTAP Config command. The parameter value type (bytes, integer, or text)
/// determines which CBOR key (0x02/0x03/0x04) is used.
pub fn send_vendor_config(
&self,
pin_token: &[u8],
@@ -537,6 +727,9 @@ impl HidTransport {
/// 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.
///
/// Builds the authenticatorConfig CBOR map with keys in ascending order, signs
/// it with the PIN token, and sends it as a CTAP Config command.
pub fn send_config(
&self,
sub_cmd: ConfigSubCommand,
@@ -585,6 +778,10 @@ impl HidTransport {
}
/// Send authenticatorConfig command to enable Enterprise attestation.
///
/// Calls the EnableEnterpriseAttestation sub-command (0x01) via [`send_config`](HidTransport::send_config).
/// Enterprise attestation allows RPs to receive a per-device attestation certificate
/// during MakeCredential, enabling enterprise device identification.
pub fn send_config_enable_ea(&self, pin_token: &[u8]) -> Result<(), PFError> {
log::debug!("Sending Enterprise Attestation enable config command...");
match self.send_config(
@@ -608,6 +805,10 @@ impl HidTransport {
}
/// Send authenticatorConfig command to set minimum PIN length.
///
/// Calls the SetMinPinLength sub-command (0x03) via [`send_config`](HidTransport::send_config).
/// The minimum PIN length can only be increased; attempting to decrease it returns
/// `PIN_POLICY_VIOLATION` (0x37). A device reset is required to lower the minimum.
pub fn send_config_set_min_pin_length(
&self,
pin_token: &[u8],
@@ -653,6 +854,11 @@ impl HidTransport {
}
}
/// Request the authenticator's P-256 ECDH public key for PIN protocol v1.
///
/// Sends a `getClientPin` command with `getKeyAgreement` sub-command (0x02).
/// The returned COSE Key contains the authenticator's ephemeral public key
/// (x and y coordinates) used for ECDH key agreement in PIN operations.
pub fn get_key_agreement(&self) -> Result<Value, PFError> {
let mut map = BTreeMap::new();
map.insert(
@@ -685,6 +891,14 @@ impl HidTransport {
}
}
/// Obtain an encrypted PIN token using the standard getPinToken flow.
///
/// Implements the full CTAP2 §11.5.4 PIN token acquisition:
/// 1. Fetches the authenticator's key agreement public key.
/// 2. Generates an ephemeral P-256 key pair on the platform.
/// 3. Performs ECDH and derives `SHA-256(shared_secret)`.
/// 4. Encrypts the first 16 bytes of `SHA-256(pin)` with AES-256-CBC.
/// 5. Sends getPinToken (sub-command 0x05) and decrypts the response token.
pub fn get_pin_token(&self, pin: &str) -> Result<Vec<u8>, PFError> {
log::info!("Starting custom get_pin_token (Subcommand 0x05)...");
@@ -789,6 +1003,12 @@ impl HidTransport {
}
}
/// Obtain a PIN token with specific permissions and optional RP ID scope.
///
/// Like [`get_pin_token`](HidTransport::get_pin_token) but uses the
/// `getPinUvAuthTokenUsingPinWithPermissions` sub-command (0x09). This allows
/// requesting only the permissions needed (e.g., `CREDENTIAL_MANAGEMENT` for
/// enumeration/deletion), following the principle of least privilege.
pub fn get_pin_token_with_permission(
&self,
pin: &str,
@@ -911,6 +1131,16 @@ impl HidTransport {
}
}
/// Set a new PIN on the authenticator (sub-command 0x03).
///
/// Implements the full CTAP2 setPin flow:
/// 1. Performs ECDH key agreement to derive the shared secret.
/// 2. Encrypts the new PIN (padded to 64 bytes) with AES-256-CBC.
/// 3. Computes `HMAC-SHA-256(shared_secret, newPinEnc)[0..16]` as pinUvAuthParam.
/// 4. Sends the SetPin command with the platform's public key, encrypted PIN, and HMAC.
///
/// The PIN must be 463 characters. Fails with `PIN_POLICY_VIOLATION` (0x37) if
/// the PIN is too short.
pub fn set_pin(&self, new_pin: &str) -> Result<(), PFError> {
log::info!("Starting custom set_pin (Subcommand 0x03)...");
@@ -1030,6 +1260,18 @@ impl HidTransport {
}
}
/// Change the authenticator PIN (sub-command 0x04).
///
/// Implements the full CTAP2 changePin flow:
/// 1. Performs ECDH key agreement to derive the shared secret.
/// 2. Encrypts `SHA-256(current_pin)[0..16]` with AES-256-CBC (pinHashEnc).
/// 3. Encrypts the new PIN (padded to 64 bytes) with AES-256-CBC (newPinEnc).
/// 4. Computes `HMAC-SHA-256(shared_secret, newPinEnc || pinHashEnc)[0..16]`.
/// 5. Sends the ChangePin command.
///
/// Returns `CTAP2_ERR_PIN_AUTH_INVALID` (0x31) if the current PIN is wrong,
/// `CTAP2_ERR_PIN_BLOCKED` (0x32) if the PIN is blocked, or
/// `CTAP2_ERR_PIN_POLICY_VIOLATION` (0x37) if the new PIN violates policy.
pub fn change_pin(&self, current_pin: &str, new_pin: &str) -> Result<(), PFError> {
log::info!("Starting custom change_pin (Subcommand 0x04)...");
@@ -1173,7 +1415,11 @@ impl HidTransport {
}
}
/// Helper to sign the authenticatorConfig command
/// Sign an authenticatorConfig command using HMAC-SHA-256.
///
/// Computes `HMAC-SHA-256(pin_token, 0x0d || subCommand || subCommandParams)[0..16]`
/// per the CTAP2 authenticatorConfig signing specification. The 0x0d byte
/// identifies the Config command category.
fn sign_config_command(
&self,
pin_token: &[u8],
@@ -1193,6 +1439,10 @@ impl HidTransport {
sig.as_ref()[0..16].to_vec()
}
/// Encode an uncompressed P-256 public key as a COSE_Key map.
///
/// Returns CBOR bytes for a map with keys: kty(1)=EC2(2), alg(3)=ES256(-7),
/// crv(-1)=P-256(1), x(-2), y(-3). Used in PIN key agreement payloads.
fn encode_cose_key(&self, x: &[u8], y: &[u8]) -> Vec<u8> {
let mut bytes = vec![0xA5]; // Map(5)
bytes.extend(to_vec(&Value::Integer(1)).unwrap());
@@ -1208,6 +1458,11 @@ impl HidTransport {
bytes
}
/// Build a CBOR map for ClientPin sub-command parameters.
///
/// Constructs the parameter map with `pinProtocol`, `subCommand`, `keyAgreement`,
/// and `pinHashEnc`. Optionally includes `permissions` and `rpId` when the
/// `getPinUvAuthTokenUsingPinWithPermissions` sub-command is used.
fn encode_client_pin_params(
&self,
sub_cmd: ClientPinSubCommand,
@@ -1243,6 +1498,14 @@ impl HidTransport {
bytes
}
/// Enumerate all Relying Parties stored on the authenticator.
///
/// Performs the CTAP2 credential management enumeration flow:
/// 1. Obtains a PIN token with `CREDENTIAL_MANAGEMENT` permission.
/// 2. Sends `EnumerateRpsBegin` (sub-command 0x02) to get the first RP.
/// 3. Iterates with `EnumerateRpsGetNextRp` (sub-command 0x03) until all RPs are returned.
///
/// Returns an empty vector if no credentials exist on the device.
pub fn credential_management_enumerate_rps(
&self,
pin: &str,
@@ -1382,6 +1645,14 @@ impl HidTransport {
Ok(all_rps)
}
/// Enumerate all credentials registered under a specific Relying Party.
///
/// Given an `rp_id_hash` (SHA-256 of the RP's ID), performs:
/// 1. Obtains a PIN token with `CREDENTIAL_MANAGEMENT` permission.
/// 2. Sends `EnumerateCredentialsBegin` (sub-command 0x04) with the RP ID hash.
/// 3. Iterates with `EnumerateCredentialsGetNextCredential` (sub-command 0x05).
///
/// Returns user info, credential ID, and public key for each credential.
pub fn credential_management_enumerate_credentials(
&self,
pin: &str,
@@ -1553,6 +1824,12 @@ impl HidTransport {
Ok(all_creds)
}
/// Delete a specific credential from the authenticator.
///
/// Obtains a PIN token with `CREDENTIAL_MANAGEMENT` permission, then sends
/// the `DeleteCredential` command (sub-command 0x06) with the credential ID
/// descriptor map. The `credential_id_map` must be a CBOR map with key 0x02
/// containing the credential ID.
pub fn credential_management_delete_credential(
&self,
pin: &str,
@@ -1607,6 +1884,13 @@ impl HidTransport {
Ok(())
}
/// Sign a credential management command using HMAC-SHA-256.
///
/// Uses pico-fido's non-standard signing scheme: for sub-commands 0x01
/// (GetCredsMetadata) and 0x02 (EnumerateRpsBegin), only the sub-command
/// byte is signed. For all others, the sub-command byte followed by the
/// CBOR-encoded SubCommandParams is signed. Returns the first 16 bytes
/// of the HMAC digest.
fn sign_credential_mgmt_command(
&self,
pin_token: &[u8],
+56
View File
@@ -1,3 +1,59 @@
//! FIDO2 / CTAP2 protocol implementation for pico-fido and RS-Key firmware.
//!
//! ```text
//! fido/
//! ├── mod.rs — high-level FIDO2 operations (info, PIN, credentials, config)
//! ├── constants.rs — CTAP2 command codes, CBOR map keys, COSE algorithms, bitflags
//! └── hid.rs — USB HID transport (CTAPHID framing, channel init, CBOR exchange)
//! ```
//!
//! # Architecture
//!
//! Communication flows top-down:
//!
//! ```text
//! io::read_device_details()
//! │
//! ▼
//! fido::read_device_details() ← this file
//! │
//! ▼
//! HidTransport::open() ← hid.rs
//! │
//! ▼
//! USB HID (CTAPHID protocol)
//! ```
//!
//! [`constants`] is imported by both `mod.rs` and `hid.rs` and should be the
//! single source of truth for every CTAP2-defined byte value. If you need to
//! add a new command, sub-command, or CBOR key, put it there.
//!
//! [`hid`] owns the raw byte-level exchange: channel ID negotiation, packet
//! framing (init + continuation packets), PIN token acquisition, ECDH key
//! agreement, and CBOR serialization. It exposes [`HidTransport`] which the
//! rest of the module uses for all device I/O.
//!
//! [`mod.rs`] contains the public functions called from [`super::io`].
//! Each function opens an [`HidTransport`], performs the CTAP2 operation,
//! and parses the CBOR response into the structs defined in [`super::types`].
//!
//! # Vendor extensions
//!
//! Pico-fido firmware exposes vendor-specific CTAP commands (`0xC1`, `0xC2`)
//! for hardware configuration (VID/PID, LED, memory stats). These are handled
//! through [`HidTransport::send_vendor_config`] and the
//! [`VendorConfigCommand`] enum in constants. Legacy firmware (≤7.2) uses a
//! different physical-options encoding; see `firmware_supports_legacy_fido_hardware_config`.
//!
//! # Adding a new FIDO2 operation
//!
//! 1. Add any new command/sub-command enums to [`constants`].
//! 2. Implement the CBOR encoding and transport call in [`hid`] (if it
//! requires new framing or PIN token logic).
//! 3. Add the high-level function in this file, following the pattern:
//! open transport → build CBOR payload → send → parse response → return.
//! 4. Expose it through [`super::io`].
pub mod constants;
pub mod hid;
+63 -31
View File
@@ -1,8 +1,20 @@
//! Tauri Commands to interact with the pico-fido firmware via rescue and fido protocols.
//! Device I/O layer bridging rescue (pcsc) and FIDO2 protocols.
//!
//! High-level entry points for reading/writing device configuration,
//! managing credentials, and controlling LED/boot behavior.
//!
//! Functions are grouped by the protocol they use:
//! - Functions that use both rescue and FIDO (fallback/dispatch logic)
//! - Functions that communicate exclusively over the rescue (PC/SC) channel
//! - Functions that communicate exclusively over the FIDO2 channel
#![allow(unused)]
use crate::{device::fido, device::rescue, device::types::*, error::PFError};
// ── Shared: functions that use both rescue and FIDO ─────────────────────────
/// Read full device status. Tries rescue first, falls back to FIDO on failure.
pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
match rescue::read_device_details() {
Ok(status) => Ok(status),
@@ -13,6 +25,7 @@ pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
}
}
/// Write app config. Dispatches to rescue or FIDO based on `method`.
pub fn write_config(
config: AppConfigInput,
method: DeviceMethod,
@@ -25,48 +38,24 @@ pub fn write_config(
}
}
// ── Rescue protocol (PC/SC) ─────────────────────────────────────────────────
/// Lock or unlock secure boot via rescue.
pub fn enable_secure_boot(lock: bool) -> Result<String, PFError> {
rescue::enable_secure_boot(lock)
}
pub(crate) fn get_fido_info() -> Result<FidoDeviceInfo, String> {
fido::get_fido_info()
}
pub(crate) fn change_fido_pin(
current_pin: Option<String>,
new_pin: String,
) -> Result<String, String> {
fido::change_fido_pin(current_pin, new_pin)
}
pub(crate) fn set_min_pin_length(
current_pin: String,
min_pin_length: u8,
) -> Result<String, String> {
fido::set_min_pin_length(current_pin, min_pin_length)
}
/// Reboot the device. Pass `true` to enter BOOTSEL mode.
pub fn reboot(to_bootsel: bool) -> Result<String, PFError> {
rescue::reboot_device(to_bootsel)
}
pub fn get_credentials(pin: String) -> Result<Vec<StoredCredential>, String> {
fido::get_credentials(pin)
}
pub fn delete_credential(pin: String, credential_id: String) -> Result<String, String> {
fido::delete_credential(pin, credential_id)
}
pub fn reset_device() -> Result<String, String> {
fido::reset_device()
}
/// Read current LED status config via rescue.
pub fn read_led_config() -> Result<LedStatusConfig, PFError> {
rescue::read_led_config()
}
/// Write LED status (on/off, color, brightness, steady/blinking).
pub fn write_led_status(
status: u8,
color: u8,
@@ -76,22 +65,65 @@ pub fn write_led_status(
rescue::write_led_status(status, color, brightness, steady)
}
/// Read management app config via rescue.
pub fn read_management_config() -> Result<ManagementAppConfig, PFError> {
rescue::read_management_config()
}
/// Write management app enabled-mask via rescue.
pub fn write_management_config(enabled_mask: u16) -> Result<String, PFError> {
rescue::write_management_config(enabled_mask)
}
// ── FIDO2 protocol ──────────────────────────────────────────────────────────
/// Query basic FIDO device info (AAGUID, version, etc.).
pub(crate) fn get_fido_info() -> Result<FidoDeviceInfo, String> {
fido::get_fido_info()
}
/// Change the FIDO user PIN.
pub(crate) fn change_fido_pin(
current_pin: Option<String>,
new_pin: String,
) -> Result<String, String> {
fido::change_fido_pin(current_pin, new_pin)
}
/// Set the minimum PIN length requirement.
pub(crate) fn set_min_pin_length(
current_pin: String,
min_pin_length: u8,
) -> Result<String, String> {
fido::set_min_pin_length(current_pin, min_pin_length)
}
/// List stored credentials for the given PIN.
pub fn get_credentials(pin: String) -> Result<Vec<StoredCredential>, String> {
fido::get_credentials(pin)
}
/// Delete a single credential by its ID.
pub fn delete_credential(pin: String, credential_id: String) -> Result<String, String> {
fido::delete_credential(pin, credential_id)
}
/// Factory-reset the device, wiping all credentials and settings.
pub fn reset_device() -> Result<String, String> {
fido::reset_device()
}
/// Enable enterprise attestation for the device.
pub fn enable_enterprise_attestation(pin: String) -> Result<String, String> {
fido::enable_enterprise_attestation(pin)
}
/// Retrieve the enterprise attestation CSR.
pub fn get_enterprise_attestation_csr() -> Result<String, String> {
fido::get_enterprise_attestation_csr()
}
/// Upload a signed enterprise attestation certificate.
pub fn upload_enterprise_attestation_cert(
pin: String,
cert_path: String,
+41
View File
@@ -1,3 +1,44 @@
//! Device communication layer for pico-forge.
//!
//! ```text
//! device/
//! ├── mod.rs — module root, re-exports submodules
//! ├── io.rs — high-level entry points (both protocols)
//! ├── rescue.rs — rescue / PC/SC protocol implementation
//! ├── fido.rs — FIDO2 / CTAP2 protocol implementation
//! └── types.rs — shared structs, enums, and constants
//! ```
//!
//! # Overview
//!
//! The `device` module is the only place that talks to the hardware token.
//! Everything above it (UI state, gpui-component views) depends on the
//! public functions exported here; nothing below it should know about the
//! communication details.
//!
//! Two protocols are used:
//!
//! - **Rescue (PC/SC)** — low-level APDU channel for firmware-level
//! configuration: secure boot, LED status, USB applet management, and
//! device reboot. Implemented in [`rescue`].
//!
//! - **FIDO2 (CTAP2)** — standard authenticator protocol for credential
//! management, PIN operations, and enterprise attestation.
//! Implemented in [`fido`].
//!
//! [`io`] sits on top of both and exposes a single function per device
//! operation. Some functions dispatch to one protocol or the other based
//! on a [`types::DeviceMethod`] flag; others try rescue first and fall back
//! to FIDO on failure.
//!
//! # Adding a new device operation
//!
//! 1. Add any new structs/enums to [`types`].
//! 2. Implement the raw protocol call in [`rescue`] or [`fido`].
//! 3. Expose a high-level wrapper in [`io`] that picks the right protocol
//! and converts errors to the caller's expected type.
//! 4. Wire the wrapper into a gpui-component view or action handler.
pub mod fido;
pub mod io;
pub mod rescue;
File diff suppressed because it is too large Load Diff
+260 -4
View File
@@ -1,6 +1,161 @@
//! Implements communication with the pico-fido firmware via the `Rescue API`.
//! Rescue applet implementation for pico-fido and RS-Key firmware.
//!
//! For more details checkout the [pico-key-sdk](https://github.com/polhenarejos/pico-keys-sdk/blob/main/src/rescue.c)
//! ```text
//! rescue/
//! ├── mod.rs — high-level rescue operations (read/write config, reboot, LED, management)
//! └── constants.rs — ISO 7816-4 constants, rescue instructions, PHY tags, vendor applets
//! ```
//!
//! # What is the Rescue Applet?
//!
//! The Rescue applet is a low-level firmware recovery and hardware configuration
//! interface that operates independently of the FIDO2/CTAP2 stack. It provides
//! direct access to device hardware settings, flash memory, and security features
//! through a proprietary APDU-based protocol.
//!
//! Both [pico-fido](https://github.com/polhenarejos/pico-fido) (C) and
//! [RS-Key](https://github.com/TheMaxMur/RS-Key) (Rust) firmware implement
//! this applet with the same AID and command set.
//!
//! # Why is Rescue Mode Needed?
//!
//! FIDO2 devices expose a standardized interface (CTAP2) that abstracts away
//! hardware details. However, there are scenarios where direct hardware access
//! is required:
//!
//! - **Firmware recovery**: When FIDO mode is unresponsive or corrupted
//! - **Hardware configuration**: Changing USB VID/PID, LED settings, touch timeout
//! without requiring FIDO PIN authentication
//! - **Secure boot management**: Enabling/disabling secure boot, reading OTP status
//! - **Device provisioning**: Uploading attestation certificates, setting serial numbers
//! - **Firmware updates**: Rebooting into bootloader (BOOTSEL) mode for flashing
//!
//! The Rescue applet runs on the CCID (smart card) USB interface, which is always
//! available even when FIDO functionality is disabled or misconfigured.
//!
//! # Communication Protocol: PC/SC
//!
//! Unlike FIDO2 which uses USB HID (CTAPHID), the Rescue applet communicates via
//! **PC/SC** (Personal Computer/Smart Card) — the standard protocol for interacting
//! with smart card readers and ICCs (Integrated Circuit Cards).
//!
//! ```text
//! Host Application
//! │
//! ▼
//! pcsc-lite daemon (pcscd) ← Linux/macOS daemon
//! │
//! ▼
//! USB CCID Class Driver ← Smart card reader driver
//! │
//! ▼
//! Device CCID Interface ← Composite USB device
//! │
//! ▼
//! Rescue Applet (APDU commands) ← Firmware
//! ```
//!
//! ## PC/SC Architecture
//!
//! The PC/SC specification defines a standard API for communicating with smart
//! cards. In our case, the RP2040/RP2350 device emulates a CCID-compliant smart
//! card reader with an embedded ICC.
//!
//! Key concepts:
//! - **Context**: A connection to the PC/SC daemon (establishes resource manager)
//! - **Reader**: A physical or virtual smart card reader (our device appears as one)
//! - **Card**: A connection to a specific card in a reader
//! - **APDU**: Application Protocol Data Unit — the command/response format
//!
//! ## APDU Command Structure
//!
//! ```text
//! ┌─────┬─────┬─────┬─────┬─────┬─────────────┐
//! │ CLA │ INS │ P1 │ P2 │ Lc │ Data │
//! └─────┴─────┴─────┴─────┴─────┴─────────────┘
//! 1B 1B 1B 1B 0-1B 0-255 bytes
//! ```
//!
//! - **CLA** (0x80 for Rescue): Command class — proprietary extension
//! - **INS**: Instruction code (e.g., 0x1E for READ, 0x1C for WRITE)
//! - **P1/P2**: Parameters (sub-command selectors)
//! - **Lc**: Length of data field
//! - **Data**: Command payload
//!
//! Response ends with Status Words (SW1 SW2):
//! - `0x90 0x00`: Success
//! - `0x6A 0x82`: File/application not found
//! - `0x69 0x82`: Security status not satisfied
//!
//! # Data Flow
//!
//! ```text
//! io::read_device_details()
//! │
//! ▼
//! rescue::read_device_details() ← this file
//! │
//! ▼
//! connect_and_select() ← PC/SC connection + applet selection
//! │
//! ▼
//! card.transmit(apdu) ← ISO 7816-4 APDU exchange
//! │
//! ▼
//! PC/SC (CCID USB interface)
//! ```
//!
//! ## Applet Selection
//!
//! Every session begins with applet selection:
//!
//! ```text
//! APDU: 00 A4 04 04 08 A0 58 3F C1 9B 7E 4F 21
//! ── ── ── ── ── ─────────────────────────
//! CLA INS P1 P2 Len AID (Rescue Applet)
//! ```
//!
//! The SELECT response contains device identity:
//! - Byte 0: MCU type (1=RP2350, 2=ESP32-S3, etc.)
//! - Byte 1: Product type (2=FIDO)
//! - Byte 2: SDK version major
//! - Byte 3: SDK version minor
//! - Bytes 4-11: Serial number (8 bytes)
//!
//! # Module Structure
//!
//! [`constants`] defines all protocol constants shared between pico-fido and RS-Key:
//! - ISO 7816-4 command bytes (CLA, INS, P1, P2, SW)
//! - Rescue instruction codes and parameters
//! - PHY configuration tags and bitflags
//! - Vendor applet AIDs and instructions (LED, Management)
//!
//! [`mod.rs`] contains the public functions called from [`super::io`]:
//! - `read_device_details()`: Reads full device status via Rescue
//! - `write_config()`: Writes PHY configuration (VID/PID, LED, curves, etc.)
//! - `reboot_device()`: Reboots device (normal or BOOTSEL mode)
//! - `enable_secure_boot()`: Enables secure boot (WIP)
//! - `read_led_config()` / `write_led_status()`: LED color configuration (RS-Key)
//! - `read_management_config()` / `write_management_config()`: USB interface config (RS-Key)
//!
//! # Firmware Differences
//!
//! | Feature | pico-fido | RS-Key |
//! |---------|-----------|--------|
//! | Language | C | Rust |
//! | Rescue AID | `A0 58 3F C1 9B 7E 4F 21` | Same |
//! | Secure Boot | `INS_SECURE` (0x1D) | `INS_OTP_LOCK` (0x1B) — irreversible |
//! | LED Applet | Not available | Available (AID: `F0 00 00 00 01`) |
//! | Management | Not available | Available (Yubico-compatible) |
//! | Anti-rollback | Not available | Available (OTP fuses) |
//!
//! # References
//!
//! - [pico-fido Rescue](https://github.com/polhenarejos/pico-fido/blob/main/src/rescue.c)
//! - [RS-Key Rescue](https://github.com/TheMaxMur/RS-Key/blob/main/crates/rsk-rescue/src/lib.rs)
//! - [PC/SC Specification](https://pcsc1groupwg.readthedocs.io/)
//! - [ISO 7816-4](https://www.iso.org/standard/74873.html)
//! - [CCID Specification](https://www.usb.org/document-library/class-specification-12-chip-smart-card-interface)
pub mod constants;
@@ -10,7 +165,21 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use pcsc::{Context, Protocols, Scope, ShareMode};
use std::io::Cursor;
/// Connects to the first available reader and selects the Rescue Applet
/// Establishes a PC/SC connection to the first available smart card reader and selects the Rescue Applet.
///
/// Sends a SELECT APDU (`00 A4 04 04 08 A0 58 3F C1 9B 7E 4F 21`) to the device via the CCID interface.
/// The response contains device identity data (MCU type, product type, firmware version, serial number).
///
/// # Returns
/// A tuple of `(Card, SelectResponse, FirmwareType)` where:
/// - `Card` is the active PC/SC card handle for subsequent APDU exchanges
/// - `SelectResponse` is the raw FCI/identity data from the SELECT command
/// - `FirmwareType` is detected as `RSKey`, `PicoFido`, or `Unknown`
///
/// # Errors
/// - `PFError::NoDevice` if no smart card reader is found
/// - `PFError::Pcsc` if the PC/SC context cannot be established
/// - `PFError::Device` if the Rescue Applet is not found (wrong AID or device in wrong mode)
fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>, FirmwareType), PFError> {
let ctx = Context::establish(Scope::User).map_err(|e| {
log::error!("Failed to establish PCSC context: {}", e);
@@ -73,6 +242,20 @@ fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>, FirmwareType), PFError>
Ok((card, data, fw_type))
}
/// Reads comprehensive device details including identity, flash usage, secure boot status, and PHY configuration.
///
/// Performs three sequential APDU operations after applet selection:
/// 1. SELECT response is parsed for MCU type, firmware version, and serial number
/// 2. `READ(FlashInfo)` — reads flash usage statistics (free, used, total)
/// 3. `READ(SecureBootStatus)` — reads secure boot enable/lock state
/// 4. `READ(PhyConfig)` — reads TLV-encoded hardware configuration (VID/PID, LED, curves, etc.)
///
/// # Returns
/// A `FullDeviceStatus` struct containing device info, parsed PHY config, and secure boot state.
///
/// # Errors
/// - `PFError::Device` if the SELECT response is malformed or any READ command fails
/// - `PFError::NoDevice` if no reader is available
pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
log::info!("Reading full device details");
let (card, select_resp, fw_type) = connect_and_select()?;
@@ -269,6 +452,28 @@ pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
})
}
/// Writes PHY configuration to the device via the Rescue Applet's WRITE command.
///
/// Constructs a TLV (Tag-Length-Value) blob from the provided `AppConfigInput` fields and sends
/// it as a single APDU: `80 1C 01 00 [Lc] [TLV Data]`. Supported tags include:
/// - `0x00`: VID:PID (4 bytes, big-endian)
/// - `0x04`: LED GPIO pin
/// - `0x05`: LED brightness
/// - `0x08`: Touch/presence timeout
/// - `0x06`: Options bitmask (LED_DIMMABLE, DISABLE_POWER_RESET, LED_STEADY)
/// - `0x07`: Elliptic curves bitmask (SECP256K1, etc.)
/// - `0x0C`: LED driver selection
/// - `0x09`: USB product name (null-terminated)
/// - `0x0D`: LED order (RS-Key extension)
/// - `0x0B`: Enabled USB interfaces (CCID bit is always forced on for safety)
///
/// # Returns
/// A success message string on `SW 9000`.
///
/// # Errors
/// - `PFError::Io` if VID/PID are not valid hex strings
/// - `PFError::Device` if the WRITE APDU fails or returns a non-success status
/// - `PFError::Io` if the product name exceeds 32 bytes
pub fn write_config(config: AppConfigInput) -> Result<String, PFError> {
log::info!("Writing configuration to device");
log::debug!("Config input: {:?}", config);
@@ -415,6 +620,21 @@ pub fn write_config(config: AppConfigInput) -> Result<String, PFError> {
}
}
/// Reboots the device, optionally entering BOOTSEL (mass storage) mode for firmware updates.
///
/// Sends a REBOOT APDU: `80 1B [P1] 00 00` where:
/// - `P1 = 0x00` (`RebootParam::Normal`): Reboots into normal FIDO mode
/// - `P1 = 0x01` (`RebootParam::Bootsel`): Reboots into BOOTSEL/UF2 bootloader mode
///
/// # Arguments
/// * `to_bootsel` - If `true`, device enters UF2 bootloader mode for firmware flashing.
/// If `false`, device performs a normal reboot into FIDO mode.
///
/// # Returns
/// A confirmation string if the reboot command was accepted.
///
/// # Errors
/// - `PFError::Device` if the APDU fails or returns a non-success status
pub fn reboot_device(to_bootsel: bool) -> Result<String, PFError> {
let (card, _, _) = connect_and_select()?;
@@ -442,7 +662,27 @@ pub fn reboot_device(to_bootsel: bool) -> Result<String, PFError> {
}
}
/// UNSTABLE! (WIP)
/// Enables or disables secure boot on the device. **UNSTABLE — work in progress.**
///
/// Sends a SECURE APDU: `80 1D 00 [LockBool] 00` where:
/// - `LockBool = 0x01`: Enable and lock secure boot (irreversible on some firmware)
/// - `LockBool = 0x00`: Disable secure boot
///
/// Uses pico-fido instruction `INS_SECURE` (0x1D). RS-Key uses `INS_OTP_LOCK` (0x1B)
/// for OTP fuse locking, which is a different operation.
///
/// # Arguments
/// * `lock` - If `true`, enables secure boot with lock (may be irreversible).
///
/// # Returns
/// A confirmation string if the secure boot command was accepted.
///
/// # Errors
/// - `PFError::Device` if the APDU fails or returns a non-success status
///
/// # Warning
/// This function is unstable and may change. Locking secure boot can permanently
/// prevent firmware downgrades. Use with caution.
pub fn enable_secure_boot(lock: bool) -> Result<String, PFError> {
let (card, _, _) = connect_and_select()?;
@@ -470,6 +710,22 @@ pub fn enable_secure_boot(lock: bool) -> Result<String, PFError> {
// --- Vendor/LED Applet (RS-Key) ---
/// Establishes a PC/SC connection and selects a specific vendor applet by AID.
///
/// Unlike [`connect_and_select`] which selects the Rescue Applet, this function
/// selects an arbitrary applet (e.g., LED applet `F0 00 00 00 01` or Management applet).
/// Sends a SELECT APDU: `00 A4 04 00 [Len] [AID] 00`.
///
/// # Arguments
/// * `aid` - The Application Identifier of the target applet (e.g., `VENDOR_LED_AID`, `MANAGEMENT_AID`)
///
/// # Returns
/// An active `pcsc::Card` handle ready for APDU exchange with the selected applet.
///
/// # Errors
/// - `PFError::NoDevice` if no smart card reader is found
/// - `PFError::Pcsc` if the PC/SC context cannot be established
/// - `PFError::Device` if the applet is not found (AID not recognized by firmware)
fn connect_and_select_aid(aid: &[u8]) -> Result<pcsc::Card, PFError> {
let ctx = Context::establish(Scope::User).map_err(|e| {
log::error!("Failed to establish PCSC context: {}", e);
+34 -13
View File
@@ -1,12 +1,23 @@
//! Shared types for device communication.
//!
//! Organized into three groups:
//! - Application-level types: device info, config, and status used across both protocols
//! - Rescue (PC/SC) types: LED and USB applet configuration read/written over PC/SC
//! - FIDO2 types: credential and authenticator info from CTAP2
#![allow(unused)]
use serde::{Deserialize, Serialize};
use std::fmt;
// ── Application-level types ─────────────────────────────────────────────────
/// Internal application state holding device info for the current session.
struct PForgeState {
device_info: DeviceInfo,
}
/// Basic device identity and flash usage reported by the firmware.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DeviceInfo {
@@ -16,6 +27,7 @@ pub struct DeviceInfo {
pub firmware_version: String,
}
/// Full device configuration (USB descriptors, LED, touch, crypto options).
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AppConfig {
@@ -39,6 +51,7 @@ pub struct AppConfig {
pub enabled_usb_itf: Option<u8>,
}
/// Partial config update; `None` fields are left unchanged on the device.
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AppConfigInput {
@@ -58,6 +71,7 @@ pub struct AppConfigInput {
pub enabled_usb_itf: Option<u8>,
}
/// Aggregated snapshot of device info, config, and security state.
#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FullDeviceStatus {
@@ -69,6 +83,7 @@ pub struct FullDeviceStatus {
pub firmware_type: FirmwareType,
}
/// Protocol channel used to communicate with the device.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum DeviceMethod {
#[serde(rename = "FIDO")]
@@ -76,8 +91,8 @@ pub enum DeviceMethod {
Rescue,
}
/// Represents the recognized firmware variants running on the connected hardware token.
/// Used extensively to gate UI features, connection methods, and compatibility checks.
/// Recognized firmware variants. Gates UI features, connection methods, and
/// compatibility checks throughout the application.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub enum FirmwareType {
PicoFido,
@@ -96,31 +111,29 @@ impl fmt::Display for FirmwareType {
}
}
/// The globally unique Authenticator Attestation GUID (AAGUID) assigned to RS-Key hardware.
pub const RSKEY_AAGUID: &str = "2479C7BF6B3056839EC80E8171A918B7";
/// The globally unique Authenticator Attestation GUID (AAGUID) assigned to Pico-Fido hardware.
pub const PICOFIDO_AAGUID: &str = "89FB94B706C936739B7E30526D968145";
// ── Rescue (PC/SC) types ────────────────────────────────────────────────────
/// Aggregates the LED status configurations read from the RS-Key Vendor/LED applet.
/// Contains the global steady flag and a fixed array of `(color_code, brightness)` pairs
/// mapped chronologically to device statuses: [Idle, Processing, Touch, Boot].
/// LED status configuration read from the Vendor/LED applet.
/// `statuses` is a fixed array of `(color, brightness)` pairs indexed by
/// device status: Idle, Processing, Touch, Boot.
#[derive(Serialize, Debug, Default, Clone, PartialEq)]
pub struct LedStatusConfig {
pub steady: bool,
pub statuses: [(u8, u8); 4],
}
/// Encapsulates the bitmasks defining USB application endpoints on the device.
/// The `usb_supported` mask indicates which applets the firmware is capable of running,
/// while `usb_enabled` reflects the active endpoints the device will enumerate on next boot.
/// USB application endpoint bitmasks from the Management applet.
/// `usb_supported` lists applets the firmware can run;
/// `usb_enabled` lists those active on next boot.
#[derive(Serialize, Debug, Default, Clone, PartialEq)]
pub struct ManagementAppConfig {
pub usb_supported: u16,
pub usb_enabled: u16,
}
// Fido stuff:
// ── FIDO2 types ─────────────────────────────────────────────────────────────
/// Authenticator metadata from CTAP2 GetInfo.
#[derive(Serialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct FidoDeviceInfo {
@@ -145,6 +158,7 @@ pub struct FidoDeviceInfo {
pub max_cred_blob_length: Option<i128>,
}
/// A single FIDO2 credential stored on the device.
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StoredCredential {
@@ -155,3 +169,10 @@ pub struct StoredCredential {
pub user_id: String,
pub credential_id: String,
}
// ── Constants ───────────────────────────────────────────────────────────────
/// AAGUID assigned to RS-Key hardware.
pub const RSKEY_AAGUID: &str = "2479C7BF6B3056839EC80E8171A918B7";
/// AAGUID assigned to Pico-Fido hardware.
pub const PICOFIDO_AAGUID: &str = "89FB94B706C936739B7E30526D968145";