smimesign is an S/MIME / X.509 signing tool for Git commits and tags.
This fork is maintained at droren/smimesign and adds Linux support beyond the
original github/smimesign project, including PKCS#11 smart-card signing.
The current implementation supports:
- Windows certificate store signing
- macOS keychain signing
- Linux PKCS#12 signing
- Linux PKCS#11 smart-card signing
- Git X.509 signing with
gpg.format=x509 - extraction of embedded signing certificates from Git commits
Use smimesign when your organization issues X.509 certificates for user
identity and you want Git commits and tags signed with those certificates
instead of OpenPGP keys.
Typical setups:
- Windows workstation with a user certificate in
CurrentUser\My - Linux workstation with a YubiKey or CAC exposed through PKCS#11
- Linux or macOS workstation using a PKCS#12 file
main.goandcommand_*.go: main CLIcertstore/: OS-specific certificate store accessietf-cms/: CMS / PKCS#7 signing and verificationcmd/git-x509-cert/: helper for extracting and displaying commit signing certsMakefile: build and test targets
Build the binary:
git clone https://github.com/droren/smimesign.git
cd smimesign
go build -o smimesign.exe .Put smimesign.exe somewhere on PATH, for example:
New-Item -ItemType Directory -Force $HOME\bin | Out-Null
Copy-Item .\smimesign.exe $HOME\bin\smimesign.exe
$env:Path = "$HOME\bin;$env:Path"The Windows installer also ships offline documentation in the installed
docs\ directory, including:
README.mdsmimesign-help.txtgit-x509-cert-help.txt
smimesign on Windows reads identities from the Windows certificate store.
The normal expectation is that the signing certificate is present in the
current user's personal store:
- Store:
Current User - Logical store:
Personal/My
To list what smimesign can currently use:
smimesign.exe --list-keysIf multiple certificates match the same Git identity, the current
implementation prefers the most signing-oriented certificate automatically. If
ambiguity still remains, set a persistent certificate fingerprint with
SMIMESIGN_CERT_ID.
Example:
$env:SMIMESIGN_CERT_ID = "0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"To persist it for future PowerShell sessions:
setx SMIMESIGN_CERT_ID 0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEFBuild the binary:
git clone https://github.com/droren/smimesign.git
cd smimesign
go build -o smimesign .Put it on PATH:
install -m 0755 ./smimesign ~/.local/bin/smimesign
export PATH="$HOME/.local/bin:$PATH"Linux packages should also install offline documentation under
/usr/share/doc/smimesign/, including the README, CLI help text, and the
PKCS#11 wrapper example.
Linux supports two primary identity sources.
export SMIMESIGN_P12=/path/to/user.p12
export SMIMESIGN_P12_PASSWORD='your-password'
smimesign --list-keysSet the PKCS#11 module path:
export SMIMESIGN_PKCS11_MODULE=/usr/lib64/pkcs11/opensc-pkcs11.so
smimesign --list-smartcard-keysIf your token requires a PIN, you can either:
- export
SMIMESIGN_PKCS11_PINfor the current shell, or - use a wrapper script that prompts on
/dev/ttyand then execssmimesign - expect some tokens to require the PIN even for
--list-smartcard-keys
An example wrapper is provided in
examples/smimesign-pkcs11-wrapper.sh.
Minimal wrapper:
#!/usr/bin/env bash
set -euo pipefail
if [[ -z "${SMIMESIGN_PKCS11_PIN:-}" ]]; then
printf 'YubiKey PIN: ' > /dev/tty
IFS= read -r -s SMIMESIGN_PKCS11_PIN < /dev/tty
printf '\n' > /dev/tty
export SMIMESIGN_PKCS11_PIN
fi
exec /path/to/smimesign "$@"When multiple certificates match the same identity, smimesign prefers a
signing-capable certificate automatically. To pin one explicitly, set
SMIMESIGN_CERT_ID:
export SMIMESIGN_CERT_ID=0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEFsmimesign now prints and prefers SHA-256 certificate fingerprints by
default. Exact legacy SHA-1 fingerprints are still accepted for backwards
compatibility, and longer suffix matching is still supported for manual
disambiguation.
Some environments do not expose the smart card directly inside the shell or
container where Git is running. In that case, smimesign itself still works
fine, but it needs help from a wrapper that provides:
- the forwarded PKCS#11 module path
- any forwarding environment such as
P11_KIT_SERVER_ADDRESS - the certificate fingerprint to prefer for signing
- a
/dev/ttyPIN prompt when Git invokes signing non-interactively
Typical setup:
export P11_KIT_SERVER_ADDRESS=unix:path=/run/user/1000/p11-kit/pkcs11
export REMOTE_PKCS11_MODULE=/usr/lib64/pkcs11/p11-kit-client.so
pkcs11-tool --module "$REMOTE_PKCS11_MODULE" -LIf that shows the token, install the wrapper and point Git to it:
install -m 0755 examples/smimesign-pkcs11-wrapper.sh ~/.local/bin/smimesign
install -m 0755 ./smimesign ~/.local/bin/smimesign-real
git config --global gpg.x509.program "$HOME/.local/bin/smimesign"
git config --global gpg.format x509
git config --global commit.gpgsign true
git config --global tag.gpgSign true
git config --global log.showSignature trueThen customize the wrapper defaults:
- set
REAL_SMIMESIGNto the real binary path if different - set
PKCS11_MODULE_DEFAULTorREMOTE_PKCS11_MODULE - set
CERT_ID_DEFAULTorSMIMESIGN_CERT_IDto the signing certificate
Validation flow:
smimesign --list-smartcard-keys
printf "forwarded pkcs11 test\n" > payload.txt
smimesign --sign -u alex.example@example.invalid -b payload.txt > payload.sig
smimesign --verify payload.sig payload.txt
git commit -S -m "x509 signing test"macOS uses the system keychain. Build and install like any other Go binary:
git clone https://github.com/droren/smimesign.git
cd smimesign
go build -o smimesign .List available identities:
./smimesign --list-keysFor Git 2.19 and newer, configure smimesign as the global X.509 signing
program:
git config --global gpg.x509.program smimesign.exe
git config --global gpg.format x509
git config --global commit.gpgsign true
git config --global tag.gpgSign true
git config --global log.showSignature true
git config --global user.name "Alex Example"
git config --global user.email alex.example@example.invalid
git config --global user.signingkey alex.example@example.invalidgit config --global gpg.x509.program /absolute/path/to/smimesign
git config --global gpg.format x509
git config --global commit.gpgsign true
git config --global tag.gpgSign true
git config --global log.showSignature true
git config --global user.name "Alex Example"
git config --global user.email alex.example@example.invalid
git config --global user.signingkey alex.example@example.invalidAfter configuration, validate with:
git commit -S -m "x509 signing test"
git log --show-signature -1When more than one certificate matches the same Git user identity, the current implementation does this:
- If
SMIMESIGN_CERT_IDis set, that fingerprint wins. - Otherwise,
smimesignprefers the most signing-oriented certificate. - If the choice is still ambiguous, signing fails with guidance to set
SMIMESIGN_CERT_ID.
In practice, this means a certificate with KU=contentCommitment is preferred
over a client-authentication certificate with EKU=clientAuth.
During verification, the defaults are intentionally stricter:
- local signing identities are not treated as trust roots unless explicitly
enabled with
--trust-local-certsorSMIMESIGN_TRUST_LOCAL_CERTS=1 - certificate usage is checked against a signing-oriented policy:
emailProtection,codeSigning, Microsoft commercial code signing, and Microsoft document signing are accepted; plain client-auth certificates are not - if your environment needs broader compatibility than that policy, relax it
with
--allow-any-ekuorSMIMESIGN_ALLOW_ANY_EKU=1 - revocation checking can be enabled with
--revocation-check=ocsporSMIMESIGN_REVOCATION_CHECK=ocsp - if you want revocation problems to be warnings rather than hard failures,
use
--revocation-check=ocsp-softorSMIMESIGN_REVOCATION_CHECK=ocsp-soft
For Git history inspection, ocsp-soft is often the better operational
default. In many organizations, certificates are revoked when an employee
leaves the company. That revocation is still useful information, but it should
not make existing repository history unreadable or unusable. Otherwise, an
ordinary shared repository would eventually become impossible to verify without
rewriting history or splitting the project into new repositories every time a
developer leaves. In that model:
- the signature still proves who signed the commit at the time
- revocation becomes an important warning about current trust state
- historical code remains accessible and reviewable
Recommended practice:
- set
user.signingkeyto the Git email address present in the certificate - set
SMIMESIGN_CERT_IDif your environment has multiple matching certs
List identities from the OS store or PKCS#12:
smimesign --list-keysList smart-card identities on Linux:
smimesign --list-smartcard-keysCreate and verify a detached signature:
smimesign --sign -u user@example.com -b file.txt > file.txt.sig
smimesign --verify file.txt.sig file.txtExtract certificates from an existing CMS signature:
smimesign --dump-certs file.txt.sig > certs.pemThere are two supported ways to inspect the signing certificate embedded in a Git commit.
Build it once:
make build-toolsThis creates build/tools/git-x509-cert.
Show the signer certificate from HEAD in human-readable form:
build/tools/git-x509-certShow a specific commit:
build/tools/git-x509-cert 6f274166f5db657127bfd29a07a49e46db03500dShow all embedded certificates:
build/tools/git-x509-cert --all HEADExport the signer certificate as PEM:
build/tools/git-x509-cert --pem HEAD > signer.pemThe helper uses:
certutil -dumpon Windowsopenssl x509 -text -noouton Linuxopenssl x509 -text -noouton macOS
If those tools are unavailable, it falls back to a built-in certificate summary plus PEM output.
This helper extracts and displays the certificate embedded in the commit
signature. It does not by itself establish that the commit is trusted. Pair it
with git log --show-signature or git verify-commit when you need both
certificate inspection and signature verification.
Export the signing certificate from a commit:
build/tools/git-x509-cert --pem <commit-sha> > signer.pemView it on Linux or macOS:
openssl x509 -in signer.pem -text -nooutView it on Windows:
certutil -dump .\signer.pemYou can also inspect the commit signature at a higher level with:
git log --show-signature -1 <commit-sha>If the cryptographic signature is valid but the issuing CA is not trusted on
the local machine, smimesign --verify reports:
- a good signature
- a trust warning
- exit status
0
This is intentional. It distinguishes:
- "the signature bytes are valid"
- "this workstation trusts the issuing CA"
To preserve older local-only workflows, you can opt back into trusting local store identities as verification anchors:
export SMIMESIGN_TRUST_LOCAL_CERTS=1Import the issuing CA into the appropriate Windows trust store, typically
Trusted Root Certification Authorities or Intermediate Certification Authorities depending on what you are installing.
Example:
certutil -addstore Root company-root-ca.cer
certutil -addstore CA company-issuing-ca.cerRHEL / Fedora / CentOS:
sudo cp company-ca.pem /etc/pki/ca-trust/source/anchors/
sudo update-ca-trustDebian / Ubuntu:
sudo cp company-ca.pem /usr/local/share/ca-certificates/company-ca.crt
sudo update-ca-certificatessudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain company-root-ca.cerAfter trust is installed, rerun:
git log --show-signature -1Current behavior:
smimesigntries to prefer the most signing-oriented certificate- if still ambiguous, it tells you to set
SMIMESIGN_CERT_ID
Recommended fix:
smimesign.exe --list-keys
$env:SMIMESIGN_CERT_ID = "0xYOUR_FINGERPRINT"
git commit -S -m "retry"Check:
Get-Command smimesign.exe
git config --global --get gpg.x509.programCheck the PKCS#11 module and token visibility:
pkcs11-tool --module "$SMIMESIGN_PKCS11_MODULE" -L
smimesign --list-smartcard-keysIf the token is visible with pkcs11-tool but not in smimesign, verify the
module path and any PKCS#11 forwarding environment required by your setup.
If Git signs non-interactively, use a wrapper script that prompts on /dev/tty
and exports SMIMESIGN_PKCS11_PIN only for that process.
The signature is valid, but your system does not trust the issuing CA yet. Install the relevant CA certificate into the system trust store and rerun the verification command.
If you enable --revocation-check=ocsp, smimesign requires the signing
certificate to expose a reachable OCSP responder and to return a good
status. In restricted or air-gapped environments, leave revocation checking at
none, or use ocsp-soft if you want revocation failures to be surfaced as
warnings without making git log --show-signature fail.
This distinction matters for long-lived repositories. A revoked certificate may
mean "do not trust this identity for new signing activity" without meaning
"historical commits signed by this person must disappear from normal use." For
that reason, ocsp-soft is the recommended mode for many repository browsing
and review workflows, while strict ocsp remains available for fail-closed
policies.
By default --timestamp-authority must use https://. If you must use a
plain HTTP TSA in a controlled environment, set:
export SMIMESIGN_ALLOW_HTTP_TSA=1Check:
git config --global --get gpg.format
git config --global --get gpg.x509.program
git config --global --get log.showSignatureExpected:
gpg.format = x509gpg.x509.program = /absolute/path/to/smimesignorsmimesign.exelog.showSignature = true
If you depend on a wrapper for PKCS#11 forwarding or PIN prompting, use the
absolute wrapper path rather than a bare command name. That avoids accidental
fallback to a different smimesign binary found earlier on PATH.
Build the main binary:
go build .Build platform targets:
make build-linux
make build-windows
make build-darwin
make build-tools
make build-allRun the security audit flow:
make audit-tools
make auditmake build-windows and make build-darwin require cgo cross-toolchains.
The Makefile now checks explicitly for those tools and fails early with a clear
message. Override the compiler commands if your environment uses different
tool names:
make build-windows WINDOWS_CC_AMD64=x86_64-w64-mingw32-gcc
make build-darwin DARWIN_CC_AMD64=o64-clang DARWIN_CC_ARM64=oa64-clangRun the minimal safe subset:
make test-minRun the full suite:
make test-allOn macOS or other constrained environments, you may want:
export GODEBUG=x509usefallbackroots=1PKI environments vary widely. Contributions that improve compatibility with enterprise Windows, Linux PKCS#11, smart cards, and certificate-chain handling are welcome. See CONTRIBUTING.md.