Generating and distributing entropy with the OneRNG HWRNG USB in various virtualisation environments

Part of the provisioning process for the VPS host in which this Ghost blog platform runs atop involved the generation of a 4096-bit GPG key pair. Exporting, transferring, importing, and ultimately signing the GPG public keys between my laptop and the VPS established the "trust" necessary for strong asymmetric encryption. This encryption mechanism provided the necessary foundation for securely employing the "Single Packet Authentication" utility, fwknop, for which I use in addition to SSH public key authentication for accessing my VPS.
It was during the generation of the GPG keys on the VPS that I witnessed the severe extent in which traditional hardware virtualised machines (HVM) struggle to generate entropy.

Intro

The painfully slow rate in which a HVM generates entropy arises from a reduced amount of available "environment noise" that traditional physical hardware inputs provide as a desirable side effect to their general usage. As per the Linux Kernel's (4.12-rc6) random.c source file, these "inputs" refer to:

[...] inter-keyboard timings, inter-interrupt timings from some interrupts, and other events which are both (a) non-deterministic and (b) hard for an outside observer to measure.

Newer generations of the x86 processors produced by both Intel (IvyBridge and onwards) and AMD (post 2015) include the "RdRand" instruction which is "seeded" by an on-chip entropy source (source). Sadly my server's AMD Opteron 4386 chip (released late 2012) does not have this particular x86 instruction resulting in prologned delays (similar to that faced with my VPS) when applications consume data from the blocking pseudorandom number generator character device node; /dev/random.

Being dissatisfied with the speed in which certain operations/utilities consuming random data from /dev/random operated, I began looking into acquiring an affordable, open source based (software and hardware) USB HWRNG (HardWare Random Number Generator). With these constraints in mind I discovered a small open source hardware producer, Moonbase Otago, produced their own, Linux & GPLv3 friendly, HWRNG/entropy source USB device: OneRNG.

The various virtualisation environments (e.g. LXC, KVM, QEMU) I interact with on a regular basis have their own respective way(s) of interfacing with the OneRNG HWRNG USB device. This blog covers the configuration options necessary for obtaining entropy sourced from the underlying host's OneRNG HWRNG USB for consumption by applications within their respective virtual environment.

Further to the virtualisation platform configuration setups; I have have included the steps necessary for provisioning a basic, client-server modeled entropy broker/distributor service that leverages the OneRNG HWRNG USB. Such a deployment (inspired from a LWN post) allows a single server to distribute entropy (obtained from the OneRNG HWRNG USB) to multiple clients over a TCP/IP network.

Prerequisites

1. A OneRNG HWRNG USB device ("External" or "Internal") connected to a USB port which has been correctly identified by the Linux kernel (ID: 1d50:6086):

[email protected]:~$ dmesg  
...
usb 4-1: USB disconnect, device number 2  
usb 4-1: new full-speed USB device number 3 using ohci-pci  
usb 4-1: New USB device found, idVendor=1d50, idProduct=6086  
usb 4-1: New USB device strings: Mfr=1, Product=3, SerialNumber=3  
usb 4-1: Product: 00  
usb 4-1: Manufacturer: Moonbase Otago http://www.moonbaseotago.com/random  
usb 4-1: SerialNumber: 00  
...

2. The Communication Device Class (CDC) Abstract Control Model (ACM) driver (cdc_acm) is present either in-kernel or has been dynamically loaded as kernel module. The OneRNG HWRNG USB is exposed as a TTY ACM character device node /dev/ttyACM$:

[email protected]:~$ dmesg  
...
cdc_acm 2-1:1.0: This device cannot do calls on its own. It is not a modem.  
cdc_acm 2-1:1.0: ttyACM0: USB ACM device  
usbcore: registered new interface driver cdc_acm  
cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters  
...

[email protected]:~$ lsmod | grep cdc_acm  
cdc_acm         30362  3  
usbcore        195468  4 uhci_hcd,ehci_hcd,ehci_pci,cdc_acm

[email protected]:~$ ls /sys/devices/pci0000:00/0000:00:03.0/usb2/2-1/2-1:1.0/tty  
ttyACM0  
[email protected]:~$ stat /dev/ttyACM0  
File: '/dev/ttyACM0'  
  Size: 0       Blocks: 0       IO Block: 4096   character special file
Device: 5h/5d    Inode: 10402    Links: 1    Device type: a6,0  
Access: (0600/crw-------)  Uid: (0/root)   Gid: (0/root)  
...

Packages Used

linux-image-amd64: 3.16+63
rng-tools: 2-unofficial-mt.14-1
at: 3.1.16-1
python-gnupg: 0.3.6-1
openssl: 1.0.1t-1+deb8u6
onerng: 3.5-2
lxc: 1:1.0.6-6+deb8u6
qemu-kvm: 1:2.8+dfsg-6

Baremetal

