blob: 820c11bafcb9221d3cbce3f88ba447e8fe501ba1 [file] [log] [blame]
/* 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]).
*
* 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/
*/
#include "apu.h"
#include "apr_crypto.h"
#include "apr_crypto_internal.h"
#include "apr_strings.h"
#if APU_HAVE_CRYPTO
#if APU_HAVE_CRYPTO_PRNG
#include "apr_ring.h"
#include "apr_pools.h"
#include "apr_thread_mutex.h"
#include "apr_thread_proc.h"
#include <stdlib.h> /* for malloc() */
#if APU_HAVE_OPENSSL
/** Recommended prng driver for this platform */
#define APU_CRYPTO_PRNG_RECOMMENDED_DRIVER "openssl"
#endif
/* 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 CPRNG_BUF_SIZE_MIN (CPRNG_KEY_SIZE * (8 - 1))
#define CPRNG_BUF_SIZE_DEF (CPRNG_KEY_SIZE * (24 - 1))
APR_TYPEDEF_STRUCT(apr_crypto_t,
apr_pool_t *pool;
apr_crypto_driver_t *provider;
)
struct apr_crypto_prng_t {
APR_RING_ENTRY(apr_crypto_prng_t) link;
apr_pool_t *pool;
apr_crypto_t *crypto;
cprng_stream_ctx_t *ctx;
#if APR_HAS_THREADS
apr_thread_mutex_t *mutex;
#endif
unsigned char *key, *buf;
apr_size_t len, pos;
int flags;
apr_crypto_cipher_e cipher;
};
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)
{
apr_threadkey_private_set(NULL, cprng_thread_key);
if (cprng) {
apr_crypto_prng_destroy(cprng);
}
if (!cprng_global) {
apr_threadkey_private_delete(cprng_thread_key);
cprng_thread_key = NULL;
}
}
#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_crypto_t *crypto,
apr_crypto_cipher_e cipher, apr_size_t bufsize, const unsigned char seed[], int flags)
{
apr_status_t rv;
if (cprng_global) {
return APR_EREINIT;
}
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 (flags & APR_CRYPTO_PRNG_PER_THREAD) {
#if !APR_HAS_THREADS
return APR_ENOTIMPL;
#else
rv = apr_threadkey_private_create(&cprng_thread_key,
cprng_thread_destroy, pool);
if (rv != APR_SUCCESS) {
return rv;
}
#endif
}
#if APR_HAS_THREADS
rv = apr_thread_mutex_create(&cprng_ring_mutex, APR_THREAD_MUTEX_DEFAULT,
pool);
if (rv != APR_SUCCESS) {
if (flags & APR_CRYPTO_PRNG_PER_THREAD) {
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
rv = apr_crypto_prng_create(&cprng_global, crypto, cipher, bufsize, flags,
seed, pool);
if (rv != APR_SUCCESS) {
if (flags & APR_CRYPTO_PRNG_PER_THREAD) {
apr_threadkey_private_delete(cprng_thread_key);
cprng_thread_key = NULL;
}
return rv;
}
return APR_SUCCESS;
}
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_random_bytes(void *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_random_thread_bytes(void *buf,
apr_size_t len)
{
apr_status_t rv;
apr_crypto_prng_t *cprng;
void *private = NULL;
if (!cprng_thread_key || !cprng_global) {
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, cprng_global->crypto, cprng_global->cipher, 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 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) {
cprng->crypto->provider->cprng_stream_ctx_free(cprng->ctx);
}
if (cprng->key) {
apr_memzero_explicit(cprng->key, CPRNG_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_crypto_t *crypto, apr_crypto_cipher_e cipher, 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 (!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;
}
#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->cipher = cipher;
cprng->flags = flags;
cprng->pool = pool;
if (bufsize == 0) {
bufsize = CPRNG_BUF_SIZE_DEF;
}
else if (bufsize < CPRNG_BUF_SIZE_MIN) {
bufsize = CPRNG_BUF_SIZE_MIN;
}
else if (bufsize % CPRNG_KEY_SIZE) {
bufsize += CPRNG_KEY_SIZE;
bufsize -= bufsize % CPRNG_KEY_SIZE;
}
if (pool) {
cprng->key = apr_palloc(pool, CPRNG_KEY_SIZE + bufsize);
}
else {
cprng->key = malloc(CPRNG_KEY_SIZE + bufsize);
}
if (!cprng->key) {
cprng_cleanup(cprng);
return APR_ENOMEM;
}
cprng->buf = cprng->key + CPRNG_KEY_SIZE;
cprng->len = bufsize;
if (crypto) {
cprng->crypto = crypto;
}
else if (cprng_global && cprng_global->crypto) {
cprng->crypto = cprng_global->crypto;
}
#ifdef APU_CRYPTO_PRNG_RECOMMENDED_DRIVER
else {
const apr_crypto_driver_t *driver = NULL;
if (!pool) {
return APR_EINVAL;
}
rv = apr_crypto_init(pool);
if (rv != APR_SUCCESS) {
cprng_cleanup(cprng);
return rv;
}
rv = apr_crypto_get_driver(&driver, APU_CRYPTO_PRNG_RECOMMENDED_DRIVER,
NULL, NULL, pool);
if (rv != APR_SUCCESS) {
cprng_cleanup(cprng);
return rv;
}
rv = apr_crypto_make(&cprng->crypto, driver, NULL, pool);
if (rv != APR_SUCCESS) {
cprng_cleanup(cprng);
return rv;
}
}
#else
else {
return APR_ENOTIMPL;
}
#endif
rv = cprng->crypto->provider->cprng_stream_ctx_make(&cprng->ctx,
cprng->crypto, cprng->cipher, pool);
if (rv != APR_SUCCESS) {
cprng_cleanup(cprng);
return rv;
}
if (seed) {
memset(cprng->key, 0, CPRNG_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->mutex, APR_THREAD_MUTEX_DEFAULT,
pool);
if (rv != APR_SUCCESS) {
cprng_cleanup(cprng);
return rv;
}
}
#endif
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) {
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);
}
static apr_status_t cprng_stream_bytes(apr_crypto_prng_t *cprng,
void *to, apr_size_t len)
{
apr_status_t rv;
rv = cprng->crypto->provider->cprng_stream_ctx_bytes(&cprng->ctx,
cprng->key, to, len, cprng->buf);
if (rv != APR_SUCCESS && len) {
apr_memzero_explicit(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 (!cprng) {
/* Fall through with global CPRNG. */
cprng = cprng_global;
if (!cprng) {
return APR_EINIT;
}
}
cprng_lock(cprng);
cprng->pos = cprng->len;
apr_memzero_explicit(cprng->buf, cprng->len);
if (seed) {
apr_size_t n = 0;
do {
cprng->key[n] ^= seed[n];
} 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 {
/* 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, NULL, 0);
}
cprng_unlock(cprng);
return rv;
}
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;
while (len) {
n = cprng->len - cprng->pos;
if (n == 0) {
n = cprng->len;
if (len >= n) {
do {
rv = cprng_stream_bytes(cprng, ptr, n);
if (rv != APR_SUCCESS) {
return rv;
}
ptr += n;
len -= n;
} while (len >= n);
if (!len) {
break;
}
}
rv = cprng_stream_bytes(cprng, 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.
*/
memcpy(ptr, cprng->buf + cprng->pos, n);
apr_memzero_explicit(cprng->buf + cprng->pos, n);
cprng->pos += n;
ptr += n;
len -= n;
}
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;
}
}
cprng_lock(cprng);
rv = cprng_bytes(cprng, buf, len);
cprng_unlock(cprng);
return rv;
}
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);
/* Clear state and renew the key. */
cprng->pos = cprng->len;
apr_memzero_explicit(cprng->buf, cprng->len);
rv = cprng_stream_bytes(cprng, 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 flags)
{
apr_status_t rv = APR_SUCCESS;
int is_child = flags & APR_CRYPTO_FORK_INCHILD;
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,
* and that further fork()s from either process will not either.
* This is done by rekeying (and clearing the buffers) in both processes,
* and by rekeying a second time in the parent process to ensure both that
* keys are different and that after apr_crypto_prng_after_fork() is called
* the keys are unknown to each other processes.
* The new key to be used by the parent process is generated in the same
* pass as the rekey, and since cprng_stream_bytes() is designed to burn
* and never reuse keys we are sure that this key is unique to the parent,
* and that nothing is left over from the initial state in both processes.
*/
cprng->pos = cprng->len;
apr_memzero_explicit(cprng->buf, cprng->len);
if (!is_child) {
rv = cprng_stream_bytes(cprng, cprng->key, CPRNG_KEY_SIZE);
}
if (rv == APR_SUCCESS) {
rv = cprng_stream_bytes(cprng, 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, flags);
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 */