gopass v3 + YubiKey GPG Runbook

Overview

This runbook covers setting up gopass v3 with GPG keys stored on YubiKey hardware. Every secret decryption requires physical YubiKey touch.

Security model:

┌─────────────────────────────────────────────┐
│  gopass show secret                         │
│      ↓                                      │
│  GPG decrypt request                        │
│      ↓                                      │
│  YubiKey touch required ← physical presence │
│      ↓                                      │
│  Secret revealed                            │
└─────────────────────────────────────────────┘

Laptop stolen? No YubiKey = no secrets.

Prerequisites

Hardware

Key Model Purpose

Primary

YubiKey 5C NFC (or 5 series)

Daily driver - on keychain

Backup

YubiKey 5C NFC (or 5 series)

Identical config - home safe

Software

Required Packages

Package Purpose Min Version

gnupg

GNU Privacy Guard - handles all encryption/decryption. GPG keys live on YubiKey, gpg-agent talks to the hardware.

2.4+

yubikey-manager

CLI tool (ykman) for configuring YubiKey hardware - set PINs, enable touch policies, manage OpenPGP applet.

5.0+

gopass

Password manager that uses GPG for encryption. Stores secrets as GPG-encrypted files in a git repo.

1.15+

git

Version control for the gopass store. Enables sync across machines via multiple remotes.

2.40+

pcscd

Smart card daemon - required for YubiKey communication. Runs as systemd service.

1.9+

Check Existing Installation

# command -v: Returns path if command exists, empty if not
# &>/dev/null: Redirect BOTH stdout (&) and stderr (>) to /dev/null (discard)
#   - Equivalent to: >/dev/null 2>&1
#   - Suppresses all output, we only care about exit code
# $?: Exit code of last command (0 = success, non-zero = failure)

for cmd in gpg ykman gopass git pcscd; do
    if command -v "$cmd" &>/dev/null; then
        # command found - print path
        # $(command -v $cmd): Command substitution - runs command, captures output
        echo "✓ $cmd: $(command -v $cmd)"
    else
        echo "✗ $cmd: NOT INSTALLED"
    fi
done

Install (Arch Linux)

# pacman -S: Sync/install packages
# ccid: USB smart card driver (required for YubiKey CCID interface)
sudo pacman -S gnupg yubikey-manager gopass git ccid
# systemctl enable: Start service at boot
# --now: Also start it immediately (don't wait for reboot)
# pcscd.service: PC/SC Smart Card Daemon
sudo systemctl enable --now pcscd.service

Verify Versions (with Exit Code Checks)

# Pattern breakdown:
#   gpg --version     : Run command, outputs to stdout
#   2>&1              : Redirect stderr (2) to stdout (1) - capture errors too
#   | head -1         : Pipe (|) stdout to head, show first line only
#   && echo "..."     : If command succeeds (exit 0), run the echo

gpg --version 2>&1 | head -1 && echo "Exit code: $?"
# awk '{print $NF}': Print last field ($NF = Number of Fields = last column)
# Useful when version is always at the end but column position varies
ykman --version 2>&1 | awk '{print "ykman version:", $NF}'
# awk '{print $2}': Print second field (space-delimited)
# gopass outputs: "gopass 1.15.14 go1.23.0 linux amd64"
#                  $1     $2      $3       $4    $5
gopass --version 2>&1 | awk '{print "gopass version:", $2}'
# git outputs: "git version 2.47.2"
#               $1  $2      $3
git --version 2>&1 | awk '{print "git version:", $3}'

Comprehensive Version Check Loop

# Advanced loop with conditional formatting
# printf: Formatted print (like C's printf)
#   "%-8s": Left-align (-), 8 characters wide, string (s)
#   "%d": Decimal integer (for exit code)
# version=$(...): Capture command output into variable

