Implement automatic reseeding of DRBG after a specified time interval
[openssl.git] / crypto / rand / drbg_lib.c
index eef5e11cc5986f6949699421278f9bf055a3b3b3..13dce3057cdc6e89834de44767cee0e5f11bf346 100644 (file)
 #include "internal/thread_once.h"
 #include "internal/rand_int.h"
 
-static RAND_DRBG rand_drbg; /* The default global DRBG. */
-static RAND_DRBG priv_drbg; /* The global private-key DRBG. */
-
-/* NIST SP 800-90A DRBG recommends the use of a personalization string. */
-static const char ossl_pers_string[] = "OpenSSL NIST SP 800-90A DRBG";
-
 /*
  * Support framework for NIST SP 800-90A DRBG, AES-CTR mode.
  * The RAND_DRBG is OpenSSL's pointer to an instance of the DRBG.
@@ -33,9 +27,78 @@ static const char ossl_pers_string[] = "OpenSSL NIST SP 800-90A DRBG";
  * a much bigger deal than just re-setting an allocated resource.)
  */
 
+/*
+ * THE THREE SHARED DRBGs
+ *
+ * There are three shared DRBGs (master, public and private), which are
+ * accessed concurrently by all threads.
+ *
+ * THE MASTER DRBG
+ *
+ * Not used directly by the application, only for reseeding the two other
+ * DRBGs. It reseeds itself by pulling either randomness from os entropy
+ * sources or by consuming randomnes which was added by RAND_add()
+ */
+static RAND_DRBG drbg_master;
+/*
+ * THE PUBLIC DRBG
+ *
+ * Used by default for generating random bytes using RAND_bytes().
+ */
+static RAND_DRBG drbg_public;
+/*
+ * THE PRIVATE DRBG
+ *
+ * Used by default for generating private keys using RAND_priv_bytes()
+ */
+static RAND_DRBG drbg_private;
+/*+
+ * DRBG HIERARCHY
+ *
+ * In addition there are DRBGs, which are not shared, but used only by a
+ * single thread at every time, for example the DRBGs which are owned by
+ * an SSL context. All DRBGs are organized in a hierarchical fashion
+ * with the <master> DRBG as root.
+ *
+ * This gives the following overall picture:
+ *
+ *                  <os entropy sources>
+ *                         |
+ *    RAND_add() ==>    <master>          \
+ *                       /   \            | shared DRBGs (with locking)
+ *                 <public>  <private>    /
+ *                     |
+ *                   <ssl>  owned by an SSL context
+ *
+ * AUTOMATIC RESEEDING
+ *
+ * Before satisfying a generate request, a DRBG reseeds itself automatically,
+ * if one of the following two conditions holds:
+ *
+ * - the number of generate requests since the last reseeding exceeds a
+ *   certain threshold, the so called |reseed_interval|. This behaviour
+ *   can be disabled by setting the |reseed_interval| to 0.
+ *
+ * - the time elapsed since the last reseeding exceeds a certain time
+ *   interval, the so called |reseed_time_interval|. This behaviour
+ *   can be disabled by setting the |reseed_time_interval| to 0.
+ *
+ * MANUAL RESEEDING
+ *
+ * For the three shared DRBGs (and only for these) there is another way to
+ * reseed them manually by calling RAND_seed() (or RAND_add() with a positive
+ * |randomness| argument). This will immediately reseed the <master> DRBG.
+ * The <public> and <private> DRBG will detect this on their next generate
+ * call and reseed, pulling randomness from <master>.
+ */
+
+
+/* NIST SP 800-90A DRBG recommends the use of a personalization string. */
+static const char ossl_pers_string[] = "OpenSSL NIST SP 800-90A DRBG";
+
 static CRYPTO_ONCE rand_drbg_init = CRYPTO_ONCE_STATIC_INIT;
 
