Browse Source

Use NSS in crypto_rand.c

This is comparatively straightforward too, except for a couple of
twists:

   * For as long as we're building with two crypto libraries, we
     want to seed _both_ their RNGs, and use _both_ their RNGs to
     improve the output of crypto_strongest_rand()

   * The NSS prng will sometimes refuse to generate huge outputs.
     When it does, we stretch the output with SHAKE.  We only need
     this for the tests.
Nick Mathewson 5 years ago
parent
commit
60705a5719
2 changed files with 107 additions and 8 deletions
  1. 102 8
      src/lib/crypt_ops/crypto_rand.c
  2. 5 0
      src/test/test-timers.c

+ 102 - 8
src/lib/crypt_ops/crypto_rand.c

@@ -35,9 +35,22 @@
 #include "lib/testsupport/testsupport.h"
 #include "lib/fs/files.h"
 
+#ifdef ENABLE_NSS
+#include "lib/crypt_ops/crypto_nss_mgt.h"
+#include "lib/crypt_ops/crypto_digest.h"
+#endif
+
+#ifdef ENABLE_OPENSSL
 DISABLE_GCC_WARNING(redundant-decls)
 #include <openssl/rand.h>
 ENABLE_GCC_WARNING(redundant-decls)
+#endif
+
+#ifdef ENABLE_NSS
+#include <pk11pub.h>
+#include <secerr.h>
+#include <prerror.h>
+#endif
 
 #if __GNUC__ && GCC_VERSION >= 402
 #if GCC_VERSION >= 406
@@ -324,14 +337,21 @@ crypto_strongest_rand(uint8_t *out, size_t out_len)
 {
 #define DLEN SHA512_DIGEST_LENGTH
   /* We're going to hash DLEN bytes from the system RNG together with some
-   * bytes from the openssl PRNG, in order to yield DLEN bytes.
+   * bytes from the PRNGs from our crypto librar(y/ies), in order to yield
+   * DLEN bytes.
    */
-  uint8_t inp[DLEN*2];
+  uint8_t inp[DLEN*3];
   uint8_t tmp[DLEN];
   tor_assert(out);
   while (out_len) {
-    crypto_rand((char*) inp, DLEN);
-    if (crypto_strongest_rand_raw(inp+DLEN, DLEN) < 0) {
+    memset(inp, 0, sizeof(inp));
+#ifdef ENABLE_OPENSSL
+    RAND_bytes(inp, DLEN);
+#endif
+#ifdef ENABLE_NSS
+    PK11_GenerateRandom(inp+DLEN, DLEN);
+#endif
+    if (crypto_strongest_rand_raw(inp+DLEN*2, DLEN) < 0) {
       // LCOV_EXCL_START
       log_err(LD_CRYPTO, "Failed to load strong entropy when generating an "
               "important key. Exiting.");
@@ -354,12 +374,13 @@ crypto_strongest_rand(uint8_t *out, size_t out_len)
 #undef DLEN
 }
 
+#ifdef ENABLE_OPENSSL
 /**
  * Seed OpenSSL's random number generator with bytes from the operating
  * system.  Return 0 on success, -1 on failure.
  **/
-int
-crypto_seed_rng(void)
+static int
+crypto_seed_openssl_rng(void)
 {
   int rand_poll_ok = 0, load_entropy_ok = 0;
   uint8_t buf[ADD_ENTROPY];
@@ -383,6 +404,52 @@ crypto_seed_rng(void)
   else
     return -1;
 }
+#endif
+
+#ifdef ENABLE_NSS
+/**
+ * Seed OpenSSL's random number generator with bytes from the operating
+ * system.  Return 0 on success, -1 on failure.
+ **/
+static int
+crypto_seed_nss_rng(void)
+{
+  uint8_t buf[ADD_ENTROPY];
+
+  int load_entropy_ok = !crypto_strongest_rand_raw(buf, sizeof(buf));
+  if (load_entropy_ok) {
+    if (PK11_RandomUpdate(buf, sizeof(buf)) != SECSuccess) {
+      load_entropy_ok = 0;
+    }
+  }
+
+  memwipe(buf, 0, sizeof(buf));
+
+  return load_entropy_ok ? 0 : -1;
+}
+#endif
+
+/**
+ * Seed the RNG for any and all crypto libraries that we're using with bytes
+ * from the operating system.  Return 0 on success, -1 on failure.
+ */
+int
+crypto_seed_rng(void)
+{
+  int seeded = 0;
+#ifdef ENABLE_NSS
+  if (crypto_seed_nss_rng() < 0)
+    return -1;
+  ++seeded;
+#endif
+#ifdef ENABLE_OPENSSL
+  if (crypto_seed_openssl_rng() < 0)
+    return -1;
+  ++seeded;
+#endif
+  tor_assert(seeded);
+  return 0;
+}
 
 /**
  * Write <b>n</b> bytes of strong random data to <b>to</b>. Supports mocking
@@ -407,17 +474,44 @@ crypto_rand, (char *to, size_t n))
 void
 crypto_rand_unmocked(char *to, size_t n)
 {
-  int r;
   if (n == 0)
     return;
 
   tor_assert(n < INT_MAX);
   tor_assert(to);
-  r = RAND_bytes((unsigned char*)to, (int)n);
+
+#ifdef ENABLE_NSS
+  SECStatus s = PK11_GenerateRandom((unsigned char*)to, (int)n);
+  if (s != SECSuccess) {
+    /* NSS rather sensibly might refuse to generate huge amounts of random
+     * data at once.  Unfortunately, our unit test do this in a couple of
+     * places.  To solve this issue, we use our XOF to stretch a shorter
+     * output when a longer one is needed.
+     *
+     * Yes, this is secure. */
+
+    /* This is longer than it needs to be; 1600 bits == 200 bytes is the
+     * state-size of SHA3. */
+#define BUFLEN 512
+    tor_assert(PR_GetError() == SEC_ERROR_INVALID_ARGS && n > BUFLEN);
+    unsigned char buf[BUFLEN];
+    s = PK11_GenerateRandom(buf, BUFLEN);
+    tor_assert(s == SECSuccess);
+    crypto_xof_t *xof = crypto_xof_new();
+    crypto_xof_add_bytes(xof, buf, BUFLEN);
+    crypto_xof_squeeze_bytes(xof, (unsigned char *)to, n);
+    crypto_xof_free(xof);
+    memwipe(buf, 0, BUFLEN);
+
+#undef BUFLEN
+  }
+#else
+  int r = RAND_bytes((unsigned char*)to, (int)n);
   /* We consider a PRNG failure non-survivable. Let's assert so that we get a
    * stack trace about where it happened.
    */
   tor_assert(r >= 0);
+#endif
 }
 
 /**

+ 5 - 0
src/test/test-timers.c

@@ -9,6 +9,7 @@
 
 #include "lib/evloop/compat_libevent.h"
 #include "lib/evloop/timers.h"
+#include "lib/crypt_ops/crypto_init.h"
 #include "lib/crypt_ops/crypto_rand.h"
 #include "lib/log/util_bug.h"
 #include "lib/time/compat_time.h"
@@ -62,6 +63,10 @@ main(int argc, char **argv)
   memset(&cfg, 0, sizeof(cfg));
   tor_libevent_initialize(&cfg);
   timers_initialize();
+  init_logging(1);
+
+  if (crypto_global_init(0, NULL, NULL) < 0)
+    return 1;
 
   int i;
   int ret;