THS: The Holy Setup - A series of blog posts about my computer 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:
- Use
gpg --edit-key <your-user-id>
to edit your key. (0) - Use
addkey
to add an encryption subkey. (1) (2) - Use
addkey
to add a signing subkey. - Use
addkey
to add an authentication subkey. (3) - Use
change-usage
to remove the Sign capability from the primary key. (4) - Use
key $number
to select the authentication key. (5) - Use
change-usage
to remove the Sign capability and add the Authenticate capability to the authentication key. - 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 thehelp
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:
- 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
- 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
- Set
SSH_AUTH_SOCK
to point SSH socket togpg-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)
- 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:
- Get the fingerprint of the signing key using
gpg --list-keys --with-subkey-fingerprint
. - 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>
- 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:
- Ensures GnuPG config directories exist.
- Decrypts and copies backed-up keys.
- Imports the keys.
- Cleans up temporary files.
- 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 🤗!