On 10/28/26 8:14 PM, Aaron Conole 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)

I guess, we should consider dropping this tag as kernel did. :)

> 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)

OPENSSL_NO_CHACHA is probably never defined here as we didn't include
any OpenSSL headers yet.  Or am I missing something?

> +#include <openssl/evp.h>
> +#define HAVE_CHACHA20_EVP 1

Can we detect that at configuration time?  The EVP_chacha20() function
should not be defined with OPENSSL_NO_CHACHA.

> +#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"

Can we order this lexicographically?

> +
> +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). */
>  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);
> +}
> +
> +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);

What happens if this function returns 'false' ?

Best regards, Ilya Maximets.
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to