-static int drbg_setup(RAND_DRBG *drbg, const char *name);
+static int drbg_setup(RAND_DRBG *drbg, const char *name, RAND_DRBG *parent);
 
 /*
  * Set/initialize |drbg| to be of type |nid|, with optional |flags|.
@@ -72,6 +135,8 @@ int RAND_DRBG_set(RAND_DRBG *drbg, int nid, unsigned int flags)
 /*
  * Allocate memory and initialize a new DRBG.  The |parent|, if not
  * NULL, will be used to auto-seed this RAND_DRBG as needed.
+ *
+ * Returns a pointer to the new DRBG instance on success, NULL on failure.
  */
 RAND_DRBG *RAND_DRBG_new(int type, unsigned int flags, RAND_DRBG *parent)
 {
@@ -86,12 +151,10 @@ RAND_DRBG *RAND_DRBG_new(int type, unsigned int flags, RAND_DRBG *parent)
     if (RAND_DRBG_set(drbg, type, flags) < 0)
         goto err;
 
-    if (parent != NULL) {
-        if (!RAND_DRBG_set_callbacks(drbg, rand_drbg_get_entropy,
-                                     rand_drbg_cleanup_entropy,
-                                     NULL, NULL))
-            goto err;
-    }
+    if (!RAND_DRBG_set_callbacks(drbg, rand_drbg_get_entropy,
+                                 rand_drbg_cleanup_entropy,
+                                 NULL, NULL))
+        goto err;
 
     return drbg;
 
@@ -116,6 +179,8 @@ void RAND_DRBG_free(RAND_DRBG *drbg)
 /*
  * Instantiate |drbg|, after it has been initialized.  Use |pers| and
  * |perslen| as prediction-resistance input.
+ *
+ * Requires that drbg->lock is already locked for write, if non-null.
  */
 int RAND_DRBG_instantiate(RAND_DRBG *drbg,
                           const unsigned char *pers, size_t perslen)
@@ -162,7 +227,14 @@ int RAND_DRBG_instantiate(RAND_DRBG *drbg,
     }
 
     drbg->state = DRBG_READY;
-    drbg->reseed_counter = 1;
+    drbg->generate_counter = 0;
+    drbg->reseed_time = time(NULL);
+    if (drbg->reseed_counter > 0) {
+        if (drbg->parent == NULL)
+            drbg->reseed_counter++;
+        else
+            drbg->reseed_counter = drbg->parent->reseed_counter;
+    }
 
 end:
     if (entropy != NULL && drbg->cleanup_entropy != NULL)
@@ -185,18 +257,21 @@ end:
 
 /*
  * Uninstantiate |drbg|. Must be instantiated before it can be used.
+ *
+ * Requires that drbg->lock is already locked for write, if non-null.
  */
 int RAND_DRBG_uninstantiate(RAND_DRBG *drbg)
 {
     int ret = ctr_uninstantiate(drbg);
 
-    OPENSSL_cleanse(&drbg->ctr, sizeof(drbg->ctr));
     drbg->state = DRBG_UNINITIALISED;
     return ret;
 }
 
 /*
  * Reseed |drbg|, mixing in the specified data
+ *
+ * Requires that drbg->lock is already locked for write, if non-null.
  */
 int RAND_DRBG_reseed(RAND_DRBG *drbg,
                      const unsigned char *adin, size_t adinlen)
@@ -232,8 +307,16 @@ int RAND_DRBG_reseed(RAND_DRBG *drbg,
 
     if (!ctr_reseed(drbg, entropy, entropylen, adin, adinlen))
         goto end;
+
     drbg->state = DRBG_READY;
-    drbg->reseed_counter = 1;
+    drbg->generate_counter = 0;
+    drbg->reseed_time = time(NULL);
+    if (drbg->reseed_counter > 0) {
+        if (drbg->parent == NULL)
+            drbg->reseed_counter++;
+        else
+            drbg->reseed_counter = drbg->parent->reseed_counter;
+    }
 
 end:
     if (entropy != NULL && drbg->cleanup_entropy != NULL)
@@ -304,12 +387,18 @@ int rand_drbg_restart(RAND_DRBG *drbg,
     }
 
     /* repair error state */
-    if (drbg->state == DRBG_ERROR)
+    if (drbg->state == DRBG_ERROR) {
         RAND_DRBG_uninstantiate(drbg);
+        /* The drbg->ctr member needs to be reinitialized before reinstantiation */
+        RAND_DRBG_set(drbg, drbg->nid, drbg->flags);
+    }
 
     /* repair uninitialized state */
     if (drbg->state == DRBG_UNINITIALISED) {
-        drbg_setup(drbg, NULL);
+        /* reinstantiate drbg */
+        RAND_DRBG_instantiate(drbg,
+                              (const unsigned char *) ossl_pers_string,
+                              sizeof(ossl_pers_string) - 1);
         /* already reseeded. prevent second reseeding below */
         reseeded = (drbg->state == DRBG_READY);
     }
@@ -349,6 +438,8 @@ int rand_drbg_restart(RAND_DRBG *drbg,
  * to or if |prediction_resistance| is set.  Additional input can be
  * sent in |adin| and |adinlen|.
  *
+ * Requires that drbg->lock is already locked for write, if non-null.
+ *
  * Returns 1 on success, 0 on failure.
  *
  */
@@ -356,6 +447,8 @@ int RAND_DRBG_generate(RAND_DRBG *drbg, unsigned char *out, size_t outlen,
                        int prediction_resistance,
                        const unsigned char *adin, size_t adinlen)
 {
+    int reseed_required = 0;
+
     if (drbg->state != DRBG_READY) {
         /* try to recover from previous errors */
         rand_drbg_restart(drbg, NULL, 0, 0);
@@ -381,13 +474,25 @@ int RAND_DRBG_generate(RAND_DRBG *drbg, unsigned char *out, size_t outlen,
 
     if (drbg->fork_count != rand_fork_count) {
         drbg->fork_count = rand_fork_count;
-        drbg->state = DRBG_RESEED;
+        reseed_required = 1;
     }
 
-    if (drbg->reseed_counter >= drbg->reseed_interval)
-        drbg->state = DRBG_RESEED;
+    if (drbg->reseed_interval > 0) {
+        if (drbg->generate_counter >= drbg->reseed_interval)
+            reseed_required = 1;
+    }
+    if (drbg->reseed_time_interval > 0) {
+        time_t now = time(NULL);
+        if (now < drbg->reseed_time
+            || now - drbg->reseed_time >= drbg->reseed_time_interval)
+            reseed_required = 1;
+    }
+    if (drbg->reseed_counter > 0 && drbg->parent != NULL) {
+        if (drbg->reseed_counter != drbg->parent->reseed_counter)
+            reseed_required = 1;
+    }
 
-    if (drbg->state == DRBG_RESEED || prediction_resistance) {
+    if (reseed_required || prediction_resistance) {
         if (!RAND_DRBG_reseed(drbg, adin, adinlen)) {
             RANDerr(RAND_F_RAND_DRBG_GENERATE, RAND_R_RESEED_ERROR);
             return 0;
@@ -402,10 +507,8 @@ int RAND_DRBG_generate(RAND_DRBG *drbg, unsigned char *out, size_t outlen,
         return 0;
     }
 
-    if (drbg->reseed_counter >= drbg->reseed_interval)
-        drbg->state = DRBG_RESEED;
-    else
-        drbg->reseed_counter++;
+    drbg->generate_counter++;
+
     return 1;
 }
 
@@ -464,15 +567,39 @@ int RAND_DRBG_set_callbacks(RAND_DRBG *drbg,
 
 /*
  * Set the reseed interval.
+ *
+ * The drbg will reseed automatically whenever the number of generate
+ * requests exceeds the given reseed interval. If the reseed interval
+ * is 0, then this feature is disabled.
+ *
+ * Returns 1 on success, 0 on failure.
  */
-int RAND_DRBG_set_reseed_interval(RAND_DRBG *drbg, int interval)
+int RAND_DRBG_set_reseed_interval(RAND_DRBG *drbg, unsigned int interval)
 {
-    if (interval < 0 || interval > MAX_RESEED)
+    if (interval > MAX_RESEED_INTERVAL)
         return 0;
     drbg->reseed_interval = interval;
     return 1;
 }
 
+/*
+ * Set the reseed time interval.
+ *
+ * The drbg will reseed automatically whenever the time elapsed since
+ * the last reseeding exceeds the given reseed time interval. For safety,
+ * a reseeding will also occur if the clock has been reset to a smaller
+ * value.
+ *
+ * Returns 1 on success, 0 on failure.
+ */
+int RAND_DRBG_set_reseed_time_interval(RAND_DRBG *drbg, time_t interval)
+{
+    if (interval > MAX_RESEED_TIME_INTERVAL)
+        return 0;
+    drbg->reseed_time_interval = interval;
+    return 1;
+}
+
 /*
  * Get and set the EXDATA
  */
@@ -493,31 +620,43 @@ void *RAND_DRBG_get_ex_data(const RAND_DRBG *drbg, int idx)
  */
 
 /*
- * Initializes the DRBG with default settings.
- * For global DRBGs a global lock is created with the given name
- * Returns 1 on success, 0 on failure
+ * Initializes the given global DRBG with default settings.
+ * A global lock for the DRBG is created with the given name.
+ *
+ * Returns a pointer to the new DRBG instance on success, NULL on failure.
  */
-static int drbg_setup(RAND_DRBG *drbg, const char *name)
+static int drbg_setup(RAND_DRBG *drbg, const char *name, RAND_DRBG *parent)
 {
     int ret = 1;
 
-    if (name != NULL) {
-        if (drbg->lock != NULL) {
-            RANDerr(RAND_F_DRBG_SETUP, ERR_R_INTERNAL_ERROR);
-            return 0;
-        }
+    if (name == NULL || drbg->lock != NULL) {
+        RANDerr(RAND_F_DRBG_SETUP, ERR_R_INTERNAL_ERROR);
+        return 0;
+    }
 
-        drbg->lock = CRYPTO_THREAD_glock_new(name);
-        if (drbg->lock == NULL) {
-            RANDerr(RAND_F_DRBG_SETUP, RAND_R_FAILED_TO_CREATE_LOCK);
-            return 0;
-        }
+    drbg->lock = CRYPTO_THREAD_glock_new(name);
+    if (drbg->lock == NULL) {
+        RANDerr(RAND_F_DRBG_SETUP, RAND_R_FAILED_TO_CREATE_LOCK);
+        return 0;
     }
 
     ret &= RAND_DRBG_set(drbg,
                          RAND_DRBG_NID, RAND_DRBG_FLAG_CTR_USE_DF) == 1;
     ret &= RAND_DRBG_set_callbacks(drbg, rand_drbg_get_entropy,
                                    rand_drbg_cleanup_entropy, NULL, NULL) == 1;
+
+    if (parent == NULL) {
+        drbg->reseed_interval = MASTER_RESEED_INTERVAL;
+        drbg->reseed_time_interval = MASTER_RESEED_TIME_INTERVAL;
+    } else {
+        drbg->parent = parent;
+        drbg->reseed_interval = SLAVE_RESEED_INTERVAL;
+        drbg->reseed_time_interval = SLAVE_RESEED_TIME_INTERVAL;
+    }
+
+    /* enable seed propagation */
+    drbg->reseed_counter = 1;
+
     /*
      * Ignore instantiation error so support just-in-time instantiation.
      *
@@ -538,8 +677,9 @@ DEFINE_RUN_ONCE_STATIC(do_rand_drbg_init)
 {
     int ret = 1;
 
-    ret &= drbg_setup(&rand_drbg, "rand_drbg");
-    ret &= drbg_setup(&priv_drbg, "priv_drbg");
+    ret &= drbg_setup(&drbg_master, "drbg_master", NULL);
+    ret &= drbg_setup(&drbg_public, "drbg_public", &drbg_master);
+    ret &= drbg_setup(&drbg_private, "drbg_private", &drbg_master);
 
     return ret;
 }
@@ -554,8 +694,9 @@ static void drbg_cleanup(RAND_DRBG *drbg)
 /* Clean up the global DRBGs before exit */
 void rand_drbg_cleanup_int(void)
 {
-    drbg_cleanup(&rand_drbg);
-    drbg_cleanup(&priv_drbg);
+    drbg_cleanup(&drbg_private);
+    drbg_cleanup(&drbg_public);
+    drbg_cleanup(&drbg_master);
 }
 
 /* Implements the default OpenSSL RAND_bytes() method */
@@ -563,7 +704,7 @@ static int drbg_bytes(unsigned char *out, int count)
 {
     int ret = 0;
     size_t chunk;
-    RAND_DRBG *drbg = RAND_DRBG_get0_global();
+    RAND_DRBG *drbg = RAND_DRBG_get0_public();
 
     if (drbg == NULL)
         return 0;
@@ -591,7 +732,7 @@ err:
 static int drbg_add(const void *buf, int num, double randomness)
 {
     int ret = 0;
-    RAND_DRBG *drbg = RAND_DRBG_get0_global();
+    RAND_DRBG *drbg = RAND_DRBG_get0_master();
 
     if (drbg == NULL)
         return 0;
@@ -628,7 +769,7 @@ static int drbg_seed(const void *buf, int num)
 static int drbg_status(void)
 {
     int ret;
-    RAND_DRBG *drbg = RAND_DRBG_get0_global();
+    RAND_DRBG *drbg = RAND_DRBG_get0_master();
 
     if (drbg == NULL)
         return 0;
@@ -640,27 +781,40 @@ static int drbg_status(void)
 }
 
 /*
- * Get the global public DRBG.
+ * Get the master DRBG.
+ * Returns pointer to the DRBG on success, NULL on failure.
+ *
+ */
+RAND_DRBG *RAND_DRBG_get0_master(void)
+{
+    if (!RUN_ONCE(&rand_drbg_init, do_rand_drbg_init))
+        return NULL;
+
+    return &drbg_master;
+}
+
+/*
+ * Get the public DRBG.
  * Returns pointer to the DRBG on success, NULL on failure.
  */
-RAND_DRBG *RAND_DRBG_get0_global(void)
+RAND_DRBG *RAND_DRBG_get0_public(void)
 {
     if (!RUN_ONCE(&rand_drbg_init, do_rand_drbg_init))
         return NULL;
 
-    return &rand_drbg;
+    return &drbg_public;
 }
 
 /*
- * Get the global private DRBG.
+ * Get the private DRBG.
  * Returns pointer to the DRBG on success, NULL on failure.
  */
-RAND_DRBG *RAND_DRBG_get0_priv_global(void)
+RAND_DRBG *RAND_DRBG_get0_private(void)
 {
     if (!RUN_ONCE(&rand_drbg_init, do_rand_drbg_init))
         return NULL;
 
-    return &priv_drbg;
+    return &drbg_private;
 }
 
 RAND_METHOD rand_meth = {