Introduction

This document describes how to get one instance of the Yubikey Validation Server (YK-VAL) up and running.

The purpose of the Yubikey validation server is to validate Yubikey OTPs. The validation server is written in PHP, and thus needs a web server and a database. We will use Apache and MySQL, but with small modifications it should be possible to use with other implementations too (e.g., lighttpd and PostgreSQL).

The validation server needs to talk to a Yubikey Key Storage Module (YK-KSM) to work. Thus, you either need to arrange for access to a remote YK-KSM and get the URL to it, or install your own YK-KSM.

Currently there are two recommended implementations of a YK-KSM. If you have a YubiHSM hardware dongle and want improve security, we recommend using Python-PyHSM.

Otherwise we recommend the "soft" YK-KSM

The YK-KSM can be on the same machine as the validation server, but for improved security we recommend to use different machines for the validation server and the KSM.

The OTP validation service is delivered through web service API. There’s no web-based HTML form interface involved. The protocol is defined at: http://www.yubico.com/developers/api/

For redundancy it is possible to set up multiple instances of the YubiKey Validation Server. The intent is that clients should be able to continue validate OTPs even if one of the servers are down. This is reflected in the configuration of a YK-VAL by having a "sync pool" concept. The sync pool of a particular YK-VAL instance is a list of URLs that the local server will synchronize the OTP with, depending on client request. Normally if you have 5 servers, each server will have a list of the 4 other servers in its sync pool — however it IS possible to deviate from this if for some reason it is not possible to reach one particular server from one of the servers. For simplicity, we strongly recommend you to list non-local servers in each sync pool though.

Installation

The following steps apply to any GNU/Linux-like system, although it was written for Debian GNU/Linux. If you do not know which OS to use, we recommend a default choice of Ubuntu 14.04 LTS since it is a well-known distribution that comes with 5 years of security support. Install the OS following its manual and enable automatic security upgrades if prompted.

Step 1: YK-VAL Installation

First you should download and install the latest YK-VAL release:

user@val:~$ sudo apt-get install git make
...
user@val:~$ git clone https://github.com/Yubico/yubikey-val.git
...
user@val:~$ cd yubikey-val
user@val:~/yubikey-val$ sudo make install

Depending on your distribution, the group of Apache (or the HTTP server) might be different from www-data, used in Debian and Ubuntu. On Red Hat, Fedora or CentOS the group is apache and in SUSE it is www.

user@val:~/yubikey-val: sudo make install wwwgroup=apache

The rest of this documentation will assume you have YK-VAL available in the default installation targets. You can override the paths, see the Makefile.

Step 2: Install web server and PHP

You also need to install a web server with PHP5, php5-curl and php-pear.

user@val:~$ sudo apt-get install apache2 php5 php5-curl php-pear

Any web server with PHP support should work.

Step 3: Database installation

Any SQL database with PHP support should work. We give examples for MySQL and PostgreSQL here. Note that you need to chose between either PostgreSQL or MySQL here.

Step 3A: MySQL Installation

Install the required packages:

user@val:~$ sudo apt-get install mysql-server php5-mysql

The installation asks you for a MySQL "root" password, and I recommend to specify one. To avoid having to specify a password when using the mysql tool interactively, you can store the password in ~/.my.cnf, see /usr/share/doc/mysql-server-5.0/README.Debian.gz. For example:

user@val:~$ cat > .my.cnf
[client]
user = root
password = YOURPASSWORD
user@val:~$ chmod go-r .my.cnf
user@val:~$

Note the chmod to protect your password from non-root users.

The database needs to be initialized as follows:

user@val:~$ echo 'create database ykval' | mysql
user@val:~$ mysql ykval < /usr/share/doc/yubikey-val/ykval-db.sql
user@val:~$

You also need to create a database user for the verifier interface, normally called ykval_verifier:

user@val:~$ mysql --silent ykval
mysql> CREATE USER 'ykval_verifier'@'localhost'; \
GRANT SELECT,INSERT,UPDATE(modified, yk_counter, yk_low, yk_high, yk_use, nonce) ON ykval.yubikeys TO 'ykval_verifier'@'localhost'; \
GRANT SELECT,INSERT,UPDATE(id, secret, active) ON ykval.clients TO 'ykval_verifier'@'localhost'; \
GRANT SELECT,INSERT,UPDATE,DELETE ON ykval.queue TO 'ykval_verifier'@'localhost'; \
SET PASSWORD FOR 'ykval_verifier'@'localhost' = PASSWORD('yourpassword'); \
FLUSH PRIVILEGES;
mysql> \q
user@val:~$

Step 3B: PostgreSQL Installation

Install the required packages:

