Commit Signing with CAC
Overview
Use this document to understand commit signing and how to set your operating system up to sign commits with a CAC.
All commits must be signed using either a CAC or a GPG key. Developers who have a CAC can use it to sign commits in GitLab after completing the required setup steps and configuration. For projects operating in IL4 and IL5 environments, CAC signing is mandatory for all commits.
To learn how to sign commits using GPG instead, follow the How to Commit Sign with GPG Guide.
WARNING
Party Bus does not support using 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 your operating system, you must ensure that DoW Certificates are installed on your machine. Refer to militarycac.com for more information and instructions to install the necessary certificates.
Ensure your CAC has a CDS with an associated email address. If no email is present, either visit a local ID Card (find a location here) or use GPG signing by following the How to Commit Sign with GPG Guide.
Confirm User’s Commit Signing Email Address
Confirm that the user's commit email address in GitLab matches the one associated with their CAC's CDS.
a. Check which email address is currently set as the commit email in GitLab,
- View User Settings > Emails
- Look for this icon:
b. Request that your CAC's CDS email address be added if it is not already listed by submitting a Request for Gitlab Commit Sign Email Validation .
Requests for repo1.dso.mil should be directed to the Iron Bank team.
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 email, and P1 prevents emails from being sent from code.ilx.dso.mil.
NOTE
If multiple developers need their email addresses added and verified in GitLab, collect all user details and submit a single ticket. If the team is not yet in the pipeline queue, add the information to the COT epic in Jira.
Once the ticket has been closed, go to GitLab's User Settings > Profile and change the commit email to the (newly-added/verified) CAC CDS-associated email address.
Mac
The user's CAC must have a CDS with an associated email address. Follow the instructions below to verify that a CAC has a CDS with an associated email address.
Install Homebrew by following the instructions on the Homebrew website.
Identify the CAC's CDS.
a. In a terminal, install opensc by running
brew install opensc.b. List all of the CAC's certificates with pkcs15-tool by running
pkcs15-tool --list-certificates.c. Identify the CDS. Find the
Encoded Serialfield and note down the third number shown (e.g., Encoded serial: 02 04 01234567).Run the following in your terminal.
shell# First Install Homebrew via bash script on brew.sh # Install OpenSC brew install opensc # List all certs pkcs15-tool --list-certificatesCheck to see if the certificate has an email address.
a. Install a signing utility.
- MacOS/Windows: Use S/MIME. View installation instructions on GitHub.
b. List certificates by running
smimesign --list-keysc. Identify the certificate 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).
Once the correct certificate has been identified, note the ID field's value; it will be used as the $signing_key in the following section.
ID: 1234567890abcdefghijklmnopea7de1908d74b0 ⬅ Copy this signing key for git commit signing setup
S/N: 123456 ⬅ Matches 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 matchesConfigure Git
This section explains how to configure Git user settings for global use. If you already have Git settings for other accounts or repositories, adjust these steps as needed.
Set global Git user configuration
Set global Git signing configuration
Paste the following in your terminal (using the ID from above)
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. Add this manually.
PowerShell# Add smimesign to the 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. This section explains how to ensure that the CAC has a certificate with the intended purpose of Document Signing and how to determine its serial number.
Use the following command to 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"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
This section explains how to configure Git user settings for global use. If you already have Git settings for other accounts or repositories, adjust these steps as needed.
Check Git configuration for existing user email.
PowerShellgit config --get user.emailSet global Git user configuration.
PowerShell# Replace text with your DoW email address associated with your CAC. git config --global user.e-mail "<DoW 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 the detection of the 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 refer to the CAC 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# Option A $ 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/pinentry # Option B $ 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:
providers 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:
scdaemon-program /usr/bin/gnupg-pkcs11-scd(OPTIONAL) If you would like to use a command line interface for entering your PIN, 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 DoW Root Certificate 3 to your system's root trust store by running 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.shell#$ pkcs15-tool --list-certificates 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 "Digital Signature" 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.
shellgpgconf --kill allThere 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.shellsigningEmail=$(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 trueCommit
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 DoW 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.shellgit commit -m "ticket number message"
Using Commit Signing
WARNING
If the identified CAC certificate does not have an email address associated with it:
- Stop here. Instead, set up commit signing using a GPG key by following the How To Commit Signing with GPG Guide.
- Visit a local ID Card office to associate an email address with 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 above to set up global signing, please note that gpg.x509.program is gpgsm, not smimesign.
For WSL users, when defining gpg.x509.program, it should be set to 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.
a. 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 that should be placed in the home directory.a. 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.
a. 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 screenshot:INFO
Git Configuration Precedence: When Git configurations are output using
git config --list,settings listed last take precedence over those listed higher up.Git configurations are listed in the following order:
- Global (NOTE: The folder set up above is considered global. Its precedence depends on where the
includeifis listed.) - Repository
In 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.- Global (NOTE: The folder set up above is considered global. Its precedence depends on where the
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 FAQ.
Troubleshooting
If you are having any issues with the GPG agent, aside from rebooting or re-inserting your CAC, 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, 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 with 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
If you see this error, check that the DoW Certificates are installed on your machine. Refer to militarycac.com.
$ git commit -m -S 'commit message'
error: gpg failed to sign the data
fatal: failed to write commit objectIf 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 the previous good push.
- Get the SHA from that push.
- Run:
git reset --soft < SHA >, which will undo commits. - Do a successful signed commit.
- Push.