Skip to main content

Architecture Overview

FIDO Bridge is a multi-component system designed to bridge WebAuthn/FIDO2 authentication from Linux browsers to NFC-capable Android devices. This document provides a comprehensive overview of the system architecture.

System Architecture

Component Breakdown

1. Linux Rust Client (/crates/client)

The Linux client is the core component running on the desktop machine.

Key Responsibilities:

  • Creates and manages a virtual UHID (USB HID) FIDO device at /dev/hidrawX
  • Implements the CTAP2 (Client to Authenticator Protocol 2) specification
  • Handles WebAuthn requests from browsers (GetInfo, MakeCredential, GetAssertion)
  • Manages end-to-end encryption with paired Android devices
  • Maintains transaction state and timeouts
  • Stores paired device credentials in ~/.config/fido-bridge/

Key Files:

  • src/uhid_fido.rs: UHID device management and CTAP2 protocol handler
  • src/webauthn_handler.rs: WebAuthn message parsing and response construction
  • src/message_handler.rs: Message routing and transaction management
  • src/storage.rs: Persistent storage for paired devices
  • src/uhid.rs: Low-level UHID kernel interface

Technical Details:

  • Uses Linux UHID (User-space HID) subsystem for creating virtual FIDO devices
  • Spawns async tasks for message polling (250ms interval during active transactions)
  • Implements timeout handling (240s for CTAP operations, 30s for ClientPIN)
  • Supports multiple concurrent transactions with unique transaction IDs

2. Relay Server (/crates/server)

A lightweight, stateless relay server that forwards encrypted messages between clients and Android devices.

Key Responsibilities:

  • Accepts and queues encrypted messages from Linux clients
  • Provides polling endpoint for Android devices
  • Maintains temporary message storage with 5-minute TTL
  • Handles SPAKE2 pairing protocol messages
  • Implements CORS for web-based administration (optional)

Key Files:

  • src/main.rs: Axum web server setup and routing
  • src/blob.rs: Message blob storage with expiration
  • src/handlers.rs: HTTP endpoint handlers

Design Principles:

  • Server-blind: Never decrypts messages, only forwards encrypted blobs
  • Stateless: No persistent storage of credentials or keys
  • Minimal: Simple queue-based architecture with TTL cleanup

Endpoints:

POST   /api/pairing/initiate       - Start pairing session
GET /api/pairing/poll/:id - Poll for pairing responses
POST /api/messages/send - Send encrypted message
GET /api/messages/poll/:token - Poll for messages by device token

3. Transport Library (/crates/transport)

Shared library containing cryptographic primitives, protocol definitions, and message types.

Key Responsibilities:

  • SPAKE2 pairing protocol implementation
  • X25519 ECDH ephemeral key exchange
  • AES-256-GCM encryption/decryption
  • Message serialization/deserialization
  • Transaction state management

Key Files:

  • src/pairing/protocol.rs: SPAKE2 pairing flow and device info
  • src/pairing/crypto.rs: Cryptographic operations (SPAKE2, Ed25519)
  • src/secure_channel.rs: Ephemeral key exchange and AES-GCM encryption
  • src/webauthn_messages.rs: CTAP2 message types (GetInfo, MakeCredential, etc.)
  • src/transaction.rs: Transaction lifecycle and state machine
  • src/client.rs: HTTP client for server communication

Cryptographic Stack:

  • Pairing: SPAKE2 with Ed25519 curve
  • Session Encryption: X25519 ECDH + AES-256-GCM
  • Key Derivation: SHA-256 for deriving encryption keys
  • Random: OsRng for cryptographically secure randomness

4. Flutter Android App (/app)

The Android application provides the NFC interface and user interaction.

Key Responsibilities:

  • NFC communication with YubiKey via APDU commands
  • Message polling from relay server (adaptive intervals)
  • Pairing UI with QR code scanning and PIN entry
  • Transaction approval/rejection UI
  • Background service for persistent message monitoring
  • Session caching to reduce NFC taps

Key Files:

  • lib/main.dart: Application entry point and notification setup
  • lib/src/screens/home_screen.dart: Main UI with transaction list
  • lib/src/services/background_service.dart: Background polling service
  • lib/src/services/nfc_service.dart: NFC APDU communication
  • lib/src/providers/pairing_provider.dart: Pairing state management
  • lib/rust/api/simple.dart: Flutter Rust Bridge bindings

Kotlin NFC Layer:

  • android/app/src/main/kotlin/com/example/fido_bridge/NfcHandler.kt: Low-level NFC tag handling
  • Implements ISO 14443A protocol for YubiKey communication
  • Handles APDU command/response parsing

Polling Strategy:

  • Active polling: 250ms interval when waiting for responses
  • Background polling: Reduced frequency when app is backgrounded
  • Exponential backoff on errors

5. Data Flow

Pairing Flow

WebAuthn Authentication Flow

Storage and State Management

Linux Client Storage