user@val:~$ sudo apt-get install postgresql php5-pgsql
...
user@val:~$

The database needs to be initialized as follows:

user@val:~$ sudo su postgres
postgres@val:~$ createdb ykval
postgres@val:~$ psql ykval < /usr/share/doc/yubikey-val/ykval-db.sql
postgres@val:~$

You also need to create a database user for the verifier interface, normally called ykval_verifier:

postgres@val:~$ psql ykval -q
ykval=# CREATE USER ykval_verifier PASSWORD 'yourpassword';
ykval=# GRANT SELECT,INSERT,UPDATE ON yubikeys TO ykval_verifier;
ykval=# GRANT SELECT,INSERT,UPDATE ON clients TO ykval_verifier;
ykval=# GRANT SELECT, INSERT, UPDATE, DELETE ON queue TO ykval_verifier;
ykval=# \q
postgres@val:~$

Don’t forget to switch back to your normal user

postgres@val:~$ exit
user@val:~$

During installation and debugging it may be useful to watch the database log entries:

user@val:~$ sudo tail -F /var/log/postgresql/postgresql-*-main.log &

Step 4: Setup Verify OTP Interface

The interface to verify OTPs is implemented using a PHP script. You can place the script under any URL, but we recommend serving it as http://ykval.example.org/wsapi/verify. The simplest way to setup the symlinks is to invoke make symlink in your YK-VAL source tree. Like this:

user@val:~/yubikey-val$ sudo make symlink
install -d /var/www/wsapi/2.0
ln -sf /usr/share/yubikey-val/ykval-verify.php /var/www/wsapi/2.0/verify.php
ln -sf /usr/share/yubikey-val/ykval-sync.php /var/www/wsapi/2.0/sync.php
user@val:~/yubikey-val$

If you want to do it manually, you can invoke the above commands manually.

Step 5: Include path configuration

Set the include path for the queue daemon by creating a file /etc/default/ykval-queue with the following content:

user@val:~$ sudo sh -c 'cat > /etc/default/ykval-queue'
DAEMON_ARGS="/etc/yubico/val:/usr/share/yubikey-val"
user@val:~$

You also need to set the include path for the PHP scripts running via Apache, using a .htaccess file:

user@val:~$ sudo sh -c 'cat > /var/www/wsapi/2.0/.htaccess'
RewriteEngine on
RewriteRule ^([^/\.\?]+)(\?.*)?$ $1.php$2 [L]
<IfModule mod_php5.c>
  php_value include_path ".:/etc/yubico/val:/usr/share/yubikey-val"
</IfModule>
user@val:~$ sudo ln -s 2.0/.htaccess /var/www/wsapi/.htaccess
user@val:~$

The .htaccess file also sets up rewriting from the non-.PHP suffix URL name to the right script.

The paths are the default, if you installed the YK-VAL in some other place you need to modify the paths.

Step 6: YK-VAL Configuration

You also need to create a ykval-config.php script. An example file is included in YK-VAL package as ykval-config.php

A template is typically installed in /etc/yubico/val/ykval-config.php-template.

user@val:~$ sudo cp /etc/yubico/val/ykval-config.php-template /etc/yubico/val/ykval-config.php
user@val:~$ sudo emacs -nw /etc/yubico/val/ykval-config.php

Be careful about the user permissions and ownership so that unrelated users on the system cannot read the database password.

You will typically need to modify the DSN (__YKVAL_DB_DSN__), database passwords (__YKVAL_DB_PW__), the sync pool lists (__YKVAL_SYNC_POOL__ and __YKVAL_ALLOWED_SYNC_POOL__), and the YK-KSM URLs inside the otp2ksmurls function.

An example DSN for a MySQL setup:

$baseParams['__YKVAL_DB_DSN__'] = "mysql:dbname=ykval;host=127.0.0.1";

An example DSN for a PostgreSQL setup:

$baseParams['__YKVAL_DB_DSN__'] = "pgsql:dbname=ykval;host=127.0.0.1";

We recommend to add the hosts in YKVAL_SYNC_POOL as entries in /etc/hosts to avoid network delays caused by DNS-lookups. For example:

user@val:~$ sudo sh -c 'cat >> /etc/hosts'
1.2.3.4 api1.example.com
2.3.4.5 api2.example.com
user@val:~$

To improve database performance you can use persistent database connection so that each request doesn’t require a new connection to be setup. To enable this modify __YKVAL_DB_OPTIONS__ as follows:

$baseParams['__YKVAL_DB_OPTIONS__'] = array(PDO::ATTR_PERSISTENT => true);

Step 7: Apache configuration

Create an apache web configuration file for the normal HTTP interface like this:

