aboutsummaryrefslogtreecommitdiff
path: root/p11util.c
diff options
context:
space:
mode:
authorRob Austein <sra@hactrn.net>2015-07-01 16:30:51 -0400
committerRob Austein <sra@hactrn.net>2015-07-01 16:30:51 -0400
commitc45562762aab7e874eac71792f9eebb5185ee47d (patch)
tree99c2943914bc095ee1a6ba4b83d8fc0c98fd8f61 /p11util.c
parent4f7d9f0bcc0de535083bdc240f1f5c1037955990 (diff)
Add p11util program to do things like fiddling with the BPKDF2
iteration count, setting PINs, and so forth. Factor some SQL utility code out to a separate file so we can reuse it for p11util.
Diffstat (limited to 'p11util.c')
-rw-r--r--p11util.c301
1 files changed, 301 insertions, 0 deletions
diff --git a/p11util.c b/p11util.c
new file mode 100644
index 0000000..514422d
--- /dev/null
+++ b/p11util.c
@@ -0,0 +1,301 @@
+/*
+ * Command line tool for setting up PKCS #11. Mostly this means
+ * things like setting PINs.
+ */
+
+/*
+ * Apparently getopt_long() works everywhere we're likely to care
+ * about. At least, we've been getting away with it for years in
+ * rcynic. rcynic.c has code to wrap option and usage stuff using
+ * getopt_long(), proably just reuse that.
+ */
+
+#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('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_ARG('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", _short_, _long_, _help_);
+#define OPT_ARG(_short_, _long_, _help_) fprintf(f, " -%c ARG --%-32s%s", _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;
+
+ 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;
+}