Location: ~/.config/fido-bridge/

Stored Data:

  • Paired device credentials (device ID, device token, shared secret)
  • Device metadata (name, type, OS version)
  • Pairing timestamps

Format: JSON serialization via serde_json

Android App Storage

Uses Flutter Riverpod for state management with persistent storage.

Stored Data:

  • Paired device list
  • Device metadata
  • Shared secrets (encrypted at rest)
  • Last sync timestamps

Session Cache:

  • ClientPIN session keys (30-second TTL)
  • Recent transactions for quick retry

Configuration

Linux Client Configuration

File: ~/.config/fido-bridge/config.toml

[server]
url = "https://relay.fido-bridge.example.com"
mode = "remote" # or "embedded"

[logging]
level = "info" # debug, info, warn, error

Environment Variables

  • FIDO_BRIDGE_SERVER_URL: Override server URL
  • FIDO_BRIDGE_LOG_LEVEL: Set logging verbosity
  • FIDO_BRIDGE_STORAGE_PATH: Custom storage directory

Performance Characteristics

Latency

  • Local encryption/decryption: Less than 5ms
  • Network round-trip (relay server): 50-200ms (depends on network)
  • NFC communication: 100-500ms (depends on operation)
  • Total authentication time: 1-3 seconds

Polling Overhead

  • Linux client: 250ms poll interval during active transactions (minimal CPU usage)
  • Android app: 250ms active, reduced in background (battery-optimized)

Transaction Timeouts

  • CTAP operations: 240 seconds (4 minutes)
  • ClientPIN operations: 30 seconds
  • Pairing session: 300 seconds (5 minutes)
  • Server message TTL: 300 seconds (5 minutes)

Error Handling and Recovery

Transaction Expiration

Transactions automatically expire after timeout, with cleanup:

  • Client removes transaction from active set
  • Server purges expired messages
  • Android app displays timeout error to user

Network Failures

  • Exponential backoff on polling errors
  • Automatic retry with jitter
  • User notification of connection issues

NFC Communication Errors

  • Automatic retry for transient errors
  • User guidance for "tag lost" errors
  • Session cache minimizes repeated taps

Pairing Failures

  • Clear error messages (QR code parse errors, PIN mismatch, timeout)
  • Automatic cleanup of incomplete pairing sessions
  • Ability to retry pairing without restarting components

Daemon Component

The Linux client runs as a systemd user daemon, managing multiple background services and coordinating all client-side operations.

Source: /path/to/fido-bridge/crates/client/src/daemon.rs

Daemon Startup Sequence

When the daemon starts (lines 76-280), it performs these steps in order:

  1. Log Startup Information (lines 77-80):

    • Logs device ID
    • Logs server URL
    • Logs config file location
  2. Setup Signal Handlers (lines 82-85):

    • Registers handlers for SIGTERM, SIGINT, SIGHUP
    • Spawns async task to listen for signals
    • See "Signal Handling" section below
  3. Start Embedded Server (optional, lines 88-97):

    • Only if config.server_mode is Embedded
    • Starts HTTP server on configured port (default: 3000)
    • Server runs in background as async task
  4. Start D-Bus Service (lines 99-101):

    • Registers org.fidobridge.Client on session bus
    • Object path: /org/fidobridge/Client
    • Provides all pairing and management methods
    • See D-Bus API Documentation for details
  5. Start UHID FIDO Device (lines 104-245):

    • Creates virtual FIDO device at /dev/hidrawX
    • Spawns event loop for UHID device handling
    • Spawns WebAuthn message sender task
    • Routes requests to paired devices via relay server
    • Handles device-specific routing to prevent broadcast issues (lines 140-171)
  6. Start Background Message Polling (lines 247-261):

    • Polls relay server every 250ms for incoming responses
    • Processes WebAuthn responses from Android devices
    • Routes responses to appropriate UHID channel
  7. Wait for Signals (line 268):

    • Main thread blocks waiting for termination signal
    • All other work happens in background async tasks

Background Tasks

The daemon spawns several long-running async tasks:

1. Signal Handler Task

Source: Lines 16-30, spawned at line 85

What it does:

  • Listens for POSIX signals (SIGTERM, SIGINT, SIGHUP)
  • On SIGTERM/SIGINT: Logs shutdown message and exits cleanly
  • On SIGHUP: Logs reload message (config reload TODO)

Signals handled:

Signals::new([SIGTERM, SIGINT, SIGHUP])

2. Embedded Server Task

Source: Lines 88-97

When started: Only if config.server_mode is ServerMode::Embedded { port }

What it does:

  • Runs the relay server locally
  • Listens on configured port
  • Same functionality as standalone server

3. D-Bus Service Task

Source: Lines 99-101, registered via start_dbus_service()

What it does:

  • Handles D-Bus method calls
  • Emits signals for pairing events
  • Manages pairing session cleanup (60-second interval)
  • See /path/to/fido-bridge/crates/client/src/dbus_interface.rs

