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; }