Signing Git Commits with Your SSH Key

Caleb Hearth - Aug 31 '22 - - Dev Community

You may already be signing your Git commits with a GPG key, but as of today you can instead choose to sign with your SSH key! Signing in SSH is a relatively new feature that lets you use your private SSH key to sign arbitrary text and others to verify that signature with your public key.

This is great, because pretty much everyone has an SSH key (and pretty much no one has a GPG key). As developers, we’ve probably all uploaded one or more SSH keys to GitHub because that’s necessary to use git(1) to push to GitHub.

This all makes it pretty easy to sign and verify Git commits with keys we already have. There are a few settings you’ll need to configure to start signing.

Signing

First, we’ll want to configure Git to use SSH as the format for signing:

$ git config --global commit.gpgsign true
$ git config --global gpg.format ssh

Enter fullscreen mode Exit fullscreen mode

Next, tell Git what key to use for signing. You can find your SSH keys in ~/.ssh/ or list them with

$ ssh-add -L

Enter fullscreen mode Exit fullscreen mode

On the off chance that you don’t already have an SSH key, GitHub have instructions you can follow.

To set the signing key:

$ git config --global user.signingkey "ssh-ed25519 <your key id>"

Enter fullscreen mode Exit fullscreen mode

I ran this, which won’t be useful to you:

$ git config --global user.signingkey \
  "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf"

Enter fullscreen mode Exit fullscreen mode

To confirm that you’ve configured signing correctly, run:

$ git commit --allow-empty --message="Testing SSH signing"

Enter fullscreen mode Exit fullscreen mode

You should see something like [main 02f6137] Testing SSH signing if you’ve configured signing correctly.

Verifying

Git also lets you verify signatures, but there are more steps involved in this. First, some background. GPG/PGP provides a trust framework that allows you to specify specific keys to trust as well as to let you say “I trust this person(‘s key) to verify others’ keys” so that if you trust Caleb and Caleb vouches for Derek, gpg(1) will agree that Derek’s valid signatures can be trusted.

SSH doesn’t have this “web of trust”. Instead, it uses files like “~/.ssh/authorized_keys” and “~/.ssh/known_hosts” to configure what keys and hosts it should trust, respectively. For verification, we’ll need to create a file to configure allowed signers (see ssh-keygen(1) ALLOWED SIGNERS). While there’s no formal place to store this file yet, it makes sense to store a global “database” of allowed signers in “~/.ssh/allowed_signers” and a project-specific file might be stored in “/.git/allowed_signers" if you'd like to maintain your own list or "/.allowed_signers" if you want to commit the file and share it.

Viewing Signatures

So given all of that, let’s see what our signature looks like on that commit we just made:

$ git show --show-signature
error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification
commit 52b2c062225a77eea1e142c4073b7b0907eeef5c
No signature
Author: Caleb Hearth <caleb@calebhearth.com>
Date: Mon Nov 15 16:28:46 2021 -0600

    Testing SSH signing

Enter fullscreen mode Exit fullscreen mode

The first thing you’ll note is that Git claims there is “No signature” even if we made one. Don’t Panic. We also see on the first line an error from Git noting that we need to configure an allowed signers file. We can set that up globally with:

$ git config --global gpg.ssh.allowedSignersFile ~/.ssh/allowed_signers
$ touch ~/.ssh/allowed_signers
$ git show --show-signature
commit 52b2c062225a77eea1e142c4073b7b0907eeef5c
Good "git" signature with ED25519 key SHA256:l1J5VwpF7tSJ0SE9/iawdFhwR7BdzPPxdV3jIURg/yo
sig_find_principals: sshsig_get_principal: key not found^M
No principal matched.
Author: Caleb Hearth <caleb@calebhearth.com>
Date: Mon Nov 15 16:28:46 2021 -0600

    Testing SSH signing

Enter fullscreen mode Exit fullscreen mode

Allowed Signers

We’re making progress here, and we see that there is a good signature, but that the key is not found. That’s because the allowed_signers file is empty. We can populate it with our own key like this:

echo "caleb@calebhearth.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf" \
  > ~/.ssh/authorized_signatures

Enter fullscreen mode Exit fullscreen mode

The format for the allowed signers file is documented in ssh-keygen(1) ALLOWED SIGNERS and effectively boils down to:

<email>[,<email>...] <key type> <public key>

Enter fullscreen mode Exit fullscreen mode

What is not made explicit in that documentation is that wildcards can be used in place of the email portion. While this may make reduce security in principle by allowing anyone with access to the key to sign, in practice Git allows both committer and author emails to be specified so there is no real security impact here and it makes things somewhat easier.

* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf

Enter fullscreen mode Exit fullscreen mode

When we try showing the signature again, we’ll see a successful verification:

commit 52b2c062225a77eea1e142c4073b7b0907eeef5c
Good "git" signature for caleb@calebhearth.com with ED25519 key SHA256:l1J5VwpF7tSJ0SE9/iawdFhwR7BdzPPxdV3jIURg/yo
Author: Caleb Hearth <caleb@calebhearth.com>
Date: Mon Nov 15 16:28:46 2021 -0600

    Testing SSH signing

Enter fullscreen mode Exit fullscreen mode

User SSH Keys from GitHub

GitHub provide public SSH keys for all users at URLs like https://github.com/calebhearth.keys, so you can use that along with the Git author email address to add new allowed signers to that file. That’s manual and a bit of a pain, so I wrote up this very quick-and-dirty script that will pull all keys for all signed commits since 14 November 2021 (the day before Git SSH signing was released) and print them out in authorized_signers format. You can run it on any repo and append the output to your authorized_signers, after which you may want to de-duplicate entries.

.gitconfig

After all of this, here are the changes to my ~/.gitconfig that I’ve made to sign with SSH:

[gpg]
  format = ssh
[gpg "ssh"]
  allowedSignersFile = ~/.ssh/allowed_signers
[user]
  signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM9V5SC0UdggJItk8StyYrJTj4eSArjuz4kgqXRy8hnf

Enter fullscreen mode Exit fullscreen mode

GitHub

Screenshot of a commit showing a 'Verified' badge. Below, a hovercard shows a checkmark next to text indicating that the commit was signed with a verified signature, my profile username and real name, and an SSH key fingerprint for my SSH key.

You’ll need to upload your key to GitHub as a signing key, even if it has already been uploaded as an authentication key (this is a new dichotomy introduced to support this feature). Once that’s been done, you’ll see that commits signed with your SSH key are correctly shown as verified on GitHub:

Conclusion

Using SSH to sign and verify Git commits, after a bit of setup, should be really easy and a great alternative to GPG signing and verification.

I do have an early wishlist for improvements:

  • GitHub support for displaying SSH signatures (Fixed! See the GitHub section above)
  • a default global allowed_keys file both for Git and SSH
  • bugfixes such as to remove those ^M Windows linefeed characters from the error output (Fixed!)
  • hopefully we’ll see companies setting up cert-authority for their domains to make setting this up even easier
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .