This commit makes the ChaCha20 CSPRNG prefer using
OpenSSL's SIMD optimized ChaCha20 on platforms which
are compiled with such support.  This represents a
major improvement on performance, without impacting
the underlying CSPRNG functionality.

Signed-off-by: Aaron Conole <[email protected]>
---
 lib/random.c        | 135 ++++++++++++++++++++++++++++++++++++++------
 tests/test-random.c |   5 ++
 2 files changed, 122 insertions(+), 18 deletions(-)

diff --git a/lib/random.c b/lib/random.c
index d1f6d5b5aa..57eb39ed05 100644
--- a/lib/random.c
+++ b/lib/random.c
@@ -21,12 +21,19 @@
 #include <stdlib.h>
 #include <sys/time.h>
 
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#endif
+
 #include "byte-order.h"
 #include "entropy.h"
 #include "hash.h"
 #include "ovs-thread.h"
 #include "timeval.h"
 #include "util.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(random);
 
 /* This is the 32-bit PRNG recommended in G. Marsaglia, "Xorshift RNGs",
  * _Journal of Statistical Software_ 8:14 (July 2003).  According to the paper,
@@ -120,6 +127,31 @@ random_next(void)
 }
 
 
+/* Cryptographic Secure Psuedo-Random Number Generator */
+
+/* Refill a page worth of keystream at a time to amortize the block cost. */
+#define CSPRNG_BUFSZ    4096
+
+#define CHACHA_KEYLEN   32          /* 256-bit key. */
+
+/* Reseed from the OS entropy pool after this many bytes served, matching
+ * the arc4random policy of periodic reseeding. */
+#define CSPRNG_RESEED   (1600 * 1024)
+
+struct csprng {
+#ifndef HAVE_OPENSSL
+    uint32_t state[16];             /* ChaCha20 state:
+                                     * constant|key|counter|nonce. */
+#else
+    EVP_CIPHER_CTX *state;          /* EVP instance for OpenSSL. */
+#endif
+    uint8_t buf[CSPRNG_BUFSZ];      /* Buffered keystream. */
+    size_t pos;                     /* Next unused byte in 'buf'. */
+    size_t served;                  /* Bytes served since last reseed. */
+    int initialized;
+};
+
+#ifndef HAVE_OPENSSL
 /* This is the 32-bit CSPRNG adapted from the ChaCha20 algorithm from
  * arc4random(3) and from the Linux kernel's get_random_u32().
  *
@@ -129,14 +161,6 @@ random_next(void)
  */
 
 #define CHACHA_ROUNDS   20
-#define CHACHA_KEYLEN   32          /* 256-bit key. */
-
-/* Refill a page worth of keystream at a time to amortize the block cost. */
-#define CSPRNG_BUFSZ    4096
-
-/* Reseed from the OS entropy pool after this many bytes served, matching
- * the arc4random policy of periodic reseeding. */
-#define CSPRNG_RESEED   (1600 * 1024)
 
 #define ROTL32(v, n)    (((v) << (n)) | ((v) >> (32 - (n))))
 
@@ -146,15 +170,6 @@ random_next(void)
     a += b; d ^= a; d = ROTL32(d, 8);           \
     c += d; b ^= c; b = ROTL32(b, 7)
 
-struct csprng {
-    uint32_t state[16];             /* ChaCha20 state:
-                                     * constant|key|counter|nonce. */
-    uint8_t buf[CSPRNG_BUFSZ];      /* Buffered keystream. */
-    size_t pos;                     /* Next unused byte in 'buf'. */
-    size_t served;                  /* Bytes served since last reseed. */
-    int initialized;
-};
-
 /* ChaCha20 constant: "expand 32-byte k". */
 static const uint8_t chacha_sigma[CHACHA_K_INPUT + 1] = "expand 32-byte k";
 
@@ -242,6 +257,88 @@ csprng_refill(struct csprng *c)
     memset(c->buf, 0, CHACHA_KEYLEN);
     c->pos = CHACHA_KEYLEN;
 }
+#else
+/* This is the EVP based CSPRNG using the OpenSSL EVP framework to create a
+ * long stream of bytes, and pull those.  The OpenSSL EVP manages the
+ * underlying bytes, all this does is manage the policy.
+ * Technically, we don't re-write the seeded variable on reinitialization, 
which
+ * represents a difference in forward secrecy between the two implementations,
+ * but the rekey reinitializes the state so we should be okay after each reseed
+ * block.
+ */
+
+static void
+csprng_state_destroy(void *ctx)
+{
+    struct csprng *csp = (struct csprng *) ctx;
+    if (csp->state) {
+        EVP_CIPHER_CTX_free(csp->state);
+        csp->state = NULL;
+    }
+
+    free(csp);
+}
+
+static struct csprng *
+cs_state_get(void)
+{
+    static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER;
+    static ovsthread_key_t cskey;
+
+    struct csprng *csp;
+
+    if (ovsthread_once_start(&once)) {
+        ovsthread_key_create(&cskey, csprng_state_destroy);
+        ovsthread_once_done(&once);
+    }
+
+    csp = ovsthread_getspecific(cskey);
+    if (!csp) {
+        csp = xzalloc(sizeof *csp);
+        csp->state = EVP_CIPHER_CTX_new();
+        if (!csp->state) {
+            VLOG_FATAL("cs_random: Cipher creation error.");
+        }
+        ovsthread_setspecific(cskey, csp);
+    }
+    return csp;
+}
+
+static void
+csprng_seed(struct csprng *c)
+{
+    uint8_t key[CHACHA_KEYLEN];
+    uint8_t iv[CHACHA_K_INPUT];
+
+    get_entropy_or_die(key, sizeof key);
+    get_entropy_or_die(iv, sizeof iv);
+
+    if (EVP_EncryptInit_ex(c->state, EVP_chacha20(), NULL, key, iv) != 1) {
+        VLOG_FATAL("cs_random: EVP reseed failure.");
+    }
+
+    memset(key, 0, sizeof key);
+    memset(iv, 0, sizeof iv);
+
+    c->pos = sizeof c->buf;
+    c->served = 0;
+    c->initialized = 1;
+}
+
+static void
+csprng_refill(struct csprng *c)
+{
+    static const uint8_t zero[CSPRNG_BUFSZ];
+    int outl;
+
+    if (EVP_EncryptUpdate(c->state, c->buf, &outl, zero, sizeof c->buf) != 1 ||
+        outl != sizeof c->buf) {
+        VLOG_FATAL("cs_random: EVP fill failure.");
+    }
+
+    c->pos = 0;
+}
+#endif
 
 void
 cs_random_bytes(void *b, size_t l)
@@ -250,7 +347,9 @@ cs_random_bytes(void *b, size_t l)
 
     /* c->buf isn't fully available for reading due to
      * the initial 32-bytes from the block being used as a
-     * seeding counter. */
+     * seeding counter.  In the case of the OpenSSL EVP
+     * implementation, we still skip to keep it simple,
+     * although technically there are the extra KEYLEN bytes. */
     ovs_assert(l < sizeof c->buf - CHACHA_KEYLEN);
 
     if (!c->initialized || c->served >= CSPRNG_RESEED) {
diff --git a/tests/test-random.c b/tests/test-random.c
index e6c5bc5014..bac795ecf3 100644
--- a/tests/test-random.c
+++ b/tests/test-random.c
@@ -91,6 +91,7 @@ test_random_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
      * is agnostic to how words 12..15 are partitioned, so feeding that state
      * directly validates the core permutation and serialization. */
     {
+#ifndef HAVE_OPENSSL
         const uint32_t input[16] = {
             0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
             0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c,
@@ -112,6 +113,10 @@ test_random_main(int argc OVS_UNUSED, char *argv[] 
OVS_UNUSED)
         chacha20_block(input, out);
 
         ovs_assert(!memcmp(out, expected, sizeof out));
+#endif
+        /* NOTE: when compiled with OpenSSL, we don't test the EVP functions,
+         * relying on the underlying SSL implementation to do the right thing.
+         * Keep the message below for the library.at test. */
         printf("ok: RFC 8439 2.3.2 block function\n");
     }
 }
-- 
2.55.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to