Free 2-factor authentication with RADIUS and HOTP

HOTP is an internet standard that can be used for 2-factor authentication (i.e., something you know and something you have). This is an event-based One-Time Password protocol (i.e., the passwords are generated based on a counter i.o. the current time). This article is a description of how I used free tools to setup a complete environment for two-factor authentication on various servers. The article below is targetted to debian-based systems, but users of other Linux distributions (or other PAM enabled unices) should not have too great a difficulty to adept the description to their own environment.

A free HOTP soft-token for J2ME based mobile phones and PDA's can be found on the Data Security Systems Solutions site (or locally from here, the manual (1.4MB) is here).

On the server-side we use the OTP daemon from Tri-D systems (website no longer available). On that site you could find the source tarball for the HOTP daemon (otpd, now avalailable locally) which is prepared for the debian build tools (and rpm-based systems for that matter). Starting with version 3.0.0, the local state manager daemon (lsmd) is integrated, but with earlier versions this should also be built.

Building the OTP daemon

Unpack the otpd tarball and change to the otpd-3.0.0 directory. We need to make two minor changes, but the files lack the write-bit, so we can just make everything writable with "chmod -R u+w ." The two fixes that we apply are changing a small typo in the debian/changelog and add the install of the otppasswd manpage. Apply the following patch:

DIFF:
  1. --- otpd-3.0.0/debian/changelog 2008-01-31 03:30:25.000000000 -0500
  2. +++ otpd-3.0.0-1/debian/changelog       2008-02-09 19:45:20.000000000 -0500
  3. @@ -2,7 +2,7 @@
  4.  
  5.    * New release
  6.  
  7. - -- Frank Cusack <frank@tri-dsystems.com>  Thu Jan 31 2008 00:30:04 -0700
  8. + -- Frank Cusack <frank@tri-dsystems.com>  Thu, 31 Jan 2008 00:30:04 -0700
  9.  
  10.  otpd (2.5.2-1) dapper; urgency=low
  11.  
  12. diff -wurN otpd-3.0.0/Makefile.in otpd-3.0.0-1/Makefile.in
  13. --- otpd-3.0.0/Makefile.in      2008-01-31 01:32:33.000000000 -0500
  14. +++ otpd-3.0.0-1/Makefile.in    2008-02-09 19:49:38.000000000 -0500
  15. @@ -73,6 +73,8 @@
  16.  install-otpd: otpd
  17.         $(INSTALL) -d $(DESTDIR)$(sbindir)
  18.         $(INSTALL) otpd $(DESTDIR)$(sbindir)/otpd
  19. +       $(INSTALL) -d $(DESTDIR)$(mandir)/man5
  20. +       $(INSTALL_DATA) otpd.8 $(DESTDIR)$(mandir)/man5/otppasswd.5
  21.         $(INSTALL) -d $(DESTDIR)$(mandir)/man8
  22.         $(INSTALL_DATA) otpd.8 $(DESTDIR)$(mandir)/man8/otpd.8
  23.         $(INSTALL) -d $(DESTDIR)$(sysconfdir)

After this, the package can be built with

CODE:
  1. dpkg-buildpackage -rfakeroot -uc -us

The pre-build package for the i386 architecture is available here.

If all you want is to use the OTP daemon on a single server, you might install Tri-D's PAM module from their download page. This too is prepared with the debian build files and a spec file for rpm-based systems. I opt for a central server with freeradius and otpd running and having a RADIUS PAM module on the separate systems.

Building the freeradius package

Version 3.0.0 of otpd requires at least version 1.1.7 of freeradius. The rlm_otp module is included, but the debian rules file excludes that module. So to build that we should get the source files from testing. Make sure your /etc/apt/sources.list contains a the line:

deb-src http://ftp.debian.nl/debian/ testing main

then update the apt database and get the source files with

CODE:
  1. apt-get source freeradius

Alternately, you can get the .orig.tar.gz, .diff and .dsc from the debian packages repository and unpack it with

CODE:
  1. dpkg-source -x freeradius_1.1.7-1.dsc

Change to the freeradius-1.1.7 directory and apply the following patch:

