crypto: drbg - add FIPS 140-2 CTRNG for noise source
authorStephan Mueller <smueller@chronox.de>
Wed, 8 May 2019 14:19:24 +0000 (16:19 +0200)
committerHerbert Xu <herbert@gondor.apana.org.au>
Thu, 23 May 2019 06:01:06 +0000 (14:01 +0800)
FIPS 140-2 section 4.9.2 requires a continuous self test of the noise
source. Up to kernel 4.8 drivers/char/random.c provided this continuous
self test. Afterwards it was moved to a location that is inconsistent
with the FIPS 140-2 requirements. The relevant patch was
e192be9d9a30555aae2ca1dc3aad37cba484cd4a .

Thus, the FIPS 140-2 CTRNG is added to the DRBG when it obtains the
seed. This patch resurrects the function drbg_fips_continous_test that
existed some time ago and applies it to the noise sources. The patch
that removed the drbg_fips_continous_test was
b3614763059b82c26bdd02ffcb1c016c1132aad0 .

The Jitter RNG implements its own FIPS 140-2 self test and thus does not
need to be subjected to the test in the DRBG.

The patch contains a tiny fix to ensure proper zeroization in case of an
error during the Jitter RNG data gathering.

Signed-off-by: Stephan Mueller <smueller@chronox.de>
Reviewed-by: Yann Droneaud <ydroneaud@opteya.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
crypto/drbg.c
include/crypto/drbg.h

index 2a5b16bb000c384e90c7de620efd71db35995324..b6929eb5f565d4b6450f08e3d4d88f1d18cfca77 100644 (file)
@@ -219,6 +219,57 @@ static inline unsigned short drbg_sec_strength(drbg_flag_t flags)
        }
 }
 
+/*
+ * FIPS 140-2 continuous self test for the noise source
+ * The test is performed on the noise source input data. Thus, the function
+ * implicitly knows the size of the buffer to be equal to the security
+ * strength.
+ *
+ * Note, this function disregards the nonce trailing the entropy data during
+ * initial seeding.
+ *
+ * drbg->drbg_mutex must have been taken.
+ *
+ * @drbg DRBG handle
+ * @entropy buffer of seed data to be checked
+ *
+ * return:
+ *     0 on success
+ *     -EAGAIN on when the CTRNG is not yet primed
+ *     < 0 on error
+ */
+static int drbg_fips_continuous_test(struct drbg_state *drbg,
+                                    const unsigned char *entropy)
+{
+       unsigned short entropylen = drbg_sec_strength(drbg->core->flags);
+       int ret = 0;
+
+       if (!IS_ENABLED(CONFIG_CRYPTO_FIPS))
+               return 0;
+
+       /* skip test if we test the overall system */
+       if (list_empty(&drbg->test_data.list))
+               return 0;
+       /* only perform test in FIPS mode */
+       if (!fips_enabled)
+               return 0;
+
+       if (!drbg->fips_primed) {
+               /* Priming of FIPS test */
+               memcpy(drbg->prev, entropy, entropylen);
+               drbg->fips_primed = true;
+               /* priming: another round is needed */
+               return -EAGAIN;
+       }
+       ret = memcmp(drbg->prev, entropy, entropylen);
+       if (!ret)
+               panic("DRBG continuous self test failed\n");
+       memcpy(drbg->prev, entropy, entropylen);
+
+       /* the test shall pass when the two values are not equal */
+       return 0;
+}
+
 /*
  * Convert an integer into a byte representation of this integer.
  * The byte representation is big-endian
@@ -998,6 +1049,22 @@ static inline int __drbg_seed(struct drbg_state *drbg, struct list_head *seed,
        return ret;
 }
 
+static inline int drbg_get_random_bytes(struct drbg_state *drbg,
+                                       unsigned char *entropy,
+                                       unsigned int entropylen)
+{
+       int ret;
+
+       do {
+               get_random_bytes(entropy, entropylen);
+               ret = drbg_fips_continuous_test(drbg, entropy);
+               if (ret && ret != -EAGAIN)
+                       return ret;
+       } while (ret);
+
+       return 0;
+}
+
 static void drbg_async_seed(struct work_struct *work)
 {
        struct drbg_string data;
@@ -1006,16 +1073,20 @@ static void drbg_async_seed(struct work_struct *work)
                                               seed_work);
        unsigned int entropylen = drbg_sec_strength(drbg->core->flags);
        unsigned char entropy[32];
+       int ret;
 
        BUG_ON(!entropylen);
        BUG_ON(entropylen > sizeof(entropy));
-       get_random_bytes(entropy, entropylen);
 
        drbg_string_fill(&data, entropy, entropylen);
        list_add_tail(&data.list, &seedlist);
 
        mutex_lock(&drbg->drbg_mutex);
 
+       ret = drbg_get_random_bytes(drbg, entropy, entropylen);
+       if (ret)
+               goto unlock;
+
        /* If nonblocking pool is initialized, deactivate Jitter RNG */
        crypto_free_rng(drbg->jent);
        drbg->jent = NULL;
