Author
Leo Vandewoestijne
Original Publication
forum.yubico.com
Original Publication date
Sept 06, 2012
Updated
Feb 22, 2024

Yubikey + SSH + OTP

Yubikey OTP and OpenSSH, without PAM, nor remote API.

The Yubikey is IMHO the biggest pioneer in Hardware Security Keys. I was an early adaptor since almost the beginning. The concept they innovated (and later improved) was simply brilliant. Because their One Time Password (OTP) functionality was a gamechanger, enabling administrator to eliminate password sharing by (mis)users and (ab)users of all kinds.

But mind: back then the SK interactions with ssh certificates didn't exists yet, which nowadays brought that to a whole different level, and today is much better to implement. Therefore the publication of below concept is just for historic reason.
For know-how and inspiration. Not as recommended use
in todays world.

To protect your SSH authentication with an additional OTP you will need these following files:

- /home/john/.ssh/yubikey
- /home/john/.ssh/yubikey_count
- /etc/ssh/sshd_config
- /etc/ssh/yubikey.sh
- /etc/ssh/yubikey.pl

Their function and content are described below.

/home/john/.ssh/yubikey contains the private id, a colon as delimiter, and the AES.


1d1d1d1d1d1d:ae5ae5ae5ae5ae5ae5ae5ae5ae5aeetc

/home/john/.ssh/yubikey_count contains the counter against replay attacks.
When the descrypted string is equal or lower than our count, then the One Time Password (OTP) obvious is re-used in a mallicious attempt.


0

sshd_config contains the ForceCommand, piping the session.


AllowUsers john@192.168.0.37

Match User john # ...or Match Group ykusers
  ForceCommand /etc/ssh/yubikey.sh

# or:

Match Group ykusers
  ForceCommand /etc/ssh/yubikey.sh

After a normal login, then yubikey.sh does the prompt for a OTP of the Yubikey, then uses Perl to decrypt and validate.

Mind to chmod 744 /etc/ssh/yubikey.sh, otherwise execution is impossible.


#!/bin/sh

trap disconnect INT
disconnect() {
  kill -9 $PPID
  exit 1
  }

if [ -n "$SSH_ORIGINAL_COMMAND" ]; then
  $SHELL -c "$SSH_ORIGINAL_COMMAND"
else
  read -p "OTP: " -t 15 OTP_INPUT
  OTP=$(echo "$OTP_INPUT" | tr -c -d a-z)
  if [ $? -eq 0 ] && [ ${#OTP} -eq 48 ]; then
  # OTP is by default 44 char, but customized ones differ.
    CNT=`cat .ssh/yubikey_count`
    NEW=`perl -T -- /etc/ssh/yubikey.pl $OTP $CNT`
    if [ $? -eq 0 ]; then
      echo $NEW > .ssh/yubikey_count
      echo; echo; # or `clear`
      login -f $USER
      disconnect
    fi
  fi
  echo "invalid OTP" > /dev/stderr
  disconnect
fi

yubikey.sh uses yubikey.pl for the decryption, since the Perl library could do so easily, and -not unimportant- it was available. Other libraries, like Python, didn't exist yet.


use strict;
use Auth::Yubikey_Decrypter;

# get values
open (FILE, "<", ".ssh/yubikey") or die "Could not open yubikey file.\n";
my @line = ;
chomp $line[0];
my @ykdata = split ":" , $line[0];
close FILE or die $!;

# decrypt:
my ($publicID,$secretid_hex,$counter_dec,$timestamp_dec,$session_use_dec,$random_dec,$crc_dec,$crc_ok) =
    Auth::Yubikey_Decrypter::yubikey_decrypt($ARGV[0],$ykdata[1]);

# prepare to check replay attacks
my $ctr32 = (($counter_dec & 0x7fff) << 8) + $session_use_dec;

# validate:
if ( $ykdata[0] eq $secretid_hex && $crc_ok == 1 && $ctr32 > $ARGV[1] ) {
  print $ctr32;
  exit 0;
  }

exit 1;

Now user john (or users in the group 'ykusers') will authenticate as usual, and after that are prompted to provide the OTP. When validated and accepted they will get their session as usual.