Bitbucket Code Signing with X.509 Certificates#

This guide explains how to configure your development environment to sign Git commits and tags using X.509 certificates stored on your YubiKey, enabling verified signatures in Bitbucket Data Center.

Prerequisites#

Before you begin, ensure you have:

  1. YubiKey with enrolled certificates - Your YubiKey must have the Code Signing certificate (slot 9c) enrolled through Kleidia
  2. smimesign - Tool for signing commits with X.509 certificates
  3. Git - Version 2.19 or later recommended
  4. Bitbucket Data Center - Version 8.15 or later (with X.509 signing support)

Step 1: Install smimesign#

macOS#

# Using Homebrew
brew install smimesign

Windows#

Download the latest release from the smimesign GitHub releases page and add it to your PATH.

Linux#

# Download and install
curl -L https://github.com/github/smimesign/releases/latest/download/smimesign-linux-amd64 -o /usr/local/bin/smimesign
chmod +x /usr/local/bin/smimesign

Step 2: Find Your Certificate ID#

With your YubiKey plugged in, list available certificates:

smimesign --list-keys

You should see output similar to:

       ID: 4df4ce209319b0904fb3f2813115d5e4438e8c3c
      S/N: 468e1711b4ade3fc
Algorithm: SHA256-RSA
 Validity: 2024-01-01 00:00:00 +0000 UTC - 2025-01-01 00:00:00 +0000 UTC
   Issuer: CN=Kleidia Intermediate CA,O=Your Organization
  Subject: John Doe <john.doe@example.com>
   Emails: john.doe@example.com

Important: Note the ID value - you’ll need this for Git configuration. The email address shown must match your Bitbucket user account email.

Step 3: Configure Git for X.509 Signing#

Global Configuration (All Repositories)#

# Set smimesign as the signing program
git config --global gpg.x509.program smimesign
git config --global gpg.format x509

# Set your signing key (use the ID from step 2)
git config --global user.signingkey 4df4ce209319b0904fb3f2813115d5e4438e8c3c

# Optional: Sign all commits automatically
git config --global commit.gpgsign true

Per-Repository Configuration#

If you only want to use X.509 signing for specific repositories:

cd /path/to/your/repo
git config --local gpg.x509.program smimesign
git config --local gpg.format x509
git config --local user.signingkey 4df4ce209319b0904fb3f2813115d5e4438e8c3c
git config --local commit.gpgsign true

Step 4: Sign Your First Commit#

Signing Commits#

# Sign a single commit
git commit -S -m "My signed commit message"

# If you enabled commit.gpgsign, all commits are signed automatically
git commit -m "This commit is automatically signed"

When you run the commit command, you’ll be prompted for your YubiKey PIN.

Signing Tags#

git tag -s v1.0.0 -m "Release version 1.0.0"

Verify Signatures Locally#

# View commit signatures
git log --show-signature

# Verify a specific commit
git verify-commit HEAD

# Verify a tag
git verify-tag v1.0.0

Step 5: Verify in Bitbucket#

Once your commits are pushed to Bitbucket, you should see:

  1. Verified badge - Commits signed with a trusted certificate show a “Verified” badge
  2. Certificate details - Click on the badge to see certificate information

Troubleshooting Unverified Signatures#

If your signatures appear as “Unverified” in Bitbucket:

  1. Email mismatch - Ensure the email in your certificate matches your Bitbucket account email
  2. CA not trusted - Ask your Bitbucket administrator to import the CA chain (see Admin Guide below)
  3. Certificate revoked - Check if your certificate has been revoked
  4. Certificate expired - Generate a new certificate through Kleidia

Administrator Guide: Importing CA Chain#

Bitbucket administrators must import the Kleidia CA chain before user signatures can be verified.

Export CA Chain from Kleidia#

  1. Log into Kleidia as an administrator
  2. Navigate to SettingsOpenBao CA
  3. Click Load CA Chain in the “Export CA Chain for Bitbucket” section
  4. Copy or download the CA chain PEM file

Import into Bitbucket#

Via Web Interface:

  1. Log into Bitbucket as an administrator
  2. Navigate to AdministrationSecuritySigning certificates
  3. Click Add certificate chain
  4. Paste the CA chain from Kleidia
  5. Click Save

Via REST API:

curl -u admin:password -X POST \
  "https://your-bitbucket.example.com/rest/api/latest/signing/certificate-chains" \
  -H "Content-Type: application/json" \
  -d '{"certificateChain": "<paste CA chain PEM here>"}'

Configure External CRL Access#

For Bitbucket to check certificate revocation status, it must be able to access the CRL distribution point. This requires:

  1. Network access - Bitbucket server must be able to reach the Kleidia PKI endpoint
  2. PKI URL configuration - Set openbao.pki.urls.externalBaseUrl in Kleidia Helm values to an externally accessible URL
    • Ensure your load balancer exposes OpenBao on TCP 8200 at that DNS name

Example Helm values:

openbao:
  pki:
    urls:
      externalBaseUrl: "https://pki.example.com:8200" # replace with your public PKI endpoint
      crlExpiry: "24h"

Security Best Practices#

  1. Protect your YubiKey PIN - Use a strong PIN and never share it
  2. Keep YubiKey secure - Store your YubiKey safely when not in use
  3. Use dedicated signing key - The Code Signing certificate (slot 9c) is separate from authentication
  4. Regular certificate renewal - Certificates have expiration dates; renew before they expire
  5. Report lost YubiKeys - If your YubiKey is lost or stolen, report it immediately so certificates can be revoked

Troubleshooting#

“No signing keys available”#

# Ensure YubiKey is plugged in
smimesign --list-keys

# If empty, verify certificate is on YubiKey
ykman piv info

“PIN required” errors#

You’ll be prompted for your YubiKey PIN when signing. If you see repeated prompts:

  • Verify your PIN is correct
  • Check if PIN is blocked (too many wrong attempts)
  • Use Kleidia to check PIN retry count

“Certificate not trusted”#

Contact your Bitbucket administrator to import the CA chain.

Git commit hangs#

This may occur if:

  • YubiKey is not plugged in
  • Smart card service is not running
  • Conflicting smart card applications

Try:

# Restart smart card service (macOS)
sudo pkill -9 pcscd

# Windows
# Restart "Smart Card" service in Services.msc

References#