Some tools to play with GnuPG’s smartcard daemon and OpenPGP smartcards.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
scdtools/src/scdrand.c

366 lines
9.3 KiB

/*
* scdtools - Tools for Scdaemon and OpenPGP smartcards
* Copyright (C) 2014,2015,2016 Damien Goutte-Gattat
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <err.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/random.h>
#include <gcrypt.h>
#include "gpg-util.h"
#define MAX_RANDOM_BYTES 256
#define DEFAULT_RANDOM_BYTES 8
#define DEFAULT_LOOP_ITERATIONS 1
#define DEFAULT_LOOP_INTERVAL 10
#define DEFAULT_ENTROPY_FACTOR 8
static void
usage(int status)
{
printf("Usage: scdrand [options] [BYTES]\n\
Read the specified number of random bytes from a smartcard\n\
and feed them to the kernel entropy pool. If BYTES is not\n\
specified, the default is %d bytes.\n\n", DEFAULT_RANDOM_BYTES);
puts("Options:\n\
-h, --help Display this help message.\n\
-v, --version Display the version message.\n\
");
printf("\
-l, --loop Loop indefinitely.\n\
-L, --max-loop N Loop N times (default: %d).\n\
-i, --interval N Sleep N seconds between iterations\n\
(default: %d seconds).\n\
\n", DEFAULT_LOOP_ITERATIONS, DEFAULT_LOOP_INTERVAL);
puts("\
-t, --threshold N Do nothing if there is already N bits\n\
of entropy available in the kernel pool.\n\
Set to 0 (default) to always add entropy.\n\
");
printf("\
-e, --entropy-bits N Set the entropic value of a random byte.\n\
Must be between 1 and 8. Default is %d.\n\
\n", DEFAULT_ENTROPY_FACTOR);
printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
exit(status);
}
static void
info(void)
{
printf("\
scdrand (scdtools %s)\n\
Copyright (C) 2017 Damien Goutte-Gattat\n\
\n\
This program is released under the GNU General Public License.\n\
See the COPYING file or <http://www.gnu.org/licenses/gpl.html>.\n\
", VERSION);
exit(EXIT_SUCCESS);
}
/*
* Represents a response to a GET CHALLENGE command.
*/
struct challenge
{
size_t len;
unsigned char *data;
};
/*
* Data callback for the below function.
*/
gpg_error_t
get_challenge_data_cb(void *arg, const void *line, size_t len)
{
struct challenge *c = arg;
if ( len > c->len )
return gpg_error(GPG_ERR_INV_LENGTH);
memcpy(c->data, line, len);
c->len = len;
return 0;
}
/*
* Get random data ("challenge") from the smartcard.
*
* @param ctx Assuan context connected to a running scdaemon.
* @param buffer The buffer to store the returned data into.
* @param len Number of bytes of random data to retrieve. The buffer
* must be big enough to store the requested amount.
*
* @return
* The number of bytes of random data actually retrieved and stored
* in the provided buffer.
*/
static int
get_challenge(assuan_context_t ctx, unsigned char *buffer, size_t len)
{
char command[12];
gpg_error_t ge;
struct challenge c;
snprintf(command, sizeof(command), "RANDOM %lu", len);
c.len = len;
c.data = buffer;
if ( (ge = assuan_transact(ctx, command, get_challenge_data_cb, &c,
NULL, NULL, NULL, NULL)) )
errx(EXIT_FAILURE, "Cannot get challenge from card: %s",
gpg_strerror(ge));
return c.len;
}
/*
* Wait until the available entropy falls below the specified threshold.
* Available entropy is checked regularly at the specified interval.
*/
static void
wait_for_threshold(unsigned threshold, unsigned interval)
{
int random_fd, loop = 1;
unsigned entropy;
if ( (random_fd = open("/dev/random", O_RDONLY)) == -1 )
err(EXIT_FAILURE, "Cannot open /dev/random");
while ( loop ) {
if ( ioctl(random_fd, RNDGETENTCNT, &entropy) == -1 )
err(EXIT_FAILURE, "Cannot get available entropy");
if ( entropy < threshold )
loop = 0;
else
sleep(interval);
}
close(random_fd);
}
/*
* Fork a new process solely charged with the task of adding entropy
* to the kernel pool (the only task that requires root privileges).
* The new process will listen to a pipe opened by its parent and
* write the data it receives to the entropy pool.
*
* The function only returns in the parent process; the child process
* remains in a loop until it terminates.
*
* @param factor The number of entropy bits in a single byte.
*
* @return
* A file descriptor for the pipe connecting the parent process to
* its child.
*/
static int
start_privileged_process(unsigned factor)
{
int fd[2];
pid_t pid;
if ( pipe(fd) == -1 )
err(EXIT_FAILURE, "Cannot create pipe");
if ( (pid = fork()) == 0 ) {
int random_fd, n;
char buffer[sizeof(struct rand_pool_info) + MAX_RANDOM_BYTES], *ptr;
struct rand_pool_info *rpi;
rpi = (struct rand_pool_info *)buffer;
ptr = (char *) &(rpi->buf[0]);
close(fd[1]);
if ( (random_fd = open("/dev/random", O_RDONLY)) == -1 )
err(EXIT_FAILURE, "Cannot open /dev/random");
while ( 1 ) {
n = read(fd[0], ptr, MAX_RANDOM_BYTES);
if ( n == -1 )
err(EXIT_FAILURE, "Cannot read from pipe");
if ( n == 0 ) /* EOF, parent process must have closed its end. */
exit(EXIT_SUCCESS);
rpi->entropy_count = n * factor;
rpi->buf_size = n;
if ( ioctl(random_fd, RNDADDENTROPY, rpi) == -1 )
warn("Cannot add entropy");
}
}
else if ( pid > 0 ) {
close(fd[0]);
/* Drop privileges, then return to caller. */
if ( setgid(getgid()) == -1 || setuid(getuid()) == -1 )
err(EXIT_FAILURE, "Cannot drop privileges");
return fd[1];
}
else
err(EXIT_FAILURE, "Cannot fork");
return 0; /* Not reached. */
}
static int
get_uinteger_or_die(const char *arg)
{
unsigned val;
char *endptr;
errno = 0;
val = strtoul(arg, &endptr, 10);
if ( errno != 0 || endptr == arg )
errx(EXIT_FAILURE, "Invalid argument, unsigned integer expected: %s", arg);
return val;
}
int
main(int argc, char **argv)
{
int c, fd, n, loop;
unsigned nbytes, interval, threshold, factor;
assuan_context_t ctx;
gpg_error_t e;
unsigned char random_buffer[MAX_RANDOM_BYTES];
struct option options[] = {
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'v' },
{ "loop", 0, NULL, 'l' },
{ "max-loop", 1, NULL, 'L' },
{ "interval", 1, NULL, 'i' },
{ "threshold", 1, NULL, 't' },
{ "entropy-bits", 1, NULL, 'e' },
{ NULL, 0, NULL, 0 }
};
setprogname(argv[0]);
nbytes = DEFAULT_RANDOM_BYTES;
loop = DEFAULT_LOOP_ITERATIONS;
interval = DEFAULT_LOOP_INTERVAL;
factor = DEFAULT_ENTROPY_FACTOR;
threshold = 0;
while ( (c = getopt_long(argc, argv, "hvlL:i:t:e:",
options, NULL)) != -1 ) {
switch ( c ) {
case 'h':
usage(EXIT_SUCCESS);
break;
case '?':
usage(EXIT_FAILURE);
break;
case 'v':
info();
break;
case 'l':
loop = -1;
break;
case 'L':
loop = get_uinteger_or_die(optarg);
break;
case 'i':
interval = get_uinteger_or_die(optarg);
break;
case 't':
threshold = get_uinteger_or_die(optarg);
break;
case 'e':
factor = get_uinteger_or_die(optarg);
if ( factor == 0 || factor > 8 )
errx(EXIT_FAILURE, "Expected an entropy factor between 1 and 8");
break;
}
}
fd = start_privileged_process(factor);
if ( optind < argc ) {
nbytes = get_uinteger_or_die(argv[optind++]);
if ( nbytes == 0 ) /* Funny guy? */
exit(EXIT_FAILURE);
if ( nbytes > MAX_RANDOM_BYTES )
nbytes = MAX_RANDOM_BYTES;
}
#ifndef GPG_ERR_INITIALIZED
gpg_err_init();
#endif
assuan_set_gpg_err_source(GPG_ERR_SOURCE_USER_1);
if ( (e = connect_to_scdaemon(&ctx)) )
err(EXIT_FAILURE, "Cannot connect to Scdaemon: %s", gcry_strerror(e));
while ( loop == -1 || loop-- > 0 ) {
if ( threshold != 0 )
wait_for_threshold(threshold, interval);
n = get_challenge(ctx, random_buffer, nbytes);
write(fd, random_buffer, n);
if ( loop != 0 )
sleep(interval);
}
assuan_release(ctx);
close(fd);
return EXIT_SUCCESS;
}