Git supports using GPG to sign commit records, but GPG is complicated to use. Git started supporting SSH signatures in 2.34, and since we all have SSH keys, it’s time to turn on signatures. But GitHub didn’t support displaying SSH signatures for a long time after this feature was released, so I didn’t push it forward. Yesterday GitHub announced that it officially supports SSH signatures. I’m going to share a little bit about it with you today.

First, why do you need to sign your Git commits? That’s because it’s easy to fake commit identities in Git.

We know that Git requires that the author’s name and email address be specified before committing.

1
2
git config user.name taoshu
git config user.email hi@taoshu.in

But these two configurations can be filled in as you wish. Anyone can claim to be “taoshu”. To protect my reputation, I can sign my commits with asymmetric cryptography and then publish my public key. This way, other people can verify that the Git changes were submitted by me based on this public key. As long as I don’t reveal my private key, it’s hard for others to pretend to be me and do bad things.

The most common tool for implementing signatures is GPG, and if you’ve played around with Linux, you’re no stranger to the fact that many distributions use GPG as a package manager to sign packages and prevent bad guys from forging them.

However, GPG is not newbie-friendly, so the average Git player rarely turns on signatures. The situation didn’t change until OpenSSH 8.0 was released. This version of OpenSSH supports signing arbitrary data.

The signature feature is broken in OpenSSH 8.7, so it is recommended to use 8.8 or later.

The tool for SSH signing is ssh-keygen, which I guess many people only use when generating SSH keys for the first time, and then never touch it again. It’s a bit strange 🤔 but the signature checking feature is also provided by ssh-keygen.

Suppose there is a file /tmp/a.txt and we want to sign it using ~/.ssh/id_ed25519, we can execute the following command.

1
ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n file /tmp/a.txt

The function of each parameter is as follows.

  • -Y sign indicates that the signature is calculated
  • -f specifies the private key
  • -n file is to assign a type to the signature
  • -file is defined by us, different types of signatures do not conflict

After execution, we get a signature file /tmp/a.txt.sig, which looks like this.

1
2
3
4
5
6
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgulKNunkcVxiDzY0wmqJo4rAG9L
ClGRq9mMfA/PqsKYkAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
AAAAQP1FljU1ZQ327DZE11wjHIDgz1s0ULi7QO5rhg+MyEn12nwkV0fk69qDqmcpAE562x
pIxa+yaGuMV6hK97Hq+gE=
-----END SSH SIGNATURE-----

With a signature, we can verify that someone has not tampered with the contents of the file. To verify the signature a list of public keys is needed.

1
2
hi@taoshu.in ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILpSjbp5HFcYg82NMJqiaOKwBvSwpRkavZjHwPz6rCmJ ts@tc
...

The first column is the identification of the public key, which we use here as the email address. The second column is the public key type, and the part after that is the content of the public key, which is the content of ~/.ssh/id_ed25519.pub. Each public key occupies one line.

The signature check command is as follows.

1
ssh-keygen -Y verify -f allowed_signers -I hi@taoshu.in -n file -s /tmp/a.txt.sig < /tmp/a.txt

The function of each parameter is as follows.

  • -Y verify indicates that the signature is to be verified
  • -f to specify the public key list file
  • -I specifies that the public key identifier is to be used
  • -n file needs to be consistent with the signature
  • -s specifies the file where the signature is located

Finally, pass the contents of the file to ssh-keygen via redirection, and if the verification passes, you will get the following result.

1
Good "file" signature for hi@taoshu.in with ED25519 key SHA256:19/J4WKT7flBNcfmqQUqyAZeH4TdhMf5f0u+a4fZj1c

If someone modifies the contents of the file, the following results are obtained.

1
2
Signature verification failed: incorrect signature
Could not verify signature.

Considering that most Git users have their own SSH keys, wouldn’t it be a waste not to support SSH signing? So Git 2.34 includes SSH signing.

