summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crypto/apr_crypto_prng.c446
-rw-r--r--include/apr_crypto.h41
-rw-r--r--test/testcrypto.c90
-rw-r--r--threadproc/unix/proc.c17
4 files changed, 420 insertions, 174 deletions
diff --git a/crypto/apr_crypto_prng.c b/crypto/apr_crypto_prng.c
index 4b302fb33..69e1de976 100644
--- a/crypto/apr_crypto_prng.c
+++ b/crypto/apr_crypto_prng.c
@@ -18,6 +18,22 @@
* 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]).
+ *
+ * The CPRNG key is changed as soon as it's used to initialize the stream
+ * cipher, so it never resides in memory at the same time as the keystream
+ * it produced (a.k.a. the random bytes, which for efficiency are pooled).
+ *
+ * Likewise, the keystream is always cleared from the internal state before
+ * being returned to the user, thus there is no way to recover the produced
+ * random bytes afterward (e.g. from a memory/core dump after a crash).
+ *
+ * IOW, this CPRNG ensures forward secrecy, one may want to run it in a process
+ * and/or environment protected from live memory eavesdropping, thus keep the
+ * pooled/future random bytes secret by design, and use it as a replacement
+ * for some blocking/inefficient system RNG. The random bytes could then be
+ * serviced through a named pipe/socket, RPC, or any specific API. This is
+ * outside the scope of this/below code, though.
+ *
* [1] https://blog.cr.yp.to/20170723-random.html
* [2] https://libpqcrypto.org/
*/
@@ -29,6 +45,7 @@
#if APU_HAVE_CRYPTO
#if APU_HAVE_CRYPTO_PRNG
+#include "apr_ring.h"
#include "apr_pools.h"
#include "apr_strings.h"
#include "apr_thread_mutex.h"
@@ -36,19 +53,28 @@
#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
+#define CPRNG_KEY_SIZE 32
+
+/* Be consistent with the .h (the seed is xor-ed with key on reseed). */
+#if CPRNG_KEY_SIZE != APR_CRYPTO_PRNG_SEED_SIZE
+#error apr_crypto_prng handles stream ciphers with 256bit keys 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))
+#define CPRNG_BUF_SIZE_MIN (CPRNG_KEY_SIZE * (8 - 1))
+#define CPRNG_BUF_SIZE_DEF (CPRNG_KEY_SIZE * (24 - 1))
#if APU_HAVE_OPENSSL
#include <openssl/evp.h>
#include <openssl/sha.h>
+/* We only handle Chacha20 and AES256-CTR stream ciphers, for now.
+ * AES256-CTR should be in any openssl version of this century but is used
+ * only if Chacha20 is missing (openssl < 1.1). This is because Chacha20 is
+ * fast (enough) in software and timing attacks safe, though AES256-CTR can
+ * be faster and constant-time but only when the CPU (aesni) or some crypto
+ * hardware are in the place.
+ */
#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! */
@@ -93,30 +119,43 @@ void cprng_stream_ctx_free(cprng_stream_ctx_t *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)
+apr_status_t cprng_stream_ctx_bytes(cprng_stream_ctx_t **pctx,
+ unsigned char *key, unsigned char *to,
+ apr_size_t n, const unsigned char *z)
{
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;
-}
-
-static apr_status_t cprng_hash_to_seed(pid_t pid, unsigned char seed[])
-{
- SHA256_CTX ctx;
+ /* Can be called for rekeying (key != NULL), streaming more bytes
+ * with the current key (n != 0), or both successively in a one go.
+ */
+ if (key) {
+ /* We never encrypt twice with the same key, so we can use an
+ * all zeros IV. Also EVP_EncryptInit() can be called multiple
+ * times and it will recycle its previous/replaced resources by
+ * itself, so since padding is disabled/irrelevant too we don't
+ * need EVP_EncryptFinish() after each call or before rekeying.
+ */
+#if defined(NID_chacha20)
+ /* With CHACHA20, iv=NULL is the same as zeros but it's faster
+ * to (re-)init; use that for efficiency.
+ */
+ EVP_EncryptInit_ex(ctx, NULL, NULL, key, NULL);
+#else
+ /* With AES256-CTR, iv=NULL seems to peek up and random one (for
+ * the initial CTR), while we can live with zeros (fixed CTR);
+ * efficiency still.
+ */
+ EVP_EncryptInit_ex(ctx, NULL, NULL, key, z);
+#endif
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
- SHA256_Init(&ctx);
- SHA256_Update(&ctx, &pid, sizeof(pid));
- SHA256_Final(seed, &ctx);
+ memset(key, 0, CPRNG_KEY_SIZE);
+ EVP_EncryptUpdate(ctx, key, &len, key, CPRNG_KEY_SIZE);
+ }
+ if (n) {
+ EVP_EncryptUpdate(ctx, to, &len, z, n);
+ }
return APR_SUCCESS;
}
@@ -129,10 +168,11 @@ static apr_status_t cprng_hash_to_seed(pid_t pid, unsigned char seed[])
#endif /* APU_HAVE_OPENSSL */
struct apr_crypto_prng_t {
+ APR_RING_ENTRY(apr_crypto_prng_t) link;
apr_pool_t *pool;
cprng_stream_ctx_t *ctx;
#if APR_HAS_THREADS
- apr_thread_mutex_t *lock;
+ apr_thread_mutex_t *mutex;
#endif
unsigned char *key, *buf;
apr_size_t len, pos;
@@ -140,10 +180,29 @@ struct apr_crypto_prng_t {
};
static apr_crypto_prng_t *cprng_global = NULL;
+static APR_RING_HEAD(apr_cprng_ring, apr_crypto_prng_t) *cprng_ring;
#if APR_HAS_THREADS
+static apr_thread_mutex_t *cprng_ring_mutex;
+
static apr_threadkey_t *cprng_thread_key = NULL;
+#define cprng_lock(g) \
+ if ((g)->mutex) \
+ apr_thread_mutex_lock((g)->mutex)
+
+#define cprng_unlock(g) \
+ if ((g)->mutex) \
+ apr_thread_mutex_unlock((g)->mutex)
+
+#define cprng_ring_lock() \
+ if (cprng_ring_mutex) \
+ apr_thread_mutex_lock(cprng_ring_mutex)
+
+#define cprng_ring_unlock() \
+ if (cprng_ring_mutex) \
+ apr_thread_mutex_unlock(cprng_ring_mutex)
+
static void cprng_thread_destroy(void *cprng)
{
if (!cprng_global) {
@@ -152,7 +211,13 @@ static void cprng_thread_destroy(void *cprng)
}
apr_crypto_prng_destroy(cprng);
}
-#endif
+
+#else /* !APR_HAS_THREADS */
+#define cprng_lock(g)
+#define cprng_unlock(g)
+#define cprng_ring_lock()
+#define cprng_ring_unlock()
+#endif /* !APR_HAS_THREADS */
APR_DECLARE(apr_status_t) apr_crypto_prng_init(apr_pool_t *pool,
apr_size_t bufsize,
@@ -182,10 +247,25 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_init(apr_pool_t *pool,
#endif
}
+ cprng_ring = apr_palloc(pool, sizeof(*cprng_ring));
+ if (!cprng_ring) {
+ return APR_ENOMEM;
+ }
+ APR_RING_INIT(cprng_ring, apr_crypto_prng_t, link);
+
#if APR_HAS_THREADS
- /* Global CPRNG is locked */
+ rv = apr_thread_mutex_create(&cprng_ring_mutex, APR_THREAD_MUTEX_DEFAULT,
+ pool);
+ if (rv != APR_SUCCESS) {
+ apr_threadkey_private_delete(cprng_thread_key);
+ cprng_thread_key = NULL;
+ return rv;
+ }
+
+ /* Global CPRNG is locked (and obviously not per-thread) */
flags = (flags | APR_CRYPTO_PRNG_LOCKED) & ~APR_CRYPTO_PRNG_PER_THREAD;
#endif
+
return apr_crypto_prng_create(&cprng_global, bufsize, flags, seed, pool);
}
@@ -201,26 +281,6 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_term(void)
return APR_SUCCESS;
}
-APR_DECLARE(apr_status_t) apr_crypto_prng_after_fork(apr_proc_t *proc)
-{
- unsigned char seedb[APR_CRYPTO_PRNG_SEED_SIZE], *seed = NULL;
-
- if (!cprng_global) {
- return APR_EINIT;
- }
-
- if (proc) {
- apr_status_t rv;
- rv = cprng_hash_to_seed(proc->pid, seedb);
- if (rv != APR_SUCCESS) {
- return rv;
- }
- seed = seedb;
- }
-
- return apr_crypto_prng_reseed(cprng_global, seed);
-}
-
APR_DECLARE(apr_status_t) apr_crypto_random_bytes(void *buf, apr_size_t len)
{
if (!cprng_global) {
@@ -249,7 +309,8 @@ APR_DECLARE(apr_status_t) apr_crypto_random_thread_bytes(void *buf,
cprng = private;
if (!cprng) {
- rv = apr_crypto_prng_create(&cprng, 0, 0, NULL, NULL);
+ rv = apr_crypto_prng_create(&cprng, 0, APR_CRYPTO_PRNG_PER_THREAD,
+ NULL, NULL);
if (rv != APR_SUCCESS) {
return rv;
}
@@ -271,6 +332,15 @@ static apr_status_t cprng_cleanup(void *arg)
if (cprng == cprng_global) {
cprng_global = NULL;
+#if APR_HAS_THREADS
+ cprng_ring_mutex = NULL;
+#endif
+ cprng_ring = NULL;
+ }
+ else if (cprng_global && !(cprng->flags & APR_CRYPTO_PRNG_PER_THREAD)) {
+ cprng_ring_lock();
+ APR_RING_REMOVE(cprng, link);
+ cprng_ring_unlock();
}
if (cprng->ctx) {
@@ -278,7 +348,7 @@ static apr_status_t cprng_cleanup(void *arg)
}
if (cprng->key) {
- apr_crypto_memzero(cprng->key, APR_CRYPTO_PRNG_KEY_SIZE + cprng->len);
+ apr_crypto_memzero(cprng->key, CPRNG_KEY_SIZE + cprng->len);
}
if (!cprng->pool) {
@@ -299,7 +369,11 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_create(apr_crypto_prng_t **pcprng,
*pcprng = NULL;
- if (bufsize > APR_INT32_MAX - APR_CRYPTO_PRNG_KEY_SIZE
+ if (!cprng_global && pcprng != &cprng_global) {
+ return APR_EINIT;
+ }
+
+ if (bufsize > APR_INT32_MAX - CPRNG_KEY_SIZE
|| (flags & APR_CRYPTO_PRNG_LOCKED && !pool)
|| (flags & ~APR_CRYPTO_PRNG_MASK)) {
return APR_EINVAL;
@@ -324,30 +398,36 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_create(apr_crypto_prng_t **pcprng,
cprng->pool = pool;
if (bufsize == 0) {
- bufsize = APR_CRYPTO_PRNG_BUF_SIZE_DEF;
+ bufsize = CPRNG_BUF_SIZE_DEF;
}
- else if (bufsize < APR_CRYPTO_PRNG_BUF_SIZE_MIN) {
- bufsize = APR_CRYPTO_PRNG_BUF_SIZE_MIN;
+ else if (bufsize < CPRNG_BUF_SIZE_MIN) {
+ bufsize = CPRNG_BUF_SIZE_MIN;
}
- else if (bufsize % APR_CRYPTO_PRNG_KEY_SIZE) {
- bufsize += APR_CRYPTO_PRNG_KEY_SIZE;
- bufsize -= bufsize % APR_CRYPTO_PRNG_KEY_SIZE;
+ else if (bufsize % CPRNG_KEY_SIZE) {
+ bufsize += CPRNG_KEY_SIZE;
+ bufsize -= bufsize % CPRNG_KEY_SIZE;
}
if (pool) {
- cprng->key = apr_palloc(pool, APR_CRYPTO_PRNG_KEY_SIZE + bufsize);
+ cprng->key = apr_palloc(pool, CPRNG_KEY_SIZE + bufsize);
}
else {
- cprng->key = malloc(APR_CRYPTO_PRNG_KEY_SIZE + bufsize);
+ cprng->key = malloc(CPRNG_KEY_SIZE + bufsize);
}
if (!cprng->key) {
cprng_cleanup(cprng);
return APR_ENOMEM;
}
- cprng->buf = cprng->key + APR_CRYPTO_PRNG_KEY_SIZE;
+ cprng->buf = cprng->key + CPRNG_KEY_SIZE;
cprng->len = bufsize;
+ rv = cprng_stream_ctx_make(&cprng->ctx);
+ if (rv != APR_SUCCESS) {
+ cprng_cleanup(cprng);
+ return rv;
+ }
+
if (seed) {
- memset(cprng->key, 0, APR_CRYPTO_PRNG_KEY_SIZE);
+ memset(cprng->key, 0, CPRNG_KEY_SIZE);
}
rv = apr_crypto_prng_reseed(cprng, seed);
if (rv != APR_SUCCESS) {
@@ -357,7 +437,7 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_create(apr_crypto_prng_t **pcprng,
#if APR_HAS_THREADS
if (flags & APR_CRYPTO_PRNG_LOCKED) {
- rv = apr_thread_mutex_create(&cprng->lock, APR_THREAD_MUTEX_DEFAULT,
+ rv = apr_thread_mutex_create(&cprng->mutex, APR_THREAD_MUTEX_DEFAULT,
pool);
if (rv != APR_SUCCESS) {
cprng_cleanup(cprng);
@@ -366,10 +446,13 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_create(apr_crypto_prng_t **pcprng,
}
#endif
- rv = cprng_stream_ctx_make(&cprng->ctx);
- if (rv != APR_SUCCESS) {
- cprng_cleanup(cprng);
- return rv;
+ if (cprng_global && !(flags & APR_CRYPTO_PRNG_PER_THREAD)) {
+ cprng_ring_lock();
+ APR_RING_INSERT_TAIL(cprng_ring, cprng, apr_crypto_prng_t, link);
+ cprng_ring_unlock();
+ }
+ else {
+ APR_RING_ELEM_INIT(cprng, link);
}
if (pool) {
@@ -390,96 +473,106 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_destroy(apr_crypto_prng_t *cprng)
return apr_pool_cleanup_run(cprng->pool, cprng, cprng_cleanup);
}
+static apr_status_t cprng_stream_bytes(apr_crypto_prng_t *cprng,
+ unsigned char *key, void *to,
+ size_t len)
+{
+ apr_status_t rv;
+
+ rv = cprng_stream_ctx_bytes(&cprng->ctx, key, to, len, cprng->buf);
+ if (rv != APR_SUCCESS && len) {
+ apr_crypto_memzero(to, len);
+ }
+ return rv;
+}
+
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;
+ if (!cprng) {
+ /* Fall through with global CPRNG. */
+ cprng = cprng_global;
+ if (!cprng) {
+ return APR_EINIT;
}
}
-#endif
+ cprng_lock(cprng);
+
+ cprng->pos = cprng->len;
+ apr_crypto_memzero(cprng->buf, cprng->len);
if (seed) {
apr_size_t n = 0;
do {
cprng->key[n] ^= seed[n];
- } while (++n < APR_CRYPTO_PRNG_KEY_SIZE);
+ } while (++n < CPRNG_KEY_SIZE);
+ }
+ else if (cprng_global && cprng_global != cprng) {
+ /* Use the global CPRNG: no need for more than the initial entropy. */
+ rv = apr_crypto_random_bytes(cprng->key, CPRNG_KEY_SIZE);
}
else {
- rv = apr_generate_random_bytes(cprng->key, APR_CRYPTO_PRNG_KEY_SIZE);
+ /* Use the system entropy, i.e. one of "/dev/[u]random", getrandom(),
+ * arc4random()... This may block but still we really want to wait for
+ * the system to gather enough entropy for these 32 initial bytes, much
+ * more than we want non-random bytes, and that's once and for all!
+ */
+ rv = apr_generate_random_bytes(cprng->key, CPRNG_KEY_SIZE);
+ }
+ if (rv == APR_SUCCESS) {
+ /* Init/set the stream with the new key, without buffering for now
+ * so that the buffer and/or the next random bytes won't be generated
+ * directly from this key but from the first stream bytes it generates,
+ * i.e. the next key is always extracted from the stream cipher state
+ * and cleared upon use.
+ */
+ rv = cprng_stream_bytes(cprng, cprng->key, NULL, 0);
}
- 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
+ cprng_unlock(cprng);
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,
- void *buf, apr_size_t len)
+static apr_status_t cprng_bytes(apr_crypto_prng_t *cprng,
+ void *buf, apr_size_t len)
{
unsigned char *ptr = buf;
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) {
+ n = cprng->len - cprng->pos;
+ if (n == 0) {
+ n = cprng->len;
+ if (len >= n) {
do {
- rv = cprng_stream_mix(cprng, ptr);
+ rv = cprng_stream_bytes(cprng, cprng->key, ptr, n);
if (rv != APR_SUCCESS) {
return rv;
}
- ptr += cprng->len;
- len -= cprng->len;
- } while (len >= cprng->len);
+ ptr += n;
+ len -= n;
+ } while (len >= n);
if (!len) {
break;
}
}
- rv = cprng_stream_mix(cprng, cprng->buf);
+ rv = cprng_stream_bytes(cprng, cprng->key, cprng->buf, n);
if (rv != APR_SUCCESS) {
return rv;
}
cprng->pos = 0;
}
+ if (n > len) {
+ n = len;
+ }
/* 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(ptr, cprng->buf + cprng->pos, n);
apr_crypto_memzero(cprng->buf + cprng->pos, n);
cprng->pos += n;
@@ -488,17 +581,132 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_bytes(apr_crypto_prng_t *cprng,
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;
+ return APR_SUCCESS;
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_bytes(apr_crypto_prng_t *cprng,
+ void *buf, apr_size_t len)
+{
+ apr_status_t rv;
+
+ if (!cprng) {
+ /* Fall through with global CPRNG. */
+ cprng = cprng_global;
+ if (!cprng) {
+ return APR_EINIT;
}
}
-#endif
- return APR_SUCCESS;
+ cprng_lock(cprng);
+
+ rv = cprng_bytes(cprng, buf, len);
+
+ cprng_unlock(cprng);
+
+ return rv;
+}
+
+/* Reset the buffer and use the next stream bytes as the new key. */
+static apr_status_t cprng_newkey(apr_crypto_prng_t *cprng)
+{
+ cprng->pos = cprng->len;
+ apr_crypto_memzero(cprng->buf, cprng->len);
+ return cprng_stream_bytes(cprng, NULL, cprng->key, CPRNG_KEY_SIZE);
+}
+
+APR_DECLARE(apr_status_t) apr_crypto_prng_rekey(apr_crypto_prng_t *cprng)
+{
+ apr_status_t rv;
+
+ if (!cprng) {
+ /* Fall through with global CPRNG. */
+ cprng = cprng_global;
+ if (!cprng) {
+ return APR_EINIT;
+ }
+ }
+
+ cprng_lock(cprng);
+
+ /* Renew and apply the new key. */
+ rv = cprng_newkey(cprng);
+ if (rv == APR_SUCCESS) {
+ rv = cprng_stream_bytes(cprng, cprng->key, NULL, 0);
+ }
+
+ cprng_unlock(cprng);
+
+ if (cprng == cprng_global) {
+ /* Forward to all maintained CPRNGs. */
+ cprng_ring_lock();
+ for (cprng = APR_RING_FIRST(cprng_ring);
+ cprng != APR_RING_SENTINEL(cprng_ring, apr_crypto_prng_t, link);
+ cprng = APR_RING_NEXT(cprng, link)) {
+ apr_status_t rt = apr_crypto_prng_rekey(cprng);
+ if (rt != APR_SUCCESS && rv == APR_SUCCESS) {
+ rv = rt;
+ }
+ }
+ cprng_ring_unlock();
+ }
+
+ return rv;
+}
+
+#if APR_HAS_FORK
+APR_DECLARE(apr_status_t) apr_crypto_prng_after_fork(apr_crypto_prng_t *cprng,
+ int in_child)
+{
+ apr_status_t rv;
+
+ if (!cprng) {
+ /* Fall through with global CPRNG. */
+ cprng = cprng_global;
+ if (!cprng) {
+ return APR_EINIT;
+ }
+ }
+
+ cprng_lock(cprng);
+
+ /* Make sure the parent and child processes never share the same state, so
+ * renew the key first (also clears the buffer) for both parent and child,
+ * so that further fork()s (from parent or child) won't use the same state.
+ */
+ rv = cprng_newkey(cprng);
+ if (rv == APR_SUCCESS && !in_child) {
+ /* For the parent process only, renew a second time to ensure that key
+ * material is different from the child.
+ */
+ rv = cprng_stream_bytes(cprng, NULL, cprng->key, CPRNG_KEY_SIZE);
+ }
+ if (rv == APR_SUCCESS) {
+ /* Finally apply the new key, parent and child ones will now be
+ * different and unknown to each other (including at the stream ctx
+ * level).
+ */
+ rv = cprng_stream_bytes(cprng, cprng->key, NULL, 0);
+ }
+
+ cprng_unlock(cprng);
+
+ if (cprng == cprng_global) {
+ /* Forward to all maintained CPRNGs. */
+ cprng_ring_lock();
+ for (cprng = APR_RING_FIRST(cprng_ring);
+ cprng != APR_RING_SENTINEL(cprng_ring, apr_crypto_prng_t, link);
+ cprng = APR_RING_NEXT(cprng, link)) {
+ apr_status_t rt = apr_crypto_prng_after_fork(cprng, in_child);
+ if (rt != APR_SUCCESS && rv == APR_SUCCESS) {
+ rv = rt;
+ }
+ }
+ cprng_ring_unlock();
+ }
+
+ return rv;
}
+#endif /* APR_HAS_FORK */
#endif /* APU_HAVE_CRYPTO_PRNG */
#endif /* APU_HAVE_CRYPTO */
diff --git a/include/apr_crypto.h b/include/apr_crypto.h
index f8627b937..cd3e636d0 100644
--- a/include/apr_crypto.h
+++ b/include/apr_crypto.h
@@ -553,15 +553,6 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_init(apr_pool_t *pool,
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.
- *
- * @param proc The child process (including its PID).
- * @return Any system error (APR_ENOMEM, ...).
- */
-APR_DECLARE(apr_status_t) apr_crypto_prng_after_fork(apr_proc_t *proc);
-
-/**
* @brief Generate cryptographically secure random bytes from the global CPRNG.
*
* @param buf The destination buffer
@@ -618,9 +609,17 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_create(apr_crypto_prng_t **pcprng,
APR_DECLARE(apr_status_t) apr_crypto_prng_destroy(apr_crypto_prng_t *cprng);
/**
- * @brief Reseed a standalone CPRNG.
+ * @brief Rekey a CPRNG.
+ *
+ * @param cprng The CPRNG, or NULL for all the created CPRNGs (but per-thread).
+ * @return Any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_rekey(apr_crypto_prng_t *cprng);
+
+/**
+ * @brief Reseed a CPRNG.
*
- * @param cprng The CPRNG to reseed.
+ * @param cprng The CPRNG to reseed, or NULL for the global CPRNG.
* @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, ...).
@@ -628,16 +627,32 @@ APR_DECLARE(apr_status_t) apr_crypto_prng_destroy(apr_crypto_prng_t *cprng);
APR_DECLARE(apr_status_t) apr_crypto_prng_reseed(apr_crypto_prng_t *cprng,
const unsigned char seed[]);
+#if APR_HAS_FORK
/**
- * @brief Generate cryptographically secure random bytes a standalone CPRNG.
+ * @brief Rekey a CPRNG in the parent and/or child process after a fork(),
+ * so that they don't share the same state.
+ *
+ * @param cprng The CPRNG, or NULL for all the created CPRNGs (but per-thread).
+ * @param in_child Whether in the child process (non zero), or in the parent
+ * process otherwise (zero).
*
- * @param cprng The CPRNG.
+ * @return Any system error (APR_ENOMEM, ...).
+ */
+APR_DECLARE(apr_status_t) apr_crypto_prng_after_fork(apr_crypto_prng_t *cprng,
+ int in_child);
+#endif
+
+/**
+ * @brief Generate cryptographically secure random bytes from a CPRNG.
+ *
+ * @param cprng The CPRNG, or NULL for the global 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,
void *buf, apr_size_t len);
+
#endif /* APU_HAVE_CRYPTO_PRNG */
#endif /* APU_HAVE_CRYPTO */
diff --git a/test/testcrypto.c b/test/testcrypto.c
index 2789adcb0..9d95e10c2 100644
--- a/test/testcrypto.c
+++ b/test/testcrypto.c
@@ -1460,53 +1460,63 @@ static void test_crypto_equals(abts_case *tc, void *data)
#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
+ * KAT for CHACHA20:
+ * # iv=$(printf "%.32d" 0)
+ * # key=$(printf "%.64d" 0)
+ * # key=$(openssl enc -chacha20 -e \
+ * -in /dev/zero -K $key -iv $iv \
+ * | xxd -l32 -c64 -p)
+ * # openssl enc -chacha20 -e \
+ * -in /dev/zero -K $key -iv $iv \
+ * | xxd -l128 -c8 -i
*/
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
+ 0xb0, 0xfd, 0x14, 0xff, 0x96, 0xa0, 0xbd, 0xa1,
+ 0x54, 0xc3, 0x29, 0x08, 0x2c, 0x9c, 0x65, 0x33,
+ 0xbb, 0x4c, 0x94, 0x73, 0xbf, 0x5d, 0xde, 0x13,
+ 0x8f, 0x82, 0xc9, 0xac, 0x55, 0x53, 0xd9, 0x58,
+ 0xaf, 0xbd, 0xad, 0x28, 0x45, 0xb9, 0x3c, 0xdb,
+ 0xb2, 0xfe, 0x64, 0x63, 0xd2, 0xfe, 0x16, 0x2a,
+ 0xda, 0xe0, 0xf6, 0xe6, 0x76, 0xf0, 0x49, 0x42,
+ 0x18, 0xf5, 0xce, 0x05, 0x96, 0xe7, 0x9f, 0x5c,
+ 0x55, 0x1a, 0xaa, 0x9b, 0xa4, 0x6f, 0xaa, 0xd5,
+ 0x28, 0xf6, 0x76, 0x3d, 0xde, 0x93, 0xc0, 0x3f,
+ 0xa3, 0xb1, 0x21, 0xb2, 0xff, 0xc0, 0x53, 0x3a,
+ 0x69, 0x5e, 0xd5, 0x6e, 0x8f, 0xda, 0x05, 0x89,
+ 0xa2, 0xed, 0xeb, 0xfa, 0xd4, 0xae, 0xd3, 0x35,
+ 0x7c, 0x7a, 0xad, 0xad, 0x93, 0x28, 0x02, 0x7b,
+ 0xb8, 0x79, 0xb5, 0x57, 0x47, 0x97, 0xa1, 0xb7,
+ 0x3d, 0xce, 0x7c, 0xd0, 0x9f, 0x24, 0x51, 0x01
};
#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
+ * KAT for AES256-CTR:
+ * # iv=$(printf "%.32d" 0)
+ * # key=$(printf "%.64d" 0)
+ * # key=$(openssl enc -aes-256-ctr -e \
+ * -in /dev/zero -K $key -iv $iv \
+ * | xxd -l32 -c64 -p)
+ * # openssl enc -aes-256-ctr -e \
+ * -in /dev/zero -K $key -iv $iv \
+ * | xxd -l128 -c8 -i
*/
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
+ 0x79, 0x04, 0x2a, 0x33, 0xfa, 0x41, 0x1a, 0x37,
+ 0x97, 0x3a, 0xec, 0xa0, 0xfc, 0xde, 0x6b, 0x2b,
+ 0x16, 0xa4, 0x5f, 0xa1, 0x2a, 0xe3, 0xf5, 0x4c,
+ 0x84, 0x28, 0x83, 0xeb, 0x60, 0xce, 0x44, 0xe9,
+ 0x9c, 0x4c, 0xa2, 0x6e, 0x70, 0xcc, 0x26, 0x68,
+ 0xf8, 0x99, 0x5a, 0xa1, 0x9f, 0xde, 0x99, 0xb9,
+ 0x80, 0x0b, 0xb6, 0x83, 0x14, 0x9d, 0x72, 0x93,
+ 0xf4, 0xd1, 0x49, 0xf3, 0xf0, 0x9e, 0x49, 0x80,
+ 0x76, 0x84, 0x01, 0x1e, 0x79, 0x9e, 0x70, 0x70,
+ 0x61, 0x7c, 0x13, 0xce, 0x2d, 0x64, 0xca, 0x08,
+ 0xb7, 0xc1, 0xd5, 0x61, 0xf1, 0x95, 0x5d, 0x1b,
+ 0x92, 0x8c, 0xd2, 0x70, 0xef, 0x26, 0xfe, 0x24,
+ 0x01, 0xd8, 0x65, 0x63, 0x68, 0x71, 0x09, 0x4e,
+ 0x7b, 0x01, 0x36, 0x19, 0x85, 0x13, 0x16, 0xfd,
+ 0xc5, 0x0c, 0xe6, 0x71, 0x42, 0xbf, 0x81, 0xb0,
+ 0xd1, 0x59, 0x28, 0xa1, 0x04, 0xe9, 0x8d, 0xad
};
#endif
diff --git a/threadproc/unix/proc.c b/threadproc/unix/proc.c
index f7d02d204..950405c09 100644
--- a/threadproc/unix/proc.c
+++ b/threadproc/unix/proc.c
@@ -223,22 +223,35 @@ APR_DECLARE(apr_status_t) apr_proc_fork(apr_proc_t *proc, apr_pool_t *pool)
memset(proc, 0, sizeof(apr_proc_t));
+ /* Rekey PRNG(s) to clear buffer(s) and make sure that the
+ * state(s) change between fork()s in any case.
+ */
+#if APU_HAVE_CRYPTO_PRNG
+ apr_crypto_prng_rekey(NULL);
+#endif
+
if ((pid = fork()) < 0) {
return errno;
}
else if (pid == 0) {
proc->pid = getpid();
- apr_random_after_fork(proc);
+ /* Do the work needed for children PRNG(s). */
#if APU_HAVE_CRYPTO_PRNG
- apr_crypto_prng_after_fork(proc);
+ apr_crypto_prng_after_fork(NULL, 1);
#endif
+ apr_random_after_fork(proc);
return APR_INCHILD;
}
proc->pid = pid;
+ /* Do the work needed for parent PRNG(s). */
+#if APU_HAVE_CRYPTO_PRNG
+ apr_crypto_prng_after_fork(NULL, 0);
+#endif
+
return APR_INPARENT;
}