summaryrefslogtreecommitdiff
path: root/lib/nettle/rnd.c
blob: a7fe09db75e5ac9cec81af0da895697375965bb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/*
 * 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,
};