aboutsummaryrefslogblamecommitdiff
path: root/p11util.c
blob: a08cb14d9b1b46abeac4c7f709362c84c76e279e (plain) (tree)
1
2
3
4
5
6
7
8
9
10
  


            

                                                                

                      

                                   
  




                                                                           
  


                                                                         
  


                                                                           
  










                                                                           

   
                     






















                                                                      

                                              















































                                                                             





                                                                                 



               
                                                   





                                                      

                                                                                                               






              
                                              




                                                 
















                                                                                      

                                                                                   

             


                      









                                                                          
                          
























                           



                                         








                                                   


                                                                  











                                                                          
                                        
































                                                                                    

                                                                                    


             

                                                          

                                                              
 
                                                                   



                                                                           
                                                                                        
                                                                                       



                                                                             



                                                                                  

                                       
 









                                    
                                



























                                                                                                              





                        
/*
 * p11util.c
 * ---------
 *
 * Command line tool for setting up PKCS #11.  Mostly this means
 * things like setting PINs.
 *
 * Author: Rob Austein
 * Copyright (c) 2015, NORDUnet A/S
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - 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.
 *
 * - Neither the name of the NORDUnet nor the names of its contributors may
 *   be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * 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
 * HOLDER 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(NULL, salt, sizeof(salt))) != HAL_OK) {
    fprintf(stderr, "Couldn't generate salt: %s\n", hal_error_string(err));
    goto fail;
  }

  if ((err = hal_pbkdf2(NULL, 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:
 */