Freeradius and rlm_yubikey

One of the rlm_yubikey module’s functionalities is to check yubikey OTP token values against a cloud-based Yubico validation server.

Yubico OTP is a strong authentication mechanism, that can be used without any additional client-side software. You need Yubikey series 4 or 5. They just work as a USB keyboard to the operating system. No need to install any special drivers. The principle of operation is described here.

An example otp is as follows: ccccccukiegehhulguubvvcufvlnelicklfitvndnkeu. The sequence as a whole is encoded using the so-called Modhex code. The first 12 characters (ccccccukiege) are always constant and represent the Yubikey ID. It is simply Yubikey’s serial number encoded in Modhex. In the freeradius configuration, we can assign one or more Yubikey IDs to a given user. The remaining 32 characters contain a AES-encryped variable and unique passcode. In the presented example, the user’s password will consist of two connected parts: a permanent password and an otp. Suppose the user’s password is 0pa$$, then together with otp it will take the form: 0pa$$ccccccukiegehhulguubvvcufvlnelicklfitvndnkeu.

To use the Yubico validation server, you must first register here. You’ll receive two parameters that will identify you: client_id and api_key.

Installation on Ubuntu 20.04 LTS

sudo apt-get install freeradius
sudo apt-get install freeradius-yubikey

Let’s modify the files below. For simplicity, the user database is in the users file. User fred has password 0pa$$ (hashed with salted SHA-512) and two Yubikeys assigned.

/etc/freeradius/3.0/mods-config/files/authorize:

fred SSHA2-512-Password := "R/yRaSHMPMQDipVoQNLh1NiAkkfyeXMu2ZD2QUEauW4g9yDOEWsbt+JkxNAEeGZd9GLxfcO7hmLLkN0thDFxh/Wvq9k="
      Yubikey-Private-ID += "ccccccukiege",
      Yubikey-Private-ID += "ccccccuhnvgr"

The rlm_yubikey module has a configuration file in which we complete the previously obtained parameters: client_id and api_key.

/etc/freeradius/3.0/mods-available/yubikey:

yubikey {
	validate = yes

	validation {
		servers {
			uri = 'https://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s'
			uri = 'https://api2.yubico.com/wsapi/2.0/verify?id=%d&otp=%s'
		}

		client_id = 00000
		api_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

		pool {
			start = ${thread[pool].start_servers}
			min = ${thread[pool].min_spare_servers}
			max = ${thread[pool].max_servers}
			uses = 0
			retry_delay = 30
			lifetime = 0
			idle_timeout = 60
			spread = yes
		}

	}

}

Now we create a default virtual server configuration. Of course, below is an example file limited to a working minimum. We’ll name this file yubikey_test and put it in the sites_available directory.

/etc/freeradius/3.0/sites-available/yubikey_test:

server default {
    listen {
	type = auth
	ipaddr = *
	port = 0
    }

    authorize {
	yubikey
	if (ok) {
	    update control {
		    Auth-Type := yubikey
		}
	}

	files

    }


    authenticate {

	Auth-Type yubikey {
		yubikey
		pap
		update request {
		    Yubikey-Private-Id := Yubikey-Public-ID
		}
	}

    }


    post-auth {
	
	#check that the yubikey used is assigned to this user
	if (&Yubikey-Private-Id != &reply:Yubikey-Private-ID[*]) {
	    update reply {
		Reply-Message := "Token not allowed for use by user %{User-Name}"
	    }
	    reject
	}

    	#remove list of Yubikey Private IDs from the reply
	update reply{
		Yubikey-Private-ID !* ANY
	}

        #remove attributes in case of reject
	Post-Auth-Type REJECT {
	    attr_filter.access_reject
	}

    }
}

Next, we link the configuration file in sites-enabled. If necessary, we remove unnecessary links from here:

sudo ln -s /etc/freeradius/3.0/sites-available/yubikey_test /etc/freeradius/3.0/sites-enabled/

You also need to link the yubikey module in mods-enabled. If we don’t use eap, we also remove the link to the eap module:

sudo ln -s /etc/freeradius/3.0/mods-available/yubikey /etc/freeradius/3.0/mods-enabled/
sudo rm /etc/freeradius/3.0/mods-enabled/eap

Now we can start testing. It’s best to do this on two consoles. On the first one, we run freeradius in debug mode (sudo freeradius -X).

On the next console, let’s create a simple script that combines the user’s password and otp read from Yubikey:

#!/bin/bash

PASS='0pa$$'

echo Tap Yubikey
read OTP

radtest fred $PASS$OTP 127.0.0.1 100 testing123

Run it and touch the button on Yubikey. If you use a Yubikey which is assigned to a user (see the ‘authorize’ file above) you should get:

Tap Yubikey
ccccccuhnvgrkeerurgejjdubburvfrrbgvnjkfjfdvv
Sent Access-Request Id 132 from 0.0.0.0:32913 to 127.0.0.1:1812 length 122
	User-Name = "fred"
	User-Password = "0pa$$ccccccuhnvgrkeerurgejjdubburvfrrbgvnjkfjfdvv"
	NAS-IP-Address = 10.0.0.3
	NAS-Port = 100
	Message-Authenticator = 0x00
	Cleartext-Password = "0pa$$ccccccuhnvgrkeerurgejjdubburvfrrbgvnjkfjfdvv"
Received Access-Accept Id 132 from 127.0.0.1:1812 to 127.0.0.1:32913 length 20

In case of using Yubikey not assigned to the user, you’ll get:

(…)
Received Access-Reject Id 224 from 127.0.0.1:1812 to 127.0.0.1:54164 length 60
	Reply-Message = "Token not allowed for use by user fred"

If you try to reuse otp, you’ll get Access-Reject and in debug on the first console yubikey: ERROR: Yubikey OTP was replayed (REPLAYED_OTP). Likewise, in the event of a mismatch of the user password (0pa$$) or otp you’ll get an Access-Reject.