// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.
#ident "$XORP: xorp/rtrmgr/randomness.cc,v 1.21 2007/02/16 22:47:24 pavlin Exp $"
#include "rtrmgr_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/timeval.hh"
#include "libxorp/timer.hh"
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
// XXX: Needs HAVE_OPENSSL_MD5_H
#include <openssl/md5.h>
#include "randomness.hh"
RandomGen::RandomGen()
: _random_exists(false),
_urandom_exists(false),
_random_data(NULL),
_counter(0)
{
bool not_enough_randomness = false;
#ifndef HOST_OS_WINDOWS
FILE* file;
file = fopen("/dev/urandom", "r");
if (file == NULL) {
_urandom_exists = false;
} else {
_urandom_exists = true;
fclose(file);
return; // XXX: reading /dev/urandom is good enough method
}
file = fopen("/dev/random", "r");
if (file == NULL) {
_random_exists = false;
} else {
_random_exists = true;
fclose(file);
}
if (_random_exists) {
file = fopen("/dev/random", "r");
#ifdef O_NONBLOCK
// We need non-blocking reads
fcntl(fileno(file), F_SETFD, O_NONBLOCK);
#endif
// Use /dev/random to initialized the random pool
_random_data = new uint8_t[RAND_POOL_SIZE];
int bytes = fread(_random_data, 1, RAND_POOL_SIZE, file);
if (bytes < 16) {
// We didn't get enough randomness to be useful
not_enough_randomness = true;
}
fclose(file);
}
#endif // ! HOST_OS_WINDOWS
if ((!_urandom_exists && !_random_exists) || not_enough_randomness) {
//
// We need to generate our own randomness. This is hard.
// General strategy:
// 1. Read a bunch of stuff that the attacker can't read
// (this assumes we're running as root).
// 2. Read a bunch of stuff that the attacker can read, but
// that will be different each time the system starts.
// 3. Build a big buffer of all this data, XORed together.
// 4. Use MD5 to extract data from the buffer.
// 5. Add timing-based randomness every opportunity we can.
//
if (_random_data == NULL)
_random_data = new uint8_t[RAND_POOL_SIZE];
// Current time - this is mostly predictable, but a few less
// significant bits are hard to predict.
TimeVal now;
TimerList::system_gettimeofday(&now);
double d = now.get_double();
for (size_t i = 0; i < sizeof(d); i++) {
_random_data[i] ^= *(((char *)(&d))+i);
}
#ifndef HOST_OS_WINDOWS
int count = 0;
int scount = 0;
//
// Read host-based secrets - good source of unpredictable data,
// but we need to be very careful not to reveal the secrets.
// Note: these so won't vary between iterations, so they're
// definitely not good enough by themselves.
//
if (read_file("/etc/ssh_host_key")) {
scount++; count++;
}
if (read_file("/etc/ssh/ssh_host_key")) {
scount++; count++;
}
if (read_file("/etc/ssh_host_dsa_key")) {
scount++; count++;
}
if (read_file("/etc/ssh/ssh_host_dsa_key")) {
scount++; count++;
}
if (read_file("/etc/ssh_host_rsa_key")) {
scount++; count++;
}
if (read_file("/etc/ssh/ssh_host_rsa_key")) {
scount++; count++;
}
if (read_file("/etc/master.passwd")) {
scount++; count++;
}
if (read_file("/etc/shadow")) {
scount++; count++;
}
if (read_file("/etc/ssh_random_seed")) {
scount++; count++;
}
if (read_file("/tmp/rtrmgr-seed")) {
scount++; count++;
}
if (read_file("/usr/tmp/rtrmgr-seed")) {
scount++; count++;
}
if (read_file("/var/tmp/rtrmgr-seed")) {
scount++; count++;
}
//
// Read packet counts - another source of hopefully
// non-repeating but predictable data.
//
file = popen("/usr/bin/netstat -i", "r");
if (file != NULL) {
count++;
read_fd(file);
pclose(file);
} else {
XLOG_ERROR("popen of \"netstat -i\" failed");
}
//
// Read last log - another source of hopefully
// non-repeating but predictable data.
//
file = popen("/usr/bin/last", "r");
if (file != NULL) {
count++;
read_fd(file);
pclose(file);
} else {
debug_msg("popen of \"last\" failed\n");
}
//
// Read current processes - another source of hopefully
// non-repeating but predictable data.
//
file = popen("/usr/bin/ps -elf", "r");
if (file != NULL) {
count++;
read_fd(file);
pclose(file);
} else {
debug_msg("popen of \"ps -elf\" failed\n");
}
//
// Read current processes, attempt 2 - another source of hopefully
// non-repeating but predictable data.
//
file = popen("/usr/bin/ps -auxw", "r");
if (file != NULL) {
count++;
read_fd(file);
pclose(file);
} else {
debug_msg("popen of \"ps -auxw\" failed\n");
}
debug_msg("random data pool initialized: count=%d scount=%d\n",
count, scount);
debug_msg("\n");
for (size_t i = 0; i < 80; i++) {
debug_msg("%x", _random_data[i]);
}
debug_msg("\n");
file = fopen("/tmp/rtrmgr-seed", "w");
if (file == NULL)
file = fopen("/usr/tmp/rtrmgr-seed", "w");
if (file == NULL)
file = fopen("/var/tmp/rtrmgr-seed", "w");
if (file != NULL) {
uint8_t outbuf[16];
MD5_CTX md5_context;
MD5_Init(&md5_context);
MD5_Update(&md5_context, _random_data, RAND_POOL_SIZE);
MD5_Final(outbuf, &md5_context);
fwrite(outbuf, 1, 16, file);
fwrite(&d, 1, sizeof(d), file);
fclose(file);
}
#endif // ! HOST_OS_WINDOWS
}
}
RandomGen::~RandomGen()
{
if (_random_data != NULL) {
delete[] _random_data;
_random_data = NULL;
}
}
bool
RandomGen::read_file(const string& filename)
{
bool result = false;
FILE* file = fopen(filename.c_str(), "r");
debug_msg("RNG: reading %s\n", filename.c_str());
if (file == NULL) {
debug_msg("failed to read %s\n", filename.c_str());
} else {
result = read_fd(file);
fclose(file);
}
return result;
}
bool
RandomGen::read_fd(FILE* file)
{
size_t bytes = 0;
if (file != NULL) {
char ixb[4];
uint32_t ix;
u_char tbuf[RAND_POOL_SIZE];
bytes = fread(tbuf, 1, RAND_POOL_SIZE, file);
debug_msg("RNG: read %u bytes\n", XORP_UINT_CAST(bytes));
if (bytes > 0) {
size_t i;
debug_msg("orig tbuf:\n");
for (i = 0; i < min((size_t)80, bytes); i++)
debug_msg("%2x", tbuf[i]);
debug_msg("\n");
//
// Use MD5 to self-encrypt the data. We don't want to risk
// sensitive data being able to be read from a memory image.
//
MD5_CTX md5_context;
u_char chain[16];
MD5_Init(&md5_context);
MD5_Update(&md5_context, tbuf, bytes);
MD5_Final(chain, &md5_context);
for (i = 0; i < RAND_POOL_SIZE/16; i++) {
MD5_Init(&md5_context);
MD5_Update(&md5_context, chain, 16);
MD5_Update(&md5_context, tbuf + (i*16), 16);
MD5_Final(chain, &md5_context);
// Overwrite in situe
memcpy(tbuf + (i * 16), chain, 16);
if (i * 16 > bytes) {
debug_msg("i=%u\n", XORP_UINT_CAST(i));
break;
}
}
debug_msg("tbuf:\n");
for (i = 0; i < min((size_t)80, bytes); i++)
debug_msg("%2x", tbuf[i]);
debug_msg("\n");
for (i = 0; i < bytes; i++) {
// XOR the two together
_random_data[i] ^= tbuf[i];
ixb[i%4] ^= _random_data[i];
}
// Zero the temporary buffer, just to be safe
memset(tbuf, 0, bytes);
// Add more time bits - don't get a lot from this but it's cheap
TimeVal now;
TimerList::system_gettimeofday(&now);
double d = now.get_double();
memcpy(&ix, ixb, 4);
ix ^= now.usec();
ix = ix % (RAND_POOL_SIZE - sizeof(d));
// Add them at a "random" location - not truely random
// because we may not have enough entropy in the pool yet.
for (i = 0; i < sizeof(d); i++) {
_random_data[i] ^= *(((char *)(&d)) + i + ix);
}
}
}
if (bytes > 0)
return true;
return false;
}
void
RandomGen::add_buf_to_randomness(uint8_t* buf, size_t len)
{
if (len > RAND_POOL_SIZE)
len = RAND_POOL_SIZE;
XLOG_ASSERT(_random_data != NULL);
for (size_t i = 0; i < len; i++) {
_random_data[i] ^= buf[i];
}
}
// get_random_bytes is guaranteed to never block
void
RandomGen::get_random_bytes(size_t len, uint8_t* buf)
{
// Don't allow stupid usage
XLOG_ASSERT(len < RAND_POOL_SIZE/100);
if (_urandom_exists) {
FILE *file = fopen("/dev/urandom", "r");
if (file == NULL) {
XLOG_FATAL("Failed to open /dev/urandom");
} else {
size_t bytes_read = fread(buf, 1, len, file);
if (bytes_read < len) {
XLOG_ERROR("Failed read on randomness source; read %u words",
XORP_UINT_CAST(bytes_read));
}
}
fclose(file);
return;
}
if (_random_exists) {
FILE *file = fopen("/dev/random", "r");
if (file == NULL) {
XLOG_FATAL("Failed to open /dev/random");
} else {
#ifdef O_NONBLOCK
fcntl(fileno(file), F_SETFD, O_NONBLOCK);
#endif
size_t bytes_read = fread(buf, 1, len, file);
if (len > 0)
add_buf_to_randomness(buf, len);
if (bytes_read == len) {
fclose(file);
return;
}
// else fall through
}
}
//
// Generate the random data using repeated MD5 hashing of the random pool
//
uint8_t outbuf[16];
MD5_CTX md5_context;
size_t bytes_written = 0;
while (bytes_written < len) {
TimeVal now;
TimerList::system_gettimeofday(&now);
double d = now.get_double();
MD5_Init(&md5_context);
_counter++;
add_buf_to_randomness((uint8_t*)(&_counter), sizeof(_counter));
add_buf_to_randomness((uint8_t*)(&d), sizeof(d));
MD5_Update(&md5_context, _random_data, RAND_POOL_SIZE);
MD5_Final(outbuf, &md5_context);
memcpy(buf + bytes_written, outbuf,
min((size_t)16, len - bytes_written));
bytes_written += min((size_t)16, len - bytes_written);
}
}
syntax highlighted by Code2HTML, v. 0.9.1