user@val:~$ sudo sh -c 'cat > /etc/apache2/sites-available/ykval.conf'
<VirtualHost *:80>
  ServerName api.example.com
  ServerAdmin support@example.com

  DocumentRoot /var/www/
  <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
  <Directory /var/www/>
    Options FollowSymLinks
    AllowOverride All
    Order allow,deny
    allow from all
  </Directory>

  ErrorLog /var/log/apache2/ykval-error.log
  LogLevel warn

  CustomLog /var/log/apache2/ykval-access.log "%h %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\""
  ServerSignature On

</VirtualHost>
user@val:~$

HTTPS is strictly speaking not required, but we strongly recommend it.

You need to install a TLS stack for Apache, there are two popular options here: mod_gnutls and mod_ssl. We’ll explain how to install both, but you will need to decide which one to use.

You will need to create a key/certificate for your server using normal tools like GnuTLS "certtool". A small howto for !GoDaddy is available from http://permalink.gmane.org/gmane.comp.encryption.gpg.gnutls.devel/4062.

Step 7A: HTTPS via mod_gnutls

First install and enable the mod_gnutls module:

user@val:~$ sudo apt-get install libapache2-mod-gnutls
user@val:~$ sudo a2enmod gnutls
Enabling module gnutls.
Run '/etc/init.d/apache2 restart' to activate new configuration!
user@val:~$

You will need to place the private key in /etc/ssl/private/api.example.com-key.pem and the certificate chain in /etc/ssl/private/api.example.com-chain.pem.

Create Apache web configuration files:

user@val:~$ sudo sh -c 'cat > /etc/apache2/sites-available/ykval-ssl.conf'
Listen 443
<VirtualHost *:443>
  ServerName api.example.com
  ServerAdmin support@example.com

  GnuTLSEnable on
  GnuTLSCertificateFile /etc/ssl/private/api.example.com-chain.pem
  GnuTLSKeyFile /etc/ssl/private/api.example.com-key.pem
  GnuTLSPriorities NORMAL

  DocumentRoot /var/www/
  <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
  <Directory /var/www/>
    Options FollowSymLinks
    AllowOverride All
    Order allow,deny
    allow from all
  </Directory>

  ErrorLog /var/log/apache2/ykval-ssl-error.log
  LogLevel warn

  CustomLog /var/log/apache2/ykval-ssl-access.log "%h %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\""
  ServerSignature On

</VirtualHost>
user@val:~$

Step 7B: HTTPS via mod_ssl

The mod_ssl module is typically installed by default, but you need to enable it.

user@val:~$ sudo a2enmod ssl
Enabling module ssl.
Run '/etc/init.d/apache2 restart' to activate new configuration!
user@val:~$

You will need to place the private key in /etc/ssl/private/api.example.com-key.pem and the certificate chain in /etc/ssl/private/api.example.com-chain.pem.

user@val:~$ sudo sh -c 'cat > /etc/apache2/sites-available/ykval-ssl.conf'
<VirtualHost *:443>
  ServerName api.example.com
  ServerAdmin support@example.com

  SSLEngine on
  SSLCertificateFile /etc/ssl/private/api.example.com-chain.pem
  SSLCertificateChainFile /etc/ssl/private/api.example.com-chain.pem
  SSLCertificateKeyFile /etc/ssl/private/api.example.com-key.pem

  DocumentRoot /var/www/
  <Directory />
    Options FollowSymLinks
    AllowOverride None
  </Directory>
  <Directory /var/www/>
    Options FollowSymLinks
    AllowOverride All
    Order allow,deny
    allow from all
  </Directory>

  ErrorLog /var/log/apache2/ykval-ssl-error.log
  LogLevel warn

  CustomLog /var/log/apache2/ykval-ssl-access.log "%h %l %u %t \"%r\" %>s %b %D \"%{Referer}i\" \"%{User-Agent}i\""
  ServerSignature On

</VirtualHost>
user@val:~$

Common Apache Configuration

This step is the same for both mod_gnutls and mod_ssl.

user@val:~$ sudo a2enmod rewrite
Enabling module rewrite.
Run '/etc/init.d/apache2 restart' to activate new configuration!
user@val:~$ sudo a2dissite default
Site default disabled.
Run '/etc/init.d/apache2 reload' to activate new configuration!
user@val:~$ sudo a2ensite ykval ykval-ssl
Enabling site ykval.
Enabling site ykval-ssl.
Run '/etc/init.d/apache2 reload' to activate new configuration!
user@val:~$ sudo /etc/init.d/apache2 restart
user@val:~$

Step 8: Logging

The PHP interface uses syslog for logging of incoming requests. The facility is LOG_LOCAL0. To place these messages in a separate file, you can add the following to /etc/syslog.conf, or if you use rsyslog, create a file /etc/rsyslog.d/ykval.conf with this content:

