linux, puppet, and stuff that comes along for the ride

Troubleshooting x509 commit signing with SMIME / gpgsm

Some notes on signing commits with SMIME and x509. Part of an investigation into why commits showed as unverified in GitLab.

My client-side setup is based on a discussion in a GitLab product issue and the GitLab Development Kit notes.

The purpose of this post is to add fixes and troubleshooting steps that I found helpful when it didn’t work.

Edit: in a follow-up post, I create a test CA and use that to generate verified commits.

Self-signed certificates (from the issue thread) proved to be a dead end;  the certificate (details) were missing off the signed commit.  One of the tests GitLab does to verify commits is that the signature has the certificate included.

Viewing git configuration

Git Client configuration changes are needed. Since I was testing this, I wanted to isolate these changes to the test repository, so I put the settings in “local” – .git/config. View the visible config with:

git config --list --show-origin --show-scope

On RHEL9 flavours:

dnf install gnupg2-smime pinentry

The first provides gpgsm, the second fixes:

$ gpgsm --import codesigning.p12
gpgsm: directory '/home/ben/.gnupg' created
gpgsm: keybox '/home/ben/.gnupg/pubring.kbx' created
gpgsm: total number processed: 0
gpgsm: error importing certificate: No pinentry

gpgsm: error at “bag.encryptedData”

The procedure involves creating a PKCS#12 / PFX file with the private keys and certificates insisw. I found an apparent compatibility issue between the supplied RHEL9 versions:

OpenSSL 3.0.7 1 Nov 2022 (Library: OpenSSL 3.0.7 1 Nov 2022)

gpgsm (GnuPG) 2.3.3
libgcrypt 1.10.0-unknown
libksba 1.5.1

Supported algorithms:
Cipher: 3DES, AES128, AES192, AES256, SERPENT128, SERPENT192, SERPENT256, 
   SEED, CAMELLIA128, CAMELLIA192, CAMELLIA256
Pubkey: RSA, ECC, ECC
Hash: MD5, SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224, WHIRLPOOL

OpenSSL creates the PFX file:

# no password is set for testing purposes. Don't do this for real.
openssl pkcs12 -export -inkey private.key -in public.crt \
    -out codesigning.p12 -passin pass: -passout pass:

Importing it into GPG fails:

gpgsm: encryptedData error at "pkcs5PBES2-params", offset 123
gpgsm: error at "bag.encryptedData", offset 49
gpgsm: error parsing or decrypting the PKCS#12 file
gpgsm: total number processed: 0

This probably isn’t the right fix, but it unblocked me – create the PFX file with -legacy:

openssl pkcs12 -export -inkey private.key -in public.crt \
    -out codesigning.p12 -legacy -passin pass: -passout pass:

List imported keys:

gpgsm --list-keys

How Git uses gpgsm

When trying to sign commits, get more of an idea about what’s going on with git tracing:

$ GIT_TRACE=1 git commit -a -m 'x509 test commit'
13:19:26.059161 git.c:460 trace: built-in: git commit -a -m 'x509 test commit'
13:19:26.060550 run-command.c:655 trace: run_command: 
                gpgsm --status-fd=2 -bsau 0xB77E3069
error: gpg failed to sign the data
fatal: failed to write commit object

The gpgsm command is key to both viewing and creating signatures.

The man page for gpgsm does not document arguments –status-fd, -b, -s.

These are actually switches for the default gpg signing command, but Git doesn’t change its behaviour when the signing command is changed. This is .. odd.

The gpgsm command there specifies what key to use to sign with (this comes from one of the Git config steps) and you can run this direct:

If you’re set up correctly, it hangs because it’s expecting input.

$ gpgsm --status-fd=2 -bsau 0xB77E3069
gpgsm: CRLs not checked due to --disable-crl-checks option

root certificate is not marked trusted

If you’ve missed the step of  trusting the issuer it doesn’t hang:

$ gpgsm --status-fd=2 -bsau 0xEF49BCD0
gpgsm: root certificate is not marked trusted
gpgsm: fingerprint=C3:7F:F7:10:59:5D:68:A2:7C:2A:CB:71:51:11:DF:81:EF:49:BC:D0
gpgsm: DBG: BEGIN Certificate 'issuer':
gpgsm: DBG: serial: 4C0F22F2E7AD1AC9D7BBA333E891047849770D59
gpgsm: DBG: notBefore: 2024-01-04 11:59:42
gpgsm: DBG: notAfter: 2025-01-03 11:59:42
gpgsm: DBG: issuer: CN=codesigning
gpgsm: DBG: subject: CN=codesigning
gpgsm: DBG: hash algo: 1.2.840.113549.1.1.11
gpgsm: DBG: SHA1 Fingerprint: C3:7F:F7:10:59:5D:68:A2:7C:2A:CB:71:51:11:DF:81:EF:49:BC:D0
gpgsm: DBG: END Certificate
gpgsm: after checking the fingerprint, you may want to add it 
       manually to the list of trusted certificates.
gpgsm: can't sign using '0xEF49BCD0': Not trusted
[GNUPG:] INV_SGNR 10 0xEF49BCD0
[GNUPG:] INV_RECP 10 0xEF49BCD0

Do this usually with:

gpgsm --import /path/to/foo.crt

Refer also to the linked documentation and populate ~/.gnupg/trustlist.txt.

Reload gpg agent a lot

If you import a certificate OR change the trust list, it won’t work right away.

You’ll just keep getting ‘Not trusted’ errors.

gpgconf --reload gpg-agent

Test that signing is working outside of Git

To check what it’s sending back to Git, do a simple test using the command git is using.  If you’re not getting signatures, it might be because there’s a failure that Git isn’t reporting back.

$ echo foo | gpgsm --status-fd=2 -bsau 0xB77E3069
gpgsm: CRLs not checked due to --disable-crl-checks option
[GNUPG:] SIG_CREATED D 1 8 00 20240124T193847 91416CA9C1AA6955C1BF444F8A111C69B77E3069
gpgsm: signature created
-----BEGIN SIGNED MESSAGE-----
<snip>
-----END SIGNED MESSAGE-----

Git Trace is your friend

Additional output is also provided when showing signatures.  I can’t see an obvious way to make use of the gpgsm command here, as you’d need the contents of the temporary file.  But at least you can see what’s going on under the hood.

$ GIT_TRACE=1 git log --show-signature
12:07:57.566707 git.c:460 trace: built-in: git log --show-signature
12:07:57.568838 run-command.c:655 trace: run_command: unset GIT_PAGER_IN_USE; LESS=FRX LV=-c less
12:07:57.570666 run-command.c:655 trace: run_command: 
                gpgsm --status-fd=1 --verify /tmp/.git_vtag_tmpW9ZDWl -
commit b4c1a221e3f2e3a1134bb29c5bf6a12b639a5503 (HEAD -> signtest1)
gpgsm: Signature made 2024-01-24 12:06:42 UTC
gpgsm: using rsa2048 key C37FF710595D68A27C2ACB715111DF81EF49BCD0
gpgsm: CRLs not checked due to --disable-crl-checks option
gpgsm: Good signature from "/CN=codesigning"
gpgsm: aka "john.doe@example.com"
Author: John Doe <john.doe@example.com>
Date: Wed Jan 24 12:06:42 2024 +0000

five

Leave a comment