for tool in gpg ykman gopass git; do
    # Capture version output; if command succeeds, print success
    if version=$($tool --version 2>&1 | head -1); then
        printf "✓ %-8s %s\n" "$tool:" "$version"
    else
        # $? contains exit code from the failed command
        printf "✗ %-8s FAILED (exit code: %d)\n" "$tool:" $?
    fi
done
Expected output
✓ gpg:     gpg (GnuPG) 2.4.7
✓ ykman:   YubiKey Manager (ykman) version: 5.5.1
✓ gopass:  gopass 1.15.14 go1.23.0 linux amd64
✓ git:     git version 2.47.2

Verify pcscd (Socket Activation)

# Socket activation: systemd starts service on-demand when socket is accessed
# The socket listens, service only runs when something connects
# This is why pcscd.service shows "inactive" but YubiKey still works

systemctl is-active pcscd.socket && echo "Socket ready - service starts on demand"
# Advanced: Check both socket and service, format with awk
# paste - -: Merge two lines into one (socket status + service status)
# Ternary in awk: (condition ? "if_true" : "if_false")

systemctl is-active pcscd.socket pcscd.service 2>&1 | paste - - | awk '{
    print "pcscd.socket:", $1, ($1=="active" ? "✓" : "✗")
    print "pcscd.service:", $2, "(starts on demand, inactive is OK)"
}'

Verify YubiKey Detection

# ykman list: Show all connected YubiKeys
# awk 'NF': Only print lines with content (NF = Number of Fields > 0)
# NF is truthy when line has fields, falsy for empty lines

ykman list 2>&1 | awk 'NF {print "YubiKey found:", $0}'
# Extract serial number using field separator
# -F'Serial: ': Split line on "Serial: " (the text before the number)
# /Serial/: Only process lines containing "Serial"
# $2: Second field (everything after "Serial: ")

ykman list 2>&1 | awk -F'Serial: ' '/Serial/ {print "Serial:", $2}'
# GPG card status - shows OpenPGP applet info
# head -5: First 5 lines contain Reader, Application ID, Version
gpg --card-status 2>&1 | head -5
# Multi-pattern awk: Match multiple patterns, extract specific fields
# /Reader/: Match lines containing "Reader"
# /Serial/: Match lines containing "Serial"
# /PIN retry/: Match PIN retry counter line

gpg --card-status 2>&1 | awk '
    /Reader/ {print "Reader:", $NF}
    /Serial/ {print "Serial:", $NF}
    /PIN retry/ {print "PIN retries:", $4, $5, $6}
'

Full Diagnostic Script

#!/bin/bash
# yubikey-check - Full YubiKey GPG diagnostic
# Save as: ~/bin/yubikey-check && chmod +x ~/bin/yubikey-check

echo "=== YubiKey GPG Diagnostic ==="
echo ""

echo "--- Tools ---"
# ver=$(...) && printf ... || printf ...: Ternary pattern
# If command succeeds, print version; else print "missing"
for cmd in gpg ykman gopass git; do
    ver=$($cmd --version 2>&1 | head -1) && \
        printf "✓ %s\n" "$ver" || \
        printf "✗ %s missing\n" "$cmd"
done
echo ""

echo "--- pcscd ---"
# $(...): Command substitution - output becomes the string
printf "Socket: %s\n" "$(systemctl is-active pcscd.socket)"
echo ""

echo "--- YubiKey ---"
# ||: If left side fails (non-zero exit), run right side
ykman list 2>&1 || echo "No YubiKey detected"
echo ""

echo "--- GPG Card ---"
# Pipe to awk, filter specific lines, handle failure
gpg --card-status 2>&1 | awk '/Reader|Serial|PIN retry/ {print}' || \
    echo "Card not accessible"
Save this diagnostic as ~/bin/yubikey-check for quick verification anytime.

Understanding Redirection

Pattern Meaning

>

Redirect stdout to file (overwrite)

>>

Redirect stdout to file (append)

2>

Redirect stderr to file

2>&1

Redirect stderr (fd 2) to wherever stdout (fd 1) is going

&>

Redirect BOTH stdout and stderr (shorthand for >/dev/null 2>&1)

