|
- /* Jitter RNG: Health Tests
- *
- * Copyright (C) 2021 - 2022, Joshua E. Hill <josh@keypair.us>
- * Copyright (C) 2021 - 2022, Stephan Mueller <smueller@chronox.de>
- *
- * License: see LICENSE file in root directory
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
- * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
- * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
- * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- */
-
- #include "jitterentropy-health.h"
-
- static jent_fips_failure_cb fips_cb = NULL;
- static int jent_health_cb_switch_blocked = 0;
-
- void jent_health_cb_block_switch(void)
- {
- jent_health_cb_switch_blocked = 1;
- }
-
- int jent_set_fips_failure_callback_internal(jent_fips_failure_cb cb)
- {
- if (jent_health_cb_switch_blocked)
- return -EAGAIN;
- fips_cb = cb;
- return 0;
- }
-
- /***************************************************************************
- * Lag Predictor Test
- *
- * This test is a vendor-defined conditional test that is designed to detect
- * a known failure mode where the result becomes mostly deterministic
- * Note that (lag_observations & JENT_LAG_MASK) is the index where the next
- * value provided will be stored.
- ***************************************************************************/
-
- #ifdef JENT_HEALTH_LAG_PREDICTOR
-
- /*
- * These cutoffs are configured using an entropy estimate of 1/osr under an
- * alpha=2^(-22) for a window size of 131072. The other health tests use
- * alpha=2^-30, but operate on much smaller window sizes. This larger selection
- * of alpha makes the behavior per-lag-window similar to the APT test.
- *
- * The global cutoffs are calculated using the
- * InverseBinomialCDF(n=(JENT_LAG_WINDOW_SIZE-JENT_LAG_HISTORY_SIZE), p=2^(-1/osr); 1-alpha)
- * The local cutoffs are somewhat more complicated. For background, see Feller's
- * _Introduction to Probability Theory and It's Applications_ Vol. 1,
- * Chapter 13, section 7 (in particular see equation 7.11, where x is a root
- * of the denominator of equation 7.6).
- *
- * We'll proceed using the notation of SP 800-90B Section 6.3.8 (which is
- * developed in Kelsey-McKay-Turan paper "Predictive Models for Min-entropy
- * Estimation".)
- *
- * Here, we set p=2^(-1/osr), seeking a run of successful guesses (r) with
- * probability of less than (1-alpha). That is, it is very very likely
- * (probability 1-alpha) that there is _no_ run of length r in a block of size
- * JENT_LAG_WINDOW_SIZE-JENT_LAG_HISTORY_SIZE.
- *
- * We have to iteratively look for an appropriate value for the cutoff r.
- */
- static const unsigned int jent_lag_global_cutoff_lookup[20] =
- { 66443, 93504, 104761, 110875, 114707, 117330, 119237, 120686, 121823,
- 122739, 123493, 124124, 124660, 125120, 125520, 125871, 126181, 126457,
- 126704, 126926 };
- static const unsigned int jent_lag_local_cutoff_lookup[20] =
- { 38, 75, 111, 146, 181, 215, 250, 284, 318, 351,
- 385, 419, 452, 485, 518, 551, 584, 617, 650, 683 };
-
- void jent_lag_init(struct rand_data *ec, unsigned int osr)
- {
- /*
- * Establish the lag global and local cutoffs based on the presumed
- * entropy rate of 1/osr.
- */
- if (osr > ARRAY_SIZE(jent_lag_global_cutoff_lookup)) {
- ec->lag_global_cutoff =
- jent_lag_global_cutoff_lookup[
- ARRAY_SIZE(jent_lag_global_cutoff_lookup) - 1];
- } else {
- ec->lag_global_cutoff = jent_lag_global_cutoff_lookup[osr - 1];
- }
-
- if (osr > ARRAY_SIZE(jent_lag_local_cutoff_lookup)) {
- ec->lag_local_cutoff =
- jent_lag_local_cutoff_lookup[
- ARRAY_SIZE(jent_lag_local_cutoff_lookup) - 1];
- } else {
- ec->lag_local_cutoff = jent_lag_local_cutoff_lookup[osr - 1];
- }
- }
-
- /**
- * Reset the lag counters
- *
- * @ec [in] Reference to entropy collector
- */
- static void jent_lag_reset(struct rand_data *ec)
- {
- unsigned int i;
-
- /* Reset Lag counters */
- ec->lag_prediction_success_count = 0;
- ec->lag_prediction_success_run = 0;
- ec->lag_best_predictor = 0; /* The first guess is basically arbitrary. */
- ec->lag_observations = 0;
-
- for (i = 0; i < JENT_LAG_HISTORY_SIZE; i++) {
- ec->lag_scoreboard[i] = 0;
- ec->lag_delta_history[i] = 0;
- }
- }
-
- /*
- * A macro for accessing the history. Index 0 is the last observed symbol
- * index 1 is the symbol observed two inputs ago, etc.
- */
- #define JENT_LAG_HISTORY(EC,LOC) \
- ((EC)->lag_delta_history[((EC)->lag_observations - (LOC) - 1) & \
- JENT_LAG_MASK])
-
- /**
- * Insert a new entropy event into the lag predictor test
- *
- * @ec [in] Reference to entropy collector
- * @current_delta [in] Current time delta
- */
- static void jent_lag_insert(struct rand_data *ec, uint64_t current_delta)
- {
- uint64_t prediction;
- unsigned int i;
-
- /* Initialize the delta_history */
- if (ec->lag_observations < JENT_LAG_HISTORY_SIZE) {
- ec->lag_delta_history[ec->lag_observations] = current_delta;
- ec->lag_observations++;
- return;
- }
-
- /*
- * The history is initialized. First make a guess and examine the
- * results.
- */
- prediction = JENT_LAG_HISTORY(ec, ec->lag_best_predictor);
-
- if (prediction == current_delta) {
- /* The prediction was correct. */
- ec->lag_prediction_success_count++;
- ec->lag_prediction_success_run++;
-
- if ((ec->lag_prediction_success_run >= ec->lag_local_cutoff) ||
- (ec->lag_prediction_success_count >= ec->lag_global_cutoff))
- ec->health_failure |= JENT_LAG_FAILURE;
- } else {
- /* The prediction wasn't correct. End any run of successes.*/
- ec->lag_prediction_success_run = 0;
- }
-
- /* Now update the predictors using the current data. */
- for (i = 0; i < JENT_LAG_HISTORY_SIZE; i++) {
- if (JENT_LAG_HISTORY(ec, i) == current_delta) {
- /*
- * The ith predictor (which guesses i + 1 symbols in
- * the past) successfully guessed.
- */
- ec->lag_scoreboard[i] ++;
-
- /*
- * Keep track of the best predictor (tie goes to the
- * shortest lag)
- */
- if (ec->lag_scoreboard[i] >
- ec->lag_scoreboard[ec->lag_best_predictor])
- ec->lag_best_predictor = i;
- }
- }
-
- /*
- * Finally, update the lag_delta_history array with the newly input
- * value.
- */
- ec->lag_delta_history[(ec->lag_observations) & JENT_LAG_MASK] =
- current_delta;
- ec->lag_observations++;
-
- /*
- * lag_best_predictor now is the index of the predictor with the largest
- * number of correct guesses.
- * This establishes our next guess.
- */
-
- /* Do we now need a new window? */
- if (ec->lag_observations >= JENT_LAG_WINDOW_SIZE)
- jent_lag_reset(ec);
- }
-
- static inline uint64_t jent_delta2(struct rand_data *ec, uint64_t current_delta)
- {
- /* Note that delta2_n = delta_n - delta_{n-1} */
- return jent_delta(JENT_LAG_HISTORY(ec, 0), current_delta);
- }
-
- static inline uint64_t jent_delta3(struct rand_data *ec, uint64_t delta2)
- {
- /*
- * Note that delta3_n = delta2_n - delta2_{n-1}
- * = delta2_n - (delta_{n-1} - delta_{n-2})
- */
- return jent_delta(jent_delta(JENT_LAG_HISTORY(ec, 1),
- JENT_LAG_HISTORY(ec, 0)), delta2);
- }
-
- #else /* JENT_HEALTH_LAG_PREDICTOR */
-
- static inline void jent_lag_insert(struct rand_data *ec, uint64_t current_delta)
- {
- (void)ec;
- (void)current_delta;
- }
-
- static inline uint64_t jent_delta2(struct rand_data *ec, uint64_t current_delta)
- {
- uint64_t delta2 = jent_delta(ec->last_delta, current_delta);
-
- ec->last_delta = current_delta;
- return delta2;
- }
-
- static inline uint64_t jent_delta3(struct rand_data *ec, uint64_t delta2)
- {
- uint64_t delta3 = jent_delta(ec->last_delta2, delta2);
-
- ec->last_delta2 = delta2;
- return delta3;
- }
-
- #endif /* JENT_HEALTH_LAG_PREDICTOR */
-
- /***************************************************************************
- * Adaptive Proportion Test
- *
- * This test complies with SP800-90B section 4.4.2.
- ***************************************************************************/
-
- /*
- * See the SP 800-90B comment #10b for the corrected cutoff for the SP 800-90B
- * APT.
- * http://www.untruth.org/~josh/sp80090b/UL%20SP800-90B-final%20comments%20v1.9%2020191212.pdf
- * In in the syntax of R, this is C = 2 + qbinom(1 − 2^(−30), 511, 2^(-1/osr)).
- * (The original formula wasn't correct because the first symbol must
- * necessarily have been observed, so there is no chance of observing 0 of these
- * symbols.)
- *
- * For any value above 14, this yields the maximal allowable value of 512
- * (by FIPS 140-2 IG 7.19 Resolution # 16, we cannot choose a cutoff value that
- * renders the test unable to fail).
- */
- static const unsigned int jent_apt_cutoff_lookup[15]=
- { 325, 422, 459, 477, 488, 494, 499, 502,
- 505, 507, 508, 509, 510, 511, 512 };
-
- void jent_apt_init(struct rand_data *ec, unsigned int osr)
- {
- /*
- * Establish the apt_cutoff based on the presumed entropy rate of
- * 1/osr.
- */
- if (osr >= ARRAY_SIZE(jent_apt_cutoff_lookup)) {
- ec->apt_cutoff = jent_apt_cutoff_lookup[
- ARRAY_SIZE(jent_apt_cutoff_lookup) - 1];
- } else {
- ec->apt_cutoff = jent_apt_cutoff_lookup[osr - 1];
- }
- }
-
- /**
- * Reset the APT counter
- *
- * @ec [in] Reference to entropy collector
- */
- static void jent_apt_reset(struct rand_data *ec)
- {
- /* When reset, accept the _next_ value input as the new base. */
- ec->apt_base_set = 0;
- }
-
- /**
- * Insert a new entropy event into APT
- *
- * @ec [in] Reference to entropy collector
- * @current_delta [in] Current time delta
- */
- static void jent_apt_insert(struct rand_data *ec, uint64_t current_delta)
- {
- /* Initialize the base reference */
- if (!ec->apt_base_set) {
- ec->apt_base = current_delta; /* APT Step 1 */
- ec->apt_base_set = 1; /* APT Step 2 */
-
- /*
- * Reset APT counter
- * Note that we've taken in the first symbol in the window.
- */
- ec->apt_count = 1; /* B = 1 */
- ec->apt_observations = 1;
-
- return;
- }
-
- if (current_delta == ec->apt_base) {
- ec->apt_count++; /* B = B + 1 */
-
- /* Note, ec->apt_count starts with one. */
- if (ec->apt_count >= ec->apt_cutoff)
- ec->health_failure |= JENT_APT_FAILURE;
- }
-
- ec->apt_observations++;
-
- /* Completed one window, the next symbol input will be new apt_base. */
- if (ec->apt_observations >= JENT_APT_WINDOW_SIZE)
- jent_apt_reset(ec); /* APT Step 4 */
- }
-
- /***************************************************************************
- * Stuck Test and its use as Repetition Count Test
- *
- * The Jitter RNG uses an enhanced version of the Repetition Count Test
- * (RCT) specified in SP800-90B section 4.4.1. Instead of counting identical
- * back-to-back values, the input to the RCT is the counting of the stuck
- * values during the generation of one Jitter RNG output block.
- *
- * The RCT is applied with an alpha of 2^{-30} compliant to FIPS 140-2 IG 9.8.
- *
- * During the counting operation, the Jitter RNG always calculates the RCT
- * cut-off value of C. If that value exceeds the allowed cut-off value,
- * the Jitter RNG output block will be calculated completely but discarded at
- * the end. The caller of the Jitter RNG is informed with an error code.
- ***************************************************************************/
-
- /**
- * Repetition Count Test as defined in SP800-90B section 4.4.1
- *
- * @ec [in] Reference to entropy collector
- * @stuck [in] Indicator whether the value is stuck
- */
- static void jent_rct_insert(struct rand_data *ec, int stuck)
- {
- /*
- * If we have a count less than zero, a previous RCT round identified
- * a failure. We will not overwrite it.
- */
- if (ec->rct_count < 0)
- return;
-
- if (stuck) {
- ec->rct_count++;
-
- /*
- * The cutoff value is based on the following consideration:
- * alpha = 2^-30 as recommended in FIPS 140-2 IG 9.8.
- * In addition, we require an entropy value H of 1/osr as this
- * is the minimum entropy required to provide full entropy.
- * Note, we collect (DATA_SIZE_BITS + ENTROPY_SAFETY_FACTOR)*osr
- * deltas for inserting them into the entropy pool which should
- * then have (close to) DATA_SIZE_BITS bits of entropy in the
- * conditioned output.
- *
- * Note, ec->rct_count (which equals to value B in the pseudo
- * code of SP800-90B section 4.4.1) starts with zero. Hence
- * we need to subtract one from the cutoff value as calculated
- * following SP800-90B. Thus C = ceil(-log_2(alpha)/H) = 30*osr.
- */
- if ((unsigned int)ec->rct_count >= (30 * ec->osr)) {
- ec->rct_count = -1;
- ec->health_failure |= JENT_RCT_FAILURE;
- }
- } else {
- ec->rct_count = 0;
- }
- }
-
- /**
- * Stuck test by checking the:
- * 1st derivative of the jitter measurement (time delta)
- * 2nd derivative of the jitter measurement (delta of time deltas)
- * 3rd derivative of the jitter measurement (delta of delta of time deltas)
- *
- * All values must always be non-zero.
- *
- * @ec [in] Reference to entropy collector
- * @current_delta [in] Jitter time delta
- *
- * @return
- * 0 jitter measurement not stuck (good bit)
- * 1 jitter measurement stuck (reject bit)
- */
- unsigned int jent_stuck(struct rand_data *ec, uint64_t current_delta)
- {
- uint64_t delta2 = jent_delta2(ec, current_delta);
- uint64_t delta3 = jent_delta3(ec, delta2);
-
- /*
- * Insert the result of the comparison of two back-to-back time
- * deltas.
- */
- jent_apt_insert(ec, current_delta);
- jent_lag_insert(ec, current_delta);
-
- if (!current_delta || !delta2 || !delta3) {
- /* RCT with a stuck bit */
- jent_rct_insert(ec, 1);
- return 1;
- }
-
- /* RCT with a non-stuck bit */
- jent_rct_insert(ec, 0);
-
- return 0;
- }
-
- /**
- * Report any health test failures
- *
- * @ec [in] Reference to entropy collector
- *
- * @return a bitmask indicating which tests failed
- * 0 No health test failure
- * 1 RCT failure
- * 2 APT failure
- * 4 Lag predictor test failure
- */
- unsigned int jent_health_failure(struct rand_data *ec)
- {
- /* Test is only enabled in FIPS mode */
- if (!ec->fips_enabled)
- return 0;
-
- if (fips_cb && ec->health_failure) {
- fips_cb(ec, ec->health_failure);
- }
-
- return ec->health_failure;
- }
|