26 April 2024

How to Manage GnuPG Keys and Sign and Verify Git Commits

Introduction

Software and infastructure security is more prominent than ever, and an important part is signing stuff.

I have postponed using GnuPG for many years, but now is the time. GnuPG has a reputation for being cumbersome to use. One of its major intended uses apart from signing is to facilitate a web of trust in a community. Suprisingly, in my professional life related to software and infrastructure, I have seen nothing of this yet.

This post is about creating and managing a primary key and one subkey, and using the latter to sign Git commits. Also, it is shown how commits and other signatures can more easily be verified by others.

The main resources I used are listed at the end of this article.

Create the Primary Key

Create a primary key for signing and certification only, using gpg --full-generate-key, choose ECC (sign only), Curve 25519, and a suitable expiry, e.g. 2 years. Create a strong passphrase, e.g. using Diceware. You can specify a comment to be included in your identity. Here, we create a key for Alice.

Use gpg --list-keys --keyid-format LONG to get the ID and the fingerprint of your primary key. Note that you can add part of your identity to this command as a filter, e.g. gpg --list-keys alice.

pub   ed25519/FE0306B61A94B0D7 2024-05-04 [SC] [expires: 2024-06-03]
      48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
uid                 [ultimate] Alice (demo only) <alice@mgfeller.net>

FE0306B61A94B0D7 is the key ID, and 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7 is the fingerprint.

Create the Subkey

Create a subkey to be used for signing only. Some recommend a different subkey for each device you use and for each customer engagement.

Create the subkey using gpg --edit-key ID. ID can be the key ID, the fingerprint of the primary key, or a part of the identity.

At the gpg> prompt, type addkey, choose ECC (sign only), Curve 25519, and a suitable expiry, e.g. 2 years.

The output of gpg --list-keys alice is now:

pub   ed25519 2024-05-04 [SC] [expires: 2024-06-03]
      48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
uid           [ultimate] Alice (demo only) <alice@mgfeller.net>
sub   ed25519 2024-05-04 [S] [expires: 2024-06-03]

Create a Backup

Backup your public and secret keys, and the whole GnuPG directory for good measure:

gpg --export --export-options backup --output public-backup.gpg 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
gpg --export-secret-keys --export-options backup --output secret-backup.gpg 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
gpg --export-secret-subkeys --export-options backup --output secret-subkeys.pgp 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
gpg --export-ownertrust > trust.gpg

umask 077; tar -cf ./gnupg-backup.tar -C $HOME .gnupg

Copy your backup securely to a secure place. This is a fascinating aspect in itself, but not discussed here. There are plenty of resources on the internet about this topic.

Secure the Local Key

You might now like to change the local passphrase for your key, see https://wiki.debian.org/Subkeys.

Remove the primary secret key from the current device by deleting the file $HOME/.gnupg/private-keys-v1.d/KEYGRIP.key (GnuPG 2.1 or later).

The KEYGRIP can be obtained by running gpg --list-secret-keys --with-keygrip.

After having done that, the output of gpg --list-secret-keys indicates this by the # suffix added to the sec label.

Publish the Public Key

Export the public key in ascii format:

gpg --output 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc --armor --export 48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7

This can e.g. be added to your GitHub account, as a new GPG key.

In order for others to verify a signature, the public key needs to be publicly accessible. It can be uploaded to a (known) key server. Alternatively, it can be uploaded to your website as plain document, accessible using https. I keep mine on openpgp.mgfeller.net, named using the key’s fingerprint.

For the key just generated, the URL of the public key might thus become https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc .

This URL can be included in the signature, and automatically be retreived by someone else’s GnuPG.

To achieve this, use the option --sig-keyserver-url to specify the URL. It can also be specified in the GnuPG config file.

Sign Git Commits

Use gpg --list-secret-keys --keyid-format=long ID to get the ID of the subkey used for signing:

sec#  ed25519/FE0306B61A94B0D7 2024-05-04 [SC] [expires: 2024-06-03]
      48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7
uid                 [ultimate] Alice (demo only) <alice@mgfeller.net>
ssb   ed25519/490AC8047B27173D 2024-05-04 [S] [expires: 2024-06-03]