&>/dev/null

Discard all output (silent execution)

cmd | head -1

Pipe stdout to head, show first line only

cmd || echo "fail"

OR: If cmd fails (non-zero exit), run the echo

cmd && echo "ok"

AND: If cmd succeeds (exit 0), run the echo

$?

Exit code of last command (0=success, non-zero=error)

$(cmd)

Command substitution: Run cmd, capture its stdout as string

Phase 1: GPG Master Key Generation

Generate the master key on a secure machine. Air-gapped preferred, or at minimum a trusted workstation with no network during generation.

1.1 Configure GPG for Strong Defaults

# Create GPG home directory with restrictive permissions
# 700 = rwx------ (only owner can read/write/execute)
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
# Heredoc: << 'EOF' ... EOF
# Single quotes around EOF: No variable expansion (literal text)
# Without quotes: Variables like $HOME would be expanded

cat > ~/.gnupg/gpg.conf << 'EOF'
# Cipher preferences: AES-256 first (strongest)
personal-cipher-preferences AES256 AES192 AES

# Digest (hash) preferences: SHA-512 first (strongest)
personal-digest-preferences SHA512 SHA384 SHA256

# Compression preferences
personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed

# Default preferences for new keys
default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed

# Use SHA-512 for certifying other keys
cert-digest-algo SHA512

# String-to-key (passphrase hashing) settings
s2k-digest-algo SHA512
s2k-cipher-algo AES256

# Display settings
charset utf-8
no-comments
no-emit-version
no-greeting
keyid-format 0xlong
list-options show-uid-validity
verify-options show-uid-validity
with-fingerprint

# Security settings
require-cross-certification
no-symkey-cache
use-agent

# Privacy: Hide recipient key IDs in encrypted messages
throw-keyids
EOF
# Verify config was written
# wc -l: Count lines
# cat -n: Show with line numbers (for verification)
wc -l ~/.gnupg/gpg.conf && echo "Config written successfully"

1.2 Generate Master Key (Certify Only)

# --expert: Enable expert mode (needed for capability toggling)
# --full-generate-key: Interactive key generation with all options
gpg --expert --full-generate-key

Interactive prompts - select these options:

Please select what kind of key you want:
   (8) RSA (set your own capabilities)    <-- SELECT THIS

Current allowed actions: Sign Certify Encrypt
   (S) Toggle the sign capability
   (E) Toggle the encrypt capability
   (A) Toggle the authenticate capability
   (Q) Finished

# Toggle OFF: S, E, A (press each letter)
# Only "Certify" should remain
# Press Q when done

What keysize do you want? (3072) 4096    <-- ENTER 4096
Key is valid for? (0) 0                   <-- 0 = never expires (or 2y)
Real name: Your Full Name
Email address: your.email@example.com
Comment:                                  <-- Leave blank
# After generation, capture the key ID
# grep -A1 "sec": Show line containing "sec" plus 1 line After
# tail -1: Take the last line (the fingerprint/ID line)
# awk '{print $1}': Extract first field (the key ID)

export KEYID=$(gpg --list-keys --keyid-format long 2>&1 | \
    grep -A1 "^sec" | tail -1 | awk '{print $1}')
echo "Master Key ID: $KEYID"
# Alternative: More robust extraction using fingerprint
# sed -n: Silent mode (only print when told)
# '/fpr/p': Print lines containing "fpr" (fingerprint)
# tail -c 17: Last 16 chars + newline (long key ID)

KEYID=$(gpg --list-keys --with-colons 2>&1 | \
    awk -F: '/^pub/ {getline; print $10}' | head -1)
echo "Master Key ID: $KEYID"

1.3 Add Subkeys

# Edit the key to add subkeys
# Subkeys do the actual work; master key only certifies
gpg --expert --edit-key $KEYID

Add three subkeys (one at a time):

