/*
* p11util.c
* ---------
*
* Command line tool for setting up PKCS #11. Mostly this means
* things like setting PINs.
*
* Author: Rob Austein
* Copyright (c) 2015, SUNET
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE 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.
*/
#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <termios.h>
#include <getopt.h>
#include <hal.h>
#include "sql_common.h"
/*
* Apparently the cool kids don't use getpassword() anymore, and there
* is no fully portable replacement, so the advice is just to roll
* one's own using termios. Yuck, but gives us the opportunity to
* provide a bit of visual feedback, what the heck.
*
* This version requires /dev/tty. Use something else if you're
* reading from a pipe.
*/
static int getpin_tty(const char *prompt,
char *pinbuf,
const size_t pinbuf_len)
{
struct termios oflags, nflags;
int c = '\0', ok = 0, fixtty = 0;
char *p = pinbuf;
FILE *f = NULL;
assert(prompt != NULL && pinbuf != NULL);
if ((f = fopen("/dev/tty", "r+")) == NULL ||
setvbuf(f, NULL, _IONBF, 0) == EOF)
goto fail;
tcgetattr(fileno(f), &oflags);
nflags = oflags;
nflags.c_lflag &= ~ (ECHO | ICANON);
if (tcsetattr(fileno(f), TCSANOW, &nflags) < 0)
goto fail;
fixtty = 1;
fputs(prompt, f);
while (p < pinbuf + pinbuf_len - 1 && (c = fgetc(f)) != EOF && c != '\n') {
fputc('*', f);
*p++ = (char) c;
}
assert(p <= pinbuf + pinbuf_len - 1);
*p = '\0';
fputc('\n', f);
ok = (c == '\n');
fail:
if (!ok)
perror("getpin_tty() failed");
if (fixtty && tcsetattr(fileno(f), TCSANOW, &oflags) < 0)
perror("getpin_tty() couldn't restore termios settings");
if (f != NULL)
fclose(f);
return ok;
}
#define OPTIONS \
OPT_FLG('h', "help", "show help") \
OPT_FLG('s', "set-so-pin", "set Security Officer PIN") \
OPT_FLG('u', "set-user-pin", "set \"user\" PIN") \
OPT_ARG('i', "set-iterations", "set PBKDF2 iteration count") \
OPT_FLG('p', "pin-from-stdin", "read PIN from stdin instead of /dev/tty") \
OPT_END
#define OPT_END
static void usage(const int code, const char *jane)
{
assert(jane != NULL);
FILE *f = code ? stderr : stdout;
fprintf(f, "usage: %s [options]\noptions:\n", jane);
#define OPT_FLG(_short_, _long_, _help_) fprintf(f, " -%c --%-32s%s\n", _short_, _long_, _help_);
#define OPT_ARG(_short_, _long_, _help_) fprintf(f, " -%c ARG --%-32s%s\n", _short_, _long_ " ARG", _help_);
OPTIONS;
#undef OPT_ARG
#undef OPT_FLG
exit(code);
}
static void parse_args(int argc, char *argv[],
int *do_set_so_pin,
int *do_set_user_pin,
int *do_set_iterations,
int *read_from_stdin,
unsigned long *iterations)
{
char *endptr;
int c;
#define OPT_FLG(_short_, _long_, _help_) _short_,
#define OPT_ARG(_short_, _long_, _help_) _short_, ':',
static const char short_opts[] = { OPTIONS '\0' };
#undef OPT_ARG
#undef OPT_FLG
#define OPT_FLG(_short_, _long_, _help_) { _long_, no_argument, NULL, _short_ },
#define OPT_ARG(_short_, _long_, _help_) { _long_, required_argument, NULL, _short_ },
struct option long_opts[] = { OPTIONS { NULL } };
#undef OPT_ARG
#undef OPT_FLG
assert(argv != 0 &&
do_set_so_pin != 0 && do_set_user_pin != 0 && do_set_iterations != NULL &&
read_from_stdin != NULL && iterations != NULL);
opterr = 0;
if (argc == 1)
usage(0, argv[0]);
while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) > 0) {
switch (c) {
case 'h':
usage(0, argv[0]);
case 'i':
*do_set_iterations = 1;
*iterations = strtoul(optarg, &endptr, 0);
if (*optarg == '\0' || *endptr != '\0')
usage(1, argv[0]);
continue;
case 'p':
*read_from_stdin = 1;
continue;
case 's':
*do_set_so_pin = 1;
continue;
case 'u':
*do_set_user_pin = 1;
continue;
default:
usage(1, argv[0]);
}
}
if (optind != argc)
usage(1, argv[0]);
}
#define lose(_msg_) \
do { \
fprintf(stderr, "%s\n", _msg_); \
goto fail; \
} while (0)
static int set_iterations(unsigned long iterations)
{
static const char update_query[] =
" UPDATE global SET pbkdf2_iterations = ?";
sqlite3_stmt *q = NULL;
int ok = 0;
if (!sql_check_ok(sql_prepare(&q, update_query)) ||
!sql_check_ok(sqlite3_bind_int64(q, 1, iterations)) ||
!sql_check_done(sqlite3_step(q)))
lose("Couldn't update database");
ok = 1;
fail:
sqlite3_finalize(q);
return ok;
}
static int set_pin(const char * const pin_type, const int read_from_stdin)
{
static const char iterations_query[] =
" SELECT pbkdf2_iterations FROM global";
static const char update_format[] =
" UPDATE global SET %s_pin = ?1, %s_pin_salt = ?2";
/* Allow user to change these lengths? */
uint8_t pinbuf[32], salt[16];
char pin[P11_MAX_PIN_LENGTH + 1], *p;
sqlite3_stmt *q = NULL;
hal_error_t err;
int ok = 0;
if (read_from_stdin) {
if (fgets(pin, sizeof(pin), stdin) == NULL) {
perror("Couldn't read PIN");
return 0;
}
if ((p = strchr(pin, '\n')) != NULL)
*p = '\0';
}
else {
char prompt[sizeof("Enter user PIN: ")];
snprintf(prompt, sizeof(prompt), "Enter %s PIN: ", pin_type);
if (!getpin_tty(prompt, pin, sizeof(pin)))
return 0;
}
const size_t len = strlen(pin);
if (len < P11_MIN_PIN_LENGTH || len > P11_MAX_PIN_LENGTH) {
fprintf(stderr, "Unacceptable length %lu for %s PIN, allowd range [%lu, %lu]\n",
(unsigned long) len, pin_type,
(unsigned long) P11_MIN_PIN_LENGTH, (unsigned long) P11_MAX_PIN_LENGTH);
return 0;
}
if (!sql_check_ok(sql_prepare(&q, iterations_query)) ||
!sql_check_row(sqlite3_step(q)) ||
sqlite3_column_type(q, 0) == SQLITE_NULL)
lose("Couldn't retrieve PBKDF2 iteration count from SQL");
if ((err = hal_get_random(salt, sizeof(salt))) != HAL_OK) {
fprintf(stderr, "Couldn't generate salt: %s\n", hal_error_string(err));
goto fail;
}
if ((err = hal_pbkdf2(hal_hash_sha256, (uint8_t *) pin, len, salt, sizeof(salt),
pinbuf, sizeof(pinbuf), sqlite3_column_int(q, 0))) != HAL_OK) {
fprintf(stderr, "Couldn't process new PIN: %s\n", hal_error_string(err));
goto fail;
}
if (!sql_check_ok(sql_finalize_and_clear(&q)) ||
!sql_check_ok(sql_prepare(&q, update_format, pin_type, pin_type)) ||
!sql_check_ok(sqlite3_bind_blob(q, 1, pinbuf, sizeof(pinbuf), NULL)) ||
!sql_check_ok(sqlite3_bind_blob(q, 2, salt, sizeof(salt), NULL)) ||
!sql_check_done(sqlite3_step(q)))
lose("Couldn't update database");
ok = 1;
fail:
sqlite3_finalize(q);
memset(pin, 0, sizeof(pin));
memset(pinbuf, 0, sizeof(pinbuf));
memset(salt, 0, sizeof(salt));
return ok;
}
int main(int argc, char *argv[])
{
int do_set_so_pin = 0, do_set_user_pin = 0, do_set_iterations = 0, read_from_stdin = 0;
unsigned long iterations;
int ok = 0;
parse_args(argc, argv, &do_set_so_pin, &do_set_user_pin, &do_set_iterations, &read_from_stdin, &iterations);
if (!sql_init() || !sql_exec("BEGIN"))
lose("Couldn't initialize SQL, giving up");
if (do_set_iterations && !set_iterations(iterations))
lose("Couldn't set PBKDF2 iteration count");
if (do_set_so_pin && !set_pin("so", read_from_stdin))
lose("Couldn't set SO PIN");
if (do_set_user_pin && !set_pin("user", read_from_stdin))
lose("Couldn't set user PIN");
if (!sql_exec("COMMIT"))
lose("Couldn't commit SQL transaction");
ok = 1;
fail:
sql_fini();
return !ok;
}
/*
* Local variables:
* indent-tabs-mode: nil
* End:
*/