1# Entropy quality tests 2 3This document describes how we test the quality of the entropy sources used to 4seed the Zircon CPRNG. 5 6[TOC] 7 8## Theoretical concerns 9 10Approximately speaking, it's sometimes easy to tell that a stream of numbers is 11not random by recognizing a pattern in it. It's impossible to be sure that the 12numbers are truly random. The state of the art seems to be running several 13statistical tests on the data, and hoping to detect any exploitable weaknesses. 14 15The problem of testing for randomness gets more difficult when the random 16numbers aren't perfectly random (when their distributions aren't uniform, or 17when there are some limited correlations between numbers in the sequence). A 18stream of non-perfect random numbers still contains some randomness, but it's 19hard to determine how random it is. 20 21For our purposes, a good measure of how much randomness is contained in a stream 22of non-perfectly random numbers is the min-entropy. This is related to the 23Shannon entropy used in information theory, but is always takes a smaller value. 24The min-entropy controls how much randomness we can reliably extract from the 25entropy source; see, for example 26<https://en.wikipedia.org/wiki/Randomness_extractor#Formal_definition_of_extractors> 27 28From a practical standpoint, we can use the test suite described in US NIST 29SP800-90B to analyze samples of random from an entropy source. A prototype 30implementation for the tests is available from 31<https://github.com/usnistgov/SP800-90B_EntropyAssessment>. The suite takes a 32sample data file (say, 1MB of random bytes) as input. The nice thing about this 33test suite is that it can handle non-perfect RNGs, and it reports an estimate 34for how much min-entropy is contained in each byte of the random data stream. 35 36### The importance of testing unprocessed data 37 38After drawing entropy from our entropy sources, we will mix it into the CPRNG in 39a "safe" way that basically gets rid of detectable correlations and 40distributional imperfections in the raw random byte stream from the entropy 41source. This is a very important thing to do when actually generating random 42numbers to use, but we must avoid this mixing and processing phase when testing 43the entropy source itself. 44 45For a stark example of why it's important to test unprocessed data if we want to 46test our actual entropy sources, here's an experiment. It should run on any 47modern linux system with OpenSSL installed. 48 49 head -c 1000000 /dev/zero >zero.bin 50 openssl enc -aes-256-ctr -in zero.bin -out random.bin -nosalt -k "password" 51 52This takes one million bytes from /dev/zero, encrypts them via AES-256, with a 53weak password and no salt (a terrible crypto scheme, of course!). The fact that 54the output looks like good random data is a sign that AES is working as 55intended, but this demonstrates the risk of estimating entropy content from 56processed data: together, /dev/zero and "password" provide ~0 bits of entropy, 57but our tests are way more optimistic about the resulting data! 58 59For a more concrete Zircon-related example, consider jitterentropy (the RNG 60discussed here: <http://www.chronox.de/jent/doc/CPU-Jitter-NPTRNG.html>). 61Jitterentropy draws entropy from variations in CPU timing. The unprocessed data 62are how long it took to run a certain block of CPU- and memory-intensive code 63(in nanoseconds). Naturally, these time data are not perfectly random: there's 64an average value that they center around, with some fluctuations. Each 65individual data sample might be several bits (e.g. a 64-bit integer) but only 66contribute 1 bit or less of min-entropy. 67 68The full jitterentropy RNG code takes several raw time data samples and 69processes them into a single random output (by shifting through a LFSR, among 70other things). If we test the processed output, we're seeing apparent randomness 71both from the actual timing variations and from the LFSR. We want to focus on 72just the timing variation, so we should test the raw time samples. Note that 73jitterentropy's built-in processing can be turned on and off via the 74`kernel.jitterentropy.raw` cmdline. 75 76## Quality test implementation 77 78As mentioned above, the NIST test suite takes a file full of random bytes as 79input. We collect those bytes on a Zircon system (possibly with a thin Fuchsia 80layer on top), then usually export them to a more capable workstation to run the 81test suite. 82 83## Boot-time tests 84 85Some of our entropy sources are read during boot, before userspace is started. 86To test these entropy sources in a realistic environment, we run the tests 87during boot. The relevant code is in 88`kernel/lib/crypto/entropy/quality\_test.cpp`, but the basic idea is that the 89kernel allocates a large static buffer to hold test data during early boot 90(before the VMM is up, so before it's possible to allocate a VMO). Later on, the 91data is copied into a VMO, and the VMO is passed to userboot and devmgr, where 92it's presented as a pseudo-file at `/boot/kernel/debug/entropy.bin`. Userspace 93apps can read this file and export the data (by copying to persistent storage or 94using the network, for example). 95 96In theory, you should be able to build Zircon with entropy collector testing 97enabled using `scripts/entropy-test/make-parallel`, and then you should be able 98to run a single boot-time test with the script 99`scripts/entropy-test/run-boot-test`. The `run-boot-test` script is mostly 100intended to be invoked by other scripts, so it's a little bit rough around the 101edges (for example, most of its arguments are passed via command line options 102like `-a x86-64`, but many of these "options" are in fact mandatory). 103 104Assuming the `run-boot-test` script succeeds, it should produce two files in the 105output directory: `entropy.000000000.bin` and `entropy.000000000.meta`. The 106first is the raw data collected from the entropy source, and the second is a 107simple text file, where each line is a key-value pair. The keys are single words 108matching `/[a-zA-Z0-9_-]+/`, and the values are separated by whitespace matching 109`/[ \t]+/`. This file can be pretty easily parsed via `read` in Bash, 110`str.split()` in Python, or (with the usual caution about buffer overruns) 111`scanf` in C. 112 113In practice, I'm nervous about bit-rot in these scripts, so the next couple 114sections document what the scripts are supposed to do, to make it easier to run 115the tests manually or fix the scripts if/when they break. 116 117### Boot-time tests: building 118 119Since the boot-time entropy test requires that a large block of memory be 120permanently reserved (for the temporary, pre-VMM buffer), we don't usually build 121the entropy test mode into the kernel. The tests are enabled by passing the 122`ENABLE_ENTROPY_COLLECTOR_TEST` flag at build time, e.g. by adding the line 123 124``` 125EXTERNAL_DEFINES += ENABLE_ENTROPY_COLLECTOR_TEST=1 126``` 127 128to `local.mk`. Currently, there's also a build-time constant, 129`ENTROPY_COLLECTOR_TEST_MAXLEN`, which (if provided) is the size of the 130statically allocated buffer. The default value if unspecified is 1MiB. 131 132### Boot-time tests: configuring 133 134The boot-time tests are controlled via kernel cmdlines. The relevant cmdlines 135are `kernel.entropy-test.*`, documented in 136[kernel\_cmdline.md](kernel_cmdline.md). 137 138Some entropy sources, notably jitterentropy, have parameter values that can be 139tweaked via kernel cmdline. Again, see [kernel\_cmdline.md](kernel_cmdline.md) 140for further details. 141 142### Boot-time tests: running 143 144The boot-time tests will run automatically during boot, as long as the correct 145kernel cmdlines are passed (if there are problems with the cmdlines, error 146messages will be printed instead). The tests run just before the first stage of 147RNG seeding, which happens at LK\_INIT\_LEVEL\_PLATFORM\_EARLY, shortly before 148the heap the VMM are brought up. If running a large test, boot will often slow 149down noticeably. For example, collecting 128kB of data from jitterentropy on 150rpi3 can take around a minute, depending on the parameter values. 151 152## Run-time tests 153 154*TODO(SEC-29): discuss actual user-mode test process* 155 156*Current rough ideas: only the kernel can trigger hwrng reads. To test, 157userspace issues a kernel command (e.g. `k hwrng test`), with some arguments to 158specify the test source and length. The kernel collects random bytes into the 159existing VMO-backed pseudo-file at `/boot/kernel/debug/entropy.bin`, assuming 160that this is safely writeable. Currently unimplemented; blocked by lack of a 161userspace HWRNG driver. Can test the VMO-rewriting mechanism first.* 162 163## Test data export 164 165Test data is saved in `/boot/kernel/debug/entropy.bin` in the Zircon system 166under test. So far I've usually exported the data file manually via `netcp`. 167Other options include `scp` if you build with the correct Fuchsia packages, or 168saving to persistent storage (probably using the Fuchsia `thinfs` FAT 169filesystem, so you can read the files on a non-Zircon computer). 170 171## Running the NIST test suite 172 173*Note: the NIST tests aren't actually mirrored in Fuchsia yet. Today, you need 174to clone the tests from the repo at 175<https://github.com/usnistgov/SP800-90B_EntropyAssessment>.* 176 177The NIST test suite has three entry points (as of the version committed on Oct. 17825, 2016): `iid_main.py`, `noniid_main.py`, and `restart.py`. The two "main" 179scripts perform the bulk of the work. The `iid_main.py` script is meant for 180entropy sources that produce independent, identically distributed data samples. 181Most of the testing is to validate the iid condition. Many entropy sources will 182not be iid, so the `noniid_main.py` test implements several entropy estimators 183that don't require iid data. 184 185Note that the test binaries from the NIST repo are Python scripts without a 186shebang line, so you probably need to explicitly call `python` on the command 187line when invoking them. 188 189The first two scripts take two arguments, both mandatory: the data file to read, 190and the number of significant bits per sample (if less than 8, only the low `N` 191bits will be used from each byte). They optionally accept a `-v` flag to produce 192verbose output or `-h` for help. 193 194The `noniid_main.py` also optionally accepts a `-u <int>` flag that can reduce 195the number of bits below the `N` value passed in the second mandatory argument. 196I'm not entirely sure why this flag is provided; it seems functionally 197redundant, but passing it does change the verbose output slightly. My best guess 198is that this is provided because the noniid Markov test only works on samples of 199at most 6 bits, so 7- or 8-bit datasets will be reduced to their low 6 bits for 200this test. In contrast, all the iid tests can run on 8-bit samples. 201 202A sample invocation of the `iid_main.py` script: 203 204``` 205python2 -- $FUCHSIA_DIR/third_party/sp800-90b-entropy-assessment/iid_main.py -v /path/to/datafile.bin 8 206``` 207 208The `restart.py` script takes the same two arguments, plus a third argument: the 209min-entropy estimate returned by a previous run of `iid_main.py` or 210`noniid_main.py`. This document doesn't describe restart tests. For now, see 211NIST SP800-90B for more details. 212 213## Future directions 214 215### Automation 216 217It would be nice to automate the process of building, configuring, and running a 218quality test. As a first step, it should be easy to write a shell script to 219perform these steps. Even better would be to use the testing infrastructure to 220run entropy collector quality tests this automatically, mostly to reduce bit-rot 221in the test code. Failing automation, we have to rely on humans to periodically 222run the tests (or to fix the tests when they break). 223