summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYann Ylavic <ylavic@apache.org>2018-06-11 22:06:09 +0000
committerYann Ylavic <ylavic@apache.org>2018-06-11 22:06:09 +0000
commit214bee3ec4c25be010a0b94091da61de005c604c (patch)
treeed79099e853bca1344dfd7a8bd18a6d47b2a6e42
parentecbddf2ead64b58a9c17136113eb0b91cd858a59 (diff)
downloadapr-214bee3ec4c25be010a0b94091da61de005c604c.tar.gz
Cryptographic Pseudo Random Number Generator (CPRNG).
New apr_crypto_prng API and apr_crypto[_thread]_random_bytes() functions. Allows to generate cryptographically secure random bytes indefinitely given an initial seed of 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 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). For now it's only implemented with the OpenSSL library as underlying crypto, that is --with-crypto --with-openssl needs to be configured, and the latter links libcrypto with APR. git-svn-id: https://svn.apache.org/repos/asf/apr/apr/trunk@1833359 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--CHANGES3
-rw-r--r--CMakeLists.txt1
-rw-r--r--build.conf1
-rw-r--r--build/crypto.m411
-rw-r--r--crypto/apr_crypto.c12
-rw-r--r--crypto/apr_crypto_prng.c471
-rw-r--r--include/apr.h.in1
-rw-r--r--include/apr_crypto.h141
-rw-r--r--test/testcrypto.c241
-rw-r--r--threadproc/unix/proc.c4
10 files changed, 884 insertions, 2 deletions
diff --git a/CHANGES b/CHANGES
index ae24bb443..1922c5b7e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,9 @@
-*- coding: utf-8 -*-
Changes for APR 2.0.0
+ *) New apr_crypto_prng API and apr_crypto[_thread]_random_bytes() functions.
+ [Yann Ylavic]
+
*) apr_file_write: Optimize large reads from buffered files on Windows.
[Evgeny Kotkov]
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9a36de9c2..f347531ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
diff --git a/build.conf b/build.conf
index d01fd1dc6..1dfc3b4ed 100644
--- a/build.conf
+++ b/build.conf
@@ -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
diff --git a/build/crypto.m4 b/build/crypto.m4
index e972494d7..7d69ed027 100644
--- a/build/crypto.m4
+++ b/build/crypto.m4
@@ -23,6 +23,7 @@ dnl APU_CHECK_CRYPTO: look for crypto libraries and headers
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
@@ -153,6 +159,11 @@ AC_DEFUN([APU_CHECK_CRYPTO_OPENSSL], [
LIBS="$old_libs"
CPPFLAGS="$old_cppflags"
LDFLAGS="$old_ldflags"
+
+ if test "$apu_have_openssl" = "1"; then
+ APR_ADDTO(APRUTIL_EXPORT_LIBS, [-lcrypto])
+ APR_ADDTO(LIBS, [-lcrypto])
+ fi
])
AC_DEFUN([APU_CHECK_CRYPTO_NSS], [
diff --git a/crypto/apr_crypto.c b/crypto/apr_crypto.c
index 3b50ca531..821775f47 100644
--- a/crypto/apr_crypto.c
+++ b/crypto/apr_crypto.c
@@ -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_t *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)
diff --git a/crypto/apr_crypto_prng.c b/crypto/apr_crypto_prng.c
new file mode 100644
index 000000000..cbbf3c4fe
--- /dev/null
+++ b/crypto/apr_crypto_prng.c
@@ -0,0 +1,471 @@
+/* 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" 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 (APR_CRYPTO_PRNG_KEY_SIZE * (8 - 1))
+#define APR_CRYPTO_PRNG_BUF_SIZE_DEF (APR_CRYPTO_PRNG_KEY_SIZE * (24 - 1))
+
+#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
+ return APR_ENOTIMPL;
+#else
+ apr_status_t rv;
+ rv = apr_threadkey_private_create(&cprng_thread_key,
+ cprng_thread_destroy, pool);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+#endif
+ }
+
+#if APR_HAS_THREADS
+ /* Global CPRNG is locked */
+ flags = (flags | APR_CRYPTO_PRNG_LOCKED) & ~APR_CRYPTO_PRNG_PER_THREAD;
+#endif
+ return apr_crypto_prng_create(&cprng_global, bufsize, flags, seed, pool);
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_term(void)
+{
+ if (!cprng_global) {
+ return APR_EINIT;
+ }
+
+ apr_crypto_prng_destroy(cprng_global);
+ cprng_global = NULL;
+
+ return APR_SUCCESS;
+}
+
+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, 0, 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);
+ 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 !APR_HAS_THREADS
+ if (flags & (APR_CRYPTO_PRNG_LOCKED | APR_CRYPTO_PRNG_PER_THREAD)) {
+ return APR_ENOTIMPL;
+ }
+#endif
+
+ 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) {
+ cprng_cleanup(cprng);
+ 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 APR_HAS_THREADS
+ if (flags & APR_CRYPTO_PRNG_LOCKED) {
+ rv = apr_thread_mutex_create(&cprng->lock, APR_THREAD_MUTEX_DEFAULT,
+ pool);
+ if (rv != APR_SUCCESS) {
+ cprng_cleanup(cprng);
+ return rv;
+ }
+ }
+#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) {
+ rv = apr_thread_mutex_lock(cprng->lock);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+#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_status_t rt = apr_thread_mutex_unlock(cprng->lock);
+ if (rv == APR_SUCCESS && rt != APR_SUCCESS) {
+ rv = rt;
+ }
+ }
+#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) {
+ rv = apr_thread_mutex_lock(cprng->lock);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+#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_status_t rt = apr_thread_mutex_unlock(cprng->lock);
+ if (rv == APR_SUCCESS && rt != APR_SUCCESS) {
+ rv = rt;
+ }
+ }
+#endif
+
+ return APR_SUCCESS;
+}
+
+#endif /* APU_HAVE_CRYPTO_PRNG */
+#endif /* APU_HAVE_CRYPTO */
diff --git a/include/apr.h.in b/include/apr.h.in
index ea936158a..2cdb555e4 100644
--- a/include/apr.h.in
+++ b/include/apr.h.in
@@ -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@
diff --git a/include/apr_crypto.h b/include/apr_crypto.h
index 68b196ad6..0ca8e524c 100644
--- a/include/apr_crypto.h
+++ b/include/apr_crypto.h
@@ -495,6 +495,147 @@ APR_DECLARE(apr_status_t) apr_crypto_cleanup(apr_crypto_t *f);
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.
+ *
+ * @return APR_EINIT if \ref apr_crypto_prng_init() was not called.
+ */
+APR_DECLARE(apr_status_t) 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,
+ * APR_ENOTIMPL if \ref APR_CRYPTO_PRNG_LOCKED with !APR_HAS_THREADS,
+ * 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 */
/** @} */
diff --git a/test/testcrypto.c b/test/testcrypto.c
index 865cffc6d..372097409 100644
--- a/test/testcrypto.c
+++ b/test/testcrypto.c
@@ -22,6 +22,8 @@
#include "apr_dso.h"
#include "apr_crypto.h"
#include "apr_strings.h"
+#include "apr_file_io.h"
+#include "apr_thread_proc.h"
#if APU_HAVE_CRYPTO
@@ -554,9 +556,16 @@ static void test_crypto_init(abts_case *tc, void *data)
{
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(apr_pool_parent_get(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 +1459,230 @@ static void test_crypto_equals(abts_case *tc, void *data)
TEST_SCALAR_MATCH(6, p, 0);
}
+#if APU_HAVE_OPENSSL
+#include <openssl/obj_mac.h> /* for NID_* */
+#endif
+#if defined(NID_chacha20)
+/*
+ * KAT for CHACHA20 with key, IV and plaintext full of zeros:
+ * $ openssl enc -chacha20 -in /dev/zero \
+ * -K `printf "%.64d" 0` -iv `printf "%.32d" 0` \
+ * -e | xxd -l128 -i -c8
+ */
+static const unsigned char test_PRNG_kat0[128] = {
+ 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,
+ 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
+};
+#else
+/*
+ * KAT for AES256-CTR with key, IV and plaintext full of zeros:
+ * $ openssl enc -aes-256-ctr -in /dev/zero \
+ * -K `printf "%.64d" 0` -iv `printf "%.32d" 0` \
+ * -e | xxd -l128 -i -c8
+ */
+static const unsigned char test_PRNG_kat0[128] = {
+ 0xdc, 0x95, 0xc0, 0x78, 0xa2, 0x40, 0x89, 0x89,
+ 0xad, 0x48, 0xa2, 0x14, 0x92, 0x84, 0x20, 0x87,
+ 0x53, 0x0f, 0x8a, 0xfb, 0xc7, 0x45, 0x36, 0xb9,
+ 0xa9, 0x63, 0xb4, 0xf1, 0xc4, 0xcb, 0x73, 0x8b,
+ 0xce, 0xa7, 0x40, 0x3d, 0x4d, 0x60, 0x6b, 0x6e,
+ 0x07, 0x4e, 0xc5, 0xd3, 0xba, 0xf3, 0x9d, 0x18,
+ 0x72, 0x60, 0x03, 0xca, 0x37, 0xa6, 0x2a, 0x74,
+ 0xd1, 0xa2, 0xf5, 0x8e, 0x75, 0x06, 0x35, 0x8e,
+ 0xdd, 0x4a, 0xb1, 0x28, 0x4d, 0x4a, 0xe1, 0x7b,
+ 0x41, 0xe8, 0x59, 0x24, 0x47, 0x0c, 0x36, 0xf7,
+ 0x47, 0x41, 0xcb, 0xe1, 0x81, 0xbb, 0x7f, 0x30,
+ 0x61, 0x7c, 0x1d, 0xe3, 0xab, 0x0c, 0x3a, 0x1f,
+ 0xd0, 0xc4, 0x8f, 0x73, 0x21, 0xa8, 0x2d, 0x37,
+ 0x60, 0x95, 0xac, 0xe0, 0x41, 0x91, 0x67, 0xa0,
+ 0xbc, 0xaf, 0x49, 0xb0, 0xc0, 0xce, 0xa6, 0x2d,
+ 0xe6, 0xbc, 0x1c, 0x66, 0x54, 0x5e, 0x1d, 0xad
+};
+#endif
+
+static void test_crypto_prng(abts_case *tc, void *data)
+{
+ unsigned char randbytes[128], seed[APR_CRYPTO_PRNG_SEED_SIZE];
+ apr_crypto_prng_t *cprng;
+ apr_pool_t *pool = NULL;
+ apr_status_t rv;
+ int i;
+
+ rv = apr_pool_create(&pool, NULL);
+ ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+ ABTS_PTR_NOTNULL(tc, pool);
+
+ for (i = 0; i < 10; ++i) {
+ /* Initial seed full of zeros (deterministic) */
+ memset(seed, 0, sizeof(seed));
+
+ rv = apr_crypto_prng_create(&cprng, 0, 0, seed, pool);
+ ABTS_ASSERT(tc, "apr_crypto_prng_create failed", rv == APR_SUCCESS);
+
+ /* Second time and more, change one bit of the seed */
+ if (i != 0) {
+ unsigned char pos = 0;
+ rv = apr_generate_random_bytes(&pos, sizeof pos);
+ ABTS_ASSERT(tc, "apr_generate_random_bytes failed",
+ rv == APR_SUCCESS);
+
+ seed[pos % APR_CRYPTO_PRNG_SEED_SIZE] = 1;
+ rv = apr_crypto_prng_reseed(cprng, seed);
+ ABTS_ASSERT(tc, "apr_crypto_prng_reseed failed",
+ rv == APR_SUCCESS);
+ }
+
+ rv = apr_crypto_prng_bytes(cprng, randbytes, 128 - 32);
+ ABTS_ASSERT(tc, "apr_crypto_prng_bytes failed", rv == APR_SUCCESS);
+
+ /* Should match the first time only */
+ if (i != 0) {
+ ABTS_ASSERT(tc, "test vector should not match",
+ /* first 32 bytes (256 bits) are used for the next key */
+ memcmp(randbytes, test_PRNG_kat0 + 32, 128 - 32) != 0);
+ }
+ else {
+ ABTS_ASSERT(tc, "test vector should match",
+ /* first 32 bytes (256 bits) are used for the next key */
+ memcmp(randbytes, test_PRNG_kat0 + 32, 128 - 32) == 0);
+ }
+
+ rv = apr_crypto_prng_destroy(cprng);
+ ABTS_ASSERT(tc, "apr_crypto_prng_destroy failed", rv == APR_SUCCESS);
+ }
+
+ apr_pool_destroy(pool);
+}
+
+#if APR_HAS_FORK
+static void test_crypto_fork_random(abts_case *tc, void *data)
+{
+ unsigned char randbytes[1024];
+ apr_pool_t *pool = NULL;
+ apr_file_t *pread = NULL;
+ apr_file_t *pwrite = NULL;
+ apr_size_t nbytes;
+ apr_proc_t proc;
+ apr_status_t rv;
+
+ rv = apr_pool_create(&pool, NULL);
+ ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+ ABTS_PTR_NOTNULL(tc, pool);
+
+ rv = apr_file_pipe_create(&pread, &pwrite, p);
+ ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+ ABTS_PTR_NOTNULL(tc, pread);
+ ABTS_PTR_NOTNULL(tc, pwrite);
+
+ rv = apr_proc_fork(&proc, pool);
+ if (rv == APR_INCHILD) {
+ apr_file_close(pread);
+ rv = apr_crypto_random_bytes(randbytes, 1024);
+ if (rv == APR_SUCCESS) {
+ apr_file_write_full(pwrite, randbytes, 1024, &nbytes);
+ }
+ apr_file_close(pwrite);
+
+ exit(rv != APR_SUCCESS);
+ }
+ else if (rv == APR_INPARENT) {
+ int exitcode;
+ apr_exit_why_e why;
+ unsigned char childbytes[1024];
+
+ apr_file_close(pwrite);
+ rv = apr_file_read_full(pread, childbytes, 1024, &nbytes);
+ ABTS_INT_EQUAL(tc, (int)nbytes, 1024);
+ apr_file_close(pread);
+
+ apr_proc_wait(&proc, &exitcode, &why, APR_WAIT);
+ if (why != APR_PROC_EXIT) {
+ ABTS_FAIL(tc, "child terminated abnormally");
+ }
+ else if (exitcode != 0) {
+ ABTS_FAIL(tc, "apr_crypto_random_bytes failed in child");
+ }
+
+ rv = apr_crypto_random_bytes(randbytes, 1024);
+ ABTS_ASSERT(tc, "apr_crypto_random_bytes failed in parent",
+ rv == APR_SUCCESS);
+ ABTS_ASSERT(tc, "parent and child generated same random bytes",
+ memcmp(randbytes, childbytes, 1024) != 0);
+ }
+ else {
+ ABTS_FAIL(tc, "apr_proc_fork failed");
+ }
+
+ apr_pool_destroy(pool);
+}
+#endif
+
+#if APR_HAS_THREADS
+#define NUM_THREADS 8
+
+static void *APR_THREAD_FUNC thread_func(apr_thread_t *thd, void *data)
+{
+ unsigned char *randbytes = data;
+ apr_status_t rv;
+
+ rv = apr_crypto_thread_random_bytes(randbytes, 800);
+ apr_thread_exit(thd, rv);
+
+ return NULL;
+}
+
+static void test_crypto_thread_random(abts_case *tc, void *data)
+{
+ static unsigned char zerobytes[800];
+ unsigned char *randbytes[NUM_THREADS];
+ apr_thread_t *threads[NUM_THREADS];
+ apr_pool_t *pool = NULL;
+ apr_status_t rv, ret;
+ int i, j;
+
+ rv = apr_pool_create(&pool, NULL);
+ ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
+ ABTS_PTR_NOTNULL(tc, pool);
+
+ for (i = 0; i < NUM_THREADS; ++i) {
+ randbytes[i] = apr_pcalloc(pool, 800);
+ rv = apr_thread_create(&threads[i], NULL, thread_func,
+ randbytes[i], pool);
+ ABTS_ASSERT(tc, "apr_thread_create failed", rv == APR_SUCCESS);
+ }
+ for (i = 0; i < NUM_THREADS; ++i) {
+ rv = apr_thread_join(&ret, threads[i]);
+ ABTS_ASSERT(tc, "apr_thread_join failed", rv == APR_SUCCESS);
+ ABTS_ASSERT(tc, "apr_crypto_thread_random_bytes failed",
+ ret == APR_SUCCESS);
+ }
+ for (i = 0; i < NUM_THREADS; ++i) {
+ ABTS_ASSERT(tc, "some thread generated zero bytes",
+ memcmp(randbytes[i], zerobytes, 800) != 0);
+ for (j = 0; j < i; ++j) {
+ ABTS_ASSERT(tc, "two threads generated same random bytes",
+ memcmp(randbytes[i], randbytes[j], 800) != 0);
+ }
+ }
+
+ apr_pool_destroy(pool);
+}
+#endif
+
abts_suite *testcrypto(abts_suite *suite)
{
suite = ADD_SUITE(suite);
@@ -1529,6 +1762,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 APR_HAS_FORK
+ abts_run_test(suite, test_crypto_fork_random, NULL);
+#endif
+#if APR_HAS_THREADS
+ abts_run_test(suite, test_crypto_thread_random, NULL);
+#endif
+
return suite;
}
diff --git a/threadproc/unix/proc.c b/threadproc/unix/proc.c
index 004772ff8..de9720045 100644
--- a/threadproc/unix/proc.c
+++ b/threadproc/unix/proc.c
@@ -19,6 +19,7 @@
#include "apr_portable.h"
#include "apr_signal.h"
#include "apr_random.h"
+#include "apr_crypto.h"
/* Heavy on no'ops, here's what we want to pass if there is APR_NO_FILE
* requested for a specific child handle;
@@ -229,6 +230,9 @@ APR_DECLARE(apr_status_t) apr_proc_fork(apr_proc_t *proc, apr_pool_t *pool)
proc->pid = getpid();
apr_random_after_fork(proc);
+#if APU_HAVE_CRYPTO_PRNG
+ apr_crypto_prng_after_fork();
+#endif
return APR_INCHILD;
}