Commit Signing with CAC
Summary
All commits must be signed. Developers with a Common Access Card (CAC) may use it to sign commits in GitLab once certain prerequisites are met, and settings are configured. For projects in IL4 and IL5, signing commits with a CAC is required.
To learn how to sign commits using GNU Privacy Guard (GPG) instead, follow this guide: GitLab: How to Commit Signing with GPG.
WARNING
Party Bus Does not support using Integrated Development Environments (IDEs) for code signing. You are free to do so, but if you cannot sign commits in your IDE, please do not open a ticket with the Party Bus help desk. You will need to fix your issue or use the command line to sign commits that you create in your IDE.
Prerequisites
Regardless of operating system (OS), you must ensure that DoD Certificates are installed on your machine. Refer to https://militarycac.com/dodcerts.htm.
Ensure your CAC has a Certificate for Digital Signature (CDS) with an associated email address. If no email is present, either visit a local ID Card (find a location) or use GPG signing by following GitLab: How to Commit Signing with GPG.
Confirm User’s Commit Email Address
- Confirm the user's commit email address in GitLab matches the one associated to their CAC's CDS. a. To check which e-mail address is currently set as the commit e-mail in GitLab, view User Settings > Emails and look for this icon:
b. If GitLab does not list the CAC CDS's e-mail address, open a help desk ticket to request it be added. Include the information listed in the following:
- The user's GitLab username (i.e., also their P1 username).
- The CAC e-mail address to be verified.
- The GitLab instance the user is working in (e.g., repo1.dso.mil, code.il2.dso.mil, code.il4.dso.mil, or code.il5.dso.mil).
- Requests for repo1.dso.mil should be directed to the Iron Bank team.
- An example of a help desk ticket is provided in the following:

WARNING
Users should not add the new email address to GitLab themselves. Attempting to do so will cause errors. Email addresses added by the user must be verified with a confirmation e-mail, and Platform One prevents emails from being sent from code.ilx.dso.mil. Instead, open a help desk ticket so the address can be added by a GitLab admin.
NOTE
If more than one developer on a Product Team requires their email address be added and verified in GitLab, gather this information for all users and open a single ticket for everyone. If the Product Team is not yet in the pipeline queue, add this information in to the COT epic in JIRA.
- Once the ticket has been closed, go to GitLab's User Settings > Profile and change the commit e-mail to the (newly-added/verified) CAC CDS-associated e-mail address.

