2FA Login on Ubuntu with U2F and Google Authenticator

Note: This article infers that you have already configured a U2F device (like a Yubikey), and Google Authenticator. Also, when running pamu2fcfg, run it with the -P option.

Update: It turns out that repository package of pam-u2f version 1.0.8 didn’t support the userpresence=0 option necessary to disable the request for pressing the tactile trigger twice; this option is only supported by FIDO2-capable devices, which was clarified in this GitHub issue by the very knowledgeable (and patient) Alessio Di Mauro.

I’ve been interested in creating a similar authentication flow for my computers to the one we usually encounter on several popular websites (depicted in this Stack Exchange answer). This mainly consists of the following steps:

  • Enter username & password to authenticate
  • If available, use a U2F device as the second factor authentication (like Yubico’s Yubikey)
  • Otherwise, ask for a verification code (from, for example, Google Authenticator)

At first, I wanted to implement my own PAM module, but I’m still not confident enough to not shoot myself in the foot; then I tried my hand at cobbling something together with the pam_exec.so module, but realized that it wasn’t able to pass user input to a script, and in turn making it impossible to make use of the success=n action.

After reading a lot of documentation, I was able to put together something useful, without the need for extra dependencies (other than libpam-u2f, and libpam-google-authenticator).

# What the following lines accomplish:
#   Prompt for a U2F device, and to then press ENTER.
#   In case the U2F device is known, prompt the user to press the tactile trigger.
#   In case the U2F device is not know or present, prompt for a verification code from Google Authenticator.
#   Allow users that are not configured to use U2F or Google Authenticator to log in.
auth [success=1 new_authtok_reqd=ok ignore=ignore default=ignore] pam_u2f.so interactive userpresence=0 nouserok authfile=/etc/Yubico/u2f_keys
auth [success=1 new_authtok_reqd=ok ignore=ignore default=bad]    pam_google_authenticator.so nullok
auth required                                                     pam_u2f.so cue userpresence=1 nouserok authfile=/etc/Yubico/u2f_keys

Sources:

Note: As it was mentioned in the Update above, the desired behaviour would only be achieved with a modern FIDO2-capable device (as was discussed in this GitHub issue). However, for backwards compatibility with devices that don’t support U2F 1.2, I’ve included a version of the code that would require a double touch of the tactile trigger., but still work as intended.

# What the following lines accomplish:
#   Prompt for a U2F device, and to then press ENTER.
#   In case the U2F device is known, prompt the user to press the tactile trigger (twice).
#   In case the U2F device is not know or present, prompt for a verification code from Google Authenticator.
#   Allow users that are not configured to use U2F or Google Authenticator to log in.
auth [success=1 new_authtok_reqd=ok ignore=ignore default=ignore] pam_u2f.so interactive cue nouserok authfile=/etc/Yubico/u2f_keys
auth [success=1 new_authtok_reqd=ok ignore=ignore default=bad]    pam_google_authenticator.so nullok
auth required                                                     pam_u2f.so cue nouserok authfile=/etc/Yubico/u2f_keys