A PIV smart-card (NIST SP 800-73-4) over CCID: X.509 client certificates,
S/MIME, PIV-aware OS login, SSH and age through PKCS#11. Driven with
ykman piv or yubico-piv-tool; the applet also speaks the Yubico extensions
(metadata, serial, attestation, move/delete, set-retries) those tools use. Note
that ykman piv and yubico-piv-tool gate on the "Yubico YubiKey" reader name,
which the default RS-Key build (VID:PID 0x1209:0x0001) does not present — they
need the opt-in VIDPID=Yubikey5 interop build (build.md). The
PKCS#11 / OpenSC and OS-native (macOS CryptoTokenKit, Windows) routes below
identify the card by its applet, not the reader name, so they work on the default
build.
Prereqs: on Linux, pcscd plus the polkit rule from linux.md;
if you also use GnuPG, the disable-ccid line so scdaemon and pcscd stop
fighting over the reader. Check the card is visible (the ykman commands here
assume the opt-in VIDPID=Yubikey5 build):
ykman piv info # PIV version 5.7.4, slot + PIN/PUK/mgmt-key state| Default | Notes | |
|---|---|---|
| PIN | 123456 |
6–8 chars; padded to 8 with 0xFF on the wire |
| PUK | 12345678 |
6–8 chars; unblocks a blocked PIN |
| Management key | 010203040506070801020304050607080102030405060708 |
AES-192, the well-known YubiKey 5.7-era default |
| PIN / PUK retries | 3 / 3 | resets to full on each correct entry |
Change all three before real use:
ykman piv access change-pin
ykman piv access change-puk
ykman piv access change-management-key --generate --protect--protect stores the new management key on the card, encrypted under the PIN,
so ykman can recover it from the PIN alone (no separate hex string to carry).
The applet accepts AES-128/192/256 management keys; under the FIPS-style build
it refuses to set a new 3DES key, though an existing 3DES key still
authenticates so a reflashed device can migrate itself to AES.
On the panel (trusted-display builds). The PIV PIN and PUK can be changed —
and a blocked PIN unblocked with the PUK — on the device, no host needed:
Settings → Security → PIV PIN → Change PIN / Change PUK / Unblock PIN.
Each verifies the current PIN/PUK against the applet's own retry counter (shown
on the pad) and stores the new value in the 8-byte 0xFF-padded wire form, so a
later ykman / yubico-piv-tool VERIFY accepts it.
A 24-byte management key can't be typed on a numeric pad, so the panel sets a
random, PIN-protected one instead — Settings → Security → PIV PIN →
Protect mgmt key. The device generates a random AES-256 management key, seals
it, and marks it PIN-protected (the ykman --protect scheme), so a host then
uses it with just the PIV PIN — ykman piv info shows it as protected and
ykman piv operations no longer need the hex key. Security: once protected,
the PIV PIN alone grants management access (it unlocks the random key), so
treat the PIN accordingly; the panel states this and gates the action behind the
device PIN and a hold. (ykman piv access change-management-key --generate --protect does the same thing from the host.)
The panel manages PINs/PUKs that follow the standard PIV convention — 6–8
digits, padded to 8 bytes with 0xFF — which is what ykman, yubico-piv-tool
and OpenSC all use. A PIN or PUK provisioned outside that convention (e.g.
hand-crafted raw CHANGE REFERENCE DATA / RESET RETRY APDUs that store an
unpadded or sub-6-digit value) can't be verified on the panel; re-set it with
ykman first. The factory defaults follow the convention, so the panel works
out of the box.
The defaults are public. Until you change the PIN, PUK and management key, anyone with physical access can generate, import or delete keys. Treat a default-credential card as unprovisioned.
| Slot | Role | Typical use | Default PIN policy |
|---|---|---|---|
9a |
PIV Authentication | system / domain login, SSH, client TLS | once per session |
9c |
Digital Signature | document & email signing | every operation |
9d |
Key Management | decryption, key agreement (ECDH) | once per session |
9e |
Card Authentication | physical-access / contactless | once per session |
82–95 |
Retired Key Management | 20 slots for old decryption keys | once per session |
9b |
Management Key | admin auth (not an asymmetric key) | — |
f9 |
Attestation | signs slot attestation certs (on-card) | — |
The signature slot (9c) demands the PIN before every private-key
operation; the other slots cache the PIN for the rest of the session after one
VERIFY. 9e carries no special default on this firmware — like the other
non-signature slots it defaults to PIN once per session, so a default-policy
9e key still needs one VERIFY before use. For true card-auth / contactless
no-PIN behaviour, generate the 9e key with an explicit --pin-policy NEVER.
Algorithms. On-card generation and import accept RSA-2048 / 3072 / 4096,
RSA-1024 (disabled under the FIPS-style build, SP 800-131A), ECC P-256 /
P-384, and the Curve25519 pair Ed25519 (signing) and X25519 (key
agreement) — the Yubico 5.7 PIV algorithm ids 0xE0 / 0xE1, so ykman drives
them as --algorithm ED25519 / X25519. An Ed25519 key generates with a
self-signed certificate like the other curves; an X25519 key is key-agreement-only
and can't self-sign, so generation writes no auto-certificate (provision one
from a CA via ykman piv certificates import). RSA-3072/4096 keygen is slow on
this hardware (tens of seconds to a minute-plus).
ykman piv keys generate --algorithm ECCP256 9a pub.pem # on-card key, public part out
ykman piv certificates generate --subject "CN=me" 9a pub.pem # self-signed cert into 9a
ykman piv infoGenerating in a slot already writes a self-signed certificate into that slot's
certificate object, so a GET DATA serves one immediately even before you run
certificates generate. Management-key auth is required to generate.
For a real CA, emit a CSR instead of a self-signed cert:
ykman piv certificates request --subject "CN=me" 9a pub.pem me.csr
# … sign me.csr at your CA, then import the issued cert:
ykman piv certificates import 9a issued.pemOn-card generation means the private key never existed off-device and cannot be exported or backed up — losing the card loses the key (that is the point). RSA generation is slow on this hardware (RSA-2048 takes roughly 4–6 s, and the prime search is random so run-to-run times vary; the device streams CCID keepalives so the connection stays alive — it is not a hang). See limitations.md for the measured dual-core figures. EC generation is instant.
ykman piv keys import 9d existing.pem # PEM with the private key
ykman piv certificates import 9d existing-cert.pemImport is management-key gated and also accepts RSA-2048/1024, P-256/P-384 and Ed25519/X25519. An imported key keeps whatever copy you imported it from — your call which way the trade-off goes. Imported keys cannot be attested (see below): attestation proves on-card generation, which import didn't do.
Both policies are fixed at generate/import time and stored in the slot metadata:
ykman piv keys generate --pin-policy ALWAYS --touch-policy ALWAYS 9a pub.pem--pin-policy |
Effect |
|---|---|
NEVER |
no PIN to use the key |
ONCE |
PIN once per session (default for 9a/9d/9e/retired) |
ALWAYS |
PIN before every operation (default for 9c) |
--touch-policy |
Effect |
|---|---|
NEVER |
no button press (default for the 9b management key) |
ALWAYS |
a physical touch before every private-key operation (default for generated slot keys) |
CACHED |
treated as ALWAYS on this device — see below |
Generated slot keys default to touch ALWAYS: each sign / decrypt / ECDH
needs a button press, declined-touch fails the operation with 6982. The
management key ships touch NEVER so admin provisioning isn't gated; raise it
with ykman piv access change-management-key --touch if you want admin actions
to require a press too.
CACHEDis treated asALWAYS. The device has no wall clock, so it cannot honour the 15-second touch cache a real YubiKey offers; it errs strict and asks every time. If you setCACHED, expectALWAYSbehaviour.
ykman piv keys attest 9a attestation.pemProves a slot key was generated on-device, not imported. The attestation
certificate is signed on-card by the f9 key (a P-384 CA key, self-signed at
first boot) and carries the standard Yubico OIDs — firmware version, device
serial, and the slot's pin/touch policy. Subject/issuer names are
C=ES, O=RS-Key, CN=RS-Key PIV …. Read the f9 CA cert with:
ykman piv certificates export f9 attestation-ca.pemAttestation only works for generated keys; an imported key returns
6A80 / INCORRECT PARAMS (there is nothing to attest). For the FIDO side of
attestation — org-provisioned enterprise attestation — see
attestation.md.
ykman piv 5.7 can move a key (with its certificate and metadata) between
slots, or delete it:
ykman piv keys move 9a 82 # 9a → retired slot 82, cert + metadata follow
ykman piv keys delete 9c # wipe the signature slot's keyA key in a retired slot cannot be moved back into an active slot. Both operations require management-key auth.
The card shows up as a standard PIV token; nothing here is RS-Key-specific.
-
PKCS#11 (browsers, VPNs, SSH,
age): point the app at OpenSC'sopensc-pkcs11.so—/usr/lib/x86_64-linux-gnu/opensc-pkcs11.soon Debian,/usr/lib/opensc-pkcs11.soon many distros, the Nix store path under NixOS (linux.md). -
SSH via PKCS#11:
ssh-keygen -D /usr/lib/opensc-pkcs11.so # print the slot 9a public key ssh -I /usr/lib/opensc-pkcs11.so you@host # log in with it (touch + PIN per policy)
For an
ed25519-skhardware SSH key the FIDO path is simpler — see ssh.md. PIV-over-PKCS#11 is the route when you need an RSA or NIST-curve key, a smart-card-login certificate, or a server that wants a real X.509 chain. -
ageencryption:age-plugin-yubikeydrives PIV slots directly for identity files but, likeykman, keys off the "Yubico YubiKey" reader name, so it wants the opt-inVIDPID=Yubikey5build; on the default RS-Key build use any PKCS#11-awareagebuild againstopensc-pkcs11.so. -
ECDH / key agreement (
9dand retired slots, P-256/P-384 and X25519):ykman piv ...exposes it (ykman piv keys calculate-secretfor X25519); at the wire level it is GENERAL AUTHENTICATE with tag0x85, the operationyubico-piv-tooland OpenSC use for decryption. -
Windows / macOS native smart-card stacks pick the PIV applet up as-is; macOS CryptoTokenKit binds its
pivtoken.appexto the reader (interop.md).
PIV private keys are stored AES-256-GCM-sealed under the device root (the
sealed blob is nonce ‖ ciphertext ‖ tag, authenticated against the device
serial). Once the OTP master key is fused, a flash dump does
not yield key material; before that burn the seal's root derives from
on-chip state an attacker with the flash and chip could reconstruct, so at-rest
protection is only meaningful after provisioning (see
threat-model.md). The seal is bound to the device, not the
slot, so a keys move re-homes the blob verbatim — no re-encryption.
ykman piv resetWipes PIV keys, certificates and PINs only; the other applets are untouched.
The reset is only accepted once both the PIN and the PUK are blocked —
ykman blocks them for you first. To wipe every applet at once (PIV included),
use rsk offboard, which blocks PIN+PUK then resets PIV as part of a full-device
wipe with a signed receipt — see fleet.md.
There is no rsk piv command group: PIV is provisioned entirely through
ykman piv / yubico-piv-tool / PKCS#11, with rsk only involved for a
whole-device offboard.
ykmancan't connect → linux.md (pcscd + polkit + thedisable-ccidscdaemon note).ykmanstops seeing the card aftergpgused it →scdaemongrabbed the raw CCID interface; applydisable-ccidandgpgconf --kill scdaemon(openpgp.md).- PIN blocked →
ykman piv access unblock-pin(needs the PUK). PUK blocked too → onlyykman piv resetrecovers, and it wipes the slots. ykman piv keys attestfails withINCORRECT PARAMS→ the key in that slot was imported, not generated; attestation is generated-keys-only.change-management-keyrejects 3DES on the FIPS-style build → expected; set an AES-128/192/256 key instead.- RSA-2048 generate takes a few seconds (≈ 4–6 s, occasionally longer since the prime search is random) → that's the prime search on this hardware, not a hang; the device keeps the CCID connection alive with keepalives.