On Thu, Jun 7, 2018 at 6:37 PM, Eric Covener <cove...@gmail.com> wrote:
> On Thu, Jun 7, 2018 at 11:59 AM, Yann Ylavic <ylavic....@gmail.com> wrote:
>> I'd like to propose a new RNG for APR, based on a design from D.J.
>> Bernstein ([1]).
>>
>> Called "Fast-key-erasure random-number generators" by the author, it
>> requires 256bits (32 bytes) of initial entropy only, is fast, and
>> ensures Forward Secrecy.
>>
>> The current RNGs available in APR are:
>> 1/ apr_generate_random_bytes(); the wrapper around the system call,
>> 2/ apr_random_(in)secure_bytes(); the ones from "random/unix/apr_random.c".
>>
>> Both are obviously considered secure, but 1/ is usually not very fast
>> (and could block on some systems), and 2/ requires kilos of initial
>> entropy (taken from 1/ thus possibly blocking too).
>> Also the system RNG wrapped by 1/ is unlikely to return a great number
>> of bytes at once (256 max on some systems like BSDs), and better suits
>> as a source of entropy (like in 2/).
>> Neither ensures forward secrecy (AFAICT).
>>
>
> I think the guts of 2) really needs to go, it is totally orphaned.

That was the plan, with a substitute (if it makes sense).
So we could:
a/ Replace 2/ with this new RNG,
b/ Axe 2/ from APR-2 and add the new RNG in apr_crypto,
c/ Axe 2/ from APR-2 and leave 1/ as the only RNG.

While c/ is straightforward, it leaves users with 1/ only which is not
ideal on some systems, so it's not what I'd propose :)

I'm fine with a/ or b/ and actually started to implement b/ (see
attached) until I realized that the crypto libraries needed to be
chosen by the user at runtime and loaded explicitly/dynamically with a
call to apr_crypto_get_driver().
I don't find this very convenient for a RNG (where the user needs not
have the choice on the underlying crypto), so I turned --with-openssl
(only for now) to link APR-2 with libcrypto at build time (like
--with-xml2 does for libxml2).
So at this point I'm not very far from a/ while I first thought it
would have required to implement a stream cipher in APR-2 (e.g.
CHACHA20 which is quite simple code, like it's currently done for
SHA256 for apr_random) to avoid any crypto lib dependency.

All this to say... :p
First WDYT of this new RNG (and attached first implementation, missing
some tests still)?
If it sounds good, how about we link the configured --with-whatever
crypto library directly with APR-2 without requiring runtime loading?
And if it's still fine, what about a/ vs b/ (where a/ wouldn't need
custom crypto in APR)?

Thanks for reading so far, if anyone :)

