Yubico

Setup of Self-hosted Yubico OTP Validation Server

See: https://developers.yubico.com/OTP/Guides/Self-hosted_OTP_validation.html

This article will show the installation of Yubico KVM and Validation Server on seperate Proxmox Debian VM’s. First set up 2 Proxmox VM’s. Next install several packages.

Pre-requisites for Both Servers

Run:

#apt-get install software-properties-common
root@k1:~# apt-get install software-properties-common
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
gir1.2-glib-2.0 gir1.2-packagekitglib-1.0 libappstream4
libgirepository-1.0-1 libglib2.0-0 libglib2.0-bin libglib2.0-data
libgstreamer1.0-0 libpackagekit-glib2-18 libpolkit-agent-1-0
libpolkit-backend-1-0 libpolkit-gobject-1-0 libstemmer0d libyaml-0-2
packagekit packagekit-tools policykit-1 python3-dbus python3-distro-info
python3-gi python3-software-properties shared-mime-info unattended-upgrades
xdg-user-dirs
Suggested packages:
gstreamer1.0-tools appstream python-dbus-doc python3-dbus-dbg bsd-mailx
default-mta | mail-transport-agent needrestart
The following NEW packages will be installed:
gir1.2-glib-2.0 gir1.2-packagekitglib-1.0 libappstream4
libgirepository-1.0-1 libglib2.0-0 libglib2.0-bin libglib2.0-data
libgstreamer1.0-0 libpackagekit-glib2-18 libpolkit-agent-1-0
libpolkit-backend-1-0 libpolkit-gobject-1-0 libstemmer0d libyaml-0-2
packagekit packagekit-tools policykit-1 python3-dbus python3-distro-info
python3-gi python3-software-properties shared-mime-info
software-properties-common unattended-upgrades xdg-user-dirs
0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.
Need to get 7,351 kB of archives.
After this operation, 31.7 MB of additional disk space will be used.
Do you want to continue? [Y/n]

Next,

# add-apt-repository ppa:yubico/stable
 PPA for stable Yubico software.
 More info: https://launchpad.net/~yubico/+archive/ubuntu/stable
Press [ENTER] to continue or ctrl-c to cancel adding it
 
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.7/threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 688, in addkey_func
    func(**kwargs)
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 386, in add_key
    return apsk.add_ppa_signing_key()
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 252, in add_ppa_signing_key
    tmp_keyring, tmp_secret_keyring, signing_key_fingerprint, tmp_keyring_dir):
  File "/usr/lib/python3/dist-packages/softwareproperties/ppa.py", line 181, in _recv_key
    "--recv", signing_key_fingerprint,
  File "/usr/lib/python3.7/subprocess.py", line 323, in call
    with Popen(*popenargs, **kwargs) as p:
  File "/usr/lib/python3.7/subprocess.py", line 775, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.7/subprocess.py", line 1522, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'gpg': 'gpg'

Check the repo

root@k1:/etc/apt/sources.list.d# ls
yubico-ubuntu-stable-hirsute.list
# apt-get update
Hit:1 http://security.debian.org/debian-security buster/updates InRelease
Hit:2 http://deb.debian.org/debian buster InRelease
Hit:3 http://deb.debian.org/debian buster-updates InRelease
Ign:4 http://ppa.launchpad.net/yubico/stable/ubuntu hirsute InRelease
Err:5 http://ppa.launchpad.net/yubico/stable/ubuntu hirsute Release
  404  Not Found [IP: 2001:67c:1560:8008::15 80]
Reading package lists... Done
E: The repository 'http://ppa.launchpad.net/yubico/stable/ubuntu hirsute Release' does not have a Release file.
N: Updating from such a repository can't be done securely, and is therefore disabled by default.
N: See apt-secure(8) manpage for repository creation and user configuration details.

Add ykpersonalize

# apt-get install yubikey-personalization
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  libykpers-1-1 libyubikey-udev libyubikey0
The following NEW packages will be installed:
  libykpers-1-1 libyubikey-udev libyubikey0 yubikey-personalization
