Signing your Git Commits with SSH Keys
By Mark DeNeve
In August of this year, there was a bunch of panic about GitHub being compromised, and 35K repos having malicious code in them. Further investigation clarified that it was Github Repos that were set up to do a “phishing” type attack, by creating repositories that were improperly named or Typosquatting. That being said it has led to further discussion and attention around Code Supply Chain, and ensuring that code contributions, libraries and releases are validated before use. One such way to do this is by signing code commits.
Github and GitLab have supported using GPG keys for years to sign code commits, however most users do not actually cryptographically sign their commits. Additional options besides GPG are also available including using “Keyless Signing” with Sigstore using Gitsign, and using SSH keys to sign your commits. While I think “Keyless Signing” may be the way to go long term, using GPG or SSH keys is a great way to get started.
NOTE: Cryptographically signing your commits is different than “signing off” on your commits (with
git commit -s
).
Github has added a new feature that allows you to Cryptographically sign your commits with an SSH key. This helps to lower the barrier to signing your commits going forward. While this is not yet supported on GitLab, there is a feature request out there to add this support as well.
What does signing do?
When you use git to sign your work, you add additional information to the commit, that helps to ensure that the changes you made, the commit message and all associated metadata on the commit are from YOU. Why would you want to do this,? The User Name and e-mail address that are shown in Github, can be anything. Don’t believe me? Check this out:
$ git config user.email "[email protected]"
$ git config user.name "Tony Stark"
$ git commit --allow-empty -m "I am Iron Man"
[main 1909eab] I am Iron Man
1 file changed, 2 insertions(+)
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 279 bytes | 279.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:xphyr/signingtest.git
b181cd1..1909eab main -> main
This is what you will see in GitHub:
Why would Git allow something like this? There is a great explanation here by Free Code Camp What is Commit Signing in Git?, but basically it comes down to the following:
When you authenticate to push to remote repositories, you’re authenticating to do just that– push changes. The commits don’t require authentication regardless of who authored or committed them.
So what can we do to help close this gap. This is where signing your commits comes into play and what we will cover how to do below.
Keeping your SSH Keys Safe and Secure
Before proceeding, one thing to keep in mind, when using your SSH key for committing to Github and now signing code, you should ensure that your SSH key is protected with a password. See the instructions from GitHub on Generating a new SSH key for more details on creating an SSH key and using it to commit to GitHub.
Getting Started
We will assume that you have already set up SSH keys for use in pushing commits to GitHub. If not, go back to Keeping your SSH Keys Safe and Secure and get your base SSH key set up before proceeding.
I use a separate SSH key for signing my commits. This ensures that if I was to loose control of the SSH key used for pushing commits to GitHub I still have the added layer of a second signing key that can help indicate if a commit was not pushed by me. I would suggest that you do the same, but you can also use the SSH key used to push commits as a signing key. Its up to you. The instructions below will assume that you have created a new key. So let’s create that NEW key which will be used just for signing commits going forward:
$ ssh-keygen -t ed25519 -C "[email protected]"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/markd/.ssh/id_ed25519): /Users/markd/.ssh/code_sign
Enter passphrase (empty for no passphrase): superstrongpasswordhere
Enter same passphrase again: superstrongpasswordhere
Your identification has been saved in /Users/markd/.ssh/code_sign
Your public key has been saved in /Users/markd/.ssh/code_sign.pub
The key fingerprint is:
SHA256:egcmSeaTFeSsq4ytWHV24DVHCorQWhby8khYNI0uZvM [email protected]
NOTE: I suggest using the “No Reply” e-mail address that is supplied by github to cut down on spam. See Setting your commit email address for more details.
With our new signing key created, and protected with a secure password, we can now move on to configuring our local git configuration to use this new key for commit signing.
Configuring Git
The following steps will configure Git on your machine to use a SSH key for signing all commits going forward. It is also possible to only configure SSH key signing on individual repos. If you want to only enable signing on individual repos, be sure to eliminate the --global
option from the commands below and run these commands for each repository you wish to sign your commits.
$ git config --global commit.gpgsign true
$ git config --global tag.gpgsign true
$ git config --global gpg.format ssh
Now we will configure Git to use the SSH key that we created in the previous step:
If you are using an ssh-agent to manage ssh keys and passwords put the value of the key you wish to use from ssh-add -L
$ git config --global user.signingkey "ssh-ed25519 SHA256:egcmSeaTFeSsq4ytWHV24DVHCorQWhby8khYNI0uZvM"
If you are not using a ssh-agent, put the full path to the private key in the configuration
$ git config --global user.signingkey "/Users/markd/.ssh/code_sign"
You are now configured to use SSH key signing on your commits.
Testing Code Signing
With Git properly configured to use our SSH signing key, lets make a commit to a repository and see the effects that it has. Create a GitHub repo for testing, or create an empty commit in a repository of your liking as shown below:
$ git --allow-empty commit -m "this is a signed commit"
# if you are not using an ssh agent, you will be prompted for your SSH key password here
$ git push
Validating the Signature in GitHub
So if we log into GitHub and check our commit we will see it is signed and all is set right?
NOPE, we still need to tell GitHub about our signing key.
- Log into Github, and go into your settings, and select “SSH and GPG Keys”
- Click “New SSH key”
- Enter a title, and then change Key type to “Signing Key”
- Copy the contents from /Users/markd/.ssh/code_sign.pub and paste into the Key section
- Click Add SSH key
CAUTION: Ensure that you upload the contents of the “.pub” file only. While your private key is encrypted with a password it is bad practice to share that file with anyone.
Now verify that your latest commit shows as verified by going back to your commit history and look for the verified tag:
Success! If you look at the image above you will see that the “first commit” has no “Verified” tag on it. This was a commit done prior to adding signatures to the commit. Commits that have a valid signature will have the “Verified” tag on them.
One Last Thing
Once you have enabled commit signing for your account, you can tell GitHub to show any commits by your username that are not signed as “Unverified”. If you want to enable this feature, go into your settings and under “SSH and GPG keys” check the “Flag unsigned commits as unverified” checkbox. All code commits attributed to your user name will show one of three statuses:
- Verified
- Partially verified
- Unverified
For more details on this check out the GitHub Docs on About vigilant mode.
Once Vigilant mode is enabled, you will see “Unverified” on any commit you have made but did not sign:
Use caution when enabling this feature as it may cause some confusion when people view your old commits.
Conclusion
By enabling code signing on your GitHub commits you can help others know that the code committed by you is your code, and has not been changed in any way. Consumers of your code will still need to be vigilant to ensure that they are only using code that shows that it was cryptographically signed, but you can rest comfortably knowing that you have given them additional tools to help validate this state.