diff options
Diffstat (limited to 'lib/nettle/rnd.c')
-rw-r--r-- | lib/nettle/rnd.c | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/lib/nettle/rnd.c b/lib/nettle/rnd.c new file mode 100644 index 0000000..cddf1f7 --- /dev/null +++ b/lib/nettle/rnd.c @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2010-2012 Free Software Foundation, Inc. + * Copyright (C) 2016-2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GNUTLS. + * + * The GNUTLS library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" +#include "errors.h" +#include <locks.h> +#include <num.h> +#include <nettle/chacha.h> +#include <rnd-common.h> +#include <system.h> +#include <atfork.h> +#include <errno.h> +#include <minmax.h> + +#define PRNG_KEY_SIZE CHACHA_KEY_SIZE + +/* For a high level description see the documentation and + * the 'Random number generation' section of chapter + * 'Using GnuTLS as a cryptographic library'. + */ + +/* We have two "refresh" operations for the PRNG: + * re-seed: the random generator obtains a new key from the system or another PRNG + * (occurs when a time or data-based limit is reached for the GNUTLS_RND_RANDOM + * and GNUTLS_RND_KEY levels and data-based for the nonce level) + * re-key: the random generator obtains a new key by utilizing its own output. + * This only happens for the GNUTLS_RND_KEY level, on every operation. + */ + +/* after this number of bytes PRNG will rekey using the system RNG */ +static const unsigned prng_reseed_limits[] = { + [GNUTLS_RND_NONCE] = 16*1024*1024, /* 16 MB - we re-seed using the GNUTLS_RND_RANDOM output */ + [GNUTLS_RND_RANDOM] = 2*1024*1024, /* 2MB - we re-seed by time as well */ + [GNUTLS_RND_KEY] = 2*1024*1024 /* same as GNUTLS_RND_RANDOM - but we re-key on every operation */ +}; + +static const time_t prng_reseed_time[] = { + [GNUTLS_RND_NONCE] = 14400, /* 4 hours */ + [GNUTLS_RND_RANDOM] = 7200, /* 2 hours */ + [GNUTLS_RND_KEY] = 7200 /* same as RANDOM */ +}; + +struct prng_ctx_st { + struct chacha_ctx ctx; + size_t counter; + unsigned int forkid; + time_t last_reseed; +}; + +struct generators_ctx_st { + struct prng_ctx_st nonce; /* GNUTLS_RND_NONCE */ + struct prng_ctx_st normal; /* GNUTLS_RND_RANDOM, GNUTLS_RND_KEY */ +}; + + +static void wrap_nettle_rnd_deinit(void *_ctx) +{ + gnutls_free(_ctx); +} + +/* Initializes the nonce level random generator. + * + * the @new_key must be provided. + * + * @init must be non zero on first initialization, and + * zero on any subsequent reinitializations. + */ +static int single_prng_init(struct prng_ctx_st *ctx, + uint8_t new_key[PRNG_KEY_SIZE], + unsigned new_key_size, + unsigned init) +{ + uint8_t nonce[CHACHA_NONCE_SIZE]; + + memset(nonce, 0, sizeof(nonce)); /* to prevent valgrind from whinning */ + + if (init == 0) { + /* use the previous key to generate IV as well */ + chacha_crypt(&ctx->ctx, sizeof(nonce), nonce, nonce); + + /* Add key continuity by XORing the new key with data generated + * from the old key */ + chacha_crypt(&ctx->ctx, new_key_size, new_key, new_key); + } else { + struct timespec now; /* current time */ + + ctx->forkid = _gnutls_get_forkid(); + + gnutls_gettime(&now); + memcpy(nonce, &now, MIN(sizeof(nonce), sizeof(now))); + ctx->last_reseed = now.tv_sec; + } + + chacha_set_key(&ctx->ctx, new_key); + chacha_set_nonce(&ctx->ctx, nonce); + + zeroize_key(new_key, new_key_size); + + ctx->counter = 0; + + return 0; +} + +/* API functions */ + +static int wrap_nettle_rnd_init(void **_ctx) +{ + int ret; + uint8_t new_key[PRNG_KEY_SIZE*2]; + struct generators_ctx_st *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + /* initialize the nonce RNG */ + ret = _rnd_get_system_entropy(new_key, sizeof(new_key)); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + + ret = single_prng_init(&ctx->nonce, new_key, PRNG_KEY_SIZE, 1); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + + /* initialize the random/key RNG */ + ret = single_prng_init(&ctx->normal, new_key+PRNG_KEY_SIZE, PRNG_KEY_SIZE, 1); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + + *_ctx = ctx; + + return 0; + fail: + gnutls_free(ctx); + return ret; +} + +static int +wrap_nettle_rnd(void *_ctx, int level, void *data, size_t datasize) +{ + struct generators_ctx_st *ctx = _ctx; + struct prng_ctx_st *prng_ctx; + int ret, reseed = 0; + uint8_t new_key[PRNG_KEY_SIZE]; + time_t now; + + if (level == GNUTLS_RND_RANDOM || level == GNUTLS_RND_KEY) + prng_ctx = &ctx->normal; + else if (level == GNUTLS_RND_NONCE) + prng_ctx = &ctx->nonce; + else { + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + return gnutls_assert_val(GNUTLS_E_RANDOM_FAILED); + } + + /* Two reasons for this memset(): + * 1. avoid getting filled with valgrind warnings + * 2. avoid a cipher/PRNG failure to expose stack data + */ + memset(data, 0, datasize); + + now = gnutls_time(0); + + /* We re-seed based on time in addition to output data. That is, + * to prevent a temporal state compromise to become permanent for low + * traffic sites */ + if (unlikely(_gnutls_detect_fork(prng_ctx->forkid))) { + reseed = 1; + } else { + if (now > prng_ctx->last_reseed + prng_reseed_time[level]) + reseed = 1; + } + + if (reseed != 0 || prng_ctx->counter > prng_reseed_limits[level]) { + if (level == GNUTLS_RND_NONCE) { + ret = wrap_nettle_rnd(_ctx, GNUTLS_RND_RANDOM, new_key, sizeof(new_key)); + } else { + + /* we also use the system entropy to reduce the impact + * of a temporal state compromise for these two levels. */ + ret = _rnd_get_system_entropy(new_key, sizeof(new_key)); + } + + if (ret < 0) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + goto cleanup; + } + + ret = single_prng_init(prng_ctx, new_key, sizeof(new_key), 0); + if (ret < 0) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + goto cleanup; + } + + prng_ctx->last_reseed = now; + prng_ctx->forkid = _gnutls_get_forkid(); + } + + chacha_crypt(&prng_ctx->ctx, datasize, data, data); + prng_ctx->counter += datasize; + + if (level == GNUTLS_RND_KEY) { /* prevent backtracking */ + ret = wrap_nettle_rnd(_ctx, GNUTLS_RND_RANDOM, new_key, sizeof(new_key)); + if (ret < 0) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + goto cleanup; + } + + ret = single_prng_init(prng_ctx, new_key, sizeof(new_key), 0); + if (ret < 0) { + gnutls_assert(); + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_ERROR); + goto cleanup; + } + } + + ret = 0; + _gnutls_switch_fips_state(GNUTLS_FIPS140_OP_NOT_APPROVED); + +cleanup: + return ret; +} + +static void wrap_nettle_rnd_refresh(void *_ctx) +{ + struct generators_ctx_st *ctx = _ctx; + char tmp; + + /* force reseed */ + ctx->nonce.counter = prng_reseed_limits[GNUTLS_RND_NONCE]+1; + ctx->normal.counter = prng_reseed_limits[GNUTLS_RND_RANDOM]+1; + + wrap_nettle_rnd(_ctx, GNUTLS_RND_NONCE, &tmp, 1); + wrap_nettle_rnd(_ctx, GNUTLS_RND_RANDOM, &tmp, 1); +} + +int crypto_rnd_prio = INT_MAX; + +gnutls_crypto_rnd_st _gnutls_rnd_ops = { + .init = wrap_nettle_rnd_init, + .deinit = wrap_nettle_rnd_deinit, + .rnd = wrap_nettle_rnd, + .rnd_refresh = wrap_nettle_rnd_refresh, + .self_test = NULL, +}; |