Session Cleanup: Lines 520-563 in dbus_interface.rs

  • Runs every 60 seconds
  • Removes expired pairing sessions
  • Emits PairingFailed signals for timeouts

4. UHID Device Event Loop Task

Source: Lines 113-117

What it does:

  • Reads CTAP2 commands from UHID device
  • Processes WebAuthn requests (GetInfo, MakeCredential, GetAssertion)
  • Creates transactions with unique IDs
  • Sends encrypted requests to relay server
  • Maintains pending transaction state

Transaction management:

  • Each request gets unique UUID
  • Timeout: 240s for CTAP operations, 30s for ClientPIN
  • Transactions stored in shared state for response routing

5. WebAuthn Message Sender Task

Source: Lines 123-235

What it does:

  • Receives WebAuthn messages from UHID device
  • Determines routing: broadcast vs. device-specific (lines 140-171)
  • Loads paired device credentials from storage
  • Encrypts messages with device-specific shared secrets
  • Sends to relay server

Device-Specific Routing (lines 140-171):

  • After initial GetInfo response, subsequent messages route only to responding device
  • Prevents duplicate responses from multiple devices
  • Maps UHID channels to device IDs

Broadcast vs. Targeted:

  • Broadcast: Initial GetInfo request (discovery phase)
  • Targeted: All subsequent requests in transaction

6. Message Polling Task

Source: Lines 247-261

What it does:

  • Polls relay server every 250ms
  • Calls poll_messages_background() function (lines 32-74)
  • Retrieves encrypted responses from Android devices
  • Decrypts responses using shared secrets
  • Routes responses to UHID device via transaction ID mapping

Polling function (lines 32-74):

async fn poll_messages_background(
config: &ClientConfig,
pending_transactions: Option<Arc<Mutex<HashMap<...>>>>,
channel_device_map: Option<Arc<Mutex<HashMap<...>>>>,
) -> Result<()>

What it does:

  1. Creates client with device ID
  2. Loads all paired device shared secrets
  3. Calls client.get_messages().await
  4. Passes messages to MessageHandler::process_messages()
  5. Routes responses to correct UHID channel

Component Interactions

┌─────────────────────────────────────────────────────────┐
│ Main Thread │
│ - Waits for termination signal │
└────────────────────────┬────────────────────────────────┘

┌───────────────┼───────────────┐
│ │ │
┌────────▼────────┐ ┌───▼──────┐ ┌─────▼──────────┐
│ Signal Handler │ │ D-Bus │ │ UHID Device │
│ Task │ │ Service │ │ Event Loop │
│ - SIGTERM │ │ Task │ │ - Read CTAP2 │
│ - SIGINT │ │ - RPC │ │ - Create Txn │
│ - SIGHUP │ │ - Pair │ │ - Send Req │
└─────────────────┘ └──────────┘ └────┬───────────┘

┌─────────────────┴──────────┐
│ │
┌──────────▼────────┐ ┌───────────▼──────┐
│ WebAuthn Sender │ │ Message Poller │
│ Task │ │ Task │
│ - Encrypt │ │ - Poll Server │
│ - Route to Dev │ │ - Decrypt │
│ - Send to Server │ │ - Route to UHID │
└───────────────────┘ └──────────────────┘

Signal Handling

Source: Lines 16-30

Graceful Shutdown Sequence (triggered by SIGTERM/SIGINT):

  1. Signal received (lines 19-22):

    SIGTERM | SIGINT => {
    info!("Received termination signal, shutting down gracefully...");
    std::process::exit(0);
    }
  2. Cleanup on shutdown (lines 270-278):

    • Signal handle closed
    • Embedded server stopped (if running)
    • Shutdown logged

Note: Task cleanup is automatic via Rust's Drop trait and tokio's runtime shutdown.

Resource Management

Source: Service template at /path/to/fido-bridge/install/fido-bridge.service.template

Resource Limits:

  • LimitNOFILE=65536: File descriptor limit (for many simultaneous connections)
  • PrivateTmp=true: Isolated /tmp directory
  • ProtectSystem=strict: Read-only system directories
  • ProtectHome=read-only: Read-only home except config directory
  • ReadWritePaths=%h/.config/fido-bridge: Only config dir is writable

Restart Policy:

  • Restart=on-failure: Auto-restart on crashes
  • RestartSec=5s: Wait 5 seconds before restart

Security Hardening:

Configuration Reload

Current Status: Partially implemented

SIGHUP Handler (lines 23-26):

SIGHUP => {
info!("Received SIGHUP, reloading configuration...");
// TODO: Implement config reload
}

Planned Behavior:

  • Reload ~/.config/fido-bridge/config.toml
  • Update log level
  • Reconnect to server if URL changed
  • No restart required

Security Considerations

See the Security Documentation for detailed analysis of the threat model, encryption layers, and attack surface.

Next Steps