From 10fcd3a946a270fc6d111252b2de08dcd625a2b8 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 14 Feb 2018 11:12:45 +0100 Subject: Initial commit with support for OTP generation --- nkotp.c | 331 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 nkotp.c (limited to 'nkotp.c') diff --git a/nkotp.c b/nkotp.c new file mode 100644 index 0000000..2c8e251 --- /dev/null +++ b/nkotp.c @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2018 Robin Krahl + * + * SPDX-License-Identifier: MIT + */ + +#include /* libnitrokey needs stdbool.h */ +#include +#include +#include +#include +#include +#include +#include + +#include "options.h" + +static const size_t MAX_PWLEN = 25; +static const size_t MAX_TMP_PWLEN = 25; + +/* operation modes */ +static int mod_generate(struct options *options); + +/* device communication */ +static int authenticate_user(char *tmp_pw, size_t len); +static int connect(enum model model); +static int otp_needs_password(void); +static int otp_generate(enum algorithm alg, unsigned int slot, + const char **otp); +static int otp_generate_password(enum algorithm alg, unsigned int slot, + const char **otp); + +/* helpers */ +static int generate_tmp_password(char *tmp_pw, size_t len); +static int read_user_password(char *pw, size_t len); +static int read_password(const char *prompt, char *pw, size_t len, + FILE *stream); +static int read_password_line(char *pw, size_t len, FILE *stream); + +int main(int argc, char **argv) +{ + int err; + struct options options = {0}; + + err = parse_options(argc, argv, &options); + if (err > 0) + goto cleanup; + + switch (options.mode) { + case MODE_GENERATE: + err = mod_generate(&options); + break; + case MODE_NONE: + break; + } + +cleanup: + free_options(&options); + + return err; +} + +static int mod_generate(struct options *options) +{ + int err = 0; + const char *otp = NULL; + + err = connect(options->model); + if (err) + return err; + + if (otp_needs_password()) + err = otp_generate_password(options->alg, options->slot, &otp); + else + err = otp_generate(options->alg, options->slot, &otp); + + if (!err) + printf("%s\n", otp); + + free((char *) otp); + + return err; +} + +static int authenticate_user(char *tmp_pw, size_t len) +{ + int err = 0; + char pw[MAX_PWLEN]; + + err = read_user_password(pw, MAX_PWLEN); + if (err) + return err; + + err = generate_tmp_password(tmp_pw, len); + if (err) + goto cleanup; + + err = NK_user_authenticate(pw, tmp_pw); + if (err) { + memset(tmp_pw, 0, len); + fprintf(stderr, "User authentication failed.\n"); + } + +cleanup: + memset(pw, 0, MAX_PWLEN); + + return err; +} + +static int connect(enum model model) +{ + int nk_err = 0; + + switch (model) { + case MODEL_AUTO: + printf("Trying to connect in automatic mode ...\n"); + nk_err = NK_login_auto(); + break; + case MODEL_PRO: + printf("Trying to connect to a Nitrokey Pro ...\n"); + nk_err = NK_login("P"); + break; + case MODEL_STORAGE: + printf("Trying to connect to a Nitrokey Storage ...\n"); + nk_err = NK_login("S"); + break; + case MODEL_DEFAULT: + default: + fprintf(stderr, "Programming error: invalid model.\n"); + return 1; + } + + if (nk_err == 0) { + fprintf(stderr, "Could not connect to the Nitrokey device.\n"); + return 1; + } + + printf("successfully connected.\n"); + + return 0; +} + +static int otp_needs_password(void) +{ + /* + * config = {numlock, capslock, scrollock, enable_user_password, + * disable_user_password} + */ + const uint8_t *config = NK_read_config(); + return config[3]; +} + +static int otp_generate(enum algorithm alg, unsigned int slot, + const char **otp) +{ + int err = 0; + + *otp = NULL; + switch (alg) { + case ALGORITHM_HOTP: + if (slot < 1 || slot > MAX_SLOT_HOTP) { + fprintf(stderr, "Programming error: invalid slot.\n"); + err = 1; + } else { + *otp = NK_get_hotp_code(slot); + } + break; + case ALGORITHM_TOTP: + if (slot < 1 || slot > MAX_SLOT_TOTP) { + fprintf(stderr, "Programming error: invalid slot.\n"); + err = 1; + } else { + /* TODO: pass appropriate values */ + *otp = NK_get_totp_code(slot, 0, 0, 0); + } + break; + default: + fprintf(stderr, "Programming error: invalid algorithm.\n"); + err = 1; + } + + return err; +} + +static int otp_generate_password(enum algorithm alg, unsigned int slot, + const char **otp) +{ + int err = 0; + char tmp_pw[MAX_TMP_PWLEN]; + + err = authenticate_user(tmp_pw, MAX_TMP_PWLEN); + if (err) + return err; + + *otp = NULL; + switch (alg) { + case ALGORITHM_HOTP: + if (slot < 1 || slot > MAX_SLOT_HOTP) { + fprintf(stderr, "Programming error: invalid slot.\n"); + err = 1; + } else { + *otp = NK_get_hotp_code_PIN(slot, tmp_pw); + } + break; + case ALGORITHM_TOTP: + if (slot < 1 || slot > MAX_SLOT_TOTP) { + fprintf(stderr, "Programming error: invalid slot.\n"); + err = 1; + } else { + /* TODO: pass appropriate values */ + *otp = NK_get_totp_code_PIN(slot, 0, 0, 0, tmp_pw); + } + break; + default: + fprintf(stderr, "Programming error: invalid algorithm.\n"); + err = 1; + } + + memset(tmp_pw, 0, MAX_TMP_PWLEN); + + return err; +} + +static int generate_tmp_password(char *tmp_pw, size_t len) +{ + static const char *charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn" + "opqrstuvwxyz0123456789"; + size_t charset_len = strlen(charset); + + /* TODO: use a better seed, maybe even a better generator */ + srandom(time(NULL)); + + for (size_t i = 0; i < len - 1; i++) { + size_t j = random() / (RAND_MAX / charset_len); + tmp_pw[i] = charset[j]; + } + tmp_pw[len - 1] = '\0'; + + return 0; +} + +static int read_user_password(char *pw, size_t len) +{ + int err = 0; + char *env = NULL; + + env = getenv("NKOTP_USER_PASSWORD"); + if (env) { + /* TODO: rethink policy for overlong password inputs */ + strncpy(pw, env, len); + if (pw[len - 1] != '\0') { + memset(pw, 0, len); + fprintf(stderr, "The user password is too long.\n"); + err = 1; + } + } else { + err = read_password("User password: ", pw, len, stdin); + } + + return err; +} + +static int read_password(const char *prompt, char *pw, size_t len, + FILE *stream) +{ + int err = 0; + struct termios old, new; + + /* get and store current terminal state */ + err = tcgetattr(fileno(stdin), &old); + if (err) + goto cleanup; + + /* surpress echo for password input */ + new = old; + new.c_lflag &= ~ECHO; + err = tcsetattr(fileno(stdin), TCSAFLUSH, &new); + if (err) + goto cleanup; + + printf(prompt); + fflush(stdout); + + /* TODO: rethink policy for overlong password inputs */ + err = read_password_line(pw, len, stream); + + /* reset terminal state */ + tcsetattr(fileno(stdin), TCSAFLUSH, &old); + +cleanup: + if (err) + fprintf(stderr, "Could not read password. Aborting.\n"); + + return err; +} + +static int read_password_line(char *pw, size_t len, FILE *stream) +{ + int err = 0; + char *line = NULL; + size_t n = 0; + int nread = 0; + + nread = getline(&line, &n, stdin); + /* no echo, so manually print the newline before doing anything */ + printf("\n"); + if (nread < 0) { + fprintf(stderr, "Could not read user input.\n"); + err = 1; + goto cleanup; + } + if (nread == 0) { + fprintf(stderr, "Empty password provided. Aborting.\n"); + err = 1; + goto cleanup; + } + line[nread - 1] = '\0'; + + /* TODO: rethink policy for overlong password inputs */ + strncpy(pw, line, len); + if (pw[len - 1] != '\0') { + memset(pw, 0, len); + fprintf(stderr, "The user password is too long.\n"); + err = 1; + } + +cleanup: + free(line); + + return err; +} -- cgit v1.2.1