summaryrefslogtreecommitdiffstats
path: root/tools/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/util.c')
-rw-r--r--tools/util.c643
1 files changed, 643 insertions, 0 deletions
diff --git a/tools/util.c b/tools/util.c
new file mode 100644
index 0000000..0e518bb
--- /dev/null
+++ b/tools/util.c
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#include <fido.h>
+#include <fido/es256.h>
+#include <fido/es384.h>
+#include <fido/rs256.h>
+#include <fido/eddsa.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#ifdef _MSC_VER
+#include "../openbsd-compat/posix_win.h"
+#endif
+
+#include "extern.h"
+
+char *
+get_pin(const char *path)
+{
+ char *pin;
+ char prompt[1024];
+ int r, ok = -1;
+
+ if ((pin = calloc(1, PINBUF_LEN)) == NULL) {
+ warn("%s: calloc", __func__);
+ return NULL;
+ }
+ if ((r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ",
+ path)) < 0 || (size_t)r >= sizeof(prompt)) {
+ warn("%s: snprintf", __func__);
+ goto out;
+ }
+ if (!readpassphrase(prompt, pin, PINBUF_LEN, RPP_ECHO_OFF)) {
+ warnx("%s: readpassphrase", __func__);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ if (ok < 0) {
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+
+ return pin;
+}
+
+FILE *
+open_write(const char *file)
+{
+ int fd;
+ FILE *f;
+
+ if (file == NULL || strcmp(file, "-") == 0)
+ return (stdout);
+ if ((fd = open(file, O_WRONLY | O_CREAT, 0600)) < 0)
+ err(1, "open %s", file);
+ if ((f = fdopen(fd, "w")) == NULL)
+ err(1, "fdopen %s", file);
+
+ return (f);
+}
+
+FILE *
+open_read(const char *file)
+{
+ int fd;
+ FILE *f;
+
+ if (file == NULL || strcmp(file, "-") == 0) {
+#ifdef FIDO_FUZZ
+ setvbuf(stdin, NULL, _IONBF, 0);
+#endif
+ return (stdin);
+ }
+ if ((fd = open(file, O_RDONLY)) < 0)
+ err(1, "open %s", file);
+ if ((f = fdopen(fd, "r")) == NULL)
+ err(1, "fdopen %s", file);
+
+ return (f);
+}
+
+int
+base10(const char *str)
+{
+ char *ep;
+ long long ll;
+
+ ll = strtoll(str, &ep, 10);
+ if (str == ep || *ep != '\0')
+ return (-1);
+ else if (ll == LLONG_MIN && errno == ERANGE)
+ return (-1);
+ else if (ll == LLONG_MAX && errno == ERANGE)
+ return (-1);
+ else if (ll < 0 || ll > INT_MAX)
+ return (-1);
+
+ return ((int)ll);
+}
+
+void
+xxd(const void *buf, size_t count)
+{
+ const uint8_t *ptr = buf;
+ size_t i;
+
+ fprintf(stderr, " ");
+
+ for (i = 0; i < count; i++) {
+ fprintf(stderr, "%02x ", *ptr++);
+ if ((i + 1) % 16 == 0 && i + 1 < count)
+ fprintf(stderr, "\n ");
+ }
+
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+int
+string_read(FILE *f, char **out)
+{
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t n;
+
+ *out = NULL;
+
+ if ((n = getline(&line, &linesize, f)) <= 0 ||
+ (size_t)n != strlen(line)) {
+ free(line);
+ return (-1);
+ }
+
+ line[n - 1] = '\0'; /* trim \n */
+ *out = line;
+
+ return (0);
+}
+
+fido_dev_t *
+open_dev(const char *path)
+{
+ fido_dev_t *dev;
+ int r;
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+
+ r = fido_dev_open(dev, path);
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_open %s: %s", path, fido_strerr(r));
+
+ return (dev);
+}
+
+int
+get_devopt(fido_dev_t *dev, const char *name, int *val)
+{
+ fido_cbor_info_t *cbor_info;
+ char * const *names;
+ const bool *values;
+ int r, ok = -1;
+
+ if ((cbor_info = fido_cbor_info_new()) == NULL) {
+ warnx("fido_cbor_info_new");
+ goto out;
+ }
+
+ if ((r = fido_dev_get_cbor_info(dev, cbor_info)) != FIDO_OK) {
+ warnx("fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r);
+ goto out;
+ }
+
+ if ((names = fido_cbor_info_options_name_ptr(cbor_info)) == NULL ||
+ (values = fido_cbor_info_options_value_ptr(cbor_info)) == NULL) {
+ warnx("fido_dev_get_cbor_info: NULL name/value pointer");
+ goto out;
+ }
+
+ *val = -1;
+ for (size_t i = 0; i < fido_cbor_info_options_len(cbor_info); i++)
+ if (strcmp(names[i], name) == 0) {
+ *val = values[i];
+ break;
+ }
+
+ ok = 0;
+out:
+ fido_cbor_info_free(&cbor_info);
+
+ return (ok);
+}
+
+EC_KEY *
+read_ec_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ EC_KEY *ec = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+ if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) {
+ warnx("EVP_PKEY_get1_EC_KEY");
+ goto fail;
+ }
+
+fail:
+ if (fp) {
+ fclose(fp);
+ }
+ if (pkey) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ec);
+}
+
+int
+write_es256_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ es256_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = es256_pk_new()) == NULL) {
+ warnx("es256_pk_new");
+ goto fail;
+ }
+
+ if (es256_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("es256_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("es256_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es256_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+int
+write_es384_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ es384_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = es384_pk_new()) == NULL) {
+ warnx("es384_pk_new");
+ goto fail;
+ }
+
+ if (es384_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("es384_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("es384_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es384_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+RSA *
+read_rsa_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
+ warnx("EVP_PKEY_get1_RSA");
+ goto fail;
+ }
+
+fail:
+ if (fp) {
+ fclose(fp);
+ }
+ if (pkey) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (rsa);
+}
+
+int
+write_rsa_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ rs256_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = rs256_pk_new()) == NULL) {
+ warnx("rs256_pk_new");
+ goto fail;
+ }
+
+ if (rs256_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("rs256_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("rs256_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ rs256_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+EVP_PKEY *
+read_eddsa_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+
+fail:
+ if (fp) {
+ fclose(fp);
+ }
+
+ return (pkey);
+}
+
+int
+write_eddsa_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ eddsa_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = eddsa_pk_new()) == NULL) {
+ warnx("eddsa_pk_new");
+ goto fail;
+ }
+
+ if (eddsa_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("eddsa_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("eddsa_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ eddsa_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+void
+print_cred(FILE *out_f, int type, const fido_cred_t *cred)
+{
+ char *id;
+ int r;
+
+ r = base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred), &id);
+ if (r < 0)
+ errx(1, "output error");
+
+ fprintf(out_f, "%s\n", id);
+
+ switch (type) {
+ case COSE_ES256:
+ write_es256_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ case COSE_ES384:
+ write_es384_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ case COSE_RS256:
+ write_rsa_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ case COSE_EDDSA:
+ write_eddsa_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ default:
+ errx(1, "print_cred: unknown type");
+ }
+
+ free(id);
+}
+
+int
+cose_type(const char *str, int *type)
+{
+ if (strcmp(str, "es256") == 0)
+ *type = COSE_ES256;
+ else if (strcmp(str, "es384") == 0)
+ *type = COSE_ES384;
+ else if (strcmp(str, "rs256") == 0)
+ *type = COSE_RS256;
+ else if (strcmp(str, "eddsa") == 0)
+ *type = COSE_EDDSA;
+ else {
+ *type = 0;
+ return (-1);
+ }
+
+ return (0);
+}
+
+const char *
+cose_string(int type)
+{
+ switch (type) {
+ case COSE_ES256:
+ return ("es256");
+ case COSE_ES384:
+ return ("es384");
+ case COSE_RS256:
+ return ("rs256");
+ case COSE_EDDSA:
+ return ("eddsa");
+ default:
+ return ("unknown");
+ }
+}
+
+const char *
+prot_string(int prot)
+{
+ switch (prot) {
+ case FIDO_CRED_PROT_UV_OPTIONAL:
+ return ("uvopt");
+ case FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID:
+ return ("uvopt+id");
+ case FIDO_CRED_PROT_UV_REQUIRED:
+ return ("uvreq");
+ default:
+ return ("unknown");
+ }
+}
+
+int
+read_file(const char *path, u_char **ptr, size_t *len)
+{
+ int fd, ok = -1;
+ struct stat st;
+ ssize_t n;
+
+ *ptr = NULL;
+ *len = 0;
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ warn("%s: open %s", __func__, path);
+ goto fail;
+ }
+ if (fstat(fd, &st) < 0) {
+ warn("%s: stat %s", __func__, path);
+ goto fail;
+ }
+ if (st.st_size < 0) {
+ warnx("%s: stat %s: invalid size", __func__, path);
+ goto fail;
+ }
+ *len = (size_t)st.st_size;
+ if ((*ptr = malloc(*len)) == NULL) {
+ warn("%s: malloc", __func__);
+ goto fail;
+ }
+ if ((n = read(fd, *ptr, *len)) < 0) {
+ warn("%s: read", __func__);
+ goto fail;
+ }
+ if ((size_t)n != *len) {
+ warnx("%s: read", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1) {
+ close(fd);
+ }
+ if (ok < 0) {
+ free(*ptr);
+ *ptr = NULL;
+ *len = 0;
+ }
+
+ return ok;
+}
+
+int
+write_file(const char *path, const u_char *ptr, size_t len)
+{
+ int fd, ok = -1;
+ ssize_t n;
+
+ if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) < 0) {
+ warn("%s: open %s", __func__, path);
+ goto fail;
+ }
+ if ((n = write(fd, ptr, len)) < 0) {
+ warn("%s: write", __func__);
+ goto fail;
+ }
+ if ((size_t)n != len) {
+ warnx("%s: write", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1) {
+ close(fd);
+ }
+
+ return ok;
+}
+
+const char *
+plural(size_t x)
+{
+ return x == 1 ? "" : "s";
+}
+
+int
+should_retry_with_pin(const fido_dev_t *dev, int r)
+{
+ if (fido_dev_has_pin(dev) == false) {
+ return 0;
+ }
+
+ switch (r) {
+ case FIDO_ERR_PIN_REQUIRED:
+ case FIDO_ERR_UNAUTHORIZED_PERM:
+ case FIDO_ERR_UV_BLOCKED:
+ case FIDO_ERR_UV_INVALID:
+ return 1;
+ }
+
+ return 0;
+}