gpg> addkey
(8) RSA (set your own capabilities)
# Toggle to ONLY "Sign" (S on, E off, A off)
Keysize: 4096
Valid for: 1y

gpg> addkey
(8) RSA (set your own capabilities)
# Toggle to ONLY "Encrypt" (S off, E on, A off)
Keysize: 4096
Valid for: 1y

gpg> addkey
(8) RSA (set your own capabilities)
# Toggle to ONLY "Authenticate" (S off, E off, A on)
Keysize: 4096
Valid for: 1y

gpg> save

1.4 Verify Key Structure

# List keys with long format IDs
# sec = Secret key (master)
# ssb = Secret subkey
# [C] = Certify, [S] = Sign, [E] = Encrypt, [A] = Authenticate

gpg --list-keys --keyid-format long $KEYID
# Detailed view with awk formatting
gpg --list-keys --with-colons $KEYID 2>&1 | awk -F: '
    /^pub/ {print "Master (Certify):", $5}
    /^sub/ {
        # $12 contains capabilities (s, e, a)
        cap = $12
        gsub(/s/, "Sign", cap)
        gsub(/e/, "Encrypt", cap)
        gsub(/a/, "Auth", cap)
        print "Subkey:", $5, "(" cap ")"
    }
'

Expected output:

sec   rsa4096/0x1234567890ABCDEF 2026-02-17 [C]
uid                   [ultimate] Your Name <your.email@example.com>
ssb   rsa4096/0xAAAAAAAAAAAAAAAA 2026-02-17 [S] [expires: 2027-02-17]
ssb   rsa4096/0xBBBBBBBBBBBBBBBB 2026-02-17 [E] [expires: 2027-02-17]
ssb   rsa4096/0xCCCCCCCCCCCCCCCC 2026-02-17 [A] [expires: 2027-02-17]

Phase 2: Backup Master Key

CRITICAL: Do this BEFORE moving keys to YubiKey. Moving is one-way - keys are deleted from disk.

2.1 Export Master Key

# Create secure backup directory
# 700 permissions: Only owner can access
mkdir -p ~/gpg-backup && chmod 700 ~/gpg-backup
# --armor: ASCII armor output (text, not binary)
# --export-secret-keys: Export ALL secret keys (master + subkeys)
gpg --armor --export-secret-keys $KEYID > ~/gpg-backup/master-secret.asc
# Export public key (safe to share)
gpg --armor --export $KEYID > ~/gpg-backup/public.asc
# Export ONLY subkeys (useful for recovery without exposing master)
gpg --armor --export-secret-subkeys $KEYID > ~/gpg-backup/subkeys-secret.asc
# Generate revocation certificate (use if key is compromised)
# This is CRITICAL - store securely
gpg --gen-revoke $KEYID > ~/gpg-backup/revoke.asc
# Verify all files were created
ls -la ~/gpg-backup/ | awk 'NF > 2 {print $NF, $5, "bytes"}'

2.2 Secure the Backup

# Create encrypted tarball
# tar -czf -: Create gzipped tar, output to stdout (-)
# | gpg --symmetric: Pipe to GPG for symmetric encryption (passphrase)
# --cipher-algo AES256: Use AES-256 encryption

tar -czf - ~/gpg-backup | gpg --symmetric --cipher-algo AES256 \
    > ~/gpg-master-backup-$(date +%Y%m%d).tar.gz.gpg
# Verify backup is valid (list contents without extracting)
# gpg --decrypt: Decrypt the file
# | tar -tzf -: List (-t) gzipped (-z) tar from stdin (-f -)

gpg --decrypt ~/gpg-master-backup-*.tar.gz.gpg 2>/dev/null | tar -tzf - | head -10

Copy to multiple locations:

  1. Encrypted USB drive (LUKS or VeraCrypt)

  2. Offline storage (safe deposit box)

  3. Another encrypted volume

2.3 Secure Passphrase Storage

Store the backup encryption passphrase separately from the backup:

  • Written on paper in fireproof safe

  • Temporarily in separate password manager

  • Split with Shamir’s Secret Sharing

