diff options
-rw-r--r-- | crypto/apr_crypto_prng.c | 446 | ||||
-rw-r--r-- | include/apr_crypto.h | 41 | ||||
-rw-r--r-- | test/testcrypto.c | 90 | ||||
-rw-r--r-- | threadproc/unix/proc.c | 17 |
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; } |