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
