Building a Zero-Trust Homelab with YubiKey PIV
Hardware-backed authentication for your homelab using YubiKey PIV smart cards, a private CA, SSH hardening, digital signing, and a Bastion server. No cloud dependencies. No subscriptions. Full control.
Most homelabs authenticate with passwords or SSHA cryptographic protocol for secure remote login, command execution, and file transfer over an unsecured network. Read more → key files sitting on disk. Both are software secrets. If your machine is compromised, those secrets walk out with it. A hardware security tokenA physical device with a tamper-resistant chip that generates and stores cryptographic keys. The private key never leaves the hardware, making it immune to software-based credential theft. Read more → changes the equation: the private key is generated on the chip and never leaves it. Authentication requires physical possession and a PIN. No software exploit can extract what never existed on disk.
Concepts at a Glance
New to hardware tokens or PKIPublic Key Infrastructure - the complete system of policies, roles, hardware, software, and procedures needed to create, manage, distribute, and revoke digital certificates and manage public-key encryption. Read more →? Read this once. It is the “why” behind every command below.
What You Will Build
By the end of this guide, you will have:
- A private Certificate AuthorityA trusted entity that issues and signs digital certificates, cryptographically vouching that a public key belongs to a specific identity. The foundation of PKI trust chains. Read more → (CA) issuing X.509 certificates
- YubiKey PIVPersonal Identity Verification - a US government standard (FIPS 201) for smart card authentication. Defines a set of key slots on a chip for different cryptographic purposes: login, signing, and encryption. Read more → slots programmed with CA-signed certificates
- macOS smart card login using the YubiKey
- SSH authentication to all Linux servers using PIV certificates
- Digital document signing with cryptographic proof
- A hardened Bastion serverA hardened server that acts as the single, controlled entry point for SSH access to an internal network. All connections to internal hosts must route through the Bastion, creating one audited choke point. Read more → as the single entry point for all SSH access
- Agent forwarding through the Bastion using the YubiKey
- Password authentication disabled across all servers
- A backupA copy of data stored separately from the original, used to restore systems and files after data loss from hardware failure, deletion, ransomware, or disaster. Read more → YubiKey enrolled for redundancy
Prerequisites
Hardware:
- Two YubiKey 5 series tokens (one primary, one backup). USB-C recommended for modern MacBooks.
- A server or VMA software-based emulation of a complete computer that runs its own operating system and applications, isolated from the host hardware. Read more → to run the CA (any Linux host with DockerA platform that packages applications into containers, providing a standardized way to build, ship, and run software consistently across any environment. Read more → or bare metal)
- A Bastion/jump server (can be a lightweight VM or container)
Software (macOS):
- Homebrew
ykman(YubiKey Manager CLI):brew install ykman- Yubico Authenticator (GUI):
brew install --cask yubico-authenticator stepCLI (Smallstep):brew install step- OpenSC:
brew install opensc - Homebrew OpenSSH:
brew install openssh(required for PKCS#11 agent forwarding) - NSS tools:
brew install nss(for document signing with LibreOffice)
Network:
- A dedicated VLAN for the CA (recommended, not required)
- Firewall rules to restrict access to the CA and Bastion
Required knowledge:
- Basic Linux command line (navigating directories, running commands, editing files with nano/vim)
- What SSH is and how to connect to a remote server
- Basic networking concepts (IP addresses, VLANs, ports)
Affiliate link. BytesNation earns a small commission at no extra cost to you.
Part 1: Building the Certificate Authority
Your CA is a certificate vending machine. You bring it a public key, it stamps a certificate on it saying “I vouch for this key.” Once the certificate is loaded onto your YubiKey, the CA can sit offline. It is not in the authentication path at all; it only needs to come online when you issue, renew, or revoke certificates. No third-party dependency. No subscription. No certificate chain touching the internet. You control who gets issued, for how long, and you can revoke instantly.
Installing step-ca
Smallstep’s step-ca is a lightweight, open source CA that runs as a single binary. It handles all the complexity of running a two-tier PKI in a simple CLI.
Add the Smallstep apt repository on your CA server (Debian/Ubuntu):
apt-get update && apt-get install -y --no-install-recommends curl gpg ca-certificates
curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg -o \
/etc/apt/keyrings/smallstep.asc
cat << 'EOF' > /etc/apt/sources.list.d/smallstep.sources
Types: deb
URIs: https://packages.smallstep.com/stable/debian
Suites: debs
Components: main
Signed-By: /etc/apt/keyrings/smallstep.asc
EOF
apt-get update && apt-get -y install step-cli step-ca
Verify the installation:
step version && step-ca version
Initializing the CA
step ca init
You will be prompted for:
- Deployment Type: Standalone
- PKI Name: Choose a name (e.g., “HomeLab CA”)
- DNS names or IP addresses: The CA server’s hostname or IP
- Bind address:
:443 - Provisioner name: Your email or admin identity
- Password: This protects your CA keys. Store it securely offline.
This created a two-tier PKI. A Root CA and an Intermediate CA were generated. The Root CA signed the Intermediate CA’s certificate, and the Intermediate is what will actually sign your YubiKey certificates. Why two tiers? The Root CA stays offline after this - only the Intermediate is exposed. If the Intermediate is ever compromised, you revoke it and issue a new one from the Root. Your Root is never at risk.
Save the root fingerprint printed at the end - you will need it when you set up client machines to trust your CA.
Extending Certificate Lifetimes
The default certificate lifetime is 24 hours. That is designed for short-lived automated certificates, not hardware tokens you want to use for a year. Update the provisioner:
step ca provisioner update your-provisioner@email.com \
--x509-max-dur=8760h \
--x509-default-dur=8760h
8760 hours is one year. Adjust to your preference.
Running as a Service
Save the CA password for unattended startup:
echo 'YOUR_PASSWORD' > /path/to/step/secrets/password.txt
chmod 600 /path/to/step/secrets/password.txt
Create a systemd unit so the CA starts automatically on reboot:
cat << 'EOF' > /etc/systemd/system/step-ca.service
[Unit]
Description=Smallstep CA Server
After=network.target
[Service]
Type=simple
User=root
ExecStart=/usr/bin/step-ca /path/to/step/config/ca.json \
--password-file /path/to/step/secrets/password.txt
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable step-ca
systemctl start step-ca
Verify it started cleanly:
systemctl status step-ca
step ca health
Bootstrapping Clients
“Bootstrapping” means teaching your Mac to trust your private CA. Without this step, your Mac will see certificates from your CA and reject them as untrusted.
On your workstation (macOS), run:
step ca bootstrap \
--ca-url https://YOUR_CA_IP \
--fingerprint YOUR_ROOT_FINGERPRINT
Then install the root cert into the macOS system trust store:
step certificate install $(step path)/certs/root_ca.crt
Your Mac now trusts any certificate issued by your CA. This is the same process companies use to deploy internal CAs to corporate laptops - you are doing the same thing for your homelab.
Part 2: Programming the YubiKey
The YubiKey has several applications on it (OTP, FIDO2, OpenPGP, PIV). This guide uses the PIV application. PIV gives you dedicated key slots, each designed for a specific purpose.
Understanding PIV Slots
Different operations have different security requirements. Logging in (9a) is something you do frequently, so you PIN once per session. Signing a document (9c) is a deliberate act, so it asks for a PIN every single time. The slot design enforces these policies at the hardware level.
| Slot | Purpose | PIN Policy | Use Case |
|---|---|---|---|
| 9a | Authentication | Once per session | System login, SSH, VPN |
| 9c | Digital Signature | Every use | Signing documents, code, emails |
| 9d | Key Management | Configurable | Encryption/decryption |
| 9e | Card Authentication | Never | Physical access, badge tap |
The private key is generated on the YubiKey’s chip and never leaves it.
Initial YubiKey Setup
Change the default PIN (123456) and PUK (12345678). The PUK is a backup code used to unblock a locked PIN - treat it like a recovery key:
ykman piv access change-pin
ykman piv access change-puk
Generate the CHUID and CCC objects (metadata structures macOS requires to recognize the card as a smart card):
ykman piv objects generate chuid
ykman piv objects generate ccc
Secure the management key by setting it to PIN-protected mode:
ykman piv access change-management-key --protect
The management key controls administrative operations on the PIV application (like importing certs or generating keys). By running this command, a random AES key is generated and stored on the YubiKey itself, locked behind your PIN. You never need to know or remember it - the YubiKey handles it. This prevents someone who finds your YubiKey from reprogramming its slots without your PIN.
Programming Slot 9a (Authentication)
Generate a key pair directly on the YubiKey chip:
ykman piv keys generate \
--algorithm ECCP256 \
--pin-policy ONCE \
--touch-policy ALWAYS \
9a ~/yubikey-pub.pem
ECCP256: Elliptic Curve P-256. A modern, efficient algorithm. Equivalent security to a 3072-bit RSA key but much faster.--pin-policy ONCE: You enter your PIN once when you plug in the YubiKey. Subsequent operations in that session do not re-prompt.--touch-policy ALWAYS: You must physically touch the gold disc on the YubiKey for every authentication. This is the anti-remote-exploit protection.
The command exports the public key only to ~/yubikey-pub.pem. The private key was generated inside the chip and has never been on your filesystem.
Create a Certificate Signing Request (CSR) - essentially an application to your CA asking it to certify this public key:
ykman piv certificates request \
--subject "CN=Your Name,O=Your Org" \
9a ~/yubikey-pub.pem ~/yubikey.csr
Sign it with your CA (this is the CA doing its job - stamping the cert):
step ca sign --not-after=8760h ~/yubikey.csr ~/yubikey.crt
Import the signed certificate back onto the YubiKey:
ykman piv certificates import 9a ~/yubikey.crt
Verify it looks correct:
ykman piv certificates export 9a - | step certificate inspect
Clean up - these files are no longer needed. The private key never left the chip:
rm ~/yubikey-pub.pem ~/yubikey.csr ~/yubikey.crt
Programming Slot 9c (Digital Signature)
Same workflow, different PIN policy:
ykman piv keys generate \
--algorithm ECCP256 \
--pin-policy ALWAYS \
--touch-policy ALWAYS \
9c ~/yubikey-9c-pub.pem
ykman piv certificates request \
--subject "CN=Your Name,O=Your Org" \
9c ~/yubikey-9c-pub.pem ~/yubikey-9c.csr
step ca sign --not-after=8760h ~/yubikey-9c.csr ~/yubikey-9c.crt
ykman piv certificates import 9c ~/yubikey-9c.crt
rm ~/yubikey-9c-pub.pem ~/yubikey-9c.csr ~/yubikey-9c.crt
Note --pin-policy ALWAYS for slot 9c. Every signature requires explicit PIN entry because each signature is a deliberate act: you are cryptographically attesting “I signed this.”
Part 3: macOS Smart Card Login
macOS has built-in support for PIV smart cards through a framework called CryptoTokenKit. When you plug in a YubiKey, macOS can use its certificates for system login - the same mechanism large enterprises use for CAC/PIV card login on government machines. You are building the same thing for your homelab.
Pairing the YubiKey
Verify macOS detects the YubiKey:
system_profiler SPSmartCardsDataType
You should see the YubiKey listed as a reader with the PIV token driver loaded.
List the identities (certificates) macOS sees on the card:
sc_auth identities
If the cert does not appear, unplug and replug the YubiKey to force CryptoTokenKit to re-enumerate.
Pair the authentication cert to your macOS user account. The cert hash is the hex string shown by sc_auth identities:
sudo sc_auth pair -u YOUR_USERNAME -h YOUR_CERT_HASH
Touch the YubiKey when prompted. Verify the pairing succeeded:
sc_auth list -u YOUR_USERNAME
Setting the Auth Policy
Enable smart card authentication while keeping password/biometric as a fallback:
sudo defaults write /Library/Preferences/com.apple.security.smartcard \
allowSmartCard -bool true
sudo defaults write /Library/Preferences/com.apple.security.smartcard \
allowUnmappedUsers -int 0
Do not enforce smart card only until you have a second YubiKey enrolled as a backup. If you enforce with one key and lose it, you are locked out of your own machine.
Testing
Lock your screen (Ctrl+Cmd+Q). With the YubiKey plugged in, you should see a PIN prompt. Enter your PIV PIN, touch the YubiKey. You are in.
Pull the YubiKey out. The login screen reverts to password/Touch ID. This confirms your fallback works and you are not yet locked out without the key.
Part 4: SSH Authentication
Now you use the same key on slot 9a to authenticate SSH sessions. Instead of a key file on disk, your Mac asks the YubiKey to sign the SSH challenge. The private key never leaves the chip.
Extracting the SSH Public Key
OpenSC exposes the YubiKey through a PKCS#11 module. SSH can ask that module for the public key:
ssh-keygen -D /opt/homebrew/lib/pkcs11/opensc-pkcs11.so
This outputs the public keys from all populated PIV slots. The 9a key is labeled PIV AUTH pubkey. The “failed to fetch key” messages for empty slots are normal - just ignore them.
Copy the full line starting with ecdsa-sha2-nistp256.
Deploying to Servers
On each target Linux server, add the public key to the authorized keys file:
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo 'YOUR_ECDSA_PUBLIC_KEY PIV AUTH pubkey' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
You are doing the same thing you would with a regular SSH key - putting the public key in authorized_keys. The difference is the corresponding private key is on a hardware chip, not a file. The server does not care; it just verifies the signature.
SSH Config
Tell SSH to use the PKCS#11 provider for specific hosts. Add to your ~/.ssh/config:
Host myserver
HostName x.x.x.x
User myuser
PKCS11Provider /opt/homebrew/lib/pkcs11/opensc-pkcs11.so
Test the connection:
ssh myserver
You will be prompted for your PIV PIN and then a physical touch on the YubiKey. Verify on the server that YubiKey auth was used (not a password or key file):
journalctl -u ssh --no-pager -n 5
Look for Accepted publickey with ECDSA to confirm YubiKey auth was used.
SSH Agent Conflicts
If SSH uses your ed25519 key on disk instead of the YubiKey, that key is cached in the ssh-agent and being offered first. Remove it:
ssh-add -d ~/.ssh/id_ed25519
For hosts where you want YubiKey-only auth, do not include IdentityFile or IdentitiesOnly in the SSH config block for those hosts.
Part 5: Digital Document Signing
Slot 9c exists for signing. When you sign a document with it, the signature proves two things: this document came from the holder of this private key, and it has not been modified since signing. The YubiKey provides cryptographic non-repudiation - you cannot later claim you did not sign it.
Command Line Signing
Sign a file:
pkcs11-tool \
--module /opt/homebrew/lib/pkcs11/opensc-pkcs11.so \
--sign --slot 0 --id 02 \
--mechanism ECDSA-SHA256 \
--input-file ~/document.txt \
--output-file ~/document.sig \
--login
Verify the signature (anyone with your public certificate can do this):
pkcs11-tool \
--module /opt/homebrew/lib/pkcs11/opensc-pkcs11.so \
--verify --slot 0 --id 02 \
--mechanism ECDSA-SHA256 \
--input-file ~/document.txt \
--signature-file ~/document.sig
PDF Signing with LibreOffice
LibreOffice uses Firefox’s NSS certificate database. Register the PKCS#11 module:
modutil \
-add "OpenSC" \
-libfile /opt/homebrew/lib/pkcs11/opensc-pkcs11.so \
-dbdir "YOUR_FIREFOX_PROFILE_PATH"
Import your CA root cert to eliminate trust warnings:
certutil -A \
-n "Your CA Name" \
-t "CT,C,C" \
-i /path/to/root_ca.crt \
-d "YOUR_FIREFOX_PROFILE_PATH"
Then in LibreOffice: File > Digital Signatures > Digital Signatures > Sign Document. Select your cert, enter PIN, touch the YubiKey. The signature is embedded in PAdES format directly in the PDF.
Part 6: The Bastion Server
Why a Bastion
Right now, your workstation can SSH directly to every server. That means every server is exposed to your workstation, and if your workstation is compromised, an attacker can move laterally to every machine they can reach from it.
A Bastion (jump host) creates a single choke point. Your workstation can only SSH to the Bastion. Internal servers only accept SSH from the Bastion. To reach any internal machine, you have to deliberately enter the Bastion first - that is the “intent” in intent-based access. One hop, one audit point, one thing to harden.
Hardening the Bastion
Create a hardened sshd config at /etc/ssh/sshd_config.d/99-bastion.conf:
# Authentication
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Disable legacy auth methods
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no
# Reduce attack surface
X11Forwarding no
PermitTunnel no
AllowAgentForwarding yes
# Session hygiene
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 2
# Logging
LogLevel VERBOSE
AllowAgentForwarding yes is critical. Without it, your YubiKey cannot authenticate the second hop from the Bastion to an internal server. The agent forwarding is what carries the signing request back to your physical key.
The Dual-Agent Problem (macOS)
This is the trickiest part of the whole guide. Here is what is happening:
When you SSH from the Bastion to an internal server, the internal server sends a signing challenge back through the tunnel to the Bastion, which forwards it to your workstation’s SSH agent, which asks the YubiKey to sign it. This is agent forwarding - your key never leaves your desk, but requests travel to it through the tunnel.
The problem: Apple ships its own ssh-agent which, for security reasons, refuses to load third-party PKCS#11 providers like OpenSC. So Apple’s agent cannot load the YubiKey for forwarding. But you cannot just disable Apple’s agent - VS Code Remote SSH, Claude Code, git, and macOS Keychain all depend on it.
The solution: keep Apple’s agent for everyday use. Spin up Homebrew’s ssh-agent temporarily only when you need the Bastion.
The bastion() Function
Add this to your ~/.zshrc:
function bastion() {
export ORIG_SSH_AUTH_SOCK="$SSH_AUTH_SOCK"
eval $(/opt/homebrew/bin/ssh-agent -s -P \
'/opt/homebrew/lib/pkcs11/*,/opt/homebrew/Cellar/opensc/*/lib/*.so')
ssh-add -s /opt/homebrew/lib/pkcs11/opensc-pkcs11.so
ssh -A jump
export SSH_AUTH_SOCK="$ORIG_SSH_AUTH_SOCK"
}
What each line does:
- Save Apple’s agent socket so you can restore it when you exit
- Start Homebrew’s ssh-agent, whitelisting the OpenSC PKCS#11 provider
- Load the YubiKey into Homebrew’s agent (prompts for PIV PIN)
- SSH to the Bastion with agent forwarding (
-A) - When you type
exit, restore Apple’s agent
Daily workflow: type bastion, enter PIN, touch YubiKey, you are on the Bastion. From there, ssh server-a, touch YubiKey for each hop. Type exit when done; Apple’s agent is restored automatically.
Agent Whitelist
The -P flag tells Homebrew’s ssh-agent which PKCS#11 providers are allowed. You need both:
/opt/homebrew/lib/pkcs11/*(the symlink path)/opt/homebrew/Cellar/opensc/*/lib/*.so(the resolved real path)
Without both, the agent resolves the symlink and rejects the real path as “provider not allowed.” This trips up almost everyone the first time.
Bastion SSH Config
On the Bastion, create ~/.ssh/config for easy access to internal hosts:
Host server-a
HostName 10.x.x.x
User myuser
Host server-b
HostName 10.x.x.x
User myuser
From the Bastion: ssh server-a, touch YubiKey, you are in.
Authentication Flow
This is what actually happens when you type bastion and then ssh server-a:
First hop (workstation to Bastion):
bastionstarts Homebrew’s agent and loads the YubiKey (PIN entered)- SSH connects to the Bastion with agent forwarding active
- Bastion sends a signing challenge; YubiKey prompts for touch
- You touch the YubiKey; the signed response goes to the Bastion; session opens
Second hop (Bastion to internal host):
ssh server-aon the Bastionserver-asends a signing challenge to the Bastion- The Bastion forwards it through the tunnel to your workstation’s agent
- Your agent sends it to the YubiKey (PIN is cached from step 1)
- YubiKey prompts for touch only; you touch it at your desk
- Signed response travels back through the tunnel; access granted
Your key never moves. The requests travel to it.
Firewall Rules
For full enforcement: allow SSH (port 22) to internal hosts only from the Bastion’s IP. Block all other SSH sources, including your own workstation. The only path in is through the Bastion.
Part 7: Enrolling a Backup YubiKey
Never enforce hardware-only auth with a single key. If you lose it or it fails, you are locked out of every machine. Always enroll a backup before enabling enforcement.
When programming a second key, use the --device SERIAL flag so ykman targets the right token. Get the serial by running ykman list:
ykman --device SERIAL piv keys generate \
--algorithm ECCP256 \
--pin-policy ONCE \
--touch-policy ALWAYS \
9a ~/yubikey2-pub.pem
ykman --device SERIAL piv certificates request \
--subject "CN=Your Name,O=Your Org" \
9a ~/yubikey2-pub.pem ~/yubikey2.csr
step ca sign --not-after=8760h ~/yubikey2.csr ~/yubikey2.crt
ykman --device SERIAL piv certificates import 9a ~/yubikey2.crt
Repeat for slot 9c. Issue a separate cert from the same CA - same identity, different key material. Pair it with macOS. Add its SSH public key to every server. Verify both keys can authenticate everywhere before continuing.
Never plug both YubiKeys in simultaneously. The OTP applet on both fires and floods your terminal with garbage characters.
Store the backup in a fireproof safe, ideally offsite. The primary is your daily driver. The backup only comes out if the primary is lost or damaged.
Part 8: Enforcement
You have two enrolled keys, you have tested both, and you have a recovery path. Now you lock down.
Disable Password Auth on All Servers
On each Linux host, remove password authentication from SSH:
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart ssh # Ubuntu
sudo systemctl restart sshd # Debian
Test with a second terminal before closing your current session. Verify you can reconnect with your YubiKey, then close the original session.
Recovery Path
If both YubiKeys are lost or damaged:
- Access your VMs through the hypervisor console (e.g., the Proxmox web UI console). This bypasses SSH entirely.
- From the console, re-enable password auth, set a strong temporary password, and reconnect via SSH.
- Once you have a new YubiKey set up, disable passwords again.
This is your break-glass procedure. Document it somewhere offline.
PKI Server Hardening
Your CA server holds the most sensitive keys in the whole setup. Disable root SSH login and create a standard user with sudo:
adduser myuser
apt install -y sudo
usermod -aG sudo myuser
cp /root/.ssh/authorized_keys /home/myuser/.ssh/authorized_keys
chown -R myuser:myuser /home/myuser/.ssh
sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl restart sshd
CA keys are now only accessible by logging in as myuser and using sudo. Root direct access is gone.
Part 9: Certificate Renewal
Certs expire. Set a calendar reminder 30 days before the expiration date you chose (one year from issuance if you followed this guide).
Export the current public key from the YubiKey:
ykman piv keys export 9a ~/yubikey-pub.pem
Generate a new CSR with the same key:
ykman piv certificates request \
--subject "CN=Your Name,O=Your Org" \
9a ~/yubikey-pub.pem ~/yubikey-renewal.csr
Sign it with your CA:
step ca sign --not-after=8760h ~/yubikey-renewal.csr ~/yubikey-renewal.crt
Import the new cert - this overwrites the old one. The key pair stays the same, only the cert changes:
ykman piv certificates import 9a ~/yubikey-renewal.crt
Clean up, then repeat for slot 9c and for the backup key.
If the cert expires before you renew, password/biometric fallback (if still enabled) lets you log in and fix it. If the CA is down, restore from backup - all existing certs stay valid because the trust chain is unchanged. The CA going offline does not invalidate anything it has already issued.
Security Model: Why This Works
Three attack vectors, all neutralized by the hardware touch requirement:
Remote workstation compromise: An attacker gains a shell on your machine. They can run commands, but they cannot physically touch the YubiKey sitting on your desk. Every signing operation requires that touch. The attack stops there.
Bastion socket hijack: An attacker compromises the Bastion and tries to hijack the forwarded agent socket. The signing request routes back to the YubiKey, which waits for a touch that never comes. The attack stops there.
Lateral movement from a compromised server: No keys are on disk, no agent socket is present, no credentials are stored anywhere. The server is an island. The attack stops there.
The physical touch requirement is the kill switch for all three. No amount of software compromise, no remote exploit, no stolen session can bypass a hardware button that a human has to physically press.
CA Backup Strategy
Your CA keys are the root of trust for the entire infrastructure. If they are lost, you rebuild from scratch. Treat them accordingly:
- Back up the root and intermediate CA keys to an encrypted USB drive immediately after setup
- Store the USB in a fireproof safe, separate from your YubiKey backup
- If the CA server is destroyed: rebuild the server, restore keys from backup, bring it back up. All existing certs remain valid.
- If both CA and backup are lost: rebuild the entire PKI from scratch, new root, new intermediate, new certs, re-trust everywhere. This is painful. This is why you make the backup.
The CA is not in the authentication path. It is a vending machine that can be unplugged after issuing certs. Nothing stops working while it is offline.
What You Have Now
- A private PKI issuing certificates under your full control
- Two YubiKeys with CA-signed PIV certificates for authentication and signing
- macOS smart card login backed by hardware
- SSH authentication to all servers using hardware-backed keys, no key files on disk
- Digital document signing with cryptographic proof of identity
- A hardened Bastion server as the single entry point
- Agent forwarding that keeps your YubiKey at your desk while authenticating through tunnels
- Password authentication disabled across all servers
- A backup key and a documented recovery path
No cloud dependencies. No subscription fees. No software secrets on disk. Physical possession of the YubiKey is the only way in.
Next Steps
- Deploy an Identity Provider (Authentik or Keycloak) for web app SSO with YubiKey as the MFA factor
- Configure FreeRADIUS for 802.1X cert-based network authentication (Wi-Fi and wired)
- Set up git commit signing with slot 9c
- Explore Windows smart card login for RDP access
- Configure S/MIME email signing