aboutsummaryrefslogtreecommitdiff
path: root/nkotp.c
diff options
context:
space:
mode:
authorRobin Krahl <robin.krahl@ireas.org>2018-02-14 11:12:45 +0100
committerRobin Krahl <robin.krahl@ireas.org>2018-02-14 11:14:30 +0100
commit10fcd3a946a270fc6d111252b2de08dcd625a2b8 (patch)
tree32f874025493ee14563a02dc9c767b3f9e3739b8 /nkotp.c
downloadnkotp-10fcd3a946a270fc6d111252b2de08dcd625a2b8.tar.gz
nkotp-10fcd3a946a270fc6d111252b2de08dcd625a2b8.tar.bz2
Initial commit with support for OTP generationHEADmaster
Diffstat (limited to 'nkotp.c')
-rw-r--r--nkotp.c331
1 files changed, 331 insertions, 0 deletions
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 <robin.krahl@ireas.org>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <stdbool.h> /* libnitrokey needs stdbool.h */
+#include <libnitrokey/NK_C_API.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+
+#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;
+}