Skip to content

About

Virtual FIDO2 authenticator using ML-DSA 44/65/87 and ES256 algorithms with CTAP 2.3 and UHID implementations written Rust.

Topics

Resources

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

pqkey

A FIDO2 security key in software for Linux, with post-quantum ML-DSA passkeys.

CI E2E Security License: MIT

pqkey shows up in your browser as an ordinary USB security key. A user daemon creates a virtual HID device through the kernel's uhid driver, so any FIDO2 client can use it, and every registration, reset and sign-in that asks for your presence waits for you to approve it in a desktop notification.

  • Post-quantum: ML-DSA-44, ML-DSA-65 and ML-DSA-87 credentials (FIPS 204), alongside ECDSA (ES256, ESP256, ES384, ESP384, ES512, ESP512 and ES256K), EdDSA (EdDSA, Ed25519 and Ed448), and RSA-2048 (RS256, RS384, RS512, PS256, PS384 and PS512).
  • CTAP 2.3: PIN (protocols 1 and 2), passkeys with credential management, always-UV, minimum PIN length and PIN complexity configuration, minPinLength, pinComplexityPolicy, credBlob, hmac-secret, hmac-secret-mc, credProtect, largeBlobKey, thirdPartyPayment, fragmented large-blob storage, and packed self-attestation.
  • U2F: CTAP1 registration and authentication over USB HID, using ES256 credentials that also work over CTAP2. U2F uses approval rather than a PIN, and its prompt cannot identify the site. Always-UV disables it.
  • Device identification: CTAPHID_WINK briefly names the software key in a desktop notification with no approval buttons. It expires after two seconds and does not authorize an operation. CTAPHID_LOCK supports channel leases of up to ten seconds, renewed or released by their owner.
  • Rust throughout: pure-Rust cryptography, and unsafe code only where pqkey calls into the operating system.

Warning

pqkey is a research project, not for production use. A software key is not a hardware key: its private keys are files in your home directory, which anything running as you, or as root, can use. See SECURITY.md for the threat model.

FIDO clients can identify a connection with WINK even when a notification server cannot show action buttons. If delivery fails or exceeds 500 ms, WINK returns a transport error; the key remains usable. Identification also works with PIN complexity and always-UV enabled. LOCK is volatile: expiry or daemon shutdown releases it, while channel resynchronization and authenticator reset preserve an unexpired lease. Clients should release leases in cleanup handlers.

Install

You need Linux with systemd and the uhid module (Ubuntu, Debian and Fedora have it), and a desktop whose notifications show buttons (GNOME, KDE Plasma or dunst). Copy this one command:

git clone https://github.com/rainzhang05/SoftwareAuthenticator.git && SoftwareAuthenticator/install.sh

The installer says how much disk space it will use (about 1.2 GB with the build tools and Rust), asks once to continue and once for your sudo password, and shows its progress as it:

  1. installs a C linker, Rust and ACL tools, if they are missing;
  2. builds pqkey and installs it as /usr/local/bin/pqkey;
  3. gives the key access to /dev/uhid with udev rules, and loads the uhid module at every boot;
  4. starts the key as a systemd user service and asks you to choose its PIN.

A successful installation means the installed service is running, its HID client can exchange messages, the configured presence provider is usable, and its PIN requirements are complete. pqkey works in the same terminal, with no reboot or new login. Missing requirements produce a nonzero exit status and instructions; the installer log is ~/.cache/pqkey/install.log (or $XDG_CACHE_HOME/pqkey/install.log).

./install.sh --yes approves installation; you still choose and confirm the PIN. Without terminal input, a fresh key remains installed but setup fails until you run pqkey pin, then pqkey setup, in a terminal. A configured key can be updated without PIN input unless its policy requires a PIN change. If your terminal's PATH excludes /usr/local/bin, run export PATH="/usr/local/bin:$PATH" in that terminal and rerun the installer.

Before upgrading, stop the key and back up its complete state directory, including the keys. Then run git pull and ./install.sh again. Reruns repair partial setup and preserve credentials, valid PINs and authenticator policy. Updates replace the executable atomically. If setup fails after replacement, the previous executable remains at /usr/local/bin/pqkey.previous; the installer prints retry and restore commands. The recovery copy is removed after successful setup.

Registering or replacing a discoverable credential upgrades older stores to the authenticated membership format. Restore only the executable when the previous version understands that format. A version predating this format requires its matching complete state backup as well; credentials created after that backup will be absent.

Note

Anyone who can open /dev/uhid can create any HID device, keyboards included. The rules give it to the plugdev group, and the installer adds you to that group if you are not in it yet (on Ubuntu you already are).

Usage

Register pqkey on any site that supports passkeys or security keys, and approve the notification that appears.

