Skip to content

robertteir/gpgsm-wsl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 

Repository files navigation

gpgsm-wsl

Sign Git commits from WSL using a certificate stored on a YubiKey (PIV), by delegating to gpgsm.exe running on the Windows host via Gpg4win.


Overview

WSL does not have native access to smart-card readers, so gpgsm inside WSL cannot talk to a YubiKey directly. This setup works around that by:

  1. Configuring Gpg4win on Windows to talk to the YubiKey over PCSC (SCardSvr).
  2. Providing a thin wrapper script (gpgsm-wsl) inside WSL that translates Linux paths to Windows paths and forwards all calls to gpgsm.exe on the host.
  3. Pointing Git's X.509 signing pipeline at that wrapper.

Application flow

The following sequence diagram shows how a signed Git operation flows from WSL through the wrapper to Windows gpgsm.exe, the GnuPG agent stack, PCSC, and the YubiKey.

sequenceDiagram
    autonumber
    participant U as User WSL shell
    participant G as Git WSL
    participant W as gpgsm-wsl wrapper
    participant T as wslpath and temp file
    participant X as Windows gpgsm from Gpg4win
    participant A as Windows gpg agent
    participant S as scdaemon Windows
    participant P as PCSC SCardSvr
    participant Y as YubiKey PIV 9a

    U->>G: git commit signing or verify
    G->>W: run gpg.x509.program
    Note over W: parse argv translate paths buffer stdin if needed

    alt path is Linux absolute
        W->>T: run wslpath for path
        T-->>W: UNC to WSL filesystem
    else stdin sentinel for gpgsm
        G->>W: payload on stdin
        W->>T: write temp file run wslpath
        T-->>W: UNC to stdin buffer
    end

    Note over W: Flags and fingerprints pass through without path translation

    W->>X: exec with translated argv
    X->>A: sign or verify
    A->>S: smartcard op
    S->>P: PCSC
    P->>Y: APDU PIV 9a
    Y-->>P: signature or card result
    P-->>S: response
    S-->>A: result
    A-->>X: CMS output
    X-->>G: exit code stdout stderr
    G-->>U: signed commit or verify outcome

    Note over W,T: EXIT trap removes temp dir and stdin file
    Note over P,Y: Key stays on Windows USB no passthrough to WSL
Loading

Prerequisites

  • Windows with a YubiKey (PIV applet) plugged in
  • Gpg4win installed (GnuPG is required; Kleopatra is optional)
  • WSL 2 (any distro)

Certificate Generation

This section covers creating a self-signed CA, generating a CSR from the YubiKey, signing it, and importing the resulting certificate back onto the key.

1. Generate a CSR on the YubiKey (PIV slot 9a)

Use YubiKey Manager to generate a CSR directly on the key so that the private key never leaves the hardware.

In the YubiKey Manager GUI:

  1. Go to Applications → PIV → Certificates → Authentication (9a)
  2. Click Generate → choose Certificate Signing Request (CSR)
  3. Fill in the subject (e.g. CN=Your Name,E=you@example.com)
  4. Save the CSR as csr-9a.csr

Slot 9a (Authentication) is the one confirmed to work with this setup. I was not able to make 9c/9d work.

2. Create a self-signed CA

# Generate the CA private key
openssl genrsa -out ca.key 4096

# Self-sign the CA certificate (10-year validity)
openssl req -x509 -new -nodes -key ca.key -days 3650 -out ca.crt -subj "/CN=Cat Factory CA/"

Keep ca.key and ca.crt somewhere safe — you will need ca.crt later when importing into Gpg4win so it trusts signatures made by this CA.

3. Create the OpenSSL extension config

Edit openssl.config and update the email address in the [ san ] section to match your Git identity.

The otherName OID (1.3.6.1.4.1.311.20.2.3) is the Microsoft UPN SAN, which Gpg4win/gpgsm uses to match a certificate to a Git user.signingkey value.

4. Sign the CSR

openssl x509 -req \
  -days 3650 \
  -in csr-9a.csr \
  -CA ca.crt \
  -CAkey ca.key \
  -CAcreateserial \
  -out cert.pem \
  -extfile openssl.config \
  -extensions v3_req

Verify the extensions were applied correctly:

openssl x509 -in cert.pem -noout -text"

5. Import the certificate back onto the YubiKey

In YubiKey Manager:

  1. Go to Applications → PIV → Certificates → Authentication (9a)
  2. Click Import and select cert.pem

Windows Setup

1. Install Gpg4win

Download and install from https://www.gpg4win.org/. Only GnuPG is required, Kleopatra is not needed but nice to have.

2. Configure gpg-agent

Edit %APPDATA%\gnupg\gpg-agent.conf (create it if it does not exist):

pinentry-program "C:\Program Files\Gpg4win\bin\pinentry.exe"
# log-file C:\temp\gpg-agent.log
# debug-level basic

3. Configure scdaemon

