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