ddf7d0
# HG changeset patch
ddf7d0
# User Daiki Ueno <dueno@redhat.com>
ddf7d0
# Date 1561465415 -7200
ddf7d0
#      Tue Jun 25 14:23:35 2019 +0200
ddf7d0
# Node ID a0114e3d8b22d4c6ee77504c483a0fe0037f4c71
ddf7d0
# Parent  313dfef345bd93bc67982249bffa2cfdd5a9d1b5
ddf7d0
Bug 1560329, drbg: perform continuous test on entropy source
ddf7d0
ddf7d0
Summary: FIPS 140-2 section 4.9.2 requires a conditional self test to check that consecutive entropy blocks from the system are different.  As neither getentropy() nor /dev/urandom provides that check on the output, this adds the self test at caller side.
ddf7d0
ddf7d0
Reviewers: rrelyea
ddf7d0
ddf7d0
Bug #: 1560329
ddf7d0
ddf7d0
Differential Revision: https://phabricator.services.mozilla.com/D35636
ddf7d0
ddf7d0
diff --git a/lib/freebl/drbg.c b/lib/freebl/drbg.c
ddf7d0
--- a/lib/freebl/drbg.c
ddf7d0
+++ b/lib/freebl/drbg.c
ddf7d0
@@ -30,6 +30,7 @@
ddf7d0
 #define PRNG_ADDITONAL_DATA_CACHE_SIZE (8 * 1024) /* must be less than          \
ddf7d0
                                                    *  PRNG_MAX_ADDITIONAL_BYTES \
ddf7d0
                                                    */
ddf7d0
+#define PRNG_ENTROPY_BLOCK_SIZE SHA256_LENGTH
ddf7d0
 
ddf7d0
 /* RESEED_COUNT is how many calls to the prng before we need to reseed
ddf7d0
  * under normal NIST rules, you must return an error. In the NSS case, we
ddf7d0
@@ -96,6 +97,8 @@ struct RNGContextStr {
ddf7d0
     PRUint32 additionalAvail;
ddf7d0
     PRBool isValid;   /* false if RNG reaches an invalid state */
ddf7d0
     PRBool isKatTest; /* true if running NIST PRNG KAT tests */
ddf7d0
+    /* for continuous entropy check */
ddf7d0
+    PRUint8 previousEntropyHash[SHA256_LENGTH];
ddf7d0
 };
ddf7d0
 
ddf7d0
 typedef struct RNGContextStr RNGContext;
ddf7d0
@@ -169,6 +172,82 @@ prng_instantiate(RNGContext *rng, const 
ddf7d0
     return SECSuccess;
ddf7d0
 }
ddf7d0
 
