Yubikey Validation Server Setup

If the YubiCloud is not to be used for the validation of Yubico one-time passwords (OTP), validation servers can also be managed in-house. In principle this is not very difficult, but the redundant setup is rather poorly documented.
OTP validation process

Overview

MySQL is used as the backend and Apache as the frontend. The two components yubikey-val and yubikey-ksm are simple PHP applications, which consist of just a few files.
Block diagram validation server

Installation

Web server

Apache, PHP and MySQL should be installed first. Due to the packet dependency of yubikey-ksm and yubikey-val, a MariaDB server cannot be used.
Then, two databases should be created (ykksm and ykval) with associated users (ykksm_reader and ykval_verifier).

yubikey_ksm

The individual Yubikeys, that is to say all data (incl. the secret AES key) of all Yubikeys, are recorded in the Yubikey key storage module (KSM). The service can be used to verify whether a Yubikey with an associated one-time password (OTP) is valid, but not whether it is a replay attack.

As the software does not have a mechanism to synchronise the servers, this is resolved via a MySQL master-slave setup. Corresponding instructions on how this can be done is available, for example, on DigitalOcean. It must be noted that only the ykksm database should be synced (binlog_do_db = ykksm).

On a Debian yubikey_ksm can be easily installed via apt install yubikey-ksm. The configuration of the Apache and the database is then already completed. If the configuration is to be changed afterwards, the corresponding files are located in /etc/yubico/ksm/. More detailed installation instructions can be found at Yubico.

Apache is configured so that there is a global alias /wsapi/decrypt as /usr/share/yubikey-ksm/ykksm-decrypt.php. If there are several VirtualHosts present on the Apache, these should be deactivated in the configuration and only the VirtualHost required for yubikey_ksm should be activated.

New Yubikeys can be created using the ykksm-gen-keys tool. This results in the following output:

$ ykksm-gen-keys 1
1,cccccccccccb,42e31d069785,cf00b1f4c2c80e395b5e7532a5929cba,d05f7e394f0e,2016-03-22T13:12:25, 

The yubikeys table is present in the database. This has the following schema:

