The Yubikey is a nice little device. It’s quite simple in design and operation.
The key actually emulating a USB keyboard, which makes it instantly usable on any modern OS. You just press the button on the key to generate a one-time-password (OTP) to validate you. The method works by typing in your password, but before hitting the return key, you press the Yubikey button to finish it off. At the end of the OTP generation, it sends a carriage return itself.
The OTP is then sent to a validation server, either hosted by Yubico themselves, or you can host your own.
I’m going to walk through how you can set the infrastructre for doing two-factor authentication on Debian. In my specific case, the requirement was two-factor with an Active Directory username/password combination and the Yubikey as the second factor.
Unfortunately, the documentation from Yubico is quite average. To top it off, they insist on using multiple Google Code project sites for hosting their software.
This would normally be fine, but in this case, they have a Google Code project for every single little piece of code. Much of the documentation I found relates to older projects which are not supported by Yubico. This makes working out exactly what you need difficult. Within the Google Code project sites, documentation often runs in circles between projects.
In this document, I’ll look at using PAM to auth again the Yubico auth servers first. Once that’s working, I’ll move onto flashing the Yubikey with a new key and using our own Validation System.
NOTE: This is just some rough notes I put together. You should definitely read the Yubico documentation for this to really make sense.
Authenticating with the Yubikey with PAM
Get some dependencies
apt-get install libpam-dev libcurl4-openssl-dev libpam-radius-auth
Make ourselves a source directory
mkdir ~/yubikey; cd ~/yubikey
Get the current tarball of libyubikey, and install it
wget http://yubico-c.googlecode.com/files/libyubikey-1.5.tar.gz tar xf libyubikey-1.5.tar.gz cd libyubikey-1.5 ./configure make check install
Get the current tarball of the Yubico C client, and install it
wget http://yubico-c-client.googlecode.com/files/ykclient-2.3.tar.gz tar -xf ykclient-2.3.tar.gz cd ykclient-2.3 ./configure make make install
Get the current tarball of the Yubico PAM module, and install it
wget http://yubico-pam.googlecode.com/files/pam_yubico-2.3.tar.gz tar -xf pam_yubico-2.3.tar.gz cd pam_yubico-2.3 ./configure make make install
You should end up with your Yubico PAM module ‘/usr/local/lib/security/pam_yubico.so’
We’ll refer to this in our PAM config /etc/pam.d/openvpn
# # /etc/pam.d/openvpn - OpenVPN pam configuiration # # We fall back to the system default in /etc/pam.d/common-* # auth required /usr/local/lib/security/pam_yubico.so id=1 debug authfile=/etc/yubikeyid auth required pam_radius_auth.so no_warn try_first_pass @include common-account @include common-password @include common-session
This configuration will tell PAM to hit the Yubico module first. This splits apart your password field into your password and OTP. The OTP is validated against the Validation Servers, and the password is then passed onto the next module. This configuration will use the Yubico auth servers to check your token.
Once you have a working config, we’ll move to setting up our own Validation Servers. We’ll need to specify the URL for that in this config later on.
In that case, we’re also using RADIUS. This could be LDAP if you had an LDAP server available. You should be able to use the standard UNIX credentials (/etc/password, /etc/shadow) also.
The other important piece to note here is the authfile, /etc/yubikeyid
This file lists the mapping between username and the fixed part of your Yubikey. This is the first 12 chars of the Yubikey OTP (e.g. when you press the button)
abotting:vvcnrdkvevtj
FreeRADIUS authenticating against Active Directory 2008.
I banged my head against a wall for a while on this one. The trick is that you need at least FreeRADIUS 2.1.6 for AD authentication to work properly.
Add Debian backports to your /etc/apt/sources.list
deb http://www.backports.org/debian lenny-backports main contrib non-free
Import the backports key
wget -O - http://backports.org/debian/archive.key | apt-key add -
Update and install the new freeradius
apt-get update apt-get -t lenny-backports install freeradius freeradius-ldap
In your radiusd.conf
ldap { # Define the LDAP server and the base domain name server = "ad.yourcompany.com" basedn = "dc=ad, dc=yourcompany, dc=com" # Active Directory doesn't allow for Anonymous Binding identity = "[email protected]" password = password password_attribute = "userPassword" filter = "(&(sAMAccountname=%{Stripped-User-Name:-%{User-Name}})(memberOf=CN=Users,DC=ad,DC=yourcompany,DC=com))" # This fixes Active Directory 2008 access chase_referrals = yes rebind = yes # The following are RADIUS defaults start_tls = no dictionary_mapping = ${raddbdir}/ldap.attrmap ldap_connections_number = 5 timeout = 4 timelimit = 3 net_timeout = 1 }
In our FreeRADIUS client file /etc/freeradius/clients.conf:
client localhost { ipaddr = 127.0.0.1 secret = testing123 nastype = other }
Use radtest to test our RADIUS is authenticating properly
radtest <username> <password> localhost 1 testing123
Should return Accept.
Set the address and shared secret of the radius server in /etc/pam_radius_auth.conf. The password of testing123 was defined in our RADIUS client config.
# server[:port] shared_secret timeout (s) 127.0.0.1 testing123 1
OpenVPN has an issue with PAM loading the Yubikey module, so we have to LD_PRELOAD the pam module before starting OpenVPN.
export LD_PRELOAD=/lib/libpam.so.0.81.12; openvpn --config openvpn.conf
For a permanent fix, at the end of the start_vpn function in /etc/init.d/openvpn, just before the $DAEMON line:
export LD_PRELOAD=/lib/libpam.so.0.81.12 $DAEMON $OPTARGS --writepid /var/run/openvpn.$NAME.pid \ $DAEMONARG $STATUSARG --cd $CONFIG_DIR \ --config $CONFIG_DIR/$NAME.conf || STATUS=1
Change the path of /lib/libpam.so.0.81.12 to suit your own system.
I won’t go into the OpenVPN configuration, except that for PAM authentication you need these options in your server config:
plugin /usr/lib/openvpn/openvpn-auth-pam.so openvpn username-as-common-name ns-cert-type server client-cert-not-required
Personalising your Yubikey
To host your own Yubikey validation system, you require the secret AES key of your Yubikey. In the past, Yubico could provide this to you. Now, you’re required to flash your Yubikey yourself which will generate a new AES key.
Yubico provide a personalisation tool for Linux, Mac and Windows. If you’re on Windows, you get a nice little GUI. For Linux and Mac, you have a CLI based tool. It’s worth having a look at the ‘Personalization Tool’ page at: http://www.yubico.com/developers/personalization/
Installing the Personalisation Tool
Install some dependencies:
apt-get install libusb-1.0.0-dev
Grab the latest Pesonalisation Tool tarball from: http://code.google.com/p/yubikey-personalization/
cd ~/yubikey wget http://yubico-c.googlecode.com/files/libyubikey-1.5.tar.gz
Extract, build and install libyubikey
tar xf libyubikey-1.5.tar.gz cd libyubikey-1.5 ./configure make make install
You’ll need to provide a UID value for flashing your Yubikey. It needs to be 6 characters, and in hexadecimal. You can use this command to generate one for you.
dd if=/dev/urandom of=/dev/stdout count=100 2>/dev/null | xargs -0 modhex | cut -c 1-10 | awk '{print "vv" $1}' 74657374696e
You must provide the public name (fixed) parameter in modhex format. The modhex format is a special encoding used to ensure characters sent by the key are always correctly interpreted whatever keyboard layout you use.
You also need to generate yourself a public name for your key. This is known as the ‘fixed’ part, and it’ll be the first 16 chars when you generate your OTP. This will identify your key from anybody else’s.
dd if=/dev/urandom of=/dev/stdout count=100 2>/dev/null | xargs -0 modhex | cut -c 1-10 | awk '{print "vv" $1}' vvcnrdkvevtj
This comamnd generate some random text, does a modhex operation, grabs the first 10 chars, then adds ‘vv’ to the front to make it up to 12.
You’ll be prompted for a passphrase on your AES key. I leave mine blank, but if you do set one, don’t ever lose it. I believe it’ll stop you from re-personalising your Yubikey.
ykpersonalize -ouid=74657374696e -ofixed=vvcnrdkvevtj Firmware version 2.1.2 Touch level 1793 Program sequence 1 Passphrase to create AES key: Configuration data to be written to key configuration 1: fixed: m:vvcnrdkvevtj uid: h:74657374696e key: h:fcaad309a20ne1809c2db2f7f0e8d6ea acc_code: h:000000000000 ticket_flags: APPEND_CR config_flags: Commit? (y/n) [n]: y
Save this information, as we’ll need it later.
Setting up yor own YubiKey OTP Validation Server
You need to install two things: The Key Storage Module and the Yubico Validation Server. The Key Storage Module (KSM) holds the secret AES key of your Yubikey token, while the Validation Server does the OTP check against the KSM.
In their 2.0 architecture, you can have multiple KSM’s and Validation servers with work together for reduncancy.
KSM Installation
Make a working directory, and get the KSM package
mkdir ~/yubikey && cd ~/yubikey wget http://yubikey-ksm.googlecode.com/files/yubikey-ksm-1.3.tgz tar xfz yubikey-ksm-1.3.tgz
Install the KSM files
cd yubikey-ksm-1.3 make install
Install Apache2 and PHP
Install Apache2, PHP and MySQL
apt-get install apache2 php5 php5-mcrypt php5-curl mysql-server php5-mysql libdbd-mysql-perl
Create the ykksm table
echo "CREATE DATABASE ykksm;" | mysql -u root -p
Import the DB schema
mysql -u root -p ykksm < /usr/share/doc/ykksm/ykksm-db.sql
Set up some MySQL permissions
CREATE USER 'ykksmreader'; GRANT SELECT ON ykksm.yubikeys TO 'ykksmreader'@'localhost'; SET PASSWORD FOR 'ykksmreader'@'localhost' = PASSWORD('hYea3Inb'); CREATE USER 'ykksmimporter'; GRANT INSERT ON ykksm.yubikeys TO 'ykksmimporter'@'localhost'; SET PASSWORD FOR 'ykksmimporter'@'localhost' = PASSWORD('ikSab29'); FLUSH PRIVILEGES;
Include path configuration
Set the include path by creating a file /etc/php5/conf.d/ykksm.ini
cat > /etc/php5/conf.d/ykksm.ini << EOF include_path = "/etc/ykksm:/usr/share/ykksm" EOF
Make a web server symlink
make -f /usr/share/doc/ykksm/ykksm.mk symlink
Set your configuration settings in /etc/ykksm/ykksm-config.php
<?php $db_dsn = "mysql:dbname=ykksm;host=127.0.0.1"; $db_username = "ykksmreader"; $db_password = "hYe63Inb"; $db_options = array(); $logfacility = LOG_LOCAL0; ?>
Restart Apache2
/etc/init.d/apache2 restart
Test the KSM Server
Try this URL:
curl 'http://localhost/wsapi/decrypt?otp=dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh' ERR Unknown yubikey
It should return ‘Unknown Key’ until we have imported our Yubikey into the database.
Install the Yubico Validation Server
The latest version, and documentation can be found at: http://code.google.com/p/yubikey-val-server-php/
Installation
Go to our working source directory, and grab the package
cd ~/yubikey wget http://yubikey-val-server-php.googlecode.com/files/yubikey-val-2.4.tgz
Extract, build and install the server
tar -zxf yubikey-val-2.4.tgz cd yubikey-val-2.4 make install
Create the ykval database and import the schema
echo 'create database ykval' | mysql -u root -p mysql -u root -p ykval < /usr/share/doc/ykval/ykval-db.sql
Install the symlink
make symlink
Include path configuration
cat > /etc/default/ykval-queue << EOF DAEMON_ARGS="/etc/ykval:/usr/share/ykval EOF
Create a htaccess file: /var/www/wsapi/2.0/.htaccess
RewriteEngine on RewriteRule ^([^/\.\?]+)(\?.*)?$ $1.php$2 [L]
php_value include_path ".:/etc/ykval:/usr/share/ykval"
Symlink the htaccess file
cd /var/www/wsapi; ln -s 2.0/.htaccess /var/www/wsapi/.htaccess
Copy the template config file for the Validation Server
cp /etc/ykval/ykval-config.php-template /etc/ykval/ykval-config.php
Edit the file and configure settings in /etc/ykval/ykval-config.php
<?php # For the validation interface. $baseParams = array (); $baseParams['__YKVAL_DB_DSN__'] = "mysql:dbname=ykval;host=127.0.0.1"; $baseParams['__YKVAL_DB_USER__'] = 'ykvalverifier'; $baseParams['__YKVAL_DB_PW__'] = 'password'; $baseParams['__YKVAL_DB_OPTIONS__'] = array(); # For the validation server sync $baseParams['__YKVAL_SYNC_POOL__'] = array("http://localhost/wsapi/2.0/sync"); # An array of IP addresses allowed to issue sync requests # NOTE: You must use IP addresses here. $baseParams['__YKVAL_ALLOWED_SYNC_POOL__'] = array("127.0.0.1"); # Specify how often the sync daemon awakens $baseParams['__YKVAL_SYNC_INTERVAL__'] = 10; # Specify how long the sync daemon will wait for response $baseParams['__YKVAL_SYNC_RESYNC_TIMEOUT__'] = 30; # Specify how old entries in the database should be considered aborted attempts $baseParams['__YKVAL_SYNC_OLD_LIMIT__'] = 10; # These are settings for the validation server. $baseParams['__YKVAL_SYNC_FAST_LEVEL__'] = 1; $baseParams['__YKVAL_SYNC_SECURE_LEVEL__'] = 40; $baseParams['__YKVAL_SYNC_DEFAULT_LEVEL__'] = 60; $baseParams['__YKVAL_SYNC_DEFAULT_TIMEOUT__'] = 1; // otp2ksmurls: Return array of YK-KSM URLs for decrypting OTP for // CLIENT. The URLs must be fully qualified, i.e., contain the OTP // itself. function otp2ksmurls ($otp, $client) { return array("http://localhost/wsapi/decrypt?otp=$otp",); } ?>
In the above configuration, we’re only expecting to use one Validation Server and one KSM. If you’re planning on having multiple Validation servers and KSM’s, then you’ll be including the other Validation Servers in the SYNC_POOL, and your KSM’s in the URLs at the bottom, returned by the otp2ksmurls function.
Enable the mod_rewrite
a2enmod rewrite
Create the ykval database user
CREATE USER 'ykvalverifier'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON `ykval`. * TO 'ykvalverifier'@'localhost';
Fix some privileges on our config file
chgrp www-data /etc/ykval/ykval-config.php
The Sync Daemon uses the PEAR module System_Daemon so you need to install it:
apt-get install php-pear pear install System_Daemon-0.9.2
Install the init.d script
ykval-queue install update-rc.d -f ykval-queue defaults
Start the daemon
/etc/init.d/ykval-queue start
Testing
Use CURL to test our server is working
curl 'http://localhost/wsapi/verify?id=1&otp=vvcnrdkvevtefjbrjnlnldnhcujvddbikngjrtgh'
It should return something like this:
h=aPCQ4kWJilDgriyEii3j8J8lfuY= t=2009-04-27T19:08:51Z0100 status=NO_SUCH_CLIENT
Once we import our Yubikey into the database, we should get a nice ‘status=OK’ message.
Importing your keys into the KSM server
Refer back to the output from personalising your Yubikey. You’ll need the fixed part (referred to as publicname in the DB), internal name (UID) and our AES key.
This is an entry for our newly personalised Yubikey.
USE ykksm; INSERT INTO `yubikeys` (`serialnr`, `publicname`, `created`, `internalname`, `aeskey`, `lockcode`, `creator`, `active`, `hardware`) VALUES (101209, 'vvcnrdkvevtj', '2010-05-07 15:18:40', '74657374696e', 'fcaad309a20ne1809c2db2f7f0e8d6ea', '000000000000', '', 1, 1);
This entry is required for our systems to authenticate against the Validation server. I’m not exactly sure about this, as the documentation is somewhat bare. I think you need an administrator-type person’s key details in here. The imporant part is the ID. This values corresponds the the ‘id=’ value in our CURL requests and in our PAM config.
USE ykval; INSERT INTO `clients` (`id`, `active`, `created`, `secret`, `email`, `notes`, `otp`) VALUES (1, 1, 1, 'fcaad309a20ne1809c2db2f7f0e8d6ea', '[email protected]', 'Any text your want', 'vvcnrdkvevterfbtelvnvkkueenecrlfnlhdjetrhgnk');
We’ll hit our new Validation Server to make sure it’s working
curl "http://localhost/wsapi/2.0/verify?id=1&nonce=askjdnvajsndjkasndvjsnad&otp=vvcnrdkvevtjkreuvvlhtubjecbrticjneckgrigkck" h=KLEb3gOJ4KqQaCVbh8cEvXjH50U=
It should return something like this:
t=2010-05-20T11:24:53Z0051 otp=vvvcnrdkvevtjkreuvvlhtubjecbrticjneckgrigkck nonce=askjdnvajsndjkasndvjsnad sl=100 status=OK
In this URL, we’ve added the ‘nonce’ parameter. This just a test to make sure the v2.0 API is working. ‘status=OK’ means it’s all good! If you get ‘NOT_ENOUGH_ANSWERS’, it means it has trouble trying to sync with other Validation Servers.
We’ll get PAM using our new Validation Servers for auth
/etc/pam.d/openvpn
auth required /usr/local/lib/security/pam_yubico.so id=1 authfile=/etc/yubikeyid url=http://10.68.130.198/wsapi/verify?id=%d&otp=%s debug
If you watch /var/log/auth.log, you should see the PAM module spitting out some debugging information which may be useful. It also spits out your plain text password too, while you have the debug option on. Make sure you remove this later.
Problems
If you see an error like this:
PAM unable to dlopen(/lib/security/pam_yubico.so): /lib/security/pam_yubico.so: undefined symbol: pam_set_data
you’ll need the LD_PRELOAD trick from above. Something to do with dlopening the PAM module I believe.