user@val:~$ sudo sh -c 'cat > /etc/rsyslog.d/ykval.conf'
local0.* -/var/log/ykval.log
user@val:~$ sudo /etc/init.d/rsyslog restart
...
user@val:~$

The - before the filename avoids syncing the file after each write, which is recommended for performance.

The log file can grow large quickly, so it is a good idea to setup rotation of log files. Here is an example that rotates the log file weekly. Create a file /etc/logrotate.d/ykval like this:

user@val:~$ sudo sh -c 'cat > /etc/logrotate.d/ykval'
/var/log/ykval.log {
  weekly
        dateext
  compress
  missingok
  rotate 9999
  notifempty
  postrotate
    invoke-rc.d rsyslog reload > /dev/null
        endscript
}
user@val:~$

You may want to modify the default /etc/logrotate.d/apache2, useful things to add are dateext and compress and change rotate to something large if you want to retain logs.

Step 8.1: Fix default log (optional)

Unfortunately, most default syslog configuration, including the syslog.conf configuration file on Debian, will also log all entries to /var/log/syslog and/or /var/log/messages.

I am not aware of any way to avoid this without modifying these other rules. To avoid YK-VAL log entries in these other files, you must modify the default rules. For example, edit the following lines of /etc/rsyslog.conf (or /etc/syslog.conf if you don’t use rsyslog):

*.=debug;\
       auth,authpriv.none;\
       news.none;mail.none     -/var/log/debug
*.*;auth,authpriv.none          -/var/log/syslog
*.=info;*.=notice;*.=warn;\
       auth,authpriv.none;\
       cron,daemon.none;\
       mail,news.none          -/var/log/messages

Change them into:

*.=debug;\
       auth,authpriv.none;\
       news.none;mail.none;local0.none     -/var/log/debug
*.*;auth,authpriv.none,local0.none              -/var/log/syslog
*.=info;*.=notice;*.=warn;\
       auth,authpriv.none;\
       cron,daemon.none;\
       local0.none;\
       mail,news.none          -/var/log/messages

Idempotent commands to speed this up:

user@host:~$ sudo perl -pi -e 's/;auth,authpriv.none/;auth,local0.none,authpriv.none/' /etc/rsyslog.conf
user@host:~$ sudo perl -pi -e 's/news.none;mail.none/news.none;local0.none;mail.none/' /etc/rsyslog.conf
user@host:~$ sudo perl -pi -e 's/cron,daemon.none/cron,daemon.none;local0.none/' /etc/rsyslog.conf
user@host:~$ sudo /etc/init.d/rsyslog restart

Step 9: Start Sync Daemon

When using yubikey-val in a sync pool, you need to have the ykval-queue daemon running to ensure that data is synchronized between the servers in the pool. The easiest way of running this is to simply invoke ykval-queue in a shell:

user@val:~$ sudo ykval-queue

However, the recommended approach is to automate running this process in the background, by use of an init script or similar. Instructions on doing so vary depending on your operating system.

Step 10: Sync data from an existing server (optional)

If you’re adding a new server to an existing pool, you can synchronize all YubiKey counter data from one of the existing servers. To do so, the server you want to sync from needs to be configured to allow it. Do this by editing /etc/yubico/val/ykval-config.php on the existing server, adding the new servers IP address to the __YKRESYNC_IPS__ setting. You’ll most likely want to add the IP to the __YKVAL_ALLOWED_SYNC_POOL__ setting as well. You also need to edit this file on the new server, adding the existing server(s) IP address(es) to __YKVAL_ALLOWED_SYNC_POOL__.

Once these permissions have been configured, you can initiate the full sync by running the following command from the new server:

user@val:~$ ykval-synchronize http://<IP or hostname of existing server>/wsapi/2.0/resync all

Step 11: Test it

You can test the service by requesting a URL. Using wget, for example:

user@val:~$ wget -q -O - 'http://localhost/wsapi/2.0/verify?id=1&nonce=asdmalksdmlkasmdlkasmdlakmsdaasklmdlak&otp=dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh'
h=/QVWkl5VlcX+Or1A2b3vOeoLEwI=
t=2010-05-17T14:48:15Z0355
otp=dteffujehknhfjbrjnlnldnhcujvddbikngjrtgh
nonce=asdmalksdmlkasmdlkasmdlakmsdaasklmdlak
status=NO_SUCH_CLIENT

user@val:~$

Naturally, you will need to import client keys into the database for the verify function to work properly.

The End

You now have a YK-VAL up and running. See https://developers.yubico.com/yubikey-ksm/Server_Hardening.html on how to improve security of your system.