mirror of
https://github.com/henrydcase/pqc.git
synced 2024-11-22 23:48:58 +00:00
458 lines
14 KiB
C
458 lines
14 KiB
C
/* 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;
|
||
}
|