Configuring Git for signing commits with the chosen key is quite straightforward:

  • git config user.signingkey 490AC8047B27173D! which is the subkey be used, note the required ! suffix.

  • git config commit.gpgsign true tells Git to sign all commits by default.

  • export GPG_TTY=$(tty), put this in your .bashrc file, or equivalent.

Using git commit -S -m "clarify description", commit the change and sign the commit.

The signature can be inspected like this:

git log -1 --show-signature

commit cca31ca43729aa9819888fb4604a6a8a3bcf3bc0 (HEAD -> master, origin/master, origin/HEAD)
gpg: Signature made lø. 04. mai 2024 kl. 18.01 +0200 CEST
gpg:                using EDDSA key 7EDFC2DB2A8EED75DD1255A4490AC8047B27173D
gpg: Good signature from "Alice (demo only) <alice@mgfeller.net>" [ultimate]
gpg: Preferred keyserver: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
Author: Alice (demo only) <alice@mgfeller.net>
Date:   Sat May 4 17:57:57 2024 +0200

    clarify description

Everything is good because we’re verifying it on our PC. Note that the preferred keyserver is the URL of the public key, as discussed above.

Verify Git Commit Signatures

For another user, the signature cannot be verified yet as nothing has been done to download and import Alice’s public key.

Looking at the Git log will thus show

git log -1 --show-signature

commit cca31ca43729aa9819888fb4604a6a8a3bcf3bc0 (HEAD -> master, origin/master, origin/HEAD)
gpg: Signature made Sat 04 May 2024 04:01:00 PM UTC
gpg:                using EDDSA key 7EDFC2DB2A8EED75DD1255A4490AC8047B27173D
gpg: Key available at: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
gpg: Can't check signature: No public key
Author: Alice (demo only) <alice@mgfeller.net>
Date:   Sat May 4 17:57:57 2024 +0200

    clarify description

GnuPG can be configured to automatically download keys, and honor included key location by adding the following lines to ~/.gnupg/gpg.conf:

auto-key-retrieve
keyserver-options honor-keyserver-url

This results in

commit cca31ca43729aa9819888fb4604a6a8a3bcf3bc0 (HEAD -> master, origin/master, origin/HEAD)
gpg: Signature made Sat 04 May 2024 04:01:00 PM UTC
gpg:                using EDDSA key 7EDFC2DB2A8EED75DD1255A4490AC8047B27173D
gpg: Key available at: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
gpg: requesting key 490AC8047B27173D from https server openpgp.mgfeller.net
gpg: key FE0306B61A94B0D7: public key "Alice (demo only) <alice@mgfeller.net>" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: Good signature from "Alice (demo only) <alice@mgfeller.net>" [unknown]
gpg: Preferred keyserver: https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc

gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 48DC 4FF5 BC33 43C2 E427  C7A2 FE03 06B6 1A94 B0D7
     Subkey fingerprint: 7EDF C2DB 2A8E ED75 DD12  55A4 490A C804 7B27 173D
Author: Alice (demo only) <alice@mgfeller.net>
Date:   Sat May 4 17:57:57 2024 +0200

    clarify description

Of course, the key is not yet trusted. How to handle that will be topic of another blog post.

Summary

This articles described how to

  • create a primary key, for certification and signing only

  • create a subkey, for signing only; that is the key actually used for signing commits

  • backup the public and secret keys

  • secure the local primary key

  • publish the public key to a website

  • sign Git commits, including the address of the public key

Also, it was shown how another user can configure her GnuPG to automatically download public key referenced in the signature.

GnuPG Config File

Configuration options can be specified in the file ~/.gnupg/gpg.conf, assuming the default GnuPG home directory.

The example used for this blogpost is

list-options show-uid-validity show-keyserver-urls
verify-options show-uid-validity
default-new-key-algo ed25519/cert
auto-key-retrieve
keyserver-options honor-keyserver-url
sig-keyserver-url https://openpgp.mgfeller.net/48DC4FF5BC3343C2E427C7A2FE0306B61A94B0D7.asc
Tags: Security GnuGP OpenPGP