soft-fido2 0.4.2

A pure Rust implementation of FIDO2/WebAuthn CTAP 2.0/2.1 protocol
Documentation

Build status Crates.io Documentation License A pure Rust implementation of FIDO2/WebAuthn CTAP 2.0/2.1 protocol for virtual authenticators.

soft-fido2 provides virtual FIDO2 authenticator capabilities for testing and development, enabling developers to implement WebAuthn authentication flows without physical security keys.

Features

  • ๐Ÿ” Full CTAP 2.0/2.1 Protocol - Complete implementation of FIDO2 Client-to-Authenticator Protocol
  • ๐Ÿšซ no_std Support - Core protocol and cryptography work in embedded environments
  • ๐Ÿ”Œ Multiple Transports - USB HID and Linux UHID virtual device support
  • ๐Ÿงช Testing-First - Designed for WebAuthn integration testing and development
  • ๐ŸŽฏ Callback-Based - Flexible user interaction model with customizable callbacks
  • ๐Ÿ“ฆ Modular Architecture - Separate crates for crypto, protocol, and transport layers
  • ๐Ÿ”’ Well-Audited Crypto - Uses industry-standard cryptographic libraries (p256, sha2, aes)

Basic Example

use soft_fido2::{Authenticator, CallbacksBuilder, TransportList, Client};

// Create an in-memory authenticator with default callbacks
let callbacks = CallbacksBuilder::new()
    .up(|_, _, _| Ok(soft_fido2::UpResult::Accepted))  // User presence
    .uv(|_, _, _| Ok(soft_fido2::UvResult::Accepted))  // User verification
    .build();

let mut auth = Authenticator::new(callbacks)?;

// Enumerate available transports (USB HID or UHID)
let mut transport_list = TransportList::enumerate()?;
let mut transport = transport_list.get(0)?;
transport.open()?;

// Get authenticator info
let info = Client::authenticator_get_info(&mut transport)?;
println!("Authenticator: {:?}", info);

Architecture

soft-fido2 is organized into four main crates:

soft-fido2/
โ”œโ”€โ”€ soft-fido2           # High-level API and examples
โ”œโ”€โ”€ soft-fido2-crypto    # Cryptographic primitives (ECDSA, ECDH, PIN protocols)
โ”œโ”€โ”€ soft-fido2-ctap      # CTAP 2.0/2.1 protocol implementation
โ””โ”€โ”€ soft-fido2-transport # Transport layers (USB HID, UHID)

Crate Overview

Crate Description no_std
soft-fido2 High-level API combining all components โš ๏ธ Core only
soft-fido2-crypto P-256 ECDSA/ECDH, PIN protocols V1/V2 โœ… Yes
soft-fido2-ctap CTAP command handlers and authenticator logic โœ… Yes
soft-fido2-transport USB HID and UHID transport implementations โŒ Requires std

Usage

Creating a Virtual Authenticator

use soft_fido2::{
    Authenticator, AuthenticatorConfig, CallbacksBuilder,
    UpResult, UvResult, Credential
};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;

// Setup credential storage
let credentials = Arc::new(Mutex::new(HashMap::<Vec<u8>, Credential>::new()));

// Build callbacks for user interaction
let creds_write = credentials.clone();
let creds_read = credentials.clone();

let callbacks = CallbacksBuilder::new()
    .up(Arc::new(|_info, _user, _rp| {
        println!("User presence check - tap to continue");
        Ok(UpResult::Accepted)
    }))
    .uv(Arc::new(|_info, _user, _rp| {
        println!("User verification - provide PIN or biometric");
        Ok(UvResult::Accepted)
    }))
    .write(Arc::new(move |_id, _rp, cred| {
        let mut store = creds_write.lock().unwrap();
        store.insert(cred.id.to_vec(), cred.to_owned());
        Ok(())
    }))
    .read_credentials(Arc::new(move |rp_id, user_id| {
        let store = creds_read.lock().unwrap();
        let filtered: Vec<Credential> = store
            .values()
            .filter(|c| c.rp.id == rp_id &&
                       (user_id.is_none() || user_id == Some(c.user.id.as_slice())))
            .cloned()
            .collect();
        Ok(filtered)
    }))
    .build();

// Create authenticator with custom configuration
let config = AuthenticatorConfig::builder()
    .aaguid([0x6f, 0x15, 0x82, 0x74, 0xaa, 0xb6, 0x44, 0x3d,
             0x9b, 0xcf, 0x8a, 0x3f, 0x69, 0x29, 0x7c, 0x88])
    .max_credentials(100)
    .extensions(vec!["credProtect".to_string(), "hmac-secret".to_string()])
    .build();

let auth = Authenticator::with_config(callbacks, config)?;

WebAuthn Registration Flow

use soft_fido2::{
    Client, MakeCredentialRequest, ClientDataHash,
    RelyingParty, User, PinUvAuthProtocol
};
use sha2::{Sha256, Digest};

// 1. Create client data hash (normally from browser)
let client_data_json = r#"{"type":"webauthn.create","challenge":"...","origin":"https://example.com"}"#;
let mut hasher = Sha256::new();
hasher.update(client_data_json.as_bytes());
let client_data_hash = ClientDataHash::from(hasher.finalize().as_slice());

