Vendor-neutral guide for storing Oversight recipient private keys on a hardware device (YubiKey, OnlyKey, Nitrokey) rather than a disk file. The canonical source is docs/HARDWARE_KEYS.md; this page mirrors it for readability and adds an implementation-status footnote so visitors know what is shipped versus what is documented as forward design.

Implementation status (2026-05-07). As of v0.4.10 the KeyProvider trait and the OSGT-HW-P256-v1 suite are implemented end-to-end in oversight-rust/oversight-crypto, with a software reference provider (SoftwareP256KeyProvider) so the new suite can be seal/open round-tripped today. The oversight-container crate recognizes suite_id = 3 and ships seal_hw_p256 + open_sealed_with_provider. The PivKeyProvider (PKCS#11 binding) and the oversight CLI flags described below are the next bounded follow-up; until they ship, hardware-backed Oversight is real at the protocol layer but is software-tested rather than token-tested. See the v0.4.10 release post for the design rationale.

Why

When a recipient's .key file lives on disk, full compromise of that recipient's laptop gives an attacker the private key forever. That attacker can decrypt every sealed file addressed to that recipient, past and future, with no way to tell the issuer it happened.

A hardware-backed key eliminates this. The private key is generated inside the device's secure element and never leaves it. All ECDH (P-256) and signing (Ed25519) operations happen on-device. The host OS gets ECDH outputs, never the raw key. To decrypt, an adversary needs physical possession of the device and typically a touch, PIN, or biometric.

This does not give you enclave-grade guarantees. A compromised client running while the YubiKey is plugged in can still open files via the device. What it does give you:

Supported devices

Any device exposing PIV (Personal Identity Verification, PKCS#11-compatible) slots works. Tested:

Device Cost (USD) PIV slots Notes
YubiKey 5C NFC~$75yesMost tested; widely available
YubiKey 5 NFC~$55yesUSB-A version
YubiKey Security Key NFC~$29FIDO2 onlyCheapest but limited
Nitrokey 3 NFC~$80yesFully open-source firmware
OnlyKey~$50yesOpen hardware + firmware

Recommendation: YubiKey 5C NFC for most users (best tooling), Nitrokey 3 if firmware openness matters more than ecosystem support.

First-time setup

1. Install the tooling

# Debian / Ubuntu
sudo apt install yubikey-manager pcscd opensc
sudo systemctl enable --now pcscd

# macOS
brew install yubikey-manager opensc

# Arch
sudo pacman -S yubikey-manager opensc ccid

2. Verify the device is seen

ykman info
# Should print serial, firmware version, and enabled applications.

pkcs11-tool --list-slots --module /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
# Should list the YubiKey as slot 0.

3. Set a PIN and management key

Do not skip this. The factory defaults (PIN 123456, PUK 12345678) are publicly known. Change both now.

# PIV PIN (6-8 digits)
ykman piv access change-pin

# PIV PUK (used to unblock if you lock yourself out)
ykman piv access change-puk

# Management key (used for admin ops; 24-byte hex)
ykman piv access change-management-key --generate --protect
# --protect stashes the new key in PIV slot so you don't need to manage it

4. Generate an Oversight recipient key on-device

PIV has four main slots. Use slot 9d (Key Management) for Oversight. It is meant for decryption operations and does not require a PIN on every use (only first use per session, via cached auth).

# Generate an ECC P-256 key in slot 9d
ykman piv keys generate 9d --algorithm ECCP256 -
# Note: P-256, not Curve25519. See "Curve choice" below.

# Self-sign a cert so PIV treats the slot as initialized
ykman piv certificates generate 9d \
    --subject "CN=oversight-recipient" \
    --valid-days 3650 -

5. Export the public key in Oversight format

Oversight identities are JSON. We need to convert the PIV slot's public key to the format Oversight uses.

# Export the cert, extract the pubkey
ykman piv certificates export 9d - | \
    openssl x509 -pubkey -noout -in - | \
    openssl ec -pubin -text -noout

Write the resulting pubkey hex into an Oversight identity file with a hardware: true marker:

{
  "hardware": true,
  "provider": "piv",
  "piv_slot": "9d",
  "p256_pub": "<hex of SEC1 uncompressed P-256 pubkey, 65 bytes>",
  "ed25519_pub": null,
  "device_serial": "<yubikey-serial>"
}

The p256_pub field corresponds to Recipient.p256_pub in the manifest schema (see SPEC.md § 4.1.3). A hardware recipient leaves x25519_pub empty in the manifest; classic recipients leave p256_pub absent.

Curve choice: why P-256 for hardware-backed recipients

The default Oversight suite uses X25519 for key agreement. PIV-compatible hardware devices historically only supported P-256 and P-384 for PIV slots. YubiKey 5.7+ firmware does support Curve25519 via a dedicated OpenPGP applet, but PIV itself does not.

To stay compatible with the broadest set of devices (Nitrokey, OnlyKey, older YubiKeys), Oversight uses P-256 ECDH for hardware-backed recipients. The suite identifier in the manifest becomes OSGT-HW-P256-v1 instead of OSGT-CLASSIC-v1. The crypto is just as strong: P-256 ECDH is NIST-standardized, FIPS 140-3 compliant, and battle-tested.

Open clients that want to decrypt for hardware-backed recipients must support both suites. The default file-backed provider stays on X25519.

Opening a sealed file with a hardware-backed key (CLI)

# Insert YubiKey. You may be prompted for PIN.
oversight open --input secret.sealed --output secret.txt \
    --recipient-hw piv:9d

# First op prompts for PIN; subsequent ops within the session don't.

Under the hood, this calls PKCS#11 C_DeriveKey to run ECDH against the on-device private key, then runs the standard Oversight HKDF + AEAD decrypt on the host. The raw private key never leaves the device.

Status: the trait and the protocol-level decrypt path are implemented in oversight-rust/oversight-crypto and oversight-rust/oversight-container as of v0.4.10. The --recipient-hw CLI flag and the underlying PivKeyProvider are the next follow-up.

Revocation

If a device is lost, stolen, or retired:

  1. POST to the registry:
    POST /recipients/{recipient_id}/revoke
    Authorization: Bearer <issuer_token>
    {"reason": "device_lost", "replaced_by": "<new_pubkey_hex>"}
  2. The registry appends a revocation event to the tlog with a qualified RFC 3161 timestamp. Anyone verifying future sealed files addressed to the old pubkey will see the revocation in the event history and reject the file.
  3. Issue new sealed files to the recipient's new pubkey.

Note: the revocation does not un-seal already-delivered ciphertext. Any file the lost device opened before it was lost is already out. Revocation protects against future misuse of the device.

Threat model for hardware-backed keys

What hardware keys defend against

What hardware keys do NOT defend against

Known hardware caveats

Checklist before deploying to real recipients

Further reading