You can sign Git commits using GPG and Signum Linux Agent.
When you sign a commit, Git calls GPG to create a signature over the commit data. The signature is embedded in the commit object itself. Anyone with your public key can verify that the commit came from you and was not tampered with. The private key never leaves the Signum HSM at any point.
Prerequisites
-
Signum Linux Agent
-
Linux distribution supported by the Linux Agent
-
sudoprivileges -
Git installed
-
GitLab or GitHub account
Step 1 - Install Dependencies
-
Install the packages:
sudo apt update
sudo apt install -y \
gnupg2 \
pinentry-curses \
opensc \
build-essential \
bzip2 \
wget \
devscripts \
libgpg-error-dev \
libgcrypt20-dev \
libassuan-dev \
libpkcs11-helper1-dev \
pkg-config
-
Build gnupg-pkcs11-scd 0.11.0 from source with a one-line patch in
cmd_keyinfo:
The version of gnupg-pkcs11-scd available in Debian apt repositories is 0.10.0 and has a known bug. Version 0.11.0 partially fixes this but still requires a one-line patch in cmd_keyinfo.
cd /tmp
wget https://github.com/alonbl/gnupg-pkcs11-scd/releases/download/gnupg-pkcs11-scd-0.11.0/gnupg-pkcs11-scd-0.11.0.tar.bz2
tar -xjf gnupg-pkcs11-scd-0.11.0.tar.bz2
cd gnupg-pkcs11-scd-0.11.0
# Apply the patch
python3 << 'EOF'
with open('gnupg-pkcs11-scd/command.c', 'r') as f:
content = f.read()
old = '\t\tif (error != GPG_ERR_NO_ERROR) {\n\t\t\tgoto cleanup;\n\t\t}\n\t}\n\n\terror = found ? GPG_ERR_NO_ERROR : GPG_ERR_NOT_FOUND;'
new = '\t\tif (error == GPG_ERR_WRONG_PUBKEY_ALGO) {\n\t\t\terror = GPG_ERR_NO_ERROR;\n\t\t}\n\t\tif (error != GPG_ERR_NO_ERROR) {\n\t\t\tgoto cleanup;\n\t\t}\n\t}\n\n\terror = found ? GPG_ERR_NO_ERROR : GPG_ERR_NOT_FOUND;'
if old in content:
content = content.replace(old, new)
with open('gnupg-pkcs11-scd/command.c', 'w') as f:
f.write(content)
print("Patch applied!")
else:
print("Pattern not found!")
EOF
# Build and install
./configure && make && sudo make install
# Verify
/usr/local/bin/gnupg-pkcs11-scd --version
Expected output:
gnupg-pkcs11-scd 0.11.0
Step 2 - Verify Signum Agent and PKCS#11 Library
-
Verify the Agent is authenticated and certificates are visible:
signum-util test
signum-util listcertificates
-
Verify the PKCS#11 library is present and keys are accessible:
ls -la /usr/lib/libsignumpkcs11.so
pkcs11-tool --module /usr/lib/libsignumpkcs11.so --list-objects
Step 3 - Create GPG Configuration Files
-
Create the GPG configuration files:
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
cat > ~/.gnupg/gpg-agent.conf << 'EOF'
verbose
debug-all
log-file /tmp/gpg-agent.log
scdaemon-program /usr/local/bin/gnupg-pkcs11-scd
pinentry-program /usr/bin/pinentry-curses
allow-loopback-pinentry
EOF
cat > ~/.gnupg/gnupg-pkcs11-scd.conf << 'EOF'
providers signum
provider-signum-library /usr/lib/libsignumpkcs11.so
EOF
chmod 600 ~/.gnupg/gpg-agent.conf
chmod 600 ~/.gnupg/gnupg-pkcs11-scd.conf
-
Set GPG_TTY and start the smart card daemon:
GPG_TTY must always be set before signing.
export GPG_TTY=$(tty)
gpgconf --kill all
gpg --card-status
Expected result is the card detected with Application type: OpenPGP.
-
Get the
KEY-FRIENDLYhash for your certificate from Signum:
gpg-connect-agent << 'EOF'
SCD LEARN --force
SCD GETATTR KEYPAIRINFO
EOF
Note the KEY-FRIENDLY hash for the certificate you want to use:
S KEY-FRIENDLY 2588F2F67BBC82E872B15287621964636FB074D9 /CN=SignumCertificate on Signum for Linux
Save this hash for the following steps.
Step 4 - Map the Signing Key
Update gnupg-pkcs11-scd.conf with the KEY-FRIENDLY hash:
-
Replace
<KEY-FRIENDLY-HASH>with your actual hash value:
cat > ~/.gnupg/gnupg-pkcs11-scd.conf << 'EOF'
providers signum
provider-signum-library /usr/lib/libsignumpkcs11.so
openpgp-sign <KEY-FRIENDLY-HASH>
EOF
-
Restart the daemon to apply the change:
gpgconf --kill all
gpg --card-status
-
Confirm the
Signature keyfield is now populated. It should show the keygrip instead of[none]:
Signature key ....: 2588 F2F6 7BBC 82E8 72B1 5287 6219 6463 6FB0 74D9
Step 5 - Create a Virtual GPG Key with Your GitLab/GitHub Email
The GPG key UID email must match your Git hosting account email for the "Verified" badge to appear on commits.
-
Generate the virtual GPG key:
gpg --expert --full-generate-key
-
When prompted, provide the following values:
|
Prompt |
Value |
|---|---|
|
Key type |
|
|
Keygrip |
|
|
Capabilities |
|
|
Expiry |
|
|
Real name |
Your full name |
|
Email address |
Your GitLab or GitHub email |
|
Confirm |
|
|
PIN prompt |
your Signum password |
Expected output:
pub rsa2048 2026-05-21 [SCE]
BB07CE2D9778F6CED7D0F05A46C163CE62A3B77B
uid Your Name <your@email.com>
-
Verify the key is linked to the Signum Card:
gpg --list-secret-keys --with-keygrip
-
Confirm
sec>(with>) that the private key is in the Signum HSM, not on disk:
sec> rsa2048 2026-05-21 [SCE]
BB07CE2D9778F6CED7D0F05A46C163CE62A3B77B
Keygrip = 2588F2F67BBC82E872B15287621964636FB074D9
Card serial no. = 3131 DC6624E9
uid [ultimate] Your Name <your@email.com>
-
Set this key as the GPG default. Replace
<GPG_KEY_FINGERPRINT>with the full fingerprint
from the output above:
echo "default-key <GPG_KEY_FINGERPRINT>" >> ~/.gnupg/gpg.conf
-
Make GPG_TTY permanent across sessions:
echo 'export GPG_TTY=$(tty)' >> ~/.bashrc
source ~/.bashrc
Step 6 - Configure Git
-
Configure Git to use your identity and the Signum GPG key:
git config --global user.name "<YOUR_FULL_NAME>"
git config --global user.email "<YOUR_GITLAB_OR_GITHUB_EMAIL>"
git config --global user.signingkey <GPG_KEY_FINGERPRINT>
git config --global commit.gpgsign true
git config --global gpg.program gpg2
-
Verify the configuration:
git config --global --list | grep -E "user|gpg|sign"
Expected output:
user.name=Your full name
user.email=<your email>
user.signingkey=<GPG_KEY_FINGERPRINT>
commit.gpgsign=true
gpg.program=gpg2
Step 7 - Export Your Public Key
Export your public key to share with GitLab or GitHub so it can verify your
signed commits.
-
Run the following command to export the public key:
gpg --armor --export <YOUR_GITLAB_OR_GITHUB_EMAIL>
-
Copy the full output including the header and footer lines:
-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----
Step 8 - Upload Public Key to GitLab or GitHub
GitLab:
-
Go to avatar (top right) → Edit profile → GPG Keys
-
Paste the full public key block.
-
Click Add key.
GitHub:
-
Go to Settings → SSH and GPG keys → New GPG key
-
Paste the full public key block.
-
Click Add GPG key.
Step 9 - Make a Signed Commit
-
Make sure GPG_TTY is set and the smart card daemon is running:
export GPG_TTY=$(tty)
gpgconf --kill all
gpg --card-status
If you get Card error, run gpgconf --kill all && gpg --card-status and try again.
-
Make a signed commit (signing is automatic if
commit.gpgsign=trueis set):
git commit -m "your commit message"
To test the functionality without making changes, use the flag --allow-empty to create a commit:
git commit --allow-empty -m "your commit message"
-
Verify the signature locally:
git log --show-signature -1
Expected output:
gpg: Signature made <DATE>
gpg: using RSA key <GPG_KEY_FINGERPRINT>
gpg: Good signature from "<YOUR_FULL_NAME> <<YOUR_GITLAB_OR_GITHUB_EMAIL>>" [ultimate]
commit <COMMIT_HASH>
Author: <YOUR_FULL_NAME> <<YOUR_GITLAB_OR_GITHUB_EMAIL>>
Date: <DATE>
your commit message
On GitLab or GitHub, the commit shows a green Verified badge.
Troubleshooting
gpg --card-status Returns "No card" or Hangs
-
Confirm
gnupg-pkcs11-scdis the patched 0.11.0 build:/usr/local/bin/gnupg-pkcs11-scd --version -
Check the
scdaemon-programpath ingpg-agent.confpoints to the correct binary. -
Kill all GPG daemons and retry:
gpgconf --kill all gpg --card-status -
Review the agent log for errors:
cat /tmp/gpg-agent.log
KEY-FRIENDLY Hash Not Appearing in SCD GETATTR KEYPAIRINFO
-
Confirm the Signum Agent is authenticated:
signum-util test -
Confirm the PKCS#11 library is accessible:
pkcs11-tool --module /usr/lib/libsignumpkcs11.so --list-objects -
If the token has mixed RSA + ECC certificates, confirm you applied the patch to the
gnupg-pkcs11-scdbuild, or the loop stops before reaching RSA certificates.
gpg --full-generate-key Fails at Keygrip Prompt
-
The keygrip entered must match exactly the
KEY-FRIENDLYhash. -
Make sure you ran
gpg --card-statusafter updatinggnupg-pkcs11-scd.conf
and that theSignature keyfield is populated (not[none]).
sec (Without >) After Key Generation
-
This means the private key was stored locally on disk, not linked to the HSM.
-
Delete the key (
gpg --delete-secret-and-public-key <FINGERPRINT>) and repeat
Step 5, confirming you selected key type 13 (Existing key) and entered the
correct keygrip.
Commit Shows "Unverified" on GitLab or GitHub
-
The email in the GPG key UID must exactly match the email on your GitLab or
GitHub account. -
Confirm the public key has been uploaded and that GitLab or GitHub
shows it as active. -
Check that the key has not expired:
gpg --list-keys