How it works

I have Feitian ePass PKI token (available from http://www.gooze.eu) which I use everyday. After a few years I finally became tired of running ssh-add -s /usr/lib/opensc-pkcs11.so every morning.

With the help of udev you can run scripts when a device is plugged in, so let's just run ssh-add and be done with it.

Why it doesn't

Unfortunately it's not so simple. Udev runs as root. I'm a mortal user running some X. Worse, we need to interact with the ssh-agent, so we need a proper environment too, so solutions proposed on pages like http://superuser.com/questions/2490... don't really work.

Solution

The solution is to use two scripts. The first will be triggered by udev, the second will run in our X session and execute the ssh-add command. Of course we need some IPC so the second script actually knows it has to do something: a named pipe.

Let's define the location of the named pipe:

# /etc/default/control-token
#
# Set location of the IPC pipe
PIPE=/tmp/control_token.pipe

Now setup udev to run a script when I insert my token, and also trigger the script when I remove it, so I can tell the ssh-agent to drop the keys.

# /etc/udev/rules.d/99-run-askpass.rules
ACTION=="add", SUBSYSTEMS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0503", RUN+="/etc/udev/send_token_control.sh add &"

ACTION=="remove", SUBSYSTEMS=="usb", ENV{ID_VENDOR_ID}=="096e", ENV{ID_MODEL_ID}=="0503", RUN+="/etc/udev/send_token_control.sh remove &"
#!/bin/sh
#
# /etc/udev/send_token_control.sh

if [ -f /etc/default/control-token ]; then
  . /etc/default/control-token
fi

# Just exit if the control pipe doesn't exist
[ -p "$PIPE" ] || exit 0

echo "$1" "$ID_SERIAL" > "$PIPE"

The second script will read the command from the named pipe and will run ssh-add. It captures the result to display a notification. Removing the keys from the ssh-add is actually the tricky bit: ssh-add -D does not properly remove pkcs11 keys, this has to be done using ssh-add -e /usr/lib/openpkcs11.so. Unfortunately ssh-add insists on asking a password, although any will do. So we just use echo to keep it quiet.

#!/bin/bash 
#
# /usr/local/run-ssh-askpass.sh

if [ -f /etc/default/control-token ]; then
  . /etc/default/control-token
fi

# We need a control pipe location
[ -v PIPE ] || exit 1

trap "rm -f '$PIPE'" EXIT

if [ ! -p "$PIPE" ]; then
  rm -f "$PIPE"  
  mkfifo "$PIPE"
fi

NOTIFY_CMD="notify-send -t 1000 --hint=int:transient:1"

while true
do
  if read cmd token < $PIPE; then
    token=${token:-Token}
    case "$cmd" in
      add)
        result=$( /usr/bin/ssh-add -s /usr/lib/opensc-pkcs11.so < /dev/null 2>&1 )
        sleep 1
        if [ $? -gt 0 ]; then
          $NOTIFY_CMD "$token" "$result" -i error
        else
          $NOTIFY_CMD "$token" "$result"
        fi
        ;;
      remove)
        result=$( SSH_ASKPASS="/bin/echo" /usr/bin/ssh-add -e /usr/lib/opensc-pkcs11.so < /dev/null 2>&1 )
        sleep 1
        if [[ $? -eq 0 && "${result:0:17}" != "SSH_AGENT_FAILURE" ]]; then
          $NOTIFY_CMD "$token" "$result"
        fi
        ;;
    esac
  fi
done

Next make sure this script autostarts when you start your X session, using gnome-session-properties if you use gnome.