llave

March 21, 2026 · View on GitHub

Alternative implementation of Llave — Spain's digital identity system for interacting with public administration services. CLI and Flutter (mobile/desktop) clients sharing a single Rust core.

Features

  • Device activation: Register your device with Llave
  • PIN requests: Get a temporary Llave PIN for accessing government services
  • NIF checking: Verify if a NIF/NIE is registered in Llave
  • DNI/NIE auth: Weak authentication using ID card data
  • QR authentication: Authenticate by scanning QR codes
  • Account data: View your Llave account information
  • Operations history: Browse past authentication operations
  • Structured output: JSON by default, --plain for human-readable text
  • Secure storage: Credentials stored via system keyring (fallback to XDG data dir)
  • Flutter apps: iOS, Android, and desktop powered by the same Rust core via FFI

Project Structure

crates/
├── llave-core/       # Shared Rust library: API client, auth flows, config, crypto, session
├── llave-core-ffi/   # FFI bridge layer for Flutter (flutter_rust_bridge)
└── llave-cli/        # CLI binary (`llave`)
flutter/              # Flutter app (iOS, Android, desktop)

Installation

nix run github:openhacienda/llave#llave-cli

Development shell

nix develop
cargo build --release

From source

cargo install --path crates/llave-cli

Usage

Activate your device

llave activate --nif 12345678Z

Request a Llave PIN

llave pin

Check NIF registration

llave check --nif 12345678Z

DNI/NIE weak authentication

llave dni-auth --nif 12345678Z --fecha 01-01-2030 --soporte ABC123456

View session status

llave status

View account data

llave my-data

View operations history

llave history

Llave Móvil (push notification replacement)

Since the CLI cannot receive Firebase push notifications, it polls the server instead.

Check for pending requests (single poll):

llave pending

Listen continuously for authentication requests:

llave listen                          # default: 5s interval, 60 attempts
llave listen --interval 3 --max-attempts 120  # custom polling

Confirm a pending authentication request:

llave confirm --token <TOKEN> --idp-code <IDP_CODE>

Reject a pending authentication request:

llave reject --token <TOKEN> --idp-code <IDP_CODE>

QR authentication

llave qr --value "qr-code-content"

Log out

llave logout

Output formats

By default, all commands output JSON:

{
  "pin": "123456",
  "time_to_live_seconds": "180",
  "nif": "12345678Z"
}

Use --plain for human-readable output:

pin: 123456
time_to_live_seconds: 180
nif: 12345678Z

Use -v / --verbose for debug logging.

Configuration

Configuration and session data are stored following XDG conventions:

  • Config: $XDG_CONFIG_HOME/llave-cli/config.json
  • Session: Stored in system keyring, or $XDG_DATA_HOME/llave-cli/session.json as fallback

Documentation

Technical documentation is available as an mdbook under book/. Based on reverse engineering of the official es.aeat.pin24h Android app (v6.2.5).

To build and serve locally:

nix run nixpkgs#mdbook -- serve book/

Topics covered:

  • Architecture — system overview, AEAT infrastructure, Android app structure
  • API Reference — endpoints, request/response formats, error codes, constants
  • Authentication Flows — PIN request, Clave Movil, QR, DNI/NIE + SMS activation, SAML/DNIe
  • Implementation Guide — cookie management, client identity, redirect handling, session persistence
  • Cryptography — browser encryption, device password generation, credential storage

License

MIT