DIFF:
  1. --- freeradius-1.1.7/debian/rules       2008-01-31 21:05:10.000000000 -0500
  2. +++ freeradius-1.1.7-1/debian/rules     2008-02-09 18:25:02.000000000 -0500
  3. @@ -65,7 +65,6 @@
  4.                 --without-rlm_eap_tls \
  5.                 --without-rlm_eap_ttls \
  6.                 --without-rlm_eap_peap \
  7. -               --without-rlm_otp \
  8.                 --with-rlm_sql_postgresql_lib_dir=`pg_config --libdir` \
  9.                 --with-rlm_sql_postgresql_include_dir=`pg_config --includedir` \
  10.                 --without-openssl \

and build the package. Aside from the package itself, various modules are built as separate packages . I only install the basic package (freeradius_1.1.7-1_i386.deb) since flat files are sufficient for my needs.

Combining the two

There was a minor problem I ran into when trying to have freeradius actually use the otpd for verifying the responses: file permissions. otpd has the option of running as a user other than root but then some files and directories have to be readable and/or writable by that user. If you install the package they are not. freeradius runs as user "freerad" by default, and this is the user that will access the socket file in /var/run/otpd. There are two solutions: have freeradius run as root, or have otpd run as freerad and change the file ownerships. I opted for the latter. For this reason I postponed configuring otpd until after I installed freeradius. I also ran into a small bug in the otpd package: the /etc/otpd.conf has an empty "state" section, which is considered a syntax error so the debian post-install script failed. I just unhashed the "mode = local" line and issued "dpkg --configure --pending" to make it happy.

Configuring otpd

First, we stop both the freeradius and otpd daemons. Some files and directories need to be created and the correct permissions set. Execute the following commands:

CODE:
  1. mkdir /etc/otpstate
  2. touch /etc/otppasswd
  3. chmod 600 /etc/otppasswd
  4. chmod 700 /etc/otpstate
  5. chown freerad:freerad /etc/otpd.conf /etc/otpstate/ /etc/otppasswd /var/run/otpd/

Generate a token on the J2ME client. The seedlength may be any value from 16 to 20 (I use the maximum), but the OTP length should be 6. This is because of a limitation of the "resynctool" that we will see later. After this the seed is displayed. Make sure you see the complete seed (i.e. opening and closing bracket should both be visible). I use a Windows Mobile based PDA and I need to use landscape mode to prevent the seed being truncated. The seed is displayed only once. Be cautious in copying it in the next step. After this, you are opted to place a PIN code on the token you just created. You might want to do that to have real 2-factor authentication.

Now create an entry in /etc/otppasswd with the following syntax:

user:hotp-d6:key

where "user" is the loginname of the user that will be authenticated via RADIUS, and "key" is the seed you just generated on the soft-token. So the line might look like this:

CODE:
  1. foo:hotp-d6:3cd0f53cd4d94b08d249b1f861e655d774fcf0e5

Now we need a state file for this user. Calculate 2 consecutive passwords with the token, and use "resynctool" to generate state information like this:

CODE:
  1. resynctool -1 990878 -2 457035 -u foo -k 3cd0f53cd4d94b08d249b1f861e655d774fcf0e5

This results in output of the form:

5:foo:0000000000000002:::0:0:0:

Copy this into the file "/etc/otpstate/user" where "user" is the same as the second field from the pasted text (in this case: "foo"). Make sure user freerad has read and write access to the file.

Now start the otpd daemon (/etc/init.d/otpd start) and test it with the "otpauth" command:

CODE:
  1. otpauth -u foo -p 123456 -s /var/run/otpd/socket

If you get the output "5 (service error)", your configuration is still incorrect. Review /var/log/auth.log for clues what should be fixed. The error the we should expect here is "3 (authentication error)". Try the same command with a newly generated password, and you should get "0 (ok)".

Configuring freeradius

We should enable the otp module in freeradius. That means removing the hash to include otp.conf and adding "otp" to both the authorize and authenticate modules of /etc/freeradius/radiusd.conf (or apply the following patch):

