blob: 86117d25001efd21b4d3c69779c734df543273ed [file] [log] [blame]
/*
* rngtest.c -- Random Number Generator FIPS 140-1/140-2 tests
*
* This program tests the input stream in stdin for randomness,
* using the tests defined by FIPS 140-1/140-2 2001-10-10.
*
* Copyright (C) 2004 Henrique de Moraes Holschuh <hmh@debian.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
*/
#define _GNU_SOURCE
#ifndef HAVE_CONFIG_H
#error Invalid or missing autoconf build environment
#endif
#include "rng-tools-config.h"
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <argp.h>
#include <signal.h>
#include "fips.h"
#include "stats.h"
#include "exits.h"
#define PROGNAME "rngtest"
const char* logprefix = PROGNAME ": ";
/*
* argp stuff
*/
const char *argp_program_version =
PROGNAME " " VERSION "\n"
"Copyright (c) 2004 by Henrique de Moraes Holschuh\n"
"This is free software; see the source for copying conditions. There is NO "
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.";
const char *argp_program_bug_address = PACKAGE_BUGREPORT;
error_t argp_err_exit_status = EXIT_USAGE;
static char doc[] =
"Check the randomness of data using FIPS 140-2 RNG tests.\n"
"\v"
"FIPS tests operate on 20000-bit blocks. Data is read from stdin. Statistics "
"and messages are sent to stderr.\n\n"
"If no errors happen nor any blocks fail the FIPS tests, the program will return "
"exit status 0. If any blocks fail the tests, the exit status will be 1.\n";
static struct argp_option options[] = {
{ "blockcount", 'c', "n", 0,
"Exit after processing n blocks (default: 0)" },
{ "pipe", 'p', 0, 0,
"Enable pipe mode: work silently, and echo to stdout all good blocks" },
{ "timedstats", 't', "n", 0,
"Dump statistics every n secods (default: 0)" },
{ "blockstats", 'b', "n", 0,
"Dump statistics every n blocks (default: 0)" },
{ 0 },
};
struct arguments {
int blockstats;
uint64_t timedstats; /* microseconds */
int pipemode;
int blockcount;
};
static struct arguments default_arguments = {
.blockstats = 0,
.timedstats = 0,
.pipemode = 0,
.blockcount = 0,
};
static error_t parse_opt (int key, char *arg, struct argp_state *state)
{
struct arguments *arguments = state->input;
switch(key) {
case 'c': {
int n;
if ((sscanf(arg, "%i", &n) == 0) || (n < 0))
argp_usage(state);
else
arguments->blockcount = n;
break;
}
case 'b': {
int n;
if ((sscanf(arg, "%i", &n) == 0) || (n < 0))
argp_usage(state);
else
arguments->blockstats = n;
break;
}
case 't': {
int n;
if ((sscanf(arg, "%i", &n) == 0) || (n < 0))
argp_usage(state);
else
arguments->timedstats = 1000000ULL * n;
break;
}
case 'p':
arguments->pipemode = 1;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/*
* Globals
*/
/* RNG Buffers */
unsigned char rng_buffer[FIPS_RNG_BUFFER_SIZE];
/* Statistics */
struct {
/* simple counters */
uint64_t bad_fips_blocks; /* Blocks reproved by FIPS 140-2 */
uint64_t good_fips_blocks; /* Blocks approved by FIPS 140-2 */
uint64_t fips_failures[N_FIPS_TESTS]; /* Breakdown of block
failures per FIPS test */
uint64_t bytes_received; /* Bytes read from input */
uint64_t bytes_sent; /* Bytes sent to output */
/* performance timers */
struct rng_stat source_blockfill; /* Block-receive time */
struct rng_stat fips_blockfill; /* FIPS run time */
struct rng_stat sink_blockfill; /* Block-send time */
struct timeval progstart; /* Program start time */
} rng_stats;
/* Logic and contexts */
static fips_ctx_t fipsctx; /* Context for the FIPS tests */
static int exitstatus = EXIT_SUCCESS; /* Exit status */
/* Command line arguments and processing */
struct arguments *arguments = &default_arguments;
static struct argp argp = { options, parse_opt, NULL, doc };
/* signals */
static volatile int gotsigterm = 0; /* Received SIGTERM/SIGINT */
/*
* Signal handling
*/
static void sigterm_handler(int sig)
{
gotsigterm = sig;
}
static void init_sighandlers(void)
{
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
action.sa_handler = sigterm_handler;
/* Handle SIGTERM and SIGINT the same way */
if (sigaction(SIGTERM, &action, NULL) < 0) {
fprintf(stderr,
"unable to install signal handler for SIGTERM: %s",
strerror(errno));
exit(EXIT_OSERR);
}
if (sigaction(SIGINT, &action, NULL) < 0) {
fprintf(stderr,
"unable to install signal handler for SIGINT: %s",
strerror(errno));
exit(EXIT_OSERR);
}
}
static int xread(void *buf, size_t size)
{
size_t off = 0;
ssize_t r;
while (size) {
r = read(0, buf + off, size);
if (r < 0) {
if (gotsigterm) return -1;
if ((errno == EAGAIN) || (errno == EINTR)) continue;
break;
} else if (!r) {
if (!arguments->pipemode)
fprintf(stderr,
"%sentropy source drained\n",
logprefix);
return -1;
}
off += r;
size -= r;
rng_stats.bytes_received += r;
}
if (size) {
fprintf(stderr,
"%serror reading input: %s\n", logprefix,
strerror(errno));
exitstatus = EXIT_IOERR;
return -1;
}
return 0;
}
static int xwrite(void *buf, size_t size)
{
size_t off = 0;
ssize_t r;
while (size) {
r = write(1, buf + off, size);
if (r < 0) {
if (gotsigterm) return -1;
if ((errno == EAGAIN) || (errno == EINTR)) continue;
break;
} else if (!r) {
fprintf(stderr,
"%swrite channel stuck\n", logprefix);
exitstatus = EXIT_IOERR;
return -1;
}
off += r;
size -= r;
rng_stats.bytes_sent += r;
}
if (size) {
fprintf(stderr,
"%serror writing to output: %s\n", logprefix,
strerror(errno));
exitstatus = EXIT_IOERR;
return -1;
}
return 0;
}
static void init_rng_stats(void)
{
memset(&rng_stats, 0, sizeof(rng_stats));
gettimeofday(&rng_stats.progstart, 0);
set_stat_prefix(logprefix);
}
static void dump_rng_stats(void)
{
int j;
char buf[256];
struct timeval now;
fprintf(stderr, "%s\n", dump_stat_counter(buf, sizeof(buf),
"bits received from input",
rng_stats.bytes_received * 8));
if (arguments->pipemode)
fprintf(stderr, "%s\n", dump_stat_counter(buf, sizeof(buf),
"bits sent to output",
rng_stats.bytes_sent * 8));
fprintf(stderr, "%s\n", dump_stat_counter(buf, sizeof(buf),
"FIPS 140-2 successes",
rng_stats.good_fips_blocks));
fprintf(stderr, "%s\n", dump_stat_counter(buf, sizeof(buf),
"FIPS 140-2 failures",
rng_stats.bad_fips_blocks));
for (j = 0; j < N_FIPS_TESTS; j++)
fprintf(stderr, "%s\n", dump_stat_counter(buf, sizeof(buf),
fips_test_names[j],
rng_stats.fips_failures[j]));
fprintf(stderr, "%s\n", dump_stat_bw(buf, sizeof(buf),
"input channel speed", "bits",
&rng_stats.source_blockfill, FIPS_RNG_BUFFER_SIZE*8));
fprintf(stderr, "%s\n", dump_stat_bw(buf, sizeof(buf),
"FIPS tests speed", "bits",
&rng_stats.fips_blockfill, FIPS_RNG_BUFFER_SIZE*8));
if (arguments->pipemode)
fprintf(stderr, "%s\n", dump_stat_bw(buf, sizeof(buf),
"output channel speed", "bits",
&rng_stats.sink_blockfill, FIPS_RNG_BUFFER_SIZE*8));
gettimeofday(&now, 0);
fprintf(stderr, "%sProgram run time: %llu microseconds\n",
logprefix,
(unsigned long long) elapsed_time(&rng_stats.progstart, &now));
}
/* Return 32 bits of bootstrap data */
static int discard_initial_data(void)
{
unsigned char tempbuf[4];
/* Do full startup discards when in pipe mode */
if (arguments->pipemode)
if (xread(tempbuf, sizeof tempbuf)) exit(EXIT_FAIL);
/* Bootstrap data for FIPS tests */
if (xread(tempbuf, sizeof tempbuf)) exit(EXIT_FAIL);
return tempbuf[0] | (tempbuf[1] << 8) |
(tempbuf[2] << 16) | (tempbuf[3] << 24);
}
static void do_rng_fips_test_loop( void )
{
int j;
int fips_result;
struct timeval start, stop, statdump, now;
int statruns, runs;
runs = statruns = 0;
gettimeofday(&statdump, 0);
while (!gotsigterm) {
gettimeofday(&start, 0);
if (xread(rng_buffer, sizeof(rng_buffer))) return;
gettimeofday(&stop, 0);
update_usectimer_stat(&rng_stats.source_blockfill,
&start, &stop);
gettimeofday(&start, 0);
fips_result = fips_run_rng_test(&fipsctx, &rng_buffer);
gettimeofday (&stop, 0);
update_usectimer_stat(&rng_stats.fips_blockfill,
&start, &stop);
if (fips_result) {
rng_stats.bad_fips_blocks++;
for (j = 0; j < N_FIPS_TESTS; j++)
if (fips_result & fips_test_mask[j])
rng_stats.fips_failures[j]++;
} else {
rng_stats.good_fips_blocks++;
if (arguments->pipemode) {
gettimeofday(&start, 0);
if (xwrite(rng_buffer, sizeof(rng_buffer)))
return;
gettimeofday (&stop, 0);
update_usectimer_stat(
&rng_stats.sink_blockfill,
&start, &stop);
}
}
if (arguments->blockcount &&
(++runs >= arguments->blockcount)) break;
gettimeofday(&now, 0);
if ((arguments->blockstats &&
(++statruns >= arguments->blockstats)) ||
(arguments->timedstats &&
(elapsed_time(&statdump, &now) > arguments->timedstats))) {
dump_rng_stats();
gettimeofday(&statdump, 0);
statruns = 0;
}
}
}
int main(int argc, char **argv)
{
argp_parse(&argp, argc, argv, 0, 0, arguments);
if (!arguments->pipemode)
fprintf(stderr, "%s\n\n",
argp_program_version);
init_sighandlers();
/* Init data structures */
init_rng_stats();
if (!arguments->pipemode)
fprintf(stderr, "%sstarting FIPS tests...\n",
logprefix);
/* Bootstrap FIPS tests */
fips_init(&fipsctx, discard_initial_data());
do_rng_fips_test_loop();
dump_rng_stats();
if ((exitstatus == EXIT_SUCCESS) &&
(rng_stats.bad_fips_blocks || !rng_stats.good_fips_blocks)) {
exitstatus = EXIT_FAIL;
}
exit(exitstatus);
}