This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Passless is a software FIDO2 authenticator emulator written in Rust. It creates a virtual FIDO2 security token that operates through the UHID (Userspace HID) kernel interface, allowing systems to authenticate using FIDO2/WebAuthn without physical hardware tokens.
Key characteristics:
- Uses the
keylibcrate for FIDO2/CTAP protocol implementation - Operates as a platform authenticator with resident key (passkey) support
- Supports multiple storage backends: local filesystem, password-store (pass), and TPM 2.0
- Implements security hardening (mlock, core dump prevention)
- Desktop notifications for user verification prompts
# Standard build
cargo build
# Release build
cargo build --release
# Full release with vendoring (for distribution)
make releaseShell Completions:
Shell completions for bash, zsh, fish, and elvish are automatically generated during build time via build.rs. The completions are placed in target/*/build/passless-rs-*/out/completions/ and are installed by the PKGBUILD to the appropriate system directories:
- Bash:
/usr/share/bash-completion/completions/passless - Zsh:
/usr/share/zsh/site-functions/_passless - Fish:
/usr/share/fish/vendor_completions.d/passless.fish - Elvish:
/usr/share/elvish/lib/passless.elv
# Run unit tests
cargo test
# Run unit tests with linting
make test
# Run E2E tests (requires authenticator to be running or will auto-start)
make test-e2eThe E2E tests use environment variables:
PASSLESS_E2E_AUTO_ACCEPT_UV=1- Auto-accept user verification (debug builds only)PASSLESS_LOCAL_PATH=/tmp/passless/fido2- Use temporary storage
# Format code
cargo fmt
# or
make fmt
# Check formatting
make fmt-check
# Run clippy
cargo clippy --all-targets --all-features -- -D warnings
# or
make clippy
# Auto-fix clippy issues
make clippy-fix
# Run all lints
make lint
# Auto-fix all lints
make lint-fix# Install hooks
make pre-commit-install
# Run hooks manually
make pre-commitThe authenticator requires UHID kernel module and proper permissions:
# As root, setup UHID access
modprobe uhid
groupadd fido 2>/dev/null || true
usermod -a -G fido $USER
echo 'KERNEL=="uhid", GROUP="fido", MODE="0660"' > /etc/udev/rules.d/90-uinput.rules
udevadm control --reload-rules && udevadm trigger
# Run authenticator
cargo run
# With verbose logging
cargo run -- --verbose
# With custom backend
cargo run -- --backend-type pass
cargo run -- --backend-type local --local-path /tmp/passless
cargo run -- --backend-type tpm --tpm-tcti "device:/dev/tpmrm0"
# With swtpm (software TPM for testing)
cargo run -- --backend-type tpm --tpm-tcti "swtpm:path=$HOME/.local/run/swtpm-sock"TPM Backend Setup: For detailed TPM and swtpm setup instructions, see:
docs/TPM_SETUP.md- Comprehensive TPM setup guidedocs/SWTPM_QUICK_START.md- Quick swtpm reference
Main Loop (cmd/passless/src/main.rs):
- Runs in
run_with_service()function - Reads CTAPHID packets from UHID device in 64-byte chunks
- Processes CBOR commands through
AuthenticatorService - Sends response packets back through UHID
- Periodically cleans up expired credential cache (every 5 seconds)
- Implements graceful shutdown on Ctrl+C with 5-second timeout:
- Sets shutdown flag when Ctrl+C is received
- Spawns a timeout thread that forcefully exits after 5 seconds
- Performs final cache cleanup before normal exit
- If graceful shutdown takes longer than 5 seconds, process is forcefully terminated
AuthenticatorService (src/authenticator.rs):
- Orchestrates FIDO2 operations using dependency-injected
CredentialStorage - Builds callbacks for credential operations (read, write, delete, iterate)
- Handles user presence/verification through notifications
- Wraps
keylib::Authenticatorwith storage backend integration
Storage Abstraction (src/storage/mod.rs):
CredentialStoragetrait defines interface for all backends- Key methods:
read_first(),read_next(),read(),write(),delete(),select_users() - Supports iteration with filtering (by ID, RP, or hash)
- Cache cleanup method for security (removes sensitive data from memory)
Storage Implementations:
LocalStorageAdapter(src/storage/local.rs): JSON files in local directory with in-memory cachingPassStorageAdapter(src/storage/pass.rs): Encrypted storage using password-store/GPG- Automatic git sync: If the password-store has a git remote configured, changes are automatically synced
- Git pull on initialization: Ensures latest credentials are loaded
- Git commit + push on write/delete: Automatically commits with descriptive messages and pushes to remote
- Non-blocking: Git failures are logged as warnings and don't prevent credential operations
TpmStorageAdapter(src/storage/tpm.rs): TPM 2.0 hardware-backed storage- Credentials sealed by TPM, can only be unsealed on the same machine
- Uses
tss-esapicrate for TPM operations - Supports hardware TPM and software TPM (swtpm) via TCTI configuration
- Stores sealed credentials as files with
.tpmextension - Uses owner hierarchy for sealing/unsealing operations
CTAP Command Handlers (src/commands/):
credential_mgmt.rs: Implements credential management subcommands (enumerate, delete)custom.rs: Wraps credential management as custom CTAP commands (0x0a standard, 0x41 Yubikey-compatible)- Commands use storage backend directly for operations
Configuration (src/config/mod.rs):
AppConfig: Main config structure with backend selectionBackendConfig: Enum for Local, Pass, or TPM backendSecurityConfig: Memory locking, core dump prevention- CLI args merged with TOML config (CLI takes precedence)
- Default config location:
~/.config/passless/config.toml - TPM-specific config: TCTI string (e.g.,
device:/dev/tpmrm0,swtpm:path=/path/to/socket)
Security Hardening (src/config.rs:74-113):
mlockall(): Locks all memory to prevent swapping credentials to disksetrlimit(RLIMIT_CORE, 0): Disables core dumpsprctl(PR_SET_DUMPABLE, 0): Prevents process dumping- Applied during startup, failures logged as warnings
The authenticator is configured as a FIDO 2.1 platform authenticator with:
- AAGUID:
66:69:64:6F:2E:70:61:73:73:6C:65:73:73:2E:72:73("fido.passless.rs") - Resident keys (rk=true) for discoverable credentials/passkeys
- User verification support (uv=true, always_uv=true)
- Platform authenticator mode (plat=true)
- PIN UV auth token support
- Credential management enabled (standard 0x0a + Yubikey 0x41 commands)
- Max 100 resident credentials
- Supported extensions: credProtect
Registration Flow:
- Host sends MakeCredential request via UHID
- CTAPHID layer assembles packets
- AuthenticatorService processes CBOR request
- User presence callback shows notification
- Keylib generates credential
- Write callback stores credential via CredentialStorage
- Response sent back through UHID
Authentication Flow:
- Host sends GetAssertion request
- Select callback retrieves users for RP ID
- Read callbacks retrieve matching credentials
- User presence callback confirms authentication
- Keylib signs assertion
- Response returned to host
Credential Management:
- Custom command (0x0a or 0x41) received
- Subcommand router in
credential_mgmt.rs - Direct storage backend calls for enumerate/delete operations
- Metadata encoded in CBOR and returned
Unit Tests:
- Located in
#[cfg(test)]modules within source files - Test storage adapters, service creation, basic operations
E2E Tests (tests/e2e_webauthn.rs):
- Requires running authenticator instance
- Tests real WebAuthn registration/assertion flows
- Use
make test-e2ewhich auto-starts authenticator if needed - Run with
--test-threads=1 --ignored --nocapture
- Create module in
src/storage/ - Implement
CredentialStoragetrait - Add variant to
BackendConfigenum insrc/config.rs - Add CLI args struct with
#[derive(Args)] - Update
main.rsmatch statement for backend initialization - Consider if
disable_user_verification()should return true
- Define command handler in
src/commands/ - Create handler function matching
CustomCommandHandlersignature - Use
create_command_with_handler()or similar builder - Add to
custom_commandsvec inbuild_authenticator_config() - Access storage via
Arc<Mutex<S>>parameter if needed
Configuration can be set via environment variables:
PASSLESS_CONFIG: Path to config filePASSLESS_BACKEND_TYPE: "local", "pass", or "tpm"PASSLESS_LOCAL_PATH: Local storage directoryPASSLESS_PASS_STORE_PATH: Password store pathPASSLESS_PASS_PATH: Relative path in password store (default: "fido2")PASSLESS_PASS_GPG_BACKEND: "gpgme" or "gnupg-bin"PASSLESS_TPM_PATH: TPM storage directoryPASSLESS_TPM_TCTI: TPM TCTI configuration (e.g., "device:/dev/tpmrm0", "swtpm:path=/path/to/socket")PASSLESS_USE_MLOCK: Enable memory locking (default: true)PASSLESS_DISABLE_CORE_DUMPS: Disable core dumps (default: true)PASSLESS_NO_NEW_PRIVS: Set no new privileges (default: true)PASSLESS_LOG_LEVEL: Log level filterPASSLESS_LOG_STYLE: Log stylePASSLESS_E2E_AUTO_ACCEPT_UV: Auto-accept UV in E2E tests (debug only)
Formatting Rules:
- Imports from different crate paths must be on separate lines
- Imports from the same crate path (same submodule) must be grouped with braces
- Each import line must end with a semicolon
Correct Example:
use super::SomeType;
use crate::error::Error;
use crate::pin_token::{Permission, PinToken, PinTokenManager};
use soft_fido2_crypto::{ecdsa, pin_protocol};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use ciborium::cbor;
use serde::Serialize;Incorrect Examples:
// ❌ Wrong: combining different submodules with braces
use std::{collections::HashMap, sync::{Arc, Mutex}};
// ❌ Wrong: not grouping same submodule imports
use crate::pin_token::Permission;
use crate::pin_token::PinToken;
use crate::pin_token::PinTokenManager;
// ❌ Wrong: imports not in correct order
use serde::Serialize;
use crate::error::Error;
use std::sync::Arc;The project uses GitHub Actions (.github/workflows/rust.yml):
- fmt: Checks code formatting with
cargo fmt --check - clippy: Runs clippy with
--deny warnings - build: Builds and tests on Ubuntu 22.04 (for older glibc compatibility)
- release: On tags, creates release artifacts with vendored dependencies
Release process:
- Update version in
Cargo.toml - Run
make update-changelogto generate changelog - Tag with
v*pattern - CI builds, runs tests, publishes to crates.io, creates GitHub release
Keep README files concise and focused on basic examples only:
- DO: Provide simple, working examples
- DO: Show the most common use cases
- DO: Keep explanations brief and to the point
- DON'T: Create lengthy, comprehensive documentation
- DON'T: Duplicate information already in other docs
- DON'T: Recreate README files that have been intentionally deleted
Examples of good README content:
- Quick installation command
- Basic usage example
- Link to more detailed documentation if needed
For detailed documentation, use dedicated files like INSTALL.md, CONTRIBUTING.md, etc.
This project uses Conventional Commits enforced by commitlint.
<type>: <subject>
<body>
<footer>
-
Type: Required, must be one of:
feat: New featurefix: Bug fixrefactor: Code refactoring (no functional changes)docs: Documentation changesstyle: Code style changes (formatting, etc.)test: Adding or updating testschore: Maintenance tasksbuild: Build system changesrevert: Reverting a previous commitrelease: Release-related commits
-
Subject:
- Use sentence-case or lower-case (NOT Title Case)
- Don't end with a period
- Keep it concise and descriptive
-
Body (optional):
- Must have a blank line before it
- Explain what and why, not how
- Use present tense
-
Footer (optional):
- Must have a blank line before it
- Use for breaking changes (
BREAKING CHANGE:) or issue references
feat: add TOML config file support
refactor: flatten config structure for better TOML serialization
fix: resolve clippy warnings in storage module
docs: update installation guide with systemd service