Edit %APPDATA%\gnupg\scdaemon.conf (create it if it does not exist):

disable-ccid
pcsc-shared
application-priority piv
# log-file C:\temp\scd.log
# debug-level guru

disable-ccid tells scdaemon to use the system PCSC stack instead of its own CCID driver, which is required for YubiKey PIV to work reliably on Windows.

4. Restart the GnuPG agent

Open PowerShell or Command Prompt and run:

& 'C:\Program Files\GnuPG\bin\gpgconf.exe' --kill all

5. Verify the YubiKey is visible

certutil.exe -scinfo

Your certificate should appear in the output. If it does not, check that the YubiKey PIV applet is initialised and that the smartcard service is running.

Also verify that GnuPG itself can see the card:

& 'C:\Program Files\GnuPG\bin\gpg.exe' --card-status

This should print the card serial number, reader name, and key information. If it fails, scdaemon is not communicating with the card.


Importing Certificates into GnuPG

gpgsm --learn-card fails to import the certificate if the CA is not trusted yet. It can reach the card and create key stubs, but then bails out before completing the certificate import. The fix is to import and trust the CA first, then run --learn-card.

1. Import the CA certificate

& 'C:\Program Files\GnuPG\bin\gpgsm.exe' --import ca.crt

2. Trust the CA certificate

Get the CA fingerprint:

& 'C:\Program Files\GnuPG\bin\gpgsm.exe' --list-keys

The fingerprint is shown with colons (e.g. AB:CD:EF:...). Strip the colons and add a line to %APPDATA%\gnupg\trustlist.txt (create the file if it does not exist):

ABCDEF...  S

The S flag marks it as a trusted S/MIME root CA, which is what gpgsm uses for X.509 signing.

3. Restart the GnuPG agent

& 'C:\Program Files\GnuPG\bin\gpgconf.exe' --kill all

4. Import the signing certificate from the YubiKey

& 'C:\Program Files\GnuPG\bin\gpgsm.exe' --learn-card

5. Verify gpgsm can see the key

# Should show the certificate
& 'C:\Program Files\GnuPG\bin\gpgsm.exe' --list-keys

# Should show the card-backed secret key stub
& 'C:\Program Files\GnuPG\bin\gpgsm.exe' --list-secret-keys

Both should return the same certificate. Copy the fingerprint from --list-keys — you will need it for the Git configuration in the next section.


WSL Setup

1. Install the wrapper script

Copy gpgsm-wsl from this repository to /usr/bin/ and make it executable:

sudo cp ./gpgsm-wsl /usr/bin/gpgsm-wsl
sudo chmod +x /usr/bin/gpgsm-wsl

The script does the following:

  • Path translation — converts absolute Linux paths (e.g. /home/user/file) to their Windows equivalents using wslpath.
  • Stdin forwarding — when Git passes - to signal that signed data should be read from stdin, the script buffers it to a temporary file and passes the Windows path to gpgsm.exe instead, since cross-OS stdin piping is unreliable.
  • Card wake-up — runs gpg.exe --card-status with stdin from /dev/null and output discarded before gpgsm.exe so scdaemon/PCSC is ready. Without redirecting stdin, gpg could consume the commit data Git pipes in for signing and the signature would be wrong. This also avoids a first-use "insert card" prompt right after another stack touched the YubiKey (for example SSH via PKCS#11).

2. Configure Git

Find your certificate fingerprint from the previous step (gpgsm --list-keys), then configure Git globally:

git config --global gpg.format x509
git config --global gpg.x509.program gpgsm-wsl
git config --global commit.gpgsign true
git config --global user.signingkey "<fingerprint>"

Replace <fingerprint> with the fingerprint of your signing certificate as shown by --list-keys.


Verification

Make a test commit and confirm the signature is present:

git commit --allow-empty -m "test: verify gpgsm signing"
git verify-commit HEAD

You should see a line like Good signature from [...] in the output.


Troubleshooting

Symptom Likely cause Fix
gpgsm.exe cannot find the key gpg-agent not running Run gpgconf.exe --kill all and retry
PIN prompt never appears Wrong pinentry-program path Verify the path in gpg-agent.conf matches your Gpg4win install
certutil -scinfo shows no card PCSC service stopped or YubiKey not inserted Restart the Smart Card service (SCardSvr) via services.msc or run Restart-Service SCardSvr in PowerShell, then replug the YubiKey
wslpath: invalid argument Path contains characters special to WSL Ensure filenames do not contain backslashes or colons
First sign says insert card, second works (e.g. after SSH with PKCS#11) PCSC reader handoff between clients The wrapper runs gpg.exe --card-status before each gpgsm call; update the script if yours is older. Or run gpg.exe --card-status once in PowerShell before signing

To enable verbose logging, uncomment the log-file and debug-level lines in the config files and restart the agent/daemon.

About

Sign Git commits from WSL using a YubiKey PIV x509 certificate with gpgsm

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages