Skip to content

FIDO2 ​

The fido2 provider encrypts values into fnox.toml with a key derived from a FIDO2 security key through the CTAP2 hmac-secret extension. Encrypting and decrypting both need the same physical key; fnox set writes new ciphertext.

Config typefido2
StorageEncrypted in config
Writes (fnox set)Yes
RequiresA FIDO2 key with hmac-secret support, plugged in

FIDO2 support alone is not enough: confirm that your key and firmware support the hmac-secret extension. The provider is not included in Linux musl builds of fnox.

Quick start ​

Plug in the key and create the provider:

sh
fnox provider add secure fido2

Setup asks for the key's PIN only if the key has one (a fingerprint or touch-only key gets no PIN prompt), creates a credential with hmac-secret enabled, generates a random salt, and verifies the key with a second touch. It writes the result to fnox.toml:

toml
[providers.secure]
type = "fido2"
credential_id = "a1b2c3..." # generated hex credential ID, abbreviated
salt = "d4e5f6..."          # generated 32-byte hex salt, abbreviated
rp_id = "fnox.secure"

Store a value, then use it. Each command asks you to touch the key:

sh
fnox set API_KEY --provider secure
fnox exec -- ./start.sh

The config is portable: on another machine, plug in the same key and it decrypts.

Authentication ​

fnox talks to the key directly; there is no CLI or account. Each fnox process asks for one touch per provider and keeps the hmac-secret response in memory until it exits, so fnox exec resolves every secret from the provider with a single touch.

fnox asks the key how it verifies you and prompts to match:

Key setupWhat fnox asks for
Fingerprint enrolled (for example, YubiKey Bio)Your fingerprint. No PIN prompt; if the read fails in a terminal, fnox offers the PIN.
PIN set, no fingerprintThe PIN, then a touch.
No PIN, no fingerprintA touch only.

fnox provider add asks for a PIN only when the key has one, and does not store it. With no terminal and a PIN-protected key, set pin as described in Avoid typing the PIN.

The provider needs interactive approval, so it cannot run in non-interactive mode or in CI. Use another provider for CI secrets.

Fewer prompts ​

  • Turn on the daemon. After one touch, later reads in your session come from memory instead of asking for another. A cache miss still needs a touch in a terminal.
  • Use a lease. The key is touched when a lease is created; cached leases are reused without a touch.
  • Encrypt most values with age. Keep one age identity behind the key (see Plugin support) and encrypt the rest of the project with age.

Avoid typing the PIN ​

pin accepts a secret reference, so the PIN can live in the OS keychain instead of being typed on each run. Keep the PIN provider and secret in your gitignored fnox.local.toml:

toml
[providers.keychain]
type = "keychain"
service = "fnox"

[secrets]
FIDO2_PIN = { provider = "keychain", env = false }

[providers.secure]
type = "fido2"
credential_id = "a1b2c3..."
salt = "d4e5f6..."
rp_id = "fnox.secure"
pin = { secret = "FIDO2_PIN" }

The key is still touched on each use. Never put the PIN itself in a committed fnox.toml.

Configuration ​

FieldRequiredDefaultDescription
credential_idYesNoneHex-encoded credential ID, generated by fnox provider add.
saltYesNoneHex-encoded 32-byte salt sent to the key, generated by fnox provider add.
rp_idYesNoneRelying party ID of the credential. Setup uses fnox.<provider-name>.
pinNoNoneKey PIN. When unset, fnox prompts on a terminal only if the key needs one.

All fields accept { secret = "NAME" }. Every provider also accepts auth_command and daemon_cache.

How values are stored ​

  1. fnox sends salt to the key during an assertion for credential_id. The key returns a 32-byte secret derived from its internal device secret.
  2. HKDF-SHA256 derives a 256-bit AES key from that response, using the provider name as context.
  3. AES-256-GCM encrypts the value. fnox stores base64(nonce || ciphertext || tag) as the secret's value.

The hmac-secret response is never written to disk.

Renaming providers invalidates ciphertext

The provider name is part of key derivation. Renaming a provider (for example, from secure to my_fido2) changes the derived key, making all previously encrypted secrets and cached lease credentials undecryptable. To migrate, keep the old provider available, create a new provider under the new name, and read values through the old provider before storing them with the new one. Verify the new values before removing the old configuration.

Protect lease credentials ​

Store long-lived master credentials with the key and mark them env = false, so only a credential lease uses them and only short-lived credentials reach your command:

toml
[secrets]
AWS_ACCESS_KEY_ID = { provider = "secure", env = false }
AWS_SECRET_ACCESS_KEY = { provider = "secure", env = false }

[leases.aws]
type = "aws-sts"
role_arn = "arn:aws:iam::123456789012:role/dev-role"
region = "us-east-1"

FIDO2 or YubiKey ​

Featurefido2yubikey
Key typesAny FIDO2 keyYubiKey only
ProtocolCTAP2 hmac-secretHMAC-SHA1 challenge-response
PIN supportYesNo
Key output32 bytes20 bytes

Choose fido2 if your key supports hmac-secret. Choose yubikey if you use a YubiKey OTP slot configured for HMAC-SHA1 challenge-response.

Troubleshooting ​

FIDO2 authenticator does not support hmac-secret extension ​

The key created a credential but did not enable hmac-secret. Use a key and firmware that support the extension.

Failed to find FIDO2 device ​

fnox found no FIDO2 key. Plug the key in, close other applications that hold it open, and retry with fnox check --all. fnox provider test does not contact the key.

requires interactive authentication and cannot be used in non-interactive mode ​

The command ran with --non-interactive or FNOX_NON_INTERACTIVE set, or from shell integration, the MCP server, or the TUI. Run fnox exec in a terminal instead.

needs the key's PIN but there is no terminal to ask on ​

The key has a PIN and fnox cannot prompt. Set pin = { secret = "NAME" } as in Avoid typing the PIN, or run the command in a terminal.

See also ​

  • YubiKey: HMAC-SHA1 challenge-response alternative.
  • Credential leases: use hardware-protected credentials to mint short-lived ones.
  • Plugin support: use a FIDO2 key through an age plugin instead.
MIT LicenseCopyright © 2026jdx.dev