Phase 3: Move Subkeys to YubiKey

3.1 Configure YubiKey for GPG

# Verify YubiKey is detected by GPG
# Should show Reader, Application ID, card capabilities
gpg --card-status 2>&1 | head -10
# Enter card edit mode
gpg --card-edit

In the GPG card shell:

gpg/card> admin            # Enable admin commands
Admin commands are allowed

gpg/card> passwd           # Change PINs

Set PINs (CHANGE FROM DEFAULTS):

# PIN menu:
1 - change PIN          # Default: 123456 → Change to 6+ digits
3 - change Admin PIN    # Default: 12345678 → Change to 8+ digits
4 - set Reset Code      # Optional: Recovery if PIN locked

gpg/card> quit
# Verify PIN retries (should be 3/3/3)
gpg --card-status 2>&1 | awk '/PIN retry/ {print "PIN retries:", $4, $5, $6}'

3.2 Move Subkeys to YubiKey

This operation is ONE-WAY. Keys are MOVED (deleted from disk), not copied. Ensure backup is complete!
gpg --edit-key $KEYID

Move each subkey to card:

# Select first subkey (Sign)
gpg> key 1                          # Asterisk appears next to key 1
gpg> keytocard
Please select where to store the key:
   (1) Signature key                # <-- Select 1
Your selection? 1

# Deselect key 1, select key 2 (Encrypt)
gpg> key 1                          # Deselect
gpg> key 2                          # Select key 2
gpg> keytocard
   (2) Encryption key               # <-- Select 2
Your selection? 2

# Deselect key 2, select key 3 (Authenticate)
gpg> key 2                          # Deselect
gpg> key 3                          # Select key 3
gpg> keytocard
   (3) Authentication key           # <-- Select 3
Your selection? 3

gpg> save                           # IMPORTANT: Save changes

3.3 Verify Keys on YubiKey

# Card status should now show your subkeys
gpg --card-status 2>&1 | awk '
    /Signature key/ {print "Sign key:", $NF}
    /Encryption key/ {print "Encrypt key:", $NF}
    /Authentication key/ {print "Auth key:", $NF}
'
# Full verification - keys should show "card-no:" indicating on hardware
gpg --list-secret-keys --keyid-format long 2>&1 | grep -E "(sec|ssb|card-no)"

3.4 Configure Touch Requirement

# Require physical touch for ALL cryptographic operations
# This is the key security feature - no touch = no decrypt
ykman openpgp keys set-touch sig on     # Signing requires touch
ykman openpgp keys set-touch enc on     # Encryption/decryption requires touch
ykman openpgp keys set-touch aut on     # Authentication requires touch
# Verify touch policy
ykman openpgp info 2>&1 | awk '/Touch policy/ {found=1} found {print}'

Phase 4: Setup Backup YubiKey

Repeat Phase 3 with your backup YubiKey, using the backup you created in Phase 2.

4.1 Restore Keys from Backup

# On the same or different machine
# Import the full secret key (master + original subkeys)
gpg --import ~/gpg-backup/master-secret.asc
# Import public key (needed for trust)
gpg --import ~/gpg-backup/public.asc
# Verify import
gpg --list-secret-keys --keyid-format long 2>&1 | head -10

4.2 Move to Backup YubiKey

  1. Remove primary YubiKey

  2. Insert backup YubiKey

  3. Repeat gpg --edit-key $KEYID and keytocard process from Phase 3.2

  4. Set touch policies with ykman openpgp keys set-touch

4.3 Store Backup YubiKey Securely

  • Home safe (fireproof)

  • Bank safe deposit box

  • Trusted family member (different location)

Phase 5: gopass v3 Initialization

5.1 Initialize gopass with GPG Key

# Initialize v3 store encrypted with your GPG key
# --store v3: Create store named "v3"
# $KEYID: Your GPG key ID from earlier

gopass init --store v3 $KEYID
# Verify initialization
gopass --version && gopass ls v3