While running directly on a host (i.e. "baremetal") is not strictly classified as a "virtualised" environment I felt this particular section was warranted for outlining advanced interactions, service oddities, and troubleshooting steps with the OneRNG HWRNG USB (beyond what is provided in the official documentation).

OneRNG Service Oddities
  • The OneRNG service does not follow the conventional means of service/daemon control management through either a SysVinit init.d script or a systemd service file.

  • The OneRNG service initialisation script, /sbin/onerng.sh, is invoked via a udev rule when the OneRNG HWRNG USB is detected by the Linux kernel (ID: 1d50:6086).

  • The reason for using udev over traditional service files/scripts is that udev can provide the corresponding device node of the detected OneRNG HWRNG USB, e.g. ttyACM$. This appears intended to handle cases whereby an arbitrary amount of ttyACM$'s have already been enumerated by the running system.

  • The /sbin/onerng.sh acknowledges that udev should not be be used for invoking long-running services and so employs the at command in conjunction with an echo command to re-run the /sbin/onerng.sh script with the appropriate arguments (obtained from first invocation via udev).

  • The /sbin/onerng.sh script explicitly disables the rng-tools daemon/service and instead invokes it manually with arguments determined from configuration options set in OneRNG's global configuration file: /etc/onerng.conf.

  • Rather worryingly the /sbin/onerng.sh contains systemd commands (e.g. systemctl) but does not have systemd listed as a dependency in the appropriate Debian binary archive control file.

Installation & Verification

1. Update the APT package cache to ensure that we have the latest packages list and their correct remote location:
sudo apt-get update

2. Install the necessary package dependencies in preparation for installing the individual OneRNG Debian binary archive:
sudo apt-get install --yes rng-tools at python-gnupg openssl ca-certificates

3. Download the OneRNG Debian binary archive and save it to the current user's /home directory:
wget http://moonbaseotago.com/onerng/onerng_3.5-1_all.deb --output-document=~/onerng_3.5-1_all.deb

4. Install the OneRNG Debian binary archive:
sudo dpkg --install onerng_3.5-1_all.deb

5. Confirm the OneRNG Debian binary archive has been correctly installed by dpkg. The 'onerng' package entry should be prefixed by "ii" which can be read as: desired state = installed, and actual state = installed :
sudo dpkg --list onerng | tail --lines 1

6. Confirm the OneRNG service is operational by determining whether the leveraged rngd daemon is running and pointing to the correct ttyACM$ device:
ps aux | grep rngd

7. Confirm the rngd daemon is feeding the Linux kernel's entropy pool from data provided by the OneRNG USB:
cat /proc/sys/kernel/random/entropy_avail
The above command should return a single value in the excess of 1000; such a value indicates that entropy pool is being filled correctly by the rngd daemon and OneRNG HWRNG USB.

8. Verify the quality of the randomness produced via the OneRNG HWRNG USB passes the Federal Information Processing Standard publication 140-2 (FIPS 140-2) cryptographic standard:
sudo cat /dev/ttyACM$ | rngtest --blockcount=100 |& grep 'failures\|successes'
For those wondering why the final pipe of the above command is '|&' as opposed to a single |: This is due to the fact rngtest outputs to STDERR instead of STDOUT and a traditional pipe (|) will not pass STDERR output through to the following command.

Altering OneRNG States

During my dissection of the /sbin/onenrng.sh OneRNG script I encountered the "core" commands used to directly interact (as opposed to via rngd daemon) with the OneRNG HWRNG USB character device node.
As the command "modes" (i.e. the manner in which the OneRNG HWRNG USB generates entropy) are explained in the OneRNG's configuration file (/etc/onerng.conf), this subsection examines the omitted - but utilised commands as they may provide useful during testing/debugging/troubleshooting sessions.

1. Power down the OneRNG HWRNG USB:
echo "cmdo" | sudo tee /dev/ttyACM$
This operation should result in the entropy pool (/proc/sys/kernel/random/entropy_avail) drain until reaching levels prior to connecting the the OneRNG HWRNG USB.

2. Power up the OneRNG HWRNG USB:
echo "cmdO" | sudo tee /dev/ttyACM$
This operation should result in the entropy pool (/proc/sys/kernel/random/entropy_avail) rapidly filling up again as the OneRNG HWRNG USB can once again feed the rngd daemon. Note: The entropy pool value will fill up to the maximum amount specified in /proc/sys/kernel/random/poolsize.

3. Flush the current OneRNG HWRNG USB's on-board entropy pool:
echo "cmdw" | sudo tee /dev/ttyACM$