Command What it does
pqkey Shows the key's status, its PIN, its free passkey slots and anything to fix
pqkey pin Sets or changes the PIN
pqkey config Shows always-UV and PIN policy settings
pqkey config always-uv on|off Requires or stops requiring the PIN
pqkey config min-pin-length N [--rp RP_ID]... Raises the PIN minimum
pqkey config pin-complexity [--yes] Enables the ASCII letter and digit rule
pqkey config force-pin-change Requires a different PIN
pqkey passkeys Lists the passkeys stored on the key
pqkey passkeys delete QUERY Deletes the passkey whose site, user or ID contains QUERY
pqkey reset Erases credentials, large blobs and PIN; resets settings
pqkey stop, pqkey start Unplugs and plugs in the key

Configuration changes ask for the current PIN when one is set. Always-UV requires the PIN for registrations and sign-ins that ask for user presence. Without a PIN, turn always-UV off or set one with pqkey pin before using the key in a browser.

The minimum PIN length counts Unicode code points, from 4 to 63, with a separate limit of 63 UTF-8 bytes. Raising it asks for confirmation, unless --yes is given: only pqkey reset, which erases every passkey and large blob, can lower it again. Repeated --rp options replace the RP IDs allowed to receive the minPinLength extension; omitting them keeps the list. A key can remember 8 RP IDs, each up to 253 bytes. The complete configuration request must also fit the key's advertised 1,768-byte message limit, including CBOR and PIN authentication. If it is too large, pqkey reports its encoded size and limit before sending the command; use fewer or shorter RP IDs. The list is applied in one operation. If a PIN change becomes required, run pqkey pin and choose a different PIN meeting the current minimum.

Enable PIN complexity with pqkey config pin-complexity. New PINs must then contain an ASCII letter and digit in addition to meeting the length requirements. Only reset disables it; enabling with an existing PIN requires one replacement.

pqkey passkeys lists only passkeys, which are discoverable credentials. A site that registers the key as a second factor keeps the credential's ID and sends it back to sign in, so the key does not list that credential. The prompt makes the difference clear, saying "Create a passkey" or "Register a security key". Like a hardware key, pqkey accepts a reset only within 10 seconds of being plugged in, so pqkey reset restarts it first. The key's state lives in ~/.local/share/pqkey.

FIDO clients can store a serialized large-blob array of up to 16,384 bytes, transferred in fragments of at most 1,704 bytes. Writes require the PIN's large-blob write permission when a PIN is set or always-UV is on. Reset restores the empty array; deleting a credential leaves blob collection to the client. Discoverable credentials registered with largeBlobKey keep a secret 32-byte key for platforms to encrypt and decrypt their blobs.

Credentials explicitly registered with thirdPartyPayment: true retain that metadata for both discoverable and non-discoverable credentials. Assertions requesting it return the stored boolean inside signed authenticator data; creation returns no payment extension output. pqkey passkeys marks enabled passkeys in its PAYMENT column. Enabled non-discoverable credentials consume storage but still require their RP's allow list and remain outside passkey management. Ordinary eligible credentials continue to use sealed IDs.

The flag does not grant cross-RP access or implement a payment prompt. The client platform determines payment eligibility and UI, including Secure Payment Confirmation (SPC). Authenticator regressions and libfido2 signed-byte checks are separate from browser/SPC verification; see the verification notes.

Clients

Client ES256 ML-DSA Notes
Chromium 153 ✓ ✓ Needs a PIN on the key for passkeys
Firefox 154 ✓ Ignores algorithms it does not know
python-fido2 2.2.1 ✓ ✓ Used by the end-to-end tests
libfido2 1.16 ✓ PIN, credential management and hmac-secret work

These were tested on Ubuntu 26.04. Each client's limits are listed under Clients in the development notes.

Troubleshooting

pqkey status names anything that is missing and how to fix it, and scripts/diagnose.sh shows the whole state of the key on your computer. For browser behaviour, see Troubleshooting.

Uninstall

pqkey setup --uninstall

This stops the key and removes its service. It then prints one sudo command that removes pqkey, its udev rules and the uhid setting. Your passkeys stay in ~/.local/share/pqkey until you delete that directory.

Documentation

  • SECURITY.md: the threat model, and how to report a vulnerability
  • Architecture: how pqkey works, and what it supports of CTAP 2.3
  • Development notes: the scripts that run every check and test (scripts/check.sh, scripts/e2e.sh and more), and troubleshooting
  • Browser test kit: a local relying party that checks everything the key returns

License

pqkey is free software under the MIT License: you may use, change and share it, as long as the copyright notice comes with it. It comes with no warranty.

About

Virtual FIDO2 authenticator using ML-DSA 44/65/87 and ES256 algorithms with CTAP 2.3 and UHID implementations written Rust.

Topics

Resources

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Contributors

Languages