0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
Need to get 196 kB of archives.
After this operation, 392 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://deb.debian.org/debian buster/main amd64 libyubikey0 amd64 1.13-4 [12.0 kB]
Get:2 http://deb.debian.org/debian buster/main amd64 libykpers-1-1 amd64 1.19.3-3+deb10u1 [65.2 kB]
Get:3 http://deb.debian.org/debian buster/main amd64 libyubikey-udev all 1.19.3-3+deb10u1 [40.9 kB]
Get:4 http://deb.debian.org/debian buster/main amd64 yubikey-personalization amd64 1.19.3-3+deb10u1 [77.5 kB]
Fetched 196 kB in 2s (88.2 kB/s)
Selecting previously unselected package libyubikey0.
(Reading database ... 32663 files and directories currently installed.)
Preparing to unpack .../libyubikey0_1.13-4_amd64.deb ...
Unpacking libyubikey0 (1.13-4) ...
Selecting previously unselected package libykpers-1-1:amd64.
Preparing to unpack .../libykpers-1-1_1.19.3-3+deb10u1_amd64.deb ...
Unpacking libykpers-1-1:amd64 (1.19.3-3+deb10u1) ...
Selecting previously unselected package libyubikey-udev.
Preparing to unpack .../libyubikey-udev_1.19.3-3+deb10u1_all.deb ...
Unpacking libyubikey-udev (1.19.3-3+deb10u1) ...
Selecting previously unselected package yubikey-personalization.
Preparing to unpack .../yubikey-personalization_1.19.3-3+deb10u1_amd64.deb ...
Unpacking yubikey-personalization (1.19.3-3+deb10u1) ...
Setting up libyubikey-udev (1.19.3-3+deb10u1) ...
Setting up libyubikey0 (1.13-4) ...
Setting up libykpers-1-1:amd64 (1.19.3-3+deb10u1) ...
Setting up yubikey-personalization (1.19.3-3+deb10u1) ...
Processing triggers for man-db (2.8.5-2) ...
Processing triggers for libc-bin (2.28-10) ...

Installing the KSM

For the Key Storage Module we will use yhsm-yubikey-ksm – part of the python-pyhsm project. This server can be used with or without a YubiHSM device. Install the required packages:

# apt-get install yhsm-yubikey-ksm
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  python-crypto python-daemon python-lockfile python-pkg-resources python-pyhsm python-serial python-setuptools python-sqlalchemy python-sqlalchemy-ext yhsm-tools
Suggested packages:
  python-crypto-doc python-lockfile-doc yhsm-daemon python-wxgtk3.0 | python-wxgtk python-setuptools-doc python-sqlalchemy-doc python-psycopg2 python-mysqldb python-fdb python-pymssql
The following NEW packages will be installed:
  python-crypto python-daemon python-lockfile python-pkg-resources python-pyhsm python-serial python-setuptools python-sqlalchemy python-sqlalchemy-ext yhsm-tools yhsm-yubikey-ksm