Regards,
Yann.
Index: CMakeLists.txt
===================================================================
--- CMakeLists.txt	(revision 1833215)
+++ CMakeLists.txt	(working copy)
@@ -239,6 +239,7 @@ SET(APR_SOURCES
   buckets/apr_buckets_simple.c
   buckets/apr_buckets_socket.c
   crypto/apr_crypto.c
+  crypto/apr_crypto_prng.c
   crypto/apr_md4.c
   crypto/apr_md5.c
   crypto/apr_passwd.c
Index: build/crypto.m4
===================================================================
--- build/crypto.m4	(revision 1833215)
+++ build/crypto.m4	(working copy)
@@ -23,6 +23,7 @@ dnl APU_CHECK_CRYPTO: look for crypto libraries an
 dnl
 AC_DEFUN([APU_CHECK_CRYPTO], [
   apu_have_crypto=0
+  apu_have_crypto_prng=0
   apu_have_openssl=0
   apu_have_nss=0
   apu_have_commoncrypto=0
@@ -66,13 +67,18 @@ AC_DEFUN([APU_CHECK_CRYPTO], [
       dnl add checks for other varieties of ssl here
       if test "$apu_have_crypto" = "0"; then
         AC_ERROR([Crypto was requested but no crypto library could be enabled; specify the location of a crypto library using --with-openssl, --with-nss, and/or --with-commoncrypto.])
+      elif test "$apu_have_openssl" = "1"; then
+        dnl PRNG only implemented with openssl for now
+        apu_have_crypto_prng=1
       fi
     fi
   ], [
       apu_have_crypto=0
+      apu_have_crypto_prng=0
   ])
 
   AC_SUBST(apu_have_crypto)
+  AC_SUBST(apu_have_crypto_prng)
 
 ])
 dnl
@@ -150,6 +156,9 @@ AC_DEFUN([APU_CHECK_CRYPTO_OPENSSL], [
   AC_SUBST(LDADD_crypto_openssl)
   AC_SUBST(apu_have_crypto)
 
+  APR_ADDTO(APRUTIL_EXPORT_LIBS, [-lcrypto])
+  APR_ADDTO(LIBS, [-lcrypto])
+
   LIBS="$old_libs"
   CPPFLAGS="$old_cppflags"
   LDFLAGS="$old_ldflags"
Index: build.conf
===================================================================
--- build.conf	(revision 1833215)
+++ build.conf	(working copy)
@@ -11,6 +11,7 @@ paths =
   tables/*.c
   buckets/*.c
   crypto/apr_crypto.c
+  crypto/apr_crypto_prng.c
   crypto/apr_md4.c
   crypto/apr_md5.c
   crypto/apr_passwd.c
Index: crypto/apr_crypto.c
===================================================================
--- crypto/apr_crypto.c	(revision 1833215)
+++ crypto/apr_crypto.c	(working copy)
@@ -86,7 +86,7 @@ static apr_status_t apr_crypto_term(void *ptr)
 
 APR_DECLARE(apr_status_t) apr_crypto_init(apr_pool_t *pool)
 {
-    apr_status_t ret = APR_SUCCESS;
+    apr_status_t rv;
     apr_pool_t *parent;
 
     if (drivers != NULL) {
@@ -107,7 +107,15 @@ APR_DECLARE(apr_status_t) apr_crypto_init(apr_pool
     apr_pool_cleanup_register(pool, NULL, apr_crypto_term,
             apr_pool_cleanup_null);
 
-    return ret;
+    /* apr_crypto_prng_init() may already have been called with
+     * non-default parameters, so ignore APR_EREINIT.
+     */
+    rv = apr_crypto_prng_init(pool, 0, NULL, 0);
+    if (rv != APR_SUCCESS && rv != APR_EREINIT) {
+        return rv;
+    }
+
+    return APR_SUCCESS;
 }
 
 static apr_status_t crypto_clear(void *ptr)
Index: crypto/apr_crypto_prng.c
===================================================================
--- crypto/apr_crypto_prng.c	(nonexistent)
+++ crypto/apr_crypto_prng.c	(working copy)
@@ -0,0 +1,454 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Cryptographic Pseudo Random Number Generator (CPRNG), based on
+ * "Fast-key-erasure random-number generators" blog from D.J. Bernstein ([1]),
+ * and public domain implementation in libpqcrypto's randombytes() ([2]).
+ *
+ * [1] https://blog.cr.yp.to/20170723-random.html
+ * [2] https://libpqcrypto.org/
+ */
+
+#include "apu.h"
+
+#include "apr_crypto.h"
+
+#if APU_HAVE_CRYPTO
+#if APU_HAVE_CRYPTO_PRNG
+
+#include "apr_pools.h"
+#include "apr_strings.h"
+#include "apr_thread_mutex.h"
+#include "apr_thread_proc.h"
+
+#include <stdlib.h> /* for malloc() */
+
+#define APR_CRYPTO_PRNG_KEY_SIZE 32
+#if APR_CRYPTO_PRNG_SEED_SIZE > APR_CRYPTO_PRNG_KEY_SIZE
+#error apr_crypto_prng uses 256bit stream ciphers only
+#endif
+
+#define APR_CRYPTO_PRNG_BUF_SIZE_MIN 256
+#define APR_CRYPTO_PRNG_BUF_SIZE_DEF 768
+
+#if APU_HAVE_OPENSSL
+
+#include <openssl/evp.h>
+
+#include <openssl/obj_mac.h> /* for NID_* */
+#if !defined(NID_chacha20) && !defined(NID_aes_256_ctr)
+/* XXX: APU_HAVE_CRYPTO_PRNG && APU_HAVE_OPENSSL shoudn't be defined! */
+#error apr_crypto_prng needs OpenSSL implementation for Chacha20 or AES256-CTR
+#endif
+
+typedef EVP_CIPHER_CTX cprng_stream_ctx_t;
+
+static apr_status_t cprng_stream_ctx_make(cprng_stream_ctx_t **pctx)
+{
+    EVP_CIPHER_CTX *ctx;
+    const EVP_CIPHER *cipher;
+
+    ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) {
+        return APR_ENOMEM;
+    }
+
+#if defined(NID_chacha20)
+    cipher = EVP_chacha20();
+#else
+    cipher = EVP_aes_256_ctr();
+#endif
+    if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) <= 0) {
+        EVP_CIPHER_CTX_free(ctx);
+        return APR_ENOMEM;
+    }
+
+    *pctx = ctx;
+    return APR_SUCCESS;
+}
+
+static APR_INLINE
+void cprng_stream_ctx_free(cprng_stream_ctx_t *ctx)
+{
+    EVP_CIPHER_CTX_free(ctx);
+}
+
+static APR_INLINE
+apr_status_t cprng_stream_ctx_mix(cprng_stream_ctx_t **pctx,
+                                  unsigned char *key, unsigned char *to,
+                                  const unsigned char *z, apr_size_t n)
+{
+    cprng_stream_ctx_t *ctx = *pctx;
+    int len;
+
+    EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL);
+    EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+    memset(key, 0, APR_CRYPTO_PRNG_KEY_SIZE);
+    EVP_EncryptUpdate(ctx, key, &len, key, APR_CRYPTO_PRNG_KEY_SIZE);
+    EVP_EncryptUpdate(ctx, to, &len, z, n);
+
+    return APR_SUCCESS;
+}
+
+#else /* APU_HAVE_OPENSSL */
+
+/* XXX: APU_HAVE_CRYPTO_PRNG shoudn't be defined! */
+#error apr_crypto_prng implemented with OpenSSL only for now
+
+#endif /* APU_HAVE_OPENSSL */
+
+struct apr_crypto_prng_t {
+    apr_pool_t *pool;
+    cprng_stream_ctx_t *ctx;
+#if APR_HAS_THREADS
+    apr_thread_mutex_t *lock;
+#endif
+    unsigned char *key, *buf;
+    apr_size_t len, pos;
+    int flags;
+};
+
+static apr_crypto_prng_t *cprng_global = NULL;
+
+#if APR_HAS_THREADS
+static apr_threadkey_t *cprng_thread_key = NULL;
+
+static void cprng_thread_destroy(void *cprng)
+{
+    if (!cprng_global) {
+        apr_threadkey_private_delete(cprng_thread_key);
+        cprng_thread_key = NULL;
+    }
+    apr_crypto_prng_destroy(cprng);
+}
+#endif
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_init(apr_pool_t *pool,
+                                               apr_size_t bufsize,
+                                               const unsigned char seed[],
+                                               int flags)
+{
+    if (cprng_global) {
+        return APR_EREINIT;
+    }
+
+    if (flags & APR_CRYPTO_PRNG_PER_THREAD) {
+#if APR_HAS_THREADS
+        apr_status_t rv;
+        rv = apr_threadkey_private_create(&cprng_thread_key,
+                                          cprng_thread_destroy, pool);
+        if (rv != APR_SUCCESS) {
+            return rv;
+        }
+        flags &= ~APR_CRYPTO_PRNG_PER_THREAD;
+#else
+        return APR_ENOTIMPL;
+#endif
+    }
+
+    return apr_crypto_prng_create(&cprng_global, bufsize,
+                                  flags | APR_CRYPTO_PRNG_LOCKED,
+                                  seed, pool);
+}
+
+APR_DECLARE(void) apr_crypto_prng_term(void)
+{
+    if (cprng_global) {
+        apr_crypto_prng_destroy(cprng_global);
+        cprng_global = NULL;
+    }
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_after_fork(void)
+{
+    if (!cprng_global) {
+        return APR_EINIT;
+    }
+
+    return apr_crypto_prng_reseed(cprng_global, NULL);
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_random_bytes(unsigned char *buf,
+                                                  apr_size_t len)
+{
+    if (!cprng_global) {
+        return APR_EINIT;
+    }
+
+    return apr_crypto_prng_bytes(cprng_global, buf, len);
+}
+
+#if APR_HAS_THREADS
+APR_DECLARE(apr_status_t) apr_crypto_thread_random_bytes(unsigned char *buf,
+                                                         apr_size_t len)
+{
+    apr_status_t rv;
+    apr_crypto_prng_t *cprng;
+    void *private = NULL;
+
+    if (!cprng_thread_key) {
+        return APR_EINIT;
+    }
+
+    rv = apr_threadkey_private_get(&private, cprng_thread_key);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    cprng = private;
+    if (!cprng) {
+        rv = apr_crypto_prng_create(&cprng, 0, APR_CRYPTO_PRNG_PER_THREAD,
+                                    NULL, NULL);
+        if (rv != APR_SUCCESS) {
+            return rv;
+        }
+
+        rv = apr_threadkey_private_set(cprng, cprng_thread_key);
+        if (rv != APR_SUCCESS) {
+            apr_crypto_prng_destroy(cprng);
+            return rv;
+        }
+    }
+
+    return apr_crypto_prng_bytes(cprng, buf, len);
+}
+#endif
+
+static apr_status_t cprng_cleanup(void *arg)
+{
+    apr_crypto_prng_t *cprng = arg;
+
+    if (cprng == cprng_global) {
+        cprng_global = NULL;
+    }
+
+    if (cprng->ctx) {
+        cprng_stream_ctx_free(cprng->ctx);
+    }
+
+    if (cprng->key) {
+        apr_crypto_memzero(cprng->key, APR_CRYPTO_PRNG_KEY_SIZE + cprng->len);
+        if (!cprng->pool) {
+            free(cprng->key);
+        }
+    }
+
+    if (!cprng->pool) {
+        free(cprng);
+    }
+
+    return APR_SUCCESS;
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_create(apr_crypto_prng_t **pcprng,
+                                                 apr_size_t bufsize, int flags,
+                                                 const unsigned char seed[],
+                                                 apr_pool_t *pool)
+{
+    apr_status_t rv;
+    apr_crypto_prng_t *cprng;
+
+    *pcprng = NULL;
+
+    if (bufsize > APR_INT32_MAX - APR_CRYPTO_PRNG_KEY_SIZE
+            || (flags & APR_CRYPTO_PRNG_LOCKED && !pool)
+            || (flags & ~APR_CRYPTO_PRNG_MASK)) {
+        return APR_EINVAL;
+    }
+
+    if (pool) {
+        cprng = apr_pcalloc(pool, sizeof(*cprng));
+    }
+    else {
+        cprng = calloc(1, sizeof(*cprng));
+    }
+    if (!cprng) {
+        return APR_ENOMEM;
+    }
+    cprng->flags = flags;
+    cprng->pool = pool;
+
+    if (bufsize == 0) {
+        bufsize = APR_CRYPTO_PRNG_BUF_SIZE_DEF;
+    }
+    else if (bufsize < APR_CRYPTO_PRNG_BUF_SIZE_MIN) {
+        bufsize = APR_CRYPTO_PRNG_BUF_SIZE_MIN;
+    }
+    else if (bufsize % APR_CRYPTO_PRNG_KEY_SIZE) {
+        bufsize += APR_CRYPTO_PRNG_KEY_SIZE;
+        bufsize -= bufsize % APR_CRYPTO_PRNG_KEY_SIZE;
+    }
+    if (pool) {
+        cprng->key = apr_palloc(pool, APR_CRYPTO_PRNG_KEY_SIZE + bufsize);
+    }
+    else {
+        cprng->key = malloc(APR_CRYPTO_PRNG_KEY_SIZE + bufsize);
+    }
+    if (!cprng->key) {
+        return APR_ENOMEM;
+    }
+    cprng->buf = cprng->key + APR_CRYPTO_PRNG_KEY_SIZE;
+    cprng->len = bufsize;
+
+    if (seed) {
+        memset(cprng->key, 0, APR_CRYPTO_PRNG_KEY_SIZE);
+    }
+    rv = apr_crypto_prng_reseed(cprng, seed);
+    if (rv != APR_SUCCESS) {
+        cprng_cleanup(cprng);
+        return rv;
+    }
+
+    if (flags & APR_CRYPTO_PRNG_LOCKED) {
+#if APR_HAS_THREADS
+        rv = apr_thread_mutex_create(&cprng->lock, APR_THREAD_MUTEX_DEFAULT,
+                                     pool);
+        if (rv != APR_SUCCESS) {
+            cprng_cleanup(cprng);
+            return rv;
+        }
+#else
+        cprng_cleanup(cprng);
+        return APR_ENOTIMPL;
+#endif
+    }
+
+    rv = cprng_stream_ctx_make(&cprng->ctx);
+    if (rv != APR_SUCCESS) {
+        cprng_cleanup(cprng);
+        return rv;
+    }
+
+    if (pool) {
+        apr_pool_cleanup_register(pool, cprng, cprng_cleanup,
+                                  apr_pool_cleanup_null);
+    }
+
+    *pcprng = cprng;
+    return APR_SUCCESS;
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_destroy(apr_crypto_prng_t *cprng)
+{
+    if (!cprng->pool) {
+        return cprng_cleanup(cprng);
+    }
+
+    return apr_pool_cleanup_run(cprng->pool, cprng, cprng_cleanup);
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_reseed(apr_crypto_prng_t *cprng,
+                                                 const unsigned char seed[])
+{
+    apr_status_t rv = APR_SUCCESS;
+
+#if APR_HAS_THREADS
+    if (cprng->lock) {
+        apr_thread_mutex_lock(cprng->lock);
+    }
+#endif
+
+    if (seed) {
+        apr_size_t n = 0;
+        do {
+            cprng->key[n] ^= seed[n];
+        } while (++n < APR_CRYPTO_PRNG_KEY_SIZE);
+    }
+    else {
+        rv = apr_generate_random_bytes(cprng->key, APR_CRYPTO_PRNG_KEY_SIZE);
+    }
+    apr_crypto_memzero(cprng->buf, cprng->len);
+    cprng->pos = cprng->len;
+
+#if APR_HAS_THREADS
+    if (cprng->lock) {
+        apr_thread_mutex_unlock(cprng->lock);
+    }
+#endif
+
+    return rv;
+}
+
+static APR_INLINE
+apr_status_t cprng_stream_mix(apr_crypto_prng_t *cprng, unsigned char *to)
+{
+    return cprng_stream_ctx_mix(&cprng->ctx, cprng->key, to,
+                                cprng->buf, cprng->len);
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_bytes(apr_crypto_prng_t *cprng,
+                                                unsigned char *buf,
+                                                apr_size_t len)
+{
+    apr_status_t rv;
+    apr_size_t n;
+
+#if APR_HAS_THREADS
+    if (cprng->lock) {
+        apr_thread_mutex_lock(cprng->lock);
+    }
+#endif
+
+    while (len) {
+        if (cprng->pos == cprng->len) {
+            if (len >= cprng->len) {
+                do {
+                    rv = cprng_stream_mix(cprng, buf);
+                    if (rv != APR_SUCCESS) {
+                        return rv;
+                    }
+                    buf += cprng->len;
+                    len -= cprng->len;
+                } while (len >= cprng->len);
+                if (!len) {
+                    break;
+                }
+            }
+            rv = cprng_stream_mix(cprng, cprng->buf);
+            if (rv != APR_SUCCESS) {
+                return rv;
+            }
+            cprng->pos = 0;
+        }
+
+        /* Random bytes are consumed then zero-ed to ensure
+         * both forward secrecy and cleared next mixed data.
+         */
+        n = cprng->len - cprng->pos;
+        if (n > len) {
+            n = len;
+        }
+        memcpy(buf, cprng->buf + cprng->pos, n);
+        apr_crypto_memzero(cprng->buf + cprng->pos, n);
+        cprng->pos += n;
+
+        buf += n;
+        len -= n;
+    }
+
+#if APR_HAS_THREADS
+    if (cprng->lock) {
+        apr_thread_mutex_unlock(cprng->lock);
+    }
+#endif
+
+    return APR_SUCCESS;
+}
+
+#endif /* APU_HAVE_CRYPTO_PRNG */
+#endif /* APU_HAVE_CRYPTO */

Property changes on: crypto/apr_crypto_prng.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: include/apr.h.in
===================================================================
--- include/apr.h.in	(revision 1833215)
+++ include/apr.h.in	(working copy)
@@ -683,6 +683,7 @@ typedef int apr_wait_t;
 #define APU_HAVE_ODBC          @apu_have_odbc@
 
 #define APU_HAVE_CRYPTO        @apu_have_crypto@
+#define APU_HAVE_CRYPTO_PRNG   @apu_have_crypto_prng@
 #define APU_HAVE_OPENSSL       @apu_have_openssl@
 #define APU_HAVE_NSS           @apu_have_nss@
 #define APU_HAVE_COMMONCRYPTO  @apu_have_commoncrypto@
Index: include/apr_crypto.h
===================================================================
--- include/apr_crypto.h	(revision 1833215)
+++ include/apr_crypto.h	(working copy)
@@ -495,6 +495,144 @@ APR_DECLARE(apr_status_t) apr_crypto_cleanup(apr_c
 APR_DECLARE(apr_status_t)
         apr_crypto_shutdown(const apr_crypto_driver_t *driver);
 
+
+/**
+ * Cryptographic Pseudo Random Number Generator (CPRNG).
+ *
+ * Allows to generate cryptographically secure random bytes indefinitely
+ * given an initial seed of \ref APR_CRYPTO_PRNG_SEED_SIZE bytes (32), which
+ * is either provided by the caller or automatically gathered from the system.
+ * The CPRNG can also be re-seeded at any time, or after a process is fork()ed.
+ *
+ * The internal key is renewed every \ref APR_CRYPTO_PRNG_SEED_SIZE random
+ * bytes produced and those data once returned to the caller are cleared from
+ * the internal state, which ensures forward secrecy.
+ *
+ * This CPRNG is fast, based on a stream cipher, and will never block besides
+ * the initial seed or any reseed if it depends on the system entropy.
+ *
+ * Finally, it can be used either globally (locked in multithread environment),
+ * per-thread (a lock free instance is automatically created for each thread on
+ * first use), or created as standalone instance (manageable independently).
+ */
+
+#define APR_CRYPTO_PRNG_SEED_SIZE 32
+
+#define APR_CRYPTO_PRNG_LOCKED      (0x1)
+#define APR_CRYPTO_PRNG_PER_THREAD  (0x2)
+#define APR_CRYPTO_PRNG_MASK        (0x3)
+
+/** Opaque CPRNG state */
+typedef struct apr_crypto_prng_t apr_crypto_prng_t;
+
+/**
+ * @brief Perform global initialisation. Call once only.
+ *
+ * @param pool Used to allocate memory and register cleanups
+ * @param bufsize The size of the buffer used to cache upcoming random bytes.
+ * @param seed A custom seed of \ref APR_CRYPTO_PRNG_SEED_SIZE bytes,
+ *             or NULL for the seed to be gathered from system entropy.
+ * @param flags \ref APR_CRYPTO_PRNG_PER_THREAD to allow for per-thread CPRNG,
+ *              or zero.
+ * @return APR_EREINIT if called more than once,
+ *         any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_init(apr_pool_t *pool,
+                                               apr_size_t bufsize,
+                                               const unsigned char seed[],
+                                               int flags);
+/**
+ * @brief Terminate global initialisation if needed, before automatic cleanups.
+ */
+APR_DECLARE(void) apr_crypto_prng_term(void);
+
+/**
+ * @brief Reseed global CPRNG after a process is fork()ed to avoid any
+ *        duplicated state.
+ *
+ * @return Any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_after_fork(void);
+
+/**
+ * @brief Generate cryptographically secure random bytes from the global CPRNG.
+ *
+ * @param buf The destination buffer
+ * @param len The destination length
+ * @return APR_EINIT if \ref apr_crypto_prng_init() was not called.
+ *         any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_random_bytes(unsigned char *buf,
+                                                  apr_size_t len);
+
+#if APR_HAS_THREADS
+/**
+ * @brief Generate cryptographically secure random bytes from the CPRNG of
+ *        the current thread.
+ *
+ * @param buf The destination buffer
+ * @param len The destination length
+ * @return APR_EINIT if \ref apr_crypto_prng_init() was not called or
+ *                   called without \ref APR_CRYPTO_PRNG_PER_THREAD,
+ *         any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_thread_random_bytes(unsigned char *buf,
+                                                         apr_size_t len);
+#endif
+
+/**
+ * @brief Create a standalone CPRNG.
+ *
+ * @param pcprng The CPRNG created.
+ * @param bufsize The size of the buffer used to cache upcoming random bytes.
+ * @param flags \ref APR_CRYPTO_PRNG_LOCKED to control concurrent accesses,
+ *              or zero.
+ * @param seed A custom seed of \ref APR_CRYPTO_PRNG_SEED_SIZE bytes,
+ *             or NULL for the seed to be gathered from system entropy.
+ * @param pool Used to allocate memory and register cleanups, or NULL
+ *             if the memory should be managed outside (besides per-thread
+ *             which has an automatic memory management with no pool, when
+ *             NULL is given the caller is responsible for calling
+ *             \ref apr_crypto_prng_destroy() or some memory would leak.
+ * @return APR_EINVAL if \ref bufsize is too large or flags are unknown,
+ *         any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_create(apr_crypto_prng_t **pcprng,
+                                                 apr_size_t bufsize, int flags,
+                                                 const unsigned char seed[],
+                                                 apr_pool_t *pool);
+
+/**
+ * @brief Destroy a standalone CPRNG.
+ *
+ * @param cprng The CPRNG to destroy.
+ * @return APR_SUCCESS.
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_destroy(apr_crypto_prng_t *cprng);
+
+/**
+ * @brief Reseed a standalone CPRNG.
+ *
+ * @param cprng The CPRNG to reseed.
+ * @param seed A custom seed of \ref APR_CRYPTO_PRNG_SEED_SIZE bytes,
+ *             or NULL for the seed to be gathered from system entropy.
+ * @return Any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_reseed(apr_crypto_prng_t *cprng,
+                                                 const unsigned char seed[]);
+
+/**
+ * @brief Generate cryptographically secure random bytes a standalone CPRNG.
+ *
+ * @param cprng The CPRNG.
+ * @param buf The destination buffer
+ * @param len The destination length
+ * @return Any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_bytes(apr_crypto_prng_t *cprng,
+                                                unsigned char *buf,
+                                                apr_size_t len);
+
 #endif /* APU_HAVE_CRYPTO */
 
 /** @} */
Index: test/testcrypto.c
===================================================================
--- test/testcrypto.c	(revision 1833215)
+++ test/testcrypto.c	(working copy)
@@ -554,9 +554,16 @@ static void test_crypto_init(abts_case *tc, void *
 {
     apr_pool_t *pool = NULL;
     apr_status_t rv;
+    int flags = 0;
 
     apr_pool_create(&pool, NULL);
 
+#if APR_HAS_THREADS
+    flags = APR_CRYPTO_PRNG_PER_THREAD;
+#endif
+    rv = apr_crypto_prng_init(pool, 0, NULL, flags);
+    ABTS_ASSERT(tc, "failed to init apr_crypto_prng", rv == APR_SUCCESS);
+
     rv = apr_crypto_init(pool);
     ABTS_ASSERT(tc, "failed to init apr_crypto", rv == APR_SUCCESS);
 
@@ -1450,6 +1457,111 @@ static void test_crypto_equals(abts_case *tc, void
     TEST_SCALAR_MATCH(6, p, 0);
 }
 
+/*
+ * Test vector for Chacha20:
+ * https://www.ietf.org/archive/id/draft-strombergson-chacha-test-vectors-01.txt
+ *
+   Key:    0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+           0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+           0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+           0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+   IV:     0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+   Rounds: 20
+
+   Internal state after init:
+   state[00 - 01] = 0x61707865 0x3320646e
+   state[02 - 03] = 0x79622d32 0x6b206574
+   state[04 - 05] = 0x00000000 0x00000000
+   state[06 - 07] = 0x00000000 0x00000000
+   state[08 - 09] = 0x00000000 0x00000000
+   state[10 - 11] = 0x00000000 0x00000000
+   state[12 - 13] = 0x00000000 0x00000000
+   state[14 - 15] = 0x00000000 0x00000000
+
+   Keystream block :
+   0x76 0xb8 0xe0 0xad 0xa0 0xf1 0x3d 0x90
+   0x40 0x5d 0x6a 0xe5 0x53 0x86 0xbd 0x28
+   0xbd 0xd2 0x19 0xb8 0xa0 0x8d 0xed 0x1a
+   0xa8 0x36 0xef 0xcc 0x8b 0x77 0x0d 0xc7
+   0xda 0x41 0x59 0x7c 0x51 0x57 0x48 0x8d
+   0x77 0x24 0xe0 0x3f 0xb8 0xd8 0x4a 0x37
+   0x6a 0x43 0xb8 0xf4 0x15 0x18 0xa1 0x1c
+   0xc3 0x87 0xb6 0x69 0xb2 0xee 0x65 0x86
+
+   Keystream block 1:
+   0x9f 0x07 0xe7 0xbe 0x55 0x51 0x38 0x7a
+   0x98 0xba 0x97 0x7c 0x73 0x2d 0x08 0x0d
+   0xcb 0x0f 0x29 0xa0 0x48 0xe3 0x65 0x69
+   0x12 0xc6 0x53 0x3e 0x32 0xee 0x7a 0xed
+   0x29 0xb7 0x21 0x76 0x9c 0xe6 0x4e 0x43
+   0xd5 0x71 0x33 0xb0 0x74 0xd8 0x39 0xd5
+   0x31 0xed 0x1f 0x28 0x51 0x0a 0xfb 0x45
+   0xac 0xe1 0x0a 0x1f 0x4b 0x79 0x4d 0x6f
+ *
+ */
+static const unsigned char CHACHA20_vector_0[64] = {
+   0x76,0xb8,0xe0,0xad,0xa0,0xf1,0x3d,0x90,
+   0x40,0x5d,0x6a,0xe5,0x53,0x86,0xbd,0x28,
+   0xbd,0xd2,0x19,0xb8,0xa0,0x8d,0xed,0x1a,
+   0xa8,0x36,0xef,0xcc,0x8b,0x77,0x0d,0xc7,
+   0xda,0x41,0x59,0x7c,0x51,0x57,0x48,0x8d,
+   0x77,0x24,0xe0,0x3f,0xb8,0xd8,0x4a,0x37,
+   0x6a,0x43,0xb8,0xf4,0x15,0x18,0xa1,0x1c,
+   0xc3,0x87,0xb6,0x69,0xb2,0xee,0x65,0x86
+};
+static const unsigned char CHACHA20_vector_1[64] = {
+   0x9f,0x07,0xe7,0xbe,0x55,0x51,0x38,0x7a,
+   0x98,0xba,0x97,0x7c,0x73,0x2d,0x08,0x0d,
+   0xcb,0x0f,0x29,0xa0,0x48,0xe3,0x65,0x69,
+   0x12,0xc6,0x53,0x3e,0x32,0xee,0x7a,0xed,
+   0x29,0xb7,0x21,0x76,0x9c,0xe6,0x4e,0x43,
+   0xd5,0x71,0x33,0xb0,0x74,0xd8,0x39,0xd5,
+   0x31,0xed,0x1f,0x28,0x51,0x0a,0xfb,0x45,
+   0xac,0xe1,0x0a,0x1f,0x4b,0x79,0x4d,0x6f
+};
+
+static void test_crypto_prng(abts_case *tc, void *data)
+{
+    /* Use all zeros seed for deterministic random data */
+    static const unsigned char seed[APR_CRYPTO_PRNG_SEED_SIZE];
+    unsigned char randbytes[64];
+    apr_pool_t *pool = NULL;
+    apr_crypto_prng_t *cprng;
+    apr_status_t rv;
+
+    apr_pool_create(&pool, NULL);
+    rv = apr_crypto_prng_create(&cprng, 0, 0, seed, pool);
+    ABTS_ASSERT(tc, "failed to apr_crypto_prng_create", rv == APR_SUCCESS);
+
+    rv = apr_crypto_prng_bytes(cprng, randbytes, 64);
+    ABTS_ASSERT(tc, "failed to apr_crypto_prng_bytes", rv == APR_SUCCESS);
+    ABTS_ASSERT(tc, "test vector 0 for CHACHA20 should match",
+                /* first 32 bytes (256 bits) are used for the next key */
+                memcmp(randbytes, CHACHA20_vector_0 + 32, 64 - 32) == 0);
+
+    rv = apr_crypto_prng_reseed(cprng, NULL);
+    ABTS_ASSERT(tc, "failed to apr_crypto_prng_reseed", rv == APR_SUCCESS);
+    ABTS_ASSERT(tc, "test vector 1 for CHACHA20 shouldn't match after reseed",
+                memcmp(randbytes, CHACHA20_vector_1 + 32, 64 - 32) != 0);
+
+    rv = apr_crypto_prng_destroy(cprng);
+    ABTS_ASSERT(tc, "failed to apr_crypto_prng_destroy", rv == APR_SUCCESS);
+
+    apr_pool_destroy(pool);
+}
+
+#if 0
+static void test_crypto_random(abts_case *tc, void *data)
+{
+}
+
+#if APR_HAS_THREADS
+static void test_crypto_thread_random(abts_case *tc, void *data)
+{
+}
+#endif
+#endif
+
 abts_suite *testcrypto(abts_suite *suite)
 {
     suite = ADD_SUITE(suite);
@@ -1529,6 +1641,14 @@ abts_suite *testcrypto(abts_suite *suite)
     abts_run_test(suite, test_crypto_memzero, NULL);
     abts_run_test(suite, test_crypto_equals, NULL);
 
+    abts_run_test(suite, test_crypto_prng, NULL);
+#if 0
+    abts_run_test(suite, test_crypto_random, NULL);
+#if APR_HAS_THREADS
+    abts_run_test(suite, test_crypto_thread_random, NULL);
+#endif
+#endif
+
     return suite;
 }
 

Reply via email to