summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CMakeLists.txt498
-rw-r--r--LICENSE26
-rw-r--r--NEWS266
-rw-r--r--README.adoc144
-rw-r--r--SECURITY.md5
-rw-r--r--examples/CMakeLists.txt59
-rw-r--r--examples/README.adoc100
-rw-r--r--examples/assert.c349
-rw-r--r--examples/cred.c331
-rw-r--r--examples/extern.h27
-rw-r--r--examples/info.c382
-rw-r--r--examples/manifest.c42
-rw-r--r--examples/reset.c49
-rw-r--r--examples/retries.c49
-rw-r--r--examples/select.c215
-rw-r--r--examples/setpin.c55
-rw-r--r--examples/util.c444
-rw-r--r--fuzz/CMakeLists.txt82
-rw-r--r--fuzz/Dockerfile16
-rw-r--r--fuzz/Makefile90
-rw-r--r--fuzz/README43
-rwxr-xr-xfuzz/build-coverage34
-rw-r--r--fuzz/clock.c80
-rw-r--r--fuzz/dummy.h182
-rw-r--r--fuzz/export.gnu285
-rw-r--r--fuzz/functions.txt954
-rw-r--r--fuzz/fuzz_assert.c534
-rw-r--r--fuzz/fuzz_bio.c442
-rw-r--r--fuzz/fuzz_cred.c482
-rw-r--r--fuzz/fuzz_credman.c407
-rw-r--r--fuzz/fuzz_hid.c239
-rw-r--r--fuzz/fuzz_largeblob.c272
-rw-r--r--fuzz/fuzz_mgmt.c528
-rw-r--r--fuzz/fuzz_netlink.c164
-rw-r--r--fuzz/fuzz_pcsc.c269
-rw-r--r--fuzz/libfuzzer.c230
-rw-r--r--fuzz/mutator_aux.c332
-rw-r--r--fuzz/mutator_aux.h111
-rw-r--r--fuzz/pcsc.c153
-rw-r--r--fuzz/preload-fuzz.c105
-rw-r--r--fuzz/preload-snoop.c218
-rw-r--r--fuzz/prng.c113
-rw-r--r--fuzz/report.tgzbin0 -> 361946 bytes
-rw-r--r--fuzz/summary.txt64
-rw-r--r--fuzz/udev.c270
-rw-r--r--fuzz/uniform_random.c57
-rw-r--r--fuzz/wiredata_fido2.h708
-rw-r--r--fuzz/wiredata_u2f.h153
-rw-r--r--fuzz/wrap.c700
-rw-r--r--fuzz/wrapped.sym102
-rw-r--r--man/CMakeLists.txt413
-rw-r--r--man/NOTES7
-rwxr-xr-xman/check.sh43
-rw-r--r--man/dyc.css14
-rw-r--r--man/eddsa_pk_new.3146
-rw-r--r--man/es256_pk_new.3164
-rw-r--r--man/es384_pk_new.3164
-rw-r--r--man/fido2-assert.1286
-rw-r--r--man/fido2-cred.1297
-rw-r--r--man/fido2-token.1421
-rw-r--r--man/fido_assert_allow_cred.380
-rw-r--r--man/fido_assert_new.3293
-rw-r--r--man/fido_assert_set_authdata.3314
-rw-r--r--man/fido_assert_verify.3104
-rw-r--r--man/fido_bio_dev_get_info.3145
-rw-r--r--man/fido_bio_enroll_new.3118
-rw-r--r--man/fido_bio_info_new.3104
-rw-r--r--man/fido_bio_template.3202
-rw-r--r--man/fido_cbor_info_new.3389
-rw-r--r--man/fido_cred_exclude.393
-rw-r--r--man/fido_cred_new.3316
-rw-r--r--man/fido_cred_set_authdata.3382
-rw-r--r--man/fido_cred_verify.3114
-rw-r--r--man/fido_credman_metadata_new.3349
-rw-r--r--man/fido_dev_enable_entattest.3149
-rw-r--r--man/fido_dev_get_assert.399
-rw-r--r--man/fido_dev_get_touch_begin.396
-rw-r--r--man/fido_dev_info_manifest.3211
-rw-r--r--man/fido_dev_largeblob_get.3216
-rw-r--r--man/fido_dev_make_cred.3100
-rw-r--r--man/fido_dev_open.3316
-rw-r--r--man/fido_dev_set_io_functions.3261
-rw-r--r--man/fido_dev_set_pin.3128
-rw-r--r--man/fido_init.395
-rw-r--r--man/fido_strerr.350
-rw-r--r--man/rs256_pk_new.3160
-rw-r--r--man/style.css24
-rw-r--r--openbsd-compat/bsd-asprintf.c88
-rw-r--r--openbsd-compat/bsd-getline.c115
-rw-r--r--openbsd-compat/bsd-getpagesize.c27
-rw-r--r--openbsd-compat/clock_gettime.c33
-rw-r--r--openbsd-compat/endian_win32.c52
-rw-r--r--openbsd-compat/err.h85
-rw-r--r--openbsd-compat/explicit_bzero.c57
-rw-r--r--openbsd-compat/explicit_bzero_win32.c19
-rw-r--r--openbsd-compat/freezero.c30
-rw-r--r--openbsd-compat/getopt.h74
-rw-r--r--openbsd-compat/getopt_long.c523
-rw-r--r--openbsd-compat/openbsd-compat.h123
-rw-r--r--openbsd-compat/posix_ioctl_check.c7
-rw-r--r--openbsd-compat/posix_win.c61
-rw-r--r--openbsd-compat/posix_win.h47
-rw-r--r--openbsd-compat/readpassphrase.c214
-rw-r--r--openbsd-compat/readpassphrase.h44
-rw-r--r--openbsd-compat/readpassphrase_win32.c131
-rw-r--r--openbsd-compat/recallocarray.c91
-rw-r--r--openbsd-compat/strlcat.c63
-rw-r--r--openbsd-compat/strlcpy.c59
-rw-r--r--openbsd-compat/strsep.c79
-rw-r--r--openbsd-compat/time.h61
-rw-r--r--openbsd-compat/timingsafe_bcmp.c35
-rw-r--r--openbsd-compat/types.h69
-rw-r--r--regress/CMakeLists.txt57
-rw-r--r--regress/assert.c685
-rw-r--r--regress/compress.c268
-rw-r--r--regress/cred.c2185
-rw-r--r--regress/dev.c439
-rw-r--r--regress/eddsa.c159
-rw-r--r--regress/es256.c199
-rw-r--r--regress/es384.c213
-rw-r--r--regress/rs256.c201
-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
-rw-r--r--tools/CMakeLists.txt74
-rw-r--r--tools/assert_get.c328
-rw-r--r--tools/assert_verify.c208
-rw-r--r--tools/base64.c135
-rw-r--r--tools/bio.c278
-rw-r--r--tools/config.c198
-rw-r--r--tools/cred_make.c255
-rw-r--r--tools/cred_verify.c182
-rw-r--r--tools/credman.c330
-rw-r--r--tools/extern.h103
-rw-r--r--tools/fido2-assert.c55
-rwxr-xr-xtools/fido2-attach.sh15
-rw-r--r--tools/fido2-cred.c53
-rwxr-xr-xtools/fido2-detach.sh13
-rw-r--r--tools/fido2-token.c110
-rwxr-xr-xtools/fido2-unprot.sh76
-rwxr-xr-xtools/include_check.sh22
-rw-r--r--tools/largeblob.c618
-rw-r--r--tools/pin.c159
-rwxr-xr-xtools/test.sh304
-rw-r--r--tools/token.c729
-rw-r--r--tools/util.c643
-rw-r--r--udev/70-u2f.rules270
-rw-r--r--udev/CMakeLists.txt8
-rwxr-xr-xudev/check.sh32
-rw-r--r--udev/fidodevs137
-rwxr-xr-xudev/genrules.awk79
-rw-r--r--windows/build.ps1243
-rw-r--r--windows/const.ps148
-rwxr-xr-xwindows/cygwin.gpgbin0 -> 2193 bytes
-rwxr-xr-xwindows/cygwin.ps170
-rw-r--r--windows/libressl.gpgbin0 -> 16425 bytes
-rw-r--r--windows/release.ps197
225 files changed, 54621 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c4f7b1b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,498 @@
+# 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
+cmake_minimum_required(VERSION 3.7)
+
+# detect AppleClang; needs to come before project()
+cmake_policy(SET CMP0025 NEW)
+
+project(libfido2 C)
+# Set PIE flags for POSITION_INDEPENDENT_CODE targets, added in CMake 3.14.
+if(POLICY CMP0083)
+ cmake_policy(SET CMP0083 NEW)
+endif()
+
+include(CheckCCompilerFlag)
+include(CheckFunctionExists)
+include(CheckLibraryExists)
+include(CheckSymbolExists)
+include(CheckIncludeFiles)
+include(CheckTypeSize)
+include(GNUInstallDirs)
+include(CheckPIESupported OPTIONAL RESULT_VARIABLE CHECK_PIE_SUPPORTED)
+if(CHECK_PIE_SUPPORTED)
+ check_pie_supported(LANGUAGES C)
+endif()
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+set(CMAKE_COLOR_MAKEFILE OFF)
+set(CMAKE_VERBOSE_MAKEFILE ON)
+set(FIDO_MAJOR "1")
+set(FIDO_MINOR "14")
+set(FIDO_PATCH "0")
+set(FIDO_VERSION ${FIDO_MAJOR}.${FIDO_MINOR}.${FIDO_PATCH})
+
+option(BUILD_TESTS "Build the regress tests" ON)
+option(BUILD_EXAMPLES "Build example programs" ON)
+option(BUILD_MANPAGES "Build man pages" ON)
+option(BUILD_SHARED_LIBS "Build a shared library" ON)
+option(BUILD_STATIC_LIBS "Build a static library" ON)
+option(BUILD_TOOLS "Build tool programs" ON)
+option(FUZZ "Enable fuzzing instrumentation" OFF)
+option(USE_HIDAPI "Use hidapi as the HID backend" OFF)
+option(USE_PCSC "Enable experimental PCSC support" OFF)
+option(USE_WINHELLO "Abstract Windows Hello as a FIDO device" ON)
+option(NFC_LINUX "Enable NFC support on Linux" ON)
+
+add_definitions(-D_FIDO_MAJOR=${FIDO_MAJOR})
+add_definitions(-D_FIDO_MINOR=${FIDO_MINOR})
+add_definitions(-D_FIDO_PATCH=${FIDO_PATCH})
+
+if(BUILD_SHARED_LIBS)
+ set(_FIDO2_LIBRARY fido2_shared)
+elseif(BUILD_STATIC_LIBS)
+ set(_FIDO2_LIBRARY fido2)
+else()
+ message(FATAL_ERROR "Nothing to build (BUILD_*_LIBS=OFF)")
+endif()
+
+if(CYGWIN OR MSYS OR MINGW)
+ set(WIN32 1)
+endif()
+
+if(WIN32)
+ add_definitions(-DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0600)
+endif()
+
+if(APPLE)
+ set(CMAKE_INSTALL_NAME_DIR
+ "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
+endif()
+
+if(NOT MSVC)
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_POSIX_C_SOURCE=200809L")
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_BSD_SOURCE")
+ if(APPLE)
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_DARWIN_C_SOURCE")
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D__STDC_WANT_LIB_EXT1__=1")
+ elseif((CMAKE_SYSTEM_NAME STREQUAL "Linux") OR MINGW OR CYGWIN)
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_GNU_SOURCE")
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_DEFAULT_SOURCE")
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
+ CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD")
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D__BSD_VISIBLE=1")
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD")
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_NETBSD_SOURCE")
+ endif()
+ set(FIDO_CFLAGS "${FIDO_CFLAGS} -std=c99")
+ set(CMAKE_C_FLAGS "${FIDO_CFLAGS} ${CMAKE_C_FLAGS}")
+endif()
+
+check_c_compiler_flag("-Wshorten-64-to-32" HAVE_SHORTEN_64_TO_32)
+check_c_compiler_flag("-Werror -fstack-protector-all" HAVE_STACK_PROTECTOR_ALL)
+
+check_include_files(cbor.h HAVE_CBOR_H)
+check_include_files(endian.h HAVE_ENDIAN_H)
+check_include_files(err.h HAVE_ERR_H)
+check_include_files(openssl/opensslv.h HAVE_OPENSSLV_H)
+check_include_files(signal.h HAVE_SIGNAL_H)
+check_include_files(sys/random.h HAVE_SYS_RANDOM_H)
+check_include_files(unistd.h HAVE_UNISTD_H)
+
+check_symbol_exists(arc4random_buf stdlib.h HAVE_ARC4RANDOM_BUF)
+check_symbol_exists(asprintf stdio.h HAVE_ASPRINTF)
+check_symbol_exists(clock_gettime time.h HAVE_CLOCK_GETTIME)
+check_symbol_exists(explicit_bzero string.h HAVE_EXPLICIT_BZERO)
+check_symbol_exists(freezero stdlib.h HAVE_FREEZERO)
+check_symbol_exists(getline stdio.h HAVE_GETLINE)
+check_symbol_exists(getopt unistd.h HAVE_GETOPT)
+check_symbol_exists(getpagesize unistd.h HAVE_GETPAGESIZE)
+check_symbol_exists(getrandom sys/random.h HAVE_GETRANDOM)
+check_symbol_exists(memset_s string.h HAVE_MEMSET_S)
+check_symbol_exists(readpassphrase readpassphrase.h HAVE_READPASSPHRASE)
+check_symbol_exists(recallocarray stdlib.h HAVE_RECALLOCARRAY)
+check_symbol_exists(strlcat string.h HAVE_STRLCAT)
+check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
+check_symbol_exists(strsep string.h HAVE_STRSEP)
+check_symbol_exists(sysconf unistd.h HAVE_SYSCONF)
+check_symbol_exists(timespecsub sys/time.h HAVE_TIMESPECSUB)
+check_symbol_exists(timingsafe_bcmp string.h HAVE_TIMINGSAFE_BCMP)
+
+set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
+try_compile(HAVE_POSIX_IOCTL
+ "${CMAKE_CURRENT_BINARY_DIR}/posix_ioctl_check.o"
+ "${CMAKE_CURRENT_SOURCE_DIR}/openbsd-compat/posix_ioctl_check.c"
+ COMPILE_DEFINITIONS "-Werror -Woverflow -Wsign-conversion")
+
+list(APPEND CHECK_VARIABLES
+ HAVE_ARC4RANDOM_BUF
+ HAVE_ASPRINTF
+ HAVE_CBOR_H
+ HAVE_CLOCK_GETTIME
+ HAVE_ENDIAN_H
+ HAVE_ERR_H
+ HAVE_FREEZERO
+ HAVE_GETLINE
+ HAVE_GETOPT
+ HAVE_GETPAGESIZE
+ HAVE_GETRANDOM
+ HAVE_MEMSET_S
+ HAVE_OPENSSLV_H
+ HAVE_POSIX_IOCTL
+ HAVE_READPASSPHRASE
+ HAVE_RECALLOCARRAY
+ HAVE_SIGNAL_H
+ HAVE_STRLCAT
+ HAVE_STRLCPY
+ HAVE_STRSEP
+ HAVE_SYSCONF
+ HAVE_SYS_RANDOM_H
+ HAVE_TIMESPECSUB
+ HAVE_TIMINGSAFE_BCMP
+ HAVE_UNISTD_H
+)
+
+foreach(v ${CHECK_VARIABLES})
+ if (${v})
+ add_definitions(-D${v})
+ endif()
+endforeach()
+
+if(HAVE_EXPLICIT_BZERO AND NOT FUZZ)
+ add_definitions(-DHAVE_EXPLICIT_BZERO)
+endif()
+
+if(UNIX)
+ add_definitions(-DHAVE_DEV_URANDOM)
+endif()
+
+
+if(MSVC)
+ if((NOT CBOR_INCLUDE_DIRS) OR (NOT CBOR_LIBRARY_DIRS) OR
+ (NOT CRYPTO_INCLUDE_DIRS) OR (NOT CRYPTO_LIBRARY_DIRS) OR
+ (NOT ZLIB_INCLUDE_DIRS) OR (NOT ZLIB_LIBRARY_DIRS))
+ message(FATAL_ERROR "please define "
+ "{CBOR,CRYPTO,ZLIB}_{INCLUDE,LIBRARY}_DIRS when "
+ "building under msvc")
+ endif()
+ if(BUILD_TESTS AND BUILD_SHARED_LIBS AND
+ ((NOT CBOR_BIN_DIRS) OR (NOT ZLIB_BIN_DIRS) OR (NOT CRYPTO_BIN_DIRS)))
+ message(FATAL_ERROR "please define {CBOR,CRYPTO,ZLIB}_BIN_DIRS "
+ "when building tests")
+ endif()
+ if(NOT CBOR_LIBRARIES)
+ set(CBOR_LIBRARIES cbor)
+ endif()
+ if(NOT ZLIB_LIBRARIES)
+ set(ZLIB_LIBRARIES zlib1)
+ endif()
+ if(NOT CRYPTO_LIBRARIES)
+ set(CRYPTO_LIBRARIES crypto)
+ endif()
+
+ set(MSVC_DISABLED_WARNINGS_LIST
+ "C4152" # nonstandard extension used: function/data pointer
+ # conversion in expression;
+ "C4200" # nonstandard extension used: zero-sized array in
+ # struct/union;
+ "C4201" # nonstandard extension used: nameless struct/union;
+ "C4204" # nonstandard extension used: non-constant aggregate
+ # initializer;
+ "C4706" # assignment within conditional expression;
+ "C4996" # The POSIX name for this item is deprecated. Instead,
+ # use the ISO C and C++ conformant name;
+ "C6287" # redundant code: the left and right subexpressions are identical
+ )
+ # The construction in the following 3 lines was taken from LibreSSL's
+ # CMakeLists.txt.
+ string(REPLACE "C" " -wd" MSVC_DISABLED_WARNINGS_STR
+ ${MSVC_DISABLED_WARNINGS_LIST})
+ string(REGEX REPLACE "[/-]W[1234][ ]?" "" CMAKE_C_FLAGS ${CMAKE_C_FLAGS})
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -MP -W4 -WX ${MSVC_DISABLED_WARNINGS_STR}")
+ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Z7 /guard:cf /sdl /RTCcsu")
+ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zi /guard:cf /sdl")
+ if(USE_WINHELLO)
+ add_definitions(-DUSE_WINHELLO)
+ endif()
+ set(NFC_LINUX OFF)
+else()
+ include(FindPkgConfig)
+ pkg_search_module(CBOR libcbor)
+ pkg_search_module(CRYPTO libcrypto)
+ pkg_search_module(ZLIB zlib)
+
+ if(NOT CBOR_FOUND AND NOT HAVE_CBOR_H)
+ message(FATAL_ERROR "could not find libcbor")
+ endif()
+ if(NOT CRYPTO_FOUND AND NOT HAVE_OPENSSLV_H)
+ message(FATAL_ERROR "could not find libcrypto")
+ endif()
+ if(NOT ZLIB_FOUND)
+ message(FATAL_ERROR "could not find zlib")
+ endif()
+
+ if(NOT CBOR_LIBRARIES)
+ set(CBOR_LIBRARIES "cbor")
+ endif()
+ if(NOT CRYPTO_LIBRARIES)
+ set(CRYPTO_LIBRARIES "crypto")
+ endif()
+
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ pkg_search_module(UDEV libudev REQUIRED)
+ set(UDEV_NAME "udev")
+ # If using hidapi, use hidapi-hidraw.
+ set(HIDAPI_SUFFIX -hidraw)
+ if(NOT HAVE_CLOCK_GETTIME)
+ # Look for clock_gettime in librt.
+ check_library_exists(rt clock_gettime "time.h"
+ HAVE_CLOCK_GETTIME)
+ if (HAVE_CLOCK_GETTIME)
+ add_definitions(-DHAVE_CLOCK_GETTIME)
+ set(BASE_LIBRARIES ${BASE_LIBRARIES} rt)
+ endif()
+ endif()
+ else()
+ set(NFC_LINUX OFF)
+ endif()
+
+ if(MINGW)
+ # MinGW is stuck with a flavour of C89.
+ add_definitions(-DFIDO_NO_DIAGNOSTIC)
+ add_definitions(-DWC_ERR_INVALID_CHARS=0x80)
+ add_compile_options(-Wno-unused-parameter)
+ endif()
+
+ if(FUZZ)
+ set(USE_PCSC ON)
+ add_definitions(-DFIDO_FUZZ)
+ endif()
+
+ # If building with PCSC, look for pcsc-lite.
+ if(USE_PCSC AND NOT (APPLE OR CYGWIN OR MSYS OR MINGW))
+ pkg_search_module(PCSC libpcsclite REQUIRED)
+ set(PCSC_LIBRARIES pcsclite)
+ endif()
+
+ if(USE_HIDAPI)
+ add_definitions(-DUSE_HIDAPI)
+ pkg_search_module(HIDAPI hidapi${HIDAPI_SUFFIX} REQUIRED)
+ set(HIDAPI_LIBRARIES hidapi${HIDAPI_SUFFIX})
+ endif()
+
+ if(NFC_LINUX)
+ add_definitions(-DUSE_NFC)
+ endif()
+
+ if(WIN32)
+ if(USE_WINHELLO)
+ add_definitions(-DUSE_WINHELLO)
+ endif()
+ else()
+ set(USE_WINHELLO OFF)
+ endif()
+
+ add_compile_options(-Wall)
+ add_compile_options(-Wextra)
+ add_compile_options(-Werror)
+ add_compile_options(-Wshadow)
+ add_compile_options(-Wcast-qual)
+ add_compile_options(-Wwrite-strings)
+ add_compile_options(-Wmissing-prototypes)
+ add_compile_options(-Wbad-function-cast)
+ add_compile_options(-Wimplicit-fallthrough)
+ add_compile_options(-pedantic)
+ add_compile_options(-pedantic-errors)
+
+ set(EXTRA_CFLAGS "-Wconversion -Wsign-conversion")
+
+ if(WIN32)
+ add_compile_options(-Wno-type-limits)
+ add_compile_options(-Wno-cast-function-type)
+ endif()
+
+ if(HAVE_SHORTEN_64_TO_32)
+ add_compile_options(-Wshorten-64-to-32)
+ endif()
+
+ if(HAVE_STACK_PROTECTOR_ALL)
+ add_compile_options(-fstack-protector-all)
+ endif()
+
+ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g2")
+ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer")
+ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -D_FORTIFY_SOURCE=2")
+
+ if(CRYPTO_VERSION VERSION_GREATER_EQUAL 3.0)
+ add_definitions(-DOPENSSL_API_COMPAT=0x10100000L)
+ endif()
+
+ if(NOT FUZZ)
+ set(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wframe-larger-than=2047")
+ endif()
+endif()
+
+# Avoid https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425
+if(CMAKE_COMPILER_IS_GNUCC)
+ add_compile_options(-Wno-unused-result)
+endif()
+
+# Decide which keyword to use for thread-local storage.
+if(CMAKE_COMPILER_IS_GNUCC OR
+ CMAKE_C_COMPILER_ID STREQUAL "Clang" OR
+ CMAKE_C_COMPILER_ID STREQUAL "AppleClang")
+ set(TLS "__thread")
+elseif(WIN32)
+ set(TLS "__declspec(thread)")
+endif()
+add_definitions(-DTLS=${TLS})
+
+if(USE_PCSC)
+ add_definitions(-DUSE_PCSC)
+endif()
+
+# export list
+if(APPLE AND (CMAKE_C_COMPILER_ID STREQUAL "Clang" OR
+ CMAKE_C_COMPILER_ID STREQUAL "AppleClang"))
+ # clang + lld
+ string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}
+ " -exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/src/export.llvm")
+elseif(NOT MSVC)
+ # clang/gcc + gnu ld
+ if(FUZZ)
+ string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}
+ " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/fuzz/export.gnu")
+ else()
+ string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}
+ " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/export.gnu")
+ endif()
+ if(NOT WIN32)
+ string(CONCAT CMAKE_SHARED_LINKER_FLAGS
+ ${CMAKE_SHARED_LINKER_FLAGS}
+ " -Wl,-z,noexecstack -Wl,-z,relro,-z,now")
+ string(CONCAT CMAKE_EXE_LINKER_FLAGS
+ ${CMAKE_EXE_LINKER_FLAGS}
+ " -Wl,-z,noexecstack -Wl,-z,relro,-z,now")
+ if(FUZZ)
+ file(STRINGS fuzz/wrapped.sym WRAPPED_SYMBOLS)
+ foreach(s ${WRAPPED_SYMBOLS})
+ string(CONCAT CMAKE_SHARED_LINKER_FLAGS
+ ${CMAKE_SHARED_LINKER_FLAGS}
+ " -Wl,--wrap=${s}")
+ endforeach()
+ endif()
+ endif()
+else()
+ string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}
+ " /def:\"${CMAKE_CURRENT_SOURCE_DIR}/src/export.msvc\"")
+endif()
+
+include_directories(${PROJECT_SOURCE_DIR}/src)
+include_directories(${CBOR_INCLUDE_DIRS})
+include_directories(${CRYPTO_INCLUDE_DIRS})
+include_directories(${HIDAPI_INCLUDE_DIRS})
+include_directories(${PCSC_INCLUDE_DIRS})
+include_directories(${UDEV_INCLUDE_DIRS})
+include_directories(${ZLIB_INCLUDE_DIRS})
+
+link_directories(${CBOR_LIBRARY_DIRS})
+link_directories(${CRYPTO_LIBRARY_DIRS})
+link_directories(${HIDAPI_LIBRARY_DIRS})
+link_directories(${PCSC_LIBRARY_DIRS})
+link_directories(${UDEV_LIBRARY_DIRS})
+link_directories(${ZLIB_LIBRARY_DIRS})
+
+message(STATUS "BASE_LIBRARIES: ${BASE_LIBRARIES}")
+message(STATUS "BUILD_EXAMPLES: ${BUILD_EXAMPLES}")
+message(STATUS "BUILD_MANPAGES: ${BUILD_MANPAGES}")
+message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}")
+message(STATUS "BUILD_STATIC_LIBS: ${BUILD_STATIC_LIBS}")
+message(STATUS "BUILD_TOOLS: ${BUILD_TOOLS}")
+message(STATUS "CBOR_INCLUDE_DIRS: ${CBOR_INCLUDE_DIRS}")
+message(STATUS "CBOR_LIBRARIES: ${CBOR_LIBRARIES}")
+message(STATUS "CBOR_LIBRARY_DIRS: ${CBOR_LIBRARY_DIRS}")
+if(BUILD_TESTS)
+ message(STATUS "CBOR_BIN_DIRS: ${CBOR_BIN_DIRS}")
+endif()
+message(STATUS "CBOR_VERSION: ${CBOR_VERSION}")
+message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
+message(STATUS "CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}")
+message(STATUS "CMAKE_C_COMPILER_ID: ${CMAKE_C_COMPILER_ID}")
+message(STATUS "CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}")
+message(STATUS "CMAKE_CROSSCOMPILING: ${CMAKE_CROSSCOMPILING}")
+message(STATUS "CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}")
+message(STATUS "CMAKE_HOST_SYSTEM_NAME: ${CMAKE_HOST_SYSTEM_NAME}")
+message(STATUS "CMAKE_HOST_SYSTEM_PROCESSOR: ${CMAKE_HOST_SYSTEM_PROCESSOR}")
+message(STATUS "CMAKE_INSTALL_LIBDIR: ${CMAKE_INSTALL_LIBDIR}")
+message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
+message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}")
+message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
+message(STATUS "CMAKE_SYSTEM_VERSION: ${CMAKE_SYSTEM_VERSION}")
+message(STATUS "CRYPTO_INCLUDE_DIRS: ${CRYPTO_INCLUDE_DIRS}")
+message(STATUS "CRYPTO_LIBRARIES: ${CRYPTO_LIBRARIES}")
+message(STATUS "CRYPTO_LIBRARY_DIRS: ${CRYPTO_LIBRARY_DIRS}")
+if(BUILD_TESTS)
+ message(STATUS "CRYPTO_BIN_DIRS: ${CRYPTO_BIN_DIRS}")
+endif()
+message(STATUS "CRYPTO_VERSION: ${CRYPTO_VERSION}")
+message(STATUS "FIDO_VERSION: ${FIDO_VERSION}")
+message(STATUS "FUZZ: ${FUZZ}")
+if(FUZZ)
+ message(STATUS "FUZZ_LDFLAGS: ${FUZZ_LDFLAGS}")
+endif()
+message(STATUS "ZLIB_INCLUDE_DIRS: ${ZLIB_INCLUDE_DIRS}")
+message(STATUS "ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}")
+message(STATUS "ZLIB_LIBRARY_DIRS: ${ZLIB_LIBRARY_DIRS}")
+if(BUILD_TESTS)
+ message(STATUS "ZLIB_BIN_DIRS: ${ZLIB_BIN_DIRS}")
+endif()
+message(STATUS "ZLIB_VERSION: ${ZLIB_VERSION}")
+if(USE_HIDAPI)
+ message(STATUS "HIDAPI_INCLUDE_DIRS: ${HIDAPI_INCLUDE_DIRS}")
+ message(STATUS "HIDAPI_LIBRARIES: ${HIDAPI_LIBRARIES}")
+ message(STATUS "HIDAPI_LIBRARY_DIRS: ${HIDAPI_LIBRARY_DIRS}")
+ message(STATUS "HIDAPI_VERSION: ${HIDAPI_VERSION}")
+endif()
+message(STATUS "PCSC_INCLUDE_DIRS: ${PCSC_INCLUDE_DIRS}")
+message(STATUS "PCSC_LIBRARIES: ${PCSC_LIBRARIES}")
+message(STATUS "PCSC_LIBRARY_DIRS: ${PCSC_LIBRARY_DIRS}")
+message(STATUS "PCSC_VERSION: ${PCSC_VERSION}")
+message(STATUS "TLS: ${TLS}")
+message(STATUS "UDEV_INCLUDE_DIRS: ${UDEV_INCLUDE_DIRS}")
+message(STATUS "UDEV_LIBRARIES: ${UDEV_LIBRARIES}")
+message(STATUS "UDEV_LIBRARY_DIRS: ${UDEV_LIBRARY_DIRS}")
+message(STATUS "UDEV_RULES_DIR: ${UDEV_RULES_DIR}")
+message(STATUS "UDEV_VERSION: ${UDEV_VERSION}")
+message(STATUS "USE_HIDAPI: ${USE_HIDAPI}")
+message(STATUS "USE_PCSC: ${USE_PCSC}")
+message(STATUS "USE_WINHELLO: ${USE_WINHELLO}")
+message(STATUS "NFC_LINUX: ${NFC_LINUX}")
+
+if(BUILD_TESTS)
+ enable_testing()
+endif()
+
+add_subdirectory(src)
+
+if(BUILD_TESTS)
+ add_subdirectory(regress)
+endif()
+if(BUILD_EXAMPLES)
+ add_subdirectory(examples)
+endif()
+if(BUILD_TOOLS)
+ add_subdirectory(tools)
+endif()
+if(BUILD_MANPAGES)
+ add_subdirectory(man)
+endif()
+
+if(NOT WIN32)
+ if(FUZZ)
+ add_subdirectory(fuzz)
+ endif()
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ add_subdirectory(udev)
+ endif()
+endif()
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ad0e133
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+
+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.
+
+SPDX-License-Identifier: BSD-2-Clause
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..58387ff
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,266 @@
+* Version 1.14.0 (2023-11-13)
+ ** fido2-cred -M, fido2-token -G: support raw client data via -w flag.
+ ** winhello: support U2F AppID extension for assertions.
+ ** winhello: fix restrictive parsing of the hmac-secret on assertions.
+ ** winhello: translate NTE_USER_CANCELLED to FIDO_ERR_OPERATION_DENIED; gh#685.
+ ** New API calls:
+ ** fido_assert_authdata_raw_len;
+ ** fido_assert_authdata_raw_ptr;
+ ** fido_assert_set_winhello_appid.
+
+* Version 1.13.0 (2023-02-20)
+ ** Support for linking against OpenSSL on Windows; gh#668.
+ ** New API calls:
+ - fido_assert_empty_allow_list;
+ - fido_cred_empty_exclude_list.
+ ** fido2-token: fix issue when listing large blobs.
+ ** Improved support for different fuzzing engines.
+
+* Version 1.12.0 (2022-09-22)
+ ** Support for COSE_ES384.
+ ** Support for hidraw(4) on FreeBSD; gh#597.
+ ** Improved support for FIDO 2.1 authenticators.
+ ** New API calls:
+ - 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_cbor_info_certs_len;
+ - fido_cbor_info_certs_name_ptr;
+ - fido_cbor_info_certs_value_ptr;
+ - fido_cbor_info_maxrpid_minpinlen;
+ - fido_cbor_info_minpinlen;
+ - fido_cbor_info_new_pin_required;
+ - fido_cbor_info_rk_remaining;
+ - fido_cbor_info_uv_attempts;
+ - fido_cbor_info_uv_modality.
+ ** Documentation and reliability fixes.
+
+* Version 1.11.0 (2022-05-03)
+ ** Experimental PCSC support; enable with -DUSE_PCSC.
+ ** Improved OpenSSL 3.0 compatibility.
+ ** Use RFC1951 raw deflate to compress CTAP 2.1 largeBlobs.
+ ** winhello: advertise "uv" instead of "clientPin".
+ ** winhello: support hmac-secret in fido_dev_get_assert().
+ ** New API calls:
+ - fido_cbor_info_maxlargeblob.
+ ** Documentation and reliability fixes.
+ ** Separate build and regress targets.
+
+* Version 1.10.0 (2022-01-17)
+ ** hid_osx: handle devices with paths > 511 bytes; gh#462.
+ ** bio: fix CTAP2 canonical CBOR encoding in fido_bio_dev_enroll_*(); gh#480.
+ ** winhello: fallback to GetTopWindow() if GetForegroundWindow() fails.
+ ** winhello: fallback to hid_win.c if webauthn.dll isn't available.
+ ** New API calls:
+ - fido_dev_info_set;
+ - fido_dev_io_handle;
+ - fido_dev_new_with_info;
+ - fido_dev_open_with_info.
+ ** Cygwin and NetBSD build fixes.
+ ** Documentation and reliability fixes.
+ ** Support for TPM 2.0 attestation of COSE_ES256 credentials.
+
+* Version 1.9.0 (2021-10-27)
+ ** Enabled NFC support on Linux.
+ ** Added OpenSSL 3.0 compatibility.
+ ** Removed OpenSSL 1.0 compatibility.
+ ** Support for FIDO 2.1 "minPinLength" extension.
+ ** Support for COSE_EDDSA, COSE_ES256, and COSE_RS1 attestation.
+ ** Support for TPM 2.0 attestation.
+ ** Support for device timeouts; see fido_dev_set_timeout().
+ ** New API calls:
+ - es256_pk_from_EVP_PKEY;
+ - fido_cred_attstmt_len;
+ - fido_cred_attstmt_ptr;
+ - fido_cred_pin_minlen;
+ - fido_cred_set_attstmt;
+ - fido_cred_set_pin_minlen;
+ - fido_dev_set_pin_minlen_rpid;
+ - fido_dev_set_timeout;
+ - rs256_pk_from_EVP_PKEY.
+ ** Reliability and portability fixes.
+ ** Better handling of HID devices without identification strings; gh#381.
+ ** Fixed detection of Windows's native webauthn API; gh#382.
+
+* Version 1.8.0 (2021-07-22)
+ ** Dropped 'Requires.private' entry from pkg-config file.
+ ** Better support for FIDO 2.1 authenticators.
+ ** Support for Windows's native webauthn API.
+ ** Support for attestation format 'none'.
+ ** New API calls:
+ - fido_assert_set_clientdata;
+ - fido_cbor_info_algorithm_cose;
+ - fido_cbor_info_algorithm_count;
+ - fido_cbor_info_algorithm_type;
+ - fido_cbor_info_transports_len;
+ - fido_cbor_info_transports_ptr;
+ - fido_cred_set_clientdata;
+ - fido_cred_set_id;
+ - fido_credman_set_dev_rk;
+ - fido_dev_is_winhello.
+ ** fido2-token: new -Sc option to update a resident credential.
+ ** Documentation and reliability fixes.
+ ** HID access serialisation on Linux.
+
+* Version 1.7.0 (2021-03-29)
+ ** New dependency on zlib.
+ ** Fixed musl build; gh#259.
+ ** hid_win: detect devices with vendor or product IDs > 0x7fff; gh#264.
+ ** Support for FIDO 2.1 authenticator configuration.
+ ** Support for FIDO 2.1 UV token permissions.
+ ** Support for FIDO 2.1 "credBlobs" and "largeBlobs" extensions.
+ ** New API calls:
+ - fido_assert_blob_len;
+ - fido_assert_blob_ptr;
+ - fido_assert_largeblob_key_len;
+ - fido_assert_largeblob_key_ptr;
+ - fido_assert_set_hmac_secret;
+ - fido_cbor_info_maxcredbloblen;
+ - fido_cred_largeblob_key_len;
+ - fido_cred_largeblob_key_ptr;
+ - fido_cred_set_blob;
+ - fido_dev_enable_entattest;
+ - fido_dev_force_pin_change;
+ - fido_dev_has_uv;
+ - fido_dev_largeblob_get;
+ - fido_dev_largeblob_get_array;
+ - fido_dev_largeblob_remove;
+ - fido_dev_largeblob_set;
+ - fido_dev_largeblob_set_array;
+ - fido_dev_set_pin_minlen;
+ - fido_dev_set_sigmask;
+ - fido_dev_supports_credman;
+ - fido_dev_supports_permissions;
+ - fido_dev_supports_uv;
+ - fido_dev_toggle_always_uv.
+ ** New fido_init flag to disable fido_dev_open's U2F fallback; gh#282.
+ ** Experimental NFC support on Linux; enable with -DNFC_LINUX.
+
+* Version 1.6.0 (2020-12-22)
+ ** Fix OpenSSL 1.0 and Cygwin builds.
+ ** hid_linux: fix build on 32-bit systems.
+ ** hid_osx: allow reads from spawned threads.
+ ** Documentation and reliability fixes.
+ ** New API calls:
+ - fido_cred_authdata_raw_len;
+ - fido_cred_authdata_raw_ptr;
+ - fido_cred_sigcount;
+ - fido_dev_get_uv_retry_count;
+ - fido_dev_supports_credman.
+ ** Hardened Windows build.
+ ** Native FreeBSD and NetBSD support.
+ ** Use CTAP2 canonical CBOR when combining hmac-secret and credProtect.
+
+* Version 1.5.0 (2020-09-01)
+ ** hid_linux: return FIDO_OK if no devices are found.
+ ** hid_osx:
+ - repair communication with U2F tokens, gh#166;
+ - reliability fixes.
+ ** fido2-{assert,cred}: new options to explicitly toggle UP, UV.
+ ** Support for configurable report lengths.
+ ** New API calls:
+ - fido_cbor_info_maxcredcntlst;
+ - fido_cbor_info_maxcredidlen;
+ - fido_cred_aaguid_len;
+ - fido_cred_aaguid_ptr;
+ - fido_dev_get_touch_begin;
+ - fido_dev_get_touch_status.
+ ** Use COSE_ECDH_ES256 with CTAP_CBOR_CLIENT_PIN; gh#154.
+ ** Allow CTAP messages up to 2048 bytes; gh#171.
+ ** Ensure we only list USB devices by default.
+
+* Version 1.4.0 (2020-04-15)
+ ** hid_hidapi: hidapi backend; enable with -DUSE_HIDAPI=1.
+ ** Fall back to U2F if the key claims to, but does not support FIDO2.
+ ** FIDO2 credential protection (credprot) support.
+ ** New API calls:
+ - fido_cbor_info_fwversion;
+ - fido_cred_prot;
+ - fido_cred_set_prot;
+ - fido_dev_set_transport_functions;
+ - fido_set_log_handler.
+ ** Support for FreeBSD.
+ ** Support for C++.
+ ** Support for MSYS.
+ ** Fixed EdDSA and RSA self-attestation.
+
+* Version 1.3.1 (2020-02-19)
+ ** fix zero-ing of le1 and le2 when talking to a U2F device.
+ ** dropping sk-libfido2 middleware, please find it in the openssh tree.
+
+* Version 1.3.0 (2019-11-28)
+ ** assert/hmac: encode public key as per spec, gh#60.
+ ** fido2-cred: fix creation of resident keys.
+ ** fido2-{assert,cred}: support for hmac-secret extension.
+ ** hid_osx: detect device removal, gh#56.
+ ** hid_osx: fix device detection in MacOS Catalina.
+ ** New API calls:
+ - fido_assert_set_authdata_raw;
+ - fido_assert_sigcount;
+ - fido_cred_set_authdata_raw;
+ - fido_dev_cancel.
+ ** Middleware library for use by OpenSSH.
+ ** Support for biometric enrollment.
+ ** Support for OpenBSD.
+ ** Support for self-attestation.
+
+* Version 1.2.0 (released 2019-07-26)
+ ** Credential management support.
+ ** New API reflecting FIDO's 3-state booleans (true, false, absent):
+ - fido_assert_set_up;
+ - fido_assert_set_uv;
+ - fido_cred_set_rk;
+ - fido_cred_set_uv.
+ ** Command-line tools for Windows.
+ ** Documentation and reliability fixes.
+ ** fido_{assert,cred}_set_options() are now marked as deprecated.
+
+* Version 1.1.0 (released 2019-05-08)
+ ** MacOS: fix IOKit crash on HID read.
+ ** Windows: fix contents of release file.
+ ** EdDSA (Ed25519) support.
+ ** fido_dev_make_cred: fix order of CBOR map keys.
+ ** fido_dev_get_assert: plug memory leak when operating on U2F devices.
+
+* Version 1.0.0 (released 2019-03-21)
+ ** Native HID support on Linux, MacOS, and Windows.
+ ** fido2-{assert,cred}: new -u option to force U2F on dual authenticators.
+ ** fido2-assert: support for multiple resident keys with the same RP.
+ ** Strict checks for CTAP2 compliance on received CBOR payloads.
+ ** Better fuzzing harnesses.
+ ** Documentation and reliability fixes.
+
+* Version 0.4.0 (released 2019-01-07)
+ ** fido2-assert: print the user id for resident credentials.
+ ** Fix encoding of COSE algorithms when making a credential.
+ ** Rework purpose of fido_cred_set_type; no ABI change.
+ ** Minor documentation and code fixes.
+
+* Version 0.3.0 (released 2018-09-11)
+ ** Various reliability fixes.
+ ** Merged fuzzing instrumentation.
+ ** Added regress tests.
+ ** Added support for FIDO 2's hmac-secret extension.
+ ** New API calls:
+ - fido_assert_hmac_secret_len;
+ - fido_assert_hmac_secret_ptr;
+ - fido_assert_set_extensions;
+ - fido_assert_set_hmac_salt;
+ - fido_cred_set_extensions;
+ - fido_dev_force_fido2.
+ ** Support for native builds with Microsoft Visual Studio 17.
+
+* Version 0.2.0 (released 2018-06-20)
+ ** Added command-line tools.
+ ** Added a couple of missing get functions.
+
+* Version 0.1.1 (released 2018-06-05)
+ ** Added documentation.
+ ** Added OpenSSL 1.0 support.
+ ** Minor fixes.
+
+* Version 0.1.0 (released 2018-05-18)
+ ** First beta release.
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..fb6f3d3
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,144 @@
+== libfido2
+
+image:https://github.com/yubico/libfido2/workflows/linux/badge.svg["Linux Build Status (github actions)", link="https://github.com/Yubico/libfido2/actions"]
+image:https://github.com/yubico/libfido2/workflows/macos/badge.svg["macOS Build Status (github actions)", link="https://github.com/Yubico/libfido2/actions"]
+image:https://github.com/yubico/libfido2/workflows/windows/badge.svg["Windows Build Status (github actions)", link="https://github.com/Yubico/libfido2/actions"]
+image:https://github.com/yubico/libfido2/workflows/fuzzer/badge.svg["Fuzz Status (github actions)", link="https://github.com/Yubico/libfido2/actions"]
+image:https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfido2.svg["Fuzz Status (oss-fuzz)", link="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:libfido2"]
+
+*libfido2* provides library functionality and command-line tools to
+communicate with a FIDO device over USB or NFC, and to verify attestation and
+assertion signatures.
+
+*libfido2* supports the FIDO U2F (CTAP 1) and FIDO2 (CTAP 2) protocols.
+
+For usage, see the `examples/` directory.
+
+=== License
+
+*libfido2* is licensed under the BSD 2-clause license. See the LICENSE
+file for the full license text.
+
+=== Supported Platforms
+
+*libfido2* is known to work on Linux, macOS, Windows, OpenBSD, and FreeBSD.
+
+=== Documentation
+
+Documentation is available in troff and HTML formats. An
+https://developers.yubico.com/libfido2/Manuals/[online mirror of *libfido2*'s documentation]
+is also available.
+
+=== Bindings
+
+* .NET: https://github.com/borrrden/Fido2Net[Fido2Net]
+* Go: https://github.com/keys-pub/go-libfido2[go-libfido2]
+* Perl: https://github.com/jacquesg/p5-FIDO-Raw[p5-FIDO-Raw]
+* Rust: https://github.com/PvdBerg1998/libfido2[libfido2]
+
+=== Releases
+
+The current release of *libfido2* is 1.14.0. Signed release tarballs are
+available at Yubico's
+https://developers.yubico.com/libfido2/Releases[release page].
+
+=== Dependencies
+
+*libfido2* depends on https://github.com/pjk/libcbor[libcbor],
+https://www.openssl.org[OpenSSL] 1.1 or newer, and https://zlib.net[zlib].
+On Linux, libudev
+(part of https://www.freedesktop.org/wiki/Software/systemd[systemd]) is also
+required.
+
+=== Installation
+
+==== Fedora 35 and 34
+
+ $ sudo dnf install libfido2 libfido2-devel fido2-tools
+
+==== Ubuntu 22.04 (Jammy) and 20.04 (Focal)
+
+ $ sudo apt install libfido2-1 libfido2-dev libfido2-doc fido2-tools
+
+Alternatively, newer versions of *libfido2* are available in Yubico's PPA.
+Follow the instructions for Ubuntu 18.04 (Bionic) below.
+
+==== Ubuntu 18.04 (Bionic)
+
+ $ sudo apt install software-properties-common
+ $ sudo apt-add-repository ppa:yubico/stable
+ $ sudo apt update
+ $ sudo apt install libfido2-1 libfido2-dev libfido2-doc fido2-tools
+
+On Linux, you may need to add a udev rule to be able to access the FIDO
+device. For example, the udev rule may contain the following:
+
+----
+#udev rule for allowing HID access to Yubico devices for FIDO support.
+
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", \
+ MODE="0664", GROUP="plugdev", ATTRS{idVendor}=="1050"
+----
+
+==== macOS
+
+ $ brew install libfido2
+
+==== Windows
+
+Please consult Yubico's
+https://developers.yubico.com/libfido2/Releases[release page] for ARM, ARM64,
+Win32, and Win64 artefacts.
+
+=== Building from source
+
+On UNIX-like systems:
+
+ $ cmake -B build
+ $ make -C build
+ $ sudo make -C build install
+
+Depending on the platform,
+https://www.freedesktop.org/wiki/Software/pkg-config/[pkg-config] may need to
+be installed, or the PKG_CONFIG_PATH environment variable set. For complete,
+OS-specific build instructions, please refer to the `.actions/`
+(Linux, macOS, BSD) and `windows/` directories.
+
+=== Build-time Customisation
+
+*libfido2* supports a number of CMake options. Some of the options require
+additional dependencies. Options that are disabled by default are not
+officially supported.
+
+[%autowidth.stretch]
+|===
+|*Option* |*Description* |*Default*
+| BUILD_EXAMPLES | Build example programs | ON
+| BUILD_MANPAGES | Build man pages | ON
+| BUILD_SHARED_LIBS | Build a shared library | ON
+| BUILD_STATIC_LIBS | Build a static library | ON
+| BUILD_TOOLS | Build auxiliary tools | ON
+| FUZZ | Enable fuzzing instrumentation | OFF
+| NFC_LINUX | Enable netlink NFC support on Linux | ON
+| USE_HIDAPI | Use hidapi as the HID backend | OFF
+| USE_PCSC | Enable experimental PCSC support | OFF
+| USE_WINHELLO | Abstract Windows Hello as a FIDO device | ON
+|===
+
+The USE_HIDAPI option requires https://github.com/libusb/hidapi[hidapi]. The
+USE_PCSC option requires https://github.com/LudovicRousseau/PCSC[pcsc-lite] on
+Linux.
+
+=== Development
+
+Please use https://github.com/Yubico/libfido2/discussions[GitHub Discussions]
+to ask questions and suggest features, and
+https://github.com/Yubico/libfido2/pulls[GitHub pull-requests] for code
+contributions.
+
+=== Reporting bugs
+
+Please use https://github.com/Yubico/libfido2/issues[GitHub Issues] to report
+bugs. To report security issues, please contact security@yubico.com. A PGP
+public key can be found at
+https://www.yubico.com/support/security-advisories/issue-rating-system/.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..e12a48a
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,5 @@
+# Reporting libfido2 Security Issues
+
+To report security issues in libfido2, please contact security@yubico.com.
+A PGP public key can be found at
+https://www.yubico.com/support/security-advisories/issue-rating-system/.
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..f013df4
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,59 @@
+# 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
+
+list(APPEND COMPAT_SOURCES
+ ../openbsd-compat/clock_gettime.c
+ ../openbsd-compat/getopt_long.c
+ ../openbsd-compat/strlcat.c
+ ../openbsd-compat/strlcpy.c
+)
+
+if(WIN32 AND BUILD_SHARED_LIBS AND NOT CYGWIN AND NOT MSYS)
+ list(APPEND COMPAT_SOURCES ../openbsd-compat/posix_win.c)
+endif()
+
+# enable -Wconversion -Wsign-conversion
+if(NOT MSVC)
+ set_source_files_properties(assert.c cred.c info.c manifest.c reset.c
+ retries.c setpin.c util.c PROPERTIES COMPILE_FLAGS
+ "-Wconversion -Wsign-conversion")
+endif()
+
+# manifest
+add_executable(manifest manifest.c ${COMPAT_SOURCES})
+target_link_libraries(manifest ${_FIDO2_LIBRARY})
+
+# info
+add_executable(info info.c ${COMPAT_SOURCES})
+target_link_libraries(info ${_FIDO2_LIBRARY})
+
+# reset
+add_executable(reset reset.c util.c ${COMPAT_SOURCES})
+target_link_libraries(reset ${_FIDO2_LIBRARY})
+
+# cred
+add_executable(cred cred.c util.c ${COMPAT_SOURCES})
+target_link_libraries(cred ${_FIDO2_LIBRARY})
+
+# assert
+add_executable(assert assert.c util.c ${COMPAT_SOURCES})
+target_link_libraries(assert ${_FIDO2_LIBRARY})
+
+# setpin
+add_executable(setpin setpin.c ${COMPAT_SOURCES})
+target_link_libraries(setpin ${_FIDO2_LIBRARY})
+
+# retries
+add_executable(retries retries.c ${COMPAT_SOURCES})
+target_link_libraries(retries ${_FIDO2_LIBRARY})
+
+# select
+add_executable(select select.c ${COMPAT_SOURCES})
+target_link_libraries(select ${_FIDO2_LIBRARY})
+
+if(MINGW)
+ # needed for nanosleep() in mingw
+ target_link_libraries(select winpthread)
+endif()
diff --git a/examples/README.adoc b/examples/README.adoc
new file mode 100644
index 0000000..6151b70
--- /dev/null
+++ b/examples/README.adoc
@@ -0,0 +1,100 @@
+= Examples
+
+=== Definitions
+
+The following definitions are used in the description below:
+
+- <device>
+
+ The file system path or subsystem-specific identification string of a
+ FIDO device.
+
+- <pin>, [oldpin]
+
+ Strings passed directly in the executed command's argument vector.
+
+- <cred_id>
+
+ The file system path of a file containing a FIDO credential ID in
+ binary representation.
+
+- <pubkey>
+
+ The file system path of a file containing a public key in PEM format.
+
+- <blobkey>
+
+ A credential's associated CTAP 2.1 "largeBlob" symmetric key.
+
+=== Description
+
+The following examples are provided:
+
+- manifest
+
+ Prints a list of configured FIDO devices.
+
+- info <device>
+
+ Prints information about <device>.
+
+- reset <device>
+
+ Performs a factory reset on <device>.
+
+- setpin <pin> [oldpin] <device>
+
+ Configures <pin> as the new PIN of <device>. If [oldpin] is provided,
+ the device's PIN is changed from [oldpin] to <pin>.
+
+- cred [-t es256|es384|rs256|eddsa] [-k pubkey] [-ei cred_id] [-P pin]
+ [-T seconds] [-b blobkey] [-hruv] [-c cred_protect] <device>
+
+ Creates a new credential on <device> and verify that the credential
+ was signed by the authenticator. The device's attestation certificate
+ is not verified. If option -k is specified, the credential's public
+ key is stored in <pubkey>. If option -i is specified, the credential
+ ID is stored in <cred_id>. The -e option may be used to add <cred_id>
+ to the list of excluded credentials. If option -h is specified,
+ the hmac-secret FIDO2 extension is enabled on the generated
+ credential. If option -r is specified, the generated credential
+ will involve a resident key. User verification may be requested
+ through the -v option. If option -u is specified, the credential
+ is generated using U2F (CTAP1) instead of FIDO2 (CTAP2) commands.
+ The -T option may be used to enforce a timeout of <seconds>. If the
+ option -b is specified, the credential's "largeBlob" key is stored in
+ <blobkey>. If the option -c is specified the the generated credential
+ will be bound by the specified protection policy.
+
+- assert [-t es256|es384|rs256|eddsa] [-a cred_id] [-h hmac_secret] [-P pin]
+ [-s hmac_salt] [-T seconds] [-b blobkey] [-puv] <pubkey> <device>
+
+ Asks <device> for a FIDO2 assertion corresponding to [cred_id],
+ which may be omitted for resident keys. The obtained assertion
+ is verified using <pubkey>. The -p option requests that the user
+ be present and checks whether the user presence bit was signed by the
+ authenticator. The -v option requests user verification and checks
+ whether the user verification bit was signed by the authenticator.
+ If option -u is specified, the assertion is generated using
+ U2F (CTAP1) instead of FIDO2 (CTAP2) commands. If option -s is
+ specified, a FIDO2 hmac-secret is requested from the authenticator,
+ and the contents of <hmac_salt> are used as the salt. If option -h
+ is specified, the resulting hmac-secret is stored in <hmac_secret>.
+ The -T option may be used to enforce a timeout of <seconds>. If the
+ option -b specified, the credential's "largeBlob" key is stored in
+ <blobkey>.
+
+- retries <device>
+ Get the number of PIN attempts left on <device> before lockout.
+
+- select
+
+ Enumerates available FIDO devices and, if more than one is present,
+ simultaneously requests touch on all of them, printing information
+ about the device touched.
+
+Debugging is possible through the use of the FIDO_DEBUG environment variable.
+If set, libfido2 will produce a log of its transactions with the authenticator.
+
+Additionally, an example of a WebAuthn client using libfido2 is available at
+https://github.com/martelletto/fido2-webauthn-client.
diff --git a/examples/assert.c b/examples/assert.c
new file mode 100644
index 0000000..32ba97b
--- /dev/null
+++ b/examples/assert.c
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <fido/es256.h>
+#include <fido/es384.h>
+#include <fido/rs256.h>
+#include <fido/eddsa.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static const unsigned char cd[32] = {
+ 0xec, 0x8d, 0x8f, 0x78, 0x42, 0x4a, 0x2b, 0xb7,
+ 0x82, 0x34, 0xaa, 0xca, 0x07, 0xa1, 0xf6, 0x56,
+ 0x42, 0x1c, 0xb6, 0xf6, 0xb3, 0x00, 0x86, 0x52,
+ 0x35, 0x2d, 0xa2, 0x62, 0x4a, 0xbe, 0x89, 0x76,
+};
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: assert [-t es256|es384|rs256|eddsa] "
+ "[-a cred_id] [-h hmac_secret] [-s hmac_salt] [-P pin] "
+ "[-T seconds] [-b blobkey] [-puv] <pubkey> <device>\n");
+ exit(EXIT_FAILURE);
+}
+
+static void
+verify_assert(int type, const unsigned char *authdata_ptr, size_t authdata_len,
+ const unsigned char *sig_ptr, size_t sig_len, bool up, bool uv, int ext,
+ const char *key)
+{
+ fido_assert_t *assert = NULL;
+ EC_KEY *ec = NULL;
+ RSA *rsa = NULL;
+ EVP_PKEY *eddsa = NULL;
+ es256_pk_t *es256_pk = NULL;
+ es384_pk_t *es384_pk = NULL;
+ rs256_pk_t *rs256_pk = NULL;
+ eddsa_pk_t *eddsa_pk = NULL;
+ void *pk;
+ int r;
+
+ /* credential pubkey */
+ switch (type) {
+ case COSE_ES256:
+ if ((ec = read_ec_pubkey(key)) == NULL)
+ errx(1, "read_ec_pubkey");
+
+ if ((es256_pk = es256_pk_new()) == NULL)
+ errx(1, "es256_pk_new");
+
+ if (es256_pk_from_EC_KEY(es256_pk, ec) != FIDO_OK)
+ errx(1, "es256_pk_from_EC_KEY");
+
+ pk = es256_pk;
+ EC_KEY_free(ec);
+ ec = NULL;
+
+ break;
+ case COSE_ES384:
+ if ((ec = read_ec_pubkey(key)) == NULL)
+ errx(1, "read_ec_pubkey");
+
+ if ((es384_pk = es384_pk_new()) == NULL)
+ errx(1, "es384_pk_new");
+
+ if (es384_pk_from_EC_KEY(es384_pk, ec) != FIDO_OK)
+ errx(1, "es384_pk_from_EC_KEY");
+
+ pk = es384_pk;
+ EC_KEY_free(ec);
+ ec = NULL;
+
+ break;
+ case COSE_RS256:
+ if ((rsa = read_rsa_pubkey(key)) == NULL)
+ errx(1, "read_rsa_pubkey");
+
+ if ((rs256_pk = rs256_pk_new()) == NULL)
+ errx(1, "rs256_pk_new");
+
+ if (rs256_pk_from_RSA(rs256_pk, rsa) != FIDO_OK)
+ errx(1, "rs256_pk_from_RSA");
+
+ pk = rs256_pk;
+ RSA_free(rsa);
+ rsa = NULL;
+
+ break;
+ case COSE_EDDSA:
+ if ((eddsa = read_eddsa_pubkey(key)) == NULL)
+ errx(1, "read_eddsa_pubkey");
+
+ if ((eddsa_pk = eddsa_pk_new()) == NULL)
+ errx(1, "eddsa_pk_new");
+
+ if (eddsa_pk_from_EVP_PKEY(eddsa_pk, eddsa) != FIDO_OK)
+ errx(1, "eddsa_pk_from_EVP_PKEY");
+
+ pk = eddsa_pk;
+ EVP_PKEY_free(eddsa);
+ eddsa = NULL;
+
+ break;
+ default:
+ errx(1, "unknown credential type %d", type);
+ }
+
+ if ((assert = fido_assert_new()) == NULL)
+ errx(1, "fido_assert_new");
+
+ /* client data hash */
+ r = fido_assert_set_clientdata(assert, cd, sizeof(cd));
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_clientdata: %s (0x%x)", fido_strerr(r), r);
+
+ /* relying party */
+ r = fido_assert_set_rp(assert, "localhost");
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_rp: %s (0x%x)", fido_strerr(r), r);
+
+ /* authdata */
+ r = fido_assert_set_count(assert, 1);
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_count: %s (0x%x)", fido_strerr(r), r);
+ r = fido_assert_set_authdata(assert, 0, authdata_ptr, authdata_len);
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_authdata: %s (0x%x)", fido_strerr(r), r);
+
+ /* extension */
+ r = fido_assert_set_extensions(assert, ext);
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s (0x%x)", fido_strerr(r),
+ r);
+
+ /* user presence */
+ if (up && (r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_up: %s (0x%x)", fido_strerr(r), r);
+
+ /* user verification */
+ if (uv && (r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_uv: %s (0x%x)", fido_strerr(r), r);
+
+ /* sig */
+ r = fido_assert_set_sig(assert, 0, sig_ptr, sig_len);
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_sig: %s (0x%x)", fido_strerr(r), r);
+
+ r = fido_assert_verify(assert, 0, type, pk);
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_verify: %s (0x%x)", fido_strerr(r), r);
+
+ es256_pk_free(&es256_pk);
+ es384_pk_free(&es384_pk);
+ rs256_pk_free(&rs256_pk);
+ eddsa_pk_free(&eddsa_pk);
+
+ fido_assert_free(&assert);
+}
+
+int
+main(int argc, char **argv)
+{
+ bool up = false;
+ bool uv = false;
+ bool u2f = false;
+ fido_dev_t *dev = NULL;
+ fido_assert_t *assert = NULL;
+ const char *pin = NULL;
+ const char *blobkey_out = NULL;
+ const char *hmac_out = NULL;
+ unsigned char *body = NULL;
+ long long ms = 0;
+ size_t len;
+ int type = COSE_ES256;
+ int ext = 0;
+ int ch;
+ int r;
+
+ if ((assert = fido_assert_new()) == NULL)
+ errx(1, "fido_assert_new");
+
+ while ((ch = getopt(argc, argv, "P:T:a:b:h:ps:t:uv")) != -1) {
+ switch (ch) {
+ case 'P':
+ pin = optarg;
+ break;
+ case 'T':
+ if (base10(optarg, &ms) < 0)
+ errx(1, "base10: %s", optarg);
+ if (ms <= 0 || ms > 30)
+ errx(1, "-T: %s must be in (0,30]", optarg);
+ ms *= 1000; /* seconds to milliseconds */
+ break;
+ case 'a':
+ if (read_blob(optarg, &body, &len) < 0)
+ errx(1, "read_blob: %s", optarg);
+ if ((r = fido_assert_allow_cred(assert, body,
+ len)) != FIDO_OK)
+ errx(1, "fido_assert_allow_cred: %s (0x%x)",
+ fido_strerr(r), r);
+ free(body);
+ body = NULL;
+ break;
+ case 'b':
+ ext |= FIDO_EXT_LARGEBLOB_KEY;
+ blobkey_out = optarg;
+ break;
+ case 'h':
+ hmac_out = optarg;
+ break;
+ case 'p':
+ up = true;
+ break;
+ case 's':
+ ext |= FIDO_EXT_HMAC_SECRET;
+ if (read_blob(optarg, &body, &len) < 0)
+ errx(1, "read_blob: %s", optarg);
+ if ((r = fido_assert_set_hmac_salt(assert, body,
+ len)) != FIDO_OK)
+ errx(1, "fido_assert_set_hmac_salt: %s (0x%x)",
+ fido_strerr(r), r);
+ free(body);
+ body = NULL;
+ break;
+ case 't':
+ if (strcmp(optarg, "es256") == 0)
+ type = COSE_ES256;
+ else if (strcmp(optarg, "es384") == 0)
+ type = COSE_ES384;
+ else if (strcmp(optarg, "rs256") == 0)
+ type = COSE_RS256;
+ else if (strcmp(optarg, "eddsa") == 0)
+ type = COSE_EDDSA;
+ else
+ errx(1, "unknown type %s", optarg);
+ break;
+ case 'u':
+ u2f = true;
+ break;
+ case 'v':
+ uv = true;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 2)
+ usage();
+
+ fido_init(0);
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+
+ r = fido_dev_open(dev, argv[1]);
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r);
+ if (u2f)
+ fido_dev_force_u2f(dev);
+
+ /* client data hash */
+ r = fido_assert_set_clientdata(assert, cd, sizeof(cd));
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_clientdata: %s (0x%x)", fido_strerr(r), r);
+
+ /* relying party */
+ r = fido_assert_set_rp(assert, "localhost");
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_rp: %s (0x%x)", fido_strerr(r), r);
+
+ /* extensions */
+ r = fido_assert_set_extensions(assert, ext);
+ if (r != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s (0x%x)", fido_strerr(r),
+ r);
+
+ /* user presence */
+ if (up && (r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_up: %s (0x%x)", fido_strerr(r), r);
+
+ /* user verification */
+ if (uv && (r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_uv: %s (0x%x)", fido_strerr(r), r);
+
+ /* timeout */
+ if (ms != 0 && (r = fido_dev_set_timeout(dev, (int)ms)) != FIDO_OK)
+ errx(1, "fido_dev_set_timeout: %s (0x%x)", fido_strerr(r), r);
+
+ if ((r = fido_dev_get_assert(dev, assert, pin)) != FIDO_OK) {
+ fido_dev_cancel(dev);
+ errx(1, "fido_dev_get_assert: %s (0x%x)", fido_strerr(r), r);
+ }
+
+ r = fido_dev_close(dev);
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r);
+
+ fido_dev_free(&dev);
+
+ if (fido_assert_count(assert) != 1)
+ errx(1, "fido_assert_count: %d signatures returned",
+ (int)fido_assert_count(assert));
+
+ /* when verifying, pin implies uv */
+ if (pin)
+ uv = true;
+
+ verify_assert(type, fido_assert_authdata_ptr(assert, 0),
+ fido_assert_authdata_len(assert, 0), fido_assert_sig_ptr(assert, 0),
+ fido_assert_sig_len(assert, 0), up, uv, ext, argv[0]);
+
+ if (hmac_out != NULL) {
+ /* extract the hmac secret */
+ if (write_blob(hmac_out, fido_assert_hmac_secret_ptr(assert, 0),
+ fido_assert_hmac_secret_len(assert, 0)) < 0)
+ errx(1, "write_blob");
+ }
+
+ if (blobkey_out != NULL) {
+ /* extract the hmac secret */
+ if (write_blob(blobkey_out,
+ fido_assert_largeblob_key_ptr(assert, 0),
+ fido_assert_largeblob_key_len(assert, 0)) < 0)
+ errx(1, "write_blob");
+ }
+
+ fido_assert_free(&assert);
+
+ exit(0);
+}
diff --git a/examples/cred.c b/examples/cred.c
new file mode 100644
index 0000000..5a2a27f
--- /dev/null
+++ b/examples/cred.c
@@ -0,0 +1,331 @@
+/*
+ * 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 <errno.h>
+#include <fido.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static const unsigned char cd[32] = {
+ 0xf9, 0x64, 0x57, 0xe7, 0x2d, 0x97, 0xf6, 0xbb,
+ 0xdd, 0xd7, 0xfb, 0x06, 0x37, 0x62, 0xea, 0x26,
+ 0x20, 0x44, 0x8e, 0x69, 0x7c, 0x03, 0xf2, 0x31,
+ 0x2f, 0x99, 0xdc, 0xaf, 0x3e, 0x8a, 0x91, 0x6b,
+};
+
+static const unsigned char user_id[32] = {
+ 0x78, 0x1c, 0x78, 0x60, 0xad, 0x88, 0xd2, 0x63,
+ 0x32, 0x62, 0x2a, 0xf1, 0x74, 0x5d, 0xed, 0xb2,
+ 0xe7, 0xa4, 0x2b, 0x44, 0x89, 0x29, 0x39, 0xc5,
+ 0x56, 0x64, 0x01, 0x27, 0x0d, 0xbb, 0xc4, 0x49,
+};
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: cred [-t es256|es384|rs256|eddsa] [-k pubkey] "
+ "[-ei cred_id] [-P pin] [-T seconds] [-b blobkey] [-c cred_protect] [-hruv] "
+ "<device>\n");
+ exit(EXIT_FAILURE);
+}
+
+static void
+verify_cred(int type, const char *fmt, const unsigned char *authdata_ptr,
+ size_t authdata_len, const unsigned char *attstmt_ptr, size_t attstmt_len,
+ bool rk, bool uv, int ext, int cred_protect, const char *key_out,
+ const char *id_out)
+{
+ fido_cred_t *cred;
+ int r;
+
+ if ((cred = fido_cred_new()) == NULL)
+ errx(1, "fido_cred_new");
+
+ /* type */
+ r = fido_cred_set_type(cred, type);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r);
+
+ /* client data */
+ r = fido_cred_set_clientdata(cred, cd, sizeof(cd));
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r);
+
+ /* relying party */
+ r = fido_cred_set_rp(cred, "localhost", "sweet home localhost");
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r);
+
+ /* authdata */
+ r = fido_cred_set_authdata(cred, authdata_ptr, authdata_len);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_authdata: %s (0x%x)", fido_strerr(r), r);
+
+ /* extensions */
+ r = fido_cred_set_extensions(cred, ext);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r);
+
+ /* resident key */
+ if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r);
+
+ /* user verification */
+ if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r);
+
+ /* credProt */
+ if (cred_protect != 0 && (r = fido_cred_set_prot(cred,
+ cred_protect)) != FIDO_OK)
+ errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r);
+
+ /* fmt */
+ r = fido_cred_set_fmt(cred, fmt);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_fmt: %s (0x%x)", fido_strerr(r), r);
+
+ if (!strcmp(fido_cred_fmt(cred), "none")) {
+ warnx("no attestation data, skipping credential verification");
+ goto out;
+ }
+
+ /* attestation statement */
+ r = fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_attstmt: %s (0x%x)", fido_strerr(r), r);
+
+ r = fido_cred_verify(cred);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_verify: %s (0x%x)", fido_strerr(r), r);
+
+out:
+ if (key_out != NULL) {
+ /* extract the credential pubkey */
+ if (type == COSE_ES256) {
+ if (write_es256_pubkey(key_out,
+ fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred)) < 0)
+ errx(1, "write_es256_pubkey");
+ } else if (type == COSE_ES384) {
+ if (write_es384_pubkey(key_out,
+ fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred)) < 0)
+ errx(1, "write_es384_pubkey");
+ } else if (type == COSE_RS256) {
+ if (write_rs256_pubkey(key_out,
+ fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred)) < 0)
+ errx(1, "write_rs256_pubkey");
+ } else if (type == COSE_EDDSA) {
+ if (write_eddsa_pubkey(key_out,
+ fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred)) < 0)
+ errx(1, "write_eddsa_pubkey");
+ }
+ }
+
+ if (id_out != NULL) {
+ /* extract the credential id */
+ if (write_blob(id_out, fido_cred_id_ptr(cred),
+ fido_cred_id_len(cred)) < 0)
+ errx(1, "write_blob");
+ }
+
+ fido_cred_free(&cred);
+}
+
+int
+main(int argc, char **argv)
+{
+ bool rk = false;
+ bool uv = false;
+ bool u2f = false;
+ fido_dev_t *dev;
+ fido_cred_t *cred = NULL;
+ const char *pin = NULL;
+ const char *blobkey_out = NULL;
+ const char *key_out = NULL;
+ const char *id_out = NULL;
+ unsigned char *body = NULL;
+ long long ms = 0;
+ size_t len;
+ int type = COSE_ES256;
+ int ext = 0;
+ int ch;
+ int r;
+ long long cred_protect = 0;
+
+ if ((cred = fido_cred_new()) == NULL)
+ errx(1, "fido_cred_new");
+
+ while ((ch = getopt(argc, argv, "P:T:b:e:hi:k:rt:uvc:")) != -1) {
+ switch (ch) {
+ case 'P':
+ pin = optarg;
+ break;
+ case 'T':
+ if (base10(optarg, &ms) < 0)
+ errx(1, "base10: %s", optarg);
+ if (ms <= 0 || ms > 30)
+ errx(1, "-T: %s must be in (0,30]", optarg);
+ ms *= 1000; /* seconds to milliseconds */
+ break;
+ case 'b':
+ ext |= FIDO_EXT_LARGEBLOB_KEY;
+ blobkey_out = optarg;
+ break;
+ case 'e':
+ if (read_blob(optarg, &body, &len) < 0)
+ errx(1, "read_blob: %s", optarg);
+ r = fido_cred_exclude(cred, body, len);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_exclude: %s (0x%x)",
+ fido_strerr(r), r);
+ free(body);
+ body = NULL;
+ break;
+ case 'h':
+ ext |= FIDO_EXT_HMAC_SECRET;
+ break;
+ case 'c':
+ if (base10(optarg, &cred_protect) < 0)
+ errx(1, "base10: %s", optarg);
+ if (cred_protect <= 0 || cred_protect > 3)
+ errx(1, "-c: %s must be in (1,3)", optarg);
+ ext |= FIDO_EXT_CRED_PROTECT;
+ break;
+ case 'i':
+ id_out = optarg;
+ break;
+ case 'k':
+ key_out = optarg;
+ break;
+ case 'r':
+ rk = true;
+ break;
+ case 't':
+ if (strcmp(optarg, "es256") == 0)
+ type = COSE_ES256;
+ else if (strcmp(optarg, "es384") == 0)
+ type = COSE_ES384;
+ else if (strcmp(optarg, "rs256") == 0)
+ type = COSE_RS256;
+ else if (strcmp(optarg, "eddsa") == 0)
+ type = COSE_EDDSA;
+ else
+ errx(1, "unknown type %s", optarg);
+ break;
+ case 'u':
+ u2f = true;
+ break;
+ case 'v':
+ uv = true;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage();
+
+ fido_init(0);
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+
+ r = fido_dev_open(dev, argv[0]);
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r);
+ if (u2f)
+ fido_dev_force_u2f(dev);
+
+ /* type */
+ r = fido_cred_set_type(cred, type);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r);
+
+ /* client data */
+ r = fido_cred_set_clientdata(cred, cd, sizeof(cd));
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r);
+
+ /* relying party */
+ r = fido_cred_set_rp(cred, "localhost", "sweet home localhost");
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r);
+
+ /* user */
+ r = fido_cred_set_user(cred, user_id, sizeof(user_id), "john smith",
+ "jsmith", NULL);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_user: %s (0x%x)", fido_strerr(r), r);
+
+ /* extensions */
+ r = fido_cred_set_extensions(cred, ext);
+ if (r != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r);
+
+ /* resident key */
+ if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r);
+
+ /* user verification */
+ if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r);
+
+ /* credProt */
+ if (cred_protect != 0 && (r = fido_cred_set_prot(cred,
+ (int)cred_protect)) != FIDO_OK)
+ errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r);
+
+ /* timeout */
+ if (ms != 0 && (r = fido_dev_set_timeout(dev, (int)ms)) != FIDO_OK)
+ errx(1, "fido_dev_set_timeout: %s (0x%x)", fido_strerr(r), r);
+
+ if ((r = fido_dev_make_cred(dev, cred, pin)) != FIDO_OK) {
+ fido_dev_cancel(dev);
+ errx(1, "fido_makecred: %s (0x%x)", fido_strerr(r), r);
+ }
+
+ r = fido_dev_close(dev);
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r);
+
+ fido_dev_free(&dev);
+
+ /* when verifying, pin implies uv */
+ if (pin)
+ uv = true;
+
+ verify_cred(type, fido_cred_fmt(cred), fido_cred_authdata_ptr(cred),
+ fido_cred_authdata_len(cred), fido_cred_attstmt_ptr(cred),
+ fido_cred_attstmt_len(cred), rk, uv, ext, fido_cred_prot(cred),
+ key_out, id_out);
+
+ if (blobkey_out != NULL) {
+ /* extract the "largeBlob" key */
+ if (write_blob(blobkey_out, fido_cred_largeblob_key_ptr(cred),
+ fido_cred_largeblob_key_len(cred)) < 0)
+ errx(1, "write_blob");
+ }
+
+ fido_cred_free(&cred);
+
+ exit(0);
+}
diff --git a/examples/extern.h b/examples/extern.h
new file mode 100644
index 0000000..5cffd7f
--- /dev/null
+++ b/examples/extern.h
@@ -0,0 +1,27 @@
+/*
+ * 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_
+
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+
+/* util.c */
+EC_KEY *read_ec_pubkey(const char *);
+RSA *read_rsa_pubkey(const char *);
+EVP_PKEY *read_eddsa_pubkey(const char *);
+int base10(const char *, long long *);
+int read_blob(const char *, unsigned char **, size_t *);
+int write_blob(const char *, const unsigned char *, size_t);
+int write_es256_pubkey(const char *, const void *, size_t);
+int write_es384_pubkey(const char *, const void *, size_t);
+int write_rs256_pubkey(const char *, const void *, size_t);
+int write_eddsa_pubkey(const char *, const void *, size_t);
+
+#endif /* _EXTERN_H_ */
diff --git a/examples/info.c b/examples/info.c
new file mode 100644
index 0000000..a10a50c
--- /dev/null
+++ b/examples/info.c
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+/*
+ * Pretty-print a device's capabilities flags and return the result.
+ */
+static void
+format_flags(char *ret, size_t retlen, uint8_t flags)
+{
+ memset(ret, 0, retlen);
+
+ if (flags & FIDO_CAP_WINK) {
+ if (strlcat(ret, "wink,", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, "nowink,", retlen) >= retlen)
+ goto toolong;
+ }
+
+ if (flags & FIDO_CAP_CBOR) {
+ if (strlcat(ret, " cbor,", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, " nocbor,", retlen) >= retlen)
+ goto toolong;
+ }
+
+ if (flags & FIDO_CAP_NMSG) {
+ if (strlcat(ret, " nomsg", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, " msg", retlen) >= retlen)
+ goto toolong;
+ }
+
+ return;
+toolong:
+ strlcpy(ret, "toolong", retlen);
+}
+
+/*
+ * Print a FIDO device's attributes on stdout.
+ */
+static void
+print_attr(const fido_dev_t *dev)
+{
+ char flags_txt[128];
+
+ printf("proto: 0x%02x\n", fido_dev_protocol(dev));
+ printf("major: 0x%02x\n", fido_dev_major(dev));
+ printf("minor: 0x%02x\n", fido_dev_minor(dev));
+ printf("build: 0x%02x\n", fido_dev_build(dev));
+
+ format_flags(flags_txt, sizeof(flags_txt), fido_dev_flags(dev));
+ printf("caps: 0x%02x (%s)\n", fido_dev_flags(dev), flags_txt);
+}
+
+/*
+ * Auxiliary function to print an array of strings on stdout.
+ */
+static void
+print_str_array(const char *label, char * const *sa, size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s strings: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s", i > 0 ? ", " : "", sa[i]);
+
+ printf("\n");
+}
+
+/*
+ * Auxiliary function to print (char *, bool) pairs on stdout.
+ */
+static void
+print_opt_array(const char *label, char * const *name, const bool *value,
+ size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s%s", i > 0 ? ", " : "",
+ value[i] ? "" : "no", name[i]);
+
+ printf("\n");
+}
+
+/*
+ * Auxiliary function to print (char *, uint64_t) pairs on stdout.
+ */
+static void
+print_cert_array(const char *label, char * const *name, const uint64_t *value,
+ size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s %llu", i > 0 ? ", " : "", name[i],
+ (unsigned long long)value[i]);
+
+ printf("\n");
+}
+
+/*
+ * Auxiliary function to print a list of supported COSE algorithms on stdout.
+ */
+static void
+print_algorithms(const fido_cbor_info_t *ci)
+{
+ const char *cose, *type;
+ size_t len;
+
+ if ((len = fido_cbor_info_algorithm_count(ci)) == 0)
+ return;
+
+ printf("algorithms: ");
+
+ for (size_t i = 0; i < len; i++) {
+ cose = type = "unknown";
+ switch (fido_cbor_info_algorithm_cose(ci, i)) {
+ case COSE_ES256:
+ cose = "es256";
+ break;
+ case COSE_ES384:
+ cose = "es384";
+ break;
+ case COSE_RS256:
+ cose = "rs256";
+ break;
+ case COSE_EDDSA:
+ cose = "eddsa";
+ break;
+ }
+ if (fido_cbor_info_algorithm_type(ci, i) != NULL)
+ type = fido_cbor_info_algorithm_type(ci, i);
+ printf("%s%s (%s)", i > 0 ? ", " : "", cose, type);
+ }
+
+ printf("\n");
+}
+
+/*
+ * Auxiliary function to print an authenticator's AAGUID on stdout.
+ */
+static void
+print_aaguid(const unsigned char *buf, size_t buflen)
+{
+ printf("aaguid: ");
+
+ while (buflen--)
+ printf("%02x", *buf++);
+
+ printf("\n");
+}
+
+/*
+ * Auxiliary function to print an authenticator's maximum message size on
+ * stdout.
+ */
+static void
+print_maxmsgsiz(uint64_t maxmsgsiz)
+{
+ printf("maxmsgsiz: %d\n", (int)maxmsgsiz);
+}
+
+/*
+ * Auxiliary function to print an authenticator's maximum number of credentials
+ * in a credential list on stdout.
+ */
+static void
+print_maxcredcntlst(uint64_t maxcredcntlst)
+{
+ printf("maxcredcntlst: %d\n", (int)maxcredcntlst);
+}
+
+/*
+ * Auxiliary function to print an authenticator's maximum credential ID length
+ * on stdout.
+ */
+static void
+print_maxcredidlen(uint64_t maxcredidlen)
+{
+ printf("maxcredlen: %d\n", (int)maxcredidlen);
+}
+
+/*
+ * Auxiliary function to print the maximum size of an authenticator's
+ * serialized largeBlob array.
+ */
+static void
+print_maxlargeblob(uint64_t maxlargeblob)
+{
+ printf("maxlargeblob: %d\n", (int)maxlargeblob);
+}
+
+/*
+ * Auxiliary function to print the authenticator's estimated number of
+ * remaining resident credentials.
+ */
+static void
+print_rk_remaining(int64_t rk_remaining)
+{
+ printf("remaining rk(s): ");
+
+ if (rk_remaining == -1)
+ printf("undefined\n");
+ else
+ printf("%d\n", (int)rk_remaining);
+}
+
+/*
+ * Auxiliary function to print the minimum pin length observed by the
+ * authenticator.
+ */
+static void
+print_minpinlen(uint64_t minpinlen)
+{
+ printf("minpinlen: %d\n", (int)minpinlen);
+}
+
+/*
+ * Auxiliary function to print the authenticator's preferred (platform)
+ * UV attempts.
+ */
+static void
+print_uv_attempts(uint64_t uv_attempts)
+{
+ printf("platform uv attempt(s): %d\n", (int)uv_attempts);
+}
+
+/*
+ * Auxiliary function to print an authenticator's firmware version on stdout.
+ */
+static void
+print_fwversion(uint64_t fwversion)
+{
+ printf("fwversion: 0x%x\n", (int)fwversion);
+}
+
+/*
+ * Auxiliary function to print an array of bytes on stdout.
+ */
+static void
+print_byte_array(const char *label, const uint8_t *ba, size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%u", i > 0 ? ", " : "", (unsigned)ba[i]);
+
+ printf("\n");
+}
+
+static void
+getinfo(const char *path)
+{
+ fido_dev_t *dev;
+ fido_cbor_info_t *ci;
+ int r;
+
+ fido_init(0);
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+ if ((r = fido_dev_open(dev, path)) != FIDO_OK)
+ errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r);
+
+ print_attr(dev);
+
+ if (fido_dev_is_fido2(dev) == false)
+ goto end;
+ if ((ci = fido_cbor_info_new()) == NULL)
+ errx(1, "fido_cbor_info_new");
+ if ((r = fido_dev_get_cbor_info(dev, ci)) != FIDO_OK)
+ errx(1, "fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r);
+
+ /* print supported protocol versions */
+ print_str_array("version", fido_cbor_info_versions_ptr(ci),
+ fido_cbor_info_versions_len(ci));
+
+ /* print supported extensions */
+ print_str_array("extension", fido_cbor_info_extensions_ptr(ci),
+ fido_cbor_info_extensions_len(ci));
+
+ /* print supported transports */
+ print_str_array("transport", fido_cbor_info_transports_ptr(ci),
+ fido_cbor_info_transports_len(ci));
+
+ /* print supported algorithms */
+ print_algorithms(ci);
+
+ /* print aaguid */
+ print_aaguid(fido_cbor_info_aaguid_ptr(ci),
+ fido_cbor_info_aaguid_len(ci));
+
+ /* print supported options */
+ print_opt_array("options", fido_cbor_info_options_name_ptr(ci),
+ fido_cbor_info_options_value_ptr(ci),
+ fido_cbor_info_options_len(ci));
+
+ /* print certifications */
+ print_cert_array("certifications", fido_cbor_info_certs_name_ptr(ci),
+ fido_cbor_info_certs_value_ptr(ci),
+ fido_cbor_info_certs_len(ci));
+
+ /* print firmware version */
+ print_fwversion(fido_cbor_info_fwversion(ci));
+
+ /* print maximum message size */
+ print_maxmsgsiz(fido_cbor_info_maxmsgsiz(ci));
+
+ /* print maximum number of credentials allowed in credential lists */
+ print_maxcredcntlst(fido_cbor_info_maxcredcntlst(ci));
+
+ /* print maximum length of a credential ID */
+ print_maxcredidlen(fido_cbor_info_maxcredidlen(ci));
+
+ /* print maximum length of largeBlob array */
+ print_maxlargeblob(fido_cbor_info_maxlargeblob(ci));
+
+ /* print number of remaining resident credentials */
+ print_rk_remaining(fido_cbor_info_rk_remaining(ci));
+
+ /* print minimum pin length */
+ print_minpinlen(fido_cbor_info_minpinlen(ci));
+
+ /* print supported pin protocols */
+ print_byte_array("pin protocols", fido_cbor_info_protocols_ptr(ci),
+ fido_cbor_info_protocols_len(ci));
+
+ /* print whether a new pin is required */
+ printf("pin change required: %s\n",
+ fido_cbor_info_new_pin_required(ci) ? "true" : "false");
+
+ /* print platform uv attempts */
+ print_uv_attempts(fido_cbor_info_uv_attempts(ci));
+
+ fido_cbor_info_free(&ci);
+end:
+ if ((r = fido_dev_close(dev)) != FIDO_OK)
+ errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r);
+
+ fido_dev_free(&dev);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "usage: info <device>\n");
+ exit(EXIT_FAILURE);
+ }
+
+ getinfo(argv[1]);
+
+ exit(0);
+}
diff --git a/examples/manifest.c b/examples/manifest.c
new file mode 100644
index 0000000..c2b3bf1
--- /dev/null
+++ b/examples/manifest.c
@@ -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
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+int
+main(void)
+{
+ fido_dev_info_t *devlist;
+ size_t ndevs;
+ int r;
+
+ fido_init(0);
+
+ if ((devlist = fido_dev_info_new(64)) == NULL)
+ errx(1, "fido_dev_info_new");
+
+ if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK)
+ errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r);
+
+ for (size_t i = 0; i < ndevs; i++) {
+ const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i);
+ printf("%s: vendor=0x%04x, product=0x%04x (%s %s)\n",
+ fido_dev_info_path(di),
+ (uint16_t)fido_dev_info_vendor(di),
+ (uint16_t)fido_dev_info_product(di),
+ fido_dev_info_manufacturer_string(di),
+ fido_dev_info_product_string(di));
+ }
+
+ fido_dev_info_free(&devlist, ndevs);
+
+ exit(0);
+}
diff --git a/examples/reset.c b/examples/reset.c
new file mode 100644
index 0000000..767a162
--- /dev/null
+++ b/examples/reset.c
@@ -0,0 +1,49 @@
+/*
+ * 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
+ */
+
+/*
+ * Perform a factory reset on a given authenticator.
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+main(int argc, char **argv)
+{
+ fido_dev_t *dev;
+ int r;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: reset <device>\n");
+ exit(EXIT_FAILURE);
+ }
+
+ fido_init(0);
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+
+ if ((r = fido_dev_open(dev, argv[1])) != FIDO_OK)
+ errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r);
+
+ if ((r = fido_dev_reset(dev)) != FIDO_OK) {
+ fido_dev_cancel(dev);
+ errx(1, "fido_dev_reset: %s (0x%x)", fido_strerr(r), r);
+ }
+
+ if ((r = fido_dev_close(dev)) != FIDO_OK)
+ errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r);
+
+ fido_dev_free(&dev);
+
+ exit(0);
+}
diff --git a/examples/retries.c b/examples/retries.c
new file mode 100644
index 0000000..a0610fe
--- /dev/null
+++ b/examples/retries.c
@@ -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
+ */
+
+/*
+ * Get an authenticator's number of PIN attempts left.
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+int
+main(int argc, char **argv)
+{
+ fido_dev_t *dev;
+ int n;
+ int r;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: retries <device>\n");
+ exit(EXIT_FAILURE);
+ }
+
+ fido_init(0);
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+
+ if ((r = fido_dev_open(dev, argv[1])) != FIDO_OK)
+ errx(1, "fido_open: %s (0x%x)", fido_strerr(r), r);
+
+ if ((r = fido_dev_get_retry_count(dev, &n)) != FIDO_OK)
+ errx(1, "fido_dev_get_retry_count: %s (0x%x)", fido_strerr(r), r);
+
+ if ((r = fido_dev_close(dev)) != FIDO_OK)
+ errx(1, "fido_close: %s (0x%x)", fido_strerr(r), r);
+
+ fido_dev_free(&dev);
+
+ printf("%d\n", n);
+
+ exit(0);
+}
diff --git a/examples/select.c b/examples/select.c
new file mode 100644
index 0000000..008eb2e
--- /dev/null
+++ b/examples/select.c
@@ -0,0 +1,215 @@
+/*
+ * 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 <errno.h>
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+#define FIDO_POLL_MS 50
+
+#if defined(_MSC_VER)
+static int
+nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
+{
+ if (rmtp != NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ Sleep((DWORD)(rqtp->tv_sec * 1000) + (DWORD)(rqtp->tv_nsec / 1000000));
+
+ return (0);
+}
+#endif
+
+static fido_dev_t *
+open_dev(const fido_dev_info_t *di)
+{
+ fido_dev_t *dev;
+ int r;
+
+ if ((dev = fido_dev_new()) == NULL) {
+ warnx("%s: fido_dev_new", __func__);
+ return (NULL);
+ }
+
+ if ((r = fido_dev_open(dev, fido_dev_info_path(di))) != FIDO_OK) {
+ warnx("%s: fido_dev_open %s: %s", __func__,
+ fido_dev_info_path(di), fido_strerr(r));
+ fido_dev_free(&dev);
+ return (NULL);
+ }
+
+ printf("%s (0x%04x:0x%04x) is %s\n", fido_dev_info_path(di),
+ fido_dev_info_vendor(di), fido_dev_info_product(di),
+ fido_dev_is_fido2(dev) ? "fido2" : "u2f");
+
+ return (dev);
+}
+
+static int
+select_dev(const fido_dev_info_t *devlist, size_t ndevs, fido_dev_t **dev,
+ size_t *idx, int secs)
+{
+ const fido_dev_info_t *di;
+ fido_dev_t **devtab;
+ struct timespec ts_start;
+ struct timespec ts_now;
+ struct timespec ts_delta;
+ struct timespec ts_pause;
+ size_t nopen = 0;
+ int touched;
+ int r;
+ long ms_remain;
+
+ *dev = NULL;
+ *idx = 0;
+
+ printf("%u authenticator(s) detected\n", (unsigned)ndevs);
+
+ if (ndevs == 0)
+ return (0); /* nothing to do */
+
+ if ((devtab = calloc(ndevs, sizeof(*devtab))) == NULL) {
+ warn("%s: calloc", __func__);
+ return (-1);
+ }
+
+ for (size_t i = 0; i < ndevs; i++) {
+ di = fido_dev_info_ptr(devlist, i);
+ if ((devtab[i] = open_dev(di)) != NULL) {
+ *idx = i;
+ nopen++;
+ }
+ }
+
+ printf("%u authenticator(s) opened\n", (unsigned)nopen);
+
+ if (nopen < 2) {
+ if (nopen == 1)
+ *dev = devtab[*idx]; /* single candidate */
+ r = 0;
+ goto out;
+ }
+
+ for (size_t i = 0; i < ndevs; i++) {
+ di = fido_dev_info_ptr(devlist, i);
+ if (devtab[i] == NULL)
+ continue; /* failed to open */
+ if ((r = fido_dev_get_touch_begin(devtab[i])) != FIDO_OK) {
+ warnx("%s: fido_dev_get_touch_begin %s: %s", __func__,
+ fido_dev_info_path(di), fido_strerr(r));
+ r = -1;
+ goto out;
+ }
+ }
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts_start) != 0) {
+ warn("%s: clock_gettime", __func__);
+ r = -1;
+ goto out;
+ }
+
+ ts_pause.tv_sec = 0;
+ ts_pause.tv_nsec = 200000000; /* 200ms */
+
+ do {
+ nanosleep(&ts_pause, NULL);
+
+ for (size_t i = 0; i < ndevs; i++) {
+ di = fido_dev_info_ptr(devlist, i);
+ if (devtab[i] == NULL) {
+ /* failed to open or discarded */
+ continue;
+ }
+ if ((r = fido_dev_get_touch_status(devtab[i], &touched,
+ FIDO_POLL_MS)) != FIDO_OK) {
+ warnx("%s: fido_dev_get_touch_status %s: %s",
+ __func__, fido_dev_info_path(di),
+ fido_strerr(r));
+ fido_dev_close(devtab[i]);
+ fido_dev_free(&devtab[i]);
+ continue; /* discard */
+ }
+ if (touched) {
+ *dev = devtab[i];
+ *idx = i;
+ r = 0;
+ goto out;
+ }
+ }
+
+ if (clock_gettime(CLOCK_MONOTONIC, &ts_now) != 0) {
+ warn("%s: clock_gettime", __func__);
+ r = -1;
+ goto out;
+ }
+
+ timespecsub(&ts_now, &ts_start, &ts_delta);
+ ms_remain = (secs * 1000) - ((long)ts_delta.tv_sec * 1000) +
+ ((long)ts_delta.tv_nsec / 1000000);
+ } while (ms_remain > FIDO_POLL_MS);
+
+ printf("timeout after %d seconds\n", secs);
+ r = -1;
+out:
+ if (r != 0) {
+ *dev = NULL;
+ *idx = 0;
+ }
+
+ for (size_t i = 0; i < ndevs; i++) {
+ if (devtab[i] && devtab[i] != *dev) {
+ fido_dev_cancel(devtab[i]);
+ fido_dev_close(devtab[i]);
+ fido_dev_free(&devtab[i]);
+ }
+ }
+
+ free(devtab);
+
+ return (r);
+}
+
+int
+main(void)
+{
+ const fido_dev_info_t *di;
+ fido_dev_info_t *devlist;
+ fido_dev_t *dev;
+ size_t idx;
+ size_t ndevs;
+ int r;
+
+ fido_init(0);
+
+ if ((devlist = fido_dev_info_new(64)) == NULL)
+ errx(1, "fido_dev_info_new");
+
+ if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK)
+ errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r);
+ if (select_dev(devlist, ndevs, &dev, &idx, 15) != 0)
+ errx(1, "select_dev");
+ if (dev == NULL)
+ errx(1, "no authenticator found");
+
+ di = fido_dev_info_ptr(devlist, idx);
+ printf("%s: %s by %s (PIN %sset)\n", fido_dev_info_path(di),
+ fido_dev_info_product_string(di),
+ fido_dev_info_manufacturer_string(di),
+ fido_dev_has_pin(dev) ? "" : "un");
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_dev_info_free(&devlist, ndevs);
+
+ exit(0);
+}
diff --git a/examples/setpin.c b/examples/setpin.c
new file mode 100644
index 0000000..72e08e4
--- /dev/null
+++ b/examples/setpin.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Configure a PIN on a given authenticator.
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+static void
+setpin(const char *path, const char *pin, const char *oldpin)
+{
+ fido_dev_t *dev;
+ int r;
+
+ fido_init(0);
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+
+ if ((r = fido_dev_open(dev, path)) != FIDO_OK)
+ errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r);
+
+ if ((r = fido_dev_set_pin(dev, pin, oldpin)) != FIDO_OK)
+ errx(1, "fido_dev_set_pin: %s (0x%x)", fido_strerr(r), r);
+
+ if ((r = fido_dev_close(dev)) != FIDO_OK)
+ errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r);
+
+ fido_dev_free(&dev);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 3 || argc > 4) {
+ fprintf(stderr, "usage: setpin <pin> [oldpin] <device>\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (argc == 3)
+ setpin(argv[2], argv[1], NULL);
+ else
+ setpin(argv[3], argv[1], argv[2]);
+
+ exit(0);
+}
diff --git a/examples/util.c b/examples/util.c
new file mode 100644
index 0000000..0c0c77a
--- /dev/null
+++ b/examples/util.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#include <fido.h>
+#include <fido/es256.h>
+#include <fido/es384.h>
+#include <fido/rs256.h>
+#include <fido/eddsa.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef _MSC_VER
+#include "../openbsd-compat/posix_win.h"
+#endif
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+base10(const char *str, long long *ll)
+{
+ char *ep;
+
+ *ll = strtoll(str, &ep, 10);
+ if (str == ep || *ep != '\0')
+ return (-1);
+ else if (*ll == LLONG_MIN && errno == ERANGE)
+ return (-1);
+ else if (*ll == LLONG_MAX && errno == ERANGE)
+ return (-1);
+
+ return (0);
+}
+
+int
+write_blob(const char *path, const unsigned char *ptr, size_t len)
+{
+ int fd, ok = -1;
+ ssize_t n;
+
+ if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) < 0) {
+ warn("open %s", path);
+ goto fail;
+ }
+
+ if ((n = write(fd, ptr, len)) < 0) {
+ warn("write");
+ goto fail;
+ }
+ if ((size_t)n != len) {
+ warnx("write");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1) {
+ close(fd);
+ }
+
+ return (ok);
+}
+
+int
+read_blob(const char *path, unsigned char **ptr, size_t *len)
+{
+ int fd, ok = -1;
+ struct stat st;
+ ssize_t n;
+
+ *ptr = NULL;
+ *len = 0;
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ warn("open %s", path);
+ goto fail;
+ }
+ if (fstat(fd, &st) < 0) {
+ warn("stat %s", path);
+ goto fail;
+ }
+ if (st.st_size < 0) {
+ warnx("stat %s: invalid size", path);
+ goto fail;
+ }
+ *len = (size_t)st.st_size;
+ if ((*ptr = malloc(*len)) == NULL) {
+ warn("malloc");
+ goto fail;
+ }
+ if ((n = read(fd, *ptr, *len)) < 0) {
+ warn("read");
+ goto fail;
+ }
+ if ((size_t)n != *len) {
+ warnx("read");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1) {
+ close(fd);
+ }
+ if (ok < 0) {
+ free(*ptr);
+ *ptr = NULL;
+ *len = 0;
+ }
+
+ return (ok);
+}
+
+EC_KEY *
+read_ec_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ EC_KEY *ec = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+ if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) {
+ warnx("EVP_PKEY_get1_EC_KEY");
+ goto fail;
+ }
+
+fail:
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ec);
+}
+
+int
+write_es256_pubkey(const char *path, const void *ptr, size_t len)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ es256_pk_t *pk = NULL;
+ int fd = -1;
+ int ok = -1;
+
+ if ((pk = es256_pk_new()) == NULL) {
+ warnx("es256_pk_new");
+ goto fail;
+ }
+
+ if (es256_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("es256_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) {
+ warn("open %s", path);
+ goto fail;
+ }
+
+ if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("fdopen");
+ goto fail;
+ }
+ fd = -1; /* owned by fp now */
+
+ if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("es256_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(fp, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es256_pk_free(&pk);
+
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (fd != -1) {
+ close(fd);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+int
+write_es384_pubkey(const char *path, const void *ptr, size_t len)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ es384_pk_t *pk = NULL;
+ int fd = -1;
+ int ok = -1;
+
+ if ((pk = es384_pk_new()) == NULL) {
+ warnx("es384_pk_new");
+ goto fail;
+ }
+
+ if (es384_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("es384_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) {
+ warn("open %s", path);
+ goto fail;
+ }
+
+ if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("fdopen");
+ goto fail;
+ }
+ fd = -1; /* owned by fp now */
+
+ if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("es384_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(fp, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es384_pk_free(&pk);
+
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (fd != -1) {
+ close(fd);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+RSA *
+read_rsa_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
+ warnx("EVP_PKEY_get1_RSA");
+ goto fail;
+ }
+
+fail:
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (rsa);
+}
+
+int
+write_rs256_pubkey(const char *path, const void *ptr, size_t len)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ rs256_pk_t *pk = NULL;
+ int fd = -1;
+ int ok = -1;
+
+ if ((pk = rs256_pk_new()) == NULL) {
+ warnx("rs256_pk_new");
+ goto fail;
+ }
+
+ if (rs256_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("rs256_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) {
+ warn("open %s", path);
+ goto fail;
+ }
+
+ if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("fdopen");
+ goto fail;
+ }
+ fd = -1; /* owned by fp now */
+
+ if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("rs256_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(fp, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ rs256_pk_free(&pk);
+
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (fd != -1) {
+ close(fd);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+EVP_PKEY *
+read_eddsa_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+
+fail:
+ if (fp) {
+ fclose(fp);
+ }
+
+ return (pkey);
+}
+
+int
+write_eddsa_pubkey(const char *path, const void *ptr, size_t len)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ eddsa_pk_t *pk = NULL;
+ int fd = -1;
+ int ok = -1;
+
+ if ((pk = eddsa_pk_new()) == NULL) {
+ warnx("eddsa_pk_new");
+ goto fail;
+ }
+
+ if (eddsa_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("eddsa_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) {
+ warn("open %s", path);
+ goto fail;
+ }
+
+ if ((fp = fdopen(fd, "w")) == NULL) {
+ warn("fdopen");
+ goto fail;
+ }
+ fd = -1; /* owned by fp now */
+
+ if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("eddsa_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(fp, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ eddsa_pk_free(&pk);
+
+ if (fp != NULL) {
+ fclose(fp);
+ }
+ if (fd != -1) {
+ close(fd);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..cc30baa
--- /dev/null
+++ b/fuzz/CMakeLists.txt
@@ -0,0 +1,82 @@
+# Copyright (c) 2019-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
+
+list(APPEND COMPAT_SOURCES
+ ../openbsd-compat/strlcpy.c
+ ../openbsd-compat/strlcat.c
+)
+
+list(APPEND COMMON_SOURCES
+ libfuzzer.c
+ mutator_aux.c
+)
+
+# XXX: OSS-Fuzz require linking using CXX
+set(FUZZ_LINKER_LANGUAGE "C" CACHE STRING "Linker language for fuzz harnesses")
+mark_as_advanced(FUZZ_LINKER_LANGUAGE)
+enable_language(${FUZZ_LINKER_LANGUAGE})
+
+# fuzz_cred
+add_executable(fuzz_cred fuzz_cred.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_cred PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_cred fido2_shared)
+
+# fuzz_assert
+add_executable(fuzz_assert fuzz_assert.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_assert PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_assert fido2_shared)
+
+# fuzz_mgmt
+add_executable(fuzz_mgmt fuzz_mgmt.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_mgmt PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_mgmt fido2_shared)
+
+# fuzz_credman
+add_executable(fuzz_credman fuzz_credman.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_credman PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_credman fido2_shared)
+
+# fuzz_bio
+add_executable(fuzz_bio fuzz_bio.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_bio PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_bio fido2_shared)
+
+# fuzz_hid
+add_executable(fuzz_hid fuzz_hid.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_hid PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_hid fido2_shared)
+
+# fuzz_netlink
+add_executable(fuzz_netlink fuzz_netlink.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_netlink PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_netlink fido2_shared)
+
+# fuzz_largeblob
+add_executable(fuzz_largeblob fuzz_largeblob.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_largeblob PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_largeblob fido2_shared)
+
+# fuzz_pcsc
+add_executable(fuzz_pcsc fuzz_pcsc.c ${COMMON_SOURCES} ${COMPAT_SOURCES})
+set_target_properties(fuzz_pcsc PROPERTIES
+ LINK_FLAGS ${FUZZ_LDFLAGS}
+ LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE})
+target_link_libraries(fuzz_pcsc fido2_shared)
diff --git a/fuzz/Dockerfile b/fuzz/Dockerfile
new file mode 100644
index 0000000..7b26e6e
--- /dev/null
+++ b/fuzz/Dockerfile
@@ -0,0 +1,16 @@
+# Copyright (c) 2019-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
+
+FROM alpine:latest
+ENV CC=clang
+ENV CXX=clang++
+RUN apk -q update
+RUN apk add build-base clang clang-analyzer cmake compiler-rt coreutils
+RUN apk add eudev-dev git linux-headers llvm openssl-dev pcsc-lite-dev
+RUN apk add sudo tar zlib-dev
+RUN git clone --branch v0.10.2 --depth=1 https://github.com/PJK/libcbor
+RUN git clone --depth=1 https://github.com/yubico/libfido2
+WORKDIR /libfido2
+RUN ./fuzz/build-coverage /libcbor /libfido2
diff --git a/fuzz/Makefile b/fuzz/Makefile
new file mode 100644
index 0000000..55a506b
--- /dev/null
+++ b/fuzz/Makefile
@@ -0,0 +1,90 @@
+# Copyright (c) 2019-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
+
+IMAGE := libfido2-coverage:1.14.0
+RUNNER := libfido2-runner
+PROFDATA := llvm-profdata
+COV := llvm-cov
+TARGETS := fuzz_assert fuzz_bio fuzz_cred fuzz_credman fuzz_hid \
+ fuzz_largeblob fuzz_netlink fuzz_mgmt fuzz_pcsc
+CORPORA := $(foreach f,${TARGETS},${f}/corpus)
+MINIFY := $(foreach f,${TARGETS},/minify/${f}/corpus)
+REMOTE := gs://libfido2-corpus.clusterfuzz-external.appspot.com
+.DEFAULT_GOAL := all
+
+all: ${TARGETS}
+
+build:
+ docker build -t ${IMAGE} - < Dockerfile
+
+run: build
+ -docker run -it -d --name ${RUNNER} ${IMAGE}
+ docker start ${RUNNER}
+
+sync: run
+ tar Ccf .. - src fuzz | docker exec -i ${RUNNER} tar Cxf /libfido2 -
+ docker exec ${RUNNER} make -C /libfido2/build
+
+corpus: sync
+ docker exec ${RUNNER} /bin/sh -c 'cd /libfido2/fuzz && rm -rf ${TARGETS}'
+ docker exec ${RUNNER} tar Czxf /libfido2/fuzz /libfido2/fuzz/corpus.tgz
+
+${TARGETS}: corpus sync
+ docker exec -e LLVM_PROFILE_FILE=/profraw/$@ ${RUNNER} \
+ /bin/sh -c 'rm -f /profraw/$@ && /libfido2/build/fuzz/$@ \
+ -runs=1 /libfido2/fuzz/$@'
+
+${MINIFY}: /minify/%/corpus: %
+ docker exec ${RUNNER} /bin/sh -c 'rm -rf $@ && mkdir -p $@ && \
+ /libfido2/build/fuzz/$< -use_value_profile=1 -merge=1 $@ \
+ /libfido2/fuzz/$</corpus'
+
+corpus.tgz-: ${MINIFY}
+ docker exec -i ${RUNNER} tar Czcf /minify - ${TARGETS} > $@
+
+profdata: run
+ docker exec ${RUNNER} /bin/sh -c 'rm -f /$@ && ${PROFDATA} \
+ merge -sparse /profraw/* -o /$@'
+
+report.tgz: profdata
+ docker exec ${RUNNER} /bin/sh -c 'rm -rf /report && mkdir /report && \
+ ${COV} show -format=html -tab-size=8 -instr-profile=/$< \
+ -ignore-filename-regex=pcsclite.h --show-branch-summary=false \
+ -output-dir=/report /libfido2/build/src/libfido2.so'
+ docker exec -i ${RUNNER} tar Czcf / - report > $@
+
+summary.txt: profdata
+ docker exec ${RUNNER} ${COV} report -use-color=false \
+ -ignore-filename-regex=pcsclite.h --show-branch-summary=false \
+ /libfido2/build/src/libfido2.so -instr-profile=/$< > $@
+
+functions.txt: profdata
+ docker exec ${RUNNER} /bin/sh -c '${COV} report -use-color=false \
+ -ignore-filename-regex=pcsclite.h -show-functions \
+ --show-branch-summary=false -instr-profile=/$< \
+ /libfido2/build/src/libfido2.so /libfido2/src/*.[ch]' > $@
+
+clean: run
+ docker exec ${RUNNER} /bin/sh -c 'rm -rf /profraw /profdata && \
+ make -C /libfido2/build clean'
+ -docker stop ${RUNNER}
+ rm -rf ${TARGETS}
+
+${CORPORA}:
+ -mkdir -p $@
+ gsutil -q -m rsync -d -r ${REMOTE}/libFuzzer/libfido2_$(@:/corpus=) $@
+
+fetch-oss-fuzz: ${CORPORA}
+ find ${TARGETS} -type f -size +8192c -print0 | xargs -0 rm
+
+fetch-franz:
+ ssh franz tar -C corpus -cf- . | tar -xf-
+
+corpus.tgz:
+ tar zcf $@ ${TARGETS}
+
+.PHONY: build run sync corpus ${TARGETS} ${CORPORA}
+.PHONY: report.tgz summary.txt functions.txt
+.PHONY: fetch-oss-fuzz fetch-franz corpus.tgz
diff --git a/fuzz/README b/fuzz/README
new file mode 100644
index 0000000..427625c
--- /dev/null
+++ b/fuzz/README
@@ -0,0 +1,43 @@
+libfido2 can be fuzzed using AFL or libFuzzer, with or without
+ASAN/MSAN/UBSAN.
+
+AFL is more convenient when fuzzing the path from the authenticator to
+libfido2 in an existing application. To do so, use preload-snoop.c with a real
+authenticator to obtain an initial corpus, rebuild libfido2 with -DFUZZ=ON, and
+use preload-fuzz.c to read device data from stdin.
+
+libFuzzer is better suited for bespoke fuzzers; see fuzz_cred.c, fuzz_credman.c,
+fuzz_assert.c, fuzz_hid.c, and fuzz_mgmt.c for examples. To build these
+harnesses, use -DCMAKE_C_FLAGS=-fsanitize=fuzzer-no-link
+-DFUZZ_LDFLAGS=-fsanitize=fuzzer -DFUZZ=ON.
+
+If -DFUZZ=ON is enabled, symbols listed in wrapped.sym are wrapped in the
+resulting shared object. The wrapper functions simulate failure according to a
+deterministic RNG and probabilities defined in wrap.c. Harnesses wishing to
+use this functionality should call prng_init() with a seed obtained from the
+corpus. To mutate only the seed part of a libFuzzer harness's corpora,
+use '-reduce_inputs=0 --fido-mutate=seed'.
+
+To run under ASAN/MSAN/UBSAN, libfido2 needs to be linked against flavours of
+libcbor and OpenSSL built with the respective sanitiser. In order to keep
+memory utilisation at a manageable level, you can either enforce limits at
+the OS level (e.g. cgroups on Linux), or patch libcbor with the diff below.
+N.B., the patch below is relative to libcbor 0.10.1.
+
+diff --git src/cbor/internal/memory_utils.c src/cbor/internal/memory_utils.c
+index bbea63c..3f7c9af 100644
+--- src/cbor/internal/memory_utils.c
++++ src/cbor/internal/memory_utils.c
+@@ -41,7 +41,11 @@ size_t _cbor_safe_signaling_add(size_t a, size_t b) {
+
+ void* _cbor_alloc_multiple(size_t item_size, size_t item_count) {
+ if (_cbor_safe_to_multiply(item_size, item_count)) {
+- return _cbor_malloc(item_size * item_count);
++ if (item_count > 1000) {
++ return NULL;
++ } else {
++ return _cbor_malloc(item_size * item_count);
++ }
+ } else {
+ return NULL;
+ }
diff --git a/fuzz/build-coverage b/fuzz/build-coverage
new file mode 100755
index 0000000..6cc5041
--- /dev/null
+++ b/fuzz/build-coverage
@@ -0,0 +1,34 @@
+#!/bin/sh -eux
+
+# Copyright (c) 2019 Yubico AB. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+# SPDX-License-Identifier: BSD-2-Clause
+
+LIBCBOR="$1"
+LIBFIDO2="$2"
+
+CC="${CC:-clang}"
+CXX="${CXX:-clang++}"
+PKG_CONFIG_PATH="${PKG_CONFIG_PATH:-${LIBCBOR}/install/lib/pkgconfig}"
+export CC PKG_CONFIG_PATH
+
+# Clean up.
+rm -rf "${LIBCBOR}/build" "${LIBCBOR}/install" "${LIBFIDO2}/build"
+
+# Patch, build, and install libcbor.
+(cd "${LIBCBOR}" && patch -N -l -s -p0 < "${LIBFIDO2}/fuzz/README") || true
+mkdir "${LIBCBOR}/build" "${LIBCBOR}/install"
+(cd "${LIBCBOR}/build" && cmake -DBUILD_SHARED_LIBS=ON \
+ -DCMAKE_INSTALL_PREFIX="${LIBCBOR}/install" ..)
+make -C "${LIBCBOR}/build" VERBOSE=1 all install
+
+# Build libfido2.
+mkdir -p "${LIBFIDO2}/build"
+export CFLAGS="-fprofile-instr-generate -fcoverage-mapping"
+export CFLAGS="${CFLAGS} -fsanitize=fuzzer-no-link"
+export LDFLAGS="${CFLAGS}"
+export FUZZ_LDFLAGS="${LDFLAGS} -fsanitize=fuzzer"
+(cd "${LIBFIDO2}/build" && cmake -DFUZZ=ON -DFUZZ_LDFLAGS="${FUZZ_LDFLAGS}" \
+ -DCMAKE_BUILD_TYPE=Debug ..)
+make -C "${LIBFIDO2}/build"
diff --git a/fuzz/clock.c b/fuzz/clock.c
new file mode 100644
index 0000000..bd758ea
--- /dev/null
+++ b/fuzz/clock.c
@@ -0,0 +1,80 @@
+/*
+ * 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 <stdint.h>
+#include <time.h>
+
+#include "mutator_aux.h"
+
+/*
+ * A pseudo-random monotonic clock with a probabilistic discontinuity to
+ * the end of time (as measured by struct timespec).
+ */
+
+extern int prng_up;
+extern int __wrap_clock_gettime(clockid_t, struct timespec *);
+extern int __real_clock_gettime(clockid_t, struct timespec *);
+extern int __wrap_usleep(unsigned int);
+static TLS struct timespec fuzz_clock;
+
+static void
+tick(unsigned int usec)
+{
+ long long drift;
+
+ /*
+ * Simulate a jump to the end of time with 0.125% probability.
+ * This condition should be gracefully handled by callers of
+ * clock_gettime().
+ */
+ if (uniform_random(800) < 1) {
+ fuzz_clock.tv_sec = LLONG_MAX;
+ fuzz_clock.tv_nsec = LONG_MAX;
+ return;
+ }
+
+ drift = usec * 1000LL + (long long)uniform_random(10000000); /* 10ms */
+ if (LLONG_MAX - drift < (long long)fuzz_clock.tv_nsec) {
+ fuzz_clock_reset(); /* Not much we can do here. */
+ } else if (drift + (long long)fuzz_clock.tv_nsec < 1000000000) {
+ fuzz_clock.tv_nsec += (long)(drift);
+ } else {
+ fuzz_clock.tv_sec += (long)(drift / 1000000000);
+ fuzz_clock.tv_nsec += (long)(drift % 1000000000);
+ }
+}
+
+int
+__wrap_clock_gettime(clockid_t clk_id, struct timespec *tp)
+{
+ if (!prng_up || clk_id != CLOCK_MONOTONIC)
+ return __real_clock_gettime(clk_id, tp);
+ if (uniform_random(400) < 1)
+ return -1;
+
+ tick(0);
+ *tp = fuzz_clock;
+
+ return 0;
+}
+
+int
+__wrap_usleep(unsigned int usec)
+{
+ if (uniform_random(400) < 1)
+ return -1;
+
+ tick(usec);
+
+ return 0;
+}
+
+void
+fuzz_clock_reset(void)
+{
+ memset(&fuzz_clock, 0, sizeof(fuzz_clock));
+}
diff --git a/fuzz/dummy.h b/fuzz/dummy.h
new file mode 100644
index 0000000..fc4bfc5
--- /dev/null
+++ b/fuzz/dummy.h
@@ -0,0 +1,182 @@
+/*
+ * 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
+ */
+
+#ifndef _DUMMY_H
+#define _DUMMY_H
+
+#include <stdint.h>
+
+const char dummy_name[] = "finger1";
+const char dummy_pin1[] = "skepp cg0u3;Y..";
+const char dummy_pin2[] = "bastilha 6rJrfQZI.";
+const char dummy_pin[] = "9}4gT:8d=A37Dh}U";
+const char dummy_rp_id[] = "localhost";
+const char dummy_rp_name[] = "sweet home localhost";
+const char dummy_user_icon[] = "an icon";
+const char dummy_user_name[] = "john smith";
+const char dummy_user_nick[] = "jsmith";
+const char dummy_pcsc_list[] = "reader1\0reader2\0reader3\0\0";
+const char dummy_pcsc_path[] = "pcsc://slot7";
+const uint8_t dummy_id[] = { 0x5e, 0xd2 };
+
+const uint8_t dummy_user_id[] = {
+ 0x78, 0x1c, 0x78, 0x60, 0xad, 0x88, 0xd2, 0x63,
+ 0x32, 0x62, 0x2a, 0xf1, 0x74, 0x5d, 0xed, 0xb2,
+ 0xe7, 0xa4, 0x2b, 0x44, 0x89, 0x29, 0x39, 0xc5,
+ 0x56, 0x64, 0x01, 0x27, 0x0d, 0xbb, 0xc4, 0x49,
+};
+
+const uint8_t dummy_cred_id[] = {
+ 0x4f, 0x72, 0x98, 0x42, 0x4a, 0xe1, 0x17, 0xa5,
+ 0x85, 0xa0, 0xef, 0x3b, 0x11, 0x24, 0x4a, 0x3d,
+};
+
+const uint8_t dummy_cdh[] = {
+ 0xec, 0x8d, 0x8f, 0x78, 0x42, 0x4a, 0x2b, 0xb7,
+ 0x82, 0x34, 0xaa, 0xca, 0x07, 0xa1, 0xf6, 0x56,
+ 0x42, 0x1c, 0xb6, 0xf6, 0xb3, 0x00, 0x86, 0x52,
+ 0x35, 0x2d, 0xa2, 0x62, 0x4a, 0xbe, 0x89, 0x76,
+};
+
+const uint8_t dummy_es256[] = {
+ 0xcc, 0x1b, 0x50, 0xac, 0xc4, 0x19, 0xf8, 0x3a,
+ 0xee, 0x0a, 0x77, 0xd6, 0xf3, 0x53, 0xdb, 0xef,
+ 0xf2, 0xb9, 0x5c, 0x2d, 0x8b, 0x1e, 0x52, 0x58,
+ 0x88, 0xf4, 0x0b, 0x85, 0x1f, 0x40, 0x6d, 0x18,
+ 0x15, 0xb3, 0xcc, 0x25, 0x7c, 0x38, 0x3d, 0xec,
+ 0xdf, 0xad, 0xbd, 0x46, 0x91, 0xc3, 0xac, 0x30,
+ 0x94, 0x2a, 0xf7, 0x78, 0x35, 0x70, 0x59, 0x6f,
+ 0x28, 0xcb, 0x8e, 0x07, 0x85, 0xb5, 0x91, 0x96,
+};
+
+const uint8_t dummy_rs256[] = {
+ 0xd2, 0xa8, 0xc0, 0x11, 0x82, 0x9e, 0x57, 0x2e,
+ 0x60, 0xae, 0x8c, 0xb0, 0x09, 0xe1, 0x58, 0x2b,
+ 0x99, 0xec, 0xc3, 0x11, 0x1b, 0xef, 0x81, 0x49,
+ 0x34, 0x53, 0x6a, 0x01, 0x65, 0x2c, 0x24, 0x09,
+ 0x30, 0x87, 0x98, 0x51, 0x6e, 0x30, 0x4f, 0x60,
+ 0xbd, 0x54, 0xd2, 0x54, 0xbd, 0x94, 0x42, 0xdd,
+ 0x63, 0xe5, 0x2c, 0xc6, 0x04, 0x32, 0xc0, 0x8f,
+ 0x72, 0xd5, 0xb4, 0xf0, 0x4f, 0x42, 0xe5, 0xb0,
+ 0xa2, 0x95, 0x11, 0xfe, 0xd8, 0xb0, 0x65, 0x34,
+ 0xff, 0xfb, 0x44, 0x97, 0x52, 0xfc, 0x67, 0x23,
+ 0x0b, 0xad, 0xf3, 0x3a, 0x82, 0xd4, 0x96, 0x10,
+ 0x87, 0x6b, 0xfa, 0xd6, 0x51, 0x60, 0x3e, 0x1c,
+ 0xae, 0x19, 0xb8, 0xce, 0x08, 0xae, 0x9a, 0xee,
+ 0x78, 0x16, 0x22, 0xcc, 0x92, 0xcb, 0xa8, 0x95,
+ 0x34, 0xe5, 0xb9, 0x42, 0x6a, 0xf0, 0x2e, 0x82,
+ 0x1f, 0x4c, 0x7d, 0x84, 0x94, 0x68, 0x7b, 0x97,
+ 0x2b, 0xf7, 0x7d, 0x67, 0x83, 0xbb, 0xc7, 0x8a,
+ 0x31, 0x5a, 0xf3, 0x2a, 0x95, 0xdf, 0x63, 0xe7,
+ 0x4e, 0xee, 0x26, 0xda, 0x87, 0x00, 0xe2, 0x23,
+ 0x4a, 0x33, 0x9a, 0xa0, 0x1b, 0xce, 0x60, 0x1f,
+ 0x98, 0xa1, 0xb0, 0xdb, 0xbf, 0x20, 0x59, 0x27,
+ 0xf2, 0x06, 0xd9, 0xbe, 0x37, 0xa4, 0x03, 0x6b,
+ 0x6a, 0x4e, 0xaf, 0x22, 0x68, 0xf3, 0xff, 0x28,
+ 0x59, 0x05, 0xc9, 0xf1, 0x28, 0xf4, 0xbb, 0x35,
+ 0xe0, 0xc2, 0x68, 0xc2, 0xaa, 0x54, 0xac, 0x8c,
+ 0xc1, 0x69, 0x9e, 0x4b, 0x32, 0xfc, 0x53, 0x58,
+ 0x85, 0x7d, 0x3f, 0x51, 0xd1, 0xc9, 0x03, 0x02,
+ 0x13, 0x61, 0x62, 0xda, 0xf8, 0xfe, 0x3e, 0xc8,
+ 0x95, 0x12, 0xfb, 0x0c, 0xdf, 0x06, 0x65, 0x6f,
+ 0x23, 0xc7, 0x83, 0x7c, 0x50, 0x2d, 0x27, 0x25,
+ 0x4d, 0xbf, 0x94, 0xf0, 0x89, 0x04, 0xb9, 0x2d,
+ 0xc4, 0xa5, 0x32, 0xa9, 0x25, 0x0a, 0x99, 0x59,
+ 0x01, 0x00, 0x01,
+};
+
+const uint8_t dummy_eddsa[] = {
+ 0xfe, 0x8b, 0x61, 0x50, 0x31, 0x7a, 0xe6, 0xdf,
+ 0xb1, 0x04, 0x9d, 0x4d, 0xb5, 0x7a, 0x5e, 0x96,
+ 0x4c, 0xb2, 0xf9, 0x5f, 0x72, 0x47, 0xb5, 0x18,
+ 0xe2, 0x39, 0xdf, 0x2f, 0x87, 0x19, 0xb3, 0x02,
+};
+
+const uint8_t dummy_netlink_wiredata[] = {
+ 0xd8, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x9d, 0x2e, 0x00, 0x00,
+ 0x01, 0x02, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x6e, 0x66, 0x63, 0x00, 0x06, 0x00, 0x01, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x05, 0x00,
+ 0x1f, 0x00, 0x00, 0x00, 0x80, 0x01, 0x06, 0x00,
+ 0x14, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x03, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x05, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x06, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x07, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x08, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x09, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x0f, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0a, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x0b, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x13, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0c, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x15, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x11, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0e, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x1a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x10, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x1b, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00,
+ 0x08, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00,
+ 0x14, 0x00, 0x13, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x07, 0x00,
+ 0x18, 0x00, 0x01, 0x00, 0x08, 0x00, 0x02, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00,
+ 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x9d, 0x2e, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x9d, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x05, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x09, 0x01, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x9d, 0x2e, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00,
+ 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x05, 0x00, 0x44, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x0b, 0x00, 0x07, 0x00, 0x27, 0x00, 0x00, 0x00,
+ 0x93, 0xb9, 0x25, 0x00
+};
+
+#endif /* !_DUMMY_H */
diff --git a/fuzz/export.gnu b/fuzz/export.gnu
new file mode 100644
index 0000000..bc25dd6
--- /dev/null
+++ b/fuzz/export.gnu
@@ -0,0 +1,285 @@
+{
+ 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_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_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_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_is_fido2;
+ fido_dev_major;
+ fido_dev_make_cred;
+ fido_dev_minor;
+ fido_dev_new;
+ fido_dev_open;
+ fido_dev_protocol;
+ fido_dev_reset;
+ fido_dev_set_io_functions;
+ fido_dev_set_pcsc;
+ fido_dev_set_pin;
+ fido_dev_set_pin_minlen;
+ fido_dev_set_pin_minlen_rpid;
+ 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_hid_get_report_len;
+ fido_hid_get_usage;
+ fido_init;
+ fido_nfc_rx;
+ fido_nfc_tx;
+ fido_nl_free;
+ fido_nl_get_nfc_target;
+ fido_nl_new;
+ fido_nl_power_nfc;
+ fido_pcsc_close;
+ fido_pcsc_manifest;
+ fido_pcsc_open;
+ fido_pcsc_read;
+ fido_pcsc_rx;
+ fido_pcsc_tx;
+ fido_pcsc_write;
+ 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;
+ prng_init;
+ prng_up;
+ fuzz_clock_reset;
+ fuzz_save_corpus;
+ set_netlink_io_functions;
+ set_pcsc_parameters;
+ set_pcsc_io_functions;
+ set_udev_parameters;
+ uniform_random;
+ local:
+ *;
+};
diff --git a/fuzz/functions.txt b/fuzz/functions.txt
new file mode 100644
index 0000000..4ad5a0c
--- /dev/null
+++ b/fuzz/functions.txt
@@ -0,0 +1,954 @@
+File '/libfido2/src/aes256.c':
+Name Regions Miss Cover Lines Miss Cover
+--------------------------------------------------------------------------------------------------------
+aes256_cbc_enc 4 0 100.00% 4 0 100.00%
+aes256_cbc_dec 4 0 100.00% 4 0 100.00%
+aes256_gcm_enc 1 0 100.00% 3 0 100.00%
+aes256_gcm_dec 1 0 100.00% 3 0 100.00%
+aes256.c:aes256_cbc_fips 26 1 96.15% 42 4 90.48%
+aes256.c:aes256_cbc 29 1 96.55% 36 3 91.67%
+aes256.c:aes256_cbc_proto1 1 0 100.00% 5 0 100.00%
+aes256.c:aes256_gcm 52 1 98.08% 60 4 93.33%
+--------------------------------------------------------------------------------------------------------
+TOTAL 118 3 97.46% 157 11 92.99%
+
+File '/libfido2/src/assert.c':
+Name Regions Miss Cover Lines Miss Cover
+-----------------------------------------------------------------------------------------------------------------
+fido_dev_get_assert 40 0 100.00% 35 0 100.00%
+fido_check_flags 13 0 100.00% 15 0 100.00%
+fido_get_signed_hash 20 1 95.00% 34 3 91.18%
+fido_assert_verify 50 4 92.00% 70 7 90.00%
+fido_assert_set_clientdata 12 12 0.00% 11 11 0.00%
+fido_assert_set_clientdata_hash 8 0 100.00% 6 0 100.00%
+fido_assert_set_hmac_salt 10 0 100.00% 6 0 100.00%
+fido_assert_set_hmac_secret 12 12 0.00% 7 7 0.00%
+fido_assert_set_rp 12 0 100.00% 11 0 100.00%
+fido_assert_set_winhello_appid 2 2 0.00% 5 5 0.00%
+fido_assert_allow_cred 13 2 84.62% 22 3 86.36%
+fido_assert_empty_allow_list 2 0 100.00% 5 0 100.00%
+fido_assert_set_extensions 14 0 100.00% 10 0 100.00%
+fido_assert_set_options 8 8 0.00% 5 5 0.00%
+fido_assert_set_up 2 0 100.00% 4 0 100.00%
+fido_assert_set_uv 2 0 100.00% 4 0 100.00%
+fido_assert_clientdata_hash_ptr 1 0 100.00% 3 0 100.00%
+fido_assert_clientdata_hash_len 1 0 100.00% 3 0 100.00%
+fido_assert_new 1 0 100.00% 3 0 100.00%
+fido_assert_reset_tx 1 0 100.00% 13 0 100.00%
+fido_assert_reset_rx 4 0 100.00% 20 0 100.00%
+fido_assert_free 6 0 100.00% 9 0 100.00%
+fido_assert_count 1 0 100.00% 3 0 100.00%
+fido_assert_rp_id 1 0 100.00% 3 0 100.00%
+fido_assert_flags 4 0 100.00% 5 0 100.00%
+fido_assert_sigcount 4 0 100.00% 5 0 100.00%
+fido_assert_authdata_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_authdata_len 4 0 100.00% 5 0 100.00%
+fido_assert_authdata_raw_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_authdata_raw_len 4 0 100.00% 5 0 100.00%
+fido_assert_sig_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_sig_len 4 0 100.00% 5 0 100.00%
+fido_assert_id_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_id_len 4 0 100.00% 5 0 100.00%
+fido_assert_user_id_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_user_id_len 4 0 100.00% 5 0 100.00%
+fido_assert_user_icon 4 0 100.00% 5 0 100.00%
+fido_assert_user_name 4 0 100.00% 5 0 100.00%
+fido_assert_user_display_name 4 0 100.00% 5 0 100.00%
+fido_assert_hmac_secret_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_hmac_secret_len 4 0 100.00% 5 0 100.00%
+fido_assert_largeblob_key_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_largeblob_key_len 4 0 100.00% 5 0 100.00%
+fido_assert_blob_ptr 4 0 100.00% 5 0 100.00%
+fido_assert_blob_len 4 0 100.00% 5 0 100.00%
+fido_assert_set_authdata 28 0 100.00% 33 0 100.00%
+fido_assert_set_authdata_raw 28 0 100.00% 32 0 100.00%
+fido_assert_set_sig 14 0 100.00% 7 0 100.00%
+fido_assert_set_count 10 0 100.00% 17 0 100.00%
+assert.c:fido_dev_get_assert_wait 21 0 100.00% 14 0 100.00%
+assert.c:fido_dev_get_assert_tx 56 2 96.43% 62 5 91.94%
+assert.c:fido_dev_get_assert_rx 27 0 100.00% 36 0 100.00%
+assert.c:adjust_assert_count 24 0 100.00% 26 0 100.00%
+assert.c:parse_assert_reply 15 0 100.00% 28 0 100.00%
+assert.c:fido_get_next_assert_tx 8 0 100.00% 8 0 100.00%
+assert.c:fido_get_next_assert_rx 23 2 91.30% 29 5 82.76%
+assert.c:decrypt_hmac_secrets 9 0 100.00% 15 0 100.00%
+assert.c:get_es256_hash 16 0 100.00% 17 0 100.00%
+assert.c:get_es384_hash 16 0 100.00% 17 0 100.00%
+assert.c:get_eddsa_hash 6 0 100.00% 9 0 100.00%
+assert.c:check_extensions 5 0 100.00% 9 0 100.00%
+assert.c:fido_assert_reset_extattr 1 0 100.00% 5 0 100.00%
+assert.c:fido_assert_clean_authdata 1 0 100.00% 6 0 100.00%
+-----------------------------------------------------------------------------------------------------------------
+TOTAL 628 45 92.83% 782 51 93.48%
+
+File '/libfido2/src/authkey.c':
+Name Regions Miss Cover Lines Miss Cover
+-----------------------------------------------------------------------------------------------------------------
+fido_dev_authkey 1 0 100.00% 3 0 100.00%
+authkey.c:fido_dev_authkey_wait 10 0 100.00% 7 0 100.00%
+authkey.c:fido_dev_authkey_tx 19 0 100.00% 25 0 100.00%
+authkey.c:fido_dev_authkey_rx 14 0 100.00% 21 0 100.00%
+authkey.c:parse_authkey 8 0 100.00% 10 0 100.00%
+-----------------------------------------------------------------------------------------------------------------
+TOTAL 52 0 100.00% 66 0 100.00%
+
+File '/libfido2/src/bio.c':
+Name Regions Miss Cover Lines Miss Cover
+-----------------------------------------------------------------------------------------------------------------
+fido_bio_dev_get_template_array 5 2 60.00% 6 1 83.33%
+fido_bio_dev_set_template_name 7 0 100.00% 6 0 100.00%
+fido_bio_dev_enroll_begin 25 2 92.00% 31 1 96.77%
+fido_bio_dev_enroll_continue 5 2 60.00% 6 1 83.33%
+fido_bio_dev_enroll_cancel 1 1 0.00% 4 4 0.00%
+fido_bio_dev_enroll_remove 1 0 100.00% 4 0 100.00%
+fido_bio_dev_get_info 1 0 100.00% 4 0 100.00%
+fido_bio_template_name 1 0 100.00% 3 0 100.00%
+fido_bio_template_id_ptr 1 0 100.00% 3 0 100.00%
+fido_bio_template_id_len 1 0 100.00% 3 0 100.00%
+fido_bio_template_array_count 1 0 100.00% 3 0 100.00%
+fido_bio_template_array_new 1 0 100.00% 3 0 100.00%
+fido_bio_template_new 1 0 100.00% 3 0 100.00%
+fido_bio_template_array_free 6 0 100.00% 8 0 100.00%
+fido_bio_template_free 6 0 100.00% 8 0 100.00%
+fido_bio_template_set_name 8 0 100.00% 7 0 100.00%
+fido_bio_template_set_id 8 0 100.00% 6 0 100.00%
+fido_bio_template 4 0 100.00% 5 0 100.00%
+fido_bio_enroll_new 1 0 100.00% 3 0 100.00%
+fido_bio_info_new 1 0 100.00% 3 0 100.00%
+fido_bio_info_type 1 0 100.00% 3 0 100.00%
+fido_bio_info_max_samples 1 0 100.00% 3 0 100.00%
+fido_bio_enroll_free 6 0 100.00% 8 0 100.00%
+fido_bio_info_free 6 0 100.00% 7 0 100.00%
+fido_bio_enroll_remaining_samples 1 0 100.00% 3 0 100.00%
+fido_bio_enroll_last_status 1 0 100.00% 3 0 100.00%
+bio.c:bio_get_template_array_wait 11 0 100.00% 7 0 100.00%
+bio.c:bio_tx 43 0 100.00% 55 0 100.00%
+bio.c:bio_prepare_hmac 18 0 100.00% 29 0 100.00%
+bio.c:bio_rx_template_array 19 0 100.00% 24 0 100.00%
+bio.c:bio_parse_template_array 26 1 96.15% 27 4 85.19%
+bio.c:decode_template_array 12 1 91.67% 18 3 83.33%
+bio.c:decode_template 9 0 100.00% 15 0 100.00%
+bio.c:bio_set_template_name_wait 19 0 100.00% 20 0 100.00%
+bio.c:bio_enroll_begin_wait 17 0 100.00% 19 0 100.00%
+bio.c:bio_rx_enroll_begin 23 0 100.00% 31 0 100.00%
+bio.c:bio_parse_enroll_status 20 0 100.00% 28 0 100.00%
+bio.c:bio_parse_template_id 8 0 100.00% 10 0 100.00%
+bio.c:bio_enroll_continue_wait 19 0 100.00% 20 0 100.00%
+bio.c:bio_rx_enroll_continue 19 0 100.00% 25 0 100.00%
+bio.c:bio_enroll_cancel_wait 11 11 0.00% 10 10 0.00%
+bio.c:bio_enroll_remove_wait 17 0 100.00% 19 0 100.00%
+bio.c:bio_get_info_wait 11 0 100.00% 10 0 100.00%
+bio.c:bio_rx_info 19 0 100.00% 24 0 100.00%
+bio.c:bio_reset_info 1 0 100.00% 4 0 100.00%
+bio.c:bio_parse_info 20 0 100.00% 28 0 100.00%
+bio.c:bio_reset_template_array 4 0 100.00% 7 0 100.00%
+bio.c:bio_reset_template 1 0 100.00% 5 0 100.00%
+bio.c:bio_reset_enroll 3 0 100.00% 6 0 100.00%
+-----------------------------------------------------------------------------------------------------------------
+TOTAL 451 20 95.57% 587 24 95.91%
+
+File '/libfido2/src/blob.c':
+Name Regions Miss Cover Lines Miss Cover
+-----------------------------------------------------------------------------------------------------------------
+fido_blob_new 1 0 100.00% 3 0 100.00%
+fido_blob_reset 1 0 100.00% 4 0 100.00%
+fido_blob_set 9 0 100.00% 15 0 100.00%
+fido_blob_append 12 1 91.67% 20 3 85.00%
+fido_blob_free 6 0 100.00% 8 0 100.00%
+fido_free_blob_array 7 0 100.00% 12 0 100.00%
+fido_blob_encode 6 0 100.00% 5 0 100.00%
+fido_blob_decode 1 0 100.00% 3 0 100.00%
+fido_blob_is_empty 3 0 100.00% 3 0 100.00%
+fido_blob_serialise 7 1 85.71% 10 1 90.00%
+-----------------------------------------------------------------------------------------------------------------
+TOTAL 53 2 96.23% 83 4 95.18%
+
+File '/libfido2/src/buf.c':
+Name Regions Miss Cover Lines Miss Cover
+-----------------------------------------------------------------------------------------------------------------
+fido_buf_read 4 0 100.00% 8 0 100.00%
+fido_buf_write 4 1 75.00% 8 1 87.50%
+-----------------------------------------------------------------------------------------------------------------
+TOTAL 8 1 87.50% 16 1 93.75%
+
+File '/libfido2/src/cbor.c':
+Name Regions Miss Cover Lines Miss Cover
+------------------------------------------------------------------------------------------------------------------
+cbor_map_iter 20 1 95.00% 26 4 84.62%
+cbor_array_iter 12 0 100.00% 16 0 100.00%
+cbor_parse_reply 27 0 100.00% 36 0 100.00%
+cbor_vector_free 6 0 100.00% 5 0 100.00%
+cbor_bytestring_copy 14 0 100.00% 18 0 100.00%
+cbor_string_copy 14 0 100.00% 18 0 100.00%
+cbor_add_bytestring 14 0 100.00% 21 0 100.00%
+cbor_add_string 14 0 100.00% 21 0 100.00%
+cbor_add_bool 14 0 100.00% 21 0 100.00%
+cbor_flatten_vector 14 1 92.86% 16 1 93.75%
+cbor_build_frame 15 0 100.00% 25 0 100.00%
+cbor_encode_rp_entity 13 0 100.00% 11 0 100.00%
+cbor_encode_user_entity 21 0 100.00% 15 0 100.00%
+cbor_encode_pubkey_param 36 0 100.00% 39 0 100.00%
+cbor_encode_pubkey 10 0 100.00% 11 0 100.00%
+cbor_encode_pubkey_list 18 0 100.00% 19 0 100.00%
+cbor_encode_str_array 18 0 100.00% 19 0 100.00%
+cbor_encode_cred_ext 55 0 100.00% 50 0 100.00%
+cbor_encode_cred_opt 13 0 100.00% 11 0 100.00%
+cbor_encode_assert_opt 13 0 100.00% 11 0 100.00%
+cbor_encode_pin_auth 21 1 95.24% 22 3 86.36%
+cbor_encode_pin_opt 4 0 100.00% 8 0 100.00%
+cbor_encode_change_pin_auth 32 1 96.88% 36 3 91.67%
+cbor_encode_assert_ext 33 0 100.00% 32 0 100.00%
+cbor_decode_fmt 13 0 100.00% 15 0 100.00%
+cbor_decode_pubkey 26 1 96.15% 36 2 94.44%
+cbor_decode_cred_authdata 31 1 96.77% 35 3 91.43%
+cbor_decode_assert_authdata 21 1 95.24% 32 3 90.62%
+cbor_decode_attstmt 13 0 100.00% 16 0 100.00%
+cbor_decode_uint64 4 0 100.00% 8 0 100.00%
+cbor_decode_cred_id 8 0 100.00% 9 0 100.00%
+cbor_decode_user 8 0 100.00% 9 0 100.00%
+cbor_decode_rp_entity 8 0 100.00% 9 0 100.00%
+cbor_decode_bool 10 0 100.00% 11 0 100.00%
+cbor_build_uint 10 1 90.00% 9 1 88.89%
+cbor_array_append 17 0 100.00% 21 0 100.00%
+cbor_array_drop 18 0 100.00% 17 0 100.00%
+cbor.c:ctap_check_cbor 28 0 100.00% 26 0 100.00%
+cbor.c:check_key_type 8 0 100.00% 7 0 100.00%
+cbor.c:cbor_add_arg 13 0 100.00% 21 0 100.00%
+cbor.c:cbor_add_uint8 14 0 100.00% 21 0 100.00%
+cbor.c:cbor_encode_largeblob_key_ext 6 0 100.00% 6 0 100.00%
+cbor.c:cbor_encode_hmac_secret_param 59 4 93.22% 66 8 87.88%
+cbor.c:get_cose_alg 46 0 100.00% 45 0 100.00%
+cbor.c:find_cose_alg 35 0 100.00% 33 0 100.00%
+cbor.c:decode_attcred 25 0 100.00% 44 0 100.00%
+cbor.c:decode_cred_extensions 14 0 100.00% 24 0 100.00%
+cbor.c:decode_cred_extension 41 0 100.00% 45 0 100.00%
+cbor.c:decode_assert_extensions 14 0 100.00% 23 0 100.00%
+cbor.c:decode_assert_extension 19 0 100.00% 27 0 100.00%
+cbor.c:decode_attstmt_entry 56 0 100.00% 51 0 100.00%
+cbor.c:decode_x5c 4 0 100.00% 6 0 100.00%
+cbor.c:decode_cred_id_entry 10 0 100.00% 19 0 100.00%
+cbor.c:decode_user_entry 25 0 100.00% 35 0 100.00%
+cbor.c:decode_rp_entity_entry 15 0 100.00% 25 0 100.00%
+------------------------------------------------------------------------------------------------------------------
+TOTAL 1070 12 98.88% 1258 28 97.77%
+
+File '/libfido2/src/compress.c':
+Name Regions Miss Cover Lines Miss Cover
+------------------------------------------------------------------------------------------------------------------
+fido_compress 1 0 100.00% 3 0 100.00%
+fido_uncompress 6 0 100.00% 5 0 100.00%
+compress.c:rfc1951_deflate 33 4 87.88% 47 6 87.23%
+compress.c:rfc1950_inflate 27 2 92.59% 22 4 81.82%
+compress.c:rfc1951_inflate 38 8 78.95% 45 14 68.89%
+------------------------------------------------------------------------------------------------------------------
+TOTAL 105 14 86.67% 122 24 80.33%
+
+File '/libfido2/src/config.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_dev_enable_entattest 1 0 100.00% 4 0 100.00%
+fido_dev_toggle_always_uv 1 0 100.00% 4 0 100.00%
+fido_dev_set_pin_minlen 1 0 100.00% 4 0 100.00%
+fido_dev_force_pin_change 1 0 100.00% 4 0 100.00%
+fido_dev_set_pin_minlen_rpid 6 0 100.00% 15 0 100.00%
+config.c:config_enable_entattest_wait 6 0 100.00% 7 0 100.00%
+config.c:config_tx 39 0 100.00% 49 0 100.00%
+config.c:config_prepare_hmac 10 0 100.00% 21 0 100.00%
+config.c:config_toggle_always_uv_wait 6 0 100.00% 7 0 100.00%
+config.c:config_pin_minlen 5 0 100.00% 7 0 100.00%
+config.c:config_pin_minlen_tx 36 0 100.00% 32 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 112 0 100.00% 154 0 100.00%
+
+File '/libfido2/src/cred.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_dev_make_cred 12 0 100.00% 10 0 100.00%
+fido_check_rp_id 4 0 100.00% 11 0 100.00%
+fido_cred_verify 59 2 96.61% 75 4 94.67%
+fido_cred_verify_self 60 4 93.33% 87 7 91.95%
+fido_cred_new 1 0 100.00% 3 0 100.00%
+fido_cred_reset_tx 1 0 100.00% 18 0 100.00%
+fido_cred_reset_rx 1 0 100.00% 7 0 100.00%
+fido_cred_free 6 0 100.00% 9 0 100.00%
+fido_cred_set_authdata 23 0 100.00% 28 0 100.00%
+fido_cred_set_authdata_raw 25 0 100.00% 29 0 100.00%
+fido_cred_set_id 6 0 100.00% 5 0 100.00%
+fido_cred_set_x509 6 0 100.00% 5 0 100.00%
+fido_cred_set_sig 6 0 100.00% 5 0 100.00%
+fido_cred_set_attstmt 20 0 100.00% 23 0 100.00%
+fido_cred_exclude 14 2 85.71% 19 3 84.21%
+fido_cred_empty_exclude_list 2 0 100.00% 5 0 100.00%
+fido_cred_set_clientdata 12 12 0.00% 11 11 0.00%
+fido_cred_set_clientdata_hash 8 0 100.00% 6 0 100.00%
+fido_cred_set_rp 18 0 100.00% 22 0 100.00%
+fido_cred_set_user 32 0 100.00% 41 0 100.00%
+fido_cred_set_extensions 16 0 100.00% 10 0 100.00%
+fido_cred_set_options 8 8 0.00% 5 5 0.00%
+fido_cred_set_rk 2 0 100.00% 4 0 100.00%
+fido_cred_set_uv 2 0 100.00% 4 0 100.00%
+fido_cred_set_prot 21 0 100.00% 14 0 100.00%
+fido_cred_set_pin_minlen 7 0 100.00% 8 0 100.00%
+fido_cred_set_blob 13 0 100.00% 8 0 100.00%
+fido_cred_set_fmt 20 4 80.00% 12 2 83.33%
+fido_cred_set_type 23 2 91.30% 9 1 88.89%
+fido_cred_type 1 0 100.00% 3 0 100.00%
+fido_cred_flags 1 0 100.00% 3 0 100.00%
+fido_cred_sigcount 1 0 100.00% 3 0 100.00%
+fido_cred_clientdata_hash_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_clientdata_hash_len 1 0 100.00% 3 0 100.00%
+fido_cred_x5c_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_x5c_len 1 0 100.00% 3 0 100.00%
+fido_cred_sig_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_sig_len 1 0 100.00% 3 0 100.00%
+fido_cred_authdata_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_authdata_len 1 0 100.00% 3 0 100.00%
+fido_cred_authdata_raw_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_authdata_raw_len 1 0 100.00% 3 0 100.00%
+fido_cred_attstmt_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_attstmt_len 1 0 100.00% 3 0 100.00%
+fido_cred_pubkey_ptr 11 0 100.00% 21 0 100.00%
+fido_cred_pubkey_len 11 0 100.00% 21 0 100.00%
+fido_cred_id_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_id_len 1 0 100.00% 3 0 100.00%
+fido_cred_aaguid_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_aaguid_len 1 0 100.00% 3 0 100.00%
+fido_cred_prot 1 0 100.00% 3 0 100.00%
+fido_cred_pin_minlen 1 0 100.00% 3 0 100.00%
+fido_cred_fmt 1 0 100.00% 3 0 100.00%
+fido_cred_rp_id 1 0 100.00% 3 0 100.00%
+fido_cred_rp_name 1 0 100.00% 3 0 100.00%
+fido_cred_user_name 1 0 100.00% 3 0 100.00%
+fido_cred_display_name 1 0 100.00% 3 0 100.00%
+fido_cred_user_id_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_user_id_len 1 0 100.00% 3 0 100.00%
+fido_cred_largeblob_key_ptr 1 0 100.00% 3 0 100.00%
+fido_cred_largeblob_key_len 1 0 100.00% 3 0 100.00%
+cred.c:fido_dev_make_cred_wait 10 0 100.00% 7 0 100.00%
+cred.c:fido_dev_make_cred_tx 64 0 100.00% 70 0 100.00%
+cred.c:fido_dev_make_cred_rx 29 0 100.00% 32 0 100.00%
+cred.c:parse_makecred_reply 14 0 100.00% 27 0 100.00%
+cred.c:check_extensions 2 0 100.00% 6 0 100.00%
+cred.c:get_signed_hash_u2f 27 0 100.00% 27 0 100.00%
+cred.c:verify_attstmt 25 2 92.00% 43 6 86.05%
+cred.c:fido_cred_clean_authdata 1 0 100.00% 8 0 100.00%
+cred.c:fido_cred_clean_attstmt 1 0 100.00% 8 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 653 36 94.49% 853 39 95.43%
+
+File '/libfido2/src/credman.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_credman_get_dev_metadata 1 0 100.00% 4 0 100.00%
+fido_credman_get_dev_rk 1 0 100.00% 4 0 100.00%
+fido_credman_del_dev_rk 1 0 100.00% 4 0 100.00%
+fido_credman_get_dev_rp 1 0 100.00% 4 0 100.00%
+fido_credman_set_dev_rk 1 0 100.00% 4 0 100.00%
+fido_credman_rk_new 1 0 100.00% 3 0 100.00%
+fido_credman_rk_free 6 1 83.33% 8 1 87.50%
+fido_credman_rk_count 1 0 100.00% 3 0 100.00%
+fido_credman_rk 4 0 100.00% 5 0 100.00%
+fido_credman_metadata_new 1 0 100.00% 3 0 100.00%
+fido_credman_metadata_free 6 1 83.33% 7 1 85.71%
+fido_credman_rk_existing 1 0 100.00% 3 0 100.00%
+fido_credman_rk_remaining 1 0 100.00% 3 0 100.00%
+fido_credman_rp_new 1 0 100.00% 3 0 100.00%
+fido_credman_rp_free 6 1 83.33% 8 1 87.50%
+fido_credman_rp_count 1 0 100.00% 3 0 100.00%
+fido_credman_rp_id 4 0 100.00% 5 0 100.00%
+fido_credman_rp_name 4 0 100.00% 5 0 100.00%
+fido_credman_rp_id_hash_len 4 0 100.00% 5 0 100.00%
+fido_credman_rp_id_hash_ptr 4 0 100.00% 5 0 100.00%
+credman.c:credman_get_metadata_wait 11 0 100.00% 8 0 100.00%
+credman.c:credman_tx 36 0 100.00% 50 0 100.00%
+credman.c:credman_prepare_hmac 31 1 96.77% 50 2 96.00%
+credman.c:credman_rx_metadata 19 0 100.00% 24 0 100.00%
+credman.c:credman_parse_metadata 9 0 100.00% 17 0 100.00%
+credman.c:credman_get_rk_wait 27 0 100.00% 23 0 100.00%
+credman.c:credman_rx_rk 27 0 100.00% 35 0 100.00%
+credman.c:credman_parse_rk_count 16 0 100.00% 20 0 100.00%
+credman.c:credman_grow_array 17 2 88.24% 21 5 76.19%
+credman.c:credman_parse_rk 23 0 100.00% 31 0 100.00%
+credman.c:credman_rx_next_rk 23 2 91.30% 29 5 82.76%
+credman.c:credman_del_rk_wait 16 0 100.00% 15 0 100.00%
+credman.c:credman_get_rp_wait 23 0 100.00% 15 0 100.00%
+credman.c:credman_rx_rp 27 0 100.00% 35 0 100.00%
+credman.c:credman_parse_rp_count 16 0 100.00% 20 0 100.00%
+credman.c:credman_parse_rp 9 0 100.00% 17 0 100.00%
+credman.c:credman_rx_next_rp 23 2 91.30% 29 5 82.76%
+credman.c:credman_set_dev_rk_wait 11 0 100.00% 8 0 100.00%
+credman.c:credman_reset_rk 4 0 100.00% 9 0 100.00%
+credman.c:credman_reset_rp 4 0 100.00% 12 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 422 10 97.63% 557 20 96.41%
+
+File '/libfido2/src/dev.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_dev_info_manifest 2 0 100.00% 11 0 100.00%
+fido_dev_open_with_info 5 5 0.00% 6 6 0.00%
+fido_dev_open 13 4 69.23% 16 6 62.50%
+fido_dev_close 9 2 77.78% 8 1 87.50%
+fido_dev_set_sigmask 18 18 0.00% 11 11 0.00%
+fido_dev_cancel 11 0 100.00% 8 0 100.00%
+fido_dev_set_io_functions 18 4 77.78% 14 6 57.14%
+fido_dev_set_transport_functions 6 2 66.67% 9 3 66.67%
+fido_dev_io_handle 1 1 0.00% 3 3 0.00%
+fido_init 8 1 87.50% 5 0 100.00%
+fido_dev_new 5 0 100.00% 14 0 100.00%
+fido_dev_new_with_info 10 10 0.00% 16 16 0.00%
+fido_dev_free 6 0 100.00% 8 0 100.00%
+fido_dev_protocol 1 0 100.00% 3 0 100.00%
+fido_dev_major 1 0 100.00% 3 0 100.00%
+fido_dev_minor 1 0 100.00% 3 0 100.00%
+fido_dev_build 1 0 100.00% 3 0 100.00%
+fido_dev_flags 1 0 100.00% 3 0 100.00%
+fido_dev_is_fido2 2 0 100.00% 3 0 100.00%
+fido_dev_is_winhello 2 2 0.00% 3 3 0.00%
+fido_dev_supports_pin 3 0 100.00% 3 0 100.00%
+fido_dev_has_pin 2 0 100.00% 3 0 100.00%
+fido_dev_supports_cred_prot 2 0 100.00% 3 0 100.00%
+fido_dev_supports_credman 2 0 100.00% 3 0 100.00%
+fido_dev_supports_uv 3 0 100.00% 3 0 100.00%
+fido_dev_has_uv 2 0 100.00% 3 0 100.00%
+fido_dev_supports_permissions 2 0 100.00% 3 0 100.00%
+fido_dev_force_u2f 2 0 100.00% 4 0 100.00%
+fido_dev_force_fido2 2 2 0.00% 3 3 0.00%
+fido_dev_get_pin_protocol 11 0 100.00% 7 0 100.00%
+fido_dev_maxmsgsize 1 0 100.00% 3 0 100.00%
+fido_dev_set_timeout 6 2 66.67% 6 1 83.33%
+dev.c:run_manifest 10 0 100.00% 13 0 100.00%
+dev.c:fido_dev_open_wait 10 0 100.00% 7 0 100.00%
+dev.c:fido_dev_open_tx 56 11 80.36% 56 20 64.29%
+dev.c:set_random_report_len 11 0 100.00% 6 0 100.00%
+dev.c:fido_dev_open_rx 36 1 97.22% 53 1 98.11%
+dev.c:fido_dev_set_flags 1 0 100.00% 5 0 100.00%
+dev.c:fido_dev_set_extension_flags 7 0 100.00% 7 0 100.00%
+dev.c:fido_dev_set_option_flags 31 0 100.00% 20 0 100.00%
+dev.c:fido_dev_set_protocol_flags 11 0 100.00% 17 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 332 65 80.42% 378 80 78.84%
+
+File '/libfido2/src/ecdh.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_do_ecdh 29 0 100.00% 36 0 100.00%
+ecdh.c:do_ecdh 37 0 100.00% 44 0 100.00%
+ecdh.c:kdf 19 1 94.74% 28 2 92.86%
+ecdh.c:hkdf_sha256 32 1 96.88% 38 3 92.11%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 117 2 98.29% 146 5 96.58%
+
+File '/libfido2/src/eddsa.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+eddsa_pk_decode 8 0 100.00% 9 0 100.00%
+eddsa_pk_new 1 0 100.00% 3 0 100.00%
+eddsa_pk_free 6 0 100.00% 7 0 100.00%
+eddsa_pk_from_ptr 10 0 100.00% 12 0 100.00%
+eddsa_pk_to_EVP_PKEY 3 0 100.00% 7 0 100.00%
+eddsa_pk_from_EVP_PKEY 18 2 88.89% 12 1 91.67%
+eddsa_verify_sig 19 2 89.47% 30 6 80.00%
+eddsa_pk_verify_sig 7 1 85.71% 13 2 84.62%
+eddsa.c:decode_pubkey_point 8 0 100.00% 11 0 100.00%
+eddsa.c:decode_coord 8 0 100.00% 10 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 88 5 94.32% 114 9 92.11%
+
+File '/libfido2/src/err.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_strerr 122 10 91.80% 126 10 92.06%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 122 10 91.80% 126 10 92.06%
+
+File '/libfido2/src/es256.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+es256_pk_decode 8 0 100.00% 9 0 100.00%
+es256_pk_encode 56 0 100.00% 48 0 100.00%
+es256_sk_new 1 0 100.00% 3 0 100.00%
+es256_sk_free 6 0 100.00% 7 0 100.00%
+es256_pk_new 1 0 100.00% 3 0 100.00%
+es256_pk_free 6 0 100.00% 7 0 100.00%
+es256_pk_from_ptr 15 0 100.00% 17 0 100.00%
+es256_pk_set_x 1 0 100.00% 4 0 100.00%
+es256_pk_set_y 1 0 100.00% 4 0 100.00%
+es256_sk_create 39 0 100.00% 40 0 100.00%
+es256_pk_to_EVP_PKEY 42 0 100.00% 53 0 100.00%
+es256_pk_from_EC_KEY 42 2 95.24% 47 4 91.49%
+es256_pk_from_EVP_PKEY 8 0 100.00% 7 0 100.00%
+es256_sk_to_EVP_PKEY 28 0 100.00% 39 0 100.00%
+es256_derive_pk 25 0 100.00% 29 0 100.00%
+es256_verify_sig 12 2 83.33% 19 5 73.68%
+es256_pk_verify_sig 7 1 85.71% 13 2 84.62%
+es256.c:decode_pubkey_point 9 0 100.00% 13 0 100.00%
+es256.c:decode_coord 8 0 100.00% 10 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 315 5 98.41% 372 11 97.04%
+
+File '/libfido2/src/es384.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+es384_pk_decode 8 0 100.00% 9 0 100.00%
+es384_pk_new 1 0 100.00% 3 0 100.00%
+es384_pk_free 6 0 100.00% 7 0 100.00%
+es384_pk_from_ptr 15 0 100.00% 17 0 100.00%
+es384_pk_to_EVP_PKEY 42 0 100.00% 53 0 100.00%
+es384_pk_from_EC_KEY 42 2 95.24% 47 4 91.49%
+es384_pk_from_EVP_PKEY 8 0 100.00% 7 0 100.00%
+es384_verify_sig 12 2 83.33% 19 5 73.68%
+es384_pk_verify_sig 7 1 85.71% 13 2 84.62%
+es384.c:decode_pubkey_point 9 0 100.00% 13 0 100.00%
+es384.c:decode_coord 8 0 100.00% 10 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 158 5 96.84% 198 11 94.44%
+
+File '/libfido2/src/extern.h':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+
+File '/libfido2/src/fallthrough.h':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+
+File '/libfido2/src/fido.h':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+
+File '/libfido2/src/hid.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_hid_get_usage 13 0 100.00% 22 0 100.00%
+fido_hid_get_report_len 19 0 100.00% 27 0 100.00%
+fido_dev_info_new 1 0 100.00% 3 0 100.00%
+fido_dev_info_free 9 0 100.00% 9 0 100.00%
+fido_dev_info_ptr 1 0 100.00% 3 0 100.00%
+fido_dev_info_set 26 2 92.31% 30 3 90.00%
+fido_dev_info_path 1 0 100.00% 3 0 100.00%
+fido_dev_info_vendor 1 0 100.00% 3 0 100.00%
+fido_dev_info_product 1 0 100.00% 3 0 100.00%
+fido_dev_info_manufacturer_string 1 0 100.00% 3 0 100.00%
+fido_dev_info_product_string 1 0 100.00% 3 0 100.00%
+hid.c:get_key_len 6 0 100.00% 12 0 100.00%
+hid.c:get_key_val 6 0 100.00% 18 0 100.00%
+hid.c:fido_dev_info_reset 1 0 100.00% 6 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 87 2 97.70% 145 3 97.93%
+
+File '/libfido2/src/hid_linux.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_hid_manifest 35 4 88.57% 41 2 95.12%
+fido_hid_open 33 33 0.00% 51 51 0.00%
+fido_hid_close 3 3 0.00% 6 6 0.00%
+fido_hid_set_sigmask 2 2 0.00% 6 6 0.00%
+fido_hid_read 15 15 0.00% 21 21 0.00%
+fido_hid_write 12 12 0.00% 17 17 0.00%
+fido_hid_report_in_len 1 1 0.00% 4 4 0.00%
+fido_hid_report_out_len 1 1 0.00% 4 4 0.00%
+hid_linux.c:copy_info 34 0 100.00% 44 0 100.00%
+hid_linux.c:is_fido 15 1 93.33% 16 1 93.75%
+hid_linux.c:get_parent_attr 6 0 100.00% 9 0 100.00%
+hid_linux.c:parse_uevent 12 0 100.00% 24 0 100.00%
+hid_linux.c:get_usb_attr 1 0 100.00% 3 0 100.00%
+hid_linux.c:get_report_descriptor 14 1 92.86% 17 3 82.35%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 184 73 60.33% 263 115 56.27%
+
+File '/libfido2/src/hid_unix.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_hid_unix_open 18 11 38.89% 22 14 36.36%
+fido_hid_unix_wait 11 10 9.09% 21 12 42.86%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 29 21 27.59% 43 26 39.53%
+
+File '/libfido2/src/info.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_dev_get_cbor_info_wait 10 0 100.00% 7 0 100.00%
+fido_dev_get_cbor_info 1 0 100.00% 4 0 100.00%
+fido_cbor_info_new 4 0 100.00% 7 0 100.00%
+fido_cbor_info_reset 1 0 100.00% 10 0 100.00%
+fido_cbor_info_free 6 0 100.00% 8 0 100.00%
+fido_cbor_info_versions_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_versions_len 1 0 100.00% 3 0 100.00%
+fido_cbor_info_extensions_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_extensions_len 1 0 100.00% 3 0 100.00%
+fido_cbor_info_transports_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_transports_len 1 0 100.00% 3 0 100.00%
+fido_cbor_info_aaguid_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_aaguid_len 1 0 100.00% 3 0 100.00%
+fido_cbor_info_options_name_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_options_value_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_options_len 1 0 100.00% 3 0 100.00%
+fido_cbor_info_maxcredbloblen 1 0 100.00% 3 0 100.00%
+fido_cbor_info_maxmsgsiz 1 0 100.00% 3 0 100.00%
+fido_cbor_info_maxcredcntlst 1 0 100.00% 3 0 100.00%
+fido_cbor_info_maxcredidlen 1 0 100.00% 3 0 100.00%
+fido_cbor_info_maxlargeblob 1 0 100.00% 3 0 100.00%
+fido_cbor_info_fwversion 1 0 100.00% 3 0 100.00%
+fido_cbor_info_minpinlen 1 0 100.00% 3 0 100.00%
+fido_cbor_info_maxrpid_minpinlen 1 0 100.00% 3 0 100.00%
+fido_cbor_info_uv_attempts 1 0 100.00% 3 0 100.00%
+fido_cbor_info_uv_modality 1 0 100.00% 3 0 100.00%
+fido_cbor_info_rk_remaining 1 0 100.00% 3 0 100.00%
+fido_cbor_info_protocols_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_protocols_len 1 0 100.00% 3 0 100.00%
+fido_cbor_info_algorithm_count 1 0 100.00% 3 0 100.00%
+fido_cbor_info_algorithm_type 4 0 100.00% 5 0 100.00%
+fido_cbor_info_algorithm_cose 4 0 100.00% 5 0 100.00%
+fido_cbor_info_new_pin_required 1 0 100.00% 3 0 100.00%
+fido_cbor_info_certs_name_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_certs_value_ptr 1 0 100.00% 3 0 100.00%
+fido_cbor_info_certs_len 1 0 100.00% 3 0 100.00%
+info.c:fido_dev_get_cbor_info_tx 8 0 100.00% 9 0 100.00%
+info.c:fido_dev_get_cbor_info_rx 14 0 100.00% 21 0 100.00%
+info.c:parse_reply_element 32 0 100.00% 59 0 100.00%
+info.c:decode_string_array 12 0 100.00% 17 0 100.00%
+info.c:decode_string 4 0 100.00% 10 0 100.00%
+info.c:decode_aaguid 8 0 100.00% 10 0 100.00%
+info.c:decode_options 11 0 100.00% 15 0 100.00%
+info.c:decode_option 7 0 100.00% 15 0 100.00%
+info.c:decode_protocols 12 0 100.00% 17 0 100.00%
+info.c:decode_protocol 6 0 100.00% 12 0 100.00%
+info.c:decode_algorithms 12 0 100.00% 17 0 100.00%
+info.c:decode_algorithm 9 0 100.00% 17 0 100.00%
+info.c:decode_algorithm_entry 20 0 100.00% 27 0 100.00%
+info.c:decode_certs 11 0 100.00% 15 0 100.00%
+info.c:decode_cert 7 0 100.00% 15 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 232 0 100.00% 409 0 100.00%
+
+File '/libfido2/src/io.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_tx 14 0 100.00% 11 0 100.00%
+fido_rx 13 1 92.31% 14 3 78.57%
+fido_rx_cbor_status 16 0 100.00% 19 0 100.00%
+io.c:transport_tx 7 0 100.00% 10 0 100.00%
+io.c:tx_empty 9 0 100.00% 14 0 100.00%
+io.c:tx_pkt 7 0 100.00% 10 0 100.00%
+io.c:tx 13 0 100.00% 19 0 100.00%
+io.c:tx_preamble 17 1 94.12% 20 1 95.00%
+io.c:tx_frame 16 1 93.75% 18 1 94.44%
+io.c:transport_rx 7 0 100.00% 10 0 100.00%
+io.c:rx 40 2 95.00% 52 2 96.15%
+io.c:rx_preamble 23 2 91.30% 22 5 77.27%
+io.c:rx_frame 11 0 100.00% 11 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 193 7 96.37% 230 12 94.78%
+
+File '/libfido2/src/iso7816.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+iso7816_new 4 0 100.00% 16 0 100.00%
+iso7816_free 6 0 100.00% 7 0 100.00%
+iso7816_add 6 1 83.33% 8 1 87.50%
+iso7816_ptr 1 0 100.00% 3 0 100.00%
+iso7816_len 1 0 100.00% 4 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 18 1 94.44% 38 1 97.37%
+
+File '/libfido2/src/largeblob.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_dev_largeblob_get 26 2 92.31% 38 4 89.47%
+fido_dev_largeblob_set 27 0 100.00% 36 0 100.00%
+fido_dev_largeblob_remove 12 0 100.00% 18 0 100.00%
+fido_dev_largeblob_get_array 15 2 86.67% 27 4 85.19%
+fido_dev_largeblob_set_array 14 0 100.00% 19 0 100.00%
+largeblob.c:largeblob_get_array 32 0 100.00% 36 0 100.00%
+largeblob.c:get_chunklen 10 1 90.00% 9 1 88.89%
+largeblob.c:largeblob_get_tx 19 0 100.00% 24 0 100.00%
+largeblob.c:largeblob_get_rx 26 0 100.00% 30 0 100.00%
+largeblob.c:parse_largeblob_reply 8 0 100.00% 9 0 100.00%
+largeblob.c:largeblob_array_check 7 0 100.00% 16 0 100.00%
+largeblob.c:largeblob_array_digest 10 0 100.00% 9 0 100.00%
+largeblob.c:largeblob_array_load 14 2 85.71% 19 7 63.16%
+largeblob.c:largeblob_array_lookup 25 0 100.00% 33 0 100.00%
+largeblob.c:largeblob_decode 16 2 87.50% 16 6 62.50%
+largeblob.c:largeblob_do_decode 27 3 88.89% 30 7 76.67%
+largeblob.c:largeblob_decrypt 15 0 100.00% 24 0 100.00%
+largeblob.c:largeblob_aad 1 0 100.00% 10 0 100.00%
+largeblob.c:largeblob_reset 1 0 100.00% 5 0 100.00%
+largeblob.c:largeblob_encode 16 0 100.00% 21 0 100.00%
+largeblob.c:largeblob_new 1 0 100.00% 3 0 100.00%
+largeblob.c:largeblob_seal 20 0 100.00% 32 0 100.00%
+largeblob.c:largeblob_get_nonce 8 0 100.00% 16 0 100.00%
+largeblob.c:largeblob_free 6 0 100.00% 8 0 100.00%
+largeblob.c:largeblob_add 27 2 92.59% 35 3 91.43%
+largeblob.c:largeblob_drop 21 0 100.00% 27 0 100.00%
+largeblob.c:largeblob_set_array 54 2 96.30% 61 4 93.44%
+largeblob.c:largeblob_get_uv_token 19 0 100.00% 23 0 100.00%
+largeblob.c:largeblob_set_tx 35 0 100.00% 36 0 100.00%
+largeblob.c:prepare_hmac 13 2 84.62% 23 7 69.57%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 525 18 96.57% 693 43 93.80%
+
+File '/libfido2/src/log.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_log_init 1 0 100.00% 4 0 100.00%
+fido_log_debug 6 1 83.33% 8 1 87.50%
+fido_log_xxd 16 1 93.75% 24 1 95.83%
+fido_log_error 8 2 75.00% 11 2 81.82%
+fido_set_log_handler 3 0 100.00% 4 0 100.00%
+log.c:log_on_stderr 1 1 0.00% 3 3 0.00%
+log.c:do_log 4 0 100.00% 9 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 39 5 87.18% 63 7 88.89%
+
+File '/libfido2/src/netlink.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_nl_power_nfc 18 0 100.00% 24 0 100.00%
+fido_nl_get_nfc_target 17 0 100.00% 31 0 100.00%
+fido_nl_free 10 2 80.00% 9 2 77.78%
+fido_nl_new 16 1 93.75% 26 3 88.46%
+set_netlink_io_functions 1 0 100.00% 4 0 100.00%
+netlink.c:nlmsg_new 8 0 100.00% 15 0 100.00%
+netlink.c:nlmsg_set_genl 1 0 100.00% 7 0 100.00%
+netlink.c:nlmsg_write 6 1 83.33% 7 1 85.71%
+netlink.c:nlmsg_set_u32 1 0 100.00% 3 0 100.00%
+netlink.c:nlmsg_setattr 15 1 93.33% 17 0 100.00%
+netlink.c:nlmsg_tx 10 1 90.00% 13 3 76.92%
+netlink.c:nlmsg_ptr 1 0 100.00% 3 0 100.00%
+netlink.c:nlmsg_len 1 0 100.00% 3 0 100.00%
+netlink.c:nlmsg_rx 11 2 81.82% 17 6 64.71%
+netlink.c:nl_parse_reply 20 0 100.00% 28 0 100.00%
+netlink.c:nlmsg_from_buf 15 0 100.00% 17 0 100.00%
+netlink.c:nlmsg_type 1 0 100.00% 3 0 100.00%
+netlink.c:nlmsg_get_status 8 0 100.00% 8 0 100.00%
+netlink.c:nlmsg_read 6 0 100.00% 7 0 100.00%
+netlink.c:nlmsg_get_genl 6 0 100.00% 7 0 100.00%
+netlink.c:nlmsg_iter 6 0 100.00% 13 0 100.00%
+netlink.c:nlmsg_getattr 1 0 100.00% 3 0 100.00%
+netlink.c:nla_from_buf 17 0 100.00% 21 0 100.00%
+netlink.c:nl_nfc_poll 18 0 100.00% 25 0 100.00%
+netlink.c:parse_nfc_event 10 0 100.00% 17 0 100.00%
+netlink.c:nla_type 1 0 100.00% 3 0 100.00%
+netlink.c:nla_get_u32 1 0 100.00% 3 0 100.00%
+netlink.c:nla_read 6 0 100.00% 7 0 100.00%
+netlink.c:nl_dump_nfc_target 19 0 100.00% 31 0 100.00%
+netlink.c:parse_target 9 0 100.00% 13 0 100.00%
+netlink.c:nl_get_nfc_family 23 0 100.00% 33 0 100.00%
+netlink.c:nlmsg_set_u16 1 0 100.00% 3 0 100.00%
+netlink.c:nlmsg_set_str 1 0 100.00% 3 0 100.00%
+netlink.c:parse_family 10 0 100.00% 17 0 100.00%
+netlink.c:nla_get_u16 1 0 100.00% 3 0 100.00%
+netlink.c:nla_iter 6 0 100.00% 13 0 100.00%
+netlink.c:nla_getattr 1 0 100.00% 3 0 100.00%
+netlink.c:parse_mcastgrps 1 0 100.00% 3 0 100.00%
+netlink.c:parse_mcastgrp 15 0 100.00% 24 0 100.00%
+netlink.c:nla_get_str 10 0 100.00% 11 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 329 8 97.57% 498 15 96.99%
+
+File '/libfido2/src/nfc.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_nfc_tx 28 0 100.00% 43 0 100.00%
+fido_nfc_rx 8 0 100.00% 13 0 100.00%
+nfc_is_fido 13 1 92.31% 21 3 85.71%
+fido_is_nfc 3 0 100.00% 3 0 100.00%
+fido_dev_set_nfc 4 1 75.00% 18 3 83.33%
+nfc.c:nfc_do_tx 20 0 100.00% 25 0 100.00%
+nfc.c:tx_short_apdu 14 0 100.00% 32 0 100.00%
+nfc.c:rx_init 25 0 100.00% 27 0 100.00%
+nfc.c:rx_cbor 4 0 100.00% 6 0 100.00%
+nfc.c:rx_msg 18 2 88.89% 23 6 73.91%
+nfc.c:rx_apdu 14 1 92.86% 22 3 86.36%
+nfc.c:tx_get_response 4 0 100.00% 11 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 155 5 96.77% 244 15 93.85%
+
+File '/libfido2/src/nfc_linux.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_nfc_manifest 35 7 80.00% 45 15 66.67%
+fido_nfc_open 20 3 85.00% 23 4 82.61%
+fido_nfc_close 1 1 0.00% 4 4 0.00%
+fido_nfc_set_sigmask 2 2 0.00% 6 6 0.00%
+fido_nfc_read 14 14 0.00% 30 30 0.00%
+fido_nfc_write 12 12 0.00% 18 18 0.00%
+nfc_linux.c:copy_info 39 22 43.59% 44 16 63.64%
+nfc_linux.c:get_usb_attr 1 1 0.00% 3 3 0.00%
+nfc_linux.c:get_parent_attr 6 6 0.00% 9 9 0.00%
+nfc_linux.c:sysnum_from_syspath 15 0 100.00% 17 0 100.00%
+nfc_linux.c:nfc_new 6 0 100.00% 11 0 100.00%
+nfc_linux.c:nfc_target_connect 9 9 0.00% 21 21 0.00%
+nfc_linux.c:nfc_free 12 0 100.00% 11 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 172 77 55.23% 242 126 47.93%
+
+File '/libfido2/src/pcsc.c':
+Name Regions Miss Cover Lines Miss Cover
+-------------------------------------------------------------------------------------------------------------------
+fido_pcsc_manifest 51 0 100.00% 55 0 100.00%
+fido_pcsc_open 32 0 100.00% 43 0 100.00%
+fido_pcsc_close 6 0 100.00% 9 0 100.00%
+fido_pcsc_read 8 0 100.00% 16 0 100.00%
+fido_pcsc_write 8 0 100.00% 22 0 100.00%
+fido_pcsc_tx 1 0 100.00% 3 0 100.00%
+fido_pcsc_rx 1 0 100.00% 3 0 100.00%
+fido_is_pcsc 3 0 100.00% 3 0 100.00%
+fido_dev_set_pcsc 4 1 75.00% 18 3 83.33%
+pcsc.c:list_readers 24 0 100.00% 24 0 100.00%
+pcsc.c:copy_info 30 0 100.00% 41 0 100.00%
+pcsc.c:get_reader 25 0 100.00% 28 0 100.00%
+pcsc.c:prepare_io_request 11 0 100.00% 17 0 100.00%
+-------------------------------------------------------------------------------------------------------------------
+TOTAL 204 1 99.51% 282 3 98.94%
+
+File '/libfido2/src/pin.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_sha256 7 0 100.00% 10 0 100.00%
+fido_dev_get_uv_token 1 0 100.00% 3 0 100.00%
+fido_dev_set_pin 1 0 100.00% 4 0 100.00%
+fido_dev_get_retry_count 1 0 100.00% 4 0 100.00%
+fido_dev_get_uv_retry_count 1 0 100.00% 4 0 100.00%
+cbor_add_uv_params 17 0 100.00% 23 0 100.00%
+pin.c:uv_token_wait 14 2 85.71% 12 1 91.67%
+pin.c:ctap21_uv_token_tx 49 0 100.00% 53 0 100.00%
+pin.c:pin_sha256_enc 19 0 100.00% 24 0 100.00%
+pin.c:encode_uv_permission 20 1 95.00% 19 3 84.21%
+pin.c:ctap20_uv_token_tx 37 0 100.00% 45 0 100.00%
+pin.c:uv_token_rx 27 0 100.00% 34 0 100.00%
+pin.c:parse_uv_token 8 0 100.00% 10 0 100.00%
+pin.c:fido_dev_set_pin_wait 21 0 100.00% 24 0 100.00%
+pin.c:fido_dev_change_pin_tx 45 0 100.00% 56 0 100.00%
+pin.c:pin_pad64_enc 15 0 100.00% 21 0 100.00%
+pin.c:pad64 18 0 100.00% 20 0 100.00%
+pin.c:fido_dev_set_pin_tx 33 0 100.00% 41 0 100.00%
+pin.c:fido_dev_get_pin_retry_count_wait 10 0 100.00% 7 0 100.00%
+pin.c:fido_dev_get_retry_count_tx 19 0 100.00% 23 0 100.00%
+pin.c:fido_dev_get_pin_retry_count_rx 19 0 100.00% 24 0 100.00%
+pin.c:parse_pin_retry_count 1 0 100.00% 3 0 100.00%
+pin.c:parse_retry_count 13 0 100.00% 16 0 100.00%
+pin.c:fido_dev_get_uv_retry_count_wait 10 0 100.00% 7 0 100.00%
+pin.c:fido_dev_get_uv_retry_count_rx 19 0 100.00% 24 0 100.00%
+pin.c:parse_uv_retry_count 1 0 100.00% 3 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 426 3 99.30% 514 4 99.22%
+
+File '/libfido2/src/random.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_get_random 6 0 100.00% 6 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 6 0 100.00% 6 0 100.00%
+
+File '/libfido2/src/reset.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_dev_reset 1 0 100.00% 4 0 100.00%
+reset.c:fido_dev_reset_wait 15 0 100.00% 11 0 100.00%
+reset.c:fido_dev_reset_tx 8 0 100.00% 8 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 24 0 100.00% 23 0 100.00%
+
+File '/libfido2/src/rs1.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+rs1_verify_sig 20 2 90.00% 30 6 80.00%
+rs1.c:rs1_get_EVP_MD 1 0 100.00% 3 0 100.00%
+rs1.c:rs1_free_EVP_MD 1 0 100.00% 3 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 22 2 90.91% 36 6 83.33%
+
+File '/libfido2/src/rs256.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+rs256_pk_decode 8 0 100.00% 9 0 100.00%
+rs256_pk_new 1 0 100.00% 3 0 100.00%
+rs256_pk_free 6 0 100.00% 7 0 100.00%
+rs256_pk_from_ptr 10 0 100.00% 12 0 100.00%
+rs256_pk_to_EVP_PKEY 35 0 100.00% 43 0 100.00%
+rs256_pk_from_RSA 32 6 81.25% 26 9 65.38%
+rs256_pk_from_EVP_PKEY 8 0 100.00% 7 0 100.00%
+rs256_verify_sig 20 2 90.00% 30 5 83.33%
+rs256_pk_verify_sig 7 1 85.71% 13 2 84.62%
+rs256.c:decode_rsa_pubkey 9 0 100.00% 13 0 100.00%
+rs256.c:decode_bignum 8 0 100.00% 10 0 100.00%
+rs256.c:rs256_get_EVP_MD 1 0 100.00% 3 0 100.00%
+rs256.c:rs256_free_EVP_MD 1 0 100.00% 3 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 146 9 93.84% 179 16 91.06%
+
+File '/libfido2/src/time.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_time_now 4 0 100.00% 7 0 100.00%
+fido_time_delta 23 1 95.65% 23 0 100.00%
+time.c:timespec_to_ms 16 2 87.50% 13 2 84.62%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 43 3 93.02% 43 2 95.35%
+
+File '/libfido2/src/touch.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_dev_get_touch_begin 50 0 100.00% 59 0 100.00%
+fido_dev_get_touch_status 17 0 100.00% 20 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 67 0 100.00% 79 0 100.00%
+
+File '/libfido2/src/tpm.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_get_signed_hash_tpm 25 0 100.00% 39 0 100.00%
+tpm.c:check_es256_pubarea 19 0 100.00% 30 0 100.00%
+tpm.c:bswap_es256_pubarea 1 0 100.00% 12 0 100.00%
+tpm.c:check_rs256_pubarea 17 0 100.00% 28 0 100.00%
+tpm.c:bswap_rs256_pubarea 1 0 100.00% 10 0 100.00%
+tpm.c:check_sha1_certinfo 15 0 100.00% 38 0 100.00%
+tpm.c:get_signed_sha1 17 0 100.00% 19 0 100.00%
+tpm.c:get_signed_name 7 0 100.00% 10 0 100.00%
+tpm.c:bswap_sha1_certinfo 1 0 100.00% 8 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 103 0 100.00% 194 0 100.00%
+
+File '/libfido2/src/types.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_str_array_free 4 0 100.00% 7 0 100.00%
+fido_opt_array_free 4 0 100.00% 9 0 100.00%
+fido_byte_array_free 1 0 100.00% 5 0 100.00%
+fido_algo_free 1 0 100.00% 5 0 100.00%
+fido_algo_array_free 4 0 100.00% 7 0 100.00%
+fido_cert_array_free 4 0 100.00% 9 0 100.00%
+fido_str_array_pack 11 0 100.00% 14 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 29 0 100.00% 56 0 100.00%
+
+File '/libfido2/src/u2f.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+u2f_register 76 0 100.00% 81 0 100.00%
+u2f_authenticate 33 0 100.00% 37 0 100.00%
+u2f_get_touch_begin 37 0 100.00% 45 0 100.00%
+u2f_get_touch_status 26 0 100.00% 36 0 100.00%
+u2f.c:key_lookup 51 0 100.00% 65 0 100.00%
+u2f.c:send_dummy_register 37 0 100.00% 45 0 100.00%
+u2f.c:delay_ms 13 1 92.31% 15 3 80.00%
+u2f.c:parse_register_reply 49 0 100.00% 62 0 100.00%
+u2f.c:x5c_get 21 1 95.24% 26 3 88.46%
+u2f.c:sig_get 6 0 100.00% 10 0 100.00%
+u2f.c:encode_cred_attstmt 45 0 100.00% 52 0 100.00%
+u2f.c:encode_cred_authdata 33 2 93.94% 61 6 90.16%
+u2f.c:cbor_blob_from_ec_point 22 0 100.00% 31 0 100.00%
+u2f.c:u2f_authenticate_single 32 0 100.00% 43 0 100.00%
+u2f.c:do_auth 56 0 100.00% 67 0 100.00%
+u2f.c:parse_auth_reply 23 0 100.00% 23 0 100.00%
+u2f.c:authdata_fake 12 0 100.00% 27 0 100.00%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 572 4 99.30% 726 12 98.35%
+
+File '/libfido2/src/util.c':
+Name Regions Miss Cover Lines Miss Cover
+---------------------------------------------------------------------------------------------------------------------
+fido_to_uint64 14 1 92.86% 14 1 92.86%
+---------------------------------------------------------------------------------------------------------------------
+TOTAL 14 1 92.86% 14 1 92.86%
diff --git a/fuzz/fuzz_assert.c b/fuzz/fuzz_assert.c
new file mode 100644
index 0000000..03cb51c
--- /dev/null
+++ b/fuzz/fuzz_assert.c
@@ -0,0 +1,534 @@
+/*
+ * Copyright (c) 2019-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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mutator_aux.h"
+#include "wiredata_fido2.h"
+#include "wiredata_u2f.h"
+#include "dummy.h"
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+/* Parameter set defining a FIDO2 get assertion operation. */
+struct param {
+ char pin[MAXSTR];
+ char rp_id[MAXSTR];
+ int ext;
+ int seed;
+ struct blob cdh;
+ struct blob cred;
+ struct blob es256;
+ struct blob rs256;
+ struct blob eddsa;
+ struct blob wire_data;
+ uint8_t cred_count;
+ uint8_t type;
+ uint8_t opt;
+ uint8_t up;
+ uint8_t uv;
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * get assertion using the example parameters above.
+ */
+static const uint8_t dummy_wire_data_fido[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_ASSERT,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a U2F
+ * authentication using the example parameters above.
+ */
+static const uint8_t dummy_wire_data_u2f[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_AUTH,
+};
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 15 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_byte(v[0], &p->uv) < 0 ||
+ unpack_byte(v[1], &p->up) < 0 ||
+ unpack_byte(v[2], &p->opt) < 0 ||
+ unpack_byte(v[3], &p->type) < 0 ||
+ unpack_byte(v[4], &p->cred_count) < 0 ||
+ unpack_int(v[5], &p->ext) < 0 ||
+ unpack_int(v[6], &p->seed) < 0 ||
+ unpack_string(v[7], p->rp_id) < 0 ||
+ unpack_string(v[8], p->pin) < 0 ||
+ unpack_blob(v[9], &p->wire_data) < 0 ||
+ unpack_blob(v[10], &p->rs256) < 0 ||
+ unpack_blob(v[11], &p->es256) < 0 ||
+ unpack_blob(v[12], &p->eddsa) < 0 ||
+ unpack_blob(v[13], &p->cred) < 0 ||
+ unpack_blob(v[14], &p->cdh) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[15], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(15)) == NULL ||
+ (argv[0] = pack_byte(p->uv)) == NULL ||
+ (argv[1] = pack_byte(p->up)) == NULL ||
+ (argv[2] = pack_byte(p->opt)) == NULL ||
+ (argv[3] = pack_byte(p->type)) == NULL ||
+ (argv[4] = pack_byte(p->cred_count)) == NULL ||
+ (argv[5] = pack_int(p->ext)) == NULL ||
+ (argv[6] = pack_int(p->seed)) == NULL ||
+ (argv[7] = pack_string(p->rp_id)) == NULL ||
+ (argv[8] = pack_string(p->pin)) == NULL ||
+ (argv[9] = pack_blob(&p->wire_data)) == NULL ||
+ (argv[10] = pack_blob(&p->rs256)) == NULL ||
+ (argv[11] = pack_blob(&p->es256)) == NULL ||
+ (argv[12] = pack_blob(&p->eddsa)) == NULL ||
+ (argv[13] = pack_blob(&p->cred)) == NULL ||
+ (argv[14] = pack_blob(&p->cdh)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 15; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 15; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ dummy.type = 1; /* rsa */
+ dummy.ext = FIDO_EXT_HMAC_SECRET;
+
+ strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin));
+ strlcpy(dummy.rp_id, dummy_rp_id, sizeof(dummy.rp_id));
+
+ dummy.cred.len = sizeof(dummy_cdh); /* XXX */
+ dummy.cdh.len = sizeof(dummy_cdh);
+ dummy.es256.len = sizeof(dummy_es256);
+ dummy.rs256.len = sizeof(dummy_rs256);
+ dummy.eddsa.len = sizeof(dummy_eddsa);
+ dummy.wire_data.len = sizeof(dummy_wire_data_fido);
+
+ memcpy(&dummy.cred.body, &dummy_cdh, dummy.cred.len); /* XXX */
+ memcpy(&dummy.cdh.body, &dummy_cdh, dummy.cdh.len);
+ memcpy(&dummy.wire_data.body, &dummy_wire_data_fido,
+ dummy.wire_data.len);
+ memcpy(&dummy.es256.body, &dummy_es256, dummy.es256.len);
+ memcpy(&dummy.rs256.body, &dummy_rs256, dummy.rs256.len);
+ memcpy(&dummy.eddsa.body, &dummy_eddsa, dummy.eddsa.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static void
+get_assert(fido_assert_t *assert, uint8_t opt, const struct blob *cdh,
+ const char *rp_id, int ext, uint8_t up, uint8_t uv, const char *pin,
+ uint8_t cred_count, const struct blob *cred)
+{
+ fido_dev_t *dev;
+
+ if ((dev = open_dev(opt & 2)) == NULL)
+ return;
+ if (opt & 1)
+ fido_dev_force_u2f(dev);
+ if (ext & FIDO_EXT_HMAC_SECRET)
+ fido_assert_set_extensions(assert, FIDO_EXT_HMAC_SECRET);
+ if (ext & FIDO_EXT_CRED_BLOB)
+ fido_assert_set_extensions(assert, FIDO_EXT_CRED_BLOB);
+ if (ext & FIDO_EXT_LARGEBLOB_KEY)
+ fido_assert_set_extensions(assert, FIDO_EXT_LARGEBLOB_KEY);
+ if (up & 1)
+ fido_assert_set_up(assert, FIDO_OPT_TRUE);
+ else if (opt & 1)
+ fido_assert_set_up(assert, FIDO_OPT_FALSE);
+ if (uv & 1)
+ fido_assert_set_uv(assert, FIDO_OPT_TRUE);
+
+ for (uint8_t i = 0; i < cred_count; i++)
+ fido_assert_allow_cred(assert, cred->body, cred->len);
+
+ fido_assert_set_clientdata_hash(assert, cdh->body, cdh->len);
+ fido_assert_set_rp(assert, rp_id);
+ /* XXX reuse cred as hmac salt */
+ fido_assert_set_hmac_salt(assert, cred->body, cred->len);
+
+ /* repeat memory operations to trigger reallocation paths */
+ fido_assert_set_clientdata_hash(assert, cdh->body, cdh->len);
+ fido_assert_set_rp(assert, rp_id);
+ fido_assert_set_hmac_salt(assert, cred->body, cred->len);
+
+ if (strlen(pin) == 0)
+ pin = NULL;
+
+ fido_dev_get_assert(dev, assert, (opt & 1) ? NULL : pin);
+
+ fido_dev_cancel(dev);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+verify_assert(int type, const unsigned char *cdh_ptr, size_t cdh_len,
+ const char *rp_id, const unsigned char *authdata_ptr, size_t authdata_len,
+ const unsigned char *sig_ptr, size_t sig_len, uint8_t up, uint8_t uv,
+ int ext, void *pk)
+{
+ fido_assert_t *assert = NULL;
+ int r;
+
+ if ((assert = fido_assert_new()) == NULL)
+ return;
+
+ fido_assert_set_clientdata_hash(assert, cdh_ptr, cdh_len);
+ fido_assert_set_rp(assert, rp_id);
+ fido_assert_set_count(assert, 1);
+
+ if (fido_assert_set_authdata(assert, 0, authdata_ptr,
+ authdata_len) != FIDO_OK) {
+ fido_assert_set_authdata_raw(assert, 0, authdata_ptr,
+ authdata_len);
+ }
+
+ if (up & 1)
+ fido_assert_set_up(assert, FIDO_OPT_TRUE);
+ if (uv & 1)
+ fido_assert_set_uv(assert, FIDO_OPT_TRUE);
+
+ fido_assert_set_extensions(assert, ext);
+ fido_assert_set_sig(assert, 0, sig_ptr, sig_len);
+
+ /* repeat memory operations to trigger reallocation paths */
+ if (fido_assert_set_authdata(assert, 0, authdata_ptr,
+ authdata_len) != FIDO_OK) {
+ fido_assert_set_authdata_raw(assert, 0, authdata_ptr,
+ authdata_len);
+ }
+ fido_assert_set_sig(assert, 0, sig_ptr, sig_len);
+
+ r = fido_assert_verify(assert, 0, type, pk);
+ consume(&r, sizeof(r));
+
+ fido_assert_free(&assert);
+}
+
+/*
+ * Do a dummy conversion to exercise es256_pk_from_EVP_PKEY().
+ */
+static void
+es256_convert(const es256_pk_t *k)
+{
+ EVP_PKEY *pkey = NULL;
+ es256_pk_t *pk = NULL;
+ int r;
+
+ if ((pkey = es256_pk_to_EVP_PKEY(k)) == NULL ||
+ (pk = es256_pk_new()) == NULL)
+ goto out;
+
+ r = es256_pk_from_EVP_PKEY(pk, pkey);
+ consume(&r, sizeof(r));
+out:
+ es256_pk_free(&pk);
+ EVP_PKEY_free(pkey);
+}
+
+/*
+ * Do a dummy conversion to exercise es384_pk_from_EVP_PKEY().
+ */
+static void
+es384_convert(const es384_pk_t *k)
+{
+ EVP_PKEY *pkey = NULL;
+ es384_pk_t *pk = NULL;
+ int r;
+
+ if ((pkey = es384_pk_to_EVP_PKEY(k)) == NULL ||
+ (pk = es384_pk_new()) == NULL)
+ goto out;
+
+ r = es384_pk_from_EVP_PKEY(pk, pkey);
+ consume(&r, sizeof(r));
+out:
+ es384_pk_free(&pk);
+ EVP_PKEY_free(pkey);
+}
+
+/*
+ * Do a dummy conversion to exercise rs256_pk_from_EVP_PKEY().
+ */
+static void
+rs256_convert(const rs256_pk_t *k)
+{
+ EVP_PKEY *pkey = NULL;
+ rs256_pk_t *pk = NULL;
+ int r;
+
+ if ((pkey = rs256_pk_to_EVP_PKEY(k)) == NULL ||
+ (pk = rs256_pk_new()) == NULL)
+ goto out;
+
+ r = rs256_pk_from_EVP_PKEY(pk, pkey);
+ consume(&r, sizeof(r));
+out:
+ rs256_pk_free(&pk);
+ EVP_PKEY_free(pkey);
+}
+
+/*
+ * Do a dummy conversion to exercise eddsa_pk_from_EVP_PKEY().
+ */
+static void
+eddsa_convert(const eddsa_pk_t *k)
+{
+ EVP_PKEY *pkey = NULL;
+ eddsa_pk_t *pk = NULL;
+ int r;
+
+ if ((pkey = eddsa_pk_to_EVP_PKEY(k)) == NULL ||
+ (pk = eddsa_pk_new()) == NULL)
+ goto out;
+
+ r = eddsa_pk_from_EVP_PKEY(pk, pkey);
+ consume(&r, sizeof(r));
+out:
+ if (pk)
+ eddsa_pk_free(&pk);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+}
+
+void
+test(const struct param *p)
+{
+ fido_assert_t *assert = NULL;
+ es256_pk_t *es256_pk = NULL;
+ es384_pk_t *es384_pk = NULL;
+ rs256_pk_t *rs256_pk = NULL;
+ eddsa_pk_t *eddsa_pk = NULL;
+ uint8_t flags;
+ uint32_t sigcount;
+ int cose_alg = 0;
+ void *pk;
+
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ switch (p->type & 3) {
+ case 0:
+ cose_alg = COSE_ES256;
+
+ if ((es256_pk = es256_pk_new()) == NULL)
+ return;
+
+ es256_pk_from_ptr(es256_pk, p->es256.body, p->es256.len);
+ pk = es256_pk;
+
+ es256_convert(pk);
+
+ break;
+ case 1:
+ cose_alg = COSE_RS256;
+
+ if ((rs256_pk = rs256_pk_new()) == NULL)
+ return;
+
+ rs256_pk_from_ptr(rs256_pk, p->rs256.body, p->rs256.len);
+ pk = rs256_pk;
+
+ rs256_convert(pk);
+
+ break;
+ case 2:
+ cose_alg = COSE_ES384;
+
+ if ((es384_pk = es384_pk_new()) == NULL)
+ return;
+
+ /* XXX reuse p->es256 as es384 */
+ es384_pk_from_ptr(es384_pk, p->es256.body, p->es256.len);
+ pk = es384_pk;
+
+ es384_convert(pk);
+
+ break;
+ default:
+ cose_alg = COSE_EDDSA;
+
+ if ((eddsa_pk = eddsa_pk_new()) == NULL)
+ return;
+
+ eddsa_pk_from_ptr(eddsa_pk, p->eddsa.body, p->eddsa.len);
+ pk = eddsa_pk;
+
+ eddsa_convert(pk);
+
+ break;
+ }
+
+ if ((assert = fido_assert_new()) == NULL)
+ goto out;
+
+ set_wire_data(p->wire_data.body, p->wire_data.len);
+
+ get_assert(assert, p->opt, &p->cdh, p->rp_id, p->ext, p->up, p->uv,
+ p->pin, p->cred_count, &p->cred);
+
+ /* XXX +1 on purpose */
+ for (size_t i = 0; i <= fido_assert_count(assert); i++) {
+ verify_assert(cose_alg,
+ fido_assert_clientdata_hash_ptr(assert),
+ fido_assert_clientdata_hash_len(assert),
+ fido_assert_rp_id(assert),
+ fido_assert_authdata_ptr(assert, i),
+ fido_assert_authdata_len(assert, i),
+ fido_assert_sig_ptr(assert, i),
+ fido_assert_sig_len(assert, i), p->up, p->uv, p->ext, pk);
+ consume(fido_assert_authdata_raw_ptr(assert, i),
+ fido_assert_authdata_raw_len(assert, i));
+ consume(fido_assert_id_ptr(assert, i),
+ fido_assert_id_len(assert, i));
+ consume(fido_assert_user_id_ptr(assert, i),
+ fido_assert_user_id_len(assert, i));
+ consume(fido_assert_hmac_secret_ptr(assert, i),
+ fido_assert_hmac_secret_len(assert, i));
+ consume_str(fido_assert_user_icon(assert, i));
+ consume_str(fido_assert_user_name(assert, i));
+ consume_str(fido_assert_user_display_name(assert, i));
+ consume(fido_assert_blob_ptr(assert, i),
+ fido_assert_blob_len(assert, i));
+ consume(fido_assert_largeblob_key_ptr(assert, i),
+ fido_assert_largeblob_key_len(assert, i));
+ flags = fido_assert_flags(assert, i);
+ consume(&flags, sizeof(flags));
+ sigcount = fido_assert_sigcount(assert, i);
+ consume(&sigcount, sizeof(sigcount));
+ }
+
+out:
+ es256_pk_free(&es256_pk);
+ es384_pk_free(&es384_pk);
+ rs256_pk_free(&rs256_pk);
+ eddsa_pk_free(&eddsa_pk);
+
+ fido_assert_free(&assert);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_byte(&p->uv);
+ mutate_byte(&p->up);
+ mutate_byte(&p->opt);
+ mutate_byte(&p->type);
+ mutate_byte(&p->cred_count);
+ mutate_int(&p->ext);
+ mutate_blob(&p->rs256);
+ mutate_blob(&p->es256);
+ mutate_blob(&p->eddsa);
+ mutate_blob(&p->cred);
+ mutate_blob(&p->cdh);
+ mutate_string(p->rp_id);
+ mutate_string(p->pin);
+ }
+
+ if (flags & MUTATE_WIREDATA) {
+ if (p->opt & 1) {
+ p->wire_data.len = sizeof(dummy_wire_data_u2f);
+ memcpy(&p->wire_data.body, &dummy_wire_data_u2f,
+ p->wire_data.len);
+ } else {
+ p->wire_data.len = sizeof(dummy_wire_data_fido);
+ memcpy(&p->wire_data.body, &dummy_wire_data_fido,
+ p->wire_data.len);
+ }
+ mutate_blob(&p->wire_data);
+ }
+}
diff --git a/fuzz/fuzz_bio.c b/fuzz/fuzz_bio.c
new file mode 100644
index 0000000..0c6b12c
--- /dev/null
+++ b/fuzz/fuzz_bio.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mutator_aux.h"
+#include "wiredata_fido2.h"
+#include "dummy.h"
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+/* Parameter set defining a FIDO2 credential management operation. */
+struct param {
+ char pin[MAXSTR];
+ char name[MAXSTR];
+ int seed;
+ struct blob id;
+ struct blob info_wire_data;
+ struct blob enroll_wire_data;
+ struct blob list_wire_data;
+ struct blob set_name_wire_data;
+ struct blob remove_wire_data;
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'getFingerprintSensorInfo' bio enrollment command.
+ */
+static const uint8_t dummy_info_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_BIO_INFO,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with FIDO2
+ * 'enrollBegin' + 'enrollCaptureNextSample' bio enrollment commands.
+ */
+static const uint8_t dummy_enroll_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_BIO_ENROLL,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'enumerateEnrollments' bio enrollment command.
+ */
+static const uint8_t dummy_list_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_BIO_ENUM,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'setFriendlyName' bio enrollment command.
+ */
+static const uint8_t dummy_set_name_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_STATUS,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'removeEnrollment' bio enrollment command.
+ */
+static const uint8_t dummy_remove_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_STATUS,
+};
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 9 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_int(v[0], &p->seed) < 0 ||
+ unpack_string(v[1], p->pin) < 0 ||
+ unpack_string(v[2], p->name) < 0 ||
+ unpack_blob(v[3], &p->id) < 0 ||
+ unpack_blob(v[4], &p->info_wire_data) < 0 ||
+ unpack_blob(v[5], &p->enroll_wire_data) < 0 ||
+ unpack_blob(v[6], &p->list_wire_data) < 0 ||
+ unpack_blob(v[7], &p->set_name_wire_data) < 0 ||
+ unpack_blob(v[8], &p->remove_wire_data) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[9], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(9)) == NULL ||
+ (argv[0] = pack_int(p->seed)) == NULL ||
+ (argv[1] = pack_string(p->pin)) == NULL ||
+ (argv[2] = pack_string(p->name)) == NULL ||
+ (argv[3] = pack_blob(&p->id)) == NULL ||
+ (argv[4] = pack_blob(&p->info_wire_data)) == NULL ||
+ (argv[5] = pack_blob(&p->enroll_wire_data)) == NULL ||
+ (argv[6] = pack_blob(&p->list_wire_data)) == NULL ||
+ (argv[7] = pack_blob(&p->set_name_wire_data)) == NULL ||
+ (argv[8] = pack_blob(&p->remove_wire_data)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 9; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 9; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin));
+ strlcpy(dummy.name, dummy_name, sizeof(dummy.name));
+
+ dummy.info_wire_data.len = sizeof(dummy_info_wire_data);
+ dummy.enroll_wire_data.len = sizeof(dummy_enroll_wire_data);
+ dummy.list_wire_data.len = sizeof(dummy_list_wire_data);
+ dummy.set_name_wire_data.len = sizeof(dummy_set_name_wire_data);
+ dummy.remove_wire_data.len = sizeof(dummy_remove_wire_data);
+ dummy.id.len = sizeof(dummy_id);
+
+ memcpy(&dummy.info_wire_data.body, &dummy_info_wire_data,
+ dummy.info_wire_data.len);
+ memcpy(&dummy.enroll_wire_data.body, &dummy_enroll_wire_data,
+ dummy.enroll_wire_data.len);
+ memcpy(&dummy.list_wire_data.body, &dummy_list_wire_data,
+ dummy.list_wire_data.len);
+ memcpy(&dummy.set_name_wire_data.body, &dummy_set_name_wire_data,
+ dummy.set_name_wire_data.len);
+ memcpy(&dummy.remove_wire_data.body, &dummy_remove_wire_data,
+ dummy.remove_wire_data.len);
+ memcpy(&dummy.id.body, &dummy_id, dummy.id.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static fido_dev_t *
+prepare_dev(void)
+{
+ fido_dev_t *dev;
+ bool x;
+
+ if ((dev = open_dev(0)) == NULL)
+ return NULL;
+
+ x = fido_dev_is_fido2(dev);
+ consume(&x, sizeof(x));
+ x = fido_dev_supports_pin(dev);
+ consume(&x, sizeof(x));
+ x = fido_dev_has_pin(dev);
+ consume(&x, sizeof(x));
+ x = fido_dev_supports_uv(dev);
+ consume(&x, sizeof(x));
+ x = fido_dev_has_uv(dev);
+ consume(&x, sizeof(x));
+
+ return dev;
+}
+
+static void
+get_info(const struct param *p)
+{
+ fido_dev_t *dev = NULL;
+ fido_bio_info_t *i = NULL;
+ uint8_t type;
+ uint8_t max_samples;
+ int r;
+
+ set_wire_data(p->info_wire_data.body, p->info_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL || (i = fido_bio_info_new()) == NULL)
+ goto done;
+
+ r = fido_bio_dev_get_info(dev, i);
+ consume_str(fido_strerr(r));
+
+ type = fido_bio_info_type(i);
+ max_samples = fido_bio_info_max_samples(i);
+ consume(&type, sizeof(type));
+ consume(&max_samples, sizeof(max_samples));
+
+done:
+ if (dev)
+ fido_dev_close(dev);
+
+ fido_dev_free(&dev);
+ fido_bio_info_free(&i);
+}
+
+static void
+consume_template(const fido_bio_template_t *t)
+{
+ consume_str(fido_bio_template_name(t));
+ consume(fido_bio_template_id_ptr(t), fido_bio_template_id_len(t));
+}
+
+static void
+consume_enroll(fido_bio_enroll_t *e)
+{
+ uint8_t last_status;
+ uint8_t remaining_samples;
+
+ last_status = fido_bio_enroll_last_status(e);
+ remaining_samples = fido_bio_enroll_remaining_samples(e);
+ consume(&last_status, sizeof(last_status));
+ consume(&remaining_samples, sizeof(remaining_samples));
+}
+
+static void
+enroll(const struct param *p)
+{
+ fido_dev_t *dev = NULL;
+ fido_bio_template_t *t = NULL;
+ fido_bio_enroll_t *e = NULL;
+ size_t cnt = 0;
+
+ set_wire_data(p->enroll_wire_data.body, p->enroll_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL ||
+ (t = fido_bio_template_new()) == NULL ||
+ (e = fido_bio_enroll_new()) == NULL)
+ goto done;
+
+ fido_bio_dev_enroll_begin(dev, t, e, (uint32_t)p->seed, p->pin);
+
+ consume_template(t);
+ consume_enroll(e);
+
+ while (fido_bio_enroll_remaining_samples(e) > 0 && cnt++ < 5) {
+ fido_bio_dev_enroll_continue(dev, t, e, p->seed);
+ consume_template(t);
+ consume_enroll(e);
+ }
+
+done:
+ if (dev)
+ fido_dev_close(dev);
+
+ fido_dev_free(&dev);
+ fido_bio_template_free(&t);
+ fido_bio_enroll_free(&e);
+}
+
+static void
+list(const struct param *p)
+{
+ fido_dev_t *dev = NULL;
+ fido_bio_template_array_t *ta = NULL;
+ const fido_bio_template_t *t = NULL;
+
+ set_wire_data(p->list_wire_data.body, p->list_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL ||
+ (ta = fido_bio_template_array_new()) == NULL)
+ goto done;
+
+ fido_bio_dev_get_template_array(dev, ta, p->pin);
+
+ /* +1 on purpose */
+ for (size_t i = 0; i < fido_bio_template_array_count(ta) + 1; i++)
+ if ((t = fido_bio_template(ta, i)) != NULL)
+ consume_template(t);
+
+done:
+ if (dev)
+ fido_dev_close(dev);
+
+ fido_dev_free(&dev);
+ fido_bio_template_array_free(&ta);
+}
+
+static void
+set_name(const struct param *p)
+{
+ fido_dev_t *dev = NULL;
+ fido_bio_template_t *t = NULL;
+
+ set_wire_data(p->set_name_wire_data.body, p->set_name_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL ||
+ (t = fido_bio_template_new()) == NULL)
+ goto done;
+
+ fido_bio_template_set_name(t, p->name);
+ fido_bio_template_set_id(t, p->id.body, p->id.len);
+ consume_template(t);
+
+ fido_bio_dev_set_template_name(dev, t, p->pin);
+
+done:
+ if (dev)
+ fido_dev_close(dev);
+
+ fido_dev_free(&dev);
+ fido_bio_template_free(&t);
+}
+
+static void
+del(const struct param *p)
+{
+ fido_dev_t *dev = NULL;
+ fido_bio_template_t *t = NULL;
+ int r;
+
+ set_wire_data(p->remove_wire_data.body, p->remove_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL ||
+ (t = fido_bio_template_new()) == NULL)
+ goto done;
+
+ r = fido_bio_template_set_id(t, p->id.body, p->id.len);
+ consume_template(t);
+ consume_str(fido_strerr(r));
+
+ fido_bio_dev_enroll_remove(dev, t, p->pin);
+
+done:
+ if (dev)
+ fido_dev_close(dev);
+
+ fido_dev_free(&dev);
+ fido_bio_template_free(&t);
+}
+
+void
+test(const struct param *p)
+{
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ get_info(p);
+ enroll(p);
+ list(p);
+ set_name(p);
+ del(p);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_blob(&p->id);
+ mutate_string(p->pin);
+ mutate_string(p->name);
+ }
+
+ if (flags & MUTATE_WIREDATA) {
+ mutate_blob(&p->info_wire_data);
+ mutate_blob(&p->enroll_wire_data);
+ mutate_blob(&p->list_wire_data);
+ mutate_blob(&p->set_name_wire_data);
+ mutate_blob(&p->remove_wire_data);
+ }
+}
diff --git a/fuzz/fuzz_cred.c b/fuzz/fuzz_cred.c
new file mode 100644
index 0000000..497298f
--- /dev/null
+++ b/fuzz/fuzz_cred.c
@@ -0,0 +1,482 @@
+/*
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mutator_aux.h"
+#include "wiredata_fido2.h"
+#include "wiredata_u2f.h"
+#include "dummy.h"
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+/* Parameter set defining a FIDO2 make credential operation. */
+struct param {
+ char pin[MAXSTR];
+ char rp_id[MAXSTR];
+ char rp_name[MAXSTR];
+ char user_icon[MAXSTR];
+ char user_name[MAXSTR];
+ char user_nick[MAXSTR];
+ int ext;
+ int seed;
+ struct blob cdh;
+ struct blob excl_cred;
+ struct blob user_id;
+ struct blob wire_data;
+ uint8_t excl_count;
+ uint8_t rk;
+ uint8_t type;
+ uint8_t opt;
+ uint8_t uv;
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * make credential using the example parameters above.
+ */
+static const uint8_t dummy_wire_data_fido[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_CBOR_CRED,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a U2F
+ * registration using the example parameters above.
+ */
+static const uint8_t dummy_wire_data_u2f[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_6985,
+ WIREDATA_CTAP_U2F_REGISTER,
+};
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 17 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_byte(v[0], &p->rk) < 0 ||
+ unpack_byte(v[1], &p->type) < 0 ||
+ unpack_byte(v[2], &p->opt) < 0 ||
+ unpack_byte(v[3], &p->uv) < 0 ||
+ unpack_byte(v[4], &p->excl_count) < 0 ||
+ unpack_int(v[5], &p->ext) < 0 ||
+ unpack_int(v[6], &p->seed) < 0 ||
+ unpack_string(v[7], p->pin) < 0 ||
+ unpack_string(v[8], p->rp_id) < 0 ||
+ unpack_string(v[9], p->rp_name) < 0 ||
+ unpack_string(v[10], p->user_icon) < 0 ||
+ unpack_string(v[11], p->user_name) < 0 ||
+ unpack_string(v[12], p->user_nick) < 0 ||
+ unpack_blob(v[13], &p->cdh) < 0 ||
+ unpack_blob(v[14], &p->user_id) < 0 ||
+ unpack_blob(v[15], &p->wire_data) < 0 ||
+ unpack_blob(v[16], &p->excl_cred) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[17], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(17)) == NULL ||
+ (argv[0] = pack_byte(p->rk)) == NULL ||
+ (argv[1] = pack_byte(p->type)) == NULL ||
+ (argv[2] = pack_byte(p->opt)) == NULL ||
+ (argv[3] = pack_byte(p->uv)) == NULL ||
+ (argv[4] = pack_byte(p->excl_count)) == NULL ||
+ (argv[5] = pack_int(p->ext)) == NULL ||
+ (argv[6] = pack_int(p->seed)) == NULL ||
+ (argv[7] = pack_string(p->pin)) == NULL ||
+ (argv[8] = pack_string(p->rp_id)) == NULL ||
+ (argv[9] = pack_string(p->rp_name)) == NULL ||
+ (argv[10] = pack_string(p->user_icon)) == NULL ||
+ (argv[11] = pack_string(p->user_name)) == NULL ||
+ (argv[12] = pack_string(p->user_nick)) == NULL ||
+ (argv[13] = pack_blob(&p->cdh)) == NULL ||
+ (argv[14] = pack_blob(&p->user_id)) == NULL ||
+ (argv[15] = pack_blob(&p->wire_data)) == NULL ||
+ (argv[16] = pack_blob(&p->excl_cred)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 17; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 17; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ dummy.type = 1;
+ dummy.ext = FIDO_EXT_HMAC_SECRET;
+
+ strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin));
+ strlcpy(dummy.rp_id, dummy_rp_id, sizeof(dummy.rp_id));
+ strlcpy(dummy.rp_name, dummy_rp_name, sizeof(dummy.rp_name));
+ strlcpy(dummy.user_icon, dummy_user_icon, sizeof(dummy.user_icon));
+ strlcpy(dummy.user_name, dummy_user_name, sizeof(dummy.user_name));
+ strlcpy(dummy.user_nick, dummy_user_nick, sizeof(dummy.user_nick));
+
+ dummy.cdh.len = sizeof(dummy_cdh);
+ dummy.user_id.len = sizeof(dummy_user_id);
+ dummy.wire_data.len = sizeof(dummy_wire_data_fido);
+
+ memcpy(&dummy.cdh.body, &dummy_cdh, dummy.cdh.len);
+ memcpy(&dummy.user_id.body, &dummy_user_id, dummy.user_id.len);
+ memcpy(&dummy.wire_data.body, &dummy_wire_data_fido,
+ dummy.wire_data.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static void
+make_cred(fido_cred_t *cred, uint8_t opt, int type, const struct blob *cdh,
+ const char *rp_id, const char *rp_name, const struct blob *user_id,
+ const char *user_name, const char *user_nick, const char *user_icon,
+ int ext, uint8_t rk, uint8_t uv, const char *pin, uint8_t excl_count,
+ const struct blob *excl_cred)
+{
+ fido_dev_t *dev;
+
+ if ((dev = open_dev(opt & 2)) == NULL)
+ return;
+ if (opt & 1)
+ fido_dev_force_u2f(dev);
+
+ for (uint8_t i = 0; i < excl_count; i++)
+ fido_cred_exclude(cred, excl_cred->body, excl_cred->len);
+
+ fido_cred_set_type(cred, type);
+ fido_cred_set_clientdata_hash(cred, cdh->body, cdh->len);
+ fido_cred_set_rp(cred, rp_id, rp_name);
+ fido_cred_set_user(cred, user_id->body, user_id->len, user_name,
+ user_nick, user_icon);
+
+ if (ext & FIDO_EXT_HMAC_SECRET)
+ fido_cred_set_extensions(cred, FIDO_EXT_HMAC_SECRET);
+ if (ext & FIDO_EXT_CRED_BLOB)
+ fido_cred_set_blob(cred, user_id->body, user_id->len);
+ if (ext & FIDO_EXT_LARGEBLOB_KEY)
+ fido_cred_set_extensions(cred, FIDO_EXT_LARGEBLOB_KEY);
+ if (ext & FIDO_EXT_MINPINLEN)
+ fido_cred_set_pin_minlen(cred, strlen(pin));
+
+ if (rk & 1)
+ fido_cred_set_rk(cred, FIDO_OPT_TRUE);
+ if (uv & 1)
+ fido_cred_set_uv(cred, FIDO_OPT_TRUE);
+ if (user_id->len)
+ fido_cred_set_prot(cred, user_id->body[0] & 0x03);
+
+ /* repeat memory operations to trigger reallocation paths */
+ fido_cred_set_type(cred, type);
+ fido_cred_set_clientdata_hash(cred, cdh->body, cdh->len);
+ fido_cred_set_rp(cred, rp_id, rp_name);
+ fido_cred_set_user(cred, user_id->body, user_id->len, user_name,
+ user_nick, user_icon);
+
+ if (strlen(pin) == 0)
+ pin = NULL;
+
+ fido_dev_make_cred(dev, cred, (opt & 1) ? NULL : pin);
+
+ fido_dev_cancel(dev);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+verify_cred(int type, const unsigned char *cdh_ptr, size_t cdh_len,
+ const char *rp_id, const char *rp_name, const unsigned char *authdata_ptr,
+ size_t authdata_len, const unsigned char *authdata_raw_ptr,
+ size_t authdata_raw_len, int ext, uint8_t rk, uint8_t uv,
+ const unsigned char *x5c_ptr, size_t x5c_len, const unsigned char *sig_ptr,
+ size_t sig_len, const unsigned char *attstmt_ptr, size_t attstmt_len,
+ const char *fmt, int prot, size_t minpinlen)
+{
+ fido_cred_t *cred;
+ uint8_t flags;
+ uint32_t sigcount;
+ int r;
+
+ if ((cred = fido_cred_new()) == NULL)
+ return;
+
+ fido_cred_set_type(cred, type);
+ fido_cred_set_clientdata_hash(cred, cdh_ptr, cdh_len);
+ fido_cred_set_rp(cred, rp_id, rp_name);
+ consume(authdata_ptr, authdata_len);
+ consume(authdata_raw_ptr, authdata_raw_len);
+ consume(x5c_ptr, x5c_len);
+ consume(sig_ptr, sig_len);
+ consume(attstmt_ptr, attstmt_len);
+ if (fido_cred_set_authdata(cred, authdata_ptr, authdata_len) != FIDO_OK)
+ fido_cred_set_authdata_raw(cred, authdata_raw_ptr,
+ authdata_raw_len);
+ fido_cred_set_extensions(cred, ext);
+ if (fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len) != FIDO_OK) {
+ fido_cred_set_x509(cred, x5c_ptr, x5c_len);
+ fido_cred_set_sig(cred, sig_ptr, sig_len);
+ }
+ fido_cred_set_prot(cred, prot);
+ fido_cred_set_pin_minlen(cred, minpinlen);
+
+ if (rk & 1)
+ fido_cred_set_rk(cred, FIDO_OPT_TRUE);
+ if (uv & 1)
+ fido_cred_set_uv(cred, FIDO_OPT_TRUE);
+ if (fmt)
+ fido_cred_set_fmt(cred, fmt);
+
+ /* repeat memory operations to trigger reallocation paths */
+ if (fido_cred_set_authdata(cred, authdata_ptr, authdata_len) != FIDO_OK)
+ fido_cred_set_authdata_raw(cred, authdata_raw_ptr,
+ authdata_raw_len);
+ if (fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len) != FIDO_OK) {
+ fido_cred_set_x509(cred, x5c_ptr, x5c_len);
+ fido_cred_set_sig(cred, sig_ptr, sig_len);
+ }
+ fido_cred_set_x509(cred, x5c_ptr, x5c_len);
+ fido_cred_set_sig(cred, sig_ptr, sig_len);
+
+ r = fido_cred_verify(cred);
+ consume(&r, sizeof(r));
+ r = fido_cred_verify_self(cred);
+ consume(&r, sizeof(r));
+
+ consume(fido_cred_pubkey_ptr(cred), fido_cred_pubkey_len(cred));
+ consume(fido_cred_id_ptr(cred), fido_cred_id_len(cred));
+ consume(fido_cred_aaguid_ptr(cred), fido_cred_aaguid_len(cred));
+ consume(fido_cred_user_id_ptr(cred), fido_cred_user_id_len(cred));
+ consume_str(fido_cred_user_name(cred));
+ consume_str(fido_cred_display_name(cred));
+ consume(fido_cred_largeblob_key_ptr(cred),
+ fido_cred_largeblob_key_len(cred));
+
+ flags = fido_cred_flags(cred);
+ consume(&flags, sizeof(flags));
+ sigcount = fido_cred_sigcount(cred);
+ consume(&sigcount, sizeof(sigcount));
+ type = fido_cred_type(cred);
+ consume(&type, sizeof(type));
+ minpinlen = fido_cred_pin_minlen(cred);
+ consume(&minpinlen, sizeof(minpinlen));
+
+ fido_cred_free(&cred);
+}
+
+static void
+test_cred(const struct param *p)
+{
+ fido_cred_t *cred = NULL;
+ int cose_alg = 0;
+
+ if ((cred = fido_cred_new()) == NULL)
+ return;
+
+ switch (p->type & 3) {
+ case 0:
+ cose_alg = COSE_ES256;
+ break;
+ case 1:
+ cose_alg = COSE_RS256;
+ break;
+ case 2:
+ cose_alg = COSE_ES384;
+ break;
+ default:
+ cose_alg = COSE_EDDSA;
+ break;
+ }
+
+ set_wire_data(p->wire_data.body, p->wire_data.len);
+
+ make_cred(cred, p->opt, cose_alg, &p->cdh, p->rp_id, p->rp_name,
+ &p->user_id, p->user_name, p->user_nick, p->user_icon, p->ext,
+ p->rk, p->uv, p->pin, p->excl_count, &p->excl_cred);
+
+ verify_cred(cose_alg,
+ fido_cred_clientdata_hash_ptr(cred),
+ fido_cred_clientdata_hash_len(cred), fido_cred_rp_id(cred),
+ fido_cred_rp_name(cred), fido_cred_authdata_ptr(cred),
+ fido_cred_authdata_len(cred), fido_cred_authdata_raw_ptr(cred),
+ fido_cred_authdata_raw_len(cred), p->ext, p->rk, p->uv,
+ fido_cred_x5c_ptr(cred), fido_cred_x5c_len(cred),
+ fido_cred_sig_ptr(cred), fido_cred_sig_len(cred),
+ fido_cred_attstmt_ptr(cred), fido_cred_attstmt_len(cred),
+ fido_cred_fmt(cred), fido_cred_prot(cred),
+ fido_cred_pin_minlen(cred));
+
+ fido_cred_free(&cred);
+}
+
+static void
+test_touch(const struct param *p)
+{
+ fido_dev_t *dev;
+ int r;
+ int touched;
+
+ set_wire_data(p->wire_data.body, p->wire_data.len);
+
+ if ((dev = open_dev(p->opt & 2)) == NULL)
+ return;
+ if (p->opt & 1)
+ fido_dev_force_u2f(dev);
+
+ r = fido_dev_get_touch_begin(dev);
+ consume_str(fido_strerr(r));
+ r = fido_dev_get_touch_status(dev, &touched, -1);
+ consume_str(fido_strerr(r));
+ consume(&touched, sizeof(touched));
+
+ fido_dev_cancel(dev);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+test_misc(const struct param *p)
+{
+ fido_cred_t *cred = NULL;
+
+ if ((cred = fido_cred_new()) == NULL)
+ return;
+
+ /* reuse user id as credential id */
+ fido_cred_set_id(cred, p->user_id.body, p->user_id.len);
+ consume(fido_cred_id_ptr(cred), fido_cred_id_len(cred));
+ fido_cred_free(&cred);
+}
+
+void
+test(const struct param *p)
+{
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ test_cred(p);
+ test_touch(p);
+ test_misc(p);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_byte(&p->rk);
+ mutate_byte(&p->type);
+ mutate_byte(&p->opt);
+ mutate_byte(&p->uv);
+ mutate_byte(&p->excl_count);
+ mutate_int(&p->ext);
+ mutate_blob(&p->cdh);
+ mutate_blob(&p->user_id);
+ mutate_blob(&p->excl_cred);
+ mutate_string(p->pin);
+ mutate_string(p->user_icon);
+ mutate_string(p->user_name);
+ mutate_string(p->user_nick);
+ mutate_string(p->rp_id);
+ mutate_string(p->rp_name);
+ }
+
+ if (flags & MUTATE_WIREDATA) {
+ if (p->opt & 1) {
+ p->wire_data.len = sizeof(dummy_wire_data_u2f);
+ memcpy(&p->wire_data.body, &dummy_wire_data_u2f,
+ p->wire_data.len);
+ } else {
+ p->wire_data.len = sizeof(dummy_wire_data_fido);
+ memcpy(&p->wire_data.body, &dummy_wire_data_fido,
+ p->wire_data.len);
+ }
+ mutate_blob(&p->wire_data);
+ }
+}
diff --git a/fuzz/fuzz_credman.c b/fuzz/fuzz_credman.c
new file mode 100644
index 0000000..ef21475
--- /dev/null
+++ b/fuzz/fuzz_credman.c
@@ -0,0 +1,407 @@
+/*
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mutator_aux.h"
+#include "wiredata_fido2.h"
+#include "dummy.h"
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+/* Parameter set defining a FIDO2 credential management operation. */
+struct param {
+ char pin[MAXSTR];
+ char rp_id[MAXSTR];
+ int seed;
+ struct blob cred_id;
+ struct blob del_wire_data;
+ struct blob meta_wire_data;
+ struct blob rk_wire_data;
+ struct blob rp_wire_data;
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'getCredsMetadata' credential management command.
+ */
+static const uint8_t dummy_meta_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_CREDMAN_META,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'enumerateRPsBegin' credential management command.
+ */
+static const uint8_t dummy_rp_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_CREDMAN_RPLIST,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'enumerateCredentialsBegin' credential management command.
+ */
+static const uint8_t dummy_rk_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_CREDMAN_RKLIST,
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'deleteCredential' credential management command.
+ */
+static const uint8_t dummy_del_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_STATUS,
+};
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 8 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_int(v[0], &p->seed) < 0 ||
+ unpack_string(v[1], p->pin) < 0 ||
+ unpack_string(v[2], p->rp_id) < 0 ||
+ unpack_blob(v[3], &p->cred_id) < 0 ||
+ unpack_blob(v[4], &p->meta_wire_data) < 0 ||
+ unpack_blob(v[5], &p->rp_wire_data) < 0 ||
+ unpack_blob(v[6], &p->rk_wire_data) < 0 ||
+ unpack_blob(v[7], &p->del_wire_data) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[8], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(8)) == NULL ||
+ (argv[0] = pack_int(p->seed)) == NULL ||
+ (argv[1] = pack_string(p->pin)) == NULL ||
+ (argv[2] = pack_string(p->rp_id)) == NULL ||
+ (argv[3] = pack_blob(&p->cred_id)) == NULL ||
+ (argv[4] = pack_blob(&p->meta_wire_data)) == NULL ||
+ (argv[5] = pack_blob(&p->rp_wire_data)) == NULL ||
+ (argv[6] = pack_blob(&p->rk_wire_data)) == NULL ||
+ (argv[7] = pack_blob(&p->del_wire_data)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 8; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 8; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin));
+ strlcpy(dummy.rp_id, dummy_rp_id, sizeof(dummy.rp_id));
+
+ dummy.meta_wire_data.len = sizeof(dummy_meta_wire_data);
+ dummy.rp_wire_data.len = sizeof(dummy_rp_wire_data);
+ dummy.rk_wire_data.len = sizeof(dummy_rk_wire_data);
+ dummy.del_wire_data.len = sizeof(dummy_del_wire_data);
+ dummy.cred_id.len = sizeof(dummy_cred_id);
+
+ memcpy(&dummy.meta_wire_data.body, &dummy_meta_wire_data,
+ dummy.meta_wire_data.len);
+ memcpy(&dummy.rp_wire_data.body, &dummy_rp_wire_data,
+ dummy.rp_wire_data.len);
+ memcpy(&dummy.rk_wire_data.body, &dummy_rk_wire_data,
+ dummy.rk_wire_data.len);
+ memcpy(&dummy.del_wire_data.body, &dummy_del_wire_data,
+ dummy.del_wire_data.len);
+ memcpy(&dummy.cred_id.body, &dummy_cred_id, dummy.cred_id.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static fido_dev_t *
+prepare_dev(void)
+{
+ fido_dev_t *dev;
+ bool x;
+
+ if ((dev = open_dev(0)) == NULL)
+ return NULL;
+
+ x = fido_dev_is_fido2(dev);
+ consume(&x, sizeof(x));
+ x = fido_dev_supports_cred_prot(dev);
+ consume(&x, sizeof(x));
+ x = fido_dev_supports_credman(dev);
+ consume(&x, sizeof(x));
+
+ return dev;
+}
+
+static void
+get_metadata(const struct param *p)
+{
+ fido_dev_t *dev;
+ fido_credman_metadata_t *metadata;
+ uint64_t existing;
+ uint64_t remaining;
+
+ set_wire_data(p->meta_wire_data.body, p->meta_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL)
+ return;
+
+ if ((metadata = fido_credman_metadata_new()) == NULL) {
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ return;
+ }
+
+ fido_credman_get_dev_metadata(dev, metadata, p->pin);
+
+ existing = fido_credman_rk_existing(metadata);
+ remaining = fido_credman_rk_remaining(metadata);
+ consume(&existing, sizeof(existing));
+ consume(&remaining, sizeof(remaining));
+
+ fido_credman_metadata_free(&metadata);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+get_rp_list(const struct param *p)
+{
+ fido_dev_t *dev;
+ fido_credman_rp_t *rp;
+
+ set_wire_data(p->rp_wire_data.body, p->rp_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL)
+ return;
+
+ if ((rp = fido_credman_rp_new()) == NULL) {
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ return;
+ }
+
+ fido_credman_get_dev_rp(dev, rp, p->pin);
+
+ /* +1 on purpose */
+ for (size_t i = 0; i < fido_credman_rp_count(rp) + 1; i++) {
+ consume(fido_credman_rp_id_hash_ptr(rp, i),
+ fido_credman_rp_id_hash_len(rp, i));
+ consume_str(fido_credman_rp_id(rp, i));
+ consume_str(fido_credman_rp_name(rp, i));
+ }
+
+ fido_credman_rp_free(&rp);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+get_rk_list(const struct param *p)
+{
+ fido_dev_t *dev;
+ fido_credman_rk_t *rk;
+ const fido_cred_t *cred;
+ int val;
+
+ set_wire_data(p->rk_wire_data.body, p->rk_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL)
+ return;
+
+ if ((rk = fido_credman_rk_new()) == NULL) {
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ return;
+ }
+
+ fido_credman_get_dev_rk(dev, p->rp_id, rk, p->pin);
+
+ /* +1 on purpose */
+ for (size_t i = 0; i < fido_credman_rk_count(rk) + 1; i++) {
+ if ((cred = fido_credman_rk(rk, i)) == NULL) {
+ assert(i >= fido_credman_rk_count(rk));
+ continue;
+ }
+ val = fido_cred_type(cred);
+ consume(&val, sizeof(val));
+ consume(fido_cred_id_ptr(cred), fido_cred_id_len(cred));
+ consume(fido_cred_pubkey_ptr(cred), fido_cred_pubkey_len(cred));
+ consume(fido_cred_user_id_ptr(cred),
+ fido_cred_user_id_len(cred));
+ consume_str(fido_cred_user_name(cred));
+ consume_str(fido_cred_display_name(cred));
+ val = fido_cred_prot(cred);
+ consume(&val, sizeof(val));
+ }
+
+ fido_credman_rk_free(&rk);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+del_rk(const struct param *p)
+{
+ fido_dev_t *dev;
+
+ set_wire_data(p->del_wire_data.body, p->del_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL)
+ return;
+
+ fido_credman_del_dev_rk(dev, p->cred_id.body, p->cred_id.len, p->pin);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+set_rk(const struct param *p)
+{
+ fido_dev_t *dev = NULL;
+ fido_cred_t *cred = NULL;
+ const char *pin = p->pin;
+ int r0, r1, r2;
+
+ set_wire_data(p->del_wire_data.body, p->del_wire_data.len);
+
+ if ((dev = prepare_dev()) == NULL)
+ return;
+ if ((cred = fido_cred_new()) == NULL)
+ goto out;
+ r0 = fido_cred_set_id(cred, p->cred_id.body, p->cred_id.len);
+ r1 = fido_cred_set_user(cred, p->cred_id.body, p->cred_id.len, p->rp_id,
+ NULL, NULL);
+ if (strlen(pin) == 0)
+ pin = NULL;
+ r2 = fido_credman_set_dev_rk(dev, cred, pin);
+ consume(&r0, sizeof(r0));
+ consume(&r1, sizeof(r1));
+ consume(&r2, sizeof(r2));
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_cred_free(&cred);
+}
+
+void
+test(const struct param *p)
+{
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ get_metadata(p);
+ get_rp_list(p);
+ get_rk_list(p);
+ del_rk(p);
+ set_rk(p);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_blob(&p->cred_id);
+ mutate_string(p->pin);
+ mutate_string(p->rp_id);
+ }
+
+ if (flags & MUTATE_WIREDATA) {
+ mutate_blob(&p->meta_wire_data);
+ mutate_blob(&p->rp_wire_data);
+ mutate_blob(&p->rk_wire_data);
+ mutate_blob(&p->del_wire_data);
+ }
+}
diff --git a/fuzz/fuzz_hid.c b/fuzz/fuzz_hid.c
new file mode 100644
index 0000000..daaadad
--- /dev/null
+++ b/fuzz/fuzz_hid.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2020-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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "mutator_aux.h"
+#include "dummy.h"
+
+extern int fido_hid_get_usage(const uint8_t *, size_t, uint32_t *);
+extern int fido_hid_get_report_len(const uint8_t *, size_t, size_t *, size_t *);
+extern void set_udev_parameters(const char *, const struct blob *);
+
+struct param {
+ int seed;
+ char uevent[MAXSTR];
+ struct blob report_descriptor;
+ struct blob netlink_wiredata;
+};
+
+/*
+ * Sample HID report descriptor from the FIDO HID interface of a YubiKey 5.
+ */
+static const uint8_t dummy_report_descriptor[] = {
+ 0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09,
+ 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08,
+ 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00,
+ 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 0x91,
+ 0x02, 0xc0
+};
+
+/*
+ * Sample uevent file from a Yubico Security Key.
+ */
+static const char dummy_uevent[] =
+ "DRIVER=hid-generic\n"
+ "HID_ID=0003:00001050:00000120\n"
+ "HID_NAME=Yubico Security Key by Yubico\n"
+ "HID_PHYS=usb-0000:00:14.0-3/input0\n"
+ "HID_UNIQ=\n"
+ "MODALIAS=hid:b0003g0001v00001050p00000120\n";
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 4 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_int(v[0], &p->seed) < 0 ||
+ unpack_string(v[1], p->uevent) < 0 ||
+ unpack_blob(v[2], &p->report_descriptor) < 0 ||
+ unpack_blob(v[3], &p->netlink_wiredata) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[4], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(4)) == NULL ||
+ (argv[0] = pack_int(p->seed)) == NULL ||
+ (argv[1] = pack_string(p->uevent)) == NULL ||
+ (argv[2] = pack_blob(&p->report_descriptor)) == NULL ||
+ (argv[3] = pack_blob(&p->netlink_wiredata)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 4; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 4; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ dummy.report_descriptor.len = sizeof(dummy_report_descriptor);
+ strlcpy(dummy.uevent, dummy_uevent, sizeof(dummy.uevent));
+ memcpy(&dummy.report_descriptor.body, &dummy_report_descriptor,
+ dummy.report_descriptor.len);
+ dummy.netlink_wiredata.len = sizeof(dummy_netlink_wiredata);
+ memcpy(&dummy.netlink_wiredata.body, &dummy_netlink_wiredata,
+ dummy.netlink_wiredata.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+ if (blob_len > len)
+ blob_len = len;
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static void
+get_usage(const struct param *p)
+{
+ uint32_t usage_page = 0;
+
+ fido_hid_get_usage(p->report_descriptor.body, p->report_descriptor.len,
+ &usage_page);
+ consume(&usage_page, sizeof(usage_page));
+}
+
+static void
+get_report_len(const struct param *p)
+{
+ size_t report_in_len = 0;
+ size_t report_out_len = 0;
+
+ fido_hid_get_report_len(p->report_descriptor.body,
+ p->report_descriptor.len, &report_in_len, &report_out_len);
+ consume(&report_in_len, sizeof(report_in_len));
+ consume(&report_out_len, sizeof(report_out_len));
+}
+
+static void
+manifest(const struct param *p)
+{
+ size_t ndevs, nfound;
+ fido_dev_info_t *devlist = NULL, *devlist_set = NULL;
+ int16_t vendor_id, product_id;
+ fido_dev_io_t io;
+ fido_dev_transport_t t;
+
+ memset(&io, 0, sizeof(io));
+ memset(&t, 0, sizeof(t));
+ set_netlink_io_functions(fd_read, fd_write);
+ set_wire_data(p->netlink_wiredata.body, p->netlink_wiredata.len);
+ set_udev_parameters(p->uevent, &p->report_descriptor);
+
+ ndevs = uniform_random(64);
+ if ((devlist = fido_dev_info_new(ndevs)) == NULL ||
+ (devlist_set = fido_dev_info_new(1)) == NULL ||
+ fido_dev_info_manifest(devlist, ndevs, &nfound) != FIDO_OK)
+ goto out;
+ for (size_t i = 0; i < nfound; i++) {
+ const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i);
+ consume_str(fido_dev_info_path(di));
+ consume_str(fido_dev_info_manufacturer_string(di));
+ consume_str(fido_dev_info_product_string(di));
+ vendor_id = fido_dev_info_vendor(di);
+ product_id = fido_dev_info_product(di);
+ consume(&vendor_id, sizeof(vendor_id));
+ consume(&product_id, sizeof(product_id));
+ fido_dev_info_set(devlist_set, 0, fido_dev_info_path(di),
+ fido_dev_info_manufacturer_string(di),
+ fido_dev_info_product_string(di), &io, &t);
+ }
+out:
+ fido_dev_info_free(&devlist, ndevs);
+ fido_dev_info_free(&devlist_set, 1);
+}
+
+void
+test(const struct param *p)
+{
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ get_usage(p);
+ get_report_len(p);
+ manifest(p);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_blob(&p->report_descriptor);
+ mutate_string(p->uevent);
+ }
+
+ if (flags & MUTATE_WIREDATA)
+ mutate_blob(&p->netlink_wiredata);
+}
diff --git a/fuzz/fuzz_largeblob.c b/fuzz/fuzz_largeblob.c
new file mode 100644
index 0000000..6cdc0c0
--- /dev/null
+++ b/fuzz/fuzz_largeblob.c
@@ -0,0 +1,272 @@
+/*
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mutator_aux.h"
+#include "wiredata_fido2.h"
+#include "dummy.h"
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+/* Parameter set defining a FIDO2 "large blob" operation. */
+struct param {
+ char pin[MAXSTR];
+ int seed;
+ struct blob key;
+ struct blob get_wiredata;
+ struct blob set_wiredata;
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'authenticatorLargeBlobs' 'get' command.
+ */
+static const uint8_t dummy_get_wiredata[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY
+};
+
+/*
+ * Collection of HID reports from an authenticator issued with a FIDO2
+ * 'authenticatorLargeBlobs' 'set' command.
+ */
+static const uint8_t dummy_set_wiredata[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_PINTOKEN,
+ WIREDATA_CTAP_CBOR_STATUS
+};
+
+/*
+ * XXX this needs to match the encrypted blob embedded in
+ * WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY.
+ */
+static const uint8_t dummy_key[] = {
+ 0xa9, 0x1b, 0xc4, 0xdd, 0xfc, 0x9a, 0x93, 0x79,
+ 0x75, 0xba, 0xf7, 0x7f, 0x4d, 0x57, 0xfc, 0xa6,
+ 0xe1, 0xf8, 0x06, 0x43, 0x23, 0x99, 0x51, 0x32,
+ 0xce, 0x6e, 0x19, 0x84, 0x50, 0x13, 0x2d, 0x7b
+};
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 5 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_int(v[0], &p->seed) < 0 ||
+ unpack_string(v[1], p->pin) < 0 ||
+ unpack_blob(v[2], &p->key) < 0 ||
+ unpack_blob(v[3], &p->get_wiredata) < 0 ||
+ unpack_blob(v[4], &p->set_wiredata) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[5], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(5)) == NULL ||
+ (argv[0] = pack_int(p->seed)) == NULL ||
+ (argv[1] = pack_string(p->pin)) == NULL ||
+ (argv[2] = pack_blob(&p->key)) == NULL ||
+ (argv[3] = pack_blob(&p->get_wiredata)) == NULL ||
+ (argv[4] = pack_blob(&p->set_wiredata)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 5; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 5; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin));
+
+ dummy.get_wiredata.len = sizeof(dummy_get_wiredata);
+ dummy.set_wiredata.len = sizeof(dummy_set_wiredata);
+ dummy.key.len = sizeof(dummy_key);
+
+ memcpy(&dummy.get_wiredata.body, &dummy_get_wiredata,
+ dummy.get_wiredata.len);
+ memcpy(&dummy.set_wiredata.body, &dummy_set_wiredata,
+ dummy.set_wiredata.len);
+ memcpy(&dummy.key.body, &dummy_key, dummy.key.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static fido_dev_t *
+prepare_dev(void)
+{
+ fido_dev_t *dev;
+
+ if ((dev = open_dev(0)) == NULL)
+ return NULL;
+
+ return dev;
+}
+
+static void
+get_blob(const struct param *p, int array)
+{
+ fido_dev_t *dev;
+ u_char *ptr = NULL;
+ size_t len = 0;
+
+ set_wire_data(p->get_wiredata.body, p->get_wiredata.len);
+
+ if ((dev = prepare_dev()) == NULL)
+ return;
+
+ if (array)
+ fido_dev_largeblob_get_array(dev, &ptr, &len);
+ else
+ fido_dev_largeblob_get(dev, p->key.body, p->key.len, &ptr, &len);
+ consume(ptr, len);
+ free(ptr);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+
+static void
+set_blob(const struct param *p, int op)
+{
+ fido_dev_t *dev;
+ const char *pin;
+
+ set_wire_data(p->set_wiredata.body, p->set_wiredata.len);
+
+ if ((dev = prepare_dev()) == NULL)
+ return;
+ pin = p->pin;
+ if (strlen(pin) == 0)
+ pin = NULL;
+
+ switch (op) {
+ case 0:
+ fido_dev_largeblob_remove(dev, p->key.body, p->key.len, pin);
+ break;
+ case 1:
+ /* XXX reuse p->get_wiredata as the blob to be set */
+ fido_dev_largeblob_set(dev, p->key.body, p->key.len,
+ p->get_wiredata.body, p->get_wiredata.len, pin);
+ break;
+ case 2:
+ /* XXX reuse p->get_wiredata as the body of the cbor array */
+ fido_dev_largeblob_set_array(dev, p->get_wiredata.body,
+ p->get_wiredata.len, pin);
+ }
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+void
+test(const struct param *p)
+{
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ get_blob(p, 0);
+ get_blob(p, 1);
+ set_blob(p, 0);
+ set_blob(p, 1);
+ set_blob(p, 2);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_blob(&p->key);
+ mutate_string(p->pin);
+ }
+
+ if (flags & MUTATE_WIREDATA) {
+ mutate_blob(&p->get_wiredata);
+ mutate_blob(&p->set_wiredata);
+ }
+}
diff --git a/fuzz/fuzz_mgmt.c b/fuzz/fuzz_mgmt.c
new file mode 100644
index 0000000..cbc313d
--- /dev/null
+++ b/fuzz/fuzz_mgmt.c
@@ -0,0 +1,528 @@
+/*
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mutator_aux.h"
+#include "wiredata_fido2.h"
+#include "dummy.h"
+
+#include "../openbsd-compat/openbsd-compat.h"
+
+#define MAXRPID 64
+
+struct param {
+ char pin1[MAXSTR];
+ char pin2[MAXSTR];
+ struct blob reset_wire_data;
+ struct blob info_wire_data;
+ struct blob set_pin_wire_data;
+ struct blob change_pin_wire_data;
+ struct blob retry_wire_data;
+ struct blob config_wire_data;
+ int seed;
+};
+
+static const uint8_t dummy_reset_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_CBOR_STATUS,
+};
+
+static const uint8_t dummy_info_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_INFO,
+};
+
+static const uint8_t dummy_set_pin_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_STATUS,
+};
+
+static const uint8_t dummy_change_pin_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_STATUS,
+};
+
+static const uint8_t dummy_retry_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_RETRIES,
+};
+
+static const uint8_t dummy_config_wire_data[] = {
+ WIREDATA_CTAP_INIT,
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_STATUS,
+};
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 9 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_int(v[0], &p->seed) < 0 ||
+ unpack_string(v[1], p->pin1) < 0 ||
+ unpack_string(v[2], p->pin2) < 0 ||
+ unpack_blob(v[3], &p->reset_wire_data) < 0 ||
+ unpack_blob(v[4], &p->info_wire_data) < 0 ||
+ unpack_blob(v[5], &p->set_pin_wire_data) < 0 ||
+ unpack_blob(v[6], &p->change_pin_wire_data) < 0 ||
+ unpack_blob(v[7], &p->retry_wire_data) < 0 ||
+ unpack_blob(v[8], &p->config_wire_data) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[9], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(9)) == NULL ||
+ (argv[0] = pack_int(p->seed)) == NULL ||
+ (argv[1] = pack_string(p->pin1)) == NULL ||
+ (argv[2] = pack_string(p->pin2)) == NULL ||
+ (argv[3] = pack_blob(&p->reset_wire_data)) == NULL ||
+ (argv[4] = pack_blob(&p->info_wire_data)) == NULL ||
+ (argv[5] = pack_blob(&p->set_pin_wire_data)) == NULL ||
+ (argv[6] = pack_blob(&p->change_pin_wire_data)) == NULL ||
+ (argv[7] = pack_blob(&p->retry_wire_data)) == NULL ||
+ (argv[8] = pack_blob(&p->config_wire_data)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 9; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 9; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ strlcpy(dummy.pin1, dummy_pin1, sizeof(dummy.pin1));
+ strlcpy(dummy.pin2, dummy_pin2, sizeof(dummy.pin2));
+
+ dummy.reset_wire_data.len = sizeof(dummy_reset_wire_data);
+ dummy.info_wire_data.len = sizeof(dummy_info_wire_data);
+ dummy.set_pin_wire_data.len = sizeof(dummy_set_pin_wire_data);
+ dummy.change_pin_wire_data.len = sizeof(dummy_change_pin_wire_data);
+ dummy.retry_wire_data.len = sizeof(dummy_retry_wire_data);
+ dummy.config_wire_data.len = sizeof(dummy_config_wire_data);
+
+ memcpy(&dummy.reset_wire_data.body, &dummy_reset_wire_data,
+ dummy.reset_wire_data.len);
+ memcpy(&dummy.info_wire_data.body, &dummy_info_wire_data,
+ dummy.info_wire_data.len);
+ memcpy(&dummy.set_pin_wire_data.body, &dummy_set_pin_wire_data,
+ dummy.set_pin_wire_data.len);
+ memcpy(&dummy.change_pin_wire_data.body, &dummy_change_pin_wire_data,
+ dummy.change_pin_wire_data.len);
+ memcpy(&dummy.retry_wire_data.body, &dummy_retry_wire_data,
+ dummy.retry_wire_data.len);
+ memcpy(&dummy.config_wire_data.body, &dummy_config_wire_data,
+ dummy.config_wire_data.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static void
+dev_reset(const struct param *p)
+{
+ fido_dev_t *dev;
+
+ set_wire_data(p->reset_wire_data.body, p->reset_wire_data.len);
+
+ if ((dev = open_dev(0)) == NULL)
+ return;
+
+ fido_dev_reset(dev);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_get_cbor_info(const struct param *p)
+{
+ fido_dev_t *dev;
+ fido_cbor_info_t *ci;
+ uint64_t n;
+ uint8_t proto, major, minor, build, flags;
+ bool v;
+
+ set_wire_data(p->info_wire_data.body, p->info_wire_data.len);
+
+ if ((dev = open_dev(0)) == NULL)
+ return;
+
+ proto = fido_dev_protocol(dev);
+ major = fido_dev_major(dev);
+ minor = fido_dev_minor(dev);
+ build = fido_dev_build(dev);
+ flags = fido_dev_flags(dev);
+
+ consume(&proto, sizeof(proto));
+ consume(&major, sizeof(major));
+ consume(&minor, sizeof(minor));
+ consume(&build, sizeof(build));
+ consume(&flags, sizeof(flags));
+
+ if ((ci = fido_cbor_info_new()) == NULL)
+ goto out;
+
+ fido_dev_get_cbor_info(dev, ci);
+
+ for (size_t i = 0; i < fido_cbor_info_versions_len(ci); i++) {
+ char * const *sa = fido_cbor_info_versions_ptr(ci);
+ consume(sa[i], strlen(sa[i]));
+ }
+
+ for (size_t i = 0; i < fido_cbor_info_extensions_len(ci); i++) {
+ char * const *sa = fido_cbor_info_extensions_ptr(ci);
+ consume(sa[i], strlen(sa[i]));
+ }
+
+ for (size_t i = 0; i < fido_cbor_info_transports_len(ci); i++) {
+ char * const *sa = fido_cbor_info_transports_ptr(ci);
+ consume(sa[i], strlen(sa[i]));
+ }
+
+ for (size_t i = 0; i < fido_cbor_info_options_len(ci); i++) {
+ char * const *sa = fido_cbor_info_options_name_ptr(ci);
+ const bool *va = fido_cbor_info_options_value_ptr(ci);
+ consume(sa[i], strlen(sa[i]));
+ consume(&va[i], sizeof(va[i]));
+ }
+
+ /* +1 on purpose */
+ for (size_t i = 0; i <= fido_cbor_info_algorithm_count(ci); i++) {
+ const char *type = fido_cbor_info_algorithm_type(ci, i);
+ int cose = fido_cbor_info_algorithm_cose(ci, i);
+ consume_str(type);
+ consume(&cose, sizeof(cose));
+ }
+
+ for (size_t i = 0; i < fido_cbor_info_certs_len(ci); i++) {
+ char * const *na = fido_cbor_info_certs_name_ptr(ci);
+ const uint64_t *va = fido_cbor_info_certs_value_ptr(ci);
+ consume(na[i], strlen(na[i]));
+ consume(&va[i], sizeof(va[i]));
+ }
+
+ n = fido_cbor_info_maxmsgsiz(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_maxcredbloblen(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_maxcredcntlst(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_maxcredidlen(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_maxlargeblob(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_fwversion(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_minpinlen(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_maxrpid_minpinlen(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_uv_attempts(ci);
+ consume(&n, sizeof(n));
+ n = fido_cbor_info_uv_modality(ci);
+ consume(&n, sizeof(n));
+ n = (uint64_t)fido_cbor_info_rk_remaining(ci);
+ consume(&n, sizeof(n));
+
+ consume(fido_cbor_info_aaguid_ptr(ci), fido_cbor_info_aaguid_len(ci));
+ consume(fido_cbor_info_protocols_ptr(ci),
+ fido_cbor_info_protocols_len(ci));
+
+ v = fido_cbor_info_new_pin_required(ci);
+ consume(&v, sizeof(v));
+
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ fido_cbor_info_free(&ci);
+}
+
+static void
+dev_set_pin(const struct param *p)
+{
+ fido_dev_t *dev;
+
+ set_wire_data(p->set_pin_wire_data.body, p->set_pin_wire_data.len);
+
+ if ((dev = open_dev(0)) == NULL)
+ return;
+
+ fido_dev_set_pin(dev, p->pin1, NULL);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_change_pin(const struct param *p)
+{
+ fido_dev_t *dev;
+
+ set_wire_data(p->change_pin_wire_data.body, p->change_pin_wire_data.len);
+
+ if ((dev = open_dev(0)) == NULL)
+ return;
+
+ fido_dev_set_pin(dev, p->pin2, p->pin1);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_get_retry_count(const struct param *p)
+{
+ fido_dev_t *dev;
+ int n = 0;
+
+ set_wire_data(p->retry_wire_data.body, p->retry_wire_data.len);
+
+ if ((dev = open_dev(0)) == NULL)
+ return;
+
+ fido_dev_get_retry_count(dev, &n);
+ consume(&n, sizeof(n));
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_get_uv_retry_count(const struct param *p)
+{
+ fido_dev_t *dev;
+ int n = 0;
+
+ set_wire_data(p->retry_wire_data.body, p->retry_wire_data.len);
+
+ if ((dev = open_dev(0)) == NULL)
+ return;
+
+ fido_dev_get_uv_retry_count(dev, &n);
+ consume(&n, sizeof(n));
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_enable_entattest(const struct param *p)
+{
+ fido_dev_t *dev;
+ const char *pin;
+ int r;
+
+ set_wire_data(p->config_wire_data.body, p->config_wire_data.len);
+ if ((dev = open_dev(0)) == NULL)
+ return;
+ pin = p->pin1;
+ if (strlen(pin) == 0)
+ pin = NULL;
+ r = fido_dev_enable_entattest(dev, pin);
+ consume_str(fido_strerr(r));
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_toggle_always_uv(const struct param *p)
+{
+ fido_dev_t *dev;
+ const char *pin;
+ int r;
+
+ set_wire_data(p->config_wire_data.body, p->config_wire_data.len);
+ if ((dev = open_dev(0)) == NULL)
+ return;
+ pin = p->pin1;
+ if (strlen(pin) == 0)
+ pin = NULL;
+ r = fido_dev_toggle_always_uv(dev, pin);
+ consume_str(fido_strerr(r));
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_force_pin_change(const struct param *p)
+{
+ fido_dev_t *dev;
+ const char *pin;
+ int r;
+
+ set_wire_data(p->config_wire_data.body, p->config_wire_data.len);
+ if ((dev = open_dev(0)) == NULL)
+ return;
+ pin = p->pin1;
+ if (strlen(pin) == 0)
+ pin = NULL;
+ r = fido_dev_force_pin_change(dev, pin);
+ consume_str(fido_strerr(r));
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_set_pin_minlen(const struct param *p)
+{
+ fido_dev_t *dev;
+ const char *pin;
+ int r;
+
+ set_wire_data(p->config_wire_data.body, p->config_wire_data.len);
+ if ((dev = open_dev(0)) == NULL)
+ return;
+ pin = p->pin1;
+ if (strlen(pin) == 0)
+ pin = NULL;
+ r = fido_dev_set_pin_minlen(dev, strlen(p->pin2), pin);
+ consume_str(fido_strerr(r));
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+static void
+dev_set_pin_minlen_rpid(const struct param *p)
+{
+ fido_dev_t *dev;
+ const char *rpid[MAXRPID];
+ const char *pin;
+ size_t n;
+ int r;
+
+ set_wire_data(p->config_wire_data.body, p->config_wire_data.len);
+ if ((dev = open_dev(0)) == NULL)
+ return;
+ n = uniform_random(MAXRPID);
+ for (size_t i = 0; i < n; i++)
+ rpid[i] = dummy_rp_id;
+ pin = p->pin1;
+ if (strlen(pin) == 0)
+ pin = NULL;
+ r = fido_dev_set_pin_minlen_rpid(dev, rpid, n, pin);
+ consume_str(fido_strerr(r));
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+}
+
+void
+test(const struct param *p)
+{
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ dev_reset(p);
+ dev_get_cbor_info(p);
+ dev_set_pin(p);
+ dev_change_pin(p);
+ dev_get_retry_count(p);
+ dev_get_uv_retry_count(p);
+ dev_enable_entattest(p);
+ dev_toggle_always_uv(p);
+ dev_force_pin_change(p);
+ dev_set_pin_minlen(p);
+ dev_set_pin_minlen_rpid(p);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_string(p->pin1);
+ mutate_string(p->pin2);
+ }
+
+ if (flags & MUTATE_WIREDATA) {
+ mutate_blob(&p->reset_wire_data);
+ mutate_blob(&p->info_wire_data);
+ mutate_blob(&p->set_pin_wire_data);
+ mutate_blob(&p->change_pin_wire_data);
+ mutate_blob(&p->retry_wire_data);
+ }
+}
diff --git a/fuzz/fuzz_netlink.c b/fuzz/fuzz_netlink.c
new file mode 100644
index 0000000..4d28129
--- /dev/null
+++ b/fuzz/fuzz_netlink.c
@@ -0,0 +1,164 @@
+/*
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "mutator_aux.h"
+#include "dummy.h"
+
+struct param {
+ int seed;
+ int dev;
+ struct blob wiredata;
+};
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 3 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_int(v[0], &p->seed) < 0 ||
+ unpack_int(v[1], &p->dev) < 0 ||
+ unpack_blob(v[2], &p->wiredata) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[3], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(3)) == NULL ||
+ (argv[0] = pack_int(p->seed)) == NULL ||
+ (argv[1] = pack_int(p->dev)) == NULL ||
+ (argv[2] = pack_blob(&p->wiredata)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 3; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 3; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ dummy.wiredata.len = sizeof(dummy_netlink_wiredata);
+ memcpy(&dummy.wiredata.body, &dummy_netlink_wiredata,
+ dummy.wiredata.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+void
+test(const struct param *p)
+{
+ fido_nl_t *nl;
+ uint32_t target;
+
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ set_netlink_io_functions(fd_read, fd_write);
+ set_wire_data(p->wiredata.body, p->wiredata.len);
+
+ if ((nl = fido_nl_new()) == NULL)
+ return;
+
+ consume(&nl->fd, sizeof(nl->fd));
+ consume(&nl->nfc_type, sizeof(nl->nfc_type));
+ consume(&nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp));
+ consume(&nl->saddr, sizeof(nl->saddr));
+
+ fido_nl_power_nfc(nl, (uint32_t)p->dev);
+
+ if (fido_nl_get_nfc_target(nl, (uint32_t)p->dev, &target) == 0)
+ consume(&target, sizeof(target));
+
+ fido_nl_free(&nl);
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM)
+ mutate_int(&p->dev);
+
+ if (flags & MUTATE_WIREDATA)
+ mutate_blob(&p->wiredata);
+}
diff --git a/fuzz/fuzz_pcsc.c b/fuzz/fuzz_pcsc.c
new file mode 100644
index 0000000..cf6210b
--- /dev/null
+++ b/fuzz/fuzz_pcsc.c
@@ -0,0 +1,269 @@
+/*
+ * 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
+ */
+
+#define _FIDO_INTERNAL
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <winscard.h>
+
+#include "mutator_aux.h"
+#include "wiredata_fido2.h"
+#include "dummy.h"
+
+#include "../src/extern.h"
+
+struct param {
+ int seed;
+ char path[MAXSTR];
+ struct blob pcsc_list;
+ struct blob tx_apdu;
+ struct blob wiredata_init;
+ struct blob wiredata_msg;
+};
+
+static const uint8_t dummy_tx_apdu[] = { WIREDATA_CTAP_EXTENDED_APDU };
+static const uint8_t dummy_wiredata_init[] = { WIREDATA_CTAP_NFC_INIT };
+static const uint8_t dummy_wiredata_msg[] = { WIREDATA_CTAP_NFC_MSG };
+
+struct param *
+unpack(const uint8_t *ptr, size_t len)
+{
+ cbor_item_t *item = NULL, **v;
+ struct cbor_load_result cbor;
+ struct param *p;
+ int ok = -1;
+
+ if ((p = calloc(1, sizeof(*p))) == NULL ||
+ (item = cbor_load(ptr, len, &cbor)) == NULL ||
+ cbor.read != len ||
+ cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false ||
+ cbor_array_size(item) != 6 ||
+ (v = cbor_array_handle(item)) == NULL)
+ goto fail;
+
+ if (unpack_int(v[0], &p->seed) < 0 ||
+ unpack_string(v[1], p->path) < 0 ||
+ unpack_blob(v[2], &p->pcsc_list) < 0 ||
+ unpack_blob(v[3], &p->tx_apdu) < 0 ||
+ unpack_blob(v[4], &p->wiredata_init) < 0 ||
+ unpack_blob(v[5], &p->wiredata_msg) < 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (ok < 0) {
+ free(p);
+ p = NULL;
+ }
+
+ if (item)
+ cbor_decref(&item);
+
+ return p;
+}
+
+size_t
+pack(uint8_t *ptr, size_t len, const struct param *p)
+{
+ cbor_item_t *argv[6], *array = NULL;
+ size_t cbor_alloc_len, cbor_len = 0;
+ unsigned char *cbor = NULL;
+
+ memset(argv, 0, sizeof(argv));
+
+ if ((array = cbor_new_definite_array(6)) == NULL ||
+ (argv[0] = pack_int(p->seed)) == NULL ||
+ (argv[1] = pack_string(p->path)) == NULL ||
+ (argv[2] = pack_blob(&p->pcsc_list)) == NULL ||
+ (argv[3] = pack_blob(&p->tx_apdu)) == NULL ||
+ (argv[4] = pack_blob(&p->wiredata_init)) == NULL ||
+ (argv[5] = pack_blob(&p->wiredata_msg)) == NULL)
+ goto fail;
+
+ for (size_t i = 0; i < 6; i++)
+ if (cbor_array_push(array, argv[i]) == false)
+ goto fail;
+
+ if ((cbor_len = cbor_serialize_alloc(array, &cbor,
+ &cbor_alloc_len)) == 0 || cbor_len > len) {
+ cbor_len = 0;
+ goto fail;
+ }
+
+ memcpy(ptr, cbor, cbor_len);
+fail:
+ for (size_t i = 0; i < 6; i++)
+ if (argv[i])
+ cbor_decref(&argv[i]);
+
+ if (array)
+ cbor_decref(&array);
+
+ free(cbor);
+
+ return cbor_len;
+}
+
+size_t
+pack_dummy(uint8_t *ptr, size_t len)
+{
+ struct param dummy;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&dummy, 0, sizeof(dummy));
+
+ strlcpy(dummy.path, dummy_pcsc_path, sizeof(dummy.path));
+
+ dummy.pcsc_list.len = sizeof(dummy_pcsc_list);
+ memcpy(&dummy.pcsc_list.body, &dummy_pcsc_list, dummy.pcsc_list.len);
+
+ dummy.tx_apdu.len = sizeof(dummy_tx_apdu);
+ memcpy(&dummy.tx_apdu.body, &dummy_tx_apdu, dummy.tx_apdu.len);
+
+ dummy.wiredata_init.len = sizeof(dummy_wiredata_init);
+ memcpy(&dummy.wiredata_init.body, &dummy_wiredata_init,
+ dummy.wiredata_init.len);
+
+ dummy.wiredata_msg.len = sizeof(dummy_wiredata_msg);
+ memcpy(&dummy.wiredata_msg.body, &dummy_wiredata_msg,
+ dummy.wiredata_msg.len);
+
+ assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0);
+
+ if (blob_len > len) {
+ memcpy(ptr, blob, len);
+ return len;
+ }
+
+ memcpy(ptr, blob, blob_len);
+
+ return blob_len;
+}
+
+static void
+test_manifest(void)
+{
+ size_t ndevs, nfound;
+ fido_dev_info_t *devlist = NULL;
+ int16_t vendor_id, product_id;
+ int r;
+
+ r = fido_pcsc_manifest(NULL, 0, &nfound);
+ assert(r == FIDO_OK && nfound == 0);
+ r = fido_pcsc_manifest(NULL, 1, &nfound);
+ assert(r == FIDO_ERR_INVALID_ARGUMENT);
+
+ ndevs = uniform_random(64);
+ if ((devlist = fido_dev_info_new(ndevs)) == NULL ||
+ fido_pcsc_manifest(devlist, ndevs, &nfound) != FIDO_OK)
+ goto out;
+
+ for (size_t i = 0; i < nfound; i++) {
+ const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i);
+ consume_str(fido_dev_info_path(di));
+ consume_str(fido_dev_info_manufacturer_string(di));
+ consume_str(fido_dev_info_product_string(di));
+ vendor_id = fido_dev_info_vendor(di);
+ product_id = fido_dev_info_product(di);
+ consume(&vendor_id, sizeof(vendor_id));
+ consume(&product_id, sizeof(product_id));
+ }
+
+out:
+ fido_dev_info_free(&devlist, ndevs);
+}
+
+static void
+test_tx(const char *path, const struct blob *apdu, uint8_t cmd, u_char *rx_buf,
+ size_t rx_len)
+{
+ fido_dev_t dev;
+ const u_char *tx_ptr = NULL;
+ size_t tx_len = 0;
+ int n;
+
+ memset(&dev, 0, sizeof(dev));
+
+ if (fido_dev_set_pcsc(&dev) < 0)
+ return;
+ if ((dev.io_handle = fido_pcsc_open(path)) == NULL)
+ return;
+
+ if (apdu) {
+ tx_ptr = apdu->body;
+ tx_len = apdu->len;
+ }
+
+ fido_pcsc_tx(&dev, cmd, tx_ptr, tx_len);
+
+ if ((n = fido_pcsc_rx(&dev, cmd, rx_buf, rx_len, -1)) >= 0)
+ consume(rx_buf, n);
+
+ fido_pcsc_close(dev.io_handle);
+}
+
+static void
+test_misc(void)
+{
+ assert(fido_pcsc_open(NULL) == NULL);
+ assert(fido_pcsc_write(NULL, NULL, INT_MAX + 1LL) == -1);
+}
+
+void
+test(const struct param *p)
+{
+ u_char buf[512];
+
+ prng_init((unsigned int)p->seed);
+ fuzz_clock_reset();
+ fido_init(FIDO_DEBUG);
+ fido_set_log_handler(consume_str);
+
+ set_pcsc_parameters(&p->pcsc_list);
+ set_pcsc_io_functions(nfc_read, nfc_write, consume);
+
+ set_wire_data(p->wiredata_init.body, p->wiredata_init.len);
+ test_manifest();
+
+ test_misc();
+
+ set_wire_data(p->wiredata_init.body, p->wiredata_init.len);
+ test_tx(p->path, NULL, CTAP_CMD_INIT, buf, uniform_random(20));
+
+ set_wire_data(p->wiredata_msg.body, p->wiredata_msg.len);
+ test_tx(p->path, &p->tx_apdu, CTAP_CMD_MSG, buf, sizeof(buf));
+
+ set_wire_data(p->wiredata_msg.body, p->wiredata_msg.len);
+ test_tx(p->path, &p->tx_apdu, CTAP_CMD_CBOR, buf, sizeof(buf));
+
+ set_wire_data(p->wiredata_msg.body, p->wiredata_msg.len);
+ test_tx(p->path, &p->tx_apdu, CTAP_CMD_LOCK, buf, sizeof(buf));
+}
+
+void
+mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN
+{
+ if (flags & MUTATE_SEED)
+ p->seed = (int)seed;
+
+ if (flags & MUTATE_PARAM) {
+ mutate_string(p->path);
+ mutate_blob(&p->pcsc_list);
+ mutate_blob(&p->tx_apdu);
+ }
+
+ if (flags & MUTATE_WIREDATA) {
+ mutate_blob(&p->wiredata_init);
+ mutate_blob(&p->wiredata_msg);
+ }
+}
diff --git a/fuzz/libfuzzer.c b/fuzz/libfuzzer.c
new file mode 100644
index 0000000..073ebe6
--- /dev/null
+++ b/fuzz/libfuzzer.c
@@ -0,0 +1,230 @@
+/*
+ * 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 <err.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mutator_aux.h"
+
+extern int fuzz_save_corpus;
+
+static bool debug;
+static unsigned int flags = MUTATE_ALL;
+static unsigned long long test_fail;
+static unsigned long long test_total;
+static unsigned long long mutate_fail;
+static unsigned long long mutate_total;
+
+int LLVMFuzzerInitialize(int *, char ***);
+int LLVMFuzzerTestOneInput(const uint8_t *, size_t);
+size_t LLVMFuzzerCustomMutator(uint8_t *, size_t, size_t, unsigned int);
+
+static int
+save_seed(const char *opt)
+{
+ const char *path;
+ int fd = -1, status = 1;
+ void *buf = NULL;
+ const size_t buflen = MAXCORPUS;
+ size_t n;
+ struct param *p = NULL;
+
+ if ((path = strchr(opt, '=')) == NULL || strlen(++path) == 0) {
+ warnx("usage: --fido-save-seed=<path>");
+ goto fail;
+ }
+
+ if ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644)) == -1) {
+ warn("open %s", path);
+ goto fail;
+ }
+
+ if ((buf = malloc(buflen)) == NULL) {
+ warn("malloc");
+ goto fail;
+ }
+
+ n = pack_dummy(buf, buflen);
+
+ if ((p = unpack(buf, n)) == NULL) {
+ warnx("unpack");
+ goto fail;
+ }
+
+ if (write(fd, buf, n) != (ssize_t)n) {
+ warn("write %s", path);
+ goto fail;
+ }
+
+ status = 0;
+fail:
+ if (fd != -1)
+ close(fd);
+ free(buf);
+ free(p);
+
+ return status;
+}
+
+static int
+save_corpus(const struct param *p)
+{
+ uint8_t blob[MAXCORPUS], dgst[SHA256_DIGEST_LENGTH];
+ size_t blob_len;
+ char path[PATH_MAX];
+ int r, fd;
+
+ if ((blob_len = pack(blob, sizeof(blob), p)) == 0 ||
+ blob_len > sizeof(blob)) {
+ warnx("pack");
+ return -1;
+ }
+
+ if (SHA256(blob, blob_len, dgst) != dgst) {
+ warnx("sha256");
+ return -1;
+ }
+
+ if ((r = snprintf(path, sizeof(path), "saved_corpus_%02x%02x%02x%02x"
+ "%02x%02x%02x%02x", dgst[0], dgst[1], dgst[2], dgst[3], dgst[4],
+ dgst[5], dgst[6], dgst[7])) < 0 || (size_t)r >= sizeof(path)) {
+ warnx("snprintf");
+ return -1;
+ }
+
+ if ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644)) == -1) {
+ warn("open %s", path);
+ return -1;
+ }
+
+ if (write(fd, blob, blob_len) != (ssize_t)blob_len) {
+ warn("write");
+ r = -1;
+ } else {
+ warnx("wrote %s", path);
+ r = 0;
+ }
+
+ close(fd);
+
+ return r;
+}
+
+static void
+parse_mutate_flags(const char *opt, unsigned int *mutate_flags)
+{
+ const char *f;
+
+ if ((f = strchr(opt, '=')) == NULL || strlen(++f) == 0)
+ errx(1, "usage: --fido-mutate=<flag>");
+
+ if (strcmp(f, "seed") == 0)
+ *mutate_flags |= MUTATE_SEED;
+ else if (strcmp(f, "param") == 0)
+ *mutate_flags |= MUTATE_PARAM;
+ else if (strcmp(f, "wiredata") == 0)
+ *mutate_flags |= MUTATE_WIREDATA;
+ else
+ errx(1, "--fido-mutate: unknown flag '%s'", f);
+}
+
+int
+LLVMFuzzerInitialize(int *argc, char ***argv)
+{
+ unsigned int mutate_flags = 0;
+
+ for (int i = 0; i < *argc; i++)
+ if (strcmp((*argv)[i], "--fido-debug") == 0) {
+ debug = 1;
+ } else if (strncmp((*argv)[i], "--fido-save-seed=", 17) == 0) {
+ exit(save_seed((*argv)[i]));
+ } else if (strncmp((*argv)[i], "--fido-mutate=", 14) == 0) {
+ parse_mutate_flags((*argv)[i], &mutate_flags);
+ }
+
+ if (mutate_flags)
+ flags = mutate_flags;
+
+ return 0;
+}
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ struct param *p;
+
+ if (size > MAXCORPUS)
+ return 0;
+
+ if (++test_total % 100000 == 0 && debug) {
+ double r = (double)test_fail/(double)test_total * 100.0;
+ fprintf(stderr, "%s: %llu/%llu (%.2f%%)\n", __func__,
+ test_fail, test_total, r);
+ }
+
+ if ((p = unpack(data, size)) == NULL)
+ test_fail++;
+ else {
+ fuzz_save_corpus = 0;
+ test(p);
+ if (fuzz_save_corpus && save_corpus(p) < 0)
+ fprintf(stderr, "%s: failed to save corpus\n",
+ __func__);
+ free(p);
+ }
+
+ return 0;
+}
+
+size_t
+LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t maxsize,
+ unsigned int seed) NO_MSAN
+{
+ struct param *p;
+ uint8_t blob[MAXCORPUS];
+ size_t blob_len;
+
+ memset(&p, 0, sizeof(p));
+
+#ifdef WITH_MSAN
+ __msan_unpoison(data, maxsize);
+#endif
+
+ if (++mutate_total % 100000 == 0 && debug) {
+ double r = (double)mutate_fail/(double)mutate_total * 100.0;
+ fprintf(stderr, "%s: %llu/%llu (%.2f%%)\n", __func__,
+ mutate_fail, mutate_total, r);
+ }
+
+ if ((p = unpack(data, size)) == NULL) {
+ mutate_fail++;
+ return pack_dummy(data, maxsize);
+ }
+
+ mutate(p, seed, flags);
+
+ if ((blob_len = pack(blob, sizeof(blob), p)) == 0 ||
+ blob_len > sizeof(blob) || blob_len > maxsize) {
+ mutate_fail++;
+ free(p);
+ return 0;
+ }
+
+ free(p);
+
+ memcpy(data, blob, blob_len);
+
+ return blob_len;
+}
diff --git a/fuzz/mutator_aux.c b/fuzz/mutator_aux.c
new file mode 100644
index 0000000..64c633f
--- /dev/null
+++ b/fuzz/mutator_aux.c
@@ -0,0 +1,332 @@
+/*
+ * 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 <assert.h>
+#include <cbor.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mutator_aux.h"
+
+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);
+size_t LLVMFuzzerMutate(uint8_t *, size_t, size_t);
+
+extern int prng_up;
+static const uint8_t *wire_data_ptr = NULL;
+static size_t wire_data_len = 0;
+
+void
+consume(const void *body, size_t len)
+{
+ const volatile uint8_t *ptr = body;
+ volatile uint8_t x = 0;
+
+#ifdef WITH_MSAN
+ __msan_check_mem_is_initialized(body, len);
+#endif
+
+ while (len--)
+ x ^= *ptr++;
+
+ (void)x;
+}
+
+void
+consume_str(const char *str)
+{
+ if (str != NULL)
+ consume(str, strlen(str) + 1);
+}
+
+int
+unpack_int(cbor_item_t *item, int *v)
+{
+ if (cbor_is_int(item) == false ||
+ cbor_int_get_width(item) != CBOR_INT_64)
+ return -1;
+
+ if (cbor_isa_uint(item))
+ *v = (int)cbor_get_uint64(item);
+ else
+ *v = (int)(-cbor_get_uint64(item) - 1);
+
+ return 0;
+}
+
+int
+unpack_string(cbor_item_t *item, char *v)
+{
+ size_t len;
+
+ if (cbor_isa_bytestring(item) == false ||
+ (len = cbor_bytestring_length(item)) >= MAXSTR)
+ return -1;
+
+ memcpy(v, cbor_bytestring_handle(item), len);
+ v[len] = '\0';
+
+ return 0;
+}
+
+int
+unpack_byte(cbor_item_t *item, uint8_t *v)
+{
+ if (cbor_isa_uint(item) == false ||
+ cbor_int_get_width(item) != CBOR_INT_8)
+ return -1;
+
+ *v = cbor_get_uint8(item);
+
+ return 0;
+}
+
+int
+unpack_blob(cbor_item_t *item, struct blob *v)
+{
+ if (cbor_isa_bytestring(item) == false ||
+ (v->len = cbor_bytestring_length(item)) > sizeof(v->body))
+ return -1;
+
+ memcpy(v->body, cbor_bytestring_handle(item), v->len);
+
+ return 0;
+}
+
+cbor_item_t *
+pack_int(int v) NO_MSAN
+{
+ if (v < 0)
+ return cbor_build_negint64((uint64_t)(-(int64_t)v - 1));
+ else
+ return cbor_build_uint64((uint64_t)v);
+}
+
+cbor_item_t *
+pack_string(const char *v) NO_MSAN
+{
+ if (strlen(v) >= MAXSTR)
+ return NULL;
+
+ return cbor_build_bytestring((const unsigned char *)v, strlen(v));
+}
+
+cbor_item_t *
+pack_byte(uint8_t v) NO_MSAN
+{
+ return cbor_build_uint8(v);
+}
+
+cbor_item_t *
+pack_blob(const struct blob *v) NO_MSAN
+{
+ return cbor_build_bytestring(v->body, v->len);
+}
+
+void
+mutate_byte(uint8_t *b)
+{
+ LLVMFuzzerMutate(b, sizeof(*b), sizeof(*b));
+}
+
+void
+mutate_int(int *i)
+{
+ LLVMFuzzerMutate((uint8_t *)i, sizeof(*i), sizeof(*i));
+}
+
+void
+mutate_blob(struct blob *blob)
+{
+ blob->len = LLVMFuzzerMutate((uint8_t *)blob->body, blob->len,
+ sizeof(blob->body));
+}
+
+void
+mutate_string(char *s)
+{
+ size_t n;
+
+ n = LLVMFuzzerMutate((uint8_t *)s, strlen(s), MAXSTR - 1);
+ s[n] = '\0';
+}
+
+static int
+buf_read(unsigned char *ptr, size_t len, int ms)
+{
+ size_t n;
+
+ (void)ms;
+
+ if (prng_up && uniform_random(400) < 1) {
+ errno = EIO;
+ return -1;
+ }
+
+ if (wire_data_len < len)
+ n = wire_data_len;
+ else
+ n = len;
+
+ memcpy(ptr, wire_data_ptr, n);
+
+ wire_data_ptr += n;
+ wire_data_len -= n;
+
+ return (int)n;
+}
+
+static int
+buf_write(const unsigned char *ptr, size_t len)
+{
+ consume(ptr, len);
+
+ if (prng_up && uniform_random(400) < 1) {
+ errno = EIO;
+ return -1;
+ }
+
+ return (int)len;
+}
+
+static void *
+hid_open(const char *path)
+{
+ (void)path;
+
+ return (void *)HID_DEV_HANDLE;
+}
+
+static void
+hid_close(void *handle)
+{
+ assert(handle == (void *)HID_DEV_HANDLE);
+}
+
+static int
+hid_read(void *handle, unsigned char *ptr, size_t len, int ms)
+{
+ assert(handle == (void *)HID_DEV_HANDLE);
+ assert(len >= CTAP_MIN_REPORT_LEN && len <= CTAP_MAX_REPORT_LEN);
+
+ return buf_read(ptr, len, ms);
+}
+
+static int
+hid_write(void *handle, const unsigned char *ptr, size_t len)
+{
+ assert(handle == (void *)HID_DEV_HANDLE);
+ assert(len >= CTAP_MIN_REPORT_LEN + 1 &&
+ len <= CTAP_MAX_REPORT_LEN + 1);
+
+ return buf_write(ptr, len);
+}
+
+static void *
+nfc_open(const char *path)
+{
+ (void)path;
+
+ return (void *)NFC_DEV_HANDLE;
+}
+
+static void
+nfc_close(void *handle)
+{
+ assert(handle == (void *)NFC_DEV_HANDLE);
+}
+
+int
+nfc_read(void *handle, unsigned char *ptr, size_t len, int ms)
+{
+ assert(handle == (void *)NFC_DEV_HANDLE);
+ assert(len > 0 && len <= 264);
+
+ return buf_read(ptr, len, ms);
+}
+
+int
+nfc_write(void *handle, const unsigned char *ptr, size_t len)
+{
+ assert(handle == (void *)NFC_DEV_HANDLE);
+ assert(len > 0 && len <= 256 + 2);
+
+ return buf_write(ptr, len);
+}
+
+ssize_t
+fd_read(int fd, void *ptr, size_t len)
+{
+ assert(fd != -1);
+
+ return buf_read(ptr, len, -1);
+}
+
+ssize_t
+fd_write(int fd, const void *ptr, size_t len)
+{
+ assert(fd != -1);
+
+ return buf_write(ptr, len);
+}
+
+fido_dev_t *
+open_dev(int nfc)
+{
+ fido_dev_t *dev;
+ fido_dev_io_t io;
+ fido_dev_transport_t t;
+
+ memset(&io, 0, sizeof(io));
+ memset(&t, 0, sizeof(t));
+
+ if ((dev = fido_dev_new()) == NULL)
+ return NULL;
+
+ if (nfc) {
+ io.open = nfc_open;
+ io.close = nfc_close;
+ io.read = nfc_read;
+ io.write = nfc_write;
+ } else {
+ io.open = hid_open;
+ io.close = hid_close;
+ io.read = hid_read;
+ io.write = hid_write;
+ }
+
+ if (fido_dev_set_io_functions(dev, &io) != FIDO_OK)
+ goto fail;
+
+ if (nfc) {
+ t.rx = fido_nfc_rx;
+ t.tx = fido_nfc_tx;
+ if (fido_dev_set_transport_functions(dev, &t) != FIDO_OK)
+ goto fail;
+ }
+
+ if (fido_dev_set_timeout(dev, 300) != FIDO_OK ||
+ fido_dev_open(dev, "nodev") != FIDO_OK)
+ goto fail;
+
+ return dev;
+fail:
+ fido_dev_free(&dev);
+
+ return NULL;
+}
+
+void
+set_wire_data(const uint8_t *ptr, size_t len)
+{
+ wire_data_ptr = ptr;
+ wire_data_len = len;
+}
diff --git a/fuzz/mutator_aux.h b/fuzz/mutator_aux.h
new file mode 100644
index 0000000..5ad5661
--- /dev/null
+++ b/fuzz/mutator_aux.h
@@ -0,0 +1,111 @@
+/*
+ * 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
+ */
+
+#ifndef _MUTATOR_AUX_H
+#define _MUTATOR_AUX_H
+
+#include <sys/types.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <cbor.h>
+
+#include "../src/fido.h"
+#include "../src/fido/bio.h"
+#include "../src/fido/config.h"
+#include "../src/fido/credman.h"
+#include "../src/fido/eddsa.h"
+#include "../src/fido/es256.h"
+#include "../src/fido/es384.h"
+#include "../src/fido/rs256.h"
+#include "../src/netlink.h"
+
+/*
+ * As of LLVM 10.0.0, MSAN support in libFuzzer was still experimental.
+ * We therefore have to be careful when using our custom mutator, or
+ * MSAN will flag uninitialised reads on memory populated by libFuzzer.
+ * Since there is no way to suppress MSAN without regenerating object
+ * code (in which case you might as well rebuild libFuzzer with MSAN),
+ * we adjust our mutator to make it less accurate while allowing
+ * fuzzing to proceed.
+ */
+
+#if defined(__has_feature)
+# if __has_feature(memory_sanitizer)
+# include <sanitizer/msan_interface.h>
+# define NO_MSAN __attribute__((no_sanitize("memory")))
+# define WITH_MSAN 1
+# endif
+#endif
+
+#if !defined(WITH_MSAN)
+# define NO_MSAN
+#endif
+
+#define MUTATE_SEED 0x01
+#define MUTATE_PARAM 0x02
+#define MUTATE_WIREDATA 0x04
+#define MUTATE_ALL (MUTATE_SEED | MUTATE_PARAM | MUTATE_WIREDATA)
+
+#define MAXSTR 1024
+#define MAXBLOB 3600
+#define MAXCORPUS 8192
+
+#define HID_DEV_HANDLE 0x68696421
+#define NFC_DEV_HANDLE 0x6e666321
+
+struct blob {
+ uint8_t body[MAXBLOB];
+ size_t len;
+};
+
+struct param;
+
+struct param *unpack(const uint8_t *, size_t);
+size_t pack(uint8_t *, size_t, const struct param *);
+size_t pack_dummy(uint8_t *, size_t);
+void mutate(struct param *, unsigned int, unsigned int);
+void test(const struct param *);
+
+void consume(const void *, size_t);
+void consume_str(const char *);
+
+int unpack_blob(cbor_item_t *, struct blob *);
+int unpack_byte(cbor_item_t *, uint8_t *);
+int unpack_int(cbor_item_t *, int *);
+int unpack_string(cbor_item_t *, char *);
+
+cbor_item_t *pack_blob(const struct blob *);
+cbor_item_t *pack_byte(uint8_t);
+cbor_item_t *pack_int(int);
+cbor_item_t *pack_string(const char *);
+
+void mutate_byte(uint8_t *);
+void mutate_int(int *);
+void mutate_blob(struct blob *);
+void mutate_string(char *);
+
+ssize_t fd_read(int, void *, size_t);
+ssize_t fd_write(int, const void *, size_t);
+
+int nfc_read(void *, unsigned char *, size_t, int);
+int nfc_write(void *, const unsigned char *, size_t);
+
+fido_dev_t *open_dev(int);
+void set_wire_data(const uint8_t *, size_t);
+
+void fuzz_clock_reset(void);
+void prng_init(unsigned long);
+unsigned long prng_uint32(void);
+
+uint32_t uniform_random(uint32_t);
+
+void set_pcsc_parameters(const struct blob *);
+void set_pcsc_io_functions(int (*)(void *, u_char *, size_t, int),
+ int (*)(void *, const u_char *, size_t), void (*)(const void *, size_t));
+
+#endif /* !_MUTATOR_AUX_H */
diff --git a/fuzz/pcsc.c b/fuzz/pcsc.c
new file mode 100644
index 0000000..f6a3e9b
--- /dev/null
+++ b/fuzz/pcsc.c
@@ -0,0 +1,153 @@
+/*
+ * 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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <winscard.h>
+
+#include "mutator_aux.h"
+
+static const struct blob *reader_list;
+static int (*xread)(void *, u_char *, size_t, int);
+static int (*xwrite)(void *, const u_char *, size_t);
+static void (*xconsume)(const void *, size_t);
+
+LONG __wrap_SCardEstablishContext(DWORD, LPCVOID, LPCVOID, LPSCARDCONTEXT);
+LONG __wrap_SCardListReaders(SCARDCONTEXT, LPCSTR, LPSTR, LPDWORD);
+LONG __wrap_SCardReleaseContext(SCARDCONTEXT);
+LONG __wrap_SCardConnect(SCARDCONTEXT, LPCSTR, DWORD, DWORD, LPSCARDHANDLE,
+ LPDWORD);
+LONG __wrap_SCardDisconnect(SCARDHANDLE, DWORD);
+LONG __wrap_SCardTransmit(SCARDHANDLE, const SCARD_IO_REQUEST *, LPCBYTE,
+ DWORD, SCARD_IO_REQUEST *, LPBYTE, LPDWORD);
+
+LONG
+__wrap_SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1,
+ LPCVOID pvReserved2, LPSCARDCONTEXT phContext)
+{
+ assert(dwScope == SCARD_SCOPE_SYSTEM);
+ assert(pvReserved1 == NULL);
+ assert(pvReserved2 == NULL);
+
+ *phContext = 1;
+
+ if (uniform_random(400) < 1)
+ return SCARD_E_NO_SERVICE;
+ if (uniform_random(400) < 1)
+ return SCARD_E_NO_SMARTCARD;
+ if (uniform_random(400) < 1)
+ return SCARD_E_NO_MEMORY;
+ if (uniform_random(400) < 1)
+ *phContext = 0;
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG
+__wrap_SCardListReaders(SCARDCONTEXT hContext, LPCSTR mszGroups,
+ LPSTR mszReaders, LPDWORD pcchReaders)
+{
+ assert(hContext == 1);
+ assert(mszGroups == NULL);
+ assert(mszReaders != NULL);
+ assert(pcchReaders != 0);
+
+ if (reader_list == NULL || uniform_random(400) < 1)
+ return SCARD_E_NO_READERS_AVAILABLE;
+ if (uniform_random(400) < 1)
+ return SCARD_E_NO_MEMORY;
+
+ memcpy(mszReaders, reader_list->body, reader_list->len > *pcchReaders ?
+ *pcchReaders : reader_list->len);
+ *pcchReaders = (DWORD)reader_list->len; /* on purpose */
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG
+__wrap_SCardReleaseContext(SCARDCONTEXT hContext)
+{
+ assert(hContext == 1);
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG
+__wrap_SCardConnect(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode,
+ DWORD dwPreferredProtocols, LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
+{
+ uint32_t r;
+
+ assert(hContext == 1);
+ xconsume(szReader, strlen(szReader) + 1);
+ assert(dwShareMode == SCARD_SHARE_SHARED);
+ assert(dwPreferredProtocols == SCARD_PROTOCOL_ANY);
+ assert(phCard != NULL);
+ assert(pdwActiveProtocol != NULL);
+
+ if ((r = uniform_random(400)) < 1)
+ return SCARD_E_UNEXPECTED;
+
+ *phCard = 1;
+ *pdwActiveProtocol = (r & 1) ? SCARD_PROTOCOL_T0 : SCARD_PROTOCOL_T1;
+
+ if (uniform_random(400) < 1)
+ *pdwActiveProtocol = SCARD_PROTOCOL_RAW;
+
+ return SCARD_S_SUCCESS;
+}
+
+LONG
+__wrap_SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition)
+{
+ assert(hCard == 1);
+ assert(dwDisposition == SCARD_LEAVE_CARD);
+
+ return SCARD_S_SUCCESS;
+}
+
+extern void consume(const void *body, size_t len);
+
+LONG
+__wrap_SCardTransmit(SCARDHANDLE hCard, const SCARD_IO_REQUEST *pioSendPci,
+ LPCBYTE pbSendBuffer, DWORD cbSendLength, SCARD_IO_REQUEST *pioRecvPci,
+ LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength)
+{
+ void *ioh = (void *)NFC_DEV_HANDLE;
+ int n;
+
+ assert(hCard == 1);
+ xconsume(pioSendPci, sizeof(*pioSendPci));
+ xwrite(ioh, pbSendBuffer, cbSendLength);
+ assert(pioRecvPci == NULL);
+
+ if (uniform_random(400) < 1 ||
+ (n = xread(ioh, pbRecvBuffer, *pcbRecvLength, -1)) == -1)
+ return SCARD_E_UNEXPECTED;
+ *pcbRecvLength = (DWORD)n;
+
+ return SCARD_S_SUCCESS;
+}
+
+void
+set_pcsc_parameters(const struct blob *reader_list_ptr)
+{
+ reader_list = reader_list_ptr;
+}
+
+void
+set_pcsc_io_functions(int (*read_f)(void *, u_char *, size_t, int),
+ int (*write_f)(void *, const u_char *, size_t),
+ void (*consume_f)(const void *, size_t))
+{
+ xread = read_f;
+ xwrite = write_f;
+ xconsume = consume_f;
+}
diff --git a/fuzz/preload-fuzz.c b/fuzz/preload-fuzz.c
new file mode 100644
index 0000000..f18848d
--- /dev/null
+++ b/fuzz/preload-fuzz.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * cc -fPIC -D_GNU_SOURCE -shared -o preload-fuzz.so preload-fuzz.c
+ * LD_PRELOAD=$(realpath preload-fuzz.so)
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dlfcn.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define FUZZ_DEV_PREFIX "nodev"
+
+static int fd_fuzz = -1;
+static int (*open_f)(const char *, int, mode_t);
+static int (*close_f)(int);
+static ssize_t (*write_f)(int, const void *, size_t);
+
+int
+open(const char *path, int flags, ...)
+{
+ va_list ap;
+ mode_t mode;
+
+ va_start(ap, flags);
+ mode = va_arg(ap, mode_t);
+ va_end(ap);
+
+ if (open_f == NULL) {
+ open_f = dlsym(RTLD_NEXT, "open");
+ if (open_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EACCES;
+ return (-1);
+ }
+ }
+
+ if (strncmp(path, FUZZ_DEV_PREFIX, strlen(FUZZ_DEV_PREFIX)) != 0)
+ return (open_f(path, flags, mode));
+
+ if (fd_fuzz != -1) {
+ warnx("%s: fd_fuzz != -1", __func__);
+ errno = EACCES;
+ return (-1);
+ }
+
+ if ((fd_fuzz = dup(STDIN_FILENO)) < 0) {
+ warn("%s: dup", __func__);
+ errno = EACCES;
+ return (-1);
+ }
+
+ return (fd_fuzz);
+}
+
+int
+close(int fd)
+{
+ if (close_f == NULL) {
+ close_f = dlsym(RTLD_NEXT, "close");
+ if (close_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EACCES;
+ return (-1);
+ }
+ }
+
+ if (fd == fd_fuzz)
+ fd_fuzz = -1;
+
+ return (close_f(fd));
+}
+
+ssize_t
+write(int fd, const void *buf, size_t nbytes)
+{
+ if (write_f == NULL) {
+ write_f = dlsym(RTLD_NEXT, "write");
+ if (write_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EBADF;
+ return (-1);
+ }
+ }
+
+ if (fd != fd_fuzz)
+ return (write_f(fd, buf, nbytes));
+
+ return (nbytes);
+}
diff --git a/fuzz/preload-snoop.c b/fuzz/preload-snoop.c
new file mode 100644
index 0000000..34d57ad
--- /dev/null
+++ b/fuzz/preload-snoop.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * cc -fPIC -D_GNU_SOURCE -shared -o preload-snoop.so preload-snoop.c
+ * LD_PRELOAD=$(realpath preload-snoop.so)
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dlfcn.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define SNOOP_DEV_PREFIX "/dev/hidraw"
+
+struct fd_tuple {
+ int snoop_in;
+ int snoop_out;
+ int real_dev;
+};
+
+static struct fd_tuple *fd_tuple;
+static int (*open_f)(const char *, int, mode_t);
+static int (*close_f)(int);
+static ssize_t (*read_f)(int, void *, size_t);
+static ssize_t (*write_f)(int, const void *, size_t);
+
+static int
+get_fd(const char *hid_path, const char *suffix)
+{
+ char *s = NULL;
+ char path[PATH_MAX];
+ int fd;
+ int r;
+
+ if ((s = strdup(hid_path)) == NULL) {
+ warnx("%s: strdup", __func__);
+ return (-1);
+ }
+
+ for (size_t i = 0; i < strlen(s); i++)
+ if (s[i] == '/')
+ s[i] = '_';
+
+ if ((r = snprintf(path, sizeof(path), "%s-%s", s, suffix)) < 0 ||
+ (size_t)r >= sizeof(path)) {
+ warnx("%s: snprintf", __func__);
+ free(s);
+ return (-1);
+ }
+
+ free(s);
+ s = NULL;
+
+ if ((fd = open_f(path, O_CREAT | O_WRONLY, 0644)) < 0) {
+ warn("%s: open", __func__);
+ return (-1);
+ }
+
+ return (fd);
+}
+
+int
+open(const char *path, int flags, ...)
+{
+ va_list ap;
+ mode_t mode;
+
+ va_start(ap, flags);
+ mode = va_arg(ap, mode_t);
+ va_end(ap);
+
+ if (open_f == NULL) {
+ open_f = dlsym(RTLD_NEXT, "open");
+ if (open_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EACCES;
+ return (-1);
+ }
+ }
+
+ if (strncmp(path, SNOOP_DEV_PREFIX, strlen(SNOOP_DEV_PREFIX)) != 0)
+ return (open_f(path, flags, mode));
+
+ if (fd_tuple != NULL) {
+ warnx("%s: fd_tuple != NULL", __func__);
+ errno = EACCES;
+ return (-1);
+ }
+
+ if ((fd_tuple = calloc(1, sizeof(*fd_tuple))) == NULL) {
+ warn("%s: calloc", __func__);
+ errno = ENOMEM;
+ return (-1);
+ }
+
+ fd_tuple->snoop_in = -1;
+ fd_tuple->snoop_out = -1;
+ fd_tuple->real_dev = -1;
+
+ if ((fd_tuple->snoop_in = get_fd(path, "in")) < 0 ||
+ (fd_tuple->snoop_out = get_fd(path, "out")) < 0 ||
+ (fd_tuple->real_dev = open_f(path, flags, mode)) < 0) {
+ warn("%s: get_fd/open", __func__);
+ goto fail;
+ }
+
+ return (fd_tuple->real_dev);
+fail:
+ if (fd_tuple->snoop_in != -1)
+ close(fd_tuple->snoop_in);
+ if (fd_tuple->snoop_out != -1)
+ close(fd_tuple->snoop_out);
+ if (fd_tuple->real_dev != -1)
+ close(fd_tuple->real_dev);
+
+ free(fd_tuple);
+ fd_tuple = NULL;
+
+ errno = EACCES;
+
+ return (-1);
+}
+
+int
+close(int fd)
+{
+ if (close_f == NULL) {
+ close_f = dlsym(RTLD_NEXT, "close");
+ if (close_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EBADF;
+ return (-1);
+ }
+ }
+
+ if (fd_tuple == NULL || fd_tuple->real_dev != fd)
+ return (close_f(fd));
+
+ close_f(fd_tuple->snoop_in);
+ close_f(fd_tuple->snoop_out);
+ close_f(fd_tuple->real_dev);
+
+ free(fd_tuple);
+ fd_tuple = NULL;
+
+ return (0);
+}
+
+ssize_t
+read(int fd, void *buf, size_t nbytes)
+{
+ ssize_t n;
+
+ if (read_f == NULL) {
+ read_f = dlsym(RTLD_NEXT, "read");
+ if (read_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EBADF;
+ return (-1);
+ }
+ }
+
+ if (write_f == NULL) {
+ write_f = dlsym(RTLD_NEXT, "write");
+ if (write_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EBADF;
+ return (-1);
+ }
+ }
+
+ if (fd_tuple == NULL || fd_tuple->real_dev != fd)
+ return (read_f(fd, buf, nbytes));
+
+ if ((n = read_f(fd, buf, nbytes)) < 0 ||
+ write_f(fd_tuple->snoop_in, buf, n) != n)
+ return (-1);
+
+ return (n);
+}
+
+ssize_t
+write(int fd, const void *buf, size_t nbytes)
+{
+ ssize_t n;
+
+ if (write_f == NULL) {
+ write_f = dlsym(RTLD_NEXT, "write");
+ if (write_f == NULL) {
+ warnx("%s: dlsym", __func__);
+ errno = EBADF;
+ return (-1);
+ }
+ }
+
+ if (fd_tuple == NULL || fd_tuple->real_dev != fd)
+ return (write_f(fd, buf, nbytes));
+
+ if ((n = write_f(fd, buf, nbytes)) < 0 ||
+ write_f(fd_tuple->snoop_out, buf, n) != n)
+ return (-1);
+
+ return (n);
+}
diff --git a/fuzz/prng.c b/fuzz/prng.c
new file mode 100644
index 0000000..61114ac
--- /dev/null
+++ b/fuzz/prng.c
@@ -0,0 +1,113 @@
+/*
+ A C-program for MT19937, with initialization improved 2002/1/26.
+ Coded by Takuji Nishimura and Makoto Matsumoto.
+
+ Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+ All rights reserved.
+
+ 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.
+
+ 3. The names of its contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ 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 OWNER 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.
+
+
+ Any feedback is very welcome.
+ http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+ email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "mutator_aux.h"
+
+#define init_genrand prng_init
+#define genrand_int32 prng_uint32
+
+/* Period parameters */
+#define N 624
+#define M 397
+#define MATRIX_A 0x9908b0dfUL /* constant vector a */
+#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
+#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
+
+int prng_up = 0;
+static unsigned long mt[N]; /* the array for the state vector */
+static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */
+
+/* initializes mt[N] with a seed */
+void init_genrand(unsigned long s)
+{
+ mt[0]= s & 0xffffffffUL;
+ for (mti=1; mti<N; mti++) {
+ mt[mti] =
+ (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) +
+ (unsigned long)mti);
+ /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
+ /* In the previous versions, MSBs of the seed affect */
+ /* only MSBs of the array mt[]. */
+ /* 2002/01/09 modified by Makoto Matsumoto */
+ mt[mti] &= 0xffffffffUL;
+ /* for >32 bit machines */
+ }
+ prng_up = 1;
+}
+
+/* generates a random number on [0,0xffffffff]-interval */
+unsigned long genrand_int32(void)
+{
+ unsigned long y;
+ static unsigned long mag01[2]={0x0UL, MATRIX_A};
+ /* mag01[x] = x * MATRIX_A for x=0,1 */
+
+ if (mti >= N) { /* generate N words at one time */
+ int kk;
+
+ assert(mti != N+1);
+
+ for (kk=0;kk<N-M;kk++) {
+ y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
+ }
+ for (;kk<N-1;kk++) {
+ y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
+ }
+ y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+
+ /* Tempering */
+ y ^= (y >> 11);
+ y ^= (y << 7) & 0x9d2c5680UL;
+ y ^= (y << 15) & 0xefc60000UL;
+ y ^= (y >> 18);
+
+ return y;
+}
diff --git a/fuzz/report.tgz b/fuzz/report.tgz
new file mode 100644
index 0000000..9c01263
--- /dev/null
+++ b/fuzz/report.tgz
Binary files differ
diff --git a/fuzz/summary.txt b/fuzz/summary.txt
new file mode 100644
index 0000000..adda3ac
--- /dev/null
+++ b/fuzz/summary.txt
@@ -0,0 +1,64 @@
+Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+fuzz/clock.c 24 1 95.83% 4 0 100.00% 35 1 97.14%
+fuzz/pcsc.c 59 0 100.00% 8 0 100.00% 75 12 84.00%
+fuzz/prng.c 31 0 100.00% 2 0 100.00% 35 1 97.14%
+fuzz/udev.c 110 2 98.18% 17 0 100.00% 126 12 90.48%
+fuzz/uniform_random.c 7 1 85.71% 1 0 100.00% 12 1 91.67%
+fuzz/wrap.c 23 0 100.00% 3 0 100.00% 29 0 100.00%
+openbsd-compat/explicit_bzero.c 4 0 100.00% 1 0 100.00% 7 0 100.00%
+openbsd-compat/freezero.c 4 0 100.00% 1 0 100.00% 6 0 100.00%
+openbsd-compat/recallocarray.c 41 7 82.93% 1 0 100.00% 36 7 80.56%
+openbsd-compat/timingsafe_bcmp.c 4 0 100.00% 1 0 100.00% 7 0 100.00%
+src/aes256.c 118 3 97.46% 8 0 100.00% 157 11 92.99%
+src/assert.c 628 45 92.83% 63 4 93.65% 782 51 93.48%
+src/authkey.c 52 0 100.00% 5 0 100.00% 66 0 100.00%
+src/bio.c 451 20 95.57% 49 2 95.92% 587 24 95.91%
+src/blob.c 53 2 96.23% 10 0 100.00% 83 4 95.18%
+src/buf.c 8 1 87.50% 2 0 100.00% 16 1 93.75%
+src/cbor.c 1070 12 98.88% 55 0 100.00% 1258 28 97.77%
+src/compress.c 105 14 86.67% 5 0 100.00% 122 24 80.33%
+src/config.c 112 0 100.00% 11 0 100.00% 154 0 100.00%
+src/cred.c 653 36 94.49% 70 2 97.14% 853 39 95.43%
+src/credman.c 422 10 97.63% 40 0 100.00% 557 20 96.41%
+src/dev.c 332 65 80.42% 41 6 85.37% 378 80 78.84%
+src/ecdh.c 117 2 98.29% 4 0 100.00% 146 5 96.58%
+src/eddsa.c 88 5 94.32% 10 0 100.00% 114 9 92.11%
+src/err.c 122 10 91.80% 1 0 100.00% 126 10 92.06%
+src/es256.c 315 5 98.41% 19 0 100.00% 372 11 97.04%
+src/es384.c 158 5 96.84% 11 0 100.00% 198 11 94.44%
+src/hid.c 87 2 97.70% 14 0 100.00% 145 3 97.93%
+src/hid_linux.c 184 73 60.33% 14 7 50.00% 263 115 56.27%
+src/hid_unix.c 29 21 27.59% 2 0 100.00% 43 26 39.53%
+src/info.c 232 0 100.00% 51 0 100.00% 409 0 100.00%
+src/io.c 193 7 96.37% 13 0 100.00% 230 12 94.78%
+src/iso7816.c 18 1 94.44% 5 0 100.00% 38 1 97.37%
+src/largeblob.c 525 18 96.57% 30 0 100.00% 693 43 93.80%
+src/log.c 39 5 87.18% 7 1 85.71% 63 7 88.89%
+src/netlink.c 329 8 97.57% 40 0 100.00% 498 15 96.99%
+src/nfc.c 155 5 96.77% 12 0 100.00% 244 15 93.85%
+src/nfc_linux.c 172 77 55.23% 13 7 46.15% 242 126 47.93%
+src/pcsc.c 204 1 99.51% 13 0 100.00% 282 3 98.94%
+src/pin.c 426 3 99.30% 26 0 100.00% 514 4 99.22%
+src/random.c 6 0 100.00% 1 0 100.00% 6 0 100.00%
+src/reset.c 24 0 100.00% 3 0 100.00% 23 0 100.00%
+src/rs1.c 22 2 90.91% 3 0 100.00% 36 6 83.33%
+src/rs256.c 146 9 93.84% 13 0 100.00% 179 16 91.06%
+src/time.c 43 3 93.02% 3 0 100.00% 43 2 95.35%
+src/touch.c 67 0 100.00% 2 0 100.00% 79 0 100.00%
+src/tpm.c 103 0 100.00% 9 0 100.00% 194 0 100.00%
+src/types.c 29 0 100.00% 7 0 100.00% 56 0 100.00%
+src/u2f.c 572 4 99.30% 17 0 100.00% 726 12 98.35%
+src/util.c 14 1 92.86% 1 0 100.00% 14 1 92.86%
+
+Files which contain no functions:
+fuzz/mutator_aux.h 0 0 - 0 0 - 0 0 -
+openbsd-compat/openbsd-compat.h 0 0 - 0 0 - 0 0 -
+openbsd-compat/time.h 0 0 - 0 0 - 0 0 -
+src/extern.h 0 0 - 0 0 - 0 0 -
+src/fallthrough.h 0 0 - 0 0 - 0 0 -
+src/fido.h 0 0 - 0 0 - 0 0 -
+src/fido/err.h 0 0 - 0 0 - 0 0 -
+src/fido/param.h 0 0 - 0 0 - 0 0 -
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+TOTAL 8730 486 94.43% 742 29 96.09% 11357 769 93.23%
diff --git a/fuzz/udev.c b/fuzz/udev.c
new file mode 100644
index 0000000..3194012
--- /dev/null
+++ b/fuzz/udev.c
@@ -0,0 +1,270 @@
+/*
+ * 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 <sys/types.h>
+
+#include <linux/hidraw.h>
+#include <linux/input.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <libudev.h>
+#include <stdlib.h>
+
+#include "mutator_aux.h"
+
+struct udev {
+ int magic;
+};
+
+struct udev_enumerate {
+ int magic;
+ struct udev_list_entry *list_entry;
+};
+
+struct udev_list_entry {
+ int magic;
+};
+
+struct udev_device {
+ int magic;
+ struct udev_device *parent;
+};
+
+#define UDEV_MAGIC 0x584492cc
+#define UDEV_DEVICE_MAGIC 0x569180dd
+#define UDEV_LIST_ENTRY_MAGIC 0x497422ee
+#define UDEV_ENUM_MAGIC 0x583570ff
+
+#define ASSERT_TYPE(x, m) assert((x) != NULL && (x)->magic == (m))
+#define ASSERT_UDEV(x) ASSERT_TYPE((x), UDEV_MAGIC)
+#define ASSERT_UDEV_ENUM(x) ASSERT_TYPE((x), UDEV_ENUM_MAGIC)
+#define ASSERT_UDEV_LIST_ENTRY(x) ASSERT_TYPE((x), UDEV_LIST_ENTRY_MAGIC)
+#define ASSERT_UDEV_DEVICE(x) ASSERT_TYPE((x), UDEV_DEVICE_MAGIC)
+
+static const char *uevent;
+static const struct blob *report_descriptor;
+
+struct udev *__wrap_udev_new(void);
+struct udev_device *__wrap_udev_device_get_parent_with_subsystem_devtype(
+ struct udev_device *, const char *, const char *);
+struct udev_device *__wrap_udev_device_new_from_syspath(struct udev *,
+ const char *);
+struct udev_enumerate *__wrap_udev_enumerate_new(struct udev *);
+struct udev_list_entry *__wrap_udev_enumerate_get_list_entry(
+ struct udev_enumerate *);
+struct udev_list_entry *__wrap_udev_list_entry_get_next(
+ struct udev_list_entry *);
+const char *__wrap_udev_device_get_sysattr_value(struct udev_device *,
+ const char *);
+const char *__wrap_udev_list_entry_get_name(struct udev_list_entry *);
+const char *__wrap_udev_device_get_devnode(struct udev_device *);
+const char *__wrap_udev_device_get_sysnum(struct udev_device *);
+int __wrap_udev_enumerate_add_match_subsystem(struct udev_enumerate *,
+ const char *);
+int __wrap_udev_enumerate_scan_devices(struct udev_enumerate *);
+int __wrap_ioctl(int, unsigned long , ...);
+void __wrap_udev_device_unref(struct udev_device *);
+void __wrap_udev_enumerate_unref(struct udev_enumerate *);
+void __wrap_udev_unref(struct udev *);
+void set_udev_parameters(const char *, const struct blob *);
+
+struct udev_device *
+__wrap_udev_device_get_parent_with_subsystem_devtype(struct udev_device *child,
+ const char *subsystem, const char *devtype)
+{
+ ASSERT_UDEV_DEVICE(child);
+ fido_log_debug("%s", subsystem); /* XXX consume */
+ fido_log_debug("%s", devtype); /* XXX consume */
+ if (child->parent != NULL)
+ return child->parent;
+ if ((child->parent = calloc(1, sizeof(*child->parent))) == NULL)
+ return NULL;
+ child->parent->magic = UDEV_DEVICE_MAGIC;
+
+ return child->parent;
+}
+
+const char *
+__wrap_udev_device_get_sysattr_value(struct udev_device *udev_device,
+ const char *sysattr)
+{
+ ASSERT_UDEV_DEVICE(udev_device);
+ if (uniform_random(400) < 1)
+ return NULL;
+ if (!strcmp(sysattr, "manufacturer") || !strcmp(sysattr, "product"))
+ return "product info"; /* XXX randomise? */
+ else if (!strcmp(sysattr, "uevent"))
+ return uevent;
+
+ return NULL;
+}
+
+const char *
+__wrap_udev_list_entry_get_name(struct udev_list_entry *entry)
+{
+ ASSERT_UDEV_LIST_ENTRY(entry);
+ return uniform_random(400) < 1 ? NULL : "name"; /* XXX randomise? */
+}
+
+struct udev_device *
+__wrap_udev_device_new_from_syspath(struct udev *udev, const char *syspath)
+{
+ struct udev_device *udev_device;
+
+ ASSERT_UDEV(udev);
+ fido_log_debug("%s", syspath);
+ if ((udev_device = calloc(1, sizeof(*udev_device))) == NULL)
+ return NULL;
+ udev_device->magic = UDEV_DEVICE_MAGIC;
+
+ return udev_device;
+}
+
+const char *
+__wrap_udev_device_get_devnode(struct udev_device *udev_device)
+{
+ ASSERT_UDEV_DEVICE(udev_device);
+ return uniform_random(400) < 1 ? NULL : "/dev/zero";
+}
+
+const char *
+__wrap_udev_device_get_sysnum(struct udev_device *udev_device)
+{
+ ASSERT_UDEV_DEVICE(udev_device);
+ return uniform_random(400) < 1 ? NULL : "101010"; /* XXX randomise? */
+}
+
+void
+__wrap_udev_device_unref(struct udev_device *udev_device)
+{
+ ASSERT_UDEV_DEVICE(udev_device);
+ if (udev_device->parent) {
+ ASSERT_UDEV_DEVICE(udev_device->parent);
+ free(udev_device->parent);
+ }
+ free(udev_device);
+}
+
+struct udev *
+__wrap_udev_new(void)
+{
+ struct udev *udev;
+
+ if ((udev = calloc(1, sizeof(*udev))) == NULL)
+ return NULL;
+ udev->magic = UDEV_MAGIC;
+
+ return udev;
+}
+
+struct udev_enumerate *
+__wrap_udev_enumerate_new(struct udev *udev)
+{
+ struct udev_enumerate *udev_enum;
+
+ ASSERT_UDEV(udev);
+ if ((udev_enum = calloc(1, sizeof(*udev_enum))) == NULL)
+ return NULL;
+ udev_enum->magic = UDEV_ENUM_MAGIC;
+
+ return udev_enum;
+}
+
+int
+__wrap_udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enum,
+ const char *subsystem)
+{
+ ASSERT_UDEV_ENUM(udev_enum);
+ fido_log_debug("%s:", subsystem);
+ return uniform_random(400) < 1 ? -EINVAL : 0;
+}
+
+int
+__wrap_udev_enumerate_scan_devices(struct udev_enumerate *udev_enum)
+{
+ ASSERT_UDEV_ENUM(udev_enum);
+ return uniform_random(400) < 1 ? -EINVAL : 0;
+}
+
+struct udev_list_entry *
+__wrap_udev_enumerate_get_list_entry(struct udev_enumerate *udev_enum)
+{
+ ASSERT_UDEV_ENUM(udev_enum);
+ if ((udev_enum->list_entry = calloc(1,
+ sizeof(*udev_enum->list_entry))) == NULL)
+ return NULL;
+ udev_enum->list_entry->magic = UDEV_LIST_ENTRY_MAGIC;
+
+ return udev_enum->list_entry;
+}
+
+struct udev_list_entry *
+__wrap_udev_list_entry_get_next(struct udev_list_entry *udev_list_entry)
+{
+ ASSERT_UDEV_LIST_ENTRY(udev_list_entry);
+ return uniform_random(400) < 1 ? NULL : udev_list_entry;
+}
+
+void
+__wrap_udev_enumerate_unref(struct udev_enumerate *udev_enum)
+{
+ ASSERT_UDEV_ENUM(udev_enum);
+ if (udev_enum->list_entry)
+ ASSERT_UDEV_LIST_ENTRY(udev_enum->list_entry);
+ free(udev_enum->list_entry);
+ free(udev_enum);
+}
+
+void
+__wrap_udev_unref(struct udev *udev)
+{
+ ASSERT_UDEV(udev);
+ free(udev);
+}
+
+int
+__wrap_ioctl(int fd, unsigned long request, ...)
+{
+ va_list ap;
+ struct hidraw_report_descriptor *hrd;
+
+ (void)fd;
+
+ if (uniform_random(400) < 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ va_start(ap, request);
+
+ switch (IOCTL_REQ(request)) {
+ case IOCTL_REQ(HIDIOCGRDESCSIZE):
+ *va_arg(ap, int *) = (int)report_descriptor->len;
+ break;
+ case IOCTL_REQ(HIDIOCGRDESC):
+ hrd = va_arg(ap, struct hidraw_report_descriptor *);
+ assert(hrd->size == report_descriptor->len);
+ memcpy(hrd->value, report_descriptor->body, hrd->size);
+ break;
+ default:
+ warnx("%s: unknown request 0x%lx", __func__, request);
+ abort();
+ }
+
+ va_end(ap);
+
+ return 0;
+}
+
+void
+set_udev_parameters(const char *uevent_ptr,
+ const struct blob *report_descriptor_ptr)
+{
+ uevent = uevent_ptr;
+ report_descriptor = report_descriptor_ptr;
+}
diff --git a/fuzz/uniform_random.c b/fuzz/uniform_random.c
new file mode 100644
index 0000000..357091c
--- /dev/null
+++ b/fuzz/uniform_random.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+uint32_t uniform_random(uint32_t);
+unsigned long prng_uint32(void);
+
+/*
+ * Calculate a uniformly distributed random number less than upper_bound
+ * avoiding "modulo bias".
+ *
+ * Uniformity is achieved by generating new random numbers until the one
+ * returned is outside the range [0, 2**32 % upper_bound). This
+ * guarantees the selected random number will be inside
+ * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound)
+ * after reduction modulo upper_bound.
+ */
+uint32_t
+uniform_random(uint32_t upper_bound)
+{
+ uint32_t r, min;
+
+ if (upper_bound < 2)
+ return 0;
+
+ /* 2**32 % x == (2**32 - x) % x */
+ min = -upper_bound % upper_bound;
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ for (;;) {
+ r = (uint32_t)prng_uint32();
+ if (r >= min)
+ break;
+ }
+
+ return r % upper_bound;
+}
diff --git a/fuzz/wiredata_fido2.h b/fuzz/wiredata_fido2.h
new file mode 100644
index 0000000..6c66c54
--- /dev/null
+++ b/fuzz/wiredata_fido2.h
@@ -0,0 +1,708 @@
+/*
+ * 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
+ */
+
+#ifndef _WIREDATA_FIDO2_H
+#define _WIREDATA_FIDO2_H
+
+#define WIREDATA_CTAP_INIT \
+ 0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11, 0x80, \
+ 0x43, 0x56, 0x40, 0xb1, 0x4e, 0xd9, 0x2d, 0x00, \
+ 0x22, 0x00, 0x02, 0x02, 0x05, 0x02, 0x01, 0x05, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_KEEPALIVE \
+ 0x00, 0x22, 0x00, 0x02, 0xbb, 0x00, 0x01, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_INFO \
+ 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0xb9, 0x00, \
+ 0xa9, 0x01, 0x83, 0x66, 0x55, 0x32, 0x46, 0x5f, \
+ 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4f, 0x5f, \
+ 0x32, 0x5f, 0x30, 0x6c, 0x46, 0x49, 0x44, 0x4f, \
+ 0x5f, 0x32, 0x5f, 0x31, 0x5f, 0x50, 0x52, 0x45, \
+ 0x02, 0x82, 0x6b, 0x63, 0x72, 0x65, 0x64, 0x50, \
+ 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x6b, 0x68, \
+ 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, \
+ 0x00, 0x22, 0x00, 0x02, 0x00, 0x65, 0x74, 0x03, \
+ 0x50, 0x19, 0x56, 0xe5, 0xbd, 0xa3, 0x74, 0x45, \
+ 0xf1, 0xa8, 0x14, 0x35, 0x64, 0x03, 0xfd, 0xbc, \
+ 0x18, 0x04, 0xa5, 0x62, 0x72, 0x6b, 0xf5, 0x62, \
+ 0x75, 0x70, 0xf5, 0x64, 0x70, 0x6c, 0x61, 0x74, \
+ 0xf4, 0x69, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, \
+ 0x50, 0x69, 0x6e, 0xf4, 0x75, 0x63, 0x72, 0x65, \
+ 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4d, \
+ 0x00, 0x22, 0x00, 0x02, 0x01, 0x67, 0x6d, 0x74, \
+ 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0xf5, \
+ 0x05, 0x19, 0x04, 0xb0, 0x06, 0x81, 0x01, 0x07, \
+ 0x08, 0x08, 0x18, 0x80, 0x0a, 0x82, 0xa2, 0x63, \
+ 0x61, 0x6c, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, \
+ 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, \
+ 0x2d, 0x6b, 0x65, 0x79, 0xa2, 0x63, 0x61, 0x6c, \
+ 0x67, 0x27, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6a, \
+ 0x00, 0x22, 0x00, 0x02, 0x02, 0x70, 0x75, 0x62, \
+ 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_AUTHKEY \
+ 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x51, 0x00, \
+ 0xa1, 0x01, 0xa5, 0x01, 0x02, 0x03, 0x38, 0x18, \
+ 0x20, 0x01, 0x21, 0x58, 0x20, 0x2a, 0xb8, 0x2d, \
+ 0x36, 0x69, 0xab, 0x30, 0x9d, 0xe3, 0x5e, 0x9b, \
+ 0xfb, 0x94, 0xfc, 0x1d, 0x92, 0x95, 0xaf, 0x01, \
+ 0x47, 0xfe, 0x4b, 0x87, 0xe5, 0xcf, 0x3f, 0x05, \
+ 0x0b, 0x39, 0xda, 0x17, 0x49, 0x22, 0x58, 0x20, \
+ 0x15, 0x1b, 0xbe, 0x08, 0x78, 0x60, 0x4d, 0x3c, \
+ 0x00, 0x22, 0x00, 0x02, 0x00, 0x3f, 0xf1, 0x60, \
+ 0xa6, 0xd8, 0xf8, 0xed, 0xce, 0x4a, 0x30, 0x5d, \
+ 0x1a, 0xaf, 0x80, 0xc4, 0x0a, 0xd2, 0x6f, 0x77, \
+ 0x38, 0x12, 0x97, 0xaa, 0xbd, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_PINTOKEN \
+ 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x14, 0x00, \
+ 0xa1, 0x02, 0x50, 0xee, 0x40, 0x4c, 0x85, 0xd7, \
+ 0xa1, 0x2f, 0x56, 0xc4, 0x4e, 0xc5, 0x93, 0x41, \
+ 0xd0, 0x3b, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_STATUS \
+ 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x01, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_RETRIES \
+ 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x04, 0x00, \
+ 0xa1, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_ASSERT \
+ 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0xcb, 0x00, \
+ 0xa3, 0x01, 0xa2, 0x62, 0x69, 0x64, 0x58, 0x40, \
+ 0x4a, 0x4c, 0x9e, 0xcc, 0x81, 0x7d, 0x42, 0x03, \
+ 0x2b, 0x41, 0xd1, 0x38, 0xd3, 0x49, 0xb4, 0xfc, \
+ 0xfb, 0xe4, 0x4e, 0xe4, 0xff, 0x76, 0x34, 0x16, \
+ 0x68, 0x06, 0x9d, 0xa6, 0x01, 0x32, 0xb9, 0xff, \
+ 0xc2, 0x35, 0x0d, 0x89, 0x43, 0x66, 0x12, 0xf8, \
+ 0x8e, 0x5b, 0xde, 0xf4, 0xcc, 0xec, 0x9d, 0x03, \
+ 0x00, 0x92, 0x00, 0x0e, 0x00, 0x85, 0xc2, 0xf5, \
+ 0xe6, 0x8e, 0xeb, 0x3f, 0x3a, 0xec, 0xc3, 0x1d, \
+ 0x04, 0x6e, 0xf3, 0x5b, 0x88, 0x64, 0x74, 0x79, \
+ 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, \
+ 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x02, 0x58, 0x25, \
+ 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, 0x8c, 0x68, \
+ 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, 0x60, 0x5b, \
+ 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7, \
+ 0x00, 0x92, 0x00, 0x0e, 0x01, 0x99, 0x5c, 0xf3, \
+ 0xba, 0x83, 0x1d, 0x97, 0x63, 0x04, 0x00, 0x00, \
+ 0x00, 0x09, 0x03, 0x58, 0x47, 0x30, 0x45, 0x02, \
+ 0x21, 0x00, 0xcf, 0x3f, 0x36, 0x0e, 0x1f, 0x6f, \
+ 0xd6, 0xa0, 0x9d, 0x13, 0xcf, 0x55, 0xf7, 0x49, \
+ 0x8f, 0xc8, 0xc9, 0x03, 0x12, 0x76, 0x41, 0x75, \
+ 0x7b, 0xb5, 0x0a, 0x90, 0xa5, 0x82, 0x26, 0xf1, \
+ 0x6b, 0x80, 0x02, 0x20, 0x34, 0x9b, 0x7a, 0x82, \
+ 0x00, 0x92, 0x00, 0x0e, 0x02, 0xd3, 0xe1, 0x79, \
+ 0x49, 0x55, 0x41, 0x9f, 0xa4, 0x06, 0x06, 0xbd, \
+ 0xc8, 0xb9, 0x2b, 0x5f, 0xe1, 0xa7, 0x99, 0x1c, \
+ 0xa1, 0xfc, 0x7e, 0x3e, 0xd5, 0x85, 0x2e, 0x11, \
+ 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_CRED \
+ 0x00, 0x91, 0x00, 0x03, 0x90, 0x03, 0xe1, 0x00, \
+ 0xa3, 0x01, 0x66, 0x70, 0x61, 0x63, 0x6b, 0x65, \
+ 0x64, 0x02, 0x58, 0xc4, 0x49, 0x96, 0x0d, 0xe5, \
+ 0x88, 0x0e, 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, \
+ 0x64, 0x76, 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, \
+ 0xa2, 0x86, 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, \
+ 0x83, 0x1d, 0x97, 0x63, 0x45, 0x00, 0x00, 0x00, \
+ 0x00, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, \
+ 0x00, 0x91, 0x00, 0x03, 0x00, 0x15, 0x80, 0x06, \
+ 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x40, \
+ 0xed, 0x88, 0x48, 0xa1, 0xdb, 0x56, 0x4d, 0x0f, \
+ 0x0d, 0xc8, 0x8f, 0x0f, 0xe9, 0x16, 0xb1, 0x78, \
+ 0xa9, 0x40, 0x98, 0x71, 0xa0, 0xb3, 0xf2, 0xcf, \
+ 0x05, 0x73, 0x6c, 0x12, 0xbf, 0x00, 0x96, 0xf3, \
+ 0x7b, 0x93, 0xba, 0x49, 0xee, 0x23, 0xb4, 0x78, \
+ 0x2e, 0xfb, 0xce, 0x27, 0xa8, 0xc2, 0x26, 0x78, \
+ 0x00, 0x91, 0x00, 0x03, 0x01, 0xcc, 0x95, 0x2d, \
+ 0x40, 0xdb, 0xd1, 0x40, 0x3d, 0x2b, 0xa3, 0x31, \
+ 0xa0, 0x75, 0x82, 0x63, 0xf0, 0xa5, 0x01, 0x02, \
+ 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x9d, \
+ 0x95, 0xa1, 0xb5, 0xd6, 0x11, 0xbf, 0xe2, 0x28, \
+ 0xa0, 0x7f, 0xca, 0x1e, 0xd9, 0x09, 0x0f, 0x0d, \
+ 0xe7, 0x8e, 0x29, 0xe8, 0x2e, 0x11, 0xdb, 0x55, \
+ 0x62, 0x13, 0xd7, 0x26, 0xc2, 0x7e, 0x2b, 0x22, \
+ 0x00, 0x91, 0x00, 0x03, 0x02, 0x58, 0x20, 0xbe, \
+ 0x74, 0x2a, 0xac, 0xde, 0x11, 0x40, 0x76, 0x31, \
+ 0x0b, 0xed, 0x55, 0xde, 0xf3, 0x03, 0xe4, 0x1c, \
+ 0xac, 0x42, 0x63, 0x8f, 0xe8, 0x30, 0x63, 0xb7, \
+ 0x07, 0x4e, 0x5d, 0xfb, 0x17, 0x5e, 0x9b, 0x03, \
+ 0xa3, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, \
+ 0x69, 0x67, 0x58, 0x48, 0x30, 0x46, 0x02, 0x21, \
+ 0x00, 0xfb, 0xd1, 0x26, 0x76, 0x34, 0x74, 0xac, \
+ 0x00, 0x91, 0x00, 0x03, 0x03, 0xf6, 0xd8, 0x5c, \
+ 0x5d, 0xbc, 0xda, 0xe0, 0x43, 0xe0, 0xa5, 0x42, \
+ 0x9f, 0xc7, 0xe2, 0x18, 0x3e, 0xe2, 0x2c, 0x94, \
+ 0x78, 0xbf, 0x9c, 0xeb, 0x3e, 0x9d, 0x02, 0x21, \
+ 0x00, 0xab, 0x21, 0x1b, 0xc4, 0x30, 0x69, 0xee, \
+ 0x7f, 0x09, 0xe6, 0x6b, 0x99, 0x98, 0x34, 0x07, \
+ 0x7b, 0x9a, 0x58, 0xb2, 0xe8, 0x77, 0xe0, 0xba, \
+ 0x7d, 0xab, 0x65, 0xf8, 0xba, 0x2a, 0xcb, 0x9a, \
+ 0x00, 0x91, 0x00, 0x03, 0x04, 0x41, 0x63, 0x78, \
+ 0x35, 0x63, 0x81, 0x59, 0x02, 0xb3, 0x30, 0x82, \
+ 0x02, 0xaf, 0x30, 0x82, 0x01, 0x97, 0xa0, 0x03, \
+ 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x5b, 0x3d, \
+ 0xb6, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, \
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, \
+ 0x30, 0x21, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, \
+ 0x55, 0x04, 0x03, 0x0c, 0x16, 0x59, 0x75, 0x62, \
+ 0x00, 0x91, 0x00, 0x03, 0x05, 0x69, 0x63, 0x6f, \
+ 0x20, 0x46, 0x49, 0x44, 0x4f, 0x20, 0x50, 0x72, \
+ 0x65, 0x76, 0x69, 0x65, 0x77, 0x20, 0x43, 0x41, \
+ 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x34, \
+ 0x31, 0x32, 0x31, 0x30, 0x35, 0x37, 0x31, 0x30, \
+ 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x31, 0x32, 0x33, \
+ 0x31, 0x31, 0x30, 0x35, 0x37, 0x31, 0x30, 0x5a, \
+ 0x30, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, \
+ 0x00, 0x91, 0x00, 0x03, 0x06, 0x55, 0x04, 0x06, \
+ 0x13, 0x02, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, \
+ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x59, \
+ 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, \
+ 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, \
+ 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, \
+ 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, \
+ 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, \
+ 0x00, 0x91, 0x00, 0x03, 0x07, 0x74, 0x69, 0x6f, \
+ 0x6e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, \
+ 0x04, 0x03, 0x0c, 0x1f, 0x59, 0x75, 0x62, 0x69, \
+ 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, \
+ 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, \
+ 0x20, 0x31, 0x32, 0x31, 0x33, 0x39, 0x33, 0x39, \
+ 0x31, 0x32, 0x36, 0x30, 0x59, 0x30, 0x13, 0x06, \
+ 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, \
+ 0x00, 0x91, 0x00, 0x03, 0x08, 0x06, 0x08, 0x2a, \
+ 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, \
+ 0x42, 0x00, 0x04, 0xfb, 0x2c, 0xdd, 0x30, 0x43, \
+ 0x28, 0xc5, 0x72, 0x4a, 0x50, 0xcc, 0xe6, 0xf6, \
+ 0x0b, 0xad, 0x7d, 0x27, 0xa9, 0x1b, 0x59, 0xe1, \
+ 0xe6, 0x6f, 0x29, 0x7b, 0x89, 0xc9, 0xd4, 0x3d, \
+ 0xc2, 0xb2, 0xc7, 0x78, 0x89, 0xb4, 0xf0, 0xff, \
+ 0x9d, 0x02, 0x28, 0xcb, 0x94, 0x6d, 0xfc, 0xe0, \
+ 0x00, 0x91, 0x00, 0x03, 0x09, 0x1b, 0x19, 0x58, \
+ 0x9b, 0x67, 0x80, 0x4a, 0xac, 0x97, 0x7f, 0x28, \
+ 0x18, 0x9c, 0xcd, 0xb3, 0x25, 0x74, 0xca, 0x28, \
+ 0xa3, 0x6c, 0x30, 0x6a, 0x30, 0x22, 0x06, 0x09, \
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, \
+ 0x02, 0x04, 0x15, 0x31, 0x2e, 0x33, 0x2e, 0x36, \
+ 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, \
+ 0x31, 0x34, 0x38, 0x32, 0x2e, 0x31, 0x2e, 0x36, \
+ 0x00, 0x91, 0x00, 0x03, 0x0a, 0x30, 0x13, 0x06, \
+ 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, \
+ 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, \
+ 0x04, 0x30, 0x30, 0x21, 0x06, 0x0b, 0x2b, 0x06, \
+ 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x01, 0x01, \
+ 0x04, 0x04, 0x12, 0x04, 0x10, 0xf8, 0xa0, 0x11, \
+ 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, \
+ 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x30, 0x0c, 0x06, \
+ 0x00, 0x91, 0x00, 0x03, 0x0b, 0x03, 0x55, 0x1d, \
+ 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, \
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, \
+ 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, \
+ 0x82, 0x01, 0x01, 0x00, 0x32, 0xf3, 0xe4, 0xbd, \
+ 0x58, 0xd7, 0x42, 0x2b, 0xaf, 0x49, 0x99, 0x86, \
+ 0x08, 0x1f, 0x0d, 0xa9, 0x3b, 0xc6, 0xaa, 0x1c, \
+ 0x72, 0x11, 0xf9, 0x28, 0x53, 0xeb, 0xf3, 0xeb, \
+ 0x00, 0x91, 0x00, 0x03, 0x0c, 0x73, 0xda, 0x69, \
+ 0x3b, 0x06, 0xde, 0x31, 0x33, 0x8e, 0x5d, 0x02, \
+ 0xec, 0xf6, 0x76, 0xe9, 0x5c, 0x42, 0xbe, 0xa5, \
+ 0x8f, 0x25, 0xd3, 0x37, 0x3f, 0x77, 0xbb, 0x2a, \
+ 0x9d, 0x7c, 0xb2, 0x3e, 0x11, 0x8c, 0x41, 0xd4, \
+ 0x9a, 0x4c, 0x9a, 0xd8, 0xf3, 0xe2, 0xa4, 0xec, \
+ 0x01, 0x77, 0x7a, 0x74, 0xa8, 0xc4, 0x12, 0x43, \
+ 0xc3, 0x1e, 0xce, 0x20, 0x8f, 0x2d, 0x0f, 0x6e, \
+ 0x00, 0x91, 0x00, 0x03, 0x0d, 0xbc, 0x61, 0x9b, \
+ 0xe1, 0x84, 0xa1, 0x72, 0xf6, 0xa9, 0xac, 0xcb, \
+ 0xf8, 0x73, 0x6d, 0x5b, 0xe2, 0x98, 0xb3, 0x6b, \
+ 0xec, 0xe7, 0x1e, 0x77, 0x8d, 0x0a, 0x69, 0xaa, \
+ 0xf9, 0x94, 0xb8, 0x63, 0x6d, 0xe8, 0xfa, 0xf6, \
+ 0x2f, 0xd3, 0xce, 0x7f, 0x04, 0x4c, 0x32, 0x2c, \
+ 0xf7, 0x26, 0x3e, 0x34, 0x99, 0xe6, 0xa5, 0xb2, \
+ 0xb0, 0x2a, 0xbb, 0xad, 0x5b, 0xd9, 0xec, 0xe5, \
+ 0x00, 0x91, 0x00, 0x03, 0x0e, 0xb0, 0x71, 0x4d, \
+ 0x73, 0xbb, 0x94, 0x61, 0x49, 0x9c, 0x94, 0x2a, \
+ 0x5f, 0x1d, 0xcc, 0xaf, 0x65, 0x03, 0x3b, 0x39, \
+ 0x39, 0xd4, 0x47, 0xd9, 0xfc, 0xc4, 0x7b, 0x0b, \
+ 0x16, 0xd8, 0xe9, 0x01, 0xfc, 0xec, 0x3f, 0x8c, \
+ 0x1b, 0xc0, 0xc6, 0xac, 0x0b, 0x5d, 0x74, 0xc7, \
+ 0xbb, 0x03, 0x05, 0x69, 0x17, 0xe9, 0x98, 0x1a, \
+ 0x19, 0xb9, 0x09, 0x5c, 0xa1, 0xf4, 0xab, 0x9f, \
+ 0x00, 0x91, 0x00, 0x03, 0x0f, 0x02, 0x7c, 0x28, \
+ 0x0f, 0x8a, 0xf9, 0xed, 0x1d, 0x29, 0x3c, 0xf6, \
+ 0xcc, 0x2f, 0x04, 0x6d, 0x9a, 0xd6, 0x62, 0xb4, \
+ 0xa9, 0x6e, 0xb1, 0xca, 0xca, 0xac, 0x5e, 0x05, \
+ 0x3e, 0x83, 0x91, 0x47, 0x7c, 0x1f, 0x8b, 0x60, \
+ 0x01, 0xde, 0x65, 0x3a, 0xbf, 0xf2, 0xaa, 0xbb, \
+ 0x55, 0x98, 0x86, 0x91, 0x7e, 0xad, 0x3b, 0x36, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_CREDMAN_META \
+ 0x00, 0x12, 0x00, 0x04, 0x90, 0x00, 0x07, 0x00, \
+ 0xa2, 0x01, 0x00, 0x02, 0x18, 0x19, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_CREDMAN_RPLIST \
+ 0x00, 0x15, 0x00, 0x02, 0x90, 0x00, 0x37, 0x00, \
+ 0xa3, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x6a, 0x79, \
+ 0x75, 0x62, 0x69, 0x63, 0x6f, 0x2e, 0x63, 0x6f, \
+ 0x6d, 0x04, 0x58, 0x20, 0x37, 0x82, 0x09, 0xb7, \
+ 0x2d, 0xef, 0xcb, 0xa9, 0x1d, 0xcb, 0xf8, 0x54, \
+ 0xed, 0xb4, 0xda, 0xa6, 0x48, 0x82, 0x8a, 0x2c, \
+ 0xbd, 0x18, 0x0a, 0xfc, 0x77, 0xa7, 0x44, 0x34, \
+ 0x65, 0x5a, 0x1c, 0x7d, 0x05, 0x03, 0x00, 0x00, \
+ 0x00, 0x15, 0x00, 0x02, 0x90, 0x00, 0x36, 0x00, \
+ 0xa2, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x6b, 0x79, \
+ 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x2e, 0x6f, \
+ 0x72, 0x67, 0x04, 0x58, 0x20, 0x12, 0x6b, 0xba, \
+ 0x6a, 0x2d, 0x7a, 0x81, 0x84, 0x25, 0x7b, 0x74, \
+ 0xdd, 0x1d, 0xdd, 0x46, 0xb6, 0x2a, 0x8c, 0xa2, \
+ 0xa7, 0x83, 0xfe, 0xdb, 0x5b, 0x19, 0x48, 0x73, \
+ 0x55, 0xb7, 0xe3, 0x46, 0x09, 0x00, 0x00, 0x00, \
+ 0x00, 0x15, 0x00, 0x02, 0x90, 0x00, 0x37, 0x00, \
+ 0xa2, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x6c, 0x77, \
+ 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, \
+ 0x64, 0x65, 0x76, 0x04, 0x58, 0x20, 0xd6, 0x32, \
+ 0x7d, 0x8c, 0x6a, 0x5d, 0xe6, 0xae, 0x0e, 0x33, \
+ 0xd0, 0xa3, 0x31, 0xfb, 0x67, 0x77, 0xb9, 0x4e, \
+ 0xf4, 0x73, 0x19, 0xfe, 0x7e, 0xfd, 0xfa, 0x82, \
+ 0x70, 0x8e, 0x1f, 0xbb, 0xa2, 0x55, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_CREDMAN_RKLIST \
+ 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xc5, 0x00, \
+ 0xa5, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \
+ 0xe4, 0xe1, 0x06, 0x31, 0xde, 0x00, 0x0f, 0x4f, \
+ 0x12, 0x6e, 0xc9, 0x68, 0x2d, 0x43, 0x3f, 0xf1, \
+ 0x02, 0x2c, 0x6e, 0xe6, 0x96, 0x10, 0xbf, 0x73, \
+ 0x35, 0xc9, 0x20, 0x27, 0x06, 0xba, 0x39, 0x09, \
+ 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \
+ 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \
+ 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \
+ 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \
+ 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \
+ 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x19, \
+ 0xf7, 0x78, 0x0c, 0xa0, 0xbc, 0xb9, 0xa6, 0xd5, \
+ 0x1e, 0xd7, 0x87, 0xfb, 0x6c, 0x80, 0x03, 0x64, \
+ 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \
+ 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \
+ 0x00, 0x15, 0x00, 0x04, 0x01, 0xa5, 0x01, 0x02, \
+ 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x81, \
+ 0x6c, 0xdd, 0x8c, 0x8f, 0x8c, 0xc8, 0x43, 0xa7, \
+ 0xbb, 0x79, 0x51, 0x09, 0xb1, 0xdf, 0xbe, 0xc4, \
+ 0xa5, 0x54, 0x16, 0x9e, 0x58, 0x56, 0xb3, 0x0b, \
+ 0x34, 0x4f, 0xa5, 0x6c, 0x05, 0xa2, 0x21, 0x22, \
+ 0x58, 0x20, 0xcd, 0xc2, 0x0c, 0x99, 0x83, 0x5a, \
+ 0x61, 0x73, 0xd8, 0xe0, 0x74, 0x23, 0x46, 0x64, \
+ 0x00, 0x15, 0x00, 0x04, 0x02, 0x39, 0x4c, 0xb0, \
+ 0xf4, 0x6c, 0x0a, 0x37, 0x72, 0xaa, 0xa8, 0xea, \
+ 0x58, 0xd3, 0xd4, 0xe0, 0x51, 0xb2, 0x28, 0x09, \
+ 0x05, 0x0a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xa0, 0x00, \
+ 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \
+ 0x56, 0xa1, 0x3c, 0x06, 0x2b, 0xad, 0xa2, 0x21, \
+ 0x7d, 0xcd, 0x91, 0x08, 0x47, 0xa8, 0x8a, 0x06, \
+ 0x06, 0xf6, 0x66, 0x91, 0xf6, 0xeb, 0x89, 0xe4, \
+ 0xdf, 0x26, 0xbc, 0x46, 0x59, 0xc3, 0x7d, 0xc0, \
+ 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \
+ 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \
+ 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \
+ 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \
+ 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \
+ 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0xd8, \
+ 0x27, 0x4b, 0x25, 0xed, 0x19, 0xef, 0x11, 0xaf, \
+ 0xa6, 0x89, 0x7b, 0x84, 0x50, 0xe7, 0x62, 0x64, \
+ 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \
+ 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \
+ 0x00, 0x15, 0x00, 0x04, 0x01, 0xa4, 0x01, 0x01, \
+ 0x03, 0x27, 0x20, 0x06, 0x21, 0x58, 0x20, 0x8d, \
+ 0xfe, 0x45, 0xd5, 0x7d, 0xb6, 0x17, 0xab, 0x86, \
+ 0x2d, 0x32, 0xf6, 0x85, 0xf0, 0x92, 0x76, 0xb7, \
+ 0xce, 0x73, 0xca, 0x4e, 0x0e, 0xfd, 0xd5, 0xdb, \
+ 0x2a, 0x1d, 0x55, 0x90, 0x96, 0x52, 0xc2, 0x0a, \
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xa0, 0x00, \
+ 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \
+ 0x04, 0x0e, 0x0f, 0xa0, 0xcd, 0x60, 0x35, 0x9a, \
+ 0xba, 0x47, 0x0c, 0x10, 0xb6, 0x82, 0x6e, 0x2f, \
+ 0x66, 0xb9, 0xa7, 0xcf, 0xd8, 0x47, 0xb4, 0x3d, \
+ 0xfd, 0x77, 0x1a, 0x38, 0x22, 0xa1, 0xda, 0xa5, \
+ 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \
+ 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \
+ 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \
+ 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \
+ 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \
+ 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x00, \
+ 0x5d, 0xdf, 0xef, 0xe2, 0xf3, 0x06, 0xb2, 0xa5, \
+ 0x46, 0x4d, 0x98, 0xbc, 0x14, 0x65, 0xc1, 0x64, \
+ 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \
+ 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \
+ 0x00, 0x15, 0x00, 0x04, 0x01, 0xa4, 0x01, 0x01, \
+ 0x03, 0x27, 0x20, 0x06, 0x21, 0x58, 0x20, 0x72, \
+ 0x79, 0x14, 0x69, 0xdf, 0xcb, 0x64, 0x75, 0xee, \
+ 0xd4, 0x45, 0x94, 0xbc, 0x48, 0x4d, 0x2a, 0x9f, \
+ 0xc9, 0xf4, 0xb5, 0x1b, 0x05, 0xa6, 0x5b, 0x54, \
+ 0x9a, 0xac, 0x6c, 0x2e, 0xc6, 0x90, 0x62, 0x0a, \
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xc3, 0x00, \
+ 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \
+ 0xce, 0x32, 0xd8, 0x79, 0xdd, 0x86, 0xa2, 0x42, \
+ 0x7c, 0xc3, 0xe1, 0x95, 0x12, 0x93, 0x1a, 0x03, \
+ 0xe6, 0x70, 0xb8, 0xff, 0xcd, 0xa5, 0xdf, 0x15, \
+ 0xfc, 0x88, 0x2a, 0xf5, 0x44, 0xf1, 0x33, 0x9c, \
+ 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \
+ 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \
+ 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \
+ 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \
+ 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \
+ 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x0a, \
+ 0x26, 0x5b, 0x7e, 0x1a, 0x2a, 0xba, 0x70, 0x5f, \
+ 0x18, 0x26, 0x14, 0xb2, 0x71, 0xca, 0x98, 0x64, \
+ 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \
+ 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \
+ 0x00, 0x15, 0x00, 0x04, 0x01, 0xa5, 0x01, 0x02, \
+ 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x8b, \
+ 0x48, 0xf0, 0x69, 0xfb, 0x22, 0xfb, 0xf3, 0x86, \
+ 0x57, 0x7c, 0xdd, 0x82, 0x2c, 0x1c, 0x0c, 0xdc, \
+ 0x27, 0xe2, 0x6a, 0x4c, 0x1a, 0x10, 0x04, 0x27, \
+ 0x51, 0x3e, 0x2a, 0x9d, 0x3a, 0xb6, 0xb5, 0x22, \
+ 0x58, 0x20, 0x70, 0xfe, 0x91, 0x67, 0x64, 0x53, \
+ 0x63, 0x83, 0x72, 0x31, 0xe9, 0xe5, 0x20, 0xb7, \
+ 0x00, 0x15, 0x00, 0x04, 0x02, 0xee, 0xc9, 0xfb, \
+ 0x63, 0xd7, 0xe4, 0x76, 0x39, 0x80, 0x82, 0x74, \
+ 0xb8, 0xfa, 0x67, 0xf5, 0x1b, 0x8f, 0xe0, 0x0a, \
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xc3, 0x00, \
+ 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \
+ 0xf9, 0xa3, 0x67, 0xbf, 0x5e, 0x80, 0x95, 0xdb, \
+ 0x4c, 0xc5, 0x8f, 0x65, 0x36, 0xc5, 0xaf, 0xdd, \
+ 0x90, 0x2e, 0x62, 0x68, 0x67, 0x9c, 0xa2, 0x26, \
+ 0x2f, 0x2a, 0xf9, 0x3a, 0xda, 0x15, 0xf2, 0x27, \
+ 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \
+ 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \
+ 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \
+ 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \
+ 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \
+ 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0xfb, \
+ 0xa6, 0xbe, 0xc1, 0x01, 0xf6, 0x7a, 0x81, 0xf9, \
+ 0xcd, 0x6d, 0x20, 0x41, 0x7a, 0x1c, 0x40, 0x64, \
+ 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \
+ 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \
+ 0x00, 0x15, 0x00, 0x04, 0x01, 0xa5, 0x01, 0x02, \
+ 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xda, \
+ 0x2b, 0x53, 0xc3, 0xbe, 0x48, 0xf8, 0xab, 0xbd, \
+ 0x06, 0x28, 0x46, 0xfa, 0x35, 0xab, 0xf9, 0xc5, \
+ 0x2e, 0xfd, 0x3c, 0x38, 0x88, 0xb3, 0xe1, 0xa7, \
+ 0xc5, 0xc6, 0xed, 0x72, 0x54, 0x37, 0x93, 0x22, \
+ 0x58, 0x20, 0x12, 0x82, 0x32, 0x2d, 0xab, 0xbc, \
+ 0x64, 0xb3, 0xed, 0xcc, 0xd5, 0x22, 0xec, 0x79, \
+ 0x00, 0x15, 0x00, 0x04, 0x02, 0x4b, 0xe2, 0x4d, \
+ 0x0c, 0x4b, 0x8d, 0x31, 0x4c, 0xb4, 0x0f, 0xd4, \
+ 0xa9, 0xbe, 0x0c, 0xab, 0x9e, 0x0a, 0xc9, 0x0a, \
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_BIO_INFO \
+ 0x00, 0x10, 0x00, 0x04, 0x90, 0x00, 0x06, 0x00, \
+ 0xa2, 0x02, 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_BIO_ENROLL \
+ 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0x90, 0x00, 0x0a, 0x00, \
+ 0xa3, 0x04, 0x42, 0x68, 0x96, 0x05, 0x00, 0x06, \
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0x90, 0x00, 0x06, 0x00, \
+ 0xa2, 0x05, 0x00, 0x06, 0x01, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x0a, 0x00, 0x05, 0x90, 0x00, 0x06, 0x00, \
+ 0xa2, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_BIO_ENUM \
+ 0x00, 0x10, 0x00, 0x0f, 0x90, 0x00, 0x2e, 0x00, \
+ 0xa1, 0x07, 0x83, 0xa2, 0x01, 0x42, 0xce, 0xa3, \
+ 0x02, 0x67, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, \
+ 0x31, 0xa2, 0x01, 0x42, 0xbf, 0x5e, 0x02, 0x67, \
+ 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x32, 0xa2, \
+ 0x01, 0x42, 0x5e, 0xd2, 0x02, 0x67, 0x66, 0x69, \
+ 0x6e, 0x67, 0x65, 0x72, 0x33, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY \
+ 0x89, 0xc9, 0x8d, 0x28, 0x90, 0x01, 0xe6, 0x00, \
+ 0xa1, 0x01, 0x59, 0x01, 0xe0, 0x81, 0xa3, 0x01, \
+ 0x59, 0x01, 0xb8, 0xb3, 0x26, 0x24, 0x99, 0xde, \
+ 0x06, 0x3f, 0xca, 0xde, 0x98, 0x8d, 0x9d, 0xc5, \
+ 0x3f, 0x26, 0x6c, 0xc7, 0x40, 0x93, 0xc4, 0x88, \
+ 0x06, 0x51, 0x4f, 0xb9, 0x61, 0xf2, 0xc9, 0x8d, \
+ 0xbc, 0xce, 0x79, 0x08, 0xec, 0x90, 0xc5, 0x5b, \
+ 0xe5, 0x0a, 0x72, 0x08, 0x7b, 0xe1, 0xf9, 0x16, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x00, 0x06, 0x8b, 0x76, \
+ 0x32, 0xa0, 0xae, 0x55, 0xb2, 0x39, 0x71, 0xce, \
+ 0x34, 0x4b, 0x6e, 0x6b, 0x89, 0xa6, 0x5e, 0x69, \
+ 0x07, 0xac, 0xf6, 0x01, 0x3c, 0xba, 0x45, 0x7a, \
+ 0x75, 0x25, 0x3a, 0xbd, 0x95, 0x22, 0x9d, 0xc3, \
+ 0xe4, 0x42, 0x31, 0x5c, 0xb5, 0xf4, 0x64, 0x6a, \
+ 0x56, 0x1d, 0xab, 0xc7, 0x6e, 0x96, 0x75, 0xe7, \
+ 0xb3, 0x22, 0x0b, 0x82, 0xac, 0x57, 0x78, 0xdf, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x01, 0x57, 0x06, 0xc5, \
+ 0x4b, 0x61, 0x0b, 0x4d, 0xa1, 0x66, 0xa0, 0x89, \
+ 0xad, 0x19, 0x8f, 0xd8, 0x96, 0x55, 0x22, 0x5f, \
+ 0xca, 0x2e, 0xc1, 0xd7, 0xbd, 0xa1, 0x83, 0x66, \
+ 0x4d, 0x85, 0xcb, 0x01, 0x60, 0x3f, 0xf7, 0xf7, \
+ 0xa3, 0x7a, 0xfa, 0x99, 0xa0, 0x1e, 0x25, 0x90, \
+ 0xd0, 0xd0, 0x3b, 0x54, 0x90, 0x77, 0x94, 0xa6, \
+ 0x88, 0xea, 0xc3, 0x6b, 0xa0, 0x59, 0x5e, 0x69, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x02, 0x78, 0x0b, 0x2b, \
+ 0xab, 0x5b, 0x04, 0x2f, 0x78, 0x15, 0x86, 0x2b, \
+ 0x0f, 0x63, 0xb2, 0xd7, 0xc9, 0xe9, 0xac, 0x0e, \
+ 0xbc, 0x17, 0xe4, 0x19, 0x88, 0xe0, 0xe6, 0x13, \
+ 0xf8, 0x15, 0x08, 0xa7, 0xe1, 0x6e, 0x71, 0x5c, \
+ 0xef, 0x3e, 0xc1, 0x0f, 0x74, 0xdb, 0xdc, 0x52, \
+ 0x9c, 0xfc, 0xe9, 0xa9, 0xf3, 0x0d, 0x52, 0xbc, \
+ 0x0c, 0xe8, 0xba, 0xd1, 0x76, 0x46, 0x87, 0xb5, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x03, 0x30, 0xe6, 0x9d, \
+ 0xa1, 0x2b, 0xa5, 0x9e, 0x3b, 0x86, 0xb3, 0x5f, \
+ 0xe3, 0x81, 0xa6, 0x76, 0x32, 0x9d, 0xf9, 0xc5, \
+ 0x07, 0x93, 0xb3, 0xdf, 0x64, 0xe2, 0x78, 0x9c, \
+ 0x00, 0xc7, 0x86, 0x79, 0xd6, 0x67, 0xa2, 0xfb, \
+ 0xf2, 0x8d, 0xea, 0xe9, 0xc8, 0xfc, 0x43, 0xd2, \
+ 0x0f, 0x2f, 0x7d, 0x9d, 0xd3, 0x8f, 0x9c, 0xdd, \
+ 0xa2, 0x9f, 0x42, 0x76, 0x40, 0xcc, 0x4a, 0xd0, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x04, 0xb4, 0x87, 0x18, \
+ 0x06, 0xc3, 0xc7, 0x89, 0x98, 0x72, 0xcc, 0x1a, \
+ 0xd1, 0xd8, 0x78, 0xb9, 0x75, 0x0b, 0x92, 0xe3, \
+ 0xcc, 0xed, 0x38, 0x39, 0x4b, 0xa9, 0xcf, 0x30, \
+ 0xd6, 0xb5, 0xa1, 0x3f, 0xfa, 0x4f, 0x29, 0x99, \
+ 0xa9, 0x03, 0x77, 0xf6, 0x53, 0xfa, 0xd8, 0x32, \
+ 0xce, 0xf4, 0xf6, 0x0a, 0x3c, 0xe8, 0x9c, 0x3d, \
+ 0xaa, 0xe0, 0x7b, 0x2c, 0xa5, 0x28, 0xe1, 0xdd, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x05, 0x51, 0xbf, 0xe1, \
+ 0xd4, 0xf5, 0x5e, 0x38, 0x2c, 0xec, 0xab, 0xdd, \
+ 0xb8, 0x5c, 0x13, 0x43, 0x62, 0xc2, 0xb6, 0x02, \
+ 0x18, 0xce, 0x9a, 0x62, 0x67, 0x6a, 0xeb, 0x99, \
+ 0xf6, 0x2f, 0xf1, 0xf1, 0xec, 0x3e, 0x74, 0xfa, \
+ 0xf8, 0x16, 0x43, 0xea, 0x1e, 0xef, 0x5d, 0x37, \
+ 0x6c, 0x13, 0xf9, 0x7f, 0x65, 0x09, 0xab, 0x60, \
+ 0x38, 0xda, 0x0f, 0xe7, 0xfa, 0x9e, 0x17, 0x10, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x06, 0xdc, 0x4c, 0x4d, \
+ 0xae, 0x5c, 0xb4, 0x0d, 0x6b, 0x05, 0x6d, 0x25, \
+ 0x3f, 0x78, 0x5d, 0xf3, 0x34, 0x33, 0xa4, 0x89, \
+ 0x34, 0x0e, 0x88, 0x66, 0x40, 0x57, 0x6b, 0x34, \
+ 0x83, 0xfd, 0x39, 0xe7, 0xfb, 0x84, 0x09, 0xb3, \
+ 0x16, 0x8f, 0x80, 0xdf, 0x1b, 0xe0, 0x02, 0x4c, \
+ 0xde, 0x31, 0x2a, 0x32, 0x58, 0x5b, 0xa3, 0x23, \
+ 0x8e, 0x2a, 0xa6, 0xaf, 0x03, 0x19, 0x02, 0x7a, \
+ 0x89, 0xc9, 0x8d, 0x28, 0x07, 0xf8, 0xbf, 0xa6, \
+ 0xad, 0xf9, 0xd1, 0xdc, 0xbd, 0x6e, 0xb3, 0xc1, \
+ 0xfb, 0x65, 0xd8, 0x5f, 0x2e, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_NFC_INIT \
+ 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00
+
+#define WIREDATA_CTAP_NFC_MSG \
+ 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00
+
+#define WIREDATA_CTAP_EXTENDED_APDU \
+ 0x00, 0xa4, 0x04, 0x00, 0x00, 0x02, 0x00, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, \
+ 0x00
+
+#endif /* _WIREDATA_FIDO2_H */
diff --git a/fuzz/wiredata_u2f.h b/fuzz/wiredata_u2f.h
new file mode 100644
index 0000000..3be22d3
--- /dev/null
+++ b/fuzz/wiredata_u2f.h
@@ -0,0 +1,153 @@
+/*
+ * 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 _WIREDATA_U2F_H
+#define _WIREDATA_U2F_H
+
+#define WIREDATA_CTAP_U2F_6985 \
+ 0x00, 0x00, 0x99, 0x01, 0x83, 0x00, 0x02, 0x69, \
+ 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_U2F_AUTH \
+ 0x00, 0x00, 0x99, 0x01, 0x83, 0x00, 0x4e, 0x01, \
+ 0x00, 0x00, 0x00, 0x2c, 0x30, 0x45, 0x02, 0x20, \
+ 0x1c, 0xf5, 0x7c, 0xf6, 0xde, 0xbe, 0xe9, 0x86, \
+ 0xee, 0x97, 0xb7, 0x64, 0xa3, 0x4e, 0x7a, 0x70, \
+ 0x85, 0xd0, 0x66, 0xf9, 0xf0, 0xcd, 0x04, 0x5d, \
+ 0x97, 0xf2, 0x3c, 0x22, 0xe3, 0x0e, 0x61, 0xc8, \
+ 0x02, 0x21, 0x00, 0x97, 0xef, 0xae, 0x36, 0xe6, \
+ 0x17, 0x9f, 0x5e, 0x2d, 0xd7, 0x8c, 0x34, 0xa7, \
+ 0x00, 0x00, 0x99, 0x01, 0x00, 0xa1, 0xe9, 0xfb, \
+ 0x8f, 0x86, 0x8c, 0xe3, 0x1e, 0xde, 0x3f, 0x4e, \
+ 0x1b, 0xe1, 0x2f, 0x8f, 0x2f, 0xca, 0x42, 0x26, \
+ 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#define WIREDATA_CTAP_U2F_REGISTER \
+ 0x00, 0x00, 0x99, 0x01, 0x83, 0x03, 0x1e, 0x05, \
+ 0x04, 0x9f, 0xa0, 0xf9, 0x0d, 0x4c, 0xf4, 0xae, \
+ 0x96, 0x3c, 0xb7, 0x46, 0xb7, 0x5c, 0x9d, 0x8b, \
+ 0x48, 0x19, 0xdf, 0xc4, 0xad, 0xea, 0xb2, 0x70, \
+ 0x58, 0x72, 0xd9, 0xce, 0x75, 0xf5, 0xe6, 0x8e, \
+ 0x0f, 0x9c, 0x0e, 0x2e, 0x62, 0x3e, 0x91, 0xd3, \
+ 0x7b, 0x97, 0x46, 0x60, 0xb9, 0x57, 0x13, 0x97, \
+ 0x26, 0xae, 0x0f, 0xb3, 0x8f, 0x2e, 0x9b, 0x3f, \
+ 0x00, 0x00, 0x99, 0x01, 0x00, 0xa5, 0x55, 0xec, \
+ 0x8c, 0x25, 0x7c, 0x65, 0xb7, 0x09, 0x40, 0x48, \
+ 0xae, 0xa8, 0xcb, 0xa1, 0x91, 0xac, 0x40, 0x24, \
+ 0xf2, 0x34, 0x6e, 0x3a, 0x8f, 0xa5, 0xb7, 0x48, \
+ 0x54, 0x6e, 0xfb, 0xf4, 0x37, 0x88, 0x69, 0x79, \
+ 0x6f, 0x12, 0xc1, 0x32, 0xdf, 0x15, 0x5d, 0x6e, \
+ 0x82, 0x54, 0xc0, 0x6e, 0x56, 0x4f, 0x3a, 0x9c, \
+ 0xc3, 0x96, 0x7a, 0xde, 0xa5, 0xfe, 0xec, 0xd1, \
+ 0x00, 0x00, 0x99, 0x01, 0x01, 0x5a, 0x21, 0x85, \
+ 0x0e, 0x25, 0x7b, 0x8d, 0x6e, 0x1d, 0x32, 0x29, \
+ 0xdb, 0x21, 0xb0, 0xa3, 0x30, 0x82, 0x02, 0x4f, \
+ 0x30, 0x82, 0x01, 0x37, 0xa0, 0x03, 0x02, 0x01, \
+ 0x02, 0x02, 0x04, 0x2a, 0xd9, 0x6a, 0xf3, 0x30, \
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, \
+ 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x2e, \
+ 0x31, 0x2c, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x04, \
+ 0x00, 0x00, 0x99, 0x01, 0x02, 0x03, 0x13, 0x23, \
+ 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, \
+ 0x32, 0x46, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, \
+ 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, \
+ 0x6c, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30, 0x30, \
+ 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x31, \
+ 0x34, 0x30, 0x38, 0x30, 0x31, 0x30, 0x30, 0x30, \
+ 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x30, \
+ 0x00, 0x00, 0x99, 0x01, 0x03, 0x35, 0x30, 0x30, \
+ 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, \
+ 0x30, 0x5a, 0x30, 0x31, 0x31, 0x2f, 0x30, 0x2d, \
+ 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x26, 0x59, \
+ 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, \
+ 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, 0x72, \
+ 0x69, 0x61, 0x6c, 0x20, 0x32, 0x33, 0x39, 0x32, \
+ 0x35, 0x37, 0x33, 0x34, 0x35, 0x31, 0x36, 0x35, \
+ 0x00, 0x00, 0x99, 0x01, 0x04, 0x35, 0x30, 0x33, \
+ 0x38, 0x37, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, \
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, \
+ 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, \
+ 0x07, 0x03, 0x42, 0x00, 0x04, 0x2f, 0xe1, 0xa2, \
+ 0x3e, 0xbf, 0xa5, 0x5b, 0x3e, 0x46, 0x1d, 0x59, \
+ 0xa4, 0x35, 0x22, 0xd7, 0x97, 0x48, 0x98, 0x1c, \
+ 0xba, 0x6d, 0x28, 0x9a, 0x98, 0xf1, 0xbd, 0x7d, \
+ 0x00, 0x00, 0x99, 0x01, 0x05, 0xff, 0x65, 0x66, \
+ 0x80, 0xdb, 0xbb, 0xed, 0xbc, 0x2b, 0xae, 0x60, \
+ 0x7e, 0x6e, 0xf7, 0x72, 0xf5, 0x76, 0xb0, 0x4d, \
+ 0x54, 0xc4, 0xe5, 0xf3, 0x2f, 0x59, 0x6f, 0x26, \
+ 0xe6, 0x11, 0x15, 0xc7, 0x27, 0x2c, 0xf6, 0xca, \
+ 0x75, 0x94, 0xa3, 0x3b, 0x30, 0x39, 0x30, 0x22, \
+ 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, \
+ 0xc4, 0x0a, 0x02, 0x04, 0x15, 0x31, 0x2e, 0x33, \
+ 0x00, 0x00, 0x99, 0x01, 0x06, 0x2e, 0x36, 0x2e, \
+ 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, \
+ 0x34, 0x38, 0x32, 0x2e, 0x31, 0x2e, 0x32, 0x30, \
+ 0x13, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, \
+ 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, \
+ 0x03, 0x02, 0x04, 0x30, 0x30, 0x0d, 0x06, 0x09, \
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, \
+ 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, \
+ 0x00, 0x00, 0x99, 0x01, 0x07, 0x85, 0x6a, 0xfa, \
+ 0x8b, 0xcf, 0x4f, 0x3f, 0x62, 0x5f, 0x29, 0x1b, \
+ 0xc1, 0x15, 0x8e, 0x3c, 0x7e, 0xbd, 0x25, 0x52, \
+ 0xbc, 0xf7, 0x57, 0x07, 0x53, 0xf5, 0x12, 0x1d, \
+ 0xa6, 0xa5, 0x4d, 0x24, 0xcc, 0xcf, 0xae, 0x27, \
+ 0xce, 0xd6, 0xab, 0x31, 0x12, 0x8c, 0x29, 0x7e, \
+ 0x5b, 0x5b, 0x89, 0x05, 0xdd, 0xa0, 0x20, 0x17, \
+ 0x93, 0x1f, 0x1f, 0x5f, 0x59, 0x25, 0x93, 0x59, \
+ 0x00, 0x00, 0x99, 0x01, 0x08, 0x51, 0xfc, 0x00, \
+ 0x4b, 0xcb, 0xe2, 0x0a, 0xdd, 0x7d, 0x8d, 0x05, \
+ 0x2f, 0x95, 0x43, 0xb3, 0x49, 0x6c, 0x15, 0xb8, \
+ 0x31, 0x0e, 0x10, 0xcb, 0xd9, 0xbb, 0x05, 0x38, \
+ 0x27, 0x4f, 0x58, 0x3e, 0xad, 0x1f, 0x45, 0x12, \
+ 0x88, 0xc3, 0xea, 0x76, 0xd0, 0x70, 0xad, 0x44, \
+ 0xe5, 0x3a, 0xfe, 0xa8, 0xf2, 0x2d, 0x1f, 0x73, \
+ 0x62, 0x5f, 0xf2, 0xd5, 0x89, 0xfe, 0x30, 0xdf, \
+ 0x00, 0x00, 0x99, 0x01, 0x09, 0x26, 0x62, 0xcb, \
+ 0x7c, 0xbb, 0x7c, 0x99, 0x61, 0x80, 0xad, 0xcf, \
+ 0xa9, 0x8a, 0x4d, 0x01, 0x2c, 0xf3, 0x13, 0x46, \
+ 0xcd, 0x11, 0x74, 0x6a, 0x58, 0x48, 0xe8, 0xbe, \
+ 0xed, 0xf3, 0xe3, 0x0c, 0xcb, 0xd9, 0xc1, 0xdd, \
+ 0x22, 0x16, 0x71, 0xb2, 0x83, 0x88, 0x61, 0xf6, \
+ 0x5a, 0x45, 0x36, 0x23, 0xb5, 0x18, 0xd5, 0x56, \
+ 0x7f, 0xa8, 0xf0, 0xa3, 0xce, 0x10, 0x5d, 0xf4, \
+ 0x00, 0x00, 0x99, 0x01, 0x0a, 0xf1, 0x39, 0x53, \
+ 0xe1, 0x14, 0xea, 0x59, 0xe0, 0xa7, 0xf2, 0xfe, \
+ 0x66, 0x88, 0x67, 0x43, 0x2e, 0x52, 0xfd, 0x6a, \
+ 0x2f, 0x64, 0xf7, 0x3c, 0x48, 0xcd, 0x9b, 0x38, \
+ 0xf2, 0xdf, 0xba, 0x2c, 0x7a, 0x4b, 0x3b, 0x11, \
+ 0x28, 0xdf, 0x26, 0xd6, 0x6a, 0x24, 0xf8, 0x95, \
+ 0xdd, 0xa0, 0xb6, 0x11, 0x80, 0xf4, 0x14, 0x4f, \
+ 0x6b, 0x70, 0x75, 0xc3, 0x18, 0xa4, 0x9a, 0xe0, \
+ 0x00, 0x00, 0x99, 0x01, 0x0b, 0x8b, 0x58, 0xd3, \
+ 0x6a, 0xdb, 0x1e, 0x30, 0x53, 0x67, 0x2b, 0x17, \
+ 0xc5, 0xa1, 0x9f, 0x7f, 0x0a, 0x22, 0xf1, 0x0e, \
+ 0x94, 0x30, 0x44, 0x02, 0x20, 0x07, 0x5c, 0x4f, \
+ 0xd2, 0x83, 0xb6, 0x9f, 0x0a, 0x4a, 0x4d, 0x4b, \
+ 0x08, 0x35, 0xeb, 0xc0, 0x7e, 0x4a, 0x14, 0x2e, \
+ 0xc7, 0x8c, 0xd6, 0x64, 0x2f, 0xd3, 0x1e, 0xcc, \
+ 0xb5, 0xe8, 0x42, 0xea, 0xf6, 0x02, 0x20, 0x6b, \
+ 0x00, 0x00, 0x99, 0x01, 0x0c, 0x5a, 0xba, 0x4a, \
+ 0xc8, 0xd7, 0x89, 0xcc, 0x77, 0xe6, 0xb9, 0xa3, \
+ 0x34, 0xea, 0x06, 0x85, 0x72, 0xc6, 0x28, 0xa8, \
+ 0x7a, 0xaa, 0x19, 0x88, 0x34, 0xbb, 0xdc, 0x64, \
+ 0x90, 0x0a, 0xdb, 0x39, 0x90, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+
+#endif /* !_WIREDATA_U2F_H */
diff --git a/fuzz/wrap.c b/fuzz/wrap.c
new file mode 100644
index 0000000..6f40ea1
--- /dev/null
+++ b/fuzz/wrap.c
@@ -0,0 +1,700 @@
+/*
+ * 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/random.h>
+#include <sys/socket.h>
+
+#include <openssl/bn.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+
+#include <cbor.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <zlib.h>
+
+#include "mutator_aux.h"
+
+extern int prng_up;
+
+int fuzz_save_corpus;
+
+/*
+ * Build wrappers around functions of interest, and have them fail
+ * in a pseudo-random manner. A uniform probability of 0.25% (1/400)
+ * allows for a depth of log(0.5)/log(399/400) > 276 operations
+ * before simulated errors become statistically more likely.
+ */
+
+#define WRAP(type, name, args, retval, param, prob) \
+extern type __wrap_##name args; \
+extern type __real_##name args; \
+type __wrap_##name args { \
+ if (prng_up && uniform_random(400) < (prob)) { \
+ return (retval); \
+ } \
+ \
+ return (__real_##name param); \
+}
+
+WRAP(void *,
+ malloc,
+ (size_t size),
+ NULL,
+ (size),
+ 1
+)
+
+WRAP(void *,
+ calloc,
+ (size_t nmemb, size_t size),
+ NULL,
+ (nmemb, size),
+ 1
+)
+
+WRAP(void *,
+ realloc,
+ (void *ptr, size_t size),
+ NULL,
+ (ptr, size),
+ 1
+)
+
+WRAP(char *,
+ strdup,
+ (const char *s),
+ NULL,
+ (s),
+ 1
+)
+
+WRAP(ssize_t,
+ getrandom,
+ (void *buf, size_t buflen, unsigned int flags),
+ -1,
+ (buf, buflen, flags),
+ 1
+)
+
+WRAP(int,
+ EVP_Cipher,
+ (EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in,
+ unsigned int inl),
+ -1,
+ (ctx, out, in, inl),
+ 1
+)
+
+WRAP(int,
+ EVP_CIPHER_CTX_ctrl,
+ (EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr),
+ 0,
+ (ctx, type, arg, ptr),
+ 1
+)
+
+WRAP(EVP_CIPHER_CTX *,
+ EVP_CIPHER_CTX_new,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(int,
+ EVP_CipherInit,
+ (EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
+ const unsigned char *key, const unsigned char *iv, int enc),
+ 0,
+ (ctx, cipher, key, iv, enc),
+ 1
+)
+
+WRAP(RSA *,
+ EVP_PKEY_get0_RSA,
+ (EVP_PKEY *pkey),
+ NULL,
+ (pkey),
+ 1
+)
+
+WRAP(EC_KEY *,
+ EVP_PKEY_get0_EC_KEY,
+ (EVP_PKEY *pkey),
+ NULL,
+ (pkey),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_get_raw_public_key,
+ (const EVP_PKEY *pkey, unsigned char *pub, size_t *len),
+ 0,
+ (pkey, pub, len),
+ 1
+)
+
+WRAP(EVP_MD_CTX *,
+ EVP_MD_CTX_new,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(int,
+ EVP_DigestVerifyInit,
+ (EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, const EVP_MD *type, ENGINE *e,
+ EVP_PKEY *pkey),
+ 0,
+ (ctx, pctx, type, e, pkey),
+ 1
+)
+
+WRAP(int,
+ EVP_DigestInit_ex,
+ (EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl),
+ 0,
+ (ctx, type, impl),
+ 1
+)
+
+WRAP(int,
+ EVP_DigestUpdate,
+ (EVP_MD_CTX *ctx, const void *data, size_t count),
+ 0,
+ (ctx, data, count),
+ 1
+)
+
+WRAP(int,
+ EVP_DigestFinal_ex,
+ (EVP_MD_CTX *ctx, unsigned char *md, unsigned int *isize),
+ 0,
+ (ctx, md, isize),
+ 1
+)
+
+WRAP(BIGNUM *,
+ BN_bin2bn,
+ (const unsigned char *s, int len, BIGNUM *ret),
+ NULL,
+ (s, len, ret),
+ 1
+)
+
+WRAP(int,
+ BN_bn2bin,
+ (const BIGNUM *a, unsigned char *to),
+ -1,
+ (a, to),
+ 1
+)
+
+WRAP(BIGNUM *,
+ BN_CTX_get,
+ (BN_CTX *ctx),
+ NULL,
+ (ctx),
+ 1
+)
+
+WRAP(BN_CTX *,
+ BN_CTX_new,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(BIGNUM *,
+ BN_new,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(RSA *,
+ RSA_new,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(int,
+ RSA_set0_key,
+ (RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d),
+ 0,
+ (r, n, e, d),
+ 1
+)
+
+WRAP(int,
+ RSA_pkey_ctx_ctrl,
+ (EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void *p2),
+ -1,
+ (ctx, optype, cmd, p1, p2),
+ 1
+)
+
+WRAP(EC_KEY *,
+ EC_KEY_new_by_curve_name,
+ (int nid),
+ NULL,
+ (nid),
+ 1
+)
+
+WRAP(const EC_GROUP *,
+ EC_KEY_get0_group,
+ (const EC_KEY *key),
+ NULL,
+ (key),
+ 1
+)
+
+WRAP(const BIGNUM *,
+ EC_KEY_get0_private_key,
+ (const EC_KEY *key),
+ NULL,
+ (key),
+ 1
+)
+
+WRAP(EC_POINT *,
+ EC_POINT_new,
+ (const EC_GROUP *group),
+ NULL,
+ (group),
+ 1
+)
+
+WRAP(int,
+ EC_POINT_get_affine_coordinates_GFp,
+ (const EC_GROUP *group, const EC_POINT *p, BIGNUM *x, BIGNUM *y, BN_CTX *ctx),
+ 0,
+ (group, p, x, y, ctx),
+ 1
+)
+
+WRAP(EVP_PKEY *,
+ EVP_PKEY_new,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_assign,
+ (EVP_PKEY *pkey, int type, void *key),
+ 0,
+ (pkey, type, key),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_keygen_init,
+ (EVP_PKEY_CTX *ctx),
+ 0,
+ (ctx),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_keygen,
+ (EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey),
+ 0,
+ (ctx, ppkey),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_paramgen_init,
+ (EVP_PKEY_CTX *ctx),
+ 0,
+ (ctx),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_paramgen,
+ (EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey),
+ 0,
+ (ctx, ppkey),
+ 1
+)
+
+WRAP(EVP_PKEY *,
+ EVP_PKEY_new_raw_public_key,
+ (int type, ENGINE *e, const unsigned char *key, size_t keylen),
+ NULL,
+ (type, e, key, keylen),
+ 1
+)
+
+WRAP(EVP_PKEY_CTX *,
+ EVP_PKEY_CTX_new,
+ (EVP_PKEY *pkey, ENGINE *e),
+ NULL,
+ (pkey, e),
+ 1
+)
+
+WRAP(EVP_PKEY_CTX *,
+ EVP_PKEY_CTX_new_id,
+ (int id, ENGINE *e),
+ NULL,
+ (id, e),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_derive,
+ (EVP_PKEY_CTX *ctx, unsigned char *key, size_t *pkeylen),
+ 0,
+ (ctx, key, pkeylen),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_derive_init,
+ (EVP_PKEY_CTX *ctx),
+ 0,
+ (ctx),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_derive_set_peer,
+ (EVP_PKEY_CTX *ctx, EVP_PKEY *peer),
+ 0,
+ (ctx, peer),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_verify_init,
+ (EVP_PKEY_CTX *ctx),
+ 0,
+ (ctx),
+ 1
+)
+
+WRAP(int,
+ EVP_PKEY_CTX_ctrl,
+ (EVP_PKEY_CTX *ctx, int keytype, int optype, int cmd, int p1, void *p2),
+ -1,
+ (ctx, keytype, optype, cmd, p1, p2),
+ 1
+)
+
+WRAP(const EVP_MD *,
+ EVP_sha1,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(const EVP_MD *,
+ EVP_sha256,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(const EVP_CIPHER *,
+ EVP_aes_256_cbc,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(const EVP_CIPHER *,
+ EVP_aes_256_gcm,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(unsigned char *,
+ HMAC,
+ (const EVP_MD *evp_md, const void *key, int key_len,
+ const unsigned char *d, int n, unsigned char *md,
+ unsigned int *md_len),
+ NULL,
+ (evp_md, key, key_len, d, n, md, md_len),
+ 1
+)
+
+WRAP(HMAC_CTX *,
+ HMAC_CTX_new,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(int,
+ HMAC_Init_ex,
+ (HMAC_CTX *ctx, const void *key, int key_len, const EVP_MD *md,
+ ENGINE *impl),
+ 0,
+ (ctx, key, key_len, md, impl),
+ 1
+)
+
+WRAP(int,
+ HMAC_Update,
+ (HMAC_CTX *ctx, const unsigned char *data, int len),
+ 0,
+ (ctx, data, len),
+ 1
+)
+
+WRAP(int,
+ HMAC_Final,
+ (HMAC_CTX *ctx, unsigned char *md, unsigned int *len),
+ 0,
+ (ctx, md, len),
+ 1
+)
+
+WRAP(unsigned char *,
+ SHA1,
+ (const unsigned char *d, size_t n, unsigned char *md),
+ NULL,
+ (d, n, md),
+ 1
+)
+
+WRAP(unsigned char *,
+ SHA256,
+ (const unsigned char *d, size_t n, unsigned char *md),
+ NULL,
+ (d, n, md),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_string,
+ (const char *val),
+ NULL,
+ (val),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_bytestring,
+ (cbor_data handle, size_t length),
+ NULL,
+ (handle, length),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_bool,
+ (bool value),
+ NULL,
+ (value),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_negint8,
+ (uint8_t value),
+ NULL,
+ (value),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_negint16,
+ (uint16_t value),
+ NULL,
+ (value),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_load,
+ (cbor_data source, size_t source_size, struct cbor_load_result *result),
+ NULL,
+ (source, source_size, result),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_uint8,
+ (uint8_t value),
+ NULL,
+ (value),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_uint16,
+ (uint16_t value),
+ NULL,
+ (value),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_uint32,
+ (uint32_t value),
+ NULL,
+ (value),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_build_uint64,
+ (uint64_t value),
+ NULL,
+ (value),
+ 1
+)
+
+WRAP(struct cbor_pair *,
+ cbor_map_handle,
+ (const cbor_item_t *item),
+ NULL,
+ (item),
+ 1
+)
+
+WRAP(cbor_item_t **,
+ cbor_array_handle,
+ (const cbor_item_t *item),
+ NULL,
+ (item),
+ 1
+)
+
+WRAP(bool,
+ cbor_array_push,
+ (cbor_item_t *array, cbor_item_t *pushee),
+ false,
+ (array, pushee),
+ 1
+)
+
+WRAP(bool,
+ cbor_map_add,
+ (cbor_item_t *item, struct cbor_pair pair),
+ false,
+ (item, pair),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_new_definite_map,
+ (size_t size),
+ NULL,
+ (size),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_new_definite_array,
+ (size_t size),
+ NULL,
+ (size),
+ 1
+)
+
+WRAP(cbor_item_t *,
+ cbor_new_definite_bytestring,
+ (void),
+ NULL,
+ (),
+ 1
+)
+
+WRAP(size_t,
+ cbor_serialize_alloc,
+ (const cbor_item_t *item, cbor_mutable_data *buffer,
+ size_t *buffer_size),
+ 0,
+ (item, buffer, buffer_size),
+ 1
+)
+
+WRAP(int,
+ fido_tx,
+ (fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms),
+ -1,
+ (d, cmd, buf, count, ms),
+ 1
+)
+
+WRAP(int,
+ bind,
+ (int sockfd, const struct sockaddr *addr, socklen_t addrlen),
+ -1,
+ (sockfd, addr, addrlen),
+ 1
+)
+
+WRAP(int,
+ deflateInit2_,
+ (z_streamp strm, int level, int method, int windowBits, int memLevel,
+ int strategy, const char *version, int stream_size),
+ Z_STREAM_ERROR,
+ (strm, level, method, windowBits, memLevel, strategy, version,
+ stream_size),
+ 1
+)
+
+int __wrap_deflate(z_streamp, int);
+int __real_deflate(z_streamp, int);
+
+int
+__wrap_deflate(z_streamp strm, int flush)
+{
+ if (prng_up && uniform_random(400) < 1) {
+ return Z_BUF_ERROR;
+ }
+ /* should never happen, but we check for it */
+ if (prng_up && uniform_random(400) < 1) {
+ strm->avail_out = UINT_MAX;
+ return Z_STREAM_END;
+ }
+
+ return __real_deflate(strm, flush);
+}
+
+int __wrap_asprintf(char **, const char *, ...);
+
+int
+__wrap_asprintf(char **strp, const char *fmt, ...)
+{
+ va_list ap;
+ int r;
+
+ if (prng_up && uniform_random(400) < 1) {
+ *strp = (void *)0xdeadbeef;
+ return -1;
+ }
+
+ va_start(ap, fmt);
+ r = vasprintf(strp, fmt, ap);
+ va_end(ap);
+
+ return r;
+}
diff --git a/fuzz/wrapped.sym b/fuzz/wrapped.sym
new file mode 100644
index 0000000..219a0d8
--- /dev/null
+++ b/fuzz/wrapped.sym
@@ -0,0 +1,102 @@
+asprintf
+bind
+BN_bin2bn
+BN_bn2bin
+BN_CTX_get
+BN_CTX_new
+BN_new
+calloc
+cbor_array_handle
+cbor_array_push
+cbor_build_bool
+cbor_build_bytestring
+cbor_build_negint16
+cbor_build_negint8
+cbor_build_string
+cbor_build_uint16
+cbor_build_uint32
+cbor_build_uint64
+cbor_build_uint8
+cbor_load
+cbor_map_add
+cbor_map_handle
+cbor_new_definite_array
+cbor_new_definite_bytestring
+cbor_new_definite_map
+cbor_serialize_alloc
+clock_gettime
+deflate
+deflateInit2_
+EC_KEY_get0_group
+EC_KEY_get0_private_key
+EC_KEY_new_by_curve_name
+EC_POINT_get_affine_coordinates_GFp
+EC_POINT_new
+EVP_aes_256_cbc
+EVP_aes_256_gcm
+EVP_Cipher
+EVP_CIPHER_CTX_ctrl
+EVP_CIPHER_CTX_new
+EVP_CipherInit
+EVP_DigestFinal_ex
+EVP_DigestInit_ex
+EVP_DigestUpdate
+EVP_DigestVerifyInit
+EVP_MD_CTX_new
+EVP_PKEY_assign
+EVP_PKEY_CTX_ctrl
+EVP_PKEY_CTX_new
+EVP_PKEY_CTX_new_id
+EVP_PKEY_derive
+EVP_PKEY_derive_init
+EVP_PKEY_derive_set_peer
+EVP_PKEY_get0_EC_KEY
+EVP_PKEY_get0_RSA
+EVP_PKEY_get_raw_public_key
+EVP_PKEY_keygen
+EVP_PKEY_keygen_init
+EVP_PKEY_new
+EVP_PKEY_new_raw_public_key
+EVP_PKEY_paramgen
+EVP_PKEY_paramgen_init
+EVP_PKEY_verify_init
+EVP_sha1
+EVP_sha256
+fido_tx
+getrandom
+HMAC
+HMAC_CTX_new
+HMAC_Final
+HMAC_Init_ex
+HMAC_Update
+ioctl
+malloc
+realloc
+RSA_new
+RSA_pkey_ctx_ctrl
+RSA_set0_key
+SCardConnect
+SCardDisconnect
+SCardEstablishContext
+SCardListReaders
+SCardReleaseContext
+SCardTransmit
+SHA1
+SHA256
+strdup
+udev_device_get_devnode
+udev_device_get_parent_with_subsystem_devtype
+udev_device_get_sysattr_value
+udev_device_get_sysnum
+udev_device_new_from_syspath
+udev_device_unref
+udev_enumerate_add_match_subsystem
+udev_enumerate_get_list_entry
+udev_enumerate_new
+udev_enumerate_scan_devices
+udev_enumerate_unref
+udev_list_entry_get_name
+udev_list_entry_get_next
+udev_new
+udev_unref
+usleep
diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt
new file mode 100644
index 0000000..6616e4e
--- /dev/null
+++ b/man/CMakeLists.txt
@@ -0,0 +1,413 @@
+# 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
+
+find_program(MANDOC_PATH mandoc)
+find_program(GZIP_PATH gzip)
+
+message(STATUS "MANDOC_PATH: ${MANDOC_PATH}")
+message(STATUS "GZIP_PATH: ${GZIP_PATH}")
+
+list(APPEND MAN_SOURCES
+ eddsa_pk_new.3
+ es256_pk_new.3
+ es384_pk_new.3
+ fido2-assert.1
+ fido2-cred.1
+ fido2-token.1
+ fido_init.3
+ fido_assert_new.3
+ fido_assert_allow_cred.3
+ fido_assert_set_authdata.3
+ fido_assert_verify.3
+ fido_bio_dev_get_info.3
+ fido_bio_enroll_new.3
+ fido_bio_info_new.3
+ fido_bio_template.3
+ fido_cbor_info_new.3
+ fido_cred_new.3
+ fido_cred_exclude.3
+ fido_credman_metadata_new.3
+ fido_cred_set_authdata.3
+ fido_cred_verify.3
+ fido_dev_enable_entattest.3
+ fido_dev_get_assert.3
+ fido_dev_get_touch_begin.3
+ fido_dev_info_manifest.3
+ fido_dev_largeblob_get.3
+ fido_dev_make_cred.3
+ fido_dev_open.3
+ fido_dev_set_io_functions.3
+ fido_dev_set_pin.3
+ fido_strerr.3
+ rs256_pk_new.3
+)
+
+list(APPEND MAN_ALIAS
+ eddsa_pk_new eddsa_pk_free
+ eddsa_pk_new eddsa_pk_from_EVP_PKEY
+ eddsa_pk_new eddsa_pk_from_ptr
+ eddsa_pk_new eddsa_pk_to_EVP_PKEY
+ es256_pk_new es256_pk_free
+ es256_pk_new es256_pk_from_EC_KEY
+ es256_pk_new es256_pk_from_EVP_PKEY
+ es256_pk_new es256_pk_from_ptr
+ es256_pk_new es256_pk_to_EVP_PKEY
+ es384_pk_new es384_pk_free
+ es384_pk_new es384_pk_from_EC_KEY
+ es384_pk_new es384_pk_from_EVP_PKEY
+ es384_pk_new es384_pk_from_ptr
+ es384_pk_new es384_pk_to_EVP_PKEY
+ fido_assert_allow_cred fido_assert_empty_allow_list
+ fido_assert_new fido_assert_authdata_len
+ fido_assert_new fido_assert_authdata_ptr
+ fido_assert_new fido_assert_authdata_raw_len
+ fido_assert_new fido_assert_authdata_raw_ptr
+ fido_assert_new fido_assert_blob_len
+ fido_assert_new fido_assert_blob_ptr
+ fido_assert_new fido_assert_clientdata_hash_len
+ fido_assert_new fido_assert_clientdata_hash_ptr
+ fido_assert_new fido_assert_count
+ fido_assert_new fido_assert_flags
+ fido_assert_new fido_assert_free
+ fido_assert_new fido_assert_hmac_secret_len
+ fido_assert_new fido_assert_hmac_secret_ptr
+ fido_assert_new fido_assert_id_len
+ fido_assert_new fido_assert_id_ptr
+ fido_assert_new fido_assert_largeblob_key_len
+ fido_assert_new fido_assert_largeblob_key_ptr
+ fido_assert_new fido_assert_rp_id
+ fido_assert_new fido_assert_sigcount
+ fido_assert_new fido_assert_sig_len
+ fido_assert_new fido_assert_sig_ptr
+ fido_assert_new fido_assert_user_display_name
+ fido_assert_new fido_assert_user_icon
+ fido_assert_new fido_assert_user_id_len
+ fido_assert_new fido_assert_user_id_ptr
+ fido_assert_new fido_assert_user_name
+ fido_assert_set_authdata fido_assert_set_authdata_raw
+ fido_assert_set_authdata fido_assert_set_clientdata
+ fido_assert_set_authdata fido_assert_set_clientdata_hash
+ fido_assert_set_authdata fido_assert_set_count
+ fido_assert_set_authdata fido_assert_set_extensions
+ fido_assert_set_authdata fido_assert_set_hmac_salt
+ fido_assert_set_authdata fido_assert_set_hmac_secret
+ fido_assert_set_authdata fido_assert_set_rp
+ fido_assert_set_authdata fido_assert_set_sig
+ fido_assert_set_authdata fido_assert_set_up
+ fido_assert_set_authdata fido_assert_set_uv
+ fido_assert_set_authdata fido_assert_set_winhello_appid
+ fido_bio_dev_get_info fido_bio_dev_enroll_begin
+ fido_bio_dev_get_info fido_bio_dev_enroll_cancel
+ fido_bio_dev_get_info fido_bio_dev_enroll_continue
+ fido_bio_dev_get_info fido_bio_dev_enroll_remove
+ fido_bio_dev_get_info fido_bio_dev_get_template_array
+ fido_bio_dev_get_info fido_bio_dev_set_template_name
+ fido_bio_enroll_new fido_bio_enroll_free
+ fido_bio_enroll_new fido_bio_enroll_last_status
+ fido_bio_enroll_new fido_bio_enroll_remaining_samples
+ fido_bio_info_new fido_bio_info_free
+ fido_bio_info_new fido_bio_info_max_samples
+ fido_bio_info_new fido_bio_info_type
+ fido_bio_template fido_bio_template_array_count
+ fido_bio_template fido_bio_template_array_free
+ fido_bio_template fido_bio_template_array_new
+ fido_bio_template fido_bio_template_free
+ fido_bio_template fido_bio_template_id_len
+ fido_bio_template fido_bio_template_id_ptr
+ fido_bio_template fido_bio_template_name
+ fido_bio_template fido_bio_template_new
+ fido_bio_template fido_bio_template_set_id
+ fido_bio_template fido_bio_template_set_name
+ fido_cbor_info_new fido_cbor_info_aaguid_len
+ fido_cbor_info_new fido_cbor_info_aaguid_ptr
+ fido_cbor_info_new fido_cbor_info_algorithm_cose
+ fido_cbor_info_new fido_cbor_info_algorithm_count
+ fido_cbor_info_new fido_cbor_info_algorithm_type
+ fido_cbor_info_new fido_cbor_info_certs_len
+ fido_cbor_info_new fido_cbor_info_certs_name_ptr
+ fido_cbor_info_new fido_cbor_info_certs_value_ptr
+ fido_cbor_info_new fido_cbor_info_extensions_len
+ fido_cbor_info_new fido_cbor_info_extensions_ptr
+ fido_cbor_info_new fido_cbor_info_free
+ fido_cbor_info_new fido_cbor_info_fwversion
+ fido_cbor_info_new fido_cbor_info_maxcredbloblen
+ fido_cbor_info_new fido_cbor_info_maxcredcntlst
+ fido_cbor_info_new fido_cbor_info_maxcredidlen
+ fido_cbor_info_new fido_cbor_info_maxlargeblob
+ fido_cbor_info_new fido_cbor_info_maxmsgsiz
+ fido_cbor_info_new fido_cbor_info_maxrpid_minpinlen
+ fido_cbor_info_new fido_cbor_info_minpinlen
+ fido_cbor_info_new fido_cbor_info_new_pin_required
+ fido_cbor_info_new fido_cbor_info_options_len
+ fido_cbor_info_new fido_cbor_info_options_name_ptr
+ fido_cbor_info_new fido_cbor_info_options_value_ptr
+ fido_cbor_info_new fido_cbor_info_protocols_len
+ fido_cbor_info_new fido_cbor_info_protocols_ptr
+ fido_cbor_info_new fido_cbor_info_rk_remaining
+ fido_cbor_info_new fido_cbor_info_transports_len
+ fido_cbor_info_new fido_cbor_info_transports_ptr
+ fido_cbor_info_new fido_cbor_info_uv_attempts
+ fido_cbor_info_new fido_cbor_info_uv_modality
+ fido_cbor_info_new fido_cbor_info_versions_len
+ fido_cbor_info_new fido_cbor_info_versions_ptr
+ fido_cbor_info_new fido_dev_get_cbor_info
+ fido_cred_exclude fido_cred_empty_exclude_list
+ fido_cred_new fido_cred_aaguid_len
+ fido_cred_new fido_cred_aaguid_ptr
+ fido_cred_new fido_cred_attstmt_len
+ fido_cred_new fido_cred_attstmt_ptr
+ fido_cred_new fido_cred_authdata_len
+ fido_cred_new fido_cred_authdata_ptr
+ fido_cred_new fido_cred_authdata_raw_len
+ fido_cred_new fido_cred_authdata_raw_ptr
+ fido_cred_new fido_cred_clientdata_hash_len
+ fido_cred_new fido_cred_clientdata_hash_ptr
+ fido_cred_new fido_cred_display_name
+ fido_cred_new fido_cred_flags
+ fido_cred_new fido_cred_fmt
+ fido_cred_new fido_cred_free
+ fido_cred_new fido_cred_id_len
+ fido_cred_new fido_cred_id_ptr
+ fido_cred_new fido_cred_largeblob_key_len
+ fido_cred_new fido_cred_largeblob_key_ptr
+ fido_cred_new fido_cred_pin_minlen
+ fido_cred_new fido_cred_prot
+ fido_cred_new fido_cred_pubkey_len
+ fido_cred_new fido_cred_pubkey_ptr
+ fido_cred_new fido_cred_rp_id
+ fido_cred_new fido_cred_rp_name
+ fido_cred_new fido_cred_sigcount
+ fido_cred_new fido_cred_sig_len
+ fido_cred_new fido_cred_sig_ptr
+ fido_cred_new fido_cred_type
+ fido_cred_new fido_cred_user_id_len
+ fido_cred_new fido_cred_user_id_ptr
+ fido_cred_new fido_cred_user_name
+ fido_cred_new fido_cred_x5c_len
+ fido_cred_new fido_cred_x5c_ptr
+ fido_cred_verify fido_cred_verify_self
+ fido_credman_metadata_new fido_credman_del_dev_rk
+ fido_credman_metadata_new fido_credman_get_dev_metadata
+ fido_credman_metadata_new fido_credman_get_dev_rk
+ fido_credman_metadata_new fido_credman_get_dev_rp
+ fido_credman_metadata_new fido_credman_metadata_free
+ fido_credman_metadata_new fido_credman_rk
+ fido_credman_metadata_new fido_credman_rk_count
+ fido_credman_metadata_new fido_credman_rk_existing
+ fido_credman_metadata_new fido_credman_rk_free
+ fido_credman_metadata_new fido_credman_rk_new
+ fido_credman_metadata_new fido_credman_rk_remaining
+ fido_credman_metadata_new fido_credman_rp_count
+ fido_credman_metadata_new fido_credman_rp_free
+ fido_credman_metadata_new fido_credman_rp_id
+ fido_credman_metadata_new fido_credman_rp_id_hash_len
+ fido_credman_metadata_new fido_credman_rp_id_hash_ptr
+ fido_credman_metadata_new fido_credman_rp_name
+ fido_credman_metadata_new fido_credman_rp_new
+ fido_credman_metadata_new fido_credman_set_dev_rk
+ fido_cred_set_authdata fido_cred_set_attstmt
+ fido_cred_set_authdata fido_cred_set_authdata_raw
+ fido_cred_set_authdata fido_cred_set_blob
+ fido_cred_set_authdata fido_cred_set_clientdata
+ fido_cred_set_authdata fido_cred_set_clientdata_hash
+ fido_cred_set_authdata fido_cred_set_extensions
+ fido_cred_set_authdata fido_cred_set_fmt
+ fido_cred_set_authdata fido_cred_set_id
+ fido_cred_set_authdata fido_cred_set_pin_minlen
+ fido_cred_set_authdata fido_cred_set_prot
+ fido_cred_set_authdata fido_cred_set_rk
+ fido_cred_set_authdata fido_cred_set_rp
+ fido_cred_set_authdata fido_cred_set_sig
+ fido_cred_set_authdata fido_cred_set_type
+ fido_cred_set_authdata fido_cred_set_user
+ fido_cred_set_authdata fido_cred_set_uv
+ fido_cred_set_authdata fido_cred_set_x509
+ fido_dev_enable_entattest fido_dev_toggle_always_uv
+ fido_dev_enable_entattest fido_dev_force_pin_change
+ fido_dev_enable_entattest fido_dev_set_pin_minlen
+ fido_dev_enable_entattest fido_dev_set_pin_minlen_rpid
+ fido_dev_get_touch_begin fido_dev_get_touch_status
+ fido_dev_info_manifest fido_dev_info_free
+ fido_dev_info_manifest fido_dev_info_manufacturer_string
+ fido_dev_info_manifest fido_dev_info_new
+ fido_dev_info_manifest fido_dev_info_path
+ fido_dev_info_manifest fido_dev_info_product
+ fido_dev_info_manifest fido_dev_info_product_string
+ fido_dev_info_manifest fido_dev_info_ptr
+ fido_dev_info_manifest fido_dev_info_set
+ fido_dev_info_manifest fido_dev_info_vendor
+ fido_dev_open fido_dev_build
+ fido_dev_open fido_dev_cancel
+ fido_dev_open fido_dev_close
+ fido_dev_open fido_dev_flags
+ fido_dev_open fido_dev_force_fido2
+ fido_dev_open fido_dev_force_u2f
+ fido_dev_open fido_dev_free
+ fido_dev_open fido_dev_has_pin
+ fido_dev_open fido_dev_has_uv
+ fido_dev_open fido_dev_is_fido2
+ fido_dev_open fido_dev_is_winhello
+ fido_dev_open fido_dev_major
+ fido_dev_open fido_dev_minor
+ fido_dev_open fido_dev_new
+ fido_dev_open fido_dev_new_with_info
+ fido_dev_open fido_dev_open_with_info
+ fido_dev_open fido_dev_protocol
+ fido_dev_open fido_dev_supports_cred_prot
+ fido_dev_open fido_dev_supports_credman
+ fido_dev_open fido_dev_supports_permissions
+ fido_dev_open fido_dev_supports_pin
+ fido_dev_open fido_dev_supports_uv
+ fido_dev_set_pin fido_dev_get_retry_count
+ fido_dev_set_pin fido_dev_get_uv_retry_count
+ fido_dev_set_pin fido_dev_reset
+ fido_dev_set_io_functions fido_dev_io_handle
+ fido_dev_set_io_functions fido_dev_set_sigmask
+ fido_dev_set_io_functions fido_dev_set_timeout
+ fido_dev_set_io_functions fido_dev_set_transport_functions
+ fido_dev_largeblob_get fido_dev_largeblob_set
+ fido_dev_largeblob_get fido_dev_largeblob_remove
+ fido_dev_largeblob_get fido_dev_largeblob_get_array
+ fido_dev_largeblob_get fido_dev_largeblob_set_array
+ fido_init fido_set_log_handler
+ rs256_pk_new rs256_pk_free
+ rs256_pk_new rs256_pk_from_ptr
+ rs256_pk_new rs256_pk_from_EVP_PKEY
+ rs256_pk_new rs256_pk_from_RSA
+ rs256_pk_new rs256_pk_to_EVP_PKEY
+)
+
+list(LENGTH MAN_ALIAS MAN_ALIAS_LEN)
+math(EXPR MAN_ALIAS_MAX "${MAN_ALIAS_LEN} - 2")
+
+# man_copy
+foreach(f ${MAN_SOURCES})
+ add_custom_command(OUTPUT ${f}
+ COMMAND cp -f ${PROJECT_SOURCE_DIR}/man/${f} .
+ DEPENDS ${f})
+ list(APPEND COPY_FILES ${f})
+endforeach()
+
+# man_lint
+foreach(f ${MAN_SOURCES})
+ add_custom_command(OUTPUT ${f}.lint
+ COMMAND mandoc -T lint -W warning ${f} > ${f}.lint
+ DEPENDS ${f})
+ list(APPEND LINT_FILES ${f}.lint)
+endforeach()
+
+# man_html
+foreach(f ${MAN_SOURCES})
+ string(REGEX REPLACE "\\.[13]$" "" g ${f})
+ add_custom_command(OUTPUT ${g}.html
+ COMMAND mandoc -T html -O man="%N.html",style=style.css -I os="Yubico AB" ${f} > ${g}.html
+ DEPENDS ${f})
+ list(APPEND HTML_FILES ${g}.html)
+endforeach()
+
+# man_html_partial
+foreach(f ${MAN_SOURCES})
+ string(REGEX REPLACE "\\.[13]$" "" g ${f})
+ add_custom_command(OUTPUT ${g}.partial
+ COMMAND cat ${PROJECT_SOURCE_DIR}/man/dyc.css > ${g}.partial
+ COMMAND mandoc -T html -O man="%N.html",fragment ${f} >> ${g}.partial
+ DEPENDS ${f})
+ list(APPEND HTML_PARTIAL_FILES ${g}.partial)
+endforeach()
+
+# man_gzip
+foreach(f ${MAN_SOURCES})
+ add_custom_command(OUTPUT ${f}.gz
+ COMMAND gzip -cn ${f} > ${f}.gz
+ DEPENDS ${f})
+ list(APPEND GZ_FILES ${f}.gz)
+endforeach()
+
+macro(define_symlink_target NAME EXT)
+ foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2)
+ math(EXPR j "${i} + 1")
+ list(GET MAN_ALIAS ${i} SRC)
+ list(GET MAN_ALIAS ${j} DST)
+ add_custom_command(OUTPUT ${DST}.${EXT}
+ COMMAND ln -sf ${SRC}.${EXT} ${DST}.${EXT})
+ list(APPEND ${NAME}_LINK_FILES ${DST}.${EXT})
+ endforeach()
+ add_custom_target(${NAME} DEPENDS ${${NAME}_LINK_FILES})
+endmacro()
+
+add_custom_target(man_copy DEPENDS ${COPY_FILES})
+add_custom_target(man_lint DEPENDS ${LINT_FILES})
+add_custom_target(man_html DEPENDS ${HTML_FILES})
+add_custom_target(man_html_partial DEPENDS ${HTML_PARTIAL_FILES})
+add_custom_target(man_gzip DEPENDS ${GZ_FILES})
+
+define_symlink_target(man_symlink 3)
+define_symlink_target(man_symlink_html html)
+define_symlink_target(man_symlink_html_partial partial)
+define_symlink_target(man_symlink_gzip 3.gz)
+
+add_dependencies(man_symlink man_copy)
+add_dependencies(man_lint man_symlink)
+add_dependencies(man_html man_lint)
+add_dependencies(man_symlink_html man_html)
+add_dependencies(man_html_partial man_lint)
+add_dependencies(man_symlink_html_partial man_html_partial)
+add_custom_target(man ALL)
+
+if(MANDOC_PATH)
+ add_dependencies(man man_symlink_html)
+ add_dependencies(man_gzip man_lint)
+ install(FILES ${PROJECT_SOURCE_DIR}/man/style.css
+ DESTINATION "${CMAKE_INSTALL_DOCDIR}/html")
+ foreach(f ${MAN_SOURCES})
+ string(REGEX REPLACE "\\.[13]$" "" f ${f})
+ install(FILES ${PROJECT_BINARY_DIR}/man/${f}.html
+ DESTINATION "${CMAKE_INSTALL_DOCDIR}/html")
+ endforeach()
+ foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2)
+ math(EXPR j "${i} + 1")
+ list(GET MAN_ALIAS ${j} DST)
+ install(FILES ${PROJECT_BINARY_DIR}/man/${DST}.html
+ DESTINATION "${CMAKE_INSTALL_DOCDIR}/html")
+ endforeach()
+endif()
+
+if(GZIP_PATH)
+ add_dependencies(man_gzip man_copy)
+ add_dependencies(man_symlink_gzip man_gzip)
+ add_dependencies(man man_symlink_gzip)
+ foreach(f ${MAN_SOURCES})
+ if (${f} MATCHES ".1$")
+ install(FILES ${PROJECT_BINARY_DIR}/man/${f}.gz
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man1")
+ elseif(${f} MATCHES ".3$")
+ install(FILES ${PROJECT_BINARY_DIR}/man/${f}.gz
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man3")
+ endif()
+ endforeach()
+ foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2)
+ math(EXPR j "${i} + 1")
+ list(GET MAN_ALIAS ${j} DST)
+ install(FILES ${PROJECT_BINARY_DIR}/man/${DST}.3.gz
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man3")
+ endforeach()
+elseif(NOT MSVC)
+ add_dependencies(man man_symlink)
+ foreach(f ${MAN_SOURCES})
+ if (${f} MATCHES ".1$")
+ install(FILES ${PROJECT_BINARY_DIR}/man/${f}
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man1")
+ elseif(${f} MATCHES ".3$")
+ install(FILES ${PROJECT_BINARY_DIR}/man/${f}
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man3")
+ endif()
+ endforeach()
+ foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2)
+ math(EXPR j "${i} + 1")
+ list(GET MAN_ALIAS ${j} DST)
+ install(FILES ${PROJECT_BINARY_DIR}/man/${DST}.3
+ DESTINATION "${CMAKE_INSTALL_MANDIR}/man3")
+ endforeach()
+endif()
diff --git a/man/NOTES b/man/NOTES
new file mode 100644
index 0000000..5cba436
--- /dev/null
+++ b/man/NOTES
@@ -0,0 +1,7 @@
+To generate .partial files for https://developers.yubico.com/:
+
+$ make -C build man_symlink_html_partial
+$ (cd build/man && pax -p p -r -w *.partial /tmp/partial)
+
+Use mandoc 1.14.4. Otherwise, adjust dyc.css to mandoc's HTML
+output.
diff --git a/man/check.sh b/man/check.sh
new file mode 100755
index 0000000..d969a7a
--- /dev/null
+++ b/man/check.sh
@@ -0,0 +1,43 @@
+#!/bin/sh -u
+
+# 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
+
+T=$(mktemp -d) || exit 1
+find . -maxdepth 1 -type f -name '*.3' -print0 > "$T/files"
+
+xargs -0 awk '/^.Sh NAME/,/^.Nd/' < "$T/files" | \
+ awk '/^.Nm/ { print $2 }' | sort -u > "$T/Nm"
+xargs -0 awk '/^.Fn/ { print $2 }' < "$T/files" | sort -u > "$T/Fn"
+(cd "$T" && diff -u Nm Fn)
+
+cut -c2- ../src/export.llvm | sort > "$T/exports"
+(cd "$T" && diff -u Nm exports)
+
+awk '/^list\(APPEND MAN_SOURCES/,/^\)/' CMakeLists.txt | \
+ awk '/.3$/ { print $1 }' | sort > "$T/listed_sources"
+xargs -0 -n1 basename < "$T/files" | sort > "$T/actual_sources"
+(cd "$T" && diff -u listed_sources actual_sources)
+
+awk '/^list\(APPEND MAN_ALIAS/,/^\)/' CMakeLists.txt | \
+ sed '1d;$d' | awk '{ print $1, $2 }' | sort > "$T/listed_aliases"
+xargs -0 grep -o "^.Fn [A-Za-z0-9_]* \"" < "$T/files" | \
+ cut -c3- | sed 's/\.3:\.Fn//;s/ "//' | awk '$1 != $2' | \
+ sort > "$T/actual_aliases"
+(cd "$T" && diff -u listed_aliases actual_aliases)
+
+xargs -0 grep -hB1 "^.Fn [A-Za-z0-9_]* \"" < "$T/files" | \
+ sed -E 's/^.F[tn] //;s/\*[^"\*]+"/\*"/g;s/ [^" \*]+"/"/g;/^--$/d' | \
+ paste -d " " - - | sed 's/\* /\*/' | sort > "$T/documented_prototypes"
+while read -r f; do
+ awk "/\/\*/ { next } /$f\(/,/;/" ../src/fido.h ../src/fido/*.h | \
+ sed -E 's/^[ ]+//;s/[ ]+/ /' | tr '\n' ' ' | \
+ sed 's/(/ "/;s/, /" "/g;s/);/"/;s/ $/\n/'
+done < "$T/exports" | sort > "$T/actual_prototypes"
+(cd "$T" && diff -u documented_prototypes actual_prototypes)
+
+(cd "$T" && rm files Nm Fn exports listed_sources actual_sources \
+ listed_aliases actual_aliases documented_prototypes actual_prototypes)
+rmdir -- "$T"
diff --git a/man/dyc.css b/man/dyc.css
new file mode 100644
index 0000000..1ff5b59
--- /dev/null
+++ b/man/dyc.css
@@ -0,0 +1,14 @@
+<style>
+ table.head, table.foot { width: 100%; }
+ td.head-rtitle, td.foot-os { text-align: right; }
+ td.head-vol { text-align: center; }
+ div.Pp { margin: 1ex 0ex; }
+ div.Nd, div.Bf, div.Op { display: inline; }
+ span.Pa, span.Ad { font-style: italic; }
+ span.Ms { font-weight: bold; }
+ dl.Bl-diag > dt { font-weight: bold; }
+ code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
+ code.Cd { font-weight: bold; font-family: monospace; }
+ var { font-family: monospace; }
+ .Sh { font-size: 1.5em; padding-top: 1em; padding-bottom: 1em; }
+</style>
diff --git a/man/eddsa_pk_new.3 b/man/eddsa_pk_new.3
new file mode 100644
index 0000000..428d724
--- /dev/null
+++ b/man/eddsa_pk_new.3
@@ -0,0 +1,146 @@
+.\" Copyright (c) 2019-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 15 2022 $
+.Dt EDDSA_PK_NEW 3
+.Os
+.Sh NAME
+.Nm eddsa_pk_new ,
+.Nm eddsa_pk_free ,
+.Nm eddsa_pk_from_EVP_PKEY ,
+.Nm eddsa_pk_from_ptr ,
+.Nm eddsa_pk_to_EVP_PKEY
+.Nd FIDO2 COSE EDDSA API
+.Sh SYNOPSIS
+.In openssl/evp.h
+.In fido/eddsa.h
+.Ft eddsa_pk_t *
+.Fn eddsa_pk_new "void"
+.Ft void
+.Fn eddsa_pk_free "eddsa_pk_t **pkp"
+.Ft int
+.Fn eddsa_pk_from_EVP_PKEY "eddsa_pk_t *pk" "const EVP_PKEY *pkey"
+.Ft int
+.Fn eddsa_pk_from_ptr "eddsa_pk_t *pk" "const void *ptr" "size_t len"
+.Ft EVP_PKEY *
+.Fn eddsa_pk_to_EVP_PKEY "const eddsa_pk_t *pk"
+.Sh DESCRIPTION
+EDDSA is the name given in the CBOR Object Signing and Encryption
+(COSE) RFC to EDDSA over Curve25519 with SHA-512.
+The COSE EDDSA API of
+.Em libfido2
+is an auxiliary API with routines to convert between the different
+EDDSA public key types used in
+.Em libfido2
+and
+.Em OpenSSL .
+.Pp
+In
+.Em libfido2 ,
+EDDSA public keys are abstracted by the
+.Vt eddsa_pk_t
+type.
+.Pp
+The
+.Fn eddsa_pk_new
+function returns a pointer to a newly allocated, empty
+.Vt eddsa_pk_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn eddsa_pk_free
+function releases the memory backing
+.Fa *pkp ,
+where
+.Fa *pkp
+must have been previously allocated by
+.Fn eddsa_pk_new .
+On return,
+.Fa *pkp
+is set to NULL.
+Either
+.Fa pkp
+or
+.Fa *pkp
+may be NULL, in which case
+.Fn eddsa_pk_free
+is a NOP.
+.Pp
+The
+.Fn eddsa_pk_from_EVP_PKEY
+function fills
+.Fa pk
+with the contents of
+.Fa pkey .
+No references to
+.Fa pkey
+are kept.
+.Pp
+The
+.Fn eddsa_pk_from_ptr
+function fills
+.Fa pk
+with the contents of
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+No references to
+.Fa ptr
+are kept.
+.Pp
+The
+.Fn eddsa_pk_to_EVP_PKEY
+function converts
+.Fa pk
+to a newly allocated
+.Fa EVP_PKEY
+type with a reference count of 1.
+No internal references to the returned pointer are kept.
+If an error occurs,
+.Fn eddsa_pk_to_EVP_PKEY
+returns NULL.
+.Sh RETURN VALUES
+The
+.Fn eddsa_pk_from_EVP_PKEY
+and
+.Fn eddsa_pk_from_ptr
+functions return
+.Dv FIDO_OK
+on success.
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+.Sh SEE ALSO
+.Xr es256_pk_new 3 ,
+.Xr es384_pk_new 3 ,
+.Xr fido_assert_verify 3 ,
+.Xr fido_cred_pubkey_ptr 3 ,
+.Xr rs256_pk_new 3
diff --git a/man/es256_pk_new.3 b/man/es256_pk_new.3
new file mode 100644
index 0000000..7d6be4d
--- /dev/null
+++ b/man/es256_pk_new.3
@@ -0,0 +1,164 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 15 2022 $
+.Dt ES256_PK_NEW 3
+.Os
+.Sh NAME
+.Nm es256_pk_new ,
+.Nm es256_pk_free ,
+.Nm es256_pk_from_EC_KEY ,
+.Nm es256_pk_from_EVP_PKEY ,
+.Nm es256_pk_from_ptr ,
+.Nm es256_pk_to_EVP_PKEY
+.Nd FIDO2 COSE ES256 API
+.Sh SYNOPSIS
+.In openssl/ec.h
+.In fido/es256.h
+.Ft es256_pk_t *
+.Fn es256_pk_new "void"
+.Ft void
+.Fn es256_pk_free "es256_pk_t **pkp"
+.Ft int
+.Fn es256_pk_from_EC_KEY "es256_pk_t *pk" "const EC_KEY *ec"
+.Ft int
+.Fn es256_pk_from_EVP_PKEY "es256_pk_t *pk" "const EVP_PKEY *pkey"
+.Ft int
+.Fn es256_pk_from_ptr "es256_pk_t *pk" "const void *ptr" "size_t len"
+.Ft EVP_PKEY *
+.Fn es256_pk_to_EVP_PKEY "const es256_pk_t *pk"
+.Sh DESCRIPTION
+ES256 is the name given in the CBOR Object Signing and Encryption
+(COSE) RFC to ECDSA over P-256 with SHA-256.
+The COSE ES256 API of
+.Em libfido2
+is an auxiliary API with routines to convert between the different
+ECDSA public key types used in
+.Em libfido2
+and
+.Em OpenSSL .
+.Pp
+In
+.Em libfido2 ,
+ES256 public keys are abstracted by the
+.Vt es256_pk_t
+type.
+.Pp
+The
+.Fn es256_pk_new
+function returns a pointer to a newly allocated, empty
+.Vt es256_pk_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn es256_pk_free
+function releases the memory backing
+.Fa *pkp ,
+where
+.Fa *pkp
+must have been previously allocated by
+.Fn es256_pk_new .
+On return,
+.Fa *pkp
+is set to NULL.
+Either
+.Fa pkp
+or
+.Fa *pkp
+may be NULL, in which case
+.Fn es256_pk_free
+is a NOP.
+.Pp
+The
+.Fn es256_pk_from_EC_KEY
+function fills
+.Fa pk
+with the contents of
+.Fa ec .
+No references to
+.Fa ec
+are kept.
+.Pp
+The
+.Fn es256_pk_from_EVP_PKEY
+function fills
+.Fa pk
+with the contents of
+.Fa pkey .
+No references to
+.Fa pkey
+are kept.
+.Pp
+The
+.Fn es256_pk_from_ptr
+function fills
+.Fa pk
+with the contents of
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+The
+.Fa ptr
+pointer may point to an uncompressed point, or to the
+concatenation of the x and y coordinates.
+No references to
+.Fa ptr
+are kept.
+.Pp
+The
+.Fn es256_pk_to_EVP_PKEY
+function converts
+.Fa pk
+to a newly allocated
+.Fa EVP_PKEY
+type with a reference count of 1.
+No internal references to the returned pointer are kept.
+If an error occurs,
+.Fn es256_pk_to_EVP_PKEY
+returns NULL.
+.Sh RETURN VALUES
+The
+.Fn es256_pk_from_EC_KEY ,
+.Fn es256_pk_from_EVP_PKEY ,
+and
+.Fn es256_pk_from_ptr
+functions return
+.Dv FIDO_OK
+on success.
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+.Sh SEE ALSO
+.Xr eddsa_pk_new 3 ,
+.Xr es384_pk_new 3 ,
+.Xr fido_assert_verify 3 ,
+.Xr fido_cred_pubkey_ptr 3 ,
+.Xr rs256_pk_new 3
diff --git a/man/es384_pk_new.3 b/man/es384_pk_new.3
new file mode 100644
index 0000000..e865913
--- /dev/null
+++ b/man/es384_pk_new.3
@@ -0,0 +1,164 @@
+.\" Copyright (c) 2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 15 2022 $
+.Dt ES384_PK_NEW 3
+.Os
+.Sh NAME
+.Nm es384_pk_new ,
+.Nm es384_pk_free ,
+.Nm es384_pk_from_EC_KEY ,
+.Nm es384_pk_from_EVP_PKEY ,
+.Nm es384_pk_from_ptr ,
+.Nm es384_pk_to_EVP_PKEY
+.Nd FIDO2 COSE ES384 API
+.Sh SYNOPSIS
+.In openssl/ec.h
+.In fido/es384.h
+.Ft es384_pk_t *
+.Fn es384_pk_new "void"
+.Ft void
+.Fn es384_pk_free "es384_pk_t **pkp"
+.Ft int
+.Fn es384_pk_from_EC_KEY "es384_pk_t *pk" "const EC_KEY *ec"
+.Ft int
+.Fn es384_pk_from_EVP_PKEY "es384_pk_t *pk" "const EVP_PKEY *pkey"
+.Ft int
+.Fn es384_pk_from_ptr "es384_pk_t *pk" "const void *ptr" "size_t len"
+.Ft EVP_PKEY *
+.Fn es384_pk_to_EVP_PKEY "const es384_pk_t *pk"
+.Sh DESCRIPTION
+ES384 is the name given in the CBOR Object Signing and Encryption
+(COSE) RFC to ECDSA over P-384 with SHA-384.
+The COSE ES384 API of
+.Em libfido2
+is an auxiliary API with routines to convert between the different
+ECDSA public key types used in
+.Em libfido2
+and
+.Em OpenSSL .
+.Pp
+In
+.Em libfido2 ,
+ES384 public keys are abstracted by the
+.Vt es384_pk_t
+type.
+.Pp
+The
+.Fn es384_pk_new
+function returns a pointer to a newly allocated, empty
+.Vt es384_pk_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn es384_pk_free
+function releases the memory backing
+.Fa *pkp ,
+where
+.Fa *pkp
+must have been previously allocated by
+.Fn es384_pk_new .
+On return,
+.Fa *pkp
+is set to NULL.
+Either
+.Fa pkp
+or
+.Fa *pkp
+may be NULL, in which case
+.Fn es384_pk_free
+is a NOP.
+.Pp
+The
+.Fn es384_pk_from_EC_KEY
+function fills
+.Fa pk
+with the contents of
+.Fa ec .
+No references to
+.Fa ec
+are kept.
+.Pp
+The
+.Fn es384_pk_from_EVP_PKEY
+function fills
+.Fa pk
+with the contents of
+.Fa pkey .
+No references to
+.Fa pkey
+are kept.
+.Pp
+The
+.Fn es384_pk_from_ptr
+function fills
+.Fa pk
+with the contents of
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+The
+.Fa ptr
+pointer may point to an uncompressed point, or to the
+concatenation of the x and y coordinates.
+No references to
+.Fa ptr
+are kept.
+.Pp
+The
+.Fn es384_pk_to_EVP_PKEY
+function converts
+.Fa pk
+to a newly allocated
+.Fa EVP_PKEY
+type with a reference count of 1.
+No internal references to the returned pointer are kept.
+If an error occurs,
+.Fn es384_pk_to_EVP_PKEY
+returns NULL.
+.Sh RETURN VALUES
+The
+.Fn es384_pk_from_EC_KEY ,
+.Fn es384_pk_from_EVP_PKEY ,
+and
+.Fn es384_pk_from_ptr
+functions return
+.Dv FIDO_OK
+on success.
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+.Sh SEE ALSO
+.Xr eddsa_pk_new 3 ,
+.Xr es256_pk_new 3 ,
+.Xr fido_assert_verify 3 ,
+.Xr fido_cred_pubkey_ptr 3 ,
+.Xr rs256_pk_new 3
diff --git a/man/fido2-assert.1 b/man/fido2-assert.1
new file mode 100644
index 0000000..882b7ab
--- /dev/null
+++ b/man/fido2-assert.1
@@ -0,0 +1,286 @@
+.\" Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 3 2023 $
+.Dt FIDO2-ASSERT 1
+.Os
+.Sh NAME
+.Nm fido2-assert
+.Nd get/verify a FIDO2 assertion
+.Sh SYNOPSIS
+.Nm
+.Fl G
+.Op Fl bdhpruvw
+.Op Fl t Ar option
+.Op Fl i Ar input_file
+.Op Fl o Ar output_file
+.Ar device
+.Nm
+.Fl V
+.Op Fl dhpv
+.Op Fl i Ar input_file
+.Ar key_file
+.Op Ar type
+.Sh DESCRIPTION
+.Nm
+gets or verifies a FIDO2 assertion.
+.Pp
+The input of
+.Nm
+is defined by the parameters of the assertion to be obtained/verified.
+See the
+.Sx INPUT FORMAT
+section for details.
+.Pp
+The output of
+.Nm
+is defined by the result of the selected operation.
+See the
+.Sx OUTPUT FORMAT
+section for details.
+.Pp
+If an assertion is successfully obtained or verified,
+.Nm
+exits 0.
+Otherwise,
+.Nm
+exits 1.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl G
+Tells
+.Nm
+to obtain a new assertion from
+.Ar device .
+.It Fl V
+Tells
+.Nm
+to verify an assertion using the PEM-encoded public key in
+.Ar key_file
+of type
+.Ar type ,
+where
+.Ar type
+may be
+.Em es256
+(denoting ECDSA over NIST P-256 with SHA-256),
+.Em rs256
+(denoting 2048-bit RSA with PKCS#1.5 padding and SHA-256), or
+.Em eddsa
+(denoting EDDSA over Curve25519 with SHA-512).
+If
+.Ar type
+is not specified,
+.Em es256
+is assumed.
+.It Fl b
+Request the credential's
+.Dq largeBlobKey ,
+a 32-byte symmetric key associated with the asserted credential.
+.It Fl h
+If obtaining an assertion, enable the FIDO2 hmac-secret
+extension.
+If verifying an assertion, check whether the extension data bit was
+signed by the authenticator.
+.It Fl d
+Causes
+.Nm
+to emit debugging output on
+.Em stderr .
+.It Fl i Ar input_file
+Tells
+.Nm
+to read the parameters of the assertion from
+.Ar input_file
+instead of
+.Em stdin .
+.It Fl o Ar output_file
+Tells
+.Nm
+to write output on
+.Ar output_file
+instead of
+.Em stdout .
+.It Fl p
+If obtaining an assertion, request user presence.
+If verifying an assertion, check whether the user presence bit was
+signed by the authenticator.
+.It Fl r
+Obtain an assertion using a resident credential.
+If
+.Fl r
+is specified,
+.Nm
+will not expect a credential id in its input, and may output
+multiple assertions.
+Resident credentials are called
+.Dq discoverable credentials
+in CTAP 2.1.
+.It Fl t Ar option
+Toggles a key/value
+.Ar option ,
+where
+.Ar option
+is a string of the form
+.Dq key=value .
+The options supported at present are:
+.Bl -tag -width Ds
+.It Cm up Ns = Ns Ar true|false
+Asks the authenticator for user presence to be enabled or disabled.
+.It Cm uv Ns = Ns Ar true|false
+Asks the authenticator for user verification to be enabled or
+disabled.
+.It Cm pin Ns = Ns Ar true|false
+Tells
+.Nm
+whether to prompt for a PIN and request user verification.
+.El
+.Pp
+The
+.Fl t
+option may be specified multiple times.
+.It Fl u
+Obtain an assertion using U2F.
+By default,
+.Nm
+will use FIDO2 if supported by the authenticator, and fallback to
+U2F otherwise.
+.It Fl v
+If obtaining an assertion, prompt the user for a PIN and request
+user verification from the authenticator.
+If verifying an assertion, check whether the user verification bit
+was signed by the authenticator.
+.It Fl w
+Tells
+.Nm
+that the first line of input when obtaining an assertion shall be
+interpreted as unhashed client data.
+This is required by Windows Hello, which calculates the client data hash
+internally.
+.El
+.Pp
+If a
+.Em tty
+is available,
+.Nm
+will use it to obtain the PIN.
+Otherwise,
+.Em stdin
+is used.
+.Sh INPUT FORMAT
+The input of
+.Nm
+consists of base64 blobs and UTF-8 strings separated
+by newline characters ('\\n').
+.Pp
+When obtaining an assertion,
+.Nm
+expects its input to consist of:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+client data hash (base64 blob);
+.It
+relying party id (UTF-8 string);
+.It
+credential id, if credential not resident (base64 blob);
+.It
+hmac salt, if the FIDO2 hmac-secret extension is enabled
+(base64 blob);
+.El
+.Pp
+When verifying an assertion,
+.Nm
+expects its input to consist of:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+client data hash (base64 blob);
+.It
+relying party id (UTF-8 string);
+.It
+authenticator data (base64 blob);
+.It
+assertion signature (base64 blob);
+.El
+.Pp
+UTF-8 strings passed to
+.Nm
+must not contain embedded newline or NUL characters.
+.Sh OUTPUT FORMAT
+The output of
+.Nm
+consists of base64 blobs and UTF-8 strings separated
+by newline characters ('\\n').
+.Pp
+For each generated assertion,
+.Nm
+outputs:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+client data hash (base64 blob);
+.It
+relying party id (UTF-8 string);
+.It
+authenticator data (base64 blob);
+.It
+assertion signature (base64 blob);
+.It
+user id, if credential resident (base64 blob);
+.It
+hmac secret, if the FIDO2 hmac-secret extension is enabled
+(base64 blob);
+.It
+the credential's associated 32-byte symmetric key
+.Pq Dq largeBlobKey ,
+if requested (base64 blob).
+.El
+.Pp
+When verifying an assertion,
+.Nm
+produces no output.
+.Sh EXAMPLES
+Assuming
+.Pa cred
+contains a
+.Em es256
+credential created according to the steps outlined in
+.Xr fido2-cred 1 ,
+obtain an assertion from an authenticator at
+.Pa /dev/hidraw5
+and verify it:
+.Pp
+.Dl $ echo assertion challenge | openssl sha256 -binary | base64 > assert_param
+.Dl $ echo relying party >> assert_param
+.Dl $ head -1 cred >> assert_param
+.Dl $ tail -n +2 cred > pubkey
+.Dl $ fido2-assert -G -i assert_param /dev/hidraw5 | fido2-assert -V pubkey es256
+.Sh SEE ALSO
+.Xr fido2-cred 1 ,
+.Xr fido2-token 1
diff --git a/man/fido2-cred.1 b/man/fido2-cred.1
new file mode 100644
index 0000000..3f181db
--- /dev/null
+++ b/man/fido2-cred.1
@@ -0,0 +1,297 @@
+.\" Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 3 2023 $
+.Dt FIDO2-CRED 1
+.Os
+.Sh NAME
+.Nm fido2-cred
+.Nd make/verify a FIDO2 credential
+.Sh SYNOPSIS
+.Nm
+.Fl M
+.Op Fl bdhqruvw
+.Op Fl c Ar cred_protect
+.Op Fl i Ar input_file
+.Op Fl o Ar output_file
+.Ar device
+.Op Ar type
+.Nm
+.Fl V
+.Op Fl dhv
+.Op Fl c Ar cred_protect
+.Op Fl i Ar input_file
+.Op Fl o Ar output_file
+.Op Ar type
+.Sh DESCRIPTION
+.Nm
+makes or verifies a FIDO2 credential.
+.Pp
+A credential
+.Ar type
+may be
+.Em es256
+(denoting ECDSA over NIST P-256 with SHA-256),
+.Em rs256
+(denoting 2048-bit RSA with PKCS#1.5 padding and SHA-256), or
+.Em eddsa
+(denoting EDDSA over Curve25519 with SHA-512).
+If
+.Ar type
+is not specified,
+.Em es256
+is assumed.
+.Pp
+When making a credential, the authenticator may require the user
+to authenticate with a PIN.
+If the
+.Fl q
+option is not specified,
+.Nm
+will prompt the user for the PIN.
+If a
+.Em tty
+is available,
+.Nm
+will use it to obtain the PIN.
+Otherwise,
+.Em stdin
+is used.
+.Pp
+The input of
+.Nm
+is defined by the parameters of the credential to be made/verified.
+See the
+.Sx INPUT FORMAT
+section for details.
+.Pp
+The output of
+.Nm
+is defined by the result of the selected operation.
+See the
+.Sx OUTPUT FORMAT
+section for details.
+.Pp
+If a credential is successfully created or verified,
+.Nm
+exits 0.
+Otherwise,
+.Nm
+exits 1.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl M
+Tells
+.Nm
+to make a new credential on
+.Ar device .
+.It Fl V
+Tells
+.Nm
+to verify a credential.
+.It Fl b
+Request the credential's
+.Dq largeBlobKey ,
+a 32-byte symmetric key associated with the generated credential.
+.It Fl c Ar cred_protect
+If making a credential, set the credential's protection level to
+.Ar cred_protect ,
+where
+.Ar cred_protect
+is the credential's protection level in decimal notation.
+Please refer to
+.In fido/param.h
+for the set of possible values.
+If verifying a credential, check whether the credential's protection
+level was signed by the authenticator as
+.Ar cred_protect .
+.It Fl d
+Causes
+.Nm
+to emit debugging output on
+.Em stderr .
+.It Fl h
+If making a credential, enable the FIDO2 hmac-secret extension.
+If verifying a credential, check whether the extension data bit was
+signed by the authenticator.
+.It Fl i Ar input_file
+Tells
+.Nm
+to read the parameters of the credential from
+.Ar input_file
+instead of
+.Em stdin .
+.It Fl o Ar output_file
+Tells
+.Nm
+to write output on
+.Ar output_file
+instead of
+.Em stdout .
+.It Fl q
+Tells
+.Nm
+to be quiet.
+If a PIN is required and
+.Fl q
+is specified,
+.Nm
+will fail.
+.It Fl r
+Create a resident credential.
+Resident credentials are called
+.Dq discoverable credentials
+in CTAP 2.1.
+.It Fl u
+Create a U2F credential.
+By default,
+.Nm
+will use FIDO2 if supported by the authenticator, and fallback to
+U2F otherwise.
+.It Fl v
+If making a credential, request user verification.
+If verifying a credential, check whether the user verification bit
+was signed by the authenticator.
+.It Fl w
+Tells
+.Nm
+that the first line of input when making a credential shall be
+interpreted as unhashed client data.
+This is required by Windows Hello, which calculates the client data hash
+internally.
+.El
+.Sh INPUT FORMAT
+The input of
+.Nm
+consists of base64 blobs and UTF-8 strings separated
+by newline characters ('\\n').
+.Pp
+When making a credential,
+.Nm
+expects its input to consist of:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+client data hash (base64 blob);
+.It
+relying party id (UTF-8 string);
+.It
+user name (UTF-8 string);
+.It
+user id (base64 blob).
+.El
+.Pp
+When verifying a credential,
+.Nm
+expects its input to consist of:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+client data hash (base64 blob);
+.It
+relying party id (UTF-8 string);
+.It
+credential format (UTF-8 string);
+.It
+authenticator data (base64 blob);
+.It
+credential id (base64 blob);
+.It
+attestation signature (base64 blob);
+.It
+attestation certificate (optional, base64 blob).
+.El
+.Pp
+UTF-8 strings passed to
+.Nm
+must not contain embedded newline or NUL characters.
+.Sh OUTPUT FORMAT
+The output of
+.Nm
+consists of base64 blobs, UTF-8 strings, and PEM-encoded public
+keys separated by newline characters ('\\n').
+.Pp
+Upon the successful generation of a credential,
+.Nm
+outputs:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+client data hash (base64 blob);
+.It
+relying party id (UTF-8 string);
+.It
+credential format (UTF-8 string);
+.It
+authenticator data (base64 blob);
+.It
+credential id (base64 blob);
+.It
+attestation signature (base64 blob);
+.It
+attestation certificate, if present (base64 blob).
+.It
+the credential's associated 32-byte symmetric key
+.Pq Dq largeBlobKey ,
+if present (base64 blob).
+.El
+.Pp
+Upon the successful verification of a credential,
+.Nm
+outputs:
+.Pp
+.Bl -enum -offset indent -compact
+.It
+credential id (base64 blob);
+.It
+PEM-encoded credential key.
+.El
+.Sh EXAMPLES
+Create a new
+.Em es256
+credential on
+.Pa /dev/hidraw5 ,
+verify it, and save the id and the public key of the credential in
+.Em cred :
+.Pp
+.Dl $ echo credential challenge | openssl sha256 -binary | base64 > cred_param
+.Dl $ echo relying party >> cred_param
+.Dl $ echo user name >> cred_param
+.Dl $ dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param
+.Dl $ fido2-cred -M -i cred_param /dev/hidraw5 | fido2-cred -V -o cred
+.Sh SEE ALSO
+.Xr fido2-assert 1 ,
+.Xr fido2-token 1
+.Sh CAVEATS
+Please note that
+.Nm
+handles Basic Attestation and Self Attestation transparently.
+In the case of Basic Attestation, the validity of the authenticator's
+attestation certificate is
+.Em not
+verified.
diff --git a/man/fido2-token.1 b/man/fido2-token.1
new file mode 100644
index 0000000..65a228c
--- /dev/null
+++ b/man/fido2-token.1
@@ -0,0 +1,421 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: April 11 2022 $
+.Dt FIDO2-TOKEN 1
+.Os
+.Sh NAME
+.Nm fido2-token
+.Nd find and manage a FIDO2 authenticator
+.Sh SYNOPSIS
+.Nm
+.Fl C
+.Op Fl d
+.Ar device
+.Nm
+.Fl D
+.Op Fl d
+.Fl i
+.Ar cred_id
+.Ar device
+.Nm
+.Fl D
+.Fl b
+.Op Fl d
+.Fl k Ar key_path
+.Ar device
+.Nm
+.Fl D
+.Fl b
+.Op Fl d
+.Fl n Ar rp_id
+.Op Fl i Ar cred_id
+.Ar device
+.Nm
+.Fl D
+.Fl e
+.Op Fl d
+.Fl i
+.Ar template_id
+.Ar device
+.Nm
+.Fl D
+.Fl u
+.Op Fl d
+.Ar device
+.Nm
+.Fl G
+.Fl b
+.Op Fl d
+.Fl k Ar key_path
+.Ar blob_path
+.Ar device
+.Nm
+.Fl G
+.Fl b
+.Op Fl d
+.Fl n Ar rp_id
+.Op Fl i Ar cred_id
+.Ar blob_path
+.Ar device
+.Nm
+.Fl I
+.Op Fl cd
+.Op Fl k Ar rp_id Fl i Ar cred_id
+.Ar device
+.Nm
+.Fl L
+.Op Fl bder
+.Op Fl k Ar rp_id
+.Op device
+.Nm
+.Fl R
+.Op Fl d
+.Ar device
+.Nm
+.Fl S
+.Op Fl adefu
+.Ar device
+.Nm
+.Fl S
+.Op Fl d
+.Fl i Ar template_id
+.Fl n Ar template_name
+.Ar device
+.Nm
+.Fl S
+.Op Fl d
+.Fl l Ar pin_length
+.Ar device
+.Nm
+.Fl S
+.Fl b
+.Op Fl d
+.Fl k Ar key_path
+.Ar blob_path
+.Ar device
+.Nm
+.Fl S
+.Fl b
+.Op Fl d
+.Fl n Ar rp_id
+.Op Fl i Ar cred_id
+.Ar blob_path
+.Ar device
+.Nm
+.Fl S
+.Fl c
+.Op Fl d
+.Fl i Ar cred_id
+.Fl k Ar user_id
+.Fl n Ar name
+.Fl p Ar display_name
+.Ar device
+.Nm
+.Fl S
+.Fl m
+.Ar rp_id
+.Ar device
+.Nm
+.Fl V
+.Sh DESCRIPTION
+.Nm
+manages a FIDO2 authenticator.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl C Ar device
+Changes the PIN of
+.Ar device .
+The user will be prompted for the current and new PINs.
+.It Fl D Fl i Ar id Ar device
+Deletes the resident credential specified by
+.Ar id
+from
+.Ar device ,
+where
+.Ar id
+is the credential's base64-encoded id.
+The user will be prompted for the PIN.
+.It Fl D Fl b Fl k Ar key_path Ar device
+Deletes a
+.Dq largeBlob
+encrypted with
+.Ar key_path
+from
+.Ar device ,
+where
+.Ar key_path
+holds the blob's base64-encoded 32-byte AES-256 GCM encryption key.
+A PIN or equivalent user-verification gesture is required.
+.It Fl D Fl b Fl n Ar rp_id Oo Fl i Ar cred_id Oc Ar device
+Deletes a
+.Dq largeBlob
+corresponding to
+.Ar rp_id
+from
+.Ar device .
+If
+.Ar rp_id
+has multiple credentials enrolled on
+.Ar device ,
+the credential ID must be specified using
+.Fl i Ar cred_id ,
+where
+.Ar cred_id
+is a base64-encoded blob.
+A PIN or equivalent user-verification gesture is required.
+.It Fl D Fl e Fl i Ar id Ar device
+Deletes the biometric enrollment specified by
+.Ar id
+from
+.Ar device ,
+where
+.Ar id
+is the enrollment's template base64-encoded id.
+The user will be prompted for the PIN.
+.It Fl D Fl u Ar device
+Disables the CTAP 2.1
+.Dq user verification always
+feature on
+.Ar device .
+.It Fl G Fl b Fl k Ar key_path Ar blob_path Ar device
+Gets a CTAP 2.1
+.Dq largeBlob
+encrypted with
+.Ar key_path
+from
+.Ar device ,
+where
+.Ar key_path
+holds the blob's base64-encoded 32-byte AES-256 GCM encryption key.
+The blob is written to
+.Ar blob_path .
+A PIN or equivalent user-verification gesture is required.
+.It Fl G Fl b Fl n Ar rp_id Oo Fl i Ar cred_id Oc Ar blob_path Ar device
+Gets a CTAP 2.1
+.Dq largeBlob
+associated with
+.Ar rp_id
+from
+.Ar device .
+If
+.Ar rp_id
+has multiple credentials enrolled on
+.Ar device ,
+the credential ID must be specified using
+.Fl i Ar cred_id ,
+where
+.Ar cred_id
+is a base64-encoded blob.
+The blob is written to
+.Ar blob_path .
+A PIN or equivalent user-verification gesture is required.
+.It Fl I Ar device
+Retrieves information on
+.Ar device .
+.It Fl I Fl c Ar device
+Retrieves resident credential metadata from
+.Ar device .
+The user will be prompted for the PIN.
+.It Fl I Fl k Ar rp_id Fl i Ar cred_id Ar device
+Prints the credential id (base64-encoded) and public key
+(PEM encoded) of the resident credential specified by
+.Ar rp_id
+and
+.Ar cred_id ,
+where
+.Ar rp_id
+is a UTF-8 relying party id, and
+.Ar cred_id
+is a base64-encoded credential id.
+The user will be prompted for the PIN.
+.It Fl L
+Produces a list of authenticators found by the operating system.
+.It Fl L Fl b Ar device
+Produces a list of CTAP 2.1
+.Dq largeBlobs
+on
+.Ar device .
+A PIN or equivalent user-verification gesture is required.
+.It Fl L Fl e Ar device
+Produces a list of biometric enrollments on
+.Ar device .
+The user will be prompted for the PIN.
+.It Fl L Fl r Ar device
+Produces a list of relying parties with resident credentials on
+.Ar device .
+The user will be prompted for the PIN.
+.It Fl L Fl k Ar rp_id Ar device
+Produces a list of resident credentials corresponding to
+relying party
+.Ar rp_id
+on
+.Ar device .
+The user will be prompted for the PIN.
+.It Fl R
+Performs a reset on
+.Ar device .
+.Nm
+will NOT prompt for confirmation.
+.It Fl S
+Sets the PIN of
+.Ar device .
+The user will be prompted for the PIN.
+.It Fl S Fl a Ar device
+Enables CTAP 2.1 Enterprise Attestation on
+.Ar device .
+.It Fl S Fl b Fl k Ar key_path Ar blob_path Ar device
+Sets a CTAP 2.1
+.Dq largeBlob
+encrypted with
+.Ar key_path
+on
+.Ar device ,
+where
+.Ar key_path
+holds the blob's base64-encoded 32-byte AES-256 GCM encryption key.
+The blob is read from
+.Fa blob_path .
+A PIN or equivalent user-verification gesture is required.
+.It Fl S Fl b Fl n Ar rp_id Oo Fl i Ar cred_id Oc Ar blob_path Ar device
+Sets a CTAP 2.1
+.Dq largeBlob
+associated with
+.Ar rp_id
+on
+.Ar device .
+The blob is read from
+.Fa blob_path .
+If
+.Ar rp_id
+has multiple credentials enrolled on
+.Ar device ,
+the credential ID must be specified using
+.Fl i Ar cred_id ,
+where
+.Ar cred_id
+is a base64-encoded blob.
+A PIN or equivalent user-verification gesture is required.
+.It Fl S Fl c Fl i Ar cred_id Fl k Ar user_id Fl n Ar name Fl p Ar display_name Ar device
+Sets the
+.Ar name
+and
+.Ar display_name
+attributes of the resident credential identified by
+.Ar cred_id
+and
+.Ar user_id ,
+where
+.Ar name
+and
+.Ar display_name
+are UTF-8 strings and
+.Ar cred_id
+and
+.Ar user_id
+are base64-encoded blobs.
+A PIN or equivalent user-verification gesture is required.
+.It Fl S Fl e Ar device
+Performs a new biometric enrollment on
+.Ar device .
+The user will be prompted for the PIN.
+.It Fl S Fl e Fl i Ar template_id Fl n Ar template_name Ar device
+Sets the friendly name of the biometric enrollment specified by
+.Ar template_id
+to
+.Ar template_name
+on
+.Ar device ,
+where
+.Ar template_id
+is base64-encoded and
+.Ar template_name
+is a UTF-8 string.
+The user will be prompted for the PIN.
+.It Fl S Fl f Ar device
+Forces a PIN change on
+.Ar device .
+The user will be prompted for the PIN.
+.It Fl S Fl l Ar pin_length Ar device
+Sets the minimum PIN length of
+.Ar device
+to
+.Ar pin_length .
+The user will be prompted for the PIN.
+.It Fl S Fl m Ar rp_id Ar device
+Sets the list of relying party IDs that are allowed to retrieve
+the minimum PIN length of
+.Ar device .
+Multiple IDs may be specified, separated by commas.
+The user will be prompted for the PIN.
+.It Fl S Fl u Ar device
+Enables the CTAP 2.1
+.Dq user verification always
+feature on
+.Ar device .
+.It Fl V
+Prints version information.
+.It Fl d
+Causes
+.Nm
+to emit debugging output on
+.Em stderr .
+.El
+.Pp
+If a
+.Em tty
+is available,
+.Nm
+will use it to prompt for PINs.
+Otherwise,
+.Em stdin
+is used.
+.Pp
+.Nm
+exits 0 on success and 1 on error.
+.Sh SEE ALSO
+.Xr fido2-assert 1 ,
+.Xr fido2-cred 1
+.Sh CAVEATS
+The actual user-flow to perform a reset is outside the scope of the
+FIDO2 specification, and may therefore vary depending on the
+authenticator.
+Yubico authenticators do not allow resets after 5 seconds from
+power-up, and expect a reset to be confirmed by the user through
+touch within 30 seconds.
+.Pp
+An authenticator's path may contain spaces.
+.Pp
+Resident credentials are called
+.Dq discoverable credentials
+in CTAP 2.1.
+.Pp
+Whether the CTAP 2.1
+.Dq user verification always
+feature is activated or deactivated after an authenticator reset
+is vendor-specific.
diff --git a/man/fido_assert_allow_cred.3 b/man/fido_assert_allow_cred.3
new file mode 100644
index 0000000..6520137
--- /dev/null
+++ b/man/fido_assert_allow_cred.3
@@ -0,0 +1,80 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: December 1 2022 $
+.Dt FIDO_ASSERT_ALLOW_CRED 3
+.Os
+.Sh NAME
+.Nm fido_assert_allow_cred ,
+.Nm fido_assert_empty_allow_list
+.Nd manage allow lists in a FIDO2 assertion
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_assert_allow_cred "fido_assert_t *assert" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_empty_allow_list "fido_assert_t *assert"
+.Sh DESCRIPTION
+The
+.Fn fido_assert_allow_cred
+function adds
+.Fa ptr
+to the list of credentials allowed in
+.Fa assert ,
+where
+.Fa ptr
+points to a credential ID of
+.Fa len
+bytes.
+A copy of
+.Fa ptr
+is made, and no references to the passed pointer are kept.
+If
+.Fn fido_assert_allow_cred
+fails, the existing list of allowed credentials is preserved.
+.Pp
+For the format of a FIDO2 credential ID, please refer to the
+Web Authentication (webauthn) standard.
+.Pp
+The
+.Fn fido_assert_empty_allow_list
+function empties the list of credentials allowed in
+.Fa assert .
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_assert_allow_cred
+and
+.Fn fido_assert_empty_allow_list
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_assert_new 3 ,
+.Xr fido_assert_set_authdata 3 ,
+.Xr fido_dev_get_assert 3
diff --git a/man/fido_assert_new.3 b/man/fido_assert_new.3
new file mode 100644
index 0000000..fdc74a9
--- /dev/null
+++ b/man/fido_assert_new.3
@@ -0,0 +1,293 @@
+.\" Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: June 19 2023 $
+.Dt FIDO_ASSERT_NEW 3
+.Os
+.Sh NAME
+.Nm fido_assert_new ,
+.Nm fido_assert_free ,
+.Nm fido_assert_count ,
+.Nm fido_assert_rp_id ,
+.Nm fido_assert_user_display_name ,
+.Nm fido_assert_user_icon ,
+.Nm fido_assert_user_name ,
+.Nm fido_assert_authdata_ptr ,
+.Nm fido_assert_authdata_raw_ptr ,
+.Nm fido_assert_blob_ptr ,
+.Nm fido_assert_clientdata_hash_ptr ,
+.Nm fido_assert_hmac_secret_ptr ,
+.Nm fido_assert_largeblob_key_ptr ,
+.Nm fido_assert_user_id_ptr ,
+.Nm fido_assert_sig_ptr ,
+.Nm fido_assert_id_ptr ,
+.Nm fido_assert_authdata_len ,
+.Nm fido_assert_authdata_raw_len ,
+.Nm fido_assert_blob_len ,
+.Nm fido_assert_clientdata_hash_len ,
+.Nm fido_assert_hmac_secret_len ,
+.Nm fido_assert_largeblob_key_len ,
+.Nm fido_assert_user_id_len ,
+.Nm fido_assert_sig_len ,
+.Nm fido_assert_id_len ,
+.Nm fido_assert_sigcount ,
+.Nm fido_assert_flags
+.Nd FIDO2 assertion API
+.Sh SYNOPSIS
+.In fido.h
+.Ft fido_assert_t *
+.Fn fido_assert_new "void"
+.Ft void
+.Fn fido_assert_free "fido_assert_t **assert_p"
+.Ft size_t
+.Fn fido_assert_count "const fido_assert_t *assert"
+.Ft const char *
+.Fn fido_assert_rp_id "const fido_assert_t *assert"
+.Ft const char *
+.Fn fido_assert_user_display_name "const fido_assert_t *assert" "size_t idx"
+.Ft const char *
+.Fn fido_assert_user_icon "const fido_assert_t *assert" "size_t idx"
+.Ft const char *
+.Fn fido_assert_user_name "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_authdata_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_authdata_raw_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_clientdata_hash_ptr "const fido_assert_t *assert"
+.Ft const unsigned char *
+.Fn fido_assert_blob_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_hmac_secret_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_largeblob_key_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_user_id_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_sig_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft const unsigned char *
+.Fn fido_assert_id_ptr "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_authdata_len "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_authdata_raw_len "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_clientdata_hash_len "const fido_assert_t *assert"
+.Ft size_t
+.Fn fido_assert_blob_len "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_hmac_secret_len "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_largeblob_key_len "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_user_id_len "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_sig_len "const fido_assert_t *assert" "size_t idx"
+.Ft size_t
+.Fn fido_assert_id_len "const fido_assert_t *assert" "size_t idx"
+.Ft uint32_t
+.Fn fido_assert_sigcount "const fido_assert_t *assert" "size_t idx"
+.Ft uint8_t
+.Fn fido_assert_flags "const fido_assert_t *assert" "size_t idx"
+.Sh DESCRIPTION
+A FIDO2 assertion is a collection of statements, each statement a
+map between a challenge, a credential, a signature, and ancillary
+attributes.
+In
+.Em libfido2 ,
+a FIDO2 assertion is abstracted by the
+.Vt fido_assert_t
+type.
+The functions described in this page allow a
+.Vt fido_assert_t
+type to be allocated, deallocated, and inspected.
+For other operations on
+.Vt fido_assert_t ,
+please refer to
+.Xr fido_assert_set_authdata 3 ,
+.Xr fido_assert_allow_cred 3 ,
+.Xr fido_assert_verify 3 ,
+and
+.Xr fido_dev_get_assert 3 .
+.Pp
+The
+.Fn fido_assert_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_assert_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_assert_free
+function releases the memory backing
+.Fa *assert_p ,
+where
+.Fa *assert_p
+must have been previously allocated by
+.Fn fido_assert_new .
+On return,
+.Fa *assert_p
+is set to NULL.
+Either
+.Fa assert_p
+or
+.Fa *assert_p
+may be NULL, in which case
+.Fn fido_assert_free
+is a NOP.
+.Pp
+The
+.Fn fido_assert_count
+function returns the number of statements in
+.Fa assert .
+.Pp
+The
+.Fn fido_assert_rp_id
+function returns a pointer to a NUL-terminated string holding the
+relying party ID of
+.Fa assert .
+.Pp
+The
+.Fn fido_assert_user_display_name ,
+.Fn fido_assert_user_icon ,
+and
+.Fn fido_assert_user_name ,
+functions return pointers to the user display name, icon, and
+name attributes of statement
+.Fa idx
+in
+.Fa assert .
+If not NULL, the values returned by these functions point to
+NUL-terminated UTF-8 strings.
+The user display name, icon, and name attributes will typically
+only be returned by the authenticator if user verification was
+performed by the authenticator and multiple resident/discoverable
+credentials were involved in the assertion.
+.Pp
+The
+.Fn fido_assert_authdata_ptr ,
+.Fn fido_assert_authdata_raw_ptr ,
+.Fn fido_assert_clientdata_hash_ptr ,
+.Fn fido_assert_id_ptr ,
+.Fn fido_assert_user_id_ptr ,
+.Fn fido_assert_sig_ptr ,
+.Fn fido_assert_sigcount ,
+and
+.Fn fido_assert_flags
+functions return pointers to the CBOR-encoded and raw authenticator data,
+client data hash, credential ID, user ID, signature, signature
+count, and authenticator data flags of statement
+.Fa idx
+in
+.Fa assert .
+.Pp
+The
+.Fn fido_assert_hmac_secret_ptr
+function returns a pointer to the hmac-secret attribute of statement
+.Fa idx
+in
+.Fa assert .
+The HMAC Secret Extension
+.Pq hmac-secret
+is a CTAP 2.0 extension.
+Note that the resulting hmac-secret varies according to whether
+user verification was performed by the authenticator.
+.Pp
+The
+.Fn fido_assert_blob_ptr
+and
+.Fn fido_assert_largeblob_key_ptr
+functions return pointers to the
+.Dq credBlob
+and
+.Dq largeBlobKey
+attributes of statement
+.Fa idx
+in
+.Fa assert .
+Credential Blob
+.Pq credBlob
+and
+Large Blob Key
+.Pq largeBlobKey
+are CTAP 2.1 extensions.
+.Pp
+The
+.Fn fido_assert_authdata_len ,
+.Fn fido_assert_authdata_raw_len ,
+.Fn fido_assert_clientdata_hash_len ,
+.Fn fido_assert_id_len ,
+.Fn fido_assert_user_id_len ,
+.Fn fido_assert_sig_len ,
+.Fn fido_assert_hmac_secret_len ,
+.Fn fido_assert_blob_len ,
+and
+.Fn fido_assert_largeblob_key_len
+functions return the length of a given attribute.
+.Pp
+Please note that the first statement in
+.Fa assert
+has an
+.Fa idx
+(index) value of 0.
+.Pp
+The authenticator data and signature parts of an assertion
+statement are typically passed to a FIDO2 server for verification.
+.Sh RETURN VALUES
+The authenticator data returned by
+.Fn fido_assert_authdata_ptr
+is a CBOR-encoded byte string, as obtained from the authenticator.
+.Pp
+The
+.Fn fido_assert_rp_id ,
+.Fn fido_assert_user_display_name ,
+.Fn fido_assert_user_icon ,
+.Fn fido_assert_user_name ,
+.Fn fido_assert_authdata_ptr ,
+.Fn fido_assert_clientdata_hash_ptr ,
+.Fn fido_assert_id_ptr ,
+.Fn fido_assert_user_id_ptr ,
+.Fn fido_assert_sig_ptr ,
+.Fn fido_assert_hmac_secret_ptr ,
+.Fn fido_assert_blob_ptr ,
+and
+.Fn fido_assert_largeblob_key_ptr
+functions may return NULL if the respective field in
+.Fa assert
+is not set.
+If not NULL, returned pointers are guaranteed to exist until any API
+function that takes
+.Fa assert
+without the
+.Em const
+qualifier is invoked.
+.Sh SEE ALSO
+.Xr fido_assert_allow_cred 3 ,
+.Xr fido_assert_set_authdata 3 ,
+.Xr fido_assert_verify 3 ,
+.Xr fido_dev_get_assert 3 ,
+.Xr fido_dev_largeblob_get 3
diff --git a/man/fido_assert_set_authdata.3 b/man/fido_assert_set_authdata.3
new file mode 100644
index 0000000..503e2bf
--- /dev/null
+++ b/man/fido_assert_set_authdata.3
@@ -0,0 +1,314 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: April 8 2023 $
+.Dt FIDO_ASSERT_SET_AUTHDATA 3
+.Os
+.Sh NAME
+.Nm fido_assert_set_authdata ,
+.Nm fido_assert_set_authdata_raw ,
+.Nm fido_assert_set_clientdata ,
+.Nm fido_assert_set_clientdata_hash ,
+.Nm fido_assert_set_count ,
+.Nm fido_assert_set_extensions ,
+.Nm fido_assert_set_hmac_salt ,
+.Nm fido_assert_set_hmac_secret ,
+.Nm fido_assert_set_up ,
+.Nm fido_assert_set_uv ,
+.Nm fido_assert_set_rp ,
+.Nm fido_assert_set_sig ,
+.Nm fido_assert_set_winhello_appid
+.Nd set parameters of a FIDO2 assertion
+.Sh SYNOPSIS
+.In fido.h
+.Bd -literal
+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;
+.Ed
+.Ft int
+.Fn fido_assert_set_authdata "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_set_authdata_raw "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_set_clientdata "fido_assert_t *assert" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_set_clientdata_hash "fido_assert_t *assert" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_set_count "fido_assert_t *assert" "size_t n"
+.Ft int
+.Fn fido_assert_set_extensions "fido_assert_t *assert" "int flags"
+.Ft int
+.Fn fido_assert_set_hmac_salt "fido_assert_t *assert" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_set_hmac_secret "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_set_up "fido_assert_t *assert" "fido_opt_t up"
+.Ft int
+.Fn fido_assert_set_uv "fido_assert_t *assert" "fido_opt_t uv"
+.Ft int
+.Fn fido_assert_set_rp "fido_assert_t *assert" "const char *id"
+.Ft int
+.Fn fido_assert_set_sig "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_assert_set_winhello_appid "fido_assert_t *assert" "const char *id"
+.Sh DESCRIPTION
+The
+.Nm
+set of functions define the various parameters of a FIDO2
+assertion, allowing a
+.Fa fido_assert_t
+type to be prepared for a subsequent call to
+.Xr fido_dev_get_assert 3
+or
+.Xr fido_assert_verify 3 .
+For the complete specification of a FIDO2 assertion and the format
+of its constituent parts, please refer to the Web Authentication
+(webauthn) standard.
+.Pp
+The
+.Fn fido_assert_set_count
+function sets the number of assertion statements in
+.Fa assert
+to
+.Fa n .
+.Pp
+The
+.Fn fido_assert_set_authdata
+and
+.Fn fido_assert_set_sig
+functions set the authenticator data and signature parts of the
+statement with index
+.Fa idx
+of
+.Fa assert
+to
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+A copy of
+.Fa ptr
+is made, and no references to the passed pointer are kept.
+Please note that the first assertion statement of
+.Fa assert
+has an
+.Fa idx
+of
+.Em 0 .
+The authenticator data passed to
+.Fn fido_assert_set_authdata
+must be a CBOR-encoded byte string, as obtained from
+.Fn fido_assert_authdata_ptr .
+Alternatively, a raw binary blob may be passed to
+.Fn fido_assert_set_authdata_raw .
+.Pp
+The
+.Fn fido_assert_set_clientdata_hash
+function sets the client data hash of
+.Fa assert
+to
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+A copy of
+.Fa ptr
+is made, and no references to the passed pointer are kept.
+.Pp
+The
+.Fn fido_assert_set_clientdata
+function allows an application to set the client data hash of
+.Fa assert
+by specifying the assertion's unhashed client data.
+This is required by Windows Hello, which calculates the client data
+hash internally.
+For compatibility with Windows Hello, applications should use
+.Fn fido_assert_set_clientdata
+instead of
+.Fn fido_assert_set_clientdata_hash .
+.Pp
+The
+.Fn fido_assert_set_rp
+function sets the relying party
+.Fa id
+of
+.Fa assert ,
+where
+.Fa id
+is a NUL-terminated UTF-8 string.
+The content of
+.Fa id
+is copied, and no references to the passed pointer are kept.
+.Pp
+The
+.Fn fido_assert_set_extensions
+function sets the extensions of
+.Fa assert
+to the bitmask
+.Fa flags .
+At the moment, only the
+.Dv FIDO_EXT_CRED_BLOB ,
+.Dv FIDO_EXT_HMAC_SECRET ,
+and
+.Dv FIDO_EXT_LARGEBLOB_KEY
+extensions are supported.
+If
+.Fa flags
+is zero, the extensions of
+.Fa assert
+are cleared.
+.Pp
+The
+.Fn fido_assert_set_hmac_salt
+and
+.Fn fido_assert_set_hmac_secret
+functions set the hmac-salt and hmac-secret parts of
+.Fa assert
+to
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+A copy of
+.Fa ptr
+is made, and no references to the passed pointer are kept.
+The HMAC Secret
+.Pq hmac-secret
+Extension is a CTAP 2.0 extension.
+Note that the resulting hmac-secret varies according to whether
+user verification was performed by the authenticator.
+The
+.Fn fido_assert_set_hmac_secret
+function is normally only useful when writing tests.
+.Pp
+The
+.Fn fido_assert_set_up
+and
+.Fn fido_assert_set_uv
+functions set the
+.Fa up
+(user presence) and
+.Fa uv
+(user verification)
+attributes of
+.Fa assert .
+Both are
+.Dv FIDO_OPT_OMIT
+by default, allowing the authenticator to use its default settings.
+.Pp
+The
+.Fn fido_assert_set_winhello_appid
+function sets the U2F application
+.Fa id
+.Pq Dq U2F AppID
+of
+.Fa assert ,
+where
+.Fa id
+is a NUL-terminated UTF-8 string.
+The content of
+.Fa id
+is copied, and no references to the passed pointer are kept.
+The
+.Fn fido_assert_set_winhello_appid
+function is a no-op unless
+.Fa assert
+is passed to
+.Xr fido_dev_get_assert 3
+with a device
+.Fa dev
+on which
+.Xr fido_dev_is_winhello 3
+holds true.
+In this case,
+.Em libfido2
+will instruct Windows Hello to try the assertion twice,
+first with the
+.Fa id
+passed to
+.Fn fido_assert_set_rp ,
+and a second time with the
+.Fa id
+passed to
+.Fn fido_assert_set_winhello_appid .
+If the second assertion succeeds,
+.Xr fido_assert_rp_id 3
+will point to the U2F AppID once
+.Xr fido_dev_get_assert 3
+completes.
+This mechanism exists in Windows Hello to ensure U2F backwards
+compatibility without the application inadvertently prompting the
+user twice.
+Note that
+.Fn fido_assert_set_winhello_appid
+is not needed on platforms offering CTAP primitives, since the
+authenticator can be silently probed for the existence of U2F
+credentials.
+.Pp
+Use of the
+.Nm
+set of functions may happen in two distinct situations:
+when asking a FIDO2 device to produce a series of assertion
+statements, prior to
+.Xr fido_dev_get_assert 3
+(i.e, in the context of a FIDO2 client), or when verifying assertion
+statements using
+.Xr fido_assert_verify 3
+(i.e, in the context of a FIDO2 server).
+.Pp
+For a complete description of the generation of a FIDO2 assertion
+and its verification, please refer to the FIDO2 specification.
+An example of how to use the
+.Nm
+set of functions can be found in the
+.Pa examples/assert.c
+file shipped with
+.Em libfido2 .
+.Sh RETURN VALUES
+The
+.Nm
+functions return
+.Dv FIDO_OK
+on success.
+The error codes returned by the
+.Nm
+set of functions are defined in
+.In fido/err.h .
+.Sh SEE ALSO
+.Xr fido_assert_allow_cred 3 ,
+.Xr fido_assert_verify 3 ,
+.Xr fido_dev_get_assert 3 ,
+.Xr fido_dev_is_winhello 3
diff --git a/man/fido_assert_verify.3 b/man/fido_assert_verify.3
new file mode 100644
index 0000000..1b79448
--- /dev/null
+++ b/man/fido_assert_verify.3
@@ -0,0 +1,104 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 15 2022 $
+.Dt FIDO_ASSERT_VERIFY 3
+.Os
+.Sh NAME
+.Nm fido_assert_verify
+.Nd verifies the signature of a FIDO2 assertion statement
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_assert_verify "const fido_assert_t *assert" "size_t idx" "int cose_alg" "const void *pk"
+.Sh DESCRIPTION
+The
+.Fn fido_assert_verify
+function verifies whether the signature contained in statement index
+.Fa idx
+of
+.Fa assert
+matches the parameters of the assertion.
+Before using
+.Fn fido_assert_verify
+in a sensitive context, the reader is strongly encouraged to make
+herself familiar with the FIDO2 assertion statement process
+as defined in the Web Authentication (webauthn) standard.
+.Pp
+A brief description follows:
+.Pp
+The
+.Fn fido_assert_verify
+function verifies whether the client data hash, relying party ID,
+user presence and user verification attributes of
+.Fa assert
+have been attested by the holder of the private counterpart of
+the public key
+.Fa pk
+of COSE type
+.Fa cose_alg ,
+where
+.Fa cose_alg
+is
+.Dv COSE_ES256 ,
+.Dv COSE_ES384 ,
+.Dv COSE_RS256 ,
+or
+.Dv COSE_EDDSA ,
+and
+.Fa pk
+points to a
+.Vt es256_pk_t ,
+.Vt es384_pk_t ,
+.Vt rs256_pk_t ,
+or
+.Vt eddsa_pk_t
+type accordingly.
+.Pp
+Please note that the first statement in
+.Fa assert
+has an
+.Fa idx
+of 0.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_assert_verify
+are defined in
+.In fido/err.h .
+If
+statement
+.Fa idx
+of
+.Fa assert
+passes verification with
+.Fa pk ,
+then
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_assert_new 3 ,
+.Xr fido_assert_set_authdata 3
diff --git a/man/fido_bio_dev_get_info.3 b/man/fido_bio_dev_get_info.3
new file mode 100644
index 0000000..b8fc104
--- /dev/null
+++ b/man/fido_bio_dev_get_info.3
@@ -0,0 +1,145 @@
+.\" Copyright (c) 2019 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: September 13 2019 $
+.Dt FIDO_BIO_DEV_GET_INFO 3
+.Os
+.Sh NAME
+.Nm fido_bio_dev_get_info ,
+.Nm fido_bio_dev_enroll_begin ,
+.Nm fido_bio_dev_enroll_continue ,
+.Nm fido_bio_dev_enroll_cancel ,
+.Nm fido_bio_dev_enroll_remove ,
+.Nm fido_bio_dev_get_template_array ,
+.Nm fido_bio_dev_set_template_name
+.Nd FIDO2 biometric authenticator API
+.Sh SYNOPSIS
+.In fido.h
+.In fido/bio.h
+.Ft int
+.Fn fido_bio_dev_get_info "fido_dev_t *dev" "fido_bio_info_t *info"
+.Ft int
+.Fn fido_bio_dev_enroll_begin "fido_dev_t *dev" "fido_bio_template_t *template" "fido_bio_enroll_t *enroll" "uint32_t timeout_ms" "const char *pin"
+.Ft int
+.Fn fido_bio_dev_enroll_continue "fido_dev_t *dev" "const fido_bio_template_t *template" "fido_bio_enroll_t *enroll" "uint32_t timeout_ms"
+.Ft int
+.Fn fido_bio_dev_enroll_cancel "fido_dev_t *dev"
+.Ft int
+.Fn fido_bio_dev_enroll_remove "fido_dev_t *dev" "const fido_bio_template_t *template" "const char *pin"
+.Ft int
+.Fn fido_bio_dev_get_template_array "fido_dev_t *dev" "fido_bio_template_array_t *template_array" "const char *pin"
+.Ft int
+.Fn fido_bio_dev_set_template_name "fido_dev_t *dev" "const fido_bio_template_t *template" "const char *pin"
+.Sh DESCRIPTION
+The functions described in this page allow biometric
+templates on a FIDO2 authenticator to be listed, created,
+removed, and customised.
+Please note that not all FIDO2 authenticators support biometric
+enrollment.
+For a description of the types involved, please refer to
+.Xr fido_bio_info_new 3 ,
+.Xr fido_bio_enroll_new 3 ,
+and
+.Xr fido_bio_template 3 .
+.Pp
+The
+.Fn fido_bio_dev_get_info
+function populates
+.Fa info
+with sensor information from
+.Fa dev .
+.Pp
+The
+.Fn fido_bio_dev_enroll_begin
+function initiates a biometric enrollment on
+.Fa dev ,
+instructing the authenticator to wait
+.Fa timeout_ms
+milliseconds.
+On success,
+.Fa template
+and
+.Fa enroll
+will be populated with the newly created template's
+information and enrollment status, respectively.
+.Pp
+The
+.Fn fido_bio_dev_enroll_continue
+function continues an ongoing enrollment on
+.Fa dev ,
+instructing the authenticator to wait
+.Fa timeout_ms
+milliseconds.
+On success,
+.Fa enroll
+will be updated to reflect the status of the biometric
+enrollment.
+.Pp
+The
+.Fn fido_bio_dev_enroll_cancel
+function cancels an ongoing enrollment on
+.Fa dev .
+.Pp
+The
+.Fn fido_bio_dev_enroll_remove
+function removes
+.Fa template
+from
+.Fa dev .
+.Pp
+The
+.Fn fido_bio_dev_get_template_array
+function populates
+.Fa template_array
+with the templates currently enrolled on
+.Fa dev .
+.Pp
+The
+.Fn fido_bio_dev_set_template_name
+function sets the friendly name of
+.Fa template
+on
+.Fa dev .
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_bio_dev_get_info ,
+.Fn fido_bio_dev_enroll_begin ,
+.Fn fido_bio_dev_enroll_continue ,
+.Fn fido_bio_dev_enroll_cancel ,
+.Fn fido_bio_dev_enroll_remove ,
+.Fn fido_bio_dev_get_template_array ,
+and
+.Fn fido_bio_dev_set_template_name
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_bio_enroll_new 3 ,
+.Xr fido_bio_info_new 3 ,
+.Xr fido_bio_template 3
diff --git a/man/fido_bio_enroll_new.3 b/man/fido_bio_enroll_new.3
new file mode 100644
index 0000000..536ba9a
--- /dev/null
+++ b/man/fido_bio_enroll_new.3
@@ -0,0 +1,118 @@
+.\" Copyright (c) 2019 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: September 13 2019 $
+.Dt FIDO_BIO_ENROLL_NEW 3
+.Os
+.Sh NAME
+.Nm fido_bio_enroll_new ,
+.Nm fido_bio_enroll_free ,
+.Nm fido_bio_enroll_last_status ,
+.Nm fido_bio_enroll_remaining_samples
+.Nd FIDO2 biometric enrollment API
+.Sh SYNOPSIS
+.In fido.h
+.In fido/bio.h
+.Bd -literal
+#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
+.Ed
+.Ft fido_bio_enroll_t *
+.Fn fido_bio_enroll_new "void"
+.Ft void
+.Fn fido_bio_enroll_free "fido_bio_enroll_t **enroll_p"
+.Ft uint8_t
+.Fn fido_bio_enroll_last_status "const fido_bio_enroll_t *enroll"
+.Ft uint8_t
+.Fn fido_bio_enroll_remaining_samples "const fido_bio_enroll_t *enroll"
+.Sh DESCRIPTION
+Ongoing FIDO2 biometric enrollments are abstracted in
+.Em libfido2
+by the
+.Vt fido_bio_enroll_t
+type.
+.Pp
+The functions described in this page allow a
+.Vt fido_bio_enroll_t
+type to be allocated, deallocated, and inspected.
+For device operations on
+.Vt fido_bio_enroll_t ,
+please refer to
+.Xr fido_bio_dev_get_info 3 .
+.Pp
+The
+.Fn fido_bio_enroll_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_bio_enroll_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_bio_enroll_free
+function releases the memory backing
+.Fa *enroll_p ,
+where
+.Fa *enroll_p
+must have been previously allocated by
+.Fn fido_bio_enroll_new .
+On return,
+.Fa *enroll_p
+is set to NULL.
+Either
+.Fa enroll_p
+or
+.Fa *enroll_p
+may be NULL, in which case
+.Fn fido_bio_enroll_free
+is a NOP.
+.Pp
+The
+.Fn fido_bio_enroll_last_status
+function returns the enrollment status of
+.Fa enroll .
+.Pp
+The
+.Fn fido_bio_enroll_remaining_samples
+function returns the number of samples left for
+.Fa enroll
+to complete.
+.Sh SEE ALSO
+.Xr fido_bio_dev_get_info 3 ,
+.Xr fido_bio_template 3
diff --git a/man/fido_bio_info_new.3 b/man/fido_bio_info_new.3
new file mode 100644
index 0000000..4134306
--- /dev/null
+++ b/man/fido_bio_info_new.3
@@ -0,0 +1,104 @@
+.\" Copyright (c) 2019 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: September 13 2019 $
+.Dt FIDO_BIO_INFO_NEW 3
+.Os
+.Sh NAME
+.Nm fido_bio_info_new ,
+.Nm fido_bio_info_free ,
+.Nm fido_bio_info_type ,
+.Nm fido_bio_info_max_samples
+.Nd FIDO2 biometric sensor information API
+.Sh SYNOPSIS
+.In fido.h
+.In fido/bio.h
+.Ft fido_bio_info_t *
+.Fn fido_bio_info_new "void"
+.Ft void
+.Fn fido_bio_info_free "fido_bio_info_t **info_p"
+.Ft uint8_t
+.Fn fido_bio_info_type "const fido_bio_info_t *info"
+.Ft uint8_t
+.Fn fido_bio_info_max_samples "const fido_bio_info_t *info"
+.Sh DESCRIPTION
+Biometric sensor metadata is abstracted in
+.Em libfido2
+by the
+.Vt fido_bio_info_t
+type.
+.Pp
+The functions described in this page allow a
+.Vt fido_bio_info_t
+type to be allocated, deallocated, and inspected.
+For device operations on
+.Vt fido_bio_info_t ,
+please refer to
+.Xr fido_bio_dev_get_info 3 .
+.Pp
+The
+.Fn fido_bio_info_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_bio_info_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_bio_info_free
+function releases the memory backing
+.Fa *info_p ,
+where
+.Fa *info_p
+must have been previously allocated by
+.Fn fido_bio_info_new .
+On return,
+.Fa *info_p
+is set to NULL.
+Either
+.Fa info_p
+or
+.Fa *info_p
+may be NULL, in which case
+.Fn fido_bio_info_free
+is a NOP.
+.Pp
+The
+.Fn fido_bio_info_type
+function returns the fingerprint sensor type, which is
+.Dv 1
+for touch sensors, and
+.Dv 2
+for swipe sensors.
+.Pp
+The
+.Fn fido_bio_info_max_samples
+function returns the maximum number of successful samples
+required for enrollment.
+.Sh SEE ALSO
+.Xr fido_bio_dev_get_info 3 ,
+.Xr fido_bio_enroll_new 3 ,
+.Xr fido_bio_template 3
diff --git a/man/fido_bio_template.3 b/man/fido_bio_template.3
new file mode 100644
index 0000000..a8ff8bc
--- /dev/null
+++ b/man/fido_bio_template.3
@@ -0,0 +1,202 @@
+.\" Copyright (c) 2019 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: September 13 2019 $
+.Dt FIDO_BIO_TEMPLATE 3
+.Os
+.Sh NAME
+.Nm fido_bio_template ,
+.Nm fido_bio_template_array_count ,
+.Nm fido_bio_template_array_free ,
+.Nm fido_bio_template_array_new ,
+.Nm fido_bio_template_free ,
+.Nm fido_bio_template_id_len ,
+.Nm fido_bio_template_id_ptr ,
+.Nm fido_bio_template_name ,
+.Nm fido_bio_template_new ,
+.Nm fido_bio_template_set_id ,
+.Nm fido_bio_template_set_name
+.Nd FIDO2 biometric template API
+.Sh SYNOPSIS
+.In fido.h
+.In fido/bio.h
+.Ft fido_bio_template_t *
+.Fn fido_bio_template_new "void"
+.Ft void
+.Fn fido_bio_template_free "fido_bio_template_t **template_p"
+.Ft const char *
+.Fn fido_bio_template_name "const fido_bio_template_t *template"
+.Ft const unsigned char *
+.Fn fido_bio_template_id_ptr "const fido_bio_template_t *template"
+.Ft size_t
+.Fn fido_bio_template_id_len "const fido_bio_template_t *template"
+.Ft int
+.Fn fido_bio_template_set_id "fido_bio_template_t *template" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_bio_template_set_name "fido_bio_template_t *template" "const char *name"
+.Ft fido_bio_template_array_t *
+.Fn fido_bio_template_array_new "void"
+.Ft void
+.Fn fido_bio_template_array_free "fido_bio_template_array_t **array_p"
+.Ft size_t
+.Fn fido_bio_template_array_count "const fido_bio_template_array_t *array"
+.Ft const fido_bio_template_t *
+.Fn fido_bio_template "const fido_bio_template_array_t *array" "size_t idx"
+.Sh DESCRIPTION
+Existing FIDO2 biometric enrollments are abstracted in
+.Em libfido2
+by the
+.Vt fido_bio_template_t
+and
+.Vt fido_bio_template_array_t
+types.
+.Pp
+The functions described in this page allow a
+.Vt fido_bio_template_t
+type to be allocated, deallocated, changed, and inspected,
+and a
+.Vt fido_bio_template_array_t
+type to be allocated, deallocated, and inspected.
+For device operations on
+.Vt fido_bio_template_t
+and
+.Vt fido_bio_template_array_t ,
+please refer to
+.Xr fido_bio_dev_get_info 3 .
+.Pp
+The
+.Fn fido_bio_template_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_bio_template_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_bio_template_free
+function releases the memory backing
+.Fa *template_p ,
+where
+.Fa *template_p
+must have been previously allocated by
+.Fn fido_bio_template_new .
+On return,
+.Fa *template_p
+is set to NULL.
+Either
+.Fa template_p
+or
+.Fa *template_p
+may be NULL, in which case
+.Fn fido_bio_template_free
+is a NOP.
+.Pp
+The
+.Fn fido_bio_template_name
+function returns a pointer to a NUL-terminated string containing
+the friendly name of
+.Fa template ,
+or NULL if
+.Fa template
+does not have a friendly name set.
+.Pp
+The
+.Fn fido_bio_template_id_ptr
+function returns a pointer to the template id of
+.Fa template ,
+or NULL if
+.Fa template
+does not have an id.
+The corresponding length can be obtained by
+.Fn fido_bio_template_id_len .
+.Pp
+The
+.Fn fido_bio_template_set_name
+function sets the friendly name of
+.Fa template
+to
+.Fa name .
+If
+.Fa name
+is NULL, the friendly name of
+.Fa template
+is unset.
+.Pp
+The
+.Fn fido_bio_template_array_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_bio_template_array_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_bio_template_array_free
+function releases the memory backing
+.Fa *array_p ,
+where
+.Fa *array_p
+must have been previously allocated by
+.Fn fido_bio_template_array_new .
+On return,
+.Fa *array_p
+is set to NULL.
+Either
+.Fa array_p
+or
+.Fa *array_p
+may be NULL, in which case
+.Fn fido_bio_template_array_free
+is a NOP.
+.Pp
+The
+.Fn fido_bio_template_array_count
+function returns the number of templates in
+.Fa array .
+.Pp
+The
+.Fn fido_bio_template
+function returns a pointer to the template at index
+.Fa idx
+in
+.Fa array .
+Please note that the first template in
+.Fa array
+has an
+.Fa idx
+(index) value of 0.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_bio_template_set_id
+and
+.Fn fido_bio_template_set_name
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_bio_dev_get_info 3 ,
+.Xr fido_bio_enroll_new 3
diff --git a/man/fido_cbor_info_new.3 b/man/fido_cbor_info_new.3
new file mode 100644
index 0000000..a8168c0
--- /dev/null
+++ b/man/fido_cbor_info_new.3
@@ -0,0 +1,389 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: April 22 2022 $
+.Dt FIDO_CBOR_INFO_NEW 3
+.Os
+.Sh NAME
+.Nm fido_cbor_info_new ,
+.Nm fido_cbor_info_free ,
+.Nm fido_dev_get_cbor_info ,
+.Nm fido_cbor_info_aaguid_ptr ,
+.Nm fido_cbor_info_extensions_ptr ,
+.Nm fido_cbor_info_protocols_ptr ,
+.Nm fido_cbor_info_transports_ptr ,
+.Nm fido_cbor_info_versions_ptr ,
+.Nm fido_cbor_info_options_name_ptr ,
+.Nm fido_cbor_info_options_value_ptr ,
+.Nm fido_cbor_info_algorithm_type ,
+.Nm fido_cbor_info_algorithm_cose ,
+.Nm fido_cbor_info_algorithm_count ,
+.Nm fido_cbor_info_certs_name_ptr ,
+.Nm fido_cbor_info_certs_value_ptr ,
+.Nm fido_cbor_info_certs_len ,
+.Nm fido_cbor_info_aaguid_len ,
+.Nm fido_cbor_info_extensions_len ,
+.Nm fido_cbor_info_protocols_len ,
+.Nm fido_cbor_info_transports_len ,
+.Nm fido_cbor_info_versions_len ,
+.Nm fido_cbor_info_options_len ,
+.Nm fido_cbor_info_maxmsgsiz ,
+.Nm fido_cbor_info_maxcredbloblen ,
+.Nm fido_cbor_info_maxcredcntlst ,
+.Nm fido_cbor_info_maxcredidlen ,
+.Nm fido_cbor_info_maxlargeblob ,
+.Nm fido_cbor_info_maxrpid_minpinlen ,
+.Nm fido_cbor_info_minpinlen ,
+.Nm fido_cbor_info_fwversion ,
+.Nm fido_cbor_info_uv_attempts ,
+.Nm fido_cbor_info_uv_modality ,
+.Nm fido_cbor_info_rk_remaining ,
+.Nm fido_cbor_info_new_pin_required
+.Nd FIDO2 CBOR Info API
+.Sh SYNOPSIS
+.In fido.h
+.Ft fido_cbor_info_t *
+.Fn fido_cbor_info_new "void"
+.Ft void
+.Fn fido_cbor_info_free "fido_cbor_info_t **ci_p"
+.Ft int
+.Fn fido_dev_get_cbor_info "fido_dev_t *dev" "fido_cbor_info_t *ci"
+.Ft const unsigned char *
+.Fn fido_cbor_info_aaguid_ptr "const fido_cbor_info_t *ci"
+.Ft char **
+.Fn fido_cbor_info_extensions_ptr "const fido_cbor_info_t *ci"
+.Ft const uint8_t *
+.Fn fido_cbor_info_protocols_ptr "const fido_cbor_info_t *ci"
+.Ft char **
+.Fn fido_cbor_info_transports_ptr "const fido_cbor_info_t *ci"
+.Ft char **
+.Fn fido_cbor_info_versions_ptr "const fido_cbor_info_t *ci"
+.Ft char **
+.Fn fido_cbor_info_options_name_ptr "const fido_cbor_info_t *ci"
+.Ft const bool *
+.Fn fido_cbor_info_options_value_ptr "const fido_cbor_info_t *ci"
+.Ft const char *
+.Fn fido_cbor_info_algorithm_type "const fido_cbor_info_t *ci" "size_t idx"
+.Ft int
+.Fn fido_cbor_info_algorithm_cose "const fido_cbor_info_t *ci" "size_t idx"
+.Ft size_t
+.Fn fido_cbor_info_algorithm_count "const fido_cbor_info_t *ci"
+.Ft char **
+.Fn fido_cbor_info_certs_name_ptr "const fido_cbor_info_t *ci"
+.Ft const uint64_t *
+.Fn fido_cbor_info_certs_value_ptr "const fido_cbor_info_t *ci"
+.Ft size_t
+.Fn fido_cbor_info_certs_len "const fido_cbor_info_t *ci"
+.Ft size_t
+.Fn fido_cbor_info_aaguid_len "const fido_cbor_info_t *ci"
+.Ft size_t
+.Fn fido_cbor_info_extensions_len "const fido_cbor_info_t *ci"
+.Ft size_t
+.Fn fido_cbor_info_protocols_len "const fido_cbor_info_t *ci"
+.Ft size_t
+.Fn fido_cbor_info_transports_len "const fido_cbor_info_t *ci"
+.Ft size_t
+.Fn fido_cbor_info_versions_len "const fido_cbor_info_t *ci"
+.Ft size_t
+.Fn fido_cbor_info_options_len "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_maxmsgsiz "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_maxcredbloblen "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_maxcredcntlst "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_maxcredidlen "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_maxlargeblob "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_maxrpid_minpinlen "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_minpinlen "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_fwversion "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_uv_attempts "const fido_cbor_info_t *ci"
+.Ft uint64_t
+.Fn fido_cbor_info_uv_modality "const fido_cbor_info_t *ci"
+.Ft int64_t
+.Fn fido_cbor_info_rk_remaining "const fido_cbor_info_t *ci"
+.Ft bool
+.Fn fido_cbor_info_new_pin_required "const fido_cbor_info_t *ci"
+.Sh DESCRIPTION
+The
+.Fn fido_cbor_info_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_cbor_info_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_cbor_info_free
+function releases the memory backing
+.Fa *ci_p ,
+where
+.Fa *ci_p
+must have been previously allocated by
+.Fn fido_cbor_info_new .
+On return,
+.Fa *ci_p
+is set to NULL.
+Either
+.Fa ci_p
+or
+.Fa *ci_p
+may be NULL, in which case
+.Fn fido_cbor_info_free
+is a NOP.
+.Pp
+The
+.Fn fido_dev_get_cbor_info
+function transmits a
+.Dv CTAP_CBOR_GETINFO
+command to
+.Fa dev
+and fills
+.Fa ci
+with attributes retrieved from the command's response.
+The
+.Fn fido_dev_get_cbor_info
+function may block.
+.Pp
+The
+.Fn fido_cbor_info_aaguid_ptr ,
+.Fn fido_cbor_info_extensions_ptr ,
+.Fn fido_cbor_info_protocols_ptr ,
+.Fn fido_cbor_info_transports_ptr ,
+and
+.Fn fido_cbor_info_versions_ptr
+functions return pointers to the authenticator attestation GUID,
+supported extensions, PIN protocol, transports, and CTAP version
+strings of
+.Fa ci .
+The corresponding length of a given attribute can be
+obtained by
+.Fn fido_cbor_info_aaguid_len ,
+.Fn fido_cbor_info_extensions_len ,
+.Fn fido_cbor_info_protocols_len ,
+.Fn fido_cbor_info_transports_len ,
+or
+.Fn fido_cbor_info_versions_len .
+.Pp
+The
+.Fn fido_cbor_info_options_name_ptr
+and
+.Fn fido_cbor_info_options_value_ptr
+functions return pointers to the array of option names and their
+respective values
+in
+.Fa ci .
+The length of the options array is returned by
+.Fn fido_cbor_info_options_len .
+.Pp
+The
+.Fn fido_cbor_info_algorithm_count
+function returns the number of supported algorithms in
+.Fa ci .
+The
+.Fn fido_cbor_info_algorithm_cose
+function returns the COSE identifier of algorithm
+.Fa idx
+in
+.Fa ci ,
+or 0 if the COSE identifier is unknown or unset.
+The
+.Fn fido_cbor_info_algorithm_type
+function returns the type of algorithm
+.Fa idx
+in
+.Fa ci ,
+or NULL if the type is unset.
+Please note that the first algorithm in
+.Fa ci
+has an
+.Fa idx
+(index) value of 0.
+.Pp
+The
+.Fn fido_cbor_info_certs_name_ptr
+and
+.Fn fido_cbor_info_certs_value_ptr
+functions return pointers to the array of certification names and their
+respective values
+in
+.Fa ci .
+The length of the certifications array is returned by
+.Fn fido_cbor_info_certs_len .
+.Pp
+The
+.Fn fido_cbor_info_maxmsgsiz
+function returns the maximum message size attribute of
+.Fa ci .
+.Pp
+The
+.Fn fido_cbor_info_maxcredbloblen
+function returns the maximum
+.Dq credBlob
+length in bytes supported by the authenticator as reported in
+.Fa ci .
+.Pp
+The
+.Fn fido_cbor_info_maxcredcntlst
+function returns the maximum supported number of credentials in
+a single credential ID list as reported in
+.Fa ci .
+.Pp
+The
+.Fn fido_cbor_info_maxcredidlen
+function returns the maximum supported length of a credential ID
+as reported in
+.Fa ci .
+.Pp
+The
+.Fn fido_cbor_info_maxrpid_minpinlen
+function returns the maximum number of RP IDs that may be passed to
+.Xr fido_dev_set_pin_minlen_rpid 3 ,
+as reported in
+.Fa ci .
+The minimum PIN length attribute is a CTAP 2.1 addition.
+If the attribute is not advertised by the authenticator, the
+.Fn fido_cbor_info_maxrpid_minpinlen
+function returns zero.
+.Pp
+The
+.Fn fido_cbor_info_maxlargeblob
+function returns the maximum length in bytes of an authenticator's
+serialized largeBlob array as reported in
+.Fa ci .
+.Pp
+The
+.Fn fido_cbor_info_minpinlen
+function returns the minimum PIN length enforced by the
+authenticator as reported in
+.Fa ci .
+The minimum PIN length attribute is a CTAP 2.1 addition.
+If the attribute is not advertised by the authenticator, the
+.Fn fido_cbor_info_minpinlen
+function returns zero.
+.Pp
+The
+.Fn fido_cbor_info_fwversion
+function returns the firmware version attribute of
+.Fa ci .
+.Pp
+The
+.Fn fido_cbor_info_uv_attempts
+function returns the number of UV attempts that the platform may
+attempt before falling back to PIN authentication.
+If 1, then all
+.Xr fido_dev_get_uv_retry_count 3
+retries are handled internally by the authenticator and the
+platform may only attempt non-PIN UV once.
+The UV attempts attribute is a CTAP 2.1 addition.
+If the attribute is not advertised by the authenticator,
+the
+.Fn fido_cbor_info_uv_attempts
+function returns zero.
+.Pp
+The
+.Fn fido_cbor_info_uv_modality
+function returns a bitmask representing different UV modes
+supported by the authenticator, as defined in the FIDO Registry of
+Predefined Values and reported in
+.Fa ci .
+See the
+.Em FIDO_UV_MODE_*
+definitions in
+.In fido/param.h
+for the set of values defined by libfido2 and a brief description
+of each.
+The UV modality attribute is a CTAP 2.1 addition.
+If the attribute is not advertised by the authenticator, the
+.Fn fido_cbor_info_uv_modality
+function returns zero.
+.Pp
+The
+.Fn fido_cbor_info_rk_remaining
+function returns the estimated number of additional
+resident/discoverable credentials that can be stored on the
+authenticator as reported in
+.Fa ci .
+The estimated number of remaining resident credentials is a
+CTAP 2.1 addition.
+If the attribute is not advertised by the authenticator, the
+.Fn fido_cbor_info_rk_remaining
+function returns -1.
+.Pp
+The
+.Fn fido_cbor_info_new_pin_required
+function returns whether a new PIN is required by the authenticator
+as reported in
+.Fa ci .
+If
+.Fn fido_cbor_info_new_pin_required
+returns true, operations requiring PIN authentication will fail
+until a new PIN is set on the authenticator.
+The
+.Xr fido_dev_set_pin 3
+function can be used to set a new PIN.
+.Pp
+A complete example of how to use these functions can be found in the
+.Pa example/info.c
+file shipped with
+.Em libfido2 .
+.Sh RETURN VALUES
+The
+.Fn fido_cbor_info_aaguid_ptr ,
+.Fn fido_cbor_info_extensions_ptr ,
+.Fn fido_cbor_info_protocols_ptr ,
+.Fn fido_cbor_info_transports_ptr ,
+.Fn fido_cbor_info_versions_ptr ,
+.Fn fido_cbor_info_options_name_ptr ,
+and
+.Fn fido_cbor_info_options_value_ptr
+functions return NULL if the respective field in
+.Fa ci
+is absent.
+If not NULL, returned pointers are guaranteed to exist until any
+API function that takes
+.Fa ci
+without the
+.Em const
+qualifier is invoked.
+.Sh SEE ALSO
+.Xr fido_dev_get_uv_retry_count 3 ,
+.Xr fido_dev_open 3 ,
+.Xr fido_dev_set_pin 3 ,
+.Xr fido_dev_set_pin_minlen_rpid 3
+.Rs
+.%D 2021-05-25
+.%O Review Draft, Version 2.2
+.%Q FIDO Alliance
+.%R FIDO Registry of Predefined Values
+.%U https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-rd-20210525.html
+.Re
diff --git a/man/fido_cred_exclude.3 b/man/fido_cred_exclude.3
new file mode 100644
index 0000000..d5e840d
--- /dev/null
+++ b/man/fido_cred_exclude.3
@@ -0,0 +1,93 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: December 2 2022 $
+.Dt FIDO_CRED_EXCLUDE 3
+.Os
+.Sh NAME
+.Nm fido_cred_exclude ,
+.Nm fido_cred_empty_exclude_list
+.Nd manage exclude lists in a FIDO2 credential
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_cred_exclude "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_empty_exclude_list "fido_cred_t *cred"
+.Sh DESCRIPTION
+The
+.Fn fido_cred_exclude
+function adds
+.Fa ptr
+to the list of credentials excluded by
+.Fa cred ,
+where
+.Fa ptr
+points to a credential ID of
+.Fa len
+bytes.
+A copy of
+.Fa ptr
+is made, and no references to the passed pointer are kept.
+If
+.Fn fido_cred_exclude
+fails, the existing list of excluded credentials is preserved.
+.Pp
+If
+.Nm
+returns success and
+.Fa cred
+is later passed to
+.Xr fido_dev_make_cred 3
+on a device that contains the credential
+denoted by
+.Fa ptr ,
+then
+.Xr fido_dev_make_cred 3
+will fail.
+.Pp
+For the format of a FIDO2 credential ID, please refer to the
+Web Authentication (webauthn) standard.
+.Pp
+The
+.Fn fido_cred_empty_exclude_list
+function empties the list of credentials excluded by
+.Fa cred .
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_cred_exclude
+and
+.Fn fido_cred_empty_exclude_list
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_cred_new 3 ,
+.Xr fido_cred_set_authdata 3 ,
+.Xr fido_dev_make_cred 3
diff --git a/man/fido_cred_new.3 b/man/fido_cred_new.3
new file mode 100644
index 0000000..4f8b1be
--- /dev/null
+++ b/man/fido_cred_new.3
@@ -0,0 +1,316 @@
+.\" Copyright (c) 2018-2021 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 23 2018 $
+.Dt FIDO_CRED_NEW 3
+.Os
+.Sh NAME
+.Nm fido_cred_new ,
+.Nm fido_cred_free ,
+.Nm fido_cred_pin_minlen ,
+.Nm fido_cred_prot ,
+.Nm fido_cred_fmt ,
+.Nm fido_cred_rp_id ,
+.Nm fido_cred_rp_name ,
+.Nm fido_cred_user_name ,
+.Nm fido_cred_display_name ,
+.Nm fido_cred_authdata_ptr ,
+.Nm fido_cred_authdata_raw_ptr ,
+.Nm fido_cred_clientdata_hash_ptr ,
+.Nm fido_cred_id_ptr ,
+.Nm fido_cred_aaguid_ptr ,
+.Nm fido_cred_largeblob_key_ptr ,
+.Nm fido_cred_pubkey_ptr ,
+.Nm fido_cred_sig_ptr ,
+.Nm fido_cred_user_id_ptr ,
+.Nm fido_cred_x5c_ptr ,
+.Nm fido_cred_attstmt_ptr ,
+.Nm fido_cred_authdata_len ,
+.Nm fido_cred_authdata_raw_len ,
+.Nm fido_cred_clientdata_hash_len ,
+.Nm fido_cred_id_len ,
+.Nm fido_cred_aaguid_len ,
+.Nm fido_cred_largeblob_key_len ,
+.Nm fido_cred_pubkey_len ,
+.Nm fido_cred_sig_len ,
+.Nm fido_cred_user_id_len ,
+.Nm fido_cred_x5c_len ,
+.Nm fido_cred_attstmt_len ,
+.Nm fido_cred_type ,
+.Nm fido_cred_flags ,
+.Nm fido_cred_sigcount
+.Nd FIDO2 credential API
+.Sh SYNOPSIS
+.In fido.h
+.Ft fido_cred_t *
+.Fn fido_cred_new "void"
+.Ft void
+.Fn fido_cred_free "fido_cred_t **cred_p"
+.Ft size_t
+.Fn fido_cred_pin_minlen "const fido_cred_t *cred"
+.Ft int
+.Fn fido_cred_prot "const fido_cred_t *cred"
+.Ft const char *
+.Fn fido_cred_fmt "const fido_cred_t *cred"
+.Ft const char *
+.Fn fido_cred_rp_id "const fido_cred_t *cred"
+.Ft const char *
+.Fn fido_cred_rp_name "const fido_cred_t *cred"
+.Ft const char *
+.Fn fido_cred_user_name "const fido_cred_t *cred"
+.Ft const char *
+.Fn fido_cred_display_name "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_authdata_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_authdata_raw_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_clientdata_hash_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_id_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_aaguid_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_largeblob_key_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_pubkey_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_sig_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_user_id_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_x5c_ptr "const fido_cred_t *cred"
+.Ft const unsigned char *
+.Fn fido_cred_attstmt_ptr "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_authdata_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_authdata_raw_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_clientdata_hash_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_id_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_aaguid_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_largeblob_key_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_pubkey_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_sig_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_user_id_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_x5c_len "const fido_cred_t *cred"
+.Ft size_t
+.Fn fido_cred_attstmt_len "const fido_cred_t *cred"
+.Ft int
+.Fn fido_cred_type "const fido_cred_t *cred"
+.Ft uint8_t
+.Fn fido_cred_flags "const fido_cred_t *cred"
+.Ft uint32_t
+.Fn fido_cred_sigcount "const fido_cred_t *cred"
+.Sh DESCRIPTION
+FIDO2 credentials are abstracted in
+.Em libfido2
+by the
+.Vt fido_cred_t
+type.
+The functions described in this page allow a
+.Vt fido_cred_t
+type to be allocated, deallocated, and inspected.
+For other operations on
+.Vt fido_cred_t ,
+please refer to
+.Xr fido_cred_set_authdata 3 ,
+.Xr fido_cred_exclude 3 ,
+.Xr fido_cred_verify 3 ,
+and
+.Xr fido_dev_make_cred 3 .
+.Pp
+The
+.Fn fido_cred_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_cred_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_cred_free
+function releases the memory backing
+.Fa *cred_p ,
+where
+.Fa *cred_p
+must have been previously allocated by
+.Fn fido_cred_new .
+On return,
+.Fa *cred_p
+is set to NULL.
+Either
+.Fa cred_p
+or
+.Fa *cred_p
+may be NULL, in which case
+.Fn fido_cred_free
+is a NOP.
+.Pp
+If the CTAP 2.1
+.Dv FIDO_EXT_MINPINLEN
+extension is enabled on
+.Fa cred ,
+then the
+.Fn fido_cred_pin_minlen
+function returns the minimum PIN length of
+.Fa cred .
+Otherwise,
+.Fn fido_cred_pin_minlen
+returns zero.
+See
+.Xr fido_cred_set_pin_minlen 3
+on how to enable this extension.
+.Pp
+If the CTAP 2.1
+.Dv FIDO_EXT_CRED_PROTECT
+extension is enabled on
+.Fa cred ,
+then the
+.Fn fido_cred_prot
+function returns the protection of
+.Fa cred .
+Otherwise,
+.Fn fido_cred_prot
+returns zero.
+See
+.Xr fido_cred_set_prot 3
+for the protection policies understood by
+.Em libfido2 .
+.Pp
+The
+.Fn fido_cred_fmt
+function returns a pointer to a NUL-terminated string containing
+the attestation statement format identifier of
+.Fa cred ,
+or NULL if
+.Fa cred
+does not have a format set.
+.Pp
+The
+.Fn fido_cred_rp_id ,
+.Fn fido_cred_rp_name ,
+.Fn fido_cred_user_name ,
+and
+.Fn fido_cred_display_name
+functions return pointers to NUL-terminated strings holding the
+relying party ID, relying party name, user name, and user display
+name attributes of
+.Fa cred ,
+or NULL if the respective entry is not set.
+.Pp
+The
+.Fn fido_cred_authdata_ptr ,
+.Fn fido_cred_authdata_raw_ptr ,
+.Fn fido_cred_clientdata_hash_ptr ,
+.Fn fido_cred_id_ptr ,
+.Fn fido_cred_aaguid_ptr ,
+.Fn fido_cred_largeblob_key_ptr ,
+.Fn fido_cred_pubkey_ptr ,
+.Fn fido_cred_sig_ptr ,
+.Fn fido_cred_user_id_ptr ,
+.Fn fido_cred_x5c_ptr ,
+and
+.Fn fido_cred_attstmt_ptr
+functions return pointers to the CBOR-encoded and raw authenticator
+data, client data hash, ID, authenticator attestation GUID,
+.Dq largeBlobKey ,
+public key, signature, user ID, x509 certificate, and attestation
+statement parts of
+.Fa cred ,
+or NULL if the respective entry is not set.
+.Pp
+The corresponding length can be obtained by
+.Fn fido_cred_authdata_len ,
+.Fn fido_cred_authdata_raw_len ,
+.Fn fido_cred_clientdata_hash_len ,
+.Fn fido_cred_id_len ,
+.Fn fido_cred_aaguid_len ,
+.Fn fido_cred_largeblob_key_len ,
+.Fn fido_cred_pubkey_len ,
+.Fn fido_cred_sig_len ,
+.Fn fido_cred_user_id_len ,
+.Fn fido_cred_x5c_len ,
+and
+.Fn fido_cred_attstmt_len .
+.Pp
+The authenticator data, x509 certificate, and signature parts of a
+credential are typically passed to a FIDO2 server for verification.
+.Pp
+The
+.Fn fido_cred_type
+function returns the COSE algorithm of
+.Fa cred .
+.Pp
+The
+.Fn fido_cred_flags
+function returns the authenticator data flags of
+.Fa cred .
+.Pp
+The
+.Fn fido_cred_sigcount
+function returns the authenticator data signature counter of
+.Fa cred .
+.Sh RETURN VALUES
+The authenticator data returned by
+.Fn fido_cred_authdata_ptr
+is a CBOR-encoded byte string, as obtained from the authenticator.
+To obtain the decoded byte string, use
+.Fn fido_cred_authdata_raw_ptr .
+.Pp
+If not NULL, pointers returned by
+.Fn fido_cred_fmt ,
+.Fn fido_cred_authdata_ptr ,
+.Fn fido_cred_clientdata_hash_ptr ,
+.Fn fido_cred_id_ptr ,
+.Fn fido_cred_aaguid_ptr ,
+.Fn fido_cred_largeblob_key_ptr ,
+.Fn fido_cred_pubkey_ptr ,
+.Fn fido_cred_sig_ptr ,
+and
+.Fn fido_cred_x5c_ptr
+are guaranteed to exist until any API function that takes
+.Fa cred
+without the
+.Em const
+qualifier is invoked.
+.Sh SEE ALSO
+.Xr fido_cred_exclude 3 ,
+.Xr fido_cred_set_authdata 3 ,
+.Xr fido_cred_set_pin_minlen 3 ,
+.Xr fido_cred_set_prot 3 ,
+.Xr fido_cred_verify 3 ,
+.Xr fido_credman_metadata_new 3 ,
+.Xr fido_dev_largeblob_get 3 ,
+.Xr fido_dev_make_cred 3
diff --git a/man/fido_cred_set_authdata.3 b/man/fido_cred_set_authdata.3
new file mode 100644
index 0000000..e453832
--- /dev/null
+++ b/man/fido_cred_set_authdata.3
@@ -0,0 +1,382 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 15 2022 $
+.Dt FIDO_CRED_SET_AUTHDATA 3
+.Os
+.Sh NAME
+.Nm fido_cred_set_authdata ,
+.Nm fido_cred_set_authdata_raw ,
+.Nm fido_cred_set_attstmt ,
+.Nm fido_cred_set_x509 ,
+.Nm fido_cred_set_sig ,
+.Nm fido_cred_set_id ,
+.Nm fido_cred_set_clientdata ,
+.Nm fido_cred_set_clientdata_hash ,
+.Nm fido_cred_set_rp ,
+.Nm fido_cred_set_user ,
+.Nm fido_cred_set_extensions ,
+.Nm fido_cred_set_blob ,
+.Nm fido_cred_set_pin_minlen ,
+.Nm fido_cred_set_prot ,
+.Nm fido_cred_set_rk ,
+.Nm fido_cred_set_uv ,
+.Nm fido_cred_set_fmt ,
+.Nm fido_cred_set_type
+.Nd set parameters of a FIDO2 credential
+.Sh SYNOPSIS
+.In fido.h
+.Bd -literal
+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;
+.Ed
+.Ft int
+.Fn fido_cred_set_authdata "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_authdata_raw "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_attstmt "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_x509 "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_sig "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_id "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_clientdata "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_clientdata_hash "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_rp "fido_cred_t *cred" "const char *id" "const char *name"
+.Ft int
+.Fn 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"
+.Ft int
+.Fn fido_cred_set_extensions "fido_cred_t *cred" "int flags"
+.Ft int
+.Fn fido_cred_set_blob "fido_cred_t *cred" "const unsigned char *ptr" "size_t len"
+.Ft int
+.Fn fido_cred_set_pin_minlen "fido_cred_t *cred" "size_t len"
+.Ft int
+.Fn fido_cred_set_prot "fido_cred_t *cred" "int prot"
+.Ft int
+.Fn fido_cred_set_rk "fido_cred_t *cred" "fido_opt_t rk"
+.Ft int
+.Fn fido_cred_set_uv "fido_cred_t *cred" "fido_opt_t uv"
+.Ft int
+.Fn fido_cred_set_fmt "fido_cred_t *cred" "const char *ptr"
+.Ft int
+.Fn fido_cred_set_type "fido_cred_t *cred" "int cose_alg"
+.Sh DESCRIPTION
+The
+.Nm
+set of functions define the various parameters of a FIDO2
+credential, allowing a
+.Fa fido_cred_t
+type to be prepared for a subsequent call to
+.Xr fido_dev_make_cred 3
+or
+.Xr fido_cred_verify 3 .
+For the complete specification of a FIDO2 credential and the format
+of its constituent parts, please refer to the Web Authentication
+(webauthn) standard.
+.Pp
+The
+.Fn fido_cred_set_authdata ,
+.Fn fido_cred_set_attstmt ,
+.Fn fido_cred_set_x509 ,
+.Fn fido_cred_set_sig ,
+.Fn fido_cred_set_id ,
+and
+.Fn fido_cred_set_clientdata_hash
+functions set the authenticator data, attestation statement,
+attestation certificate, attestation signature, id, and client
+data hash parts of
+.Fa cred
+to
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+A copy of
+.Fa ptr
+is made, and no references to the passed pointer are kept.
+.Pp
+The authenticator data passed to
+.Fn fido_cred_set_authdata
+must be a CBOR-encoded byte string, as obtained from
+.Fn fido_cred_authdata_ptr .
+Alternatively, a raw binary blob may be passed to
+.Fn fido_cred_set_authdata_raw .
+An application calling
+.Fn fido_cred_set_authdata
+does not need to call
+.Fn fido_cred_set_id .
+The latter is meant to be used in contexts where the
+credential's authenticator data is not available.
+.Pp
+The attestation statement passed to
+.Fn fido_cred_set_attstmt
+must be a CBOR-encoded map, as obtained from
+.Fn fido_cred_attstmt_ptr .
+An application calling
+.Fn fido_cred_set_attstmt
+does not need to call
+.Fn fido_cred_set_x509
+or
+.Fn fido_cred_set_sig .
+The latter two are meant to be used in contexts where the
+credential's complete attestation statement is not available or
+required.
+.Pp
+The
+.Fn fido_cred_set_clientdata
+function allows an application to set the client data hash of
+.Fa cred
+by specifying the credential's unhashed client data.
+This is required by Windows Hello, which calculates the client data
+hash internally.
+For compatibility with Windows Hello, applications should use
+.Fn fido_cred_set_clientdata
+instead of
+.Fn fido_cred_set_clientdata_hash .
+.Pp
+The
+.Fn fido_cred_set_rp
+function sets the relying party
+.Fa id
+and
+.Fa name
+parameters of
+.Fa cred ,
+where
+.Fa id
+and
+.Fa name
+are NUL-terminated UTF-8 strings.
+The contents of
+.Fa id
+and
+.Fa name
+are copied, and no references to the passed pointers are kept.
+.Pp
+The
+.Fn fido_cred_set_user
+function sets the user attributes of
+.Fa cred ,
+where
+.Fa user_id
+points to
+.Fa user_id_len
+bytes and
+.Fa name ,
+.Fa display_name ,
+and
+.Fa icon
+are NUL-terminated UTF-8 strings.
+The contents of
+.Fa user_id ,
+.Fa name ,
+.Fa display_name ,
+and
+.Fa icon
+are copied, and no references to the passed pointers are kept.
+Previously set user attributes are flushed.
+The
+.Fa user_id ,
+.Fa name ,
+.Fa display_name ,
+and
+.Fa icon
+parameters may be NULL.
+.Pp
+The
+.Fn fido_cred_set_extensions
+function sets the extensions of
+.Fa cred
+to the bitmask
+.Fa flags .
+At the moment, only the
+.Dv FIDO_EXT_CRED_BLOB ,
+.Dv FIDO_EXT_CRED_PROTECT ,
+.Dv FIDO_EXT_HMAC_SECRET ,
+.Dv FIDO_EXT_MINPINLEN ,
+and
+.Dv FIDO_EXT_LARGEBLOB_KEY
+extensions are supported.
+If
+.Fa flags
+is zero, the extensions of
+.Fa cred
+are cleared.
+.Pp
+The
+.Fn fido_cred_set_blob
+function sets the
+.Dq credBlob
+to be stored with
+.Fa cred
+to the data pointed to by
+.Fa ptr ,
+which must be
+.Fa len
+bytes long.
+.Pp
+The
+.Fn fido_cred_set_pin_minlen
+function enables the CTAP 2.1
+.Dv FIDO_EXT_MINPINLEN
+extension on
+.Fa cred
+and sets the expected minimum PIN length of
+.Fa cred
+to
+.Fa len ,
+where
+.Fa len
+is greater than zero.
+If
+.Fa len
+is zero, the
+.Dv FIDO_EXT_MINPINLEN
+extension is disabled on
+.Fa cred .
+.Pp
+The
+.Fn fido_cred_set_prot
+function enables the CTAP 2.1
+.Dv FIDO_EXT_CRED_PROTECT
+extension on
+.Fa cred
+and sets the protection of
+.Fa cred
+to the scalar
+.Fa prot .
+At the moment, only the
+.Dv FIDO_CRED_PROT_UV_OPTIONAL ,
+.Dv FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID ,
+and
+.Dv FIDO_CRED_PROT_UV_REQUIRED
+protections are supported.
+If
+.Fa prot
+is zero, the protection of
+.Fa cred
+is cleared.
+.Pp
+The
+.Fn fido_cred_set_rk
+and
+.Fn fido_cred_set_uv
+functions set the
+.Em rk
+.Pq resident/discoverable key
+and
+.Em uv
+.Pq user verification
+attributes of
+.Fa cred .
+Both are
+.Dv FIDO_OPT_OMIT
+by default, allowing the authenticator to use its default settings.
+.Pp
+The
+.Fn fido_cred_set_fmt
+function sets the attestation statement format identifier of
+.Fa cred
+to
+.Fa fmt ,
+where
+.Fa fmt
+must be
+.Vt "packed"
+.Pq the format used in FIDO2 ,
+.Vt "fido-u2f"
+.Pq the format used in U2F ,
+.Vt "tpm"
+.Pq the format used by TPM-based authenticators ,
+or
+.Vt "none" .
+A copy of
+.Fa fmt
+is made, and no references to the passed pointer are kept.
+Note that not all authenticators support FIDO2 and therefore may only
+be able to generate
+.Vt fido-u2f
+attestation statements.
+.Pp
+The
+.Fn fido_cred_set_type
+function sets the type of
+.Fa cred to
+.Fa cose_alg ,
+where
+.Fa cose_alg
+is
+.Dv COSE_ES256 ,
+.Dv COSE_ES384 ,
+.Dv COSE_RS256 ,
+or
+.Dv COSE_EDDSA .
+The type of a credential may only be set once.
+Note that not all authenticators support COSE_RS256, COSE_ES384, or
+COSE_EDDSA.
+.Pp
+Use of the
+.Nm
+set of functions may happen in two distinct situations:
+when generating a new credential on a FIDO2 device, prior to
+.Xr fido_dev_make_cred 3
+(i.e, in the context of a FIDO2 client), or when validating
+a generated credential using
+.Xr fido_cred_verify 3
+(i.e, in the context of a FIDO2 server).
+.Pp
+For a complete description of the generation of a FIDO2 credential
+and its verification, please refer to the FIDO2 specification.
+A concrete utilisation example of the
+.Nm
+set of functions can be found in the
+.Pa cred.c
+example shipped with
+.Em libfido2 .
+.Sh RETURN VALUES
+The error codes returned by the
+.Nm
+set of functions are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_cred_exclude 3 ,
+.Xr fido_cred_verify 3 ,
+.Xr fido_dev_make_cred 3
diff --git a/man/fido_cred_verify.3 b/man/fido_cred_verify.3
new file mode 100644
index 0000000..9548870
--- /dev/null
+++ b/man/fido_cred_verify.3
@@ -0,0 +1,114 @@
+.\" Copyright (c) 2018-2021 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 23 2018 $
+.Dt FIDO_CRED_VERIFY 3
+.Os
+.Sh NAME
+.Nm fido_cred_verify ,
+.Nm fido_cred_verify_self
+.Nd verify the attestation signature of a FIDO2 credential
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_cred_verify "const fido_cred_t *cred"
+.Ft int
+.Fn fido_cred_verify_self "const fido_cred_t *cred"
+.Sh DESCRIPTION
+The
+.Fn fido_cred_verify
+and
+.Fn fido_cred_verify_self
+functions verify whether the attestation signature contained in
+.Fa cred
+matches the attributes of the credential.
+Before using
+.Fn fido_cred_verify
+or
+.Fn fido_cred_verify_self
+in a sensitive context, the reader is strongly encouraged to make
+herself familiar with the FIDO2 credential attestation process
+as defined in the Web Authentication (webauthn) standard.
+.Pp
+The
+.Fn fido_cred_verify
+function verifies whether the client data hash, relying party ID,
+credential ID, type, protection policy, minimum PIN length, and
+resident/discoverable key and user verification attributes of
+.Fa cred
+have been attested by the holder of the private counterpart of
+the public key contained in the credential's x509 certificate.
+.Pp
+Please note that the x509 certificate itself is not verified.
+.Pp
+The attestation statement formats supported by
+.Fn fido_cred_verify
+are
+.Em packed ,
+.Em fido-u2f ,
+and
+.Em tpm .
+The attestation type implemented by
+.Fn fido_cred_verify
+is
+.Em Basic Attestation .
+.Pp
+The
+.Fn fido_cred_verify_self
+function verifies whether the client data hash, relying party ID,
+credential ID, type, protection policy, minimum PIN length, and
+resident/discoverable key and user verification attributes of
+.Fa cred
+have been attested by the holder of the credential's private key.
+.Pp
+The attestation statement formats supported by
+.Fn fido_cred_verify_self
+are
+.Em packed
+and
+.Em fido-u2f .
+The attestation type implemented by
+.Fn fido_cred_verify_self
+is
+.Em Self Attestation .
+.Pp
+Other attestation formats and types are not supported.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_cred_verify
+and
+.Fn fido_cred_verify_self
+are defined in
+.In fido/err.h .
+If
+.Fa cred
+passes verification, then
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_cred_new 3 ,
+.Xr fido_cred_set_authdata 3
diff --git a/man/fido_credman_metadata_new.3 b/man/fido_credman_metadata_new.3
new file mode 100644
index 0000000..122020b
--- /dev/null
+++ b/man/fido_credman_metadata_new.3
@@ -0,0 +1,349 @@
+.\" Copyright (c) 2019-2021 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: June 28 2019 $
+.Dt FIDO_CREDMAN_METADATA_NEW 3
+.Os
+.Sh NAME
+.Nm fido_credman_metadata_new ,
+.Nm fido_credman_rk_new ,
+.Nm fido_credman_rp_new ,
+.Nm fido_credman_metadata_free ,
+.Nm fido_credman_rk_free ,
+.Nm fido_credman_rp_free ,
+.Nm fido_credman_rk_existing ,
+.Nm fido_credman_rk_remaining ,
+.Nm fido_credman_rk ,
+.Nm fido_credman_rk_count ,
+.Nm fido_credman_rp_id ,
+.Nm fido_credman_rp_name ,
+.Nm fido_credman_rp_count ,
+.Nm fido_credman_rp_id_hash_ptr ,
+.Nm fido_credman_rp_id_hash_len ,
+.Nm fido_credman_get_dev_metadata ,
+.Nm fido_credman_get_dev_rk ,
+.Nm fido_credman_set_dev_rk ,
+.Nm fido_credman_del_dev_rk ,
+.Nm fido_credman_get_dev_rp
+.Nd FIDO2 credential management API
+.Sh SYNOPSIS
+.In fido.h
+.In fido/credman.h
+.Ft fido_credman_metadata_t *
+.Fn fido_credman_metadata_new "void"
+.Ft fido_credman_rk_t *
+.Fn fido_credman_rk_new "void"
+.Ft fido_credman_rp_t *
+.Fn fido_credman_rp_new "void"
+.Ft void
+.Fn fido_credman_metadata_free "fido_credman_metadata_t **metadata_p"
+.Ft void
+.Fn fido_credman_rk_free "fido_credman_rk_t **rk_p"
+.Ft void
+.Fn fido_credman_rp_free "fido_credman_rp_t **rp_p"
+.Ft uint64_t
+.Fn fido_credman_rk_existing "const fido_credman_metadata_t *metadata"
+.Ft uint64_t
+.Fn fido_credman_rk_remaining "const fido_credman_metadata_t *metadata"
+.Ft const fido_cred_t *
+.Fn fido_credman_rk "const fido_credman_rk_t *rk" "size_t idx"
+.Ft size_t
+.Fn fido_credman_rk_count "const fido_credman_rk_t *rk"
+.Ft const char *
+.Fn fido_credman_rp_id "const fido_credman_rp_t *rp" "size_t idx"
+.Ft const char *
+.Fn fido_credman_rp_name "const fido_credman_rp_t *rp" "size_t idx"
+.Ft size_t
+.Fn fido_credman_rp_count "const fido_credman_rp_t *rp"
+.Ft const unsigned char *
+.Fn fido_credman_rp_id_hash_ptr "const fido_credman_rp_t *rp" "size_t idx"
+.Ft size_t
+.Fn fido_credman_rp_id_hash_len "const fido_credman_rp_t *" "size_t idx"
+.Ft int
+.Fn fido_credman_get_dev_metadata "fido_dev_t *dev" "fido_credman_metadata_t *metadata" "const char *pin"
+.Ft int
+.Fn fido_credman_get_dev_rk "fido_dev_t *dev" "const char *rp_id" "fido_credman_rk_t *rk" "const char *pin"
+.Ft int
+.Fn fido_credman_set_dev_rk "fido_dev_t *dev" "fido_cred_t *cred" "const char *pin"
+.Ft int
+.Fn fido_credman_del_dev_rk "fido_dev_t *dev" "const unsigned char *cred_id" "size_t cred_id_len" "const char *pin"
+.Ft int
+.Fn fido_credman_get_dev_rp "fido_dev_t *dev" "fido_credman_rp_t *rp" "const char *pin"
+.Sh DESCRIPTION
+The credential management API of
+.Em libfido2
+allows resident credentials on a FIDO2 authenticator to be listed,
+inspected, modified, and removed.
+Please note that not all FIDO2 authenticators support credential
+management.
+To obtain information on what an authenticator supports, please
+refer to
+.Xr fido_cbor_info_new 3 .
+.Pp
+The
+.Vt fido_credman_metadata_t
+type abstracts credential management metadata.
+.Pp
+The
+.Fn fido_credman_metadata_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_credman_metadata_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_credman_metadata_free
+function releases the memory backing
+.Fa *metadata_p ,
+where
+.Fa *metadata_p
+must have been previously allocated by
+.Fn fido_credman_metadata_new .
+On return,
+.Fa *metadata_p
+is set to NULL.
+Either
+.Fa metadata_p
+or
+.Fa *metadata_p
+may be NULL, in which case
+.Fn fido_credman_metadata_free
+is a NOP.
+.Pp
+The
+.Fn fido_credman_get_dev_metadata
+function populates
+.Fa metadata
+with information retrieved from
+.Fa dev .
+A valid
+.Fa pin
+must be provided.
+.Pp
+The
+.Fn fido_credman_rk_existing
+function inspects
+.Fa metadata
+and returns the number of resident credentials on the
+authenticator.
+The
+.Fn fido_credman_rk_remaining
+function inspects
+.Fa metadata
+and returns the estimated number of resident credentials that can
+be created on the authenticator.
+.Pp
+The
+.Vt fido_credman_rk_t
+type abstracts the set of resident credentials belonging to a
+given relying party.
+.Pp
+The
+.Fn fido_credman_rk_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_credman_rk_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_credman_rk_free
+function releases the memory backing
+.Fa *rk_p ,
+where
+.Fa *rk_p
+must have been previously allocated by
+.Fn fido_credman_rk_new .
+On return,
+.Fa *rk_p
+is set to NULL.
+Either
+.Fa rk_p
+or
+.Fa *rk_p
+may be NULL, in which case
+.Fn fido_credman_rk_free
+is a NOP.
+.Pp
+The
+.Fn fido_credman_get_dev_rk
+function populates
+.Fa rk
+with the set of resident credentials belonging to
+.Fa rp_id
+in
+.Fa dev .
+A valid
+.Fa pin
+must be provided.
+.Pp
+The
+.Fn fido_credman_rk_count
+function returns the number of resident credentials in
+.Fa rk .
+The
+.Fn fido_credman_rk
+function returns a pointer to the credential at index
+.Fa idx
+in
+.Fa rk .
+Please note that the first credential in
+.Fa rk
+has an
+.Fa idx
+(index) value of 0.
+.Pp
+The
+.Fn fido_credman_set_dev_rk
+function updates the credential pointed to by
+.Fa cred
+in
+.Fa dev .
+The credential id and user id attributes of
+.Fa cred
+must be set.
+See
+.Xr fido_cred_set_id 3
+and
+.Xr fido_cred_set_user 3
+for details.
+Only a credential's user attributes (name, display name)
+may be updated at this time.
+.Pp
+The
+.Fn fido_credman_del_dev_rk
+function deletes the resident credential identified by
+.Fa cred_id
+from
+.Fa dev ,
+where
+.Fa cred_id
+points to
+.Fa cred_id_len
+bytes.
+A valid
+.Fa pin
+must be provided.
+.Pp
+The
+.Vt fido_credman_rp_t
+type abstracts information about a relying party.
+.Pp
+The
+.Fn fido_credman_rp_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_credman_rp_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_credman_rp_free
+function releases the memory backing
+.Fa *rp_p ,
+where
+.Fa *rp_p
+must have been previously allocated by
+.Fn fido_credman_rp_new .
+On return,
+.Fa *rp_p
+is set to NULL.
+Either
+.Fa rp_p
+or
+.Fa *rp_p
+may be NULL, in which case
+.Fn fido_credman_rp_free
+is a NOP.
+.Pp
+The
+.Fn fido_credman_get_dev_rp
+function populates
+.Fa rp
+with information about relying parties with resident credentials
+in
+.Fa dev .
+A valid
+.Fa pin
+must be provided.
+.Pp
+The
+.Fn fido_credman_rp_count
+function returns the number of relying parties in
+.Fa rp .
+.Pp
+The
+.Fn fido_credman_rp_id
+and
+.Fn fido_credman_rp_name
+functions return pointers to the id and name of relying party
+.Fa idx
+in
+.Fa rp .
+If not NULL, the values returned by these functions point to
+NUL-terminated UTF-8 strings.
+Please note that the first relying party in
+.Fa rp
+has an
+.Fa idx
+(index) value of 0.
+.Pp
+The
+.Fn fido_credman_rp_id_hash_ptr
+function returns a pointer to the hashed id of relying party
+.Fa idx
+in
+.Fa rp .
+The corresponding length can be obtained by
+.Fn fido_credman_rp_id_hash_len .
+Please note that the first relying party in
+.Fa rp
+has an
+.Fa idx
+(index) value of 0.
+.Sh RETURN VALUES
+The
+.Fn fido_credman_get_dev_metadata ,
+.Fn fido_credman_get_dev_rk ,
+.Fn fido_credman_set_dev_rk ,
+.Fn fido_credman_del_dev_rk ,
+and
+.Fn fido_credman_get_dev_rp
+functions return
+.Dv FIDO_OK
+on success.
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+Functions returning pointers are not guaranteed to succeed, and
+should have their return values checked for NULL.
+.Sh SEE ALSO
+.Xr fido_cbor_info_new 3 ,
+.Xr fido_cred_new 3 ,
+.Xr fido_dev_supports_credman 3
+.Sh CAVEATS
+Resident credentials are called
+.Dq discoverable credentials
+in CTAP 2.1.
diff --git a/man/fido_dev_enable_entattest.3 b/man/fido_dev_enable_entattest.3
new file mode 100644
index 0000000..7617f22
--- /dev/null
+++ b/man/fido_dev_enable_entattest.3
@@ -0,0 +1,149 @@
+.\" Copyright (c) 2020-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: March 30 2022 $
+.Dt FIDO_DEV_ENABLE_ENTATTEST 3
+.Os
+.Sh NAME
+.Nm fido_dev_enable_entattest ,
+.Nm fido_dev_toggle_always_uv ,
+.Nm fido_dev_force_pin_change ,
+.Nm fido_dev_set_pin_minlen ,
+.Nm fido_dev_set_pin_minlen_rpid
+.Nd CTAP 2.1 configuration authenticator API
+.Sh SYNOPSIS
+.In fido.h
+.In fido/config.h
+.Ft int
+.Fn fido_dev_enable_entattest "fido_dev_t *dev" "const char *pin"
+.Ft int
+.Fn fido_dev_toggle_always_uv "fido_dev_t *dev" "const char *pin"
+.Ft int
+.Fn fido_dev_force_pin_change "fido_dev_t *dev" "const char *pin"
+.Ft int
+.Fn fido_dev_set_pin_minlen "fido_dev_t *dev" "size_t len" "const char *pin"
+.Ft int
+.Fn fido_dev_set_pin_minlen_rpid "fido_dev_t *dev" "const char * const *rpid" "size_t n" "const char *pin"
+.Sh DESCRIPTION
+The functions described in this page allow configuration of a
+CTAP 2.1 authenticator.
+.Pp
+The
+.Fn fido_dev_enable_entattest
+function enables the
+.Em Enterprise Attestation
+feature on
+.Fa dev .
+.Em Enterprise Attestation
+instructs the authenticator to include uniquely identifying
+information in subsequent attestation statements.
+The
+.Fa pin
+parameter may be NULL if
+.Fa dev
+does not have a PIN set.
+.Pp
+The
+.Fn fido_dev_toggle_always_uv
+function toggles the
+.Dq user verification always
+feature on
+.Fa dev .
+When set, this toggle enforces user verification at the
+authenticator level for all known credentials.
+If
+.Fa dev
+supports U2F (CTAP1) and the user verification methods supported by
+the authenticator do not allow protection of U2F credentials, the
+U2F subsystem will be disabled by the authenticator.
+The
+.Fa pin
+parameter may be NULL if
+.Fa dev
+does not have a PIN set.
+.Pp
+The
+.Fn fido_dev_force_pin_change
+function instructs
+.Fa dev
+to require a PIN change.
+Subsequent PIN authentication attempts against
+.Fa dev
+will fail until its PIN is changed.
+.Pp
+The
+.Fn fido_dev_set_pin_minlen
+function sets the minimum PIN length of
+.Fa dev
+to
+.Fa len .
+Minimum PIN lengths may only be increased.
+.Pp
+The
+.Fn fido_dev_set_pin_minlen_rpid
+function sets the list of relying party identifiers
+.Pq RP IDs
+that are allowed to obtain the minimum PIN length of
+.Fa dev
+through the CTAP 2.1
+.Dv FIDO_EXT_MINPINLEN
+extension.
+The list of RP identifiers is denoted by
+.Fa rpid ,
+a vector of
+.Fa n
+NUL-terminated UTF-8 strings.
+A copy of
+.Fa rpid
+is made, and no reference to it or its contents is kept.
+The maximum value of
+.Fa n
+supported by the authenticator can be obtained using
+.Xr fido_cbor_info_maxrpid_minpinlen 3 .
+.Pp
+Configuration settings are reflected in the payload returned by the
+authenticator in response to a
+.Xr fido_dev_get_cbor_info 3
+call.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_dev_enable_entattest ,
+.Fn fido_dev_toggle_always_uv ,
+.Fn fido_dev_force_pin_change ,
+.Fn fido_dev_set_pin_minlen ,
+and
+.Fn fido_dev_set_pin_minlen_rpid
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_cbor_info_maxrpid_minpinlen 3 ,
+.Xr fido_cred_pin_minlen 3 ,
+.Xr fido_dev_get_cbor_info 3 ,
+.Xr fido_dev_reset 3
diff --git a/man/fido_dev_get_assert.3 b/man/fido_dev_get_assert.3
new file mode 100644
index 0000000..bb2fc43
--- /dev/null
+++ b/man/fido_dev_get_assert.3
@@ -0,0 +1,99 @@
+.\" Copyright (c) 2018 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 24 2018 $
+.Dt FIDO_DEV_GET_ASSERT 3
+.Os
+.Sh NAME
+.Nm fido_dev_get_assert
+.Nd obtains an assertion from a FIDO2 device
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_dev_get_assert "fido_dev_t *dev" "fido_assert_t *assert" "const char *pin"
+.Sh DESCRIPTION
+The
+.Fn fido_dev_get_assert
+function asks the FIDO2 device represented by
+.Fa dev
+for an assertion according to the following parameters defined in
+.Fa assert :
+.Pp
+.Bl -dash -compact
+.It
+.Nm relying party ID ;
+.It
+.Nm client data hash ;
+.It
+.Nm list of allowed credential IDs ;
+.It
+.Nm user presence and user verification attributes .
+.El
+.Pp
+See
+.Xr fido_assert_set_authdata 3
+for information on how these values are set.
+.Pp
+If a PIN is not needed to authenticate the request against
+.Fa dev ,
+then
+.Fa pin
+may be NULL.
+Otherwise
+.Fa pin
+must point to a NUL-terminated UTF-8 string.
+.Pp
+After a successful call to
+.Fn fido_dev_get_assert ,
+the
+.Xr fido_assert_count 3 ,
+.Xr fido_assert_user_display_name 3 ,
+.Xr fido_assert_user_icon 3 ,
+.Xr fido_assert_user_name 3 ,
+.Xr fido_assert_authdata_ptr 3 ,
+.Xr fido_assert_user_id_ptr 3 ,
+.Xr fido_assert_sig_ptr 3 ,
+and
+.Xr fido_assert_sigcount 3
+functions may be invoked on
+.Fa assert
+to retrieve the various attributes of the generated assertion.
+.Pp
+Please note that
+.Fn fido_dev_get_assert
+is synchronous and will block if necessary.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_dev_get_assert
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_assert_new 3 ,
+.Xr fido_assert_set_authdata 3
diff --git a/man/fido_dev_get_touch_begin.3 b/man/fido_dev_get_touch_begin.3
new file mode 100644
index 0000000..f015eff
--- /dev/null
+++ b/man/fido_dev_get_touch_begin.3
@@ -0,0 +1,96 @@
+.\" Copyright (c) 2020 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: August 5 2020 $
+.Dt FIDO_DEV_GET_TOUCH_BEGIN 3
+.Os
+.Sh NAME
+.Nm fido_dev_get_touch_begin ,
+.Nm fido_dev_get_touch_status
+.Nd asynchronously wait for touch on a FIDO2 authenticator
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_dev_get_touch_begin "fido_dev_t *dev"
+.Ft int
+.Fn fido_dev_get_touch_status "fido_dev_t *dev" "int *touched" "int ms"
+.Sh DESCRIPTION
+The functions described in this page allow an application to
+asynchronously wait for touch on a FIDO2 authenticator.
+This is useful when multiple authenticators are present and
+the application needs to know which one to use.
+.Pp
+The
+.Fn fido_dev_get_touch_begin
+function initiates a touch request on
+.Fa dev .
+.Pp
+The
+.Fn fido_dev_get_touch_status
+function continues an ongoing touch request on
+.Fa dev ,
+blocking up to
+.Fa ms
+milliseconds.
+On success,
+.Fa touched
+will be updated to reflect the touch request status.
+If
+.Fa touched
+is 1, the device was touched, and the touch request is
+terminated.
+If
+.Fa touched
+is 0, the application may call
+.Fn fido_dev_get_touch_status
+to continue the touch request, or
+.Fn fido_dev_cancel
+to terminate it.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_dev_get_touch_begin
+and
+.Fn fido_dev_get_touch_status
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh EXAMPLES
+Please refer to
+.Em examples/select.c
+in
+.Em libfido2's
+source tree.
+.Sh SEE ALSO
+.Xr fido_dev_cancel 3
+.Sh CAVEATS
+The
+.Fn fido_dev_get_touch_status
+function will cause a command to be transmitted to U2F
+authenticators.
+These transmissions should not exceed a frequency of 5Hz.
diff --git a/man/fido_dev_info_manifest.3 b/man/fido_dev_info_manifest.3
new file mode 100644
index 0000000..a70a3cb
--- /dev/null
+++ b/man/fido_dev_info_manifest.3
@@ -0,0 +1,211 @@
+.\" Copyright (c) 2018 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: March 30 2022 $
+.Dt FIDO_DEV_INFO_MANIFEST 3
+.Os
+.Sh NAME
+.Nm fido_dev_info_manifest ,
+.Nm fido_dev_info_new ,
+.Nm fido_dev_info_free ,
+.Nm fido_dev_info_ptr ,
+.Nm fido_dev_info_path ,
+.Nm fido_dev_info_product ,
+.Nm fido_dev_info_vendor ,
+.Nm fido_dev_info_manufacturer_string ,
+.Nm fido_dev_info_product_string ,
+.Nm fido_dev_info_set
+.Nd FIDO2 device discovery functions
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_dev_info_manifest "fido_dev_info_t *devlist" "size_t ilen" "size_t *olen"
+.Ft fido_dev_info_t *
+.Fn fido_dev_info_new "size_t n"
+.Ft void
+.Fn fido_dev_info_free "fido_dev_info_t **devlist_p" "size_t n"
+.Ft const fido_dev_info_t *
+.Fn fido_dev_info_ptr "const fido_dev_info_t *devlist" "size_t i"
+.Ft const char *
+.Fn fido_dev_info_path "const fido_dev_info_t *di"
+.Ft int16_t
+.Fn fido_dev_info_product "const fido_dev_info_t *di"
+.Ft int16_t
+.Fn fido_dev_info_vendor "const fido_dev_info_t *di"
+.Ft const char *
+.Fn fido_dev_info_manufacturer_string "const fido_dev_info_t *di"
+.Ft const char *
+.Fn fido_dev_info_product_string "const fido_dev_info_t *di"
+.Ft int
+.Fn 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"
+.Sh DESCRIPTION
+The
+.Fn fido_dev_info_manifest
+function fills
+.Fa devlist
+with up to
+.Fa ilen
+FIDO2 devices found by the underlying operating system.
+Currently only USB HID devices are supported.
+The number of discovered devices is returned in
+.Fa olen ,
+where
+.Fa olen
+is an addressable pointer.
+.Pp
+The
+.Fn fido_dev_info_new
+function returns a pointer to a newly allocated, empty device list
+with
+.Fa n
+available slots.
+If memory is not available, NULL is returned.
+.Pp
+The
+.Fn fido_dev_info_free
+function releases the memory backing
+.Fa *devlist_p ,
+where
+.Fa *devlist_p
+must have been previously allocated by
+.Fn fido_dev_info_new .
+The number
+.Fa n
+of allocated slots must also be provided.
+On return,
+.Fa *devlist_p
+is set to NULL.
+Either
+.Fa devlist_p
+or
+.Fa *devlist_p
+may be NULL, in which case
+.Fn fido_dev_info_free
+is a NOP.
+.Pp
+The
+.Fn fido_dev_info_ptr
+function returns a pointer to slot number
+.Fa i
+of
+.Fa devlist .
+It is the caller's responsibility to ensure that
+.Fa i
+is bounded.
+Please note that the first slot has index 0.
+.Pp
+The
+.Fn fido_dev_info_path
+function returns the filesystem path or subsystem-specific identification
+string of
+.Fa di .
+.Pp
+The
+.Fn fido_dev_info_product
+function returns the product ID of
+.Fa di .
+.Pp
+The
+.Fn fido_dev_info_vendor
+function returns the vendor ID of
+.Fa di .
+.Pp
+The
+.Fn fido_dev_info_manufacturer_string
+function returns the manufacturer string of
+.Fa di .
+If
+.Fa di
+does not have an associated manufacturer string,
+.Fn fido_dev_info_manufacturer_string
+returns an empty string.
+.Pp
+The
+.Fn fido_dev_info_product_string
+function returns the product string of
+.Fa di .
+If
+.Fa di
+does not have an associated product string,
+.Fn fido_dev_info_product_string
+returns an empty string.
+.Pp
+An example of how to use the functions described in this document
+can be found in the
+.Pa examples/manifest.c
+file shipped with
+.Em libfido2 .
+.Pp
+The
+.Fn fido_dev_info_set
+function initializes an entry in a device list allocated by
+.Fn fido_dev_info_new
+with the specified path, manufacturer, and product strings, and with
+the specified I/O handlers and, optionally, transport functions, as
+described in
+.Xr fido_dev_set_io_functions 3 .
+The
+.Fa io
+argument must be specified; the
+.Fa transport
+argument may be
+.Dv NULL .
+The path, I/O handlers, and transport functions will be used
+automatically by
+.Xr fido_dev_new_with_info 3
+and
+.Xr fido_dev_open_with_info 3 .
+An application can use this, for example, to substitute mock FIDO2
+devices in testing for the real ones that
+.Fn fido_dev_info_manifest
+would discover.
+.Sh RETURN VALUES
+The
+.Fn fido_dev_info_manifest
+function always returns
+.Dv FIDO_OK .
+If a discovery error occurs, the
+.Fa olen
+pointer is set to 0.
+.Pp
+On success, the
+.Fn fido_dev_info_set
+function returns
+.Dv FIDO_OK .
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+.Pp
+The pointers returned by
+.Fn fido_dev_info_ptr ,
+.Fn fido_dev_info_path ,
+.Fn fido_dev_info_manufacturer_string ,
+and
+.Fn fido_dev_info_product_string
+are guaranteed to exist until
+.Fn fido_dev_info_free
+is called on the corresponding device list.
diff --git a/man/fido_dev_largeblob_get.3 b/man/fido_dev_largeblob_get.3
new file mode 100644
index 0000000..12dd319
--- /dev/null
+++ b/man/fido_dev_largeblob_get.3
@@ -0,0 +1,216 @@
+.\" Copyright (c) 2020 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: October 26 2020 $
+.Dt FIDO_LARGEBLOB_GET 3
+.Os
+.Sh NAME
+.Nm fido_dev_largeblob_get ,
+.Nm fido_dev_largeblob_set ,
+.Nm fido_dev_largeblob_remove ,
+.Nm fido_dev_largeblob_get_array ,
+.Nm fido_dev_largeblob_set_array
+.Nd FIDO2 large blob API
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn 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"
+.Ft int
+.Fn 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"
+.Ft int
+.Fn fido_dev_largeblob_remove "fido_dev_t *dev" "const unsigned char *key_ptr" "size_t key_len" "const char *pin"
+.Ft int
+.Fn fido_dev_largeblob_get_array "fido_dev_t *dev" "unsigned char **cbor_ptr" "size_t *cbor_len"
+.Ft int
+.Fn fido_dev_largeblob_set_array "fido_dev_t *dev" "const unsigned char *cbor_ptr" "size_t cbor_len" "const char *pin"
+.Sh DESCRIPTION
+The
+.Dq largeBlobs
+API of
+.Em libfido2
+allows binary blobs residing on a CTAP 2.1 authenticator to be
+read, written, and inspected.
+.Dq largeBlobs
+is a CTAP 2.1 extension.
+.Pp
+.Dq largeBlobs
+are stored as elements of a CBOR array.
+Confidentiality is ensured by encrypting each element with a
+distinct, credential-bound 256-bit AES-GCM key.
+The array is otherwise shared between different credentials and
+FIDO2 relying parties.
+.Pp
+Retrieval of a credential's encryption key is possible during
+enrollment with
+.Xr fido_cred_set_extensions 3
+and
+.Xr fido_cred_largeblob_key_ptr 3 ,
+during assertion with
+.Xr fido_assert_set_extensions 3
+and
+.Xr fido_assert_largeblob_key_ptr 3 ,
+or, in the case of a resident credential, via
+.Em libfido2's
+credential management API.
+.Pp
+The
+.Dq largeBlobs
+CBOR array is opaque to the authenticator.
+Management of the array is left at the discretion of FIDO2 clients.
+For further details on CTAP 2.1's
+.Dq largeBlobs
+extension, please refer to the CTAP 2.1 spec.
+.Pp
+The
+.Fn fido_dev_largeblob_get
+function retrieves the authenticator's
+.Dq largeBlobs
+CBOR array and, on success, returns the first blob
+.Pq iterating from array index zero
+that can be decrypted by
+.Fa key_ptr ,
+where
+.Fa key_ptr
+points to
+.Fa key_len
+bytes.
+On success,
+.Fn fido_dev_largeblob_get
+sets
+.Fa blob_ptr
+to the body of the decrypted blob, and
+.Fa blob_len
+to the length of the decrypted blob in bytes.
+It is the caller's responsibility to free
+.Fa blob_ptr .
+.Pp
+The
+.Fn fido_dev_largeblob_set
+function uses
+.Fa key_ptr
+to encrypt
+.Fa blob_ptr
+and inserts the result in the authenticator's
+.Dq largeBlobs
+CBOR array.
+Insertion happens at the end of the array if no existing element
+can be decrypted by
+.Fa key_ptr ,
+or at the position of the first element
+.Pq iterating from array index zero
+that can be decrypted by
+.Fa key_ptr .
+.Fa key_len
+holds the length of
+.Fa key_ptr
+in bytes, and
+.Fa blob_len
+the length of
+.Fa blob_ptr
+in bytes.
+A
+.Fa pin
+or equivalent user-verification gesture is required.
+.Pp
+The
+.Fn fido_dev_largeblob_remove
+function retrieves the authenticator's
+.Dq largeBlobs
+CBOR array and, on success, drops the first blob
+.Pq iterating from array index zero
+that can be decrypted by
+.Fa key_ptr ,
+where
+.Fa key_ptr
+points to
+.Fa key_len
+bytes.
+A
+.Fa pin
+or equivalent user-verification gesture is required.
+.Pp
+The
+.Fn fido_dev_largeblob_get_array
+function retrieves the authenticator's
+.Dq largeBlobs
+CBOR array and, on success,
+sets
+.Fa cbor_ptr
+to the body of the CBOR array, and
+.Fa cbor_len
+to its corresponding length in bytes.
+It is the caller's responsibility to free
+.Fa cbor_ptr .
+.Pp
+Finally, the
+.Fn fido_dev_largeblob_set_array
+function sets the authenticator's
+.Dq largeBlobs
+CBOR array to the data pointed to by
+.Fa cbor_ptr ,
+where
+.Fa cbor_ptr
+points to
+.Fa cbor_len
+bytes.
+A
+.Fa pin
+or equivalent user-verification gesture is required.
+.Sh RETURN VALUES
+The functions
+.Fn fido_dev_largeblob_set ,
+.Fn fido_dev_largeblob_get ,
+.Fn fido_dev_largeblob_remove ,
+.Fn fido_dev_largeblob_get_array ,
+and
+.Fn fido_dev_largeblob_set_array
+return
+.Dv FIDO_OK
+on success.
+On error, an error code defined in
+.In fido/err.h
+is returned.
+.Sh SEE ALSO
+.Xr fido_assert_largeblob_key_len 3 ,
+.Xr fido_assert_largeblob_key_ptr 3 ,
+.Xr fido_assert_set_extensions 3 ,
+.Xr fido_cred_largeblob_key_len 3 ,
+.Xr fido_cred_largeblob_key_ptr 3 ,
+.Xr fido_cred_set_extensions 3 ,
+.Xr fido_credman_get_dev_rk 3 ,
+.Xr fido_credman_get_dev_rp 3 ,
+.Xr fido_dev_get_assert 3 ,
+.Xr fido_dev_make_cred 3
+.Sh CAVEATS
+The
+.Dq largeBlobs
+extension is not meant to be used to store sensitive data.
+When retrieved, a credential's
+.Dq largeBlobs
+encryption key is transmitted in the clear, and an authenticator's
+.Dq largeBlobs
+CBOR array can be read without user interaction or verification.
diff --git a/man/fido_dev_make_cred.3 b/man/fido_dev_make_cred.3
new file mode 100644
index 0000000..b13f9a1
--- /dev/null
+++ b/man/fido_dev_make_cred.3
@@ -0,0 +1,100 @@
+.\" Copyright (c) 2018 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 23 2018 $
+.Dt FIDO_DEV_MAKE_CRED 3
+.Os
+.Sh NAME
+.Nm fido_dev_make_cred
+.Nd generates a new credential on a FIDO2 device
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_dev_make_cred "fido_dev_t *dev" "fido_cred_t *cred" "const char *pin"
+.Sh DESCRIPTION
+The
+.Fn fido_dev_make_cred
+function asks the FIDO2 device represented by
+.Fa dev
+to generate a new credential according to the following parameters
+defined in
+.Fa cred :
+.Pp
+.Bl -dash -compact
+.It
+.Nm type ;
+.It
+.Nm client data hash ;
+.It
+.Nm relying party ;
+.It
+.Nm user attributes ;
+.It
+.Nm list of excluded credential IDs ;
+.It
+.Nm resident/discoverable key and user verification attributes .
+.El
+.Pp
+See
+.Xr fido_cred_set_authdata 3
+for information on how these values are set.
+.Pp
+If a PIN is not needed to authenticate the request against
+.Fa dev ,
+then
+.Fa pin
+may be NULL.
+Otherwise
+.Fa pin
+must point to a NUL-terminated UTF-8 string.
+.Pp
+After a successful call to
+.Fn fido_dev_make_cred ,
+the
+.Xr fido_cred_authdata_ptr 3 ,
+.Xr fido_cred_pubkey_ptr 3 ,
+.Xr fido_cred_x5c_ptr 3 ,
+and
+.Xr fido_cred_sig_ptr 3
+functions may be invoked on
+.Fa cred
+to retrieve the various parts of the generated credential.
+.Pp
+Please note that
+.Fn fido_dev_make_cred
+is synchronous and will block if necessary.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_dev_make_cred
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_cred_new 3 ,
+.Xr fido_cred_set_authdata 3
diff --git a/man/fido_dev_open.3 b/man/fido_dev_open.3
new file mode 100644
index 0000000..f839e26
--- /dev/null
+++ b/man/fido_dev_open.3
@@ -0,0 +1,316 @@
+.\" Copyright (c) 2018 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 25 2018 $
+.Dt FIDO_DEV_OPEN 3
+.Os
+.Sh NAME
+.Nm fido_dev_open ,
+.Nm fido_dev_open_with_info ,
+.Nm fido_dev_close ,
+.Nm fido_dev_cancel ,
+.Nm fido_dev_new ,
+.Nm fido_dev_new_with_info ,
+.Nm fido_dev_free ,
+.Nm fido_dev_force_fido2 ,
+.Nm fido_dev_force_u2f ,
+.Nm fido_dev_is_fido2 ,
+.Nm fido_dev_is_winhello ,
+.Nm fido_dev_supports_credman ,
+.Nm fido_dev_supports_cred_prot ,
+.Nm fido_dev_supports_permissions ,
+.Nm fido_dev_supports_pin ,
+.Nm fido_dev_supports_uv ,
+.Nm fido_dev_has_pin ,
+.Nm fido_dev_has_uv ,
+.Nm fido_dev_protocol ,
+.Nm fido_dev_build ,
+.Nm fido_dev_flags ,
+.Nm fido_dev_major ,
+.Nm fido_dev_minor
+.Nd FIDO2 device open/close and related functions
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_dev_open "fido_dev_t *dev" "const char *path"
+.Ft int
+.Fn fido_dev_open_with_info "fido_dev_t *dev"
+.Ft int
+.Fn fido_dev_close "fido_dev_t *dev"
+.Ft int
+.Fn fido_dev_cancel "fido_dev_t *dev"
+.Ft fido_dev_t *
+.Fn fido_dev_new "void"
+.Ft fido_dev_t *
+.Fn fido_dev_new_with_info "const fido_dev_info_t *"
+.Ft void
+.Fn fido_dev_free "fido_dev_t **dev_p"
+.Ft void
+.Fn fido_dev_force_fido2 "fido_dev_t *dev"
+.Ft void
+.Fn fido_dev_force_u2f "fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_is_fido2 "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_is_winhello "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_supports_credman "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_supports_cred_prot "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_supports_permissions "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_supports_pin "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_supports_uv "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_has_pin "const fido_dev_t *dev"
+.Ft bool
+.Fn fido_dev_has_uv "const fido_dev_t *dev"
+.Ft uint8_t
+.Fn fido_dev_protocol "const fido_dev_t *dev"
+.Ft uint8_t
+.Fn fido_dev_build "const fido_dev_t *dev"
+.Ft uint8_t
+.Fn fido_dev_flags "const fido_dev_t *dev"
+.Ft uint8_t
+.Fn fido_dev_major "const fido_dev_t *dev"
+.Ft uint8_t
+.Fn fido_dev_minor "const fido_dev_t *dev"
+.Sh DESCRIPTION
+The
+.Fn fido_dev_open
+function opens the device pointed to by
+.Fa path ,
+where
+.Fa dev
+is a freshly allocated or otherwise closed
+.Vt fido_dev_t .
+If
+.Fa dev
+claims to be FIDO2,
+.Em libfido2
+will attempt to speak FIDO2 to
+.Fa dev .
+If that fails,
+.Em libfido2
+will fallback to U2F unless the
+.Dv FIDO_DISABLE_U2F_FALLBACK
+flag was set in
+.Xr fido_init 3 .
+.Pp
+The
+.Fn fido_dev_open_with_info
+function opens
+.Fa dev
+as previously allocated using
+.Fn fido_dev_new_with_info .
+.Pp
+The
+.Fn fido_dev_close
+function closes the device represented by
+.Fa dev .
+If
+.Fa dev
+is already closed,
+.Fn fido_dev_close
+is a NOP.
+.Pp
+The
+.Fn fido_dev_cancel
+function cancels any pending requests on
+.Fa dev .
+.Pp
+The
+.Fn fido_dev_new
+function returns a pointer to a newly allocated, empty
+.Vt fido_dev_t .
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_dev_new_with_info
+function returns a pointer to a newly allocated
+.Vt fido_dev_t
+with
+.Vt fido_dev_info_t
+parameters, for use with
+.Xr fido_dev_info_manifest 3
+and
+.Fn fido_dev_open_with_info .
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn fido_dev_free
+function releases the memory backing
+.Fa *dev_p ,
+where
+.Fa *dev_p
+must have been previously allocated by
+.Fn fido_dev_new .
+On return,
+.Fa *dev_p
+is set to NULL.
+Either
+.Fa dev_p
+or
+.Fa *dev_p
+may be NULL, in which case
+.Fn fido_dev_free
+is a NOP.
+.Pp
+The
+.Fn fido_dev_force_fido2
+function can be used to force CTAP2 communication with
+.Fa dev ,
+where
+.Fa dev
+is an open device.
+.Pp
+The
+.Fn fido_dev_force_u2f
+function can be used to force CTAP1 (U2F) communication with
+.Fa dev ,
+where
+.Fa dev
+is an open device.
+.Pp
+The
+.Fn fido_dev_is_fido2
+function returns
+.Dv true
+if
+.Fa dev
+is a FIDO2 device.
+.Pp
+The
+.Fn fido_dev_is_winhello
+function returns
+.Dv true
+if
+.Fa dev
+is a Windows Hello device.
+.Pp
+The
+.Fn fido_dev_supports_credman
+function returns
+.Dv true
+if
+.Fa dev
+supports CTAP 2.1 Credential Management.
+.Pp
+The
+.Fn fido_dev_supports_cred_prot
+function returns
+.Dv true
+if
+.Fa dev
+supports CTAP 2.1 Credential Protection.
+.Pp
+The
+.Fn fido_dev_supports_permissions
+function returns
+.Dv true
+if
+.Fa dev
+supports CTAP 2.1 UV token permissions.
+.Pp
+The
+.Fn fido_dev_supports_pin
+function returns
+.Dv true
+if
+.Fa dev
+supports CTAP 2.0 Client PINs.
+.Pp
+The
+.Fn fido_dev_supports_uv
+function returns
+.Dv true
+if
+.Fa dev
+supports a built-in user verification method.
+.Pp
+The
+.Fn fido_dev_has_pin
+function returns
+.Dv true
+if
+.Fa dev
+has a CTAP 2.0 Client PIN set.
+.Pp
+The
+.Fn fido_dev_has_uv
+function returns
+.Dv true
+if
+.Fa dev
+supports built-in user verification and its user verification
+feature is configured.
+.Pp
+The
+.Fn fido_dev_protocol
+function returns the CTAPHID protocol version identifier of
+.Fa dev .
+.Pp
+The
+.Fn fido_dev_build
+function returns the CTAPHID build version number of
+.Fa dev .
+.Pp
+The
+.Fn fido_dev_flags
+function returns the CTAPHID capabilities flags of
+.Fa dev .
+.Pp
+The
+.Fn fido_dev_major
+function returns the CTAPHID major version number of
+.Fa dev .
+.Pp
+The
+.Fn fido_dev_minor
+function returns the CTAPHID minor version number of
+.Fa dev .
+.Pp
+For the format and meaning of the CTAPHID parameters returned by
+functions above, please refer to the FIDO Client to Authenticator
+Protocol (CTAP) specification.
+.Sh RETURN VALUES
+On success,
+.Fn fido_dev_open ,
+.Fn fido_dev_open_with_info ,
+and
+.Fn fido_dev_close
+return
+.Dv FIDO_OK .
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+.Sh SEE ALSO
+.Xr fido_dev_info_manifest 3 ,
+.Xr fido_dev_set_io_functions 3 ,
+.Xr fido_init 3
diff --git a/man/fido_dev_set_io_functions.3 b/man/fido_dev_set_io_functions.3
new file mode 100644
index 0000000..e3e10ba
--- /dev/null
+++ b/man/fido_dev_set_io_functions.3
@@ -0,0 +1,261 @@
+.\" Copyright (c) 2018-2021 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 25 2018 $
+.Dt FIDO_DEV_SET_IO_FUNCTIONS 3
+.Os
+.Sh NAME
+.Nm fido_dev_set_io_functions ,
+.Nm fido_dev_set_sigmask ,
+.Nm fido_dev_set_timeout ,
+.Nm fido_dev_set_transport_functions ,
+.Nm fido_dev_io_handle
+.Nd FIDO2 device I/O interface
+.Sh SYNOPSIS
+.In fido.h
+.Bd -literal
+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 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;
+
+#ifdef _WIN32
+typedef int fido_sigset_t;
+#else
+typedef sigset_t fido_sigset_t;
+#endif
+
+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_transport {
+ fido_dev_rx_t *rx;
+ fido_dev_tx_t *tx;
+} fido_dev_transport_t;
+.Ed
+.Pp
+.Ft int
+.Fn fido_dev_set_io_functions "fido_dev_t *dev" "const fido_dev_io_t *io"
+.Ft int
+.Fn fido_dev_set_sigmask "fido_dev_t *dev" "const fido_sigset_t *sigmask"
+.Ft int
+.Fn fido_dev_set_timeout "fido_dev_t *dev" "int ms"
+.Ft int
+.Fn fido_dev_set_transport_functions "fido_dev_t *dev" "const fido_dev_transport_t *t"
+.Ft void *
+.Fn fido_dev_io_handle "const fido_dev_t *dev"
+.Sh DESCRIPTION
+The
+.Fn fido_dev_set_io_functions
+function sets the I/O handlers used by
+.Em libfido2
+to talk to
+.Fa dev .
+By default, these handlers are set to the operating system's native HID or NFC
+interfaces.
+They are defined as follows:
+.Bl -tag -width Ds
+.It Vt fido_dev_open_t
+Receives a
+.Vt const char *
+holding a path and opens the corresponding device, returning a
+non-NULL opaque pointer on success and NULL on error.
+.It Vt fido_dev_close_t
+Receives the opaque pointer returned by
+.Vt fido_dev_open_t
+and closes the device.
+.It Vt fido_dev_read_t
+Reads a single transmission unit (HID report, APDU) from a device.
+The first parameter is the opaque pointer returned by
+.Vt fido_dev_open_t .
+The second parameter is the read buffer, and the third parameter
+is the read buffer size.
+The fourth parameter is the number of milliseconds the caller is
+willing to sleep, should the call need to block.
+If this value holds -1,
+.Vt fido_dev_read_t
+may block indefinitely.
+On success, the number of bytes read is returned.
+On error, -1 is returned.
+.It Vt fido_dev_write_t
+Writes a single transmission unit (HID report, APDU) to
+.Fa dev .
+The first parameter is the opaque pointer returned by
+.Vt fido_dev_open_t .
+The second parameter is the write buffer, and the third parameter
+is the number of bytes to be written.
+A
+.Vt fido_dev_write_t
+may block.
+On success, the number of bytes written is returned.
+On error, -1 is returned.
+.El
+.Pp
+When calling
+.Fn fido_dev_set_io_functions ,
+the
+.Fa open ,
+.Fa close ,
+.Fa read ,
+and
+.Fa write
+fields of
+.Fa io
+may not be NULL.
+.Pp
+No references to
+.Fa io
+are held by
+.Fn fido_dev_set_io_functions .
+.Pp
+The
+.Fn fido_dev_set_sigmask
+function may be used to specify a non-NULL signal mask
+.Fa sigmask
+to be used while
+.Em libfido2's
+default I/O handlers wait on
+.Fa dev .
+On UNIX-like operating systems,
+.Vt fido_sigset_t
+is defined as
+.Vt sigset_t .
+On Windows,
+.Vt fido_sigset_t
+is defined as
+.Vt int
+and
+.Fn fido_dev_set_sigmask
+is a no-op.
+.Pp
+No references to
+.Fa sigmask
+are held by
+.Fn fido_dev_set_sigmask .
+.Pp
+The
+.Fn fido_dev_set_timeout
+function informs
+.Em libfido2
+not to block for more than
+.Fa ms
+milliseconds while communicating with
+.Fa dev .
+If a timeout occurs, the corresponding
+.Em fido_dev_*
+function will fail with
+.Dv FIDO_ERR_RX .
+If
+.Fa ms
+is -1,
+then
+.Em libfido2
+may block indefinitely.
+This is the default behaviour.
+When using the Windows Hello backend,
+.Fa ms
+is used as a guidance and may be overwritten by the platform.
+.Pp
+The
+.Fn fido_dev_set_transport_functions
+function sets the transport functions used by
+.Em libfido2
+to talk to
+.Fa dev .
+While the I/O handlers are responsible for sending and receiving
+transmission units of initialization and continuation packets already
+formatted by
+.Em libfido2 ,
+the transport handlers are responsible for sending and receiving
+the CTAPHID commands and data directly, as defined in the FIDO Client
+to Authenticator Protocol (CTAP) standard.
+They are defined as follows:
+.Bl -tag -width Ds
+.It Vt fido_dev_tx_t
+Receives a device, a CTAPHID command to transmit, a data buffer to
+transmit, and the length of the data buffer.
+On success, 0 is returned.
+On error, -1 is returned.
+.It Vt fido_dev_rx_t
+Receives a device, a CTAPHID command whose response the caller expects
+to receive, a data buffer to receive into, the size of the data buffer
+determining the maximum length of a response, and the maximum number of
+milliseconds to wait for a response.
+On success, the number of bytes read into the data buffer is returned.
+On error, -1 is returned.
+.El
+.Pp
+When transport functions are specified,
+.Em libfido2
+will use them instead of the
+.Dv read
+and
+.Dv write
+functions of the I/O handlers.
+However, the I/O handlers must still be specified to open and close the
+device.
+.Pp
+The
+.Fn fido_dev_io_handle
+function returns the opaque pointer returned by the
+.Dv open
+function of the I/O handlers.
+This is useful mainly for the transport functions, which unlike the I/O
+handlers are passed the
+.Vt fido_dev_t
+pointer instead of the opaque I/O handle.
+.Sh RETURN VALUES
+On success,
+.Fn fido_dev_set_io_functions ,
+.Fn fido_dev_set_transport_functions ,
+.Fn fido_dev_set_sigmask ,
+and
+.Fn fido_dev_set_timeout
+return
+.Dv FIDO_OK .
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+.Sh SEE ALSO
+.Xr fido_dev_info_manifest 3 ,
+.Xr fido_dev_open 3
+.Rs
+.%D 2021-06-15
+.%O Proposed Standard, Version 2.1
+.%Q FIDO Alliance
+.%R Client to Authenticator Protocol (CTAP)
+.%U https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html
+.Re
diff --git a/man/fido_dev_set_pin.3 b/man/fido_dev_set_pin.3
new file mode 100644
index 0000000..eec062d
--- /dev/null
+++ b/man/fido_dev_set_pin.3
@@ -0,0 +1,128 @@
+.\" Copyright (c) 2018 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 25 2018 $
+.Dt FIDO_DEV_SET_PIN 3
+.Os
+.Sh NAME
+.Nm fido_dev_set_pin ,
+.Nm fido_dev_get_retry_count ,
+.Nm fido_dev_get_uv_retry_count ,
+.Nm fido_dev_reset
+.Nd FIDO2 device management functions
+.Sh SYNOPSIS
+.In fido.h
+.Ft int
+.Fn fido_dev_set_pin "fido_dev_t *dev" "const char *pin" "const char *oldpin"
+.Ft int
+.Fn fido_dev_get_retry_count "fido_dev_t *dev" "int *retries"
+.Ft int
+.Fn fido_dev_get_uv_retry_count "fido_dev_t *dev" "int *retries"
+.Ft int
+.Fn fido_dev_reset "fido_dev_t *dev"
+.Sh DESCRIPTION
+The
+.Fn fido_dev_set_pin
+function sets the PIN of device
+.Fa dev
+to
+.Fa pin ,
+where
+.Fa pin
+is a NUL-terminated UTF-8 string.
+If
+.Fa oldpin
+is not NULL, the device's PIN is changed from
+.Fa oldpin
+to
+.Fa pin ,
+where
+.Fa pin
+and
+.Fa oldpin
+are NUL-terminated UTF-8 strings.
+.Pp
+The
+.Fn fido_dev_get_retry_count
+function fills
+.Fa retries
+with the number of PIN retries left in
+.Fa dev
+before lock-out, where
+.Fa retries
+is an addressable pointer.
+.Pp
+The
+.Fn fido_dev_get_uv_retry_count
+function fills
+.Fa retries
+with the number of built-in UV retries left in
+.Fa dev
+before built-in UV is disabled, where
+.Fa retries
+is an addressable pointer.
+.Pp
+The
+.Fn fido_dev_reset
+function performs a reset on
+.Fa dev ,
+resetting the device's PIN and erasing credentials stored on the
+device.
+.Pp
+Please note that
+.Fn fido_dev_set_pin ,
+.Fn fido_dev_get_retry_count ,
+.Fn fido_dev_get_uv_retry_count ,
+and
+.Fn fido_dev_reset
+are synchronous and will block if necessary.
+.Sh RETURN VALUES
+The error codes returned by
+.Fn fido_dev_set_pin ,
+.Fn fido_dev_get_retry_count ,
+.Fn fido_dev_get_uv_retry_count ,
+and
+.Fn fido_dev_reset
+are defined in
+.In fido/err.h .
+On success,
+.Dv FIDO_OK
+is returned.
+.Sh SEE ALSO
+.Xr fido_cbor_info_uv_attempts 3
+.Sh CAVEATS
+Regarding
+.Fn fido_dev_reset ,
+the actual user-flow to perform a reset is outside the scope of the
+FIDO2 specification, and may therefore vary depending on the
+authenticator.
+Yubico authenticators will return
+.Dv FIDO_ERR_NOT_ALLOWED
+if a reset is issued later than 5 seconds after power-up, and
+.Dv FIDO_ERR_ACTION_TIMEOUT
+if the user fails to confirm the reset by touching the key
+within 30 seconds.
diff --git a/man/fido_init.3 b/man/fido_init.3
new file mode 100644
index 0000000..12437e1
--- /dev/null
+++ b/man/fido_init.3
@@ -0,0 +1,95 @@
+.\" Copyright (c) 2018 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 25 2018 $
+.Dt FIDO_INIT 3
+.Os
+.Sh NAME
+.Nm fido_init ,
+.Nm fido_set_log_handler
+.Nd initialise the FIDO2 library
+.Sh SYNOPSIS
+.In fido.h
+.Bd -literal
+typedef void fido_log_handler_t(const char *);
+.Ed
+.Pp
+.Ft void
+.Fn fido_init "int flags"
+.Ft void
+.Fn fido_set_log_handler "fido_log_handler_t *handler"
+.Sh DESCRIPTION
+The
+.Fn fido_init
+function initialises the
+.Em libfido2
+library.
+Its invocation must precede that of any other
+.Em libfido2
+function in the context of the executing thread.
+.Pp
+If
+.Dv FIDO_DEBUG
+is set in
+.Fa flags ,
+then
+debug output will be emitted by
+.Em libfido2
+on
+.Em stderr .
+Alternatively, the
+.Ev FIDO_DEBUG
+environment variable may be set.
+.Pp
+If
+.Dv FIDO_DISABLE_U2F_FALLBACK
+is set in
+.Fa flags ,
+then
+.Em libfido2
+will not fallback to U2F in
+.Xr fido_dev_open 3
+if a device claims to support FIDO2 but fails to respond to
+a CTAP 2.0 greeting.
+.Pp
+The
+.Fn fido_set_log_handler
+function causes
+.Fa handler
+to be called for each log line generated in the context of the
+executing thread.
+Lines passed to
+.Fa handler
+include a trailing newline character and are not printed by
+.Em libfido2
+on
+.Em stderr .
+.Sh SEE ALSO
+.Xr fido_assert_new 3 ,
+.Xr fido_cred_new 3 ,
+.Xr fido_dev_info_manifest 3 ,
+.Xr fido_dev_open 3
diff --git a/man/fido_strerr.3 b/man/fido_strerr.3
new file mode 100644
index 0000000..94b48bd
--- /dev/null
+++ b/man/fido_strerr.3
@@ -0,0 +1,50 @@
+.\" Copyright (c) 2018 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: May 25 2018 $
+.Dt FIDO_STRERR 3
+.Os
+.Sh NAME
+.Nm fido_strerr
+.Nd FIDO2 error codes
+.Sh SYNOPSIS
+.In fido.h
+.Ft const char *
+.Fn fido_strerr "int n"
+.Sh DESCRIPTION
+The
+.Fn fido_strerr
+function translates the error code
+.Fa n
+into a readable string,
+where
+.Fa n
+is an error code defined in
+.In fido/err.h .
+.Fn fido_strerr
+never returns NULL.
+Returned pointers point to static strings.
diff --git a/man/rs256_pk_new.3 b/man/rs256_pk_new.3
new file mode 100644
index 0000000..0c0ab78
--- /dev/null
+++ b/man/rs256_pk_new.3
@@ -0,0 +1,160 @@
+.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+.\"
+.\" 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.
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd $Mdocdate: July 15 2022 $
+.Dt RS256_PK_NEW 3
+.Os
+.Sh NAME
+.Nm rs256_pk_new ,
+.Nm rs256_pk_free ,
+.Nm rs256_pk_from_RSA ,
+.Nm rs256_pk_from_EVP_PKEY ,
+.Nm rs256_pk_from_ptr ,
+.Nm rs256_pk_to_EVP_PKEY
+.Nd FIDO2 COSE RS256 API
+.Sh SYNOPSIS
+.In openssl/rsa.h
+.In fido/rs256.h
+.Ft rs256_pk_t *
+.Fn rs256_pk_new "void"
+.Ft void
+.Fn rs256_pk_free "rs256_pk_t **pkp"
+.Ft int
+.Fn rs256_pk_from_EVP_PKEY "rs256_pk_t *pk" "const EVP_PKEY *pkey"
+.Ft int
+.Fn rs256_pk_from_RSA "rs256_pk_t *pk" "const RSA *rsa"
+.Ft int
+.Fn rs256_pk_from_ptr "rs256_pk_t *pk" "const void *ptr" "size_t len"
+.Ft EVP_PKEY *
+.Fn rs256_pk_to_EVP_PKEY "const rs256_pk_t *pk"
+.Sh DESCRIPTION
+RS256 is the name given in the CBOR Object Signing and Encryption
+(COSE) RFC to PKCS#1.5 2048-bit RSA with SHA-256.
+The COSE RS256 API of
+.Em libfido2
+is an auxiliary API with routines to convert between the different
+RSA public key types used in
+.Em libfido2
+and
+.Em OpenSSL .
+.Pp
+In
+.Em libfido2 ,
+RS256 public keys are abstracted by the
+.Vt rs256_pk_t
+type.
+.Pp
+The
+.Fn rs256_pk_new
+function returns a pointer to a newly allocated, empty
+.Vt rs256_pk_t
+type.
+If memory cannot be allocated, NULL is returned.
+.Pp
+The
+.Fn rs256_pk_free
+function releases the memory backing
+.Fa *pkp ,
+where
+.Fa *pkp
+must have been previously allocated by
+.Fn rs256_pk_new .
+On return,
+.Fa *pkp
+is set to NULL.
+Either
+.Fa pkp
+or
+.Fa *pkp
+may be NULL, in which case
+.Fn rs256_pk_free
+is a NOP.
+.Pp
+The
+.Fn rs256_pk_from_EVP_PKEY
+function fills
+.Fa pk
+with the contents of
+.Fa pkey .
+No references to
+.Fa pkey
+are kept.
+.Pp
+The
+.Fn rs256_pk_from_RSA
+function fills
+.Fa pk
+with the contents of
+.Fa rsa .
+No references to
+.Fa rsa
+are kept.
+.Pp
+The
+.Fn rs256_pk_from_ptr
+function fills
+.Fa pk
+with the contents of
+.Fa ptr ,
+where
+.Fa ptr
+points to
+.Fa len
+bytes.
+No references to
+.Fa ptr
+are kept.
+.Pp
+The
+.Fn rs256_pk_to_EVP_PKEY
+function converts
+.Fa pk
+to a newly allocated
+.Fa EVP_PKEY
+type with a reference count of 1.
+No internal references to the returned pointer are kept.
+If an error occurs,
+.Fn rs256_pk_to_EVP_PKEY
+returns NULL.
+.Sh RETURN VALUES
+The
+.Fn rs256_pk_from_EVP_PKEY ,
+.Fn rs256_pk_from_RSA ,
+and
+.Fn rs256_pk_from_ptr
+functions return
+.Dv FIDO_OK
+on success.
+On error, a different error code defined in
+.In fido/err.h
+is returned.
+.Sh SEE ALSO
+.Xr eddsa_pk_new 3 ,
+.Xr es256_pk_new 3 ,
+.Xr es384_pk_new 3 ,
+.Xr fido_assert_verify 3 ,
+.Xr fido_cred_pubkey_ptr 3
diff --git a/man/style.css b/man/style.css
new file mode 100644
index 0000000..8c223fa
--- /dev/null
+++ b/man/style.css
@@ -0,0 +1,24 @@
+* { margin: 0; padding: 0; }
+
+body {
+ font-family: monospace;
+ font-size: 1em;
+ margin: 2% auto;
+ max-width: 54em;
+}
+
+ul { margin-left: 1em; }
+a { color: #009900; }
+.Sh { font-size: 1em; padding-top: 1em; padding-bottom: 1em; }
+.foot { padding-top: 1em; }
+
+table.head, table.foot { width: 100%; }
+td.head-rtitle, td.foot-os { text-align: right; }
+td.head-vol { text-align: center; }
+div.Pp { margin: 1ex 0ex; }
+div.Nd, div.Bf, div.Op { display: inline; }
+span.Pa, span.Ad { font-style: italic; }
+span.Ms { font-weight: bold; }
+dl.Bl-diag > dt { font-weight: bold; }
+code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
+code.Cd { font-weight: bold; font-family: inherit; }
diff --git a/openbsd-compat/bsd-asprintf.c b/openbsd-compat/bsd-asprintf.c
new file mode 100644
index 0000000..fbcb867
--- /dev/null
+++ b/openbsd-compat/bsd-asprintf.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2004 Darren Tucker.
+ *
+ * Based originally on asprintf.c from OpenBSD:
+ * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#ifndef HAVE_ASPRINTF
+
+#include <errno.h>
+#include <limits.h> /* for INT_MAX */
+#include <stdarg.h>
+#include <stdio.h> /* for vsnprintf */
+#include <stdlib.h>
+
+#define VA_COPY(dest, src) va_copy(dest, src)
+
+#define INIT_SZ 128
+
+int
+vasprintf(char **str, const char *fmt, va_list ap)
+{
+ int ret;
+ va_list ap2;
+ char *string, *newstr;
+ size_t len;
+
+ if ((string = malloc(INIT_SZ)) == NULL)
+ goto fail;
+
+ VA_COPY(ap2, ap);
+ ret = vsnprintf(string, INIT_SZ, fmt, ap2);
+ va_end(ap2);
+ if (ret >= 0 && ret < INIT_SZ) { /* succeeded with initial alloc */
+ *str = string;
+ } else if (ret == INT_MAX || ret < 0) { /* Bad length */
+ free(string);
+ goto fail;
+ } else { /* bigger than initial, realloc allowing for nul */
+ len = (size_t)ret + 1;
+ if ((newstr = realloc(string, len)) == NULL) {
+ free(string);
+ goto fail;
+ }
+ VA_COPY(ap2, ap);
+ ret = vsnprintf(newstr, len, fmt, ap2);
+ va_end(ap2);
+ if (ret < 0 || (size_t)ret >= len) { /* failed with realloc'ed string */
+ free(newstr);
+ goto fail;
+ }
+ *str = newstr;
+ }
+ return (ret);
+
+fail:
+ *str = NULL;
+ errno = ENOMEM;
+ return (-1);
+}
+
+int asprintf(char **str, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ *str = NULL;
+ va_start(ap, fmt);
+ ret = vasprintf(str, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+#endif
diff --git a/openbsd-compat/bsd-getline.c b/openbsd-compat/bsd-getline.c
new file mode 100644
index 0000000..52b44f7
--- /dev/null
+++ b/openbsd-compat/bsd-getline.c
@@ -0,0 +1,115 @@
+/* $NetBSD: getline.c,v 1.1.1.6 2015/01/02 20:34:27 christos Exp $ */
+
+/* NetBSD: getline.c,v 1.2 2014/09/16 17:23:50 christos Exp */
+
+/*-
+ * Copyright (c) 2011 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Christos Zoulas.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/* NETBSD ORIGINAL: external/bsd/file/dist/src/getline.c */
+
+#include "openbsd-compat.h"
+
+#if 0
+#include "file.h"
+#endif
+
+#if !HAVE_GETLINE
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+static ssize_t
+getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp)
+{
+ char *ptr, *eptr;
+
+
+ if (*buf == NULL || *bufsiz == 0) {
+ if ((*buf = malloc(BUFSIZ)) == NULL)
+ return -1;
+ *bufsiz = BUFSIZ;
+ }
+
+ for (ptr = *buf, eptr = *buf + *bufsiz;;) {
+ int c = fgetc(fp);
+ if (c == -1) {
+ if (feof(fp)) {
+ ssize_t diff = (ssize_t)(ptr - *buf);
+ if (diff != 0) {
+ *ptr = '\0';
+ return diff;
+ }
+ }
+ return -1;
+ }
+ *ptr++ = (char)c;
+ if (c == delimiter) {
+ *ptr = '\0';
+ return ptr - *buf;
+ }
+ if (ptr + 2 >= eptr) {
+ char *nbuf;
+ size_t nbufsiz = *bufsiz * 2;
+ ssize_t d = ptr - *buf;
+ if ((nbuf = realloc(*buf, nbufsiz)) == NULL)
+ return -1;
+ *buf = nbuf;
+ *bufsiz = nbufsiz;
+ eptr = nbuf + nbufsiz;
+ ptr = nbuf + d;
+ }
+ }
+}
+
+ssize_t
+getline(char **buf, size_t *bufsiz, FILE *fp)
+{
+ return getdelim(buf, bufsiz, '\n', fp);
+}
+
+#endif
+
+#ifdef TEST
+int
+main(int argc, char *argv[])
+{
+ char *p = NULL;
+ ssize_t len;
+ size_t n = 0;
+
+ while ((len = getline(&p, &n, stdin)) != -1)
+ (void)printf("%" SIZE_T_FORMAT "d %s", len, p);
+ free(p);
+ return 0;
+}
+#endif
diff --git a/openbsd-compat/bsd-getpagesize.c b/openbsd-compat/bsd-getpagesize.c
new file mode 100644
index 0000000..903bfc3
--- /dev/null
+++ b/openbsd-compat/bsd-getpagesize.c
@@ -0,0 +1,27 @@
+/* Placed in the public domain */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_GETPAGESIZE)
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <limits.h>
+
+int
+getpagesize(void)
+{
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+ long r = sysconf(_SC_PAGESIZE);
+ if (r > 0 && r < INT_MAX)
+ return (int)r;
+#endif
+ /*
+ * This is at the lower end of common values and appropriate for
+ * our current use of getpagesize() in recallocarray().
+ */
+ return 4096;
+}
+
+#endif /* !defined(HAVE_GETPAGESIZE) */
diff --git a/openbsd-compat/clock_gettime.c b/openbsd-compat/clock_gettime.c
new file mode 100644
index 0000000..bbf978c
--- /dev/null
+++ b/openbsd-compat/clock_gettime.c
@@ -0,0 +1,33 @@
+/*
+ * 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 "openbsd-compat.h"
+
+#if !defined(HAVE_CLOCK_GETTIME)
+
+#if _WIN32
+int
+clock_gettime(clockid_t clock_id, struct timespec *tp)
+{
+ ULONGLONG ms;
+
+ if (clock_id != CLOCK_MONOTONIC) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ ms = GetTickCount64();
+ tp->tv_sec = ms / 1000L;
+ tp->tv_nsec = (ms % 1000L) * 1000000L;
+
+ return (0);
+}
+#else
+#error "please provide an implementation of clock_gettime() for your platform"
+#endif /* _WIN32 */
+
+#endif /* !defined(HAVE_CLOCK_GETTIME) */
diff --git a/openbsd-compat/endian_win32.c b/openbsd-compat/endian_win32.c
new file mode 100644
index 0000000..756c0cb
--- /dev/null
+++ b/openbsd-compat/endian_win32.c
@@ -0,0 +1,52 @@
+/*
+ * 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 "openbsd-compat.h"
+
+#if defined(_WIN32) && !defined(HAVE_ENDIAN_H)
+
+/*
+ * Hopefully, if the endianness differs from the end result, the compiler
+ * optimizes these functions with some type of bswap instruction. Or,
+ * otherwise, to just return the input value unmodified. GCC and clang
+ * both does these optimization at least. This should be preferred over
+ * relying on some BYTE_ORDER macro, which may or may not be defined.
+ */
+
+uint32_t
+htole32(uint32_t in)
+{
+ uint32_t out = 0;
+ uint8_t *b = (uint8_t *)&out;
+
+ b[0] = (uint8_t)((in >> 0) & 0xff);
+ b[1] = (uint8_t)((in >> 8) & 0xff);
+ b[2] = (uint8_t)((in >> 16) & 0xff);
+ b[3] = (uint8_t)((in >> 24) & 0xff);
+
+ return (out);
+}
+
+uint64_t
+htole64(uint64_t in)
+{
+ uint64_t out = 0;
+ uint8_t *b = (uint8_t *)&out;
+
+ b[0] = (uint8_t)((in >> 0) & 0xff);
+ b[1] = (uint8_t)((in >> 8) & 0xff);
+ b[2] = (uint8_t)((in >> 16) & 0xff);
+ b[3] = (uint8_t)((in >> 24) & 0xff);
+ b[4] = (uint8_t)((in >> 32) & 0xff);
+ b[5] = (uint8_t)((in >> 40) & 0xff);
+ b[6] = (uint8_t)((in >> 48) & 0xff);
+ b[7] = (uint8_t)((in >> 56) & 0xff);
+
+ return (out);
+}
+
+#endif /* WIN32 && !HAVE_ENDIAN_H */
diff --git a/openbsd-compat/err.h b/openbsd-compat/err.h
new file mode 100644
index 0000000..394c7bb
--- /dev/null
+++ b/openbsd-compat/err.h
@@ -0,0 +1,85 @@
+/*
+ * Public domain
+ * err.h compatibility shim
+ */
+
+#ifndef _COMPAT_ERR_H
+#define _COMPAT_ERR_H
+
+#if !defined(HAVE_ERR_H)
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#if defined(_MSC_VER)
+__declspec(noreturn)
+#else
+__attribute__((noreturn))
+#endif
+static inline void
+err(int eval, const char *fmt, ...)
+{
+ int sverrno = errno;
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (fmt != NULL) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, ": ");
+ }
+ va_end(ap);
+ fprintf(stderr, "%s\n", strerror(sverrno));
+ exit(eval);
+}
+
+#if defined(_MSC_VER)
+__declspec(noreturn)
+#else
+__attribute__((noreturn))
+#endif
+static inline void
+errx(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (fmt != NULL)
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+ exit(eval);
+}
+
+static inline void
+warn(const char *fmt, ...)
+{
+ int sverrno = errno;
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (fmt != NULL) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, ": ");
+ }
+ va_end(ap);
+ fprintf(stderr, "%s\n", strerror(sverrno));
+}
+
+static inline void
+warnx(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (fmt != NULL)
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
+
+#endif /* !defined(HAVE_ERR_H) */
+
+#endif /* _COMPAT_ERR_H */
diff --git a/openbsd-compat/explicit_bzero.c b/openbsd-compat/explicit_bzero.c
new file mode 100644
index 0000000..ac64e69
--- /dev/null
+++ b/openbsd-compat/explicit_bzero.c
@@ -0,0 +1,57 @@
+/* OPENBSD ORIGINAL: lib/libc/string/explicit_bzero.c */
+/* $OpenBSD: explicit_bzero.c,v 1.1 2014/01/22 21:06:45 tedu Exp $ */
+/*
+ * Public domain.
+ * Written by Ted Unangst
+ */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_EXPLICIT_BZERO) && !defined(_WIN32)
+
+#include <string.h>
+
+/*
+ * explicit_bzero - don't let the compiler optimize away bzero
+ */
+
+#ifdef HAVE_MEMSET_S
+
+void
+explicit_bzero(void *p, size_t n)
+{
+ if (n == 0)
+ return;
+ (void)memset_s(p, n, 0, n);
+}
+
+#else /* HAVE_MEMSET_S */
+
+/*
+ * Indirect bzero through a volatile pointer to hopefully avoid
+ * dead-store optimisation eliminating the call.
+ */
+static void (* volatile ssh_bzero)(void *, size_t) = bzero;
+
+void
+explicit_bzero(void *p, size_t n)
+{
+ if (n == 0)
+ return;
+ /*
+ * clang -fsanitize=memory needs to intercept memset-like functions
+ * to correctly detect memory initialisation. Make sure one is called
+ * directly since our indirection trick above successfully confuses it.
+ */
+#if defined(__has_feature)
+# if __has_feature(memory_sanitizer)
+ memset(p, 0, n);
+# endif
+#endif
+
+ ssh_bzero(p, n);
+}
+
+#endif /* HAVE_MEMSET_S */
+
+#endif /* !defined(HAVE_EXPLICIT_BZERO) && !defined(_WIN32) */
diff --git a/openbsd-compat/explicit_bzero_win32.c b/openbsd-compat/explicit_bzero_win32.c
new file mode 100644
index 0000000..8017aff
--- /dev/null
+++ b/openbsd-compat/explicit_bzero_win32.c
@@ -0,0 +1,19 @@
+/*
+ * Public domain.
+ * Win32 explicit_bzero compatibility shim.
+ */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_EXPLICIT_BZERO) && defined(_WIN32)
+
+#include <windows.h>
+#include <string.h>
+
+void
+explicit_bzero(void *buf, size_t len)
+{
+ SecureZeroMemory(buf, len);
+}
+
+#endif /* !defined(HAVE_EXPLICIT_BZERO) && defined(_WIN32) */
diff --git a/openbsd-compat/freezero.c b/openbsd-compat/freezero.c
new file mode 100644
index 0000000..d1e0066
--- /dev/null
+++ b/openbsd-compat/freezero.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "openbsd-compat.h"
+
+#ifndef HAVE_FREEZERO
+
+void
+freezero(void *ptr, size_t sz)
+{
+ if (ptr == NULL)
+ return;
+ explicit_bzero(ptr, sz);
+ free(ptr);
+}
+
+#endif /* HAVE_FREEZERO */
diff --git a/openbsd-compat/getopt.h b/openbsd-compat/getopt.h
new file mode 100644
index 0000000..8eb1244
--- /dev/null
+++ b/openbsd-compat/getopt.h
@@ -0,0 +1,74 @@
+/* $OpenBSD: getopt.h,v 1.2 2008/06/26 05:42:04 ray Exp $ */
+/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 _GETOPT_H_
+#define _GETOPT_H_
+
+/*
+ * GNU-like getopt_long() and 4.4BSD getsubopt()/optreset extensions
+ */
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+struct option {
+ /* name of long option */
+ const char *name;
+ /*
+ * one of no_argument, required_argument, and optional_argument:
+ * whether option takes an argument
+ */
+ int has_arg;
+ /* if not NULL, set *flag to val when option found */
+ int *flag;
+ /* if flag not NULL, value to set *flag to; else return value */
+ int val;
+};
+
+int getopt_long(int, char * const *, const char *,
+ const struct option *, int *);
+int getopt_long_only(int, char * const *, const char *,
+ const struct option *, int *);
+#ifndef _GETOPT_DEFINED_
+#define _GETOPT_DEFINED_
+int getopt(int, char * const *, const char *);
+int getsubopt(char **, char * const *, char **);
+
+extern char *optarg; /* getopt(3) external variables */
+extern int opterr;
+extern int optind;
+extern int optopt;
+extern int optreset;
+extern char *suboptarg; /* getsubopt(3) external variable */
+#endif
+
+#endif /* !_GETOPT_H_ */
diff --git a/openbsd-compat/getopt_long.c b/openbsd-compat/getopt_long.c
new file mode 100644
index 0000000..dabbb46
--- /dev/null
+++ b/openbsd-compat/getopt_long.c
@@ -0,0 +1,523 @@
+/* $OpenBSD: getopt_long.c,v 1.25 2011/03/05 22:10:11 guenther Exp $ */
+/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */
+
+/*
+ * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Dieter Baron and Thomas Klausner.
+ *
+ * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/stdlib/getopt_long.c */
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_GETOPT)
+
+#if 0
+#include <err.h>
+#include <getopt.h>
+#endif
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+int opterr = 1; /* if error message should be printed */
+int optind = 1; /* index into parent argv vector */
+int optopt = '?'; /* character checked for validity */
+int optreset; /* reset getopt */
+char *optarg; /* argument associated with option */
+
+#define PRINT_ERROR ((opterr) && (*options != ':'))
+
+#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
+#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
+#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
+
+/* return values */
+#define BADCH (int)'?'
+#define BADARG ((*options == ':') ? (int)':' : (int)'?')
+#define INORDER (int)1
+
+#define EMSG ""
+
+static int getopt_internal(int, char * const *, const char *,
+ const struct option *, int *, int);
+static int parse_long_options(char * const *, const char *,
+ const struct option *, int *, int);
+static int gcd(int, int);
+static void permute_args(int, int, int, char * const *);
+
+static char *place = EMSG; /* option letter processing */
+
+/* XXX: set optreset to 1 rather than these two */
+static int nonopt_start = -1; /* first non option argument (for permute) */
+static int nonopt_end = -1; /* first option after non options (for permute) */
+
+/* Error messages */
+static const char recargchar[] = "option requires an argument -- %c";
+static const char recargstring[] = "option requires an argument -- %s";
+static const char ambig[] = "ambiguous option -- %.*s";
+static const char noarg[] = "option doesn't take an argument -- %.*s";
+static const char illoptchar[] = "unknown option -- %c";
+static const char illoptstring[] = "unknown option -- %s";
+
+/*
+ * Compute the greatest common divisor of a and b.
+ */
+static int
+gcd(int a, int b)
+{
+ int c;
+
+ c = a % b;
+ while (c != 0) {
+ a = b;
+ b = c;
+ c = a % b;
+ }
+
+ return (b);
+}
+
+/*
+ * Exchange the block from nonopt_start to nonopt_end with the block
+ * from nonopt_end to opt_end (keeping the same order of arguments
+ * in each block).
+ */
+static void
+permute_args(int panonopt_start, int panonopt_end, int opt_end,
+ char * const *nargv)
+{
+ int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
+ char *swap;
+
+ /*
+ * compute lengths of blocks and number and size of cycles
+ */
+ nnonopts = panonopt_end - panonopt_start;
+ nopts = opt_end - panonopt_end;
+ ncycle = gcd(nnonopts, nopts);
+ cyclelen = (opt_end - panonopt_start) / ncycle;
+
+ for (i = 0; i < ncycle; i++) {
+ cstart = panonopt_end+i;
+ pos = cstart;
+ for (j = 0; j < cyclelen; j++) {
+ if (pos >= panonopt_end)
+ pos -= nnonopts;
+ else
+ pos += nopts;
+ swap = nargv[pos];
+ /* LINTED const cast */
+ ((char **) nargv)[pos] = nargv[cstart];
+ /* LINTED const cast */
+ ((char **)nargv)[cstart] = swap;
+ }
+ }
+}
+
+/*
+ * parse_long_options --
+ * Parse long options in argc/argv argument vector.
+ * Returns -1 if short_too is set and the option does not match long_options.
+ */
+static int
+parse_long_options(char * const *nargv, const char *options,
+ const struct option *long_options, int *idx, int short_too)
+{
+ char *current_argv, *has_equal;
+ size_t current_argv_len;
+ int i, match;
+
+ current_argv = place;
+ match = -1;
+
+ optind++;
+
+ if ((has_equal = strchr(current_argv, '=')) != NULL) {
+ /* argument found (--option=arg) */
+ current_argv_len = has_equal - current_argv;
+ has_equal++;
+ } else
+ current_argv_len = strlen(current_argv);
+
+ for (i = 0; long_options[i].name; i++) {
+ /* find matching long option */
+ if (strncmp(current_argv, long_options[i].name,
+ current_argv_len))
+ continue;
+
+ if (strlen(long_options[i].name) == current_argv_len) {
+ /* exact match */
+ match = i;
+ break;
+ }
+ /*
+ * If this is a known short option, don't allow
+ * a partial match of a single character.
+ */
+ if (short_too && current_argv_len == 1)
+ continue;
+
+ if (match == -1) /* partial match */
+ match = i;
+ else {
+ /* ambiguous abbreviation */
+ if (PRINT_ERROR)
+ warnx(ambig, (int)current_argv_len,
+ current_argv);
+ optopt = 0;
+ return (BADCH);
+ }
+ }
+ if (match != -1) { /* option found */
+ if (long_options[match].has_arg == no_argument
+ && has_equal) {
+ if (PRINT_ERROR)
+ warnx(noarg, (int)current_argv_len,
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ return (BADARG);
+ }
+ if (long_options[match].has_arg == required_argument ||
+ long_options[match].has_arg == optional_argument) {
+ if (has_equal)
+ optarg = has_equal;
+ else if (long_options[match].has_arg ==
+ required_argument) {
+ /*
+ * optional argument doesn't use next nargv
+ */
+ optarg = nargv[optind++];
+ }
+ }
+ if ((long_options[match].has_arg == required_argument)
+ && (optarg == NULL)) {
+ /*
+ * Missing argument; leading ':' indicates no error
+ * should be generated.
+ */
+ if (PRINT_ERROR)
+ warnx(recargstring,
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ --optind;
+ return (BADARG);
+ }
+ } else { /* unknown option */
+ if (short_too) {
+ --optind;
+ return (-1);
+ }
+ if (PRINT_ERROR)
+ warnx(illoptstring, current_argv);
+ optopt = 0;
+ return (BADCH);
+ }
+ if (idx)
+ *idx = match;
+ if (long_options[match].flag) {
+ *long_options[match].flag = long_options[match].val;
+ return (0);
+ } else
+ return (long_options[match].val);
+}
+
+/*
+ * getopt_internal --
+ * Parse argc/argv argument vector. Called by user level routines.
+ */
+static int
+getopt_internal(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx, int flags)
+{
+ char *oli; /* option letter list index */
+ int optchar, short_too;
+ static int posixly_correct = -1;
+
+ if (options == NULL)
+ return (-1);
+
+ /*
+ * XXX Some GNU programs (like cvs) set optind to 0 instead of
+ * XXX using optreset. Work around this braindamage.
+ */
+ if (optind == 0)
+ optind = optreset = 1;
+
+ /*
+ * Disable GNU extensions if POSIXLY_CORRECT is set or options
+ * string begins with a '+'.
+ */
+ if (posixly_correct == -1 || optreset)
+ posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
+ if (*options == '-')
+ flags |= FLAG_ALLARGS;
+ else if (posixly_correct || *options == '+')
+ flags &= ~FLAG_PERMUTE;
+ if (*options == '+' || *options == '-')
+ options++;
+
+ optarg = NULL;
+ if (optreset)
+ nonopt_start = nonopt_end = -1;
+start:
+ if (optreset || !*place) { /* update scanning pointer */
+ optreset = 0;
+ if (optind >= nargc) { /* end of argument vector */
+ place = EMSG;
+ if (nonopt_end != -1) {
+ /* do permutation, if we have to */
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ else if (nonopt_start != -1) {
+ /*
+ * If we skipped non-options, set optind
+ * to the first of them.
+ */
+ optind = nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return (-1);
+ }
+ if (*(place = nargv[optind]) != '-' ||
+ (place[1] == '\0' && strchr(options, '-') == NULL)) {
+ place = EMSG; /* found non-option */
+ if (flags & FLAG_ALLARGS) {
+ /*
+ * GNU extension:
+ * return non-option as argument to option 1
+ */
+ optarg = nargv[optind++];
+ return (INORDER);
+ }
+ if (!(flags & FLAG_PERMUTE)) {
+ /*
+ * If no permutation wanted, stop parsing
+ * at first non-option.
+ */
+ return (-1);
+ }
+ /* do permutation */
+ if (nonopt_start == -1)
+ nonopt_start = optind;
+ else if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ nonopt_start = optind -
+ (nonopt_end - nonopt_start);
+ nonopt_end = -1;
+ }
+ optind++;
+ /* process next argument */
+ goto start;
+ }
+ if (nonopt_start != -1 && nonopt_end == -1)
+ nonopt_end = optind;
+
+ /*
+ * If we have "-" do nothing, if "--" we are done.
+ */
+ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
+ optind++;
+ place = EMSG;
+ /*
+ * We found an option (--), so if we skipped
+ * non-options, we have to permute.
+ */
+ if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return (-1);
+ }
+ }
+
+ /*
+ * Check long options if:
+ * 1) we were passed some
+ * 2) the arg is not just "-"
+ * 3) either the arg starts with -- we are getopt_long_only()
+ */
+ if (long_options != NULL && place != nargv[optind] &&
+ (*place == '-' || (flags & FLAG_LONGONLY))) {
+ short_too = 0;
+ if (*place == '-')
+ place++; /* --foo long option */
+ else if (*place != ':' && strchr(options, *place) != NULL)
+ short_too = 1; /* could be short option too */
+
+ optchar = parse_long_options(nargv, options, long_options,
+ idx, short_too);
+ if (optchar != -1) {
+ place = EMSG;
+ return (optchar);
+ }
+ }
+
+ if ((optchar = (int)*place++) == (int)':' ||
+ (optchar == (int)'-' && *place != '\0') ||
+ (oli = strchr(options, optchar)) == NULL) {
+ /*
+ * If the user specified "-" and '-' isn't listed in
+ * options, return -1 (non-option) as per POSIX.
+ * Otherwise, it is an unknown option character (or ':').
+ */
+ if (optchar == (int)'-' && *place == '\0')
+ return (-1);
+ if (!*place)
+ ++optind;
+ if (PRINT_ERROR)
+ warnx(illoptchar, optchar);
+ optopt = optchar;
+ return (BADCH);
+ }
+ if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
+ /* -W long-option */
+ if (*place) /* no space */
+ /* NOTHING */;
+ else if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ warnx(recargchar, optchar);
+ optopt = optchar;
+ return (BADARG);
+ } else /* white space */
+ place = nargv[optind];
+ optchar = parse_long_options(nargv, options, long_options,
+ idx, 0);
+ place = EMSG;
+ return (optchar);
+ }
+ if (*++oli != ':') { /* doesn't take argument */
+ if (!*place)
+ ++optind;
+ } else { /* takes (optional) argument */
+ optarg = NULL;
+ if (*place) /* no white space */
+ optarg = place;
+ else if (oli[1] != ':') { /* arg not optional */
+ if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ warnx(recargchar, optchar);
+ optopt = optchar;
+ return (BADARG);
+ } else
+ optarg = nargv[optind];
+ }
+ place = EMSG;
+ ++optind;
+ }
+ /* dump back option letter */
+ return (optchar);
+}
+
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ *
+ * [eventually this will replace the BSD getopt]
+ */
+int
+getopt(int nargc, char * const *nargv, const char *options)
+{
+
+ /*
+ * We don't pass FLAG_PERMUTE to getopt_internal() since
+ * the BSD getopt(3) (unlike GNU) has never done this.
+ *
+ * Furthermore, since many privileged programs call getopt()
+ * before dropping privileges it makes sense to keep things
+ * as simple (and bug-free) as possible.
+ */
+ return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
+}
+
+#if 0
+/*
+ * getopt_long --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_long(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx)
+{
+
+ return (getopt_internal(nargc, nargv, options, long_options, idx,
+ FLAG_PERMUTE));
+}
+
+/*
+ * getopt_long_only --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_long_only(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx)
+{
+
+ return (getopt_internal(nargc, nargv, options, long_options, idx,
+ FLAG_PERMUTE|FLAG_LONGONLY));
+}
+#endif
+
+#endif /* !defined(HAVE_GETOPT) */
diff --git a/openbsd-compat/openbsd-compat.h b/openbsd-compat/openbsd-compat.h
new file mode 100644
index 0000000..9f1ea3e
--- /dev/null
+++ b/openbsd-compat/openbsd-compat.h
@@ -0,0 +1,123 @@
+/*
+ * 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
+ */
+
+#ifndef _OPENBSD_COMPAT_H
+#define _OPENBSD_COMPAT_H
+
+#if defined(_MSC_VER)
+#include "types.h"
+#endif
+
+#if defined(HAVE_ENDIAN_H)
+#include <endian.h>
+#endif
+
+#if defined(__APPLE__) && !defined(HAVE_ENDIAN_H)
+#include <libkern/OSByteOrder.h>
+#define be16toh(x) OSSwapBigToHostInt16((x))
+#define htobe16(x) OSSwapHostToBigInt16((x))
+#define be32toh(x) OSSwapBigToHostInt32((x))
+#define htobe32(x) OSSwapHostToBigInt32((x))
+#define htole32(x) OSSwapHostToLittleInt32((x))
+#define htole64(x) OSSwapHostToLittleInt64((x))
+#endif /* __APPLE__ && !HAVE_ENDIAN_H */
+
+#if defined(_WIN32) && !defined(HAVE_ENDIAN_H)
+#include <stdint.h>
+#include <winsock2.h>
+#if !defined(_MSC_VER)
+#include <sys/param.h>
+#endif
+#define be16toh(x) ntohs((x))
+#define htobe16(x) htons((x))
+#define be32toh(x) ntohl((x))
+#define htobe32(x) htonl((x))
+uint32_t htole32(uint32_t);
+uint64_t htole64(uint64_t);
+#endif /* _WIN32 && !HAVE_ENDIAN_H */
+
+#if (defined(__FreeBSD__) || defined(__MidnightBSD__)) && !defined(HAVE_ENDIAN_H)
+#include <sys/endian.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#if !defined(HAVE_STRLCAT)
+size_t strlcat(char *, const char *, size_t);
+#endif
+
+#if !defined(HAVE_STRLCPY)
+size_t strlcpy(char *, const char *, size_t);
+#endif
+
+#if !defined(HAVE_STRSEP)
+char *strsep(char **, const char *);
+#endif
+
+#if !defined(HAVE_RECALLOCARRAY)
+void *recallocarray(void *, size_t, size_t, size_t);
+#endif
+
+#if !defined(HAVE_EXPLICIT_BZERO)
+void explicit_bzero(void *, size_t);
+#endif
+
+#if !defined(HAVE_FREEZERO)
+void freezero(void *, size_t);
+#endif
+
+#if !defined(HAVE_GETPAGESIZE)
+int getpagesize(void);
+#endif
+
+#if !defined(HAVE_TIMINGSAFE_BCMP)
+int timingsafe_bcmp(const void *, const void *, size_t);
+#endif
+
+#if !defined(HAVE_READPASSPHRASE)
+#include "readpassphrase.h"
+#else
+#include <readpassphrase.h>
+#endif
+
+#include <openssl/opensslv.h>
+
+#if !defined(HAVE_ERR_H)
+#include "err.h"
+#else
+#include <err.h>
+#endif
+
+#if !defined(HAVE_GETOPT)
+#include "getopt.h"
+#else
+#include <unistd.h>
+#endif
+
+#if !defined(HAVE_GETLINE)
+#include <stdio.h>
+ssize_t getline(char **, size_t *, FILE *);
+#endif
+
+#if defined(_MSC_VER)
+#define strerror_r(e, b, l) strerror_s((b), (l), (e))
+#endif
+
+#include "time.h"
+
+#if !defined(HAVE_POSIX_IOCTL)
+#define IOCTL_REQ(x) (x)
+#else
+#define IOCTL_REQ(x) ((int)(x))
+#endif
+
+#if !defined(HAVE_ASPRINTF)
+int asprintf(char **, const char *, ...);
+#endif
+
+#endif /* !_OPENBSD_COMPAT_H */
diff --git a/openbsd-compat/posix_ioctl_check.c b/openbsd-compat/posix_ioctl_check.c
new file mode 100644
index 0000000..599a3bf
--- /dev/null
+++ b/openbsd-compat/posix_ioctl_check.c
@@ -0,0 +1,7 @@
+#include <sys/ioctl.h>
+
+int
+posix_ioctl_check(int fd)
+{
+ return ioctl(fd, -1, 0);
+}
diff --git a/openbsd-compat/posix_win.c b/openbsd-compat/posix_win.c
new file mode 100644
index 0000000..eac67c2
--- /dev/null
+++ b/openbsd-compat/posix_win.c
@@ -0,0 +1,61 @@
+/*
+ * Public domain
+ *
+ * File IO compatibility shims
+ * Brent Cook <bcook@openbsd.org>
+ */
+
+#define NO_REDEF_POSIX_FUNCTIONS
+
+#include <windows.h>
+
+#include <errno.h>
+#include <io.h>
+
+#include "posix_win.h"
+
+int
+posix_open(const char *path, ...)
+{
+ va_list ap;
+ int mode = 0;
+ int flags;
+
+ va_start(ap, path);
+ flags = va_arg(ap, int);
+ if (flags & O_CREAT)
+ mode = va_arg(ap, int);
+ va_end(ap);
+
+ flags |= O_BINARY | O_NOINHERIT;
+
+ return (open(path, flags, mode));
+}
+
+int
+posix_close(int fd)
+{
+ return (close(fd));
+}
+
+ssize_t
+posix_read(int fd, void *buf, size_t count)
+{
+ if (count > INT_MAX) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ return (read(fd, buf, (unsigned int)count));
+}
+
+ssize_t
+posix_write(int fd, const void *buf, size_t count)
+{
+ if (count > INT_MAX) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ return (write(fd, buf, (unsigned int)count));
+}
diff --git a/openbsd-compat/posix_win.h b/openbsd-compat/posix_win.h
new file mode 100644
index 0000000..a1e0888
--- /dev/null
+++ b/openbsd-compat/posix_win.h
@@ -0,0 +1,47 @@
+/*
+ * Public domain
+ *
+ * BSD socket emulation code for Winsock2
+ * Brent Cook <bcook@openbsd.org>
+ */
+
+#ifndef _COMPAT_POSIX_WIN_H
+#define _COMPAT_POSIX_WIN_H
+
+#ifdef _WIN32
+
+#include <windows.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if _MSC_VER >= 1900
+#include <../ucrt/fcntl.h>
+#else
+#include <../include/fcntl.h>
+#endif
+
+#include "types.h"
+
+int posix_open(const char *path, ...);
+
+int posix_close(int fd);
+
+ssize_t posix_read(int fd, void *buf, size_t count);
+
+ssize_t posix_write(int fd, const void *buf, size_t count);
+
+#ifndef NO_REDEF_POSIX_FUNCTIONS
+#define open(path, ...) posix_open(path, __VA_ARGS__)
+#define close(fd) posix_close(fd)
+#define read(fd, buf, count) posix_read(fd, buf, count)
+#define write(fd, buf, count) posix_write(fd, buf, count)
+#endif
+
+#endif /* _WIN32 */
+
+#endif /* !_COMPAT_POSIX_WIN_H */
diff --git a/openbsd-compat/readpassphrase.c b/openbsd-compat/readpassphrase.c
new file mode 100644
index 0000000..8b84190
--- /dev/null
+++ b/openbsd-compat/readpassphrase.c
@@ -0,0 +1,214 @@
+/* $OpenBSD: readpassphrase.c,v 1.26 2016/10/18 12:47:18 millert Exp $ */
+
+/*
+ * Copyright (c) 2000-2002, 2007, 2010
+ * Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/gen/readpassphrase.c */
+
+#include "openbsd-compat.h"
+
+#ifndef HAVE_READPASSPHRASE
+
+#include <termios.h>
+#include <signal.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <paths.h>
+
+#ifndef _PATH_TTY
+# define _PATH_TTY "/dev/tty"
+#endif
+
+#ifndef TCSASOFT
+/* If we don't have TCSASOFT define it so that ORing it it below is a no-op. */
+# define TCSASOFT 0
+#endif
+
+/* SunOS 4.x which lacks _POSIX_VDISABLE, but has VDISABLE */
+#if !defined(_POSIX_VDISABLE) && defined(VDISABLE)
+# define _POSIX_VDISABLE VDISABLE
+#endif
+
+static volatile sig_atomic_t signo[NSIG];
+
+static void handler(int);
+
+char *
+readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags)
+{
+ ssize_t nr;
+ int input, output, save_errno, i, need_restart;
+ char ch, *p, *end;
+ struct termios term, oterm;
+ struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm;
+ struct sigaction savetstp, savettin, savettou, savepipe;
+
+ /* I suppose we could alloc on demand in this case (XXX). */
+ if (bufsiz == 0) {
+ errno = EINVAL;
+ return(NULL);
+ }
+
+restart:
+ for (i = 0; i < NSIG; i++)
+ signo[i] = 0;
+ need_restart = 0;
+ /*
+ * Read and write to /dev/tty if available. If not, read from
+ * stdin and write to stderr unless a tty is required.
+ */
+ if ((flags & RPP_STDIN) ||
+ (input = output = open(_PATH_TTY, O_RDWR)) == -1) {
+ if (flags & RPP_REQUIRE_TTY) {
+ errno = ENOTTY;
+ return(NULL);
+ }
+ input = STDIN_FILENO;
+ output = STDERR_FILENO;
+ }
+
+ /*
+ * Turn off echo if possible.
+ * If we are using a tty but are not the foreground pgrp this will
+ * generate SIGTTOU, so do it *before* installing the signal handlers.
+ */
+ if (input != STDIN_FILENO && tcgetattr(input, &oterm) == 0) {
+ memcpy(&term, &oterm, sizeof(term));
+ if (!(flags & RPP_ECHO_ON))
+ term.c_lflag &= ~(ECHO | ECHONL);
+#ifdef VSTATUS
+ if (term.c_cc[VSTATUS] != _POSIX_VDISABLE)
+ term.c_cc[VSTATUS] = _POSIX_VDISABLE;
+#endif
+ (void)tcsetattr(input, TCSAFLUSH|TCSASOFT, &term);
+ } else {
+ memset(&term, 0, sizeof(term));
+ term.c_lflag |= ECHO;
+ memset(&oterm, 0, sizeof(oterm));
+ oterm.c_lflag |= ECHO;
+ }
+
+ /*
+ * Catch signals that would otherwise cause the user to end
+ * up with echo turned off in the shell. Don't worry about
+ * things like SIGXCPU and SIGVTALRM for now.
+ */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0; /* don't restart system calls */
+ sa.sa_handler = handler;
+ (void)sigaction(SIGALRM, &sa, &savealrm);
+ (void)sigaction(SIGHUP, &sa, &savehup);
+ (void)sigaction(SIGINT, &sa, &saveint);
+ (void)sigaction(SIGPIPE, &sa, &savepipe);
+ (void)sigaction(SIGQUIT, &sa, &savequit);
+ (void)sigaction(SIGTERM, &sa, &saveterm);
+ (void)sigaction(SIGTSTP, &sa, &savetstp);
+ (void)sigaction(SIGTTIN, &sa, &savettin);
+ (void)sigaction(SIGTTOU, &sa, &savettou);
+
+ if (!(flags & RPP_STDIN))
+ (void)write(output, prompt, strlen(prompt));
+ end = buf + bufsiz - 1;
+ p = buf;
+ while ((nr = read(input, &ch, 1)) == 1 && ch != '\n' && ch != '\r') {
+ if (p < end) {
+ if ((flags & RPP_SEVENBIT))
+ ch &= 0x7f;
+ if (isalpha((unsigned char)ch)) {
+ if ((flags & RPP_FORCELOWER))
+ ch = (char)tolower((unsigned char)ch);
+ if ((flags & RPP_FORCEUPPER))
+ ch = (char)toupper((unsigned char)ch);
+ }
+ *p++ = ch;
+ }
+ }
+ *p = '\0';
+ save_errno = errno;
+ if (!(term.c_lflag & ECHO))
+ (void)write(output, "\n", 1);
+
+ /* Restore old terminal settings and signals. */
+ if (memcmp(&term, &oterm, sizeof(term)) != 0) {
+ const int sigttou = signo[SIGTTOU];
+
+ /* Ignore SIGTTOU generated when we are not the fg pgrp. */
+ while (tcsetattr(input, TCSAFLUSH|TCSASOFT, &oterm) == -1 &&
+ errno == EINTR && !signo[SIGTTOU])
+ continue;
+ signo[SIGTTOU] = sigttou;
+ }
+ (void)sigaction(SIGALRM, &savealrm, NULL);
+ (void)sigaction(SIGHUP, &savehup, NULL);
+ (void)sigaction(SIGINT, &saveint, NULL);
+ (void)sigaction(SIGQUIT, &savequit, NULL);
+ (void)sigaction(SIGPIPE, &savepipe, NULL);
+ (void)sigaction(SIGTERM, &saveterm, NULL);
+ (void)sigaction(SIGTSTP, &savetstp, NULL);
+ (void)sigaction(SIGTTIN, &savettin, NULL);
+ (void)sigaction(SIGTTOU, &savettou, NULL);
+ if (input != STDIN_FILENO)
+ (void)close(input);
+
+ /*
+ * If we were interrupted by a signal, resend it to ourselves
+ * now that we have restored the signal handlers.
+ */
+ for (i = 0; i < NSIG; i++) {
+ if (signo[i]) {
+ kill(getpid(), i);
+ switch (i) {
+ case SIGTSTP:
+ case SIGTTIN:
+ case SIGTTOU:
+ need_restart = 1;
+ }
+ }
+ }
+ if (need_restart)
+ goto restart;
+
+ if (save_errno)
+ errno = save_errno;
+ return(nr == -1 ? NULL : buf);
+}
+
+#if 0
+char *
+getpass(const char *prompt)
+{
+ static char buf[_PASSWORD_LEN + 1];
+
+ return(readpassphrase(prompt, buf, sizeof(buf), RPP_ECHO_OFF));
+}
+#endif
+
+static void handler(int s)
+{
+
+ signo[s] = 1;
+}
+#endif /* HAVE_READPASSPHRASE */
diff --git a/openbsd-compat/readpassphrase.h b/openbsd-compat/readpassphrase.h
new file mode 100644
index 0000000..e4451f3
--- /dev/null
+++ b/openbsd-compat/readpassphrase.h
@@ -0,0 +1,44 @@
+/* $OpenBSD: readpassphrase.h,v 1.5 2003/06/17 21:56:23 millert Exp $ */
+
+/*
+ * Copyright (c) 2000, 2002 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.
+ */
+
+/* OPENBSD ORIGINAL: include/readpassphrase.h */
+
+#ifndef _READPASSPHRASE_H_
+#define _READPASSPHRASE_H_
+
+#ifndef HAVE_READPASSPHRASE
+
+#include <stdlib.h>
+
+#define RPP_ECHO_OFF 0x00 /* Turn off echo (default). */
+#define RPP_ECHO_ON 0x01 /* Leave echo on. */
+#define RPP_REQUIRE_TTY 0x02 /* Fail if there is no tty. */
+#define RPP_FORCELOWER 0x04 /* Force input to lower case. */
+#define RPP_FORCEUPPER 0x08 /* Force input to upper case. */
+#define RPP_SEVENBIT 0x10 /* Strip the high bit from input. */
+#define RPP_STDIN 0x20 /* Read from stdin, not /dev/tty */
+
+char * readpassphrase(const char *, char *, size_t, int);
+
+#endif /* HAVE_READPASSPHRASE */
+
+#endif /* !_READPASSPHRASE_H_ */
diff --git a/openbsd-compat/readpassphrase_win32.c b/openbsd-compat/readpassphrase_win32.c
new file mode 100644
index 0000000..968987c
--- /dev/null
+++ b/openbsd-compat/readpassphrase_win32.c
@@ -0,0 +1,131 @@
+/*
+* Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
+*
+* Author: Bryan Berns <berns@uwalumni.com>
+* Modified group detection use s4u token information
+*
+* Copyright(c) 2016 Microsoft Corp.
+* All rights reserved
+*
+* Misc Unix POSIX routine implementations for Windows
+*
+* 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 AUTHOR ``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 AUTHOR 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.
+*/
+
+#define UMDF_USING_NTSTATUS
+#define SECURITY_WIN32
+#include <windows.h>
+#include <stdio.h>
+#include <time.h>
+#include <shlwapi.h>
+#include <conio.h>
+#include <lm.h>
+#include <sddl.h>
+#include <aclapi.h>
+#include <ntsecapi.h>
+#include <security.h>
+#include <ntstatus.h>
+#include <wchar.h>
+
+#include "openbsd-compat.h"
+
+#ifndef HAVE_READPASSPHRASE
+
+/*on error returns NULL and sets errno*/
+static wchar_t *
+utf8_to_utf16(const char *utf8)
+{
+ int needed = 0;
+ wchar_t* utf16 = NULL;
+ if ((needed = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) == 0 ||
+ (utf16 = malloc(needed * sizeof(wchar_t))) == NULL ||
+ MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, needed) == 0) {
+ /* debug3("failed to convert utf8 payload:%s error:%d", utf8, GetLastError()); */
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return utf16;
+}
+
+char *
+readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags)
+{
+ size_t current_index = 0;
+ char ch;
+ wchar_t* wtmp = NULL;
+
+ if (outBufLen == 0) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ while (_kbhit()) (void)_getch();
+
+ wtmp = utf8_to_utf16(prompt);
+ if (wtmp == NULL)
+ errx(1, "unable to alloc memory");
+
+ _cputws(wtmp);
+ free(wtmp);
+
+ while (current_index < outBufLen - 1) {
+ ch = (char)_getch();
+
+ if (ch == '\r') {
+ if (_kbhit()) (void)_getch(); /* read linefeed if its there */
+ break;
+ } else if (ch == '\n') {
+ break;
+ } else if (ch == '\b') { /* backspace */
+ if (current_index > 0) {
+ if (flags & RPP_ECHO_ON)
+ printf_s("%c \b", ch);
+
+ current_index--; /* overwrite last character */
+ }
+ } else if (ch == '\003') { /* exit on Ctrl+C */
+ errx(1, "");
+ } else {
+ if (flags & RPP_SEVENBIT)
+ ch &= 0x7f;
+
+ if (isalpha((unsigned char)ch)) {
+ if(flags & RPP_FORCELOWER)
+ ch = (char)tolower((unsigned char)ch);
+ if(flags & RPP_FORCEUPPER)
+ ch = (char)toupper((unsigned char)ch);
+ }
+
+ outBuf[current_index++] = ch;
+ if(flags & RPP_ECHO_ON)
+ printf_s("%c", ch);
+ }
+ }
+
+ outBuf[current_index] = '\0';
+ _cputs("\n");
+
+ return outBuf;
+}
+
+#endif /* HAVE_READPASSPHRASE */
diff --git a/openbsd-compat/recallocarray.c b/openbsd-compat/recallocarray.c
new file mode 100644
index 0000000..5d2f8d9
--- /dev/null
+++ b/openbsd-compat/recallocarray.c
@@ -0,0 +1,91 @@
+/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */
+/*
+ * Copyright (c) 2008, 2017 Otto Moerbeek <otto@drijf.net>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/stdlib/recallocarray.c */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_RECALLOCARRAY)
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+/*
+ * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
+ * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
+ */
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size)
+{
+ size_t oldsize, newsize;
+ void *newptr;
+
+ if (ptr == NULL)
+ return calloc(newnmemb, size);
+
+ if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ newnmemb > 0 && SIZE_MAX / newnmemb < size) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ newsize = newnmemb * size;
+
+ if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ oldnmemb > 0 && SIZE_MAX / oldnmemb < size) {
+ errno = EINVAL;
+ return NULL;
+ }
+ oldsize = oldnmemb * size;
+
+ /*
+ * Don't bother too much if we're shrinking just a bit,
+ * we do not shrink for series of small steps, oh well.
+ */
+ if (newsize <= oldsize) {
+ size_t d = oldsize - newsize;
+
+ if (d < oldsize / 2 && d < (size_t)getpagesize()) {
+ memset((char *)ptr + newsize, 0, d);
+ return ptr;
+ }
+ }
+
+ newptr = malloc(newsize);
+ if (newptr == NULL)
+ return NULL;
+
+ if (newsize > oldsize) {
+ memcpy(newptr, ptr, oldsize);
+ memset((char *)newptr + oldsize, 0, newsize - oldsize);
+ } else
+ memcpy(newptr, ptr, newsize);
+
+ explicit_bzero(ptr, oldsize);
+ free(ptr);
+
+ return newptr;
+}
+/* DEF_WEAK(recallocarray); */
+
+#endif /* !defined(HAVE_RECALLOCARRAY) */
diff --git a/openbsd-compat/strlcat.c b/openbsd-compat/strlcat.c
new file mode 100644
index 0000000..44470de
--- /dev/null
+++ b/openbsd-compat/strlcat.c
@@ -0,0 +1,63 @@
+/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/strlcat.c */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_STRLCAT)
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
+
+#endif /* !defined(HAVE_STRLCAT) */
diff --git a/openbsd-compat/strlcpy.c b/openbsd-compat/strlcpy.c
new file mode 100644
index 0000000..a8b18ea
--- /dev/null
+++ b/openbsd-compat/strlcpy.c
@@ -0,0 +1,59 @@
+/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/strlcpy.c */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_STRLCPY)
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+#endif /* !defined(HAVE_STRLCPY) */
diff --git a/openbsd-compat/strsep.c b/openbsd-compat/strsep.c
new file mode 100644
index 0000000..578668c
--- /dev/null
+++ b/openbsd-compat/strsep.c
@@ -0,0 +1,79 @@
+/* $OpenBSD: strsep.c,v 1.6 2005/08/08 08:05:37 espie Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * 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.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/strsep.c */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_STRSEP)
+
+#include <string.h>
+#include <stdio.h>
+
+/*
+ * Get next token from string *stringp, where tokens are possibly-empty
+ * strings separated by characters from delim.
+ *
+ * Writes NULs into the string at *stringp to end tokens.
+ * delim need not remain constant from call to call.
+ * On return, *stringp points past the last NUL written (if there might
+ * be further tokens), or is NULL (if there are definitely no more tokens).
+ *
+ * If *stringp is NULL, strsep returns NULL.
+ */
+char *
+strsep(char **stringp, const char *delim)
+{
+ char *s;
+ const char *spanp;
+ int c, sc;
+ char *tok;
+
+ if ((s = *stringp) == NULL)
+ return (NULL);
+ for (tok = s;;) {
+ c = *s++;
+ spanp = delim;
+ do {
+ if ((sc = *spanp++) == c) {
+ if (c == 0)
+ s = NULL;
+ else
+ s[-1] = 0;
+ *stringp = s;
+ return (tok);
+ }
+ } while (sc != 0);
+ }
+ /* NOTREACHED */
+}
+
+#endif /* !defined(HAVE_STRSEP) */
diff --git a/openbsd-compat/time.h b/openbsd-compat/time.h
new file mode 100644
index 0000000..b125f73
--- /dev/null
+++ b/openbsd-compat/time.h
@@ -0,0 +1,61 @@
+/*
+ * Public domain
+ * sys/time.h compatibility shim
+ */
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1900)
+#include <../ucrt/time.h>
+#elif defined(_MSC_VER) && (_MSC_VER < 1900)
+#include <../include/time.h>
+#else
+#include <time.h>
+#endif
+
+#ifndef _COMPAT_TIME_H
+#define _COMPAT_TIME_H
+
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC CLOCK_REALTIME
+#endif
+
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME 0
+#endif
+
+#ifndef HAVE_CLOCK_GETTIME
+typedef int clockid_t;
+int clock_gettime(clockid_t, struct timespec *);
+#endif
+
+#ifdef HAVE_TIMESPECSUB
+#include <sys/time.h>
+#endif
+
+#ifndef HAVE_TIMESPECSUB
+#define timespecadd(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec >= 1000000000L) { \
+ (vsp)->tv_sec++; \
+ (vsp)->tv_nsec -= 1000000000L; \
+ } \
+ } while (0)
+
+#define timespecsub(tsp, usp, vsp) \
+ do { \
+ (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
+ (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
+ if ((vsp)->tv_nsec < 0) { \
+ (vsp)->tv_sec--; \
+ (vsp)->tv_nsec += 1000000000L; \
+ } \
+ } while (0)
+
+#define timespeccmp(tsp, usp, cmp) \
+ (((tsp)->tv_sec == (usp)->tv_sec) ? \
+ ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \
+ ((tsp)->tv_sec cmp (usp)->tv_sec))
+#endif
+
+#endif /* _COMPAT_TIME_H */
diff --git a/openbsd-compat/timingsafe_bcmp.c b/openbsd-compat/timingsafe_bcmp.c
new file mode 100644
index 0000000..3f7b9e5
--- /dev/null
+++ b/openbsd-compat/timingsafe_bcmp.c
@@ -0,0 +1,35 @@
+/* $OpenBSD: timingsafe_bcmp.c,v 1.1 2010/09/24 13:33:00 matthew Exp $ */
+/*
+ * Copyright (c) 2010 Damien Miller. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* OPENBSD ORIGINAL: lib/libc/string/timingsafe_bcmp.c */
+
+#include "openbsd-compat.h"
+
+#if !defined(HAVE_TIMINGSAFE_BCMP)
+
+int
+timingsafe_bcmp(const void *b1, const void *b2, size_t n)
+{
+ const unsigned char *p1 = b1, *p2 = b2;
+ int ret = 0;
+
+ for (; n > 0; n--)
+ ret |= *p1++ ^ *p2++;
+ return (ret != 0);
+}
+
+#endif /* !defined(HAVE_TIMINGSAFE_BCMP) */
diff --git a/openbsd-compat/types.h b/openbsd-compat/types.h
new file mode 100644
index 0000000..6170230
--- /dev/null
+++ b/openbsd-compat/types.h
@@ -0,0 +1,69 @@
+/*
+ * Public domain
+ * sys/types.h compatibility shim
+ */
+
+#ifdef _MSC_VER
+#if _MSC_VER >= 1900
+#include <../ucrt/sys/types.h>
+#else
+#include <../include/sys/types.h>
+#endif
+#endif
+
+#ifndef _COMPAT_TYPES_H
+#define _COMPAT_TYPES_H
+
+#include <stdint.h>
+
+#ifdef __MINGW32__
+#include <_bsd_types.h>
+typedef uint32_t in_addr_t;
+typedef uint32_t uid_t;
+#endif
+
+#ifdef _MSC_VER
+typedef unsigned char u_char;
+typedef unsigned short u_short;
+typedef unsigned int u_int;
+typedef unsigned long u_long;
+
+#include <basetsd.h>
+typedef SSIZE_T ssize_t;
+
+#ifndef SSIZE_MAX
+#ifdef _WIN64
+#define SSIZE_MAX _I64_MAX
+#else
+#define SSIZE_MAX INT_MAX
+#endif
+#endif
+
+#endif
+
+#if !defined(HAVE_ATTRIBUTE__BOUNDED__) && !defined(__bounded__)
+# define __bounded__(x, y, z)
+#endif
+
+#ifdef _WIN32
+#define __warn_references(sym,msg)
+#else
+
+#ifndef __warn_references
+
+#ifndef __STRING
+#define __STRING(x) #x
+#endif
+
+#if defined(__GNUC__) && defined (HAS_GNU_WARNING_LONG)
+#define __warn_references(sym,msg) \
+ __asm__(".section .gnu.warning." __STRING(sym) \
+ "\n\t.ascii \"" msg "\"\n\t.text");
+#else
+#define __warn_references(sym,msg)
+#endif
+
+#endif /* __warn_references */
+#endif /* _WIN32 */
+
+#endif /* !_COMPAT_TYPES_H */
diff --git a/regress/CMakeLists.txt b/regress/CMakeLists.txt
new file mode 100644
index 0000000..246bffa
--- /dev/null
+++ b/regress/CMakeLists.txt
@@ -0,0 +1,57 @@
+# 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_custom_target(regress)
+
+macro(add_regress_test NAME SOURCES LIB)
+ add_executable(${NAME} ${SOURCES})
+ add_test(${NAME} ${NAME})
+ add_dependencies(regress ${NAME})
+ target_link_libraries(${NAME} ${LIB})
+endmacro()
+
+if(MSVC AND BUILD_SHARED_LIBS)
+ add_custom_command(TARGET regress POST_BUILD
+ COMMAND "${CMAKE_COMMAND}" -E copy
+ "${CBOR_BIN_DIRS}/${CBOR_LIBRARIES}.dll"
+ "${CRYPTO_BIN_DIRS}/${CRYPTO_LIBRARIES}.dll"
+ "${ZLIB_BIN_DIRS}/${ZLIB_LIBRARIES}.dll"
+ "$<TARGET_FILE:${_FIDO2_LIBRARY}>"
+ "${CMAKE_CURRENT_BINARY_DIR}")
+endif()
+
+if(CYGWIN AND BUILD_SHARED_LIBS)
+ add_custom_command(TARGET regress POST_BUILD
+ COMMAND "${CMAKE_COMMAND}" -E copy
+ "$<TARGET_FILE:${_FIDO2_LIBRARY}>"
+ "${CMAKE_CURRENT_BINARY_DIR}")
+endif()
+
+if(CMAKE_CROSSCOMPILING OR (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64" AND
+ CMAKE_GENERATOR_PLATFORM MATCHES "^ARM.*$"))
+ add_custom_command(TARGET regress POST_BUILD
+ COMMAND "${CMAKE_COMMAND}" -E echo
+ "Cross-compilation detected. Skipping regress tests.")
+else()
+ add_custom_command(TARGET regress POST_BUILD
+ COMMAND "${CMAKE_CTEST_COMMAND}" --output-on-failure
+ WORKING_DIRECTORY ${PROJECT_BINARY_DIR})
+endif()
+
+add_regress_test(regress_assert assert.c ${_FIDO2_LIBRARY})
+add_regress_test(regress_cred cred.c ${_FIDO2_LIBRARY})
+add_regress_test(regress_dev dev.c ${_FIDO2_LIBRARY})
+add_regress_test(regress_eddsa eddsa.c ${_FIDO2_LIBRARY})
+add_regress_test(regress_es256 es256.c ${_FIDO2_LIBRARY})
+add_regress_test(regress_es384 es384.c ${_FIDO2_LIBRARY})
+add_regress_test(regress_rs256 rs256.c ${_FIDO2_LIBRARY})
+if(BUILD_STATIC_LIBS)
+ add_regress_test(regress_compress compress.c fido2)
+endif()
+
+if(MINGW)
+ # needed for nanosleep() in mingw
+ target_link_libraries(regress_dev winpthread)
+endif()
diff --git a/regress/assert.c b/regress/assert.c
new file mode 100644
index 0000000..ad31903
--- /dev/null
+++ b/regress/assert.c
@@ -0,0 +1,685 @@
+/*
+ * 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
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <string.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+#include <fido/es256.h>
+#include <fido/rs256.h>
+#include <fido/eddsa.h>
+
+static int fake_dev_handle;
+
+static const unsigned char es256_pk[64] = {
+ 0x34, 0xeb, 0x99, 0x77, 0x02, 0x9c, 0x36, 0x38,
+ 0xbb, 0xc2, 0xae, 0xa0, 0xa0, 0x18, 0xc6, 0x64,
+ 0xfc, 0xe8, 0x49, 0x92, 0xd7, 0x74, 0x9e, 0x0c,
+ 0x46, 0x8c, 0x9d, 0xa6, 0xdf, 0x46, 0xf7, 0x84,
+ 0x60, 0x1e, 0x0f, 0x8b, 0x23, 0x85, 0x4a, 0x9a,
+ 0xec, 0xc1, 0x08, 0x9f, 0x30, 0xd0, 0x0d, 0xd7,
+ 0x76, 0x7b, 0x55, 0x48, 0x91, 0x7c, 0x4f, 0x0f,
+ 0x64, 0x1a, 0x1d, 0xf8, 0xbe, 0x14, 0x90, 0x8a,
+};
+
+static const unsigned char rs256_pk[259] = {
+ 0x9e, 0x54, 0x78, 0xb2, 0x51, 0xbe, 0x19, 0x7c,
+ 0xcb, 0x1a, 0x9a, 0xc3, 0x49, 0x2a, 0x2f, 0xfd,
+ 0x99, 0x64, 0x76, 0xc6, 0xdb, 0xca, 0x38, 0x3f,
+ 0xb0, 0x6a, 0xc9, 0xc0, 0x07, 0x9f, 0x5c, 0x4d,
+ 0xfc, 0xd1, 0x01, 0x7f, 0x69, 0x65, 0xab, 0x9c,
+ 0x2a, 0xc2, 0x95, 0xd9, 0x44, 0xf3, 0xea, 0x94,
+ 0x6b, 0x25, 0x66, 0x54, 0x81, 0xee, 0x24, 0x1d,
+ 0xe1, 0x7d, 0x7f, 0xbe, 0xea, 0x76, 0x90, 0x5c,
+ 0xbf, 0x59, 0x22, 0xd3, 0xa0, 0x68, 0x1a, 0x65,
+ 0x8b, 0x2f, 0xb6, 0xa8, 0x30, 0x2d, 0x26, 0x81,
+ 0xfa, 0x9e, 0x59, 0xec, 0x2f, 0xee, 0x59, 0x39,
+ 0xe2, 0x79, 0x19, 0x54, 0x54, 0xdf, 0x24, 0x83,
+ 0xee, 0x61, 0x5a, 0x66, 0x24, 0x2b, 0x7b, 0xfb,
+ 0x82, 0x66, 0xe4, 0x85, 0x18, 0x20, 0x76, 0xe5,
+ 0x4a, 0xb6, 0xcb, 0xec, 0x43, 0xbe, 0xfd, 0xb0,
+ 0x8f, 0xfd, 0x2f, 0x69, 0xda, 0x06, 0x9c, 0x09,
+ 0x68, 0x7a, 0x94, 0x6c, 0xb7, 0x51, 0x6d, 0x4c,
+ 0xf7, 0x13, 0xe8, 0xd5, 0x22, 0x6b, 0x1e, 0xba,
+ 0xb9, 0x85, 0xe8, 0x5f, 0xa1, 0x66, 0xe3, 0x20,
+ 0x75, 0x30, 0x11, 0xb5, 0xa3, 0xc3, 0xb0, 0x72,
+ 0x08, 0xff, 0xa3, 0xbb, 0xf1, 0x32, 0x0b, 0x06,
+ 0xc4, 0x12, 0xa3, 0x49, 0x30, 0x19, 0xb9, 0xfe,
+ 0x69, 0x0c, 0xd6, 0xe1, 0x58, 0x36, 0xe6, 0x41,
+ 0x22, 0x41, 0xbf, 0x96, 0x50, 0x35, 0x56, 0x0d,
+ 0x92, 0x8c, 0x34, 0xea, 0x28, 0x91, 0x88, 0x9e,
+ 0x8a, 0xaa, 0x36, 0xd0, 0x0f, 0xbe, 0x16, 0xde,
+ 0x9d, 0x5f, 0x7b, 0xda, 0x52, 0xf7, 0xf1, 0xb6,
+ 0x28, 0x10, 0x05, 0x8f, 0xb9, 0x19, 0x7a, 0xcf,
+ 0x18, 0x9b, 0x40, 0xcd, 0xff, 0x78, 0xea, 0x61,
+ 0x24, 0x3b, 0x80, 0x68, 0x04, 0x9b, 0x40, 0x07,
+ 0x98, 0xd4, 0x94, 0xd1, 0x18, 0x44, 0xa5, 0xed,
+ 0xee, 0x18, 0xc2, 0x25, 0x52, 0x66, 0x42, 0xdf,
+ 0x01, 0x00, 0x01,
+};
+
+static const unsigned char cdh[32] = {
+ 0xec, 0x8d, 0x8f, 0x78, 0x42, 0x4a, 0x2b, 0xb7,
+ 0x82, 0x34, 0xaa, 0xca, 0x07, 0xa1, 0xf6, 0x56,
+ 0x42, 0x1c, 0xb6, 0xf6, 0xb3, 0x00, 0x86, 0x52,
+ 0x35, 0x2d, 0xa2, 0x62, 0x4a, 0xbe, 0x89, 0x76,
+};
+
+static const unsigned char authdata[39] = {
+ 0x58, 0x25, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e,
+ 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76,
+ 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86,
+ 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d,
+ 0x97, 0x63, 0x00, 0x00, 0x00, 0x00, 0x03,
+};
+
+static const unsigned char sig[72] = {
+ 0x30, 0x46, 0x02, 0x21, 0x00, 0xf6, 0xd1, 0xa3,
+ 0xd5, 0x24, 0x2b, 0xde, 0xee, 0xa0, 0x90, 0x89,
+ 0xcd, 0xf8, 0x9e, 0xbd, 0x6b, 0x4d, 0x55, 0x79,
+ 0xe4, 0xc1, 0x42, 0x27, 0xb7, 0x9b, 0x9b, 0xa4,
+ 0x0a, 0xe2, 0x47, 0x64, 0x0e, 0x02, 0x21, 0x00,
+ 0xe5, 0xc9, 0xc2, 0x83, 0x47, 0x31, 0xc7, 0x26,
+ 0xe5, 0x25, 0xb2, 0xb4, 0x39, 0xa7, 0xfc, 0x3d,
+ 0x70, 0xbe, 0xe9, 0x81, 0x0d, 0x4a, 0x62, 0xa9,
+ 0xab, 0x4a, 0x91, 0xc0, 0x7d, 0x2d, 0x23, 0x1e,
+};
+
+static void *
+dummy_open(const char *path)
+{
+ (void)path;
+
+ return (&fake_dev_handle);
+}
+
+static void
+dummy_close(void *handle)
+{
+ assert(handle == &fake_dev_handle);
+}
+
+static int
+dummy_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ (void)handle;
+ (void)buf;
+ (void)len;
+ (void)ms;
+
+ abort();
+ /* NOTREACHED */
+}
+
+static int
+dummy_write(void *handle, const unsigned char *buf, size_t len)
+{
+ (void)handle;
+ (void)buf;
+ (void)len;
+
+ abort();
+ /* NOTREACHED */
+}
+
+static fido_assert_t *
+alloc_assert(void)
+{
+ fido_assert_t *a;
+
+ a = fido_assert_new();
+ assert(a != NULL);
+
+ return (a);
+}
+
+static void
+free_assert(fido_assert_t *a)
+{
+ fido_assert_free(&a);
+ assert(a == NULL);
+}
+
+static fido_dev_t *
+alloc_dev(void)
+{
+ fido_dev_t *d;
+
+ d = fido_dev_new();
+ assert(d != NULL);
+
+ return (d);
+}
+
+static void
+free_dev(fido_dev_t *d)
+{
+ fido_dev_free(&d);
+ assert(d == NULL);
+}
+
+static es256_pk_t *
+alloc_es256_pk(void)
+{
+ es256_pk_t *pk;
+
+ pk = es256_pk_new();
+ assert(pk != NULL);
+
+ return (pk);
+}
+
+static void
+free_es256_pk(es256_pk_t *pk)
+{
+ es256_pk_free(&pk);
+ assert(pk == NULL);
+}
+
+static rs256_pk_t *
+alloc_rs256_pk(void)
+{
+ rs256_pk_t *pk;
+
+ pk = rs256_pk_new();
+ assert(pk != NULL);
+
+ return (pk);
+}
+
+static void
+free_rs256_pk(rs256_pk_t *pk)
+{
+ rs256_pk_free(&pk);
+ assert(pk == NULL);
+}
+
+static eddsa_pk_t *
+alloc_eddsa_pk(void)
+{
+ eddsa_pk_t *pk;
+
+ pk = eddsa_pk_new();
+ assert(pk != NULL);
+
+ return (pk);
+}
+
+static void
+free_eddsa_pk(eddsa_pk_t *pk)
+{
+ eddsa_pk_free(&pk);
+ assert(pk == NULL);
+}
+
+static void
+empty_assert(fido_dev_t *d, fido_assert_t *a, size_t idx)
+{
+ es256_pk_t *es256;
+ rs256_pk_t *rs256;
+ eddsa_pk_t *eddsa;
+
+ assert(fido_assert_flags(a, idx) == 0);
+ assert(fido_assert_authdata_len(a, idx) == 0);
+ assert(fido_assert_authdata_ptr(a, idx) == NULL);
+ assert(fido_assert_authdata_raw_len(a, idx) == 0);
+ assert(fido_assert_authdata_raw_ptr(a, idx) == NULL);
+ assert(fido_assert_clientdata_hash_len(a) == 0);
+ assert(fido_assert_clientdata_hash_ptr(a) == NULL);
+ assert(fido_assert_id_len(a, idx) == 0);
+ assert(fido_assert_id_ptr(a, idx) == NULL);
+ assert(fido_assert_rp_id(a) == NULL);
+ assert(fido_assert_sig_len(a, idx) == 0);
+ assert(fido_assert_sig_ptr(a, idx) == NULL);
+ assert(fido_assert_user_display_name(a, idx) == NULL);
+ assert(fido_assert_user_icon(a, idx) == NULL);
+ assert(fido_assert_user_id_len(a, idx) == 0);
+ assert(fido_assert_user_id_ptr(a, idx) == NULL);
+ assert(fido_assert_user_name(a, idx) == NULL);
+
+ es256 = alloc_es256_pk();
+ rs256 = alloc_rs256_pk();
+ eddsa = alloc_eddsa_pk();
+
+ fido_dev_force_u2f(d);
+ assert(fido_dev_get_assert(d, a, NULL) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_get_assert(d, a, "") == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_ES256,
+ NULL) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_ES256,
+ es256) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, -1,
+ es256) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_RS256,
+ rs256) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_EDDSA,
+ eddsa) == FIDO_ERR_INVALID_ARGUMENT);
+
+ fido_dev_force_fido2(d);
+ assert(fido_dev_get_assert(d, a, NULL) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_get_assert(d, a, "") == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_ES256,
+ NULL) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_ES256,
+ es256) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, -1,
+ es256) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_RS256,
+ rs256) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_verify(a, idx, COSE_EDDSA,
+ eddsa) == FIDO_ERR_INVALID_ARGUMENT);
+
+ free_es256_pk(es256);
+ free_rs256_pk(rs256);
+ free_eddsa_pk(eddsa);
+}
+
+static void
+empty_assert_tests(void)
+{
+ fido_assert_t *a;
+ fido_dev_t *d;
+ fido_dev_io_t io_f;
+ size_t i;
+
+ memset(&io_f, 0, sizeof(io_f));
+
+ a = alloc_assert();
+ d = alloc_dev();
+
+ io_f.open = dummy_open;
+ io_f.close = dummy_close;
+ io_f.read = dummy_read;
+ io_f.write = dummy_write;
+
+ assert(fido_dev_set_io_functions(d, &io_f) == FIDO_OK);
+
+ empty_assert(d, a, 0);
+ assert(fido_assert_count(a) == 0);
+ assert(fido_assert_set_count(a, 4) == FIDO_OK);
+ assert(fido_assert_count(a) == 4);
+ for (i = 0; i < 4; i++) {
+ empty_assert(d, a, i);
+ }
+ empty_assert(d, a, 10);
+ free_assert(a);
+ free_dev(d);
+}
+
+static void
+valid_assert(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *es256;
+ rs256_pk_t *rs256;
+ eddsa_pk_t *eddsa;
+
+ a = alloc_assert();
+ es256 = alloc_es256_pk();
+ rs256 = alloc_rs256_pk();
+ eddsa = alloc_eddsa_pk();
+ assert(es256_pk_from_ptr(es256, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "localhost") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256, es256) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_RS256, rs256) == FIDO_ERR_INVALID_SIG);
+ assert(fido_assert_verify(a, 0, COSE_EDDSA, eddsa) == FIDO_ERR_INVALID_SIG);
+ free_assert(a);
+ free_es256_pk(es256);
+ free_rs256_pk(rs256);
+ free_eddsa_pk(eddsa);
+}
+
+static void
+no_cdh(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "localhost") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256,
+ pk) == FIDO_ERR_INVALID_ARGUMENT);
+ free_assert(a);
+ free_es256_pk(pk);
+}
+
+static void
+no_rp(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256,
+ pk) == FIDO_ERR_INVALID_ARGUMENT);
+ free_assert(a);
+ free_es256_pk(pk);
+}
+
+static void
+no_authdata(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "localhost") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256,
+ pk) == FIDO_ERR_INVALID_ARGUMENT);
+ free_assert(a);
+ free_es256_pk(pk);
+}
+
+static void
+no_sig(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "localhost") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256,
+ pk) == FIDO_ERR_INVALID_ARGUMENT);
+ free_assert(a);
+ free_es256_pk(pk);
+}
+
+static void
+junk_cdh(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+ unsigned char *junk;
+
+ junk = malloc(sizeof(cdh));
+ assert(junk != NULL);
+ memcpy(junk, cdh, sizeof(cdh));
+ junk[0] = (unsigned char)~junk[0];
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, junk, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "localhost") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256, pk) == FIDO_ERR_INVALID_SIG);
+ free_assert(a);
+ free_es256_pk(pk);
+ free(junk);
+}
+
+static void
+junk_rp(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "potato") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256,
+ pk) == FIDO_ERR_INVALID_PARAM);
+ free_assert(a);
+ free_es256_pk(pk);
+}
+
+static void
+junk_authdata(void)
+{
+ fido_assert_t *a;
+ unsigned char *junk;
+
+ junk = malloc(sizeof(authdata));
+ assert(junk != NULL);
+ memcpy(junk, authdata, sizeof(authdata));
+ junk[0] = (unsigned char)~junk[0];
+
+ a = alloc_assert();
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, junk,
+ sizeof(authdata)) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_assert_authdata_ptr(a, 0) == NULL);
+ assert(fido_assert_authdata_len(a, 0) == 0);
+ assert(fido_assert_authdata_raw_ptr(a, 0) == NULL);
+ assert(fido_assert_authdata_raw_len(a, 0) == 0);
+ free_assert(a);
+ free(junk);
+}
+
+static void
+junk_sig(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+ unsigned char *junk;
+
+ junk = malloc(sizeof(sig));
+ assert(junk != NULL);
+ memcpy(junk, sig, sizeof(sig));
+ junk[0] = (unsigned char)~junk[0];
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "localhost") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, junk, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256, pk) == FIDO_ERR_INVALID_SIG);
+ free_assert(a);
+ free_es256_pk(pk);
+ free(junk);
+}
+
+static void
+wrong_options(void)
+{
+ fido_assert_t *a;
+ es256_pk_t *pk;
+
+ a = alloc_assert();
+ pk = alloc_es256_pk();
+ assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_assert_set_rp(a, "localhost") == FIDO_OK);
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_set_up(a, FIDO_OPT_TRUE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256,
+ pk) == FIDO_ERR_INVALID_PARAM);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_TRUE) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256,
+ pk) == FIDO_ERR_INVALID_PARAM);
+ assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_assert_verify(a, 0, COSE_ES256, pk) == FIDO_OK);
+ free_assert(a);
+ free_es256_pk(pk);
+}
+
+/* cbor_serialize_alloc misuse */
+static void
+bad_cbor_serialize(void)
+{
+ fido_assert_t *a;
+
+ a = alloc_assert();
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert(fido_assert_authdata_len(a, 0) == sizeof(authdata));
+ free_assert(a);
+}
+
+/* rs256 <-> EVP_PKEY transformations */
+static void
+rs256_PKEY(void)
+{
+ rs256_pk_t *pk1, *pk2;
+ EVP_PKEY *pkey;
+
+ pk1 = alloc_rs256_pk();
+ pk2 = alloc_rs256_pk();
+
+ assert(rs256_pk_from_ptr(pk1, rs256_pk, sizeof(rs256_pk)) == FIDO_OK);
+ assert((pkey = rs256_pk_to_EVP_PKEY(pk1)) != NULL);
+ assert(rs256_pk_from_EVP_PKEY(pk2, pkey) == FIDO_OK);
+ assert(memcmp(pk1, pk2, sizeof(*pk1)) == 0);
+
+ free_rs256_pk(pk1);
+ free_rs256_pk(pk2);
+ EVP_PKEY_free(pkey);
+}
+
+/* es256 <-> EVP_PKEY transformations */
+static void
+es256_PKEY(void)
+{
+ es256_pk_t *pk1, *pk2;
+ EVP_PKEY *pkey;
+
+ pk1 = alloc_es256_pk();
+ pk2 = alloc_es256_pk();
+
+ assert(es256_pk_from_ptr(pk1, es256_pk, sizeof(es256_pk)) == FIDO_OK);
+ assert((pkey = es256_pk_to_EVP_PKEY(pk1)) != NULL);
+ assert(es256_pk_from_EVP_PKEY(pk2, pkey) == FIDO_OK);
+ assert(memcmp(pk1, pk2, sizeof(*pk1)) == 0);
+
+ free_es256_pk(pk1);
+ free_es256_pk(pk2);
+ EVP_PKEY_free(pkey);
+}
+
+static void
+raw_authdata(void)
+{
+ fido_assert_t *a;
+ cbor_item_t *item;
+ struct cbor_load_result cbor_result;
+ const unsigned char *ptr;
+ unsigned char *cbor;
+ size_t len;
+ size_t cbor_len;
+ size_t alloclen;
+
+ a = alloc_assert();
+ assert(fido_assert_set_count(a, 1) == FIDO_OK);
+ assert(fido_assert_set_authdata(a, 0, authdata,
+ sizeof(authdata)) == FIDO_OK);
+ assert((ptr = fido_assert_authdata_ptr(a, 0)) != NULL);
+ assert((len = fido_assert_authdata_len(a, 0)) != 0);
+ assert((item = cbor_load(ptr, len, &cbor_result)) != NULL);
+ assert(cbor_result.read == len);
+ assert(cbor_isa_bytestring(item));
+ assert((ptr = fido_assert_authdata_raw_ptr(a, 0)) != NULL);
+ assert((len = fido_assert_authdata_raw_len(a, 0)) != 0);
+ assert(cbor_bytestring_length(item) == len);
+ assert(memcmp(ptr, cbor_bytestring_handle(item), len) == 0);
+ assert((len = fido_assert_authdata_len(a, 0)) != 0);
+ assert((cbor_len = cbor_serialize_alloc(item, &cbor, &alloclen)) == len);
+ assert((ptr = cbor_bytestring_handle(item)) != NULL);
+ assert((len = cbor_bytestring_length(item)) != 0);
+ assert(fido_assert_set_authdata_raw(a, 0, ptr, len) == FIDO_OK);
+ assert((ptr = fido_assert_authdata_ptr(a, 0)) != NULL);
+ assert((len = fido_assert_authdata_len(a, 0)) != 0);
+ assert(len == cbor_len);
+ assert(memcmp(cbor, ptr, len) == 0);
+ assert(cbor_len == sizeof(authdata));
+ assert(memcmp(cbor, authdata, cbor_len) == 0);
+ cbor_decref(&item);
+ free(cbor);
+ free_assert(a);
+}
+
+int
+main(void)
+{
+ fido_init(0);
+
+ empty_assert_tests();
+ valid_assert();
+ no_cdh();
+ no_rp();
+ no_authdata();
+ no_sig();
+ junk_cdh();
+ junk_rp();
+ junk_authdata();
+ junk_sig();
+ wrong_options();
+ bad_cbor_serialize();
+ rs256_PKEY();
+ es256_PKEY();
+ raw_authdata();
+
+ exit(0);
+}
diff --git a/regress/compress.c b/regress/compress.c
new file mode 100644
index 0000000..7afc8bb
--- /dev/null
+++ b/regress/compress.c
@@ -0,0 +1,268 @@
+/*
+ * 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
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <string.h>
+
+#include <openssl/sha.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+
+/*
+ * zlib compressed data (RFC1950); see https://www.ietf.org/rfc/rfc6713.txt
+ */
+static /* const */ unsigned char rfc1950_blob[694] = {
+ 0x78, 0x9c, 0xb5, 0x52, 0x3b, 0x6f, 0xdb, 0x30,
+ 0x10, 0xde, 0xf5, 0x2b, 0x0e, 0x99, 0x12, 0x40,
+ 0x75, 0x13, 0x4f, 0x45, 0x3b, 0xd1, 0x12, 0x6d,
+ 0x1d, 0x20, 0x8b, 0x2a, 0x49, 0xd9, 0xf5, 0x28,
+ 0x4b, 0x4c, 0x42, 0xc0, 0x12, 0x03, 0x3d, 0x12,
+ 0xe4, 0xdf, 0xf7, 0xc8, 0x3a, 0x88, 0xd3, 0x0c,
+ 0x9d, 0xea, 0xc1, 0x3e, 0xf3, 0x8e, 0xdf, 0xeb,
+ 0x98, 0xb8, 0xa7, 0xd7, 0xc1, 0x3e, 0x3c, 0x4e,
+ 0x70, 0xdd, 0xdc, 0xc0, 0xf2, 0xf6, 0xee, 0xdb,
+ 0x97, 0xe5, 0xed, 0x72, 0x09, 0x87, 0xf9, 0x68,
+ 0x1b, 0x07, 0x6c, 0xb5, 0x00, 0x76, 0x3a, 0x41,
+ 0x18, 0x19, 0x61, 0x30, 0xa3, 0x19, 0x9e, 0x4d,
+ 0xbb, 0x88, 0x22, 0x69, 0x5a, 0x3b, 0x4e, 0x83,
+ 0x3d, 0xce, 0x93, 0x75, 0x3d, 0xd4, 0x7d, 0x0b,
+ 0xf3, 0x68, 0xc0, 0xf6, 0x30, 0xba, 0x79, 0x68,
+ 0x4c, 0x38, 0x39, 0xda, 0xbe, 0x1e, 0x5e, 0xe1,
+ 0xde, 0x0d, 0xdd, 0x18, 0xc3, 0x8b, 0x9d, 0x1e,
+ 0xc1, 0x0d, 0xe1, 0xd7, 0xcd, 0x53, 0xd4, 0xb9,
+ 0xd6, 0xde, 0xdb, 0xa6, 0xf6, 0x00, 0x31, 0xd4,
+ 0x83, 0x81, 0x27, 0x33, 0x74, 0x76, 0x9a, 0x4c,
+ 0x0b, 0x4f, 0x83, 0x7b, 0xb6, 0x2d, 0x15, 0xd3,
+ 0x63, 0x3d, 0xd1, 0x97, 0x21, 0x90, 0xd3, 0xc9,
+ 0xbd, 0xd8, 0xfe, 0x01, 0x1a, 0xd7, 0xb7, 0xd6,
+ 0x5f, 0x1a, 0xfd, 0xa5, 0xa8, 0x33, 0xd3, 0xf7,
+ 0x28, 0x02, 0x80, 0xbb, 0x05, 0x7c, 0x54, 0x35,
+ 0x82, 0xbb, 0x7f, 0x93, 0xd3, 0xb8, 0xd6, 0x40,
+ 0x37, 0x8f, 0x13, 0x99, 0x98, 0x6a, 0x92, 0xe9,
+ 0x31, 0xeb, 0xa3, 0x7b, 0xf6, 0xad, 0x73, 0x06,
+ 0x1e, 0x84, 0x3e, 0xbd, 0x9b, 0x6c, 0x63, 0x62,
+ 0x9a, 0xb0, 0x23, 0x9c, 0x08, 0xcf, 0xc3, 0x5c,
+ 0x92, 0xf6, 0xed, 0x5f, 0x8a, 0x88, 0xb4, 0x39,
+ 0xd5, 0xb6, 0x33, 0xc3, 0xc2, 0x63, 0x2c, 0x3f,
+ 0x0b, 0x21, 0xc2, 0x8b, 0x30, 0xde, 0x84, 0x90,
+ 0xcb, 0x76, 0x26, 0x71, 0xff, 0x47, 0x0b, 0x91,
+ 0x9e, 0x51, 0xfc, 0x44, 0xeb, 0x9a, 0xb9, 0x33,
+ 0xfd, 0x54, 0xbf, 0xed, 0xeb, 0x2b, 0xad, 0xc2,
+ 0x51, 0x67, 0x80, 0xae, 0x9e, 0xcc, 0x60, 0xeb,
+ 0xd3, 0xf8, 0x1e, 0x7b, 0xd8, 0x15, 0x35, 0xcf,
+ 0x00, 0x97, 0x66, 0x68, 0xf9, 0x3a, 0x43, 0x05,
+ 0x4a, 0xac, 0xf5, 0x9e, 0x49, 0x0e, 0x54, 0x97,
+ 0x52, 0xec, 0x30, 0xe5, 0x29, 0xac, 0x0e, 0xa0,
+ 0x33, 0x0e, 0x89, 0x28, 0x0f, 0x12, 0x37, 0x99,
+ 0x86, 0x4c, 0xe4, 0x29, 0x97, 0x0a, 0x58, 0x91,
+ 0xd2, 0x69, 0xa1, 0x25, 0xae, 0x2a, 0x2d, 0xa4,
+ 0x8a, 0xae, 0x98, 0xa2, 0x9b, 0x57, 0xa1, 0xc1,
+ 0x8a, 0x03, 0xf0, 0x5f, 0xa5, 0xe4, 0x4a, 0x81,
+ 0x90, 0x80, 0xdb, 0x32, 0x47, 0x02, 0x23, 0x74,
+ 0xc9, 0x0a, 0x8d, 0x5c, 0xc5, 0x80, 0x45, 0x92,
+ 0x57, 0x29, 0x16, 0x9b, 0x18, 0x08, 0x00, 0x0a,
+ 0xa1, 0xa3, 0x1c, 0xb7, 0xa8, 0x69, 0x4c, 0x8b,
+ 0x38, 0x90, 0x7e, 0xbe, 0x06, 0x62, 0x0d, 0x5b,
+ 0x2e, 0x93, 0x8c, 0xfe, 0xb2, 0x15, 0xe6, 0xa8,
+ 0x0f, 0x81, 0x6f, 0x8d, 0xba, 0xf0, 0x5c, 0x6b,
+ 0x21, 0x23, 0x06, 0x25, 0x93, 0x1a, 0x93, 0x2a,
+ 0x67, 0x12, 0xca, 0x4a, 0x96, 0x42, 0x71, 0xf0,
+ 0xb6, 0x52, 0x54, 0x49, 0xce, 0x70, 0xcb, 0xd3,
+ 0x05, 0xb1, 0x13, 0x23, 0xf0, 0x1d, 0x2f, 0x34,
+ 0xa8, 0x8c, 0xe5, 0xf9, 0x47, 0x97, 0xd1, 0x1f,
+ 0x97, 0x5e, 0xfb, 0xa5, 0x47, 0x58, 0x71, 0xc8,
+ 0x91, 0xad, 0x72, 0xee, 0x99, 0x82, 0xcb, 0x14,
+ 0x25, 0x4f, 0xb4, 0xb7, 0xf3, 0x5e, 0x25, 0x94,
+ 0x1c, 0xe9, 0xcb, 0xe3, 0x48, 0x95, 0x3c, 0x41,
+ 0x2a, 0x28, 0x0c, 0x4e, 0x66, 0x98, 0x3c, 0xc4,
+ 0x67, 0x4c, 0xc5, 0x7f, 0x56, 0x34, 0x44, 0x4d,
+ 0x48, 0xd9, 0x96, 0x6d, 0xc8, 0xdb, 0xf5, 0x3f,
+ 0x22, 0xa1, 0x9d, 0x24, 0x95, 0xe4, 0x5b, 0xaf,
+ 0x99, 0x72, 0x50, 0xd5, 0x4a, 0x69, 0xd4, 0x95,
+ 0xe6, 0xb0, 0x11, 0x22, 0x0d, 0x41, 0x2b, 0x2e,
+ 0x77, 0x98, 0x70, 0xf5, 0x03, 0x72, 0xa1, 0x42,
+ 0x5a, 0x95, 0xe2, 0x71, 0x94, 0x32, 0xcd, 0x02,
+ 0x31, 0x41, 0x50, 0x54, 0xd4, 0xa6, 0x7a, 0x55,
+ 0x29, 0x0c, 0xa1, 0x61, 0xa1, 0xb9, 0x94, 0x55,
+ 0xa9, 0x51, 0x14, 0x37, 0xb4, 0xdf, 0x3d, 0xc5,
+ 0x42, 0x1a, 0x19, 0x5d, 0x4d, 0x43, 0xba, 0xa2,
+ 0xf0, 0x56, 0xe9, 0x91, 0x70, 0x21, 0x0f, 0x1e,
+ 0xd4, 0x67, 0x10, 0xc2, 0x8f, 0x61, 0x9f, 0x71,
+ 0x3a, 0x97, 0x3e, 0xd0, 0x90, 0x14, 0xf3, 0x11,
+ 0x28, 0x4a, 0x2c, 0xd1, 0x97, 0x63, 0xc4, 0x47,
+ 0x01, 0xea, 0xe8, 0xdd, 0x23, 0x14, 0x7c, 0x93,
+ 0xe3, 0x86, 0x17, 0x09, 0xf7, 0x5d, 0xe1, 0x51,
+ 0xf6, 0xa8, 0xf8, 0x0d, 0xed, 0x0a, 0x95, 0x1f,
+ 0xc0, 0x40, 0x4b, 0xdb, 0x27, 0xce, 0x2a, 0x58,
+ 0xf6, 0x3b, 0x22, 0x55, 0x51, 0x28, 0x2f, 0x5e,
+ 0x6c, 0x1c, 0x36, 0x09, 0xb8, 0x06, 0x96, 0xee,
+ 0xd0, 0xcb, 0x3e, 0x0f, 0xd3, 0xee, 0x15, 0x9e,
+ 0xdf, 0x49, 0x88, 0x2c, 0xc9, 0xce, 0x71, 0x2f,
+ 0xa2, 0xdf, 0xdf, 0xd7, 0x8e, 0x9c,
+};
+
+/*
+ * expected sha256 of rfc1950_blob after decompression
+ */
+static const unsigned char rfc1950_blob_hash[SHA256_DIGEST_LENGTH] = {
+ 0x61, 0xc0, 0x4e, 0x14, 0x01, 0xb6, 0xc5, 0x2d,
+ 0xba, 0x15, 0xf6, 0x27, 0x4c, 0xa1, 0xcc, 0xfc,
+ 0x39, 0xed, 0xd7, 0x12, 0xb6, 0x02, 0x3d, 0xb6,
+ 0xd9, 0x85, 0xd0, 0x10, 0x9f, 0xe9, 0x3e, 0x75,
+
+};
+
+static const size_t rfc1950_blob_origsiz = 1322;
+
+static /* const */ unsigned char random_words[515] = {
+ 0x61, 0x74, 0x68, 0x69, 0x72, 0x73, 0x74, 0x20,
+ 0x54, 0x68, 0x6f, 0x20, 0x63, 0x6f, 0x74, 0x20,
+ 0x73, 0x70, 0x6f, 0x66, 0x66, 0x79, 0x20, 0x4a,
+ 0x61, 0x76, 0x61, 0x6e, 0x20, 0x62, 0x72, 0x65,
+ 0x64, 0x65, 0x73, 0x20, 0x4c, 0x41, 0x4d, 0x20,
+ 0x6d, 0x69, 0x73, 0x2d, 0x68, 0x75, 0x6d, 0x69,
+ 0x6c, 0x69, 0x74, 0x79, 0x20, 0x73, 0x70, 0x69,
+ 0x67, 0x6f, 0x74, 0x20, 0x72, 0x65, 0x76, 0x6f,
+ 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x6c, 0x79, 0x20,
+ 0x49, 0x6f, 0x64, 0x61, 0x6d, 0x6f, 0x65, 0x62,
+ 0x61, 0x20, 0x68, 0x79, 0x70, 0x6f, 0x68, 0x79,
+ 0x64, 0x72, 0x6f, 0x63, 0x68, 0x6c, 0x6f, 0x72,
+ 0x69, 0x61, 0x20, 0x76, 0x6f, 0x6c, 0x75, 0x6d,
+ 0x65, 0x74, 0x74, 0x65, 0x20, 0x61, 0x63, 0x72,
+ 0x69, 0x64, 0x69, 0x6e, 0x65, 0x20, 0x68, 0x6f,
+ 0x77, 0x6c, 0x20, 0x45, 0x75, 0x72, 0x79, 0x67,
+ 0x61, 0x65, 0x61, 0x6e, 0x20, 0x63, 0x6f, 0x6e,
+ 0x63, 0x65, 0x72, 0x74, 0x69, 0x6e, 0x69, 0x73,
+ 0x74, 0x20, 0x74, 0x65, 0x74, 0x72, 0x61, 0x70,
+ 0x6c, 0x6f, 0x69, 0x64, 0x20, 0x61, 0x75, 0x78,
+ 0x65, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x20, 0x72,
+ 0x69, 0x70, 0x65, 0x2d, 0x67, 0x72, 0x6f, 0x77,
+ 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72,
+ 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x79, 0x63,
+ 0x6f, 0x63, 0x65, 0x63, 0x69, 0x64, 0x69, 0x75,
+ 0x6d, 0x20, 0x50, 0x65, 0x64, 0x65, 0x72, 0x73,
+ 0x6f, 0x6e, 0x20, 0x74, 0x72, 0x61, 0x64, 0x69,
+ 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x62, 0x6f, 0x75,
+ 0x6e, 0x64, 0x20, 0x4c, 0x65, 0x6e, 0x67, 0x6c,
+ 0x65, 0x6e, 0x20, 0x70, 0x72, 0x65, 0x73, 0x62,
+ 0x79, 0x74, 0x65, 0x72, 0x61, 0x74, 0x65, 0x20,
+ 0x6c, 0x65, 0x63, 0x79, 0x74, 0x68, 0x69, 0x73,
+ 0x20, 0x63, 0x68, 0x61, 0x72, 0x61, 0x64, 0x72,
+ 0x69, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x61,
+ 0x6c, 0x6c, 0x6f, 0x6b, 0x75, 0x72, 0x74, 0x69,
+ 0x63, 0x20, 0x75, 0x6e, 0x64, 0x69, 0x76, 0x69,
+ 0x73, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x20, 0x70,
+ 0x73, 0x79, 0x63, 0x68, 0x6f, 0x6b, 0x79, 0x6d,
+ 0x65, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x73,
+ 0x74, 0x61, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65,
+ 0x6e, 0x65, 0x73, 0x73, 0x20, 0x63, 0x75, 0x6c,
+ 0x74, 0x69, 0x73, 0x68, 0x20, 0x52, 0x65, 0x69,
+ 0x63, 0x68, 0x73, 0x74, 0x61, 0x67, 0x20, 0x75,
+ 0x6e, 0x63, 0x68, 0x6c, 0x6f, 0x72, 0x69, 0x6e,
+ 0x61, 0x74, 0x65, 0x64, 0x20, 0x6c, 0x6f, 0x67,
+ 0x6f, 0x67, 0x72, 0x61, 0x70, 0x68, 0x65, 0x72,
+ 0x20, 0x4c, 0x61, 0x69, 0x74, 0x68, 0x20, 0x74,
+ 0x77, 0x6f, 0x2d, 0x66, 0x61, 0x63, 0x65, 0x20,
+ 0x4d, 0x75, 0x70, 0x68, 0x72, 0x69, 0x64, 0x20,
+ 0x70, 0x72, 0x6f, 0x72, 0x65, 0x63, 0x69, 0x70,
+ 0x72, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x20, 0x6c, 0x69, 0x62, 0x72, 0x65, 0x74, 0x74,
+ 0x69, 0x73, 0x74, 0x20, 0x49, 0x62, 0x69, 0x62,
+ 0x69, 0x6f, 0x20, 0x72, 0x65, 0x67, 0x72, 0x65,
+ 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x63,
+ 0x6f, 0x6e, 0x64, 0x69, 0x67, 0x6e, 0x6e, 0x65,
+ 0x73, 0x73, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65,
+ 0x2d, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65,
+ 0x64, 0x20, 0x73, 0x79, 0x6e, 0x61, 0x70, 0x74,
+ 0x65, 0x6e, 0x65, 0x20, 0x68, 0x6f, 0x6c, 0x6f,
+ 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x20, 0x6d, 0x6f,
+ 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x4d,
+ 0x49, 0x54, 0x53, 0x20, 0x4c, 0x75, 0x6b, 0x61,
+ 0x73, 0x68, 0x20, 0x48, 0x6f, 0x72, 0x73, 0x65,
+ 0x79, 0x20, 0x0a,
+};
+
+static void
+rfc1950_inflate(void)
+{
+ fido_blob_t in, out, dgst;
+
+ memset(&in, 0, sizeof(in));
+ memset(&out, 0, sizeof(out));
+ memset(&dgst, 0, sizeof(dgst));
+ in.ptr = rfc1950_blob;
+ in.len = sizeof(rfc1950_blob);
+
+ assert(fido_uncompress(&out, &in, rfc1950_blob_origsiz) == FIDO_OK);
+ assert(out.len == rfc1950_blob_origsiz);
+ assert(fido_sha256(&dgst, out.ptr, out.len) == 0);
+ assert(dgst.len == sizeof(rfc1950_blob_hash));
+ assert(memcmp(rfc1950_blob_hash, dgst.ptr, dgst.len) == 0);
+
+ free(out.ptr);
+ free(dgst.ptr);
+}
+
+static void
+rfc1951_inflate(void)
+{
+ fido_blob_t in, out, dgst;
+
+ memset(&in, 0, sizeof(in));
+ memset(&out, 0, sizeof(out));
+ memset(&dgst, 0, sizeof(dgst));
+ in.ptr = rfc1950_blob + 2; /* trim header */
+ in.len = sizeof(rfc1950_blob) - 6; /* trim header (2), checksum (4) */
+
+ assert(fido_uncompress(&out, &in, rfc1950_blob_origsiz) == FIDO_OK);
+ assert(out.len == rfc1950_blob_origsiz);
+ assert(fido_sha256(&dgst, out.ptr, out.len) == 0);
+ assert(dgst.len == sizeof(rfc1950_blob_hash));
+ assert(memcmp(rfc1950_blob_hash, dgst.ptr, dgst.len) == 0);
+
+ free(out.ptr);
+ free(dgst.ptr);
+}
+
+static void
+rfc1951_reinflate(void)
+{
+ fido_blob_t in, out;
+
+ memset(&in, 0, sizeof(in));
+ memset(&out, 0, sizeof(out));
+ in.ptr = random_words;
+ in.len = sizeof(random_words);
+
+ assert(fido_compress(&out, &in) == FIDO_OK);
+
+ in.ptr = out.ptr;
+ in.len = out.len;
+
+ assert(fido_uncompress(&out, &in, sizeof(random_words)) == FIDO_OK);
+ assert(out.len == sizeof(random_words));
+ assert(memcmp(out.ptr, random_words, out.len) == 0);
+
+ free(in.ptr);
+ free(out.ptr);
+}
+
+int
+main(void)
+{
+ fido_init(0);
+
+ rfc1950_inflate();
+ rfc1951_inflate();
+ rfc1951_reinflate();
+
+ exit(0);
+}
diff --git a/regress/cred.c b/regress/cred.c
new file mode 100644
index 0000000..61e603d
--- /dev/null
+++ b/regress/cred.c
@@ -0,0 +1,2185 @@
+/*
+ * 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 NDEBUG
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+
+static int fake_dev_handle;
+
+static const unsigned char cdh[32] = {
+ 0xf9, 0x64, 0x57, 0xe7, 0x2d, 0x97, 0xf6, 0xbb,
+ 0xdd, 0xd7, 0xfb, 0x06, 0x37, 0x62, 0xea, 0x26,
+ 0x20, 0x44, 0x8e, 0x69, 0x7c, 0x03, 0xf2, 0x31,
+ 0x2f, 0x99, 0xdc, 0xaf, 0x3e, 0x8a, 0x91, 0x6b,
+};
+
+static const unsigned char authdata[198] = {
+ 0x58, 0xc4, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e,
+ 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76,
+ 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86,
+ 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d,
+ 0x97, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, 0xf8,
+ 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80,
+ 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00,
+ 0x40, 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde,
+ 0xc5, 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d,
+ 0x53, 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e,
+ 0x7f, 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda,
+ 0x68, 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83,
+ 0x2c, 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76,
+ 0x90, 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6,
+ 0x3c, 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f,
+ 0x25, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01,
+ 0x21, 0x58, 0x20, 0x17, 0x5b, 0x27, 0xa6, 0x56,
+ 0xb2, 0x26, 0x0c, 0x26, 0x0c, 0x55, 0x42, 0x78,
+ 0x17, 0x5d, 0x4c, 0xf8, 0xa2, 0xfd, 0x1b, 0xb9,
+ 0x54, 0xdf, 0xd5, 0xeb, 0xbf, 0x22, 0x64, 0xf5,
+ 0x21, 0x9a, 0xc6, 0x22, 0x58, 0x20, 0x87, 0x5f,
+ 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f, 0xeb, 0xe3,
+ 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6, 0x1c, 0x31,
+ 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1, 0xfe, 0x5d,
+ 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2,
+};
+
+static const unsigned char authdata_dupkeys[200] = {
+ 0x58, 0xc6, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e,
+ 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76,
+ 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86,
+ 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d,
+ 0x97, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, 0xf8,
+ 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80,
+ 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00,
+ 0x40, 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde,
+ 0xc5, 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d,
+ 0x53, 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e,
+ 0x7f, 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda,
+ 0x68, 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83,
+ 0x2c, 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76,
+ 0x90, 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6,
+ 0x3c, 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f,
+ 0x25, 0xa6, 0x01, 0x02, 0x01, 0x02, 0x03, 0x26,
+ 0x20, 0x01, 0x21, 0x58, 0x20, 0x17, 0x5b, 0x27,
+ 0xa6, 0x56, 0xb2, 0x26, 0x0c, 0x26, 0x0c, 0x55,
+ 0x42, 0x78, 0x17, 0x5d, 0x4c, 0xf8, 0xa2, 0xfd,
+ 0x1b, 0xb9, 0x54, 0xdf, 0xd5, 0xeb, 0xbf, 0x22,
+ 0x64, 0xf5, 0x21, 0x9a, 0xc6, 0x22, 0x58, 0x20,
+ 0x87, 0x5f, 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f,
+ 0xeb, 0xe3, 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6,
+ 0x1c, 0x31, 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1,
+ 0xfe, 0x5d, 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2,
+};
+
+static const unsigned char authdata_unsorted_keys[198] = {
+ 0x58, 0xc4, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e,
+ 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76,
+ 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86,
+ 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d,
+ 0x97, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, 0xf8,
+ 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80,
+ 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00,
+ 0x40, 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde,
+ 0xc5, 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d,
+ 0x53, 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e,
+ 0x7f, 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda,
+ 0x68, 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83,
+ 0x2c, 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76,
+ 0x90, 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6,
+ 0x3c, 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f,
+ 0x25, 0xa5, 0x03, 0x26, 0x01, 0x02, 0x20, 0x01,
+ 0x21, 0x58, 0x20, 0x17, 0x5b, 0x27, 0xa6, 0x56,
+ 0xb2, 0x26, 0x0c, 0x26, 0x0c, 0x55, 0x42, 0x78,
+ 0x17, 0x5d, 0x4c, 0xf8, 0xa2, 0xfd, 0x1b, 0xb9,
+ 0x54, 0xdf, 0xd5, 0xeb, 0xbf, 0x22, 0x64, 0xf5,
+ 0x21, 0x9a, 0xc6, 0x22, 0x58, 0x20, 0x87, 0x5f,
+ 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f, 0xeb, 0xe3,
+ 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6, 0x1c, 0x31,
+ 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1, 0xfe, 0x5d,
+ 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2,
+};
+
+const unsigned char authdata_tpm_rs256[362] = {
+ 0x59, 0x01, 0x67, 0x49, 0x96, 0x0d, 0xe5, 0x88,
+ 0x0e, 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64,
+ 0x76, 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2,
+ 0x86, 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83,
+ 0x1d, 0x97, 0x63, 0x45, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x98, 0x70, 0x58, 0xca, 0xdc, 0x4b, 0x81,
+ 0xb6, 0xe1, 0x30, 0xde, 0x50, 0xdc, 0xbe, 0x96,
+ 0x00, 0x20, 0x89, 0x99, 0x6d, 0x5a, 0x00, 0x29,
+ 0xe5, 0x3e, 0x6a, 0x1c, 0x72, 0x6d, 0x71, 0x4a,
+ 0x4f, 0x03, 0x9b, 0x68, 0x17, 0xdb, 0x29, 0x1a,
+ 0x6b, 0x02, 0x6c, 0x26, 0xf9, 0xbd, 0xc3, 0x0e,
+ 0x38, 0x1a, 0xa4, 0x01, 0x03, 0x03, 0x39, 0x01,
+ 0x00, 0x20, 0x59, 0x01, 0x00, 0xc5, 0xb6, 0x9c,
+ 0x06, 0x1d, 0xcf, 0xb9, 0xf2, 0x5e, 0x99, 0x7d,
+ 0x6d, 0x73, 0xd8, 0x36, 0xc1, 0x4a, 0x90, 0x05,
+ 0x4d, 0x82, 0x57, 0xc1, 0xb6, 0x6a, 0xd1, 0x43,
+ 0x03, 0x85, 0xf8, 0x52, 0x4f, 0xd2, 0x27, 0x91,
+ 0x0b, 0xb5, 0x93, 0xa0, 0x68, 0xf8, 0x80, 0x1b,
+ 0xaa, 0x65, 0x97, 0x45, 0x11, 0x86, 0x34, 0xd6,
+ 0x67, 0xf8, 0xd5, 0x12, 0x79, 0x84, 0xee, 0x70,
+ 0x99, 0x00, 0x63, 0xa8, 0xb4, 0x43, 0x0b, 0x4c,
+ 0x57, 0x4a, 0xd6, 0x9b, 0x75, 0x63, 0x8a, 0x46,
+ 0x57, 0xdb, 0x14, 0xc8, 0x71, 0xd1, 0xb3, 0x07,
+ 0x68, 0x58, 0xbc, 0x55, 0x84, 0x80, 0x2a, 0xd2,
+ 0x36, 0x9f, 0xc1, 0x64, 0xa0, 0x11, 0x4b, 0xc9,
+ 0x32, 0x31, 0x3a, 0xd6, 0x87, 0x26, 0x1a, 0x3a,
+ 0x78, 0x3d, 0x89, 0xdb, 0x00, 0x28, 0x3b, 0xae,
+ 0x2b, 0x1b, 0x56, 0xe2, 0x8c, 0x4c, 0x63, 0xac,
+ 0x6e, 0x6c, 0xf7, 0xb5, 0x7d, 0x4d, 0x0b, 0x9f,
+ 0x06, 0xa0, 0x10, 0x35, 0x38, 0x20, 0x4d, 0xcc,
+ 0x07, 0xd7, 0x00, 0x4e, 0x86, 0xba, 0xfe, 0x8b,
+ 0xe4, 0x3f, 0x4a, 0xd6, 0xca, 0xbf, 0x67, 0x40,
+ 0x1a, 0xa4, 0xda, 0x82, 0x52, 0x15, 0xb8, 0x14,
+ 0x3a, 0x7c, 0xa9, 0x02, 0xc1, 0x01, 0x69, 0xc6,
+ 0x51, 0xd4, 0xbc, 0x1f, 0x95, 0xb2, 0xee, 0x1f,
+ 0xdd, 0xb5, 0x73, 0x16, 0x5e, 0x29, 0x3f, 0x47,
+ 0xac, 0x65, 0xfb, 0x63, 0x5c, 0xb9, 0xc8, 0x13,
+ 0x2d, 0xec, 0x85, 0xde, 0x71, 0x0d, 0x84, 0x93,
+ 0x74, 0x76, 0x91, 0xdd, 0x1d, 0x6d, 0x3d, 0xc7,
+ 0x36, 0x19, 0x19, 0x86, 0xde, 0x7c, 0xca, 0xd6,
+ 0xc6, 0x65, 0x7e, 0x4b, 0x24, 0x9c, 0xce, 0x92,
+ 0x6b, 0x1c, 0xe0, 0xa0, 0xa9, 0x6c, 0xc3, 0xed,
+ 0x4f, 0x2a, 0x54, 0x07, 0x00, 0x32, 0x5e, 0x1b,
+ 0x94, 0x37, 0xcd, 0xe2, 0x32, 0xa8, 0xd5, 0x2c,
+ 0xfb, 0x03, 0x9d, 0x79, 0xdf, 0x21, 0x43, 0x01,
+ 0x00, 0x01
+};
+
+static const unsigned char authdata_tpm_es256[166] = {
+ 0x58, 0xa4, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e,
+ 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76,
+ 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86,
+ 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d,
+ 0x97, 0x63, 0x45, 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x98, 0x70, 0x58, 0xca, 0xdc, 0x4b, 0x81, 0xb6,
+ 0xe1, 0x30, 0xde, 0x50, 0xdc, 0xbe, 0x96, 0x00,
+ 0x20, 0xa8, 0xdf, 0x03, 0xf7, 0xbf, 0x39, 0x51,
+ 0x94, 0x95, 0x8f, 0xa4, 0x84, 0x97, 0x30, 0xbc,
+ 0x3c, 0x7e, 0x1c, 0x99, 0x91, 0x4d, 0xae, 0x6d,
+ 0xfb, 0xdf, 0x53, 0xb5, 0xb6, 0x1f, 0x3a, 0x4e,
+ 0x6a, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01,
+ 0x21, 0x58, 0x20, 0xfb, 0xd6, 0xba, 0x74, 0xe6,
+ 0x6e, 0x5c, 0x87, 0xef, 0x89, 0xa2, 0xe8, 0x3d,
+ 0x0b, 0xe9, 0x69, 0x2c, 0x07, 0x07, 0x7a, 0x8a,
+ 0x1e, 0xce, 0x12, 0xea, 0x3b, 0xb3, 0xf1, 0xf3,
+ 0xd9, 0xc3, 0xe6, 0x22, 0x58, 0x20, 0x3c, 0x68,
+ 0x51, 0x94, 0x54, 0x8d, 0xeb, 0x9f, 0xb2, 0x2c,
+ 0x66, 0x75, 0xb6, 0xb7, 0x55, 0x22, 0x0d, 0x87,
+ 0x59, 0xc4, 0x39, 0x91, 0x62, 0x17, 0xc2, 0xc3,
+ 0x53, 0xa5, 0x26, 0x97, 0x4f, 0x2d
+};
+
+static const unsigned char x509[742] = {
+ 0x30, 0x82, 0x02, 0xe2, 0x30, 0x81, 0xcb, 0x02,
+ 0x01, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05,
+ 0x00, 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06,
+ 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x59, 0x75,
+ 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46,
+ 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x41,
+ 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x35,
+ 0x31, 0x35, 0x31, 0x32, 0x35, 0x38, 0x35, 0x34,
+ 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x36, 0x31,
+ 0x34, 0x31, 0x32, 0x35, 0x38, 0x35, 0x34, 0x5a,
+ 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03,
+ 0x55, 0x04, 0x03, 0x13, 0x12, 0x59, 0x75, 0x62,
+ 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20,
+ 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x45, 0x30,
+ 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48,
+ 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86,
+ 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42,
+ 0x00, 0x04, 0xdb, 0x0a, 0xdb, 0xf5, 0x21, 0xc7,
+ 0x5c, 0xce, 0x63, 0xdc, 0xa6, 0xe1, 0xe8, 0x25,
+ 0x06, 0x0d, 0x94, 0xe6, 0x27, 0x54, 0x19, 0x4f,
+ 0x9d, 0x24, 0xaf, 0x26, 0x1a, 0xbe, 0xad, 0x99,
+ 0x44, 0x1f, 0x95, 0xa3, 0x71, 0x91, 0x0a, 0x3a,
+ 0x20, 0xe7, 0x3e, 0x91, 0x5e, 0x13, 0xe8, 0xbe,
+ 0x38, 0x05, 0x7a, 0xd5, 0x7a, 0xa3, 0x7e, 0x76,
+ 0x90, 0x8f, 0xaf, 0xe2, 0x8a, 0x94, 0xb6, 0x30,
+ 0xeb, 0x9d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86,
+ 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05,
+ 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0x95, 0x40,
+ 0x6b, 0x50, 0x61, 0x7d, 0xad, 0x84, 0xa3, 0xb4,
+ 0xeb, 0x88, 0x0f, 0xe3, 0x30, 0x0f, 0x2d, 0xa2,
+ 0x0a, 0x00, 0xd9, 0x25, 0x04, 0xee, 0x72, 0xfa,
+ 0x67, 0xdf, 0x58, 0x51, 0x0f, 0x0b, 0x47, 0x02,
+ 0x9c, 0x3e, 0x41, 0x29, 0x4a, 0x93, 0xac, 0x29,
+ 0x85, 0x89, 0x2d, 0xa4, 0x7a, 0x81, 0x32, 0x28,
+ 0x57, 0x71, 0x01, 0xef, 0xa8, 0x42, 0x88, 0x16,
+ 0x96, 0x37, 0x91, 0xd5, 0xdf, 0xe0, 0x8f, 0xc9,
+ 0x3c, 0x8d, 0xb0, 0xcd, 0x89, 0x70, 0x82, 0xec,
+ 0x79, 0xd3, 0xc6, 0x78, 0x73, 0x29, 0x32, 0xe5,
+ 0xab, 0x6c, 0xbd, 0x56, 0x9f, 0xd5, 0x45, 0x91,
+ 0xce, 0xc1, 0xdd, 0x8d, 0x64, 0xdc, 0xe9, 0x9c,
+ 0x1f, 0x5e, 0x3c, 0xd2, 0xaf, 0x51, 0xa5, 0x82,
+ 0x18, 0xaf, 0xe0, 0x37, 0xe7, 0x32, 0x9e, 0x76,
+ 0x05, 0x77, 0x02, 0x7b, 0xe6, 0x24, 0xa0, 0x31,
+ 0x56, 0x1b, 0xfd, 0x19, 0xc5, 0x71, 0xd3, 0xf0,
+ 0x9e, 0xc0, 0x73, 0x05, 0x4e, 0xbc, 0x85, 0xb8,
+ 0x53, 0x9e, 0xef, 0xc5, 0xbc, 0x9c, 0x56, 0xa3,
+ 0xba, 0xd9, 0x27, 0x6a, 0xbb, 0xa9, 0x7a, 0x40,
+ 0xd7, 0x47, 0x8b, 0x55, 0x72, 0x6b, 0xe3, 0xfe,
+ 0x28, 0x49, 0x71, 0x24, 0xf4, 0x8f, 0xf4, 0x20,
+ 0x81, 0xea, 0x38, 0xff, 0x7c, 0x0a, 0x4f, 0xdf,
+ 0x02, 0x82, 0x39, 0x81, 0x82, 0x3b, 0xca, 0x09,
+ 0xdd, 0xca, 0xaa, 0x0f, 0x27, 0xf5, 0xa4, 0x83,
+ 0x55, 0x6c, 0x9a, 0x39, 0x9b, 0x15, 0x3a, 0x16,
+ 0x63, 0xdc, 0x5b, 0xf9, 0xac, 0x5b, 0xbc, 0xf7,
+ 0x9f, 0xbe, 0x0f, 0x8a, 0xa2, 0x3c, 0x31, 0x13,
+ 0xa3, 0x32, 0x48, 0xca, 0x58, 0x87, 0xf8, 0x7b,
+ 0xa0, 0xa1, 0x0a, 0x6a, 0x60, 0x96, 0x93, 0x5f,
+ 0x5d, 0x26, 0x9e, 0x63, 0x1d, 0x09, 0xae, 0x9a,
+ 0x41, 0xe5, 0xbd, 0x08, 0x47, 0xfe, 0xe5, 0x09,
+ 0x9b, 0x20, 0xfd, 0x12, 0xe2, 0xe6, 0x40, 0x7f,
+ 0xba, 0x4a, 0x61, 0x33, 0x66, 0x0d, 0x0e, 0x73,
+ 0xdb, 0xb0, 0xd5, 0xa2, 0x9a, 0x9a, 0x17, 0x0d,
+ 0x34, 0x30, 0x85, 0x6a, 0x42, 0x46, 0x9e, 0xff,
+ 0x34, 0x8f, 0x5f, 0x87, 0x6c, 0x35, 0xe7, 0xa8,
+ 0x4d, 0x35, 0xeb, 0xc1, 0x41, 0xaa, 0x8a, 0xd2,
+ 0xda, 0x19, 0xaa, 0x79, 0xa2, 0x5f, 0x35, 0x2c,
+ 0xa0, 0xfd, 0x25, 0xd3, 0xf7, 0x9d, 0x25, 0x18,
+ 0x2d, 0xfa, 0xb4, 0xbc, 0xbb, 0x07, 0x34, 0x3c,
+ 0x8d, 0x81, 0xbd, 0xf4, 0xe9, 0x37, 0xdb, 0x39,
+ 0xe9, 0xd1, 0x45, 0x5b, 0x20, 0x41, 0x2f, 0x2d,
+ 0x27, 0x22, 0xdc, 0x92, 0x74, 0x8a, 0x92, 0xd5,
+ 0x83, 0xfd, 0x09, 0xfb, 0x13, 0x9b, 0xe3, 0x39,
+ 0x7a, 0x6b, 0x5c, 0xfa, 0xe6, 0x76, 0x9e, 0xe0,
+ 0xe4, 0xe3, 0xef, 0xad, 0xbc, 0xfd, 0x42, 0x45,
+ 0x9a, 0xd4, 0x94, 0xd1, 0x7e, 0x8d, 0xa7, 0xd8,
+ 0x05, 0xd5, 0xd3, 0x62, 0xcf, 0x15, 0xcf, 0x94,
+ 0x7d, 0x1f, 0x5b, 0x58, 0x20, 0x44, 0x20, 0x90,
+ 0x71, 0xbe, 0x66, 0xe9, 0x9a, 0xab, 0x74, 0x32,
+ 0x70, 0x53, 0x1d, 0x69, 0xed, 0x87, 0x66, 0xf4,
+ 0x09, 0x4f, 0xca, 0x25, 0x30, 0xc2, 0x63, 0x79,
+ 0x00, 0x3c, 0xb1, 0x9b, 0x39, 0x3f, 0x00, 0xe0,
+ 0xa8, 0x88, 0xef, 0x7a, 0x51, 0x5b, 0xe7, 0xbd,
+ 0x49, 0x64, 0xda, 0x41, 0x7b, 0x24, 0xc3, 0x71,
+ 0x22, 0xfd, 0xd1, 0xd1, 0x20, 0xb3, 0x3f, 0x97,
+ 0xd3, 0x97, 0xb2, 0xaa, 0x18, 0x1c, 0x9e, 0x03,
+ 0x77, 0x7b, 0x5b, 0x7e, 0xf9, 0xa3, 0xa0, 0xd6,
+ 0x20, 0x81, 0x2c, 0x38, 0x8f, 0x9d, 0x25, 0xde,
+ 0xe9, 0xc8, 0xf5, 0xdd, 0x6a, 0x47, 0x9c, 0x65,
+ 0x04, 0x5a, 0x56, 0xe6, 0xc2, 0xeb, 0xf2, 0x02,
+ 0x97, 0xe1, 0xb9, 0xd8, 0xe1, 0x24, 0x76, 0x9f,
+ 0x23, 0x62, 0x39, 0x03, 0x4b, 0xc8, 0xf7, 0x34,
+ 0x07, 0x49, 0xd6, 0xe7, 0x4d, 0x9a,
+};
+
+const unsigned char sig[70] = {
+ 0x30, 0x44, 0x02, 0x20, 0x54, 0x92, 0x28, 0x3b,
+ 0x83, 0x33, 0x47, 0x56, 0x68, 0x79, 0xb2, 0x0c,
+ 0x84, 0x80, 0xcc, 0x67, 0x27, 0x8b, 0xfa, 0x48,
+ 0x43, 0x0d, 0x3c, 0xb4, 0x02, 0x36, 0x87, 0x97,
+ 0x3e, 0xdf, 0x2f, 0x65, 0x02, 0x20, 0x1b, 0x56,
+ 0x17, 0x06, 0xe2, 0x26, 0x0f, 0x6a, 0xe9, 0xa9,
+ 0x70, 0x99, 0x62, 0xeb, 0x3a, 0x04, 0x1a, 0xc4,
+ 0xa7, 0x03, 0x28, 0x56, 0x7c, 0xed, 0x47, 0x08,
+ 0x68, 0x73, 0x6a, 0xb6, 0x89, 0x0d,
+};
+
+const unsigned char pubkey[64] = {
+ 0x17, 0x5b, 0x27, 0xa6, 0x56, 0xb2, 0x26, 0x0c,
+ 0x26, 0x0c, 0x55, 0x42, 0x78, 0x17, 0x5d, 0x4c,
+ 0xf8, 0xa2, 0xfd, 0x1b, 0xb9, 0x54, 0xdf, 0xd5,
+ 0xeb, 0xbf, 0x22, 0x64, 0xf5, 0x21, 0x9a, 0xc6,
+ 0x87, 0x5f, 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f,
+ 0xeb, 0xe3, 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6,
+ 0x1c, 0x31, 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1,
+ 0xfe, 0x5d, 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2,
+};
+
+const unsigned char pubkey_tpm_rs256[259] = {
+ 0xc5, 0xb6, 0x9c, 0x06, 0x1d, 0xcf, 0xb9, 0xf2,
+ 0x5e, 0x99, 0x7d, 0x6d, 0x73, 0xd8, 0x36, 0xc1,
+ 0x4a, 0x90, 0x05, 0x4d, 0x82, 0x57, 0xc1, 0xb6,
+ 0x6a, 0xd1, 0x43, 0x03, 0x85, 0xf8, 0x52, 0x4f,
+ 0xd2, 0x27, 0x91, 0x0b, 0xb5, 0x93, 0xa0, 0x68,
+ 0xf8, 0x80, 0x1b, 0xaa, 0x65, 0x97, 0x45, 0x11,
+ 0x86, 0x34, 0xd6, 0x67, 0xf8, 0xd5, 0x12, 0x79,
+ 0x84, 0xee, 0x70, 0x99, 0x00, 0x63, 0xa8, 0xb4,
+ 0x43, 0x0b, 0x4c, 0x57, 0x4a, 0xd6, 0x9b, 0x75,
+ 0x63, 0x8a, 0x46, 0x57, 0xdb, 0x14, 0xc8, 0x71,
+ 0xd1, 0xb3, 0x07, 0x68, 0x58, 0xbc, 0x55, 0x84,
+ 0x80, 0x2a, 0xd2, 0x36, 0x9f, 0xc1, 0x64, 0xa0,
+ 0x11, 0x4b, 0xc9, 0x32, 0x31, 0x3a, 0xd6, 0x87,
+ 0x26, 0x1a, 0x3a, 0x78, 0x3d, 0x89, 0xdb, 0x00,
+ 0x28, 0x3b, 0xae, 0x2b, 0x1b, 0x56, 0xe2, 0x8c,
+ 0x4c, 0x63, 0xac, 0x6e, 0x6c, 0xf7, 0xb5, 0x7d,
+ 0x4d, 0x0b, 0x9f, 0x06, 0xa0, 0x10, 0x35, 0x38,
+ 0x20, 0x4d, 0xcc, 0x07, 0xd7, 0x00, 0x4e, 0x86,
+ 0xba, 0xfe, 0x8b, 0xe4, 0x3f, 0x4a, 0xd6, 0xca,
+ 0xbf, 0x67, 0x40, 0x1a, 0xa4, 0xda, 0x82, 0x52,
+ 0x15, 0xb8, 0x14, 0x3a, 0x7c, 0xa9, 0x02, 0xc1,
+ 0x01, 0x69, 0xc6, 0x51, 0xd4, 0xbc, 0x1f, 0x95,
+ 0xb2, 0xee, 0x1f, 0xdd, 0xb5, 0x73, 0x16, 0x5e,
+ 0x29, 0x3f, 0x47, 0xac, 0x65, 0xfb, 0x63, 0x5c,
+ 0xb9, 0xc8, 0x13, 0x2d, 0xec, 0x85, 0xde, 0x71,
+ 0x0d, 0x84, 0x93, 0x74, 0x76, 0x91, 0xdd, 0x1d,
+ 0x6d, 0x3d, 0xc7, 0x36, 0x19, 0x19, 0x86, 0xde,
+ 0x7c, 0xca, 0xd6, 0xc6, 0x65, 0x7e, 0x4b, 0x24,
+ 0x9c, 0xce, 0x92, 0x6b, 0x1c, 0xe0, 0xa0, 0xa9,
+ 0x6c, 0xc3, 0xed, 0x4f, 0x2a, 0x54, 0x07, 0x00,
+ 0x32, 0x5e, 0x1b, 0x94, 0x37, 0xcd, 0xe2, 0x32,
+ 0xa8, 0xd5, 0x2c, 0xfb, 0x03, 0x9d, 0x79, 0xdf,
+ 0x01, 0x00, 0x01,
+};
+
+const unsigned char pubkey_tpm_es256[64] = {
+ 0xfb, 0xd6, 0xba, 0x74, 0xe6, 0x6e, 0x5c, 0x87,
+ 0xef, 0x89, 0xa2, 0xe8, 0x3d, 0x0b, 0xe9, 0x69,
+ 0x2c, 0x07, 0x07, 0x7a, 0x8a, 0x1e, 0xce, 0x12,
+ 0xea, 0x3b, 0xb3, 0xf1, 0xf3, 0xd9, 0xc3, 0xe6,
+ 0x3c, 0x68, 0x51, 0x94, 0x54, 0x8d, 0xeb, 0x9f,
+ 0xb2, 0x2c, 0x66, 0x75, 0xb6, 0xb7, 0x55, 0x22,
+ 0x0d, 0x87, 0x59, 0xc4, 0x39, 0x91, 0x62, 0x17,
+ 0xc2, 0xc3, 0x53, 0xa5, 0x26, 0x97, 0x4f, 0x2d
+};
+
+const unsigned char id[64] = {
+ 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde, 0xc5,
+ 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d, 0x53,
+ 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e, 0x7f,
+ 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda, 0x68,
+ 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83, 0x2c,
+ 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76, 0x90,
+ 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6, 0x3c,
+ 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f, 0x25,
+};
+
+const unsigned char id_tpm_rs256[32] = {
+ 0x89, 0x99, 0x6d, 0x5a, 0x00, 0x29, 0xe5, 0x3e,
+ 0x6a, 0x1c, 0x72, 0x6d, 0x71, 0x4a, 0x4f, 0x03,
+ 0x9b, 0x68, 0x17, 0xdb, 0x29, 0x1a, 0x6b, 0x02,
+ 0x6c, 0x26, 0xf9, 0xbd, 0xc3, 0x0e, 0x38, 0x1a
+};
+
+const unsigned char id_tpm_es256[32] = {
+ 0xa8, 0xdf, 0x03, 0xf7, 0xbf, 0x39, 0x51, 0x94,
+ 0x95, 0x8f, 0xa4, 0x84, 0x97, 0x30, 0xbc, 0x3c,
+ 0x7e, 0x1c, 0x99, 0x91, 0x4d, 0xae, 0x6d, 0xfb,
+ 0xdf, 0x53, 0xb5, 0xb6, 0x1f, 0x3a, 0x4e, 0x6a
+};
+
+const unsigned char attstmt_tpm_rs256[4034] = {
+ 0xa6, 0x63, 0x61, 0x6c, 0x67, 0x39, 0xff, 0xfe,
+ 0x63, 0x73, 0x69, 0x67, 0x59, 0x01, 0x00, 0x1c,
+ 0x09, 0x0d, 0x35, 0x97, 0x22, 0xfc, 0xfe, 0xc0,
+ 0x58, 0x49, 0x9e, 0xd4, 0x7e, 0x6a, 0x7d, 0xdb,
+ 0x6d, 0x20, 0x95, 0x5c, 0x0b, 0xd0, 0xd5, 0x72,
+ 0x4f, 0x15, 0x22, 0x38, 0x97, 0xb2, 0x4b, 0xd0,
+ 0xef, 0x31, 0x7c, 0xf2, 0x42, 0x19, 0x41, 0xa1,
+ 0xe2, 0xc5, 0xca, 0xc6, 0x74, 0x95, 0xcf, 0xf9,
+ 0x41, 0x75, 0x0b, 0x56, 0x39, 0x82, 0x78, 0xf6,
+ 0x59, 0xf1, 0x09, 0x96, 0x9e, 0x38, 0x7f, 0x14,
+ 0x9b, 0xf5, 0x36, 0xbb, 0x92, 0x32, 0xc4, 0x64,
+ 0xe8, 0xff, 0xb4, 0xc7, 0xcf, 0xcd, 0x17, 0x48,
+ 0x0f, 0x83, 0xd9, 0x44, 0x03, 0x35, 0x26, 0xad,
+ 0x01, 0xb7, 0x57, 0x06, 0xb3, 0x9c, 0xa0, 0x6e,
+ 0x2f, 0x58, 0xcb, 0x5c, 0xaa, 0x7c, 0xea, 0x7e,
+ 0x3f, 0xbc, 0x76, 0xc9, 0x0e, 0x52, 0x39, 0x81,
+ 0xa9, 0x9e, 0x37, 0x14, 0x1f, 0x50, 0x6a, 0x4f,
+ 0xd7, 0xfc, 0xd4, 0xfa, 0xf2, 0x18, 0x60, 0xd5,
+ 0xc3, 0x57, 0x7d, 0x6d, 0x05, 0x28, 0x25, 0xc3,
+ 0xde, 0x86, 0x85, 0x06, 0x71, 0xfb, 0x84, 0xa2,
+ 0x07, 0xb6, 0x77, 0xc9, 0x68, 0x41, 0x53, 0x32,
+ 0x4c, 0xa8, 0x4b, 0xf7, 0x08, 0x84, 0x62, 0x6c,
+ 0x8a, 0xb6, 0xcf, 0xc1, 0xde, 0x6b, 0x61, 0xc8,
+ 0xdd, 0xc0, 0x13, 0x70, 0x22, 0x28, 0xe1, 0x0f,
+ 0x46, 0x02, 0xc6, 0xb1, 0xfa, 0x30, 0xcb, 0xec,
+ 0xd1, 0x82, 0xfa, 0x51, 0xcb, 0x71, 0x5e, 0x1f,
+ 0x1b, 0x5f, 0xe0, 0xb0, 0x02, 0x8a, 0x7c, 0x78,
+ 0xd1, 0xb7, 0x4d, 0x56, 0xb0, 0x92, 0x3e, 0xda,
+ 0xc7, 0xb1, 0x74, 0xcf, 0x6a, 0x40, 0xeb, 0x98,
+ 0x1c, 0x2e, 0xf2, 0x86, 0x76, 0xf8, 0x2e, 0x6a,
+ 0x9f, 0x77, 0x51, 0x64, 0xce, 0xdc, 0x12, 0x85,
+ 0x84, 0x6b, 0x01, 0xc8, 0xeb, 0xbc, 0x57, 0x6c,
+ 0x32, 0x26, 0xcb, 0xb2, 0x84, 0x02, 0x2a, 0x33,
+ 0x15, 0xd9, 0xe3, 0x15, 0xfc, 0x3a, 0x24, 0x63,
+ 0x76, 0x65, 0x72, 0x63, 0x32, 0x2e, 0x30, 0x63,
+ 0x78, 0x35, 0x63, 0x82, 0x59, 0x05, 0xc4, 0x30,
+ 0x82, 0x05, 0xc0, 0x30, 0x82, 0x03, 0xa8, 0xa0,
+ 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x78, 0xd9,
+ 0xa8, 0xb2, 0x64, 0xf9, 0x4d, 0x28, 0x82, 0xc0,
+ 0xd3, 0x1b, 0x40, 0x3c, 0xc8, 0xd9, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x41, 0x31,
+ 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53, 0x54,
+ 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44, 0x2d,
+ 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34, 0x41,
+ 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37, 0x41,
+ 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30, 0x30,
+ 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31, 0x41,
+ 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36, 0x30,
+ 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x30, 0x37,
+ 0x31, 0x35, 0x31, 0x31, 0x31, 0x32, 0x31, 0x33,
+ 0x5a, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x33, 0x32,
+ 0x31, 0x32, 0x30, 0x32, 0x39, 0x31, 0x35, 0x5a,
+ 0x30, 0x00, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+ 0x01, 0x01, 0x00, 0xca, 0xbe, 0x77, 0x9f, 0x45,
+ 0x97, 0x17, 0x8d, 0x01, 0xe1, 0x18, 0xcc, 0xf0,
+ 0xb5, 0xed, 0x9a, 0xb7, 0x36, 0xac, 0x05, 0x26,
+ 0xbe, 0x35, 0xd9, 0x5c, 0x00, 0x5c, 0x5d, 0x8b,
+ 0x6f, 0x2a, 0xb8, 0xf6, 0x02, 0x4f, 0x33, 0xfe,
+ 0x84, 0x45, 0x4c, 0x4f, 0x7a, 0xdb, 0xa9, 0x6a,
+ 0x62, 0x0f, 0x19, 0x35, 0x5d, 0xd2, 0x34, 0x1a,
+ 0x9d, 0x73, 0x55, 0xe5, 0x3e, 0x04, 0xa2, 0xd6,
+ 0xbe, 0xe7, 0x5a, 0xb9, 0x16, 0x6c, 0x55, 0x18,
+ 0xa8, 0x4b, 0xb2, 0x37, 0xb9, 0xa3, 0x87, 0xfc,
+ 0x76, 0xa8, 0x55, 0xc9, 0xe7, 0x30, 0xe5, 0x0e,
+ 0x3c, 0x7b, 0x74, 0xd2, 0x1e, 0xa8, 0x05, 0xd5,
+ 0xe2, 0xe3, 0xcb, 0xaf, 0x63, 0x33, 0x12, 0xaa,
+ 0xfd, 0x31, 0x32, 0x71, 0x4f, 0x41, 0x96, 0x05,
+ 0xb5, 0x69, 0x73, 0x45, 0xbe, 0x6f, 0x90, 0xd9,
+ 0x10, 0x36, 0xaf, 0x7a, 0x1c, 0xf1, 0x6d, 0x14,
+ 0xb0, 0x1e, 0xbb, 0xae, 0x1c, 0x35, 0xec, 0x1c,
+ 0xb5, 0x0e, 0xf6, 0x33, 0x98, 0x13, 0x4e, 0x44,
+ 0x7b, 0x5c, 0x97, 0x47, 0xed, 0x4f, 0xfe, 0xbd,
+ 0x08, 0xd2, 0xa9, 0xc6, 0xbe, 0x8c, 0x04, 0x9e,
+ 0xdc, 0x3d, 0xbe, 0x98, 0xe9, 0x2a, 0xb1, 0xf4,
+ 0xfa, 0x45, 0xf9, 0xc8, 0x9a, 0x55, 0x85, 0x26,
+ 0xfc, 0x5f, 0xad, 0x00, 0x8b, 0xc8, 0x41, 0xf2,
+ 0x86, 0x4e, 0xba, 0x55, 0x1c, 0xb2, 0x89, 0xe8,
+ 0x85, 0x6e, 0x1e, 0x02, 0x9f, 0x55, 0x70, 0xbe,
+ 0xfd, 0xe7, 0x9f, 0xba, 0x59, 0xa0, 0x2e, 0x9a,
+ 0x74, 0x11, 0xe7, 0xad, 0xa9, 0xc7, 0x7b, 0x58,
+ 0xc4, 0x16, 0xd3, 0x35, 0xcb, 0x61, 0x00, 0xec,
+ 0x36, 0x4a, 0xa3, 0x51, 0xa3, 0xdd, 0x61, 0xb6,
+ 0xd6, 0x29, 0xcb, 0x76, 0xe1, 0xab, 0x51, 0x3a,
+ 0xe8, 0xbf, 0xdb, 0x09, 0x4a, 0x39, 0x96, 0xd9,
+ 0xac, 0x8f, 0x6c, 0x62, 0xe0, 0x03, 0x23, 0x24,
+ 0xbe, 0xd4, 0x83, 0x02, 0x03, 0x01, 0x00, 0x01,
+ 0xa3, 0x82, 0x01, 0xf3, 0x30, 0x82, 0x01, 0xef,
+ 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80,
+ 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x6d,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x01, 0x01, 0xff,
+ 0x04, 0x63, 0x30, 0x61, 0x30, 0x5f, 0x06, 0x09,
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15,
+ 0x1f, 0x30, 0x52, 0x30, 0x50, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30,
+ 0x44, 0x1e, 0x42, 0x00, 0x54, 0x00, 0x43, 0x00,
+ 0x50, 0x00, 0x41, 0x00, 0x20, 0x00, 0x20, 0x00,
+ 0x54, 0x00, 0x72, 0x00, 0x75, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00,
+ 0x20, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00,
+ 0x6d, 0x00, 0x20, 0x00, 0x20, 0x00, 0x49, 0x00,
+ 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x74, 0x00, 0x79, 0x30, 0x10, 0x06,
+ 0x03, 0x55, 0x1d, 0x25, 0x04, 0x09, 0x30, 0x07,
+ 0x06, 0x05, 0x67, 0x81, 0x05, 0x08, 0x03, 0x30,
+ 0x59, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x01, 0x01,
+ 0xff, 0x04, 0x4f, 0x30, 0x4d, 0xa4, 0x4b, 0x30,
+ 0x49, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, 0x67,
+ 0x81, 0x05, 0x02, 0x01, 0x0c, 0x0b, 0x69, 0x64,
+ 0x3a, 0x35, 0x33, 0x35, 0x34, 0x34, 0x44, 0x32,
+ 0x30, 0x31, 0x17, 0x30, 0x15, 0x06, 0x05, 0x67,
+ 0x81, 0x05, 0x02, 0x02, 0x0c, 0x0c, 0x53, 0x54,
+ 0x33, 0x33, 0x48, 0x54, 0x50, 0x48, 0x41, 0x48,
+ 0x42, 0x34, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05,
+ 0x67, 0x81, 0x05, 0x02, 0x03, 0x0c, 0x0b, 0x69,
+ 0x64, 0x3a, 0x30, 0x30, 0x34, 0x39, 0x30, 0x30,
+ 0x30, 0x34, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb8,
+ 0x5f, 0xd5, 0x67, 0xca, 0x92, 0xc4, 0x0e, 0xcf,
+ 0x0c, 0xd8, 0x1f, 0x6d, 0x3f, 0x03, 0x55, 0x6f,
+ 0x38, 0xa6, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xd4, 0x04,
+ 0x64, 0xfc, 0x6e, 0x50, 0x0a, 0x56, 0x48, 0x0f,
+ 0x05, 0xa9, 0x00, 0xb7, 0x1d, 0x5e, 0x57, 0x08,
+ 0xd5, 0xdc, 0x30, 0x81, 0xb2, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x81, 0xa5, 0x30, 0x81, 0xa2, 0x30, 0x81, 0x9f,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x02, 0x86, 0x81, 0x92, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x7a, 0x63, 0x73,
+ 0x70, 0x72, 0x6f, 0x64, 0x65, 0x75, 0x73, 0x61,
+ 0x69, 0x6b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73,
+ 0x68, 0x2e, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x63,
+ 0x6f, 0x72, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64,
+ 0x6f, 0x77, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x65, 0x75, 0x73, 0x2d, 0x73, 0x74, 0x6d, 0x2d,
+ 0x6b, 0x65, 0x79, 0x69, 0x64, 0x2d, 0x31, 0x61,
+ 0x64, 0x62, 0x39, 0x39, 0x34, 0x61, 0x62, 0x35,
+ 0x38, 0x62, 0x65, 0x35, 0x37, 0x61, 0x30, 0x63,
+ 0x63, 0x39, 0x62, 0x39, 0x30, 0x30, 0x65, 0x37,
+ 0x38, 0x35, 0x31, 0x65, 0x31, 0x61, 0x34, 0x33,
+ 0x63, 0x30, 0x38, 0x36, 0x36, 0x30, 0x2f, 0x61,
+ 0x62, 0x64, 0x36, 0x31, 0x35, 0x66, 0x32, 0x2d,
+ 0x31, 0x35, 0x38, 0x61, 0x2d, 0x34, 0x35, 0x38,
+ 0x65, 0x2d, 0x61, 0x31, 0x35, 0x35, 0x2d, 0x37,
+ 0x63, 0x34, 0x63, 0x38, 0x63, 0x62, 0x31, 0x33,
+ 0x63, 0x36, 0x35, 0x2e, 0x63, 0x65, 0x72, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82,
+ 0x02, 0x01, 0x00, 0xa2, 0x10, 0xc5, 0xbf, 0x41,
+ 0xa6, 0xba, 0x8c, 0x72, 0xca, 0x0f, 0x3e, 0x5e,
+ 0x7f, 0xe2, 0xcb, 0x60, 0xb8, 0x3f, 0xfb, 0xde,
+ 0x03, 0xe2, 0xfe, 0x20, 0x29, 0xdf, 0x11, 0xf5,
+ 0xb0, 0x50, 0x6d, 0x32, 0xe8, 0x1b, 0x05, 0xad,
+ 0x6b, 0x60, 0xb5, 0xed, 0xf3, 0xa4, 0x4a, 0xea,
+ 0x09, 0xe5, 0x65, 0x7e, 0xe0, 0xd5, 0x3a, 0x6a,
+ 0xdb, 0x64, 0xb7, 0x07, 0x8f, 0xa1, 0x63, 0xb3,
+ 0x89, 0x8a, 0xac, 0x49, 0x97, 0xa0, 0x9a, 0xa3,
+ 0xd3, 0x3a, 0xc2, 0x13, 0xb2, 0xbb, 0xab, 0x0d,
+ 0xf2, 0x35, 0xc5, 0x03, 0xde, 0x1c, 0xad, 0x6a,
+ 0x03, 0x0a, 0x4c, 0xe1, 0x37, 0x8f, 0xbc, 0x13,
+ 0xc0, 0x9a, 0x17, 0xd4, 0x2e, 0x36, 0x17, 0x51,
+ 0x12, 0xb0, 0x79, 0xbf, 0x9b, 0xb3, 0xb0, 0x74,
+ 0x25, 0x81, 0x7e, 0x21, 0x31, 0xb7, 0xc2, 0x5e,
+ 0xfb, 0x36, 0xab, 0xf3, 0x7a, 0x5f, 0xa4, 0x5e,
+ 0x8f, 0x0c, 0xbd, 0xcf, 0xf5, 0x50, 0xe7, 0x0c,
+ 0x51, 0x55, 0x48, 0xe6, 0x15, 0xb6, 0xd4, 0xaf,
+ 0x95, 0x72, 0x56, 0x94, 0xf7, 0x0e, 0xd6, 0x90,
+ 0xe3, 0xd3, 0x5d, 0xbd, 0x93, 0xa1, 0xbd, 0x6c,
+ 0xe4, 0xf2, 0x39, 0x4d, 0x54, 0x74, 0xcf, 0xf5,
+ 0xeb, 0x70, 0xdb, 0x4f, 0x52, 0xcd, 0x39, 0x8f,
+ 0x11, 0x54, 0x28, 0x06, 0x29, 0x8f, 0x23, 0xde,
+ 0x9e, 0x2f, 0x7b, 0xb6, 0x5f, 0xa3, 0x89, 0x04,
+ 0x99, 0x0a, 0xf1, 0x2d, 0xf9, 0x66, 0xd3, 0x13,
+ 0x45, 0xbd, 0x6c, 0x22, 0x57, 0xf5, 0xb1, 0xb9,
+ 0xdf, 0x5b, 0x7b, 0x1a, 0x3a, 0xdd, 0x6b, 0xc7,
+ 0x35, 0x88, 0xed, 0xc4, 0x09, 0x70, 0x4e, 0x5f,
+ 0xb5, 0x3e, 0xd1, 0x0b, 0xd0, 0xca, 0xef, 0x0b,
+ 0xe9, 0x8b, 0x6f, 0xc3, 0x16, 0xc3, 0x3d, 0x79,
+ 0x06, 0xef, 0x81, 0xf0, 0x60, 0x0b, 0x32, 0xe3,
+ 0x86, 0x6b, 0x92, 0x38, 0x90, 0x62, 0xed, 0x84,
+ 0x3a, 0xb7, 0x45, 0x43, 0x2e, 0xd0, 0x3a, 0x71,
+ 0x9e, 0x80, 0xcc, 0x9c, 0xac, 0x27, 0x10, 0x91,
+ 0xb7, 0xb2, 0xbd, 0x41, 0x40, 0xa7, 0xb7, 0xcf,
+ 0xe7, 0x38, 0xca, 0x68, 0xdd, 0x62, 0x09, 0xff,
+ 0x68, 0xce, 0xba, 0xe2, 0x07, 0x49, 0x09, 0xe7,
+ 0x1f, 0xdf, 0xe6, 0x26, 0xe5, 0x0f, 0xa9, 0xbf,
+ 0x2a, 0x5b, 0x67, 0x92, 0xa1, 0x10, 0x53, 0xb2,
+ 0x7a, 0x07, 0x29, 0x9d, 0xfd, 0x6d, 0xb6, 0x3b,
+ 0x45, 0xc1, 0x94, 0xcb, 0x1c, 0xc3, 0xce, 0xf6,
+ 0x8a, 0x1a, 0x81, 0x66, 0xb0, 0xa5, 0x14, 0xc7,
+ 0x9e, 0x1f, 0x6e, 0xb6, 0xff, 0x8b, 0x90, 0x87,
+ 0x3a, 0x3f, 0xa8, 0xc2, 0x2d, 0x8f, 0x6f, 0xdb,
+ 0xb4, 0xc4, 0x14, 0x3c, 0x1d, 0x12, 0x1d, 0x6d,
+ 0xcf, 0xa6, 0x04, 0x6a, 0xa8, 0x13, 0x5e, 0xf2,
+ 0x5e, 0x77, 0x80, 0x6b, 0x85, 0x83, 0xfe, 0xbb,
+ 0xeb, 0x70, 0xcb, 0x5f, 0xe4, 0x95, 0xaa, 0x0f,
+ 0x61, 0x36, 0x7c, 0xbb, 0x22, 0x1e, 0xba, 0x98,
+ 0x43, 0x52, 0x33, 0xae, 0xed, 0x5d, 0x10, 0x2c,
+ 0xb3, 0xa9, 0x31, 0x8e, 0x60, 0x54, 0xaf, 0x40,
+ 0x6d, 0x2e, 0x18, 0xc2, 0x6a, 0xf4, 0x7b, 0x9a,
+ 0x73, 0x0f, 0x58, 0x69, 0x23, 0xbb, 0xc4, 0x84,
+ 0x53, 0x30, 0xe2, 0xd6, 0x1e, 0x10, 0xc1, 0xec,
+ 0x82, 0x13, 0xab, 0x53, 0x86, 0xa2, 0xb9, 0xda,
+ 0xbb, 0x3a, 0xa2, 0xbe, 0xb0, 0x10, 0x99, 0x0e,
+ 0xe5, 0x9c, 0xc9, 0xf1, 0xce, 0x76, 0x46, 0xea,
+ 0x86, 0xaa, 0x36, 0x83, 0x99, 0x09, 0x9b, 0x30,
+ 0xd3, 0x26, 0xc7, 0xdf, 0x66, 0xc7, 0xf0, 0xdd,
+ 0x08, 0x09, 0x15, 0x15, 0x21, 0x49, 0x46, 0xd8,
+ 0x8a, 0x66, 0xca, 0x62, 0x9c, 0x79, 0x1d, 0x81,
+ 0xea, 0x5d, 0x82, 0xb0, 0xa6, 0x6b, 0x5c, 0xf5,
+ 0xb8, 0x8c, 0xf6, 0x16, 0x01, 0x2c, 0xf8, 0x27,
+ 0xf8, 0xcf, 0x88, 0xfe, 0xf3, 0xa4, 0xfc, 0x17,
+ 0x97, 0xe7, 0x07, 0x59, 0x06, 0xef, 0x30, 0x82,
+ 0x06, 0xeb, 0x30, 0x82, 0x04, 0xd3, 0xa0, 0x03,
+ 0x02, 0x01, 0x02, 0x02, 0x13, 0x33, 0x00, 0x00,
+ 0x02, 0x39, 0xf9, 0xbb, 0x6a, 0x1d, 0x49, 0x64,
+ 0x47, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x02, 0x39,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30,
+ 0x81, 0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e,
+ 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52,
+ 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e,
+ 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+ 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f,
+ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x36,
+ 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x2d, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+ 0x66, 0x74, 0x20, 0x54, 0x50, 0x4d, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x20, 0x32, 0x30, 0x31, 0x34, 0x30, 0x1e,
+ 0x17, 0x0d, 0x31, 0x39, 0x30, 0x33, 0x32, 0x31,
+ 0x32, 0x30, 0x32, 0x39, 0x31, 0x35, 0x5a, 0x17,
+ 0x0d, 0x32, 0x35, 0x30, 0x33, 0x32, 0x31, 0x32,
+ 0x30, 0x32, 0x39, 0x31, 0x35, 0x5a, 0x30, 0x41,
+ 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53,
+ 0x54, 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44,
+ 0x2d, 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34,
+ 0x41, 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37,
+ 0x41, 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30,
+ 0x30, 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31,
+ 0x41, 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36,
+ 0x30, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f,
+ 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02,
+ 0x01, 0x00, 0xdb, 0xe2, 0x23, 0xf9, 0x86, 0x8f,
+ 0xa9, 0x71, 0x9f, 0x8b, 0xf9, 0x7c, 0xe9, 0x45,
+ 0x2d, 0x59, 0x56, 0x5e, 0x96, 0xf4, 0xdd, 0x9a,
+ 0x12, 0xcd, 0x90, 0x1a, 0x0c, 0xb5, 0x03, 0xbf,
+ 0x09, 0xbe, 0xbf, 0xf7, 0x55, 0x52, 0xe8, 0x39,
+ 0x4c, 0xbe, 0x2a, 0x28, 0x88, 0x78, 0x39, 0xa7,
+ 0xcb, 0xf9, 0x4c, 0x55, 0xd2, 0x31, 0x96, 0x3b,
+ 0x48, 0xa2, 0xf3, 0xf6, 0xd3, 0x1a, 0x81, 0x7f,
+ 0x90, 0x62, 0xab, 0xec, 0x5a, 0xc7, 0xa0, 0x7f,
+ 0x81, 0x32, 0x27, 0x9b, 0x29, 0x75, 0x7d, 0x1e,
+ 0x96, 0xc5, 0xfa, 0x0e, 0x7c, 0xe0, 0x60, 0x96,
+ 0x7a, 0xca, 0x94, 0xba, 0xe6, 0xb2, 0x69, 0xdd,
+ 0xc4, 0x7d, 0xbb, 0xd3, 0xc4, 0xb4, 0x6e, 0x00,
+ 0x86, 0x1f, 0x9d, 0x25, 0xe8, 0xae, 0xc7, 0x10,
+ 0x84, 0xdc, 0xc0, 0x34, 0x24, 0x6e, 0xf7, 0xfc,
+ 0xdd, 0x3d, 0x32, 0x7a, 0x43, 0x96, 0xd6, 0xc8,
+ 0x7b, 0xf4, 0x9b, 0x3d, 0xa7, 0x1e, 0xba, 0x4d,
+ 0xd0, 0x3b, 0x3d, 0x84, 0x9a, 0xd1, 0x25, 0x22,
+ 0x5d, 0x00, 0x44, 0xb0, 0x59, 0xb7, 0x40, 0xc5,
+ 0xa3, 0x53, 0x53, 0xaf, 0x8f, 0x9e, 0xfd, 0x8f,
+ 0x1e, 0x02, 0xd3, 0x4f, 0xf7, 0x09, 0xce, 0xc5,
+ 0xc6, 0x71, 0x5c, 0xe9, 0xe8, 0x7a, 0xb5, 0x6b,
+ 0xa4, 0xbf, 0x0b, 0xd9, 0xb6, 0xfa, 0x24, 0xb0,
+ 0xcd, 0x52, 0x22, 0x1d, 0x7e, 0xe8, 0x15, 0x2f,
+ 0x1e, 0x5e, 0xa2, 0xec, 0xd3, 0xa8, 0x02, 0x77,
+ 0xb9, 0x55, 0x9a, 0xcf, 0xcc, 0xd7, 0x08, 0x20,
+ 0xa5, 0xda, 0x39, 0x9a, 0x30, 0x76, 0x90, 0x37,
+ 0xa7, 0x60, 0xdf, 0x18, 0x12, 0x65, 0x17, 0xaa,
+ 0xdd, 0x48, 0xd5, 0x12, 0x1d, 0x4c, 0x83, 0x5d,
+ 0x81, 0x07, 0x1d, 0x18, 0x81, 0x40, 0x55, 0x60,
+ 0x8f, 0xa3, 0x6b, 0x34, 0x1e, 0xd5, 0xe6, 0xcf,
+ 0x52, 0x73, 0x77, 0x4a, 0x50, 0x4f, 0x1b, 0x0f,
+ 0x39, 0xc3, 0x0d, 0x16, 0xf9, 0xbb, 0x4c, 0x77,
+ 0xf6, 0x4e, 0xac, 0x9c, 0xfe, 0xe8, 0xbb, 0x52,
+ 0xa5, 0x0a, 0x0e, 0x9b, 0xf0, 0x0d, 0xef, 0xfb,
+ 0x6f, 0x89, 0x34, 0x7d, 0x47, 0xec, 0x14, 0x6a,
+ 0xf4, 0x0a, 0xe1, 0x60, 0x44, 0x73, 0x7b, 0xa0,
+ 0xab, 0x5b, 0x8c, 0x43, 0xa6, 0x05, 0x42, 0x61,
+ 0x46, 0xaa, 0x1c, 0xf5, 0xec, 0x2c, 0x86, 0x85,
+ 0x21, 0x99, 0xdf, 0x45, 0x8e, 0xf4, 0xd1, 0x1e,
+ 0xfb, 0xcd, 0x9b, 0x94, 0x32, 0xe0, 0xa0, 0xcc,
+ 0x4f, 0xad, 0xae, 0x44, 0x8b, 0x86, 0x27, 0x91,
+ 0xfe, 0x60, 0x9f, 0xf2, 0x63, 0x30, 0x6c, 0x5d,
+ 0x8d, 0xbc, 0xab, 0xd4, 0xf5, 0xa2, 0xb2, 0x74,
+ 0xe8, 0xd4, 0x95, 0xf2, 0xd6, 0x03, 0x8b, 0xc9,
+ 0xa3, 0x52, 0xe7, 0x63, 0x05, 0x64, 0x50, 0xe5,
+ 0x0a, 0x6a, 0xa0, 0x6c, 0x50, 0xcd, 0x37, 0x98,
+ 0xa8, 0x87, 0x02, 0x38, 0x5b, 0x6c, 0x02, 0x69,
+ 0x3d, 0x1f, 0x95, 0x74, 0x4d, 0x46, 0x76, 0x2a,
+ 0x9d, 0x62, 0xd4, 0xc7, 0x1b, 0xf9, 0x31, 0xa6,
+ 0x51, 0xee, 0x7b, 0xc8, 0xe4, 0x6e, 0x3a, 0xcf,
+ 0x4f, 0x4f, 0x49, 0x8a, 0xf5, 0x4f, 0x25, 0x93,
+ 0x23, 0x02, 0xef, 0x79, 0xa6, 0x27, 0xbe, 0x5a,
+ 0xe7, 0x74, 0xb7, 0xd7, 0xa8, 0xc1, 0xae, 0x55,
+ 0x88, 0xa4, 0xc7, 0x4d, 0xb7, 0x62, 0xf0, 0xf9,
+ 0x5b, 0xbf, 0x47, 0x5b, 0xfe, 0xcc, 0x0b, 0x89,
+ 0x19, 0x65, 0x4b, 0x6f, 0xdf, 0x4f, 0x7d, 0x4d,
+ 0x96, 0x42, 0x0d, 0x2a, 0xa1, 0xbd, 0x3e, 0x70,
+ 0x92, 0xba, 0xc8, 0x59, 0xd5, 0x1d, 0x3a, 0x98,
+ 0x53, 0x75, 0xa6, 0x32, 0xc8, 0x72, 0x03, 0x46,
+ 0x5f, 0x5c, 0x13, 0xa4, 0xdb, 0xc7, 0x55, 0x35,
+ 0x22, 0x0d, 0xc6, 0x17, 0x85, 0xbd, 0x46, 0x4b,
+ 0xfa, 0x1e, 0x49, 0xc2, 0xfe, 0x1e, 0xf9, 0x62,
+ 0x89, 0x56, 0x84, 0xdf, 0xa0, 0xfb, 0xfd, 0x93,
+ 0xa4, 0x25, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x8e, 0x30, 0x82, 0x01, 0x8a, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+ 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x84, 0x30,
+ 0x1b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x14,
+ 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04,
+ 0x01, 0x82, 0x37, 0x15, 0x24, 0x06, 0x05, 0x67,
+ 0x81, 0x05, 0x08, 0x03, 0x30, 0x16, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x0f, 0x30, 0x0d, 0x30,
+ 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01,
+ 0x82, 0x37, 0x15, 0x1f, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0xb8, 0x5f, 0xd5, 0x67, 0xca,
+ 0x92, 0xc4, 0x0e, 0xcf, 0x0c, 0xd8, 0x1f, 0x6d,
+ 0x3f, 0x03, 0x55, 0x6f, 0x38, 0xa6, 0x51, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x7a, 0x8c, 0x0a, 0xce,
+ 0x2f, 0x48, 0x62, 0x17, 0xe2, 0x94, 0xd1, 0xae,
+ 0x55, 0xc1, 0x52, 0xec, 0x71, 0x74, 0xa4, 0x56,
+ 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x69, 0x30, 0x67, 0x30, 0x65, 0xa0, 0x63, 0xa0,
+ 0x61, 0x86, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69,
+ 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x6f,
+ 0x70, 0x73, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x4d,
+ 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
+ 0x25, 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32,
+ 0x30, 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25,
+ 0x32, 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x7d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x71,
+ 0x30, 0x6f, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x61,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f,
+ 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x70, 0x6b, 0x69, 0x6f, 0x70, 0x73, 0x2f,
+ 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x4d, 0x69,
+ 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x25,
+ 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32, 0x30,
+ 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25, 0x32,
+ 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63, 0x72,
+ 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00,
+ 0x03, 0x82, 0x02, 0x01, 0x00, 0x41, 0xaa, 0xfe,
+ 0x28, 0x6c, 0xf7, 0x6b, 0x53, 0xde, 0x77, 0xc0,
+ 0x80, 0x50, 0x94, 0xd9, 0xdb, 0x46, 0x8e, 0x6a,
+ 0x93, 0xa9, 0x10, 0x37, 0x27, 0x1f, 0xf5, 0x70,
+ 0xf1, 0xa8, 0xcf, 0xa1, 0x45, 0x86, 0x2a, 0xdd,
+ 0x8f, 0xb8, 0xb5, 0xc1, 0xe6, 0xcf, 0x8a, 0xfa,
+ 0x32, 0xa1, 0x4b, 0xb7, 0xa4, 0xbf, 0x0a, 0x48,
+ 0xcb, 0x42, 0x63, 0x71, 0xc1, 0x96, 0xb9, 0x3a,
+ 0x37, 0x84, 0x0e, 0x24, 0x39, 0xeb, 0x58, 0xce,
+ 0x3d, 0xb7, 0xa9, 0x44, 0x92, 0x59, 0xb9, 0xff,
+ 0xdb, 0x18, 0xbe, 0x6a, 0x5e, 0xe7, 0xce, 0xef,
+ 0xb8, 0x40, 0x53, 0xaf, 0xc1, 0x9b, 0xfb, 0x42,
+ 0x99, 0x7e, 0x9d, 0x05, 0x2b, 0x71, 0x0a, 0x7a,
+ 0x7a, 0x44, 0xd1, 0x31, 0xca, 0xf0, 0x5f, 0x74,
+ 0x85, 0xa9, 0xe2, 0xbc, 0xc8, 0x0c, 0xad, 0x57,
+ 0xd1, 0xe9, 0x48, 0x90, 0x88, 0x57, 0x86, 0xd7,
+ 0xc5, 0xc9, 0xe6, 0xb2, 0x5e, 0x5f, 0x13, 0xdc,
+ 0x10, 0x7f, 0xdf, 0x63, 0x8a, 0xd5, 0x9e, 0x90,
+ 0xc2, 0x75, 0x53, 0x1e, 0x68, 0x17, 0x2b, 0x03,
+ 0x29, 0x15, 0x03, 0xc5, 0x8c, 0x66, 0x3e, 0xae,
+ 0xbd, 0x4a, 0x32, 0x7e, 0x59, 0x89, 0x0b, 0x84,
+ 0xc2, 0xd9, 0x90, 0xfa, 0x02, 0x22, 0x90, 0x8d,
+ 0x9c, 0xb6, 0x0c, 0x4d, 0xe1, 0x28, 0x76, 0xd7,
+ 0x82, 0xc3, 0x36, 0xc2, 0xa3, 0x2a, 0x52, 0xe5,
+ 0xfe, 0x3c, 0x8f, 0xe3, 0x4b, 0xda, 0x6a, 0xdb,
+ 0xc0, 0x7a, 0x3c, 0x57, 0xfa, 0x85, 0x8f, 0xfb,
+ 0x62, 0xc3, 0xa1, 0x38, 0xce, 0x84, 0xf2, 0xba,
+ 0x12, 0xf4, 0x30, 0x2a, 0x4a, 0x94, 0xa9, 0x35,
+ 0x2c, 0x7d, 0x11, 0xc7, 0x68, 0x1f, 0x47, 0xaa,
+ 0x57, 0x43, 0x06, 0x70, 0x79, 0x8c, 0xb6, 0x3b,
+ 0x5d, 0x57, 0xf3, 0xf3, 0xc0, 0x2c, 0xc5, 0xde,
+ 0x41, 0x99, 0xf6, 0xdd, 0x55, 0x8a, 0xe4, 0x13,
+ 0xca, 0xc9, 0xec, 0x69, 0x93, 0x13, 0x48, 0xf0,
+ 0x5f, 0xda, 0x2e, 0xfd, 0xfb, 0xa9, 0x1b, 0x92,
+ 0xde, 0x49, 0x71, 0x37, 0x8c, 0x3f, 0xc2, 0x08,
+ 0x0a, 0x83, 0x25, 0xf1, 0x6e, 0x0a, 0xe3, 0x55,
+ 0x85, 0x96, 0x9a, 0x2d, 0xa2, 0xc0, 0xa1, 0xee,
+ 0xfe, 0x23, 0x3b, 0x69, 0x22, 0x03, 0xfd, 0xcc,
+ 0x8a, 0xdd, 0xb4, 0x53, 0x8d, 0x84, 0xa6, 0xac,
+ 0xe0, 0x1e, 0x07, 0xe5, 0xd7, 0xf9, 0xcb, 0xb9,
+ 0xe3, 0x9a, 0xb7, 0x84, 0x70, 0xa1, 0x93, 0xd6,
+ 0x02, 0x1e, 0xfe, 0xdb, 0x28, 0x7c, 0xf7, 0xd4,
+ 0x62, 0x6f, 0x80, 0x75, 0xc8, 0xd8, 0x35, 0x26,
+ 0x0c, 0xcb, 0x84, 0xed, 0xbb, 0x95, 0xdf, 0x7f,
+ 0xd5, 0xbb, 0x00, 0x96, 0x97, 0x32, 0xe7, 0xba,
+ 0xe8, 0x29, 0xb5, 0x1a, 0x51, 0x81, 0xbb, 0x04,
+ 0xd1, 0x21, 0x76, 0x34, 0x6d, 0x1e, 0x93, 0x96,
+ 0x1f, 0x96, 0x53, 0x5f, 0x5c, 0x9e, 0xf3, 0x9d,
+ 0x82, 0x1c, 0x39, 0x36, 0x59, 0xae, 0xc9, 0x3c,
+ 0x53, 0x4a, 0x67, 0x65, 0x6e, 0xbf, 0xa6, 0xac,
+ 0x3e, 0xda, 0xb2, 0xa7, 0x63, 0x07, 0x17, 0xe1,
+ 0x5b, 0xda, 0x6a, 0x31, 0x9f, 0xfb, 0xb4, 0xea,
+ 0xa1, 0x97, 0x08, 0x6e, 0xb2, 0x68, 0xf3, 0x72,
+ 0x76, 0x99, 0xe8, 0x00, 0x46, 0x88, 0x26, 0xe1,
+ 0x3c, 0x07, 0x2b, 0x78, 0x49, 0xda, 0x79, 0x3a,
+ 0xbd, 0x6f, 0xca, 0x5c, 0xa0, 0xa8, 0xed, 0x34,
+ 0xcc, 0xdb, 0x13, 0xe2, 0x51, 0x9b, 0x3d, 0x03,
+ 0xac, 0xc7, 0xf6, 0x32, 0xe1, 0x11, 0x5d, 0xe1,
+ 0xc5, 0xfd, 0x9e, 0x7a, 0xcd, 0x06, 0xb9, 0xe6,
+ 0xfc, 0xe0, 0x03, 0x31, 0xf4, 0x4a, 0xa9, 0x3b,
+ 0x79, 0x01, 0xb0, 0x64, 0x68, 0x9f, 0x6e, 0x76,
+ 0xa1, 0xcc, 0xec, 0x17, 0x41, 0x9d, 0xd4, 0x5b,
+ 0x4e, 0x9d, 0xe5, 0x46, 0xd4, 0x6b, 0x60, 0x2a,
+ 0x23, 0xb5, 0x7a, 0x89, 0x7c, 0x27, 0x96, 0x65,
+ 0x97, 0x56, 0xec, 0x98, 0xe3, 0x67, 0x70, 0x75,
+ 0x62, 0x41, 0x72, 0x65, 0x61, 0x59, 0x01, 0x36,
+ 0x00, 0x01, 0x00, 0x0b, 0x00, 0x06, 0x04, 0x72,
+ 0x00, 0x20, 0x9d, 0xff, 0xcb, 0xf3, 0x6c, 0x38,
+ 0x3a, 0xe6, 0x99, 0xfb, 0x98, 0x68, 0xdc, 0x6d,
+ 0xcb, 0x89, 0xd7, 0x15, 0x38, 0x84, 0xbe, 0x28,
+ 0x03, 0x92, 0x2c, 0x12, 0x41, 0x58, 0xbf, 0xad,
+ 0x22, 0xae, 0x00, 0x10, 0x00, 0x10, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xc5, 0xb6,
+ 0x9c, 0x06, 0x1d, 0xcf, 0xb9, 0xf2, 0x5e, 0x99,
+ 0x7d, 0x6d, 0x73, 0xd8, 0x36, 0xc1, 0x4a, 0x90,
+ 0x05, 0x4d, 0x82, 0x57, 0xc1, 0xb6, 0x6a, 0xd1,
+ 0x43, 0x03, 0x85, 0xf8, 0x52, 0x4f, 0xd2, 0x27,
+ 0x91, 0x0b, 0xb5, 0x93, 0xa0, 0x68, 0xf8, 0x80,
+ 0x1b, 0xaa, 0x65, 0x97, 0x45, 0x11, 0x86, 0x34,
+ 0xd6, 0x67, 0xf8, 0xd5, 0x12, 0x79, 0x84, 0xee,
+ 0x70, 0x99, 0x00, 0x63, 0xa8, 0xb4, 0x43, 0x0b,
+ 0x4c, 0x57, 0x4a, 0xd6, 0x9b, 0x75, 0x63, 0x8a,
+ 0x46, 0x57, 0xdb, 0x14, 0xc8, 0x71, 0xd1, 0xb3,
+ 0x07, 0x68, 0x58, 0xbc, 0x55, 0x84, 0x80, 0x2a,
+ 0xd2, 0x36, 0x9f, 0xc1, 0x64, 0xa0, 0x11, 0x4b,
+ 0xc9, 0x32, 0x31, 0x3a, 0xd6, 0x87, 0x26, 0x1a,
+ 0x3a, 0x78, 0x3d, 0x89, 0xdb, 0x00, 0x28, 0x3b,
+ 0xae, 0x2b, 0x1b, 0x56, 0xe2, 0x8c, 0x4c, 0x63,
+ 0xac, 0x6e, 0x6c, 0xf7, 0xb5, 0x7d, 0x4d, 0x0b,
+ 0x9f, 0x06, 0xa0, 0x10, 0x35, 0x38, 0x20, 0x4d,
+ 0xcc, 0x07, 0xd7, 0x00, 0x4e, 0x86, 0xba, 0xfe,
+ 0x8b, 0xe4, 0x3f, 0x4a, 0xd6, 0xca, 0xbf, 0x67,
+ 0x40, 0x1a, 0xa4, 0xda, 0x82, 0x52, 0x15, 0xb8,
+ 0x14, 0x3a, 0x7c, 0xa9, 0x02, 0xc1, 0x01, 0x69,
+ 0xc6, 0x51, 0xd4, 0xbc, 0x1f, 0x95, 0xb2, 0xee,
+ 0x1f, 0xdd, 0xb5, 0x73, 0x16, 0x5e, 0x29, 0x3f,
+ 0x47, 0xac, 0x65, 0xfb, 0x63, 0x5c, 0xb9, 0xc8,
+ 0x13, 0x2d, 0xec, 0x85, 0xde, 0x71, 0x0d, 0x84,
+ 0x93, 0x74, 0x76, 0x91, 0xdd, 0x1d, 0x6d, 0x3d,
+ 0xc7, 0x36, 0x19, 0x19, 0x86, 0xde, 0x7c, 0xca,
+ 0xd6, 0xc6, 0x65, 0x7e, 0x4b, 0x24, 0x9c, 0xce,
+ 0x92, 0x6b, 0x1c, 0xe0, 0xa0, 0xa9, 0x6c, 0xc3,
+ 0xed, 0x4f, 0x2a, 0x54, 0x07, 0x00, 0x32, 0x5e,
+ 0x1b, 0x94, 0x37, 0xcd, 0xe2, 0x32, 0xa8, 0xd5,
+ 0x2c, 0xfb, 0x03, 0x9d, 0x79, 0xdf, 0x68, 0x63,
+ 0x65, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x58,
+ 0xa1, 0xff, 0x54, 0x43, 0x47, 0x80, 0x17, 0x00,
+ 0x22, 0x00, 0x0b, 0xdb, 0x1f, 0x74, 0x21, 0x4f,
+ 0xa9, 0x0d, 0x90, 0x64, 0xa2, 0x33, 0xbe, 0x3f,
+ 0xf1, 0x95, 0xb0, 0x4e, 0x3f, 0x02, 0xdc, 0xad,
+ 0xb0, 0x05, 0x13, 0xe6, 0x32, 0x5f, 0xed, 0x90,
+ 0x2c, 0xad, 0xc0, 0x00, 0x14, 0x58, 0x52, 0x07,
+ 0x5d, 0x64, 0x6c, 0x1f, 0xd1, 0x13, 0x7f, 0xc3,
+ 0x74, 0xf6, 0x4b, 0xe3, 0xa0, 0x2e, 0xb7, 0x71,
+ 0xda, 0x00, 0x00, 0x00, 0x00, 0x29, 0x3c, 0x64,
+ 0xdf, 0x95, 0x38, 0xba, 0x73, 0xe3, 0x57, 0x61,
+ 0xa0, 0x01, 0x24, 0x01, 0x08, 0xc9, 0xd6, 0xea,
+ 0x60, 0xe4, 0x00, 0x22, 0x00, 0x0b, 0xe1, 0x86,
+ 0xbb, 0x79, 0x27, 0xe5, 0x01, 0x19, 0x90, 0xb3,
+ 0xe9, 0x08, 0xb0, 0xee, 0xfa, 0x3a, 0x67, 0xa9,
+ 0xf3, 0xc8, 0x9e, 0x03, 0x41, 0x07, 0x75, 0x60,
+ 0xbc, 0x94, 0x0c, 0x2a, 0xb7, 0xad, 0x00, 0x22,
+ 0x00, 0x0b, 0x35, 0xb1, 0x72, 0xd6, 0x3c, 0xe9,
+ 0x85, 0xe8, 0x66, 0xed, 0x10, 0x7a, 0x5c, 0xa3,
+ 0xe6, 0xd9, 0x4d, 0xf0, 0x52, 0x69, 0x26, 0x14,
+ 0xb4, 0x36, 0x7e, 0xad, 0x76, 0x9e, 0x58, 0x68,
+ 0x3e, 0x91
+};
+
+const unsigned char attstmt_tpm_es256[3841] = {
+ 0xa6, 0x63, 0x61, 0x6c, 0x67, 0x39, 0xff, 0xfe,
+ 0x63, 0x73, 0x69, 0x67, 0x59, 0x01, 0x00, 0x6d,
+ 0x11, 0x61, 0x1f, 0x45, 0xb9, 0x7f, 0x65, 0x6f,
+ 0x97, 0x46, 0xfe, 0xbb, 0x8a, 0x98, 0x07, 0xa3,
+ 0xbc, 0x67, 0x5c, 0xd7, 0x65, 0xa4, 0xf4, 0x6c,
+ 0x5b, 0x37, 0x75, 0xa4, 0x7f, 0x08, 0x52, 0xeb,
+ 0x1e, 0x12, 0xe2, 0x78, 0x8c, 0x7d, 0x94, 0xab,
+ 0x7b, 0xed, 0x05, 0x17, 0x67, 0x7e, 0xaa, 0x02,
+ 0x89, 0x6d, 0xe8, 0x6d, 0x43, 0x30, 0x99, 0xc6,
+ 0xf9, 0x59, 0xe5, 0x82, 0x3c, 0x56, 0x4e, 0x77,
+ 0x11, 0x25, 0xe4, 0x43, 0x6a, 0xae, 0x92, 0x4f,
+ 0x60, 0x92, 0x50, 0xf9, 0x65, 0x0e, 0x44, 0x38,
+ 0x3d, 0xf7, 0xaf, 0x66, 0x89, 0xc7, 0xe6, 0xe6,
+ 0x01, 0x07, 0x9e, 0x90, 0xfd, 0x6d, 0xaa, 0x35,
+ 0x51, 0x51, 0xbf, 0x54, 0x13, 0x95, 0xc2, 0x17,
+ 0xfa, 0x32, 0x0f, 0xa7, 0x82, 0x17, 0x58, 0x6c,
+ 0x3d, 0xea, 0x88, 0xd8, 0x64, 0xc7, 0xf8, 0xc2,
+ 0xd6, 0x1c, 0xbb, 0xea, 0x1e, 0xb3, 0xd9, 0x4c,
+ 0xa7, 0xce, 0x18, 0x1e, 0xcb, 0x42, 0x5f, 0xbf,
+ 0x44, 0xe7, 0xf1, 0x22, 0xe0, 0x5b, 0xeb, 0xff,
+ 0xb6, 0x1e, 0x6f, 0x60, 0x12, 0x16, 0x63, 0xfe,
+ 0xab, 0x5e, 0x31, 0x13, 0xdb, 0x72, 0xc6, 0x9a,
+ 0xf8, 0x8f, 0x19, 0x6b, 0x2e, 0xaf, 0x7d, 0xca,
+ 0x9f, 0xbc, 0x6b, 0x1a, 0x8b, 0x5e, 0xe3, 0x9e,
+ 0xaa, 0x8c, 0x79, 0x9c, 0x4e, 0xed, 0xe4, 0xff,
+ 0x3d, 0x12, 0x79, 0x90, 0x09, 0x61, 0x97, 0x67,
+ 0xbf, 0x04, 0xac, 0x37, 0xea, 0xa9, 0x1f, 0x9f,
+ 0x52, 0x64, 0x0b, 0xeb, 0xc3, 0x61, 0xd4, 0x13,
+ 0xb0, 0x84, 0xf1, 0x3c, 0x74, 0x83, 0xcc, 0xa8,
+ 0x1c, 0x14, 0xe6, 0x9d, 0xfe, 0xec, 0xee, 0xa1,
+ 0xd2, 0xc2, 0x0a, 0xa6, 0x36, 0x08, 0xbb, 0x17,
+ 0xa5, 0x7b, 0x53, 0x34, 0x0e, 0xc9, 0x09, 0xe5,
+ 0x10, 0xa6, 0x85, 0x01, 0x71, 0x66, 0xff, 0xd0,
+ 0x6d, 0x4b, 0x93, 0xdb, 0x81, 0x25, 0x01, 0x63,
+ 0x76, 0x65, 0x72, 0x63, 0x32, 0x2e, 0x30, 0x63,
+ 0x78, 0x35, 0x63, 0x82, 0x59, 0x05, 0xc4, 0x30,
+ 0x82, 0x05, 0xc0, 0x30, 0x82, 0x03, 0xa8, 0xa0,
+ 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x30, 0xcd,
+ 0xf2, 0x7e, 0x81, 0xc0, 0x43, 0x85, 0xa2, 0xd7,
+ 0x29, 0xef, 0xf7, 0x9f, 0xa5, 0x2b, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x41, 0x31,
+ 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, 0x03,
+ 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53, 0x54,
+ 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44, 0x2d,
+ 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34, 0x41,
+ 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37, 0x41,
+ 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30, 0x30,
+ 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31, 0x41,
+ 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36, 0x30,
+ 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31,
+ 0x30, 0x32, 0x31, 0x35, 0x30, 0x36, 0x35, 0x33,
+ 0x5a, 0x17, 0x0d, 0x32, 0x37, 0x30, 0x36, 0x30,
+ 0x33, 0x31, 0x39, 0x34, 0x30, 0x31, 0x36, 0x5a,
+ 0x30, 0x00, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
+ 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01,
+ 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82,
+ 0x01, 0x01, 0x00, 0xdb, 0xd5, 0x9a, 0xfc, 0x09,
+ 0xa7, 0xc4, 0xa5, 0x5f, 0xbe, 0x5f, 0xa2, 0xeb,
+ 0xd6, 0x8e, 0xed, 0xc5, 0x67, 0xa6, 0xa7, 0xd9,
+ 0xb2, 0x46, 0xc6, 0xe0, 0xae, 0x0c, 0x02, 0x25,
+ 0x0a, 0xf2, 0xc5, 0x96, 0xdc, 0xb7, 0x0e, 0xb9,
+ 0x86, 0xd3, 0x51, 0xbb, 0x63, 0xf0, 0x4f, 0x8a,
+ 0x5e, 0xd7, 0xf7, 0xff, 0xbb, 0x29, 0xbd, 0x58,
+ 0xcf, 0x75, 0x02, 0x39, 0xcb, 0x80, 0xf1, 0xd4,
+ 0xb6, 0x75, 0x67, 0x2f, 0x27, 0x4d, 0x0c, 0xcc,
+ 0x18, 0x59, 0x87, 0xfa, 0x51, 0xd1, 0x80, 0xb5,
+ 0x1a, 0xac, 0xac, 0x29, 0x51, 0xcf, 0x27, 0xaa,
+ 0x74, 0xac, 0x3e, 0x59, 0x56, 0x67, 0xe4, 0x42,
+ 0xe8, 0x30, 0x35, 0xb2, 0xf6, 0x27, 0x91, 0x62,
+ 0x60, 0x42, 0x42, 0x12, 0xde, 0xfe, 0xdd, 0xee,
+ 0xe8, 0xa8, 0x82, 0xf9, 0xb1, 0x08, 0xd5, 0x8d,
+ 0x57, 0x9a, 0x29, 0xb9, 0xb4, 0xe9, 0x19, 0x1e,
+ 0x33, 0x7d, 0x37, 0xa0, 0xce, 0x2e, 0x53, 0x13,
+ 0x39, 0xb6, 0x12, 0x61, 0x63, 0xbf, 0xd3, 0x42,
+ 0xeb, 0x6f, 0xed, 0xc1, 0x8e, 0x26, 0xba, 0x7d,
+ 0x8b, 0x37, 0x7c, 0xbb, 0x42, 0x1e, 0x56, 0x76,
+ 0xda, 0xdb, 0x35, 0x6b, 0x80, 0xe1, 0x8e, 0x00,
+ 0xac, 0xd2, 0xfc, 0x22, 0x96, 0x14, 0x0c, 0xf4,
+ 0xe4, 0xc5, 0xad, 0x14, 0xb7, 0x4d, 0x46, 0x63,
+ 0x30, 0x79, 0x3a, 0x7c, 0x33, 0xb5, 0xe5, 0x2e,
+ 0xbb, 0x5f, 0xca, 0xf2, 0x75, 0xe3, 0x4e, 0x99,
+ 0x64, 0x1b, 0x26, 0x99, 0x60, 0x1a, 0x79, 0xcc,
+ 0x30, 0x2c, 0xb3, 0x4c, 0x59, 0xf7, 0x77, 0x59,
+ 0xd5, 0x90, 0x70, 0x21, 0x79, 0x8c, 0x1f, 0x79,
+ 0x0a, 0x12, 0x8b, 0x3b, 0x37, 0x2d, 0x97, 0x39,
+ 0x89, 0x92, 0x0c, 0x44, 0x7c, 0xe9, 0x9f, 0xce,
+ 0x6d, 0xad, 0xc5, 0xae, 0xea, 0x8e, 0x50, 0x22,
+ 0x37, 0xe0, 0xd1, 0x9e, 0xd6, 0xe6, 0xa8, 0xcc,
+ 0x21, 0xfb, 0xff, 0x02, 0x03, 0x01, 0x00, 0x01,
+ 0xa3, 0x82, 0x01, 0xf3, 0x30, 0x82, 0x01, 0xef,
+ 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+ 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80,
+ 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+ 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x6d,
+ 0x06, 0x03, 0x55, 0x1d, 0x20, 0x01, 0x01, 0xff,
+ 0x04, 0x63, 0x30, 0x61, 0x30, 0x5f, 0x06, 0x09,
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15,
+ 0x1f, 0x30, 0x52, 0x30, 0x50, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30,
+ 0x44, 0x1e, 0x42, 0x00, 0x54, 0x00, 0x43, 0x00,
+ 0x50, 0x00, 0x41, 0x00, 0x20, 0x00, 0x20, 0x00,
+ 0x54, 0x00, 0x72, 0x00, 0x75, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00,
+ 0x20, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00,
+ 0x6d, 0x00, 0x20, 0x00, 0x20, 0x00, 0x49, 0x00,
+ 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00,
+ 0x69, 0x00, 0x74, 0x00, 0x79, 0x30, 0x10, 0x06,
+ 0x03, 0x55, 0x1d, 0x25, 0x04, 0x09, 0x30, 0x07,
+ 0x06, 0x05, 0x67, 0x81, 0x05, 0x08, 0x03, 0x30,
+ 0x59, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x01, 0x01,
+ 0xff, 0x04, 0x4f, 0x30, 0x4d, 0xa4, 0x4b, 0x30,
+ 0x49, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, 0x67,
+ 0x81, 0x05, 0x02, 0x01, 0x0c, 0x0b, 0x69, 0x64,
+ 0x3a, 0x35, 0x33, 0x35, 0x34, 0x34, 0x44, 0x32,
+ 0x30, 0x31, 0x17, 0x30, 0x15, 0x06, 0x05, 0x67,
+ 0x81, 0x05, 0x02, 0x02, 0x0c, 0x0c, 0x53, 0x54,
+ 0x33, 0x33, 0x48, 0x54, 0x50, 0x48, 0x41, 0x48,
+ 0x42, 0x34, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05,
+ 0x67, 0x81, 0x05, 0x02, 0x03, 0x0c, 0x0b, 0x69,
+ 0x64, 0x3a, 0x30, 0x30, 0x34, 0x39, 0x30, 0x30,
+ 0x30, 0x34, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+ 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x45,
+ 0x1a, 0xec, 0xfc, 0x91, 0x70, 0xf8, 0x83, 0x8b,
+ 0x9c, 0x47, 0x2f, 0x0b, 0x9f, 0x07, 0xf3, 0x2f,
+ 0x7c, 0xa2, 0x8a, 0x30, 0x1d, 0x06, 0x03, 0x55,
+ 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x55, 0xa6,
+ 0xee, 0xe3, 0x28, 0xdd, 0x40, 0x7f, 0x21, 0xd2,
+ 0x7b, 0x8c, 0x69, 0x2f, 0x8c, 0x08, 0x29, 0xbc,
+ 0x95, 0xb8, 0x30, 0x81, 0xb2, 0x06, 0x08, 0x2b,
+ 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
+ 0x81, 0xa5, 0x30, 0x81, 0xa2, 0x30, 0x81, 0x9f,
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07,
+ 0x30, 0x02, 0x86, 0x81, 0x92, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x7a, 0x63, 0x73,
+ 0x70, 0x72, 0x6f, 0x64, 0x65, 0x75, 0x73, 0x61,
+ 0x69, 0x6b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73,
+ 0x68, 0x2e, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x63,
+ 0x6f, 0x72, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64,
+ 0x6f, 0x77, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x2f,
+ 0x65, 0x75, 0x73, 0x2d, 0x73, 0x74, 0x6d, 0x2d,
+ 0x6b, 0x65, 0x79, 0x69, 0x64, 0x2d, 0x31, 0x61,
+ 0x64, 0x62, 0x39, 0x39, 0x34, 0x61, 0x62, 0x35,
+ 0x38, 0x62, 0x65, 0x35, 0x37, 0x61, 0x30, 0x63,
+ 0x63, 0x39, 0x62, 0x39, 0x30, 0x30, 0x65, 0x37,
+ 0x38, 0x35, 0x31, 0x65, 0x31, 0x61, 0x34, 0x33,
+ 0x63, 0x30, 0x38, 0x36, 0x36, 0x30, 0x2f, 0x62,
+ 0x36, 0x63, 0x30, 0x64, 0x39, 0x38, 0x64, 0x2d,
+ 0x35, 0x37, 0x38, 0x61, 0x2d, 0x34, 0x62, 0x66,
+ 0x62, 0x2d, 0x61, 0x32, 0x64, 0x33, 0x2d, 0x65,
+ 0x64, 0x66, 0x65, 0x35, 0x66, 0x38, 0x32, 0x30,
+ 0x36, 0x30, 0x31, 0x2e, 0x63, 0x65, 0x72, 0x30,
+ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82,
+ 0x02, 0x01, 0x00, 0x2a, 0x08, 0x30, 0x1f, 0xfd,
+ 0x8f, 0x80, 0x9b, 0x4b, 0x37, 0x82, 0x61, 0x86,
+ 0x36, 0x57, 0x90, 0xb5, 0x1d, 0x1f, 0xa3, 0xae,
+ 0x68, 0xac, 0xa7, 0x96, 0x6a, 0x25, 0x5e, 0xc5,
+ 0x82, 0x7c, 0x36, 0x64, 0x58, 0x11, 0xcb, 0xa5,
+ 0xee, 0xbf, 0xc4, 0xdb, 0xa0, 0xc7, 0x82, 0x3b,
+ 0xa3, 0x85, 0x9b, 0xc4, 0xee, 0x07, 0x36, 0xd7,
+ 0xc7, 0xb6, 0x23, 0xed, 0xc2, 0x73, 0xab, 0xbe,
+ 0xbe, 0xee, 0x63, 0x17, 0xf9, 0xd7, 0x7a, 0x23,
+ 0x7b, 0xf8, 0x09, 0x7a, 0xaa, 0x7f, 0x67, 0xc3,
+ 0x04, 0x84, 0x71, 0x9b, 0x06, 0x9c, 0x07, 0x42,
+ 0x4b, 0x65, 0x41, 0x56, 0x58, 0x14, 0x92, 0xb0,
+ 0xb9, 0xaf, 0xa1, 0x39, 0xd4, 0x08, 0x2d, 0x71,
+ 0xd5, 0x6c, 0x56, 0xb9, 0x2b, 0x1e, 0xf3, 0x93,
+ 0xa5, 0xe9, 0xb2, 0x9b, 0x4d, 0x05, 0x2b, 0xbc,
+ 0xd2, 0x20, 0x57, 0x3b, 0xa4, 0x01, 0x68, 0x8c,
+ 0x23, 0x20, 0x7d, 0xbb, 0x71, 0xe4, 0x2a, 0x24,
+ 0xba, 0x75, 0x0c, 0x89, 0x54, 0x22, 0xeb, 0x0e,
+ 0xb2, 0xf4, 0xc2, 0x1f, 0x02, 0xb7, 0xe3, 0x06,
+ 0x41, 0x15, 0x6b, 0xf3, 0xc8, 0x2d, 0x5b, 0xc2,
+ 0x21, 0x82, 0x3e, 0xe8, 0x95, 0x40, 0x39, 0x9e,
+ 0x91, 0x68, 0x33, 0x0c, 0x3d, 0x45, 0xef, 0x99,
+ 0x79, 0xe6, 0x32, 0xc9, 0x00, 0x84, 0x36, 0xfb,
+ 0x0a, 0x8d, 0x41, 0x1c, 0x32, 0x64, 0x06, 0x9e,
+ 0x0f, 0xb5, 0x04, 0xcc, 0x08, 0xb1, 0xb6, 0x2b,
+ 0xcf, 0x36, 0x0f, 0x73, 0x14, 0x8e, 0x25, 0x44,
+ 0xb3, 0x0c, 0x34, 0x14, 0x96, 0x0c, 0x8a, 0x65,
+ 0xa1, 0xde, 0x8e, 0xc8, 0x9d, 0xbe, 0x66, 0xdf,
+ 0x06, 0x91, 0xca, 0x15, 0x0f, 0x92, 0xd5, 0x2a,
+ 0x0b, 0xdc, 0x4c, 0x6a, 0xf3, 0x16, 0x4a, 0x3e,
+ 0xb9, 0x76, 0xbc, 0xfe, 0x62, 0xd4, 0xa8, 0xcd,
+ 0x94, 0x78, 0x0d, 0xdd, 0x94, 0xfd, 0x5e, 0x63,
+ 0x57, 0x27, 0x05, 0x9c, 0xd0, 0x80, 0x91, 0x91,
+ 0x79, 0xe8, 0x5e, 0x18, 0x64, 0x22, 0xe4, 0x2c,
+ 0x13, 0x65, 0xa4, 0x51, 0x5a, 0x1e, 0x3b, 0x71,
+ 0x2e, 0x70, 0x9f, 0xc4, 0xa5, 0x20, 0xcd, 0xef,
+ 0xd8, 0x3f, 0xa4, 0xf5, 0x89, 0x8a, 0xa5, 0x4f,
+ 0x76, 0x2d, 0x49, 0x56, 0x00, 0x8d, 0xde, 0x40,
+ 0xba, 0x24, 0x46, 0x51, 0x38, 0xad, 0xdb, 0xc4,
+ 0x04, 0xf4, 0x6e, 0xc0, 0x29, 0x48, 0x07, 0x6a,
+ 0x1b, 0x26, 0x32, 0x0a, 0xfb, 0xea, 0x71, 0x2a,
+ 0x11, 0xfc, 0x98, 0x7c, 0x44, 0x87, 0xbc, 0x06,
+ 0x3a, 0x4d, 0xbd, 0x91, 0x63, 0x4f, 0x26, 0x48,
+ 0x54, 0x47, 0x1b, 0xbd, 0xf0, 0xf1, 0x56, 0x05,
+ 0xc5, 0x0f, 0x8f, 0x20, 0xa5, 0xcc, 0xfb, 0x76,
+ 0xb0, 0xbd, 0x83, 0xde, 0x7f, 0x39, 0x4f, 0xcf,
+ 0x61, 0x74, 0x52, 0xa7, 0x1d, 0xf6, 0xb5, 0x5e,
+ 0x4a, 0x82, 0x20, 0xc1, 0x94, 0xaa, 0x2c, 0x33,
+ 0xd6, 0x0a, 0xf9, 0x8f, 0x92, 0xc6, 0x29, 0x80,
+ 0xf5, 0xa2, 0xb1, 0xff, 0xb6, 0x2b, 0xaa, 0x04,
+ 0x00, 0x72, 0xb4, 0x12, 0xbb, 0xb1, 0xf1, 0x3c,
+ 0x88, 0xa3, 0xab, 0x49, 0x17, 0x90, 0x80, 0x59,
+ 0xa2, 0x96, 0x41, 0x69, 0x74, 0x33, 0x8a, 0x28,
+ 0x33, 0x7e, 0xb3, 0x19, 0x92, 0x28, 0xc1, 0xf0,
+ 0xd1, 0x82, 0xd5, 0x42, 0xff, 0xe7, 0xa5, 0x3f,
+ 0x1e, 0xb6, 0x4a, 0x23, 0xcc, 0x6a, 0x7f, 0x15,
+ 0x15, 0x52, 0x25, 0xb1, 0xca, 0x21, 0x95, 0x11,
+ 0x53, 0x3e, 0x1f, 0x50, 0x33, 0x12, 0x7a, 0x62,
+ 0xce, 0xcc, 0x71, 0xc2, 0x5f, 0x34, 0x47, 0xc6,
+ 0x7c, 0x71, 0xfa, 0xa0, 0x54, 0x00, 0xb2, 0xdf,
+ 0xc5, 0x54, 0xac, 0x6c, 0x53, 0xef, 0x64, 0x6b,
+ 0x08, 0x82, 0xd8, 0x16, 0x1e, 0xca, 0x40, 0xf3,
+ 0x1f, 0xdf, 0x56, 0x63, 0x10, 0xbc, 0xd7, 0xa0,
+ 0xeb, 0xee, 0xd1, 0x95, 0xe5, 0xef, 0xf1, 0x6a,
+ 0x83, 0x2d, 0x5a, 0x59, 0x06, 0xef, 0x30, 0x82,
+ 0x06, 0xeb, 0x30, 0x82, 0x04, 0xd3, 0xa0, 0x03,
+ 0x02, 0x01, 0x02, 0x02, 0x13, 0x33, 0x00, 0x00,
+ 0x05, 0x23, 0xbf, 0xe8, 0xa1, 0x1a, 0x2a, 0x68,
+ 0xbd, 0x09, 0x00, 0x00, 0x00, 0x00, 0x05, 0x23,
+ 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30,
+ 0x81, 0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
+ 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08,
+ 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e,
+ 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e,
+ 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52,
+ 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e,
+ 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13,
+ 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+ 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f,
+ 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x36,
+ 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13,
+ 0x2d, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f,
+ 0x66, 0x74, 0x20, 0x54, 0x50, 0x4d, 0x20, 0x52,
+ 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74,
+ 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20,
+ 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74,
+ 0x79, 0x20, 0x32, 0x30, 0x31, 0x34, 0x30, 0x1e,
+ 0x17, 0x0d, 0x32, 0x31, 0x30, 0x36, 0x30, 0x33,
+ 0x31, 0x39, 0x34, 0x30, 0x31, 0x36, 0x5a, 0x17,
+ 0x0d, 0x32, 0x37, 0x30, 0x36, 0x30, 0x33, 0x31,
+ 0x39, 0x34, 0x30, 0x31, 0x36, 0x5a, 0x30, 0x41,
+ 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04,
+ 0x03, 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53,
+ 0x54, 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44,
+ 0x2d, 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34,
+ 0x41, 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37,
+ 0x41, 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30,
+ 0x30, 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31,
+ 0x41, 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36,
+ 0x30, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06,
+ 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01,
+ 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f,
+ 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02,
+ 0x01, 0x00, 0xdb, 0x03, 0x34, 0x82, 0xfa, 0x81,
+ 0x1c, 0x84, 0x0b, 0xa0, 0x0e, 0x60, 0xd8, 0x9d,
+ 0x84, 0xf4, 0x81, 0xc4, 0xe9, 0xff, 0xcf, 0xe9,
+ 0xa3, 0x57, 0x53, 0x60, 0xa8, 0x19, 0xce, 0xbe,
+ 0xe1, 0x97, 0xee, 0x5d, 0x8c, 0x9f, 0xe4, 0xbd,
+ 0xef, 0xbd, 0x94, 0x14, 0xe4, 0x74, 0x41, 0x02,
+ 0xe9, 0x03, 0x19, 0x9f, 0xdd, 0x48, 0x2d, 0xbd,
+ 0xca, 0x26, 0x47, 0x2c, 0x01, 0x31, 0x5f, 0x34,
+ 0xef, 0x59, 0x35, 0x48, 0x36, 0x3d, 0x1e, 0xdf,
+ 0xd8, 0x13, 0xf0, 0xd0, 0x67, 0xc1, 0xb0, 0x47,
+ 0x67, 0xa2, 0xd6, 0x62, 0xc8, 0xe1, 0x00, 0x36,
+ 0x8b, 0x45, 0xf6, 0x3b, 0x96, 0x60, 0xa0, 0x45,
+ 0x26, 0xcb, 0xc7, 0x0b, 0x5b, 0x97, 0xd1, 0xaf,
+ 0x54, 0x25, 0x7a, 0x67, 0xe4, 0x2a, 0xd8, 0x9d,
+ 0x53, 0x05, 0xbd, 0x12, 0xac, 0xa2, 0x8e, 0x95,
+ 0xb4, 0x2a, 0xca, 0x89, 0x93, 0x64, 0x97, 0x25,
+ 0xdc, 0x1f, 0xa9, 0xe0, 0x55, 0x07, 0x38, 0x1d,
+ 0xee, 0x02, 0x90, 0x22, 0xf5, 0xad, 0x4e, 0x5c,
+ 0xf8, 0xc5, 0x1f, 0x9e, 0x84, 0x7e, 0x13, 0x47,
+ 0x52, 0xa2, 0x36, 0xf9, 0xf6, 0xbf, 0x76, 0x9e,
+ 0x0f, 0xdd, 0x14, 0x99, 0xb9, 0xd8, 0x5a, 0x42,
+ 0x3d, 0xd8, 0xbf, 0xdd, 0xb4, 0x9b, 0xbf, 0x6a,
+ 0x9f, 0x89, 0x13, 0x75, 0xaf, 0x96, 0xd2, 0x72,
+ 0xdf, 0xb3, 0x80, 0x6f, 0x84, 0x1a, 0x9d, 0x06,
+ 0x55, 0x09, 0x29, 0xea, 0xa7, 0x05, 0x31, 0xec,
+ 0x47, 0x3a, 0xcf, 0x3f, 0x9c, 0x2c, 0xbd, 0xd0,
+ 0x7d, 0xe4, 0x75, 0x5b, 0x33, 0xbe, 0x12, 0x86,
+ 0x09, 0xcf, 0x66, 0x9a, 0xeb, 0xf8, 0xf8, 0x72,
+ 0x91, 0x88, 0x4a, 0x5e, 0x89, 0x62, 0x6a, 0x94,
+ 0xdc, 0x48, 0x37, 0x13, 0xd8, 0x91, 0x02, 0xe3,
+ 0x42, 0x41, 0x7c, 0x2f, 0xe3, 0xb6, 0x0f, 0xb4,
+ 0x96, 0x06, 0x80, 0xca, 0x28, 0x01, 0x6f, 0x4b,
+ 0xcd, 0x28, 0xd4, 0x2c, 0x94, 0x7e, 0x40, 0x7e,
+ 0xdf, 0x01, 0xe5, 0xf2, 0x33, 0xd4, 0xda, 0xf4,
+ 0x1a, 0x17, 0xf7, 0x5d, 0xcb, 0x66, 0x2c, 0x2a,
+ 0xeb, 0xe1, 0xb1, 0x4a, 0xc3, 0x85, 0x63, 0xb2,
+ 0xac, 0xd0, 0x3f, 0x1a, 0x8d, 0xa5, 0x0c, 0xee,
+ 0x4f, 0xde, 0x74, 0x9c, 0xe0, 0x5a, 0x10, 0xc7,
+ 0xb8, 0xe4, 0xec, 0xe7, 0x73, 0xa6, 0x41, 0x42,
+ 0x37, 0xe1, 0xdf, 0xb9, 0xc7, 0xb5, 0x14, 0xa8,
+ 0x80, 0x95, 0xa0, 0x12, 0x67, 0x99, 0xf5, 0xba,
+ 0x25, 0x0a, 0x74, 0x86, 0x71, 0x9c, 0x7f, 0x59,
+ 0x97, 0xd2, 0x3f, 0x10, 0xfe, 0x6a, 0xb9, 0xe4,
+ 0x47, 0x36, 0xfb, 0x0f, 0x50, 0xee, 0xfc, 0x87,
+ 0x99, 0x7e, 0x36, 0x64, 0x1b, 0xc7, 0x13, 0xb3,
+ 0x33, 0x18, 0x71, 0xa4, 0xc3, 0xb0, 0xfc, 0x45,
+ 0x37, 0x11, 0x40, 0xb3, 0xde, 0x2c, 0x9f, 0x0a,
+ 0xcd, 0xaf, 0x5e, 0xfb, 0xd5, 0x9c, 0xea, 0xd7,
+ 0x24, 0x19, 0x3a, 0x92, 0x80, 0xa5, 0x63, 0xc5,
+ 0x3e, 0xdd, 0x51, 0xd0, 0x9f, 0xb8, 0x5e, 0xd5,
+ 0xf1, 0xfe, 0xa5, 0x93, 0xfb, 0x7f, 0xd9, 0xb8,
+ 0xb7, 0x0e, 0x0d, 0x12, 0x71, 0xf0, 0x52, 0x9d,
+ 0xe9, 0xd0, 0xd2, 0x8b, 0x38, 0x8b, 0x85, 0x83,
+ 0x98, 0x24, 0x88, 0xe8, 0x42, 0x30, 0x83, 0x12,
+ 0xef, 0x09, 0x96, 0x2f, 0x21, 0x81, 0x05, 0x30,
+ 0x0c, 0xbb, 0xba, 0x21, 0x39, 0x16, 0x12, 0xe8,
+ 0x4b, 0x7b, 0x7a, 0x66, 0xb8, 0x22, 0x2c, 0x71,
+ 0xaf, 0x59, 0xa1, 0xfc, 0x61, 0xf1, 0xb4, 0x5e,
+ 0xfc, 0x43, 0x19, 0x45, 0x6e, 0xa3, 0x45, 0xe4,
+ 0xcb, 0x66, 0x5f, 0xe0, 0x57, 0xf6, 0x0a, 0x30,
+ 0xa3, 0xd6, 0x51, 0x24, 0xc9, 0x07, 0x55, 0x82,
+ 0x4a, 0x66, 0x0e, 0x9d, 0xb2, 0x2f, 0x84, 0x56,
+ 0x6c, 0x3e, 0x71, 0xef, 0x9b, 0x35, 0x4d, 0x72,
+ 0xdc, 0x46, 0x2a, 0xe3, 0x7b, 0x13, 0x20, 0xbf,
+ 0xab, 0x77, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3,
+ 0x82, 0x01, 0x8e, 0x30, 0x82, 0x01, 0x8a, 0x30,
+ 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01,
+ 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x84, 0x30,
+ 0x1b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x14,
+ 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04,
+ 0x01, 0x82, 0x37, 0x15, 0x24, 0x06, 0x05, 0x67,
+ 0x81, 0x05, 0x08, 0x03, 0x30, 0x16, 0x06, 0x03,
+ 0x55, 0x1d, 0x20, 0x04, 0x0f, 0x30, 0x0d, 0x30,
+ 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01,
+ 0x82, 0x37, 0x15, 0x1f, 0x30, 0x12, 0x06, 0x03,
+ 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08,
+ 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04,
+ 0x16, 0x04, 0x14, 0x45, 0x1a, 0xec, 0xfc, 0x91,
+ 0x70, 0xf8, 0x83, 0x8b, 0x9c, 0x47, 0x2f, 0x0b,
+ 0x9f, 0x07, 0xf3, 0x2f, 0x7c, 0xa2, 0x8a, 0x30,
+ 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+ 0x30, 0x16, 0x80, 0x14, 0x7a, 0x8c, 0x0a, 0xce,
+ 0x2f, 0x48, 0x62, 0x17, 0xe2, 0x94, 0xd1, 0xae,
+ 0x55, 0xc1, 0x52, 0xec, 0x71, 0x74, 0xa4, 0x56,
+ 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04,
+ 0x69, 0x30, 0x67, 0x30, 0x65, 0xa0, 0x63, 0xa0,
+ 0x61, 0x86, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69,
+ 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e,
+ 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x6f,
+ 0x70, 0x73, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x4d,
+ 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74,
+ 0x25, 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32,
+ 0x30, 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30,
+ 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+ 0x61, 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75,
+ 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25,
+ 0x32, 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63,
+ 0x72, 0x6c, 0x30, 0x7d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x71,
+ 0x30, 0x6f, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06,
+ 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x61,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77,
+ 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f,
+ 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d,
+ 0x2f, 0x70, 0x6b, 0x69, 0x6f, 0x70, 0x73, 0x2f,
+ 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x4d, 0x69,
+ 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x25,
+ 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32, 0x30,
+ 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30, 0x43,
+ 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+ 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, 0x74,
+ 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25, 0x32,
+ 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63, 0x72,
+ 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
+ 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00,
+ 0x03, 0x82, 0x02, 0x01, 0x00, 0x48, 0x24, 0x32,
+ 0xe8, 0xd6, 0x38, 0xda, 0x65, 0xec, 0x1b, 0x18,
+ 0x8e, 0x37, 0x07, 0xd5, 0x18, 0x5a, 0xc8, 0xb9,
+ 0xbb, 0x24, 0x8a, 0x4d, 0xa1, 0x3c, 0x9e, 0x46,
+ 0x76, 0xcf, 0xa5, 0xdf, 0xd7, 0x61, 0xba, 0x05,
+ 0x89, 0x3c, 0x13, 0xc2, 0x1f, 0x71, 0xe3, 0xec,
+ 0x5d, 0x54, 0x9e, 0xd9, 0x01, 0x5a, 0x10, 0x3b,
+ 0x17, 0x75, 0xde, 0xa1, 0x45, 0xbf, 0x1d, 0x1b,
+ 0x41, 0x21, 0x42, 0x68, 0x22, 0x6b, 0xbb, 0xcb,
+ 0x11, 0x04, 0xd2, 0xae, 0x86, 0xcf, 0x73, 0x5a,
+ 0xf2, 0x80, 0x18, 0x00, 0xf0, 0xd6, 0x6c, 0x5a,
+ 0x1e, 0xb3, 0x4d, 0x30, 0x02, 0x4a, 0x6a, 0x03,
+ 0x36, 0x42, 0xde, 0xb2, 0x52, 0x55, 0xff, 0x71,
+ 0xeb, 0x7b, 0x8b, 0x55, 0x6c, 0xdf, 0x05, 0x35,
+ 0x47, 0x70, 0x53, 0xfb, 0x6c, 0xba, 0x06, 0xb2,
+ 0x61, 0x86, 0xdc, 0x2a, 0x64, 0x81, 0x24, 0x79,
+ 0x46, 0x73, 0x04, 0x55, 0x59, 0xed, 0xd6, 0x06,
+ 0x61, 0x15, 0xf9, 0x8d, 0x78, 0x39, 0x7b, 0x84,
+ 0x7a, 0x40, 0x45, 0x13, 0x1a, 0x91, 0x71, 0x8f,
+ 0xd1, 0x4f, 0x78, 0x10, 0x68, 0x9b, 0x15, 0x79,
+ 0x3f, 0x79, 0x2d, 0x9b, 0xc7, 0x5d, 0xa3, 0xcf,
+ 0xa9, 0x14, 0xb0, 0xc4, 0xdb, 0xa9, 0x45, 0x6a,
+ 0x6e, 0x60, 0x45, 0x0b, 0x14, 0x25, 0xc7, 0x74,
+ 0xd0, 0x36, 0xaf, 0xc5, 0xbd, 0x4f, 0x7b, 0xc0,
+ 0x04, 0x43, 0x85, 0xbb, 0x06, 0x36, 0x77, 0x26,
+ 0x02, 0x23, 0x0b, 0xf8, 0x57, 0x8f, 0x1f, 0x27,
+ 0x30, 0x95, 0xff, 0x83, 0x23, 0x2b, 0x49, 0x33,
+ 0x43, 0x62, 0x87, 0x5d, 0x27, 0x12, 0x1a, 0x68,
+ 0x7b, 0xba, 0x2d, 0xf6, 0xed, 0x2c, 0x26, 0xb5,
+ 0xbb, 0xe2, 0x6f, 0xc2, 0x61, 0x17, 0xfc, 0x72,
+ 0x14, 0x57, 0x2c, 0x2c, 0x5a, 0x92, 0x13, 0x41,
+ 0xc4, 0x7e, 0xb5, 0x64, 0x5b, 0x86, 0x57, 0x13,
+ 0x14, 0xff, 0xf5, 0x04, 0xb9, 0x3d, 0x2d, 0xc3,
+ 0xe9, 0x75, 0x1f, 0x68, 0x0b, 0xb5, 0x76, 0xe1,
+ 0x7d, 0xe3, 0xb0, 0x14, 0xa8, 0x45, 0x05, 0x98,
+ 0x81, 0x32, 0xc1, 0xf5, 0x49, 0x4d, 0x58, 0xa4,
+ 0xee, 0xd8, 0x84, 0xba, 0x65, 0x07, 0x8d, 0xf7,
+ 0x9a, 0xff, 0x7d, 0xa5, 0xbc, 0x9a, 0xed, 0x4a,
+ 0x5d, 0xa4, 0x97, 0x4b, 0x4d, 0x31, 0x90, 0xb5,
+ 0x7d, 0x28, 0x77, 0x25, 0x88, 0x1c, 0xbf, 0x78,
+ 0x22, 0xb2, 0xb5, 0x5c, 0x9a, 0xc9, 0x63, 0x17,
+ 0x96, 0xe9, 0xc2, 0x52, 0x30, 0xb8, 0x9b, 0x37,
+ 0x69, 0x1a, 0x6a, 0x66, 0x76, 0x18, 0xac, 0xc0,
+ 0x48, 0xee, 0x46, 0x5b, 0xbe, 0x6a, 0xd5, 0x72,
+ 0x07, 0xdc, 0x7d, 0x05, 0xbe, 0x76, 0x7d, 0xa5,
+ 0x5e, 0x53, 0xb5, 0x47, 0x80, 0x58, 0xf0, 0xaf,
+ 0x6f, 0x4e, 0xc0, 0xf1, 0x1e, 0x37, 0x64, 0x15,
+ 0x42, 0x96, 0x18, 0x3a, 0x89, 0xc8, 0x14, 0x48,
+ 0x89, 0x5c, 0x12, 0x88, 0x98, 0x0b, 0x7b, 0x4e,
+ 0xce, 0x1c, 0xda, 0xd5, 0xa4, 0xd3, 0x32, 0x32,
+ 0x74, 0x5b, 0xcc, 0xfd, 0x2b, 0x02, 0xfb, 0xae,
+ 0xd0, 0x5a, 0x4c, 0xc9, 0xc1, 0x35, 0x19, 0x90,
+ 0x5f, 0xca, 0x14, 0xeb, 0x4c, 0x17, 0xd7, 0xe3,
+ 0xe2, 0x5d, 0xb4, 0x49, 0xaa, 0xf0, 0x50, 0x87,
+ 0xc3, 0x20, 0x00, 0xda, 0xe9, 0x04, 0x80, 0x64,
+ 0xac, 0x9f, 0xcd, 0x26, 0x41, 0x48, 0xe8, 0x4c,
+ 0x46, 0xcc, 0x5b, 0xd7, 0xca, 0x4c, 0x1b, 0x43,
+ 0x43, 0x1e, 0xbd, 0x94, 0xe7, 0xa7, 0xa6, 0x86,
+ 0xe5, 0xd1, 0x78, 0x29, 0xa2, 0x40, 0xc5, 0xc5,
+ 0x47, 0xb6, 0x6d, 0x53, 0xde, 0xac, 0x97, 0x74,
+ 0x24, 0x57, 0xcc, 0x05, 0x93, 0xfd, 0x52, 0x35,
+ 0x29, 0xd5, 0xe0, 0xfa, 0x23, 0x0d, 0xd7, 0xaa,
+ 0x8b, 0x07, 0x4b, 0xf6, 0x64, 0xc7, 0xad, 0x3c,
+ 0xa1, 0xb5, 0xc5, 0x70, 0xaf, 0x46, 0xfe, 0x9a,
+ 0x82, 0x4d, 0x75, 0xb8, 0x6d, 0x67, 0x70, 0x75,
+ 0x62, 0x41, 0x72, 0x65, 0x61, 0x58, 0x76, 0x00,
+ 0x23, 0x00, 0x0b, 0x00, 0x04, 0x00, 0x72, 0x00,
+ 0x20, 0x9d, 0xff, 0xcb, 0xf3, 0x6c, 0x38, 0x3a,
+ 0xe6, 0x99, 0xfb, 0x98, 0x68, 0xdc, 0x6d, 0xcb,
+ 0x89, 0xd7, 0x15, 0x38, 0x84, 0xbe, 0x28, 0x03,
+ 0x92, 0x2c, 0x12, 0x41, 0x58, 0xbf, 0xad, 0x22,
+ 0xae, 0x00, 0x10, 0x00, 0x10, 0x00, 0x03, 0x00,
+ 0x10, 0x00, 0x20, 0xfb, 0xd6, 0xba, 0x74, 0xe6,
+ 0x6e, 0x5c, 0x87, 0xef, 0x89, 0xa2, 0xe8, 0x3d,
+ 0x0b, 0xe9, 0x69, 0x2c, 0x07, 0x07, 0x7a, 0x8a,
+ 0x1e, 0xce, 0x12, 0xea, 0x3b, 0xb3, 0xf1, 0xf3,
+ 0xd9, 0xc3, 0xe6, 0x00, 0x20, 0x3c, 0x68, 0x51,
+ 0x94, 0x54, 0x8d, 0xeb, 0x9f, 0xb2, 0x2c, 0x66,
+ 0x75, 0xb6, 0xb7, 0x55, 0x22, 0x0d, 0x87, 0x59,
+ 0xc4, 0x39, 0x91, 0x62, 0x17, 0xc2, 0xc3, 0x53,
+ 0xa5, 0x26, 0x97, 0x4f, 0x2d, 0x68, 0x63, 0x65,
+ 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x58, 0xa1,
+ 0xff, 0x54, 0x43, 0x47, 0x80, 0x17, 0x00, 0x22,
+ 0x00, 0x0b, 0x73, 0xbe, 0xb7, 0x40, 0x82, 0xc0,
+ 0x49, 0x9a, 0xf7, 0xf2, 0xd0, 0x79, 0x6c, 0x88,
+ 0xf3, 0x56, 0x7b, 0x7a, 0x7d, 0xcd, 0x70, 0xd1,
+ 0xbc, 0x41, 0x88, 0x48, 0x51, 0x03, 0xf3, 0x58,
+ 0x3e, 0xb8, 0x00, 0x14, 0x9f, 0x57, 0x39, 0x67,
+ 0xa8, 0x7b, 0xd8, 0xf6, 0x9e, 0x75, 0xc9, 0x85,
+ 0xab, 0xe3, 0x55, 0xc7, 0x9c, 0xf6, 0xd8, 0x4f,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x1c, 0x12,
+ 0xfd, 0xc6, 0x05, 0xc6, 0x2b, 0xf5, 0xe9, 0x88,
+ 0x01, 0x1f, 0x70, 0x8d, 0x98, 0x2a, 0x04, 0x21,
+ 0x30, 0x00, 0x22, 0x00, 0x0b, 0xf4, 0xfd, 0x9a,
+ 0x33, 0x55, 0x21, 0x08, 0x27, 0x48, 0x55, 0x01,
+ 0x56, 0xf9, 0x0b, 0x4e, 0x47, 0x55, 0x08, 0x2e,
+ 0x3c, 0x91, 0x3d, 0x6e, 0x53, 0xcf, 0x08, 0xe9,
+ 0x0a, 0x4b, 0xc9, 0x7e, 0x99, 0x00, 0x22, 0x00,
+ 0x0b, 0x51, 0xd3, 0x38, 0xfe, 0xaa, 0xda, 0xc6,
+ 0x68, 0x84, 0x39, 0xe7, 0xb1, 0x03, 0x22, 0x5e,
+ 0xc4, 0xd3, 0xf1, 0x0c, 0xec, 0x35, 0x5d, 0x50,
+ 0xa3, 0x9d, 0xab, 0xa1, 0x7b, 0x61, 0x51, 0x8f,
+ 0x4e
+};
+
+/*
+ * Security Key By Yubico
+ * 5.1.X
+ * f8a011f3-8c0a-4d15-8006-17111f9edc7d
+ */
+const unsigned char aaguid[16] = {
+ 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15,
+ 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d,
+};
+
+/*
+ * Windows Hello by Microsoft
+ */
+const unsigned char aaguid_tpm[16] = {
+ 0x08, 0x98, 0x70, 0x58, 0xca, 0xdc, 0x4b, 0x81,
+ 0xb6, 0xe1, 0x30, 0xde, 0x50, 0xdc, 0xbe, 0x96,
+};
+
+const char rp_id[] = "localhost";
+const char rp_name[] = "sweet home localhost";
+
+static void *
+dummy_open(const char *path)
+{
+ (void)path;
+
+ return (&fake_dev_handle);
+}
+
+static void
+dummy_close(void *handle)
+{
+ assert(handle == &fake_dev_handle);
+}
+
+static int
+dummy_read(void *handle, unsigned char *buf, size_t len, int ms)
+{
+ (void)handle;
+ (void)buf;
+ (void)len;
+ (void)ms;
+
+ abort();
+ /* NOTREACHED */
+}
+
+static int
+dummy_write(void *handle, const unsigned char *buf, size_t len)
+{
+ (void)handle;
+ (void)buf;
+ (void)len;
+
+ abort();
+ /* NOTREACHED */
+}
+
+static fido_cred_t *
+alloc_cred(void)
+{
+ fido_cred_t *c;
+
+ c = fido_cred_new();
+ assert(c != NULL);
+
+ return (c);
+}
+
+static void
+free_cred(fido_cred_t *c)
+{
+ fido_cred_free(&c);
+ assert(c == NULL);
+}
+
+static fido_dev_t *
+alloc_dev(void)
+{
+ fido_dev_t *d;
+
+ d = fido_dev_new();
+ assert(d != NULL);
+
+ return (d);
+}
+
+static void
+free_dev(fido_dev_t *d)
+{
+ fido_dev_free(&d);
+ assert(d == NULL);
+}
+
+static void
+empty_cred(void)
+{
+ fido_cred_t *c;
+ fido_dev_t *d;
+ fido_dev_io_t io_f;
+
+ c = alloc_cred();
+ assert(fido_cred_authdata_len(c) == 0);
+ assert(fido_cred_authdata_ptr(c) == NULL);
+ assert(fido_cred_authdata_raw_len(c) == 0);
+ assert(fido_cred_authdata_raw_ptr(c) == NULL);
+ assert(fido_cred_clientdata_hash_len(c) == 0);
+ assert(fido_cred_clientdata_hash_ptr(c) == NULL);
+ assert(fido_cred_flags(c) == 0);
+ assert(fido_cred_fmt(c) == NULL);
+ assert(fido_cred_id_len(c) == 0);
+ assert(fido_cred_id_ptr(c) == NULL);
+ assert(fido_cred_prot(c) == 0);
+ assert(fido_cred_pubkey_len(c) == 0);
+ assert(fido_cred_pubkey_ptr(c) == NULL);
+ assert(fido_cred_rp_id(c) == NULL);
+ assert(fido_cred_rp_name(c) == NULL);
+ assert(fido_cred_sig_len(c) == 0);
+ assert(fido_cred_sig_ptr(c) == NULL);
+ assert(fido_cred_x5c_len(c) == 0);
+ assert(fido_cred_x5c_ptr(c) == NULL);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+
+ memset(&io_f, 0, sizeof(io_f));
+
+ io_f.open = dummy_open;
+ io_f.close = dummy_close;
+ io_f.read = dummy_read;
+ io_f.write = dummy_write;
+
+ d = alloc_dev();
+
+ fido_dev_force_u2f(d);
+ assert(fido_dev_set_io_functions(d, &io_f) == FIDO_OK);
+ assert(fido_dev_make_cred(d, c, NULL) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_make_cred(d, c, "") == FIDO_ERR_UNSUPPORTED_OPTION);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+
+ fido_dev_force_fido2(d);
+ assert(fido_dev_set_io_functions(d, &io_f) == FIDO_OK);
+ assert(fido_dev_make_cred(d, c, NULL) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_make_cred(d, c, "") == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+
+ free_cred(c);
+ free_dev(d);
+}
+
+static void
+valid_cred(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_OK);
+ assert(fido_cred_prot(c) == 0);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+no_cdh(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+no_rp_id(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+no_rp_name(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, NULL) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_OK);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+no_authdata(void)
+{
+ fido_cred_t *c;
+ unsigned char *unset;
+
+ unset = calloc(1, sizeof(aaguid));
+ assert(unset != NULL);
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_pubkey_len(c) == 0);
+ assert(fido_cred_pubkey_ptr(c) == NULL);
+ assert(fido_cred_id_len(c) == 0);
+ assert(fido_cred_id_ptr(c) == NULL);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), unset, sizeof(aaguid)) == 0);
+ free_cred(c);
+ free(unset);
+}
+
+static void
+no_x509(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+no_sig(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+no_fmt(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+wrong_options(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_TRUE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_PARAM);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+junk_cdh(void)
+{
+ fido_cred_t *c;
+ unsigned char *junk;
+
+ junk = malloc(sizeof(cdh));
+ assert(junk != NULL);
+ memcpy(junk, cdh, sizeof(cdh));
+ junk[0] = (unsigned char)~junk[0];
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, junk, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_SIG);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+ free(junk);
+}
+
+static void
+junk_fmt(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "junk") == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ free_cred(c);
+}
+
+static void
+junk_rp_id(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, "potato", rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_PARAM);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+junk_rp_name(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, "potato") == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_OK);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+junk_authdata(void)
+{
+ fido_cred_t *c;
+ unsigned char *junk;
+ unsigned char *unset;
+
+ junk = malloc(sizeof(authdata));
+ assert(junk != NULL);
+ memcpy(junk, authdata, sizeof(authdata));
+ junk[0] = (unsigned char)~junk[0];
+
+ unset = calloc(1, sizeof(aaguid));
+ assert(unset != NULL);
+
+ c = alloc_cred();
+ assert(fido_cred_set_authdata(c, junk,
+ sizeof(authdata)) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_authdata_len(c) == 0);
+ assert(fido_cred_authdata_ptr(c) == NULL);
+ assert(fido_cred_authdata_raw_len(c) == 0);
+ assert(fido_cred_authdata_raw_ptr(c) == NULL);
+ assert(fido_cred_flags(c) == 0);
+ assert(fido_cred_fmt(c) == NULL);
+ assert(fido_cred_id_len(c) == 0);
+ assert(fido_cred_id_ptr(c) == NULL);
+ assert(fido_cred_pubkey_len(c) == 0);
+ assert(fido_cred_pubkey_ptr(c) == NULL);
+ assert(fido_cred_rp_id(c) == NULL);
+ assert(fido_cred_rp_name(c) == NULL);
+ assert(fido_cred_sig_len(c) == 0);
+ assert(fido_cred_sig_ptr(c) == NULL);
+ assert(fido_cred_x5c_len(c) == 0);
+ assert(fido_cred_x5c_ptr(c) == NULL);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), unset, sizeof(aaguid)) == 0);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ free_cred(c);
+ free(junk);
+ free(unset);
+}
+
+static void
+junk_sig(void)
+{
+ fido_cred_t *c;
+ unsigned char *junk;
+
+ junk = malloc(sizeof(sig));
+ assert(junk != NULL);
+ memcpy(junk, sig, sizeof(sig));
+ junk[0] = (unsigned char)~junk[0];
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, junk, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_SIG);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+ free(junk);
+}
+
+static void
+junk_x509(void)
+{
+ fido_cred_t *c;
+ unsigned char *junk;
+
+ junk = malloc(sizeof(x509));
+ assert(junk != NULL);
+ memcpy(junk, x509, sizeof(x509));
+ junk[0] = (unsigned char)~junk[0];
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, junk, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_SIG);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+ free(junk);
+}
+
+/* github issue #6 */
+static void
+invalid_type(void)
+{
+ fido_cred_t *c;
+ unsigned char *unset;
+
+ unset = calloc(1, sizeof(aaguid));
+ assert(unset != NULL);
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_RS256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_pubkey_len(c) == 0);
+ assert(fido_cred_pubkey_ptr(c) == NULL);
+ assert(fido_cred_id_len(c) == 0);
+ assert(fido_cred_id_ptr(c) == NULL);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), unset, sizeof(aaguid)) == 0);
+ free_cred(c);
+ free(unset);
+}
+
+/* cbor_serialize_alloc misuse */
+static void
+bad_cbor_serialize(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_authdata_len(c) == sizeof(authdata));
+ free_cred(c);
+}
+
+static void
+duplicate_keys(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata_dupkeys,
+ sizeof(authdata_dupkeys)) == FIDO_ERR_INVALID_ARGUMENT);
+ free_cred(c);
+}
+
+static void
+unsorted_keys(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata_unsorted_keys,
+ sizeof(authdata_unsorted_keys)) == FIDO_ERR_INVALID_ARGUMENT);
+ free_cred(c);
+}
+
+static void
+wrong_credprot(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK);
+ assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "packed") == FIDO_OK);
+ assert(fido_cred_set_prot(c, FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_PARAM);
+ free_cred(c);
+}
+
+static void
+raw_authdata(void)
+{
+ fido_cred_t *c;
+ cbor_item_t *item;
+ struct cbor_load_result cbor_result;
+ const unsigned char *ptr;
+ unsigned char *cbor;
+ size_t len;
+ size_t cbor_len;
+ size_t alloclen;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert((ptr = fido_cred_authdata_ptr(c)) != NULL);
+ assert((len = fido_cred_authdata_len(c)) != 0);
+ assert((item = cbor_load(ptr, len, &cbor_result)) != NULL);
+ assert(cbor_result.read == len);
+ assert(cbor_isa_bytestring(item));
+ assert((ptr = fido_cred_authdata_raw_ptr(c)) != NULL);
+ assert((len = fido_cred_authdata_raw_len(c)) != 0);
+ assert(cbor_bytestring_length(item) == len);
+ assert(memcmp(ptr, cbor_bytestring_handle(item), len) == 0);
+ assert((len = fido_cred_authdata_len(c)) != 0);
+ assert((cbor_len = cbor_serialize_alloc(item, &cbor, &alloclen)) == len);
+ assert((ptr = cbor_bytestring_handle(item)) != NULL);
+ assert((len = cbor_bytestring_length(item)) != 0);
+ assert(fido_cred_set_authdata_raw(c, ptr, len) == FIDO_OK);
+ assert((ptr = fido_cred_authdata_ptr(c)) != NULL);
+ assert((len = fido_cred_authdata_len(c)) != 0);
+ assert(len == cbor_len);
+ assert(memcmp(cbor, ptr, len) == 0);
+ assert(cbor_len == sizeof(authdata));
+ assert(memcmp(cbor, authdata, cbor_len) == 0);
+ cbor_decref(&item);
+ free(cbor);
+ free_cred(c);
+}
+
+static void
+fmt_none(void)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "none") == FIDO_OK);
+ assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_cred_prot(c) == 0);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id));
+ assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0);
+ free_cred(c);
+}
+
+static void
+valid_tpm_rs256_cred(bool xfail)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_RS256) == FIDO_OK);
+ assert(fido_cred_set_clientdata(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata_tpm_rs256, sizeof(authdata_tpm_rs256)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_TRUE) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "tpm") == FIDO_OK);
+ assert(fido_cred_set_attstmt(c, attstmt_tpm_rs256, sizeof(attstmt_tpm_rs256)) == FIDO_OK);
+ // XXX: RHEL9 has deprecated SHA-1 for signing.
+ assert(fido_cred_verify(c) == (xfail ? FIDO_ERR_INVALID_SIG : FIDO_OK));
+ assert(fido_cred_prot(c) == 0);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey_tpm_rs256));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey_tpm_rs256, sizeof(pubkey_tpm_rs256)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id_tpm_rs256));
+ assert(memcmp(fido_cred_id_ptr(c), id_tpm_rs256, sizeof(id_tpm_rs256)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid_tpm));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid_tpm, sizeof(aaguid_tpm)) == 0);
+ free_cred(c);
+}
+
+static void
+valid_tpm_es256_cred(bool xfail)
+{
+ fido_cred_t *c;
+
+ c = alloc_cred();
+ assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK);
+ assert(fido_cred_set_clientdata(c, cdh, sizeof(cdh)) == FIDO_OK);
+ assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK);
+ assert(fido_cred_set_authdata(c, authdata_tpm_es256, sizeof(authdata_tpm_es256)) == FIDO_OK);
+ assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK);
+ assert(fido_cred_set_uv(c, FIDO_OPT_TRUE) == FIDO_OK);
+ assert(fido_cred_set_fmt(c, "tpm") == FIDO_OK);
+ assert(fido_cred_set_attstmt(c, attstmt_tpm_es256, sizeof(attstmt_tpm_es256)) == FIDO_OK);
+ // XXX: RHEL9 has deprecated SHA-1 for signing.
+ assert(fido_cred_verify(c) == (xfail ? FIDO_ERR_INVALID_SIG : FIDO_OK));
+ assert(fido_cred_prot(c) == 0);
+ assert(fido_cred_pubkey_len(c) == sizeof(pubkey_tpm_es256));
+ assert(memcmp(fido_cred_pubkey_ptr(c), pubkey_tpm_es256, sizeof(pubkey_tpm_es256)) == 0);
+ assert(fido_cred_id_len(c) == sizeof(id_tpm_es256));
+ assert(memcmp(fido_cred_id_ptr(c), id_tpm_es256, sizeof(id_tpm_es256)) == 0);
+ assert(fido_cred_aaguid_len(c) == sizeof(aaguid_tpm));
+ assert(memcmp(fido_cred_aaguid_ptr(c), aaguid_tpm, sizeof(aaguid_tpm)) == 0);
+ free_cred(c);
+}
+
+int
+main(void)
+{
+ bool xfail = getenv("FIDO_REGRESS_RS1_XFAIL") != NULL;
+
+ fido_init(0);
+
+ empty_cred();
+ valid_cred();
+ no_cdh();
+ no_rp_id();
+ no_rp_name();
+ no_authdata();
+ no_x509();
+ no_sig();
+ no_fmt();
+ junk_cdh();
+ junk_fmt();
+ junk_rp_id();
+ junk_rp_name();
+ junk_authdata();
+ junk_x509();
+ junk_sig();
+ wrong_options();
+ invalid_type();
+ bad_cbor_serialize();
+ duplicate_keys();
+ unsorted_keys();
+ wrong_credprot();
+ raw_authdata();
+ fmt_none();
+ valid_tpm_rs256_cred(xfail);
+ valid_tpm_es256_cred(xfail);
+
+ exit(0);
+}
diff --git a/regress/dev.c b/regress/dev.c
new file mode 100644
index 0000000..0ba552b
--- /dev/null
+++ b/regress/dev.c
@@ -0,0 +1,439 @@
+/*
+ * 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
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+
+#include "../fuzz/wiredata_fido2.h"
+
+#define REPORT_LEN (64 + 1)
+
+static uint8_t ctap_nonce[8];
+static uint8_t *wiredata_ptr;
+static size_t wiredata_len;
+static int fake_dev_handle;
+static int initialised;
+static long interval_ms;
+
+#if defined(_MSC_VER)
+static int
+nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
+{
+ if (rmtp != NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+
+ Sleep((DWORD)(rqtp->tv_sec * 1000) + (DWORD)(rqtp->tv_nsec / 1000000));
+
+ return (0);
+}
+#endif
+
+static void *
+dummy_open(const char *path)
+{
+ (void)path;
+
+ return (&fake_dev_handle);
+}
+
+static void
+dummy_close(void *handle)
+{
+ assert(handle == &fake_dev_handle);
+}
+
+static int
+dummy_read(void *handle, unsigned char *ptr, size_t len, int ms)
+{
+ struct timespec tv;
+ size_t n;
+ long d;
+
+ assert(handle == &fake_dev_handle);
+ assert(ptr != NULL);
+ assert(len == REPORT_LEN - 1);
+
+ if (wiredata_ptr == NULL)
+ return (-1);
+
+ if (!initialised) {
+ assert(wiredata_len >= REPORT_LEN - 1);
+ memcpy(&wiredata_ptr[7], &ctap_nonce, sizeof(ctap_nonce));
+ initialised = 1;
+ }
+
+ if (ms >= 0 && ms < interval_ms)
+ d = ms;
+ else
+ d = interval_ms;
+
+ if (d) {
+ tv.tv_sec = d / 1000;
+ tv.tv_nsec = (d % 1000) * 1000000;
+ if (nanosleep(&tv, NULL) == -1)
+ err(1, "nanosleep");
+ }
+
+ if (d != interval_ms)
+ return (-1); /* timeout */
+
+ if (wiredata_len < len)
+ n = wiredata_len;
+ else
+ n = len;
+
+ memcpy(ptr, wiredata_ptr, n);
+ wiredata_ptr += n;
+ wiredata_len -= n;
+
+ return ((int)n);
+}
+
+static int
+dummy_write(void *handle, const unsigned char *ptr, size_t len)
+{
+ struct timespec tv;
+
+ assert(handle == &fake_dev_handle);
+ assert(ptr != NULL);
+ assert(len == REPORT_LEN);
+
+ if (!initialised)
+ memcpy(&ctap_nonce, &ptr[8], sizeof(ctap_nonce));
+
+ if (interval_ms) {
+ tv.tv_sec = interval_ms / 1000;
+ tv.tv_nsec = (interval_ms % 1000) * 1000000;
+ if (nanosleep(&tv, NULL) == -1)
+ err(1, "nanosleep");
+ }
+
+ return ((int)len);
+}
+
+static uint8_t *
+wiredata_setup(const uint8_t *data, size_t len)
+{
+ const uint8_t ctap_init_data[] = { WIREDATA_CTAP_INIT };
+
+ assert(wiredata_ptr == NULL);
+ assert(SIZE_MAX - len > sizeof(ctap_init_data));
+ assert((wiredata_ptr = malloc(sizeof(ctap_init_data) + len)) != NULL);
+
+#if defined(_MSC_VER)
+#pragma warning(push)
+#pragma warning(disable:6386)
+#endif
+ memcpy(wiredata_ptr, ctap_init_data, sizeof(ctap_init_data));
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+ if (len)
+ memcpy(wiredata_ptr + sizeof(ctap_init_data), data, len);
+
+ wiredata_len = sizeof(ctap_init_data) + len;
+
+ return (wiredata_ptr);
+}
+
+static void
+wiredata_clear(uint8_t **wiredata)
+{
+ free(*wiredata);
+ *wiredata = NULL;
+ wiredata_ptr = NULL;
+ wiredata_len = 0;
+ initialised = 0;
+}
+
+/* gh#56 */
+static void
+open_iff_ok(void)
+{
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_ERR_RX);
+ assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT);
+
+ fido_dev_free(&dev);
+}
+
+static void
+reopen(void)
+{
+ const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO };
+ uint8_t *wiredata;
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data));
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ wiredata_clear(&wiredata);
+
+ wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data));
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ fido_dev_free(&dev);
+ wiredata_clear(&wiredata);
+}
+
+static void
+double_open(void)
+{
+ const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO };
+ uint8_t *wiredata;
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data));
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ fido_dev_free(&dev);
+ wiredata_clear(&wiredata);
+}
+
+static void
+double_close(void)
+{
+ const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO };
+ uint8_t *wiredata;
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data));
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT);
+ fido_dev_free(&dev);
+ wiredata_clear(&wiredata);
+}
+
+static void
+is_fido2(void)
+{
+ const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO };
+ uint8_t *wiredata;
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data));
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_is_fido2(dev) == true);
+ assert(fido_dev_supports_pin(dev) == true);
+ fido_dev_force_u2f(dev);
+ assert(fido_dev_is_fido2(dev) == false);
+ assert(fido_dev_supports_pin(dev) == false);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ wiredata_clear(&wiredata);
+
+ wiredata = wiredata_setup(NULL, 0);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_is_fido2(dev) == false);
+ assert(fido_dev_supports_pin(dev) == false);
+ fido_dev_force_fido2(dev);
+ assert(fido_dev_is_fido2(dev) == true);
+ assert(fido_dev_supports_pin(dev) == false);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ fido_dev_free(&dev);
+ wiredata_clear(&wiredata);
+}
+
+static void
+has_pin(void)
+{
+ const uint8_t set_pin_data[] = {
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_CBOR_AUTHKEY,
+ WIREDATA_CTAP_CBOR_STATUS,
+ WIREDATA_CTAP_CBOR_STATUS
+ };
+ uint8_t *wiredata;
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ wiredata = wiredata_setup(set_pin_data, sizeof(set_pin_data));
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_has_pin(dev) == false);
+ assert(fido_dev_set_pin(dev, "top secret", NULL) == FIDO_OK);
+ assert(fido_dev_has_pin(dev) == true);
+ assert(fido_dev_reset(dev) == FIDO_OK);
+ assert(fido_dev_has_pin(dev) == false);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ fido_dev_free(&dev);
+ wiredata_clear(&wiredata);
+}
+
+static void
+timeout_rx(void)
+{
+ const uint8_t timeout_rx_data[] = {
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_CBOR_STATUS
+ };
+ uint8_t *wiredata;
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ wiredata = wiredata_setup(timeout_rx_data, sizeof(timeout_rx_data));
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_set_timeout(dev, 3 * 1000) == FIDO_OK);
+ interval_ms = 1000;
+ assert(fido_dev_reset(dev) == FIDO_ERR_RX);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ fido_dev_free(&dev);
+ wiredata_clear(&wiredata);
+ interval_ms = 0;
+}
+
+static void
+timeout_ok(void)
+{
+ const uint8_t timeout_ok_data[] = {
+ WIREDATA_CTAP_CBOR_INFO,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_KEEPALIVE,
+ WIREDATA_CTAP_CBOR_STATUS
+ };
+ uint8_t *wiredata;
+ fido_dev_t *dev = NULL;
+ fido_dev_io_t io;
+
+ memset(&io, 0, sizeof(io));
+
+ io.open = dummy_open;
+ io.close = dummy_close;
+ io.read = dummy_read;
+ io.write = dummy_write;
+
+ wiredata = wiredata_setup(timeout_ok_data, sizeof(timeout_ok_data));
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK);
+ assert(fido_dev_open(dev, "dummy") == FIDO_OK);
+ assert(fido_dev_set_timeout(dev, 30 * 1000) == FIDO_OK);
+ interval_ms = 1000;
+ assert(fido_dev_reset(dev) == FIDO_OK);
+ assert(fido_dev_close(dev) == FIDO_OK);
+ fido_dev_free(&dev);
+ wiredata_clear(&wiredata);
+ interval_ms = 0;
+}
+
+static void
+timeout_misc(void)
+{
+ fido_dev_t *dev;
+
+ assert((dev = fido_dev_new()) != NULL);
+ assert(fido_dev_set_timeout(dev, -2) == FIDO_ERR_INVALID_ARGUMENT);
+ assert(fido_dev_set_timeout(dev, 3 * 1000) == FIDO_OK);
+ assert(fido_dev_set_timeout(dev, -1) == FIDO_OK);
+ fido_dev_free(&dev);
+}
+
+int
+main(void)
+{
+ fido_init(0);
+
+ open_iff_ok();
+ reopen();
+ double_open();
+ double_close();
+ is_fido2();
+ has_pin();
+ timeout_rx();
+ timeout_ok();
+ timeout_misc();
+
+ exit(0);
+}
diff --git a/regress/eddsa.c b/regress/eddsa.c
new file mode 100644
index 0000000..f97f97c
--- /dev/null
+++ b/regress/eddsa.c
@@ -0,0 +1,159 @@
+/*
+ * 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
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <string.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+#include <fido/eddsa.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#define ASSERT_NOT_NULL(e) assert((e) != NULL)
+#define ASSERT_NULL(e) assert((e) == NULL)
+#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT)
+#define ASSERT_OK(e) assert((e) == FIDO_OK)
+
+static const char ecdsa[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOwiq14c80b7C1Jzsx5w1zMvk2GgW\n"
+"5kfGMOKXjwF/U+51ZfBDKehs3ivdeXAJBkxIh7E3iA32s+HyNqk+ntl9fg==\n"
+"-----END PUBLIC KEY-----\n";
+
+static const char eddsa[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MCowBQYDK2VwAyEADt/RHErAxAHxH9FUmsjOhQ2ALl6Y8nE0m3zQxkEE2iM=\n"
+"-----END PUBLIC KEY-----\n";
+
+static const unsigned char eddsa_raw[] = {
+ 0x0e, 0xdf, 0xd1, 0x1c, 0x4a, 0xc0, 0xc4, 0x01,
+ 0xf1, 0x1f, 0xd1, 0x54, 0x9a, 0xc8, 0xce, 0x85,
+ 0x0d, 0x80, 0x2e, 0x5e, 0x98, 0xf2, 0x71, 0x34,
+ 0x9b, 0x7c, 0xd0, 0xc6, 0x41, 0x04, 0xda, 0x23,
+};
+
+static EVP_PKEY *
+EVP_PKEY_from_PEM(const char *ptr, size_t len)
+{
+ BIO *bio = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) {
+ warnx("BIO_new");
+ goto out;
+ }
+ if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) {
+ warnx("BIO_write");
+ goto out;
+ }
+ if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL)
+ warnx("PEM_read_bio_PUBKEY");
+out:
+ BIO_free(bio);
+
+ return pkey;
+}
+
+static int
+eddsa_pk_cmp(const char *ptr, size_t len)
+{
+ EVP_PKEY *pkA = NULL;
+ EVP_PKEY *pkB = NULL;
+ eddsa_pk_t *k = NULL;
+ int r, ok = -1;
+
+ if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) {
+ warnx("EVP_PKEY_from_PEM");
+ goto out;
+ }
+ if ((k = eddsa_pk_new()) == NULL) {
+ warnx("eddsa_pk_new");
+ goto out;
+ }
+ if ((r = eddsa_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) {
+ warnx("eddsa_pk_from_EVP_PKEY: 0x%x", r);
+ goto out;
+ }
+ if ((pkB = eddsa_pk_to_EVP_PKEY(k)) == NULL) {
+ warnx("eddsa_pk_to_EVP_PKEY");
+ goto out;
+ }
+ if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) {
+ warnx("EVP_PKEY_cmp: %d", r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ EVP_PKEY_free(pkA);
+ EVP_PKEY_free(pkB);
+ eddsa_pk_free(&k);
+
+ return ok;
+}
+
+static void
+invalid_key(void)
+{
+ EVP_PKEY *pkey;
+ eddsa_pk_t *pk;
+
+ ASSERT_NOT_NULL((pkey = EVP_PKEY_from_PEM(ecdsa, sizeof(ecdsa))));
+ ASSERT_NOT_NULL((pk = eddsa_pk_new()));
+ ASSERT_INVAL(eddsa_pk_from_EVP_PKEY(pk, pkey));
+
+ EVP_PKEY_free(pkey);
+ eddsa_pk_free(&pk);
+}
+
+static void
+valid_key(void)
+{
+ EVP_PKEY *pkeyA = NULL;
+ EVP_PKEY *pkeyB = NULL;
+ eddsa_pk_t *pkA = NULL;
+ eddsa_pk_t *pkB = NULL;
+
+#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3070000f
+ /* incomplete support; test what we can */
+ ASSERT_NULL(EVP_PKEY_from_PEM(eddsa, sizeof(eddsa)));
+ ASSERT_NOT_NULL((pkB = eddsa_pk_new()));
+ ASSERT_INVAL(eddsa_pk_from_ptr(pkB, eddsa_raw, sizeof(eddsa_raw)));
+ ASSERT_NULL(eddsa_pk_to_EVP_PKEY((const eddsa_pk_t *)eddsa_raw));
+ assert(eddsa_pk_cmp(eddsa, sizeof(eddsa)) < 0);
+#else
+ ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(eddsa, sizeof(eddsa))));
+ ASSERT_NOT_NULL((pkA = eddsa_pk_new()));
+ ASSERT_NOT_NULL((pkB = eddsa_pk_new()));
+ ASSERT_OK(eddsa_pk_from_EVP_PKEY(pkA, pkeyA));
+ ASSERT_OK(eddsa_pk_from_ptr(pkB, eddsa_raw, sizeof(eddsa_raw)));
+ ASSERT_NOT_NULL((pkeyB = eddsa_pk_to_EVP_PKEY((const eddsa_pk_t *)eddsa_raw)));
+ assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1);
+ assert(eddsa_pk_cmp(eddsa, sizeof(eddsa)) == 0);
+#endif
+
+ EVP_PKEY_free(pkeyA);
+ EVP_PKEY_free(pkeyB);
+ eddsa_pk_free(&pkA);
+ eddsa_pk_free(&pkB);
+}
+
+int
+main(void)
+{
+ fido_init(0);
+
+ invalid_key();
+ valid_key();
+
+ exit(0);
+}
diff --git a/regress/es256.c b/regress/es256.c
new file mode 100644
index 0000000..3a62a41
--- /dev/null
+++ b/regress/es256.c
@@ -0,0 +1,199 @@
+/*
+ * 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
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <string.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+#include <fido/es256.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#define ASSERT_NOT_NULL(e) assert((e) != NULL)
+#define ASSERT_NULL(e) assert((e) == NULL)
+#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT)
+#define ASSERT_OK(e) assert((e) == FIDO_OK)
+
+static const char short_x[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAAeeHTZj4LEbt7Czs+u5gEZJfnGE\n"
+"6Z+YLe4AYu7SoGY7IH/2jKifsA7w+lkURL4DL63oEjd3f8foH9bX4eaVug==\n"
+"-----END PUBLIC KEY-----";
+
+static const char short_y[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL8CWUP1r0tpJ5QmkzLc69O74C/Ti\n"
+"83hTiys/JFNVkp0ArW3pKt5jNRrgWSZYE4S/D3AMtpqifFXz/FLCzJqojQ==\n"
+"-----END PUBLIC KEY-----\n";
+
+static const char p256k1[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEU1y8c0Jg9FGr3vYChpEo9c4dpkijriYM\n"
+"QzU/DeskC89hZjLNH1Sj8ra2MsBlVGGJTNPCZSyx8Jo7ERapxdN7UQ==\n"
+"-----END PUBLIC KEY-----\n";
+
+static const char p256v1[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOwiq14c80b7C1Jzsx5w1zMvk2GgW\n"
+"5kfGMOKXjwF/U+51ZfBDKehs3ivdeXAJBkxIh7E3iA32s+HyNqk+ntl9fg==\n"
+"-----END PUBLIC KEY-----\n";
+
+static const unsigned char p256k1_raw[] = {
+ 0x04, 0x53, 0x5c, 0xbc, 0x73, 0x42, 0x60, 0xf4,
+ 0x51, 0xab, 0xde, 0xf6, 0x02, 0x86, 0x91, 0x28,
+ 0xf5, 0xce, 0x1d, 0xa6, 0x48, 0xa3, 0xae, 0x26,
+ 0x0c, 0x43, 0x35, 0x3f, 0x0d, 0xeb, 0x24, 0x0b,
+ 0xcf, 0x61, 0x66, 0x32, 0xcd, 0x1f, 0x54, 0xa3,
+ 0xf2, 0xb6, 0xb6, 0x32, 0xc0, 0x65, 0x54, 0x61,
+ 0x89, 0x4c, 0xd3, 0xc2, 0x65, 0x2c, 0xb1, 0xf0,
+ 0x9a, 0x3b, 0x11, 0x16, 0xa9, 0xc5, 0xd3, 0x7b,
+ 0x51,
+};
+
+static const unsigned char p256v1_raw[] = {
+ 0x04, 0x3b, 0x08, 0xaa, 0xd7, 0x87, 0x3c, 0xd1,
+ 0xbe, 0xc2, 0xd4, 0x9c, 0xec, 0xc7, 0x9c, 0x35,
+ 0xcc, 0xcb, 0xe4, 0xd8, 0x68, 0x16, 0xe6, 0x47,
+ 0xc6, 0x30, 0xe2, 0x97, 0x8f, 0x01, 0x7f, 0x53,
+ 0xee, 0x75, 0x65, 0xf0, 0x43, 0x29, 0xe8, 0x6c,
+ 0xde, 0x2b, 0xdd, 0x79, 0x70, 0x09, 0x06, 0x4c,
+ 0x48, 0x87, 0xb1, 0x37, 0x88, 0x0d, 0xf6, 0xb3,
+ 0xe1, 0xf2, 0x36, 0xa9, 0x3e, 0x9e, 0xd9, 0x7d,
+ 0x7e,
+};
+
+static EVP_PKEY *
+EVP_PKEY_from_PEM(const char *ptr, size_t len)
+{
+ BIO *bio = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) {
+ warnx("BIO_new");
+ goto out;
+ }
+ if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) {
+ warnx("BIO_write");
+ goto out;
+ }
+ if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL)
+ warnx("PEM_read_bio_PUBKEY");
+out:
+ BIO_free(bio);
+
+ return pkey;
+}
+
+static int
+es256_pk_cmp(const char *ptr, size_t len)
+{
+ EVP_PKEY *pkA = NULL;
+ EVP_PKEY *pkB = NULL;
+ es256_pk_t *k = NULL;
+ int r, ok = -1;
+
+ if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) {
+ warnx("EVP_PKEY_from_PEM");
+ goto out;
+ }
+ if ((k = es256_pk_new()) == NULL) {
+ warnx("es256_pk_new");
+ goto out;
+ }
+ if ((r = es256_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) {
+ warnx("es256_pk_from_EVP_PKEY: 0x%x", r);
+ goto out;
+ }
+ if ((pkB = es256_pk_to_EVP_PKEY(k)) == NULL) {
+ warnx("es256_pk_to_EVP_PKEY");
+ goto out;
+ }
+ if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) {
+ warnx("EVP_PKEY_cmp: %d", r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ EVP_PKEY_free(pkA);
+ EVP_PKEY_free(pkB);
+ es256_pk_free(&k);
+
+ return ok;
+}
+
+static void
+short_coord(void)
+{
+ assert(es256_pk_cmp(short_x, sizeof(short_x)) == 0);
+ assert(es256_pk_cmp(short_y, sizeof(short_y)) == 0);
+}
+
+static void
+invalid_curve(const unsigned char *raw, size_t raw_len)
+{
+ EVP_PKEY *pkey;
+ es256_pk_t *pk;
+
+ ASSERT_NOT_NULL((pkey = EVP_PKEY_from_PEM(p256k1, sizeof(p256k1))));
+ ASSERT_NOT_NULL((pk = es256_pk_new()));
+ ASSERT_INVAL(es256_pk_from_EVP_PKEY(pk, pkey));
+ ASSERT_INVAL(es256_pk_from_ptr(pk, raw, raw_len));
+ ASSERT_NULL(es256_pk_to_EVP_PKEY((const es256_pk_t *)raw));
+
+ EVP_PKEY_free(pkey);
+ es256_pk_free(&pk);
+}
+
+static void
+full_coord(void)
+{
+ assert(es256_pk_cmp(p256v1, sizeof(p256v1)) == 0);
+}
+
+static void
+valid_curve(const unsigned char *raw, size_t raw_len)
+{
+ EVP_PKEY *pkeyA;
+ EVP_PKEY *pkeyB;
+ es256_pk_t *pkA;
+ es256_pk_t *pkB;
+
+ ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(p256v1, sizeof(p256v1))));
+ ASSERT_NOT_NULL((pkA = es256_pk_new()));
+ ASSERT_NOT_NULL((pkB = es256_pk_new()));
+ ASSERT_OK(es256_pk_from_EVP_PKEY(pkA, pkeyA));
+ ASSERT_OK(es256_pk_from_ptr(pkB, raw, raw_len));
+ ASSERT_NOT_NULL((pkeyB = es256_pk_to_EVP_PKEY(pkB)));
+ assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1);
+
+ EVP_PKEY_free(pkeyA);
+ EVP_PKEY_free(pkeyB);
+ es256_pk_free(&pkA);
+ es256_pk_free(&pkB);
+}
+
+int
+main(void)
+{
+ fido_init(0);
+
+ short_coord();
+ full_coord();
+
+ invalid_curve(p256k1_raw, sizeof(p256k1_raw)); /* uncompressed */
+ invalid_curve(p256k1_raw + 1, sizeof(p256k1_raw) - 1); /* libfido2 */
+ valid_curve(p256v1_raw, sizeof(p256v1_raw)); /* uncompressed */
+ valid_curve(p256v1_raw + 1, sizeof(p256v1_raw) - 1); /* libfido2 */
+
+ exit(0);
+}
diff --git a/regress/es384.c b/regress/es384.c
new file mode 100644
index 0000000..b55ce01
--- /dev/null
+++ b/regress/es384.c
@@ -0,0 +1,213 @@
+/*
+ * 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
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <string.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+#include <fido/es384.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#define ASSERT_NOT_NULL(e) assert((e) != NULL)
+#define ASSERT_NULL(e) assert((e) == NULL)
+#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT)
+#define ASSERT_OK(e) assert((e) == FIDO_OK)
+
+static const char short_x[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAAZ/VVCUmFU6aH9kJdDnUHCCglkatFTX\n"
+"onMwIvNYyS8BW/HOoZiOQLs2Hg+qifwaP1pHKILzCVfFmWuZMhxhtmjNXFuOPDnS\n"
+"Wa1PMdkCoWXA2BbXxnqL9v36gIOcFBil\n"
+"-----END PUBLIC KEY-----";
+
+static const char short_y[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuDpRBAg87cnWVhxbWnaWlnj100w9pm5k\n"
+"6T4eYToISaIhEK70TnGwULHX0+qHCYEGACOM7B/ZJbqjo6I7MIXaKZLemGi+tqvy\n"
+"ajBAsTVSyrYBLQjTMMcaFmYmsxvFx7pK\n"
+"-----END PUBLIC KEY-----\n";
+
+static const char brainpoolP384r1[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MHowFAYHKoZIzj0CAQYJKyQDAwIIAQELA2IABFKswbBzqqyZ4h1zz8rivqHzJxAO\n"
+"XC2aLyC9x5gwBM7GVu8k6jkX7VypRpg3yyCneiIQ+vVCNXgbDchJ0cPVuhwm3Zru\n"
+"AK49dezUPahWF0YiJRFVeV+KyB/MEaaZvinzqw==\n"
+"-----END PUBLIC KEY-----\n";
+
+static const char secp384r1[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdJN9DoqPtTNAOmjnECHBIqnJgyBW0rct\n"
+"tbUSqQjb6UG2lldmrQJbgCP/ywuXvkkJl4yfXxOr0UP3rgcnqTVA1/46s2TG+R5u\n"
+"NSQbCM1JPQuvTyFlAn5mdR8ZJJ8yPBQm\n"
+"-----END PUBLIC KEY-----\n";
+
+static const unsigned char brainpoolP384r1_raw[] = {
+ 0x04, 0x52, 0xac, 0xc1, 0xb0, 0x73, 0xaa, 0xac,
+ 0x99, 0xe2, 0x1d, 0x73, 0xcf, 0xca, 0xe2, 0xbe,
+ 0xa1, 0xf3, 0x27, 0x10, 0x0e, 0x5c, 0x2d, 0x9a,
+ 0x2f, 0x20, 0xbd, 0xc7, 0x98, 0x30, 0x04, 0xce,
+ 0xc6, 0x56, 0xef, 0x24, 0xea, 0x39, 0x17, 0xed,
+ 0x5c, 0xa9, 0x46, 0x98, 0x37, 0xcb, 0x20, 0xa7,
+ 0x7a, 0x22, 0x10, 0xfa, 0xf5, 0x42, 0x35, 0x78,
+ 0x1b, 0x0d, 0xc8, 0x49, 0xd1, 0xc3, 0xd5, 0xba,
+ 0x1c, 0x26, 0xdd, 0x9a, 0xee, 0x00, 0xae, 0x3d,
+ 0x75, 0xec, 0xd4, 0x3d, 0xa8, 0x56, 0x17, 0x46,
+ 0x22, 0x25, 0x11, 0x55, 0x79, 0x5f, 0x8a, 0xc8,
+ 0x1f, 0xcc, 0x11, 0xa6, 0x99, 0xbe, 0x29, 0xf3,
+ 0xab,
+};
+
+static const unsigned char secp384r1_raw[] = {
+ 0x04, 0x74, 0x93, 0x7d, 0x0e, 0x8a, 0x8f, 0xb5,
+ 0x33, 0x40, 0x3a, 0x68, 0xe7, 0x10, 0x21, 0xc1,
+ 0x22, 0xa9, 0xc9, 0x83, 0x20, 0x56, 0xd2, 0xb7,
+ 0x2d, 0xb5, 0xb5, 0x12, 0xa9, 0x08, 0xdb, 0xe9,
+ 0x41, 0xb6, 0x96, 0x57, 0x66, 0xad, 0x02, 0x5b,
+ 0x80, 0x23, 0xff, 0xcb, 0x0b, 0x97, 0xbe, 0x49,
+ 0x09, 0x97, 0x8c, 0x9f, 0x5f, 0x13, 0xab, 0xd1,
+ 0x43, 0xf7, 0xae, 0x07, 0x27, 0xa9, 0x35, 0x40,
+ 0xd7, 0xfe, 0x3a, 0xb3, 0x64, 0xc6, 0xf9, 0x1e,
+ 0x6e, 0x35, 0x24, 0x1b, 0x08, 0xcd, 0x49, 0x3d,
+ 0x0b, 0xaf, 0x4f, 0x21, 0x65, 0x02, 0x7e, 0x66,
+ 0x75, 0x1f, 0x19, 0x24, 0x9f, 0x32, 0x3c, 0x14,
+ 0x26,
+};
+
+static EVP_PKEY *
+EVP_PKEY_from_PEM(const char *ptr, size_t len)
+{
+ BIO *bio = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) {
+ warnx("BIO_new");
+ goto out;
+ }
+ if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) {
+ warnx("BIO_write");
+ goto out;
+ }
+ if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL)
+ warnx("PEM_read_bio_PUBKEY");
+out:
+ BIO_free(bio);
+
+ return pkey;
+}
+
+static int
+es384_pk_cmp(const char *ptr, size_t len)
+{
+ EVP_PKEY *pkA = NULL;
+ EVP_PKEY *pkB = NULL;
+ es384_pk_t *k = NULL;
+ int r, ok = -1;
+
+ if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) {
+ warnx("EVP_PKEY_from_PEM");
+ goto out;
+ }
+ if ((k = es384_pk_new()) == NULL) {
+ warnx("es384_pk_new");
+ goto out;
+ }
+ if ((r = es384_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) {
+ warnx("es384_pk_from_EVP_PKEY: 0x%x", r);
+ goto out;
+ }
+ if ((pkB = es384_pk_to_EVP_PKEY(k)) == NULL) {
+ warnx("es384_pk_to_EVP_PKEY");
+ goto out;
+ }
+ if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) {
+ warnx("EVP_PKEY_cmp: %d", r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ EVP_PKEY_free(pkA);
+ EVP_PKEY_free(pkB);
+ es384_pk_free(&k);
+
+ return ok;
+}
+
+static void
+short_coord(void)
+{
+ assert(es384_pk_cmp(short_x, sizeof(short_x)) == 0);
+ assert(es384_pk_cmp(short_y, sizeof(short_y)) == 0);
+}
+
+static void
+invalid_curve(const unsigned char *raw, size_t raw_len)
+{
+ EVP_PKEY *pkey;
+ es384_pk_t *pk;
+
+ pkey = EVP_PKEY_from_PEM(brainpoolP384r1, sizeof(brainpoolP384r1));
+ if (pkey == NULL)
+ return; /* assume no brainpool support in libcrypto */
+ ASSERT_NOT_NULL((pk = es384_pk_new()));
+ ASSERT_INVAL(es384_pk_from_EVP_PKEY(pk, pkey));
+ ASSERT_INVAL(es384_pk_from_ptr(pk, raw, raw_len));
+ ASSERT_NULL(es384_pk_to_EVP_PKEY((const es384_pk_t *)raw));
+
+ EVP_PKEY_free(pkey);
+ es384_pk_free(&pk);
+}
+
+static void
+full_coord(void)
+{
+ assert(es384_pk_cmp(secp384r1, sizeof(secp384r1)) == 0);
+}
+
+static void
+valid_curve(const unsigned char *raw, size_t raw_len)
+{
+ EVP_PKEY *pkeyA;
+ EVP_PKEY *pkeyB;
+ es384_pk_t *pkA;
+ es384_pk_t *pkB;
+
+ ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(secp384r1, sizeof(secp384r1))));
+ ASSERT_NOT_NULL((pkA = es384_pk_new()));
+ ASSERT_NOT_NULL((pkB = es384_pk_new()));
+ ASSERT_OK(es384_pk_from_EVP_PKEY(pkA, pkeyA));
+ ASSERT_OK(es384_pk_from_ptr(pkB, raw, raw_len));
+ ASSERT_NOT_NULL((pkeyB = es384_pk_to_EVP_PKEY(pkB)));
+ assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1);
+
+ EVP_PKEY_free(pkeyA);
+ EVP_PKEY_free(pkeyB);
+ es384_pk_free(&pkA);
+ es384_pk_free(&pkB);
+}
+
+int
+main(void)
+{
+ fido_init(0);
+
+ short_coord();
+ full_coord();
+
+ invalid_curve(brainpoolP384r1_raw, sizeof(brainpoolP384r1_raw)); /* uncompressed */
+ invalid_curve(brainpoolP384r1_raw + 1, sizeof(brainpoolP384r1_raw) - 1); /* libfido2 */
+ valid_curve(secp384r1_raw, sizeof(secp384r1_raw)); /* uncompressed */
+ valid_curve(secp384r1_raw + 1, sizeof(secp384r1_raw) - 1); /* libfido2 */
+
+ exit(0);
+}
diff --git a/regress/rs256.c b/regress/rs256.c
new file mode 100644
index 0000000..799396f
--- /dev/null
+++ b/regress/rs256.c
@@ -0,0 +1,201 @@
+/*
+ * 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
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <string.h>
+
+#define _FIDO_INTERNAL
+
+#include <fido.h>
+#include <fido/rs256.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#define ASSERT_NOT_NULL(e) assert((e) != NULL)
+#define ASSERT_NULL(e) assert((e) == NULL)
+#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT)
+#define ASSERT_OK(e) assert((e) == FIDO_OK)
+
+static char rsa1024[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw92gn9Ku/bEfFj1AutaZyltpf\n"
+"zzXrg70kQFymNq+spMt/HlxKiImw8TZU08zWW4ZLE/Ch4JYjMW6ETAdQFhSC63Ih\n"
+"Wecui0JJ1f+2CsUVg+h7lO1877LZYUpdNiJrbqMb5Yc4N3FPtvdl3NoLIIQsF76H\n"
+"VRvpjQgkWipRfZ97JQIDAQAB\n"
+"-----END PUBLIC KEY-----";
+
+static char rsa2048[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApvIq/55ZodBIxzo/8BnE\n"
+"UQN1fo1hmJ6V20hQHSzJq5tHyxRCcvKikuJ1ZvR4RdZlEzdTdbEfMBdZ8sxve0/U\n"
+"yYEjH92CG0vgTCYuUaFLJTaWZSvWa96G8Lw+V4VyNFDRCM7sflOaSVH5pAsz8OEc\n"
+"TLZfM4NhnDsJAM+mQ6X7Tza0sczPchgDA+9KByXo/VIqyuBQs17rlKC2reMa8NkY\n"
+"rBRQZJLNzi68d5/BHH1flGWE1l8wJ9dr1Ex93H/KdzX+7/28TWUC98nneUo8RfRx\n"
+"FwUt/EInDMHOORCaCHSs28U/9IUyMjqLB1rxKhIp09yGXMiTrrT+p+Pcn8dO01HT\n"
+"vQIDAQAB\n"
+"-----END PUBLIC KEY-----";
+
+static char rsa3072[] = \
+"-----BEGIN PUBLIC KEY-----\n"
+"MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAwZunKrMs/o92AniLPNTF\n"
+"Ta4EYfhy5NDmMvQvRFT/eTYItLrOTPmYMap68KLyZYmgz/AdaxAL/992QWre7XTY\n"
+"gqLwtZT+WsSu7xPHWKTTXrlVohKBeLHQ0I7Zy0NSMUxhlJEMrBAjSyFAS86zWm5w\n"
+"ctC3pNCqfUKugA07BVj+d5Mv5fziwgMR86kuhkVuMYfsR4IYwX4+va0pyLzxx624\n"
+"s9nJ107g+A+3MUk4bAto3lruFeeZPUI2AFzFQbGg5By6VtvVi3gKQ7lUNtAr0Onu\n"
+"I6Fb+yz8sbFcvDpJcu5CXW20GrKMVP4KY5pn2LCajWuZjBl/dXWayPfm4UX5Y2O4\n"
+"73tzPpUBNwnEdz79His0v80Vmvjwn5IuF2jAoimrBNPJFFwCCuVNy8kgj2vllk1l\n"
+"RvLOG6hf8VnlDb40QZS3QAQ09xFfF+xlVLb8cHH6wllaAGEM230TrmawpC7xpz4Z\n"
+"sTuwJwI0AWEi//noMsRz2BuF2fCp//aORYJQU2S8kYk3AgMBAAE=\n"
+"-----END PUBLIC KEY-----";
+
+static const unsigned char rsa2048_raw[] = {
+ 0xa6, 0xf2, 0x2a, 0xff, 0x9e, 0x59, 0xa1, 0xd0,
+ 0x48, 0xc7, 0x3a, 0x3f, 0xf0, 0x19, 0xc4, 0x51,
+ 0x03, 0x75, 0x7e, 0x8d, 0x61, 0x98, 0x9e, 0x95,
+ 0xdb, 0x48, 0x50, 0x1d, 0x2c, 0xc9, 0xab, 0x9b,
+ 0x47, 0xcb, 0x14, 0x42, 0x72, 0xf2, 0xa2, 0x92,
+ 0xe2, 0x75, 0x66, 0xf4, 0x78, 0x45, 0xd6, 0x65,
+ 0x13, 0x37, 0x53, 0x75, 0xb1, 0x1f, 0x30, 0x17,
+ 0x59, 0xf2, 0xcc, 0x6f, 0x7b, 0x4f, 0xd4, 0xc9,
+ 0x81, 0x23, 0x1f, 0xdd, 0x82, 0x1b, 0x4b, 0xe0,
+ 0x4c, 0x26, 0x2e, 0x51, 0xa1, 0x4b, 0x25, 0x36,
+ 0x96, 0x65, 0x2b, 0xd6, 0x6b, 0xde, 0x86, 0xf0,
+ 0xbc, 0x3e, 0x57, 0x85, 0x72, 0x34, 0x50, 0xd1,
+ 0x08, 0xce, 0xec, 0x7e, 0x53, 0x9a, 0x49, 0x51,
+ 0xf9, 0xa4, 0x0b, 0x33, 0xf0, 0xe1, 0x1c, 0x4c,
+ 0xb6, 0x5f, 0x33, 0x83, 0x61, 0x9c, 0x3b, 0x09,
+ 0x00, 0xcf, 0xa6, 0x43, 0xa5, 0xfb, 0x4f, 0x36,
+ 0xb4, 0xb1, 0xcc, 0xcf, 0x72, 0x18, 0x03, 0x03,
+ 0xef, 0x4a, 0x07, 0x25, 0xe8, 0xfd, 0x52, 0x2a,
+ 0xca, 0xe0, 0x50, 0xb3, 0x5e, 0xeb, 0x94, 0xa0,
+ 0xb6, 0xad, 0xe3, 0x1a, 0xf0, 0xd9, 0x18, 0xac,
+ 0x14, 0x50, 0x64, 0x92, 0xcd, 0xce, 0x2e, 0xbc,
+ 0x77, 0x9f, 0xc1, 0x1c, 0x7d, 0x5f, 0x94, 0x65,
+ 0x84, 0xd6, 0x5f, 0x30, 0x27, 0xd7, 0x6b, 0xd4,
+ 0x4c, 0x7d, 0xdc, 0x7f, 0xca, 0x77, 0x35, 0xfe,
+ 0xef, 0xfd, 0xbc, 0x4d, 0x65, 0x02, 0xf7, 0xc9,
+ 0xe7, 0x79, 0x4a, 0x3c, 0x45, 0xf4, 0x71, 0x17,
+ 0x05, 0x2d, 0xfc, 0x42, 0x27, 0x0c, 0xc1, 0xce,
+ 0x39, 0x10, 0x9a, 0x08, 0x74, 0xac, 0xdb, 0xc5,
+ 0x3f, 0xf4, 0x85, 0x32, 0x32, 0x3a, 0x8b, 0x07,
+ 0x5a, 0xf1, 0x2a, 0x12, 0x29, 0xd3, 0xdc, 0x86,
+ 0x5c, 0xc8, 0x93, 0xae, 0xb4, 0xfe, 0xa7, 0xe3,
+ 0xdc, 0x9f, 0xc7, 0x4e, 0xd3, 0x51, 0xd3, 0xbd,
+ 0x01, 0x00, 0x01,
+};
+
+static EVP_PKEY *
+EVP_PKEY_from_PEM(const char *ptr, size_t len)
+{
+ BIO *bio = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((bio = BIO_new(BIO_s_mem())) == NULL) {
+ warnx("BIO_new");
+ goto out;
+ }
+ if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) {
+ warnx("BIO_write");
+ goto out;
+ }
+ if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL)
+ warnx("PEM_read_bio_PUBKEY");
+out:
+ BIO_free(bio);
+
+ return pkey;
+}
+
+static int
+rs256_pk_cmp(const char *ptr, size_t len)
+{
+ EVP_PKEY *pkA = NULL;
+ EVP_PKEY *pkB = NULL;
+ rs256_pk_t *k = NULL;
+ int r, ok = -1;
+
+ if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) {
+ warnx("EVP_PKEY_from_PEM");
+ goto out;
+ }
+ if ((k = rs256_pk_new()) == NULL) {
+ warnx("rs256_pk_new");
+ goto out;
+ }
+ if ((r = rs256_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) {
+ warnx("rs256_pk_from_EVP_PKEY: 0x%x", r);
+ goto out;
+ }
+ if ((pkB = rs256_pk_to_EVP_PKEY(k)) == NULL) {
+ warnx("rs256_pk_to_EVP_PKEY");
+ goto out;
+ }
+ if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) {
+ warnx("EVP_PKEY_cmp: %d", r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ EVP_PKEY_free(pkA);
+ EVP_PKEY_free(pkB);
+ rs256_pk_free(&k);
+
+ return ok;
+}
+
+static void
+invalid_size(const char *pem)
+{
+ EVP_PKEY *pkey;
+ rs256_pk_t *pk;
+
+ ASSERT_NOT_NULL((pkey = EVP_PKEY_from_PEM(pem, strlen(pem))));
+ ASSERT_NOT_NULL((pk = rs256_pk_new()));
+ ASSERT_INVAL(rs256_pk_from_EVP_PKEY(pk, pkey));
+
+ EVP_PKEY_free(pkey);
+ rs256_pk_free(&pk);
+}
+
+static void
+valid_size(const char *pem, const unsigned char *raw, size_t raw_len)
+{
+ EVP_PKEY *pkeyA;
+ EVP_PKEY *pkeyB;
+ rs256_pk_t *pkA;
+ rs256_pk_t *pkB;
+
+ ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(pem, strlen(pem))));
+ ASSERT_NOT_NULL((pkA = rs256_pk_new()));
+ ASSERT_NOT_NULL((pkB = rs256_pk_new()));
+ ASSERT_OK(rs256_pk_from_EVP_PKEY(pkA, pkeyA));
+ ASSERT_OK(rs256_pk_from_ptr(pkB, raw, raw_len));
+ ASSERT_NOT_NULL((pkeyB = rs256_pk_to_EVP_PKEY(pkB)));
+ assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1);
+ assert(rs256_pk_cmp(pem, strlen(pem)) == 0);
+
+ EVP_PKEY_free(pkeyA);
+ EVP_PKEY_free(pkeyB);
+ rs256_pk_free(&pkA);
+ rs256_pk_free(&pkB);
+}
+
+int
+main(void)
+{
+ fido_init(0);
+
+ invalid_size(rsa1024);
+ invalid_size(rsa3072);
+ valid_size(rsa2048, rsa2048_raw, sizeof(rsa2048_raw));
+
+ exit(0);
+}
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;
+}
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000..e1f4366
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,74 @@
+# Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+# SPDX-License-Identifier: BSD-2-Clause
+
+list(APPEND COMPAT_SOURCES
+ ../openbsd-compat/bsd-getpagesize.c
+ ../openbsd-compat/explicit_bzero.c
+ ../openbsd-compat/freezero.c
+ ../openbsd-compat/recallocarray.c
+ ../openbsd-compat/strlcat.c
+ ../openbsd-compat/strlcpy.c
+ ../openbsd-compat/strsep.c
+)
+
+if(WIN32 AND NOT CYGWIN AND NOT MSYS)
+ list(APPEND COMPAT_SOURCES
+ ../openbsd-compat/bsd-getline.c
+ ../openbsd-compat/endian_win32.c
+ ../openbsd-compat/explicit_bzero_win32.c
+ ../openbsd-compat/getopt_long.c
+ ../openbsd-compat/readpassphrase_win32.c
+ )
+ if (BUILD_SHARED_LIBS)
+ list(APPEND COMPAT_SOURCES ../openbsd-compat/posix_win.c)
+ endif()
+else()
+ list(APPEND COMPAT_SOURCES ../openbsd-compat/readpassphrase.c)
+endif()
+
+if(NOT MSVC)
+ set_source_files_properties(assert_get.c assert_verify.c base64.c bio.c
+ config.c cred_make.c cred_verify.c credman.c fido2-assert.c
+ fido2-cred.c fido2-token.c pin.c token.c util.c
+ PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS}")
+endif()
+
+add_executable(fido2-cred
+ fido2-cred.c
+ cred_make.c
+ cred_verify.c
+ base64.c
+ util.c
+ ${COMPAT_SOURCES}
+)
+
+add_executable(fido2-assert
+ fido2-assert.c
+ assert_get.c
+ assert_verify.c
+ base64.c
+ util.c
+ ${COMPAT_SOURCES}
+)
+
+add_executable(fido2-token
+ fido2-token.c
+ base64.c
+ bio.c
+ config.c
+ credman.c
+ largeblob.c
+ pin.c
+ token.c
+ util.c
+ ${COMPAT_SOURCES}
+)
+
+target_link_libraries(fido2-cred ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY})
+target_link_libraries(fido2-assert ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY})
+target_link_libraries(fido2-token ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY})
+
+install(TARGETS fido2-cred fido2-assert fido2-token
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/tools/assert_get.c b/tools/assert_get.c
new file mode 100644
index 0000000..32d40b1
--- /dev/null
+++ b/tools/assert_get.c
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+struct toggle {
+ fido_opt_t up;
+ fido_opt_t uv;
+ fido_opt_t pin;
+};
+
+static const char *
+opt2str(fido_opt_t v)
+{
+ switch (v) {
+ case FIDO_OPT_OMIT:
+ return "omit";
+ case FIDO_OPT_TRUE:
+ return "true";
+ case FIDO_OPT_FALSE:
+ return "false";
+ default:
+ return "unknown";
+ }
+}
+
+static void
+parse_toggle(const char *str, struct toggle *opt)
+{
+ fido_opt_t *k;
+ fido_opt_t v;
+ char *assignment;
+ char *key;
+ char *val;
+
+ if ((assignment = strdup(str)) == NULL)
+ err(1, "strdup");
+ if ((val = strchr(assignment, '=')) == NULL)
+ errx(1, "invalid assignment '%s'", assignment);
+
+ key = assignment;
+ *val++ = '\0';
+
+ if (!strcmp(val, "true"))
+ v = FIDO_OPT_TRUE;
+ else if (!strcmp(val, "false"))
+ v = FIDO_OPT_FALSE;
+ else
+ errx(1, "unknown value '%s'", val);
+
+ if (!strcmp(key, "up"))
+ k = &opt->up;
+ else if (!strcmp(key, "uv"))
+ k = &opt->uv;
+ else if (!strcmp(key, "pin"))
+ k = &opt->pin;
+ else
+ errx(1, "unknown key '%s'", key);
+
+ free(assignment);
+
+ *k = v;
+}
+
+static fido_assert_t *
+prepare_assert(FILE *in_f, int flags, const struct toggle *opt)
+{
+ fido_assert_t *assert = NULL;
+ struct blob cdh;
+ struct blob id;
+ struct blob hmac_salt;
+ char *rpid = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&id, 0, sizeof(id));
+ memset(&hmac_salt, 0, sizeof(hmac_salt));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ if ((flags & FLAG_RK) == 0)
+ r |= base64_read(in_f, &id);
+ if (flags & FLAG_HMAC)
+ r |= base64_read(in_f, &hmac_salt);
+ if (r < 0)
+ errx(1, "input error");
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data%s:\n",
+ flags & FLAG_CD ? "" : " hash");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ if ((flags & FLAG_RK) == 0) {
+ fprintf(stderr, "credential id:\n");
+ xxd(id.ptr, id.len);
+ }
+ fprintf(stderr, "up=%s\n", opt2str(opt->up));
+ fprintf(stderr, "uv=%s\n", opt2str(opt->uv));
+ fprintf(stderr, "pin=%s\n", opt2str(opt->pin));
+ }
+
+ if ((assert = fido_assert_new()) == NULL)
+ errx(1, "fido_assert_new");
+
+ if (flags & FLAG_CD)
+ r = fido_assert_set_clientdata(assert, cdh.ptr, cdh.len);
+ else
+ r = fido_assert_set_clientdata_hash(assert, cdh.ptr, cdh.len);
+
+ if (r != FIDO_OK || (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK)
+ errx(1, "fido_assert_set: %s", fido_strerr(r));
+ if ((r = fido_assert_set_up(assert, opt->up)) != FIDO_OK)
+ errx(1, "fido_assert_set_up: %s", fido_strerr(r));
+ if ((r = fido_assert_set_uv(assert, opt->uv)) != FIDO_OK)
+ errx(1, "fido_assert_set_uv: %s", fido_strerr(r));
+
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_assert_set_extensions(assert,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s",
+ fido_strerr(r));
+ if ((r = fido_assert_set_hmac_salt(assert, hmac_salt.ptr,
+ hmac_salt.len)) != FIDO_OK)
+ errx(1, "fido_assert_set_hmac_salt: %s",
+ fido_strerr(r));
+ }
+ if (flags & FLAG_LARGEBLOB) {
+ if ((r = fido_assert_set_extensions(assert,
+ FIDO_EXT_LARGEBLOB_KEY)) != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s", fido_strerr(r));
+ }
+ if ((flags & FLAG_RK) == 0) {
+ if ((r = fido_assert_allow_cred(assert, id.ptr,
+ id.len)) != FIDO_OK)
+ errx(1, "fido_assert_allow_cred: %s", fido_strerr(r));
+ }
+
+ free(hmac_salt.ptr);
+ free(cdh.ptr);
+ free(id.ptr);
+ free(rpid);
+
+ return (assert);
+}
+
+static void
+print_assert(FILE *out_f, const fido_assert_t *assert, size_t idx, int flags)
+{
+ char *cdh = NULL;
+ char *authdata = NULL;
+ char *sig = NULL;
+ char *user_id = NULL;
+ char *hmac_secret = NULL;
+ char *key = NULL;
+ int r;
+
+ r = base64_encode(fido_assert_clientdata_hash_ptr(assert),
+ fido_assert_clientdata_hash_len(assert), &cdh);
+ r |= base64_encode(fido_assert_authdata_ptr(assert, idx),
+ fido_assert_authdata_len(assert, 0), &authdata);
+ r |= base64_encode(fido_assert_sig_ptr(assert, idx),
+ fido_assert_sig_len(assert, idx), &sig);
+ if (flags & FLAG_RK)
+ r |= base64_encode(fido_assert_user_id_ptr(assert, idx),
+ fido_assert_user_id_len(assert, idx), &user_id);
+ if (flags & FLAG_HMAC)
+ r |= base64_encode(fido_assert_hmac_secret_ptr(assert, idx),
+ fido_assert_hmac_secret_len(assert, idx), &hmac_secret);
+ if (flags & FLAG_LARGEBLOB)
+ r |= base64_encode(fido_assert_largeblob_key_ptr(assert, idx),
+ fido_assert_largeblob_key_len(assert, idx), &key);
+ if (r < 0)
+ errx(1, "output error");
+
+ fprintf(out_f, "%s\n", cdh);
+ fprintf(out_f, "%s\n", fido_assert_rp_id(assert));
+ fprintf(out_f, "%s\n", authdata);
+ fprintf(out_f, "%s\n", sig);
+ if (flags & FLAG_RK)
+ fprintf(out_f, "%s\n", user_id);
+ if (hmac_secret) {
+ fprintf(out_f, "%s\n", hmac_secret);
+ explicit_bzero(hmac_secret, strlen(hmac_secret));
+ }
+ if (key) {
+ fprintf(out_f, "%s\n", key);
+ explicit_bzero(key, strlen(key));
+ }
+
+ free(key);
+ free(hmac_secret);
+ free(cdh);
+ free(authdata);
+ free(sig);
+ free(user_id);
+}
+
+int
+assert_get(int argc, char **argv)
+{
+ fido_dev_t *dev = NULL;
+ fido_assert_t *assert = NULL;
+ struct toggle opt;
+ char prompt[1024];
+ char pin[128];
+ char *in_path = NULL;
+ char *out_path = NULL;
+ FILE *in_f = NULL;
+ FILE *out_f = NULL;
+ int flags = 0;
+ int ch;
+ int r;
+
+ opt.up = opt.uv = opt.pin = FIDO_OPT_OMIT;
+
+ while ((ch = getopt(argc, argv, "bdhi:o:prt:uvw")) != -1) {
+ switch (ch) {
+ case 'b':
+ flags |= FLAG_LARGEBLOB;
+ break;
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'o':
+ out_path = optarg;
+ break;
+ case 'p':
+ opt.up = FIDO_OPT_TRUE;
+ break;
+ case 'r':
+ flags |= FLAG_RK;
+ break;
+ case 't' :
+ parse_toggle(optarg, &opt);
+ break;
+ case 'u':
+ flags |= FLAG_U2F;
+ break;
+ case 'v':
+ /* -v implies both pin and uv for historical reasons */
+ opt.pin = FIDO_OPT_TRUE;
+ opt.uv = FIDO_OPT_TRUE;
+ break;
+ case 'w':
+ flags |= FLAG_CD;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1)
+ usage();
+
+ in_f = open_read(in_path);
+ out_f = open_write(out_path);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+
+ assert = prepare_assert(in_f, flags, &opt);
+
+ dev = open_dev(argv[0]);
+ if (flags & FLAG_U2F)
+ fido_dev_force_u2f(dev);
+
+ if (opt.pin == FIDO_OPT_TRUE) {
+ r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ",
+ argv[0]);
+ if (r < 0 || (size_t)r >= sizeof(prompt))
+ errx(1, "snprintf");
+ if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF))
+ errx(1, "readpassphrase");
+ if (strlen(pin) < 4 || strlen(pin) > 63) {
+ explicit_bzero(pin, sizeof(pin));
+ errx(1, "invalid PIN length");
+ }
+ r = fido_dev_get_assert(dev, assert, pin);
+ } else
+ r = fido_dev_get_assert(dev, assert, NULL);
+
+ explicit_bzero(pin, sizeof(pin));
+
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_get_assert: %s", fido_strerr(r));
+
+ if (flags & FLAG_RK) {
+ for (size_t idx = 0; idx < fido_assert_count(assert); idx++)
+ print_assert(out_f, assert, idx, flags);
+ } else {
+ if (fido_assert_count(assert) != 1)
+ errx(1, "fido_assert_count: %zu",
+ fido_assert_count(assert));
+ print_assert(out_f, assert, 0, flags);
+ }
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_assert_free(&assert);
+
+ fclose(in_f);
+ fclose(out_f);
+ in_f = NULL;
+ out_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/assert_verify.c b/tools/assert_verify.c
new file mode 100644
index 0000000..4cc2e86
--- /dev/null
+++ b/tools/assert_verify.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <fido/es256.h>
+#include <fido/es384.h>
+#include <fido/rs256.h>
+#include <fido/eddsa.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static fido_assert_t *
+prepare_assert(FILE *in_f, int flags)
+{
+ fido_assert_t *assert = NULL;
+ struct blob cdh;
+ struct blob authdata;
+ struct blob sig;
+ char *rpid = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&authdata, 0, sizeof(authdata));
+ memset(&sig, 0, sizeof(sig));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ r |= base64_read(in_f, &authdata);
+ r |= base64_read(in_f, &sig);
+ if (r < 0)
+ errx(1, "input error");
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data hash:\n");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ fprintf(stderr, "authenticator data:\n");
+ xxd(authdata.ptr, authdata.len);
+ fprintf(stderr, "signature:\n");
+ xxd(sig.ptr, sig.len);
+ }
+
+ if ((assert = fido_assert_new()) == NULL)
+ errx(1, "fido_assert_new");
+ if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK)
+ errx(1, "fido_assert_count: %s", fido_strerr(r));
+
+ if ((r = fido_assert_set_clientdata_hash(assert, cdh.ptr,
+ cdh.len)) != FIDO_OK ||
+ (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK ||
+ (r = fido_assert_set_authdata(assert, 0, authdata.ptr,
+ authdata.len)) != FIDO_OK ||
+ (r = fido_assert_set_sig(assert, 0, sig.ptr, sig.len)) != FIDO_OK)
+ errx(1, "fido_assert_set: %s", fido_strerr(r));
+
+ if (flags & FLAG_UP) {
+ if ((r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_up: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_UV) {
+ if ((r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_assert_set_uv: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_assert_set_extensions(assert,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_assert_set_extensions: %s",
+ fido_strerr(r));
+ }
+
+ free(cdh.ptr);
+ free(authdata.ptr);
+ free(sig.ptr);
+ free(rpid);
+
+ return (assert);
+}
+
+static void *
+load_pubkey(int type, const char *file)
+{
+ EC_KEY *ec = NULL;
+ RSA *rsa = NULL;
+ EVP_PKEY *eddsa = NULL;
+ es256_pk_t *es256_pk = NULL;
+ es384_pk_t *es384_pk = NULL;
+ rs256_pk_t *rs256_pk = NULL;
+ eddsa_pk_t *eddsa_pk = NULL;
+ void *pk = NULL;
+
+ switch (type) {
+ case COSE_ES256:
+ if ((ec = read_ec_pubkey(file)) == NULL)
+ errx(1, "read_ec_pubkey");
+ if ((es256_pk = es256_pk_new()) == NULL)
+ errx(1, "es256_pk_new");
+ if (es256_pk_from_EC_KEY(es256_pk, ec) != FIDO_OK)
+ errx(1, "es256_pk_from_EC_KEY");
+ pk = es256_pk;
+ EC_KEY_free(ec);
+ break;
+ case COSE_ES384:
+ if ((ec = read_ec_pubkey(file)) == NULL)
+ errx(1, "read_ec_pubkey");
+ if ((es384_pk = es384_pk_new()) == NULL)
+ errx(1, "es384_pk_new");
+ if (es384_pk_from_EC_KEY(es384_pk, ec) != FIDO_OK)
+ errx(1, "es384_pk_from_EC_KEY");
+ pk = es384_pk;
+ EC_KEY_free(ec);
+ break;
+ case COSE_RS256:
+ if ((rsa = read_rsa_pubkey(file)) == NULL)
+ errx(1, "read_rsa_pubkey");
+ if ((rs256_pk = rs256_pk_new()) == NULL)
+ errx(1, "rs256_pk_new");
+ if (rs256_pk_from_RSA(rs256_pk, rsa) != FIDO_OK)
+ errx(1, "rs256_pk_from_RSA");
+ pk = rs256_pk;
+ RSA_free(rsa);
+ break;
+ case COSE_EDDSA:
+ if ((eddsa = read_eddsa_pubkey(file)) == NULL)
+ errx(1, "read_eddsa_pubkey");
+ if ((eddsa_pk = eddsa_pk_new()) == NULL)
+ errx(1, "eddsa_pk_new");
+ if (eddsa_pk_from_EVP_PKEY(eddsa_pk, eddsa) != FIDO_OK)
+ errx(1, "eddsa_pk_from_EVP_PKEY");
+ pk = eddsa_pk;
+ EVP_PKEY_free(eddsa);
+ break;
+ default:
+ errx(1, "invalid type %d", type);
+ }
+
+ return (pk);
+}
+
+int
+assert_verify(int argc, char **argv)
+{
+ fido_assert_t *assert = NULL;
+ void *pk = NULL;
+ char *in_path = NULL;
+ FILE *in_f = NULL;
+ int type = COSE_ES256;
+ int flags = 0;
+ int ch;
+ int r;
+
+ while ((ch = getopt(argc, argv, "dhi:pv")) != -1) {
+ switch (ch) {
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'p':
+ flags |= FLAG_UP;
+ break;
+ case 'v':
+ flags |= FLAG_UV;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1 || argc > 2)
+ usage();
+
+ in_f = open_read(in_path);
+
+ if (argc > 1 && cose_type(argv[1], &type) < 0)
+ errx(1, "unknown type %s", argv[1]);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+
+ pk = load_pubkey(type, argv[0]);
+ assert = prepare_assert(in_f, flags);
+ if ((r = fido_assert_verify(assert, 0, type, pk)) != FIDO_OK)
+ errx(1, "fido_assert_verify: %s", fido_strerr(r));
+ fido_assert_free(&assert);
+
+ fclose(in_f);
+ in_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/base64.c b/tools/base64.c
new file mode 100644
index 0000000..2cfa98d
--- /dev/null
+++ b/tools/base64.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+
+#include <limits.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+base64_encode(const void *ptr, size_t len, char **out)
+{
+ BIO *bio_b64 = NULL;
+ BIO *bio_mem = NULL;
+ char *b64_ptr = NULL;
+ long b64_len;
+ int n;
+ int ok = -1;
+
+ if (ptr == NULL || out == NULL || len > INT_MAX)
+ return (-1);
+
+ *out = NULL;
+
+ if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL)
+ goto fail;
+ if ((bio_mem = BIO_new(BIO_s_mem())) == NULL)
+ goto fail;
+
+ BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
+ BIO_push(bio_b64, bio_mem);
+
+ n = BIO_write(bio_b64, ptr, (int)len);
+ if (n < 0 || (size_t)n != len)
+ goto fail;
+
+ if (BIO_flush(bio_b64) < 0)
+ goto fail;
+
+ b64_len = BIO_get_mem_data(bio_b64, &b64_ptr);
+ if (b64_len < 0 || (size_t)b64_len == SIZE_MAX || b64_ptr == NULL)
+ goto fail;
+ if ((*out = calloc(1, (size_t)b64_len + 1)) == NULL)
+ goto fail;
+
+ memcpy(*out, b64_ptr, (size_t)b64_len);
+ ok = 0;
+
+fail:
+ BIO_free(bio_b64);
+ BIO_free(bio_mem);
+
+ return (ok);
+}
+
+int
+base64_decode(const char *in, void **ptr, size_t *len)
+{
+ BIO *bio_mem = NULL;
+ BIO *bio_b64 = NULL;
+ size_t alloc_len;
+ int n;
+ int ok = -1;
+
+ if (in == NULL || ptr == NULL || len == NULL || strlen(in) > INT_MAX)
+ return (-1);
+
+ *ptr = NULL;
+ *len = 0;
+
+ if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL)
+ goto fail;
+ if ((bio_mem = BIO_new_mem_buf((const void *)in, -1)) == NULL)
+ goto fail;
+
+ BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL);
+ BIO_push(bio_b64, bio_mem);
+
+ alloc_len = strlen(in);
+ if ((*ptr = calloc(1, alloc_len)) == NULL)
+ goto fail;
+
+ n = BIO_read(bio_b64, *ptr, (int)alloc_len);
+ if (n <= 0 || BIO_eof(bio_b64) == 0)
+ goto fail;
+
+ *len = (size_t)n;
+ ok = 0;
+
+fail:
+ BIO_free(bio_b64);
+ BIO_free(bio_mem);
+
+ if (ok < 0) {
+ free(*ptr);
+ *ptr = NULL;
+ *len = 0;
+ }
+
+ return (ok);
+}
+
+int
+base64_read(FILE *f, struct blob *out)
+{
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t n;
+
+ out->ptr = NULL;
+ out->len = 0;
+
+ if ((n = getline(&line, &linesize, f)) <= 0 ||
+ (size_t)n != strlen(line)) {
+ free(line); /* XXX should be free'd _even_ if getline() fails */
+ return (-1);
+ }
+
+ if (base64_decode(line, (void **)&out->ptr, &out->len) < 0) {
+ free(line);
+ return (-1);
+ }
+
+ free(line);
+
+ return (0);
+}
diff --git a/tools/bio.c b/tools/bio.c
new file mode 100644
index 0000000..7a1406d
--- /dev/null
+++ b/tools/bio.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <fido/bio.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static int
+print_template(const fido_bio_template_array_t *ta, size_t idx)
+{
+ const fido_bio_template_t *t = NULL;
+ char *id = NULL;
+
+ if ((t = fido_bio_template(ta, idx)) == NULL) {
+ warnx("fido_bio_template");
+ return -1;
+ }
+ if (base64_encode(fido_bio_template_id_ptr(t),
+ fido_bio_template_id_len(t), &id) < 0) {
+ warnx("output error");
+ return -1;
+ }
+
+ printf("%02u: %s %s\n", (unsigned)idx, id, fido_bio_template_name(t));
+ free(id);
+
+ return 0;
+}
+
+int
+bio_list(const char *path)
+{
+ fido_bio_template_array_t *ta = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ if ((ta = fido_bio_template_array_new()) == NULL)
+ errx(1, "fido_bio_template_array_new");
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_bio_dev_get_template_array(dev, ta, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_get_template_array: %s", fido_strerr(r));
+ goto out;
+ }
+ for (size_t i = 0; i < fido_bio_template_array_count(ta); i++)
+ if (print_template(ta, i) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ fido_bio_template_array_free(&ta);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+bio_set_name(const char *path, const char *id, const char *name)
+{
+ fido_bio_template_t *t = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ void *id_blob_ptr = NULL;
+ size_t id_blob_len = 0;
+ int r, ok = 1;
+
+ if ((t = fido_bio_template_new()) == NULL)
+ errx(1, "fido_bio_template_new");
+ if (base64_decode(id, &id_blob_ptr, &id_blob_len) < 0)
+ errx(1, "base64_decode");
+ if ((r = fido_bio_template_set_name(t, name)) != FIDO_OK)
+ errx(1, "fido_bio_template_set_name: %s", fido_strerr(r));
+ if ((r = fido_bio_template_set_id(t, id_blob_ptr,
+ id_blob_len)) != FIDO_OK)
+ errx(1, "fido_bio_template_set_id: %s", fido_strerr(r));
+
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_bio_dev_set_template_name(dev, t, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_set_template_name: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(id_blob_ptr);
+ fido_bio_template_free(&t);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static const char *
+enroll_strerr(uint8_t n)
+{
+ switch (n) {
+ case FIDO_BIO_ENROLL_FP_GOOD:
+ return "Sample ok";
+ case FIDO_BIO_ENROLL_FP_TOO_HIGH:
+ return "Sample too high";
+ case FIDO_BIO_ENROLL_FP_TOO_LOW:
+ return "Sample too low";
+ case FIDO_BIO_ENROLL_FP_TOO_LEFT:
+ return "Sample too left";
+ case FIDO_BIO_ENROLL_FP_TOO_RIGHT:
+ return "Sample too right";
+ case FIDO_BIO_ENROLL_FP_TOO_FAST:
+ return "Sample too fast";
+ case FIDO_BIO_ENROLL_FP_TOO_SLOW:
+ return "Sample too slow";
+ case FIDO_BIO_ENROLL_FP_POOR_QUALITY:
+ return "Poor quality sample";
+ case FIDO_BIO_ENROLL_FP_TOO_SKEWED:
+ return "Sample too skewed";
+ case FIDO_BIO_ENROLL_FP_TOO_SHORT:
+ return "Sample too short";
+ case FIDO_BIO_ENROLL_FP_MERGE_FAILURE:
+ return "Sample merge failure";
+ case FIDO_BIO_ENROLL_FP_EXISTS:
+ return "Sample exists";
+ case FIDO_BIO_ENROLL_FP_DATABASE_FULL:
+ return "Fingerprint database full";
+ case FIDO_BIO_ENROLL_NO_USER_ACTIVITY:
+ return "No user activity";
+ case FIDO_BIO_ENROLL_NO_USER_PRESENCE_TRANSITION:
+ return "No user presence transition";
+ default:
+ return "Unknown error";
+ }
+}
+
+int
+bio_enroll(const char *path)
+{
+ fido_bio_template_t *t = NULL;
+ fido_bio_enroll_t *e = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ if ((t = fido_bio_template_new()) == NULL)
+ errx(1, "fido_bio_template_new");
+ if ((e = fido_bio_enroll_new()) == NULL)
+ errx(1, "fido_bio_enroll_new");
+
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ printf("Touch your security key.\n");
+ r = fido_bio_dev_enroll_begin(dev, t, e, 10000, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_enroll_begin: %s", fido_strerr(r));
+ goto out;
+ }
+ printf("%s.\n", enroll_strerr(fido_bio_enroll_last_status(e)));
+
+ while (fido_bio_enroll_remaining_samples(e) > 0) {
+ printf("Touch your security key (%u sample%s left).\n",
+ (unsigned)fido_bio_enroll_remaining_samples(e),
+ plural(fido_bio_enroll_remaining_samples(e)));
+ if ((r = fido_bio_dev_enroll_continue(dev, t, e,
+ 10000)) != FIDO_OK) {
+ fido_dev_cancel(dev);
+ warnx("fido_bio_dev_enroll_continue: %s",
+ fido_strerr(r));
+ goto out;
+ }
+ printf("%s.\n", enroll_strerr(fido_bio_enroll_last_status(e)));
+ }
+
+ ok = 0;
+out:
+ fido_bio_template_free(&t);
+ fido_bio_enroll_free(&e);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+bio_delete(const char *path, const char *id)
+{
+ fido_bio_template_t *t = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ void *id_blob_ptr = NULL;
+ size_t id_blob_len = 0;
+ int r, ok = 1;
+
+ if ((t = fido_bio_template_new()) == NULL)
+ errx(1, "fido_bio_template_new");
+ if (base64_decode(id, &id_blob_ptr, &id_blob_len) < 0)
+ errx(1, "base64_decode");
+ if ((r = fido_bio_template_set_id(t, id_blob_ptr,
+ id_blob_len)) != FIDO_OK)
+ errx(1, "fido_bio_template_set_id: %s", fido_strerr(r));
+
+ dev = open_dev(path);
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_bio_dev_enroll_remove(dev, t, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ if (r != FIDO_OK) {
+ warnx("fido_bio_dev_enroll_remove: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(id_blob_ptr);
+ fido_bio_template_free(&t);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static const char *
+type_str(uint8_t t)
+{
+ switch (t) {
+ case 1:
+ return "touch";
+ case 2:
+ return "swipe";
+ default:
+ return "unknown";
+ }
+}
+
+void
+bio_info(fido_dev_t *dev)
+{
+ fido_bio_info_t *i = NULL;
+
+ if ((i = fido_bio_info_new()) == NULL) {
+ warnx("fido_bio_info_new");
+ return;
+ }
+ if (fido_bio_dev_get_info(dev, i) != FIDO_OK) {
+ fido_bio_info_free(&i);
+ return;
+ }
+
+ printf("sensor type: %u (%s)\n", (unsigned)fido_bio_info_type(i),
+ type_str(fido_bio_info_type(i)));
+ printf("max samples: %u\n", (unsigned)fido_bio_info_max_samples(i));
+
+ fido_bio_info_free(&i);
+}
diff --git a/tools/config.c b/tools/config.c
new file mode 100644
index 0000000..49253e8
--- /dev/null
+++ b/tools/config.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2020 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <fido.h>
+#include <fido/config.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+config_entattest(char *path)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((r = fido_dev_enable_entattest(dev, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_enable_entattest(dev, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_enable_entattest: %s (0x%x)",
+ fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_always_uv(char *path, int toggle)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int v, r, ok = 1;
+
+ dev = open_dev(path);
+ if (get_devopt(dev, "alwaysUv", &v) < 0) {
+ warnx("%s: getdevopt", __func__);
+ goto out;
+ }
+ if (v == -1) {
+ warnx("%s: option not found", __func__);
+ goto out;
+ }
+ if (v == toggle) {
+ ok = 0;
+ goto out;
+ }
+ if ((r = fido_dev_toggle_always_uv(dev, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_toggle_always_uv(dev, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_toggle_always_uv: %s (0x%x)",
+ fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_pin_minlen(char *path, const char *pinlen)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int len, r, ok = 1;
+
+ dev = open_dev(path);
+ if ((len = base10(pinlen)) < 0 || len > 63) {
+ warnx("%s: len > 63", __func__);
+ goto out;
+ }
+ if ((r = fido_dev_set_pin_minlen(dev, (size_t)len, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_set_pin_minlen(dev, (size_t)len, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_set_pin_minlen: %s (0x%x)", fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_force_pin_change(char *path)
+{
+ fido_dev_t *dev;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((r = fido_dev_force_pin_change(dev, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_force_pin_change(dev, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_force_pin_change: %s (0x%x)", fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+config_pin_minlen_rpid(char *path, const char *rpids)
+{
+ fido_dev_t *dev;
+ char *otmp, *tmp, *cp;
+ char *pin = NULL, **rpid = NULL;
+ int r, ok = 1;
+ size_t n;
+
+ if ((tmp = strdup(rpids)) == NULL)
+ err(1, "strdup");
+ otmp = tmp;
+ for (n = 0; (cp = strsep(&tmp, ",")) != NULL; n++) {
+ if (n == SIZE_MAX || (rpid = recallocarray(rpid, n, n + 1,
+ sizeof(*rpid))) == NULL)
+ err(1, "recallocarray");
+ if ((rpid[n] = strdup(cp)) == NULL)
+ err(1, "strdup");
+ if (*rpid[n] == '\0')
+ errx(1, "empty rpid");
+ }
+ free(otmp);
+ if (rpid == NULL || n == 0)
+ errx(1, "could not parse rp_id");
+ dev = open_dev(path);
+ if ((r = fido_dev_set_pin_minlen_rpid(dev, (const char * const *)rpid,
+ n, NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_set_pin_minlen_rpid(dev, (const char * const *)rpid,
+ n, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_set_pin_minlen_rpid: %s (0x%x)",
+ fido_strerr(r), r);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
diff --git a/tools/cred_make.c b/tools/cred_make.c
new file mode 100644
index 0000000..66c8b52
--- /dev/null
+++ b/tools/cred_make.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static fido_cred_t *
+prepare_cred(FILE *in_f, int type, int flags)
+{
+ fido_cred_t *cred = NULL;
+ struct blob cdh;
+ struct blob uid;
+ char *rpid = NULL;
+ char *uname = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&uid, 0, sizeof(uid));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ r |= string_read(in_f, &uname);
+ r |= base64_read(in_f, &uid);
+ if (r < 0)
+ errx(1, "input error");
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data%s:\n",
+ flags & FLAG_CD ? "" : " hash");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ fprintf(stderr, "user name: %s\n", uname);
+ fprintf(stderr, "user id:\n");
+ xxd(uid.ptr, uid.len);
+ }
+
+ if ((cred = fido_cred_new()) == NULL)
+ errx(1, "fido_cred_new");
+
+
+ if (flags & FLAG_CD)
+ r = fido_cred_set_clientdata(cred, cdh.ptr, cdh.len);
+ else
+ r = fido_cred_set_clientdata_hash(cred, cdh.ptr, cdh.len);
+
+ if (r != FIDO_OK || (r = fido_cred_set_type(cred, type)) != FIDO_OK ||
+ (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK ||
+ (r = fido_cred_set_user(cred, uid.ptr, uid.len, uname, NULL,
+ NULL)) != FIDO_OK)
+ errx(1, "fido_cred_set: %s", fido_strerr(r));
+
+ if (flags & FLAG_RK) {
+ if ((r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_rk: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_UV) {
+ if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_uv: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_cred_set_extensions(cred,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_LARGEBLOB) {
+ if ((r = fido_cred_set_extensions(cred,
+ FIDO_EXT_LARGEBLOB_KEY)) != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s", fido_strerr(r));
+ }
+
+ free(cdh.ptr);
+ free(uid.ptr);
+ free(rpid);
+ free(uname);
+
+ return (cred);
+}
+
+static void
+print_attcred(FILE *out_f, const fido_cred_t *cred)
+{
+ char *cdh = NULL;
+ char *authdata = NULL;
+ char *id = NULL;
+ char *sig = NULL;
+ char *x5c = NULL;
+ char *key = NULL;
+ int r;
+
+ r = base64_encode(fido_cred_clientdata_hash_ptr(cred),
+ fido_cred_clientdata_hash_len(cred), &cdh);
+ r |= base64_encode(fido_cred_authdata_ptr(cred),
+ fido_cred_authdata_len(cred), &authdata);
+ r |= base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred),
+ &id);
+ r |= base64_encode(fido_cred_sig_ptr(cred), fido_cred_sig_len(cred),
+ &sig);
+ if (fido_cred_x5c_ptr(cred) != NULL)
+ r |= base64_encode(fido_cred_x5c_ptr(cred),
+ fido_cred_x5c_len(cred), &x5c);
+ if (fido_cred_largeblob_key_ptr(cred) != NULL)
+ r |= base64_encode(fido_cred_largeblob_key_ptr(cred),
+ fido_cred_largeblob_key_len(cred), &key);
+ if (r < 0)
+ errx(1, "output error");
+
+ fprintf(out_f, "%s\n", cdh);
+ fprintf(out_f, "%s\n", fido_cred_rp_id(cred));
+ fprintf(out_f, "%s\n", fido_cred_fmt(cred));
+ fprintf(out_f, "%s\n", authdata);
+ fprintf(out_f, "%s\n", id);
+ fprintf(out_f, "%s\n", sig);
+ if (x5c != NULL)
+ fprintf(out_f, "%s\n", x5c);
+ if (key != NULL) {
+ fprintf(out_f, "%s\n", key);
+ explicit_bzero(key, strlen(key));
+ }
+
+ free(cdh);
+ free(authdata);
+ free(id);
+ free(sig);
+ free(x5c);
+ free(key);
+}
+
+int
+cred_make(int argc, char **argv)
+{
+ fido_dev_t *dev = NULL;
+ fido_cred_t *cred = NULL;
+ char prompt[1024];
+ char pin[128];
+ char *in_path = NULL;
+ char *out_path = NULL;
+ FILE *in_f = NULL;
+ FILE *out_f = NULL;
+ int type = COSE_ES256;
+ int flags = 0;
+ int cred_protect = -1;
+ int ch;
+ int r;
+
+ while ((ch = getopt(argc, argv, "bc:dhi:o:qruvw")) != -1) {
+ switch (ch) {
+ case 'b':
+ flags |= FLAG_LARGEBLOB;
+ break;
+ case 'c':
+ if ((cred_protect = base10(optarg)) < 0)
+ errx(1, "-c: invalid argument '%s'", optarg);
+ break;
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'o':
+ out_path = optarg;
+ break;
+ case 'q':
+ flags |= FLAG_QUIET;
+ break;
+ case 'r':
+ flags |= FLAG_RK;
+ break;
+ case 'u':
+ flags |= FLAG_U2F;
+ break;
+ case 'v':
+ flags |= FLAG_UV;
+ break;
+ case 'w':
+ flags |= FLAG_CD;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1 || argc > 2)
+ usage();
+
+ in_f = open_read(in_path);
+ out_f = open_write(out_path);
+
+ if (argc > 1 && cose_type(argv[1], &type) < 0)
+ errx(1, "unknown type %s", argv[1]);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+
+ cred = prepare_cred(in_f, type, flags);
+
+ dev = open_dev(argv[0]);
+ if (flags & FLAG_U2F)
+ fido_dev_force_u2f(dev);
+
+ if (cred_protect > 0) {
+ r = fido_cred_set_prot(cred, cred_protect);
+ if (r != FIDO_OK) {
+ errx(1, "fido_cred_set_prot: %s", fido_strerr(r));
+ }
+ }
+
+ r = fido_dev_make_cred(dev, cred, NULL);
+ if (r == FIDO_ERR_PIN_REQUIRED && !(flags & FLAG_QUIET)) {
+ r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ",
+ argv[0]);
+ if (r < 0 || (size_t)r >= sizeof(prompt))
+ errx(1, "snprintf");
+ if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF))
+ errx(1, "readpassphrase");
+ if (strlen(pin) < 4 || strlen(pin) > 63) {
+ explicit_bzero(pin, sizeof(pin));
+ errx(1, "invalid PIN length");
+ }
+ r = fido_dev_make_cred(dev, cred, pin);
+ }
+
+ explicit_bzero(pin, sizeof(pin));
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_make_cred: %s", fido_strerr(r));
+ print_attcred(out_f, cred);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_cred_free(&cred);
+
+ fclose(in_f);
+ fclose(out_f);
+ in_f = NULL;
+ out_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/cred_verify.c b/tools/cred_verify.c
new file mode 100644
index 0000000..3eae435
--- /dev/null
+++ b/tools/cred_verify.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static fido_cred_t *
+prepare_cred(FILE *in_f, int type, int flags)
+{
+ fido_cred_t *cred = NULL;
+ struct blob cdh;
+ struct blob authdata;
+ struct blob id;
+ struct blob sig;
+ struct blob x5c;
+ char *rpid = NULL;
+ char *fmt = NULL;
+ int r;
+
+ memset(&cdh, 0, sizeof(cdh));
+ memset(&authdata, 0, sizeof(authdata));
+ memset(&id, 0, sizeof(id));
+ memset(&sig, 0, sizeof(sig));
+ memset(&x5c, 0, sizeof(x5c));
+
+ r = base64_read(in_f, &cdh);
+ r |= string_read(in_f, &rpid);
+ r |= string_read(in_f, &fmt);
+ r |= base64_read(in_f, &authdata);
+ r |= base64_read(in_f, &id);
+ r |= base64_read(in_f, &sig);
+ if (r < 0)
+ errx(1, "input error");
+
+ (void)base64_read(in_f, &x5c);
+
+ if (flags & FLAG_DEBUG) {
+ fprintf(stderr, "client data hash:\n");
+ xxd(cdh.ptr, cdh.len);
+ fprintf(stderr, "relying party id: %s\n", rpid);
+ fprintf(stderr, "format: %s\n", fmt);
+ fprintf(stderr, "authenticator data:\n");
+ xxd(authdata.ptr, authdata.len);
+ fprintf(stderr, "credential id:\n");
+ xxd(id.ptr, id.len);
+ fprintf(stderr, "signature:\n");
+ xxd(sig.ptr, sig.len);
+ fprintf(stderr, "x509:\n");
+ xxd(x5c.ptr, x5c.len);
+ }
+
+ if ((cred = fido_cred_new()) == NULL)
+ errx(1, "fido_cred_new");
+
+ if ((r = fido_cred_set_type(cred, type)) != FIDO_OK ||
+ (r = fido_cred_set_clientdata_hash(cred, cdh.ptr,
+ cdh.len)) != FIDO_OK ||
+ (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK ||
+ (r = fido_cred_set_authdata(cred, authdata.ptr,
+ authdata.len)) != FIDO_OK ||
+ (r = fido_cred_set_sig(cred, sig.ptr, sig.len)) != FIDO_OK ||
+ (r = fido_cred_set_fmt(cred, fmt)) != FIDO_OK)
+ errx(1, "fido_cred_set: %s", fido_strerr(r));
+
+ if (x5c.ptr != NULL) {
+ if ((r = fido_cred_set_x509(cred, x5c.ptr, x5c.len)) != FIDO_OK)
+ errx(1, "fido_cred_set_x509: %s", fido_strerr(r));
+ }
+
+ if (flags & FLAG_UV) {
+ if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK)
+ errx(1, "fido_cred_set_uv: %s", fido_strerr(r));
+ }
+ if (flags & FLAG_HMAC) {
+ if ((r = fido_cred_set_extensions(cred,
+ FIDO_EXT_HMAC_SECRET)) != FIDO_OK)
+ errx(1, "fido_cred_set_extensions: %s", fido_strerr(r));
+ }
+
+ free(cdh.ptr);
+ free(authdata.ptr);
+ free(id.ptr);
+ free(sig.ptr);
+ free(x5c.ptr);
+ free(rpid);
+ free(fmt);
+
+ return (cred);
+}
+
+int
+cred_verify(int argc, char **argv)
+{
+ fido_cred_t *cred = NULL;
+ char *in_path = NULL;
+ char *out_path = NULL;
+ FILE *in_f = NULL;
+ FILE *out_f = NULL;
+ int type = COSE_ES256;
+ int flags = 0;
+ int cred_prot = -1;
+ int ch;
+ int r;
+
+ while ((ch = getopt(argc, argv, "c:dhi:o:v")) != -1) {
+ switch (ch) {
+ case 'c':
+ if ((cred_prot = base10(optarg)) < 0)
+ errx(1, "-c: invalid argument '%s'", optarg);
+ break;
+ case 'd':
+ flags |= FLAG_DEBUG;
+ break;
+ case 'h':
+ flags |= FLAG_HMAC;
+ break;
+ case 'i':
+ in_path = optarg;
+ break;
+ case 'o':
+ out_path = optarg;
+ break;
+ case 'v':
+ flags |= FLAG_UV;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1)
+ usage();
+
+ in_f = open_read(in_path);
+ out_f = open_write(out_path);
+
+ if (argc > 0 && cose_type(argv[0], &type) < 0)
+ errx(1, "unknown type %s", argv[0]);
+
+ fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0);
+ cred = prepare_cred(in_f, type, flags);
+
+ if (cred_prot > 0) {
+ r = fido_cred_set_prot(cred, cred_prot);
+ if (r != FIDO_OK) {
+ errx(1, "fido_cred_set_prot: %s", fido_strerr(r));
+ }
+ }
+
+ if (fido_cred_x5c_ptr(cred) == NULL) {
+ if ((r = fido_cred_verify_self(cred)) != FIDO_OK)
+ errx(1, "fido_cred_verify_self: %s", fido_strerr(r));
+ } else {
+ if ((r = fido_cred_verify(cred)) != FIDO_OK)
+ errx(1, "fido_cred_verify: %s", fido_strerr(r));
+ }
+
+ print_cred(out_f, type, cred);
+ fido_cred_free(&cred);
+
+ fclose(in_f);
+ fclose(out_f);
+ in_f = NULL;
+ out_f = NULL;
+
+ exit(0);
+}
diff --git a/tools/credman.c b/tools/credman.c
new file mode 100644
index 0000000..a0a3149
--- /dev/null
+++ b/tools/credman.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2019 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <fido/credman.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+credman_get_metadata(fido_dev_t *dev, const char *path)
+{
+ fido_credman_metadata_t *metadata = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ if ((metadata = fido_credman_metadata_new()) == NULL) {
+ warnx("fido_credman_metadata_new");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_metadata(dev, metadata,
+ NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_metadata(dev, metadata, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_metadata: %s", fido_strerr(r));
+ goto out;
+ }
+
+ printf("existing rk(s): %u\n",
+ (unsigned)fido_credman_rk_existing(metadata));
+ printf("remaining rk(s): %u\n",
+ (unsigned)fido_credman_rk_remaining(metadata));
+
+ ok = 0;
+out:
+ fido_credman_metadata_free(&metadata);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static int
+print_rp(fido_credman_rp_t *rp, size_t idx)
+{
+ char *rp_id_hash = NULL;
+
+ if (base64_encode(fido_credman_rp_id_hash_ptr(rp, idx),
+ fido_credman_rp_id_hash_len(rp, idx), &rp_id_hash) < 0) {
+ warnx("output error");
+ return -1;
+ }
+ printf("%02u: %s %s\n", (unsigned)idx, rp_id_hash,
+ fido_credman_rp_id(rp, idx));
+ free(rp_id_hash);
+
+ return 0;
+}
+
+int
+credman_list_rp(const char *path)
+{
+ fido_credman_rp_t *rp = NULL;
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((rp = fido_credman_rp_new()) == NULL) {
+ warnx("fido_credman_rp_new");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rp(dev, rp, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rp(dev, rp, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_rp: %s", fido_strerr(r));
+ goto out;
+ }
+ for (size_t i = 0; i < fido_credman_rp_count(rp); i++)
+ if (print_rp(rp, i) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ fido_credman_rp_free(&rp);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static int
+print_rk(const fido_credman_rk_t *rk, size_t idx)
+{
+ const fido_cred_t *cred;
+ char *id = NULL;
+ char *user_id = NULL;
+ const char *type;
+ const char *prot;
+
+ if ((cred = fido_credman_rk(rk, idx)) == NULL) {
+ warnx("fido_credman_rk");
+ return -1;
+ }
+ if (base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred),
+ &id) < 0 || base64_encode(fido_cred_user_id_ptr(cred),
+ fido_cred_user_id_len(cred), &user_id) < 0) {
+ warnx("output error");
+ return -1;
+ }
+
+ type = cose_string(fido_cred_type(cred));
+ prot = prot_string(fido_cred_prot(cred));
+
+ printf("%02u: %s %s %s %s %s\n", (unsigned)idx, id,
+ fido_cred_display_name(cred), user_id, type, prot);
+
+ free(user_id);
+ free(id);
+
+ return 0;
+}
+
+int
+credman_list_rk(const char *path, const char *rp_id)
+{
+ fido_dev_t *dev = NULL;
+ fido_credman_rk_t *rk = NULL;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if ((rk = fido_credman_rk_new()) == NULL) {
+ warnx("fido_credman_rk_new");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rk(dev, rp_id, rk, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+ for (size_t i = 0; i < fido_credman_rk_count(rk); i++)
+ if (print_rk(rk, i) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ fido_credman_rk_free(&rk);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+credman_print_rk(fido_dev_t *dev, const char *path, const char *rp_id,
+ const char *cred_id)
+{
+ fido_credman_rk_t *rk = NULL;
+ const fido_cred_t *cred = NULL;
+ char *pin = NULL;
+ void *cred_id_ptr = NULL;
+ size_t cred_id_len = 0;
+ int r, ok = 1;
+
+ if ((rk = fido_credman_rk_new()) == NULL) {
+ warnx("fido_credman_rk_new");
+ goto out;
+ }
+ if (base64_decode(cred_id, &cred_id_ptr, &cred_id_len) < 0) {
+ warnx("base64_decode");
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rk(dev, rp_id, rk, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_get_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+
+ for (size_t i = 0; i < fido_credman_rk_count(rk); i++) {
+ if ((cred = fido_credman_rk(rk, i)) == NULL ||
+ fido_cred_id_ptr(cred) == NULL) {
+ warnx("output error");
+ goto out;
+ }
+ if (cred_id_len != fido_cred_id_len(cred) ||
+ memcmp(cred_id_ptr, fido_cred_id_ptr(cred), cred_id_len))
+ continue;
+ print_cred(stdout, fido_cred_type(cred), cred);
+ ok = 0;
+ goto out;
+ }
+
+ warnx("credential not found");
+out:
+ free(cred_id_ptr);
+ fido_credman_rk_free(&rk);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+credman_delete_rk(const char *path, const char *id)
+{
+ fido_dev_t *dev = NULL;
+ char *pin = NULL;
+ void *id_ptr = NULL;
+ size_t id_len = 0;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if (base64_decode(id, &id_ptr, &id_len) < 0) {
+ warnx("base64_decode");
+ goto out;
+ }
+ if ((r = fido_credman_del_dev_rk(dev, id_ptr, id_len,
+ NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_del_dev_rk(dev, id_ptr, id_len, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_del_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(id_ptr);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+credman_update_rk(const char *path, const char *user_id, const char *cred_id,
+ const char *name, const char *display_name)
+{
+ fido_dev_t *dev = NULL;
+ fido_cred_t *cred = NULL;
+ char *pin = NULL;
+ void *user_id_ptr = NULL;
+ void *cred_id_ptr = NULL;
+ size_t user_id_len = 0;
+ size_t cred_id_len = 0;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ if (base64_decode(user_id, &user_id_ptr, &user_id_len) < 0 ||
+ base64_decode(cred_id, &cred_id_ptr, &cred_id_len) < 0) {
+ warnx("base64_decode");
+ goto out;
+ }
+ if ((cred = fido_cred_new()) == NULL) {
+ warnx("fido_cred_new");
+ goto out;
+ }
+ if ((r = fido_cred_set_id(cred, cred_id_ptr, cred_id_len)) != FIDO_OK) {
+ warnx("fido_cred_set_id: %s", fido_strerr(r));
+ goto out;
+ }
+ if ((r = fido_cred_set_user(cred, user_id_ptr, user_id_len, name,
+ display_name, NULL)) != FIDO_OK) {
+ warnx("fido_cred_set_user: %s", fido_strerr(r));
+ goto out;
+ }
+ if ((r = fido_credman_set_dev_rk(dev, cred, NULL)) != FIDO_OK &&
+ should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_set_dev_rk(dev, cred, pin);
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_credman_set_dev_rk: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0;
+out:
+ free(user_id_ptr);
+ free(cred_id_ptr);
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+ fido_cred_free(&cred);
+
+ exit(ok);
+}
diff --git a/tools/extern.h b/tools/extern.h
new file mode 100644
index 0000000..b806ddd
--- /dev/null
+++ b/tools/extern.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _EXTERN_H_
+#define _EXTERN_H_
+
+#include <sys/types.h>
+
+#include <openssl/ec.h>
+
+#include <fido.h>
+#include <stddef.h>
+#include <stdio.h>
+
+struct blob {
+ unsigned char *ptr;
+ size_t len;
+};
+
+#define TOKEN_OPT "CDGILPRSVabcdefi:k:l:m:n:p:ru"
+
+#define FLAG_DEBUG 0x001
+#define FLAG_QUIET 0x002
+#define FLAG_RK 0x004
+#define FLAG_UV 0x008
+#define FLAG_U2F 0x010
+#define FLAG_HMAC 0x020
+#define FLAG_UP 0x040
+#define FLAG_LARGEBLOB 0x080
+#define FLAG_CD 0x100
+
+#define PINBUF_LEN 256
+
+EC_KEY *read_ec_pubkey(const char *);
+fido_dev_t *open_dev(const char *);
+FILE *open_read(const char *);
+FILE *open_write(const char *);
+char *get_pin(const char *);
+const char *plural(size_t);
+const char *cose_string(int);
+const char *prot_string(int);
+int assert_get(int, char **);
+int assert_verify(int, char **);
+int base64_decode(const char *, void **, size_t *);
+int base64_encode(const void *, size_t, char **);
+int base64_read(FILE *, struct blob *);
+int bio_delete(const char *, const char *);
+int bio_enroll(const char *);
+void bio_info(fido_dev_t *);
+int bio_list(const char *);
+int bio_set_name(const char *, const char *, const char *);
+int blob_clean(const char *);
+int blob_list(const char *);
+int blob_delete(const char *, const char *, const char *, const char *);
+int blob_get(const char *, const char *, const char *, const char *,
+ const char *);
+int blob_set(const char *, const char *, const char *, const char *,
+ const char *);
+int config_always_uv(char *, int);
+int config_entattest(char *);
+int config_force_pin_change(char *);
+int config_pin_minlen(char *, const char *);
+int config_pin_minlen_rpid(char *, const char *);
+int cose_type(const char *, int *);
+int cred_make(int, char **);
+int cred_verify(int, char **);
+int credman_delete_rk(const char *, const char *);
+int credman_update_rk(const char *, const char *, const char *, const char *,
+ const char *);
+int credman_get_metadata(fido_dev_t *, const char *);
+int credman_list_rk(const char *, const char *);
+int credman_list_rp(const char *);
+int credman_print_rk(fido_dev_t *, const char *, const char *, const char *);
+int get_devopt(fido_dev_t *, const char *, int *);
+int pin_change(char *);
+int pin_set(char *);
+int should_retry_with_pin(const fido_dev_t *, int);
+int string_read(FILE *, char **);
+int token_config(int, char **, char *);
+int token_delete(int, char **, char *);
+int token_get(int, char **, char *);
+int token_info(int, char **, char *);
+int token_list(int, char **, char *);
+int token_reset(char *);
+int token_set(int, char **, char *);
+int write_es256_pubkey(FILE *, const void *, size_t);
+int write_es384_pubkey(FILE *, const void *, size_t);
+int write_rsa_pubkey(FILE *, const void *, size_t);
+int read_file(const char *, u_char **, size_t *);
+int write_file(const char *, const u_char *, size_t);
+RSA *read_rsa_pubkey(const char *);
+EVP_PKEY *read_eddsa_pubkey(const char *);
+int write_eddsa_pubkey(FILE *, const void *, size_t);
+void print_cred(FILE *, int, const fido_cred_t *);
+void usage(void);
+void xxd(const void *, size_t);
+int base10(const char *);
+
+#endif /* _EXTERN_H_ */
diff --git a/tools/fido2-assert.c b/tools/fido2-assert.c
new file mode 100644
index 0000000..351ed4f
--- /dev/null
+++ b/tools/fido2-assert.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Example usage:
+ *
+ * $ echo assertion challenge | openssl sha256 -binary | base64 > assert_param
+ * $ echo relying party >> assert_param
+ * $ head -1 cred >> assert_param # credential id
+ * $ tail -n +2 cred > pubkey # credential pubkey
+ * $ fido2-assert -G -i assert_param /dev/hidraw5 | fido2-assert -V pubkey rs256
+ *
+ * See blurb in fido2-cred.c on how to obtain cred.
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"usage: fido2-assert -G [-bdhpruvw] [-t option] [-i input_file] [-o output_file] device\n"
+" fido2-assert -V [-dhpv] [-i input_file] key_file [type]\n"
+ );
+
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-')
+ usage();
+
+ switch (argv[1][1]) {
+ case 'G':
+ return (assert_get(--argc, ++argv));
+ case 'V':
+ return (assert_verify(--argc, ++argv));
+ }
+
+ usage();
+
+ /* NOTREACHED */
+}
diff --git a/tools/fido2-attach.sh b/tools/fido2-attach.sh
new file mode 100755
index 0000000..ef02db6
--- /dev/null
+++ b/tools/fido2-attach.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Copyright (c) 2020 Yubico AB. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+# SPDX-License-Identifier: BSD-2-Clause
+
+DEV=""
+
+while [ -z "${DEV}" ]; do
+ sleep .5
+ DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')"
+done
+
+printf '%s\n' "${DEV}"
diff --git a/tools/fido2-cred.c b/tools/fido2-cred.c
new file mode 100644
index 0000000..76081c6
--- /dev/null
+++ b/tools/fido2-cred.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2023 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Example usage:
+ *
+ * $ echo credential challenge | openssl sha256 -binary | base64 > cred_param
+ * $ echo relying party >> cred_param
+ * $ echo user name >> cred_param
+ * $ dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param
+ * $ fido2-cred -M -i cred_param /dev/hidraw5 | fido2-cred -V -o cred
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"usage: fido2-cred -M [-bdhqruvw] [-c cred_protect] [-i input_file] [-o output_file] device [type]\n"
+" fido2-cred -V [-dhv] [-c cred_protect] [-i input_file] [-o output_file] [type]\n"
+ );
+
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-')
+ usage();
+
+ switch (argv[1][1]) {
+ case 'M':
+ return (cred_make(--argc, ++argv));
+ case 'V':
+ return (cred_verify(--argc, ++argv));
+ }
+
+ usage();
+
+ /* NOTREACHED */
+}
diff --git a/tools/fido2-detach.sh b/tools/fido2-detach.sh
new file mode 100755
index 0000000..140278f
--- /dev/null
+++ b/tools/fido2-detach.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# Copyright (c) 2020 Yubico AB. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+# SPDX-License-Identifier: BSD-2-Clause
+
+DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')"
+
+while [ -n "${DEV}" ]; do
+ sleep .5
+ DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')"
+done
diff --git a/tools/fido2-token.c b/tools/fido2-token.c
new file mode 100644
index 0000000..412c2f9
--- /dev/null
+++ b/tools/fido2-token.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static int action;
+
+void
+usage(void)
+{
+ fprintf(stderr,
+"usage: fido2-token -C [-d] device\n"
+" fido2-token -Db [-k key_path] [-i cred_id -n rp_id] device\n"
+" fido2-token -Dei template_id device\n"
+" fido2-token -Du device\n"
+" fido2-token -Gb [-k key_path] [-i cred_id -n rp_id] blob_path device\n"
+" fido2-token -I [-cd] [-k rp_id -i cred_id] device\n"
+" fido2-token -L [-bder] [-k rp_id] [device]\n"
+" fido2-token -R [-d] device\n"
+" fido2-token -S [-adefu] [-l pin_length] [-i template_id -n template_name] device\n"
+" fido2-token -Sb [-k key_path] [-i cred_id -n rp_id] blob_path device\n"
+" fido2-token -Sc -i cred_id -k user_id -n name -p display_name device\n"
+" fido2-token -Sm rp_id device\n"
+" fido2-token -V\n"
+ );
+
+ exit(1);
+}
+
+static void
+setaction(int ch)
+{
+ if (action)
+ usage();
+ action = ch;
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch;
+ int flags = 0;
+ char *device;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'e':
+ case 'f':
+ case 'i':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'p':
+ case 'r':
+ case 'u':
+ break; /* ignore */
+ case 'd':
+ flags = FIDO_DEBUG;
+ break;
+ default:
+ setaction(ch);
+ break;
+ }
+ }
+
+ if (argc - optind < 1)
+ device = NULL;
+ else
+ device = argv[argc - 1];
+
+ fido_init(flags);
+
+ switch (action) {
+ case 'C':
+ return (pin_change(device));
+ case 'D':
+ return (token_delete(argc, argv, device));
+ case 'G':
+ return (token_get(argc, argv, device));
+ case 'I':
+ return (token_info(argc, argv, device));
+ case 'L':
+ return (token_list(argc, argv, device));
+ case 'R':
+ return (token_reset(device));
+ case 'S':
+ return (token_set(argc, argv, device));
+ case 'V':
+ fprintf(stderr, "%d.%d.%d\n", _FIDO_MAJOR, _FIDO_MINOR,
+ _FIDO_PATCH);
+ exit(0);
+ }
+
+ usage();
+
+ /* NOTREACHED */
+}
diff --git a/tools/fido2-unprot.sh b/tools/fido2-unprot.sh
new file mode 100755
index 0000000..7d8c779
--- /dev/null
+++ b/tools/fido2-unprot.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+# Copyright (c) 2020 Fabian Henneke.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+# SPDX-License-Identifier: BSD-2-Clause
+
+
+if [ $(uname) != "Linux" ] ; then
+ echo "Can only run on Linux"
+ exit 1
+fi
+
+TOKEN_VERSION=$(${FIDO_TOOLS_PREFIX}fido2-token -V 2>&1)
+if [ $? -ne 0 ] ; then
+ echo "Please install libfido2 1.5.0 or higher"
+ exit
+fi
+
+TOKEN_VERSION_MAJOR=$(echo "$TOKEN_VERSION" | cut -d. -f1)
+TOKEN_VERSION_MINOR=$(echo "$TOKEN_VERSION" | cut -d. -f2)
+if [ $TOKEN_VERSION_MAJOR -eq 0 -o $TOKEN_VERSION_MAJOR -eq 1 -a $TOKEN_VERSION_MINOR -lt 5 ] ; then
+ echo "Please install libfido2 1.5.0 or higher (current version: $TOKEN_VERSION)"
+ exit 1
+fi
+
+set -e
+
+TOKEN_OUTPUT=$(${FIDO_TOOLS_PREFIX}fido2-token -L)
+DEV_PATH_NAMES=$(echo "$TOKEN_OUTPUT" | sed -r 's/^(.*): .*\((.*)\)$/\1 \2/g')
+DEV_COUNT=$(echo "$DEV_PATH_NAMES" | wc -l)
+
+for i in $(seq 1 $DEV_COUNT)
+do
+ DEV_PATH_NAME=$(echo "$DEV_PATH_NAMES" | sed "${i}q;d")
+ DEV_PATH=$(echo "$DEV_PATH_NAME" | cut -d' ' -f1)
+ DEV_NAME=$(echo "$DEV_PATH_NAME" | cut -d' ' -f1 --complement)
+ DEV_PRETTY=$(echo "$DEV_NAME (at '$DEV_PATH')")
+ if expr match "$(${FIDO_TOOLS_PREFIX}fido2-token -I $DEV_PATH)" ".* credMgmt.* clientPin.*\|.* clientPin.* credMgmt.*" > /dev/null ; then
+ printf "Enter PIN for $DEV_PRETTY once (ignore further prompts): "
+ stty -echo
+ read PIN
+ stty echo
+ printf "\n"
+ RESIDENT_RPS=$(echo "${PIN}\n" | setsid -w ${FIDO_TOOLS_PREFIX}fido2-token -L -r $DEV_PATH | cut -d' ' -f3)
+ printf "\n"
+ RESIDENT_RPS_COUNT=$(echo "$RESIDENT_RPS" | wc -l)
+ FOUND=0
+ for j in $(seq 1 $DEV_RESIDENT_RPS_COUNT)
+ do
+ RESIDENT_RP=$(echo "$RESIDENT_RPS" | sed "${j}q;d")
+ UNPROT_CREDS=$(echo "${PIN}\n" | setsid -w ${FIDO_TOOLS_PREFIX}fido2-token -L -k $RESIDENT_RP $DEV_PATH | grep ' uvopt$' | cut -d' ' -f2,3,4)
+ printf "\n"
+ UNPROT_CREDS_COUNT=$(echo "$UNPROT_CREDS" | wc -l)
+ if [ $UNPROT_CREDS_COUNT -gt 0 ] ; then
+ FOUND=1
+ echo "Unprotected credentials on $DEV_PRETTY for '$RESIDENT_RP':"
+ echo "$UNPROT_CREDS"
+ fi
+ done
+ if [ $FOUND -eq 0 ] ; then
+ echo "No unprotected credentials on $DEV_PRETTY"
+ fi
+ else
+ echo "$DEV_PRETTY cannot enumerate credentials"
+ echo "Discovering unprotected SSH credentials only..."
+ STUB_HASH=$(echo -n "" | openssl sha256 -binary | base64)
+ printf "$STUB_HASH\nssh:\n" | ${FIDO_TOOLS_PREFIX}fido2-assert -G -r -t up=false $DEV_PATH 2> /dev/null || ASSERT_EXIT_CODE=$?
+ if [ $ASSERT_EXIT_CODE -eq 0 ] ; then
+ echo "Found an unprotected SSH credential on $DEV_PRETTY!"
+ else
+ echo "No unprotected SSH credentials (default settings) on $DEV_PRETTY"
+ fi
+ fi
+ printf "\n"
+done
diff --git a/tools/include_check.sh b/tools/include_check.sh
new file mode 100755
index 0000000..70abada
--- /dev/null
+++ b/tools/include_check.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# Copyright (c) 2019 Yubico AB. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+# SPDX-License-Identifier: BSD-2-Clause
+
+check() {
+ for f in $(find $1 -maxdepth 1 -name '*.h'); do
+ echo "#include \"$f\"" | \
+ cc $CFLAGS -Isrc -xc -c - -o /dev/null 2>&1
+ echo "$f $CFLAGS $?"
+ done
+}
+
+check examples
+check fuzz
+check openbsd-compat
+CFLAGS="${CFLAGS} -D_FIDO_INTERNAL" check src
+check src/fido.h
+check src/fido
+check tools
diff --git a/tools/largeblob.c b/tools/largeblob.c
new file mode 100644
index 0000000..78b97ab
--- /dev/null
+++ b/tools/largeblob.c
@@ -0,0 +1,618 @@
+/*
+ * Copyright (c) 2020-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <fido.h>
+#include <fido/credman.h>
+
+#include <cbor.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <zlib.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+#define BOUND (1024UL * 1024UL)
+
+struct rkmap {
+ fido_credman_rp_t *rp; /* known rps */
+ fido_credman_rk_t **rk; /* rk per rp */
+};
+
+static void
+free_rkmap(struct rkmap *map)
+{
+ if (map->rp != NULL) {
+ for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++)
+ fido_credman_rk_free(&map->rk[i]);
+ fido_credman_rp_free(&map->rp);
+ }
+ free(map->rk);
+}
+
+static int
+map_known_rps(fido_dev_t *dev, const char *path, struct rkmap *map)
+{
+ const char *rp_id;
+ char *pin = NULL;
+ size_t n;
+ int r, ok = -1;
+
+ if ((map->rp = fido_credman_rp_new()) == NULL) {
+ warnx("%s: fido_credman_rp_new", __func__);
+ goto out;
+ }
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ if ((r = fido_credman_get_dev_rp(dev, map->rp, pin)) != FIDO_OK) {
+ warnx("fido_credman_get_dev_rp: %s", fido_strerr(r));
+ goto out;
+ }
+ if ((n = fido_credman_rp_count(map->rp)) > UINT8_MAX) {
+ warnx("%s: fido_credman_rp_count > UINT8_MAX", __func__);
+ goto out;
+ }
+ if ((map->rk = calloc(n, sizeof(*map->rk))) == NULL) {
+ warnx("%s: calloc", __func__);
+ goto out;
+ }
+ for (size_t i = 0; i < n; i++) {
+ if ((rp_id = fido_credman_rp_id(map->rp, i)) == NULL) {
+ warnx("%s: fido_credman_rp_id %zu", __func__, i);
+ goto out;
+ }
+ if ((map->rk[i] = fido_credman_rk_new()) == NULL) {
+ warnx("%s: fido_credman_rk_new", __func__);
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, map->rk[i],
+ pin)) != FIDO_OK) {
+ warnx("%s: fido_credman_get_dev_rk %s: %s", __func__,
+ rp_id, fido_strerr(r));
+ goto out;
+ }
+ }
+
+ ok = 0;
+out:
+ freezero(pin, PINBUF_LEN);
+
+ return ok;
+}
+
+static int
+lookup_key(const char *path, fido_dev_t *dev, const char *rp_id,
+ const struct blob *cred_id, char **pin, struct blob *key)
+{
+ fido_credman_rk_t *rk = NULL;
+ const fido_cred_t *cred = NULL;
+ size_t i, n;
+ int r, ok = -1;
+
+ if ((rk = fido_credman_rk_new()) == NULL) {
+ warnx("%s: fido_credman_rk_new", __func__);
+ goto out;
+ }
+ if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin)) != FIDO_OK &&
+ *pin == NULL && should_retry_with_pin(dev, r)) {
+ if ((*pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin);
+ }
+ if (r != FIDO_OK) {
+ warnx("%s: fido_credman_get_dev_rk: %s", __func__,
+ fido_strerr(r));
+ goto out;
+ }
+ if ((n = fido_credman_rk_count(rk)) == 0) {
+ warnx("%s: rp id not found", __func__);
+ goto out;
+ }
+ if (n == 1 && cred_id->len == 0) {
+ /* use the credential we found */
+ cred = fido_credman_rk(rk, 0);
+ } else {
+ if (cred_id->len == 0) {
+ warnx("%s: multiple credentials found", __func__);
+ goto out;
+ }
+ for (i = 0; i < n; i++) {
+ const fido_cred_t *x = fido_credman_rk(rk, i);
+ if (fido_cred_id_len(x) <= cred_id->len &&
+ !memcmp(fido_cred_id_ptr(x), cred_id->ptr,
+ fido_cred_id_len(x))) {
+ cred = x;
+ break;
+ }
+ }
+ }
+ if (cred == NULL) {
+ warnx("%s: credential not found", __func__);
+ goto out;
+ }
+ if (fido_cred_largeblob_key_ptr(cred) == NULL) {
+ warnx("%s: no associated blob key", __func__);
+ goto out;
+ }
+ key->len = fido_cred_largeblob_key_len(cred);
+ if ((key->ptr = malloc(key->len)) == NULL) {
+ warnx("%s: malloc", __func__);
+ goto out;
+ }
+ memcpy(key->ptr, fido_cred_largeblob_key_ptr(cred), key->len);
+
+ ok = 0;
+out:
+ fido_credman_rk_free(&rk);
+
+ return ok;
+}
+
+static int
+load_key(const char *keyf, const char *cred_id64, const char *rp_id,
+ const char *path, fido_dev_t *dev, char **pin, struct blob *key)
+{
+ struct blob cred_id;
+ FILE *fp;
+ int r;
+
+ memset(&cred_id, 0, sizeof(cred_id));
+
+ if (keyf != NULL) {
+ if (rp_id != NULL || cred_id64 != NULL)
+ usage();
+ fp = open_read(keyf);
+ if ((r = base64_read(fp, key)) < 0)
+ warnx("%s: base64_read %s", __func__, keyf);
+ fclose(fp);
+ return r;
+ }
+ if (rp_id == NULL)
+ usage();
+ if (cred_id64 != NULL && base64_decode(cred_id64, (void *)&cred_id.ptr,
+ &cred_id.len) < 0) {
+ warnx("%s: base64_decode %s", __func__, cred_id64);
+ return -1;
+ }
+ r = lookup_key(path, dev, rp_id, &cred_id, pin, key);
+ free(cred_id.ptr);
+
+ return r;
+}
+
+int
+blob_set(const char *path, const char *keyf, const char *rp_id,
+ const char *cred_id64, const char *blobf)
+{
+ fido_dev_t *dev;
+ struct blob key, blob;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ memset(&key, 0, sizeof(key));
+ memset(&blob, 0, sizeof(blob));
+
+ if (read_file(blobf, &blob.ptr, &blob.len) < 0 ||
+ load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
+ goto out;
+ if ((r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
+ blob.len, pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr,
+ blob.len, pin);
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_largeblob_set: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0; /* success */
+out:
+ freezero(key.ptr, key.len);
+ freezero(blob.ptr, blob.len);
+ freezero(pin, PINBUF_LEN);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+blob_get(const char *path, const char *keyf, const char *rp_id,
+ const char *cred_id64, const char *blobf)
+{
+ fido_dev_t *dev;
+ struct blob key, blob;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ memset(&key, 0, sizeof(key));
+ memset(&blob, 0, sizeof(blob));
+
+ if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
+ goto out;
+ if ((r = fido_dev_largeblob_get(dev, key.ptr, key.len, &blob.ptr,
+ &blob.len)) != FIDO_OK) {
+ warnx("fido_dev_largeblob_get: %s", fido_strerr(r));
+ goto out;
+ }
+ if (write_file(blobf, blob.ptr, blob.len) < 0)
+ goto out;
+
+ ok = 0; /* success */
+out:
+ freezero(key.ptr, key.len);
+ freezero(blob.ptr, blob.len);
+ freezero(pin, PINBUF_LEN);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+int
+blob_delete(const char *path, const char *keyf, const char *rp_id,
+ const char *cred_id64)
+{
+ fido_dev_t *dev;
+ struct blob key;
+ char *pin = NULL;
+ int r, ok = 1;
+
+ dev = open_dev(path);
+ memset(&key, 0, sizeof(key));
+
+ if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0)
+ goto out;
+ if ((r = fido_dev_largeblob_remove(dev, key.ptr, key.len,
+ pin)) != FIDO_OK && should_retry_with_pin(dev, r)) {
+ if ((pin = get_pin(path)) == NULL)
+ goto out;
+ r = fido_dev_largeblob_remove(dev, key.ptr, key.len, pin);
+ }
+ if (r != FIDO_OK) {
+ warnx("fido_dev_largeblob_remove: %s", fido_strerr(r));
+ goto out;
+ }
+
+ ok = 0; /* success */
+out:
+ freezero(key.ptr, key.len);
+ freezero(pin, PINBUF_LEN);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
+
+static int
+try_decompress(const struct blob *in, uint64_t origsiz, int wbits)
+{
+ struct blob out;
+ z_stream zs;
+ u_int ilen, olen;
+ int ok = -1;
+
+ memset(&zs, 0, sizeof(zs));
+ memset(&out, 0, sizeof(out));
+
+ if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND)
+ return -1;
+ if (origsiz > SIZE_MAX || origsiz > UINT_MAX ||
+ (olen = (u_int)origsiz) > BOUND)
+ return -1;
+ if (inflateInit2(&zs, wbits) != Z_OK)
+ return -1;
+
+ if ((out.ptr = calloc(1, olen)) == NULL)
+ goto fail;
+
+ out.len = olen;
+ zs.next_in = in->ptr;
+ zs.avail_in = ilen;
+ zs.next_out = out.ptr;
+ zs.avail_out = olen;
+
+ if (inflate(&zs, Z_FINISH) != Z_STREAM_END)
+ goto fail;
+ if (zs.avail_out != 0)
+ goto fail;
+
+ ok = 0;
+fail:
+ if (inflateEnd(&zs) != Z_OK)
+ ok = -1;
+
+ freezero(out.ptr, out.len);
+
+ return ok;
+}
+
+static int
+decompress(const struct blob *plaintext, uint64_t origsiz)
+{
+ if (try_decompress(plaintext, origsiz, MAX_WBITS) == 0) /* rfc1950 */
+ return 0;
+ return try_decompress(plaintext, origsiz, -MAX_WBITS); /* rfc1951 */
+}
+
+static int
+decode(const struct blob *ciphertext, const struct blob *nonce,
+ uint64_t origsiz, const fido_cred_t *cred)
+{
+ uint8_t aad[4 + sizeof(uint64_t)];
+ EVP_CIPHER_CTX *ctx = NULL;
+ const EVP_CIPHER *cipher;
+ struct blob plaintext;
+ uint64_t tmp;
+ int ok = -1;
+
+ memset(&plaintext, 0, sizeof(plaintext));
+
+ if (nonce->len != 12)
+ return -1;
+ if (cred == NULL ||
+ fido_cred_largeblob_key_ptr(cred) == NULL ||
+ fido_cred_largeblob_key_len(cred) != 32)
+ return -1;
+ if (ciphertext->len > UINT_MAX ||
+ ciphertext->len > SIZE_MAX - 16 ||
+ ciphertext->len < 16)
+ return -1;
+ plaintext.len = ciphertext->len - 16;
+ if ((plaintext.ptr = calloc(1, plaintext.len)) == NULL)
+ return -1;
+ if ((ctx = EVP_CIPHER_CTX_new()) == NULL ||
+ (cipher = EVP_aes_256_gcm()) == NULL ||
+ EVP_CipherInit(ctx, cipher, fido_cred_largeblob_key_ptr(cred),
+ nonce->ptr, 0) == 0)
+ goto out;
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
+ ciphertext->ptr + ciphertext->len - 16) == 0)
+ goto out;
+ aad[0] = 0x62; /* b */
+ aad[1] = 0x6c; /* l */
+ aad[2] = 0x6f; /* o */
+ aad[3] = 0x62; /* b */
+ tmp = htole64(origsiz);
+ memcpy(&aad[4], &tmp, sizeof(uint64_t));
+ if (EVP_Cipher(ctx, NULL, aad, (u_int)sizeof(aad)) < 0 ||
+ EVP_Cipher(ctx, plaintext.ptr, ciphertext->ptr,
+ (u_int)plaintext.len) < 0 ||
+ EVP_Cipher(ctx, NULL, NULL, 0) < 0)
+ goto out;
+ if (decompress(&plaintext, origsiz) < 0)
+ goto out;
+
+ ok = 0;
+out:
+ freezero(plaintext.ptr, plaintext.len);
+
+ if (ctx != NULL)
+ EVP_CIPHER_CTX_free(ctx);
+
+ return ok;
+}
+
+static const fido_cred_t *
+try_rp(const fido_credman_rk_t *rk, const struct blob *ciphertext,
+ const struct blob *nonce, uint64_t origsiz)
+{
+ const fido_cred_t *cred;
+
+ for (size_t i = 0; i < fido_credman_rk_count(rk); i++)
+ if ((cred = fido_credman_rk(rk, i)) != NULL &&
+ decode(ciphertext, nonce, origsiz, cred) == 0)
+ return cred;
+
+ return NULL;
+}
+
+static int
+decode_cbor_blob(struct blob *out, const cbor_item_t *item)
+{
+ if (out->ptr != NULL ||
+ cbor_isa_bytestring(item) == false ||
+ cbor_bytestring_is_definite(item) == false)
+ return -1;
+ out->len = cbor_bytestring_length(item);
+ if ((out->ptr = malloc(out->len)) == NULL)
+ return -1;
+ memcpy(out->ptr, cbor_bytestring_handle(item), out->len);
+
+ return 0;
+}
+
+static int
+decode_blob_entry(const cbor_item_t *item, struct blob *ciphertext,
+ struct blob *nonce, uint64_t *origsiz)
+{
+ struct cbor_pair *v;
+
+ if (item == NULL)
+ return -1;
+ if (cbor_isa_map(item) == false ||
+ cbor_map_is_definite(item) == false ||
+ (v = cbor_map_handle(item)) == NULL)
+ return -1;
+ if (cbor_map_size(item) > UINT8_MAX)
+ return -1;
+
+ for (size_t i = 0; i < cbor_map_size(item); i++) {
+ if (cbor_isa_uint(v[i].key) == false ||
+ cbor_int_get_width(v[i].key) != CBOR_INT_8)
+ continue; /* ignore */
+ switch (cbor_get_uint8(v[i].key)) {
+ case 1: /* ciphertext */
+ if (decode_cbor_blob(ciphertext, v[i].value) < 0)
+ return -1;
+ break;
+ case 2: /* nonce */
+ if (decode_cbor_blob(nonce, v[i].value) < 0)
+ return -1;
+ break;
+ case 3: /* origSize */
+ if (*origsiz != 0 ||
+ cbor_isa_uint(v[i].value) == false ||
+ (*origsiz = cbor_get_int(v[i].value)) > SIZE_MAX)
+ return -1;
+ }
+ }
+ if (ciphertext->ptr == NULL || nonce->ptr == NULL || *origsiz == 0)
+ return -1;
+
+ return 0;
+}
+
+static void
+print_blob_entry(size_t idx, const cbor_item_t *item, const struct rkmap *map)
+{
+ struct blob ciphertext, nonce;
+ const fido_cred_t *cred = NULL;
+ const char *rp_id = NULL;
+ char *cred_id = NULL;
+ uint64_t origsiz = 0;
+
+ memset(&ciphertext, 0, sizeof(ciphertext));
+ memset(&nonce, 0, sizeof(nonce));
+
+ if (decode_blob_entry(item, &ciphertext, &nonce, &origsiz) < 0) {
+ printf("%02zu: <skipped: bad cbor>\n", idx);
+ goto out;
+ }
+ for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) {
+ if ((cred = try_rp(map->rk[i], &ciphertext, &nonce,
+ origsiz)) != NULL) {
+ rp_id = fido_credman_rp_id(map->rp, i);
+ break;
+ }
+ }
+ if (cred == NULL) {
+ if ((cred_id = strdup("<unknown>")) == NULL) {
+ printf("%02zu: <skipped: strdup failed>\n", idx);
+ goto out;
+ }
+ } else {
+ if (base64_encode(fido_cred_id_ptr(cred),
+ fido_cred_id_len(cred), &cred_id) < 0) {
+ printf("%02zu: <skipped: base64_encode failed>\n", idx);
+ goto out;
+ }
+ }
+ if (rp_id == NULL)
+ rp_id = "<unknown>";
+
+ printf("%02zu: %4zu %4zu %s %s\n", idx, ciphertext.len,
+ (size_t)origsiz, cred_id, rp_id);
+out:
+ free(ciphertext.ptr);
+ free(nonce.ptr);
+ free(cred_id);
+}
+
+static cbor_item_t *
+get_cbor_array(fido_dev_t *dev)
+{
+ struct cbor_load_result cbor_result;
+ cbor_item_t *item = NULL;
+ u_char *cbor_ptr = NULL;
+ size_t cbor_len;
+ int r, ok = -1;
+
+ if ((r = fido_dev_largeblob_get_array(dev, &cbor_ptr,
+ &cbor_len)) != FIDO_OK) {
+ warnx("%s: fido_dev_largeblob_get_array: %s", __func__,
+ fido_strerr(r));
+ goto out;
+ }
+ if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) {
+ warnx("%s: cbor_load", __func__);
+ goto out;
+ }
+ if (cbor_result.read != cbor_len) {
+ warnx("%s: cbor_result.read (%zu) != cbor_len (%zu)", __func__,
+ cbor_result.read, cbor_len);
+ /* continue */
+ }
+ if (cbor_isa_array(item) == false ||
+ cbor_array_is_definite(item) == false) {
+ warnx("%s: cbor type", __func__);
+ goto out;
+ }
+ if (cbor_array_size(item) > UINT8_MAX) {
+ warnx("%s: cbor_array_size > UINT8_MAX", __func__);
+ goto out;
+ }
+ if (cbor_array_size(item) == 0) {
+ ok = 0; /* nothing to do */
+ goto out;
+ }
+
+ printf("total map size: %zu byte%s\n", cbor_len, plural(cbor_len));
+
+ ok = 0;
+out:
+ if (ok < 0 && item != NULL) {
+ cbor_decref(&item);
+ item = NULL;
+ }
+ free(cbor_ptr);
+
+ return item;
+}
+
+int
+blob_list(const char *path)
+{
+ struct rkmap map;
+ fido_dev_t *dev = NULL;
+ cbor_item_t *item = NULL, **v;
+ int ok = 1;
+
+ memset(&map, 0, sizeof(map));
+ dev = open_dev(path);
+ if (map_known_rps(dev, path, &map) < 0 ||
+ (item = get_cbor_array(dev)) == NULL)
+ goto out;
+ if (cbor_array_size(item) == 0) {
+ ok = 0; /* nothing to do */
+ goto out;
+ }
+ if ((v = cbor_array_handle(item)) == NULL) {
+ warnx("%s: cbor_array_handle", __func__);
+ goto out;
+ }
+ for (size_t i = 0; i < cbor_array_size(item); i++)
+ print_blob_entry(i, v[i], &map);
+
+ ok = 0; /* success */
+out:
+ free_rkmap(&map);
+
+ if (item != NULL)
+ cbor_decref(&item);
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(ok);
+}
diff --git a/tools/pin.c b/tools/pin.c
new file mode 100644
index 0000000..8b2697e
--- /dev/null
+++ b/tools/pin.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2018 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+int
+pin_set(char *path)
+{
+ fido_dev_t *dev = NULL;
+ char prompt[1024];
+ char pin1[128];
+ char pin2[128];
+ int r;
+ int status = 1;
+
+ dev = open_dev(path);
+
+ r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path);
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: ");
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ if (strcmp(pin1, pin2) != 0) {
+ fprintf(stderr, "PINs do not match. Try again.\n");
+ goto out;
+ }
+
+ if (strlen(pin1) < 4 || strlen(pin1) > 63) {
+ fprintf(stderr, "invalid PIN length\n");
+ goto out;
+ }
+
+ if ((r = fido_dev_set_pin(dev, pin1, NULL)) != FIDO_OK) {
+ warnx("fido_dev_set_pin: %s", fido_strerr(r));
+ goto out;
+ }
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ status = 0;
+out:
+ explicit_bzero(pin1, sizeof(pin1));
+ explicit_bzero(pin2, sizeof(pin2));
+
+ exit(status);
+}
+
+int
+pin_change(char *path)
+{
+ fido_dev_t *dev = NULL;
+ char prompt[1024];
+ char pin0[128];
+ char pin1[128];
+ char pin2[128];
+ int r;
+ int status = 1;
+
+ if (path == NULL)
+ usage();
+
+ dev = open_dev(path);
+
+ r = snprintf(prompt, sizeof(prompt), "Enter current PIN for %s: ", path);
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin0, sizeof(pin0), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ if (strlen(pin0) < 4 || strlen(pin0) > 63) {
+ warnx("invalid PIN length");
+ goto out;
+ }
+
+ r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path);
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: ");
+ if (r < 0 || (size_t)r >= sizeof(prompt)) {
+ warnx("snprintf");
+ goto out;
+ }
+
+ if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) {
+ warnx("readpassphrase");
+ goto out;
+ }
+
+ if (strcmp(pin1, pin2) != 0) {
+ fprintf(stderr, "PINs do not match. Try again.\n");
+ goto out;
+ }
+
+ if (strlen(pin1) < 4 || strlen(pin1) > 63) {
+ fprintf(stderr, "invalid PIN length\n");
+ goto out;
+ }
+
+ if ((r = fido_dev_set_pin(dev, pin1, pin0)) != FIDO_OK) {
+ warnx("fido_dev_set_pin: %s", fido_strerr(r));
+ goto out;
+ }
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ status = 0;
+out:
+ explicit_bzero(pin0, sizeof(pin0));
+ explicit_bzero(pin1, sizeof(pin1));
+ explicit_bzero(pin2, sizeof(pin2));
+
+ exit(status);
+}
diff --git a/tools/test.sh b/tools/test.sh
new file mode 100755
index 0000000..67b757e
--- /dev/null
+++ b/tools/test.sh
@@ -0,0 +1,304 @@
+#!/bin/sh -ex
+
+# Copyright (c) 2021-2022 Yubico AB. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+# SPDX-License-Identifier: BSD-2-Clause
+
+# usage: ./test.sh "$(mktemp -d fido2test-XXXXXXXX)" device
+
+# Please note that this test script:
+# - is incomplete;
+# - assumes CTAP 2.1-like hmac-secret;
+# - should pass as-is on a YubiKey with a PIN set;
+# - may otherwise require set +e above;
+# - can be executed with UV=1 to run additional UV tests;
+# - was last tested on 2022-01-11 with firmware 5.4.3.
+
+cd "$1"
+DEV="$2"
+TYPE="es256"
+#TYPE="es384"
+#TYPE="eddsa"
+
+make_cred() {
+ sed /^$/d > cred_param << EOF
+$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)
+$1
+some user name
+$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)
+EOF
+ fido2-cred -M $2 "${DEV}" "${TYPE}" > "$3" < cred_param
+}
+
+verify_cred() {
+ fido2-cred -V $1 "${TYPE}" > cred_out < "$2"
+ head -1 cred_out > "$3"
+ tail -n +2 cred_out > "$4"
+}
+
+get_assert() {
+ sed /^$/d > assert_param << EOF
+$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64)
+$1
+$(cat $3)
+$(cat $4)
+EOF
+ fido2-assert -G $2 "${DEV}" > "$5" < assert_param
+}
+
+verify_assert() {
+ fido2-assert -V $1 "$2" "${TYPE}" < "$3"
+}
+
+dd if=/dev/urandom bs=32 count=1 | base64 > hmac-salt
+
+# u2f
+if [ "x${TYPE}" = "xes256" ]; then
+ make_cred no.tld "-u" u2f
+ ! make_cred no.tld "-ru" /dev/null
+ ! make_cred no.tld "-uc1" /dev/null
+ ! make_cred no.tld "-uc2" /dev/null
+ verify_cred "--" u2f u2f-cred u2f-pubkey
+ ! verify_cred "-h" u2f /dev/null /dev/null
+ ! verify_cred "-v" u2f /dev/null /dev/null
+ verify_cred "-c0" u2f /dev/null /dev/null
+ ! verify_cred "-c1" u2f /dev/null /dev/null
+ ! verify_cred "-c2" u2f /dev/null /dev/null
+ ! verify_cred "-c3" u2f /dev/null /dev/null
+fi
+
+# wrap (non-resident)
+make_cred no.tld "--" wrap
+verify_cred "--" wrap wrap-cred wrap-pubkey
+! verify_cred "-h" wrap /dev/null /dev/null
+! verify_cred "-v" wrap /dev/null /dev/null
+verify_cred "-c0" wrap /dev/null /dev/null
+! verify_cred "-c1" wrap /dev/null /dev/null
+! verify_cred "-c2" wrap /dev/null /dev/null
+! verify_cred "-c3" wrap /dev/null /dev/null
+
+# wrap (non-resident) + hmac-secret
+make_cred no.tld "-h" wrap-hs
+! verify_cred "--" wrap-hs /dev/null /dev/null
+verify_cred "-h" wrap-hs wrap-hs-cred wrap-hs-pubkey
+! verify_cred "-v" wrap-hs /dev/null /dev/null
+verify_cred "-hc0" wrap-hs /dev/null /dev/null
+! verify_cred "-c0" wrap-hs /dev/null /dev/null
+! verify_cred "-c1" wrap-hs /dev/null /dev/null
+! verify_cred "-c2" wrap-hs /dev/null /dev/null
+! verify_cred "-c3" wrap-hs /dev/null /dev/null
+
+# resident
+make_cred no.tld "-r" rk
+verify_cred "--" rk rk-cred rk-pubkey
+! verify_cred "-h" rk /dev/null /dev/null
+! verify_cred "-v" rk /dev/null /dev/null
+verify_cred "-c0" rk /dev/null /dev/null
+! verify_cred "-c1" rk /dev/null /dev/null
+! verify_cred "-c2" rk /dev/null /dev/null
+! verify_cred "-c3" rk /dev/null /dev/null
+
+# resident + hmac-secret
+make_cred no.tld "-hr" rk-hs
+! verify_cred "--" rk-hs rk-hs-cred rk-hs-pubkey
+verify_cred "-h" rk-hs /dev/null /dev/null
+! verify_cred "-v" rk-hs /dev/null /dev/null
+verify_cred "-hc0" rk-hs /dev/null /dev/null
+! verify_cred "-c0" rk-hs /dev/null /dev/null
+! verify_cred "-c1" rk-hs /dev/null /dev/null
+! verify_cred "-c2" rk-hs /dev/null /dev/null
+! verify_cred "-c3" rk-hs /dev/null /dev/null
+
+# u2f
+if [ "x${TYPE}" = "xes256" ]; then
+ get_assert no.tld "-u" u2f-cred /dev/null u2f-assert
+ ! get_assert no.tld "-u -t up=false" u2f-cred /dev/null /dev/null
+ verify_assert "--" u2f-pubkey u2f-assert
+ verify_assert "-p" u2f-pubkey u2f-assert
+fi
+
+# wrap (non-resident)
+get_assert no.tld "--" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+get_assert no.tld "-t pin=true" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-v" wrap-pubkey wrap-assert
+get_assert no.tld "-t pin=false" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=true" wrap-cred /dev/null wrap-assert
+verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=true -t pin=true" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-p" wrap-pubkey wrap-assert
+verify_assert "-v" wrap-pubkey wrap-assert
+verify_assert "-pv" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=true -t pin=false" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=false" wrap-cred /dev/null wrap-assert
+verify_assert "--" wrap-pubkey wrap-assert
+! verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=false -t pin=true" wrap-cred /dev/null wrap-assert
+! verify_assert "-p" wrap-pubkey wrap-assert
+verify_assert "-v" wrap-pubkey wrap-assert
+! verify_assert "-pv" wrap-pubkey wrap-assert
+get_assert no.tld "-t up=false -t pin=false" wrap-cred /dev/null wrap-assert
+! verify_assert "-p" wrap-pubkey wrap-assert
+get_assert no.tld "-h" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t pin=true" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hv" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t pin=false" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t up=true" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hp" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t up=true -t pin=true" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hp" wrap-pubkey wrap-assert
+verify_assert "-hv" wrap-pubkey wrap-assert
+verify_assert "-hpv" wrap-pubkey wrap-assert
+get_assert no.tld "-h -t up=true -t pin=false" wrap-cred hmac-salt wrap-assert
+! verify_assert "--" wrap-pubkey wrap-assert
+verify_assert "-h" wrap-pubkey wrap-assert
+verify_assert "-hp" wrap-pubkey wrap-assert
+! get_assert no.tld "-h -t up=false" wrap-cred hmac-salt wrap-assert
+! get_assert no.tld "-h -t up=false -t pin=true" wrap-cred hmac-salt wrap-assert
+! get_assert no.tld "-h -t up=false -t pin=false" wrap-cred hmac-salt wrap-assert
+
+if [ "x${UV}" != "x" ]; then
+ get_assert no.tld "-t uv=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=true -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=true -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=false" wrap-cred /dev/null wrap-assert
+ verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=false -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t uv=false -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=true -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=true -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-p" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=false -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-pv" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=true -t uv=false -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-p" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=true -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=true -t pin=false" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=false" wrap-cred /dev/null wrap-assert
+ ! verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=false -t pin=true" wrap-cred /dev/null wrap-assert
+ verify_assert "-v" wrap-pubkey wrap-assert
+ get_assert no.tld "-t up=false -t uv=false -t pin=false" wrap-cred /dev/null wrap-assert
+ ! verify_assert "--" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-h" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-h" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hp" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hpv" wrap-pubkey wrap-assert
+ get_assert no.tld "-h -t up=true -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert
+ verify_assert "-hp" wrap-pubkey wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=true" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=false" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert
+ ! get_assert no.tld "-h -t up=false -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert
+fi
+
+# resident
+get_assert no.tld "-r" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t pin=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t pin=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=true -t pin=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=true -t pin=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=false -t pin=true" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -t up=false -t pin=false" /dev/null /dev/null wrap-assert
+get_assert no.tld "-r -h" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t pin=true" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t pin=false" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t up=true" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t up=true -t pin=true" /dev/null hmac-salt wrap-assert
+get_assert no.tld "-r -h -t up=true -t pin=false" /dev/null hmac-salt wrap-assert
+! get_assert no.tld "-r -h -t up=false" /dev/null hmac-salt wrap-assert
+! get_assert no.tld "-r -h -t up=false -t pin=true" /dev/null hmac-salt wrap-assert
+! get_assert no.tld "-r -h -t up=false -t pin=false" /dev/null hmac-salt wrap-assert
+
+if [ "x${UV}" != "x" ]; then
+ get_assert no.tld "-r -t uv=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=true -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=true -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=false -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t uv=false -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=true -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=true -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=false -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=true -t uv=false -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=true -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=true -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=false -t pin=true" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -t up=false -t uv=false -t pin=false" /dev/null /dev/null wrap-assert
+ get_assert no.tld "-r -h -t uv=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=false" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert
+ get_assert no.tld "-r -h -t up=true -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=true" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=false" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert
+ ! get_assert no.tld "-r -h -t up=false -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert
+fi
+
+exit 0
diff --git a/tools/token.c b/tools/token.c
new file mode 100644
index 0000000..366d5a1
--- /dev/null
+++ b/tools/token.c
@@ -0,0 +1,729 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fido.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "../openbsd-compat/openbsd-compat.h"
+#include "extern.h"
+
+static void
+format_flags(char *ret, size_t retlen, uint8_t flags)
+{
+ memset(ret, 0, retlen);
+
+ if (flags & FIDO_CAP_WINK) {
+ if (strlcat(ret, "wink,", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, "nowink,", retlen) >= retlen)
+ goto toolong;
+ }
+
+ if (flags & FIDO_CAP_CBOR) {
+ if (strlcat(ret, " cbor,", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, " nocbor,", retlen) >= retlen)
+ goto toolong;
+ }
+
+ if (flags & FIDO_CAP_NMSG) {
+ if (strlcat(ret, " nomsg", retlen) >= retlen)
+ goto toolong;
+ } else {
+ if (strlcat(ret, " msg", retlen) >= retlen)
+ goto toolong;
+ }
+
+ return;
+toolong:
+ strlcpy(ret, "toolong", retlen);
+}
+
+static void
+print_attr(const fido_dev_t *dev)
+{
+ char flags_txt[128];
+
+ printf("proto: 0x%02x\n", fido_dev_protocol(dev));
+ printf("major: 0x%02x\n", fido_dev_major(dev));
+ printf("minor: 0x%02x\n", fido_dev_minor(dev));
+ printf("build: 0x%02x\n", fido_dev_build(dev));
+
+ format_flags(flags_txt, sizeof(flags_txt), fido_dev_flags(dev));
+ printf("caps: 0x%02x (%s)\n", fido_dev_flags(dev), flags_txt);
+}
+
+static void
+print_str_array(const char *label, char * const *sa, size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s strings: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s", i > 0 ? ", " : "", sa[i]);
+
+ printf("\n");
+}
+
+static void
+print_opt_array(const char *label, char * const *name, const bool *value,
+ size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s%s", i > 0 ? ", " : "",
+ value[i] ? "" : "no", name[i]);
+
+ printf("\n");
+}
+
+static void
+print_cert_array(const char *label, char * const *name, const uint64_t *value,
+ size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%s %llu", i > 0 ? ", " : "", name[i],
+ (unsigned long long)value[i]);
+
+ printf("\n");
+}
+
+static void
+print_algorithms(const fido_cbor_info_t *ci)
+{
+ const char *cose, *type;
+ size_t len;
+
+ if ((len = fido_cbor_info_algorithm_count(ci)) == 0)
+ return;
+
+ printf("algorithms: ");
+
+ for (size_t i = 0; i < len; i++) {
+ cose = type = "unknown";
+ switch (fido_cbor_info_algorithm_cose(ci, i)) {
+ case COSE_ES256:
+ cose = "es256";
+ break;
+ case COSE_ES384:
+ cose = "es384";
+ break;
+ case COSE_RS256:
+ cose = "rs256";
+ break;
+ case COSE_EDDSA:
+ cose = "eddsa";
+ break;
+ }
+ if (fido_cbor_info_algorithm_type(ci, i) != NULL)
+ type = fido_cbor_info_algorithm_type(ci, i);
+ printf("%s%s (%s)", i > 0 ? ", " : "", cose, type);
+ }
+
+ printf("\n");
+}
+
+static void
+print_aaguid(const unsigned char *buf, size_t buflen)
+{
+ printf("aaguid: ");
+
+ while (buflen--)
+ printf("%02x", *buf++);
+
+ printf("\n");
+}
+
+static void
+print_maxmsgsiz(uint64_t maxmsgsiz)
+{
+ printf("maxmsgsiz: %d\n", (int)maxmsgsiz);
+}
+
+static void
+print_maxcredcntlst(uint64_t maxcredcntlst)
+{
+ printf("maxcredcntlst: %d\n", (int)maxcredcntlst);
+}
+
+static void
+print_maxcredidlen(uint64_t maxcredidlen)
+{
+ printf("maxcredlen: %d\n", (int)maxcredidlen);
+}
+
+static void
+print_maxlargeblob(uint64_t maxlargeblob)
+{
+ printf("maxlargeblob: %d\n", (int)maxlargeblob);
+}
+
+static void
+print_maxrpid_minpinlen(uint64_t maxrpid)
+{
+ if (maxrpid > 0)
+ printf("maxrpids in minpinlen: %d\n", (int)maxrpid);
+}
+
+static void
+print_minpinlen(uint64_t minpinlen)
+{
+ if (minpinlen > 0)
+ printf("minpinlen: %d\n", (int)minpinlen);
+}
+
+static void
+print_uv_attempts(uint64_t uv_attempts)
+{
+ if (uv_attempts > 0)
+ printf("platform uv attempt(s): %d\n", (int)uv_attempts);
+}
+
+static void
+print_uv_modality(uint64_t uv_modality)
+{
+ uint64_t mode;
+ bool printed = false;
+
+ if (uv_modality == 0)
+ return;
+
+ printf("uv modality: 0x%x (", (int)uv_modality);
+
+ for (size_t i = 0; i < 64; i++) {
+ mode = 1ULL << i;
+ if ((uv_modality & mode) == 0)
+ continue;
+ if (printed)
+ printf(", ");
+ switch (mode) {
+ case FIDO_UV_MODE_TUP:
+ printf("test of user presence");
+ break;
+ case FIDO_UV_MODE_FP:
+ printf("fingerprint check");
+ break;
+ case FIDO_UV_MODE_PIN:
+ printf("pin check");
+ break;
+ case FIDO_UV_MODE_VOICE:
+ printf("voice recognition");
+ break;
+ case FIDO_UV_MODE_FACE:
+ printf("face recognition");
+ break;
+ case FIDO_UV_MODE_LOCATION:
+ printf("location check");
+ break;
+ case FIDO_UV_MODE_EYE:
+ printf("eyeprint check");
+ break;
+ case FIDO_UV_MODE_DRAWN:
+ printf("drawn pattern check");
+ break;
+ case FIDO_UV_MODE_HAND:
+ printf("handprint verification");
+ break;
+ case FIDO_UV_MODE_NONE:
+ printf("none");
+ break;
+ case FIDO_UV_MODE_ALL:
+ printf("all required");
+ break;
+ case FIDO_UV_MODE_EXT_PIN:
+ printf("external pin");
+ break;
+ case FIDO_UV_MODE_EXT_DRAWN:
+ printf("external drawn pattern check");
+ break;
+ default:
+ printf("unknown 0x%llx", (unsigned long long)mode);
+ break;
+ }
+ printed = true;
+ }
+
+ printf(")\n");
+}
+
+static void
+print_rk_remaining(int64_t rk_remaining)
+{
+ if (rk_remaining != -1)
+ printf("remaining rk(s): %d\n", (int)rk_remaining);
+}
+
+static void
+print_fwversion(uint64_t fwversion)
+{
+ printf("fwversion: 0x%x\n", (int)fwversion);
+}
+
+static void
+print_byte_array(const char *label, const uint8_t *ba, size_t len)
+{
+ if (len == 0)
+ return;
+
+ printf("%s: ", label);
+
+ for (size_t i = 0; i < len; i++)
+ printf("%s%u", i > 0 ? ", " : "", (unsigned)ba[i]);
+
+ printf("\n");
+}
+
+int
+token_info(int argc, char **argv, char *path)
+{
+ char *cred_id = NULL;
+ char *rp_id = NULL;
+ fido_cbor_info_t *ci = NULL;
+ fido_dev_t *dev = NULL;
+ int ch;
+ int credman = 0;
+ int r;
+ int retrycnt;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'c':
+ credman = 1;
+ break;
+ case 'i':
+ cred_id = optarg;
+ break;
+ case 'k':
+ rp_id = optarg;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ if (path == NULL || (credman && (cred_id != NULL || rp_id != NULL)))
+ usage();
+
+ dev = open_dev(path);
+
+ if (credman)
+ return (credman_get_metadata(dev, path));
+ if (cred_id && rp_id)
+ return (credman_print_rk(dev, path, rp_id, cred_id));
+ if (cred_id || rp_id)
+ usage();
+
+ print_attr(dev);
+
+ if (fido_dev_is_fido2(dev) == false)
+ goto end;
+ if ((ci = fido_cbor_info_new()) == NULL)
+ errx(1, "fido_cbor_info_new");
+ if ((r = fido_dev_get_cbor_info(dev, ci)) != FIDO_OK)
+ errx(1, "fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r);
+
+ /* print supported protocol versions */
+ print_str_array("version", fido_cbor_info_versions_ptr(ci),
+ fido_cbor_info_versions_len(ci));
+
+ /* print supported extensions */
+ print_str_array("extension", fido_cbor_info_extensions_ptr(ci),
+ fido_cbor_info_extensions_len(ci));
+
+ /* print supported transports */
+ print_str_array("transport", fido_cbor_info_transports_ptr(ci),
+ fido_cbor_info_transports_len(ci));
+
+ /* print supported algorithms */
+ print_algorithms(ci);
+
+ /* print aaguid */
+ print_aaguid(fido_cbor_info_aaguid_ptr(ci),
+ fido_cbor_info_aaguid_len(ci));
+
+ /* print supported options */
+ print_opt_array("options", fido_cbor_info_options_name_ptr(ci),
+ fido_cbor_info_options_value_ptr(ci),
+ fido_cbor_info_options_len(ci));
+
+ /* print certifications */
+ print_cert_array("certifications", fido_cbor_info_certs_name_ptr(ci),
+ fido_cbor_info_certs_value_ptr(ci),
+ fido_cbor_info_certs_len(ci));
+
+ /* print firmware version */
+ print_fwversion(fido_cbor_info_fwversion(ci));
+
+ /* print maximum message size */
+ print_maxmsgsiz(fido_cbor_info_maxmsgsiz(ci));
+
+ /* print maximum number of credentials allowed in credential lists */
+ print_maxcredcntlst(fido_cbor_info_maxcredcntlst(ci));
+
+ /* print maximum length of a credential ID */
+ print_maxcredidlen(fido_cbor_info_maxcredidlen(ci));
+
+ /* print maximum length of serialized largeBlob array */
+ print_maxlargeblob(fido_cbor_info_maxlargeblob(ci));
+
+ /* print maximum number of RP IDs in fido_dev_set_pin_minlen_rpid() */
+ print_maxrpid_minpinlen(fido_cbor_info_maxrpid_minpinlen(ci));
+
+ /* print estimated number of resident credentials */
+ print_rk_remaining(fido_cbor_info_rk_remaining(ci));
+
+ /* print minimum pin length */
+ print_minpinlen(fido_cbor_info_minpinlen(ci));
+
+ /* print supported pin protocols */
+ print_byte_array("pin protocols", fido_cbor_info_protocols_ptr(ci),
+ fido_cbor_info_protocols_len(ci));
+
+ if (fido_dev_get_retry_count(dev, &retrycnt) != FIDO_OK)
+ printf("pin retries: undefined\n");
+ else
+ printf("pin retries: %d\n", retrycnt);
+
+ printf("pin change required: %s\n",
+ fido_cbor_info_new_pin_required(ci) ? "true" : "false");
+
+ if (fido_dev_get_uv_retry_count(dev, &retrycnt) != FIDO_OK)
+ printf("uv retries: undefined\n");
+ else
+ printf("uv retries: %d\n", retrycnt);
+
+ /* print platform uv attempts */
+ print_uv_attempts(fido_cbor_info_uv_attempts(ci));
+
+ /* print supported uv mechanisms */
+ print_uv_modality(fido_cbor_info_uv_modality(ci));
+
+ bio_info(dev);
+
+ fido_cbor_info_free(&ci);
+end:
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(0);
+}
+
+int
+token_reset(char *path)
+{
+ fido_dev_t *dev = NULL;
+ int r;
+
+ if (path == NULL)
+ usage();
+
+ dev = open_dev(path);
+ if ((r = fido_dev_reset(dev)) != FIDO_OK)
+ errx(1, "fido_dev_reset: %s", fido_strerr(r));
+
+ fido_dev_close(dev);
+ fido_dev_free(&dev);
+
+ exit(0);
+}
+
+int
+token_get(int argc, char **argv, char *path)
+{
+ char *id = NULL;
+ char *key = NULL;
+ char *name = NULL;
+ int blob = 0;
+ int ch;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'b':
+ blob = 1;
+ break;
+ case 'i':
+ id = optarg;
+ break;
+ case 'k':
+ key = optarg;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (blob == 0 || argc != 2)
+ usage();
+
+ return blob_get(path, key, name, id, argv[0]);
+}
+
+int
+token_set(int argc, char **argv, char *path)
+{
+ char *id = NULL;
+ char *key = NULL;
+ char *len = NULL;
+ char *display_name = NULL;
+ char *name = NULL;
+ char *rpid = NULL;
+ int blob = 0;
+ int cred = 0;
+ int ch;
+ int enroll = 0;
+ int ea = 0;
+ int uv = 0;
+ bool force = false;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'a':
+ ea = 1;
+ break;
+ case 'b':
+ blob = 1;
+ break;
+ case 'c':
+ cred = 1;
+ break;
+ case 'e':
+ enroll = 1;
+ break;
+ case 'f':
+ force = true;
+ break;
+ case 'i':
+ id = optarg;
+ break;
+ case 'k':
+ key = optarg;
+ break;
+ case 'l':
+ len = optarg;
+ break;
+ case 'p':
+ display_name = optarg;
+ break;
+ case 'm':
+ rpid = optarg;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ uv = 1;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (path == NULL)
+ usage();
+
+ if (blob) {
+ if (argc != 2)
+ usage();
+ return (blob_set(path, key, name, id, argv[0]));
+ }
+
+ if (cred) {
+ if (!id || !key)
+ usage();
+ if (!name && !display_name)
+ usage();
+ return (credman_update_rk(path, key, id, name, display_name));
+ }
+
+ if (enroll) {
+ if (ea || uv)
+ usage();
+ if (id && name)
+ return (bio_set_name(path, id, name));
+ if (!id && !name)
+ return (bio_enroll(path));
+ usage();
+ }
+
+ if (ea) {
+ if (uv)
+ usage();
+ return (config_entattest(path));
+ }
+
+ if (len)
+ return (config_pin_minlen(path, len));
+ if (rpid)
+ return (config_pin_minlen_rpid(path, rpid));
+ if (force)
+ return (config_force_pin_change(path));
+ if (uv)
+ return (config_always_uv(path, 1));
+
+ return (pin_set(path));
+}
+
+int
+token_list(int argc, char **argv, char *path)
+{
+ fido_dev_info_t *devlist;
+ size_t ndevs;
+ const char *rp_id = NULL;
+ int blobs = 0;
+ int enrolls = 0;
+ int keys = 0;
+ int rplist = 0;
+ int ch;
+ int r;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'b':
+ blobs = 1;
+ break;
+ case 'e':
+ enrolls = 1;
+ break;
+ case 'k':
+ keys = 1;
+ rp_id = optarg;
+ break;
+ case 'r':
+ rplist = 1;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ if (blobs || enrolls || keys || rplist) {
+ if (path == NULL)
+ usage();
+ if (blobs)
+ return (blob_list(path));
+ if (enrolls)
+ return (bio_list(path));
+ if (keys)
+ return (credman_list_rk(path, rp_id));
+ if (rplist)
+ return (credman_list_rp(path));
+ /* NOTREACHED */
+ }
+
+ if ((devlist = fido_dev_info_new(64)) == NULL)
+ errx(1, "fido_dev_info_new");
+ if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK)
+ errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r);
+
+ for (size_t i = 0; i < ndevs; i++) {
+ const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i);
+ printf("%s: vendor=0x%04x, product=0x%04x (%s %s)\n",
+ fido_dev_info_path(di),
+ (uint16_t)fido_dev_info_vendor(di),
+ (uint16_t)fido_dev_info_product(di),
+ fido_dev_info_manufacturer_string(di),
+ fido_dev_info_product_string(di));
+ }
+
+ fido_dev_info_free(&devlist, ndevs);
+
+ exit(0);
+}
+
+int
+token_delete(int argc, char **argv, char *path)
+{
+ char *id = NULL;
+ char *key = NULL;
+ char *name = NULL;
+ int blob = 0;
+ int ch;
+ int enroll = 0;
+ int uv = 0;
+
+ optind = 1;
+
+ while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) {
+ switch (ch) {
+ case 'b':
+ blob = 1;
+ break;
+ case 'e':
+ enroll = 1;
+ break;
+ case 'i':
+ id = optarg;
+ break;
+ case 'k':
+ key = optarg;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'u':
+ uv = 1;
+ break;
+ default:
+ break; /* ignore */
+ }
+ }
+
+ if (path == NULL)
+ usage();
+
+ if (blob)
+ return (blob_delete(path, key, name, id));
+
+ if (id) {
+ if (uv)
+ usage();
+ if (enroll == 0)
+ return (credman_delete_rk(path, id));
+ return (bio_delete(path, id));
+ }
+
+ if (uv == 0)
+ usage();
+
+ return (config_always_uv(path, 0));
+}
diff --git a/tools/util.c b/tools/util.c
new file mode 100644
index 0000000..0e518bb
--- /dev/null
+++ b/tools/util.c
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2018-2022 Yubico AB. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <openssl/ec.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+#include <fido.h>
+#include <fido/es256.h>
+#include <fido/es384.h>
+#include <fido/rs256.h>
+#include <fido/eddsa.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../openbsd-compat/openbsd-compat.h"
+#ifdef _MSC_VER
+#include "../openbsd-compat/posix_win.h"
+#endif
+
+#include "extern.h"
+
+char *
+get_pin(const char *path)
+{
+ char *pin;
+ char prompt[1024];
+ int r, ok = -1;
+
+ if ((pin = calloc(1, PINBUF_LEN)) == NULL) {
+ warn("%s: calloc", __func__);
+ return NULL;
+ }
+ if ((r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ",
+ path)) < 0 || (size_t)r >= sizeof(prompt)) {
+ warn("%s: snprintf", __func__);
+ goto out;
+ }
+ if (!readpassphrase(prompt, pin, PINBUF_LEN, RPP_ECHO_OFF)) {
+ warnx("%s: readpassphrase", __func__);
+ goto out;
+ }
+
+ ok = 0;
+out:
+ if (ok < 0) {
+ freezero(pin, PINBUF_LEN);
+ pin = NULL;
+ }
+
+ return pin;
+}
+
+FILE *
+open_write(const char *file)
+{
+ int fd;
+ FILE *f;
+
+ if (file == NULL || strcmp(file, "-") == 0)
+ return (stdout);
+ if ((fd = open(file, O_WRONLY | O_CREAT, 0600)) < 0)
+ err(1, "open %s", file);
+ if ((f = fdopen(fd, "w")) == NULL)
+ err(1, "fdopen %s", file);
+
+ return (f);
+}
+
+FILE *
+open_read(const char *file)
+{
+ int fd;
+ FILE *f;
+
+ if (file == NULL || strcmp(file, "-") == 0) {
+#ifdef FIDO_FUZZ
+ setvbuf(stdin, NULL, _IONBF, 0);
+#endif
+ return (stdin);
+ }
+ if ((fd = open(file, O_RDONLY)) < 0)
+ err(1, "open %s", file);
+ if ((f = fdopen(fd, "r")) == NULL)
+ err(1, "fdopen %s", file);
+
+ return (f);
+}
+
+int
+base10(const char *str)
+{
+ char *ep;
+ long long ll;
+
+ ll = strtoll(str, &ep, 10);
+ if (str == ep || *ep != '\0')
+ return (-1);
+ else if (ll == LLONG_MIN && errno == ERANGE)
+ return (-1);
+ else if (ll == LLONG_MAX && errno == ERANGE)
+ return (-1);
+ else if (ll < 0 || ll > INT_MAX)
+ return (-1);
+
+ return ((int)ll);
+}
+
+void
+xxd(const void *buf, size_t count)
+{
+ const uint8_t *ptr = buf;
+ size_t i;
+
+ fprintf(stderr, " ");
+
+ for (i = 0; i < count; i++) {
+ fprintf(stderr, "%02x ", *ptr++);
+ if ((i + 1) % 16 == 0 && i + 1 < count)
+ fprintf(stderr, "\n ");
+ }
+
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+int
+string_read(FILE *f, char **out)
+{
+ char *line = NULL;
+ size_t linesize = 0;
+ ssize_t n;
+
+ *out = NULL;
+
+ if ((n = getline(&line, &linesize, f)) <= 0 ||
+ (size_t)n != strlen(line)) {
+ free(line);
+ return (-1);
+ }
+
+ line[n - 1] = '\0'; /* trim \n */
+ *out = line;
+
+ return (0);
+}
+
+fido_dev_t *
+open_dev(const char *path)
+{
+ fido_dev_t *dev;
+ int r;
+
+ if ((dev = fido_dev_new()) == NULL)
+ errx(1, "fido_dev_new");
+
+ r = fido_dev_open(dev, path);
+ if (r != FIDO_OK)
+ errx(1, "fido_dev_open %s: %s", path, fido_strerr(r));
+
+ return (dev);
+}
+
+int
+get_devopt(fido_dev_t *dev, const char *name, int *val)
+{
+ fido_cbor_info_t *cbor_info;
+ char * const *names;
+ const bool *values;
+ int r, ok = -1;
+
+ if ((cbor_info = fido_cbor_info_new()) == NULL) {
+ warnx("fido_cbor_info_new");
+ goto out;
+ }
+
+ if ((r = fido_dev_get_cbor_info(dev, cbor_info)) != FIDO_OK) {
+ warnx("fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r);
+ goto out;
+ }
+
+ if ((names = fido_cbor_info_options_name_ptr(cbor_info)) == NULL ||
+ (values = fido_cbor_info_options_value_ptr(cbor_info)) == NULL) {
+ warnx("fido_dev_get_cbor_info: NULL name/value pointer");
+ goto out;
+ }
+
+ *val = -1;
+ for (size_t i = 0; i < fido_cbor_info_options_len(cbor_info); i++)
+ if (strcmp(names[i], name) == 0) {
+ *val = values[i];
+ break;
+ }
+
+ ok = 0;
+out:
+ fido_cbor_info_free(&cbor_info);
+
+ return (ok);
+}
+
+EC_KEY *
+read_ec_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ EC_KEY *ec = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+ if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) {
+ warnx("EVP_PKEY_get1_EC_KEY");
+ goto fail;
+ }
+
+fail:
+ if (fp) {
+ fclose(fp);
+ }
+ if (pkey) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ec);
+}
+
+int
+write_es256_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ es256_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = es256_pk_new()) == NULL) {
+ warnx("es256_pk_new");
+ goto fail;
+ }
+
+ if (es256_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("es256_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("es256_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es256_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+int
+write_es384_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ es384_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = es384_pk_new()) == NULL) {
+ warnx("es384_pk_new");
+ goto fail;
+ }
+
+ if (es384_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("es384_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("es384_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ es384_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+RSA *
+read_rsa_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ RSA *rsa = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+ if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) {
+ warnx("EVP_PKEY_get1_RSA");
+ goto fail;
+ }
+
+fail:
+ if (fp) {
+ fclose(fp);
+ }
+ if (pkey) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (rsa);
+}
+
+int
+write_rsa_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ rs256_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = rs256_pk_new()) == NULL) {
+ warnx("rs256_pk_new");
+ goto fail;
+ }
+
+ if (rs256_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("rs256_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("rs256_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ rs256_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+EVP_PKEY *
+read_eddsa_pubkey(const char *path)
+{
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+
+ if ((fp = fopen(path, "r")) == NULL) {
+ warn("fopen");
+ goto fail;
+ }
+
+ if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) {
+ warnx("PEM_read_PUBKEY");
+ goto fail;
+ }
+
+fail:
+ if (fp) {
+ fclose(fp);
+ }
+
+ return (pkey);
+}
+
+int
+write_eddsa_pubkey(FILE *f, const void *ptr, size_t len)
+{
+ EVP_PKEY *pkey = NULL;
+ eddsa_pk_t *pk = NULL;
+ int ok = -1;
+
+ if ((pk = eddsa_pk_new()) == NULL) {
+ warnx("eddsa_pk_new");
+ goto fail;
+ }
+
+ if (eddsa_pk_from_ptr(pk, ptr, len) != FIDO_OK) {
+ warnx("eddsa_pk_from_ptr");
+ goto fail;
+ }
+
+ if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) {
+ warnx("eddsa_pk_to_EVP_PKEY");
+ goto fail;
+ }
+
+ if (PEM_write_PUBKEY(f, pkey) == 0) {
+ warnx("PEM_write_PUBKEY");
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ eddsa_pk_free(&pk);
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+
+ return (ok);
+}
+
+void
+print_cred(FILE *out_f, int type, const fido_cred_t *cred)
+{
+ char *id;
+ int r;
+
+ r = base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred), &id);
+ if (r < 0)
+ errx(1, "output error");
+
+ fprintf(out_f, "%s\n", id);
+
+ switch (type) {
+ case COSE_ES256:
+ write_es256_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ case COSE_ES384:
+ write_es384_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ case COSE_RS256:
+ write_rsa_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ case COSE_EDDSA:
+ write_eddsa_pubkey(out_f, fido_cred_pubkey_ptr(cred),
+ fido_cred_pubkey_len(cred));
+ break;
+ default:
+ errx(1, "print_cred: unknown type");
+ }
+
+ free(id);
+}
+
+int
+cose_type(const char *str, int *type)
+{
+ if (strcmp(str, "es256") == 0)
+ *type = COSE_ES256;
+ else if (strcmp(str, "es384") == 0)
+ *type = COSE_ES384;
+ else if (strcmp(str, "rs256") == 0)
+ *type = COSE_RS256;
+ else if (strcmp(str, "eddsa") == 0)
+ *type = COSE_EDDSA;
+ else {
+ *type = 0;
+ return (-1);
+ }
+
+ return (0);
+}
+
+const char *
+cose_string(int type)
+{
+ switch (type) {
+ case COSE_ES256:
+ return ("es256");
+ case COSE_ES384:
+ return ("es384");
+ case COSE_RS256:
+ return ("rs256");
+ case COSE_EDDSA:
+ return ("eddsa");
+ default:
+ return ("unknown");
+ }
+}
+
+const char *
+prot_string(int prot)
+{
+ switch (prot) {
+ case FIDO_CRED_PROT_UV_OPTIONAL:
+ return ("uvopt");
+ case FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID:
+ return ("uvopt+id");
+ case FIDO_CRED_PROT_UV_REQUIRED:
+ return ("uvreq");
+ default:
+ return ("unknown");
+ }
+}
+
+int
+read_file(const char *path, u_char **ptr, size_t *len)
+{
+ int fd, ok = -1;
+ struct stat st;
+ ssize_t n;
+
+ *ptr = NULL;
+ *len = 0;
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ warn("%s: open %s", __func__, path);
+ goto fail;
+ }
+ if (fstat(fd, &st) < 0) {
+ warn("%s: stat %s", __func__, path);
+ goto fail;
+ }
+ if (st.st_size < 0) {
+ warnx("%s: stat %s: invalid size", __func__, path);
+ goto fail;
+ }
+ *len = (size_t)st.st_size;
+ if ((*ptr = malloc(*len)) == NULL) {
+ warn("%s: malloc", __func__);
+ goto fail;
+ }
+ if ((n = read(fd, *ptr, *len)) < 0) {
+ warn("%s: read", __func__);
+ goto fail;
+ }
+ if ((size_t)n != *len) {
+ warnx("%s: read", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1) {
+ close(fd);
+ }
+ if (ok < 0) {
+ free(*ptr);
+ *ptr = NULL;
+ *len = 0;
+ }
+
+ return ok;
+}
+
+int
+write_file(const char *path, const u_char *ptr, size_t len)
+{
+ int fd, ok = -1;
+ ssize_t n;
+
+ if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) < 0) {
+ warn("%s: open %s", __func__, path);
+ goto fail;
+ }
+ if ((n = write(fd, ptr, len)) < 0) {
+ warn("%s: write", __func__);
+ goto fail;
+ }
+ if ((size_t)n != len) {
+ warnx("%s: write", __func__);
+ goto fail;
+ }
+
+ ok = 0;
+fail:
+ if (fd != -1) {
+ close(fd);
+ }
+
+ return ok;
+}
+
+const char *
+plural(size_t x)
+{
+ return x == 1 ? "" : "s";
+}
+
+int
+should_retry_with_pin(const fido_dev_t *dev, int r)
+{
+ if (fido_dev_has_pin(dev) == false) {
+ return 0;
+ }
+
+ switch (r) {
+ case FIDO_ERR_PIN_REQUIRED:
+ case FIDO_ERR_UNAUTHORIZED_PERM:
+ case FIDO_ERR_UV_BLOCKED:
+ case FIDO_ERR_UV_INVALID:
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/udev/70-u2f.rules b/udev/70-u2f.rules
new file mode 100644
index 0000000..6df852b
--- /dev/null
+++ b/udev/70-u2f.rules
@@ -0,0 +1,270 @@
+# Copyright (c) 2020 Yubico AB. All rights reserved.
+#
+# 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.
+#
+# SPDX-License-Identifier: BSD-2-Clause
+
+# This file is automatically generated, and should be used with udev 188
+# or newer.
+
+ACTION!="add|change", GOTO="fido_end"
+
+# ellipticSecure MIRKey by STMicroelectronics
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a2ac", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by STMicroelectronics
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a2ca", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by STMicroelectronics
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="cdab", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Infineon FIDO by Infineon Technologies
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="058b", ATTRS{idProduct}=="022d", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Kensington VeriMark by Synaptics Inc.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="06cb", ATTRS{idProduct}=="0088", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# FS ePass FIDO by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0850", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0852", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0853", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0854", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0856", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0858", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# FS MultiPass FIDO U2F by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="085a", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="085b", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="085d", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# BioPass FIDO2 K33 by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0866", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# BioPass FIDO2 K43 by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0867", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Hypersecu HyperFIDO by Feitian Technologies Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0880", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey NEO FIDO by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0113", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey NEO OTP+FIDO by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0114", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey NEO FIDO+CCID by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0115", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey NEO OTP+FIDO+CCID by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0116", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Security Key by Yubico by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0120", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Unknown product by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0121", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Gnubby U2F by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0200", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey 4 FIDO by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0402", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey 4 OTP+FIDO by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0403", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey 4 FIDO+CCID by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0406", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey 4 OTP+FIDO+CCID by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0407", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# YubiKey Plus by Yubico AB
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0410", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# U2F Zero by Silicon Laboratories, Inc.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="8acf", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# SoloKeys SoloHacker by pid.codes
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="5070", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# SoloKeys SoloBoot by pid.codes
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="50b0", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# SatoshiLabs TREZOR by pid.codes
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# SoloKeys v2 by pid.codes
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="beee", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Google Titan U2F by Google Inc.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="5026", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# VASCO SecureClick by VASCO Data Security NV
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1a44", ATTRS{idProduct}=="00bb", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# OnlyKey (FIDO2/U2F) by OpenMoko, Inc.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="60fc", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Neowave Keydo AES by NEOWAVE
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1e0d", ATTRS{idProduct}=="f1ae", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Neowave Keydo by NEOWAVE
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1e0d", ATTRS{idProduct}=="f1d0", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Thethis Key by Shenzhen Excelsecu Data Technology Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1ea8", ATTRS{idProduct}=="f025", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# ExcelSecu FIDO2 Security Key by Shenzhen Excelsecu Data Technology Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1ea8", ATTRS{idProduct}=="fc25", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# GoTrust Idem Key by NXP Semiconductors
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="f143", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Nitrokey FIDO U2F by Clay Logic
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="4287", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Nitrokey FIDO2 by Clay Logic
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b1", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Nitrokey 3C NFC by Clay Logic
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b2", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Safetech SafeKey by Clay Logic
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b3", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# CanoKey by Clay Logic
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42d4", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# JaCarta U2F by Aladdin Software Security R.D.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="24dc", ATTRS{idProduct}=="0101", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# JaCarta U2F by Aladdin Software Security R.D.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="24dc", ATTRS{idProduct}=="0501", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Happlink Security Key by Plug‐up
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="f1d0", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Bluink Key by Bluink Ltd
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2abe", ATTRS{idProduct}=="1002", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Blue by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano S Old firmware by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano X Old firmware by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Blue by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0011", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Blue Legacy by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0015", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano S HID+U2F by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1005", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano S HID+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1011", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano S HID+U2F+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1015", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano X HID+U2F by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4005", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano X HID+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4011", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano X HID+U2F+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4015", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano S+ HID+U2F by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="5005", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano S+ HID+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="5011", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Nano S+ HID+U2F+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="5015", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Stax HID+U2F by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="6005", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger Stax HID+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="6011", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Ledger stax HID+U2F+WEBUSB by LEDGER
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="6015", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Hypersecu HyperFIDO by Hypersecu Information Systems, Inc.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2ccf", ATTRS{idProduct}=="0880", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# TrustKey Solutions FIDO2 G310 by eWBM Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="4a1a", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# TrustKey Solutions FIDO2 G310H/G320H by eWBM Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="4a2a", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# TrustKey Solutions FIDO2 G320 by eWBM Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="4c2a", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# eWBM FIDO2 Goldengate G500 by eWBM Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="5c2f", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# TrustKey Solutions FIDO2 T120 by eWBM Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="a6e9", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# TrustKey Solutions FIDO2 T110 by eWBM Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="a7f9", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# eWBM FIDO2 Goldengate G450 by eWBM Co., Ltd.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="f47c", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Idem Key by GoTrustID Inc.
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="32a3", ATTRS{idProduct}=="3201", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# Longmai mFIDO by Unknown vendor
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="4c4d", ATTRS{idProduct}=="f703", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+# SatoshiLabs TREZOR by SatoshiLabs
+KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", TAG+="uaccess", GROUP="plugdev", MODE="0660"
+
+LABEL="fido_end"
diff --git a/udev/CMakeLists.txt b/udev/CMakeLists.txt
new file mode 100644
index 0000000..abddb80
--- /dev/null
+++ b/udev/CMakeLists.txt
@@ -0,0 +1,8 @@
+# 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
+
+if(UDEV_RULES_DIR)
+ install(FILES 70-u2f.rules DESTINATION ${UDEV_RULES_DIR})
+endif()
diff --git a/udev/check.sh b/udev/check.sh
new file mode 100755
index 0000000..804a884
--- /dev/null
+++ b/udev/check.sh
@@ -0,0 +1,32 @@
+#!/bin/sh -u
+
+# 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
+
+sort_by_id() {
+ awk '{ printf "%d\n", $3 }' | sort -Cnu
+}
+
+if ! grep '^vendor' "$1" | sort_by_id; then
+ echo unsorted vendor section 1>&2
+ exit 1
+fi
+
+VENDORS=$(grep '^vendor' "$1" | awk '{ print $2 }')
+PRODUCTS=$(grep '^product' "$1" | awk '{ print $2 }' | uniq)
+
+if [ "${VENDORS}" != "${PRODUCTS}" ]; then
+ echo vendors: "$(echo "${VENDORS}" | tr '\n' ',')" 1>&2
+ echo products: "$(echo "${PRODUCTS}" | tr '\n' ',')" 1>&2
+ echo vendors and products in different order 1>&2
+ exit 2
+fi
+
+for v in ${VENDORS}; do
+ if ! grep "^product ${v}" "$1" | sort_by_id; then
+ echo "${v}": unsorted product section 1>&2
+ exit 3
+ fi
+done
diff --git a/udev/fidodevs b/udev/fidodevs
new file mode 100644
index 0000000..ba62696
--- /dev/null
+++ b/udev/fidodevs
@@ -0,0 +1,137 @@
+# 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
+
+# After modifying this file, regenerate 70-u2f.rules:
+# ./genrules.awk fidodevs > 70-u2f.rules
+
+# List of known vendors. Sorted by vendor ID.
+
+vendor STMICRO 0x0483 STMicroelectronics
+vendor INFINEON 0x058b Infineon Technologies
+vendor SYNAPTICS 0x06cb Synaptics Inc.
+vendor FEITIAN 0x096e Feitian Technologies Co., Ltd.
+vendor YUBICO 0x1050 Yubico AB
+vendor SILICON 0x10c4 Silicon Laboratories, Inc.
+vendor PIDCODES 0x1209 pid.codes
+vendor GOOGLE 0x18d1 Google Inc.
+vendor VASCO 0x1a44 VASCO Data Security NV
+vendor OPENMOKO 0x1d50 OpenMoko, Inc.
+vendor NEOWAVE 0x1e0d NEOWAVE
+vendor EXCELSECU 0x1ea8 Shenzhen Excelsecu Data Technology Co., Ltd.
+vendor NXP 0x1fc9 NXP Semiconductors
+vendor CLAYLOGIC 0x20a0 Clay Logic
+vendor ALLADIN 0x24dc Aladdin Software Security R.D.
+vendor PLUGUP 0x2581 Plug‐up
+vendor BLUINK 0x2abe Bluink Ltd
+vendor LEDGER 0x2c97 LEDGER
+vendor HYPERSECU 0x2ccf Hypersecu Information Systems, Inc.
+vendor EWBM 0x311f eWBM Co., Ltd.
+vendor GOTRUST 0x32a3 GoTrustID Inc.
+vendor UNKNOWN1 0x4c4d Unknown vendor
+vendor SATOSHI 0x534c SatoshiLabs
+
+# List of known products. Grouped by vendor; sorted by product ID.
+
+product STMICRO 0xa2ac ellipticSecure MIRKey
+product STMICRO 0xa2ca Unknown product
+product STMICRO 0xcdab Unknown product
+
+product INFINEON 0x022d Infineon FIDO
+
+product SYNAPTICS 0x0088 Kensington VeriMark
+
+product FEITIAN 0x0850 FS ePass FIDO
+product FEITIAN 0x0852 Unknown product
+product FEITIAN 0x0853 Unknown product
+product FEITIAN 0x0854 Unknown product
+product FEITIAN 0x0856 Unknown product
+product FEITIAN 0x0858 Unknown product
+product FEITIAN 0x085a FS MultiPass FIDO U2F
+product FEITIAN 0x085b Unknown product
+product FEITIAN 0x085d Unknown product
+product FEITIAN 0x0866 BioPass FIDO2 K33
+product FEITIAN 0x0867 BioPass FIDO2 K43
+product FEITIAN 0x0880 Hypersecu HyperFIDO
+
+product YUBICO 0x0113 YubiKey NEO FIDO
+product YUBICO 0x0114 YubiKey NEO OTP+FIDO
+product YUBICO 0x0115 YubiKey NEO FIDO+CCID
+product YUBICO 0x0116 YubiKey NEO OTP+FIDO+CCID
+product YUBICO 0x0120 Security Key by Yubico
+product YUBICO 0x0121 Unknown product
+product YUBICO 0x0200 Gnubby U2F
+product YUBICO 0x0402 YubiKey 4 FIDO
+product YUBICO 0x0403 YubiKey 4 OTP+FIDO
+product YUBICO 0x0406 YubiKey 4 FIDO+CCID
+product YUBICO 0x0407 YubiKey 4 OTP+FIDO+CCID
+product YUBICO 0x0410 YubiKey Plus
+
+product SILICON 0x8acf U2F Zero
+
+product PIDCODES 0x5070 SoloKeys SoloHacker
+product PIDCODES 0x50b0 SoloKeys SoloBoot
+product PIDCODES 0x53c1 SatoshiLabs TREZOR
+product PIDCODES 0xbeee SoloKeys v2
+
+product GOOGLE 0x5026 Google Titan U2F
+
+product VASCO 0x00bb VASCO SecureClick
+
+product OPENMOKO 0x60fc OnlyKey (FIDO2/U2F)
+
+product NEOWAVE 0xf1ae Neowave Keydo AES
+product NEOWAVE 0xf1d0 Neowave Keydo
+
+product EXCELSECU 0xf025 Thethis Key
+product EXCELSECU 0xfc25 ExcelSecu FIDO2 Security Key
+
+product NXP 0xf143 GoTrust Idem Key
+
+product CLAYLOGIC 0x4287 Nitrokey FIDO U2F
+product CLAYLOGIC 0x42b1 Nitrokey FIDO2
+product CLAYLOGIC 0x42b2 Nitrokey 3C NFC
+product CLAYLOGIC 0x42b3 Safetech SafeKey
+product CLAYLOGIC 0x42d4 CanoKey
+
+product ALLADIN 0x0101 JaCarta U2F
+product ALLADIN 0x0501 JaCarta U2F
+
+product PLUGUP 0xf1d0 Happlink Security Key
+
+product BLUINK 0x1002 Bluink Key
+
+product LEDGER 0x0000 Ledger Blue
+product LEDGER 0x0001 Ledger Nano S Old firmware
+product LEDGER 0x0004 Ledger Nano X Old firmware
+product LEDGER 0x0011 Ledger Blue
+product LEDGER 0x0015 Ledger Blue Legacy
+product LEDGER 0x1005 Ledger Nano S HID+U2F
+product LEDGER 0x1011 Ledger Nano S HID+WEBUSB
+product LEDGER 0x1015 Ledger Nano S HID+U2F+WEBUSB
+product LEDGER 0x4005 Ledger Nano X HID+U2F
+product LEDGER 0x4011 Ledger Nano X HID+WEBUSB
+product LEDGER 0x4015 Ledger Nano X HID+U2F+WEBUSB
+product LEDGER 0x5005 Ledger Nano S+ HID+U2F
+product LEDGER 0x5011 Ledger Nano S+ HID+WEBUSB
+product LEDGER 0x5015 Ledger Nano S+ HID+U2F+WEBUSB
+product LEDGER 0x6005 Ledger Stax HID+U2F
+product LEDGER 0x6011 Ledger Stax HID+WEBUSB
+product LEDGER 0x6015 Ledger stax HID+U2F+WEBUSB
+
+product HYPERSECU 0x0880 Hypersecu HyperFIDO
+
+product EWBM 0x4a1a TrustKey Solutions FIDO2 G310
+product EWBM 0x4a2a TrustKey Solutions FIDO2 G310H/G320H
+product EWBM 0x4c2a TrustKey Solutions FIDO2 G320
+product EWBM 0x5c2f eWBM FIDO2 Goldengate G500
+product EWBM 0xa6e9 TrustKey Solutions FIDO2 T120
+product EWBM 0xa7f9 TrustKey Solutions FIDO2 T110
+product EWBM 0xf47c eWBM FIDO2 Goldengate G450
+
+product GOTRUST 0x3201 Idem Key
+
+product UNKNOWN1 0xf703 Longmai mFIDO
+
+product SATOSHI 0x0001 SatoshiLabs TREZOR
diff --git a/udev/genrules.awk b/udev/genrules.awk
new file mode 100755
index 0000000..3dad667
--- /dev/null
+++ b/udev/genrules.awk
@@ -0,0 +1,79 @@
+#!/usr/bin/awk -f
+
+# 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
+
+NR == 1 {
+ print "# Copyright (c) 2020 Yubico AB. All rights reserved."
+ print "#"
+ print "# Redistribution and use in source and binary forms, with or without"
+ print "# modification, are permitted provided that the following conditions are"
+ print "# met:"
+ print "# "
+ print "# 1. Redistributions of source code must retain the above copyright"
+ print "# notice, this list of conditions and the following disclaimer."
+ print "# 2. Redistributions in binary form must reproduce the above copyright"
+ print "# notice, this list of conditions and the following disclaimer in"
+ print "# the documentation and/or other materials provided with the"
+ print "# distribution."
+ print "# "
+ print "# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS"
+ print "# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT"
+ print "# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR"
+ print "# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT"
+ print "# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,"
+ print "# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT"
+ print "# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,"
+ print "# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY"
+ print "# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT"
+ print "# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE"
+ print "# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+ print "#"
+ print "# SPDX-License-Identifier: BSD-2-Clause"
+ print ""
+ print "# This file is automatically generated, and should be used with udev 188"
+ print "# or newer."
+ print ""
+ print "ACTION!=\"add|change\", GOTO=\"fido_end\""
+
+ next
+}
+
+$1 == "vendor" {
+ sub("0x", "", $3)
+ vendors[$2, "id"] = $3
+
+ f = 4
+ while (f <= NF) {
+ vendors[$2, "name"] = vendors[$2, "name"] " " $f
+ f++
+ }
+}
+
+$1 == "product" {
+ sub("0x", "", $3)
+ name = ""
+
+ f = 4
+ while (f <= NF) {
+ name = name " " $f
+ f++
+ }
+
+ line = "\n#" name " by" vendors[$2, "name"]"\n"
+ line = line"KERNEL==\"hidraw*\""
+ line = line", SUBSYSTEM==\"hidraw\""
+ line = line", ATTRS{idVendor}==\""vendors[$2, "id"]"\""
+ line = line", ATTRS{idProduct}==\""$3"\""
+ line = line", TAG+=\"uaccess\""
+ line = line", GROUP=\"plugdev\""
+ line = line", MODE=\"0660\""
+
+ print line
+}
+
+END {
+ print "\nLABEL=\"fido_end\""
+}
diff --git a/windows/build.ps1 b/windows/build.ps1
new file mode 100644
index 0000000..ff43d8b
--- /dev/null
+++ b/windows/build.ps1
@@ -0,0 +1,243 @@
+# 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
+
+param(
+ [string]$CMakePath = "C:\Program Files\CMake\bin\cmake.exe",
+ [string]$GitPath = "C:\Program Files\Git\bin\git.exe",
+ [string]$SevenZPath = "C:\Program Files\7-Zip\7z.exe",
+ [string]$GPGPath = "C:\Program Files (x86)\GnuPG\bin\gpg.exe",
+ [string]$WinSDK = "",
+ [string]$Config = "Release",
+ [string]$Arch = "x64",
+ [string]$Type = "dynamic",
+ [string]$Fido2Flags = ""
+)
+
+$ErrorActionPreference = "Stop"
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+. "$PSScriptRoot\const.ps1"
+
+Function ExitOnError() {
+ if ($LastExitCode -ne 0) {
+ throw "A command exited with status $LastExitCode"
+ }
+}
+
+Function GitClone(${REPO}, ${BRANCH}, ${DIR}) {
+ Write-Host "Cloning ${REPO}..."
+ & $Git -c advice.detachedHead=false clone --quiet --depth=1 `
+ --branch "${BRANCH}" "${REPO}" "${DIR}"
+ Write-Host "${REPO}'s ${BRANCH} HEAD is:"
+ & $Git -C "${DIR}" show -s HEAD
+}
+
+# Find Git.
+$Git = $(Get-Command git -ErrorAction Ignore | `
+ Select-Object -ExpandProperty Source)
+if ([string]::IsNullOrEmpty($Git)) {
+ $Git = $GitPath
+}
+if (-Not (Test-Path $Git)) {
+ throw "Unable to find Git at $Git"
+}
+
+# Find CMake.
+$CMake = $(Get-Command cmake -ErrorAction Ignore | `
+ Select-Object -ExpandProperty Source)
+if ([string]::IsNullOrEmpty($CMake)) {
+ $CMake = $CMakePath
+}
+if (-Not (Test-Path $CMake)) {
+ throw "Unable to find CMake at $CMake"
+}
+
+# Find 7z.
+$SevenZ = $(Get-Command 7z -ErrorAction Ignore | `
+ Select-Object -ExpandProperty Source)
+if ([string]::IsNullOrEmpty($SevenZ)) {
+ $SevenZ = $SevenZPath
+}
+if (-Not (Test-Path $SevenZ)) {
+ throw "Unable to find 7z at $SevenZ"
+}
+
+# Find GPG.
+$GPG = $(Get-Command gpg -ErrorAction Ignore | `
+ Select-Object -ExpandProperty Source)
+if ([string]::IsNullOrEmpty($GPG)) {
+ $GPG = $GPGPath
+}
+if (-Not (Test-Path $GPG)) {
+ throw "Unable to find GPG at $GPG"
+}
+
+# Override CMAKE_SYSTEM_VERSION if $WinSDK is set.
+if (-Not ([string]::IsNullOrEmpty($WinSDK))) {
+ $CMAKE_SYSTEM_VERSION = "-DCMAKE_SYSTEM_VERSION='$WinSDK'"
+} else {
+ $CMAKE_SYSTEM_VERSION = ''
+}
+
+Write-Host "WinSDK: $WinSDK"
+Write-Host "Config: $Config"
+Write-Host "Arch: $Arch"
+Write-Host "Type: $Type"
+Write-Host "Git: $Git"
+Write-Host "CMake: $CMake"
+Write-Host "7z: $SevenZ"
+Write-Host "GPG: $GPG"
+
+# Create build directories.
+New-Item -Type Directory "${BUILD}" -Force
+New-Item -Type Directory "${BUILD}\${Arch}" -Force
+New-Item -Type Directory "${BUILD}\${Arch}\${Type}" -Force
+New-Item -Type Directory "${STAGE}\${LIBRESSL}" -Force
+New-Item -Type Directory "${STAGE}\${LIBCBOR}" -Force
+New-Item -Type Directory "${STAGE}\${ZLIB}" -Force
+
+# Create output directories.
+New-Item -Type Directory "${OUTPUT}" -Force
+New-Item -Type Directory "${OUTPUT}\${Arch}" -Force
+New-Item -Type Directory "${OUTPUT}\${Arch}\${Type}" -force
+
+# Fetch and verify dependencies.
+Push-Location ${BUILD}
+try {
+ if (-Not (Test-Path .\${LIBRESSL})) {
+ if (-Not (Test-Path .\${LIBRESSL}.tar.gz -PathType leaf)) {
+ Invoke-WebRequest ${LIBRESSL_URL}/${LIBRESSL}.tar.gz `
+ -OutFile .\${LIBRESSL}.tar.gz
+ }
+ if (-Not (Test-Path .\${LIBRESSL}.tar.gz.asc -PathType leaf)) {
+ Invoke-WebRequest ${LIBRESSL_URL}/${LIBRESSL}.tar.gz.asc `
+ -OutFile .\${LIBRESSL}.tar.gz.asc
+ }
+
+ Copy-Item "$PSScriptRoot\libressl.gpg" -Destination "${BUILD}"
+ & $GPG --list-keys
+ & $GPG --quiet --no-default-keyring --keyring ./libressl.gpg `
+ --verify .\${LIBRESSL}.tar.gz.asc .\${LIBRESSL}.tar.gz
+ if ($LastExitCode -ne 0) {
+ throw "GPG signature verification failed"
+ }
+ & $SevenZ e .\${LIBRESSL}.tar.gz
+ & $SevenZ x .\${LIBRESSL}.tar
+ Remove-Item -Force .\${LIBRESSL}.tar
+ }
+ if (-Not (Test-Path .\${LIBCBOR})) {
+ GitClone "${LIBCBOR_GIT}" "${LIBCBOR_BRANCH}" ".\${LIBCBOR}"
+ }
+ if (-Not (Test-Path .\${ZLIB})) {
+ GitClone "${ZLIB_GIT}" "${ZLIB_BRANCH}" ".\${ZLIB}"
+ }
+} catch {
+ throw "Failed to fetch and verify dependencies"
+} finally {
+ Pop-Location
+}
+
+# Build LibreSSL.
+Push-Location ${STAGE}\${LIBRESSL}
+try {
+ & $CMake ..\..\..\${LIBRESSL} -A "${Arch}" `
+ -DBUILD_SHARED_LIBS="${SHARED}" -DLIBRESSL_TESTS=OFF `
+ -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" `
+ -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" `
+ -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; `
+ ExitOnError
+ & $CMake --build . --config ${Config} --verbose; ExitOnError
+ & $CMake --build . --config ${Config} --target install --verbose; `
+ ExitOnError
+} catch {
+ throw "Failed to build LibreSSL"
+} finally {
+ Pop-Location
+}
+
+# Build libcbor.
+Push-Location ${STAGE}\${LIBCBOR}
+try {
+ & $CMake ..\..\..\${LIBCBOR} -A "${Arch}" `
+ -DWITH_EXAMPLES=OFF `
+ -DBUILD_SHARED_LIBS="${SHARED}" `
+ -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG} /wd4703" `
+ -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE} /wd4703" `
+ -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; `
+ ExitOnError
+ & $CMake --build . --config ${Config} --verbose; ExitOnError
+ & $CMake --build . --config ${Config} --target install --verbose; `
+ ExitOnError
+} catch {
+ throw "Failed to build libcbor"
+} finally {
+ Pop-Location
+}
+
+# Build zlib.
+Push-Location ${STAGE}\${ZLIB}
+try {
+ & $CMake ..\..\..\${ZLIB} -A "${Arch}" `
+ -DBUILD_SHARED_LIBS="${SHARED}" `
+ -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" `
+ -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" `
+ -DCMAKE_MSVC_RUNTIME_LIBRARY="${CMAKE_MSVC_RUNTIME_LIBRARY}" `
+ -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; `
+ ExitOnError
+ & $CMake --build . --config ${Config} --verbose; ExitOnError
+ & $CMake --build . --config ${Config} --target install --verbose; `
+ ExitOnError
+ # Patch up zlib's various names.
+ if ("${Type}" -eq "Dynamic") {
+ ((Get-ChildItem -Path "${PREFIX}/lib") -Match "zlib[d]?.lib") |
+ Copy-Item -Destination "${PREFIX}/lib/zlib1.lib" -Force
+ ((Get-ChildItem -Path "${PREFIX}/bin") -Match "zlibd1.dll") |
+ Copy-Item -Destination "${PREFIX}/bin/zlib1.dll" -Force
+ } else {
+ ((Get-ChildItem -Path "${PREFIX}/lib") -Match "zlibstatic[d]?.lib") |
+ Copy-item -Destination "${PREFIX}/lib/zlib1.lib" -Force
+ }
+} catch {
+ throw "Failed to build zlib"
+} finally {
+ Pop-Location
+}
+
+# Build libfido2.
+Push-Location ${STAGE}
+try {
+ & $CMake ..\..\.. -A "${Arch}" `
+ -DCMAKE_BUILD_TYPE="${Config}" `
+ -DBUILD_SHARED_LIBS="${SHARED}" `
+ -DCBOR_INCLUDE_DIRS="${PREFIX}\include" `
+ -DCBOR_LIBRARY_DIRS="${PREFIX}\lib" `
+ -DCBOR_BIN_DIRS="${PREFIX}\bin" `
+ -DZLIB_INCLUDE_DIRS="${PREFIX}\include" `
+ -DZLIB_LIBRARY_DIRS="${PREFIX}\lib" `
+ -DZLIB_BIN_DIRS="${PREFIX}\bin" `
+ -DCRYPTO_INCLUDE_DIRS="${PREFIX}\include" `
+ -DCRYPTO_LIBRARY_DIRS="${PREFIX}\lib" `
+ -DCRYPTO_BIN_DIRS="${PREFIX}\bin" `
+ -DCRYPTO_LIBRARIES="${CRYPTO_LIBRARIES}" `
+ -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG} ${Fido2Flags}" `
+ -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE} ${Fido2Flags}" `
+ -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; `
+ ExitOnError
+ & $CMake --build . --config ${Config} --verbose; ExitOnError
+ & $CMake --build . --config ${Config} --target regress --verbose; `
+ ExitOnError
+ & $CMake --build . --config ${Config} --target install --verbose; `
+ ExitOnError
+ # Copy DLLs.
+ if ("${SHARED}" -eq "ON") {
+ "cbor.dll", "${CRYPTO_LIBRARIES}.dll", "zlib1.dll" | `
+ %{ Copy-Item "${PREFIX}\bin\$_" `
+ -Destination "examples\${Config}" }
+ }
+} catch {
+ throw "Failed to build libfido2"
+} finally {
+ Pop-Location
+}
diff --git a/windows/const.ps1 b/windows/const.ps1
new file mode 100644
index 0000000..7fac21e
--- /dev/null
+++ b/windows/const.ps1
@@ -0,0 +1,48 @@
+# Copyright (c) 2021-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
+
+# LibreSSL coordinates.
+New-Variable -Name 'LIBRESSL_URL' `
+ -Value 'https://cloudflare.cdn.openbsd.org/pub/OpenBSD/LibreSSL' `
+ -Option Constant
+New-Variable -Name 'LIBRESSL' -Value 'libressl-3.7.3' -Option Constant
+New-Variable -Name 'CRYPTO_LIBRARIES' -Value 'crypto-50' -Option Constant
+
+# libcbor coordinates.
+New-Variable -Name 'LIBCBOR' -Value 'libcbor-0.10.2' -Option Constant
+New-Variable -Name 'LIBCBOR_BRANCH' -Value 'v0.10.2' -Option Constant
+New-Variable -Name 'LIBCBOR_GIT' -Value 'https://github.com/pjk/libcbor' `
+ -Option Constant
+
+# zlib coordinates.
+New-Variable -Name 'ZLIB' -Value 'zlib-1.3' -Option Constant
+New-Variable -Name 'ZLIB_BRANCH' -Value 'v1.3' -Option Constant
+New-Variable -Name 'ZLIB_GIT' -Value 'https://github.com/madler/zlib' `
+ -Option Constant
+
+# Work directories.
+New-Variable -Name 'BUILD' -Value "$PSScriptRoot\..\build" -Option Constant
+New-Variable -Name 'OUTPUT' -Value "$PSScriptRoot\..\output" -Option Constant
+
+# Prefixes.
+New-Variable -Name 'STAGE' -Value "${BUILD}\${Arch}\${Type}" -Option Constant
+New-Variable -Name 'PREFIX' -Value "${OUTPUT}\${Arch}\${Type}" -Option Constant
+
+# Build flags.
+if ("${Type}" -eq "dynamic") {
+ New-Variable -Name 'RUNTIME' -Value '/MD' -Option Constant
+ New-Variable -Name 'SHARED' -Value 'ON' -Option Constant
+ New-Variable -Name 'CMAKE_MSVC_RUNTIME_LIBRARY' -Option Constant `
+ -Value 'MultiThreaded$<$<CONFIG:Debug>:Debug>DLL'
+} else {
+ New-Variable -Name 'RUNTIME' -Value '/MT' -Option Constant
+ New-Variable -Name 'SHARED' -Value 'OFF' -Option Constant
+ New-Variable -Name 'CMAKE_MSVC_RUNTIME_LIBRARY' -Option Constant `
+ -Value 'MultiThreaded$<$<CONFIG:Debug>:Debug>'
+}
+New-Variable -Name 'CFLAGS_DEBUG' -Value "${RUNTIME}d /Zi /guard:cf /sdl" `
+ -Option Constant
+New-Variable -Name 'CFLAGS_RELEASE' -Value "${RUNTIME} /Zi /guard:cf /sdl" `
+ -Option Constant
diff --git a/windows/cygwin.gpg b/windows/cygwin.gpg
new file mode 100755
index 0000000..1e87237
--- /dev/null
+++ b/windows/cygwin.gpg
Binary files differ
diff --git a/windows/cygwin.ps1 b/windows/cygwin.ps1
new file mode 100755
index 0000000..0681830
--- /dev/null
+++ b/windows/cygwin.ps1
@@ -0,0 +1,70 @@
+# 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
+
+param(
+ [string]$GPGPath = "C:\Program Files (x86)\GnuPG\bin\gpg.exe",
+ [string]$Config = "Release"
+)
+
+$ErrorActionPreference = "Stop"
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+
+# Cygwin coordinates.
+$URL = 'https://www.cygwin.com'
+$Setup = 'setup-x86_64.exe'
+$Mirror = 'https://mirrors.kernel.org/sourceware/cygwin/'
+$Packages = 'gcc-core,pkg-config,cmake,make,libcbor-devel,libssl-devel,zlib-devel'
+
+# Work directories.
+$Cygwin = "$PSScriptRoot\..\cygwin"
+$Root = "${Cygwin}\root"
+
+# Find GPG.
+$GPG = $(Get-Command gpg -ErrorAction Ignore | `
+ Select-Object -ExpandProperty Source)
+if ([string]::IsNullOrEmpty($GPG)) {
+ $GPG = $GPGPath
+}
+if (-Not (Test-Path $GPG)) {
+ throw "Unable to find GPG at $GPG"
+}
+
+Write-Host "Config: $Config"
+Write-Host "GPG: $GPG"
+
+# Create work directories.
+New-Item -Type Directory "${Cygwin}" -Force
+New-Item -Type Directory "${Root}" -Force
+
+# Fetch and verify Cygwin.
+try {
+ if (-Not (Test-Path ${Cygwin}\${Setup} -PathType leaf)) {
+ Invoke-WebRequest ${URL}/${Setup} `
+ -OutFile ${Cygwin}\${Setup}
+ }
+ if (-Not (Test-Path ${Cygwin}\${Setup}.sig -PathType leaf)) {
+ Invoke-WebRequest ${URL}/${Setup}.sig `
+ -OutFile ${Cygwin}\${Setup}.sig
+ }
+ & $GPG --list-keys
+ & $GPG --quiet --no-default-keyring `
+ --keyring ${PSScriptRoot}/cygwin.gpg `
+ --verify ${Cygwin}\${Setup}.sig ${Cygwin}\${Setup}
+ if ($LastExitCode -ne 0) {
+ throw "GPG signature verification failed"
+ }
+} catch {
+ throw "Failed to fetch and verify Cygwin"
+}
+
+# Bootstrap Cygwin.
+Start-Process "${Cygwin}\${Setup}" -Wait -NoNewWindow `
+ -ArgumentList "-dnNOqW -s ${Mirror} -R ${Root} -P ${Packages}"
+
+# Build libfido2.
+$Env:PATH = "${Root}\bin\;" + $Env:PATH
+cmake "-DCMAKE_BUILD_TYPE=${Config}" -B "build-${Config}"
+make -C "build-${Config}"
+make -C "build-${Config}" regress
diff --git a/windows/libressl.gpg b/windows/libressl.gpg
new file mode 100644
index 0000000..87d5dad
--- /dev/null
+++ b/windows/libressl.gpg
Binary files differ
diff --git a/windows/release.ps1 b/windows/release.ps1
new file mode 100644
index 0000000..cc5f635
--- /dev/null
+++ b/windows/release.ps1
@@ -0,0 +1,97 @@
+# 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
+
+$ErrorActionPreference = "Stop"
+$Architectures = @('x64', 'Win32', 'ARM64', 'ARM')
+$InstallPrefixes = @('Win64', 'Win32', 'ARM64', 'ARM')
+$Types = @('dynamic', 'static')
+$Config = 'Release'
+$SDK = '143'
+
+. "$PSScriptRoot\const.ps1"
+
+foreach ($Arch in $Architectures) {
+ foreach ($Type in $Types) {
+ ./build.ps1 -Arch ${Arch} -Type ${Type} -Config ${Config}
+ }
+}
+
+foreach ($InstallPrefix in $InstallPrefixes) {
+ foreach ($Type in $Types) {
+ New-Item -Type Directory `
+ "${OUTPUT}/pkg/${InstallPrefix}/${Config}/v${SDK}/${Type}"
+ }
+}
+
+Function Package-Headers() {
+ Copy-Item "${OUTPUT}\x64\dynamic\include" -Destination "${OUTPUT}\pkg" `
+ -Recurse -ErrorAction Stop
+}
+
+Function Package-Dynamic(${SRC}, ${DEST}) {
+ Copy-Item "${SRC}\bin\cbor.dll" "${DEST}"
+ Copy-Item "${SRC}\lib\cbor.lib" "${DEST}"
+ Copy-Item "${SRC}\bin\zlib1.dll" "${DEST}"
+ Copy-Item "${SRC}\lib\zlib1.lib" "${DEST}"
+ Copy-Item "${SRC}\bin\${CRYPTO_LIBRARIES}.dll" "${DEST}"
+ Copy-Item "${SRC}\lib\${CRYPTO_LIBRARIES}.lib" "${DEST}"
+ Copy-Item "${SRC}\bin\fido2.dll" "${DEST}"
+ Copy-Item "${SRC}\lib\fido2.lib" "${DEST}"
+}
+
+Function Package-Static(${SRC}, ${DEST}) {
+ Copy-Item "${SRC}/lib/cbor.lib" "${DEST}"
+ Copy-Item "${SRC}/lib/zlib1.lib" "${DEST}"
+ Copy-Item "${SRC}/lib/${CRYPTO_LIBRARIES}.lib" "${DEST}"
+ Copy-Item "${SRC}/lib/fido2_static.lib" "${DEST}/fido2.lib"
+}
+
+Function Package-PDBs(${SRC}, ${DEST}) {
+ Copy-Item "${SRC}\${LIBRESSL}\crypto\crypto_obj.dir\${Config}\crypto_obj.pdb" `
+ "${DEST}\${CRYPTO_LIBRARIES}.pdb"
+ Copy-Item "${SRC}\${LIBCBOR}\src\cbor.dir\${Config}\vc${SDK}.pdb" `
+ "${DEST}\cbor.pdb"
+ Copy-Item "${SRC}\${ZLIB}\zlib.dir\${Config}\vc${SDK}.pdb" `
+ "${DEST}\zlib1.pdb"
+ Copy-Item "${SRC}\src\fido2_shared.dir\${Config}\vc${SDK}.pdb" `
+ "${DEST}\fido2.pdb"
+}
+
+Function Package-StaticPDBs(${SRC}, ${DEST}) {
+ Copy-Item "${SRC}\${LIBRESSL}\crypto\crypto_obj.dir\${Config}\crypto_obj.pdb" `
+ "${DEST}\${CRYPTO_LIBRARIES}.pdb"
+ Copy-Item "${SRC}\${LIBCBOR}\src\${Config}\cbor.pdb" `
+ "${DEST}\cbor.pdb"
+ Copy-Item "${SRC}\${ZLIB}\${Config}\zlibstatic.pdb" `
+ "${DEST}\zlib1.pdb"
+ Copy-Item "${SRC}\src\${Config}\fido2_static.pdb" `
+ "${DEST}\fido2.pdb"
+}
+
+Function Package-Tools(${SRC}, ${DEST}) {
+ Copy-Item "${SRC}\tools\${Config}\fido2-assert.exe" `
+ "${DEST}\fido2-assert.exe"
+ Copy-Item "${SRC}\tools\${Config}\fido2-cred.exe" `
+ "${DEST}\fido2-cred.exe"
+ Copy-Item "${SRC}\tools\${Config}\fido2-token.exe" `
+ "${DEST}\fido2-token.exe"
+}
+
+Package-Headers
+
+for ($i = 0; $i -lt $Architectures.Length; $i++) {
+ $Arch = $Architectures[$i]
+ $InstallPrefix = $InstallPrefixes[$i]
+ Package-Dynamic "${OUTPUT}\${Arch}\dynamic" `
+ "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\dynamic"
+ Package-PDBs "${BUILD}\${Arch}\dynamic" `
+ "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\dynamic"
+ Package-Tools "${BUILD}\${Arch}\dynamic" `
+ "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\dynamic"
+ Package-Static "${OUTPUT}\${Arch}\static" `
+ "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\static"
+ Package-StaticPDBs "${BUILD}\${Arch}\static" `
+ "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\static"
+}