diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:45:25 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:45:25 +0000 |
commit | 814d128d1c52fe82be73ecff5b7472378041313f (patch) | |
tree | 581db0c07936d6d608e8c2e72d4903df306dd589 /tools | |
parent | Initial commit. (diff) | |
download | libfido2-814d128d1c52fe82be73ecff5b7472378041313f.tar.xz libfido2-814d128d1c52fe82be73ecff5b7472378041313f.zip |
Adding upstream version 1.14.0.upstream/1.14.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | tools/CMakeLists.txt | 74 | ||||
-rw-r--r-- | tools/assert_get.c | 328 | ||||
-rw-r--r-- | tools/assert_verify.c | 208 | ||||
-rw-r--r-- | tools/base64.c | 135 | ||||
-rw-r--r-- | tools/bio.c | 278 | ||||
-rw-r--r-- | tools/config.c | 198 | ||||
-rw-r--r-- | tools/cred_make.c | 255 | ||||
-rw-r--r-- | tools/cred_verify.c | 182 | ||||
-rw-r--r-- | tools/credman.c | 330 | ||||
-rw-r--r-- | tools/extern.h | 103 | ||||
-rw-r--r-- | tools/fido2-assert.c | 55 | ||||
-rwxr-xr-x | tools/fido2-attach.sh | 15 | ||||
-rw-r--r-- | tools/fido2-cred.c | 53 | ||||
-rwxr-xr-x | tools/fido2-detach.sh | 13 | ||||
-rw-r--r-- | tools/fido2-token.c | 110 | ||||
-rwxr-xr-x | tools/fido2-unprot.sh | 76 | ||||
-rwxr-xr-x | tools/include_check.sh | 22 | ||||
-rw-r--r-- | tools/largeblob.c | 618 | ||||
-rw-r--r-- | tools/pin.c | 159 | ||||
-rwxr-xr-x | tools/test.sh | 304 | ||||
-rw-r--r-- | tools/token.c | 729 | ||||
-rw-r--r-- | tools/util.c | 643 |
22 files changed, 4888 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..32d40b1 --- /dev/null +++ b/tools/assert_get.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2018-2023 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%s:\n", + flags & FLAG_CD ? "" : " hash"); + 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 (flags & FLAG_CD) + r = fido_assert_set_clientdata(assert, cdh.ptr, cdh.len); + else + r = fido_assert_set_clientdata_hash(assert, cdh.ptr, cdh.len); + + if (r != 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:uvw")) != -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; + case 'w': + flags |= FLAG_CD; + 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..66c8b52 --- /dev/null +++ b/tools/cred_make.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2018-2023 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%s:\n", + flags & FLAG_CD ? "" : " hash"); + 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 (flags & FLAG_CD) + r = fido_cred_set_clientdata(cred, cdh.ptr, cdh.len); + else + r = fido_cred_set_clientdata_hash(cred, cdh.ptr, cdh.len); + + if (r != FIDO_OK || (r = fido_cred_set_type(cred, type)) != 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:qruvw")) != -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; + case 'w': + flags |= FLAG_CD; + 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..b806ddd --- /dev/null +++ b/tools/extern.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2023 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 0x001 +#define FLAG_QUIET 0x002 +#define FLAG_RK 0x004 +#define FLAG_UV 0x008 +#define FLAG_U2F 0x010 +#define FLAG_HMAC 0x020 +#define FLAG_UP 0x040 +#define FLAG_LARGEBLOB 0x080 +#define FLAG_CD 0x100 + +#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..351ed4f --- /dev/null +++ b/tools/fido2-assert.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2023 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 [-bdhpruvw] [-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..76081c6 --- /dev/null +++ b/tools/fido2-cred.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2023 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 [-bdhqruvw] [-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..78b97ab --- /dev/null +++ b/tools/largeblob.c @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2020-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 <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" + +#define BOUND (1024UL * 1024UL) + +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 +try_decompress(const struct blob *in, uint64_t origsiz, int wbits) +{ + struct blob out; + z_stream zs; + u_int ilen, olen; + int ok = -1; + + memset(&zs, 0, sizeof(zs)); + memset(&out, 0, sizeof(out)); + + if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND) + return -1; + if (origsiz > SIZE_MAX || origsiz > UINT_MAX || + (olen = (u_int)origsiz) > BOUND) + return -1; + if (inflateInit2(&zs, wbits) != Z_OK) + return -1; + + if ((out.ptr = calloc(1, olen)) == NULL) + goto fail; + + out.len = olen; + zs.next_in = in->ptr; + zs.avail_in = ilen; + zs.next_out = out.ptr; + zs.avail_out = olen; + + if (inflate(&zs, Z_FINISH) != Z_STREAM_END) + goto fail; + if (zs.avail_out != 0) + goto fail; + + ok = 0; +fail: + if (inflateEnd(&zs) != Z_OK) + ok = -1; + + freezero(out.ptr, out.len); + + return ok; +} + +static int +decompress(const struct blob *plaintext, uint64_t origsiz) +{ + if (try_decompress(plaintext, origsiz, MAX_WBITS) == 0) /* rfc1950 */ + return 0; + return try_decompress(plaintext, origsiz, -MAX_WBITS); /* rfc1951 */ +} + +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; +} |