Skip to content

Instantly share code, notes, and snippets.

@rfdonnelly
Last active March 17, 2024 18:32
Show Gist options
  • Save rfdonnelly/08a64c9fb2cdfab4c8166a5592754d82 to your computer and use it in GitHub Desktop.
Save rfdonnelly/08a64c9fb2cdfab4c8166a5592754d82 to your computer and use it in GitHub Desktop.
SSH Smart Card Authentication for WSL 2

SSH Smart Card Authentication for WSL 2

Windows 11 Native

This section is useful for establishing a baseline and for solving the Workaround: Too Many Authentication Failures problem. Otherwise, this section is optional.

  1. Install OpenSC (win64)

  2. Test smart card access

    In PowerShell
    & 'C:\Program Files\OpenSC Project\OpenSC\tools\pkcs11-tool.exe' --login --test
  3. SSH into host

    In PowerShell
    ssh -I 'C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll' user@host

WSL 2

  1. Download and install rfdonnelly/WinCryptSSHAgent

    Note
    This is a fork of buptczq/WinCryptSSHAgent which makes makes the following workarounds unnecessary: Workaround: Too Many Authentication Failures, Workaround: Compile WinCryptSSHAgent from Source.
  2. Run WinCryptSSHAgent

    In PowerShell
    .\WinCryptSSHAgent.exe --smart-card-logon-only
  3. Expose to WSL 2

    1. Right click on the WinCryptSSHAgent icon in the Windows system tray

    2. Select "Show WSL2 / Linux on Hyper-V Settings"

      This will install the WinCryptSSHAgent Hyper-V service and may require a restart.

    3. Install socat

      In WSL 2
      sudo apt install socat
    4. Create the WinCryptSSHAgent socket

      In WSL 2
      export WINCRYPT_SSH_AUTH_SOCK=/tmp/wincrypt-hv.sock
      already_running=$(ss -lnx | grep -q $WINCRYPT_SSH_AUTH_SOCK; echo $?)
      if test $already_running != 0; then
          rm -f $WINCRYPT_SSH_AUTH_SOCK
          echo "[WinCrypt SSH Agent] Starting relay"
          (setsid nohup socat UNIX-LISTEN:$WINCRYPT_SSH_AUTH_SOCK,fork SOCKET-CONNECT:40:0:x0000x33332222x02000000x00000000 >/dev/null 2>&1 &)
      else
          echo "[WinCrypt SSH Agent] Using existing relay"
      fi
      Note

      This is similar to what the WinCryptSSHAgent "WSL2 / Linux on Hyper-V Settings" provide but with the following major differences:

      • SSH_AUTH_SOCK environment variable renamed to WINCRYPT_SSH_AUTH_SOCK so that it can coexist with another SSH Agent

      • Run socat in the background

      • Add echo statements

      Note

      This snippet can be added to your shell rc to automate it.

  4. List available keys

    In WSL 2
    SSH_AUTH_SOCK=$WINCRYPT_SSH_AUTH_SOCK ssh-add -l
  5. SSH into host

    In WSL 2
    SSH_AUTH_SOCK=$WINCRYPT_SSH_AUTH_SOCK ssh user@host
    Important

    If too many keys are provided, SSH will fail to authenticate since the SSH server will only allow the client to try a limited number of keys before disconnecting. You will get an error like:

    Received disconnect from x.x.x.x port 22:2: Too many authentication failures
  6. Add to ~/.ssh/config

    ~/.ssh/config
    Host <host>
        User <user>
        IdentityAgent $WINCRYPT_SSH_AUTH_SOCK
  7. SSH into host using SSH config

    In WSL 2
    ssh host

Appendix A: Workaround: Too Many Authentication Failures

If WinCryptSSHAgent provides too many keys, you will need to tell SSH which key to use via an IdentityFile.

  1. Determine which key is the right one

    In PowerShell
    ssh -I 'C:\Program Files\OpenSC Project\OpenSC\pkcs11\opensc-pkcs11.dll' user@host -v

    Note the fingerprint of the key that was used.

  2. Find the same fingerprint provided by WinCryptSSHAgent

    In WSL 2
    SSH_AUTH_SOCK=$WINCRYPT_SSH_AUTH_SOCK ssh-add -l

    Note the line number.

  3. Isolate the fingerprint

    In WSL 2
    SSH_AUTH_SOCK=$WINCRYPT_SSH_AUTH_SOCK ssh-add -l | head -n$line | tail -n1

    Adjust $line until the correct fingerprint is returned.

  4. Copy the key to an IdentifyFile

    In WSL 2
    SSH_AUTH_SOCK=$WINCRYPT_SSH_AUTH_SOCK ssh-add -L | head -n$line | tail -n1 > ~/.ssh/id.pub
  5. SSH into host

    In WSL 2
    SSH_AUTH_SOCK=$WINCRYPT_SSH_AUTH_SOCK ssh user@host -i ~/.ssh/id.pub
  6. Add to ~/.ssh/config

    ~/.ssh/config
    Host <host>
        User <user>
        IdentityAgent $WINCRYPT_SSH_AUTH_SOCK
        IdentityFile ~/.ssh/id.pub
  7. SSH into host using SSH config

    In WSL 2
    ssh host

Appendix B: Workaround: Compile WinCryptSSHAgent from Source

  1. Install Go for Windows

  2. Install build script dependency

    go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo
  3. Clone WinCryptSSHAgent

    git clone https://github.com/buptczq/WinCryptSSHAgent.git
    cd WinCryptSSHAgent
  4. Apply the following diff

    This avoids conflict with other SSH Agents like the Windows SSH Agent and the 1Password SSH Agent.

    diff --git a/app/app.go b/app/app.go
    index a780eb2..bd13005 100644
    --- a/app/app.go
    +++ b/app/app.go
    @@ -8,7 +8,7 @@ import (
     const (
            WSL_SOCK    = "wincrypt-wsl.sock"
            CYGWIN_SOCK = "wincrypt-cygwin.sock"
    -       NAMED_PIPE  = "\\\\.\\pipe\\openssh-ssh-agent"
    +       NAMED_PIPE  = "\\\\.\\pipe\\wincrypt-ssh-agent"
            APP_CYGWIN  = iota
            APP_WSL
            APP_WINSSH
  5. Build WinCryptSSHAgent

    .\build.bat

Appendix C: Tool Versions

In PowerShell
> systeminfo | findstr /B /C:"OS Name" /B /C:"OS Version"
OS Name:                   Microsoft Windows 11 Pro
OS Version:                10.0.22631 N/A Build 22631
> ssh -V
OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3
> & 'C:\Program Files\OpenSC Project\OpenSC\tools\opensc-tool.exe' --version
OpenSC-0.24.0, rev: f15d0c52, commit-time: 2023-12-13 10:43:57 +0100
> go version
go version go1.22.0 windows/amd64
In WSL 2
> lsb_release -a | grep Description
Description:    Ubuntu 23.04
> uname -a
Linux hyperion 5.15.133.1-microsoft-standard-WSL2 #1 SMP Thu Oct 5 21:02:42 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
> socat -V | grep 'socat version'
socat version 1.7.4.4 on 06 Nov 2022 08:15:51
> ssh -V
OpenSSH_9.0p1 Ubuntu-1ubuntu8.7, OpenSSL 3.0.8 7 Feb 2023
@NicolasRouquette
Copy link

For 2, step iv, I suggest wrapping the snippet with a test for a WSL2 environment:

if [ ! -z "$WSL_DISTRO_NAME" ] && [ -f "/etc/wsl.conf" ]; then
  # In a WSL linux environment.
fi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment