From c79050aa44b8836d836c5dd22a383a073c28b74b Mon Sep 17 00:00:00 2001 From: nicoo Date: Wed, 12 Feb 2020 13:42:22 +0100 Subject: Import upstream release 1.3.0 Closes: #951184 --- tools/CMakeLists.txt | 65 +++++ tools/assert_get.c | 233 +++++++++++++++ tools/assert_verify.c | 201 +++++++++++++ tools/base64.c | 135 +++++++++ tools/bio.c | 270 +++++++++++++++++ tools/cred_make.c | 221 ++++++++++++++ tools/cred_verify.c | 177 ++++++++++++ tools/credman.c | 237 +++++++++++++++ tools/extern.h | 64 +++++ tools/fido2-assert.c | 54 ++++ tools/fido2-cred.c | 52 ++++ tools/fido2-token.c | 95 ++++++ tools/pin.c | 146 ++++++++++ tools/sk-libfido2.c | 784 ++++++++++++++++++++++++++++++++++++++++++++++++++ tools/test.sh | 96 +++++++ tools/token.c | 364 +++++++++++++++++++++++ tools/util.c | 364 +++++++++++++++++++++++ 17 files changed, 3558 insertions(+) create mode 100644 tools/CMakeLists.txt create mode 100644 tools/assert_get.c create mode 100644 tools/assert_verify.c create mode 100644 tools/base64.c create mode 100644 tools/bio.c create mode 100644 tools/cred_make.c create mode 100644 tools/cred_verify.c create mode 100644 tools/credman.c create mode 100644 tools/extern.h create mode 100644 tools/fido2-assert.c create mode 100644 tools/fido2-cred.c create mode 100644 tools/fido2-token.c create mode 100644 tools/pin.c create mode 100644 tools/sk-libfido2.c create mode 100755 tools/test.sh create mode 100644 tools/token.c create mode 100644 tools/util.c (limited to 'tools') diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..5f27e88 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,65 @@ +# 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. + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/explicit_bzero.c + ../openbsd-compat/strlcpy.c + ../openbsd-compat/strlcat.c +) + +if(WIN32) + list(APPEND COMPAT_SOURCES + ../openbsd-compat/bsd-getline.c + ../openbsd-compat/explicit_bzero_win32.c + ../openbsd-compat/getopt_long.c + ../openbsd-compat/posix_win.c + ../openbsd-compat/readpassphrase_win32.c + ) +else() + list(APPEND COMPAT_SOURCES ../openbsd-compat/readpassphrase.c) +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 + credman.c + pin.c + token.c + util.c + ${COMPAT_SOURCES} +) + +add_library(sk-libfido2 MODULE sk-libfido2.c) +set_target_properties(sk-libfido2 PROPERTIES + COMPILE_FLAGS "-DSK_STANDALONE -DWITH_OPENSSL" + OUTPUT_NAME sk-libfido2 +) + +target_link_libraries(fido2-cred ${CRYPTO_LIBRARIES} fido2_shared) +target_link_libraries(fido2-assert ${CRYPTO_LIBRARIES} fido2_shared) +target_link_libraries(fido2-token ${CRYPTO_LIBRARIES} fido2_shared) +target_link_libraries(sk-libfido2 ${CRYPTO_LIBRARIES} fido2_shared) + +install(TARGETS fido2-cred fido2-assert fido2-token + DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(TARGETS sk-libfido2 DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/tools/assert_get.c b/tools/assert_get.c new file mode 100644 index 0000000..5e209cd --- /dev/null +++ b/tools/assert_get.c @@ -0,0 +1,233 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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 id; + struct blob hmac_salt; + char *rpid = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&id, 0, sizeof(id)); + memset(&hmac_salt, 0, sizeof(hmac_salt)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + if ((flags & FLAG_RK) == 0) + r |= base64_read(in_f, &id); + if (flags & FLAG_HMAC) + r |= base64_read(in_f, &hmac_salt); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + if ((flags & FLAG_RK) == 0) { + fprintf(stderr, "credential id:\n"); + xxd(id.ptr, id.len); + } + } + + if ((assert = fido_assert_new()) == NULL) + errx(1, "fido_assert_new"); + + if ((r = fido_assert_set_clientdata_hash(assert, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK) + errx(1, "fido_assert_set: %s", fido_strerr(r)); + + if (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)); + 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_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; + 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 (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)); + } + + 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; + char pin[1024]; + char prompt[1024]; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int flags = 0; + int ch; + int r; + + while ((ch = getopt(argc, argv, "dhi:o:pruv")) != -1) { + switch (ch) { + 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': + flags |= FLAG_UP; + break; + case 'r': + flags |= FLAG_RK; + break; + case 'u': + flags |= FLAG_U2F; + break; + case 'v': + flags |= FLAG_UV; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) + 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); + + dev = open_dev(argv[0]); + if (flags & FLAG_U2F) + fido_dev_force_u2f(dev); + + if (flags & FLAG_UV) { + 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"); + 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..ccff57a --- /dev/null +++ b/tools/assert_verify.c @@ -0,0 +1,201 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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; + rs256_pk_t *rs256_pk = NULL; + eddsa_pk_t *eddsa_pk = NULL; + void *pk = NULL; + + if (type == 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); + } else if (type == 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); + } else if (type == 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); + } + + 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) { + if (strcmp(argv[1], "es256") == 0) + type = COSE_ES256; + else if (strcmp(argv[1], "rs256") == 0) + type = COSE_RS256; + else if (strcmp(argv[1], "eddsa") == 0) + type = COSE_EDDSA; + else + 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..9f31def --- /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. + */ + +#include +#include + +#include +#include +#include +#include + +#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(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((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..b8f9b38 --- /dev/null +++ b/tools/bio.c @@ -0,0 +1,270 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static void +print_template(const fido_bio_template_array_t *ta, size_t idx) +{ + char *id = NULL; + const fido_bio_template_t *t = NULL; + + if ((t = fido_bio_template(ta, idx)) == NULL) + errx(1, "fido_bio_template"); + + if (base64_encode(fido_bio_template_id_ptr(t), + fido_bio_template_id_len(t), &id) < 0) + errx(1, "output error"); + + printf("%02u: %s %s\n", (unsigned)idx, id, fido_bio_template_name(t)); + + free(id); +} + +int +bio_list(char *path) +{ + char pin[1024]; + fido_bio_template_array_t *ta = NULL; + fido_dev_t *dev = NULL; + int r; + + if (path == NULL) + usage(); + if ((ta = fido_bio_template_array_new()) == NULL) + errx(1, "fido_bio_template_array_new"); + + dev = open_dev(path); + read_pin(path, pin, sizeof(pin)); + r = fido_bio_dev_get_template_array(dev, ta, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_bio_dev_get_template_array: %s", fido_strerr(r)); + for (size_t i = 0; i < fido_bio_template_array_count(ta); i++) + print_template(ta, i); + + fido_bio_template_array_free(&ta); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +bio_set_name(char *path, char *id, char *name) +{ + char pin[1024]; + fido_bio_template_t *t = NULL; + fido_dev_t *dev = NULL; + int r; + size_t id_blob_len = 0; + void *id_blob_ptr = NULL; + + if (path == NULL) + usage(); + 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); + read_pin(path, pin, sizeof(pin)); + r = fido_bio_dev_set_template_name(dev, t, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_bio_dev_set_template_name: %s", fido_strerr(r)); + + free(id_blob_ptr); + fido_bio_template_free(&t); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +static const char * +plural(uint8_t n) +{ + if (n == 1) + return ""; + return "s"; +} + +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(char *path) +{ + char pin[1024]; + fido_bio_enroll_t *e = NULL; + fido_bio_template_t *t = NULL; + fido_dev_t *dev = NULL; + int r; + + if (path == NULL) + usage(); + 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); + read_pin(path, pin, sizeof(pin)); + + printf("Touch your security key.\n"); + + r = fido_bio_dev_enroll_begin(dev, t, e, 10000, pin); + explicit_bzero(pin, sizeof(pin)); + if (r != FIDO_OK) + errx(1, "fido_bio_dev_enroll_begin: %s", fido_strerr(r)); + + 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) { + errx(1, "fido_bio_dev_enroll_continue: %s", + fido_strerr(r)); + } + printf("%s.\n", enroll_strerr(fido_bio_enroll_last_status(e))); + } + + fido_bio_template_free(&t); + fido_bio_enroll_free(&e); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +bio_delete(fido_dev_t *dev, char *path, char *id) +{ + char pin[1024]; + fido_bio_template_t *t = NULL; + int r; + size_t id_blob_len = 0; + void *id_blob_ptr = NULL; + + if (path == NULL) + usage(); + 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)); + + read_pin(path, pin, sizeof(pin)); + r = fido_bio_dev_enroll_remove(dev, t, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_bio_dev_enroll_remove: %s", fido_strerr(r)); + + free(id_blob_ptr); + fido_bio_template_free(&t); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +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; + int r; + + if ((i = fido_bio_info_new()) == NULL) + errx(1, "fido_bio_info_new"); + if ((r = 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/cred_make.c b/tools/cred_make.c new file mode 100644 index 0000000..380c67a --- /dev/null +++ b/tools/cred_make.c @@ -0,0 +1,221 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static fido_cred_t * +prepare_cred(FILE *in_f, int type, int flags) +{ + fido_cred_t *cred = NULL; + struct blob cdh; + struct blob uid; + char *rpid = NULL; + char *uname = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&uid, 0, sizeof(uid)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + r |= string_read(in_f, &uname); + r |= base64_read(in_f, &uid); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + fprintf(stderr, "user name: %s\n", uname); + fprintf(stderr, "user id:\n"); + xxd(uid.ptr, uid.len); + } + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + if ((r = fido_cred_set_type(cred, type)) != FIDO_OK || + (r = fido_cred_set_clientdata_hash(cred, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK || + (r = fido_cred_set_user(cred, uid.ptr, uid.len, uname, NULL, + NULL)) != FIDO_OK) + errx(1, "fido_cred_set: %s", fido_strerr(r)); + + if (flags & FLAG_RK) { + if ((r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_rk: %s", fido_strerr(r)); + } + if (flags & FLAG_UV) { + if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_uv: %s", fido_strerr(r)); + } + if (flags & FLAG_HMAC) { + if ((r = fido_cred_set_extensions(cred, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s", fido_strerr(r)); + } + + 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; + 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 (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); + + free(cdh); + free(authdata); + free(id); + free(sig); + free(x5c); +} + +int +cred_make(int argc, char **argv) +{ + fido_dev_t *dev = NULL; + fido_cred_t *cred = NULL; + char prompt[1024]; + char pin[1024]; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int type = COSE_ES256; + int flags = 0; + int ch; + int r; + + while ((ch = getopt(argc, argv, "dhi:o:qruv")) != -1) { + switch (ch) { + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'o': + out_path = optarg; + break; + case 'q': + flags |= FLAG_QUIET; + break; + case 'r': + flags |= FLAG_RK; + break; + case 'u': + flags |= FLAG_U2F; + break; + case 'v': + flags |= FLAG_UV; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1 || argc > 2) + usage(); + + in_f = open_read(in_path); + out_f = open_write(out_path); + + if (argc > 1) { + if (strcmp(argv[1], "es256") == 0) + type = COSE_ES256; + else if (strcmp(argv[1], "rs256") == 0) + type = COSE_RS256; + else if (strcmp(argv[1], "eddsa") == 0) + type = COSE_EDDSA; + else + 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); + + 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"); + 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..3f7a400 --- /dev/null +++ b/tools/cred_verify.c @@ -0,0 +1,177 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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 ch; + int r; + + while ((ch = getopt(argc, argv, "dhi:o:v")) != -1) { + switch (ch) { + 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) { + if (strcmp(argv[0], "es256") == 0) + type = COSE_ES256; + else if (strcmp(argv[0], "rs256") == 0) + type = COSE_RS256; + else if (strcmp(argv[0], "eddsa") == 0) + type = COSE_EDDSA; + else + errx(1, "unknown type %s", argv[0]); + } + + fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0); + cred = prepare_cred(in_f, type, flags); + + 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..08c9eb8 --- /dev/null +++ b/tools/credman.c @@ -0,0 +1,237 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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[1024]; + int r; + + if ((metadata = fido_credman_metadata_new()) == NULL) + errx(1, "fido_credman_metadata_new"); + + read_pin(path, pin, sizeof(pin)); + r = fido_credman_get_dev_metadata(dev, metadata, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_credman_get_dev_metadata: %s", fido_strerr(r)); + + printf("existing rk(s): %u\n", + (unsigned)fido_credman_rk_existing(metadata)); + printf("possible rk(s): %u\n", + (unsigned)fido_credman_rk_remaining(metadata)); + + fido_credman_metadata_free(&metadata); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +static void +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) + errx(1, "output error"); + + printf("%02u: %s %s\n", (unsigned)idx, rp_id_hash, + fido_credman_rp_id(rp, idx)); + + free(rp_id_hash); + rp_id_hash = NULL; +} + +int +credman_list_rp(char *path) +{ + fido_dev_t *dev = NULL; + fido_credman_rp_t *rp = NULL; + char pin[1024]; + int r; + + if (path == NULL) + usage(); + if ((rp = fido_credman_rp_new()) == NULL) + errx(1, "fido_credman_rp_new"); + + dev = open_dev(path); + read_pin(path, pin, sizeof(pin)); + r = fido_credman_get_dev_rp(dev, rp, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_credman_get_dev_rp: %s", fido_strerr(r)); + + for (size_t i = 0; i < fido_credman_rp_count(rp); i++) + print_rp(rp, i); + + fido_credman_rp_free(&rp); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +static void +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; + + if ((cred = fido_credman_rk(rk, idx)) == NULL) + errx(1, "fido_credman_rk"); + 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) + errx(1, "output error"); + + switch (fido_cred_type(cred)) { + case COSE_EDDSA: + type = "eddsa"; + break; + case COSE_ES256: + type = "es256"; + break; + case COSE_RS256: + type = "rs256"; + break; + default: + type = "unknown"; + break; + } + + printf("%02u: %s %s (%s) %s\n", (unsigned)idx, id, + fido_cred_display_name(cred), user_id, type); + + free(user_id); + free(id); + user_id = NULL; + id = NULL; +} + +int +credman_list_rk(char *path, const char *rp_id) +{ + fido_dev_t *dev = NULL; + fido_credman_rk_t *rk = NULL; + char pin[1024]; + int r; + + if (path == NULL) + usage(); + if ((rk = fido_credman_rk_new()) == NULL) + errx(1, "fido_credman_rk_new"); + + dev = open_dev(path); + read_pin(path, pin, sizeof(pin)); + r = fido_credman_get_dev_rk(dev, rp_id, rk, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_credman_get_dev_rk: %s", fido_strerr(r)); + for (size_t i = 0; i < fido_credman_rk_count(rk); i++) + print_rk(rk, i); + + fido_credman_rk_free(&rk); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +credman_print_rk(fido_dev_t *dev, const char *path, char *rp_id, char *cred_id) +{ + const fido_cred_t *cred = NULL; + fido_credman_rk_t *rk = NULL; + char pin[1024]; + void *cred_id_ptr = NULL; + size_t cred_id_len = 0; + int r; + + if ((rk = fido_credman_rk_new()) == NULL) + errx(1, "fido_credman_rk_new"); + if (base64_decode(cred_id, &cred_id_ptr, &cred_id_len) < 0) + errx(1, "base64_decode"); + + read_pin(path, pin, sizeof(pin)); + r = fido_credman_get_dev_rk(dev, rp_id, rk, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_credman_get_dev_rk: %s", fido_strerr(r)); + + 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) + errx(1, "output error"); + 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); + goto out; + } + + errx(1, "credential not found"); + +out: + free(cred_id_ptr); + cred_id_ptr = NULL; + + fido_credman_rk_free(&rk); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +credman_delete_rk(fido_dev_t *dev, const char *path, char *id) +{ + char pin[1024]; + void *id_ptr = NULL; + size_t id_len = 0; + int r; + + if (base64_decode(id, &id_ptr, &id_len) < 0) + errx(1, "base64_decode"); + + read_pin(path, pin, sizeof(pin)); + r = fido_credman_del_dev_rk(dev, id_ptr, id_len, pin); + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_credman_del_dev_rk: %s", fido_strerr(r)); + + free(id_ptr); + id_ptr = NULL; + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} diff --git a/tools/extern.h b/tools/extern.h new file mode 100644 index 0000000..e79e6f0 --- /dev/null +++ b/tools/extern.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +struct blob { + unsigned char *ptr; + size_t len; +}; + +#define TOKEN_OPT "CDILPRSVbcdei:k:n:r" + +#define FLAG_DEBUG 0x01 +#define FLAG_QUIET 0x02 +#define FLAG_RK 0x04 +#define FLAG_UV 0x08 +#define FLAG_U2F 0x10 +#define FLAG_HMAC 0x20 +#define FLAG_UP 0x40 + +EC_KEY *read_ec_pubkey(const char *); +fido_dev_t *open_dev(const char *); +FILE *open_read(const char *); +FILE *open_write(const char *); +int assert_get(int, char **); +int assert_verify(int, char **); +int base64_decode(char *, void **, size_t *); +int base64_encode(const void *, size_t, char **); +int base64_read(FILE *, struct blob *); +int bio_delete(fido_dev_t *, char *, char *); +int bio_enroll(char *); +void bio_info(fido_dev_t *); +int bio_list(char *); +int bio_set_name(char *, char *, char *); +int cred_make(int, char **); +int cred_verify(int, char **); +int credman_delete_rk(fido_dev_t *, const char *, char *); +int credman_get_metadata(fido_dev_t *, const char *); +int credman_list_rk(char *, const char *); +int credman_list_rp(char *); +int credman_print_rk(fido_dev_t *, const char *, char *, char *); +int pin_change(char *); +int pin_set(char *); +int string_read(FILE *, char **); +int token_delete(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_ec_pubkey(FILE *, const void *, size_t); +int write_rsa_pubkey(FILE *, const void *, 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 read_pin(const char *, char *, size_t); +void usage(void); +void xxd(const void *, size_t); + +#endif /* _EXTERN_H_ */ diff --git a/tools/fido2-assert.c b/tools/fido2-assert.c new file mode 100644 index 0000000..9ce537a --- /dev/null +++ b/tools/fido2-assert.c @@ -0,0 +1,54 @@ +/* + * 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. + */ + +/* + * 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 +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-assert -G [-dhpruv] [-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-cred.c b/tools/fido2-cred.c new file mode 100644 index 0000000..45efca0 --- /dev/null +++ b/tools/fido2-cred.c @@ -0,0 +1,52 @@ +/* + * 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. + */ + +/* + * 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 +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-cred -M [-dhqruv] [-i input_file] [-o output_file] device [type]\n" +" fido2-cred -V [-dhv] [-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-token.c b/tools/fido2-token.c new file mode 100644 index 0000000..0b02fea --- /dev/null +++ b/tools/fido2-token.c @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static int action; + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-token [-CR] [-d] device\n" +" fido2-token -D [-de] -i id device\n" +" fido2-token -I [-cd] [-k rp_id -i cred_id] device\n" +" fido2-token -L [-der] [-k rp_id] [device]\n" +" fido2-token -S [-de] [-i template_id -n template_name] 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 'b': + case 'c': + case 'e': + case 'i': + case 'k': + case 'n': + case 'r': + break; /* ignore */ + case 'd': + flags = FIDO_DEBUG; + break; + default: + setaction(ch); + break; + } + } + + if (argc - optind == 1) + device = argv[optind]; + else + device = NULL; + + fido_init(flags); + + switch (action) { + case 'C': + return (pin_change(device)); + case 'D': + return (token_delete(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/pin.c b/tools/pin.c new file mode 100644 index 0000000..eab178a --- /dev/null +++ b/tools/pin.c @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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[1024]; + char pin2[1024]; + int r; + int status = 1; + + if (path == NULL) + usage(); + + 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 ((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[1024]; + char pin1[1024]; + char pin2[1024]; + 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; + } + + 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 ((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/sk-libfido2.c b/tools/sk-libfido2.c new file mode 100644 index 0000000..15aa813 --- /dev/null +++ b/tools/sk-libfido2.c @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2019 Markus Friedl + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef WITH_OPENSSL +#include +#include +#include +#include +#include +#endif /* WITH_OPENSSL */ + +#include + +#ifndef SK_STANDALONE +#include "log.h" +#include "xmalloc.h" +#endif + +/* #define SK_DEBUG 1 */ + +#if defined(_WIN32) +#include +#include +#include +#include +#include +#endif + +#define MAX_FIDO_DEVICES 256 + +/* Compatibility with OpenSSL 1.0.x */ +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) +#define ECDSA_SIG_get0(sig, pr, ps) \ + do { \ + (*pr) = sig->r; \ + (*ps) = sig->s; \ + } while (0) +#endif + +#define SK_VERSION_MAJOR 0x00020000 /* current API version */ + +/* Flags */ +#define SK_USER_PRESENCE_REQD 0x01 + +/* Algs */ +#define SK_ECDSA 0x00 +#define SK_ED25519 0x01 + +struct sk_enroll_response { + uint8_t *public_key; + size_t public_key_len; + uint8_t *key_handle; + size_t key_handle_len; + uint8_t *signature; + size_t signature_len; + uint8_t *attestation_cert; + size_t attestation_cert_len; +}; + +struct sk_sign_response { + uint8_t flags; + uint32_t counter; + uint8_t *sig_r; + size_t sig_r_len; + uint8_t *sig_s; + size_t sig_s_len; +}; + +/* If building as part of OpenSSH, then rename exported functions */ +#if !defined(SK_STANDALONE) +#define sk_api_version ssh_sk_api_version +#define sk_enroll ssh_sk_enroll +#define sk_sign ssh_sk_sign +#endif + +/* Return the version of the middleware API */ +uint32_t sk_api_version(void); + +/* Enroll a U2F key (private key generation) */ +int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, + const char *application, uint8_t flags, + struct sk_enroll_response **enroll_response); + +/* Sign a challenge */ +int sk_sign(int alg, const uint8_t *message, size_t message_len, + const char *application, const uint8_t *key_handle, size_t key_handle_len, + uint8_t flags, struct sk_sign_response **sign_response); + +#ifdef SK_DEBUG +static void skdebug(const char *func, const char *fmt, ...) + __attribute__((__format__ (printf, 2, 3))); + +static void +skdebug(const char *func, const char *fmt, ...) +{ +#if !defined(SK_STANDALONE) + char *msg; + va_list ap; + + va_start(ap, fmt); + xvasprintf(&msg, fmt, ap); + va_end(ap); + debug("%s: %s", func, msg); + free(msg); +#else + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s: ", func); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); +#endif /* !SK_STANDALONE */ +} +#else +#define skdebug(...) do { /* nothing */ } while (0) +#endif /* SK_DEBUG */ + +uint32_t +sk_api_version(void) +{ + return SK_VERSION_MAJOR; +} + +/* Select the first identified FIDO device attached to the system */ +static char * +pick_first_device(void) +{ + char *ret = NULL; + fido_dev_info_t *devlist = NULL; + size_t olen = 0; + int r; + const fido_dev_info_t *di; + + if ((devlist = fido_dev_info_new(1)) == NULL) { + skdebug(__func__, "fido_dev_info_new failed"); + goto out; + } + if ((r = fido_dev_info_manifest(devlist, 1, &olen)) != FIDO_OK) { + skdebug(__func__, "fido_dev_info_manifest failed: %s", + fido_strerr(r)); + goto out; + } + if (olen != 1) { + skdebug(__func__, "fido_dev_info_manifest bad len %zu", olen); + goto out; + } + di = fido_dev_info_ptr(devlist, 0); + if ((ret = strdup(fido_dev_info_path(di))) == NULL) { + skdebug(__func__, "fido_dev_info_path failed"); + goto out; + } + out: + fido_dev_info_free(&devlist, 1); + return ret; +} + +#if defined(HAVE_ARC4RANDOM_BUF) +static int +get_random_challenge(uint8_t *ptr, size_t len) +{ + arc4random_buf(ptr, len); + + return 0; +} +#elif defined(HAVE_GETENTROPY) +static int +get_random_challenge(uint8_t *ptr, size_t len) +{ + if (getentropy(ptr, len) == -1) { + skdebug(__func__, "getentropy failed"); + return -1; + } + + return 0; +} +#elif defined(HAS_DEV_URANDOM) +static int +get_random_challenge(uint8_t *ptr, size_t len) +{ + int fd; + ssize_t n; + + if ((fd = open(FIDO_RANDOM_DEV, O_RDONLY)) < 0) { + skdebug(__func__, "open %s failed", FIDO_RANDOM_DEV); + return -1; + } + + n = read(fd, ptr, len); + close(fd); + + if (n < 0 || (size_t)n != len) { + skdebug(__func__, "read from %s failed", FIDO_RANDOM_DEV); + return -1; + } + + return 0; +} +#elif defined(_WIN32) +static int +get_random_challenge(uint8_t *ptr, size_t len) +{ + NTSTATUS status; + + status = BCryptGenRandom(NULL, ptr, len, + BCRYPT_USE_SYSTEM_PREFERRED_RNG); + if (!NT_SUCCESS(status)) + return -1; + + return 0; +} +#else +#error "please provide an implementation of get_random_challenge() for your platform" +#endif + +/* Check if the specified key handle exists on a given device. */ +static int +try_device(fido_dev_t *dev, const char *application, + const uint8_t *key_handle, size_t key_handle_len) +{ + fido_assert_t *assert = NULL; + uint8_t challenge[32]; + int r = FIDO_ERR_INTERNAL; + + if (get_random_challenge(challenge, sizeof(challenge)) == -1) { + skdebug(__func__, "get_random_challenge failed"); + goto out; + } + + if ((assert = fido_assert_new()) == NULL) { + skdebug(__func__, "fido_assert_new failed"); + goto out; + } + if ((r = fido_assert_set_clientdata_hash(assert, challenge, + sizeof(challenge))) != FIDO_OK) { + skdebug(__func__, "fido_assert_set_clientdata_hash: %s", + fido_strerr(r)); + goto out; + } + if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { + skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_assert_allow_cred(assert, key_handle, + key_handle_len)) != FIDO_OK) { + skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) { + skdebug(__func__, "fido_assert_up: %s", fido_strerr(r)); + goto out; + } + r = fido_dev_get_assert(dev, assert, NULL); + skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); + if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) { + /* U2F tokens may return this */ + r = FIDO_OK; + } + out: + fido_assert_free(&assert); + + return r != FIDO_OK ? -1 : 0; +} + +/* Iterate over configured devices looking for a specific key handle */ +static fido_dev_t * +find_device(const char *application, const uint8_t *key_handle, + size_t key_handle_len) +{ + fido_dev_info_t *devlist = NULL; + fido_dev_t *dev = NULL; + size_t devlist_len = 0, i; + const char *path; + int r; + + if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { + skdebug(__func__, "fido_dev_info_new failed"); + goto out; + } + if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES, + &devlist_len)) != FIDO_OK) { + skdebug(__func__, "fido_dev_info_manifest: %s", fido_strerr(r)); + goto out; + } + + skdebug(__func__, "found %zu device(s)", devlist_len); + + for (i = 0; i < devlist_len; i++) { + const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); + + if (di == NULL) { + skdebug(__func__, "fido_dev_info_ptr %zu failed", i); + continue; + } + if ((path = fido_dev_info_path(di)) == NULL) { + skdebug(__func__, "fido_dev_info_path %zu failed", i); + continue; + } + skdebug(__func__, "trying device %zu: %s", i, path); + if ((dev = fido_dev_new()) == NULL) { + skdebug(__func__, "fido_dev_new failed"); + continue; + } + if ((r = fido_dev_open(dev, path)) != FIDO_OK) { + skdebug(__func__, "fido_dev_open failed"); + fido_dev_free(&dev); + continue; + } + if (try_device(dev, application, key_handle, + key_handle_len) == 0) { + skdebug(__func__, "found key"); + break; + } + fido_dev_close(dev); + fido_dev_free(&dev); + } + + out: + if (devlist != NULL) + fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); + + return dev; +} + +#ifdef WITH_OPENSSL +/* + * The key returned via fido_cred_pubkey_ptr() is in affine coordinates, + * but the API expects a SEC1 octet string. + */ +static int +pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response) +{ + const uint8_t *ptr; + BIGNUM *x = NULL, *y = NULL; + EC_POINT *q = NULL; + EC_GROUP *g = NULL; + int ret = -1; + + response->public_key = NULL; + response->public_key_len = 0; + + if ((x = BN_new()) == NULL || + (y = BN_new()) == NULL || + (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL || + (q = EC_POINT_new(g)) == NULL) { + skdebug(__func__, "libcrypto setup failed"); + goto out; + } + if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { + skdebug(__func__, "fido_cred_pubkey_ptr failed"); + goto out; + } + if (fido_cred_pubkey_len(cred) != 64) { + skdebug(__func__, "bad fido_cred_pubkey_len %zu", + fido_cred_pubkey_len(cred)); + goto out; + } + + if (BN_bin2bn(ptr, 32, x) == NULL || + BN_bin2bn(ptr + 32, 32, y) == NULL) { + skdebug(__func__, "BN_bin2bn failed"); + goto out; + } + if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) { + skdebug(__func__, "EC_POINT_set_affine_coordinates_GFp failed"); + goto out; + } + response->public_key_len = EC_POINT_point2oct(g, q, + POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); + if (response->public_key_len == 0 || response->public_key_len > 2048) { + skdebug(__func__, "bad pubkey length %zu", + response->public_key_len); + goto out; + } + if ((response->public_key = malloc(response->public_key_len)) == NULL) { + skdebug(__func__, "malloc pubkey failed"); + goto out; + } + if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED, + response->public_key, response->public_key_len, NULL) == 0) { + skdebug(__func__, "EC_POINT_point2oct failed"); + goto out; + } + /* success */ + ret = 0; + out: + if (ret != 0 && response->public_key != NULL) { + memset(response->public_key, 0, response->public_key_len); + free(response->public_key); + response->public_key = NULL; + } + EC_POINT_free(q); + EC_GROUP_free(g); + BN_clear_free(x); + BN_clear_free(y); + return ret; +} +#endif /* WITH_OPENSSL */ + +static int +pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response) +{ + const uint8_t *ptr; + size_t len; + int ret = -1; + + response->public_key = NULL; + response->public_key_len = 0; + + if ((len = fido_cred_pubkey_len(cred)) != 32) { + skdebug(__func__, "bad fido_cred_pubkey_len len %zu", len); + goto out; + } + if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { + skdebug(__func__, "fido_cred_pubkey_ptr failed"); + goto out; + } + response->public_key_len = len; + if ((response->public_key = malloc(response->public_key_len)) == NULL) { + skdebug(__func__, "malloc pubkey failed"); + goto out; + } + memcpy(response->public_key, ptr, len); + ret = 0; + out: + if (ret != 0) + free(response->public_key); + return ret; +} + +static int +pack_public_key(int alg, fido_cred_t *cred, struct sk_enroll_response *response) +{ + switch(alg) { +#ifdef WITH_OPENSSL + case SK_ECDSA: + return pack_public_key_ecdsa(cred, response); +#endif /* WITH_OPENSSL */ + case SK_ED25519: + return pack_public_key_ed25519(cred, response); + default: + return -1; + } +} + +int +sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, + const char *application, uint8_t flags, + struct sk_enroll_response **enroll_response) +{ + fido_cred_t *cred = NULL; + fido_dev_t *dev = NULL; + const uint8_t *ptr; + uint8_t user_id[32]; + struct sk_enroll_response *response = NULL; + size_t len; + int cose_alg; + int ret = -1; + int r; + char *device = NULL; + + (void)flags; /* XXX; unused */ +#ifdef SK_DEBUG + fido_init(FIDO_DEBUG); +#endif + if (enroll_response == NULL) { + skdebug(__func__, "enroll_response == NULL"); + goto out; + } + *enroll_response = NULL; + switch(alg) { +#ifdef WITH_OPENSSL + case SK_ECDSA: + cose_alg = COSE_ES256; + break; +#endif /* WITH_OPENSSL */ + case SK_ED25519: + cose_alg = COSE_EDDSA; + break; + default: + skdebug(__func__, "unsupported key type %d", alg); + goto out; + } + if ((device = pick_first_device()) == NULL) { + skdebug(__func__, "pick_first_device failed"); + goto out; + } + skdebug(__func__, "using device %s", device); + if ((cred = fido_cred_new()) == NULL) { + skdebug(__func__, "fido_cred_new failed"); + goto out; + } + memset(user_id, 0, sizeof(user_id)); + if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { + skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_cred_set_clientdata_hash(cred, challenge, + challenge_len)) != FIDO_OK) { + skdebug(__func__, "fido_cred_set_clientdata_hash: %s", + fido_strerr(r)); + goto out; + } + if ((r = fido_cred_set_user(cred, user_id, sizeof(user_id), + "openssh", "openssh", NULL)) != FIDO_OK) { + skdebug(__func__, "fido_cred_set_user: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_cred_set_rp(cred, application, NULL)) != FIDO_OK) { + skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r)); + goto out; + } + if ((dev = fido_dev_new()) == NULL) { + skdebug(__func__, "fido_dev_new failed"); + goto out; + } + if ((r = fido_dev_open(dev, device)) != FIDO_OK) { + skdebug(__func__, "fido_dev_open: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_dev_make_cred(dev, cred, NULL)) != FIDO_OK) { + skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r)); + goto out; + } + if (fido_cred_x5c_ptr(cred) != NULL) { + if ((r = fido_cred_verify(cred)) != FIDO_OK) { + skdebug(__func__, "fido_cred_verify: %s", + fido_strerr(r)); + goto out; + } + } else { + skdebug(__func__, "self-attested credential"); + if ((r = fido_cred_verify_self(cred)) != FIDO_OK) { + skdebug(__func__, "fido_cred_verify_self: %s", + fido_strerr(r)); + goto out; + } + } + if ((response = calloc(1, sizeof(*response))) == NULL) { + skdebug(__func__, "calloc response failed"); + goto out; + } + if (pack_public_key(alg, cred, response) != 0) { + skdebug(__func__, "pack_public_key failed"); + goto out; + } + if ((ptr = fido_cred_id_ptr(cred)) != NULL) { + len = fido_cred_id_len(cred); + if ((response->key_handle = calloc(1, len)) == NULL) { + skdebug(__func__, "calloc key handle failed"); + goto out; + } + memcpy(response->key_handle, ptr, len); + response->key_handle_len = len; + } + if ((ptr = fido_cred_sig_ptr(cred)) != NULL) { + len = fido_cred_sig_len(cred); + if ((response->signature = calloc(1, len)) == NULL) { + skdebug(__func__, "calloc signature failed"); + goto out; + } + memcpy(response->signature, ptr, len); + response->signature_len = len; + } + if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) { + len = fido_cred_x5c_len(cred); + if ((response->attestation_cert = calloc(1, len)) == NULL) { + skdebug(__func__, "calloc attestation cert failed"); + goto out; + } + memcpy(response->attestation_cert, ptr, len); + response->attestation_cert_len = len; + } + *enroll_response = response; + response = NULL; + ret = 0; + out: + free(device); + if (response != NULL) { + free(response->public_key); + free(response->key_handle); + free(response->signature); + free(response->attestation_cert); + free(response); + } + if (dev != NULL) { + fido_dev_close(dev); + fido_dev_free(&dev); + } + if (cred != NULL) { + fido_cred_free(&cred); + } + return ret; +} + +#ifdef WITH_OPENSSL +static int +pack_sig_ecdsa(fido_assert_t *assert, struct sk_sign_response *response) +{ + ECDSA_SIG *sig = NULL; + const BIGNUM *sig_r, *sig_s; + const unsigned char *cp; + size_t sig_len; + int ret = -1; + + cp = fido_assert_sig_ptr(assert, 0); + sig_len = fido_assert_sig_len(assert, 0); + if ((sig = d2i_ECDSA_SIG(NULL, &cp, sig_len)) == NULL) { + skdebug(__func__, "d2i_ECDSA_SIG failed"); + goto out; + } + ECDSA_SIG_get0(sig, &sig_r, &sig_s); + response->sig_r_len = BN_num_bytes(sig_r); + response->sig_s_len = BN_num_bytes(sig_s); + if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL || + (response->sig_s = calloc(1, response->sig_s_len)) == NULL) { + skdebug(__func__, "calloc signature failed"); + goto out; + } + BN_bn2bin(sig_r, response->sig_r); + BN_bn2bin(sig_s, response->sig_s); + ret = 0; + out: + ECDSA_SIG_free(sig); + if (ret != 0) { + free(response->sig_r); + free(response->sig_s); + response->sig_r = NULL; + response->sig_s = NULL; + } + return ret; +} +#endif /* WITH_OPENSSL */ + +static int +pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response) +{ + const unsigned char *ptr; + size_t len; + int ret = -1; + + ptr = fido_assert_sig_ptr(assert, 0); + len = fido_assert_sig_len(assert, 0); + if (len != 64) { + skdebug(__func__, "bad length %zu", len); + goto out; + } + response->sig_r_len = len; + if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) { + skdebug(__func__, "calloc signature failed"); + goto out; + } + memcpy(response->sig_r, ptr, len); + ret = 0; + out: + if (ret != 0) { + free(response->sig_r); + response->sig_r = NULL; + } + return ret; +} + +static int +pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response) +{ + switch(alg) { +#ifdef WITH_OPENSSL + case SK_ECDSA: + return pack_sig_ecdsa(assert, response); +#endif /* WITH_OPENSSL */ + case SK_ED25519: + return pack_sig_ed25519(assert, response); + default: + return -1; + } +} + +int +sk_sign(int alg, const uint8_t *message, size_t message_len, + const char *application, + const uint8_t *key_handle, size_t key_handle_len, + uint8_t flags, struct sk_sign_response **sign_response) +{ + fido_assert_t *assert = NULL; + fido_dev_t *dev = NULL; + struct sk_sign_response *response = NULL; + int ret = -1; + int r; + +#ifdef SK_DEBUG + fido_init(FIDO_DEBUG); +#endif + + if (sign_response == NULL) { + skdebug(__func__, "sign_response == NULL"); + goto out; + } + *sign_response = NULL; + if ((dev = find_device(application, key_handle, + key_handle_len)) == NULL) { + skdebug(__func__, "couldn't find device for key handle"); + goto out; + } + if ((assert = fido_assert_new()) == NULL) { + skdebug(__func__, "fido_assert_new failed"); + goto out; + } + if ((r = fido_assert_set_clientdata_hash(assert, message, + message_len)) != FIDO_OK) { + skdebug(__func__, "fido_assert_set_clientdata_hash: %s", + fido_strerr(r)); + goto out; + } + if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { + skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_assert_allow_cred(assert, key_handle, + key_handle_len)) != FIDO_OK) { + skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_assert_set_up(assert, + (flags & SK_USER_PRESENCE_REQD) ? + FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK) { + skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_dev_get_assert(dev, assert, NULL)) != FIDO_OK) { + skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); + goto out; + } + if ((response = calloc(1, sizeof(*response))) == NULL) { + skdebug(__func__, "calloc response failed"); + goto out; + } + response->flags = fido_assert_flags(assert, 0); + response->counter = fido_assert_sigcount(assert, 0); + if (pack_sig(alg, assert, response) != 0) { + skdebug(__func__, "pack_sig failed"); + goto out; + } + *sign_response = response; + response = NULL; + ret = 0; + out: + if (response != NULL) { + free(response->sig_r); + free(response->sig_s); + free(response); + } + if (dev != NULL) { + fido_dev_close(dev); + fido_dev_free(&dev); + } + if (assert != NULL) { + fido_assert_free(&assert); + } + return ret; +} diff --git a/tools/test.sh b/tools/test.sh new file mode 100755 index 0000000..8159a44 --- /dev/null +++ b/tools/test.sh @@ -0,0 +1,96 @@ +#!/bin/bash -e +# +# 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. + +if [[ "$#" -ne 1 ]]; then + echo "usage: test.sh device" 1>&2 + exit 1 +fi + +read -p "This script will reset the authenticator at $1, permanently erasing "\ +"its credentials. Are you *SURE* you want to proceed (yes/no)? " +if [[ "${REPLY}" != "yes" ]]; then + exit 1 +fi + +echo "Resetting authenticator... (tap to continue!)" +fido2-token -R $1 + +CRED_PARAM="$(mktemp /tmp/cred_param.XXXXXXXX)" +ASSERT_PARAM="$(mktemp /tmp/assert_param.XXXXXXXX)" +ASSERT_PUBKEY="$(mktemp /tmp/assert_pubkey.XXXXXXXX)" +ES256_CRED="$(mktemp /tmp/es256_cred.XXXXXXX)" +ES256_CRED_R="$(mktemp /tmp/es256_cred_r.XXXXXXXX)" + +cleanup() { + echo "Cleaning up..." + [[ "${CRED_PARAM}" != "" ]] && rm "${CRED_PARAM}" + [[ "${ASSERT_PARAM}" != "" ]] && rm "${ASSERT_PARAM}" + [[ "${ASSERT_PUBKEY}" != "" ]] && rm "${ASSERT_PUBKEY}" + [[ "${ES256_CRED}" != "" ]] && rm "${ES256_CRED}" + [[ "${ES256_CRED_R}" != "" ]] && rm "${ES256_CRED_R}" +} + +trap cleanup EXIT + +dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 > "${CRED_PARAM}" +echo "Boring Relying Party" >> "${CRED_PARAM}" +echo "Boring User Name" >> "${CRED_PARAM}" +dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 >> "${CRED_PARAM}" +echo "Credential parameters:" +cat "${CRED_PARAM}" + +echo "Generating non-resident ES256 credential... (tap to continue!)" +fido2-cred -M -i "${CRED_PARAM}" $1 | fido2-cred -V | tee "${ES256_CRED}" +echo "Generating resident ES256 credential... (tap to continue!)" +fido2-cred -M -r -i "${CRED_PARAM}" $1 | fido2-cred -V | tee "${ES256_CRED_R}" + +PIN1="$(dd if=/dev/urandom | tr -cd '[:print:]' | fold -w50 | head -1)" +PIN2="$(dd if=/dev/urandom | tr -cd '[:print:]' | fold -w50 | head -1)" + +echo "Setting ${PIN1} as the PIN..." +echo -e "${PIN1}\n${PIN1}" | setsid -w fido2-token -S $1 +echo "Changing PIN from ${PIN1} to ${PIN2}..." +echo -e "${PIN1}\n${PIN2}\n${PIN2}" | setsid -w fido2-token -C $1 +echo "" + +echo "Testing non-resident ES256 credential..." +echo "Getting assertion without user presence verification..." +dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 > "${ASSERT_PARAM}" +echo "Boring Relying Party" >> "${ASSERT_PARAM}" +head -1 "${ES256_CRED}" >> "${ASSERT_PARAM}" +tail -n +2 "${ES256_CRED}" > "${ASSERT_PUBKEY}" +echo "Assertion parameters:" +cat "${ASSERT_PARAM}" +fido2-assert -G -i "${ASSERT_PARAM}" $1 | fido2-assert -V "${ASSERT_PUBKEY}" +echo "Checking that the user presence bit is observed..." +! fido2-assert -G -i "${ASSERT_PARAM}" $1 | fido2-assert -V -p "${ASSERT_PUBKEY}" +echo "Checking that the user verification bit is observed..." +! fido2-assert -G -i "${ASSERT_PARAM}" $1 | fido2-assert -V -v "${ASSERT_PUBKEY}" +echo "Getting assertion _with_ user presence verification... (tap to continue!)" +fido2-assert -G -p -i "${ASSERT_PARAM}" $1 | fido2-assert -V -p "${ASSERT_PUBKEY}" +echo "Getting assertion _with_ user verification..." +echo -e "${PIN2}\n" | setsid -w fido2-assert -G -v -i "${ASSERT_PARAM}" $1 | \ + fido2-assert -V -v "${ASSERT_PUBKEY}" +echo "" + +echo "Testing resident ES256 credential..." +echo "Getting assertion without user presence verification..." +dd if=/dev/urandom bs=1 count=32 2>/dev/null | base64 > "${ASSERT_PARAM}" +echo "Boring Relying Party" >> "${ASSERT_PARAM}" +tail -n +2 "${ES256_CRED_R}" > "${ASSERT_PUBKEY}" +echo "Assertion parameters:" +cat "${ASSERT_PARAM}" +fido2-assert -G -r -i "${ASSERT_PARAM}" $1 | fido2-assert -V "${ASSERT_PUBKEY}" +echo "Checking that the user presence bit is observed..." +! fido2-assert -G -r -i "${ASSERT_PARAM}" $1 | fido2-assert -V -p "${ASSERT_PUBKEY}" +echo "Checking that the user verification bit is observed..." +! fido2-assert -G -r -i "${ASSERT_PARAM}" $1 | fido2-assert -V -v "${ASSERT_PUBKEY}" +echo "Getting assertion _with_ user presence verification... (tap to continue!)" +fido2-assert -G -r -p -i "${ASSERT_PARAM}" $1 | fido2-assert -V -p "${ASSERT_PUBKEY}" +echo "Getting assertion _with_ user verification..." +echo -e "${PIN2}\n" | setsid -w fido2-assert -G -v -r -i "${ASSERT_PARAM}" $1 | \ + fido2-assert -V -v "${ASSERT_PUBKEY}" +echo "" diff --git a/tools/token.c b/tools/token.c new file mode 100644 index 0000000..b149208 --- /dev/null +++ b/tools/token.c @@ -0,0 +1,364 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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_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_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 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 maximum message size */ + print_maxmsgsiz(fido_cbor_info_maxmsgsiz(ci)); + + /* print supported pin protocols */ + print_byte_array("pin protocols", fido_cbor_info_protocols_ptr(ci), + fido_cbor_info_protocols_len(ci)); + + if ((r = fido_dev_get_retry_count(dev, &retrycnt)) != FIDO_OK) + printf("pin retries: undefined\n"); + else + printf("pin retries: %d\n", retrycnt); + + 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_set(int argc, char **argv, char *path) +{ + char *id = NULL; + char *name = NULL; + int ch; + int enroll = 0; + + optind = 1; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'e': + enroll = 1; + break; + case 'i': + id = optarg; + break; + case 'n': + name = optarg; + break; + default: + break; /* ignore */ + } + } + + if (enroll) { + if (id && name) + return (bio_set_name(path, id, name)); + if (!id && !name) + return (bio_enroll(path)); + usage(); + } + + 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 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 'e': + enrolls = 1; + break; + case 'k': + keys = 1; + rp_id = optarg; + break; + case 'r': + rplist = 1; + break; + default: + break; /* ignore */ + } + } + + if (enrolls) + return (bio_list(path)); + if (keys) + return (credman_list_rk(path, rp_id)); + if (rplist) + return (credman_list_rp(path)); + + 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; + fido_dev_t *dev = NULL; + int ch; + int enroll = 0; + + optind = 1; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'e': + enroll = 1; + break; + case 'i': + id = optarg; + break; + default: + break; /* ignore */ + } + } + + if (path == NULL || id == NULL) + usage(); + + dev = open_dev(path); + + if (id && !enroll) + return (credman_delete_rk(dev, path, id)); + + return (bio_delete(dev, path, id)); +} diff --git a/tools/util.c b/tools/util.c new file mode 100644 index 0000000..de70388 --- /dev/null +++ b/tools/util.c @@ -0,0 +1,364 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#ifdef _MSC_VER +#include "../openbsd-compat/posix_win.h" +#endif + +#include "extern.h" + +void +read_pin(const char *path, char *buf, size_t len) +{ + char prompt[1024]; + int r; + + r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", path); + if (r < 0 || (size_t)r >= sizeof(prompt)) + errx(1, "snprintf"); + if (!readpassphrase(prompt, buf, len, RPP_ECHO_OFF)) + errx(1, "readpassphrase"); +} + +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); +} + +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); +} + +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_ec_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); +} + +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); + + if (type == COSE_ES256) { + write_ec_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + } else if (type == COSE_RS256) { + write_rsa_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + } else if (type == COSE_EDDSA) { + write_eddsa_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + } else { + errx(1, "print_cred: unknown type"); + } + + free(id); +} -- cgit v1.2.3