From a58dee945a5da64d0e97f35a508928e0d17c9cc7 Mon Sep 17 00:00:00 2001 From: Ed Maste Date: Fri, 1 Oct 2021 19:52:55 -0400 Subject: [PATCH] Vendor import of libfido2 1.8.0 --- CMakeLists.txt | 418 +++++++ LICENSE | 24 + NEWS | 179 +++ README.adoc | 93 ++ examples/CMakeLists.txt | 69 ++ examples/README.adoc | 98 ++ examples/assert.c | 342 ++++++ examples/cred.c | 346 ++++++ examples/extern.h | 33 + examples/info.c | 293 +++++ examples/manifest.c | 41 + examples/reset.c | 55 + examples/retries.c | 48 + examples/select.c | 214 ++++ examples/setpin.c | 54 + examples/util.c | 413 +++++++ fuzz/CMakeLists.txt | 63 + fuzz/Dockerfile | 12 + fuzz/Makefile | 79 ++ fuzz/README | 33 + fuzz/build-coverage | 31 + fuzz/dummy.h | 96 ++ fuzz/export.gnu | 242 ++++ fuzz/functions.txt | 807 ++++++++++++ fuzz/fuzz_assert.c | 471 +++++++ fuzz/fuzz_bio.c | 440 +++++++ fuzz/fuzz_cred.c | 455 +++++++ fuzz/fuzz_credman.c | 405 ++++++ fuzz/fuzz_hid.c | 215 ++++ fuzz/fuzz_largeblob.c | 270 ++++ fuzz/fuzz_mgmt.c | 480 ++++++++ fuzz/fuzz_netlink.c | 249 ++++ fuzz/libfuzzer.c | 177 +++ fuzz/mutator_aux.c | 326 +++++ fuzz/mutator_aux.h | 96 ++ fuzz/preload-fuzz.c | 104 ++ fuzz/preload-snoop.c | 217 ++++ fuzz/prng.c | 113 ++ fuzz/report.tgz | Bin 0 -> 303082 bytes fuzz/summary.txt | 51 + fuzz/udev.c | 269 ++++ fuzz/uniform_random.c | 57 + fuzz/wiredata_fido2.h | 633 ++++++++++ fuzz/wiredata_u2f.h | 152 +++ fuzz/wrap.c | 582 +++++++++ fuzz/wrapped.sym | 83 ++ man/CMakeLists.txt | 371 ++++++ man/NOTES | 7 + man/dyc.css | 14 + man/eddsa_pk_new.3 | 122 ++ man/es256_pk_new.3 | 126 ++ man/fido2-assert.1 | 256 ++++ man/fido2-cred.1 | 267 ++++ man/fido2-token.1 | 388 ++++++ man/fido_assert_allow_cred.3 | 47 + man/fido_assert_new.3 | 243 ++++ man/fido_assert_set_authdata.3 | 221 ++++ man/fido_assert_verify.3 | 79 ++ man/fido_bio_dev_get_info.3 | 122 ++ man/fido_bio_enroll_new.3 | 95 ++ man/fido_bio_info_new.3 | 81 ++ man/fido_bio_template.3 | 179 +++ man/fido_cbor_info_new.3 | 231 ++++ man/fido_cred_exclude.3 | 60 + man/fido_cred_new.3 | 257 ++++ man/fido_cred_set_authdata.3 | 307 +++++ man/fido_cred_verify.3 | 69 ++ man/fido_credman_metadata_new.3 | 326 +++++ man/fido_dev_enable_entattest.3 | 98 ++ man/fido_dev_get_assert.3 | 76 ++ man/fido_dev_get_touch_begin.3 | 73 ++ man/fido_dev_info_manifest.3 | 143 +++ man/fido_dev_largeblob_get.3 | 194 +++ man/fido_dev_make_cred.3 | 77 ++ man/fido_dev_open.3 | 250 ++++ man/fido_dev_set_io_functions.3 | 134 ++ man/fido_dev_set_pin.3 | 103 ++ man/fido_init.3 | 52 + man/fido_strerr.3 | 27 + man/rs256_pk_new.3 | 122 ++ man/style.css | 24 + openbsd-compat/bsd-getline.c | 115 ++ openbsd-compat/bsd-getpagesize.c | 27 + openbsd-compat/clock_gettime.c | 32 + openbsd-compat/endian_win32.c | 51 + openbsd-compat/err.h | 85 ++ openbsd-compat/explicit_bzero.c | 57 + openbsd-compat/explicit_bzero_win32.c | 19 + openbsd-compat/freezero.c | 30 + openbsd-compat/getopt.h | 74 ++ openbsd-compat/getopt_long.c | 523 ++++++++ openbsd-compat/hkdf.c | 124 ++ openbsd-compat/hkdf.h | 65 + openbsd-compat/openbsd-compat.h | 119 ++ openbsd-compat/posix_ioctl_check.c | 7 + openbsd-compat/posix_win.c | 61 + openbsd-compat/posix_win.h | 47 + openbsd-compat/readpassphrase.c | 214 ++++ openbsd-compat/readpassphrase.h | 44 + openbsd-compat/readpassphrase_win32.c | 131 ++ openbsd-compat/recallocarray.c | 91 ++ openbsd-compat/strlcat.c | 63 + openbsd-compat/strlcpy.c | 59 + openbsd-compat/time.h | 61 + openbsd-compat/timingsafe_bcmp.c | 35 + openbsd-compat/types.h | 69 ++ regress/CMakeLists.txt | 16 + regress/assert.c | 553 +++++++++ regress/cred.c | 988 +++++++++++++++ regress/dev.c | 266 ++++ src/CMakeLists.txt | 136 ++ src/aes256.c | 215 ++++ src/assert.c | 1134 +++++++++++++++++ src/authkey.c | 97 ++ src/bio.c | 841 +++++++++++++ src/blob.c | 133 ++ src/blob.h | 41 + src/buf.c | 33 + src/cbor.c | 1635 +++++++++++++++++++++++++ src/compress.c | 49 + src/config.c | 191 +++ src/cred.c | 1086 ++++++++++++++++ src/credman.c | 767 ++++++++++++ src/dev.c | 732 +++++++++++ src/diff_exports.sh | 26 + src/ecdh.c | 207 ++++ src/eddsa.c | 172 +++ src/err.c | 136 ++ src/es256.c | 453 +++++++ src/export.gnu | 234 ++++ src/export.llvm | 229 ++++ src/export.msvc | 230 ++++ src/extern.h | 240 ++++ src/fido.h | 228 ++++ src/fido/bio.h | 111 ++ src/fido/config.h | 34 + src/fido/credman.h | 91 ++ src/fido/eddsa.h | 54 + src/fido/err.h | 84 ++ src/fido/es256.h | 48 + src/fido/param.h | 117 ++ src/fido/rs256.h | 36 + src/fido/types.h | 281 +++++ src/hid.c | 179 +++ src/hid_freebsd.c | 253 ++++ src/hid_hidapi.c | 268 ++++ src/hid_linux.c | 375 ++++++ src/hid_netbsd.c | 338 +++++ src/hid_openbsd.c | 260 ++++ src/hid_osx.c | 571 +++++++++ src/hid_unix.c | 76 ++ src/hid_win.c | 540 ++++++++ src/info.c | 553 +++++++++ src/io.c | 288 +++++ src/iso7816.c | 64 + src/iso7816.h | 49 + src/largeblob.c | 881 +++++++++++++ src/libfido2.pc.in | 12 + src/log.c | 121 ++ src/netlink.c | 782 ++++++++++++ src/netlink.h | 44 + src/nfc_linux.c | 631 ++++++++++ src/packed.h | 22 + src/pin.c | 690 +++++++++++ src/random.c | 82 ++ src/reset.c | 43 + src/rs256.c | 200 +++ src/u2f.c | 820 +++++++++++++ src/winhello.c | 934 ++++++++++++++ tools/CMakeLists.txt | 77 ++ tools/assert_get.c | 316 +++++ tools/assert_verify.c | 192 +++ tools/base64.c | 134 ++ tools/bio.c | 277 +++++ tools/config.c | 149 +++ tools/cred_make.c | 242 ++++ tools/cred_verify.c | 181 +++ tools/credman.c | 329 +++++ tools/extern.h | 99 ++ tools/fido2-assert.c | 54 + tools/fido2-attach.sh | 14 + tools/fido2-cred.c | 52 + tools/fido2-detach.sh | 12 + tools/fido2-token.c | 107 ++ tools/fido2-unprot.sh | 75 ++ tools/include_check.sh | 21 + tools/largeblob.c | 593 +++++++++ tools/pin.c | 143 +++ tools/test.sh | 296 +++++ tools/token.c | 576 +++++++++ tools/util.c | 591 +++++++++ udev/70-u2f.rules | 217 ++++ udev/CMakeLists.txt | 7 + udev/check.sh | 31 + udev/fidodevs | 126 ++ udev/genrules.awk | 55 + windows/build.ps1 | 272 ++++ windows/libressl.gpg | Bin 0 -> 16425 bytes 198 files changed, 43610 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 NEWS create mode 100644 README.adoc create mode 100644 examples/CMakeLists.txt create mode 100644 examples/README.adoc create mode 100644 examples/assert.c create mode 100644 examples/cred.c create mode 100644 examples/extern.h create mode 100644 examples/info.c create mode 100644 examples/manifest.c create mode 100644 examples/reset.c create mode 100644 examples/retries.c create mode 100644 examples/select.c create mode 100644 examples/setpin.c create mode 100644 examples/util.c create mode 100644 fuzz/CMakeLists.txt create mode 100644 fuzz/Dockerfile create mode 100644 fuzz/Makefile create mode 100644 fuzz/README create mode 100755 fuzz/build-coverage create mode 100644 fuzz/dummy.h create mode 100644 fuzz/export.gnu create mode 100644 fuzz/functions.txt create mode 100644 fuzz/fuzz_assert.c create mode 100644 fuzz/fuzz_bio.c create mode 100644 fuzz/fuzz_cred.c create mode 100644 fuzz/fuzz_credman.c create mode 100644 fuzz/fuzz_hid.c create mode 100644 fuzz/fuzz_largeblob.c create mode 100644 fuzz/fuzz_mgmt.c create mode 100644 fuzz/fuzz_netlink.c create mode 100644 fuzz/libfuzzer.c create mode 100644 fuzz/mutator_aux.c create mode 100644 fuzz/mutator_aux.h create mode 100644 fuzz/preload-fuzz.c create mode 100644 fuzz/preload-snoop.c create mode 100644 fuzz/prng.c create mode 100644 fuzz/report.tgz create mode 100644 fuzz/summary.txt create mode 100644 fuzz/udev.c create mode 100644 fuzz/uniform_random.c create mode 100644 fuzz/wiredata_fido2.h create mode 100644 fuzz/wiredata_u2f.h create mode 100644 fuzz/wrap.c create mode 100644 fuzz/wrapped.sym create mode 100644 man/CMakeLists.txt create mode 100644 man/NOTES create mode 100644 man/dyc.css create mode 100644 man/eddsa_pk_new.3 create mode 100644 man/es256_pk_new.3 create mode 100644 man/fido2-assert.1 create mode 100644 man/fido2-cred.1 create mode 100644 man/fido2-token.1 create mode 100644 man/fido_assert_allow_cred.3 create mode 100644 man/fido_assert_new.3 create mode 100644 man/fido_assert_set_authdata.3 create mode 100644 man/fido_assert_verify.3 create mode 100644 man/fido_bio_dev_get_info.3 create mode 100644 man/fido_bio_enroll_new.3 create mode 100644 man/fido_bio_info_new.3 create mode 100644 man/fido_bio_template.3 create mode 100644 man/fido_cbor_info_new.3 create mode 100644 man/fido_cred_exclude.3 create mode 100644 man/fido_cred_new.3 create mode 100644 man/fido_cred_set_authdata.3 create mode 100644 man/fido_cred_verify.3 create mode 100644 man/fido_credman_metadata_new.3 create mode 100644 man/fido_dev_enable_entattest.3 create mode 100644 man/fido_dev_get_assert.3 create mode 100644 man/fido_dev_get_touch_begin.3 create mode 100644 man/fido_dev_info_manifest.3 create mode 100644 man/fido_dev_largeblob_get.3 create mode 100644 man/fido_dev_make_cred.3 create mode 100644 man/fido_dev_open.3 create mode 100644 man/fido_dev_set_io_functions.3 create mode 100644 man/fido_dev_set_pin.3 create mode 100644 man/fido_init.3 create mode 100644 man/fido_strerr.3 create mode 100644 man/rs256_pk_new.3 create mode 100644 man/style.css create mode 100644 openbsd-compat/bsd-getline.c create mode 100644 openbsd-compat/bsd-getpagesize.c create mode 100644 openbsd-compat/clock_gettime.c create mode 100644 openbsd-compat/endian_win32.c create mode 100644 openbsd-compat/err.h create mode 100644 openbsd-compat/explicit_bzero.c create mode 100644 openbsd-compat/explicit_bzero_win32.c create mode 100644 openbsd-compat/freezero.c create mode 100644 openbsd-compat/getopt.h create mode 100644 openbsd-compat/getopt_long.c create mode 100644 openbsd-compat/hkdf.c create mode 100644 openbsd-compat/hkdf.h create mode 100644 openbsd-compat/openbsd-compat.h create mode 100644 openbsd-compat/posix_ioctl_check.c create mode 100644 openbsd-compat/posix_win.c create mode 100644 openbsd-compat/posix_win.h create mode 100644 openbsd-compat/readpassphrase.c create mode 100644 openbsd-compat/readpassphrase.h create mode 100644 openbsd-compat/readpassphrase_win32.c create mode 100644 openbsd-compat/recallocarray.c create mode 100644 openbsd-compat/strlcat.c create mode 100644 openbsd-compat/strlcpy.c create mode 100644 openbsd-compat/time.h create mode 100644 openbsd-compat/timingsafe_bcmp.c create mode 100644 openbsd-compat/types.h create mode 100644 regress/CMakeLists.txt create mode 100644 regress/assert.c create mode 100644 regress/cred.c create mode 100644 regress/dev.c create mode 100644 src/CMakeLists.txt create mode 100644 src/aes256.c create mode 100644 src/assert.c create mode 100644 src/authkey.c create mode 100644 src/bio.c create mode 100644 src/blob.c create mode 100644 src/blob.h create mode 100644 src/buf.c create mode 100644 src/cbor.c create mode 100644 src/compress.c create mode 100644 src/config.c create mode 100644 src/cred.c create mode 100644 src/credman.c create mode 100644 src/dev.c create mode 100755 src/diff_exports.sh create mode 100644 src/ecdh.c create mode 100644 src/eddsa.c create mode 100644 src/err.c create mode 100644 src/es256.c create mode 100644 src/export.gnu create mode 100644 src/export.llvm create mode 100644 src/export.msvc create mode 100644 src/extern.h create mode 100644 src/fido.h create mode 100644 src/fido/bio.h create mode 100644 src/fido/config.h create mode 100644 src/fido/credman.h create mode 100644 src/fido/eddsa.h create mode 100644 src/fido/err.h create mode 100644 src/fido/es256.h create mode 100644 src/fido/param.h create mode 100644 src/fido/rs256.h create mode 100644 src/fido/types.h create mode 100644 src/hid.c create mode 100644 src/hid_freebsd.c create mode 100644 src/hid_hidapi.c create mode 100644 src/hid_linux.c create mode 100644 src/hid_netbsd.c create mode 100644 src/hid_openbsd.c create mode 100644 src/hid_osx.c create mode 100644 src/hid_unix.c create mode 100644 src/hid_win.c create mode 100644 src/info.c create mode 100644 src/io.c create mode 100644 src/iso7816.c create mode 100644 src/iso7816.h create mode 100644 src/largeblob.c create mode 100644 src/libfido2.pc.in create mode 100644 src/log.c create mode 100644 src/netlink.c create mode 100644 src/netlink.h create mode 100644 src/nfc_linux.c create mode 100644 src/packed.h create mode 100644 src/pin.c create mode 100644 src/random.c create mode 100644 src/reset.c create mode 100644 src/rs256.c create mode 100644 src/u2f.c create mode 100644 src/winhello.c create mode 100644 tools/CMakeLists.txt create mode 100644 tools/assert_get.c create mode 100644 tools/assert_verify.c create mode 100644 tools/base64.c create mode 100644 tools/bio.c create mode 100644 tools/config.c create mode 100644 tools/cred_make.c create mode 100644 tools/cred_verify.c create mode 100644 tools/credman.c create mode 100644 tools/extern.h create mode 100644 tools/fido2-assert.c create mode 100755 tools/fido2-attach.sh create mode 100644 tools/fido2-cred.c create mode 100755 tools/fido2-detach.sh create mode 100644 tools/fido2-token.c create mode 100755 tools/fido2-unprot.sh create mode 100755 tools/include_check.sh create mode 100644 tools/largeblob.c create mode 100644 tools/pin.c create mode 100755 tools/test.sh create mode 100644 tools/token.c create mode 100644 tools/util.c create mode 100644 udev/70-u2f.rules create mode 100644 udev/CMakeLists.txt create mode 100755 udev/check.sh create mode 100644 udev/fidodevs create mode 100755 udev/genrules.awk create mode 100644 windows/build.ps1 create mode 100644 windows/libressl.gpg diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000000..101b7b33e2fc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,418 @@ +# 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. + +# detect AppleClang; needs to come before project() +cmake_policy(SET CMP0025 NEW) + +project(libfido2 C) +cmake_minimum_required(VERSION 3.0) +# 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 "8") +set(FIDO_PATCH "0") +set(FIDO_VERSION ${FIDO_MAJOR}.${FIDO_MINOR}.${FIDO_PATCH}) + +option(BUILD_EXAMPLES "Build example programs" ON) +option(BUILD_MANPAGES "Build man pages" ON) +option(BUILD_SHARED_LIBS "Build the shared library" ON) +option(BUILD_STATIC_LIBS "Build the static library" ON) +option(BUILD_TOOLS "Build tool programs" ON) +option(FUZZ "Enable fuzzing instrumentation" OFF) +option(LIBFUZZER "Build libfuzzer harnesses" OFF) +option(USE_HIDAPI "Use hidapi as the HID backend" OFF) +option(USE_WINHELLO "Abstract Windows Hello as a FIDO device" OFF) +option(NFC_LINUX "Experimental NFC support on Linux" OFF) + +add_definitions(-D_FIDO_MAJOR=${FIDO_MAJOR}) +add_definitions(-D_FIDO_MINOR=${FIDO_MINOR}) +add_definitions(-D_FIDO_PATCH=${FIDO_PATCH}) + +if(CYGWIN OR MSYS) + set(WIN32 1) + add_definitions(-DWINVER=0x0a00) +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") + set(NFC_LINUX OFF) + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_GNU_SOURCE") + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_DEFAULT_SOURCE") + elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D__BSD_VISIBLE=1") + 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("-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_include_files("windows.h;webauthn.h" HAVE_WEBAUTHN_H) + +check_symbol_exists(arc4random_buf stdlib.h HAVE_ARC4RANDOM_BUF) +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(sigaction signal.h HAVE_SIGACTION) +check_symbol_exists(strlcat string.h HAVE_STRLCAT) +check_symbol_exists(strlcpy string.h HAVE_STRLCPY) +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_EXTRA_INCLUDE_FILES signal.h) +check_type_size("sig_atomic_t" HAVE_SIG_ATOMIC_T) +set(CMAKE_EXTRA_INCLUDE_FILES) + +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_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_SIGACTION + HAVE_SIGNAL_H + HAVE_STRLCAT + HAVE_STRLCPY + HAVE_SYSCONF + HAVE_SYS_RANDOM_H + HAVE_TIMESPECSUB + HAVE_TIMINGSAFE_BCMP + HAVE_UNISTD_H + HAVE_WEBAUTHN_H +) + +foreach(v ${CHECK_VARIABLES}) + if (${v}) + add_definitions(-D${v}) + endif() +endforeach() + +if(HAVE_EXPLICIT_BZERO AND NOT LIBFUZZER) + add_definitions(-DHAVE_EXPLICIT_BZERO) +endif() + +if(HAVE_SIGACTION AND (NOT HAVE_SIG_ATOMIC_T STREQUAL "")) + add_definitions(-DSIGNAL_EXAMPLE) +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 provide definitions for " + "{CBOR,CRYPTO,ZLIB}_{INCLUDE,LIBRARY}_DIRS when building " + "under msvc") + endif() + set(CBOR_LIBRARIES cbor) + set(ZLIB_LIBRARIES zlib) + set(CRYPTO_LIBRARIES crypto-46) + set(MSVC_DISABLED_WARNINGS_LIST + "C4200" # nonstandard extension used: zero-sized array in + # 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} /Z7 /guard:cf /sdl /RTCcsu") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zi /guard:cf /sdl") + if (HAVE_WEBAUTHN_H) + add_definitions(-DUSE_WINHELLO) + set(USE_WINHELLO ON) + endif() +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() + + set(CBOR_LIBRARIES "cbor") + set(CRYPTO_LIBRARIES "crypto") + + 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() + 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(USE_HIDAPI) + add_definitions(-DUSE_HIDAPI) + pkg_search_module(HIDAPI hidapi${HIDAPI_SUFFIX} REQUIRED) + set(HIDAPI_LIBRARIES hidapi${HIDAPI_SUFFIX}) + endif() + + if(FUZZ) + set(NFC_LINUX ON) + endif() + + if(NFC_LINUX) + add_definitions(-DNFC_LINUX) + 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(-pedantic) + add_compile_options(-pedantic-errors) + + 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(FUZZ) + add_definitions(-DFIDO_FUZZ) + endif() + if(LIBFUZZER) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=fuzzer-no-link") + 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}) + +# 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(${CMAKE_SOURCE_DIR}/src) +include_directories(${CBOR_INCLUDE_DIRS}) +include_directories(${CRYPTO_INCLUDE_DIRS}) +include_directories(${HIDAPI_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(${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}") +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_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_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}") +message(STATUS "CRYPTO_VERSION: ${CRYPTO_VERSION}") +message(STATUS "FIDO_VERSION: ${FIDO_VERSION}") +message(STATUS "FUZZ: ${FUZZ}") +message(STATUS "ZLIB_INCLUDE_DIRS: ${ZLIB_INCLUDE_DIRS}") +message(STATUS "ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}") +message(STATUS "ZLIB_LIBRARY_DIRS: ${ZLIB_LIBRARY_DIRS}") +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 "LIBFUZZER: ${LIBFUZZER}") +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_WINHELLO: ${USE_WINHELLO}") +message(STATUS "NFC_LINUX: ${NFC_LINUX}") + +subdirs(src) +if(BUILD_EXAMPLES) + subdirs(examples) +endif() +if(BUILD_TOOLS) + subdirs(tools) +endif() +if(BUILD_MANPAGES) + subdirs(man) +endif() + +if(NOT WIN32) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if(NOT LIBFUZZER AND NOT FUZZ) + subdirs(regress) + endif() + endif() + if(FUZZ) + subdirs(fuzz) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + subdirs(udev) + endif() +endif() diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..4224f20992c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +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. diff --git a/NEWS b/NEWS new file mode 100644 index 000000000000..a89766b72e89 --- /dev/null +++ b/NEWS @@ -0,0 +1,179 @@ +* 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 000000000000..f5ffa7e4e602 --- /dev/null +++ b/README.adoc @@ -0,0 +1,93 @@ +== 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, and to verify attestation and +assertion signatures. + +*libfido2* supports the FIDO U2F (CTAP 1) and FIDO 2.0 (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] + +=== Installation + +==== Releases + +The current release of *libfido2* is 1.8.0. Please consult Yubico's +https://developers.yubico.com/libfido2/Releases[release page] for source +and binary releases. + +==== Ubuntu 20.04 (Focal) + + $ sudo apt install libfido2-1 + $ sudo apt install libfido2-dev + $ sudo apt install libfido2-doc + +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-dev + +==== macOS + + $ brew install libfido2 + +Or from source, on UNIX-like systems: + + $ (rm -rf build && mkdir build && cd build && cmake ..) + $ 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. + +*libfido2* depends on https://github.com/pjk/libcbor[libcbor], +https://www.openssl.org[OpenSSL], and https://zlib.net[zlib]. On Linux, libudev +(part of https://www.freedesktop.org/wiki/Software/systemd[systemd]) is also +required. + +For complete, OS-specific installation instructions, please refer to the +`.actions/` (Linux, macOS) and `windows/` directories. + +On Linux, you will need to add a udev rule to be able to access the FIDO +device, or run as root. 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" +---- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000000..ad3d44faad6b --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,69 @@ +# Copyright (c) 2018 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/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() + +# set the library to link against +if(BUILD_STATIC_LIBS) + # drop -rdynamic + set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + set(_FIDO2_LIBRARY fido2) +elseif(BUILD_SHARED_LIBS) + set(_FIDO2_LIBRARY fido2_shared) +else() + set(_FIDO2_LIBRARY ${CRYPTO_LIBRARIES} fido2) +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 000000000000..bcecb22f5258 --- /dev/null +++ b/examples/README.adoc @@ -0,0 +1,98 @@ += Examples + +=== Definitions + +The following definitions are used in the description below: + +- + + The file system path or subsystem-specific identification string of a + FIDO device. + +- , [oldpin] + + Strings passed directly in the executed command's argument vector. + +- + + The file system path of a file containing a FIDO credential ID in + binary representation. + +- + + The file system path of a file containing a NIST P-256 public key in + PEM format. + +- + + A credential's associated FIDO 2.1 "largeBlob" symmetric key. + +=== Description + +The following examples are provided: + +- manifest + + Prints a list of configured FIDO devices. + +- info + + Prints information about . + +- reset + + Performs a factory reset on . + +- setpin [oldpin] + + Configures as the new PIN of . If [oldpin] is provided, + the device's PIN is changed from [oldpin] to . + +- cred [-t ecdsa|rsa|eddsa] [-k pubkey] [-ei cred_id] [-P pin] [-T seconds] + [-b blobkey] [-hruv] + + Creates a new credential on 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 . If option -i is specified, the credential + ID is stored in . The -e option may be used to add + 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 . If the + option -b is specified, the credential's "largeBlob" key is stored in + . + +- assert [-t ecdsa|rsa|eddsa] [-a cred_id] [-h hmac_secret] [-s hmac_salt] + [-P pin] [-T seconds] [-b blobkey] [-puv] + + Asks for a FIDO2 assertion corresponding to [cred_id], + which may be omitted for resident keys. The obtained assertion + is verified using . The -p option requests that the user + be present. User verification may be requested through the -v + option. 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 are used as the salt. If option -h + is specified, the resulting hmac-secret is stored in . + The -T option may be used to enforce a timeout of . If the + option -b specified, the credential's "largeBlob" key is stored in + . + +- retries + Get the number of PIN attempts left on 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 000000000000..dc3fda3ac447 --- /dev/null +++ b/examples/assert.c @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static 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 void +usage(void) +{ + fprintf(stderr, "usage: assert [-t ecdsa|rsa|eddsa] [-a cred_id] " + "[-h hmac_secret] [-s hmac_salt] [-P pin] [-T seconds] " + "[-b blobkey] [-puv] \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; + 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_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_hash(assert, cdh, sizeof(cdh)); + if (r != FIDO_OK) + errx(1, "fido_assert_set_clientdata_hash: %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); + 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 seconds = 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': +#ifndef SIGNAL_EXAMPLE + (void)seconds; + errx(1, "-T not supported"); +#else + if (base10(optarg, &seconds) < 0) + errx(1, "base10: %s", optarg); + if (seconds <= 0 || seconds > 30) + errx(1, "-T: %s must be in (0,30]", optarg); + break; +#endif + 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, "ecdsa") == 0) + type = COSE_ES256; + else if (strcmp(optarg, "rsa") == 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_hash(assert, cdh, sizeof(cdh)); + if (r != FIDO_OK) + errx(1, "fido_assert_set_clientdata_hash: %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); + +#ifdef SIGNAL_EXAMPLE + prepare_signal_handler(SIGINT); + if (seconds) { + prepare_signal_handler(SIGALRM); + alarm((unsigned)seconds); + } +#endif + + r = fido_dev_get_assert(dev, assert, pin); + if (r != FIDO_OK) { +#ifdef SIGNAL_EXAMPLE + if (got_signal) + fido_dev_cancel(dev); +#endif + 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 000000000000..74145c761380 --- /dev/null +++ b/examples/cred.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +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 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 ecdsa|rsa|eddsa] [-k pubkey] " + "[-ei cred_id] [-P pin] [-T seconds] [-b blobkey] [-hruv] " + "\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 *x509_ptr, size_t x509_len, + const unsigned char *sig_ptr, size_t sig_len, bool rk, bool uv, int ext, + 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 hash */ + r = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); + if (r != FIDO_OK) + errx(1, "fido_cred_set_clientdata_hash: %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); + + /* 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; + } + + /* x509 */ + r = fido_cred_set_x509(cred, x509_ptr, x509_len); + if (r != FIDO_OK) + errx(1, "fido_cred_set_x509: %s (0x%x)", fido_strerr(r), r); + + /* sig */ + r = fido_cred_set_sig(cred, sig_ptr, sig_len); + if (r != FIDO_OK) + errx(1, "fido_cred_set_sig: %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_ec_pubkey(key_out, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)) < 0) + errx(1, "write_ec_pubkey"); + } else if (type == COSE_RS256) { + if (write_rsa_pubkey(key_out, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)) < 0) + errx(1, "write_rsa_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); +} + +static fido_dev_t * +open_from_manifest(const fido_dev_info_t *dev_infos, size_t len, + const char *path) +{ + size_t i; + fido_dev_t *dev; + + for (i = 0; i < len; i++) { + const fido_dev_info_t *curr = fido_dev_info_ptr(dev_infos, i); + if (path == NULL || + strcmp(path, fido_dev_info_path(curr)) == 0) { + dev = fido_dev_new_with_info(curr); + if (fido_dev_open_with_info(dev) == FIDO_OK) + return (dev); + fido_dev_free(&dev); + } + } + + return (NULL); +} + +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; + const char *path = NULL; + unsigned char *body = NULL; + long long seconds = 0; + size_t len; + int type = COSE_ES256; + int ext = 0; + int ch; + int r; + fido_dev_info_t *dev_infos = NULL; + size_t dev_infos_len = 0; + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + while ((ch = getopt(argc, argv, "P:T:b:e:hi:k:rt:uv")) != -1) { + switch (ch) { + case 'P': + pin = optarg; + break; + case 'T': +#ifndef SIGNAL_EXAMPLE + (void)seconds; + errx(1, "-T not supported"); +#else + if (base10(optarg, &seconds) < 0) + errx(1, "base10: %s", optarg); + if (seconds <= 0 || seconds > 30) + errx(1, "-T: %s must be in (0,30]", optarg); + break; +#endif + 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 'i': + id_out = optarg; + break; + case 'k': + key_out = optarg; + break; + case 'r': + rk = true; + break; + case 't': + if (strcmp(optarg, "ecdsa") == 0) + type = COSE_ES256; + else if (strcmp(optarg, "rsa") == 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(); + } + } + + fido_init(0); + + argc -= optind; + argv += optind; + + if (argc > 1) + usage(); + dev_infos = fido_dev_info_new(16); + fido_dev_info_manifest(dev_infos, 16, &dev_infos_len); + if (argc == 1) + path = argv[0]; + + if ((dev = open_from_manifest(dev_infos, dev_infos_len, path)) == NULL) + errx(1, "open_from_manifest"); + + 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 hash */ + r = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); + if (r != FIDO_OK) + errx(1, "fido_cred_set_clientdata_hash: %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); + +#ifdef SIGNAL_EXAMPLE + prepare_signal_handler(SIGINT); + if (seconds) { + prepare_signal_handler(SIGALRM); + alarm((unsigned)seconds); + } +#endif + + r = fido_dev_make_cred(dev, cred, pin); + if (r != FIDO_OK) { +#ifdef SIGNAL_EXAMPLE + if (got_signal) + fido_dev_cancel(dev); +#endif + 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_x5c_ptr(cred), + fido_cred_x5c_len(cred), fido_cred_sig_ptr(cred), + fido_cred_sig_len(cred), rk, uv, ext, 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 000000000000..0ea68c4fb585 --- /dev/null +++ b/examples/extern.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +#include +#include +#include + +#ifdef HAVE_SIGNAL_H +#include +#endif + +/* 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_ec_pubkey(const char *, const void *, size_t); +int write_rsa_pubkey(const char *, const void *, size_t); +int write_eddsa_pubkey(const char *, const void *, size_t); +#ifdef SIGNAL_EXAMPLE +void prepare_signal_handler(int); +extern volatile sig_atomic_t got_signal; +#endif + +#endif /* _EXTERN_H_ */ diff --git a/examples/info.c b/examples/info.c new file mode 100644 index 000000000000..72b786a8bd83 --- /dev/null +++ b/examples/info.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" + +/* + * 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 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_EDDSA: + cose = "eddsa"; + break; + case COSE_ES256: + cose = "es256"; + break; + case COSE_RS256: + cose = "rs256"; + 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 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 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 firmware version */ + print_fwversion(fido_cbor_info_fwversion(ci)); + + /* print supported pin protocols */ + print_byte_array("pin protocols", fido_cbor_info_protocols_ptr(ci), + fido_cbor_info_protocols_len(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 \n"); + exit(EXIT_FAILURE); + } + + getinfo(argv[1]); + + exit(0); +} diff --git a/examples/manifest.c b/examples/manifest.c new file mode 100644 index 000000000000..d38166a9fea9 --- /dev/null +++ b/examples/manifest.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include + +#include "../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 000000000000..eb341c26c0cd --- /dev/null +++ b/examples/reset.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. + */ + +/* + * Perform a factory reset on a given authenticator. + */ + +#include +#include +#include + +#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 \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); + +#ifdef SIGNAL_EXAMPLE + prepare_signal_handler(SIGINT); +#endif + + if ((r = fido_dev_reset(dev)) != FIDO_OK) { +#ifdef SIGNAL_EXAMPLE + if (got_signal) + fido_dev_cancel(dev); +#endif + errx(1, "fido_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 000000000000..b96118b1e154 --- /dev/null +++ b/examples/retries.c @@ -0,0 +1,48 @@ +/* + * 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. + */ + +/* + * Get an authenticator's number of PIN attempts left. + */ + +#include +#include +#include + +#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 \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_get_retries: %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 000000000000..6ede9b490a95 --- /dev/null +++ b/examples/select.c @@ -0,0 +1,214 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#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(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 000000000000..4b9e792769d9 --- /dev/null +++ b/examples/setpin.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/* + * Configure a PIN on a given authenticator. + */ + +#include +#include +#include + +#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_setpin: %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 [oldpin] \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 000000000000..caa68aa880ee --- /dev/null +++ b/examples/util.c @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#ifdef HAVE_SIGNAL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef _MSC_VER +#include "../openbsd-compat/posix_win.h" +#endif +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +#ifdef SIGNAL_EXAMPLE +volatile sig_atomic_t got_signal = 0; + +static void +signal_handler(int signo) +{ + (void)signo; + got_signal = 1; +} + +void +prepare_signal_handler(int signo) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + + sigemptyset(&sa.sa_mask); + sa.sa_handler = signal_handler; + + if (sigaction(signo, &sa, NULL) < 0) + err(1, "sigaction"); +} +#endif + +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_ec_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); +} + +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_rsa_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 000000000000..b1eebd55481b --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -0,0 +1,63 @@ +# 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. + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/strlcpy.c + ../openbsd-compat/strlcat.c +) + +list(APPEND COMMON_SOURCES + libfuzzer.c + mutator_aux.c +) + +set(FUZZ_LDFLAGS "-fsanitize=fuzzer") + +# fuzz_cred +add_executable(fuzz_cred fuzz_cred.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_cred PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_cred PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_cred fido2_shared) + +# fuzz_assert +add_executable(fuzz_assert fuzz_assert.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_assert PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_assert PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_assert fido2_shared) + +# fuzz_mgmt +add_executable(fuzz_mgmt fuzz_mgmt.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_mgmt PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_mgmt PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_mgmt fido2_shared) + +# fuzz_credman +add_executable(fuzz_credman fuzz_credman.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_credman PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_credman PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_credman fido2_shared) + +# fuzz_bio +add_executable(fuzz_bio fuzz_bio.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_bio PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_bio PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_bio fido2_shared) + +# fuzz_hid +add_executable(fuzz_hid fuzz_hid.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_hid PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_hid PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_hid fido2_shared) + +# fuzz_netlink +add_executable(fuzz_netlink fuzz_netlink.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_netlink PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_netlink PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_netlink fido2_shared) + +# fuzz_largeblob +add_executable(fuzz_largeblob fuzz_largeblob.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +target_compile_options(fuzz_largeblob PRIVATE ${FUZZ_LDFLAGS}) +set_target_properties(fuzz_largeblob PROPERTIES LINK_FLAGS ${FUZZ_LDFLAGS}) +target_link_libraries(fuzz_largeblob fido2_shared) diff --git a/fuzz/Dockerfile b/fuzz/Dockerfile new file mode 100644 index 000000000000..895da69e4c4c --- /dev/null +++ b/fuzz/Dockerfile @@ -0,0 +1,12 @@ +# 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. + +FROM ubuntu:focal +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update +RUN apt-get install -y clang-11 cmake git libssl-dev libudev-dev make pkg-config +RUN apt-get install -y zlib1g-dev +RUN git clone --branch v0.8.0 https://github.com/PJK/libcbor +RUN git clone https://github.com/yubico/libfido2 +RUN CC=clang-11 CXX=clang++-11 /libfido2/fuzz/build-coverage /libcbor /libfido2 diff --git a/fuzz/Makefile b/fuzz/Makefile new file mode 100644 index 000000000000..4b067c23aac2 --- /dev/null +++ b/fuzz/Makefile @@ -0,0 +1,79 @@ +# 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. + +IMAGE := libfido2-coverage:1.8.0 +RUNNER := libfido2-runner +PROFDATA := llvm-profdata-11 +COV := llvm-cov-11 +TARGETS := fuzz_assert fuzz_bio fuzz_cred fuzz_credman fuzz_hid \ + fuzz_largeblob fuzz_netlink fuzz_mgmt +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/$ $@ + +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=/$< \ + -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 \ + /libfido2/build/src/libfido2.so -instr-profile=/$< > $@ + +functions.txt: profdata + docker exec ${RUNNER} /bin/sh -c '${COV} report -use-color=false \ + -show-functions -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=) $@ + +corpus.tgz: ${CORPORA} + tar zcf $@ ${TARGETS} + +.PHONY: build run sync corpus ${TARGETS} ${CORPORA} +.PHONY: report.tgz summary.txt functions.txt diff --git a/fuzz/README b/fuzz/README new file mode 100644 index 000000000000..28fc7f8f51b2 --- /dev/null +++ b/fuzz/README @@ -0,0 +1,33 @@ +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 -DFUZZ=ON -DLIBFUZZER=ON. + +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. + +diff --git src/cbor/internal/memory_utils.c src/cbor/internal/memory_utils.c +index aa049a2..e294b38 100644 +--- src/cbor/internal/memory_utils.c ++++ src/cbor/internal/memory_utils.c +@@ -28,7 +28,10 @@ bool _cbor_safe_to_multiply(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 000000000000..e0e90da02b5d --- /dev/null +++ b/fuzz/build-coverage @@ -0,0 +1,31 @@ +#!/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. + +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 LDFLAGS="${CFLAGS}" +(cd "${LIBFIDO2}/build" && cmake -DFUZZ=ON -DLIBFUZZER=ON \ + -DCMAKE_BUILD_TYPE=Debug ..) +make -C "${LIBFIDO2}/build" diff --git a/fuzz/dummy.h b/fuzz/dummy.h new file mode 100644 index 000000000000..981cceec37b5 --- /dev/null +++ b/fuzz/dummy.h @@ -0,0 +1,96 @@ +/* + * 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. + */ + +#ifndef _DUMMY_H +#define _DUMMY_H + +#include + +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 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, +}; + +#endif /* !_DUMMY_H */ diff --git a/fuzz/export.gnu b/fuzz/export.gnu new file mode 100644 index 000000000000..bd70d1c7eaac --- /dev/null +++ b/fuzz/export.gnu @@ -0,0 +1,242 @@ +{ + 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_ptr; + es256_pk_new; + es256_pk_to_EVP_PKEY; + fido_assert_allow_cred; + fido_assert_authdata_len; + fido_assert_authdata_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_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_extensions_len; + fido_cbor_info_extensions_ptr; + fido_cbor_info_free; + fido_cbor_info_maxmsgsiz; + fido_cbor_info_maxcredbloblen; + fido_cbor_info_maxcredcntlst; + fido_cbor_info_maxcredidlen; + fido_cbor_info_fwversion; + fido_cbor_info_new; + 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_transports_len; + fido_cbor_info_transports_ptr; + fido_cbor_info_versions_len; + fido_cbor_info_versions_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_prot; + fido_cred_pubkey_len; + fido_cred_pubkey_ptr; + fido_cred_rp_id; + fido_cred_rp_name; + fido_cred_set_authdata; + fido_cred_set_authdata_raw; + fido_cred_set_blob; + fido_cred_set_clientdata_hash; + fido_cred_set_extensions; + fido_cred_set_fmt; + fido_cred_set_id; + fido_cred_set_options; + 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_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_pin; + fido_dev_set_pin_minlen; + 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_set_log_handler; + fido_strerr; + rs256_pk_free; + rs256_pk_from_ptr; + rs256_pk_from_RSA; + rs256_pk_new; + rs256_pk_to_EVP_PKEY; + prng_init; + set_netlink_io_functions; + set_udev_parameters; + uniform_random; + local: + *; +}; diff --git a/fuzz/functions.txt b/fuzz/functions.txt new file mode 100644 index 000000000000..28fe4f6af17b --- /dev/null +++ b/fuzz/functions.txt @@ -0,0 +1,807 @@ +File '/libfido2/src/aes256.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------ +aes256_cbc_enc 3 0 100.00% 4 0 100.00% +aes256_cbc_dec 3 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 2 92.31% 45 7 84.44% +aes256.c:aes256_cbc 29 1 96.55% 40 3 92.50% +aes256.c:aes256_cbc_proto1 1 0 100.00% 7 0 100.00% +aes256.c:aes256_gcm 51 1 98.04% 69 4 94.20% +------------------------------------------------------------------------------ +TOTAL 115 4 96.52% 175 14 92.00% + +File '/libfido2/src/assert.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------- +fido_dev_get_assert 40 0 100.00% 41 0 100.00% +fido_check_flags 13 0 100.00% 18 0 100.00% +fido_get_signed_hash 32 0 100.00% 46 0 100.00% +fido_verify_sig_es256 17 2 88.24% 31 7 77.42% +fido_verify_sig_rs256 17 2 88.24% 31 7 77.42% +fido_verify_sig_eddsa 23 2 91.30% 43 7 83.72% +fido_assert_verify 48 4 91.67% 79 5 93.67% +fido_assert_set_clientdata 12 12 0.00% 12 12 0.00% +fido_assert_set_clientdata_hash 8 0 100.00% 7 0 100.00% +fido_assert_set_hmac_salt 10 0 100.00% 7 0 100.00% +fido_assert_set_hmac_secret 12 12 0.00% 8 8 0.00% +fido_assert_set_rp 12 0 100.00% 14 0 100.00% +fido_assert_allow_cred 13 2 84.62% 29 3 89.66% +fido_assert_set_extensions 14 0 100.00% 11 0 100.00% +fido_assert_set_options 6 6 0.00% 6 6 0.00% +fido_assert_set_up 2 0 100.00% 5 0 100.00% +fido_assert_set_uv 2 0 100.00% 5 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% 12 0 100.00% +fido_assert_reset_rx 4 0 100.00% 19 0 100.00% +fido_assert_free 6 0 100.00% 10 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% 6 0 100.00% +fido_assert_sigcount 4 0 100.00% 6 0 100.00% +fido_assert_authdata_ptr 4 0 100.00% 6 0 100.00% +fido_assert_authdata_len 4 0 100.00% 6 0 100.00% +fido_assert_sig_ptr 4 0 100.00% 6 0 100.00% +fido_assert_sig_len 4 0 100.00% 6 0 100.00% +fido_assert_id_ptr 4 0 100.00% 6 0 100.00% +fido_assert_id_len 4 0 100.00% 6 0 100.00% +fido_assert_user_id_ptr 4 0 100.00% 6 0 100.00% +fido_assert_user_id_len 4 0 100.00% 6 0 100.00% +fido_assert_user_icon 4 0 100.00% 6 0 100.00% +fido_assert_user_name 4 0 100.00% 6 0 100.00% +fido_assert_user_display_name 4 0 100.00% 6 0 100.00% +fido_assert_hmac_secret_ptr 4 0 100.00% 6 0 100.00% +fido_assert_hmac_secret_len 4 0 100.00% 6 0 100.00% +fido_assert_largeblob_key_ptr 4 0 100.00% 6 0 100.00% +fido_assert_largeblob_key_len 4 0 100.00% 6 0 100.00% +fido_assert_blob_ptr 4 0 100.00% 6 0 100.00% +fido_assert_blob_len 4 0 100.00% 6 0 100.00% +fido_assert_set_authdata 24 0 100.00% 35 0 100.00% +fido_assert_set_authdata_raw 24 0 100.00% 34 0 100.00% +fido_assert_set_sig 14 0 100.00% 8 0 100.00% +fido_assert_set_count 10 0 100.00% 21 0 100.00% +assert.c:fido_dev_get_assert_wait 21 0 100.00% 16 0 100.00% +assert.c:fido_dev_get_assert_tx 56 2 96.43% 77 5 93.51% +assert.c:fido_dev_get_assert_rx 19 0 100.00% 38 0 100.00% +assert.c:adjust_assert_count 24 0 100.00% 33 0 100.00% +assert.c:parse_assert_reply 12 0 100.00% 26 0 100.00% +assert.c:fido_get_next_assert_tx 8 0 100.00% 10 0 100.00% +assert.c:fido_get_next_assert_rx 15 2 86.67% 26 4 84.62% +assert.c:decrypt_hmac_secrets 9 0 100.00% 16 0 100.00% +assert.c:check_extensions 5 0 100.00% 11 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% 5 0 100.00% +--------------------------------------------------------------------------------------- +TOTAL 616 46 92.53% 924 64 93.07% + +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% 9 0 100.00% +authkey.c:fido_dev_authkey_tx 19 0 100.00% 33 0 100.00% +authkey.c:fido_dev_authkey_rx 6 0 100.00% 18 0 100.00% +authkey.c:parse_authkey 8 0 100.00% 12 0 100.00% +--------------------------------------------------------------------------------------- +TOTAL 44 0 100.00% 75 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 0 100.00% +fido_bio_dev_set_template_name 7 0 100.00% 6 0 100.00% +fido_bio_dev_enroll_begin 25 2 92.00% 37 0 100.00% +fido_bio_dev_enroll_continue 5 2 60.00% 6 0 100.00% +fido_bio_dev_enroll_cancel 1 1 0.00% 3 3 0.00% +fido_bio_dev_enroll_remove 1 0 100.00% 3 0 100.00% +fido_bio_dev_get_info 1 0 100.00% 3 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% 10 0 100.00% +fido_bio_template_free 6 0 100.00% 10 0 100.00% +fido_bio_template_set_name 8 0 100.00% 9 0 100.00% +fido_bio_template_set_id 8 0 100.00% 8 0 100.00% +fido_bio_template 4 0 100.00% 6 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% 11 0 100.00% +fido_bio_info_free 6 0 100.00% 9 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% 9 0 100.00% +bio.c:bio_tx 43 0 100.00% 66 0 100.00% +bio.c:bio_prepare_hmac 18 0 100.00% 36 0 100.00% +bio.c:bio_rx_template_array 11 0 100.00% 21 0 100.00% +bio.c:bio_parse_template_array 26 1 96.15% 34 4 88.24% +bio.c:decode_template_array 12 1 91.67% 23 3 86.96% +bio.c:decode_template 9 0 100.00% 18 0 100.00% +bio.c:bio_set_template_name_wait 19 0 100.00% 24 0 100.00% +bio.c:bio_enroll_begin_wait 17 0 100.00% 24 0 100.00% +bio.c:bio_rx_enroll_begin 15 0 100.00% 29 0 100.00% +bio.c:bio_parse_enroll_status 20 0 100.00% 31 0 100.00% +bio.c:bio_parse_template_id 8 0 100.00% 12 0 100.00% +bio.c:bio_enroll_continue_wait 19 0 100.00% 25 0 100.00% +bio.c:bio_rx_enroll_continue 11 0 100.00% 22 0 100.00% +bio.c:bio_enroll_cancel_wait 11 11 0.00% 12 12 0.00% +bio.c:bio_enroll_remove_wait 17 0 100.00% 24 0 100.00% +bio.c:bio_get_info_wait 11 0 100.00% 11 0 100.00% +bio.c:bio_rx_info 11 0 100.00% 21 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% 31 0 100.00% +bio.c:bio_reset_template_array 4 0 100.00% 8 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% 7 0 100.00% +--------------------------------------------------------------------------------------- +TOTAL 419 20 95.23% 660 22 96.67% + +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% 19 0 100.00% +fido_blob_append 12 2 83.33% 22 6 72.73% +fido_blob_free 6 0 100.00% 10 0 100.00% +fido_free_blob_array 7 0 100.00% 14 0 100.00% +fido_blob_encode 6 0 100.00% 6 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% 12 1 91.67% +--------------------------------------------------------------------------------------- +TOTAL 53 3 94.34% 96 7 92.71% + +File '/libfido2/src/buf.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------- +fido_buf_read 4 0 100.00% 10 0 100.00% +fido_buf_write 4 1 75.00% 10 1 90.00% +--------------------------------------------------------------------------------------- +TOTAL 8 1 87.50% 20 1 95.00% + +File '/libfido2/src/cbor.c': +Name Regions Miss Cover Lines Miss Cover +---------------------------------------------------------------------------------------- +cbor_map_iter 20 1 95.00% 30 4 86.67% +cbor_array_iter 12 0 100.00% 20 0 100.00% +cbor_parse_reply 27 0 100.00% 43 0 100.00% +cbor_vector_free 6 0 100.00% 5 0 100.00% +cbor_bytestring_copy 14 0 100.00% 22 0 100.00% +cbor_string_copy 14 0 100.00% 23 0 100.00% +cbor_add_bytestring 14 0 100.00% 26 0 100.00% +cbor_add_string 14 0 100.00% 26 0 100.00% +cbor_add_bool 14 0 100.00% 26 0 100.00% +cbor_flatten_vector 14 1 92.86% 21 1 95.24% +cbor_build_frame 15 0 100.00% 32 0 100.00% +cbor_encode_rp_entity 13 0 100.00% 14 0 100.00% +cbor_encode_user_entity 21 0 100.00% 18 0 100.00% +cbor_encode_pubkey_param 36 0 100.00% 48 0 100.00% +cbor_encode_pubkey 10 0 100.00% 13 0 100.00% +cbor_encode_pubkey_list 18 0 100.00% 23 0 100.00% +cbor_encode_cred_ext 46 0 100.00% 46 0 100.00% +cbor_encode_cred_opt 13 0 100.00% 13 0 100.00% +cbor_encode_assert_opt 13 0 100.00% 13 0 100.00% +cbor_encode_pin_auth 20 1 95.00% 30 3 90.00% +cbor_encode_pin_opt 4 0 100.00% 10 0 100.00% +cbor_encode_change_pin_auth 33 1 96.97% 49 3 93.88% +cbor_encode_assert_ext 33 0 100.00% 35 0 100.00% +cbor_decode_fmt 11 0 100.00% 19 0 100.00% +cbor_decode_pubkey 21 1 95.24% 32 2 93.75% +cbor_decode_cred_authdata 31 1 96.77% 45 3 93.33% +cbor_decode_assert_authdata 21 0 100.00% 42 0 100.00% +cbor_decode_attstmt 8 0 100.00% 10 0 100.00% +cbor_decode_uint64 4 0 100.00% 10 0 100.00% +cbor_decode_cred_id 8 0 100.00% 10 0 100.00% +cbor_decode_user 8 0 100.00% 10 0 100.00% +cbor_decode_rp_entity 8 0 100.00% 10 0 100.00% +cbor_build_uint 10 4 60.00% 10 5 50.00% +cbor_array_append 17 0 100.00% 23 0 100.00% +cbor_array_drop 18 2 88.89% 19 3 84.21% +cbor.c:ctap_check_cbor 28 0 100.00% 32 0 100.00% +cbor.c:check_key_type 8 0 100.00% 9 0 100.00% +cbor.c:cbor_add_arg 13 0 100.00% 28 0 100.00% +cbor.c:cbor_add_uint8 14 0 100.00% 26 0 100.00% +cbor.c:cbor_encode_largeblob_key_ext 6 0 100.00% 7 0 100.00% +cbor.c:cbor_encode_hmac_secret_param 53 2 96.23% 75 4 94.67% +cbor.c:get_cose_alg 36 0 100.00% 48 0 100.00% +cbor.c:find_cose_alg 35 0 100.00% 40 0 100.00% +cbor.c:decode_attcred 25 0 100.00% 56 0 100.00% +cbor.c:decode_cred_extensions 14 0 100.00% 31 0 100.00% +cbor.c:decode_cred_extension 40 3 92.50% 45 9 80.00% +cbor.c:decode_assert_extensions 14 0 100.00% 29 0 100.00% +cbor.c:decode_assert_extension 19 0 100.00% 31 0 100.00% +cbor.c:decode_attstmt_entry 38 0 100.00% 44 0 100.00% +cbor.c:decode_x5c 4 0 100.00% 8 0 100.00% +cbor.c:decode_cred_id_entry 10 0 100.00% 23 0 100.00% +cbor.c:decode_user_entry 25 0 100.00% 39 0 100.00% +cbor.c:decode_rp_entity_entry 15 0 100.00% 29 0 100.00% +---------------------------------------------------------------------------------------- +TOTAL 986 17 98.28% 1426 37 97.41% + +File '/libfido2/src/compress.c': +Name Regions Miss Cover Lines Miss Cover +---------------------------------------------------------------------------------------- +fido_compress 1 0 100.00% 3 0 100.00% +fido_uncompress 1 0 100.00% 3 0 100.00% +compress.c:do_compress 32 4 87.50% 24 3 87.50% +---------------------------------------------------------------------------------------- +TOTAL 34 4 88.24% 30 3 90.00% + +File '/libfido2/src/config.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_dev_enable_entattest 1 0 100.00% 3 0 100.00% +fido_dev_toggle_always_uv 1 0 100.00% 3 0 100.00% +fido_dev_set_pin_minlen 1 0 100.00% 3 0 100.00% +fido_dev_force_pin_change 1 0 100.00% 3 0 100.00% +config.c:config_enable_entattest_wait 6 0 100.00% 8 0 100.00% +config.c:config_tx 37 0 100.00% 57 0 100.00% +config.c:config_prepare_hmac 8 1 87.50% 22 3 86.36% +config.c:config_toggle_always_uv_wait 6 0 100.00% 8 0 100.00% +config.c:config_pin_minlen 5 0 100.00% 8 0 100.00% +config.c:config_pin_minlen_tx 28 0 100.00% 31 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 94 1 98.94% 146 3 97.95% + +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% 14 0 100.00% +fido_cred_verify 50 4 92.00% 75 8 89.33% +fido_cred_verify_self 58 6 89.66% 94 10 89.36% +fido_cred_new 1 0 100.00% 3 0 100.00% +fido_cred_reset_tx 1 0 100.00% 22 0 100.00% +fido_cred_reset_rx 1 0 100.00% 8 0 100.00% +fido_cred_free 6 0 100.00% 10 0 100.00% +fido_cred_set_authdata 23 0 100.00% 37 0 100.00% +fido_cred_set_authdata_raw 25 0 100.00% 38 0 100.00% +fido_cred_set_id 6 0 100.00% 6 0 100.00% +fido_cred_set_x509 6 0 100.00% 6 0 100.00% +fido_cred_set_sig 6 0 100.00% 6 0 100.00% +fido_cred_exclude 14 2 85.71% 25 3 88.00% +fido_cred_set_clientdata 12 12 0.00% 12 12 0.00% +fido_cred_set_clientdata_hash 8 0 100.00% 7 0 100.00% +fido_cred_set_rp 18 0 100.00% 26 0 100.00% +fido_cred_set_user 32 0 100.00% 46 0 100.00% +fido_cred_set_extensions 15 0 100.00% 11 0 100.00% +fido_cred_set_options 6 6 0.00% 6 6 0.00% +fido_cred_set_rk 2 0 100.00% 5 0 100.00% +fido_cred_set_uv 2 0 100.00% 5 0 100.00% +fido_cred_set_prot 21 0 100.00% 16 0 100.00% +fido_cred_set_blob 13 2 84.62% 10 1 90.00% +fido_cred_set_fmt 18 4 77.78% 16 1 93.75% +fido_cred_set_type 17 0 100.00% 9 0 100.00% +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_pubkey_ptr 9 0 100.00% 20 0 100.00% +fido_cred_pubkey_len 9 0 100.00% 20 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_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% 9 0 100.00% +cred.c:fido_dev_make_cred_tx 64 0 100.00% 85 0 100.00% +cred.c:fido_dev_make_cred_rx 19 0 100.00% 27 0 100.00% +cred.c:parse_makecred_reply 14 0 100.00% 29 0 100.00% +cred.c:check_extensions 2 0 100.00% 9 0 100.00% +cred.c:get_signed_hash_u2f 22 0 100.00% 20 0 100.00% +cred.c:verify_sig 27 2 92.59% 40 7 82.50% +cred.c:fido_cred_clean_authdata 1 0 100.00% 9 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 581 38 93.46% 872 48 94.50% + +File '/libfido2/src/credman.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_credman_get_dev_metadata 1 0 100.00% 3 0 100.00% +fido_credman_get_dev_rk 1 0 100.00% 3 0 100.00% +fido_credman_del_dev_rk 1 0 100.00% 3 0 100.00% +fido_credman_get_dev_rp 1 0 100.00% 3 0 100.00% +fido_credman_set_dev_rk 1 0 100.00% 3 0 100.00% +fido_credman_rk_new 1 0 100.00% 3 0 100.00% +fido_credman_rk_free 6 1 83.33% 10 0 100.00% +fido_credman_rk_count 1 0 100.00% 3 0 100.00% +fido_credman_rk 4 0 100.00% 6 0 100.00% +fido_credman_metadata_new 1 0 100.00% 3 0 100.00% +fido_credman_metadata_free 6 1 83.33% 9 0 100.00% +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% 10 0 100.00% +fido_credman_rp_count 1 0 100.00% 3 0 100.00% +fido_credman_rp_id 4 0 100.00% 6 0 100.00% +fido_credman_rp_name 4 0 100.00% 6 0 100.00% +fido_credman_rp_id_hash_len 4 0 100.00% 6 0 100.00% +fido_credman_rp_id_hash_ptr 4 0 100.00% 6 0 100.00% +credman.c:credman_get_metadata_wait 11 0 100.00% 10 0 100.00% +credman.c:credman_tx 36 0 100.00% 60 0 100.00% +credman.c:credman_prepare_hmac 31 1 96.77% 56 2 96.43% +credman.c:credman_rx_metadata 11 0 100.00% 21 0 100.00% +credman.c:credman_parse_metadata 9 0 100.00% 19 0 100.00% +credman.c:credman_get_rk_wait 27 0 100.00% 28 0 100.00% +credman.c:credman_rx_rk 19 0 100.00% 36 0 100.00% +credman.c:credman_parse_rk_count 16 0 100.00% 25 0 100.00% +credman.c:credman_grow_array 17 2 88.24% 28 5 82.14% +credman.c:credman_parse_rk 23 0 100.00% 33 0 100.00% +credman.c:credman_rx_next_rk 15 2 86.67% 26 4 84.62% +credman.c:credman_del_rk_wait 16 0 100.00% 20 0 100.00% +credman.c:credman_get_rp_wait 23 0 100.00% 18 0 100.00% +credman.c:credman_rx_rp 19 0 100.00% 36 0 100.00% +credman.c:credman_parse_rp_count 16 0 100.00% 25 0 100.00% +credman.c:credman_parse_rp 9 0 100.00% 19 0 100.00% +credman.c:credman_rx_next_rp 15 2 86.67% 26 4 84.62% +credman.c:credman_set_dev_rk_wait 11 0 100.00% 10 0 100.00% +credman.c:credman_reset_rk 4 0 100.00% 10 0 100.00% +credman.c:credman_reset_rp 4 0 100.00% 13 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 382 10 97.38% 614 15 97.56% + +File '/libfido2/src/dev.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_dev_register_manifest_func 10 2 80.00% 18 3 83.33% +fido_dev_unregister_manifest_func 7 7 0.00% 13 13 0.00% +fido_dev_info_manifest 22 4 81.82% 28 0 100.00% +fido_dev_open_with_info 5 5 0.00% 6 6 0.00% +fido_dev_open 11 5 54.55% 26 12 53.85% +fido_dev_close 9 2 77.78% 10 0 100.00% +fido_dev_set_sigmask 12 12 0.00% 10 10 0.00% +fido_dev_cancel 11 0 100.00% 8 0 100.00% +fido_dev_get_touch_begin 50 0 100.00% 68 0 100.00% +fido_dev_get_touch_status 17 0 100.00% 25 0 100.00% +fido_dev_set_io_functions 18 4 77.78% 17 6 64.71% +fido_dev_set_transport_functions 6 2 66.67% 11 3 72.73% +fido_init 8 1 87.50% 6 0 100.00% +fido_dev_new 5 0 100.00% 16 0 100.00% +fido_dev_new_with_info 10 10 0.00% 20 20 0.00% +fido_dev_free 6 0 100.00% 11 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% 8 0 100.00% +fido_dev_maxmsgsize 1 0 100.00% 3 0 100.00% +dev.c:find_manifest_func_node 5 0 100.00% 9 0 100.00% +dev.c:fido_dev_open_wait 10 0 100.00% 9 0 100.00% +dev.c:fido_dev_open_tx 56 15 73.21% 67 26 61.19% +dev.c:set_random_report_len 11 0 100.00% 6 0 100.00% +dev.c:fido_dev_open_rx 36 1 97.22% 62 1 98.39% +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% 8 0 100.00% +dev.c:fido_dev_set_option_flags 29 0 100.00% 19 0 100.00% +dev.c:fido_dev_set_protocol_flags 11 0 100.00% 18 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 414 74 82.13% 556 106 80.94% + +File '/libfido2/src/ecdh.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_do_ecdh 29 0 100.00% 40 0 100.00% +ecdh.c:do_ecdh 37 0 100.00% 48 0 100.00% +ecdh.c:kdf 19 1 94.74% 32 2 93.75% +ecdh.c:hkdf_sha256 32 1 96.88% 41 3 92.68% +----------------------------------------------------------------------------------------- +TOTAL 117 2 98.29% 161 5 96.89% + +File '/libfido2/src/eddsa.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +eddsa_pk_decode 8 0 100.00% 10 0 100.00% +eddsa_pk_new 1 0 100.00% 3 0 100.00% +eddsa_pk_free 6 0 100.00% 9 0 100.00% +eddsa_pk_from_ptr 6 0 100.00% 8 0 100.00% +eddsa_pk_to_EVP_PKEY 3 0 100.00% 9 0 100.00% +eddsa_pk_from_EVP_PKEY 14 0 100.00% 12 0 100.00% +eddsa.c:decode_pubkey_point 8 0 100.00% 14 0 100.00% +eddsa.c:decode_coord 8 0 100.00% 12 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 54 0 100.00% 77 0 100.00% + +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% 10 0 100.00% +es256_pk_encode 56 0 100.00% 70 0 100.00% +es256_sk_new 1 0 100.00% 3 0 100.00% +es256_sk_free 6 0 100.00% 9 0 100.00% +es256_pk_new 1 0 100.00% 3 0 100.00% +es256_pk_free 6 0 100.00% 9 0 100.00% +es256_pk_from_ptr 11 0 100.00% 13 0 100.00% +es256_pk_set_x 1 0 100.00% 5 0 100.00% +es256_pk_set_y 1 0 100.00% 5 0 100.00% +es256_sk_create 39 0 100.00% 46 0 100.00% +es256_pk_to_EVP_PKEY 42 0 100.00% 66 0 100.00% +es256_pk_from_EC_KEY 38 0 100.00% 43 0 100.00% +es256_sk_to_EVP_PKEY 28 0 100.00% 50 0 100.00% +es256_derive_pk 25 0 100.00% 34 0 100.00% +es256.c:decode_pubkey_point 9 0 100.00% 16 0 100.00% +es256.c:decode_coord 8 0 100.00% 12 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 280 0 100.00% 394 0 100.00% + +File '/libfido2/src/extern.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% 28 0 100.00% +fido_hid_get_report_len 19 0 100.00% 33 0 100.00% +fido_dev_info_new 1 0 100.00% 3 0 100.00% +fido_dev_info_free 9 0 100.00% 18 0 100.00% +fido_dev_info_ptr 1 0 100.00% 3 0 100.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% 14 0 100.00% +hid.c:get_key_val 6 0 100.00% 20 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 60 0 100.00% 134 0 100.00% + +File '/libfido2/src/hid_linux.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_hid_manifest 35 4 88.57% 50 1 98.00% +fido_hid_open 27 27 0.00% 44 44 0.00% +fido_hid_close 3 3 0.00% 8 8 0.00% +fido_hid_set_sigmask 2 2 0.00% 8 8 0.00% +fido_hid_read 15 15 0.00% 26 26 0.00% +fido_hid_write 12 12 0.00% 21 21 0.00% +fido_hid_report_in_len 1 1 0.00% 5 5 0.00% +fido_hid_report_out_len 1 1 0.00% 5 5 0.00% +hid_linux.c:copy_info 34 0 100.00% 53 0 100.00% +hid_linux.c:is_fido 10 2 80.00% 19 2 89.47% +hid_linux.c:get_parent_attr 6 0 100.00% 11 0 100.00% +hid_linux.c:parse_uevent 12 0 100.00% 28 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% 22 3 86.36% +----------------------------------------------------------------------------------------- +TOTAL 173 68 60.69% 303 123 59.41% + +File '/libfido2/src/hid_unix.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_hid_unix_open 18 11 38.89% 26 14 46.15% +fido_hid_unix_wait 12 9 25.00% 26 14 46.15% +----------------------------------------------------------------------------------------- +TOTAL 30 20 33.33% 52 28 46.15% + +File '/libfido2/src/info.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_dev_get_cbor_info_wait 10 0 100.00% 9 0 100.00% +fido_dev_get_cbor_info 1 0 100.00% 3 0 100.00% +fido_cbor_info_new 1 0 100.00% 3 0 100.00% +fido_cbor_info_reset 1 0 100.00% 8 0 100.00% +fido_cbor_info_free 6 0 100.00% 9 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_fwversion 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% 6 0 100.00% +fido_cbor_info_algorithm_cose 4 0 100.00% 6 0 100.00% +info.c:fido_dev_get_cbor_info_tx 8 0 100.00% 12 0 100.00% +info.c:fido_dev_get_cbor_info_rx 6 0 100.00% 18 0 100.00% +info.c:parse_reply_element 19 0 100.00% 39 0 100.00% +info.c:decode_string_array 12 0 100.00% 21 0 100.00% +info.c:decode_string 4 0 100.00% 14 0 100.00% +info.c:decode_aaguid 8 0 100.00% 12 0 100.00% +info.c:decode_options 11 0 100.00% 18 0 100.00% +info.c:decode_option 11 0 100.00% 22 0 100.00% +info.c:decode_protocols 12 0 100.00% 21 0 100.00% +info.c:decode_protocol 6 0 100.00% 16 0 100.00% +info.c:decode_algorithms 12 0 100.00% 21 0 100.00% +info.c:decode_algorithm 9 0 100.00% 23 0 100.00% +info.c:decode_algorithm_entry 20 0 100.00% 31 0 100.00% +info.c:free_algo 1 0 100.00% 5 0 100.00% +info.c:free_str_array 4 0 100.00% 8 0 100.00% +info.c:free_opt_array 4 0 100.00% 9 0 100.00% +info.c:free_byte_array 1 0 100.00% 6 0 100.00% +info.c:free_algo_array 4 0 100.00% 8 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 198 0 100.00% 405 0 100.00% + +File '/libfido2/src/io.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_tx 13 0 100.00% 13 0 100.00% +fido_rx 13 1 92.31% 17 3 82.35% +fido_rx_cbor_status 8 0 100.00% 12 0 100.00% +io.c:tx_empty 9 0 100.00% 17 0 100.00% +io.c:tx 13 0 100.00% 21 0 100.00% +io.c:tx_preamble 16 1 93.75% 24 1 95.83% +io.c:tx_frame 15 1 93.33% 22 1 95.45% +io.c:rx 40 2 95.00% 65 1 98.46% +io.c:rx_preamble 23 2 91.30% 26 5 80.77% +io.c:rx_frame 8 0 100.00% 11 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 158 7 95.57% 228 11 95.18% + +File '/libfido2/src/iso7816.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +iso7816_new 4 0 100.00% 18 0 100.00% +iso7816_free 6 0 100.00% 8 0 100.00% +iso7816_add 6 1 83.33% 9 0 100.00% +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% 42 0 100.00% + +File '/libfido2/src/largeblob.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_dev_largeblob_get 26 2 92.31% 41 4 90.24% +fido_dev_largeblob_set 27 0 100.00% 39 0 100.00% +fido_dev_largeblob_remove 12 0 100.00% 21 0 100.00% +fido_dev_largeblob_get_array 15 2 86.67% 30 4 86.67% +fido_dev_largeblob_set_array 14 0 100.00% 21 0 100.00% +largeblob.c:largeblob_get_array 32 0 100.00% 39 0 100.00% +largeblob.c:get_chunklen 9 1 88.89% 11 0 100.00% +largeblob.c:largeblob_get_tx 19 0 100.00% 28 0 100.00% +largeblob.c:largeblob_get_rx 15 0 100.00% 23 0 100.00% +largeblob.c:parse_largeblob_reply 8 0 100.00% 10 0 100.00% +largeblob.c:largeblob_array_check 7 0 100.00% 18 0 100.00% +largeblob.c:largeblob_array_digest 10 0 100.00% 11 0 100.00% +largeblob.c:largeblob_array_load 14 2 85.71% 21 7 66.67% +largeblob.c:largeblob_array_lookup 25 0 100.00% 36 0 100.00% +largeblob.c:largeblob_decode 16 2 87.50% 17 6 64.71% +largeblob.c:largeblob_do_decode 27 3 88.89% 32 5 84.38% +largeblob.c:largeblob_decrypt 15 0 100.00% 28 0 100.00% +largeblob.c:largeblob_aad 1 0 100.00% 12 0 100.00% +largeblob.c:largeblob_reset 1 0 100.00% 5 0 100.00% +largeblob.c:largeblob_encode 16 0 100.00% 23 0 100.00% +largeblob.c:largeblob_new 1 0 100.00% 3 0 100.00% +largeblob.c:largeblob_seal 20 0 100.00% 35 0 100.00% +largeblob.c:largeblob_get_nonce 8 1 87.50% 19 3 84.21% +largeblob.c:largeblob_free 6 0 100.00% 9 0 100.00% +largeblob.c:largeblob_add 27 2 92.59% 40 3 92.50% +largeblob.c:largeblob_drop 21 0 100.00% 30 0 100.00% +largeblob.c:largeblob_set_array 54 2 96.30% 64 4 93.75% +largeblob.c:largeblob_get_uv_token 19 0 100.00% 27 0 100.00% +largeblob.c:largeblob_set_tx 35 0 100.00% 40 0 100.00% +largeblob.c:prepare_hmac 13 2 84.62% 26 7 73.08% +----------------------------------------------------------------------------------------- +TOTAL 513 19 96.30% 759 43 94.33% + +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% 10 0 100.00% +fido_log_xxd 16 1 93.75% 27 0 100.00% +fido_log_error 8 2 75.00% 13 1 92.31% +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% 12 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 39 5 87.18% 73 4 94.52% + +File '/libfido2/src/netlink.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_nl_power_nfc 18 1 94.44% 26 3 88.46% +fido_nl_get_nfc_target 16 1 93.75% 33 3 90.91% +fido_nl_free 10 2 80.00% 11 1 90.91% +fido_nl_new 16 2 87.50% 29 6 79.31% +set_netlink_io_functions 1 0 100.00% 4 0 100.00% +netlink.c:nlmsg_new 8 0 100.00% 18 0 100.00% +netlink.c:nlmsg_set_genl 1 0 100.00% 9 0 100.00% +netlink.c:nlmsg_write 6 1 83.33% 9 1 88.89% +netlink.c:nlmsg_set_u32 1 0 100.00% 3 0 100.00% +netlink.c:nlmsg_setattr 14 1 92.86% 21 0 100.00% +netlink.c:nlmsg_tx 10 1 90.00% 15 3 80.00% +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 3 72.73% 19 9 52.63% +netlink.c:nl_parse_reply 20 0 100.00% 30 0 100.00% +netlink.c:nlmsg_from_buf 15 0 100.00% 22 0 100.00% +netlink.c:nlmsg_type 1 0 100.00% 3 0 100.00% +netlink.c:nlmsg_get_status 8 0 100.00% 10 0 100.00% +netlink.c:nlmsg_read 6 0 100.00% 9 0 100.00% +netlink.c:nlmsg_get_genl 6 0 100.00% 10 0 100.00% +netlink.c:nlmsg_iter 6 0 100.00% 15 0 100.00% +netlink.c:nlmsg_getattr 1 0 100.00% 3 0 100.00% +netlink.c:nla_from_buf 17 0 100.00% 26 0 100.00% +netlink.c:nl_nfc_poll 18 1 94.44% 27 3 88.89% +netlink.c:parse_nfc_event 10 0 100.00% 19 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% 9 0 100.00% +netlink.c:nl_dump_nfc_target 19 1 94.74% 33 3 90.91% +netlink.c:parse_target 9 0 100.00% 15 0 100.00% +netlink.c:nl_get_nfc_family 23 1 95.65% 35 3 91.43% +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% 20 0 100.00% +netlink.c:nla_get_u16 1 0 100.00% 3 0 100.00% +netlink.c:nla_iter 6 0 100.00% 15 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% 27 0 100.00% +netlink.c:nla_get_str 10 0 100.00% 13 0 100.00% +----------------------------------------------------------------------------------------- +TOTAL 327 15 95.41% 565 35 93.81% + +File '/libfido2/src/nfc_linux.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------- +fido_nfc_tx 28 0 100.00% 48 0 100.00% +fido_nfc_rx 8 1 87.50% 13 3 76.92% +fido_nfc_manifest 35 2 94.29% 54 0 100.00% +fido_nfc_open 14 14 0.00% 21 21 0.00% +fido_nfc_close 1 1 0.00% 5 5 0.00% +fido_nfc_set_sigmask 2 2 0.00% 8 8 0.00% +fido_nfc_read 14 14 0.00% 34 34 0.00% +fido_nfc_write 12 12 0.00% 21 21 0.00% +nfc_linux.c:nfc_do_tx 20 2 90.00% 30 6 80.00% +nfc_linux.c:tx_short_apdu 14 0 100.00% 37 0 100.00% +nfc_linux.c:rx_init 25 6 76.00% 34 5 85.29% +nfc_linux.c:rx_cbor 4 0 100.00% 8 0 100.00% +nfc_linux.c:rx_msg 18 2 88.89% 28 6 78.57% +nfc_linux.c:rx_apdu 8 1 87.50% 22 3 86.36% +nfc_linux.c:tx_get_response 4 0 100.00% 14 0 100.00% +nfc_linux.c:copy_info 30 6 80.00% 42 0 100.00% +nfc_linux.c:get_usb_attr 1 0 100.00% 3 0 100.00% +nfc_linux.c:get_parent_attr 6 0 100.00% 11 0 100.00% +nfc_linux.c:to_int 21 21 0.00% 16 16 0.00% +nfc_linux.c:sysnum_from_syspath 12 12 0.00% 20 20 0.00% +nfc_linux.c:nfc_new 6 6 0.00% 14 14 0.00% +nfc_linux.c:nfc_target_connect 9 9 0.00% 24 24 0.00% +nfc_linux.c:nfc_free 12 12 0.00% 13 13 0.00% +----------------------------------------------------------------------------------------- +TOTAL 304 123 59.54% 520 199 61.73% + +File '/libfido2/src/pin.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------- +fido_sha256 7 0 100.00% 13 0 100.00% +fido_dev_get_uv_token 1 0 100.00% 3 0 100.00% +fido_dev_set_pin 1 0 100.00% 3 0 100.00% +fido_dev_get_retry_count 1 0 100.00% 3 0 100.00% +fido_dev_get_uv_retry_count 1 0 100.00% 3 0 100.00% +cbor_add_uv_params 17 0 100.00% 28 0 100.00% +pin.c:uv_token_wait 14 2 85.71% 14 0 100.00% +pin.c:ctap21_uv_token_tx 49 0 100.00% 59 0 100.00% +pin.c:pin_sha256_enc 19 0 100.00% 30 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% 53 0 100.00% +pin.c:uv_token_rx 20 0 100.00% 36 0 100.00% +pin.c:parse_uv_token 8 0 100.00% 12 0 100.00% +pin.c:fido_dev_set_pin_wait 21 0 100.00% 27 0 100.00% +pin.c:fido_dev_change_pin_tx 45 0 100.00% 68 0 100.00% +pin.c:pin_pad64_enc 15 0 100.00% 26 0 100.00% +pin.c:pad64 18 0 100.00% 24 0 100.00% +pin.c:fido_dev_set_pin_tx 33 0 100.00% 48 0 100.00% +pin.c:fido_dev_get_pin_retry_count_wait 10 0 100.00% 9 0 100.00% +pin.c:fido_dev_get_retry_count_tx 19 0 100.00% 28 0 100.00% +pin.c:fido_dev_get_pin_retry_count_rx 11 0 100.00% 21 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% 20 0 100.00% +pin.c:fido_dev_get_uv_retry_count_wait 10 0 100.00% 9 0 100.00% +pin.c:fido_dev_get_uv_retry_count_rx 11 0 100.00% 21 0 100.00% +pin.c:parse_uv_retry_count 1 0 100.00% 3 0 100.00% +------------------------------------------------------------------------------------------- +TOTAL 403 3 99.26% 583 3 99.49% + +File '/libfido2/src/random.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------- +fido_get_random 6 1 83.33% 8 1 87.50% +------------------------------------------------------------------------------------------- +TOTAL 6 1 83.33% 8 1 87.50% + +File '/libfido2/src/reset.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------- +fido_dev_reset 1 0 100.00% 3 0 100.00% +reset.c:fido_dev_reset_wait 15 0 100.00% 14 0 100.00% +reset.c:fido_dev_reset_tx 8 0 100.00% 10 0 100.00% +------------------------------------------------------------------------------------------- +TOTAL 24 0 100.00% 27 0 100.00% + +File '/libfido2/src/rs256.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------- +rs256_pk_decode 8 0 100.00% 10 0 100.00% +rs256_pk_new 1 0 100.00% 3 0 100.00% +rs256_pk_free 6 0 100.00% 9 0 100.00% +rs256_pk_from_ptr 6 0 100.00% 8 0 100.00% +rs256_pk_to_EVP_PKEY 32 0 100.00% 48 0 100.00% +rs256_pk_from_RSA 32 4 87.50% 32 6 81.25% +rs256.c:decode_rsa_pubkey 9 0 100.00% 16 0 100.00% +rs256.c:decode_bignum 8 0 100.00% 12 0 100.00% +------------------------------------------------------------------------------------------- +TOTAL 102 4 96.08% 138 6 95.65% + +File '/libfido2/src/u2f.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------- +u2f_register 70 1 98.57% 88 0 100.00% +u2f_authenticate 32 0 100.00% 44 0 100.00% +u2f_get_touch_begin 30 0 100.00% 46 0 100.00% +u2f_get_touch_status 18 0 100.00% 29 0 100.00% +u2f.c:key_lookup 44 0 100.00% 69 0 100.00% +u2f.c:send_dummy_register 31 1 96.77% 49 0 100.00% +u2f.c:parse_register_reply 49 0 100.00% 71 0 100.00% +u2f.c:x5c_get 21 1 95.24% 34 3 91.18% +u2f.c:sig_get 6 0 100.00% 11 0 100.00% +u2f.c:encode_cred_authdata 33 2 93.94% 76 6 92.11% +u2f.c:cbor_blob_from_ec_point 22 0 100.00% 39 0 100.00% +u2f.c:u2f_authenticate_single 32 0 100.00% 52 0 100.00% +u2f.c:do_auth 50 1 98.00% 71 0 100.00% +u2f.c:parse_auth_reply 23 0 100.00% 29 0 100.00% +u2f.c:authdata_fake 12 0 100.00% 34 0 100.00% +------------------------------------------------------------------------------------------- +TOTAL 473 6 98.73% 742 9 98.79% diff --git a/fuzz/fuzz_assert.c b/fuzz/fuzz_assert.c new file mode 100644 index 000000000000..1ecbde38bd0a --- /dev/null +++ b/fuzz/fuzz_assert.c @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include + +#include "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)) > 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[4096]; + 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; + + 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); + + assert(fido_assert_verify(assert, 0, type, pk) != FIDO_OK); + + fido_assert_free(&assert); +} + +/* + * Do a dummy conversion to exercise rs256_pk_from_RSA(). + */ +static void +rs256_convert(const rs256_pk_t *k) +{ + EVP_PKEY *pkey = NULL; + rs256_pk_t *pk = NULL; + RSA *rsa = NULL; + volatile int r; + + if ((pkey = rs256_pk_to_EVP_PKEY(k)) == NULL || + (pk = rs256_pk_new()) == NULL || + (rsa = EVP_PKEY_get0_RSA(pkey)) == NULL) + goto out; + + r = rs256_pk_from_RSA(pk, rsa); +out: + if (pk) + rs256_pk_free(&pk); + if (pkey) + 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; + volatile 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); +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; + 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); + 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; + + 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; + 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_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); + 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 000000000000..ed3deec93693 --- /dev/null +++ b/fuzz/fuzz_bio.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include + +#include "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)) > 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[4096]; + 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); + 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 000000000000..004852d3451a --- /dev/null +++ b/fuzz/fuzz_cred.c @@ -0,0 +1,455 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include + +#include "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)) > 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[4096]; + 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 (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 char *fmt, int prot) +{ + fido_cred_t *cred; + uint8_t flags; + uint32_t sigcount; + + 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); + 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); + fido_cred_set_x509(cred, x5c_ptr, x5c_len); + fido_cred_set_sig(cred, sig_ptr, sig_len); + fido_cred_set_prot(cred, prot); + + 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_ptr, authdata_len); + fido_cred_set_x509(cred, x5c_ptr, x5c_len); + fido_cred_set_sig(cred, sig_ptr, sig_len); + + assert(fido_cred_verify(cred) != FIDO_OK); + assert(fido_cred_verify_self(cred) != FIDO_OK); + + 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)); + + 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; + 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_fmt(cred), fido_cred_prot(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); + 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 000000000000..89a37379d87f --- /dev/null +++ b/fuzz/fuzz_credman.c @@ -0,0 +1,405 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#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)) > 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[4096]; + 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); + 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 000000000000..6aca7ef5da5b --- /dev/null +++ b/fuzz/fuzz_hid.c @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "mutator_aux.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; +}; + +/* + * 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) != 3 || + (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) + 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_string(p->uevent)) == NULL || + (argv[2] = pack_blob(&p->report_descriptor)) == 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)) > 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[4096]; + 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); + + 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; + int16_t vendor_id, product_id; + + set_udev_parameters(p->uevent, &p->report_descriptor); + ndevs = uniform_random(64); + if ((devlist = fido_dev_info_new(ndevs)) == 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)); + } +out: + fido_dev_info_free(&devlist, ndevs); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + 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); + } +} diff --git a/fuzz/fuzz_largeblob.c b/fuzz/fuzz_largeblob.c new file mode 100644 index 000000000000..6886261bf529 --- /dev/null +++ b/fuzz/fuzz_largeblob.c @@ -0,0 +1,270 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#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)) > 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[4096]; + 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); + 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 000000000000..28afbc6aae5f --- /dev/null +++ b/fuzz/fuzz_mgmt.c @@ -0,0 +1,480 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "dummy.h" + +#include "../openbsd-compat/openbsd-compat.h" + +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)) > 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[4096]; + 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; + + 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)); + } + + 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_fwversion(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)); + +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); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + 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); +} + +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 000000000000..9b7f930cde38 --- /dev/null +++ b/fuzz/fuzz_netlink.c @@ -0,0 +1,249 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "mutator_aux.h" + +struct param { + int seed; + int dev; + struct blob wiredata; +}; + +/* + * Sample netlink messages. These are unlikely to get the harness very far in + * terms of coverage, but serve to give libFuzzer a sense of the underlying + * structure. + */ +static const uint8_t sample_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 +}; + +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)) > 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[4096]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + dummy.wiredata.len = sizeof(sample_netlink_wiredata); + memcpy(&dummy.wiredata.body, &sample_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); + 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/libfuzzer.c b/fuzz/libfuzzer.c new file mode 100644 index 000000000000..09aec4ea2b68 --- /dev/null +++ b/fuzz/libfuzzer.c @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mutator_aux.h" + +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 = 4096; + size_t n; + struct param *p = NULL; + + if ((path = strchr(opt, '=')) == NULL || strlen(++path) == 0) { + warnx("usage: --fido-save-seed="); + 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 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="); + + 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 > 4096) + 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 { + test(p); + 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[4096]; + 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 000000000000..0dc3ae1bf054 --- /dev/null +++ b/fuzz/mutator_aux.c @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mutator_aux.h" + +#define HID_DEV_HANDLE 0x68696421 +#define NFC_DEV_HANDLE 0x6e666321 + +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); + +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 +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'; +} + +/* XXX should fail, but doesn't */ +static int +buf_read(unsigned char *ptr, size_t len, int ms) +{ + size_t n; + + (void)ms; + + 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 (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); +} + +static int +nfc_read(void *handle, unsigned char *ptr, size_t len, int ms) +{ + assert(handle == (void *)NFC_DEV_HANDLE); + assert(len > 0 && len <= 256 + 2); + + return buf_read(ptr, len, ms); +} + +static 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_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 000000000000..6b1a98215b07 --- /dev/null +++ b/fuzz/mutator_aux.h @@ -0,0 +1,96 @@ +/* + * 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. + */ + +#ifndef _MUTATOR_AUX_H +#define _MUTATOR_AUX_H + +#include +#include +#include + +#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/es256.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 +# 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 3072 + +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); + +fido_dev_t *open_dev(int); +void set_wire_data(const uint8_t *, size_t); + +void prng_init(unsigned long); +unsigned long prng_uint32(void); + +uint32_t uniform_random(uint32_t); + +#endif /* !_MUTATOR_AUX_H */ diff --git a/fuzz/preload-fuzz.c b/fuzz/preload-fuzz.c new file mode 100644 index 000000000000..efcb8c632605 --- /dev/null +++ b/fuzz/preload-fuzz.c @@ -0,0 +1,104 @@ +/* + * 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. + */ + +/* + * cc -fPIC -D_GNU_SOURCE -shared -o preload-fuzz.so preload-fuzz.c + * LD_PRELOAD=$(realpath preload-fuzz.so) + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 000000000000..373acc560a60 --- /dev/null +++ b/fuzz/preload-snoop.c @@ -0,0 +1,217 @@ +/* + * 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. + */ + +/* + * cc -fPIC -D_GNU_SOURCE -shared -o preload-snoop.so preload-snoop.c + * LD_PRELOAD=$(realpath preload-snoop.so) + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 000000000000..61114ac94228 --- /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 +#include +#include +#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> 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> 1) ^ mag01[y & 0x1UL]; + } + for (;kk> 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 0000000000000000000000000000000000000000..c8d4d3f38028e034432e767f03012117960f9d7b GIT binary patch literal 303082 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༻m_w~5Brrc3~Z-}4fSRfaziEQqzH%n9M*Do<>Qas^t4~rh&rNRI zeZGcFFJ^rC;?W~PILgm~aDUR>TTtkcpoI>4CbO#J*stek3@`{|~iToHG>_2^q|C$E>rAzYS$3xdx zbW4)312e5Yc(bI##PVVy>U~;ZHuVpv-PvppL4{)+Y>L<}8wPVMP~Cj73{?ysrP2CT*o1c14Ts}i(noZpTwF6D9m_U=$)flJZ1hbdFAq#~bJE##)#ZhH1 z6Wcy9^;O_BQITt!CL;73o%gJwH8v^~w6mP@AbCaqXy>H^=HH~rZKDxH+f8$~YniOt zg85lDoHfhMywKX;PaJ1;mtK%X{TR}k-il%{ik$QVB4UGTlYScIl6&ob!xKzn_A1Nf zTIA=dF&r&7gFyq$I21DtRd{QY5?fqtaaU}a!cj&J7S8mgPklV5e-T=+iC9k=k5TDn zQNQ8e#;=ibcc+t4&~&h9qpLhOGB&c5y0N-{a1xind{znH(s2;KTUg@KHu5lF&uU~D zd*TCE{A8$7S35o0vjX&a?VIHBqnGxPNSTd;($J(kUEM!1%mi7kDKo4U&MI=0P5ns3 z-pK(oJK1#%RSD{~9uuz&B{nr{W9ZqP^1vBF}!SQVz z5cjevp2&v>tL{6*eV8OJ$%0Xv(F9sDDpKKkLlR;a)hd~-$*4?~nnleHvD<1!8bzw8YOfMwh&t1Y9B0#Iw17vhJ$l7b*uJIg~Max8j`3i znQ7;+-8vrtr#Zj`2p664sFDw$!BeSPqF74!4vq@<@t?m2L?my`9611I{if;I6eL&jle!UdjyZbG5~HBfR6kAYt~pR0?Ovbs*(^7? zemBXZ*Hho==oxaU@F5{?wC+v1+XybtYL{ECQ_ymyK)Be9N5_0FdY&MXF!f1s=FG)i zi^HR_GC^zK<%x||s2Y+eH<4FO76O9WH8LWsDbswhi4%Lt7yC$DQjGX>)@h_ASzv*9 zWZGmFbrxh>zuSxnDn>1qs%y^7(T;Wb$nb)e!p~O8&jGbJ+2t(w(r=UXH%p&wFuW|p zLFr-=Z6L&9QWTX$^bS%xGaoEF#)Gs ziuYSM??3KZ$rZ1idBew^#=v~m2j2>IKol8Inivkk{B^m4%%v6mja9xiB*mY+mRw$r z7;cN`!p%afxV1{UOqhccWIW34i+5qz;?e5vxf-;qU_X8~8Mm7SGqqx)^kP#mm%fxh z3omlE;r`7R?LN2-bVov5$DwTmXU~83j>h~?M`d&^+7fVSLAdgaM$24$SUv}2k;2TF zZE>NgQk#r59@!aL{P_jbC&Ka)dMC;q^S?0QFBMCm;`a=-ugW(@F0V%>t_tIYZZ>^= z&YHYMtm{&=Q%H|c;4NRZEInIB%ew#~~m=aplSTpp?r{E?0;clv< zt$UV^aq8Wbi?mtcQ~u0=(AToOgOY4GKjBmFGn-&|!A|OQ1mtCmsWOD_V2)#B;QB5y zej5AjMmOnXqEuGU*cyC&27RCS|Ahei|GyC6G8_cBO6va~0$gN78FE1Ih1B48eYG)U z;LkqNecnbebaE;~;pKXg#nqng^Jd-xQLR%OF)OVa+rh9lgXTAE*GFpYcc&bQZ|2E( zjxLL*1@qhn7bUE6{&{-qVsK}!b1%C8^+*;!AQBO`%5pIZ6fTc+HpObl zHpTvj1U$rDp7+)S%v(Ue@drmUyq!Yc-nknh_J7cSQFNe0Z~*RnPCY~6r0g#P>7^zk<~h8q0_3Wa8(?8DIsl~>&@3n17$~wmYAA_vBby!#EXg$f(e2yDRF+q@^A1@Ufw)Ysc&+V#5C{h34iu=@%Dt$WKq;v&u^pD9&k$D%OL+S>t7Pa>5n0@ zd5agM_2*}Jllb~hEVz)}(W)E-@_ zIU|t=k9m@&-O+^8}dSOQGOl78BnyHn-4Ox<+(90I-+^6F^ zGLhjlZ{Ml#{@l+v9g{9hx{b%?=f;V0zKtYzZo-mFrtP{s`UrKrUI~?N!t)__HzCkr z_x!X$8FG1NuYkS!>)$cHJ9LjQ5B&hKgEvOH`HAN>C2{Dx9rmiOT1%!f7OI7a2~lUe zFIhoSlQV%2++Z@iA|1Z(NxjHPDk5`14;qXA?w@|W-Y&)}vH*w)a_x96eak5t#t^)Xf^AlBg2#;48rFe=41adi7H zlDTYZ@wz4|KHKi{>{7b|a{iP@TZk$}&Yh5zWoS7$>hkJGta;DMQwCaq-I)l1gw~MtDBQ7mp$dJR$;9_ubaXTx4en+bS&^$ zv%hwrY{!X+Ro>ZC`?JSVOUtd{fuGA^1<;i6Be_U`(7Q6ywq}YE;f5)6hS|~m)(h6p z`C;$wrwPb8u4d&_iT;6}T=kK>n;YPRmL$a;wgT3NJg=Grijt^x^ zlWqUXI}3f|qCXQkx-$O^VbeBwt3!X&f&X{*vaxBsX!5(q;_oMGgTpj0YMMIfKa05M z=byV|@e78n0KrW1cKD>9kwtpgjj`hN%NLpPRb&t2PXL9^w}cg2(cJQn}0 zo{;@SYqEL~6tj{O({QFlkaK+Ad(ZIDx)D|HBTOh0)2au=`ma#p3j`U=YEML} z1byzIneL?!=5C4I1H(t7c?L4$TI_#N>edMy)Sw4(lJ5v9ye*oPDYid$sM@dR$a3}Z z^i6W0L-B|_XQfNpLKPgS{cS?5gY(~9;cfD4`M;9%lUY9?uBq*}JXuvLyGMN=Er&Py zdi{T8C*$YTFgX++*e1d@_+sUYI~jn0kjdukJ|TrvV-JWwLJbe0d(Z2Tv%j8A2vXTQ z+5M{AC+{VwJzhPU?s9*l>zB;xy+^XzNZ0XRnr+eY5nlDNvEK5fP?jM9qkhXA z^eXd#(@1J?8sp&6)SJ}WRZfk(wKcg>H#vSij7JEF&e{>FC$bbBkRdG4ElI6|nf3z#;+x7iPKG>&^ zse#f|esGfH#(LMN3-q{4%y{?$*#FJ=K>20P<&a_Hr3irmOUk1Z3gAJsE=K`#Z>^#y z<65;90l8kQ{OvwikAV8YJQurKV=tzZJ25*WYQ2%Z{_;OKEfyR!M}ebJO-t(=JxB4g zr%G|Z;(+t##RZ}uyMqe2=O>Ymp=N^eIq}9K!Xa|hwd_%_1NRo|fhRBKDf!7J&aOeD?t=!Z!ElDYE!ikQ+&%^-5(( zobUnlNZFW$bd}C>aBBbm7Ac)=Z5A{4GDq#}Sg3`VxE9Nc69kDB_gR zddo%NHV^eufGqkN+l%vqpKpxLh^Kt$E}mn6k>a(>j^bU*9dFkEmRK)FWG-gH z!4-AM<$46qHr+IEH`2I-islXarPaei${da|x-O#PI-*P%z`-`PxZ;y5wUtp~`^!B! zkOk+u|BWzjplOU1*9wAq6#?6Hq-mUSyBm6S4|Ip1{)d3!Ig622vf|ZoluP{mNN(nw zOzOZtxaQM%^Quf#xk?mkU;a2~yYA(#F@5DW-+={r| z3iJoj_>X&r{)x!pPZhaa}iu86%m@q#QE|*ux11aSp;qz#|8F$Y!rdPXOgoS*dLO$?e zQvP^^u*LLI*Wkf4xRbBv3J3Tjy*I9;PpGVq;Lkfb{dgII$JyhE_a-QOW>gX?eF+Ox z8O;NY=OOhz*|+}7H~gu=6{N3F*2y>A$%j*voOCYbio9K+YhIzF{R9piHC><{tcV+| zz-7?~2=vn9Ss3FMB;tgpj?4`vaVgh+@KzoYw6wxR4DvJEsRj$sOw{(ElddJB+r8O2chvADA$F@SMw3%@Xu-NY*kt(* z8)O8H~rK1AN&{bKqXQcCsK)i^Vq2S&FR-RDK#Mx7P0I!--L4B&wdkMS9DExG5{4>0;u;Z;`S@t-bBpcGFbu||-KOtPhmVXwi^ z=||S#d;!4H#*0yJg}lF@9R&3* z0tR!kb-JYHM`iN8&E)&f^c88S&!V*F+GTRTc(k_F29{s=0Edwfh9ckp`pdn+rsyN} zBTM|`b%RibBZ<&?5q4^+HTS8KzKoym8`n=*9`N1M&rdU@5<%KbqS&1`J_Y^Hm5iS_ zpZO%~X=6kk6e~O~%3JLS#&z@wb@j!g@;@2OHbyyq(N>{BLr>)tY+A8fN! zF8-lh47e_@#b@#PnzF79AiV;eB&toC51N{LX$dt}HqR-`v7mq&tLf^aboCLjEMvEO z)l(Oif=8Mb;_5B|r7S#ci>AshQ3*|A%57p;OubJi^nt>4Qq4DOtAf1)CLYid9c+n? zwr7vFq6cg2#SEocp#!eaWz6l#8D$AAfl{=nQ?+1`?qna!gCSc>bf>t=?<&kKE(JuX z0PYD??l`86n;oapGF<$AZEN4Rt3>#kRAr5ttvbY{jTzPj~0|U{`Yzb9! zIiML=s%OYf)EDO&+s!h%- zP0l0qjPltbi!w?ME(#7VVE#l}a;Yc#mFaoq@p~bH+o8~c1^A@r6Bi83%Q77rJxL{sU)13Ve zuhUb=W+|?59@RK69=fRg!zR$uUe!Gd?2f}2)JpZGSaJnv&<1IU4C$}2R$(FCYU%Sz z>GOyLgTN%3WHhf9ns)}H)a<2WVF2mM8TAe@C|MB!k#qnxBzzTN`C44o8no6LBG-Dy z`E-qb!9EkazKe{$Hq|**HH4M(2ub=SO<7Z*Z>^|Lt!Re%;+IYJowD17)8V zD$D=T9Su5m&mL2`Th?a~nWD^HqRd^uYYBcvE-$=4``R@2k-XDkk`s8em*og=-Tyk@Na4CK|M<;9Is)5ASCfih->s20)47E#8`#k+NKMWy9q2;uhYOWn{eK|kog z<&4DHFY0U4yereZiTCrv`d>#_VrkYkZ1g)k)swK5+i8R4@q^{Lw*ryT;HTmpjb3^K z|8oo9{o*IFLR7caP=sM^@9+*KO}BYomwDdAm)A~7yLd4dW}hc|GeCWXb^6;NxEdGp zS(oT(muSWqPvAt~+DROT29rEol{{EnJ)psgCCj zQNqrP^D}Vm-L78wXBHFT48thzwwDtJ+5cT_YgQLl`73qJix1QLmDGp+!uIvVMekyc z{o$d57CN6knIm*k|7lyPadB?dh;G)jksI@PZ~I0)8_m(#J}{NZoff{6NTowZ6K*x6 z!+*UBPT#5vgUW2^^?>{s=+SD%dFnl{zvzYh z^f8j2`Ew~K`*p-)W4TL^;}7q=()2t6$*?geU~oJrdw0705$9kg+ZPsoyOq39N~LZF zOm9;FtJn6c4j&lCN!O z+hSq>r9!ybjh(lBJbZYy9GoIZ(i=L21n;}FYJ|&JgbQz+0M0Y0f-QkK&9h)5+mY+^**aNyDUs|FB&9i^(uIoH-Kx zmW!utqIzr%^che9?7S$XF&(1D)Z#v=a6cWvr{jM~;{VXYyRChu zb=n0Bae%04l0~VYtfIW_Z@Z87!eq-6fd3;JN5r9hKY*Up*nK1!sG(O>N{vCWljeSd zF}}3)s-j5OVxY;#K@=vWyE*B(2yV@;ZSE7mPCOOhbho)+N>q^-i8+^o@1dqx`nmcf_L^^4YCS`-7#eSsal4`xix z?0-7-AkWs~{TO^7J0<0keqSsSJiE2Yz)VFGyUL!vR{;M@vU}O@Wc5UMGUD@ny62#> z7=(gZ&QoSF^nalWqb_#)!);W2j=*Qr3amJ3fPgvy(`&2(C@}`wg1%Hu{@j?>WDO+! z6VU%`i&g{Z+o`bS83R4Z=84DyS`Ons*xmaxq-^UVZgPLuIDbUFwfk|dKg=tfum255 ziOg7ysUab!mm$PC0>TzT+M6`tuBo<63O;Ug?n^VB#%(|8DczRP+v%L zKMU-h@?tLrLh@@slMwDq948 zraxw&@q^mK$CzZ|qZupkl~_r0+nP-R#XD73Ow`(%)6`kr?7r@L0tIDAs2Le(1flj@ z!z-VmVaIVdB!CP06ztYnTLE5PMnD*+OF0c1-R;R^R z!dM2AZ~a7K48>}Xe#PO)2XpjrYRmKi=I_u&*&VkS%o0g%XuUXtkzIe>jVvx-&^h!U z8u&|z-2Ol2cRWl&OU%dK%_NSM@_!Lg=bIJ{fVht&fJ%C}j~Uw!lBp&2snW0i%C+8e za17pTUiETVhf7?op2}jVDK3(maBPHH>}ffUbUZS|RtAffFK`FyLAc9|#+6+UKC!Q% z-Ai7ihT!Zdg17CiyDM8SfoI(cTYfPXGK?D@=dPn7=T~J9sramb&-xSuv57xoU;KR; z$JUr8X$8#LUR;0fk{u!0nC5L&LcZVbk$EX)o}t)gtmfSKA@(;7%QvCNO6$gIeT^SR z|In;J!;g7KP$wi$OL}p_{t%jCNZV=m^OnA)Vv2W=d zzMDx@nYSRUHU&Pzg|Jv{a*QDxMCdhh^SPnB$p)ZW#l0j9Tt{H~4k z&th+MOv?`T=o5f=Eec@H#O=k{ekb+Poxs9aVhjl&jvgLq0uB6+C;4K}zLw+2+<$$^ zYXhw5dLvym(6wpt-CJF!H;t(?*KZzqg%A8P&^9W@TD{q#S>v}Ku9vj3kbi8@n2V<` zt&7yrt%&!ahc6p>NaJX4AzD624_Ji%B?m=YOdS2ps%6>t&M`INk>0kI@B&?Ri2%J37umIKak|!6KE(c zS8bQ5D0-e*oKhKMoFkplsK*%s;r@mD-@O1sRUL*TUVmic4oLuu^uEt5Lm3NGxw%Fl z+_A>ac0t{yf`xOyB?xx|ZtV;4VC7hz$v?pDla2y@(y!dZjljko-J|KBXC^YN+Nv2d z!VH0w1_4naF^H<1@#>41{hUJn^{_!HKYx<@MZ7k^4E#?H#tM;-`i#dhFMQ0t5QmC! zNN9HGSPjUqv&n|t+%zk$sUCp;`!e1}M}PtJC;abuINFl(@W;Tu~0Z6A+1Q4r|0#b0yDrav# z7q>)K{8Q&zskmZ%Fq4u34%;J|qW_O}$MH8G`zG3(Y6faQKW&g2=+F_&T>W3=)dQSC z1Dmw7*OVhbQTdBWG)VFlZ%;wJoF3$07CxE7`f2M%n<{fsO4ut;2p%o@9}!r zA8A?H+!wt>(h3@8N||kOn(7%(4p~~cP*uu=&l0<+#0DM=qaOmf1wd+M7*yFsa5>{% z`-xOe3!A1MBpgI7#grc1C;cupTG>LbxC7j-Ct>F=0+tA#hN=lfS4_|I7xY}@#A7?~PeZwUgE;vg z9$CvqlP;)>N*rS%u@D376#3=5sR}V=A~8DyY}2+Nwjrf^2J-nBT};a3_u7JWMpD*7xR6qz#Qe@828O8J^2Ltbl)i3G7P5rg@`A5 z*JdiR=`3P(uZ+o}#^>doeBL&G7;fnyF%G@1u;_O6Y07%dly-%bmn@|ziyH{r|ti~L= z%W=8%KDnl391R5!-zPeBKxGIh!3b04+n+l8RTO5HBq~!T`z5na`scUbuzlXyp z**rCeby``)1q-CVvbf3;JwRCYGd^bs_SaPP_lqh;=Q`DwSNm9C$A{9J0o^((UspQ1 z(6`~r;)ljg$-cq;Z#H`;bPL(Agc09t)05dh<(AGIf*2dqgczu;p)IG_z84p=DS|fa z^7mTCi8xn1j#ZgVm4tDRxz4G?D8(nV3h!o#4Dk&@8+{J-4w?4Il|&NVMMYG{rjOoC z2l5*umNmVaX5%$-+{SjEJRufKBz9$hl{n_A4v*d{>JqhZ%AR)AN87Z;y>q6fO#=U% z4}S8%#ff?j7wq4YY`gux-}7;x-kb!18=%J|`P9IAD9FqdQ4QU0KRQDx6kRw4#uI?^UXt3v{nKMv#LXo~x=oAx0S{ZGx zW`Mc_bvmx)YQ)zHKX(OAQzf~UH~{y5#AI5;+=;|$49gMr^9wr8oHfFD6ClO?w+ZxnW?Gz3m8EFL1iW6t^u#| zHH?DDy;87|2m%r|a9($_PqB|Wp*~Oj$h8KUpjuSq8$xb9Q*xCraW4sRUbBIDx`Zk) zRscHkg*z!Srn)j+NFV~nmB58Z#yO238;zr``A(QeC-wK@>IgNnct*_>e`$0ZGn}a& z8rAyh5mFFDfE4telN2;a42j?+^x2M1-8%4+MsES*xFBd4bbt*^Aqj=^g9MNW5uc=^|n!KGt3$b=Bqor1od0xz#rh zsd6wdY}+H%Pyj|YEEN~J7{Ata@XKWxWh)%JFFv4({v?KqjB zXMrZTN)~uaGDB}S>AUx}7k$Ydr)G(UAzgX1z;CpNEJ)WLCjs#vOhBF5Oym&ZQi^@+ zTD4T1z6@2v;9F2@CaE^65(G7okXzEmp_UPf$@)L7xj4yde0lVx7hTU1r)HE!U?h4X z?!4LTyKY@XwIvRz-Tcz-TNg{5tx+0=@#vXhBS&582R53&dgfoU*OD<==`v)I!Ls(D zb*F;macYD(xoovK!=ANf-%V>Hy3FG-f%_myeiHU@L2b2EO4s#jDQ!KOa^yW>c_<~- z9wdT9AT{qDn4P5u=EMK0o90!pEHdIe-@F}i!b0aKEAuxh#hkMcZz6l%bpPQXKVi;F zhg0o6+6IynWQ4)#@1#SW%u2(i)7R(P;QZN^vXoM?iH5-)GSyY+U;2w#b>Qr3@96AV zsTjMT04JIi+|w*1wnwO?MqSTJV~vEDc&E?v+q`m7^a?u$$SBJ@5gwV1Oby^=MbyQdW#jBM(yUjNdwjVOi{AUl{|wzHU0q?~GrOmw4D^ zbZWh0jW#s*Mf6=qr2?x?Xq*!Qyk%C)ymrypgAwIT2TfW=HU<^l(cRF181)!mU^x;PfWFdQ zEGNbQ*0rY%b0Fn6BCm|Vp&-N)Wozv2HUGOL=fR(MF)A}Dd+GSKVOo4{+#xW{PXzME1cd*6I37MXXb7B#EVB^1nOTT& zx`{~Q&_=Q`;OdRi#iM!8a?P;BswJ)uN>i|KQzPjrAp0U#Cxx4+$WwxqU&g28eZ6Su zA8m}dr{G}HiV>54=WbmLasoO)`^v@s{biej&*$2}O=R%@I=J2?e83JI>W)A`0tDhJ z4P4t8IRp4M_gupL?8*k+c%MM*F#-S~> z;eFEmp8LzX-lNmgi{cd2RQ9K%z|V*7@49ulztwacwkhHr|=p=$&2%ToZ|mpda;zu&O1QrCh>X#l^!&&tf|1)C!T6q4Pf(eX4@CyBN~Vn zhlUSPygBC5;|Ndt_(^wz!}dtfw~ccll!f__6>+PkwnF zHH5ArMM1wNrhs0c#&%r!FUa_(yG_?K2xHF#LH_zp;xB`mn2v_4eYuzPKeX+4tUn2Q z#rMQob{B~Xkp_;2wB0(GXj|+~0`yp&mz}%@gpN8Hq}i#e?o81#!|0`Z*o|5=dX$b3fM#>2e*QZdFR; zAC|`$@8`6I27_;i1f=Ql&P+{wM*geryjMuF6VW&krQkyds8f9>5(SB(G3DtTgJAi* zdv`1w>!;&)zm@dN{+CeWMP3*JXq&RSGCXkXcegc{y>#v3LJ~t)o1$ROogl;D22YT| zvN7kLLc&#IfctTFEa7xDDGK147`lD?R=!l}E*)S{)#BURWlsvG!{0fW!7bodZU_M` z=;Y_bZ63qyj6Fumw?zqC4}0B7Q~sJA8>j|EZZ@M*DmuUnxkSRGPYePOM5%y!#oP zXPC!!BUGaW0RIjSj;Eh;`LTe)6bwPI7#=ywN zSlnz}msMkIv?RpR$t^_6F)^$$$}xrhJ#Esc26L_9B&d7<9WsKs&8eSTkB7q6DB1Pc zd?cYy{g#aZLwB?<2+hzXMqA-6Boc#=cgXZVM16EgQ|@nFUUza}+L%)E+v9gA`z~<_d$oD(Vw#Km6E6|nI z1R3)%1QMdID#1$s!N5+QGixl+T2e8yf23Q?cCdvE7Q{ej5(k8c&tx#N*S78(7;`wz>+vJv%F zl8)lH*)a(+W6=B8O$k`qbjb)AHb`n8Ph`^>;oW)2qCLLNb|S)1Mekv>^3<>@Lzsw# z`(gyl(HWhsqn>TB8@zvkysds1df|G{J7l5)XLNU2RbF zjHD!7xixe2TY$6 zKOaTi3@x_@mz2f+2Bi~ncnKEIX4fu^11C(PAhQwn?ew1j;*^rHlh|t1hlKb3svqrZ zqd}>X*r-|b!1+Ht!Vg(#fd4^}6cR725LInFU(bx_IQ_`RGJd|^Um77=cqU(HjI4H` zu}!aXnbGDqTJfg)q09}(PXLT2J)NF?Rls&j#@=T$1CPVg8|eo>0V3RzxjtEC6!JkV zzGFDeM;2IGS$oP^Ug4ttSQ2WhV$UXgHBY4PV*;c^ z^C;Gt#IOqvEJ44zMe{}o15l9|3U#X!7WJrNrqMz?IwYb&6p+%#YupWd_7Nw}j@AkB z`Zr3%tMSu4Vo!0}{F7w4o|JA~Xe9q}tvItJp`H|5ZRilq4!_rT9PDgeWo(&b>wj@2&d+tY*}(Js07w1WMz(u!H3to0Un#n&e0pP1^FsqKw-Gmh z?J!u}GE>ax*lbM-@p)}pDBR+mM0x8S!&S#$qV?A24c8OL;u91lfba`gL-^r_s79xu znM*+LCl#c3RWf!Ud(GB@BXN?1q>zD>b7S9ItE$I(9)AEzlDenIcoaPy}swe?ypIaGd{^CmgrWfd2XV0CcN1X+r7DKCpeAlgVB8CB| zNlaYXN}o)V}%k-9?mOj6pY#)itUR=l)Er2mtoSFF1L=CK#se6O5e8zXkHad{xbGx z=l563y~=|ze|@j{ESRbz^s_%2Q6ChQg!8?{ zoLk_(`l-zg-6qe<>upS>p>u#{-FEFp05l6H4T{ajStEG`CkpJ_r4Jnf*r&8*qDn}e z;|6ci3!r9&oM&f#P<=bHWscDI5%?;ZhDk-kNiAaxmfjuG&WefW_!*ni7W&x-jVRHp z;C@X}{PGlcdyO8rZX`mge)*xapI<8|*O>Bv^&jfFh~jR}J!&TaH4w_Rfsrw12XWM#`+qHJJSsm?C?+U{KGUN-0h%W}RlJhoZlKs_oMxBL zx2;OOBSGgIBCqQ3P+l{jyalG1e)hoUeYv%l_p{~UtQN3sBa!!vEA>PVoXs9hV4Xl5 zMj=&jAX@GtF2iu_L6kfDYrO!pntLSOq-sqqE-gY>L9-)Nac?Jfo@A3V=6l2;0Cfh+ z#U1ddyzaOl9)AMGUg0#Wed^gQM$k0Q$|P#iTp(!^CL(w>BZe{OvB7(`-0&2l_QQ3t zgXhp?rP=mS#kHLli$m38Jjixn=w^FpmmeBl+rlA-@c-5yuy1`~Nml;ISPEGeuy*WD zMBeR(_U#6j)Vu1z=M~K*uGB8QfXuqeKH6XlQd>h8g^f+mLXQBfvILH|mj@r(aTEUK@RT=#DmZh|7>!nHj_Pq1pfB5cyQl zV{+~*2c(P&3tpY6_K(cv{2Bo<#@Ru!7dXsZPrOwOTL?5A#gr)GPjDHQDl`9p@YI(g z%%EJvo^5Z%Cnjv(bvYOKX|_4yinx5qf5yq?FuOd2JJ@SVgQw_$gyeOCf(uIXuI6ET z_u4b^-N#n{d_=vUZx8$om_GavfecmykSAOG=xIWXG3%S(?IK3<%?s`287}F0r2*UI zp|hyVA2j|t_e{^64hEw@x#< z2~qkJBG1F9(YFB$?F2a7ww|c)e1NlG;^9%LhCvYLBm8Dpmwy7rB3OXS8hyJkq;hab z5#^*X;l$iPiE<~tg!@%r(-_&R#)m?2(Ks98&l|sG2N*H$c?Xz8y=Y5(;a!3mbTC97 zy~Z;`%O^%NlS(x1@wG-XYOy7;+Km}cL?=3~x@<9n#)*S|-Eeq!tbrH#`p+`|?0TZ` zyq|6YQQ^!|C`2s*pn;$~)E=T$m6+|{8GpD9;~#o3PaT68+ACLn%*x|En9~^S5-XqD zRPf0v87U>g6BSGB@YI2;c%T(f=m-o23P^SSq_WD|`HG2Uxj{P}C zO4xSrZh7LM9y27vBGCDUAfCCW0TgTpV{1;Hy=F&u<>vfZxgBdKNc(R>ndv!u9b>)C z*UvwIeW!^%u=ghRtAD^#mGg=y5a(4gE()84WS76do&3x$Wk~B}ZHf?f8e2o|kfXe! zK~z_5Vvt1%rtW^bs{hGw1OApJbN3jCboF<#W+g0w6@aFbyzFeTcJN}56c8zxyzKo& zWauR_G#}TTtpORD?-~TLV_g_-g5c+RpMg2%0tQoIP=*EIP{4EW_Afs|3SCbLzjjuR8gGRo6HFb%M*l+3 z_I2C|Ra>4Rjtg4@&VVPLu{tDzFnQV9jAQH1m-KU%EPd*CZ1h;V!@EI${*2x1DTT1| zw1dFDFkqn$G83KZI_wLjiwW~q2Z-UcEan9H3$7*ND2b=UIHUg$fp+}gB-|6$hN+Iw zWIzZ@UMEQED=cf$1eC896Y-GbyB+9IA@Nf`DosYHY8G$&{g)$px|UDa?$v+I?RE*m z^=EmvePIHX3ylr)#Unt%m&VL5+d!mU0Q;T2h!R2P9l0K-<_b0W6$eCf?+N zuv{*j0H%hi@X(NsHf*Qr`?dgLIWly$_z1~FS7I3#Qj7>8{2Uq_b*4Ss@A^wfe z$mUVES*aIwo=!&IzDw5e5se)^0gL&983X|#)4l_3(S=$zaVZ#xaN!&|nqOspK+A$*4h;edx7;s9BE}cfeyD>3n?{V^Z z7`USjy69Kdv}<^GXXW)Iun zGS|G{?vC<#F;&c?%EwZo&a@PoH!PZ4ynD<;ECu5URX#bYdih-;81Hc+bG3Gg`q2p`2WCE=*E?BK%mKT+}Hy!r7FL=2@y z*zSj(3Hb2iGCjszSC)+x57qbzSD|*p-zm4O@H>} zuHaDj%=-5=EOt_j7!PyrXrO29L!x|-oQ1!A;_qK+tB#s#U_}f6Wc=RKhd<|@O2+Z@ zNjLzOow@?}%K1dxe+v(#Y$X_a$T1f1sIoCb^U^U~K*U=GwOGy|#yZGhX$Vg`#+Ev9 zcRmlPsO>`dDQ|&{g-qnIeIk=n_-WJVzrL;aQvl*zC=aQx&BDQN-q4R0DBQ!k*tGws zU;K%>l_7?xy0@!)e{Y`sjL!gmXTj#FHj?S!MpB*dIp|&nBX_hPd)cGiC`=(YI!-DT zlX+IQJ#(hy0pm`Z_#&K#^sDv4oF_5(J5LX~g+RI5jrxmEQp7HyJf!jwX0r%y_;6q9 z3s5jm9*Tv4Qk*-Q1=3Hm{!LKt(fs7auR6YuRO(E$d20Q1=pJMkY9$e5iQGeyZKl;7 zW>0RWd0qJqcD>tg<-gPRsaZ;6IDE&ligpu$(i*B#Oe3jJvWZK&GAoB`?8%`>SW_EL zGE<#dWFNyik{1HH&hZ^NFD9GZE|iy2mkZ5{R%Eh0fNCnosVCy(Gu3fN!FBLLKUknp z4%?qo7p8^kvoWGl45NxBE_F$RBIa+OV%z|fQNf?{qJw0J@f2n&@~D0?(bB0~O5?5> z%StAy%Q5AThVj5)l0eAodWOy+hi7oR~3@MD=|%i%=dZt9-{7;dg4p1ymL?l1Q*i zL1r07a175YQj5%>7-1b^I^_P(!zn0qf=SFbny_4~C3)hSSRRgtVnD9swYuCdl zV`lBK-G=8?EmWq~{Rt{wOg>KE*Lb4GgXAc|g*M`ep5utEdpqLKZ zzbG^sLFY;4LGq#u`$0Oj+qF|s)`iiNKw_e?*^+W9e`eZSkYxzx$!Be~W7OQlC2S=V zo9AHXg6K_!RLmcACe$m4**B>Q#N6>b9C#%f-y|E=8TAC3Sdy0pj`*> zN1uoTV7ltVbxo`OpOo!KGP&z1d2;DUrl`qr>a7LPRINxjm@Ow-_cL+An_WG5LrW%9tYK6#q@YNOEa2>-TJ+%z zi@Jm&{ll7Zq>=q$Uw)F7)K`met zmnDwlcMk-s7Qv_Gf2(9yiv*B*7P%Q6($W^GMrU-y#MkBDDN^PN1OzW%JE)Pi#JNj| z9n!)JRez<4;~CA=#9dGIi!UwiDVt=Xp#@vqqa+{lcJr9n_)g0!0JEvM0~qKEH3%dH zfZ2;&0UV_LWl=(-D)$jlhj=W76S4^RQX15dLp+Q^r;u*yCNmSyN;ZXWhj?!ao$|X@!4{IKbgsY+xgRY? z8u;b|*OBBi2en_|_(U=T*J#M?yf2TDBVjB@e5#Tez-(up01lNxjqj3Kz-(`x08-UL zzweT$$iZ?2kfK=XT;UhAT}NI{H>n$w(ObMrAdo?JdN+MQV+?; z&M@i&H}KfyZ1JPWE==wPo<~%sNCHATuhE{To4QRhG+|sVcFM@)t=U4z3-+#1OPv(O75BP*`VLimAR9sy?QQCrDfo$i7MC4FHc{r;eto$iC_333xSfE%I`t zo`~Q}X4{-PBbp4djSMXTK`6343o z+76CR73a3R&*ZXPhCpIR4(|sG5Ao6qH_+1M*m#&iQBn_AsCd^P7$A##nWRI%yy4r` zF8Nttwi<7MYB8}UZkwggyYyQHWTVtVC)%0chIVnhGmPWFPP}p8fquy6H1U@<_du#K zomrC5$!j#J87nDk;W?7N(Q7nbzW$GYVCm(*hZtNlK=vEDBWR!Vn?`X@Bw^+O1hL&=RCm?v_TCCfmG9j7VexEk2P}MrE15>%q%|>z(nEjdeN#!$d zCg44;&Am*)A#Ws~i5)K`4qt%O&7!Uz$rAg36^(wJ)`UaeMU7 zWo?KL2Cm(`O!?tT1iuKB=zmx83`J_DrH!hhP_-V}llE1^K6eCiY*~d9eKYQ*?1MRn zcp=Ce86ua%&cRD;zHl9BgzVFZc8;)b>KaXcrfJI>zCgG;aUCgv>@)k~Q-UOua&``1 zfT|ivVtlRqsp+eb^EB~pyL$oeu~UM#T5&;qPbxutMy%0C)bPSb+k9d%hPM6VF*9@x zt=@N956*s3f8Kv_?^QA~xSKorgBFm1jD1h#KIKVgCPO2o2x|y$Dh}?fX0S~mp0f-b zqj}!WjwSU(G#CY5Pg>|}e2nKS2iP7zE}TMLXP!a}tHm6IlIB&`@|K@^(#p*&wbNT- z_a444J%vd;EoU2PpF^w!N{dKJOKj)NdmP|eWOI|UTE}2UY|`9qp-~rfd6KkJTji6` zZ?xKEbJ%iP@AEMo>Ruz%G3hlMaD980*mQd5Zs~4?NMrjL1Z33b@_wN?hAg}C425gJ z*Y|S1sd4x^J8wKPWBHa+=adqvkv>}5P0lEW>*tgFEVflN-?m>))q4|DTLD-y*&Xrs zI6v#X@*x%&zMX2$>!sG`GuNEs87|mFHC%DepZ_zRDXHX>2<(fef4aejQg zg{V=wlXFA(&nHceB$H5phK6QK6-0OJiTw;8T& z$Pf^I0KhTgoUjv|;jE!GlUD83S51x1^867|VLxc2JHm_y@3lWopO*^$`ozj4<#28- z#xn}tU)vtZ650*J{ElV!y|Koxu_cY{&)x{#|DVr*sM~wedy4sc$DM!nW2>5wKa@2| zm_t~0LyeL_ecJPqr5IHfEbR_xvg^MvUw*KAY4s||`&4&-Vb5OwR7Lt%ul}#p@|+(J zYSOK}`mIaYR4V!D)*jW0VhSf%!@z1(rSRi%t#R0y*2`mKDwUR=u$1tI6y8sgf7seQ zqFMYF%>M>*4R08=N!;H2I{-J{&rl|B%o|s22xGhrnW0Iiz#tPgls@RyKS(X7{+9l| z9AzQQtVUX0`Yd1nEVY38oAjAgnVz#Snd)x8;B-*2%4>U68o?a|>kD=1fqeY|2lKoW z$(-J`;9}}wb@pKa(fCCr46Qv=^1c05=-DDi{vv~^g9#S?#9)3{n|wnX?~<(O*Rf-` z1<#1D>G@g0Ty~Tuhys=$_9)-*$jhzpr(i}Sas7Go32{;!&m`lK*CoS%Ms~*JDCinh zJkFegO8-roV%w8eFXG7j8=4OIBWLUiCL-1rVFgNvLY+G1a2&uSjncl_K9)8jU)*!{DSip7cW%C)@8R2`rNvAWi50qZn(O*erqR+aU zD?`8f{_>ceW+y>3X8?2ShuxMX4-}qxhrn!)v1_;JO>7pkG=q;=+7J5yrTKUC`H{`^ zVh`=-s2r`W4DA^4I%i!rY$D7JA#@1^eRhww-4kZvVvbCfhOC8{Rdn+-HPwumLqOXh zB-0UhWe&TqfKW!&n?9UMsEjtDtk10^ zq++yyI!BQ`N8tA7A`Vgp8-TVA^;0RU=?`}H7KNAyWjjQ}ROVkZR$d1MPkn$u!l@>w zuc)(CA#`k{K6l%uVjH-d0DedRPuk&gz~g((?U3%>* z8#@?}v?p_}$Ki`P(vUJ;Lhi|07iMYIV99%@!|rNUR52VVUH#Bt%1f*}nz1$_Zqb>$ z`=mdVI>;J!GBnqFR&s~0F(@L?t`y6MSwdAWl0}w z-x>UT3Oo8^H&=I2=~+u(Lh_46E59re(`c`nzJ;u_vlee@F|;kPNcDG~ZF8|L(UU!D z`pf+ev=S0)R;~OBd_0Fa2Zv(Y^AgRmA`nnKN#js=!GOK=TN8+XU1{lDd%RT%+S~B2 zf^cs0OG#QR%-+AeA{^iwEk<{BYb*Rl9V-mlC4h*Nz_7M}>)&Iyrtujn+v=p2O+Q{+ zj=VPHoD~*ruHRDD_v;mNp96@j#Asm-CYMqhM4>I&TJ6p6bKrt~Xm3dOZ|tsGx>#ATeaua%)J8DWeyt zi}DOZAzaPg&mOJ=Z0jz3wkaWj!BUtiJo_QYM~AWUGzj=pTZHwWgTL^w>cV^0jn1cu zfA|J#aor?8VJUHJ#sge>^Yy^^kS5ulc+4z`XT1)W0up>SEsbjYgkY`gcb*$U@ZVWY zYcz8&%t}Z?VJTE>-$Q6E9{s~!s2-iT*BS*jaq}I@*D_QN;t7*yJY3*`v#dO>!ReXFJSH$J3_|k!k;-Qgd%(8Il(rkQ8Q*4 z1e_)O=i*KPB(*gTX~f5?*AkjFYEQ7Gs@IH}1OY>lSD|MEN~hcTCWEJuK703=_{pW}x>1om+YJp(T19VZ z0dvu82y^oW-hZ}Pf1UaL1-Z|n`zaw38)I@NQ6H?zu7*qkZ&r?zI|=Xn8+FClT6@m} zo}GCDt)_9FoLhu^gv7*%@yU0$ucn6LeX@G~-+WIl#RR(h%qU-*j1<_1r`D{~L%JuU z$*ZG7{wm0xZ8K`{2D@cl(#e72{c~2w^W($RZ}UGES?~6Op04&M{B@&Gul%*Xr^fC) zCLA(-<$EYA^N0Ih52)@;9sQnS`X?klM44zp_M>QL&RfW*nr-^#F@CE2QYS@h(ZcvK zsmZD1tLb!ho@xNo_QviMCA5q)!QUtO0O2H{EqcGh^3TwHV6QgHMqd8I!oMU5U-OPH zc>tSD;n|GCPSGXzjZLkkOvu^s_R}v;Rl+;xq}UG3C<++G_4GS9QSonsIt*R^n+e&j zJXD=q^H<=D5y35tj+P7~Hgra14N=_~9Xx%t4##>;?%L>xW3G;8QVGJXMq=>7nFRN7 z?n=i6{IMt`gl1ERdmWixy$W*WV>+(+gJ{q3h9To!!sX3}e>7tql8P+E7xmX1yr`Gq zEI#LT+7zoFvPSn!2zPF*$~HH5vks}-Hz|qE`WfpsFTCWtcZTSgI$JR?xC1Y^DanWY zgMQ~Jv5OK2YHG2elj$>b=o-`+Rgj*(^4ex&f;z|^lon|HTBCN@cW@VKlHDg_;FMT z(M~EP*IRLGKl|Lx^f)SaWN9eee-CVTB>-dj`*rQfG-H0Up#SUZ$%ttL*MDMC{+pJr6>rZUh}s+Ag$N;F;F-| zC^gEiyB_zqyg5msF>qjHZg5D`@Crs?BJql@9+k+!W@x@?JaHw~@~t|b@g36Z%CHrd zWU!$x-7lW;fvDF_Fu|oW(G%d{2Xjp*x*G-xk)OQb&wEl+ZF*-^>(!P4OI0E}L8Rg- zXLb})Y^^h%`19O|&8aVLFaGL`xt!TPLvaeE5WPEIA^J+%7Wg-NKoa{`F(V&2N>rn? zm=Otb4CWD@Yz_}ekEE{!-yIIjXd#1gdoO-LO+w_mzrJgq=v_Cvu=?;1IGU|)`#f88 z-huuszx++I-I{Wy5psC6HPHQCeC@Vb{PM>0Y0LQv<`{Sva5W-%HPY$y)OtDp;#&c` z`bt=_pIwT=hV>5smV{m4UH#o2!0y=k*vqdn=FPJ5N=4(?{AoBVDxX8evVgW;Y*m|j zq&6jtvC(-W(No#MuW%H}?a1k(A6-0DxQ%e}OB9fIFvx%PWlRm#Bc_7Y5({J}8Eu0% z+xX_FdwI_JLL6%Op_x|cA-8c&?OQhjWgr8^xO?HCckf*Zf@-S%jqS<{0^PPBappHq zF$lMkp`0yug18eJDn;6=5NV$tYhkogRT#?Kwxe*4`x>LGUM_8tg9|Vve15>QMD@Uh zqG~G_;BBvSer}=fKS+5to}2Dwf3weJZxBGiHeva;mxt1*e4>tEYC1YzT`RsNbq-^6 z+^BcicO^xUmKD z-0G0G!vcdEr^)$$liDr@7B9zMXxfUb8%YZ;RG(FttyJ2r98AYYK^G?H_)ZgOuKhP)%E>)k^n1$se+(Cs=o^b#9>V9&Kpiq>?S+|zH zS*mub+Y*l(Lpu&>SY8+5vcVGif=gZ_?Y;jUtKFTt9XW2xLr2oTV*=%~n|wkaEAsb# z|C#}}4IhqlFF6aR?S^;jub0a2j862mI{qpo-4_#F{_@iNu%W6{zKtfFW6;bvmezTk zfFY7-ck++W=!nZl%lGd|F9ONnj~0LyNjoTI4j}3P}~OoYk^+@ z+LzstH;0oB!i}x&jm>I1@&-2>av$|~d#C1@>K|q9{#?$q_kuaxCa@psSGBP1;bsG# zo#O(gWGgeIz3~_8I($6_-oLOY_Ay0WiHt&UjmM>RuDCe&w+uLAl)b%^RH`luM0PYn zN9@p&vq!JT23?uQ^tk<4*Fl}huXq1^#V}5^Nar*e3P)EEFzW1W^Jm;MIriPrZAsbB zKDZlVp(McaBjuyoa+m#Z6-@EYDpMX);nUh$Z(EDJX@~RT<{MG>C#Oer2>;;LD- zNl&;+xKFBzi1hcr-<)TRgh$iey)pGFnf`{h<8zMY9=oPM_v+IRg+7;EFp$CDh5Nk6 zauU`KtsA;EZzE}Yu(-);)S{k4opg|4ObF z>QenQM$4oK&)~EC_H#;>uhGsL&RO!(CGXh69y;>dnfIepV(x>vebW$^JuhQ?6q)58 zp9eXvlfTIjHt%!vmS95vqty2~z@QvybFEz|8BNzH7VcesvUzUj#h_CjPZ+LCS z2oyf9z3gp!S(tf!_L zQ;hk1J^>lo!4MEyg)^?ZNjI%7o;J5U(PC9~=8ibo>ujvn;)FxjCzAw6-#fuIZQCxw zPFu~^J$oUje7F>%=-1i7;Wr_3V1EjrtJY%`;?1M?6Q)sMZ`*z0OjsCO5J#<<@t59o zi$GygRoCc_u!d!(9$e1hZJ40@9_CdK>6W(gmHlz?`0kNr)z;3IrN*$!!N>pWCYwL{ zX*ta%CDd(sAKBdzJFVtEc-l9e+o(_pxDZSeaU$~ zV?>#Db9%j*cT;H(-Oz`G^?a`$oyuR1rYMOSgPae_ok|{S3m6~077MrghHZ|bv+H5n z@Tij0Z-Hr^pB<%NqOM#kLHuXVX~gC-c!(obXD>x8v5b^S)811YzMt3YMO0Lu30kkA z?XMQh>EYK%X{TP@dn1hL{B`WaYQp6cw7}j|Q;B-R9{k2`yIn|}FXf59HoKjA6bX~r z+Owr@?D%$EW%KmgmoDtjACFXU87c6YWN3mzY}=juV7MOObu=3t=m}Hz(&6Ce=qWkt zHZuQG&aI}+pxlSP{+ft1aNB56K5W0B!^y+$kF34l}_A7%io-?h4|^f zp7Y2SypFi0K)7E?gSki?esB>4@&~wQ{o?1?EjimUGai`fzB`GzTDSC#Df2!`_f8?Y z@Nd^x9eeOPI`&FwI{W>b%Im5&^S+XNUrR)>BE5n=uxT(Hv6kJj5CN~vSsoQy|MpdLeHta z-Rzl7oCvOmlBhM^CsfsIk<&Oet*40eG~ zx%N= zcYPzfhWuj%u<0Pk=f%sW#F*y2TQa%3!*g&gE}wdB5 zV2G}{f!;SaFTf~Tz>sH&>j#r=2~tVL>4MWRW`pVG>B^3oA4SNJV*3JH#!B9)PP@vx|PG9`yngr5LxJ;s9^HT_n29!+)8NpYU<6-!S)E zL^$hS*QeH1!qHP&ngq30WpEhmOwHFG{z)!1_*Nt3 zn?&QERC`h8_;smXYA}grIH}gG(ot`(UJ50d=4~=qsu~`R#WX3?2m;mTA8B;dXyzI-pWd^+1KDr}Q1;zZS(HO(v3?q%)TJ|>)!Old6X znoQ%4G{Mx7W%5^r%$O8*hkP~x`&4fHiMZ1IpEL+yFEldiZtP^0ss65zN`%rRLuI)04GRuZ6-Z!8vv&rfQ+1thu@8+Ast3c!S2iq1!ez%dOAjsfc(@Q zB9~w(la(B#_;blZ`onLhUF|1b>v}WfRzq(um6rXEeYg3~Rt-a3#3h(Ca~{{tt{6uW zqQwrcDKGS*ZuLrE2hHz&cID6Ky_01Quv>h{&l&z*`M7&Ck<~#a%6*X6;df_yrLoNs zS*zO2Zn5L=N&Lc9B-;IPYyo8X3M+jZR!)ee`XfPfXT@GKvZtjFY*?oR#3Fro>Q zf1Ar*Tw_!WDYcT_{@3I-+M&A~vcEVizIc5~^ZTOh&i%xd8IblC(OFA2QMi9PeR191 zB#PE;95rY>jAX<3K058ps0WfHwjVz<{kzKsg>dgsB|^cqZFI4}hCkY4G=OFJ-~PD$ zX4)gg#AWg28~6O=zcGvtB^D`Z*eiggP2=EJAF;xz!Fv$O14v+*sEoNN|Dt4;ZXFL6uJrm?rVpmF;CaxZ`I-_KoK^>{S|gvYANG|jCWzKyggo}X#@Qq z)?0OGZjh?T>h93Jz@E9so|(a_td%i8z6=&w21_+$A5SO=?(g>2pW{Xg371PwW=Kx9 ztgT=E$Vrw;LamoZrMLWc=*T}@pB4vzhXYuN&=pzDomc3cmg$`qAR#>KIlH;DtsaD4 zExD(fyk~Lw9B18UVAF%72CkRN#uRad&kw?;`(V>R4uX8t1|Ql+_Ze;41SPx#CEip` z_TS0N(A)sxr2$d(xelW;Bdly4QoB$sn_w*)j`GrWqCZJp?GudcAdjy?fxO-xkM~7T zPGd^Qo(^r75?+^*+e>Yr>TsE@5!P3`P<@+VeH+qQ+oC73JMFYv?dAR^SG$G3L^%R- zLC1=AFiK0ca6)6Yh9O^0?NF+AE{dJ_MIX4YeR&IQcJdb28fvpVz| zobVjWM zd40dL98r^nt^b3rw~C6ZiMmBU2*HCB0t5)|4vo9JHSX?i!GpVdSrTpMWINpKnq z?r=E&xc5HXan9RbYgFw~FIBttTyxDS;7R~d2F)t3g@vuXI-gsZdiA;}lC#TVv&*h$ zsN`_Mz{6ALvB3O@N@-Ooc34$$h}EN2(4z(K$-3inv*7&*O0mEy<_y4`CkK0U%$8#j;3-sJQu~#r+%SS!+ExccPZSU zoQ%=L)xIJh*S^SE4}~2k4aG%tf>CDf8h>vRnrF`lCXue^IdnrWK7~)!`YI#mcTl3F zN+?W-ZYCWC!eoY{wY|yU@RA1|vv%}vihV!6W^a@Otx$r^(mR{~ zJ)x=!#8RKbvP5FG7ia##DqB^cjb0p9(WhDL8)o`mI9xn#GAAEhA%`&sZ}LPIDZ@@l zHLY?NpL2FocX%dL4fwU_Y4Pe<_1xG=U9^m%VlhX-Ze2Q6jDTdgTIv7O&6!`O%_@Q= znJPnu-Iz)y1}W`JqQ#szyEPSf$hw>3FFCk4(&A(F6_a}D&4C&ENFPB)$w=4KgoLFtK|ND@31fmc5%-IYQVA3i?q*#!tUa?@`bR)`I!x=stHn&83z0j4cATuOte5O4B(;qn?L8gvUBh| z<-bUSYP*!Ra1j*tJY%`kK{nG-&V+>0DY|`B8T3EA5jT zmPFRg$@fm6o^K5?`E^PL$|eh!WWuVaf1R@`;4PesnkEpTRme}V2vXGt(cAx^ViPUm zz>YN`QtFGJtE#J7tW;xYXzMUmCBU`-?0g&o>98`c>GS>4O4fyC9Lc#scos>9tQpf| zpgDQlO1V#UB#T}>2ui+2tmBv>C7E#Q=^k?PEQKndSsJit(OjOxlzA_8f!d@twz_*Rm!?WwfK!?_{mV~oBV{C`g$#v+nLFjJ>{EXN6h z&Cy;E?heS0Ei*(%Hu>&IuCEwqZC`X0!m6nTn?c?+)Mm=IY{i_JGnJh93m{Z$1*)2* z1=R8q#^x1KIY#*=&Q-_wj_=^fW0xbp)}CP(fA_|z+81`w+xJTuZ2_8;i+jy=+AQg> znxoN5(dJ$a%(3If@^L1z>@vEC238J>Z~JAFF6?2Qs)5aqJOH7x1NM81eT48Hf$?uc z*{H^fVUaxUw&Viib{#)K(Q+FI=qyn%68+gj<`RLL|22q!lAfk;o^6Pfvg#xdgEVYZOza&IbszG8oi_Ri3IUrmZHxM~y?N?{d(m!dU} z$WVqRb)49VGx`L}fM?xSO+(XFQ){gZb);xEd4?#GrQ2(b;iIkvVNTtfD z)+b?0ZJLtitsFw#TCxXuBKTMN@?*i!0MwYBWYWv=kfAc7_`pP8HgUmB@xA1W z-rBFDF)&V3e=tjE#8gv|WK{S__{s3hBcjg-SYRp~oaMSOD2h=rRjI_KZ?{A9zkIOE*+>a(!U^?Iv6RCQX|T!i zxsNXGv^eb@cac_?hp#8iM~ftA&uLi-5>d=WnShPfE$s@0@Tt^d6Uj)lO5>JrS?nqX zBMJ@#a5<=fid1mCl;|Jk@VRwwn33(@#B)(em8e4aDN(3v_ag-HMf2nl{bU2ks20g> z-Cdb2lgADGl9!n+QqrHnImBKr39)QLl9?EUsr=&aKmBdZi$m+2U%Ba-Ke?yEhNf0_ zL6#j~(johHm(TB>D=*$BO54%=85vVD1#Ec|1)ZEDS(CV20aSN|12CUdfTajgm9ee0 z!tU&Ge^U1^sz+C^519>LSY9qyqu1-0%l$Q`59e3UgtTA1r0R0JAO4oN(W6QUz?o#` z1Zf!#;f4*;tG38XFFtK(t|S}2vDUJDhs8p$5dFULK1Gi@nj8|9i9vVgRa<|cFR$%! z^Ul-VwYbh1`bp<%Mt)4_InPVsuXKhUC$`IC@uFvTKjq79XJCFM3*(w4k% zt~6tDVh?`vyxlsu6s(oByssnEK!X^w@6~6Dqh?}SZfXE;Ue zkc>w}Q+D~7*ySVLCG|!Nb1ed-D7!Fmz#z`*&%_7o%}Z-LZP;LqEYd14OHHeTLOK9b zjo8aJ?6~GK?!58ggasLug33Eo3 z?Y>jN<@VrsCIUO4KtIRAnuyhDk#R)8EdRc0%S*7)$i^syI&;v3(&xDE((J1%)oIwq z;opb6W*>G67;IjDaDGy$s%n4vkbRNAygh+c=Q*`J%EgNY&Sve&RyAYC&;vK zLDow!FRiIyTdJM&-_`OfAY&8T?s8C9Q6$uaqlcF;2RP$X5>Vo0stLz1up7^5D1*t5 zWfz7B7^GO)>o=UXEc5qu@!6=#YbS1Oju);6W2{(Y7!eBnq40x)+K1f$1{c=h5#q15 zo)B-=Q6KHYj@#UAO?>n;Z>Opp4fRu^PY;*3%`Ka}?s~Ixrw`EA<)(y+qM~aFI95?J z?WoKeB}Ucn{ch*BYdl9EZkXLu+FphaB>x6k4QbsN7 zMFubG9SFqJ#uIpW=&e)4c}v(s@lAPoJwJS){LEmRl}^a?9(+n=+l3e}QD#q?pqz67Bvzvfn*1jx0p; z*B!i5@@maD%=D3Y9m2kIVY_U+;knZu_2Xat`8cso`sB1JXBPaB2b{zHa1Vy;dTf;a zpt3|vW|x<*T^9VD!!4$mZOrT3y-T2OKAul#ITp)K%BA=+MXX{u{)5soB8i<;mT4TA zm?!$kd!C4p$s;zEjl}9YUMk^qerTh^Ie_AbebSjE<+ePo=C%sU3lctVGobXD~6nd zMC+D);HLu&I6Q#jIfR#E=QZzV+(#6!lZKtkd6^^qzk|lxB(_!>o41HDaS5n|8js}1 z3QyRZ&z^+}VTa75jdG9z-H#2q)_q@@0c7%^;>FAajr2E#eD9*k(G_WyHY@{6(Vv@m zR|EK1?qLzU^Pg0xeF<$ZkO2VsNpznR^YK}4`da^h7?T`D|lK|4h;+Mrd%_jmYF2 zf)6;5g?UqVdGFshZeIAjBI1o2R)@Sl=Vw9FmkE1r(jJcnCjT1S*M{9y{)o^V{Wx{; zT{-KtVv49TsEdFhj)5VH(MLrg*L_3OeT&fPorGxf;cJEz1Q^eN}aXGz0bC<|^iGNEhp>io-Tg2sb3e9aI^>y0K-%g(v@)=;K z{uEQ{l%~D&<@&w5{JeoK{#%XalBzS{$28QY0q9^6GV0o$R{hJ7 zsD)rHTdnGu-ka)qUSO`*Fvv!y6-!K}eSPLD%`k}6ZYH&0z3Z&WwH&!Mu8FJSa3>qG zLNaM4jc}H0>RN8<68u%;*qI)X#$>IVLck~!3!vb15IFT*=1T!!TvHXq07BT12_u$= zZK`n}=Mp8-AXt-DVhpBcyT^|=8rpkH2&zYY?^k<|O&8%x2Cx6F;$Fkp%{T%?`!&swfvk`enn^jFb?29vYf5`RE3-EjyU)tr6^bL4%2`0CEcK-P1D#&f6m3E8egzH}!tgn^CrpK$k4F5yX+T99-e)URj&8*0xq`yyX*38f^N-dv_(Cr8%7J~Q07mE z{p#c(mDnbvS=LTEv6HTiw(W58tdxkKPLi?|Bi?NF8M;$1*eK++l*wr#b!YOnymJ3? zUJ%U_az@ML=4`n_n#<(kSvCj7}-Sx811$XifEyu5$>}KNGr@pq~Ui0+( zry)JxK5&Og=1~^Su*LQ=eX&FTa1li$jBU-O)SY3o?_rvUVAh99V9a1eSZ4kp_Ov(c zA;t*T)uOm$Nc@>5vmZbMFmswcV+L$EJbtPD479nU=b#_wY%uF(PnFJJnXP_ByxXpLyRM2~ zeNEn%vepb;0#Vw6!>U`D_x!w~-9VJQ;IO0SUIRMADaEZ=MWw^HL{{AxGOk(jI*Uln zCQ^el6t85MMNT;==u3s|xYGEnb=1(J0WX))s~Cl{|)OqgX+ z^yQu!Cm`Ue>~FM?Kx{0^JZ*skAf)paQ?mzAxerg<-I}ut=tl|QnCcAaUB!4>lCr8& z^kwfnh;D!}nWI9P4r6E+guQt@auL*>RtwH_9HL^<<{up0CjNQ3UjVyqOtXmU{2Y%jJK)hGd;O zg7I<%1H5h~A@i?eC_e}s4Lu)QH|e=vyBs|qNu9^T{&iJI=>=)r8wQ3un69YfU44j?K_um@ip&dQ z4Gq@BXCYWeR-UlDThdtr7*66+^+lUVr{7jxzDFjmTX{kOTiONBEKgX2iTHnGJ*a=0 zo|c4uVpb(!l(Uu>G>-pMNa+M={5{M^eY!p4d-g^9*$F68U1;7A^%wuSbzr2$%UeiA zs5?b*vB<}2T%eiH8J#@5@d#b#3gJxgwZCSP%2^;>jD0o{|H#7xUb0oscoMh8H{F_( zzwoY277w5Om#jvBBxk+Orn|s%A{sL~xYakihz@{Xg`qa2l26W76?__=gJ?-4VC&fNgidME;jUB zB_b6R^&c9pxD;~XGlDV(5nV(aj`+Pcu6Q_dA^B$rr7DIddMZ~QW^v7MBF1V2Q)2AV zx~3`V?(y8wUp8Akb!|CkpvXc2ib}ZQm|BV0CrD!8ktqC*(2&|k(`1~PI9?KFUqVO- zTp>9uBouLI^TO@lv!i74_^Fvv4Sm5QYHP48Dw?^3RAV8^cobjl(0^icquP8wPPEpG z+J=?Gql(CfA)z>dddt{7hBwDczLmiKKfr@REg)6-P|a6I}?uDvqqaa zumz4pc&~owj|IQ5p-}RQ6BCifGllT`lXf6}k&+54Lcx#Y^FO!SdS20Z^icm)FT(Kb z^G}>{F7fNJsLtLw34fK?!n8ePZ%M!~>A+Rc`@_IAqQQ-Hr19Oay=?z-@o~#r-`;%% z%kj5+gbuGLqdsEGDWiutX_3i5B2q;fQ?bt5Ab)D1&+*s?sp;OOjsD+R$lU&^n06l% z%C*AtgWF<2e8f#Dx%YhN5qR@1;;iB7;!ExJr@pgaXO@I0rC{`nKQuc>alXoUS0bK1 zIBne`7atT&UY6H=Y%?jMh9ah+IR7wTRi>&ws6Ny;2)=?mcp(&Zj#)4-pAR}lQdsxCum$Tgg3rcup} zqfx>u=1VMM- z3-3%ISqq?!(<>)uS_o~Q)lV_^IQqrwBCJ0KlX~+v8tUcED%kgpam)T@?^(NPPyOBK z_S4&8+Uo0KyzH}AvoMi%K9gCXKkX~ONq!uQGTwmjd&84gvnY{v0h3v%Kj|w!Sz#Q8 z3ZAIA&$@DBr^w5*O;tdw0Aq%~452%zee=bim!+pznF;YklTNCZg@%Q>88;XcMXI%h z2CI3XQbL;`f9=1EbxT=tB@)=mxc}5gVSz&Pzu|6yQr?xO#}(rUR7ZupE-FNm5@)7H zLDd!HOA8HJ^Jt&24W|#+BKB-_+&Sj2+*;5NAE4E%5Bgu^srKFawgjCU652u9KP{t2eKz;ymk1hH(L zcEIaCtg>x){_mpOi}Qmq$CkJv^Vest+ySB8Ej%~@qQKnqreC>j=YAbt(rS*uB7O6T z+sA^@+nF&oZql~Ag`As;*5F>iz>nHv#AP>uKOlRGsn}GXUd60{JHI~X_hs_(_4|?7 z^hGQBdfQF(c8zM4fE-w?ytoLZHnp8=$ug&C!@YPX=+=LJ%2I;m>yhYdhK|2H%E|c_ z&KA(lYPfzI6GRr&BCrBi2&b}e-tkvl!YL}qlNK7J=eE>d9N$me+(T-&r=C~&pC(3G z$sfz&_VfG$@7d8dr4`&6IB%nS@|^i4n=4NOHE_3P&Rm-`chH20{o@Mw2x{X{HQUq% zZ>OI-1r#II+~Q_!6=!XGv5#8Xba|!V0Mu}RZCcp%dBz;_Q4sbh=)S)Jnb4{}FpQvG zF3V3;sz)fk3oEvpR<&WV@ibfokl_Hi^ zuNqSG+2GyCJd{BqFB+Ddj;4@#`+O|%-&EwkDeVx;r`lso02GsxE_Rc2OC+JFA~U%n zvq>I|#)l|4H9F~XQabWa680~A(Dxp2b~iYC-(Hq^yIsUMj?Pk%&T?9n$MiW#kPBlE z(dz)t>wsa5o%c7#$sCVr9*;`iqhk5YSej`gTRDq zpNA};hcy2&J^0s?x`L2Uu7J;_#lvp0p~pK*4*4e#_9xIqHp~}6dCgJUNA*~86chgI znSr0BK5>$L?9_c9FWp{UcI6<*<-yPOLJuo*vZ*$BunDtk|F?KIn^*VCGEUq|QQT?^ z0h6GoR-NScf+rtVF%M?37dqUr;)Ta|NlY=&MGEQJnZsz{SLcuP_lkcGd7l5-!;Wb2 zG4CBhs;2*bScY)dk^?xmX|ud75AG*T7crmC0`%>*iETR2z;zxl%(j@?HNumffUr+MO#v&*`NoFq(A zquB4M^Ow)iywRq4rA21Pbi8CC|I~l%%*>Y8`-8k7h}WYNo8aAMs(*V%9{=}Ll(Vbg ztZOI9PN8% z5!CPX^VR7k$xA_pZlA`N?e72N@T%v=SC|-mldOD46pHSmse0c#n%nmJ`v(sKZsUm`E*_rq9gJMnx)%d{J3D=w zG;jCy{0ub>+X8%fnWZMLZr0^VY;OP5|I9~c=X_XF8dl$a6?3QsHhS5nGA3n!E-8J2gUgs?0^2f_VbUWlR5>34HOsU0@Ywt$iMO^dbrx}u06>br<9;0=HQm_820%eTw!_K=3Ch%nH>g(z8 z>D9ci@0&;FS=A@tDaksiwPr25@wS zI;s%!gq=qk<8~aLwrixt8$u(r?eH!BlAdNXuTkZ0^W=hLp)H;PzIwgqX?;x$I3fTx|4DB zR@}lo!&WWwe&n)~Gg7*S6E@=?fS1Eh7vDw%c<`l}$LVK~=RUR02od0wA7b zNw4&iQteY`M25X0W;6}cVDF|}%8eH##%ysHGN)rmcZ9@LRoN^Y;SspD|P z$H>Js?>uND!*B6M^5^!FD2$Y8yK}t6T;8pkaRU>Z%-fY^PEz<7IamC#`ghuWrK`gE z^!a1f*CVO|nji3Vp5<|4r-?Q zrh~PCRjuP#-_5=-?YaHa+pDGf^-Xt_SplZqOqxEd;Paq(;;ufG4Oa}h6%kW2D&FdO zA4(R|Ae;_03S+W;PqfS8D^umjrxPDoCX8d}Fj82?$WV=;!cYG;e;W`E@4NJ>{KGHQ z)l5y=zGLal=WLiwT4{|gm-bxda&}lBC5=SWT(A;hY|c%i>RjO?Ob1&nGBjI#UAFrh z8*7ePL1$7nli+2@f!>rA-@ZKIv&adowE4$2ge(HpVv;(=4rxcMTkjxkO@e-$k9W37r4X%Tx6+Toy#I> z8iW&j0&u7>^}Qsi_bBT3(YCGimcO68fP&0G0mtVrrq3XeK^u2qH50H(aijN6 zcxk>jd@ec8cA%MNV-)yYNtafhm|#<3q*rdBmz0SqboK1b`5mj3(1j5<7-cai+TmUj zp^x}(Ayh?!Q$;*bn76dhxd7xWqbTfdT;~#7znGv~0jF0EyU}_GYUvo0p^@J#-Zt2rs6+9F}?&eja3cOL~ra%7PKn|6D_9R^9&S=+k;{)&womtmX-O%O6&Sfhf z)MJtDpQ}w@qMpAmQ_>g{k6_sVMPk*386Vw$F{{q`q*%M?+YyfxS^(!`)aRNN_eU2H(6t|YVZ}ASXS#;b}0>M4aL}Ggvw^vXANp^hgQt9Xb=|-m=<)J4jgRy zV(D6dg~6U5Rh0g;JDmqrlAig|>~1r-1QVRV+ktXDt#X}=dho!~$Dt5qR1jp8DAgRU z;HM=V0}T(I2tT{~PmH*T&ZL0MBoSd{W`kps_566!{z<@3OF9M`aaJ6&vV@;ETU&VW zYooJ<=A1ud?oJ!kkxu>j>3gF9?jD8=7ol&wUA(O zs}rH%63NYG{y~!vB0@#?o#T|Q=9c_CDtbH83Pyb=cMu#3G6&W%0Bac({`|a;0DTFD zs}%W4C-T4dA2#_{tRh~M0ydL%hqzl+LS9sY zGM2}x_%-EvE04+&Wpm4lOP4Qw(@JZ>y+F=lqek`sMl~(f!7#&8j?Dtj+B}j&53WEV#JbWj+mxw;!}??Z*rRQs(~yNY}QE4~us! z^cy2oi#}yHlRDIlGnRHN*2LN$W)TmqJ9R9v;He$#NF=Tei3Q0e_LqnvYPz9T$vSNn z#{J9}rjtudEfLkyaGS*b`1CfTr{Hi>4!hp zcnd6K&>`ij>Ul+ZV&<9qxciUR{F3#NJ`H<3?9(z|IwR12Quq&9fLp`tsmJQkOi&h=JU}D6y5cW{m{8OGXq0 zmfO*qv;s*=EWOt(%M<2Q&8%seN{7#DcwHiv)0rLqMBJKWOzdAjN4U_ccHpG%^U7P% zp4p`rik`?}-j!smrBgg;iA!XD@%K4%WS8QIDV3vEPJpVKxzkcD-AcP7U=eT4NS2kY zy11QJ<(`5|Plk6_`E{p9@ow*@51OJo&BQq=t~EjL{qruj!E>@-kb9UUjM7b_^s}9~ z(z!JXEZz~iA{~8aF3%3ouv4S!1isHQCIr%`nHPt*i-14@RDaeX8lX%{Z0F3lzekDSPV9397m06 zcN9P1-lOP9+*9VNvLKbtC;I@op}VTMR<*i6_}Jpu&R-cGBkD+1QXw8haY8< z-O7iqG>I1gmHwqSd0Rspvzfjo+cj6`Eig+`-xhT}9B4@FilY%kS$<^PPU$LLZPj{|tw4WZApv;7M zezNm^MF*u3u4?i#vDjw^Y8vyp&(`6$J3nCW+Kt-1Rb&JYgV?gk2oyu@+Q*Du!)@>C zB#Dp)h9U4Bu5@rSw-^qt4p{-jl6a)bM~k$uCqU_Hx$BWtv=fYS-=gBf&Qi;Zxu;QT!a{ri_g zVMk;`G0~yyL)xDr;e6(c(2x*Ueo|S)xSBk*5?1NEUGG!yB#1Wmd%}#`-XSej{<0rz z_%iwZ&E7A%JhC(;R((OQS%?6~j!)E$0PfCx65Nloa+UE9QSgd{AWfFAT_w^-KPoFdn}PhcfSj0Jh>&_sinPH!U?pEqOHvg7PG(?q)RD0H{Z74vXyC}r2lhRtj}j+AJq&P z-e2R*QL#RZ`<6u(s}RcF`o`zEu!wVML$_CTR_rSEMFBIsRFoK0?JC6Rx!h5dT8b#A zM#^}GaG@NG%L-#8(YS7I2o16U>v^+EH&3 zN11@cS&6WQ%{I4MSncaJiOI}B7;HI+q|Y&%#G)3UAM8XyQ>~2LCtZh0C7Z-L79fU? zwN8aFelVY|E{T&YK+^0)WZ)3z&C@&L{|lURJ2Qj#nwg&7^mt|LM;kLe8J>LEaDj3& zIQ+8r_wuKQlgpnO(}itkEk76A6T_;RgF3)+D4QXzk5;=L?f#8scqMo9Q`ISpN=*OE zq6rVka#izs)eiruqpSBF{poRVxvVMqgP6)z<+vGNEu*ZMm7?EKhoNYi(X zWTvf{9v^UT?Az)4-0m&;f5LMFHJ1ZEtOz^*KRgG)Og;R%7W$PsKA=s@ACwZ^X0H7@ z-isR_aCo05$gjdB?Hx2~#gT}Ut| zy~y0_$zDT1tr8)8_>*5oMSojwQ(fnwS0Iz{G>IF6btFr{ASJ zyIJ6GT35*#;Yn`&aYt63pMiU0mcH%vZCO(lxIyJPcKOy2bf)h2^2GSK{dy@>%fuuQ z>Kp~ntboP z!)r^Wc!O@iN~SyLQhEzXn8aB=4P-CK?V5H8!SGc(&$WU;F1q$DpF{1W4_`@%N^_BN z_0<1{Xup5`VB4H=7;042M-FbhO-ozq@dDa5RF6R~WUhbp)wJ9;N$B}-)nB?BC(LwA zw+2SGF`6hEd6rLIWzAnzKQT9!3Hb~0dtXBLuMY<=;{1Q!75e?+duboHah4Xqxm`od z;7b8-#+&Jq{dB&W%t9rb9Y6%=H3DE2zg`Q`MEyBV##4&Vts`rvh1X!sNCx9d_ zeLpm>s7DBtS4Rh@-dE%AN|qZv;w1(G$a7)#K%fK^y@1VAa-!V0H1gwOc0sziCNfR> zHVMZrdS!a*>TLT|Piv!fe2%7ix3To#*i>yJF>hHm|24JGE$hq@C~1tPgM+iN})gF#A6Z&+{j>Z5qxmJSiu<<;mEJ z#Es)VQRHN>dTFKV)(+Mfig23q!w(xu=?`8L(EEEoIOk9m_O@iIzx45#N55(vEH731%PV^28jSI|L`=G8Bs^Zt{ zK079uG(Om;VD}z$VECEd2Z}~O<&!1n=xIY@pyqS0si`u5k*y#(Y0g8`%dC{v^k~2? zeN|Kh_nQW5lfsQX&>=YiZxhR@@;(OgK(&)a>x&{9>(}+I#-Xq?QLNK%s#ml zJk+|_Z|pb@({Fdxn5pN{c4xmacGh0dlZv$&p}+hOQRJqTNDL5+HlK)e}Q!U0(+) zatv`?ez8IVU>?*#E=HS^eM%JlZQ*O0p~k-ji9WIsMJ z_!u!GuE!o~yQSY<3H09;?7hWQ^!5_|N`;eUZBiC_DjBda#dI+*EKWeyxi)0Ou5ND- zk=4-u;H=w5AB)_`-mxz4(KVwBFJq6@l9>;dZ2EF;%o)Zt>QZ+j> z!nF;)wdb1w%bQx=>-*v>-RT^L1yy?HINLR>)z-&nWw{o6tIrp&tv3vnopwq-BohzT zVA~yWfBblT?1vLM%Ng83$zc1BMt19YQhKbWDPS6T*7@k`+>+P?g-@moGyBD#lV{c! zi|EnS`H#yrf#B=;e$`*k8H3j6Ie%|=idYmZ=D$<&S($jtvHIWq&rg(Qdh3F`Zfzb; zhqH<_KRjD5&^bVnRu;(M%>m3-bT-D-33;7N&-SWOuf7qzI?C)b%Ww`>XR6#}VTnu( z^V$Liw1MrRZ{hDf{5aB6;^eO8X3jL=Zw^FJ|C51)P`JkK2{QYQ^@WhJE0}E+gl*b9 z`$xREc{sCza-$3fL>M#c%}P_JPU&BDL5o?BzxT1f^9ko!&FlM!`67Cqv|-kkzw zn?s(^WA6&(SPJgsS6s)uw^~Og$|f$R5SC{wD~o?6aPZ8LO7hxwL&jkY*Gk$vGQZ;W zJ0w*pEk9s6x6w9m`F{l?m0H&icJ) zQEN`N?F@yzv0u)}zh>g~(=|l;NbY6JxLTM@$HS?AhawOPUCvkgPKEi~xYZdvm3O^P zdR7Ho8rJ2)^%^K(_PjLl1En`ZM8oBe7z+-9&xhxEEn)cKD%k$BnGaD2uRb2?A1VD0 zSb!u$7kt<61B{28)Y=m<<&X{gVYfE&pxBE? z3>?yQT~>Vu5xAW=^j8enHeBot4TLZiTB!|<6p4r}4N`C24Gm*#uJ5&+L%2!ph3J;I z|9mxKg-7PEr3px?3R`iaTDzR@tGsU@;N(yWEb}K^ zEY!}{cW<)1EIH@OsB5_%4(a`ws5;vDvQivp!9E_py-X;3*VGjJ^d+q6Q(u-)=lLuA zr&f>O8MU4W3{|e@TEn>v&URKtwrDZOfkb2|pYGrSc|yIP3kQnf>(beG{?9nPaB9h}ka_G_V631W{b>8(xM zCi4MX8dV{^ z-}-C-YFg!A*X{X_zt%bwn(jU4@L5-$kh1FOifdaMou5oCDus5~tTf-r{g9`S@v-Ol zpESke$%CeIT{u%JS%xgDc_kBG%10Y3kxZ$oJm{m#k?We5NdK{5vplh$De1Q!n|ZTu zFeGXmDlJXRR*Zw4xWnOh%bU?=xO@@#7^mw0z^a)HC?6;tWWsULTz!wLEY$@|h>`OP)JkX=AOt#HE=mD?ODM(X z$j2d7j;avG?NRU57~`S6%ErNVgUEP|;pOnPiotXlRC;^S>p|RtMk?{p%*+SX|`H za_aTZUNrjMgy$Ic>CwA(z5IJK6VZ^b@}46bQlHYX+%GC!h=hY|B3DWK9~_|(4}#l& zTNkg>QJ0Ty0OtK?nuL5+Gi6#P(M18YW3B&a=_J0UA{ztOiL=hP45;l%4)l{MfDxrh zEp-A3(dfJTR|J7}%5nX$IgZM=zJVKB}K?lB%+av?UqkIHx%B-eze^x;HkOW}rj)(Dj#ctw)}0p?{tqa@j66Y)zjtZ=At zyklwy$_Mb5J_DobYM5)v@ba15?b+!+9|R&pX-hzR8yS`GKo=wnP<9 zjkZ*NSeF9v4r`fA9zZ6XAe$&%l0kthGz<4Bs9&<=*}cN&0M7kl7sjj{hX)viq|$$0 zzu?Jpr55!$?xy!gmaSv9C?MJF* z&r?hW)b!UFdUBMbRfi-h67Uw^y9p{4qqL}TU2;5l>cXs&$y#JWSEvrc<|iS(5OW*G zW^DC*> zFaFDO?|WzN+?jVKf1G{JN#^XGb@r^i*8YB0b|2d)IYJzZ)w!VOIky9oke6Vhx5cf! zupBtMfNeAqPF46%vy9*si7Yk#u$I;j9!^|`_tv3A<;2nhcEWIBYH+qa8#(Ta5n%q* zG!BbBW{NOCfwV8vTUMvo3yBh+gzU$VM7upHO%a|{ps@$qPj`DDzPf%mq}96hMWtJf zI-ggM4wcc2vF(~NT5%?El9}t<*836I81B3^eK(H-USVgBLEFt;Y{nh)=y|kwBhf9B z@BFsoBzbLh{l8;6mq5RVFAv5yTa*bgDZoCJXyW2!{w`Aq<2fP#eZJl?v7ds#ARrMb z{~GG5+!sPxEnwf6Dr}@G4&w^CehHzuW>>$16P~D9{K7*h#eo6+$Sg9Qkb5>#NHz{| zlr1y!esg8OB$nfXpbNX0;jzX-a;ban;m?+ujA@DN#=j4_z){a^Dp?LXrh;)K*lyCa zaQ7%^b@FoU`-i{mqx~6e$7)^a=8W$a`Pfb-2CT5MAlZ(f3!g^8Uc@(K>nZfASUt(O zatF{QW9lk!RlDo5yh`0cQ8nQ*2@|z1T}F~RWSKlwzx)@rhX7uH-_LM_e#$Bc=t)(l zNhP$`a(B*<`d&a>0*;wvW_B=<56DrFWmZKHm&nVdRt3#zcKYr@i}6apk&~w6pPQTa zU%|DqRXFKc3jHlwr|l%|>+?K^{1?`SOO!^zh;5{NHXvj8x@86>#=Cl3Qchz{$ftKW z=heVZF*(7+t(lDmROsf3e;ec+4N-Yx&ywdE;~N(+aq7@Um9IInr@WH5?KS5nc6~la zQ*wWT;+O7xSTk^^46R78h?90X0}-fHrstC|z-QgG(~G|5vc_>_ zVYK<>?FDe^CVCgu^{_0tHcOVEn+oJs6{_I0jm_pJ@0UJjA}@(W^Fg?|*LqI&zOe@k z%WA0tI7_k$QUhl)Et<|G?rH(7w) zan-hVT7%+Mz|LNtt?SHf)%o{;>Gv~2bd!Gt&fr?;5!W;Ib@pET$|ayqFO$HN2258K zGU6VH$Mx~nYJRqKr;uJSBDbrG@GQxiOk>)%1Dh7RmY|v^A**o{o@i0YcpC7Js!-v} z8Opwrl-kKY1ACywOkt48nV#2RTA`6Ie#Aipv#R#uAbC^?tCl#daf%ko}(;=LbIcI(9 z33C&{XMF_4#`YgFSzrLEF}>Maw@6-wi19W^b`Z|KXy!YSR|OtNVQBSc4#*qQJxCaRgKoF zXpu!Ub}0&|0;o!|EOqn4*~yB9W3{unl-v;kUMkkjErmoSC^VjhQ58Py;!@%iuJ{1Sv066u&A#{TFj$P@7#&JG+2$G@|JBPOYR`VYC#D+l>4O zw(5@=mn-t*@OjiB#E5W|;{wRuof3(3Qx0O!Jkojt5L`O4_Gik>w@+ zGOWG`Ipl31QIA?M=aR`bNO7O+du>m#(Kv;|AN6Z+_@_xs+W$yfOWo_cl!aFi_jHHqmealO&$-Tx;}Oqso&z~Q$K%Mb>b z?~x{%E*6kU?5oNgk2-%qDr5>=%&dmOzJ<&eFG;6 zePXk&eHQZy#ZoX}3Yi4~bZN=_i(K9I1TLzFO(TGNV4(_*;N+c#EglKl1djD6>qOu& zSg51yh#-S7T03YGDYct?(|9O?o)PfEF;jsUs?k`SR!>FSK0BZNtxa0N|4;DLPTFxY zQbU?DsUQ`or5Y_$F=V%I?%28VUDT93n~JxVLjBL&yR|Jzic(+95>K4e1sc;H(cpAO zM&QzI6kQrHQB_Ec8~owqo;(p}sA3XQ7B}T-m1IL)2#V062FlIMy^wm2%Po(UW<{#| z@hSk+CKS1|!t}}}CL08C_&lP8vM5|tHL~E# zFJ*j-GFEt4Dzogcz_W@61`_;J&8UAIy_@;3qb2;mOfy};V*Ejny^3ba8GJm=s0a=K zX`X+6fbftw_1jO77_WtXc%E!ADtQ7Xr-bkX9}gUCXaE@q;ObFBKmbdQ)JRyKtS^fA z8!YoLe3$7ZPIUV7Aa?mqxYdoSnQ@FJZe4-Fl=OKZW9yH1$yT5~0cyTpGFhXgHgArj z+!Pm(f+#iL^0dW(ygrJ!5++v#E&51lh+&_;y>76LPYRBTVFR$hBPPHNtZ$X0>R5Yw z5+^OlGU-v=AfC!UV`*Z?OlphKMsOw;Kv+!6vHM&tG=1vY2{d1f_3Lh>tP8q;D%6Gz zae^SlX@0J6Qwp0CQB`(z+~ex;3y+VfT_JxRIN^1Ktm5>csPxhNQO zZ))T}S>09hn(dUL?4*k1-^{%c(QU#OXfM?fhD%+_-Y3=>{AYr0W9z_-i(!Tt1jC^Q zIl)x^)fjZ#=t%3&IZ9I{|1<8UQ!Yxjijyh?ob~ zQkVRV)HEA}_dJVP{zvi}NWLQ@Qs@r>^=TCT6fHNy#aFqQ^kinWApzdP|0pZB)_Z;Z zPQ7P@oGIi|R==qbQnN(kBk02DV|;1{6FxIcgXf4Q@IkQW)TPvY_Qwgxx>LoRAB+D! zUo`w2oFzYP7*d~DIN15@O7EM&yN+)a40Ie4s4m11h0$bWfc3Y z+oIq0Zn%FX>tUhnYGo8G<9X7ov!B z!YcnS1$Rxk-IPS`%TTf*Ozy3=>HOizZX&uqF*itoJd0SbasPseEEYdjA&QGMi!kk= zB-}|j7C%cN%AORRsU0Y9Gas+f2=NLmP7l)pg4h7mrI6{8YqaT0=878mRB z{FS2KumVm$vdEw02Uj}L_9f$6sQ@FGOYrt=)0|(17%Kf8BKC#J{hs)`i$5@yop9?X zx-TEYPTd2?2dKfZvV4&VrEq2)lz=+B2Gb-nC5vhlF?+8<>hlg>;=;Eu6+sZBzg+&d z#sYU=cou^7>dfjPDDY$^p(67eYwvlYcmzu)g}^*1x{H?DkG-zKaro4Vz!YsV$vdU$ zp=5m+slDP$fM-*~cFP+^&Gsh&g8b_RuCO6dP;6lF#S{1Uy;^KfVdK1@O^U2!6E@TF zRG?c#9KI~tJjmVCO#@!K+awEv6lkb{_I;!E z&9eE>*eLP!czgu^I^AN9P0eNc`kREeDa>Dn1p14llK4v%NaOK!c-HAw8O-}-5JSme zSSFA}ItH9=AgDzLVk4w3eLcOQ7-pQz%sV8|#H5dXB{ot(tq_IJEFTq%@7^q2QXXzQ zP7m5d-zkx*6$UoN9xX6|4e^2?%_*=>hFS;n441z*J$2x4HU0bN$SiX5QnKe#vgq=JG$8uYnZ9GV^41WU#xtQRqVN($!zX#B z!fnKo<`kp57qr}6R0IQS*E1{HNAD9-=%F|8!U-x=et2PAt?2kjBd)7qnFCA>5DK#>N z=P5kbDk7V#{YqNL&L++aHC=9NS0b-o;aYSAJ_P<-o*?>Z9`m&4K=~`2U|ux^%cwORtCNhL&@qB@#qd+GX(epMJ81an=_5~Ek9_^oe$!=C$K#?B8vKn=+ zqSTbTaYT$Tb9ewwVW06(u5W*|ZoZmNo?~NN9r9OQ9DFr>J z8euAdm{v;n4G{(A%CGaO$qbt|QY;EyF17Sj0wbu&YVu)c^U+x1OM6_*_<~e+t>Gw{ z3Iz-$A7dPKqp(sQK=!(@*@83kYa86$dWhTSE_>QOB##v|Jr?X<>Rc30Z1_J-FKRwT zGUhr?RU^_89-Y*;(u$Wl5#L$3ZE(}-A-bOj1%)xS(?4*IU=^V~kKiNfZVt8I2%9iX zNe#)OO&Hjto*7l~SfQ+&!&9Lyf)qI{!V6WbLXpnGBY`p{|8aeKEiC;oIu{ z7kcaawZg|)M%Rb$)&0sY_xwlIova-U6E^jmAfwWe>5Yq+C^)-33PiwubkQ<*sk2Y$ zWE}iUO)&Ih0i0b61(Icd5LTQdXH-b%VOC3z3AV6&Ef!5+qe1p-{~4nVduCwIJ+XsgS;g7r)=ca zer=JaNI=|k1baBjg8(R-JK-a|2TAt?j?83Hsb{QA*@28eSv+&FJ=a-WFi?#RL68FJ zdA{PIX^?dP3aKr5e~X5=0Q?v>+u!jo-cw3L}Es=;u&dN7%$^<{Cna^(eMD)J_pL!t zaA6zzU6p|(BBhK;X`1=LNV#Qm(9KFu+r zn{O!kdq*LShXtBW%r&Bbe0_4GiKmMy!nQ$$*F%VgPpK&USC&FQ!%`CJ`;?4;wCphg z%$2Z|sCor?BOpOPj-0rs@*@iV2+Mjv?7vX``bWyhBcEp~Xm7l8G|yi2Gh9)4n3TpQ z%Z`jy{Bf63PmXh#l+318OSh-l)n6oy4g^i4;u?e@u}GAE9=_fzpmWzsM9B(f^Bt7Z zEodela11u)|5&b6nY^TS$}hYW%rI!L^OKH(U|$`uzj4cw*|8P|R~l{%6l z!ab<8HC-+6_Z(MNDr zAC%xS`|{ctz}6%BJDk-LB^X-5$Ll1~)R!A4ABsx(zA1;P)Afs|}bzGVBTj9wii!bDq>m&yFT2TbYG-KZzt0S7N~{NMb_)QxrZ zFzt+e?}aG!__sem`0qLO{2~)*PGY_PE=+zEm2$k<(g|Qyc$_Dp+8_?_h>#QmUcyM% zvm|2#32`};k_>=`1&0Ksm} zL|kbv``=0i7e+s*q6t%3v4`F1IhE?X%DkpN7TO$$|BJBwDF19Y1#_zfPdBg5P!=jQ zP)M<*67ONkt4eGcSR2`kiHW2GB%`PQC1)#d4)Cxbd&R?Z!x(9?o_sn$$T|e7m?KE| zibFXYvEiUWPecFZ=^Qg^GX~~i3Xi=lUrM6h(6aYH3X9cDPOnCOe2rkuMWJ*3V{3`| z4$5^Xl3$ydmO4p*>T)5m$t+WqcY7g4rm<0^$XUIixy)>s$Yn54r!5uSs;UmGI8J1To@icpa`Y#zeU6Sc7IjxMk&@Y-rg*CuSyuLMRF>}66U-ov) zM}~P%I!ThzEjeL>MkUCOMeMTIg_1 z`PkfoxHM(#?8WLEO@}5`S5U4wYN3NO{=XWs#bfcv5yILgd7stZE5hQ50+KUEWHLa*aLKRQG&4v_rxh)6yClq6hy z%48YVP}5~vJ~MrVc^^wS!+99Uzv-YT)Z3DSQdDT9V>afCu>yd<3=fm&3UT)sCR*|gMto^g03J+u zL{xc%Rh<&UW&)DZS+&yu+G)`-+z)Z6+xdUG3sMP(Q`4OhiSJuEGkQ>hV^Xf;=BVR-7p50^RLwD@T{;Xd9nJ#%2MkOmd*;!H-mohdFiKK2j9WDb0O zw)iLotn+@4toA6XJ|%W$cj|1(;jW8()+8{>QqrjX)M@$yO`2_xptqH%vNJb_GdGdF z&JXj?BozDR4zCPD``d2O0xMl?x~_*{;rZHtRexU1g{(r|G=y%NP|4@FZdZL(jY9IV z+#Ipo?^Jcss5n8juPNN0%?rf`CWA2!D{>Dia_u$US^iccTj^^uD5c{T6H$q|6}h_* zFZ*_MD0P1*YdC9+Z0GmQ?%!<<(~k~#cC~Ga`xliB6EN*Kj*j=RQjfQ#9zdaf+(=uW z&zyTV0gMA>BhBh>ETqTw{NsL_f1@VsLoAC;>WWS1iXlp&Nzf^!;3~L7kA!kAH%Bh_ zyLDYS)1q=+Z$Q^iv^Zt}%|8A1<48L7d01;E-RIn0EqBZmLYr~;FbY1Lm7cmk4z+rk z@^Lu!<8T&CH{<1}T$uDy4?nqfRdn_jqQb(Z8X0@N1N88cTgrR>C_fC~9wnd% z0HwW8sgQpt{h%RvxV@$_yHzBShLA{mzg!VhzF2b=3datm&UdQFbyUL*OZIM8QQcBd z-GV?>&jhOysG~Y&M7{^CyYq(Wh=uBi0dh(E+~4pkANN%coI~*S#?U!eYp=GWj|nY8 z18nM8dSh(muMh8+J_&m0i@EEIAsT4@)>i(Ur!4kQL-?l&%~Zw|06hC~thcbNw;)t% z==cza68TFG@|W*E8>%h;^>}xtH92At>ieqPyQO~VZSL!GonoK$#VYjuRGL)j!&-JTf2*LceNL;}{jX@FF*nf!G-dE|VFmH! z^;98K8iEP^6cx)1CJ{v*VMV9JWaI(M!DBbgVjdb|?izkE@8dqsg+QmfMkmyUQEJ2T z=99Aqw4|NP87)s-5(S_d0#Jsq6w=7dI!u>DQm6(glmVX1QkeaA(&BWKx zmc4zORywodT+`w=n>6UDb6)Z!?ssB;2T7?pY5ZxjAsMxK&hl(P`s6pVjAF}Fgk@?p zRXvKw?RulE$;qL~N#rLtc{TD=)O-Lx=>b_Rl^X5= z5BDggo8BoWy3Wu2m!FGzbek0EEb9=%cYW{Ew>U56AT4GuEv9q3i_R2D%S>}{Omh$w zN=f`75|B(;MMU2*C>3i-CZjQw09+|QmA@&T_x_tOykeGI?9|oH@n~*tWda`hz zdoR4qdX)=Q)@RLT5%XgTnuv*Jg(YmJ4Fq5VAw-?;GMaIT$$7oWcD>1rDtk14QjwQi zp7%!KqAnnGG=XyR=*dK0?s(oCl7;tYiH!M#{26EF;#kOFkD}mH;){keLX7$R++X=` z`X1PhhzH8BXK<5!a5Gm`E>ZIJKabk6Anp-2o6OViu+1<_UwQK-1P> zYO9)F_DrZ#f-3eF9flj`;xTWdsls;I4G70HG1PkDcNS@8>|4xF^-2?`_gdM{0KK)3 zsQltvbRAzcr4Lq8U1U;uWd8fvI+^@3ru;IT>o>SZUrWxp3T3Lxn5xTg>?Ls*BzUC{ z{P4j3XT{?>$>TZ#kqkOZ9hHjccCSuyuZ{qH9v|A8Lsd%g90&M z*zGQ&H>T31o@cvXs@Q)Ur)z88Zcqtfor=Jwv3f(K~iMR<%Ve zt{bg@DuVMR$M#<2I5>b=d)QQ#Al+NGh9u5Fnv<=tij?13VYojt*IhK^e#v;g#q)TJ zR|=Ra2|dIx>q4~nS?JHjzgB0xgA(6C1=c7v$h@K%rr!QY>ib3p*^ZoAzrDCOT8&;; zvOZ$lE3t&~o6+#nzbIURCa*vv&eI%k^i_0*N_XBoVS|iYy0PtIN!*yzL7EiR=gYMab~dv z$(hFypOQ-2xWE71&`g%ZNbJpp_k#a}?GIL0P^m239Iy1e$MN*u<{%SWJtGW~ z5oX_ZF7Y(NPEFQMjYQ7mhVg=F5K24uuD1Wu}+Y9-9w6XVrKmBZ~a^xoZ zNHJ-1T$^i1o2w-ekM;Y0B^$CEUEyN;&rCeW&n;2RQhB&OUTNc7Pm4*_&`DLG7V7lH?c#3jG2N4P)7V}FLr zuBgHp%kb`_<}sCx>0($&YwinFKBf^O%n-rSmo4z3RHXf>P}i}}f)XT1A%_gIz;^tk zQB_&NE|UzFNe1afs=W{`iv%`oY#Vvjjs+hAP25)_r@^w*V3Hz@2zT^q#V`IB`ls>~ z80@5n%8&TztgDjl6Ii?QuLHhx{xY%Dq~QX}km|-I7u2&)DqDOve7cqz_B%BUx9mw+ z-d#~opo)uXx2H~9XrN=@H-T!%Z}|KdH`6abJqK?SS;TmB=AIGJQv z=WP^JvYREGR5&Q5RadtcKP z)ZiJAqTrOKw1i|@LgK6ed+Pc+o|Fo~HR2Q|V;i+Ikt(8G(0yR_J2q!_TkayYIASvv z7aLEa54$lqh%j*G_b|&-h9_z8uvBBMZK&|6{Q` zG}#@>2GT$#^aLvAf>UCIjM91$UkQZBuh|rbI8v_E^Z8ugNDr_$JzHmgw#NPpz`sjk z^-_z2ONE0gQ#=;S%7n5$ZS^#>J%*P{&I#qx--Jnk2*XGB-wNG-Am^l7oPs}inpavT z5hg>2V(LqYarNU|&bk?6WHJIV)x*BCsri39!?uIt53x}TsFPlQB2ungSCuXE3mIC3dv63X+yDOBY- zb0qQ+1G9+bshr|-W?k63sm7sb{1_tYh=0j3vDtopf;5_gQ)i2%j#m1z3#cMq5R(b) zd!yoKjjw-Pe}8Bj`rw%&t3pZAd~$Ppwq-Ne-zba>)qHmT523-qTbhqp62mMmk0 zu|CCAehzU%Pg>1B&)x3&m!EL6K7}ehhu9$1@I@s;qemuA`a-rm?>cXGP7cm>E=Cr& z`Ztp-z*1+YXqdEA)uO04KcmLL;6vCn5#<>o8Zm#}jS7A2RSfaI6|0hJZMvwP*;%L^ zEAPE~X+!iP<7G+i!=4s-w0=k$z0!R(y|lqhzrfG>+U zh^{>n_&;*p<{h+Lx5H|Gv0+yu8!gvezRtM{K+APIU-x`<{Z{ih=~TPtyD&B&9ep=A z({J-NSI^$syep?|&!`@JzmxR9385b^X`(mI7QB^Ty985JHOpTp)S^~?qNz91qk}uH z8S^Wsj=cq~aO?+6)WiLrZy;zSFVH7hG7D0FqfC)y;hWjTuSo<8{Z7?EUsyI@rvL2p ze+hOe?f*xxOZ7N=dQ#?;8gzAYi+w@B7LPIT_Yg;F)b;~+6nG{CQu#XFRe9t9COn$> z9|jxO|7Ebn)gFZy-C8ZJBrZ%H&<>%46HPcToT?wHezuj+cpEgmrvzu8znlLX zO({)e(RFt{GgW-Bc7KpD_wx-UbZn>L+O5+^Gmy&tU?s6vYQrjG%}3&&MuYHa&dE8< zLvMNhL2{@6)Kgem+IN4jp=X6bX{~WOznl+sQJVNpo43DDA#3-2=Xl)6S)(|kaV{l& zdz)zoqlqqd?Sqe%HS_JbYLH!F=VLE+Yef30eC2UfuaEwf$VJWyzd2!7$mM1$(nq7psNcpCyiFY3)TcRn<9gDF`_qxDpGoGhtce~GIZ$EAg+jkvK|-zh zgGqXYdTaEA$`D;+Tb`YJhx6vyJO0GGl^E8HA^O>VoRcVQ` zBJ^A3y9wtNBm;j$0b z7W;86d+PBkg|A%QpDwx-Nqv9ldf0c!eB2M-o@4#f-1F_D+EycUjOWJ$FR9fn6Un55 zSOX{au}YT(#K5dxy7Qc(^KrD}zN)itU(Lk-JG0ItT@ zNT60)QmUfV9r=!EY@eZ`VSPBax4;W;&i4iWi>Gnq66KcI4d%+t&wCOM%D&VOW|6M+ z7*>X>dI`Mrs#{$0meOk%EtNqE$^eVA>3_iKm){b5%NsU^J9tN^dY`WlSfvqg*OPqy z@&)07vu~r-`cbP1A1vx=%wXXxB{Qc$ z6&Wo=G?_yKCDr?hL;jOk)Oc2^3Mm^YEaxR$MD#gC{I>1zm)(z zFaef?Df*5($8523S9Pr^d`qN!L8<)LKus;ZilMH3xRG6Wko?Wz7+Q*=Jo}zwQc#*= zt$>E^2PxgRueGIb?k_%B&SJ;AY8?{T?h;&n;V&x-&`uaI3vYBzzAcz3ndl)QoSi5D z(G+sibR@aeF+5M}#nJ2ig~Gv(x6~r|sD+D~!s?k{ABEQC4i7!2JwD$srA?XX~*)DP_!L7pMH{xOB#*`14Ng9-YdXN`&DejDov} zWH~rQ@5ejFhlPWt8!zcfzVa@SoL*!+Ep5}QA!i%gHyXsaN(Ho$LbHnDQ_V)dz9q=T zI-h06etfTTOB>>nI%b%Wv&Wwr9G#z2BD~<1NfaSz{+jlU-{PC?4W=Etslbq#igC}7 zi(}zM3$MhakN_jfbN{==$F?GHNPhEP1iP{iNs_a?jg$hY{gcARL3b2y`;DXAtJiOH zI%#a@b~yc0C4b5MTbRAd^aSA7W7AG*FK#mc4)6C24dlZ^sLjSGx*kKKM{C~}J+XM= zO1ml_MlM1mh*Qs$**Z=4X76FzOC;xAfb&Jw9w*~N=S>M8h2a(F%yH-SwmC2;hh7qA z9ew8<9nNg?v4Qm$*c^Tw36_$;R1Ig`LjG`YPQ_9Z9dd^D3k?qWQ`{NMc?S=b(rOpj z*S#t&+scQaj>I6l$NjyztW^+Y2FCuM4myZRf2Lf0y7{v()R&Xx>$)4>n5W z!#E8?F;%0VG-n3DaT4-|^%30Z0x!~sT&ns#q{OIsb9fR{Zr0LY)2VFhRz?_oW>b_d z%ndo9x(%tm{d*u=+GqB9R;@5AGih+@GE&OkT={mBn!thH>3@?GUtP za3k5clUefh257i+S=hBB$@WV2d@ir7%^?q*97!-c@sv+2y_OQGcFI zU~#@`A6iS1DJ6j$n3M1Dozs?p%62u!j>gzIM$i!yWkeq}^>0SOGk{C+r(&hLs;x8X zJfjuxL#dL$#rAO2t+LzGuF1`_M&x7GE6|x=S{8w=?coo$FxyRQ&nBzkD(x(V&k#w- zm6ylgYTEX9DNtRnJZ&3E3OG-* z@$R}&y!$uonbRzJTFs#d*Ccc!d1rNIM^NiW4MPiNZWF(mPfCd8jXvHuUIgB+{%@Us zW$~P{Ykk*oTmVHByAypzaN?Uk!JyL4?8?fvHu#l6^!4EJzI=y8{uATkzg?|9wevLo zoGTiA6vfTXC6=0VOZAa|7M;k0+Q&h3d3t8OZk%}^xA&&NMUF3C&oq#ss}>Qg-mwsV zP}5O<#`Njq`sB2UD`F-6OAD~6&dcQI!-%xn&o{I+6e1?RW4Fl4NZ!4P%0znN3wwPF zTH+w8gp9dph1(|jAm)DI`OXWMXeFl^aS=6xL63Ja7aD^SPUGU;zVCkW|5~D(z6}lR z_z9ayunoD*5dhYN(^DfJtE{s_=x*GNO=KE6DJyS@Sr^kXJ~8n@T;B^?3_ojV7XDUC z`+>kLejzFlA7M(QW9_)PJVzc_wmRg`kzN;!o3;ryV#&cBV6=xF}o;;UmsP^1&? zuKVSq8Ih|0g&W2pgWS#H#;DHuS$vFd%eJ@fo1SiSJoWZw6A=vL@_o9xaEqpu?dr1K z?)-Mx{C(xM=}Gnk%SmS5sA>G*d93Ygo!_&Pgc~|k)43Sx0WVzxfw{!OCla7w~wvPY`=0tTxhQ$m25#Sv|_yXu2Jz@fp6Fq zmw*2F5341l=Ctdm_V>iZ-JQ@8%a>^EuU#1s(gW7r+3yKW4|^>{%4gSqbiB$9@^6a| zzJy302_4a`J)1NYewqD3I&YU|<}4iH!A-!?t&eb73+xPH`PO^?H~6xKhreND>#mkD zWK>DdyPD87h+^P{bP**-Jz2Y)@0WjB4>RJ)$01Q1Gj=YuyY73}C);}`DTyHotCI-J zpE%zfSN!02v+9y_teRijIr=*RvIU1fCZ4jp_{B+beN0yq^<(=L4qfK#*!_5*jz;P> zVZ+<5T{}~bp8R&vnZFR+a?y*GDm^^(IZEI z7Eq_GJP5d1Z~9xbox@=O)!>4F03Mb49Hh?E#rB6FiA_{?^O*F83n{qwUTb>eTfS>P zxLH4BAc3{2Se142s8I^Onq)Gux#b{9WCqjpVkUCT5I8ur#sbIj;!Bw=U*yrd4p4NT@tZgEsODH%qnYY zeeY;fCJbngZoOFSKWq=)>ql@NU>>gn)qeUVHLXOdoiFtqN#rV6&3W!<7JXFOG1!<| zEYlRRVCY)Cdrj`?6f9AdS6s4H3DEt(PiPwbLf^PkT-NW(g<$E1IOxvas3yG~APH_5 z=~l*8(45@2#7vqx{obOt6@XjSMn0C|Y9JVNPjyVZ>lSa88SE&o`Hd{ovxrHNBmf+7 zB4155b1243S-MPiaS;Dh=ls>Qs`k%LliP)-e%j`_roju2XY&_}E#1nC^O!BMC9fI* zWUa>+SZtieKP50Gte45W4%%NC8K>u(RCPYqZ!VplNzm=rSN=E~Y(rb8ugJ2+{4 zQ5@*s#Z@u*&RfnKjm~3c$aC5e!&<&zsl>n8z4 z_gj}WoW+cS7Rh5b*Eh7E)NPBm{=&ra5A?3=!#HBd`j7lh#sLa|k=78eiLR+>EL};d zY4OGUysp4gTVKwk7a*Gn+u^WQT6FQ-i(H>KIc5^F>l}rcAcqOt=`f?AtM3ArfkzKU z4mxX(QGGe2ak(qO|;5|)DE!vdbZ@(A5)nzzJ**ff%vl;wY-wn;@e+; z-h6P9vTL~hV3c7%Y`3uJ|3oZgeESZgfsUUQZ%tkDgRYdF&h>|k;mRD~aW$JHa#J=3 zj{PllsXFK|)M>TvFM+RN$wnn;Ymw^d(2sBtAtK+bEK1F(7c-%{%gL*M*N#o3Y3ODx zkj%=G5&k#ZGiPf#2GWdRSM#CaaFLfpzD7o*7C(R&7_i^6Iic9!@R#-_pTzo%16MF4 zPC$wF=ebi)RxwNFWae92@9n?!%yO8|HPe+6Cw;77ttk3qZ6VptbU(XW!|xl%somi(`w`bXTU`g0H{o3o zonLNuAG+sk%c&Q6qoKZ(4RuF!bmdIBe$mw1G$*#s36#Lv-DzQ!Q%GM+HjR>Zfp16r znZnH*w*9DWlQQ~yBM!w-G~uSA(#tHG^d3`fUhgD+$zI5tGId<=T&(N&9iJbktc~A;&ec}n*W6D(fI&3ccQwXnQTY5Jw9uiMJW<(A0vkAWxp|W%`UShPv4fm&+`05katE zzzP~NJ>KzIJX=?@E!JW2tf(-0nVb06s_bp={!HvU{uO*|4{mPtirb4Hw;BJ2;^R_S z22=#;Se$1z z1ClTD7b+wH$jl=W{#-S;y#+6wRiW!n@9}fU`b&C*Ff%yLXi$w*@oC-JviOgj0 zxal|&1OUpBnO+^f$|C-)y3AR~AqW&ATwzk#|B1m?^DRPmNPX{-nNX2UkqwTQrP3Z4 zBY&fk5`>idmvYB^RMS6|5S?(FF^Mg{2`BRd$Ke9Wv7_&K3%DEBwraXfk0M+%L{?NN z-S?C6T3HoWQr5$i!@dskDssaSB4>xvQ$&e#P$Mun!Z18(Hia(MK&H?}nYO|roCvS>a0m{b01&r;pYxf)qyGdbr zP!YT@zWc51dW_)4N+!cqi32lvIj(<&%)~x^&i9Q{yJmT&suIV^*58=yt9=uE1tP&H zJ%}>w0`B{PvnXYTB`9=+w3XxR>GejBoe`CCTPh^)k(p!K&A=6vQBj^cHRF6o<+&56 zq&PUPfUQ3j&cGfRxnUfGg43R=#s#wXW2IxIl(y&z2m(5ga+grk#S#>OEKrdJftLa? zam|zsxe{)J#90E#;aPp?O4YbS9MVb2tXzjy6yDI~aL2B7Pt$yp^dW_%TSX9;)f{Vd ztp8sIHrbw1oG)wtd4g|3Z5zVrvc};b+CdPp6_bl6)8XBlbwYE`OHxdbnQkO!Ls6@V zm+JA1I;<|}Yz`E%f|DNI+1JW~1t-G=b1Sg1 z=9qzB7=I2d4qIr8pfR902(iG#Q;ox6?-x$}y!fp-Obw5KT`>m!mRof>J4SM1PLJQy z!@YH9@k~n128D#YQ=4Smt+JJ1C?Oh5rw6;sXS< zDFBcpupF}1|Hsx_hqV!P?Ylr}akt_gq-b!8CwOouP~5$^d!e{PAp~~{v}kcH!HT;> z@#3yUetF;TJLfv*I_JO4p1G2l$=)-2?e(ntQHjA2>I)}dCSsf@RzMt4s7M&vATJlK zNYGadC7ljomTSz5e_u@T=S1grsv}EiE({`eAsePh@T(Y#nJT)yfEqlmv|hhDS5}Q? zOpqfAgT$x<`m{4+5aj6;AyH2%!TC;eEiq3|&A@Don6gjWYDO7qR-1&Pfdd&HNb&q> z43tW)uB^V$Nov{zphB=F4 z>NYpxVF@xajtXQe9fW6}XJuIB)NhyM^PUpc zBl@>yHJ@q}JDcWL;#8eY>IJWTn;X?2OI~`y#$xD_W)r2#iiwjpib)0vo@M>>d=S32 zCKLNNaPgG=@c=i1w-(u+!eFAvjFSGyX{5G!>ZH1-Os0%)sL90j4Zo!!&~()-9W3ev zmQzeFDuyO$HVJ<6$U#(>=@}-|)DsaX$JlY4FLi$M+6H1UD95BR_gSVh#hZvL#-P2n z>I5o7HA0^roul1_Nud>+V3P5j2}q`>?-hOG8*$-mrOIlO9=$0 zP$}&Ij;MlzWX&<(2}GvCsq6r5Oc=l*-Ez521tQntOgKT=`LFX>xqXFHkK)WJjV9jo zhUmna=*05qBaY#>-NtETv2HE%BL#_nMo*rOlGfZ8?jr7-(BfB;0VfKP%|%9eDDXu7 z4SCUUTG>35-x%{8RZe@B|33Zx`x-C4A_btM7^z+2J5s3(nv#P=-Zwo&VC8e2!AVk1tOFXe;t*lo(eN^C$CXD!}X* z&T*Zt99Ss?m{cfItnC$6KXM)e@MWe@>G$!&XZ~4c$~`C$1r~#@CUIGY`8DNWtc=u! zaSVXr6B+3rY6s{xWBf64FfijNalBz!yy{dB?S(Atj$w2=w)N-vM>5LM&$i+5{5^6o z49aK<+P%-V@Ob_WIaoBMr$YO*rlD!|4l)(m_asjOY*6ejzWN0 z1(t$qr?mX*q0nYXU3aSZon7eq1Dodf;B{hTJ|K1vNkgH`Q7c(vGgV z)&$KTGMIum4|y(~AS50D+szdIRAt1Sq{MiLzy(sQW*5ZeWr05^5>*ywR%s@)cvSyD z%j6g>7%5h?o`Pg55;}bT_j(kdVx4LYQ_1dECZcXthOIDArC9$) zoRrTKMUvVWU3oEv_7p_@KksW6+DrCmB~d2!Ye|tX^b`Q9VkCaCk?L5!VX-3D6n#t7 zWIKXXqk+4(v$H*2lbuGk<1zGFrtntS4mE)vSssQ#MY5X@V*gqy(-6mMzMKV3{QhU^rg zlu-07>}H|LKtn#Q)bWelf3Fu*HI-g-)x+4+w(vgtUMg3SXuKFCrd7vdCY99PIx)y= zw+Z!#vWUTz6;+bTP)r>z&g9Z^U9e&KqL`@%7)2DW3Dk;b|1Skf{^yT}Kc|OL@5!~e z_aU%vd?aWIylO7MidOCG?_E`ul6D)hj>FuJ|HU!D*!a&;nu8L5y%&UvuWq>e^Z5h6UD(Lwh6A2)NPf}-5IbFi5EA3t|$ghL9V08KL2(69u_Zb z0)15cJO#n1Xyqr*0S9(M0z2i3F5gM=kmbFDTv&?Pm7c>Lo;mj_FiJ#}D~VZROb^hK-;F{tR zB2}JdU8wom!L3a~@rQ&WSy7@S*O-YenK9h=7cy#r7A#uzT&6iF_aGF0V0m8){^Xon z$vzj`=j)j&o6SjOo+MwANkdZ21P*MxcyKgAAPj%**lzD(U9+h?q*)(mJ+_h>oK$+c z=1|(ib}~yEu&UXml<{lj{>F3bmkA7PjJa9!`}m~4;~_$$k~{XJbl7HkZ%Y==CC6XN zPp$>CgPfxfuGz87n)m^YR7nYxGtf2dP`>dK6<7S)S#t`@+B;p`zjX8K44`zqaKV<* z`)X~j({9V4x_0NG`5Vi|0ba?!p#zK7-8mGOSshy`dKme?ypQ{@QiLDqN|gA%va2FB zT_tbyJc-Ukxqq?1cdOu^{5f7kT0*mSl>NvcyC+cpA!5er-;gn*ux70lr)5 ze|1wGuAiK>ymyLObmed2&oqG9eL7!S5Atfx4OPeh6f5&3bLm6RDXAt-r?$QAO4s|c z+%ve$=$1+_j;A0El?DcVPVgf&+l=Qb+YGq|Wum2$=|ZE6(YLlhVn#wMDZ%rB<`Nd=UGU<-}HS`6rwpd`n$FASN0WJn~q z4ou$&s+JW0_q=oVceZjP%Vb-_GzgFYaTlq3?m!A-&7@BoanY!2k45EnD*Odtw`X`y zjkShS&5c-GOgBb<^OYC$8@j=6k+a0riHhAgsZJ?rqbL(*2&Ly532&~UPH@5bWfJj+ zo9J`brhM}yCF9D|_bij!!%bT}*yRltOBZflHH3X<874z)(=30gPk^|DQ|4i0>ZDfOujPytnowdwoJ&%y* z`W+p}c}$hG>qwHYwag||82!92!8db(H#LenRS=Vp_nR4@ID(ggIhaeHZ6t}$YqU&_)0V%HK9pF z6gLq=r?9TNh&XkgVpar3f;x9ExZ|$U+yC;$?$e<)N|*J=F3ir4ukgFpJ|&^@fj^T2 zF#54-a)U8Y7bLoW>{|1vpBsg>WC-*0Zg=0AZHn_}tkXe1Mx*dSFsp=xk5pE82Ls7B zJF!#$A!Z2Q6QKlHW`9G--MX=-kq_s*_7y0pxj_jT7$U|`i46oRK-5o}_{~34lLbG1 zjNsrTs%t2eUs`>$IQ5J*$_d&{H-j0}ynzPT#&4(7yAAUUedpp_5fyCN8H&6Ze8o(g zmp^PZxC4Y_R4UtUt|3oTely&yuP}V0!v$i`P%L&6Z|8O*wPv4chA_mxYs=Hg*yxpQ z8F}1xy5zcBbPOsIx(gJ91-uRXXjx>{7+_#M%PNqSGyCEvn432Bhv#c@uayig**oEl zn}D#Jbb9~c@T2d~-0H;**cu-JU^~}oxZEwb6Tq5pvjGw{og(D^F{nI4t|S_>g#%=m zZsw*xE9F12&Vj$A8*%imx1ky_@9Tf`W~8fBIeyDU)DmEZ-v$gfA_sWQa&p;~%JltC zGPjb6nZ*@F8PSI8Wa8`o92VVr^PL^^;VXu|&Wh+(>`n~RCoYAD@25?qQz$fVoY+Bv zU(J@Z4{9Vyrt&4M6!L(OKqY-#Onn@*n|#xwjnhh#gV)D>tsaho&%2cSX7bj*@%gNv zBWYG7n?-*D1g>Rc>KxyEV*|~knQ3a}{GhSJ;$2S0vw})!myxcy1?-@arP3{`N7#XC zVVw%vjs0}zmQg-H$fn}dO>>378z&Y}W2)JbY7PaL$^G}hzI1!62I2pS@AtIWAeA>dvFDy5ieteKeyhx^g zW!FIKibEwvZhA1u&JtRhOzt8yu7RoFx3Nr=T14;;7*0YC_%L&fk|IdJM~#61ncyP^ zvs0vAPyp{N0jni6KdJrvR6eybYR;mQQDu-vfZq6Uqg)=B4<8+fib@W^XR;3rT(HPW zR7`cG>um^a6)+K9lA0O7Kd_J~S9>YK3HQU676+hSA9*x{b|%o941@8khqv65~l>^#+&&GK3TuIa>Vu(+^zQ$eDmK%#luCg z`*}fiS1T>Pirn8u}xg{sUvMB zghL66h&Q7UY~Q?w9vr;0tzIL4uLRVI+l;3_@8{{uGQOnwC=gLa(c55;+8d+sSc5`9 z35tq0V-Rd_EMs^3q+ZI)H2t;|!e}30IVR`Qk1uQi{R8O|p%ftpWyfK-0z38`W8LDI zMfxMQ-y)RsLraBZ7DKVM^xSft8AZOwq8ozXSj#wOU_WpOYE0B93+yK(w*Xl}Qi4d+ zCz?oc2)$59f08RN#o^fT1@^zP@`MwJ#0_eI18sg8FrX+KQfVX*><}Y7YR{J{CK@j| zHeE2W%ko_wMxgU$IgMm}`11VSQE!MfXfv<+rM#8FNQJQ=zi@i-!Xzkn->rynjqQ>i z(8+PIVL#Xf~TS*ah$mifu4IM;L-AL6%M#Ah*-Sqrf#HTYMe0(Vs4iL?5}(+)D6fBDX3 zWO6<)$?>0r;)~4n)vV#<^Wn7+d|q%DjAFq-6u0quHV8J_yZP>oJ1rYe`nPU${y!c_ zX|l-uDM%KXDA;^&)rk{pwtIIZP8wl6Q`N1PL8+v@7 zTGaLh{Ifcz?Y1?fw1A}0nONq7zKC)%Gb|x3aHF@=+y#yl|J(N!u+ZD;$Hf!TO7IZIu8XhcTg5yZvn; zsf-~Gv_fS1nNgz;K&XSzhl|O6jKQ&E%O>ba$NV;Rj$Fu=K0!<=qqR-lYwHmtAaQKx z7@{4+%-#q5mPTVH8;cpk{JIZFLX1(haH3O@*wxG@#n_jUY@R8nl;~F|i`hr`#T-Pe z^pb;2!IBPgQu9AgJKk~R@cXqkZOsP7ou^@SlT-O@oFRon1e193){Ll z@!BUb2K|vooS2Fil9KASjFH%gm5F0;G+3W*QEc<{>eL(uB^DQ%kO=|l=tPtZknPoO zd*#cCmmP7O*Ga%yFd`zh(xfVX{;L$59*sb?069|;mP%H_HKIglyj9V%b8tcj^UNy6 zCSf==N~y>%Wk+=GMd`>8@>mEVB~Wo;9DSgE5&UQyauCJr36GdbzB4Y^)qFT0<#8v_ zFb9E$#@b#JD|19M3o@l&&2oZ6rAh6GlpCUPCIRnH$tHMeEujlZW>djkA-2)X4e*GG z6eDr2d`0sirds+`rcU|4d%noWgvwsgR!hu%2#cjuwG}ecC5A#cFggWsqD9I2QH4hD|(@HWOJ>=;m4fbPM9_Lu7yG^qlM)5b<|hAUrLVbRhE|@y50tg zeS2=(8aisuJ3)FPR1S*Xd0-Y-rOh`sSLCjRbTFA;xLi_2`M*L{<_Kj@$4I{t2@FAr zW)6i%pr$OgO6HMhuqnD4YTYjliD1&31}`t4SQw3IiH?V_0z*N8@MMY2<_Cpf5K3fj6r&p4RXq7Gy|g=KF=8he zPQb9)9HkJ3EIpc&CJB%W0`xin^B|TGy-3EYUf$XS7U(EfRD} zVb=wcSu{_BbV-3hgrP#hXsUo>h=G(tHYLE|Mc-t+cV_7& zaBQ)OpNS(s60rrOJj_#Dqok|fh(a804O;lMYz(I37pUNq%Ir-Z*^mK7^5DAahD0X> z;ARZ&CH+U4#7oHqcdW@HGdEUpvvm{(3K|YBC`{6GNE)SwKIgMtonZN92NB=mKsyIl zTp;efRhM2DoydJS@|zJ@(UiYP8w118Br8F%`-BbG#*WCOH(`p2#kgcCq6~ZBAKpF! z+U&>c>X$CXzRgLywI%ltCnqY1f@P0wcj40U$$~)dvgFo-k^KP^yhjxd$v@Dy7?&=P zks^@PRKRDsNXDYxFL4Raml%aq3F^2R=sR!KO16|dM5tP*xRQqn-s*Bas4|xO32TZQ zDqJskQuKi3H6tyQs7(|oq-T4R4>q6`@(>CUVsASIU*SSAs?>|W+>!a)8wwXL^g~6r zRGX)!_s9dq3Ybw?Y;@n;277n@`fPT&i3XWGUHW)Ee0$hAzA(QSsD8e`&%4dcFuwEA zGm$8|yx6b|IO0qAkNvy0Ne2Qw)VKL(yW0eLs%xv}VS8?_>qDMHm;Ev3)7QGlFT-Co z5mdc$yszw?zj3wR_$}|**H+Q+)&Kt#AarNV-rmuEc;S%OiFDE6T9(*mLW;|F2(I z6Nlu7j4kuYn-DZKysVd5Nxwf`TknfoLsd?%KD?aoA6$$Y&t|w^2O0&gKA-QmQ*jPY zJM#sW&xZAGhDwag{4wbA&VM+et&m6%?PCY5hkgjYt3V6?e>=c02d;TFHFVgoMhY4Q zUDAqG`|XXYw%jA=n2F8Dl+}k_BFmL2pe+1HnU!EeBSY(9eF5R@`MBYV+i}2kp5#N3 zOB#xA=jesqt`{w4nJZmwlb!fz+$S$df6=x_e%ib)qYu81xm{AY^}$`DG*6^&=z3p-Vzux-h#s9VZ3D1i2^WN%>-L1Ro-(MD={v~DY65I-H?GJ5Nv@!+u zZN5}I42LxZ4RSazi`6yaRoU&j&wqa$uUx#i?NqB?(tl~IDmnYRB+(o+Mp53}mMZ8C zy!EOGla72nzMh?KGaH;5czdx&wr=BppuU`!r|a!|$vxE`9vuzTg3T~F5?BJ3`Z8^a z_9`&jeWozI-KVU_!2TjnhBNli>6zHe(~En>Z;P{Tl40bPH-}qnDj#&l2cN&>1=8w! zeSDCV_Y;?rD5NeJDRjDjxKI;!|3WElm&f9sWs|Y{bDBW6cgWE%S7*Ogbb-$V*LdNR zf>Oi3)Rz0y=}*DbBPC8>;$)Y_GTX%38gv#!2eljeQuQt3-I=(5W0`1u@5I2%qWt95 zxaX|IJN4zgj2(!z9!_y+e&$}S#h!K&JQ@`Ae9?#3OPlwoNp?ml^m^tNHvF{q5P2#f zxmujv^`vrFj?9)Km;oHFcip_s(@AXRT+krO3xq+e zS^O)8L~l=iUJI6#5l@L8hu}DcsCX#pvfz(R|Bnfh17NdMe~E9nNoUhN%<2>D_+)!}N0Zu0($W|wY$JVP{Ge{F-jA;F zx7u~a0D-}b%Q~l1o7G9{==`bLI=QuoKDte`iv{O#rPZg(od#mH!7){|laZ46mV&?* zj87@PH`iN7FSsVpi+DKJ{9=AjM=vx#UqxK&7QAn6Vm5g4&MLEx?+l(slSbYro)KG64u5pzjqWJx-ky>W~&ri?rIaesF5aK7o;a+{#1#R1m7 zJHiB~N$wi>N&AMlK|Zrh_TJsneB~=7E2YIhgB7*N)W`^!3)#LLP{ zQ)}$j?aQpU^75;zy6f>*YcDGg89|466p!wQ>yk~+waYK#--CO09UY&tj6T@-@%lFV zynook?G6s74ym&678K_kuM-+K!J?esdhYZB6}G#If4css@}Jc@jOE!aK}oaN=H-jG zoEKrB;CRmT%SxiIz_X__bAIZ)udzF4RS)hFT6vFNKg|+T0Uz?RIE}3-yVhq*#if^f z(Vm_gDC$n_DQSzYgS$$4K&}=oZ4VD!Tg_QlcgsnYL6>t|(AD+30|tl27X=|v7way;!G_m*u-adF87jwRZu)qXoxi}afa&CAxeRXq#IN>FOu_}r$$p|h-ei=3MhMZoT_t7rLk>WOjX`^R6DAC|;U zRI8qwnp~Rn&H>=ZdixHueKpCK^_R9Hrw?sZ{k2V6t`xdlk(ZNK9$vhT=`0SOEykg@ zVR`n&?(8&sHMjasEo#{!tbXq4i_1UYIN_Pmt*P0;RDzUbd7Zu&(`N%V+!56YL4NlK zqi6S%u}4|d$MZ1>{lPyfP{p!Ls<;X}0J$t(reb)F^5nI>bUg`Ni@pXou4{yjNdM48 zoG}`sJY%o0R+vcnURL%#)N(uL>3RIJbiUsFWPbT=(q_!Q+(0!DCud{P(xbCES4c>( z!KpdrS=W{G>h8OFUyoTHv)a~eu&0CRa)yB4^?~VpqW1N>)Ops|M9BXLDQRo?`mzF#y*cZtCpPPN&2hK47zu-)o^l>)vdG-qY^ zj{3PAS==+Hl(aF(hBH>nNSU5$c*GXC`wwN*U9+nmH%$Yhmz)0)P)h`QUOk2j|} zA!}TLmKUI@EjcGdY%=jHe0Jk`KMRp%#<#^-{Bq-|-wQXuqU}}ne&TcZs^FS&P zxu}SW&~Xj0kgY#B>n79U@4-)=F}(0Z1(1<+Y4`CawOKOYr&O;of(G?abi z=%p%BQBvHWHz4o|@XzZ~S+c9VleHt#^leg8Z%DSj*avE=i>+sW9OrQPF3DUIN9@49 z1s&X1G40gq+aptWg*+SX4$xUoF&dSC8p%=Rmt94eGDQT1@B5iSUl2~9`t}9%sm1As zxmlA|w`s908slqc9?dV^gQhvBWAQc_w0Z6LYr(JMz%gD-nmkx4*ti}K|4t7AQz~mh zq!lJ{WiWT)ttZbPOY#k#?z!jvXAj=-=GY^@N-d;@5|5aZezD}*_>t--lKcGlJT!Z8 zoVC~nRN3V)P19;9%Kj|!yY|ruZ(o%B?K1K4Lg}A)rz5{SEj*)e4jT>3R&BT8$$vjn*M}I>=K4kUm*d{@<+SFB+8IsMF9f2Txi#h z-8bYpp7G}k(Fw@g%hFlSXn_#a1_H0FzYA4`G$iy)i_%$^XzfhPd)fm?Zh!Mf3tk!H z>fFTYp;JgzP&IsW>t6BY^mF_e@RnO`&AO64%STEuWLv~GbuT1CY_&(KX2Km<(Vu(m zMIy<$G4F0H5qNKivA>Jl`A>G|xv@g?+S zxvyz)_6jk!%wRuXr5-iujtbq@n_b$^s^1YVFup|+vqwWp^m!EmO?tBVDpZm|vHc|P zv=$5>{>MuIUIipw(Een5A-MI`2U-(INJ@TQc`hG4PJCF9WO1T%Bi+^yFrF|6HRz?1 zDw@1am)xoZfjMy%e+ok`trg(BCSy z45%7X7dE;otn{0~oIXv-6;!w?R3SbuZv8d)i&Su=N~v{)0&XV-_5-UnF1DO)2=Q(T0k`yf06wC>0D?}0s=^=Dw5e_WusIq;u z)2%gXmv1F=an=(`%TntCog@M^2!A50ecxdt72gTw(KkwQ!pj=f@IfSzDHEZK@fIZd zLtv+n4-S(tvL8!C>E12)su|KA?*^*|W-VgdI3-c;A`cEMJ&>H?ix<=bv!-L-b#NEO{wZ z*taCxo34t(9s>MOdv>OZKvyHYgb;K+Er{9o^&e>RQt)!N(xU!TzXOBQY~q}bB~}OL zAN!7enws%v@zoElBSFK1X0%2`36bbdTJ%4iZjGjYzRK_u3(wAC7+3is)x=$>W&fN0 zc8pkZ?ibe7Q!m_Angzfx~`LRJqpA83|_EXo1^vwK$#h*3}9e&oA#)*j* z=o4&&e~DxB4?F&~cxk72O9}%RE>&wM+Fqhp@e;nEFKKbuow_5MNf{EZH~clp&}Viq*2(j}r&ypQ zEKaR{cH{mycy$F5e2m`@0D&<{*uJpCiEnPD4B}Bvf}nxd!`~%|{5f1qrSc0fqa!+~ zV=ptg)!$R7nOiKC4>xj+G!A3L4))+GMaGnaNa}FCb6H%rrEUr^HzE};;dM=7p223ph|QR|>9?yjd<2w`DU%;#ytvpE`bytH}s810?hX(I`zwssNLwpR9d>=8@m&(QA#L@Q@qk=HnE?Noy z^P|xqfZk%dDpJvXIeMVBT!3K@+Sl|C#ss30u1LaZ z^j#XR$nw%i3txv|31K8aauPBtOMasVQUaNn7$(`iV&cl6e4!v&`m}NdC2a_MtpU+K zq49Uyxr}p!bEUHk6Fv!fR7^CTR}22*4tqd}Nr6Q0+ z0F|<)y{=y)BG7QqE)Xq`qSl6Z*C?YRD0nwaAJln`q{rw#oes1-)^&R*K=RE+Ls$I> z>}(7jiPcSX)nBTrJs8l+2VZGNOLhO%6`g#e9sa5UEc$2nUCt$Ewa0@lF2ebWOoRYW zyBN`_XjwbiODwFwNF;0_F+)ff7pFLTrq|zE!Uj)BMx|tJ^UW(l+kGB&>aO{?xmofa z8={0MAl|5`?4|fnb_!Qw=mk2mHsp29ycsJnB!)_=FB~ZoZOOT%Sh;u=#&ddhC-#x6 zMBtQ!Jv0U?)#riYoBS~T(hq}rz*I<#BO<4Oe7zBR z{JmFpRgK`Z4BBbZ1OO~Gg%#&9Osnx~bbV6!RE>1Rg?DhTv5;c&VjLiDp$RxtU3?k|vEF$BdBOZHhl#%++@HoJfOnZU7_kIXA2OOcQ18WN47!e=?5fM#F# z$}^&t>K)M()k=b|D-Z=bO^m40y6|99&~~p{04?9rFNS_e!_Uop;N0dZ{Vz zJr#Gv^SWn8+sEu8e#{UUsuVRLQWNsReQ4@et`!57ext0ataRF&vuib1pI6wA&t1tF9#j&)?O+M~o*pS6HZ<9o_qAf*{_9>fQ1enxKOw^afFl8o+!2TDyCpshX zNf&kw^-D{OWNPc>>@(!tx3}Q6T?FhK`HknE4vUMJ`1qsZNQF;jD$LO*G^qeEs2)kz z8-#KzK8%oZXqwdXI+NRbUp#h;wOX9=~5OtX*>S>Uled549!l*hqzJ>(b5_reZyP z=`X~MbdS+bfUFTD3ci<(WIMh|TA2o)?BlmIb*PCPeP0GWs}cp5fgW?35fw7UY>4@G zy76m;H9Lk={=D`>7ZUK1_Wa*9JGWE*^^Vk2SuuntKFmeVv&weC4tV4-ShIYdAQTVJ z2AHcxMzt`#U)xo*)>Z#J&D67>gxcikdMq_eAqAGU`nD8BQl%w?YR ziKbeM!^il|Y+A{V;@rHzvz+lWnScYxtxS$$R+L=~6(6cohbo1?xi z8u8#~%_UWc1{}JNZ_#hdk}yF!d_UB;W9+Uq%lNZ#zc(+p{eIr>{6*a_i{7&|T6CKJ z0D|9tjr!r1hPHpYX~z;+>HpQ*JL0NUZXS~APzw4%zE>E%z*R{=H;tOKk1v(}25()D z;!`ZsoY8d*wn^f@zGV#oQF9m-FSQ#yMM~PB(7|M;rPG`v9-EK-o2h8=XXfd^tpVS^ z!FCs29)mAS*V{M`#;^eH(W?$cg*zPfSb`bnFek2`W zty~ZNzD&tXD_P`TGSo7~u~*T1=WCpsNH**y(f(Ftz-Gr>UoGoYz{2L`6{Sga|D4U# z305t;ir!aTGo58?*yt)HR&kh3aT))lmMB;a!vLL+eIEC$;eq}IRqILntH?>7;ppBh z_tbotfTlK#m3_3hyfvH&VQC}qxtyP zSjs;Q-30HV>33c?w6Zyg>`E6F@uwziHi&r&g5N8pQkP_U&8)TwmW=j-6(rKEAJkWc zzRndnzdYX!mHV}dENY=vuI8;B)@vuqnJA@lmw-BFY*@|pXurZq{P5Hv8`)fI3ELU{s1IFMKgs##bJ?s0AU-*e+$wO0cOFKa7?0QMOZ%#M| zHDO&Lu4BbHI>Sd8J}0$I1Nb;yBD1KlL~(K|nwZn92!CKI++rU;q0e^azkF#Ph#8al zeo_>(FMk*8(+WF`M7YH7=iwC586_Q*fGXxss$T`u zohf#&(_Q`Ri5tQt1ccX}UuURKt-H*Wm+7~9a3 zSkseO-t$!QZ!F{J>WFEdITo5pKQh9@TmRyk!GUQP5zgO>$P5_O5oE35wAw}(@)NmD zN(?Adz47D&wPs@MTmQ-s7F`8j$$_WdV+`P;=4Ijsr2oN5q-+ZJ5SXVkvyS57qyOb7 zf9my?cnCY%GJ-ATTDV`@cm$u@TBdy={SB_R_vM|Vpovp#du<0}?`lfrx>43!;;xWS z_~d-yapVACI_<@=f?rAuIVU5DbrdEagdH;V80&>akP{U@4ZuM!-~p*a&1B3{LJrAK z4JFL1yHMWuF-};E9!IkF_}q_8M6s2Pya51)eSp%x8{zq0Lo-05;788Tft`A_Db~Zh zBx$R@4=nZ|FP)k#*_ldB6wIK{8V+~-v6k@-+uksOpNnX!*{c>}m9z|mJ7wZGF*ot{ z4>w)^p7EpuE#(#VSPlLG(uW;lEwbCPpxFy z=l7O!W}ONTxz%zd{{`J`&*DkG_*`wOm~hv;4e!J5F$bW=29g_yVlSCR9Ptp<^`_*V zzaPIMT`ZF$KAT$@rz!WJ+#`^GRh*caByscQ2!l5&nf%uv1fB6va*;vOI*OK$KFE<} zh%}TJ)D6WzwwSW?Z<1z4*iR&p6AnTY=$3zqTj4YDp{8WwD*!%O6z;qO|Hh`b0)(f@ zrn2LmDpST{WIggu$4WNlDh;MP55!hIth4&JRVcU^bJbsL`P5!nVajD>q@EAiiv0L- zb#wrvR8gzHpY=HC+8fASyn={XMZz-aM@OzIGIav&~X0}UC{;iK2(l&--m`QDeU+zP|kw^y9`4=cyW!eLbT z0FNw>^!V^#_LfCwx8*s0_xm#_{L~p2^x?%R2MUyI{(WR`it>_k=0m?_9p%bL-_IHS z;ir{NFv*&UkW66GqRG?7ea7UDq5;5}HpZUFe;@foXq z)T1Ti#Z_9kbmJJlfsKr#k@I*Z9sRh(omJz`V?|s5jdb%EzM+kb%PQ_>rFj_6SiM<2 z9R&$XLQ7OF;zxfp2`}S0y}f90k_1Xnl@fT^$V{xBY*va~EFigR%RzE6czxTjT#OS` zEw*~4-)i^)!28Esop_h$zz5XL?pP{`Ubj2V*LSxI#zc`;@LO`y^^Dee!!h}3*(Y#F z4ojebG;NGoN_Yg4OfM?>`>~IGj>jOii6j8y%O*{*BX4C9rlK2%1`ynvmQG)2= zU*Wh}u5n47femNj=Sz z@O_e_!UA|rA*(2$tz1)wVCK`mo(L!I<}UGylwKT9I;AO>aIj` zADXAQ$j4z-ors1Veu~}W9r1eI)2CZ6ZFs{xAc*&4dz;gq#?#J$&12$+b^WIXkMa;$ z4*lrZRsJHgi8wfHEJ@nN%YMRh>0z0JZSBD#2z6m+e1S^n_jB{??9{ECCHM@R-p9HO zod!qYB~*}svCSrGmG8aTErUVA7KY~eh^lsb)_b%fh8{CCHec&9rM2&5dS%ZM=_R!@?b_XTodr@#9C}EsOOt4>6wQwZFFFr*p+~{!P!(wN396y3tYiInZmCHDY0W z(i9W;IJSEHrcKss;?Ytb8uRXC{xL2q@{jpu=V4P=@7>6;t)xM9`4~f=pAt_Ai1@=X zVEBOM*z*oq+zCQ7TM$*gEwUNEI2%j!2Yil{;PgSsVWyXr(7KNAbb3sW90Wmv;Jd&C z64Y3+Jm%c-2%k=Fm)AWEn>HBd_4IJ=CrlalFQ;p$oWyf@m+Jhu3B~44Zjc}7>Ck1iI%DG>o2{ASTwEndyCCUZWb;~03Rn6J4;-m~DbHRfQtlm& za&Pj5-{mp~j04ZbJ$YOLN2LtH&p4!g(lItX9n3e$ZDU1qnW@JkJj7g=zwk|vT8SwG zb5)qO6e+)ur?I}zimFRZR8KCi2hS{m!PLwd?vmfd+C+Wdi*bz)2(1Nw_0lh2(APZT z(5lr!`*Px39+Q;AJU9lVZ~Nrr=zRNdMx$muz*)@6S!YX7Rv$jC4^SO&cp5r)l%V1s zj@3ClP2Al(eR=q+>xvCsP$;j56eN)MJ_McsB2tH%v$@pdhcy~y#yel`2WVdc@2>9a z?ZFpMsX}VS8wI*HLzVTAYE{BDo!L`tSgOZQ)=GD;R&s-DBpsa2PBOHJ=oAmp;hBA( z(Ep7?mC8=GuZ~SjUJi+lFE(z+gM|H_tnVKG_Tf1{>jVfC>e(n()kA7kCum2w=c}F# z9|{X(tHuaF*ktPE4w*te_vBnm(7+FW3u79Vm`Vh3Kq}F$};492NjMHYS@6lt8?%Fp4izS{8TFI;j@eQ zx-5=#Yn!uroB2W6KR)dsDCOcl6+zcp*O1j3@k(9s;$*WFz}Z+tciZ6LV;PM;)t#VY z{Eu8dpY!d3BG2%P6HEayWs2M#dmoIpO3^jTRT3q?bSClPuU8A_IpgW|_RYZDD9VKB z95M0X>np$0oTRCG@V-8vq-;)G|6&b;2sq@O&bE$l7K`U~6z^(3e^kHLtDGXP-0zMr zi$1u$wT;&dQnrWy+>Zfw+QJ!=;w_R-#`>>Rc5|#?-j6(6r;a0g@O6Da@cc{jt3?sz zqim}>cl*HAlFoV!k9ww7{nEd4C83mC34Y&q&U8(52Dp2vOyXI}dbiYu>y(VP_I>&M z!?c7j-Y~E>>Ju(IDmcbs|D=hUDPYI^nf*X}HN8Cxn)bQ>mb0NqNDr_FE;|l7Dw7s; z(wME1Tf~hTs(ohVtxR382ALbwv5sD|a#ts;f6a@?D7yb^e!4$AFW*}dZlyj9n32%_L|lJx%i^%jjw@$*d{KXSd{tR6UZ!=n!~>24;%=9+;xP7Y|kSJ5pm{O>5= zPjVshOFR{FBes1>G2kb0-Q3b+JMqB@t4HWt!bWHC)0A8m1-H_6Eydq|ykkZ-Y-4|8 z5q(JWjP$CfT32czhO$pP7ys==?+*-)>s zK-{;b)#X&IX?+SgdvNQm(#MJ0r*FPjbxf!6q8inMy<^sF^z8BZNF#X^`!g(Y?d!tg zlndDaN%Jftb}3AiH!Y3tT7ilN8J4cLS>_~$RjUQLRwK&wV6mT4b_u&y(labnw!9%l zH*b#w6*egvz)X1=NcU?fM+P0?1pW8vrjFM}o!AuZ=uEWq4XU8-*M!T4hqVF8Hhf%7 z9>t0bjB-1npik(bKt;(63{+c%bxG`Q-fwIfbOaM&1`UR1`!W>ciC9)rqB_@{jx|em z&wh*|4f}4om#q%Nd&>{`17I2KR166N<4Y^sxfT{%1KuK+n+{#ydG@~nR35>a1dNlj zHrBdz^$x?6%O`(rEh<`Y#YV$TdB{Trw4ef=VlsvMVZXQ|Lw zM>TJ^GGl&sqQlUI#4thp0#m#@ADMD~ZKPu_ifcvD(1ojTKq&)}$vPmUnT(XzQI#@# zAg~*C5p{|B(!{>wX3N*&4bdz9@$Y6zIhVXMkkz46>Twh&04jN{X>inRVL^8+Oopoy zi^4KQ`wJmP?0;}>EqjN?5-n~oA+IwPKK3;2?uZ)vU<|&wFT1=}uRm`3U`7~!!OYYx z*KSN(7CB(bhiM(#Wpb}_euu_FEpB`1gUO$wtW&bukm7r^k=jmy#?*3i4`=5m)EYs@ zSPHeD;sX8kq?@|2(bgA*F*)R}j1~g8b)?$Eg2(Hgo5BfFr=vJt(6m*})D$$5h@@ni z(NPzsPlNg3B546YH7Tlg>?Z>co7U@{LUZVFtt-_~#hrp=_?KjwmQlLzmHm(q%oAog z&tydlZAT3z!l}b9H-Q|bW-zW~`S+$#ch4YUyh%9zh8of(Z8i&I$0IAD`6E10Wx@Yj zHIu;wGo4cn%Z*37J-f#UVz^`q4b@m^NY#4Unf}#_J8yuB^SA@%P?G_L2ResyVm(xH zQpV5JaUR3&0%mQbkb}c|mP)BFV>m4^u-nJ(dZMXcPHWy; zcfwu5M=9*iNp*NX1ZVbZtCSD1q%8NB5vq=5)GWSTMa|GDBdQ z9}5pv$nHz#Vm2wFD^#SrD41?we(r^P_~9e2_pvw?V|~;oAYldv>|6sUO}O3HdzA1&<~4KqZk< zuMcuTU{3n?f8Sy@#A&n9#D~TbwxMaU+SO+{gQ=ep@;adM>#ks*O2rRi3&3~F`mx8X z1a-}?LrU;@%TW0fSE|ONpxJXkEr*;=+6Plph4F689F`6qKUGnLa$%! zfK9KPyLWfX`3KBmipn3`^kY|9hdi2fkhcbZ)pwot!y<6>Vue|UZW;tbDIKXx1mJ2( z3Hr^*ptBcd?!rvrfqOSgHe3Sa*&UU2Hz|teOGgYFB4Wg^Rd2xOS;*d0x8J2kjtBOD zYlxWuThu(0n(ey8pHUnesAQhzn-B5}liX|4hX?h<$Z$(fV;G9|M?bg~o14z|U&HgS zz51dSsXm~>aFRK-`Xnj3@3?3h)kwN4*t!BHVIzo zBhNTQo9`{xY*kaycSu)OQInm0%;ZXQl<53ULZPo0D2JY8&ZW}vt6B^)cS2w+n1&GP z=?Y#hUnoZD&m`ScijCwON%8w^_pWo=AtylW`E-7aVM8q7@-gQAi+Kvo;s~c{X@q5S zXUtFgt<)c+y~?rw7=}V}?=uOWOHC8~Nu7sErMeZ&jfoLUl(+IpA>kk#na)yaQlt{? z%N){KKM+pXh6Z&k93DsQ=hzX!;)sHEcp41&6M8vMi21To2M0`>AP(Ek6@f>9es0eG zE{+;PlQ6<5L%#%SprDPwVzttO%OJ(GIrPFOPN0{QhkT$?dJ%Ka@7<9%b)^cmZN@T0 z;ShjJGe9xH6?MlD`;qD#zJ0LdaP~NOb!dO1-lrqzF5^9d)3GCYu2vUXHEgN3lS+D_ z$kxwJXB^!Au>TPsXIHc?-C)=X^ikDd49D6vgu4}cGU?gi9=TJWz+Xu8{d$)PsgFNL zU*bEcB%Frx*g^AP%LSv)k+O`@w!D%+L5V>`Mj|;XY)4}b`2>}$1{4~@!=;eaej3O(Mqx}^FY0Yq=Ke7o+#WU**s3;&Xpge-PaF|Is3HBP z&4yrvn!ys_Eq*kb-~pBVi(lH#Qd+vrZ9B_xfNKqv%$j#n$1u~1<@iLHT{ z?L7WLO^z^qC!x``U#}J0KH0y;&76DgCryUtT*J|k2`tYP_cf(^;OxWxGxFrwE4+Ce ze=LUlk}H*EWiw6e%3d`^)FOvVgSb}e2VKL?(4{K0RbNSGHiB!T_u zG>1m9?yxo8>G`&(lY2ZieRs;OA=kpf(87Y%W7+B4caFxQPrS5>%+s-PcI7#2V(PC zz)C7tdqUxt6v&s9@R0f)sY&}IX4Rn;Gyas`rGZ}zr!Uc@(E6;MdSTk!o36t|8s`)| z=ag{o`fP{VNmc4G1?n*=+;oe^;&Qb2kjKD4Z*L|8!x0>+F;uFtbQIk{@AI-|GG~EH zbAbzU0SLv>-$4>oZo47mX zIW%4!(BTwle+rbUiAAt#qBi0_qX$90Nte8C!n0$6N^X~hVV4Dq(|X#E>@XIK!n0^L zS-{Lnz|_izJ#Fdc?-)spPn>&w2f-S6S$h0y2n0%%#q!v)ZtOwhVN503l!l-H7?Ubk ztZx3jGwciP_l2_j{q#U2SN4ga>=TxR;SX8En9{Kxl?cuqMt3N!JCrJ4k&C=N3-(b1 zS=xCVOto62oG`L+f?7C1!ER%u>xRVYf{aoWpBN}UVLj;del!Z|b$0u0)0#!m5N^C$ zQ{b8maZL_S`eaHvBSev3o|RXgg{V~AeZ@(JLzW!AU$(aPkQP4tV^qp2X=k#V@MXv$ z=e}u3TG~uw5>V*ddBTZ-gPP2(P`1C)C3?H%M?PJa^`|T=QufAYiUuPY4rO&#qrF zl0a!mpx~vlDL-=C!YrG@ECmvojjNklf{m+v66g-BY~#N&)WR&3LSbpbo1iHg{Ukj7 zr0~Up?6oTtJSYMllqyZWVRS1yy8lj?o)Rwl5?iO?2-X?w0~sHA_QUNQ=V!_bkvP=o zqC#O>vXjUEI@hmCP96K$y*CXU9RZF?mEaVr{+Ft`%mJ3RK!;M9%e(fePtO(I2_H%d z97qbN6O7*UmavCx zLU&N1x$}6V(|DsJc%yhelAN9IOBh{4F6UxZ=1Kesl{Zo#BQXSeG(rgDpAlefn(Tk@ zN#~LCVr1}Q#A=lq;TteKD*Tk(qJ8pmYFFKf(O){58BMDCS72mNfH55<*We(Yh-FC7A|xmkPuW}byY#%Q zv^-%PJis(QC4Lefeo|@4cbkqcYA;?llSTcVaJ!6futpgh9Gy*oIA(DelEV3^Tx_-i-$TgXO9*^G0j;y=y+9x@kox0Xqenz( zFIPz~*N<&|Cj@M=kwa+v(48IV4i&rgUMF0lfIxzPPsI3=Va@qAgkVeb{XlkBf3`5~ z>2Zg$xHtoYID<06J}jNG^~bv`VVtjcvkWG1G#&KHAq%U=c-89+rA?63Lg(I(yd?!&w(IDv!OdDB#a1U~);5CgUG3_&UR?Uq%$Zj7WWtZi%U? zm7L`p7J?i14*9i16(U2L+CuYtM{X4~xurGZg^)Hq%rT-HrXU7Oy5+=`bxq;u)eB?X zBiJ4&VB^iK(`UrSWkSJal8hftQJf`Te7#iwM0^K?d< zwzu6-$wO0%M^g*Yz;^l4+m$a)SVT!!WYR-f{uBo16ZhRa?)!k>>N=x+Z=5ThansUfP2GhDx@+ z0hb1q-B^H_>*kTF#gnRq?8LKM1Z#f_l$QvU$GMd9{BvYNO~+Y;5=qtWJ6j|UD53-u zq2yC0#_n|(Jf4ue@Z%y@P$8C6$;e|Y-Hto^v6LE6QmbK8tA$|WxR9(&S|X~1-hH_r zSWZCT*%N7jx=1cW34z)8|a@Pk!80=h{Z^~L?x zAFg;J=@Hd|o1tENOKO+(p#oVZkngCOWx)k}92MeAMU+cLC?oV+^hRPc;g|5y=}G$# zYU$hveb#_MakyeixMF$fJlK3CN_K}(7iDtP2RIEJkefojn?R@$GKc@9{TDc7yv*cS zAU9J)Ia6ekNZ&Rq!=!W_kpT$F08q_IuMVzQsVoc``@hiyt6>MLg%AQ<2>H(5{{P@u zFISLfZuXa%p}t3i*vYYQ$c2lD!uc0YF&`bahB2(qn9tL3_d=MJ%1(|i_BTtZvVn#`0P1@dKY-0BxpkBK3mF!~&mltQEKa^9QDYRcE?~D9Z-mHRP)~unz zk>G(58#U2eGLsj%tPZjX0?V7+=jUBzEPuVKwn#6{DITBa_JPaUB7+Xru|wtCpWe^y zmJhXUqy*7TSc3fCi_bN$)9`O(8x(UG4uat@jaOyuROj1@uB^(lqr;=yIORR4jc!h0 z=7H2ta?+1j*QO;%8;?FYhwVXSZG?xAinLK*@-3`NOq8zHG{cEX%9niaU!?2|j%STG zPPTpxZEtqItGlJS+S%Jy;fww+2gi18tG6)=H^!MKngMPr6n?yWV%2PH_0X!(sw?Jh zwZzH)o1MxrAbDx0uAMP59p3v`9Urz#n`EU1Y|j#Q{z7_I428Cy23FSd zFJEA;*?EGo`ZoW_ zt?Sd*XtxI5R2y5T0F)z;lqk2BT=jpzD^3PgH4r4Z3Fw*Jvz>vi8?AMP%esrIk021C zb)XaN-rQV9EW|#vj3QgSQ9y8UYs!clOuLp*k%7S z8N1eI!7x>RujdOg9FwwvXa;(ngxZTX-(r|9ui!3o1@k;O*3|`Ar=GW!(^)u%-5Ugl z08iWSl}@2o&=pOvv6#~trme1(L3QE0;I3J=9=ndaQD93WS591W;5k?`lZfES=4V{% z97E)!I*;GV!TXlu~=vmBg#@=M+3vYwF+S7DdM@c#6)suuqN+ ze+CV$kLD~6CXkkrwU$JWs7b18r*ZWQ@S7H=S=ef)(ew)to91n9FAwhxA8fq^Dv4Vi zRrCYg#8xiONaJuwqy5Ky6^SOd=YhPUxs$ua z-d2E0LKPf%D~w>~*mWc#oxZOi!VqS{h?NEMje+os%bkIlpb*{hK zk%xfD9U^WgL?32n#iLF$cwhoqzpq z*XP?gM0sZw>gzk_&(0z@oQKrVG!(w+%J+~9n{`y`=YBHGtyE^3#81)71AQ2m{Lq1- zvv)Y#=yt=jWgvlB>WV&YhyG~td(oJQbirH~sbUnHd6d$r>{md`u6%UtYU;iKGM6K9 zA2X&#Kb1zG#A@IH;ya%dJDv~_&-JTG_yAEEuT&WjzEEb1L6D_Myz!%C@khd#0=e)K zueN|lvuz?Ne$oC6bd5wRjRaHsF5rtun`r8qK z(gK?N@Mz{J(G==mqwx4XCV#ynB5iah?=ti^*8-a?sdtPzt#ZkthWd193iP#-)?PgLG3`! zWmA+_yfh_MSmcV<$3!Y*sI^o$`cc=%^-;^ub%awo5)eiiIIu z`H=XtN7oH^hCBfy?+5+{i7&Rtv=ZabYGd$po+SxioZvSfmki->nw;SY9an~AehDBy z{?E}U5|nUe9S)?}4Wig|Em-?2+rv`0zOBl}E8jFvF;MmPyCbU-d9qWpM(2)W_7YyC za$Y&_)aa#Qw;jwG@s(VX1i!wF^;5;Io9I zJ3I{sY7Kj8L9Vy_;q^FrZD`Y?d(`4N)X(=WC)+Z;Hb4o&S~xp9Z!pHZ>Nk^vvca6c z0P@!L6rt*DcS70NEY-eW!e@NsraR{e^o`EV)M{^VUc(sCi;`g~qn9b8m2p?Bf2vFs zSnpu1FJ(8T5J}IP+TDh(M8h-(-33HRQm>BfRC6A+cgSs0Psl`GUX3Y8J14tv!cdk{ zD)d95^ovj~jNhJLaw`V+?kZ}ZFHepSpB=BzOm4af*f-|bCt{Xc>ZeC8?SSe2xP!<7 z>1f91M@HvcR}A}U*sek-z zxeZdc6%u;xM_INKxQPvcw{O%NO+N-+`>` zYY<9$EZd)qiODZhYZ7!d(ydhFvP9+p?pz6!a&&WTHBdHaD6dqNLztWsz8RvQ(r%eud zE1=qvLS7hpv<+aE3TxU+zsbD$Mq$~}kxR+GG%|Z6eY7FpC&rrXgixq| zsiSgWu4zLfj7?8e77yR}LVDBmyPB7ytPu%+E3aS4A0ovAPkdKaHx^b0*HynC`4U%072^&Q%Um-#!u?fLzw1ae~HXIL$ zmqjh(>esF1x$pa}k!Lv+RG6hDPEL=336WD+R@T(T8pBCZqGDUI_}uI`>dY44lQwf1 zT2KORaT*xlT(#6?_mQVULKu`lUYzEy(L988@F4fUVoC5ErvD~}i8m{HDOFe|U(3g{ z9p`MOog9FVGBbjx0x>_U*s+h9((+KjW)X^u8hD_b|IdqNnAScw6I=#B-RAJ`;E~te zhz(ad%zW=FW*J~RJn}KL075;Zz^PQ~gcIrP2UbPkEstMp2wov~!yy7Qhj5$}71T*` zD&W9~5mt#io?@A)Q1aW)cLZl_#do5_3Q_U20~(2sop-8yYW*;5h;x7Y-NilIy26>u zPAtGjE((?We`@L^xUU(#dm=p-nAJgoW!1V_z?wi~k+i?st~;4FXc)9IC@?UCd`ASY zdf2^d53QVFBHD%gioVlRy8(wiPHK^)QDRFZFhJGA`%QDwdY(s;sijSAr<}lpPNpLf z?Ey3`v0B4^UzRS_kOVLVMU^vo*f+WNf6YpyR#pfVFMgFnXQW{&NYEITO?-!aiHsN$ z3s8__mL^jmCVa~KYLGx(Bk$5fy<*8FE2ty>MLvPQsAWf`l-JW=rR9g8$`_JG!@mQD zX?RK5)VLeBX{`}dYEMP*z^=;5gNl(xXmtC68WKlMR)vgGEpJa_|#GTtf{;>ZU7?b zs7UPp#!026<2LV$t{9^My|;>Zy*!RyQJNhNdjSv&4F$l28wG$cR;Wn^VWG)mG1Y|w z@F@P1gvUZlJbdeYDtih_E%4*J*U;Di!i0CEn>z{aPTY1j%e5Z+&SVC+PwIYmi zY`(d-i1#OyGT!lM2NLanzT#&{ME#+}R|~d)4_ktn&;&Nxs~31m4AlpVA4TU`elY4p zn8Z_a%5#q733vOcN4f*Qal}&-$#dG~v9T6~?mlwVtbeVFb)U!3kkeoOKuKC8kEsS0 zEK__c#^`5}Rf{)MlgNTU9>SaFSvd~qL7<9x2O*6Vk5Yhx9-VNFUr06Gm0E9flahB{ z${4&}?mX0=g)1AqoL|Msv9CwCkxPn4(NYc+e?48dV{zP&-JI}%D((7VuQf|H6DaX4 zhbYoN(W@)*gu~K>)*ktVp1o}b2=J_=slNhd-))`o875y`y*%@67#NZ8KiqxtXKpQf z9SbFJfUW!JX|>luL_x_YN~(e6khZhHq-bn3Ix3l@_$@uj=j@CxAjUPPti+M~MUr*F zr~BsafGAOhVN|Be5C8$-@FYbJQ$7K?sD)DHPCu~FE8xM}#W-p*j(T6tr8u{pgpDke zX0vekT$QT7^i7rDOeCs-9DmE#lM77_B8M|tD9IdW0q9~(?Tc$h@2M$?zIbRj3{zf@ z-CcT#n=uIHh@(!IyPCl^XBM8)8ZY-!RL|Ia$vMhb8#-}2c!Hq>z zQy9{4Ui zDy%magcL^fs)9e1!hV29W= zQ=wGVI9r=mOv(>>McHr`qLJ!3h>w7t)2U)|cT#ZzWV(OEhMqy){jx0drA!u27J^!s zP7iM8ZQ{F|HV!0yz1}Mfq7V(ZQmof;uX`qJ)+@pZnhxo?vzi@%Z#A-uoN{=j_-QJ`)~;CZu2{it z{he-I=G$651#A;+zV@tcdo=AoZP8^@@<44Xk~EPXtNg-qef+e^9v9Y1ZZtVWV3|y` zu4un!(_jSDWzVqfJnwB&MV4Ca#n7+jd7ayol@XVRubX!d6$i4RO>tE1j4oo${{ zrx3Mj8vHAaU`1B>rutbra(As!+U=$EkV5Ci>J0bVYJbJ%(h)e@?9;!I*7mR9Y!Egh#l}u6 zOgD)Y-r6pQZGi7S-_?5nvxcDuC`9B+=y8~w!ur${=X^zLIk8Q~DaxLa^WGlwA@yyMJphZSfXqIC7IgSL~!0cR*d=~p9A|qHpzF1 zjNg=GaKVYB&tA326!VlycJaHF64S_U+0XyX+2}OouQaJnlg}uY%;UtYWL!gm8~9NI zuYuM1t?!?uTJM1-8MHz2S1(kjBJo>5lU-U{`K#i5+R{zKIosdpir4YGvr;c2nbaah zk>oS}z7Z}BD-#;^5rIhDET&Q!kr*B%?egJY89&%2hPumtJPeY;SChgv)c2QF{W!I{k0A zFk~)rYb8u_Xv*Zn^9t@Mog8rOD@0`mzKy^hU}SYMTW1){*$q)Nk6pj#_Hc zBSi7igivK?z=V+m2#%gloAYD2K^5Qm{YIh4g-`F(90`uQ_=03-d4&wx;oW)c3uXsr-aT#e6sCL2I6UN$;<{8 zSZ5~~&h)d{(zk2ybny{^Xex?OHD|yY<@$K#3(jou1Fk@0UhSBBA50fIrItjiEzk?O z>3F5Zr-S&PfPj#rC!EuPt2&WCsDJsVEoXMP4&uiEWbtpu0cc3HC#LakGyMVw*n3H| zS+pYPy-FGX=qExlD+cfQ)I(e$^sNfq&=M!5{-cc=jJVQA?>0H-gy!h(O1|N{`Z!c4 z=H^i7{!n zK!vF}gjYQvFxa8)6jgyyi8g6BQ6<+@h|x?n;}_$^K*8e%W)y!CJt>IWrR?x@|4ahT zhfzs53G+f_3-*Liep_1oeY5KPq7#%;ASS5;!ec3x`laE{|0G?442>Y}d1mh+I?W9~)@Aoj4oocho*6KrBJLM|z8|f*!=EN-YxtE?Z~G#Q zZF_5gc->sc@N4>FKNgeek3rnEl}<{l`{CN3G#i#I+`=^11*l?+QHN%zNIwbYwYk~B zmj~MTi#l#_o|~Rp&);*@1xp$!;?)#Ob!csv_x*g&aFqAwWM;?nAb#a06df<&V5Z;Q zmVUM|*p%AUFr$TSVyA6f;-akk6!nUW$RNY^3oL}*kAGo>s3RO^q#kSDmhluIQBHmR zJQ+I%(-Z6(t{5xw4`OO^;dwO7cxIcpZhsVcPO}?1ngQ2Dd)*g$4v`>qpuabwNAQkJ zU8IH`0z1*uVXy;VI^9KwzdYTsSrr`eK|}@J;D}dH)8sEb9;VV#mm^0cCJre;nyH$J zs>IxiJx$>+!q8&I#pux=j3Gorlqgmx2;5-oswpyLq@V0ke^X8MjGsyBK`btQgBWne zVPDz%5V|bdKWJf9DjBxmJv0jc#zq22=B_k7_qQdNv`l(W6wsKzL0v2RKm#y+C*3PK7tEQimyz}<=$#!{WP z73mB-1KODMYM1TkvuC*d*xhUMl*g7Rw_M=G=xI;eQ|YuZ>~YI94(;w{hTYFI$LWY?$v|*rRs^U?O@2I9KiFDPg6uK(@EMnr;Iw_a2m^-Wh34)R*ti2Cw`|jC;LeJt2Cq^4dKgsm6YA%=ew2pn8{JN z9y4n#o*fY{oc0uH*P`)24J~c$083Fas~kky&?CNm<)|_-&cs9W^qAf{2;7najwORH z?=Ifst%~WY!u!*rh!%Bxe`hx&fK@i&*XO8lK@U`%M}p~;B}T9)6J;(S-T&|kWjpc1 zAcUG&xq)VNkBcOflM#_HiP}u1A>(!L;+lc{eo7fYUIT_7U3yjAYwG1|z0Us-AOwldwWUl%mQYsA1v8>|GWE8mZcrzOgErHz%A~1k zv+(`vUIUC4W**Pne^L!oX+vti&6z0T@U!@wa+`gELOQVKz~)|x-`b1)VIuT z=?|S*JN&<`fPI6gGB-|Fbf=|XOU1vBVXa1E3p88y(Wk;e6mgWQkAWvOK`}k7i2qWk zzw56_I$)KO^#t7T-ST`r;v&?KvT^VrSwc-;i^O0>3`?OV*1O)ElCKx4%_$b2Qj->m zzVjw=^kn#357^g@DziqU!!h+_*sTYg>qZS*PuKn9^78bA+!D&RpiXu+CjznP)% zmHa|B8w{P*!A07koy>?v$<)4@hrqKhny!jONR@!Eno%fP9?{c|FOEDBT-=CKnrL~< zG9eWmV zkV{x*p8B7oXO0n;2T^+5ANv5Qv?lml&lf3v{oSKDjg<`~K zsd-3PF$YQU*BCVo-l!y%HQt*FQ>GtMOuzF1IN^0;BGf?TPCFg6Of@txlMJw!5H*}n zx#bp~t2km9K;`Or6*9)Jq8KSEV50ZJbH1>Q>hT?CK8R^e3=M15BSIA&|5|TU! zkBaG0KJ=kKgFU~Nrno0zMp}-UG4lKYm-vG^aR`$Vpu|R(<5TS5ToRHU2(P?c6#0RN zw-}hFmSF>$b{;puP&5aD;OUAZlK{@*VzFVxoXo}VlV_wgDi)65GgXPl8I^#A`wMbG zl#s!}BqVbXUJR2&?h^%;#SCHc%vX>tGqcPT)$@GYU|JFq7^KcwzOKz-!y;!JUkFr< zJ-oZ^pU3`R9X8F@=bje=4#%j=j~+_o21)()lOyaS)It4Jm~{@5P$?$ zbJWZ`nFDzh4eo45wd#^HSlr`Uj)K#X7enMHpo`GQw3HcfMOzBiMlZHql^fn8C39|2 zFj4V{6^u@pk453*n>bjOgrot2s4{_Yr>#3|SwObf#X#L8YAYNMXSWy`CX9k4`e?Nz zS~DS8;%GHuX(pwQ#rq1^0&7a<=AdBC;*miB-Ou6KH6VQn5L-P14E7lLMncV2B`#~r zMI<9QZsR-Q2^uBdEh5!q*qrt>8(p3Rms14xZjjpk0NaYg&~NDlJNBZ;KGrq0%EdjU zGZwi~S%z^@kU?NFQU(ZO%j~MwB{F}HwMAGw^Ht538bk-k#_zggHTD=a>ztX@DWA#H zuq{v*|KPJSCCO-70Z%?dDYWx=q_&l3T$0p9q)P>ztT+}8?d`7!nE+$6iru} zQhHMp)vaO6G00-qDkUi0WxseK!wNu}XP9k!ZG7bKMpMmRV0i)WOXOHdMrZ``Vz&Yw ztVbw!C{7+*c|MUtcIxNNYA;?^r8eH2A0KwFMz+kZTy*JgiZ3T_yP+*@)lQ>IejeSq zDczYLpo-k`-AsF#rN*zK*$&mYpGWUzqc2@v!XXF#ZRxSraSH_7x({;=tT)CkE+R&n zQ}oK+&(ITN^2|g{!_%`0A*b8xv76oPDg)zG%NxE-#+<-bCNYiFZQr$Z#nMvcjJL|5 z>pA}+g9VST6EcCU-2q*l{*Z+ms6l|Smhr-dsB!YPlE8yt0z4Br*5#0Ee8iSL_Yq8U zop1gp+?`f3q!qui+ux5S-?^HgxsqAIjgj2BBvaRq;Iv(TnWVl8BQvbyJ~2~Yc5nO3+jJI?_=wA;f9-QihLNFjVqvu0`~X$d4n_0cY_!bfar^Afl0(Ye zhM_jWL7PD*J40y)>G}7>lkU@Uqz2MJ-PDn-x^&ZZgS^OxZ$*#W$q)9s^E@uEoJ0&8 zoe!@r0aqMH#1(dqO3}%ERdzzVn&g>B7xb-!Q;;jJ)~eHR1+HvuV-^U7v9Orm!){gE zVx?a$ftc^O9Yx*NSJ~r(r~QZW0N*Hc!i!#sILVV>l`LI>po~d&tLAw+TPq5-l=*a{ zsT_liAC=^xKJSO3KZ?9HD2ut29397a28dvyeiYenh!b$xJzq3AdM~CPQo(<;!JQd& zZ2_;Y9>A^c*UfS!*;QW^WIJPXyly-*8Rck z&VyHtQQZ3L_FnxoyA|k9(2MrgLqe*x7*MQ#oKTZK%?Xt42pVabe0+6Z?+AF=f?}RZZ zcJ`?e={3L48#L1z4>KE<{hHyGB|?eHx=_o)y#ca~IP^)6yO5cj-d885;u9KJ;60h>B2_SpJ+RTLyW<*no(#ilZWWf?8tlS2=k2!) z7IAv~cRaloCYJ?P5UAR>nQl5nTWV%}2`~H%1Av&Pp>DhLzVG&bc1O&vUcu1tV0yYSN>?{ zHETu>rTpcn))3ycSx&u3JDsbaZ79hqCW0iVi|I$>d3Vj4FU7a|JA=;o?{he%>;I-G2*|T&M z_0Xd)X68w%dAbo)y`AF}7ARAS`%6PuQkq-$kN-;4beZ8NO@eT_U@TeZ9 z1ZyWTKIp{2#3hQc>B9DdMN$~~@AbN1IEZ!4D` zZe`OgCYN!hvkcke?T#UtCl?s_%{cL|Ppv#TtkCAHw$q;jNB=c8ee1mfO3 z6_NHM8-L7b=lL5yw?Or%Z_sa|VaSgO!>qY%RcgAzst|^~oOv9}x??lL>Fz^|z`f@LcwRJj2M7 zj7KR&oVGT@F6+Yd1+*|gY>efPH@EWQ*lcq$12n*NCWG6lXyr%CJmJ|1s=nW&=j|%r zDK|f>uirxG-9rdWkG9>v4&Fh(vZBw^uU2Gy?81FM@a0~l{-W3KTd!7LSGIVc`eN5z z+tp^*`#zHIy@7fCC*tkJzkgt90vA6dwcsF1kOC+XBmcfC=bwxxa7ph$ps>xU|@{P9vQvHNrV)^?Y(1EKpPDG&FYz}l*|!rcgs z?#h~wgIV@hk9JmkQ)IY#uSN5@<^h{C!&n`8Jq{GNQAUBoV`x{h~ zO0|v8#hnz0&9O*DVR!tYte*pfvTjqByZbK1*WebHl3Hq++S4=4xP!d9EIg>_-|{P zPp0A{z#&xU&91SCmfV6@LCQgk=Vok3zT`>Pyth5CdH)jp$n)zPv&)-C0CQT+ua3Jo zA_WrX3a+l~zpZIPh-)cgR=Z_x@R^mkcUl-@9irXG`Y7zDq4S;n%_|a{ayCdR;c!i| zbf-T?$G$D0|6^4(mDAV?D=V`4O2Tl6^gFDq|3&4=pRIo-zt^d(e?rA}YOROe@FJ}A z*9MbI`>Yd{+mt{>`7gI}=k1wwW4O&u8B=@Jjmd&=Z`A)gH3Blj)QsU`v@0)$lplyu z&iK$)Vjew7=(>>5{I-Lei9@qxBS~ULw;HCk^ao z5?0sIAFs&92%K&%#|+V8ICpdtI*`B>ZHapx6>eUDHH&=9k0g&G8>pAgAMI9A9gI^F zbioHRRCaG~fo)%>0OqaO5-NdAtVGjQFTP{X9?mKjGZ~F4$;&8}6Vm-rkhb(;6a=C9 zSU@E?xgtVCvu2{UPgyr83Pojl)Tl)m`bYh54)vD$T7gxrzd`g*aeUi24JVbBM?HGx z8Km;}ER@wn@1#+4v1Q1jbcq#x8kke-<5n(`R~~$QEgQ7ASr~@)>IWVap5EENnrNl{ z+az*wYKe!Vqw`MzxL}u%^HyBxpHK)Lnm1L)$3ks5<+l2qmn{jCRw8>CliwwSu z4JcB%GXTFOu)ZHnq2$tU_}BUZW^qELY(|(~O{^HxGjNK)%y+=BG{ZlI!3_ToO;LeNNZZ2pUbi5{X^g7Z;$&g?cPEK@39<-iZl9ua<}>RM`vLvX;J_MFuU6U zno|?(JE$UVLsed<1H!bp5 zwnlVQ!U#0~$&#hp5o;o1xRCU4j1tSK`iFroVxQM|6q*#uHD2EI!!tMHqBMvr&;vMN9^WnhN|_KLvY^j5NaG|7}1 z_8$M=-lj(k0>=XQGuR+=;QB*R!xeNx*%1}TaW%1;Nk)W0 z0jNxNVv`rZ-xtgBT=O2J)FONgP&hU{RMEc%cQLG@j!9HNs$s=$QUl@2g{+`~KJi|D z@;)XJzz@LAVy84+_QLae@)pPsAkI1xHob2hH*KL*uplb*3`j9cG;O9-FefT>RrV|z zllMZ`E`&9jzWEkTvpxE9VC{5K)1@mJXTjUUiU?JQtKR|9Az+z(=CgRXG#@|frK}m! zvP;Y9wL7fn#q_-Cp@-S|bQAHBIZ2afyfivFd*PQ#AJf45CBaXmzimY|uo$^Q-HbB+ zf%xT6+!6(oV^b zPWIagpCeq;#y;czyOu{L>;Y~Z0(U#6$z{hs_n3AabdfeNy66ATN6o%i9eV;C4@;+D zsnc>QRQx~Lu$*Gi;&+#r-R~1h94$&LZgt$8=XfJ;_0|Z*VQQg^YEm`lNQ;2Ro@zWV zc>{Y%^~gMMyqCeH><`w^qPNcmxf{QLyUo*J?(rWigG>zVqtqk2!SVQphan6a!~E*D zS>RN&o%0LkRl+z5j^nr7=NHi&Ep5H-luDYEMO^C9S!&xktdCkr#>4f5AjPY2Y69jFqpRl(VVeEw!`?PNIqKPa7)8 zYC#u_#1W8ynM_nK4>~=+4og0LSo1iWYO=vG=d}#z)=z_}#%JVTi?M*{T|_J2WD<(a z4rmjY*kfgZfu^8xs@=N&bA~Oh?N8ZcVNX|XNXXL9q_jT%Xk{U&63prBi17Y_z>YY^NfgvS~e4FUkg| zT7y`&R($#61-OZ5)4}Fi1(6%H`omE5mCTZ4lbtYnO?!wsb|yH?Xi_n@QRodAAfG08 zhaBG2<-YQ~?-H(Y@~LM1DH5{k^I!KlSq6C9FwOLq>u`8-;b6ImMX2ggY;|lVEk`3w zflD2fSt9MJ)g89c-JFWMphoHX$xFD!^H+gY#z=VE2z~5ZFiItjkZFQa$Zvkt%6NtK zN#I;t1;7;?U!vdI@Reh=3O_9stfA7(X>pz~yI%JGk9%E0`pfa9dhDjLT$VoJp3bt2$zB;X+7t-7fP6DYbq=JFU_F#r~ zINTnjj-86}TuKfD(+h~N^#Ia|0LO};P2yP{<=6@QuQkF>1~a_Suc`s z)fFO3$cZqV&UBjclqGE2>2JtHk`jSzM9Abb4!L~0r30n5QMw*;aZ8%+yHwbzYI!+} zwUiGVme36+;64?0R&J5xDM}F_LwzbwdBvvwFWiL)Mo-?)n1V}+QC#F@_n%UqGRZib zIxO5pwdMP<9?7E`A{-2S*uF%pE#My|gL~xrG_Up6KF$%Ot>CB^lfyJeLl0QU`yD@HCJ7ku{OV_UU{+`I?K=>UCPCPj zsbL#}4A=EZ|B5jX-j4MtgkF%=U`3G zxDW2Y>mBn}^|pERqjeCX)>NV_;ZVRmmW{*PM2MM0x*a?H- zVaOi-aj0j+RppgM^^A}Lf7u}7O2}F5rO~4V*G`YG8o150;2)&qNPQ0ts zlfSC^pGi7hXV*ggtf6Y0m%_K%rCG@HU zftjifDhw|4ypoiY)ECi3vw6o(!Kn?ZN;HAd)X!d^Hwk@Jl)S466Loq& zp6lYji-CBM^EJd#Pq0q*#ua|e=?sqnaNr?QK-e#=KpspHIcr74QHsPUc4gcs-oJ6l zk&r!HKA%YH38EcJF-Zl>E(DPQj2r1^6v6~BrYA5!;P8RSrq{X$_L8wLRDttslU$)i z6S^^-?0D8GpkT#}>EX@ajbmlU$>PU`cBX0o{t{%vI9u@FjvkRz?DuNdFN@^|8i-~S zskfBT<#b_bc2qSCeB((Ox}pmqi6x!lx@|q!`X!+-ps2!bNkzVX z9M+-|gTz35Hwe!*wK!6lBBjfV^I?lS zHkMjJDS?>OcT{1V9;+padQLIHjQ~ho@yHzg*27*`ErO?{;)7mzvf&)iV1pXP+9^yUH9=(VbPBvP=M)UG{>!g`rN zCm*C!;}V4>O0bv=RE;C)LeK6#-WKA6+fo`P+q zrXTQ82Xo*&^?KLvOV-{Kz%OG&3^fhy&gqdoObq}ksX$U}A6u6%SIrhFIzuM#xQ*3(agKwTogx1zx}t z%yQXo?KI~>U*VA#bU#(AODpms`6CICYzR!p=hqiSBPzd$J-Zt&0sd2C5)@x>yd(A*4#1ob|*#wu%@l|gd zZj>|n9c#99LooHk&Ni(q27ljFHd(*h+#fRzU(Ei~a_%h+cJF!=xFqgDBx9pgfq+8mdiJDq zUQ{qOaQCB)qN;JzB$?gl&>y>9pVMa+HrCx1XAqA&UZFP`V^>5vo~?AoKgPO3eH!t| z)vjfVqHN^a60k{2LdC{EP&}C&iW};ONafCwC;SwCjGG+J)eT;~Df=$#X29BD*ssh0<4~rK8HcwQPqYp&053>QzhTSFA_X z9f)N=do48pcL_3wg6C~n&3jDRsC3<65eZ+h2rRzfmgMbEcD$DZ#t{(!Z1V!vb%T6THWsql@ZC}3M(uB7~tgPQ+@m*RDX?mwyMj}=0_pAdib z0n@#K<0H!>h;mNSWx z;>Nm9ioYgcR+u%Aj%%+H#0d$l(QrxHcR>Y;0T(~6|2-J7UbmxsGNQC91OBIo+4GzV5L&VM!bK%UH+5(y8cn)i0=2dt7K8pi;*|KXX%M15xA}(%98eagopft z6lRRk{eJap{~*7Z>PqlYW;K|6HRU2+$(NGMi$+MZB!X*FE{m7nZ{xrB0}845MQXQ* zVkJo%Q>MuxQrBV$*!R3-LAJbB_|V(!mfvC{v`gbo8RBh6cBN`j1|{(tK!K*9>}Pcg zw$!f0`bKzv7NZT`DuYNtsoV*$_Ii^NC5x1Ek+i;k6@qu+WN$=W zm$cd@9Lm&i3`;PJG=ZDElMVZ8;Bx28gm=OO(R1kyQZZ?$C9+#$5_8|~FHJTkWyU0s zwpfA-ft<^WvVnsX{8wsJ%OAaT0RSoy@s=pDImKKTf{DU`ow&xaUShJ1D6u2O+(Cll z56uVPnuLjty%jB=1L!AVwiUmA1Zt7Evd4<0(igMr4H=uw7EnJ#^ovu;aA==L2g^3J zfwA#RD(NYs6rwkbPZSt&*|n(=Rk%0M;knz1m3x=s%P9fa=Bk;%?q>UT z34==AV>(LgYwg1V|DH!IUwH#A-ho) z+b&$y2zjly*C5i+)Qe-~kXxz0y7b&O7x%I6IZ208FN9TnCy|rpwo__Rt|jqhK#pLq zWU5{jMq5tp=(l~_wsSU3u|pE$%9s@EfG56}Ai|DlUtxl8YE;L?UJ*@W@^wQ+bgqTg z(~ndHDo8PQ*g_ly%T+uM6O54rv@eRx(PeJk_4~5<{?mgW&G+lfMV?;9RQ$JURN^3C z$gKsuT=;281l=SI!|#-I`$77MXF!y&1u13<%XI+)PaCsG0)N})jg_Fw1ZOrx76SX` z-v3P_piM0^hb0ZS7%m;X&(-=hG(U&ZOqsvXb_3NP#9O~a>Fr_u*604odmJQFR&{R#@Js|!8J<4@`O zo>8{7VwI(p1*;U1(T+Dj^TDDTypjceD0=N_&!{drq#5`=~k_uQm! z(z$Q6FMJ=UNJVXz)Nu|{=Wt}FvVIa2}2k@_LAgyOe#pYvd#Oi*x}cR zx{kkZ)^)Zg6Xo|F`;*D0I&|z7`rk|1*OvUVPM@Ed1pK1+oJ3%XFEocAXh zW797fboS^APG+W7uxSg$kh%*za_8A&*Y+pEVr-VbQDKv?%lND@mVoc~Mwi&qq}8FPZ+UucIb8$y3R&T54o0 zReU=M6R-N!`&OBk6qIohulLua^;MtiH}kjhtnq|-Z{3DpxY~XcON}Y!a+XtF+&Jwf zVgOWPaKZcxZa;&w>4guS*Bbl*Z>%%Zi^8 zS@C2h8?3ix|k(+YC$g6;>;HmD5H4cneasXN-uXyX~M5Nd+Nh-{^q*z%zu>g`Xhcu zwc{J9dC$LRIJjxxh?mAs8!6^c8;s6!iJB&9x}r+OfZ4^ki`#wFs8dTcQ z;CI~x6iMc;3bkUuFFUNyUfBYB;f%J>{is^9Hd?S3@yZBYu+F``d+%sKI4*TPu53xj{;~L9vXo z{FD7p!CUbtE*hDtj2-=cDd1xM)h0HbSxU7*m1X~Px)FNyt$7p|Eut!e*r;EcDFA+V z8llObV$7FWv-1VmPeVOem7xJoeHWCZZp#-~W#Cg~P-Qr?EgWS5YgEaMRLQ&%tNP6o zQA(e#0Z7*f%bB*r2yi{etU z9@KwiM0{kV5nb-G-96LXWL`FMb&&9W<6TR&(|F+o`aR+k0?MZV=L3Ewenx!B9C5JL zclQy<|FV?*WhsoqUF{EuJoX-k(S7e_=?dl>p6V5EsetlLA_m_@*sxnE%B@rm4jsKl zHV$(3Kfs9};5(%Qv)}i}iAt#>; zUke3hVh3hYHbRYkBn&GKsm?49$((1#C{ilz&da~|2aW#lhZFn5cRn>0JCf`R<DCefM!;P%e-;%ZcqkoO(62T6Kn+Iq={g|0?#hapE@7iq~tvnj6zA3!baoC z(`22zhqLV0`S8~H&iQn2buxp3`5Nk_;K|_H-;lBUMD>+kl4Mlg2FmtM=6Jyy$b;F~*CxsX8+z~t3 z>9QJUjhMAil3fQMUI!o7g7*B;U)i9q;=pPxz6vcqea9WjkA_C8o-P^oiF|m8d|c*Q z86+*A1~;-(Xq-%4G!$Jl;$IkMqrL)GHSqTgG+t)&s?MHeSKT(Se(GYV|06!=KK13< z?fL%J^={Ll>Yw|o9q9wo`1fVhIb|7xV)_O>bR#A=PX@1GSY%U# zJHLtyQSOwGPOKZJP)3r;pEscJJh%V@wkCTbk?(9x*4fDT2D(Ao|eLb`IzKnaI%@$ z>jv4mOJTy5nuxPeV8{cuM3#Y2mO*hNc%SKHl#Wzu;bPC7>_=bXt+e0(sqny7t8gU9XVlAe|#FiJw>3$^o|NKAZF-WwDT z!*nC16Jq|axf}^{oRhChsjo^i2A}GKxB_~vC57u2QP5JDHBq;D33uCw_g8a|y`A#D z`_H}BpZ~bNq_;w2S)q|zlstwmHCm08eEre^8=H`+6Z<9s@g)+xQ9Xs&K7DtmSOjdf ziO$kd#6!x_R>~1J6#w8>mJLa_+u+05;Nuk0z4Nd)dQZZn$3U;gFo7uM>GtRlC6RZ; zl6Qn%@j=`F(Cw*-GP~lZQWo|Qr5X$@UtRjVn?iF46rW~HJ!NPu^tOhsWnJDlg&qfa z9_+t;{30TMitTKzumM|KO`Z%U^_5T>jBOja`<9AOR+ zXz(jLQGN|ab`3|EegY*Q#wEk(9Bd&twh&HiL&Y`f%hB>G@2!lmHqL3U<3(sA#PFJwoWwFiTIR>6pPMq;dmk1v?8c8fu+1?j*G**7A4!pdQZzK#2Ym7 zjEbwaqJX7F(y>U=5!R`$v4Hw9u~T5CfH!tm|0ei2@KEK?D-|;M;Q}CPrsHUNM08Ky zaRphRg_tOyCG_Fs;6q z!k(Jrikc%#wJPLjiIsVvLdmRpp5FAFx>4e^JSIBv%-VB0muZB@=CGG87>QcUH--8Z<7rSKAg)&qq5P+E^4lO zd80!Xs1XCN5rh86o9WJ1{F;vJnvO8D9jf!+!Yr8pmQ3Jw#Y4!UX&hQ14vl=C!7OtZ zo5I8{__s*T5q6I0?fM~y^awN-0*%Bn%5Io%4c5R@de&tcDjG@tRb8y z2KTYZC%jh%3=`SKR(Lb&@>X7O+aL(3+{Tf_VSdNjg2M0lj;mz-ruI~Pyh1H{bal! z2X_8EO!`L+|7vq}2!`MUV_eMeReR0`0h@JmfaKZo=G_g&Fs;dq-LvnV&)rmPyG+Bw z9vfW=Tva{|L8>MOWmxc8NG-ntzMw-ZjJ;JIUQKiIKD8W;=s~Md!(FT&$+Q4;{)i?K zw)}uCZrmX=hC#FUx}(HiFFr7vXrI-5n*%$Qs(iMrk(lb2F|*{(+Z8c0VL7jISQK0R zc}(SH-!_Spn+crY4_=%Td#jti>0Nj)b93E46fCe=y;=VzBZ#_H0DcZpY;&PFxlqnY%M&AV*Q!VT^XW5}5TXLG)6sy}2f}c-E)&^VzouRd z6MPL56{Gg8`I%Qiw=xI$trv+Kcw=20gkGWut9la4`lPMk3Bjpb4Y{WP%iZoHG&0sC zpelHAo4wc41mDs`C33XYC2NVUb@|F|U1B7}1R-rTFn_R`))rfnx8FcbI%>YFPn z(UKq<*r&;4Vqn`S0!r-Hl-5WFrkgUPm@=gH6?35k3UYeUh+Z_3Ig38O?0)%QTS{b9 zCXl7Ziz7G~{%;K2mk}~7&8JF-UMs)*Qo-C@kzb*#pN5nTfc}6Pk->}(IExAQCkv|{9;yq+ zth-*Hu5BGU*Z=tzbnbU{l3my(9nx7a@SsT2hYEf3av(XNg4w@fsay%TXna~}{AJTS zJf`lTe*@A(gdV1ry<>t%3SV_*eI5zCnFD0Dn4^`1?T5WdgAxzC{5 zE+><^tIXFe%UjJdfizXHRdpxgH~NXjYK&oB9w$dJhTjX5j2* zP??oK)KMfoUq?J&mz(C>!wSE7?>eb9@)%j}X=RwyiJ!DSC2cp#vddcnd1!zD4I&8}b_32;`K{WDw zf2T4@+NIC|0wI7vF7@^A$&n7%d<55gmp^rrIIoXF<-M-jLL=`pZufXpu6%^QRR~7w zUjyp!q@{hGy9(d8nSQ@@e5O-4 zDs^QlHSuHh-{P6Y&CX|AVJLwxlqbQg@7v98r>zo{Knco&Nynz~i`)KT`x;8{8p?yV zINOU15i|XVR{V#K2bmn^v0*M9LPxBi<8iZtpN(%QH<~dm`UW98#Uy^Xn2@=c94K`J zN%^kzcH$g$_!a)?Ppt2JM)q|S^5epU?84-r={s4-nJ)_g3VI2!wo`p1>%T!kpn7KW z;(&A;sT3Qj)VtzCnpfW1b9sT^KoQXATXs_V8ct}1b?$q$l&v;hXZ*PF?iH#JYjg@Z35CBcF{fKerC3R&?(K{R*wGPNj$`ei!)#3+>!pOtGjYl@ z)1TGeMwxpL7E|d`lWdXga-vpBu^k!Fti#Ie%~1XEOqQTzY51c@MH6^ zT(`Ac2d|;Sz4y&V4Xw5676atg7f8PF|Ct#I{k?B9F80B*?Hm`!R0_*f3W!3pda1PO zV&{BEM|?-e-x*`V0Rp{Vg{j|4)je%PneESgLwA2e$8Q)Xc??unUp`Ln#c0lbL3e*a z$76`PQg`BaFYKrOH8Ja&q{Lt2NI$WJ0ou^oAL>8Z=bDSdIDkJ+?>wd{85Frvg1 z&5p>u(YWyEKF4_b5FR%Z76S}Q&{@U&ttx-Z7)W-k{klm|Qgy>xRvn+`oHDxK@!t93 zWS7~(NXpbmYNEKbjSEpdG4oge$)3xSajCk&z z`~ElX!|4gjL;AK%bN%0*ANxZ3nK=5HRPm6GL1^g8=*!2m#C7$|!22hotv5Xf6s)a< z$iLOhE7kdgHo}yWg`Q8Z?(Ltezhe{cRK0XJ9!t%m7d&IPS%+OiH0-aYkMl?@PZWQm z!!+Rep~F38!DAIozv?l(+Fi0C_a_V;a}IDQ0UUbSXzu%&kX3kK2~M^IKUiz&0d2m+ zwKatjm_m7a%-ic(uKkkS))8*&Fr#Xv(6$zoKwAM(QGP#F$Da{@a%h9lU?$FB=B+|q zZ#}9L+fM1VLaNHQ6*%o{0S>zaC4zD@tAihHSqMLHDb zGROADj*+(8kD}YJknlp)A}3b6fmy2|-zD;Vu55J_@XS_f0>If)sXEg1orMu8&O(!J zCzWC+l}f_-(m`&`33al1*F&zOw*m8|7vj9iqcEG{fstZCP&r@J>}C(krtPdD|He^6Ezaf|ptVGf`${~I-q`G1xx zjp=YiI-H`Q`5@xICo(PL#0}%b#W7_!!5)x?NH`)APC?U5b}Tj17{+GPs+iq$>|{8q zdN`CgV*e$~K|-?r#q8oQM_?|Pq>Kiuj0Pqoa8dJ8@;peJ>VN@J52pwd5B}_%$Gt>~ z^|F>7qcbID zRnP^MZ{Ou=ap%gFp4M^!%$dAK|_b<0gR`f2@|>hC4Hnpe58>h*-!qJ@4#G(#aw&z*Cjk@gBiyS56dlv zT<;b7szR{Ledbp!TQL(h*m<(%<9GCPu*#}_044fTYUQr$pk=({^Y<-g#R!{;&Y6!E$NI|x%NDn|LhQjqF2=k>e}s&3NGhJ&6GibV+xO)*UpZ3xRH zlO<}Kix7uX)HXij`S=qV&gvtJ>srIE;c&lPnL`dmkVBE7^zL@r%e=Fy^WPjC*laDC z74e}+5z}M>dOFpHZ^Xsx%Fj^yCpH(uyV(C(;`vzOrLOIG?|VmB)nZ%L_H4SHcU`oi zp2Gpp;eV$Z$vQS|B4VnsqpN#(EQPZl;eVhe_ykML*Ah2P^6;l+_o(IFdK4}kfD8Z2 z*>GcdH&h#9ItWo1gjBqkUHL0@x%822&x&TnipJ=$N-0`Lec(x7FWiRKv=5@t2dMzj zazyP1!ZxxY7XWD=%Pp)9NX49S-2Y;Y1PjKBGj)U*tWl~O{^>(OxTy7a_pE0gs)=vH zq=O%B_HHL$$#>nn3sYLvAZ#~QoUY~N`Y*bbl=Sn)yFuWd; zV`xKR+Zj_3RreN>XqnR}nA5-}Asauhj^}sTMu)?MZx-j}+sbh?P4U!B@$`FcaLaM% z%utQQpm)l$EZVz&Tt3GuoiWG<8Cd6z$ZLqY*gWzE{}|S$GI8^1A}-GBmy#8hD19na zXpB{8OmWRGzP10AY|RvJ#S{u9eSq9R7 zZiP1Hc-oAJ*VhYHh5zlST3qfLcEg_ze`j1hOnSO4leM-(5uw}i0aSlI9@ ziUyP#UzpN`Y|?!__Fa9?W_Uk?Xl&N5^-T@yg-dRe(=g(#47Di$ssMnlLTzuo_MB=0 zLqK_Aq%O2eclJ^>SW-3YXuC9>`pc;Y`{y-S&uiEx?En~JAw(k+9Ki&qSQb*D-3uh8 z^JVWL1_8PEU3cp72JjfM8X^f1`k+!-iWBdbmdz1(P$YlOU@z0(`Je#fS)4D2=;ZTf z{$ZK~lE(D3P%K&~vSE&`v>X}Hh>d8BBuolh{QxTKCobwI1_=bVVLlHK7Yz`D#JNuo z#!UCre68>~YuFj>tU&AdNT`6RHQvNYF*BOJ#V?|?4!fi-k~biLC@*ZoR{$@h`e@$b z{=02|1{{z9|NE(#P_fZ?9b3r^Pr)pP99`T6uQd-foDuYvuyENI%*(g5Og8y>?0YV{ z#GypuN`vJ}W26QRxoZEHWuxUvqks`Ls+FSkd`9$Z<$mNP4~ZPjaN;)-V>J@9A^Vd) zV4x*!lp&4W2guRUi|@5&e&X$>{PHcF;bkEX?}CHsNjVMi*bQUI-!J@9F7iG(DEyf0 z_fA5SZP5^K&M;=|LqfWn1q0iYH+yY@*w2kACiq_LEuA3GE3wZiBdc^v@0SPLjIeo) z^%N_~0IN)2+dn+@%2M6<;7M?hy_WcDJ&+X5smlKCwrRuZbtv@d;e*&E?@ABJm62j! zIj?GW=*3G3n_*2;X|zXQY)%d%wy<~!r6Dkz;XQTXo5n6p?%7(2(;Cvo7vi`vZfeFA z^sjTTE9^dMGPg&M$CJ@GyfyM!lkSt4q422Um?#7Fje3N}(N{8J8;F;HjH~Kj>nUmJ zkwz3T8W2^H0b1fF{cozfD!&HKm&^AmI#Y!?Z)BwB4~m4Wt%M1VW9K*-BUQz~!LVM#%+6hRdT*paF^4HksaSbhK;_JI zL+tj)QAfFYK-(E#u4>bnK}6H**1kis z5?Qgf7_9?oGAy{v!R1S1L)Az;HY#l;_S{%lF&}^D4S0h1%{eZM>5=fQ8199u+RH+} z2j(9a|2@7FYpQm^W9!gU^7$AGql3I?0TRnel*HPqk;cawDF`k_CKFY1P#7qIIJk7; z?n#MkS=6ym6|l_UdJg^(0#gu1!qEpWhfo z)pKjx;Je4_pXV&ItN)a51b5~>g>!{y)^Wr(#$q{;h6%yXz=v$=iU0kuGj}I!o*h?p zR~`c%D71#lF5X@9`8V`Z^~(~Zw8YA}Ru{s254$Zp&fgAd6ThNFEi+Os3;5=)+o|vC z@}E2-8_U1 z?&3Yx<7{Y`vt$Zt=o*vCkz%>Npa_)unTd+$bgGm}zb6Oh>0dH$;0 zy7~KG3EZ3(Mr==U@FaZMZv_Kb(;gWsfoznehcSc??Mq@qJ&Q`AX(T#sPd$BKPrrFP zGD_a~S{(Nj)A02-vPW&r5w_xBVt6wSIjJhvp;RCK3Oem)XOdw7Z#+QX8t12K@cUw#tprXeLF? zSU++zaG_CN95>ue?X0|tXI0T#`>8}}tgQxVe3?-z#mB{29m1vGz2)a`hq~;1?eKLm z$X$UrHjG!|x-K-+HkxIooV@CbK4Yi)g`FOba! ze8D#PPp={t7L$-|9Ok!BGt6$1H#0WlO58T!qfPL?%46?L1}63PUVx*LHC z?=ju|z>YLHJ87hnBgaswSW5W`b7uVE_wdss3+@ILaoo3VYSR@NVNYqx)xO;YoEKyl&Cu|+PV@d>nC z9u{oYaWX2lR_=uDvndtsM2d%7LA$-Xl4Dn%d|6*&+1*LwKhuVFAarLl>}}i$yJl0i z+>fc=ji=c=xf2f0rVM&a7}`pfv{^^Bh=biM?8kq}T`1Lfk}57TQhuow1Ppkp`6}bF zx~u(bvfSVhlg&|5q2Yp7cRj z*EqAtM}$t|zg0s&o6dL$<#sf$C$dj@{w$s9H!oZt!V~J)TP`PB9^L0iiX=c^M2_}qGE#S3d`97khC_v@qp`uv*^ z|MC+ei|bJ8UtO6IxMerp*6ccxvrSH!sfKbf0)=wFp$1jLH%2{`c|Eh*g}Aiz@*S_~ zbgrpeLT@Y^qUc`#oz}2(=K@|8(_9BaP`>|>pB`G3M-yATh(Dvdz1Klq70|1dk84=C z#4petelhQiGwc`%H9T!0b?>AQXtT=Hsm*GEQ+*U#4zs8l9og0SeQa*JJ?-nd`%Itm ziQ01&C1}C(r=A7nXuC79x&2!IownwHd}NgID=H=FCk#^6FZ_v{nq%G%Mori!pRyJc zPx1{-W;qr=_A@k!)y`Fo!G|kXC*i)JW3H>kr@t2yYJ=VT^K~Mc3$TmNXj1$$;KSQ2 zYKzkBAe$tyx!N4R8(j1S5weTU;l{v}sx05H-s5X|l}!y@B?pyf6-7Z+t;bB6y>-&7 zUjdRHB4)`UZm0}Hbf&;M3~yMD9JUwx_4N;TRK^sYUdvO3j9%=tcM}M{a*HC5R>UAu zcydM3R>xUkeiUm4-|$QrvltoXJ4a^)gASDA@Ba75?Yqsrtj!zuKd!mnjfK0JhCP`A zf8{!MN7X8?yW@UoE@>D4_hekB8X?JlYyFzFHE%1L1z0XwnA6aJ^)kd_2>$cERibH`4(&Fxx0HGB3BEcPkLvWWuX>lkn!QCB#yA&_3f#U82 zmz#6Wy}$0inc1^uW$pdVWPkR&wu$~MftEhs`6Cu~87vl+KUo{;bwQ+doiS?<34iCY z4~j;doXH6y^%p&rF58>XN88_|zA3Z)dC6#i;eL~s7E@w7fRxe4n8GRklKP^=)*tDh z8~7St(cT9ekwBg=UKzm8kr!6_iVJ#1V^DUO%fBt3xzberitYvV`@l4qYCs)pCnz?o z!A(vttbRUu2i^{8Z<&2TBa|{((>}0{FMM)543J zQ!*k{g;jNg2hd4qKTe__KCmpm$Bwi1RaX||<*nn557%{(2_nx^RZ-PZ2`ME$%oLk_ z><$`GRwYdd`0IMME<9bJXsc*zOQ)?VobjpvR8S8h0 zNh;1IG)nf98z2%aV znraG9qhw1~DoE7o4jnYetk}o-tDmdj(Y*fguCSu=WPVIAS#^b5XC)*>qg|;rSryK$ z0}n|jJuJHFtS{M(cqTM`&)LVC0r)9g^E1uDkMk0K>^{Wb*w?(tT@d?wUhzdVp@}Kk zW`bMOv*g1U_#+%&ukUr1cVC;K`BYMuIUCs$TTq@dXEZY*dEgj6{=P!EGSG3+(wl2h%ws;`ZuNk zpG*C1eT5bZg(GK7$3@yt#%AF*IXqs9?4RMS=-H-{uQL!i18Hqv?e5-~HpwIBP$$(H zi`7c`f11R1;~owR`NDCU(TsTwwS2HpSrph3<8>qJwRoDmgl;poM}KoOR)NO1IsNC+ zG$y$ob#p8+DCANf-tsQX+xnU>wSQiB)*K&p747{j+cW(okJOyTDkvtcFOH?HW>a4C z@*11?vN5p+TrS0IIp5s!dz;lls_4OP<3XQo2Fy`{U#{Gau7=FL5+H7?Bu8;FudNv4lMUEF8|13Ke_bZhPZ+JP^+8^O*`rPXA z@ZjxP7l|X21W9bVUBNm+ewKgTvuK%=DE62%R?3ZntuX{IZ30xAmnNO^pD1^;0$-~9 zWh=<}&EsDZZ>oSkX~veU4?yAXS%=T@8F{{*9JpHTT%i4`r~EQCc81G5HOC2b@wC|I zQRdz!H1YZ}axQrWK1X-7qNuyk_@U|?RKG)L$)$2(Mt1EGx09AFSSyiO7=!fm5vX&u zux3@J${}0zq{DVoie};}xWKjD&08={qbO6b?$e*-b{^dOn*c7BlB9T9i18eR_i>CA zSAmX2fi9TmJ?s7D4TdbjV_2e+5!G*aMP zyk9{E?wob!r&bh%-GHtOm)*_BW>=!jSp#_V9pKF+69Z{NPB3%tl-RolIx<~(OVNlk%{BNC71v% z1y*%-s1aIdJ1T;X?ZZxc*Xum&7I+NYrw*to|Iz?9=iitIFr+fx24~lRw>E7!m&4ik zO0-(FyswvIz;*U->Nm&GqeV<|)!n|zTPbKu>fHMP=XLoTlM&dcY9r5!L`nl;FToFi zxjS)A)|r^u{v!{Hdy5KWy={qa!X=N9$?_qcTd>mG@ zT`{&?-K>T?Nyjot7c8%{+=_K_oXdkePEY^tq5Xt<@#9FVJYV1r(yAW<0lyNm*G$b3Z*hEZoUa2M)t3hhZn2&?ovC*`Nrbga{%K z-%-0lnX@6l$Oy3dWAm}HZX#uzF6i*|%*xOs=_6Au3Hy^u!^f;^sx~!v* zM}$SMRu@wo>Kac9CA-24Vf4P9ist>EjsZIZ1QT8jvcCK}sZvbQk;3$)!t}wHshR9A zckYblJ9h(nchLz7OmbiStlkcS1eFpcloEle*&IVLyfj^TfJVH)QyHG7%X3WNvq*jV zQhj=FPlO)~_xle7W*7pK()B9n@RU?)@ z=;o+jW2Nfu8Ey+bOAEdCiT)hGwB5&^17ySrTJ65Zy}#^Sy$;;G4vbpI^1j383sVvp zOs^bFKVkG5CT#$J98Bw?C(}~W)KdBe5gt?v18Y&#Ewgaj6x8ZiDt*gqrC3=NbWX2y zPS2e12o(Ehk`L?Nz#Q7ZWQv_#wKL(@iS(r}^`-Z|MLaia0)(ekhNqoyx?-g5ZsEIj z5xRCs&HWs8)^X!ioYw+ChMQcvwr3pFChzittD40lA@rpX`rrh;Q+sp&hrh=b&Rtf{ z0fpl(!Q(z^2U5JmxMK7yV)Vh-GLAz4~7^v!Mte^Xgx1qb+{>jv!N7B~#j`VrL%;?}A!wX+`|cfUZsv zvKA&X9z{(aMf~vwkkWTm6IN{cU=FgpDnRM=XEG&4O(n%|!hqYEZy0Yy0?{Tgu_iE? zq@|R&-Eqq7{{V~z0HU5AqzD+!+TQ|-)=V~DDD*AZfK0Z}j$*RQY zh`ZLxs zk7&k^Nc@R)9~!)9v=RteL67#?$4BWsd_fj0jN5wds%&3FZTa6N9+o6iCM_Tqd-5`3s!Rd5uK^7V^;OQIP| zBJqL<{N_pCU~^J|g_O#LluuUH!k{}KD5VmV^6Azh@CW(2UL;x$CRPq6RrpNSrlE#; zzZlVs7!e5X@L7c;8JdD7n!+`9?T@xW2Tg7xE$&s5k#~zzWwV=B-R4*gy8!p(O84ZG zRY;E;a5Xz%JsNXA8Z%oepCWw{#Zz>g<20J*6gBM!_Us5r&oi61%$M%*132|wN-+?r zGZ2Yqh5w#mD|z-X{;nk;CmFLO88h2V@=pOA4v4g+E48HyuGjjn#q^H9FbjB{q>Otd z8vasBm`GiiNE{j7CV2fhNQsO=L6boNKa0mRfivP||2LwUZ$#o^5$O_zRiDU36tqPY z2p?*XvYhUcCdS8er^jiOrS_wi;&CJBSR?2{cs?jE2?thwA{SE77E&NQ<&p7QF?GXA zW*volUMfde@_Jq$IUB~_7s~jmjk0&?nCxcSOk~?k1XW8kc2nBKyWz>#_GtmvV6|(o zix8-CEG~4Ji~i?u{ulAy0mc(p;}RwS{+VcHe}drZN2zFHBsP5+HhmEnS*M4{zdhit zw8Mm~Lkv^zI6Kfv%+i;BGf$B7un80o?B6@QyoABSr{kcIy5VOybPEffa4NOR_O-QBDc8u%%^N(0+LUb)xVvUA4 zWZzau4mby^orC2XLlJm3fo^IO&zEvvF3}L!$KpXbN9`)e+!VCk6k?X@C=?r2Cs=;a z$zW?o9T(<`CCkVq%Md~&eWj+a0;qR0%_g$VCW2BNNXQT#L4=U`Xy-XD%Xuyt)1k9R zGW*?NlZjB17^X;qxdVzsa+x5~*aXs8asBY$O6)_CxniL*a-lL*jFQs16{O$^qpn;? zS8f#M`eqmIc+qc?lNeA3^MO63-Da5cGj_}8?7L_!xZW>Pxvar#GHQ(svv)=sZPG!T zF$tS7;t9-azB8w!ZPHTPG3whf;%Q;&kdtuh-9I`LgE}z`P-Il7wA4h5`b3Pl46~oR zs=jNmB5OTi0*$OD`PUw}Gy9N!F14Nvvz|q?-zm*;}ZbCnv`>c|fE4wAqV zDPXP`t=`C?A}jUW7*KR5zDCC)I!1O3Np=hE=*I zLR$CA(6{oMFcEcb_+OqiMsmC7`$d||kkK3vpX@T4{5hpJ4NstlE) z#E`{Z!pTv=p8vV3!(fp_DGfnYXxyMXpN?*L0HNcXGM5sX@v^-Hd_mcQDeWd%!d<2X zBez%gqbS~+*|bv2XRe@r*q3RAz`Yw*M^cw$=vDcMG;e~}pJ zPg~|s>+?z-ijjF(DMGRu0zkaNGLxZ9A{MUt(qjP3PpHjLph?kw;0wh~pk+;<4bdcN zRd)8w7*Q00fd$#{|55-|5^7aY=A?CTX|E(}mT7l)Pmzn;w4<* zAEQPdmYUm)|16`~HTuh9n)}-p!miG$+}VaiKok<0@(?**$FhE+Tr1d zSE=vYCP}OQB+yXl1fI-Ovq#p`metdSEU3F+Z3@?qDMrIf6KJZn5!p6)R_87Ci|x%6 zWArt)vypxw0=)5#!s@-(3^FSE*(vmyLpe%_9m5|%0cuT;jaDrzHg11zEgjf zVg4?Y{j5)u^|~Xw@sKmboe_w5E*B_6MbIbBE9u~T&0%@NA+yo_dbK7j$K;oc^)H$1 z_v#2G%P6;2)vmG+bUs$^1P3oO>)oJ;V?Ct`HWIi~&bzckSv!(hDX(@)`*{K3XL7$~ zC`DbbRafI&hERuMB;HJW+kebDD3=+|VJiI}VrM?Yf~;8tFjB+34s}QSH1_j<(~PZv z?mR9I5>X}RkMoP>Ip$DUZ}te0dPjFPTA~bAq70R8pWM9nLWuK-){@Sb)e-IL2Sx_i zL2cSmK;2)O@vk4b5}x<7Fyi{svHH@5Tx*r%aeSVO9?6X#p;6}BCu9+nERfWxk|*rt z8hq~ZsvA3)G>g`05DrES^XBAsTJ{fTyPd;4z1Bs~sgZtjA%em$O44PrJ^N(H>dcxy zX53I_e zftDBSn>KRPSj+{dJ09>r4tXSMCq0G92Sb$S--RGk?12k+)#{S^4-r02O#K|HGaRbo z-KBCT_b@J(0D0Tr8P1?I$4{J)&zze-dT_Zi6aj`w)rLuD{zk($&g*8a!`<9_LPXPZ z9wdtCKS#LF?weYjc5Ze5RFHAWYjMdd4^&Rp_G?)VI<&>Pw7ng_*&{Jqe0C|otN;j3 ztPW13qSCJozfFqICmWX28kQsAtdK53p;T3Ik$Ei0yuvAsg1|XtAb2`dFOxnj;rC)<+IS`2PeG~9(s)z zJIL7=$J_UI{M|lzXAsiE6?4BEPtx(Rk3PKMvCWHkv#K zt`Y=e&L@M*Yr*AjmZ}+KDD2CV9N+Rlh#6xL-UNW zk%~^SZK0@(UA;CR$eM}En|T|py??_3fFNQlL#6Y_M<*x^2Vsshd=OebiMTncBW%Q2 zIREB%o528^(AU-D+!4yFX9Y+r6u{n<=SKL7S(^OMft(G9}W{l z4!rp{Gi?U5Z9;>qk={(HvUYX65>qofW$aKpi7!t3?`#G?*o0bDju#Qf9=A3)ZOafp zFA@}fZe7J#+}j6JSYriTktfail$?Yu9kKE2L-OdkA*O?X=Jc^(e(Ji>v=~N(i3AbCV&T$TH&s%sKZIwel zrNoomFS-&ZvGSI{Xa6QnAnh4eD|xdRu(M9vpd~jkfE}#b4tCaPd*Q5MaTz1AMCmZUn zO5+YoaV|@7V-6;-cAaq+0V>lnuNP(X02{FC4cJ*UboebQs37ro0Xk^j{*uD%9AAYT zaY;`81a!4ZK_cSD+!n7uDIzJ{?r$O8R+}AmgI#um7|n+=c{|YJGPF8ItU5-jshQ6` zh#YTojFEiW((){+d*j1kyTMSqP=+R#KS6p$v zf~h#-sW_!?CI`Rk=1ckc%~bbC_SV}{9?I|CV-9XSTQO|h8QosIL>kmwyqp{~3uS;s z<2nnLxe8kT`)^1GUWCN23t{altffw6WKLx)w!iF8_t|@AKKQ{r)GBp+a>?!)eqP1F zC$m8+v(Vrq&WYEEe>woND-);OTGb z30}+{E4kcRGxIQgVHsUm{F}GkY!Q?G| z5SwZOo2ptOc*ye?`d*<3sQ4&T$_-l3 zA5us!DddOTvT?qY%-3oFWszmdYh}tS`*TUIxs;4=A?PkW>8qMW94;TOpy@w(=oIlf zv&}+7Q^qlPDQ)EKw)Yn|9jg|Sz!{4@8NLqYo)liifj@E?!*Us!V*19&GmzhZP3$IU zN+~Fl^ZHV);^a*dRmrDhb6H-HU87EhDlJl|vMr_QJsbQ`nS5(!g9>}$h`-FvpLijk zc_n^M@%UJVre%h|`nSJ2WY(+9T|P4k4nN^J!nYbEv+P9;CYLTcm zOQA}!ychbh_&P{VH9<~QoGl{#MRSP#UmnOfkA(PS;1THV%%5&e%#$E1#QqkCL|awd zqU@gn)6ea`Q!C!bcZXHu4zqDCvv0@sOiUpl7j+lO(c&wMc>n@Etb~!OoaD1$4eR-% zWNQ=9$j$7$kM}Fu@dZn18Op>y{2=$O{2AoMDT{UNELGZ;AAGC*B|A%@#q}GXtumj2 z`20J7{i6HfC)))o+mzs1%Ptc5yBLrcF+|ZQt6t+Yn#lgU^>Hq!DBOKc=*q1 zVa+NfxWvUn2aGUS68=%QUShdlVwq7va_i^~PH@2sMATXa8B8V^OoDt5E7@7<)+;Ud zD=jm+N!%@{ZE8x$H57C-6z+6+w-9~M`d^B_#Xg#*mPt1{0BBO`X;LnuEifFO*O|a9 ztzniKwtX(oVH8Vf6q#wCCP$-IN5v;1UGvLa^FtPr$%KUTdk#IUq&OzkIVQzBOXYrh zWklNb4O!46x#n`)=5Z^;jWW!4IElrFNWH)s7-S440ZxkVglEKikXYHp94a`(9Keo9 zA8B&Qk@7l7d7|l>@JFt_??g(q-RYJ?>6TPpebU@rp`wQk<_k^cDLd%|UFL}p4A3MA zXj1K39y!bIG{ywDk4*tp-O&&Hx;@|zul)(LB5g3P64I)oAeb6opBhi+ zsE(L0l-iw=u?AaagC&IKf;7wW$vb3qI%J8SGg}7fJpRV}fa{sZ>X{d^Lrgywygyx= zV`N!kWSRY|cYxacHt2_`WtgcYRg45?I>O88H?wUtvx0c}hUNIm=;(rId96e_4?*J| z39TLMQUR2zxX>81|xS35Qh?7&Ykv9z?d+?7sH@0d&O zwzM2V9jB7`qBW9#*>6y`nGQ3i-+ar>Y;T#-QAa8^Eo_%nOfD*?BPvJqvdqqC&DUBa zwu{V0PRB-$sESn^v^9BL%OSNqp}ssJ?!ipMF34#E^amJ?20I6u<4SE!sBcZECGSTG zH?3FdEmZ5J47!d_hM#_zf8JdCvYChY0N7C(jOJ#E1V#Xbal-^&C)U4C%%{># z%U)_Fd7*C$EKR5{O~|j-PJ6L!@KRB@-X`I)N(*Br5kQkrPm^#Nt%>1NxBEqN;j88X zhS9iz?Q;3hG`&~F?icNaui7cEEsInU{^jPRQbGL_3H=k|6eT)Iih89=W0$S@H$MMh z3K%^TBs~-2_+be$>`*@PCs~~*S)vDKZKm-R8(>p>eN#N$-v2BtPG(=jGTQ)UVHh4Y zy8;>uK#i0g-8=$*s)MZk{X+GHV)c|lZO)1C8~w@X;@bFP9_3==T!MSYj_CZ__&v&%#Dxx$5nTg1y7ZfJsBL28pFV6>FtD zaj(6}I`SfFl|1!a*$}GfRn*AyVihd4Jn>A~G~Bg1a#t%z$3sq9Ppv}uOe+J|vhOpB zn`zJw@g%EcAg;O69A@T&>QK|vgf`W^FWH?{Oxdvo({#%f7gYP9(iA%>;mi<>Y zn?F9diYMLVz*St^hAB5R;mGUU$l7>qOJ<~uPA3Mh4TZ(tJKZAQ29=D?UJ4Li5=IIz zQ{76mEbg@xfq~1*F*F5oQ|7uq>aybXo<31AKzDVkQxw|7d>j`V=#g23W|~LxM>gb( zI{YpY5e%pYTiKROnd=}$RzA6K6i*qe72)Lo{|Nn^^3PYnQpf{Ygffq-wZbvz{OwlU zXQ9oFL|7^qxw5R%@hYOliei2TM+GJ-c{91PMRdfLfz@!!%%UIQJdzUGB28jO%JW^9 z4;FjZ>P5&hl?*5`fL#!0YUw=(!PMwV$cey)yH*jhS|uZ~4qE+0c?)*mo6{+(gR5k; z64Q^ouKA)Gv>?k`7q3!O_Gmy|5DAahhBDK7>P)9in!txeYgNK5^2EhUbmE$4U-*$& z>{V(OL8?@!!ceftNASNmlOcmV(Bs~-R<58XSyq?$+rkpD(H9EjTTFGs;60P_otiHi za;jqau&9=z%!7l}Caas0seS{3YHl~J6PD?z)e05V6T~0i*isU;Gw*)WD9S5Wp>mm~ zl`b&TNK_Q0J#(+ahGnDx3DvD=OQ-0KUYd>fA52YU)u_ms=YfFC9MN-5s;W}6vSsD* zx6Z6)gs4sH8w<6g!dHIp~@!AT^v{u^2Useitf44eFEy|YF#}n4EDy9>d z=jY?r#cNwJ)2isC3I!Nc2z01h`J>j^MmfOTjh@_`xo%Wl*10}6P5FJRo?}btl&2Qs z^w1tYwO!(^C+$IlW|2#kN_JSUH}yfXc(rz)7v({rRuQ;HB|BxhKYS|q_y@TGb6uOd z>_r`D)tNvFAzRiIPdHtMrbz1Wx_pXImofey)o*Z6QbY=nM8k@;JkG*FBBQd+_5+p6 zAF@Q|IyMbi=f=k!Ze`W}RoSxcctY}Wr8yx1Tn#Jo^0*y)@=6bTvhpdQF5~nnmCf)V z-pnqpW+Zc-qb_*>q<`nFQwcOr+$YTO3mSc>0JetJ`|`L!dl&ZQtCfC?@4FfH1ngm=K3lE`<|FpLJgBHnKfl+mD&{ zR!8-j0(PnJjZ%oVsjoEyH}0QaiU889#HK9ctEVuC?Yp zK9|rAznS;QAYEPUkFHT+HqR3W$lYi8S56Vst1#Q-i5rDQn}rvB#%+q%c4p4Y)_FiP zDiVX{B`nK^TqJzBn0@aQS2o3@!>Bxu;xZk%;B&C{D8p4s?D9Y^Vek2#?ge@02z4!K z0>a1>Rb5$I;w1y%sfsv)(gi8n0A}QX*3_r5|G?^N42&ize?(BB68Lc1=BrJ2?IL8Y zN_GLQ(mBX{8>p#nRfYNjKwPkht~&O*b!HC=2G~)Zd#sWo!gujL4djf>bt>wzan%+c z2R!D2q-qY%YEN7XLRRIpt~%S#w2NNWs!(NugO7$_M)E#a;vp;t@&MX#E*-|``Rt7~ z5hW^XMYVX%>tx5!qM!9Dl(jAeBU~bLt!^qAMbMd{)0T&x~%S71CT|MiLnK&$`b##qUx#{ODE;oV=;z)7-1+%+{-V!i`wo_0i;J zK_(kv?6?7sG^`L6Q`^4eX4rrIn^dq|@<4@Q3yWkG>g!QCr;b{q^iqoSHw}+71Blfx zu30W$%BKQ!8HqQTDD=fTkNkie>Q)0~ak6$UuP5GX1gt+k(g4aA2wi*K(95UZ=rSt* z%Qme{e=I2t$Tr?Kr&3%t{n^4bT#>PsL0wj&>NUp9MRMda{LX;iZ6E0lN{*Kr z!MQD@Q0AS?M|-74{*t-wjk+wj@^y0Z*!a@Ou&$({K2-m{KY179*T&YA!jMUx8bw%& zjtd@qegwtaeb*vWH+WwnJ{Q{$RZHu#Vl0c3wp5-JQ(}J}iy)_Cu9H`nHLcD)07dLN zR>?-zf9qhWS3L4aU!vKK3!`h0B@o57DeWGq zA0%62s_6ks(v-ix!VsC63gNA*QYrDdLi0XWz^+jIVYfCsz)D$QwZLnEgad;uv1vABG7N7|> zt12E20wYDfB>>)n%@~V^rIg7{Gg+Lji-ylswP<|bfHwxLs+L6LXF%WNLH3lJs0_j& zi$?rSL$ME@Sa$nq)v$jF7nTMDg)n}ft4eGf5e30jjn~R&qUHj}VSnxK=fWr(WYI=J zH;UOrYfPNqB>pVqq7s(?RARiR93l&4s(A}WA;lR`3;bi<4<5n8w9B7b(&hKLp!?k-V+?5*5KbM zR{!o`>0~@XS~;8OO6z{QP2V>6dHleI4|3(acf$lZgxoT_JT!kg4?+H4ZCjA_ddb;< zji;UZ+8_G;>E77=-HnU2DYCOnKa+1hTxI(PC1ax%9_eBC+*ueNDVU#CZsxyipp}8- zUPU`z_}a~Wr0({q*Jj~s%kbK{dr}Zt@djz>CQXB7e9V1n9-x47yKb1{Wc{n-g3aTt zuQwC_e#0EwJZ?9SCkM7}cXPc(S{W#3{RBA2lqXLQPH&Bcm@`sdXg(45E$bvsKW(5g z3LZ8R=Pz`A9uNbVZllP6HgX^-XA5p1-Dke8CA`JEDoO;61cCN3}X0W-Y$qGw^F`aFL?= zucYlbO42rZQF88r@$vl9EZK%+VAB~gV^u2*H&uUnScf*^l@=DQ_-qpKB;5oy-o8Xi zE|g2tuw4jvv}yY4DpZv74~+Nb>@DC+oJB33@Agu8Tp}#bZ208%j>}7|o%PC>6Ju5N zaSKf*E@jtBFUlRSA$oLSL#9r`dh{d6O9^plREpdf$}w#&Vl?cS(c z4qFVmQ7m*ms^C7V1^-~BM%$O37toBm^-{LGNoQZq<}$Vcv(<7-2Biwv$~l#U($S&G zT|w$_M0)LF|ABy0!w*r*`zh?PVFx+~gLv3@^VzzE`siYv-hi36OH8ew#p~Tej^)!! zsYVG*4O#}d9z>ie#(An>WiVy(s(*4rWuu(;NsBJ|*q&u0@ZX5EG1s-dN2gC)chtzq zUwPeAdCWFn7ZvkoRd^}UBe(nqWlG8)T*FLNKFk+QT<}I+g}$WgFZh9HfV~lcOEwpv)hKC*mHOHJo$L;$>(Lhq3ap@LdoF{CMG%Sm{Rhd40bI5!v4SP@Yb z@>b~lvHQ4fb%g)4)h+6Z($^M;y)2}nRAv@h`8HrhyxYrSO=m}uz)4A<*y<#I{wlrk zDqYxD51T#Ary>+@uCc$uYE^=Dm-nzc?GDxAJoCcx63n@7^S5AyPeLJTrSx@$TaeM^ z$?2k|JE`2#j5MyGE^LX863pzLyyq1^7=fq1^So0i;V(RSjJv(8 zm3uLK@(moopFee#7q{O0=+o5LpcuJ;p=xLFtey3zTV4-;9@Q8oX&)W-hhF`mX3;TKFB9YJVb1-7~S{FFW-4ha2p z@J)O`4yPIZZ%h)`hf05;wgu3 zn?J3YiKbC)rQ56V8nJ&`QT)=18W{04ETLSH6baJI{KdcLZ|ms|yTA`9{y4e6Yij!I zjomEji)yhQQkVRVuUM8V{GCm^=_{P1_gTgmf@jy4>z|J(tfjd2;v|RmM24T^w3Of; z6p0~}rQCMpIWlxk69AP)xGv49Y^S1QlZ`?#uF~E5bi;jbpt)#^JNW$LpJ}UGlMV~s zN5M(?`3Z!?Ugj6#Q<65*bYbJgbI$1nHNYbi0=G+6Dhg`209;bDEL>Rl3@0GL${}2r zDDr`wx#rmx4{MU^xG=Xhe;@b4<`a9aqU^^#X1l+I0$prEQ^M{7q8f>#>;1!-r+q?| ztavV}>1?9h-D2_ht}CC9TDyVyf4^(sNiKr|E>w($Vfc3163yCInYyTIqx)`c6B$ObWjNOkh=qa!jFGd z(5s{r1*P~XtlPR95>c#?2#NU&zFW5z*>R}bGx`O_Xi>(qnG{>f4NAm0-|K6>>_`{x zA@{m%X;QBH+3FE2cwSjoS<-?4_ZHZ=4|@o*qdfA-mKW0KK$3$q_~(Lo~IhU4i; z+1dATzoQ=5rr?6h84%(2$N#~>yLsJSs`Bt30@bT`IwMfYiEF7t_QX2H)-?YLn8r_j zlg7?&$NyZ;@dU(I85RM>gZZTWJ6Ij@OZMSv@xneJUta|Ldau7|jO1;Ka7*%kfOt0# zp>K`P{^MH~e^vkc)b0GiUWrAMI|!> zx9vEt0DNdT1r&&;GkVRJIfh8XYn9yxvVmkTMnNI0f&3^g&XRq}8F^esMB^ef6uda!VI;(1e!VzD8HT}S*#ylD{F;9HgEQ9S5l{EPkS${h>kKT9m zTnzCdUp4sd$mboL@(knXXANsOg7{YhW4QcoGu`~qN0Ed;Vyx8zG86vDtfP-kDUSLI z^R_XoQ*S@B;+efTNZ{-tH`(~^XSK9SRCAxX8!z;AJKQI~lGTtuChLgWsrViB{@v&l z$A-L7aEBd>*ph}M?lrHmxj6G#QJ11WBK%haDGt4`9nyY|a{uugJY%=UvmrBz*ryP0 zAa##mvo*>752ZC~Aj=4Xzllh)U!uf%`gWE38Pd^~>yTC7gcp6a3*Ar z1ae=KC)?9d$erS|rn7O@C<(H~&ip(5Ec9?Jz0o14zI-|^I5n#hd*(BJ*r1yP=S5Mu z=BwKx@#c)A0PKFyD{Dh6=qQNW74|;6cyj$C?dk!S34ikE2|%jOy|ECCl`XQq;)Eno zN|1CQN9usDW8Sduh+Pcg>S=?PdT4ZT!~wD_ctTyC>OIvn!E`rhW}$9LsouFVP3 zp^;t)z4AfFD;4?3s1lL?K$9&np{X|!FUr5U!b#T5SX=qtG<=aR{j3YIh;y5>Lh9)*7e`)YbjA>FuqY?dLc{V;1+ zdi)`g7dw=qb$$V{4ewFyM*XBynoAPuxEC^Zb7HQTm(1 zCl!h%5b&2j`kyZW(r8FcOpZh9j!OdZr2@I{M|pp1*$=Frt%F`TQ;Kf*+zOSWd>_*xVkzL=VcZ&p>hp#E&V)Lh-ej-0U zGM8I_Iewkd+U@&J-%efX3K^5aJYzRK;`6P8E$Ug46h0(U z!8^D90oPmSdo$8R?tk8aW9LTO9kyf~N7P@g2%_C7QA)u2&;L?WVG55NLih+w;IB0| z2|CVRXAE^i35wZnS`}V2<8;_XJ!_D{-%R&id?tVPn!^K~U~{$ZIcIv1in$j~bNj2q ztn%$U%2~b>_dvW2K*vKA)>~=8E~&%63DF@oJqRq!Nx+K(vej;p$h4SCZ zeFMkyJnG;C#ddzbEt_c@H>-SMtCxuTkyT0s`g>ukRMyl(+|eG1ht~RM$u&ycQ(Tm} zlYUsg8@)KhzBw$<{H#M=zN$ue0!GiL6i=a&erx>ClWRz85TT7x3q5u(Qd!dvD3oeX zdn#{N$euF!o{@4_TA>q^>^9)Fc|tvJ`<%be5Ftk@xm2?-oX{XG!)45MtPat&`$C@KXvdw`Sy_) zeCFtx++78u7b`4=vXb9vslAhi{b4V4>%WH)fY|A3{r(z+{j<@xaAUux#vwLVm;BC5 z9b73NZug>{vm*~T^Sx-CSwlo;>>)04gzj8v_~_d-`z#DL56@5*8IcX4cX3`XnLv#D zHtBEt{G-UOIAWmq*t|HFd26aY+peCGW&laYsU8cY*s!Dvl-@M|QQZ4sBZC(0vR(Rm zshl6Y814Q4`EoWOwxQfM-?sTjIGG0NqC%r3`_0wlnVVCVw`G2y&EU<}ZeqIF&ds9eL9<4I-V9jDQ}`RS86Zi&o?kg zgdq804C%g&j3R1@>z;^Z!-4{(l&2EEiz)AZ-2<2JBk_K$?LKX--m$aDOLtJ9y2y#y9i!h=2M4jbArDwo#;{-P_A83tCPJFJY2#VxWNL z!?e7tmr>3fzm1Og%yI4lt>Hi)Y5SzzQ0~FKQ6DTz%OZF8Uolgs!K;qH& z>zCf1RnqA`ZYe^rk(k@&{XL*wV#(F!MqDD=vWM+mg%n{%WURpqq`E<#Z+6mULN_Pm z#y|IVJ^sdeYns>Lk@G$MO6$#|gU{26W5>rXn{RlPc2DI>fPg6Su0w;j z<_C{TlQ+Ew^-VM|@&tJk4^PB>Iw>P63;fhqKz7mbL42_!jC*7LZS1%^pb;nO&8h&d zEsVmvB`4x<=?kmzvTh>{iv=;Ov9a}pSiZiJvYh)9)2JD zf&1czw<#1aY$zREE8d+mdl6El&(6M5O8qSH##c}xU;pH7b;EQraQc;^F6#oL{Si#) zo~j}uszEc(*>E+OPH7Qrdi3**;pWXs)O5y1Ml3y8k4*yk6r;DA3eLaO`;x)m0daIT zjVV?^u%(rJAQyNR6+mhuV8VPdyka6Lm_zz`lHxQ83%e12_BM4ZkalHDe9DCN`VDql z`)LCfb~Ap#$McDft3RsVsB*kcECVA1v&+1-CQ}5s=?-ECDx~UW(%Pfa(g&GwWa_4= zi&!4r&nLm2`z{4;D>x3|{>j|q1ix2%+BEyV$H$V4EK7<=?^H>GiP^79ZGKdry(O8I zjPi5+yoeJNX4Y=xya>xEdEU6DTocLg-n*e?1D9%@wz<-H7H$X+GL4TdjkR|7Ekt`} z?Or73F1Xnrju-Ew*(s;jAqRY1kZ(o=%KCX`Jp2`!An|NL*{q)dsctQf6T@G zJxgf-En7fd-X;n9(R9EbX{j46n_J#A`();Qi7xBjx?lA+X4E3M!&={HfZ3s!nKgd> zviOZhVc(E}5t7;E)H-AO9lPJp>58`AiU(*?-otK%#3|R)*3}08G zbF*@A`pE0_U0#C<0BUay+zcQ+$EgwRmeV!*$?V{7?NAJ4`DCtc+0sQ!(r`NiuSVU3DjBea8Rd}lfUm&<7Cz9IjL*cLEX*47Ebt2?~3wIyR*Sf6jjWElhFWJ@~ z;a(8QmZ#Gu9Zgp`U6;lEhbU4+SBlO`%%bMiQ~K;q)M5yk*DJ%A{^9#?{y#RG+oqd4 zZ<5FCDBShu(s`8olNXbFto@A`E1|UZJ$-Cj)e#ny$G1^oOy{uEmRTgF_JAmz+pM2y z<6{PrGU)n#>^AB!?PJ>`+EK#g=rQCW$b<7jWM9Nn^NG{jhArkv3;j$a?8~3ir!gvg zsDLZMADi>--fH0F{(LMFd3~{PvFG0T_yi%BzIe&w!-hFe#G=+)nOQU>{Ne}OEZ4{j zv3#~?y9UO=mBnHU{)cyXJGm{TkE8?V8Eurpem<8EI|ViHu{OWU%lXS9=t-wtr|j^j zuCvIgQOW(d&ixFjm9&SGHCkW0GOpjl*v2f+qVb@1jj^3WH3lnmg6|p3UJ=Z={81e@ zvVKV))P5+8m69wL7*Ce$t@(X+_2>w+VgkO^30m zST_G))Oy_{W|Wxi*u4U#XI4u< z!uxHsJsFD+PShIenNI5FMRyhuB-|Oc+MTv%x~&Q`s_C7!P)T3WC+AgSGAM0Vbmg{g zbBYOiu^$pLIKSmzKNl^$>GY z&!2F%uGbpw5NG34=|OMYsg9us$QfjN-TQ zI8-gRr%;9+h>mo2vsdsk*dhEU;S_Vr5iP$2#L!G8M+C0XdUJLZQMtWvyRs-3nk?v2 z_wt;g@uS<-!reufA0nM*>seiU3Er=pZaZ>N5v9P3NU7_!NX`Ck@A@$n``ah71f3JE zXwAk(MAYxN0Mg{Z{EOqtYibsz-3rmDs_ei84hmb!&#?v5;EC_tBXeThdxk+D>pwPN zpz(03$A60npM4osEaWXx-b7j8OnUppi~~t;_;w| zhPu(fX`%RSkqsY;3mMjW4Zc+34ZZ(gB{^`@0F9qjd13%tzsh< zv+oa)-Qo=WB8kTMKA|_1gNp6mvwR?)ixu4SO|;ckLJ!9x)SlJpW_Wxfr^cd6aa41L zW>;5qbeHf?mmqfmYwkk@iPnwW#m?!vsfXasfy4Sv$l~MOpMxEd3VgOZ0mh9B+*_Y3 z`@0tABl>OXKSQ3UDqRm>$ptG@>p|I$@@>6ss|o6uZ6)ZAMChG^_iv+Oh+V`=Lq+sL zHY0Lg^|0L zUD>O=A>(VVwh;j}IS)}T_4T;**dN~-Q66oBVUHBLV`6KbshI411*s+~lbvx@+;N|u zZC0D#7VV$jcmB0#J|J26yu1_NF|Zzxa6qE%xr~|6LAb&E*%({vIHqOs&ESWC@`G=Z z0-ZLW-KDBmqQw<~#aY=&fvwXAB*!u0G_?8Z?)*(urfU8EV?=lxtH~-94VDrq*dp%da-;~e4v9I8YMBaSTlK%Wmu<&4Ja2zpc=H&fv(p*Aj$bXsik=_}> zkfvoBU%i30WyoKFikNxO*Q0i>xQlA@$6FJ0WjBh#ehEA5*TlcM%TK0te7KJP>w9x> zdOFt^!G-pBVEsPKs*A_&$1j2#rf_>la|JIj^|53=Si$TnIifs^$F7rH`bem2c0D4X z{Z8Tth_kBFpZG3*gI>+bx4apDbygghP(+0H@fl;YE672VE4`^wQ0M>jN_7i0iJ4-2 zH-xWlPuud%Pa)ev)*+}HRWtwTEUbi0~)(dp8Wr}uUU;UY$g*0Fa=dz{Lb2$|gwC$No7?J!Dx zV}2=4R#Pu)y(v3wb~4CdB;z9^+HwyWft9Eei_hG84QS3~an-L17~;YmGKll~3ne(Yn3l{$OEKmB z8c&F3{IK3(9|T3zJZyJh9Hr1XtSKwzFJj0~c-TLUOn3y9Axf(w6|+dK)n=uk8q2F$ z^B5a-zm^@;G7!unlGfBOk2n-3N;i>@v;Z@>)-slI|5wKJ)Tvw7y0MKf(M|pQ^bngv zUOOZS#oUqG7|eNlNO{LISFKiUmiPZQVt(}=*r&u`mEtddSX+;XfwysB-J!G(6w1dmnoG z7IV@J^P(PwaIi4U_&ZmP=n8Vi=|UG~g=oF}RHR(!QZbUt2c&l#QYha#<}mSF>co`% z0m~bJKu65N9$8i1E|Pu_^&Y#_T6vex)*2~Bhz^nr+cskj-a3+5Iut<(PVy*_5|ymL6U;GxyB@SN6~TvFu@+O-jqY?Sjl4+@$&g(tw1JGgKFh}NchERMhD|Z?E1qu_b-$3S(PPpY?(H`Heg#+i4+JECm;EHpEyN^AMI`>k&P0`s zL`>^I^icEzV=$NxF)j1L%qpn~&rv4Z{_Baj@?a909M*{(iU&xNlSrmRDHji;l~`0~ zEe2swiGZZZq2Y`4U>I#8uawrfO+=ITPd-hpva71WSOI&T-TMct4rA3fE-wQ#!{aD6 zmcgT`Gl2Gh1LrFdOVN~C|MW(C53bd+{G+kNOt8)m6mIL_-`O*~VNC8<1>p3lEYqgT zb&opZJYag60IILohjSa6hwKPtengh9bd@%@_L{|aWLcu06P8Pmt3Dy=2}rpvkdcej zac;bf$J4&?y_iEGRI?bQHN51mW|Gt)lKBuvAl&L{s-PTL%oGXx;Fg8&%|?^iBwM)7 zm}D3u8tQq7_yQ{?7zL&?iWWnhEE%`Y|oGefif$aZ`663w44B8Io# zCir)uvvuDJ@lg`M0gwS9+A79mfDLCzhco|%gV`^x`W8qO_@($~BAJKw)I0$H%yyG8 zdot;etYhn^JSFw^+Vq0qO8Ykbx+XJj8zxD9BAMA(cc)ct0z`DubOr7y6heVxoq#q= zs#Qy*3*POyi3byuB+*Bt)wm!rl8eL`I&M}bNfIJ)o!Fwm1l{8=u9^my;3t{uYZ}?Nuag{PoZ-h-*l>$kaBK0!-^|1EDY0PPq z0`Zw5CG{c@ayVsuR+`_$wiEo?)b*P^3UtZhQHsE&v3KpdbD&R!aNT<^O*gdib)q=h+ zbgJK*b!;dd>OPHpwDZN3>_Jh#XUL@*6Blli3)u(xW-OJ73RnHLdY2QCLv1(~bOI1> zVw=3J-=g3$HZ1Ae1!yI*@sbg>LM_G=5%xjlX>Y_vMztwoOpr#or1-yM422n#@T`J* zPXRJ*Rnqw<2+o;k4vPP}YBta%fGy2Hy$`e2w$x4+(&q0KkqSV5p`;cU7B_wIzH0?^ zG`<^R73nh@)tS;bfGd5#KX%Ao)BM1G^a@`JS$A@vF@(W;fxfo|vn{8t%Bm`x-zLm< zHmxb)wc5n>`TArjZ{w-gNSw1F7$nt~EQ1=8IDUq8ERs4?y?1uR#FGFD8cCL+VFMz& z?~%v93mLT~X^`Gq`}c*)1n+W&m3n*BA)RW~eXv$2d^l_!49l2Jmb8U1lq~FS)9m~U zC!js8Nc}>;)MUK+SeK*G^GhP4?5uK00_U*!WsdPcuXV6F4dsI(z?S2E#|=q~Kk++6 zh`Etsa4jgRgVZm8_VuYqyZK%oFlPv`;rvt7UI<*ioj1dpJvO-)-lD)wPQQj2O9^v} z_5o6i0Sa7y7`tz8W@-J|cZMS-*qjV!N1QKrTntLwIsLCFY8`CjTWY4&oA6HXAoMuB4D5MeeyxmtFlG5@Et(Z1dz=IaqYoSt-yb)@i-T^WVL;S$DaY5HH{4o zVP;jR4ubsb1*#(-2gpd7-zWw@fTE6c7j-qmgt;)Y(}7=%@grJW4YdW??w&0A6_K_< zQK3r%&i^ISN)VhOw1W$;Lb(6;J^hl6Ci($D#qr0bnQWK;K$|GG_enh0L8u5$*>j#k zF;jd9twAW8V{gKy(&>*(Ly=Jg5OQRSKl!H`H1ouQ5Qmdx$f^`Wf38%|3^);+Vy3=2|asJIz*Aew&H{3l2AV+_ z!ey0tRpd!_^#D%nkiiD;<5x2>MWk3z6!+rxV}RaJa=tmFFnYn{6~9vlV955zrRL%M z)hq%UEDiF*BcW&UAHnC-1Z05!Sd?No3wI08J@OfKC+fOQvh!$w5G#_!i&?9$M(iDU zv_V0Y$y$|_BPz7+rR~l@{pnEckCa)=I?Lgzu_N4;6x4fKfjPLB^ZH6^}l1 zV?A@c8+McDs%0sGDIv$giSo$`G`KNmS!7GhWu|#lhUGy*$uc{Ufb$&Ah1)?mkh|)3 z+{0Mu@&NDJlmLPh35=oxFsojz388K>z|wD#7J-qjA(8D3UhGG6hip zpQcckUWa)n2?R+d2uh}gvPsdm)$(nZ`KloVd|dfzJ6kzoshV7LMDXs@EMCBIq&72VWeTuTWsQnaz>+4LQx@=^wc zVy78Z&nr0}-uf#O*=>v6cH9nnn+MELO|}ug%re6MCZOEkgZIO(tAq78uOe@G`4H_6 z1tB_FhJ}^ZRYK5gA`GNc1T0sdEt5kI_N`J z7G15uVt+Uil@${y^%_&_`FrKS5e&r*Zf631s1mu7i=m{g&B<4Q z)NS##>Wq0!MHDm&l(;{y5~m^LbP$#O+&Ib`*l4#3D)NCp-A-bSqt{F>}$K5 z&AO-qXU$YZxZOHa+gkOD@N=AzIX4vuF+N#Fh*ia^sx5PS2NMQ^TT(YqE}sh5CgejP zg-i>lw+G>o6AXbG$rw;KY(h?gqKRKsL4IIUIVch%C@Pki!=2K7_QZ7rk0xDPtpuo^ zT8R4HD6Gf(uv(&S{LL%z)(*BF3}~*t-ng|9mJyjODFdM;TnMPc5>)|EG5>MN#uG-w zYElL)GySp1LSmAPgS${%O_>2dM15nz{E%{OtBjsz0}9UoN-8B|@KhL=S(}AGg8P6` zXqv5La7C)qrl_4aY1&tyg7Pf00BRZHTvHowY(FdoX&iFT!t6f3Mu3W^E*U8U4R(c( zdFHGkt9L%e&m8-{eALs6Cw;hKHB6{FDTN=D10o@oSe+_ZhRm{VIS-jyq={+6wzdi} zHt#NHzgRD1z`{@D)x&C-Q5RDfKB)fXw?4`Ey(!Io@UyI+8-_#%B{X9({_2b_LGrek zm`o$uMqx(7%sq$%t&kW&JsLyCav;LQ+X=%A4PBz)DasT?s4!_DhgnPW&B7mca!#W=UYEhBzRa+|1|h4$XqC zS{D4XkXSvYa1UTGtdo(V(F*}HFq6QLL|qMk2c~Jre5h3XJ&C}>QSv{H=r4BXlMVU} z9vj-ru^Ogw;6n0+!|fpwd=?hj;(Uelv$DZ@5}g9#a<%C4_v(KI^2jJ!`dMH|#85(Y zmIsD!(zeT3O&VBwVA+o)|Eew+Y?iTzG_fMVvcOXB%|&A~I96Jq#CLwa=P`ePQ)&&t zSsB$GdcO>|3Op@N{O$Cw&_2rADu^Rd{+aU zsYvP2d&A1!CnsBX>raD8T_mK{#27l!EP7E=lT&@S2b48^Lkww1go=c+>?*^3GKKxK zsYrj|H-Qa$vQ|nOql-1jwog=dTdQ(nuSRvOi*?PmKUbH5xYIIDz4JB&3+X{lZ&ZWs zBRDVdTb*#U$DzZVw@9Q%(@r$5LK*{u{xvbVYpTR)KoG8g!=M!oUTP}mLv&FsA*VnIn{C@E;u&#y$G8;D~K4s0SO|8dliPFyoK7(s}ByVeoGjztSKis@B5v^)soq+e7%JA-)h%_~^ z%E9|ZrJjzwhx-vz+s4-B`p_WulE_A_MYB~lhdd-~b*xYpS>nPDndP@A&)q);)nG_0 zQ1t?q_nb2(*5+7HMAVXUVl4%%lb_C0$e2)dP&rl*RE@?EEAeuyqT!w{4z$*g$%RvC|7S#1k6isZu+WZ}hzXF-zlPA5;)uXhjcdhS8)2pqB=Ak_GC6X|3FrlLH0V zP`{@Bwbdc~weYjRd%nIN{j?#hq_hXEk%^EEVYvs=i)jX5yp88fygZttBSuJR;cIi1-@V zXK7~&=B_7R=shA3zT30-0}XO5@txIv4VloZ*Pzn_=b5^V_E{=vg7Yh238m&|nmyhg z(|G%yWg`n;_;c0-woC)fJth##L8u`ZHJnwJm*eM|!ri@{K*if%i z|IX;brGc5V$ywTo*UtHeyT#00wI@`k8oYKkm26T(EV5}QvqnMzGFcU=BL zK3DG7yDJm48!xG1KNGasQCZU%D)pMH3*%BOI294oOCk+*TDRKicX%;~(-<`L26qj_ zeEHvu&7=jmm=IG-{A$TMo~rczD^&S<{kyoQ<0#krit?blwxOt`Z)R3To^2=a>cQKP zI!4dPC61627bBgC!H_UKYqbH>p9x^iKu@$|-0$ZxClKDkFKO@zx7R{PNd`?XXL{U!ZUUWTre7z zzF=FZuhRT2_51uVcUQC&K7jVTbU!Pf~(7a ztCliMk%oq=NF=~s^GM;nxZE{sYHVBRm0EGehUWi0_s4intG7o|fd3udu^xphyYfN< z(C_2lg7A}lxaq3uCyBLtEA`-K^&h= z%4fg*`F;3(8hnD42Q+5W`|`awhCkgYbAA_nnw~;`BYn}(JUzk?f3?Yn?{EDt&)}dw zw?t}sRz|F9$t)mFn;n-mjj2*^&^gRq@UIwG%GX*szJInVvT&5BUVZDQndQk@Q!CrT z%mof9ceV&csx7@=5f(R_<7Nbk$dM&}bc=|aE}z(qZ3*g{RlCf)4zpoR5gRUNv1?1B zftRcAPCnW?H8xL=F|>HCKJT|Uza`4r={cV#`#u|JV$0y4N{U~Hx&PK@xoy_e(6-Qd z0o30zGjJ|4)o|EiO;$G>sB6n$EE~0Eh`shEkU~_OBAZp&wGzI(#U*$niBQQ;SYAjq zed_+P{)%TKb@D0GI<^dYB>U^Tn|aUwb{=P%mL#73&aF(eo>XD^Xj62tDqmO57IGPASQtZ*xk{qC zw9*kSwcXBz4)3fba=XPS|I7W0{o3?hO~oZYA0$pii-r<`S)sWHb0e!1O4_#hmHbJS zg;pAQ4X3|a)WuF$uirZ^O;yD3w?rke;f|&@qHXDPH8C=o{`=bI4Wa}$@ML2Ys%t1_&dCEc!@q}G;1 z75Q!d0$KSif|f0V+Jc2yKSC;6Al!|Dbvy_+pe~3Ij2j7KV$gVw!?1V*4Z$w)iZn-8Bn#JYf=YGz6){ z4@>J+nI=4szgJZR@I?IXT`VRGE($DOM)H)M)llQ^0&j!Oz(N`tLSNz+J}+P9h_l>K+0AJZ?R}9WCJ_8FeXb4S7l($xTXdB{A zG6oRFlnjRoZ!8T@QOv1P*sv%U7qC=hsFvAh-CzCXrYJ#-o0Z{@=AMBQG<`esnRq-z z7MF8ow^VEkMTbUt&fbnt_-IhP11nz_V|Y1pns-0)j|v}kz<2@5V3jEPkZ=6UYA zH$ys6wO^hMuO3XWDf_d~m^*^8x#O|T!%~6>o0U<60xD(w9QYsCw%+4L!#|_}^YoaG z^8+nr=XaNtcn@~a)YT#Xz^>g1RIsIG_~ytC@tAh`AKeP&ZB!o#g)j5zRSpvJ3kryW zPn9Br`P3qp0Yu&|XORIxo-Fp`)-<);Cbd1`vb{*>pD{TCfJNUMmLQtWiU(G_@}Su< z+SVsEPsFQ)mzi}<&xe+ZBWRF+Bh)L#!s zu4J220&X*f8G&$XlW*Ih-OQHe*vi9BLDsbblTEx-DNJ-(kgV zOS#=v=_CK*L#$^zo6m2XH#^AA4UedA&{82p+NGR`8*@sRT*!uR%_Edb8G$xUE)jBZqV`C7LLw?&~LjVKJ(No{u<%D*~}XGnT%!w zdCV_C?%`45iH0FX!yeR%0|mpU@T%tT!q?_eRFow@Id3d{&0@dyvx+V#BF-;@VNjC{ zMC2uA`rQ%}$%6^x!AU|#fP%TQWxowC| z>VmVg{=02C#0UmBW$>;&wSUpOkRCy$9W8`GNpAuyc0b|0Rfjf5?QNOSL{rk$PD%nw zWRlAj1AoM=e<80LIxqUqkjm3_{+$Orq9J#1HQ9IE)IafW*uafSl^{*fAV|%#jpzj)Z$O6b^1#Pz`%ErcI<*||&T zxJ$%?z}%h=2I0!3#7d>}*1V^2&8-o-vFs&*>?PtKqsX^%@z?~d3_}ogpcVBR5g99G z)af5gQ#EL-<{uI4qQS+)EXDKk^weMA@IeJIi-Ii>6_c`iBEo%eBmNtzh!B_wra=eO zpe<;wQ#pzdz;Kuvh}OW0);Q9HWe~P_1UQPCmu0BH7kE%VR}E9n29H4%o@^LWHVm*` zoKL)5u=^gpPy_jmjcm=PEqaTmN_!x3smhgU_!CEy*M!M-LGMzx}Gg{Z*rIMkXp}eSW6{S49)* z`Zd&#ji$3=h)xrWP7{if*?|(d%IX1Ion*rJO=;E$Mr{Oxl9hvRf2{i)J6ypRsqU@j zJ7Clua0x*5nu_t%AkiF|6&0EFhFr}I)9GCA5lFa|Wiy_yyHxmeNJtCQw2tf(=p zH~LzSXE71T`@<2~oTmR7y6g<0m-&ZqPnAXjCl`$mA;OZ#*uM zDj421$rH*bzB%YW7Vca|>{JGe6y@QKy-&jpQ3VI8f(d;YfPJUQX3b#KW-zEwnfJIx zFrKHBn7b4f$>WAuF9w58c5vr~b>HeEC$GzRiRe(bQexLqSUI2idynv%soTv3$J{dl zzW<-~suvV8nsVQVm!f_iy3wA)i@x_CoLVd>>HG-4vt|*pqhf(C!rFEc!kd~5tI(4# zaoAj&*qRPn*%n78(*}=~N2eOCerY9Nzo*Tgs@lly_P;+F@d@DlSHD$@dZAm9@IBgJ z?}a)8>C8MZAr;_PEv|@kMj!Yk6_CpIsgpwFT>=H^t)y5>y+(b#Mq!xj*qX05Ma2k; z4i<_I6jV0Qxzd}qhz2QP_tU_%laZQKJ5Apl9lVHQthrP-d`LqT|YA_`A zL8($Yp54NM71e?D##u8qY2TaZc3(`?Xp^tGtJ9ueP|A=)stU?ztL^rk&IX5SKpWhv zU4dWwOHkpbSvXmfjP-KucyH~vuR96f``@;xc33%P39QHotXE!IlTPi3P^$b;gecK* zjrcN0`IRM!gdWky?V*X&+lD^TF{|aq@!rPq$TSj=#MK`&<`tX}9r8dOa>C|9tDWo$ zD_JwYMx`P$YuXi@DqZq&U2^sALMGA@uCHbUY{&#`jKO+v&RL3W_@64A-&L^+Cg24> zuc7m$IsJCN-yZ3yRuaXQdcEV4z2lKcY01}dBeb8|jdPzLTI}0?2DE$OIp<1`ru^d` zADy%9d-;1ZBG~Yi`^3U>%feEIHYRJ&`SbGdc<=Cd zr+cSpEX zjHC4?sX->G>d%#UNlmKaoIiTCEX7MJ?|6c7vb4xEv@)lQMF#Pv$xF+5^UHZ@Wrx8Q=;o7EQDkO}TG5-X|hv?O^0~ zaM8GOD3kl0zEkU=Q64~RUpg0~;SLsb2P5n*<6LoG#l{i0L=d+Wzo>Y?$0=vC4{Q_?uF zNXC%5yleWw?GHorhuvQk1!jHUz;#iba5H@FUtbg%Xi-ONQJ4D`=WS2OZ306yfx*iW zG-B!xh3Yn(&wVE;EPE*|Wfsc~{!2(wBd@S9j>|P$7}gzC)*4ACW(E;Aji}s&9aZ$S z7q8Ye)!7@KN{Wt0{vzZkyLIs(QPi|(QPf-31#rgG!4ET2zR8A}E*u!m%b+DAdL;Vg zy7P$DJl%E@-?A6qQpP`W`PV!p^_9i&g&Nw0np|jv4G*ngtHbEoFnM?+Z(t>T%yQ0?}d7f!TA@aFXvU{R=peg_H8c4ZGHxFKyXDnlRZf z(QQ)O6sY5B2b$q5T*d31U{hBV18H(S>C9=0omSKyoqVtSI)8~A->nu5GRkoT%JInM zL?I5X01l+ZP~M-RyoF3~xb9k99p2{{-Yyp=8<(rz8(f=$SOsiL#Lk)pe44dz+yQCw zUTJbdn#u=(##RTBmSEocVBSKKd5*z#i&b{|0CdICs;Wc5mxf_kg1!7$7IZhoPy?Cz z=aiu5l>3vsml%u|U*39O-n{(*`)tSYDNo*wSZOFGrx(J*NAaJ`Lce})yW2f|G=2K0 zwaa{JFVTdfDNe2?PM-Sr4*|V>`MNPTBJx?r`B>ojn6EEBpRhw`AUFqyB_~$qkBsA+ z`oNT+z?3AEY~%3N2C0-FsgxuficdsN9zUsDEk`cUZxP<<*z&5`@>W7q#Bq-6S1}B` z0!zlq*_9(O?(lkcch_HX*BQ2w(SIn(xmk%6_u~@i3fEik7E((KFP7dp)R|ke^gK6` zC(6I^{$2l0&VpCcf|r3pdaU74%1deNqvY5}8EEN96&4XMIRP(uYGT1!U+Icn9l1N= zNTU*3qmo>zC5AKYt^vJ`4dp5?`7$s0LfRh!(blUV`%E0W%#)N~yyPvGy!Doi3N4eT zu03P?J~osSyyT<2`apdvkFCllZX?Iit17?U50B9donphXghH8+>CpI#7&tM^wT3 zZZ_(~Nxlbo6;52|-V^Glf@7saGKAd?zc?9w$=mJkxl4Q6o}dzC7Zze~pEkpcHgOFZyHd2neSfwkBUfrR6c%keM8knfy2j!{{h#w_4BaoA9=} z;Pu7e9g&3MNkTV}6Tf88k5c)t3VX78!2?u5NyB9XxiOzW^y##-fdTUoVKD3zq%6BvMoR`t2Rp$VVvN zNBCva>=9KxVWUPDhp;YXT_szZ(BCK4L?0>S$NQGqE&9sC>88U7VODjmB51!ND7rgO18q+K@K{mw=aYvB!o{XYlnGMZK0+(Aaop1LTG_g zNWU8{9qb(-6u^%V6qG_0n6ieRjUwg~TRU?6xk=M%QvjukMYxysbz6CbDjlZn#FBHYFzJR|$3 zq4PlXMoz*8!9A;H8n1mFma-~!6aW8&&h6j-N9fEElzGx)AGe`8u{jm?w`6-v zaNqapdSbIIW$I=Qa_{f0E?)cU#G9ik`2W~eiEBLTX8134RKGBP?|PoFm=mb|TIQjB zEbQ~J?|tR0Q(HID_>iSctonToMq4@I-0iU1GdIUP^|~P8{i(;|L!n4-seVV=rue3D z&)D(9m)ah#s=8yxJh;-NG}||4G>A9>{oX=YLFV#XChZ5myR1s*kj47(it0m4zb5;K zpMpy!o&%Jb5qs!f_EDn_68qXvy~M$pRN1EwbIKKX`Yn66_llGlMYn(rC)$7Y;>m(l5(Pm5cs*v9*^CYL>QF=@FtWb#HnDbxX zhNrf##zv0GMKSUV3-r-@w5^A8F|vP8I_LIQ?9%2pjhL-SSC0IIh4=RZy=(2i3;bA( zVU75IE1e7eZ>94j&OEYC1BnbMQBQD&PI(Zm_E~pc0>V!O;KNs5M}TIY&PH zA=93V4=NP!^#_+#>jKEO#mShximy9YOgJykIO~j;ONW1Q7O!zIQ=>(2alxAkz)s<8 zJ7TaC+j4^I?);Khlyxbq(Chh(&;EK@^YZ8U*Wj_AX?ud>H~eFkm+!4HQ+djuIy~cN zAFKNEk9`a@n-sbArmbS}^@-|X!P;VZuIl*4bI6L`3^l*+=dO9ZZd|n0HlBnY9#@V9 zxs1E1x}WyT&VLvSc244pM+TMCD*As!ONhp0e~fBAVs;f(?8--9y!w}R1!tN)AFTZL z?a+5bXRDH68ewygCthIGTOhL4wKyOE8doB-xiOsDc8PwyUY}4Wk#ynLo$6)uF`_r_ zL&o-GIQ5$J#Sco~>cyhkM9SWvr0NPsOZ)jx=O@)g?$Cs_=}oNTCC+7T4b234k;nc> z(LuZzq{rt{dV!8<#N}~y_9d;eo~*7Y$dW&)g~n=Q))O8GrbIPga<9!!oo2mLKA5B= zh7rNrwY}z<3ZE@A2n!mKK7VB3NLt}w z3pS9x0Fdb+?iwJ>1>p9v8JqUSICd+ZTLbFMf=Y9^DKz(5!G`Do86PX)`M_m;12mJhoiE#etD>MF^t^Ehrn?>D+WGlI*d;@zAf$E ze{8XjP)QDi?7)qB+0!m-QiV_G<+Vu{C)DncnW+A5Y2$g)<5Y(fIN+_(n;zhX>-P0B zkZn?AY;;z8%3`zIT-I0%o2HZ0=>?&(_1v&ta~@Y7adJ3LpGtt4A$ie3pBnlLi`hV9lckuRuC#AO`xjcvY!jk-U?V6&^0hT~Z5VX|d)t;X8V>7ezoZzPBwlw_WAJBE}qR3ibAcN;sZ9&Z9R?6`8aK9{E^aI2MJZ(I`E`?r7z<~c?&uL`QK@(V4ev0Gb z4A{HAxcxccs<`lLgv1NeTb;)z$qqKILk!1Q0-m2g^u~k}|%^(nJ z3g3x~Gc&Q+Y#H_`q`H+w*}{!C3$WO{9q!t?z0S$FEQDgO(mcA09Slu~EtLEAzYO)9 z#`v6bZsW$7eK+59AIi?f^kHM^Lg%rQI#~_MATruQQ}d)WUk=M?!)y>D3E8N3>5AaC zqgPs!YBLDz^tjA9%{w9K3!obo#TY>AAAAn!EE(t;yTt3S52|;3-P&;c(j~}khkLS0 z{*R7QeA_6%Y|dme_j`6$=L#i?fDBxkww8l?!PHxiUhjy#<%hiG=kPgD5GDLM&~Fv7 z2{}}hntEkl^o%<06Tu1b$tp_*^;ZY>>3 z|Bh)dU;2=4@!@ZVgro@7*4-vPC!%cV>-GNV?{96oDDXi;awK9m%`)9yWomoNdmIL&6;Iw*1JsZ`tfDnBK)$ z1UvZ4ilv8SzQ-1ka)C08zQoFUdutIa`{fyFL%>Drg{2S5L2l%TU9lJUypsqyM~l~m zarZ8*y~5dsHzC?C+Us-7tCSrNQZLo~L8%moN^x`!T~yA-6e@2w;^JaNSOkOHy!gZ> zE*>e;U7Z|JoWCDzFqdK4ZDaI0p*UL{DBUdH^iIYh9Lf<0TAcw*eOTaizq`s7qg-zm zq~E(OG40+1!6{c?S~5&B@pKHrFzFBJRi~a1(fBW|HqLX-k)J+mX;yL7;N#kAN$_`n zO{k>MvbNEYj+JI(KX69*Clrje_tS)b-N5{=n6hTp{GEJYE@Qbbp4jS^MvOKUB71y( zhtN9X;o^ttEeum9l$pLU8AoIpvR*>EEewXBIZ*GdS|QJGF}Ee(){6GenywRfivBZ0 zzVQeW@&t%EH2C7BPAGap!I9f)c>ZD=!dn~eWFv(6yBG`!bCz9G{CNKrn^=-6P)YGS zms{kzF!F}tSeb+65mFf=A7x$8_`dtBC{#YA9+$nQ6w0()JJ!3-?^w9YQ&%MbWl;)V`GjIiTj_Oam9!d|e&s3<34k%87{fFyRAH(_H+M&?B?Jrxdw-(AuN_ghW&gw?5WEh!3S`woB zgfdeO5w)BB!SO+_jIehOqfm7Y{@glv+}bEGC~ftH#~}>L5s+l%6Tg=?Z1~6+*hnZd z!Nd{+cE8%)A6vD5rskZPGgCd?)7AYPc>?-opI@)7_+;cB>!;`P zBM}cLn*v-mMRq)JB%9tV8+#t2AV5-R_r%{7*1B*soMCGC{EtqCgEz_9cmJ|RLEuZF zS!AGGH^R>>Yn5gxC<9+6o4b=%4QbDQL(B3&(GiI>*m(%-wDkMCQHJaxZ-;RIO#LM( zYjK$GRc{b=c>dY+Lfggh&V9@2;?nZspFjyxo6NkZ1H0DTPBZ`UH#a>ItSwH$5ku(p ziR_{aMHM$g|KsYhmUR7qFERD@e0bTfC%t?h+4}3xkK+4*ko|)BUh~G!3cF?vfiL~^ zE9Wm4Q_H^qOe&C)lG|U0I~)G@Rjc#g4@o>}K+DD^4-dC`vd8K6ez{<4+9AMdYxi60 zm!Nv*+V%DnX`z2+iR%+hXIYic(@*Ej7lshlRG_Bm?N!~m_Q>@$V;&jb3@VV*%qMA@ z?pIbsw3NVTqnCWn=9FI;MjVKwGfw{wa029^`f0W)(|0(V4;|fwGD+1t&3=7HojEZ^ z{SJXZ4Wdx%&~Qz6LX^QIG`w(w;F-b3cChZjGb( zX2iq!y5OybUh{VLRMpY)>GRL>3-^B`7@Wi9mxzMHvvLTrqf>Acinnj|CTJ`ZufXQh z3AaS5>`I*Q$jd{Lmk*!WDei1V99+01g_G6G^mCsvd*|wng!HCXHz2bQH|}Wif#6(G zhHaL&_T})MlS)^%K|=H?Fayuri!V#iz%asmADn1k^%)j^dTpt%@fmNp@v&cP(Odqo zt3Mw#0WQt90m?``J*}(wFvA>}W#OKec-%AYv>`11Kj4d%Ffe?v-SYnhUucVH{Rh5y z_IoG;!x!!Qn^uIhcN6zJF6z`4C{@y_cgjQoNYodD>Ee9WvJ_cxM(QsW;?3)|ReW6a^@~p( z?z=KFZops`(6V_JEA21KDVw$)SN(AMZ;o%k7&V^`HlK-(HkwHw&F)@1_a?CFlGFgj zP3q{pn?&ll+#yAMVyI*Er=#Eo74AIcjeDw|26sPI>SQ@f(nvH*R(2=IQA7W1{(LH& z^wM*=Yc>oE=sHA8h;B7;k7$m)T`Ae*qnz<94ld6#NxN^##`iieP#y6x8jd=9yxPq> z*^vjFZx2Ywz5Ws8^2)6Whm#LX;Ayj3O;G*w(qY~pl$)Pz(DAnYso}t3Jb1+FX&qsI zGLc<$8@_BkkCrA9eLr-MIoqfOMnND^%o?J2=OhV>@!m@_6pNC_TZl?>B2QH>wD%$0 zT`Ww-e_vKleRy+u-0lQ}j&J8YMy?;Wtj=>D-hu2LI+Gr{j>K##2s5cUwH$vORC8Wr{0n~2I8e3 z-k&3SZ@l$+J^D5B_8fo~h+1R082?Ofn*iO7cu}@^vI!SIokPALy4eY#L~-22M99{w z`1*7xDMNmpH1D0((6#zYsGnh&BG1wcuzHvLtg~XB^J9w^UfmFF z1Jt8?Igh()E!_OP$E9;$$!%9z6)$n$@06IbjGJ!Z?6ka(hiOCB@(jT(+A=9LdrvwE zrXd+QYHB-cUTl4~E|MaAc$S*S1#y#J;$VI3TXifa+)lsV7}skN^8)SGE+GHP!~V6| zYJ1jd&QUv&E#bgXYh8)J8Jke{0%~;QTQ@zLy?eTFmEZ+DA5d-@n>gAFW*H{t?>h+* zY}!;kI`O7d(!g!^1l-!&bvCAXb`1+jlqdv{A5!k+W#pas2$f}FUAJ0v;{e6p@{l(g z2I$vO*HvhS@H9!v7JG)Dv3Nfc&7pm+UKpp1?H@)&3JHgKvhi@wZ%%U?#Ux14c(ciI zDRB+iachS{wt4LiL1%hyt3yJ_CEO5DnW#D8&IIGw&W}uXvpp+BMvg+i0pAIl56w(X z`s@AV`N)nz6#5+;lJ&3a{7l!5 z*IS)5f5|l)>G(t4@GCE3NeQ&gVg0%Bp1p43pkV5pA<78sRDoY9hkaRlJQCI!O?G~} z=^GVdYm*W){QG3^=KTj=lrlf-IjDem_Zvt>((~(30~@t2IdbL(Gei&fJ41 zTSt3Enara1FW#0rS=8g}elCZ9FGK0YK6VMx8K}q70?BM>a>g{Ohy!_ zfdZs4xE{B1w8RSV{Df*XezUh09+1m=8;ez`ytgCYfbeQ1y$iR~9+12|g4gZmDbIz= zZfTicvzU=)O?TB!*G*jbeP01JH6LVfZ#|dcLyx@69DnCuRV{++pxHz+A<#Z4{Cr&5 zN>>=B=n>DCj2}^#fY~onav;+m=s-F zkTQP6<82Gy!iSCuU2Q|;%bked`_zNfKEW0+`&BB#&hhmJUwV`#X?k(g{Emz2d-c;4 z0p2V0>6FyFeOl_Z2^*gwlbKYzb4x4Ji6zm`J?y`}TTm}r!Y*uo^p`8jh6H`SuRf+Z zvMaY{G#HxE1b^Xl^AiHZEX5;x{s{F0UcG}lNFV>X0?#J0mk@JbkXo{*9)41*p^9o) ztf_jZSLv_M^7DB&JesF0=f-^{%b9|vGLTypqJP}_iH0U1ObS2D`!w+uFs6R5NRs-Y z^TXhi2mUd;yYcdxQt;gf2Te_w^vm!l5<&6wfU&!xp=YtYKOz!jYp%-Iyc|@ly*(Fp z1Mjd^*Z}`6#5kT_Ky~MX+28J9B~y5)7%B)C9#T{TbHC^ntuyGF=)HSqX=4kGf6-uH z#{x~QZf!31lEII06D3&qwkg_2^lFMJ)6FUDKbAsDK0?RQy0^BQ+_oJH7gS;WO)Jv| zc0Z?4g@M_JLv6F`uFP4j*smTOYvW2I@cGf=cY-m?lQ2AwQ)|D!&U%oY#u|0L=2x+N z@{OCJ7}lH0t%ZV?dZ3}AskWRiB%_kY9E4giyKl{12ufO@0}fTMo-E`v!cEWOeMh;; z+=VEsAC;lxmi&wkq0fHh!^F>wD=;tTL*b=E|DKeZKv#6VowT%9eRuD)p7Ttd50T)7 z*au}1(W2gVA7{v16-HPBdK`o_t3GA%)#b?cK>kExEh*_mzOAF^gbu>P=&-!Zis7zL z*_)*23+=!11PLNZt&fK*p)9&?=)k$Xzz@f(RZ0LIhaK4$MrCmw4Wc>^FW>i2NC>O| z9_MI6EHfIoExE5dYPDtag58~amkj(pOZ^ZHp&}i)o$~xnp4x)P5wx$QPXAbaz0y#A z*YrcAV?zrw!d+*HlhM zB%jR$Rpn%*`xT-YubhYR3Ba;}3$!fAgqb=u=FwVH3n)_FlkH5C=SywmVeYDhM zM@RQtM4~Sz(J2DHsi$;Nm(Vb*h|`tbe%8 z>2$Tbd^XhO(h1nybb4i#+SrJdWggbIW8pOhgftZ}cp4NK#z7v|>ILZOz;P`^y9#lt z;V<_qa!bk{)-%&+%)xlAF==kEmyJK*WPJmuDMYjs*n*E5e|(dLNTsGo)xKzbw%Z)5 z@>amAs_?Z6l^#usUq09v_u#W=ne-o}rl{3ZuzYtVs5zvDxWkAQX93r#F7V$4KgU4* z{>6(go5|adc5&YdN9C_wB5by-I8@u#_maFyXyWyV!`U&1$b}EZ!{qRHRQfYUs4;!0 zBLMpJg+qMVZ^wwl2`EB+s2Bjc%yAo`yK2^MU{p8&1lk|VF3HAWjGRFYFa{#1Ua!9eOe&VeICo+iXuSqgQy{x!s`&ONn z7oPPYc-7n7K3gODlDTw-;p-AAZHXK9*(Fr`3ZP@cuW&&$Y8R(!Mv8MUfhIn_$I)nq zsnMs1TOC0Df@|=vS)9tdyp7R3obh8l1e+N$A}M6=m-YIghizMOwd7{E+r!^6P1}tw zx5>zbW%(W(S~Lrw-?kiPwseqZcYYuC9-| zZZ-@G)yJzfrG}`-h~;49UMUK)rwvGFOeN$4WansvN?Rqiwo6LVYRgaAa_|3ga^fJ+ z2WW8bPW(zBV%Ag#?%z3h}jzA~aC z)}FY1pOeWGQwXi!jb6W@Rakabl$lM-PagX=30YNZ1b)*m^&v;H@>w(o6gy?u{qN$& zRiX@N4;3)J8WeHAusfZH?lBq|&_l~l7TW4v>-(Is#KBU*4tL}k$gU7J{=2wnDZ=>| zen>sr-fWCm2!;-K`Pt7MV4JbocVBk!2v+5qX;-X<{I)xTn3fRIFJm9j*NEmuhdvTq z7PVFRHN`B8mvCd5_;RQ7Yh@o{Z5HY`nn9J(6 z7h7k9A#YJ+K(}b?DZGA@uB!nP{asB{E?hov(%rcy1CHp4W!NPz2E@o%e;t?e8^+CIM zh*lZ#y!11%ho<@th6$qKhoFvgk=rR=z(NjtsI&nmY*!GkAbJrgEi8y4@oe-vJ`PG4e_Iv zp-V2LLZb@sVscPaH0Ytm{!S;2nPBgmy(5+d0VYlg44NE8UhmvEctfh0S^xexcuqH? z0yoZzXqO^R6%tB_Rn^_Y7%JTrw`^#7Z{LcTLK#|14Y?8*($CzBt@QqOcayKZ_pOZo zq-)(XbBNhSr{%$ahKk~we5~tF%oJk`qi>;0c$j>M<-rPl41+Q~*m%O=7Mp~5W7xUF zR>+9Yq&;jP!nr?gAWHk0XT#`O_PSvR?H$i%!i4@;HJ6AzF5bij`uF_P>z9{bQ&m#v z>vEwZw1|805#mA;KzIx(M)DO!ZE0$n94a&=HVjDhdf&@pT+S?CkLEx5}NF$AztpD)R&RlrJ= z!;=Rv>H=kJ4u<_$Gfk*w5m^?V$5=kEN!Q5iymGwtyDsnsS(fOBKiT~pC21tm>M5Zt?VM}BrvJ`*=-?dF#Os9B$O%7O&afk z$FarLr;aW_eSDknfY5$yQPhUiey@N3zPu>~>~`bz0MDfDTJ-oJyd`)POOh4{s9q{B znAt3WFjYaLgX=Nb;r>1HRCl8i8K^lbFD3AWI@LtB8@G>_v)AdVYQtOs4sKMvePNq8 zp;n7aXLYnaGur6vtj_*L_1+0+T*<49f+@$9_+Tal>f1K9;@$aMO<))w>gsduf!`Rl zlERD>@A()d@}$^;yR-{QOP>EWmDeBDj)#-v?%jjYz1(H@Ees^%_`6Vf&l{!UfQV#x z%PKh%SbkK!Xg~8fp)8B+f8IKFlS8YTKK$iX72uT5PKz615FaNh7_Bsmqe>ll+w#pC z_@bE#Y=9;S4EiN{K7Q9q2gFd37^ix<( z<=nU8%1Zv}jf#jQj}b_7j0qmOrD1ChA2+j#pLl|w*8||}(oLY}YegH^$Kt1F`_fx0 zIG>nUFU8L~PN>O3k#C2C)|K_d$>3AK^mPm&*(xThK+$8xr^pT0AXC+|s%70_oL6Qm!-Q&b zB(%+1QVIxuxhBZV`nk0_TcFgspvKXEjEhHsEpE3T`OBgaM2ZQOFJgpm`2LxT^+z@# z4WL$-ZA6-CwtM2H;U>UcVI%ADaDoMr83;(rVIPs^!!-DS59T7%(v$Nuj*F$EF;hxW zixXxTkuHc6^v{l-^!EA>;Cod4in8_ph{zhhwb(kLb>{hB?16*f`|huJzt^@+uakWL50bh1Y_`e0G57h)L9LPFUz=Lv6VTQ+X~Zm6PeJp3 zZp2zFZv3R%=Z$$WS$I};MM1rQ7R#(Yj9|V=CX!uo_y31IF#a>T)AfhVuJl5~VR6H5 z;8YBUL~hv&lp9la4z-S%7DXe~{`L473>1Y31|!B6R1UyVVAJ-qt!+9KmrUzyybNx? zelV+XU~D+md1Sn76f|Qd77e_caxvi(w$hbo@*`lgm9V9K|K&s?&jKG`qL&@}9z^$f z)Azy2mHn`OSc3a*_qHQb>-yjK*Rbe5r%g2LR}J0OaYrh6{yowM@=ZPd)#kuE*gTV} ze#pKgCc4}Y7uY@EQ*$M$js^<3x)#Wfkabn@Bdn&(jfW zbv$-t?d=+x*BzupA>y*mc$es9Ll2Y$<*pl>4*u=Wb=JAv2WAz!>)i)qx7}H>DSq-a zekhz2;P9!azkFkHzEgO(Qt$Oag4O#KA@Vr@2UB5_e3RcR`K@5u$6)EFkrYj`X1T)JaQ*oFA?OVid-x^*pPP9gW$b9@Qgg_ zbfEr3+v1R1AHVxL-qdvuN84TqIg9nPdtN0GaDl9Hn&FCq%Q=ZfpRcU_vT#zmA{zC~ z{HCnBSFDe*pO^$TW*wntmus;Q(4V$`3U%(YXWyi%z$1XJCbF{5T3H^D0cIf2;$qi} zG5+6bozSEpz2au)i&+om*h@H}zQ+=v(CItR<|kY}+LX`5BQUX_W+Z9jbEcy3)9q7t z=rH=R+vw8ArJ3X9&D>(yN8t1Q`t5P(c9V3<2P`aiQz*r)<-6%;4;0Dddax{L-)Xl=BFAS86>y-a2F9 zI%Pr4`t$cg-Q*H~fS4zh#S^17xaprZU(Fi4fS)f<56+BrzP$Y>_JL2BeB-S z-^}2GF=&v7S5sARyRS+&Kd}PK;=L2D)&=7?$MEL@T&0WpjfThQL)v!RwrW?ObCUB; zUID)2t`+`}Eb#%?N7M(!HsJYr*Ajo-=Eq@M7KEVMo2$uGuQ8K|qoUsrYuR|kK-9`K znNP4oMmNn<>`wo53KiAA!uf(RZ&9cDQA9mL-9`(q2n<-&ErJ% zvmWFHBWX3@_b;S+gG*9@9VCOk65tf|`cnh3*G2>vmymT*^#b9u=BN7G?JC`8k=?=a zl*jMB`5Mv-0>jv|V!j2DI&R_&$sH=3hjgOgsfW_l?ygjT%L(u5V2obdLr<|%PW!y1nemTf$>@<`(=iD`)H`kFlh&>7)#a%^YyH>E|+KC%p-Nmp-#iVrl3d5AnM1rBzIQ@z&>77yTy zo3aeu$yaYAirGeBLp@`a*8ac|<|^toxh||XL!IaDgu~Aiv2>|gq*LSjfJ4y)&%>4< zh6x3WBlK=_4L2q9L-{L)U$s%^ZxL#`eJ#v2lp6LSRaj! z1{|M_C`%rd(%4}#w&Aj2vfpfMDf2Zr?DaFdwo)I`(O)}!G~7QUTz{??RFC&0n9ykK5r1_PsS5j@30WgWDHL%vpWC%D#ZPgPAv;5mMjHT~ou)3ti4-40xMf+3 z45-y_?RQBLFIWr4-G29a42%Exv_3GXMMkOB=k4d3A}`9!JYdsX*+O4Ul(M=NFcyR? zKPSET&ka@jye(Ydo-?t%0gEJ6`tD%y?|(WHEJju9^LBAfU0>fJx>PU0WiM5MjMEP> zPeTC-s2PUs4&K&Tw?9$N1s}s?NA@2#7*{|x)t&=f3l=K5G((vyhHr5f*sEJntBL$J zw^1$ffI5_?U4{K<{&6#p3WU!bGcO9fntWum<{3$a?m z(UMh&uMRRt(_OoWY&RfnH%dF)i!E$Fr2nJe%^Zz(b<>;|Fg5qH#sq6%*wAAAwBZs2 z`w9~*nR5sa&fuCnt0A$I!$Q{n;P?^hSPe~VWx=O-L~(6NP;bsxc4N8JM|8+WI6g<(W=V)euSZ0=O@24# z0&bI1GUS^kmaFKk2x%a8USt&XpZUdI+j5d}D1;OIsaHJeJ4B-~d&NQx5=)_XI&dVz ztWOOShOccDT@<4I_N~2q*j3g(UznxFoGam_dlRJE@_3ZqM>O66x+{1ZU0tS3l0i?F zvG=S;Ksi9YD7VD(Kc}POrx)d?$6ckbse)`uFc2o2#1E1C+9iy5oF%#yPq7p4K%U+4 zn4qQc6&7K=Acfe7AW|b{xd36MAVtu~#6tQ;mIH>Do`RPichq8CTl!0{LfEjviSo3< zmt}>pEd>U&bs6v804-VxJ=%RJe*1Kh`L=z;^ne1i>w=K$f|O2OK1(T1*Vcd*T8R$Y zfwMUk#XaW&K9rLA(TN&I8EUd{l@UAe6YH znA5wq`W}Fps3TnbrWOFhB!R#*U}7{8z4F1G60jY-p>6}?xf{NE`nH8LX=MZwc48Xn z|B0xEY?ma$mqa0u1ddg~tCOV^>o=j_F`=iSE8j4P3kwl}M6bcZ^cz^cKvL`(kPyE! zL?=fx-w>*!ea=ZjCf5$5(+&$ADJw2>8_40N#Y%Fhtw{sQ_9G>H@*aF5xwA{gBvQmB zI^TEKrf7QOCG!uylIC z%UDgK0$z0UoP9h!T>2!3MWDo7sfLYYm(kwbguqwgsve6bx4NhEW@%RNK9NLz$LcG@ znl8k`WEmj5@sT2Bf!BQh1wHu2h<8WfG4R}zjUB2#04yNL-=*G4Fvci;3b*2}ATNJ*ET3I`@o z{7G;SDIj3EpVSXAtZWv&N}#w&FwQH+P&XfvmJrXD@1Prik(|ej<>KfP-xQ33_lcGa zOQ48IFm@<7ImR2#r38LQ7ykYYQDxjhTi!(PhnbfOX^cQ2xJY8kS!r7Q_U=}F&}8lH zLaiM9=Fx#fv|J1Q{*JtRHO46v(e}`&#NB*n`7g(y_xH=x`9zd2Z8j7J^f)(VS&XfW zV4uoUmy{E~4q>IDq0dUx8IU`idtis3S>K!G^YLID>Ln5$qX~<=2`ju7+fv}nJWGz# zP;&KFqM_3PQMoN~QQ9a*L=UhJ<;cl}5q_w~Qpq|@mXho+-QoZH-c}Kt6@k1c*;urb zfU|MaMxa{ch38^K2$Mtv(?FmJC-^*tNg{)30LJthWfRHuMWw$< z6p%#0nq(YTn-Z5?{I9AVbU{)_Lq}u`nYBRBd}lH}W@T z2GPrdqcWtybH}KqgP#SJu?2;(1r2(*Z8$FOG8E9+yp%eAiR8D5g@v(&1&wUDZ3|BF z(wF^?)*6Bzx@ktbE3m6CH=9TtJFOhstqn+1jSt9}`5|CHx*$5+lyQwI-v7Jrp6G!; z2ca0{zAWaxY&)G{uLJHyCpA%UFsy}%w}r8{1H zs2&c;9u7>1g}ufkNr)60h!iuZ9=AmuJ2)oa5!0sgLH|R>SF>e#eXBE<3)uYqY{O7o zbcbFJ`(BQCBpT42-o_p)(Nx#R;`w^o&nMW{LA)<$6msn7KOzAVFn$(Leik$c5uYiX zj7tq;U!`=;WY92+5HX8VQ&>A^ry&JLEU5!{vG<}ZcA_k3T;+-!A&UJ?zR8<>o1hB6 z?oZ~!E(o^B3$~yUWU}Gb)T@qtEDU`tOzo)lpx9B698zJr4^mAUijo8>oUu%p_#BI4 zuuW(k4^Y3}OtZ%P?m_bv{*i|GCEh~weEm&8VEt?ESJ5605pa8<22~~7;?Z{s5qAnx z(O4?ecmG<7J6VW2ShixO;E+b_nYC9 zg<*`@evDbUB&~W41g~%8-5i+D)VjnHE{~I={TGGKx9ZcX@+9_T=dzgRvfPTV9;{m} z>FL(@>rZ^BtzAOthbChUlFjlNtm{OyR`c*|HLe#_hAaw>x$R5cWL9#RR&wk=ev+)f zAvDBD7KKU{rE;+Oq0I0gr5dND7^jV3{hsS1SCPY1k!z1J7GWkbz!It*>JQIT9$@V^ zC6627h#hGNmuot~r%b%!DW7`#Lj5vmrVl9AXWG{peT4Jy>dUaeJz#37`njr3C4N#O zxt2<^mTI;$X|nRhe_|;Aaw8E;4*laF2WCK#ZpJIlN1pDA%Z+JpF0{xT2WH+uv32cP z64s0CofD%6_r>Fel&)f7+YeviC(eof>Jg`S52Aufi@Zt;8tX{&$$`2&e%gyPGlxZj zP5}v}G#aHeGyj>CHgusC5MWbE=}>=Ur5wSx)M_I+Ik0FrGtaP5u;u+4M6b|_mj*0#P&i0~zr|6Gz?5*bGUTt}){Y%ZMfs%KrG@&62>-e@kB@w>aguIM1P~D@oIp ziys3Q$&A#5mAKFS+q|9w5hAy^%6{O+`=nX_HSgh zs9G#2S}bUKE3@Wi*ts#5IMA0Gz$qDTsf75y)9`+;3~1?&r4k}qq#;KJ9{@yFH*eSB0;g&z2gG`rITtHBmg;AK5v(pj8FVE}>k+0?dJUF=p zhJjK9@;trXZnBM$d?#Z=%vU4?ZWjitVh4UvFAg5w!+tYOV>Mj~qQ_(;$41x3yZ+k{ zPI>^GYh=dy!(l88Yrq3`!Q-xblgj?WhkLwz~ zwic5Pfw01|>7NOiZw5K82RY)o7qUFRc#k)N>Qd!@*VrMso2I#$rj0PhmuN|F1evb# z^PP~qGb}>sb6k&c#2XGyixO;;4ai{*$hFg%l|#AT+GT~P2|JC6bIbzrQk8o4gOvE% z7J&QZ5S^UU;Bn+agY|xK2F!fe&|kjHKo;8jY*1Wz?gkVuYHofWM^Ek0W*<$vy)@vx zZb*C{&u?XN{(d<&0lhe_5oSeJxhlAxEscB6kt=>;CJp`1%)z^ZSMv@H5l9>-EISiSHdW8y)t3y?&PcZqSul z^*U=Q=7VkmTmd*5ssjf&T*>c*-vrrp}viHhL z`$?Y{eH+r@oISq#@YU)|vju2x9{lwvN9uDWfmDX}{A|QzmX*c}tnylp#2X4P(?TogNXj4kLGQoMdhh?Ycft$o zozT*OE&KuSb0DsAIQ=$$pJMP(G;Lvv*Lf;+`0ULyrda;W{W@L#e&`85BJT!Hy}g%odx`m%~_7oX)*lF#!N&j!z{oh#=@+T3|527rh? zZ;tz5%A{VwPzeQAa0(BA3PnmUdjWH}hC0w1&y_j>A+}Mx0(lpNL-VlaklAtIl>5z% z+5!(ZAbtP5_v3m5=~`0zmzItU{$MOq#NoLeYigD|@{y=m)Au=6lEG>qQqyzEp2yr| z0g}?g_q=~3g)L+a@IfGza3E5UzEQnK-yeI1J_Ikw0iA)P+>9aj1l#qu#YwIw|WHIQWGyZ?mA!n)I}R^HjigS(9ymO4lCm=UzKFw zdGt_zZE_9L^KN1dGEc%<`d3MVIKW=2dK}yhE!}bZ?DZjGb%`fr7@N2~m zcSh9^sbl_bIESEg@HJqk{|ihEnh%(&Y}-s-@2+_LMy<}i*|K**`^!FDwwMwIZhRnl z^A#+1FL^(*{SPr7T=M#<-jVB(i;>mc5;+z03*@~ZG(f)uk_E_?>@8r46mS=8bT&#J zo+SYkXqLP(^>I_Hl1*0^7&BgTdp)_@+ApFWL%@d278^#zy%x&}d4IclJF>M^XtRwD zXAFz)TwLdt@&sNG6Q6e;k?a#lYr^qRK>4-skWEy7z2feFxBCHvfwsJr>Eh!#Ch7M2 z09`)0-4K!?glNaKQ4luC9{wP8PCrFZk_9~B0R;XSy>+w?lS2kT z-~m2h#iW0$#TMb%z4`(ET(dOLwz>at27ELq{k#Qv48)M>c=rarm z8r#O|Ao6eMakK~m+dB#zq?qJF{zvElw?HG7$H8KI-n4ZP(O7{OsxQ1TWeCBxzobCe zGbE{44Lt7Y599-QBE)Y5dLeMY+gpdrf(D_!34ah=AA8FYxB!s=f-xCN0Mg&9AT5#t zcC-w?5i{u>xUs?5USR|{8o%5uU#FFl7yd7@gQGw0PNo8hY9$-S zUnZw)!E{`e+oKD%QkC<|(@hVHKeYoMDJG$jZjF4d!!3)0Lf|O`SRqj(0gQH5;;u%0 z#BO7wtGJX~eorWJ9b$s9L0S0MDB`Ymy~J+raW!OsACv8%L6ht49)p(<+#D{ z)gO}R5l;C5q!a|cNN`*Bmu}zcj;A|&6YZ#tLV@Z}?O)yscWrhhWKlftBewKKe=E5^XmO6S$g#zVLvod6SLWAtCf?MsBKQ}HH+5v z0F@iPbBw*#qz++UJ9xZ8`e6>m2VVUwACtLXTL!Do3A3aa+j_bd1FUxYpp1MU5!u-(+pERyI&{1xbMV&N}mS!rx zp?B{Y@9o4+;)R!SpTEIMpm(@gA5*g7#7eNTleC{dWH!xya(m=Lw61qw^o+YGM4zYy zP@KQ1GIn<f-n)f#|%vzKj0y?{)7pA8L*mW}UvAD6{L* zv(SxO1BwB&xR*~6ukEeb7AV+xq^U4i%x9&Kon1IiPGwVfE&Jk+ zK_bmWl0RVl?%wD`y=>k@utX`3W=tpmmz_%ssg3Gv4eEJ#)u<4xg&PxrcJ{We5tyUt zMO8SSu>E=Z_F(vVveKL&zlv6DDXP+`6fR;&CDd~ z4ZB1FfS=!FTSB59Tnv}EFD%Uh{T&&VG{0mK{MqsmTmdepBN~hcE8WnHDevQUMB-!t zCOU^2xV4?yIHrXh*Gzg#6&LqDi@(S@`fxU&oIYKtKN?-`&42Vx^{uACAt`+RY&sGi z-myp0foBb}t{I&(Kj+l8WJoCKjhiXfov!ZwAh`d+Ew~#*+Ocwe(Y4~^Zs$PPMPXTY zbO*EvKQ|tDJ06%W?}W|`K~9R-Xg3A?e8vJ@o!noKIoNTV4WErTfjs#l|7dm|J26#- zzxk9k%?ULvVGE173bxi*8x;=h|E6Qe3>vvIo$hFKb>`$__NLpkK*mYkpzJKX&^?wq zRF~4Jy*s6o*0&0*ab{;GJVg2PF~hA@%9p%hhAI6UhOk0cUje0W-Ob&MUjfS}L1UX! ztJ8zFveYUDGJ7rUEx%#D@c!e_s`=nlx=V|I$*Xej<1vq*d8XI4X2tows-kHJSRgNx zR`KVZyEUci4z6lsR5Mo`IjI%EdlcSP9d1(x!4;BgUAK2nBF0as{@e1Lv=CbZIcZ>_ zzMka~#b9EhqC3+RcU&kd(=>UqE@A<+8ajc~uF_;*KTs8U&#QU&R*~crdHR!tX=%U0 zaIV*1|LjGAm}$wm`t-q7exY+?_i~glZioMww9wQ6IjMWW(e;**{Pg^7T|d6D-Vc1- zuPc8nc&yDEaai%05{y_9;no&s1ox5gGCxY2=SDk;o)?a8EnK*ng$T+Y)x-k@8#|iJ zT~4eS9s0=0!VaNx`6Aq1!qN(4oY4o@bC=Ry}U?HpPo;2YUa>h@h~S| znbQ6Ya7tTLy8WItC<^yN`ttYqSgC`Fkjd}emGkmOe%6TkzYGm7ZPF}kLDSh`&GG>f z)$=+Tu2O(9)PmcNKgL=sTe_qkn$7PlwPY?r`#Z$5CeHscLi%am>YH*!i&fVfwy*=J zNY9?;{?2>o`g3}r@oJg)l}}aI+)UHFKA$hL_PK_Uozv9p!K8}T2u!hV{Ful4bdQ;y z=iROv(a7B*)lYi1^O;K3&?yWNYgOlzh|d2qnZ-$G>Bd3MslB>l#0a}l`n0~;$* zBRm{fmitBuIwZR>$&5c%;VD*65jyrR7-HGTzl49OcXx^+Zjm1oNPTef4x6`;U_Fl}P3&ymMcM^(d@4 z<%92PCp1d2DsKh~W_KIr(1f+C9Z%-ywi6MG6q8Zrxs8-z$7ZjxGlSIp4+_=a78L83 zTV;HSFKlj zr7?!@RjH0v*J|F&I?gKXJl!^W8uWZ$-a3x@d6V0{^1Vp~Q=fk>Hm>7OYuoTJPu&ho zEXAwkxEj=bmD1YDW{IFK=8f92tT`3*+OB2^*e>QQ%w6OP)8&0a<~kAzzKzdUgU9=e z>go7jNrzeOyI?PL!HbczUHE8#u0ZzC?2xxrocG;V^`|0Mn!-5SM^pue-#_OQe*Y=# zXD*|*xd!D3*PrTD&r-;nh++S6aAfy*V5&oI?{C5wi_ldwU)v!cd~P!eR2gR3Sk_8h z%d6YNc`Qj@;PFL}(1S1GdkZt}+qtQGD{$!Be;N?>W@l!f)xVB+&w z;>G<7JBUE_GBIcDV(tTo$M651)0~;lp~n;{{C!Q3b&qe^CsbVoSHRr5#fUHx>>fyM z1;AGuxcZ!toWEB$dk)b~+nPSnYoKI4W=wAPIdbIbwf?U8Dq4ZwcFDW`H|SBTT_3!H zGxuo8A=wB}D7LKqs{`RVK0*R$2-dRPM$}=>5jv=dmO6j0in+)|xXkUl5hUxB1;t4I z38n}M{vv)oOnsIWZN9!fO4v|XppicR{qTr6I-tRTOm<>gMJ`0@H|_5Wvd9J$i58TB zedoD2d+(SmmbkI=U$SY-WoS#eAxpV>!zi}f=BE^kxkp%>?!XwDD0&!AItouZ+!ZmV zE;VtdpMm#EfU4j}gjLmh4f~2FeQY!M>9I^*i^3MsgJ6oIUGPft1) z%_Cz2lyzL|*iJklsML5faFWA9eGuXxBDK)z5F?a_0L;^SL%&34No!4@5r}@gMW5XL z%zFP^zbX4YaiT3uavS2^j6HO4|1z~!mH0a1Z`NybkLHsX;*+Nb zkr1P^?h3nl+u|>w7e-+a<^ZYcU()a+;0s@H(oup6n8%e22`!VaRy6@B3q!Jc;|A7> zQ)071FXLmeKEy=1-$WTeCyOQ@;O-66NJr5~hg)VE1a(flt``!*fg{0#v%gatBB8hc z&2wK+VV~2SP~vNY#HXSgSYPk6c%*7FklmVlx}WNyx`{dED$VoOC+dzVLT_eFKMd~g1c|r zQL&mpF)e{UBhi`y*bvigEzJUv=f_jeggfLK^sC6)nxMbug}moM^%@q4<{nZFhJS%^ zM|7hMFTs_lz{N&*Fpk#8eJ^Ep-qea|qD6jTQ|^U3d^7A1`#u3JYKWz5IG1m1s8;u{ z)@Y-SOb**PM%+B816QI47dzz1c=1@Y$PsPV_acmrWi-fL+nKP!HI30mDa1j!-$5CW z60SAK?+4Ktp3H45G&1?66cAPe7#(m%XQWtvLvT?RcTx8H2T;lV7lhs<0lU)#6M>92 zm=`jb2eqv=+7RLHv}JgbgEo>EGLomKl!PtIwyKO=#7!9>fgUK)O2=f3e+m!3DBIiH z=ws*0iYvi^yWiIuf{ncz93&)oD=8SQeNhs#n}@KM2NkPl_V9x>VGtIb8a8(v}RR(l~Ye|9x#)2C` zp;}Qm9V9epU-Lu0=Ii~!v$Z}eKGLz)RIV*KAr5KEH%?u?SMS38A9THURFgsX@2dz( z1SO&ODnUTOPz32Mln`1V^deP2q$nMvm(W27(t8WNccg|Uh=3r7^xiv2KY4%WoV(t; z?z;bEt(i=oB+ty=&y@Z7?)toKVix}J=lp}g$$H7=EI!%pIX!1|82<1|{y}WIp1Aoi zMF_LCJjh1ANuA#eM71AD<%X?6!5Uug<4>=aRU5&VlBLvO{j{rQlck@*KjTQd<(rsa z&+%)N9VU9er4pGRHkfy+3EfhB`9uR~o{KWi#q+nrjfuNw7EzCTN&z~JVJo!I4O$A6 z5&!oT6SZDWRtIv3LjsX!4K2gi!t6s*2XYpNga{9|#Hwn%N|;?2rqPpZQz-mED0~E` zQYv9U*@QO-DD3am!0*%W7E%2K1)#oZ^nBCs{`bTjm((_ml~Do2q|kKl4R?a#Dkxp& z2O!6p9G6dmsG#QKV|~GYvIr1WbIXsJmZLp|?J(V_4`kB?sA-L!X$|<#rv@8!ko`!pO8ZZPR6Km26*Q z@KFf){ZR6F;T|1GQj8p994m928EM#wYGJPm8t2uPJ^o-z1G4RK8HZzWn5XbLm*4*{p`HwHmIlmG-(8zEiWOT`;?&7yM6vb#i5 zcF8i9kmG5|?R#;S4ScadGNY72u&bJBhg$Y2y1mczQY}o~2P`Pu( z9hPAW65J8zQoC0&7aawG@3Dkm`TW&adE+>+ah%mrcuG3RxkQd_3{DGjkZ+xD7YGOy z@DCLr6Vp<&wdn}BW>&do-d%bm$W+OUr$~mU*aDQ=Ce{VsC9t(aRYbE2aRnnPe9Hq{zqRzY*;7`AaxIsO&5_3 zm2tm8TQGBh;rs1yV;~|AfXLH}Wxa~H7RuNM3$>Y^dR@$)t?#pBTkVl!9kcMIhZgfNtJJM-FpQBdzJMkg@3#)@T8iXaOdtUkj00$Lx`DYvz&o*vChe z4+rE^`sGv9UfY)QNwYO7k~J!}bU=r3xir|u@Y%+As7FP*Y4Jfecpw`p6pCRIemP$L zLGT;>w66Kj`naLfMPcG*9tD*)op^qdb_P_lVb)UGg+~07&Q-|eFC|-}sU5~<- z)M~aFKQm-|3Xo0UjK^bO{JPRpT$JB%0UW!J=bYHHKMMCz6EDltc!=jMaNoO@Xkpwt z!JnQ{g`RPD>7bNii5DqXMPrA06!(9fe0n|A7q0*$5sv2^=~J#K&l= z^;T*;B>gA=`X~?|tEu)8gW8xU49QJ?ML)Oj8f3`~Q6PgT@Q1w_ekv&Px@*}Q%or;4 z>uioBBU0zGcA6k*SilgK`pt9AQt#&?$XcOAO03}PVe}B<{wO;LuE1|9QYvk<>+{_S%w*`(w1E2VFuk*N6Bxg@ zee>WDp`a%JmtlBi&(ZnmMhK`s7to(;B$a{e8d=`eCwooj)&5Sp=ghk6%$lnH9R*R` z9hYud+D6#Cy?j~>I4#C?(L2bTmjZahy67c9Hs7sBKM|g>t8>_&)Ay_(#)|Lr79mem zAh0(V(3@-YDr0S#Id9)OXMaQLgkfKp@QqlCu$TxiG&Cc1Z>b{d*kPKGdfc`$!_Zi@ zJZ>pBY$?}>JafWQWH`H80O2X1;VBS5rl}@RoYIRB_3LL;>1Q04J3GMVfjkgutEuT! z_bv;@h0i-P4(A;o;8*BVd1+NM8!M0*EAStRh*iZYv+vsH$Cc%VmE|VerF#Y_<02*G z?HGnFwqFTlmK7Lj1OQIIsvE(ABigEqwTquO%jaxcgN;H9t*dU!GN6k3;B_cnjY{j3 zQupm-d=dTq_Az)=ETc*+<8aW)f#4HoS1J7%uzrlym?zE`H+gdgomvN+T2qxOyf722 zc0Zr%ZaL4m-eb6sNcku62YY@vncGl9SaABjxa5BuiP1)87T4d!05l-*ZFNsg;B#h` z=gh-$_Ybw`H1A(58F-GEfz>e==uj6Z|e5{Vhs=DL-5T-!x2sO!-&Qu>6ZS@QXMrvoQDZPZE3Q z+-|%=4>o=Ppn1v9bFBI(jb0yy;7*KsYPD(<&0jF-o-741kHPVAPl0?Y5fs%}5Y-q@ ztNJmO2q9n$A27zF_)~<}83@b+0P~Fem^E$>%3w^5KeKryPs;N~#lH9o#Q6%uUymoR z2jjQ5ONYmR!(*%v-dl=|t>;v{zmE=iL)%cgM=XUX?9+C!r?M$A;1t|_^xoRws3Rp@ z9>rfC<)I#-EycfliML}-_4ZwvHr{8-PM2Dua<&>$`QMeK_4fw8!?JkU8l$bcTz*B{ zMmv{-@^S%rxyj7wo_rGF`L9raAwhp3@D)|@C&Bey%%A#zpZXe;M5AUN2P!hiDl#m~ zY?Y2`Q|E0`=WHfP38XibCH?Yd@_!h z#h4u=r~*u)db@<%x_Zv9xC-(OAtx(NX#AKhJ)B z)o8K?qRa_V=7v)AK3}oZP3Bb}9w&#Dkswn2r08X(=)3Wko?V)z#QXv?lk@>e`d)bm zyF6=sPiD1iYZsd0F@H?LScNxcPdsW*9Pg^BK!YKWnu=nXigN0-PieTmu{B3Y~=P5I14hCg^c>!oJxr3OO^Lj07Klw~(3<<}xu)*?*#OsMg`ZckW|%nX65 zrZ(D856qM3lW5J9S7jeTd#JjfeR~yB*RNGga(&G6XVsv%I$7+O-X0U>=b~F1qW#+% zH~y6CRo{j|-w2cVCjsWf{^pTH^vi|CH+FE!FJG9c28tti9L;?7E&nJieU4&`l@jPB z$kB5#>E(5L&S-@VTX+Z%3?UTkg%$zW+z5}_q@|)*q@ot_Y`2topGd|c%uxvQq=XdL z8@GI=y11|BRqjQjHKb*|uWSSb1!4z7qtWVuAz{U)k*|rTDg)s3=zrYS;{oe!macnAqOfb<5(fQVk=9=c>K7Li0O%i`rw zIole6JI`9*>IPGtPfe{3JLXucQ}pNj#*DfPkDL_{t(g_EsTDC9fBazy3(@nPiH)2Y zrWl3gsO!w`QP-2qyK`58oA1Bl9dcRctFAwaZn=(p`t)-*JpJ((TjDrd;&?4K^>Im) zIp&gHU`g*QJwf8>{Af^^UCsO(P|LxJta_r5&0AQ*|f)JrcbY zA@Q~UbuY9Ygcq_O?z8VIvi+av+NAy5iaX?i*?%H=j;#Jf_bN}W@MbLgt7g0s=?xyW zu@j>_Pyeb-eGkaf*Xa1Bv+lB?@G&(LP@wNs_M6ryC8DG8LOt0uG^3sUQQ)-avjb|@ z18T5;VMaXy1gT_Rh&IJ@b0=Gh0`L(oLhu(MJcKd2wipr_a10AL2GFw-_c)j_O;HRC z414D)oJz2Q0FAnlx%V}8h+fa>9zXbla68R6`?s6pedWn2Uc-{x2h8nzMI&xoIBUN* z6ar_Ig)??r9Q}%iOeH^gBOW3w9x||r;i%P;J8=n`fWS+iIv9Qc!an=O#GK!Kdq#CZaAOL6;Et zFB0DdiirJ>j+1Q+PbFL=&mN916|8tx8);9De@Fg%iTajCE!~SH)W4%_6&34rd)7;Y z&&u}#I7-#X261Hm5@#A0$>^nEW++|Dww%L2Fdc_YUmcy>i!ftDOb8mOhs>j>BY+qE zjr|J44n1Bx{JSO1ZDD#&1j}s}kU5sdFHW6kpGR~c06c^c-V1$uIrf^hI*GU{i8vnH zGlL@f%7bOhyZZT%M;|z2K{#WPG*uw4_JGiu58s*(E=A@*+Di2M312EPB$YUR8aKn< zgc2hY^5QM4%t= znJn;GcM!AF%Gn|ZEj}F$9vuyMswlbfved$ zYsjATj8eT99b;bZ2QK$FQY)13ONkD*J!9MG2X6E?N-3DpaK8tx=?1E^$*Qw;htN7b zI~SQ^UhD@h_E!QFT<{aasM&I!gXJ`aGW88gQ}PDJ;Lf zl9L^ToVAc@>n$1EJx=gF#10-a!Fbm`u1L=b7Dns@un~}n_v1c-TYw7WB`M1;35n&= z=#JoOaN)`7`AI~`FWg(cm0@vd5%dZkoF z6kpqmD@5icQ7bty%a0ut8@T)JHTr%!^~il;P?{3z(AGlRmRqZP1#Re$Vkg(tII*5~ zLCpSqNovdaG)TYtsu+QF<=M+H&Me#hOUYKi35IL#bP1G+J`k3t)r1QaBq^CMwW;um z5r=nR6u@+w0O~6b`ocQcwt0U~GB#nXj|Z4D1aXJ4m7=lW5cV{Jmn8h=@UW zwAc(>HmH!jWYs5o-+84cD?FK)tAgfm7##tEP>`8%S+N#8!=<IC1jyO-7n-7k?`2!Bmf3sHXHug zC$ptNyu(o(?()PbRF8cW3~%Q;_*^TP-zM zSPCBrG2lC3@#n&m7s}+nJDOS8V|)IF8x57M%{v1lDafd~t^BKKy~m|hk?^$QBrK-c zr1eO8{jydl**MZ+uhuE+5pZ+-;Zc#t-roaV`)*4z~B^=qSn@FhjfD8eN#F&yvuu3N!G{pe%t{1XQgd$He!ht`UA!VZDvf;&2|q@59;q3xCzIDUgO9B)i%S z%%$x3X|LS}7(_uv$nEN1*@fEkEtX0|!e5k3VXO7A4KWYgV{WJr^ZpyfNk5TQL`JI^ ze32NwX6}?4EvI^c6nQ@+{9Z{Cpc1d~`3z|r-MQC3HUjWjc(lljD#{i;3=(m&)A^mc zs#lsqx9_E$r-GGB4L7RAAZy5PQ%=pqn%e-b-QoW*Ne7x$M3NL*!}q8XP?_NXNecc$ z&26tpXt)-f95V%t({J+sXDsT+A$_N^Q8*RsqrZxeyrCQ%(~wWogbU+pK9Rz>D1hmF zohfGE(lGvn^OS^6tfK%UG4IakbKB3Cm*Ym()Qo2N5swyCAf2gZ;8x54r}PMXqaZ2F zehD6S7cJ+g3128k0@xR`)wpxyaIo8HUKdRjP*N&x7n)HM-weBWj z0AJHc-GZb_IE3oE*7B%2?Ho%YC-{Zt5S9;;Ay~ET5J&T~d+1)ulO_-7|M;pTWxL|+ zFTY5c^EknZnmYjkf(`)S5II>Ar>lSRqzwRwi%_@Vbd^q=O3V_I3Kn{R(hdidk@Jtj z22{1#FuJycvDxHgvYb|Y30niY$pH)d@+ggPKrZIp2ho0 zQ8GNthn#;?fkED}(K+mF8Mi~B<9pmFElZDp3+E+;$u3#@2?;qW^9#r8ljl9d$|;2> zCp_!0FFW5Orh!2q31(nf&Qb3}s=Xgt&0BkRK;}<0{>qU1U(LXd82^=?P6lAT@DCXa zO?ZF7RIuISVsk%keta=3zI(WlUm-J>;sb?e5?DF!aHF0=^p$)K`YmOy|U3Gyi7AZ2`qepOd)I)&|$7=Q=6!!N_H^{z^FqsvdzF0TsRjw z5U|wdXPoAz4k05%s%a;q0C?G?crxs8W^xB%ri+jK(pFkBLq$OYk+!b-0^vA)po~vz zDiIAg@K^#gkci8)I%`Ym<==R~SO#g1I6KcD&1EV&z?WoFR(^IKzHwUK*Q7x_?D$47 z!=LPoYds;&-N)lwu7Q{Lv|q%R;aMQOKR8Pl9(YZu#SSI_(VV7;WRb&eRRnbgN52^i zg-v*+2X}c^dge=bsSw~@5cCfYh#%6*}cpVW;^?lpUm9W1FI9Bpqw+Co-D&-=r6 z#H4)#w?)Y5aPfNmW;MS#+l&2|W|y4!fJ@C+INCPi_tzPa>brX?qr0SZxZ9MylJZCF z2$}My^}XT%&v<1U8cL#ShzS}%f8i9p(Q<7)`38mq#nC`duEm_fsjmPZf)s#EA!&dr zJA4Bd1$sxIU)t#;TRY=d^{jp}>k+styyA7k2ZNC{+FdXz z0!B!MZgkYxcBnOoLM)s$19I|7Wt7=@YJYv5pWv6C(~@b$^k7pjyP7@0s8krC5c-vq z<_^OfPZPTV0{tC3O_|V<;C{nmX88wlg|H-NuIPX>S?Tl6Ig{_G4~t5*o<>DG7?b-( z0gc5$jz|EN4*D=cf<6R){cL7qo1!g1VILcf&!P>GE+qx`z;c}Y90AE%00@Q@93Rfs zD)3h3L`!A^6BL#Kty9RUaL8qO*wx)#X}tojN~%yDmoFX!}_)u zl7PvsA}wYjg9h0Zfp5{k4lYx?(r2C_Q_rX&z;G_poFd__2qA#{{hl~j5dBRPa2FhXK6hstmMqKekz7Tr3b+O^^1-UO~cE|zJ^mN+T5zq)l0S1gC24AHL!ka*PVpJ(y~`Z}W>`)gq+8}zGWZSNaA z`URjN8raBX8c;&p7X?<#grOwRuQoMLW0E)2GoUcB){XKoVf3poO_GS06eQ__owxxl zR(xCZuySd`D~u7>R)a+Og;6021tSEZ8-W_bnQp+_1mHENQ)ec%%m9f<|0ft~J)zNE zmAL8gwu3{X0@Iaw>ceJ@&m1J(p*pc=9+P*>;`_`4oGf27yV&1M2>UCi^LC|>1_-jl zb;;I^*%Z4ty1tSI@Uk2JBbD881Y{~|!B9mBj$tt3>9%-qTvh^5nsbpfftvM-U4>Pe zlO+Jr_3XKzdpas9FX)vLfGoa;_GgPye0K?JWEJOuWx-Wv$?sXFmX;CUr)bHr$owE` zWk7sYk4F59A(`7e@uiusf2q9Mo#($Ds#VJhXv$a@1ch0B*|v-^<*y4*jWDdh#Tu^; zV_g?3uaR>5$}b9n2F$)-gm1LUhNU)x`ai%>GYPMnREoQKOU`hMeRoZngMuJ{!xuuv zg%gxvVNj!LSj6@UXR82@!vFPb`s_9>x4SX>%~GN8e}z54zDMN$>`58zM*I4)vO!uh zgqWV-*vriLh*0O^pbf3C2-QYOAX^^#U zSVRbx?VhfhJAZSt?b05Vk`5rQoD{rSJkVmYqQ%~WfhDCC*UjIsrgE{oMs`_fivO79 z{a8%8q{R+bozUO+0jZ&Z8C<4Im`S(uh-bw?idukpxrv2Y-s(J3GbMI-;x8)S^9btV zpjTP|GFghfz4Lz-=NoxfYX@jws7Ls9al$_&XS~TN`}|!2ssskmLGw8w7qz@UIlF)S zcYQG7bkVl-eed=#JA8S{*<_#bj$Z0rl*s$Bi*xt&bXH1k_7~cNJ!YH|-J|=q{7@}z zSW)S(;K9di4R-(mXpS~auIv}|*HOHYJFQY&8Jg69+ocM(z&NS(i2=_ez~v~j0_sDzoLhK7jujoV=dm!eQr9avgEe$Of_*|3}z zPurEbXVoqIq%2{>fHVFZ_KT-+h6sgn)LulR9ws7nmBXcy?us0v!>Lh$pgJ8?bDk%< zA`jE%1eGDEMhDf*Dy}ZcH<6s9B?xd^aY!TL;%6Pmo6EARP`F#~s{R%`^V=yzd% zCM--6&3BTzb*AgDyXtA%Z}QY9P7rzwG4iwo_F7uX+;FM=fhE7 zK^+%Nn~V37-*Q4a5n!_a42-F#VQ)D>xDx0;1EW!vPZwrUnV|HLUktXZGoFsOv{Xg& zbAW7@+j8t%?Ly;H0Tr}+RDjEw2n&IM_R*ws+?Gpi_{9&W9-_`+fPFOIp)qcocjn%v zG3RI-0z6w%_5GEFOMcH5EV;B?kTN7saUK@-4b8V{5H%5;hnj)`s?g0qgW>ix7Bz16 z4DGEYo3i6o;wC*#C){5aiinb`gS@@N!8aKJDXco&?B&{nE4InB6&Hw#5c;wNVSUc{ zT%mGXk8Yob0EkzU^OEKzIuST%-9qO-P4tK8uXCBXWH6 z;g?hjt{69kNgY^eMZ&=4*q?|={l9n}qVavM(tXPGl?em)iBPt~b&p-YNn7i}epV&` zo_GcYfuO&^ShpC@JTl3qz<^ig1UGg=FTpnE#7zql*k zY#m&xPph$ef#Ncr7S4OK8FBTW!os%Ed`-r%--XXkD!`vHl{mYcL4RyAA~xM_dDl{` zjeF_n84t8i_A}um`-glNsz@L=udH5?061N;Gre=ae`w6Pi0hQOlIS6`Zd5ed-53+G zK5_l>9%(&-)h_=<*W``NK+GLsI}G51u3peN|BkdA_;U}IBF25nOV|Me_@SHc>26!f z=k+(h!W?iGr5%H3q8Mj@CT-xd{8-}N8nLC#=C)u~H3}dNuaEiU(O1>gA>ED=U;gG}q|BBw88Qme7ou%S!t~fWot8{a|9>1lt$syt^ zN0=Gr^Yo&n1g|*vDw)L#ZH1LETHHR`1=w{uX}glxSTrdM&h=FXUB09)x^)+$by&Hh zkFL*cN>5R9_$5koULGS>mf)_Bi6~snwzzi{1T0Hn#@TBQJ5bjy;U_LJ9=FS3wXi%! zQj9K587li**}60#qIc=nw~rSzOM?EliCMVT#i!Ke_r$p&RR}7w5)Yk&rHzBAcHaKj zFf@pa`xB(fN9`N4EVH(}ROt{wkA4iVFkOtj%$q=BkS+~NB_9>uwnn;iNK#4UE1hJb zPjwRSVp)c`*piTI0eWQvG#35EgD4~#qKBk^_jY%zvC1r-I9ZzpQpQI$zU2$AQVQ`d zjr^yZ91(N6N|kDXY8@o>K$lT?tm@c(VQJ)Ib40G;>3G=lm%e_J?$hNNf357)s~A#Q z9QhWROr*VvQR3NCJ#^oW(?PRjaj{7udkM54yMOhGRQd3=Eb)s58~wOKqR-4fRsCms zr)WzhxWqjaYFaW8hVX_|7 zQ%(cL=P9&_6V21HdlF+9MqH&!V_L-rrc};xK@Iym-3Qh1Q9-_Ix^M1DMQY1amkkAv z9zDt`?a&!E&Z^IStZu1xoKc}cBh$zi;AqK1_?v6B3jzEpK;qh{0M?O*mq&hI@g&AG z3u!EmbTp!&`aR)3=nxWA9!Y3K6aRBU>sCADd3mI|Aq^R}u6L)57+^>PYUBg^msI^N zBla+$IjrZSaws{(6)^fV8g+bB$Ox8%m;PTKhxo)e>L(KwvMbJU^}md*t`lr+27xUt z6?+F!k!Z+(Zu-B)k3JYO*$$+7Dl515OB6vrBKwgv z@rKyUtm(j%wjuYN%Ker9kS&>aGN&8QkR;+v_bd3Q4q)y#7YE+rC(KA|9}dK6x`}g{ z+|R)z{4(%M{AN8oLrb0xbj502#s_!y=z>@n8ieNhq=6A*pW`CdH*L(5VRPnU+t6AI zPz3}hKJ@QT7({WRo01U2kNo>lgD4QXiCQB-mpkTu-chzMJGs`Hj5J`S<{px+5?1zV zER8ScjU_xqlf&m9qbBVgOB^Q~t7h*rzKOV3*4Lqi`H_tZZ=rcJkh?&&zB)3;XO_BT8>KayFQhOD|r z!{*{FC)T*x0+^Nn66Q1yh;P*33c`%ArgHzvwBNZER=X3C@V@afa`*)H<; z%4NUl1V9QWe;38SgRdFziZZzVjd$;gNo&DgU-$h``ELqGjdVN~Kx7 zY+(}f6(`%7=9-zL;A=dp+|B1A`%lWfU1a7}or{K`p_gN%a3C`pB*pdVex8`n+)yRl z^WD2;2CwPK|9}iX%7*^7qEy&J@bNPS4Hq!?3XW#qx`)_Q)5UN8J9F0GVJoqytqXfo z7`$XA8g}@gs)UcKh08D95FYuG+9xQS zkW$8*>4$)`zfq1AkqgE&WP9uyltSrn=SXGL4>V-ckbX3u%A|j35QWA0B*S5}>O0iJ zAYmsO0y0b++gu9CX3y{>2BNCa5E=uQ(gdTdd04(2m0D$KB;fnfpD}NXca-W}uRX2s zEG^;ciBq>o-f{j1y!Cedql2hiG~|u`lW!5s&$-#uwR;IHA&Ckun#=sf8}xuEG-OoQ zLBIau$XJ{s)5CJN5vzM7v($63*&)}uw97b{9!_h~RAKn2 zQo{rF>*J8VDR*;MdvCSywZ=QGg{O;vymSW3KKrF!-3$} za5+Q7dO5#F|N6_i!^Jj*>-1VeBXb!V-!w>`)BoN)N9 z0Ao}^`MlT0di(f@n@M_<>h3H;Yo}-5>TYQG{CwW%ybwM7@70EHEz#=8WR&Vv_hyZx zFMXN{Z)WQzuxFK z>iPetsP&5ei=rNuXL)NQ%k%%Cs6F;V3vOy0*k;GJl(@Ze4B~8k-@jiu%Jlhaz*=q6 zqH-3ewQ2d_oL3_pB%m0k=;`j3x~Le%VJq9-SkEjzP|fjrtk;GB}aOuh=h#C@qQgj zNFFZWdoylNiRKG2Mnl6j)1^(TZz5V8movL9otw}9+?aCwX=@G-&s04^>Q5H#_B;D& z>bZc4f-iN&0qo#13QfQ%6)uw4?t$_5TRG3K{>Gz-#-C z3dNI<|2ID^gyW}$=aGdiQes7Ag?wSL4+O7Ty6Crp4w>ZwkuRt>f+YS&;We)H6* zU%bil(!TMNO(w5Jbyrh~h)Xcuo3dD!lf66V3a>&5ylzOTNKi1RHAvq>SRaJxB(McO zF+Ef@7dMyqh|LV{%28u2B=6-e1G=tt-pD>OHij`;ph~yo5o* z+XzBD9zVaB$O~{>co@e^5nJ(sk*+U3#T#xECqxhT=4#_-78fqpihVyg;0IJWSDj_T z?u^iP!!eLXaPgTN#T8Mnu-q3h$_ve?_L81Vmcj!(hV4sC>77Q&WmQq8?d1dXNyJRm zTWQIO<40WUg$sRFsFwtimTCMXv-Wn8HT35z?Yxm*m0OwNw?=|@)s63z8BgC%NK)Li z*xshbcvj|QKE5DNpffif!1>;|pNRmierc{!LfjL&_g}uI07Q z)#mV5!mLdU{k^7mwY}C4k7xmNJBz%XyQU5#$q8`NX=_oCVzGK?8Yeo@*aG{T8UwBj zO6Qs~BG`!gYe&M0VrkO=`lLJWf!C9YS~&y zjS;RqN#~k33f?zU;M`vbaoN*a+bjM^^<9?-iAh>`UsO4Z{JqCkAhy$S>B{BchHUum zFgd4!`XKagIGYf%=_XRSF~PD4QDQn{S+s$i#!)}>cXQm(cG;oIcdJg zf6NnRn{SjqHl#Bgs!+@BTh+s5%@trZA=M%~&GnH2_J}7OmY_#5=#qCF%xgLUp|4ZymL@fjaL)+EwF%82l437RG3{eexAYg3B2#l7}#%Q-c0M)E>AV^T4e8>F4|UBKt-v zmlvF>ML%SuKjc3p0d#5T6l1?*p3B-}xVn?Cb15GzI(L zPuxLZ%S+z6P52~nKp(kQd~mGT9c-)j5Xcu9Lk7D4Zyjk<@yVltww)&<$O_E-pyN|Nd{PrBeg3WKL*V)cos5RzlU70eM0?0V-tJ(0JrjR! zPSk{|)*ktq;W}MieP?PNx5MPqPaUbyGMtN=JC}5GkZ+1D3HZ%g=72rN#lRLP)eC(C z%2zQQSm9(gU6STrcs1_lzDnBg8{g7WE$MdF7wjo{?W;jM(dJ2>mA@R7DY@KHvo zrbqSDni{ny1Hohd9NStMrQLSh-PFH>$2%*1O}~16D}NyOlfltAebB=tsSH4!qK=AZ$C6y>#VNx$(X)npd=cJFMf zIQ@2?(0>li_|1CkU8+j!@D~SihW^cwS)m-6os84sV%BD8DB5YniZStp@RcI9UcNxs zYoI?ycm$PZ`?)`Pi*||_j5LVR>&n;HHU67(xul(>AbvP4RaYs{2(9ZZd!^<@9+6nE zb?dbooQ2ev{@X>mL^%O$NYW`C=-zG2C&0ceeQvLz!Z_tEbxvU&xLj^6ngyaY5DJjeGr)or&yF7zvl5f34Oh8%$g&x?NBuWxA$ zcnAR+asUk;IF7;;kzISZZ*p_^+=T6Tu19Epwfy&s1IqOd=$cJLevfw5LPYRON;(E6 ze`rF)3sy=4S_rd_97tDAoJ&uRMPCkNAV&}o(ZBx|1k6DJbMRPXnBP(xxj7UpM?nkg zrySVt-mFr3b zZX{27PA<+YhtNF7kUk!V6ONNe{#Isy;K9j3@B|`L)`ZGGdl9WlBi8gb9N~@}gIG~e zIF8jYq8wFDATn9PsTBX5PI^*C^jTGM#w$Bs&ZRygY%01KAB^eRm?MnNF?1$Z3*KE< ztw!(P5R7S(FKQ-4WRglnEoFV4H!m~i3lyIR6VczkL1nJXrv6pikOfrX?kdHI2d0ax zC4Xf(+&EGA#UOzhDE#FU)!4cm!ulM%QSJZ1`g+439Ao2Ry}fvUINiz^_GA^?uH_55wjT-WDad&=EikVVZ!+<#^DW$0A2Dp-2@^m zYk~n6v!{qm<#Vb)?5DRMf|i!Pe5F6)qviZq9AUdufjbw?FbYwpf@nZLQnz8$flga?d-aL)4p))BRJv1{ zI-^)|+Z=W3n;15(>$a|^Vm_KLYduQ0b7J^+756aq7KzZVS&C$|4_rt3pQ;wq!bxtA z1I}L(SjgYj;pfdX(U402NH!PpHg?IgWG|v5#25Mgf8=%k8Enq*Nxak^8cbE#Y|GwiVx@vKEI=47OwrS^R zU@p`MwMzLnjNee@+L7#jJHFT1Iajhr;t+EOPJe3OzWcE7!Ng%InSI|LE?2|W!NtgI zZrk0d>z%b4txwgJmyj?g(dED4#|_f%59U7C_XHhx?Gb!?crG#C|K4hfp-1-5E9G!+ zugdf7)V6gFFWzPjU#}XDbVmE%hu{y>kL#}2P%~w%VYBhv?CEs&Y{xEk>4psLZx&r8 zNQ5~m&T$dqB*vbZ;OVw8y0j!wk&EG{4evbrpTE63y;XAC|Gnv7r(#_P|#}TT~!h2(dno<0w zMAU$Qw0kVQ&L^SKS?{ZB3yi;+)tR(y_vChy-KjrS37K%KJ4$7-uekE#KUv%O`Au;3 zevGpjvqV-7LPy0Wkc?la*Fl2$brww1D%<8JZPZKkO>J+`alk~8R^(tQlp}{+Fi6&*q)7Wl@5k7soKB#ysSm}t~(oVK4$+ohR zW@^{-f9^M^pNuSHQO#?qFlJ=j&AdL*7ZC*l)elQb&RkSD5M$pR5^-O`rLP0}z?S&( zJm+djotE1kvoMKtXrb)ZE?yB7()CadZWwAd*!(+%5caY-76~&m<(XLJ`~FdOcqj+g zKX{|}(7SvkuB*3eJ?oaV+@6@MuX-bNKPJ-twZqI0c_#Mwl-*?i`HUAbNm2u4|#x73;1jz-M03S z+YRIRRj+~Wl0l%O^=%t3`iigQ^XjedzDju?uh;B)%cBwO0E6y7WtaA$>Ul>{M-I&A z(*uZ>otNU|I+YB)u%lV+_zW0jwDM9$08S{ z&V&~`9+zEP5tghL8Aek*QFO@t(BE{mHL+-d4IL)#@+*D(fX|p3xJ64_i?hCiJH6FA z{*1ed5oSHlifPFr!vaV7KMU70=wkhniWc)N>1S; z4;gI&#~pN3+lN10W}gvSFAK3P?9ZUv9r)=;-G}=3X5urwg&w<+Fs{9Zm*0*$!mSU?!!utu0IzZou{9sos^L} zJT=*syfZledeg1b!g&6Q!SOnM@DSIuwjK7e{Y?O2mL~KbuIaxrK8{eyeFo=qpU=xR z9?bX~S}aoQPET*#oR3l&Q1?ZU#6R`ah~KJo%pEF~51S?*JWppJ#1laRe|n_hpcPf0 zng<$Eh2eN2+aC7`KeCMbOJ!*i;HCF9-H^CbjZ6P~I_fs5C3w&)M6O;&ATdJ!^H^~e z&!G$dL2eh`7Jc=UPZ_VOFpN#^pT^~N zLHCV0!e7Q-tXWG?rB-y_$ZY)^9$+1G_;F-jPo<4WoE)fF+LVfs484YM`qB@~l6^cX zS%+s;WIni;jNhU9g5U?kz&pIQYx>*1ygR37ZX-Q$`|vE^%)q5yXPpIIN{9tLiE?|bu6^&Q@R?@jE*D{%k8xduG`T_bQL zO<42lRX@vI_7WaRe5lvCSmP`0b$lI_@A(A`7*u#AD*QXNG@IE0kGt*KPLz+7LYfpE z@#UXxeVZbS_orI7R<{H17#=0MnE`4?kdH>wzlis910{WILj}D>k*}f#u{PIBrJory zZ{7c$Wi=55urOsaqaz5K)(8+$Qc3Lo(##N6G!f|q_GQ?5XvMD28+%(N<{$(p7}4}9 z;k_{_PCj3cw7)}Ep9mco5AdXl38*`14 zzKG~( zh=}=D#CNpa>BZDH5AMGSSt@=~X5_7ZZ-kU6)qkh#XtaGL8DM~v>o@M+&djv@^(^dO zR18E78xY>7y`BcGr@CC?n}$Bv7lIa-{f6Fuw=)p=&3>ftDt}rL%ig5n!#!D*-lnxp zUDC*1{KV-hn4jEUGv33e?!(a1SD(>_d(W}yts9WLJ!!UHX1KK=`D-DKP+_myO>thH zunbA1$xl2p#dMX*%`b+|-Cufqv?WTydGE?_LMbfr)9i;YkI%estncaBE@JugeB-h0 z#MVywc@ExsJ7CJ`0siIto%}FpH`g3(&Yq{?NIw$Tbo-8dr5S-lQjpyE2;K59Vo+T|1b$m6N7<1`cz5G}V@=h>llSD*QS(saOW!6kMv zX!0&nP}$o!GEk^@Ta;urLfQs0v-#rHt{bV~eE%mFPs4vW)nYYpPOP@oI7)Ev>q@bU zzIcm6O5;X(Vp@BKLXQ&#&K84Sfd4{9eCUlaPDatgX{9o zk9$&%b4k(ru9Ne?jW!rxH)((76E1|@7h4H0@92qSP`Nvss*lHg6Zt_=|=4I6Wj2siAH`xgI7ToKI-yVYc4O~5Uq0r?qZaw^`Nwo8<9z$tI5G}Q~l%H$2I3Q zxvYn2SI4v^4>tjZr(Bg(s>J728?NIw!*#)T4xSP0@l7(Lsi(WlA_rS>ax5->!$w-_ zy}{V_ykcChia#5gm4#1Lu2;J-DnPNvaF}D>7!#v9qhaR;TzIocykyIGeXXAmxqP2% z7L24md^dGU&V**~2NPc0U4-Jcp%(trDiZSxG4NNtnfX+ARg@o~-1=e&4i%VQ=9)|U zJCXW8iA?VvUV(FPi24Qc0_)55vGi<1>+}R|i=v;4twS^P8V|W;XvJ<(YgSakQCx1_ zzclRcSew;>0FS9>BsUj%{CZC(Mz<$kOI~Vasb^QP8|mZFBe&=OC7M1b6>~lc7Z70g zH6u>`F>(6u;tm|hRX^u+&zhl@zh5%T$I|FZJ?~n(U(zx)?2)&%S;H$hvU8tGpMxDV zptLglQ$^cO{D-&i5N$9SuOf(%$>Hg;p1yUgX{H@d&$aGHPCr$02Vgu!Q{c$@lFfUEig{NXH0myx=Sm$lC zQ!jBUsdL*_|Ec*XbFnqmD+6>t)5+v5+r_wDDjc3VVE^tLgkRGZrVM8*Evy<={1F!6 zpQ1b!U&&$0L^8r00#u*fxXN5FM3pyNt5(bP59P)hk1ompsNe_IK-Y0y9lvXq#Xf*g z2lBDE_cjbY=I90{(rx!~t;Iugg9HsiiGOUJUhO?&oMw zX#8eCO+%wW^TgQD&at7p@I1W}xOA(+V2&B-!NHRepIxdGjj2A`81gepYQv2i3WDDP zDSUx2;4^crhrjkjH!gV#n;zII>}IubqMvsA3}a82Q0lFY@kPT2b9ax$q%RtPDv2$2 zsS@RwIxgnm6-s|7JH zV3;VdS)79HWnH|<+|bEf-2%W$M+R}Kwz(iH=a6@`pt3}N9L->yljHAcp}}2;u#{Zg zS=tkBTh^;X-?pad)L(cA;@HPAj35GcT)-58H&10E(fhJ)FR~T-q0h_yNGn44&c;}6 z3ud)`xw11Hk1I4A+vfhuVykGrKE0KimZN;?vm^AG^^gy3?2Vofej3sHweHEXPCcUF zQM00SRTSr=Zg&+>-*>Bq`iiR`>{aP$9)V1i?BYs_M4u7ed}ecsCZivMC+Q?-=@6U1 zDMH7Nv~R0B4Zj;G@Onq=UN)uCKjkO%@3+a0))KbMvSTSWvsYvWb5s;v6!#4?%e94z zq~pHMUTD6vURzMMR!dZnC*&ty|3h?&6k_2eTK!doCmn`$P|L}xEN*N`rE@{5rC@HPL%)Av~gr2sb z6?z1np0h_w{pS1+?XL<*?ssp*Ft?pC2WiUk5PbP`QN)dd6lYja+-8fQ_1uK~?Z{6= zgCD(ac-UIpV6mI3pE8T2Dhs093US_9y$Zw;M_C6WmC?mfep677L~n*o3kFn61|Hes zp4znZYkQcnvFX=Q=B;dSaKnC`Tf4!nEzhPN1FAj) d9OfYX#eV1;?I#8ooWwecu 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 literal 0 HcmV?d00001 diff --git a/fuzz/summary.txt b/fuzz/summary.txt new file mode 100644 index 000000000000..8516bf3723aa --- /dev/null +++ b/fuzz/summary.txt @@ -0,0 +1,51 @@ +Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +fuzz/prng.c 31 0 100.00% 2 0 100.00% 49 0 100.00% +fuzz/udev.c 103 5 95.15% 17 1 94.12% 141 7 95.04% +fuzz/uniform_random.c 7 1 85.71% 1 0 100.00% 23 1 95.65% +fuzz/wrap.c 6 0 100.00% 1 0 100.00% 7 0 100.00% +openbsd-compat/explicit_bzero.c 4 0 100.00% 1 0 100.00% 13 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% 49 7 85.71% +openbsd-compat/strlcat.c 12 1 91.67% 1 0 100.00% 25 1 96.00% +openbsd-compat/timingsafe_bcmp.c 4 0 100.00% 1 0 100.00% 8 0 100.00% +src/aes256.c 115 4 96.52% 8 0 100.00% 175 14 92.00% +src/assert.c 616 46 92.53% 59 3 94.92% 924 64 93.07% +src/authkey.c 44 0 100.00% 5 0 100.00% 75 0 100.00% +src/bio.c 419 20 95.23% 49 2 95.92% 660 22 96.67% +src/blob.c 53 3 94.34% 10 0 100.00% 96 7 92.71% +src/buf.c 8 1 87.50% 2 0 100.00% 20 1 95.00% +src/cbor.c 986 17 98.28% 53 0 100.00% 1426 37 97.41% +src/compress.c 34 4 88.24% 3 0 100.00% 30 3 90.00% +src/config.c 94 1 98.94% 10 0 100.00% 146 3 97.95% +src/cred.c 581 38 93.46% 63 2 96.83% 872 48 94.50% +src/credman.c 382 10 97.38% 40 0 100.00% 614 15 97.56% +src/dev.c 414 74 82.13% 43 6 86.05% 556 106 80.94% +src/ecdh.c 117 2 98.29% 4 0 100.00% 161 5 96.89% +src/eddsa.c 54 0 100.00% 8 0 100.00% 77 0 100.00% +src/err.c 122 10 91.80% 1 0 100.00% 126 10 92.06% +src/es256.c 280 0 100.00% 16 0 100.00% 394 0 100.00% +src/hid.c 60 0 100.00% 12 0 100.00% 134 0 100.00% +src/hid_linux.c 173 68 60.69% 14 7 50.00% 303 123 59.41% +src/hid_unix.c 30 20 33.33% 2 0 100.00% 52 28 46.15% +src/info.c 198 0 100.00% 44 0 100.00% 405 0 100.00% +src/io.c 158 7 95.57% 10 0 100.00% 228 11 95.18% +src/iso7816.c 18 1 94.44% 5 0 100.00% 42 0 100.00% +src/largeblob.c 513 19 96.30% 30 0 100.00% 759 43 94.33% +src/log.c 39 5 87.18% 7 1 85.71% 73 4 94.52% +src/netlink.c 327 15 95.41% 40 0 100.00% 565 35 93.81% +src/nfc_linux.c 304 123 59.54% 23 10 56.52% 520 199 61.73% +src/pin.c 403 3 99.26% 26 0 100.00% 583 3 99.49% +src/random.c 6 1 83.33% 1 0 100.00% 8 1 87.50% +src/reset.c 24 0 100.00% 3 0 100.00% 27 0 100.00% +src/rs256.c 102 4 96.08% 8 0 100.00% 138 6 95.65% +src/u2f.c 473 6 98.73% 15 0 100.00% 742 9 98.79% + +Files which contain no functions: +openbsd-compat/openbsd-compat.h 0 0 - 0 0 - 0 0 - +src/extern.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 7359 516 92.99% 640 32 95.00% 11252 813 92.77% diff --git a/fuzz/udev.c b/fuzz/udev.c new file mode 100644 index 000000000000..3984d8f555ed --- /dev/null +++ b/fuzz/udev.c @@ -0,0 +1,269 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include +#include +#include +#include + +#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 (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 000000000000..357091c3c2f1 --- /dev/null +++ b/fuzz/uniform_random.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +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 000000000000..da905516f92a --- /dev/null +++ b/fuzz/wiredata_fido2.h @@ -0,0 +1,633 @@ +/* + * 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. + */ + +#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 + +#endif /* _WIREDATA_FIDO2_H */ diff --git a/fuzz/wiredata_u2f.h b/fuzz/wiredata_u2f.h new file mode 100644 index 000000000000..afe418fe9d96 --- /dev/null +++ b/fuzz/wiredata_u2f.h @@ -0,0 +1,152 @@ +/* + * 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. + */ + +#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 000000000000..5b91a64dbf4b --- /dev/null +++ b/fuzz/wrap.c @@ -0,0 +1,582 @@ +/* + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "mutator_aux.h" + +extern int prng_up; + +/* + * Build wrappers around functions of interest, and have them fail + * in a pseudo-random manner. + */ + +#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(char *, + strdup, + (const char *s), + NULL, + (s), + 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_EncryptInit_ex, + (EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, + const unsigned char *key, const unsigned char *iv), + 0, + (ctx, type, impl, key, iv), + 1 +) + +WRAP(int, + EVP_CIPHER_CTX_set_padding, + (EVP_CIPHER_CTX *x, int padding), + 0, + (x, padding), + 1 +) + +WRAP(int, + EVP_EncryptUpdate, + (EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, + const unsigned char *in, int inl), + 0, + (ctx, out, outl, in, inl), + 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(int, + EVP_DecryptInit_ex, + (EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, ENGINE *impl, + const unsigned char *key, const unsigned char *iv), + 0, + (ctx, type, impl, key, iv), + 1 +) + +WRAP(int, + EVP_DecryptUpdate, + (EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, + const unsigned char *in, int inl), + 0, + (ctx, out, outl, in, inl), + 1 +) + +WRAP(int, + SHA256_Init, + (SHA256_CTX *c), + 0, + (c), + 1 +) + +WRAP(int, + SHA256_Update, + (SHA256_CTX *c, const void *data, size_t len), + 0, + (c, data, len), + 1 +) + +WRAP(int, + SHA256_Final, + (unsigned char *md, SHA256_CTX *c), + 0, + (md, c), + 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(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(int, + RSA_set0_key, + (RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d), + 0, + (r, n, e, d), + 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(const EVP_MD *, + EVP_sha256, + (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 *, + 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_uint32, + (uint32_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(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), + -1, + (d, cmd, buf, count), + 1 +) + +WRAP(int, + usleep, + (unsigned int usec), + -1, + (usec), + 1 +) diff --git a/fuzz/wrapped.sym b/fuzz/wrapped.sym new file mode 100644 index 000000000000..de4f24ae0355 --- /dev/null +++ b/fuzz/wrapped.sym @@ -0,0 +1,83 @@ +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_uint32 +cbor_build_uint8 +cbor_load +cbor_map_add +cbor_map_handle +cbor_new_definite_array +cbor_new_definite_map +cbor_serialize_alloc +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_Cipher +EVP_CIPHER_CTX_ctrl +EVP_CIPHER_CTX_new +EVP_CIPHER_CTX_set_padding +EVP_CipherInit +EVP_DecryptInit_ex +EVP_DecryptUpdate +EVP_DigestVerifyInit +EVP_EncryptInit_ex +EVP_EncryptUpdate +EVP_MD_CTX_new +EVP_PKEY_assign +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_sha256 +fido_tx +HMAC +HMAC_CTX_new +HMAC_Final +HMAC_Init_ex +HMAC_Update +ioctl +malloc +RSA_set0_key +SHA256 +SHA256_Final +SHA256_Init +SHA256_Update +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 000000000000..ad9f339e6f9b --- /dev/null +++ b/man/CMakeLists.txt @@ -0,0 +1,371 @@ +# 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. + +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 + 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_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_ptr + es256_pk_new es256_pk_to_EVP_PKEY + fido_assert_new fido_assert_authdata_len + fido_assert_new fido_assert_authdata_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_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_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_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_maxmsgsiz + 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_fwversion + 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_transports_len + fido_cbor_info_new fido_cbor_info_transports_ptr + 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_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_sigcount + 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_aaguid_len + fido_cred_new fido_cred_aaguid_ptr + fido_cred_new fido_cred_largeblob_key_len + fido_cred_new fido_cred_largeblob_key_ptr + 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_sig_len + fido_cred_new fido_cred_sig_ptr + fido_cred_new fido_cred_type + fido_cred_new fido_cred_user_name + fido_cred_new fido_cred_user_id_len + fido_cred_new fido_cred_user_id_ptr + fido_cred_new fido_cred_x5c_len + fido_cred_new fido_cred_x5c_ptr + 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_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_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_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_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_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_protocol + fido_dev_open fido_dev_supports_cred_prot + fido_dev_open fido_dev_supports_credman + fido_dev_open fido_dev_supports_pin + fido_dev_open fido_dev_supports_uv + fido_dev_open fido_dev_has_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_set_sigmask + 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 + rs256_pk_new rs256_pk_free + rs256_pk_new rs256_pk_from_ptr + 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 ${CMAKE_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 ${CMAKE_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 ${CMAKE_SOURCE_DIR}/man/style.css + DESTINATION "${CMAKE_INSTALL_DOCDIR}/html") + foreach(f ${MAN_SOURCES}) + string(REGEX REPLACE ".[13]" "" f ${f}) + install(FILES ${CMAKE_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 ${CMAKE_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 ${CMAKE_BINARY_DIR}/man/${f}.gz + DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") + elseif(${f} MATCHES ".3$") + install(FILES ${CMAKE_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 ${CMAKE_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 ${CMAKE_BINARY_DIR}/man/${f} + DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") + elseif(${f} MATCHES ".3$") + install(FILES ${CMAKE_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 ${CMAKE_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 000000000000..5cba43663b7f --- /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/dyc.css b/man/dyc.css new file mode 100644 index 000000000000..1ff5b593e8d2 --- /dev/null +++ b/man/dyc.css @@ -0,0 +1,14 @@ + diff --git a/man/eddsa_pk_new.3 b/man/eddsa_pk_new.3 new file mode 100644 index 000000000000..65bf9a9f753d --- /dev/null +++ b/man/eddsa_pk_new.3 @@ -0,0 +1,122 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 15 2019 $ +.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 FIDO 2 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_EC_KEY +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 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 000000000000..54439cd300cf --- /dev/null +++ b/man/es256_pk_new.3 @@ -0,0 +1,126 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 24 2018 $ +.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_ptr , +.Nm es256_pk_to_EVP_PKEY +.Nd FIDO 2 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_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_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 +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 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 000000000000..da47d6f19dd3 --- /dev/null +++ b/man/fido2-assert.1 @@ -0,0 +1,256 @@ +.\" 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. +.\" +.Dd $Mdocdate: November 5 2019 $ +.Dt FIDO2-ASSERT 1 +.Os +.Sh NAME +.Nm fido2-assert +.Nd get/verify a FIDO 2 assertion +.Sh SYNOPSIS +.Nm +.Fl G +.Op Fl bdhpruv +.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 FIDO 2 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 FIDO 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. +.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 000000000000..301564d688e5 --- /dev/null +++ b/man/fido2-cred.1 @@ -0,0 +1,267 @@ +.\" 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. +.\" +.Dd $Mdocdate: November 5 2019 $ +.Dt FIDO2-CRED 1 +.Os +.Sh NAME +.Nm fido2-cred +.Nd make/verify a FIDO 2 credential +.Sh SYNOPSIS +.Nm +.Fl M +.Op Fl bdhqruv +.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 FIDO 2 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 FIDO 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. +.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 000000000000..43f1c0ea48b7 --- /dev/null +++ b/man/fido2-token.1 @@ -0,0 +1,388 @@ +.\" 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. +.\" +.Dd $Mdocdate: September 13 2019 $ +.Dt FIDO2-TOKEN 1 +.Os +.Sh NAME +.Nm fido2-token +.Nd find and manage a FIDO 2 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 +.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 V +.Sh DESCRIPTION +.Nm +manages a FIDO 2 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 +must hold the blob's base64-encoded 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 FIDO 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 FIDO 2.1 +.Dq largeBlob +encrypted with +.Ar key_path +from +.Ar device , +where +.Ar key_path +must hold the blob's base64-encoded 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 FIDO 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 FIDO 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 FIDO 2.1 Enterprise Attestation on +.Ar device . +.It Fl S Fl b Fl k Ar key_path Ar blob_path Ar device +Sets +.Ar blob_path +as a FIDO 2.1 +.Dq largeBlob +encrypted with +.Ar key_path +on +.Ar device , +where +.Ar blob_path +holds the blob's plaintext, and +.Ar key_path +the blob's base64-encoded encryption. +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 +.Ar blob_path +as a FIDO 2.1 +.Dq largeBlob +associated with +.Ar rp_id +on +.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 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 u Ar device +Enables the FIDO 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 FIDO 2.1. +.Pp +Whether the FIDO 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 000000000000..bbe6e4d8929a --- /dev/null +++ b/man/fido_assert_allow_cred.3 @@ -0,0 +1,47 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_ASSERT_ALLOW_CRED 3 +.Os +.Sh NAME +.Nm fido_assert_allow_cred +.Nd appends a credential ID to the list of credentials allowed in an assertion +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_assert_allow_cred "fido_assert_t *assert" "const unsigned char *ptr" "size_t len" +.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 FIDO 2 credential ID, please refer to the +Web Authentication (webauthn) standard. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_assert_allow_cred +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 000000000000..16f4e3a6e46d --- /dev/null +++ b/man/fido_assert_new.3 @@ -0,0 +1,243 @@ +.\" 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. +.\" +.Dd $Mdocdate: October 22 2019 $ +.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_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_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 FIDO 2 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_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_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 +FIDO 2 assertions are abstracted in +.Em libfido2 +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. +.Pp +The +.Fn fido_assert_user_id_ptr , +.Fn fido_assert_authdata_ptr , +.Fn fido_assert_blob_ptr , +.Fn fido_assert_hmac_secret_ptr , +.Fn fido_assert_largeblob_key_ptr , +.Fn fido_assert_sig_ptr , +and +.Fn fido_assert_id_ptr +functions return pointers to the user ID, CBOR-encoded +authenticator data, cred blob, hmac-secret, +.Dq largeBlobKey , +signature, and credential ID attributes of statement +.Fa idx +in +.Fa assert . +.Pp +The +.Fn fido_assert_user_id_len , +.Fn fido_assert_authdata_len , +.Fn fido_assert_blob_len , +.Fn fido_assert_hmac_secret_len , +.Fn fido_assert_largeblob_key_len , +.Fn fido_assert_sig_len , +and +.Fn fido_assert_id_len +functions can be used to retrieve the corresponding length of a +specific attribute. +.Pp +The +.Fn fido_assert_sigcount +function can be used to obtain the signature counter of statement +.Fa idx +in +.Fa assert . +.Pp +The +.Fn fido_assert_flags +function returns the authenticator data flags of statement +.Fa idx +in +.Fa assert . +.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 FIDO 2 server for verification. +.Pp +The +.Fn fido_assert_clientdata_hash_ptr +function returns a pointer to the client data hash of +.Fa assert . +The corresponding length can be obtained by +.Fn fido_assert_clientdata_hash_len . +.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_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_hmac_secret_ptr , +.Fn fido_assert_largeblob_key_ptr , +.Fn fido_assert_user_id_ptr , +and +.Fn fido_assert_sig_ptr +functions 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 000000000000..2f2ca5b45d6e --- /dev/null +++ b/man/fido_assert_set_authdata.3 @@ -0,0 +1,221 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" Use of this source code is governed by a BSD-style +.\" license that can be found in the LICENSE file. +.\" +.Dd $Mdocdate: May 23 2018 $ +.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 +.Nd set parameters of a FIDO 2 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" "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" +.Sh DESCRIPTION +The +.Nm +set of functions define the various parameters of a FIDO 2 +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 FIDO 2 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 , +.Fn fido_assert_set_hmac_salt , +and +.Fn fido_assert_set_hmac_secret +functions set the client data hash and hmac-salt 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. +.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_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 +Use of the +.Nm +set of functions may happen in two distinct situations: +when asking a FIDO device to produce a series of assertion +statements, prior to +.Xr fido_dev_get_assert 3 +(i.e, in the context of a FIDO client), or when verifying assertion +statements using +.Xr fido_assert_verify 3 +(i.e, in the context of a FIDO server). +.Pp +For a complete description of the generation of a FIDO 2 assertion +and its verification, please refer to the FIDO 2 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 . +.Pp +.Fn fido_assert_set_hmac_secret +is not normally useful in a FIDO client or server \(em it is provided +to enable testing other functionality that relies on retrieving the +HMAC secret from an assertion obtained from an authenticator. +.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 diff --git a/man/fido_assert_verify.3 b/man/fido_assert_verify.3 new file mode 100644 index 000000000000..82e64e12e27a --- /dev/null +++ b/man/fido_assert_verify.3 @@ -0,0 +1,79 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 24 2018 $ +.Dt FIDO_ASSERT_VERIFY 3 +.Os +.Sh NAME +.Nm fido_assert_verify +.Nd verifies the signature of a FIDO 2 assertion statement +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_assert_verify "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 FIDO 2 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_RS256 , +or +.Dv COSE_EDDSA , +and +.Fa pk +points to a +.Vt es256_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 000000000000..1fe3e8ebc18f --- /dev/null +++ b/man/fido_bio_dev_get_info.3 @@ -0,0 +1,122 @@ +.\" 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. +.\" +.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 FIDO 2 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 000000000000..3db3e7acd45d --- /dev/null +++ b/man/fido_bio_enroll_new.3 @@ -0,0 +1,95 @@ +.\" 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. +.\" +.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 FIDO 2 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 FIDO 2 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 000000000000..c82733337b4e --- /dev/null +++ b/man/fido_bio_info_new.3 @@ -0,0 +1,81 @@ +.\" 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. +.\" +.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 FIDO 2 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 000000000000..12a379e9a46a --- /dev/null +++ b/man/fido_bio_template.3 @@ -0,0 +1,179 @@ +.\" 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. +.\" +.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 FIDO 2 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 FIDO 2 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 000000000000..ecba77291f53 --- /dev/null +++ b/man/fido_cbor_info_new.3 @@ -0,0 +1,231 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 24 2018 $ +.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_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_maxcredcntlst , +.Nm fido_cbor_info_maxcredidlen , +.Nm fido_cbor_info_fwversion +.Nd FIDO 2 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 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_fwversion "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_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_fwversion +function returns the firmware version attribute of +.Fa ci . +.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_open 3 diff --git a/man/fido_cred_exclude.3 b/man/fido_cred_exclude.3 new file mode 100644 index 000000000000..700d6afd8746 --- /dev/null +++ b/man/fido_cred_exclude.3 @@ -0,0 +1,60 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_CRED_EXCLUDE 3 +.Os +.Sh NAME +.Nm fido_cred_exclude +.Nd appends a credential ID to a credential's list of excluded credentials +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_cred_exclude "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.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 FIDO 2 credential ID, please refer to the +Web Authentication (webauthn) standard. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_cred_exclude +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 000000000000..8cecf5f29850 --- /dev/null +++ b/man/fido_cred_new.3 @@ -0,0 +1,257 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_CRED_NEW 3 +.Os +.Sh NAME +.Nm fido_cred_new , +.Nm fido_cred_free , +.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_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_type , +.Nm fido_cred_flags , +.Nm fido_cred_sigcount +.Nd FIDO 2 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 int +.Fn fido_cred_prot "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 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 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 +FIDO 2 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 +The +.Fn fido_cred_prot +function returns the protection of +.Fa cred . +See +.Xr fido_cred_set_prot 3 +for the values understood by +.Em libfido2 . +.Pp +The +.Fn fido_cred_fmt +function returns a pointer to a NUL-terminated string containing +the format 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 , +and +.Fn fido_cred_x5c_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, and x509 certificate 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 , +and +.Fn fido_cred_x5c_len . +.Pp +The authenticator data, x509 certificate, and signature parts of a +credential are typically passed to a FIDO 2 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_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 000000000000..91e1edbaf810 --- /dev/null +++ b/man/fido_cred_set_authdata.3 @@ -0,0 +1,307 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_CRED_SET_AUTHDATA 3 +.Os +.Sh NAME +.Nm fido_cred_set_authdata , +.Nm fido_cred_set_authdata_raw , +.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_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 FIDO 2 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_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_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 FIDO 2 +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 FIDO 2 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_x509 , +.Fn fido_cred_set_sig , +.Fn fido_cred_set_id , +and +.Fn fido_cred_set_clientdata_hash +functions set the authenticator data, attestation certificate, +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. +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 . +.Pp +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 +.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 , +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_prot +function 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 format 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 by U2F , +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 not +be able to generate +.Vt "packed" . +.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_RS256 , +or +.Dv COSE_EDDSA . +The type of a credential may only be set once. +Note that not all authenticators support COSE_RS256 or COSE_EDDSA. +.Pp +Use of the +.Nm +set of functions may happen in two distinct situations: +when generating a new credential on a FIDO device, prior to +.Xr fido_dev_make_cred 3 +(i.e, in the context of a FIDO client), or when validating +a generated credential using +.Xr fido_cred_verify 3 +(i.e, in the context of a FIDO server). +.Pp +For a complete description of the generation of a FIDO 2 credential +and its verification, please refer to the FIDO 2 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 000000000000..6b720f2132ea --- /dev/null +++ b/man/fido_cred_verify.3 @@ -0,0 +1,69 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_CRED_VERIFY 3 +.Os +.Sh NAME +.Nm fido_cred_verify +.Nd verifies the attestation signature of a FIDO 2 credential +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_cred_verify "const fido_cred_t *cred" +.Sh DESCRIPTION +The +.Fn fido_cred_verify +function verifies whether the attestation signature contained in +.Fa cred +matches the attributes of the credential. +Before using +.Fn fido_cred_verify +in a sensitive context, the reader is strongly encouraged to make +herself familiar with the FIDO 2 credential attestation process +as defined in the Web Authentication (webauthn) standard. +.Pp +A brief description follows: +.Pp +The +.Fn fido_cred_verify +function verifies whether the client data hash, relying party ID, +credential ID, type, 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 +and +.Em fido-u2f . +The attestation type implemented by +.Fn fido_cred_verify +is +.Em Basic Attestation . +The attestation key pair is assumed to be of the type ES256. +Other attestation formats and types are not supported. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_cred_verify +are defined in +.In fido/err.h . +If +.Fa cred +does not contain attestation data, then +.Dv FIDO_ERR_INVALID_ARGUMENT +is returned. +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 000000000000..31f240fbbe8c --- /dev/null +++ b/man/fido_credman_metadata_new.3 @@ -0,0 +1,326 @@ +.\" 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. +.\" +.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 FIDO 2 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 FIDO 2.1. diff --git a/man/fido_dev_enable_entattest.3 b/man/fido_dev_enable_entattest.3 new file mode 100644 index 000000000000..7cb766d41d0c --- /dev/null +++ b/man/fido_dev_enable_entattest.3 @@ -0,0 +1,98 @@ +.\" 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. +.\" +.Dd $Mdocdate: September 22 2020 $ +.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 +.Nd FIDO 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" +.Sh DESCRIPTION +The functions described in this page allow configuration of a +FIDO 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 +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 +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 , +and +.Fn fido_dev_set_pin_minlen +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.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 000000000000..2e33fc516e7d --- /dev/null +++ b/man/fido_dev_get_assert.3 @@ -0,0 +1,76 @@ +.\" 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. +.\" +.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 FIDO 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 FIDO 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 000000000000..8372c6ff010b --- /dev/null +++ b/man/fido_dev_get_touch_begin.3 @@ -0,0 +1,73 @@ +.\" 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. +.\" +.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 FIDO 2 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 FIDO 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 000000000000..22519e29b9fa --- /dev/null +++ b/man/fido_dev_info_manifest.3 @@ -0,0 +1,143 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 25 2018 $ +.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 +.Nd FIDO 2 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" +.Sh DESCRIPTION +The +.Fn fido_dev_info_manifest +function fills +.Fa devlist +with up to +.Fa ilen +FIDO 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 +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 . +.Pp +The +.Fn fido_dev_info_product_string +function returns the product string of +.Fa di . +.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 . +.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 +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 000000000000..830534ed0e7b --- /dev/null +++ b/man/fido_dev_largeblob_get.3 @@ -0,0 +1,194 @@ +.\" 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. +.\" +.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 FIDO 2 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 FIDO 2.1 authenticator to be +read, written, and inspected. +.Dq largeBlobs +is a FIDO 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 FIDO 2.1's +.Dq largeBlobs +extension, please refer to the FIDO 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_dev_get_rk 3 , +.Xr fido_credman_dev_get_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 000000000000..cd156dc94f89 --- /dev/null +++ b/man/fido_dev_make_cred.3 @@ -0,0 +1,77 @@ +.\" 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. +.\" +.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 FIDO 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 FIDO 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 000000000000..f2af7817d801 --- /dev/null +++ b/man/fido_dev_open.3 @@ -0,0 +1,250 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_DEV_OPEN 3 +.Os +.Sh NAME +.Nm fido_dev_open , +.Nm fido_dev_close , +.Nm fido_dev_cancel , +.Nm fido_dev_new , +.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_pin , +.Nm fido_dev_has_pin , +.Nm fido_dev_supports_uv , +.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 FIDO 2 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_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 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_pin "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_has_pin "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_supports_uv "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_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_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 . +.Pp +The +.Fn fido_dev_force_u2f +function can be used to force CTAP1 (U2F) communication with +.Fa dev . +.Pp +The +.Fn fido_dev_is_fido2 +function returns +.Dv true +if +.Fa dev +is a FIDO 2 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 FIDO 2.1 Credential Management. +.Pp +The +.Fn fido_dev_supports_cred_prot +function returns +.Dv true +if +.Fa dev +supports FIDO 2.1 Credential Protection. +.Pp +The +.Fn fido_dev_supports_pin +function returns +.Dv true +if +.Fa dev +supports FIDO 2.0 Client PINs. +.Pp +The +.Fn fido_dev_has_pin +function returns +.Dv true +if +.Fa dev +has a FIDO 2.0 Client PIN set. +.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_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 +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 000000000000..231ae2411be8 --- /dev/null +++ b/man/fido_dev_set_io_functions.3 @@ -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. +.\" +.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 +.Nd FIDO 2 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 +.Ed +.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" +.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 . +.Sh RETURN VALUES +On success, +.Fn fido_dev_set_io_functions +and +.Fn fido_dev_set_sigmask +return +.Dv FIDO_OK . +On error, a different error code defined in +.In fido/err.h +is returned. diff --git a/man/fido_dev_set_pin.3 b/man/fido_dev_set_pin.3 new file mode 100644 index 000000000000..f5ef94ff6fb5 --- /dev/null +++ b/man/fido_dev_set_pin.3 @@ -0,0 +1,103 @@ +.\" 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. +.\" +.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 FIDO 2 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 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 000000000000..dcfc530c59ae --- /dev/null +++ b/man/fido_init.3 @@ -0,0 +1,52 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" Use of this source code is governed by a BSD-style +.\" license that can be found in the LICENSE file. +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_INIT 3 +.Os +.Sh NAME +.Nm fido_init +.Nd initialise the FIDO 2 library +.Sh SYNOPSIS +.In fido.h +.Ft void +.Fn fido_init "int flags" +.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 be FIDO2 but fails to respond to a +FIDO2 command. +.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 000000000000..05c86b92a158 --- /dev/null +++ b/man/fido_strerr.3 @@ -0,0 +1,27 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_STRERR 3 +.Os +.Sh NAME +.Nm fido_strerr +.Nd FIDO 2 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 000000000000..4ad0ebe936f3 --- /dev/null +++ b/man/rs256_pk_new.3 @@ -0,0 +1,122 @@ +.\" 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. +.\" +.Dd $Mdocdate: May 24 2018 $ +.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_ptr , +.Nm rs256_pk_to_EVP_PKEY +.Nd FIDO 2 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_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_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_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 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 000000000000..8c223faa9a19 --- /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-getline.c b/openbsd-compat/bsd-getline.c new file mode 100644 index 000000000000..52b44f70ba2f --- /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 +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +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 000000000000..903bfc310fb9 --- /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 +#endif +#include + +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 000000000000..ca261a65e7f1 --- /dev/null +++ b/openbsd-compat/clock_gettime.c @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#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 000000000000..9981dfafbaeb --- /dev/null +++ b/openbsd-compat/endian_win32.c @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#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 000000000000..394c7bb12f68 --- /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 +#include +#include +#include +#include + +#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 000000000000..ac64e69b4d18 --- /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 + +/* + * 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 000000000000..8017aff99991 --- /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 +#include + +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 000000000000..d1e00661fd27 --- /dev/null +++ b/openbsd-compat/freezero.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek + * + * 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 000000000000..8eb12447ed64 --- /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 000000000000..dabbb461cbe5 --- /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 + * + * 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 +#include +#endif +#include +#include +#include +#include + +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/hkdf.c b/openbsd-compat/hkdf.c new file mode 100644 index 000000000000..745b420f3747 --- /dev/null +++ b/openbsd-compat/hkdf.c @@ -0,0 +1,124 @@ +/* $OpenBSD: hkdf.c,v 1.4 2019/11/21 20:02:20 tim Exp $ */ +/* Copyright (c) 2014, Google Inc. + * + * Permission to use, copy, modify, and/or 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" +#include "fido.h" + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + +#include +#include + +#include +#include + +#define CRYPTOerror(r) CRYPTOerr(ERR_LIB_CRYPTO, (r)) + +/* https://tools.ietf.org/html/rfc5869#section-2 */ +int +HKDF(uint8_t *out_key, size_t out_len, const EVP_MD *digest, + const uint8_t *secret, size_t secret_len, const uint8_t *salt, + size_t salt_len, const uint8_t *info, size_t info_len) +{ + uint8_t prk[EVP_MAX_MD_SIZE]; + size_t prk_len; + + if (!HKDF_extract(prk, &prk_len, digest, secret, secret_len, salt, + salt_len)) + return 0; + if (!HKDF_expand(out_key, out_len, digest, prk, prk_len, info, + info_len)) + return 0; + + return 1; +} + +/* https://tools.ietf.org/html/rfc5869#section-2.2 */ +int +HKDF_extract(uint8_t *out_key, size_t *out_len, + const EVP_MD *digest, const uint8_t *secret, size_t secret_len, + const uint8_t *salt, size_t salt_len) +{ + unsigned int len; + + /* + * If salt is not given, HashLength zeros are used. However, HMAC does + * that internally already so we can ignore it. + */ + if (salt_len > INT_MAX || HMAC(digest, salt, (int)salt_len, secret, + secret_len, out_key, &len) == NULL) { + CRYPTOerror(ERR_R_CRYPTO_LIB); + return 0; + } + *out_len = len; + return 1; +} + +/* https://tools.ietf.org/html/rfc5869#section-2.3 */ +int +HKDF_expand(uint8_t *out_key, size_t out_len, + const EVP_MD *digest, const uint8_t *prk, size_t prk_len, + const uint8_t *info, size_t info_len) +{ + const size_t digest_len = EVP_MD_size(digest); + uint8_t previous[EVP_MAX_MD_SIZE]; + size_t n, done = 0; + unsigned int i; + int ret = 0; + HMAC_CTX hmac; + + /* Expand key material to desired length. */ + n = (out_len + digest_len - 1) / digest_len; + if (out_len + digest_len < out_len || n > 255 || prk_len > INT_MAX) { + CRYPTOerror(EVP_R_TOO_LARGE); + return 0; + } + + HMAC_CTX_init(&hmac); + if (!HMAC_Init_ex(&hmac, prk, (int)prk_len, digest, NULL)) + goto out; + + for (i = 0; i < n; i++) { + uint8_t ctr = i + 1; + size_t todo; + + if (i != 0 && (!HMAC_Init_ex(&hmac, NULL, 0, NULL, NULL) || + !HMAC_Update(&hmac, previous, digest_len))) + goto out; + + if (!HMAC_Update(&hmac, info, info_len) || + !HMAC_Update(&hmac, &ctr, 1) || + !HMAC_Final(&hmac, previous, NULL)) + goto out; + + todo = digest_len; + if (done + todo > out_len) + todo = out_len - done; + + memcpy(out_key + done, previous, todo); + done += todo; + } + + ret = 1; + + out: + HMAC_CTX_cleanup(&hmac); + explicit_bzero(previous, sizeof(previous)); + if (ret != 1) + CRYPTOerror(ERR_R_CRYPTO_LIB); + return ret; +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ diff --git a/openbsd-compat/hkdf.h b/openbsd-compat/hkdf.h new file mode 100644 index 000000000000..34450f9dd7f0 --- /dev/null +++ b/openbsd-compat/hkdf.h @@ -0,0 +1,65 @@ +/* $OpenBSD: hkdf.h,v 1.2 2018/04/03 13:33:53 tb Exp $ */ +/* Copyright (c) 2014, Google Inc. + * + * Permission to use, copy, modify, and/or 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. */ + +#ifndef OPENSSL_HEADER_HKDF_H +#define OPENSSL_HEADER_HKDF_H + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/* + * HKDF computes HKDF (as specified by RFC 5869) of initial keying + * material |secret| with |salt| and |info| using |digest|, and + * outputs |out_len| bytes to |out_key|. It returns one on success and + * zero on error. + * + * HKDF is an Extract-and-Expand algorithm. It does not do any key + * stretching, and as such, is not suited to be used alone to generate + * a key from a password. + */ + +int HKDF(uint8_t *out_key, size_t out_len, const struct env_md_st *digest, + const uint8_t *secret, size_t secret_len, const uint8_t *salt, + size_t salt_len, const uint8_t *info, size_t info_len); + +/* + * HKDF_extract computes a HKDF PRK (as specified by RFC 5869) from + * initial keying material |secret| and salt |salt| using |digest|, + * and outputs |out_len| bytes to |out_key|. The maximum output size + * is |EVP_MAX_MD_SIZE|. It returns one on success and zero on error. + */ +int HKDF_extract(uint8_t *out_key, size_t *out_len, + const struct env_md_st *digest, const uint8_t *secret, + size_t secret_len, const uint8_t *salt, size_t salt_len); + +/* + * HKDF_expand computes a HKDF OKM (as specified by RFC 5869) of + * length |out_len| from the PRK |prk| and info |info| using |digest|, + * and outputs the result to |out_key|. It returns one on success and + * zero on error. + */ +int HKDF_expand(uint8_t *out_key, size_t out_len, + const EVP_MD *digest, const uint8_t *prk, size_t prk_len, + const uint8_t *info, size_t info_len); + + +#if defined(__cplusplus) +} /* extern C */ +#endif + +#endif /* OPENSSL_HEADER_HKDF_H */ diff --git a/openbsd-compat/openbsd-compat.h b/openbsd-compat/openbsd-compat.h new file mode 100644 index 000000000000..1be3aa295051 --- /dev/null +++ b/openbsd-compat/openbsd-compat.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _OPENBSD_COMPAT_H +#define _OPENBSD_COMPAT_H + +#if defined(_MSC_VER) +#include "types.h" +#endif + +#if defined(HAVE_ENDIAN_H) +#include +#endif + +#if defined(__APPLE__) && !defined(HAVE_ENDIAN_H) +#include +#define be16toh(x) OSSwapBigToHostInt16((x)) +#define htobe16(x) OSSwapHostToBigInt16((x)) +#define be32toh(x) OSSwapBigToHostInt32((x)) +#define htole32(x) OSSwapHostToLittleInt32((x)) +#define htole64(x) OSSwapHostToLittleInt64((x)) +#endif /* __APPLE__ && !HAVE_ENDIAN_H */ + +#if defined(_WIN32) && !defined(HAVE_ENDIAN_H) +#include +#include +#if !defined(_MSC_VER) +#include +#endif +#define be16toh(x) ntohs((x)) +#define htobe16(x) htons((x)) +#define be32toh(x) ntohl((x)) +uint32_t htole32(uint32_t); +uint64_t htole64(uint64_t); +#endif /* _WIN32 && !HAVE_ENDIAN_H */ + +#if defined(__FreeBSD__) && !defined(HAVE_ENDIAN_H) +#include +#endif + +#include +#include + +#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_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 +#endif + +#include + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#include +#include "hkdf.h" +#define EVP_PKEY_get0_EC_KEY(x) ((x)->pkey.ec) +#define EVP_PKEY_get0_RSA(x) ((x)->pkey.rsa) +#endif + +#if !defined(HAVE_ERR_H) +#include "err.h" +#else +#include +#endif + +#if !defined(HAVE_GETOPT) +#include "getopt.h" +#else +#include +#endif + +#if !defined(HAVE_GETLINE) +#include +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 + +#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 000000000000..599a3bff3bc6 --- /dev/null +++ b/openbsd-compat/posix_ioctl_check.c @@ -0,0 +1,7 @@ +#include + +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 000000000000..eac67c2304f8 --- /dev/null +++ b/openbsd-compat/posix_win.c @@ -0,0 +1,61 @@ +/* + * Public domain + * + * File IO compatibility shims + * Brent Cook + */ + +#define NO_REDEF_POSIX_FUNCTIONS + +#include + +#include +#include + +#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 000000000000..a1e0888cc7f5 --- /dev/null +++ b/openbsd-compat/posix_win.h @@ -0,0 +1,47 @@ +/* + * Public domain + * + * BSD socket emulation code for Winsock2 + * Brent Cook + */ + +#ifndef _COMPAT_POSIX_WIN_H +#define _COMPAT_POSIX_WIN_H + +#ifdef _WIN32 + +#include + +#include +#include +#include +#include +#include +#include + +#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 000000000000..8b841906a735 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#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 000000000000..e4451f302ba5 --- /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 + * + * 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 + +#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 000000000000..968987c563ab --- /dev/null +++ b/openbsd-compat/readpassphrase_win32.c @@ -0,0 +1,131 @@ +/* +* Author: Manoj Ampalam +* +* Author: Bryan Berns +* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 000000000000..5d2f8d9885fd --- /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 + * + * 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 +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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 000000000000..44470debc972 --- /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 + * + * 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 +#include + +/* + * 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 000000000000..a8b18eaccf8f --- /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 + * + * 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 +#include + +/* + * 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/time.h b/openbsd-compat/time.h new file mode 100644 index 000000000000..b125f73a7072 --- /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 +#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 +#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 000000000000..3f7b9e541cec --- /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 000000000000..617023078be3 --- /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 + +#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 +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 000000000000..0314c38f7161 --- /dev/null +++ b/regress/CMakeLists.txt @@ -0,0 +1,16 @@ +# 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. + +add_custom_target(regress ALL) + +macro(add_regress_test NAME SOURCES) + add_executable(${NAME} ${SOURCES}) + target_link_libraries(${NAME} fido2_shared) + add_custom_command(TARGET regress POST_BUILD COMMAND ${NAME} + DEPENDS ${NAME}) +endmacro() + +add_regress_test(regress_cred cred.c) +add_regress_test(regress_assert assert.c) +add_regress_test(regress_dev dev.c) diff --git a/regress/assert.c b/regress/assert.c new file mode 100644 index 000000000000..dfaf50662c76 --- /dev/null +++ b/regress/assert.c @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include + +#define FAKE_DEV_HANDLE ((void *)0xdeadbeef) + +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 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_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] = ~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] = ~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); + 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] = ~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); +} + +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(); + + exit(0); +} diff --git a/regress/cred.c b/regress/cred.c new file mode 100644 index 000000000000..01df1ef9320d --- /dev/null +++ b/regress/cred.c @@ -0,0 +1,988 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include + +#define FAKE_DEV_HANDLE ((void *)0xdeadbeef) + +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, +}; + +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 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, +}; + +/* + * 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, +}; + +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] = ~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] = ~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] = ~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] = ~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); +} + +int +main(void) +{ + 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(); + + exit(0); +} diff --git a/regress/dev.c b/regress/dev.c new file mode 100644 index 000000000000..35061aabbb64 --- /dev/null +++ b/regress/dev.c @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include + +#include "../fuzz/wiredata_fido2.h" + +#define FAKE_DEV_HANDLE ((void *)0xdeadbeef) +#define REPORT_LEN (64 + 1) + +static uint8_t ctap_nonce[8]; +static uint8_t *wiredata_ptr; +static size_t wiredata_len; +static int initialised; + +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) +{ + size_t n; + + (void)ms; + + 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 (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) +{ + assert(handle == FAKE_DEV_HANDLE); + assert(ptr != NULL); + assert(len == REPORT_LEN); + + if (!initialised) + memcpy(&ctap_nonce, &ptr[8], sizeof(ctap_nonce)); + + 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); + + memcpy(wiredata_ptr, ctap_init_data, sizeof(ctap_init_data)); + + 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); + 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); + 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); + 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); + wiredata_clear(&wiredata); +} + +int +main(void) +{ + fido_init(0); + + open_iff_ok(); + reopen(); + double_open(); + is_fido2(); + has_pin(); + + exit(0); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000000..f9efd3f234ed --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,136 @@ +# 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. + +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 + hid.c + info.c + io.c + iso7816.c + largeblob.c + log.c + pin.c + random.c + reset.c + rs256.c + u2f.c +) + +if(FUZZ) + list(APPEND FIDO_SOURCES ../fuzz/prng.c) + list(APPEND FIDO_SOURCES ../fuzz/uniform_random.c) + list(APPEND FIDO_SOURCES ../fuzz/udev.c) + list(APPEND FIDO_SOURCES ../fuzz/wrap.c) +endif() +if(NFC_LINUX) + list(APPEND FIDO_SOURCES netlink.c nfc_linux.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") + 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 + "-Wconversion -Wsign-conversion") +endif() + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/bsd-getpagesize.c + ../openbsd-compat/endian_win32.c + ../openbsd-compat/explicit_bzero.c + ../openbsd-compat/explicit_bzero_win32.c + ../openbsd-compat/freezero.c + ../openbsd-compat/hkdf.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_WINHELLO) + list(APPEND BASE_LIBRARIES webauthn) + endif() +elseif(APPLE) + list(APPEND BASE_LIBRARIES "-framework CoreFoundation" "-framework IOKit") +endif() + +list(APPEND TARGET_LIBRARIES + ${CBOR_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${UDEV_LIBRARIES} + ${BASE_LIBRARIES} + ${HIDAPI_LIBRARIES} + ${ZLIB_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 WIN32) + 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 000000000000..f093b7ce0bd5 --- /dev/null +++ b/src/aes256.c @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#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 000000000000..b36f8e324660 --- /dev/null +++ b/src/assert.c @@ -0,0 +1,1134 @@ +/* + * 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. + */ + +#include +#include + +#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 */ + 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) +{ + 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])) != 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) < 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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + fido_assert_reset_rx(assert); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + /* start with room for a single assertion */ + if ((assert->stmt = calloc(1, sizeof(fido_assert_stmt))) == NULL) + return (FIDO_ERR_INTERNAL); + + assert->stmt_len = 0; + assert->stmt_cnt = 1; + + /* adjust as needed */ + if ((r = cbor_parse_reply(reply, (size_t)reply_len, assert, + adjust_assert_count)) != FIDO_OK) { + fido_log_debug("%s: adjust_assert_count", __func__); + return (r); + } + + /* parse the first assertion */ + if ((r = cbor_parse_reply(reply, (size_t)reply_len, + &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_assert_reply", __func__); + return (r); + } + + assert->stmt_len++; + + return (FIDO_OK); +} + +static int +fido_get_next_assert_tx(fido_dev_t *dev) +{ + const unsigned char cbor[] = { CTAP_CBOR_NEXT_ASSERT }; + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor)) < 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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + /* 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); + return (FIDO_ERR_INTERNAL); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, + &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_assert_reply", __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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)) != 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 r; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_get_assert(dev, assert, pin)); +#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, -1)); + } + + 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)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + } + + r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, -1); + 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); +} + +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; + unsigned char *authdata_ptr = NULL; + size_t authdata_len; + struct cbor_load_result cbor; + SHA256_CTX ctx; + int ok = -1; + + 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); + + if (cose_alg != COSE_EDDSA) { + if (dgst->len < SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 || + SHA256_Update(&ctx, authdata_ptr, authdata_len) == 0 || + SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 || + SHA256_Final(dgst->ptr, &ctx) == 0) { + fido_log_debug("%s: sha256", __func__); + goto fail; + } + dgst->len = SHA256_DIGEST_LENGTH; + } else { + if (SIZE_MAX - authdata_len < clientdata->len || + dgst->len < authdata_len + clientdata->len) { + fido_log_debug("%s: memcpy", __func__); + goto fail; + } + memcpy(dgst->ptr, authdata_ptr, authdata_len); + memcpy(dgst->ptr + authdata_len, clientdata->ptr, + clientdata->len); + dgst->len = authdata_len + clientdata->len; + } + + ok = 0; +fail: + if (item != NULL) + cbor_decref(&item); + + return (ok); +} + +int +fido_verify_sig_es256(const fido_blob_t *dgst, const es256_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey = NULL; + EC_KEY *ec = NULL; + int ok = -1; + + /* ECDSA_verify 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 ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL || + (ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) { + fido_log_debug("%s: pk -> ec", __func__); + goto fail; + } + + if (ECDSA_verify(0, dgst->ptr, (int)dgst->len, sig->ptr, + (int)sig->len, ec) != 1) { + fido_log_debug("%s: ECDSA_verify", __func__); + goto fail; + } + + ok = 0; +fail: + if (pkey != NULL) + EVP_PKEY_free(pkey); + + return (ok); +} + +int +fido_verify_sig_rs256(const fido_blob_t *dgst, const rs256_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey = NULL; + RSA *rsa = NULL; + int ok = -1; + + /* RSA_verify needs unsigned ints */ + if (dgst->len > UINT_MAX || sig->len > UINT_MAX) { + fido_log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__, + dgst->len, sig->len); + return (-1); + } + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL || + (rsa = EVP_PKEY_get0_RSA(pkey)) == NULL) { + fido_log_debug("%s: pk -> ec", __func__); + goto fail; + } + + if (RSA_verify(NID_sha256, dgst->ptr, (unsigned int)dgst->len, sig->ptr, + (unsigned int)sig->len, rsa) != 1) { + fido_log_debug("%s: RSA_verify", __func__); + goto fail; + } + + ok = 0; +fail: + if (pkey != NULL) + EVP_PKEY_free(pkey); + + return (ok); +} + +int +fido_verify_sig_eddsa(const fido_blob_t *dgst, const eddsa_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey = NULL; + EVP_MD_CTX *mdctx = NULL; + int ok = -1; + + /* 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 ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: pk -> pkey", __func__); + goto fail; + } + + 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: + if (mdctx != NULL) + EVP_MD_CTX_free(mdctx); + + if (pkey != NULL) + EVP_PKEY_free(pkey); + + 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 = fido_verify_sig_es256(&dgst, pk, &stmt->sig); + break; + case COSE_RS256: + ok = fido_verify_sig_rs256(&dgst, pk, &stmt->sig); + break; + case COSE_EDDSA: + ok = fido_verify_sig_eddsa(&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); +} + +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_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); + fido_blob_reset(&assert->cd); + fido_blob_reset(&assert->cdh); + fido_blob_reset(&assert->ext.hmac_salt); + fido_free_blob_array(&assert->allow_list); + memset(&assert->ext, 0, sizeof(assert->ext)); + memset(&assert->allow_list, 0, sizeof(assert->allow_list)); + assert->rp_id = 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].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_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_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 (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 ((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 000000000000..c3474ccafc01 --- /dev/null +++ b/src/authkey.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "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) +{ + 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) < 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 reply[FIDO_MAXMSG]; + int reply_len; + + fido_log_debug("%s: dev=%p, authkey=%p, ms=%d", __func__, (void *)dev, + (void *)authkey, ms); + + memset(authkey, 0, sizeof(*authkey)); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + return (cbor_parse_reply(reply, (size_t)reply_len, authkey, + parse_authkey)); +} + +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)) != 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) +{ + return (fido_dev_authkey_wait(dev, authkey, -1)); +} diff --git a/src/bio.c b/src/bio.c new file mode 100644 index 000000000000..06bc32eea7ed --- /dev/null +++ b/src/bio.c @@ -0,0 +1,841 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "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) +{ + 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)) != 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])) != 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) < 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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + bio_reset_template_array(ta); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, ta, + bio_parse_template_array)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_array" , __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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) +{ + if (pin == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_get_template_array_wait(dev, ta, pin, -1)); +} + +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)) != 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) +{ + if (pin == NULL || t->name == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_set_template_name_wait(dev, t, pin, -1)); +} + +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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + bio_reset_template(t); + + e->remaining_samples = 0; + e->last_status = 0; + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + return (r); + } + if ((r = cbor_parse_reply(reply, (size_t)reply_len, &t->id, + bio_parse_template_id)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_id", __func__); + return (r); + } + + return (FIDO_OK); +} + +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_uint32(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token)) != 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 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)) != 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)) != 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, -1)); +} + +static int +bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int ms) +{ + unsigned char reply[FIDO_MAXMSG]; + int reply_len; + int r; + + e->remaining_samples = 0; + e->last_status = 0; + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + return (r); + } + + return (FIDO_OK); +} + +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_uint32(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token)) != 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) +{ + if (e->token == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_enroll_continue_wait(dev, t, e, timo_ms, -1)); +} + +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)) != 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) +{ + return (bio_enroll_cancel_wait(dev, -1)); +} + +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)) != 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) +{ + return (bio_enroll_remove_wait(dev, t, pin, -1)); +} + +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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + bio_reset_info(i); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, i, + bio_parse_info)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_info" , __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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) +{ + return (bio_get_info_wait(dev, i, -1)); +} + +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 000000000000..31e4cab0edc4 --- /dev/null +++ b/src/blob.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "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 000000000000..76a8dd994f22 --- /dev/null +++ b/src/blob.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _BLOB_H +#define _BLOB_H + +#include +#include + +#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 000000000000..f7161e64a9ca --- /dev/null +++ b/src/buf.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "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 000000000000..5c1b11583e7b --- /dev/null +++ b/src/cbor.c @@ -0,0 +1,1635 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include "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); +} + +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 (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); + } + } + + 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; +#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX ctx; +#else + HMAC_CTX *ctx = NULL; +#endif + 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 OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX_init(&ctx); + + if ((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; + } +#else + 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; + } +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + + 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: +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (ctx != NULL) + HMAC_CTX_free(ctx); +#endif + + 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; + 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; + } + + /* 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 || + (argv[3] = cbor_encode_pin_opt(dev)) == 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(¶m); + 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")) { + 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_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_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_isa_float_ctrl(val) == false || + cbor_float_get_width(val) != CBOR_FLOAT_0 || + cbor_is_bool(val) == false) { + fido_log_debug("%s: cbor type", __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_isa_float_ctrl(val) == false || + cbor_float_get_width(val) != CBOR_FLOAT_0 || + cbor_is_bool(val) == false) { + fido_log_debug("%s: cbor type", __func__); + goto out; + } + if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE) + authdata_ext->mask |= FIDO_EXT_CRED_BLOB; + } + + 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 cose_alg = 0; + 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; + } + if ((cose_alg = -(int)cbor_get_int(val) - 1) != COSE_ES256 && + cose_alg != COSE_RS256 && cose_alg != COSE_EDDSA) { + fido_log_debug("%s: unsupported cose_alg=%d", __func__, + cose_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; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_decode_attstmt(const cbor_item_t *item, fido_attstmt_t *attstmt) +{ + 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); + } + + 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); +} + +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 000000000000..ee5501b4a4a1 --- /dev/null +++ b/src/compress.c @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#include +#include "fido.h" + +#define BOUND (1024UL * 1024UL) + +static int +do_compress(fido_blob_t *out, const fido_blob_t *in, size_t origsiz, int decomp) +{ + u_long ilen, olen; + int r; + + memset(out, 0, sizeof(*out)); + if (in->len > ULONG_MAX || (ilen = (u_long)in->len) > BOUND || + origsiz > ULONG_MAX || (olen = decomp ? (u_long)origsiz : + compressBound(ilen)) > BOUND) + return FIDO_ERR_INVALID_ARGUMENT; + if ((out->ptr = calloc(1, olen)) == NULL) + return FIDO_ERR_INTERNAL; + out->len = olen; + if (decomp) + r = uncompress(out->ptr, &olen, in->ptr, ilen); + else + r = compress(out->ptr, &olen, in->ptr, ilen); + if (r != Z_OK || olen > SIZE_MAX || olen > out->len) { + fido_blob_reset(out); + return FIDO_ERR_COMPRESS; + } + out->len = olen; + + return FIDO_OK; +} + +int +fido_compress(fido_blob_t *out, const fido_blob_t *in) +{ + return do_compress(out, in, 0, 0); +} + +int +fido_uncompress(fido_blob_t *out, const fido_blob_t *in, size_t origsiz) +{ + return do_compress(out, in, origsiz, 1); +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 000000000000..0dda16163bc8 --- /dev/null +++ b/src/config.c @@ -0,0 +1,191 @@ +/* + * 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. + */ + +#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; + + memset(prefix, 0xff, sizeof(prefix)); + prefix[sizeof(prefix) - 2] = CTAP_CBOR_CONFIG; + prefix[sizeof(prefix) - 1] = subcmd; + + 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) +{ + 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; + } + + /* pinProtocol, pinAuth */ + if (pin != NULL || (fido_dev_supports_permissions(dev) && + fido_dev_has_uv(dev))) { + if ((argv[1] = cbor_flatten_vector(paramv, paramc)) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + goto fail; + } + 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)) != 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])) != 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) < 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)) != FIDO_OK) + return r; + + return fido_rx_cbor_status(dev, ms); +} + +int +fido_dev_enable_entattest(fido_dev_t *dev, const char *pin) +{ + return (config_enable_entattest_wait(dev, pin, -1)); +} + +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)) != FIDO_OK) + return r; + + return (fido_rx_cbor_status(dev, ms)); +} + +int +fido_dev_toggle_always_uv(fido_dev_t *dev, const char *pin) +{ + return config_toggle_always_uv_wait(dev, pin, -1); +} + +static int +config_pin_minlen_tx(fido_dev_t *dev, size_t len, bool force, const char *pin) +{ + cbor_item_t *argv[3]; + int r; + + memset(argv, 0, sizeof(argv)); + + if ((!len && !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 (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)) != 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 char *pin, + int ms) +{ + int r; + + if ((r = config_pin_minlen_tx(dev, len, force, pin)) != 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) +{ + return config_pin_minlen(dev, len, false, pin, -1); +} + +int +fido_dev_force_pin_change(fido_dev_t *dev, const char *pin) +{ + return config_pin_minlen(dev, 0, true, pin, -1); +} diff --git a/src/cred.c b/src/cred.c new file mode 100644 index 000000000000..5e65b08293b1 --- /dev/null +++ b/src/cred.c @@ -0,0 +1,1086 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include + +#include "fido.h" +#include "fido/es256.h" + +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) +{ + 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)) != 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])) != 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) < 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[FIDO_MAXMSG]; + int reply_len; + int r; + + fido_cred_reset_rx(cred); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, cred, + parse_makecred_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_makecred_reply", __func__); + return (r); + } + + if (cred->fmt == NULL || fido_blob_is_empty(&cred->authdata_cbor) || + fido_blob_is_empty(&cred->attcred.id)) { + fido_cred_reset_rx(cred); + return (FIDO_ERR_INVALID_CBOR); + } + + return (FIDO_OK); +} + +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)) != 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) +{ +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_make_cred(dev, cred, pin)); +#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, -1)); + } + + return (fido_dev_make_cred_wait(dev, cred, pin, -1)); +} + +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 */ + SHA256_CTX ctx; + + if (dgst->len != SHA256_DIGEST_LENGTH || SHA256_Init(&ctx) == 0 || + SHA256_Update(&ctx, &zero, sizeof(zero)) == 0 || + SHA256_Update(&ctx, rp_id, rp_id_len) == 0 || + SHA256_Update(&ctx, clientdata->ptr, clientdata->len) == 0 || + SHA256_Update(&ctx, id->ptr, id->len) == 0 || + SHA256_Update(&ctx, &four, sizeof(four)) == 0 || + SHA256_Update(&ctx, pk->x, sizeof(pk->x)) == 0 || + SHA256_Update(&ctx, pk->y, sizeof(pk->y)) == 0 || + SHA256_Final(dgst->ptr, &ctx) == 0) { + fido_log_debug("%s: sha256", __func__); + return (-1); + } + + return (0); +} + +static int +verify_sig(const fido_blob_t *dgst, const fido_blob_t *x5c, + const fido_blob_t *sig) +{ + BIO *rawcert = NULL; + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; + EC_KEY *ec; + int ok = -1; + + /* openssl needs ints */ + if (dgst->len > INT_MAX || x5c->len > INT_MAX || sig->len > INT_MAX) { + fido_log_debug("%s: dgst->len=%zu, x5c->len=%zu, sig->len=%zu", + __func__, dgst->len, x5c->len, sig->len); + return (-1); + } + + /* fetch key from x509 */ + if ((rawcert = BIO_new_mem_buf(x5c->ptr, (int)x5c->len)) == NULL || + (cert = d2i_X509_bio(rawcert, NULL)) == NULL || + (pkey = X509_get_pubkey(cert)) == NULL || + (ec = EVP_PKEY_get0_EC_KEY(pkey)) == NULL) { + fido_log_debug("%s: x509 key", __func__); + goto fail; + } + + if (ECDSA_verify(0, dgst->ptr, (int)dgst->len, sig->ptr, + (int)sig->len, ec) != 1) { + fido_log_debug("%s: ECDSA_verify", __func__); + goto fail; + } + + ok = 0; +fail: + if (rawcert != NULL) + BIO_free(rawcert); + if (cert != NULL) + X509_free(cert); + if (pkey != NULL) + EVP_PKEY_free(pkey); + + return (ok); +} + +int +fido_cred_verify(const fido_cred_t *cred) +{ + unsigned char buf[SHA256_DIGEST_LENGTH]; + fido_blob_t dgst; + 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(COSE_ES256, &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; + } + + if (verify_sig(&dgst, &cred->attstmt.x5c, &cred->attstmt.sig) < 0) { + fido_log_debug("%s: verify_sig", __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 = fido_verify_sig_es256(&dgst, &cred->attcred.pubkey.es256, + &cred->attstmt.sig); + break; + case COSE_RS256: + ok = fido_verify_sig_rs256(&dgst, &cred->attcred.pubkey.rs256, + &cred->attstmt.sig); + break; + case COSE_EDDSA: + ok = fido_verify_sig_eddsa(&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)); +} + +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_free_blob_array(&cred->excl); + + memset(&cred->rp, 0, sizeof(cred->rp)); + memset(&cred->user, 0, sizeof(cred->user)); + memset(&cred->excl, 0, sizeof(cred->excl)); + 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_blob_reset(&cred->attstmt.x5c); + fido_blob_reset(&cred->attstmt.sig); + 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_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_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_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")) + 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 ((cose_alg != COSE_ES256 && cose_alg != COSE_RS256 && + cose_alg != COSE_EDDSA) || cred->type != 0) + 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_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_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_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); +} + +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 000000000000..e48ca4543b10 --- /dev/null +++ b/src/credman.c @@ -0,0 +1,767 @@ +/* + * 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. + */ + +#include + +#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, 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(¶m_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) +{ + 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)) != 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])) != 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) < 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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + memset(metadata, 0, sizeof(*metadata)); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, metadata, + credman_parse_metadata)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_metadata", __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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) +{ + return (credman_get_metadata_wait(dev, metadata, pin, -1)); +} + +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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + credman_reset_rk(rk); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(reply, (size_t)reply_len, rk, + credman_parse_rk_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk_count", __func__); + return (r); + } + + if (rk->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + return (FIDO_OK); + } + + /* parse the first rk */ + if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rk->ptr[0], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + return (r); + } + + rk->n_rx++; + + return (FIDO_OK); +} + +static int +credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int ms) +{ + unsigned char reply[FIDO_MAXMSG]; + int reply_len; + int r; + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + /* 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); + return (FIDO_ERR_INTERNAL); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rk->ptr[rk->n_rx], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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)) != 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) +{ + return (credman_get_rk_wait(dev, rp_id, rk, pin, -1)); +} + +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)) != 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) +{ + return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, -1)); +} + +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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + credman_reset_rp(rp); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(reply, (size_t)reply_len, rp, + credman_parse_rp_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp_count", __func__); + return (r); + } + + if (rp->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + return (FIDO_OK); + } + + /* parse the first rp */ + if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rp->ptr[0], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + return (r); + } + + rp->n_rx++; + + return (FIDO_OK); +} + +static int +credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int ms) +{ + unsigned char reply[FIDO_MAXMSG]; + int reply_len; + int r; + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + /* 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); + return (FIDO_ERR_INTERNAL); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, &rp->ptr[rp->n_rx], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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)) != 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) +{ + return (credman_get_rp_wait(dev, rp, pin, -1)); +} + +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)) != 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) +{ + return (credman_set_dev_rk_wait(dev, cred, pin, -1)); +} + +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 000000000000..a003854f89d2 --- /dev/null +++ b/src/dev.c @@ -0,0 +1,732 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include "fido.h" + +#ifndef TLS +#define TLS +#endif + +typedef struct dev_manifest_func_node { + dev_manifest_func_t manifest_func; + struct dev_manifest_func_node *next; +} dev_manifest_func_node_t; + +static TLS dev_manifest_func_node_t *manifest_funcs = NULL; +static TLS bool disable_u2f_fallback; + +static void +find_manifest_func_node(dev_manifest_func_t f, dev_manifest_func_node_t **curr, + dev_manifest_func_node_t **prev) +{ + *prev = NULL; + *curr = manifest_funcs; + + while (*curr != NULL && (*curr)->manifest_func != f) { + *prev = *curr; + *curr = (*curr)->next; + } +} + +#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 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)) < 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)) != FIDO_OK || + (r = fido_dev_open_rx(dev, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_dev_register_manifest_func(const dev_manifest_func_t f) +{ + dev_manifest_func_node_t *prev, *curr, *n; + + find_manifest_func_node(f, &curr, &prev); + if (curr != NULL) + return (FIDO_OK); + + if ((n = calloc(1, sizeof(*n))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return (FIDO_ERR_INTERNAL); + } + + n->manifest_func = f; + n->next = manifest_funcs; + manifest_funcs = n; + + return (FIDO_OK); +} + +void +fido_dev_unregister_manifest_func(const dev_manifest_func_t f) +{ + dev_manifest_func_node_t *prev, *curr; + + find_manifest_func_node(f, &curr, &prev); + if (curr == NULL) + return; + if (prev != NULL) + prev->next = curr->next; + else + manifest_funcs = curr->next; + + free(curr); +} + +int +fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + dev_manifest_func_node_t *curr = NULL; + dev_manifest_func_t m_func; + size_t curr_olen; + int r; + + *olen = 0; + + if (fido_dev_register_manifest_func(fido_hid_manifest) != FIDO_OK) + return (FIDO_ERR_INTERNAL); +#ifdef NFC_LINUX + if (fido_dev_register_manifest_func(fido_nfc_manifest) != FIDO_OK) + return (FIDO_ERR_INTERNAL); +#endif +#ifdef USE_WINHELLO + if (fido_dev_register_manifest_func(fido_winhello_manifest) != FIDO_OK) + return (FIDO_ERR_INTERNAL); +#endif + + for (curr = manifest_funcs; curr != NULL; curr = curr->next) { + curr_olen = 0; + m_func = curr->manifest_func; + r = m_func(devlist + *olen, ilen - *olen, &curr_olen); + if (r != FIDO_OK) + return (r); + *olen += curr_olen; + if (*olen == ilen) + break; + } + + return (FIDO_OK); +} + +int +fido_dev_open_with_info(fido_dev_t *dev) +{ + if (dev->path == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (fido_dev_open_wait(dev, dev->path, -1)); +} + +int +fido_dev_open(fido_dev_t *dev, const char *path) +{ +#ifdef NFC_LINUX + /* + * this is a hack to get existing applications up and running with nfc; + * it will *NOT* be part of a libfido2 release. to support nfc in your + * application, please change it to use fido_dev_open_with_info(). + */ + if (strncmp(path, "/sys", strlen("/sys")) == 0 && strlen(path) > 4 && + path[strlen(path) - 4] == 'n' && path[strlen(path) - 3] == 'f' && + path[strlen(path) - 2] == 'c') { + dev->io_own = true; + dev->io = (fido_dev_io_t) { + fido_nfc_open, + fido_nfc_close, + fido_nfc_read, + fido_nfc_write, + }; + dev->transport = (fido_dev_transport_t) { + fido_nfc_rx, + fido_nfc_tx, + }; + } +#endif + + return (fido_dev_open_wait(dev, path, -1)); +} + +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_own || dev->io_handle == NULL || sigmask == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + +#ifdef NFC_LINUX + if (dev->transport.rx == fido_nfc_rx) + return (fido_nfc_set_sigmask(dev->io_handle, sigmask)); +#endif + return (fido_hid_set_sigmask(dev->io_handle, sigmask)); +} + +int +fido_dev_cancel(fido_dev_t *dev) +{ +#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) < 0) + return (FIDO_ERR_TX); + + return (FIDO_OK); +} + +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 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)); + + 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) < 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); +} + +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_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->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; + + 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); +} diff --git a/src/diff_exports.sh b/src/diff_exports.sh new file mode 100755 index 000000000000..9cff0095a201 --- /dev/null +++ b/src/diff_exports.sh @@ -0,0 +1,26 @@ +#!/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. + +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 000000000000..3ea47ae6457e --- /dev/null +++ b/src/ecdh.c @@ -0,0 +1,207 @@ +/* + * 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. + */ + +#include +#include +#if defined(LIBRESSL_VERSION_NUMBER) +#include +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L +#include +#endif + +#include "fido.h" +#include "fido/es256.h" + +#if defined(LIBRESSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L +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, 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) || OPENSSL_VERSION_NUMBER < 0x10100000L */ + +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) +{ + 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) != 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 000000000000..89b84c5a6bd4 --- /dev/null +++ b/src/eddsa.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include + +#include "fido.h" +#include "fido/eddsa.h" + +#if defined(LIBRESSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10101000L +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); +} + +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 || OPENSSL_VERSION_NUMBER < 0x10101000L */ + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +EVP_MD_CTX * +EVP_MD_CTX_new(void) +{ + fido_log_debug("%s: unimplemented", __func__); + + return (NULL); +} + +void +EVP_MD_CTX_free(EVP_MD_CTX *ctx) +{ + (void)ctx; +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + +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) +{ + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + memcpy(pk, ptr, sizeof(*pk)); + + 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_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); +} diff --git a/src/err.c b/src/err.c new file mode 100644 index 000000000000..8c2ae5ff4170 --- /dev/null +++ b/src/err.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "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 000000000000..9cdb48e4832d --- /dev/null +++ b/src/es256.c @@ -0,0 +1,453 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include + +#include "fido.h" +#include "fido/es256.h" + +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; + + 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 */ + + 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; + const int nid = NID_X9_62_prime256v1; + 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, 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; + const int nid = NID_X9_62_prime256v1; + 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)) == 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; + const EC_GROUP *g = NULL; + int ok = FIDO_ERR_INTERNAL; + int n; + + if ((q = EC_KEY_get0_public_key(ec)) == NULL || + (g = EC_KEY_get0_group(ec)) == 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_get_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 || + (n = BN_num_bytes(x)) < 0 || (size_t)n > sizeof(pk->x) || + (n = BN_num_bytes(y)) < 0 || (size_t)n > sizeof(pk->y)) { + fido_log_debug("%s: EC_POINT_get_affine_coordinates_GFp", + __func__); + goto fail; + } + + if ((n = BN_bn2bin(x, pk->x)) < 0 || (size_t)n > sizeof(pk->x) || + (n = BN_bn2bin(y, pk->y)) < 0 || (size_t)n > sizeof(pk->y)) { + fido_log_debug("%s: BN_bn2bin", __func__); + goto fail; + } + + ok = FIDO_OK; +fail: + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + return (ok); +} + +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; + const int nid = NID_X9_62_prime256v1; + 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(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; + const int nid = NID_X9_62_prime256v1; + int ok = -1; + + if ((d = BN_bin2bn(sk->d, (int)sizeof(sk->d), NULL)) == NULL || + (ec = EC_KEY_new_by_curve_name(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); +} diff --git a/src/export.gnu b/src/export.gnu new file mode 100644 index 000000000000..40dc7915e6e2 --- /dev/null +++ b/src/export.gnu @@ -0,0 +1,234 @@ +{ + 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_ptr; + es256_pk_new; + es256_pk_to_EVP_PKEY; + fido_assert_allow_cred; + fido_assert_authdata_len; + fido_assert_authdata_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_extensions_len; + fido_cbor_info_extensions_ptr; + fido_cbor_info_free; + fido_cbor_info_maxmsgsiz; + fido_cbor_info_maxcredbloblen; + fido_cbor_info_maxcredcntlst; + fido_cbor_info_maxcredidlen; + fido_cbor_info_fwversion; + fido_cbor_info_new; + 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_transports_len; + fido_cbor_info_transports_ptr; + fido_cbor_info_versions_len; + fido_cbor_info_versions_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_prot; + fido_cred_pubkey_len; + fido_cred_pubkey_ptr; + fido_cred_rp_id; + fido_cred_rp_name; + 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_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_vendor; + fido_dev_is_fido2; + fido_dev_is_winhello; + 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_pin; + fido_dev_set_pin_minlen; + fido_dev_set_sigmask; + 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_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 000000000000..8d3810f92ce9 --- /dev/null +++ b/src/export.llvm @@ -0,0 +1,229 @@ +_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_ptr +_es256_pk_new +_es256_pk_to_EVP_PKEY +_fido_assert_allow_cred +_fido_assert_authdata_len +_fido_assert_authdata_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_extensions_len +_fido_cbor_info_extensions_ptr +_fido_cbor_info_free +_fido_cbor_info_maxmsgsiz +_fido_cbor_info_maxcredbloblen +_fido_cbor_info_maxcredcntlst +_fido_cbor_info_maxcredidlen +_fido_cbor_info_fwversion +_fido_cbor_info_new +_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_transports_len +_fido_cbor_info_transports_ptr +_fido_cbor_info_versions_len +_fido_cbor_info_versions_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_prot +_fido_cred_pubkey_len +_fido_cred_pubkey_ptr +_fido_cred_rp_id +_fido_cred_rp_name +_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_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_vendor +_fido_dev_is_fido2 +_fido_dev_is_winhello +_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_pin +_fido_dev_set_pin_minlen +_fido_dev_set_sigmask +_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_RSA +_rs256_pk_new +_rs256_pk_to_EVP_PKEY diff --git a/src/export.msvc b/src/export.msvc new file mode 100644 index 000000000000..ca4971dec2d1 --- /dev/null +++ b/src/export.msvc @@ -0,0 +1,230 @@ +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_ptr +es256_pk_new +es256_pk_to_EVP_PKEY +fido_assert_allow_cred +fido_assert_authdata_len +fido_assert_authdata_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_extensions_len +fido_cbor_info_extensions_ptr +fido_cbor_info_free +fido_cbor_info_maxmsgsiz +fido_cbor_info_maxcredbloblen +fido_cbor_info_maxcredcntlst +fido_cbor_info_maxcredidlen +fido_cbor_info_fwversion +fido_cbor_info_new +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_transports_len +fido_cbor_info_transports_ptr +fido_cbor_info_versions_len +fido_cbor_info_versions_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_prot +fido_cred_pubkey_len +fido_cred_pubkey_ptr +fido_cred_rp_id +fido_cred_rp_name +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_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_vendor +fido_dev_is_fido2 +fido_dev_is_winhello +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_pin +fido_dev_set_pin_minlen +fido_dev_set_sigmask +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_RSA +rs256_pk_new +rs256_pk_to_EVP_PKEY diff --git a/src/extern.h b/src/extern.h new file mode 100644 index 000000000000..3be33236f2b1 --- /dev/null +++ b/src/extern.h @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _EXTERN_H +#define _EXTERN_H + +#ifdef __MINGW32__ +#include +#endif + +#ifdef HAVE_SIGNAL_H +#include +#endif + +#include + +#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_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_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 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 **); +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 */ +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 *); + +/* 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 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 *); + +/* 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); + +/* 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 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 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 *); +uint64_t fido_dev_maxmsgsize(const fido_dev_t *); +int fido_do_ecdh(fido_dev_t *, es256_pk_t **, fido_blob_t **); +bool fido_dev_supports_permissions(const fido_dev_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); + +/* crypto */ +int fido_verify_sig_es256(const fido_blob_t *, const es256_pk_t *, + const fido_blob_t *); +int fido_verify_sig_rs256(const fido_blob_t *, const rs256_pk_t *, + const fido_blob_t *); +int fido_verify_sig_eddsa(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 *); + +/* 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 *); + +/* device manifest registration */ +typedef int (*dev_manifest_func_t)(fido_dev_info_t *, size_t, size_t *); +int fido_dev_register_manifest_func(const dev_manifest_func_t); +void fido_dev_unregister_manifest_func(const dev_manifest_func_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" + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_EXTERN_H */ diff --git a/src/fido.h b/src/fido.h new file mode 100644 index 000000000000..d5446516f972 --- /dev/null +++ b/src/fido.h @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _FIDO_H +#define _FIDO_H + +#include +#include + +#include +#include +#include + +#ifdef _FIDO_INTERNAL +#include + +#include +#include + +#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_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_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_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 unsigned char *fido_cbor_info_aaguid_ptr(const fido_cbor_info_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_aaguid_ptr(const fido_cred_t *); +const unsigned char *fido_cred_user_id_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_x5c_ptr(const fido_cred_t *); +const unsigned char *fido_cred_largeblob_key_ptr(const fido_cred_t *); + +int fido_assert_allow_cred(fido_assert_t *, const unsigned char *, size_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_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_exclude(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_prot(const fido_cred_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_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 *); +int fido_dev_set_sigmask(fido_dev_t *, const fido_sigset_t *); +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_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 *); + +size_t fido_assert_authdata_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_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_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_aaguid_len(const fido_cred_t *); +size_t fido_cred_user_id_len(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_x5c_len(const fido_cred_t *); +size_t fido_cred_largeblob_key_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_maxmsgsiz(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_fwversion(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_pin(const fido_dev_t *); +bool fido_dev_supports_cred_prot(const fido_dev_t *); +bool fido_dev_supports_credman(const fido_dev_t *); +bool fido_dev_supports_uv(const fido_dev_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 000000000000..afe9ca4752b4 --- /dev/null +++ b/src/fido/bio.h @@ -0,0 +1,111 @@ +/* + * 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. + */ + +#ifndef _FIDO_BIO_H +#define _FIDO_BIO_H + +#include +#include + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include +#include +#include +#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 000000000000..869927df914b --- /dev/null +++ b/src/fido/config.h @@ -0,0 +1,34 @@ +/* + * 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. + */ + +#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 +#include +#include +#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 *); + +#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 000000000000..66a966970501 --- /dev/null +++ b/src/fido/credman.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +#ifndef _FIDO_CREDMAN_H +#define _FIDO_CREDMAN_H + +#include +#include + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include +#include +#include +#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 000000000000..4a810179b6fa --- /dev/null +++ b/src/fido/eddsa.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#ifndef _FIDO_EDDSA_H +#define _FIDO_EDDSA_H + +#include + +#include +#include + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include +#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) || OPENSSL_VERSION_NUMBER < 0x10101000L +#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 || OPENSSL_VERSION_NUMBER < 0x10101000L */ + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +EVP_MD_CTX *EVP_MD_CTX_new(void); +void EVP_MD_CTX_free(EVP_MD_CTX *); +#endif + +#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 000000000000..74fdf9d2bfe8 --- /dev/null +++ b/src/fido/err.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _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 000000000000..80f4db39c7b0 --- /dev/null +++ b/src/fido/es256.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _FIDO_ES256_H +#define _FIDO_ES256_H + +#include + +#include +#include + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include +#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_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/param.h b/src/fido/param.h new file mode 100644 index 000000000000..025bb57dd81c --- /dev/null +++ b/src/fido/param.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _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_ES256 -7 +#define COSE_EDDSA -8 +#define COSE_ECDH_ES256 -25 +#define COSE_RS256 -257 + +/* 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_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 + +/* 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) +#endif /* _FIDO_INTERNAL */ + +#endif /* !_FIDO_PARAM_H */ diff --git a/src/fido/rs256.h b/src/fido/rs256.h new file mode 100644 index 000000000000..2b08d59980c1 --- /dev/null +++ b/src/fido/rs256.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _FIDO_RS256_H +#define _FIDO_RS256_H + +#include + +#include +#include + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include +#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_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 000000000000..00b6058c7e13 --- /dev/null +++ b/src/fido/types.h @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _FIDO_TYPES_H +#define _FIDO_TYPES_H + +#ifdef __MINGW32__ +#include +#endif + +#include +#include +#include + +#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 *); + +#ifdef _WIN32 +typedef int fido_sigset_t; +#else +typedef sigset_t fido_sigset_t; +#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 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; + rs256_pk_t rs256; + eddsa_pk_t eddsa; + } pubkey; +} fido_attcred_t; + +typedef struct fido_attstmt { + fido_blob_t x5c; /* attestation certificate */ + fido_blob_t sig; /* attestation signature */ +} 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 */ +} 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; /* FIDO 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 FIDO 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_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 */ + 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_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 number of credentials in list */ + uint64_t maxcredidlen; /* max credential ID length */ + uint64_t fwversion; /* firmware version */ + uint64_t maxcredbloblen; /* max credBlob length */ +} 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 */ +} 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 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 000000000000..a3768ad3cae8 --- /dev/null +++ b/src/hid.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "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))); +} + +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++) { + const fido_dev_info_t *di; + di = &devlist[i]; + free(di->path); + free(di->manufacturer); + free(di->product); + } + + 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]); +} + +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 000000000000..86c1854e9c8c --- /dev/null +++ b/src/hid_freebsd.c @@ -0,0 +1,253 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include +#include + +#include "fido.h" + +#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); +} + +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__); + strlcpy(udi.udi_vendor, "FreeBSD", 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); + + 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); +} + +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); + + 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 000000000000..f6d21711e152 --- /dev/null +++ b/src/hid_hidapi.c @@ -0,0 +1,268 @@ +/* + * 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. + */ + +#ifdef __linux__ +#include +#include +#include +#include +#endif + +#include +#include +#include + +#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; + + memset(&hrd, 0, sizeof(hrd)); + + if (get_report_descriptor(hdi->path, &hrd) < 0 || + fido_hid_get_usage(hrd.value, hrd.size, &usage_page) < 0) { + return false; + } + + 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 000000000000..c622880a2594 --- /dev/null +++ b/src/hid_linux.c @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#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; + uint32_t usage_page = 0; + struct hidraw_report_descriptor hrd; + + memset(&hrd, 0, sizeof(hrd)); + + if ((fd = fido_hid_unix_open(path)) == -1) + return (false); + + if (get_report_descriptor(fd, &hrd) < 0 || + fido_hid_get_usage(hrd.value, hrd.size, &usage_page) < 0) + usage_page = 0; + + if (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("unknown"); + if ((di->product = get_usb_attr(dev, "product")) == NULL) + di->product = strdup("unknown"); + 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; + + 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); + } + if (retries++ >= 15) { + 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 (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; + } + + 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 000000000000..c24c6de7ce29 --- /dev/null +++ b/src/hid_netbsd.c @@ -0,0 +1,338 @@ +/* + * 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. + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 000000000000..fbf10fd11ab9 --- /dev/null +++ b/src/hid_openbsd.c @@ -0,0 +1,260 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "fido.h" + +#define MAX_UHID 64 + +struct hid_openbsd { + int fd; + size_t report_in_len; + size_t report_out_len; +}; + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + size_t i; + char path[64]; + int fd; + struct usb_device_info udi; + fido_dev_info_t *di; + + 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 ((fd = fido_hid_unix_open(path)) == -1) + continue; + memset(&udi, 0, sizeof(udi)); + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + fido_log_error(errno, "%s: get device info %s", + __func__, path); + if (close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + continue; + } + if (close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + 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); + + di = &devlist[*olen]; + memset(di, 0, sizeof(*di)); + di->io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + return FIDO_ERR_INTERNAL; + } + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + (*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) +{ + (void)handle; + (void)sigmask; + + return (FIDO_ERR_INTERNAL); +} + +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; + + (void)ms; /* XXX */ + + 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 ((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 000000000000..e9866658a4eb --- /dev/null +++ b/src/hid_osx.c @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "fido.h" + +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) { + fido_log_debug("%s: get_utf8 manufacturer", __func__); + goto fail; + } + + if ((*manufacturer = strdup(buf)) == NULL) { + fido_log_debug("%s: strdup manufacturer", __func__); + goto fail; + } + + if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0) { + fido_log_debug("%s: get_utf8 product", __func__); + goto fail; + } + + if ((*product = strdup(buf)) == NULL) { + fido_log_debug("%s: strdup product", __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; + io_string_t path; + + if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) { + fido_log_debug("%s: IOHIDDeviceGetService", __func__); + return (NULL); + } + + if (IORegistryEntryGetPath(s, kIOServicePlane, path) != KERN_SUCCESS) { + fido_log_debug("%s: IORegistryEntryGetPath", __func__); + return (NULL); + } + + return (strdup(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); +} + +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 = IORegistryEntryFromPath(kIOMasterPortDefault, + path)) == MACH_PORT_NULL) { + fido_log_debug("%s: IORegistryEntryFromPath", __func__); + 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 000000000000..4b2aff9d67f6 --- /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. + */ + +#include + +#include +#include +#include +#include + +#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 + if (ms < 0) + 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 000000000000..455cf8bae835 --- /dev/null +++ b/src/hid_win.c @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include + +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#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__) +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_int(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_str(HANDLE dev, char **manufacturer, char **product) +{ + wchar_t buf[512]; + int utf8_len; + int ok = -1; + + *manufacturer = NULL; + *product = 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; + } + + 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(*manufacturer); + free(*product); + *manufacturer = NULL; + *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_int(dev, &di->vendor_id, &di->product_id) < 0 || + get_str(dev, &di->manufacturer, &di->product) < 0) { + fido_log_debug("%s: get_int/get_str", __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__, 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 000000000000..57bc8de44063 --- /dev/null +++ b/src/info.c @@ -0,0 +1,553 @@ +/* + * 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. + */ + +#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_isa_float_ctrl(val) == false || + cbor_float_get_width(val) != CBOR_FLOAT_0 || + cbor_is_bool(val) == false) { + fido_log_debug("%s: cbor type", __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 void +free_algo(fido_algo_t *a) +{ + free(a->type); + a->type = NULL; + a->cose = 0; +} + +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__); + free_algo(&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 +parse_reply_element(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cbor_info_t *ci = 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: /* 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 14: /* fwVersion */ + return (cbor_decode_uint64(val, &ci->fwversion)); + case 15: /* maxCredBlobLen */ + return (cbor_decode_uint64(val, &ci->maxcredbloblen)); + default: /* ignore */ + fido_log_debug("%s: cbor type", __func__); + return (0); + } +} + +static int +fido_dev_get_cbor_info_tx(fido_dev_t *dev) +{ + 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)) < 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 reply[FIDO_MAXMSG]; + int reply_len; + + fido_log_debug("%s: dev=%p, ci=%p, ms=%d", __func__, (void *)dev, + (void *)ci, ms); + + fido_cbor_info_reset(ci); + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + return (cbor_parse_reply(reply, (size_t)reply_len, ci, + parse_reply_element)); +} + +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)) != 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) +{ + return (fido_dev_get_cbor_info_wait(dev, ci, -1)); +} + +/* + * get/set functions for fido_cbor_info_t; always at the end of the file + */ + +fido_cbor_info_t * +fido_cbor_info_new(void) +{ + return (calloc(1, sizeof(fido_cbor_info_t))); +} + +static void +free_str_array(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; +} + +static void +free_opt_array(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; +} + +static void +free_byte_array(fido_byte_array_t *ba) +{ + free(ba->ptr); + + ba->ptr = NULL; + ba->len = 0; +} + +static void +free_algo_array(fido_algo_array_t *aa) +{ + for (size_t i = 0; i < aa->len; i++) + free_algo(&aa->ptr[i]); + + free(aa->ptr); + aa->ptr = NULL; + aa->len = 0; +} + +void +fido_cbor_info_reset(fido_cbor_info_t *ci) +{ + free_str_array(&ci->versions); + free_str_array(&ci->extensions); + free_str_array(&ci->transports); + free_opt_array(&ci->options); + free_byte_array(&ci->protocols); + free_algo_array(&ci->algorithms); +} + +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_fwversion(const fido_cbor_info_t *ci) +{ + return (ci->fwversion); +} + +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); +} diff --git a/src/io.c b/src/io.c new file mode 100644 index 000000000000..e2594203efb0 --- /dev/null +++ b/src/io.c @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "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_empty(fido_dev_t *d, uint8_t cmd) +{ + 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 = d->io.write(d->io_handle, pkt, + len)) < 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) +{ + 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 = d->io.write(d->io_handle, pkt, + len)) < 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) +{ + 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 = d->io.write(d->io_handle, pkt, + len)) < 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) +{ + size_t n, sent; + + if ((sent = tx_preamble(d, cmd, buf, count)) == 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)) == 0) { + fido_log_debug("%s: tx_frame", __func__); + return (-1); + } + } + + return (0); +} + +int +fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count) +{ + 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 (d->transport.tx(d, cmd, buf, count)); + 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) : tx(d, cmd, buf, count)); +} + +static int +rx_frame(fido_dev_t *d, struct frame *fp, int ms) +{ + int n; + + memset(fp, 0, sizeof(*fp)); + + 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 (0); +} + +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); +} + +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 (d->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 reply[FIDO_MAXMSG]; + int reply_len; + + if ((reply_len = fido_rx(d, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0 || (size_t)reply_len < 1) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + return (reply[0]); +} diff --git a/src/iso7816.c b/src/iso7816.c new file mode 100644 index 000000000000..a11aae3e99d1 --- /dev/null +++ b/src/iso7816.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#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 - sizeof(apdu->alloc_len) - + sizeof(apdu->payload_len) - sizeof(apdu->payload_ptr); +} diff --git a/src/iso7816.h b/src/iso7816.h new file mode 100644 index 000000000000..5f5363a63a56 --- /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. + */ + +#ifndef _ISO7816_H +#define _ISO7816_H + +#include +#include + +#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; +}) + +PACKED_TYPE(iso7816_apdu_t, +struct iso7816_apdu { + size_t alloc_len; + uint16_t payload_len; + uint8_t *payload_ptr; + iso7816_header_t header; + uint8_t payload[]; +}) + +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 000000000000..fa453f5de33a --- /dev/null +++ b/src/largeblob.c @@ -0,0 +1,881 @@ +/* + * 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. + */ + +#include + +#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) +{ + 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) < 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 reply[FIDO_MAXMSG]; + int reply_len, r; + + *chunk = NULL; + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return FIDO_ERR_RX; + } + if ((*chunk = fido_blob_new()) == NULL) { + fido_log_debug("%s: fido_blob_new", __func__); + return FIDO_ERR_INTERNAL; + } + if ((r = cbor_parse_reply(reply, (size_t)reply_len, *chunk, + parse_largeblob_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_largeblob_reply", __func__); + fido_blob_free(chunk); + return r; + } + + return FIDO_OK; +} + +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) +{ + 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)) != FIDO_OK || + (r = largeblob_get_rx(dev, &chunk, -1)) != 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) +{ + 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) < 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) +{ + 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)) != 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)) != 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) +{ + 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)) != 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)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, -1)) != FIDO_OK) { + fido_log_debug("%s: body", __func__); + goto fail; + } + } + if ((r = largeblob_set_tx(dev, token, dgst, sizeof(dgst) - 16, cbor.len, + totalsize)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, -1)) != 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) +{ + cbor_item_t *array = NULL; + size_t idx; + int r; + + if ((r = largeblob_get_array(dev, &array)) != 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)) != 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) +{ + cbor_item_t *array = NULL; + size_t idx; + int r; + + if ((r = largeblob_get_array(dev, &array)) != 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)) != 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 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)) != 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 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)) != 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 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)) != 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 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)) != 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 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)) != 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 000000000000..03d060667954 --- /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 000000000000..ab18ae12b4fa --- /dev/null +++ b/src/log.c @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#undef _GNU_SOURCE /* XSI strerror_r() */ + +#include +#include + +#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 000000000000..6fd9f63cb937 --- /dev/null +++ b/src/netlink.c @@ -0,0 +1,782 @@ +/* + * 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. + */ + +#include + +#include +#include +#include + +#include +#include + +#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 + +/* 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), -1); +#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 000000000000..9b98064ab8e7 --- /dev/null +++ b/src/netlink.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef _FIDO_NETLINK_H +#define _FIDO_NETLINK_H + +#include + +#include +#include +#include + +#include +#include + +#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_linux.c b/src/nfc_linux.c new file mode 100644 index 000000000000..dea9f3f98fd0 --- /dev/null +++ b/src/nfc_linux.c @@ -0,0 +1,631 @@ +/* + * 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. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "fido.h" +#include "fido/param.h" +#include "netlink.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' }; + +struct nfc_linux { + int fd; + uint32_t dev; + uint32_t target; + sigset_t sigmask; + const sigset_t *sigmaskp; + struct fido_nl *nl; +}; + +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, 0x80, + (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]; + int n, ok = -1; + + if ((n = d->io.read(d->io_handle, f, sizeof(f), ms)) < 2) { + fido_log_debug("%s: read", __func__); + 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); + } +} + +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 +to_int(const char *str, int base) +{ + char *ep; + long long ll; + + ll = strtoll(str, &ep, base); + 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); +} + +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; + int id, 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 ((di->path = strdup(name)) == NULL || + (di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL || + (di->product = get_usb_attr(dev, "product")) == NULL) + goto fail; + + /* XXX assumes USB for vendor/product info */ + if ((str = get_usb_attr(dev, "idVendor")) != NULL && + (id = to_int(str, 16)) > 0 && id <= UINT16_MAX) + di->vendor_id = (int16_t)id; + free(str); + + if ((str = get_usb_attr(dev, "idProduct")) != NULL && + (id = to_int(str, 16)) > 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; + int idx; + + if ((udev = udev_new()) == NULL || + (dev = udev_device_new_from_syspath(udev, path)) == NULL || + (str = udev_device_get_sysnum(dev)) == NULL) + idx = -1; + else + idx = to_int(str, 10); + + 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 ((idx = sysnum_from_syspath(path)) < 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 000000000000..3857c22dd2ba --- /dev/null +++ b/src/packed.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _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/pin.c b/src/pin.c new file mode 100644 index 000000000000..d3104e0ca6ec --- /dev/null +++ b/src/pin.c @@ -0,0 +1,690 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include "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 > 255) { + 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) +{ + 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) < 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) +{ + 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) < 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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + if ((aes_token = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + 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, 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); + + 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); + else + r = ctap20_uv_token_tx(dev, pin, ecdh, pk); + 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) +{ + return (uv_token_wait(dev, cmd, pin, ecdh, pk, rpid, token, -1)); +} + +static int +fido_dev_change_pin_tx(fido_dev_t *dev, const char *pin, const char *oldpin) +{ + 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)) != 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) < 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) +{ + 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)) != 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) < 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)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_change_pin_tx", __func__); + return (r); + } + } else { + if ((r = fido_dev_set_pin_tx(dev, pin)) != 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) +{ + return (fido_dev_set_pin_wait(dev, pin, oldpin, -1)); +} + +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) +{ + 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) < 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 reply[FIDO_MAXMSG]; + int reply_len; + int r; + + *retries = 0; + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, retries, + parse_pin_retry_count)) != FIDO_OK) { + fido_log_debug("%s: parse_pin_retry_count", __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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) +{ + return (fido_dev_get_pin_retry_count_wait(dev, retries, -1)); +} + +static int +fido_dev_get_uv_retry_count_rx(fido_dev_t *dev, int *retries, int ms) +{ + unsigned char reply[FIDO_MAXMSG]; + int reply_len; + int r; + + *retries = 0; + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, &reply, sizeof(reply), + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_ERR_RX); + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, retries, + parse_uv_retry_count)) != FIDO_OK) { + fido_log_debug("%s: parse_uv_retry_count", __func__); + return (r); + } + + return (FIDO_OK); +} + +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)) != 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) +{ + return (fido_dev_get_uv_retry_count_wait(dev, retries, -1)); +} + +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) +{ + 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)) != 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 000000000000..f13482bfddf9 --- /dev/null +++ b/src/random.c @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#ifdef HAVE_SYS_RANDOM_H +#include +#endif + +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "fido.h" + +#if defined(_WIN32) +#include + +#include +#include +#include +#include +#include + +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 000000000000..11380cea0904 --- /dev/null +++ b/src/reset.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include "fido.h" + +static int +fido_dev_reset_tx(fido_dev_t *dev) +{ + const unsigned char cbor[] = { CTAP_CBOR_RESET }; + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor)) < 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)) != 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) +{ + return (fido_dev_reset_wait(dev, -1)); +} diff --git a/src/rs256.c b/src/rs256.c new file mode 100644 index 000000000000..c6d87a3ea22c --- /dev/null +++ b/src/rs256.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include + +#include "fido.h" +#include "fido/rs256.h" + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static int +RSA_bits(const RSA *r) +{ + return (BN_num_bits(r->n)); +} + +static int +RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + r->n = n; + r->e = e; + r->d = d; + + return (1); +} + +static void +RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) +{ + *n = r->n; + *e = r->e; + *d = r->d; +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + +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) +{ + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + memcpy(pk, ptr, sizeof(*pk)); + + 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 ((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); +} diff --git a/src/u2f.c b/src/u2f.c new file mode 100644 index 000000000000..c5fbe0cfbb6c --- /dev/null +++ b/src/u2f.c @@ -0,0 +1,820 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "fido.h" +#include "fido/es256.h" + +#if defined(_MSC_VER) +static int +usleep(unsigned int usec) +{ + Sleep(usec / 1000); + + return (0); +} +#endif + +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 challenge[SHA256_DIGEST_LENGTH]; + unsigned char application[SHA256_DIGEST_LENGTH]; + unsigned char reply[FIDO_MAXMSG]; + int r; + +#ifdef FIDO_FUZZ + ms = 0; /* XXX */ +#endif + + /* 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; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu)) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if (fido_rx(dev, CTAP_CMD_MSG, &reply, sizeof(reply), ms) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { + fido_log_debug("%s: usleep", __func__); + r = FIDO_ERR_RX; + goto fail; + } + } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + + 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 challenge[SHA256_DIGEST_LENGTH]; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + unsigned char reply[FIDO_MAXMSG]; + 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 (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu)) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if (fido_rx(dev, CTAP_CMD_MSG, &reply, sizeof(reply), 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); + + 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 rp_id_hash[SHA256_DIGEST_LENGTH]; + unsigned char reply[FIDO_MAXMSG]; + 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; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu)) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, &reply, + sizeof(reply), ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { + fido_log_debug("%s: usleep", __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); + + 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_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; + 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)); + 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; + } + + /* 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_x509(cred, x5c.ptr, x5c.len) != FIDO_OK || + fido_cred_set_sig(cred, sig.ptr, sig.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); + + 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[FIDO_MAXMSG]; + int reply_len; + int found; + int r; + +#ifdef FIDO_FUZZ + ms = 0; /* XXX */ +#endif + + 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; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu)) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, &reply, + sizeof(reply), ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { + fido_log_debug("%s: usleep", __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); + + 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) +{ + iso7816_apdu_t *apdu = NULL; + const char *clientdata = FIDO_DUMMY_CLIENTDATA; + const char *rp_id = FIDO_DUMMY_RP_ID; + unsigned char clientdata_hash[SHA256_DIGEST_LENGTH]; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + unsigned char reply[FIDO_MAXMSG]; + 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 (dev->attr.flags & FIDO_CAP_WINK) { + fido_tx(dev, CTAP_CMD_WINK, NULL, 0); + fido_rx(dev, CTAP_CMD_WINK, &reply, sizeof(reply), 200); + } + + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu)) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + + return (r); +} + +int +u2f_get_touch_status(fido_dev_t *dev, int *touched, int ms) +{ + unsigned char reply[FIDO_MAXMSG]; + int reply_len; + int r; + + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, &reply, sizeof(reply), + ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_OK); /* ignore */ + } + + switch ((reply[reply_len - 2] << 8) | reply[reply_len - 1]) { + case SW_CONDITIONS_NOT_SATISFIED: + if ((r = u2f_get_touch_begin(dev)) != FIDO_OK) { + fido_log_debug("%s: u2f_get_touch_begin", __func__); + return (r); + } + *touched = 0; + break; + case SW_NO_ERROR: + *touched = 1; + break; + default: + fido_log_debug("%s: unexpected sw", __func__); + return (FIDO_ERR_RX); + } + + return (FIDO_OK); +} diff --git a/src/winhello.c b/src/winhello.c new file mode 100644 index 000000000000..0fe5b4cfe4c7 --- /dev/null +++ b/src/winhello.c @@ -0,0 +1,934 @@ +/* + * 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. + */ + +#include + +#include +#include +#include + +#include "fido.h" + +#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; +}; + +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; +}; + +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 char * +to_utf8(const wchar_t *utf16) +{ + int nch; + char *utf8; + + if (utf16 == NULL) { + fido_log_debug("%s: NULL", __func__); + return NULL; + } + if ((nch = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, + -1, NULL, 0, NULL, NULL)) < 1 || (size_t)nch > MAXCHARS) { + fido_log_debug("%s: WideCharToMultiByte %d", __func__); + return NULL; + } + if ((utf8 = calloc((size_t)nch, sizeof(*utf8))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return NULL; + } + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, + utf8, nch, NULL, NULL) != nch) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + free(utf8); + return NULL; + } + + return utf8; +} + +static int +to_fido_str_array(fido_str_array_t *sa, const char **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; +} + +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; + default: + fido_log_debug("%s: hr=0x%x", __func__, 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_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_ANY; + 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, + 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, 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_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, 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", 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) { + 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 +unpack_fmt(fido_cred_t *cred, WEBAUTHN_CREDENTIAL_ATTESTATION *att) +{ + char *fmt; + int r; + + if ((fmt = to_utf8(att->pwszFormatType)) == NULL) { + fido_log_debug("%s: fmt", __func__); + return -1; + } + r = fido_cred_set_fmt(cred, fmt); + free(fmt); + fmt = NULL; + if (r != FIDO_OK) { + fido_log_debug("%s: fido_cred_set_fmt: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_cred_authdata(fido_cred_t *cred, WEBAUTHN_CREDENTIAL_ATTESTATION *att) +{ + int r; + + if (att->cbAuthenticatorData > SIZE_MAX) { + fido_log_debug("%s: cbAuthenticatorData", __func__); + return -1; + } + if ((r = fido_cred_set_authdata_raw(cred, att->pbAuthenticatorData, + (size_t)att->cbAuthenticatorData)) != FIDO_OK) { + fido_log_debug("%s: fido_cred_set_authdata_raw: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_cred_sig(fido_cred_t *cred, WEBAUTHN_COMMON_ATTESTATION *attr) +{ + int r; + + if (attr->cbSignature > SIZE_MAX) { + fido_log_debug("%s: cbSignature", __func__); + return -1; + } + if ((r = fido_cred_set_sig(cred, attr->pbSignature, + (size_t)attr->cbSignature)) != FIDO_OK) { + fido_log_debug("%s: fido_cred_set_sig: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_x5c(fido_cred_t *cred, WEBAUTHN_COMMON_ATTESTATION *attr) +{ + int r; + + fido_log_debug("%s: %u cert(s)", __func__, attr->cX5c); + + if (attr->cX5c == 0) + return 0; /* self-attestation */ + if (attr->lAlg != WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256) { + fido_log_debug("%s: lAlg %d", __func__, attr->lAlg); + return -1; + } + if (attr->pX5c[0].cbData > SIZE_MAX) { + fido_log_debug("%s: cbData", __func__); + return -1; + } + if ((r = fido_cred_set_x509(cred, attr->pX5c[0].pbData, + (size_t)attr->pX5c[0].cbData)) != FIDO_OK) { + fido_log_debug("%s: fido_cred_set_x509: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_assert_authdata(fido_assert_t *assert, WEBAUTHN_ASSERTION *wa) +{ + int r; + + if (wa->cbAuthenticatorData > SIZE_MAX) { + fido_log_debug("%s: cbAuthenticatorData", __func__); + return -1; + } + if ((r = fido_assert_set_authdata_raw(assert, 0, wa->pbAuthenticatorData, + (size_t)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, WEBAUTHN_ASSERTION *wa) +{ + int r; + + if (wa->cbSignature > SIZE_MAX) { + fido_log_debug("%s: cbSignature", __func__); + return -1; + } + if ((r = fido_assert_set_sig(assert, 0, wa->pbSignature, + (size_t)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, WEBAUTHN_ASSERTION *wa) +{ + if (wa->Credential.cbId > SIZE_MAX) { + fido_log_debug("%s: Credential.cbId", __func__); + return -1; + } + if (fido_blob_set(&assert->stmt[0].id, wa->Credential.pbId, + (size_t)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, WEBAUTHN_ASSERTION *wa) +{ + if (wa->cbUserId == 0) + return 0; /* user id absent */ + if (wa->cbUserId > SIZE_MAX) { + fido_log_debug("%s: cbUserId", __func__); + return -1; + } + if (fido_blob_set(&assert->stmt[0].user.id, wa->pbUserId, + (size_t)wa->cbUserId) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return -1; + } + + return 0; +} + +static int +translate_fido_assert(struct winhello_assert *ctx, fido_assert_t *assert, + const char *pin) +{ + 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; + } + /* not implemented */ + if (assert->ext.mask) { + fido_log_debug("%s: ext 0x%x", __func__, assert->ext.mask); + return FIDO_ERR_UNSUPPORTED_EXTENSION; + } + 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 = MAXMSEC; + if (pack_credlist(&opt->CredentialList, &assert->allow_list) < 0) { + fido_log_debug("%s: pack_credlist", __func__); + return FIDO_ERR_INTERNAL; + } + if (set_uv(&opt->dwUserVerificationRequirement, assert->uv, pin) < 0) { + fido_log_debug("%s: set_uv", __func__); + return FIDO_ERR_INTERNAL; + } + + return FIDO_OK; +} + +static int +translate_winhello_assert(fido_assert_t *assert, WEBAUTHN_ASSERTION *wa) +{ + 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; + } + 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; + } + + return FIDO_OK; +} + +static int +translate_fido_cred(struct winhello_cred *ctx, fido_cred_t *cred, + const char *pin) +{ + 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 = MAXMSEC; + 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_uv(&opt->dwUserVerificationRequirement, cred->uv, pin) < 0) { + fido_log_debug("%s: set_uv", __func__); + return FIDO_ERR_INTERNAL; + } + if (cred->rk == FIDO_OPT_TRUE) { + opt->bRequireResidentKey = true; + } + + return FIDO_OK; +} + +static int +translate_winhello_cred(fido_cred_t *cred, WEBAUTHN_CREDENTIAL_ATTESTATION *att) +{ + if (unpack_fmt(cred, att) < 0) { + fido_log_debug("%s: unpack_fmt", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_cred_authdata(cred, att) < 0) { + fido_log_debug("%s: unpack_cred_authdata", __func__); + return FIDO_ERR_INTERNAL; + } + + switch (att->dwAttestationDecodeType) { + case WEBAUTHN_ATTESTATION_DECODE_NONE: + if (att->pvAttestationDecode != NULL) { + fido_log_debug("%s: pvAttestationDecode", __func__); + return FIDO_ERR_INTERNAL; + } + break; + case WEBAUTHN_ATTESTATION_DECODE_COMMON: + if (att->pvAttestationDecode == NULL) { + fido_log_debug("%s: pvAttestationDecode", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_cred_sig(cred, att->pvAttestationDecode) < 0) { + fido_log_debug("%s: unpack_cred_sig", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_x5c(cred, att->pvAttestationDecode) < 0) { + fido_log_debug("%s: unpack_x5c", __func__); + return FIDO_ERR_INTERNAL; + } + break; + default: + fido_log_debug("%s: dwAttestationDecodeType: %u", __func__, + att->dwAttestationDecodeType); + return FIDO_ERR_INTERNAL; + } + + return FIDO_OK; +} + +static int +winhello_manifest(BOOL *present) +{ + DWORD n; + HRESULT hr; + int r = FIDO_OK; + + if ((n = WebAuthNGetApiVersionNumber()) < 1) { + fido_log_debug("%s: unsupported api %u", __func__, n); + return FIDO_ERR_INTERNAL; + } + fido_log_debug("%s: api version %u", __func__, n); + hr = WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(present); + if (hr != S_OK) { + r = to_fido(hr); + fido_log_debug("%s: %ls -> %s", __func__, + WebAuthNGetErrorName(hr), fido_strerr(r)); + } + + return r; +} + +static int +winhello_get_assert(HWND w, struct winhello_assert *ctx) +{ + HRESULT hr; + int r = FIDO_OK; + + hr = WebAuthNAuthenticatorGetAssertion(w, ctx->rp_id, &ctx->cd, + &ctx->opt, &ctx->assert); + if (hr != S_OK) { + r = to_fido(hr); + fido_log_debug("%s: %ls -> %s", __func__, + WebAuthNGetErrorName(hr), fido_strerr(r)); + } + + return r; +} + +static int +winhello_make_cred(HWND w, struct winhello_cred *ctx) +{ + HRESULT hr; + int r = FIDO_OK; + + hr = WebAuthNAuthenticatorMakeCredential(w, &ctx->rp, &ctx->user, + &ctx->cose, &ctx->cd, &ctx->opt, &ctx->att); + if (hr != S_OK) { + r = to_fido(hr); + fido_log_debug("%s: %ls -> %s", __func__, + WebAuthNGetErrorName(hr), fido_strerr(r)); + } + + return r; +} + +static void +winhello_assert_free(struct winhello_assert *ctx) +{ + if (ctx == NULL) + return; + if (ctx->assert != NULL) + WebAuthNFreeAssertion(ctx->assert); + + free(ctx->rp_id); + free(ctx->opt.CredentialList.pCredentials); + free(ctx); +} + +static void +winhello_cred_free(struct winhello_cred *ctx) +{ + if (ctx == NULL) + return; + if (ctx->att != NULL) + WebAuthNFreeCredentialAttestation(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) +{ + int r; + BOOL present; + fido_dev_info_t *di; + + if (ilen == 0) { + return FIDO_OK; + } + if (devlist == NULL) { + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((r = winhello_manifest(&present)) != FIDO_OK) { + fido_log_debug("%s: winhello_manifest", __func__); + return r; + } + if (present == false) { + fido_log_debug("%s: not present", __func__); + return FIDO_OK; + } + + 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 (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) +{ + HWND w; + struct winhello_assert *ctx; + int r = FIDO_ERR_INTERNAL; + + (void)dev; + + 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__); + goto fail; + } + if ((r = translate_fido_assert(ctx, assert, pin)) != FIDO_OK) { + fido_log_debug("%s: translate_fido_assert", __func__); + goto fail; + } + if ((r = winhello_get_assert(w, ctx)) != S_OK) { + fido_log_debug("%s: winhello_get_assert", __func__); + goto fail; + } + if ((r = translate_winhello_assert(assert, ctx->assert)) != 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", "plat", "clientPin" }; + + (void)dev; + + fido_cbor_info_reset(ci); + + if (to_fido_str_array(&ci->versions, v, nitems(v)) < 0 || + to_fido_str_array(&ci->extensions, e, nitems(e)) < 0 || + to_fido_str_array(&ci->transports, t, nitems(t)) < 0) { + fido_log_debug("%s: to_fido_str_array", __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) +{ + HWND w; + struct winhello_cred *ctx; + int r = FIDO_ERR_INTERNAL; + + (void)dev; + + 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__); + goto fail; + } + if ((r = translate_fido_cred(ctx, cred, pin)) != 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 000000000000..b1dde9949c4f --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,77 @@ +# Copyright (c) 2018 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/explicit_bzero.c + ../openbsd-compat/freezero.c + ../openbsd-compat/strlcat.c + ../openbsd-compat/strlcpy.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 "-Wconversion -Wsign-conversion") +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} +) + +# set the library to link against +if(BUILD_SHARED_LIBS) + set(_FIDO2_LIBRARY fido2_shared) +else() + set(_FIDO2_LIBRARY fido2) +endif() + +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 000000000000..c38040253520 --- /dev/null +++ b/tools/assert_get.c @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +struct toggle { + fido_opt_t up; + fido_opt_t uv; + fido_opt_t pin; +}; + +static const char * +opt2str(fido_opt_t v) +{ + switch (v) { + case FIDO_OPT_OMIT: + return "omit"; + case FIDO_OPT_TRUE: + return "true"; + case FIDO_OPT_FALSE: + return "false"; + default: + return "unknown"; + } +} + +static void +parse_toggle(const char *str, struct toggle *opt) +{ + fido_opt_t *k; + fido_opt_t v; + char *assignment; + char *key; + char *val; + + if ((assignment = strdup(str)) == NULL) + err(1, "strdup"); + if ((val = strchr(assignment, '=')) == NULL) + errx(1, "invalid assignment '%s'", assignment); + + key = assignment; + *val++ = '\0'; + + if (!strcmp(val, "true")) + v = FIDO_OPT_TRUE; + else if (!strcmp(val, "false")) + v = FIDO_OPT_FALSE; + else + errx(1, "unknown value '%s'", val); + + if (!strcmp(key, "up")) + k = &opt->up; + else if (!strcmp(key, "uv")) + k = &opt->uv; + else if (!strcmp(key, "pin")) + k = &opt->pin; + else + errx(1, "unknown key '%s'", key); + + free(assignment); + + *k = v; +} + +static fido_assert_t * +prepare_assert(FILE *in_f, int flags, const struct toggle *opt) +{ + fido_assert_t *assert = NULL; + struct blob cdh; + struct blob id; + struct blob hmac_salt; + char *rpid = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&id, 0, sizeof(id)); + memset(&hmac_salt, 0, sizeof(hmac_salt)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + if ((flags & FLAG_RK) == 0) + r |= base64_read(in_f, &id); + if (flags & FLAG_HMAC) + r |= base64_read(in_f, &hmac_salt); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + if ((flags & FLAG_RK) == 0) { + fprintf(stderr, "credential id:\n"); + xxd(id.ptr, id.len); + } + fprintf(stderr, "up=%s\n", opt2str(opt->up)); + fprintf(stderr, "uv=%s\n", opt2str(opt->uv)); + fprintf(stderr, "pin=%s\n", opt2str(opt->pin)); + } + + if ((assert = fido_assert_new()) == NULL) + errx(1, "fido_assert_new"); + + if ((r = fido_assert_set_clientdata_hash(assert, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK) + errx(1, "fido_assert_set: %s", fido_strerr(r)); + if ((r = fido_assert_set_up(assert, opt->up)) != FIDO_OK) + errx(1, "fido_assert_set_up: %s", fido_strerr(r)); + if ((r = fido_assert_set_uv(assert, opt->uv)) != FIDO_OK) + errx(1, "fido_assert_set_uv: %s", fido_strerr(r)); + + if (flags & FLAG_HMAC) { + if ((r = fido_assert_set_extensions(assert, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s", + fido_strerr(r)); + if ((r = fido_assert_set_hmac_salt(assert, hmac_salt.ptr, + hmac_salt.len)) != FIDO_OK) + errx(1, "fido_assert_set_hmac_salt: %s", + fido_strerr(r)); + } + if (flags & FLAG_LARGEBLOB) { + if ((r = fido_assert_set_extensions(assert, + FIDO_EXT_LARGEBLOB_KEY)) != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s", fido_strerr(r)); + } + if ((flags & FLAG_RK) == 0) { + if ((r = fido_assert_allow_cred(assert, id.ptr, + id.len)) != FIDO_OK) + errx(1, "fido_assert_allow_cred: %s", fido_strerr(r)); + } + + free(hmac_salt.ptr); + free(cdh.ptr); + free(id.ptr); + free(rpid); + + return (assert); +} + +static void +print_assert(FILE *out_f, const fido_assert_t *assert, size_t idx, int flags) +{ + char *cdh = NULL; + char *authdata = NULL; + char *sig = NULL; + char *user_id = NULL; + char *hmac_secret = NULL; + char *key = NULL; + int r; + + r = base64_encode(fido_assert_clientdata_hash_ptr(assert), + fido_assert_clientdata_hash_len(assert), &cdh); + r |= base64_encode(fido_assert_authdata_ptr(assert, idx), + fido_assert_authdata_len(assert, 0), &authdata); + r |= base64_encode(fido_assert_sig_ptr(assert, idx), + fido_assert_sig_len(assert, idx), &sig); + if (flags & FLAG_RK) + r |= base64_encode(fido_assert_user_id_ptr(assert, idx), + fido_assert_user_id_len(assert, idx), &user_id); + if (flags & FLAG_HMAC) + r |= base64_encode(fido_assert_hmac_secret_ptr(assert, idx), + fido_assert_hmac_secret_len(assert, idx), &hmac_secret); + if (flags & FLAG_LARGEBLOB) + r |= base64_encode(fido_assert_largeblob_key_ptr(assert, idx), + fido_assert_largeblob_key_len(assert, idx), &key); + if (r < 0) + errx(1, "output error"); + + fprintf(out_f, "%s\n", cdh); + fprintf(out_f, "%s\n", fido_assert_rp_id(assert)); + fprintf(out_f, "%s\n", authdata); + fprintf(out_f, "%s\n", sig); + if (flags & FLAG_RK) + fprintf(out_f, "%s\n", user_id); + if (hmac_secret) { + fprintf(out_f, "%s\n", hmac_secret); + explicit_bzero(hmac_secret, strlen(hmac_secret)); + } + if (key) { + fprintf(out_f, "%s\n", key); + explicit_bzero(key, strlen(key)); + } + + free(key); + free(hmac_secret); + free(cdh); + free(authdata); + free(sig); + free(user_id); +} + +int +assert_get(int argc, char **argv) +{ + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + struct toggle opt; + char pin[1024]; + char prompt[1024]; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int flags = 0; + int ch; + int r; + + opt.up = opt.uv = opt.pin = FIDO_OPT_OMIT; + + while ((ch = getopt(argc, argv, "bdhi:o:prt:uv")) != -1) { + switch (ch) { + case 'b': + flags |= FLAG_LARGEBLOB; + break; + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'o': + out_path = optarg; + break; + case 'p': + opt.up = FIDO_OPT_TRUE; + break; + case 'r': + flags |= FLAG_RK; + break; + case 't' : + parse_toggle(optarg, &opt); + break; + case 'u': + flags |= FLAG_U2F; + break; + case 'v': + /* -v implies both pin and uv for historical reasons */ + opt.pin = FIDO_OPT_TRUE; + opt.uv = FIDO_OPT_TRUE; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + in_f = open_read(in_path); + out_f = open_write(out_path); + + fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0); + + assert = prepare_assert(in_f, flags, &opt); + + dev = open_dev(argv[0]); + if (flags & FLAG_U2F) + fido_dev_force_u2f(dev); + + if (opt.pin == FIDO_OPT_TRUE) { + r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", + argv[0]); + if (r < 0 || (size_t)r >= sizeof(prompt)) + errx(1, "snprintf"); + if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF)) + errx(1, "readpassphrase"); + 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 000000000000..7985e95042c5 --- /dev/null +++ b/tools/assert_verify.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static fido_assert_t * +prepare_assert(FILE *in_f, int flags) +{ + fido_assert_t *assert = NULL; + struct blob cdh; + struct blob authdata; + struct blob sig; + char *rpid = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&authdata, 0, sizeof(authdata)); + memset(&sig, 0, sizeof(sig)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + r |= base64_read(in_f, &authdata); + r |= base64_read(in_f, &sig); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + fprintf(stderr, "authenticator data:\n"); + xxd(authdata.ptr, authdata.len); + fprintf(stderr, "signature:\n"); + xxd(sig.ptr, sig.len); + } + + if ((assert = fido_assert_new()) == NULL) + errx(1, "fido_assert_new"); + if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK) + errx(1, "fido_assert_count: %s", fido_strerr(r)); + + if ((r = fido_assert_set_clientdata_hash(assert, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK || + (r = fido_assert_set_authdata(assert, 0, authdata.ptr, + authdata.len)) != FIDO_OK || + (r = fido_assert_set_sig(assert, 0, sig.ptr, sig.len)) != FIDO_OK) + errx(1, "fido_assert_set: %s", fido_strerr(r)); + + if (flags & FLAG_UP) { + if ((r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_up: %s", fido_strerr(r)); + } + if (flags & FLAG_UV) { + if ((r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_uv: %s", fido_strerr(r)); + } + if (flags & FLAG_HMAC) { + if ((r = fido_assert_set_extensions(assert, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s", + fido_strerr(r)); + } + + free(cdh.ptr); + free(authdata.ptr); + free(sig.ptr); + free(rpid); + + return (assert); +} + +static void * +load_pubkey(int type, const char *file) +{ + EC_KEY *ec = NULL; + RSA *rsa = NULL; + EVP_PKEY *eddsa = NULL; + es256_pk_t *es256_pk = NULL; + rs256_pk_t *rs256_pk = NULL; + eddsa_pk_t *eddsa_pk = NULL; + void *pk = NULL; + + if (type == COSE_ES256) { + if ((ec = read_ec_pubkey(file)) == NULL) + errx(1, "read_ec_pubkey"); + if ((es256_pk = es256_pk_new()) == NULL) + errx(1, "es256_pk_new"); + if (es256_pk_from_EC_KEY(es256_pk, ec) != FIDO_OK) + errx(1, "es256_pk_from_EC_KEY"); + + pk = es256_pk; + EC_KEY_free(ec); + } else if (type == COSE_RS256) { + if ((rsa = read_rsa_pubkey(file)) == NULL) + errx(1, "read_rsa_pubkey"); + if ((rs256_pk = rs256_pk_new()) == NULL) + errx(1, "rs256_pk_new"); + if (rs256_pk_from_RSA(rs256_pk, rsa) != FIDO_OK) + errx(1, "rs256_pk_from_RSA"); + + pk = rs256_pk; + RSA_free(rsa); + } else if (type == COSE_EDDSA) { + if ((eddsa = read_eddsa_pubkey(file)) == NULL) + errx(1, "read_eddsa_pubkey"); + if ((eddsa_pk = eddsa_pk_new()) == NULL) + errx(1, "eddsa_pk_new"); + if (eddsa_pk_from_EVP_PKEY(eddsa_pk, eddsa) != FIDO_OK) + errx(1, "eddsa_pk_from_EVP_PKEY"); + + pk = eddsa_pk; + EVP_PKEY_free(eddsa); + } + + return (pk); +} + +int +assert_verify(int argc, char **argv) +{ + fido_assert_t *assert = NULL; + void *pk = NULL; + char *in_path = NULL; + FILE *in_f = NULL; + int type = COSE_ES256; + int flags = 0; + int ch; + int r; + + while ((ch = getopt(argc, argv, "dhi:pv")) != -1) { + switch (ch) { + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'p': + flags |= FLAG_UP; + break; + case 'v': + flags |= FLAG_UV; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1 || argc > 2) + usage(); + + in_f = open_read(in_path); + + if (argc > 1 && 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 000000000000..e13119823c98 --- /dev/null +++ b/tools/base64.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. + */ + +#include +#include + +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +base64_encode(const void *ptr, size_t len, char **out) +{ + BIO *bio_b64 = NULL; + BIO *bio_mem = NULL; + char *b64_ptr = NULL; + long b64_len; + int n; + int ok = -1; + + if (ptr == NULL || out == NULL || len > INT_MAX) + return (-1); + + *out = NULL; + + if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL) + goto fail; + if ((bio_mem = BIO_new(BIO_s_mem())) == NULL) + goto fail; + + BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL); + BIO_push(bio_b64, bio_mem); + + n = BIO_write(bio_b64, ptr, (int)len); + if (n < 0 || (size_t)n != len) + goto fail; + + if (BIO_flush(bio_b64) < 0) + goto fail; + + b64_len = BIO_get_mem_data(bio_b64, &b64_ptr); + if (b64_len < 0 || (size_t)b64_len == SIZE_MAX || b64_ptr == NULL) + goto fail; + if ((*out = calloc(1, (size_t)b64_len + 1)) == NULL) + goto fail; + + memcpy(*out, b64_ptr, (size_t)b64_len); + ok = 0; + +fail: + BIO_free(bio_b64); + BIO_free(bio_mem); + + return (ok); +} + +int +base64_decode(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 000000000000..1ce1041c30c6 --- /dev/null +++ b/tools/bio.c @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#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 000000000000..17dfe4457902 --- /dev/null +++ b/tools/config.c @@ -0,0 +1,149 @@ +/* + * 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. + */ + +#include +#include +#include + +#include +#include + +#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); +} diff --git a/tools/cred_make.c b/tools/cred_make.c new file mode 100644 index 000000000000..7955fa2b7f9f --- /dev/null +++ b/tools/cred_make.c @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static fido_cred_t * +prepare_cred(FILE *in_f, int type, int flags) +{ + fido_cred_t *cred = NULL; + struct blob cdh; + struct blob uid; + char *rpid = NULL; + char *uname = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&uid, 0, sizeof(uid)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + r |= string_read(in_f, &uname); + r |= base64_read(in_f, &uid); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + fprintf(stderr, "user name: %s\n", uname); + fprintf(stderr, "user id:\n"); + xxd(uid.ptr, uid.len); + } + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + if ((r = fido_cred_set_type(cred, type)) != FIDO_OK || + (r = fido_cred_set_clientdata_hash(cred, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK || + (r = fido_cred_set_user(cred, uid.ptr, uid.len, uname, NULL, + NULL)) != FIDO_OK) + errx(1, "fido_cred_set: %s", fido_strerr(r)); + + if (flags & FLAG_RK) { + if ((r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_rk: %s", fido_strerr(r)); + } + if (flags & FLAG_UV) { + if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_uv: %s", fido_strerr(r)); + } + if (flags & FLAG_HMAC) { + if ((r = fido_cred_set_extensions(cred, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s", fido_strerr(r)); + } + 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[1024]; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int type = COSE_ES256; + int flags = 0; + int cred_protect = -1; + int ch; + int r; + + while ((ch = getopt(argc, argv, "bc:dhi:o:qruv")) != -1) { + switch (ch) { + case 'b': + flags |= FLAG_LARGEBLOB; + break; + case 'c': + if ((cred_protect = base10(optarg)) < 0) + errx(1, "-c: invalid argument '%s'", optarg); + break; + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'o': + out_path = optarg; + break; + case 'q': + flags |= FLAG_QUIET; + break; + case 'r': + flags |= FLAG_RK; + break; + case 'u': + flags |= FLAG_U2F; + break; + case 'v': + flags |= FLAG_UV; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1 || argc > 2) + usage(); + + in_f = open_read(in_path); + out_f = open_write(out_path); + + if (argc > 1 && cose_type(argv[1], &type) < 0) + errx(1, "unknown type %s", argv[1]); + + fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0); + + cred = prepare_cred(in_f, type, flags); + + dev = open_dev(argv[0]); + if (flags & FLAG_U2F) + fido_dev_force_u2f(dev); + + if (cred_protect > 0) { + r = fido_cred_set_prot(cred, cred_protect); + if (r != FIDO_OK) { + errx(1, "fido_cred_set_prot: %s", fido_strerr(r)); + } + } + + r = fido_dev_make_cred(dev, cred, NULL); + if (r == FIDO_ERR_PIN_REQUIRED && !(flags & FLAG_QUIET)) { + r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", + argv[0]); + if (r < 0 || (size_t)r >= sizeof(prompt)) + errx(1, "snprintf"); + if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF)) + errx(1, "readpassphrase"); + 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 000000000000..d622ed7369bd --- /dev/null +++ b/tools/cred_verify.c @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static fido_cred_t * +prepare_cred(FILE *in_f, int type, int flags) +{ + fido_cred_t *cred = NULL; + struct blob cdh; + struct blob authdata; + struct blob id; + struct blob sig; + struct blob x5c; + char *rpid = NULL; + char *fmt = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&authdata, 0, sizeof(authdata)); + memset(&id, 0, sizeof(id)); + memset(&sig, 0, sizeof(sig)); + memset(&x5c, 0, sizeof(x5c)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + r |= string_read(in_f, &fmt); + r |= base64_read(in_f, &authdata); + r |= base64_read(in_f, &id); + r |= base64_read(in_f, &sig); + if (r < 0) + errx(1, "input error"); + + (void)base64_read(in_f, &x5c); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + fprintf(stderr, "format: %s\n", fmt); + fprintf(stderr, "authenticator data:\n"); + xxd(authdata.ptr, authdata.len); + fprintf(stderr, "credential id:\n"); + xxd(id.ptr, id.len); + fprintf(stderr, "signature:\n"); + xxd(sig.ptr, sig.len); + fprintf(stderr, "x509:\n"); + xxd(x5c.ptr, x5c.len); + } + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + if ((r = fido_cred_set_type(cred, type)) != FIDO_OK || + (r = fido_cred_set_clientdata_hash(cred, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK || + (r = fido_cred_set_authdata(cred, authdata.ptr, + authdata.len)) != FIDO_OK || + (r = fido_cred_set_sig(cred, sig.ptr, sig.len)) != FIDO_OK || + (r = fido_cred_set_fmt(cred, fmt)) != FIDO_OK) + errx(1, "fido_cred_set: %s", fido_strerr(r)); + + if (x5c.ptr != NULL) { + if ((r = fido_cred_set_x509(cred, x5c.ptr, x5c.len)) != FIDO_OK) + errx(1, "fido_cred_set_x509: %s", fido_strerr(r)); + } + + if (flags & FLAG_UV) { + if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_uv: %s", fido_strerr(r)); + } + if (flags & FLAG_HMAC) { + if ((r = fido_cred_set_extensions(cred, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s", fido_strerr(r)); + } + + free(cdh.ptr); + free(authdata.ptr); + free(id.ptr); + free(sig.ptr); + free(x5c.ptr); + free(rpid); + free(fmt); + + return (cred); +} + +int +cred_verify(int argc, char **argv) +{ + fido_cred_t *cred = NULL; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int type = COSE_ES256; + int flags = 0; + int 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 000000000000..d7fb15580f87 --- /dev/null +++ b/tools/credman.c @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +credman_get_metadata(fido_dev_t *dev, const char *path) +{ + fido_credman_metadata_t *metadata = NULL; + char *pin = 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 000000000000..207c35894f8b --- /dev/null +++ b/tools/extern.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +#include + +#include + +#include +#include +#include + +struct blob { + unsigned char *ptr; + size_t len; +}; + +#define TOKEN_OPT "CDGILPRSVabcdefi:k:l:n:p:ru" + +#define FLAG_DEBUG 0x01 +#define FLAG_QUIET 0x02 +#define FLAG_RK 0x04 +#define FLAG_UV 0x08 +#define FLAG_U2F 0x10 +#define FLAG_HMAC 0x20 +#define FLAG_UP 0x40 +#define FLAG_LARGEBLOB 0x80 + +#define PINBUF_LEN 256 + +EC_KEY *read_ec_pubkey(const char *); +fido_dev_t *open_dev(const char *); +FILE *open_read(const char *); +FILE *open_write(const char *); +char *get_pin(const char *); +const char *plural(size_t); +const char *cose_string(int); +const char *prot_string(int); +int assert_get(int, char **); +int assert_verify(int, char **); +int base64_decode(const char *, void **, size_t *); +int base64_encode(const void *, size_t, char **); +int base64_read(FILE *, struct blob *); +int bio_delete(const char *, const char *); +int bio_enroll(const char *); +void bio_info(fido_dev_t *); +int bio_list(const char *); +int bio_set_name(const char *, const char *, const char *); +int blob_clean(const char *); +int blob_list(const char *); +int blob_delete(const char *, const char *, const char *, const char *); +int blob_get(const char *, const char *, const char *, const char *, + const char *); +int blob_set(const char *, const char *, const char *, const char *, + const char *); +int config_always_uv(char *, int); +int config_entattest(char *); +int config_force_pin_change(char *); +int config_pin_minlen(char *, const char *); +int 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_ec_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 000000000000..c363d9a49927 --- /dev/null +++ b/tools/fido2-assert.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/* + * Example usage: + * + * $ echo assertion challenge | openssl sha256 -binary | base64 > assert_param + * $ echo relying party >> assert_param + * $ head -1 cred >> assert_param # credential id + * $ tail -n +2 cred > pubkey # credential pubkey + * $ fido2-assert -G -i assert_param /dev/hidraw5 | fido2-assert -V pubkey rs256 + * + * See blurb in fido2-cred.c on how to obtain cred. + */ + +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-assert -G [-bdhpruv] [-t option] [-i input_file] [-o output_file] device\n" +" fido2-assert -V [-dhpv] [-i input_file] key_file [type]\n" + ); + + exit(1); +} + +int +main(int argc, char **argv) +{ + if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-') + usage(); + + switch (argv[1][1]) { + case 'G': + return (assert_get(--argc, ++argv)); + case 'V': + return (assert_verify(--argc, ++argv)); + } + + usage(); + + /* NOTREACHED */ +} diff --git a/tools/fido2-attach.sh b/tools/fido2-attach.sh new file mode 100755 index 000000000000..d4bc44989f2b --- /dev/null +++ b/tools/fido2-attach.sh @@ -0,0 +1,14 @@ +#!/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. + +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 000000000000..9463cd591e75 --- /dev/null +++ b/tools/fido2-cred.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/* + * Example usage: + * + * $ echo credential challenge | openssl sha256 -binary | base64 > cred_param + * $ echo relying party >> cred_param + * $ echo user name >> cred_param + * $ dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param + * $ fido2-cred -M -i cred_param /dev/hidraw5 | fido2-cred -V -o cred + */ + +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-cred -M [-bdhqruv] [-c cred_protect] [-i input_file] [-o output_file] device [type]\n" +" fido2-cred -V [-dhv] [-c cred_protect] [-i input_file] [-o output_file] [type]\n" + ); + + exit(1); +} + +int +main(int argc, char **argv) +{ + if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-') + usage(); + + switch (argv[1][1]) { + case 'M': + return (cred_make(--argc, ++argv)); + case 'V': + return (cred_verify(--argc, ++argv)); + } + + usage(); + + /* NOTREACHED */ +} diff --git a/tools/fido2-detach.sh b/tools/fido2-detach.sh new file mode 100755 index 000000000000..9cd2e64bbe31 --- /dev/null +++ b/tools/fido2-detach.sh @@ -0,0 +1,12 @@ +#!/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. + +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 000000000000..c1539b8bc08e --- /dev/null +++ b/tools/fido2-token.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include + +#include "../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 -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 '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 000000000000..44b28b8d06b8 --- /dev/null +++ b/tools/fido2-unprot.sh @@ -0,0 +1,75 @@ +#!/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. + + +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 000000000000..e684d0b6f12d --- /dev/null +++ b/tools/include_check.sh @@ -0,0 +1,21 @@ +#!/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. + +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 000000000000..fc2584ce8bdf --- /dev/null +++ b/tools/largeblob.c @@ -0,0 +1,593 @@ +/* + * 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. + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +struct rkmap { + fido_credman_rp_t *rp; /* known rps */ + fido_credman_rk_t **rk; /* rk per rp */ +}; + +static void +free_rkmap(struct rkmap *map) +{ + if (map->rp != NULL) { + for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) + fido_credman_rk_free(&map->rk[i]); + fido_credman_rp_free(&map->rp); + } + free(map->rk); +} + +static int +map_known_rps(fido_dev_t *dev, const char *path, struct rkmap *map) +{ + const char *rp_id; + char *pin = NULL; + size_t n; + int r, ok = -1; + + if ((map->rp = fido_credman_rp_new()) == NULL) { + warnx("%s: fido_credman_rp_new", __func__); + goto out; + } + if ((pin = get_pin(path)) == NULL) + goto out; + if ((r = fido_credman_get_dev_rp(dev, map->rp, pin)) != FIDO_OK) { + warnx("fido_credman_get_dev_rp: %s", fido_strerr(r)); + goto out; + } + if ((n = fido_credman_rp_count(map->rp)) > UINT8_MAX) { + warnx("%s: fido_credman_rp_count > UINT8_MAX", __func__); + goto out; + } + if ((map->rk = calloc(n, sizeof(*map->rk))) == NULL) { + warnx("%s: calloc", __func__); + goto out; + } + for (size_t i = 0; i < n; i++) { + if ((rp_id = fido_credman_rp_id(map->rp, i)) == NULL) { + warnx("%s: fido_credman_rp_id %zu", __func__, i); + goto out; + } + if ((map->rk[i] = fido_credman_rk_new()) == NULL) { + warnx("%s: fido_credman_rk_new", __func__); + goto out; + } + if ((r = fido_credman_get_dev_rk(dev, rp_id, map->rk[i], + pin)) != FIDO_OK) { + warnx("%s: fido_credman_get_dev_rk %s: %s", __func__, + rp_id, fido_strerr(r)); + goto out; + } + } + + ok = 0; +out: + freezero(pin, PINBUF_LEN); + + return ok; +} + +static int +lookup_key(const char *path, fido_dev_t *dev, const char *rp_id, + const struct blob *cred_id, char **pin, struct blob *key) +{ + fido_credman_rk_t *rk = NULL; + const fido_cred_t *cred = NULL; + size_t i, n; + int r, ok = -1; + + if ((rk = fido_credman_rk_new()) == NULL) { + warnx("%s: fido_credman_rk_new", __func__); + goto out; + } + if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin)) != FIDO_OK && + *pin == NULL && should_retry_with_pin(dev, r)) { + if ((*pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin); + } + if (r != FIDO_OK) { + warnx("%s: fido_credman_get_dev_rk: %s", __func__, + fido_strerr(r)); + goto out; + } + if ((n = fido_credman_rk_count(rk)) == 0) { + warnx("%s: rp id not found", __func__); + goto out; + } + if (n == 1 && cred_id->len == 0) { + /* use the credential we found */ + cred = fido_credman_rk(rk, 0); + } else { + if (cred_id->len == 0) { + warnx("%s: multiple credentials found", __func__); + goto out; + } + for (i = 0; i < n; i++) { + const fido_cred_t *x = fido_credman_rk(rk, i); + if (fido_cred_id_len(x) <= cred_id->len && + !memcmp(fido_cred_id_ptr(x), cred_id->ptr, + fido_cred_id_len(x))) { + cred = x; + break; + } + } + } + if (cred == NULL) { + warnx("%s: credential not found", __func__); + goto out; + } + if (fido_cred_largeblob_key_ptr(cred) == NULL) { + warnx("%s: no associated blob key", __func__); + goto out; + } + key->len = fido_cred_largeblob_key_len(cred); + if ((key->ptr = malloc(key->len)) == NULL) { + warnx("%s: malloc", __func__); + goto out; + } + memcpy(key->ptr, fido_cred_largeblob_key_ptr(cred), key->len); + + ok = 0; +out: + fido_credman_rk_free(&rk); + + return ok; +} + +static int +load_key(const char *keyf, const char *cred_id64, const char *rp_id, + const char *path, fido_dev_t *dev, char **pin, struct blob *key) +{ + struct blob cred_id; + FILE *fp; + int r; + + memset(&cred_id, 0, sizeof(cred_id)); + + if (keyf != NULL) { + if (rp_id != NULL || cred_id64 != NULL) + usage(); + fp = open_read(keyf); + if ((r = base64_read(fp, key)) < 0) + warnx("%s: base64_read %s", __func__, keyf); + fclose(fp); + return r; + } + if (rp_id == NULL) + usage(); + if (cred_id64 != NULL && base64_decode(cred_id64, (void *)&cred_id.ptr, + &cred_id.len) < 0) { + warnx("%s: base64_decode %s", __func__, cred_id64); + return -1; + } + r = lookup_key(path, dev, rp_id, &cred_id, pin, key); + free(cred_id.ptr); + + return r; +} + +int +blob_set(const char *path, const char *keyf, const char *rp_id, + const char *cred_id64, const char *blobf) +{ + fido_dev_t *dev; + struct blob key, blob; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + memset(&key, 0, sizeof(key)); + memset(&blob, 0, sizeof(blob)); + + if (read_file(blobf, &blob.ptr, &blob.len) < 0 || + load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0) + goto out; + if ((r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr, + blob.len, pin)) != FIDO_OK && should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr, + blob.len, pin); + } + if (r != FIDO_OK) { + warnx("fido_dev_largeblob_set: %s", fido_strerr(r)); + goto out; + } + + ok = 0; /* success */ +out: + freezero(key.ptr, key.len); + freezero(blob.ptr, blob.len); + freezero(pin, PINBUF_LEN); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +blob_get(const char *path, const char *keyf, const char *rp_id, + const char *cred_id64, const char *blobf) +{ + fido_dev_t *dev; + struct blob key, blob; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + memset(&key, 0, sizeof(key)); + memset(&blob, 0, sizeof(blob)); + + if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0) + goto out; + if ((r = fido_dev_largeblob_get(dev, key.ptr, key.len, &blob.ptr, + &blob.len)) != FIDO_OK) { + warnx("fido_dev_largeblob_get: %s", fido_strerr(r)); + goto out; + } + if (write_file(blobf, blob.ptr, blob.len) < 0) + goto out; + + ok = 0; /* success */ +out: + freezero(key.ptr, key.len); + freezero(blob.ptr, blob.len); + freezero(pin, PINBUF_LEN); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +blob_delete(const char *path, const char *keyf, const char *rp_id, + const char *cred_id64) +{ + fido_dev_t *dev; + struct blob key; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + memset(&key, 0, sizeof(key)); + + if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0) + goto out; + if ((r = fido_dev_largeblob_remove(dev, key.ptr, key.len, + pin)) != FIDO_OK && should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_largeblob_remove(dev, key.ptr, key.len, pin); + } + if (r != FIDO_OK) { + warnx("fido_dev_largeblob_remove: %s", fido_strerr(r)); + goto out; + } + + ok = 0; /* success */ +out: + freezero(key.ptr, key.len); + freezero(pin, PINBUF_LEN); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +static int +decompress(const struct blob *plaintext, uint64_t origsiz) +{ + struct blob inflated; + u_long ilen, plen; + int ok = -1; + + memset(&inflated, 0, sizeof(inflated)); + + if (plaintext->len > ULONG_MAX) + return -1; + if (origsiz > ULONG_MAX || origsiz > SIZE_MAX) + return -1; + plen = (u_long)plaintext->len; + ilen = (u_long)origsiz; + inflated.len = (size_t)origsiz; + if ((inflated.ptr = calloc(1, inflated.len)) == NULL) + return -1; + if (uncompress(inflated.ptr, &ilen, plaintext->ptr, plen) != Z_OK || + ilen > SIZE_MAX || (size_t)ilen != (size_t)origsiz) + goto out; + + ok = 0; /* success */ +out: + freezero(inflated.ptr, inflated.len); + + return ok; +} + +static int +decode(const struct blob *ciphertext, const struct blob *nonce, + uint64_t origsiz, const fido_cred_t *cred) +{ + uint8_t aad[4 + sizeof(uint64_t)]; + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher; + struct blob plaintext; + uint64_t tmp; + int ok = -1; + + memset(&plaintext, 0, sizeof(plaintext)); + + if (nonce->len != 12) + return -1; + if (cred == NULL || + fido_cred_largeblob_key_ptr(cred) == NULL || + fido_cred_largeblob_key_len(cred) != 32) + return -1; + if (ciphertext->len > UINT_MAX || + ciphertext->len > SIZE_MAX - 16 || + ciphertext->len < 16) + return -1; + plaintext.len = ciphertext->len - 16; + if ((plaintext.ptr = calloc(1, plaintext.len)) == NULL) + return -1; + if ((ctx = EVP_CIPHER_CTX_new()) == NULL || + (cipher = EVP_aes_256_gcm()) == NULL || + EVP_CipherInit(ctx, cipher, fido_cred_largeblob_key_ptr(cred), + nonce->ptr, 0) == 0) + goto out; + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, + ciphertext->ptr + ciphertext->len - 16) == 0) + goto out; + aad[0] = 0x62; /* b */ + aad[1] = 0x6c; /* l */ + aad[2] = 0x6f; /* o */ + aad[3] = 0x62; /* b */ + tmp = htole64(origsiz); + memcpy(&aad[4], &tmp, sizeof(uint64_t)); + if (EVP_Cipher(ctx, NULL, aad, (u_int)sizeof(aad)) < 0 || + EVP_Cipher(ctx, plaintext.ptr, ciphertext->ptr, + (u_int)plaintext.len) < 0 || + EVP_Cipher(ctx, NULL, NULL, 0) < 0) + goto out; + if (decompress(&plaintext, origsiz) < 0) + goto out; + + ok = 0; +out: + freezero(plaintext.ptr, plaintext.len); + + if (ctx != NULL) + EVP_CIPHER_CTX_free(ctx); + + return ok; +} + +static const fido_cred_t * +try_rp(const fido_credman_rk_t *rk, const struct blob *ciphertext, + const struct blob *nonce, uint64_t origsiz) +{ + const fido_cred_t *cred; + + for (size_t i = 0; i < fido_credman_rk_count(rk); i++) + if ((cred = fido_credman_rk(rk, i)) != NULL && + decode(ciphertext, nonce, origsiz, cred) == 0) + return cred; + + return NULL; +} + +static int +decode_cbor_blob(struct blob *out, const cbor_item_t *item) +{ + if (out->ptr != NULL || + cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) + return -1; + out->len = cbor_bytestring_length(item); + if ((out->ptr = malloc(out->len)) == NULL) + return -1; + memcpy(out->ptr, cbor_bytestring_handle(item), out->len); + + return 0; +} + +static int +decode_blob_entry(const cbor_item_t *item, struct blob *ciphertext, + struct blob *nonce, uint64_t *origsiz) +{ + struct cbor_pair *v; + + if (item == NULL) + return -1; + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + (v = cbor_map_handle(item)) == NULL) + return -1; + if (cbor_map_size(item) > UINT8_MAX) + return -1; + + for (size_t i = 0; i < cbor_map_size(item); i++) { + if (cbor_isa_uint(v[i].key) == false || + cbor_int_get_width(v[i].key) != CBOR_INT_8) + continue; /* ignore */ + switch (cbor_get_uint8(v[i].key)) { + case 1: /* ciphertext */ + if (decode_cbor_blob(ciphertext, v[i].value) < 0) + return -1; + break; + case 2: /* nonce */ + if (decode_cbor_blob(nonce, v[i].value) < 0) + return -1; + break; + case 3: /* origSize */ + if (*origsiz != 0 || + cbor_isa_uint(v[i].value) == false || + (*origsiz = cbor_get_int(v[i].value)) > SIZE_MAX) + return -1; + } + } + if (ciphertext->ptr == NULL || nonce->ptr == NULL || *origsiz == 0) + return -1; + + return 0; +} + +static void +print_blob_entry(size_t idx, const cbor_item_t *item, const struct rkmap *map) +{ + struct blob ciphertext, nonce; + const fido_cred_t *cred = NULL; + const char *rp_id = NULL; + char *cred_id = NULL; + uint64_t origsiz = 0; + + memset(&ciphertext, 0, sizeof(ciphertext)); + memset(&nonce, 0, sizeof(nonce)); + + if (decode_blob_entry(item, &ciphertext, &nonce, &origsiz) < 0) { + printf("%02zu: \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("")) == NULL) { + printf("%02zu: \n", idx); + goto out; + } + } else { + if (base64_encode(fido_cred_id_ptr(cred), + fido_cred_id_len(cred), &cred_id) < 0) { + printf("%02zu: \n", idx); + goto out; + } + } + if (rp_id == NULL) + rp_id = ""; + + 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 000000000000..f342347d1ff5 --- /dev/null +++ b/tools/pin.c @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +pin_set(char *path) +{ + fido_dev_t *dev = NULL; + char prompt[1024]; + char pin1[1024]; + char pin2[1024]; + int r; + int status = 1; + + dev = open_dev(path); + + r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: "); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + if (strcmp(pin1, pin2) != 0) { + fprintf(stderr, "PINs do not match. Try again.\n"); + goto out; + } + + if ((r = fido_dev_set_pin(dev, pin1, NULL)) != FIDO_OK) { + warnx("fido_dev_set_pin: %s", fido_strerr(r)); + goto out; + } + + fido_dev_close(dev); + fido_dev_free(&dev); + + status = 0; +out: + explicit_bzero(pin1, sizeof(pin1)); + explicit_bzero(pin2, sizeof(pin2)); + + exit(status); +} + +int +pin_change(char *path) +{ + fido_dev_t *dev = NULL; + char prompt[1024]; + char pin0[1024]; + char pin1[1024]; + char pin2[1024]; + int r; + int status = 1; + + if (path == NULL) + usage(); + + dev = open_dev(path); + + r = snprintf(prompt, sizeof(prompt), "Enter current PIN for %s: ", path); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin0, sizeof(pin0), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: "); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + if (strcmp(pin1, pin2) != 0) { + fprintf(stderr, "PINs do not match. Try again.\n"); + goto out; + } + + if ((r = fido_dev_set_pin(dev, pin1, pin0)) != FIDO_OK) { + warnx("fido_dev_set_pin: %s", fido_strerr(r)); + goto out; + } + + fido_dev_close(dev); + fido_dev_free(&dev); + + status = 0; +out: + explicit_bzero(pin0, sizeof(pin0)); + explicit_bzero(pin1, sizeof(pin1)); + explicit_bzero(pin2, sizeof(pin2)); + + exit(status); +} diff --git a/tools/test.sh b/tools/test.sh new file mode 100755 index 000000000000..656bff2c1596 --- /dev/null +++ b/tools/test.sh @@ -0,0 +1,296 @@ +#!/bin/sh -ex + +# 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. + +# 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 2021-07-21 with firmware 5.2.7. + +cd "$1" +DEV="$2" + +make_cred() { + cat > 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}" > "$3" < cred_param +} + +verify_cred() { + fido2-cred -V $1 > cred_out < "$2" + head -1 cred_out > "$3" + tail -n +2 cred_out > "$4" +} + +get_assert() { + cat > 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" < "$3" +} + +dd if=/dev/urandom bs=32 count=1 | base64 > hmac-salt + +# u2f +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 + +# 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 +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 + +# 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 000000000000..4dcc2fea6dbd --- /dev/null +++ b/tools/token.c @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static void +format_flags(char *ret, size_t retlen, uint8_t flags) +{ + memset(ret, 0, retlen); + + if (flags & FIDO_CAP_WINK) { + if (strlcat(ret, "wink,", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, "nowink,", retlen) >= retlen) + goto toolong; + } + + if (flags & FIDO_CAP_CBOR) { + if (strlcat(ret, " cbor,", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, " nocbor,", retlen) >= retlen) + goto toolong; + } + + if (flags & FIDO_CAP_NMSG) { + if (strlcat(ret, " nomsg", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, " msg", retlen) >= retlen) + goto toolong; + } + + return; +toolong: + strlcpy(ret, "toolong", retlen); +} + +static void +print_attr(const fido_dev_t *dev) +{ + char flags_txt[128]; + + printf("proto: 0x%02x\n", fido_dev_protocol(dev)); + printf("major: 0x%02x\n", fido_dev_major(dev)); + printf("minor: 0x%02x\n", fido_dev_minor(dev)); + printf("build: 0x%02x\n", fido_dev_build(dev)); + + format_flags(flags_txt, sizeof(flags_txt), fido_dev_flags(dev)); + printf("caps: 0x%02x (%s)\n", fido_dev_flags(dev), flags_txt); +} + +static void +print_str_array(const char *label, char * const *sa, size_t len) +{ + if (len == 0) + return; + + printf("%s strings: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s", i > 0 ? ", " : "", sa[i]); + + printf("\n"); +} + +static void +print_opt_array(const char *label, char * const *name, const bool *value, + size_t len) +{ + if (len == 0) + return; + + printf("%s: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s%s", i > 0 ? ", " : "", + value[i] ? "" : "no", name[i]); + + printf("\n"); +} + +static void +print_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_EDDSA: + cose = "eddsa"; + break; + case COSE_ES256: + cose = "es256"; + break; + case COSE_RS256: + cose = "rs256"; + 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_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 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 firmware version */ + print_fwversion(fido_cbor_info_fwversion(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); + + if (fido_dev_get_uv_retry_count(dev, &retrycnt) != FIDO_OK) + printf("uv retries: undefined\n"); + else + printf("uv retries: %d\n", retrycnt); + + bio_info(dev); + + fido_cbor_info_free(&ci); +end: + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +token_reset(char *path) +{ + fido_dev_t *dev = NULL; + int r; + + if (path == NULL) + usage(); + + dev = open_dev(path); + if ((r = fido_dev_reset(dev)) != FIDO_OK) + errx(1, "fido_dev_reset: %s", fido_strerr(r)); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +token_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; + 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 '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 (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 000000000000..612d81b2000c --- /dev/null +++ b/tools/util.c @@ -0,0 +1,591 @@ +/* + * 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. + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../openbsd-compat/openbsd-compat.h" +#ifdef _MSC_VER +#include "../openbsd-compat/posix_win.h" +#endif + +#include "extern.h" + +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_ec_pubkey(FILE *f, const void *ptr, size_t len) +{ + EVP_PKEY *pkey = NULL; + es256_pk_t *pk = NULL; + int ok = -1; + + if ((pk = es256_pk_new()) == NULL) { + warnx("es256_pk_new"); + goto fail; + } + + if (es256_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("es256_pk_from_ptr"); + goto fail; + } + + if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("es256_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(f, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + es256_pk_free(&pk); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +RSA * +read_rsa_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + RSA *rsa = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) { + warnx("EVP_PKEY_get1_RSA"); + goto fail; + } + +fail: + if (fp) { + fclose(fp); + } + if (pkey) { + EVP_PKEY_free(pkey); + } + + return (rsa); +} + +int +write_rsa_pubkey(FILE *f, const void *ptr, size_t len) +{ + EVP_PKEY *pkey = NULL; + rs256_pk_t *pk = NULL; + int ok = -1; + + if ((pk = rs256_pk_new()) == NULL) { + warnx("rs256_pk_new"); + goto fail; + } + + if (rs256_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("rs256_pk_from_ptr"); + goto fail; + } + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("rs256_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(f, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + rs256_pk_free(&pk); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +EVP_PKEY * +read_eddsa_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + +fail: + if (fp) { + fclose(fp); + } + + return (pkey); +} + +int +write_eddsa_pubkey(FILE *f, const void *ptr, size_t len) +{ + EVP_PKEY *pkey = NULL; + eddsa_pk_t *pk = NULL; + int ok = -1; + + if ((pk = eddsa_pk_new()) == NULL) { + warnx("eddsa_pk_new"); + goto fail; + } + + if (eddsa_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("eddsa_pk_from_ptr"); + goto fail; + } + + if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("eddsa_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(f, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + eddsa_pk_free(&pk); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +void +print_cred(FILE *out_f, int type, const fido_cred_t *cred) +{ + char *id; + int r; + + r = base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred), &id); + if (r < 0) + errx(1, "output error"); + + fprintf(out_f, "%s\n", id); + + if (type == COSE_ES256) { + write_ec_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + } else if (type == COSE_RS256) { + write_rsa_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + } else if (type == COSE_EDDSA) { + write_eddsa_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + } else { + errx(1, "print_cred: unknown type"); + } + + free(id); +} + +int +cose_type(const char *str, int *type) +{ + if (strcmp(str, "es256") == 0) + *type = COSE_ES256; + 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_EDDSA: + return ("eddsa"); + case COSE_ES256: + return ("es256"); + case COSE_RS256: + return ("rs256"); + 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 000000000000..0dfc3e276c7a --- /dev/null +++ b/udev/70-u2f.rules @@ -0,0 +1,217 @@ +# 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. + +# 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" + +# 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 by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1011", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S Legacy by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1015", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano X by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4011", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano X Legacy by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4015", 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 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 000000000000..29a9d41fe37d --- /dev/null +++ b/udev/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2018 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +if(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 000000000000..97bbb97b26c8 --- /dev/null +++ b/udev/check.sh @@ -0,0 +1,31 @@ +#!/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. + +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 000000000000..cea60a0be9fb --- /dev/null +++ b/udev/fidodevs @@ -0,0 +1,126 @@ +# 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. + +# 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 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 0x1011 Ledger Nano S +product LEDGER 0x1015 Ledger Nano S Legacy +product LEDGER 0x4011 Ledger Nano X +product LEDGER 0x4015 Ledger Nano X Legacy + +product HYPERSECU 0x0880 Hypersecu HyperFIDO + +product EWBM 0x4a1a TrustKey Solutions FIDO2 G310 +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 000000000000..2a85c7cbf98f --- /dev/null +++ b/udev/genrules.awk @@ -0,0 +1,55 @@ +#!/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. + +NR == 1 { + print "# Copyright (c) 2020 Yubico AB. All rights reserved." + print "# Use of this source code is governed by a BSD-style" + print "# license that can be found in the LICENSE file." + print "" + print "# This file is automatically generated, and should" + print "# be used with udev 188 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 000000000000..55aac9d96bc5 --- /dev/null +++ b/windows/build.ps1 @@ -0,0 +1,272 @@ +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]$Fido2Flags = "" +) + +$ErrorActionPreference = "Continue" + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# LibreSSL coordinates. +New-Variable -Name 'LIBRESSL_URL' ` + -Value 'https://fastly.cdn.openbsd.org/pub/OpenBSD/LibreSSL' -Option Constant +New-Variable -Name 'LIBRESSL' -Value 'libressl-3.2.5' -Option Constant + +# libcbor coordinates. +New-Variable -Name 'LIBCBOR' -Value 'libcbor-0.8.0' -Option Constant +New-Variable -Name 'LIBCBOR_BRANCH' -Value 'v0.8.0' -Option Constant +New-Variable -Name 'LIBCBOR_GIT' -Value 'https://github.com/pjk/libcbor' ` + -Option Constant + +# zlib coordinates. +New-Variable -Name 'ZLIB' -Value 'zlib-1.2.11' -Option Constant +New-Variable -Name 'ZLIB_BRANCH' -Value 'v1.2.11' -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 + +# Find CMake. +$CMake = $(Get-Command cmake -ErrorAction Ignore | Select-Object -ExpandProperty Source) +if([string]::IsNullOrEmpty($CMake)) { + $CMake = $CMakePath +} + +# Find Git. +$Git = $(Get-Command git -ErrorAction Ignore | Select-Object -ExpandProperty Source) +if([string]::IsNullOrEmpty($Git)) { + $Git = $GitPath +} + +# Find 7z. +$SevenZ = $(Get-Command 7z -ErrorAction Ignore | Select-Object -ExpandProperty Source) +if([string]::IsNullOrEmpty($SevenZ)) { + $SevenZ = $SevenZPath +} + +# Find GPG. +$GPG = $(Get-Command gpg -ErrorAction Ignore | Select-Object -ExpandProperty Source) +if([string]::IsNullOrEmpty($GPG)) { + $GPG = $GPGPath +} + +# Override CMAKE_SYSTEM_VERSION if $WinSDK is set. +if(-Not ([string]::IsNullOrEmpty($WinSDK))) { + $CMAKE_SYSTEM_VERSION = "-DCMAKE_SYSTEM_VERSION='$WinSDK'" +} else { + $CMAKE_SYSTEM_VERSION = '' +} + +if(-Not (Test-Path $CMake)) { + throw "Unable to find CMake at $CMake" +} + +if(-Not (Test-Path $Git)) { + throw "Unable to find Git at $Git" +} + +if(-Not (Test-Path $SevenZ)) { + throw "Unable to find 7z at $SevenZ" +} + +if(-Not (Test-Path $GPG)) { + throw "Unable to find GPG at $GPG" +} + +Write-Host "Git: $Git" +Write-Host "CMake: $CMake" +Write-Host "7z: $SevenZ" +Write-Host "GPG: $GPG" + +New-Item -Type Directory ${BUILD} +New-Item -Type Directory ${BUILD}\32 +New-Item -Type Directory ${BUILD}\32\dynamic +New-Item -Type Directory ${BUILD}\32\static +New-Item -Type Directory ${BUILD}\64 +New-Item -Type Directory ${BUILD}\64\dynamic +New-Item -Type Directory ${BUILD}\64\static +New-Item -Type Directory ${OUTPUT} +New-Item -Type Directory ${OUTPUT}\pkg\Win64\Release\v142\dynamic +New-Item -Type Directory ${OUTPUT}\pkg\Win32\Release\v142\dynamic +New-Item -Type Directory ${OUTPUT}\pkg\Win64\Release\v142\static +New-Item -Type Directory ${OUTPUT}\pkg\Win32\Release\v142\static + +Push-Location ${BUILD} + +try { + if (Test-Path .\${LIBRESSL}) { + Remove-Item .\${LIBRESSL} -Recurse -ErrorAction Stop + } + + 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 -v --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})) { + Write-Host "Cloning ${LIBCBOR}..." + & $Git clone --branch ${LIBCBOR_BRANCH} ${LIBCBOR_GIT} ` + .\${LIBCBOR} + } + + if(-Not (Test-Path .\${ZLIB})) { + Write-Host "Cloning ${ZLIB}..." + & $Git clone --branch ${ZLIB_BRANCH} ${ZLIB_GIT} ` + .\${ZLIB} + } +} catch { + throw "Failed to fetch and verify dependencies" +} finally { + Pop-Location +} + +Function Build(${OUTPUT}, ${GENERATOR}, ${ARCH}, ${SHARED}, ${FLAGS}) { + if (-Not (Test-Path .\${LIBRESSL})) { + New-Item -Type Directory .\${LIBRESSL} -ErrorAction Stop + } + + Push-Location .\${LIBRESSL} + & $CMake ..\..\..\${LIBRESSL} -G "${GENERATOR}" -A "${ARCH}" ` + -DBUILD_SHARED_LIBS="${SHARED}" -DLIBRESSL_TESTS=OFF ` + -DCMAKE_C_FLAGS_RELEASE="${FLAGS} /Zi /guard:cf /sdl" ` + -DCMAKE_INSTALL_PREFIX="${OUTPUT}" "${CMAKE_SYSTEM_VERSION}" + & $CMake --build . --config Release --verbose + & $CMake --build . --config Release --target install --verbose + Pop-Location + + if (-Not (Test-Path .\${LIBCBOR})) { + New-Item -Type Directory .\${LIBCBOR} -ErrorAction Stop + } + + Push-Location .\${LIBCBOR} + & $CMake ..\..\..\${LIBCBOR} -G "${GENERATOR}" -A "${ARCH}" ` + -DBUILD_SHARED_LIBS="${SHARED}" ` + -DCMAKE_C_FLAGS_RELEASE="${FLAGS} /Zi /guard:cf /sdl" ` + -DCMAKE_INSTALL_PREFIX="${OUTPUT}" "${CMAKE_SYSTEM_VERSION}" + & $CMake --build . --config Release --verbose + & $CMake --build . --config Release --target install --verbose + Pop-Location + + if(-Not (Test-Path .\${ZLIB})) { + New-Item -Type Directory .\${ZLIB} -ErrorAction Stop + } + + Push-Location .\${ZLIB} + & $CMake ..\..\..\${ZLIB} -G "${GENERATOR}" -A "${ARCH}" ` + -DBUILD_SHARED_LIBS="${SHARED}" ` + -DCMAKE_C_FLAGS_RELEASE="${FLAGS} /Zi /guard:cf /sdl" ` + -DCMAKE_INSTALL_PREFIX="${OUTPUT}" "${CMAKE_SYSTEM_VERSION}" + & $CMake --build . --config Release --verbose + & $CMake --build . --config Release --target install --verbose + Pop-Location + + & $CMake ..\..\.. -G "${GENERATOR}" -A "${ARCH}" ` + -DBUILD_SHARED_LIBS="${SHARED}" ` + -DCBOR_INCLUDE_DIRS="${OUTPUT}\include" ` + -DCBOR_LIBRARY_DIRS="${OUTPUT}\lib" ` + -DZLIB_INCLUDE_DIRS="${OUTPUT}\include" ` + -DZLIB_LIBRARY_DIRS="${OUTPUT}\lib" ` + -DCRYPTO_INCLUDE_DIRS="${OUTPUT}\include" ` + -DCRYPTO_LIBRARY_DIRS="${OUTPUT}\lib" ` + -DCMAKE_C_FLAGS_RELEASE="${FLAGS} /Zi /guard:cf /sdl ${Fido2Flags}" ` + -DCMAKE_INSTALL_PREFIX="${OUTPUT}" "${CMAKE_SYSTEM_VERSION}" + & $CMake --build . --config Release --verbose + & $CMake --build . --config Release --target install --verbose + if ("${SHARED}" -eq "ON") { + "cbor.dll", "crypto-46.dll", "zlib1.dll" | %{ Copy-Item "${OUTPUT}\bin\$_" ` + -Destination "examples\Release" } + } +} + +Function Package-Headers() { + Copy-Item "${OUTPUT}\64\dynamic\include" -Destination "${OUTPUT}\pkg" ` + -Recurse -ErrorAction Stop +} + +Function Package-Dynamic(${SRC}, ${DEST}) { + Copy-Item "${SRC}\bin\cbor.dll" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}\lib\cbor.lib" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}\bin\zlib1.dll" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}\lib\zlib.lib" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}\bin\crypto-46.dll" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}\lib\crypto-46.lib" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}\bin\fido2.dll" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}\lib\fido2.lib" "${DEST}" -ErrorAction Stop +} + +Function Package-Static(${SRC}, ${DEST}) { + Copy-Item "${SRC}/lib/cbor.lib" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}/lib/zlib.lib" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}/lib/crypto-46.lib" "${DEST}" -ErrorAction Stop + Copy-Item "${SRC}/lib/fido2_static.lib" "${DEST}/fido2.lib" ` + -ErrorAction Stop +} + +Function Package-PDBs(${SRC}, ${DEST}) { + Copy-Item "${SRC}\${LIBRESSL}\crypto\crypto.dir\Release\vc142.pdb" ` + "${DEST}\crypto-46.pdb" -ErrorAction Stop + Copy-Item "${SRC}\${LIBCBOR}\src\cbor.dir\Release\vc142.pdb" ` + "${DEST}\cbor.pdb" -ErrorAction Stop + Copy-Item "${SRC}\${ZLIB}\zlib.dir\Release\vc142.pdb" ` + "${DEST}\zlib.pdb" -ErrorAction Stop + Copy-Item "${SRC}\src\fido2_shared.dir\Release\vc142.pdb" ` + "${DEST}\fido2.pdb" -ErrorAction Stop +} + +Function Package-Tools(${SRC}, ${DEST}) { + Copy-Item "${SRC}\tools\Release\fido2-assert.exe" ` + "${DEST}\fido2-assert.exe" -ErrorAction stop + Copy-Item "${SRC}\tools\Release\fido2-cred.exe" ` + "${DEST}\fido2-cred.exe" -ErrorAction stop + Copy-Item "${SRC}\tools\Release\fido2-token.exe" ` + "${DEST}\fido2-token.exe" -ErrorAction stop +} + +Push-Location ${BUILD}\64\dynamic +Build ${OUTPUT}\64\dynamic "Visual Studio 16 2019" "x64" "ON" "/MD" +Pop-Location +Push-Location ${BUILD}\32\dynamic +Build ${OUTPUT}\32\dynamic "Visual Studio 16 2019" "Win32" "ON" "/MD" +Pop-Location + +Push-Location ${BUILD}\64\static +Build ${OUTPUT}\64\static "Visual Studio 16 2019" "x64" "OFF" "/MT" +Pop-Location +Push-Location ${BUILD}\32\static +Build ${OUTPUT}\32\static "Visual Studio 16 2019" "Win32" "OFF" "/MT" +Pop-Location + +Package-Headers + +Package-Dynamic ${OUTPUT}\64\dynamic ${OUTPUT}\pkg\Win64\Release\v142\dynamic +Package-PDBs ${BUILD}\64\dynamic ${OUTPUT}\pkg\Win64\Release\v142\dynamic +Package-Tools ${BUILD}\64\dynamic ${OUTPUT}\pkg\Win64\Release\v142\dynamic + +Package-Dynamic ${OUTPUT}\32\dynamic ${OUTPUT}\pkg\Win32\Release\v142\dynamic +Package-PDBs ${BUILD}\32\dynamic ${OUTPUT}\pkg\Win32\Release\v142\dynamic +Package-Tools ${BUILD}\32\dynamic ${OUTPUT}\pkg\Win32\Release\v142\dynamic + +Package-Static ${OUTPUT}\64\static ${OUTPUT}\pkg\Win64\Release\v142\static +Package-Static ${OUTPUT}\32\static ${OUTPUT}\pkg\Win32\Release\v142\static diff --git a/windows/libressl.gpg b/windows/libressl.gpg new file mode 100644 index 0000000000000000000000000000000000000000..87d5dada4d6a1313727e0f4be89b47ea79d52403 GIT binary patch literal 16425 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 wb