Troubleshooting
  • "The rngd service is not reading from the OneRNG ttyACM$ device node...".
    As covered previously in the "Service Oddities" section: the rngd daemon is not started at boot by systemd, instead it is invoked via the /sbin/onerng.sh script which is turn triggered by a specific udev event. While the /sbin/onerng.sh script should manually stop the rngd daemon I'd argue that explicitly disabling the rngd daemon via systemd prevents confusion for why the daemon is running in the first place as well as ensuring the manually invoked rngd daemon is the only one running:
    sudo systemctl stop rng-tools
    sudo systemctl disable rng-tools
    sudo systemctl mask rng-tools

  • "The rngd service is not running at boot or when I plug the OneRNG HWRNG USB in..."
    Open the /sbin/onerng.sh script up in an editor and insert set -x on its own line immediately under the shebang (#!) and save the file. From there invoke the script in the same manner that the at command does (ensuring to pass the correct ttyACM$ device node) and examine the verbose output to begin triaging the issue:
    sudo /sbin/onerng.sh daemon ttyACM$

  • "Manually invoking the /sbin/onerng.sh returns 1 (failure) but the script appears to execute correctly..."
    If you have enabled the ONERNG_VERIFY_FIRMWARE option (default is enabled) in the /etc/onerng.conf OneRNG configuration file than part of the /sbin/onerng.sh will invoke a python script /sbin/onerng_verify.py that verifies the integrity of the loaded firmware based off a small contents dump directly from the OneRNG /dev/ttyACM$ device node. This python script logs all output to /var/log/syslog so examining the log contents can help with determining whether the OneRNG firmware has been compromised.
    The set -x flag won't expose the STDOUT of the /sbin/onerng_verify.py python script so manually invoke it against some raw binary data extracted from the OneRNG HWRNG USB so as to examine the verification results:
    echo "cmdO" | sudo tee /dev/ttyACM$
    echo "cmd0" | sudo tee /dev/ttyACM$
    sudo dd if=/dev/ttyACM$ iflag=fullblock bs=512 count=4 of=/tmp/onerng_dump.bin
    sudo /sbin/onerng_verify.py /tmp/onerng_dump.bin

KVM

The Linux based Kernel-based Virtual Machine (KVM) infrastructure provides the virtualisation foundation that powers not only my VPS host but also all HVM/PVM VM environments on my server ("Octeron"). From reading various QEMU/KVM man pages there appears to be two distinct approaches for providing entropy to KVM guests (accelerated or emulated) from the underlying baremetal host:
1. Through the paravirtualised interface VirtIO RNG, or
2. Passing the OneRNG HWRNG USB through into the guest and running the OneRNG service as you would on a baremetal system.
Thankfully both methods have been supported since 2009 (source ~ VirtIO RNG, source ~ QEMU USB Passthrough) making either approach a viable solution for even "OldOldStable" Debian 6.0 "Squeeze" guests! For simplicity the aforementioned approaches are executed against fully supported (in the context of the two entropy approaches), hardware accelerated Debian 8.0 "Jessie" KVM guest.

VirtIO RNG

The Fedora feature documentation succinctly summarises the paravirtualised VirtIO RNG interface as:

"[...] a paravirtualized device that is exposed as a hardware RNG device to the guest. On the host side, it can be wired up to one of several sources of entropy, including a real hardware RNG device as well as the host's /dev/random" (source)

Unlike the generic USB Passthrough approach, the paravirtualised VirtIO RNG interface can be exposed to multiple KVM guests whilst simultaneously applying user-configured, per-guest rate limiting on the consumption of the host's entropy pool. Its flexibility over the alternative option in this regard makes it a favourable solution, however it does require that the KVM guest support the KVM VirtIO RNG interface in order for it to be utilised.

This section outlines the operations necessary for a preconfigured OneRNG HWRNG USB to provide entropy from the KVM host (i.e. baremetal) to a given KVM guest.

KVM Host

1. Check that the OneRNG service is installed correctly and is currently "feeding" the entropy pool of the baremetal KVM host via the /dev/ttyACM$ character device node. Refer to the Baremetal section (above) for the setting up the OneRNG HWRNG USB on a Debian 8.0 GNU/Linux systems.

2. Ensure KVM guests targeted for being "fed" entropy over the VirtIO RNG paravirtualised interface have been passed the correct arguments through either the Libvirt CLI utility (virt-install) or directly via the QEMU (qemu-system-x86_64) invocation:

# Libvirt 'virt-install' VirtIO RNG argument
...
--rng rate_bytes=$BYTES,rate_period=$MILLISECONDS /dev/random \
...

# QEMU direct invocation 'qemu-system-x86_64' arguments
...
-object rng-random,id=rng0,filename=/dev/random,id=rng0 \
-device virtio-rng-pci,rng=rng0,max-bytes=$BYTES,period=$MILLISECONDS \
...

Note: It may be possible to specify the random input source to /dev/ttyACM$ as opposed to the /dev/random device node specified above - however I have not tested this configuration so your results may vary!

KVM Guest

1. The required paravirtualised RNG kernel module, virtio-rng.ko depends upon the Linux's core HWRNG driver rng-core.ko in order to "feed" the kernel's entropy pool from an external source. Check that the KVM guest has the rng-core.ko kernel module as either an externally loadable module or compiled directly into the running Linux kernel itself. If the virtio-rng.ko kernel module is an externally loadable module ensure it has been loaded:
stat /lib/modules/$(uname -r)/kernel/drivers/char/hw_random/rng-core.ko
sudo modprobe --verbose rng-core.ko
-- OR --
grep rng-core.ko /lib/modules/$(uname -r)/modules.builtin

2. Check that the KVM guest has the virtio-rng.ko kernel module as either an externally loadable module or compiled directly into the running Linux kernel itself. If the virtio-rng.ko kernel module is an externally loadable module ensure it has been loaded (note: modprobe will automatically pull in rng-core.ko if not already loaded):
stat /lib/modules/$(uname -r)/kernel/drivers/char/hw_random/virtio-rng.ko
sudo modprobe --verbose virtio-rng.ko
-- OR --
grep virtio-rng.ko /lib/modules/$(uname -r)/modules.builtin

3. Check that the HWRNG core detected the VirtIO RNG interface as both an available and selected ("current") source of entropy for the KVM guest's kernel:
cat /sys/devices/virtual/misc/hw_random/rng_available
cat /sys/devices/virtual/misc/hw_random/rng_current
Note: Linux kernel developments in the later 3.X series interact with the VirtIO RNG interface in a manner whereby the entropy passed via the paravirtualised interface is "fed" directly to the KVM guest kernel's entropy pool (source). For completeness I have included the remaining steps (for this subsection) that involve configuring and validating the otherwise unnecessary rng-tools daemon.

4. Check that the corresponding /dev/hwrng character device node has been exposed within the KVM guest. This device node serves as the bridge between the paravirtualised RNG interface provided to the guest and the rng-tools daemon, rngd, which "feeds" entropy to the KVM guest's kernel:
stat /dev/hwrng

5. Install the rng-tools Debian binary archive wiithin the KVM guest. By default the rngd daemon will utilise the /dev/hwrng character device node as the input source for random data so no additional configuration is necessary for getting the rngd daemon running after installation:
sudo apt-get install --yes rng-tools

6. Check that the rngd daemon has successfully opened the /dev/hwrng character device node with read permissions:
lsof /dev/hwrng

COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME  
rngd    414 root    3r   CHR 10,183      0t0 10061 /dev/hwrng  

7. Check that the KVM guest's kernel entropy pool is being "fed" from the rngd daemon:
cat /proc/sys/kernel/random/entropy_avail
The above command should return a single value in the excess of 1000; such a value indicates that entropy pool is being filled correctly by the rngd daemon via the paravirtualised VirtIO RNG interface.

8. Verify the quality of the randomness produced via the paravirtualised VirtIO RNG interface (ultimately obtained from the OneRNG HWRNG USB) still passes the Federal Information Processing Standard publication 140-2 (FIPS 140-2) cryptographic standard:
sudo cat rngtest --blockcount=100 < /dev/hwrng |& grep 'failures\|successes'

USB Passthrough

As (briefly) outlined previously, the paravirtualised VirtIO RNG interface requires the KVM guest to support the VirtIO RNG interface in order for entropy generated on the underlying KVM host to be passed to it. Chances are VirtIO's broad support (i.e. guest implementation) for numerous, popular operating systems such as GNU/Linux, OpenBSD (source), FreeBSD (source), NetBSD (source), Plan 9 (Source) and Windows (source) will cover the vast majority of desired use cases.
For edge cases whereby VirtIO support is not available (or not feasible/suitable) we can leverage QEMU/KVM's generic USB passthrough capability as a means of providing an entropy generator source to a single KVM guest.

Sadly QEMU/KVM does not support the ability to passthrough a single USB device to multiple KVM guests; attempting to do so results in the following error:

error: Requested operation is not valid: USB device 003:002 is in use by driver QEMU, domain moonbase_rng  

While this behaviour makes perfect sense for devices such as USB Mass Storage devices it would have been interesting to have had a pre-initialised OneRNG HWRNG USB (i.e. enable internal entropy generation via raw cmd messages) passed through in read-only mode to multiple KVM guests. At that stage you could configure the rngd daemon on each of the KVM guests to source its entropy from the /dev/ttyACM$ character device node.

KVM Host

1. Check that the OneRNG service is not running and the rngd daemon is no longer feeding the entropy pool from the OneRNG HWRNG USB /dev/ttyACM$ character device node:
sudo systemctl stop rng-tools
sudo lsof /dev/ttyACM$
The above command should return an empty response indicating that the /dev/ttyACM$ character device node is not being held by any running process(es) on the baremetal host.

2. Specify the "vendor" and "product" ID pair to either the Libvirt CLI utility (virt-install) or, alternatively, via the QEMU (qemu-system-x86_64) invocation during VM creation/definition for passing the OneRNG HWRNG USB into the KVM guest:

# Libvirt 'virt-install' USB Passthrough
...
--host-device 1d50:6086 \
...

# QEMU direct invocation 'qemu-system-x86_64' arguments
...
-usb \
-device usb-host,vendorid=0x1d50,productid=0x6086 \
...

3. Check that the qemu-system-x86_64 process has successfully opened the USB /dev/ttyACM$ character device node with read and write permissions. Both interaction permission types are required as the KVM guest will need to interact with the OneRNG HWRNG USB in the same manner the underlying host typically would (i.e. sending raw commands for initialising/modesetting the OneRNG HWRNG USB):
lsof /dev/ttyACM0

COMMAND    PID         USER   FD   TYPE DEVICE SIZE/OFF  NODE    NAME  
qemu-syst 30203 libvirt-qemu  24u  CHR  189,387  0t0     945139  /dev/ttyACM0
KVM Guest

1. Check that the OneRNG HWRNG USB device has been successfully passed through from the KVM host to the KVM guest:
lsusb -d 1d50:6086
The above command should return a single line displaying the unique "Bus", "Device", and "ID" combination allocated to the OneRNG HWRNG USB.

2. Given that the hardware environment is essentially a "simplified" baremetal scenario (i.e. reduced amount of peripherals, PCI devices, buses, etc.) proceed to follow the installation and configuration steps outlined in the baremetal section (above).

LXC

Several testing environments I interact with rely heavily on the OS-level virtualisation userspace utilities, Linux Containers (LXC), for rapidly provisioning "namespaced", resource constrained ("cgroups"), lightweight GNU/Linux systems (i.e. "chroot on steriods"). One particular characteristic of OS-level virtualisation is that guests share the same Linux kernel as the underlying host and also the same entropy pool.
With this in mind it is possible to provision "standard" (i.e. Debian GNU/Linux Jessie LXC container template) LXC guests that will automatically consume entropy from the underlying LXC host via their own respective /dev/random character device node (i.e. not "bind" mounted from the LXC host). Assuming the LXC host has a correctly configured OneRNG HWRNG USB the generated entropy can be passed through without any intermediate transport protocol (e.g. VirtIO) to an arbitrary amount of LXC guests. Essentially it is as simple as provisioning a LXC guest and running the entropy hungry application (e.g. gpg) to take advantage of the vast amounts of entropy generated by the OneRNG HWRNG USB.

Nevertheless, this section covers the steps necessary for passing the OneRNG HWRNG USB character device node through into a privileged LXC container and running the OneRNG service from within the targeted guest. From examining the LXC man page I discovered that there are two plausible methods for "passing through" the OneRNG HWRNG USB character device node to a LXC guest:
1. Specifying the OneRNG HWRNG USB character device node's major and minor values (e.g. 166:0) explicitly via the "devices" subsystem cgroup. By leveraging the "devices" cgroup, LXC offers granular permission based control (read, write, and mknod) when exposing host devices to LXC guests. This approach employs the following configuration structure:
lxc.cgroup.devices.allow $DEV_TYPE $MAJOR:$MINOR $PERMS.
2. Specifying the OneRNG HWRNG USB character device node's unique ttyACM$ node via the mount namespace. While typically utilised for sharing specified directories (be it locally or on remote storage) to LXC guests the mount namespace can be used to "bind" mount device node(s) from the LXC host to the LXC guest. This approach employs the following configuration structure:
lxc.mount.entry = $HOST_MOUNT $GUEST_MOUNT $MOUNT_OPTS,create=$CREATE_TYPE.

Unlike QEMU/KVM, LXC does not appear to support using either the OneRNG HWRNG USB's unique USB "vendor:model" identifier (useful when using a single OneRNG HWRNG USBs) or the USB "bus:device" identifier (useful when using multiple OneRNG HWRNG USBs) within the LXC guest's configuration file. Unfortunately the two identification methods used by LXC may result in the guest failing to start as the underlying host's kernel has enumerated the OneRNG HWRNG USB with a different device node "major:minor" pairing or device node name.
The QEMU/KVM identification methods may eventually be added as usable options within the LXC container configuration file given that modern versions of LXC have demonstrated the ability to hotplug USB devices (albeit to running LXC guests only) by specifying the USB device's "vendor:model" identification pair (source).

The following operations cover the "devices" cgroup approach given the fact it is was originally designed to "[...] track and enforce open and mknod restrictions on device files." (source). Please note: I have not attempted the alternative approach ("mount" namepsace) and therefore cannot comment on its viability for this particular scenario.

1. Create a Debian GNU/Linux 8.0 "Jessie" LXC guest that will be targeted for passing the OneRNG HWRNG USB through to:
sudo lxc-create -n -m moonbase_lxc -t debian -- --release=jessie --arch=amd64

2. Create a dedicated "hook" Bash script (/var/lib/lxc/moonbase_lxc/autodev.sh) that will be invoked at boot whenever the previously defined LXC guest is started. This small script automates the commands required for enumerating the OneRNG HWRNG USB character device node within the LXC guest's /dev directory:

#!/usr/bin/env bash

# Creates the ttyACM$ device node for access by the 'rng-tools' daemon
# This mimics the default 'udev' behaviour in a Debian "Jessie" on a baremetal system
# Credits: http://fault.itsprite.com/running-systemd-based-container-in-lxc/

mknod ${LXC_ROOTFS_MOUNT}/dev/ttyACM$ c 166 0  
chown root:dialout ${LXC_ROOTFS_MOUNT}/dev/ttyACM$  
chmod 660 ${LXC_ROOTFS_MOUNT}/dev/ttyACM$  

Ensure you substitute $ with the number allocated by the underlying LXC host.

3. Permit the execution of the autodev.sh Bash script:
sudo chmod 755 /var/lib/lxc/moonbase_lxc/autodev.sh

4. Edit the "moonbase_lxc" LXC container configuration file so as to: A) permit read and write access to the OneRNG HWRNG USB character device node and, B) specify the autodev.sh hook script for invocation at container startup.