5.2 Create Domain-Aligned Structure

# Create metadata entry for network credentials
# Heredoc with gopass insert:
#   -f: Force (overwrite if exists)
#   -m: Multiline mode

cat << 'EOF' | gopass insert -f -m v3/domains/d000/network/.meta
---
description: Network infrastructure credentials
scope: d000 (Personal Infrastructure)
categories:
  - devices: Switch, router, firewall logins
  - radius: RADIUS shared secrets
  - snmp: Community strings
  - routing: OSPF, EIGRP, BGP keys
  - tacacs: TACACS+ secrets
created: 2026-02-17
EOF
# Verify structure was created (requires YubiKey touch)
gopass ls v3

5.3 Configure Multi-Remote Sync

# gopass stores are git repos - add multiple remotes for redundancy
# git -C PATH: Run git command in specified directory

STORE_PATH="$HOME/.local/share/gopass/stores/v3"

# Add remotes (replace USER with your username)
git -C "$STORE_PATH" remote add origin git@github.com:USER/gopass-v3.git
git -C "$STORE_PATH" remote add gitlab git@gitlab.com:USER/gopass-v3.git
git -C "$STORE_PATH" remote add gitea git@gitea.inside.domusdigitalis.dev:user/gopass-v3.git
# Verify remotes
git -C "$STORE_PATH" remote -v | awk '{print $1, $2}'
# Push to all remotes
# -u origin main: Set origin/main as upstream for future pushes
git -C "$STORE_PATH" push -u origin main
git -C "$STORE_PATH" push gitlab main
git -C "$STORE_PATH" push gitea main
# Create sync-all script for daily use
cat > ~/bin/gopass-sync-all << 'EOF'
#!/bin/bash
STORE_PATH="$HOME/.local/share/gopass/stores/v3"
echo "Syncing gopass v3 to all remotes..."
for remote in origin gitlab gitea; do
    git -C "$STORE_PATH" push $remote main 2>&1 | \
        awk -v r="$remote" '{print r": "$0}'
done
EOF
chmod +x ~/bin/gopass-sync-all

5.4 Test YubiKey Integration

# Generate test secret (24 characters)
# YubiKey should blink during encryption
gopass generate v3/test/yubikey-test 24
# Retrieve secret (requires touch to decrypt)
# YubiKey blinks - touch to authorize
gopass show v3/test/yubikey-test
# Clean up test secret
gopass rm -f v3/test/yubikey-test

gopass v3 Architecture

Full Structure

v3/
├── domains/
│   ├── d000/                    # Personal Infrastructure
│   │   ├── hardware/            # IPMI, printers, IoT
│   │   ├── identity/            # AD, IPA, Keycloak, LDAP
│   │   ├── network/
│   │   │   ├── devices/         # Switch/router/firewall logins
│   │   │   ├── radius/          # RADIUS shared secrets
│   │   │   ├── snmp/            # Community strings
│   │   │   ├── routing/         # OSPF/EIGRP/BGP keys
│   │   │   └── tacacs/          # TACACS+ secrets
│   │   ├── servers/
│   │   ├── storage/             # NAS, S3, backup
│   │   └── wifi/                # PSKs
│   └── d001/                    # Client domain
│       ├── identity/
│       └── network/
├── keys/
│   ├── ssh/
│   │   ├── personal/            # github, gitlab, gitea
│   │   └── service/             # deploy keys, automation
│   ├── age/                     # Age encryption keys
│   ├── gpg/                     # GPG passphrases
│   └── encryption/              # borg, veracrypt, LUKS
├── certificates/                # Cert passphrases, PFX
├── licenses/                    # Software licenses
└── personal/                    # Consumer/personal
    ├── finance/
    ├── shopping/
    ├── social/
    ├── streaming/
    ├── gaming/
    ├── email/
    ├── documents/
    ├── recovery/                # 2FA backup codes
    ├── travel/
    ├── health/
    └── government/

