Signing commits with X.509 certificates Jump to heading
Many organisations already run a certificate authority. Engineers have smart cards or certificates issued at onboarding, revocation is handled centrally, and security teams would rather not introduce a parallel trust system of SSH keys or OpenPGP keys that nobody else manages. Git supports this third format: gpg.format = x509, which hands signing to an S/MIME-capable program such as gpgsm or smimesign. The signatures chain to your CA, and revocation follows the certificateโs lifecycle. The trade-off is weaker support outside the command line. This page covers the setup and the limits, as an alternative explored in GPG vs SSH commit signing.
When to use this approach Jump to heading
- Your organisation issues X.509 certificates to engineers and wants code signing tied to that identity system.
- Compliance requirements name a specific CA or require certificate-based signatures.
- Certificates live on smart cards or in an OS certificate store that already has the private key.
- Your verification happens mostly in your own gates, where you control the trust anchors.
- If your forgeโs web interface must show commits as verified and it does not trust your CA, SSH signing is the pragmatic choice.
Step 1 โ Choose the signing program Jump to heading
Git does not implement X.509 signing itself; it calls an external program with GPG-compatible arguments. Two are common.
# gpgsm (Linux, or anywhere GnuPG is installed)
gpgsm --version | head -1
# smimesign (macOS / Windows), installed from your package manager
smimesign --version Step 2 โ Make the certificate and its chain available Jump to heading
With gpgsm, import the personal certificate with its private key, then the CA chain. With smimesign, the certificate must already be in the OS store, typically pushed by device management.
# gpgsm: import a PKCS#12 bundle and the CA certificates
gpgsm --import alice.p12
gpgsm --import corp-root-ca.crt corp-issuing-ca.crt
gpgsm --list-secret-keys
# Note the ID line, e.g. ID: 0x4A3B2C1D Root CA certificates must be marked trusted before gpgsm will accept chains to them.
# Trust the corporate root by fingerprint
gpgsm --list-keys --with-colons corp-root-ca | awk -F: '/^fpr/{print $10; exit}'
echo "<ROOT_FPR> S" >> ~/.gnupg/trustlist.txt # Verification: the personal certificate validates to a trusted root
gpgsm --list-keys --with-validation [email protected] | grep -i validity Step 3 โ Configure Git to sign with X.509 Jump to heading
Set the format and program, and identify the certificate by its ID or email.
git config --global gpg.format x509
git config --global gpg.x509.program gpgsm # or smimesign
git config --global user.signingKey 0x4A3B2C1D # gpgsm ID, or the cert email for smimesign
git config --global commit.gpgSign true # Verification: sign and check
git commit --allow-empty -m "x509 signing test"
git log -1 --show-signature
git log -1 --format='%G? %GS' %GS reports the certificate subject for X.509 signatures, which is a distinguished name rather than an email.
Step 4 โ Verify in your own gates Jump to heading
Your CI or server hook needs the same trust anchors. Build an isolated gpgsm home with only the corporate root and issuing CA, then verify.
export GNUPGHOME="$RUNNER_TEMP/gnupg"; mkdir -p -m 700 "$GNUPGHOME"
gpgsm --import trust/corp-root-ca.crt trust/corp-issuing-ca.crt
echo "$(cat trust/corp-root.fpr) S" > "$GNUPGHOME/trustlist.txt"
git -c gpg.format=x509 -c gpg.x509.program=gpgsm log --format='%H %G?' "$BASE..HEAD" |
awk '$2!="G"{print; bad=1} END{exit bad}' The gateโs range logic is the same as for any other format; see verifying a range of commits in CI, not just the tip.
Step 5 โ Plan for revocation and certificate renewal Jump to heading
Certificates expire, often after a year or two, and are renewed with a new serial number. Old signatures remain cryptographically valid, but whether a verifier accepts a signature made by a since-expired certificate depends on its settings โ and many default to rejecting signatures whose certificate is expired now, not at signing time.
For gates that only verify new commits, this rarely matters: the certificate is current when the commit arrives. For historical audits it matters a great deal, so record the certificate fingerprint and validity window alongside each verification result rather than re-deriving them years later.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Will GitHub show X.509-signed commits as verified? Jump to heading
Only if the certificate chains to a CA GitHub trusts, which in practice means certificates issued by a small set of public CAs. Commits signed by a private corporate CA appear as unverified on the web while still verifying correctly in your own gates.
Can I mix X.509 with SSH signing in one repository? Jump to heading
Yes. Each commit records its own signature type, and Git chooses the verifier from the signature, not from gpg.format. Your gate must have trust configured for every format it accepts.
Do smart cards work with this? Jump to heading
Yes, and they are a common reason to choose X.509. With gpgsm, the smart card is reached through scdaemon; with smimesign, through the OS certificate store. The private key never leaves the card.
Related Jump to heading
- GPG vs SSH Commit Signing โ the parent topic and the two more common formats.
- Keyless Commit Signing with Sigstore Gitsign โ another certificate-based approach, with short-lived certificates.
- Signing Git Commits with a YubiKey โ hardware-backed keys for the OpenPGP and SSH formats.
- Rolling Out Signature Enforcement in Warn-Only Mode โ introducing any new format without blocking people.