# R/W access to OneRNG HWRNG USB
lxc.cgroup.devices.allow = c 166:0 rw

# OneRNG HWRNG USB character device node auto-creation
lxc.autodev = 1  
lxc.hook.autodev = /var/lib/lxc/moonbase_lxc/autodev.sh  

As per the LXC man page the lxc.hook.autodev is used to "[...] to assist in populating the /dev directory of the container when using the autodev option for systemd based containers". Given that the Debian GNU/Linux 8.0 "Jessie" LXC guest utilises systemd this hook mount ordering option is most suitable for this scenario.

4. Start the preconfigured LXC guest:
sudo lxc-start --name moonbase_lxc --daemon

5. Access the LXC guest's console:
sudo lxc-console --name moonbase_lxc
Note: The remaining operations are performed as the root user within the LXC guest.

6. Install the necessary prerequisite packages:
apt-get install -y rng-tools at python-gnupg openssl

7. Download the OneRNG Debian binary archive and save it to the root user's dedicated directory:
wget http://moonbaseotago.com/onerng/onerng_3.5-1_all.deb --output-document=/root/onerng_3.5-1_all.deb

8. Install the OneRNG Debian binary archive. The Debian binary package installer, dpkg, will error out midway through the installation (and subsequent configuration) of the OneRNG package due to its postinst script invoking udev which in turn fails (return code 2):
dpkg --install onerng_3.5-1_all.deb

