Securing SSH with TOTP

I’ve been playing with personal security lately and trying to get OATH-TOTP to work on my private server. This post is a tutorial of how to get the more interesting features implemented.

Introduction

OATH-TOTP (not related to OAuth) is a standardized way of providing 2-factor authentication (2FA) with tokens that change at predetermined intervals. You may be familiar with the Google Authenticator app, which implements the client side of the spec. A wide range of services implement the server-side part - from Battle.net, through Google, to Facebook and Dropbox.

This post will describe how you too can implement TOTP 2FA for SSH logins to your servers.

The simple stuff

As part of the Authenticator project Google released a PAM (Pluggable Authentication Module) implementation of the 2-factor system. This is readily available and packaged as libpam-google-authenticator on Debian/Ubuntu.

Once installed, you can run google-authenticator to generate your user’s secret and configuration settings. Just follow the prompts and scan the ASCII QR code to add it to the Authenticator app.

You can then add auth required pam_google_authenticator.so to /etc/pam.d/sshd. You’ll also need to have ChallengeResponseAuthentication yes in your /etc/ssh/sshd_config.

This way, when you log in with a password, the system will request your one-time token as well.

The hard stuff

The reason this is less than ideal is that any sufficiently hardened setup already disables password logins. What I wanted in my particular case was a public key login with a mandatory second factor. Turns out, however, that public key login completely bypasses challenge-response login.

After some searching, I found the AuthenticationMethods sshd_config parameter. Adding the line

AuthenticationMethods publickey,keyboard-interactive:pam

to my sshd_config did indeed prompt me for my verification code. But not before prompting me for my password as well. This was a bit too much, so I set out to limit the login sequence to just a prompt for the code.

PAM is magic

The reason this was happening was that publickey,keyboard-interactive:pam required a successful complete PAM run before letting you proceed onwards. My PAM config (in /etc/pam.d/sshd), however, was asking for a password and I’d added the line at the end to also ask for a verification code.

I moved the line to the beginning of the file and changed it so that it is enough to log you in:

auth sufficient pam_google_authenticator.so

However, pressing Ctrl+C or entering an empty code at the 2FA prompt still dropped me to a password prompt. This would clearly not do.

The solution

Turns out, sufficient is an alias for [success=done new_authtok_reqd=done default=ignore]. Notice that the default action is ignore. That is, the module is sufficient to authenticate me but not required.

The final solution was to prepend the following to my /etc/pam.d/sshd instead:

auth [success=done new_authtok_reqd=done default=die] pam_google_authenticator.so nullok

The nullok option means that if the user doesn’t have 2FA set up, the module will still let you through.

I also left the rest of /etc/pam.d/sshd intact, even though it’s unnecessary now - the 2FA module either kills authentication or declares it complete, nothing else should ever execute. That said, PAM is magic and I’m no wizard.

I hope I freed someone’s afternoon for better things than fiddling with ssh and google-authenticator.

P.S.: Always have a backup SSH session open, in case you screw up any part of the configuration. I learned the hard way.


← On the importance of case-insensitivity Droidcon NYC Talk_→