summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:45:25 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:45:25 +0000
commit814d128d1c52fe82be73ecff5b7472378041313f (patch)
tree581db0c07936d6d608e8c2e72d4903df306dd589 /src
parentInitial commit. (diff)
downloadlibfido2-814d128d1c52fe82be73ecff5b7472378041313f.tar.xz
libfido2-814d128d1c52fe82be73ecff5b7472378041313f.zip
Adding upstream version 1.14.0.upstream/1.14.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt158
-rw-r--r--src/aes256.c216
-rw-r--r--src/assert.c1170
-rw-r--r--src/authkey.c107
-rw-r--r--src/bio.c894
-rw-r--r--src/blob.c134
-rw-r--r--src/blob.h42
-rw-r--r--src/buf.c34
-rw-r--r--src/cbor.c1705
-rw-r--r--src/compress.c168
-rw-r--r--src/config.c235
-rw-r--r--src/cred.c1230
-rw-r--r--src/credman.c825
-rw-r--r--src/dev.c601
-rwxr-xr-xsrc/diff_exports.sh27
-rw-r--r--src/ecdh.c208
-rw-r--r--src/eddsa.c232
-rw-r--r--src/err.c137
-rw-r--r--src/es256.c541
-rw-r--r--src/es384.c296
-rw-r--r--src/export.gnu268
-rw-r--r--src/export.llvm263
-rw-r--r--src/export.msvc264
-rw-r--r--src/extern.h276
-rw-r--r--src/fallthrough.h21
-rw-r--r--src/fido.h278
-rw-r--r--src/fido/bio.h133
-rw-r--r--src/fido/config.h58
-rw-r--r--src/fido/credman.h113
-rw-r--r--src/fido/eddsa.h71
-rw-r--r--src/fido/err.h106
-rw-r--r--src/fido/es256.h71
-rw-r--r--src/fido/es384.h59
-rw-r--r--src/fido/param.h160
-rw-r--r--src/fido/rs256.h59
-rw-r--r--src/fido/types.h337
-rw-r--r--src/hid.c222
-rw-r--r--src/hid_freebsd.c337
-rw-r--r--src/hid_hidapi.c269
-rw-r--r--src/hid_linux.c391
-rw-r--r--src/hid_netbsd.c339
-rw-r--r--src/hid_openbsd.c280
-rw-r--r--src/hid_osx.c597
-rw-r--r--src/hid_unix.c76
-rw-r--r--src/hid_win.c571
-rw-r--r--src/info.c647
-rw-r--r--src/io.c356
-rw-r--r--src/iso7816.c65
-rw-r--r--src/iso7816.h49
-rw-r--r--src/largeblob.c902
-rw-r--r--src/libfido2.pc.in12
-rw-r--r--src/log.c122
-rw-r--r--src/netlink.c785
-rw-r--r--src/netlink.h45
-rw-r--r--src/nfc.c350
-rw-r--r--src/nfc_linux.c356
-rw-r--r--src/packed.h23
-rw-r--r--src/pcsc.c394
-rw-r--r--src/pin.c723
-rw-r--r--src/random.c83
-rw-r--r--src/reset.c46
-rw-r--r--src/rs1.c100
-rw-r--r--src/rs256.c316
-rw-r--r--src/time.c75
-rw-r--r--src/touch.c109
-rw-r--r--src/tpm.c391
-rw-r--r--src/types.c91
-rw-r--r--src/u2f.c959
-rw-r--r--src/util.c31
-rw-r--r--src/webauthn.h1149
-rw-r--r--src/winhello.c1075
71 files changed, 23833 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..73493b1
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,158 @@
+# 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
+
+add_definitions(-D_FIDO_INTERNAL)
+
+list(APPEND FIDO_SOURCES
+ aes256.c
+ assert.c
+ authkey.c
+ bio.c
+ blob.c
+ buf.c
+ cbor.c
+ compress.c
+ config.c
+ cred.c
+ credman.c
+ dev.c
+ ecdh.c
+ eddsa.c
+ err.c
+ es256.c
+ es384.c
+ hid.c
+ info.c
+ io.c
+ iso7816.c
+ largeblob.c
+ log.c
+ pin.c
+ random.c
+ reset.c
+ rs1.c
+ rs256.c
+ time.c
+ touch.c
+ tpm.c
+ types.c
+ u2f.c
+ util.c
+)
+
+if(FUZZ)
+ list(APPEND FIDO_SOURCES ../fuzz/clock.c)
+ list(APPEND FIDO_SOURCES ../fuzz/pcsc.c)
+ list(APPEND FIDO_SOURCES ../fuzz/prng.c)
+ list(APPEND FIDO_SOURCES ../fuzz/udev.c)
+ list(APPEND FIDO_SOURCES ../fuzz/uniform_random.c)
+ list(APPEND FIDO_SOURCES ../fuzz/wrap.c)
+endif()
+
+if(NFC_LINUX)
+ list(APPEND FIDO_SOURCES netlink.c nfc.c nfc_linux.c)
+endif()
+
+if(USE_PCSC)
+ list(APPEND FIDO_SOURCES nfc.c pcsc.c)
+endif()
+
+if(USE_HIDAPI)
+ list(APPEND FIDO_SOURCES hid_hidapi.c)
+ if(NOT WIN32 AND NOT APPLE)
+ list(APPEND FIDO_SOURCES hid_unix.c)
+ endif()
+elseif(WIN32)
+ list(APPEND FIDO_SOURCES hid_win.c)
+ if(USE_WINHELLO)
+ list(APPEND FIDO_SOURCES winhello.c)
+ endif()
+elseif(APPLE)
+ list(APPEND FIDO_SOURCES hid_osx.c)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ list(APPEND FIDO_SOURCES hid_linux.c hid_unix.c)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
+ list(APPEND FIDO_SOURCES hid_netbsd.c hid_unix.c)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
+ list(APPEND FIDO_SOURCES hid_openbsd.c hid_unix.c)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
+ CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD")
+ list(APPEND FIDO_SOURCES hid_freebsd.c hid_unix.c)
+else()
+ message(FATAL_ERROR "please define a hid backend for your platform")
+endif()
+
+if(NOT MSVC)
+ set_source_files_properties(${FIDO_SOURCES}
+ PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS}")
+endif()
+
+list(APPEND COMPAT_SOURCES
+ ../openbsd-compat/bsd-asprintf.c
+ ../openbsd-compat/bsd-getpagesize.c
+ ../openbsd-compat/clock_gettime.c
+ ../openbsd-compat/endian_win32.c
+ ../openbsd-compat/explicit_bzero.c
+ ../openbsd-compat/explicit_bzero_win32.c
+ ../openbsd-compat/freezero.c
+ ../openbsd-compat/recallocarray.c
+ ../openbsd-compat/strlcat.c
+ ../openbsd-compat/timingsafe_bcmp.c
+)
+
+if(WIN32)
+ list(APPEND BASE_LIBRARIES wsock32 ws2_32 bcrypt setupapi hid)
+ if(USE_PCSC)
+ list(APPEND BASE_LIBRARIES winscard)
+ endif()
+elseif(APPLE)
+ list(APPEND BASE_LIBRARIES "-framework CoreFoundation"
+ "-framework IOKit")
+ if(USE_PCSC)
+ list(APPEND BASE_LIBRARIES "-framework PCSC")
+ endif()
+endif()
+
+list(APPEND TARGET_LIBRARIES
+ ${CBOR_LIBRARIES}
+ ${CRYPTO_LIBRARIES}
+ ${UDEV_LIBRARIES}
+ ${BASE_LIBRARIES}
+ ${HIDAPI_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+ ${PCSC_LIBRARIES}
+)
+
+# static library
+if(BUILD_STATIC_LIBS)
+ add_library(fido2 STATIC ${FIDO_SOURCES} ${COMPAT_SOURCES})
+ if(WIN32 AND NOT MINGW)
+ set_target_properties(fido2 PROPERTIES OUTPUT_NAME fido2_static)
+ endif()
+ target_link_libraries(fido2 ${TARGET_LIBRARIES})
+ install(TARGETS fido2 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+endif()
+
+# dynamic library
+if(BUILD_SHARED_LIBS)
+ add_library(fido2_shared SHARED ${FIDO_SOURCES} ${COMPAT_SOURCES})
+ set_target_properties(fido2_shared PROPERTIES OUTPUT_NAME fido2
+ VERSION ${FIDO_VERSION} SOVERSION ${FIDO_MAJOR})
+ target_link_libraries(fido2_shared ${TARGET_LIBRARIES})
+ install(TARGETS fido2_shared
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+endif()
+
+install(FILES fido.h DESTINATION include)
+install(DIRECTORY fido DESTINATION include)
+
+if(NOT MSVC)
+ configure_file(libfido2.pc.in libfido2.pc @ONLY)
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libfido2.pc"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+endif()
diff --git a/src/aes256.c b/src/aes256.c
new file mode 100644
index 0000000..dcf716d
--- /dev/null
+++ b/src/aes256.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2021 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"
+
+static int
+aes256_cbc(const fido_blob_t *key, const u_char *iv, const fido_blob_t *in,
+ fido_blob_t *out, int encrypt)
+{
+ EVP_CIPHER_CTX *ctx = NULL;
+ const EVP_CIPHER *cipher;
+ int ok = -1;
+
+ memset(out, 0, sizeof(*out));
+
+ if (key->len != 32) {
+ fido_log_debug("%s: invalid key len %zu", __func__, key->len);
+ goto fail;
+ }
+ if (in->len > UINT_MAX || in->len % 16 || in->len == 0) {
+ fido_log_debug("%s: invalid input len %zu", __func__, in->len);
+ goto fail;
+ }
+ out->len = in->len;
+ if ((out->ptr = calloc(1, out->len)) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
+ (cipher = EVP_aes_256_cbc()) == NULL) {
+ fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__);
+ goto fail;
+ }
+ if (EVP_CipherInit(ctx, cipher, key->ptr, iv, encrypt) == 0 ||
+ EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)out->len) < 0) {
+ fido_log_debug("%s: EVP_Cipher", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (ctx != NULL)
+ EVP_CIPHER_CTX_free(ctx);
+ if (ok < 0)
+ fido_blob_reset(out);
+
+ return ok;
+}
+
+static int
+aes256_cbc_proto1(const fido_blob_t *key, const fido_blob_t *in,
+ fido_blob_t *out, int encrypt)
+{
+ u_char iv[16];
+
+ memset(&iv, 0, sizeof(iv));
+
+ return aes256_cbc(key, iv, in, out, encrypt);
+}
+
+static int
+aes256_cbc_fips(const fido_blob_t *secret, const fido_blob_t *in,
+ fido_blob_t *out, int encrypt)
+{
+ fido_blob_t key, cin, cout;
+ u_char iv[16];
+
+ memset(out, 0, sizeof(*out));
+
+ if (secret->len != 64) {
+ fido_log_debug("%s: invalid secret len %zu", __func__,
+ secret->len);
+ return -1;
+ }
+ if (in->len < sizeof(iv)) {
+ fido_log_debug("%s: invalid input len %zu", __func__, in->len);
+ return -1;
+ }
+ if (encrypt) {
+ if (fido_get_random(iv, sizeof(iv)) < 0) {
+ fido_log_debug("%s: fido_get_random", __func__);
+ return -1;
+ }
+ cin = *in;
+ } else {
+ memcpy(iv, in->ptr, sizeof(iv));
+ cin.ptr = in->ptr + sizeof(iv);
+ cin.len = in->len - sizeof(iv);
+ }
+ key.ptr = secret->ptr + 32;
+ key.len = secret->len - 32;
+ if (aes256_cbc(&key, iv, &cin, &cout, encrypt) < 0)
+ return -1;
+ if (encrypt) {
+ if (cout.len > SIZE_MAX - sizeof(iv) ||
+ (out->ptr = calloc(1, sizeof(iv) + cout.len)) == NULL) {
+ fido_blob_reset(&cout);
+ return -1;
+ }
+ out->len = sizeof(iv) + cout.len;
+ memcpy(out->ptr, iv, sizeof(iv));
+ memcpy(out->ptr + sizeof(iv), cout.ptr, cout.len);
+ fido_blob_reset(&cout);
+ } else
+ *out = cout;
+
+ return 0;
+}
+
+static int
+aes256_gcm(const fido_blob_t *key, const fido_blob_t *nonce,
+ const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out,
+ int encrypt)
+{
+ EVP_CIPHER_CTX *ctx = NULL;
+ const EVP_CIPHER *cipher;
+ size_t textlen;
+ int ok = -1;
+
+ memset(out, 0, sizeof(*out));
+
+ if (nonce->len != 12 || key->len != 32 || aad->len > UINT_MAX) {
+ fido_log_debug("%s: invalid params %zu, %zu, %zu", __func__,
+ nonce->len, key->len, aad->len);
+ goto fail;
+ }
+ if (in->len > UINT_MAX || in->len > SIZE_MAX - 16 || in->len < 16) {
+ fido_log_debug("%s: invalid input len %zu", __func__, in->len);
+ goto fail;
+ }
+ /* add tag to (on encrypt) or trim tag from the output (on decrypt) */
+ out->len = encrypt ? in->len + 16 : in->len - 16;
+ if ((out->ptr = calloc(1, out->len)) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
+ (cipher = EVP_aes_256_gcm()) == NULL) {
+ fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__);
+ goto fail;
+ }
+ if (EVP_CipherInit(ctx, cipher, key->ptr, nonce->ptr, encrypt) == 0) {
+ fido_log_debug("%s: EVP_CipherInit", __func__);
+ goto fail;
+ }
+
+ if (encrypt)
+ textlen = in->len;
+ else {
+ textlen = in->len - 16;
+ /* point openssl at the mac tag */
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
+ in->ptr + in->len - 16) == 0) {
+ fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__);
+ goto fail;
+ }
+ }
+ /* the last EVP_Cipher() will either compute or verify the mac tag */
+ if (EVP_Cipher(ctx, NULL, aad->ptr, (u_int)aad->len) < 0 ||
+ EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)textlen) < 0 ||
+ EVP_Cipher(ctx, NULL, NULL, 0) < 0) {
+ fido_log_debug("%s: EVP_Cipher", __func__);
+ goto fail;
+ }
+ if (encrypt) {
+ /* append the mac tag */
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16,
+ out->ptr + out->len - 16) == 0) {
+ fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__);
+ goto fail;
+ }
+ }
+
+ ok = 0;
+fail:
+ if (ctx != NULL)
+ EVP_CIPHER_CTX_free(ctx);
+ if (ok < 0)
+ fido_blob_reset(out);
+
+ return ok;
+}
+
+int
+aes256_cbc_enc(const fido_dev_t *dev, const fido_blob_t *secret,
+ const fido_blob_t *in, fido_blob_t *out)
+{
+ return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret,
+ in, out, 1) : aes256_cbc_proto1(secret, in, out, 1);
+}
+
+int
+aes256_cbc_dec(const fido_dev_t *dev, const fido_blob_t *secret,
+ const fido_blob_t *in, fido_blob_t *out)
+{
+ return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret,
+ in, out, 0) : aes256_cbc_proto1(secret, in, out, 0);
+}
+
+int
+aes256_gcm_enc(const fido_blob_t *key, const fido_blob_t *nonce,
+ const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out)
+{
+ return aes256_gcm(key, nonce, aad, in, out, 1);
+}
+
+int
+aes256_gcm_dec(const fido_blob_t *key, const fido_blob_t *nonce,
+ const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out)
+{
+ return aes256_gcm(key, nonce, aad, in, out, 0);
+}
diff --git a/src/assert.c b/src/assert.c
new file mode 100644
index 0000000..39d63bc
--- /dev/null
+++ b/src/assert.c
@@ -0,0 +1,1170 @@
+/*
+ * 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 <openssl/sha.h>
+
+#include "fido.h"
+#include "fido/es256.h"
+#include "fido/rs256.h"
+#include "fido/eddsa.h"
+
+static int
+adjust_assert_count(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_assert_t *assert = arg;
+ uint64_t n;
+
+ /* numberOfCredentials; see section 6.2 */
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 5) {
+ fido_log_debug("%s: cbor_type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+
+ if (assert->stmt_len != 0 || assert->stmt_cnt != 1 ||
+ (size_t)n < assert->stmt_cnt) {
+ fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu, n=%zu",
+ __func__, assert->stmt_len, assert->stmt_cnt, (size_t)n);
+ return (-1);
+ }
+
+ if (fido_assert_set_count(assert, (size_t)n) != FIDO_OK) {
+ fido_log_debug("%s: fido_assert_set_count", __func__);
+ return (-1);
+ }
+
+ assert->stmt_len = 0; /* XXX */
+
+ return (0);
+}
+
+static int
+parse_assert_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_assert_stmt *stmt = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* credential id */
+ return (cbor_decode_cred_id(val, &stmt->id));
+ case 2: /* authdata */
+ if (fido_blob_decode(val, &stmt->authdata_raw) < 0) {
+ fido_log_debug("%s: fido_blob_decode", __func__);
+ return (-1);
+ }
+ return (cbor_decode_assert_authdata(val, &stmt->authdata_cbor,
+ &stmt->authdata, &stmt->authdata_ext));
+ case 3: /* signature */
+ return (fido_blob_decode(val, &stmt->sig));
+ case 4: /* user attributes */
+ return (cbor_decode_user(val, &stmt->user));
+ case 7: /* large blob key */
+ return (fido_blob_decode(val, &stmt->largeblob_key));
+ default: /* ignore */
+ fido_log_debug("%s: cbor type", __func__);
+ return (0);
+ }
+}
+
+static int
+fido_dev_get_assert_tx(fido_dev_t *dev, fido_assert_t *assert,
+ const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int *ms)
+{
+ fido_blob_t f;
+ fido_opt_t uv = assert->uv;
+ cbor_item_t *argv[7];
+ const uint8_t cmd = CTAP_CBOR_ASSERT;
+ int r;
+
+ memset(argv, 0, sizeof(argv));
+ memset(&f, 0, sizeof(f));
+
+ /* do we have everything we need? */
+ if (assert->rp_id == NULL || assert->cdh.ptr == NULL) {
+ fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__,
+ (void *)assert->rp_id, (void *)assert->cdh.ptr);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_build_string(assert->rp_id)) == NULL ||
+ (argv[1] = fido_blob_encode(&assert->cdh)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* allowed credentials */
+ if (assert->allow_list.len) {
+ const fido_blob_array_t *cl = &assert->allow_list;
+ if ((argv[2] = cbor_encode_pubkey_list(cl)) == NULL) {
+ fido_log_debug("%s: cbor_encode_pubkey_list", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ }
+
+ if (assert->ext.mask)
+ if ((argv[3] = cbor_encode_assert_ext(dev, &assert->ext, ecdh,
+ pk)) == NULL) {
+ fido_log_debug("%s: cbor_encode_assert_ext", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* user verification */
+ if (pin != NULL || (uv == FIDO_OPT_TRUE &&
+ fido_dev_supports_permissions(dev))) {
+ if ((r = cbor_add_uv_params(dev, cmd, &assert->cdh, pk, ecdh,
+ pin, assert->rp_id, &argv[5], &argv[6], ms)) != FIDO_OK) {
+ fido_log_debug("%s: cbor_add_uv_params", __func__);
+ goto fail;
+ }
+ uv = FIDO_OPT_OMIT;
+ }
+
+ /* options */
+ if (assert->up != FIDO_OPT_OMIT || uv != FIDO_OPT_OMIT)
+ if ((argv[4] = cbor_encode_assert_opt(assert->up, uv)) == NULL) {
+ fido_log_debug("%s: cbor_encode_assert_opt", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* frame and transmit */
+ if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_get_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ fido_assert_reset_rx(assert);
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ /* start with room for a single assertion */
+ if ((assert->stmt = calloc(1, sizeof(fido_assert_stmt))) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ assert->stmt_len = 0;
+ assert->stmt_cnt = 1;
+
+ /* adjust as needed */
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, assert,
+ adjust_assert_count)) != FIDO_OK) {
+ fido_log_debug("%s: adjust_assert_count", __func__);
+ goto out;
+ }
+
+ /* parse the first assertion */
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, &assert->stmt[0],
+ parse_assert_reply)) != FIDO_OK) {
+ fido_log_debug("%s: parse_assert_reply", __func__);
+ goto out;
+ }
+ assert->stmt_len = 1;
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+fido_get_next_assert_tx(fido_dev_t *dev, int *ms)
+{
+ const unsigned char cbor[] = { CTAP_CBOR_NEXT_ASSERT };
+
+ if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ return (FIDO_ERR_TX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_get_next_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ /* sanity check */
+ if (assert->stmt_len >= assert->stmt_cnt) {
+ fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu", __func__,
+ assert->stmt_len, assert->stmt_cnt);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen,
+ &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) {
+ fido_log_debug("%s: parse_assert_reply", __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+fido_dev_get_assert_wait(fido_dev_t *dev, fido_assert_t *assert,
+ const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int *ms)
+{
+ int r;
+
+ if ((r = fido_dev_get_assert_tx(dev, assert, pk, ecdh, pin,
+ ms)) != FIDO_OK ||
+ (r = fido_dev_get_assert_rx(dev, assert, ms)) != FIDO_OK)
+ return (r);
+
+ while (assert->stmt_len < assert->stmt_cnt) {
+ if ((r = fido_get_next_assert_tx(dev, ms)) != FIDO_OK ||
+ (r = fido_get_next_assert_rx(dev, assert, ms)) != FIDO_OK)
+ return (r);
+ assert->stmt_len++;
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+decrypt_hmac_secrets(const fido_dev_t *dev, fido_assert_t *assert,
+ const fido_blob_t *key)
+{
+ for (size_t i = 0; i < assert->stmt_cnt; i++) {
+ fido_assert_stmt *stmt = &assert->stmt[i];
+ if (stmt->authdata_ext.hmac_secret_enc.ptr != NULL) {
+ if (aes256_cbc_dec(dev, key,
+ &stmt->authdata_ext.hmac_secret_enc,
+ &stmt->hmac_secret) < 0) {
+ fido_log_debug("%s: aes256_cbc_dec %zu",
+ __func__, i);
+ return (-1);
+ }
+ }
+ }
+
+ return (0);
+}
+
+int
+fido_dev_get_assert(fido_dev_t *dev, fido_assert_t *assert, const char *pin)
+{
+ fido_blob_t *ecdh = NULL;
+ es256_pk_t *pk = NULL;
+ int ms = dev->timeout_ms;
+ int r;
+
+#ifdef USE_WINHELLO
+ if (dev->flags & FIDO_DEV_WINHELLO)
+ return (fido_winhello_get_assert(dev, assert, pin, ms));
+#endif
+
+ if (assert->rp_id == NULL || assert->cdh.ptr == NULL) {
+ fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__,
+ (void *)assert->rp_id, (void *)assert->cdh.ptr);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (fido_dev_is_fido2(dev) == false) {
+ if (pin != NULL || assert->ext.mask != 0)
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ return (u2f_authenticate(dev, assert, &ms));
+ }
+
+ if (pin != NULL || (assert->uv == FIDO_OPT_TRUE &&
+ fido_dev_supports_permissions(dev)) ||
+ (assert->ext.mask & FIDO_EXT_HMAC_SECRET)) {
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ }
+
+ r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, &ms);
+ if (r == FIDO_OK && (assert->ext.mask & FIDO_EXT_HMAC_SECRET))
+ if (decrypt_hmac_secrets(dev, assert, ecdh) < 0) {
+ fido_log_debug("%s: decrypt_hmac_secrets", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+
+ return (r);
+}
+
+int
+fido_check_flags(uint8_t flags, fido_opt_t up, fido_opt_t uv)
+{
+ fido_log_debug("%s: flags=%02x", __func__, flags);
+ fido_log_debug("%s: up=%d, uv=%d", __func__, up, uv);
+
+ if (up == FIDO_OPT_TRUE &&
+ (flags & CTAP_AUTHDATA_USER_PRESENT) == 0) {
+ fido_log_debug("%s: CTAP_AUTHDATA_USER_PRESENT", __func__);
+ return (-1); /* user not present */
+ }
+
+ if (uv == FIDO_OPT_TRUE &&
+ (flags & CTAP_AUTHDATA_USER_VERIFIED) == 0) {
+ fido_log_debug("%s: CTAP_AUTHDATA_USER_VERIFIED", __func__);
+ return (-1); /* user not verified */
+ }
+
+ return (0);
+}
+
+static int
+check_extensions(int authdata_ext, int ext)
+{
+ /* XXX: largeBlobKey is not part of extensions map */
+ ext &= ~FIDO_EXT_LARGEBLOB_KEY;
+ if (authdata_ext != ext) {
+ fido_log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__,
+ authdata_ext, ext);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_es256_hash(fido_blob_t *dgst, const fido_blob_t *clientdata,
+ const fido_blob_t *authdata)
+{
+ const EVP_MD *md;
+ EVP_MD_CTX *ctx = NULL;
+
+ if (dgst->len < SHA256_DIGEST_LENGTH ||
+ (md = EVP_sha256()) == NULL ||
+ (ctx = EVP_MD_CTX_new()) == NULL ||
+ EVP_DigestInit_ex(ctx, md, NULL) != 1 ||
+ EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 ||
+ EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 ||
+ EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) {
+ EVP_MD_CTX_free(ctx);
+ return (-1);
+ }
+ dgst->len = SHA256_DIGEST_LENGTH;
+
+ EVP_MD_CTX_free(ctx);
+
+ return (0);
+}
+
+static int
+get_es384_hash(fido_blob_t *dgst, const fido_blob_t *clientdata,
+ const fido_blob_t *authdata)
+{
+ const EVP_MD *md;
+ EVP_MD_CTX *ctx = NULL;
+
+ if (dgst->len < SHA384_DIGEST_LENGTH ||
+ (md = EVP_sha384()) == NULL ||
+ (ctx = EVP_MD_CTX_new()) == NULL ||
+ EVP_DigestInit_ex(ctx, md, NULL) != 1 ||
+ EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 ||
+ EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 ||
+ EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) {
+ EVP_MD_CTX_free(ctx);
+ return (-1);
+ }
+ dgst->len = SHA384_DIGEST_LENGTH;
+
+ EVP_MD_CTX_free(ctx);
+
+ return (0);
+}
+
+static int
+get_eddsa_hash(fido_blob_t *dgst, const fido_blob_t *clientdata,
+ const fido_blob_t *authdata)
+{
+ if (SIZE_MAX - authdata->len < clientdata->len ||
+ dgst->len < authdata->len + clientdata->len)
+ return (-1);
+
+ memcpy(dgst->ptr, authdata->ptr, authdata->len);
+ memcpy(dgst->ptr + authdata->len, clientdata->ptr, clientdata->len);
+ dgst->len = authdata->len + clientdata->len;
+
+ return (0);
+}
+
+int
+fido_get_signed_hash(int cose_alg, fido_blob_t *dgst,
+ const fido_blob_t *clientdata, const fido_blob_t *authdata_cbor)
+{
+ cbor_item_t *item = NULL;
+ fido_blob_t authdata;
+ struct cbor_load_result cbor;
+ int ok = -1;
+
+ fido_log_debug("%s: cose_alg=%d", __func__, cose_alg);
+
+ if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len,
+ &cbor)) == NULL || cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ fido_log_debug("%s: authdata", __func__);
+ goto fail;
+ }
+ authdata.ptr = cbor_bytestring_handle(item);
+ authdata.len = cbor_bytestring_length(item);
+
+ switch (cose_alg) {
+ case COSE_ES256:
+ case COSE_RS256:
+ ok = get_es256_hash(dgst, clientdata, &authdata);
+ break;
+ case COSE_ES384:
+ ok = get_es384_hash(dgst, clientdata, &authdata);
+ break;
+ case COSE_EDDSA:
+ ok = get_eddsa_hash(dgst, clientdata, &authdata);
+ break;
+ default:
+ fido_log_debug("%s: unknown cose_alg", __func__);
+ break;
+ }
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+int
+fido_assert_verify(const fido_assert_t *assert, size_t idx, int cose_alg,
+ const void *pk)
+{
+ unsigned char buf[1024]; /* XXX */
+ fido_blob_t dgst;
+ const fido_assert_stmt *stmt = NULL;
+ int ok = -1;
+ int r;
+
+ dgst.ptr = buf;
+ dgst.len = sizeof(buf);
+
+ if (idx >= assert->stmt_len || pk == NULL) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ stmt = &assert->stmt[idx];
+
+ /* do we have everything we need? */
+ if (assert->cdh.ptr == NULL || assert->rp_id == NULL ||
+ stmt->authdata_cbor.ptr == NULL || stmt->sig.ptr == NULL) {
+ fido_log_debug("%s: cdh=%p, rp_id=%s, authdata=%p, sig=%p",
+ __func__, (void *)assert->cdh.ptr, assert->rp_id,
+ (void *)stmt->authdata_cbor.ptr, (void *)stmt->sig.ptr);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if (fido_check_flags(stmt->authdata.flags, assert->up,
+ assert->uv) < 0) {
+ fido_log_debug("%s: fido_check_flags", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_extensions(stmt->authdata_ext.mask, assert->ext.mask) < 0) {
+ fido_log_debug("%s: check_extensions", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (fido_check_rp_id(assert->rp_id, stmt->authdata.rp_id_hash) != 0) {
+ fido_log_debug("%s: fido_check_rp_id", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (fido_get_signed_hash(cose_alg, &dgst, &assert->cdh,
+ &stmt->authdata_cbor) < 0) {
+ fido_log_debug("%s: fido_get_signed_hash", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ switch (cose_alg) {
+ case COSE_ES256:
+ ok = es256_pk_verify_sig(&dgst, pk, &stmt->sig);
+ break;
+ case COSE_ES384:
+ ok = es384_pk_verify_sig(&dgst, pk, &stmt->sig);
+ break;
+ case COSE_RS256:
+ ok = rs256_pk_verify_sig(&dgst, pk, &stmt->sig);
+ break;
+ case COSE_EDDSA:
+ ok = eddsa_pk_verify_sig(&dgst, pk, &stmt->sig);
+ break;
+ default:
+ fido_log_debug("%s: unsupported cose_alg %d", __func__,
+ cose_alg);
+ r = FIDO_ERR_UNSUPPORTED_OPTION;
+ goto out;
+ }
+
+ if (ok < 0)
+ r = FIDO_ERR_INVALID_SIG;
+ else
+ r = FIDO_OK;
+out:
+ explicit_bzero(buf, sizeof(buf));
+
+ return (r);
+}
+
+int
+fido_assert_set_clientdata(fido_assert_t *assert, const unsigned char *data,
+ size_t data_len)
+{
+ if (!fido_blob_is_empty(&assert->cdh) ||
+ fido_blob_set(&assert->cd, data, data_len) < 0) {
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+ if (fido_sha256(&assert->cdh, data, data_len) < 0) {
+ fido_blob_reset(&assert->cd);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_clientdata_hash(fido_assert_t *assert,
+ const unsigned char *hash, size_t hash_len)
+{
+ if (!fido_blob_is_empty(&assert->cd) ||
+ fido_blob_set(&assert->cdh, hash, hash_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_hmac_salt(fido_assert_t *assert, const unsigned char *salt,
+ size_t salt_len)
+{
+ if ((salt_len != 32 && salt_len != 64) ||
+ fido_blob_set(&assert->ext.hmac_salt, salt, salt_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_hmac_secret(fido_assert_t *assert, size_t idx,
+ const unsigned char *secret, size_t secret_len)
+{
+ if (idx >= assert->stmt_len || (secret_len != 32 && secret_len != 64) ||
+ fido_blob_set(&assert->stmt[idx].hmac_secret, secret,
+ secret_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_rp(fido_assert_t *assert, const char *id)
+{
+ if (assert->rp_id != NULL) {
+ free(assert->rp_id);
+ assert->rp_id = NULL;
+ }
+
+ if (id == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((assert->rp_id = strdup(id)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+#ifdef USE_WINHELLO
+int
+fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id)
+{
+ if (assert->appid != NULL) {
+ free(assert->appid);
+ assert->appid = NULL;
+ }
+
+ if (id == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((assert->appid = strdup(id)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+#else
+int
+fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id)
+{
+ (void)assert;
+ (void)id;
+
+ return (FIDO_ERR_UNSUPPORTED_EXTENSION);
+}
+#endif /* USE_WINHELLO */
+
+int
+fido_assert_allow_cred(fido_assert_t *assert, const unsigned char *ptr,
+ size_t len)
+{
+ fido_blob_t id;
+ fido_blob_t *list_ptr;
+ int r;
+
+ memset(&id, 0, sizeof(id));
+
+ if (assert->allow_list.len == SIZE_MAX) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if (fido_blob_set(&id, ptr, len) < 0 || (list_ptr =
+ recallocarray(assert->allow_list.ptr, assert->allow_list.len,
+ assert->allow_list.len + 1, sizeof(fido_blob_t))) == NULL) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ list_ptr[assert->allow_list.len++] = id;
+ assert->allow_list.ptr = list_ptr;
+
+ return (FIDO_OK);
+fail:
+ free(id.ptr);
+
+ return (r);
+}
+
+int
+fido_assert_empty_allow_list(fido_assert_t *assert)
+{
+ fido_free_blob_array(&assert->allow_list);
+ memset(&assert->allow_list, 0, sizeof(assert->allow_list));
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_extensions(fido_assert_t *assert, int ext)
+{
+ if (ext == 0)
+ assert->ext.mask = 0;
+ else {
+ if ((ext & FIDO_EXT_ASSERT_MASK) != ext)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ assert->ext.mask |= ext;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_options(fido_assert_t *assert, bool up, bool uv)
+{
+ assert->up = up ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+ assert->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_up(fido_assert_t *assert, fido_opt_t up)
+{
+ assert->up = up;
+
+ return (FIDO_OK);
+}
+
+int
+fido_assert_set_uv(fido_assert_t *assert, fido_opt_t uv)
+{
+ assert->uv = uv;
+
+ return (FIDO_OK);
+}
+
+const unsigned char *
+fido_assert_clientdata_hash_ptr(const fido_assert_t *assert)
+{
+ return (assert->cdh.ptr);
+}
+
+size_t
+fido_assert_clientdata_hash_len(const fido_assert_t *assert)
+{
+ return (assert->cdh.len);
+}
+
+fido_assert_t *
+fido_assert_new(void)
+{
+ return (calloc(1, sizeof(fido_assert_t)));
+}
+
+void
+fido_assert_reset_tx(fido_assert_t *assert)
+{
+ free(assert->rp_id);
+ free(assert->appid);
+ fido_blob_reset(&assert->cd);
+ fido_blob_reset(&assert->cdh);
+ fido_blob_reset(&assert->ext.hmac_salt);
+ fido_assert_empty_allow_list(assert);
+ memset(&assert->ext, 0, sizeof(assert->ext));
+ assert->rp_id = NULL;
+ assert->appid = NULL;
+ assert->up = FIDO_OPT_OMIT;
+ assert->uv = FIDO_OPT_OMIT;
+}
+
+static void
+fido_assert_reset_extattr(fido_assert_extattr_t *ext)
+{
+ fido_blob_reset(&ext->hmac_secret_enc);
+ fido_blob_reset(&ext->blob);
+ memset(ext, 0, sizeof(*ext));
+}
+
+void
+fido_assert_reset_rx(fido_assert_t *assert)
+{
+ for (size_t i = 0; i < assert->stmt_cnt; i++) {
+ free(assert->stmt[i].user.icon);
+ free(assert->stmt[i].user.name);
+ free(assert->stmt[i].user.display_name);
+ fido_blob_reset(&assert->stmt[i].user.id);
+ fido_blob_reset(&assert->stmt[i].id);
+ fido_blob_reset(&assert->stmt[i].hmac_secret);
+ fido_blob_reset(&assert->stmt[i].authdata_cbor);
+ fido_blob_reset(&assert->stmt[i].authdata_raw);
+ fido_blob_reset(&assert->stmt[i].largeblob_key);
+ fido_blob_reset(&assert->stmt[i].sig);
+ fido_assert_reset_extattr(&assert->stmt[i].authdata_ext);
+ memset(&assert->stmt[i], 0, sizeof(assert->stmt[i]));
+ }
+ free(assert->stmt);
+ assert->stmt = NULL;
+ assert->stmt_len = 0;
+ assert->stmt_cnt = 0;
+}
+
+void
+fido_assert_free(fido_assert_t **assert_p)
+{
+ fido_assert_t *assert;
+
+ if (assert_p == NULL || (assert = *assert_p) == NULL)
+ return;
+ fido_assert_reset_tx(assert);
+ fido_assert_reset_rx(assert);
+ free(assert);
+ *assert_p = NULL;
+}
+
+size_t
+fido_assert_count(const fido_assert_t *assert)
+{
+ return (assert->stmt_len);
+}
+
+const char *
+fido_assert_rp_id(const fido_assert_t *assert)
+{
+ return (assert->rp_id);
+}
+
+uint8_t
+fido_assert_flags(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata.flags);
+}
+
+uint32_t
+fido_assert_sigcount(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata.sigcount);
+}
+
+const unsigned char *
+fido_assert_authdata_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].authdata_cbor.ptr);
+}
+
+size_t
+fido_assert_authdata_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata_cbor.len);
+}
+
+const unsigned char *
+fido_assert_authdata_raw_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].authdata_raw.ptr);
+}
+
+size_t
+fido_assert_authdata_raw_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata_raw.len);
+}
+
+const unsigned char *
+fido_assert_sig_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].sig.ptr);
+}
+
+size_t
+fido_assert_sig_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].sig.len);
+}
+
+const unsigned char *
+fido_assert_id_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].id.ptr);
+}
+
+size_t
+fido_assert_id_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].id.len);
+}
+
+const unsigned char *
+fido_assert_user_id_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.id.ptr);
+}
+
+size_t
+fido_assert_user_id_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].user.id.len);
+}
+
+const char *
+fido_assert_user_icon(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.icon);
+}
+
+const char *
+fido_assert_user_name(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.name);
+}
+
+const char *
+fido_assert_user_display_name(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].user.display_name);
+}
+
+const unsigned char *
+fido_assert_hmac_secret_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].hmac_secret.ptr);
+}
+
+size_t
+fido_assert_hmac_secret_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].hmac_secret.len);
+}
+
+const unsigned char *
+fido_assert_largeblob_key_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].largeblob_key.ptr);
+}
+
+size_t
+fido_assert_largeblob_key_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].largeblob_key.len);
+}
+
+const unsigned char *
+fido_assert_blob_ptr(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (NULL);
+
+ return (assert->stmt[idx].authdata_ext.blob.ptr);
+}
+
+size_t
+fido_assert_blob_len(const fido_assert_t *assert, size_t idx)
+{
+ if (idx >= assert->stmt_len)
+ return (0);
+
+ return (assert->stmt[idx].authdata_ext.blob.len);
+}
+
+static void
+fido_assert_clean_authdata(fido_assert_stmt *stmt)
+{
+ fido_blob_reset(&stmt->authdata_cbor);
+ fido_blob_reset(&stmt->authdata_raw);
+ fido_assert_reset_extattr(&stmt->authdata_ext);
+ memset(&stmt->authdata, 0, sizeof(stmt->authdata));
+}
+
+int
+fido_assert_set_authdata(fido_assert_t *assert, size_t idx,
+ const unsigned char *ptr, size_t len)
+{
+ cbor_item_t *item = NULL;
+ fido_assert_stmt *stmt = NULL;
+ struct cbor_load_result cbor;
+ int r;
+
+ if (idx >= assert->stmt_len || ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ stmt = &assert->stmt[idx];
+ fido_assert_clean_authdata(stmt);
+
+ if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if (fido_blob_decode(item, &stmt->authdata_raw) < 0) {
+ fido_log_debug("%s: fido_blob_decode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor,
+ &stmt->authdata, &stmt->authdata_ext) < 0) {
+ fido_log_debug("%s: cbor_decode_assert_authdata", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_assert_clean_authdata(stmt);
+
+ return (r);
+}
+
+int
+fido_assert_set_authdata_raw(fido_assert_t *assert, size_t idx,
+ const unsigned char *ptr, size_t len)
+{
+ cbor_item_t *item = NULL;
+ fido_assert_stmt *stmt = NULL;
+ int r;
+
+ if (idx >= assert->stmt_len || ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ stmt = &assert->stmt[idx];
+ fido_assert_clean_authdata(stmt);
+
+ if (fido_blob_set(&stmt->authdata_raw, ptr, len) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((item = cbor_build_bytestring(ptr, len)) == NULL) {
+ fido_log_debug("%s: cbor_build_bytestring", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor,
+ &stmt->authdata, &stmt->authdata_ext) < 0) {
+ fido_log_debug("%s: cbor_decode_assert_authdata", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_assert_clean_authdata(stmt);
+
+ return (r);
+}
+
+int
+fido_assert_set_sig(fido_assert_t *a, size_t idx, const unsigned char *ptr,
+ size_t len)
+{
+ if (idx >= a->stmt_len || ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if (fido_blob_set(&a->stmt[idx].sig, ptr, len) < 0)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+/* XXX shrinking leaks memory; fortunately that shouldn't happen */
+int
+fido_assert_set_count(fido_assert_t *assert, size_t n)
+{
+ void *new_stmt;
+
+#ifdef FIDO_FUZZ
+ if (n > UINT8_MAX) {
+ fido_log_debug("%s: n > UINT8_MAX", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+#endif
+
+ new_stmt = recallocarray(assert->stmt, assert->stmt_cnt, n,
+ sizeof(fido_assert_stmt));
+ if (new_stmt == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ assert->stmt = new_stmt;
+ assert->stmt_cnt = n;
+ assert->stmt_len = n;
+
+ return (FIDO_OK);
+}
diff --git a/src/authkey.c b/src/authkey.c
new file mode 100644
index 0000000..761562b
--- /dev/null
+++ b/src/authkey.c
@@ -0,0 +1,107 @@
+/*
+ * 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"
+
+static int
+parse_authkey(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ es256_pk_t *authkey = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 1) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ return (es256_pk_decode(val, authkey));
+}
+
+static int
+fido_dev_authkey_tx(fido_dev_t *dev, int *ms)
+{
+ fido_blob_t f;
+ cbor_item_t *argv[2];
+ int r;
+
+ fido_log_debug("%s: dev=%p", __func__, (void *)dev);
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ /* add command parameters */
+ if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL ||
+ (argv[1] = cbor_build_uint8(2)) == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* frame and transmit */
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv),
+ &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_authkey_rx(fido_dev_t *dev, es256_pk_t *authkey, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ fido_log_debug("%s: dev=%p, authkey=%p, ms=%d", __func__, (void *)dev,
+ (void *)authkey, *ms);
+
+ memset(authkey, 0, sizeof(*authkey));
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ r = cbor_parse_reply(msg, (size_t)msglen, authkey, parse_authkey);
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+fido_dev_authkey_wait(fido_dev_t *dev, es256_pk_t *authkey, int *ms)
+{
+ int r;
+
+ if ((r = fido_dev_authkey_tx(dev, ms)) != FIDO_OK ||
+ (r = fido_dev_authkey_rx(dev, authkey, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_authkey(fido_dev_t *dev, es256_pk_t *authkey, int *ms)
+{
+ return (fido_dev_authkey_wait(dev, authkey, ms));
+}
diff --git a/src/bio.c b/src/bio.c
new file mode 100644
index 0000000..57db85f
--- /dev/null
+++ b/src/bio.c
@@ -0,0 +1,894 @@
+/*
+ * Copyright (c) 2019-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/bio.h"
+#include "fido/es256.h"
+
+#define CMD_ENROLL_BEGIN 0x01
+#define CMD_ENROLL_NEXT 0x02
+#define CMD_ENROLL_CANCEL 0x03
+#define CMD_ENUM 0x04
+#define CMD_SET_NAME 0x05
+#define CMD_ENROLL_REMOVE 0x06
+#define CMD_GET_INFO 0x07
+
+static int
+bio_prepare_hmac(uint8_t cmd, cbor_item_t **argv, size_t argc,
+ cbor_item_t **param, fido_blob_t *hmac_data)
+{
+ const uint8_t prefix[2] = { 0x01 /* modality */, cmd };
+ int ok = -1;
+ size_t cbor_alloc_len;
+ size_t cbor_len;
+ unsigned char *cbor = NULL;
+
+ if (argv == NULL || param == NULL)
+ return (fido_blob_set(hmac_data, prefix, sizeof(prefix)));
+
+ if ((*param = cbor_flatten_vector(argv, argc)) == NULL) {
+ fido_log_debug("%s: cbor_flatten_vector", __func__);
+ goto fail;
+ }
+
+ if ((cbor_len = cbor_serialize_alloc(*param, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > SIZE_MAX - sizeof(prefix)) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ goto fail;
+ }
+
+ if ((hmac_data->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ goto fail;
+ }
+
+ memcpy(hmac_data->ptr, prefix, sizeof(prefix));
+ memcpy(hmac_data->ptr + sizeof(prefix), cbor, cbor_len);
+ hmac_data->len = cbor_len + sizeof(prefix);
+
+ ok = 0;
+fail:
+ free(cbor);
+
+ return (ok);
+}
+
+static int
+bio_tx(fido_dev_t *dev, uint8_t subcmd, cbor_item_t **sub_argv, size_t sub_argc,
+ const char *pin, const fido_blob_t *token, int *ms)
+{
+ cbor_item_t *argv[5];
+ es256_pk_t *pk = NULL;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t f;
+ fido_blob_t hmac;
+ const uint8_t cmd = CTAP_CBOR_BIO_ENROLL_PRE;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&f, 0, sizeof(f));
+ memset(&hmac, 0, sizeof(hmac));
+ memset(&argv, 0, sizeof(argv));
+
+ /* modality, subCommand */
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(subcmd)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ /* subParams */
+ if (pin || token) {
+ if (bio_prepare_hmac(subcmd, sub_argv, sub_argc, &argv[2],
+ &hmac) < 0) {
+ fido_log_debug("%s: bio_prepare_hmac", __func__);
+ goto fail;
+ }
+ }
+
+ /* pinProtocol, pinAuth */
+ if (pin) {
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin,
+ NULL, &argv[4], &argv[3], ms)) != FIDO_OK) {
+ fido_log_debug("%s: cbor_add_uv_params", __func__);
+ goto fail;
+ }
+ } else if (token) {
+ if ((argv[3] = cbor_encode_pin_opt(dev)) == NULL ||
+ (argv[4] = cbor_encode_pin_auth(dev, token, &hmac)) == NULL) {
+ fido_log_debug("%s: encode pin", __func__);
+ goto fail;
+ }
+ }
+
+ /* framing and transmission */
+ if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ free(f.ptr);
+ free(hmac.ptr);
+
+ return (r);
+}
+
+static void
+bio_reset_template(fido_bio_template_t *t)
+{
+ free(t->name);
+ t->name = NULL;
+ fido_blob_reset(&t->id);
+}
+
+static void
+bio_reset_template_array(fido_bio_template_array_t *ta)
+{
+ for (size_t i = 0; i < ta->n_alloc; i++)
+ bio_reset_template(&ta->ptr[i]);
+
+ free(ta->ptr);
+ ta->ptr = NULL;
+ memset(ta, 0, sizeof(*ta));
+}
+
+static int
+decode_template(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_bio_template_t *t = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* id */
+ return (fido_blob_decode(val, &t->id));
+ case 2: /* name */
+ return (cbor_string_copy(val, &t->name));
+ }
+
+ return (0); /* ignore */
+}
+
+static int
+decode_template_array(const cbor_item_t *item, void *arg)
+{
+ fido_bio_template_array_t *ta = arg;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (ta->n_rx >= ta->n_alloc) {
+ fido_log_debug("%s: n_rx >= n_alloc", __func__);
+ return (-1);
+ }
+
+ if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) {
+ fido_log_debug("%s: decode_template", __func__);
+ return (-1);
+ }
+
+ ta->n_rx++;
+
+ return (0);
+}
+
+static int
+bio_parse_template_array(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_bio_template_array_t *ta = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 7) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_isa_array(val) == false ||
+ cbor_array_is_definite(val) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) {
+ fido_log_debug("%s: ptr != NULL || n_alloc != 0 || n_rx != 0",
+ __func__);
+ return (-1);
+ }
+
+ if ((ta->ptr = calloc(cbor_array_size(val), sizeof(*ta->ptr))) == NULL)
+ return (-1);
+
+ ta->n_alloc = cbor_array_size(val);
+
+ if (cbor_array_iter(val, ta, decode_template_array) < 0) {
+ fido_log_debug("%s: decode_template_array", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+bio_rx_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ bio_reset_template_array(ta);
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, ta,
+ bio_parse_template_array)) != FIDO_OK) {
+ fido_log_debug("%s: bio_parse_template_array" , __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+bio_get_template_array_wait(fido_dev_t *dev, fido_bio_template_array_t *ta,
+ const char *pin, int *ms)
+{
+ int r;
+
+ if ((r = bio_tx(dev, CMD_ENUM, NULL, 0, pin, NULL, ms)) != FIDO_OK ||
+ (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta,
+ const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ if (pin == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (bio_get_template_array_wait(dev, ta, pin, &ms));
+}
+
+static int
+bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin, int *ms)
+{
+ cbor_item_t *argv[2];
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
+ (argv[1] = cbor_build_string(t->name)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL,
+ ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ if (pin == NULL || t->name == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (bio_set_template_name_wait(dev, t, pin, &ms));
+}
+
+static void
+bio_reset_enroll(fido_bio_enroll_t *e)
+{
+ e->remaining_samples = 0;
+ e->last_status = 0;
+
+ if (e->token)
+ fido_blob_free(&e->token);
+}
+
+static int
+bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_bio_enroll_t *e = arg;
+ uint64_t x;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 5:
+ if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+ e->last_status = (uint8_t)x;
+ break;
+ case 6:
+ if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+ e->remaining_samples = (uint8_t)x;
+ break;
+ default:
+ return (0); /* ignore */
+ }
+
+ return (0);
+}
+
+static int
+bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_blob_t *id = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 4) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ return (fido_blob_decode(val, id));
+}
+
+static int
+bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
+ fido_bio_enroll_t *e, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ bio_reset_template(t);
+
+ e->remaining_samples = 0;
+ e->last_status = 0;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, e,
+ bio_parse_enroll_status)) != FIDO_OK) {
+ fido_log_debug("%s: bio_parse_enroll_status", __func__);
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, &t->id,
+ bio_parse_template_id)) != FIDO_OK) {
+ fido_log_debug("%s: bio_parse_template_id", __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+bio_enroll_begin_wait(fido_dev_t *dev, fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms, int *ms)
+{
+ cbor_item_t *argv[3];
+ const uint8_t cmd = CMD_ENROLL_BEGIN;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[2] = cbor_build_uint(timo_ms)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK ||
+ (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) {
+ fido_log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms, const char *pin)
+{
+ es256_pk_t *pk = NULL;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t *token = NULL;
+ int ms = dev->timeout_ms;
+ int r;
+
+ if (pin == NULL || e->token != NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((token = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+
+ if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_BIO_ENROLL_PRE, pin, ecdh,
+ pk, NULL, token, &ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_get_uv_token", __func__);
+ goto fail;
+ }
+
+ e->token = token;
+ token = NULL;
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ fido_blob_free(&token);
+
+ if (r != FIDO_OK)
+ return (r);
+
+ return (bio_enroll_begin_wait(dev, t, e, timo_ms, &ms));
+}
+
+static int
+bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ e->remaining_samples = 0;
+ e->last_status = 0;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, e,
+ bio_parse_enroll_status)) != FIDO_OK) {
+ fido_log_debug("%s: bio_parse_enroll_status", __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+bio_enroll_continue_wait(fido_dev_t *dev, const fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms, int *ms)
+{
+ cbor_item_t *argv[3];
+ const uint8_t cmd = CMD_ENROLL_NEXT;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[0] = fido_blob_encode(&t->id)) == NULL ||
+ (argv[2] = cbor_build_uint(timo_ms)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK ||
+ (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) {
+ fido_log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_enroll_continue(fido_dev_t *dev, const fido_bio_template_t *t,
+ fido_bio_enroll_t *e, uint32_t timo_ms)
+{
+ int ms = dev->timeout_ms;
+
+ if (e->token == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (bio_enroll_continue_wait(dev, t, e, timo_ms, &ms));
+}
+
+static int
+bio_enroll_cancel_wait(fido_dev_t *dev, int *ms)
+{
+ const uint8_t cmd = CMD_ENROLL_CANCEL;
+ int r;
+
+ if ((r = bio_tx(dev, cmd, NULL, 0, NULL, NULL, ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: tx/rx", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_dev_enroll_cancel(fido_dev_t *dev)
+{
+ int ms = dev->timeout_ms;
+
+ return (bio_enroll_cancel_wait(dev, &ms));
+}
+
+static int
+bio_enroll_remove_wait(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin, int *ms)
+{
+ cbor_item_t *argv[1];
+ const uint8_t cmd = CMD_ENROLL_REMOVE;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&argv, 0, sizeof(argv));
+
+ if ((argv[0] = fido_blob_encode(&t->id)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if ((r = bio_tx(dev, cmd, argv, 1, pin, NULL, ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: tx/rx", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return (r);
+}
+
+int
+fido_bio_dev_enroll_remove(fido_dev_t *dev, const fido_bio_template_t *t,
+ const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return (bio_enroll_remove_wait(dev, t, pin, &ms));
+}
+
+static void
+bio_reset_info(fido_bio_info_t *i)
+{
+ i->type = 0;
+ i->max_samples = 0;
+}
+
+static int
+bio_parse_info(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_bio_info_t *i = arg;
+ uint64_t x;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 2:
+ if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+ i->type = (uint8_t)x;
+ break;
+ case 3:
+ if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+ i->max_samples = (uint8_t)x;
+ break;
+ default:
+ return (0); /* ignore */
+ }
+
+ return (0);
+}
+
+static int
+bio_rx_info(fido_dev_t *dev, fido_bio_info_t *i, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ bio_reset_info(i);
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, i,
+ bio_parse_info)) != FIDO_OK) {
+ fido_log_debug("%s: bio_parse_info" , __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+bio_get_info_wait(fido_dev_t *dev, fido_bio_info_t *i, int *ms)
+{
+ int r;
+
+ if ((r = bio_tx(dev, CMD_GET_INFO, NULL, 0, NULL, NULL,
+ ms)) != FIDO_OK ||
+ (r = bio_rx_info(dev, i, ms)) != FIDO_OK) {
+ fido_log_debug("%s: tx/rx", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_dev_get_info(fido_dev_t *dev, fido_bio_info_t *i)
+{
+ int ms = dev->timeout_ms;
+
+ return (bio_get_info_wait(dev, i, &ms));
+}
+
+const char *
+fido_bio_template_name(const fido_bio_template_t *t)
+{
+ return (t->name);
+}
+
+const unsigned char *
+fido_bio_template_id_ptr(const fido_bio_template_t *t)
+{
+ return (t->id.ptr);
+}
+
+size_t
+fido_bio_template_id_len(const fido_bio_template_t *t)
+{
+ return (t->id.len);
+}
+
+size_t
+fido_bio_template_array_count(const fido_bio_template_array_t *ta)
+{
+ return (ta->n_rx);
+}
+
+fido_bio_template_array_t *
+fido_bio_template_array_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_template_array_t)));
+}
+
+fido_bio_template_t *
+fido_bio_template_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_template_t)));
+}
+
+void
+fido_bio_template_array_free(fido_bio_template_array_t **tap)
+{
+ fido_bio_template_array_t *ta;
+
+ if (tap == NULL || (ta = *tap) == NULL)
+ return;
+
+ bio_reset_template_array(ta);
+ free(ta);
+ *tap = NULL;
+}
+
+void
+fido_bio_template_free(fido_bio_template_t **tp)
+{
+ fido_bio_template_t *t;
+
+ if (tp == NULL || (t = *tp) == NULL)
+ return;
+
+ bio_reset_template(t);
+ free(t);
+ *tp = NULL;
+}
+
+int
+fido_bio_template_set_name(fido_bio_template_t *t, const char *name)
+{
+ free(t->name);
+ t->name = NULL;
+
+ if (name && (t->name = strdup(name)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+int
+fido_bio_template_set_id(fido_bio_template_t *t, const unsigned char *ptr,
+ size_t len)
+{
+ fido_blob_reset(&t->id);
+
+ if (ptr && fido_blob_set(&t->id, ptr, len) < 0)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+const fido_bio_template_t *
+fido_bio_template(const fido_bio_template_array_t *ta, size_t idx)
+{
+ if (idx >= ta->n_alloc)
+ return (NULL);
+
+ return (&ta->ptr[idx]);
+}
+
+fido_bio_enroll_t *
+fido_bio_enroll_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_enroll_t)));
+}
+
+fido_bio_info_t *
+fido_bio_info_new(void)
+{
+ return (calloc(1, sizeof(fido_bio_info_t)));
+}
+
+uint8_t
+fido_bio_info_type(const fido_bio_info_t *i)
+{
+ return (i->type);
+}
+
+uint8_t
+fido_bio_info_max_samples(const fido_bio_info_t *i)
+{
+ return (i->max_samples);
+}
+
+void
+fido_bio_enroll_free(fido_bio_enroll_t **ep)
+{
+ fido_bio_enroll_t *e;
+
+ if (ep == NULL || (e = *ep) == NULL)
+ return;
+
+ bio_reset_enroll(e);
+
+ free(e);
+ *ep = NULL;
+}
+
+void
+fido_bio_info_free(fido_bio_info_t **ip)
+{
+ fido_bio_info_t *i;
+
+ if (ip == NULL || (i = *ip) == NULL)
+ return;
+
+ free(i);
+ *ip = NULL;
+}
+
+uint8_t
+fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *e)
+{
+ return (e->remaining_samples);
+}
+
+uint8_t
+fido_bio_enroll_last_status(const fido_bio_enroll_t *e)
+{
+ return (e->last_status);
+}
diff --git a/src/blob.c b/src/blob.c
new file mode 100644
index 0000000..b431f49
--- /dev/null
+++ b/src/blob.c
@@ -0,0 +1,134 @@
+/*
+ * 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"
+
+fido_blob_t *
+fido_blob_new(void)
+{
+ return calloc(1, sizeof(fido_blob_t));
+}
+
+void
+fido_blob_reset(fido_blob_t *b)
+{
+ freezero(b->ptr, b->len);
+ explicit_bzero(b, sizeof(*b));
+}
+
+int
+fido_blob_set(fido_blob_t *b, const u_char *ptr, size_t len)
+{
+ fido_blob_reset(b);
+
+ if (ptr == NULL || len == 0) {
+ fido_log_debug("%s: ptr=%p, len=%zu", __func__,
+ (const void *)ptr, len);
+ return -1;
+ }
+
+ if ((b->ptr = malloc(len)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ return -1;
+ }
+
+ memcpy(b->ptr, ptr, len);
+ b->len = len;
+
+ return 0;
+}
+
+int
+fido_blob_append(fido_blob_t *b, const u_char *ptr, size_t len)
+{
+ u_char *tmp;
+
+ if (ptr == NULL || len == 0) {
+ fido_log_debug("%s: ptr=%p, len=%zu", __func__,
+ (const void *)ptr, len);
+ return -1;
+ }
+ if (SIZE_MAX - b->len < len) {
+ fido_log_debug("%s: overflow", __func__);
+ return -1;
+ }
+ if ((tmp = realloc(b->ptr, b->len + len)) == NULL) {
+ fido_log_debug("%s: realloc", __func__);
+ return -1;
+ }
+ b->ptr = tmp;
+ memcpy(&b->ptr[b->len], ptr, len);
+ b->len += len;
+
+ return 0;
+}
+
+void
+fido_blob_free(fido_blob_t **bp)
+{
+ fido_blob_t *b;
+
+ if (bp == NULL || (b = *bp) == NULL)
+ return;
+
+ fido_blob_reset(b);
+ free(b);
+ *bp = NULL;
+}
+
+void
+fido_free_blob_array(fido_blob_array_t *array)
+{
+ if (array->ptr == NULL)
+ return;
+
+ for (size_t i = 0; i < array->len; i++) {
+ fido_blob_t *b = &array->ptr[i];
+ freezero(b->ptr, b->len);
+ b->ptr = NULL;
+ }
+
+ free(array->ptr);
+ array->ptr = NULL;
+ array->len = 0;
+}
+
+cbor_item_t *
+fido_blob_encode(const fido_blob_t *b)
+{
+ if (b == NULL || b->ptr == NULL)
+ return NULL;
+
+ return cbor_build_bytestring(b->ptr, b->len);
+}
+
+int
+fido_blob_decode(const cbor_item_t *item, fido_blob_t *b)
+{
+ return cbor_bytestring_copy(item, &b->ptr, &b->len);
+}
+
+int
+fido_blob_is_empty(const fido_blob_t *b)
+{
+ return b->ptr == NULL || b->len == 0;
+}
+
+int
+fido_blob_serialise(fido_blob_t *b, const cbor_item_t *item)
+{
+ size_t alloc;
+
+ if (!fido_blob_is_empty(b))
+ return -1;
+ if ((b->len = cbor_serialize_alloc(item, &b->ptr, &alloc)) == 0) {
+ b->ptr = NULL;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/blob.h b/src/blob.h
new file mode 100644
index 0000000..7247185
--- /dev/null
+++ b/src/blob.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _BLOB_H
+#define _BLOB_H
+
+#include <cbor.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct fido_blob {
+ unsigned char *ptr;
+ size_t len;
+} fido_blob_t;
+
+typedef struct fido_blob_array {
+ fido_blob_t *ptr;
+ size_t len;
+} fido_blob_array_t;
+
+cbor_item_t *fido_blob_encode(const fido_blob_t *);
+fido_blob_t *fido_blob_new(void);
+int fido_blob_decode(const cbor_item_t *, fido_blob_t *);
+int fido_blob_is_empty(const fido_blob_t *);
+int fido_blob_set(fido_blob_t *, const u_char *, size_t);
+int fido_blob_append(fido_blob_t *, const u_char *, size_t);
+void fido_blob_free(fido_blob_t **);
+void fido_blob_reset(fido_blob_t *);
+void fido_free_blob_array(fido_blob_array_t *);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_BLOB_H */
diff --git a/src/buf.c b/src/buf.c
new file mode 100644
index 0000000..42b6df1
--- /dev/null
+++ b/src/buf.c
@@ -0,0 +1,34 @@
+/*
+ * 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"
+
+int
+fido_buf_read(const unsigned char **buf, size_t *len, void *dst, size_t count)
+{
+ if (count > *len)
+ return (-1);
+
+ memcpy(dst, *buf, count);
+ *buf += count;
+ *len -= count;
+
+ return (0);
+}
+
+int
+fido_buf_write(unsigned char **buf, size_t *len, const void *src, size_t count)
+{
+ if (count > *len)
+ return (-1);
+
+ memcpy(*buf, src, count);
+ *buf += count;
+ *len -= count;
+
+ return (0);
+}
diff --git a/src/cbor.c b/src/cbor.c
new file mode 100644
index 0000000..ab99b34
--- /dev/null
+++ b/src/cbor.c
@@ -0,0 +1,1705 @@
+/*
+ * 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 <openssl/hmac.h>
+#include <openssl/sha.h>
+#include "fido.h"
+
+static int
+check_key_type(cbor_item_t *item)
+{
+ if (item->type == CBOR_TYPE_UINT || item->type == CBOR_TYPE_NEGINT ||
+ item->type == CBOR_TYPE_STRING)
+ return (0);
+
+ fido_log_debug("%s: invalid type: %d", __func__, item->type);
+
+ return (-1);
+}
+
+/*
+ * Validate CTAP2 canonical CBOR encoding rules for maps.
+ */
+static int
+ctap_check_cbor(cbor_item_t *prev, cbor_item_t *curr)
+{
+ size_t curr_len;
+ size_t prev_len;
+
+ if (check_key_type(prev) < 0 || check_key_type(curr) < 0)
+ return (-1);
+
+ if (prev->type != curr->type) {
+ if (prev->type < curr->type)
+ return (0);
+ fido_log_debug("%s: unsorted types", __func__);
+ return (-1);
+ }
+
+ if (curr->type == CBOR_TYPE_UINT || curr->type == CBOR_TYPE_NEGINT) {
+ if (cbor_int_get_width(curr) >= cbor_int_get_width(prev) &&
+ cbor_get_int(curr) > cbor_get_int(prev))
+ return (0);
+ } else {
+ curr_len = cbor_string_length(curr);
+ prev_len = cbor_string_length(prev);
+
+ if (curr_len > prev_len || (curr_len == prev_len &&
+ memcmp(cbor_string_handle(prev), cbor_string_handle(curr),
+ curr_len) < 0))
+ return (0);
+ }
+
+ fido_log_debug("%s: invalid cbor", __func__);
+
+ return (-1);
+}
+
+int
+cbor_map_iter(const cbor_item_t *item, void *arg, int(*f)(const cbor_item_t *,
+ const cbor_item_t *, void *))
+{
+ struct cbor_pair *v;
+ size_t n;
+
+ if ((v = cbor_map_handle(item)) == NULL) {
+ fido_log_debug("%s: cbor_map_handle", __func__);
+ return (-1);
+ }
+
+ n = cbor_map_size(item);
+
+ for (size_t i = 0; i < n; i++) {
+ if (v[i].key == NULL || v[i].value == NULL) {
+ fido_log_debug("%s: key=%p, value=%p for i=%zu",
+ __func__, (void *)v[i].key, (void *)v[i].value, i);
+ return (-1);
+ }
+ if (i && ctap_check_cbor(v[i - 1].key, v[i].key) < 0) {
+ fido_log_debug("%s: ctap_check_cbor", __func__);
+ return (-1);
+ }
+ if (f(v[i].key, v[i].value, arg) < 0) {
+ fido_log_debug("%s: iterator < 0 on i=%zu", __func__,
+ i);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+int
+cbor_array_iter(const cbor_item_t *item, void *arg, int(*f)(const cbor_item_t *,
+ void *))
+{
+ cbor_item_t **v;
+ size_t n;
+
+ if ((v = cbor_array_handle(item)) == NULL) {
+ fido_log_debug("%s: cbor_array_handle", __func__);
+ return (-1);
+ }
+
+ n = cbor_array_size(item);
+
+ for (size_t i = 0; i < n; i++)
+ if (v[i] == NULL || f(v[i], arg) < 0) {
+ fido_log_debug("%s: iterator < 0 on i=%zu,%p",
+ __func__, i, (void *)v[i]);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+cbor_parse_reply(const unsigned char *blob, size_t blob_len, void *arg,
+ int(*parser)(const cbor_item_t *, const cbor_item_t *, void *))
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int r;
+
+ if (blob_len < 1) {
+ fido_log_debug("%s: blob_len=%zu", __func__, blob_len);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if (blob[0] != FIDO_OK) {
+ fido_log_debug("%s: blob[0]=0x%02x", __func__, blob[0]);
+ r = blob[0];
+ goto fail;
+ }
+
+ if ((item = cbor_load(blob + 1, blob_len - 1, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ r = FIDO_ERR_RX_NOT_CBOR;
+ goto fail;
+ }
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ r = FIDO_ERR_RX_INVALID_CBOR;
+ goto fail;
+ }
+
+ if (cbor_map_iter(item, arg, parser) < 0) {
+ fido_log_debug("%s: cbor_map_iter", __func__);
+ r = FIDO_ERR_RX_INVALID_CBOR;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (r);
+}
+
+void
+cbor_vector_free(cbor_item_t **item, size_t len)
+{
+ for (size_t i = 0; i < len; i++)
+ if (item[i] != NULL)
+ cbor_decref(&item[i]);
+}
+
+int
+cbor_bytestring_copy(const cbor_item_t *item, unsigned char **buf, size_t *len)
+{
+ if (*buf != NULL || *len != 0) {
+ fido_log_debug("%s: dup", __func__);
+ return (-1);
+ }
+
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ *len = cbor_bytestring_length(item);
+ if ((*buf = malloc(*len)) == NULL) {
+ *len = 0;
+ return (-1);
+ }
+
+ memcpy(*buf, cbor_bytestring_handle(item), *len);
+
+ return (0);
+}
+
+int
+cbor_string_copy(const cbor_item_t *item, char **str)
+{
+ size_t len;
+
+ if (*str != NULL) {
+ fido_log_debug("%s: dup", __func__);
+ return (-1);
+ }
+
+ if (cbor_isa_string(item) == false ||
+ cbor_string_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if ((len = cbor_string_length(item)) == SIZE_MAX ||
+ (*str = malloc(len + 1)) == NULL)
+ return (-1);
+
+ memcpy(*str, cbor_string_handle(item), len);
+ (*str)[len] = '\0';
+
+ return (0);
+}
+
+int
+cbor_add_bytestring(cbor_item_t *item, const char *key,
+ const unsigned char *value, size_t value_len)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if ((pair.key = cbor_build_string(key)) == NULL ||
+ (pair.value = cbor_build_bytestring(value, value_len)) == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (!cbor_map_add(item, pair)) {
+ fido_log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+ if (pair.value)
+ cbor_decref(&pair.value);
+
+ return (ok);
+}
+
+int
+cbor_add_string(cbor_item_t *item, const char *key, const char *value)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if ((pair.key = cbor_build_string(key)) == NULL ||
+ (pair.value = cbor_build_string(value)) == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (!cbor_map_add(item, pair)) {
+ fido_log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+ if (pair.value)
+ cbor_decref(&pair.value);
+
+ return (ok);
+}
+
+int
+cbor_add_bool(cbor_item_t *item, const char *key, fido_opt_t value)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if ((pair.key = cbor_build_string(key)) == NULL ||
+ (pair.value = cbor_build_bool(value == FIDO_OPT_TRUE)) == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (!cbor_map_add(item, pair)) {
+ fido_log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+ if (pair.value)
+ cbor_decref(&pair.value);
+
+ return (ok);
+}
+
+static int
+cbor_add_uint8(cbor_item_t *item, const char *key, uint8_t value)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if ((pair.key = cbor_build_string(key)) == NULL ||
+ (pair.value = cbor_build_uint8(value)) == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (!cbor_map_add(item, pair)) {
+ fido_log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+ if (pair.value)
+ cbor_decref(&pair.value);
+
+ return (ok);
+}
+
+static int
+cbor_add_arg(cbor_item_t *item, uint8_t n, cbor_item_t *arg)
+{
+ struct cbor_pair pair;
+ int ok = -1;
+
+ memset(&pair, 0, sizeof(pair));
+
+ if (arg == NULL)
+ return (0); /* empty argument */
+
+ if ((pair.key = cbor_build_uint8(n)) == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ pair.value = arg;
+
+ if (!cbor_map_add(item, pair)) {
+ fido_log_debug("%s: cbor_map_add", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pair.key)
+ cbor_decref(&pair.key);
+
+ return (ok);
+}
+
+cbor_item_t *
+cbor_flatten_vector(cbor_item_t *argv[], size_t argc)
+{
+ cbor_item_t *map;
+ uint8_t i;
+
+ if (argc > UINT8_MAX - 1)
+ return (NULL);
+
+ if ((map = cbor_new_definite_map(argc)) == NULL)
+ return (NULL);
+
+ for (i = 0; i < argc; i++)
+ if (cbor_add_arg(map, (uint8_t)(i + 1), argv[i]) < 0)
+ break;
+
+ if (i != argc) {
+ cbor_decref(&map);
+ map = NULL;
+ }
+
+ return (map);
+}
+
+int
+cbor_build_frame(uint8_t cmd, cbor_item_t *argv[], size_t argc, fido_blob_t *f)
+{
+ cbor_item_t *flat = NULL;
+ unsigned char *cbor = NULL;
+ size_t cbor_len;
+ size_t cbor_alloc_len;
+ int ok = -1;
+
+ if ((flat = cbor_flatten_vector(argv, argc)) == NULL)
+ goto fail;
+
+ cbor_len = cbor_serialize_alloc(flat, &cbor, &cbor_alloc_len);
+ if (cbor_len == 0 || cbor_len == SIZE_MAX) {
+ fido_log_debug("%s: cbor_len=%zu", __func__, cbor_len);
+ goto fail;
+ }
+
+ if ((f->ptr = malloc(cbor_len + 1)) == NULL)
+ goto fail;
+
+ f->len = cbor_len + 1;
+ f->ptr[0] = cmd;
+ memcpy(f->ptr + 1, cbor, f->len - 1);
+
+ ok = 0;
+fail:
+ if (flat != NULL)
+ cbor_decref(&flat);
+
+ free(cbor);
+
+ return (ok);
+}
+
+cbor_item_t *
+cbor_encode_rp_entity(const fido_rp_t *rp)
+{
+ cbor_item_t *item = NULL;
+
+ if ((item = cbor_new_definite_map(2)) == NULL)
+ return (NULL);
+
+ if ((rp->id && cbor_add_string(item, "id", rp->id) < 0) ||
+ (rp->name && cbor_add_string(item, "name", rp->name) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+cbor_encode_user_entity(const fido_user_t *user)
+{
+ cbor_item_t *item = NULL;
+ const fido_blob_t *id = &user->id;
+ const char *display = user->display_name;
+
+ if ((item = cbor_new_definite_map(4)) == NULL)
+ return (NULL);
+
+ if ((id->ptr && cbor_add_bytestring(item, "id", id->ptr, id->len) < 0) ||
+ (user->icon && cbor_add_string(item, "icon", user->icon) < 0) ||
+ (user->name && cbor_add_string(item, "name", user->name) < 0) ||
+ (display && cbor_add_string(item, "displayName", display) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+cbor_encode_pubkey_param(int cose_alg)
+{
+ cbor_item_t *item = NULL;
+ cbor_item_t *body = NULL;
+ struct cbor_pair alg;
+ int ok = -1;
+
+ memset(&alg, 0, sizeof(alg));
+
+ if ((item = cbor_new_definite_array(1)) == NULL ||
+ (body = cbor_new_definite_map(2)) == NULL ||
+ cose_alg > -1 || cose_alg < INT16_MIN)
+ goto fail;
+
+ alg.key = cbor_build_string("alg");
+
+ if (-cose_alg - 1 > UINT8_MAX)
+ alg.value = cbor_build_negint16((uint16_t)(-cose_alg - 1));
+ else
+ alg.value = cbor_build_negint8((uint8_t)(-cose_alg - 1));
+
+ if (alg.key == NULL || alg.value == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ goto fail;
+ }
+
+ if (cbor_map_add(body, alg) == false ||
+ cbor_add_string(body, "type", "public-key") < 0 ||
+ cbor_array_push(item, body) == false)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ if (item != NULL) {
+ cbor_decref(&item);
+ item = NULL;
+ }
+ }
+
+ if (body != NULL)
+ cbor_decref(&body);
+ if (alg.key != NULL)
+ cbor_decref(&alg.key);
+ if (alg.value != NULL)
+ cbor_decref(&alg.value);
+
+ return (item);
+}
+
+cbor_item_t *
+cbor_encode_pubkey(const fido_blob_t *pubkey)
+{
+ cbor_item_t *cbor_key = NULL;
+
+ if ((cbor_key = cbor_new_definite_map(2)) == NULL ||
+ cbor_add_bytestring(cbor_key, "id", pubkey->ptr, pubkey->len) < 0 ||
+ cbor_add_string(cbor_key, "type", "public-key") < 0) {
+ if (cbor_key)
+ cbor_decref(&cbor_key);
+ return (NULL);
+ }
+
+ return (cbor_key);
+}
+
+cbor_item_t *
+cbor_encode_pubkey_list(const fido_blob_array_t *list)
+{
+ cbor_item_t *array = NULL;
+ cbor_item_t *key = NULL;
+
+ if ((array = cbor_new_definite_array(list->len)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < list->len; i++) {
+ if ((key = cbor_encode_pubkey(&list->ptr[i])) == NULL ||
+ cbor_array_push(array, key) == false)
+ goto fail;
+ cbor_decref(&key);
+ }
+
+ return (array);
+fail:
+ if (key != NULL)
+ cbor_decref(&key);
+ if (array != NULL)
+ cbor_decref(&array);
+
+ return (NULL);
+}
+
+cbor_item_t *
+cbor_encode_str_array(const fido_str_array_t *a)
+{
+ cbor_item_t *array = NULL;
+ cbor_item_t *entry = NULL;
+
+ if ((array = cbor_new_definite_array(a->len)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < a->len; i++) {
+ if ((entry = cbor_build_string(a->ptr[i])) == NULL ||
+ cbor_array_push(array, entry) == false)
+ goto fail;
+ cbor_decref(&entry);
+ }
+
+ return (array);
+fail:
+ if (entry != NULL)
+ cbor_decref(&entry);
+ if (array != NULL)
+ cbor_decref(&array);
+
+ return (NULL);
+}
+
+static int
+cbor_encode_largeblob_key_ext(cbor_item_t *map)
+{
+ if (map == NULL ||
+ cbor_add_bool(map, "largeBlobKey", FIDO_OPT_TRUE) < 0)
+ return (-1);
+
+ return (0);
+}
+
+cbor_item_t *
+cbor_encode_cred_ext(const fido_cred_ext_t *ext, const fido_blob_t *blob)
+{
+ cbor_item_t *item = NULL;
+ size_t size = 0;
+
+ if (ext->mask & FIDO_EXT_CRED_BLOB)
+ size++;
+ if (ext->mask & FIDO_EXT_HMAC_SECRET)
+ size++;
+ if (ext->mask & FIDO_EXT_CRED_PROTECT)
+ size++;
+ if (ext->mask & FIDO_EXT_LARGEBLOB_KEY)
+ size++;
+ if (ext->mask & FIDO_EXT_MINPINLEN)
+ size++;
+
+ if (size == 0 || (item = cbor_new_definite_map(size)) == NULL)
+ return (NULL);
+
+ if (ext->mask & FIDO_EXT_CRED_BLOB) {
+ if (cbor_add_bytestring(item, "credBlob", blob->ptr,
+ blob->len) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+ if (ext->mask & FIDO_EXT_CRED_PROTECT) {
+ if (ext->prot < 0 || ext->prot > UINT8_MAX ||
+ cbor_add_uint8(item, "credProtect",
+ (uint8_t)ext->prot) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+ if (ext->mask & FIDO_EXT_HMAC_SECRET) {
+ if (cbor_add_bool(item, "hmac-secret", FIDO_OPT_TRUE) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+ if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) {
+ if (cbor_encode_largeblob_key_ext(item) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+ if (ext->mask & FIDO_EXT_MINPINLEN) {
+ if (cbor_add_bool(item, "minPinLength", FIDO_OPT_TRUE) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+cbor_encode_cred_opt(fido_opt_t rk, fido_opt_t uv)
+{
+ cbor_item_t *item = NULL;
+
+ if ((item = cbor_new_definite_map(2)) == NULL)
+ return (NULL);
+ if ((rk != FIDO_OPT_OMIT && cbor_add_bool(item, "rk", rk) < 0) ||
+ (uv != FIDO_OPT_OMIT && cbor_add_bool(item, "uv", uv) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+cbor_encode_assert_opt(fido_opt_t up, fido_opt_t uv)
+{
+ cbor_item_t *item = NULL;
+
+ if ((item = cbor_new_definite_map(2)) == NULL)
+ return (NULL);
+ if ((up != FIDO_OPT_OMIT && cbor_add_bool(item, "up", up) < 0) ||
+ (uv != FIDO_OPT_OMIT && cbor_add_bool(item, "uv", uv) < 0)) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+
+ return (item);
+}
+
+cbor_item_t *
+cbor_encode_pin_auth(const fido_dev_t *dev, const fido_blob_t *secret,
+ const fido_blob_t *data)
+{
+ const EVP_MD *md = NULL;
+ unsigned char dgst[SHA256_DIGEST_LENGTH];
+ unsigned int dgst_len;
+ size_t outlen;
+ uint8_t prot;
+ fido_blob_t key;
+
+ key.ptr = secret->ptr;
+ key.len = secret->len;
+
+ if ((prot = fido_dev_get_pin_protocol(dev)) == 0) {
+ fido_log_debug("%s: fido_dev_get_pin_protocol", __func__);
+ return (NULL);
+ }
+
+ /* select hmac portion of the shared secret */
+ if (prot == CTAP_PIN_PROTOCOL2 && key.len > 32)
+ key.len = 32;
+
+ if ((md = EVP_sha256()) == NULL || HMAC(md, key.ptr,
+ (int)key.len, data->ptr, data->len, dgst,
+ &dgst_len) == NULL || dgst_len != SHA256_DIGEST_LENGTH)
+ return (NULL);
+
+ outlen = (prot == CTAP_PIN_PROTOCOL1) ? 16 : dgst_len;
+
+ return (cbor_build_bytestring(dgst, outlen));
+}
+
+cbor_item_t *
+cbor_encode_pin_opt(const fido_dev_t *dev)
+{
+ uint8_t prot;
+
+ if ((prot = fido_dev_get_pin_protocol(dev)) == 0) {
+ fido_log_debug("%s: fido_dev_get_pin_protocol", __func__);
+ return (NULL);
+ }
+
+ return (cbor_build_uint8(prot));
+}
+
+cbor_item_t *
+cbor_encode_change_pin_auth(const fido_dev_t *dev, const fido_blob_t *secret,
+ const fido_blob_t *new_pin_enc, const fido_blob_t *pin_hash_enc)
+{
+ unsigned char dgst[SHA256_DIGEST_LENGTH];
+ unsigned int dgst_len;
+ cbor_item_t *item = NULL;
+ const EVP_MD *md = NULL;
+ HMAC_CTX *ctx = NULL;
+ fido_blob_t key;
+ uint8_t prot;
+ size_t outlen;
+
+ key.ptr = secret->ptr;
+ key.len = secret->len;
+
+ if ((prot = fido_dev_get_pin_protocol(dev)) == 0) {
+ fido_log_debug("%s: fido_dev_get_pin_protocol", __func__);
+ goto fail;
+ }
+
+ if (prot == CTAP_PIN_PROTOCOL2 && key.len > 32)
+ key.len = 32;
+
+ if ((ctx = HMAC_CTX_new()) == NULL ||
+ (md = EVP_sha256()) == NULL ||
+ HMAC_Init_ex(ctx, key.ptr, (int)key.len, md, NULL) == 0 ||
+ HMAC_Update(ctx, new_pin_enc->ptr, new_pin_enc->len) == 0 ||
+ HMAC_Update(ctx, pin_hash_enc->ptr, pin_hash_enc->len) == 0 ||
+ HMAC_Final(ctx, dgst, &dgst_len) == 0 ||
+ dgst_len != SHA256_DIGEST_LENGTH) {
+ fido_log_debug("%s: HMAC", __func__);
+ goto fail;
+ }
+
+ outlen = (prot == CTAP_PIN_PROTOCOL1) ? 16 : dgst_len;
+
+ if ((item = cbor_build_bytestring(dgst, outlen)) == NULL) {
+ fido_log_debug("%s: cbor_build_bytestring", __func__);
+ goto fail;
+ }
+
+fail:
+ HMAC_CTX_free(ctx);
+
+ return (item);
+}
+
+static int
+cbor_encode_hmac_secret_param(const fido_dev_t *dev, cbor_item_t *item,
+ const fido_blob_t *ecdh, const es256_pk_t *pk, const fido_blob_t *salt)
+{
+ cbor_item_t *param = NULL;
+ cbor_item_t *argv[4];
+ struct cbor_pair pair;
+ fido_blob_t *enc = NULL;
+ uint8_t prot;
+ int r;
+
+ memset(argv, 0, sizeof(argv));
+ memset(&pair, 0, sizeof(pair));
+
+ if (item == NULL || ecdh == NULL || pk == NULL || salt->ptr == NULL) {
+ fido_log_debug("%s: ecdh=%p, pk=%p, salt->ptr=%p", __func__,
+ (const void *)ecdh, (const void *)pk,
+ (const void *)salt->ptr);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (salt->len != 32 && salt->len != 64) {
+ fido_log_debug("%s: salt->len=%zu", __func__, salt->len);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((enc = fido_blob_new()) == NULL ||
+ aes256_cbc_enc(dev, ecdh, salt, enc) < 0) {
+ fido_log_debug("%s: aes256_cbc_enc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((prot = fido_dev_get_pin_protocol(dev)) == 0) {
+ fido_log_debug("%s: fido_dev_get_pin_protocol", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* XXX not pin, but salt */
+ if ((argv[0] = es256_pk_encode(pk, 1)) == NULL ||
+ (argv[1] = fido_blob_encode(enc)) == NULL ||
+ (argv[2] = cbor_encode_pin_auth(dev, ecdh, enc)) == NULL ||
+ (prot != 1 && (argv[3] = cbor_build_uint8(prot)) == NULL)) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((param = cbor_flatten_vector(argv, nitems(argv))) == NULL) {
+ fido_log_debug("%s: cbor_flatten_vector", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((pair.key = cbor_build_string("hmac-secret")) == NULL) {
+ fido_log_debug("%s: cbor_build", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ pair.value = param;
+
+ if (!cbor_map_add(item, pair)) {
+ fido_log_debug("%s: cbor_map_add", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ if (param != NULL)
+ cbor_decref(&param);
+ if (pair.key != NULL)
+ cbor_decref(&pair.key);
+
+ fido_blob_free(&enc);
+
+ return (r);
+}
+
+cbor_item_t *
+cbor_encode_assert_ext(fido_dev_t *dev, const fido_assert_ext_t *ext,
+ const fido_blob_t *ecdh, const es256_pk_t *pk)
+{
+ cbor_item_t *item = NULL;
+ size_t size = 0;
+
+ if (ext->mask & FIDO_EXT_CRED_BLOB)
+ size++;
+ if (ext->mask & FIDO_EXT_HMAC_SECRET)
+ size++;
+ if (ext->mask & FIDO_EXT_LARGEBLOB_KEY)
+ size++;
+ if (size == 0 || (item = cbor_new_definite_map(size)) == NULL)
+ return (NULL);
+
+ if (ext->mask & FIDO_EXT_CRED_BLOB) {
+ if (cbor_add_bool(item, "credBlob", FIDO_OPT_TRUE) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+ if (ext->mask & FIDO_EXT_HMAC_SECRET) {
+ if (cbor_encode_hmac_secret_param(dev, item, ecdh, pk,
+ &ext->hmac_salt) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+ if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) {
+ if (cbor_encode_largeblob_key_ext(item) < 0) {
+ cbor_decref(&item);
+ return (NULL);
+ }
+ }
+
+ return (item);
+}
+
+int
+cbor_decode_fmt(const cbor_item_t *item, char **fmt)
+{
+ char *type = NULL;
+
+ if (cbor_string_copy(item, &type) < 0) {
+ fido_log_debug("%s: cbor_string_copy", __func__);
+ return (-1);
+ }
+
+ if (strcmp(type, "packed") && strcmp(type, "fido-u2f") &&
+ strcmp(type, "none") && strcmp(type, "tpm")) {
+ fido_log_debug("%s: type=%s", __func__, type);
+ free(type);
+ return (-1);
+ }
+
+ *fmt = type;
+
+ return (0);
+}
+
+struct cose_key {
+ int kty;
+ int alg;
+ int crv;
+};
+
+static int
+find_cose_alg(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ struct cose_key *cose_key = arg;
+
+ if (cbor_isa_uint(key) == true &&
+ cbor_int_get_width(key) == CBOR_INT_8) {
+ switch (cbor_get_uint8(key)) {
+ case 1:
+ if (cbor_isa_uint(val) == false ||
+ cbor_get_int(val) > INT_MAX || cose_key->kty != 0) {
+ fido_log_debug("%s: kty", __func__);
+ return (-1);
+ }
+
+ cose_key->kty = (int)cbor_get_int(val);
+
+ break;
+ case 3:
+ if (cbor_isa_negint(val) == false ||
+ cbor_get_int(val) > INT_MAX || cose_key->alg != 0) {
+ fido_log_debug("%s: alg", __func__);
+ return (-1);
+ }
+
+ cose_key->alg = -(int)cbor_get_int(val) - 1;
+
+ break;
+ }
+ } else if (cbor_isa_negint(key) == true &&
+ cbor_int_get_width(key) == CBOR_INT_8) {
+ if (cbor_get_uint8(key) == 0) {
+ /* get crv if not rsa, otherwise ignore */
+ if (cbor_isa_uint(val) == true &&
+ cbor_get_int(val) <= INT_MAX &&
+ cose_key->crv == 0)
+ cose_key->crv = (int)cbor_get_int(val);
+ }
+ }
+
+ return (0);
+}
+
+static int
+get_cose_alg(const cbor_item_t *item, int *cose_alg)
+{
+ struct cose_key cose_key;
+
+ memset(&cose_key, 0, sizeof(cose_key));
+
+ *cose_alg = 0;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, &cose_key, find_cose_alg) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ switch (cose_key.alg) {
+ case COSE_ES256:
+ if (cose_key.kty != COSE_KTY_EC2 ||
+ cose_key.crv != COSE_P256) {
+ fido_log_debug("%s: invalid kty/crv", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_ES384:
+ if (cose_key.kty != COSE_KTY_EC2 ||
+ cose_key.crv != COSE_P384) {
+ fido_log_debug("%s: invalid kty/crv", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_EDDSA:
+ if (cose_key.kty != COSE_KTY_OKP ||
+ cose_key.crv != COSE_ED25519) {
+ fido_log_debug("%s: invalid kty/crv", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_RS256:
+ if (cose_key.kty != COSE_KTY_RSA) {
+ fido_log_debug("%s: invalid kty/crv", __func__);
+ return (-1);
+ }
+ break;
+ default:
+ fido_log_debug("%s: unknown alg %d", __func__, cose_key.alg);
+
+ return (-1);
+ }
+
+ *cose_alg = cose_key.alg;
+
+ return (0);
+}
+
+int
+cbor_decode_pubkey(const cbor_item_t *item, int *type, void *key)
+{
+ if (get_cose_alg(item, type) < 0) {
+ fido_log_debug("%s: get_cose_alg", __func__);
+ return (-1);
+ }
+
+ switch (*type) {
+ case COSE_ES256:
+ if (es256_pk_decode(item, key) < 0) {
+ fido_log_debug("%s: es256_pk_decode", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_ES384:
+ if (es384_pk_decode(item, key) < 0) {
+ fido_log_debug("%s: es384_pk_decode", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_RS256:
+ if (rs256_pk_decode(item, key) < 0) {
+ fido_log_debug("%s: rs256_pk_decode", __func__);
+ return (-1);
+ }
+ break;
+ case COSE_EDDSA:
+ if (eddsa_pk_decode(item, key) < 0) {
+ fido_log_debug("%s: eddsa_pk_decode", __func__);
+ return (-1);
+ }
+ break;
+ default:
+ fido_log_debug("%s: invalid cose_alg %d", __func__, *type);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_attcred(const unsigned char **buf, size_t *len, int cose_alg,
+ fido_attcred_t *attcred)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ uint16_t id_len;
+ int ok = -1;
+
+ fido_log_xxd(*buf, *len, "%s", __func__);
+
+ if (fido_buf_read(buf, len, &attcred->aaguid,
+ sizeof(attcred->aaguid)) < 0) {
+ fido_log_debug("%s: fido_buf_read aaguid", __func__);
+ return (-1);
+ }
+
+ if (fido_buf_read(buf, len, &id_len, sizeof(id_len)) < 0) {
+ fido_log_debug("%s: fido_buf_read id_len", __func__);
+ return (-1);
+ }
+
+ attcred->id.len = (size_t)be16toh(id_len);
+ if ((attcred->id.ptr = malloc(attcred->id.len)) == NULL)
+ return (-1);
+
+ fido_log_debug("%s: attcred->id.len=%zu", __func__, attcred->id.len);
+
+ if (fido_buf_read(buf, len, attcred->id.ptr, attcred->id.len) < 0) {
+ fido_log_debug("%s: fido_buf_read id", __func__);
+ return (-1);
+ }
+
+ if ((item = cbor_load(*buf, *len, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ goto fail;
+ }
+
+ if (cbor_decode_pubkey(item, &attcred->type, &attcred->pubkey) < 0) {
+ fido_log_debug("%s: cbor_decode_pubkey", __func__);
+ goto fail;
+ }
+
+ if (attcred->type != cose_alg) {
+ fido_log_debug("%s: cose_alg mismatch (%d != %d)", __func__,
+ attcred->type, cose_alg);
+ goto fail;
+ }
+
+ *buf += cbor.read;
+ *len -= cbor.read;
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+static int
+decode_cred_extension(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cred_ext_t *authdata_ext = arg;
+ char *type = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &type) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (strcmp(type, "hmac-secret") == 0) {
+ if (cbor_decode_bool(val, NULL) < 0) {
+ fido_log_debug("%s: cbor_decode_bool", __func__);
+ goto out;
+ }
+ if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE)
+ authdata_ext->mask |= FIDO_EXT_HMAC_SECRET;
+ } else if (strcmp(type, "credProtect") == 0) {
+ if (cbor_isa_uint(val) == false ||
+ cbor_int_get_width(val) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ goto out;
+ }
+ authdata_ext->mask |= FIDO_EXT_CRED_PROTECT;
+ authdata_ext->prot = cbor_get_uint8(val);
+ } else if (strcmp(type, "credBlob") == 0) {
+ if (cbor_decode_bool(val, NULL) < 0) {
+ fido_log_debug("%s: cbor_decode_bool", __func__);
+ goto out;
+ }
+ if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE)
+ authdata_ext->mask |= FIDO_EXT_CRED_BLOB;
+ } else if (strcmp(type, "minPinLength") == 0) {
+ if (cbor_isa_uint(val) == false ||
+ cbor_int_get_width(val) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ goto out;
+ }
+ authdata_ext->mask |= FIDO_EXT_MINPINLEN;
+ authdata_ext->minpinlen = cbor_get_uint8(val);
+ }
+
+ ok = 0;
+out:
+ free(type);
+
+ return (ok);
+}
+
+static int
+decode_cred_extensions(const unsigned char **buf, size_t *len,
+ fido_cred_ext_t *authdata_ext)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int ok = -1;
+
+ memset(authdata_ext, 0, sizeof(*authdata_ext));
+
+ fido_log_xxd(*buf, *len, "%s", __func__);
+
+ if ((item = cbor_load(*buf, *len, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ goto fail;
+ }
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, authdata_ext, decode_cred_extension) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ goto fail;
+ }
+
+ *buf += cbor.read;
+ *len -= cbor.read;
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+static int
+decode_assert_extension(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_assert_extattr_t *authdata_ext = arg;
+ char *type = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &type) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (strcmp(type, "hmac-secret") == 0) {
+ if (fido_blob_decode(val, &authdata_ext->hmac_secret_enc) < 0) {
+ fido_log_debug("%s: fido_blob_decode", __func__);
+ goto out;
+ }
+ authdata_ext->mask |= FIDO_EXT_HMAC_SECRET;
+ } else if (strcmp(type, "credBlob") == 0) {
+ if (fido_blob_decode(val, &authdata_ext->blob) < 0) {
+ fido_log_debug("%s: fido_blob_decode", __func__);
+ goto out;
+ }
+ authdata_ext->mask |= FIDO_EXT_CRED_BLOB;
+ }
+
+ ok = 0;
+out:
+ free(type);
+
+ return (ok);
+}
+
+static int
+decode_assert_extensions(const unsigned char **buf, size_t *len,
+ fido_assert_extattr_t *authdata_ext)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int ok = -1;
+
+ fido_log_xxd(*buf, *len, "%s", __func__);
+
+ if ((item = cbor_load(*buf, *len, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ goto fail;
+ }
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, authdata_ext, decode_assert_extension) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ goto fail;
+ }
+
+ *buf += cbor.read;
+ *len -= cbor.read;
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return (ok);
+}
+
+int
+cbor_decode_cred_authdata(const cbor_item_t *item, int cose_alg,
+ fido_blob_t *authdata_cbor, fido_authdata_t *authdata,
+ fido_attcred_t *attcred, fido_cred_ext_t *authdata_ext)
+{
+ const unsigned char *buf = NULL;
+ size_t len;
+ size_t alloc_len;
+
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (authdata_cbor->ptr != NULL ||
+ (authdata_cbor->len = cbor_serialize_alloc(item,
+ &authdata_cbor->ptr, &alloc_len)) == 0) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ return (-1);
+ }
+
+ buf = cbor_bytestring_handle(item);
+ len = cbor_bytestring_length(item);
+ fido_log_xxd(buf, len, "%s", __func__);
+
+ if (fido_buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) {
+ fido_log_debug("%s: fido_buf_read", __func__);
+ return (-1);
+ }
+
+ authdata->sigcount = be32toh(authdata->sigcount);
+
+ if (attcred != NULL) {
+ if ((authdata->flags & CTAP_AUTHDATA_ATT_CRED) == 0 ||
+ decode_attcred(&buf, &len, cose_alg, attcred) < 0)
+ return (-1);
+ }
+
+ if (authdata_ext != NULL) {
+ if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0 &&
+ decode_cred_extensions(&buf, &len, authdata_ext) < 0)
+ return (-1);
+ }
+
+ /* XXX we should probably ensure that len == 0 at this point */
+
+ return (FIDO_OK);
+}
+
+int
+cbor_decode_assert_authdata(const cbor_item_t *item, fido_blob_t *authdata_cbor,
+ fido_authdata_t *authdata, fido_assert_extattr_t *authdata_ext)
+{
+ const unsigned char *buf = NULL;
+ size_t len;
+ size_t alloc_len;
+
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (authdata_cbor->ptr != NULL ||
+ (authdata_cbor->len = cbor_serialize_alloc(item,
+ &authdata_cbor->ptr, &alloc_len)) == 0) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ return (-1);
+ }
+
+ buf = cbor_bytestring_handle(item);
+ len = cbor_bytestring_length(item);
+
+ fido_log_debug("%s: buf=%p, len=%zu", __func__, (const void *)buf, len);
+
+ if (fido_buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) {
+ fido_log_debug("%s: fido_buf_read", __func__);
+ return (-1);
+ }
+
+ authdata->sigcount = be32toh(authdata->sigcount);
+
+ if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0) {
+ if (decode_assert_extensions(&buf, &len, authdata_ext) < 0) {
+ fido_log_debug("%s: decode_assert_extensions",
+ __func__);
+ return (-1);
+ }
+ }
+
+ /* XXX we should probably ensure that len == 0 at this point */
+
+ return (FIDO_OK);
+}
+
+static int
+decode_x5c(const cbor_item_t *item, void *arg)
+{
+ fido_blob_t *x5c = arg;
+
+ if (x5c->len)
+ return (0); /* ignore */
+
+ return (fido_blob_decode(item, x5c));
+}
+
+static int
+decode_attstmt_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_attstmt_t *attstmt = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "alg")) {
+ if (cbor_isa_negint(val) == false ||
+ cbor_get_int(val) > UINT16_MAX) {
+ fido_log_debug("%s: alg", __func__);
+ goto out;
+ }
+ attstmt->alg = -(int)cbor_get_int(val) - 1;
+ if (attstmt->alg != COSE_ES256 && attstmt->alg != COSE_ES384 &&
+ attstmt->alg != COSE_RS256 && attstmt->alg != COSE_EDDSA &&
+ attstmt->alg != COSE_RS1) {
+ fido_log_debug("%s: unsupported attstmt->alg=%d",
+ __func__, attstmt->alg);
+ goto out;
+ }
+ } else if (!strcmp(name, "sig")) {
+ if (fido_blob_decode(val, &attstmt->sig) < 0) {
+ fido_log_debug("%s: sig", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "x5c")) {
+ if (cbor_isa_array(val) == false ||
+ cbor_array_is_definite(val) == false ||
+ cbor_array_iter(val, &attstmt->x5c, decode_x5c) < 0) {
+ fido_log_debug("%s: x5c", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "certInfo")) {
+ if (fido_blob_decode(val, &attstmt->certinfo) < 0) {
+ fido_log_debug("%s: certinfo", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "pubArea")) {
+ if (fido_blob_decode(val, &attstmt->pubarea) < 0) {
+ fido_log_debug("%s: pubarea", __func__);
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+cbor_decode_attstmt(const cbor_item_t *item, fido_attstmt_t *attstmt)
+{
+ size_t alloc_len;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, attstmt, decode_attstmt_entry) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (attstmt->cbor.ptr != NULL ||
+ (attstmt->cbor.len = cbor_serialize_alloc(item,
+ &attstmt->cbor.ptr, &alloc_len)) == 0) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+cbor_decode_uint64(const cbor_item_t *item, uint64_t *n)
+{
+ if (cbor_isa_uint(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ *n = cbor_get_int(item);
+
+ return (0);
+}
+
+static int
+decode_cred_id_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_blob_t *id = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "id"))
+ if (fido_blob_decode(val, id) < 0) {
+ fido_log_debug("%s: cbor_bytestring_copy", __func__);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+cbor_decode_cred_id(const cbor_item_t *item, fido_blob_t *id)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, id, decode_cred_id_entry) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_user_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_user_t *user = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "icon")) {
+ if (cbor_string_copy(val, &user->icon) < 0) {
+ fido_log_debug("%s: icon", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "name")) {
+ if (cbor_string_copy(val, &user->name) < 0) {
+ fido_log_debug("%s: name", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "displayName")) {
+ if (cbor_string_copy(val, &user->display_name) < 0) {
+ fido_log_debug("%s: display_name", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "id")) {
+ if (fido_blob_decode(val, &user->id) < 0) {
+ fido_log_debug("%s: id", __func__);
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+cbor_decode_user(const cbor_item_t *item, fido_user_t *user)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, user, decode_user_entry) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_rp_entity_entry(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_rp_t *rp = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "id")) {
+ if (cbor_string_copy(val, &rp->id) < 0) {
+ fido_log_debug("%s: id", __func__);
+ goto out;
+ }
+ } else if (!strcmp(name, "name")) {
+ if (cbor_string_copy(val, &rp->name) < 0) {
+ fido_log_debug("%s: name", __func__);
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+int
+cbor_decode_rp_entity(const cbor_item_t *item, fido_rp_t *rp)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, rp, decode_rp_entity_entry) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+cbor_decode_bool(const cbor_item_t *item, bool *v)
+{
+ if (cbor_isa_float_ctrl(item) == false ||
+ cbor_float_get_width(item) != CBOR_FLOAT_0 ||
+ cbor_is_bool(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ if (v != NULL)
+ *v = cbor_ctrl_value(item) == CBOR_CTRL_TRUE;
+
+ return (0);
+}
+
+cbor_item_t *
+cbor_build_uint(const uint64_t value)
+{
+ if (value <= UINT8_MAX)
+ return cbor_build_uint8((uint8_t)value);
+ else if (value <= UINT16_MAX)
+ return cbor_build_uint16((uint16_t)value);
+ else if (value <= UINT32_MAX)
+ return cbor_build_uint32((uint32_t)value);
+
+ return cbor_build_uint64(value);
+}
+
+int
+cbor_array_append(cbor_item_t **array, cbor_item_t *item)
+{
+ cbor_item_t **v, *ret;
+ size_t n;
+
+ if ((v = cbor_array_handle(*array)) == NULL ||
+ (n = cbor_array_size(*array)) == SIZE_MAX ||
+ (ret = cbor_new_definite_array(n + 1)) == NULL)
+ return -1;
+ for (size_t i = 0; i < n; i++) {
+ if (cbor_array_push(ret, v[i]) == 0) {
+ cbor_decref(&ret);
+ return -1;
+ }
+ }
+ if (cbor_array_push(ret, item) == 0) {
+ cbor_decref(&ret);
+ return -1;
+ }
+ cbor_decref(array);
+ *array = ret;
+
+ return 0;
+}
+
+int
+cbor_array_drop(cbor_item_t **array, size_t idx)
+{
+ cbor_item_t **v, *ret;
+ size_t n;
+
+ if ((v = cbor_array_handle(*array)) == NULL ||
+ (n = cbor_array_size(*array)) == 0 || idx >= n ||
+ (ret = cbor_new_definite_array(n - 1)) == NULL)
+ return -1;
+ for (size_t i = 0; i < n; i++) {
+ if (i != idx && cbor_array_push(ret, v[i]) == 0) {
+ cbor_decref(&ret);
+ return -1;
+ }
+ }
+ cbor_decref(array);
+ *array = ret;
+
+ return 0;
+}
diff --git a/src/compress.c b/src/compress.c
new file mode 100644
index 0000000..3be6fd5
--- /dev/null
+++ b/src/compress.c
@@ -0,0 +1,168 @@
+/*
+ * 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 <zlib.h>
+#include "fido.h"
+
+#define BOUND (1024UL * 1024UL)
+
+/* zlib inflate (raw + headers) */
+static int
+rfc1950_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz)
+{
+ u_long ilen, olen;
+ int z;
+
+ memset(out, 0, sizeof(*out));
+
+ if (in->len > ULONG_MAX || (ilen = (u_long)in->len) > BOUND ||
+ origsiz > ULONG_MAX || (olen = (u_long)origsiz) > BOUND) {
+ fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__,
+ in->len, origsiz);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+
+ if ((out->ptr = calloc(1, olen)) == NULL)
+ return FIDO_ERR_INTERNAL;
+ out->len = olen;
+
+ if ((z = uncompress(out->ptr, &olen, in->ptr, ilen)) != Z_OK ||
+ olen > SIZE_MAX || olen != out->len) {
+ fido_log_debug("%s: uncompress: %d, olen=%lu, out->len=%zu",
+ __func__, z, olen, out->len);
+ fido_blob_reset(out);
+ return FIDO_ERR_COMPRESS;
+ }
+
+ return FIDO_OK;
+}
+
+/* raw inflate */
+static int
+rfc1951_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz)
+{
+ z_stream zs;
+ u_int ilen, olen;
+ int r, z;
+
+ memset(&zs, 0, sizeof(zs));
+ memset(out, 0, sizeof(*out));
+
+ if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND ||
+ origsiz > UINT_MAX || (olen = (u_int)origsiz) > BOUND) {
+ fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__,
+ in->len, origsiz);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if ((z = inflateInit2(&zs, -MAX_WBITS)) != Z_OK) {
+ fido_log_debug("%s: inflateInit2: %d", __func__, z);
+ return FIDO_ERR_COMPRESS;
+ }
+
+ if ((out->ptr = calloc(1, olen)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ out->len = olen;
+ zs.next_in = in->ptr;
+ zs.avail_in = ilen;
+ zs.next_out = out->ptr;
+ zs.avail_out = olen;
+
+ if ((z = inflate(&zs, Z_FINISH)) != Z_STREAM_END) {
+ fido_log_debug("%s: inflate: %d", __func__, z);
+ r = FIDO_ERR_COMPRESS;
+ goto fail;
+ }
+ if (zs.avail_out != 0) {
+ fido_log_debug("%s: %u != 0", __func__, zs.avail_out);
+ r = FIDO_ERR_COMPRESS;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if ((z = inflateEnd(&zs)) != Z_OK) {
+ fido_log_debug("%s: inflateEnd: %d", __func__, z);
+ r = FIDO_ERR_COMPRESS;
+ }
+ if (r != FIDO_OK)
+ fido_blob_reset(out);
+
+ return r;
+}
+
+/* raw deflate */
+static int
+rfc1951_deflate(fido_blob_t *out, const fido_blob_t *in)
+{
+ z_stream zs;
+ u_int ilen, olen;
+ int r, z;
+
+ memset(&zs, 0, sizeof(zs));
+ memset(out, 0, sizeof(*out));
+
+ if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND) {
+ fido_log_debug("%s: in->len=%zu", __func__, in->len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if ((z = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -MAX_WBITS, 8, Z_DEFAULT_STRATEGY)) != Z_OK) {
+ fido_log_debug("%s: deflateInit2: %d", __func__, z);
+ return FIDO_ERR_COMPRESS;
+ }
+
+ olen = BOUND;
+ if ((out->ptr = calloc(1, olen)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ out->len = olen;
+ zs.next_in = in->ptr;
+ zs.avail_in = ilen;
+ zs.next_out = out->ptr;
+ zs.avail_out = olen;
+
+ if ((z = deflate(&zs, Z_FINISH)) != Z_STREAM_END) {
+ fido_log_debug("%s: inflate: %d", __func__, z);
+ r = FIDO_ERR_COMPRESS;
+ goto fail;
+ }
+ if (zs.avail_out >= out->len) {
+ fido_log_debug("%s: %u > %zu", __func__, zs.avail_out,
+ out->len);
+ r = FIDO_ERR_COMPRESS;
+ goto fail;
+ }
+ out->len -= zs.avail_out;
+
+ r = FIDO_OK;
+fail:
+ if ((z = deflateEnd(&zs)) != Z_OK) {
+ fido_log_debug("%s: deflateEnd: %d", __func__, z);
+ r = FIDO_ERR_COMPRESS;
+ }
+ if (r != FIDO_OK)
+ fido_blob_reset(out);
+
+ return r;
+}
+
+int
+fido_compress(fido_blob_t *out, const fido_blob_t *in)
+{
+ return rfc1951_deflate(out, in);
+}
+
+int
+fido_uncompress(fido_blob_t *out, const fido_blob_t *in, size_t origsiz)
+{
+ if (rfc1950_inflate(out, in, origsiz) == FIDO_OK)
+ return FIDO_OK; /* backwards compat with libfido2 < 1.11 */
+ return rfc1951_inflate(out, in, origsiz);
+}
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..5302e11
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,235 @@
+/*
+ * 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 "fido.h"
+#include "fido/config.h"
+#include "fido/es256.h"
+
+#define CMD_ENABLE_ENTATTEST 0x01
+#define CMD_TOGGLE_ALWAYS_UV 0x02
+#define CMD_SET_PIN_MINLEN 0x03
+
+static int
+config_prepare_hmac(uint8_t subcmd, const cbor_item_t *item, fido_blob_t *hmac)
+{
+ uint8_t prefix[32 + 2 * sizeof(uint8_t)], cbor[128];
+ size_t cbor_len = 0;
+
+ memset(prefix, 0xff, sizeof(prefix));
+ prefix[sizeof(prefix) - 2] = CTAP_CBOR_CONFIG;
+ prefix[sizeof(prefix) - 1] = subcmd;
+
+ if (item != NULL) {
+ if ((cbor_len = cbor_serialize(item, cbor, sizeof(cbor))) == 0) {
+ fido_log_debug("%s: cbor_serialize", __func__);
+ return -1;
+ }
+ }
+ if ((hmac->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ return -1;
+ }
+ memcpy(hmac->ptr, prefix, sizeof(prefix));
+ memcpy(hmac->ptr + sizeof(prefix), cbor, cbor_len);
+ hmac->len = cbor_len + sizeof(prefix);
+
+ return 0;
+}
+
+static int
+config_tx(fido_dev_t *dev, uint8_t subcmd, cbor_item_t **paramv, size_t paramc,
+ const char *pin, int *ms)
+{
+ cbor_item_t *argv[4];
+ es256_pk_t *pk = NULL;
+ fido_blob_t *ecdh = NULL, f, hmac;
+ const uint8_t cmd = CTAP_CBOR_CONFIG;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&f, 0, sizeof(f));
+ memset(&hmac, 0, sizeof(hmac));
+ memset(&argv, 0, sizeof(argv));
+
+ /* subCommand */
+ if ((argv[0] = cbor_build_uint8(subcmd)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ /* subCommandParams */
+ if (paramc != 0 &&
+ (argv[1] = cbor_flatten_vector(paramv, paramc)) == NULL) {
+ fido_log_debug("%s: cbor_flatten_vector", __func__);
+ goto fail;
+ }
+
+ /* pinProtocol, pinAuth */
+ if (pin != NULL ||
+ (fido_dev_supports_permissions(dev) && fido_dev_has_uv(dev))) {
+ if (config_prepare_hmac(subcmd, argv[1], &hmac) < 0) {
+ fido_log_debug("%s: config_prepare_hmac", __func__);
+ goto fail;
+ }
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin,
+ NULL, &argv[3], &argv[2], ms)) != FIDO_OK) {
+ fido_log_debug("%s: cbor_add_uv_params", __func__);
+ goto fail;
+ }
+ }
+
+ /* framing and transmission */
+ if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ free(f.ptr);
+ free(hmac.ptr);
+
+ return r;
+}
+
+static int
+config_enable_entattest_wait(fido_dev_t *dev, const char *pin, int *ms)
+{
+ int r;
+
+ if ((r = config_tx(dev, CMD_ENABLE_ENTATTEST, NULL, 0, pin,
+ ms)) != FIDO_OK)
+ return r;
+
+ return fido_rx_cbor_status(dev, ms);
+}
+
+int
+fido_dev_enable_entattest(fido_dev_t *dev, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return (config_enable_entattest_wait(dev, pin, &ms));
+}
+
+static int
+config_toggle_always_uv_wait(fido_dev_t *dev, const char *pin, int *ms)
+{
+ int r;
+
+ if ((r = config_tx(dev, CMD_TOGGLE_ALWAYS_UV, NULL, 0, pin,
+ ms)) != FIDO_OK)
+ return r;
+
+ return (fido_rx_cbor_status(dev, ms));
+}
+
+int
+fido_dev_toggle_always_uv(fido_dev_t *dev, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return config_toggle_always_uv_wait(dev, pin, &ms);
+}
+
+static int
+config_pin_minlen_tx(fido_dev_t *dev, size_t len, bool force,
+ const fido_str_array_t *rpid, const char *pin, int *ms)
+{
+ cbor_item_t *argv[3];
+ int r;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((rpid == NULL && len == 0 && !force) || len > UINT8_MAX) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+ if (len && (argv[0] = cbor_build_uint8((uint8_t)len)) == NULL) {
+ fido_log_debug("%s: cbor_encode_uint8", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if (rpid != NULL && (argv[1] = cbor_encode_str_array(rpid)) == NULL) {
+ fido_log_debug("%s: cbor_encode_str_array", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if (force && (argv[2] = cbor_build_bool(true)) == NULL) {
+ fido_log_debug("%s: cbor_build_bool", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if ((r = config_tx(dev, CMD_SET_PIN_MINLEN, argv, nitems(argv),
+ pin, ms)) != FIDO_OK) {
+ fido_log_debug("%s: config_tx", __func__);
+ goto fail;
+ }
+
+fail:
+ cbor_vector_free(argv, nitems(argv));
+
+ return r;
+}
+
+static int
+config_pin_minlen(fido_dev_t *dev, size_t len, bool force,
+ const fido_str_array_t *rpid, const char *pin, int *ms)
+{
+ int r;
+
+ if ((r = config_pin_minlen_tx(dev, len, force, rpid, pin,
+ ms)) != FIDO_OK)
+ return r;
+
+ return fido_rx_cbor_status(dev, ms);
+}
+
+int
+fido_dev_set_pin_minlen(fido_dev_t *dev, size_t len, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return config_pin_minlen(dev, len, false, NULL, pin, &ms);
+}
+
+int
+fido_dev_force_pin_change(fido_dev_t *dev, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return config_pin_minlen(dev, 0, true, NULL, pin, &ms);
+}
+
+int
+fido_dev_set_pin_minlen_rpid(fido_dev_t *dev, const char * const *rpid,
+ size_t n, const char *pin)
+{
+ fido_str_array_t sa;
+ int ms = dev->timeout_ms;
+ int r;
+
+ memset(&sa, 0, sizeof(sa));
+ if (fido_str_array_pack(&sa, rpid, n) < 0) {
+ fido_log_debug("%s: fido_str_array_pack", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ r = config_pin_minlen(dev, 0, false, &sa, pin, &ms);
+fail:
+ fido_str_array_free(&sa);
+
+ return r;
+}
diff --git a/src/cred.c b/src/cred.c
new file mode 100644
index 0000000..4a7a725
--- /dev/null
+++ b/src/cred.c
@@ -0,0 +1,1230 @@
+/*
+ * 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 <openssl/sha.h>
+#include <openssl/x509.h>
+
+#include "fido.h"
+#include "fido/es256.h"
+
+#ifndef FIDO_MAXMSG_CRED
+#define FIDO_MAXMSG_CRED 4096
+#endif
+
+static int
+parse_makecred_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cred_t *cred = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* fmt */
+ return (cbor_decode_fmt(val, &cred->fmt));
+ case 2: /* authdata */
+ if (fido_blob_decode(val, &cred->authdata_raw) < 0) {
+ fido_log_debug("%s: fido_blob_decode", __func__);
+ return (-1);
+ }
+ return (cbor_decode_cred_authdata(val, cred->type,
+ &cred->authdata_cbor, &cred->authdata, &cred->attcred,
+ &cred->authdata_ext));
+ case 3: /* attestation statement */
+ return (cbor_decode_attstmt(val, &cred->attstmt));
+ case 5: /* large blob key */
+ return (fido_blob_decode(val, &cred->largeblob_key));
+ default: /* ignore */
+ fido_log_debug("%s: cbor type", __func__);
+ return (0);
+ }
+}
+
+static int
+fido_dev_make_cred_tx(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
+ int *ms)
+{
+ fido_blob_t f;
+ fido_blob_t *ecdh = NULL;
+ fido_opt_t uv = cred->uv;
+ es256_pk_t *pk = NULL;
+ cbor_item_t *argv[9];
+ const uint8_t cmd = CTAP_CBOR_MAKECRED;
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if (cred->cdh.ptr == NULL || cred->type == 0) {
+ fido_log_debug("%s: cdh=%p, type=%d", __func__,
+ (void *)cred->cdh.ptr, cred->type);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((argv[0] = fido_blob_encode(&cred->cdh)) == NULL ||
+ (argv[1] = cbor_encode_rp_entity(&cred->rp)) == NULL ||
+ (argv[2] = cbor_encode_user_entity(&cred->user)) == NULL ||
+ (argv[3] = cbor_encode_pubkey_param(cred->type)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* excluded credentials */
+ if (cred->excl.len)
+ if ((argv[4] = cbor_encode_pubkey_list(&cred->excl)) == NULL) {
+ fido_log_debug("%s: cbor_encode_pubkey_list", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* extensions */
+ if (cred->ext.mask)
+ if ((argv[5] = cbor_encode_cred_ext(&cred->ext,
+ &cred->blob)) == NULL) {
+ fido_log_debug("%s: cbor_encode_cred_ext", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* user verification */
+ if (pin != NULL || (uv == FIDO_OPT_TRUE &&
+ fido_dev_supports_permissions(dev))) {
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = cbor_add_uv_params(dev, cmd, &cred->cdh, pk, ecdh,
+ pin, cred->rp.id, &argv[7], &argv[8], ms)) != FIDO_OK) {
+ fido_log_debug("%s: cbor_add_uv_params", __func__);
+ goto fail;
+ }
+ uv = FIDO_OPT_OMIT;
+ }
+
+ /* options */
+ if (cred->rk != FIDO_OPT_OMIT || uv != FIDO_OPT_OMIT)
+ if ((argv[6] = cbor_encode_cred_opt(cred->rk, uv)) == NULL) {
+ fido_log_debug("%s: cbor_encode_cred_opt", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ /* framing and transmission */
+ if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_make_cred_rx(fido_dev_t *dev, fido_cred_t *cred, int *ms)
+{
+ unsigned char *reply;
+ int reply_len;
+ int r;
+
+ fido_cred_reset_rx(cred);
+
+ if ((reply = malloc(FIDO_MAXMSG_CRED)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, reply, FIDO_MAXMSG_CRED,
+ ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if ((r = cbor_parse_reply(reply, (size_t)reply_len, cred,
+ parse_makecred_reply)) != FIDO_OK) {
+ fido_log_debug("%s: parse_makecred_reply", __func__);
+ goto fail;
+ }
+
+ if (cred->fmt == NULL || fido_blob_is_empty(&cred->authdata_cbor) ||
+ fido_blob_is_empty(&cred->attcred.id)) {
+ r = FIDO_ERR_INVALID_CBOR;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ free(reply);
+
+ if (r != FIDO_OK)
+ fido_cred_reset_rx(cred);
+
+ return (r);
+}
+
+static int
+fido_dev_make_cred_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
+ int *ms)
+{
+ int r;
+
+ if ((r = fido_dev_make_cred_tx(dev, cred, pin, ms)) != FIDO_OK ||
+ (r = fido_dev_make_cred_rx(dev, cred, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+#ifdef USE_WINHELLO
+ if (dev->flags & FIDO_DEV_WINHELLO)
+ return (fido_winhello_make_cred(dev, cred, pin, ms));
+#endif
+ if (fido_dev_is_fido2(dev) == false) {
+ if (pin != NULL || cred->rk == FIDO_OPT_TRUE ||
+ cred->ext.mask != 0)
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ return (u2f_register(dev, cred, &ms));
+ }
+
+ return (fido_dev_make_cred_wait(dev, cred, pin, &ms));
+}
+
+static int
+check_extensions(const fido_cred_ext_t *authdata_ext,
+ const fido_cred_ext_t *ext)
+{
+ fido_cred_ext_t tmp;
+
+ /* XXX: largeBlobKey is not part of the extensions map */
+ memcpy(&tmp, ext, sizeof(tmp));
+ tmp.mask &= ~FIDO_EXT_LARGEBLOB_KEY;
+
+ return (timingsafe_bcmp(authdata_ext, &tmp, sizeof(*authdata_ext)));
+}
+
+int
+fido_check_rp_id(const char *id, const unsigned char *obtained_hash)
+{
+ unsigned char expected_hash[SHA256_DIGEST_LENGTH];
+
+ explicit_bzero(expected_hash, sizeof(expected_hash));
+
+ if (SHA256((const unsigned char *)id, strlen(id),
+ expected_hash) != expected_hash) {
+ fido_log_debug("%s: sha256", __func__);
+ return (-1);
+ }
+
+ return (timingsafe_bcmp(expected_hash, obtained_hash,
+ SHA256_DIGEST_LENGTH));
+}
+
+static int
+get_signed_hash_u2f(fido_blob_t *dgst, const unsigned char *rp_id,
+ size_t rp_id_len, const fido_blob_t *clientdata, const fido_blob_t *id,
+ const es256_pk_t *pk)
+{
+ const uint8_t zero = 0;
+ const uint8_t four = 4; /* uncompressed point */
+ const EVP_MD *md = NULL;
+ EVP_MD_CTX *ctx = NULL;
+ int ok = -1;
+
+ if (dgst->len < SHA256_DIGEST_LENGTH ||
+ (md = EVP_sha256()) == NULL ||
+ (ctx = EVP_MD_CTX_new()) == NULL ||
+ EVP_DigestInit_ex(ctx, md, NULL) != 1 ||
+ EVP_DigestUpdate(ctx, &zero, sizeof(zero)) != 1 ||
+ EVP_DigestUpdate(ctx, rp_id, rp_id_len) != 1 ||
+ EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 ||
+ EVP_DigestUpdate(ctx, id->ptr, id->len) != 1 ||
+ EVP_DigestUpdate(ctx, &four, sizeof(four)) != 1 ||
+ EVP_DigestUpdate(ctx, pk->x, sizeof(pk->x)) != 1 ||
+ EVP_DigestUpdate(ctx, pk->y, sizeof(pk->y)) != 1 ||
+ EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) {
+ fido_log_debug("%s: sha256", __func__);
+ goto fail;
+ }
+ dgst->len = SHA256_DIGEST_LENGTH;
+
+ ok = 0;
+fail:
+ EVP_MD_CTX_free(ctx);
+
+ return (ok);
+}
+
+static int
+verify_attstmt(const fido_blob_t *dgst, const fido_attstmt_t *attstmt)
+{
+ BIO *rawcert = NULL;
+ X509 *cert = NULL;
+ EVP_PKEY *pkey = NULL;
+ int ok = -1;
+
+ /* openssl needs ints */
+ if (attstmt->x5c.len > INT_MAX) {
+ fido_log_debug("%s: x5c.len=%zu", __func__, attstmt->x5c.len);
+ return (-1);
+ }
+
+ /* fetch key from x509 */
+ if ((rawcert = BIO_new_mem_buf(attstmt->x5c.ptr,
+ (int)attstmt->x5c.len)) == NULL ||
+ (cert = d2i_X509_bio(rawcert, NULL)) == NULL ||
+ (pkey = X509_get_pubkey(cert)) == NULL) {
+ fido_log_debug("%s: x509 key", __func__);
+ goto fail;
+ }
+
+ switch (attstmt->alg) {
+ case COSE_UNSPEC:
+ case COSE_ES256:
+ ok = es256_verify_sig(dgst, pkey, &attstmt->sig);
+ break;
+ case COSE_ES384:
+ ok = es384_verify_sig(dgst, pkey, &attstmt->sig);
+ break;
+ case COSE_RS256:
+ ok = rs256_verify_sig(dgst, pkey, &attstmt->sig);
+ break;
+ case COSE_RS1:
+ ok = rs1_verify_sig(dgst, pkey, &attstmt->sig);
+ break;
+ case COSE_EDDSA:
+ ok = eddsa_verify_sig(dgst, pkey, &attstmt->sig);
+ break;
+ default:
+ fido_log_debug("%s: unknown alg %d", __func__, attstmt->alg);
+ break;
+ }
+
+fail:
+ BIO_free(rawcert);
+ X509_free(cert);
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
+
+int
+fido_cred_verify(const fido_cred_t *cred)
+{
+ unsigned char buf[1024]; /* XXX */
+ fido_blob_t dgst;
+ int cose_alg;
+ int r;
+
+ dgst.ptr = buf;
+ dgst.len = sizeof(buf);
+
+ /* do we have everything we need? */
+ if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL ||
+ cred->attstmt.x5c.ptr == NULL || cred->attstmt.sig.ptr == NULL ||
+ cred->fmt == NULL || cred->attcred.id.ptr == NULL ||
+ cred->rp.id == NULL) {
+ fido_log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, "
+ "fmt=%p id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr,
+ (void *)cred->authdata_cbor.ptr,
+ (void *)cred->attstmt.x5c.ptr,
+ (void *)cred->attstmt.sig.ptr, (void *)cred->fmt,
+ (void *)cred->attcred.id.ptr, cred->rp.id);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) {
+ fido_log_debug("%s: fido_check_rp_id", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE,
+ cred->uv) < 0) {
+ fido_log_debug("%s: fido_check_flags", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_extensions(&cred->authdata_ext, &cred->ext) != 0) {
+ fido_log_debug("%s: check_extensions", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if ((cose_alg = cred->attstmt.alg) == COSE_UNSPEC)
+ cose_alg = COSE_ES256; /* backwards compat */
+
+ if (!strcmp(cred->fmt, "packed")) {
+ if (fido_get_signed_hash(cose_alg, &dgst, &cred->cdh,
+ &cred->authdata_cbor) < 0) {
+ fido_log_debug("%s: fido_get_signed_hash", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ } else if (!strcmp(cred->fmt, "fido-u2f")) {
+ if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash,
+ sizeof(cred->authdata.rp_id_hash), &cred->cdh,
+ &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) {
+ fido_log_debug("%s: get_signed_hash_u2f", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ } else if (!strcmp(cred->fmt, "tpm")) {
+ if (fido_get_signed_hash_tpm(&dgst, &cred->cdh,
+ &cred->authdata_raw, &cred->attstmt, &cred->attcred) < 0) {
+ fido_log_debug("%s: fido_get_signed_hash_tpm", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ } else {
+ fido_log_debug("%s: unknown fmt %s", __func__, cred->fmt);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if (verify_attstmt(&dgst, &cred->attstmt) < 0) {
+ fido_log_debug("%s: verify_attstmt", __func__);
+ r = FIDO_ERR_INVALID_SIG;
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ explicit_bzero(buf, sizeof(buf));
+
+ return (r);
+}
+
+int
+fido_cred_verify_self(const fido_cred_t *cred)
+{
+ unsigned char buf[1024]; /* XXX */
+ fido_blob_t dgst;
+ int ok = -1;
+ int r;
+
+ dgst.ptr = buf;
+ dgst.len = sizeof(buf);
+
+ /* do we have everything we need? */
+ if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL ||
+ cred->attstmt.x5c.ptr != NULL || cred->attstmt.sig.ptr == NULL ||
+ cred->fmt == NULL || cred->attcred.id.ptr == NULL ||
+ cred->rp.id == NULL) {
+ fido_log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, "
+ "fmt=%p id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr,
+ (void *)cred->authdata_cbor.ptr,
+ (void *)cred->attstmt.x5c.ptr,
+ (void *)cred->attstmt.sig.ptr, (void *)cred->fmt,
+ (void *)cred->attcred.id.ptr, cred->rp.id);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) {
+ fido_log_debug("%s: fido_check_rp_id", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE,
+ cred->uv) < 0) {
+ fido_log_debug("%s: fido_check_flags", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (check_extensions(&cred->authdata_ext, &cred->ext) != 0) {
+ fido_log_debug("%s: check_extensions", __func__);
+ r = FIDO_ERR_INVALID_PARAM;
+ goto out;
+ }
+
+ if (!strcmp(cred->fmt, "packed")) {
+ if (fido_get_signed_hash(cred->attcred.type, &dgst, &cred->cdh,
+ &cred->authdata_cbor) < 0) {
+ fido_log_debug("%s: fido_get_signed_hash", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ } else if (!strcmp(cred->fmt, "fido-u2f")) {
+ if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash,
+ sizeof(cred->authdata.rp_id_hash), &cred->cdh,
+ &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) {
+ fido_log_debug("%s: get_signed_hash_u2f", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ } else {
+ fido_log_debug("%s: unknown fmt %s", __func__, cred->fmt);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ switch (cred->attcred.type) {
+ case COSE_ES256:
+ ok = es256_pk_verify_sig(&dgst, &cred->attcred.pubkey.es256,
+ &cred->attstmt.sig);
+ break;
+ case COSE_ES384:
+ ok = es384_pk_verify_sig(&dgst, &cred->attcred.pubkey.es384,
+ &cred->attstmt.sig);
+ break;
+ case COSE_RS256:
+ ok = rs256_pk_verify_sig(&dgst, &cred->attcred.pubkey.rs256,
+ &cred->attstmt.sig);
+ break;
+ case COSE_EDDSA:
+ ok = eddsa_pk_verify_sig(&dgst, &cred->attcred.pubkey.eddsa,
+ &cred->attstmt.sig);
+ break;
+ default:
+ fido_log_debug("%s: unsupported cose_alg %d", __func__,
+ cred->attcred.type);
+ r = FIDO_ERR_UNSUPPORTED_OPTION;
+ goto out;
+ }
+
+ if (ok < 0)
+ r = FIDO_ERR_INVALID_SIG;
+ else
+ r = FIDO_OK;
+
+out:
+ explicit_bzero(buf, sizeof(buf));
+
+ return (r);
+}
+
+fido_cred_t *
+fido_cred_new(void)
+{
+ return (calloc(1, sizeof(fido_cred_t)));
+}
+
+static void
+fido_cred_clean_authdata(fido_cred_t *cred)
+{
+ fido_blob_reset(&cred->authdata_cbor);
+ fido_blob_reset(&cred->authdata_raw);
+ fido_blob_reset(&cred->attcred.id);
+
+ memset(&cred->authdata_ext, 0, sizeof(cred->authdata_ext));
+ memset(&cred->authdata, 0, sizeof(cred->authdata));
+ memset(&cred->attcred, 0, sizeof(cred->attcred));
+}
+
+static void
+fido_cred_clean_attstmt(fido_attstmt_t *attstmt)
+{
+ fido_blob_reset(&attstmt->certinfo);
+ fido_blob_reset(&attstmt->pubarea);
+ fido_blob_reset(&attstmt->cbor);
+ fido_blob_reset(&attstmt->x5c);
+ fido_blob_reset(&attstmt->sig);
+
+ memset(attstmt, 0, sizeof(*attstmt));
+}
+
+void
+fido_cred_reset_tx(fido_cred_t *cred)
+{
+ fido_blob_reset(&cred->cd);
+ fido_blob_reset(&cred->cdh);
+ fido_blob_reset(&cred->user.id);
+ fido_blob_reset(&cred->blob);
+
+ free(cred->rp.id);
+ free(cred->rp.name);
+ free(cred->user.icon);
+ free(cred->user.name);
+ free(cred->user.display_name);
+ fido_cred_empty_exclude_list(cred);
+
+ memset(&cred->rp, 0, sizeof(cred->rp));
+ memset(&cred->user, 0, sizeof(cred->user));
+ memset(&cred->ext, 0, sizeof(cred->ext));
+
+ cred->type = 0;
+ cred->rk = FIDO_OPT_OMIT;
+ cred->uv = FIDO_OPT_OMIT;
+}
+
+void
+fido_cred_reset_rx(fido_cred_t *cred)
+{
+ free(cred->fmt);
+ cred->fmt = NULL;
+ fido_cred_clean_authdata(cred);
+ fido_cred_clean_attstmt(&cred->attstmt);
+ fido_blob_reset(&cred->largeblob_key);
+}
+
+void
+fido_cred_free(fido_cred_t **cred_p)
+{
+ fido_cred_t *cred;
+
+ if (cred_p == NULL || (cred = *cred_p) == NULL)
+ return;
+ fido_cred_reset_tx(cred);
+ fido_cred_reset_rx(cred);
+ free(cred);
+ *cred_p = NULL;
+}
+
+int
+fido_cred_set_authdata(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int r = FIDO_ERR_INVALID_ARGUMENT;
+
+ fido_cred_clean_authdata(cred);
+
+ if (ptr == NULL || len == 0)
+ goto fail;
+
+ if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ goto fail;
+ }
+
+ if (fido_blob_decode(item, &cred->authdata_raw) < 0) {
+ fido_log_debug("%s: fido_blob_decode", __func__);
+ goto fail;
+ }
+
+ if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor,
+ &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) {
+ fido_log_debug("%s: cbor_decode_cred_authdata", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_cred_clean_authdata(cred);
+
+ return (r);
+}
+
+int
+fido_cred_set_authdata_raw(fido_cred_t *cred, const unsigned char *ptr,
+ size_t len)
+{
+ cbor_item_t *item = NULL;
+ int r = FIDO_ERR_INVALID_ARGUMENT;
+
+ fido_cred_clean_authdata(cred);
+
+ if (ptr == NULL || len == 0)
+ goto fail;
+
+ if (fido_blob_set(&cred->authdata_raw, ptr, len) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((item = cbor_build_bytestring(ptr, len)) == NULL) {
+ fido_log_debug("%s: cbor_build_bytestring", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor,
+ &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) {
+ fido_log_debug("%s: cbor_decode_cred_authdata", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_cred_clean_authdata(cred);
+
+ return (r);
+}
+
+int
+fido_cred_set_id(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ if (fido_blob_set(&cred->attcred.id, ptr, len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_x509(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ if (fido_blob_set(&cred->attstmt.x5c, ptr, len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_sig(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ if (fido_blob_set(&cred->attstmt.sig, ptr, len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_attstmt(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int r = FIDO_ERR_INVALID_ARGUMENT;
+
+ fido_cred_clean_attstmt(&cred->attstmt);
+
+ if (ptr == NULL || len == 0)
+ goto fail;
+
+ if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ goto fail;
+ }
+
+ if (cbor_decode_attstmt(item, &cred->attstmt) < 0) {
+ fido_log_debug("%s: cbor_decode_attstmt", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ if (r != FIDO_OK)
+ fido_cred_clean_attstmt(&cred->attstmt);
+
+ return (r);
+}
+
+int
+fido_cred_exclude(fido_cred_t *cred, const unsigned char *id_ptr, size_t id_len)
+{
+ fido_blob_t id_blob;
+ fido_blob_t *list_ptr;
+
+ memset(&id_blob, 0, sizeof(id_blob));
+
+ if (fido_blob_set(&id_blob, id_ptr, id_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if (cred->excl.len == SIZE_MAX) {
+ free(id_blob.ptr);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if ((list_ptr = recallocarray(cred->excl.ptr, cred->excl.len,
+ cred->excl.len + 1, sizeof(fido_blob_t))) == NULL) {
+ free(id_blob.ptr);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ list_ptr[cred->excl.len++] = id_blob;
+ cred->excl.ptr = list_ptr;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_empty_exclude_list(fido_cred_t *cred)
+{
+ fido_free_blob_array(&cred->excl);
+ memset(&cred->excl, 0, sizeof(cred->excl));
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_clientdata(fido_cred_t *cred, const unsigned char *data,
+ size_t data_len)
+{
+ if (!fido_blob_is_empty(&cred->cdh) ||
+ fido_blob_set(&cred->cd, data, data_len) < 0) {
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+ if (fido_sha256(&cred->cdh, data, data_len) < 0) {
+ fido_blob_reset(&cred->cd);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_clientdata_hash(fido_cred_t *cred, const unsigned char *hash,
+ size_t hash_len)
+{
+ if (!fido_blob_is_empty(&cred->cd) ||
+ fido_blob_set(&cred->cdh, hash, hash_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_rp(fido_cred_t *cred, const char *id, const char *name)
+{
+ fido_rp_t *rp = &cred->rp;
+
+ if (rp->id != NULL) {
+ free(rp->id);
+ rp->id = NULL;
+ }
+ if (rp->name != NULL) {
+ free(rp->name);
+ rp->name = NULL;
+ }
+
+ if (id != NULL && (rp->id = strdup(id)) == NULL)
+ goto fail;
+ if (name != NULL && (rp->name = strdup(name)) == NULL)
+ goto fail;
+
+ return (FIDO_OK);
+fail:
+ free(rp->id);
+ free(rp->name);
+ rp->id = NULL;
+ rp->name = NULL;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_cred_set_user(fido_cred_t *cred, const unsigned char *user_id,
+ size_t user_id_len, const char *name, const char *display_name,
+ const char *icon)
+{
+ fido_user_t *up = &cred->user;
+
+ if (up->id.ptr != NULL) {
+ free(up->id.ptr);
+ up->id.ptr = NULL;
+ up->id.len = 0;
+ }
+ if (up->name != NULL) {
+ free(up->name);
+ up->name = NULL;
+ }
+ if (up->display_name != NULL) {
+ free(up->display_name);
+ up->display_name = NULL;
+ }
+ if (up->icon != NULL) {
+ free(up->icon);
+ up->icon = NULL;
+ }
+
+ if (user_id != NULL && fido_blob_set(&up->id, user_id, user_id_len) < 0)
+ goto fail;
+ if (name != NULL && (up->name = strdup(name)) == NULL)
+ goto fail;
+ if (display_name != NULL &&
+ (up->display_name = strdup(display_name)) == NULL)
+ goto fail;
+ if (icon != NULL && (up->icon = strdup(icon)) == NULL)
+ goto fail;
+
+ return (FIDO_OK);
+fail:
+ free(up->id.ptr);
+ free(up->name);
+ free(up->display_name);
+ free(up->icon);
+
+ up->id.ptr = NULL;
+ up->id.len = 0;
+ up->name = NULL;
+ up->display_name = NULL;
+ up->icon = NULL;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_cred_set_extensions(fido_cred_t *cred, int ext)
+{
+ if (ext == 0)
+ cred->ext.mask = 0;
+ else {
+ if ((ext & FIDO_EXT_CRED_MASK) != ext)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ cred->ext.mask |= ext;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_options(fido_cred_t *cred, bool rk, bool uv)
+{
+ cred->rk = rk ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+ cred->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_rk(fido_cred_t *cred, fido_opt_t rk)
+{
+ cred->rk = rk;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_uv(fido_cred_t *cred, fido_opt_t uv)
+{
+ cred->uv = uv;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_prot(fido_cred_t *cred, int prot)
+{
+ if (prot == 0) {
+ cred->ext.mask &= ~FIDO_EXT_CRED_PROTECT;
+ cred->ext.prot = 0;
+ } else {
+ if (prot != FIDO_CRED_PROT_UV_OPTIONAL &&
+ prot != FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID &&
+ prot != FIDO_CRED_PROT_UV_REQUIRED)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ cred->ext.mask |= FIDO_EXT_CRED_PROTECT;
+ cred->ext.prot = prot;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_pin_minlen(fido_cred_t *cred, size_t len)
+{
+ if (len == 0)
+ cred->ext.mask &= ~FIDO_EXT_MINPINLEN;
+ else
+ cred->ext.mask |= FIDO_EXT_MINPINLEN;
+
+ cred->ext.minpinlen = len;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_blob(fido_cred_t *cred, const unsigned char *ptr, size_t len)
+{
+ if (ptr == NULL || len == 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if (fido_blob_set(&cred->blob, ptr, len) < 0)
+ return (FIDO_ERR_INTERNAL);
+
+ cred->ext.mask |= FIDO_EXT_CRED_BLOB;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_fmt(fido_cred_t *cred, const char *fmt)
+{
+ free(cred->fmt);
+ cred->fmt = NULL;
+
+ if (fmt == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if (strcmp(fmt, "packed") && strcmp(fmt, "fido-u2f") &&
+ strcmp(fmt, "none") && strcmp(fmt, "tpm"))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((cred->fmt = strdup(fmt)) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_set_type(fido_cred_t *cred, int cose_alg)
+{
+ if (cred->type != 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if (cose_alg != COSE_ES256 && cose_alg != COSE_ES384 &&
+ cose_alg != COSE_RS256 && cose_alg != COSE_EDDSA)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ cred->type = cose_alg;
+
+ return (FIDO_OK);
+}
+
+int
+fido_cred_type(const fido_cred_t *cred)
+{
+ return (cred->type);
+}
+
+uint8_t
+fido_cred_flags(const fido_cred_t *cred)
+{
+ return (cred->authdata.flags);
+}
+
+uint32_t
+fido_cred_sigcount(const fido_cred_t *cred)
+{
+ return (cred->authdata.sigcount);
+}
+
+const unsigned char *
+fido_cred_clientdata_hash_ptr(const fido_cred_t *cred)
+{
+ return (cred->cdh.ptr);
+}
+
+size_t
+fido_cred_clientdata_hash_len(const fido_cred_t *cred)
+{
+ return (cred->cdh.len);
+}
+
+const unsigned char *
+fido_cred_x5c_ptr(const fido_cred_t *cred)
+{
+ return (cred->attstmt.x5c.ptr);
+}
+
+size_t
+fido_cred_x5c_len(const fido_cred_t *cred)
+{
+ return (cred->attstmt.x5c.len);
+}
+
+const unsigned char *
+fido_cred_sig_ptr(const fido_cred_t *cred)
+{
+ return (cred->attstmt.sig.ptr);
+}
+
+size_t
+fido_cred_sig_len(const fido_cred_t *cred)
+{
+ return (cred->attstmt.sig.len);
+}
+
+const unsigned char *
+fido_cred_authdata_ptr(const fido_cred_t *cred)
+{
+ return (cred->authdata_cbor.ptr);
+}
+
+size_t
+fido_cred_authdata_len(const fido_cred_t *cred)
+{
+ return (cred->authdata_cbor.len);
+}
+
+const unsigned char *
+fido_cred_authdata_raw_ptr(const fido_cred_t *cred)
+{
+ return (cred->authdata_raw.ptr);
+}
+
+size_t
+fido_cred_authdata_raw_len(const fido_cred_t *cred)
+{
+ return (cred->authdata_raw.len);
+}
+
+const unsigned char *
+fido_cred_attstmt_ptr(const fido_cred_t *cred)
+{
+ return (cred->attstmt.cbor.ptr);
+}
+
+size_t
+fido_cred_attstmt_len(const fido_cred_t *cred)
+{
+ return (cred->attstmt.cbor.len);
+}
+
+const unsigned char *
+fido_cred_pubkey_ptr(const fido_cred_t *cred)
+{
+ const void *ptr;
+
+ switch (cred->attcred.type) {
+ case COSE_ES256:
+ ptr = &cred->attcred.pubkey.es256;
+ break;
+ case COSE_ES384:
+ ptr = &cred->attcred.pubkey.es384;
+ break;
+ case COSE_RS256:
+ ptr = &cred->attcred.pubkey.rs256;
+ break;
+ case COSE_EDDSA:
+ ptr = &cred->attcred.pubkey.eddsa;
+ break;
+ default:
+ ptr = NULL;
+ break;
+ }
+
+ return (ptr);
+}
+
+size_t
+fido_cred_pubkey_len(const fido_cred_t *cred)
+{
+ size_t len;
+
+ switch (cred->attcred.type) {
+ case COSE_ES256:
+ len = sizeof(cred->attcred.pubkey.es256);
+ break;
+ case COSE_ES384:
+ len = sizeof(cred->attcred.pubkey.es384);
+ break;
+ case COSE_RS256:
+ len = sizeof(cred->attcred.pubkey.rs256);
+ break;
+ case COSE_EDDSA:
+ len = sizeof(cred->attcred.pubkey.eddsa);
+ break;
+ default:
+ len = 0;
+ break;
+ }
+
+ return (len);
+}
+
+const unsigned char *
+fido_cred_id_ptr(const fido_cred_t *cred)
+{
+ return (cred->attcred.id.ptr);
+}
+
+size_t
+fido_cred_id_len(const fido_cred_t *cred)
+{
+ return (cred->attcred.id.len);
+}
+
+const unsigned char *
+fido_cred_aaguid_ptr(const fido_cred_t *cred)
+{
+ return (cred->attcred.aaguid);
+}
+
+size_t
+fido_cred_aaguid_len(const fido_cred_t *cred)
+{
+ return (sizeof(cred->attcred.aaguid));
+}
+
+int
+fido_cred_prot(const fido_cred_t *cred)
+{
+ return (cred->ext.prot);
+}
+
+size_t
+fido_cred_pin_minlen(const fido_cred_t *cred)
+{
+ return (cred->ext.minpinlen);
+}
+
+const char *
+fido_cred_fmt(const fido_cred_t *cred)
+{
+ return (cred->fmt);
+}
+
+const char *
+fido_cred_rp_id(const fido_cred_t *cred)
+{
+ return (cred->rp.id);
+}
+
+const char *
+fido_cred_rp_name(const fido_cred_t *cred)
+{
+ return (cred->rp.name);
+}
+
+const char *
+fido_cred_user_name(const fido_cred_t *cred)
+{
+ return (cred->user.name);
+}
+
+const char *
+fido_cred_display_name(const fido_cred_t *cred)
+{
+ return (cred->user.display_name);
+}
+
+const unsigned char *
+fido_cred_user_id_ptr(const fido_cred_t *cred)
+{
+ return (cred->user.id.ptr);
+}
+
+size_t
+fido_cred_user_id_len(const fido_cred_t *cred)
+{
+ return (cred->user.id.len);
+}
+
+const unsigned char *
+fido_cred_largeblob_key_ptr(const fido_cred_t *cred)
+{
+ return (cred->largeblob_key.ptr);
+}
+
+size_t
+fido_cred_largeblob_key_len(const fido_cred_t *cred)
+{
+ return (cred->largeblob_key.len);
+}
diff --git a/src/credman.c b/src/credman.c
new file mode 100644
index 0000000..c364242
--- /dev/null
+++ b/src/credman.c
@@ -0,0 +1,825 @@
+/*
+ * Copyright (c) 2019-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 <openssl/sha.h>
+
+#include "fido.h"
+#include "fido/credman.h"
+#include "fido/es256.h"
+
+#define CMD_CRED_METADATA 0x01
+#define CMD_RP_BEGIN 0x02
+#define CMD_RP_NEXT 0x03
+#define CMD_RK_BEGIN 0x04
+#define CMD_RK_NEXT 0x05
+#define CMD_DELETE_CRED 0x06
+#define CMD_UPDATE_CRED 0x07
+
+static int
+credman_grow_array(void **ptr, size_t *n_alloc, const size_t *n_rx, size_t n,
+ size_t size)
+{
+ void *new_ptr;
+
+#ifdef FIDO_FUZZ
+ if (n > UINT8_MAX) {
+ fido_log_debug("%s: n > UINT8_MAX", __func__);
+ return (-1);
+ }
+#endif
+
+ if (n < *n_alloc)
+ return (0);
+
+ /* sanity check */
+ if (*n_rx > 0 || *n_rx > *n_alloc || n < *n_alloc) {
+ fido_log_debug("%s: n=%zu, n_rx=%zu, n_alloc=%zu", __func__, n,
+ *n_rx, *n_alloc);
+ return (-1);
+ }
+
+ if ((new_ptr = recallocarray(*ptr, *n_alloc, n, size)) == NULL)
+ return (-1);
+
+ *ptr = new_ptr;
+ *n_alloc = n;
+
+ return (0);
+}
+
+static int
+credman_prepare_hmac(uint8_t cmd, const void *body, cbor_item_t **param,
+ fido_blob_t *hmac_data)
+{
+ cbor_item_t *param_cbor[3];
+ const fido_cred_t *cred;
+ size_t n;
+ int ok = -1;
+
+ memset(&param_cbor, 0, sizeof(param_cbor));
+
+ if (body == NULL)
+ return (fido_blob_set(hmac_data, &cmd, sizeof(cmd)));
+
+ switch (cmd) {
+ case CMD_RK_BEGIN:
+ n = 1;
+ if ((param_cbor[0] = fido_blob_encode(body)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+ break;
+ case CMD_DELETE_CRED:
+ n = 2;
+ if ((param_cbor[1] = cbor_encode_pubkey(body)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+ break;
+ case CMD_UPDATE_CRED:
+ n = 3;
+ cred = body;
+ param_cbor[1] = cbor_encode_pubkey(&cred->attcred.id);
+ param_cbor[2] = cbor_encode_user_entity(&cred->user);
+ if (param_cbor[1] == NULL || param_cbor[2] == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+ break;
+ default:
+ fido_log_debug("%s: unknown cmd=0x%02x", __func__, cmd);
+ return (-1);
+ }
+
+ if ((*param = cbor_flatten_vector(param_cbor, n)) == NULL) {
+ fido_log_debug("%s: cbor_flatten_vector", __func__);
+ goto fail;
+ }
+ if (cbor_build_frame(cmd, param_cbor, n, hmac_data) < 0) {
+ fido_log_debug("%s: cbor_build_frame", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ cbor_vector_free(param_cbor, nitems(param_cbor));
+
+ return (ok);
+}
+
+static int
+credman_tx(fido_dev_t *dev, uint8_t subcmd, const void *param, const char *pin,
+ const char *rp_id, fido_opt_t uv, int *ms)
+{
+ fido_blob_t f;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t hmac;
+ es256_pk_t *pk = NULL;
+ cbor_item_t *argv[4];
+ const uint8_t cmd = CTAP_CBOR_CRED_MGMT_PRE;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&f, 0, sizeof(f));
+ memset(&hmac, 0, sizeof(hmac));
+ memset(&argv, 0, sizeof(argv));
+
+ if (fido_dev_is_fido2(dev) == false) {
+ fido_log_debug("%s: fido_dev_is_fido2", __func__);
+ r = FIDO_ERR_INVALID_COMMAND;
+ goto fail;
+ }
+
+ /* subCommand */
+ if ((argv[0] = cbor_build_uint8(subcmd)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ /* pinProtocol, pinAuth */
+ if (pin != NULL || uv == FIDO_OPT_TRUE) {
+ if (credman_prepare_hmac(subcmd, param, &argv[1], &hmac) < 0) {
+ fido_log_debug("%s: credman_prepare_hmac", __func__);
+ goto fail;
+ }
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin,
+ rp_id, &argv[3], &argv[2], ms)) != FIDO_OK) {
+ fido_log_debug("%s: cbor_add_uv_params", __func__);
+ goto fail;
+ }
+ }
+
+ /* framing and transmission */
+ if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ es256_pk_free(&pk);
+ fido_blob_free(&ecdh);
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+ free(hmac.ptr);
+
+ return (r);
+}
+
+static int
+credman_parse_metadata(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_credman_metadata_t *metadata = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1:
+ return (cbor_decode_uint64(val, &metadata->rk_existing));
+ case 2:
+ return (cbor_decode_uint64(val, &metadata->rk_remaining));
+ default:
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+}
+
+static int
+credman_rx_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ memset(metadata, 0, sizeof(*metadata));
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, metadata,
+ credman_parse_metadata)) != FIDO_OK) {
+ fido_log_debug("%s: credman_parse_metadata", __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+credman_get_metadata_wait(fido_dev_t *dev, fido_credman_metadata_t *metadata,
+ const char *pin, int *ms)
+{
+ int r;
+
+ if ((r = credman_tx(dev, CMD_CRED_METADATA, NULL, pin, NULL,
+ FIDO_OPT_TRUE, ms)) != FIDO_OK ||
+ (r = credman_rx_metadata(dev, metadata, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_credman_get_dev_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata,
+ const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return (credman_get_metadata_wait(dev, metadata, pin, &ms));
+}
+
+static int
+credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cred_t *cred = arg;
+ uint64_t prot;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 6:
+ return (cbor_decode_user(val, &cred->user));
+ case 7:
+ return (cbor_decode_cred_id(val, &cred->attcred.id));
+ case 8:
+ if (cbor_decode_pubkey(val, &cred->attcred.type,
+ &cred->attcred.pubkey) < 0)
+ return (-1);
+ cred->type = cred->attcred.type; /* XXX */
+ return (0);
+ case 10:
+ if (cbor_decode_uint64(val, &prot) < 0 || prot > INT_MAX ||
+ fido_cred_set_prot(cred, (int)prot) != FIDO_OK)
+ return (-1);
+ return (0);
+ case 11:
+ return (fido_blob_decode(val, &cred->largeblob_key));
+ default:
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+}
+
+static void
+credman_reset_rk(fido_credman_rk_t *rk)
+{
+ for (size_t i = 0; i < rk->n_alloc; i++) {
+ fido_cred_reset_tx(&rk->ptr[i]);
+ fido_cred_reset_rx(&rk->ptr[i]);
+ }
+
+ free(rk->ptr);
+ rk->ptr = NULL;
+ memset(rk, 0, sizeof(*rk));
+}
+
+static int
+credman_parse_rk_count(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_credman_rk_t *rk = arg;
+ uint64_t n;
+
+ /* totalCredentials */
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 9) {
+ fido_log_debug("%s: cbor_type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+
+ if (credman_grow_array((void **)&rk->ptr, &rk->n_alloc, &rk->n_rx,
+ (size_t)n, sizeof(*rk->ptr)) < 0) {
+ fido_log_debug("%s: credman_grow_array", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+credman_rx_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ credman_reset_rk(rk);
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ /* adjust as needed */
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, rk,
+ credman_parse_rk_count)) != FIDO_OK) {
+ fido_log_debug("%s: credman_parse_rk_count", __func__);
+ goto out;
+ }
+
+ if (rk->n_alloc == 0) {
+ fido_log_debug("%s: n_alloc=0", __func__);
+ r = FIDO_OK;
+ goto out;
+ }
+
+ /* parse the first rk */
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[0],
+ credman_parse_rk)) != FIDO_OK) {
+ fido_log_debug("%s: credman_parse_rk", __func__);
+ goto out;
+ }
+ rk->n_rx = 1;
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ /* sanity check */
+ if (rk->n_rx >= rk->n_alloc) {
+ fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx,
+ rk->n_alloc);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[rk->n_rx],
+ credman_parse_rk)) != FIDO_OK) {
+ fido_log_debug("%s: credman_parse_rk", __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+credman_get_rk_wait(fido_dev_t *dev, const char *rp_id, fido_credman_rk_t *rk,
+ const char *pin, int *ms)
+{
+ fido_blob_t rp_dgst;
+ uint8_t dgst[SHA256_DIGEST_LENGTH];
+ int r;
+
+ if (SHA256((const unsigned char *)rp_id, strlen(rp_id), dgst) != dgst) {
+ fido_log_debug("%s: sha256", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ rp_dgst.ptr = dgst;
+ rp_dgst.len = sizeof(dgst);
+
+ if ((r = credman_tx(dev, CMD_RK_BEGIN, &rp_dgst, pin, rp_id,
+ FIDO_OPT_TRUE, ms)) != FIDO_OK ||
+ (r = credman_rx_rk(dev, rk, ms)) != FIDO_OK)
+ return (r);
+
+ while (rk->n_rx < rk->n_alloc) {
+ if ((r = credman_tx(dev, CMD_RK_NEXT, NULL, NULL, NULL,
+ FIDO_OPT_FALSE, ms)) != FIDO_OK ||
+ (r = credman_rx_next_rk(dev, rk, ms)) != FIDO_OK)
+ return (r);
+ rk->n_rx++;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_credman_get_dev_rk(fido_dev_t *dev, const char *rp_id,
+ fido_credman_rk_t *rk, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return (credman_get_rk_wait(dev, rp_id, rk, pin, &ms));
+}
+
+static int
+credman_del_rk_wait(fido_dev_t *dev, const unsigned char *cred_id,
+ size_t cred_id_len, const char *pin, int *ms)
+{
+ fido_blob_t cred;
+ int r;
+
+ memset(&cred, 0, sizeof(cred));
+
+ if (fido_blob_set(&cred, cred_id, cred_id_len) < 0)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((r = credman_tx(dev, CMD_DELETE_CRED, &cred, pin, NULL,
+ FIDO_OPT_TRUE, ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK)
+ goto fail;
+
+ r = FIDO_OK;
+fail:
+ free(cred.ptr);
+
+ return (r);
+}
+
+int
+fido_credman_del_dev_rk(fido_dev_t *dev, const unsigned char *cred_id,
+ size_t cred_id_len, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, &ms));
+}
+
+static int
+credman_parse_rp(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ struct fido_credman_single_rp *rp = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 3:
+ return (cbor_decode_rp_entity(val, &rp->rp_entity));
+ case 4:
+ return (fido_blob_decode(val, &rp->rp_id_hash));
+ default:
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+}
+
+static void
+credman_reset_rp(fido_credman_rp_t *rp)
+{
+ for (size_t i = 0; i < rp->n_alloc; i++) {
+ free(rp->ptr[i].rp_entity.id);
+ free(rp->ptr[i].rp_entity.name);
+ rp->ptr[i].rp_entity.id = NULL;
+ rp->ptr[i].rp_entity.name = NULL;
+ fido_blob_reset(&rp->ptr[i].rp_id_hash);
+ }
+
+ free(rp->ptr);
+ rp->ptr = NULL;
+ memset(rp, 0, sizeof(*rp));
+}
+
+static int
+credman_parse_rp_count(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_credman_rp_t *rp = arg;
+ uint64_t n;
+
+ /* totalRPs */
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 5) {
+ fido_log_debug("%s: cbor_type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+
+ if (credman_grow_array((void **)&rp->ptr, &rp->n_alloc, &rp->n_rx,
+ (size_t)n, sizeof(*rp->ptr)) < 0) {
+ fido_log_debug("%s: credman_grow_array", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+credman_rx_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ credman_reset_rp(rp);
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ /* adjust as needed */
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, rp,
+ credman_parse_rp_count)) != FIDO_OK) {
+ fido_log_debug("%s: credman_parse_rp_count", __func__);
+ goto out;
+ }
+
+ if (rp->n_alloc == 0) {
+ fido_log_debug("%s: n_alloc=0", __func__);
+ r = FIDO_OK;
+ goto out;
+ }
+
+ /* parse the first rp */
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[0],
+ credman_parse_rp)) != FIDO_OK) {
+ fido_log_debug("%s: credman_parse_rp", __func__);
+ goto out;
+ }
+ rp->n_rx = 1;
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ /* sanity check */
+ if (rp->n_rx >= rp->n_alloc) {
+ fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx,
+ rp->n_alloc);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[rp->n_rx],
+ credman_parse_rp)) != FIDO_OK) {
+ fido_log_debug("%s: credman_parse_rp", __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+credman_get_rp_wait(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin,
+ int *ms)
+{
+ int r;
+
+ if ((r = credman_tx(dev, CMD_RP_BEGIN, NULL, pin, NULL,
+ FIDO_OPT_TRUE, ms)) != FIDO_OK ||
+ (r = credman_rx_rp(dev, rp, ms)) != FIDO_OK)
+ return (r);
+
+ while (rp->n_rx < rp->n_alloc) {
+ if ((r = credman_tx(dev, CMD_RP_NEXT, NULL, NULL, NULL,
+ FIDO_OPT_FALSE, ms)) != FIDO_OK ||
+ (r = credman_rx_next_rp(dev, rp, ms)) != FIDO_OK)
+ return (r);
+ rp->n_rx++;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_credman_get_dev_rp(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return (credman_get_rp_wait(dev, rp, pin, &ms));
+}
+
+static int
+credman_set_dev_rk_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
+ int *ms)
+{
+ int r;
+
+ if ((r = credman_tx(dev, CMD_UPDATE_CRED, cred, pin, NULL,
+ FIDO_OPT_TRUE, ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_credman_set_dev_rk(fido_dev_t *dev, fido_cred_t *cred, const char *pin)
+{
+ int ms = dev->timeout_ms;
+
+ return (credman_set_dev_rk_wait(dev, cred, pin, &ms));
+}
+
+fido_credman_rk_t *
+fido_credman_rk_new(void)
+{
+ return (calloc(1, sizeof(fido_credman_rk_t)));
+}
+
+void
+fido_credman_rk_free(fido_credman_rk_t **rk_p)
+{
+ fido_credman_rk_t *rk;
+
+ if (rk_p == NULL || (rk = *rk_p) == NULL)
+ return;
+
+ credman_reset_rk(rk);
+ free(rk);
+ *rk_p = NULL;
+}
+
+size_t
+fido_credman_rk_count(const fido_credman_rk_t *rk)
+{
+ return (rk->n_rx);
+}
+
+const fido_cred_t *
+fido_credman_rk(const fido_credman_rk_t *rk, size_t idx)
+{
+ if (idx >= rk->n_alloc)
+ return (NULL);
+
+ return (&rk->ptr[idx]);
+}
+
+fido_credman_metadata_t *
+fido_credman_metadata_new(void)
+{
+ return (calloc(1, sizeof(fido_credman_metadata_t)));
+}
+
+void
+fido_credman_metadata_free(fido_credman_metadata_t **metadata_p)
+{
+ fido_credman_metadata_t *metadata;
+
+ if (metadata_p == NULL || (metadata = *metadata_p) == NULL)
+ return;
+
+ free(metadata);
+ *metadata_p = NULL;
+}
+
+uint64_t
+fido_credman_rk_existing(const fido_credman_metadata_t *metadata)
+{
+ return (metadata->rk_existing);
+}
+
+uint64_t
+fido_credman_rk_remaining(const fido_credman_metadata_t *metadata)
+{
+ return (metadata->rk_remaining);
+}
+
+fido_credman_rp_t *
+fido_credman_rp_new(void)
+{
+ return (calloc(1, sizeof(fido_credman_rp_t)));
+}
+
+void
+fido_credman_rp_free(fido_credman_rp_t **rp_p)
+{
+ fido_credman_rp_t *rp;
+
+ if (rp_p == NULL || (rp = *rp_p) == NULL)
+ return;
+
+ credman_reset_rp(rp);
+ free(rp);
+ *rp_p = NULL;
+}
+
+size_t
+fido_credman_rp_count(const fido_credman_rp_t *rp)
+{
+ return (rp->n_rx);
+}
+
+const char *
+fido_credman_rp_id(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (NULL);
+
+ return (rp->ptr[idx].rp_entity.id);
+}
+
+const char *
+fido_credman_rp_name(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (NULL);
+
+ return (rp->ptr[idx].rp_entity.name);
+}
+
+size_t
+fido_credman_rp_id_hash_len(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (0);
+
+ return (rp->ptr[idx].rp_id_hash.len);
+}
+
+const unsigned char *
+fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *rp, size_t idx)
+{
+ if (idx >= rp->n_alloc)
+ return (NULL);
+
+ return (rp->ptr[idx].rp_id_hash.ptr);
+}
diff --git a/src/dev.c b/src/dev.c
new file mode 100644
index 0000000..2d662a6
--- /dev/null
+++ b/src/dev.c
@@ -0,0 +1,601 @@
+/*
+ * 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"
+
+#ifndef TLS
+#define TLS
+#endif
+
+static TLS bool disable_u2f_fallback;
+
+#ifdef FIDO_FUZZ
+static void
+set_random_report_len(fido_dev_t *dev)
+{
+ dev->rx_len = CTAP_MIN_REPORT_LEN +
+ uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1);
+ dev->tx_len = CTAP_MIN_REPORT_LEN +
+ uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1);
+}
+#endif
+
+static void
+fido_dev_set_extension_flags(fido_dev_t *dev, const fido_cbor_info_t *info)
+{
+ char * const *ptr = fido_cbor_info_extensions_ptr(info);
+ size_t len = fido_cbor_info_extensions_len(info);
+
+ for (size_t i = 0; i < len; i++)
+ if (strcmp(ptr[i], "credProtect") == 0)
+ dev->flags |= FIDO_DEV_CRED_PROT;
+}
+
+static void
+fido_dev_set_option_flags(fido_dev_t *dev, const fido_cbor_info_t *info)
+{
+ char * const *ptr = fido_cbor_info_options_name_ptr(info);
+ const bool *val = fido_cbor_info_options_value_ptr(info);
+ size_t len = fido_cbor_info_options_len(info);
+
+ for (size_t i = 0; i < len; i++)
+ if (strcmp(ptr[i], "clientPin") == 0) {
+ dev->flags |= val[i] ?
+ FIDO_DEV_PIN_SET : FIDO_DEV_PIN_UNSET;
+ } else if (strcmp(ptr[i], "credMgmt") == 0 ||
+ strcmp(ptr[i], "credentialMgmtPreview") == 0) {
+ if (val[i])
+ dev->flags |= FIDO_DEV_CREDMAN;
+ } else if (strcmp(ptr[i], "uv") == 0) {
+ dev->flags |= val[i] ?
+ FIDO_DEV_UV_SET : FIDO_DEV_UV_UNSET;
+ } else if (strcmp(ptr[i], "pinUvAuthToken") == 0) {
+ if (val[i])
+ dev->flags |= FIDO_DEV_TOKEN_PERMS;
+ }
+}
+
+static void
+fido_dev_set_protocol_flags(fido_dev_t *dev, const fido_cbor_info_t *info)
+{
+ const uint8_t *ptr = fido_cbor_info_protocols_ptr(info);
+ size_t len = fido_cbor_info_protocols_len(info);
+
+ for (size_t i = 0; i < len; i++)
+ switch (ptr[i]) {
+ case CTAP_PIN_PROTOCOL1:
+ dev->flags |= FIDO_DEV_PIN_PROTOCOL1;
+ break;
+ case CTAP_PIN_PROTOCOL2:
+ dev->flags |= FIDO_DEV_PIN_PROTOCOL2;
+ break;
+ default:
+ fido_log_debug("%s: unknown protocol %u", __func__,
+ ptr[i]);
+ break;
+ }
+}
+
+static void
+fido_dev_set_flags(fido_dev_t *dev, const fido_cbor_info_t *info)
+{
+ fido_dev_set_extension_flags(dev, info);
+ fido_dev_set_option_flags(dev, info);
+ fido_dev_set_protocol_flags(dev, info);
+}
+
+static int
+fido_dev_open_tx(fido_dev_t *dev, const char *path, int *ms)
+{
+ int r;
+
+ if (dev->io_handle != NULL) {
+ fido_log_debug("%s: handle=%p", __func__, dev->io_handle);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (dev->io.open == NULL || dev->io.close == NULL) {
+ fido_log_debug("%s: NULL open/close", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (dev->cid != CTAP_CID_BROADCAST) {
+ fido_log_debug("%s: cid=0x%x", __func__, dev->cid);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (fido_get_random(&dev->nonce, sizeof(dev->nonce)) < 0) {
+ fido_log_debug("%s: fido_get_random", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((dev->io_handle = dev->io.open(path)) == NULL) {
+ fido_log_debug("%s: dev->io.open", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if (dev->io_own) {
+ dev->rx_len = CTAP_MAX_REPORT_LEN;
+ dev->tx_len = CTAP_MAX_REPORT_LEN;
+ } else {
+ dev->rx_len = fido_hid_report_in_len(dev->io_handle);
+ dev->tx_len = fido_hid_report_out_len(dev->io_handle);
+ }
+
+#ifdef FIDO_FUZZ
+ set_random_report_len(dev);
+#endif
+
+ if (dev->rx_len < CTAP_MIN_REPORT_LEN ||
+ dev->rx_len > CTAP_MAX_REPORT_LEN) {
+ fido_log_debug("%s: invalid rx_len %zu", __func__, dev->rx_len);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if (dev->tx_len < CTAP_MIN_REPORT_LEN ||
+ dev->tx_len > CTAP_MAX_REPORT_LEN) {
+ fido_log_debug("%s: invalid tx_len %zu", __func__, dev->tx_len);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ if (fido_tx(dev, CTAP_CMD_INIT, &dev->nonce, sizeof(dev->nonce),
+ ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ return (FIDO_OK);
+fail:
+ dev->io.close(dev->io_handle);
+ dev->io_handle = NULL;
+
+ return (r);
+}
+
+static int
+fido_dev_open_rx(fido_dev_t *dev, int *ms)
+{
+ fido_cbor_info_t *info = NULL;
+ int reply_len;
+ int r;
+
+ if ((reply_len = fido_rx(dev, CTAP_CMD_INIT, &dev->attr,
+ sizeof(dev->attr), ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+#ifdef FIDO_FUZZ
+ dev->attr.nonce = dev->nonce;
+#endif
+
+ if ((size_t)reply_len != sizeof(dev->attr) ||
+ dev->attr.nonce != dev->nonce) {
+ fido_log_debug("%s: invalid nonce", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ dev->flags = 0;
+ dev->cid = dev->attr.cid;
+
+ if (fido_dev_is_fido2(dev)) {
+ if ((info = fido_cbor_info_new()) == NULL) {
+ fido_log_debug("%s: fido_cbor_info_new", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if ((r = fido_dev_get_cbor_info_wait(dev, info,
+ ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_cbor_info_wait: %d",
+ __func__, r);
+ if (disable_u2f_fallback)
+ goto fail;
+ fido_log_debug("%s: falling back to u2f", __func__);
+ fido_dev_force_u2f(dev);
+ } else {
+ fido_dev_set_flags(dev, info);
+ }
+ }
+
+ if (fido_dev_is_fido2(dev) && info != NULL) {
+ dev->maxmsgsize = fido_cbor_info_maxmsgsiz(info);
+ fido_log_debug("%s: FIDO_MAXMSG=%d, maxmsgsiz=%lu", __func__,
+ FIDO_MAXMSG, (unsigned long)dev->maxmsgsize);
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_cbor_info_free(&info);
+
+ if (r != FIDO_OK) {
+ dev->io.close(dev->io_handle);
+ dev->io_handle = NULL;
+ }
+
+ return (r);
+}
+
+static int
+fido_dev_open_wait(fido_dev_t *dev, const char *path, int *ms)
+{
+ int r;
+
+#ifdef USE_WINHELLO
+ if (strcmp(path, FIDO_WINHELLO_PATH) == 0)
+ return (fido_winhello_open(dev));
+#endif
+ if ((r = fido_dev_open_tx(dev, path, ms)) != FIDO_OK ||
+ (r = fido_dev_open_rx(dev, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+static void
+run_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen,
+ const char *type, int (*manifest)(fido_dev_info_t *, size_t, size_t *))
+{
+ size_t ndevs = 0;
+ int r;
+
+ if (*olen >= ilen) {
+ fido_log_debug("%s: skipping %s", __func__, type);
+ return;
+ }
+ if ((r = manifest(devlist + *olen, ilen - *olen, &ndevs)) != FIDO_OK)
+ fido_log_debug("%s: %s: 0x%x", __func__, type, r);
+ fido_log_debug("%s: found %zu %s device%s", __func__, ndevs, type,
+ ndevs == 1 ? "" : "s");
+ *olen += ndevs;
+}
+
+int
+fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ *olen = 0;
+
+ run_manifest(devlist, ilen, olen, "hid", fido_hid_manifest);
+#ifdef USE_NFC
+ run_manifest(devlist, ilen, olen, "nfc", fido_nfc_manifest);
+#endif
+#ifdef USE_PCSC
+ run_manifest(devlist, ilen, olen, "pcsc", fido_pcsc_manifest);
+#endif
+#ifdef USE_WINHELLO
+ run_manifest(devlist, ilen, olen, "winhello", fido_winhello_manifest);
+#endif
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_open_with_info(fido_dev_t *dev)
+{
+ int ms = dev->timeout_ms;
+
+ if (dev->path == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (fido_dev_open_wait(dev, dev->path, &ms));
+}
+
+int
+fido_dev_open(fido_dev_t *dev, const char *path)
+{
+ int ms = dev->timeout_ms;
+
+#ifdef USE_NFC
+ if (fido_is_nfc(path) && fido_dev_set_nfc(dev) < 0) {
+ fido_log_debug("%s: fido_dev_set_nfc", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+#endif
+#ifdef USE_PCSC
+ if (fido_is_pcsc(path) && fido_dev_set_pcsc(dev) < 0) {
+ fido_log_debug("%s: fido_dev_set_pcsc", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+#endif
+
+ return (fido_dev_open_wait(dev, path, &ms));
+}
+
+int
+fido_dev_close(fido_dev_t *dev)
+{
+#ifdef USE_WINHELLO
+ if (dev->flags & FIDO_DEV_WINHELLO)
+ return (fido_winhello_close(dev));
+#endif
+ if (dev->io_handle == NULL || dev->io.close == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ dev->io.close(dev->io_handle);
+ dev->io_handle = NULL;
+ dev->cid = CTAP_CID_BROADCAST;
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_set_sigmask(fido_dev_t *dev, const fido_sigset_t *sigmask)
+{
+ if (dev->io_handle == NULL || sigmask == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+#ifdef USE_NFC
+ if (dev->transport.rx == fido_nfc_rx && dev->io.read == fido_nfc_read)
+ return (fido_nfc_set_sigmask(dev->io_handle, sigmask));
+#endif
+ if (dev->transport.rx == NULL && dev->io.read == fido_hid_read)
+ return (fido_hid_set_sigmask(dev->io_handle, sigmask));
+
+ return (FIDO_ERR_INVALID_ARGUMENT);
+}
+
+int
+fido_dev_cancel(fido_dev_t *dev)
+{
+ int ms = dev->timeout_ms;
+
+#ifdef USE_WINHELLO
+ if (dev->flags & FIDO_DEV_WINHELLO)
+ return (fido_winhello_cancel(dev));
+#endif
+ if (fido_dev_is_fido2(dev) == false)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if (fido_tx(dev, CTAP_CMD_CANCEL, NULL, 0, &ms) < 0)
+ return (FIDO_ERR_TX);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_set_io_functions(fido_dev_t *dev, const fido_dev_io_t *io)
+{
+ if (dev->io_handle != NULL) {
+ fido_log_debug("%s: non-NULL handle", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ if (io == NULL || io->open == NULL || io->close == NULL ||
+ io->read == NULL || io->write == NULL) {
+ fido_log_debug("%s: NULL function", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ dev->io = *io;
+ dev->io_own = true;
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_set_transport_functions(fido_dev_t *dev, const fido_dev_transport_t *t)
+{
+ if (dev->io_handle != NULL) {
+ fido_log_debug("%s: non-NULL handle", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ dev->transport = *t;
+ dev->io_own = true;
+
+ return (FIDO_OK);
+}
+
+void *
+fido_dev_io_handle(const fido_dev_t *dev)
+{
+
+ return (dev->io_handle);
+}
+
+void
+fido_init(int flags)
+{
+ if (flags & FIDO_DEBUG || getenv("FIDO_DEBUG") != NULL)
+ fido_log_init();
+
+ disable_u2f_fallback = (flags & FIDO_DISABLE_U2F_FALLBACK);
+}
+
+fido_dev_t *
+fido_dev_new(void)
+{
+ fido_dev_t *dev;
+
+ if ((dev = calloc(1, sizeof(*dev))) == NULL)
+ return (NULL);
+
+ dev->cid = CTAP_CID_BROADCAST;
+ dev->timeout_ms = -1;
+ dev->io = (fido_dev_io_t) {
+ &fido_hid_open,
+ &fido_hid_close,
+ &fido_hid_read,
+ &fido_hid_write,
+ };
+
+ return (dev);
+}
+
+fido_dev_t *
+fido_dev_new_with_info(const fido_dev_info_t *di)
+{
+ fido_dev_t *dev;
+
+ if ((dev = calloc(1, sizeof(*dev))) == NULL)
+ return (NULL);
+
+#if 0
+ if (di->io.open == NULL || di->io.close == NULL ||
+ di->io.read == NULL || di->io.write == NULL) {
+ fido_log_debug("%s: NULL function", __func__);
+ fido_dev_free(&dev);
+ return (NULL);
+ }
+#endif
+
+ dev->io = di->io;
+ dev->io_own = di->transport.tx != NULL || di->transport.rx != NULL;
+ dev->transport = di->transport;
+ dev->cid = CTAP_CID_BROADCAST;
+ dev->timeout_ms = -1;
+
+ if ((dev->path = strdup(di->path)) == NULL) {
+ fido_log_debug("%s: strdup", __func__);
+ fido_dev_free(&dev);
+ return (NULL);
+ }
+
+ return (dev);
+}
+
+void
+fido_dev_free(fido_dev_t **dev_p)
+{
+ fido_dev_t *dev;
+
+ if (dev_p == NULL || (dev = *dev_p) == NULL)
+ return;
+
+ free(dev->path);
+ free(dev);
+
+ *dev_p = NULL;
+}
+
+uint8_t
+fido_dev_protocol(const fido_dev_t *dev)
+{
+ return (dev->attr.protocol);
+}
+
+uint8_t
+fido_dev_major(const fido_dev_t *dev)
+{
+ return (dev->attr.major);
+}
+
+uint8_t
+fido_dev_minor(const fido_dev_t *dev)
+{
+ return (dev->attr.minor);
+}
+
+uint8_t
+fido_dev_build(const fido_dev_t *dev)
+{
+ return (dev->attr.build);
+}
+
+uint8_t
+fido_dev_flags(const fido_dev_t *dev)
+{
+ return (dev->attr.flags);
+}
+
+bool
+fido_dev_is_fido2(const fido_dev_t *dev)
+{
+ return (dev->attr.flags & FIDO_CAP_CBOR);
+}
+
+bool
+fido_dev_is_winhello(const fido_dev_t *dev)
+{
+ return (dev->flags & FIDO_DEV_WINHELLO);
+}
+
+bool
+fido_dev_supports_pin(const fido_dev_t *dev)
+{
+ return (dev->flags & (FIDO_DEV_PIN_SET|FIDO_DEV_PIN_UNSET));
+}
+
+bool
+fido_dev_has_pin(const fido_dev_t *dev)
+{
+ return (dev->flags & FIDO_DEV_PIN_SET);
+}
+
+bool
+fido_dev_supports_cred_prot(const fido_dev_t *dev)
+{
+ return (dev->flags & FIDO_DEV_CRED_PROT);
+}
+
+bool
+fido_dev_supports_credman(const fido_dev_t *dev)
+{
+ return (dev->flags & FIDO_DEV_CREDMAN);
+}
+
+bool
+fido_dev_supports_uv(const fido_dev_t *dev)
+{
+ return (dev->flags & (FIDO_DEV_UV_SET|FIDO_DEV_UV_UNSET));
+}
+
+bool
+fido_dev_has_uv(const fido_dev_t *dev)
+{
+ return (dev->flags & FIDO_DEV_UV_SET);
+}
+
+bool
+fido_dev_supports_permissions(const fido_dev_t *dev)
+{
+ return (dev->flags & FIDO_DEV_TOKEN_PERMS);
+}
+
+void
+fido_dev_force_u2f(fido_dev_t *dev)
+{
+ dev->attr.flags &= (uint8_t)~FIDO_CAP_CBOR;
+ dev->flags = 0;
+}
+
+void
+fido_dev_force_fido2(fido_dev_t *dev)
+{
+ dev->attr.flags |= FIDO_CAP_CBOR;
+}
+
+uint8_t
+fido_dev_get_pin_protocol(const fido_dev_t *dev)
+{
+ if (dev->flags & FIDO_DEV_PIN_PROTOCOL2)
+ return (CTAP_PIN_PROTOCOL2);
+ else if (dev->flags & FIDO_DEV_PIN_PROTOCOL1)
+ return (CTAP_PIN_PROTOCOL1);
+
+ return (0);
+}
+
+uint64_t
+fido_dev_maxmsgsize(const fido_dev_t *dev)
+{
+ return (dev->maxmsgsize);
+}
+
+int
+fido_dev_set_timeout(fido_dev_t *dev, int ms)
+{
+ if (ms < -1)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ dev->timeout_ms = ms;
+
+ return (FIDO_OK);
+}
diff --git a/src/diff_exports.sh b/src/diff_exports.sh
new file mode 100755
index 0000000..2e15cd0
--- /dev/null
+++ b/src/diff_exports.sh
@@ -0,0 +1,27 @@
+#!/bin/sh -u
+
+# 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
+
+for f in export.gnu export.llvm export.msvc; do
+ if [ ! -f "${f}" ]; then
+ exit 1
+ fi
+done
+
+TMPDIR="$(mktemp -d)"
+GNU="${TMPDIR}/gnu"
+LLVM="${TMPDIR}/llvm"
+MSVC="${TMPDIR}/msvc"
+
+awk '/^[^*{}]+;$/' export.gnu | tr -d '\t;' | sort > "${GNU}"
+sed 's/^_//' export.llvm | sort > "${LLVM}"
+grep -v '^EXPORTS$' export.msvc | sort > "${MSVC}"
+diff -u "${GNU}" "${LLVM}" && diff -u "${MSVC}" "${LLVM}"
+ERROR=$?
+rm "${GNU}" "${LLVM}" "${MSVC}"
+rmdir "${TMPDIR}"
+
+exit ${ERROR}
diff --git a/src/ecdh.c b/src/ecdh.c
new file mode 100644
index 0000000..878f976
--- /dev/null
+++ b/src/ecdh.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2018-2021 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/evp.h>
+#include <openssl/sha.h>
+#if defined(LIBRESSL_VERSION_NUMBER)
+#include <openssl/hkdf.h>
+#else
+#include <openssl/kdf.h>
+#endif
+
+#include "fido.h"
+#include "fido/es256.h"
+
+#if defined(LIBRESSL_VERSION_NUMBER)
+static int
+hkdf_sha256(uint8_t *key, const char *info, const fido_blob_t *secret)
+{
+ const EVP_MD *md;
+ uint8_t salt[32];
+
+ memset(salt, 0, sizeof(salt));
+ if ((md = EVP_sha256()) == NULL ||
+ HKDF(key, SHA256_DIGEST_LENGTH, md, secret->ptr, secret->len, salt,
+ sizeof(salt), (const uint8_t *)info, strlen(info)) != 1)
+ return -1;
+
+ return 0;
+}
+#else
+static int
+hkdf_sha256(uint8_t *key, char *info, fido_blob_t *secret)
+{
+ const EVP_MD *const_md;
+ EVP_MD *md = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ size_t keylen = SHA256_DIGEST_LENGTH;
+ uint8_t salt[32];
+ int ok = -1;
+
+ memset(salt, 0, sizeof(salt));
+ if (secret->len > INT_MAX || strlen(info) > INT_MAX) {
+ fido_log_debug("%s: invalid param", __func__);
+ goto fail;
+ }
+ if ((const_md = EVP_sha256()) == NULL ||
+ (md = EVP_MD_meth_dup(const_md)) == NULL ||
+ (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)) == NULL) {
+ fido_log_debug("%s: init", __func__);
+ goto fail;
+ }
+ if (EVP_PKEY_derive_init(ctx) < 1 ||
+ EVP_PKEY_CTX_set_hkdf_md(ctx, md) < 1 ||
+ EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, sizeof(salt)) < 1 ||
+ EVP_PKEY_CTX_set1_hkdf_key(ctx, secret->ptr, (int)secret->len) < 1 ||
+ EVP_PKEY_CTX_add1_hkdf_info(ctx, (void *)info, (int)strlen(info)) < 1) {
+ fido_log_debug("%s: EVP_PKEY_CTX", __func__);
+ goto fail;
+ }
+ if (EVP_PKEY_derive(ctx, key, &keylen) < 1) {
+ fido_log_debug("%s: EVP_PKEY_derive", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (md != NULL)
+ EVP_MD_meth_free(md);
+ if (ctx != NULL)
+ EVP_PKEY_CTX_free(ctx);
+
+ return ok;
+}
+#endif /* defined(LIBRESSL_VERSION_NUMBER) */
+
+static int
+kdf(uint8_t prot, fido_blob_t *key, /* const */ fido_blob_t *secret)
+{
+ char hmac_info[] = "CTAP2 HMAC key"; /* const */
+ char aes_info[] = "CTAP2 AES key"; /* const */
+
+ switch (prot) {
+ case CTAP_PIN_PROTOCOL1:
+ /* use sha256 on the resulting secret */
+ key->len = SHA256_DIGEST_LENGTH;
+ if ((key->ptr = calloc(1, key->len)) == NULL ||
+ SHA256(secret->ptr, secret->len, key->ptr) != key->ptr) {
+ fido_log_debug("%s: SHA256", __func__);
+ return -1;
+ }
+ break;
+ case CTAP_PIN_PROTOCOL2:
+ /* use two instances of hkdf-sha256 on the resulting secret */
+ key->len = 2 * SHA256_DIGEST_LENGTH;
+ if ((key->ptr = calloc(1, key->len)) == NULL ||
+ hkdf_sha256(key->ptr, hmac_info, secret) < 0 ||
+ hkdf_sha256(key->ptr + SHA256_DIGEST_LENGTH, aes_info,
+ secret) < 0) {
+ fido_log_debug("%s: hkdf", __func__);
+ return -1;
+ }
+ break;
+ default:
+ fido_log_debug("%s: unknown pin protocol %u", __func__, prot);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+do_ecdh(const fido_dev_t *dev, const es256_sk_t *sk, const es256_pk_t *pk,
+ fido_blob_t **ecdh)
+{
+ EVP_PKEY *pk_evp = NULL;
+ EVP_PKEY *sk_evp = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ fido_blob_t *secret = NULL;
+ int ok = -1;
+
+ *ecdh = NULL;
+ if ((secret = fido_blob_new()) == NULL ||
+ (*ecdh = fido_blob_new()) == NULL)
+ goto fail;
+ if ((pk_evp = es256_pk_to_EVP_PKEY(pk)) == NULL ||
+ (sk_evp = es256_sk_to_EVP_PKEY(sk)) == NULL) {
+ fido_log_debug("%s: es256_to_EVP_PKEY", __func__);
+ goto fail;
+ }
+ if ((ctx = EVP_PKEY_CTX_new(sk_evp, NULL)) == NULL ||
+ EVP_PKEY_derive_init(ctx) <= 0 ||
+ EVP_PKEY_derive_set_peer(ctx, pk_evp) <= 0) {
+ fido_log_debug("%s: EVP_PKEY_derive_init", __func__);
+ goto fail;
+ }
+ if (EVP_PKEY_derive(ctx, NULL, &secret->len) <= 0 ||
+ (secret->ptr = calloc(1, secret->len)) == NULL ||
+ EVP_PKEY_derive(ctx, secret->ptr, &secret->len) <= 0) {
+ fido_log_debug("%s: EVP_PKEY_derive", __func__);
+ goto fail;
+ }
+ if (kdf(fido_dev_get_pin_protocol(dev), *ecdh, secret) < 0) {
+ fido_log_debug("%s: kdf", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (pk_evp != NULL)
+ EVP_PKEY_free(pk_evp);
+ if (sk_evp != NULL)
+ EVP_PKEY_free(sk_evp);
+ if (ctx != NULL)
+ EVP_PKEY_CTX_free(ctx);
+ if (ok < 0)
+ fido_blob_free(ecdh);
+
+ fido_blob_free(&secret);
+
+ return ok;
+}
+
+int
+fido_do_ecdh(fido_dev_t *dev, es256_pk_t **pk, fido_blob_t **ecdh, int *ms)
+{
+ es256_sk_t *sk = NULL; /* our private key */
+ es256_pk_t *ak = NULL; /* authenticator's public key */
+ int r;
+
+ *pk = NULL;
+ *ecdh = NULL;
+ if ((sk = es256_sk_new()) == NULL || (*pk = es256_pk_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if (es256_sk_create(sk) < 0 || es256_derive_pk(sk, *pk) < 0) {
+ fido_log_debug("%s: es256_derive_pk", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if ((ak = es256_pk_new()) == NULL ||
+ fido_dev_authkey(dev, ak, ms) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_authkey", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if (do_ecdh(dev, sk, ak, ecdh) < 0) {
+ fido_log_debug("%s: do_ecdh", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ es256_sk_free(&sk);
+ es256_pk_free(&ak);
+
+ if (r != FIDO_OK) {
+ es256_pk_free(pk);
+ fido_blob_free(ecdh);
+ }
+
+ return r;
+}
diff --git a/src/eddsa.c b/src/eddsa.c
new file mode 100644
index 0000000..bdb53b1
--- /dev/null
+++ b/src/eddsa.c
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2019-2021 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/bn.h>
+#include <openssl/obj_mac.h>
+
+#include "fido.h"
+#include "fido/eddsa.h"
+
+#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3070000f
+EVP_PKEY *
+EVP_PKEY_new_raw_public_key(int type, ENGINE *e, const unsigned char *key,
+ size_t keylen)
+{
+ (void)type;
+ (void)e;
+ (void)key;
+ (void)keylen;
+
+ fido_log_debug("%s: unimplemented", __func__);
+
+ return (NULL);
+}
+
+int
+EVP_PKEY_get_raw_public_key(const EVP_PKEY *pkey, unsigned char *pub,
+ size_t *len)
+{
+ (void)pkey;
+ (void)pub;
+ (void)len;
+
+ fido_log_debug("%s: unimplemented", __func__);
+
+ return (0);
+}
+#endif /* LIBRESSL_VERSION_NUMBER */
+
+#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3040000f
+int
+EVP_DigestVerify(EVP_MD_CTX *ctx, const unsigned char *sigret, size_t siglen,
+ const unsigned char *tbs, size_t tbslen)
+{
+ (void)ctx;
+ (void)sigret;
+ (void)siglen;
+ (void)tbs;
+ (void)tbslen;
+
+ fido_log_debug("%s: unimplemented", __func__);
+
+ return (0);
+}
+#endif /* LIBRESSL_VERSION_NUMBER < 0x3040000f */
+
+static int
+decode_coord(const cbor_item_t *item, void *xy, size_t xy_len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != xy_len) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(xy, cbor_bytestring_handle(item), xy_len);
+
+ return (0);
+}
+
+static int
+decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ eddsa_pk_t *k = arg;
+
+ if (cbor_isa_negint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8)
+ return (0); /* ignore */
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* x coordinate */
+ return (decode_coord(val, &k->x, sizeof(k->x)));
+ }
+
+ return (0); /* ignore */
+}
+
+int
+eddsa_pk_decode(const cbor_item_t *item, eddsa_pk_t *k)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, k, decode_pubkey_point) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+eddsa_pk_t *
+eddsa_pk_new(void)
+{
+ return (calloc(1, sizeof(eddsa_pk_t)));
+}
+
+void
+eddsa_pk_free(eddsa_pk_t **pkp)
+{
+ eddsa_pk_t *pk;
+
+ if (pkp == NULL || (pk = *pkp) == NULL)
+ return;
+
+ freezero(pk, sizeof(*pk));
+ *pkp = NULL;
+}
+
+int
+eddsa_pk_from_ptr(eddsa_pk_t *pk, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey;
+
+ if (len < sizeof(*pk))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ memcpy(pk, ptr, sizeof(*pk));
+
+ if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) {
+ fido_log_debug("%s: eddsa_pk_to_EVP_PKEY", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ EVP_PKEY_free(pkey);
+
+ return (FIDO_OK);
+}
+
+EVP_PKEY *
+eddsa_pk_to_EVP_PKEY(const eddsa_pk_t *k)
+{
+ EVP_PKEY *pkey = NULL;
+
+ if ((pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, k->x,
+ sizeof(k->x))) == NULL)
+ fido_log_debug("%s: EVP_PKEY_new_raw_public_key", __func__);
+
+ return (pkey);
+}
+
+int
+eddsa_pk_from_EVP_PKEY(eddsa_pk_t *pk, const EVP_PKEY *pkey)
+{
+ size_t len = 0;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_ED25519)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if (EVP_PKEY_get_raw_public_key(pkey, NULL, &len) != 1 ||
+ len != sizeof(pk->x))
+ return (FIDO_ERR_INTERNAL);
+ if (EVP_PKEY_get_raw_public_key(pkey, pk->x, &len) != 1 ||
+ len != sizeof(pk->x))
+ return (FIDO_ERR_INTERNAL);
+
+ return (FIDO_OK);
+}
+
+int
+eddsa_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey,
+ const fido_blob_t *sig)
+{
+ EVP_MD_CTX *mdctx = NULL;
+ int ok = -1;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_ED25519) {
+ fido_log_debug("%s: EVP_PKEY_base_id", __func__);
+ goto fail;
+ }
+
+ /* EVP_DigestVerify needs ints */
+ if (dgst->len > INT_MAX || sig->len > INT_MAX) {
+ fido_log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__,
+ dgst->len, sig->len);
+ return (-1);
+ }
+
+ if ((mdctx = EVP_MD_CTX_new()) == NULL) {
+ fido_log_debug("%s: EVP_MD_CTX_new", __func__);
+ goto fail;
+ }
+
+ if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
+ fido_log_debug("%s: EVP_DigestVerifyInit", __func__);
+ goto fail;
+ }
+
+ if (EVP_DigestVerify(mdctx, sig->ptr, sig->len, dgst->ptr,
+ dgst->len) != 1) {
+ fido_log_debug("%s: EVP_DigestVerify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_MD_CTX_free(mdctx);
+
+ return (ok);
+}
+
+int
+eddsa_pk_verify_sig(const fido_blob_t *dgst, const eddsa_pk_t *pk,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY *pkey;
+ int ok = -1;
+
+ if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL ||
+ eddsa_verify_sig(dgst, pkey, sig) < 0) {
+ fido_log_debug("%s: eddsa_verify_sig", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
diff --git a/src/err.c b/src/err.c
new file mode 100644
index 0000000..3a6f3e0
--- /dev/null
+++ b/src/err.c
@@ -0,0 +1,137 @@
+/*
+ * 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/err.h"
+
+const char *
+fido_strerr(int n)
+{
+ switch (n) {
+ case FIDO_ERR_SUCCESS:
+ return "FIDO_ERR_SUCCESS";
+ case FIDO_ERR_INVALID_COMMAND:
+ return "FIDO_ERR_INVALID_COMMAND";
+ case FIDO_ERR_INVALID_PARAMETER:
+ return "FIDO_ERR_INVALID_PARAMETER";
+ case FIDO_ERR_INVALID_LENGTH:
+ return "FIDO_ERR_INVALID_LENGTH";
+ case FIDO_ERR_INVALID_SEQ:
+ return "FIDO_ERR_INVALID_SEQ";
+ case FIDO_ERR_TIMEOUT:
+ return "FIDO_ERR_TIMEOUT";
+ case FIDO_ERR_CHANNEL_BUSY:
+ return "FIDO_ERR_CHANNEL_BUSY";
+ case FIDO_ERR_LOCK_REQUIRED:
+ return "FIDO_ERR_LOCK_REQUIRED";
+ case FIDO_ERR_INVALID_CHANNEL:
+ return "FIDO_ERR_INVALID_CHANNEL";
+ case FIDO_ERR_CBOR_UNEXPECTED_TYPE:
+ return "FIDO_ERR_CBOR_UNEXPECTED_TYPE";
+ case FIDO_ERR_INVALID_CBOR:
+ return "FIDO_ERR_INVALID_CBOR";
+ case FIDO_ERR_MISSING_PARAMETER:
+ return "FIDO_ERR_MISSING_PARAMETER";
+ case FIDO_ERR_LIMIT_EXCEEDED:
+ return "FIDO_ERR_LIMIT_EXCEEDED";
+ case FIDO_ERR_UNSUPPORTED_EXTENSION:
+ return "FIDO_ERR_UNSUPPORTED_EXTENSION";
+ case FIDO_ERR_FP_DATABASE_FULL:
+ return "FIDO_ERR_FP_DATABASE_FULL";
+ case FIDO_ERR_LARGEBLOB_STORAGE_FULL:
+ return "FIDO_ERR_LARGEBLOB_STORAGE_FULL";
+ case FIDO_ERR_CREDENTIAL_EXCLUDED:
+ return "FIDO_ERR_CREDENTIAL_EXCLUDED";
+ case FIDO_ERR_PROCESSING:
+ return "FIDO_ERR_PROCESSING";
+ case FIDO_ERR_INVALID_CREDENTIAL:
+ return "FIDO_ERR_INVALID_CREDENTIAL";
+ case FIDO_ERR_USER_ACTION_PENDING:
+ return "FIDO_ERR_USER_ACTION_PENDING";
+ case FIDO_ERR_OPERATION_PENDING:
+ return "FIDO_ERR_OPERATION_PENDING";
+ case FIDO_ERR_NO_OPERATIONS:
+ return "FIDO_ERR_NO_OPERATIONS";
+ case FIDO_ERR_UNSUPPORTED_ALGORITHM:
+ return "FIDO_ERR_UNSUPPORTED_ALGORITHM";
+ case FIDO_ERR_OPERATION_DENIED:
+ return "FIDO_ERR_OPERATION_DENIED";
+ case FIDO_ERR_KEY_STORE_FULL:
+ return "FIDO_ERR_KEY_STORE_FULL";
+ case FIDO_ERR_NOT_BUSY:
+ return "FIDO_ERR_NOT_BUSY";
+ case FIDO_ERR_NO_OPERATION_PENDING:
+ return "FIDO_ERR_NO_OPERATION_PENDING";
+ case FIDO_ERR_UNSUPPORTED_OPTION:
+ return "FIDO_ERR_UNSUPPORTED_OPTION";
+ case FIDO_ERR_INVALID_OPTION:
+ return "FIDO_ERR_INVALID_OPTION";
+ case FIDO_ERR_KEEPALIVE_CANCEL:
+ return "FIDO_ERR_KEEPALIVE_CANCEL";
+ case FIDO_ERR_NO_CREDENTIALS:
+ return "FIDO_ERR_NO_CREDENTIALS";
+ case FIDO_ERR_USER_ACTION_TIMEOUT:
+ return "FIDO_ERR_USER_ACTION_TIMEOUT";
+ case FIDO_ERR_NOT_ALLOWED:
+ return "FIDO_ERR_NOT_ALLOWED";
+ case FIDO_ERR_PIN_INVALID:
+ return "FIDO_ERR_PIN_INVALID";
+ case FIDO_ERR_PIN_BLOCKED:
+ return "FIDO_ERR_PIN_BLOCKED";
+ case FIDO_ERR_PIN_AUTH_INVALID:
+ return "FIDO_ERR_PIN_AUTH_INVALID";
+ case FIDO_ERR_PIN_AUTH_BLOCKED:
+ return "FIDO_ERR_PIN_AUTH_BLOCKED";
+ case FIDO_ERR_PIN_NOT_SET:
+ return "FIDO_ERR_PIN_NOT_SET";
+ case FIDO_ERR_PIN_REQUIRED:
+ return "FIDO_ERR_PIN_REQUIRED";
+ case FIDO_ERR_PIN_POLICY_VIOLATION:
+ return "FIDO_ERR_PIN_POLICY_VIOLATION";
+ case FIDO_ERR_PIN_TOKEN_EXPIRED:
+ return "FIDO_ERR_PIN_TOKEN_EXPIRED";
+ case FIDO_ERR_REQUEST_TOO_LARGE:
+ return "FIDO_ERR_REQUEST_TOO_LARGE";
+ case FIDO_ERR_ACTION_TIMEOUT:
+ return "FIDO_ERR_ACTION_TIMEOUT";
+ case FIDO_ERR_UP_REQUIRED:
+ return "FIDO_ERR_UP_REQUIRED";
+ case FIDO_ERR_UV_BLOCKED:
+ return "FIDO_ERR_UV_BLOCKED";
+ case FIDO_ERR_UV_INVALID:
+ return "FIDO_ERR_UV_INVALID";
+ case FIDO_ERR_UNAUTHORIZED_PERM:
+ return "FIDO_ERR_UNAUTHORIZED_PERM";
+ case FIDO_ERR_ERR_OTHER:
+ return "FIDO_ERR_ERR_OTHER";
+ case FIDO_ERR_SPEC_LAST:
+ return "FIDO_ERR_SPEC_LAST";
+ case FIDO_ERR_TX:
+ return "FIDO_ERR_TX";
+ case FIDO_ERR_RX:
+ return "FIDO_ERR_RX";
+ case FIDO_ERR_RX_NOT_CBOR:
+ return "FIDO_ERR_RX_NOT_CBOR";
+ case FIDO_ERR_RX_INVALID_CBOR:
+ return "FIDO_ERR_RX_INVALID_CBOR";
+ case FIDO_ERR_INVALID_PARAM:
+ return "FIDO_ERR_INVALID_PARAM";
+ case FIDO_ERR_INVALID_SIG:
+ return "FIDO_ERR_INVALID_SIG";
+ case FIDO_ERR_INVALID_ARGUMENT:
+ return "FIDO_ERR_INVALID_ARGUMENT";
+ case FIDO_ERR_USER_PRESENCE_REQUIRED:
+ return "FIDO_ERR_USER_PRESENCE_REQUIRED";
+ case FIDO_ERR_NOTFOUND:
+ return "FIDO_ERR_NOTFOUND";
+ case FIDO_ERR_COMPRESS:
+ return "FIDO_ERR_COMPRESS";
+ case FIDO_ERR_INTERNAL:
+ return "FIDO_ERR_INTERNAL";
+ default:
+ return "FIDO_ERR_UNKNOWN";
+ }
+}
diff --git a/src/es256.c b/src/es256.c
new file mode 100644
index 0000000..17efb0a
--- /dev/null
+++ b/src/es256.c
@@ -0,0 +1,541 @@
+/*
+ * 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 <openssl/bn.h>
+#include <openssl/ecdsa.h>
+#include <openssl/obj_mac.h>
+
+#include "fido.h"
+#include "fido/es256.h"
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+#define get0_EC_KEY(x) EVP_PKEY_get0_EC_KEY((x))
+#else
+#define get0_EC_KEY(x) EVP_PKEY_get0((x))
+#endif
+
+static const int es256_nid = NID_X9_62_prime256v1;
+
+static int
+decode_coord(const cbor_item_t *item, void *xy, size_t xy_len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != xy_len) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(xy, cbor_bytestring_handle(item), xy_len);
+
+ return (0);
+}
+
+static int
+decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ es256_pk_t *k = arg;
+
+ if (cbor_isa_negint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8)
+ return (0); /* ignore */
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* x coordinate */
+ return (decode_coord(val, &k->x, sizeof(k->x)));
+ case 2: /* y coordinate */
+ return (decode_coord(val, &k->y, sizeof(k->y)));
+ }
+
+ return (0); /* ignore */
+}
+
+int
+es256_pk_decode(const cbor_item_t *item, es256_pk_t *k)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, k, decode_pubkey_point) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+cbor_item_t *
+es256_pk_encode(const es256_pk_t *pk, int ecdh)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_pair argv[5];
+ int alg;
+ int ok = -1;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((item = cbor_new_definite_map(5)) == NULL)
+ goto fail;
+
+ /* kty */
+ if ((argv[0].key = cbor_build_uint8(1)) == NULL ||
+ (argv[0].value = cbor_build_uint8(2)) == NULL ||
+ !cbor_map_add(item, argv[0]))
+ goto fail;
+
+ /*
+ * "The COSEAlgorithmIdentifier used is -25 (ECDH-ES +
+ * HKDF-256) although this is NOT the algorithm actually
+ * used. Setting this to a different value may result in
+ * compatibility issues."
+ */
+ if (ecdh)
+ alg = COSE_ECDH_ES256;
+ else
+ alg = COSE_ES256;
+
+ /* alg */
+ if ((argv[1].key = cbor_build_uint8(3)) == NULL ||
+ (argv[1].value = cbor_build_negint8((uint8_t)(-alg - 1))) == NULL ||
+ !cbor_map_add(item, argv[1]))
+ goto fail;
+
+ /* crv */
+ if ((argv[2].key = cbor_build_negint8(0)) == NULL ||
+ (argv[2].value = cbor_build_uint8(1)) == NULL ||
+ !cbor_map_add(item, argv[2]))
+ goto fail;
+
+ /* x */
+ if ((argv[3].key = cbor_build_negint8(1)) == NULL ||
+ (argv[3].value = cbor_build_bytestring(pk->x,
+ sizeof(pk->x))) == NULL || !cbor_map_add(item, argv[3]))
+ goto fail;
+
+ /* y */
+ if ((argv[4].key = cbor_build_negint8(2)) == NULL ||
+ (argv[4].value = cbor_build_bytestring(pk->y,
+ sizeof(pk->y))) == NULL || !cbor_map_add(item, argv[4]))
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ if (item != NULL) {
+ cbor_decref(&item);
+ item = NULL;
+ }
+ }
+
+ for (size_t i = 0; i < 5; i++) {
+ if (argv[i].key)
+ cbor_decref(&argv[i].key);
+ if (argv[i].value)
+ cbor_decref(&argv[i].value);
+ }
+
+ return (item);
+}
+
+es256_sk_t *
+es256_sk_new(void)
+{
+ return (calloc(1, sizeof(es256_sk_t)));
+}
+
+void
+es256_sk_free(es256_sk_t **skp)
+{
+ es256_sk_t *sk;
+
+ if (skp == NULL || (sk = *skp) == NULL)
+ return;
+
+ freezero(sk, sizeof(*sk));
+ *skp = NULL;
+}
+
+es256_pk_t *
+es256_pk_new(void)
+{
+ return (calloc(1, sizeof(es256_pk_t)));
+}
+
+void
+es256_pk_free(es256_pk_t **pkp)
+{
+ es256_pk_t *pk;
+
+ if (pkp == NULL || (pk = *pkp) == NULL)
+ return;
+
+ freezero(pk, sizeof(*pk));
+ *pkp = NULL;
+}
+
+int
+es256_pk_from_ptr(es256_pk_t *pk, const void *ptr, size_t len)
+{
+ const uint8_t *p = ptr;
+ EVP_PKEY *pkey;
+
+ if (len < sizeof(*pk))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if (len == sizeof(*pk) + 1 && *p == 0x04)
+ memcpy(pk, ++p, sizeof(*pk)); /* uncompressed format */
+ else
+ memcpy(pk, ptr, sizeof(*pk)); /* libfido2 x||y format */
+
+ if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) {
+ fido_log_debug("%s: es256_pk_to_EVP_PKEY", __func__);
+ explicit_bzero(pk, sizeof(*pk));
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ EVP_PKEY_free(pkey);
+
+ return (FIDO_OK);
+}
+
+int
+es256_pk_set_x(es256_pk_t *pk, const unsigned char *x)
+{
+ memcpy(pk->x, x, sizeof(pk->x));
+
+ return (0);
+}
+
+int
+es256_pk_set_y(es256_pk_t *pk, const unsigned char *y)
+{
+ memcpy(pk->y, y, sizeof(pk->y));
+
+ return (0);
+}
+
+int
+es256_sk_create(es256_sk_t *key)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_PKEY_CTX *kctx = NULL;
+ EVP_PKEY *p = NULL;
+ EVP_PKEY *k = NULL;
+ const EC_KEY *ec;
+ const BIGNUM *d;
+ int n;
+ int ok = -1;
+
+ if ((pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) == NULL ||
+ EVP_PKEY_paramgen_init(pctx) <= 0 ||
+ EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, es256_nid) <= 0 ||
+ EVP_PKEY_paramgen(pctx, &p) <= 0) {
+ fido_log_debug("%s: EVP_PKEY_paramgen", __func__);
+ goto fail;
+ }
+
+ if ((kctx = EVP_PKEY_CTX_new(p, NULL)) == NULL ||
+ EVP_PKEY_keygen_init(kctx) <= 0 || EVP_PKEY_keygen(kctx, &k) <= 0) {
+ fido_log_debug("%s: EVP_PKEY_keygen", __func__);
+ goto fail;
+ }
+
+ if ((ec = EVP_PKEY_get0_EC_KEY(k)) == NULL ||
+ (d = EC_KEY_get0_private_key(ec)) == NULL ||
+ (n = BN_num_bytes(d)) < 0 || (size_t)n > sizeof(key->d) ||
+ (n = BN_bn2bin(d, key->d)) < 0 || (size_t)n > sizeof(key->d)) {
+ fido_log_debug("%s: EC_KEY_get0_private_key", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (p != NULL)
+ EVP_PKEY_free(p);
+ if (k != NULL)
+ EVP_PKEY_free(k);
+ if (pctx != NULL)
+ EVP_PKEY_CTX_free(pctx);
+ if (kctx != NULL)
+ EVP_PKEY_CTX_free(kctx);
+
+ return (ok);
+}
+
+EVP_PKEY *
+es256_pk_to_EVP_PKEY(const es256_pk_t *k)
+{
+ BN_CTX *bnctx = NULL;
+ EC_KEY *ec = NULL;
+ EC_POINT *q = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ const EC_GROUP *g = NULL;
+ int ok = -1;
+
+ if ((bnctx = BN_CTX_new()) == NULL)
+ goto fail;
+
+ BN_CTX_start(bnctx);
+
+ if ((x = BN_CTX_get(bnctx)) == NULL ||
+ (y = BN_CTX_get(bnctx)) == NULL)
+ goto fail;
+
+ if (BN_bin2bn(k->x, sizeof(k->x), x) == NULL ||
+ BN_bin2bn(k->y, sizeof(k->y), y) == NULL) {
+ fido_log_debug("%s: BN_bin2bn", __func__);
+ goto fail;
+ }
+
+ if ((ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL ||
+ (g = EC_KEY_get0_group(ec)) == NULL) {
+ fido_log_debug("%s: EC_KEY init", __func__);
+ goto fail;
+ }
+
+ if ((q = EC_POINT_new(g)) == NULL ||
+ EC_POINT_set_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 ||
+ EC_KEY_set_public_key(ec, q) == 0) {
+ fido_log_debug("%s: EC_KEY_set_public_key", __func__);
+ goto fail;
+ }
+
+ if ((pkey = EVP_PKEY_new()) == NULL ||
+ EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) {
+ fido_log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__);
+ goto fail;
+ }
+
+ ec = NULL; /* at this point, ec belongs to evp */
+
+ ok = 0;
+fail:
+ if (bnctx != NULL) {
+ BN_CTX_end(bnctx);
+ BN_CTX_free(bnctx);
+ }
+
+ if (ec != NULL)
+ EC_KEY_free(ec);
+ if (q != NULL)
+ EC_POINT_free(q);
+
+ if (ok < 0 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return (pkey);
+}
+
+int
+es256_pk_from_EC_KEY(es256_pk_t *pk, const EC_KEY *ec)
+{
+ BN_CTX *bnctx = NULL;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ const EC_POINT *q = NULL;
+ EC_GROUP *g = NULL;
+ size_t dx;
+ size_t dy;
+ int ok = FIDO_ERR_INTERNAL;
+ int nx;
+ int ny;
+
+ if ((q = EC_KEY_get0_public_key(ec)) == NULL ||
+ (g = EC_GROUP_new_by_curve_name(es256_nid)) == NULL ||
+ (bnctx = BN_CTX_new()) == NULL)
+ goto fail;
+
+ BN_CTX_start(bnctx);
+
+ if ((x = BN_CTX_get(bnctx)) == NULL ||
+ (y = BN_CTX_get(bnctx)) == NULL)
+ goto fail;
+
+ if (EC_POINT_is_on_curve(g, q, bnctx) != 1) {
+ fido_log_debug("%s: EC_POINT_is_on_curve", __func__);
+ ok = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if (EC_POINT_get_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 ||
+ (nx = BN_num_bytes(x)) < 0 || (size_t)nx > sizeof(pk->x) ||
+ (ny = BN_num_bytes(y)) < 0 || (size_t)ny > sizeof(pk->y)) {
+ fido_log_debug("%s: EC_POINT_get_affine_coordinates_GFp",
+ __func__);
+ goto fail;
+ }
+
+ dx = sizeof(pk->x) - (size_t)nx;
+ dy = sizeof(pk->y) - (size_t)ny;
+
+ if ((nx = BN_bn2bin(x, pk->x + dx)) < 0 || (size_t)nx > sizeof(pk->x) ||
+ (ny = BN_bn2bin(y, pk->y + dy)) < 0 || (size_t)ny > sizeof(pk->y)) {
+ fido_log_debug("%s: BN_bn2bin", __func__);
+ goto fail;
+ }
+
+ ok = FIDO_OK;
+fail:
+ EC_GROUP_free(g);
+
+ if (bnctx != NULL) {
+ BN_CTX_end(bnctx);
+ BN_CTX_free(bnctx);
+ }
+
+ return (ok);
+}
+
+int
+es256_pk_from_EVP_PKEY(es256_pk_t *pk, const EVP_PKEY *pkey)
+{
+ const EC_KEY *ec;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC ||
+ (ec = get0_EC_KEY(pkey)) == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (es256_pk_from_EC_KEY(pk, ec));
+}
+
+EVP_PKEY *
+es256_sk_to_EVP_PKEY(const es256_sk_t *k)
+{
+ BN_CTX *bnctx = NULL;
+ EC_KEY *ec = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *d = NULL;
+ int ok = -1;
+
+ if ((bnctx = BN_CTX_new()) == NULL)
+ goto fail;
+
+ BN_CTX_start(bnctx);
+
+ if ((d = BN_CTX_get(bnctx)) == NULL ||
+ BN_bin2bn(k->d, sizeof(k->d), d) == NULL) {
+ fido_log_debug("%s: BN_bin2bn", __func__);
+ goto fail;
+ }
+
+ if ((ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL ||
+ EC_KEY_set_private_key(ec, d) == 0) {
+ fido_log_debug("%s: EC_KEY_set_private_key", __func__);
+ goto fail;
+ }
+
+ if ((pkey = EVP_PKEY_new()) == NULL ||
+ EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) {
+ fido_log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__);
+ goto fail;
+ }
+
+ ec = NULL; /* at this point, ec belongs to evp */
+
+ ok = 0;
+fail:
+ if (bnctx != NULL) {
+ BN_CTX_end(bnctx);
+ BN_CTX_free(bnctx);
+ }
+
+ if (ec != NULL)
+ EC_KEY_free(ec);
+
+ if (ok < 0 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return (pkey);
+}
+
+int
+es256_derive_pk(const es256_sk_t *sk, es256_pk_t *pk)
+{
+ BIGNUM *d = NULL;
+ EC_KEY *ec = NULL;
+ EC_POINT *q = NULL;
+ const EC_GROUP *g = NULL;
+ int ok = -1;
+
+ if ((d = BN_bin2bn(sk->d, (int)sizeof(sk->d), NULL)) == NULL ||
+ (ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL ||
+ (g = EC_KEY_get0_group(ec)) == NULL ||
+ (q = EC_POINT_new(g)) == NULL) {
+ fido_log_debug("%s: get", __func__);
+ goto fail;
+ }
+
+ if (EC_POINT_mul(g, q, d, NULL, NULL, NULL) == 0 ||
+ EC_KEY_set_public_key(ec, q) == 0 ||
+ es256_pk_from_EC_KEY(pk, ec) != FIDO_OK) {
+ fido_log_debug("%s: set", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (d != NULL)
+ BN_clear_free(d);
+ if (q != NULL)
+ EC_POINT_free(q);
+ if (ec != NULL)
+ EC_KEY_free(ec);
+
+ return (ok);
+}
+
+int
+es256_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ int ok = -1;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
+ fido_log_debug("%s: EVP_PKEY_base_id", __func__);
+ goto fail;
+ }
+
+ if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL ||
+ EVP_PKEY_verify_init(pctx) != 1 ||
+ EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr,
+ dgst->len) != 1) {
+ fido_log_debug("%s: EVP_PKEY_verify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_CTX_free(pctx);
+
+ return (ok);
+}
+
+int
+es256_pk_verify_sig(const fido_blob_t *dgst, const es256_pk_t *pk,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY *pkey;
+ int ok = -1;
+
+ if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL ||
+ es256_verify_sig(dgst, pkey, sig) < 0) {
+ fido_log_debug("%s: es256_verify_sig", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
diff --git a/src/es384.c b/src/es384.c
new file mode 100644
index 0000000..013d285
--- /dev/null
+++ b/src/es384.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 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 <openssl/bn.h>
+#include <openssl/ecdsa.h>
+#include <openssl/obj_mac.h>
+
+#include "fido.h"
+#include "fido/es384.h"
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+#define get0_EC_KEY(x) EVP_PKEY_get0_EC_KEY((x))
+#else
+#define get0_EC_KEY(x) EVP_PKEY_get0((x))
+#endif
+
+static int
+decode_coord(const cbor_item_t *item, void *xy, size_t xy_len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != xy_len) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(xy, cbor_bytestring_handle(item), xy_len);
+
+ return (0);
+}
+
+static int
+decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ es384_pk_t *k = arg;
+
+ if (cbor_isa_negint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8)
+ return (0); /* ignore */
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* x coordinate */
+ return (decode_coord(val, &k->x, sizeof(k->x)));
+ case 2: /* y coordinate */
+ return (decode_coord(val, &k->y, sizeof(k->y)));
+ }
+
+ return (0); /* ignore */
+}
+
+int
+es384_pk_decode(const cbor_item_t *item, es384_pk_t *k)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, k, decode_pubkey_point) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+es384_pk_t *
+es384_pk_new(void)
+{
+ return (calloc(1, sizeof(es384_pk_t)));
+}
+
+void
+es384_pk_free(es384_pk_t **pkp)
+{
+ es384_pk_t *pk;
+
+ if (pkp == NULL || (pk = *pkp) == NULL)
+ return;
+
+ freezero(pk, sizeof(*pk));
+ *pkp = NULL;
+}
+
+int
+es384_pk_from_ptr(es384_pk_t *pk, const void *ptr, size_t len)
+{
+ const uint8_t *p = ptr;
+ EVP_PKEY *pkey;
+
+ if (len < sizeof(*pk))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if (len == sizeof(*pk) + 1 && *p == 0x04)
+ memcpy(pk, ++p, sizeof(*pk)); /* uncompressed format */
+ else
+ memcpy(pk, ptr, sizeof(*pk)); /* libfido2 x||y format */
+
+ if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) {
+ fido_log_debug("%s: es384_pk_to_EVP_PKEY", __func__);
+ explicit_bzero(pk, sizeof(*pk));
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ EVP_PKEY_free(pkey);
+
+ return (FIDO_OK);
+}
+
+EVP_PKEY *
+es384_pk_to_EVP_PKEY(const es384_pk_t *k)
+{
+ BN_CTX *bnctx = NULL;
+ EC_KEY *ec = NULL;
+ EC_POINT *q = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ const EC_GROUP *g = NULL;
+ int ok = -1;
+
+ if ((bnctx = BN_CTX_new()) == NULL)
+ goto fail;
+
+ BN_CTX_start(bnctx);
+
+ if ((x = BN_CTX_get(bnctx)) == NULL ||
+ (y = BN_CTX_get(bnctx)) == NULL)
+ goto fail;
+
+ if (BN_bin2bn(k->x, sizeof(k->x), x) == NULL ||
+ BN_bin2bn(k->y, sizeof(k->y), y) == NULL) {
+ fido_log_debug("%s: BN_bin2bn", __func__);
+ goto fail;
+ }
+
+ if ((ec = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL ||
+ (g = EC_KEY_get0_group(ec)) == NULL) {
+ fido_log_debug("%s: EC_KEY init", __func__);
+ goto fail;
+ }
+
+ if ((q = EC_POINT_new(g)) == NULL ||
+ EC_POINT_set_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 ||
+ EC_KEY_set_public_key(ec, q) == 0) {
+ fido_log_debug("%s: EC_KEY_set_public_key", __func__);
+ goto fail;
+ }
+
+ if ((pkey = EVP_PKEY_new()) == NULL ||
+ EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) {
+ fido_log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__);
+ goto fail;
+ }
+
+ ec = NULL; /* at this point, ec belongs to evp */
+
+ ok = 0;
+fail:
+ if (bnctx != NULL) {
+ BN_CTX_end(bnctx);
+ BN_CTX_free(bnctx);
+ }
+
+ if (ec != NULL)
+ EC_KEY_free(ec);
+ if (q != NULL)
+ EC_POINT_free(q);
+
+ if (ok < 0 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return (pkey);
+}
+
+int
+es384_pk_from_EC_KEY(es384_pk_t *pk, const EC_KEY *ec)
+{
+ BN_CTX *bnctx = NULL;
+ BIGNUM *x = NULL;
+ BIGNUM *y = NULL;
+ const EC_POINT *q = NULL;
+ EC_GROUP *g = NULL;
+ size_t dx;
+ size_t dy;
+ int ok = FIDO_ERR_INTERNAL;
+ int nx;
+ int ny;
+
+ if ((q = EC_KEY_get0_public_key(ec)) == NULL ||
+ (g = EC_GROUP_new_by_curve_name(NID_secp384r1)) == NULL ||
+ (bnctx = BN_CTX_new()) == NULL)
+ goto fail;
+
+ BN_CTX_start(bnctx);
+
+ if ((x = BN_CTX_get(bnctx)) == NULL ||
+ (y = BN_CTX_get(bnctx)) == NULL)
+ goto fail;
+
+ if (EC_POINT_is_on_curve(g, q, bnctx) != 1) {
+ fido_log_debug("%s: EC_POINT_is_on_curve", __func__);
+ ok = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if (EC_POINT_get_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 ||
+ (nx = BN_num_bytes(x)) < 0 || (size_t)nx > sizeof(pk->x) ||
+ (ny = BN_num_bytes(y)) < 0 || (size_t)ny > sizeof(pk->y)) {
+ fido_log_debug("%s: EC_POINT_get_affine_coordinates_GFp",
+ __func__);
+ goto fail;
+ }
+
+ dx = sizeof(pk->x) - (size_t)nx;
+ dy = sizeof(pk->y) - (size_t)ny;
+
+ if ((nx = BN_bn2bin(x, pk->x + dx)) < 0 || (size_t)nx > sizeof(pk->x) ||
+ (ny = BN_bn2bin(y, pk->y + dy)) < 0 || (size_t)ny > sizeof(pk->y)) {
+ fido_log_debug("%s: BN_bn2bin", __func__);
+ goto fail;
+ }
+
+ ok = FIDO_OK;
+fail:
+ EC_GROUP_free(g);
+
+ if (bnctx != NULL) {
+ BN_CTX_end(bnctx);
+ BN_CTX_free(bnctx);
+ }
+
+ return (ok);
+}
+
+int
+es384_pk_from_EVP_PKEY(es384_pk_t *pk, const EVP_PKEY *pkey)
+{
+ const EC_KEY *ec;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC ||
+ (ec = get0_EC_KEY(pkey)) == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (es384_pk_from_EC_KEY(pk, ec));
+}
+
+int
+es384_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ int ok = -1;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) {
+ fido_log_debug("%s: EVP_PKEY_base_id", __func__);
+ goto fail;
+ }
+
+ if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL ||
+ EVP_PKEY_verify_init(pctx) != 1 ||
+ EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr,
+ dgst->len) != 1) {
+ fido_log_debug("%s: EVP_PKEY_verify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_CTX_free(pctx);
+
+ return (ok);
+}
+
+int
+es384_pk_verify_sig(const fido_blob_t *dgst, const es384_pk_t *pk,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY *pkey;
+ int ok = -1;
+
+ if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL ||
+ es384_verify_sig(dgst, pkey, sig) < 0) {
+ fido_log_debug("%s: es384_verify_sig", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
diff --git a/src/export.gnu b/src/export.gnu
new file mode 100644
index 0000000..ea6ca7d
--- /dev/null
+++ b/src/export.gnu
@@ -0,0 +1,268 @@
+{
+ global:
+ eddsa_pk_free;
+ eddsa_pk_from_EVP_PKEY;
+ eddsa_pk_from_ptr;
+ eddsa_pk_new;
+ eddsa_pk_to_EVP_PKEY;
+ es256_pk_free;
+ es256_pk_from_EC_KEY;
+ es256_pk_from_EVP_PKEY;
+ es256_pk_from_ptr;
+ es256_pk_new;
+ es256_pk_to_EVP_PKEY;
+ es384_pk_free;
+ es384_pk_from_EC_KEY;
+ es384_pk_from_EVP_PKEY;
+ es384_pk_from_ptr;
+ es384_pk_new;
+ es384_pk_to_EVP_PKEY;
+ fido_assert_allow_cred;
+ fido_assert_authdata_len;
+ fido_assert_authdata_ptr;
+ fido_assert_authdata_raw_len;
+ fido_assert_authdata_raw_ptr;
+ fido_assert_blob_len;
+ fido_assert_blob_ptr;
+ fido_assert_clientdata_hash_len;
+ fido_assert_clientdata_hash_ptr;
+ fido_assert_count;
+ fido_assert_empty_allow_list;
+ fido_assert_flags;
+ fido_assert_free;
+ fido_assert_hmac_secret_len;
+ fido_assert_hmac_secret_ptr;
+ fido_assert_id_len;
+ fido_assert_id_ptr;
+ fido_assert_largeblob_key_len;
+ fido_assert_largeblob_key_ptr;
+ fido_assert_new;
+ fido_assert_rp_id;
+ fido_assert_set_authdata;
+ fido_assert_set_authdata_raw;
+ fido_assert_set_clientdata;
+ fido_assert_set_clientdata_hash;
+ fido_assert_set_count;
+ fido_assert_set_extensions;
+ fido_assert_set_hmac_salt;
+ fido_assert_set_hmac_secret;
+ fido_assert_set_options;
+ fido_assert_set_rp;
+ fido_assert_set_sig;
+ fido_assert_set_up;
+ fido_assert_set_uv;
+ fido_assert_set_winhello_appid;
+ fido_assert_sigcount;
+ fido_assert_sig_len;
+ fido_assert_sig_ptr;
+ fido_assert_user_display_name;
+ fido_assert_user_icon;
+ fido_assert_user_id_len;
+ fido_assert_user_id_ptr;
+ fido_assert_user_name;
+ fido_assert_verify;
+ fido_bio_dev_enroll_begin;
+ fido_bio_dev_enroll_cancel;
+ fido_bio_dev_enroll_continue;
+ fido_bio_dev_enroll_remove;
+ fido_bio_dev_get_info;
+ fido_bio_dev_get_template_array;
+ fido_bio_dev_set_template_name;
+ fido_bio_enroll_free;
+ fido_bio_enroll_last_status;
+ fido_bio_enroll_new;
+ fido_bio_enroll_remaining_samples;
+ fido_bio_info_free;
+ fido_bio_info_max_samples;
+ fido_bio_info_new;
+ fido_bio_info_type;
+ fido_bio_template;
+ fido_bio_template_array_count;
+ fido_bio_template_array_free;
+ fido_bio_template_array_new;
+ fido_bio_template_free;
+ fido_bio_template_id_len;
+ fido_bio_template_id_ptr;
+ fido_bio_template_name;
+ fido_bio_template_new;
+ fido_bio_template_set_id;
+ fido_bio_template_set_name;
+ fido_cbor_info_aaguid_len;
+ fido_cbor_info_aaguid_ptr;
+ fido_cbor_info_algorithm_cose;
+ fido_cbor_info_algorithm_count;
+ fido_cbor_info_algorithm_type;
+ fido_cbor_info_certs_len;
+ fido_cbor_info_certs_name_ptr;
+ fido_cbor_info_certs_value_ptr;
+ fido_cbor_info_extensions_len;
+ fido_cbor_info_extensions_ptr;
+ fido_cbor_info_free;
+ fido_cbor_info_fwversion;
+ fido_cbor_info_maxcredbloblen;
+ fido_cbor_info_maxcredcntlst;
+ fido_cbor_info_maxcredidlen;
+ fido_cbor_info_maxlargeblob;
+ fido_cbor_info_maxmsgsiz;
+ fido_cbor_info_maxrpid_minpinlen;
+ fido_cbor_info_minpinlen;
+ fido_cbor_info_new;
+ fido_cbor_info_new_pin_required;
+ fido_cbor_info_options_len;
+ fido_cbor_info_options_name_ptr;
+ fido_cbor_info_options_value_ptr;
+ fido_cbor_info_protocols_len;
+ fido_cbor_info_protocols_ptr;
+ fido_cbor_info_rk_remaining;
+ fido_cbor_info_transports_len;
+ fido_cbor_info_transports_ptr;
+ fido_cbor_info_uv_attempts;
+ fido_cbor_info_uv_modality;
+ fido_cbor_info_versions_len;
+ fido_cbor_info_versions_ptr;
+ fido_cred_attstmt_len;
+ fido_cred_attstmt_ptr;
+ fido_cred_authdata_len;
+ fido_cred_authdata_ptr;
+ fido_cred_authdata_raw_len;
+ fido_cred_authdata_raw_ptr;
+ fido_cred_clientdata_hash_len;
+ fido_cred_clientdata_hash_ptr;
+ fido_cred_display_name;
+ fido_cred_empty_exclude_list;
+ fido_cred_exclude;
+ fido_cred_flags;
+ fido_cred_largeblob_key_len;
+ fido_cred_largeblob_key_ptr;
+ fido_cred_sigcount;
+ fido_cred_fmt;
+ fido_cred_free;
+ fido_cred_id_len;
+ fido_cred_id_ptr;
+ fido_cred_aaguid_len;
+ fido_cred_aaguid_ptr;
+ fido_credman_del_dev_rk;
+ fido_credman_get_dev_metadata;
+ fido_credman_get_dev_rk;
+ fido_credman_get_dev_rp;
+ fido_credman_metadata_free;
+ fido_credman_metadata_new;
+ fido_credman_rk;
+ fido_credman_rk_count;
+ fido_credman_rk_existing;
+ fido_credman_rk_free;
+ fido_credman_rk_new;
+ fido_credman_rk_remaining;
+ fido_credman_rp_count;
+ fido_credman_rp_free;
+ fido_credman_rp_id;
+ fido_credman_rp_id_hash_len;
+ fido_credman_rp_id_hash_ptr;
+ fido_credman_rp_name;
+ fido_credman_rp_new;
+ fido_credman_set_dev_rk;
+ fido_cred_new;
+ fido_cred_pin_minlen;
+ fido_cred_prot;
+ fido_cred_pubkey_len;
+ fido_cred_pubkey_ptr;
+ fido_cred_rp_id;
+ fido_cred_rp_name;
+ fido_cred_set_attstmt;
+ fido_cred_set_authdata;
+ fido_cred_set_authdata_raw;
+ fido_cred_set_blob;
+ fido_cred_set_clientdata;
+ fido_cred_set_clientdata_hash;
+ fido_cred_set_extensions;
+ fido_cred_set_fmt;
+ fido_cred_set_id;
+ fido_cred_set_options;
+ fido_cred_set_pin_minlen;
+ fido_cred_set_prot;
+ fido_cred_set_rk;
+ fido_cred_set_rp;
+ fido_cred_set_sig;
+ fido_cred_set_type;
+ fido_cred_set_user;
+ fido_cred_set_uv;
+ fido_cred_set_x509;
+ fido_cred_sig_len;
+ fido_cred_sig_ptr;
+ fido_cred_type;
+ fido_cred_user_id_len;
+ fido_cred_user_id_ptr;
+ fido_cred_user_name;
+ fido_cred_verify;
+ fido_cred_verify_self;
+ fido_cred_x5c_len;
+ fido_cred_x5c_ptr;
+ fido_dev_build;
+ fido_dev_cancel;
+ fido_dev_close;
+ fido_dev_enable_entattest;
+ fido_dev_flags;
+ fido_dev_force_fido2;
+ fido_dev_force_pin_change;
+ fido_dev_force_u2f;
+ fido_dev_free;
+ fido_dev_get_assert;
+ fido_dev_get_cbor_info;
+ fido_dev_get_retry_count;
+ fido_dev_get_uv_retry_count;
+ fido_dev_get_touch_begin;
+ fido_dev_get_touch_status;
+ fido_dev_has_pin;
+ fido_dev_has_uv;
+ fido_dev_info_free;
+ fido_dev_info_manifest;
+ fido_dev_info_manufacturer_string;
+ fido_dev_info_new;
+ fido_dev_info_path;
+ fido_dev_info_product;
+ fido_dev_info_product_string;
+ fido_dev_info_ptr;
+ fido_dev_info_set;
+ fido_dev_info_vendor;
+ fido_dev_io_handle;
+ fido_dev_is_fido2;
+ fido_dev_is_winhello;
+ fido_dev_major;
+ fido_dev_make_cred;
+ fido_dev_minor;
+ fido_dev_new;
+ fido_dev_new_with_info;
+ fido_dev_open;
+ fido_dev_open_with_info;
+ fido_dev_protocol;
+ fido_dev_reset;
+ fido_dev_set_io_functions;
+ fido_dev_set_pin;
+ fido_dev_set_pin_minlen;
+ fido_dev_set_pin_minlen_rpid;
+ fido_dev_set_sigmask;
+ fido_dev_set_timeout;
+ fido_dev_set_transport_functions;
+ fido_dev_supports_cred_prot;
+ fido_dev_supports_credman;
+ fido_dev_supports_permissions;
+ fido_dev_supports_pin;
+ fido_dev_supports_uv;
+ fido_dev_toggle_always_uv;
+ fido_dev_largeblob_get;
+ fido_dev_largeblob_get_array;
+ fido_dev_largeblob_remove;
+ fido_dev_largeblob_set;
+ fido_dev_largeblob_set_array;
+ fido_init;
+ fido_set_log_handler;
+ fido_strerr;
+ rs256_pk_free;
+ rs256_pk_from_ptr;
+ rs256_pk_from_EVP_PKEY;
+ rs256_pk_from_RSA;
+ rs256_pk_new;
+ rs256_pk_to_EVP_PKEY;
+ local:
+ *;
+};
diff --git a/src/export.llvm b/src/export.llvm
new file mode 100644
index 0000000..2a92381
--- /dev/null
+++ b/src/export.llvm
@@ -0,0 +1,263 @@
+_eddsa_pk_free
+_eddsa_pk_from_EVP_PKEY
+_eddsa_pk_from_ptr
+_eddsa_pk_new
+_eddsa_pk_to_EVP_PKEY
+_es256_pk_free
+_es256_pk_from_EC_KEY
+_es256_pk_from_EVP_PKEY
+_es256_pk_from_ptr
+_es256_pk_new
+_es256_pk_to_EVP_PKEY
+_es384_pk_free
+_es384_pk_from_EC_KEY
+_es384_pk_from_EVP_PKEY
+_es384_pk_from_ptr
+_es384_pk_new
+_es384_pk_to_EVP_PKEY
+_fido_assert_allow_cred
+_fido_assert_authdata_len
+_fido_assert_authdata_ptr
+_fido_assert_authdata_raw_len
+_fido_assert_authdata_raw_ptr
+_fido_assert_blob_len
+_fido_assert_blob_ptr
+_fido_assert_clientdata_hash_len
+_fido_assert_clientdata_hash_ptr
+_fido_assert_count
+_fido_assert_empty_allow_list
+_fido_assert_flags
+_fido_assert_free
+_fido_assert_hmac_secret_len
+_fido_assert_hmac_secret_ptr
+_fido_assert_id_len
+_fido_assert_id_ptr
+_fido_assert_largeblob_key_len
+_fido_assert_largeblob_key_ptr
+_fido_assert_new
+_fido_assert_rp_id
+_fido_assert_set_authdata
+_fido_assert_set_authdata_raw
+_fido_assert_set_clientdata
+_fido_assert_set_clientdata_hash
+_fido_assert_set_count
+_fido_assert_set_extensions
+_fido_assert_set_hmac_salt
+_fido_assert_set_hmac_secret
+_fido_assert_set_options
+_fido_assert_set_rp
+_fido_assert_set_sig
+_fido_assert_set_up
+_fido_assert_set_uv
+_fido_assert_set_winhello_appid
+_fido_assert_sigcount
+_fido_assert_sig_len
+_fido_assert_sig_ptr
+_fido_assert_user_display_name
+_fido_assert_user_icon
+_fido_assert_user_id_len
+_fido_assert_user_id_ptr
+_fido_assert_user_name
+_fido_assert_verify
+_fido_bio_dev_enroll_begin
+_fido_bio_dev_enroll_cancel
+_fido_bio_dev_enroll_continue
+_fido_bio_dev_enroll_remove
+_fido_bio_dev_get_info
+_fido_bio_dev_get_template_array
+_fido_bio_dev_set_template_name
+_fido_bio_enroll_free
+_fido_bio_enroll_last_status
+_fido_bio_enroll_new
+_fido_bio_enroll_remaining_samples
+_fido_bio_info_free
+_fido_bio_info_max_samples
+_fido_bio_info_new
+_fido_bio_info_type
+_fido_bio_template
+_fido_bio_template_array_count
+_fido_bio_template_array_free
+_fido_bio_template_array_new
+_fido_bio_template_free
+_fido_bio_template_id_len
+_fido_bio_template_id_ptr
+_fido_bio_template_name
+_fido_bio_template_new
+_fido_bio_template_set_id
+_fido_bio_template_set_name
+_fido_cbor_info_aaguid_len
+_fido_cbor_info_aaguid_ptr
+_fido_cbor_info_algorithm_cose
+_fido_cbor_info_algorithm_count
+_fido_cbor_info_algorithm_type
+_fido_cbor_info_certs_len
+_fido_cbor_info_certs_name_ptr
+_fido_cbor_info_certs_value_ptr
+_fido_cbor_info_extensions_len
+_fido_cbor_info_extensions_ptr
+_fido_cbor_info_free
+_fido_cbor_info_fwversion
+_fido_cbor_info_maxcredbloblen
+_fido_cbor_info_maxcredcntlst
+_fido_cbor_info_maxcredidlen
+_fido_cbor_info_maxlargeblob
+_fido_cbor_info_maxmsgsiz
+_fido_cbor_info_maxrpid_minpinlen
+_fido_cbor_info_minpinlen
+_fido_cbor_info_new
+_fido_cbor_info_new_pin_required
+_fido_cbor_info_options_len
+_fido_cbor_info_options_name_ptr
+_fido_cbor_info_options_value_ptr
+_fido_cbor_info_protocols_len
+_fido_cbor_info_protocols_ptr
+_fido_cbor_info_rk_remaining
+_fido_cbor_info_transports_len
+_fido_cbor_info_transports_ptr
+_fido_cbor_info_uv_attempts
+_fido_cbor_info_uv_modality
+_fido_cbor_info_versions_len
+_fido_cbor_info_versions_ptr
+_fido_cred_attstmt_len
+_fido_cred_attstmt_ptr
+_fido_cred_authdata_len
+_fido_cred_authdata_ptr
+_fido_cred_authdata_raw_len
+_fido_cred_authdata_raw_ptr
+_fido_cred_clientdata_hash_len
+_fido_cred_clientdata_hash_ptr
+_fido_cred_display_name
+_fido_cred_empty_exclude_list
+_fido_cred_exclude
+_fido_cred_flags
+_fido_cred_largeblob_key_len
+_fido_cred_largeblob_key_ptr
+_fido_cred_sigcount
+_fido_cred_fmt
+_fido_cred_free
+_fido_cred_id_len
+_fido_cred_id_ptr
+_fido_cred_aaguid_len
+_fido_cred_aaguid_ptr
+_fido_credman_del_dev_rk
+_fido_credman_get_dev_metadata
+_fido_credman_get_dev_rk
+_fido_credman_get_dev_rp
+_fido_credman_metadata_free
+_fido_credman_metadata_new
+_fido_credman_rk
+_fido_credman_rk_count
+_fido_credman_rk_existing
+_fido_credman_rk_free
+_fido_credman_rk_new
+_fido_credman_rk_remaining
+_fido_credman_rp_count
+_fido_credman_rp_free
+_fido_credman_rp_id
+_fido_credman_rp_id_hash_len
+_fido_credman_rp_id_hash_ptr
+_fido_credman_rp_name
+_fido_credman_rp_new
+_fido_credman_set_dev_rk
+_fido_cred_new
+_fido_cred_pin_minlen
+_fido_cred_prot
+_fido_cred_pubkey_len
+_fido_cred_pubkey_ptr
+_fido_cred_rp_id
+_fido_cred_rp_name
+_fido_cred_set_attstmt
+_fido_cred_set_authdata
+_fido_cred_set_authdata_raw
+_fido_cred_set_blob
+_fido_cred_set_clientdata
+_fido_cred_set_clientdata_hash
+_fido_cred_set_extensions
+_fido_cred_set_fmt
+_fido_cred_set_id
+_fido_cred_set_options
+_fido_cred_set_pin_minlen
+_fido_cred_set_prot
+_fido_cred_set_rk
+_fido_cred_set_rp
+_fido_cred_set_sig
+_fido_cred_set_type
+_fido_cred_set_user
+_fido_cred_set_uv
+_fido_cred_set_x509
+_fido_cred_sig_len
+_fido_cred_sig_ptr
+_fido_cred_type
+_fido_cred_user_id_len
+_fido_cred_user_id_ptr
+_fido_cred_user_name
+_fido_cred_verify
+_fido_cred_verify_self
+_fido_cred_x5c_len
+_fido_cred_x5c_ptr
+_fido_dev_build
+_fido_dev_cancel
+_fido_dev_close
+_fido_dev_enable_entattest
+_fido_dev_flags
+_fido_dev_force_fido2
+_fido_dev_force_pin_change
+_fido_dev_force_u2f
+_fido_dev_free
+_fido_dev_get_assert
+_fido_dev_get_cbor_info
+_fido_dev_get_retry_count
+_fido_dev_get_uv_retry_count
+_fido_dev_get_touch_begin
+_fido_dev_get_touch_status
+_fido_dev_has_pin
+_fido_dev_has_uv
+_fido_dev_info_free
+_fido_dev_info_manifest
+_fido_dev_info_manufacturer_string
+_fido_dev_info_new
+_fido_dev_info_path
+_fido_dev_info_product
+_fido_dev_info_product_string
+_fido_dev_info_ptr
+_fido_dev_info_set
+_fido_dev_info_vendor
+_fido_dev_io_handle
+_fido_dev_is_fido2
+_fido_dev_is_winhello
+_fido_dev_major
+_fido_dev_make_cred
+_fido_dev_minor
+_fido_dev_new
+_fido_dev_new_with_info
+_fido_dev_open
+_fido_dev_open_with_info
+_fido_dev_protocol
+_fido_dev_reset
+_fido_dev_set_io_functions
+_fido_dev_set_pin
+_fido_dev_set_pin_minlen
+_fido_dev_set_pin_minlen_rpid
+_fido_dev_set_sigmask
+_fido_dev_set_timeout
+_fido_dev_set_transport_functions
+_fido_dev_supports_cred_prot
+_fido_dev_supports_credman
+_fido_dev_supports_permissions
+_fido_dev_supports_pin
+_fido_dev_supports_uv
+_fido_dev_toggle_always_uv
+_fido_dev_largeblob_get
+_fido_dev_largeblob_get_array
+_fido_dev_largeblob_remove
+_fido_dev_largeblob_set
+_fido_dev_largeblob_set_array
+_fido_init
+_fido_set_log_handler
+_fido_strerr
+_rs256_pk_free
+_rs256_pk_from_ptr
+_rs256_pk_from_EVP_PKEY
+_rs256_pk_from_RSA
+_rs256_pk_new
+_rs256_pk_to_EVP_PKEY
diff --git a/src/export.msvc b/src/export.msvc
new file mode 100644
index 0000000..c5b2edc
--- /dev/null
+++ b/src/export.msvc
@@ -0,0 +1,264 @@
+EXPORTS
+eddsa_pk_free
+eddsa_pk_from_EVP_PKEY
+eddsa_pk_from_ptr
+eddsa_pk_new
+eddsa_pk_to_EVP_PKEY
+es256_pk_free
+es256_pk_from_EC_KEY
+es256_pk_from_EVP_PKEY
+es256_pk_from_ptr
+es256_pk_new
+es256_pk_to_EVP_PKEY
+es384_pk_free
+es384_pk_from_EC_KEY
+es384_pk_from_EVP_PKEY
+es384_pk_from_ptr
+es384_pk_new
+es384_pk_to_EVP_PKEY
+fido_assert_allow_cred
+fido_assert_authdata_len
+fido_assert_authdata_ptr
+fido_assert_authdata_raw_len
+fido_assert_authdata_raw_ptr
+fido_assert_blob_len
+fido_assert_blob_ptr
+fido_assert_clientdata_hash_len
+fido_assert_clientdata_hash_ptr
+fido_assert_count
+fido_assert_empty_allow_list
+fido_assert_flags
+fido_assert_free
+fido_assert_hmac_secret_len
+fido_assert_hmac_secret_ptr
+fido_assert_id_len
+fido_assert_id_ptr
+fido_assert_largeblob_key_len
+fido_assert_largeblob_key_ptr
+fido_assert_new
+fido_assert_rp_id
+fido_assert_set_authdata
+fido_assert_set_authdata_raw
+fido_assert_set_clientdata
+fido_assert_set_clientdata_hash
+fido_assert_set_count
+fido_assert_set_extensions
+fido_assert_set_hmac_salt
+fido_assert_set_hmac_secret
+fido_assert_set_options
+fido_assert_set_rp
+fido_assert_set_sig
+fido_assert_set_up
+fido_assert_set_uv
+fido_assert_set_winhello_appid
+fido_assert_sigcount
+fido_assert_sig_len
+fido_assert_sig_ptr
+fido_assert_user_display_name
+fido_assert_user_icon
+fido_assert_user_id_len
+fido_assert_user_id_ptr
+fido_assert_user_name
+fido_assert_verify
+fido_bio_dev_enroll_begin
+fido_bio_dev_enroll_cancel
+fido_bio_dev_enroll_continue
+fido_bio_dev_enroll_remove
+fido_bio_dev_get_info
+fido_bio_dev_get_template_array
+fido_bio_dev_set_template_name
+fido_bio_enroll_free
+fido_bio_enroll_last_status
+fido_bio_enroll_new
+fido_bio_enroll_remaining_samples
+fido_bio_info_free
+fido_bio_info_max_samples
+fido_bio_info_new
+fido_bio_info_type
+fido_bio_template
+fido_bio_template_array_count
+fido_bio_template_array_free
+fido_bio_template_array_new
+fido_bio_template_free
+fido_bio_template_id_len
+fido_bio_template_id_ptr
+fido_bio_template_name
+fido_bio_template_new
+fido_bio_template_set_id
+fido_bio_template_set_name
+fido_cbor_info_aaguid_len
+fido_cbor_info_aaguid_ptr
+fido_cbor_info_algorithm_cose
+fido_cbor_info_algorithm_count
+fido_cbor_info_algorithm_type
+fido_cbor_info_certs_len
+fido_cbor_info_certs_name_ptr
+fido_cbor_info_certs_value_ptr
+fido_cbor_info_extensions_len
+fido_cbor_info_extensions_ptr
+fido_cbor_info_free
+fido_cbor_info_fwversion
+fido_cbor_info_maxcredbloblen
+fido_cbor_info_maxcredcntlst
+fido_cbor_info_maxcredidlen
+fido_cbor_info_maxlargeblob
+fido_cbor_info_maxmsgsiz
+fido_cbor_info_maxrpid_minpinlen
+fido_cbor_info_minpinlen
+fido_cbor_info_new
+fido_cbor_info_new_pin_required
+fido_cbor_info_options_len
+fido_cbor_info_options_name_ptr
+fido_cbor_info_options_value_ptr
+fido_cbor_info_protocols_len
+fido_cbor_info_protocols_ptr
+fido_cbor_info_rk_remaining
+fido_cbor_info_transports_len
+fido_cbor_info_transports_ptr
+fido_cbor_info_uv_attempts
+fido_cbor_info_uv_modality
+fido_cbor_info_versions_len
+fido_cbor_info_versions_ptr
+fido_cred_attstmt_len
+fido_cred_attstmt_ptr
+fido_cred_authdata_len
+fido_cred_authdata_ptr
+fido_cred_authdata_raw_len
+fido_cred_authdata_raw_ptr
+fido_cred_clientdata_hash_len
+fido_cred_clientdata_hash_ptr
+fido_cred_display_name
+fido_cred_empty_exclude_list
+fido_cred_exclude
+fido_cred_flags
+fido_cred_largeblob_key_len
+fido_cred_largeblob_key_ptr
+fido_cred_sigcount
+fido_cred_fmt
+fido_cred_free
+fido_cred_id_len
+fido_cred_id_ptr
+fido_cred_aaguid_len
+fido_cred_aaguid_ptr
+fido_credman_del_dev_rk
+fido_credman_get_dev_metadata
+fido_credman_get_dev_rk
+fido_credman_get_dev_rp
+fido_credman_metadata_free
+fido_credman_metadata_new
+fido_credman_rk
+fido_credman_rk_count
+fido_credman_rk_existing
+fido_credman_rk_free
+fido_credman_rk_new
+fido_credman_rk_remaining
+fido_credman_rp_count
+fido_credman_rp_free
+fido_credman_rp_id
+fido_credman_rp_id_hash_len
+fido_credman_rp_id_hash_ptr
+fido_credman_rp_name
+fido_credman_rp_new
+fido_credman_set_dev_rk
+fido_cred_new
+fido_cred_pin_minlen
+fido_cred_prot
+fido_cred_pubkey_len
+fido_cred_pubkey_ptr
+fido_cred_rp_id
+fido_cred_rp_name
+fido_cred_set_attstmt
+fido_cred_set_authdata
+fido_cred_set_authdata_raw
+fido_cred_set_blob
+fido_cred_set_clientdata
+fido_cred_set_clientdata_hash
+fido_cred_set_extensions
+fido_cred_set_fmt
+fido_cred_set_id
+fido_cred_set_options
+fido_cred_set_pin_minlen
+fido_cred_set_prot
+fido_cred_set_rk
+fido_cred_set_rp
+fido_cred_set_sig
+fido_cred_set_type
+fido_cred_set_user
+fido_cred_set_uv
+fido_cred_set_x509
+fido_cred_sig_len
+fido_cred_sig_ptr
+fido_cred_type
+fido_cred_user_id_len
+fido_cred_user_id_ptr
+fido_cred_user_name
+fido_cred_verify
+fido_cred_verify_self
+fido_cred_x5c_len
+fido_cred_x5c_ptr
+fido_dev_build
+fido_dev_cancel
+fido_dev_close
+fido_dev_enable_entattest
+fido_dev_flags
+fido_dev_force_fido2
+fido_dev_force_pin_change
+fido_dev_force_u2f
+fido_dev_free
+fido_dev_get_assert
+fido_dev_get_cbor_info
+fido_dev_get_retry_count
+fido_dev_get_uv_retry_count
+fido_dev_get_touch_begin
+fido_dev_get_touch_status
+fido_dev_has_pin
+fido_dev_has_uv
+fido_dev_info_free
+fido_dev_info_manifest
+fido_dev_info_manufacturer_string
+fido_dev_info_new
+fido_dev_info_path
+fido_dev_info_product
+fido_dev_info_product_string
+fido_dev_info_ptr
+fido_dev_info_set
+fido_dev_info_vendor
+fido_dev_io_handle
+fido_dev_is_fido2
+fido_dev_is_winhello
+fido_dev_major
+fido_dev_make_cred
+fido_dev_minor
+fido_dev_new
+fido_dev_new_with_info
+fido_dev_open
+fido_dev_open_with_info
+fido_dev_protocol
+fido_dev_reset
+fido_dev_set_io_functions
+fido_dev_set_pin
+fido_dev_set_pin_minlen
+fido_dev_set_pin_minlen_rpid
+fido_dev_set_sigmask
+fido_dev_set_timeout
+fido_dev_set_transport_functions
+fido_dev_supports_cred_prot
+fido_dev_supports_credman
+fido_dev_supports_permissions
+fido_dev_supports_pin
+fido_dev_supports_uv
+fido_dev_toggle_always_uv
+fido_dev_largeblob_get
+fido_dev_largeblob_get_array
+fido_dev_largeblob_remove
+fido_dev_largeblob_set
+fido_dev_largeblob_set_array
+fido_init
+fido_set_log_handler
+fido_strerr
+rs256_pk_free
+rs256_pk_from_ptr
+rs256_pk_from_EVP_PKEY
+rs256_pk_from_RSA
+rs256_pk_new
+rs256_pk_to_EVP_PKEY
diff --git a/src/extern.h b/src/extern.h
new file mode 100644
index 0000000..1bc95b2
--- /dev/null
+++ b/src/extern.h
@@ -0,0 +1,276 @@
+/*
+ * 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
+ */
+
+#ifndef _EXTERN_H
+#define _EXTERN_H
+
+#ifdef __MINGW32__
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+
+#include <stdint.h>
+
+#include "fido/types.h"
+#include "blob.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* aes256 */
+int aes256_cbc_dec(const fido_dev_t *dev, const fido_blob_t *,
+ const fido_blob_t *, fido_blob_t *);
+int aes256_cbc_enc(const fido_dev_t *dev, const fido_blob_t *,
+ const fido_blob_t *, fido_blob_t *);
+int aes256_gcm_dec(const fido_blob_t *, const fido_blob_t *,
+ const fido_blob_t *, const fido_blob_t *, fido_blob_t *);
+int aes256_gcm_enc(const fido_blob_t *, const fido_blob_t *,
+ const fido_blob_t *, const fido_blob_t *, fido_blob_t *);
+
+/* cbor encoding functions */
+cbor_item_t *cbor_build_uint(const uint64_t);
+cbor_item_t *cbor_flatten_vector(cbor_item_t **, size_t);
+cbor_item_t *cbor_encode_assert_opt(fido_opt_t, fido_opt_t);
+cbor_item_t *cbor_encode_change_pin_auth(const fido_dev_t *,
+ const fido_blob_t *, const fido_blob_t *, const fido_blob_t *);
+cbor_item_t *cbor_encode_cred_ext(const fido_cred_ext_t *, const fido_blob_t *);
+cbor_item_t *cbor_encode_assert_ext(fido_dev_t *,
+ const fido_assert_ext_t *, const fido_blob_t *, const es256_pk_t *);
+cbor_item_t *cbor_encode_cred_opt(fido_opt_t, fido_opt_t);
+cbor_item_t *cbor_encode_pin_auth(const fido_dev_t *, const fido_blob_t *,
+ const fido_blob_t *);
+cbor_item_t *cbor_encode_pin_opt(const fido_dev_t *);
+cbor_item_t *cbor_encode_pubkey(const fido_blob_t *);
+cbor_item_t *cbor_encode_pubkey_list(const fido_blob_array_t *);
+cbor_item_t *cbor_encode_pubkey_param(int);
+cbor_item_t *cbor_encode_rp_entity(const fido_rp_t *);
+cbor_item_t *cbor_encode_str_array(const fido_str_array_t *);
+cbor_item_t *cbor_encode_user_entity(const fido_user_t *);
+cbor_item_t *es256_pk_encode(const es256_pk_t *, int);
+
+/* cbor decoding functions */
+int cbor_decode_attstmt(const cbor_item_t *, fido_attstmt_t *);
+int cbor_decode_bool(const cbor_item_t *, bool *);
+int cbor_decode_cred_authdata(const cbor_item_t *, int, fido_blob_t *,
+ fido_authdata_t *, fido_attcred_t *, fido_cred_ext_t *);
+int cbor_decode_assert_authdata(const cbor_item_t *, fido_blob_t *,
+ fido_authdata_t *, fido_assert_extattr_t *);
+int cbor_decode_cred_id(const cbor_item_t *, fido_blob_t *);
+int cbor_decode_fmt(const cbor_item_t *, char **);
+int cbor_decode_pubkey(const cbor_item_t *, int *, void *);
+int cbor_decode_rp_entity(const cbor_item_t *, fido_rp_t *);
+int cbor_decode_uint64(const cbor_item_t *, uint64_t *);
+int cbor_decode_user(const cbor_item_t *, fido_user_t *);
+int es256_pk_decode(const cbor_item_t *, es256_pk_t *);
+int es384_pk_decode(const cbor_item_t *, es384_pk_t *);
+int rs256_pk_decode(const cbor_item_t *, rs256_pk_t *);
+int eddsa_pk_decode(const cbor_item_t *, eddsa_pk_t *);
+
+/* auxiliary cbor routines */
+int cbor_add_bool(cbor_item_t *, const char *, fido_opt_t);
+int cbor_add_bytestring(cbor_item_t *, const char *, const unsigned char *,
+ size_t);
+int cbor_add_string(cbor_item_t *, const char *, const char *);
+int cbor_array_iter(const cbor_item_t *, void *, int(*)(const cbor_item_t *,
+ void *));
+int cbor_build_frame(uint8_t, cbor_item_t *[], size_t, fido_blob_t *);
+int cbor_bytestring_copy(const cbor_item_t *, unsigned char **, size_t *);
+int cbor_map_iter(const cbor_item_t *, void *, int(*)(const cbor_item_t *,
+ const cbor_item_t *, void *));
+int cbor_string_copy(const cbor_item_t *, char **);
+int cbor_parse_reply(const unsigned char *, size_t, void *,
+ int(*)(const cbor_item_t *, const cbor_item_t *, void *));
+int cbor_add_uv_params(fido_dev_t *, uint8_t, const fido_blob_t *,
+ const es256_pk_t *, const fido_blob_t *, const char *, const char *,
+ cbor_item_t **, cbor_item_t **, int *);
+void cbor_vector_free(cbor_item_t **, size_t);
+int cbor_array_append(cbor_item_t **, cbor_item_t *);
+int cbor_array_drop(cbor_item_t **, size_t);
+
+/* deflate */
+int fido_compress(fido_blob_t *, const fido_blob_t *);
+int fido_uncompress(fido_blob_t *, const fido_blob_t *, size_t);
+
+#ifndef nitems
+#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+/* buf */
+int fido_buf_read(const unsigned char **, size_t *, void *, size_t);
+int fido_buf_write(unsigned char **, size_t *, const void *, size_t);
+
+/* hid i/o */
+void *fido_hid_open(const char *);
+void fido_hid_close(void *);
+int fido_hid_read(void *, unsigned char *, size_t, int);
+int fido_hid_write(void *, const unsigned char *, size_t);
+int fido_hid_get_usage(const uint8_t *, size_t, uint32_t *);
+int fido_hid_get_report_len(const uint8_t *, size_t, size_t *, size_t *);
+int fido_hid_unix_open(const char *);
+int fido_hid_unix_wait(int, int, const fido_sigset_t *);
+int fido_hid_set_sigmask(void *, const fido_sigset_t *);
+size_t fido_hid_report_in_len(void *);
+size_t fido_hid_report_out_len(void *);
+
+/* nfc i/o */
+bool fido_is_nfc(const char *);
+bool nfc_is_fido(const char *);
+void *fido_nfc_open(const char *);
+void fido_nfc_close(void *);
+int fido_nfc_read(void *, unsigned char *, size_t, int);
+int fido_nfc_write(void *, const unsigned char *, size_t);
+int fido_nfc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int);
+int fido_nfc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t);
+int fido_nfc_set_sigmask(void *, const fido_sigset_t *);
+int fido_dev_set_nfc(fido_dev_t *);
+
+/* pcsc i/o */
+bool fido_is_pcsc(const char *);
+void *fido_pcsc_open(const char *);
+void fido_pcsc_close(void *);
+int fido_pcsc_read(void *, unsigned char *, size_t, int);
+int fido_pcsc_write(void *, const unsigned char *, size_t);
+int fido_pcsc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int);
+int fido_pcsc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t);
+int fido_dev_set_pcsc(fido_dev_t *);
+
+/* windows hello */
+int fido_winhello_manifest(fido_dev_info_t *, size_t, size_t *);
+int fido_winhello_open(fido_dev_t *);
+int fido_winhello_close(fido_dev_t *);
+int fido_winhello_cancel(fido_dev_t *);
+int fido_winhello_get_assert(fido_dev_t *, fido_assert_t *, const char *, int);
+int fido_winhello_get_cbor_info(fido_dev_t *, fido_cbor_info_t *);
+int fido_winhello_make_cred(fido_dev_t *, fido_cred_t *, const char *, int);
+
+/* generic i/o */
+int fido_rx_cbor_status(fido_dev_t *, int *);
+int fido_rx(fido_dev_t *, uint8_t, void *, size_t, int *);
+int fido_tx(fido_dev_t *, uint8_t, const void *, size_t, int *);
+
+/* log */
+#ifdef FIDO_NO_DIAGNOSTIC
+#define fido_log_init(...) do { /* nothing */ } while (0)
+#define fido_log_debug(...) do { /* nothing */ } while (0)
+#define fido_log_xxd(...) do { /* nothing */ } while (0)
+#define fido_log_error(...) do { /* nothing */ } while (0)
+#else
+#ifdef __GNUC__
+void fido_log_init(void);
+void fido_log_debug(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void fido_log_xxd(const void *, size_t, const char *, ...)
+ __attribute__((__format__ (printf, 3, 4)));
+void fido_log_error(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+#else
+void fido_log_init(void);
+void fido_log_debug(const char *, ...);
+void fido_log_xxd(const void *, size_t, const char *, ...);
+void fido_log_error(int, const char *, ...);
+#endif /* __GNUC__ */
+#endif /* FIDO_NO_DIAGNOSTIC */
+
+/* u2f */
+int u2f_register(fido_dev_t *, fido_cred_t *, int *);
+int u2f_authenticate(fido_dev_t *, fido_assert_t *, int *);
+int u2f_get_touch_begin(fido_dev_t *, int *);
+int u2f_get_touch_status(fido_dev_t *, int *, int *);
+
+/* unexposed fido ops */
+uint8_t fido_dev_get_pin_protocol(const fido_dev_t *);
+int fido_dev_authkey(fido_dev_t *, es256_pk_t *, int *);
+int fido_dev_get_cbor_info_wait(fido_dev_t *, fido_cbor_info_t *, int *);
+int fido_dev_get_uv_token(fido_dev_t *, uint8_t, const char *,
+ const fido_blob_t *, const es256_pk_t *, const char *, fido_blob_t *,
+ int *);
+uint64_t fido_dev_maxmsgsize(const fido_dev_t *);
+int fido_do_ecdh(fido_dev_t *, es256_pk_t **, fido_blob_t **, int *);
+
+/* types */
+void fido_algo_array_free(fido_algo_array_t *);
+void fido_byte_array_free(fido_byte_array_t *);
+void fido_cert_array_free(fido_cert_array_t *);
+void fido_opt_array_free(fido_opt_array_t *);
+void fido_str_array_free(fido_str_array_t *);
+void fido_algo_free(fido_algo_t *);
+int fido_str_array_pack(fido_str_array_t *, const char * const *, size_t);
+
+/* misc */
+void fido_assert_reset_rx(fido_assert_t *);
+void fido_assert_reset_tx(fido_assert_t *);
+void fido_cred_reset_rx(fido_cred_t *);
+void fido_cred_reset_tx(fido_cred_t *);
+void fido_cbor_info_reset(fido_cbor_info_t *);
+int fido_blob_serialise(fido_blob_t *, const cbor_item_t *);
+int fido_check_flags(uint8_t, fido_opt_t, fido_opt_t);
+int fido_check_rp_id(const char *, const unsigned char *);
+int fido_get_random(void *, size_t);
+int fido_sha256(fido_blob_t *, const u_char *, size_t);
+int fido_time_now(struct timespec *);
+int fido_time_delta(const struct timespec *, int *);
+int fido_to_uint64(const char *, int, uint64_t *);
+
+/* crypto */
+int es256_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *);
+int es384_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *);
+int rs256_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *);
+int eddsa_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *);
+int rs1_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *);
+int es256_pk_verify_sig(const fido_blob_t *, const es256_pk_t *,
+ const fido_blob_t *);
+int es384_pk_verify_sig(const fido_blob_t *, const es384_pk_t *,
+ const fido_blob_t *);
+int rs256_pk_verify_sig(const fido_blob_t *, const rs256_pk_t *,
+ const fido_blob_t *);
+int eddsa_pk_verify_sig(const fido_blob_t *, const eddsa_pk_t *,
+ const fido_blob_t *);
+int fido_get_signed_hash(int, fido_blob_t *, const fido_blob_t *,
+ const fido_blob_t *);
+int fido_get_signed_hash_tpm(fido_blob_t *, const fido_blob_t *,
+ const fido_blob_t *, const fido_attstmt_t *, const fido_attcred_t *);
+
+/* device manifest functions */
+int fido_hid_manifest(fido_dev_info_t *, size_t, size_t *);
+int fido_nfc_manifest(fido_dev_info_t *, size_t, size_t *);
+int fido_pcsc_manifest(fido_dev_info_t *, size_t, size_t *);
+
+/* fuzzing instrumentation */
+#ifdef FIDO_FUZZ
+uint32_t uniform_random(uint32_t);
+#endif
+
+/* internal device capability flags */
+#define FIDO_DEV_PIN_SET 0x001
+#define FIDO_DEV_PIN_UNSET 0x002
+#define FIDO_DEV_CRED_PROT 0x004
+#define FIDO_DEV_CREDMAN 0x008
+#define FIDO_DEV_PIN_PROTOCOL1 0x010
+#define FIDO_DEV_PIN_PROTOCOL2 0x020
+#define FIDO_DEV_UV_SET 0x040
+#define FIDO_DEV_UV_UNSET 0x080
+#define FIDO_DEV_TOKEN_PERMS 0x100
+#define FIDO_DEV_WINHELLO 0x200
+
+/* miscellanea */
+#define FIDO_DUMMY_CLIENTDATA ""
+#define FIDO_DUMMY_RP_ID "localhost"
+#define FIDO_DUMMY_USER_NAME "dummy"
+#define FIDO_DUMMY_USER_ID 1
+#define FIDO_WINHELLO_PATH "windows://hello"
+#define FIDO_NFC_PREFIX "nfc:"
+#define FIDO_PCSC_PREFIX "pcsc:"
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_EXTERN_H */
diff --git a/src/fallthrough.h b/src/fallthrough.h
new file mode 100644
index 0000000..bdfd30f
--- /dev/null
+++ b/src/fallthrough.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 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
+ */
+
+#ifndef _FALLTHROUGH_H
+#define _FALLTHROUGH_H
+
+#if defined(__GNUC__)
+#if __has_attribute(fallthrough)
+#define FALLTHROUGH __attribute__((fallthrough));
+#endif
+#endif /* __GNUC__ */
+
+#ifndef FALLTHROUGH
+#define FALLTHROUGH /* FALLTHROUGH */
+#endif
+
+#endif /* !_FALLTHROUGH_H */
diff --git a/src/fido.h b/src/fido.h
new file mode 100644
index 0000000..914e377
--- /dev/null
+++ b/src/fido.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_H
+#define _FIDO_H
+
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _FIDO_INTERNAL
+#include <sys/types.h>
+
+#include <cbor.h>
+#include <limits.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "blob.h"
+#include "iso7816.h"
+#include "extern.h"
+#endif
+
+#include "fido/err.h"
+#include "fido/param.h"
+#include "fido/types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+fido_assert_t *fido_assert_new(void);
+fido_cred_t *fido_cred_new(void);
+fido_dev_t *fido_dev_new(void);
+fido_dev_t *fido_dev_new_with_info(const fido_dev_info_t *);
+fido_dev_info_t *fido_dev_info_new(size_t);
+fido_cbor_info_t *fido_cbor_info_new(void);
+void *fido_dev_io_handle(const fido_dev_t *);
+
+void fido_assert_free(fido_assert_t **);
+void fido_cbor_info_free(fido_cbor_info_t **);
+void fido_cred_free(fido_cred_t **);
+void fido_dev_force_fido2(fido_dev_t *);
+void fido_dev_force_u2f(fido_dev_t *);
+void fido_dev_free(fido_dev_t **);
+void fido_dev_info_free(fido_dev_info_t **, size_t);
+
+/* fido_init() flags. */
+#define FIDO_DEBUG 0x01
+#define FIDO_DISABLE_U2F_FALLBACK 0x02
+
+void fido_init(int);
+void fido_set_log_handler(fido_log_handler_t *);
+
+const unsigned char *fido_assert_authdata_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_authdata_raw_ptr(const fido_assert_t *,
+ size_t);
+const unsigned char *fido_assert_clientdata_hash_ptr(const fido_assert_t *);
+const unsigned char *fido_assert_hmac_secret_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_id_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_largeblob_key_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_sig_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_user_id_ptr(const fido_assert_t *, size_t);
+const unsigned char *fido_assert_blob_ptr(const fido_assert_t *, size_t);
+
+char **fido_cbor_info_certs_name_ptr(const fido_cbor_info_t *);
+char **fido_cbor_info_extensions_ptr(const fido_cbor_info_t *);
+char **fido_cbor_info_options_name_ptr(const fido_cbor_info_t *);
+char **fido_cbor_info_transports_ptr(const fido_cbor_info_t *);
+char **fido_cbor_info_versions_ptr(const fido_cbor_info_t *);
+const bool *fido_cbor_info_options_value_ptr(const fido_cbor_info_t *);
+const char *fido_assert_rp_id(const fido_assert_t *);
+const char *fido_assert_user_display_name(const fido_assert_t *, size_t);
+const char *fido_assert_user_icon(const fido_assert_t *, size_t);
+const char *fido_assert_user_name(const fido_assert_t *, size_t);
+const char *fido_cbor_info_algorithm_type(const fido_cbor_info_t *, size_t);
+const char *fido_cred_display_name(const fido_cred_t *);
+const char *fido_cred_fmt(const fido_cred_t *);
+const char *fido_cred_rp_id(const fido_cred_t *);
+const char *fido_cred_rp_name(const fido_cred_t *);
+const char *fido_cred_user_name(const fido_cred_t *);
+const char *fido_dev_info_manufacturer_string(const fido_dev_info_t *);
+const char *fido_dev_info_path(const fido_dev_info_t *);
+const char *fido_dev_info_product_string(const fido_dev_info_t *);
+const fido_dev_info_t *fido_dev_info_ptr(const fido_dev_info_t *, size_t);
+const uint8_t *fido_cbor_info_protocols_ptr(const fido_cbor_info_t *);
+const uint64_t *fido_cbor_info_certs_value_ptr(const fido_cbor_info_t *);
+const unsigned char *fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *);
+const unsigned char *fido_cred_aaguid_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_attstmt_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_authdata_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_authdata_raw_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_clientdata_hash_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_id_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_largeblob_key_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_pubkey_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_sig_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_user_id_ptr(const fido_cred_t *);
+const unsigned char *fido_cred_x5c_ptr(const fido_cred_t *);
+
+int fido_assert_allow_cred(fido_assert_t *, const unsigned char *, size_t);
+int fido_assert_empty_allow_list(fido_assert_t *);
+int fido_assert_set_authdata(fido_assert_t *, size_t, const unsigned char *,
+ size_t);
+int fido_assert_set_authdata_raw(fido_assert_t *, size_t, const unsigned char *,
+ size_t);
+int fido_assert_set_clientdata(fido_assert_t *, const unsigned char *, size_t);
+int fido_assert_set_clientdata_hash(fido_assert_t *, const unsigned char *,
+ size_t);
+int fido_assert_set_count(fido_assert_t *, size_t);
+int fido_assert_set_extensions(fido_assert_t *, int);
+int fido_assert_set_hmac_salt(fido_assert_t *, const unsigned char *, size_t);
+int fido_assert_set_hmac_secret(fido_assert_t *, size_t, const unsigned char *,
+ size_t);
+int fido_assert_set_options(fido_assert_t *, bool, bool);
+int fido_assert_set_rp(fido_assert_t *, const char *);
+int fido_assert_set_up(fido_assert_t *, fido_opt_t);
+int fido_assert_set_uv(fido_assert_t *, fido_opt_t);
+int fido_assert_set_sig(fido_assert_t *, size_t, const unsigned char *, size_t);
+int fido_assert_set_winhello_appid(fido_assert_t *, const char *);
+int fido_assert_verify(const fido_assert_t *, size_t, int, const void *);
+int fido_cbor_info_algorithm_cose(const fido_cbor_info_t *, size_t);
+int fido_cred_empty_exclude_list(fido_cred_t *);
+int fido_cred_exclude(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_prot(const fido_cred_t *);
+int fido_cred_set_attstmt(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_authdata(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_authdata_raw(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_blob(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_clientdata(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_clientdata_hash(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_extensions(fido_cred_t *, int);
+int fido_cred_set_fmt(fido_cred_t *, const char *);
+int fido_cred_set_id(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_options(fido_cred_t *, bool, bool);
+int fido_cred_set_pin_minlen(fido_cred_t *, size_t);
+int fido_cred_set_prot(fido_cred_t *, int);
+int fido_cred_set_rk(fido_cred_t *, fido_opt_t);
+int fido_cred_set_rp(fido_cred_t *, const char *, const char *);
+int fido_cred_set_sig(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_set_type(fido_cred_t *, int);
+int fido_cred_set_uv(fido_cred_t *, fido_opt_t);
+int fido_cred_type(const fido_cred_t *);
+int fido_cred_set_user(fido_cred_t *, const unsigned char *, size_t,
+ const char *, const char *, const char *);
+int fido_cred_set_x509(fido_cred_t *, const unsigned char *, size_t);
+int fido_cred_verify(const fido_cred_t *);
+int fido_cred_verify_self(const fido_cred_t *);
+#ifdef _FIDO_SIGSET_DEFINED
+int fido_dev_set_sigmask(fido_dev_t *, const fido_sigset_t *);
+#endif
+int fido_dev_cancel(fido_dev_t *);
+int fido_dev_close(fido_dev_t *);
+int fido_dev_get_assert(fido_dev_t *, fido_assert_t *, const char *);
+int fido_dev_get_cbor_info(fido_dev_t *, fido_cbor_info_t *);
+int fido_dev_get_retry_count(fido_dev_t *, int *);
+int fido_dev_get_uv_retry_count(fido_dev_t *, int *);
+int fido_dev_get_touch_begin(fido_dev_t *);
+int fido_dev_get_touch_status(fido_dev_t *, int *, int);
+int fido_dev_info_manifest(fido_dev_info_t *, size_t, size_t *);
+int fido_dev_info_set(fido_dev_info_t *, size_t, const char *, const char *,
+ const char *, const fido_dev_io_t *, const fido_dev_transport_t *);
+int fido_dev_make_cred(fido_dev_t *, fido_cred_t *, const char *);
+int fido_dev_open_with_info(fido_dev_t *);
+int fido_dev_open(fido_dev_t *, const char *);
+int fido_dev_reset(fido_dev_t *);
+int fido_dev_set_io_functions(fido_dev_t *, const fido_dev_io_t *);
+int fido_dev_set_pin(fido_dev_t *, const char *, const char *);
+int fido_dev_set_transport_functions(fido_dev_t *, const fido_dev_transport_t *);
+int fido_dev_set_timeout(fido_dev_t *, int);
+
+size_t fido_assert_authdata_len(const fido_assert_t *, size_t);
+size_t fido_assert_authdata_raw_len(const fido_assert_t *, size_t);
+size_t fido_assert_clientdata_hash_len(const fido_assert_t *);
+size_t fido_assert_count(const fido_assert_t *);
+size_t fido_assert_hmac_secret_len(const fido_assert_t *, size_t);
+size_t fido_assert_id_len(const fido_assert_t *, size_t);
+size_t fido_assert_largeblob_key_len(const fido_assert_t *, size_t);
+size_t fido_assert_sig_len(const fido_assert_t *, size_t);
+size_t fido_assert_user_id_len(const fido_assert_t *, size_t);
+size_t fido_assert_blob_len(const fido_assert_t *, size_t);
+size_t fido_cbor_info_aaguid_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_algorithm_count(const fido_cbor_info_t *);
+size_t fido_cbor_info_certs_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_extensions_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_options_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_protocols_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_transports_len(const fido_cbor_info_t *);
+size_t fido_cbor_info_versions_len(const fido_cbor_info_t *);
+size_t fido_cred_aaguid_len(const fido_cred_t *);
+size_t fido_cred_attstmt_len(const fido_cred_t *);
+size_t fido_cred_authdata_len(const fido_cred_t *);
+size_t fido_cred_authdata_raw_len(const fido_cred_t *);
+size_t fido_cred_clientdata_hash_len(const fido_cred_t *);
+size_t fido_cred_id_len(const fido_cred_t *);
+size_t fido_cred_largeblob_key_len(const fido_cred_t *);
+size_t fido_cred_pin_minlen(const fido_cred_t *);
+size_t fido_cred_pubkey_len(const fido_cred_t *);
+size_t fido_cred_sig_len(const fido_cred_t *);
+size_t fido_cred_user_id_len(const fido_cred_t *);
+size_t fido_cred_x5c_len(const fido_cred_t *);
+
+uint8_t fido_assert_flags(const fido_assert_t *, size_t);
+uint32_t fido_assert_sigcount(const fido_assert_t *, size_t);
+uint8_t fido_cred_flags(const fido_cred_t *);
+uint32_t fido_cred_sigcount(const fido_cred_t *);
+uint8_t fido_dev_protocol(const fido_dev_t *);
+uint8_t fido_dev_major(const fido_dev_t *);
+uint8_t fido_dev_minor(const fido_dev_t *);
+uint8_t fido_dev_build(const fido_dev_t *);
+uint8_t fido_dev_flags(const fido_dev_t *);
+int16_t fido_dev_info_vendor(const fido_dev_info_t *);
+int16_t fido_dev_info_product(const fido_dev_info_t *);
+uint64_t fido_cbor_info_fwversion(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_maxcredbloblen(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_maxcredidlen(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_maxlargeblob(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_maxrpid_minpinlen(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_minpinlen(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_uv_attempts(const fido_cbor_info_t *);
+uint64_t fido_cbor_info_uv_modality(const fido_cbor_info_t *);
+int64_t fido_cbor_info_rk_remaining(const fido_cbor_info_t *);
+
+bool fido_dev_has_pin(const fido_dev_t *);
+bool fido_dev_has_uv(const fido_dev_t *);
+bool fido_dev_is_fido2(const fido_dev_t *);
+bool fido_dev_is_winhello(const fido_dev_t *);
+bool fido_dev_supports_credman(const fido_dev_t *);
+bool fido_dev_supports_cred_prot(const fido_dev_t *);
+bool fido_dev_supports_permissions(const fido_dev_t *);
+bool fido_dev_supports_pin(const fido_dev_t *);
+bool fido_dev_supports_uv(const fido_dev_t *);
+bool fido_cbor_info_new_pin_required(const fido_cbor_info_t *);
+
+int fido_dev_largeblob_get(fido_dev_t *, const unsigned char *, size_t,
+ unsigned char **, size_t *);
+int fido_dev_largeblob_set(fido_dev_t *, const unsigned char *, size_t,
+ const unsigned char *, size_t, const char *);
+int fido_dev_largeblob_remove(fido_dev_t *, const unsigned char *, size_t,
+ const char *);
+int fido_dev_largeblob_get_array(fido_dev_t *, unsigned char **, size_t *);
+int fido_dev_largeblob_set_array(fido_dev_t *, const unsigned char *, size_t,
+ const char *);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_H */
diff --git a/src/fido/bio.h b/src/fido/bio.h
new file mode 100644
index 0000000..f5039e0
--- /dev/null
+++ b/src/fido/bio.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_BIO_H
+#define _FIDO_BIO_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _FIDO_INTERNAL
+#include "blob.h"
+#include "fido/err.h"
+#include "fido/param.h"
+#include "fido/types.h"
+#else
+#include <fido.h>
+#include <fido/err.h>
+#include <fido/param.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef _FIDO_INTERNAL
+struct fido_bio_template {
+ fido_blob_t id;
+ char *name;
+};
+
+struct fido_bio_template_array {
+ struct fido_bio_template *ptr;
+ size_t n_alloc; /* number of allocated entries */
+ size_t n_rx; /* number of populated entries */
+};
+
+struct fido_bio_enroll {
+ uint8_t remaining_samples;
+ uint8_t last_status;
+ fido_blob_t *token;
+};
+
+struct fido_bio_info {
+ uint8_t type;
+ uint8_t max_samples;
+};
+#endif
+
+typedef struct fido_bio_template fido_bio_template_t;
+typedef struct fido_bio_template_array fido_bio_template_array_t;
+typedef struct fido_bio_enroll fido_bio_enroll_t;
+typedef struct fido_bio_info fido_bio_info_t;
+
+#define FIDO_BIO_ENROLL_FP_GOOD 0x00
+#define FIDO_BIO_ENROLL_FP_TOO_HIGH 0x01
+#define FIDO_BIO_ENROLL_FP_TOO_LOW 0x02
+#define FIDO_BIO_ENROLL_FP_TOO_LEFT 0x03
+#define FIDO_BIO_ENROLL_FP_TOO_RIGHT 0x04
+#define FIDO_BIO_ENROLL_FP_TOO_FAST 0x05
+#define FIDO_BIO_ENROLL_FP_TOO_SLOW 0x06
+#define FIDO_BIO_ENROLL_FP_POOR_QUALITY 0x07
+#define FIDO_BIO_ENROLL_FP_TOO_SKEWED 0x08
+#define FIDO_BIO_ENROLL_FP_TOO_SHORT 0x09
+#define FIDO_BIO_ENROLL_FP_MERGE_FAILURE 0x0a
+#define FIDO_BIO_ENROLL_FP_EXISTS 0x0b
+#define FIDO_BIO_ENROLL_FP_DATABASE_FULL 0x0c
+#define FIDO_BIO_ENROLL_NO_USER_ACTIVITY 0x0d
+#define FIDO_BIO_ENROLL_NO_USER_PRESENCE_TRANSITION 0x0e
+
+const char *fido_bio_template_name(const fido_bio_template_t *);
+const fido_bio_template_t *fido_bio_template(const fido_bio_template_array_t *,
+ size_t);
+const unsigned char *fido_bio_template_id_ptr(const fido_bio_template_t *);
+fido_bio_enroll_t *fido_bio_enroll_new(void);
+fido_bio_info_t *fido_bio_info_new(void);
+fido_bio_template_array_t *fido_bio_template_array_new(void);
+fido_bio_template_t *fido_bio_template_new(void);
+int fido_bio_dev_enroll_begin(fido_dev_t *, fido_bio_template_t *,
+ fido_bio_enroll_t *, uint32_t, const char *);
+int fido_bio_dev_enroll_cancel(fido_dev_t *);
+int fido_bio_dev_enroll_continue(fido_dev_t *, const fido_bio_template_t *,
+ fido_bio_enroll_t *, uint32_t);
+int fido_bio_dev_enroll_remove(fido_dev_t *, const fido_bio_template_t *,
+ const char *);
+int fido_bio_dev_get_info(fido_dev_t *, fido_bio_info_t *);
+int fido_bio_dev_get_template_array(fido_dev_t *, fido_bio_template_array_t *,
+ const char *);
+int fido_bio_dev_set_template_name(fido_dev_t *, const fido_bio_template_t *,
+ const char *);
+int fido_bio_template_set_id(fido_bio_template_t *, const unsigned char *,
+ size_t);
+int fido_bio_template_set_name(fido_bio_template_t *, const char *);
+size_t fido_bio_template_array_count(const fido_bio_template_array_t *);
+size_t fido_bio_template_id_len(const fido_bio_template_t *);
+uint8_t fido_bio_enroll_last_status(const fido_bio_enroll_t *);
+uint8_t fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *);
+uint8_t fido_bio_info_max_samples(const fido_bio_info_t *);
+uint8_t fido_bio_info_type(const fido_bio_info_t *);
+void fido_bio_enroll_free(fido_bio_enroll_t **);
+void fido_bio_info_free(fido_bio_info_t **);
+void fido_bio_template_array_free(fido_bio_template_array_t **);
+void fido_bio_template_free(fido_bio_template_t **);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_BIO_H */
diff --git a/src/fido/config.h b/src/fido/config.h
new file mode 100644
index 0000000..cba286f
--- /dev/null
+++ b/src/fido/config.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_CONFIG_H
+#define _FIDO_CONFIG_H
+
+#ifdef _FIDO_INTERNAL
+#include "blob.h"
+#include "fido/err.h"
+#include "fido/param.h"
+#include "fido/types.h"
+#else
+#include <fido.h>
+#include <fido/err.h>
+#include <fido/param.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+int fido_dev_enable_entattest(fido_dev_t *, const char *);
+int fido_dev_force_pin_change(fido_dev_t *, const char *);
+int fido_dev_toggle_always_uv(fido_dev_t *, const char *);
+int fido_dev_set_pin_minlen(fido_dev_t *, size_t, const char *);
+int fido_dev_set_pin_minlen_rpid(fido_dev_t *, const char * const *, size_t,
+ const char *);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_CONFIG_H */
diff --git a/src/fido/credman.h b/src/fido/credman.h
new file mode 100644
index 0000000..9f9dff1
--- /dev/null
+++ b/src/fido/credman.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2019-2021 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_CREDMAN_H
+#define _FIDO_CREDMAN_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _FIDO_INTERNAL
+#include "blob.h"
+#include "fido/err.h"
+#include "fido/param.h"
+#include "fido/types.h"
+#else
+#include <fido.h>
+#include <fido/err.h>
+#include <fido/param.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#ifdef _FIDO_INTERNAL
+struct fido_credman_metadata {
+ uint64_t rk_existing;
+ uint64_t rk_remaining;
+};
+
+struct fido_credman_single_rp {
+ fido_rp_t rp_entity;
+ fido_blob_t rp_id_hash;
+};
+
+struct fido_credman_rp {
+ struct fido_credman_single_rp *ptr;
+ size_t n_alloc; /* number of allocated entries */
+ size_t n_rx; /* number of populated entries */
+};
+
+struct fido_credman_rk {
+ fido_cred_t *ptr;
+ size_t n_alloc; /* number of allocated entries */
+ size_t n_rx; /* number of populated entries */
+};
+#endif
+
+typedef struct fido_credman_metadata fido_credman_metadata_t;
+typedef struct fido_credman_rk fido_credman_rk_t;
+typedef struct fido_credman_rp fido_credman_rp_t;
+
+const char *fido_credman_rp_id(const fido_credman_rp_t *, size_t);
+const char *fido_credman_rp_name(const fido_credman_rp_t *, size_t);
+
+const fido_cred_t *fido_credman_rk(const fido_credman_rk_t *, size_t);
+const unsigned char *fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *,
+ size_t);
+
+fido_credman_metadata_t *fido_credman_metadata_new(void);
+fido_credman_rk_t *fido_credman_rk_new(void);
+fido_credman_rp_t *fido_credman_rp_new(void);
+
+int fido_credman_del_dev_rk(fido_dev_t *, const unsigned char *, size_t,
+ const char *);
+int fido_credman_get_dev_metadata(fido_dev_t *, fido_credman_metadata_t *,
+ const char *);
+int fido_credman_get_dev_rk(fido_dev_t *, const char *, fido_credman_rk_t *,
+ const char *);
+int fido_credman_get_dev_rp(fido_dev_t *, fido_credman_rp_t *, const char *);
+int fido_credman_set_dev_rk(fido_dev_t *, fido_cred_t *, const char *);
+
+size_t fido_credman_rk_count(const fido_credman_rk_t *);
+size_t fido_credman_rp_count(const fido_credman_rp_t *);
+size_t fido_credman_rp_id_hash_len(const fido_credman_rp_t *, size_t);
+
+uint64_t fido_credman_rk_existing(const fido_credman_metadata_t *);
+uint64_t fido_credman_rk_remaining(const fido_credman_metadata_t *);
+
+void fido_credman_metadata_free(fido_credman_metadata_t **);
+void fido_credman_rk_free(fido_credman_rk_t **);
+void fido_credman_rp_free(fido_credman_rp_t **);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_CREDMAN_H */
diff --git a/src/fido/eddsa.h b/src/fido/eddsa.h
new file mode 100644
index 0000000..5c0b681
--- /dev/null
+++ b/src/fido/eddsa.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_EDDSA_H
+#define _FIDO_EDDSA_H
+
+#include <openssl/ec.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _FIDO_INTERNAL
+#include "types.h"
+#else
+#include <fido.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+eddsa_pk_t *eddsa_pk_new(void);
+void eddsa_pk_free(eddsa_pk_t **);
+EVP_PKEY *eddsa_pk_to_EVP_PKEY(const eddsa_pk_t *);
+
+int eddsa_pk_from_EVP_PKEY(eddsa_pk_t *, const EVP_PKEY *);
+int eddsa_pk_from_ptr(eddsa_pk_t *, const void *, size_t);
+
+#ifdef _FIDO_INTERNAL
+
+#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3070000f
+#define EVP_PKEY_ED25519 EVP_PKEY_NONE
+int EVP_PKEY_get_raw_public_key(const EVP_PKEY *, unsigned char *, size_t *);
+EVP_PKEY *EVP_PKEY_new_raw_public_key(int, ENGINE *, const unsigned char *,
+ size_t);
+int EVP_DigestVerify(EVP_MD_CTX *, const unsigned char *, size_t,
+ const unsigned char *, size_t);
+#endif /* LIBRESSL_VERSION_NUMBER */
+
+#endif /* _FIDO_INTERNAL */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_EDDSA_H */
diff --git a/src/fido/err.h b/src/fido/err.h
new file mode 100644
index 0000000..7db25f2
--- /dev/null
+++ b/src/fido/err.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_ERR_H
+#define _FIDO_ERR_H
+
+#define FIDO_ERR_SUCCESS 0x00
+#define FIDO_ERR_INVALID_COMMAND 0x01
+#define FIDO_ERR_INVALID_PARAMETER 0x02
+#define FIDO_ERR_INVALID_LENGTH 0x03
+#define FIDO_ERR_INVALID_SEQ 0x04
+#define FIDO_ERR_TIMEOUT 0x05
+#define FIDO_ERR_CHANNEL_BUSY 0x06
+#define FIDO_ERR_LOCK_REQUIRED 0x0a
+#define FIDO_ERR_INVALID_CHANNEL 0x0b
+#define FIDO_ERR_CBOR_UNEXPECTED_TYPE 0x11
+#define FIDO_ERR_INVALID_CBOR 0x12
+#define FIDO_ERR_MISSING_PARAMETER 0x14
+#define FIDO_ERR_LIMIT_EXCEEDED 0x15
+#define FIDO_ERR_UNSUPPORTED_EXTENSION 0x16
+#define FIDO_ERR_FP_DATABASE_FULL 0x17
+#define FIDO_ERR_LARGEBLOB_STORAGE_FULL 0x18
+#define FIDO_ERR_CREDENTIAL_EXCLUDED 0x19
+#define FIDO_ERR_PROCESSING 0x21
+#define FIDO_ERR_INVALID_CREDENTIAL 0x22
+#define FIDO_ERR_USER_ACTION_PENDING 0x23
+#define FIDO_ERR_OPERATION_PENDING 0x24
+#define FIDO_ERR_NO_OPERATIONS 0x25
+#define FIDO_ERR_UNSUPPORTED_ALGORITHM 0x26
+#define FIDO_ERR_OPERATION_DENIED 0x27
+#define FIDO_ERR_KEY_STORE_FULL 0x28
+#define FIDO_ERR_NOT_BUSY 0x29
+#define FIDO_ERR_NO_OPERATION_PENDING 0x2a
+#define FIDO_ERR_UNSUPPORTED_OPTION 0x2b
+#define FIDO_ERR_INVALID_OPTION 0x2c
+#define FIDO_ERR_KEEPALIVE_CANCEL 0x2d
+#define FIDO_ERR_NO_CREDENTIALS 0x2e
+#define FIDO_ERR_USER_ACTION_TIMEOUT 0x2f
+#define FIDO_ERR_NOT_ALLOWED 0x30
+#define FIDO_ERR_PIN_INVALID 0x31
+#define FIDO_ERR_PIN_BLOCKED 0x32
+#define FIDO_ERR_PIN_AUTH_INVALID 0x33
+#define FIDO_ERR_PIN_AUTH_BLOCKED 0x34
+#define FIDO_ERR_PIN_NOT_SET 0x35
+#define FIDO_ERR_PIN_REQUIRED 0x36
+#define FIDO_ERR_PIN_POLICY_VIOLATION 0x37
+#define FIDO_ERR_PIN_TOKEN_EXPIRED 0x38
+#define FIDO_ERR_REQUEST_TOO_LARGE 0x39
+#define FIDO_ERR_ACTION_TIMEOUT 0x3a
+#define FIDO_ERR_UP_REQUIRED 0x3b
+#define FIDO_ERR_UV_BLOCKED 0x3c
+#define FIDO_ERR_UV_INVALID 0x3f
+#define FIDO_ERR_UNAUTHORIZED_PERM 0x40
+#define FIDO_ERR_ERR_OTHER 0x7f
+#define FIDO_ERR_SPEC_LAST 0xdf
+
+/* defined internally */
+#define FIDO_OK FIDO_ERR_SUCCESS
+#define FIDO_ERR_TX -1
+#define FIDO_ERR_RX -2
+#define FIDO_ERR_RX_NOT_CBOR -3
+#define FIDO_ERR_RX_INVALID_CBOR -4
+#define FIDO_ERR_INVALID_PARAM -5
+#define FIDO_ERR_INVALID_SIG -6
+#define FIDO_ERR_INVALID_ARGUMENT -7
+#define FIDO_ERR_USER_PRESENCE_REQUIRED -8
+#define FIDO_ERR_INTERNAL -9
+#define FIDO_ERR_NOTFOUND -10
+#define FIDO_ERR_COMPRESS -11
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+const char *fido_strerr(int);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* _FIDO_ERR_H */
diff --git a/src/fido/es256.h b/src/fido/es256.h
new file mode 100644
index 0000000..0450de2
--- /dev/null
+++ b/src/fido/es256.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2021 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_ES256_H
+#define _FIDO_ES256_H
+
+#include <openssl/ec.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _FIDO_INTERNAL
+#include "types.h"
+#else
+#include <fido.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+es256_pk_t *es256_pk_new(void);
+void es256_pk_free(es256_pk_t **);
+EVP_PKEY *es256_pk_to_EVP_PKEY(const es256_pk_t *);
+
+int es256_pk_from_EC_KEY(es256_pk_t *, const EC_KEY *);
+int es256_pk_from_EVP_PKEY(es256_pk_t *, const EVP_PKEY *);
+int es256_pk_from_ptr(es256_pk_t *, const void *, size_t);
+
+#ifdef _FIDO_INTERNAL
+es256_sk_t *es256_sk_new(void);
+void es256_sk_free(es256_sk_t **);
+EVP_PKEY *es256_sk_to_EVP_PKEY(const es256_sk_t *);
+
+int es256_derive_pk(const es256_sk_t *, es256_pk_t *);
+int es256_sk_create(es256_sk_t *);
+
+int es256_pk_set_x(es256_pk_t *, const unsigned char *);
+int es256_pk_set_y(es256_pk_t *, const unsigned char *);
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_ES256_H */
diff --git a/src/fido/es384.h b/src/fido/es384.h
new file mode 100644
index 0000000..b4b4ca7
--- /dev/null
+++ b/src/fido/es384.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_ES384_H
+#define _FIDO_ES384_H
+
+#include <openssl/ec.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _FIDO_INTERNAL
+#include "types.h"
+#else
+#include <fido.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+es384_pk_t *es384_pk_new(void);
+void es384_pk_free(es384_pk_t **);
+EVP_PKEY *es384_pk_to_EVP_PKEY(const es384_pk_t *);
+
+int es384_pk_from_EC_KEY(es384_pk_t *, const EC_KEY *);
+int es384_pk_from_EVP_PKEY(es384_pk_t *, const EVP_PKEY *);
+int es384_pk_from_ptr(es384_pk_t *, const void *, size_t);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_ES384_H */
diff --git a/src/fido/param.h b/src/fido/param.h
new file mode 100644
index 0000000..511370b
--- /dev/null
+++ b/src/fido/param.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_PARAM_H
+#define _FIDO_PARAM_H
+
+/* Authentication data flags. */
+#define CTAP_AUTHDATA_USER_PRESENT 0x01
+#define CTAP_AUTHDATA_USER_VERIFIED 0x04
+#define CTAP_AUTHDATA_ATT_CRED 0x40
+#define CTAP_AUTHDATA_EXT_DATA 0x80
+
+/* CTAPHID command opcodes. */
+#define CTAP_CMD_PING 0x01
+#define CTAP_CMD_MSG 0x03
+#define CTAP_CMD_LOCK 0x04
+#define CTAP_CMD_INIT 0x06
+#define CTAP_CMD_WINK 0x08
+#define CTAP_CMD_CBOR 0x10
+#define CTAP_CMD_CANCEL 0x11
+#define CTAP_KEEPALIVE 0x3b
+#define CTAP_FRAME_INIT 0x80
+
+/* CTAPHID CBOR command opcodes. */
+#define CTAP_CBOR_MAKECRED 0x01
+#define CTAP_CBOR_ASSERT 0x02
+#define CTAP_CBOR_GETINFO 0x04
+#define CTAP_CBOR_CLIENT_PIN 0x06
+#define CTAP_CBOR_RESET 0x07
+#define CTAP_CBOR_NEXT_ASSERT 0x08
+#define CTAP_CBOR_LARGEBLOB 0x0c
+#define CTAP_CBOR_CONFIG 0x0d
+#define CTAP_CBOR_BIO_ENROLL_PRE 0x40
+#define CTAP_CBOR_CRED_MGMT_PRE 0x41
+
+/* Supported CTAP PIN/UV Auth Protocols. */
+#define CTAP_PIN_PROTOCOL1 1
+#define CTAP_PIN_PROTOCOL2 2
+
+/* U2F command opcodes. */
+#define U2F_CMD_REGISTER 0x01
+#define U2F_CMD_AUTH 0x02
+
+/* U2F command flags. */
+#define U2F_AUTH_SIGN 0x03
+#define U2F_AUTH_CHECK 0x07
+
+/* ISO7816-4 status words. */
+#define SW1_MORE_DATA 0x61
+#define SW_CONDITIONS_NOT_SATISFIED 0x6985
+#define SW_WRONG_DATA 0x6a80
+#define SW_NO_ERROR 0x9000
+
+/* HID Broadcast channel ID. */
+#define CTAP_CID_BROADCAST 0xffffffff
+
+#define CTAP_INIT_HEADER_LEN 7
+#define CTAP_CONT_HEADER_LEN 5
+
+/* Maximum length of a CTAP HID report in bytes. */
+#define CTAP_MAX_REPORT_LEN 64
+
+/* Minimum length of a CTAP HID report in bytes. */
+#define CTAP_MIN_REPORT_LEN (CTAP_INIT_HEADER_LEN + 1)
+
+/* Randomness device on UNIX-like platforms. */
+#ifndef FIDO_RANDOM_DEV
+#define FIDO_RANDOM_DEV "/dev/urandom"
+#endif
+
+/* Maximum message size in bytes. */
+#ifndef FIDO_MAXMSG
+#define FIDO_MAXMSG 2048
+#endif
+
+/* CTAP capability bits. */
+#define FIDO_CAP_WINK 0x01 /* if set, device supports CTAP_CMD_WINK */
+#define FIDO_CAP_CBOR 0x04 /* if set, device supports CTAP_CMD_CBOR */
+#define FIDO_CAP_NMSG 0x08 /* if set, device doesn't support CTAP_CMD_MSG */
+
+/* Supported COSE algorithms. */
+#define COSE_UNSPEC 0
+#define COSE_ES256 -7
+#define COSE_EDDSA -8
+#define COSE_ECDH_ES256 -25
+#define COSE_ES384 -35
+#define COSE_RS256 -257
+#define COSE_RS1 -65535
+
+/* Supported COSE types. */
+#define COSE_KTY_OKP 1
+#define COSE_KTY_EC2 2
+#define COSE_KTY_RSA 3
+
+/* Supported curves. */
+#define COSE_P256 1
+#define COSE_P384 2
+#define COSE_ED25519 6
+
+/* Supported extensions. */
+#define FIDO_EXT_HMAC_SECRET 0x01
+#define FIDO_EXT_CRED_PROTECT 0x02
+#define FIDO_EXT_LARGEBLOB_KEY 0x04
+#define FIDO_EXT_CRED_BLOB 0x08
+#define FIDO_EXT_MINPINLEN 0x10
+
+/* Supported credential protection policies. */
+#define FIDO_CRED_PROT_UV_OPTIONAL 0x01
+#define FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID 0x02
+#define FIDO_CRED_PROT_UV_REQUIRED 0x03
+
+#ifdef _FIDO_INTERNAL
+#define FIDO_EXT_ASSERT_MASK (FIDO_EXT_HMAC_SECRET|FIDO_EXT_LARGEBLOB_KEY| \
+ FIDO_EXT_CRED_BLOB)
+#define FIDO_EXT_CRED_MASK (FIDO_EXT_HMAC_SECRET|FIDO_EXT_CRED_PROTECT| \
+ FIDO_EXT_LARGEBLOB_KEY|FIDO_EXT_CRED_BLOB| \
+ FIDO_EXT_MINPINLEN)
+#endif /* _FIDO_INTERNAL */
+
+/* Recognised UV modes. */
+#define FIDO_UV_MODE_TUP 0x0001 /* internal test of user presence */
+#define FIDO_UV_MODE_FP 0x0002 /* internal fingerprint check */
+#define FIDO_UV_MODE_PIN 0x0004 /* internal pin check */
+#define FIDO_UV_MODE_VOICE 0x0008 /* internal voice recognition */
+#define FIDO_UV_MODE_FACE 0x0010 /* internal face recognition */
+#define FIDO_UV_MODE_LOCATION 0x0020 /* internal location check */
+#define FIDO_UV_MODE_EYE 0x0040 /* internal eyeprint check */
+#define FIDO_UV_MODE_DRAWN 0x0080 /* internal drawn pattern check */
+#define FIDO_UV_MODE_HAND 0x0100 /* internal handprint verification */
+#define FIDO_UV_MODE_NONE 0x0200 /* TUP/UV not required */
+#define FIDO_UV_MODE_ALL 0x0400 /* all supported UV modes required */
+#define FIDO_UV_MODE_EXT_PIN 0x0800 /* external pin verification */
+#define FIDO_UV_MODE_EXT_DRAWN 0x1000 /* external drawn pattern check */
+
+#endif /* !_FIDO_PARAM_H */
diff --git a/src/fido/rs256.h b/src/fido/rs256.h
new file mode 100644
index 0000000..6f8c781
--- /dev/null
+++ b/src/fido/rs256.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2021 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_RS256_H
+#define _FIDO_RS256_H
+
+#include <openssl/rsa.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef _FIDO_INTERNAL
+#include "types.h"
+#else
+#include <fido.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+rs256_pk_t *rs256_pk_new(void);
+void rs256_pk_free(rs256_pk_t **);
+EVP_PKEY *rs256_pk_to_EVP_PKEY(const rs256_pk_t *);
+
+int rs256_pk_from_EVP_PKEY(rs256_pk_t *, const EVP_PKEY *);
+int rs256_pk_from_RSA(rs256_pk_t *, const RSA *);
+int rs256_pk_from_ptr(rs256_pk_t *, const void *, size_t);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_RS256_H */
diff --git a/src/fido/types.h b/src/fido/types.h
new file mode 100644
index 0000000..01d6820
--- /dev/null
+++ b/src/fido/types.h
@@ -0,0 +1,337 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _FIDO_TYPES_H
+#define _FIDO_TYPES_H
+
+#ifdef __MINGW32__
+#include <sys/types.h>
+#endif
+
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct fido_dev;
+
+typedef void *fido_dev_io_open_t(const char *);
+typedef void fido_dev_io_close_t(void *);
+typedef int fido_dev_io_read_t(void *, unsigned char *, size_t, int);
+typedef int fido_dev_io_write_t(void *, const unsigned char *, size_t);
+typedef int fido_dev_rx_t(struct fido_dev *, uint8_t, unsigned char *, size_t, int);
+typedef int fido_dev_tx_t(struct fido_dev *, uint8_t, const unsigned char *, size_t);
+
+typedef struct fido_dev_io {
+ fido_dev_io_open_t *open;
+ fido_dev_io_close_t *close;
+ fido_dev_io_read_t *read;
+ fido_dev_io_write_t *write;
+} fido_dev_io_t;
+
+typedef struct fido_dev_transport {
+ fido_dev_rx_t *rx;
+ fido_dev_tx_t *tx;
+} fido_dev_transport_t;
+
+typedef enum {
+ FIDO_OPT_OMIT = 0, /* use authenticator's default */
+ FIDO_OPT_FALSE, /* explicitly set option to false */
+ FIDO_OPT_TRUE, /* explicitly set option to true */
+} fido_opt_t;
+
+typedef void fido_log_handler_t(const char *);
+
+#undef _FIDO_SIGSET_DEFINED
+#define _FIDO_SIGSET_DEFINED
+#ifdef _WIN32
+typedef int fido_sigset_t;
+#elif defined(SIG_BLOCK)
+typedef sigset_t fido_sigset_t;
+#else
+#undef _FIDO_SIGSET_DEFINED
+#endif
+
+#ifdef _FIDO_INTERNAL
+#include "packed.h"
+#include "blob.h"
+
+/* COSE ES256 (ECDSA over P-256 with SHA-256) public key */
+typedef struct es256_pk {
+ unsigned char x[32];
+ unsigned char y[32];
+} es256_pk_t;
+
+/* COSE ES256 (ECDSA over P-256 with SHA-256) (secret) key */
+typedef struct es256_sk {
+ unsigned char d[32];
+} es256_sk_t;
+
+/* COSE ES384 (ECDSA over P-384 with SHA-384) public key */
+typedef struct es384_pk {
+ unsigned char x[48];
+ unsigned char y[48];
+} es384_pk_t;
+
+/* COSE RS256 (2048-bit RSA with PKCS1 padding and SHA-256) public key */
+typedef struct rs256_pk {
+ unsigned char n[256];
+ unsigned char e[3];
+} rs256_pk_t;
+
+/* COSE EDDSA (ED25519) */
+typedef struct eddsa_pk {
+ unsigned char x[32];
+} eddsa_pk_t;
+
+PACKED_TYPE(fido_authdata_t,
+struct fido_authdata {
+ unsigned char rp_id_hash[32]; /* sha256 of fido_rp.id */
+ uint8_t flags; /* user present/verified */
+ uint32_t sigcount; /* signature counter */
+ /* actually longer */
+})
+
+PACKED_TYPE(fido_attcred_raw_t,
+struct fido_attcred_raw {
+ unsigned char aaguid[16]; /* credential's aaguid */
+ uint16_t id_len; /* credential id length */
+ uint8_t body[]; /* credential id + pubkey */
+})
+
+typedef struct fido_attcred {
+ unsigned char aaguid[16]; /* credential's aaguid */
+ fido_blob_t id; /* credential id */
+ int type; /* credential's cose algorithm */
+ union { /* credential's public key */
+ es256_pk_t es256;
+ es384_pk_t es384;
+ rs256_pk_t rs256;
+ eddsa_pk_t eddsa;
+ } pubkey;
+} fido_attcred_t;
+
+typedef struct fido_attstmt {
+ fido_blob_t certinfo; /* tpm attestation TPMS_ATTEST structure */
+ fido_blob_t pubarea; /* tpm attestation TPMT_PUBLIC structure */
+ fido_blob_t cbor; /* cbor-encoded attestation statement */
+ fido_blob_t x5c; /* attestation certificate */
+ fido_blob_t sig; /* attestation signature */
+ int alg; /* attestation algorithm (cose) */
+} fido_attstmt_t;
+
+typedef struct fido_rp {
+ char *id; /* relying party id */
+ char *name; /* relying party name */
+} fido_rp_t;
+
+typedef struct fido_user {
+ fido_blob_t id; /* required */
+ char *icon; /* optional */
+ char *name; /* optional */
+ char *display_name; /* required */
+} fido_user_t;
+
+typedef struct fido_cred_ext {
+ int mask; /* enabled extensions */
+ int prot; /* protection policy */
+ size_t minpinlen; /* minimum pin length */
+} fido_cred_ext_t;
+
+typedef struct fido_cred {
+ fido_blob_t cd; /* client data */
+ fido_blob_t cdh; /* client data hash */
+ fido_rp_t rp; /* relying party */
+ fido_user_t user; /* user entity */
+ fido_blob_array_t excl; /* list of credential ids to exclude */
+ fido_opt_t rk; /* resident key */
+ fido_opt_t uv; /* user verification */
+ fido_cred_ext_t ext; /* extensions */
+ int type; /* cose algorithm */
+ char *fmt; /* credential format */
+ fido_cred_ext_t authdata_ext; /* decoded extensions */
+ fido_blob_t authdata_cbor; /* cbor-encoded payload */
+ fido_blob_t authdata_raw; /* cbor-decoded payload */
+ fido_authdata_t authdata; /* decoded authdata payload */
+ fido_attcred_t attcred; /* returned credential (key + id) */
+ fido_attstmt_t attstmt; /* attestation statement (x509 + sig) */
+ fido_blob_t largeblob_key; /* decoded large blob key */
+ fido_blob_t blob; /* CTAP 2.1 credBlob */
+} fido_cred_t;
+
+typedef struct fido_assert_extattr {
+ int mask; /* decoded extensions */
+ fido_blob_t hmac_secret_enc; /* hmac secret, encrypted */
+ fido_blob_t blob; /* decoded CTAP 2.1 credBlob */
+} fido_assert_extattr_t;
+
+typedef struct _fido_assert_stmt {
+ fido_blob_t id; /* credential id */
+ fido_user_t user; /* user attributes */
+ fido_blob_t hmac_secret; /* hmac secret */
+ fido_assert_extattr_t authdata_ext; /* decoded extensions */
+ fido_blob_t authdata_cbor; /* raw cbor payload */
+ fido_blob_t authdata_raw; /* raw authdata */
+ fido_authdata_t authdata; /* decoded authdata payload */
+ fido_blob_t sig; /* signature of cdh + authdata */
+ fido_blob_t largeblob_key; /* decoded large blob key */
+} fido_assert_stmt;
+
+typedef struct fido_assert_ext {
+ int mask; /* enabled extensions */
+ fido_blob_t hmac_salt; /* optional hmac-secret salt */
+} fido_assert_ext_t;
+
+typedef struct fido_assert {
+ char *rp_id; /* relying party id */
+ char *appid; /* winhello u2f appid */
+ fido_blob_t cd; /* client data */
+ fido_blob_t cdh; /* client data hash */
+ fido_blob_array_t allow_list; /* list of allowed credentials */
+ fido_opt_t up; /* user presence */
+ fido_opt_t uv; /* user verification */
+ fido_assert_ext_t ext; /* enabled extensions */
+ fido_assert_stmt *stmt; /* array of expected assertions */
+ size_t stmt_cnt; /* number of allocated assertions */
+ size_t stmt_len; /* number of received assertions */
+} fido_assert_t;
+
+typedef struct fido_opt_array {
+ char **name;
+ bool *value;
+ size_t len;
+} fido_opt_array_t;
+
+typedef struct fido_str_array {
+ char **ptr;
+ size_t len;
+} fido_str_array_t;
+
+typedef struct fido_byte_array {
+ uint8_t *ptr;
+ size_t len;
+} fido_byte_array_t;
+
+typedef struct fido_algo {
+ char *type;
+ int cose;
+} fido_algo_t;
+
+typedef struct fido_algo_array {
+ fido_algo_t *ptr;
+ size_t len;
+} fido_algo_array_t;
+
+typedef struct fido_cert_array {
+ char **name;
+ uint64_t *value;
+ size_t len;
+} fido_cert_array_t;
+
+typedef struct fido_cbor_info {
+ fido_str_array_t versions; /* supported versions: fido2|u2f */
+ fido_str_array_t extensions; /* list of supported extensions */
+ fido_str_array_t transports; /* list of supported transports */
+ unsigned char aaguid[16]; /* aaguid */
+ fido_opt_array_t options; /* list of supported options */
+ uint64_t maxmsgsiz; /* maximum message size */
+ fido_byte_array_t protocols; /* supported pin protocols */
+ fido_algo_array_t algorithms; /* list of supported algorithms */
+ uint64_t maxcredcntlst; /* max credentials in list */
+ uint64_t maxcredidlen; /* max credential ID length */
+ uint64_t fwversion; /* firmware version */
+ uint64_t maxcredbloblen; /* max credBlob length */
+ uint64_t maxlargeblob; /* max largeBlob array length */
+ uint64_t maxrpid_minlen; /* max rpid in set_pin_minlen_rpid */
+ uint64_t minpinlen; /* min pin len enforced */
+ uint64_t uv_attempts; /* platform uv attempts */
+ uint64_t uv_modality; /* bitmask of supported uv types */
+ int64_t rk_remaining; /* remaining resident credentials */
+ bool new_pin_reqd; /* new pin required */
+ fido_cert_array_t certs; /* associated certifications */
+} fido_cbor_info_t;
+
+typedef struct fido_dev_info {
+ char *path; /* device path */
+ int16_t vendor_id; /* 2-byte vendor id */
+ int16_t product_id; /* 2-byte product id */
+ char *manufacturer; /* manufacturer string */
+ char *product; /* product string */
+ fido_dev_io_t io; /* i/o functions */
+ fido_dev_transport_t transport; /* transport functions */
+} fido_dev_info_t;
+
+PACKED_TYPE(fido_ctap_info_t,
+/* defined in section 8.1.9.1.3 (CTAPHID_INIT) of the fido2 ctap spec */
+struct fido_ctap_info {
+ uint64_t nonce; /* echoed nonce */
+ uint32_t cid; /* channel id */
+ uint8_t protocol; /* ctaphid protocol id */
+ uint8_t major; /* major version number */
+ uint8_t minor; /* minor version number */
+ uint8_t build; /* build version number */
+ uint8_t flags; /* capabilities flags; see FIDO_CAP_* */
+})
+
+typedef struct fido_dev {
+ uint64_t nonce; /* issued nonce */
+ fido_ctap_info_t attr; /* device attributes */
+ uint32_t cid; /* assigned channel id */
+ char *path; /* device path */
+ void *io_handle; /* abstract i/o handle */
+ fido_dev_io_t io; /* i/o functions */
+ bool io_own; /* device has own io/transport */
+ size_t rx_len; /* length of HID input reports */
+ size_t tx_len; /* length of HID output reports */
+ int flags; /* internal flags; see FIDO_DEV_* */
+ fido_dev_transport_t transport; /* transport functions */
+ uint64_t maxmsgsize; /* max message size */
+ int timeout_ms; /* read timeout in ms */
+} fido_dev_t;
+
+#else
+typedef struct fido_assert fido_assert_t;
+typedef struct fido_cbor_info fido_cbor_info_t;
+typedef struct fido_cred fido_cred_t;
+typedef struct fido_dev fido_dev_t;
+typedef struct fido_dev_info fido_dev_info_t;
+typedef struct es256_pk es256_pk_t;
+typedef struct es256_sk es256_sk_t;
+typedef struct es384_pk es384_pk_t;
+typedef struct rs256_pk rs256_pk_t;
+typedef struct eddsa_pk eddsa_pk_t;
+#endif /* _FIDO_INTERNAL */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_TYPES_H */
diff --git a/src/hid.c b/src/hid.c
new file mode 100644
index 0000000..662bd44
--- /dev/null
+++ b/src/hid.c
@@ -0,0 +1,222 @@
+/*
+ * 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"
+
+static int
+get_key_len(uint8_t tag, uint8_t *key, size_t *key_len)
+{
+ *key = tag & 0xfc;
+ if ((*key & 0xf0) == 0xf0) {
+ fido_log_debug("%s: *key=0x%02x", __func__, *key);
+ return (-1);
+ }
+
+ *key_len = tag & 0x3;
+ if (*key_len == 3) {
+ *key_len = 4;
+ }
+
+ return (0);
+}
+
+static int
+get_key_val(const void *body, size_t key_len, uint32_t *val)
+{
+ const uint8_t *ptr = body;
+
+ switch (key_len) {
+ case 0:
+ *val = 0;
+ break;
+ case 1:
+ *val = ptr[0];
+ break;
+ case 2:
+ *val = (uint32_t)((ptr[1] << 8) | ptr[0]);
+ break;
+ default:
+ fido_log_debug("%s: key_len=%zu", __func__, key_len);
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+fido_hid_get_usage(const uint8_t *report_ptr, size_t report_len,
+ uint32_t *usage_page)
+{
+ const uint8_t *ptr = report_ptr;
+ size_t len = report_len;
+
+ while (len > 0) {
+ const uint8_t tag = ptr[0];
+ ptr++;
+ len--;
+
+ uint8_t key;
+ size_t key_len;
+ uint32_t key_val;
+
+ if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
+ get_key_val(ptr, key_len, &key_val) < 0) {
+ return (-1);
+ }
+
+ if (key == 0x4) {
+ *usage_page = key_val;
+ }
+
+ ptr += key_len;
+ len -= key_len;
+ }
+
+ return (0);
+}
+
+int
+fido_hid_get_report_len(const uint8_t *report_ptr, size_t report_len,
+ size_t *report_in_len, size_t *report_out_len)
+{
+ const uint8_t *ptr = report_ptr;
+ size_t len = report_len;
+ uint32_t report_size = 0;
+
+ while (len > 0) {
+ const uint8_t tag = ptr[0];
+ ptr++;
+ len--;
+
+ uint8_t key;
+ size_t key_len;
+ uint32_t key_val;
+
+ if (get_key_len(tag, &key, &key_len) < 0 || key_len > len ||
+ get_key_val(ptr, key_len, &key_val) < 0) {
+ return (-1);
+ }
+
+ if (key == 0x94) {
+ report_size = key_val;
+ } else if (key == 0x80) {
+ *report_in_len = (size_t)report_size;
+ } else if (key == 0x90) {
+ *report_out_len = (size_t)report_size;
+ }
+
+ ptr += key_len;
+ len -= key_len;
+ }
+
+ return (0);
+}
+
+fido_dev_info_t *
+fido_dev_info_new(size_t n)
+{
+ return (calloc(n, sizeof(fido_dev_info_t)));
+}
+
+static void
+fido_dev_info_reset(fido_dev_info_t *di)
+{
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ memset(di, 0, sizeof(*di));
+}
+
+void
+fido_dev_info_free(fido_dev_info_t **devlist_p, size_t n)
+{
+ fido_dev_info_t *devlist;
+
+ if (devlist_p == NULL || (devlist = *devlist_p) == NULL)
+ return;
+
+ for (size_t i = 0; i < n; i++)
+ fido_dev_info_reset(&devlist[i]);
+
+ free(devlist);
+
+ *devlist_p = NULL;
+}
+
+const fido_dev_info_t *
+fido_dev_info_ptr(const fido_dev_info_t *devlist, size_t i)
+{
+ return (&devlist[i]);
+}
+
+int
+fido_dev_info_set(fido_dev_info_t *devlist, size_t i,
+ const char *path, const char *manufacturer, const char *product,
+ const fido_dev_io_t *io, const fido_dev_transport_t *transport)
+{
+ char *path_copy = NULL, *manu_copy = NULL, *prod_copy = NULL;
+ int r;
+
+ if (path == NULL || manufacturer == NULL || product == NULL ||
+ io == NULL) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto out;
+ }
+
+ if ((path_copy = strdup(path)) == NULL ||
+ (manu_copy = strdup(manufacturer)) == NULL ||
+ (prod_copy = strdup(product)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ fido_dev_info_reset(&devlist[i]);
+ devlist[i].path = path_copy;
+ devlist[i].manufacturer = manu_copy;
+ devlist[i].product = prod_copy;
+ devlist[i].io = *io;
+ if (transport)
+ devlist[i].transport = *transport;
+ r = FIDO_OK;
+out:
+ if (r != FIDO_OK) {
+ free(prod_copy);
+ free(manu_copy);
+ free(path_copy);
+ }
+ return (r);
+}
+
+const char *
+fido_dev_info_path(const fido_dev_info_t *di)
+{
+ return (di->path);
+}
+
+int16_t
+fido_dev_info_vendor(const fido_dev_info_t *di)
+{
+ return (di->vendor_id);
+}
+
+int16_t
+fido_dev_info_product(const fido_dev_info_t *di)
+{
+ return (di->product_id);
+}
+
+const char *
+fido_dev_info_manufacturer_string(const fido_dev_info_t *di)
+{
+ return (di->manufacturer);
+}
+
+const char *
+fido_dev_info_product_string(const fido_dev_info_t *di)
+{
+ return (di->product);
+}
diff --git a/src/hid_freebsd.c b/src/hid_freebsd.c
new file mode 100644
index 0000000..2bbe80b
--- /dev/null
+++ b/src/hid_freebsd.c
@@ -0,0 +1,337 @@
+/*
+ * 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/param.h>
+
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usbhid.h>
+#if __FreeBSD_version >= 1300500
+#include <dev/hid/hidraw.h>
+#define USE_HIDRAW /* see usbhid(4) and hidraw(4) on FreeBSD 13+ */
+#endif
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "fido.h"
+
+#if defined(__MidnightBSD__)
+#define UHID_VENDOR "MidnightBSD"
+#else
+#define UHID_VENDOR "FreeBSD"
+#endif
+
+#define MAX_UHID 64
+
+struct hid_freebsd {
+ int fd;
+ size_t report_in_len;
+ size_t report_out_len;
+ sigset_t sigmask;
+ const sigset_t *sigmaskp;
+};
+
+static bool
+is_fido(int fd)
+{
+ char buf[64];
+ struct usb_gen_descriptor ugd;
+ uint32_t usage_page = 0;
+
+ memset(&buf, 0, sizeof(buf));
+ memset(&ugd, 0, sizeof(ugd));
+
+ ugd.ugd_report_type = UHID_FEATURE_REPORT;
+ ugd.ugd_data = buf;
+ ugd.ugd_maxlen = sizeof(buf);
+
+ if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) {
+ fido_log_error(errno, "%s: ioctl", __func__);
+ return (false);
+ }
+ if (ugd.ugd_actlen > sizeof(buf) || fido_hid_get_usage(ugd.ugd_data,
+ ugd.ugd_actlen, &usage_page) < 0) {
+ fido_log_debug("%s: fido_hid_get_usage", __func__);
+ return (false);
+ }
+
+ return (usage_page == 0xf1d0);
+}
+
+#ifdef USE_HIDRAW
+static int
+copy_info_hidraw(fido_dev_info_t *di, const char *path)
+{
+ int fd = -1;
+ int ok = -1;
+ struct usb_device_info udi;
+ struct hidraw_devinfo devinfo;
+ char rawname[129];
+
+ memset(di, 0, sizeof(*di));
+ memset(&udi, 0, sizeof(udi));
+ memset(&devinfo, 0, sizeof(devinfo));
+ memset(rawname, 0, sizeof(rawname));
+
+ if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
+ goto fail;
+
+ if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
+ if (ioctl(fd, IOCTL_REQ(HIDIOCGRAWINFO), &devinfo) == -1 ||
+ ioctl(fd, IOCTL_REQ(HIDIOCGRAWNAME(128)), rawname) == -1 ||
+ (di->path = strdup(path)) == NULL ||
+ (di->manufacturer = strdup(UHID_VENDOR)) == NULL ||
+ (di->product = strdup(rawname)) == NULL)
+ goto fail;
+ di->vendor_id = devinfo.vendor;
+ di->product_id = devinfo.product;
+ } else {
+ if ((di->path = strdup(path)) == NULL ||
+ (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
+ (di->product = strdup(udi.udi_product)) == NULL)
+ goto fail;
+ di->vendor_id = (int16_t)udi.udi_vendorNo;
+ di->product_id = (int16_t)udi.udi_productNo;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1 && close(fd) == -1)
+ fido_log_error(errno, "%s: close %s", __func__, path);
+
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return (ok);
+}
+#endif /* USE_HIDRAW */
+
+static int
+copy_info_uhid(fido_dev_info_t *di, const char *path)
+{
+ int fd = -1;
+ int ok = -1;
+ struct usb_device_info udi;
+
+ memset(di, 0, sizeof(*di));
+ memset(&udi, 0, sizeof(udi));
+
+ if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
+ goto fail;
+
+ if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
+ fido_log_error(errno, "%s: ioctl", __func__);
+ strlcpy(udi.udi_vendor, UHID_VENDOR, sizeof(udi.udi_vendor));
+ strlcpy(udi.udi_product, "uhid(4)", sizeof(udi.udi_product));
+ udi.udi_vendorNo = 0x0b5d; /* stolen from PCI_VENDOR_OPENBSD */
+ }
+
+ if ((di->path = strdup(path)) == NULL ||
+ (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
+ (di->product = strdup(udi.udi_product)) == NULL)
+ goto fail;
+ di->vendor_id = (int16_t)udi.udi_vendorNo;
+ di->product_id = (int16_t)udi.udi_productNo;
+
+ ok = 0;
+fail:
+ if (fd != -1 && close(fd) == -1)
+ fido_log_error(errno, "%s: close %s", __func__, path);
+
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return (ok);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ char path[64];
+ size_t i;
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+
+ if (devlist == NULL || olen == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ *olen = 0;
+
+#ifdef USE_HIDRAW
+ for (i = 0; i < MAX_UHID && *olen < ilen; i++) {
+ snprintf(path, sizeof(path), "/dev/hidraw%zu", i);
+ if (copy_info_hidraw(&devlist[*olen], path) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ ++(*olen);
+ }
+ }
+ /* hidraw(4) is preferred over uhid(4) */
+ if (*olen != 0)
+ return (FIDO_OK);
+#endif /* USE_HIDRAW */
+
+ for (i = 0; i < MAX_UHID && *olen < ilen; i++) {
+ snprintf(path, sizeof(path), "/dev/uhid%zu", i);
+ if (copy_info_uhid(&devlist[*olen], path) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ ++(*olen);
+ }
+ }
+
+ return (FIDO_OK);
+}
+
+void *
+fido_hid_open(const char *path)
+{
+ char buf[64];
+ struct hid_freebsd *ctx;
+ struct usb_gen_descriptor ugd;
+ int r;
+
+ memset(&buf, 0, sizeof(buf));
+ memset(&ugd, 0, sizeof(ugd));
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
+ return (NULL);
+
+ if ((ctx->fd = fido_hid_unix_open(path)) == -1) {
+ free(ctx);
+ return (NULL);
+ }
+
+ ugd.ugd_report_type = UHID_FEATURE_REPORT;
+ ugd.ugd_data = buf;
+ ugd.ugd_maxlen = sizeof(buf);
+
+ /*
+ * N.B. if ctx->fd is an hidraw(4) device, the ioctl() below puts it in
+ * uhid(4) compat mode, which we need to keep fido_hid_write() as-is.
+ */
+ if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) ||
+ ugd.ugd_actlen > sizeof(buf) ||
+ fido_hid_get_report_len(ugd.ugd_data, ugd.ugd_actlen,
+ &ctx->report_in_len, &ctx->report_out_len) < 0) {
+ if (r == -1)
+ fido_log_error(errno, "%s: ioctl", __func__);
+ fido_log_debug("%s: using default report sizes", __func__);
+ ctx->report_in_len = CTAP_MAX_REPORT_LEN;
+ ctx->report_out_len = CTAP_MAX_REPORT_LEN;
+ }
+
+ return (ctx);
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_freebsd *ctx = handle;
+
+ if (close(ctx->fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ struct hid_freebsd *ctx = handle;
+
+ ctx->sigmask = *sigmask;
+ ctx->sigmaskp = &ctx->sigmask;
+
+ return (FIDO_OK);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_freebsd *ctx = handle;
+ ssize_t r;
+
+ if (len != ctx->report_in_len) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
+ fido_log_debug("%s: fd not ready", __func__);
+ return (-1);
+ }
+
+ if ((r = read(ctx->fd, buf, len)) == -1) {
+ fido_log_error(errno, "%s: read", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len);
+ return (-1);
+ }
+
+ return ((int)r);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_freebsd *ctx = handle;
+ ssize_t r;
+
+ if (len != ctx->report_out_len + 1) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len - 1) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len - 1);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_freebsd *ctx = handle;
+
+ return (ctx->report_in_len);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_freebsd *ctx = handle;
+
+ return (ctx->report_out_len);
+}
diff --git a/src/hid_hidapi.c b/src/hid_hidapi.c
new file mode 100644
index 0000000..fed6f69
--- /dev/null
+++ b/src/hid_hidapi.c
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2019 Google LLC. 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
+ */
+
+#ifdef __linux__
+#include <sys/ioctl.h>
+#include <linux/hidraw.h>
+#include <linux/input.h>
+#include <fcntl.h>
+#endif
+
+#include <errno.h>
+#include <hidapi.h>
+#include <wchar.h>
+
+#include "fido.h"
+
+struct hid_hidapi {
+ void *handle;
+ size_t report_in_len;
+ size_t report_out_len;
+};
+
+static size_t
+fido_wcslen(const wchar_t *wcs)
+{
+ size_t l = 0;
+ while (*wcs++ != L'\0')
+ l++;
+ return l;
+}
+
+static char *
+wcs_to_cs(const wchar_t *wcs)
+{
+ char *cs;
+ size_t i;
+
+ if (wcs == NULL || (cs = calloc(fido_wcslen(wcs) + 1, 1)) == NULL)
+ return NULL;
+
+ for (i = 0; i < fido_wcslen(wcs); i++) {
+ if (wcs[i] >= 128) {
+ /* give up on parsing non-ASCII text */
+ free(cs);
+ return strdup("hidapi device");
+ }
+ cs[i] = (char)wcs[i];
+ }
+
+ return cs;
+}
+
+static int
+copy_info(fido_dev_info_t *di, const struct hid_device_info *d)
+{
+ memset(di, 0, sizeof(*di));
+
+ if (d->path != NULL)
+ di->path = strdup(d->path);
+ else
+ di->path = strdup("");
+
+ if (d->manufacturer_string != NULL)
+ di->manufacturer = wcs_to_cs(d->manufacturer_string);
+ else
+ di->manufacturer = strdup("");
+
+ if (d->product_string != NULL)
+ di->product = wcs_to_cs(d->product_string);
+ else
+ di->product = strdup("");
+
+ if (di->path == NULL ||
+ di->manufacturer == NULL ||
+ di->product == NULL) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ return -1;
+ }
+
+ di->product_id = (int16_t)d->product_id;
+ di->vendor_id = (int16_t)d->vendor_id;
+ di->io = (fido_dev_io_t) {
+ &fido_hid_open,
+ &fido_hid_close,
+ &fido_hid_read,
+ &fido_hid_write,
+ };
+
+ return 0;
+}
+
+#ifdef __linux__
+static int
+get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd)
+{
+ int fd;
+ int s = -1;
+ int ok = -1;
+
+ if ((fd = fido_hid_unix_open(path)) == -1) {
+ fido_log_debug("%s: fido_hid_unix_open", __func__);
+ return -1;
+ }
+
+ if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) < 0 || s < 0 ||
+ (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
+ fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
+ goto fail;
+ }
+
+ hrd->size = (unsigned)s;
+
+ if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) < 0) {
+ fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1)
+ close(fd);
+
+ return ok;
+}
+
+static bool
+is_fido(const struct hid_device_info *hdi)
+{
+ uint32_t usage_page = 0;
+ struct hidraw_report_descriptor *hrd;
+
+ if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
+ get_report_descriptor(hdi->path, hrd) < 0 ||
+ fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
+ usage_page = 0;
+
+ free(hrd);
+
+ return usage_page == 0xf1d0;
+}
+#elif defined(_WIN32) || defined(__APPLE__)
+static bool
+is_fido(const struct hid_device_info *hdi)
+{
+ return hdi->usage_page == 0xf1d0;
+}
+#else
+static bool
+is_fido(const struct hid_device_info *hdi)
+{
+ (void)hdi;
+ fido_log_debug("%s: assuming FIDO HID", __func__);
+ return true;
+}
+#endif
+
+void *
+fido_hid_open(const char *path)
+{
+ struct hid_hidapi *ctx;
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
+ return (NULL);
+ }
+
+ if ((ctx->handle = hid_open_path(path)) == NULL) {
+ free(ctx);
+ return (NULL);
+ }
+
+ ctx->report_in_len = ctx->report_out_len = CTAP_MAX_REPORT_LEN;
+
+ return ctx;
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_hidapi *ctx = handle;
+
+ hid_close(ctx->handle);
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ (void)handle;
+ (void)sigmask;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_hidapi *ctx = handle;
+
+ if (len != ctx->report_in_len) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return -1;
+ }
+
+ return hid_read_timeout(ctx->handle, buf, len, ms);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_hidapi *ctx = handle;
+
+ if (len != ctx->report_out_len + 1) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return -1;
+ }
+
+ return hid_write(ctx->handle, buf, len);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ struct hid_device_info *hdi;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return FIDO_OK; /* nothing to do */
+ if (devlist == NULL)
+ return FIDO_ERR_INVALID_ARGUMENT;
+ if ((hdi = hid_enumerate(0, 0)) == NULL)
+ return FIDO_OK; /* nothing to do */
+
+ for (struct hid_device_info *d = hdi; d != NULL; d = d->next) {
+ if (is_fido(d) == false)
+ continue;
+ if (copy_info(&devlist[*olen], d) == 0) {
+ if (++(*olen) == ilen)
+ break;
+ }
+ }
+
+ hid_free_enumeration(hdi);
+
+ return FIDO_OK;
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_hidapi *ctx = handle;
+
+ return (ctx->report_in_len);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_hidapi *ctx = handle;
+
+ return (ctx->report_out_len);
+}
diff --git a/src/hid_linux.c b/src/hid_linux.c
new file mode 100644
index 0000000..841a95b
--- /dev/null
+++ b/src/hid_linux.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2019-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/file.h>
+#include <sys/ioctl.h>
+
+#include <linux/hidraw.h>
+#include <linux/input.h>
+
+#include <errno.h>
+#include <libudev.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "fido.h"
+
+struct hid_linux {
+ int fd;
+ size_t report_in_len;
+ size_t report_out_len;
+ sigset_t sigmask;
+ const sigset_t *sigmaskp;
+};
+
+static int
+get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd)
+{
+ int s = -1;
+
+ if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) {
+ fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__);
+ return (-1);
+ }
+
+ if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) {
+ fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s);
+ return (-1);
+ }
+
+ hrd->size = (unsigned)s;
+
+ if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) {
+ fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static bool
+is_fido(const char *path)
+{
+ int fd = -1;
+ uint32_t usage_page = 0;
+ struct hidraw_report_descriptor *hrd = NULL;
+
+ if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
+ (fd = fido_hid_unix_open(path)) == -1)
+ goto out;
+ if (get_report_descriptor(fd, hrd) < 0 ||
+ fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0)
+ usage_page = 0;
+
+out:
+ free(hrd);
+
+ if (fd != -1 && close(fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+
+ return (usage_page == 0xf1d0);
+}
+
+static int
+parse_uevent(const char *uevent, int *bus, int16_t *vendor_id,
+ int16_t *product_id)
+{
+ char *cp;
+ char *p;
+ char *s;
+ int ok = -1;
+ short unsigned int x;
+ short unsigned int y;
+ short unsigned int z;
+
+ if ((s = cp = strdup(uevent)) == NULL)
+ return (-1);
+
+ while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') {
+ if (strncmp(p, "HID_ID=", 7) == 0) {
+ if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) {
+ *bus = (int)x;
+ *vendor_id = (int16_t)y;
+ *product_id = (int16_t)z;
+ ok = 0;
+ break;
+ }
+ }
+ }
+
+ free(s);
+
+ return (ok);
+}
+
+static char *
+get_parent_attr(struct udev_device *dev, const char *subsystem,
+ const char *devtype, const char *attr)
+{
+ struct udev_device *parent;
+ const char *value;
+
+ if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
+ subsystem, devtype)) == NULL || (value =
+ udev_device_get_sysattr_value(parent, attr)) == NULL)
+ return (NULL);
+
+ return (strdup(value));
+}
+
+static char *
+get_usb_attr(struct udev_device *dev, const char *attr)
+{
+ return (get_parent_attr(dev, "usb", "usb_device", attr));
+}
+
+static int
+copy_info(fido_dev_info_t *di, struct udev *udev,
+ struct udev_list_entry *udev_entry)
+{
+ const char *name;
+ const char *path;
+ char *uevent = NULL;
+ struct udev_device *dev = NULL;
+ int bus = 0;
+ int ok = -1;
+
+ memset(di, 0, sizeof(*di));
+
+ if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
+ (dev = udev_device_new_from_syspath(udev, name)) == NULL ||
+ (path = udev_device_get_devnode(dev)) == NULL ||
+ is_fido(path) == 0)
+ goto fail;
+
+ if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL ||
+ parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) {
+ fido_log_debug("%s: uevent", __func__);
+ goto fail;
+ }
+
+#ifndef FIDO_HID_ANY
+ if (bus != BUS_USB) {
+ fido_log_debug("%s: bus", __func__);
+ goto fail;
+ }
+#endif
+
+ di->path = strdup(path);
+ if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL)
+ di->manufacturer = strdup("");
+ if ((di->product = get_usb_attr(dev, "product")) == NULL)
+ di->product = strdup("");
+ if (di->path == NULL || di->manufacturer == NULL || di->product == NULL)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (dev != NULL)
+ udev_device_unref(dev);
+
+ free(uevent);
+
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return (ok);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ struct udev *udev = NULL;
+ struct udev_enumerate *udev_enum = NULL;
+ struct udev_list_entry *udev_list;
+ struct udev_list_entry *udev_entry;
+ int r = FIDO_ERR_INTERNAL;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+
+ if (devlist == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((udev = udev_new()) == NULL ||
+ (udev_enum = udev_enumerate_new(udev)) == NULL)
+ goto fail;
+
+ if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 ||
+ udev_enumerate_scan_devices(udev_enum) < 0)
+ goto fail;
+
+ if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
+ r = FIDO_OK; /* zero hidraw devices */
+ goto fail;
+ }
+
+ udev_list_entry_foreach(udev_entry, udev_list) {
+ if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ if (++(*olen) == ilen)
+ break;
+ }
+ }
+
+ r = FIDO_OK;
+fail:
+ if (udev_enum != NULL)
+ udev_enumerate_unref(udev_enum);
+ if (udev != NULL)
+ udev_unref(udev);
+
+ return (r);
+}
+
+void *
+fido_hid_open(const char *path)
+{
+ struct hid_linux *ctx;
+ struct hidraw_report_descriptor *hrd;
+ struct timespec tv_pause;
+ long interval_ms, retries = 0;
+ bool looped;
+
+retry:
+ looped = false;
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
+ (ctx->fd = fido_hid_unix_open(path)) == -1) {
+ free(ctx);
+ return (NULL);
+ }
+
+ while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) {
+ if (errno != EWOULDBLOCK) {
+ fido_log_error(errno, "%s: flock", __func__);
+ fido_hid_close(ctx);
+ return (NULL);
+ }
+ looped = true;
+ if (retries++ >= 20) {
+ fido_log_debug("%s: flock timeout", __func__);
+ fido_hid_close(ctx);
+ return (NULL);
+ }
+ interval_ms = retries * 100000000L;
+ tv_pause.tv_sec = interval_ms / 1000000000L;
+ tv_pause.tv_nsec = interval_ms % 1000000000L;
+ if (nanosleep(&tv_pause, NULL) == -1) {
+ fido_log_error(errno, "%s: nanosleep", __func__);
+ fido_hid_close(ctx);
+ return (NULL);
+ }
+ }
+
+ if (looped) {
+ fido_log_debug("%s: retrying", __func__);
+ fido_hid_close(ctx);
+ goto retry;
+ }
+
+ if ((hrd = calloc(1, sizeof(*hrd))) == NULL ||
+ get_report_descriptor(ctx->fd, hrd) < 0 ||
+ fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len,
+ &ctx->report_out_len) < 0 || ctx->report_in_len == 0 ||
+ ctx->report_out_len == 0) {
+ fido_log_debug("%s: using default report sizes", __func__);
+ ctx->report_in_len = CTAP_MAX_REPORT_LEN;
+ ctx->report_out_len = CTAP_MAX_REPORT_LEN;
+ }
+
+ free(hrd);
+
+ return (ctx);
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_linux *ctx = handle;
+
+ if (close(ctx->fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ struct hid_linux *ctx = handle;
+
+ ctx->sigmask = *sigmask;
+ ctx->sigmaskp = &ctx->sigmask;
+
+ return (FIDO_OK);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_linux *ctx = handle;
+ ssize_t r;
+
+ if (len != ctx->report_in_len) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
+ fido_log_debug("%s: fd not ready", __func__);
+ return (-1);
+ }
+
+ if ((r = read(ctx->fd, buf, len)) == -1) {
+ fido_log_error(errno, "%s: read", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len);
+ return (-1);
+ }
+
+ return ((int)r);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_linux *ctx = handle;
+ ssize_t r;
+
+ if (len != ctx->report_out_len + 1) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if ((r = write(ctx->fd, buf, len)) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len);
+ return (-1);
+ }
+
+ return ((int)r);
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_linux *ctx = handle;
+
+ return (ctx->report_in_len);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_linux *ctx = handle;
+
+ return (ctx->report_out_len);
+}
diff --git a/src/hid_netbsd.c b/src/hid_netbsd.c
new file mode 100644
index 0000000..d5b9fad
--- /dev/null
+++ b/src/hid_netbsd.c
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2020 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+
+#include <errno.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fido.h"
+
+#define MAX_UHID 64
+
+struct hid_netbsd {
+ int fd;
+ size_t report_in_len;
+ size_t report_out_len;
+ sigset_t sigmask;
+ const sigset_t *sigmaskp;
+};
+
+/* Hack to make this work with newer kernels even if /usr/include is old. */
+#if __NetBSD_Version__ < 901000000 /* 9.1 */
+#define USB_HID_GET_RAW _IOR('h', 1, int)
+#define USB_HID_SET_RAW _IOW('h', 2, int)
+#endif
+
+static bool
+is_fido(int fd)
+{
+ struct usb_ctl_report_desc ucrd;
+ uint32_t usage_page = 0;
+ int raw = 1;
+
+ memset(&ucrd, 0, sizeof(ucrd));
+
+ if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) {
+ fido_log_error(errno, "%s: ioctl", __func__);
+ return (false);
+ }
+
+ if (ucrd.ucrd_size < 0 ||
+ (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
+ fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
+ &usage_page) < 0) {
+ fido_log_debug("%s: fido_hid_get_usage", __func__);
+ return (false);
+ }
+
+ if (usage_page != 0xf1d0)
+ return (false);
+
+ /*
+ * This step is not strictly necessary -- NetBSD puts fido
+ * devices into raw mode automatically by default, but in
+ * principle that might change, and this serves as a test to
+ * verify that we're running on a kernel with support for raw
+ * mode at all so we don't get confused issuing writes that try
+ * to set the report descriptor rather than transfer data on
+ * the output interrupt pipe as we need.
+ */
+ if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) {
+ fido_log_error(errno, "%s: unable to set raw", __func__);
+ return (false);
+ }
+
+ return (true);
+}
+
+static int
+copy_info(fido_dev_info_t *di, const char *path)
+{
+ int fd = -1;
+ int ok = -1;
+ struct usb_device_info udi;
+
+ memset(di, 0, sizeof(*di));
+ memset(&udi, 0, sizeof(udi));
+
+ if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0)
+ goto fail;
+
+ if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
+ fido_log_error(errno, "%s: ioctl", __func__);
+ goto fail;
+ }
+
+ if ((di->path = strdup(path)) == NULL ||
+ (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
+ (di->product = strdup(udi.udi_product)) == NULL)
+ goto fail;
+
+ di->vendor_id = (int16_t)udi.udi_vendorNo;
+ di->product_id = (int16_t)udi.udi_productNo;
+
+ ok = 0;
+fail:
+ if (fd != -1 && close(fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return (ok);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ char path[64];
+ size_t i;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+
+ if (devlist == NULL || olen == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
+ snprintf(path, sizeof(path), "/dev/uhid%zu", i);
+ if (copy_info(&devlist[*olen], path) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ ++(*olen);
+ }
+ }
+
+ return (FIDO_OK);
+}
+
+/*
+ * Workaround for NetBSD (as of 201910) bug that loses
+ * sync of DATA0/DATA1 sequence bit across uhid open/close.
+ * Send pings until we get a response - early pings with incorrect
+ * sequence bits will be ignored as duplicate packets by the device.
+ */
+static int
+terrible_ping_kludge(struct hid_netbsd *ctx)
+{
+ u_char data[256];
+ int i, n;
+ struct pollfd pfd;
+
+ if (sizeof(data) < ctx->report_out_len + 1)
+ return -1;
+ for (i = 0; i < 4; i++) {
+ memset(data, 0, sizeof(data));
+ /* broadcast channel ID */
+ data[1] = 0xff;
+ data[2] = 0xff;
+ data[3] = 0xff;
+ data[4] = 0xff;
+ /* Ping command */
+ data[5] = 0x81;
+ /* One byte ping only, Vasili */
+ data[6] = 0;
+ data[7] = 1;
+ fido_log_debug("%s: send ping %d", __func__, i);
+ if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)
+ return -1;
+ fido_log_debug("%s: wait reply", __func__);
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = ctx->fd;
+ pfd.events = POLLIN;
+ if ((n = poll(&pfd, 1, 100)) == -1) {
+ fido_log_error(errno, "%s: poll", __func__);
+ return -1;
+ } else if (n == 0) {
+ fido_log_debug("%s: timed out", __func__);
+ continue;
+ }
+ if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)
+ return -1;
+ /*
+ * Ping isn't always supported on the broadcast channel,
+ * so we might get an error, but we don't care - we're
+ * synched now.
+ */
+ fido_log_xxd(data, ctx->report_out_len, "%s: got reply",
+ __func__);
+ return 0;
+ }
+ fido_log_debug("%s: no response", __func__);
+ return -1;
+}
+
+void *
+fido_hid_open(const char *path)
+{
+ struct hid_netbsd *ctx;
+ struct usb_ctl_report_desc ucrd;
+ int r;
+
+ memset(&ucrd, 0, sizeof(ucrd));
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
+ (ctx->fd = fido_hid_unix_open(path)) == -1) {
+ free(ctx);
+ return (NULL);
+ }
+
+ if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 ||
+ ucrd.ucrd_size < 0 ||
+ (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) ||
+ fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size,
+ &ctx->report_in_len, &ctx->report_out_len) < 0) {
+ if (r == -1)
+ fido_log_error(errno, "%s: ioctl", __func__);
+ fido_log_debug("%s: using default report sizes", __func__);
+ ctx->report_in_len = CTAP_MAX_REPORT_LEN;
+ ctx->report_out_len = CTAP_MAX_REPORT_LEN;
+ }
+
+ /*
+ * NetBSD has a bug that causes it to lose
+ * track of the DATA0/DATA1 sequence toggle across uhid device
+ * open and close. This is a terrible hack to work around it.
+ */
+ if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) {
+ fido_hid_close(ctx);
+ return NULL;
+ }
+
+ return (ctx);
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_netbsd *ctx = handle;
+
+ if (close(ctx->fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ struct hid_netbsd *ctx = handle;
+
+ ctx->sigmask = *sigmask;
+ ctx->sigmaskp = &ctx->sigmask;
+
+ return (FIDO_OK);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_netbsd *ctx = handle;
+ ssize_t r;
+
+ if (len != ctx->report_in_len) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
+ fido_log_debug("%s: fd not ready", __func__);
+ return (-1);
+ }
+
+ if ((r = read(ctx->fd, buf, len)) == -1) {
+ fido_log_error(errno, "%s: read", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len) {
+ fido_log_error(errno, "%s: %zd != %zu", __func__, r, len);
+ return (-1);
+ }
+
+ return ((int)r);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_netbsd *ctx = handle;
+ ssize_t r;
+
+ if (len != ctx->report_out_len + 1) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len - 1) {
+ fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_netbsd *ctx = handle;
+
+ return (ctx->report_in_len);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_netbsd *ctx = handle;
+
+ return (ctx->report_out_len);
+}
diff --git a/src/hid_openbsd.c b/src/hid_openbsd.c
new file mode 100644
index 0000000..2d08aca
--- /dev/null
+++ b/src/hid_openbsd.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2019 Google LLC. 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/ioctl.h>
+#include <dev/usb/usb.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "fido.h"
+
+#define MAX_UHID 64
+
+struct hid_openbsd {
+ int fd;
+ size_t report_in_len;
+ size_t report_out_len;
+ sigset_t sigmask;
+ const sigset_t *sigmaskp;
+};
+
+static int
+copy_info(fido_dev_info_t *di, const char *path)
+{
+ int fd = -1, ok = -1;
+ struct usb_device_info udi;
+
+ memset(di, 0, sizeof(*di));
+ memset(&udi, 0, sizeof(udi));
+
+ if ((fd = fido_hid_unix_open(path)) == -1)
+ goto fail;
+ if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) {
+ fido_log_error(errno, "%s: ioctl %s", __func__, path);
+ goto fail;
+ }
+
+ fido_log_debug("%s: %s: bus = 0x%02x, addr = 0x%02x", __func__, path,
+ udi.udi_bus, udi.udi_addr);
+ fido_log_debug("%s: %s: vendor = \"%s\", product = \"%s\"", __func__,
+ path, udi.udi_vendor, udi.udi_product);
+ fido_log_debug("%s: %s: productNo = 0x%04x, vendorNo = 0x%04x, "
+ "releaseNo = 0x%04x", __func__, path, udi.udi_productNo,
+ udi.udi_vendorNo, udi.udi_releaseNo);
+
+ if ((di->path = strdup(path)) == NULL ||
+ (di->manufacturer = strdup(udi.udi_vendor)) == NULL ||
+ (di->product = strdup(udi.udi_product)) == NULL)
+ goto fail;
+
+ di->vendor_id = (int16_t)udi.udi_vendorNo;
+ di->product_id = (int16_t)udi.udi_productNo;
+
+ ok = 0;
+fail:
+ if (fd != -1 && close(fd) == -1)
+ fido_log_error(errno, "%s: close %s", __func__, path);
+
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return (ok);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ size_t i;
+ char path[64];
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+
+ if (devlist == NULL || olen == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) {
+ snprintf(path, sizeof(path), "/dev/fido/%zu", i);
+ if (copy_info(&devlist[*olen], path) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ ++(*olen);
+ }
+ }
+
+ return (FIDO_OK);
+}
+
+/*
+ * Workaround for OpenBSD <=6.6-current (as of 201910) bug that loses
+ * sync of DATA0/DATA1 sequence bit across uhid open/close.
+ * Send pings until we get a response - early pings with incorrect
+ * sequence bits will be ignored as duplicate packets by the device.
+ */
+static int
+terrible_ping_kludge(struct hid_openbsd *ctx)
+{
+ u_char data[256];
+ int i, n;
+ struct pollfd pfd;
+
+ if (sizeof(data) < ctx->report_out_len + 1)
+ return -1;
+ for (i = 0; i < 4; i++) {
+ memset(data, 0, sizeof(data));
+ /* broadcast channel ID */
+ data[1] = 0xff;
+ data[2] = 0xff;
+ data[3] = 0xff;
+ data[4] = 0xff;
+ /* Ping command */
+ data[5] = 0x81;
+ /* One byte ping only, Vasili */
+ data[6] = 0;
+ data[7] = 1;
+ fido_log_debug("%s: send ping %d", __func__, i);
+ if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1)
+ return -1;
+ fido_log_debug("%s: wait reply", __func__);
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = ctx->fd;
+ pfd.events = POLLIN;
+ if ((n = poll(&pfd, 1, 100)) == -1) {
+ fido_log_error(errno, "%s: poll", __func__);
+ return -1;
+ } else if (n == 0) {
+ fido_log_debug("%s: timed out", __func__);
+ continue;
+ }
+ if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1)
+ return -1;
+ /*
+ * Ping isn't always supported on the broadcast channel,
+ * so we might get an error, but we don't care - we're
+ * synched now.
+ */
+ fido_log_xxd(data, ctx->report_out_len, "%s: got reply",
+ __func__);
+ return 0;
+ }
+ fido_log_debug("%s: no response", __func__);
+ return -1;
+}
+
+void *
+fido_hid_open(const char *path)
+{
+ struct hid_openbsd *ret = NULL;
+
+ if ((ret = calloc(1, sizeof(*ret))) == NULL ||
+ (ret->fd = fido_hid_unix_open(path)) == -1) {
+ free(ret);
+ return (NULL);
+ }
+ ret->report_in_len = ret->report_out_len = CTAP_MAX_REPORT_LEN;
+ fido_log_debug("%s: inlen = %zu outlen = %zu", __func__,
+ ret->report_in_len, ret->report_out_len);
+
+ /*
+ * OpenBSD (as of 201910) has a bug that causes it to lose
+ * track of the DATA0/DATA1 sequence toggle across uhid device
+ * open and close. This is a terrible hack to work around it.
+ */
+ if (terrible_ping_kludge(ret) != 0) {
+ fido_hid_close(ret);
+ return NULL;
+ }
+
+ return (ret);
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
+
+ if (close(ctx->fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ struct hid_openbsd *ctx = handle;
+
+ ctx->sigmask = *sigmask;
+ ctx->sigmaskp = &ctx->sigmask;
+
+ return (FIDO_OK);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
+ ssize_t r;
+
+ if (len != ctx->report_in_len) {
+ fido_log_debug("%s: invalid len: got %zu, want %zu", __func__,
+ len, ctx->report_in_len);
+ return (-1);
+ }
+
+ if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
+ fido_log_debug("%s: fd not ready", __func__);
+ return (-1);
+ }
+
+ if ((r = read(ctx->fd, buf, len)) == -1) {
+ fido_log_error(errno, "%s: read", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_openbsd *ctx = (struct hid_openbsd *)handle;
+ ssize_t r;
+
+ if (len != ctx->report_out_len + 1) {
+ fido_log_debug("%s: invalid len: got %zu, want %zu", __func__,
+ len, ctx->report_out_len);
+ return (-1);
+ }
+
+ if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len - 1) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len - 1);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_openbsd *ctx = handle;
+
+ return (ctx->report_in_len);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_openbsd *ctx = handle;
+
+ return (ctx->report_out_len);
+}
diff --git a/src/hid_osx.c b/src/hid_osx.c
new file mode 100644
index 0000000..9309762
--- /dev/null
+++ b/src/hid_osx.c
@@ -0,0 +1,597 @@
+/*
+ * Copyright (c) 2019-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 <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <Availability.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+#include <IOKit/hid/IOHIDManager.h>
+
+#include "fido.h"
+
+#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000
+#define kIOMainPortDefault kIOMasterPortDefault
+#endif
+
+#define IOREG "ioreg://"
+
+struct hid_osx {
+ IOHIDDeviceRef ref;
+ CFStringRef loop_id;
+ int report_pipe[2];
+ size_t report_in_len;
+ size_t report_out_len;
+ unsigned char report[CTAP_MAX_REPORT_LEN];
+};
+
+static int
+get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v)
+{
+ CFTypeRef ref;
+
+ if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
+ CFGetTypeID(ref) != CFNumberGetTypeID()) {
+ fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
+ return (-1);
+ }
+
+ if (CFNumberGetType(ref) != kCFNumberSInt32Type &&
+ CFNumberGetType(ref) != kCFNumberSInt64Type) {
+ fido_log_debug("%s: CFNumberGetType", __func__);
+ return (-1);
+ }
+
+ if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) {
+ fido_log_debug("%s: CFNumberGetValue", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len)
+{
+ CFTypeRef ref;
+
+ memset(buf, 0, len);
+
+ if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL ||
+ CFGetTypeID(ref) != CFStringGetTypeID()) {
+ fido_log_debug("%s: IOHIDDeviceGetProperty", __func__);
+ return (-1);
+ }
+
+ if (CFStringGetCString(ref, buf, (long)len,
+ kCFStringEncodingUTF8) == false) {
+ fido_log_debug("%s: CFStringGetCString", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len)
+{
+ CFStringRef key;
+ int32_t v;
+
+ if (dir == 0)
+ key = CFSTR(kIOHIDMaxInputReportSizeKey);
+ else
+ key = CFSTR(kIOHIDMaxOutputReportSizeKey);
+
+ if (get_int32(dev, key, &v) < 0) {
+ fido_log_debug("%s: get_int32/%d", __func__, dir);
+ return (-1);
+ }
+
+ if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) {
+ fido_log_debug("%s: report_len=%zu", __func__, *report_len);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id)
+{
+ int32_t vendor;
+ int32_t product;
+
+ if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 ||
+ vendor > UINT16_MAX) {
+ fido_log_debug("%s: get_int32 vendor", __func__);
+ return (-1);
+ }
+
+ if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 ||
+ product > UINT16_MAX) {
+ fido_log_debug("%s: get_int32 product", __func__);
+ return (-1);
+ }
+
+ *vendor_id = (int16_t)vendor;
+ *product_id = (int16_t)product;
+
+ return (0);
+}
+
+static int
+get_str(IOHIDDeviceRef dev, char **manufacturer, char **product)
+{
+ char buf[512];
+ int ok = -1;
+
+ *manufacturer = NULL;
+ *product = NULL;
+
+ if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0)
+ *manufacturer = strdup("");
+ else
+ *manufacturer = strdup(buf);
+
+ if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0)
+ *product = strdup("");
+ else
+ *product = strdup(buf);
+
+ if (*manufacturer == NULL || *product == NULL) {
+ fido_log_debug("%s: strdup", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(*manufacturer);
+ free(*product);
+ *manufacturer = NULL;
+ *product = NULL;
+ }
+
+ return (ok);
+}
+
+static char *
+get_path(IOHIDDeviceRef dev)
+{
+ io_service_t s;
+ uint64_t id;
+ char *path;
+
+ if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) {
+ fido_log_debug("%s: IOHIDDeviceGetService", __func__);
+ return (NULL);
+ }
+
+ if (IORegistryEntryGetRegistryEntryID(s, &id) != KERN_SUCCESS) {
+ fido_log_debug("%s: IORegistryEntryGetRegistryEntryID",
+ __func__);
+ return (NULL);
+ }
+
+ if (asprintf(&path, "%s%llu", IOREG, (unsigned long long)id) == -1) {
+ fido_log_error(errno, "%s: asprintf", __func__);
+ return (NULL);
+ }
+
+ return (path);
+}
+
+static bool
+is_fido(IOHIDDeviceRef dev)
+{
+ char buf[32];
+ uint32_t usage_page;
+
+ if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey),
+ (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0)
+ return (false);
+
+ if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) {
+ fido_log_debug("%s: get_utf8 transport", __func__);
+ return (false);
+ }
+
+#ifndef FIDO_HID_ANY
+ if (strcasecmp(buf, "usb") != 0) {
+ fido_log_debug("%s: transport", __func__);
+ return (false);
+ }
+#endif
+
+ return (true);
+}
+
+static int
+copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev)
+{
+ memset(di, 0, sizeof(*di));
+
+ if (is_fido(dev) == false)
+ return (-1);
+
+ if (get_id(dev, &di->vendor_id, &di->product_id) < 0 ||
+ get_str(dev, &di->manufacturer, &di->product) < 0 ||
+ (di->path = get_path(dev)) == NULL) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ IOHIDManagerRef manager = NULL;
+ CFSetRef devset = NULL;
+ size_t devcnt;
+ CFIndex n;
+ IOHIDDeviceRef *devs = NULL;
+ int r = FIDO_ERR_INTERNAL;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+
+ if (devlist == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((manager = IOHIDManagerCreate(kCFAllocatorDefault,
+ kIOHIDManagerOptionNone)) == NULL) {
+ fido_log_debug("%s: IOHIDManagerCreate", __func__);
+ goto fail;
+ }
+
+ IOHIDManagerSetDeviceMatching(manager, NULL);
+
+ if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) {
+ fido_log_debug("%s: IOHIDManagerCopyDevices", __func__);
+ goto fail;
+ }
+
+ if ((n = CFSetGetCount(devset)) < 0) {
+ fido_log_debug("%s: CFSetGetCount", __func__);
+ goto fail;
+ }
+
+ devcnt = (size_t)n;
+
+ if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+
+ CFSetGetValues(devset, (void *)devs);
+
+ for (size_t i = 0; i < devcnt; i++) {
+ if (copy_info(&devlist[*olen], devs[i]) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ if (++(*olen) == ilen)
+ break;
+ }
+ }
+
+ r = FIDO_OK;
+fail:
+ if (manager != NULL)
+ CFRelease(manager);
+ if (devset != NULL)
+ CFRelease(devset);
+
+ free(devs);
+
+ return (r);
+}
+
+static void
+report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type,
+ uint32_t id, uint8_t *ptr, CFIndex len)
+{
+ struct hid_osx *ctx = context;
+ ssize_t r;
+
+ (void)dev;
+
+ if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput ||
+ id != 0 || len < 0 || (size_t)len != ctx->report_in_len) {
+ fido_log_debug("%s: io error", __func__);
+ return;
+ }
+
+ if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return;
+ }
+
+ if (r < 0 || (size_t)r != (size_t)len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, (size_t)len);
+ return;
+ }
+}
+
+static void
+removal_callback(void *context, IOReturn result, void *sender)
+{
+ (void)context;
+ (void)result;
+ (void)sender;
+
+ CFRunLoopStop(CFRunLoopGetCurrent());
+}
+
+static int
+set_nonblock(int fd)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL)) == -1) {
+ fido_log_error(errno, "%s: fcntl F_GETFL", __func__);
+ return (-1);
+ }
+
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
+ fido_log_error(errno, "%s: fcntl F_SETFL", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+disable_sigpipe(int fd)
+{
+ int disabled = 1;
+
+ if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) {
+ fido_log_error(errno, "%s: fcntl F_SETNOSIGPIPE", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static io_registry_entry_t
+get_ioreg_entry(const char *path)
+{
+ uint64_t id;
+
+ if (strncmp(path, IOREG, strlen(IOREG)) != 0)
+ return (IORegistryEntryFromPath(kIOMainPortDefault, path));
+
+ if (fido_to_uint64(path + strlen(IOREG), 10, &id) == -1) {
+ fido_log_debug("%s: fido_to_uint64", __func__);
+ return (MACH_PORT_NULL);
+ }
+
+ return (IOServiceGetMatchingService(kIOMainPortDefault,
+ IORegistryEntryIDMatching(id)));
+}
+
+void *
+fido_hid_open(const char *path)
+{
+ struct hid_osx *ctx;
+ io_registry_entry_t entry = MACH_PORT_NULL;
+ char loop_id[32];
+ int ok = -1;
+ int r;
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+
+ ctx->report_pipe[0] = -1;
+ ctx->report_pipe[1] = -1;
+
+ if (pipe(ctx->report_pipe) == -1) {
+ fido_log_error(errno, "%s: pipe", __func__);
+ goto fail;
+ }
+
+ if (set_nonblock(ctx->report_pipe[0]) < 0 ||
+ set_nonblock(ctx->report_pipe[1]) < 0) {
+ fido_log_debug("%s: set_nonblock", __func__);
+ goto fail;
+ }
+
+ if (disable_sigpipe(ctx->report_pipe[1]) < 0) {
+ fido_log_debug("%s: disable_sigpipe", __func__);
+ goto fail;
+ }
+
+ if ((entry = get_ioreg_entry(path)) == MACH_PORT_NULL) {
+ fido_log_debug("%s: get_ioreg_entry: %s", __func__, path);
+ goto fail;
+ }
+
+ if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault,
+ entry)) == NULL) {
+ fido_log_debug("%s: IOHIDDeviceCreate", __func__);
+ goto fail;
+ }
+
+ if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 ||
+ get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) {
+ fido_log_debug("%s: get_report_len", __func__);
+ goto fail;
+ }
+
+ if (ctx->report_in_len > sizeof(ctx->report)) {
+ fido_log_debug("%s: report_in_len=%zu", __func__,
+ ctx->report_in_len);
+ goto fail;
+ }
+
+ if (IOHIDDeviceOpen(ctx->ref,
+ kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) {
+ fido_log_debug("%s: IOHIDDeviceOpen", __func__);
+ goto fail;
+ }
+
+ if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p",
+ (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) {
+ fido_log_debug("%s: snprintf", __func__);
+ goto fail;
+ }
+
+ if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id,
+ kCFStringEncodingASCII)) == NULL) {
+ fido_log_debug("%s: CFStringCreateWithCString", __func__);
+ goto fail;
+ }
+
+ IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
+ (long)ctx->report_in_len, &report_callback, ctx);
+ IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx);
+
+ ok = 0;
+fail:
+ if (entry != MACH_PORT_NULL)
+ IOObjectRelease(entry);
+
+ if (ok < 0 && ctx != NULL) {
+ if (ctx->ref != NULL)
+ CFRelease(ctx->ref);
+ if (ctx->loop_id != NULL)
+ CFRelease(ctx->loop_id);
+ if (ctx->report_pipe[0] != -1)
+ close(ctx->report_pipe[0]);
+ if (ctx->report_pipe[1] != -1)
+ close(ctx->report_pipe[1]);
+ free(ctx);
+ ctx = NULL;
+ }
+
+ return (ctx);
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_osx *ctx = handle;
+
+ IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report,
+ (long)ctx->report_in_len, NULL, ctx);
+ IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, ctx);
+
+ if (IOHIDDeviceClose(ctx->ref,
+ kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess)
+ fido_log_debug("%s: IOHIDDeviceClose", __func__);
+
+ CFRelease(ctx->ref);
+ CFRelease(ctx->loop_id);
+
+ explicit_bzero(ctx->report, sizeof(ctx->report));
+ close(ctx->report_pipe[0]);
+ close(ctx->report_pipe[1]);
+
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ (void)handle;
+ (void)sigmask;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_osx *ctx = handle;
+ ssize_t r;
+
+ explicit_bzero(buf, len);
+ explicit_bzero(ctx->report, sizeof(ctx->report));
+
+ if (len != ctx->report_in_len || len > sizeof(ctx->report)) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetCurrent(),
+ ctx->loop_id);
+
+ if (ms == -1)
+ ms = 5000; /* wait 5 seconds by default */
+
+ CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true);
+
+ IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetCurrent(),
+ ctx->loop_id);
+
+ if ((r = read(ctx->report_pipe[0], buf, len)) == -1) {
+ fido_log_error(errno, "%s: read", __func__);
+ return (-1);
+ }
+
+ if (r < 0 || (size_t)r != len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_osx *ctx = handle;
+
+ if (len != ctx->report_out_len + 1 || len > LONG_MAX) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1,
+ (long)(len - 1)) != kIOReturnSuccess) {
+ fido_log_debug("%s: IOHIDDeviceSetReport", __func__);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_osx *ctx = handle;
+
+ return (ctx->report_in_len);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_osx *ctx = handle;
+
+ return (ctx->report_out_len);
+}
diff --git a/src/hid_unix.c b/src/hid_unix.c
new file mode 100644
index 0000000..e53882d
--- /dev/null
+++ b/src/hid_unix.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include "fido.h"
+
+#ifdef __NetBSD__
+#define ppoll pollts
+#endif
+
+int
+fido_hid_unix_open(const char *path)
+{
+ int fd;
+ struct stat st;
+
+ if ((fd = open(path, O_RDWR)) == -1) {
+ if (errno != ENOENT && errno != ENXIO)
+ fido_log_error(errno, "%s: open %s", __func__, path);
+ return (-1);
+ }
+
+ if (fstat(fd, &st) == -1) {
+ fido_log_error(errno, "%s: fstat %s", __func__, path);
+ if (close(fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+ return (-1);
+ }
+
+ if (S_ISCHR(st.st_mode) == 0) {
+ fido_log_debug("%s: S_ISCHR %s", __func__, path);
+ if (close(fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+ return (-1);
+ }
+
+ return (fd);
+}
+
+int
+fido_hid_unix_wait(int fd, int ms, const fido_sigset_t *sigmask)
+{
+ struct timespec ts;
+ struct pollfd pfd;
+ int r;
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.events = POLLIN;
+ pfd.fd = fd;
+
+#ifdef FIDO_FUZZ
+ return (0);
+#endif
+ if (ms > -1) {
+ ts.tv_sec = ms / 1000;
+ ts.tv_nsec = (ms % 1000) * 1000000;
+ }
+
+ if ((r = ppoll(&pfd, 1, ms > -1 ? &ts : NULL, sigmask)) < 1) {
+ if (r == -1)
+ fido_log_error(errno, "%s: ppoll", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
diff --git a/src/hid_win.c b/src/hid_win.c
new file mode 100644
index 0000000..bc98a17
--- /dev/null
+++ b/src/hid_win.c
@@ -0,0 +1,571 @@
+/*
+ * Copyright (c) 2019-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 <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <windows.h>
+#include <setupapi.h>
+#include <initguid.h>
+#include <devpkey.h>
+#include <devpropdef.h>
+#include <hidclass.h>
+#include <hidsdi.h>
+#include <wchar.h>
+
+#include "fido.h"
+
+#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 6
+WINSETUPAPI WINBOOL WINAPI SetupDiGetDevicePropertyW(HDEVINFO,
+ PSP_DEVINFO_DATA, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE,
+ DWORD, PDWORD, DWORD);
+#endif
+
+#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 8
+DEFINE_DEVPROPKEY(DEVPKEY_Device_Parent, 0x4340a6c5, 0x93fa, 0x4706, 0x97,
+ 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7, 8);
+#endif
+
+struct hid_win {
+ HANDLE dev;
+ OVERLAPPED overlap;
+ int report_pending;
+ size_t report_in_len;
+ size_t report_out_len;
+ unsigned char report[1 + CTAP_MAX_REPORT_LEN];
+};
+
+static bool
+is_fido(HANDLE dev)
+{
+ PHIDP_PREPARSED_DATA data = NULL;
+ HIDP_CAPS caps;
+ int fido = 0;
+
+ if (HidD_GetPreparsedData(dev, &data) == false) {
+ fido_log_debug("%s: HidD_GetPreparsedData", __func__);
+ goto fail;
+ }
+
+ if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) {
+ fido_log_debug("%s: HidP_GetCaps", __func__);
+ goto fail;
+ }
+
+ fido = (uint16_t)caps.UsagePage == 0xf1d0;
+fail:
+ if (data != NULL)
+ HidD_FreePreparsedData(data);
+
+ return (fido);
+}
+
+static int
+get_report_len(HANDLE dev, int dir, size_t *report_len)
+{
+ PHIDP_PREPARSED_DATA data = NULL;
+ HIDP_CAPS caps;
+ USHORT v;
+ int ok = -1;
+
+ if (HidD_GetPreparsedData(dev, &data) == false) {
+ fido_log_debug("%s: HidD_GetPreparsedData/%d", __func__, dir);
+ goto fail;
+ }
+
+ if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) {
+ fido_log_debug("%s: HidP_GetCaps/%d", __func__, dir);
+ goto fail;
+ }
+
+ if (dir == 0)
+ v = caps.InputReportByteLength;
+ else
+ v = caps.OutputReportByteLength;
+
+ if ((*report_len = (size_t)v) == 0) {
+ fido_log_debug("%s: report_len == 0", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (data != NULL)
+ HidD_FreePreparsedData(data);
+
+ return (ok);
+}
+
+static int
+get_id(HANDLE dev, int16_t *vendor_id, int16_t *product_id)
+{
+ HIDD_ATTRIBUTES attr;
+
+ attr.Size = sizeof(attr);
+
+ if (HidD_GetAttributes(dev, &attr) == false) {
+ fido_log_debug("%s: HidD_GetAttributes", __func__);
+ return (-1);
+ }
+
+ *vendor_id = (int16_t)attr.VendorID;
+ *product_id = (int16_t)attr.ProductID;
+
+ return (0);
+}
+
+static int
+get_manufacturer(HANDLE dev, char **manufacturer)
+{
+ wchar_t buf[512];
+ int utf8_len;
+ int ok = -1;
+
+ *manufacturer = NULL;
+
+ if (HidD_GetManufacturerString(dev, &buf, sizeof(buf)) == false) {
+ fido_log_debug("%s: HidD_GetManufacturerString", __func__);
+ goto fail;
+ }
+
+ if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf,
+ -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) {
+ fido_log_debug("%s: WideCharToMultiByte", __func__);
+ goto fail;
+ }
+
+ if ((*manufacturer = malloc((size_t)utf8_len)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ goto fail;
+ }
+
+ if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1,
+ *manufacturer, utf8_len, NULL, NULL) != utf8_len) {
+ fido_log_debug("%s: WideCharToMultiByte", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(*manufacturer);
+ *manufacturer = NULL;
+ }
+
+ return (ok);
+}
+
+static int
+get_product(HANDLE dev, char **product)
+{
+ wchar_t buf[512];
+ int utf8_len;
+ int ok = -1;
+
+ *product = NULL;
+
+ if (HidD_GetProductString(dev, &buf, sizeof(buf)) == false) {
+ fido_log_debug("%s: HidD_GetProductString", __func__);
+ goto fail;
+ }
+
+ if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf,
+ -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) {
+ fido_log_debug("%s: WideCharToMultiByte", __func__);
+ goto fail;
+ }
+
+ if ((*product = malloc((size_t)utf8_len)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ goto fail;
+ }
+
+ if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1,
+ *product, utf8_len, NULL, NULL) != utf8_len) {
+ fido_log_debug("%s: WideCharToMultiByte", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(*product);
+ *product = NULL;
+ }
+
+ return (ok);
+}
+
+static char *
+get_path(HDEVINFO devinfo, SP_DEVICE_INTERFACE_DATA *ifdata)
+{
+ SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL;
+ char *path = NULL;
+ DWORD len = 0;
+
+ /*
+ * "Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail
+ * with a NULL DeviceInterfaceDetailData pointer, a
+ * DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize
+ * variable. In response to such a call, this function returns the
+ * required buffer size at RequiredSize and fails with GetLastError
+ * returning ERROR_INSUFFICIENT_BUFFER."
+ */
+ if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, NULL, 0, &len,
+ NULL) != false || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1",
+ __func__);
+ goto fail;
+ }
+
+ if ((ifdetail = malloc(len)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ goto fail;
+ }
+
+ ifdetail->cbSize = sizeof(*ifdetail);
+
+ if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, ifdetail, len,
+ NULL, NULL) == false) {
+ fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2",
+ __func__);
+ goto fail;
+ }
+
+ if ((path = strdup(ifdetail->DevicePath)) == NULL) {
+ fido_log_debug("%s: strdup", __func__);
+ goto fail;
+ }
+
+fail:
+ free(ifdetail);
+
+ return (path);
+}
+
+#ifndef FIDO_HID_ANY
+static bool
+hid_ok(HDEVINFO devinfo, DWORD idx)
+{
+ SP_DEVINFO_DATA devinfo_data;
+ wchar_t *parent = NULL;
+ DWORD parent_type = DEVPROP_TYPE_STRING;
+ DWORD len = 0;
+ bool ok = false;
+
+ memset(&devinfo_data, 0, sizeof(devinfo_data));
+ devinfo_data.cbSize = sizeof(devinfo_data);
+
+ if (SetupDiEnumDeviceInfo(devinfo, idx, &devinfo_data) == false) {
+ fido_log_debug("%s: SetupDiEnumDeviceInfo", __func__);
+ goto fail;
+ }
+
+ if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data,
+ &DEVPKEY_Device_Parent, &parent_type, NULL, 0, &len, 0) != false ||
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ fido_log_debug("%s: SetupDiGetDevicePropertyW 1", __func__);
+ goto fail;
+ }
+
+ if ((parent = malloc(len)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ goto fail;
+ }
+
+ if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data,
+ &DEVPKEY_Device_Parent, &parent_type, (PBYTE)parent, len, NULL,
+ 0) == false) {
+ fido_log_debug("%s: SetupDiGetDevicePropertyW 2", __func__);
+ goto fail;
+ }
+
+ ok = wcsncmp(parent, L"USB\\", 4) == 0;
+fail:
+ free(parent);
+
+ return (ok);
+}
+#endif
+
+static int
+copy_info(fido_dev_info_t *di, HDEVINFO devinfo, DWORD idx,
+ SP_DEVICE_INTERFACE_DATA *ifdata)
+{
+ HANDLE dev = INVALID_HANDLE_VALUE;
+ int ok = -1;
+
+ memset(di, 0, sizeof(*di));
+
+ if ((di->path = get_path(devinfo, ifdata)) == NULL) {
+ fido_log_debug("%s: get_path", __func__);
+ goto fail;
+ }
+
+ fido_log_debug("%s: path=%s", __func__, di->path);
+
+#ifndef FIDO_HID_ANY
+ if (hid_ok(devinfo, idx) == false) {
+ fido_log_debug("%s: hid_ok", __func__);
+ goto fail;
+ }
+#endif
+
+ dev = CreateFileA(di->path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (dev == INVALID_HANDLE_VALUE) {
+ fido_log_debug("%s: CreateFileA", __func__);
+ goto fail;
+ }
+
+ if (is_fido(dev) == false) {
+ fido_log_debug("%s: is_fido", __func__);
+ goto fail;
+ }
+
+ if (get_id(dev, &di->vendor_id, &di->product_id) < 0) {
+ fido_log_debug("%s: get_id", __func__);
+ goto fail;
+ }
+
+ if (get_manufacturer(dev, &di->manufacturer) < 0) {
+ fido_log_debug("%s: get_manufacturer", __func__);
+ di->manufacturer = strdup("");
+ }
+
+ if (get_product(dev, &di->product) < 0) {
+ fido_log_debug("%s: get_product", __func__);
+ di->product = strdup("");
+ }
+
+ if (di->manufacturer == NULL || di->product == NULL) {
+ fido_log_debug("%s: manufacturer/product", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (dev != INVALID_HANDLE_VALUE)
+ CloseHandle(dev);
+
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return (ok);
+}
+
+int
+fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ GUID hid_guid = GUID_DEVINTERFACE_HID;
+ HDEVINFO devinfo = INVALID_HANDLE_VALUE;
+ SP_DEVICE_INTERFACE_DATA ifdata;
+ DWORD idx;
+ int r = FIDO_ERR_INTERNAL;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return (FIDO_OK); /* nothing to do */
+ if (devlist == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ if ((devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL,
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)) == INVALID_HANDLE_VALUE) {
+ fido_log_debug("%s: SetupDiGetClassDevsA", __func__);
+ goto fail;
+ }
+
+ ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
+
+ for (idx = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid,
+ idx, &ifdata) == true; idx++) {
+ if (copy_info(&devlist[*olen], devinfo, idx, &ifdata) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_hid_open,
+ fido_hid_close,
+ fido_hid_read,
+ fido_hid_write,
+ };
+ if (++(*olen) == ilen)
+ break;
+ }
+ }
+
+ r = FIDO_OK;
+fail:
+ if (devinfo != INVALID_HANDLE_VALUE)
+ SetupDiDestroyDeviceInfoList(devinfo);
+
+ return (r);
+}
+
+void *
+fido_hid_open(const char *path)
+{
+ struct hid_win *ctx;
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
+ return (NULL);
+
+ ctx->dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL);
+
+ if (ctx->dev == INVALID_HANDLE_VALUE) {
+ free(ctx);
+ return (NULL);
+ }
+
+ if ((ctx->overlap.hEvent = CreateEventA(NULL, FALSE, FALSE,
+ NULL)) == NULL) {
+ fido_log_debug("%s: CreateEventA", __func__);
+ fido_hid_close(ctx);
+ return (NULL);
+ }
+
+ if (get_report_len(ctx->dev, 0, &ctx->report_in_len) < 0 ||
+ get_report_len(ctx->dev, 1, &ctx->report_out_len) < 0) {
+ fido_log_debug("%s: get_report_len", __func__);
+ fido_hid_close(ctx);
+ return (NULL);
+ }
+
+ return (ctx);
+}
+
+void
+fido_hid_close(void *handle)
+{
+ struct hid_win *ctx = handle;
+
+ if (ctx->overlap.hEvent != NULL) {
+ if (ctx->report_pending) {
+ fido_log_debug("%s: report_pending", __func__);
+ if (CancelIoEx(ctx->dev, &ctx->overlap) == 0)
+ fido_log_debug("%s CancelIoEx: 0x%lx",
+ __func__, (u_long)GetLastError());
+ }
+ CloseHandle(ctx->overlap.hEvent);
+ }
+
+ explicit_bzero(ctx->report, sizeof(ctx->report));
+ CloseHandle(ctx->dev);
+ free(ctx);
+}
+
+int
+fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ (void)handle;
+ (void)sigmask;
+
+ return (FIDO_ERR_INTERNAL);
+}
+
+int
+fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct hid_win *ctx = handle;
+ DWORD n;
+
+ if (len != ctx->report_in_len - 1 || len > sizeof(ctx->report) - 1) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if (ctx->report_pending == 0) {
+ memset(&ctx->report, 0, sizeof(ctx->report));
+ ResetEvent(ctx->overlap.hEvent);
+ if (ReadFile(ctx->dev, ctx->report, (DWORD)(len + 1), &n,
+ &ctx->overlap) == 0 && GetLastError() != ERROR_IO_PENDING) {
+ CancelIo(ctx->dev);
+ fido_log_debug("%s: ReadFile", __func__);
+ return (-1);
+ }
+ ctx->report_pending = 1;
+ }
+
+ if (ms > -1 && WaitForSingleObject(ctx->overlap.hEvent,
+ (DWORD)ms) != WAIT_OBJECT_0)
+ return (0);
+
+ ctx->report_pending = 0;
+
+ if (GetOverlappedResult(ctx->dev, &ctx->overlap, &n, TRUE) == 0) {
+ fido_log_debug("%s: GetOverlappedResult", __func__);
+ return (-1);
+ }
+
+ if (n != len + 1) {
+ fido_log_debug("%s: expected %zu, got %zu", __func__,
+ len + 1, (size_t)n);
+ return (-1);
+ }
+
+ memcpy(buf, ctx->report + 1, len);
+ explicit_bzero(ctx->report, sizeof(ctx->report));
+
+ return ((int)len);
+}
+
+int
+fido_hid_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct hid_win *ctx = handle;
+ OVERLAPPED overlap;
+ DWORD n;
+
+ memset(&overlap, 0, sizeof(overlap));
+
+ if (len != ctx->report_out_len) {
+ fido_log_debug("%s: len %zu", __func__, len);
+ return (-1);
+ }
+
+ if (WriteFile(ctx->dev, buf, (DWORD)len, NULL, &overlap) == 0 &&
+ GetLastError() != ERROR_IO_PENDING) {
+ fido_log_debug("%s: WriteFile", __func__);
+ return (-1);
+ }
+
+ if (GetOverlappedResult(ctx->dev, &overlap, &n, TRUE) == 0) {
+ fido_log_debug("%s: GetOverlappedResult", __func__);
+ return (-1);
+ }
+
+ if (n != len) {
+ fido_log_debug("%s: expected %zu, got %zu", __func__, len,
+ (size_t)n);
+ return (-1);
+ }
+
+ return ((int)len);
+}
+
+size_t
+fido_hid_report_in_len(void *handle)
+{
+ struct hid_win *ctx = handle;
+
+ return (ctx->report_in_len - 1);
+}
+
+size_t
+fido_hid_report_out_len(void *handle)
+{
+ struct hid_win *ctx = handle;
+
+ return (ctx->report_out_len - 1);
+}
diff --git a/src/info.c b/src/info.c
new file mode 100644
index 0000000..cd30828
--- /dev/null
+++ b/src/info.c
@@ -0,0 +1,647 @@
+/*
+ * 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"
+
+static int
+decode_string(const cbor_item_t *item, void *arg)
+{
+ fido_str_array_t *a = arg;
+ const size_t i = a->len;
+
+ /* keep ptr[x] and len consistent */
+ if (cbor_string_copy(item, &a->ptr[i]) < 0) {
+ fido_log_debug("%s: cbor_string_copy", __func__);
+ return (-1);
+ }
+
+ a->len++;
+
+ return (0);
+}
+
+static int
+decode_string_array(const cbor_item_t *item, fido_str_array_t *v)
+{
+ v->ptr = NULL;
+ v->len = 0;
+
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ v->ptr = calloc(cbor_array_size(item), sizeof(char *));
+ if (v->ptr == NULL)
+ return (-1);
+
+ if (cbor_array_iter(item, v, decode_string) < 0) {
+ fido_log_debug("%s: decode_string", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_aaguid(const cbor_item_t *item, unsigned char *aaguid, size_t aaguid_len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != aaguid_len) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(aaguid, cbor_bytestring_handle(item), aaguid_len);
+
+ return (0);
+}
+
+static int
+decode_option(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_opt_array_t *o = arg;
+ const size_t i = o->len;
+
+ if (cbor_decode_bool(val, NULL) < 0) {
+ fido_log_debug("%s: cbor_decode_bool", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_string_copy(key, &o->name[i]) < 0) {
+ fido_log_debug("%s: cbor_string_copy", __func__);
+ return (0); /* ignore */
+ }
+
+ /* keep name/value and len consistent */
+ o->value[i] = cbor_ctrl_value(val) == CBOR_CTRL_TRUE;
+ o->len++;
+
+ return (0);
+}
+
+static int
+decode_options(const cbor_item_t *item, fido_opt_array_t *o)
+{
+ o->name = NULL;
+ o->value = NULL;
+ o->len = 0;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ o->name = calloc(cbor_map_size(item), sizeof(char *));
+ o->value = calloc(cbor_map_size(item), sizeof(bool));
+ if (o->name == NULL || o->value == NULL)
+ return (-1);
+
+ return (cbor_map_iter(item, o, decode_option));
+}
+
+static int
+decode_protocol(const cbor_item_t *item, void *arg)
+{
+ fido_byte_array_t *p = arg;
+ const size_t i = p->len;
+
+ if (cbor_isa_uint(item) == false ||
+ cbor_int_get_width(item) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ /* keep ptr[x] and len consistent */
+ p->ptr[i] = cbor_get_uint8(item);
+ p->len++;
+
+ return (0);
+}
+
+static int
+decode_protocols(const cbor_item_t *item, fido_byte_array_t *p)
+{
+ p->ptr = NULL;
+ p->len = 0;
+
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ p->ptr = calloc(cbor_array_size(item), sizeof(uint8_t));
+ if (p->ptr == NULL)
+ return (-1);
+
+ if (cbor_array_iter(item, p, decode_protocol) < 0) {
+ fido_log_debug("%s: decode_protocol", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_algorithm_entry(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ fido_algo_t *alg = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto out;
+ }
+
+ if (!strcmp(name, "alg")) {
+ if (cbor_isa_negint(val) == false ||
+ cbor_get_int(val) > INT_MAX || alg->cose != 0) {
+ fido_log_debug("%s: alg", __func__);
+ goto out;
+ }
+ alg->cose = -(int)cbor_get_int(val) - 1;
+ } else if (!strcmp(name, "type")) {
+ if (cbor_string_copy(val, &alg->type) < 0) {
+ fido_log_debug("%s: type", __func__);
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ free(name);
+
+ return (ok);
+}
+
+static int
+decode_algorithm(const cbor_item_t *item, void *arg)
+{
+ fido_algo_array_t *aa = arg;
+ const size_t i = aa->len;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memset(&aa->ptr[i], 0, sizeof(aa->ptr[i]));
+
+ if (cbor_map_iter(item, &aa->ptr[i], decode_algorithm_entry) < 0) {
+ fido_log_debug("%s: decode_algorithm_entry", __func__);
+ fido_algo_free(&aa->ptr[i]);
+ return (-1);
+ }
+
+ /* keep ptr[x] and len consistent */
+ aa->len++;
+
+ return (0);
+}
+
+static int
+decode_algorithms(const cbor_item_t *item, fido_algo_array_t *aa)
+{
+ aa->ptr = NULL;
+ aa->len = 0;
+
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ aa->ptr = calloc(cbor_array_size(item), sizeof(fido_algo_t));
+ if (aa->ptr == NULL)
+ return (-1);
+
+ if (cbor_array_iter(item, aa, decode_algorithm) < 0) {
+ fido_log_debug("%s: decode_algorithm", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+decode_cert(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cert_array_t *c = arg;
+ const size_t i = c->len;
+
+ if (cbor_is_int(val) == false) {
+ fido_log_debug("%s: cbor_is_int", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_string_copy(key, &c->name[i]) < 0) {
+ fido_log_debug("%s: cbor_string_copy", __func__);
+ return (0); /* ignore */
+ }
+
+ /* keep name/value and len consistent */
+ c->value[i] = cbor_get_int(val);
+ c->len++;
+
+ return (0);
+}
+
+static int
+decode_certs(const cbor_item_t *item, fido_cert_array_t *c)
+{
+ c->name = NULL;
+ c->value = NULL;
+ c->len = 0;
+
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ c->name = calloc(cbor_map_size(item), sizeof(char *));
+ c->value = calloc(cbor_map_size(item), sizeof(uint64_t));
+ if (c->name == NULL || c->value == NULL)
+ return (-1);
+
+ return (cbor_map_iter(item, c, decode_cert));
+}
+
+static int
+parse_reply_element(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cbor_info_t *ci = arg;
+ uint64_t x;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* versions */
+ return (decode_string_array(val, &ci->versions));
+ case 2: /* extensions */
+ return (decode_string_array(val, &ci->extensions));
+ case 3: /* aaguid */
+ return (decode_aaguid(val, ci->aaguid, sizeof(ci->aaguid)));
+ case 4: /* options */
+ return (decode_options(val, &ci->options));
+ case 5: /* maxMsgSize */
+ return (cbor_decode_uint64(val, &ci->maxmsgsiz));
+ case 6: /* pinProtocols */
+ return (decode_protocols(val, &ci->protocols));
+ case 7: /* maxCredentialCountInList */
+ return (cbor_decode_uint64(val, &ci->maxcredcntlst));
+ case 8: /* maxCredentialIdLength */
+ return (cbor_decode_uint64(val, &ci->maxcredidlen));
+ case 9: /* transports */
+ return (decode_string_array(val, &ci->transports));
+ case 10: /* algorithms */
+ return (decode_algorithms(val, &ci->algorithms));
+ case 11: /* maxSerializedLargeBlobArray */
+ return (cbor_decode_uint64(val, &ci->maxlargeblob));
+ case 12: /* forcePINChange */
+ return (cbor_decode_bool(val, &ci->new_pin_reqd));
+ case 13: /* minPINLength */
+ return (cbor_decode_uint64(val, &ci->minpinlen));
+ case 14: /* fwVersion */
+ return (cbor_decode_uint64(val, &ci->fwversion));
+ case 15: /* maxCredBlobLen */
+ return (cbor_decode_uint64(val, &ci->maxcredbloblen));
+ case 16: /* maxRPIDsForSetMinPINLength */
+ return (cbor_decode_uint64(val, &ci->maxrpid_minlen));
+ case 17: /* preferredPlatformUvAttempts */
+ return (cbor_decode_uint64(val, &ci->uv_attempts));
+ case 18: /* uvModality */
+ return (cbor_decode_uint64(val, &ci->uv_modality));
+ case 19: /* certifications */
+ return (decode_certs(val, &ci->certs));
+ case 20: /* remainingDiscoverableCredentials */
+ if (cbor_decode_uint64(val, &x) < 0 || x > INT64_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+ ci->rk_remaining = (int64_t)x;
+ return (0);
+ default: /* ignore */
+ fido_log_debug("%s: cbor type: 0x%02x", __func__, cbor_get_uint8(key));
+ return (0);
+ }
+}
+
+static int
+fido_dev_get_cbor_info_tx(fido_dev_t *dev, int *ms)
+{
+ const unsigned char cbor[] = { CTAP_CBOR_GETINFO };
+
+ fido_log_debug("%s: dev=%p", __func__, (void *)dev);
+
+ if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ return (FIDO_ERR_TX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_get_cbor_info_rx(fido_dev_t *dev, fido_cbor_info_t *ci, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ fido_log_debug("%s: dev=%p, ci=%p, ms=%d", __func__, (void *)dev,
+ (void *)ci, *ms);
+
+ fido_cbor_info_reset(ci);
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ r = cbor_parse_reply(msg, (size_t)msglen, ci, parse_reply_element);
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+int
+fido_dev_get_cbor_info_wait(fido_dev_t *dev, fido_cbor_info_t *ci, int *ms)
+{
+ int r;
+
+#ifdef USE_WINHELLO
+ if (dev->flags & FIDO_DEV_WINHELLO)
+ return (fido_winhello_get_cbor_info(dev, ci));
+#endif
+ if ((r = fido_dev_get_cbor_info_tx(dev, ms)) != FIDO_OK ||
+ (r = fido_dev_get_cbor_info_rx(dev, ci, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci)
+{
+ int ms = dev->timeout_ms;
+
+ return (fido_dev_get_cbor_info_wait(dev, ci, &ms));
+}
+
+/*
+ * get/set functions for fido_cbor_info_t; always at the end of the file
+ */
+
+fido_cbor_info_t *
+fido_cbor_info_new(void)
+{
+ fido_cbor_info_t *ci;
+
+ if ((ci = calloc(1, sizeof(fido_cbor_info_t))) == NULL)
+ return (NULL);
+
+ fido_cbor_info_reset(ci);
+
+ return (ci);
+}
+
+void
+fido_cbor_info_reset(fido_cbor_info_t *ci)
+{
+ fido_str_array_free(&ci->versions);
+ fido_str_array_free(&ci->extensions);
+ fido_str_array_free(&ci->transports);
+ fido_opt_array_free(&ci->options);
+ fido_byte_array_free(&ci->protocols);
+ fido_algo_array_free(&ci->algorithms);
+ fido_cert_array_free(&ci->certs);
+ ci->rk_remaining = -1;
+}
+
+void
+fido_cbor_info_free(fido_cbor_info_t **ci_p)
+{
+ fido_cbor_info_t *ci;
+
+ if (ci_p == NULL || (ci = *ci_p) == NULL)
+ return;
+ fido_cbor_info_reset(ci);
+ free(ci);
+ *ci_p = NULL;
+}
+
+char **
+fido_cbor_info_versions_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->versions.ptr);
+}
+
+size_t
+fido_cbor_info_versions_len(const fido_cbor_info_t *ci)
+{
+ return (ci->versions.len);
+}
+
+char **
+fido_cbor_info_extensions_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->extensions.ptr);
+}
+
+size_t
+fido_cbor_info_extensions_len(const fido_cbor_info_t *ci)
+{
+ return (ci->extensions.len);
+}
+
+char **
+fido_cbor_info_transports_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->transports.ptr);
+}
+
+size_t
+fido_cbor_info_transports_len(const fido_cbor_info_t *ci)
+{
+ return (ci->transports.len);
+}
+
+const unsigned char *
+fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->aaguid);
+}
+
+size_t
+fido_cbor_info_aaguid_len(const fido_cbor_info_t *ci)
+{
+ return (sizeof(ci->aaguid));
+}
+
+char **
+fido_cbor_info_options_name_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->options.name);
+}
+
+const bool *
+fido_cbor_info_options_value_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->options.value);
+}
+
+size_t
+fido_cbor_info_options_len(const fido_cbor_info_t *ci)
+{
+ return (ci->options.len);
+}
+
+uint64_t
+fido_cbor_info_maxcredbloblen(const fido_cbor_info_t *ci)
+{
+ return (ci->maxcredbloblen);
+}
+
+uint64_t
+fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *ci)
+{
+ return (ci->maxmsgsiz);
+}
+
+uint64_t
+fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *ci)
+{
+ return (ci->maxcredcntlst);
+}
+
+uint64_t
+fido_cbor_info_maxcredidlen(const fido_cbor_info_t *ci)
+{
+ return (ci->maxcredidlen);
+}
+
+uint64_t
+fido_cbor_info_maxlargeblob(const fido_cbor_info_t *ci)
+{
+ return (ci->maxlargeblob);
+}
+
+uint64_t
+fido_cbor_info_fwversion(const fido_cbor_info_t *ci)
+{
+ return (ci->fwversion);
+}
+
+uint64_t
+fido_cbor_info_minpinlen(const fido_cbor_info_t *ci)
+{
+ return (ci->minpinlen);
+}
+
+uint64_t
+fido_cbor_info_maxrpid_minpinlen(const fido_cbor_info_t *ci)
+{
+ return (ci->maxrpid_minlen);
+}
+
+uint64_t
+fido_cbor_info_uv_attempts(const fido_cbor_info_t *ci)
+{
+ return (ci->uv_attempts);
+}
+
+uint64_t
+fido_cbor_info_uv_modality(const fido_cbor_info_t *ci)
+{
+ return (ci->uv_modality);
+}
+
+int64_t
+fido_cbor_info_rk_remaining(const fido_cbor_info_t *ci)
+{
+ return (ci->rk_remaining);
+}
+
+const uint8_t *
+fido_cbor_info_protocols_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->protocols.ptr);
+}
+
+size_t
+fido_cbor_info_protocols_len(const fido_cbor_info_t *ci)
+{
+ return (ci->protocols.len);
+}
+
+size_t
+fido_cbor_info_algorithm_count(const fido_cbor_info_t *ci)
+{
+ return (ci->algorithms.len);
+}
+
+const char *
+fido_cbor_info_algorithm_type(const fido_cbor_info_t *ci, size_t idx)
+{
+ if (idx >= ci->algorithms.len)
+ return (NULL);
+
+ return (ci->algorithms.ptr[idx].type);
+}
+
+int
+fido_cbor_info_algorithm_cose(const fido_cbor_info_t *ci, size_t idx)
+{
+ if (idx >= ci->algorithms.len)
+ return (0);
+
+ return (ci->algorithms.ptr[idx].cose);
+}
+
+bool
+fido_cbor_info_new_pin_required(const fido_cbor_info_t *ci)
+{
+ return (ci->new_pin_reqd);
+}
+
+char **
+fido_cbor_info_certs_name_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->certs.name);
+}
+
+const uint64_t *
+fido_cbor_info_certs_value_ptr(const fido_cbor_info_t *ci)
+{
+ return (ci->certs.value);
+}
+
+size_t
+fido_cbor_info_certs_len(const fido_cbor_info_t *ci)
+{
+ return (ci->certs.len);
+}
diff --git a/src/io.c b/src/io.c
new file mode 100644
index 0000000..a9715b5
--- /dev/null
+++ b/src/io.c
@@ -0,0 +1,356 @@
+/*
+ * 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 "packed.h"
+
+PACKED_TYPE(frame_t,
+struct frame {
+ uint32_t cid; /* channel id */
+ union {
+ uint8_t type;
+ struct {
+ uint8_t cmd;
+ uint8_t bcnth;
+ uint8_t bcntl;
+ uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_INIT_HEADER_LEN];
+ } init;
+ struct {
+ uint8_t seq;
+ uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_CONT_HEADER_LEN];
+ } cont;
+ } body;
+})
+
+#ifndef MIN
+#define MIN(x, y) ((x) > (y) ? (y) : (x))
+#endif
+
+static int
+tx_pkt(fido_dev_t *d, const void *pkt, size_t len, int *ms)
+{
+ struct timespec ts;
+ int n;
+
+ if (fido_time_now(&ts) != 0)
+ return (-1);
+
+ n = d->io.write(d->io_handle, pkt, len);
+
+ if (fido_time_delta(&ts, ms) != 0)
+ return (-1);
+
+ return (n);
+}
+
+static int
+tx_empty(fido_dev_t *d, uint8_t cmd, int *ms)
+{
+ struct frame *fp;
+ unsigned char pkt[sizeof(*fp) + 1];
+ const size_t len = d->tx_len + 1;
+ int n;
+
+ memset(&pkt, 0, sizeof(pkt));
+ fp = (struct frame *)(pkt + 1);
+ fp->cid = d->cid;
+ fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
+
+ if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
+ (size_t)n != len)
+ return (-1);
+
+ return (0);
+}
+
+static size_t
+tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
+{
+ struct frame *fp;
+ unsigned char pkt[sizeof(*fp) + 1];
+ const size_t len = d->tx_len + 1;
+ int n;
+
+ if (d->tx_len - CTAP_INIT_HEADER_LEN > sizeof(fp->body.init.data))
+ return (0);
+
+ memset(&pkt, 0, sizeof(pkt));
+ fp = (struct frame *)(pkt + 1);
+ fp->cid = d->cid;
+ fp->body.init.cmd = CTAP_FRAME_INIT | cmd;
+ fp->body.init.bcnth = (count >> 8) & 0xff;
+ fp->body.init.bcntl = count & 0xff;
+ count = MIN(count, d->tx_len - CTAP_INIT_HEADER_LEN);
+ memcpy(&fp->body.init.data, buf, count);
+
+ if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
+ (size_t)n != len)
+ return (0);
+
+ return (count);
+}
+
+static size_t
+tx_frame(fido_dev_t *d, uint8_t seq, const void *buf, size_t count, int *ms)
+{
+ struct frame *fp;
+ unsigned char pkt[sizeof(*fp) + 1];
+ const size_t len = d->tx_len + 1;
+ int n;
+
+ if (d->tx_len - CTAP_CONT_HEADER_LEN > sizeof(fp->body.cont.data))
+ return (0);
+
+ memset(&pkt, 0, sizeof(pkt));
+ fp = (struct frame *)(pkt + 1);
+ fp->cid = d->cid;
+ fp->body.cont.seq = seq;
+ count = MIN(count, d->tx_len - CTAP_CONT_HEADER_LEN);
+ memcpy(&fp->body.cont.data, buf, count);
+
+ if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 ||
+ (size_t)n != len)
+ return (0);
+
+ return (count);
+}
+
+static int
+tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count, int *ms)
+{
+ size_t n, sent;
+
+ if ((sent = tx_preamble(d, cmd, buf, count, ms)) == 0) {
+ fido_log_debug("%s: tx_preamble", __func__);
+ return (-1);
+ }
+
+ for (uint8_t seq = 0; sent < count; sent += n) {
+ if (seq & 0x80) {
+ fido_log_debug("%s: seq & 0x80", __func__);
+ return (-1);
+ }
+ if ((n = tx_frame(d, seq++, buf + sent, count - sent,
+ ms)) == 0) {
+ fido_log_debug("%s: tx_frame", __func__);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int
+transport_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
+{
+ struct timespec ts;
+ int n;
+
+ if (fido_time_now(&ts) != 0)
+ return (-1);
+
+ n = d->transport.tx(d, cmd, buf, count);
+
+ if (fido_time_delta(&ts, ms) != 0)
+ return (-1);
+
+ return (n);
+}
+
+int
+fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms)
+{
+ fido_log_debug("%s: dev=%p, cmd=0x%02x", __func__, (void *)d, cmd);
+ fido_log_xxd(buf, count, "%s", __func__);
+
+ if (d->transport.tx != NULL)
+ return (transport_tx(d, cmd, buf, count, ms));
+ if (d->io_handle == NULL || d->io.write == NULL || count > UINT16_MAX) {
+ fido_log_debug("%s: invalid argument", __func__);
+ return (-1);
+ }
+
+ return (count == 0 ? tx_empty(d, cmd, ms) : tx(d, cmd, buf, count, ms));
+}
+
+static int
+rx_frame(fido_dev_t *d, struct frame *fp, int *ms)
+{
+ struct timespec ts;
+ int n;
+
+ memset(fp, 0, sizeof(*fp));
+
+ if (fido_time_now(&ts) != 0)
+ return (-1);
+
+ if (d->rx_len > sizeof(*fp) || (n = d->io.read(d->io_handle,
+ (unsigned char *)fp, d->rx_len, *ms)) < 0 || (size_t)n != d->rx_len)
+ return (-1);
+
+ return (fido_time_delta(&ts, ms));
+}
+
+static int
+rx_preamble(fido_dev_t *d, uint8_t cmd, struct frame *fp, int *ms)
+{
+ do {
+ if (rx_frame(d, fp, ms) < 0)
+ return (-1);
+#ifdef FIDO_FUZZ
+ fp->cid = d->cid;
+#endif
+ } while (fp->cid != d->cid || (fp->cid == d->cid &&
+ fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE)));
+
+ if (d->rx_len > sizeof(*fp))
+ return (-1);
+
+ fido_log_xxd(fp, d->rx_len, "%s", __func__);
+#ifdef FIDO_FUZZ
+ fp->body.init.cmd = (CTAP_FRAME_INIT | cmd);
+#endif
+
+ if (fp->cid != d->cid || fp->body.init.cmd != (CTAP_FRAME_INIT | cmd)) {
+ fido_log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)",
+ __func__, fp->cid, d->cid, fp->body.init.cmd, cmd);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int *ms)
+{
+ struct frame f;
+ size_t r, payload_len, init_data_len, cont_data_len;
+
+ if (d->rx_len <= CTAP_INIT_HEADER_LEN ||
+ d->rx_len <= CTAP_CONT_HEADER_LEN)
+ return (-1);
+
+ init_data_len = d->rx_len - CTAP_INIT_HEADER_LEN;
+ cont_data_len = d->rx_len - CTAP_CONT_HEADER_LEN;
+
+ if (init_data_len > sizeof(f.body.init.data) ||
+ cont_data_len > sizeof(f.body.cont.data))
+ return (-1);
+
+ if (rx_preamble(d, cmd, &f, ms) < 0) {
+ fido_log_debug("%s: rx_preamble", __func__);
+ return (-1);
+ }
+
+ payload_len = (size_t)((f.body.init.bcnth << 8) | f.body.init.bcntl);
+ fido_log_debug("%s: payload_len=%zu", __func__, payload_len);
+
+ if (count < payload_len) {
+ fido_log_debug("%s: count < payload_len", __func__);
+ return (-1);
+ }
+
+ if (payload_len < init_data_len) {
+ memcpy(buf, f.body.init.data, payload_len);
+ return ((int)payload_len);
+ }
+
+ memcpy(buf, f.body.init.data, init_data_len);
+ r = init_data_len;
+
+ for (int seq = 0; r < payload_len; seq++) {
+ if (rx_frame(d, &f, ms) < 0) {
+ fido_log_debug("%s: rx_frame", __func__);
+ return (-1);
+ }
+
+ fido_log_xxd(&f, d->rx_len, "%s", __func__);
+#ifdef FIDO_FUZZ
+ f.cid = d->cid;
+ f.body.cont.seq = (uint8_t)seq;
+#endif
+
+ if (f.cid != d->cid || f.body.cont.seq != seq) {
+ fido_log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)",
+ __func__, f.cid, d->cid, f.body.cont.seq, seq);
+ return (-1);
+ }
+
+ if (payload_len - r > cont_data_len) {
+ memcpy(buf + r, f.body.cont.data, cont_data_len);
+ r += cont_data_len;
+ } else {
+ memcpy(buf + r, f.body.cont.data, payload_len - r);
+ r += payload_len - r; /* break */
+ }
+ }
+
+ return ((int)r);
+}
+
+static int
+transport_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms)
+{
+ struct timespec ts;
+ int n;
+
+ if (fido_time_now(&ts) != 0)
+ return (-1);
+
+ n = d->transport.rx(d, cmd, buf, count, *ms);
+
+ if (fido_time_delta(&ts, ms) != 0)
+ return (-1);
+
+ return (n);
+}
+
+int
+fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms)
+{
+ int n;
+
+ fido_log_debug("%s: dev=%p, cmd=0x%02x, ms=%d", __func__, (void *)d,
+ cmd, *ms);
+
+ if (d->transport.rx != NULL)
+ return (transport_rx(d, cmd, buf, count, ms));
+ if (d->io_handle == NULL || d->io.read == NULL || count > UINT16_MAX) {
+ fido_log_debug("%s: invalid argument", __func__);
+ return (-1);
+ }
+ if ((n = rx(d, cmd, buf, count, ms)) >= 0)
+ fido_log_xxd(buf, (size_t)n, "%s", __func__);
+
+ return (n);
+}
+
+int
+fido_rx_cbor_status(fido_dev_t *d, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((msglen = fido_rx(d, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0 ||
+ (size_t)msglen < 1) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ r = msg[0];
+out:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
diff --git a/src/iso7816.c b/src/iso7816.c
new file mode 100644
index 0000000..5bba106
--- /dev/null
+++ b/src/iso7816.c
@@ -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.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "fido.h"
+
+iso7816_apdu_t *
+iso7816_new(uint8_t cla, uint8_t ins, uint8_t p1, uint16_t payload_len)
+{
+ iso7816_apdu_t *apdu;
+ size_t alloc_len;
+
+ alloc_len = sizeof(iso7816_apdu_t) + payload_len + 2; /* le1 le2 */
+ if ((apdu = calloc(1, alloc_len)) == NULL)
+ return NULL;
+ apdu->alloc_len = alloc_len;
+ apdu->payload_len = payload_len;
+ apdu->payload_ptr = apdu->payload;
+ apdu->header.cla = cla;
+ apdu->header.ins = ins;
+ apdu->header.p1 = p1;
+ apdu->header.lc2 = (uint8_t)((payload_len >> 8) & 0xff);
+ apdu->header.lc3 = (uint8_t)(payload_len & 0xff);
+
+ return apdu;
+}
+
+void
+iso7816_free(iso7816_apdu_t **apdu_p)
+{
+ iso7816_apdu_t *apdu;
+
+ if (apdu_p == NULL || (apdu = *apdu_p) == NULL)
+ return;
+ freezero(apdu, apdu->alloc_len);
+ *apdu_p = NULL;
+}
+
+int
+iso7816_add(iso7816_apdu_t *apdu, const void *buf, size_t cnt)
+{
+ if (cnt > apdu->payload_len || cnt > UINT16_MAX)
+ return -1;
+ memcpy(apdu->payload_ptr, buf, cnt);
+ apdu->payload_ptr += cnt;
+ apdu->payload_len = (uint16_t)(apdu->payload_len - cnt);
+
+ return 0;
+}
+
+const unsigned char *
+iso7816_ptr(const iso7816_apdu_t *apdu)
+{
+ return (const unsigned char *)&apdu->header;
+}
+
+size_t
+iso7816_len(const iso7816_apdu_t *apdu)
+{
+ return apdu->alloc_len - offsetof(iso7816_apdu_t, header) -
+ (sizeof(iso7816_apdu_t) - offsetof(iso7816_apdu_t, payload));
+}
diff --git a/src/iso7816.h b/src/iso7816.h
new file mode 100644
index 0000000..7545719
--- /dev/null
+++ b/src/iso7816.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _ISO7816_H
+#define _ISO7816_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "packed.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+PACKED_TYPE(iso7816_header_t,
+struct iso7816_header {
+ uint8_t cla;
+ uint8_t ins;
+ uint8_t p1;
+ uint8_t p2;
+ uint8_t lc1;
+ uint8_t lc2;
+ uint8_t lc3;
+})
+
+typedef struct iso7816_apdu {
+ size_t alloc_len;
+ uint16_t payload_len;
+ uint8_t *payload_ptr;
+ iso7816_header_t header;
+ uint8_t payload[];
+} iso7816_apdu_t;
+
+const unsigned char *iso7816_ptr(const iso7816_apdu_t *);
+int iso7816_add(iso7816_apdu_t *, const void *, size_t);
+iso7816_apdu_t *iso7816_new(uint8_t, uint8_t, uint8_t, uint16_t);
+size_t iso7816_len(const iso7816_apdu_t *);
+void iso7816_free(iso7816_apdu_t **);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_ISO7816_H */
diff --git a/src/largeblob.c b/src/largeblob.c
new file mode 100644
index 0000000..c1f2e62
--- /dev/null
+++ b/src/largeblob.c
@@ -0,0 +1,902 @@
+/*
+ * 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 <openssl/sha.h>
+
+#include "fido.h"
+#include "fido/es256.h"
+
+#define LARGEBLOB_DIGEST_LENGTH 16
+#define LARGEBLOB_NONCE_LENGTH 12
+#define LARGEBLOB_TAG_LENGTH 16
+
+typedef struct largeblob {
+ size_t origsiz;
+ fido_blob_t ciphertext;
+ fido_blob_t nonce;
+} largeblob_t;
+
+static largeblob_t *
+largeblob_new(void)
+{
+ return calloc(1, sizeof(largeblob_t));
+}
+
+static void
+largeblob_reset(largeblob_t *blob)
+{
+ fido_blob_reset(&blob->ciphertext);
+ fido_blob_reset(&blob->nonce);
+ blob->origsiz = 0;
+}
+
+static void
+largeblob_free(largeblob_t **blob_ptr)
+{
+ largeblob_t *blob;
+
+ if (blob_ptr == NULL || (blob = *blob_ptr) == NULL)
+ return;
+ largeblob_reset(blob);
+ free(blob);
+ *blob_ptr = NULL;
+}
+
+static int
+largeblob_aad(fido_blob_t *aad, uint64_t size)
+{
+ uint8_t buf[4 + sizeof(uint64_t)];
+
+ buf[0] = 0x62; /* b */
+ buf[1] = 0x6c; /* l */
+ buf[2] = 0x6f; /* o */
+ buf[3] = 0x62; /* b */
+ size = htole64(size);
+ memcpy(&buf[4], &size, sizeof(uint64_t));
+
+ return fido_blob_set(aad, buf, sizeof(buf));
+}
+
+static fido_blob_t *
+largeblob_decrypt(const largeblob_t *blob, const fido_blob_t *key)
+{
+ fido_blob_t *plaintext = NULL, *aad = NULL;
+ int ok = -1;
+
+ if ((plaintext = fido_blob_new()) == NULL ||
+ (aad = fido_blob_new()) == NULL) {
+ fido_log_debug("%s: fido_blob_new", __func__);
+ goto fail;
+ }
+ if (largeblob_aad(aad, blob->origsiz) < 0) {
+ fido_log_debug("%s: largeblob_aad", __func__);
+ goto fail;
+ }
+ if (aes256_gcm_dec(key, &blob->nonce, aad, &blob->ciphertext,
+ plaintext) < 0) {
+ fido_log_debug("%s: aes256_gcm_dec", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ fido_blob_free(&aad);
+
+ if (ok < 0)
+ fido_blob_free(&plaintext);
+
+ return plaintext;
+}
+
+static int
+largeblob_get_nonce(largeblob_t *blob)
+{
+ uint8_t buf[LARGEBLOB_NONCE_LENGTH];
+ int ok = -1;
+
+ if (fido_get_random(buf, sizeof(buf)) < 0) {
+ fido_log_debug("%s: fido_get_random", __func__);
+ goto fail;
+ }
+ if (fido_blob_set(&blob->nonce, buf, sizeof(buf)) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ explicit_bzero(buf, sizeof(buf));
+
+ return ok;
+}
+
+static int
+largeblob_seal(largeblob_t *blob, const fido_blob_t *body,
+ const fido_blob_t *key)
+{
+ fido_blob_t *plaintext = NULL, *aad = NULL;
+ int ok = -1;
+
+ if ((plaintext = fido_blob_new()) == NULL ||
+ (aad = fido_blob_new()) == NULL) {
+ fido_log_debug("%s: fido_blob_new", __func__);
+ goto fail;
+ }
+ if (fido_compress(plaintext, body) != FIDO_OK) {
+ fido_log_debug("%s: fido_compress", __func__);
+ goto fail;
+ }
+ if (largeblob_aad(aad, body->len) < 0) {
+ fido_log_debug("%s: largeblob_aad", __func__);
+ goto fail;
+ }
+ if (largeblob_get_nonce(blob) < 0) {
+ fido_log_debug("%s: largeblob_get_nonce", __func__);
+ goto fail;
+ }
+ if (aes256_gcm_enc(key, &blob->nonce, aad, plaintext,
+ &blob->ciphertext) < 0) {
+ fido_log_debug("%s: aes256_gcm_enc", __func__);
+ goto fail;
+ }
+ blob->origsiz = body->len;
+
+ ok = 0;
+fail:
+ fido_blob_free(&plaintext);
+ fido_blob_free(&aad);
+
+ return ok;
+}
+
+static int
+largeblob_get_tx(fido_dev_t *dev, size_t offset, size_t count, int *ms)
+{
+ fido_blob_t f;
+ cbor_item_t *argv[3];
+ int r;
+
+ memset(argv, 0, sizeof(argv));
+ memset(&f, 0, sizeof(f));
+
+ if ((argv[0] = cbor_build_uint(count)) == NULL ||
+ (argv[2] = cbor_build_uint(offset)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return r;
+}
+
+static int
+parse_largeblob_reply(const cbor_item_t *key, const cbor_item_t *val,
+ void *arg)
+{
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 1) {
+ fido_log_debug("%s: cbor type", __func__);
+ return 0; /* ignore */
+ }
+
+ return fido_blob_decode(val, arg);
+}
+
+static int
+largeblob_get_rx(fido_dev_t *dev, fido_blob_t **chunk, int *ms)
+{
+ unsigned char *msg;
+ int msglen, r;
+
+ *chunk = NULL;
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+ if ((*chunk = fido_blob_new()) == NULL) {
+ fido_log_debug("%s: fido_blob_new", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, *chunk,
+ parse_largeblob_reply)) != FIDO_OK) {
+ fido_log_debug("%s: parse_largeblob_reply", __func__);
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ if (r != FIDO_OK)
+ fido_blob_free(chunk);
+
+ freezero(msg, FIDO_MAXMSG);
+
+ return r;
+}
+
+static cbor_item_t *
+largeblob_array_load(const uint8_t *ptr, size_t len)
+{
+ struct cbor_load_result cbor;
+ cbor_item_t *item;
+
+ if (len < LARGEBLOB_DIGEST_LENGTH) {
+ fido_log_debug("%s: len", __func__);
+ return NULL;
+ }
+ len -= LARGEBLOB_DIGEST_LENGTH;
+ if ((item = cbor_load(ptr, len, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ return NULL;
+ }
+ if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) {
+ fido_log_debug("%s: cbor type", __func__);
+ cbor_decref(&item);
+ return NULL;
+ }
+
+ return item;
+}
+
+static size_t
+get_chunklen(fido_dev_t *dev)
+{
+ uint64_t maxchunklen;
+
+ if ((maxchunklen = fido_dev_maxmsgsize(dev)) > SIZE_MAX)
+ maxchunklen = SIZE_MAX;
+ if (maxchunklen > FIDO_MAXMSG)
+ maxchunklen = FIDO_MAXMSG;
+ maxchunklen = maxchunklen > 64 ? maxchunklen - 64 : 0;
+
+ return (size_t)maxchunklen;
+}
+
+static int
+largeblob_do_decode(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ largeblob_t *blob = arg;
+ uint64_t origsiz;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8) {
+ fido_log_debug("%s: cbor type", __func__);
+ return 0; /* ignore */
+ }
+
+ switch (cbor_get_uint8(key)) {
+ case 1: /* ciphertext */
+ if (fido_blob_decode(val, &blob->ciphertext) < 0 ||
+ blob->ciphertext.len < LARGEBLOB_TAG_LENGTH)
+ return -1;
+ return 0;
+ case 2: /* nonce */
+ if (fido_blob_decode(val, &blob->nonce) < 0 ||
+ blob->nonce.len != LARGEBLOB_NONCE_LENGTH)
+ return -1;
+ return 0;
+ case 3: /* origSize */
+ if (!cbor_isa_uint(val) ||
+ (origsiz = cbor_get_int(val)) > SIZE_MAX)
+ return -1;
+ blob->origsiz = (size_t)origsiz;
+ return 0;
+ default: /* ignore */
+ fido_log_debug("%s: cbor type", __func__);
+ return 0;
+ }
+}
+
+static int
+largeblob_decode(largeblob_t *blob, const cbor_item_t *item)
+{
+ if (!cbor_isa_map(item) || !cbor_map_is_definite(item)) {
+ fido_log_debug("%s: cbor type", __func__);
+ return -1;
+ }
+ if (cbor_map_iter(item, blob, largeblob_do_decode) < 0) {
+ fido_log_debug("%s: cbor_map_iter", __func__);
+ return -1;
+ }
+ if (fido_blob_is_empty(&blob->ciphertext) ||
+ fido_blob_is_empty(&blob->nonce) || blob->origsiz == 0) {
+ fido_log_debug("%s: incomplete blob", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static cbor_item_t *
+largeblob_encode(const fido_blob_t *body, const fido_blob_t *key)
+{
+ largeblob_t *blob;
+ cbor_item_t *argv[3], *item = NULL;
+
+ memset(argv, 0, sizeof(argv));
+ if ((blob = largeblob_new()) == NULL ||
+ largeblob_seal(blob, body, key) < 0) {
+ fido_log_debug("%s: largeblob_seal", __func__);
+ goto fail;
+ }
+ if ((argv[0] = fido_blob_encode(&blob->ciphertext)) == NULL ||
+ (argv[1] = fido_blob_encode(&blob->nonce)) == NULL ||
+ (argv[2] = cbor_build_uint(blob->origsiz)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+ item = cbor_flatten_vector(argv, nitems(argv));
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ largeblob_free(&blob);
+
+ return item;
+}
+
+static int
+largeblob_array_lookup(fido_blob_t *out, size_t *idx, const cbor_item_t *item,
+ const fido_blob_t *key)
+{
+ cbor_item_t **v;
+ fido_blob_t *plaintext = NULL;
+ largeblob_t blob;
+ int r;
+
+ memset(&blob, 0, sizeof(blob));
+ if (idx != NULL)
+ *idx = 0;
+ if ((v = cbor_array_handle(item)) == NULL)
+ return FIDO_ERR_INVALID_ARGUMENT;
+ for (size_t i = 0; i < cbor_array_size(item); i++) {
+ if (largeblob_decode(&blob, v[i]) < 0 ||
+ (plaintext = largeblob_decrypt(&blob, key)) == NULL) {
+ fido_log_debug("%s: largeblob_decode", __func__);
+ largeblob_reset(&blob);
+ continue;
+ }
+ if (idx != NULL)
+ *idx = i;
+ break;
+ }
+ if (plaintext == NULL) {
+ fido_log_debug("%s: not found", __func__);
+ return FIDO_ERR_NOTFOUND;
+ }
+ if (out != NULL)
+ r = fido_uncompress(out, plaintext, blob.origsiz);
+ else
+ r = FIDO_OK;
+
+ fido_blob_free(&plaintext);
+ largeblob_reset(&blob);
+
+ return r;
+}
+
+static int
+largeblob_array_digest(u_char out[LARGEBLOB_DIGEST_LENGTH], const u_char *data,
+ size_t len)
+{
+ u_char dgst[SHA256_DIGEST_LENGTH];
+
+ if (data == NULL || len == 0)
+ return -1;
+ if (SHA256(data, len, dgst) != dgst)
+ return -1;
+ memcpy(out, dgst, LARGEBLOB_DIGEST_LENGTH);
+
+ return 0;
+}
+
+static int
+largeblob_array_check(const fido_blob_t *array)
+{
+ u_char expected_hash[LARGEBLOB_DIGEST_LENGTH];
+ size_t body_len;
+
+ fido_log_xxd(array->ptr, array->len, __func__);
+ if (array->len < sizeof(expected_hash)) {
+ fido_log_debug("%s: len %zu", __func__, array->len);
+ return -1;
+ }
+ body_len = array->len - sizeof(expected_hash);
+ if (largeblob_array_digest(expected_hash, array->ptr, body_len) < 0) {
+ fido_log_debug("%s: largeblob_array_digest", __func__);
+ return -1;
+ }
+
+ return timingsafe_bcmp(expected_hash, array->ptr + body_len,
+ sizeof(expected_hash));
+}
+
+static int
+largeblob_get_array(fido_dev_t *dev, cbor_item_t **item, int *ms)
+{
+ fido_blob_t *array, *chunk = NULL;
+ size_t n;
+ int r;
+
+ *item = NULL;
+ if ((n = get_chunklen(dev)) == 0)
+ return FIDO_ERR_INVALID_ARGUMENT;
+ if ((array = fido_blob_new()) == NULL)
+ return FIDO_ERR_INTERNAL;
+ do {
+ fido_blob_free(&chunk);
+ if ((r = largeblob_get_tx(dev, array->len, n, ms)) != FIDO_OK ||
+ (r = largeblob_get_rx(dev, &chunk, ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_get_wait %zu/%zu",
+ __func__, array->len, n);
+ goto fail;
+ }
+ if (fido_blob_append(array, chunk->ptr, chunk->len) < 0) {
+ fido_log_debug("%s: fido_blob_append", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ } while (chunk->len == n);
+
+ if (largeblob_array_check(array) != 0)
+ *item = cbor_new_definite_array(0); /* per spec */
+ else
+ *item = largeblob_array_load(array->ptr, array->len);
+ if (*item == NULL)
+ r = FIDO_ERR_INTERNAL;
+ else
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&array);
+ fido_blob_free(&chunk);
+
+ return r;
+}
+
+static int
+prepare_hmac(size_t offset, const u_char *data, size_t len, fido_blob_t *hmac)
+{
+ uint8_t buf[32 + 2 + sizeof(uint32_t) + SHA256_DIGEST_LENGTH];
+ uint32_t u32_offset;
+
+ if (data == NULL || len == 0) {
+ fido_log_debug("%s: invalid data=%p, len=%zu", __func__,
+ (const void *)data, len);
+ return -1;
+ }
+ if (offset > UINT32_MAX) {
+ fido_log_debug("%s: invalid offset=%zu", __func__, offset);
+ return -1;
+ }
+
+ memset(buf, 0xff, 32);
+ buf[32] = CTAP_CBOR_LARGEBLOB;
+ buf[33] = 0x00;
+ u32_offset = htole32((uint32_t)offset);
+ memcpy(&buf[34], &u32_offset, sizeof(uint32_t));
+ if (SHA256(data, len, &buf[38]) != &buf[38]) {
+ fido_log_debug("%s: SHA256", __func__);
+ return -1;
+ }
+
+ return fido_blob_set(hmac, buf, sizeof(buf));
+}
+
+static int
+largeblob_set_tx(fido_dev_t *dev, const fido_blob_t *token, const u_char *chunk,
+ size_t chunk_len, size_t offset, size_t totalsiz, int *ms)
+{
+ fido_blob_t *hmac = NULL, f;
+ cbor_item_t *argv[6];
+ int r;
+
+ memset(argv, 0, sizeof(argv));
+ memset(&f, 0, sizeof(f));
+
+ if ((argv[1] = cbor_build_bytestring(chunk, chunk_len)) == NULL ||
+ (argv[2] = cbor_build_uint(offset)) == NULL ||
+ (offset == 0 && (argv[3] = cbor_build_uint(totalsiz)) == NULL)) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if (token != NULL) {
+ if ((hmac = fido_blob_new()) == NULL ||
+ prepare_hmac(offset, chunk, chunk_len, hmac) < 0 ||
+ (argv[4] = cbor_encode_pin_auth(dev, token, hmac)) == NULL ||
+ (argv[5] = cbor_encode_pin_opt(dev)) == NULL) {
+ fido_log_debug("%s: cbor_encode_pin_auth", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ }
+ if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ fido_blob_free(&hmac);
+ free(f.ptr);
+
+ return r;
+}
+
+static int
+largeblob_get_uv_token(fido_dev_t *dev, const char *pin, fido_blob_t **token,
+ int *ms)
+{
+ es256_pk_t *pk = NULL;
+ fido_blob_t *ecdh = NULL;
+ int r;
+
+ if ((*token = fido_blob_new()) == NULL)
+ return FIDO_ERR_INTERNAL;
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+ if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_LARGEBLOB, pin, ecdh, pk,
+ NULL, *token, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_get_uv_token", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (r != FIDO_OK)
+ fido_blob_free(token);
+
+ fido_blob_free(&ecdh);
+ es256_pk_free(&pk);
+
+ return r;
+}
+
+static int
+largeblob_set_array(fido_dev_t *dev, const cbor_item_t *item, const char *pin,
+ int *ms)
+{
+ unsigned char dgst[SHA256_DIGEST_LENGTH];
+ fido_blob_t cbor, *token = NULL;
+ size_t chunklen, maxchunklen, totalsize;
+ int r;
+
+ memset(&cbor, 0, sizeof(cbor));
+
+ if ((maxchunklen = get_chunklen(dev)) == 0) {
+ fido_log_debug("%s: maxchunklen=%zu", __func__, maxchunklen);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+ if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) {
+ fido_log_debug("%s: cbor type", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+ if ((fido_blob_serialise(&cbor, item)) < 0) {
+ fido_log_debug("%s: fido_blob_serialise", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if (cbor.len > SIZE_MAX - sizeof(dgst)) {
+ fido_log_debug("%s: cbor.len=%zu", __func__, cbor.len);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+ if (SHA256(cbor.ptr, cbor.len, dgst) != dgst) {
+ fido_log_debug("%s: SHA256", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ totalsize = cbor.len + sizeof(dgst) - 16; /* the first 16 bytes only */
+ if (pin != NULL || fido_dev_supports_permissions(dev)) {
+ if ((r = largeblob_get_uv_token(dev, pin, &token,
+ ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_get_uv_token", __func__);
+ goto fail;
+ }
+ }
+ for (size_t offset = 0; offset < cbor.len; offset += chunklen) {
+ if ((chunklen = cbor.len - offset) > maxchunklen)
+ chunklen = maxchunklen;
+ if ((r = largeblob_set_tx(dev, token, cbor.ptr + offset,
+ chunklen, offset, totalsize, ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: body", __func__);
+ goto fail;
+ }
+ }
+ if ((r = largeblob_set_tx(dev, token, dgst, sizeof(dgst) - 16, cbor.len,
+ totalsize, ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: dgst", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&token);
+ fido_blob_reset(&cbor);
+
+ return r;
+}
+
+static int
+largeblob_add(fido_dev_t *dev, const fido_blob_t *key, cbor_item_t *item,
+ const char *pin, int *ms)
+{
+ cbor_item_t *array = NULL;
+ size_t idx;
+ int r;
+
+ if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_get_array", __func__);
+ goto fail;
+ }
+
+ switch (r = largeblob_array_lookup(NULL, &idx, array, key)) {
+ case FIDO_OK:
+ if (!cbor_array_replace(array, idx, item)) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ break;
+ case FIDO_ERR_NOTFOUND:
+ if (cbor_array_append(&array, item) < 0) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ break;
+ default:
+ fido_log_debug("%s: largeblob_array_lookup", __func__);
+ goto fail;
+ }
+
+ if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_set_array", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (array != NULL)
+ cbor_decref(&array);
+
+ return r;
+}
+
+static int
+largeblob_drop(fido_dev_t *dev, const fido_blob_t *key, const char *pin,
+ int *ms)
+{
+ cbor_item_t *array = NULL;
+ size_t idx;
+ int r;
+
+ if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_get_array", __func__);
+ goto fail;
+ }
+ if ((r = largeblob_array_lookup(NULL, &idx, array, key)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_array_lookup", __func__);
+ goto fail;
+ }
+ if (cbor_array_drop(&array, idx) < 0) {
+ fido_log_debug("%s: cbor_array_drop", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_set_array", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (array != NULL)
+ cbor_decref(&array);
+
+ return r;
+}
+
+int
+fido_dev_largeblob_get(fido_dev_t *dev, const unsigned char *key_ptr,
+ size_t key_len, unsigned char **blob_ptr, size_t *blob_len)
+{
+ cbor_item_t *item = NULL;
+ fido_blob_t key, body;
+ int ms = dev->timeout_ms;
+ int r;
+
+ memset(&key, 0, sizeof(key));
+ memset(&body, 0, sizeof(body));
+
+ if (key_len != 32) {
+ fido_log_debug("%s: invalid key len %zu", __func__, key_len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if (blob_ptr == NULL || blob_len == NULL) {
+ fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%p", __func__,
+ (const void *)blob_ptr, (const void *)blob_len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ *blob_ptr = NULL;
+ *blob_len = 0;
+ if (fido_blob_set(&key, key_ptr, key_len) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_get_array", __func__);
+ goto fail;
+ }
+ if ((r = largeblob_array_lookup(&body, NULL, item, &key)) != FIDO_OK)
+ fido_log_debug("%s: largeblob_array_lookup", __func__);
+ else {
+ *blob_ptr = body.ptr;
+ *blob_len = body.len;
+ }
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ fido_blob_reset(&key);
+
+ return r;
+}
+
+int
+fido_dev_largeblob_set(fido_dev_t *dev, const unsigned char *key_ptr,
+ size_t key_len, const unsigned char *blob_ptr, size_t blob_len,
+ const char *pin)
+{
+ cbor_item_t *item = NULL;
+ fido_blob_t key, body;
+ int ms = dev->timeout_ms;
+ int r;
+
+ memset(&key, 0, sizeof(key));
+ memset(&body, 0, sizeof(body));
+
+ if (key_len != 32) {
+ fido_log_debug("%s: invalid key len %zu", __func__, key_len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if (blob_ptr == NULL || blob_len == 0) {
+ fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%zu", __func__,
+ (const void *)blob_ptr, blob_len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if (fido_blob_set(&key, key_ptr, key_len) < 0 ||
+ fido_blob_set(&body, blob_ptr, blob_len) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if ((item = largeblob_encode(&body, &key)) == NULL) {
+ fido_log_debug("%s: largeblob_encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+ if ((r = largeblob_add(dev, &key, item, pin, &ms)) != FIDO_OK)
+ fido_log_debug("%s: largeblob_add", __func__);
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ fido_blob_reset(&key);
+ fido_blob_reset(&body);
+
+ return r;
+}
+
+int
+fido_dev_largeblob_remove(fido_dev_t *dev, const unsigned char *key_ptr,
+ size_t key_len, const char *pin)
+{
+ fido_blob_t key;
+ int ms = dev->timeout_ms;
+ int r;
+
+ memset(&key, 0, sizeof(key));
+
+ if (key_len != 32) {
+ fido_log_debug("%s: invalid key len %zu", __func__, key_len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if (fido_blob_set(&key, key_ptr, key_len) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if ((r = largeblob_drop(dev, &key, pin, &ms)) != FIDO_OK)
+ fido_log_debug("%s: largeblob_drop", __func__);
+
+ fido_blob_reset(&key);
+
+ return r;
+}
+
+int
+fido_dev_largeblob_get_array(fido_dev_t *dev, unsigned char **cbor_ptr,
+ size_t *cbor_len)
+{
+ cbor_item_t *item = NULL;
+ fido_blob_t cbor;
+ int ms = dev->timeout_ms;
+ int r;
+
+ memset(&cbor, 0, sizeof(cbor));
+
+ if (cbor_ptr == NULL || cbor_len == NULL) {
+ fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%p", __func__,
+ (const void *)cbor_ptr, (const void *)cbor_len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ *cbor_ptr = NULL;
+ *cbor_len = 0;
+ if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) {
+ fido_log_debug("%s: largeblob_get_array", __func__);
+ return r;
+ }
+ if (fido_blob_serialise(&cbor, item) < 0) {
+ fido_log_debug("%s: fido_blob_serialise", __func__);
+ r = FIDO_ERR_INTERNAL;
+ } else {
+ *cbor_ptr = cbor.ptr;
+ *cbor_len = cbor.len;
+ }
+
+ cbor_decref(&item);
+
+ return r;
+}
+
+int
+fido_dev_largeblob_set_array(fido_dev_t *dev, const unsigned char *cbor_ptr,
+ size_t cbor_len, const char *pin)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor_result;
+ int ms = dev->timeout_ms;
+ int r;
+
+ if (cbor_ptr == NULL || cbor_len == 0) {
+ fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%zu", __func__,
+ (const void *)cbor_ptr, cbor_len);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if ((r = largeblob_set_array(dev, item, pin, &ms)) != FIDO_OK)
+ fido_log_debug("%s: largeblob_set_array", __func__);
+
+ cbor_decref(&item);
+
+ return r;
+}
diff --git a/src/libfido2.pc.in b/src/libfido2.pc.in
new file mode 100644
index 0000000..03d0606
--- /dev/null
+++ b/src/libfido2.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/include
+
+Name: @PROJECT_NAME@
+Description: A FIDO2 library
+URL: https://github.com/yubico/libfido2
+Version: @FIDO_VERSION@
+Requires: libcrypto
+Libs: -L${libdir} -lfido2
+Cflags: -I${includedir}
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..e54f8fc
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018-2021 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
+ */
+
+#undef _GNU_SOURCE /* XSI strerror_r() */
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "fido.h"
+
+#ifndef FIDO_NO_DIAGNOSTIC
+
+#define XXDLEN 32
+#define XXDROW 128
+#define LINELEN 256
+
+#ifndef TLS
+#define TLS
+#endif
+
+static TLS int logging;
+static TLS fido_log_handler_t *log_handler;
+
+static void
+log_on_stderr(const char *str)
+{
+ fprintf(stderr, "%s", str);
+}
+
+static void
+do_log(const char *suffix, const char *fmt, va_list args)
+{
+ char line[LINELEN], body[LINELEN];
+
+ vsnprintf(body, sizeof(body), fmt, args);
+
+ if (suffix != NULL)
+ snprintf(line, sizeof(line), "%.180s: %.70s\n", body, suffix);
+ else
+ snprintf(line, sizeof(line), "%.180s\n", body);
+
+ log_handler(line);
+}
+
+void
+fido_log_init(void)
+{
+ logging = 1;
+ log_handler = log_on_stderr;
+}
+
+void
+fido_log_debug(const char *fmt, ...)
+{
+ va_list args;
+
+ if (!logging || log_handler == NULL)
+ return;
+
+ va_start(args, fmt);
+ do_log(NULL, fmt, args);
+ va_end(args);
+}
+
+void
+fido_log_xxd(const void *buf, size_t count, const char *fmt, ...)
+{
+ const uint8_t *ptr = buf;
+ char row[XXDROW], xxd[XXDLEN];
+ va_list args;
+
+ if (!logging || log_handler == NULL)
+ return;
+
+ snprintf(row, sizeof(row), "buf=%p, len=%zu", buf, count);
+ va_start(args, fmt);
+ do_log(row, fmt, args);
+ va_end(args);
+ *row = '\0';
+
+ for (size_t i = 0; i < count; i++) {
+ *xxd = '\0';
+ if (i % 16 == 0)
+ snprintf(xxd, sizeof(xxd), "%04zu: %02x", i, *ptr++);
+ else
+ snprintf(xxd, sizeof(xxd), " %02x", *ptr++);
+ strlcat(row, xxd, sizeof(row));
+ if (i % 16 == 15 || i == count - 1) {
+ fido_log_debug("%s", row);
+ *row = '\0';
+ }
+ }
+}
+
+void
+fido_log_error(int errnum, const char *fmt, ...)
+{
+ char errstr[LINELEN];
+ va_list args;
+
+ if (!logging || log_handler == NULL)
+ return;
+ if (strerror_r(errnum, errstr, sizeof(errstr)) != 0)
+ snprintf(errstr, sizeof(errstr), "error %d", errnum);
+
+ va_start(args, fmt);
+ do_log(errstr, fmt, args);
+ va_end(args);
+}
+
+void
+fido_set_log_handler(fido_log_handler_t *handler)
+{
+ if (handler != NULL)
+ log_handler = handler;
+}
+
+#endif /* !FIDO_NO_DIAGNOSTIC */
diff --git a/src/netlink.c b/src/netlink.c
new file mode 100644
index 0000000..2a9216c
--- /dev/null
+++ b/src/netlink.c
@@ -0,0 +1,785 @@
+/*
+ * Copyright (c) 2020 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/socket.h>
+
+#include <linux/genetlink.h>
+#include <linux/netlink.h>
+#include <linux/nfc.h>
+
+#include <errno.h>
+#include <limits.h>
+
+#include "fido.h"
+#include "netlink.h"
+
+#ifdef FIDO_FUZZ
+static ssize_t (*fuzz_read)(int, void *, size_t);
+static ssize_t (*fuzz_write)(int, const void *, size_t);
+# define READ fuzz_read
+# define WRITE fuzz_write
+#else
+# define READ read
+# define WRITE write
+#endif
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+#define NETLINK_POLL_MS 100
+
+/* XXX avoid signed NLA_ALIGNTO */
+#undef NLA_HDRLEN
+#define NLA_HDRLEN NLMSG_ALIGN(sizeof(struct nlattr))
+
+typedef struct nlmsgbuf {
+ size_t siz; /* alloc size */
+ size_t len; /* of payload */
+ unsigned char *ptr; /* in payload */
+ union {
+ struct nlmsghdr nlmsg;
+ char buf[NLMSG_HDRLEN]; /* align */
+ } u;
+ unsigned char payload[];
+} nlmsgbuf_t;
+
+typedef struct genlmsgbuf {
+ union {
+ struct genlmsghdr genl;
+ char buf[GENL_HDRLEN]; /* align */
+ } u;
+} genlmsgbuf_t;
+
+typedef struct nlamsgbuf {
+ size_t siz; /* alloc size */
+ size_t len; /* of payload */
+ unsigned char *ptr; /* in payload */
+ union {
+ struct nlattr nla;
+ char buf[NLA_HDRLEN]; /* align */
+ } u;
+ unsigned char payload[];
+} nlamsgbuf_t;
+
+typedef struct nl_family {
+ uint16_t id;
+ uint32_t mcastgrp;
+} nl_family_t;
+
+typedef struct nl_poll {
+ uint32_t dev;
+ unsigned int eventcnt;
+} nl_poll_t;
+
+typedef struct nl_target {
+ int found;
+ uint32_t *value;
+} nl_target_t;
+
+static const void *
+nlmsg_ptr(const nlmsgbuf_t *m)
+{
+ return (&m->u.nlmsg);
+}
+
+static size_t
+nlmsg_len(const nlmsgbuf_t *m)
+{
+ return (m->u.nlmsg.nlmsg_len);
+}
+
+static uint16_t
+nlmsg_type(const nlmsgbuf_t *m)
+{
+ return (m->u.nlmsg.nlmsg_type);
+}
+
+static nlmsgbuf_t *
+nlmsg_new(uint16_t type, uint16_t flags, size_t len)
+{
+ nlmsgbuf_t *m;
+ size_t siz;
+
+ if (len > SIZE_MAX - sizeof(*m) ||
+ (siz = sizeof(*m) + len) > UINT16_MAX ||
+ (m = calloc(1, siz)) == NULL)
+ return (NULL);
+
+ m->siz = siz;
+ m->len = len;
+ m->ptr = m->payload;
+ m->u.nlmsg.nlmsg_type = type;
+ m->u.nlmsg.nlmsg_flags = NLM_F_REQUEST | flags;
+ m->u.nlmsg.nlmsg_len = NLMSG_HDRLEN;
+
+ return (m);
+}
+
+static nlamsgbuf_t *
+nla_from_buf(const unsigned char **ptr, size_t *len)
+{
+ nlamsgbuf_t h, *a;
+ size_t nlalen, skip;
+
+ if (*len < sizeof(h.u))
+ return (NULL);
+
+ memset(&h, 0, sizeof(h));
+ memcpy(&h.u, *ptr, sizeof(h.u));
+
+ if ((nlalen = h.u.nla.nla_len) < sizeof(h.u) || nlalen > *len ||
+ nlalen - sizeof(h.u) > UINT16_MAX ||
+ nlalen > SIZE_MAX - sizeof(*a) ||
+ (skip = NLMSG_ALIGN(nlalen)) > *len ||
+ (a = calloc(1, sizeof(*a) + nlalen - sizeof(h.u))) == NULL)
+ return (NULL);
+
+ memcpy(&a->u, *ptr, nlalen);
+ a->siz = sizeof(*a) + nlalen - sizeof(h.u);
+ a->ptr = a->payload;
+ a->len = nlalen - sizeof(h.u);
+ *ptr += skip;
+ *len -= skip;
+
+ return (a);
+}
+
+static nlamsgbuf_t *
+nla_getattr(nlamsgbuf_t *a)
+{
+ return (nla_from_buf((void *)&a->ptr, &a->len));
+}
+
+static uint16_t
+nla_type(const nlamsgbuf_t *a)
+{
+ return (a->u.nla.nla_type);
+}
+
+static nlamsgbuf_t *
+nlmsg_getattr(nlmsgbuf_t *m)
+{
+ return (nla_from_buf((void *)&m->ptr, &m->len));
+}
+
+static int
+nla_read(nlamsgbuf_t *a, void *buf, size_t cnt)
+{
+ if (cnt > a->u.nla.nla_len ||
+ fido_buf_read((void *)&a->ptr, &a->len, buf, cnt) < 0)
+ return (-1);
+
+ a->u.nla.nla_len = (uint16_t)(a->u.nla.nla_len - cnt);
+
+ return (0);
+}
+
+static nlmsgbuf_t *
+nlmsg_from_buf(const unsigned char **ptr, size_t *len)
+{
+ nlmsgbuf_t h, *m;
+ size_t msglen, skip;
+
+ if (*len < sizeof(h.u))
+ return (NULL);
+
+ memset(&h, 0, sizeof(h));
+ memcpy(&h.u, *ptr, sizeof(h.u));
+
+ if ((msglen = h.u.nlmsg.nlmsg_len) < sizeof(h.u) || msglen > *len ||
+ msglen - sizeof(h.u) > UINT16_MAX ||
+ (skip = NLMSG_ALIGN(msglen)) > *len ||
+ (m = nlmsg_new(0, 0, msglen - sizeof(h.u))) == NULL)
+ return (NULL);
+
+ memcpy(&m->u, *ptr, msglen);
+ *ptr += skip;
+ *len -= skip;
+
+ return (m);
+}
+
+static int
+nlmsg_read(nlmsgbuf_t *m, void *buf, size_t cnt)
+{
+ if (cnt > m->u.nlmsg.nlmsg_len ||
+ fido_buf_read((void *)&m->ptr, &m->len, buf, cnt) < 0)
+ return (-1);
+
+ m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len - cnt);
+
+ return (0);
+}
+
+static int
+nlmsg_write(nlmsgbuf_t *m, const void *buf, size_t cnt)
+{
+ if (cnt > UINT32_MAX - m->u.nlmsg.nlmsg_len ||
+ fido_buf_write(&m->ptr, &m->len, buf, cnt) < 0)
+ return (-1);
+
+ m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len + cnt);
+
+ return (0);
+}
+
+static int
+nlmsg_set_genl(nlmsgbuf_t *m, uint8_t cmd)
+{
+ genlmsgbuf_t g;
+
+ memset(&g, 0, sizeof(g));
+ g.u.genl.cmd = cmd;
+ g.u.genl.version = NFC_GENL_VERSION;
+
+ return (nlmsg_write(m, &g, sizeof(g)));
+}
+
+static int
+nlmsg_get_genl(nlmsgbuf_t *m, uint8_t cmd)
+{
+ genlmsgbuf_t g;
+
+ memset(&g, 0, sizeof(g));
+
+ if (nlmsg_read(m, &g, sizeof(g)) < 0 || g.u.genl.cmd != cmd)
+ return (-1);
+
+ return (0);
+}
+
+static int
+nlmsg_get_status(nlmsgbuf_t *m)
+{
+ int status;
+
+ if (nlmsg_read(m, &status, sizeof(status)) < 0 || status == INT_MIN)
+ return (-1);
+ if (status < 0)
+ status = -status;
+
+ return (status);
+}
+
+static int
+nlmsg_setattr(nlmsgbuf_t *m, uint16_t type, const void *ptr, size_t len)
+{
+ int r;
+ char *padding;
+ size_t skip;
+ nlamsgbuf_t a;
+
+ if ((skip = NLMSG_ALIGN(len)) > UINT16_MAX - sizeof(a.u) ||
+ skip < len || (padding = calloc(1, skip - len)) == NULL)
+ return (-1);
+
+ memset(&a, 0, sizeof(a));
+ a.u.nla.nla_type = type;
+ a.u.nla.nla_len = (uint16_t)(len + sizeof(a.u));
+ r = nlmsg_write(m, &a.u, sizeof(a.u)) < 0 ||
+ nlmsg_write(m, ptr, len) < 0 ||
+ nlmsg_write(m, padding, skip - len) < 0 ? -1 : 0;
+
+ free(padding);
+
+ return (r);
+}
+
+static int
+nlmsg_set_u16(nlmsgbuf_t *m, uint16_t type, uint16_t val)
+{
+ return (nlmsg_setattr(m, type, &val, sizeof(val)));
+}
+
+static int
+nlmsg_set_u32(nlmsgbuf_t *m, uint16_t type, uint32_t val)
+{
+ return (nlmsg_setattr(m, type, &val, sizeof(val)));
+}
+
+static int
+nlmsg_set_str(nlmsgbuf_t *m, uint16_t type, const char *val)
+{
+ return (nlmsg_setattr(m, type, val, strlen(val) + 1));
+}
+
+static int
+nla_get_u16(nlamsgbuf_t *a, uint16_t *v)
+{
+ return (nla_read(a, v, sizeof(*v)));
+}
+
+static int
+nla_get_u32(nlamsgbuf_t *a, uint32_t *v)
+{
+ return (nla_read(a, v, sizeof(*v)));
+}
+
+static char *
+nla_get_str(nlamsgbuf_t *a)
+{
+ size_t n;
+ char *s = NULL;
+
+ if ((n = a->len) < 1 || a->ptr[n - 1] != '\0' ||
+ (s = calloc(1, n)) == NULL || nla_read(a, s, n) < 0) {
+ free(s);
+ return (NULL);
+ }
+ s[n - 1] = '\0';
+
+ return (s);
+}
+
+static int
+nlmsg_tx(int fd, const nlmsgbuf_t *m)
+{
+ ssize_t r;
+
+ if ((r = WRITE(fd, nlmsg_ptr(m), nlmsg_len(m))) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return (-1);
+ }
+ if (r < 0 || (size_t)r != nlmsg_len(m)) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, nlmsg_len(m));
+ return (-1);
+ }
+ fido_log_xxd(nlmsg_ptr(m), nlmsg_len(m), "%s", __func__);
+
+ return (0);
+}
+
+static ssize_t
+nlmsg_rx(int fd, unsigned char *ptr, size_t len, int ms)
+{
+ ssize_t r;
+
+ if (len > SSIZE_MAX) {
+ fido_log_debug("%s: len", __func__);
+ return (-1);
+ }
+ if (fido_hid_unix_wait(fd, ms, NULL) < 0) {
+ fido_log_debug("%s: fido_hid_unix_wait", __func__);
+ return (-1);
+ }
+ if ((r = READ(fd, ptr, len)) == -1) {
+ fido_log_error(errno, "%s: read %zd", __func__, r);
+ return (-1);
+ }
+ fido_log_xxd(ptr, (size_t)r, "%s", __func__);
+
+ return (r);
+}
+
+static int
+nlmsg_iter(nlmsgbuf_t *m, void *arg, int (*parser)(nlamsgbuf_t *, void *))
+{
+ nlamsgbuf_t *a;
+ int r;
+
+ while ((a = nlmsg_getattr(m)) != NULL) {
+ r = parser(a, arg);
+ free(a);
+ if (r < 0) {
+ fido_log_debug("%s: parser", __func__);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int
+nla_iter(nlamsgbuf_t *g, void *arg, int (*parser)(nlamsgbuf_t *, void *))
+{
+ nlamsgbuf_t *a;
+ int r;
+
+ while ((a = nla_getattr(g)) != NULL) {
+ r = parser(a, arg);
+ free(a);
+ if (r < 0) {
+ fido_log_debug("%s: parser", __func__);
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int
+nl_parse_reply(const uint8_t *blob, size_t blob_len, uint16_t msg_type,
+ uint8_t genl_cmd, void *arg, int (*parser)(nlamsgbuf_t *, void *))
+{
+ nlmsgbuf_t *m;
+ int r;
+
+ while (blob_len) {
+ if ((m = nlmsg_from_buf(&blob, &blob_len)) == NULL) {
+ fido_log_debug("%s: nlmsg", __func__);
+ return (-1);
+ }
+ if (nlmsg_type(m) == NLMSG_ERROR) {
+ r = nlmsg_get_status(m);
+ free(m);
+ return (r);
+ }
+ if (nlmsg_type(m) != msg_type ||
+ nlmsg_get_genl(m, genl_cmd) < 0) {
+ fido_log_debug("%s: skipping", __func__);
+ free(m);
+ continue;
+ }
+ if (parser != NULL && nlmsg_iter(m, arg, parser) < 0) {
+ fido_log_debug("%s: nlmsg_iter", __func__);
+ free(m);
+ return (-1);
+ }
+ free(m);
+ }
+
+ return (0);
+}
+
+static int
+parse_mcastgrp(nlamsgbuf_t *a, void *arg)
+{
+ nl_family_t *family = arg;
+ char *name;
+
+ switch (nla_type(a)) {
+ case CTRL_ATTR_MCAST_GRP_NAME:
+ if ((name = nla_get_str(a)) == NULL ||
+ strcmp(name, NFC_GENL_MCAST_EVENT_NAME) != 0) {
+ free(name);
+ return (-1); /* XXX skip? */
+ }
+ free(name);
+ return (0);
+ case CTRL_ATTR_MCAST_GRP_ID:
+ if (family->mcastgrp)
+ break;
+ if (nla_get_u32(a, &family->mcastgrp) < 0) {
+ fido_log_debug("%s: group", __func__);
+ return (-1);
+ }
+ return (0);
+ }
+
+ fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
+
+ return (0);
+}
+
+static int
+parse_mcastgrps(nlamsgbuf_t *a, void *arg)
+{
+ return (nla_iter(a, arg, parse_mcastgrp));
+}
+
+static int
+parse_family(nlamsgbuf_t *a, void *arg)
+{
+ nl_family_t *family = arg;
+
+ switch (nla_type(a)) {
+ case CTRL_ATTR_FAMILY_ID:
+ if (family->id)
+ break;
+ if (nla_get_u16(a, &family->id) < 0) {
+ fido_log_debug("%s: id", __func__);
+ return (-1);
+ }
+ return (0);
+ case CTRL_ATTR_MCAST_GROUPS:
+ return (nla_iter(a, family, parse_mcastgrps));
+ }
+
+ fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
+
+ return (0);
+}
+
+static int
+nl_get_nfc_family(int fd, uint16_t *type, uint32_t *mcastgrp)
+{
+ nlmsgbuf_t *m;
+ uint8_t reply[512];
+ nl_family_t family;
+ ssize_t r;
+ int ok;
+
+ if ((m = nlmsg_new(GENL_ID_CTRL, 0, 64)) == NULL ||
+ nlmsg_set_genl(m, CTRL_CMD_GETFAMILY) < 0 ||
+ nlmsg_set_u16(m, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL) < 0 ||
+ nlmsg_set_str(m, CTRL_ATTR_FAMILY_NAME, NFC_GENL_NAME) < 0 ||
+ nlmsg_tx(fd, m) < 0) {
+ free(m);
+ return (-1);
+ }
+ free(m);
+ memset(&family, 0, sizeof(family));
+ if ((r = nlmsg_rx(fd, reply, sizeof(reply), -1)) < 0) {
+ fido_log_debug("%s: nlmsg_rx", __func__);
+ return (-1);
+ }
+ if ((ok = nl_parse_reply(reply, (size_t)r, GENL_ID_CTRL,
+ CTRL_CMD_NEWFAMILY, &family, parse_family)) != 0) {
+ fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
+ return (-1);
+ }
+ if (family.id == 0 || family.mcastgrp == 0) {
+ fido_log_debug("%s: missing attr", __func__);
+ return (-1);
+ }
+ *type = family.id;
+ *mcastgrp = family.mcastgrp;
+
+ return (0);
+}
+
+static int
+parse_target(nlamsgbuf_t *a, void *arg)
+{
+ nl_target_t *t = arg;
+
+ if (t->found || nla_type(a) != NFC_ATTR_TARGET_INDEX) {
+ fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
+ return (0);
+ }
+ if (nla_get_u32(a, t->value) < 0) {
+ fido_log_debug("%s: target", __func__);
+ return (-1);
+ }
+ t->found = 1;
+
+ return (0);
+}
+
+int
+fido_nl_power_nfc(fido_nl_t *nl, uint32_t dev)
+{
+ nlmsgbuf_t *m;
+ uint8_t reply[512];
+ ssize_t r;
+ int ok;
+
+ if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL ||
+ nlmsg_set_genl(m, NFC_CMD_DEV_UP) < 0 ||
+ nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 ||
+ nlmsg_tx(nl->fd, m) < 0) {
+ free(m);
+ return (-1);
+ }
+ free(m);
+ if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) {
+ fido_log_debug("%s: nlmsg_rx", __func__);
+ return (-1);
+ }
+ if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
+ NFC_CMD_DEV_UP, NULL, NULL)) != 0 && ok != EALREADY) {
+ fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+nl_nfc_poll(fido_nl_t *nl, uint32_t dev)
+{
+ nlmsgbuf_t *m;
+ uint8_t reply[512];
+ ssize_t r;
+ int ok;
+
+ if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL ||
+ nlmsg_set_genl(m, NFC_CMD_START_POLL) < 0 ||
+ nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 ||
+ nlmsg_set_u32(m, NFC_ATTR_PROTOCOLS, NFC_PROTO_ISO14443_MASK) < 0 ||
+ nlmsg_tx(nl->fd, m) < 0) {
+ free(m);
+ return (-1);
+ }
+ free(m);
+ if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) {
+ fido_log_debug("%s: nlmsg_rx", __func__);
+ return (-1);
+ }
+ if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
+ NFC_CMD_START_POLL, NULL, NULL)) != 0) {
+ fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+nl_dump_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target, int ms)
+{
+ nlmsgbuf_t *m;
+ nl_target_t t;
+ uint8_t reply[512];
+ ssize_t r;
+ int ok;
+
+ if ((m = nlmsg_new(nl->nfc_type, NLM_F_DUMP, 64)) == NULL ||
+ nlmsg_set_genl(m, NFC_CMD_GET_TARGET) < 0 ||
+ nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 ||
+ nlmsg_tx(nl->fd, m) < 0) {
+ free(m);
+ return (-1);
+ }
+ free(m);
+ if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), ms)) < 0) {
+ fido_log_debug("%s: nlmsg_rx", __func__);
+ return (-1);
+ }
+ memset(&t, 0, sizeof(t));
+ t.value = target;
+ if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
+ NFC_CMD_GET_TARGET, &t, parse_target)) != 0) {
+ fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
+ return (-1);
+ }
+ if (!t.found) {
+ fido_log_debug("%s: target not found", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+parse_nfc_event(nlamsgbuf_t *a, void *arg)
+{
+ nl_poll_t *ctx = arg;
+ uint32_t dev;
+
+ if (nla_type(a) != NFC_ATTR_DEVICE_INDEX) {
+ fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a));
+ return (0);
+ }
+ if (nla_get_u32(a, &dev) < 0) {
+ fido_log_debug("%s: dev", __func__);
+ return (-1);
+ }
+ if (dev == ctx->dev)
+ ctx->eventcnt++;
+ else
+ fido_log_debug("%s: ignoring dev 0x%x", __func__, dev);
+
+ return (0);
+}
+
+int
+fido_nl_get_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target)
+{
+ uint8_t reply[512];
+ nl_poll_t ctx;
+ ssize_t r;
+ int ok;
+
+ if (nl_nfc_poll(nl, dev) < 0) {
+ fido_log_debug("%s: nl_nfc_poll", __func__);
+ return (-1);
+ }
+#ifndef FIDO_FUZZ
+ if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+ &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) {
+ fido_log_error(errno, "%s: setsockopt add", __func__);
+ return (-1);
+ }
+#endif
+ r = nlmsg_rx(nl->fd, reply, sizeof(reply), NETLINK_POLL_MS);
+#ifndef FIDO_FUZZ
+ if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP,
+ &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) {
+ fido_log_error(errno, "%s: setsockopt drop", __func__);
+ return (-1);
+ }
+#endif
+ if (r < 0) {
+ fido_log_debug("%s: nlmsg_rx", __func__);
+ return (-1);
+ }
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.dev = dev;
+ if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type,
+ NFC_EVENT_TARGETS_FOUND, &ctx, parse_nfc_event)) != 0) {
+ fido_log_debug("%s: nl_parse_reply: %d", __func__, ok);
+ return (-1);
+ }
+ if (ctx.eventcnt == 0) {
+ fido_log_debug("%s: dev 0x%x not observed", __func__, dev);
+ return (-1);
+ }
+ if (nl_dump_nfc_target(nl, dev, target, -1) < 0) {
+ fido_log_debug("%s: nl_dump_nfc_target", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+void
+fido_nl_free(fido_nl_t **nlp)
+{
+ fido_nl_t *nl;
+
+ if (nlp == NULL || (nl = *nlp) == NULL)
+ return;
+ if (nl->fd != -1 && close(nl->fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+
+ free(nl);
+ *nlp = NULL;
+}
+
+fido_nl_t *
+fido_nl_new(void)
+{
+ fido_nl_t *nl;
+ int ok = -1;
+
+ if ((nl = calloc(1, sizeof(*nl))) == NULL)
+ return (NULL);
+ if ((nl->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC,
+ NETLINK_GENERIC)) == -1) {
+ fido_log_error(errno, "%s: socket", __func__);
+ goto fail;
+ }
+ nl->saddr.nl_family = AF_NETLINK;
+ if (bind(nl->fd, (struct sockaddr *)&nl->saddr,
+ sizeof(nl->saddr)) == -1) {
+ fido_log_error(errno, "%s: bind", __func__);
+ goto fail;
+ }
+ if (nl_get_nfc_family(nl->fd, &nl->nfc_type, &nl->nfc_mcastgrp) < 0) {
+ fido_log_debug("%s: nl_get_nfc_family", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (ok < 0)
+ fido_nl_free(&nl);
+
+ return (nl);
+}
+
+#ifdef FIDO_FUZZ
+void
+set_netlink_io_functions(ssize_t (*read_f)(int, void *, size_t),
+ ssize_t (*write_f)(int, const void *, size_t))
+{
+ fuzz_read = read_f;
+ fuzz_write = write_f;
+}
+#endif
diff --git a/src/netlink.h b/src/netlink.h
new file mode 100644
index 0000000..c600b52
--- /dev/null
+++ b/src/netlink.h
@@ -0,0 +1,45 @@
+/*
+ * 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
+ */
+
+#ifndef _FIDO_NETLINK_H
+#define _FIDO_NETLINK_H
+
+#include <sys/socket.h>
+
+#include <linux/genetlink.h>
+#include <linux/netlink.h>
+#include <linux/nfc.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct fido_nl {
+ int fd;
+ uint16_t nfc_type;
+ uint32_t nfc_mcastgrp;
+ struct sockaddr_nl saddr;
+} fido_nl_t;
+
+fido_nl_t *fido_nl_new(void);
+void fido_nl_free(struct fido_nl **);
+int fido_nl_power_nfc(struct fido_nl *, uint32_t);
+int fido_nl_get_nfc_target(struct fido_nl *, uint32_t , uint32_t *);
+
+#ifdef FIDO_FUZZ
+void set_netlink_io_functions(ssize_t (*)(int, void *, size_t),
+ ssize_t (*)(int, const void *, size_t));
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_FIDO_NETLINK_H */
diff --git a/src/nfc.c b/src/nfc.c
new file mode 100644
index 0000000..2e97d5f
--- /dev/null
+++ b/src/nfc.c
@@ -0,0 +1,350 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+
+#include "fido.h"
+#include "fido/param.h"
+#include "iso7816.h"
+
+#define TX_CHUNK_SIZE 240
+
+static const uint8_t aid[] = { 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01 };
+static const uint8_t v_u2f[] = { 'U', '2', 'F', '_', 'V', '2' };
+static const uint8_t v_fido[] = { 'F', 'I', 'D', 'O', '_', '2', '_', '0' };
+
+static int
+tx_short_apdu(fido_dev_t *d, const iso7816_header_t *h, const uint8_t *payload,
+ uint8_t payload_len, uint8_t cla_flags)
+{
+ uint8_t apdu[5 + UINT8_MAX + 1];
+ uint8_t sw[2];
+ size_t apdu_len;
+ int ok = -1;
+
+ memset(&apdu, 0, sizeof(apdu));
+ apdu[0] = h->cla | cla_flags;
+ apdu[1] = h->ins;
+ apdu[2] = h->p1;
+ apdu[3] = h->p2;
+ apdu[4] = payload_len;
+ memcpy(&apdu[5], payload, payload_len);
+ apdu_len = (size_t)(5 + payload_len + 1);
+
+ if (d->io.write(d->io_handle, apdu, apdu_len) < 0) {
+ fido_log_debug("%s: write", __func__);
+ goto fail;
+ }
+
+ if (cla_flags & 0x10) {
+ if (d->io.read(d->io_handle, sw, sizeof(sw), -1) != 2) {
+ fido_log_debug("%s: read", __func__);
+ goto fail;
+ }
+ if ((sw[0] << 8 | sw[1]) != SW_NO_ERROR) {
+ fido_log_debug("%s: unexpected sw", __func__);
+ goto fail;
+ }
+ }
+
+ ok = 0;
+fail:
+ explicit_bzero(apdu, sizeof(apdu));
+
+ return ok;
+}
+
+static int
+nfc_do_tx(fido_dev_t *d, const uint8_t *apdu_ptr, size_t apdu_len)
+{
+ iso7816_header_t h;
+
+ if (fido_buf_read(&apdu_ptr, &apdu_len, &h, sizeof(h)) < 0) {
+ fido_log_debug("%s: header", __func__);
+ return -1;
+ }
+ if (apdu_len < 2) {
+ fido_log_debug("%s: apdu_len %zu", __func__, apdu_len);
+ return -1;
+ }
+
+ apdu_len -= 2; /* trim le1 le2 */
+
+ while (apdu_len > TX_CHUNK_SIZE) {
+ if (tx_short_apdu(d, &h, apdu_ptr, TX_CHUNK_SIZE, 0x10) < 0) {
+ fido_log_debug("%s: chain", __func__);
+ return -1;
+ }
+ apdu_ptr += TX_CHUNK_SIZE;
+ apdu_len -= TX_CHUNK_SIZE;
+ }
+
+ if (tx_short_apdu(d, &h, apdu_ptr, (uint8_t)apdu_len, 0) < 0) {
+ fido_log_debug("%s: tx_short_apdu", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+fido_nfc_tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count)
+{
+ iso7816_apdu_t *apdu = NULL;
+ const uint8_t *ptr;
+ size_t len;
+ int ok = -1;
+
+ switch (cmd) {
+ case CTAP_CMD_INIT: /* select */
+ if ((apdu = iso7816_new(0, 0xa4, 0x04, sizeof(aid))) == NULL ||
+ iso7816_add(apdu, aid, sizeof(aid)) < 0) {
+ fido_log_debug("%s: iso7816", __func__);
+ goto fail;
+ }
+ break;
+ case CTAP_CMD_CBOR: /* wrap cbor */
+ if (count > UINT16_MAX || (apdu = iso7816_new(0x80, 0x10, 0x00,
+ (uint16_t)count)) == NULL ||
+ iso7816_add(apdu, buf, count) < 0) {
+ fido_log_debug("%s: iso7816", __func__);
+ goto fail;
+ }
+ break;
+ case CTAP_CMD_MSG: /* already an apdu */
+ break;
+ default:
+ fido_log_debug("%s: cmd=%02x", __func__, cmd);
+ goto fail;
+ }
+
+ if (apdu != NULL) {
+ ptr = iso7816_ptr(apdu);
+ len = iso7816_len(apdu);
+ } else {
+ ptr = buf;
+ len = count;
+ }
+
+ if (nfc_do_tx(d, ptr, len) < 0) {
+ fido_log_debug("%s: nfc_do_tx", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ iso7816_free(&apdu);
+
+ return ok;
+}
+
+static int
+rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
+{
+ fido_ctap_info_t *attr = (fido_ctap_info_t *)buf;
+ uint8_t f[64];
+ int n;
+
+ if (count != sizeof(*attr)) {
+ fido_log_debug("%s: count=%zu", __func__, count);
+ return -1;
+ }
+
+ memset(attr, 0, sizeof(*attr));
+
+ if ((n = d->io.read(d->io_handle, f, sizeof(f), ms)) < 2 ||
+ (f[n - 2] << 8 | f[n - 1]) != SW_NO_ERROR) {
+ fido_log_debug("%s: read", __func__);
+ return -1;
+ }
+
+ n -= 2;
+
+ if (n == sizeof(v_u2f) && memcmp(f, v_u2f, sizeof(v_u2f)) == 0)
+ attr->flags = FIDO_CAP_CBOR;
+ else if (n == sizeof(v_fido) && memcmp(f, v_fido, sizeof(v_fido)) == 0)
+ attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG;
+ else {
+ fido_log_debug("%s: unknown version string", __func__);
+#ifdef FIDO_FUZZ
+ attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG;
+#else
+ return -1;
+#endif
+ }
+
+ memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce)); /* XXX */
+
+ return (int)count;
+}
+
+static int
+tx_get_response(fido_dev_t *d, uint8_t count)
+{
+ uint8_t apdu[5];
+
+ memset(apdu, 0, sizeof(apdu));
+ apdu[1] = 0xc0; /* GET_RESPONSE */
+ apdu[4] = count;
+
+ if (d->io.write(d->io_handle, apdu, sizeof(apdu)) < 0) {
+ fido_log_debug("%s: write", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+rx_apdu(fido_dev_t *d, uint8_t sw[2], unsigned char **buf, size_t *count, int *ms)
+{
+ uint8_t f[256 + 2];
+ struct timespec ts;
+ int n, ok = -1;
+
+ if (fido_time_now(&ts) != 0)
+ goto fail;
+
+ if ((n = d->io.read(d->io_handle, f, sizeof(f), *ms)) < 2) {
+ fido_log_debug("%s: read", __func__);
+ goto fail;
+ }
+
+ if (fido_time_delta(&ts, ms) != 0)
+ goto fail;
+
+ if (fido_buf_write(buf, count, f, (size_t)(n - 2)) < 0) {
+ fido_log_debug("%s: fido_buf_write", __func__);
+ goto fail;
+ }
+
+ memcpy(sw, f + n - 2, 2);
+
+ ok = 0;
+fail:
+ explicit_bzero(f, sizeof(f));
+
+ return ok;
+}
+
+static int
+rx_msg(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
+{
+ uint8_t sw[2];
+ const size_t bufsiz = count;
+
+ if (rx_apdu(d, sw, &buf, &count, &ms) < 0) {
+ fido_log_debug("%s: preamble", __func__);
+ return -1;
+ }
+
+ while (sw[0] == SW1_MORE_DATA)
+ if (tx_get_response(d, sw[1]) < 0 ||
+ rx_apdu(d, sw, &buf, &count, &ms) < 0) {
+ fido_log_debug("%s: chain", __func__);
+ return -1;
+ }
+
+ if (fido_buf_write(&buf, &count, sw, sizeof(sw)) < 0) {
+ fido_log_debug("%s: sw", __func__);
+ return -1;
+ }
+
+ if (bufsiz - count > INT_MAX) {
+ fido_log_debug("%s: bufsiz", __func__);
+ return -1;
+ }
+
+ return (int)(bufsiz - count);
+}
+
+static int
+rx_cbor(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
+{
+ int r;
+
+ if ((r = rx_msg(d, buf, count, ms)) < 2)
+ return -1;
+
+ return r - 2;
+}
+
+int
+fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms)
+{
+ switch (cmd) {
+ case CTAP_CMD_INIT:
+ return rx_init(d, buf, count, ms);
+ case CTAP_CMD_CBOR:
+ return rx_cbor(d, buf, count, ms);
+ case CTAP_CMD_MSG:
+ return rx_msg(d, buf, count, ms);
+ default:
+ fido_log_debug("%s: cmd=%02x", __func__, cmd);
+ return -1;
+ }
+}
+
+bool
+nfc_is_fido(const char *path)
+{
+ bool fido = false;
+ fido_dev_t *d;
+ int r;
+
+ if ((d = fido_dev_new()) == NULL) {
+ fido_log_debug("%s: fido_dev_new", __func__);
+ goto fail;
+ }
+ /* fido_dev_open selects the fido applet */
+ if ((r = fido_dev_open(d, path)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_open: 0x%x", __func__, r);
+ goto fail;
+ }
+ if ((r = fido_dev_close(d)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_close: 0x%x", __func__, r);
+ goto fail;
+
+ }
+
+ fido = true;
+fail:
+ fido_dev_free(&d);
+
+ return fido;
+}
+
+#ifdef USE_NFC
+bool
+fido_is_nfc(const char *path)
+{
+ return strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) == 0;
+}
+
+int
+fido_dev_set_nfc(fido_dev_t *d)
+{
+ if (d->io_handle != NULL) {
+ fido_log_debug("%s: device open", __func__);
+ return -1;
+ }
+ d->io_own = true;
+ d->io = (fido_dev_io_t) {
+ fido_nfc_open,
+ fido_nfc_close,
+ fido_nfc_read,
+ fido_nfc_write,
+ };
+ d->transport = (fido_dev_transport_t) {
+ fido_nfc_rx,
+ fido_nfc_tx,
+ };
+
+ return 0;
+}
+#endif /* USE_NFC */
diff --git a/src/nfc_linux.c b/src/nfc_linux.c
new file mode 100644
index 0000000..4b69eb1
--- /dev/null
+++ b/src/nfc_linux.c
@@ -0,0 +1,356 @@
+/*
+ * 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/uio.h>
+#include <sys/socket.h>
+
+#include <linux/nfc.h>
+
+#include <errno.h>
+#include <libudev.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fido.h"
+#include "fido/param.h"
+#include "netlink.h"
+#include "iso7816.h"
+
+struct nfc_linux {
+ int fd;
+ uint32_t dev;
+ uint32_t target;
+ sigset_t sigmask;
+ const sigset_t *sigmaskp;
+ struct fido_nl *nl;
+};
+
+static char *
+get_parent_attr(struct udev_device *dev, const char *subsystem,
+ const char *devtype, const char *attr)
+{
+ struct udev_device *parent;
+ const char *value;
+
+ if ((parent = udev_device_get_parent_with_subsystem_devtype(dev,
+ subsystem, devtype)) == NULL || (value =
+ udev_device_get_sysattr_value(parent, attr)) == NULL)
+ return NULL;
+
+ return strdup(value);
+}
+
+static char *
+get_usb_attr(struct udev_device *dev, const char *attr)
+{
+ return get_parent_attr(dev, "usb", "usb_device", attr);
+}
+
+static int
+copy_info(fido_dev_info_t *di, struct udev *udev,
+ struct udev_list_entry *udev_entry)
+{
+ const char *name;
+ char *str;
+ struct udev_device *dev = NULL;
+ uint64_t id;
+ int ok = -1;
+
+ memset(di, 0, sizeof(*di));
+
+ if ((name = udev_list_entry_get_name(udev_entry)) == NULL ||
+ (dev = udev_device_new_from_syspath(udev, name)) == NULL)
+ goto fail;
+ if (asprintf(&di->path, "%s/%s", FIDO_NFC_PREFIX, name) == -1) {
+ di->path = NULL;
+ goto fail;
+ }
+ if (nfc_is_fido(di->path) == false) {
+ fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path);
+ goto fail;
+ }
+ if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL)
+ di->manufacturer = strdup("");
+ if ((di->product = get_usb_attr(dev, "product")) == NULL)
+ di->product = strdup("");
+ if (di->manufacturer == NULL || di->product == NULL)
+ goto fail;
+ /* XXX assumes USB for vendor/product info */
+ if ((str = get_usb_attr(dev, "idVendor")) != NULL &&
+ fido_to_uint64(str, 16, &id) == 0 && id <= UINT16_MAX)
+ di->vendor_id = (int16_t)id;
+ free(str);
+ if ((str = get_usb_attr(dev, "idProduct")) != NULL &&
+ fido_to_uint64(str, 16, &id) == 0 && id <= UINT16_MAX)
+ di->product_id = (int16_t)id;
+ free(str);
+
+ ok = 0;
+fail:
+ if (dev != NULL)
+ udev_device_unref(dev);
+
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return ok;
+}
+
+static int
+sysnum_from_syspath(const char *path)
+{
+ struct udev *udev = NULL;
+ struct udev_device *dev = NULL;
+ const char *str;
+ uint64_t idx64;
+ int idx = -1;
+
+ if ((udev = udev_new()) != NULL &&
+ (dev = udev_device_new_from_syspath(udev, path)) != NULL &&
+ (str = udev_device_get_sysnum(dev)) != NULL &&
+ fido_to_uint64(str, 10, &idx64) == 0 && idx64 < INT_MAX)
+ idx = (int)idx64;
+
+ if (dev != NULL)
+ udev_device_unref(dev);
+ if (udev != NULL)
+ udev_unref(udev);
+
+ return idx;
+}
+
+int
+fido_nfc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ struct udev *udev = NULL;
+ struct udev_enumerate *udev_enum = NULL;
+ struct udev_list_entry *udev_list;
+ struct udev_list_entry *udev_entry;
+ int r = FIDO_ERR_INTERNAL;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return FIDO_OK;
+
+ if (devlist == NULL)
+ return FIDO_ERR_INVALID_ARGUMENT;
+
+ if ((udev = udev_new()) == NULL ||
+ (udev_enum = udev_enumerate_new(udev)) == NULL)
+ goto fail;
+
+ if (udev_enumerate_add_match_subsystem(udev_enum, "nfc") < 0 ||
+ udev_enumerate_scan_devices(udev_enum) < 0)
+ goto fail;
+
+ if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) {
+ r = FIDO_OK; /* zero nfc devices */
+ goto fail;
+ }
+
+ udev_list_entry_foreach(udev_entry, udev_list) {
+ if (copy_info(&devlist[*olen], udev, udev_entry) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_nfc_open,
+ fido_nfc_close,
+ fido_nfc_read,
+ fido_nfc_write,
+ };
+ devlist[*olen].transport = (fido_dev_transport_t) {
+ fido_nfc_rx,
+ fido_nfc_tx,
+ };
+ if (++(*olen) == ilen)
+ break;
+ }
+ }
+
+ r = FIDO_OK;
+fail:
+ if (udev_enum != NULL)
+ udev_enumerate_unref(udev_enum);
+ if (udev != NULL)
+ udev_unref(udev);
+
+ return r;
+}
+
+static int
+nfc_target_connect(struct nfc_linux *ctx)
+{
+ struct sockaddr_nfc sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_family = AF_NFC;
+ sa.dev_idx = ctx->dev;
+ sa.target_idx = ctx->target;
+ sa.nfc_protocol = NFC_PROTO_ISO14443;
+
+ if ((ctx->fd = socket(AF_NFC, SOCK_SEQPACKET | SOCK_CLOEXEC,
+ NFC_SOCKPROTO_RAW)) == -1) {
+ fido_log_error(errno, "%s: socket", __func__);
+ return -1;
+ }
+ if (connect(ctx->fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) {
+ fido_log_error(errno, "%s: connect", __func__);
+ if (close(ctx->fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+ ctx->fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+nfc_free(struct nfc_linux **ctx_p)
+{
+ struct nfc_linux *ctx;
+
+ if (ctx_p == NULL || (ctx = *ctx_p) == NULL)
+ return;
+ if (ctx->fd != -1 && close(ctx->fd) == -1)
+ fido_log_error(errno, "%s: close", __func__);
+ if (ctx->nl != NULL)
+ fido_nl_free(&ctx->nl);
+
+ free(ctx);
+ *ctx_p = NULL;
+}
+
+static struct nfc_linux *
+nfc_new(uint32_t dev)
+{
+ struct nfc_linux *ctx;
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL ||
+ (ctx->nl = fido_nl_new()) == NULL) {
+ nfc_free(&ctx);
+ return NULL;
+ }
+
+ ctx->fd = -1;
+ ctx->dev = dev;
+
+ return ctx;
+}
+
+void *
+fido_nfc_open(const char *path)
+{
+ struct nfc_linux *ctx = NULL;
+ int idx;
+
+ if (strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) != 0) {
+ fido_log_debug("%s: bad prefix", __func__);
+ goto fail;
+ }
+ if ((idx = sysnum_from_syspath(path + strlen(FIDO_NFC_PREFIX))) < 0 ||
+ (ctx = nfc_new((uint32_t)idx)) == NULL) {
+ fido_log_debug("%s: nfc_new", __func__);
+ goto fail;
+ }
+ if (fido_nl_power_nfc(ctx->nl, ctx->dev) < 0 ||
+ fido_nl_get_nfc_target(ctx->nl, ctx->dev, &ctx->target) < 0 ||
+ nfc_target_connect(ctx) < 0) {
+ fido_log_debug("%s: netlink", __func__);
+ goto fail;
+ }
+
+ return ctx;
+fail:
+ nfc_free(&ctx);
+ return NULL;
+}
+
+void
+fido_nfc_close(void *handle)
+{
+ struct nfc_linux *ctx = handle;
+
+ nfc_free(&ctx);
+}
+
+int
+fido_nfc_set_sigmask(void *handle, const fido_sigset_t *sigmask)
+{
+ struct nfc_linux *ctx = handle;
+
+ ctx->sigmask = *sigmask;
+ ctx->sigmaskp = &ctx->sigmask;
+
+ return FIDO_OK;
+}
+
+int
+fido_nfc_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct nfc_linux *ctx = handle;
+ struct iovec iov[2];
+ uint8_t preamble;
+ ssize_t r;
+
+ memset(&iov, 0, sizeof(iov));
+ iov[0].iov_base = &preamble;
+ iov[0].iov_len = sizeof(preamble);
+ iov[1].iov_base = buf;
+ iov[1].iov_len = len;
+
+ if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) {
+ fido_log_debug("%s: fido_hid_unix_wait", __func__);
+ return -1;
+ }
+ if ((r = readv(ctx->fd, iov, nitems(iov))) == -1) {
+ fido_log_error(errno, "%s: read", __func__);
+ return -1;
+ }
+ if (r < 1) {
+ fido_log_debug("%s: %zd < 1", __func__, r);
+ return -1;
+ }
+ if (preamble != 0x00) {
+ fido_log_debug("%s: preamble", __func__);
+ return -1;
+ }
+
+ r--;
+ fido_log_xxd(buf, (size_t)r, "%s", __func__);
+
+ return (int)r;
+}
+
+int
+fido_nfc_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct nfc_linux *ctx = handle;
+ ssize_t r;
+
+ fido_log_xxd(buf, len, "%s", __func__);
+
+ if (len > INT_MAX) {
+ fido_log_debug("%s: len", __func__);
+ return -1;
+ }
+ if ((r = write(ctx->fd, buf, len)) == -1) {
+ fido_log_error(errno, "%s: write", __func__);
+ return -1;
+ }
+ if (r < 0 || (size_t)r != len) {
+ fido_log_debug("%s: %zd != %zu", __func__, r, len);
+ return -1;
+ }
+
+ return (int)r;
+}
diff --git a/src/packed.h b/src/packed.h
new file mode 100644
index 0000000..5f53ae5
--- /dev/null
+++ b/src/packed.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _PACKED_H
+#define _PACKED_H
+
+#if defined(__GNUC__)
+#define PACKED_TYPE(type, def) \
+ typedef def __attribute__ ((__packed__)) type;
+#elif defined(_MSC_VER)
+#define PACKED_TYPE(type, def) \
+ __pragma(pack(push, 1)) \
+ typedef def type; \
+ __pragma(pack(pop))
+#else
+#error "please provide a way to define packed types on your platform"
+#endif
+
+#endif /* !_PACKED_H */
diff --git a/src/pcsc.c b/src/pcsc.c
new file mode 100644
index 0000000..d7bd6c6
--- /dev/null
+++ b/src/pcsc.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2022 Micro Focus or one of its affiliates.
+ * Copyright (c) 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
+ */
+
+#if __APPLE__
+#include <PCSC/wintypes.h>
+#include <PCSC/winscard.h>
+#else
+#include <winscard.h>
+#endif /* __APPLE__ */
+
+#include <errno.h>
+
+#include "fido.h"
+#include "fido/param.h"
+#include "iso7816.h"
+
+#if defined(_WIN32) && !defined(__MINGW32__)
+#define SCardConnect SCardConnectA
+#define SCardListReaders SCardListReadersA
+#endif
+
+#ifndef SCARD_PROTOCOL_Tx
+#define SCARD_PROTOCOL_Tx SCARD_PROTOCOL_ANY
+#endif
+
+#define BUFSIZE 1024 /* in bytes; passed to SCardListReaders() */
+#define APDULEN 264 /* 261 rounded up to the nearest multiple of 8 */
+#define READERS 8 /* maximum number of readers */
+
+struct pcsc {
+ SCARDCONTEXT ctx;
+ SCARDHANDLE h;
+ SCARD_IO_REQUEST req;
+ uint8_t rx_buf[APDULEN];
+ size_t rx_len;
+};
+
+static LONG
+list_readers(SCARDCONTEXT ctx, char **buf)
+{
+ LONG s;
+ DWORD len;
+
+ len = BUFSIZE;
+ if ((*buf = calloc(1, len)) == NULL)
+ goto fail;
+ if ((s = SCardListReaders(ctx, NULL, *buf, &len)) != SCARD_S_SUCCESS) {
+ fido_log_debug("%s: SCardListReaders 0x%lx", __func__, (long)s);
+ goto fail;
+ }
+ /* sanity check "multi-string" */
+ if (len > BUFSIZE || len < 2) {
+ fido_log_debug("%s: bogus len=%u", __func__, (unsigned)len);
+ goto fail;
+ }
+ if ((*buf)[len - 1] != 0 || (*buf)[len - 2] != '\0') {
+ fido_log_debug("%s: bogus buf", __func__);
+ goto fail;
+ }
+ return (LONG)SCARD_S_SUCCESS;
+fail:
+ free(*buf);
+ *buf = NULL;
+
+ return (LONG)SCARD_E_NO_READERS_AVAILABLE;
+}
+
+static char *
+get_reader(SCARDCONTEXT ctx, const char *path)
+{
+ char *reader = NULL, *buf = NULL;
+ const char prefix[] = FIDO_PCSC_PREFIX "//slot";
+ uint64_t n;
+
+ if (path == NULL)
+ goto out;
+ if (strncmp(path, prefix, strlen(prefix)) != 0 ||
+ fido_to_uint64(path + strlen(prefix), 10, &n) < 0 ||
+ n > READERS - 1) {
+ fido_log_debug("%s: invalid path %s", __func__, path);
+ goto out;
+ }
+ if (list_readers(ctx, &buf) != SCARD_S_SUCCESS) {
+ fido_log_debug("%s: list_readers", __func__);
+ goto out;
+ }
+ for (const char *name = buf; *name != 0; name += strlen(name) + 1) {
+ if (n == 0) {
+ reader = strdup(name);
+ goto out;
+ }
+ n--;
+ }
+ fido_log_debug("%s: failed to find reader %s", __func__, path);
+out:
+ free(buf);
+
+ return reader;
+}
+
+static int
+prepare_io_request(DWORD prot, SCARD_IO_REQUEST *req)
+{
+ switch (prot) {
+ case SCARD_PROTOCOL_T0:
+ req->dwProtocol = SCARD_PCI_T0->dwProtocol;
+ req->cbPciLength = SCARD_PCI_T0->cbPciLength;
+ break;
+ case SCARD_PROTOCOL_T1:
+ req->dwProtocol = SCARD_PCI_T1->dwProtocol;
+ req->cbPciLength = SCARD_PCI_T1->cbPciLength;
+ break;
+ default:
+ fido_log_debug("%s: unknown protocol %lu", __func__,
+ (u_long)prot);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+copy_info(fido_dev_info_t *di, SCARDCONTEXT ctx, const char *reader, size_t idx)
+{
+ SCARDHANDLE h = 0;
+ SCARD_IO_REQUEST req;
+ DWORD prot = 0;
+ LONG s;
+ int ok = -1;
+
+ memset(di, 0, sizeof(*di));
+ memset(&req, 0, sizeof(req));
+
+ if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED,
+ SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) {
+ fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s);
+ goto fail;
+ }
+ if (prepare_io_request(prot, &req) < 0) {
+ fido_log_debug("%s: prepare_io_request", __func__);
+ goto fail;
+ }
+ if (asprintf(&di->path, "%s//slot%zu", FIDO_PCSC_PREFIX, idx) == -1) {
+ di->path = NULL;
+ fido_log_debug("%s: asprintf", __func__);
+ goto fail;
+ }
+ if (nfc_is_fido(di->path) == false) {
+ fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path);
+ goto fail;
+ }
+ if ((di->manufacturer = strdup("PC/SC")) == NULL ||
+ (di->product = strdup(reader)) == NULL)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (h != 0)
+ SCardDisconnect(h, SCARD_LEAVE_CARD);
+ if (ok < 0) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ }
+
+ return ok;
+}
+
+int
+fido_pcsc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ SCARDCONTEXT ctx = 0;
+ char *buf = NULL;
+ LONG s;
+ size_t idx = 0;
+ int r = FIDO_ERR_INTERNAL;
+
+ *olen = 0;
+
+ if (ilen == 0)
+ return FIDO_OK;
+ if (devlist == NULL)
+ return FIDO_ERR_INVALID_ARGUMENT;
+
+ if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL,
+ &ctx)) != SCARD_S_SUCCESS || ctx == 0) {
+ fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__,
+ (long)s);
+ if (s == (LONG)SCARD_E_NO_SERVICE ||
+ s == (LONG)SCARD_E_NO_SMARTCARD)
+ r = FIDO_OK; /* suppress error */
+ goto out;
+ }
+ if ((s = list_readers(ctx, &buf)) != SCARD_S_SUCCESS) {
+ fido_log_debug("%s: list_readers 0x%lx", __func__, (long)s);
+ if (s == (LONG)SCARD_E_NO_READERS_AVAILABLE)
+ r = FIDO_OK; /* suppress error */
+ goto out;
+ }
+
+ for (const char *name = buf; *name != 0; name += strlen(name) + 1) {
+ if (idx == READERS) {
+ fido_log_debug("%s: stopping at %zu readers", __func__,
+ idx);
+ r = FIDO_OK;
+ goto out;
+ }
+ if (copy_info(&devlist[*olen], ctx, name, idx++) == 0) {
+ devlist[*olen].io = (fido_dev_io_t) {
+ fido_pcsc_open,
+ fido_pcsc_close,
+ fido_pcsc_read,
+ fido_pcsc_write,
+ };
+ devlist[*olen].transport = (fido_dev_transport_t) {
+ fido_pcsc_rx,
+ fido_pcsc_tx,
+ };
+ if (++(*olen) == ilen)
+ break;
+ }
+ }
+
+ r = FIDO_OK;
+out:
+ free(buf);
+ if (ctx != 0)
+ SCardReleaseContext(ctx);
+
+ return r;
+}
+
+void *
+fido_pcsc_open(const char *path)
+{
+ char *reader = NULL;
+ struct pcsc *dev = NULL;
+ SCARDCONTEXT ctx = 0;
+ SCARDHANDLE h = 0;
+ SCARD_IO_REQUEST req;
+ DWORD prot = 0;
+ LONG s;
+
+ memset(&req, 0, sizeof(req));
+
+ if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL,
+ &ctx)) != SCARD_S_SUCCESS || ctx == 0) {
+ fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__,
+ (long)s);
+ goto fail;
+
+ }
+ if ((reader = get_reader(ctx, path)) == NULL) {
+ fido_log_debug("%s: get_reader(%s)", __func__, path);
+ goto fail;
+ }
+ if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED,
+ SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) {
+ fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s);
+ goto fail;
+ }
+ if (prepare_io_request(prot, &req) < 0) {
+ fido_log_debug("%s: prepare_io_request", __func__);
+ goto fail;
+ }
+ if ((dev = calloc(1, sizeof(*dev))) == NULL)
+ goto fail;
+
+ dev->ctx = ctx;
+ dev->h = h;
+ dev->req = req;
+ ctx = 0;
+ h = 0;
+fail:
+ if (h != 0)
+ SCardDisconnect(h, SCARD_LEAVE_CARD);
+ if (ctx != 0)
+ SCardReleaseContext(ctx);
+ free(reader);
+
+ return dev;
+}
+
+void
+fido_pcsc_close(void *handle)
+{
+ struct pcsc *dev = handle;
+
+ if (dev->h != 0)
+ SCardDisconnect(dev->h, SCARD_LEAVE_CARD);
+ if (dev->ctx != 0)
+ SCardReleaseContext(dev->ctx);
+
+ explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
+ free(dev);
+}
+
+int
+fido_pcsc_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ struct pcsc *dev = handle;
+ int r;
+
+ (void)ms;
+ if (dev->rx_len == 0 || dev->rx_len > len ||
+ dev->rx_len > sizeof(dev->rx_buf)) {
+ fido_log_debug("%s: rx_len", __func__);
+ return -1;
+ }
+ fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: reading", __func__);
+ memcpy(buf, dev->rx_buf, dev->rx_len);
+ explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
+ r = (int)dev->rx_len;
+ dev->rx_len = 0;
+
+ return r;
+}
+
+int
+fido_pcsc_write(void *handle, const unsigned char *buf, size_t len)
+{
+ struct pcsc *dev = handle;
+ DWORD n;
+ LONG s;
+
+ if (len > INT_MAX) {
+ fido_log_debug("%s: len", __func__);
+ return -1;
+ }
+
+ explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
+ dev->rx_len = 0;
+ n = (DWORD)sizeof(dev->rx_buf);
+
+ fido_log_xxd(buf, len, "%s: writing", __func__);
+
+ if ((s = SCardTransmit(dev->h, &dev->req, buf, (DWORD)len, NULL,
+ dev->rx_buf, &n)) != SCARD_S_SUCCESS) {
+ fido_log_debug("%s: SCardTransmit 0x%lx", __func__, (long)s);
+ explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf));
+ return -1;
+ }
+ dev->rx_len = (size_t)n;
+
+ fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: read", __func__);
+
+ return (int)len;
+}
+
+int
+fido_pcsc_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count)
+{
+ return fido_nfc_tx(d, cmd, buf, count);
+}
+
+int
+fido_pcsc_rx(fido_dev_t *d, uint8_t cmd, u_char *buf, size_t count, int ms)
+{
+ return fido_nfc_rx(d, cmd, buf, count, ms);
+}
+
+bool
+fido_is_pcsc(const char *path)
+{
+ return strncmp(path, FIDO_PCSC_PREFIX, strlen(FIDO_PCSC_PREFIX)) == 0;
+}
+
+int
+fido_dev_set_pcsc(fido_dev_t *d)
+{
+ if (d->io_handle != NULL) {
+ fido_log_debug("%s: device open", __func__);
+ return -1;
+ }
+ d->io_own = true;
+ d->io = (fido_dev_io_t) {
+ fido_pcsc_open,
+ fido_pcsc_close,
+ fido_pcsc_read,
+ fido_pcsc_write,
+ };
+ d->transport = (fido_dev_transport_t) {
+ fido_pcsc_rx,
+ fido_pcsc_tx,
+ };
+
+ return 0;
+}
diff --git a/src/pin.c b/src/pin.c
new file mode 100644
index 0000000..c3dd927
--- /dev/null
+++ b/src/pin.c
@@ -0,0 +1,723 @@
+/*
+ * 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 <openssl/sha.h>
+#include "fido.h"
+#include "fido/es256.h"
+
+#define CTAP21_UV_TOKEN_PERM_MAKECRED 0x01
+#define CTAP21_UV_TOKEN_PERM_ASSERT 0x02
+#define CTAP21_UV_TOKEN_PERM_CRED_MGMT 0x04
+#define CTAP21_UV_TOKEN_PERM_BIO 0x08
+#define CTAP21_UV_TOKEN_PERM_LARGEBLOB 0x10
+#define CTAP21_UV_TOKEN_PERM_CONFIG 0x20
+
+int
+fido_sha256(fido_blob_t *digest, const u_char *data, size_t data_len)
+{
+ if ((digest->ptr = calloc(1, SHA256_DIGEST_LENGTH)) == NULL)
+ return (-1);
+
+ digest->len = SHA256_DIGEST_LENGTH;
+
+ if (SHA256(data, data_len, digest->ptr) != digest->ptr) {
+ fido_blob_reset(digest);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+pin_sha256_enc(const fido_dev_t *dev, const fido_blob_t *shared,
+ const fido_blob_t *pin, fido_blob_t **out)
+{
+ fido_blob_t *ph = NULL;
+ int r;
+
+ if ((*out = fido_blob_new()) == NULL ||
+ (ph = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (fido_sha256(ph, pin->ptr, pin->len) < 0 || ph->len < 16) {
+ fido_log_debug("%s: SHA256", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ ph->len = 16; /* first 16 bytes */
+
+ if (aes256_cbc_enc(dev, shared, ph, *out) < 0) {
+ fido_log_debug("%s: aes256_cbc_enc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&ph);
+
+ return (r);
+}
+
+static int
+pad64(const char *pin, fido_blob_t **ppin)
+{
+ size_t pin_len;
+ size_t ppin_len;
+
+ pin_len = strlen(pin);
+ if (pin_len < 4 || pin_len > 63) {
+ fido_log_debug("%s: invalid pin length", __func__);
+ return (FIDO_ERR_PIN_POLICY_VIOLATION);
+ }
+
+ if ((*ppin = fido_blob_new()) == NULL)
+ return (FIDO_ERR_INTERNAL);
+
+ ppin_len = (pin_len + 63U) & ~63U;
+ if (ppin_len < pin_len ||
+ ((*ppin)->ptr = calloc(1, ppin_len)) == NULL) {
+ fido_blob_free(ppin);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ memcpy((*ppin)->ptr, pin, pin_len);
+ (*ppin)->len = ppin_len;
+
+ return (FIDO_OK);
+}
+
+static int
+pin_pad64_enc(const fido_dev_t *dev, const fido_blob_t *shared,
+ const char *pin, fido_blob_t **out)
+{
+ fido_blob_t *ppin = NULL;
+ int r;
+
+ if ((r = pad64(pin, &ppin)) != FIDO_OK) {
+ fido_log_debug("%s: pad64", __func__);
+ goto fail;
+ }
+
+ if ((*out = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (aes256_cbc_enc(dev, shared, ppin, *out) < 0) {
+ fido_log_debug("%s: aes256_cbc_enc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&ppin);
+
+ return (r);
+}
+
+static cbor_item_t *
+encode_uv_permission(uint8_t cmd)
+{
+ switch (cmd) {
+ case CTAP_CBOR_ASSERT:
+ return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_ASSERT));
+ case CTAP_CBOR_BIO_ENROLL_PRE:
+ return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_BIO));
+ case CTAP_CBOR_CONFIG:
+ return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_CONFIG));
+ case CTAP_CBOR_MAKECRED:
+ return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_MAKECRED));
+ case CTAP_CBOR_CRED_MGMT_PRE:
+ return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_CRED_MGMT));
+ case CTAP_CBOR_LARGEBLOB:
+ return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_LARGEBLOB));
+ default:
+ fido_log_debug("%s: cmd 0x%02x", __func__, cmd);
+ return (NULL);
+ }
+}
+
+static int
+ctap20_uv_token_tx(fido_dev_t *dev, const char *pin, const fido_blob_t *ecdh,
+ const es256_pk_t *pk, int *ms)
+{
+ fido_blob_t f;
+ fido_blob_t *p = NULL;
+ fido_blob_t *phe = NULL;
+ cbor_item_t *argv[6];
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if (pin == NULL) {
+ fido_log_debug("%s: NULL pin", __func__);
+ r = FIDO_ERR_PIN_REQUIRED;
+ goto fail;
+ }
+
+ if ((p = fido_blob_new()) == NULL || fido_blob_set(p,
+ (const unsigned char *)pin, strlen(pin)) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((r = pin_sha256_enc(dev, ecdh, p, &phe)) != FIDO_OK) {
+ fido_log_debug("%s: pin_sha256_enc", __func__);
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL ||
+ (argv[1] = cbor_build_uint8(5)) == NULL ||
+ (argv[2] = es256_pk_encode(pk, 1)) == NULL ||
+ (argv[5] = fido_blob_encode(phe)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv),
+ &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ fido_blob_free(&p);
+ fido_blob_free(&phe);
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+ctap21_uv_token_tx(fido_dev_t *dev, const char *pin, const fido_blob_t *ecdh,
+ const es256_pk_t *pk, uint8_t cmd, const char *rpid, int *ms)
+{
+ fido_blob_t f;
+ fido_blob_t *p = NULL;
+ fido_blob_t *phe = NULL;
+ cbor_item_t *argv[10];
+ uint8_t subcmd;
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if (pin != NULL) {
+ if ((p = fido_blob_new()) == NULL || fido_blob_set(p,
+ (const unsigned char *)pin, strlen(pin)) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+ if ((r = pin_sha256_enc(dev, ecdh, p, &phe)) != FIDO_OK) {
+ fido_log_debug("%s: pin_sha256_enc", __func__);
+ goto fail;
+ }
+ subcmd = 9; /* getPinUvAuthTokenUsingPinWithPermissions */
+ } else {
+ if (fido_dev_has_uv(dev) == false) {
+ fido_log_debug("%s: fido_dev_has_uv", __func__);
+ r = FIDO_ERR_PIN_REQUIRED;
+ goto fail;
+ }
+ subcmd = 6; /* getPinUvAuthTokenUsingUvWithPermissions */
+ }
+
+ if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL ||
+ (argv[1] = cbor_build_uint8(subcmd)) == NULL ||
+ (argv[2] = es256_pk_encode(pk, 1)) == NULL ||
+ (phe != NULL && (argv[5] = fido_blob_encode(phe)) == NULL) ||
+ (argv[8] = encode_uv_permission(cmd)) == NULL ||
+ (rpid != NULL && (argv[9] = cbor_build_string(rpid)) == NULL)) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv),
+ &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ fido_blob_free(&p);
+ fido_blob_free(&phe);
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+parse_uv_token(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_blob_t *token = arg;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != 2) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ return (fido_blob_decode(val, token));
+}
+
+static int
+uv_token_rx(fido_dev_t *dev, const fido_blob_t *ecdh, fido_blob_t *token,
+ int *ms)
+{
+ fido_blob_t *aes_token = NULL;
+ unsigned char *msg = NULL;
+ int msglen;
+ int r;
+
+ if ((aes_token = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, aes_token,
+ parse_uv_token)) != FIDO_OK) {
+ fido_log_debug("%s: parse_uv_token", __func__);
+ goto fail;
+ }
+
+ if (aes256_cbc_dec(dev, ecdh, aes_token, token) < 0) {
+ fido_log_debug("%s: aes256_cbc_dec", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&aes_token);
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+uv_token_wait(fido_dev_t *dev, uint8_t cmd, const char *pin,
+ const fido_blob_t *ecdh, const es256_pk_t *pk, const char *rpid,
+ fido_blob_t *token, int *ms)
+{
+ int r;
+
+ if (ecdh == NULL || pk == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ if (fido_dev_supports_permissions(dev))
+ r = ctap21_uv_token_tx(dev, pin, ecdh, pk, cmd, rpid, ms);
+ else
+ r = ctap20_uv_token_tx(dev, pin, ecdh, pk, ms);
+ if (r != FIDO_OK)
+ return (r);
+
+ return (uv_token_rx(dev, ecdh, token, ms));
+}
+
+int
+fido_dev_get_uv_token(fido_dev_t *dev, uint8_t cmd, const char *pin,
+ const fido_blob_t *ecdh, const es256_pk_t *pk, const char *rpid,
+ fido_blob_t *token, int *ms)
+{
+ return (uv_token_wait(dev, cmd, pin, ecdh, pk, rpid, token, ms));
+}
+
+static int
+fido_dev_change_pin_tx(fido_dev_t *dev, const char *pin, const char *oldpin,
+ int *ms)
+{
+ fido_blob_t f;
+ fido_blob_t *ppine = NULL;
+ fido_blob_t *ecdh = NULL;
+ fido_blob_t *opin = NULL;
+ fido_blob_t *opinhe = NULL;
+ cbor_item_t *argv[6];
+ es256_pk_t *pk = NULL;
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if ((opin = fido_blob_new()) == NULL || fido_blob_set(opin,
+ (const unsigned char *)oldpin, strlen(oldpin)) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+
+ /* pad and encrypt new pin */
+ if ((r = pin_pad64_enc(dev, ecdh, pin, &ppine)) != FIDO_OK) {
+ fido_log_debug("%s: pin_pad64_enc", __func__);
+ goto fail;
+ }
+
+ /* hash and encrypt old pin */
+ if ((r = pin_sha256_enc(dev, ecdh, opin, &opinhe)) != FIDO_OK) {
+ fido_log_debug("%s: pin_sha256_enc", __func__);
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL ||
+ (argv[1] = cbor_build_uint8(4)) == NULL ||
+ (argv[2] = es256_pk_encode(pk, 1)) == NULL ||
+ (argv[3] = cbor_encode_change_pin_auth(dev, ecdh, ppine, opinhe)) == NULL ||
+ (argv[4] = fido_blob_encode(ppine)) == NULL ||
+ (argv[5] = fido_blob_encode(opinhe)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv),
+ &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ es256_pk_free(&pk);
+ fido_blob_free(&ppine);
+ fido_blob_free(&ecdh);
+ fido_blob_free(&opin);
+ fido_blob_free(&opinhe);
+ free(f.ptr);
+
+ return (r);
+
+}
+
+static int
+fido_dev_set_pin_tx(fido_dev_t *dev, const char *pin, int *ms)
+{
+ fido_blob_t f;
+ fido_blob_t *ppine = NULL;
+ fido_blob_t *ecdh = NULL;
+ cbor_item_t *argv[5];
+ es256_pk_t *pk = NULL;
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_do_ecdh", __func__);
+ goto fail;
+ }
+
+ if ((r = pin_pad64_enc(dev, ecdh, pin, &ppine)) != FIDO_OK) {
+ fido_log_debug("%s: pin_pad64_enc", __func__);
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL ||
+ (argv[1] = cbor_build_uint8(3)) == NULL ||
+ (argv[2] = es256_pk_encode(pk, 1)) == NULL ||
+ (argv[3] = cbor_encode_pin_auth(dev, ecdh, ppine)) == NULL ||
+ (argv[4] = fido_blob_encode(ppine)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv),
+ &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ es256_pk_free(&pk);
+ fido_blob_free(&ppine);
+ fido_blob_free(&ecdh);
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_set_pin_wait(fido_dev_t *dev, const char *pin, const char *oldpin,
+ int *ms)
+{
+ int r;
+
+ if (oldpin != NULL) {
+ if ((r = fido_dev_change_pin_tx(dev, pin, oldpin,
+ ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_change_pin_tx", __func__);
+ return (r);
+ }
+ } else {
+ if ((r = fido_dev_set_pin_tx(dev, pin, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_set_pin_tx", __func__);
+ return (r);
+ }
+ }
+
+ if ((r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_rx_cbor_status", __func__);
+ return (r);
+ }
+
+ if (dev->flags & FIDO_DEV_PIN_UNSET) {
+ dev->flags &= ~FIDO_DEV_PIN_UNSET;
+ dev->flags |= FIDO_DEV_PIN_SET;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_set_pin(fido_dev_t *dev, const char *pin, const char *oldpin)
+{
+ int ms = dev->timeout_ms;
+
+ return (fido_dev_set_pin_wait(dev, pin, oldpin, &ms));
+}
+
+static int
+parse_retry_count(const uint8_t keyval, const cbor_item_t *key,
+ const cbor_item_t *val, void *arg)
+{
+ int *retries = arg;
+ uint64_t n;
+
+ if (cbor_isa_uint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8 ||
+ cbor_get_uint8(key) != keyval) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (0); /* ignore */
+ }
+
+ if (cbor_decode_uint64(val, &n) < 0 || n > INT_MAX) {
+ fido_log_debug("%s: cbor_decode_uint64", __func__);
+ return (-1);
+ }
+
+ *retries = (int)n;
+
+ return (0);
+}
+
+static int
+parse_pin_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ return (parse_retry_count(3, key, val, arg));
+}
+
+static int
+parse_uv_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ return (parse_retry_count(5, key, val, arg));
+}
+
+static int
+fido_dev_get_retry_count_tx(fido_dev_t *dev, uint8_t subcmd, int *ms)
+{
+ fido_blob_t f;
+ cbor_item_t *argv[2];
+ int r;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+
+ if ((argv[0] = cbor_build_uint8(1)) == NULL ||
+ (argv[1] = cbor_build_uint8(subcmd)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv),
+ &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+
+ return (r);
+}
+
+static int
+fido_dev_get_pin_retry_count_rx(fido_dev_t *dev, int *retries, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ *retries = 0;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, retries,
+ parse_pin_retry_count)) != FIDO_OK) {
+ fido_log_debug("%s: parse_pin_retry_count", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+fido_dev_get_pin_retry_count_wait(fido_dev_t *dev, int *retries, int *ms)
+{
+ int r;
+
+ if ((r = fido_dev_get_retry_count_tx(dev, 1, ms)) != FIDO_OK ||
+ (r = fido_dev_get_pin_retry_count_rx(dev, retries, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_get_retry_count(fido_dev_t *dev, int *retries)
+{
+ int ms = dev->timeout_ms;
+
+ return (fido_dev_get_pin_retry_count_wait(dev, retries, &ms));
+}
+
+static int
+fido_dev_get_uv_retry_count_rx(fido_dev_t *dev, int *retries, int *ms)
+{
+ unsigned char *msg;
+ int msglen;
+ int r;
+
+ *retries = 0;
+
+ if ((msg = malloc(FIDO_MAXMSG)) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ if ((r = cbor_parse_reply(msg, (size_t)msglen, retries,
+ parse_uv_retry_count)) != FIDO_OK) {
+ fido_log_debug("%s: parse_uv_retry_count", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ freezero(msg, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+fido_dev_get_uv_retry_count_wait(fido_dev_t *dev, int *retries, int *ms)
+{
+ int r;
+
+ if ((r = fido_dev_get_retry_count_tx(dev, 7, ms)) != FIDO_OK ||
+ (r = fido_dev_get_uv_retry_count_rx(dev, retries, ms)) != FIDO_OK)
+ return (r);
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_get_uv_retry_count(fido_dev_t *dev, int *retries)
+{
+ int ms = dev->timeout_ms;
+
+ return (fido_dev_get_uv_retry_count_wait(dev, retries, &ms));
+}
+
+int
+cbor_add_uv_params(fido_dev_t *dev, uint8_t cmd, const fido_blob_t *hmac_data,
+ const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin,
+ const char *rpid, cbor_item_t **auth, cbor_item_t **opt, int *ms)
+{
+ fido_blob_t *token = NULL;
+ int r;
+
+ if ((token = fido_blob_new()) == NULL) {
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((r = fido_dev_get_uv_token(dev, cmd, pin, ecdh, pk, rpid,
+ token, ms)) != FIDO_OK) {
+ fido_log_debug("%s: fido_dev_get_uv_token", __func__);
+ goto fail;
+ }
+
+ if ((*auth = cbor_encode_pin_auth(dev, token, hmac_data)) == NULL ||
+ (*opt = cbor_encode_pin_opt(dev)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_free(&token);
+
+ return (r);
+}
diff --git a/src/random.c b/src/random.c
new file mode 100644
index 0000000..9688d35
--- /dev/null
+++ b/src/random.c
@@ -0,0 +1,83 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_RANDOM_H
+#include <sys/random.h>
+#endif
+
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "fido.h"
+
+#if defined(_WIN32)
+#include <windows.h>
+
+#include <winternl.h>
+#include <winerror.h>
+#include <stdio.h>
+#include <bcrypt.h>
+#include <sal.h>
+
+int
+fido_get_random(void *buf, size_t len)
+{
+ NTSTATUS status;
+
+ status = BCryptGenRandom(NULL, buf, (ULONG)len,
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+
+ if (!NT_SUCCESS(status))
+ return (-1);
+
+ return (0);
+}
+#elif defined(HAVE_ARC4RANDOM_BUF)
+int
+fido_get_random(void *buf, size_t len)
+{
+ arc4random_buf(buf, len);
+ return (0);
+}
+#elif defined(HAVE_GETRANDOM)
+int
+fido_get_random(void *buf, size_t len)
+{
+ ssize_t r;
+
+ if ((r = getrandom(buf, len, 0)) < 0 || (size_t)r != len)
+ return (-1);
+
+ return (0);
+}
+#elif defined(HAVE_DEV_URANDOM)
+int
+fido_get_random(void *buf, size_t len)
+{
+ int fd = -1;
+ int ok = -1;
+ ssize_t r;
+
+ if ((fd = open(FIDO_RANDOM_DEV, O_RDONLY)) < 0)
+ goto fail;
+ if ((r = read(fd, buf, len)) < 0 || (size_t)r != len)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (fd != -1)
+ close(fd);
+
+ return (ok);
+}
+#else
+#error "please provide an implementation of fido_get_random() for your platform"
+#endif /* _WIN32 */
diff --git a/src/reset.c b/src/reset.c
new file mode 100644
index 0000000..4e09dbb
--- /dev/null
+++ b/src/reset.c
@@ -0,0 +1,46 @@
+/*
+ * 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"
+
+static int
+fido_dev_reset_tx(fido_dev_t *dev, int *ms)
+{
+ const unsigned char cbor[] = { CTAP_CBOR_RESET };
+
+ if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ return (FIDO_ERR_TX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+fido_dev_reset_wait(fido_dev_t *dev, int *ms)
+{
+ int r;
+
+ if ((r = fido_dev_reset_tx(dev, ms)) != FIDO_OK ||
+ (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK)
+ return (r);
+
+ if (dev->flags & FIDO_DEV_PIN_SET) {
+ dev->flags &= ~FIDO_DEV_PIN_SET;
+ dev->flags |= FIDO_DEV_PIN_UNSET;
+ }
+
+ return (FIDO_OK);
+}
+
+int
+fido_dev_reset(fido_dev_t *dev)
+{
+ int ms = dev->timeout_ms;
+
+ return (fido_dev_reset_wait(dev, &ms));
+}
diff --git a/src/rs1.c b/src/rs1.c
new file mode 100644
index 0000000..03636b5
--- /dev/null
+++ b/src/rs1.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2021 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/rsa.h>
+#include <openssl/obj_mac.h>
+
+#include "fido.h"
+
+#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050200fL
+static EVP_MD *
+rs1_get_EVP_MD(void)
+{
+ const EVP_MD *from;
+ EVP_MD *to = NULL;
+
+ if ((from = EVP_sha1()) != NULL && (to = malloc(sizeof(*to))) != NULL)
+ memcpy(to, from, sizeof(*to));
+
+ return (to);
+}
+
+static void
+rs1_free_EVP_MD(EVP_MD *md)
+{
+ freezero(md, sizeof(*md));
+}
+#elif OPENSSL_VERSION_NUMBER >= 0x30000000
+static EVP_MD *
+rs1_get_EVP_MD(void)
+{
+ return (EVP_MD_fetch(NULL, "SHA-1", NULL));
+}
+
+static void
+rs1_free_EVP_MD(EVP_MD *md)
+{
+ EVP_MD_free(md);
+}
+#else
+static EVP_MD *
+rs1_get_EVP_MD(void)
+{
+ const EVP_MD *md;
+
+ if ((md = EVP_sha1()) == NULL)
+ return (NULL);
+
+ return (EVP_MD_meth_dup(md));
+}
+
+static void
+rs1_free_EVP_MD(EVP_MD *md)
+{
+ EVP_MD_meth_free(md);
+}
+#endif /* LIBRESSL_VERSION_NUMBER */
+
+int
+rs1_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_MD *md = NULL;
+ int ok = -1;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) {
+ fido_log_debug("%s: EVP_PKEY_base_id", __func__);
+ goto fail;
+ }
+
+ if ((md = rs1_get_EVP_MD()) == NULL) {
+ fido_log_debug("%s: rs1_get_EVP_MD", __func__);
+ goto fail;
+ }
+
+ if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL ||
+ EVP_PKEY_verify_init(pctx) != 1 ||
+ EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) != 1 ||
+ EVP_PKEY_CTX_set_signature_md(pctx, md) != 1) {
+ fido_log_debug("%s: EVP_PKEY_CTX", __func__);
+ goto fail;
+ }
+
+ if (EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr,
+ dgst->len) != 1) {
+ fido_log_debug("%s: EVP_PKEY_verify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_CTX_free(pctx);
+ rs1_free_EVP_MD(md);
+
+ return (ok);
+}
diff --git a/src/rs256.c b/src/rs256.c
new file mode 100644
index 0000000..59ceb94
--- /dev/null
+++ b/src/rs256.c
@@ -0,0 +1,316 @@
+/*
+ * 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 <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/obj_mac.h>
+
+#include "fido.h"
+#include "fido/rs256.h"
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000
+#define get0_RSA(x) EVP_PKEY_get0_RSA((x))
+#else
+#define get0_RSA(x) EVP_PKEY_get0((x))
+#endif
+
+#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050200fL
+static EVP_MD *
+rs256_get_EVP_MD(void)
+{
+ const EVP_MD *from;
+ EVP_MD *to = NULL;
+
+ if ((from = EVP_sha256()) != NULL && (to = malloc(sizeof(*to))) != NULL)
+ memcpy(to, from, sizeof(*to));
+
+ return (to);
+}
+
+static void
+rs256_free_EVP_MD(EVP_MD *md)
+{
+ freezero(md, sizeof(*md));
+}
+#elif OPENSSL_VERSION_NUMBER >= 0x30000000
+static EVP_MD *
+rs256_get_EVP_MD(void)
+{
+ return (EVP_MD_fetch(NULL, "SHA2-256", NULL));
+}
+
+static void
+rs256_free_EVP_MD(EVP_MD *md)
+{
+ EVP_MD_free(md);
+}
+#else
+static EVP_MD *
+rs256_get_EVP_MD(void)
+{
+ const EVP_MD *md;
+
+ if ((md = EVP_sha256()) == NULL)
+ return (NULL);
+
+ return (EVP_MD_meth_dup(md));
+}
+
+static void
+rs256_free_EVP_MD(EVP_MD *md)
+{
+ EVP_MD_meth_free(md);
+}
+#endif /* LIBRESSL_VERSION_NUMBER */
+
+static int
+decode_bignum(const cbor_item_t *item, void *ptr, size_t len)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false ||
+ cbor_bytestring_length(item) != len) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ memcpy(ptr, cbor_bytestring_handle(item), len);
+
+ return (0);
+}
+
+static int
+decode_rsa_pubkey(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ rs256_pk_t *k = arg;
+
+ if (cbor_isa_negint(key) == false ||
+ cbor_int_get_width(key) != CBOR_INT_8)
+ return (0); /* ignore */
+
+ switch (cbor_get_uint8(key)) {
+ case 0: /* modulus */
+ return (decode_bignum(val, &k->n, sizeof(k->n)));
+ case 1: /* public exponent */
+ return (decode_bignum(val, &k->e, sizeof(k->e)));
+ }
+
+ return (0); /* ignore */
+}
+
+int
+rs256_pk_decode(const cbor_item_t *item, rs256_pk_t *k)
+{
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, k, decode_rsa_pubkey) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ return (-1);
+ }
+
+ return (0);
+}
+
+rs256_pk_t *
+rs256_pk_new(void)
+{
+ return (calloc(1, sizeof(rs256_pk_t)));
+}
+
+void
+rs256_pk_free(rs256_pk_t **pkp)
+{
+ rs256_pk_t *pk;
+
+ if (pkp == NULL || (pk = *pkp) == NULL)
+ return;
+
+ freezero(pk, sizeof(*pk));
+ *pkp = NULL;
+}
+
+int
+rs256_pk_from_ptr(rs256_pk_t *pk, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey;
+
+ if (len < sizeof(*pk))
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ memcpy(pk, ptr, sizeof(*pk));
+
+ if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) {
+ fido_log_debug("%s: rs256_pk_to_EVP_PKEY", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ EVP_PKEY_free(pkey);
+
+ return (FIDO_OK);
+}
+
+EVP_PKEY *
+rs256_pk_to_EVP_PKEY(const rs256_pk_t *k)
+{
+ RSA *rsa = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIGNUM *n = NULL;
+ BIGNUM *e = NULL;
+ int ok = -1;
+
+ if ((n = BN_new()) == NULL || (e = BN_new()) == NULL)
+ goto fail;
+
+ if (BN_bin2bn(k->n, sizeof(k->n), n) == NULL ||
+ BN_bin2bn(k->e, sizeof(k->e), e) == NULL) {
+ fido_log_debug("%s: BN_bin2bn", __func__);
+ goto fail;
+ }
+
+ if ((rsa = RSA_new()) == NULL || RSA_set0_key(rsa, n, e, NULL) == 0) {
+ fido_log_debug("%s: RSA_set0_key", __func__);
+ goto fail;
+ }
+
+ /* at this point, n and e belong to rsa */
+ n = NULL;
+ e = NULL;
+
+ if (RSA_bits(rsa) != 2048) {
+ fido_log_debug("%s: invalid key length", __func__);
+ goto fail;
+ }
+
+ if ((pkey = EVP_PKEY_new()) == NULL ||
+ EVP_PKEY_assign_RSA(pkey, rsa) == 0) {
+ fido_log_debug("%s: EVP_PKEY_assign_RSA", __func__);
+ goto fail;
+ }
+
+ rsa = NULL; /* at this point, rsa belongs to evp */
+
+ ok = 0;
+fail:
+ if (n != NULL)
+ BN_free(n);
+ if (e != NULL)
+ BN_free(e);
+ if (rsa != NULL)
+ RSA_free(rsa);
+ if (ok < 0 && pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+
+ return (pkey);
+}
+
+int
+rs256_pk_from_RSA(rs256_pk_t *pk, const RSA *rsa)
+{
+ const BIGNUM *n = NULL;
+ const BIGNUM *e = NULL;
+ const BIGNUM *d = NULL;
+ int k;
+
+ if (RSA_bits(rsa) != 2048) {
+ fido_log_debug("%s: invalid key length", __func__);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ RSA_get0_key(rsa, &n, &e, &d);
+
+ if (n == NULL || e == NULL) {
+ fido_log_debug("%s: RSA_get0_key", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((k = BN_num_bytes(n)) < 0 || (size_t)k > sizeof(pk->n) ||
+ (k = BN_num_bytes(e)) < 0 || (size_t)k > sizeof(pk->e)) {
+ fido_log_debug("%s: invalid key", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((k = BN_bn2bin(n, pk->n)) < 0 || (size_t)k > sizeof(pk->n) ||
+ (k = BN_bn2bin(e, pk->e)) < 0 || (size_t)k > sizeof(pk->e)) {
+ fido_log_debug("%s: BN_bn2bin", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ return (FIDO_OK);
+}
+
+int
+rs256_pk_from_EVP_PKEY(rs256_pk_t *pk, const EVP_PKEY *pkey)
+{
+ const RSA *rsa;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA ||
+ (rsa = get0_RSA(pkey)) == NULL)
+ return (FIDO_ERR_INVALID_ARGUMENT);
+
+ return (rs256_pk_from_RSA(pk, rsa));
+}
+
+int
+rs256_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY_CTX *pctx = NULL;
+ EVP_MD *md = NULL;
+ int ok = -1;
+
+ if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) {
+ fido_log_debug("%s: EVP_PKEY_base_id", __func__);
+ goto fail;
+ }
+
+ if ((md = rs256_get_EVP_MD()) == NULL) {
+ fido_log_debug("%s: rs256_get_EVP_MD", __func__);
+ goto fail;
+ }
+
+ if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL ||
+ EVP_PKEY_verify_init(pctx) != 1 ||
+ EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) != 1 ||
+ EVP_PKEY_CTX_set_signature_md(pctx, md) != 1) {
+ fido_log_debug("%s: EVP_PKEY_CTX", __func__);
+ goto fail;
+ }
+
+ if (EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr,
+ dgst->len) != 1) {
+ fido_log_debug("%s: EVP_PKEY_verify", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_CTX_free(pctx);
+ rs256_free_EVP_MD(md);
+
+ return (ok);
+}
+
+int
+rs256_pk_verify_sig(const fido_blob_t *dgst, const rs256_pk_t *pk,
+ const fido_blob_t *sig)
+{
+ EVP_PKEY *pkey;
+ int ok = -1;
+
+ if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL ||
+ rs256_verify_sig(dgst, pkey, sig) < 0) {
+ fido_log_debug("%s: rs256_verify_sig", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_PKEY_free(pkey);
+
+ return (ok);
+}
diff --git a/src/time.c b/src/time.c
new file mode 100644
index 0000000..fd0e4e3
--- /dev/null
+++ b/src/time.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2021 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 <errno.h>
+#include "fido.h"
+
+static int
+timespec_to_ms(const struct timespec *ts)
+{
+ int64_t x, y;
+
+ if (ts->tv_sec < 0 || ts->tv_nsec < 0 ||
+ ts->tv_nsec >= 1000000000LL)
+ return -1;
+
+ if ((uint64_t)ts->tv_sec >= INT64_MAX / 1000LL)
+ return -1;
+
+ x = ts->tv_sec * 1000LL;
+ y = ts->tv_nsec / 1000000LL;
+
+ if (INT64_MAX - x < y || x + y > INT_MAX)
+ return -1;
+
+ return (int)(x + y);
+}
+
+int
+fido_time_now(struct timespec *ts_now)
+{
+ if (clock_gettime(CLOCK_MONOTONIC, ts_now) != 0) {
+ fido_log_error(errno, "%s: clock_gettime", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+fido_time_delta(const struct timespec *ts_start, int *ms_remain)
+{
+ struct timespec ts_end, ts_delta;
+ int ms;
+
+ if (*ms_remain < 0)
+ return 0;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts_end) != 0) {
+ fido_log_error(errno, "%s: clock_gettime", __func__);
+ return -1;
+ }
+
+ if (timespeccmp(&ts_end, ts_start, <)) {
+ fido_log_debug("%s: timespeccmp", __func__);
+ return -1;
+ }
+
+ timespecsub(&ts_end, ts_start, &ts_delta);
+
+ if ((ms = timespec_to_ms(&ts_delta)) < 0) {
+ fido_log_debug("%s: timespec_to_ms", __func__);
+ return -1;
+ }
+
+ if (ms > *ms_remain)
+ ms = *ms_remain;
+
+ *ms_remain -= ms;
+
+ return 0;
+}
diff --git a/src/touch.c b/src/touch.c
new file mode 100644
index 0000000..6844e2c
--- /dev/null
+++ b/src/touch.c
@@ -0,0 +1,109 @@
+/*
+ * 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 <openssl/sha.h>
+#include "fido.h"
+
+int
+fido_dev_get_touch_begin(fido_dev_t *dev)
+{
+ fido_blob_t f;
+ cbor_item_t *argv[9];
+ const char *clientdata = FIDO_DUMMY_CLIENTDATA;
+ const uint8_t user_id = FIDO_DUMMY_USER_ID;
+ unsigned char cdh[SHA256_DIGEST_LENGTH];
+ fido_rp_t rp;
+ fido_user_t user;
+ int ms = dev->timeout_ms;
+ int r = FIDO_ERR_INTERNAL;
+
+ memset(&f, 0, sizeof(f));
+ memset(argv, 0, sizeof(argv));
+ memset(cdh, 0, sizeof(cdh));
+ memset(&rp, 0, sizeof(rp));
+ memset(&user, 0, sizeof(user));
+
+ if (fido_dev_is_fido2(dev) == false)
+ return (u2f_get_touch_begin(dev, &ms));
+
+ if (SHA256((const void *)clientdata, strlen(clientdata), cdh) != cdh) {
+ fido_log_debug("%s: sha256", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((rp.id = strdup(FIDO_DUMMY_RP_ID)) == NULL ||
+ (user.name = strdup(FIDO_DUMMY_USER_NAME)) == NULL) {
+ fido_log_debug("%s: strdup", __func__);
+ goto fail;
+ }
+
+ if (fido_blob_set(&user.id, &user_id, sizeof(user_id)) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ goto fail;
+ }
+
+ if ((argv[0] = cbor_build_bytestring(cdh, sizeof(cdh))) == NULL ||
+ (argv[1] = cbor_encode_rp_entity(&rp)) == NULL ||
+ (argv[2] = cbor_encode_user_entity(&user)) == NULL ||
+ (argv[3] = cbor_encode_pubkey_param(COSE_ES256)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+
+ if (fido_dev_supports_pin(dev)) {
+ if ((argv[7] = cbor_new_definite_bytestring()) == NULL ||
+ (argv[8] = cbor_encode_pin_opt(dev)) == NULL) {
+ fido_log_debug("%s: cbor encode", __func__);
+ goto fail;
+ }
+ }
+
+ if (cbor_build_frame(CTAP_CBOR_MAKECRED, argv, nitems(argv), &f) < 0 ||
+ fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, &ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ cbor_vector_free(argv, nitems(argv));
+ free(f.ptr);
+ free(rp.id);
+ free(user.name);
+ free(user.id.ptr);
+
+ return (r);
+}
+
+int
+fido_dev_get_touch_status(fido_dev_t *dev, int *touched, int ms)
+{
+ int r;
+
+ *touched = 0;
+
+ if (fido_dev_is_fido2(dev) == false)
+ return (u2f_get_touch_status(dev, touched, &ms));
+
+ switch ((r = fido_rx_cbor_status(dev, &ms))) {
+ case FIDO_ERR_PIN_AUTH_INVALID:
+ case FIDO_ERR_PIN_INVALID:
+ case FIDO_ERR_PIN_NOT_SET:
+ case FIDO_ERR_SUCCESS:
+ *touched = 1;
+ break;
+ case FIDO_ERR_RX:
+ /* ignore */
+ break;
+ default:
+ fido_log_debug("%s: fido_rx_cbor_status", __func__);
+ return (r);
+ }
+
+ return (FIDO_OK);
+}
diff --git a/src/tpm.c b/src/tpm.c
new file mode 100644
index 0000000..3e09bca
--- /dev/null
+++ b/src/tpm.c
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2021 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
+ */
+
+/*
+ * Trusted Platform Module (TPM) 2.0 attestation support. Documentation
+ * references are relative to revision 01.38 of the TPM 2.0 specification.
+ */
+
+#include <openssl/sha.h>
+
+#include "packed.h"
+#include "fido.h"
+
+/* Part 1, 4.89: TPM_GENERATED_VALUE */
+#define TPM_MAGIC 0xff544347
+
+/* Part 2, 6.3: TPM_ALG_ID */
+#define TPM_ALG_RSA 0x0001
+#define TPM_ALG_SHA256 0x000b
+#define TPM_ALG_NULL 0x0010
+#define TPM_ALG_ECC 0x0023
+
+/* Part 2, 6.4: TPM_ECC_CURVE */
+#define TPM_ECC_P256 0x0003
+
+/* Part 2, 6.9: TPM_ST_ATTEST_CERTIFY */
+#define TPM_ST_CERTIFY 0x8017
+
+/* Part 2, 8.3: TPMA_OBJECT */
+#define TPMA_RESERVED 0xfff8f309 /* reserved bits; must be zero */
+#define TPMA_FIXED 0x00000002 /* object has fixed hierarchy */
+#define TPMA_CLEAR 0x00000004 /* object persists */
+#define TPMA_FIXED_P 0x00000010 /* object has fixed parent */
+#define TPMA_SENSITIVE 0x00000020 /* data originates within tpm */
+#define TPMA_SIGN 0x00040000 /* object may sign */
+
+/* Part 2, 10.4.2: TPM2B_DIGEST */
+PACKED_TYPE(tpm_sha256_digest_t,
+struct tpm_sha256_digest {
+ uint16_t size; /* sizeof(body) */
+ uint8_t body[32];
+})
+
+/* Part 2, 10.4.3: TPM2B_DATA */
+PACKED_TYPE(tpm_sha1_data_t,
+struct tpm_sha1_data {
+ uint16_t size; /* sizeof(body) */
+ uint8_t body[20];
+})
+
+/* Part 2, 10.5.3: TPM2B_NAME */
+PACKED_TYPE(tpm_sha256_name_t,
+struct tpm_sha256_name {
+ uint16_t size; /* sizeof(alg) + sizeof(body) */
+ uint16_t alg; /* TPM_ALG_SHA256 */
+ uint8_t body[32];
+})
+
+/* Part 2, 10.11.1: TPMS_CLOCK_INFO */
+PACKED_TYPE(tpm_clock_info_t,
+struct tpm_clock_info {
+ uint64_t timestamp_ms;
+ uint32_t reset_count; /* obfuscated by tpm */
+ uint32_t restart_count; /* obfuscated by tpm */
+ uint8_t safe; /* 1 if timestamp_ms is current */
+})
+
+/* Part 2, 10.12.8 TPMS_ATTEST */
+PACKED_TYPE(tpm_sha1_attest_t,
+struct tpm_sha1_attest {
+ uint32_t magic; /* TPM_MAGIC */
+ uint16_t type; /* TPM_ST_ATTEST_CERTIFY */
+ tpm_sha256_name_t signer; /* full tpm path of signing key */
+ tpm_sha1_data_t data; /* signed sha1 */
+ tpm_clock_info_t clock;
+ uint64_t fwversion; /* obfuscated by tpm */
+ tpm_sha256_name_t name; /* sha256 of tpm_rs256_pubarea_t */
+ tpm_sha256_name_t qual_name; /* full tpm path of attested key */
+})
+
+/* Part 2, 11.2.4.5: TPM2B_PUBLIC_KEY_RSA */
+PACKED_TYPE(tpm_rs256_key_t,
+struct tpm_rs256_key {
+ uint16_t size; /* sizeof(body) */
+ uint8_t body[256];
+})
+
+/* Part 2, 11.2.5.1: TPM2B_ECC_PARAMETER */
+PACKED_TYPE(tpm_es256_coord_t,
+struct tpm_es256_coord {
+ uint16_t size; /* sizeof(body) */
+ uint8_t body[32];
+})
+
+/* Part 2, 11.2.5.2: TPMS_ECC_POINT */
+PACKED_TYPE(tpm_es256_point_t,
+struct tpm_es256_point {
+ tpm_es256_coord_t x;
+ tpm_es256_coord_t y;
+})
+
+/* Part 2, 12.2.3.5: TPMS_RSA_PARMS */
+PACKED_TYPE(tpm_rs256_param_t,
+struct tpm_rs256_param {
+ uint16_t symmetric; /* TPM_ALG_NULL */
+ uint16_t scheme; /* TPM_ALG_NULL */
+ uint16_t keybits; /* 2048 */
+ uint32_t exponent; /* zero (meaning 2^16 + 1) */
+})
+
+/* Part 2, 12.2.3.6: TPMS_ECC_PARMS */
+PACKED_TYPE(tpm_es256_param_t,
+struct tpm_es256_param {
+ uint16_t symmetric; /* TPM_ALG_NULL */
+ uint16_t scheme; /* TPM_ALG_NULL */
+ uint16_t curve_id; /* TPM_ECC_P256 */
+ uint16_t kdf; /* TPM_ALG_NULL */
+})
+
+/* Part 2, 12.2.4: TPMT_PUBLIC */
+PACKED_TYPE(tpm_rs256_pubarea_t,
+struct tpm_rs256_pubarea {
+ uint16_t alg; /* TPM_ALG_RSA */
+ uint16_t hash; /* TPM_ALG_SHA256 */
+ uint32_t attr;
+ tpm_sha256_digest_t policy; /* must be present? */
+ tpm_rs256_param_t param;
+ tpm_rs256_key_t key;
+})
+
+/* Part 2, 12.2.4: TPMT_PUBLIC */
+PACKED_TYPE(tpm_es256_pubarea_t,
+struct tpm_es256_pubarea {
+ uint16_t alg; /* TPM_ALG_ECC */
+ uint16_t hash; /* TPM_ALG_SHA256 */
+ uint32_t attr;
+ tpm_sha256_digest_t policy; /* must be present? */
+ tpm_es256_param_t param;
+ tpm_es256_point_t point;
+})
+
+static int
+get_signed_sha1(tpm_sha1_data_t *dgst, const fido_blob_t *authdata,
+ const fido_blob_t *clientdata)
+{
+ const EVP_MD *md = NULL;
+ EVP_MD_CTX *ctx = NULL;
+ int ok = -1;
+
+ if ((dgst->size = sizeof(dgst->body)) != SHA_DIGEST_LENGTH ||
+ (md = EVP_sha1()) == NULL ||
+ (ctx = EVP_MD_CTX_new()) == NULL ||
+ EVP_DigestInit_ex(ctx, md, NULL) != 1 ||
+ EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 ||
+ EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 ||
+ EVP_DigestFinal_ex(ctx, dgst->body, NULL) != 1) {
+ fido_log_debug("%s: sha1", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ EVP_MD_CTX_free(ctx);
+
+ return (ok);
+}
+
+static int
+get_signed_name(tpm_sha256_name_t *name, const fido_blob_t *pubarea)
+{
+ name->alg = TPM_ALG_SHA256;
+ name->size = sizeof(name->alg) + sizeof(name->body);
+ if (sizeof(name->body) != SHA256_DIGEST_LENGTH ||
+ SHA256(pubarea->ptr, pubarea->len, name->body) != name->body) {
+ fido_log_debug("%s: sha256", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+bswap_rs256_pubarea(tpm_rs256_pubarea_t *x)
+{
+ x->alg = htobe16(x->alg);
+ x->hash = htobe16(x->hash);
+ x->attr = htobe32(x->attr);
+ x->policy.size = htobe16(x->policy.size);
+ x->param.symmetric = htobe16(x->param.symmetric);
+ x->param.scheme = htobe16(x->param.scheme);
+ x->param.keybits = htobe16(x->param.keybits);
+ x->key.size = htobe16(x->key.size);
+}
+
+static void
+bswap_es256_pubarea(tpm_es256_pubarea_t *x)
+{
+ x->alg = htobe16(x->alg);
+ x->hash = htobe16(x->hash);
+ x->attr = htobe32(x->attr);
+ x->policy.size = htobe16(x->policy.size);
+ x->param.symmetric = htobe16(x->param.symmetric);
+ x->param.scheme = htobe16(x->param.scheme);
+ x->param.curve_id = htobe16(x->param.curve_id);
+ x->param.kdf = htobe16(x->param.kdf);
+ x->point.x.size = htobe16(x->point.x.size);
+ x->point.y.size = htobe16(x->point.y.size);
+}
+
+static void
+bswap_sha1_certinfo(tpm_sha1_attest_t *x)
+{
+ x->magic = htobe32(x->magic);
+ x->type = htobe16(x->type);
+ x->signer.size = htobe16(x->signer.size);
+ x->data.size = htobe16(x->data.size);
+ x->name.alg = htobe16(x->name.alg);
+ x->name.size = htobe16(x->name.size);
+}
+
+static int
+check_rs256_pubarea(const fido_blob_t *buf, const rs256_pk_t *pk)
+{
+ const tpm_rs256_pubarea_t *actual;
+ tpm_rs256_pubarea_t expected;
+ int ok;
+
+ if (buf->len != sizeof(*actual)) {
+ fido_log_debug("%s: buf->len=%zu", __func__, buf->len);
+ return -1;
+ }
+ actual = (const void *)buf->ptr;
+
+ memset(&expected, 0, sizeof(expected));
+ expected.alg = TPM_ALG_RSA;
+ expected.hash = TPM_ALG_SHA256;
+ expected.attr = be32toh(actual->attr);
+ expected.attr &= ~(TPMA_RESERVED|TPMA_CLEAR);
+ expected.attr |= (TPMA_FIXED|TPMA_FIXED_P|TPMA_SENSITIVE|TPMA_SIGN);
+ expected.policy = actual->policy;
+ expected.policy.size = sizeof(expected.policy.body);
+ expected.param.symmetric = TPM_ALG_NULL;
+ expected.param.scheme = TPM_ALG_NULL;
+ expected.param.keybits = 2048;
+ expected.param.exponent = 0; /* meaning 2^16+1 */
+ expected.key.size = sizeof(expected.key.body);
+ memcpy(&expected.key.body, &pk->n, sizeof(expected.key.body));
+ bswap_rs256_pubarea(&expected);
+
+ ok = timingsafe_bcmp(&expected, actual, sizeof(expected));
+ explicit_bzero(&expected, sizeof(expected));
+
+ return ok != 0 ? -1 : 0;
+}
+
+static int
+check_es256_pubarea(const fido_blob_t *buf, const es256_pk_t *pk)
+{
+ const tpm_es256_pubarea_t *actual;
+ tpm_es256_pubarea_t expected;
+ int ok;
+
+ if (buf->len != sizeof(*actual)) {
+ fido_log_debug("%s: buf->len=%zu", __func__, buf->len);
+ return -1;
+ }
+ actual = (const void *)buf->ptr;
+
+ memset(&expected, 0, sizeof(expected));
+ expected.alg = TPM_ALG_ECC;
+ expected.hash = TPM_ALG_SHA256;
+ expected.attr = be32toh(actual->attr);
+ expected.attr &= ~(TPMA_RESERVED|TPMA_CLEAR);
+ expected.attr |= (TPMA_FIXED|TPMA_FIXED_P|TPMA_SENSITIVE|TPMA_SIGN);
+ expected.policy = actual->policy;
+ expected.policy.size = sizeof(expected.policy.body);
+ expected.param.symmetric = TPM_ALG_NULL;
+ expected.param.scheme = TPM_ALG_NULL; /* TCG Alg. Registry, 5.2.4 */
+ expected.param.curve_id = TPM_ECC_P256;
+ expected.param.kdf = TPM_ALG_NULL;
+ expected.point.x.size = sizeof(expected.point.x.body);
+ expected.point.y.size = sizeof(expected.point.y.body);
+ memcpy(&expected.point.x.body, &pk->x, sizeof(expected.point.x.body));
+ memcpy(&expected.point.y.body, &pk->y, sizeof(expected.point.y.body));
+ bswap_es256_pubarea(&expected);
+
+ ok = timingsafe_bcmp(&expected, actual, sizeof(expected));
+ explicit_bzero(&expected, sizeof(expected));
+
+ return ok != 0 ? -1 : 0;
+}
+
+static int
+check_sha1_certinfo(const fido_blob_t *buf, const fido_blob_t *clientdata_hash,
+ const fido_blob_t *authdata_raw, const fido_blob_t *pubarea)
+{
+ const tpm_sha1_attest_t *actual;
+ tpm_sha1_attest_t expected;
+ tpm_sha1_data_t signed_data;
+ tpm_sha256_name_t signed_name;
+ int ok = -1;
+
+ memset(&signed_data, 0, sizeof(signed_data));
+ memset(&signed_name, 0, sizeof(signed_name));
+
+ if (get_signed_sha1(&signed_data, authdata_raw, clientdata_hash) < 0 ||
+ get_signed_name(&signed_name, pubarea) < 0) {
+ fido_log_debug("%s: get_signed_sha1/name", __func__);
+ goto fail;
+ }
+ if (buf->len != sizeof(*actual)) {
+ fido_log_debug("%s: buf->len=%zu", __func__, buf->len);
+ goto fail;
+ }
+ actual = (const void *)buf->ptr;
+
+ memset(&expected, 0, sizeof(expected));
+ expected.magic = TPM_MAGIC;
+ expected.type = TPM_ST_CERTIFY;
+ expected.signer = actual->signer;
+ expected.signer.size = sizeof(expected.signer.alg) +
+ sizeof(expected.signer.body);
+ expected.data = signed_data;
+ expected.clock = actual->clock;
+ expected.clock.safe = 1;
+ expected.fwversion = actual->fwversion;
+ expected.name = signed_name;
+ expected.qual_name = actual->qual_name;
+ bswap_sha1_certinfo(&expected);
+
+ ok = timingsafe_bcmp(&expected, actual, sizeof(expected));
+fail:
+ explicit_bzero(&expected, sizeof(expected));
+ explicit_bzero(&signed_data, sizeof(signed_data));
+ explicit_bzero(&signed_name, sizeof(signed_name));
+
+ return ok != 0 ? -1 : 0;
+}
+
+int
+fido_get_signed_hash_tpm(fido_blob_t *dgst, const fido_blob_t *clientdata_hash,
+ const fido_blob_t *authdata_raw, const fido_attstmt_t *attstmt,
+ const fido_attcred_t *attcred)
+{
+ const fido_blob_t *pubarea = &attstmt->pubarea;
+ const fido_blob_t *certinfo = &attstmt->certinfo;
+
+ if (attstmt->alg != COSE_RS1) {
+ fido_log_debug("%s: unsupported alg %d", __func__,
+ attstmt->alg);
+ return -1;
+ }
+
+ switch (attcred->type) {
+ case COSE_ES256:
+ if (check_es256_pubarea(pubarea, &attcred->pubkey.es256) < 0) {
+ fido_log_debug("%s: check_es256_pubarea", __func__);
+ return -1;
+ }
+ break;
+ case COSE_RS256:
+ if (check_rs256_pubarea(pubarea, &attcred->pubkey.rs256) < 0) {
+ fido_log_debug("%s: check_rs256_pubarea", __func__);
+ return -1;
+ }
+ break;
+ default:
+ fido_log_debug("%s: unsupported type %d", __func__,
+ attcred->type);
+ return -1;
+ }
+
+ if (check_sha1_certinfo(certinfo, clientdata_hash, authdata_raw,
+ pubarea) < 0) {
+ fido_log_debug("%s: check_sha1_certinfo", __func__);
+ return -1;
+ }
+
+ if (dgst->len < SHA_DIGEST_LENGTH ||
+ SHA1(certinfo->ptr, certinfo->len, dgst->ptr) != dgst->ptr) {
+ fido_log_debug("%s: sha1", __func__);
+ return -1;
+ }
+ dgst->len = SHA_DIGEST_LENGTH;
+
+ return 0;
+}
diff --git a/src/types.c b/src/types.c
new file mode 100644
index 0000000..f31f8da
--- /dev/null
+++ b/src/types.c
@@ -0,0 +1,91 @@
+/*
+ * 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"
+
+void
+fido_str_array_free(fido_str_array_t *sa)
+{
+ for (size_t i = 0; i < sa->len; i++)
+ free(sa->ptr[i]);
+
+ free(sa->ptr);
+ sa->ptr = NULL;
+ sa->len = 0;
+}
+
+void
+fido_opt_array_free(fido_opt_array_t *oa)
+{
+ for (size_t i = 0; i < oa->len; i++)
+ free(oa->name[i]);
+
+ free(oa->name);
+ free(oa->value);
+ oa->name = NULL;
+ oa->value = NULL;
+ oa->len = 0;
+}
+
+void
+fido_byte_array_free(fido_byte_array_t *ba)
+{
+ free(ba->ptr);
+
+ ba->ptr = NULL;
+ ba->len = 0;
+}
+
+void
+fido_algo_free(fido_algo_t *a)
+{
+ free(a->type);
+ a->type = NULL;
+ a->cose = 0;
+}
+
+void
+fido_algo_array_free(fido_algo_array_t *aa)
+{
+ for (size_t i = 0; i < aa->len; i++)
+ fido_algo_free(&aa->ptr[i]);
+
+ free(aa->ptr);
+ aa->ptr = NULL;
+ aa->len = 0;
+}
+
+void
+fido_cert_array_free(fido_cert_array_t *ca)
+{
+ for (size_t i = 0; i < ca->len; i++)
+ free(ca->name[i]);
+
+ free(ca->name);
+ free(ca->value);
+ ca->name = NULL;
+ ca->value = NULL;
+ ca->len = 0;
+}
+
+int
+fido_str_array_pack(fido_str_array_t *sa, const char * const *v, size_t n)
+{
+ if ((sa->ptr = calloc(n, sizeof(char *))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return -1;
+ }
+ for (size_t i = 0; i < n; i++) {
+ if ((sa->ptr[i] = strdup(v[i])) == NULL) {
+ fido_log_debug("%s: strdup", __func__);
+ return -1;
+ }
+ sa->len++;
+ }
+
+ return 0;
+}
diff --git a/src/u2f.c b/src/u2f.c
new file mode 100644
index 0000000..b1f7bce
--- /dev/null
+++ b/src/u2f.c
@@ -0,0 +1,959 @@
+/*
+ * 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 <openssl/sha.h>
+#include <openssl/x509.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <errno.h>
+
+#include "fido.h"
+#include "fido/es256.h"
+#include "fallthrough.h"
+
+#define U2F_PACE_MS (100)
+
+#if defined(_MSC_VER)
+static int
+usleep(unsigned int usec)
+{
+ Sleep(usec / 1000);
+
+ return (0);
+}
+#endif
+
+static int
+delay_ms(unsigned int ms, int *ms_remain)
+{
+ if (*ms_remain > -1 && (unsigned int)*ms_remain < ms)
+ ms = (unsigned int)*ms_remain;
+
+ if (ms > UINT_MAX / 1000) {
+ fido_log_debug("%s: ms=%u", __func__, ms);
+ return (-1);
+ }
+
+ if (usleep(ms * 1000) < 0) {
+ fido_log_error(errno, "%s: usleep", __func__);
+ return (-1);
+ }
+
+ if (*ms_remain > -1)
+ *ms_remain -= (int)ms;
+
+ return (0);
+}
+
+static int
+sig_get(fido_blob_t *sig, const unsigned char **buf, size_t *len)
+{
+ sig->len = *len; /* consume the whole buffer */
+ if ((sig->ptr = calloc(1, sig->len)) == NULL ||
+ fido_buf_read(buf, len, sig->ptr, sig->len) < 0) {
+ fido_log_debug("%s: fido_buf_read", __func__);
+ fido_blob_reset(sig);
+ return (-1);
+ }
+
+ return (0);
+}
+
+static int
+x5c_get(fido_blob_t *x5c, const unsigned char **buf, size_t *len)
+{
+ X509 *cert = NULL;
+ int ok = -1;
+
+ if (*len > LONG_MAX) {
+ fido_log_debug("%s: invalid len %zu", __func__, *len);
+ goto fail;
+ }
+
+ /* find out the certificate's length */
+ const unsigned char *end = *buf;
+ if ((cert = d2i_X509(NULL, &end, (long)*len)) == NULL || end <= *buf ||
+ (x5c->len = (size_t)(end - *buf)) >= *len) {
+ fido_log_debug("%s: d2i_X509", __func__);
+ goto fail;
+ }
+
+ /* read accordingly */
+ if ((x5c->ptr = calloc(1, x5c->len)) == NULL ||
+ fido_buf_read(buf, len, x5c->ptr, x5c->len) < 0) {
+ fido_log_debug("%s: fido_buf_read", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (cert != NULL)
+ X509_free(cert);
+
+ if (ok < 0)
+ fido_blob_reset(x5c);
+
+ return (ok);
+}
+
+static int
+authdata_fake(const char *rp_id, uint8_t flags, uint32_t sigcount,
+ fido_blob_t *fake_cbor_ad)
+{
+ fido_authdata_t ad;
+ cbor_item_t *item = NULL;
+ size_t alloc_len;
+
+ memset(&ad, 0, sizeof(ad));
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ ad.rp_id_hash) != ad.rp_id_hash) {
+ fido_log_debug("%s: sha256", __func__);
+ return (-1);
+ }
+
+ ad.flags = flags; /* XXX translate? */
+ ad.sigcount = sigcount;
+
+ if ((item = cbor_build_bytestring((const unsigned char *)&ad,
+ sizeof(ad))) == NULL) {
+ fido_log_debug("%s: cbor_build_bytestring", __func__);
+ return (-1);
+ }
+
+ if (fake_cbor_ad->ptr != NULL ||
+ (fake_cbor_ad->len = cbor_serialize_alloc(item, &fake_cbor_ad->ptr,
+ &alloc_len)) == 0) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ cbor_decref(&item);
+ return (-1);
+ }
+
+ cbor_decref(&item);
+
+ return (0);
+}
+
+/* TODO: use u2f_get_touch_begin & u2f_get_touch_status instead */
+static int
+send_dummy_register(fido_dev_t *dev, int *ms)
+{
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char *reply = NULL;
+ unsigned char challenge[SHA256_DIGEST_LENGTH];
+ unsigned char application[SHA256_DIGEST_LENGTH];
+ int r;
+
+ /* dummy challenge & application */
+ memset(&challenge, 0xff, sizeof(challenge));
+ memset(&application, 0xff, sizeof(application));
+
+ if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 *
+ SHA256_DIGEST_LENGTH)) == NULL ||
+ iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 ||
+ iso7816_add(apdu, &application, sizeof(application)) < 0) {
+ fido_log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((reply = malloc(FIDO_MAXMSG)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ do {
+ if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu),
+ iso7816_len(apdu), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if (fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, ms) < 2) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ if (delay_ms(U2F_PACE_MS, ms) != 0) {
+ fido_log_debug("%s: delay_ms", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED);
+
+ r = FIDO_OK;
+fail:
+ iso7816_free(&apdu);
+ freezero(reply, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+key_lookup(fido_dev_t *dev, const char *rp_id, const fido_blob_t *key_id,
+ int *found, int *ms)
+{
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char *reply = NULL;
+ unsigned char challenge[SHA256_DIGEST_LENGTH];
+ unsigned char rp_id_hash[SHA256_DIGEST_LENGTH];
+ uint8_t key_id_len;
+ int r;
+
+ if (key_id->len > UINT8_MAX || rp_id == NULL) {
+ fido_log_debug("%s: key_id->len=%zu, rp_id=%p", __func__,
+ key_id->len, (const void *)rp_id);
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ memset(&challenge, 0xff, sizeof(challenge));
+ memset(&rp_id_hash, 0, sizeof(rp_id_hash));
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ rp_id_hash) != rp_id_hash) {
+ fido_log_debug("%s: sha256", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ key_id_len = (uint8_t)key_id->len;
+
+ if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_CHECK, (uint16_t)(2 *
+ SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL ||
+ iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 ||
+ iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 ||
+ iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 ||
+ iso7816_add(apdu, key_id->ptr, key_id_len) < 0) {
+ fido_log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((reply = malloc(FIDO_MAXMSG)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu),
+ iso7816_len(apdu), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if (fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, ms) != 2) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+
+ switch ((reply[0] << 8) | reply[1]) {
+ case SW_CONDITIONS_NOT_SATISFIED:
+ *found = 1; /* key exists */
+ break;
+ case SW_WRONG_DATA:
+ *found = 0; /* key does not exist */
+ break;
+ default:
+ /* unexpected sw */
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ iso7816_free(&apdu);
+ freezero(reply, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+parse_auth_reply(fido_blob_t *sig, fido_blob_t *ad, const char *rp_id,
+ const unsigned char *reply, size_t len)
+{
+ uint8_t flags;
+ uint32_t sigcount;
+
+ if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) {
+ fido_log_debug("%s: unexpected sw", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ len -= 2;
+
+ if (fido_buf_read(&reply, &len, &flags, sizeof(flags)) < 0 ||
+ fido_buf_read(&reply, &len, &sigcount, sizeof(sigcount)) < 0) {
+ fido_log_debug("%s: fido_buf_read", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if (sig_get(sig, &reply, &len) < 0) {
+ fido_log_debug("%s: sig_get", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ if (authdata_fake(rp_id, flags, sigcount, ad) < 0) {
+ fido_log_debug("%s; authdata_fake", __func__);
+ return (FIDO_ERR_RX);
+ }
+
+ return (FIDO_OK);
+}
+
+static int
+do_auth(fido_dev_t *dev, const fido_blob_t *cdh, const char *rp_id,
+ const fido_blob_t *key_id, fido_blob_t *sig, fido_blob_t *ad, int *ms)
+{
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char *reply = NULL;
+ unsigned char rp_id_hash[SHA256_DIGEST_LENGTH];
+ int reply_len;
+ uint8_t key_id_len;
+ int r;
+
+#ifdef FIDO_FUZZ
+ *ms = 0; /* XXX */
+#endif
+
+ if (cdh->len != SHA256_DIGEST_LENGTH || key_id->len > UINT8_MAX ||
+ rp_id == NULL) {
+ r = FIDO_ERR_INVALID_ARGUMENT;
+ goto fail;
+ }
+
+ memset(&rp_id_hash, 0, sizeof(rp_id_hash));
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ rp_id_hash) != rp_id_hash) {
+ fido_log_debug("%s: sha256", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ key_id_len = (uint8_t)key_id->len;
+
+ if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_SIGN, (uint16_t)(2 *
+ SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL ||
+ iso7816_add(apdu, cdh->ptr, cdh->len) < 0 ||
+ iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 ||
+ iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 ||
+ iso7816_add(apdu, key_id->ptr, key_id_len) < 0) {
+ fido_log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((reply = malloc(FIDO_MAXMSG)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ do {
+ if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu),
+ iso7816_len(apdu), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply,
+ FIDO_MAXMSG, ms)) < 2) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ if (delay_ms(U2F_PACE_MS, ms) != 0) {
+ fido_log_debug("%s: delay_ms", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED);
+
+ if ((r = parse_auth_reply(sig, ad, rp_id, reply,
+ (size_t)reply_len)) != FIDO_OK) {
+ fido_log_debug("%s: parse_auth_reply", __func__);
+ goto fail;
+ }
+
+fail:
+ iso7816_free(&apdu);
+ freezero(reply, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+cbor_blob_from_ec_point(const uint8_t *ec_point, size_t ec_point_len,
+ fido_blob_t *cbor_blob)
+{
+ es256_pk_t *pk = NULL;
+ cbor_item_t *pk_cbor = NULL;
+ size_t alloc_len;
+ int ok = -1;
+
+ /* only handle uncompressed points */
+ if (ec_point_len != 65 || ec_point[0] != 0x04) {
+ fido_log_debug("%s: unexpected format", __func__);
+ goto fail;
+ }
+
+ if ((pk = es256_pk_new()) == NULL ||
+ es256_pk_set_x(pk, &ec_point[1]) < 0 ||
+ es256_pk_set_y(pk, &ec_point[33]) < 0) {
+ fido_log_debug("%s: es256_pk_set", __func__);
+ goto fail;
+ }
+
+ if ((pk_cbor = es256_pk_encode(pk, 0)) == NULL) {
+ fido_log_debug("%s: es256_pk_encode", __func__);
+ goto fail;
+ }
+
+ if ((cbor_blob->len = cbor_serialize_alloc(pk_cbor, &cbor_blob->ptr,
+ &alloc_len)) != 77) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es256_pk_free(&pk);
+
+ if (pk_cbor)
+ cbor_decref(&pk_cbor);
+
+ return (ok);
+}
+
+static int
+encode_cred_attstmt(int cose_alg, const fido_blob_t *x5c,
+ const fido_blob_t *sig, fido_blob_t *out)
+{
+ cbor_item_t *item = NULL;
+ cbor_item_t *x5c_cbor = NULL;
+ const uint8_t alg_cbor = (uint8_t)(-cose_alg - 1);
+ struct cbor_pair kv[3];
+ size_t alloc_len;
+ int ok = -1;
+
+ memset(&kv, 0, sizeof(kv));
+ memset(out, 0, sizeof(*out));
+
+ if ((item = cbor_new_definite_map(3)) == NULL) {
+ fido_log_debug("%s: cbor_new_definite_map", __func__);
+ goto fail;
+ }
+
+ if ((kv[0].key = cbor_build_string("alg")) == NULL ||
+ (kv[0].value = cbor_build_negint8(alg_cbor)) == NULL ||
+ !cbor_map_add(item, kv[0])) {
+ fido_log_debug("%s: alg", __func__);
+ goto fail;
+ }
+
+ if ((kv[1].key = cbor_build_string("sig")) == NULL ||
+ (kv[1].value = fido_blob_encode(sig)) == NULL ||
+ !cbor_map_add(item, kv[1])) {
+ fido_log_debug("%s: sig", __func__);
+ goto fail;
+ }
+
+ if ((kv[2].key = cbor_build_string("x5c")) == NULL ||
+ (kv[2].value = cbor_new_definite_array(1)) == NULL ||
+ (x5c_cbor = fido_blob_encode(x5c)) == NULL ||
+ !cbor_array_push(kv[2].value, x5c_cbor) ||
+ !cbor_map_add(item, kv[2])) {
+ fido_log_debug("%s: x5c", __func__);
+ goto fail;
+ }
+
+ if ((out->len = cbor_serialize_alloc(item, &out->ptr,
+ &alloc_len)) == 0) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+ if (x5c_cbor != NULL)
+ cbor_decref(&x5c_cbor);
+
+ for (size_t i = 0; i < nitems(kv); i++) {
+ if (kv[i].key)
+ cbor_decref(&kv[i].key);
+ if (kv[i].value)
+ cbor_decref(&kv[i].value);
+ }
+
+ return (ok);
+}
+
+static int
+encode_cred_authdata(const char *rp_id, const uint8_t *kh, uint8_t kh_len,
+ const uint8_t *pubkey, size_t pubkey_len, fido_blob_t *out)
+{
+ fido_authdata_t authdata;
+ fido_attcred_raw_t attcred_raw;
+ fido_blob_t pk_blob;
+ fido_blob_t authdata_blob;
+ cbor_item_t *authdata_cbor = NULL;
+ unsigned char *ptr;
+ size_t len;
+ size_t alloc_len;
+ int ok = -1;
+
+ memset(&pk_blob, 0, sizeof(pk_blob));
+ memset(&authdata, 0, sizeof(authdata));
+ memset(&authdata_blob, 0, sizeof(authdata_blob));
+ memset(out, 0, sizeof(*out));
+
+ if (rp_id == NULL) {
+ fido_log_debug("%s: NULL rp_id", __func__);
+ goto fail;
+ }
+
+ if (cbor_blob_from_ec_point(pubkey, pubkey_len, &pk_blob) < 0) {
+ fido_log_debug("%s: cbor_blob_from_ec_point", __func__);
+ goto fail;
+ }
+
+ if (SHA256((const void *)rp_id, strlen(rp_id),
+ authdata.rp_id_hash) != authdata.rp_id_hash) {
+ fido_log_debug("%s: sha256", __func__);
+ goto fail;
+ }
+
+ authdata.flags = (CTAP_AUTHDATA_ATT_CRED | CTAP_AUTHDATA_USER_PRESENT);
+ authdata.sigcount = 0;
+
+ memset(&attcred_raw.aaguid, 0, sizeof(attcred_raw.aaguid));
+ attcred_raw.id_len = htobe16(kh_len);
+
+ len = authdata_blob.len = sizeof(authdata) + sizeof(attcred_raw) +
+ kh_len + pk_blob.len;
+ ptr = authdata_blob.ptr = calloc(1, authdata_blob.len);
+
+ fido_log_debug("%s: ptr=%p, len=%zu", __func__, (void *)ptr, len);
+
+ if (authdata_blob.ptr == NULL)
+ goto fail;
+
+ if (fido_buf_write(&ptr, &len, &authdata, sizeof(authdata)) < 0 ||
+ fido_buf_write(&ptr, &len, &attcred_raw, sizeof(attcred_raw)) < 0 ||
+ fido_buf_write(&ptr, &len, kh, kh_len) < 0 ||
+ fido_buf_write(&ptr, &len, pk_blob.ptr, pk_blob.len) < 0) {
+ fido_log_debug("%s: fido_buf_write", __func__);
+ goto fail;
+ }
+
+ if ((authdata_cbor = fido_blob_encode(&authdata_blob)) == NULL) {
+ fido_log_debug("%s: fido_blob_encode", __func__);
+ goto fail;
+ }
+
+ if ((out->len = cbor_serialize_alloc(authdata_cbor, &out->ptr,
+ &alloc_len)) == 0) {
+ fido_log_debug("%s: cbor_serialize_alloc", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (authdata_cbor)
+ cbor_decref(&authdata_cbor);
+
+ fido_blob_reset(&pk_blob);
+ fido_blob_reset(&authdata_blob);
+
+ return (ok);
+}
+
+static int
+parse_register_reply(fido_cred_t *cred, const unsigned char *reply, size_t len)
+{
+ fido_blob_t x5c;
+ fido_blob_t sig;
+ fido_blob_t ad;
+ fido_blob_t stmt;
+ uint8_t dummy;
+ uint8_t pubkey[65];
+ uint8_t kh_len = 0;
+ uint8_t *kh = NULL;
+ int r;
+
+ memset(&x5c, 0, sizeof(x5c));
+ memset(&sig, 0, sizeof(sig));
+ memset(&ad, 0, sizeof(ad));
+ memset(&stmt, 0, sizeof(stmt));
+ r = FIDO_ERR_RX;
+
+ /* status word */
+ if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) {
+ fido_log_debug("%s: unexpected sw", __func__);
+ goto fail;
+ }
+
+ len -= 2;
+
+ /* reserved byte */
+ if (fido_buf_read(&reply, &len, &dummy, sizeof(dummy)) < 0 ||
+ dummy != 0x05) {
+ fido_log_debug("%s: reserved byte", __func__);
+ goto fail;
+ }
+
+ /* pubkey + key handle */
+ if (fido_buf_read(&reply, &len, &pubkey, sizeof(pubkey)) < 0 ||
+ fido_buf_read(&reply, &len, &kh_len, sizeof(kh_len)) < 0 ||
+ (kh = calloc(1, kh_len)) == NULL ||
+ fido_buf_read(&reply, &len, kh, kh_len) < 0) {
+ fido_log_debug("%s: fido_buf_read", __func__);
+ goto fail;
+ }
+
+ /* x5c + sig */
+ if (x5c_get(&x5c, &reply, &len) < 0 ||
+ sig_get(&sig, &reply, &len) < 0) {
+ fido_log_debug("%s: x5c || sig", __func__);
+ goto fail;
+ }
+
+ /* attstmt */
+ if (encode_cred_attstmt(COSE_ES256, &x5c, &sig, &stmt) < 0) {
+ fido_log_debug("%s: encode_cred_attstmt", __func__);
+ goto fail;
+ }
+
+ /* authdata */
+ if (encode_cred_authdata(cred->rp.id, kh, kh_len, pubkey,
+ sizeof(pubkey), &ad) < 0) {
+ fido_log_debug("%s: encode_cred_authdata", __func__);
+ goto fail;
+ }
+
+ if (fido_cred_set_fmt(cred, "fido-u2f") != FIDO_OK ||
+ fido_cred_set_authdata(cred, ad.ptr, ad.len) != FIDO_OK ||
+ fido_cred_set_attstmt(cred, stmt.ptr, stmt.len) != FIDO_OK) {
+ fido_log_debug("%s: fido_cred_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ freezero(kh, kh_len);
+ fido_blob_reset(&x5c);
+ fido_blob_reset(&sig);
+ fido_blob_reset(&ad);
+ fido_blob_reset(&stmt);
+
+ return (r);
+}
+
+int
+u2f_register(fido_dev_t *dev, fido_cred_t *cred, int *ms)
+{
+ iso7816_apdu_t *apdu = NULL;
+ unsigned char rp_id_hash[SHA256_DIGEST_LENGTH];
+ unsigned char *reply = NULL;
+ int reply_len;
+ int found;
+ int r;
+
+ if (cred->rk == FIDO_OPT_TRUE || cred->uv == FIDO_OPT_TRUE) {
+ fido_log_debug("%s: rk=%d, uv=%d", __func__, cred->rk,
+ cred->uv);
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ }
+
+ if (cred->type != COSE_ES256 || cred->cdh.ptr == NULL ||
+ cred->rp.id == NULL || cred->cdh.len != SHA256_DIGEST_LENGTH) {
+ fido_log_debug("%s: type=%d, cdh=(%p,%zu)" , __func__,
+ cred->type, (void *)cred->cdh.ptr, cred->cdh.len);
+ return (FIDO_ERR_INVALID_ARGUMENT);
+ }
+
+ for (size_t i = 0; i < cred->excl.len; i++) {
+ if ((r = key_lookup(dev, cred->rp.id, &cred->excl.ptr[i],
+ &found, ms)) != FIDO_OK) {
+ fido_log_debug("%s: key_lookup", __func__);
+ return (r);
+ }
+ if (found) {
+ if ((r = send_dummy_register(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: send_dummy_register",
+ __func__);
+ return (r);
+ }
+ return (FIDO_ERR_CREDENTIAL_EXCLUDED);
+ }
+ }
+
+ memset(&rp_id_hash, 0, sizeof(rp_id_hash));
+
+ if (SHA256((const void *)cred->rp.id, strlen(cred->rp.id),
+ rp_id_hash) != rp_id_hash) {
+ fido_log_debug("%s: sha256", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 *
+ SHA256_DIGEST_LENGTH)) == NULL ||
+ iso7816_add(apdu, cred->cdh.ptr, cred->cdh.len) < 0 ||
+ iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) {
+ fido_log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((reply = malloc(FIDO_MAXMSG)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ do {
+ if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu),
+ iso7816_len(apdu), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+ if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply,
+ FIDO_MAXMSG, ms)) < 2) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ if (delay_ms(U2F_PACE_MS, ms) != 0) {
+ fido_log_debug("%s: delay_ms", __func__);
+ r = FIDO_ERR_RX;
+ goto fail;
+ }
+ } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED);
+
+ if ((r = parse_register_reply(cred, reply,
+ (size_t)reply_len)) != FIDO_OK) {
+ fido_log_debug("%s: parse_register_reply", __func__);
+ goto fail;
+ }
+fail:
+ iso7816_free(&apdu);
+ freezero(reply, FIDO_MAXMSG);
+
+ return (r);
+}
+
+static int
+u2f_authenticate_single(fido_dev_t *dev, const fido_blob_t *key_id,
+ fido_assert_t *fa, size_t idx, int *ms)
+{
+ fido_blob_t sig;
+ fido_blob_t ad;
+ int found;
+ int r;
+
+ memset(&sig, 0, sizeof(sig));
+ memset(&ad, 0, sizeof(ad));
+
+ if ((r = key_lookup(dev, fa->rp_id, key_id, &found, ms)) != FIDO_OK) {
+ fido_log_debug("%s: key_lookup", __func__);
+ goto fail;
+ }
+
+ if (!found) {
+ fido_log_debug("%s: not found", __func__);
+ r = FIDO_ERR_CREDENTIAL_EXCLUDED;
+ goto fail;
+ }
+
+ if (fido_blob_set(&fa->stmt[idx].id, key_id->ptr, key_id->len) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (fa->up == FIDO_OPT_FALSE) {
+ fido_log_debug("%s: checking for key existence only", __func__);
+ r = FIDO_ERR_USER_PRESENCE_REQUIRED;
+ goto fail;
+ }
+
+ if ((r = do_auth(dev, &fa->cdh, fa->rp_id, key_id, &sig, &ad,
+ ms)) != FIDO_OK) {
+ fido_log_debug("%s: do_auth", __func__);
+ goto fail;
+ }
+
+ if (fido_assert_set_authdata(fa, idx, ad.ptr, ad.len) != FIDO_OK ||
+ fido_assert_set_sig(fa, idx, sig.ptr, sig.len) != FIDO_OK) {
+ fido_log_debug("%s: fido_assert_set", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ fido_blob_reset(&sig);
+ fido_blob_reset(&ad);
+
+ return (r);
+}
+
+int
+u2f_authenticate(fido_dev_t *dev, fido_assert_t *fa, int *ms)
+{
+ size_t nfound = 0;
+ size_t nauth_ok = 0;
+ int r;
+
+ if (fa->uv == FIDO_OPT_TRUE || fa->allow_list.ptr == NULL) {
+ fido_log_debug("%s: uv=%d, allow_list=%p", __func__, fa->uv,
+ (void *)fa->allow_list.ptr);
+ return (FIDO_ERR_UNSUPPORTED_OPTION);
+ }
+
+ if ((r = fido_assert_set_count(fa, fa->allow_list.len)) != FIDO_OK) {
+ fido_log_debug("%s: fido_assert_set_count", __func__);
+ return (r);
+ }
+
+ for (size_t i = 0; i < fa->allow_list.len; i++) {
+ switch ((r = u2f_authenticate_single(dev,
+ &fa->allow_list.ptr[i], fa, nfound, ms))) {
+ case FIDO_OK:
+ nauth_ok++;
+ FALLTHROUGH
+ case FIDO_ERR_USER_PRESENCE_REQUIRED:
+ nfound++;
+ break;
+ default:
+ if (r != FIDO_ERR_CREDENTIAL_EXCLUDED) {
+ fido_log_debug("%s: u2f_authenticate_single",
+ __func__);
+ return (r);
+ }
+ /* ignore credentials that don't exist */
+ }
+ }
+
+ fa->stmt_len = nfound;
+
+ if (nfound == 0)
+ return (FIDO_ERR_NO_CREDENTIALS);
+ if (nauth_ok == 0)
+ return (FIDO_ERR_USER_PRESENCE_REQUIRED);
+
+ return (FIDO_OK);
+}
+
+int
+u2f_get_touch_begin(fido_dev_t *dev, int *ms)
+{
+ iso7816_apdu_t *apdu = NULL;
+ const char *clientdata = FIDO_DUMMY_CLIENTDATA;
+ const char *rp_id = FIDO_DUMMY_RP_ID;
+ unsigned char *reply = NULL;
+ unsigned char clientdata_hash[SHA256_DIGEST_LENGTH];
+ unsigned char rp_id_hash[SHA256_DIGEST_LENGTH];
+ int r;
+
+ memset(&clientdata_hash, 0, sizeof(clientdata_hash));
+ memset(&rp_id_hash, 0, sizeof(rp_id_hash));
+
+ if (SHA256((const void *)clientdata, strlen(clientdata),
+ clientdata_hash) != clientdata_hash || SHA256((const void *)rp_id,
+ strlen(rp_id), rp_id_hash) != rp_id_hash) {
+ fido_log_debug("%s: sha256", __func__);
+ return (FIDO_ERR_INTERNAL);
+ }
+
+ if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 *
+ SHA256_DIGEST_LENGTH)) == NULL ||
+ iso7816_add(apdu, clientdata_hash, sizeof(clientdata_hash)) < 0 ||
+ iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) {
+ fido_log_debug("%s: iso7816", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if ((reply = malloc(FIDO_MAXMSG)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto fail;
+ }
+
+ if (dev->attr.flags & FIDO_CAP_WINK) {
+ fido_tx(dev, CTAP_CMD_WINK, NULL, 0, ms);
+ fido_rx(dev, CTAP_CMD_WINK, reply, FIDO_MAXMSG, ms);
+ }
+
+ if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu),
+ iso7816_len(apdu), ms) < 0) {
+ fido_log_debug("%s: fido_tx", __func__);
+ r = FIDO_ERR_TX;
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ iso7816_free(&apdu);
+ freezero(reply, FIDO_MAXMSG);
+
+ return (r);
+}
+
+int
+u2f_get_touch_status(fido_dev_t *dev, int *touched, int *ms)
+{
+ unsigned char *reply;
+ int reply_len;
+ int r;
+
+ if ((reply = malloc(FIDO_MAXMSG)) == NULL) {
+ fido_log_debug("%s: malloc", __func__);
+ r = FIDO_ERR_INTERNAL;
+ goto out;
+ }
+
+ if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG,
+ ms)) < 2) {
+ fido_log_debug("%s: fido_rx", __func__);
+ r = FIDO_OK; /* ignore */
+ goto out;
+ }
+
+ switch ((reply[reply_len - 2] << 8) | reply[reply_len - 1]) {
+ case SW_CONDITIONS_NOT_SATISFIED:
+ if ((r = u2f_get_touch_begin(dev, ms)) != FIDO_OK) {
+ fido_log_debug("%s: u2f_get_touch_begin", __func__);
+ goto out;
+ }
+ *touched = 0;
+ break;
+ case SW_NO_ERROR:
+ *touched = 1;
+ break;
+ default:
+ fido_log_debug("%s: unexpected sw", __func__);
+ r = FIDO_ERR_RX;
+ goto out;
+ }
+
+ r = FIDO_OK;
+out:
+ freezero(reply, FIDO_MAXMSG);
+
+ return (r);
+}
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..25281bb
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 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 <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "fido.h"
+
+int
+fido_to_uint64(const char *str, int base, uint64_t *out)
+{
+ char *ep;
+ unsigned long long ull;
+
+ errno = 0;
+ ull = strtoull(str, &ep, base);
+ if (str == ep || *ep != '\0')
+ return -1;
+ else if (ull == ULLONG_MAX && errno == ERANGE)
+ return -1;
+ else if (ull > UINT64_MAX)
+ return -1;
+ *out = (uint64_t)ull;
+
+ return 0;
+}
diff --git a/src/webauthn.h b/src/webauthn.h
new file mode 100644
index 0000000..153609b
--- /dev/null
+++ b/src/webauthn.h
@@ -0,0 +1,1149 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#ifndef __WEBAUTHN_H_
+#define __WEBAUTHN_H_
+
+#pragma once
+
+#include <winapifamily.h>
+
+#ifdef _MSC_VER
+#pragma region Desktop Family or OneCore Family
+#endif
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef WINAPI
+#define WINAPI __stdcall
+#endif
+
+#ifndef INITGUID
+#define INITGUID
+#include <guiddef.h>
+#undef INITGUID
+#else
+#include <guiddef.h>
+#endif
+
+//+------------------------------------------------------------------------------------------
+// API Version Information.
+// Caller should check for WebAuthNGetApiVersionNumber to check the presence of relevant APIs
+// and features for their usage.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_API_VERSION_1 1
+// WEBAUTHN_API_VERSION_1 : Baseline Version
+// Data Structures and their sub versions:
+// - WEBAUTHN_RP_ENTITY_INFORMATION : 1
+// - WEBAUTHN_USER_ENTITY_INFORMATION : 1
+// - WEBAUTHN_CLIENT_DATA : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETER : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETERS : Not Applicable
+// - WEBAUTHN_CREDENTIAL : 1
+// - WEBAUTHN_CREDENTIALS : Not Applicable
+// - WEBAUTHN_CREDENTIAL_EX : 1
+// - WEBAUTHN_CREDENTIAL_LIST : Not Applicable
+// - WEBAUTHN_EXTENSION : Not Applicable
+// - WEBAUTHN_EXTENSIONS : Not Applicable
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 3
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 4
+// - WEBAUTHN_COMMON_ATTESTATION : 1
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 3
+// - WEBAUTHN_ASSERTION : 1
+// Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// APIs:
+// - WebAuthNGetApiVersionNumber
+// - WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable
+// - WebAuthNAuthenticatorMakeCredential
+// - WebAuthNAuthenticatorGetAssertion
+// - WebAuthNFreeCredentialAttestation
+// - WebAuthNFreeAssertion
+// - WebAuthNGetCancellationId
+// - WebAuthNCancelCurrentOperation
+// - WebAuthNGetErrorName
+// - WebAuthNGetW3CExceptionDOMError
+// Transports:
+// - WEBAUTHN_CTAP_TRANSPORT_USB
+// - WEBAUTHN_CTAP_TRANSPORT_NFC
+// - WEBAUTHN_CTAP_TRANSPORT_BLE
+// - WEBAUTHN_CTAP_TRANSPORT_INTERNAL
+
+#define WEBAUTHN_API_VERSION_2 2
+// WEBAUTHN_API_VERSION_2 : Delta From WEBAUTHN_API_VERSION_1
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+//
+
+#define WEBAUTHN_API_VERSION_3 3
+// WEBAUTHN_API_VERSION_3 : Delta From WEBAUTHN_API_VERSION_2
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 4
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 5
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 4
+// - WEBAUTHN_ASSERTION : 2
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+//
+
+#define WEBAUTHN_API_VERSION_4 4
+// WEBAUTHN_API_VERSION_4 : Delta From WEBAUTHN_API_VERSION_3
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 5
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 6
+// - WEBAUTHN_ASSERTION : 3
+// - WEBAUTHN_CREDENTIAL_DETAILS : 1
+// APIs:
+// - WebAuthNGetPlatformCredentialList
+// - WebAuthNFreePlatformCredentialList
+// - WebAuthNDeletePlatformCredential
+//
+
+#define WEBAUTHN_API_VERSION_5 5
+// WEBAUTHN_API_VERSION_5 : Delta From WEBAUTHN_API_VERSION_4
+// Data Structures and their sub versions:
+// - WEBAUTHN_CREDENTIAL_DETAILS : 2
+// Extension Changes:
+// - Enabled LARGE_BLOB Support
+//
+
+#define WEBAUTHN_API_VERSION_6 6
+// WEBAUTHN_API_VERSION_6 : Delta From WEBAUTHN_API_VERSION_5
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 6
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 5
+// - WEBAUTHN_ASSERTION : 4
+// Transports:
+// - WEBAUTHN_CTAP_TRANSPORT_HYBRID
+
+#define WEBAUTHN_API_VERSION_7 7
+// WEBAUTHN_API_VERSION_7 : Delta From WEBAUTHN_API_VERSION_6
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 7
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 7
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 6
+// - WEBAUTHN_ASSERTION : 5
+
+#define WEBAUTHN_API_CURRENT_VERSION WEBAUTHN_API_VERSION_7
+
+//+------------------------------------------------------------------------------------------
+// Information about an RP Entity
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_RP_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the RP. This field is required.
+ PCWSTR pwszId;
+
+ // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site".
+ // This field is required.
+ PCWSTR pwszName;
+
+ // Optional URL pointing to RP's logo.
+ PCWSTR pwszIcon;
+} WEBAUTHN_RP_ENTITY_INFORMATION, *PWEBAUTHN_RP_ENTITY_INFORMATION;
+typedef const WEBAUTHN_RP_ENTITY_INFORMATION *PCWEBAUTHN_RP_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about an User Entity
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_MAX_USER_ID_LENGTH 64
+
+#define WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_USER_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the User. This field is required.
+ DWORD cbId;
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Contains a detailed name for this account, such as "john.p.smith@example.com".
+ PCWSTR pwszName;
+
+ // Optional URL that can be used to retrieve an image containing the user's current avatar,
+ // or a data URI that contains the image data.
+ PCWSTR pwszIcon;
+
+ // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith".
+ PCWSTR pwszDisplayName;
+} WEBAUTHN_USER_ENTITY_INFORMATION, *PWEBAUTHN_USER_ENTITY_INFORMATION;
+typedef const WEBAUTHN_USER_ENTITY_INFORMATION *PCWEBAUTHN_USER_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about client data.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_HASH_ALGORITHM_SHA_256 L"SHA-256"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_384 L"SHA-384"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_512 L"SHA-512"
+
+#define WEBAUTHN_CLIENT_DATA_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CLIENT_DATA {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Size of the pbClientDataJSON field.
+ DWORD cbClientDataJSON;
+ // UTF-8 encoded JSON serialization of the client data.
+ _Field_size_bytes_(cbClientDataJSON)
+ PBYTE pbClientDataJSON;
+
+ // Hash algorithm ID used to hash the pbClientDataJSON field.
+ LPCWSTR pwszHashAlgId;
+} WEBAUTHN_CLIENT_DATA, *PWEBAUTHN_CLIENT_DATA;
+typedef const WEBAUTHN_CLIENT_DATA *PCWEBAUTHN_CLIENT_DATA;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential parameters.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY L"public-key"
+
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256 -7
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384 -35
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P521_WITH_SHA512 -36
+
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256 -257
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA384 -258
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA512 -259
+
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA256 -37
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA384 -38
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA512 -39
+
+#define WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Well-known credential type specifying a credential to create.
+ LPCWSTR pwszCredentialType;
+
+ // Well-known COSE algorithm specifying the algorithm to use for the credential.
+ LONG lAlg;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETER, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETER *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
+ DWORD cCredentialParameters;
+ _Field_size_(cCredentialParameters)
+ PWEBAUTHN_COSE_CREDENTIAL_PARAMETER pCredentialParameters;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential.
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_CREDENTIAL_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+} WEBAUTHN_CREDENTIAL, *PWEBAUTHN_CREDENTIAL;
+typedef const WEBAUTHN_CREDENTIAL *PCWEBAUTHN_CREDENTIAL;
+
+typedef struct _WEBAUTHN_CREDENTIALS {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL pCredentials;
+} WEBAUTHN_CREDENTIALS, *PWEBAUTHN_CREDENTIALS;
+typedef const WEBAUTHN_CREDENTIALS *PCWEBAUTHN_CREDENTIALS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential with extra information, such as, dwTransports
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_TRANSPORT_USB 0x00000001
+#define WEBAUTHN_CTAP_TRANSPORT_NFC 0x00000002
+#define WEBAUTHN_CTAP_TRANSPORT_BLE 0x00000004
+#define WEBAUTHN_CTAP_TRANSPORT_TEST 0x00000008
+#define WEBAUTHN_CTAP_TRANSPORT_INTERNAL 0x00000010
+#define WEBAUTHN_CTAP_TRANSPORT_HYBRID 0x00000020
+#define WEBAUTHN_CTAP_TRANSPORT_FLAGS_MASK 0x0000003F
+
+#define WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL_EX {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+
+ // Transports. 0 implies no transport restrictions.
+ DWORD dwTransports;
+} WEBAUTHN_CREDENTIAL_EX, *PWEBAUTHN_CREDENTIAL_EX;
+typedef const WEBAUTHN_CREDENTIAL_EX *PCWEBAUTHN_CREDENTIAL_EX;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential list with extra information
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CREDENTIAL_LIST {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL_EX *ppCredentials;
+} WEBAUTHN_CREDENTIAL_LIST, *PWEBAUTHN_CREDENTIAL_LIST;
+typedef const WEBAUTHN_CREDENTIAL_LIST *PCWEBAUTHN_CREDENTIAL_LIST;
+
+//+------------------------------------------------------------------------------------------
+// Information about linked devices
+//-------------------------------------------------------------------------------------------
+
+#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1 1
+#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_CURRENT_VERSION CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1
+
+typedef struct _CTAPCBOR_HYBRID_STORAGE_LINKED_DATA
+{
+ // Version
+ DWORD dwVersion;
+
+ // Contact Id
+ DWORD cbContactId;
+ _Field_size_bytes_(cbContactId)
+ PBYTE pbContactId;
+
+ // Link Id
+ DWORD cbLinkId;
+ _Field_size_bytes_(cbLinkId)
+ PBYTE pbLinkId;
+
+ // Link secret
+ DWORD cbLinkSecret;
+ _Field_size_bytes_(cbLinkSecret)
+ PBYTE pbLinkSecret;
+
+ // Authenticator Public Key
+ DWORD cbPublicKey;
+ _Field_size_bytes_(cbPublicKey)
+ PBYTE pbPublicKey;
+
+ // Authenticator Name
+ PCWSTR pwszAuthenticatorName;
+
+ // Tunnel server domain
+ WORD wEncodedTunnelServerDomain;
+} CTAPCBOR_HYBRID_STORAGE_LINKED_DATA, *PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA;
+typedef const CTAPCBOR_HYBRID_STORAGE_LINKED_DATA *PCCTAPCBOR_HYBRID_STORAGE_LINKED_DATA;
+
+//+------------------------------------------------------------------------------------------
+// Credential Information for WebAuthNGetPlatformCredentialList API
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2 2
+#define WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbCredentialID.
+ DWORD cbCredentialID;
+ _Field_size_bytes_(cbCredentialID)
+ PBYTE pbCredentialID;
+
+ // RP Info
+ PWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation;
+
+ // User Info
+ PWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation;
+
+ // Removable or not.
+ BOOL bRemovable;
+
+ //
+ // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2
+ //
+
+ // Backed Up or not.
+ BOOL bBackedUp;
+} WEBAUTHN_CREDENTIAL_DETAILS, *PWEBAUTHN_CREDENTIAL_DETAILS;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS *PCWEBAUTHN_CREDENTIAL_DETAILS;
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS_LIST {
+ DWORD cCredentialDetails;
+ _Field_size_(cCredentialDetails)
+ PWEBAUTHN_CREDENTIAL_DETAILS *ppCredentialDetails;
+} WEBAUTHN_CREDENTIAL_DETAILS_LIST, *PWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS_LIST *PCWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 1
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_CURRENT_VERSION WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1
+
+typedef struct _WEBAUTHN_GET_CREDENTIALS_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Optional.
+ LPCWSTR pwszRpId;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+} WEBAUTHN_GET_CREDENTIALS_OPTIONS, *PWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+typedef const WEBAUTHN_GET_CREDENTIALS_OPTIONS *PCWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+
+//+------------------------------------------------------------------------------------------
+// PRF values.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH 32
+
+// SALT values below by default are converted into RAW Hmac-Secret values as per PRF extension.
+// - SHA-256(UTF8Encode("WebAuthn PRF") || 0x00 || Value)
+//
+// Set WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG in dwFlags in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
+// if caller wants to provide RAW Hmac-Secret SALT values directly. In that case,
+// values if provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size.
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT {
+ // Size of pbFirst.
+ DWORD cbFirst;
+ _Field_size_bytes_(cbFirst)
+ PBYTE pbFirst; // Required
+
+ // Size of pbSecond.
+ DWORD cbSecond;
+ _Field_size_bytes_(cbSecond)
+ PBYTE pbSecond;
+} WEBAUTHN_HMAC_SECRET_SALT, *PWEBAUTHN_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_HMAC_SECRET_SALT *PCWEBAUTHN_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT {
+ // Size of pbCredID.
+ DWORD cbCredID;
+ _Field_size_bytes_(cbCredID)
+ PBYTE pbCredID; // Required
+
+ // PRF Values for above credential
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecretSalt; // Required
+} WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT, *PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT *PCWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT_VALUES {
+ PWEBAUTHN_HMAC_SECRET_SALT pGlobalHmacSalt;
+
+ DWORD cCredWithHmacSecretSaltList;
+ _Field_size_(cCredWithHmacSecretSaltList)
+ PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT pCredWithHmacSecretSaltList;
+} WEBAUTHN_HMAC_SECRET_SALT_VALUES, *PWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+typedef const WEBAUTHN_HMAC_SECRET_SALT_VALUES *PCWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+
+//+------------------------------------------------------------------------------------------
+// Hmac-Secret extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET L"hmac-secret"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credential
+// was successfully created with HMAC_SECRET.
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credProtect extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_USER_VERIFICATION_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL 1
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIRED 3
+
+typedef struct _WEBAUTHN_CRED_PROTECT_EXTENSION_IN {
+ // One of the above WEBAUTHN_USER_VERIFICATION_* values
+ DWORD dwCredProtect;
+ // Set the following to TRUE to require authenticator support for the credProtect extension
+ BOOL bRequireCredProtect;
+} WEBAUTHN_CRED_PROTECT_EXTENSION_IN, *PWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+typedef const WEBAUTHN_CRED_PROTECT_EXTENSION_IN *PCWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT L"credProtect"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+// MakeCredential Input Type: WEBAUTHN_CRED_PROTECT_EXTENSION_IN.
+// - pvExtension must point to a WEBAUTHN_CRED_PROTECT_EXTENSION_IN struct
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with one of the above WEBAUTHN_USER_VERIFICATION_* values
+// if credential was successfully created with CRED_PROTECT.
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credBlob extension
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CRED_BLOB_EXTENSION {
+ // Size of pbCredBlob.
+ DWORD cbCredBlob;
+ _Field_size_bytes_(cbCredBlob)
+ PBYTE pbCredBlob;
+} WEBAUTHN_CRED_BLOB_EXTENSION, *PWEBAUTHN_CRED_BLOB_EXTENSION;
+typedef const WEBAUTHN_CRED_BLOB_EXTENSION *PCWEBAUTHN_CRED_BLOB_EXTENSION;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB L"credBlob"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// MakeCredential Input Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension must point to a WEBAUTHN_CRED_BLOB_EXTENSION struct
+// - cbExtension must contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credBlob was successfully created
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the credBlob.
+// - cbExtension must contain the sizeof(BOOL).
+// GetAssertion Output Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension will point to a WEBAUTHN_CRED_BLOB_EXTENSION struct if the authenticator
+// returns the credBlob in the signed extensions
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+
+//+------------------------------------------------------------------------------------------
+// minPinLength extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH L"minPinLength"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the minPinLength.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with the minimum pin length if returned by the authenticator
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// Information about Extensions.
+//-------------------------------------------------------------------------------------------
+typedef struct _WEBAUTHN_EXTENSION {
+ LPCWSTR pwszExtensionIdentifier;
+ DWORD cbExtension;
+ PVOID pvExtension;
+} WEBAUTHN_EXTENSION, *PWEBAUTHN_EXTENSION;
+typedef const WEBAUTHN_EXTENSION *PCWEBAUTHN_EXTENSION;
+
+typedef struct _WEBAUTHN_EXTENSIONS {
+ DWORD cExtensions;
+ _Field_size_(cExtensions)
+ PWEBAUTHN_EXTENSION pExtensions;
+} WEBAUTHN_EXTENSIONS, *PWEBAUTHN_EXTENSIONS;
+typedef const WEBAUTHN_EXTENSIONS *PCWEBAUTHN_EXTENSIONS;
+
+//+------------------------------------------------------------------------------------------
+// Options.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY 0
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM 1
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM 2
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2 3
+
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED 1
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED 3
+
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY 0
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE 1
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT 2
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT 3
+
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_NONE 0
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED 1
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED 2
+
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_NONE 0
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED 1
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED 2
+
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6 6
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7 7
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Credentials used for exclusion.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // Optional. Require key to be resident or not. Defaulting to FALSE.
+ BOOL bRequireResidentKey;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Attestation Conveyance Preference.
+ DWORD dwAttestationConveyancePreference;
+
+ // Reserved for future Use
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3
+ //
+
+ // Exclude Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pExcludeCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4
+ //
+
+ // Enterprise Attestation
+ DWORD dwEnterpriseAttestation;
+
+ // Large Blob Support: none, required or preferred
+ //
+ // NTE_INVALID_PARAMETER when large blob required or preferred and
+ // bRequireResidentKey isn't set to TRUE
+ DWORD dwLargeBlobSupport;
+
+ // Optional. Prefer key to be resident. Defaulting to FALSE. When TRUE,
+ // overrides the above bRequireResidentKey.
+ BOOL bPreferResidentKey;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5
+ //
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6
+ //
+
+ // Enable PRF
+ BOOL bEnablePrf;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7
+ //
+
+ // Optional. Linked Device Connection Info.
+ PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice;
+
+ // Size of pbJsonExt
+ DWORD cbJsonExt;
+ _Field_size_bytes_(cbJsonExt)
+ PBYTE pbJsonExt;
+} WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET 1
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET 2
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_DELETE 3
+
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 6
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7 7
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7
+
+/*
+ Information about flags.
+*/
+
+#define WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG 0x00100000
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Allowed Credentials List.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Flags
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2
+ //
+
+ // Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased.
+ PCWSTR pwszU2fAppId;
+
+ // If the following is non-NULL, then, set to TRUE if the above pwszU2fAppid was used instead of
+ // PCWSTR pwszRpId;
+ BOOL *pbU2fAppId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4
+ //
+
+ // Allow Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pAllowCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5
+ //
+
+ DWORD dwCredLargeBlobOperation;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6
+ //
+
+ // PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec.
+ PWEBAUTHN_HMAC_SECRET_SALT_VALUES pHmacSecretSaltValues;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7
+ //
+
+ // Optional. Linked Device Connection Info.
+ PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice;
+
+ // Optional. Allowlist MUST contain 1 credential applicable for Hybrid transport.
+ BOOL bAutoFill;
+
+ // Size of pbJsonExt
+ DWORD cbJsonExt;
+ _Field_size_bytes_(cbJsonExt)
+ PBYTE pbJsonExt;
+} WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+
+
+//+------------------------------------------------------------------------------------------
+// Attestation Info.
+//
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_ATTESTATION_DECODE_NONE 0
+#define WEBAUTHN_ATTESTATION_DECODE_COMMON 1
+// WEBAUTHN_ATTESTATION_DECODE_COMMON supports format types
+// L"packed"
+// L"fido-u2f"
+
+#define WEBAUTHN_ATTESTATION_VER_TPM_2_0 L"2.0"
+
+typedef struct _WEBAUTHN_X5C {
+ // Length of X.509 encoded certificate
+ DWORD cbData;
+ // X.509 encoded certificate bytes
+ _Field_size_bytes_(cbData)
+ PBYTE pbData;
+} WEBAUTHN_X5C, *PWEBAUTHN_X5C;
+
+// Supports either Self or Full Basic Attestation
+
+// Note, new fields will be added to the following data structure to
+// support additional attestation format types, such as, TPM.
+// When fields are added, the dwVersion will be incremented.
+//
+// Therefore, your code must make the following check:
+// "if (dwVersion >= WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION)"
+
+#define WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COMMON_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Hash and Padding Algorithm
+ //
+ // The following won't be set for "fido-u2f" which assumes "ES256".
+ PCWSTR pwszAlg;
+ LONG lAlg; // COSE algorithm
+
+ // Signature that was generated for this attestation.
+ DWORD cbSignature;
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation.
+ // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate.
+ DWORD cX5c;
+ _Field_size_(cX5c)
+ PWEBAUTHN_X5C pX5c;
+
+ // Following are also set for tpm
+ PCWSTR pwszVer; // L"2.0"
+ DWORD cbCertInfo;
+ _Field_size_bytes_(cbCertInfo)
+ PBYTE pbCertInfo;
+ DWORD cbPubArea;
+ _Field_size_bytes_(cbPubArea)
+ PBYTE pbPubArea;
+} WEBAUTHN_COMMON_ATTESTATION, *PWEBAUTHN_COMMON_ATTESTATION;
+typedef const WEBAUTHN_COMMON_ATTESTATION *PCWEBAUTHN_COMMON_ATTESTATION;
+
+#define WEBAUTHN_ATTESTATION_TYPE_PACKED L"packed"
+#define WEBAUTHN_ATTESTATION_TYPE_U2F L"fido-u2f"
+#define WEBAUTHN_ATTESTATION_TYPE_TPM L"tpm"
+#define WEBAUTHN_ATTESTATION_TYPE_NONE L"none"
+
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 2
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 3
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 4
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 5
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 6
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
+
+typedef struct _WEBAUTHN_CREDENTIAL_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Attestation format type
+ PCWSTR pwszFormatType;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this credential.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of CBOR encoded attestation information
+ //0 => encoded as CBOR null value.
+ DWORD cbAttestation;
+ //Encoded CBOR attestation information
+ _Field_size_bytes_(cbAttestation)
+ PBYTE pbAttestation;
+
+ DWORD dwAttestationDecodeType;
+ // Following depends on the dwAttestationDecodeType
+ // WEBAUTHN_ATTESTATION_DECODE_NONE
+ // NULL - not able to decode the CBOR attestation information
+ // WEBAUTHN_ATTESTATION_DECODE_COMMON
+ // PWEBAUTHN_COMMON_ATTESTATION;
+ PVOID pvAttestationDecode;
+
+ // The CBOR encoded Attestation Object to be returned to the RP.
+ DWORD cbAttestationObject;
+ _Field_size_bytes_(cbAttestationObject)
+ PBYTE pbAttestationObject;
+
+ // The CredentialId bytes extracted from the Authenticator Data.
+ // Used by Edge to return to the RP.
+ DWORD cbCredentialId;
+ _Field_size_bytes_(cbCredentialId)
+ PBYTE pbCredentialId;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
+ //
+
+ // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
+ // the transport that was used.
+ DWORD dwUsedTransport;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
+ //
+
+ BOOL bEpAtt;
+ BOOL bLargeBlobSupported;
+ BOOL bResidentKey;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
+ //
+
+ BOOL bPrfEnabled;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
+ //
+
+ DWORD cbUnsignedExtensionOutputs;
+ _Field_size_bytes_(cbUnsignedExtensionOutputs)
+ PBYTE pbUnsignedExtensionOutputs;
+} WEBAUTHN_CREDENTIAL_ATTESTATION, *PWEBAUTHN_CREDENTIAL_ATTESTATION;
+typedef const WEBAUTHN_CREDENTIAL_ATTESTATION *PCWEBAUTHN_CREDENTIAL_ATTESTATION;
+
+
+//+------------------------------------------------------------------------------------------
+// authenticatorGetAssertion output.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS 1
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_SUPPORTED 2
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_DATA 3
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_PARAMETER 4
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_FOUND 5
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_MULTIPLE_CREDENTIALS 6
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_LACK_OF_SPACE 7
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_PLATFORM_ERROR 8
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_AUTHENTICATOR_ERROR 9
+
+#define WEBAUTHN_ASSERTION_VERSION_1 1
+#define WEBAUTHN_ASSERTION_VERSION_2 2
+#define WEBAUTHN_ASSERTION_VERSION_3 3
+#define WEBAUTHN_ASSERTION_VERSION_4 4
+#define WEBAUTHN_ASSERTION_VERSION_5 5
+#define WEBAUTHN_ASSERTION_CURRENT_VERSION WEBAUTHN_ASSERTION_VERSION_5
+
+typedef struct _WEBAUTHN_ASSERTION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this assertion.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of pbSignature.
+ DWORD cbSignature;
+ // Signature that was generated for this assertion.
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Credential that was used for this assertion.
+ WEBAUTHN_CREDENTIAL Credential;
+
+ // Size of User Id
+ DWORD cbUserId;
+ // UserId
+ _Field_size_bytes_(cbUserId)
+ PBYTE pbUserId;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ DWORD dwCredLargeBlobStatus;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3
+ //
+
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_4
+ //
+
+ // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
+ // the transport that was used.
+ DWORD dwUsedTransport;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_5
+ //
+
+ DWORD cbUnsignedExtensionOutputs;
+ _Field_size_bytes_(cbUnsignedExtensionOutputs)
+ PBYTE pbUnsignedExtensionOutputs;
+} WEBAUTHN_ASSERTION, *PWEBAUTHN_ASSERTION;
+typedef const WEBAUTHN_ASSERTION *PCWEBAUTHN_ASSERTION;
+
+//+------------------------------------------------------------------------------------------
+// APIs.
+//-------------------------------------------------------------------------------------------
+
+DWORD
+WINAPI
+WebAuthNGetApiVersionNumber();
+
+HRESULT
+WINAPI
+WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(
+ _Out_ BOOL *pbIsUserVerifyingPlatformAuthenticatorAvailable);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorMakeCredential(
+ _In_ HWND hWnd,
+ _In_ PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation,
+ _In_ PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation,
+ _In_ PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS pPubKeyCredParams,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS pWebAuthNMakeCredentialOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_ATTESTATION *ppWebAuthNCredentialAttestation);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorGetAssertion(
+ _In_ HWND hWnd,
+ _In_ LPCWSTR pwszRpId,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS pWebAuthNGetAssertionOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_ASSERTION *ppWebAuthNAssertion);
+
+void
+WINAPI
+WebAuthNFreeCredentialAttestation(
+ _In_opt_ PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation);
+
+void
+WINAPI
+WebAuthNFreeAssertion(
+ _In_ PWEBAUTHN_ASSERTION pWebAuthNAssertion);
+
+HRESULT
+WINAPI
+WebAuthNGetCancellationId(
+ _Out_ GUID* pCancellationId);
+
+HRESULT
+WINAPI
+WebAuthNCancelCurrentOperation(
+ _In_ const GUID* pCancellationId);
+
+// Returns NTE_NOT_FOUND when credentials are not found.
+HRESULT
+WINAPI
+WebAuthNGetPlatformCredentialList(
+ _In_ PCWEBAUTHN_GET_CREDENTIALS_OPTIONS pGetCredentialsOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST *ppCredentialDetailsList);
+
+void
+WINAPI
+WebAuthNFreePlatformCredentialList(
+ _In_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList);
+
+HRESULT
+WINAPI
+WebAuthNDeletePlatformCredential(
+ _In_ DWORD cbCredentialId,
+ _In_reads_bytes_(cbCredentialId) const BYTE *pbCredentialId
+ );
+
+//
+// Returns the following Error Names:
+// L"Success" - S_OK
+// L"InvalidStateError" - NTE_EXISTS
+// L"ConstraintError" - HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED),
+// NTE_NOT_SUPPORTED,
+// NTE_TOKEN_KEYSET_STORAGE_FULL
+// L"NotSupportedError" - NTE_INVALID_PARAMETER
+// L"NotAllowedError" - NTE_DEVICE_NOT_FOUND,
+// NTE_NOT_FOUND,
+// HRESULT_FROM_WIN32(ERROR_CANCELLED),
+// NTE_USER_CANCELLED,
+// HRESULT_FROM_WIN32(ERROR_TIMEOUT)
+// L"UnknownError" - All other hr values
+//
+PCWSTR
+WINAPI
+WebAuthNGetErrorName(
+ _In_ HRESULT hr);
+
+HRESULT
+WINAPI
+WebAuthNGetW3CExceptionDOMError(
+ _In_ HRESULT hr);
+
+
+#ifdef __cplusplus
+} // Balance extern "C" above
+#endif
+
+#endif // WINAPI_FAMILY_PARTITION
+#ifdef _MSC_VER
+#pragma endregion
+#endif
+
+#endif // __WEBAUTHN_H_
diff --git a/src/winhello.c b/src/winhello.c
new file mode 100644
index 0000000..ff969a4
--- /dev/null
+++ b/src/winhello.c
@@ -0,0 +1,1075 @@
+/*
+ * 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
+ */
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <windows.h>
+
+#include "fido.h"
+#include "webauthn.h"
+
+#ifndef NTE_INVALID_PARAMETER
+#define NTE_INVALID_PARAMETER _HRESULT_TYPEDEF_(0x80090027)
+#endif
+#ifndef NTE_NOT_SUPPORTED
+#define NTE_NOT_SUPPORTED _HRESULT_TYPEDEF_(0x80090029)
+#endif
+#ifndef NTE_DEVICE_NOT_FOUND
+#define NTE_DEVICE_NOT_FOUND _HRESULT_TYPEDEF_(0x80090035)
+#endif
+#ifndef NTE_USER_CANCELLED
+#define NTE_USER_CANCELLED _HRESULT_TYPEDEF_(0x80090036L)
+#endif
+
+#define MAXCHARS 128
+#define MAXCREDS 128
+#define MAXMSEC 6000 * 1000
+#define VENDORID 0x045e
+#define PRODID 0x0001
+
+struct winhello_assert {
+ WEBAUTHN_CLIENT_DATA cd;
+ WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS opt;
+ WEBAUTHN_ASSERTION *assert;
+ wchar_t *rp_id;
+ wchar_t *appid;
+};
+
+struct winhello_cred {
+ WEBAUTHN_RP_ENTITY_INFORMATION rp;
+ WEBAUTHN_USER_ENTITY_INFORMATION user;
+ WEBAUTHN_COSE_CREDENTIAL_PARAMETER alg;
+ WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose;
+ WEBAUTHN_CLIENT_DATA cd;
+ WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS opt;
+ WEBAUTHN_CREDENTIAL_ATTESTATION *att;
+ wchar_t *rp_id;
+ wchar_t *rp_name;
+ wchar_t *user_name;
+ wchar_t *user_icon;
+ wchar_t *display_name;
+};
+
+typedef DWORD WINAPI webauthn_get_api_version_t(void);
+typedef PCWSTR WINAPI webauthn_strerr_t(HRESULT);
+typedef HRESULT WINAPI webauthn_get_assert_t(HWND, LPCWSTR,
+ PCWEBAUTHN_CLIENT_DATA,
+ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
+ PWEBAUTHN_ASSERTION *);
+typedef HRESULT WINAPI webauthn_make_cred_t(HWND,
+ PCWEBAUTHN_RP_ENTITY_INFORMATION,
+ PCWEBAUTHN_USER_ENTITY_INFORMATION,
+ PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS,
+ PCWEBAUTHN_CLIENT_DATA,
+ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS,
+ PWEBAUTHN_CREDENTIAL_ATTESTATION *);
+typedef void WINAPI webauthn_free_assert_t(PWEBAUTHN_ASSERTION);
+typedef void WINAPI webauthn_free_attest_t(PWEBAUTHN_CREDENTIAL_ATTESTATION);
+
+static TLS BOOL webauthn_loaded;
+static TLS HMODULE webauthn_handle;
+static TLS webauthn_get_api_version_t *webauthn_get_api_version;
+static TLS webauthn_strerr_t *webauthn_strerr;
+static TLS webauthn_get_assert_t *webauthn_get_assert;
+static TLS webauthn_make_cred_t *webauthn_make_cred;
+static TLS webauthn_free_assert_t *webauthn_free_assert;
+static TLS webauthn_free_attest_t *webauthn_free_attest;
+
+static int
+webauthn_load(void)
+{
+ DWORD n = 1;
+
+ if (webauthn_loaded || webauthn_handle != NULL) {
+ fido_log_debug("%s: already loaded", __func__);
+ return -1;
+ }
+ if ((webauthn_handle = LoadLibrary(TEXT("webauthn.dll"))) == NULL) {
+ fido_log_debug("%s: LoadLibrary", __func__);
+ return -1;
+ }
+
+ if ((webauthn_get_api_version =
+ (webauthn_get_api_version_t *)GetProcAddress(webauthn_handle,
+ "WebAuthNGetApiVersionNumber")) == NULL) {
+ fido_log_debug("%s: WebAuthNGetApiVersionNumber", __func__);
+ /* WebAuthNGetApiVersionNumber might not exist */
+ }
+ if (webauthn_get_api_version != NULL &&
+ (n = webauthn_get_api_version()) < 1) {
+ fido_log_debug("%s: unsupported api %lu", __func__, (u_long)n);
+ goto fail;
+ }
+ fido_log_debug("%s: api version %lu", __func__, (u_long)n);
+ if ((webauthn_strerr =
+ (webauthn_strerr_t *)GetProcAddress(webauthn_handle,
+ "WebAuthNGetErrorName")) == NULL) {
+ fido_log_debug("%s: WebAuthNGetErrorName", __func__);
+ goto fail;
+ }
+ if ((webauthn_get_assert =
+ (webauthn_get_assert_t *)GetProcAddress(webauthn_handle,
+ "WebAuthNAuthenticatorGetAssertion")) == NULL) {
+ fido_log_debug("%s: WebAuthNAuthenticatorGetAssertion",
+ __func__);
+ goto fail;
+ }
+ if ((webauthn_make_cred =
+ (webauthn_make_cred_t *)GetProcAddress(webauthn_handle,
+ "WebAuthNAuthenticatorMakeCredential")) == NULL) {
+ fido_log_debug("%s: WebAuthNAuthenticatorMakeCredential",
+ __func__);
+ goto fail;
+ }
+ if ((webauthn_free_assert =
+ (webauthn_free_assert_t *)GetProcAddress(webauthn_handle,
+ "WebAuthNFreeAssertion")) == NULL) {
+ fido_log_debug("%s: WebAuthNFreeAssertion", __func__);
+ goto fail;
+ }
+ if ((webauthn_free_attest =
+ (webauthn_free_attest_t *)GetProcAddress(webauthn_handle,
+ "WebAuthNFreeCredentialAttestation")) == NULL) {
+ fido_log_debug("%s: WebAuthNFreeCredentialAttestation",
+ __func__);
+ goto fail;
+ }
+
+ webauthn_loaded = true;
+
+ return 0;
+fail:
+ fido_log_debug("%s: GetProcAddress", __func__);
+ webauthn_get_api_version = NULL;
+ webauthn_strerr = NULL;
+ webauthn_get_assert = NULL;
+ webauthn_make_cred = NULL;
+ webauthn_free_assert = NULL;
+ webauthn_free_attest = NULL;
+ FreeLibrary(webauthn_handle);
+ webauthn_handle = NULL;
+
+ return -1;
+}
+
+static wchar_t *
+to_utf16(const char *utf8)
+{
+ int nch;
+ wchar_t *utf16;
+
+ if (utf8 == NULL) {
+ fido_log_debug("%s: NULL", __func__);
+ return NULL;
+ }
+ if ((nch = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) < 1 ||
+ (size_t)nch > MAXCHARS) {
+ fido_log_debug("%s: MultiByteToWideChar %d", __func__, nch);
+ return NULL;
+ }
+ if ((utf16 = calloc((size_t)nch, sizeof(*utf16))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return NULL;
+ }
+ if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, nch) != nch) {
+ fido_log_debug("%s: MultiByteToWideChar", __func__);
+ free(utf16);
+ return NULL;
+ }
+
+ return utf16;
+}
+
+static int
+to_fido(HRESULT hr)
+{
+ switch (hr) {
+ case NTE_NOT_SUPPORTED:
+ return FIDO_ERR_UNSUPPORTED_OPTION;
+ case NTE_INVALID_PARAMETER:
+ return FIDO_ERR_INVALID_PARAMETER;
+ case NTE_TOKEN_KEYSET_STORAGE_FULL:
+ return FIDO_ERR_KEY_STORE_FULL;
+ case NTE_DEVICE_NOT_FOUND:
+ case NTE_NOT_FOUND:
+ return FIDO_ERR_NOT_ALLOWED;
+ case __HRESULT_FROM_WIN32(ERROR_CANCELLED):
+ case NTE_USER_CANCELLED:
+ return FIDO_ERR_OPERATION_DENIED;
+ default:
+ fido_log_debug("%s: hr=0x%lx", __func__, (u_long)hr);
+ return FIDO_ERR_INTERNAL;
+ }
+}
+
+static int
+pack_cd(WEBAUTHN_CLIENT_DATA *out, const fido_blob_t *in)
+{
+ if (in->ptr == NULL) {
+ fido_log_debug("%s: NULL", __func__);
+ return -1;
+ }
+ if (in->len > ULONG_MAX) {
+ fido_log_debug("%s: in->len=%zu", __func__, in->len);
+ return -1;
+ }
+ out->dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION;
+ out->cbClientDataJSON = (DWORD)in->len;
+ out->pbClientDataJSON = in->ptr;
+ out->pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256;
+
+ return 0;
+}
+
+static int
+pack_credlist(WEBAUTHN_CREDENTIALS *out, const fido_blob_array_t *in)
+{
+ WEBAUTHN_CREDENTIAL *c;
+
+ if (in->len == 0) {
+ return 0; /* nothing to do */
+ }
+ if (in->len > MAXCREDS) {
+ fido_log_debug("%s: in->len=%zu", __func__, in->len);
+ return -1;
+ }
+ if ((out->pCredentials = calloc(in->len, sizeof(*c))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return -1;
+ }
+ out->cCredentials = (DWORD)in->len;
+ for (size_t i = 0; i < in->len; i++) {
+ if (in->ptr[i].len > ULONG_MAX) {
+ fido_log_debug("%s: %zu", __func__, in->ptr[i].len);
+ return -1;
+ }
+ c = &out->pCredentials[i];
+ c->dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION;
+ c->cbId = (DWORD)in->ptr[i].len;
+ c->pbId = in->ptr[i].ptr;
+ c->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
+ }
+
+ return 0;
+}
+
+static int
+set_cred_uv(DWORD *out, fido_opt_t uv, const char *pin)
+{
+ if (pin) {
+ *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
+ return 0;
+ }
+
+ switch (uv) {
+ case FIDO_OPT_OMIT:
+ case FIDO_OPT_FALSE:
+ *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
+ break;
+ case FIDO_OPT_TRUE:
+ *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+set_assert_uv(DWORD *out, fido_opt_t uv, const char *pin)
+{
+ if (pin) {
+ *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
+ return 0;
+ }
+
+ switch (uv) {
+ case FIDO_OPT_OMIT:
+ *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED;
+ break;
+ case FIDO_OPT_FALSE:
+ *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
+ break;
+ case FIDO_OPT_TRUE:
+ *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+pack_rp(wchar_t **id, wchar_t **name, WEBAUTHN_RP_ENTITY_INFORMATION *out,
+ const fido_rp_t *in)
+{
+ /* keep non-const copies of pwsz* for free() */
+ out->dwVersion = WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION;
+ if ((out->pwszId = *id = to_utf16(in->id)) == NULL) {
+ fido_log_debug("%s: id", __func__);
+ return -1;
+ }
+ if (in->name && (out->pwszName = *name = to_utf16(in->name)) == NULL) {
+ fido_log_debug("%s: name", __func__);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+pack_user(wchar_t **name, wchar_t **icon, wchar_t **display_name,
+ WEBAUTHN_USER_ENTITY_INFORMATION *out, const fido_user_t *in)
+{
+ if (in->id.ptr == NULL || in->id.len > ULONG_MAX) {
+ fido_log_debug("%s: id", __func__);
+ return -1;
+ }
+ out->dwVersion = WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION;
+ out->cbId = (DWORD)in->id.len;
+ out->pbId = in->id.ptr;
+ /* keep non-const copies of pwsz* for free() */
+ if (in->name != NULL) {
+ if ((out->pwszName = *name = to_utf16(in->name)) == NULL) {
+ fido_log_debug("%s: name", __func__);
+ return -1;
+ }
+ }
+ if (in->icon != NULL) {
+ if ((out->pwszIcon = *icon = to_utf16(in->icon)) == NULL) {
+ fido_log_debug("%s: icon", __func__);
+ return -1;
+ }
+ }
+ if (in->display_name != NULL) {
+ if ((out->pwszDisplayName = *display_name =
+ to_utf16(in->display_name)) == NULL) {
+ fido_log_debug("%s: display_name", __func__);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg,
+ WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *cose, int type)
+{
+ switch (type) {
+ case COSE_ES256:
+ alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256;
+ break;
+ case COSE_ES384:
+ alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384;
+ break;
+ case COSE_EDDSA:
+ alg->lAlg = -8; /* XXX */;
+ break;
+ case COSE_RS256:
+ alg->lAlg = WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256;
+ break;
+ default:
+ fido_log_debug("%s: type %d", __func__, type);
+ return -1;
+ }
+ alg->dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION;
+ alg->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY;
+ cose->cCredentialParameters = 1;
+ cose->pCredentialParameters = alg;
+
+ return 0;
+}
+
+static int
+pack_cred_ext(WEBAUTHN_EXTENSIONS *out, const fido_cred_ext_t *in)
+{
+ WEBAUTHN_EXTENSION *e;
+ WEBAUTHN_CRED_PROTECT_EXTENSION_IN *p;
+ BOOL *b;
+ size_t n = 0, i = 0;
+
+ if (in->mask == 0) {
+ return 0; /* nothing to do */
+ }
+ if (in->mask & ~(FIDO_EXT_HMAC_SECRET | FIDO_EXT_CRED_PROTECT)) {
+ fido_log_debug("%s: mask 0x%x", __func__, in->mask);
+ return -1;
+ }
+ if (in->mask & FIDO_EXT_HMAC_SECRET)
+ n++;
+ if (in->mask & FIDO_EXT_CRED_PROTECT)
+ n++;
+ if ((out->pExtensions = calloc(n, sizeof(*e))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return -1;
+ }
+ out->cExtensions = (DWORD)n;
+ if (in->mask & FIDO_EXT_HMAC_SECRET) {
+ /*
+ * NOTE: webauthn.dll ignores requests to enable hmac-secret
+ * unless a discoverable credential is also requested.
+ */
+ if ((b = calloc(1, sizeof(*b))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return -1;
+ }
+ *b = true;
+ e = &out->pExtensions[i];
+ e->pwszExtensionIdentifier =
+ WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET;
+ e->pvExtension = b;
+ e->cbExtension = sizeof(*b);
+ i++;
+ }
+ if (in->mask & FIDO_EXT_CRED_PROTECT) {
+ if ((p = calloc(1, sizeof(*p))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return -1;
+ }
+ p->dwCredProtect = (DWORD)in->prot;
+ p->bRequireCredProtect = true;
+ e = &out->pExtensions[i];
+ e->pwszExtensionIdentifier =
+ WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT;
+ e->pvExtension = p;
+ e->cbExtension = sizeof(*p);
+ i++;
+ }
+
+ return 0;
+}
+
+static int
+pack_assert_ext(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *out,
+ const fido_assert_ext_t *in)
+{
+ WEBAUTHN_HMAC_SECRET_SALT_VALUES *v;
+ WEBAUTHN_HMAC_SECRET_SALT *s;
+
+ if (in->mask == 0) {
+ return 0; /* nothing to do */
+ }
+ if (in->mask != FIDO_EXT_HMAC_SECRET) {
+ fido_log_debug("%s: mask 0x%x", __func__, in->mask);
+ return -1;
+ }
+ if (in->hmac_salt.ptr == NULL ||
+ in->hmac_salt.len != WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH) {
+ fido_log_debug("%s: salt %p/%zu", __func__,
+ (const void *)in->hmac_salt.ptr, in->hmac_salt.len);
+ return -1;
+ }
+ if ((v = calloc(1, sizeof(*v))) == NULL ||
+ (s = calloc(1, sizeof(*s))) == NULL) {
+ free(v);
+ fido_log_debug("%s: calloc", __func__);
+ return -1;
+ }
+ s->cbFirst = (DWORD)in->hmac_salt.len;
+ s->pbFirst = in->hmac_salt.ptr;
+ v->pGlobalHmacSalt = s;
+ out->pHmacSecretSaltValues = v;
+ out->dwFlags |= WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG;
+ out->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6;
+
+ return 0;
+}
+
+static int
+pack_appid(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt, const char *id,
+ wchar_t **appid)
+{
+ if (id == NULL)
+ return 0; /* nothing to do */
+ if ((opt->pbU2fAppId = calloc(1, sizeof(*opt->pbU2fAppId))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return -1;
+ }
+ if ((*appid = to_utf16(id)) == NULL) {
+ fido_log_debug("%s: to_utf16", __func__);
+ return -1;
+ }
+ fido_log_debug("%s: using %s", __func__, id);
+ opt->pwszU2fAppId = *appid;
+ opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2;
+
+ return 0;
+}
+
+static void
+unpack_appid(fido_assert_t *assert,
+ const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt)
+{
+ if (assert->appid == NULL || opt->pbU2fAppId == NULL)
+ return; /* nothing to do */
+ if (*opt->pbU2fAppId == false) {
+ fido_log_debug("%s: not used", __func__);
+ return;
+ }
+ fido_log_debug("%s: %s -> %s", __func__, assert->rp_id, assert->appid);
+ free(assert->rp_id);
+ assert->rp_id = assert->appid;
+ assert->appid = NULL;
+}
+
+static int
+unpack_assert_authdata(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
+{
+ int r;
+
+ if ((r = fido_assert_set_authdata_raw(assert, 0, wa->pbAuthenticatorData,
+ wa->cbAuthenticatorData)) != FIDO_OK) {
+ fido_log_debug("%s: fido_assert_set_authdata_raw: %s", __func__,
+ fido_strerr(r));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+unpack_assert_sig(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
+{
+ int r;
+
+ if ((r = fido_assert_set_sig(assert, 0, wa->pbSignature,
+ wa->cbSignature)) != FIDO_OK) {
+ fido_log_debug("%s: fido_assert_set_sig: %s", __func__,
+ fido_strerr(r));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+unpack_cred_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
+{
+ if (fido_blob_set(&assert->stmt[0].id, wa->Credential.pbId,
+ wa->Credential.cbId) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+unpack_user_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
+{
+ if (wa->cbUserId == 0)
+ return 0; /* user id absent */
+ if (fido_blob_set(&assert->stmt[0].user.id, wa->pbUserId,
+ wa->cbUserId) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+unpack_hmac_secret(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa)
+{
+ if (wa->dwVersion < WEBAUTHN_ASSERTION_VERSION_3) {
+ fido_log_debug("%s: dwVersion %u", __func__,
+ (unsigned)wa->dwVersion);
+ return 0; /* proceed without hmac-secret */
+ }
+ if (wa->pHmacSecret == NULL ||
+ wa->pHmacSecret->cbFirst == 0 ||
+ wa->pHmacSecret->pbFirst == NULL) {
+ fido_log_debug("%s: hmac-secret absent", __func__);
+ return 0; /* proceed without hmac-secret */
+ }
+ if (wa->pHmacSecret->cbSecond != 0 ||
+ wa->pHmacSecret->pbSecond != NULL) {
+ fido_log_debug("%s: 64-byte hmac-secret", __func__);
+ return 0; /* proceed without hmac-secret */
+ }
+ if (!fido_blob_is_empty(&assert->stmt[0].hmac_secret)) {
+ fido_log_debug("%s: fido_blob_is_empty", __func__);
+ return -1;
+ }
+ if (fido_blob_set(&assert->stmt[0].hmac_secret,
+ wa->pHmacSecret->pbFirst, wa->pHmacSecret->cbFirst) < 0) {
+ fido_log_debug("%s: fido_blob_set", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+translate_fido_assert(struct winhello_assert *ctx, const fido_assert_t *assert,
+ const char *pin, int ms)
+{
+ WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt;
+
+ /* not supported by webauthn.h */
+ if (assert->up == FIDO_OPT_FALSE) {
+ fido_log_debug("%s: up %d", __func__, assert->up);
+ return FIDO_ERR_UNSUPPORTED_OPTION;
+ }
+ if ((ctx->rp_id = to_utf16(assert->rp_id)) == NULL) {
+ fido_log_debug("%s: rp_id", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (pack_cd(&ctx->cd, &assert->cd) < 0) {
+ fido_log_debug("%s: pack_cd", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ /* options */
+ opt = &ctx->opt;
+ opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1;
+ opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms;
+ if (pack_appid(opt, assert->appid, &ctx->appid) < 0) {
+ fido_log_debug("%s: pack_appid" , __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (pack_credlist(&opt->CredentialList, &assert->allow_list) < 0) {
+ fido_log_debug("%s: pack_credlist", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (pack_assert_ext(opt, &assert->ext) < 0) {
+ fido_log_debug("%s: pack_assert_ext", __func__);
+ return FIDO_ERR_UNSUPPORTED_EXTENSION;
+ }
+ if (set_assert_uv(&opt->dwUserVerificationRequirement, assert->uv,
+ pin) < 0) {
+ fido_log_debug("%s: set_assert_uv", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+
+ return FIDO_OK;
+}
+
+static int
+translate_winhello_assert(fido_assert_t *assert,
+ const struct winhello_assert *ctx)
+{
+ const WEBAUTHN_ASSERTION *wa = ctx->assert;
+ const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt = &ctx->opt;
+ int r;
+
+ if (assert->stmt_len > 0) {
+ fido_log_debug("%s: stmt_len=%zu", __func__, assert->stmt_len);
+ return FIDO_ERR_INTERNAL;
+ }
+ if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK) {
+ fido_log_debug("%s: fido_assert_set_count: %s", __func__,
+ fido_strerr(r));
+ return FIDO_ERR_INTERNAL;
+ }
+ unpack_appid(assert, opt);
+ if (unpack_assert_authdata(assert, wa) < 0) {
+ fido_log_debug("%s: unpack_assert_authdata", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (unpack_assert_sig(assert, wa) < 0) {
+ fido_log_debug("%s: unpack_assert_sig", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (unpack_cred_id(assert, wa) < 0) {
+ fido_log_debug("%s: unpack_cred_id", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (unpack_user_id(assert, wa) < 0) {
+ fido_log_debug("%s: unpack_user_id", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (assert->ext.mask & FIDO_EXT_HMAC_SECRET &&
+ unpack_hmac_secret(assert, wa) < 0) {
+ fido_log_debug("%s: unpack_hmac_secret", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+
+ return FIDO_OK;
+}
+
+static int
+translate_fido_cred(struct winhello_cred *ctx, const fido_cred_t *cred,
+ const char *pin, int ms)
+{
+ WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *opt;
+
+ if (pack_rp(&ctx->rp_id, &ctx->rp_name, &ctx->rp, &cred->rp) < 0) {
+ fido_log_debug("%s: pack_rp", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (pack_user(&ctx->user_name, &ctx->user_icon, &ctx->display_name,
+ &ctx->user, &cred->user) < 0) {
+ fido_log_debug("%s: pack_user", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (pack_cose(&ctx->alg, &ctx->cose, cred->type) < 0) {
+ fido_log_debug("%s: pack_cose", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (pack_cd(&ctx->cd, &cred->cd) < 0) {
+ fido_log_debug("%s: pack_cd", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ /* options */
+ opt = &ctx->opt;
+ opt->dwVersion = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1;
+ opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms;
+ opt->dwAttestationConveyancePreference =
+ WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT;
+ if (pack_credlist(&opt->CredentialList, &cred->excl) < 0) {
+ fido_log_debug("%s: pack_credlist", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (pack_cred_ext(&opt->Extensions, &cred->ext) < 0) {
+ fido_log_debug("%s: pack_cred_ext", __func__);
+ return FIDO_ERR_UNSUPPORTED_EXTENSION;
+ }
+ if (set_cred_uv(&opt->dwUserVerificationRequirement, (cred->ext.mask &
+ FIDO_EXT_CRED_PROTECT) ? FIDO_OPT_TRUE : cred->uv, pin) < 0) {
+ fido_log_debug("%s: set_cred_uv", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (cred->rk == FIDO_OPT_TRUE) {
+ opt->bRequireResidentKey = true;
+ }
+
+ return FIDO_OK;
+}
+
+static int
+decode_attobj(const cbor_item_t *key, const cbor_item_t *val, void *arg)
+{
+ fido_cred_t *cred = arg;
+ char *name = NULL;
+ int ok = -1;
+
+ if (cbor_string_copy(key, &name) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ ok = 0; /* ignore */
+ goto fail;
+ }
+
+ if (!strcmp(name, "fmt")) {
+ if (cbor_decode_fmt(val, &cred->fmt) < 0) {
+ fido_log_debug("%s: cbor_decode_fmt", __func__);
+ goto fail;
+ }
+ } else if (!strcmp(name, "attStmt")) {
+ if (cbor_decode_attstmt(val, &cred->attstmt) < 0) {
+ fido_log_debug("%s: cbor_decode_attstmt", __func__);
+ goto fail;
+ }
+ } else if (!strcmp(name, "authData")) {
+ if (fido_blob_decode(val, &cred->authdata_raw) < 0) {
+ fido_log_debug("%s: fido_blob_decode", __func__);
+ goto fail;
+ }
+ if (cbor_decode_cred_authdata(val, cred->type,
+ &cred->authdata_cbor, &cred->authdata, &cred->attcred,
+ &cred->authdata_ext) < 0) {
+ fido_log_debug("%s: cbor_decode_cred_authdata",
+ __func__);
+ goto fail;
+ }
+ }
+
+ ok = 0;
+fail:
+ free(name);
+
+ return (ok);
+}
+
+static int
+translate_winhello_cred(fido_cred_t *cred,
+ const WEBAUTHN_CREDENTIAL_ATTESTATION *att)
+{
+ cbor_item_t *item = NULL;
+ struct cbor_load_result cbor;
+ int r = FIDO_ERR_INTERNAL;
+
+ if (att->pbAttestationObject == NULL) {
+ fido_log_debug("%s: pbAttestationObject", __func__);
+ goto fail;
+ }
+ if ((item = cbor_load(att->pbAttestationObject,
+ att->cbAttestationObject, &cbor)) == NULL) {
+ fido_log_debug("%s: cbor_load", __func__);
+ goto fail;
+ }
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ cbor_map_iter(item, cred, decode_attobj) < 0) {
+ fido_log_debug("%s: cbor type", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ if (item != NULL)
+ cbor_decref(&item);
+
+ return r;
+}
+
+static int
+winhello_get_assert(HWND w, struct winhello_assert *ctx)
+{
+ HRESULT hr;
+ int r = FIDO_OK;
+
+ if ((hr = webauthn_get_assert(w, ctx->rp_id, &ctx->cd, &ctx->opt,
+ &ctx->assert)) != S_OK) {
+ r = to_fido(hr);
+ fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
+ fido_strerr(r));
+ }
+
+ return r;
+}
+
+static int
+winhello_make_cred(HWND w, struct winhello_cred *ctx)
+{
+ HRESULT hr;
+ int r = FIDO_OK;
+
+ if ((hr = webauthn_make_cred(w, &ctx->rp, &ctx->user, &ctx->cose,
+ &ctx->cd, &ctx->opt, &ctx->att)) != S_OK) {
+ r = to_fido(hr);
+ fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr),
+ fido_strerr(r));
+ }
+
+ return r;
+}
+
+static void
+winhello_assert_free(struct winhello_assert *ctx)
+{
+ if (ctx == NULL)
+ return;
+ if (ctx->assert != NULL)
+ webauthn_free_assert(ctx->assert);
+
+ free(ctx->rp_id);
+ free(ctx->appid);
+ free(ctx->opt.CredentialList.pCredentials);
+ if (ctx->opt.pHmacSecretSaltValues != NULL)
+ free(ctx->opt.pHmacSecretSaltValues->pGlobalHmacSalt);
+ free(ctx->opt.pHmacSecretSaltValues);
+ free(ctx);
+}
+
+static void
+winhello_cred_free(struct winhello_cred *ctx)
+{
+ if (ctx == NULL)
+ return;
+ if (ctx->att != NULL)
+ webauthn_free_attest(ctx->att);
+
+ free(ctx->rp_id);
+ free(ctx->rp_name);
+ free(ctx->user_name);
+ free(ctx->user_icon);
+ free(ctx->display_name);
+ free(ctx->opt.CredentialList.pCredentials);
+ for (size_t i = 0; i < ctx->opt.Extensions.cExtensions; i++) {
+ WEBAUTHN_EXTENSION *e;
+ e = &ctx->opt.Extensions.pExtensions[i];
+ free(e->pvExtension);
+ }
+ free(ctx->opt.Extensions.pExtensions);
+ free(ctx);
+}
+
+int
+fido_winhello_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen)
+{
+ fido_dev_info_t *di;
+
+ if (ilen == 0) {
+ return FIDO_OK;
+ }
+ if (devlist == NULL) {
+ return FIDO_ERR_INVALID_ARGUMENT;
+ }
+ if (!webauthn_loaded && webauthn_load() < 0) {
+ fido_log_debug("%s: webauthn_load", __func__);
+ return FIDO_OK; /* not an error */
+ }
+
+ di = &devlist[*olen];
+ memset(di, 0, sizeof(*di));
+ di->path = strdup(FIDO_WINHELLO_PATH);
+ di->manufacturer = strdup("Microsoft Corporation");
+ di->product = strdup("Windows Hello");
+ di->vendor_id = VENDORID;
+ di->product_id = PRODID;
+ if (di->path == NULL || di->manufacturer == NULL ||
+ di->product == NULL) {
+ free(di->path);
+ free(di->manufacturer);
+ free(di->product);
+ explicit_bzero(di, sizeof(*di));
+ return FIDO_ERR_INTERNAL;
+ }
+ ++(*olen);
+
+ return FIDO_OK;
+}
+
+int
+fido_winhello_open(fido_dev_t *dev)
+{
+ if (!webauthn_loaded && webauthn_load() < 0) {
+ fido_log_debug("%s: webauthn_load", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if (dev->flags != 0)
+ return FIDO_ERR_INVALID_ARGUMENT;
+ dev->attr.flags = FIDO_CAP_CBOR | FIDO_CAP_WINK;
+ dev->flags = FIDO_DEV_WINHELLO | FIDO_DEV_CRED_PROT | FIDO_DEV_PIN_SET;
+
+ return FIDO_OK;
+}
+
+int
+fido_winhello_close(fido_dev_t *dev)
+{
+ memset(dev, 0, sizeof(*dev));
+
+ return FIDO_OK;
+}
+
+int
+fido_winhello_cancel(fido_dev_t *dev)
+{
+ (void)dev;
+
+ return FIDO_ERR_INTERNAL;
+}
+
+int
+fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert,
+ const char *pin, int ms)
+{
+ HWND w;
+ struct winhello_assert *ctx;
+ int r = FIDO_ERR_INTERNAL;
+
+ (void)dev;
+
+ fido_assert_reset_rx(assert);
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+ if ((w = GetForegroundWindow()) == NULL) {
+ fido_log_debug("%s: GetForegroundWindow", __func__);
+ if ((w = GetTopWindow(NULL)) == NULL) {
+ fido_log_debug("%s: GetTopWindow", __func__);
+ goto fail;
+ }
+ }
+ if ((r = translate_fido_assert(ctx, assert, pin, ms)) != FIDO_OK) {
+ fido_log_debug("%s: translate_fido_assert", __func__);
+ goto fail;
+ }
+ if ((r = winhello_get_assert(w, ctx)) != FIDO_OK) {
+ fido_log_debug("%s: winhello_get_assert", __func__);
+ goto fail;
+ }
+ if ((r = translate_winhello_assert(assert, ctx)) != FIDO_OK) {
+ fido_log_debug("%s: translate_winhello_assert", __func__);
+ goto fail;
+ }
+
+fail:
+ winhello_assert_free(ctx);
+
+ return r;
+}
+
+int
+fido_winhello_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci)
+{
+ const char *v[3] = { "U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE" };
+ const char *e[2] = { "credProtect", "hmac-secret" };
+ const char *t[2] = { "nfc", "usb" };
+ const char *o[4] = { "rk", "up", "uv", "plat" };
+
+ (void)dev;
+
+ fido_cbor_info_reset(ci);
+
+ if (fido_str_array_pack(&ci->versions, v, nitems(v)) < 0 ||
+ fido_str_array_pack(&ci->extensions, e, nitems(e)) < 0 ||
+ fido_str_array_pack(&ci->transports, t, nitems(t)) < 0) {
+ fido_log_debug("%s: fido_str_array_pack", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ if ((ci->options.name = calloc(nitems(o), sizeof(char *))) == NULL ||
+ (ci->options.value = calloc(nitems(o), sizeof(bool))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ for (size_t i = 0; i < nitems(o); i++) {
+ if ((ci->options.name[i] = strdup(o[i])) == NULL) {
+ fido_log_debug("%s: strdup", __func__);
+ return FIDO_ERR_INTERNAL;
+ }
+ ci->options.value[i] = true;
+ ci->options.len++;
+ }
+
+ return FIDO_OK;
+}
+
+int
+fido_winhello_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin,
+ int ms)
+{
+ HWND w;
+ struct winhello_cred *ctx;
+ int r = FIDO_ERR_INTERNAL;
+
+ (void)dev;
+
+ fido_cred_reset_rx(cred);
+
+ if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
+ fido_log_debug("%s: calloc", __func__);
+ goto fail;
+ }
+ if ((w = GetForegroundWindow()) == NULL) {
+ fido_log_debug("%s: GetForegroundWindow", __func__);
+ if ((w = GetTopWindow(NULL)) == NULL) {
+ fido_log_debug("%s: GetTopWindow", __func__);
+ goto fail;
+ }
+ }
+ if ((r = translate_fido_cred(ctx, cred, pin, ms)) != FIDO_OK) {
+ fido_log_debug("%s: translate_fido_cred", __func__);
+ goto fail;
+ }
+ if ((r = winhello_make_cred(w, ctx)) != FIDO_OK) {
+ fido_log_debug("%s: winhello_make_cred", __func__);
+ goto fail;
+ }
+ if ((r = translate_winhello_cred(cred, ctx->att)) != FIDO_OK) {
+ fido_log_debug("%s: translate_winhello_cred", __func__);
+ goto fail;
+ }
+
+ r = FIDO_OK;
+fail:
+ winhello_cred_free(ctx);
+
+ return r;
+}