Selecting previously unselected package onerng.  
(Reading database ... 17300 files and directories currently installed.)
Preparing to unpack onerng_3.5-1_all.deb ...  
Unpacking onerng (3.5-1) ...  
Setting up onerng (3.5-1) ...  
dpkg: error processing package onerng (--install):  
 subprocess installed post-installation script returned error exit status 2
Errors were encountered while processing:  
 onerng

Despite dpkg marking the onerng package as being in an installed but "half configured" state (as per dpkg --list | grep onenrg), the OneRNG service still has all of its configuration and service/daemon files copied to the appropriate locations.

9. Create a systemd service file, /lib/systemd/system/onerng.service, for starting the OneRNG service at boot within the LXC guest:

[Unit]
Description=Onerng Moonbase Otago USB HWRNG USB Daemon  
Documentation=http://onerng.info/  
ConditionPathExists=/dev/ttyACM$  
ConditionVirtualization=container

[Service]
Type=forking  
ExecStart=/sbin/onerng.sh daemon ttyACM$

[Install]
WantedBy=multi-user.target  

Given that we explicitly know the OneRNG HWRNG USB character device node (e.g. /dev/ttyACM0) being passed through to the LXC guest we can invoke the main OneRNG service script, /sbin/onerng.sh, against the OneRNG HWRNG USB character device node. The above systemd service file will start the OneRNG service once the LXC guest has fully initialised.