DIFF:
  1. --- /oldconf/freeradius/radiusd.conf        2008-02-10 00:29:26.000000000 +0100
  2. +++ /etc/freeradius/radiusd.conf        2008-02-11 21:59:59.000000000 +0100
  3. @@ -1730,7 +1730,7 @@
  4.         # $INCLUDE  ${confdir}/postgresqlippool.conf
  5.  
  6.         # OTP token support.  Not included by default.
  7. -       # $INCLUDE  ${confdir}/otp.conf
  8. +       $INCLUDE  ${confdir}/otp.conf
  9.  
  10.  }
  11.  
  12. @@ -1788,6 +1788,8 @@
  13.  #  need to setup hints for the remote radius server
  14.  authorize {
  15.         #
  16. +       otp
  17. +       #
  18.         #  The preprocess module takes care of sanitizing some bizarre
  19.         #  attributes in the request, and turning them into attributes
  20.         #  which are more standard.
  21. @@ -1906,6 +1908,8 @@
  22.  #
  23.  authenticate {
  24.         #
  25. +       otp
  26. +       #
  27.         #  PAP authentication, when a back-end database listed
  28.         #  in the 'authorize' section supplies a password.  The
  29.         #  password can be clear-text, or encrypted.

Now we can start the RADIUS daemon (/etc/init.d/freeradius start) and test if it works with the otp module:

CODE:
  1. radtest foo 123456 localhost 10 testing123

"testing123" is the default secret for localhost in /etc/freeradius/clients.conf. You should change that. We also don't use a NAS server so the NAS port (10) doesn't matter. We used a wrong password so the output looks like this:

Sending Access-Request of id 177 to 127.0.0.1 port 1812
        User-Name = "foo"
        User-Password = "123456"
        NAS-IP-Address = 255.255.255.255
        NAS-Port = 10
Re-sending Access-Request of id 177 to 127.0.0.1 port 1812
        User-Name = "foo"
        User-Password = "123456"
        NAS-IP-Address = 255.255.255.255
        NAS-Port = 10
rad_recv: Access-Reject packet from host 127.0.0.1:1812, id=177, length=20

With a correct password we get the following output:

Sending Access-Request of id 172 to 127.0.0.1 port 1812
        User-Name = "foo"
        User-Password = "752230"
        NAS-IP-Address = 255.255.255.255
        NAS-Port = 10
rad_recv: Access-Accept packet from host 127.0.0.1:1812, id=172, length=20

Now we need to allow clients to connect to the RADIUS server. For that we edit /etc/freeradius/clients.conf and assign shared secrets to the various clients and/or networks that will be accessing the server. The pre-installed file has comments that should make the syntax clear. Reload the freeradius daemon after you finished editing clients.conf.

Configuring the clients

On each client, install libpam-radius-auth:

CODE:
  1. apt-get install libpam-radius-auth

Then edit /etc/pam_radius_auth.conf and add a line for the RADIUS server with the secret assigned to the specified client and a timeout value.

Now you need to edit those PAM files in /etc/pam.d where you want to allow RADIUS authentication. In debian, there is a single file (/etc/pam.d/common-auth) that is sourced by all modules that allow standard Unix authentication. I edited the file to allow both standard Unix authentication and RADIUS authentication. The file now reads:

CODE:
  1. auth    sufficient      pam_unix.so nullok_secure
  2. auth    required        pam_radius_auth.so try_first_pass

so I can choose my method of authentication depending on the circumstances.

Caveat emptor

First, the RADIUS daemon requires UDP port 1812 to be open. Make sure your firewall configuration allows that.
Second, since the username used by the authentication module is specified on the RADIUS server, do note that there is a risk of clashing usernames. Either make sure that identical usernames on various clients belong to the same person or that different clients with the same usernames don't share access to the RADIUS server.
Third, since the HOTP protocol is an event-based system, a brute force attack on a simple numerical 6-digit password is very possible! The reason for this is that wrong password probes or a timeslot don't invalidate the current password. Make sure that the service which you use HOTP for is not freely accessible from untrusted networks and/or use something like fail2ban. Also make sure that you do not generate many superfluous passwords with your token that are not communicated to the otpd daemon on the RADIUS server. The server will calculate 6 consecutive passwords and give up if none qualify. If this happens, you will should use "resynctool" to calculate a new content for the state file.

Enjoy!

February 12, 2008 • Posted in: security

Leave a Reply