Dual-factor authentication for SSHD

For our production entry-point servers, we wanted to enable dual-factor authentication – meaning that users have to use an SSH key and a password to authenticate to the servers.

The Why
Most of our developers are using SSH keys without a passphrase so that they don’t have to type a password every time they sign in. This poses a security risk: if a laptop is stolen (which has happened to one of our developers), anyone can use the key on the machine to connect to our servers. We looked into using passphrase protected SSH keys, but SSHD does not (and cannot) know whether or not the key used is passphrase protected. This means that passphrases as dual factor authentication would have to be human-enforced. Instead of creating more management overhead, we wanted something that would be enforced systematically by the machine.

The How
SSHD can be configured to support multiple authentication schemes (i.e. key based, password, challenge and response). Although each scheme can be enabled/disabled separately, a user has to pass just one of these mechanisms to authenticate. To enforce a second authentication, it has to be performed after the SSHD authentication step is done.

Our first idea was to make the login shell ask users for their password. Since bash doesn’t use PAM, we’d have to either make a patch to bash or rely on the init script (/etc/profile). The init script setup would cover normal use cases, but it would still be possible to circumvent the 2nd authentication.

Then we found the ForceCommand configuration option and an example of a similar feature.

Now all we needed was a way to check the user’s password. This turned out to be a non-trivial task. We were hoping that there was a command line tool to ask for the user’s password, check the keyboard input against system password database, and return the result.

Here’s what we found.

  • Linux doesn’t have a system command to do this.
  • It can be done via a simple Python script, but we don’t want to support another language.
  • Mac OSX has a chkpasswd command, and Apple open sourced it, but nobody ported it Linux.

So we decided to write a simple version in C.

The Whole Shebang
First, compile and install the chkpasswd command

chkpasswd.c

#include <stdlib.h>
#include <unistd.h>
#include <crypt.h>
#include <stdio.h>
#include <shadow.h>

int main (int argc, char *argv[]) {
  char *plain_passwd;
  char *crypt_passwd;
  char *username;
  struct spwd *shadow;

  if (argc != 2) {
    exit(-1);
  }

  username = argv[1];
  plain_passwd = getpass("password:");

  if ((shadow = getspnam(username)) == NULL) {
    exit(-1);
  }

  crypt_passwd = shadow->sp_pwdp;

  exit(strcmp(crypt(plain_passwd, crypt_passwd), crypt_passwd));
}

Makefile

all: chkpasswd.c
        gcc -o chkpasswd -l crypt chkpasswd.c
install: all
        install -s -m 4755 chkpasswd /usr/local/bin

And configure sshd to force the gate-keeper script.

/etc/ssh/sshd_config

PasswordAuthentication no
ChallengeResponseAuthentication no
ForceCommand /etc/ssh/sshd_gatekeeper.sh

/etc/ssh/sshd_gatekeepr.sh

#!/bin/bash
trap "exit 0" SIGINT

CLIENT_IP=`echo $SSH_CLIENT | awk '{print $1}'`

if [ -n "$SSH_ORIGINAL_COMMAND" ]; then
  /bin/grep -qs "^$CLIENT_IP\$" /etc/ssh/sshd_whitelist
  if [ "$?" -eq "0" ]; then
    $SHELL -c "$SSH_ORIGINAL_COMMAND"
    exit 0
  fi

  CMD=`echo $SSH_ORIGINAL_COMMAND | awk '{print $1}'`
  echo "'$CMD' is not supported"
  logger "'$CMD' failed for user=$USER, IP=$CLIENT_IP"
  exit 0
fi

echo "logging in as $USER"
CNT=0
while [ $CNT -lt 3 ]; do
  /usr/local/bin/chkpasswd $USER
  if [ "$?" -eq "0" ]; then
    $SHELL -l
    exit 0
  fi
  let CNT=CNT+1
done

logger "too many failed attempts: user=$USER, IP=$CLIENT_IP"

* You can white-list certain IPs by adding them to /etc/ssh/sshd_whitelist

Now restart SSHD and try to avoid users for a couple of days (or until they get used to entering a password to sign in)…

Afterthoughts
An SSH key is actually considered “something the user knows“, not “something the user has“, so the above approach is not dual-factor authentication in the strict sense. In reality, however, it’s very close to “something the user has” since few people will be able to memorize it. Most users will keep theirs in a physical storage device, making it behave like a physical object.

Before settling on our current approach, we did try RSA SecureId. It’s a true dual-factor authentication system with “something the user has”, but we decided against it in the end because it was simply too much of a hassle to use the device every time we needed to log into the system.

Another (free) alternative is Google authenticator. It can be integrated with SSHD using a PAM module, but it still has the same requirement that each time we log into the servers, we have to enter the generated code.

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s