10. Enable the onerng.service systemd service in order to start the OneRNG service at boot:
systemctl enable onerng.service

11. Start the onerng.service systemd service:
systemctl start onerng.service

12. Confirm the onerng.service systemd service is running:
systemctl status onerng.service

13. Confirm the rngd daemon is feeding the Linux kernel's entropy pool from data provided by the OneRNG HWRNG USB:
cat /proc/sys/kernel/random/entropy_avail
The above command should return a single value in the excess of 1000; such a value indicates that entropy pool is being filled correctly by the rngd daemon and OneRNG HWRNG USB.

14. Verify the quality of the randomness produced via the OneRNG HWRNG USB passes the Federal Information Processing Standard publication 140-2 (FIPS 140-2) cryptographic standard:
sudo cat /dev/ttyACM$ | rngtest --blockcount=100 |& grep 'failures\|successes'

Distributing Entropy

Practically all my virtualised (KVM or LXC) environments reside on my server ("Octeron") and so the aforementioned approaches to providing entropy to the targeted guest(s) is sufficiently flexible for the vast majority of my scenarios. Nevertheless, future scenarios/projects may result in a mixture of virtual and physical machines and therefore the listed approaches will not suffice for physical hosts in such an environment.
With this in mind I set about piecing together a primitive, but functional, entropy broker and entropy consumer setup (server-client model) that would facilitate the distribution of entropy (generated by the OneRNG HWRNG USB) over a TCP/IP network. Credit should be given to the LWN article which covered a high level overview of a fully-fledged entropy broker and entropy consumer utility as it served as inspiration for "building" my own entropy broker/consumer setup by using existing system utilities.

