How it works

I have Feitian ePass PKI token (available from which I use everyday. After a few years I finally became tired of running ssh-add -s /usr/lib/ 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 don't really work.


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

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/ add &"

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

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

# 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/ Unfortunately ssh-add insists on asking a password, although any will do. So we just use echo to keep it quiet.

# /usr/local/

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

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

trap "rm -f '$PIPE'" EXIT

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

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

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

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