Design Principles

  1. Domain-aligned - Mirrors dsec structure (d000, d001)

  2. No top-level sprawl - Network secrets nested under domains/dXXX/network/

  3. Clear separation - Passwords → gopass, API tokens → dsec

  4. Hardware-backed - GPG on YubiKey

Daily Operations

Retrieve Secret

# Show secret with metadata
gopass show v3/domains/d000/network/devices/switch-01
# Password only - first line (-o = output only password)
# Useful for piping to other commands
gopass show -o v3/domains/d000/network/devices/switch-01
# Copy to clipboard (clears after 45 seconds)
gopass -c v3/domains/d000/network/devices/switch-01
# Use in scripts with command substitution
PASSWORD=$(gopass show -o v3/domains/d000/network/devices/switch-01)
echo "Password length: ${#PASSWORD}"
unset PASSWORD  # Clear from memory

Add New Secret

# Generate random password (24 chars)
gopass generate v3/domains/d000/servers/newserver 24
# Insert with YAML metadata using heredoc
cat << 'EOF' | gopass insert -f -m v3/domains/d000/servers/newserver
SuperSecretPassword123!
---
username: admin
ip: 10.50.1.100
port: 22
notes: Production server
created: 2026-02-17
EOF

Sync Operations

# Sync with all configured remotes
gopass sync
# Manual sync with specific remote
git -C ~/.local/share/gopass/stores/v3 pull origin main
git -C ~/.local/share/gopass/stores/v3 push origin main

Clone on New Machine

# 1. Import your public key
gpg --import public.asc
# 2. Clone the store
gopass clone git@github.com:USER/gopass-v3.git v3
# 3. Insert YubiKey and test (requires touch)
gopass show v3/domains/d000/network/.meta

Troubleshooting

"No secret key" Error

# Check YubiKey is inserted and detected
gpg --card-status 2>&1 | head -5 || echo "Card not detected"
# If key stubs are missing, reimport public key
gpg --import public.asc

# Then refresh stubs by reading card
gpg --card-status

Touch Not Working

# Check touch policy is enabled
ykman openpgp info 2>&1 | grep -i touch
# Restart gpg-agent (may have stale state)
gpgconf --kill gpg-agent && gpgconf --launch gpg-agent

PIN Locked

# Check remaining retries
gpg --card-status 2>&1 | awk '/PIN retry/ {
    print "PIN retries:", $4
    print "Reset retries:", $5
    print "Admin retries:", $6
}'
# If PIN locked but Admin PIN works:
gpg --card-edit
gpg/card> admin
gpg/card> passwd
# Option 2: Reset PIN using Admin PIN

Lost YubiKey Recovery

  1. Get backup YubiKey from safe

  2. Import public key: gpg --import public.asc

  3. Refresh stubs: gpg --card-status

  4. Test: gopass show v3/test

  5. Order replacement, repeat Phase 4 setup

Appendix: Security Checklist

Item Status Verification Command

Master key backed up

[ ]

ls -la ~/gpg-backup/master-secret.asc

Subkeys on primary YubiKey

[ ]

gpg --card-status | grep -E "Signature|Encryption|Authentication"

Subkeys on backup YubiKey

[ ]

Test with backup YubiKey inserted

Touch required

[ ]

ykman openpgp info | grep Touch

PIN changed from default

[ ]

gpg --card-status | grep "PIN retry"

Admin PIN changed

[ ]

Test with gpg --card-edit → admin → passwd

Backup YubiKey stored securely

[ ]

Physical verification (safe, not with laptop)

Revocation cert backed up

[ ]

ls -la ~/gpg-backup/revoke.asc

gopass v3 syncing

[ ]

git -C ~/.local/share/gopass/stores/v3 remote -v

  • gopass Reference (secrets-infrastructure)

  • YubiKey SSH/FIDO2 Setup (secrets-infrastructure)

  • dsec - Domain Secrets (secrets-infrastructure)

  • Security Model (secrets-infrastructure)