Configuring and using a local Yubikey for SSH with FIDO2 is very straight forward using the Yubico manual Securing SSH with FIDO2

But in some cases I would like to use my Yubikey based SSH key on my office's workstation when I am at home using my notebook.

After some research I found USB/IP which enables to access USB devices of remote machines. ArchWiki contains helpful information: USB/IP

According to my understanding, only one application can access an USB device at once.

Manually attaching and detaching the Yubikey on the notebook and the workstation before use is not very user friendly.

Luckily ssh delegates the communication with the yubikey to the (configurable) binary ssh-sk-helper. Therefore I decided to write a wrapper. The job of the wrapper is to attach the Yubikey before usage, then call the real ssh-sk-helper and finally to detach the Yubikey.

Additionaly I wanted to access the Yubikey directly if the key is not bound to USB/IP.

The process looks as following:

  1. bind Yubikey to USB/IP: systemctl start usbip-bind@1050:0407
  2. Start SSH Login to the workstation: ssh username@workstation
    1. attach bound USB/IP Yubikey on notebook
    2. confirm presence for SSH on notebook
    3. detach Yubikey from USB/IP on notebook
  3. SSH Connection is opened with remote port forwarding
  4. Use SSH on workstation: ssh username@somewhere-else
    1. attach remote USB/IP Yubikey on workstation
    2. confirm presence for SSH on workstation
    3. detach Yubikey from USB/IP on workstation
  5. Close SSH connection
  6. Unbind Yubikey from USB/IP: systemctl stop usbip-bind@1050:0407

Configure SSH to use my ssh-sk-helper-wrapper.sh in .bashrc/.zshrc on notebook and workstation

export SSH_SK_HELPER=/path/to/ssh-sk-helper-wrapper.sh

Create /path/to/ssh-sk-helper-wrapper.sh

#!/bin/bash

# Logging tag
LT=ssh-sk-helper-wrapper

# The required USB device
USBDEVICE="1050:0407"

# mode: check if usbipd is listening directly or via ssh tunnel
USBIPMODE=`if [ $( netstat -tupln 2> /dev/null | grep LISTEN | grep 3240 | wc -l ) = 2 ] ; then echo true ; else echo false ; fi`

USBIPAVA=false
USBIPBUS=""

if [ "$USBIPMODE" = true ]
then
    # check if there are bound devices
    USBIPAVA=`if [ $( usbip list -p -r 0.0.0.0 2> /dev/null | wc -l ) = 0 ] ; then echo false ; else echo true ; fi`
    # determine device bus
    USBIPBUS=$( usbip list -p -r 0.0.0.0 | grep $USBDEVICE | cut '-d:' -f1 | awk '{print $1}')
fi

logger -t $LT "usbipmode=$USBIPMODE available=$USBIPAVA bus=$USBIPBUS"

# attach if required
if [ "$USBIPMODE" = true ] && [ "$USBIPAVA" = true ]
then
    sudo /usr/bin/usbip attach -r 0.0.0.0 -b $USBIPBUS
    # wait for device to become ready
    sleep 2
fi

# Start the real ssh-sk-helper
coproc /usr/lib/ssh/ssh-sk-helper "$@"

# first some necessary file-descriptors fiddling
exec {HLP_INPUT}>&${COPROC[1]}-
exec {HLP_OUTPUT}<&${COPROC[0]}-

# background commands to relay normal stdin/stdout activity
cat <&0 >&${HLP_INPUT} &
cat <&${HLP_OUTPUT} >&1 &

# set signal handler up
SIGTERM_RECEIVED=false ; trap 'SIGTERM_RECEIVED=true' SIGTERM

# endless loop waiting for events
while true; do
    # wait for server to exit or sigterm received
    wait ${COPROC_PID}
    EXIT_STATUS=$?
    # if sigterm received:
    if [ $EXIT_STATUS -gt 128 ] && $SIGTERM_RECEIVED ; then
        # kill proxy command relaying stdin to ssh-sk-helper
        kill %2
        # close ssh-sk-helpers's stdin
        exec {HLP_INPUT}<&-
        # wait for actual server to exit
        wait ${COPROC_PID}
        exit $?
    # something else happened: kill proxy commands and exit with ssh-sk-helpers's own exit status
    else
        kill %2
        kill %3
    fi
    # detach if required
    if [ "$USBIPMODE" = true ] && [ "$USBIPAVA" = true ]
    then
        sudo /usr/bin/usbip detach -p 00
    fi
    exit $EXIT_STATUS
done

Configure sudoers using visudo on notebook & workstation

username ALL=(ALL) NOPASSWD: /usr/bin/usbip attach -r 0.0.0.0 -b [0-9]-[0-9]
username ALL=(ALL) NOPASSWD: /usr/bin/usbip detach -p 00

Create /etc/systemd/system/usbip-bind@.service on notebook

[Unit]
Description=USB-IP Binding device id %I
After=network-online.target usbipd.service
Wants=network-online.target
Requires=usbipd.service

[Service]
Type=simple
ExecStart=/bin/sh -c "/usr/bin/usbip bind --$(/usr/sbin/usbip list -p -l | grep '#usbid=%i#' | cut '-d#' -f1)"
RemainAfterExit=yes
ExecStop=/bin/sh -c "/usr/bin/usbip unbind --$(/usr/sbin/usbip list -p -l | grep '#usbid=%i#' | cut '-d#' -f1)"
Restart=on-failure

[Install]
WantedBy=multi-user.target

Configure port forwarding in .ssh/config on notebook

Host workstation
    RemoteForward 3240 localhost:3240