0 upgraded, 11 newly installed, 0 to remove and 0 not upgraded.
Need to get 1,805 kB of archives.
After this operation, 8,917 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://deb.debian.org/debian buster/main amd64 python-crypto amd64 2.6.1-9+b1 [259 kB]
Get:2 http://deb.debian.org/debian buster/main amd64 python-pkg-resources all 40.8.0-1 [182 kB]
Get:3 http://deb.debian.org/debian buster/main amd64 python-lockfile all 1:0.12.2-2 [17.0 kB]
Get:4 http://deb.debian.org/debian buster/main amd64 python-daemon all 2.2.3-1 [24.8 kB]
Get:5 http://deb.debian.org/debian buster/main amd64 python-serial all 3.4-4 [83.0 kB]
Get:6 http://deb.debian.org/debian buster/main amd64 python-pyhsm all 1.2.1-1 [45.3 kB]
Get:7 http://deb.debian.org/debian buster/main amd64 python-setuptools all 40.8.0-1 [382 kB]
Get:8 http://deb.debian.org/debian buster/main amd64 python-sqlalchemy all 1.2.18+ds1-2 [729 kB]
Get:9 http://deb.debian.org/debian buster/main amd64 python-sqlalchemy-ext amd64 1.2.18+ds1-2 [19.2 kB]
Get:10 http://deb.debian.org/debian buster/main amd64 yhsm-tools all 1.2.1-1 [29.6 kB]
Get:11 http://deb.debian.org/debian buster/main amd64 yhsm-yubikey-ksm all 1.2.1-1 [34.2 kB]
Fetched 1,805 kB in 2s (776 kB/s)
Selecting previously unselected package python-crypto.
(Reading database ... 32695 files and directories currently installed.)
Preparing to unpack .../00-python-crypto_2.6.1-9+b1_amd64.deb ...
Unpacking python-crypto (2.6.1-9+b1) ...
Selecting previously unselected package python-pkg-resources.
Preparing to unpack .../01-python-pkg-resources_40.8.0-1_all.deb ...
Unpacking python-pkg-resources (40.8.0-1) ...
Selecting previously unselected package python-lockfile.
Preparing to unpack .../02-python-lockfile_1%3a0.12.2-2_all.deb ...
Unpacking python-lockfile (1:0.12.2-2) ...
Selecting previously unselected package python-daemon.
Preparing to unpack .../03-python-daemon_2.2.3-1_all.deb ...
Unpacking python-daemon (2.2.3-1) ...
Selecting previously unselected package python-serial.
Preparing to unpack .../04-python-serial_3.4-4_all.deb ...
Unpacking python-serial (3.4-4) ...
Selecting previously unselected package python-pyhsm.
Preparing to unpack .../05-python-pyhsm_1.2.1-1_all.deb ...
Unpacking python-pyhsm (1.2.1-1) ...
Selecting previously unselected package python-setuptools.
Preparing to unpack .../06-python-setuptools_40.8.0-1_all.deb ...
Unpacking python-setuptools (40.8.0-1) ...
Selecting previously unselected package python-sqlalchemy.
Preparing to unpack .../07-python-sqlalchemy_1.2.18+ds1-2_all.deb ...
Unpacking python-sqlalchemy (1.2.18+ds1-2) ...
Selecting previously unselected package python-sqlalchemy-ext.
Preparing to unpack .../08-python-sqlalchemy-ext_1.2.18+ds1-2_amd64.deb ...
Unpacking python-sqlalchemy-ext (1.2.18+ds1-2) ...
Selecting previously unselected package yhsm-tools.
Preparing to unpack .../09-yhsm-tools_1.2.1-1_all.deb ...
Unpacking yhsm-tools (1.2.1-1) ...
Selecting previously unselected package yhsm-yubikey-ksm.
Preparing to unpack .../10-yhsm-yubikey-ksm_1.2.1-1_all.deb ...
Unpacking yhsm-yubikey-ksm (1.2.1-1) ...
Setting up python-crypto (2.6.1-9+b1) ...
Setting up python-sqlalchemy (1.2.18+ds1-2) ...
Setting up python-serial (3.4-4) ...
Setting up python-pkg-resources (40.8.0-1) ...
Setting up python-lockfile (1:0.12.2-2) ...
Setting up python-setuptools (40.8.0-1) ...
Setting up python-sqlalchemy-ext (1.2.18+ds1-2) ...
Setting up python-pyhsm (1.2.1-1) ...
Setting up python-daemon (2.2.3-1) ...
Setting up yhsm-tools (1.2.1-1) ...
Setting up yhsm-yubikey-ksm (1.2.1-1) ...
Adding user yhsm-ksmsrv to group dialout
Processing triggers for man-db (2.8.5-2) ...
Processing triggers for systemd (241-7~deb10u4) ...

Now, as we will not be using a YubiHSM for in this guide, we need to create a master key for encrypting Now we need to create a master key for decrypting Yubico OTP secrets with, and since we will not be using a YubiHSM in this guide, we do so by creating a plaintext file:

