Signing Git Commits Using S/MIME x509 Certificates

4 minute read

Because of my work with the DoD and their PKI environment, I’ve become accustomed to using S/MIME, or x509 certificates for nearly everything that requires some sort of encryption and digital signatures.

GitLab has a very friendly guide on signing with x509 certs, but will summarize here.

Import Private Keys

Linux

On Linux, import your S/MIME private key into the gpgsm keychain and let git know about this key.

1gpgsm --import myPrivateKey.pfx
2
3# enter decryption password (if password protected)
4# enter a password to protect upon every signing attempt
5 
6signingkey=$( gpgsm --list-secret-keys | egrep '(key usage|ID)' | grep -B 1 digitalSignature | awk '/ID/ {print $2}' )
7git config --global user.signingkey $signingkey
8git config --global gpg.format x509

Windows

If using WSL, just follow the above instructions. Otherwise, install the private key by double-clicking the .pfx private key file, use smimesign, and let git know it exists.

Install smimesign:

1choco install smimesign

Install the private key and check if Windows and smimesign can see it:

 1PS C:\Users\user> smimesign --list-keys
 2       ID: c961e80210a9abb335fcb07d12b95279e98bec3b
 3      S/N: 2efcadea455508fcc3aa4e248343891e
 4Algorithm: SHA256-RSA
 5 Validity: 2020-09-06 22:16:26 +0000 UTC - 2021-09-06 22:16:26 +0000 UTC
 6   Issuer: CN=Actalis Client Authentication CA G3,O=Actalis S.p.A.,L=Ponte San Pietro,ST=Bergamo,C=IT
 7  Subject: CN=########@####
 8   Emails: #######@####, ########@####
 9
10       ID: 3cfe348cfa5a5e50988140c34deabaa182f3ff13
11      S/N: 650000004cd458d5200b177b0c00000000004c
12Algorithm: SHA256-RSA
13 Validity: 2020-09-14 21:13:03 +0000 UTC - 2022-09-14 21:23:03 +0000 UTC
14   Issuer: CN=
15  Subject: CN=Andrew Tec,OU=Users+OU=Users
16   Emails:
17
18       ID: 2e40d9d6b835f0d2da93cbc87c0098a3508742d4
19      S/N: 4c891642042d877f8b4f3449f1fd401a
20Algorithm: SHA256-RSA
21 Validity: 2019-09-05 00:00:00 +0000 UTC - 2021-09-04 23:59:59 +0000 UTC
22   Issuer: CN=Sectigo RSA Client Authentication and Secure Email CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB
23  Subject:
24   Emails: #######@######.com, ######@######.com
25
26       ID: 10e2816286ccf19ff775a7193d63aa40b8e51a72
27      S/N: 971c337bb967a32a4d5a408a3e604379
28Algorithm: SHA256-RSA
29 Validity: 2021-09-01 00:00:00 +0000 UTC - 2023-09-01 23:59:59 +0000 UTC
30   Issuer: CN=Sectigo RSA Client Authentication and Secure Email CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB
31  Subject:
32   Emails: ######@#####.com, #####@#####.com
33
34       ID: 03273015b548733da94ab4f82606c8572f1fe605
35      S/N: 650000008888cb5826339aab42000000000088
36Algorithm: SHA256-RSA
37 Validity: 2021-06-28 17:45:07 +0000 UTC - 2023-06-28 17:55:07 +0000 UTC
38   Issuer: CN=TrabusNet-TRABUSSERV-CA
39  Subject: CN=Andrew Tec\, Dev,OU=Users+OU=Admins+OU=Wireless Local Admin
40   Emails:

Copy over the value of ID: of the cert you want to sign with into your global config file:

git config --global user.signingkey <VALUE_OF_ID>

Finally, let git know to use smimesign for x509 certs.

1git config --global gpg.x509.program smimesign
2git config --global gpg.format x509

## Sign Git Commits

If everything is setup properly signing commits should just work by using the `-S` flag, or `-s` when signing tags and merges.

```bash
git commit -S -m "my commit message"
git log --show-signature
commit 01df67b4c8a75fb2c15c58b9002a178cebeba99e (HEAD -> branch, origin/branch)
gpgsm: Signature made 2021-09-30 19:22:46 using certificate ID 0xB8E51A72
gpgsm: Note: non-critical certificate policy not allowed
gpgsm: Note: non-critical certificate policy not allowed
gpgsm: Note: non-critical certificate policy not allowed
gpgsm: CRLs not checked due to --disable-crl-checks option
gpgsm: Good signature from "/EMail=#####@#####.com"
gpgsm:                 aka "######@#####.com"
Author: Andrew Tec <#####@#####.com>
Date:   Thu Sep 30 19:22:46 2021 +0000

    my commit message

commit e3e34e2558ca61ed0

If using GitLab, it should show up with a [VERIFIED] icon.

Errors

If you run into any odd errors when signing, you can prepend GIT_TRACE=1 to the git command. It should show you any subsequent gpgsm command it was trying to invoke.

1atec@pc:workspace/$ GIT_TRACE=1 git commit -S -m "my message"
219:22:46.665646 git.c:455               trace: built-in: git commit -S -m 'update build script'
319:22:46.668998 run-command.c:666       trace: run_command: gpgsm --status-fd=2 -bsau 0xB8E51A72
419:22:47.018986 run-command.c:666       trace: run_command: git maintenance run --auto --no-quiet
519:22:47.022070 git.c:455               trace: built-in: git maintenance run --auto --no-quiet

In my case, I ran into 2 odd errors. gpgsm: error creating signature: Line too long <GPG Agent> and gpgsm: no CRL found for certificate.

Line to long Error

The first was caused by a malformed~/.gnupg/trustlist.txt file. I simply deleted it and upon reattempt it prompted me to fix itself. It will mark the key as trusted within the ~/.gnupg/trustlist.txt file.

Ref: https://www.schmut.com/cheat-sheets/s-mime-key-management

CRL Error

The second error was solved by adding the --disable-crl-checks flag. You can make this flag permanent by adding the flag to the ~/.gnupg/gpgsm.conf file and restarting gpg-agent.

1echo "disable-crl-checks" > ~/.gnupg/gpgsm.conf
2gpgconf --reload gpg-agent

For more information on gpgsm flags and the configuration file you can view the man page man gpgsm or reference Oracle’s help site: https://docs.oracle.com/cd/E86824_01/html/E54763/gpgsm-1.html

Pinentry Errors

There are some systems where pinentry is installed, but gpg doesn’t know it exists. These errors are seen as ioctl for device <Pinentry> Errors or No pinentry <GPG Agent>

You designate a pinentry program with:

1echo "pinentry-program /usr/bin/pinentry" >> ~/.gnupg/gpg-agent.conf
2gpgconf --reload gpg-agent

If on macOS, brew install pinentry-mac to install the program then designate $(brew --prefix)/bin/pinentry-mac.

And ensure the terminal TTY environment always has the current gpg configurations by adding the following to your .bashrc (or .zshrc) file.

1"GPG_TTY=$(tty)"
2"export GPG_TTY"
3source ~/.bashrc

If correctly setup, you should see this: