/*
* 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
*
*/
#include "gnutls_int.h"
#include "errors.h"
#include
#include
#include
#include
#include
#include
#include
#include
#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,
};