// 2. Setup relying party and user
let rp = RelyingParty {
    id: "example.com".to_string(),
    name: Some("Example Corp".to_string()),
};

let user = User {
    id: b"user123".to_vec(),
    name: Some("alice@example.com".to_string()),
    display_name: Some("Alice Smith".to_string()),
};

// 3. Create credential
let request = MakeCredentialRequest::builder()
    .client_data_hash(client_data_hash)
    .rp(rp)
    .user(user)
    .resident_key(false)
    .user_verification(true)
    .build();

let response = Client::make_credential(&mut transport, request)?;
println!("Credential created! Credential ID: {:?}", response);

WebAuthn Authentication Flow

use soft_fido2::{Client, GetAssertionRequest, CredentialDescriptor, CredentialType};

// 1. Get assertion (authentication)
let request = GetAssertionRequest::builder()
    .rp_id("example.com")
    .client_data_hash(client_data_hash)
    .allow_list(vec![
        CredentialDescriptor {
            credential_type: CredentialType::PublicKey,
            id: credential_id.clone(),
            transports: None,
        }
    ])
    .user_verification(true)
    .build();

let response = Client::get_assertion(&mut transport, request)?;
println!("Authentication successful! Signature: {:?}", response);

PIN Protocol

use soft_fido2::{PinUvAuthEncapsulation, PinProtocol, PinUvAuthProtocol};

// Establish PIN protocol
let mut pin_encapsulation = PinUvAuthEncapsulation::new(&mut transport, PinProtocol::V2)?;

// Get PIN token for operations
let pin_token = pin_encapsulation.get_pin_uv_auth_token_using_pin_with_permissions(
    &mut transport,
    "123456",  // User's PIN
    0x01 | 0x02,  // Permissions: makeCredential | getAssertion
    Some("example.com"),
)?;

// Use PIN token for authenticated operations
let pin_auth = PinUvAuthProtocol::from_pin_token(&pin_token, client_data_hash.as_slice());

no_std Support

The core protocol and cryptographic components support no_std environments:

[dependencies]
soft-fido2 = { version = "0.2", default-features = false }

Available in no_std:

  • โœ… CTAP protocol logic
  • โœ… Cryptographic operations (ECDSA, ECDH)
  • โœ… PIN protocols V1 and V2
  • โœ… Authenticator state management
  • โœ… CBOR encoding/decoding

Requires std:

  • โŒ Transport layers (USB HID, UHID)
  • โŒ Client API
  • โŒ Time-based PIN token expiration (uses timestamp = 0 in no_std)

Examples

The soft-fido2/examples directory contains several complete examples:

Run examples:

# List available FIDO2 devices
cargo run --example client

# Run virtual authenticator (requires UHID permissions)
cargo run --example authenticator

# Complete WebAuthn flow
cargo run --example webauthn_flow

Building

Standard Build

cargo build --release

Build without USB Support

Useful on systems without libudev:

cargo build --no-default-features --release

Build for no_std

cargo build --no-default-features --target thumbv7em-none-eabihf --release

Testing

# Run all tests
cargo test --all

# Run integration tests only
cargo test --test webauthn_inmemory_test

# Run with all features
cargo test --all-features

Supported CTAP Commands

soft-fido2 implements the following CTAP 2.0/2.1 commands:

Command Support Description
authenticatorMakeCredential โœ… Create new credential (registration)
authenticatorGetAssertion โœ… Get authentication assertion (login)
authenticatorGetInfo โœ… Get authenticator metadata
authenticatorClientPIN โœ… PIN protocol operations (V1, V2)
authenticatorReset โœ… Factory reset
authenticatorGetNextAssertion โœ… Get next assertion in batch
authenticatorCredentialManagement โœ… Manage stored credentials
authenticatorSelection โœ… User verification and selection

Security

โš ๏ธ Important: This library is designed for testing and development purposes. While it implements the FIDO2 specification correctly and uses well-audited cryptographic libraries, it is not intended for production use as a real security key.

Cryptographic Dependencies

All cryptographic operations use well-maintained, audited libraries:

  • p256 (v0.13) - NIST P-256 elliptic curve operations
  • sha2 (v0.10) - SHA-256 hashing
  • aes (v0.8) - AES-256 encryption
  • hmac (v0.12) - HMAC authentication
  • rand (v0.8) - Cryptographically secure RNG

Credential Storage

Credentials are stored in memory by default. For persistent storage, implement custom callback functions that write to secure storage (encrypted filesystem, TPM, etc.).

Platform Support

Platform USB HID UHID Virtual Device
Linux โœ… Yes โœ… Yes
macOS โœ… Yes โŒ No
Windows โœ… Yes โŒ No
Embedded โŒ No โŒ No

UHID Requirements (Linux only):

  • UHID kernel module loaded: sudo modprobe uhid
  • User permissions: Add user to fido group or configure udev rules

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

# Clone repository
git clone https://github.com/pando85/soft-fido2
cd soft-fido2

# Install pre-commit hooks
make pre-commit-install

# Run tests
make test

# Run formatting and linting
make lint

License

This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.

References

Acknowledgments

Built with โค๏ธ using Rust


Note: This is a community project and is not affiliated with the FIDO Alliance.