Skip to content

Instantly share code, notes, and snippets.

@frankie567
Created April 2, 2026 07:05
Show Gist options
  • Select an option

  • Save frankie567/6d128a44c5e3dd6b9f450220e8353d95 to your computer and use it in GitHub Desktop.

Select an option

Save frankie567/6d128a44c5e3dd6b9f450220e8353d95 to your computer and use it in GitHub Desktop.
Yubikey PIV SSH on macOS

YubiKey PIV SSH Setup on macOS

Complete guide to setting up SSH authentication using a YubiKey's PIV module on macOS (Apple Silicon).

1. Install Tools

ykman (YubiKey Manager CLI)

uv tool install yubikey-manager

yubico-piv-tool (provides the PKCS#11 library)

brew install yubico-piv-tool

2. Configure YubiKey PIV PINs

Change the default PIN (default: 123456):

ykman piv access change-pin

Change the default PUK (default: 12345678):

ykman piv access change-puk

Set a random management key, protected by PIN (so you don't need to remember it):

ykman piv access change-management-key --generate --protect

3. Generate SSH Key + Certificate

Generate an RSA 2048 key in PIV slot 9a (Authentication):

ykman piv keys generate 9a public.pem

Create a self-signed certificate (required for the PKCS#11 module to discover the key):

ykman piv certificates generate -s "SSH key" 9a public.pem

Why a certificate? The PIV PKCS#11 module discovers keys by reading X.509 certificates from the YubiKey slots. Without a certificate, it can't see the private key. The certificate isn't used for SSH auth — it's just a wrapper for the public key.

Clean up:

rm public.pem

Export your SSH public key (add this to ~/.ssh/authorized_keys on remote hosts, GitHub, etc.):

ssh-keygen -D /opt/homebrew/lib/libykcs11.dylib -e 2>/dev/null | head -1

4. Custom SSH Agent for macOS

The default macOS ssh-agent doesn't allow PKCS#11 libraries. We need a custom LaunchAgent with the -P flag to whitelist the YubiKey library.

Important: ssh-add resolves symlinks, so the -P pattern must match the real Cellar path, not the Homebrew symlink.

Disable the system ssh-agent

launchctl disable gui/$(id -u)/com.openssh.ssh-agent

Create a custom LaunchAgent

Create ~/Library/LaunchAgents/com.custom.ssh-agent.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.custom.ssh-agent</string>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/bin/ssh-agent</string>
		<string>-D</string>
		<string>-P</string>
		<string>/opt/homebrew/Cellar/yubico-piv-tool/*/lib/libykcs11*</string>
		<string>-a</string>
		<string>/Users/YOUR_USERNAME/.ssh/agent.sock</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>KeepAlive</key>
	<true/>
</dict>
</plist>

Replace YOUR_USERNAME with your macOS username.

Load the agent

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.custom.ssh-agent.plist

Verify it's running

launchctl list | grep ssh-agent
SSH_AUTH_SOCK=~/.ssh/agent.sock ssh-add -l

5. Shell Configuration

Add to ~/.zshrc:

# Use custom ssh-agent with YubiKey PKCS#11 support
export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"
alias yubikey-add='ssh-add -s /opt/homebrew/lib/libykcs11.dylib'
alias yubikey-rm='ssh-add -e /opt/homebrew/lib/libykcs11.dylib'
alias yubikey-pubkey='ssh-keygen -D /opt/homebrew/lib/libykcs11.dylib -e 2>/dev/null | head -1'

6. SSH Config (for clients that don't use the agent)

Some SSH clients don't read SSH_AUTH_SOCK but do read ~/.ssh/config. For those, add IdentityAgent to point them to the custom agent socket:

Host example.com
    IdentityAgent ~/.ssh/agent.sock

Daily Usage

After login or reboot, load the YubiKey into the agent once:

yubikey-add

Enter your PIV PIN when prompted. All subsequent SSH and git operations will use the agent without further prompts.

Command Description
yubikey-add Load YubiKey into ssh-agent (prompts for PIN once)
yubikey-rm Remove YubiKey from ssh-agent
yubikey-pubkey Print your SSH public key
ssh-add -l List keys currently loaded in the agent

Troubleshooting

"agent refused operation"

The -P pattern doesn't match the resolved library path. ssh-add follows symlinks, so the pattern must match the Cellar path:

/opt/homebrew/Cellar/yubico-piv-tool/*/lib/libykcs11*

Run the agent in debug mode to see the exact reason:

ssh-agent -d -P "/opt/homebrew/Cellar/yubico-piv-tool/*/lib/libykcs11*"

"failed to fetch key" messages

These come from the PKCS#11 library scanning empty PIV slots. They are harmless but noisy. Avoid using PKCS11Provider in ~/.ssh/config globally — use the agent-based approach instead.

Certificate missing

If ssh-keygen -D returns no keys, check that a certificate exists in the slot:

ykman piv certificates export 9a -

If empty, regenerate the certificate (see Step 3).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment