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
addkeyto add an encryption subkey. (1) (2) - Use
addkeyto add a signing subkey. - Use
addkeyto add an authentication subkey. (3) - Use
change-usageto remove the Sign capability from the primary key. (4)(5) - Use
key $numberto select the authentication key. (6) - Use
change-usageto remove the Sign capability and add the Authenticate capability to the authentication key. - Use
saveto 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-usageis a hidden command and does not appear in thehelpmenu.- (5) Certification cannot be delegated to a subkey, so you will always need your master key to sign another key.
- (6) Replace
$numberwith 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-agentconfig.
# Enable SSH support
enable-ssh-support
# Set the time the passphrase should be cached
default-cache-ttl 604800
max-cache-ttl 700000
- Set the
Use-for-ssh: -1for the Authentication key.
# Get the keygrip
$ gpg --list-keys --with-keygrip
# Set the Use-for-ssh flag
$ gpg-connect-agent "KEYATTR --set <keygrip> Use-for-ssh: true" /bye
‼️ I used to use the deprecated sshcontrol file.
- Set
SSH_AUTH_SOCKto 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 🤗!
Appendices & References
gpg --list-keys --with-fingerprintdoes not print the subkeys fingerprints. We should usegpg --list-keys --with-fingerprint --with-subkey-fingerprintsfor that.- What is the difference between keygrip, fingerprint and id?
- keygrip: 40-character hexadecimal string unique identifier of the key. It is used for internal operations like ssh-agent caching, sshcontrol and naming the keys.
- fingerprint: 40-character hexadecimal string that is usually calculated from the public component of the key (this is the case for php and ssh protocols). It is often used to identify the key and verify its authenticity when shared with in public. Also it is used in signatures and revocation certificates (thus the public nature).
- id: It is a short hexadecimal string derived from the fingerprint.
- When a key expires, it should be revoked! The good practice is to keep the public key in the key ring to be able to verify old signatures and revoke the private key. After a new key is created.
- The key is revoked by generating a revocation certificate, and importing it into the local keyring and uploading it into the servers. This will prevent the key from being used for encryption and signature both locally and publicly.
gpg --exportdoes not support exporting public keys for subkeys since the subkeys are tied to the primary key.- GitLab has a kind of weak support for the signing commits with subkeys. Commits signed by subkeys may show up as
Unverifiedalthough the public GPG key is already added to GitLab. Apparently the feature is implemented, but it still doesn’t work for many people. - 🔗 a guide to setup ssh with gpg. It includes something with
GPG_TTY. - 🔗 very good article about ssh-agent.