Mac
The user's CAC must have a CDS with an associated email address. Follow the instructions below to verify whether this condition is met.
Install Homebrew by following the instructions at the Homebrew Website (https://brew.sh).
Identify the CAC's CDS. a. In a terminal, install opensc by running
brew install opensc. b. List all of the CAC's certificate's with pkcs15-tool by runningpkcs15-tool --list-certificates. c. Identify the CDS. Find theEncoded Serialfield and note down the third number shown (e.g., Encoded serial: 02 04 01234567).shell###### EXAMPLE OUTPUT ######### Using reader with a card: Identive SCR33xx v2.0 USB SC Reader X.509 Certificate [Certificate for PIV Authentication] Object Flags : [0x00] Authority : no Path : ID : 01 Encoded serial : 02 03 0F0000 X.509 Certificate [Certificate for Digital Signature] # ⬅ Look for this Object Flags : [0x00] Authority : no Path : ID : 02 Encoded serial : 02 03 123456 # ⬅ Write the third value (e.g. 123456) X.509 Certificate [Certificate for Key Management] Object Flags : [0x00] Authority : no Path : ID : 03 Encoded serial : 02 03 000000 X.509 Certificate [Certificate for Card Authentication] Object Flags : [0x00] Authority : no Path : ID : 04 Encoded serial : 02 03 000000Check to see if the certificate has an e-mail address. a. Install a signing utility.
- MacOS/Windows: Use S/MIME. View installation instructions on Github. b. List certificates by running
smimesign --list-keys. c. Scan the certificates to identify the one with a S/N value matching the encoded serial number noted in step 2c. The S/N value may have leading zeroes dropped (e.g., 01234567 --> 1234567).
- MacOS/Windows: Use S/MIME. View installation instructions on Github. b. List certificates by running
Once the correct certificate has been identified, note down the ID field's value; it will be used as the $signing_key in the following section.
ID: 1234567890873cb592d8f7d7c5ea7de1908d74b0 # ⬅ Signing key for git commit signing setup #
S/N: 123456 # ⬅ Matches encoded serial from previous step #
Algorithm: SHA256-RSA
Validity: 2025-01-07 00:00:00 +0000 UTC - 2026-01-02 23:59:59 +0000 UTC
Issuer: CN=DOD ID CA-72,OU=DoD+OU=PKI,O=U.S. Government,C=US
Subject: CN=SKYWALKER.LUKE.901082350304,OU=DoD+OU=PKI+OU=CONTRACTOR,O=U.S. Government,C=US
Emails: luke.skywalker.ctr@us.af.mil # ⬅ CAC Email #Configure Git
In this section, we will configure Git user settings for global use. If you have existing Git settings configured for different accounts or repositories, you may want to adjust some steps accordingly.
Set global Git user configuration.
Set global Git signing configuration.
shell# Configure our Document Signing certificate as the signing key. git config --global user.signingkey <ID-from-above> # Configure Git to use S/MIME to sign commits and tags for all repositories. git config --global gpg.x509.program smimesign git config --global gpg.format x509 # Configure Git to sign commits by default. git config --global commit.gpgsign true
Windows
Install Prerequisites
Open Windows Terminal. If your default profile is not PowerShell, open a new tab and select PowerShell.
Enter the following commands to install Git and smimesign.
PowerShellwinget install Git.Git winget install GitHub.smimesignSmimesign installation does not add itself to the PATH environmental variable, so we will need to do that manually.
PowerShell# Add smimesign to system PATH environmental variable. [Environment]::SetEnvironmentVariable("PATH", $Env:PATH + ";C:\Program Files\smimesign", [EnvironmentVariableTarget]::Machine) # Reload environmental variables for current session. $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
Identify Signing Certificate
Your CAC contains several certificates, each with its own intended purpose. In this section, we will ensure that the CAC has a certificate whose intended purpose is Document Signing and determine the serial number of that certificate.
The following command will identify the certificate used for code signing. If the command returns no results, you may need to verify that your CAC is recognized and has the correct certificates.
PowerShell# Verify certificate exists Get-ChildItem Cert:\CurrentUser\My\ -EKU "Document Signing"After we have verified the certificate exists, we store the certificate thumbprint in a local variable.
PowerShell# Save certificate serial to variable. $thumbprint = (Get-ChildItem Cert:\CurrentUser\My\ -EKU "Document Signing").Thumbprint
Configure Git and Smimesign
In this section, we will configure Git user settings for global use. If you have existing Git settings configured for different accounts or repositories, you may want to adjust some steps accordingly.
Check Git configuration for existing user email.
PowerShellgit config --get user.emailSet global Git user configuration.
PowerShell# Replace text with your DoD email address associated with your CAC. git config --global user.e-mail "<DoD Email Address>" # Replace text with your full name. git config --global user.name "<Firstname Lastname>"Set global Git signing configuration.
PowerShell# Configure our Document Signing certificate as the signing key git config --global user.signingkey $thumbprint # Verify settings git config list | Select-String -Pattern 'user' -CaseSensitive
Linux
WARNING
These steps have only been tested on Ubuntu 20.04 LTS. Other distributions of Linux may vary. Updates to GPG may cause issues with detection of card.
Prerequisites
Your computer must be CAC-enabled, and you must have sudo access. To enable the use of your CAC/smart card reader, please do the following:
In your command terminal, type
sudo apt install libpcsclite1 pcscd pcsc-tools.Plug in your CAC.
Type in
sudo systemctl enable pcscd --now.Type in
pcsc_scan -r. This command is used to verify that your CAC is being read. If your CAC reader is detected, you should see something like the following figure:
If this command is successful, but stuck in a running state, press CTRL+C on your keyboard to kill the pcsc_scan. Please see the Common Access Card documentation site for more information on setting up a smart card for Ubuntu.
Commit Signing with gpgsm for Linux
NOTE
Please ensure that no other GPG tools are currently installed on your system. You will need to do a fresh install of GPG using the steps in this section.
Install the GPG and smart card utilities by running the following command:
shellsudo apt -y install gpg coolkey dirmngr gnupg2 gnupg-pkcs11-scd openscRun the command below and verify that your output is similar to what is depicted in the following figure. Brew users may need to uninstall GPG-related taps and log back in for the GPG program paths to be correct.
shell$ gpgconf gpg:OpenPGP:/opt/homebrew/Cellar/gnupg/2.4.8/bin/gpg gpgsm:S/MIME:/opt/homebrew/Cellar/gnupg/2.4.8/bin/gpgsm keyboxd:Public Keys:/opt/homebrew/Cellar/gnupg/2.4.8/libexec/keyboxd gpg-agent:Private Keys:/opt/homebrew/Cellar/gnupg/2.4.8/bin/gpg-agent scdaemon:Smartcards:/opt/homebrew/Cellar/gnupg/2.4.8/libexec/scdaemon dirmngr:Network:/opt/homebrew/Cellar/gnupg/2.4.8/bin/dirmngr pinentry:Passphrase Entry:/opt/homebrew/opt/pinentry/bin/pinentryor this
shell$ gpgconf gpg:OpenPGP:/usr/bin/gpg gpgsm:S/MIME:/usr/bin/gpgsm keyboxd:Public Keys:/usr/libexec/keyboxd gpg-agent:Private Keys:/usr/bin/gpg-agent scdaemon:Smartcards:/usr/libexec/scdaemon dirmngr:Network:/usr/bin/dirmngr pinentry:Passphrase Entry:/opt/homebrew/opt/pinentry/bin/pinentryCreate the file ~/.gnupg/gnupg-pkcs11-scd.conf with the following contents:
iniproviders p1 provider-p1-library /usr/lib/pkcs11/libcoolkeypk11.so providers smartcardhsm provider-smartcardhsm-library /usr/lib/x86_64-linux-gnu/opensc-pkcs11.soCreate another file ~/.gnupg/gpg-agent.conf with the following contents:
iniscdaemon-program /usr/bin/gnupg-pkcs11-scd(OPTIONAL) If you would like to use a command line interface for entering your pin, please run the following commands:
shellecho 'pinentry-program /usr/local/bin/pinentry-curses' >> ~/.gnupg/gpg-agent.conf echo 'export GPG_TTY=$(tty)' >> ~/.bashrcRun
gpg --card-statusto verify that the gnupg program can detect your card.Add DoD Root Certificate 3 to your systems root trust store by doing the following:
shellwget https://dl.dod.cyber.mil/wp-content/uploads/pki-pke/zip/unclass-certificates_pkcs7_v5-6_dod.zip unzip unclass-certificates_pkcs7_v5-6_dod.zip sudo cp Certificates_PKCS7_v5.6_DoD/DoD_PKE_CA_chain.pem /usr/local/share/ca-certificates/DoD_PKE_CA_chain.crt sudo cp Certificates_PKCS7_v5.6_DoD/DoD_PKE_CA_chain.pem /etc/ssl/certs/DoD_PKE_CA_chain.crt sudo update-ca-certificatesRun
pkcs15-tool --list-certificates.
NOTE
The ID of the certificate used for DigitalSignature.
###### EXAMPLE OUTPUT #########
Using reader with a card: Identive SCR33xx v2.0 USB SC Reader
X.509 Certificate [Certificate for PIV Authentication]
Object Flags : [0x00]
Authority : no
Path :
ID : 01
Encoded serial : 02 03 0F0000
X.509 Certificate [Certificate for Digital Signature] # ⬅ Look for this
Object Flags : [0x00]
Authority : no
Path :
ID : 02 # ⬅ Note the ID of the Digital Signature
Encoded serial : 02 03 123456
X.509 Certificate [Certificate for Key Management]
Object Flags : [0x00]
Authority : no
Path :
ID : 03
Encoded serial : 02 03 000000
X.509 Certificate [Certificate for Card Authentication]
Object Flags : [0x00]
Authority : no
Path :
ID : 04
Encoded serial : 02 03 000000Run the following to import the Root CRL for your certificate, using the ID from the previous step. Additionally, replace < FILENAME > with the file downloaded in the second step.
shellpkcs15-tool --read-certificate < ID > > mycert.crt wget $(openssl x509 -in mycert.crt -text | grep "CA Issuers" | awk '{ print $4 }' | sed 's/URI://g')Note the file downloaded in the previous wget step and copy its name into the following commands.
shelltouch ~/.gnupg/trustlist.txt dirmngr --fetch-crl $(openssl x509 -inform der -in < FILENAME >.cer -text | grep crl | grep -v "CA Issuers" | sed 's/URI://g') gpgsm --import < FILENAME >.cer gpgsm --import mycert.crtRestart your GPG agent to use your new settings from the previous steps.
gpgconf --kill all- There are multiple commands to set up GPG signing globally. The first two commands are for a more automated approach; however, you can grab the email address and the signing key manually by running
gpgsm --list-secret-keysand retrieving the email associated with your CAC key as well as the signing key, denoted by ID.
signingEmail=$(gpgsm --list-secret-keys | grep aka | awk '{ print $2 }')
signingkey=$( gpgsm --list-secret-keys | egrep '(key usage|ID)' | grep -B 1 digitalSignature | awk '/ID/ {print $2}')
git config --global user.email $signingEmail
git config --global user.signingkey $signingkey
git config --global gpg.format x509
git config --global gpg.x509.program gpgsm
git config --global commit.gpgsign true- Commit
Your first attempt at signing a commit may fail if the Root CA is not already trusted. You will be prompted to trust (and later confirm) the DoD Root CA with a similar message, as depicted in the following image. Elect to trust the certificate and its fingerprint will then be added to ~/.gnupg/trustlist.txt. Once that is done, reattempting the commit should work.
git commit -m "ticket number message"Using Commit Signing
WARNING
If the identified CAC certificate does not have an email address associated to it
- Stop here. Instead, set up commit signing using a GPG key by following HowTo - GitLab - Commit Signing with GPG.
- Visit a local ID Card office to associate an email address to your CAC. Find a location here.
Configure Git for commit signing. There are three scopes to choose from: repository, folder, or global.
For Linux Users, if you have not followed the steps to set up global signing above within the Linux instructions, please note that gpg.x509.program is gpgsm, not smimesign.
For Windows Subsystem for Linux (WSL) users, when defining gpg.x509.program, it should be set the absolute path to the program on your base system, e.g., "/mnt/c/Program Files/smimesign/smimesign.exe". In addition to setting the path to your signing program, it is recommended that you set your credential manager to the Windows path.
shellgit config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe"Repository affects only one Git repository.
- In a terminal, navigate to the repository to be configured for commit signing. Then run the following commands:
shellgit config user.email john.doe.ctr@us.af.mil git config user.signingkey $signing_key git config gpg.x509.program smimesign git config gpg.format x509 git config commit.gpgsign trueNOTE: This repository configuration will take precedence over global configuration.
Folder affects all Git repositories in a folder. The steps below generate a
.gitconfig-partybusfile–it should be placed in the home directory.- In a terminal, run the following commands:
shell# NOTE: The path to the folder to configure is represented below as: ~/path/to/folder cd ~ git config --file=.gitconfig-partybus --add user.email john.doe.ctr@us.af.mil git config --file=.gitconfig-partybus --add user.signingkey $signing_key git config --file=.gitconfig-partybus --add gpg.x509.program smimesign git config --file=.gitconfig-partybus --add gpg.format x509 git config --file=.gitconfig-partybus --add commit.gpgsign true # set if you do not want to set -S flag for commits git config --global --add includeif.gitdir:~/path/to/folder/.path .gitconfig-partybus # NOTE: .path is not part of the gitdir path specified, which is ~/path/to/folderNote: The last configuration value listed for a field takes precedence. In the global Git configuration (
~/.gitconfig), theincludeif.gitdirshould be listed last if it should take precedence over values in the global Git configuration.Global affects all Git repositories on a local machine.
- In a terminal, run the following commands:
Shellgit config --global user.email john.doe.ctr@us.af.mil git config --global user.signingkey $signing_key git config --global gpg.x509.program smimesign git config --global gpg.format x509 git config --global commit.gpgsign true
Verify that the Git configuration is correctly set up.
a. In a terminal, navigate to a repository that should have the configuration applied.
b. To list out the repository's Git configuration,
run git config --list. The fields that should be listed are outlined in the following:
GIT Configuration Precedence:
When Git configurations are outputted using
git config --list,settings listed last take precedence over those listed higher up.Git configurations are listed in the following order:
i. Global (NOTE: The folder set up above is considered global. Its precedence depends on where the
includeifis listed.) ii. RepositoryIn the example above,
user.emailis listed twice.ejay.tumacder.ctr@us.af.milis listed second, making it take precedence overetumacder@revacomm.com.Note that this explanation of Git configuration and precedence only relays what is necessary for this guide. Visit Git's git config documentation to learn more.
c. When a commit is attempted, the user should be prompted for their pin. Run
git log --show-signatureto confirm that a commit has been signed.
Moving forward, commits should be viewable in the repository's "History" in GitLab, showing as "Verified." If this is not the case, refer to the Frequently Asked Questions.
Troubleshooting
If you are having any issues with the GPG agent, aside from rebooting or re-inserting your CAC, you can run this command to kill the gpg-agent server:
gpgconf --kill allAdditionally, if using a pin entry program other than the GUI (i.e. pinentry-curses), it may take a few tries to sign a commit. You can run this command, which is more time-friendly, in order to get the initial pin entry and certificate trust prompts squared away:
gpgsm --status-fd=2 -bsau $signingkey
Frequently Asked Questions
Commit Signing Questions
I can see my commit in GitLab, but it's stuck loading. When I try to view it through History, I see an error (e.g. 422 or 500). What's wrong?
A CAC certificate without an email address association was used. Choose a certificate that displays an email address when running smimesign --list-keys. If there's no email associated to the CAC being used, fall back to signing with a GPG Key.
My commit is "Unverified" in GitLab. What's wrong?
The email address associated with the CAC does not match the GitLab commit email. Please make sure that the CAC email is verified and is set to the email in the user's Git configuration.
I am able to do signed commits, but when I push, I get a "GitLab: Commit must be signed with a GPG key." What's wrong?
You may be trying to push multiple commits and some of them are unsigned. You will need to roll back or rebase those commits and get them signed.
"Reject Signed Commits" Push Rule Questions
Will this affect merge request commits done through GitLab UI?
Merge request commits done through the UI are not affected by this change and do not need to be signed.
Will I still be able to commit changes through GitLab's Web IDE?
No.
Known Errors
Error when Signing a Commit
$ git commit -m -S 'commit message'
error: gpg failed to sign the data
fatal: failed to write commit objectIf you see this error, check that the DoD Certificates are installed on your machine. Refer to https://militarycac.com/dodcerts.htm.
If you previously signed commits using GPG, it's likely your Git user.signingkey configuration has already been set with your GPG key ID. If you've followed the directions above, you've set it as your CAC's CDS ID. You may need to remove the first configuration (i.e., the one whose value is your GPG key ID) to successfully sign commits with a CAC.
Git Push Failing
Push is failing due to mixed signed and unsigned commits. To remedy this, complete the following:
- Reset to previous good push.
- Get the SHA from that push.
- Run:
git reset --soft < SHA >, which will undo commits. - Do a successful signed commit.
- Push.
Related Articles
- Official GitLab x509 Signed Commits Documentation: https://docs.gitlab.com/ee/user/project/repository/x509_signed_commits/
- Install Homebrew: https://brew.sh/
- S/MIME Sign GitHub: https://github.com/github/smimesign
- S/MIME Sign Windows Releases: https://github.com/github/smimesign/releases/tag/v0.2.0-rc1
- OpenSC Wiki: https://github.com/OpenSC/OpenSC/wiki
- Git config documentation: https://git-scm.com/docs/git-config
- Help Desk link for adding emails: https://jira.il2.dso.mil/servicedesk/customer/portal/73/create/581
- Install DoD Certificates: https://militarycac.com/dodcerts.htm
- ID Card Office Locator: https://idco.dmdc.osd.mil/idco/locator