We’re going to use oathtool for our DIY setup on a FreeBSD host. These steps should be nearly identical on a Linux host, however. We need to create a file with our users and secrets. These secrets are the same secrets your users will import into their MFA application (OTP Auth, Google Authenticator, Okta Verify, etc.).
For our example, we have a single user we’ll call email@example.com. This user is different from the CN (Common Name) of the client certificates. We’re going to create a secrets file called oath.secrets in our /usr/local/etc/openvpn/ path that is owned by root (we’re not dropping privileges in this example, but that file needs to be readable by the oath.sh script). Our file will have a simple user:secret format:
To get the secret, we use some common utilities to grab 30 random hex characters. On FreeBSD we leverage /dev/urandom:
root@server# head -10 /dev/urandom | sha256 | cut -b 1-30
Now, with the oath.sh script in place, add the following lines to your openvpn.conf file on the server and restart OpenVPN:
auth-user-pass-verify ./oath.sh via-file
The hex value is our real secret. The script I included provides both values, but you can always recalculate the Base32 secret with oathtool:
root@server# oathtool --totp -v e36e056d6629eb5662fe4fa0ce81d9
Hex secret: e36e056d6629eb5662fe4fa0ce81d9
Base32 secret: 4NXAK3LGFHVVMYX6J6QM5AOZ
Window size: 0
Step size (seconds): 30
Start time: 1970-01-01 00:00:00 UTC (0)
Current time: 2020-06-02 02:56:46 UTC (1591066606)
Counter: 0x3294221 (53035553)
The Base32 secret is what you will ultimately provide your end-users. You will want to use a string like the one below (substitute the user Base32 secret) to generate a QR code they can use to import into Google Authenticator or another app:
For our example user it would look like this:
Note: The entire string needs to be URL-encoded. In the example above, only the <issuer> portion had special characters (spaces as “%20”).
The QR code can be generated using command-line tools on FreeBSD/Linux like py-qrcode, Perl’s Text::QRCode module, and others. Just don’t use an online string to QR code generator – you want to keep the secrets from third parties.
I’ve included another script in the sample zip file that will take a username as an argument and generate the output strings for you (oath-secret-gen.sh).
root@server# ./oath-secret-gen.sh firstname.lastname@example.org
Password + MFA Authentication
The script I included provides the framework to support both a user password and an MFA token. This is accomplished by appending the MFA token to the password with a colon separator:
This allows a total of four factors when using client certificates:
- Client Certificate (per user or machine)
- Username (per user)
- Password (per user)
- MFA Token (per user, frequently changes)
Many places do not ask for the user password. Instead, they only require input of the MFA token in lieu of both. Note that it doesn’t take long for a computer to count to 1,000,000, which are the maximum combinations for a six-digit number. A more robust version of this script would invalidate the current token value for a period of time once used to prevent misuse or brute-force attacks.