@@ -1030,6 +1101,7 @@ static void drbg_async_seed(struct work_struct *work)
        if (drbg->seeded)
                drbg->reseed_threshold = drbg_max_requests(drbg);
 
+unlock:
        mutex_unlock(&drbg->drbg_mutex);
 
        memzero_explicit(entropy, entropylen);
@@ -1081,7 +1153,9 @@ static int drbg_seed(struct drbg_state *drbg, struct drbg_string *pers,
                BUG_ON((entropylen * 2) > sizeof(entropy));
 
                /* Get seed from in-kernel /dev/urandom */
-               get_random_bytes(entropy, entropylen);
+               ret = drbg_get_random_bytes(drbg, entropy, entropylen);
+               if (ret)
+                       goto out;
 
                if (!drbg->jent) {
                        drbg_string_fill(&data1, entropy, entropylen);
@@ -1094,7 +1168,7 @@ static int drbg_seed(struct drbg_state *drbg, struct drbg_string *pers,
                                                   entropylen);
                        if (ret) {
                                pr_devel("DRBG: jent failed with %d\n", ret);
-                               return ret;
+                               goto out;
                        }
 
                        drbg_string_fill(&data1, entropy, entropylen * 2);
@@ -1121,6 +1195,7 @@ static int drbg_seed(struct drbg_state *drbg, struct drbg_string *pers,
 
        ret = __drbg_seed(drbg, &seedlist, reseed);
 
+out:
        memzero_explicit(entropy, entropylen * 2);
 
        return ret;
@@ -1142,6 +1217,11 @@ static inline void drbg_dealloc_state(struct drbg_state *drbg)
        drbg->reseed_ctr = 0;
        drbg->d_ops = NULL;
        drbg->core = NULL;
+       if (IS_ENABLED(CONFIG_CRYPTO_FIPS)) {
+               kzfree(drbg->prev);
+               drbg->prev = NULL;
+               drbg->fips_primed = false;
+       }
 }
 
 /*
@@ -1211,6 +1291,14 @@ static inline int drbg_alloc_state(struct drbg_state *drbg)
                drbg->scratchpad = PTR_ALIGN(drbg->scratchpadbuf, ret + 1);
        }
 
+       if (IS_ENABLED(CONFIG_CRYPTO_FIPS)) {
+               drbg->prev = kzalloc(drbg_sec_strength(drbg->core->flags),
+                                    GFP_KERNEL);
+               if (!drbg->prev)
+                       goto fini;
+               drbg->fips_primed = false;
+       }
+
        return 0;
 
 fini:
index 3fb581bf3b875b8ee90874f5caec1d2fb0d2c1d2..8c9af21efce1d94c39bdd1c4358f861f15a53843 100644 (file)
@@ -129,6 +129,8 @@ struct drbg_state {
 
        bool seeded;            /* DRBG fully seeded? */
        bool pr;                /* Prediction resistance enabled? */
+       bool fips_primed;       /* Continuous test primed? */
+       unsigned char *prev;    /* FIPS 140-2 continuous test value */
        struct work_struct seed_work;   /* asynchronous seeding support */
        struct crypto_rng *jent;
        const struct drbg_state_ops *d_ops;