Given its comparative simplicity in relation to the entropy broker discussed in the LWN article there are notable disadvantages to the basic broker/consumer setup that may make it unsuitable for deployment in your own environment. These noteworthy disadvantages are:

  • Inability to of rate limit entropy from the broker to a consumer(s)
  • The same entropy byte stream is sent to all consumer(s), this may make it easier for malicious users to determine the state of the consumer's entropy state
  • Lack of authentication between the broker and consumer(s)
  • Lack of encryption between the broker and consumer(s)

I attempted to use passwordless SSH public key authentication to resolve, or at least mitigate to some degree, the authentication and encryption disadvantages of my approach without success. From a brief debugging phase it appears that the byte stream wouldn't buffer as required when piped via a SSH forwarding tunnel.

Entropy broker

1. Check that the OneRNG service is installed correctly and is currently "feeding" the entropy pool of the host via the /dev/ttyACM$ character device node. Refer to the Baremetal section (above) for the setting up the OneRNG HWRNG USB on a Debian 8.0 GNU/Linux systems.

2. Install the "SOcket CAT" (socat) utility that will provide the necessary byte stream redirection for buffering entropy from the OneRNG HWRNG USB character device node to a TCP socket:
sudo apt-get install --yes socat

3. Create a systemd service template file, /etc/systemd/system/onerng-broker-server.service, that invokes the socat listener with the STDIN stream sourcing from the OneRNG HWRNG USB character device node: /dev/ttyACM$.

[Unit]
Description=OneRNG Broker Server  
ConditionPathExists=/dev/ttyACM$

[Service]
Type=simple  
ExecStart=/usr/bin/socat FILE:/dev/ttyACM$ TCP-LISTEN:55000,fork  
RemainAfterExit=True  
Restart=on-failure  
RestartSec=5

[Install]
WantedBy=multi-user.target  

I was hoping to employ systemd's "templating" for enabling flexible creation of service files to provide a quick way of provisioning multiple OneRNG HWRNG USB devices by the entropy broker. Unfortunately this was not possible as the TCP-LISTEN argument requires a unique port for each ttyACM$ character device node it reads from.

4. Enable the onerng-broker-server.service systemd service file to ensure it starts during system initialisation:
sudo systemctl enable onerng-broker-server.service

5. Start the onerng-broker-server.service systemd service:
sudo systemctl start onerng-broker-server.service

6. Confirm that the socat utility is reading from the specified OneRNG HWRNG USB character device node and buffering the byte stream to the desired TCP socket:
socat TCP:127.0.0.1:55000 - | base64
The above command should result in a stream of alphanumeric characters populating the console. Once confirmed proceed to terminate the blocking command with the SIGINT signal (ctrl+c).

Entropy consumer

1. Install the socat and the rng-tools packages:
sudo apt-get install --yes socat rng-tools

2. Disable the default rng-tools SysVinit init script from starting the rngd daemon at boot:
sudo systemctl disable rng-tools

3. Create a static, named pipe that will be used by socat for redirecting the byte stream from the broker's public TCP socket to:
sudo mkfifo /dev/onerng-pipe

4. Create a systemd service file, /etc/systemd/system/onerng-broker-client.service, that will have socat redirect the byte stream received from connecting to the "advertised" TCP socket on the broker to the buffered named pipe:

[Unit]
Description=OneRNG Broker Client  
After=network-online.service  
Before=onerng-rngd.service

[Service]
Type=simple  
ExecStart=/usr/bin/socat TCP:$IP_OF_BROKER:55000 PIPE:/dev/onerng-pipe  
Restart=on-failure  
RestartSec=5

[Install]
WantedBy=multi-user.target  

5. Enable the onerng-broker-client.service systemd service file to ensure it starts during system initialisation:
sudo systemctl enable onerng-broker-client.service

6. Start the onerng-broker-client.service systemd service:
sudo systemctl start onerng-broker-client.service

7. Confirm that the socat process is reading input from the broker's TCP socket and is buffering the byte stream to the named pipe (/dev/onenrg-pipe):
base 64 < /dev/onerng-pipe
The above command should result in a stream of alphanumeric characters populating the console; output here illustrates that the entire broker-consumer "chain" has been correctly established. Once confirmed proceed to terminate the blocking command with the SIGINT signal (ctrl+c).

8. Edit the rngd daemon configuration file (/etc/default/rng-tools) and set the HRNGDEVICE variable to that of the named pipe (/dev/onenrg-pipe). As the TCP connection between the consumer and the broker is not encrypted I recommend passing additional options that ensure the byte stream ingested by the rngd daemon from the entropy broker only contributes (not dictates) to the entropy pool.
This particular rate-limiting, consumption restricted safeguard protects against potential "man-in-the-middle" attacks as the intruder cannot dictate the state of the entropy pool of the consumer(s).
HRNGDEVICE=/dev/onerng-pipe
RNGDOPTIONS="--fill-watermark=33% --feed-interval=90"