# mkdir -p /etc/yubico/yhsm
# nano /etc/yubico/yhsm/keys.json

The file should contain a JSON object with key handles mapped to AES keys in hex format, we will use this example:

{
  "1": "000102030405060708090a0b0c0d0e0f"
}

We save and close the file, and we now have a keyhandle “1” set up to use a dummy AES key. You will want to replace the value used in this example with a randomly generated key. While you can have multiple key handles, we will stick to using this single one in this guide. Because we want to limit the access to this file, we change ownership and permissions of the file to be readable only by the yhsm-ksmsrv user, which was created during the installation of the software:

# chown yhsm-ksmsrv /etc/yubico/yhsm/keys.json
# chmod 400 /etc/yubico/yhsm/keys.json

Now, we need to configure the yhsm-yubikey-ksm server to use this file, and to start automatically on boot. Start by editing the configuration file:

# nano /etc/default/yhsm-yubikey-ksm

Change the following values:

YHSM_KSM_ENABLE="true"
YHSM_KSM_DEVICE="/etc/yubico/yhsm/keys.json"
YHSM_KSM_KEYHANDLES="1"

Then save and close the file.

By default the yhsm-yubikey-ksm daemon will listen on port 8002 on the loopback interface only. This means that you will be able to access the service from the local machine, but not remotely. We recommend keeping this setting this way, and exposing the port only to the Validation Server(s) which will be using it. Remote Validation Servers can access the port by proxying it through another web server (with authentication) or tunneled through SSH, etc. Opening this port to the Internet without authentication will expose the server to DoS attacks (denial of service).

Now let’s start the service, and verify that it is running:

# service yhsm-yubikey-ksm restart
# curl http://localhost:8002/wsapi/decrypt?otp=
ERR Invalid OTP

Adding credentials to the KSM

The KSM isn’t very useful until we’ve added some credentials to it. We’ll now do just that, as well as program a YubiKey with one of those credentials. We’ll use the yhsm-generate-keys command for this, which takes several parameters. First off, we need to give it the keys.json file from earlier, as it needs access to the master AES key for encrypting the secrets. Second, we tell it which key-handle to use (we’ll use 1, since that’s the only one we’ve defined. Next we’ll give it an offset of where to start in the public ID namespace, and how many credentials to generate. It’s important to note that each credential must have a unique ID in the system, and if we generate more keys in the future we should take care not to generate any ID collisions. For example, if we choose our starting ID to be 1 and generate 10 credentials, the next invocation we should start at 11. The initial ID can be chosen arbitrarily (but avoid using 0, as it has some compatibility issues), and can be given as a decimal number, or a modhex value.

Now, let’s generate 10 new credentials:

# yhsm-generate-keys -D /etc/yubico/yhsm/keys.json --key-handle 1 --start-public-id interncccccc -c 10
...
Generating 10 keys
Done

The following commands show decrypting of OTP secrets, and handling of secrets on a command line. This example is for testing purposed ONLY. You should take extreme care when handling production secrets and avoid passing them as command line arguments, as they may become visible to other parts of the system or logged.

We now have 10 randomly generated credentials which are encrypted and stored in /var/cache/yubikey-ksm/aeads/1/. Let’s decrypt one of them and program a YubiKey with it:

# yhsm-decrypt-aead --aes-key 000102030405060708090a0b0c0d0e0f --key-handle 1 --format yubikey-csv /var/cache/yubikey-ksm/aeads/

The output lists all 10 of our YubiKey credentials in decrypted form. We pick one of the credentials and program a YubiKey with it. In my case, one of the credentials looked like this:

3,internccccck,37ab4177c9d7,1430205ebef371a2811cc84ead40b798,000000000000,,,,,,

The relevant parts are:

Public ID: interncccccc
Private ID: 37ab4177c9d7
Secret AES Key: 1430205ebef371a2811cc84ead40b798

We can now use ykpersonalize to program a YubiKey with the credential:

# ykpersonalize -1 -ofixed=internccccc -ouid=37ab4177c9d7 -a1430205ebef371a2811cc84ead40b798