Rosemarie O'Riorden <[email protected]> writes:

> Hi Aaron, this isn't a full review at all but I noticed two little nits
> while going over this series.
>
> On 10/28/26 3:14 PM, Aaron Conole via dev wrote:
>> 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.
>> 
>> The EVP backend re-keys from the first bytes of every
>> refill, just as the built-in implementation does, so
>> that the fast key erasure - and thus the forward
>> secrecy - of the generator is the same for both.  The
>> built-in ChaCha20 implementation is always built so
>> that it can serve as a fallback when OpenSSL is unable
>> to provide ChaCha20, for example when it only offers
>> approved algorithms in FIPS mode.
>> 
>> Assisted-by: LLM (claude, opencode, sonnet, opus, qwen3.8)
>> Signed-off-by: Aaron Conole <[email protected]>
>> ---
>> v2->v3:
>>   * Update the the csprng to provide local ChaCha20 when
>>     openssl isn't available for many reasons.
>>   * Updated the cs_random_bytes() max comment (since the OpenSSL
>>     version will shrink the max a bit more).
>>   * Updated the rekey to provide forward key erasure and forward
>>     secrecy.
>> 
>>  lib/random.c | 217 ++++++++++++++++++++++++++++++++++++++++++---------
>>  lib/random.h |   2 +-
>>  2 files changed, 182 insertions(+), 37 deletions(-)
>> 
>> diff --git a/lib/random.c b/lib/random.c
>> index d2f28866a2..27d57530f7 100644
>> --- a/lib/random.c
>> +++ b/lib/random.c
>> @@ -18,15 +18,24 @@
>>  #include "random.h"
>>  
>>  #include <errno.h>
>> +#include <stdbool.h>
>>  #include <stdlib.h>
>>  #include <sys/time.h>
>>  
>> +#if defined(HAVE_OPENSSL) && !defined(OPENSSL_NO_CHACHA)
>> +#include <openssl/evp.h>
>> +#define HAVE_CHACHA20_EVP 1
>> +#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 +129,39 @@ random_next(void)
>>  }
>>  
>>  
>> +/* Cryptographic secure pseudo-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. */
>> +#define CHACHA_IVLEN    16          /* ChaCha20 IV: counter and nonce. */
>> +
>> +/* Reseed from the OS entropy pool after this many bytes served, matching
>> + * the arc4random policy of periodic reseeding. */
>> +#define CSPRNG_RESEED   (1600 * 1024)
>> +
>> +#ifdef HAVE_CHACHA20_EVP
>> +/* The OpenSSL EVP backend consumes an IV in addition to a key. */
>> +#define CSPRNG_SECRETS  (CHACHA_KEYLEN + CHACHA_IVLEN)
>> +#else
>> +#define CSPRNG_SECRETS  CHACHA_KEYLEN
>> +#endif
>> +
>> +struct csprng {
>> +#ifdef HAVE_CHACHA20_EVP
>> +    EVP_CIPHER_CTX *ctx;            /* OpenSSL EVP state, once created. */
>> +    bool use_evp;                   /* Serve from OpenSSL rather than the
>> +                                     * built-in implementation. */
>> +#endif
>> +    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;
>> +};
>> +
>>  /* This is a 32-bit CSPRNG adapted from the ChaCha20 algorithm described in
>>   * RFC 7539 and RFC 8439, similar to arc4random(3) and the Linux kernel's
>>   * get_random_u32().
>> @@ -130,14 +172,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))))
>>  
>> @@ -147,20 +181,13 @@ 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".  Must be at least 16 bytes + NUL. 
>> */
>>  static const uint8_t chacha_sigma[] = "expand 32-byte k";
>>  
>> +#ifndef HAVE_CHACHA20_EVP
>>  /* Current random state. */
>>  DEFINE_STATIC_PER_THREAD_DATA(struct csprng, cs_state, { .initialized = 0 
>> });
>> +#endif
>>  
>>  /* Produces one 64-byte ChaCha20 block from 'input' (16 words) into 'out'. 
>> */
>>  void
>> @@ -211,22 +238,8 @@ csprng_rekey(struct csprng *c, const uint8_t 
>> key[CHACHA_KEYLEN])
>>      }
>>  }
>>  
>> -static void
>> -csprng_seed(struct csprng *c)
>> -{
>> -    uint8_t key[CHACHA_KEYLEN];
>> -
>> -    get_entropy_or_die(key, sizeof key);
>> -    csprng_rekey(c, key);
>> -    ovs_memzero(key, sizeof key);
>> -
>> -    c->pos = sizeof c->buf;
>> -    c->served = 0;
>> -    c->initialized = 1;
>> -}
>> -
>> -/* Fills csprng with fresh keystream, reserving the first 32 bytes to re-key
>> - * the generator (fast key erasure / forward secrecy). */
>> +/* Fills 'c' with fresh keystream, reserving the first 32 bytes to re-key 
>> the
>> + * generator (fast key erasure / forward secrecy). */
>
> Nit: This can be rebased into patch 2 of the set.

