Being involved in various projects with a high cadence of commits every day makes it easier for bad actors to try and slip in nasty stuff - if we don’t sign our commits, that is.
Recently, a few stories about spoofed commits made it into my Twitter feed, namely by Eddie Jaoude and David Flanagan, aka rawkode:
This why you MUST sign your commits!!
— Eddie Jaoude | EddieHub | Open Source GitHub Star (@eddiejaoude) June 19, 2023
So when someone tries to spoof your git commits, it clearly shows "unverified" and you know not to trust it
Big thank you to @intrigus_ who showed me from the GitHub logs we can see who it is - I have reported this repo and user to GitHub pic.twitter.com/kvluaNPDt1
Eddie already hints at this blog post’s topic in his tweet: Signing commits is important. But what had happened? A supposedly bad actor over at GitHub had committed to a project, hijacking Eddie’s GitHub identity and making the commit look like Eddie himself had indeed created it. David replied, describing how he apparently managed to sneak a spoofed (non-malicious) commit into the codebase of VSCode. 🤯
Fun story, VSCode previously declined to support signing commits; so I impersonated the maintainer and opened a PR and one of his colleagues merged it thinking it was the maintainer 😅
— David Flanagan (@rawkode) June 19, 2023
So, this way of spoofing commits is not only proven to work but also relatively easy to execute, and there’s a ton of material online that explains precisely how to do it - even on Twitter. Time for us to act!
What does ‘signing commits’ mean?
Forget about source code repositories and Git commits for a brief moment and think about your last (job) interview. You probably had a transcript of records from your university or maybe a letter of recommendation from your previous employer - but would your interviewer trust its contents without the signature of your university’s chancellor or your former manager? - probably not!
So why do we keep trusting unsigned commits? I don’t know, but when asking around, it’s because people either don’t know about the features that GitHub and GitLab put at their disposal or get set back by the concept of signing keys.
In the old days, when only the ugly method of signing commits was around (looking at you, GPG keys!), this was understandable. Anyways, there are more straightforward ways of signing your commits these days, and I’ll try to give an overview of their advantages and shortcomings in the following paragraphs. Let’s start with…
The Good
Picking up on the title of this blog, I’ll start with the ‘good’ option. A good option for signing commits to me is defined by two qualities:
- ease of use
- availability to possible project members
Ease of use in this context means that you don’t need to read pages of docs just to be able to generate and maintain your signing keys. Availability means, that you don’t need those keys at hand when creating a commit, an underrated quality in times of ephemeral developer environments like GitPod or CodeSpaces, in my opinion.
Those qualities are where gitsign shines. It’s a lightweight CLI that can sign your commits keyless, using your OIDC identity from GitHub, Google, or Microsoft. Installation via brew
or go install
is straightforward, and configuration for usage with Git can happen on a global or per-repository basis. For more information, head to the project’s GitHub repository.
Upon creating a commit, gitsign will create a unique authentication URL and write it to your CLI. If you follow this link, you will be taken to gitsign’s authentication page, where you can choose from the three providers mentioned above. Once authenticated, the creation of your commit will commence, with a signature bearing your OIDC signature.
The gitsign authentication challenge before and after successful authentication using sigstore.
Let’s inspect a commit signed this way:
$ git log --show-signature -1
commit b0d82aeb3ae2bb053542ebd306e135482152efae (HEAD -> main)
tlog index: 28881174
gitsign: Signature made using certificate ID
0xd02086681ee9733cf5abab19a08dcbebdd44cf82 |
CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from
[dbodky@gmail.com](https://github.com/login/oauth)
Validated Git signature: true
Validated Rekor entry: true
Validated Certificate claims: false
WARNING: git verify-commit does not verify cert
claims. Prefer using `gitsign verify` instead.
Author: Daniel Bodky <dbodky@gmail.com>
Date: Wed Jul 26 21:24:35 2023 +0200
Commit signed via gitsign
We see some output generated by gitsign: It displays the certificate ID and the CN generating it, as well as some information regarding the validation of the signature and its claims. It also prints a warning that it can’t verify cert claims and that we should use gitsign verify instead. Let’s do this next.
gitsign will need two pieces of information from us: the email of the user’s OIDC identity as well as the OIDC auth endpoint used - in this example, I chose GitHub:
$ gitsign verify --certificate-identity=dbodky@gmail.com \
--certificate-oidc-issuer=https://github.com/login/oauth
tlog index: 28881174
gitsign: Signature made using certificate ID
0xd02086681ee9733cf5abab19a08dcbebdd44cf82 |
CN=sigstore-intermediate,O=sigstore.dev
gitsign: Good signature from
[dbodky@gmail.com](https://github.com/login/oauth)
Validated Git signature: true
Validated Rekor entry: true
Validated Certificate claims: true
We can observe a difference: This time, the certificate claims have been validated as well - we made sure that the signature has indeed been created by the entity contained in the signature. It does so by looking up the certificate on Rekor, which you can think of as a free, immutable database for signed metadata hosted by the cosign project.
This approach is one of the disadvantages when using gitsign for signed commits: Platforms like GitHub or GitLab can’t look up that information when displaying our commits. This means we will not get a shiny ‘Verified’ badge for our commits. If we wanted to test the integrity of our signatures, we would need to do so in a CI/CD pipeline, e.g. by leveraging the gitsign CLI within a job and failing upon unsuccessful verification.
While the gitsign project states that it would like to work together with GitHub to provide proper signature verification in the future, this shortcoming is the reason why gitsign is just the good and not the best choice presented in this blog post, which brings us to…
The Best
I mentioned signing keys at the beginning of this post already, and the best method to sign commits right now (in my opinion) uses signing keys indeed, but no worries - most of you will be familiar with the concept already: it’s SSH keys!
Over the course of 2022, both GitHub and GitLab (in all tiers) announced the support of SSH keys for commit signing - a milestone! I added their respective announcements below for details.
This means we can take the same key we already use for authenticating with the Git servers of GitHub/Lab and sign our commits with it, too! And different to gitsign signatures, both platforms will display a ‘Verified’ badge if we do so.
In order for this to work, you will need to add your SSH public key to your account’s list of verified signing keys:
You also need to configure your global/local Git configuration to use this SSH key for signing your commits:
$ git config gpg.format ssh
$ git config user.signingkey /home/daniel/.ssh/githubkey
That’s all! You can go on and sign your commits with one of your SSH keys now and let everyone know that it is indeed you who created the commit.
You don’t need to use the same keys for authentication and signing - just like you can use different keys to authenticate with different servers, you can use different keys for authenticating and signing.
We looked at two out of three promised possibilities of signing your Git commits now, and we’re left with…
The Ugly
After gitsign and SSH keys, let’s look at GPG keys. Now, to make things clear from the start: ugly doesn’t mean ‘bad’, and it’s a personal opinion. After all, using GPG keys is natively supported by Git, just like SSH keys, and you will also end up with a ‘Verified’ badge.
However, working with them always felt more clunky than using SSH keys for me, mainly because of the additional set of CLI commands you need for generating, managing, and distributing your keys. This adds cognitive load to the process of setting up and configuring your repositories and signing commits, and, speaking for myself, leads to a lot of googling whenever I need to export my GPG keys.
If, for one reason or another, you want to use GPG keys, the process is similar to using SSH keys: First, create a GPG key and add it to your verified keys on GitHub/Lab:
Then, again, you need to configure either your local or global Git settings to use the said key:
$ git config --unset gpg.format # makes sure to use default format
$ git config user.signingkey XXXXXXXXXXXXXXXX
$ git config commit.gpgsign true
You can now use your GPG key(s) to sign commits in the same way as SSH keys.
Summing it up
We looked at the good, the better, and the ugly in this blog post, and all of them got their justification(s) for being used when signing commits. While I see the potential for gitsign in CI/CD usage and custom policies, we only get proper integration with GitHub and GitLab for SSH/GPG keys (for now!).
Below I created a small table comparing the key (missing) qualities of the methods introduced in this blog post.
Web/Chain of trust is a quality that hasn’t come up until now, but I wanted to add a few thoughts in the end - one of the core features of GPG keys is that they can be signed by others with their GPG keys. Over time, it’s possible to establish a web of trust this way, with others confirming the authenticity of your key(s). SSH keys and gitsign can’t provide such features.
In cases where you just want to verify the authenticity of a commit e.g. from within your organization, this doesn’t really matter, but it gets interesting when you want to go the extra mile and double-check a ‘foreign’ contributor:
$ curl https://github.com/<username>.gpg | gpg -v
This command will download all GPG signing keys the contributor added to their GitHub profile and inspect them (without importing them). All you need to do is find the key that shows up on the ‘Verified’ badge and have a look at its signatures - has it been signed by others? That’s a good sign.
I hope I provided a thorough overview of the different ways of signing your Git commits, but if you got suggestions, questions, or other comments, feel free to submit them below!