We need to add the following configuration.

1
2
3
4
5
6
7
8
9
# Signing with SSH
git config gpg.format ssh
# Specify the SSH private key file
git config user.signingKey ~/.ssh/id_ed25519.pub
# Specify the trusted public key list file
git config gpg.ssh.allowedSignersFile "$HOME/.config/git/allowed_signers"
# Enable automatic signature (optional)
git config commit.gpgsign true
git config tag.gpgsign true

After a bit of work, you’re ready to go. All Git commits will then be signed using SSH. If you don’t have automatic signing turned on, you can temporarily turn it on at commit time with the -s parameter.

With the default configuration, we don’t see any difference between signed commits and normal commits. If you want to show the signature information, you need to specify the -show-signature parameter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
git show --show-signature |head
commit 6292a43f184e8a347b6c8b4fe08191920c0e22a5
Good "git" signature for hi@taoshu.in with ED25519 key SHA256:19/J4WKT7flBNcfmqQUqyAZeH4TdhMf5f0u+a4fZj1c
Author: taoshu <hi@taoshu.in>
Date:   Wed Aug 24 07:55:54 2022 +0800

    Support for generating toString/parser functions for TS enumerated types

diff --git a/autoload/lv.vim b/autoload/lv.vim
index 22a6b38..a08deb9 100644
--- a/autoload/lv.vim
...

This time we see the Good "git" signatures ... validation message.

If you are using tig, you should add the same parameters, or turn on the following configuration in ~/.tigrc.

1
2
set log-options = --show-signature
set diff-options = --show-signature

Recall our previous example, when the value of -n is file, the checkmark message is Good "file".... Now the message is Good "git"..., which means that Git is using the -n git argument.

But there’s a problem: Where does Git store the signature information? The answer is the object corresponding to the commit or tag.

We can use cat-file to see what the object holds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ git cat-file commit 6292a43f184e8a347b6c8b4fe08191920c0e22a5
tree c17c1e92312dc7303018c3dc3cd25d26007305e2
parent e71d24c876aa6e82a69e1566623083923edcab03
author taoshu <hi@taoshu.in> 1661298954 +0800
committer taoshu <hi@taoshu.in> 1661298954 +0800
gpgsig -----BEGIN SSH SIGNATURE-----
 U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgulKNunkcVxiDzY0wmqJo4rAG9L
 ClGRq9mMfA/PqsKYkAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
 AAAAQP1FljU1ZQ327DZE11wjHIDgz1s0ULi7QO5rhg+MyEn12nwkV0fk69qDqmcpAE562x
 pIxa+yaGuMV6hK97Hq+gE=
 -----END SSH SIGNATURE-----

Support for generating toString/parser functions for TS enumerated types

Look at the line gpgsig, which indicates the current submission signature information. Each line after that has a space in front of it, which means that it’s the same line as gpgsig.

So how does Git sign? I didn’t look at the source code. But my guess is that it removes the gpgsig signature information and then calculates a signature for the rest of the content, with a signature type of git.

So I saved the corresponding content as a file and signed it, and it’s exactly the same as the gpgsig value.

Git signatures are a good solution to the problem of disguising your identity. But it’s not cool enough if the corresponding feature doesn’t show up on GitHub. So when Git 2.34 was first released, users suggested that GitHub add support for it. It finally went live yesterday 😄

Although GitHub supports displaying SSH signatures, you need to upload the keys for signing and for authentication separately. You need to specify the type when uploading. Even if you are using the same key, you will have to upload it again.

Once you upload the public key, GitHub will display the corresponding SSH signature.

GitHub shows the SSH signature

Reference.

  • https://www.agwa.name/blog/post/ssh_signatures
  • https://blog.dbrgn.ch/2021/11/16/git-ssh-signatures/
  • https://git-scm.com/docs/signature-format
  • https://taoshu.in/git/ssh-sign.html