PKCS#11 (PKCS#11)
PKCS#11 is a standardized interface - think of it as a universal adapter - that lets software applications talk to hardware security devices without knowing the device’s internal details. When you tell SSH to use your YubiKey, SSH does not talk to the YubiKey directly. Instead, it calls through the PKCS#11 interface, which OpenSC implements. OpenSC knows how to talk to the YubiKey; SSH just needs to know the PKCS#11 standard. The result: SSH asks “please sign this challenge,” the message goes through PKCS#11 to OpenSC to the YubiKey, the YubiKey signs it inside the chip, and the signed result comes back. The private key never leaves the hardware.
PKCS#11 (also called Cryptoki - Cryptographic Token Interface) is defined by the OASIS PKCS#11 Technical Committee (current version: 3.1). It specifies a C API for interacting with cryptographic tokens (smart cards, HSMs, USB tokens).
Core object model:
- Slot: A physical interface that can hold a token (e.g., a USB port with a YubiKey inserted)
- Token: The cryptographic device in the slot (the YubiKey itself)
- Session: A stateful connection to a token, either read-only or read-write
- Object: Data stored on the token: certificates, public keys, private keys, data
Key operation flow (signing):
- Application loads the PKCS#11 module (
.so/.dylib/.dll) withC_Initialize - Calls
C_GetSlotListto find available tokens - Opens a session with
C_OpenSession - Authenticates with
C_Login(supplies PIN) - Calls
C_SignInitspecifying the mechanism (e.g.,CKM_ECDSA) - Calls
C_Signwith the data to sign - Token performs the signing internally, returns the signature
- Application calls
C_CloseSession/C_Finalize
The private key object on the token has CKA_SENSITIVE = true and CKA_EXTRACTABLE = false. The PKCS#11 specification guarantees these attributes cannot be changed, and C_GetAttributeValue will never return the private key bytes.
Common PKCS#11 providers:
| Provider | Platform | Devices |
|---|---|---|
OpenSC (opensc-pkcs11.so) | Linux, macOS | YubiKey PIV, most smart cards |
ykcs11 (libykcs11.so) | Linux, macOS | YubiKey only (Yubico’s own) |
| SoftHSM2 | Linux, macOS | Software HSM (testing) |
| Windows CNG PKCS#11 | Windows | Platform keys, smart cards |
| AWS CloudHSM | Cloud | Managed HSM |
Using PKCS#11 with SSH:
# List available keys on the token
ssh-keygen -D /opt/homebrew/lib/pkcs11/opensc-pkcs11.so
# Use PKCS#11 provider for a specific host
# In ~/.ssh/config:
# PKCS11Provider /opt/homebrew/lib/pkcs11/opensc-pkcs11.so
# Load into SSH agent (Homebrew ssh-agent, not Apple's)
ssh-add -s /opt/homebrew/lib/pkcs11/opensc-pkcs11.somacOS restriction:
Apple’s built-in ssh-agent rejects third-party PKCS#11 providers due to Library Validation (code signing enforcement). Only providers with an Apple code signature are allowed. Homebrew’s ssh-agent accepts a provider whitelist via the -P flag, which is why homelab Bastion workflows require spawning a separate agent.
PKCS#11 is the reason a YubiKey can work with SSH, Firefox, LibreOffice, OpenVPN, and dozens of other applications without any of those applications being specifically written for YubiKey. They all speak PKCS#11; OpenSC acts as the translator for the hardware. In enterprise environments, PKCS#11 modules are how corporate smart card readers integrate with email clients for S/MIME signing, and how VPN clients authenticate using client certificates stored on hardware tokens. For homelab operators, the main friction point is macOS’s Library Validation, which requires running a separate Homebrew SSH agent for Bastion agent forwarding. Once that pattern is in place (the bastion() shell function), the PKCS#11 layer is largely transparent - enter PIN, touch key, done.