summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:23:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:23:06 +0000
commitcd13e2506379761d3464eae917eb1881876a7aa1 (patch)
tree54d32c05b0d6396408ff0d599d771fb9e7cdd60c /tools
parentInitial commit. (diff)
downloadlibfido2-cd13e2506379761d3464eae917eb1881876a7aa1.tar.xz
libfido2-cd13e2506379761d3464eae917eb1881876a7aa1.zip
Adding upstream version 1.12.0.upstream/1.12.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools')
-rw-r--r--tools/CMakeLists.txt74
-rw-r--r--tools/assert_get.c321
-rw-r--r--tools/assert_verify.c208
-rw-r--r--tools/base64.c135
-rw-r--r--tools/bio.c278
-rw-r--r--tools/config.c198
-rw-r--r--tools/cred_make.c247
-rw-r--r--tools/cred_verify.c182
-rw-r--r--tools/credman.c330
-rw-r--r--tools/extern.h102
-rw-r--r--tools/fido2-assert.c55
-rwxr-xr-xtools/fido2-attach.sh15
-rw-r--r--tools/fido2-cred.c53
-rwxr-xr-xtools/fido2-detach.sh13
-rw-r--r--tools/fido2-token.c110
-rwxr-xr-xtools/fido2-unprot.sh76
-rwxr-xr-xtools/include_check.sh22
-rw-r--r--tools/largeblob.c594
-rw-r--r--tools/pin.c159
-rwxr-xr-xtools/test.sh304
-rw-r--r--tools/token.c729
-rw-r--r--tools/util.c643
22 files changed, 4848 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000..e1f4366
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,74 @@
+# 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
+
+list(APPEND COMPAT_SOURCES
+ ../openbsd-compat/bsd-getpagesize.c
+ ../openbsd-compat/explicit_bzero.c
+ ../openbsd-compat/freezero.c
+ ../openbsd-compat/recallocarray.c
+ ../openbsd-compat/strlcat.c
+ ../openbsd-compat/strlcpy.c
+ ../openbsd-compat/strsep.c
+)
+
+if(WIN32 AND NOT CYGWIN AND NOT MSYS)
+ list(APPEND COMPAT_SOURCES
+ ../openbsd-compat/bsd-getline.c
+ ../openbsd-compat/endian_win32.c
+ ../openbsd-compat/explicit_bzero_win32.c
+ ../openbsd-compat/getopt_long.c
+ ../openbsd-compat/readpassphrase_win32.c
+ )
+ if (BUILD_SHARED_LIBS)
+ list(APPEND COMPAT_SOURCES ../openbsd-compat/posix_win.c)
+ endif()
+else()
+ list(APPEND COMPAT_SOURCES ../openbsd-compat/readpassphrase.c)
+endif()
+
+if(NOT MSVC)
+ set_source_files_properties(assert_get.c assert_verify.c base64.c bio.c
+ config.c cred_make.c cred_verify.c credman.c fido2-assert.c
+ fido2-cred.c fido2-token.c pin.c token.c util.c
+ PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS}")
+endif()
+
+add_executable(fido2-cred
+ fido2-cred.c
+ cred_make.c
+ cred_verify.c
+ base64.c
+ util.c
+ ${COMPAT_SOURCES}
+)
+
+add_executable(fido2-assert
+ fido2-assert.c
+ assert_get.c
+ assert_verify.c
+ base64.c
+ util.c
+ ${COMPAT_SOURCES}
+)
+
+add_executable(fido2-token
+ fido2-token.c
+ base64.c
+ bio.c
+ config.c
+ credman.c
+ largeblob.c
+ pin.c
+ token.c
+ util.c
+ ${COMPAT_SOURCES}
+)
+
+target_link_libraries(fido2-cred ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY})
+target_link_libraries(fido2-assert ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY})
+target_link_libraries(fido2-token ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY})
+
+install(TARGETS fido2-cred fido2-assert fido2-token
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/tools/assert_get.c b/tools/assert_get.c
new file mode 100644
index 0000000..8260fb8
--- /dev/null
+++ b/tools/assert_get.c
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2018 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 <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+struct toggle {
+ fido_opt_t up;
+ fido_opt_t uv;
+ fido_opt_t pin;
+};
+
+static const char *
+opt2str(fido_opt_t v)
+{
+ switch (v) {
+ case FIDO_OPT_OMIT:
+ return "omit";
+ case FIDO_OPT_TRUE:
+ return "true";
+ case FIDO_OPT_FALSE:
+ return "false";
+ default:
+ return "unknown";
+ }
+}
+
+static void
+parse_toggle(const char *str, struct toggle *opt)
+{
+ fido_opt_t *k;
+ fido_opt_t v;
+ char *assignment;
+ char *key;
+ char *val;
+
+ if ((assignment = strdup(str)) == NULL)
+ err(1, "strdup");
+ if ((val = strchr(assignment, '=')) == NULL)
+ errx(1, "invalid assignment '%s'", assignment);
+
+ key = assignment;
+ *val++ = '\0';
+
+ if (!strcmp(val, "true"))
+ v = FIDO_OPT_TRUE;
+ else if (!strcmp(val, "false"))
+ v = FIDO_OPT_FALSE;
+ else
+ errx(1, "unknown value '%s'", val);
+
+ if (!strcmp(key, "up"))
+ k = &opt->up;
+ else if (!strcmp(key, "uv"))
+ k = &opt->uv;
+ else if (!strcmp(key, "pin"))
+ k = &opt->pin;
+ else
+ errx(1, "unknown key '%s'", key);
+
+ free(assignment);
+
+ *k = v;
+}
+
+static fido_assert_t *
+prepare_assert(FILE *in_f, int flags, const struct toggle *opt)
+{
+ fido_assert_t *assert = NULL;
+ struct blob cdh;
+ struct blob id;
+ struct blob hmac_salt;
+ char *rpid = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&id, 0, sizeof(id));
+ memset(&hmac_salt, 0, sizeof(hmac_salt));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ if ((flags & FLAG_RK) == 0)
+ r |= base64_read(in_f, &id);
+ if (flags & FLAG_HMAC)
+ r |= base64_read(in_f, &hmac_salt);
+ if (r < 0)
+ errx(1, "input error");
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data hash:\n");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ if ((flags & FLAG_RK) == 0) {
+ fprintf(stderr, "credential id:\n");
+ xxd(id.ptr, id.len);
+ }
+ fprintf(stderr, "up=%s\n", opt2str(opt->up));
+ fprintf(stderr, "uv=%s\n", opt2str(opt->uv));
+ fprintf(stderr, "pin=%s\n", opt2str(opt->pin));
+ }
+
+ if ((assert = fido_assert_new()) == NULL)
+ errx(1, "fido_assert_new");
+
+ if ((r = fido_assert_set_clientdata_hash(assert, cdh.ptr,
+ cdh.len)) != FIDO_OK ||
+ (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK)
+ errx(1, "fido_assert_set: %s", fido_strerr(r));
+ if ((r = fido_assert_set_up(assert, opt->up)) != FIDO_OK)
+ errx(1, "fido_assert_set_up: %s", fido_strerr(r));
+ if ((r = fido_assert_set_uv(assert, opt->uv)) != FIDO_OK)
+ errx(1, "fido_assert_set_uv: %s", fido_strerr(r));
+
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_assert_set_extensions(assert,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s",
+ fido_strerr(r));
+ if ((r = fido_assert_set_hmac_salt(assert, hmac_salt.ptr,
+ hmac_salt.len)) != FIDO_OK)
+ errx(1, "fido_assert_set_hmac_salt: %s",
+ fido_strerr(r));
+ }
+ if (flags & FLAG_LARGEBLOB) {
+ if ((r = fido_assert_set_extensions(assert,
+ FIDO_EXT_LARGEBLOB_KEY)) != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s", fido_strerr(r));
+ }
+ if ((flags & FLAG_RK) == 0) {
+ if ((r = fido_assert_allow_cred(assert, id.ptr,
+ id.len)) != FIDO_OK)
+ errx(1, "fido_assert_allow_cred: %s", fido_strerr(r));
+ }
+
+ free(hmac_salt.ptr);
+ free(cdh.ptr);
+ free(id.ptr);
+ free(rpid);
+
+ return (assert);
+}
+
+static void
+print_assert(FILE *out_f, const fido_assert_t *assert, size_t idx, int flags)
+{
+ char *cdh = NULL;
+ char *authdata = NULL;
+ char *sig = NULL;
+ char *user_id = NULL;
+ char *hmac_secret = NULL;
+ char *key = NULL;
+ int r;
+
+ r = base64_encode(fido_assert_clientdata_hash_ptr(assert),
+ fido_assert_clientdata_hash_len(assert), &cdh);
+ r |= base64_encode(fido_assert_authdata_ptr(assert, idx),
+ fido_assert_authdata_len(assert, 0), &authdata);
+ r |= base64_encode(fido_assert_sig_ptr(assert, idx),
+ fido_assert_sig_len(assert, idx), &sig);
+ if (flags & FLAG_RK)
+ r |= base64_encode(fido_assert_user_id_ptr(assert, idx),
+ fido_assert_user_id_len(assert, idx), &user_id);
+ if (flags & FLAG_HMAC)
+ r |= base64_encode(fido_assert_hmac_secret_ptr(assert, idx),
+ fido_assert_hmac_secret_len(assert, idx), &hmac_secret);
+ if (flags & FLAG_LARGEBLOB)
+ r |= base64_encode(fido_assert_largeblob_key_ptr(assert, idx),
+ fido_assert_largeblob_key_len(assert, idx), &key);
+ if (r < 0)
+ errx(1, "output error");
+
+ fprintf(out_f, "%s\n", cdh);
+ fprintf(out_f, "%s\n", fido_assert_rp_id(assert));
+ fprintf(out_f, "%s\n", authdata);
+ fprintf(out_f, "%s\n", sig);
+ if (flags & FLAG_RK)
+ fprintf(out_f, "%s\n", user_id);
+ if (hmac_secret) {
+ fprintf(out_f, "%s\n", hmac_secret);
+ explicit_bzero(hmac_secret, strlen(hmac_secret));
+ }
+ if (key) {
+ fprintf(out_f, "%s\n", key);
+ explicit_bzero(key, strlen(key));
+ }
+
+ free(key);
+ free(hmac_secret);
+ free(cdh);
+ free(authdata);
+ free(sig);
+ free(user_id);
+}
+
+int
+assert_get(int argc, char **argv)
+{
+ fido_dev_t *dev = NULL;
+ fido_assert_t *assert = NULL;
+ struct toggle opt;
+ char prompt[1024];
+ char pin[128];
+ char *in_path = NULL;
+ char *out_path = NULL;
+ FILE *in_f = NULL;
+ FILE *out_f = NULL;
+ int flags = 0;
+ int ch;
+ int r;
+
+ opt.up = opt.uv = opt.pin = FIDO_OPT_OMIT;
+
+ while ((ch = getopt(argc, argv, "bdhi:o:prt:uv")) != -1) {
+ switch (ch) {
+ case 'b':
+ flags |= FLAG_LARGEBLOB;
+ break;
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'o':
+ out_path = optarg;
+ break;
+ case 'p':
+ opt.up = FIDO_OPT_TRUE;
+ break;
+ case 'r':
+ flags |= FLAG_RK;
+ break;
+ case 't' :
+ parse_toggle(optarg, &opt);
+ break;
+ case 'u':
+ flags |= FLAG_U2F;
+ break;
+ case 'v':
+ /* -v implies both pin and uv for historical reasons */
+ opt.pin = FIDO_OPT_TRUE;
+ opt.uv = FIDO_OPT_TRUE;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ in_f = open_read(in_path);
+ out_f = open_write(out_path);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+
+ assert = prepare_assert(in_f, flags, &opt);
+
+ dev = open_dev(argv[0]);
+ if (flags & FLAG_U2F)
+ fido_dev_force_u2f(dev);
+
+ if (opt.pin == FIDO_OPT_TRUE) {
+ r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ",
+ argv[0]);
+ if (r < 0 || (size_t)r >= sizeof(prompt))
+ errx(1, "snprintf");
+ if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF))
+ errx(1, "readpassphrase");
+ if (strlen(pin) < 4 || strlen(pin) > 63) {
+ explicit_bzero(pin, sizeof(pin));
+ errx(1, "invalid PIN length");
+ }
+ r = fido_dev_get_assert(dev, assert, pin);
+ } else
+ r = fido_dev_get_assert(dev, assert, NULL);
+
+ explicit_bzero(pin, sizeof(pin));
+
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_get_assert: %s", fido_strerr(r));
+
+ if (flags & FLAG_RK) {
+ for (size_t idx = 0; idx < fido_assert_count(assert); idx++)
+ print_assert(out_f, assert, idx, flags);
+ } else {
+ if (fido_assert_count(assert) != 1)
+ errx(1, "fido_assert_count: %zu",
+ fido_assert_count(assert));
+ print_assert(out_f, assert, 0, flags);
+ }
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_assert_free(&assert);
+
+ fclose(in_f);
+ fclose(out_f);
+ in_f = NULL;
+ out_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/assert_verify.c b/tools/assert_verify.c
new file mode 100644
index 0000000..4cc2e86
--- /dev/null
+++ b/tools/assert_verify.c
@@ -0,0 +1,208 @@
+/*
+ * 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 <fido.h>
+#include <fido/es256.h>
+#include <fido/es384.h>
+#include <fido/rs256.h>
+#include <fido/eddsa.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static fido_assert_t *
+prepare_assert(FILE *in_f, int flags)
+{
+ fido_assert_t *assert = NULL;
+ struct blob cdh;
+ struct blob authdata;
+ struct blob sig;
+ char *rpid = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&authdata, 0, sizeof(authdata));
+ memset(&sig, 0, sizeof(sig));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ r |= base64_read(in_f, &authdata);
+ r |= base64_read(in_f, &sig);
+ if (r < 0)
+ errx(1, "input error");
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data hash:\n");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ fprintf(stderr, "authenticator data:\n");
+ xxd(authdata.ptr, authdata.len);
+ fprintf(stderr, "signature:\n");
+ xxd(sig.ptr, sig.len);
+ }
+
+ if ((assert = fido_assert_new()) == NULL)
+ errx(1, "fido_assert_new");
+ if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK)
+ errx(1, "fido_assert_count: %s", fido_strerr(r));
+
+ if ((r = fido_assert_set_clientdata_hash(assert, cdh.ptr,
+ cdh.len)) != FIDO_OK ||
+ (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK ||
+ (r = fido_assert_set_authdata(assert, 0, authdata.ptr,
+ authdata.len)) != FIDO_OK ||
+ (r = fido_assert_set_sig(assert, 0, sig.ptr, sig.len)) != FIDO_OK)
+ errx(1, "fido_assert_set: %s", fido_strerr(r));
+
+ if (flags & FLAG_UP) {
+ if ((r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_up: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_UV) {
+ if ((r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_uv: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_assert_set_extensions(assert,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s",
+ fido_strerr(r));
+ }
+
+ free(cdh.ptr);
+ free(authdata.ptr);
+ free(sig.ptr);
+ free(rpid);
+
+ return (assert);
+}
+
+static void *
+load_pubkey(int type, const char *file)
+{
+ EC_KEY *ec = NULL;
+ RSA *rsa = NULL;
+ EVP_PKEY *eddsa = NULL;
+ es256_pk_t *es256_pk = NULL;
+ es384_pk_t *es384_pk = NULL;
+ rs256_pk_t *rs256_pk = NULL;
+ eddsa_pk_t *eddsa_pk = NULL;
+ void *pk = NULL;
+
+ switch (type) {
+ case COSE_ES256:
+ if ((ec = read_ec_pubkey(file)) == NULL)
+ errx(1, "read_ec_pubkey");
+ if ((es256_pk = es256_pk_new()) == NULL)
+ errx(1, "es256_pk_new");
+ if (es256_pk_from_EC_KEY(es256_pk, ec) != FIDO_OK)
+ errx(1, "es256_pk_from_EC_KEY");
+ pk = es256_pk;
+ EC_KEY_free(ec);
+ break;
+ case COSE_ES384:
+ if ((ec = read_ec_pubkey(file)) == NULL)
+ errx(1, "read_ec_pubkey");
+ if ((es384_pk = es384_pk_new()) == NULL)
+ errx(1, "es384_pk_new");
+ if (es384_pk_from_EC_KEY(es384_pk, ec) != FIDO_OK)
+ errx(1, "es384_pk_from_EC_KEY");
+ pk = es384_pk;
+ EC_KEY_free(ec);
+ break;
+ case COSE_RS256:
+ if ((rsa = read_rsa_pubkey(file)) == NULL)
+ errx(1, "read_rsa_pubkey");
+ if ((rs256_pk = rs256_pk_new()) == NULL)
+ errx(1, "rs256_pk_new");
+ if (rs256_pk_from_RSA(rs256_pk, rsa) != FIDO_OK)
+ errx(1, "rs256_pk_from_RSA");
+ pk = rs256_pk;
+ RSA_free(rsa);
+ break;
+ case COSE_EDDSA:
+ if ((eddsa = read_eddsa_pubkey(file)) == NULL)
+ errx(1, "read_eddsa_pubkey");
+ if ((eddsa_pk = eddsa_pk_new()) == NULL)
+ errx(1, "eddsa_pk_new");
+ if (eddsa_pk_from_EVP_PKEY(eddsa_pk, eddsa) != FIDO_OK)
+ errx(1, "eddsa_pk_from_EVP_PKEY");
+ pk = eddsa_pk;
+ EVP_PKEY_free(eddsa);
+ break;
+ default:
+ errx(1, "invalid type %d", type);
+ }
+
+ return (pk);
+}
+
+int
+assert_verify(int argc, char **argv)
+{
+ fido_assert_t *assert = NULL;
+ void *pk = NULL;
+ char *in_path = NULL;
+ FILE *in_f = NULL;
+ int type = COSE_ES256;
+ int flags = 0;
+ int ch;
+ int r;
+
+ while ((ch = getopt(argc, argv, "dhi:pv")) != -1) {
+ switch (ch) {
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'p':
+ flags |= FLAG_UP;
+ break;
+ case 'v':
+ flags |= FLAG_UV;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1 || argc > 2)
+ usage();
+
+ in_f = open_read(in_path);
+
+ if (argc > 1 && cose_type(argv[1], &type) < 0)
+ errx(1, "unknown type %s", argv[1]);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+
+ pk = load_pubkey(type, argv[0]);
+ assert = prepare_assert(in_f, flags);
+ if ((r = fido_assert_verify(assert, 0, type, pk)) != FIDO_OK)
+ errx(1, "fido_assert_verify: %s", fido_strerr(r));
+ fido_assert_free(&assert);
+
+ fclose(in_f);
+ in_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/base64.c b/tools/base64.c
new file mode 100644
index 0000000..2cfa98d
--- /dev/null
+++ b/tools/base64.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018 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 <openssl/bio.h>
+#include <openssl/evp.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+base64_encode(const void *ptr, size_t len, char **out)
+{
+ BIO *bio_b64 = NULL;
+ BIO *bio_mem = NULL;
+ char *b64_ptr = NULL;
+ long b64_len;
+ int n;
+ int ok = -1;
+
+ if (ptr == NULL || out == NULL || len > INT_MAX)
+ return (-1);
+
+ *out = NULL;
+
+ if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL)
+ goto fail;
+ if ((bio_mem = BIO_new(BIO_s_mem())) == NULL)
+ goto fail;
+
+ BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
+ BIO_push(bio_b64, bio_mem);
+
+ n = BIO_write(bio_b64, ptr, (int)len);
+ if (n < 0 || (size_t)n != len)
+ goto fail;
+
+ if (BIO_flush(bio_b64) < 0)
+ goto fail;
+
+ b64_len = BIO_get_mem_data(bio_b64, &b64_ptr);
+ if (b64_len < 0 || (size_t)b64_len == SIZE_MAX || b64_ptr == NULL)
+ goto fail;
+ if ((*out = calloc(1, (size_t)b64_len + 1)) == NULL)
+ goto fail;
+
+ memcpy(*out, b64_ptr, (size_t)b64_len);
+ ok = 0;
+
+fail:
+ BIO_free(bio_b64);
+ BIO_free(bio_mem);
+
+ return (ok);
+}
+
+int
+base64_decode(const char *in, void **ptr, size_t *len)
+{
+ BIO *bio_mem = NULL;
+ BIO *bio_b64 = NULL;
+ size_t alloc_len;
+ int n;
+ int ok = -1;
+
+ if (in == NULL || ptr == NULL || len == NULL || strlen(in) > INT_MAX)
+ return (-1);
+
+ *ptr = NULL;
+ *len = 0;
+
+ if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL)
+ goto fail;
+ if ((bio_mem = BIO_new_mem_buf((const void *)in, -1)) == NULL)
+ goto fail;
+
+ BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
+ BIO_push(bio_b64, bio_mem);
+
+ alloc_len = strlen(in);
+ if ((*ptr = calloc(1, alloc_len)) == NULL)
+ goto fail;
+
+ n = BIO_read(bio_b64, *ptr, (int)alloc_len);
+ if (n <= 0 || BIO_eof(bio_b64) == 0)
+ goto fail;
+
+ *len = (size_t)n;
+ ok = 0;
+
+fail:
+ BIO_free(bio_b64);
+ BIO_free(bio_mem);
+
+ if (ok < 0) {
+ free(*ptr);
+ *ptr = NULL;
+ *len = 0;
+ }
+
+ return (ok);
+}
+
+int
+base64_read(FILE *f, struct blob *out)
+{
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t n;
+
+ out->ptr = NULL;
+ out->len = 0;
+
+ if ((n = getline(&line, &linesize, f)) <= 0 ||
+ (size_t)n != strlen(line)) {
+ free(line); /* XXX should be free'd _even_ if getline() fails */
+ return (-1);
+ }
+
+ if (base64_decode(line, (void **)&out->ptr, &out->len) < 0) {
+ free(line);
+ return (-1);
+ }
+
+ free(line);
+
+ return (0);
+}
diff --git a/tools/bio.c b/tools/bio.c
new file mode 100644
index 0000000..7a1406d
--- /dev/null
+++ b/tools/bio.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2019 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 <fido.h>
+#include <fido/bio.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static int
+print_template(const fido_bio_template_array_t *ta, size_t idx)
+{
+ const fido_bio_template_t *t = NULL;
+ char *id = NULL;
+
+ if ((t = fido_bio_template(ta, idx)) == NULL) {
+ warnx("fido_bio_template");
+ return -1;
+ }
+ if (base64_encode(fido_bio_template_id_ptr(t),
+ fido_bio_template_id_len(t), &id) < 0) {
+ warnx("output error");
+ return -1;
+ }
+
+ printf("%02u: %s %s\n", (unsigned)idx, id, fido_bio_template_name(t));
+ free(id);
+
+ return 0;
+}
+
+int
+bio_list(const char *path)
+{
+ fido_bio_template_array_t *ta = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ if ((ta = fido_bio_template_array_new()) == NULL)
+ errx(1, "fido_bio_template_array_new");
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_bio_dev_get_template_array(dev, ta, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_get_template_array: %s", fido_strerr(r));
+ goto out;
+ }
+ for (size_t i = 0; i < fido_bio_template_array_count(ta); i++)
+ if (print_template(ta, i) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ fido_bio_template_array_free(&ta);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+bio_set_name(const char *path, const char *id, const char *name)
+{
+ fido_bio_template_t *t = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ void *id_blob_ptr = NULL;
+ size_t id_blob_len = 0;
+ int r, ok = 1;
+
+ if ((t = fido_bio_template_new()) == NULL)
+ errx(1, "fido_bio_template_new");
+ if (base64_decode(id, &id_blob_ptr, &id_blob_len) < 0)
+ errx(1, "base64_decode");
+ if ((r = fido_bio_template_set_name(t, name)) != FIDO_OK)
+ errx(1, "fido_bio_template_set_name: %s", fido_strerr(r));
+ if ((r = fido_bio_template_set_id(t, id_blob_ptr,
+ id_blob_len)) != FIDO_OK)
+ errx(1, "fido_bio_template_set_id: %s", fido_strerr(r));
+
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_bio_dev_set_template_name(dev, t, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_set_template_name: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(id_blob_ptr);
+ fido_bio_template_free(&t);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static const char *
+enroll_strerr(uint8_t n)
+{
+ switch (n) {
+ case FIDO_BIO_ENROLL_FP_GOOD:
+ return "Sample ok";
+ case FIDO_BIO_ENROLL_FP_TOO_HIGH:
+ return "Sample too high";
+ case FIDO_BIO_ENROLL_FP_TOO_LOW:
+ return "Sample too low";
+ case FIDO_BIO_ENROLL_FP_TOO_LEFT:
+ return "Sample too left";
+ case FIDO_BIO_ENROLL_FP_TOO_RIGHT:
+ return "Sample too right";
+ case FIDO_BIO_ENROLL_FP_TOO_FAST:
+ return "Sample too fast";
+ case FIDO_BIO_ENROLL_FP_TOO_SLOW:
+ return "Sample too slow";
+ case FIDO_BIO_ENROLL_FP_POOR_QUALITY:
+ return "Poor quality sample";
+ case FIDO_BIO_ENROLL_FP_TOO_SKEWED:
+ return "Sample too skewed";
+ case FIDO_BIO_ENROLL_FP_TOO_SHORT:
+ return "Sample too short";
+ case FIDO_BIO_ENROLL_FP_MERGE_FAILURE:
+ return "Sample merge failure";
+ case FIDO_BIO_ENROLL_FP_EXISTS:
+ return "Sample exists";
+ case FIDO_BIO_ENROLL_FP_DATABASE_FULL:
+ return "Fingerprint database full";
+ case FIDO_BIO_ENROLL_NO_USER_ACTIVITY:
+ return "No user activity";
+ case FIDO_BIO_ENROLL_NO_USER_PRESENCE_TRANSITION:
+ return "No user presence transition";
+ default:
+ return "Unknown error";
+ }
+}
+
+int
+bio_enroll(const char *path)
+{
+ fido_bio_template_t *t = NULL;
+ fido_bio_enroll_t *e = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ if ((t = fido_bio_template_new()) == NULL)
+ errx(1, "fido_bio_template_new");
+ if ((e = fido_bio_enroll_new()) == NULL)
+ errx(1, "fido_bio_enroll_new");
+
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ printf("Touch your security key.\n");
+ r = fido_bio_dev_enroll_begin(dev, t, e, 10000, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_enroll_begin: %s", fido_strerr(r));
+ goto out;
+ }
+ printf("%s.\n", enroll_strerr(fido_bio_enroll_last_status(e)));
+
+ while (fido_bio_enroll_remaining_samples(e) > 0) {
+ printf("Touch your security key (%u sample%s left).\n",
+ (unsigned)fido_bio_enroll_remaining_samples(e),
+ plural(fido_bio_enroll_remaining_samples(e)));
+ if ((r = fido_bio_dev_enroll_continue(dev, t, e,
+ 10000)) != FIDO_OK) {
+ fido_dev_cancel(dev);
+ warnx("fido_bio_dev_enroll_continue: %s",
+ fido_strerr(r));
+ goto out;
+ }
+ printf("%s.\n", enroll_strerr(fido_bio_enroll_last_status(e)));
+ }
+
+ ok = 0;
+out:
+ fido_bio_template_free(&t);
+ fido_bio_enroll_free(&e);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+bio_delete(const char *path, const char *id)
+{
+ fido_bio_template_t *t = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ void *id_blob_ptr = NULL;
+ size_t id_blob_len = 0;
+ int r, ok = 1;
+
+ if ((t = fido_bio_template_new()) == NULL)
+ errx(1, "fido_bio_template_new");
+ if (base64_decode(id, &id_blob_ptr, &id_blob_len) < 0)
+ errx(1, "base64_decode");
+ if ((r = fido_bio_template_set_id(t, id_blob_ptr,
+ id_blob_len)) != FIDO_OK)
+ errx(1, "fido_bio_template_set_id: %s", fido_strerr(r));
+
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_bio_dev_enroll_remove(dev, t, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_enroll_remove: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(id_blob_ptr);
+ fido_bio_template_free(&t);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static const char *
+type_str(uint8_t t)
+{
+ switch (t) {
+ case 1:
+ return "touch";
+ case 2:
+ return "swipe";
+ default:
+ return "unknown";
+ }
+}
+
+void
+bio_info(fido_dev_t *dev)
+{
+ fido_bio_info_t *i = NULL;
+
+ if ((i = fido_bio_info_new()) == NULL) {
+ warnx("fido_bio_info_new");
+ return;
+ }
+ if (fido_bio_dev_get_info(dev, i) != FIDO_OK) {
+ fido_bio_info_free(&i);
+ return;
+ }
+
+ printf("sensor type: %u (%s)\n", (unsigned)fido_bio_info_type(i),
+ type_str(fido_bio_info_type(i)));
+ printf("max samples: %u\n", (unsigned)fido_bio_info_max_samples(i));
+
+ fido_bio_info_free(&i);
+}
diff --git a/tools/config.c b/tools/config.c
new file mode 100644
index 0000000..49253e8
--- /dev/null
+++ b/tools/config.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2020 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fido.h>
+#include <fido/config.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+config_entattest(char *path)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((r = fido_dev_enable_entattest(dev, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_enable_entattest(dev, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_enable_entattest: %s (0x%x)",
+ fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_always_uv(char *path, int toggle)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int v, r, ok = 1;
+
+ dev = open_dev(path);
+ if (get_devopt(dev, "alwaysUv", &v) < 0) {
+ warnx("%s: getdevopt", __func__);
+ goto out;
+ }
+ if (v == -1) {
+ warnx("%s: option not found", __func__);
+ goto out;
+ }
+ if (v == toggle) {
+ ok = 0;
+ goto out;
+ }
+ if ((r = fido_dev_toggle_always_uv(dev, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_toggle_always_uv(dev, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_toggle_always_uv: %s (0x%x)",
+ fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_pin_minlen(char *path, const char *pinlen)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int len, r, ok = 1;
+
+ dev = open_dev(path);
+ if ((len = base10(pinlen)) < 0 || len > 63) {
+ warnx("%s: len > 63", __func__);
+ goto out;
+ }
+ if ((r = fido_dev_set_pin_minlen(dev, (size_t)len, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_set_pin_minlen(dev, (size_t)len, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_set_pin_minlen: %s (0x%x)", fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_force_pin_change(char *path)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((r = fido_dev_force_pin_change(dev, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_force_pin_change(dev, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_force_pin_change: %s (0x%x)", fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_pin_minlen_rpid(char *path, const char *rpids)
+{
+ fido_dev_t *dev;
+ char *otmp, *tmp, *cp;
+ char *pin = NULL, **rpid = NULL;
+ int r, ok = 1;
+ size_t n;
+
+ if ((tmp = strdup(rpids)) == NULL)
+ err(1, "strdup");
+ otmp = tmp;
+ for (n = 0; (cp = strsep(&tmp, ",")) != NULL; n++) {
+ if (n == SIZE_MAX || (rpid = recallocarray(rpid, n, n + 1,
+ sizeof(*rpid))) == NULL)
+ err(1, "recallocarray");
+ if ((rpid[n] = strdup(cp)) == NULL)
+ err(1, "strdup");
+ if (*rpid[n] == '\0')
+ errx(1, "empty rpid");
+ }
+ free(otmp);
+ if (rpid == NULL || n == 0)
+ errx(1, "could not parse rp_id");
+ dev = open_dev(path);
+ if ((r = fido_dev_set_pin_minlen_rpid(dev, (const char * const *)rpid,
+ n, NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_set_pin_minlen_rpid(dev, (const char * const *)rpid,
+ n, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_set_pin_minlen_rpid: %s (0x%x)",
+ fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
diff --git a/tools/cred_make.c b/tools/cred_make.c
new file mode 100644
index 0000000..a6239ec
--- /dev/null
+++ b/tools/cred_make.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2018 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 <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static fido_cred_t *
+prepare_cred(FILE *in_f, int type, int flags)
+{
+ fido_cred_t *cred = NULL;
+ struct blob cdh;
+ struct blob uid;
+ char *rpid = NULL;
+ char *uname = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&uid, 0, sizeof(uid));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ r |= string_read(in_f, &uname);
+ r |= base64_read(in_f, &uid);
+ if (r < 0)
+ errx(1, "input error");
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data hash:\n");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ fprintf(stderr, "user name: %s\n", uname);
+ fprintf(stderr, "user id:\n");
+ xxd(uid.ptr, uid.len);
+ }
+
+ if ((cred = fido_cred_new()) == NULL)
+ errx(1, "fido_cred_new");
+
+ if ((r = fido_cred_set_type(cred, type)) != FIDO_OK ||
+ (r = fido_cred_set_clientdata_hash(cred, cdh.ptr,
+ cdh.len)) != FIDO_OK ||
+ (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK ||
+ (r = fido_cred_set_user(cred, uid.ptr, uid.len, uname, NULL,
+ NULL)) != FIDO_OK)
+ errx(1, "fido_cred_set: %s", fido_strerr(r));
+
+ if (flags & FLAG_RK) {
+ if ((r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_rk: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_UV) {
+ if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_uv: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_cred_set_extensions(cred,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_LARGEBLOB) {
+ if ((r = fido_cred_set_extensions(cred,
+ FIDO_EXT_LARGEBLOB_KEY)) != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s", fido_strerr(r));
+ }
+
+ free(cdh.ptr);
+ free(uid.ptr);
+ free(rpid);
+ free(uname);
+
+ return (cred);
+}
+
+static void
+print_attcred(FILE *out_f, const fido_cred_t *cred)
+{
+ char *cdh = NULL;
+ char *authdata = NULL;
+ char *id = NULL;
+ char *sig = NULL;
+ char *x5c = NULL;
+ char *key = NULL;
+ int r;
+
+ r = base64_encode(fido_cred_clientdata_hash_ptr(cred),
+ fido_cred_clientdata_hash_len(cred), &cdh);
+ r |= base64_encode(fido_cred_authdata_ptr(cred),
+ fido_cred_authdata_len(cred), &authdata);
+ r |= base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred),
+ &id);
+ r |= base64_encode(fido_cred_sig_ptr(cred), fido_cred_sig_len(cred),
+ &sig);
+ if (fido_cred_x5c_ptr(cred) != NULL)
+ r |= base64_encode(fido_cred_x5c_ptr(cred),
+ fido_cred_x5c_len(cred), &x5c);
+ if (fido_cred_largeblob_key_ptr(cred) != NULL)
+ r |= base64_encode(fido_cred_largeblob_key_ptr(cred),
+ fido_cred_largeblob_key_len(cred), &key);
+ if (r < 0)
+ errx(1, "output error");
+
+ fprintf(out_f, "%s\n", cdh);
+ fprintf(out_f, "%s\n", fido_cred_rp_id(cred));
+ fprintf(out_f, "%s\n", fido_cred_fmt(cred));
+ fprintf(out_f, "%s\n", authdata);
+ fprintf(out_f, "%s\n", id);
+ fprintf(out_f, "%s\n", sig);
+ if (x5c != NULL)
+ fprintf(out_f, "%s\n", x5c);
+ if (key != NULL) {
+ fprintf(out_f, "%s\n", key);
+ explicit_bzero(key, strlen(key));
+ }
+
+ free(cdh);
+ free(authdata);
+ free(id);
+ free(sig);
+ free(x5c);
+ free(key);
+}
+
+int
+cred_make(int argc, char **argv)
+{
+ fido_dev_t *dev = NULL;
+ fido_cred_t *cred = NULL;
+ char prompt[1024];
+ char pin[128];
+ char *in_path = NULL;
+ char *out_path = NULL;
+ FILE *in_f = NULL;
+ FILE *out_f = NULL;
+ int type = COSE_ES256;
+ int flags = 0;
+ int cred_protect = -1;
+ int ch;
+ int r;
+
+ while ((ch = getopt(argc, argv, "bc:dhi:o:qruv")) != -1) {
+ switch (ch) {
+ case 'b':
+ flags |= FLAG_LARGEBLOB;
+ break;
+ case 'c':
+ if ((cred_protect = base10(optarg)) < 0)
+ errx(1, "-c: invalid argument '%s'", optarg);
+ break;
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'o':
+ out_path = optarg;
+ break;
+ case 'q':
+ flags |= FLAG_QUIET;
+ break;
+ case 'r':
+ flags |= FLAG_RK;
+ break;
+ case 'u':
+ flags |= FLAG_U2F;
+ break;
+ case 'v':
+ flags |= FLAG_UV;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1 || argc > 2)
+ usage();
+
+ in_f = open_read(in_path);
+ out_f = open_write(out_path);
+
+ if (argc > 1 && cose_type(argv[1], &type) < 0)
+ errx(1, "unknown type %s", argv[1]);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+
+ cred = prepare_cred(in_f, type, flags);
+
+ dev = open_dev(argv[0]);
+ if (flags & FLAG_U2F)
+ fido_dev_force_u2f(dev);
+
+ if (cred_protect > 0) {
+ r = fido_cred_set_prot(cred, cred_protect);
+ if (r != FIDO_OK) {
+ errx(1, "fido_cred_set_prot: %s", fido_strerr(r));
+ }
+ }
+
+ r = fido_dev_make_cred(dev, cred, NULL);
+ if (r == FIDO_ERR_PIN_REQUIRED && !(flags & FLAG_QUIET)) {
+ r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ",
+ argv[0]);
+ if (r < 0 || (size_t)r >= sizeof(prompt))
+ errx(1, "snprintf");
+ if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF))
+ errx(1, "readpassphrase");
+ if (strlen(pin) < 4 || strlen(pin) > 63) {
+ explicit_bzero(pin, sizeof(pin));
+ errx(1, "invalid PIN length");
+ }
+ r = fido_dev_make_cred(dev, cred, pin);
+ }
+
+ explicit_bzero(pin, sizeof(pin));
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_make_cred: %s", fido_strerr(r));
+ print_attcred(out_f, cred);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_cred_free(&cred);
+
+ fclose(in_f);
+ fclose(out_f);
+ in_f = NULL;
+ out_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/cred_verify.c b/tools/cred_verify.c
new file mode 100644
index 0000000..3eae435
--- /dev/null
+++ b/tools/cred_verify.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2018 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 <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static fido_cred_t *
+prepare_cred(FILE *in_f, int type, int flags)
+{
+ fido_cred_t *cred = NULL;
+ struct blob cdh;
+ struct blob authdata;
+ struct blob id;
+ struct blob sig;
+ struct blob x5c;
+ char *rpid = NULL;
+ char *fmt = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&authdata, 0, sizeof(authdata));
+ memset(&id, 0, sizeof(id));
+ memset(&sig, 0, sizeof(sig));
+ memset(&x5c, 0, sizeof(x5c));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ r |= string_read(in_f, &fmt);
+ r |= base64_read(in_f, &authdata);
+ r |= base64_read(in_f, &id);
+ r |= base64_read(in_f, &sig);
+ if (r < 0)
+ errx(1, "input error");
+
+ (void)base64_read(in_f, &x5c);
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data hash:\n");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ fprintf(stderr, "format: %s\n", fmt);
+ fprintf(stderr, "authenticator data:\n");
+ xxd(authdata.ptr, authdata.len);
+ fprintf(stderr, "credential id:\n");
+ xxd(id.ptr, id.len);
+ fprintf(stderr, "signature:\n");
+ xxd(sig.ptr, sig.len);
+ fprintf(stderr, "x509:\n");
+ xxd(x5c.ptr, x5c.len);
+ }
+
+ if ((cred = fido_cred_new()) == NULL)
+ errx(1, "fido_cred_new");
+
+ if ((r = fido_cred_set_type(cred, type)) != FIDO_OK ||
+ (r = fido_cred_set_clientdata_hash(cred, cdh.ptr,
+ cdh.len)) != FIDO_OK ||
+ (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK ||
+ (r = fido_cred_set_authdata(cred, authdata.ptr,
+ authdata.len)) != FIDO_OK ||
+ (r = fido_cred_set_sig(cred, sig.ptr, sig.len)) != FIDO_OK ||
+ (r = fido_cred_set_fmt(cred, fmt)) != FIDO_OK)
+ errx(1, "fido_cred_set: %s", fido_strerr(r));
+
+ if (x5c.ptr != NULL) {
+ if ((r = fido_cred_set_x509(cred, x5c.ptr, x5c.len)) != FIDO_OK)
+ errx(1, "fido_cred_set_x509: %s", fido_strerr(r));
+ }
+
+ if (flags & FLAG_UV) {
+ if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_uv: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_cred_set_extensions(cred,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s", fido_strerr(r));
+ }
+
+ free(cdh.ptr);
+ free(authdata.ptr);
+ free(id.ptr);
+ free(sig.ptr);
+ free(x5c.ptr);
+ free(rpid);
+ free(fmt);
+
+ return (cred);
+}
+
+int
+cred_verify(int argc, char **argv)
+{
+ fido_cred_t *cred = NULL;
+ char *in_path = NULL;
+ char *out_path = NULL;
+ FILE *in_f = NULL;
+ FILE *out_f = NULL;
+ int type = COSE_ES256;
+ int flags = 0;
+ int cred_prot = -1;
+ int ch;
+ int r;
+
+ while ((ch = getopt(argc, argv, "c:dhi:o:v")) != -1) {
+ switch (ch) {
+ case 'c':
+ if ((cred_prot = base10(optarg)) < 0)
+ errx(1, "-c: invalid argument '%s'", optarg);
+ break;
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'o':
+ out_path = optarg;
+ break;
+ case 'v':
+ flags |= FLAG_UV;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1)
+ usage();
+
+ in_f = open_read(in_path);
+ out_f = open_write(out_path);
+
+ if (argc > 0 && cose_type(argv[0], &type) < 0)
+ errx(1, "unknown type %s", argv[0]);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+ cred = prepare_cred(in_f, type, flags);
+
+ if (cred_prot > 0) {
+ r = fido_cred_set_prot(cred, cred_prot);
+ if (r != FIDO_OK) {
+ errx(1, "fido_cred_set_prot: %s", fido_strerr(r));
+ }
+ }
+
+ if (fido_cred_x5c_ptr(cred) == NULL) {
+ if ((r = fido_cred_verify_self(cred)) != FIDO_OK)
+ errx(1, "fido_cred_verify_self: %s", fido_strerr(r));
+ } else {
+ if ((r = fido_cred_verify(cred)) != FIDO_OK)
+ errx(1, "fido_cred_verify: %s", fido_strerr(r));
+ }
+
+ print_cred(out_f, type, cred);
+ fido_cred_free(&cred);
+
+ fclose(in_f);
+ fclose(out_f);
+ in_f = NULL;
+ out_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/credman.c b/tools/credman.c
new file mode 100644
index 0000000..a0a3149
--- /dev/null
+++ b/tools/credman.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2019 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 <fido.h>
+#include <fido/credman.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+credman_get_metadata(fido_dev_t *dev, const char *path)
+{
+ fido_credman_metadata_t *metadata = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ if ((metadata = fido_credman_metadata_new()) == NULL) {
+ warnx("fido_credman_metadata_new");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_metadata(dev, metadata,
+ NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_metadata(dev, metadata, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_metadata: %s", fido_strerr(r));
+ goto out;
+ }
+
+ printf("existing rk(s): %u\n",
+ (unsigned)fido_credman_rk_existing(metadata));
+ printf("remaining rk(s): %u\n",
+ (unsigned)fido_credman_rk_remaining(metadata));
+
+ ok = 0;
+out:
+ fido_credman_metadata_free(&metadata);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static int
+print_rp(fido_credman_rp_t *rp, size_t idx)
+{
+ char *rp_id_hash = NULL;
+
+ if (base64_encode(fido_credman_rp_id_hash_ptr(rp, idx),
+ fido_credman_rp_id_hash_len(rp, idx), &rp_id_hash) < 0) {
+ warnx("output error");
+ return -1;
+ }
+ printf("%02u: %s %s\n", (unsigned)idx, rp_id_hash,
+ fido_credman_rp_id(rp, idx));
+ free(rp_id_hash);
+
+ return 0;
+}
+
+int
+credman_list_rp(const char *path)
+{
+ fido_credman_rp_t *rp = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((rp = fido_credman_rp_new()) == NULL) {
+ warnx("fido_credman_rp_new");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rp(dev, rp, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rp(dev, rp, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_rp: %s", fido_strerr(r));
+ goto out;
+ }
+ for (size_t i = 0; i < fido_credman_rp_count(rp); i++)
+ if (print_rp(rp, i) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ fido_credman_rp_free(&rp);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static int
+print_rk(const fido_credman_rk_t *rk, size_t idx)
+{
+ const fido_cred_t *cred;
+ char *id = NULL;
+ char *user_id = NULL;
+ const char *type;
+ const char *prot;
+
+ if ((cred = fido_credman_rk(rk, idx)) == NULL) {
+ warnx("fido_credman_rk");
+ return -1;
+ }
+ if (base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred),
+ &id) < 0 || base64_encode(fido_cred_user_id_ptr(cred),
+ fido_cred_user_id_len(cred), &user_id) < 0) {
+ warnx("output error");
+ return -1;
+ }
+
+ type = cose_string(fido_cred_type(cred));
+ prot = prot_string(fido_cred_prot(cred));
+
+ printf("%02u: %s %s %s %s %s\n", (unsigned)idx, id,
+ fido_cred_display_name(cred), user_id, type, prot);
+
+ free(user_id);
+ free(id);
+
+ return 0;
+}
+
+int
+credman_list_rk(const char *path, const char *rp_id)
+{
+ fido_dev_t *dev = NULL;
+ fido_credman_rk_t *rk = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((rk = fido_credman_rk_new()) == NULL) {
+ warnx("fido_credman_rk_new");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rk(dev, rp_id, rk, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+ for (size_t i = 0; i < fido_credman_rk_count(rk); i++)
+ if (print_rk(rk, i) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ fido_credman_rk_free(&rk);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+credman_print_rk(fido_dev_t *dev, const char *path, const char *rp_id,
+ const char *cred_id)
+{
+ fido_credman_rk_t *rk = NULL;
+ const fido_cred_t *cred = NULL;
+ char *pin = NULL;
+ void *cred_id_ptr = NULL;
+ size_t cred_id_len = 0;
+ int r, ok = 1;
+
+ if ((rk = fido_credman_rk_new()) == NULL) {
+ warnx("fido_credman_rk_new");
+ goto out;
+ }
+ if (base64_decode(cred_id, &cred_id_ptr, &cred_id_len) < 0) {
+ warnx("base64_decode");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rk(dev, rp_id, rk, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+
+ for (size_t i = 0; i < fido_credman_rk_count(rk); i++) {
+ if ((cred = fido_credman_rk(rk, i)) == NULL ||
+ fido_cred_id_ptr(cred) == NULL) {
+ warnx("output error");
+ goto out;
+ }
+ if (cred_id_len != fido_cred_id_len(cred) ||
+ memcmp(cred_id_ptr, fido_cred_id_ptr(cred), cred_id_len))
+ continue;
+ print_cred(stdout, fido_cred_type(cred), cred);
+ ok = 0;
+ goto out;
+ }
+
+ warnx("credential not found");
+out:
+ free(cred_id_ptr);
+ fido_credman_rk_free(&rk);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+credman_delete_rk(const char *path, const char *id)
+{
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ void *id_ptr = NULL;
+ size_t id_len = 0;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if (base64_decode(id, &id_ptr, &id_len) < 0) {
+ warnx("base64_decode");
+ goto out;
+ }
+ if ((r = fido_credman_del_dev_rk(dev, id_ptr, id_len,
+ NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_del_dev_rk(dev, id_ptr, id_len, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_del_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(id_ptr);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+credman_update_rk(const char *path, const char *user_id, const char *cred_id,
+ const char *name, const char *display_name)
+{
+ fido_dev_t *dev = NULL;
+ fido_cred_t *cred = NULL;
+ char *pin = NULL;
+ void *user_id_ptr = NULL;
+ void *cred_id_ptr = NULL;
+ size_t user_id_len = 0;
+ size_t cred_id_len = 0;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if (base64_decode(user_id, &user_id_ptr, &user_id_len) < 0 ||
+ base64_decode(cred_id, &cred_id_ptr, &cred_id_len) < 0) {
+ warnx("base64_decode");
+ goto out;
+ }
+ if ((cred = fido_cred_new()) == NULL) {
+ warnx("fido_cred_new");
+ goto out;
+ }
+ if ((r = fido_cred_set_id(cred, cred_id_ptr, cred_id_len)) != FIDO_OK) {
+ warnx("fido_cred_set_id: %s", fido_strerr(r));
+ goto out;
+ }
+ if ((r = fido_cred_set_user(cred, user_id_ptr, user_id_len, name,
+ display_name, NULL)) != FIDO_OK) {
+ warnx("fido_cred_set_user: %s", fido_strerr(r));
+ goto out;
+ }
+ if ((r = fido_credman_set_dev_rk(dev, cred, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_set_dev_rk(dev, cred, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_set_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(user_id_ptr);
+ free(cred_id_ptr);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_cred_free(&cred);
+
+ exit(ok);
+}
diff --git a/tools/extern.h b/tools/extern.h
new file mode 100644
index 0000000..ed4b348
--- /dev/null
+++ b/tools/extern.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018 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
+ */
+
+#ifndef _EXTERN_H_
+#define _EXTERN_H_
+
+#include <sys/types.h>
+
+#include <openssl/ec.h>
+
+#include <fido.h>
+#include <stddef.h>
+#include <stdio.h>
+
+struct blob {
+ unsigned char *ptr;
+ size_t len;
+};
+
+#define TOKEN_OPT "CDGILPRSVabcdefi:k:l:m:n:p:ru"
+
+#define FLAG_DEBUG 0x01
+#define FLAG_QUIET 0x02
+#define FLAG_RK 0x04
+#define FLAG_UV 0x08
+#define FLAG_U2F 0x10
+#define FLAG_HMAC 0x20
+#define FLAG_UP 0x40
+#define FLAG_LARGEBLOB 0x80
+
+#define PINBUF_LEN 256
+
+EC_KEY *read_ec_pubkey(const char *);
+fido_dev_t *open_dev(const char *);
+FILE *open_read(const char *);
+FILE *open_write(const char *);
+char *get_pin(const char *);
+const char *plural(size_t);
+const char *cose_string(int);
+const char *prot_string(int);
+int assert_get(int, char **);
+int assert_verify(int, char **);
+int base64_decode(const char *, void **, size_t *);
+int base64_encode(const void *, size_t, char **);
+int base64_read(FILE *, struct blob *);
+int bio_delete(const char *, const char *);
+int bio_enroll(const char *);
+void bio_info(fido_dev_t *);
+int bio_list(const char *);
+int bio_set_name(const char *, const char *, const char *);
+int blob_clean(const char *);
+int blob_list(const char *);
+int blob_delete(const char *, const char *, const char *, const char *);
+int blob_get(const char *, const char *, const char *, const char *,
+ const char *);
+int blob_set(const char *, const char *, const char *, const char *,
+ const char *);
+int config_always_uv(char *, int);
+int config_entattest(char *);
+int config_force_pin_change(char *);
+int config_pin_minlen(char *, const char *);
+int config_pin_minlen_rpid(char *, const char *);
+int cose_type(const char *, int *);
+int cred_make(int, char **);
+int cred_verify(int, char **);
+int credman_delete_rk(const char *, const char *);
+int credman_update_rk(const char *, const char *, const char *, const char *,
+ const char *);
+int credman_get_metadata(fido_dev_t *, const char *);
+int credman_list_rk(const char *, const char *);
+int credman_list_rp(const char *);
+int credman_print_rk(fido_dev_t *, const char *, const char *, const char *);
+int get_devopt(fido_dev_t *, const char *, int *);
+int pin_change(char *);
+int pin_set(char *);
+int should_retry_with_pin(const fido_dev_t *, int);
+int string_read(FILE *, char **);
+int token_config(int, char **, char *);
+int token_delete(int, char **, char *);
+int token_get(int, char **, char *);
+int token_info(int, char **, char *);
+int token_list(int, char **, char *);
+int token_reset(char *);
+int token_set(int, char **, char *);
+int write_es256_pubkey(FILE *, const void *, size_t);
+int write_es384_pubkey(FILE *, const void *, size_t);
+int write_rsa_pubkey(FILE *, const void *, size_t);
+int read_file(const char *, u_char **, size_t *);
+int write_file(const char *, const u_char *, size_t);
+RSA *read_rsa_pubkey(const char *);
+EVP_PKEY *read_eddsa_pubkey(const char *);
+int write_eddsa_pubkey(FILE *, const void *, size_t);
+void print_cred(FILE *, int, const fido_cred_t *);
+void usage(void);
+void xxd(const void *, size_t);
+int base10(const char *);
+
+#endif /* _EXTERN_H_ */
diff --git a/tools/fido2-assert.c b/tools/fido2-assert.c
new file mode 100644
index 0000000..d05c541
--- /dev/null
+++ b/tools/fido2-assert.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018 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
+ */
+
+/*
+ * Example usage:
+ *
+ * $ echo assertion challenge | openssl sha256 -binary | base64 > assert_param
+ * $ echo relying party >> assert_param
+ * $ head -1 cred >> assert_param # credential id
+ * $ tail -n +2 cred > pubkey # credential pubkey
+ * $ fido2-assert -G -i assert_param /dev/hidraw5 | fido2-assert -V pubkey rs256
+ *
+ * See blurb in fido2-cred.c on how to obtain cred.
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"usage: fido2-assert -G [-bdhpruv] [-t option] [-i input_file] [-o output_file] device\n"
+" fido2-assert -V [-dhpv] [-i input_file] key_file [type]\n"
+ );
+
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-')
+ usage();
+
+ switch (argv[1][1]) {
+ case 'G':
+ return (assert_get(--argc, ++argv));
+ case 'V':
+ return (assert_verify(--argc, ++argv));
+ }
+
+ usage();
+
+ /* NOTREACHED */
+}
diff --git a/tools/fido2-attach.sh b/tools/fido2-attach.sh
new file mode 100755
index 0000000..ef02db6
--- /dev/null
+++ b/tools/fido2-attach.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Copyright (c) 2020 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
+
+DEV=""
+
+while [ -z "${DEV}" ]; do
+ sleep .5
+ DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')"
+done
+
+printf '%s\n' "${DEV}"
diff --git a/tools/fido2-cred.c b/tools/fido2-cred.c
new file mode 100644
index 0000000..965dbf9
--- /dev/null
+++ b/tools/fido2-cred.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018 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
+ */
+
+/*
+ * Example usage:
+ *
+ * $ echo credential challenge | openssl sha256 -binary | base64 > cred_param
+ * $ echo relying party >> cred_param
+ * $ echo user name >> cred_param
+ * $ dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param
+ * $ fido2-cred -M -i cred_param /dev/hidraw5 | fido2-cred -V -o cred
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"usage: fido2-cred -M [-bdhqruv] [-c cred_protect] [-i input_file] [-o output_file] device [type]\n"
+" fido2-cred -V [-dhv] [-c cred_protect] [-i input_file] [-o output_file] [type]\n"
+ );
+
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-')
+ usage();
+
+ switch (argv[1][1]) {
+ case 'M':
+ return (cred_make(--argc, ++argv));
+ case 'V':
+ return (cred_verify(--argc, ++argv));
+ }
+
+ usage();
+
+ /* NOTREACHED */
+}
diff --git a/tools/fido2-detach.sh b/tools/fido2-detach.sh
new file mode 100755
index 0000000..140278f
--- /dev/null
+++ b/tools/fido2-detach.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# Copyright (c) 2020 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
+
+DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')"
+
+while [ -n "${DEV}" ]; do
+ sleep .5
+ DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')"
+done
diff --git a/tools/fido2-token.c b/tools/fido2-token.c
new file mode 100644
index 0000000..412c2f9
--- /dev/null
+++ b/tools/fido2-token.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018 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 <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static int action;
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"usage: fido2-token -C [-d] device\n"
+" fido2-token -Db [-k key_path] [-i cred_id -n rp_id] device\n"
+" fido2-token -Dei template_id device\n"
+" fido2-token -Du device\n"
+" fido2-token -Gb [-k key_path] [-i cred_id -n rp_id] blob_path device\n"
+" fido2-token -I [-cd] [-k rp_id -i cred_id] device\n"
+" fido2-token -L [-bder] [-k rp_id] [device]\n"
+" fido2-token -R [-d] device\n"
+" fido2-token -S [-adefu] [-l pin_length] [-i template_id -n template_name] device\n"
+" fido2-token -Sb [-k key_path] [-i cred_id -n rp_id] blob_path device\n"
+" fido2-token -Sc -i cred_id -k user_id -n name -p display_name device\n"
+" fido2-token -Sm rp_id device\n"
+" fido2-token -V\n"
+ );
+
+ exit(1);
+}
+
+static void
+setaction(int ch)
+{
+ if (action)
+ usage();
+ action = ch;
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ int flags = 0;
+ char *device;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'e':
+ case 'f':
+ case 'i':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'p':
+ case 'r':
+ case 'u':
+ break; /* ignore */
+ case 'd':
+ flags = FIDO_DEBUG;
+ break;
+ default:
+ setaction(ch);
+ break;
+ }
+ }
+
+ if (argc - optind < 1)
+ device = NULL;
+ else
+ device = argv[argc - 1];
+
+ fido_init(flags);
+
+ switch (action) {
+ case 'C':
+ return (pin_change(device));
+ case 'D':
+ return (token_delete(argc, argv, device));
+ case 'G':
+ return (token_get(argc, argv, device));
+ case 'I':
+ return (token_info(argc, argv, device));
+ case 'L':
+ return (token_list(argc, argv, device));
+ case 'R':
+ return (token_reset(device));
+ case 'S':
+ return (token_set(argc, argv, device));
+ case 'V':
+ fprintf(stderr, "%d.%d.%d\n", _FIDO_MAJOR, _FIDO_MINOR,
+ _FIDO_PATCH);
+ exit(0);
+ }
+
+ usage();
+
+ /* NOTREACHED */
+}
diff --git a/tools/fido2-unprot.sh b/tools/fido2-unprot.sh
new file mode 100755
index 0000000..7d8c779
--- /dev/null
+++ b/tools/fido2-unprot.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+# Copyright (c) 2020 Fabian Henneke.
+# 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
+
+
+if [ $(uname) != "Linux" ] ; then
+ echo "Can only run on Linux"
+ exit 1
+fi
+
+TOKEN_VERSION=$(${FIDO_TOOLS_PREFIX}fido2-token -V 2>&1)
+if [ $? -ne 0 ] ; then
+ echo "Please install libfido2 1.5.0 or higher"
+ exit
+fi
+
+TOKEN_VERSION_MAJOR=$(echo "$TOKEN_VERSION" | cut -d. -f1)
+TOKEN_VERSION_MINOR=$(echo "$TOKEN_VERSION" | cut -d. -f2)
+if [ $TOKEN_VERSION_MAJOR -eq 0 -o $TOKEN_VERSION_MAJOR -eq 1 -a $TOKEN_VERSION_MINOR -lt 5 ] ; then
+ echo "Please install libfido2 1.5.0 or higher (current version: $TOKEN_VERSION)"
+ exit 1
+fi
+
+set -e
+
+TOKEN_OUTPUT=$(${FIDO_TOOLS_PREFIX}fido2-token -L)
+DEV_PATH_NAMES=$(echo "$TOKEN_OUTPUT" | sed -r 's/^(.*): .*\((.*)\)$/\1 \2/g')
+DEV_COUNT=$(echo "$DEV_PATH_NAMES" | wc -l)
+
+for i in $(seq 1 $DEV_COUNT)
+do
+ DEV_PATH_NAME=$(echo "$DEV_PATH_NAMES" | sed "${i}q;d")
+ DEV_PATH=$(echo "$DEV_PATH_NAME" | cut -d' ' -f1)
+ DEV_NAME=$(echo "$DEV_PATH_NAME" | cut -d' ' -f1 --complement)
+ DEV_PRETTY=$(echo "$DEV_NAME (at '$DEV_PATH')")
+ if expr match "$(${FIDO_TOOLS_PREFIX}fido2-token -I $DEV_PATH)" ".* credMgmt.* clientPin.*\|.* clientPin.* credMgmt.*" > /dev/null ; then
+ printf "Enter PIN for $DEV_PRETTY once (ignore further prompts): "
+ stty -echo
+ read PIN
+ stty echo
+ printf "\n"
+ RESIDENT_RPS=$(echo "${PIN}\n" | setsid -w ${FIDO_TOOLS_PREFIX}fido2-token -L -r $DEV_PATH | cut -d' ' -f3)
+ printf "\n"
+ RESIDENT_RPS_COUNT=$(echo "$RESIDENT_RPS" | wc -l)
+ FOUND=0
+ for j in $(seq 1 $DEV_RESIDENT_RPS_COUNT)
+ do
+ RESIDENT_RP=$(echo "$RESIDENT_RPS" | sed "${j}q;d")
+ UNPROT_CREDS=$(echo "${PIN}\n" | setsid -w ${FIDO_TOOLS_PREFIX}fido2-token -L -k $RESIDENT_RP $DEV_PATH | grep ' uvopt$' | cut -d' ' -f2,3,4)
+ printf "\n"
+ UNPROT_CREDS_COUNT=$(echo "$UNPROT_CREDS" | wc -l)
+ if [ $UNPROT_CREDS_COUNT -gt 0 ] ; then
+ FOUND=1
+ echo "Unprotected credentials on $DEV_PRETTY for '$RESIDENT_RP':"
+ echo "$UNPROT_CREDS"
+ fi
+ done
+ if [ $FOUND -eq 0 ] ; then
+ echo "No unprotected credentials on $DEV_PRETTY"
+ fi
+ else
+ echo "$DEV_PRETTY cannot enumerate credentials"
+ echo "Discovering unprotected SSH credentials only..."
+ STUB_HASH=$(echo -n "" | openssl sha256 -binary | base64)
+ printf "$STUB_HASH\nssh:\n" | ${FIDO_TOOLS_PREFIX}fido2-assert -G -r -t up=false $DEV_PATH 2> /dev/null || ASSERT_EXIT_CODE=$?
+ if [ $ASSERT_EXIT_CODE -eq 0 ] ; then
+ echo "Found an unprotected SSH credential on $DEV_PRETTY!"
+ else
+ echo "No unprotected SSH credentials (default settings) on $DEV_PRETTY"
+ fi
+ fi
+ printf "\n"
+done
diff --git a/tools/include_check.sh b/tools/include_check.sh
new file mode 100755
index 0000000..70abada
--- /dev/null
+++ b/tools/include_check.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# Copyright (c) 2019 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
+
+check() {
+ for f in $(find $1 -maxdepth 1 -name '*.h'); do
+ echo "#include \"$f\"" | \
+ cc $CFLAGS -Isrc -xc -c - -o /dev/null 2>&1
+ echo "$f $CFLAGS $?"
+ done
+}
+
+check examples
+check fuzz
+check openbsd-compat
+CFLAGS="${CFLAGS} -D_FIDO_INTERNAL" check src
+check src/fido.h
+check src/fido
+check tools
diff --git a/tools/largeblob.c b/tools/largeblob.c
new file mode 100644
index 0000000..00cf79e
--- /dev/null
+++ b/tools/largeblob.c
@@ -0,0 +1,594 @@
+/*
+ * Copyright (c) 2020 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 <fido.h>
+#include <fido/credman.h>
+
+#include <cbor.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <zlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+struct rkmap {
+ fido_credman_rp_t *rp; /* known rps */
+ fido_credman_rk_t **rk; /* rk per rp */
+};
+
+static void
+free_rkmap(struct rkmap *map)
+{
+ if (map->rp != NULL) {
+ for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++)
+ fido_credman_rk_free(&map->rk[i]);
+ fido_credman_rp_free(&map->rp);
+ }
+ free(map->rk);
+}
+
+static int
+map_known_rps(fido_dev_t *dev, const char *path, struct rkmap *map)
+{
+ const char *rp_id;
+ char *pin = NULL;
+ size_t n;
+ int r, ok = -1;
+
+ if ((map->rp = fido_credman_rp_new()) == NULL) {
+ warnx("%s: fido_credman_rp_new", __func__);
+ goto out;
+ }
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ if ((r = fido_credman_get_dev_rp(dev, map->rp, pin)) != FIDO_OK) {
+ warnx("fido_credman_get_dev_rp: %s", fido_strerr(r));
+ goto out;
+ }
+ if ((n = fido_credman_rp_count(map->rp)) > UINT8_MAX) {
+ warnx("%s: fido_credman_rp_count > UINT8_MAX", __func__);
+ goto out;
+ }
+ if ((map->rk = calloc(n, sizeof(*map->rk))) == NULL) {
+ warnx("%s: calloc", __func__);
+ goto out;
+ }
+ for (size_t i = 0; i < n; i++) {
+ if ((rp_id = fido_credman_rp_id(map->rp, i)) == NULL) {
+ warnx("%s: fido_credman_rp_id %zu", __func__, i);
+ goto out;
+ }
+ if ((map->rk[i] = fido_credman_rk_new()) == NULL) {
+ warnx("%s: fido_credman_rk_new", __func__);
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, map->rk[i],
+ pin)) != FIDO_OK) {
+ warnx("%s: fido_credman_get_dev_rk %s: %s", __func__,
+ rp_id, fido_strerr(r));
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ freezero(pin, PINBUF_LEN);
+
+ return ok;
+}
+
+static int
+lookup_key(const char *path, fido_dev_t *dev, const char *rp_id,
+ const struct blob *cred_id, char **pin, struct blob *key)
+{
+ fido_credman_rk_t *rk = NULL;
+ const fido_cred_t *cred = NULL;
+ size_t i, n;
+ int r, ok = -1;
+
+ if ((rk = fido_credman_rk_new()) == NULL) {
+ warnx("%s: fido_credman_rk_new", __func__);
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin)) != FIDO_OK &&
+ *pin == NULL && should_retry_with_pin(dev, r)) {
+ if ((*pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin);
+ }
+ if (r != FIDO_OK) {
+ warnx("%s: fido_credman_get_dev_rk: %s", __func__,
+ fido_strerr(r));
+ goto out;
+ }
+ if ((n = fido_credman_rk_count(rk)) == 0) {
+ warnx("%s: rp id not found", __func__);
+ goto out;
+ }
+ if (n == 1 && cred_id->len == 0) {
+ /* use the credential we found */
+ cred = fido_credman_rk(rk, 0);
+ } else {
+ if (cred_id->len == 0) {
+ warnx("%s: multiple credentials found", __func__);
+ goto out;
+ }
+ for (i = 0; i < n; i++) {
+ const fido_cred_t *x = fido_credman_rk(rk, i);
+ if (fido_cred_id_len(x) <= cred_id->len &&
+ !memcmp(fido_cred_id_ptr(x), cred_id->ptr,
+ fido_cred_id_len(x))) {
+ cred = x;
+ break;
+ }
+ }
+ }
+ if (cred == NULL) {
+ warnx("%s: credential not found", __func__);
+ goto out;
+ }
+ if (fido_cred_largeblob_key_ptr(cred) == NULL) {
+ warnx("%s: no associated blob key", __func__);
+ goto out;
+ }
+ key->len = fido_cred_largeblob_key_len(cred);
+ if ((key->ptr = malloc(key->len)) == NULL) {
+ warnx("%s: malloc", __func__);
+ goto out;
+ }
+ memcpy(key->ptr, fido_cred_largeblob_key_ptr(cred), key->len);
+
+ ok = 0;
+out:
+ fido_credman_rk_free(&rk);
+
+ return ok;
+}
+
+static int
+load_key(const char *keyf, const char *cred_id64, const char *rp_id,
+ const char *path, fido_dev_t *dev, char **pin, struct blob *key)
+{
+ struct blob cred_id;
+ FILE *fp;
+ int r;
+
+ memset(&cred_id, 0, sizeof(cred_id));
+
+ if (keyf != NULL) {
+ if (rp_id != NULL || cred_id64 != NULL)
+ usage();
+ fp = open_read(keyf);
+ if ((r = base64_read(fp, key)) < 0)
+ warnx("%s: base64_read %s", __func__, keyf);
+ fclose(fp);
+ return r;
+ }
+ if (rp_id == NULL)
+ usage();
+ if (cred_id64 != NULL && base64_decode(cred_id64, (void *)&cred_id.ptr,
+ &cred_id.len) < 0) {
+ warnx("%s: base64_decode %s", __func__, cred_id64);
+ return -1;
+ }
+ r = lookup_key(path, dev, rp_id, &cred_id, pin, key);
+ free(cred_id.ptr);
+
+ return r;
+}
+
+int
+blob_set(const char *path, const char *keyf, const char *rp_id,
+ const char *cred_id64, const char *blobf)
+{
+ fido_dev_t *dev;
+ struct blob key, blob;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ memset(&key, 0, sizeof(key));
+ memset(&blob, 0, sizeof(blob));
+
+ if (read_file(blobf, &blob.ptr, &blob.len) < 0 ||
+ load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
+ goto out;
+ if ((r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
+ blob.len, pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
+ blob.len, pin);
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_largeblob_set: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0; /* success */
+out:
+ freezero(key.ptr, key.len);
+ freezero(blob.ptr, blob.len);
+ freezero(pin, PINBUF_LEN);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+blob_get(const char *path, const char *keyf, const char *rp_id,
+ const char *cred_id64, const char *blobf)
+{
+ fido_dev_t *dev;
+ struct blob key, blob;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ memset(&key, 0, sizeof(key));
+ memset(&blob, 0, sizeof(blob));
+
+ if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
+ goto out;
+ if ((r = fido_dev_largeblob_get(dev, key.ptr, key.len, &blob.ptr,
+ &blob.len)) != FIDO_OK) {
+ warnx("fido_dev_largeblob_get: %s", fido_strerr(r));
+ goto out;
+ }
+ if (write_file(blobf, blob.ptr, blob.len) < 0)
+ goto out;
+
+ ok = 0; /* success */
+out:
+ freezero(key.ptr, key.len);
+ freezero(blob.ptr, blob.len);
+ freezero(pin, PINBUF_LEN);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+blob_delete(const char *path, const char *keyf, const char *rp_id,
+ const char *cred_id64)
+{
+ fido_dev_t *dev;
+ struct blob key;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ memset(&key, 0, sizeof(key));
+
+ if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
+ goto out;
+ if ((r = fido_dev_largeblob_remove(dev, key.ptr, key.len,
+ pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_largeblob_remove(dev, key.ptr, key.len, pin);
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_largeblob_remove: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0; /* success */
+out:
+ freezero(key.ptr, key.len);
+ freezero(pin, PINBUF_LEN);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static int
+decompress(const struct blob *plaintext, uint64_t origsiz)
+{
+ struct blob inflated;
+ u_long ilen, plen;
+ int ok = -1;
+
+ memset(&inflated, 0, sizeof(inflated));
+
+ if (plaintext->len > ULONG_MAX)
+ return -1;
+ if (origsiz > ULONG_MAX || origsiz > SIZE_MAX)
+ return -1;
+ plen = (u_long)plaintext->len;
+ ilen = (u_long)origsiz;
+ inflated.len = (size_t)origsiz;
+ if ((inflated.ptr = calloc(1, inflated.len)) == NULL)
+ return -1;
+ if (uncompress(inflated.ptr, &ilen, plaintext->ptr, plen) != Z_OK ||
+ ilen > SIZE_MAX || (size_t)ilen != (size_t)origsiz)
+ goto out;
+
+ ok = 0; /* success */
+out:
+ freezero(inflated.ptr, inflated.len);
+
+ return ok;
+}
+
+static int
+decode(const struct blob *ciphertext, const struct blob *nonce,
+ uint64_t origsiz, const fido_cred_t *cred)
+{
+ uint8_t aad[4 + sizeof(uint64_t)];
+ EVP_CIPHER_CTX *ctx = NULL;
+ const EVP_CIPHER *cipher;
+ struct blob plaintext;
+ uint64_t tmp;
+ int ok = -1;
+
+ memset(&plaintext, 0, sizeof(plaintext));
+
+ if (nonce->len != 12)
+ return -1;
+ if (cred == NULL ||
+ fido_cred_largeblob_key_ptr(cred) == NULL ||
+ fido_cred_largeblob_key_len(cred) != 32)
+ return -1;
+ if (ciphertext->len > UINT_MAX ||
+ ciphertext->len > SIZE_MAX - 16 ||
+ ciphertext->len < 16)
+ return -1;
+ plaintext.len = ciphertext->len - 16;
+ if ((plaintext.ptr = calloc(1, plaintext.len)) == NULL)
+ return -1;
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
+ (cipher = EVP_aes_256_gcm()) == NULL ||
+ EVP_CipherInit(ctx, cipher, fido_cred_largeblob_key_ptr(cred),
+ nonce->ptr, 0) == 0)
+ goto out;
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
+ ciphertext->ptr + ciphertext->len - 16) == 0)
+ goto out;
+ aad[0] = 0x62; /* b */
+ aad[1] = 0x6c; /* l */
+ aad[2] = 0x6f; /* o */
+ aad[3] = 0x62; /* b */
+ tmp = htole64(origsiz);
+ memcpy(&aad[4], &tmp, sizeof(uint64_t));
+ if (EVP_Cipher(ctx, NULL, aad, (u_int)sizeof(aad)) < 0 ||
+ EVP_Cipher(ctx, plaintext.ptr, ciphertext->ptr,
+ (u_int)plaintext.len) < 0 ||
+ EVP_Cipher(ctx, NULL, NULL, 0) < 0)
+ goto out;
+ if (decompress(&plaintext, origsiz) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ freezero(plaintext.ptr, plaintext.len);
+
+ if (ctx != NULL)
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ok;
+}
+
+static const fido_cred_t *
+try_rp(const fido_credman_rk_t *rk, const struct blob *ciphertext,
+ const struct blob *nonce, uint64_t origsiz)
+{
+ const fido_cred_t *cred;
+
+ for (size_t i = 0; i < fido_credman_rk_count(rk); i++)
+ if ((cred = fido_credman_rk(rk, i)) != NULL &&
+ decode(ciphertext, nonce, origsiz, cred) == 0)
+ return cred;
+
+ return NULL;
+}
+
+static int
+decode_cbor_blob(struct blob *out, const cbor_item_t *item)
+{
+ if (out->ptr != NULL ||
+ cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false)
+ return -1;
+ out->len = cbor_bytestring_length(item);
+ if ((out->ptr = malloc(out->len)) == NULL)
+ return -1;
+ memcpy(out->ptr, cbor_bytestring_handle(item), out->len);
+
+ return 0;
+}
+
+static int
+decode_blob_entry(const cbor_item_t *item, struct blob *ciphertext,
+ struct blob *nonce, uint64_t *origsiz)
+{
+ struct cbor_pair *v;
+
+ if (item == NULL)
+ return -1;
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ (v = cbor_map_handle(item)) == NULL)
+ return -1;
+ if (cbor_map_size(item) > UINT8_MAX)
+ return -1;
+
+ for (size_t i = 0; i < cbor_map_size(item); i++) {
+ if (cbor_isa_uint(v[i].key) == false ||
+ cbor_int_get_width(v[i].key) != CBOR_INT_8)
+ continue; /* ignore */
+ switch (cbor_get_uint8(v[i].key)) {
+ case 1: /* ciphertext */
+ if (decode_cbor_blob(ciphertext, v[i].value) < 0)
+ return -1;
+ break;
+ case 2: /* nonce */
+ if (decode_cbor_blob(nonce, v[i].value) < 0)
+ return -1;
+ break;
+ case 3: /* origSize */
+ if (*origsiz != 0 ||
+ cbor_isa_uint(v[i].value) == false ||
+ (*origsiz = cbor_get_int(v[i].value)) > SIZE_MAX)
+ return -1;
+ }
+ }
+ if (ciphertext->ptr == NULL || nonce->ptr == NULL || *origsiz == 0)
+ return -1;
+
+ return 0;
+}
+
+static void
+print_blob_entry(size_t idx, const cbor_item_t *item, const struct rkmap *map)
+{
+ struct blob ciphertext, nonce;
+ const fido_cred_t *cred = NULL;
+ const char *rp_id = NULL;
+ char *cred_id = NULL;
+ uint64_t origsiz = 0;
+
+ memset(&ciphertext, 0, sizeof(ciphertext));
+ memset(&nonce, 0, sizeof(nonce));
+
+ if (decode_blob_entry(item, &ciphertext, &nonce, &origsiz) < 0) {
+ printf("%02zu: <skipped: bad cbor>\n", idx);
+ goto out;
+ }
+ for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) {
+ if ((cred = try_rp(map->rk[i], &ciphertext, &nonce,
+ origsiz)) != NULL) {
+ rp_id = fido_credman_rp_id(map->rp, i);
+ break;
+ }
+ }
+ if (cred == NULL) {
+ if ((cred_id = strdup("<unknown>")) == NULL) {
+ printf("%02zu: <skipped: strdup failed>\n", idx);
+ goto out;
+ }
+ } else {
+ if (base64_encode(fido_cred_id_ptr(cred),
+ fido_cred_id_len(cred), &cred_id) < 0) {
+ printf("%02zu: <skipped: base64_encode failed>\n", idx);
+ goto out;
+ }
+ }
+ if (rp_id == NULL)
+ rp_id = "<unknown>";
+
+ printf("%02zu: %4zu %4zu %s %s\n", idx, ciphertext.len,
+ (size_t)origsiz, cred_id, rp_id);
+out:
+ free(ciphertext.ptr);
+ free(nonce.ptr);
+ free(cred_id);
+}
+
+static cbor_item_t *
+get_cbor_array(fido_dev_t *dev)
+{
+ struct cbor_load_result cbor_result;
+ cbor_item_t *item = NULL;
+ u_char *cbor_ptr = NULL;
+ size_t cbor_len;
+ int r, ok = -1;
+
+ if ((r = fido_dev_largeblob_get_array(dev, &cbor_ptr,
+ &cbor_len)) != FIDO_OK) {
+ warnx("%s: fido_dev_largeblob_get_array: %s", __func__,
+ fido_strerr(r));
+ goto out;
+ }
+ if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) {
+ warnx("%s: cbor_load", __func__);
+ goto out;
+ }
+ if (cbor_result.read != cbor_len) {
+ warnx("%s: cbor_result.read (%zu) != cbor_len (%zu)", __func__,
+ cbor_result.read, cbor_len);
+ /* continue */
+ }
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ warnx("%s: cbor type", __func__);
+ goto out;
+ }
+ if (cbor_array_size(item) > UINT8_MAX) {
+ warnx("%s: cbor_array_size > UINT8_MAX", __func__);
+ goto out;
+ }
+ if (cbor_array_size(item) == 0) {
+ ok = 0; /* nothing to do */
+ goto out;
+ }
+
+ printf("total map size: %zu byte%s\n", cbor_len, plural(cbor_len));
+
+ ok = 0;
+out:
+ if (ok < 0 && item != NULL) {
+ cbor_decref(&item);
+ item = NULL;
+ }
+ free(cbor_ptr);
+
+ return item;
+}
+
+int
+blob_list(const char *path)
+{
+ struct rkmap map;
+ fido_dev_t *dev = NULL;
+ cbor_item_t *item = NULL, **v;
+ int ok = 1;
+
+ memset(&map, 0, sizeof(map));
+ dev = open_dev(path);
+ if (map_known_rps(dev, path, &map) < 0 ||
+ (item = get_cbor_array(dev)) == NULL)
+ goto out;
+ if (cbor_array_size(item) == 0) {
+ ok = 0; /* nothing to do */
+ goto out;
+ }
+ if ((v = cbor_array_handle(item)) == NULL) {
+ warnx("%s: cbor_array_handle", __func__);
+ goto out;
+ }
+ for (size_t i = 0; i < cbor_array_size(item); i++)
+ print_blob_entry(i, v[i], &map);
+
+ ok = 0; /* success */
+out:
+ free_rkmap(&map);
+
+ if (item != NULL)
+ cbor_decref(&item);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
diff --git a/tools/pin.c b/tools/pin.c
new file mode 100644
index 0000000..8b2697e
--- /dev/null
+++ b/tools/pin.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018 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 <fido.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+pin_set(char *path)
+{
+ fido_dev_t *dev = NULL;
+ char prompt[1024];
+ char pin1[128];
+ char pin2[128];
+ int r;
+ int status = 1;
+
+ dev = open_dev(path);
+
+ r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path);
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: ");
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ if (strcmp(pin1, pin2) != 0) {
+ fprintf(stderr, "PINs do not match. Try again.\n");
+ goto out;
+ }
+
+ if (strlen(pin1) < 4 || strlen(pin1) > 63) {
+ fprintf(stderr, "invalid PIN length\n");
+ goto out;
+ }
+
+ if ((r = fido_dev_set_pin(dev, pin1, NULL)) != FIDO_OK) {
+ warnx("fido_dev_set_pin: %s", fido_strerr(r));
+ goto out;
+ }
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ status = 0;
+out:
+ explicit_bzero(pin1, sizeof(pin1));
+ explicit_bzero(pin2, sizeof(pin2));
+
+ exit(status);
+}
+
+int
+pin_change(char *path)
+{
+ fido_dev_t *dev = NULL;
+ char prompt[1024];
+ char pin0[128];
+ char pin1[128];
+ char pin2[128];
+ int r;
+ int status = 1;
+
+ if (path == NULL)
+ usage();
+
+ dev = open_dev(path);
+
+ r = snprintf(prompt, sizeof(prompt), "Enter current PIN for %s: ", path);
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin0, sizeof(pin0), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ if (strlen(pin0) < 4 || strlen(pin0) > 63) {
+ warnx("invalid PIN length");
+ goto out;
+ }
+
+ r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path);
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: ");
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ if (strcmp(pin1, pin2) != 0) {
+ fprintf(stderr, "PINs do not match. Try again.\n");
+ goto out;
+ }
+
+ if (strlen(pin1) < 4 || strlen(pin1) > 63) {
+ fprintf(stderr, "invalid PIN length\n");
+ goto out;
+ }
+
+ if ((r = fido_dev_set_pin(dev, pin1, pin0)) != FIDO_OK) {
+ warnx("fido_dev_set_pin: %s", fido_strerr(r));
+ goto out;
+ }
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ status = 0;
+out:
+ explicit_bzero(pin0, sizeof(pin0));
+ explicit_bzero(pin1, sizeof(pin1));
+ explicit_bzero(pin2, sizeof(pin2));
+
+ exit(status);
+}
diff --git a/tools/test.sh b/tools/test.sh
new file mode 100755
index 0000000..67b757e
--- /dev/null
+++ b/tools/test.sh
@@ -0,0 +1,304 @@
+#!/bin/sh -ex
+
+# Copyright (c) 2021-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
+
+# usage: ./test.sh "$(mktemp -d fido2test-XXXXXXXX)" device
+
+# Please note that this test script:
+# - is incomplete;
+# - assumes CTAP 2.1-like hmac-secret;
+# - should pass as-is on a YubiKey with a PIN set;
+# - may otherwise require set +e above;
+# - can be executed with UV=1 to run additional UV tests;
+# - was last tested on 2022-01-11 with firmware 5.4.3.
+
+cd "$1"
+DEV="$2"
+TYPE="es256"
+#TYPE="es384"
+#TYPE="eddsa"
+
+make_cred() {
+ sed /^$/d > cred_param << EOF
+$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)
+$1
+some user name
+$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)
+EOF
+ fido2-cred -M $2 "${DEV}" "${TYPE}" > "$3" < cred_param
+}
+
+verify_cred() {
+ fido2-cred -V $1 "${TYPE}" > cred_out < "$2"
+ head -1 cred_out > "$3"
+ tail -n +2 cred_out > "$4"
+}
+
+get_assert() {
+ sed /^$/d > assert_param << EOF
+$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)
+$1
+$(cat $3)
+$(cat $4)
+EOF
+ fido2-assert -G $2 "${DEV}" > "$5" < assert_param
+}
+
+verify_assert() {
+ fido2-assert -V $1 "$2" "${TYPE}" < "$3"
+}
+
+dd if=/dev/urandom bs=32 count=1 | base64 > hmac-salt
+
+# u2f
+if [ "x${TYPE}" = "xes256" ]; then
+ make_cred no.tld "-u" u2f
+ ! make_cred no.tld "-ru" /dev/null
+ ! make_cred no.tld "-uc1" /dev/null
+ ! make_cred no.tld "-uc2" /dev/null
+ verify_cred "--" u2f u2f-cred u2f-pubkey
+ ! verify_cred "-h" u2f /dev/null /dev/null
+ ! verify_cred "-v" u2f /dev/null /dev/null
+ verify_cred "-c0" u2f /dev/null /dev/null
+ ! verify_cred "-c1" u2f /dev/null /dev/null
+ ! verify_cred "-c2" u2f /dev/null /dev/null
+ ! verify_cred "-c3" u2f /dev/null /dev/null
+fi
+
+# wrap (non-resident)
+make_cred no.tld "--" wrap
+verify_cred "--" wrap wrap-cred wrap-pubkey
+! verify_cred "-h" wrap /dev/null /dev/null
+! verify_cred "-v" wrap /dev/null /dev/null
+verify_cred "-c0" wrap /dev/null /dev/null
+! verify_cred "-c1" wrap /dev/null /dev/null
+! verify_cred "-c2" wrap /dev/null /dev/null
+! verify_cred "-c3" wrap /dev/null /dev/null
+
+# wrap (non-resident) + hmac-secret
+make_cred no.tld "-h" wrap-hs
+! verify_cred "--" wrap-hs /dev/null /dev/null
+verify_cred "-h" wrap-hs wrap-hs-cred wrap-hs-pubkey
+! verify_cred "-v" wrap-hs /dev/null /dev/null
+verify_cred "-hc0" wrap-hs /dev/null /dev/null
+! verify_cred "-c0" wrap-hs /dev/null /dev/null
+! verify_cred "-c1" wrap-hs /dev/null /dev/null
+! verify_cred "-c2" wrap-hs /dev/null /dev/null
+! verify_cred "-c3" wrap-hs /dev/null /dev/null
+
+# resident
+make_cred no.tld "-r" rk
+verify_cred "--" rk rk-cred rk-pubkey
+! verify_cred "-h" rk /dev/null /dev/null
+! verify_cred "-v" rk /dev/null /dev/null
+verify_cred "-c0" rk /dev/null /dev/null
+! verify_cred "-c1" rk /dev/null /dev/null
+! verify_cred "-c2" rk /dev/null /dev/null
+! verify_cred "-c3" rk /dev/null /dev/null
+
+# resident + hmac-secret
+make_cred no.tld "-hr" rk-hs
+! verify_cred "--" rk-hs rk-hs-cred rk-hs-pubkey
+verify_cred "-h" rk-hs /dev/null /dev/null
+! verify_cred "-v" rk-hs /dev/null /dev/null
+verify_cred "-hc0" rk-hs /dev/null /dev/null
+! verify_cred "-c0" rk-hs /dev/null /dev/null
+! verify_cred "-c1" rk-hs /dev/null /dev/null
+! verify_cred "-c2" rk-hs /dev/null /dev/null
+! verify_cred "-c3" rk-hs /dev/null /dev/null
+
+# u2f
+if [ "x${TYPE}" = "xes256" ]; then
+ get_assert no.tld "-u" u2f-cred /dev/null u2f-assert
+ ! get_assert no.tld "-u -t up=false" u2f-cred /dev/null /dev/null
+ verify_assert "--" u2f-pubkey u2f-assert
+ verify_assert "-p" u2f-pubkey u2f-assert
+fi
+
+# wrap (non-resident)
+get_assert no.tld "--" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+get_assert no.tld "-t pin=true" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-v" wrap-pubkey wrap-assert
+get_assert no.tld "-t pin=false" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=true" wrap-cred /dev/null wrap-assert
+verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=true -t pin=true" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-p" wrap-pubkey wrap-assert
+verify_assert "-v" wrap-pubkey wrap-assert
+verify_assert "-pv" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=true -t pin=false" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=false" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+! verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=false -t pin=true" wrap-cred /dev/null wrap-assert
+! verify_assert "-p" wrap-pubkey wrap-assert
+verify_assert "-v" wrap-pubkey wrap-assert
+! verify_assert "-pv" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=false -t pin=false" wrap-cred /dev/null wrap-assert
+! verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-h" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t pin=true" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hv" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t pin=false" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t up=true" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hp" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t up=true -t pin=true" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hp" wrap-pubkey wrap-assert
+verify_assert "-hv" wrap-pubkey wrap-assert
+verify_assert "-hpv" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t up=true -t pin=false" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hp" wrap-pubkey wrap-assert
+! get_assert no.tld "-h -t up=false" wrap-cred hmac-salt wrap-assert
+! get_assert no.tld "-h -t up=false -t pin=true" wrap-cred hmac-salt wrap-assert
+! get_assert no.tld "-h -t up=false -t pin=false" wrap-cred hmac-salt wrap-assert
+
+if [ "x${UV}" != "x" ]; then
+ get_assert no.tld "-t uv=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=true -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=true -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=false" wrap-cred /dev/null wrap-assert
+ verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=false -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=false -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=true -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=true -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-p" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=false -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=false -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-p" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=true -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=true -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=false" wrap-cred /dev/null wrap-assert
+ ! verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=false -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=false -t pin=false" wrap-cred /dev/null wrap-assert
+ ! verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-h" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-h" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hp" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hp" wrap-pubkey wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=true" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=false" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert
+fi
+
+# resident
+get_assert no.tld "-r" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t pin=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t pin=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=true -t pin=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=true -t pin=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=false -t pin=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=false -t pin=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -h" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t pin=true" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t pin=false" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t up=true" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t up=true -t pin=true" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t up=true -t pin=false" /dev/null hmac-salt wrap-assert
+! get_assert no.tld "-r -h -t up=false" /dev/null hmac-salt wrap-assert
+! get_assert no.tld "-r -h -t up=false -t pin=true" /dev/null hmac-salt wrap-assert
+! get_assert no.tld "-r -h -t up=false -t pin=false" /dev/null hmac-salt wrap-assert
+
+if [ "x${UV}" != "x" ]; then
+ get_assert no.tld "-r -t uv=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=true -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=true -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=false -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=false -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=true -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=true -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=false -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=false -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=true -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=true -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=false -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=false -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -h -t uv=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=true" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=false" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert
+fi
+
+exit 0
diff --git a/tools/token.c b/tools/token.c
new file mode 100644
index 0000000..366d5a1
--- /dev/null
+++ b/tools/token.c
@@ -0,0 +1,729 @@
+/*
+ * 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 <fido.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static void
+format_flags(char *ret, size_t retlen, uint8_t flags)
+{
+ memset(ret, 0, retlen);
+
+ if (flags & FIDO_CAP_WINK) {
+ if (strlcat(ret, "wink,", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, "nowink,", retlen) >= retlen)
+ goto toolong;
+ }
+
+ if (flags & FIDO_CAP_CBOR) {
+ if (strlcat(ret, " cbor,", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, " nocbor,", retlen) >= retlen)
+ goto toolong;
+ }
+
+ if (flags & FIDO_CAP_NMSG) {
+ if (strlcat(ret, " nomsg", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, " msg", retlen) >= retlen)
+ goto toolong;
+ }
+
+ return;
+toolong:
+ strlcpy(ret, "toolong", retlen);
+}
+
+static void
+print_attr(const fido_dev_t *dev)
+{
+ char flags_txt[128];
+
+ printf("proto: 0x%02x\n", fido_dev_protocol(dev));
+ printf("major: 0x%02x\n", fido_dev_major(dev));
+ printf("minor: 0x%02x\n", fido_dev_minor(dev));
+ printf("build: 0x%02x\n", fido_dev_build(dev));
+
+ format_flags(flags_txt, sizeof(flags_txt), fido_dev_flags(dev));
+ printf("caps: 0x%02x (%s)\n", fido_dev_flags(dev), flags_txt);
+}
+
+static void
+print_str_array(const char *label, char * const *sa, size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s strings: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s", i > 0 ? ", " : "", sa[i]);
+
+ printf("\n");
+}
+
+static void
+print_opt_array(const char *label, char * const *name, const bool *value,
+ size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s%s", i > 0 ? ", " : "",
+ value[i] ? "" : "no", name[i]);
+
+ printf("\n");
+}
+
+static void
+print_cert_array(const char *label, char * const *name, const uint64_t *value,
+ size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s %llu", i > 0 ? ", " : "", name[i],
+ (unsigned long long)value[i]);
+
+ printf("\n");
+}
+
+static void
+print_algorithms(const fido_cbor_info_t *ci)
+{
+ const char *cose, *type;
+ size_t len;
+
+ if ((len = fido_cbor_info_algorithm_count(ci)) == 0)
+ return;
+
+ printf("algorithms: ");
+
+ for (size_t i = 0; i < len; i++) {
+ cose = type = "unknown";
+ switch (fido_cbor_info_algorithm_cose(ci, i)) {
+ case COSE_ES256:
+ cose = "es256";
+ break;
+ case COSE_ES384:
+ cose = "es384";
+ break;
+ case COSE_RS256:
+ cose = "rs256";
+ break;
+ case COSE_EDDSA:
+ cose = "eddsa";
+ break;
+ }
+ if (fido_cbor_info_algorithm_type(ci, i) != NULL)
+ type = fido_cbor_info_algorithm_type(ci, i);
+ printf("%s%s (%s)", i > 0 ? ", " : "", cose, type);
+ }
+
+ printf("\n");
+}
+
+static void
+print_aaguid(const unsigned char *buf, size_t buflen)
+{
+ printf("aaguid: ");
+
+ while (buflen--)
+ printf("%02x", *buf++);
+
+ printf("\n");
+}
+
+static void
+print_maxmsgsiz(uint64_t maxmsgsiz)
+{
+ printf("maxmsgsiz: %d\n", (int)maxmsgsiz);
+}
+
+static void
+print_maxcredcntlst(uint64_t maxcredcntlst)
+{
+ printf("maxcredcntlst: %d\n", (int)maxcredcntlst);
+}
+
+static void
+print_maxcredidlen(uint64_t maxcredidlen)
+{
+ printf("maxcredlen: %d\n", (int)maxcredidlen);
+}
+
+static void
+print_maxlargeblob(uint64_t maxlargeblob)
+{
+ printf("maxlargeblob: %d\n", (int)maxlargeblob);
+}
+
+static void
+print_maxrpid_minpinlen(uint64_t maxrpid)
+{
+ if (maxrpid > 0)
+ printf("maxrpids in minpinlen: %d\n", (int)maxrpid);
+}
+
+static void
+print_minpinlen(uint64_t minpinlen)
+{
+ if (minpinlen > 0)
+ printf("minpinlen: %d\n", (int)minpinlen);
+}
+
+static void
+print_uv_attempts(uint64_t uv_attempts)
+{
+ if (uv_attempts > 0)
+ printf("platform uv attempt(s): %d\n", (int)uv_attempts);
+}
+
+static void
+print_uv_modality(uint64_t uv_modality)
+{
+ uint64_t mode;
+ bool printed = false;
+
+ if (uv_modality == 0)
+ return;
+
+ printf("uv modality: 0x%x (", (int)uv_modality);
+
+ for (size_t i = 0; i < 64; i++) {
+ mode = 1ULL << i;
+ if ((uv_modality & mode) == 0)
+ continue;
+ if (printed)
+ printf(", ");
+ switch (mode) {
+ case FIDO_UV_MODE_TUP:
+ printf("test of user presence");
+ break;
+ case FIDO_UV_MODE_FP:
+ printf("fingerprint check");
+ break;
+ case FIDO_UV_MODE_PIN:
+ printf("pin check");
+ break;
+ case FIDO_UV_MODE_VOICE:
+ printf("voice recognition");
+ break;
+ case FIDO_UV_MODE_FACE:
+ printf("face recognition");
+ break;
+ case FIDO_UV_MODE_LOCATION:
+ printf("location check");
+ break;
+ case FIDO_UV_MODE_EYE:
+ printf("eyeprint check");
+ break;
+ case FIDO_UV_MODE_DRAWN:
+ printf("drawn pattern check");
+ break;
+ case FIDO_UV_MODE_HAND:
+ printf("handprint verification");
+ break;
+ case FIDO_UV_MODE_NONE:
+ printf("none");
+ break;
+ case FIDO_UV_MODE_ALL:
+ printf("all required");
+ break;
+ case FIDO_UV_MODE_EXT_PIN:
+ printf("external pin");
+ break;
+ case FIDO_UV_MODE_EXT_DRAWN:
+ printf("external drawn pattern check");
+ break;
+ default:
+ printf("unknown 0x%llx", (unsigned long long)mode);
+ break;
+ }
+ printed = true;
+ }
+
+ printf(")\n");
+}
+
+static void
+print_rk_remaining(int64_t rk_remaining)
+{
+ if (rk_remaining != -1)
+ printf("remaining rk(s): %d\n", (int)rk_remaining);
+}
+
+static void
+print_fwversion(uint64_t fwversion)
+{
+ printf("fwversion: 0x%x\n", (int)fwversion);
+}
+
+static void
+print_byte_array(const char *label, const uint8_t *ba, size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%u", i > 0 ? ", " : "", (unsigned)ba[i]);
+
+ printf("\n");
+}
+
+int
+token_info(int argc, char **argv, char *path)
+{
+ char *cred_id = NULL;
+ char *rp_id = NULL;
+ fido_cbor_info_t *ci = NULL;
+ fido_dev_t *dev = NULL;
+ int ch;
+ int credman = 0;
+ int r;
+ int retrycnt;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'c':
+ credman = 1;
+ break;
+ case 'i':
+ cred_id = optarg;
+ break;
+ case 'k':
+ rp_id = optarg;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ if (path == NULL || (credman && (cred_id != NULL || rp_id != NULL)))
+ usage();
+
+ dev = open_dev(path);
+
+ if (credman)
+ return (credman_get_metadata(dev, path));
+ if (cred_id && rp_id)
+ return (credman_print_rk(dev, path, rp_id, cred_id));
+ if (cred_id || rp_id)
+ usage();
+
+ print_attr(dev);
+
+ if (fido_dev_is_fido2(dev) == false)
+ goto end;
+ if ((ci = fido_cbor_info_new()) == NULL)
+ errx(1, "fido_cbor_info_new");
+ if ((r = fido_dev_get_cbor_info(dev, ci)) != FIDO_OK)
+ errx(1, "fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r);
+
+ /* print supported protocol versions */
+ print_str_array("version", fido_cbor_info_versions_ptr(ci),
+ fido_cbor_info_versions_len(ci));
+
+ /* print supported extensions */
+ print_str_array("extension", fido_cbor_info_extensions_ptr(ci),
+ fido_cbor_info_extensions_len(ci));
+
+ /* print supported transports */
+ print_str_array("transport", fido_cbor_info_transports_ptr(ci),
+ fido_cbor_info_transports_len(ci));
+
+ /* print supported algorithms */
+ print_algorithms(ci);
+
+ /* print aaguid */
+ print_aaguid(fido_cbor_info_aaguid_ptr(ci),
+ fido_cbor_info_aaguid_len(ci));
+
+ /* print supported options */
+ print_opt_array("options", fido_cbor_info_options_name_ptr(ci),
+ fido_cbor_info_options_value_ptr(ci),
+ fido_cbor_info_options_len(ci));
+
+ /* print certifications */
+ print_cert_array("certifications", fido_cbor_info_certs_name_ptr(ci),
+ fido_cbor_info_certs_value_ptr(ci),
+ fido_cbor_info_certs_len(ci));
+
+ /* print firmware version */
+ print_fwversion(fido_cbor_info_fwversion(ci));
+
+ /* print maximum message size */
+ print_maxmsgsiz(fido_cbor_info_maxmsgsiz(ci));
+
+ /* print maximum number of credentials allowed in credential lists */
+ print_maxcredcntlst(fido_cbor_info_maxcredcntlst(ci));
+
+ /* print maximum length of a credential ID */
+ print_maxcredidlen(fido_cbor_info_maxcredidlen(ci));
+
+ /* print maximum length of serialized largeBlob array */
+ print_maxlargeblob(fido_cbor_info_maxlargeblob(ci));
+
+ /* print maximum number of RP IDs in fido_dev_set_pin_minlen_rpid() */
+ print_maxrpid_minpinlen(fido_cbor_info_maxrpid_minpinlen(ci));
+
+ /* print estimated number of resident credentials */
+ print_rk_remaining(fido_cbor_info_rk_remaining(ci));
+
+ /* print minimum pin length */
+ print_minpinlen(fido_cbor_info_minpinlen(ci));
+
+ /* print supported pin protocols */
+ print_byte_array("pin protocols", fido_cbor_info_protocols_ptr(ci),
+ fido_cbor_info_protocols_len(ci));
+
+ if (fido_dev_get_retry_count(dev, &retrycnt) != FIDO_OK)
+ printf("pin retries: undefined\n");
+ else
+ printf("pin retries: %d\n", retrycnt);
+
+ printf("pin change required: %s\n",
+ fido_cbor_info_new_pin_required(ci) ? "true" : "false");
+
+ if (fido_dev_get_uv_retry_count(dev, &retrycnt) != FIDO_OK)
+ printf("uv retries: undefined\n");
+ else
+ printf("uv retries: %d\n", retrycnt);
+
+ /* print platform uv attempts */
+ print_uv_attempts(fido_cbor_info_uv_attempts(ci));
+
+ /* print supported uv mechanisms */
+ print_uv_modality(fido_cbor_info_uv_modality(ci));
+
+ bio_info(dev);
+
+ fido_cbor_info_free(&ci);
+end:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(0);
+}
+
+int
+token_reset(char *path)
+{
+ fido_dev_t *dev = NULL;
+ int r;
+
+ if (path == NULL)
+ usage();
+
+ dev = open_dev(path);
+ if ((r = fido_dev_reset(dev)) != FIDO_OK)
+ errx(1, "fido_dev_reset: %s", fido_strerr(r));
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(0);
+}
+
+int
+token_get(int argc, char **argv, char *path)
+{
+ char *id = NULL;
+ char *key = NULL;
+ char *name = NULL;
+ int blob = 0;
+ int ch;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'b':
+ blob = 1;
+ break;
+ case 'i':
+ id = optarg;
+ break;
+ case 'k':
+ key = optarg;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (blob == 0 || argc != 2)
+ usage();
+
+ return blob_get(path, key, name, id, argv[0]);
+}
+
+int
+token_set(int argc, char **argv, char *path)
+{
+ char *id = NULL;
+ char *key = NULL;
+ char *len = NULL;
+ char *display_name = NULL;
+ char *name = NULL;
+ char *rpid = NULL;
+ int blob = 0;
+ int cred = 0;
+ int ch;
+ int enroll = 0;
+ int ea = 0;
+ int uv = 0;
+ bool force = false;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'a':
+ ea = 1;
+ break;
+ case 'b':
+ blob = 1;
+ break;
+ case 'c':
+ cred = 1;
+ break;
+ case 'e':
+ enroll = 1;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'i':
+ id = optarg;
+ break;
+ case 'k':
+ key = optarg;
+ break;
+ case 'l':
+ len = optarg;
+ break;
+ case 'p':
+ display_name = optarg;
+ break;
+ case 'm':
+ rpid = optarg;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ uv = 1;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (path == NULL)
+ usage();
+
+ if (blob) {
+ if (argc != 2)
+ usage();
+ return (blob_set(path, key, name, id, argv[0]));
+ }
+
+ if (cred) {
+ if (!id || !key)
+ usage();
+ if (!name && !display_name)
+ usage();
+ return (credman_update_rk(path, key, id, name, display_name));
+ }
+
+ if (enroll) {
+ if (ea || uv)
+ usage();
+ if (id && name)
+ return (bio_set_name(path, id, name));
+ if (!id && !name)
+ return (bio_enroll(path));
+ usage();
+ }
+
+ if (ea) {
+ if (uv)
+ usage();
+ return (config_entattest(path));
+ }
+
+ if (len)
+ return (config_pin_minlen(path, len));
+ if (rpid)
+ return (config_pin_minlen_rpid(path, rpid));
+ if (force)
+ return (config_force_pin_change(path));
+ if (uv)
+ return (config_always_uv(path, 1));
+
+ return (pin_set(path));
+}
+
+int
+token_list(int argc, char **argv, char *path)
+{
+ fido_dev_info_t *devlist;
+ size_t ndevs;
+ const char *rp_id = NULL;
+ int blobs = 0;
+ int enrolls = 0;
+ int keys = 0;
+ int rplist = 0;
+ int ch;
+ int r;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'b':
+ blobs = 1;
+ break;
+ case 'e':
+ enrolls = 1;
+ break;
+ case 'k':
+ keys = 1;
+ rp_id = optarg;
+ break;
+ case 'r':
+ rplist = 1;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ if (blobs || enrolls || keys || rplist) {
+ if (path == NULL)
+ usage();
+ if (blobs)
+ return (blob_list(path));
+ if (enrolls)
+ return (bio_list(path));
+ if (keys)
+ return (credman_list_rk(path, rp_id));
+ if (rplist)
+ return (credman_list_rp(path));
+ /* NOTREACHED */
+ }
+
+ if ((devlist = fido_dev_info_new(64)) == NULL)
+ errx(1, "fido_dev_info_new");
+ if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK)
+ errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r);
+
+ for (size_t i = 0; i < ndevs; i++) {
+ const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i);
+ printf("%s: vendor=0x%04x, product=0x%04x (%s %s)\n",
+ fido_dev_info_path(di),
+ (uint16_t)fido_dev_info_vendor(di),
+ (uint16_t)fido_dev_info_product(di),
+ fido_dev_info_manufacturer_string(di),
+ fido_dev_info_product_string(di));
+ }
+
+ fido_dev_info_free(&devlist, ndevs);
+
+ exit(0);
+}
+
+int
+token_delete(int argc, char **argv, char *path)
+{
+ char *id = NULL;
+ char *key = NULL;
+ char *name = NULL;
+ int blob = 0;
+ int ch;
+ int enroll = 0;
+ int uv = 0;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'b':
+ blob = 1;
+ break;
+ case 'e':
+ enroll = 1;
+ break;
+ case 'i':
+ id = optarg;
+ break;
+ case 'k':
+ key = optarg;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ uv = 1;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ if (path == NULL)
+ usage();
+
+ if (blob)
+ return (blob_delete(path, key, name, id));
+
+ if (id) {
+ if (uv)
+ usage();
+ if (enroll == 0)
+ return (credman_delete_rk(path, id));
+ return (bio_delete(path, id));
+ }
+
+ if (uv == 0)
+ usage();
+
+ return (config_always_uv(path, 0));
+}
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;
+}