With security becoming more and more of an ongoing issue, which it really has been for a while now just being getting more attention now, it is common to see web based services/companies implement things like 2 factor authentication (2FA). Luckily implementing 2 factor authentication is SSH is possible with openSSH 6.2 or newer and a extra module for [PAM] (https://en.wikipedia.org/wiki/Pluggable_authentication_module “Pluggable Authentication Module”). The PAM modules I used was [libpam-google-authenticator] (https://github.com/google/google-authenticator/tree/master/libpam “Google Authenticator PAM Plugin”), available on Arch Linux in the AUR for Linux and oath-toolkit on FreeBSD both implement event-based HOTP and time-based TOTP algorithms.

Setup on Linux with Google Authenticator libpam plugin

After installing libpam-google-authenticator, either through the your distributions package manager or from source, there is a final step that needs ran to complete the installation, as root run libtool --finish /usr/lib/security. Once that is done the module needs to be added to PAM, and since we only want to use 2FA for SSH connections edit /etc/pam.d/sshd and add pam_google_authenticator.so below the system-remote-login lines.

auth        include     system-remote-login
auth        required    pam_google_authenticator.so
account     include     system-remote-login
password    include     system-remote-login
session     include     system-remote-login

This will make it so the user will have to give the correct password for the account, and a 2FA token to be able to log into the system. Next make sure that challenge-response authentication is enabled in /etc/ssh/sshd_config which should look like:

ChallengeResponseAuthentication yes

At this point the authentication methods needs to be setup in /etc/ssh/sshd_config this can go one of two ways:

  1. users with a valid public key can login without a token,
  2. users with a valid public key also has to have a token.

Either way authentication attempts without a valid public/private key pair will have to supply a password and token to be able to authenticate.

To make it work with a valid public/private key pair and no token, find the AuthenticationMethods section in /etc/ssh/sshd_config and change it to the following;

AuthenticationMethods publickey keyboard-interacive:pam

To make it so a 2FA token is required even for users with a valid key pair set the Authentication Method line to be:

AuthenticationMethods publickey,keyboard-interacive:pam

Finally the secret key file needs created, so it can be added to your OTP-generator. This is done by running google-authenticator as the user you want to enable 2FA for, it will ask a few questions then generate the secret key that you put in your Generator, as well as generate a handful of emergency “scratch” codes. If qrencode is installed it will also generate a scannable QR code.

 $ google-authenticator
   Do you want authentication tokens to be time-based (y/n) y
   <Here you will see generated QR code>
   Your new secret key is: ZVZG5UZU4D7MY4DH
   Your verification code is 269371
   Your emergency scratch codes are:

   Do you want me to update your "/home/username/.google_authenticator" file (y/n) y

   Do you want to disallow multiple uses of the same authentication
   token? This restricts you to one login about every 30s, but it increases   your chances to notice or even prevent man-in-the-middle attacks (y/n) y

   By default, tokens are good for 30 seconds and in order to compensate for
   possible time-skew between the client and the server, we allow an extra
   token before and after the current time. If you experience problems with poor
   time synchronization, you can increase the window from its default
   size of 1:30min to about 4min. Do you want to do so (/n) n
      If the computer that you are logging into is not hardened against brute-force
   login attempts, you can enable rate-limiting for the authentication module.
   By default, this limits attackers to no more than 3 login attempts every 30s.
   Do you want to enable rate-limiting (y/n) y

Once that is done, restart sshd and enjoy!

Setup on FreeBSD with OATH-Toolkit pam plugin

Note: It has recently come to my attention that the libpam-google-authenticator plugin also will work under FreeBSD, but since that was not known to me at the time of setting this up I’ll continue to document using OATH-Toolkit. I assume for using the Google developed plugin will require the same setup as it did under Linux to work.

Once OATH-Toolkit has been installed, either through pkg or from ports, PAM needs setup to load the correct module. As root edit /etc/pam.d/sshd and remove the pam_opie modules and add the pam_oath module.

auth requisite pam_uix.so no_warn try_first_pass
auth required /usr/local/lib/security/pam_oath.so usersfile=/usr/local/etc/users.oath

Once PAM has been setup, the pam_oath module requires some configuring itself before it can function. The usersfile that was specified in /etc/pam.d/ssh needs to be created with an entry for each OATH user. Each entry uses the following format:

token type, username, optional password, secret

A simple way to generate a secret and add the entry to the file is with

SECRET="$(head -c 1024 /dev/urandom | openssl sha1)"
echo "HOPT/T30/6 username - $SECRET" >> /usr/local/etc/users.oath
chmod 600 /usr/local/etc/users.oath
chown root /usr/local/etc/users.oath

The same secret used for OATH is the secret key that needs to be used in your authenticator app. The problem here is that OATH expects the secret to be hexadecimal while most token generators, like Google Authenticator, expects the secret to be Base32 encoded. The hexadecimal secret can be converted to Base32 with:

oathtool -v -totp $SECRET | grep Base32 | awk '{ print $3; }'

That will return a Base32 encoded secret that can be used to configure your token generator.

Once both the server and client side is configured it is highly recommeneded to store a copy of the secret key, and the “scratch” codes if using libpam-google-authenticator, in a safe and secure place as there is no way to recover the secret key if lost.