ddf7d0
+static PRCallOnceType coRNGInitEntropy;
ddf7d0
+
ddf7d0
+static PRStatus
ddf7d0
+prng_initEntropy(void)
ddf7d0
+{
ddf7d0
+    size_t length;
ddf7d0
+    PRUint8 block[PRNG_ENTROPY_BLOCK_SIZE];
ddf7d0
+    SHA256Context ctx;
ddf7d0
+
ddf7d0
+    /* For FIPS 140-2 4.9.2 continuous random number generator test,
ddf7d0
+     * fetch the initial entropy from the system RNG and keep it for
ddf7d0
+     * later comparison. */
ddf7d0
+    length = RNG_SystemRNG(block, sizeof(block));
ddf7d0
+    if (length == 0) {
ddf7d0
+        return PR_FAILURE; /* error is already set */
ddf7d0
+    }
ddf7d0
+    PORT_Assert(length == sizeof(block));
ddf7d0
+
ddf7d0
+    /* Store the hash of the entropy block rather than the block
ddf7d0
+     * itself for backward secrecy. */
ddf7d0
+    SHA256_Begin(&ctx;;
ddf7d0
+    SHA256_Update(&ctx, block, sizeof(block));
ddf7d0
+    SHA256_End(&ctx, globalrng->previousEntropyHash, NULL,
ddf7d0
+               sizeof(globalrng->previousEntropyHash));
ddf7d0
+    PORT_Memset(block, 0, sizeof(block));
ddf7d0
+    return PR_SUCCESS;
ddf7d0
+}
ddf7d0
+
ddf7d0
+static SECStatus
ddf7d0
+prng_getEntropy(PRUint8 *buffer, size_t requestLength)
ddf7d0
+{
ddf7d0
+    size_t total = 0;
ddf7d0
+    PRUint8 block[PRNG_ENTROPY_BLOCK_SIZE];
ddf7d0
+    PRUint8 hash[SHA256_LENGTH];
ddf7d0
+    SHA256Context ctx;
ddf7d0
+    SECStatus rv = SECSuccess;
ddf7d0
+
ddf7d0
+    if (PR_CallOnce(&coRNGInitEntropy, prng_initEntropy) != PR_SUCCESS) {
ddf7d0
+        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
ddf7d0
+        return SECFailure;
ddf7d0
+    }
ddf7d0
+
ddf7d0
+    /* For FIPS 140-2 4.9.2 continuous random generator test,
ddf7d0
+     * iteratively fetch fixed sized blocks from the system and
ddf7d0
+     * compare consecutive blocks. */
ddf7d0
+    while (total < requestLength) {
ddf7d0
+        size_t length = RNG_SystemRNG(block, sizeof(block));
ddf7d0
+        if (length == 0) {
ddf7d0
+            rv = SECFailure; /* error is already set */
ddf7d0
+            goto out;
ddf7d0
+        }
ddf7d0
+        PORT_Assert(length == sizeof(block));
ddf7d0
+
ddf7d0
+        /* Store the hash of the entropy block rather than the block
ddf7d0
+         * itself for backward secrecy. */
ddf7d0
+        SHA256_Begin(&ctx;;
ddf7d0
+        SHA256_Update(&ctx, block, sizeof(block));
ddf7d0
+        SHA256_End(&ctx, hash, NULL, sizeof(hash));
ddf7d0
+
ddf7d0
+        if (PORT_Memcmp(globalrng->previousEntropyHash, hash, sizeof(hash)) == 0) {
ddf7d0
+            PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
ddf7d0
+            rv = SECFailure;
ddf7d0
+            goto out;
ddf7d0
+        }
ddf7d0
+        PORT_Memcpy(globalrng->previousEntropyHash, hash, sizeof(hash));
ddf7d0
+        length = PR_MIN(requestLength - total, sizeof(block));
ddf7d0
+        PORT_Memcpy(buffer, block, length);
ddf7d0
+        total += length;
ddf7d0
+        buffer += length;
ddf7d0
+    }
ddf7d0
+
ddf7d0
+  out:
ddf7d0
+    PORT_Memset(block, 0, sizeof block);
ddf7d0
+    return rv;
ddf7d0
+}
ddf7d0
+
ddf7d0
 /*
ddf7d0
  * Update the global random number generator with more seeding
ddf7d0
  * material. Use the Hash_DRBG reseed algorithm from NIST SP-800-90
ddf7d0
@@ -182,11 +261,15 @@ prng_reseed(RNGContext *rng, const PRUin
ddf7d0
 {
ddf7d0
     PRUint8 noiseData[(sizeof rng->V_Data) + PRNG_SEEDLEN];
ddf7d0
     PRUint8 *noise = &noiseData[0];
ddf7d0
+    SECStatus rv;
ddf7d0
 
ddf7d0
     /* if entropy wasn't supplied, fetch it. (normal operation case) */
ddf7d0
     if (entropy == NULL) {
ddf7d0
-        entropy_len = (unsigned int)RNG_SystemRNG(
ddf7d0
-            &noiseData[sizeof rng->V_Data], PRNG_SEEDLEN);
ddf7d0
+        entropy_len = PRNG_SEEDLEN;
ddf7d0
+        rv = prng_getEntropy(&noiseData[sizeof rng->V_Data], entropy_len);
ddf7d0
+        if (rv != SECSuccess) {
ddf7d0
+            return SECFailure; /* error is already set */
ddf7d0
+        }
ddf7d0
     } else {
ddf7d0
         /* NOTE: this code is only available for testing, not to applications */
ddf7d0
         /* if entropy was too big for the stack variable, get it from malloc */
ddf7d0
@@ -384,7 +467,6 @@ static PRStatus
ddf7d0
 rng_init(void)
ddf7d0
 {
ddf7d0
     PRUint8 bytes[PRNG_SEEDLEN * 2]; /* entropy + nonce */
ddf7d0
-    unsigned int numBytes;
ddf7d0
     SECStatus rv = SECSuccess;
ddf7d0
 
ddf7d0
     if (globalrng == NULL) {
ddf7d0
@@ -403,18 +485,17 @@ rng_init(void)
ddf7d0
         }
ddf7d0
 
ddf7d0
         /* Try to get some seed data for the RNG */
ddf7d0
-        numBytes = (unsigned int)RNG_SystemRNG(bytes, sizeof bytes);
ddf7d0
-        PORT_Assert(numBytes == 0 || numBytes == sizeof bytes);
ddf7d0
-        if (numBytes != 0) {
ddf7d0
+        rv = prng_getEntropy(bytes, sizeof bytes);
ddf7d0
+        if (rv == SECSuccess) {
ddf7d0
             /* if this is our first call,  instantiate, otherwise reseed
ddf7d0
              * prng_instantiate gets a new clean state, we want to mix
ddf7d0
              * any previous entropy we may have collected */
ddf7d0
             if (V(globalrng)[0] == 0) {
ddf7d0
-                rv = prng_instantiate(globalrng, bytes, numBytes);
ddf7d0
+                rv = prng_instantiate(globalrng, bytes, sizeof bytes);
ddf7d0
             } else {
ddf7d0
-                rv = prng_reseed_test(globalrng, bytes, numBytes, NULL, 0);
ddf7d0
+                rv = prng_reseed_test(globalrng, bytes, sizeof bytes, NULL, 0);
ddf7d0
             }
ddf7d0
-            memset(bytes, 0, numBytes);
ddf7d0
+            memset(bytes, 0, sizeof bytes);
ddf7d0
         } else {
ddf7d0
             PZ_DestroyLock(globalrng->lock);
ddf7d0
             globalrng->lock = NULL;