Yeah, I'll probably restructure them a bit better for the next revision
and try not to move everything around.  Re-reading it, I actually like
the original version more.

>>  static void
>>  csprng_refill(struct csprng *c)
>>  {
>> @@ -246,21 +259,153 @@ csprng_refill(struct csprng *c)
>>      c->pos = CHACHA_KEYLEN;
>>  }
>>  
>> +#ifdef HAVE_CHACHA20_EVP
>> +/* 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.  It re-keys from 
>> the
>> + * beginning of every refill, as the built-in implementation above does, so
>> + * that both provide the same fast key erasure.
>> + */
>> +
>> +static void
>> +csprng_state_destroy(void *ctx)
>> +{
>> +    struct csprng *csp = ctx;
>> +
>> +    if (csp->ctx) {
>> +        EVP_CIPHER_CTX_free(csp->ctx);
>> +        csp->ctx = NULL;
>> +    }
>> +
>> +    free(csp);
>> +}
>
> Nit: It would be nice if the above and below functions followed the same
> naming convention.

ACK

Thanks!

> - Rosemarie O'Riorden
>
>> +
>> +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->use_evp = true;
>> +        ovsthread_setspecific(cskey, csp);
>> +    }
>> +    return csp;
>> +}
>> +
>> +/* (Re)keys the OpenSSL EVP state from 'key' (32 bytes) and 'iv' (16 
>> bytes). */
>> +static bool
>> +csprng_evp_rekey(struct csprng *c, const uint8_t key[CHACHA_KEYLEN],
>> +                 const uint8_t iv[CHACHA_IVLEN])
>> +{
>> +    return EVP_EncryptInit_ex(c->ctx, EVP_chacha20(), NULL, key, iv) == 1;
>> +}
>> +
>> +/* Seeds the OpenSSL EVP state from fresh entropy, returning false if 
>> OpenSSL
>> + * cannot provide ChaCha20. */
>> +static bool
>> +csprng_evp_seed(struct csprng *c, const uint8_t key[CHACHA_KEYLEN])
>> +{
>> +    uint8_t iv[CHACHA_IVLEN];
>> +    bool ok;
>> +
>> +    if (!c->ctx) {
>> +        c->ctx = EVP_CIPHER_CTX_new();
>> +        if (!c->ctx) {
>> +            VLOG_WARN_ONCE("cs_random: cannot create cipher context.");
>> +            return false;
>> +        }
>> +    }
>> +
>> +    get_entropy_or_die(iv, sizeof iv);
>> +    ok = csprng_evp_rekey(c, key, iv);
>> +    ovs_memzero(iv, sizeof iv);
>> +
>> +    if (!ok) {
>> +        /* OpenSSL may not provide ChaCha20 at all, for example when it only
>> +         * offers approved algorithms in FIPS mode.  Fall back to the 
>> built-in
>> +         * implementation rather than failing to provide randomness. */
>> +        VLOG_WARN_ONCE("cs_random: OpenSSL ChaCha20 unavailable, using the "
>> +                       "built-in implementation.");
>> +    }
>> +    return ok;
>> +}
>> +
>> +/* Fills 'c' with fresh keystream from OpenSSL, reserving the first bytes to
>> + * re-key the generator (fast key erasure / forward secrecy). */
>> +static void
>> +csprng_evp_refill(struct csprng *c)
>> +{
>> +    static const uint8_t zero[CSPRNG_BUFSZ];
>> +    int outl;
>> +
>> +    if (EVP_EncryptUpdate(c->ctx, c->buf, &outl, zero, sizeof c->buf) != 1 
>> ||
>> +        outl != sizeof c->buf) {
>> +        VLOG_FATAL("cs_random: EVP fill failure.");
>> +    }
>> +
>> +    csprng_evp_rekey(c, c->buf, c->buf + CHACHA_KEYLEN);
>> +    ovs_memzero(c->buf, CSPRNG_SECRETS);
>> +    c->pos = CSPRNG_SECRETS;
>> +}
>> +#endif /* HAVE_CHACHA20_EVP */
>> +
>> +static void
>> +csprng_seed(struct csprng *c)
>> +{
>> +    uint8_t key[CHACHA_KEYLEN];
>> +
>> +    get_entropy_or_die(key, sizeof key);
>> +
>> +#ifdef HAVE_CHACHA20_EVP
>> +    if (c->use_evp && !csprng_evp_seed(c, key)) {
>> +        c->use_evp = false;
>> +    }
>> +    if (!c->use_evp) {
>> +        csprng_rekey(c, key);
>> +    }
>> +#else
>> +    csprng_rekey(c, key);
>> +#endif
>> +
>> +    ovs_memzero(key, sizeof key);
>> +
>> +    c->pos = sizeof c->buf;
>> +    c->served = 0;
>> +    c->initialized = 1;
>> +}
>> +
>>  void
>>  cs_random_bytes(void *b, size_t l)
>>  {
>>      struct csprng *c = cs_state_get();
>>  
>> -    /* c->buf isn't fully available for reading because the first 32 bytes
>> -     * of every refill are consumed as the next key. */
>> -    ovs_assert(l <= sizeof c->buf - CHACHA_KEYLEN);
>> +    /* c->buf isn't fully available for reading because the first bytes of
>> +     * every refill are consumed as the next key and IV. */
>> +    ovs_assert(l <= sizeof c->buf - CSPRNG_SECRETS);
>>  
>>      if (!c->initialized || c->served >= CSPRNG_RESEED) {
>>          csprng_seed(c);
>>      }
>>  
>>      if (c->pos + l > sizeof c->buf) {
>> +#ifdef HAVE_CHACHA20_EVP
>> +        if (c->use_evp) {
>> +            csprng_evp_refill(c);
>> +        } else {
>> +            csprng_refill(c);
>> +        }
>> +#else
>>          csprng_refill(c);
>> +#endif
>>      }
>>  
>>      memcpy(b, c->buf + c->pos, l);
>> diff --git a/lib/random.h b/lib/random.h
>> index 77792e28f1..76f165c7a8 100644
>> --- a/lib/random.h
>> +++ b/lib/random.h
>> @@ -53,7 +53,7 @@ void chacha20_block(const uint32_t input[CHACHA_K_INPUT],
>>                      uint8_t out[CHACHA_BLOCK]);
>>  
>>  /* Fills 'b' with 'l' cryptographically secure random bytes.  'l' must not 
>> be
>> - * larger than 4064; callers needing more bytes should make multiple calls. 
>> */
>> + * larger than 4048; callers needing more bytes should make multiple calls. 
>> */
>>  void cs_random_bytes(void *, size_t);
>>  uint32_t cs_random_uint32(void);
>>  
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to