/* * Copyright (C) 2010-2012 Free Software Foundation, Inc. * Copyright (C) 2000, 2001, 2008 Niels Möller * * 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 * */ /* Here is the random generator layer. This code was based on the LSH * random generator (the trivia and device source functions for POSIX) * and modified to fit gnutls' needs. Relicenced with permission. * Original author Niels Möller. */ #include #include #include #include #include #ifdef HAVE_GETPID #include /* getpid */ #endif #ifdef HAVE_GETRUSAGE #include #endif #include #define SOURCES 2 #define RND_LOCK if (gnutls_mutex_lock(&rnd_mutex)!=0) abort() #define RND_UNLOCK if (gnutls_mutex_unlock(&rnd_mutex)!=0) abort() enum { RANDOM_SOURCE_TRIVIA = 0, RANDOM_SOURCE_DEVICE, }; static struct yarrow256_ctx yctx; static struct yarrow_source ysources[SOURCES]; static struct timespec device_last_read = { 0, 0 }; static struct timespec current_time = { 0, 0 }; static time_t trivia_previous_time = 0; static time_t trivia_time_count = 0; #ifdef HAVE_GETPID static pid_t pid; /* detect fork() */ #endif static void *rnd_mutex; inline static unsigned int timespec_sub_sec(struct timespec *a, struct timespec *b) { return (a->tv_sec - b->tv_sec); } #define DEVICE_READ_INTERVAL (1200) /* universal functions */ static int do_trivia_source(int init) { static struct { struct timespec now; #ifdef HAVE_GETRUSAGE struct rusage rusage; #endif #ifdef HAVE_GETPID pid_t pid; #endif unsigned count; } event; unsigned entropy = 0; memcpy(&event.now, ¤t_time, sizeof(event.now)); #ifdef HAVE_GETRUSAGE if (getrusage(RUSAGE_SELF, &event.rusage) < 0) { _gnutls_debug_log("getrusage failed: %s\n", strerror(errno)); abort(); } #endif event.count = 0; if (init) { trivia_time_count = 0; } else { event.count = trivia_time_count++; if (event.now.tv_sec != trivia_previous_time) { /* Count one bit of entropy if we either have more than two * invocations in one second, or more than two seconds * between invocations. */ if ((trivia_time_count > 2) || ((event.now.tv_sec - trivia_previous_time) > 2)) entropy++; trivia_time_count = 0; } } trivia_previous_time = event.now.tv_sec; #ifdef HAVE_GETPID event.pid = pid; #endif return yarrow256_update(&yctx, RANDOM_SOURCE_TRIVIA, entropy, sizeof(event), (void *) &event); } /* System specific functions */ #ifdef _WIN32 #include #include #define DEVICE_READ_SIZE 16 #define DEVICE_READ_SIZE_MAX 32 static HCRYPTPROV device_fd = 0; static int do_device_source(int init) { int read_size = DEVICE_READ_SIZE; if (init) { int old; if (!CryptAcquireContext (&device_fd, NULL, NULL, PROV_RSA_FULL, CRYPT_SILENT | CRYPT_VERIFYCONTEXT)) { _gnutls_debug_log ("error in CryptAcquireContext!\n"); return GNUTLS_E_RANDOM_DEVICE_ERROR; } gettime(&device_last_read); read_size = DEVICE_READ_SIZE_MAX; /* initially read more data */ } if ((device_fd != 0) && (init || timespec_sub_sec(¤t_time, &device_last_read) > DEVICE_READ_INTERVAL)) { /* More than 20 minutes since we last read the device */ uint8_t buf[DEVICE_READ_SIZE_MAX]; if (!CryptGenRandom(device_fd, (DWORD) read_size, buf)) { _gnutls_debug_log("Error in CryptGenRandom: %s\n", GetLastError()); return GNUTLS_E_RANDOM_DEVICE_ERROR; } memcpy(&device_last_read, ¤t_time, sizeof(device_last_read)); return yarrow256_update(&yctx, RANDOM_SOURCE_DEVICE, read_size * 8 / 2 /* we trust the system RNG */ , read_size, buf); } return 0; } static void wrap_nettle_rnd_deinit(void *ctx) { RND_LOCK; CryptReleaseContext(device_fd, 0); RND_UNLOCK; gnutls_mutex_deinit(&rnd_mutex); rnd_mutex = NULL; } #else /* POSIX */ #include #include #include #include #include #include #include "egd.h" #define DEVICE_READ_SIZE 16 #define DEVICE_READ_SIZE_MAX 32 static int device_fd; static int do_device_source_urandom(int init) { unsigned int read_size = DEVICE_READ_SIZE; if (init) { int old; device_fd = open("/dev/urandom", O_RDONLY); if (device_fd < 0) { _gnutls_debug_log("Cannot open urandom!\n"); return GNUTLS_E_FILE_ERROR; } old = fcntl(device_fd, F_GETFD); if (old != -1) fcntl(device_fd, F_SETFD, old | FD_CLOEXEC); memcpy(&device_last_read, ¤t_time, sizeof(device_last_read)); read_size = DEVICE_READ_SIZE_MAX; /* initially read more data */ } if ((init || (timespec_sub_sec(¤t_time, &device_last_read) > DEVICE_READ_INTERVAL)) && (device_fd > 0)) { /* More than 20 minutes since we last read the device */ uint8_t buf[DEVICE_READ_SIZE_MAX]; uint32_t done; for (done = 0; done < read_size;) { int res; do res = read(device_fd, buf + done, sizeof(buf) - done); while (res < 0 && errno == EINTR); if (res <= 0) { if (res < 0) { _gnutls_debug_log ("Failed to read /dev/urandom: %s\n", strerror(errno)); } else { _gnutls_debug_log ("Failed to read /dev/urandom: end of file\n"); } return GNUTLS_E_RANDOM_DEVICE_ERROR; } done += res; } memcpy(&device_last_read, ¤t_time, sizeof(device_last_read)); return yarrow256_update(&yctx, RANDOM_SOURCE_DEVICE, read_size * 8 / 2 /* we trust the RNG */ , read_size, buf); } return 0; } static int do_device_source_egd(int init) { unsigned int read_size = DEVICE_READ_SIZE; if (init) { device_fd = _rndegd_connect_socket(); if (device_fd < 0) { _gnutls_debug_log("Cannot open egd socket!\n"); return gnutls_assert_val (GNUTLS_E_RANDOM_DEVICE_ERROR); } memcpy(&device_last_read, ¤t_time, sizeof(device_last_read)); read_size = DEVICE_READ_SIZE_MAX; /* initially read more data */ } if ((device_fd > 0) && (init || (timespec_sub_sec(¤t_time, &device_last_read) > DEVICE_READ_INTERVAL))) { /* More than 20 minutes since we last read the device */ uint8_t buf[DEVICE_READ_SIZE_MAX]; uint32_t done; for (done = 0; done < read_size;) { int res; res = _rndegd_read(&device_fd, buf + done, sizeof(buf) - done); if (res <= 0) { if (res < 0) { _gnutls_debug_log ("Failed to read egd.\n"); } else { _gnutls_debug_log ("Failed to read egd: end of file\n"); } return gnutls_assert_val (GNUTLS_E_RANDOM_DEVICE_ERROR); } done += res; } memcpy(&device_last_read, ¤t_time, sizeof(device_last_read)); return yarrow256_update(&yctx, RANDOM_SOURCE_DEVICE, read_size * 8 / 2, read_size, buf); } return 0; } static int do_device_source(int init) { int ret; static int (*do_source) (int init) = NULL; /* using static var here is ok since we are * always called with mutexes down */ if (init == 1) { #ifdef HAVE_GETPID pid = getpid(); #endif do_source = do_device_source_urandom; ret = do_source(init); if (ret < 0) { do_source = do_device_source_egd; ret = do_source(init); } if (ret < 0) { gnutls_assert(); return ret; } return ret; } else { ret = do_source(init); return ret; } } static void wrap_nettle_rnd_deinit(void *ctx) { RND_LOCK; close(device_fd); RND_UNLOCK; gnutls_mutex_deinit(&rnd_mutex); rnd_mutex = NULL; } #endif /* API functions */ static int wrap_nettle_rnd_init(void **ctx) { int ret; ret = gnutls_mutex_init(&rnd_mutex); if (ret < 0) { gnutls_assert(); return ret; } yarrow256_init(&yctx, SOURCES, ysources); gettime(¤t_time); ret = do_device_source(1); if (ret < 0) { gnutls_assert(); return ret; } ret = do_trivia_source(1); if (ret < 0) { gnutls_assert(); return ret; } yarrow256_slow_reseed(&yctx); return 0; } static int wrap_nettle_rnd(void *_ctx, int level, void *data, size_t datasize) { int ret, reseed = 0; RND_LOCK; #ifdef HAVE_GETPID if (getpid() != pid) { /* fork() detected */ memset(&device_last_read, 0, sizeof(device_last_read)); pid = getpid(); reseed = 1; } #endif /* update state only when having a non-nonce or if nonce * and nsecs%4096 == 0, i.e., one out of 4096 times called . * * The reason we do that is to avoid any delays when generating nonces. */ if (level != GNUTLS_RND_NONCE || reseed != 0) { gettime(¤t_time); ret = do_trivia_source(0); if (ret < 0) { RND_UNLOCK; gnutls_assert(); return ret; } ret = do_device_source(0); if (ret < 0) { RND_UNLOCK; gnutls_assert(); return ret; } if (reseed) yarrow256_slow_reseed(&yctx); } yarrow256_random(&yctx, datasize, data); RND_UNLOCK; return 0; } static void wrap_nettle_rnd_refresh(void *_ctx) { RND_LOCK; gettime(¤t_time); do_trivia_source(0); do_device_source(0); RND_UNLOCK; return; } 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, };