THS: The Holy Setup - A series of blog posts about my computer setup.

  1. Road to Valhalla
  2. Treat Your Dotfiles Better
  3. Life on the Web
  4. GPG’ing My Setup

I am currently working on The Holy Setup again. One of the coolest things I did was redo my GPG setup after discarding my old key and replacing it with new keys and a new setup using Ansible.

First Steps

After decrypting my pass secrets, I moved my GnuPG config folder to $XDG_DATA_HOME to avoid dotfile clutter in my home directory. Then, I generated a new GPG key pair.

# ~/.bashrc

export XDG_DATA_HOME="$HOME/.local/share"
export GNUPGHOME="$XDG_DATA_HOME/gnupg"
# Command to generate the key pair
$ gpg --full-generate-key

More on cleaning up the home directory in the XDG Base Directory Arch Wiki page. For a more automated approach, check out xdg-ninja.

CESA: One Key at a Time

My Old Setup

When you run gpg --full-generate-key, if you choose a sign and encrypt algorithm, GnuPG generates two secret keys:

  • A primary key [CS] used to Certify other keys and Sign documents.
  • A subkey [E] used for Encryption if you choose a Sign and Encrypt algorithm.

🔗 More on that in this snippet.

This was exactly my previous setup.

My Current Setup

My current setup now has four keys:

  • Primary key [C]: For Certification.
  • Subkey 1 [E]: For Asymmetric Encryption.
  • Subkey 2 [S]: For Signing documents, commits, and other tasks.
  • Subkey 3 [A]: For Authentication (we’ll cover this later).

Thus the name CESA 😝

Although there are now two more keys to manage, I find this new model clearer and better at separating concerns between different keys.

How to Create the Four CESA Keys

Here are the steps to set up this configuration. Most steps are straightforward, but there are some hidden details:

  1. Use gpg --edit-key <your-user-id> to edit your key. (0)
  2. Use addkey to add an encryption subkey. (1) (2)
  3. Use addkey to add a signing subkey.
  4. Use addkey to add an authentication subkey. (3)
  5. Use change-usage to remove the Sign capability from the primary key. (4)
  6. Use key $number to select the authentication key. (5)
  7. Use change-usage to remove the Sign capability and add the Authenticate capability to the authentication key.
  8. Use save to save your changes.
  • (0) <your-user-id> can be a fingerprint, keygrip, a substring from your name, or your email. More details here.
  • (1) In my case (GPG v2.4.7), the Encryption subkey is already created by --full-generate-key.
  • (2) For the key algorithm, I use ECC (Sign only for [S] keys and Encrypt only for [E] keys).
  • (3) Use the Sign-only key algorithm (for convenience).
  • (4) change-usage is a hidden command and does not appear in the help menu.
  • (5) Replace $number with the key index. The count starts from 0 (including the primary key).

🔗 To see the full commands and their outputs, check this snippet.

Now That We Have the Keys, Let’s Use Them for SSH

For my new setup, I am switching my SSH keys to GPG keys, because why not!

Why?

There’s no strong reason for this change, but I recently switched to Fish shell, and Keychain (which remembered my passphrases for SSH keys) wasn’t behaving well with Fish. When I found out I could migrate my SSH keys to be managed by GPG and gpg-agent can somewhat replace Keychain, I switched.

Also, managing one set of keys in a centralized place seemed appealing.

Using GPG Keys for SSH

Now, I don’t have SSH keys anymore, just GPG keys. Remember the Authenticate subkey we created earlier? We’ll use it for SSH authentication. The process is fairly straightforward: enable SSH support in gpg-agent, point it to the GPG key, and replace ssh-agent with gpg-agent-ssh.

Here are the required changes:

  1. Enable SSH support in gpg-agent config.
# Enable SSH support
enable-ssh-support

# Set the time the passphrase should be cached
default-cache-ttl 604800
max-cache-ttl 700000
  1. Create an sshcontrol file with the keygrip of the Authentication key.
$ gpg --list-keys --with-keygrip

# Add the keygrip to sshcontrol
echo <keygrip> > $XDG_DATA_HOME/gnupg/sshcontrol
  1. Set SSH_AUTH_SOCK to point SSH socket to gpg-agent-ssh’s socket.
# Check if gpg-agent sockets are active
$ systemctl --user status gpg-agent.socket gpg-agent-ssh.socket

# Set the SSH_AUTH_SOCK environment variable
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
  1. Export your GPG/SSH key for use
gpg --export-ssh-key --armor <key-id>

You can redirect the key to a file or to clipboard. gpg-agent-ssh also support ssh-copy-id.

Signing Git Commits

Now that SSH is set up, let’s configure GPG to sign our Git commits. Signing commits is important, especially in open source projects. Otherwise, anyone could impersonate you with just:

git config --global user.name "Your Name"
git config --global user.email "Your Email"

Signing commits adds a unique signature that can’t be forged unless someone has access to your private key.

Enough theory, let’s see how to sign commits:

  1. Get the fingerprint of the signing key using gpg --list-keys --with-subkey-fingerprint.
  2. Copy the fingerprint of the signing subkey (the one with [S]) and set it in Git.
# For a specific repository
git config user.signingKey <your-signing-key>

# For all repositories
git config --global user.signingKey <your-signing-key>
  1. Sign commits.
# -S to sign commits
git commit -S -m "Your commit message"

You can enable signing by default with:

git config --global commit.gpgsign true

Or add it to your gitconfig file:

[user]
  ...
  signingkey = 5CB66BA3353BF73EC6A8E7C084D39FBEE9065757

[commit]
  ...
  gpgsign = true

Automating Everything with Ansible

Now that we have our keys set up, let’s do something cool! back them up and automate the process using Ansible.

Backing Up Our Keys

First, we need to back up our keys. We’ll export both public and private keys. It’s also a good idea to back up config files.

# Backup the public keys
gpg --export --armor 'your-user-id' > public.keys

# Backup the secret keys
gpg --export-secret-keys --armor 'your-user-id' > private.keys

Although secret keys are protected with a passphrase, encrypting them with Ansible Vault adds extra security.

ansible-vault encrypt public.keys private.keys

Enter Ansible

For this setup, I use Ansible and Stow to manage my dotfiles. I created a small task file to import my GPG keys from backups if they aren’t already imported.

The Ansible script:

  1. Ensures GnuPG config directories exist.
  2. Decrypts and copies backed-up keys.
  3. Imports the keys.
  4. Cleans up temporary files.
  5. Stows the GPG and GPG-agent configuration files.

🔗 If you are interested in this part, check out this example snippet.

That’s all for now!

Thanks for reading 🤗!