9. Create a systemd service file, /etc/systemd/system/onerng-rngd.service, that will invoke the rngd daemon in a similar fashion to its corresponding, default SysVinit counterpart.

[Unit]
Description=OneRNG rngd-daemon  
After=onerng-broker-client.service

[Service]
Type=forking  
EnvironmentFile=/etc/default/rng-tools  
ExecStart=/usr/sbin/rngd -r $HRNGDEVICE $RNGDOPTIONS

[Install]
WantedBy=multi-user.target  

The reason we do not employ the preinstalled SysVinit rngd service file (/etc/init.d/rng-tools) is because the default init script checks the HRNGDEVICE variable's value and if its path value does not correspond to a character device node (e.g. /dev/hwrng, /dev/ttyACM0) it fails and terminates. As this setup uses a named pipe as an input source the startup script will fail and the rngd daemon wont run!

10. Enable the onerng-rngd.service systemd service file to ensure it starts during system initialisation:
sudo systemctl enable onerng-rngd.service

11. Start the onerng-rngd.service systemd service:
sudo systemctl start onerng-rngd.service

12. Confirm the rngd daemon is running with the configuration options specified in the /etc/default/rng-tools file:
ps aux | grep rngd

13. Confirm the rngd daemon is feeding the entropy consumer kernel's entropy pool from the byte stream sourced from the named pipe (/dev/onenrng-pipe):
cat /proc/sys/kernel/random/entropy_avail
The above command should return a single value in the excess of 1000; such a value indicates that entropy pool is being filled correctly by the rngd daemon.

14. Verify the quality of the randomness produced via the byte stream sourced from the named pipe (/dev/onenrng-pipe) passes the Federal Information Processing Standard publication 140-2 (FIPS 140-2) cryptographic standard:
sudo cat /dev/onerng-pipe | rngtest --blockcount=100 |& grep 'failures\|successes'

Contributing

As implicitly illustrated in the LXC subsection, the OneRNG Debian binary archive was not constructed with OS level virtualisation in mind. In an effort to improve the package's portability within OS level virtualisation environments I took it upon myself to disassemble the package and add/edit the following components:

  • Created a systemd service file template, /lib/systemd/system/[email protected] (outlined in the LXC section), that is dynamically generated on a per OneRNG HWRNG USB character device node basis by the DEBIAN/postinst script.

  • Heavily modified the DEBIAN/postinst "control" file in order to provide OS virtualisation environment detection. If the Bash script detects a Docker or LXC environment it will:
    1. Iterate through all available TTY ACM device nodes.
    2. Check to see if their respective vendor USB major:minor number matches up to the OneRNG HWRNG USB's.
    3. Upon finding a match generate a systemd service file (using the system service file template) based off the OneRNG HWRNG USB character device node identifier/name.

  • Created the DEBIAN/postrm "control" file that stops the dedicated systemd service file should the installation have been performed on within a OS level virtualisation environment.

The final, reassembled OneRNG package containing these additions/editions can be downloaded from my Google Drive share here: Link (md5sum: b912d6405da0c962b498863a46d86619).
From my own testing I can confirm the package installs/uninstalls correctly and that the onerng.sh script is invoked by the OneRNG specific systemd service file in the same manner that udev does within a baremetal environment (i.e. at boot and immediately after installation).

I've sent the package upstream to a OneRNG core member - Jim Cheetham - for his consideration should he wish to include these changes in the upstream OneRNG package.

Final Words

Understanding more about the concepts of randomness/entropy and its scarcity within both traditional hardware-accelerated virtualised environments and isolated (i.e. little to no user based inputs) hardware environments (without entropy generating CPU extensions) has deepened my appreciation of affordable HWRNG devices. Interacting with Moonbase Otago's OneRNG HWRNG USB has been a pleasurable experience given the extensive Linux support from kernel space (cdc_acm) and user space (rng-tools) perspectives.

Being a proponent of open source it is encouraging to see small hardware vendors such as Moonbase Otago adopt such principles in both their hardware and software implementations. In the very spirit of the GPL I have been able to leverage the foundation established by the OneRNG team, construct/implement a particular feature that I now utilise, and ultimately contribute said feature back to the wider community.

In closing I'd like to mention an excellent blog post, "Myths about /dev/urandom", written by Thomas Hühn which corrects numerous misconceptions (which I myself admittedly fell foul to) regarding /dev/urandom's viability for producing cryptographically "strong" randomness for consumption by various utilities/applications (e.g. SSH keys). Not only did Thomas's blog post correct my perception of /dev/urandom it also refreshed (and furthered) my understanding of the difference between /dev/random and /dev/urandom from a Linux internals perspective.