| 1 | # Hardware Security Keys for Oversight |
| 2 | |
| 3 | Vendor-neutral guide for storing Oversight recipient private keys on a hardware |
| 4 | device (YubiKey, OnlyKey, Nitrokey) rather than a disk file. |
| 5 | |
| 6 | ## Why |
| 7 | |
| 8 | When a recipient's `.key` file lives on disk, full compromise of that |
| 9 | recipient's laptop gives an attacker the private key forever. That attacker |
| 10 | can decrypt every sealed file addressed to that recipient, past and future, |
| 11 | with no way to tell the issuer it happened. |
| 12 | |
| 13 | A hardware-backed key eliminates this. The private key is generated inside |
| 14 | the device's secure element and never leaves it. All ECDH (X25519) and |
| 15 | signing (Ed25519) operations happen on-device. The host OS gets ECDH |
| 16 | outputs, never the raw key. To decrypt, an adversary needs physical |
| 17 | possession of the device - and typically a touch, PIN, or biometric. |
| 18 | |
| 19 | This doesn't give you enclave-grade guarantees (a compromised client |
| 20 | running while the YubiKey is plugged in can still open files via the device). |
| 21 | What it does give you: |
| 22 | |
| 23 | - **Vendor-neutral** - any FIDO2 / PIV device works. |
| 24 | - **Theft is discrete** - physical device loss is noticeable; disk theft may not be. |
| 25 | - **Revocation is simple** - deauthorize the device's pubkey in the registry. |
| 26 | - **Works offline** - no cloud service. |
| 27 | - **No recurring cost** - $50-$80 once. |
| 28 | |
| 29 | ## Supported devices |
| 30 | |
| 31 | Any device exposing **PIV** (Personal Identity Verification, PKCS#11-compatible) |
| 32 | slots works. Tested: |
| 33 | |
| 34 | | Device | Cost (USD) | PIV slots | Notes | |
| 35 | |---|---|---|---| |
| 36 | | YubiKey 5C NFC | ~$75 | yes | Most tested; widely available | |
| 37 | | YubiKey 5 NFC | ~$55 | yes | USB-A version | |
| 38 | | YubiKey Security Key NFC | ~$29 | FIDO2 only | Cheapest but limited | |
| 39 | | Nitrokey 3 NFC | ~$80 | yes | Fully open-source firmware | |
| 40 | | OnlyKey | ~$50 | yes | Open hardware + firmware | |
| 41 | |
| 42 | Recommendation: **YubiKey 5C NFC** for most users (best tooling), **Nitrokey |
| 43 | 3** if firmware openness matters more than ecosystem support. |
| 44 | |
| 45 | ## First-time setup |
| 46 | |
| 47 | ### 1. Install the tooling |
| 48 | |
| 49 | ```bash |
| 50 | # Debian / Ubuntu |
| 51 | sudo apt install yubikey-manager pcscd opensc |
| 52 | sudo systemctl enable --now pcscd |
| 53 | |
| 54 | # macOS |
| 55 | brew install yubikey-manager opensc |
| 56 | |
| 57 | # Arch |
| 58 | sudo pacman -S yubikey-manager opensc ccid |
| 59 | ``` |
| 60 | |
| 61 | ### 2. Verify the device is seen |
| 62 | |
| 63 | ```bash |
| 64 | ykman info |
| 65 | # Should print serial, firmware version, and enabled applications. |
| 66 | |
| 67 | pkcs11-tool --list-slots --module /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so |
| 68 | # Should list the YubiKey as slot 0. |
| 69 | ``` |
| 70 | |
| 71 | ### 3. Set a PIN and management key |
| 72 | |
| 73 | **Do not skip this.** The factory defaults (PIN 123456, PUK 12345678) are |
| 74 | publicly known. Change both now. |
| 75 | |
| 76 | ```bash |
| 77 | # PIV PIN (6-8 digits) |
| 78 | ykman piv access change-pin |
| 79 | |
| 80 | # PIV PUK (used to unblock if you lock yourself out) |
| 81 | ykman piv access change-puk |
| 82 | |
| 83 | # Management key (used for admin ops; 24-byte hex) |
| 84 | ykman piv access change-management-key --generate --protect |
| 85 | # --protect stashes the new key in PIV slot so you don't need to manage it |
| 86 | ``` |
| 87 | |
| 88 | ### 4. Generate an Oversight recipient key on-device |
| 89 | |
| 90 | PIV has four main slots. Use **slot 9d (Key Management)** for Oversight - it's |
| 91 | meant for decryption operations and doesn't require PIN on every use (only |
| 92 | first use per session, via cached auth). |
| 93 | |
| 94 | ```bash |
| 95 | # Generate an ECC P-256 key in slot 9d |
| 96 | ykman piv keys generate 9d --algorithm ECCP256 - |
| 97 | # Note: P-256, not Curve25519. See "Curve choice" below. |
| 98 | |
| 99 | # Self-sign a cert so PIV treats the slot as initialized |
| 100 | ykman piv certificates generate 9d \ |
| 101 | --subject "CN=oversight-recipient" \ |
| 102 | --valid-days 3650 - |
| 103 | ``` |
| 104 | |
| 105 | ### 5. Export the public key in Oversight format |
| 106 | |
| 107 | Oversight identities are JSON. We need to convert the PIV slot's public key |
| 108 | to the format Oversight uses. |
| 109 | |
| 110 | ```bash |
| 111 | # Export the cert, extract the pubkey |
| 112 | ykman piv certificates export 9d - | \ |
| 113 | openssl x509 -pubkey -noout -in - | \ |
| 114 | openssl ec -pubin -text -noout |
| 115 | ``` |
| 116 | |
| 117 | Write the resulting pubkey hex into an Oversight identity file with a |
| 118 | `hardware: true` marker: |
| 119 | |
| 120 | ```json |
| 121 | { |
| 122 | "hardware": true, |
| 123 | "provider": "piv", |
| 124 | "piv_slot": "9d", |
| 125 | "x25519_pub_equivalent": "<hex>", |
| 126 | "ed25519_pub": null, |
| 127 | "device_serial": "<yubikey-serial>" |
| 128 | } |
| 129 | ``` |
| 130 | |
| 131 | The `x25519_pub_equivalent` is the P-256 pubkey. Oversight's hardware mode |
| 132 | uses **P-256 ECDH** instead of X25519 for this recipient, because P-256 is |
| 133 | what PIV supports natively (Curve25519 PIV support exists but is limited - |
| 134 | see below). |
| 135 | |
| 136 | ## Curve choice: why P-256 for hardware-backed recipients |
| 137 | |
| 138 | The default Oversight suite uses X25519 for key agreement. PIV-compatible |
| 139 | hardware devices historically only supported P-256 and P-384 for PIV slots. |
| 140 | YubiKey 5.7+ firmware does support Curve25519 via a dedicated OpenPGP |
| 141 | applet, but PIV itself does not. |
| 142 | |
| 143 | To stay compatible with the broadest set of devices (Nitrokey, OnlyKey, |
| 144 | older YubiKeys), Oversight uses **P-256 ECDH** for hardware-backed |
| 145 | recipients. The suite identifier in the manifest becomes `OSGT-HW-P256-v1` |
| 146 | instead of `OSGT-CLASSIC-v1`. The crypto is just as strong - P-256 ECDH |
| 147 | is NIST-standardized, FIPS 140-3 compliant, and battle-tested. |
| 148 | |
| 149 | Open clients that want to decrypt for hardware-backed recipients must |
| 150 | support both suites. The default file-backed provider stays on X25519. |
| 151 | |
| 152 | ## Opening a sealed file with a hardware-backed key (CLI) |
| 153 | |
| 154 | ```bash |
| 155 | # Insert YubiKey. You may be prompted for PIN. |
| 156 | oversight open --input secret.sealed --output secret.txt \ |
| 157 | --recipient-hw piv:9d |
| 158 | |
| 159 | # First op prompts for PIN; subsequent ops within the session don't. |
| 160 | ``` |
| 161 | |
| 162 | Under the hood, this calls PKCS#11 `C_DeriveKey` to run ECDH against the |
| 163 | on-device private key, then runs the standard Oversight HKDF + AEAD decrypt |
| 164 | on the host. The raw private key never leaves the device. |
| 165 | |
| 166 | ## Revocation |
| 167 | |
| 168 | If a device is lost, stolen, or retired: |
| 169 | |
| 170 | 1. POST to the registry: |
| 171 | ``` |
| 172 | POST /recipients/{recipient_id}/revoke |
| 173 | Authorization: Bearer <issuer_token> |
| 174 | {"reason": "device_lost", "replaced_by": "<new_pubkey_hex>"} |
| 175 | ``` |
| 176 | 2. The registry appends a revocation event to the tlog with a qualified |
| 177 | RFC 3161 timestamp. Anyone verifying future sealed files addressed to |
| 178 | the old pubkey will see the revocation in the event history and reject |
| 179 | the file. |
| 180 | 3. Issue new sealed files to the recipient's new pubkey. |
| 181 | |
| 182 | Note: the revocation does NOT un-seal already-delivered ciphertext. Any file |
| 183 | the lost device opened before it was lost is already out. Revocation |
| 184 | protects against *future* misuse of the device. |
| 185 | |
| 186 | ## Threat model for hardware-backed keys |
| 187 | |
| 188 | **What hardware keys defend against:** |
| 189 | - Recipient laptop fully compromised, attacker has root, keylogger running: |
| 190 | attacker cannot exfiltrate the private key. Can only ECDH while device is |
| 191 | plugged in. Discrete events. |
| 192 | - Recipient's encrypted laptop is stolen while powered off. Attacker brute- |
| 193 | forces disk. Gets nothing useful because the PIV key is on the YubiKey. |
| 194 | - Malware on recipient's machine installs a background decryption job. |
| 195 | Hardware-backed means each ECDH requires the device to be plugged in and |
| 196 | (optionally) a touch. Attacker can't do it passively. |
| 197 | |
| 198 | **What hardware keys do NOT defend against:** |
| 199 | - Recipient's laptop compromised WHILE YubiKey is plugged in. Attacker can |
| 200 | call PKCS#11 to do ECDH against any file the legitimate client could. |
| 201 | Mitigation: require touch-to-decrypt (YubiKey PIV policy `always-require`). |
| 202 | - Physical theft of both laptop + YubiKey. Attacker has everything needed. |
| 203 | Mitigation: strong PIN; device auto-locks after N wrong PINs. |
| 204 | - A supply-chain-compromised YubiKey. Vendor-independence is the only |
| 205 | mitigation - and is why Oversight supports Nitrokey / OnlyKey alongside. |
| 206 | |
| 207 | ## Known hardware caveats |
| 208 | |
| 209 | - **PIV key operations count against the device's attempt counter.** YubiKey |
| 210 | PIV defaults to 3 attempts before locking. Set a reasonable limit and |
| 211 | keep a PUK to recover. |
| 212 | - **Touch policy trade-off.** `always-require-touch` is more secure but |
| 213 | requires user interaction on every open. `cached` touches (one per |
| 214 | session) is the usual compromise. |
| 215 | - **No post-quantum yet.** Current hardware keys don't support ML-KEM / |
| 216 | ML-DSA. Hardware-backed recipients are CLASSIC-only for now. For PQ |
| 217 | protection, use a file-backed recipient with a PQ suite, or wait for |
| 218 | hardware-native ML-KEM support (YubiKey and Nitrokey have hinted at |
| 219 | late-2026 / 2027 firmware). |
| 220 | |
| 221 | ## Checklist before deploying to real recipients |
| 222 | |
| 223 | - [ ] PIN and PUK changed from factory defaults. |
| 224 | - [ ] Management key rotated. |
| 225 | - [ ] Touch policy decided (always vs cached vs never). |
| 226 | - [ ] Device serial recorded in a separate, encrypted inventory. |
| 227 | - [ ] Recovery procedure documented (if device lost, who is notified and how). |
| 228 | - [ ] Backup strategy: issue each recipient TWO devices (primary + backup), |
| 229 | register BOTH pubkeys, seal to both, store backup in a safe. |
| 230 | - [ ] Revocation playbook tested end-to-end on a test recipient. |
| 231 | |
| 232 | ## Further reading |
| 233 | |
| 234 | - YubiKey PIV documentation: https://developers.yubico.com/PIV/ |
| 235 | - NIST SP 800-73-4 (PIV Interfaces): https://csrc.nist.gov/pubs/sp/800/73/4 |
| 236 | - Nitrokey 3 PIV: https://docs.nitrokey.com/nitrokey3/ |