/* * 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 * * 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 #include #include #include #include #include #include #include #include #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); }