CREATE TABLE `yubikeys` (
  `serialnr` int(11) NOT NULL,
  `publicname` varchar(16) NOT NULL,
  `created` varchar(24) NOT NULL,
  `internalname` varchar(12) NOT NULL,
  `aeskey` varchar(32) NOT NULL,
  `lockcode` varchar(12) NOT NULL,
  `creator` varchar(8) NOT NULL,
  `active` tinyint(1) DEFAULT '1',
  `hardware` tinyint(1) DEFAULT '1',
  PRIMARY KEY (`publicname`),
  UNIQUE KEY `publicname` (`publicname`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

If new Yubikeys are to be created and stored directly in the database, this can be implemented with the following script:

#!/bin/bash

MYSQL='mysql'

NEXTID=$(echo "SELECT t1.serialnr + 1 FROM ykksm.yubikeys t1 WHERE NOT EXISTS (SELECT serialnr FROM ykksm.yubikeys t2 WHERE t2.serialnr = t1.serialnr + 1) LIMIT 1;" | $MYSQL | tail -n 1)
if [ -z "${NEXTID}" ]; then
    NEXTID='1'
fi

KEY="$(ykksm-gen-keys ${NEXTID} | grep -v ^#)"

IFS=',' read -r -a ARR <<< "$KEY"

SQL="INSERT INTO ykksm.yubikeys VALUES (${ARR[0]}, '${ARR[1]}', '${ARR[5]}', '${ARR[2]}', '${ARR[3]}', '${ARR[4]}', 'bash', 1, 1);"
echo $SQL | $MYSQL

echo "Set Yubikey:"
echo "ykpersonalize -1 -y -a${ARR[3]} -o fixed=${ARR[1]} -o uid=${ARR[2]}"

yubikey_val

The Yubikey Validation Service carries out the actual validation of the one-time passwords (OTP). The following points are verified and processed:

  • Client: The requesting client must identify itself, each client has an ID and associated password. This ensures that not every client can make a request. In this case, a client is understood to be, for example, a ssh daemon or a website, not the user in front of it.
  • Yubikey: The OTP is forwarded to the yubikey_ksm service. If the OTP is valid, the yubikey_val service is used to check if this is a replay attack.
  • Sync: The other validation servers are notified about the current counter of the Yubikey, so that a replay attack cannot be made on a different validation server.

On a Debian system, the installation is carried out with apt install yubikey-val. The corresponding configuration files are located in the directory /etc/yubico/val/.
Apache is then already configured globally again. If there are several VirtualHosts, the configuration should be set up again specifically for one VirtualHost only. The following aliases should be configured:

  • /wsapi/2.0/verify as /usr/share/yubikey-val/ykval-verify.php
  • /wsapi/verify as /usr/share/yubikey-val/ykval-verify.php
  • /wsapi/2.0/sync as /usr/share/yubikey-val/ykval-sync.php
  • /wsapi/2.0/resync as /usr/share/yubikey-val/ykval-resync.php
  • /wsapi/revoke as /usr/share/yubikey-val/ykval-revoke.php

Database

The database contains three tables:

  • clients
    Here, the clients (e.g. a PAM or MediaWiki) are recorded, which are permitted to query the validation server.

    CREATE TABLE `clients` (
    `id` int(11) NOT NULL,
    `active` tinyint(1) DEFAULT '1',
    `created` int(11) NOT NULL,
    `secret` varchar(60) NOT NULL DEFAULT '',
    `email` varchar(255) DEFAULT NULL,
    `notes` varchar(100) DEFAULT '',
    `otp` varchar(100) DEFAULT '',
    PRIMARY KEY (`id`),
    UNIQUE KEY `id` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
  • queue
    The queue contains entries which must still be reconciled with other validation servers. The entries are processed by the ykval-queue system service.

    CREATE TABLE `queue` (
    `queued` int(11) DEFAULT NULL,
    `modified` int(11) DEFAULT NULL,
    `server_nonce` varchar(32) NOT NULL,
    `otp` varchar(100) NOT NULL,
    `server` varchar(100) NOT NULL,
    `info` varchar(256) NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
  • yubikeys
    This table contains Yubikeys with their corresponding counters. This table is synchronised by yubikey_val itself with the other validation servers.

    CREATE TABLE `yubikeys` (
    `active` tinyint(1) DEFAULT '1',
    `created` int(11) NOT NULL,
    `modified` int(11) NOT NULL,
    `yk_publicname` varchar(16) NOT NULL,
    `yk_counter` int(11) NOT NULL,
    `yk_use` int(11) NOT NULL,
    `yk_low` int(11) NOT NULL,
    `yk_high` int(11) NOT NULL,
    `nonce` varchar(40) DEFAULT '',
    `notes` varchar(100) DEFAULT '',
    PRIMARY KEY (`yk_publicname`),
    UNIQUE KEY `yk_publicname` (`yk_publicname`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Synchronisation

The service has its own mechanism for synchronization. Because the respectively highest counter (the OTP) of every Yubikey should be known on all servers, this is not resolved via a MySQL replication. This would lead to deeper counters being synchronised as well.

The configuration must be adjusted first, so that the servers are permitted to synchronise. The two hosts srv-tfvalid-01 (IP 128.66.1.1) and srv-tfvalid-02 (IP 128.66.1.2) are used as an example. The following configuration parameters must be adjusted:

  • __YKVAL_SYNC_POOL__
    The servers with which to synchronise are entered here.

    $baseParams['__YKVAL_SYNC_POOL__'] = array(
    "https://srv-tfvalid-01/wsapi/2.0/sync",
    "https://srv-tfvalid-02/wsapi/2.0/sync"
    );
  • __YKVAL_ALLOWED_SYNC_POOL__
    All IP addresses of all validation servers are entered here, so that they are permitted to synchronise.

    $baseParams['__YKVAL_ALLOWED_SYNC_POOL__'] = array(
    "127.0.0.1",
    "128.66.1.1",
    "128.66.1.2"
    );
  • __YKVAL_RESYNC_IPS__
    This value is set to the value of __YKVAL_ALLOWED_SYNC_POOL__.

    $baseParams['__YKRESYNC_IPS__'] = $baseParams['__YKVAL_ALLOWED_SYNC_POOL__'];
  • __YKVAL_SYNC_DEFAULT_LEVEL__
    This value defines the minimum level as a percentage of how many validation servers must be synchronised successfully before the OTP is declared to be valid. If servers cannot be contacted, this can result in the OTP being declared invalid as there are insufficient responses during synchronisation.
    Values between 0 and 100 are possible. If there are two servers in the entire sync pool and sever 1 is queried, it has the following sync level:

    • Server 2 online: 100
    • Server 2 offline: 0

    If there are three servers in the entire sync pool and sever 1 is queried, it has the following sync level:

    • Both servers online: 100
    • One Server online, the other offline: 50
    • Both servers offline: 0

    So that one server can be offline in a set-up with two servers, the value must therefore be set to 0 as follows:

    $baseParams['__YKVAL_SYNC_DEFAULT_LEVEL__'] = 0;
Services for synchronisation

The ykval-queue system service can be started with the following service file at systemd:

[unit]
Description=Yubikey Validation Server Sync Queue
After=network.target

[Service]
ExecStart=/usr/sbin/ykval-queue
Restart=on-failure

[Install]
WantedBy=multi-user.target

In addition, the cronjob which triggers the synchronisation must be created. The cronjob must be created for each validation server – validation server combination and looks similar to the following:

* * * * * /usr/sbin/ykval-synchronize validation-server-2 all

Create new validation clients

New clients can be created using the ykval-gen-clients tool. Clients can be automatically created and stored in the database with the following bash script:

#!/bin/bash

MYSQL='mysql'
ERRORMSG='Failed to insert new client with query '

CLIENT="$(ykval-gen-clients 1 2>&1)"

echo "USE ykval; ${CLIENT#$ERRORMSG}" | $MYSQL

echo "Client configuration:"
echo ${CLIENT#$ERRORMSG}

Use validation server

The validation servers should be tested prior to use. For example, it should be tested whether a replay attack on the second server is not possible after the validation was already carried out on the first server. Another test is whether the MySQL databases are synchronising with each other and process the entries in the ykval.queue.

SSHd

If an SSH server is to be secured with TwoFactor, the easiest way to do so is using PAM (Pluggable Authentication Modules for Linux). There is an extra PAM module (pam_yubico), which queries the Yubikey validation servers.

In this example, PAM is a client of the validation server. Therefore, an ID and a key must be generated for PAM using ykval-gen-clients. Only the users in the Users POSIX group should have to carry out a TwoFactor validation, for other users, a login without it is sufficient. The ID of the Yubikeys is linked to the user in the LDAP, that is to say the users which have to carry out a TwoFactor validation have an attribute in the LDAP (in the example: yubikey), which corresponds to the publicname of their Yubikey (example: cccccccccccb).

The following configuration is also added to /etc/pam.d/sshd. It is best entered after the line which carries out the password identification, for example after pam_unix.so or pam_ldap.so in the authchain. These can also be included with an @include, for example from the common-auth. For this reason, it is not possible to specify a complete configuration here.

auth [success=2 default=ignore] pam_succeed_if.so user notingroup Users
auth [success=1 default=ignore] pam_yubico.so id=1 

key=bjMN3jRHquwHr5NqNKN+LEFZUjY= 
                                              
urllist=https://srv-tfvalid-01/wsapi/2.0/verify;https://srv-tfvalid-02/wsapi/2.0/verify 
                                              
ldap_uri=ldap://ldap1.example.com:389/;ldap://ldap2.example.com:389/ 
ldapdn=cn=users,dc=example,dc=com 
                                
user_attr=uid 
                                              
yubi_attr=yubikey 
                                              
verbose_otp
auth requisite                  
pam_deny.so

Afterwards, the login on the server should be tested (Attention: Always keep a root login open, because if something stops working, it may not be possible to login anymore).