467 lines
12 KiB
C
467 lines
12 KiB
C
/*
|
|
* Copyright (c) 2011, The Linux Foundation. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials provided
|
|
* with the distribution.
|
|
* * Neither the name of The Linux Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
#include <linux/random.h>
|
|
#include <linux/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/poll.h>
|
|
#include <linux/capability.h>
|
|
#include <sys/prctl.h>
|
|
|
|
|
|
#ifdef ANDROID_CHANGES
|
|
#include <android/log.h>
|
|
#include <private/android_filesystem_config.h>
|
|
#else
|
|
#define AID_SYSTEM 1000
|
|
#endif
|
|
|
|
#ifndef min
|
|
#define min(a,b) (((a)>(b))?(b):(a))
|
|
#endif
|
|
|
|
typedef unsigned char bool;
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
|
|
#define RANDOM_DEVICE "/dev/random"
|
|
#define RANDOM_DEVICE_HW "/dev/hw_random"
|
|
|
|
/* The device (/dev/random) internal limits 4096 bits of entropy, 512 bytes */
|
|
#define MAX_ENT_POOL_BITS 4096
|
|
#define MAX_ENT_POOL_BYTES (MAX_ENT_POOL_BITS / 8)
|
|
|
|
#define MAX_ENT_POOL_WRITES 128 /* write pool with smaller chunks */
|
|
|
|
///* Burst-mode timeout in us (micro-seconds) */
|
|
//#define BURST_MODE_TIMEOUT 100000 /* 100ms */
|
|
///* Idle-mode wait in us (micro-seconds) */
|
|
//#define IDLE_MODE_WAIT 10000 /* 10ms */
|
|
|
|
/* Buffer to hold hardware entropy bytes (this must be 2KB for FIPS testing */
|
|
#define MAX_BUFFER 2048 /* do not change this value */
|
|
static unsigned char databuf[MAX_BUFFER]; /* create buffer for FIPS testing */
|
|
static unsigned long buffsize; /* size of data in buffer */
|
|
static unsigned long curridx; /* position of current index */
|
|
|
|
/* Globals */
|
|
//static bool read_blocked = FALSE;
|
|
//static pid_t qrngd_pid;
|
|
|
|
/* User parameters */
|
|
struct user_options {
|
|
char input_device_name[128];
|
|
char output_device_name[128];
|
|
bool run_as_daemon;
|
|
};
|
|
|
|
/* Version number of this source */
|
|
#define APP_VERSION "1.01"
|
|
#define APP_NAME "qrngd"
|
|
|
|
const char *program_version =
|
|
APP_NAME " " APP_VERSION "\n"
|
|
"Copyright (c) 2011, The Linux Foundation. All rights reserved.\n"
|
|
"This is free software; see the source for copying conditions. There is NO\n"
|
|
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n";
|
|
|
|
const char *program_usage =
|
|
"Usage: " APP_NAME " [OPTION...]\n"
|
|
" -b background - become a daemon (default)\n"
|
|
" -f foreground - do not fork and become a daemon\n"
|
|
" -r <device name> hardware random input device (default: /dev/hw_random)\n"
|
|
" -o <device name> system random output device (default: /dev/random)\n"
|
|
" -h help (this page)\n";
|
|
|
|
/* Logging information */
|
|
enum log_level {
|
|
DEBUG = 0,
|
|
INFO = 1,
|
|
WARNING = 2,
|
|
ERROR = 3,
|
|
FATAL = 4,
|
|
LOG_MAX = 4,
|
|
};
|
|
|
|
/* Logging function for outputing to stderr or log */
|
|
void log_print(int level, char *format, ...)
|
|
{
|
|
if (level >= 0 && level <= LOG_MAX) {
|
|
#ifdef ANDROID_CHANGES
|
|
static int levels[5] = {
|
|
ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN,
|
|
ANDROID_LOG_ERROR, ANDROID_LOG_FATAL
|
|
};
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
__android_log_vprint(levels[level], APP_NAME, format, ap);
|
|
va_end(ap);
|
|
#else
|
|
static char *levels = "DIWEF";
|
|
va_list ap;
|
|
fprintf(stderr, "%c: ", levels[level]);
|
|
va_start(ap, format);
|
|
vfprintf(stderr, format, ap);
|
|
va_end(ap);
|
|
fputc('\n', stderr);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void title(void)
|
|
{
|
|
printf("%s", program_version);
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
printf("%s", program_usage);
|
|
}
|
|
|
|
/* Parse command line parameters */
|
|
static int get_user_options(struct user_options *user_ops, int argc, char **argv)
|
|
{
|
|
int max_params = argc;
|
|
int itr = 1; /* skip program name */
|
|
while (itr < max_params) {
|
|
if (argv[itr][0] != '-')
|
|
return -1;
|
|
|
|
switch (argv[itr++][1]) {
|
|
case 'b':
|
|
user_ops->run_as_daemon = TRUE;
|
|
break;
|
|
|
|
case 'f':
|
|
user_ops->run_as_daemon = FALSE;
|
|
break;
|
|
|
|
case 'r':
|
|
if (itr < max_params) {
|
|
if (strlen(argv[itr]) < sizeof(user_ops->input_device_name))
|
|
strcpy(user_ops->input_device_name, argv[itr++]);
|
|
else
|
|
return -1;
|
|
break;
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
case 'o':
|
|
if (itr < max_params) {
|
|
if (strlen(argv[itr]) < sizeof(user_ops->output_device_name))
|
|
strcpy(user_ops->output_device_name, argv[itr++]);
|
|
else
|
|
return -1;
|
|
break;
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
case 'h':
|
|
return -1;
|
|
|
|
|
|
default:
|
|
fprintf(stderr, "ERROR: Bad option: '%s'\n", argv[itr-1]);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Only check FIPS 140-2 (Continuous Random Number Generator Test) */
|
|
static int fips_test(const unsigned char *buf, size_t size)
|
|
{
|
|
unsigned long *buff_ul = (unsigned long *) buf;
|
|
size_t size_ul = size >> 2; /* convert byte to word size */
|
|
unsigned long last_value;
|
|
unsigned int rnd_ctr[256];
|
|
int i;
|
|
|
|
|
|
/* Continuous Random Number Generator Test */
|
|
last_value = *(buff_ul++);
|
|
size_ul--;
|
|
|
|
while (size_ul > 0) {
|
|
if (*buff_ul == last_value) {
|
|
log_print(ERROR, "ERROR: Bad word value from hardware.");
|
|
return -1;
|
|
} else
|
|
last_value = *buff_ul;
|
|
buff_ul++;
|
|
size_ul--;
|
|
}
|
|
|
|
/* count each random number */
|
|
for (i = 0; i < size; ++i) {
|
|
rnd_ctr[buf[i]]++;
|
|
}
|
|
|
|
/* check random numbers to make sure they are not bogus */
|
|
for (i = 0; i < 256; ++i) {
|
|
if (rnd_ctr[i] == 0) {
|
|
log_print(ERROR, "ERROR: Bad spectral random number sample.");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read data from the hardware RNG source */
|
|
static int read_src(int fd, void *buf, size_t size)
|
|
{
|
|
size_t offset = 0;
|
|
char *chr = (char *) buf;
|
|
ssize_t ret;
|
|
|
|
if (!size)
|
|
return -1;
|
|
do {
|
|
ret = read(fd, chr + offset, size);
|
|
/* any read failure is bad */
|
|
if (ret == -1)
|
|
return -1;
|
|
size -= ret;
|
|
offset += ret;
|
|
} while (size > 0);
|
|
|
|
/* should have read in all of requested data */
|
|
if (size > 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
/*Hold minimal permissions, so as to get IOCTL working*/
|
|
static int qrng_update_cap()
|
|
{
|
|
int retvalue = 0;
|
|
struct __user_cap_header_struct header;
|
|
struct __user_cap_data_struct cap;
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
memset(&cap, 0, sizeof(cap));
|
|
prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
|
|
if( 0 != setgid(AID_SYSTEM)){
|
|
fprintf(stderr, "setgid error\n");
|
|
return -1;
|
|
}
|
|
if( 0 != setuid(AID_SYSTEM)){
|
|
fprintf(stderr, "setuid error\n");
|
|
return -1;
|
|
}
|
|
header.version = _LINUX_CAPABILITY_VERSION;
|
|
header.pid = 0;
|
|
cap.effective = (1 << CAP_SYS_ADMIN) | (1 << CAP_NET_RAW);
|
|
cap.permitted = cap.effective;
|
|
cap.inheritable = 0;
|
|
retvalue = capset(&header, &cap);
|
|
if(retvalue != 0){
|
|
fprintf(stderr, "capset error\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* The beginning of everything */
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct user_options user_ops; /* holds user configuration data */
|
|
struct rand_pool_info *rand = NULL; /* structure to pass entropy (IOCTL) */
|
|
int random_fd = 0; /* output file descriptor */
|
|
int random_hw_fd = 0; /* input file descriptor */
|
|
int ent_count; /* current system entropy */
|
|
int write_size; /* max entropy data to pass */
|
|
struct pollfd fds[1]; /* used for polling file descriptor */
|
|
int ret;
|
|
int exitval = 0;
|
|
|
|
|
|
/* set default parameters */
|
|
user_ops.run_as_daemon = TRUE;
|
|
strcpy(user_ops.input_device_name, RANDOM_DEVICE_HW);
|
|
strcpy(user_ops.output_device_name, RANDOM_DEVICE);
|
|
|
|
/* display application header */
|
|
title();
|
|
|
|
/* get user preferences */
|
|
ret = get_user_options(&user_ops, argc, argv);
|
|
if (ret < 0) {
|
|
usage();
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
|
|
/* open hardware random device */
|
|
random_hw_fd = open(user_ops.input_device_name, O_RDONLY);
|
|
if (random_hw_fd < 0) {
|
|
fprintf(stderr, "Can't open hardware random device file %s\n", user_ops.input_device_name);
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
|
|
/* open random device */
|
|
random_fd = open(user_ops.output_device_name, O_RDWR);
|
|
if (random_fd < 0) {
|
|
fprintf(stderr, "Can't open random device file %s\n", user_ops.output_device_name);
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
|
|
/* allocate memory for ioctl data struct and buffer */
|
|
rand = malloc(sizeof(struct rand_pool_info) + MAX_ENT_POOL_WRITES);
|
|
if (!rand) {
|
|
fprintf(stderr, "Can't allocate memory\n");
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
|
|
if(0 != qrng_update_cap()){
|
|
log_print(ERROR, "qrngd permission reset failed, exiting\n");
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
|
|
/* setup poll() data */
|
|
memset(fds, 0 , sizeof(fds));
|
|
fds[0].fd = random_fd;
|
|
fds[0].events = POLLOUT;
|
|
|
|
/* run as daemon if requested to do so */
|
|
if (user_ops.run_as_daemon) {
|
|
fprintf(stderr, "Starting daemon.\n");
|
|
if (daemon(0, 0) < 0) {
|
|
fprintf(stderr, "can't daemonize: %s\n", strerror(errno));
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
#ifndef ANDROID_CHANGES
|
|
openlog(APP_NAME, 0, LOG_DAEMON);
|
|
#endif
|
|
}
|
|
|
|
/* log message */
|
|
log_print(INFO, APP_NAME " has started:\n" "Reading device:'%s' updating entropy for device:'%s'",
|
|
user_ops.input_device_name,
|
|
user_ops.output_device_name);
|
|
|
|
/* main loop to get data from hardware and feed RNG entropy pool */
|
|
while (1) {
|
|
|
|
/* Check for empty buffer and fill with hardware random generated numbers */
|
|
if (buffsize == 0) {
|
|
/* fill buffer with random data from hardware */
|
|
ret = read_src(random_hw_fd, databuf, MAX_BUFFER);
|
|
if (ret < 0) {
|
|
log_print(ERROR, "ERROR: Can't read from hardware source.");
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
/* run FIPS test on buffer, if buffer fails then ditch it and get new data */
|
|
ret = fips_test(databuf, MAX_BUFFER);
|
|
if (ret < 0) {
|
|
buffsize = 0;
|
|
log_print(INFO, "ERROR: Failed FIPS test.");
|
|
}
|
|
/* everything good, reset buffer variables to indicate full buffer */
|
|
else {
|
|
buffsize = MAX_BUFFER;
|
|
curridx = 0;
|
|
}
|
|
}
|
|
/* We should have data here, if not then something bad happened above and we should wait and try again */
|
|
if (buffsize == 0) {
|
|
log_print(ERROR, "ERROR: Timeout getting valid random data from hardware.");
|
|
usleep(100000); /* 100ms */
|
|
continue;
|
|
}
|
|
|
|
/* Get current entropy pool size in bits and convert to bytes */
|
|
if (ioctl(random_fd, RNDGETENTCNT, &ent_count) != 0) {
|
|
log_print(ERROR, "ERROR: Can't read entropy count.");
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
/* convert entropy bits to bytes */
|
|
ent_count >>= 3;
|
|
|
|
/* fill entropy pool */
|
|
write_size = min(buffsize, MAX_ENT_POOL_WRITES);
|
|
|
|
/* Write some data to the device */
|
|
rand->entropy_count = write_size * 8;
|
|
rand->buf_size = write_size;
|
|
memcpy(rand->buf, &databuf[curridx], write_size);
|
|
curridx += write_size;
|
|
buffsize -= write_size;
|
|
|
|
/* Issue the ioctl to increase the entropy count */
|
|
if (ioctl(random_fd, RNDADDENTROPY, rand) < 0) {
|
|
log_print(ERROR,"ERROR: RNDADDENTROPY ioctl() failed.");
|
|
exitval = 1;
|
|
goto exit;
|
|
}
|
|
|
|
/* Wait if entropy pool is full */
|
|
ret = poll(fds, 1, -1);
|
|
if (ret < 0) {
|
|
log_print(ERROR,"ERROR: poll call failed.");
|
|
/* wait if error */
|
|
usleep(100000);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
/* free other resources */
|
|
if (rand)
|
|
free(rand);
|
|
if (random_fd)
|
|
close(random_fd);
|
|
if (random_hw_fd)
|
|
close(random_hw_fd);
|
|
return exitval;
|
|
}
|
|
|