diff options
225 files changed, 54621 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c4f7b1b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,498 @@ +# Copyright (c) 2018-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause +cmake_minimum_required(VERSION 3.7) + +# detect AppleClang; needs to come before project() +cmake_policy(SET CMP0025 NEW) + +project(libfido2 C) +# Set PIE flags for POSITION_INDEPENDENT_CODE targets, added in CMake 3.14. +if(POLICY CMP0083) + cmake_policy(SET CMP0083 NEW) +endif() + +include(CheckCCompilerFlag) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckSymbolExists) +include(CheckIncludeFiles) +include(CheckTypeSize) +include(GNUInstallDirs) +include(CheckPIESupported OPTIONAL RESULT_VARIABLE CHECK_PIE_SUPPORTED) +if(CHECK_PIE_SUPPORTED) + check_pie_supported(LANGUAGES C) +endif() + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_COLOR_MAKEFILE OFF) +set(CMAKE_VERBOSE_MAKEFILE ON) +set(FIDO_MAJOR "1") +set(FIDO_MINOR "14") +set(FIDO_PATCH "0") +set(FIDO_VERSION ${FIDO_MAJOR}.${FIDO_MINOR}.${FIDO_PATCH}) + +option(BUILD_TESTS "Build the regress tests" ON) +option(BUILD_EXAMPLES "Build example programs" ON) +option(BUILD_MANPAGES "Build man pages" ON) +option(BUILD_SHARED_LIBS "Build a shared library" ON) +option(BUILD_STATIC_LIBS "Build a static library" ON) +option(BUILD_TOOLS "Build tool programs" ON) +option(FUZZ "Enable fuzzing instrumentation" OFF) +option(USE_HIDAPI "Use hidapi as the HID backend" OFF) +option(USE_PCSC "Enable experimental PCSC support" OFF) +option(USE_WINHELLO "Abstract Windows Hello as a FIDO device" ON) +option(NFC_LINUX "Enable NFC support on Linux" ON) + +add_definitions(-D_FIDO_MAJOR=${FIDO_MAJOR}) +add_definitions(-D_FIDO_MINOR=${FIDO_MINOR}) +add_definitions(-D_FIDO_PATCH=${FIDO_PATCH}) + +if(BUILD_SHARED_LIBS) + set(_FIDO2_LIBRARY fido2_shared) +elseif(BUILD_STATIC_LIBS) + set(_FIDO2_LIBRARY fido2) +else() + message(FATAL_ERROR "Nothing to build (BUILD_*_LIBS=OFF)") +endif() + +if(CYGWIN OR MSYS OR MINGW) + set(WIN32 1) +endif() + +if(WIN32) + add_definitions(-DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0600) +endif() + +if(APPLE) + set(CMAKE_INSTALL_NAME_DIR + "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") +endif() + +if(NOT MSVC) + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_POSIX_C_SOURCE=200809L") + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_BSD_SOURCE") + if(APPLE) + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_DARWIN_C_SOURCE") + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D__STDC_WANT_LIB_EXT1__=1") + elseif((CMAKE_SYSTEM_NAME STREQUAL "Linux") OR MINGW OR CYGWIN) + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_GNU_SOURCE") + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_DEFAULT_SOURCE") + elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D__BSD_VISIBLE=1") + elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + set(FIDO_CFLAGS "${FIDO_CFLAGS} -D_NETBSD_SOURCE") + endif() + set(FIDO_CFLAGS "${FIDO_CFLAGS} -std=c99") + set(CMAKE_C_FLAGS "${FIDO_CFLAGS} ${CMAKE_C_FLAGS}") +endif() + +check_c_compiler_flag("-Wshorten-64-to-32" HAVE_SHORTEN_64_TO_32) +check_c_compiler_flag("-Werror -fstack-protector-all" HAVE_STACK_PROTECTOR_ALL) + +check_include_files(cbor.h HAVE_CBOR_H) +check_include_files(endian.h HAVE_ENDIAN_H) +check_include_files(err.h HAVE_ERR_H) +check_include_files(openssl/opensslv.h HAVE_OPENSSLV_H) +check_include_files(signal.h HAVE_SIGNAL_H) +check_include_files(sys/random.h HAVE_SYS_RANDOM_H) +check_include_files(unistd.h HAVE_UNISTD_H) + +check_symbol_exists(arc4random_buf stdlib.h HAVE_ARC4RANDOM_BUF) +check_symbol_exists(asprintf stdio.h HAVE_ASPRINTF) +check_symbol_exists(clock_gettime time.h HAVE_CLOCK_GETTIME) +check_symbol_exists(explicit_bzero string.h HAVE_EXPLICIT_BZERO) +check_symbol_exists(freezero stdlib.h HAVE_FREEZERO) +check_symbol_exists(getline stdio.h HAVE_GETLINE) +check_symbol_exists(getopt unistd.h HAVE_GETOPT) +check_symbol_exists(getpagesize unistd.h HAVE_GETPAGESIZE) +check_symbol_exists(getrandom sys/random.h HAVE_GETRANDOM) +check_symbol_exists(memset_s string.h HAVE_MEMSET_S) +check_symbol_exists(readpassphrase readpassphrase.h HAVE_READPASSPHRASE) +check_symbol_exists(recallocarray stdlib.h HAVE_RECALLOCARRAY) +check_symbol_exists(strlcat string.h HAVE_STRLCAT) +check_symbol_exists(strlcpy string.h HAVE_STRLCPY) +check_symbol_exists(strsep string.h HAVE_STRSEP) +check_symbol_exists(sysconf unistd.h HAVE_SYSCONF) +check_symbol_exists(timespecsub sys/time.h HAVE_TIMESPECSUB) +check_symbol_exists(timingsafe_bcmp string.h HAVE_TIMINGSAFE_BCMP) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +try_compile(HAVE_POSIX_IOCTL + "${CMAKE_CURRENT_BINARY_DIR}/posix_ioctl_check.o" + "${CMAKE_CURRENT_SOURCE_DIR}/openbsd-compat/posix_ioctl_check.c" + COMPILE_DEFINITIONS "-Werror -Woverflow -Wsign-conversion") + +list(APPEND CHECK_VARIABLES + HAVE_ARC4RANDOM_BUF + HAVE_ASPRINTF + HAVE_CBOR_H + HAVE_CLOCK_GETTIME + HAVE_ENDIAN_H + HAVE_ERR_H + HAVE_FREEZERO + HAVE_GETLINE + HAVE_GETOPT + HAVE_GETPAGESIZE + HAVE_GETRANDOM + HAVE_MEMSET_S + HAVE_OPENSSLV_H + HAVE_POSIX_IOCTL + HAVE_READPASSPHRASE + HAVE_RECALLOCARRAY + HAVE_SIGNAL_H + HAVE_STRLCAT + HAVE_STRLCPY + HAVE_STRSEP + HAVE_SYSCONF + HAVE_SYS_RANDOM_H + HAVE_TIMESPECSUB + HAVE_TIMINGSAFE_BCMP + HAVE_UNISTD_H +) + +foreach(v ${CHECK_VARIABLES}) + if (${v}) + add_definitions(-D${v}) + endif() +endforeach() + +if(HAVE_EXPLICIT_BZERO AND NOT FUZZ) + add_definitions(-DHAVE_EXPLICIT_BZERO) +endif() + +if(UNIX) + add_definitions(-DHAVE_DEV_URANDOM) +endif() + + +if(MSVC) + if((NOT CBOR_INCLUDE_DIRS) OR (NOT CBOR_LIBRARY_DIRS) OR + (NOT CRYPTO_INCLUDE_DIRS) OR (NOT CRYPTO_LIBRARY_DIRS) OR + (NOT ZLIB_INCLUDE_DIRS) OR (NOT ZLIB_LIBRARY_DIRS)) + message(FATAL_ERROR "please define " + "{CBOR,CRYPTO,ZLIB}_{INCLUDE,LIBRARY}_DIRS when " + "building under msvc") + endif() + if(BUILD_TESTS AND BUILD_SHARED_LIBS AND + ((NOT CBOR_BIN_DIRS) OR (NOT ZLIB_BIN_DIRS) OR (NOT CRYPTO_BIN_DIRS))) + message(FATAL_ERROR "please define {CBOR,CRYPTO,ZLIB}_BIN_DIRS " + "when building tests") + endif() + if(NOT CBOR_LIBRARIES) + set(CBOR_LIBRARIES cbor) + endif() + if(NOT ZLIB_LIBRARIES) + set(ZLIB_LIBRARIES zlib1) + endif() + if(NOT CRYPTO_LIBRARIES) + set(CRYPTO_LIBRARIES crypto) + endif() + + set(MSVC_DISABLED_WARNINGS_LIST + "C4152" # nonstandard extension used: function/data pointer + # conversion in expression; + "C4200" # nonstandard extension used: zero-sized array in + # struct/union; + "C4201" # nonstandard extension used: nameless struct/union; + "C4204" # nonstandard extension used: non-constant aggregate + # initializer; + "C4706" # assignment within conditional expression; + "C4996" # The POSIX name for this item is deprecated. Instead, + # use the ISO C and C++ conformant name; + "C6287" # redundant code: the left and right subexpressions are identical + ) + # The construction in the following 3 lines was taken from LibreSSL's + # CMakeLists.txt. + string(REPLACE "C" " -wd" MSVC_DISABLED_WARNINGS_STR + ${MSVC_DISABLED_WARNINGS_LIST}) + string(REGEX REPLACE "[/-]W[1234][ ]?" "" CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -MP -W4 -WX ${MSVC_DISABLED_WARNINGS_STR}") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Od /Z7 /guard:cf /sdl /RTCcsu") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zi /guard:cf /sdl") + if(USE_WINHELLO) + add_definitions(-DUSE_WINHELLO) + endif() + set(NFC_LINUX OFF) +else() + include(FindPkgConfig) + pkg_search_module(CBOR libcbor) + pkg_search_module(CRYPTO libcrypto) + pkg_search_module(ZLIB zlib) + + if(NOT CBOR_FOUND AND NOT HAVE_CBOR_H) + message(FATAL_ERROR "could not find libcbor") + endif() + if(NOT CRYPTO_FOUND AND NOT HAVE_OPENSSLV_H) + message(FATAL_ERROR "could not find libcrypto") + endif() + if(NOT ZLIB_FOUND) + message(FATAL_ERROR "could not find zlib") + endif() + + if(NOT CBOR_LIBRARIES) + set(CBOR_LIBRARIES "cbor") + endif() + if(NOT CRYPTO_LIBRARIES) + set(CRYPTO_LIBRARIES "crypto") + endif() + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + pkg_search_module(UDEV libudev REQUIRED) + set(UDEV_NAME "udev") + # If using hidapi, use hidapi-hidraw. + set(HIDAPI_SUFFIX -hidraw) + if(NOT HAVE_CLOCK_GETTIME) + # Look for clock_gettime in librt. + check_library_exists(rt clock_gettime "time.h" + HAVE_CLOCK_GETTIME) + if (HAVE_CLOCK_GETTIME) + add_definitions(-DHAVE_CLOCK_GETTIME) + set(BASE_LIBRARIES ${BASE_LIBRARIES} rt) + endif() + endif() + else() + set(NFC_LINUX OFF) + endif() + + if(MINGW) + # MinGW is stuck with a flavour of C89. + add_definitions(-DFIDO_NO_DIAGNOSTIC) + add_definitions(-DWC_ERR_INVALID_CHARS=0x80) + add_compile_options(-Wno-unused-parameter) + endif() + + if(FUZZ) + set(USE_PCSC ON) + add_definitions(-DFIDO_FUZZ) + endif() + + # If building with PCSC, look for pcsc-lite. + if(USE_PCSC AND NOT (APPLE OR CYGWIN OR MSYS OR MINGW)) + pkg_search_module(PCSC libpcsclite REQUIRED) + set(PCSC_LIBRARIES pcsclite) + endif() + + if(USE_HIDAPI) + add_definitions(-DUSE_HIDAPI) + pkg_search_module(HIDAPI hidapi${HIDAPI_SUFFIX} REQUIRED) + set(HIDAPI_LIBRARIES hidapi${HIDAPI_SUFFIX}) + endif() + + if(NFC_LINUX) + add_definitions(-DUSE_NFC) + endif() + + if(WIN32) + if(USE_WINHELLO) + add_definitions(-DUSE_WINHELLO) + endif() + else() + set(USE_WINHELLO OFF) + endif() + + add_compile_options(-Wall) + add_compile_options(-Wextra) + add_compile_options(-Werror) + add_compile_options(-Wshadow) + add_compile_options(-Wcast-qual) + add_compile_options(-Wwrite-strings) + add_compile_options(-Wmissing-prototypes) + add_compile_options(-Wbad-function-cast) + add_compile_options(-Wimplicit-fallthrough) + add_compile_options(-pedantic) + add_compile_options(-pedantic-errors) + + set(EXTRA_CFLAGS "-Wconversion -Wsign-conversion") + + if(WIN32) + add_compile_options(-Wno-type-limits) + add_compile_options(-Wno-cast-function-type) + endif() + + if(HAVE_SHORTEN_64_TO_32) + add_compile_options(-Wshorten-64-to-32) + endif() + + if(HAVE_STACK_PROTECTOR_ALL) + add_compile_options(-fstack-protector-all) + endif() + + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g2") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -D_FORTIFY_SOURCE=2") + + if(CRYPTO_VERSION VERSION_GREATER_EQUAL 3.0) + add_definitions(-DOPENSSL_API_COMPAT=0x10100000L) + endif() + + if(NOT FUZZ) + set(EXTRA_CFLAGS "${EXTRA_CFLAGS} -Wframe-larger-than=2047") + endif() +endif() + +# Avoid https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 +if(CMAKE_COMPILER_IS_GNUCC) + add_compile_options(-Wno-unused-result) +endif() + +# Decide which keyword to use for thread-local storage. +if(CMAKE_COMPILER_IS_GNUCC OR + CMAKE_C_COMPILER_ID STREQUAL "Clang" OR + CMAKE_C_COMPILER_ID STREQUAL "AppleClang") + set(TLS "__thread") +elseif(WIN32) + set(TLS "__declspec(thread)") +endif() +add_definitions(-DTLS=${TLS}) + +if(USE_PCSC) + add_definitions(-DUSE_PCSC) +endif() + +# export list +if(APPLE AND (CMAKE_C_COMPILER_ID STREQUAL "Clang" OR + CMAKE_C_COMPILER_ID STREQUAL "AppleClang")) + # clang + lld + string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} + " -exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/src/export.llvm") +elseif(NOT MSVC) + # clang/gcc + gnu ld + if(FUZZ) + string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} + " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/fuzz/export.gnu") + else() + string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} + " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/export.gnu") + endif() + if(NOT WIN32) + string(CONCAT CMAKE_SHARED_LINKER_FLAGS + ${CMAKE_SHARED_LINKER_FLAGS} + " -Wl,-z,noexecstack -Wl,-z,relro,-z,now") + string(CONCAT CMAKE_EXE_LINKER_FLAGS + ${CMAKE_EXE_LINKER_FLAGS} + " -Wl,-z,noexecstack -Wl,-z,relro,-z,now") + if(FUZZ) + file(STRINGS fuzz/wrapped.sym WRAPPED_SYMBOLS) + foreach(s ${WRAPPED_SYMBOLS}) + string(CONCAT CMAKE_SHARED_LINKER_FLAGS + ${CMAKE_SHARED_LINKER_FLAGS} + " -Wl,--wrap=${s}") + endforeach() + endif() + endif() +else() + string(CONCAT CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} + " /def:\"${CMAKE_CURRENT_SOURCE_DIR}/src/export.msvc\"") +endif() + +include_directories(${PROJECT_SOURCE_DIR}/src) +include_directories(${CBOR_INCLUDE_DIRS}) +include_directories(${CRYPTO_INCLUDE_DIRS}) +include_directories(${HIDAPI_INCLUDE_DIRS}) +include_directories(${PCSC_INCLUDE_DIRS}) +include_directories(${UDEV_INCLUDE_DIRS}) +include_directories(${ZLIB_INCLUDE_DIRS}) + +link_directories(${CBOR_LIBRARY_DIRS}) +link_directories(${CRYPTO_LIBRARY_DIRS}) +link_directories(${HIDAPI_LIBRARY_DIRS}) +link_directories(${PCSC_LIBRARY_DIRS}) +link_directories(${UDEV_LIBRARY_DIRS}) +link_directories(${ZLIB_LIBRARY_DIRS}) + +message(STATUS "BASE_LIBRARIES: ${BASE_LIBRARIES}") +message(STATUS "BUILD_EXAMPLES: ${BUILD_EXAMPLES}") +message(STATUS "BUILD_MANPAGES: ${BUILD_MANPAGES}") +message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") +message(STATUS "BUILD_STATIC_LIBS: ${BUILD_STATIC_LIBS}") +message(STATUS "BUILD_TOOLS: ${BUILD_TOOLS}") +message(STATUS "CBOR_INCLUDE_DIRS: ${CBOR_INCLUDE_DIRS}") +message(STATUS "CBOR_LIBRARIES: ${CBOR_LIBRARIES}") +message(STATUS "CBOR_LIBRARY_DIRS: ${CBOR_LIBRARY_DIRS}") +if(BUILD_TESTS) + message(STATUS "CBOR_BIN_DIRS: ${CBOR_BIN_DIRS}") +endif() +message(STATUS "CBOR_VERSION: ${CBOR_VERSION}") +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +message(STATUS "CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}") +message(STATUS "CMAKE_C_COMPILER_ID: ${CMAKE_C_COMPILER_ID}") +message(STATUS "CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}") +message(STATUS "CMAKE_CROSSCOMPILING: ${CMAKE_CROSSCOMPILING}") +message(STATUS "CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}") +message(STATUS "CMAKE_HOST_SYSTEM_NAME: ${CMAKE_HOST_SYSTEM_NAME}") +message(STATUS "CMAKE_HOST_SYSTEM_PROCESSOR: ${CMAKE_HOST_SYSTEM_PROCESSOR}") +message(STATUS "CMAKE_INSTALL_LIBDIR: ${CMAKE_INSTALL_LIBDIR}") +message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") +message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") +message(STATUS "CMAKE_SYSTEM_VERSION: ${CMAKE_SYSTEM_VERSION}") +message(STATUS "CRYPTO_INCLUDE_DIRS: ${CRYPTO_INCLUDE_DIRS}") +message(STATUS "CRYPTO_LIBRARIES: ${CRYPTO_LIBRARIES}") +message(STATUS "CRYPTO_LIBRARY_DIRS: ${CRYPTO_LIBRARY_DIRS}") +if(BUILD_TESTS) + message(STATUS "CRYPTO_BIN_DIRS: ${CRYPTO_BIN_DIRS}") +endif() +message(STATUS "CRYPTO_VERSION: ${CRYPTO_VERSION}") +message(STATUS "FIDO_VERSION: ${FIDO_VERSION}") +message(STATUS "FUZZ: ${FUZZ}") +if(FUZZ) + message(STATUS "FUZZ_LDFLAGS: ${FUZZ_LDFLAGS}") +endif() +message(STATUS "ZLIB_INCLUDE_DIRS: ${ZLIB_INCLUDE_DIRS}") +message(STATUS "ZLIB_LIBRARIES: ${ZLIB_LIBRARIES}") +message(STATUS "ZLIB_LIBRARY_DIRS: ${ZLIB_LIBRARY_DIRS}") +if(BUILD_TESTS) + message(STATUS "ZLIB_BIN_DIRS: ${ZLIB_BIN_DIRS}") +endif() +message(STATUS "ZLIB_VERSION: ${ZLIB_VERSION}") +if(USE_HIDAPI) + message(STATUS "HIDAPI_INCLUDE_DIRS: ${HIDAPI_INCLUDE_DIRS}") + message(STATUS "HIDAPI_LIBRARIES: ${HIDAPI_LIBRARIES}") + message(STATUS "HIDAPI_LIBRARY_DIRS: ${HIDAPI_LIBRARY_DIRS}") + message(STATUS "HIDAPI_VERSION: ${HIDAPI_VERSION}") +endif() +message(STATUS "PCSC_INCLUDE_DIRS: ${PCSC_INCLUDE_DIRS}") +message(STATUS "PCSC_LIBRARIES: ${PCSC_LIBRARIES}") +message(STATUS "PCSC_LIBRARY_DIRS: ${PCSC_LIBRARY_DIRS}") +message(STATUS "PCSC_VERSION: ${PCSC_VERSION}") +message(STATUS "TLS: ${TLS}") +message(STATUS "UDEV_INCLUDE_DIRS: ${UDEV_INCLUDE_DIRS}") +message(STATUS "UDEV_LIBRARIES: ${UDEV_LIBRARIES}") +message(STATUS "UDEV_LIBRARY_DIRS: ${UDEV_LIBRARY_DIRS}") +message(STATUS "UDEV_RULES_DIR: ${UDEV_RULES_DIR}") +message(STATUS "UDEV_VERSION: ${UDEV_VERSION}") +message(STATUS "USE_HIDAPI: ${USE_HIDAPI}") +message(STATUS "USE_PCSC: ${USE_PCSC}") +message(STATUS "USE_WINHELLO: ${USE_WINHELLO}") +message(STATUS "NFC_LINUX: ${NFC_LINUX}") + +if(BUILD_TESTS) + enable_testing() +endif() + +add_subdirectory(src) + +if(BUILD_TESTS) + add_subdirectory(regress) +endif() +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif() +if(BUILD_TOOLS) + add_subdirectory(tools) +endif() +if(BUILD_MANPAGES) + add_subdirectory(man) +endif() + +if(NOT WIN32) + if(FUZZ) + add_subdirectory(fuzz) + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_subdirectory(udev) + endif() +endif() @@ -0,0 +1,26 @@ +Copyright (c) 2018-2023 Yubico AB. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +SPDX-License-Identifier: BSD-2-Clause @@ -0,0 +1,266 @@ +* Version 1.14.0 (2023-11-13) + ** fido2-cred -M, fido2-token -G: support raw client data via -w flag. + ** winhello: support U2F AppID extension for assertions. + ** winhello: fix restrictive parsing of the hmac-secret on assertions. + ** winhello: translate NTE_USER_CANCELLED to FIDO_ERR_OPERATION_DENIED; gh#685. + ** New API calls: + ** fido_assert_authdata_raw_len; + ** fido_assert_authdata_raw_ptr; + ** fido_assert_set_winhello_appid. + +* Version 1.13.0 (2023-02-20) + ** Support for linking against OpenSSL on Windows; gh#668. + ** New API calls: + - fido_assert_empty_allow_list; + - fido_cred_empty_exclude_list. + ** fido2-token: fix issue when listing large blobs. + ** Improved support for different fuzzing engines. + +* Version 1.12.0 (2022-09-22) + ** Support for COSE_ES384. + ** Support for hidraw(4) on FreeBSD; gh#597. + ** Improved support for FIDO 2.1 authenticators. + ** New API calls: + - es384_pk_free; + - es384_pk_from_EC_KEY; + - es384_pk_from_EVP_PKEY; + - es384_pk_from_ptr; + - es384_pk_new; + - es384_pk_to_EVP_PKEY; + - fido_cbor_info_certs_len; + - fido_cbor_info_certs_name_ptr; + - fido_cbor_info_certs_value_ptr; + - fido_cbor_info_maxrpid_minpinlen; + - fido_cbor_info_minpinlen; + - fido_cbor_info_new_pin_required; + - fido_cbor_info_rk_remaining; + - fido_cbor_info_uv_attempts; + - fido_cbor_info_uv_modality. + ** Documentation and reliability fixes. + +* Version 1.11.0 (2022-05-03) + ** Experimental PCSC support; enable with -DUSE_PCSC. + ** Improved OpenSSL 3.0 compatibility. + ** Use RFC1951 raw deflate to compress CTAP 2.1 largeBlobs. + ** winhello: advertise "uv" instead of "clientPin". + ** winhello: support hmac-secret in fido_dev_get_assert(). + ** New API calls: + - fido_cbor_info_maxlargeblob. + ** Documentation and reliability fixes. + ** Separate build and regress targets. + +* Version 1.10.0 (2022-01-17) + ** hid_osx: handle devices with paths > 511 bytes; gh#462. + ** bio: fix CTAP2 canonical CBOR encoding in fido_bio_dev_enroll_*(); gh#480. + ** winhello: fallback to GetTopWindow() if GetForegroundWindow() fails. + ** winhello: fallback to hid_win.c if webauthn.dll isn't available. + ** New API calls: + - fido_dev_info_set; + - fido_dev_io_handle; + - fido_dev_new_with_info; + - fido_dev_open_with_info. + ** Cygwin and NetBSD build fixes. + ** Documentation and reliability fixes. + ** Support for TPM 2.0 attestation of COSE_ES256 credentials. + +* Version 1.9.0 (2021-10-27) + ** Enabled NFC support on Linux. + ** Added OpenSSL 3.0 compatibility. + ** Removed OpenSSL 1.0 compatibility. + ** Support for FIDO 2.1 "minPinLength" extension. + ** Support for COSE_EDDSA, COSE_ES256, and COSE_RS1 attestation. + ** Support for TPM 2.0 attestation. + ** Support for device timeouts; see fido_dev_set_timeout(). + ** New API calls: + - es256_pk_from_EVP_PKEY; + - fido_cred_attstmt_len; + - fido_cred_attstmt_ptr; + - fido_cred_pin_minlen; + - fido_cred_set_attstmt; + - fido_cred_set_pin_minlen; + - fido_dev_set_pin_minlen_rpid; + - fido_dev_set_timeout; + - rs256_pk_from_EVP_PKEY. + ** Reliability and portability fixes. + ** Better handling of HID devices without identification strings; gh#381. + ** Fixed detection of Windows's native webauthn API; gh#382. + +* Version 1.8.0 (2021-07-22) + ** Dropped 'Requires.private' entry from pkg-config file. + ** Better support for FIDO 2.1 authenticators. + ** Support for Windows's native webauthn API. + ** Support for attestation format 'none'. + ** New API calls: + - fido_assert_set_clientdata; + - fido_cbor_info_algorithm_cose; + - fido_cbor_info_algorithm_count; + - fido_cbor_info_algorithm_type; + - fido_cbor_info_transports_len; + - fido_cbor_info_transports_ptr; + - fido_cred_set_clientdata; + - fido_cred_set_id; + - fido_credman_set_dev_rk; + - fido_dev_is_winhello. + ** fido2-token: new -Sc option to update a resident credential. + ** Documentation and reliability fixes. + ** HID access serialisation on Linux. + +* Version 1.7.0 (2021-03-29) + ** New dependency on zlib. + ** Fixed musl build; gh#259. + ** hid_win: detect devices with vendor or product IDs > 0x7fff; gh#264. + ** Support for FIDO 2.1 authenticator configuration. + ** Support for FIDO 2.1 UV token permissions. + ** Support for FIDO 2.1 "credBlobs" and "largeBlobs" extensions. + ** New API calls: + - fido_assert_blob_len; + - fido_assert_blob_ptr; + - fido_assert_largeblob_key_len; + - fido_assert_largeblob_key_ptr; + - fido_assert_set_hmac_secret; + - fido_cbor_info_maxcredbloblen; + - fido_cred_largeblob_key_len; + - fido_cred_largeblob_key_ptr; + - fido_cred_set_blob; + - fido_dev_enable_entattest; + - fido_dev_force_pin_change; + - fido_dev_has_uv; + - fido_dev_largeblob_get; + - fido_dev_largeblob_get_array; + - fido_dev_largeblob_remove; + - fido_dev_largeblob_set; + - fido_dev_largeblob_set_array; + - fido_dev_set_pin_minlen; + - fido_dev_set_sigmask; + - fido_dev_supports_credman; + - fido_dev_supports_permissions; + - fido_dev_supports_uv; + - fido_dev_toggle_always_uv. + ** New fido_init flag to disable fido_dev_open's U2F fallback; gh#282. + ** Experimental NFC support on Linux; enable with -DNFC_LINUX. + +* Version 1.6.0 (2020-12-22) + ** Fix OpenSSL 1.0 and Cygwin builds. + ** hid_linux: fix build on 32-bit systems. + ** hid_osx: allow reads from spawned threads. + ** Documentation and reliability fixes. + ** New API calls: + - fido_cred_authdata_raw_len; + - fido_cred_authdata_raw_ptr; + - fido_cred_sigcount; + - fido_dev_get_uv_retry_count; + - fido_dev_supports_credman. + ** Hardened Windows build. + ** Native FreeBSD and NetBSD support. + ** Use CTAP2 canonical CBOR when combining hmac-secret and credProtect. + +* Version 1.5.0 (2020-09-01) + ** hid_linux: return FIDO_OK if no devices are found. + ** hid_osx: + - repair communication with U2F tokens, gh#166; + - reliability fixes. + ** fido2-{assert,cred}: new options to explicitly toggle UP, UV. + ** Support for configurable report lengths. + ** New API calls: + - fido_cbor_info_maxcredcntlst; + - fido_cbor_info_maxcredidlen; + - fido_cred_aaguid_len; + - fido_cred_aaguid_ptr; + - fido_dev_get_touch_begin; + - fido_dev_get_touch_status. + ** Use COSE_ECDH_ES256 with CTAP_CBOR_CLIENT_PIN; gh#154. + ** Allow CTAP messages up to 2048 bytes; gh#171. + ** Ensure we only list USB devices by default. + +* Version 1.4.0 (2020-04-15) + ** hid_hidapi: hidapi backend; enable with -DUSE_HIDAPI=1. + ** Fall back to U2F if the key claims to, but does not support FIDO2. + ** FIDO2 credential protection (credprot) support. + ** New API calls: + - fido_cbor_info_fwversion; + - fido_cred_prot; + - fido_cred_set_prot; + - fido_dev_set_transport_functions; + - fido_set_log_handler. + ** Support for FreeBSD. + ** Support for C++. + ** Support for MSYS. + ** Fixed EdDSA and RSA self-attestation. + +* Version 1.3.1 (2020-02-19) + ** fix zero-ing of le1 and le2 when talking to a U2F device. + ** dropping sk-libfido2 middleware, please find it in the openssh tree. + +* Version 1.3.0 (2019-11-28) + ** assert/hmac: encode public key as per spec, gh#60. + ** fido2-cred: fix creation of resident keys. + ** fido2-{assert,cred}: support for hmac-secret extension. + ** hid_osx: detect device removal, gh#56. + ** hid_osx: fix device detection in MacOS Catalina. + ** New API calls: + - fido_assert_set_authdata_raw; + - fido_assert_sigcount; + - fido_cred_set_authdata_raw; + - fido_dev_cancel. + ** Middleware library for use by OpenSSH. + ** Support for biometric enrollment. + ** Support for OpenBSD. + ** Support for self-attestation. + +* Version 1.2.0 (released 2019-07-26) + ** Credential management support. + ** New API reflecting FIDO's 3-state booleans (true, false, absent): + - fido_assert_set_up; + - fido_assert_set_uv; + - fido_cred_set_rk; + - fido_cred_set_uv. + ** Command-line tools for Windows. + ** Documentation and reliability fixes. + ** fido_{assert,cred}_set_options() are now marked as deprecated. + +* Version 1.1.0 (released 2019-05-08) + ** MacOS: fix IOKit crash on HID read. + ** Windows: fix contents of release file. + ** EdDSA (Ed25519) support. + ** fido_dev_make_cred: fix order of CBOR map keys. + ** fido_dev_get_assert: plug memory leak when operating on U2F devices. + +* Version 1.0.0 (released 2019-03-21) + ** Native HID support on Linux, MacOS, and Windows. + ** fido2-{assert,cred}: new -u option to force U2F on dual authenticators. + ** fido2-assert: support for multiple resident keys with the same RP. + ** Strict checks for CTAP2 compliance on received CBOR payloads. + ** Better fuzzing harnesses. + ** Documentation and reliability fixes. + +* Version 0.4.0 (released 2019-01-07) + ** fido2-assert: print the user id for resident credentials. + ** Fix encoding of COSE algorithms when making a credential. + ** Rework purpose of fido_cred_set_type; no ABI change. + ** Minor documentation and code fixes. + +* Version 0.3.0 (released 2018-09-11) + ** Various reliability fixes. + ** Merged fuzzing instrumentation. + ** Added regress tests. + ** Added support for FIDO 2's hmac-secret extension. + ** New API calls: + - fido_assert_hmac_secret_len; + - fido_assert_hmac_secret_ptr; + - fido_assert_set_extensions; + - fido_assert_set_hmac_salt; + - fido_cred_set_extensions; + - fido_dev_force_fido2. + ** Support for native builds with Microsoft Visual Studio 17. + +* Version 0.2.0 (released 2018-06-20) + ** Added command-line tools. + ** Added a couple of missing get functions. + +* Version 0.1.1 (released 2018-06-05) + ** Added documentation. + ** Added OpenSSL 1.0 support. + ** Minor fixes. + +* Version 0.1.0 (released 2018-05-18) + ** First beta release. diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..fb6f3d3 --- /dev/null +++ b/README.adoc @@ -0,0 +1,144 @@ +== libfido2 + +image:https://github.com/yubico/libfido2/workflows/linux/badge.svg["Linux Build Status (github actions)", link="https://github.com/Yubico/libfido2/actions"] +image:https://github.com/yubico/libfido2/workflows/macos/badge.svg["macOS Build Status (github actions)", link="https://github.com/Yubico/libfido2/actions"] +image:https://github.com/yubico/libfido2/workflows/windows/badge.svg["Windows Build Status (github actions)", link="https://github.com/Yubico/libfido2/actions"] +image:https://github.com/yubico/libfido2/workflows/fuzzer/badge.svg["Fuzz Status (github actions)", link="https://github.com/Yubico/libfido2/actions"] +image:https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfido2.svg["Fuzz Status (oss-fuzz)", link="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:libfido2"] + +*libfido2* provides library functionality and command-line tools to +communicate with a FIDO device over USB or NFC, and to verify attestation and +assertion signatures. + +*libfido2* supports the FIDO U2F (CTAP 1) and FIDO2 (CTAP 2) protocols. + +For usage, see the `examples/` directory. + +=== License + +*libfido2* is licensed under the BSD 2-clause license. See the LICENSE +file for the full license text. + +=== Supported Platforms + +*libfido2* is known to work on Linux, macOS, Windows, OpenBSD, and FreeBSD. + +=== Documentation + +Documentation is available in troff and HTML formats. An +https://developers.yubico.com/libfido2/Manuals/[online mirror of *libfido2*'s documentation] +is also available. + +=== Bindings + +* .NET: https://github.com/borrrden/Fido2Net[Fido2Net] +* Go: https://github.com/keys-pub/go-libfido2[go-libfido2] +* Perl: https://github.com/jacquesg/p5-FIDO-Raw[p5-FIDO-Raw] +* Rust: https://github.com/PvdBerg1998/libfido2[libfido2] + +=== Releases + +The current release of *libfido2* is 1.14.0. Signed release tarballs are +available at Yubico's +https://developers.yubico.com/libfido2/Releases[release page]. + +=== Dependencies + +*libfido2* depends on https://github.com/pjk/libcbor[libcbor], +https://www.openssl.org[OpenSSL] 1.1 or newer, and https://zlib.net[zlib]. +On Linux, libudev +(part of https://www.freedesktop.org/wiki/Software/systemd[systemd]) is also +required. + +=== Installation + +==== Fedora 35 and 34 + + $ sudo dnf install libfido2 libfido2-devel fido2-tools + +==== Ubuntu 22.04 (Jammy) and 20.04 (Focal) + + $ sudo apt install libfido2-1 libfido2-dev libfido2-doc fido2-tools + +Alternatively, newer versions of *libfido2* are available in Yubico's PPA. +Follow the instructions for Ubuntu 18.04 (Bionic) below. + +==== Ubuntu 18.04 (Bionic) + + $ sudo apt install software-properties-common + $ sudo apt-add-repository ppa:yubico/stable + $ sudo apt update + $ sudo apt install libfido2-1 libfido2-dev libfido2-doc fido2-tools + +On Linux, you may need to add a udev rule to be able to access the FIDO +device. For example, the udev rule may contain the following: + +---- +#udev rule for allowing HID access to Yubico devices for FIDO support. + +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", \ + MODE="0664", GROUP="plugdev", ATTRS{idVendor}=="1050" +---- + +==== macOS + + $ brew install libfido2 + +==== Windows + +Please consult Yubico's +https://developers.yubico.com/libfido2/Releases[release page] for ARM, ARM64, +Win32, and Win64 artefacts. + +=== Building from source + +On UNIX-like systems: + + $ cmake -B build + $ make -C build + $ sudo make -C build install + +Depending on the platform, +https://www.freedesktop.org/wiki/Software/pkg-config/[pkg-config] may need to +be installed, or the PKG_CONFIG_PATH environment variable set. For complete, +OS-specific build instructions, please refer to the `.actions/` +(Linux, macOS, BSD) and `windows/` directories. + +=== Build-time Customisation + +*libfido2* supports a number of CMake options. Some of the options require +additional dependencies. Options that are disabled by default are not +officially supported. + +[%autowidth.stretch] +|=== +|*Option* |*Description* |*Default* +| BUILD_EXAMPLES | Build example programs | ON +| BUILD_MANPAGES | Build man pages | ON +| BUILD_SHARED_LIBS | Build a shared library | ON +| BUILD_STATIC_LIBS | Build a static library | ON +| BUILD_TOOLS | Build auxiliary tools | ON +| FUZZ | Enable fuzzing instrumentation | OFF +| NFC_LINUX | Enable netlink NFC support on Linux | ON +| USE_HIDAPI | Use hidapi as the HID backend | OFF +| USE_PCSC | Enable experimental PCSC support | OFF +| USE_WINHELLO | Abstract Windows Hello as a FIDO device | ON +|=== + +The USE_HIDAPI option requires https://github.com/libusb/hidapi[hidapi]. The +USE_PCSC option requires https://github.com/LudovicRousseau/PCSC[pcsc-lite] on +Linux. + +=== Development + +Please use https://github.com/Yubico/libfido2/discussions[GitHub Discussions] +to ask questions and suggest features, and +https://github.com/Yubico/libfido2/pulls[GitHub pull-requests] for code +contributions. + +=== Reporting bugs + +Please use https://github.com/Yubico/libfido2/issues[GitHub Issues] to report +bugs. To report security issues, please contact security@yubico.com. A PGP +public key can be found at +https://www.yubico.com/support/security-advisories/issue-rating-system/. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e12a48a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Reporting libfido2 Security Issues + +To report security issues in libfido2, please contact security@yubico.com. +A PGP public key can be found at +https://www.yubico.com/support/security-advisories/issue-rating-system/. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..f013df4 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,59 @@ +# Copyright (c) 2018 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/clock_gettime.c + ../openbsd-compat/getopt_long.c + ../openbsd-compat/strlcat.c + ../openbsd-compat/strlcpy.c +) + +if(WIN32 AND BUILD_SHARED_LIBS AND NOT CYGWIN AND NOT MSYS) + list(APPEND COMPAT_SOURCES ../openbsd-compat/posix_win.c) +endif() + +# enable -Wconversion -Wsign-conversion +if(NOT MSVC) + set_source_files_properties(assert.c cred.c info.c manifest.c reset.c + retries.c setpin.c util.c PROPERTIES COMPILE_FLAGS + "-Wconversion -Wsign-conversion") +endif() + +# manifest +add_executable(manifest manifest.c ${COMPAT_SOURCES}) +target_link_libraries(manifest ${_FIDO2_LIBRARY}) + +# info +add_executable(info info.c ${COMPAT_SOURCES}) +target_link_libraries(info ${_FIDO2_LIBRARY}) + +# reset +add_executable(reset reset.c util.c ${COMPAT_SOURCES}) +target_link_libraries(reset ${_FIDO2_LIBRARY}) + +# cred +add_executable(cred cred.c util.c ${COMPAT_SOURCES}) +target_link_libraries(cred ${_FIDO2_LIBRARY}) + +# assert +add_executable(assert assert.c util.c ${COMPAT_SOURCES}) +target_link_libraries(assert ${_FIDO2_LIBRARY}) + +# setpin +add_executable(setpin setpin.c ${COMPAT_SOURCES}) +target_link_libraries(setpin ${_FIDO2_LIBRARY}) + +# retries +add_executable(retries retries.c ${COMPAT_SOURCES}) +target_link_libraries(retries ${_FIDO2_LIBRARY}) + +# select +add_executable(select select.c ${COMPAT_SOURCES}) +target_link_libraries(select ${_FIDO2_LIBRARY}) + +if(MINGW) + # needed for nanosleep() in mingw + target_link_libraries(select winpthread) +endif() diff --git a/examples/README.adoc b/examples/README.adoc new file mode 100644 index 0000000..6151b70 --- /dev/null +++ b/examples/README.adoc @@ -0,0 +1,100 @@ += Examples + +=== Definitions + +The following definitions are used in the description below: + +- <device> + + The file system path or subsystem-specific identification string of a + FIDO device. + +- <pin>, [oldpin] + + Strings passed directly in the executed command's argument vector. + +- <cred_id> + + The file system path of a file containing a FIDO credential ID in + binary representation. + +- <pubkey> + + The file system path of a file containing a public key in PEM format. + +- <blobkey> + + A credential's associated CTAP 2.1 "largeBlob" symmetric key. + +=== Description + +The following examples are provided: + +- manifest + + Prints a list of configured FIDO devices. + +- info <device> + + Prints information about <device>. + +- reset <device> + + Performs a factory reset on <device>. + +- setpin <pin> [oldpin] <device> + + Configures <pin> as the new PIN of <device>. If [oldpin] is provided, + the device's PIN is changed from [oldpin] to <pin>. + +- cred [-t es256|es384|rs256|eddsa] [-k pubkey] [-ei cred_id] [-P pin] + [-T seconds] [-b blobkey] [-hruv] [-c cred_protect] <device> + + Creates a new credential on <device> and verify that the credential + was signed by the authenticator. The device's attestation certificate + is not verified. If option -k is specified, the credential's public + key is stored in <pubkey>. If option -i is specified, the credential + ID is stored in <cred_id>. The -e option may be used to add <cred_id> + to the list of excluded credentials. If option -h is specified, + the hmac-secret FIDO2 extension is enabled on the generated + credential. If option -r is specified, the generated credential + will involve a resident key. User verification may be requested + through the -v option. If option -u is specified, the credential + is generated using U2F (CTAP1) instead of FIDO2 (CTAP2) commands. + The -T option may be used to enforce a timeout of <seconds>. If the + option -b is specified, the credential's "largeBlob" key is stored in + <blobkey>. If the option -c is specified the the generated credential + will be bound by the specified protection policy. + +- assert [-t es256|es384|rs256|eddsa] [-a cred_id] [-h hmac_secret] [-P pin] + [-s hmac_salt] [-T seconds] [-b blobkey] [-puv] <pubkey> <device> + + Asks <device> for a FIDO2 assertion corresponding to [cred_id], + which may be omitted for resident keys. The obtained assertion + is verified using <pubkey>. The -p option requests that the user + be present and checks whether the user presence bit was signed by the + authenticator. The -v option requests user verification and checks + whether the user verification bit was signed by the authenticator. + If option -u is specified, the assertion is generated using + U2F (CTAP1) instead of FIDO2 (CTAP2) commands. If option -s is + specified, a FIDO2 hmac-secret is requested from the authenticator, + and the contents of <hmac_salt> are used as the salt. If option -h + is specified, the resulting hmac-secret is stored in <hmac_secret>. + The -T option may be used to enforce a timeout of <seconds>. If the + option -b specified, the credential's "largeBlob" key is stored in + <blobkey>. + +- retries <device> + Get the number of PIN attempts left on <device> before lockout. + +- select + + Enumerates available FIDO devices and, if more than one is present, + simultaneously requests touch on all of them, printing information + about the device touched. + +Debugging is possible through the use of the FIDO_DEBUG environment variable. +If set, libfido2 will produce a log of its transactions with the authenticator. + +Additionally, an example of a WebAuthn client using libfido2 is available at +https://github.com/martelletto/fido2-webauthn-client. diff --git a/examples/assert.c b/examples/assert.c new file mode 100644 index 0000000..32ba97b --- /dev/null +++ b/examples/assert.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <fido/es256.h> +#include <fido/es384.h> +#include <fido/rs256.h> +#include <fido/eddsa.h> + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static const unsigned char cd[32] = { + 0xec, 0x8d, 0x8f, 0x78, 0x42, 0x4a, 0x2b, 0xb7, + 0x82, 0x34, 0xaa, 0xca, 0x07, 0xa1, 0xf6, 0x56, + 0x42, 0x1c, 0xb6, 0xf6, 0xb3, 0x00, 0x86, 0x52, + 0x35, 0x2d, 0xa2, 0x62, 0x4a, 0xbe, 0x89, 0x76, +}; + +static void +usage(void) +{ + fprintf(stderr, "usage: assert [-t es256|es384|rs256|eddsa] " + "[-a cred_id] [-h hmac_secret] [-s hmac_salt] [-P pin] " + "[-T seconds] [-b blobkey] [-puv] <pubkey> <device>\n"); + exit(EXIT_FAILURE); +} + +static void +verify_assert(int type, const unsigned char *authdata_ptr, size_t authdata_len, + const unsigned char *sig_ptr, size_t sig_len, bool up, bool uv, int ext, + const char *key) +{ + fido_assert_t *assert = NULL; + EC_KEY *ec = NULL; + RSA *rsa = NULL; + EVP_PKEY *eddsa = NULL; + es256_pk_t *es256_pk = NULL; + es384_pk_t *es384_pk = NULL; + rs256_pk_t *rs256_pk = NULL; + eddsa_pk_t *eddsa_pk = NULL; + void *pk; + int r; + + /* credential pubkey */ + switch (type) { + case COSE_ES256: + if ((ec = read_ec_pubkey(key)) == NULL) + errx(1, "read_ec_pubkey"); + + if ((es256_pk = es256_pk_new()) == NULL) + errx(1, "es256_pk_new"); + + if (es256_pk_from_EC_KEY(es256_pk, ec) != FIDO_OK) + errx(1, "es256_pk_from_EC_KEY"); + + pk = es256_pk; + EC_KEY_free(ec); + ec = NULL; + + break; + case COSE_ES384: + if ((ec = read_ec_pubkey(key)) == NULL) + errx(1, "read_ec_pubkey"); + + if ((es384_pk = es384_pk_new()) == NULL) + errx(1, "es384_pk_new"); + + if (es384_pk_from_EC_KEY(es384_pk, ec) != FIDO_OK) + errx(1, "es384_pk_from_EC_KEY"); + + pk = es384_pk; + EC_KEY_free(ec); + ec = NULL; + + break; + case COSE_RS256: + if ((rsa = read_rsa_pubkey(key)) == NULL) + errx(1, "read_rsa_pubkey"); + + if ((rs256_pk = rs256_pk_new()) == NULL) + errx(1, "rs256_pk_new"); + + if (rs256_pk_from_RSA(rs256_pk, rsa) != FIDO_OK) + errx(1, "rs256_pk_from_RSA"); + + pk = rs256_pk; + RSA_free(rsa); + rsa = NULL; + + break; + case COSE_EDDSA: + if ((eddsa = read_eddsa_pubkey(key)) == NULL) + errx(1, "read_eddsa_pubkey"); + + if ((eddsa_pk = eddsa_pk_new()) == NULL) + errx(1, "eddsa_pk_new"); + + if (eddsa_pk_from_EVP_PKEY(eddsa_pk, eddsa) != FIDO_OK) + errx(1, "eddsa_pk_from_EVP_PKEY"); + + pk = eddsa_pk; + EVP_PKEY_free(eddsa); + eddsa = NULL; + + break; + default: + errx(1, "unknown credential type %d", type); + } + + if ((assert = fido_assert_new()) == NULL) + errx(1, "fido_assert_new"); + + /* client data hash */ + r = fido_assert_set_clientdata(assert, cd, sizeof(cd)); + if (r != FIDO_OK) + errx(1, "fido_assert_set_clientdata: %s (0x%x)", fido_strerr(r), r); + + /* relying party */ + r = fido_assert_set_rp(assert, "localhost"); + if (r != FIDO_OK) + errx(1, "fido_assert_set_rp: %s (0x%x)", fido_strerr(r), r); + + /* authdata */ + r = fido_assert_set_count(assert, 1); + if (r != FIDO_OK) + errx(1, "fido_assert_set_count: %s (0x%x)", fido_strerr(r), r); + r = fido_assert_set_authdata(assert, 0, authdata_ptr, authdata_len); + if (r != FIDO_OK) + errx(1, "fido_assert_set_authdata: %s (0x%x)", fido_strerr(r), r); + + /* extension */ + r = fido_assert_set_extensions(assert, ext); + if (r != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s (0x%x)", fido_strerr(r), + r); + + /* user presence */ + if (up && (r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_up: %s (0x%x)", fido_strerr(r), r); + + /* user verification */ + if (uv && (r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_uv: %s (0x%x)", fido_strerr(r), r); + + /* sig */ + r = fido_assert_set_sig(assert, 0, sig_ptr, sig_len); + if (r != FIDO_OK) + errx(1, "fido_assert_set_sig: %s (0x%x)", fido_strerr(r), r); + + r = fido_assert_verify(assert, 0, type, pk); + if (r != FIDO_OK) + errx(1, "fido_assert_verify: %s (0x%x)", fido_strerr(r), r); + + es256_pk_free(&es256_pk); + es384_pk_free(&es384_pk); + rs256_pk_free(&rs256_pk); + eddsa_pk_free(&eddsa_pk); + + fido_assert_free(&assert); +} + +int +main(int argc, char **argv) +{ + bool up = false; + bool uv = false; + bool u2f = false; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + const char *pin = NULL; + const char *blobkey_out = NULL; + const char *hmac_out = NULL; + unsigned char *body = NULL; + long long ms = 0; + size_t len; + int type = COSE_ES256; + int ext = 0; + int ch; + int r; + + if ((assert = fido_assert_new()) == NULL) + errx(1, "fido_assert_new"); + + while ((ch = getopt(argc, argv, "P:T:a:b:h:ps:t:uv")) != -1) { + switch (ch) { + case 'P': + pin = optarg; + break; + case 'T': + if (base10(optarg, &ms) < 0) + errx(1, "base10: %s", optarg); + if (ms <= 0 || ms > 30) + errx(1, "-T: %s must be in (0,30]", optarg); + ms *= 1000; /* seconds to milliseconds */ + break; + case 'a': + if (read_blob(optarg, &body, &len) < 0) + errx(1, "read_blob: %s", optarg); + if ((r = fido_assert_allow_cred(assert, body, + len)) != FIDO_OK) + errx(1, "fido_assert_allow_cred: %s (0x%x)", + fido_strerr(r), r); + free(body); + body = NULL; + break; + case 'b': + ext |= FIDO_EXT_LARGEBLOB_KEY; + blobkey_out = optarg; + break; + case 'h': + hmac_out = optarg; + break; + case 'p': + up = true; + break; + case 's': + ext |= FIDO_EXT_HMAC_SECRET; + if (read_blob(optarg, &body, &len) < 0) + errx(1, "read_blob: %s", optarg); + if ((r = fido_assert_set_hmac_salt(assert, body, + len)) != FIDO_OK) + errx(1, "fido_assert_set_hmac_salt: %s (0x%x)", + fido_strerr(r), r); + free(body); + body = NULL; + break; + case 't': + if (strcmp(optarg, "es256") == 0) + type = COSE_ES256; + else if (strcmp(optarg, "es384") == 0) + type = COSE_ES384; + else if (strcmp(optarg, "rs256") == 0) + type = COSE_RS256; + else if (strcmp(optarg, "eddsa") == 0) + type = COSE_EDDSA; + else + errx(1, "unknown type %s", optarg); + break; + case 'u': + u2f = true; + break; + case 'v': + uv = true; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc != 2) + usage(); + + fido_init(0); + + if ((dev = fido_dev_new()) == NULL) + errx(1, "fido_dev_new"); + + r = fido_dev_open(dev, argv[1]); + if (r != FIDO_OK) + errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r); + if (u2f) + fido_dev_force_u2f(dev); + + /* client data hash */ + r = fido_assert_set_clientdata(assert, cd, sizeof(cd)); + if (r != FIDO_OK) + errx(1, "fido_assert_set_clientdata: %s (0x%x)", fido_strerr(r), r); + + /* relying party */ + r = fido_assert_set_rp(assert, "localhost"); + if (r != FIDO_OK) + errx(1, "fido_assert_set_rp: %s (0x%x)", fido_strerr(r), r); + + /* extensions */ + r = fido_assert_set_extensions(assert, ext); + if (r != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s (0x%x)", fido_strerr(r), + r); + + /* user presence */ + if (up && (r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_up: %s (0x%x)", fido_strerr(r), r); + + /* user verification */ + if (uv && (r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_uv: %s (0x%x)", fido_strerr(r), r); + + /* timeout */ + if (ms != 0 && (r = fido_dev_set_timeout(dev, (int)ms)) != FIDO_OK) + errx(1, "fido_dev_set_timeout: %s (0x%x)", fido_strerr(r), r); + + if ((r = fido_dev_get_assert(dev, assert, pin)) != FIDO_OK) { + fido_dev_cancel(dev); + errx(1, "fido_dev_get_assert: %s (0x%x)", fido_strerr(r), r); + } + + r = fido_dev_close(dev); + if (r != FIDO_OK) + errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r); + + fido_dev_free(&dev); + + if (fido_assert_count(assert) != 1) + errx(1, "fido_assert_count: %d signatures returned", + (int)fido_assert_count(assert)); + + /* when verifying, pin implies uv */ + if (pin) + uv = true; + + verify_assert(type, fido_assert_authdata_ptr(assert, 0), + fido_assert_authdata_len(assert, 0), fido_assert_sig_ptr(assert, 0), + fido_assert_sig_len(assert, 0), up, uv, ext, argv[0]); + + if (hmac_out != NULL) { + /* extract the hmac secret */ + if (write_blob(hmac_out, fido_assert_hmac_secret_ptr(assert, 0), + fido_assert_hmac_secret_len(assert, 0)) < 0) + errx(1, "write_blob"); + } + + if (blobkey_out != NULL) { + /* extract the hmac secret */ + if (write_blob(blobkey_out, + fido_assert_largeblob_key_ptr(assert, 0), + fido_assert_largeblob_key_len(assert, 0)) < 0) + errx(1, "write_blob"); + } + + fido_assert_free(&assert); + + exit(0); +} diff --git a/examples/cred.c b/examples/cred.c new file mode 100644 index 0000000..5a2a27f --- /dev/null +++ b/examples/cred.c @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include <fido.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static const unsigned char cd[32] = { + 0xf9, 0x64, 0x57, 0xe7, 0x2d, 0x97, 0xf6, 0xbb, + 0xdd, 0xd7, 0xfb, 0x06, 0x37, 0x62, 0xea, 0x26, + 0x20, 0x44, 0x8e, 0x69, 0x7c, 0x03, 0xf2, 0x31, + 0x2f, 0x99, 0xdc, 0xaf, 0x3e, 0x8a, 0x91, 0x6b, +}; + +static const unsigned char user_id[32] = { + 0x78, 0x1c, 0x78, 0x60, 0xad, 0x88, 0xd2, 0x63, + 0x32, 0x62, 0x2a, 0xf1, 0x74, 0x5d, 0xed, 0xb2, + 0xe7, 0xa4, 0x2b, 0x44, 0x89, 0x29, 0x39, 0xc5, + 0x56, 0x64, 0x01, 0x27, 0x0d, 0xbb, 0xc4, 0x49, +}; + +static void +usage(void) +{ + fprintf(stderr, "usage: cred [-t es256|es384|rs256|eddsa] [-k pubkey] " + "[-ei cred_id] [-P pin] [-T seconds] [-b blobkey] [-c cred_protect] [-hruv] " + "<device>\n"); + exit(EXIT_FAILURE); +} + +static void +verify_cred(int type, const char *fmt, const unsigned char *authdata_ptr, + size_t authdata_len, const unsigned char *attstmt_ptr, size_t attstmt_len, + bool rk, bool uv, int ext, int cred_protect, const char *key_out, + const char *id_out) +{ + fido_cred_t *cred; + int r; + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + /* type */ + r = fido_cred_set_type(cred, type); + if (r != FIDO_OK) + errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r); + + /* client data */ + r = fido_cred_set_clientdata(cred, cd, sizeof(cd)); + if (r != FIDO_OK) + errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r); + + /* relying party */ + r = fido_cred_set_rp(cred, "localhost", "sweet home localhost"); + if (r != FIDO_OK) + errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r); + + /* authdata */ + r = fido_cred_set_authdata(cred, authdata_ptr, authdata_len); + if (r != FIDO_OK) + errx(1, "fido_cred_set_authdata: %s (0x%x)", fido_strerr(r), r); + + /* extensions */ + r = fido_cred_set_extensions(cred, ext); + if (r != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r); + + /* resident key */ + if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r); + + /* user verification */ + if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r); + + /* credProt */ + if (cred_protect != 0 && (r = fido_cred_set_prot(cred, + cred_protect)) != FIDO_OK) + errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r); + + /* fmt */ + r = fido_cred_set_fmt(cred, fmt); + if (r != FIDO_OK) + errx(1, "fido_cred_set_fmt: %s (0x%x)", fido_strerr(r), r); + + if (!strcmp(fido_cred_fmt(cred), "none")) { + warnx("no attestation data, skipping credential verification"); + goto out; + } + + /* attestation statement */ + r = fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len); + if (r != FIDO_OK) + errx(1, "fido_cred_set_attstmt: %s (0x%x)", fido_strerr(r), r); + + r = fido_cred_verify(cred); + if (r != FIDO_OK) + errx(1, "fido_cred_verify: %s (0x%x)", fido_strerr(r), r); + +out: + if (key_out != NULL) { + /* extract the credential pubkey */ + if (type == COSE_ES256) { + if (write_es256_pubkey(key_out, + fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)) < 0) + errx(1, "write_es256_pubkey"); + } else if (type == COSE_ES384) { + if (write_es384_pubkey(key_out, + fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)) < 0) + errx(1, "write_es384_pubkey"); + } else if (type == COSE_RS256) { + if (write_rs256_pubkey(key_out, + fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)) < 0) + errx(1, "write_rs256_pubkey"); + } else if (type == COSE_EDDSA) { + if (write_eddsa_pubkey(key_out, + fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)) < 0) + errx(1, "write_eddsa_pubkey"); + } + } + + if (id_out != NULL) { + /* extract the credential id */ + if (write_blob(id_out, fido_cred_id_ptr(cred), + fido_cred_id_len(cred)) < 0) + errx(1, "write_blob"); + } + + fido_cred_free(&cred); +} + +int +main(int argc, char **argv) +{ + bool rk = false; + bool uv = false; + bool u2f = false; + fido_dev_t *dev; + fido_cred_t *cred = NULL; + const char *pin = NULL; + const char *blobkey_out = NULL; + const char *key_out = NULL; + const char *id_out = NULL; + unsigned char *body = NULL; + long long ms = 0; + size_t len; + int type = COSE_ES256; + int ext = 0; + int ch; + int r; + long long cred_protect = 0; + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + while ((ch = getopt(argc, argv, "P:T:b:e:hi:k:rt:uvc:")) != -1) { + switch (ch) { + case 'P': + pin = optarg; + break; + case 'T': + if (base10(optarg, &ms) < 0) + errx(1, "base10: %s", optarg); + if (ms <= 0 || ms > 30) + errx(1, "-T: %s must be in (0,30]", optarg); + ms *= 1000; /* seconds to milliseconds */ + break; + case 'b': + ext |= FIDO_EXT_LARGEBLOB_KEY; + blobkey_out = optarg; + break; + case 'e': + if (read_blob(optarg, &body, &len) < 0) + errx(1, "read_blob: %s", optarg); + r = fido_cred_exclude(cred, body, len); + if (r != FIDO_OK) + errx(1, "fido_cred_exclude: %s (0x%x)", + fido_strerr(r), r); + free(body); + body = NULL; + break; + case 'h': + ext |= FIDO_EXT_HMAC_SECRET; + break; + case 'c': + if (base10(optarg, &cred_protect) < 0) + errx(1, "base10: %s", optarg); + if (cred_protect <= 0 || cred_protect > 3) + errx(1, "-c: %s must be in (1,3)", optarg); + ext |= FIDO_EXT_CRED_PROTECT; + break; + case 'i': + id_out = optarg; + break; + case 'k': + key_out = optarg; + break; + case 'r': + rk = true; + break; + case 't': + if (strcmp(optarg, "es256") == 0) + type = COSE_ES256; + else if (strcmp(optarg, "es384") == 0) + type = COSE_ES384; + else if (strcmp(optarg, "rs256") == 0) + type = COSE_RS256; + else if (strcmp(optarg, "eddsa") == 0) + type = COSE_EDDSA; + else + errx(1, "unknown type %s", optarg); + break; + case 'u': + u2f = true; + break; + case 'v': + uv = true; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + fido_init(0); + + if ((dev = fido_dev_new()) == NULL) + errx(1, "fido_dev_new"); + + r = fido_dev_open(dev, argv[0]); + if (r != FIDO_OK) + errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r); + if (u2f) + fido_dev_force_u2f(dev); + + /* type */ + r = fido_cred_set_type(cred, type); + if (r != FIDO_OK) + errx(1, "fido_cred_set_type: %s (0x%x)", fido_strerr(r), r); + + /* client data */ + r = fido_cred_set_clientdata(cred, cd, sizeof(cd)); + if (r != FIDO_OK) + errx(1, "fido_cred_set_clientdata: %s (0x%x)", fido_strerr(r), r); + + /* relying party */ + r = fido_cred_set_rp(cred, "localhost", "sweet home localhost"); + if (r != FIDO_OK) + errx(1, "fido_cred_set_rp: %s (0x%x)", fido_strerr(r), r); + + /* user */ + r = fido_cred_set_user(cred, user_id, sizeof(user_id), "john smith", + "jsmith", NULL); + if (r != FIDO_OK) + errx(1, "fido_cred_set_user: %s (0x%x)", fido_strerr(r), r); + + /* extensions */ + r = fido_cred_set_extensions(cred, ext); + if (r != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s (0x%x)", fido_strerr(r), r); + + /* resident key */ + if (rk && (r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_rk: %s (0x%x)", fido_strerr(r), r); + + /* user verification */ + if (uv && (r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_uv: %s (0x%x)", fido_strerr(r), r); + + /* credProt */ + if (cred_protect != 0 && (r = fido_cred_set_prot(cred, + (int)cred_protect)) != FIDO_OK) + errx(1, "fido_cred_set_prot: %s (0x%x)", fido_strerr(r), r); + + /* timeout */ + if (ms != 0 && (r = fido_dev_set_timeout(dev, (int)ms)) != FIDO_OK) + errx(1, "fido_dev_set_timeout: %s (0x%x)", fido_strerr(r), r); + + if ((r = fido_dev_make_cred(dev, cred, pin)) != FIDO_OK) { + fido_dev_cancel(dev); + errx(1, "fido_makecred: %s (0x%x)", fido_strerr(r), r); + } + + r = fido_dev_close(dev); + if (r != FIDO_OK) + errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r); + + fido_dev_free(&dev); + + /* when verifying, pin implies uv */ + if (pin) + uv = true; + + verify_cred(type, fido_cred_fmt(cred), fido_cred_authdata_ptr(cred), + fido_cred_authdata_len(cred), fido_cred_attstmt_ptr(cred), + fido_cred_attstmt_len(cred), rk, uv, ext, fido_cred_prot(cred), + key_out, id_out); + + if (blobkey_out != NULL) { + /* extract the "largeBlob" key */ + if (write_blob(blobkey_out, fido_cred_largeblob_key_ptr(cred), + fido_cred_largeblob_key_len(cred)) < 0) + errx(1, "write_blob"); + } + + fido_cred_free(&cred); + + exit(0); +} diff --git a/examples/extern.h b/examples/extern.h new file mode 100644 index 0000000..5cffd7f --- /dev/null +++ b/examples/extern.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +#include <openssl/ec.h> +#include <openssl/evp.h> +#include <openssl/rsa.h> + +/* util.c */ +EC_KEY *read_ec_pubkey(const char *); +RSA *read_rsa_pubkey(const char *); +EVP_PKEY *read_eddsa_pubkey(const char *); +int base10(const char *, long long *); +int read_blob(const char *, unsigned char **, size_t *); +int write_blob(const char *, const unsigned char *, size_t); +int write_es256_pubkey(const char *, const void *, size_t); +int write_es384_pubkey(const char *, const void *, size_t); +int write_rs256_pubkey(const char *, const void *, size_t); +int write_eddsa_pubkey(const char *, const void *, size_t); + +#endif /* _EXTERN_H_ */ diff --git a/examples/info.c b/examples/info.c new file mode 100644 index 0000000..a10a50c --- /dev/null +++ b/examples/info.c @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../openbsd-compat/openbsd-compat.h" + +/* + * Pretty-print a device's capabilities flags and return the result. + */ +static void +format_flags(char *ret, size_t retlen, uint8_t flags) +{ + memset(ret, 0, retlen); + + if (flags & FIDO_CAP_WINK) { + if (strlcat(ret, "wink,", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, "nowink,", retlen) >= retlen) + goto toolong; + } + + if (flags & FIDO_CAP_CBOR) { + if (strlcat(ret, " cbor,", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, " nocbor,", retlen) >= retlen) + goto toolong; + } + + if (flags & FIDO_CAP_NMSG) { + if (strlcat(ret, " nomsg", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, " msg", retlen) >= retlen) + goto toolong; + } + + return; +toolong: + strlcpy(ret, "toolong", retlen); +} + +/* + * Print a FIDO device's attributes on stdout. + */ +static void +print_attr(const fido_dev_t *dev) +{ + char flags_txt[128]; + + printf("proto: 0x%02x\n", fido_dev_protocol(dev)); + printf("major: 0x%02x\n", fido_dev_major(dev)); + printf("minor: 0x%02x\n", fido_dev_minor(dev)); + printf("build: 0x%02x\n", fido_dev_build(dev)); + + format_flags(flags_txt, sizeof(flags_txt), fido_dev_flags(dev)); + printf("caps: 0x%02x (%s)\n", fido_dev_flags(dev), flags_txt); +} + +/* + * Auxiliary function to print an array of strings on stdout. + */ +static void +print_str_array(const char *label, char * const *sa, size_t len) +{ + if (len == 0) + return; + + printf("%s strings: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s", i > 0 ? ", " : "", sa[i]); + + printf("\n"); +} + +/* + * Auxiliary function to print (char *, bool) pairs on stdout. + */ +static void +print_opt_array(const char *label, char * const *name, const bool *value, + size_t len) +{ + if (len == 0) + return; + + printf("%s: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s%s", i > 0 ? ", " : "", + value[i] ? "" : "no", name[i]); + + printf("\n"); +} + +/* + * Auxiliary function to print (char *, uint64_t) pairs on stdout. + */ +static void +print_cert_array(const char *label, char * const *name, const uint64_t *value, + size_t len) +{ + if (len == 0) + return; + + printf("%s: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s %llu", i > 0 ? ", " : "", name[i], + (unsigned long long)value[i]); + + printf("\n"); +} + +/* + * Auxiliary function to print a list of supported COSE algorithms on stdout. + */ +static void +print_algorithms(const fido_cbor_info_t *ci) +{ + const char *cose, *type; + size_t len; + + if ((len = fido_cbor_info_algorithm_count(ci)) == 0) + return; + + printf("algorithms: "); + + for (size_t i = 0; i < len; i++) { + cose = type = "unknown"; + switch (fido_cbor_info_algorithm_cose(ci, i)) { + case COSE_ES256: + cose = "es256"; + break; + case COSE_ES384: + cose = "es384"; + break; + case COSE_RS256: + cose = "rs256"; + break; + case COSE_EDDSA: + cose = "eddsa"; + break; + } + if (fido_cbor_info_algorithm_type(ci, i) != NULL) + type = fido_cbor_info_algorithm_type(ci, i); + printf("%s%s (%s)", i > 0 ? ", " : "", cose, type); + } + + printf("\n"); +} + +/* + * Auxiliary function to print an authenticator's AAGUID on stdout. + */ +static void +print_aaguid(const unsigned char *buf, size_t buflen) +{ + printf("aaguid: "); + + while (buflen--) + printf("%02x", *buf++); + + printf("\n"); +} + +/* + * Auxiliary function to print an authenticator's maximum message size on + * stdout. + */ +static void +print_maxmsgsiz(uint64_t maxmsgsiz) +{ + printf("maxmsgsiz: %d\n", (int)maxmsgsiz); +} + +/* + * Auxiliary function to print an authenticator's maximum number of credentials + * in a credential list on stdout. + */ +static void +print_maxcredcntlst(uint64_t maxcredcntlst) +{ + printf("maxcredcntlst: %d\n", (int)maxcredcntlst); +} + +/* + * Auxiliary function to print an authenticator's maximum credential ID length + * on stdout. + */ +static void +print_maxcredidlen(uint64_t maxcredidlen) +{ + printf("maxcredlen: %d\n", (int)maxcredidlen); +} + +/* + * Auxiliary function to print the maximum size of an authenticator's + * serialized largeBlob array. + */ +static void +print_maxlargeblob(uint64_t maxlargeblob) +{ + printf("maxlargeblob: %d\n", (int)maxlargeblob); +} + +/* + * Auxiliary function to print the authenticator's estimated number of + * remaining resident credentials. + */ +static void +print_rk_remaining(int64_t rk_remaining) +{ + printf("remaining rk(s): "); + + if (rk_remaining == -1) + printf("undefined\n"); + else + printf("%d\n", (int)rk_remaining); +} + +/* + * Auxiliary function to print the minimum pin length observed by the + * authenticator. + */ +static void +print_minpinlen(uint64_t minpinlen) +{ + printf("minpinlen: %d\n", (int)minpinlen); +} + +/* + * Auxiliary function to print the authenticator's preferred (platform) + * UV attempts. + */ +static void +print_uv_attempts(uint64_t uv_attempts) +{ + printf("platform uv attempt(s): %d\n", (int)uv_attempts); +} + +/* + * Auxiliary function to print an authenticator's firmware version on stdout. + */ +static void +print_fwversion(uint64_t fwversion) +{ + printf("fwversion: 0x%x\n", (int)fwversion); +} + +/* + * Auxiliary function to print an array of bytes on stdout. + */ +static void +print_byte_array(const char *label, const uint8_t *ba, size_t len) +{ + if (len == 0) + return; + + printf("%s: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%u", i > 0 ? ", " : "", (unsigned)ba[i]); + + printf("\n"); +} + +static void +getinfo(const char *path) +{ + fido_dev_t *dev; + fido_cbor_info_t *ci; + int r; + + fido_init(0); + + if ((dev = fido_dev_new()) == NULL) + errx(1, "fido_dev_new"); + if ((r = fido_dev_open(dev, path)) != FIDO_OK) + errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r); + + print_attr(dev); + + if (fido_dev_is_fido2(dev) == false) + goto end; + if ((ci = fido_cbor_info_new()) == NULL) + errx(1, "fido_cbor_info_new"); + if ((r = fido_dev_get_cbor_info(dev, ci)) != FIDO_OK) + errx(1, "fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r); + + /* print supported protocol versions */ + print_str_array("version", fido_cbor_info_versions_ptr(ci), + fido_cbor_info_versions_len(ci)); + + /* print supported extensions */ + print_str_array("extension", fido_cbor_info_extensions_ptr(ci), + fido_cbor_info_extensions_len(ci)); + + /* print supported transports */ + print_str_array("transport", fido_cbor_info_transports_ptr(ci), + fido_cbor_info_transports_len(ci)); + + /* print supported algorithms */ + print_algorithms(ci); + + /* print aaguid */ + print_aaguid(fido_cbor_info_aaguid_ptr(ci), + fido_cbor_info_aaguid_len(ci)); + + /* print supported options */ + print_opt_array("options", fido_cbor_info_options_name_ptr(ci), + fido_cbor_info_options_value_ptr(ci), + fido_cbor_info_options_len(ci)); + + /* print certifications */ + print_cert_array("certifications", fido_cbor_info_certs_name_ptr(ci), + fido_cbor_info_certs_value_ptr(ci), + fido_cbor_info_certs_len(ci)); + + /* print firmware version */ + print_fwversion(fido_cbor_info_fwversion(ci)); + + /* print maximum message size */ + print_maxmsgsiz(fido_cbor_info_maxmsgsiz(ci)); + + /* print maximum number of credentials allowed in credential lists */ + print_maxcredcntlst(fido_cbor_info_maxcredcntlst(ci)); + + /* print maximum length of a credential ID */ + print_maxcredidlen(fido_cbor_info_maxcredidlen(ci)); + + /* print maximum length of largeBlob array */ + print_maxlargeblob(fido_cbor_info_maxlargeblob(ci)); + + /* print number of remaining resident credentials */ + print_rk_remaining(fido_cbor_info_rk_remaining(ci)); + + /* print minimum pin length */ + print_minpinlen(fido_cbor_info_minpinlen(ci)); + + /* print supported pin protocols */ + print_byte_array("pin protocols", fido_cbor_info_protocols_ptr(ci), + fido_cbor_info_protocols_len(ci)); + + /* print whether a new pin is required */ + printf("pin change required: %s\n", + fido_cbor_info_new_pin_required(ci) ? "true" : "false"); + + /* print platform uv attempts */ + print_uv_attempts(fido_cbor_info_uv_attempts(ci)); + + fido_cbor_info_free(&ci); +end: + if ((r = fido_dev_close(dev)) != FIDO_OK) + errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r); + + fido_dev_free(&dev); +} + +int +main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "usage: info <device>\n"); + exit(EXIT_FAILURE); + } + + getinfo(argv[1]); + + exit(0); +} diff --git a/examples/manifest.c b/examples/manifest.c new file mode 100644 index 0000000..c2b3bf1 --- /dev/null +++ b/examples/manifest.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../openbsd-compat/openbsd-compat.h" + +int +main(void) +{ + fido_dev_info_t *devlist; + size_t ndevs; + int r; + + fido_init(0); + + if ((devlist = fido_dev_info_new(64)) == NULL) + errx(1, "fido_dev_info_new"); + + if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK) + errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r); + + for (size_t i = 0; i < ndevs; i++) { + const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); + printf("%s: vendor=0x%04x, product=0x%04x (%s %s)\n", + fido_dev_info_path(di), + (uint16_t)fido_dev_info_vendor(di), + (uint16_t)fido_dev_info_product(di), + fido_dev_info_manufacturer_string(di), + fido_dev_info_product_string(di)); + } + + fido_dev_info_free(&devlist, ndevs); + + exit(0); +} diff --git a/examples/reset.c b/examples/reset.c new file mode 100644 index 0000000..767a162 --- /dev/null +++ b/examples/reset.c @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Perform a factory reset on a given authenticator. + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +main(int argc, char **argv) +{ + fido_dev_t *dev; + int r; + + if (argc != 2) { + fprintf(stderr, "usage: reset <device>\n"); + exit(EXIT_FAILURE); + } + + fido_init(0); + + if ((dev = fido_dev_new()) == NULL) + errx(1, "fido_dev_new"); + + if ((r = fido_dev_open(dev, argv[1])) != FIDO_OK) + errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r); + + if ((r = fido_dev_reset(dev)) != FIDO_OK) { + fido_dev_cancel(dev); + errx(1, "fido_dev_reset: %s (0x%x)", fido_strerr(r), r); + } + + if ((r = fido_dev_close(dev)) != FIDO_OK) + errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r); + + fido_dev_free(&dev); + + exit(0); +} diff --git a/examples/retries.c b/examples/retries.c new file mode 100644 index 0000000..a0610fe --- /dev/null +++ b/examples/retries.c @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Get an authenticator's number of PIN attempts left. + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../openbsd-compat/openbsd-compat.h" + +int +main(int argc, char **argv) +{ + fido_dev_t *dev; + int n; + int r; + + if (argc != 2) { + fprintf(stderr, "usage: retries <device>\n"); + exit(EXIT_FAILURE); + } + + fido_init(0); + + if ((dev = fido_dev_new()) == NULL) + errx(1, "fido_dev_new"); + + if ((r = fido_dev_open(dev, argv[1])) != FIDO_OK) + errx(1, "fido_open: %s (0x%x)", fido_strerr(r), r); + + if ((r = fido_dev_get_retry_count(dev, &n)) != FIDO_OK) + errx(1, "fido_dev_get_retry_count: %s (0x%x)", fido_strerr(r), r); + + if ((r = fido_dev_close(dev)) != FIDO_OK) + errx(1, "fido_close: %s (0x%x)", fido_strerr(r), r); + + fido_dev_free(&dev); + + printf("%d\n", n); + + exit(0); +} diff --git a/examples/select.c b/examples/select.c new file mode 100644 index 0000000..008eb2e --- /dev/null +++ b/examples/select.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include "../openbsd-compat/openbsd-compat.h" + +#define FIDO_POLL_MS 50 + +#if defined(_MSC_VER) +static int +nanosleep(const struct timespec *rqtp, struct timespec *rmtp) +{ + if (rmtp != NULL) { + errno = EINVAL; + return (-1); + } + + Sleep((DWORD)(rqtp->tv_sec * 1000) + (DWORD)(rqtp->tv_nsec / 1000000)); + + return (0); +} +#endif + +static fido_dev_t * +open_dev(const fido_dev_info_t *di) +{ + fido_dev_t *dev; + int r; + + if ((dev = fido_dev_new()) == NULL) { + warnx("%s: fido_dev_new", __func__); + return (NULL); + } + + if ((r = fido_dev_open(dev, fido_dev_info_path(di))) != FIDO_OK) { + warnx("%s: fido_dev_open %s: %s", __func__, + fido_dev_info_path(di), fido_strerr(r)); + fido_dev_free(&dev); + return (NULL); + } + + printf("%s (0x%04x:0x%04x) is %s\n", fido_dev_info_path(di), + fido_dev_info_vendor(di), fido_dev_info_product(di), + fido_dev_is_fido2(dev) ? "fido2" : "u2f"); + + return (dev); +} + +static int +select_dev(const fido_dev_info_t *devlist, size_t ndevs, fido_dev_t **dev, + size_t *idx, int secs) +{ + const fido_dev_info_t *di; + fido_dev_t **devtab; + struct timespec ts_start; + struct timespec ts_now; + struct timespec ts_delta; + struct timespec ts_pause; + size_t nopen = 0; + int touched; + int r; + long ms_remain; + + *dev = NULL; + *idx = 0; + + printf("%u authenticator(s) detected\n", (unsigned)ndevs); + + if (ndevs == 0) + return (0); /* nothing to do */ + + if ((devtab = calloc(ndevs, sizeof(*devtab))) == NULL) { + warn("%s: calloc", __func__); + return (-1); + } + + for (size_t i = 0; i < ndevs; i++) { + di = fido_dev_info_ptr(devlist, i); + if ((devtab[i] = open_dev(di)) != NULL) { + *idx = i; + nopen++; + } + } + + printf("%u authenticator(s) opened\n", (unsigned)nopen); + + if (nopen < 2) { + if (nopen == 1) + *dev = devtab[*idx]; /* single candidate */ + r = 0; + goto out; + } + + for (size_t i = 0; i < ndevs; i++) { + di = fido_dev_info_ptr(devlist, i); + if (devtab[i] == NULL) + continue; /* failed to open */ + if ((r = fido_dev_get_touch_begin(devtab[i])) != FIDO_OK) { + warnx("%s: fido_dev_get_touch_begin %s: %s", __func__, + fido_dev_info_path(di), fido_strerr(r)); + r = -1; + goto out; + } + } + + if (clock_gettime(CLOCK_MONOTONIC, &ts_start) != 0) { + warn("%s: clock_gettime", __func__); + r = -1; + goto out; + } + + ts_pause.tv_sec = 0; + ts_pause.tv_nsec = 200000000; /* 200ms */ + + do { + nanosleep(&ts_pause, NULL); + + for (size_t i = 0; i < ndevs; i++) { + di = fido_dev_info_ptr(devlist, i); + if (devtab[i] == NULL) { + /* failed to open or discarded */ + continue; + } + if ((r = fido_dev_get_touch_status(devtab[i], &touched, + FIDO_POLL_MS)) != FIDO_OK) { + warnx("%s: fido_dev_get_touch_status %s: %s", + __func__, fido_dev_info_path(di), + fido_strerr(r)); + fido_dev_close(devtab[i]); + fido_dev_free(&devtab[i]); + continue; /* discard */ + } + if (touched) { + *dev = devtab[i]; + *idx = i; + r = 0; + goto out; + } + } + + if (clock_gettime(CLOCK_MONOTONIC, &ts_now) != 0) { + warn("%s: clock_gettime", __func__); + r = -1; + goto out; + } + + timespecsub(&ts_now, &ts_start, &ts_delta); + ms_remain = (secs * 1000) - ((long)ts_delta.tv_sec * 1000) + + ((long)ts_delta.tv_nsec / 1000000); + } while (ms_remain > FIDO_POLL_MS); + + printf("timeout after %d seconds\n", secs); + r = -1; +out: + if (r != 0) { + *dev = NULL; + *idx = 0; + } + + for (size_t i = 0; i < ndevs; i++) { + if (devtab[i] && devtab[i] != *dev) { + fido_dev_cancel(devtab[i]); + fido_dev_close(devtab[i]); + fido_dev_free(&devtab[i]); + } + } + + free(devtab); + + return (r); +} + +int +main(void) +{ + const fido_dev_info_t *di; + fido_dev_info_t *devlist; + fido_dev_t *dev; + size_t idx; + size_t ndevs; + int r; + + fido_init(0); + + if ((devlist = fido_dev_info_new(64)) == NULL) + errx(1, "fido_dev_info_new"); + + if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK) + errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r); + if (select_dev(devlist, ndevs, &dev, &idx, 15) != 0) + errx(1, "select_dev"); + if (dev == NULL) + errx(1, "no authenticator found"); + + di = fido_dev_info_ptr(devlist, idx); + printf("%s: %s by %s (PIN %sset)\n", fido_dev_info_path(di), + fido_dev_info_product_string(di), + fido_dev_info_manufacturer_string(di), + fido_dev_has_pin(dev) ? "" : "un"); + + fido_dev_close(dev); + fido_dev_free(&dev); + fido_dev_info_free(&devlist, ndevs); + + exit(0); +} diff --git a/examples/setpin.c b/examples/setpin.c new file mode 100644 index 0000000..72e08e4 --- /dev/null +++ b/examples/setpin.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Configure a PIN on a given authenticator. + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../openbsd-compat/openbsd-compat.h" + +static void +setpin(const char *path, const char *pin, const char *oldpin) +{ + fido_dev_t *dev; + int r; + + fido_init(0); + + if ((dev = fido_dev_new()) == NULL) + errx(1, "fido_dev_new"); + + if ((r = fido_dev_open(dev, path)) != FIDO_OK) + errx(1, "fido_dev_open: %s (0x%x)", fido_strerr(r), r); + + if ((r = fido_dev_set_pin(dev, pin, oldpin)) != FIDO_OK) + errx(1, "fido_dev_set_pin: %s (0x%x)", fido_strerr(r), r); + + if ((r = fido_dev_close(dev)) != FIDO_OK) + errx(1, "fido_dev_close: %s (0x%x)", fido_strerr(r), r); + + fido_dev_free(&dev); +} + +int +main(int argc, char **argv) +{ + if (argc < 3 || argc > 4) { + fprintf(stderr, "usage: setpin <pin> [oldpin] <device>\n"); + exit(EXIT_FAILURE); + } + + if (argc == 3) + setpin(argv[2], argv[1], NULL); + else + setpin(argv[3], argv[1], argv[2]); + + exit(0); +} diff --git a/examples/util.c b/examples/util.c new file mode 100644 index 0000000..0c0c77a --- /dev/null +++ b/examples/util.c @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <openssl/ec.h> +#include <openssl/evp.h> +#include <openssl/pem.h> + +#include <fido.h> +#include <fido/es256.h> +#include <fido/es384.h> +#include <fido/rs256.h> +#include <fido/eddsa.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef _MSC_VER +#include "../openbsd-compat/posix_win.h" +#endif +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +base10(const char *str, long long *ll) +{ + char *ep; + + *ll = strtoll(str, &ep, 10); + if (str == ep || *ep != '\0') + return (-1); + else if (*ll == LLONG_MIN && errno == ERANGE) + return (-1); + else if (*ll == LLONG_MAX && errno == ERANGE) + return (-1); + + return (0); +} + +int +write_blob(const char *path, const unsigned char *ptr, size_t len) +{ + int fd, ok = -1; + ssize_t n; + + if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) < 0) { + warn("open %s", path); + goto fail; + } + + if ((n = write(fd, ptr, len)) < 0) { + warn("write"); + goto fail; + } + if ((size_t)n != len) { + warnx("write"); + goto fail; + } + + ok = 0; +fail: + if (fd != -1) { + close(fd); + } + + return (ok); +} + +int +read_blob(const char *path, unsigned char **ptr, size_t *len) +{ + int fd, ok = -1; + struct stat st; + ssize_t n; + + *ptr = NULL; + *len = 0; + + if ((fd = open(path, O_RDONLY)) < 0) { + warn("open %s", path); + goto fail; + } + if (fstat(fd, &st) < 0) { + warn("stat %s", path); + goto fail; + } + if (st.st_size < 0) { + warnx("stat %s: invalid size", path); + goto fail; + } + *len = (size_t)st.st_size; + if ((*ptr = malloc(*len)) == NULL) { + warn("malloc"); + goto fail; + } + if ((n = read(fd, *ptr, *len)) < 0) { + warn("read"); + goto fail; + } + if ((size_t)n != *len) { + warnx("read"); + goto fail; + } + + ok = 0; +fail: + if (fd != -1) { + close(fd); + } + if (ok < 0) { + free(*ptr); + *ptr = NULL; + *len = 0; + } + + return (ok); +} + +EC_KEY * +read_ec_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + EC_KEY *ec = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) { + warnx("EVP_PKEY_get1_EC_KEY"); + goto fail; + } + +fail: + if (fp != NULL) { + fclose(fp); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ec); +} + +int +write_es256_pubkey(const char *path, const void *ptr, size_t len) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + es256_pk_t *pk = NULL; + int fd = -1; + int ok = -1; + + if ((pk = es256_pk_new()) == NULL) { + warnx("es256_pk_new"); + goto fail; + } + + if (es256_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("es256_pk_from_ptr"); + goto fail; + } + + if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) { + warn("open %s", path); + goto fail; + } + + if ((fp = fdopen(fd, "w")) == NULL) { + warn("fdopen"); + goto fail; + } + fd = -1; /* owned by fp now */ + + if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("es256_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(fp, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + es256_pk_free(&pk); + + if (fp != NULL) { + fclose(fp); + } + if (fd != -1) { + close(fd); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +int +write_es384_pubkey(const char *path, const void *ptr, size_t len) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + es384_pk_t *pk = NULL; + int fd = -1; + int ok = -1; + + if ((pk = es384_pk_new()) == NULL) { + warnx("es384_pk_new"); + goto fail; + } + + if (es384_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("es384_pk_from_ptr"); + goto fail; + } + + if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) { + warn("open %s", path); + goto fail; + } + + if ((fp = fdopen(fd, "w")) == NULL) { + warn("fdopen"); + goto fail; + } + fd = -1; /* owned by fp now */ + + if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("es384_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(fp, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + es384_pk_free(&pk); + + if (fp != NULL) { + fclose(fp); + } + if (fd != -1) { + close(fd); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +RSA * +read_rsa_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + RSA *rsa = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) { + warnx("EVP_PKEY_get1_RSA"); + goto fail; + } + +fail: + if (fp != NULL) { + fclose(fp); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (rsa); +} + +int +write_rs256_pubkey(const char *path, const void *ptr, size_t len) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + rs256_pk_t *pk = NULL; + int fd = -1; + int ok = -1; + + if ((pk = rs256_pk_new()) == NULL) { + warnx("rs256_pk_new"); + goto fail; + } + + if (rs256_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("rs256_pk_from_ptr"); + goto fail; + } + + if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) { + warn("open %s", path); + goto fail; + } + + if ((fp = fdopen(fd, "w")) == NULL) { + warn("fdopen"); + goto fail; + } + fd = -1; /* owned by fp now */ + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("rs256_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(fp, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + rs256_pk_free(&pk); + + if (fp != NULL) { + fclose(fp); + } + if (fd != -1) { + close(fd); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +EVP_PKEY * +read_eddsa_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + +fail: + if (fp) { + fclose(fp); + } + + return (pkey); +} + +int +write_eddsa_pubkey(const char *path, const void *ptr, size_t len) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + eddsa_pk_t *pk = NULL; + int fd = -1; + int ok = -1; + + if ((pk = eddsa_pk_new()) == NULL) { + warnx("eddsa_pk_new"); + goto fail; + } + + if (eddsa_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("eddsa_pk_from_ptr"); + goto fail; + } + + if ((fd = open(path, O_WRONLY | O_CREAT, 0644)) < 0) { + warn("open %s", path); + goto fail; + } + + if ((fp = fdopen(fd, "w")) == NULL) { + warn("fdopen"); + goto fail; + } + fd = -1; /* owned by fp now */ + + if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("eddsa_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(fp, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + eddsa_pk_free(&pk); + + if (fp != NULL) { + fclose(fp); + } + if (fd != -1) { + close(fd); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt new file mode 100644 index 0000000..cc30baa --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -0,0 +1,82 @@ +# Copyright (c) 2019-2023 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/strlcpy.c + ../openbsd-compat/strlcat.c +) + +list(APPEND COMMON_SOURCES + libfuzzer.c + mutator_aux.c +) + +# XXX: OSS-Fuzz require linking using CXX +set(FUZZ_LINKER_LANGUAGE "C" CACHE STRING "Linker language for fuzz harnesses") +mark_as_advanced(FUZZ_LINKER_LANGUAGE) +enable_language(${FUZZ_LINKER_LANGUAGE}) + +# fuzz_cred +add_executable(fuzz_cred fuzz_cred.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_cred PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_cred fido2_shared) + +# fuzz_assert +add_executable(fuzz_assert fuzz_assert.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_assert PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_assert fido2_shared) + +# fuzz_mgmt +add_executable(fuzz_mgmt fuzz_mgmt.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_mgmt PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_mgmt fido2_shared) + +# fuzz_credman +add_executable(fuzz_credman fuzz_credman.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_credman PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_credman fido2_shared) + +# fuzz_bio +add_executable(fuzz_bio fuzz_bio.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_bio PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_bio fido2_shared) + +# fuzz_hid +add_executable(fuzz_hid fuzz_hid.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_hid PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_hid fido2_shared) + +# fuzz_netlink +add_executable(fuzz_netlink fuzz_netlink.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_netlink PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_netlink fido2_shared) + +# fuzz_largeblob +add_executable(fuzz_largeblob fuzz_largeblob.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_largeblob PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_largeblob fido2_shared) + +# fuzz_pcsc +add_executable(fuzz_pcsc fuzz_pcsc.c ${COMMON_SOURCES} ${COMPAT_SOURCES}) +set_target_properties(fuzz_pcsc PROPERTIES + LINK_FLAGS ${FUZZ_LDFLAGS} + LINKER_LANGUAGE ${FUZZ_LINKER_LANGUAGE}) +target_link_libraries(fuzz_pcsc fido2_shared) diff --git a/fuzz/Dockerfile b/fuzz/Dockerfile new file mode 100644 index 0000000..7b26e6e --- /dev/null +++ b/fuzz/Dockerfile @@ -0,0 +1,16 @@ +# Copyright (c) 2019-2023 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +FROM alpine:latest +ENV CC=clang +ENV CXX=clang++ +RUN apk -q update +RUN apk add build-base clang clang-analyzer cmake compiler-rt coreutils +RUN apk add eudev-dev git linux-headers llvm openssl-dev pcsc-lite-dev +RUN apk add sudo tar zlib-dev +RUN git clone --branch v0.10.2 --depth=1 https://github.com/PJK/libcbor +RUN git clone --depth=1 https://github.com/yubico/libfido2 +WORKDIR /libfido2 +RUN ./fuzz/build-coverage /libcbor /libfido2 diff --git a/fuzz/Makefile b/fuzz/Makefile new file mode 100644 index 0000000..55a506b --- /dev/null +++ b/fuzz/Makefile @@ -0,0 +1,90 @@ +# Copyright (c) 2019-2023 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +IMAGE := libfido2-coverage:1.14.0 +RUNNER := libfido2-runner +PROFDATA := llvm-profdata +COV := llvm-cov +TARGETS := fuzz_assert fuzz_bio fuzz_cred fuzz_credman fuzz_hid \ + fuzz_largeblob fuzz_netlink fuzz_mgmt fuzz_pcsc +CORPORA := $(foreach f,${TARGETS},${f}/corpus) +MINIFY := $(foreach f,${TARGETS},/minify/${f}/corpus) +REMOTE := gs://libfido2-corpus.clusterfuzz-external.appspot.com +.DEFAULT_GOAL := all + +all: ${TARGETS} + +build: + docker build -t ${IMAGE} - < Dockerfile + +run: build + -docker run -it -d --name ${RUNNER} ${IMAGE} + docker start ${RUNNER} + +sync: run + tar Ccf .. - src fuzz | docker exec -i ${RUNNER} tar Cxf /libfido2 - + docker exec ${RUNNER} make -C /libfido2/build + +corpus: sync + docker exec ${RUNNER} /bin/sh -c 'cd /libfido2/fuzz && rm -rf ${TARGETS}' + docker exec ${RUNNER} tar Czxf /libfido2/fuzz /libfido2/fuzz/corpus.tgz + +${TARGETS}: corpus sync + docker exec -e LLVM_PROFILE_FILE=/profraw/$@ ${RUNNER} \ + /bin/sh -c 'rm -f /profraw/$@ && /libfido2/build/fuzz/$@ \ + -runs=1 /libfido2/fuzz/$@' + +${MINIFY}: /minify/%/corpus: % + docker exec ${RUNNER} /bin/sh -c 'rm -rf $@ && mkdir -p $@ && \ + /libfido2/build/fuzz/$< -use_value_profile=1 -merge=1 $@ \ + /libfido2/fuzz/$</corpus' + +corpus.tgz-: ${MINIFY} + docker exec -i ${RUNNER} tar Czcf /minify - ${TARGETS} > $@ + +profdata: run + docker exec ${RUNNER} /bin/sh -c 'rm -f /$@ && ${PROFDATA} \ + merge -sparse /profraw/* -o /$@' + +report.tgz: profdata + docker exec ${RUNNER} /bin/sh -c 'rm -rf /report && mkdir /report && \ + ${COV} show -format=html -tab-size=8 -instr-profile=/$< \ + -ignore-filename-regex=pcsclite.h --show-branch-summary=false \ + -output-dir=/report /libfido2/build/src/libfido2.so' + docker exec -i ${RUNNER} tar Czcf / - report > $@ + +summary.txt: profdata + docker exec ${RUNNER} ${COV} report -use-color=false \ + -ignore-filename-regex=pcsclite.h --show-branch-summary=false \ + /libfido2/build/src/libfido2.so -instr-profile=/$< > $@ + +functions.txt: profdata + docker exec ${RUNNER} /bin/sh -c '${COV} report -use-color=false \ + -ignore-filename-regex=pcsclite.h -show-functions \ + --show-branch-summary=false -instr-profile=/$< \ + /libfido2/build/src/libfido2.so /libfido2/src/*.[ch]' > $@ + +clean: run + docker exec ${RUNNER} /bin/sh -c 'rm -rf /profraw /profdata && \ + make -C /libfido2/build clean' + -docker stop ${RUNNER} + rm -rf ${TARGETS} + +${CORPORA}: + -mkdir -p $@ + gsutil -q -m rsync -d -r ${REMOTE}/libFuzzer/libfido2_$(@:/corpus=) $@ + +fetch-oss-fuzz: ${CORPORA} + find ${TARGETS} -type f -size +8192c -print0 | xargs -0 rm + +fetch-franz: + ssh franz tar -C corpus -cf- . | tar -xf- + +corpus.tgz: + tar zcf $@ ${TARGETS} + +.PHONY: build run sync corpus ${TARGETS} ${CORPORA} +.PHONY: report.tgz summary.txt functions.txt +.PHONY: fetch-oss-fuzz fetch-franz corpus.tgz diff --git a/fuzz/README b/fuzz/README new file mode 100644 index 0000000..427625c --- /dev/null +++ b/fuzz/README @@ -0,0 +1,43 @@ +libfido2 can be fuzzed using AFL or libFuzzer, with or without +ASAN/MSAN/UBSAN. + +AFL is more convenient when fuzzing the path from the authenticator to +libfido2 in an existing application. To do so, use preload-snoop.c with a real +authenticator to obtain an initial corpus, rebuild libfido2 with -DFUZZ=ON, and +use preload-fuzz.c to read device data from stdin. + +libFuzzer is better suited for bespoke fuzzers; see fuzz_cred.c, fuzz_credman.c, +fuzz_assert.c, fuzz_hid.c, and fuzz_mgmt.c for examples. To build these +harnesses, use -DCMAKE_C_FLAGS=-fsanitize=fuzzer-no-link +-DFUZZ_LDFLAGS=-fsanitize=fuzzer -DFUZZ=ON. + +If -DFUZZ=ON is enabled, symbols listed in wrapped.sym are wrapped in the +resulting shared object. The wrapper functions simulate failure according to a +deterministic RNG and probabilities defined in wrap.c. Harnesses wishing to +use this functionality should call prng_init() with a seed obtained from the +corpus. To mutate only the seed part of a libFuzzer harness's corpora, +use '-reduce_inputs=0 --fido-mutate=seed'. + +To run under ASAN/MSAN/UBSAN, libfido2 needs to be linked against flavours of +libcbor and OpenSSL built with the respective sanitiser. In order to keep +memory utilisation at a manageable level, you can either enforce limits at +the OS level (e.g. cgroups on Linux), or patch libcbor with the diff below. +N.B., the patch below is relative to libcbor 0.10.1. + +diff --git src/cbor/internal/memory_utils.c src/cbor/internal/memory_utils.c +index bbea63c..3f7c9af 100644 +--- src/cbor/internal/memory_utils.c ++++ src/cbor/internal/memory_utils.c +@@ -41,7 +41,11 @@ size_t _cbor_safe_signaling_add(size_t a, size_t b) { + + void* _cbor_alloc_multiple(size_t item_size, size_t item_count) { + if (_cbor_safe_to_multiply(item_size, item_count)) { +- return _cbor_malloc(item_size * item_count); ++ if (item_count > 1000) { ++ return NULL; ++ } else { ++ return _cbor_malloc(item_size * item_count); ++ } + } else { + return NULL; + } diff --git a/fuzz/build-coverage b/fuzz/build-coverage new file mode 100755 index 0000000..6cc5041 --- /dev/null +++ b/fuzz/build-coverage @@ -0,0 +1,34 @@ +#!/bin/sh -eux + +# Copyright (c) 2019 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +LIBCBOR="$1" +LIBFIDO2="$2" + +CC="${CC:-clang}" +CXX="${CXX:-clang++}" +PKG_CONFIG_PATH="${PKG_CONFIG_PATH:-${LIBCBOR}/install/lib/pkgconfig}" +export CC PKG_CONFIG_PATH + +# Clean up. +rm -rf "${LIBCBOR}/build" "${LIBCBOR}/install" "${LIBFIDO2}/build" + +# Patch, build, and install libcbor. +(cd "${LIBCBOR}" && patch -N -l -s -p0 < "${LIBFIDO2}/fuzz/README") || true +mkdir "${LIBCBOR}/build" "${LIBCBOR}/install" +(cd "${LIBCBOR}/build" && cmake -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX="${LIBCBOR}/install" ..) +make -C "${LIBCBOR}/build" VERBOSE=1 all install + +# Build libfido2. +mkdir -p "${LIBFIDO2}/build" +export CFLAGS="-fprofile-instr-generate -fcoverage-mapping" +export CFLAGS="${CFLAGS} -fsanitize=fuzzer-no-link" +export LDFLAGS="${CFLAGS}" +export FUZZ_LDFLAGS="${LDFLAGS} -fsanitize=fuzzer" +(cd "${LIBFIDO2}/build" && cmake -DFUZZ=ON -DFUZZ_LDFLAGS="${FUZZ_LDFLAGS}" \ + -DCMAKE_BUILD_TYPE=Debug ..) +make -C "${LIBFIDO2}/build" diff --git a/fuzz/clock.c b/fuzz/clock.c new file mode 100644 index 0000000..bd758ea --- /dev/null +++ b/fuzz/clock.c @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <stdint.h> +#include <time.h> + +#include "mutator_aux.h" + +/* + * A pseudo-random monotonic clock with a probabilistic discontinuity to + * the end of time (as measured by struct timespec). + */ + +extern int prng_up; +extern int __wrap_clock_gettime(clockid_t, struct timespec *); +extern int __real_clock_gettime(clockid_t, struct timespec *); +extern int __wrap_usleep(unsigned int); +static TLS struct timespec fuzz_clock; + +static void +tick(unsigned int usec) +{ + long long drift; + + /* + * Simulate a jump to the end of time with 0.125% probability. + * This condition should be gracefully handled by callers of + * clock_gettime(). + */ + if (uniform_random(800) < 1) { + fuzz_clock.tv_sec = LLONG_MAX; + fuzz_clock.tv_nsec = LONG_MAX; + return; + } + + drift = usec * 1000LL + (long long)uniform_random(10000000); /* 10ms */ + if (LLONG_MAX - drift < (long long)fuzz_clock.tv_nsec) { + fuzz_clock_reset(); /* Not much we can do here. */ + } else if (drift + (long long)fuzz_clock.tv_nsec < 1000000000) { + fuzz_clock.tv_nsec += (long)(drift); + } else { + fuzz_clock.tv_sec += (long)(drift / 1000000000); + fuzz_clock.tv_nsec += (long)(drift % 1000000000); + } +} + +int +__wrap_clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + if (!prng_up || clk_id != CLOCK_MONOTONIC) + return __real_clock_gettime(clk_id, tp); + if (uniform_random(400) < 1) + return -1; + + tick(0); + *tp = fuzz_clock; + + return 0; +} + +int +__wrap_usleep(unsigned int usec) +{ + if (uniform_random(400) < 1) + return -1; + + tick(usec); + + return 0; +} + +void +fuzz_clock_reset(void) +{ + memset(&fuzz_clock, 0, sizeof(fuzz_clock)); +} diff --git a/fuzz/dummy.h b/fuzz/dummy.h new file mode 100644 index 0000000..fc4bfc5 --- /dev/null +++ b/fuzz/dummy.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _DUMMY_H +#define _DUMMY_H + +#include <stdint.h> + +const char dummy_name[] = "finger1"; +const char dummy_pin1[] = "skepp cg0u3;Y.."; +const char dummy_pin2[] = "bastilha 6rJrfQZI."; +const char dummy_pin[] = "9}4gT:8d=A37Dh}U"; +const char dummy_rp_id[] = "localhost"; +const char dummy_rp_name[] = "sweet home localhost"; +const char dummy_user_icon[] = "an icon"; +const char dummy_user_name[] = "john smith"; +const char dummy_user_nick[] = "jsmith"; +const char dummy_pcsc_list[] = "reader1\0reader2\0reader3\0\0"; +const char dummy_pcsc_path[] = "pcsc://slot7"; +const uint8_t dummy_id[] = { 0x5e, 0xd2 }; + +const uint8_t dummy_user_id[] = { + 0x78, 0x1c, 0x78, 0x60, 0xad, 0x88, 0xd2, 0x63, + 0x32, 0x62, 0x2a, 0xf1, 0x74, 0x5d, 0xed, 0xb2, + 0xe7, 0xa4, 0x2b, 0x44, 0x89, 0x29, 0x39, 0xc5, + 0x56, 0x64, 0x01, 0x27, 0x0d, 0xbb, 0xc4, 0x49, +}; + +const uint8_t dummy_cred_id[] = { + 0x4f, 0x72, 0x98, 0x42, 0x4a, 0xe1, 0x17, 0xa5, + 0x85, 0xa0, 0xef, 0x3b, 0x11, 0x24, 0x4a, 0x3d, +}; + +const uint8_t dummy_cdh[] = { + 0xec, 0x8d, 0x8f, 0x78, 0x42, 0x4a, 0x2b, 0xb7, + 0x82, 0x34, 0xaa, 0xca, 0x07, 0xa1, 0xf6, 0x56, + 0x42, 0x1c, 0xb6, 0xf6, 0xb3, 0x00, 0x86, 0x52, + 0x35, 0x2d, 0xa2, 0x62, 0x4a, 0xbe, 0x89, 0x76, +}; + +const uint8_t dummy_es256[] = { + 0xcc, 0x1b, 0x50, 0xac, 0xc4, 0x19, 0xf8, 0x3a, + 0xee, 0x0a, 0x77, 0xd6, 0xf3, 0x53, 0xdb, 0xef, + 0xf2, 0xb9, 0x5c, 0x2d, 0x8b, 0x1e, 0x52, 0x58, + 0x88, 0xf4, 0x0b, 0x85, 0x1f, 0x40, 0x6d, 0x18, + 0x15, 0xb3, 0xcc, 0x25, 0x7c, 0x38, 0x3d, 0xec, + 0xdf, 0xad, 0xbd, 0x46, 0x91, 0xc3, 0xac, 0x30, + 0x94, 0x2a, 0xf7, 0x78, 0x35, 0x70, 0x59, 0x6f, + 0x28, 0xcb, 0x8e, 0x07, 0x85, 0xb5, 0x91, 0x96, +}; + +const uint8_t dummy_rs256[] = { + 0xd2, 0xa8, 0xc0, 0x11, 0x82, 0x9e, 0x57, 0x2e, + 0x60, 0xae, 0x8c, 0xb0, 0x09, 0xe1, 0x58, 0x2b, + 0x99, 0xec, 0xc3, 0x11, 0x1b, 0xef, 0x81, 0x49, + 0x34, 0x53, 0x6a, 0x01, 0x65, 0x2c, 0x24, 0x09, + 0x30, 0x87, 0x98, 0x51, 0x6e, 0x30, 0x4f, 0x60, + 0xbd, 0x54, 0xd2, 0x54, 0xbd, 0x94, 0x42, 0xdd, + 0x63, 0xe5, 0x2c, 0xc6, 0x04, 0x32, 0xc0, 0x8f, + 0x72, 0xd5, 0xb4, 0xf0, 0x4f, 0x42, 0xe5, 0xb0, + 0xa2, 0x95, 0x11, 0xfe, 0xd8, 0xb0, 0x65, 0x34, + 0xff, 0xfb, 0x44, 0x97, 0x52, 0xfc, 0x67, 0x23, + 0x0b, 0xad, 0xf3, 0x3a, 0x82, 0xd4, 0x96, 0x10, + 0x87, 0x6b, 0xfa, 0xd6, 0x51, 0x60, 0x3e, 0x1c, + 0xae, 0x19, 0xb8, 0xce, 0x08, 0xae, 0x9a, 0xee, + 0x78, 0x16, 0x22, 0xcc, 0x92, 0xcb, 0xa8, 0x95, + 0x34, 0xe5, 0xb9, 0x42, 0x6a, 0xf0, 0x2e, 0x82, + 0x1f, 0x4c, 0x7d, 0x84, 0x94, 0x68, 0x7b, 0x97, + 0x2b, 0xf7, 0x7d, 0x67, 0x83, 0xbb, 0xc7, 0x8a, + 0x31, 0x5a, 0xf3, 0x2a, 0x95, 0xdf, 0x63, 0xe7, + 0x4e, 0xee, 0x26, 0xda, 0x87, 0x00, 0xe2, 0x23, + 0x4a, 0x33, 0x9a, 0xa0, 0x1b, 0xce, 0x60, 0x1f, + 0x98, 0xa1, 0xb0, 0xdb, 0xbf, 0x20, 0x59, 0x27, + 0xf2, 0x06, 0xd9, 0xbe, 0x37, 0xa4, 0x03, 0x6b, + 0x6a, 0x4e, 0xaf, 0x22, 0x68, 0xf3, 0xff, 0x28, + 0x59, 0x05, 0xc9, 0xf1, 0x28, 0xf4, 0xbb, 0x35, + 0xe0, 0xc2, 0x68, 0xc2, 0xaa, 0x54, 0xac, 0x8c, + 0xc1, 0x69, 0x9e, 0x4b, 0x32, 0xfc, 0x53, 0x58, + 0x85, 0x7d, 0x3f, 0x51, 0xd1, 0xc9, 0x03, 0x02, + 0x13, 0x61, 0x62, 0xda, 0xf8, 0xfe, 0x3e, 0xc8, + 0x95, 0x12, 0xfb, 0x0c, 0xdf, 0x06, 0x65, 0x6f, + 0x23, 0xc7, 0x83, 0x7c, 0x50, 0x2d, 0x27, 0x25, + 0x4d, 0xbf, 0x94, 0xf0, 0x89, 0x04, 0xb9, 0x2d, + 0xc4, 0xa5, 0x32, 0xa9, 0x25, 0x0a, 0x99, 0x59, + 0x01, 0x00, 0x01, +}; + +const uint8_t dummy_eddsa[] = { + 0xfe, 0x8b, 0x61, 0x50, 0x31, 0x7a, 0xe6, 0xdf, + 0xb1, 0x04, 0x9d, 0x4d, 0xb5, 0x7a, 0x5e, 0x96, + 0x4c, 0xb2, 0xf9, 0x5f, 0x72, 0x47, 0xb5, 0x18, + 0xe2, 0x39, 0xdf, 0x2f, 0x87, 0x19, 0xb3, 0x02, +}; + +const uint8_t dummy_netlink_wiredata[] = { + 0xd8, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x9d, 0x2e, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x6e, 0x66, 0x63, 0x00, 0x06, 0x00, 0x01, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x05, 0x00, + 0x1f, 0x00, 0x00, 0x00, 0x80, 0x01, 0x06, 0x00, + 0x14, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x03, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x04, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x05, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x06, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x07, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x08, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x09, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0a, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x0b, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0c, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x0d, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x11, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0e, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x0f, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x10, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x1b, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x11, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x12, 0x00, + 0x08, 0x00, 0x01, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x13, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x1e, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x07, 0x00, + 0x18, 0x00, 0x01, 0x00, 0x08, 0x00, 0x02, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x9d, 0x2e, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x9d, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x01, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x1e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9d, 0x2e, 0x00, 0x00, 0x08, 0x01, 0x00, 0x00, + 0x08, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x05, 0x00, 0x44, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x07, 0x00, 0x27, 0x00, 0x00, 0x00, + 0x93, 0xb9, 0x25, 0x00 +}; + +#endif /* !_DUMMY_H */ diff --git a/fuzz/export.gnu b/fuzz/export.gnu new file mode 100644 index 0000000..bc25dd6 --- /dev/null +++ b/fuzz/export.gnu @@ -0,0 +1,285 @@ +{ + global: + eddsa_pk_free; + eddsa_pk_from_EVP_PKEY; + eddsa_pk_from_ptr; + eddsa_pk_new; + eddsa_pk_to_EVP_PKEY; + es256_pk_free; + es256_pk_from_EC_KEY; + es256_pk_from_EVP_PKEY; + es256_pk_from_ptr; + es256_pk_new; + es256_pk_to_EVP_PKEY; + es384_pk_free; + es384_pk_from_EC_KEY; + es384_pk_from_EVP_PKEY; + es384_pk_from_ptr; + es384_pk_new; + es384_pk_to_EVP_PKEY; + fido_assert_allow_cred; + fido_assert_authdata_len; + fido_assert_authdata_ptr; + fido_assert_authdata_raw_len; + fido_assert_authdata_raw_ptr; + fido_assert_blob_len; + fido_assert_blob_ptr; + fido_assert_clientdata_hash_len; + fido_assert_clientdata_hash_ptr; + fido_assert_count; + fido_assert_flags; + fido_assert_free; + fido_assert_hmac_secret_len; + fido_assert_hmac_secret_ptr; + fido_assert_id_len; + fido_assert_id_ptr; + fido_assert_largeblob_key_len; + fido_assert_largeblob_key_ptr; + fido_assert_new; + fido_assert_rp_id; + fido_assert_set_authdata; + fido_assert_set_authdata_raw; + fido_assert_set_clientdata; + fido_assert_set_clientdata_hash; + fido_assert_set_count; + fido_assert_set_extensions; + fido_assert_set_hmac_salt; + fido_assert_set_hmac_secret; + fido_assert_set_options; + fido_assert_set_rp; + fido_assert_set_sig; + fido_assert_set_up; + fido_assert_set_uv; + fido_assert_sigcount; + fido_assert_sig_len; + fido_assert_sig_ptr; + fido_assert_user_display_name; + fido_assert_user_icon; + fido_assert_user_id_len; + fido_assert_user_id_ptr; + fido_assert_user_name; + fido_assert_verify; + fido_bio_dev_enroll_begin; + fido_bio_dev_enroll_cancel; + fido_bio_dev_enroll_continue; + fido_bio_dev_enroll_remove; + fido_bio_dev_get_info; + fido_bio_dev_get_template_array; + fido_bio_dev_set_template_name; + fido_bio_enroll_free; + fido_bio_enroll_last_status; + fido_bio_enroll_new; + fido_bio_enroll_remaining_samples; + fido_bio_info_free; + fido_bio_info_max_samples; + fido_bio_info_new; + fido_bio_info_type; + fido_bio_template; + fido_bio_template_array_count; + fido_bio_template_array_free; + fido_bio_template_array_new; + fido_bio_template_free; + fido_bio_template_id_len; + fido_bio_template_id_ptr; + fido_bio_template_name; + fido_bio_template_new; + fido_bio_template_set_id; + fido_bio_template_set_name; + fido_cbor_info_aaguid_len; + fido_cbor_info_aaguid_ptr; + fido_cbor_info_algorithm_cose; + fido_cbor_info_algorithm_count; + fido_cbor_info_algorithm_type; + fido_cbor_info_certs_len; + fido_cbor_info_certs_name_ptr; + fido_cbor_info_certs_value_ptr; + fido_cbor_info_extensions_len; + fido_cbor_info_extensions_ptr; + fido_cbor_info_free; + fido_cbor_info_fwversion; + fido_cbor_info_maxcredbloblen; + fido_cbor_info_maxcredcntlst; + fido_cbor_info_maxcredidlen; + fido_cbor_info_maxlargeblob; + fido_cbor_info_maxmsgsiz; + fido_cbor_info_maxrpid_minpinlen; + fido_cbor_info_minpinlen; + fido_cbor_info_new; + fido_cbor_info_new_pin_required; + fido_cbor_info_options_len; + fido_cbor_info_options_name_ptr; + fido_cbor_info_options_value_ptr; + fido_cbor_info_protocols_len; + fido_cbor_info_protocols_ptr; + fido_cbor_info_rk_remaining; + fido_cbor_info_transports_len; + fido_cbor_info_transports_ptr; + fido_cbor_info_uv_attempts; + fido_cbor_info_uv_modality; + fido_cbor_info_versions_len; + fido_cbor_info_versions_ptr; + fido_cred_attstmt_len; + fido_cred_attstmt_ptr; + fido_cred_authdata_len; + fido_cred_authdata_ptr; + fido_cred_authdata_raw_len; + fido_cred_authdata_raw_ptr; + fido_cred_clientdata_hash_len; + fido_cred_clientdata_hash_ptr; + fido_cred_display_name; + fido_cred_exclude; + fido_cred_flags; + fido_cred_largeblob_key_len; + fido_cred_largeblob_key_ptr; + fido_cred_sigcount; + fido_cred_fmt; + fido_cred_free; + fido_cred_id_len; + fido_cred_id_ptr; + fido_cred_aaguid_len; + fido_cred_aaguid_ptr; + fido_credman_del_dev_rk; + fido_credman_get_dev_metadata; + fido_credman_get_dev_rk; + fido_credman_get_dev_rp; + fido_credman_metadata_free; + fido_credman_metadata_new; + fido_credman_rk; + fido_credman_rk_count; + fido_credman_rk_existing; + fido_credman_rk_free; + fido_credman_rk_new; + fido_credman_rk_remaining; + fido_credman_rp_count; + fido_credman_rp_free; + fido_credman_rp_id; + fido_credman_rp_id_hash_len; + fido_credman_rp_id_hash_ptr; + fido_credman_rp_name; + fido_credman_rp_new; + fido_credman_set_dev_rk; + fido_cred_new; + fido_cred_pin_minlen; + fido_cred_prot; + fido_cred_pubkey_len; + fido_cred_pubkey_ptr; + fido_cred_rp_id; + fido_cred_rp_name; + fido_cred_set_attstmt; + fido_cred_set_authdata; + fido_cred_set_authdata_raw; + fido_cred_set_blob; + fido_cred_set_clientdata; + fido_cred_set_clientdata_hash; + fido_cred_set_extensions; + fido_cred_set_fmt; + fido_cred_set_id; + fido_cred_set_options; + fido_cred_set_pin_minlen; + fido_cred_set_prot; + fido_cred_set_rk; + fido_cred_set_rp; + fido_cred_set_sig; + fido_cred_set_type; + fido_cred_set_user; + fido_cred_set_uv; + fido_cred_set_x509; + fido_cred_sig_len; + fido_cred_sig_ptr; + fido_cred_type; + fido_cred_user_id_len; + fido_cred_user_id_ptr; + fido_cred_user_name; + fido_cred_verify; + fido_cred_verify_self; + fido_cred_x5c_len; + fido_cred_x5c_ptr; + fido_dev_build; + fido_dev_cancel; + fido_dev_close; + fido_dev_enable_entattest; + fido_dev_flags; + fido_dev_force_fido2; + fido_dev_force_pin_change; + fido_dev_force_u2f; + fido_dev_free; + fido_dev_get_assert; + fido_dev_get_cbor_info; + fido_dev_get_retry_count; + fido_dev_get_uv_retry_count; + fido_dev_get_touch_begin; + fido_dev_get_touch_status; + fido_dev_has_pin; + fido_dev_has_uv; + fido_dev_info_free; + fido_dev_info_manifest; + fido_dev_info_manufacturer_string; + fido_dev_info_new; + fido_dev_info_path; + fido_dev_info_product; + fido_dev_info_product_string; + fido_dev_info_ptr; + fido_dev_info_set; + fido_dev_info_vendor; + fido_dev_is_fido2; + fido_dev_major; + fido_dev_make_cred; + fido_dev_minor; + fido_dev_new; + fido_dev_open; + fido_dev_protocol; + fido_dev_reset; + fido_dev_set_io_functions; + fido_dev_set_pcsc; + fido_dev_set_pin; + fido_dev_set_pin_minlen; + fido_dev_set_pin_minlen_rpid; + fido_dev_set_timeout; + fido_dev_set_transport_functions; + fido_dev_supports_cred_prot; + fido_dev_supports_credman; + fido_dev_supports_permissions; + fido_dev_supports_pin; + fido_dev_supports_uv; + fido_dev_toggle_always_uv; + fido_dev_largeblob_get; + fido_dev_largeblob_get_array; + fido_dev_largeblob_remove; + fido_dev_largeblob_set; + fido_dev_largeblob_set_array; + fido_hid_get_report_len; + fido_hid_get_usage; + fido_init; + fido_nfc_rx; + fido_nfc_tx; + fido_nl_free; + fido_nl_get_nfc_target; + fido_nl_new; + fido_nl_power_nfc; + fido_pcsc_close; + fido_pcsc_manifest; + fido_pcsc_open; + fido_pcsc_read; + fido_pcsc_rx; + fido_pcsc_tx; + fido_pcsc_write; + fido_set_log_handler; + fido_strerr; + rs256_pk_free; + rs256_pk_from_ptr; + rs256_pk_from_EVP_PKEY; + rs256_pk_from_RSA; + rs256_pk_new; + rs256_pk_to_EVP_PKEY; + prng_init; + prng_up; + fuzz_clock_reset; + fuzz_save_corpus; + set_netlink_io_functions; + set_pcsc_parameters; + set_pcsc_io_functions; + set_udev_parameters; + uniform_random; + local: + *; +}; diff --git a/fuzz/functions.txt b/fuzz/functions.txt new file mode 100644 index 0000000..4ad5a0c --- /dev/null +++ b/fuzz/functions.txt @@ -0,0 +1,954 @@ +File '/libfido2/src/aes256.c': +Name Regions Miss Cover Lines Miss Cover +-------------------------------------------------------------------------------------------------------- +aes256_cbc_enc 4 0 100.00% 4 0 100.00% +aes256_cbc_dec 4 0 100.00% 4 0 100.00% +aes256_gcm_enc 1 0 100.00% 3 0 100.00% +aes256_gcm_dec 1 0 100.00% 3 0 100.00% +aes256.c:aes256_cbc_fips 26 1 96.15% 42 4 90.48% +aes256.c:aes256_cbc 29 1 96.55% 36 3 91.67% +aes256.c:aes256_cbc_proto1 1 0 100.00% 5 0 100.00% +aes256.c:aes256_gcm 52 1 98.08% 60 4 93.33% +-------------------------------------------------------------------------------------------------------- +TOTAL 118 3 97.46% 157 11 92.99% + +File '/libfido2/src/assert.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------------------------------- +fido_dev_get_assert 40 0 100.00% 35 0 100.00% +fido_check_flags 13 0 100.00% 15 0 100.00% +fido_get_signed_hash 20 1 95.00% 34 3 91.18% +fido_assert_verify 50 4 92.00% 70 7 90.00% +fido_assert_set_clientdata 12 12 0.00% 11 11 0.00% +fido_assert_set_clientdata_hash 8 0 100.00% 6 0 100.00% +fido_assert_set_hmac_salt 10 0 100.00% 6 0 100.00% +fido_assert_set_hmac_secret 12 12 0.00% 7 7 0.00% +fido_assert_set_rp 12 0 100.00% 11 0 100.00% +fido_assert_set_winhello_appid 2 2 0.00% 5 5 0.00% +fido_assert_allow_cred 13 2 84.62% 22 3 86.36% +fido_assert_empty_allow_list 2 0 100.00% 5 0 100.00% +fido_assert_set_extensions 14 0 100.00% 10 0 100.00% +fido_assert_set_options 8 8 0.00% 5 5 0.00% +fido_assert_set_up 2 0 100.00% 4 0 100.00% +fido_assert_set_uv 2 0 100.00% 4 0 100.00% +fido_assert_clientdata_hash_ptr 1 0 100.00% 3 0 100.00% +fido_assert_clientdata_hash_len 1 0 100.00% 3 0 100.00% +fido_assert_new 1 0 100.00% 3 0 100.00% +fido_assert_reset_tx 1 0 100.00% 13 0 100.00% +fido_assert_reset_rx 4 0 100.00% 20 0 100.00% +fido_assert_free 6 0 100.00% 9 0 100.00% +fido_assert_count 1 0 100.00% 3 0 100.00% +fido_assert_rp_id 1 0 100.00% 3 0 100.00% +fido_assert_flags 4 0 100.00% 5 0 100.00% +fido_assert_sigcount 4 0 100.00% 5 0 100.00% +fido_assert_authdata_ptr 4 0 100.00% 5 0 100.00% +fido_assert_authdata_len 4 0 100.00% 5 0 100.00% +fido_assert_authdata_raw_ptr 4 0 100.00% 5 0 100.00% +fido_assert_authdata_raw_len 4 0 100.00% 5 0 100.00% +fido_assert_sig_ptr 4 0 100.00% 5 0 100.00% +fido_assert_sig_len 4 0 100.00% 5 0 100.00% +fido_assert_id_ptr 4 0 100.00% 5 0 100.00% +fido_assert_id_len 4 0 100.00% 5 0 100.00% +fido_assert_user_id_ptr 4 0 100.00% 5 0 100.00% +fido_assert_user_id_len 4 0 100.00% 5 0 100.00% +fido_assert_user_icon 4 0 100.00% 5 0 100.00% +fido_assert_user_name 4 0 100.00% 5 0 100.00% +fido_assert_user_display_name 4 0 100.00% 5 0 100.00% +fido_assert_hmac_secret_ptr 4 0 100.00% 5 0 100.00% +fido_assert_hmac_secret_len 4 0 100.00% 5 0 100.00% +fido_assert_largeblob_key_ptr 4 0 100.00% 5 0 100.00% +fido_assert_largeblob_key_len 4 0 100.00% 5 0 100.00% +fido_assert_blob_ptr 4 0 100.00% 5 0 100.00% +fido_assert_blob_len 4 0 100.00% 5 0 100.00% +fido_assert_set_authdata 28 0 100.00% 33 0 100.00% +fido_assert_set_authdata_raw 28 0 100.00% 32 0 100.00% +fido_assert_set_sig 14 0 100.00% 7 0 100.00% +fido_assert_set_count 10 0 100.00% 17 0 100.00% +assert.c:fido_dev_get_assert_wait 21 0 100.00% 14 0 100.00% +assert.c:fido_dev_get_assert_tx 56 2 96.43% 62 5 91.94% +assert.c:fido_dev_get_assert_rx 27 0 100.00% 36 0 100.00% +assert.c:adjust_assert_count 24 0 100.00% 26 0 100.00% +assert.c:parse_assert_reply 15 0 100.00% 28 0 100.00% +assert.c:fido_get_next_assert_tx 8 0 100.00% 8 0 100.00% +assert.c:fido_get_next_assert_rx 23 2 91.30% 29 5 82.76% +assert.c:decrypt_hmac_secrets 9 0 100.00% 15 0 100.00% +assert.c:get_es256_hash 16 0 100.00% 17 0 100.00% +assert.c:get_es384_hash 16 0 100.00% 17 0 100.00% +assert.c:get_eddsa_hash 6 0 100.00% 9 0 100.00% +assert.c:check_extensions 5 0 100.00% 9 0 100.00% +assert.c:fido_assert_reset_extattr 1 0 100.00% 5 0 100.00% +assert.c:fido_assert_clean_authdata 1 0 100.00% 6 0 100.00% +----------------------------------------------------------------------------------------------------------------- +TOTAL 628 45 92.83% 782 51 93.48% + +File '/libfido2/src/authkey.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------------------------------- +fido_dev_authkey 1 0 100.00% 3 0 100.00% +authkey.c:fido_dev_authkey_wait 10 0 100.00% 7 0 100.00% +authkey.c:fido_dev_authkey_tx 19 0 100.00% 25 0 100.00% +authkey.c:fido_dev_authkey_rx 14 0 100.00% 21 0 100.00% +authkey.c:parse_authkey 8 0 100.00% 10 0 100.00% +----------------------------------------------------------------------------------------------------------------- +TOTAL 52 0 100.00% 66 0 100.00% + +File '/libfido2/src/bio.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------------------------------- +fido_bio_dev_get_template_array 5 2 60.00% 6 1 83.33% +fido_bio_dev_set_template_name 7 0 100.00% 6 0 100.00% +fido_bio_dev_enroll_begin 25 2 92.00% 31 1 96.77% +fido_bio_dev_enroll_continue 5 2 60.00% 6 1 83.33% +fido_bio_dev_enroll_cancel 1 1 0.00% 4 4 0.00% +fido_bio_dev_enroll_remove 1 0 100.00% 4 0 100.00% +fido_bio_dev_get_info 1 0 100.00% 4 0 100.00% +fido_bio_template_name 1 0 100.00% 3 0 100.00% +fido_bio_template_id_ptr 1 0 100.00% 3 0 100.00% +fido_bio_template_id_len 1 0 100.00% 3 0 100.00% +fido_bio_template_array_count 1 0 100.00% 3 0 100.00% +fido_bio_template_array_new 1 0 100.00% 3 0 100.00% +fido_bio_template_new 1 0 100.00% 3 0 100.00% +fido_bio_template_array_free 6 0 100.00% 8 0 100.00% +fido_bio_template_free 6 0 100.00% 8 0 100.00% +fido_bio_template_set_name 8 0 100.00% 7 0 100.00% +fido_bio_template_set_id 8 0 100.00% 6 0 100.00% +fido_bio_template 4 0 100.00% 5 0 100.00% +fido_bio_enroll_new 1 0 100.00% 3 0 100.00% +fido_bio_info_new 1 0 100.00% 3 0 100.00% +fido_bio_info_type 1 0 100.00% 3 0 100.00% +fido_bio_info_max_samples 1 0 100.00% 3 0 100.00% +fido_bio_enroll_free 6 0 100.00% 8 0 100.00% +fido_bio_info_free 6 0 100.00% 7 0 100.00% +fido_bio_enroll_remaining_samples 1 0 100.00% 3 0 100.00% +fido_bio_enroll_last_status 1 0 100.00% 3 0 100.00% +bio.c:bio_get_template_array_wait 11 0 100.00% 7 0 100.00% +bio.c:bio_tx 43 0 100.00% 55 0 100.00% +bio.c:bio_prepare_hmac 18 0 100.00% 29 0 100.00% +bio.c:bio_rx_template_array 19 0 100.00% 24 0 100.00% +bio.c:bio_parse_template_array 26 1 96.15% 27 4 85.19% +bio.c:decode_template_array 12 1 91.67% 18 3 83.33% +bio.c:decode_template 9 0 100.00% 15 0 100.00% +bio.c:bio_set_template_name_wait 19 0 100.00% 20 0 100.00% +bio.c:bio_enroll_begin_wait 17 0 100.00% 19 0 100.00% +bio.c:bio_rx_enroll_begin 23 0 100.00% 31 0 100.00% +bio.c:bio_parse_enroll_status 20 0 100.00% 28 0 100.00% +bio.c:bio_parse_template_id 8 0 100.00% 10 0 100.00% +bio.c:bio_enroll_continue_wait 19 0 100.00% 20 0 100.00% +bio.c:bio_rx_enroll_continue 19 0 100.00% 25 0 100.00% +bio.c:bio_enroll_cancel_wait 11 11 0.00% 10 10 0.00% +bio.c:bio_enroll_remove_wait 17 0 100.00% 19 0 100.00% +bio.c:bio_get_info_wait 11 0 100.00% 10 0 100.00% +bio.c:bio_rx_info 19 0 100.00% 24 0 100.00% +bio.c:bio_reset_info 1 0 100.00% 4 0 100.00% +bio.c:bio_parse_info 20 0 100.00% 28 0 100.00% +bio.c:bio_reset_template_array 4 0 100.00% 7 0 100.00% +bio.c:bio_reset_template 1 0 100.00% 5 0 100.00% +bio.c:bio_reset_enroll 3 0 100.00% 6 0 100.00% +----------------------------------------------------------------------------------------------------------------- +TOTAL 451 20 95.57% 587 24 95.91% + +File '/libfido2/src/blob.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------------------------------- +fido_blob_new 1 0 100.00% 3 0 100.00% +fido_blob_reset 1 0 100.00% 4 0 100.00% +fido_blob_set 9 0 100.00% 15 0 100.00% +fido_blob_append 12 1 91.67% 20 3 85.00% +fido_blob_free 6 0 100.00% 8 0 100.00% +fido_free_blob_array 7 0 100.00% 12 0 100.00% +fido_blob_encode 6 0 100.00% 5 0 100.00% +fido_blob_decode 1 0 100.00% 3 0 100.00% +fido_blob_is_empty 3 0 100.00% 3 0 100.00% +fido_blob_serialise 7 1 85.71% 10 1 90.00% +----------------------------------------------------------------------------------------------------------------- +TOTAL 53 2 96.23% 83 4 95.18% + +File '/libfido2/src/buf.c': +Name Regions Miss Cover Lines Miss Cover +----------------------------------------------------------------------------------------------------------------- +fido_buf_read 4 0 100.00% 8 0 100.00% +fido_buf_write 4 1 75.00% 8 1 87.50% +----------------------------------------------------------------------------------------------------------------- +TOTAL 8 1 87.50% 16 1 93.75% + +File '/libfido2/src/cbor.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------ +cbor_map_iter 20 1 95.00% 26 4 84.62% +cbor_array_iter 12 0 100.00% 16 0 100.00% +cbor_parse_reply 27 0 100.00% 36 0 100.00% +cbor_vector_free 6 0 100.00% 5 0 100.00% +cbor_bytestring_copy 14 0 100.00% 18 0 100.00% +cbor_string_copy 14 0 100.00% 18 0 100.00% +cbor_add_bytestring 14 0 100.00% 21 0 100.00% +cbor_add_string 14 0 100.00% 21 0 100.00% +cbor_add_bool 14 0 100.00% 21 0 100.00% +cbor_flatten_vector 14 1 92.86% 16 1 93.75% +cbor_build_frame 15 0 100.00% 25 0 100.00% +cbor_encode_rp_entity 13 0 100.00% 11 0 100.00% +cbor_encode_user_entity 21 0 100.00% 15 0 100.00% +cbor_encode_pubkey_param 36 0 100.00% 39 0 100.00% +cbor_encode_pubkey 10 0 100.00% 11 0 100.00% +cbor_encode_pubkey_list 18 0 100.00% 19 0 100.00% +cbor_encode_str_array 18 0 100.00% 19 0 100.00% +cbor_encode_cred_ext 55 0 100.00% 50 0 100.00% +cbor_encode_cred_opt 13 0 100.00% 11 0 100.00% +cbor_encode_assert_opt 13 0 100.00% 11 0 100.00% +cbor_encode_pin_auth 21 1 95.24% 22 3 86.36% +cbor_encode_pin_opt 4 0 100.00% 8 0 100.00% +cbor_encode_change_pin_auth 32 1 96.88% 36 3 91.67% +cbor_encode_assert_ext 33 0 100.00% 32 0 100.00% +cbor_decode_fmt 13 0 100.00% 15 0 100.00% +cbor_decode_pubkey 26 1 96.15% 36 2 94.44% +cbor_decode_cred_authdata 31 1 96.77% 35 3 91.43% +cbor_decode_assert_authdata 21 1 95.24% 32 3 90.62% +cbor_decode_attstmt 13 0 100.00% 16 0 100.00% +cbor_decode_uint64 4 0 100.00% 8 0 100.00% +cbor_decode_cred_id 8 0 100.00% 9 0 100.00% +cbor_decode_user 8 0 100.00% 9 0 100.00% +cbor_decode_rp_entity 8 0 100.00% 9 0 100.00% +cbor_decode_bool 10 0 100.00% 11 0 100.00% +cbor_build_uint 10 1 90.00% 9 1 88.89% +cbor_array_append 17 0 100.00% 21 0 100.00% +cbor_array_drop 18 0 100.00% 17 0 100.00% +cbor.c:ctap_check_cbor 28 0 100.00% 26 0 100.00% +cbor.c:check_key_type 8 0 100.00% 7 0 100.00% +cbor.c:cbor_add_arg 13 0 100.00% 21 0 100.00% +cbor.c:cbor_add_uint8 14 0 100.00% 21 0 100.00% +cbor.c:cbor_encode_largeblob_key_ext 6 0 100.00% 6 0 100.00% +cbor.c:cbor_encode_hmac_secret_param 59 4 93.22% 66 8 87.88% +cbor.c:get_cose_alg 46 0 100.00% 45 0 100.00% +cbor.c:find_cose_alg 35 0 100.00% 33 0 100.00% +cbor.c:decode_attcred 25 0 100.00% 44 0 100.00% +cbor.c:decode_cred_extensions 14 0 100.00% 24 0 100.00% +cbor.c:decode_cred_extension 41 0 100.00% 45 0 100.00% +cbor.c:decode_assert_extensions 14 0 100.00% 23 0 100.00% +cbor.c:decode_assert_extension 19 0 100.00% 27 0 100.00% +cbor.c:decode_attstmt_entry 56 0 100.00% 51 0 100.00% +cbor.c:decode_x5c 4 0 100.00% 6 0 100.00% +cbor.c:decode_cred_id_entry 10 0 100.00% 19 0 100.00% +cbor.c:decode_user_entry 25 0 100.00% 35 0 100.00% +cbor.c:decode_rp_entity_entry 15 0 100.00% 25 0 100.00% +------------------------------------------------------------------------------------------------------------------ +TOTAL 1070 12 98.88% 1258 28 97.77% + +File '/libfido2/src/compress.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------ +fido_compress 1 0 100.00% 3 0 100.00% +fido_uncompress 6 0 100.00% 5 0 100.00% +compress.c:rfc1951_deflate 33 4 87.88% 47 6 87.23% +compress.c:rfc1950_inflate 27 2 92.59% 22 4 81.82% +compress.c:rfc1951_inflate 38 8 78.95% 45 14 68.89% +------------------------------------------------------------------------------------------------------------------ +TOTAL 105 14 86.67% 122 24 80.33% + +File '/libfido2/src/config.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_dev_enable_entattest 1 0 100.00% 4 0 100.00% +fido_dev_toggle_always_uv 1 0 100.00% 4 0 100.00% +fido_dev_set_pin_minlen 1 0 100.00% 4 0 100.00% +fido_dev_force_pin_change 1 0 100.00% 4 0 100.00% +fido_dev_set_pin_minlen_rpid 6 0 100.00% 15 0 100.00% +config.c:config_enable_entattest_wait 6 0 100.00% 7 0 100.00% +config.c:config_tx 39 0 100.00% 49 0 100.00% +config.c:config_prepare_hmac 10 0 100.00% 21 0 100.00% +config.c:config_toggle_always_uv_wait 6 0 100.00% 7 0 100.00% +config.c:config_pin_minlen 5 0 100.00% 7 0 100.00% +config.c:config_pin_minlen_tx 36 0 100.00% 32 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 112 0 100.00% 154 0 100.00% + +File '/libfido2/src/cred.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_dev_make_cred 12 0 100.00% 10 0 100.00% +fido_check_rp_id 4 0 100.00% 11 0 100.00% +fido_cred_verify 59 2 96.61% 75 4 94.67% +fido_cred_verify_self 60 4 93.33% 87 7 91.95% +fido_cred_new 1 0 100.00% 3 0 100.00% +fido_cred_reset_tx 1 0 100.00% 18 0 100.00% +fido_cred_reset_rx 1 0 100.00% 7 0 100.00% +fido_cred_free 6 0 100.00% 9 0 100.00% +fido_cred_set_authdata 23 0 100.00% 28 0 100.00% +fido_cred_set_authdata_raw 25 0 100.00% 29 0 100.00% +fido_cred_set_id 6 0 100.00% 5 0 100.00% +fido_cred_set_x509 6 0 100.00% 5 0 100.00% +fido_cred_set_sig 6 0 100.00% 5 0 100.00% +fido_cred_set_attstmt 20 0 100.00% 23 0 100.00% +fido_cred_exclude 14 2 85.71% 19 3 84.21% +fido_cred_empty_exclude_list 2 0 100.00% 5 0 100.00% +fido_cred_set_clientdata 12 12 0.00% 11 11 0.00% +fido_cred_set_clientdata_hash 8 0 100.00% 6 0 100.00% +fido_cred_set_rp 18 0 100.00% 22 0 100.00% +fido_cred_set_user 32 0 100.00% 41 0 100.00% +fido_cred_set_extensions 16 0 100.00% 10 0 100.00% +fido_cred_set_options 8 8 0.00% 5 5 0.00% +fido_cred_set_rk 2 0 100.00% 4 0 100.00% +fido_cred_set_uv 2 0 100.00% 4 0 100.00% +fido_cred_set_prot 21 0 100.00% 14 0 100.00% +fido_cred_set_pin_minlen 7 0 100.00% 8 0 100.00% +fido_cred_set_blob 13 0 100.00% 8 0 100.00% +fido_cred_set_fmt 20 4 80.00% 12 2 83.33% +fido_cred_set_type 23 2 91.30% 9 1 88.89% +fido_cred_type 1 0 100.00% 3 0 100.00% +fido_cred_flags 1 0 100.00% 3 0 100.00% +fido_cred_sigcount 1 0 100.00% 3 0 100.00% +fido_cred_clientdata_hash_ptr 1 0 100.00% 3 0 100.00% +fido_cred_clientdata_hash_len 1 0 100.00% 3 0 100.00% +fido_cred_x5c_ptr 1 0 100.00% 3 0 100.00% +fido_cred_x5c_len 1 0 100.00% 3 0 100.00% +fido_cred_sig_ptr 1 0 100.00% 3 0 100.00% +fido_cred_sig_len 1 0 100.00% 3 0 100.00% +fido_cred_authdata_ptr 1 0 100.00% 3 0 100.00% +fido_cred_authdata_len 1 0 100.00% 3 0 100.00% +fido_cred_authdata_raw_ptr 1 0 100.00% 3 0 100.00% +fido_cred_authdata_raw_len 1 0 100.00% 3 0 100.00% +fido_cred_attstmt_ptr 1 0 100.00% 3 0 100.00% +fido_cred_attstmt_len 1 0 100.00% 3 0 100.00% +fido_cred_pubkey_ptr 11 0 100.00% 21 0 100.00% +fido_cred_pubkey_len 11 0 100.00% 21 0 100.00% +fido_cred_id_ptr 1 0 100.00% 3 0 100.00% +fido_cred_id_len 1 0 100.00% 3 0 100.00% +fido_cred_aaguid_ptr 1 0 100.00% 3 0 100.00% +fido_cred_aaguid_len 1 0 100.00% 3 0 100.00% +fido_cred_prot 1 0 100.00% 3 0 100.00% +fido_cred_pin_minlen 1 0 100.00% 3 0 100.00% +fido_cred_fmt 1 0 100.00% 3 0 100.00% +fido_cred_rp_id 1 0 100.00% 3 0 100.00% +fido_cred_rp_name 1 0 100.00% 3 0 100.00% +fido_cred_user_name 1 0 100.00% 3 0 100.00% +fido_cred_display_name 1 0 100.00% 3 0 100.00% +fido_cred_user_id_ptr 1 0 100.00% 3 0 100.00% +fido_cred_user_id_len 1 0 100.00% 3 0 100.00% +fido_cred_largeblob_key_ptr 1 0 100.00% 3 0 100.00% +fido_cred_largeblob_key_len 1 0 100.00% 3 0 100.00% +cred.c:fido_dev_make_cred_wait 10 0 100.00% 7 0 100.00% +cred.c:fido_dev_make_cred_tx 64 0 100.00% 70 0 100.00% +cred.c:fido_dev_make_cred_rx 29 0 100.00% 32 0 100.00% +cred.c:parse_makecred_reply 14 0 100.00% 27 0 100.00% +cred.c:check_extensions 2 0 100.00% 6 0 100.00% +cred.c:get_signed_hash_u2f 27 0 100.00% 27 0 100.00% +cred.c:verify_attstmt 25 2 92.00% 43 6 86.05% +cred.c:fido_cred_clean_authdata 1 0 100.00% 8 0 100.00% +cred.c:fido_cred_clean_attstmt 1 0 100.00% 8 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 653 36 94.49% 853 39 95.43% + +File '/libfido2/src/credman.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_credman_get_dev_metadata 1 0 100.00% 4 0 100.00% +fido_credman_get_dev_rk 1 0 100.00% 4 0 100.00% +fido_credman_del_dev_rk 1 0 100.00% 4 0 100.00% +fido_credman_get_dev_rp 1 0 100.00% 4 0 100.00% +fido_credman_set_dev_rk 1 0 100.00% 4 0 100.00% +fido_credman_rk_new 1 0 100.00% 3 0 100.00% +fido_credman_rk_free 6 1 83.33% 8 1 87.50% +fido_credman_rk_count 1 0 100.00% 3 0 100.00% +fido_credman_rk 4 0 100.00% 5 0 100.00% +fido_credman_metadata_new 1 0 100.00% 3 0 100.00% +fido_credman_metadata_free 6 1 83.33% 7 1 85.71% +fido_credman_rk_existing 1 0 100.00% 3 0 100.00% +fido_credman_rk_remaining 1 0 100.00% 3 0 100.00% +fido_credman_rp_new 1 0 100.00% 3 0 100.00% +fido_credman_rp_free 6 1 83.33% 8 1 87.50% +fido_credman_rp_count 1 0 100.00% 3 0 100.00% +fido_credman_rp_id 4 0 100.00% 5 0 100.00% +fido_credman_rp_name 4 0 100.00% 5 0 100.00% +fido_credman_rp_id_hash_len 4 0 100.00% 5 0 100.00% +fido_credman_rp_id_hash_ptr 4 0 100.00% 5 0 100.00% +credman.c:credman_get_metadata_wait 11 0 100.00% 8 0 100.00% +credman.c:credman_tx 36 0 100.00% 50 0 100.00% +credman.c:credman_prepare_hmac 31 1 96.77% 50 2 96.00% +credman.c:credman_rx_metadata 19 0 100.00% 24 0 100.00% +credman.c:credman_parse_metadata 9 0 100.00% 17 0 100.00% +credman.c:credman_get_rk_wait 27 0 100.00% 23 0 100.00% +credman.c:credman_rx_rk 27 0 100.00% 35 0 100.00% +credman.c:credman_parse_rk_count 16 0 100.00% 20 0 100.00% +credman.c:credman_grow_array 17 2 88.24% 21 5 76.19% +credman.c:credman_parse_rk 23 0 100.00% 31 0 100.00% +credman.c:credman_rx_next_rk 23 2 91.30% 29 5 82.76% +credman.c:credman_del_rk_wait 16 0 100.00% 15 0 100.00% +credman.c:credman_get_rp_wait 23 0 100.00% 15 0 100.00% +credman.c:credman_rx_rp 27 0 100.00% 35 0 100.00% +credman.c:credman_parse_rp_count 16 0 100.00% 20 0 100.00% +credman.c:credman_parse_rp 9 0 100.00% 17 0 100.00% +credman.c:credman_rx_next_rp 23 2 91.30% 29 5 82.76% +credman.c:credman_set_dev_rk_wait 11 0 100.00% 8 0 100.00% +credman.c:credman_reset_rk 4 0 100.00% 9 0 100.00% +credman.c:credman_reset_rp 4 0 100.00% 12 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 422 10 97.63% 557 20 96.41% + +File '/libfido2/src/dev.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_dev_info_manifest 2 0 100.00% 11 0 100.00% +fido_dev_open_with_info 5 5 0.00% 6 6 0.00% +fido_dev_open 13 4 69.23% 16 6 62.50% +fido_dev_close 9 2 77.78% 8 1 87.50% +fido_dev_set_sigmask 18 18 0.00% 11 11 0.00% +fido_dev_cancel 11 0 100.00% 8 0 100.00% +fido_dev_set_io_functions 18 4 77.78% 14 6 57.14% +fido_dev_set_transport_functions 6 2 66.67% 9 3 66.67% +fido_dev_io_handle 1 1 0.00% 3 3 0.00% +fido_init 8 1 87.50% 5 0 100.00% +fido_dev_new 5 0 100.00% 14 0 100.00% +fido_dev_new_with_info 10 10 0.00% 16 16 0.00% +fido_dev_free 6 0 100.00% 8 0 100.00% +fido_dev_protocol 1 0 100.00% 3 0 100.00% +fido_dev_major 1 0 100.00% 3 0 100.00% +fido_dev_minor 1 0 100.00% 3 0 100.00% +fido_dev_build 1 0 100.00% 3 0 100.00% +fido_dev_flags 1 0 100.00% 3 0 100.00% +fido_dev_is_fido2 2 0 100.00% 3 0 100.00% +fido_dev_is_winhello 2 2 0.00% 3 3 0.00% +fido_dev_supports_pin 3 0 100.00% 3 0 100.00% +fido_dev_has_pin 2 0 100.00% 3 0 100.00% +fido_dev_supports_cred_prot 2 0 100.00% 3 0 100.00% +fido_dev_supports_credman 2 0 100.00% 3 0 100.00% +fido_dev_supports_uv 3 0 100.00% 3 0 100.00% +fido_dev_has_uv 2 0 100.00% 3 0 100.00% +fido_dev_supports_permissions 2 0 100.00% 3 0 100.00% +fido_dev_force_u2f 2 0 100.00% 4 0 100.00% +fido_dev_force_fido2 2 2 0.00% 3 3 0.00% +fido_dev_get_pin_protocol 11 0 100.00% 7 0 100.00% +fido_dev_maxmsgsize 1 0 100.00% 3 0 100.00% +fido_dev_set_timeout 6 2 66.67% 6 1 83.33% +dev.c:run_manifest 10 0 100.00% 13 0 100.00% +dev.c:fido_dev_open_wait 10 0 100.00% 7 0 100.00% +dev.c:fido_dev_open_tx 56 11 80.36% 56 20 64.29% +dev.c:set_random_report_len 11 0 100.00% 6 0 100.00% +dev.c:fido_dev_open_rx 36 1 97.22% 53 1 98.11% +dev.c:fido_dev_set_flags 1 0 100.00% 5 0 100.00% +dev.c:fido_dev_set_extension_flags 7 0 100.00% 7 0 100.00% +dev.c:fido_dev_set_option_flags 31 0 100.00% 20 0 100.00% +dev.c:fido_dev_set_protocol_flags 11 0 100.00% 17 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 332 65 80.42% 378 80 78.84% + +File '/libfido2/src/ecdh.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_do_ecdh 29 0 100.00% 36 0 100.00% +ecdh.c:do_ecdh 37 0 100.00% 44 0 100.00% +ecdh.c:kdf 19 1 94.74% 28 2 92.86% +ecdh.c:hkdf_sha256 32 1 96.88% 38 3 92.11% +------------------------------------------------------------------------------------------------------------------- +TOTAL 117 2 98.29% 146 5 96.58% + +File '/libfido2/src/eddsa.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +eddsa_pk_decode 8 0 100.00% 9 0 100.00% +eddsa_pk_new 1 0 100.00% 3 0 100.00% +eddsa_pk_free 6 0 100.00% 7 0 100.00% +eddsa_pk_from_ptr 10 0 100.00% 12 0 100.00% +eddsa_pk_to_EVP_PKEY 3 0 100.00% 7 0 100.00% +eddsa_pk_from_EVP_PKEY 18 2 88.89% 12 1 91.67% +eddsa_verify_sig 19 2 89.47% 30 6 80.00% +eddsa_pk_verify_sig 7 1 85.71% 13 2 84.62% +eddsa.c:decode_pubkey_point 8 0 100.00% 11 0 100.00% +eddsa.c:decode_coord 8 0 100.00% 10 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 88 5 94.32% 114 9 92.11% + +File '/libfido2/src/err.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_strerr 122 10 91.80% 126 10 92.06% +------------------------------------------------------------------------------------------------------------------- +TOTAL 122 10 91.80% 126 10 92.06% + +File '/libfido2/src/es256.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +es256_pk_decode 8 0 100.00% 9 0 100.00% +es256_pk_encode 56 0 100.00% 48 0 100.00% +es256_sk_new 1 0 100.00% 3 0 100.00% +es256_sk_free 6 0 100.00% 7 0 100.00% +es256_pk_new 1 0 100.00% 3 0 100.00% +es256_pk_free 6 0 100.00% 7 0 100.00% +es256_pk_from_ptr 15 0 100.00% 17 0 100.00% +es256_pk_set_x 1 0 100.00% 4 0 100.00% +es256_pk_set_y 1 0 100.00% 4 0 100.00% +es256_sk_create 39 0 100.00% 40 0 100.00% +es256_pk_to_EVP_PKEY 42 0 100.00% 53 0 100.00% +es256_pk_from_EC_KEY 42 2 95.24% 47 4 91.49% +es256_pk_from_EVP_PKEY 8 0 100.00% 7 0 100.00% +es256_sk_to_EVP_PKEY 28 0 100.00% 39 0 100.00% +es256_derive_pk 25 0 100.00% 29 0 100.00% +es256_verify_sig 12 2 83.33% 19 5 73.68% +es256_pk_verify_sig 7 1 85.71% 13 2 84.62% +es256.c:decode_pubkey_point 9 0 100.00% 13 0 100.00% +es256.c:decode_coord 8 0 100.00% 10 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 315 5 98.41% 372 11 97.04% + +File '/libfido2/src/es384.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +es384_pk_decode 8 0 100.00% 9 0 100.00% +es384_pk_new 1 0 100.00% 3 0 100.00% +es384_pk_free 6 0 100.00% 7 0 100.00% +es384_pk_from_ptr 15 0 100.00% 17 0 100.00% +es384_pk_to_EVP_PKEY 42 0 100.00% 53 0 100.00% +es384_pk_from_EC_KEY 42 2 95.24% 47 4 91.49% +es384_pk_from_EVP_PKEY 8 0 100.00% 7 0 100.00% +es384_verify_sig 12 2 83.33% 19 5 73.68% +es384_pk_verify_sig 7 1 85.71% 13 2 84.62% +es384.c:decode_pubkey_point 9 0 100.00% 13 0 100.00% +es384.c:decode_coord 8 0 100.00% 10 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 158 5 96.84% 198 11 94.44% + +File '/libfido2/src/extern.h': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- + +File '/libfido2/src/fallthrough.h': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- + +File '/libfido2/src/fido.h': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- + +File '/libfido2/src/hid.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_hid_get_usage 13 0 100.00% 22 0 100.00% +fido_hid_get_report_len 19 0 100.00% 27 0 100.00% +fido_dev_info_new 1 0 100.00% 3 0 100.00% +fido_dev_info_free 9 0 100.00% 9 0 100.00% +fido_dev_info_ptr 1 0 100.00% 3 0 100.00% +fido_dev_info_set 26 2 92.31% 30 3 90.00% +fido_dev_info_path 1 0 100.00% 3 0 100.00% +fido_dev_info_vendor 1 0 100.00% 3 0 100.00% +fido_dev_info_product 1 0 100.00% 3 0 100.00% +fido_dev_info_manufacturer_string 1 0 100.00% 3 0 100.00% +fido_dev_info_product_string 1 0 100.00% 3 0 100.00% +hid.c:get_key_len 6 0 100.00% 12 0 100.00% +hid.c:get_key_val 6 0 100.00% 18 0 100.00% +hid.c:fido_dev_info_reset 1 0 100.00% 6 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 87 2 97.70% 145 3 97.93% + +File '/libfido2/src/hid_linux.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_hid_manifest 35 4 88.57% 41 2 95.12% +fido_hid_open 33 33 0.00% 51 51 0.00% +fido_hid_close 3 3 0.00% 6 6 0.00% +fido_hid_set_sigmask 2 2 0.00% 6 6 0.00% +fido_hid_read 15 15 0.00% 21 21 0.00% +fido_hid_write 12 12 0.00% 17 17 0.00% +fido_hid_report_in_len 1 1 0.00% 4 4 0.00% +fido_hid_report_out_len 1 1 0.00% 4 4 0.00% +hid_linux.c:copy_info 34 0 100.00% 44 0 100.00% +hid_linux.c:is_fido 15 1 93.33% 16 1 93.75% +hid_linux.c:get_parent_attr 6 0 100.00% 9 0 100.00% +hid_linux.c:parse_uevent 12 0 100.00% 24 0 100.00% +hid_linux.c:get_usb_attr 1 0 100.00% 3 0 100.00% +hid_linux.c:get_report_descriptor 14 1 92.86% 17 3 82.35% +------------------------------------------------------------------------------------------------------------------- +TOTAL 184 73 60.33% 263 115 56.27% + +File '/libfido2/src/hid_unix.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_hid_unix_open 18 11 38.89% 22 14 36.36% +fido_hid_unix_wait 11 10 9.09% 21 12 42.86% +------------------------------------------------------------------------------------------------------------------- +TOTAL 29 21 27.59% 43 26 39.53% + +File '/libfido2/src/info.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_dev_get_cbor_info_wait 10 0 100.00% 7 0 100.00% +fido_dev_get_cbor_info 1 0 100.00% 4 0 100.00% +fido_cbor_info_new 4 0 100.00% 7 0 100.00% +fido_cbor_info_reset 1 0 100.00% 10 0 100.00% +fido_cbor_info_free 6 0 100.00% 8 0 100.00% +fido_cbor_info_versions_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_versions_len 1 0 100.00% 3 0 100.00% +fido_cbor_info_extensions_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_extensions_len 1 0 100.00% 3 0 100.00% +fido_cbor_info_transports_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_transports_len 1 0 100.00% 3 0 100.00% +fido_cbor_info_aaguid_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_aaguid_len 1 0 100.00% 3 0 100.00% +fido_cbor_info_options_name_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_options_value_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_options_len 1 0 100.00% 3 0 100.00% +fido_cbor_info_maxcredbloblen 1 0 100.00% 3 0 100.00% +fido_cbor_info_maxmsgsiz 1 0 100.00% 3 0 100.00% +fido_cbor_info_maxcredcntlst 1 0 100.00% 3 0 100.00% +fido_cbor_info_maxcredidlen 1 0 100.00% 3 0 100.00% +fido_cbor_info_maxlargeblob 1 0 100.00% 3 0 100.00% +fido_cbor_info_fwversion 1 0 100.00% 3 0 100.00% +fido_cbor_info_minpinlen 1 0 100.00% 3 0 100.00% +fido_cbor_info_maxrpid_minpinlen 1 0 100.00% 3 0 100.00% +fido_cbor_info_uv_attempts 1 0 100.00% 3 0 100.00% +fido_cbor_info_uv_modality 1 0 100.00% 3 0 100.00% +fido_cbor_info_rk_remaining 1 0 100.00% 3 0 100.00% +fido_cbor_info_protocols_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_protocols_len 1 0 100.00% 3 0 100.00% +fido_cbor_info_algorithm_count 1 0 100.00% 3 0 100.00% +fido_cbor_info_algorithm_type 4 0 100.00% 5 0 100.00% +fido_cbor_info_algorithm_cose 4 0 100.00% 5 0 100.00% +fido_cbor_info_new_pin_required 1 0 100.00% 3 0 100.00% +fido_cbor_info_certs_name_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_certs_value_ptr 1 0 100.00% 3 0 100.00% +fido_cbor_info_certs_len 1 0 100.00% 3 0 100.00% +info.c:fido_dev_get_cbor_info_tx 8 0 100.00% 9 0 100.00% +info.c:fido_dev_get_cbor_info_rx 14 0 100.00% 21 0 100.00% +info.c:parse_reply_element 32 0 100.00% 59 0 100.00% +info.c:decode_string_array 12 0 100.00% 17 0 100.00% +info.c:decode_string 4 0 100.00% 10 0 100.00% +info.c:decode_aaguid 8 0 100.00% 10 0 100.00% +info.c:decode_options 11 0 100.00% 15 0 100.00% +info.c:decode_option 7 0 100.00% 15 0 100.00% +info.c:decode_protocols 12 0 100.00% 17 0 100.00% +info.c:decode_protocol 6 0 100.00% 12 0 100.00% +info.c:decode_algorithms 12 0 100.00% 17 0 100.00% +info.c:decode_algorithm 9 0 100.00% 17 0 100.00% +info.c:decode_algorithm_entry 20 0 100.00% 27 0 100.00% +info.c:decode_certs 11 0 100.00% 15 0 100.00% +info.c:decode_cert 7 0 100.00% 15 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 232 0 100.00% 409 0 100.00% + +File '/libfido2/src/io.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_tx 14 0 100.00% 11 0 100.00% +fido_rx 13 1 92.31% 14 3 78.57% +fido_rx_cbor_status 16 0 100.00% 19 0 100.00% +io.c:transport_tx 7 0 100.00% 10 0 100.00% +io.c:tx_empty 9 0 100.00% 14 0 100.00% +io.c:tx_pkt 7 0 100.00% 10 0 100.00% +io.c:tx 13 0 100.00% 19 0 100.00% +io.c:tx_preamble 17 1 94.12% 20 1 95.00% +io.c:tx_frame 16 1 93.75% 18 1 94.44% +io.c:transport_rx 7 0 100.00% 10 0 100.00% +io.c:rx 40 2 95.00% 52 2 96.15% +io.c:rx_preamble 23 2 91.30% 22 5 77.27% +io.c:rx_frame 11 0 100.00% 11 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 193 7 96.37% 230 12 94.78% + +File '/libfido2/src/iso7816.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +iso7816_new 4 0 100.00% 16 0 100.00% +iso7816_free 6 0 100.00% 7 0 100.00% +iso7816_add 6 1 83.33% 8 1 87.50% +iso7816_ptr 1 0 100.00% 3 0 100.00% +iso7816_len 1 0 100.00% 4 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 18 1 94.44% 38 1 97.37% + +File '/libfido2/src/largeblob.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_dev_largeblob_get 26 2 92.31% 38 4 89.47% +fido_dev_largeblob_set 27 0 100.00% 36 0 100.00% +fido_dev_largeblob_remove 12 0 100.00% 18 0 100.00% +fido_dev_largeblob_get_array 15 2 86.67% 27 4 85.19% +fido_dev_largeblob_set_array 14 0 100.00% 19 0 100.00% +largeblob.c:largeblob_get_array 32 0 100.00% 36 0 100.00% +largeblob.c:get_chunklen 10 1 90.00% 9 1 88.89% +largeblob.c:largeblob_get_tx 19 0 100.00% 24 0 100.00% +largeblob.c:largeblob_get_rx 26 0 100.00% 30 0 100.00% +largeblob.c:parse_largeblob_reply 8 0 100.00% 9 0 100.00% +largeblob.c:largeblob_array_check 7 0 100.00% 16 0 100.00% +largeblob.c:largeblob_array_digest 10 0 100.00% 9 0 100.00% +largeblob.c:largeblob_array_load 14 2 85.71% 19 7 63.16% +largeblob.c:largeblob_array_lookup 25 0 100.00% 33 0 100.00% +largeblob.c:largeblob_decode 16 2 87.50% 16 6 62.50% +largeblob.c:largeblob_do_decode 27 3 88.89% 30 7 76.67% +largeblob.c:largeblob_decrypt 15 0 100.00% 24 0 100.00% +largeblob.c:largeblob_aad 1 0 100.00% 10 0 100.00% +largeblob.c:largeblob_reset 1 0 100.00% 5 0 100.00% +largeblob.c:largeblob_encode 16 0 100.00% 21 0 100.00% +largeblob.c:largeblob_new 1 0 100.00% 3 0 100.00% +largeblob.c:largeblob_seal 20 0 100.00% 32 0 100.00% +largeblob.c:largeblob_get_nonce 8 0 100.00% 16 0 100.00% +largeblob.c:largeblob_free 6 0 100.00% 8 0 100.00% +largeblob.c:largeblob_add 27 2 92.59% 35 3 91.43% +largeblob.c:largeblob_drop 21 0 100.00% 27 0 100.00% +largeblob.c:largeblob_set_array 54 2 96.30% 61 4 93.44% +largeblob.c:largeblob_get_uv_token 19 0 100.00% 23 0 100.00% +largeblob.c:largeblob_set_tx 35 0 100.00% 36 0 100.00% +largeblob.c:prepare_hmac 13 2 84.62% 23 7 69.57% +------------------------------------------------------------------------------------------------------------------- +TOTAL 525 18 96.57% 693 43 93.80% + +File '/libfido2/src/log.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_log_init 1 0 100.00% 4 0 100.00% +fido_log_debug 6 1 83.33% 8 1 87.50% +fido_log_xxd 16 1 93.75% 24 1 95.83% +fido_log_error 8 2 75.00% 11 2 81.82% +fido_set_log_handler 3 0 100.00% 4 0 100.00% +log.c:log_on_stderr 1 1 0.00% 3 3 0.00% +log.c:do_log 4 0 100.00% 9 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 39 5 87.18% 63 7 88.89% + +File '/libfido2/src/netlink.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_nl_power_nfc 18 0 100.00% 24 0 100.00% +fido_nl_get_nfc_target 17 0 100.00% 31 0 100.00% +fido_nl_free 10 2 80.00% 9 2 77.78% +fido_nl_new 16 1 93.75% 26 3 88.46% +set_netlink_io_functions 1 0 100.00% 4 0 100.00% +netlink.c:nlmsg_new 8 0 100.00% 15 0 100.00% +netlink.c:nlmsg_set_genl 1 0 100.00% 7 0 100.00% +netlink.c:nlmsg_write 6 1 83.33% 7 1 85.71% +netlink.c:nlmsg_set_u32 1 0 100.00% 3 0 100.00% +netlink.c:nlmsg_setattr 15 1 93.33% 17 0 100.00% +netlink.c:nlmsg_tx 10 1 90.00% 13 3 76.92% +netlink.c:nlmsg_ptr 1 0 100.00% 3 0 100.00% +netlink.c:nlmsg_len 1 0 100.00% 3 0 100.00% +netlink.c:nlmsg_rx 11 2 81.82% 17 6 64.71% +netlink.c:nl_parse_reply 20 0 100.00% 28 0 100.00% +netlink.c:nlmsg_from_buf 15 0 100.00% 17 0 100.00% +netlink.c:nlmsg_type 1 0 100.00% 3 0 100.00% +netlink.c:nlmsg_get_status 8 0 100.00% 8 0 100.00% +netlink.c:nlmsg_read 6 0 100.00% 7 0 100.00% +netlink.c:nlmsg_get_genl 6 0 100.00% 7 0 100.00% +netlink.c:nlmsg_iter 6 0 100.00% 13 0 100.00% +netlink.c:nlmsg_getattr 1 0 100.00% 3 0 100.00% +netlink.c:nla_from_buf 17 0 100.00% 21 0 100.00% +netlink.c:nl_nfc_poll 18 0 100.00% 25 0 100.00% +netlink.c:parse_nfc_event 10 0 100.00% 17 0 100.00% +netlink.c:nla_type 1 0 100.00% 3 0 100.00% +netlink.c:nla_get_u32 1 0 100.00% 3 0 100.00% +netlink.c:nla_read 6 0 100.00% 7 0 100.00% +netlink.c:nl_dump_nfc_target 19 0 100.00% 31 0 100.00% +netlink.c:parse_target 9 0 100.00% 13 0 100.00% +netlink.c:nl_get_nfc_family 23 0 100.00% 33 0 100.00% +netlink.c:nlmsg_set_u16 1 0 100.00% 3 0 100.00% +netlink.c:nlmsg_set_str 1 0 100.00% 3 0 100.00% +netlink.c:parse_family 10 0 100.00% 17 0 100.00% +netlink.c:nla_get_u16 1 0 100.00% 3 0 100.00% +netlink.c:nla_iter 6 0 100.00% 13 0 100.00% +netlink.c:nla_getattr 1 0 100.00% 3 0 100.00% +netlink.c:parse_mcastgrps 1 0 100.00% 3 0 100.00% +netlink.c:parse_mcastgrp 15 0 100.00% 24 0 100.00% +netlink.c:nla_get_str 10 0 100.00% 11 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 329 8 97.57% 498 15 96.99% + +File '/libfido2/src/nfc.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_nfc_tx 28 0 100.00% 43 0 100.00% +fido_nfc_rx 8 0 100.00% 13 0 100.00% +nfc_is_fido 13 1 92.31% 21 3 85.71% +fido_is_nfc 3 0 100.00% 3 0 100.00% +fido_dev_set_nfc 4 1 75.00% 18 3 83.33% +nfc.c:nfc_do_tx 20 0 100.00% 25 0 100.00% +nfc.c:tx_short_apdu 14 0 100.00% 32 0 100.00% +nfc.c:rx_init 25 0 100.00% 27 0 100.00% +nfc.c:rx_cbor 4 0 100.00% 6 0 100.00% +nfc.c:rx_msg 18 2 88.89% 23 6 73.91% +nfc.c:rx_apdu 14 1 92.86% 22 3 86.36% +nfc.c:tx_get_response 4 0 100.00% 11 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 155 5 96.77% 244 15 93.85% + +File '/libfido2/src/nfc_linux.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_nfc_manifest 35 7 80.00% 45 15 66.67% +fido_nfc_open 20 3 85.00% 23 4 82.61% +fido_nfc_close 1 1 0.00% 4 4 0.00% +fido_nfc_set_sigmask 2 2 0.00% 6 6 0.00% +fido_nfc_read 14 14 0.00% 30 30 0.00% +fido_nfc_write 12 12 0.00% 18 18 0.00% +nfc_linux.c:copy_info 39 22 43.59% 44 16 63.64% +nfc_linux.c:get_usb_attr 1 1 0.00% 3 3 0.00% +nfc_linux.c:get_parent_attr 6 6 0.00% 9 9 0.00% +nfc_linux.c:sysnum_from_syspath 15 0 100.00% 17 0 100.00% +nfc_linux.c:nfc_new 6 0 100.00% 11 0 100.00% +nfc_linux.c:nfc_target_connect 9 9 0.00% 21 21 0.00% +nfc_linux.c:nfc_free 12 0 100.00% 11 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 172 77 55.23% 242 126 47.93% + +File '/libfido2/src/pcsc.c': +Name Regions Miss Cover Lines Miss Cover +------------------------------------------------------------------------------------------------------------------- +fido_pcsc_manifest 51 0 100.00% 55 0 100.00% +fido_pcsc_open 32 0 100.00% 43 0 100.00% +fido_pcsc_close 6 0 100.00% 9 0 100.00% +fido_pcsc_read 8 0 100.00% 16 0 100.00% +fido_pcsc_write 8 0 100.00% 22 0 100.00% +fido_pcsc_tx 1 0 100.00% 3 0 100.00% +fido_pcsc_rx 1 0 100.00% 3 0 100.00% +fido_is_pcsc 3 0 100.00% 3 0 100.00% +fido_dev_set_pcsc 4 1 75.00% 18 3 83.33% +pcsc.c:list_readers 24 0 100.00% 24 0 100.00% +pcsc.c:copy_info 30 0 100.00% 41 0 100.00% +pcsc.c:get_reader 25 0 100.00% 28 0 100.00% +pcsc.c:prepare_io_request 11 0 100.00% 17 0 100.00% +------------------------------------------------------------------------------------------------------------------- +TOTAL 204 1 99.51% 282 3 98.94% + +File '/libfido2/src/pin.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_sha256 7 0 100.00% 10 0 100.00% +fido_dev_get_uv_token 1 0 100.00% 3 0 100.00% +fido_dev_set_pin 1 0 100.00% 4 0 100.00% +fido_dev_get_retry_count 1 0 100.00% 4 0 100.00% +fido_dev_get_uv_retry_count 1 0 100.00% 4 0 100.00% +cbor_add_uv_params 17 0 100.00% 23 0 100.00% +pin.c:uv_token_wait 14 2 85.71% 12 1 91.67% +pin.c:ctap21_uv_token_tx 49 0 100.00% 53 0 100.00% +pin.c:pin_sha256_enc 19 0 100.00% 24 0 100.00% +pin.c:encode_uv_permission 20 1 95.00% 19 3 84.21% +pin.c:ctap20_uv_token_tx 37 0 100.00% 45 0 100.00% +pin.c:uv_token_rx 27 0 100.00% 34 0 100.00% +pin.c:parse_uv_token 8 0 100.00% 10 0 100.00% +pin.c:fido_dev_set_pin_wait 21 0 100.00% 24 0 100.00% +pin.c:fido_dev_change_pin_tx 45 0 100.00% 56 0 100.00% +pin.c:pin_pad64_enc 15 0 100.00% 21 0 100.00% +pin.c:pad64 18 0 100.00% 20 0 100.00% +pin.c:fido_dev_set_pin_tx 33 0 100.00% 41 0 100.00% +pin.c:fido_dev_get_pin_retry_count_wait 10 0 100.00% 7 0 100.00% +pin.c:fido_dev_get_retry_count_tx 19 0 100.00% 23 0 100.00% +pin.c:fido_dev_get_pin_retry_count_rx 19 0 100.00% 24 0 100.00% +pin.c:parse_pin_retry_count 1 0 100.00% 3 0 100.00% +pin.c:parse_retry_count 13 0 100.00% 16 0 100.00% +pin.c:fido_dev_get_uv_retry_count_wait 10 0 100.00% 7 0 100.00% +pin.c:fido_dev_get_uv_retry_count_rx 19 0 100.00% 24 0 100.00% +pin.c:parse_uv_retry_count 1 0 100.00% 3 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 426 3 99.30% 514 4 99.22% + +File '/libfido2/src/random.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_get_random 6 0 100.00% 6 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 6 0 100.00% 6 0 100.00% + +File '/libfido2/src/reset.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_dev_reset 1 0 100.00% 4 0 100.00% +reset.c:fido_dev_reset_wait 15 0 100.00% 11 0 100.00% +reset.c:fido_dev_reset_tx 8 0 100.00% 8 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 24 0 100.00% 23 0 100.00% + +File '/libfido2/src/rs1.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +rs1_verify_sig 20 2 90.00% 30 6 80.00% +rs1.c:rs1_get_EVP_MD 1 0 100.00% 3 0 100.00% +rs1.c:rs1_free_EVP_MD 1 0 100.00% 3 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 22 2 90.91% 36 6 83.33% + +File '/libfido2/src/rs256.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +rs256_pk_decode 8 0 100.00% 9 0 100.00% +rs256_pk_new 1 0 100.00% 3 0 100.00% +rs256_pk_free 6 0 100.00% 7 0 100.00% +rs256_pk_from_ptr 10 0 100.00% 12 0 100.00% +rs256_pk_to_EVP_PKEY 35 0 100.00% 43 0 100.00% +rs256_pk_from_RSA 32 6 81.25% 26 9 65.38% +rs256_pk_from_EVP_PKEY 8 0 100.00% 7 0 100.00% +rs256_verify_sig 20 2 90.00% 30 5 83.33% +rs256_pk_verify_sig 7 1 85.71% 13 2 84.62% +rs256.c:decode_rsa_pubkey 9 0 100.00% 13 0 100.00% +rs256.c:decode_bignum 8 0 100.00% 10 0 100.00% +rs256.c:rs256_get_EVP_MD 1 0 100.00% 3 0 100.00% +rs256.c:rs256_free_EVP_MD 1 0 100.00% 3 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 146 9 93.84% 179 16 91.06% + +File '/libfido2/src/time.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_time_now 4 0 100.00% 7 0 100.00% +fido_time_delta 23 1 95.65% 23 0 100.00% +time.c:timespec_to_ms 16 2 87.50% 13 2 84.62% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 43 3 93.02% 43 2 95.35% + +File '/libfido2/src/touch.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_dev_get_touch_begin 50 0 100.00% 59 0 100.00% +fido_dev_get_touch_status 17 0 100.00% 20 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 67 0 100.00% 79 0 100.00% + +File '/libfido2/src/tpm.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_get_signed_hash_tpm 25 0 100.00% 39 0 100.00% +tpm.c:check_es256_pubarea 19 0 100.00% 30 0 100.00% +tpm.c:bswap_es256_pubarea 1 0 100.00% 12 0 100.00% +tpm.c:check_rs256_pubarea 17 0 100.00% 28 0 100.00% +tpm.c:bswap_rs256_pubarea 1 0 100.00% 10 0 100.00% +tpm.c:check_sha1_certinfo 15 0 100.00% 38 0 100.00% +tpm.c:get_signed_sha1 17 0 100.00% 19 0 100.00% +tpm.c:get_signed_name 7 0 100.00% 10 0 100.00% +tpm.c:bswap_sha1_certinfo 1 0 100.00% 8 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 103 0 100.00% 194 0 100.00% + +File '/libfido2/src/types.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_str_array_free 4 0 100.00% 7 0 100.00% +fido_opt_array_free 4 0 100.00% 9 0 100.00% +fido_byte_array_free 1 0 100.00% 5 0 100.00% +fido_algo_free 1 0 100.00% 5 0 100.00% +fido_algo_array_free 4 0 100.00% 7 0 100.00% +fido_cert_array_free 4 0 100.00% 9 0 100.00% +fido_str_array_pack 11 0 100.00% 14 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 29 0 100.00% 56 0 100.00% + +File '/libfido2/src/u2f.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +u2f_register 76 0 100.00% 81 0 100.00% +u2f_authenticate 33 0 100.00% 37 0 100.00% +u2f_get_touch_begin 37 0 100.00% 45 0 100.00% +u2f_get_touch_status 26 0 100.00% 36 0 100.00% +u2f.c:key_lookup 51 0 100.00% 65 0 100.00% +u2f.c:send_dummy_register 37 0 100.00% 45 0 100.00% +u2f.c:delay_ms 13 1 92.31% 15 3 80.00% +u2f.c:parse_register_reply 49 0 100.00% 62 0 100.00% +u2f.c:x5c_get 21 1 95.24% 26 3 88.46% +u2f.c:sig_get 6 0 100.00% 10 0 100.00% +u2f.c:encode_cred_attstmt 45 0 100.00% 52 0 100.00% +u2f.c:encode_cred_authdata 33 2 93.94% 61 6 90.16% +u2f.c:cbor_blob_from_ec_point 22 0 100.00% 31 0 100.00% +u2f.c:u2f_authenticate_single 32 0 100.00% 43 0 100.00% +u2f.c:do_auth 56 0 100.00% 67 0 100.00% +u2f.c:parse_auth_reply 23 0 100.00% 23 0 100.00% +u2f.c:authdata_fake 12 0 100.00% 27 0 100.00% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 572 4 99.30% 726 12 98.35% + +File '/libfido2/src/util.c': +Name Regions Miss Cover Lines Miss Cover +--------------------------------------------------------------------------------------------------------------------- +fido_to_uint64 14 1 92.86% 14 1 92.86% +--------------------------------------------------------------------------------------------------------------------- +TOTAL 14 1 92.86% 14 1 92.86% diff --git a/fuzz/fuzz_assert.c b/fuzz/fuzz_assert.c new file mode 100644 index 0000000..03cb51c --- /dev/null +++ b/fuzz/fuzz_assert.c @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2019-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "wiredata_u2f.h" +#include "dummy.h" + +#include "../openbsd-compat/openbsd-compat.h" + +/* Parameter set defining a FIDO2 get assertion operation. */ +struct param { + char pin[MAXSTR]; + char rp_id[MAXSTR]; + int ext; + int seed; + struct blob cdh; + struct blob cred; + struct blob es256; + struct blob rs256; + struct blob eddsa; + struct blob wire_data; + uint8_t cred_count; + uint8_t type; + uint8_t opt; + uint8_t up; + uint8_t uv; +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * get assertion using the example parameters above. + */ +static const uint8_t dummy_wire_data_fido[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_ASSERT, +}; + +/* + * Collection of HID reports from an authenticator issued with a U2F + * authentication using the example parameters above. + */ +static const uint8_t dummy_wire_data_u2f[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_AUTH, +}; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 15 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_byte(v[0], &p->uv) < 0 || + unpack_byte(v[1], &p->up) < 0 || + unpack_byte(v[2], &p->opt) < 0 || + unpack_byte(v[3], &p->type) < 0 || + unpack_byte(v[4], &p->cred_count) < 0 || + unpack_int(v[5], &p->ext) < 0 || + unpack_int(v[6], &p->seed) < 0 || + unpack_string(v[7], p->rp_id) < 0 || + unpack_string(v[8], p->pin) < 0 || + unpack_blob(v[9], &p->wire_data) < 0 || + unpack_blob(v[10], &p->rs256) < 0 || + unpack_blob(v[11], &p->es256) < 0 || + unpack_blob(v[12], &p->eddsa) < 0 || + unpack_blob(v[13], &p->cred) < 0 || + unpack_blob(v[14], &p->cdh) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[15], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(15)) == NULL || + (argv[0] = pack_byte(p->uv)) == NULL || + (argv[1] = pack_byte(p->up)) == NULL || + (argv[2] = pack_byte(p->opt)) == NULL || + (argv[3] = pack_byte(p->type)) == NULL || + (argv[4] = pack_byte(p->cred_count)) == NULL || + (argv[5] = pack_int(p->ext)) == NULL || + (argv[6] = pack_int(p->seed)) == NULL || + (argv[7] = pack_string(p->rp_id)) == NULL || + (argv[8] = pack_string(p->pin)) == NULL || + (argv[9] = pack_blob(&p->wire_data)) == NULL || + (argv[10] = pack_blob(&p->rs256)) == NULL || + (argv[11] = pack_blob(&p->es256)) == NULL || + (argv[12] = pack_blob(&p->eddsa)) == NULL || + (argv[13] = pack_blob(&p->cred)) == NULL || + (argv[14] = pack_blob(&p->cdh)) == NULL) + goto fail; + + for (size_t i = 0; i < 15; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 15; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + dummy.type = 1; /* rsa */ + dummy.ext = FIDO_EXT_HMAC_SECRET; + + strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin)); + strlcpy(dummy.rp_id, dummy_rp_id, sizeof(dummy.rp_id)); + + dummy.cred.len = sizeof(dummy_cdh); /* XXX */ + dummy.cdh.len = sizeof(dummy_cdh); + dummy.es256.len = sizeof(dummy_es256); + dummy.rs256.len = sizeof(dummy_rs256); + dummy.eddsa.len = sizeof(dummy_eddsa); + dummy.wire_data.len = sizeof(dummy_wire_data_fido); + + memcpy(&dummy.cred.body, &dummy_cdh, dummy.cred.len); /* XXX */ + memcpy(&dummy.cdh.body, &dummy_cdh, dummy.cdh.len); + memcpy(&dummy.wire_data.body, &dummy_wire_data_fido, + dummy.wire_data.len); + memcpy(&dummy.es256.body, &dummy_es256, dummy.es256.len); + memcpy(&dummy.rs256.body, &dummy_rs256, dummy.rs256.len); + memcpy(&dummy.eddsa.body, &dummy_eddsa, dummy.eddsa.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static void +get_assert(fido_assert_t *assert, uint8_t opt, const struct blob *cdh, + const char *rp_id, int ext, uint8_t up, uint8_t uv, const char *pin, + uint8_t cred_count, const struct blob *cred) +{ + fido_dev_t *dev; + + if ((dev = open_dev(opt & 2)) == NULL) + return; + if (opt & 1) + fido_dev_force_u2f(dev); + if (ext & FIDO_EXT_HMAC_SECRET) + fido_assert_set_extensions(assert, FIDO_EXT_HMAC_SECRET); + if (ext & FIDO_EXT_CRED_BLOB) + fido_assert_set_extensions(assert, FIDO_EXT_CRED_BLOB); + if (ext & FIDO_EXT_LARGEBLOB_KEY) + fido_assert_set_extensions(assert, FIDO_EXT_LARGEBLOB_KEY); + if (up & 1) + fido_assert_set_up(assert, FIDO_OPT_TRUE); + else if (opt & 1) + fido_assert_set_up(assert, FIDO_OPT_FALSE); + if (uv & 1) + fido_assert_set_uv(assert, FIDO_OPT_TRUE); + + for (uint8_t i = 0; i < cred_count; i++) + fido_assert_allow_cred(assert, cred->body, cred->len); + + fido_assert_set_clientdata_hash(assert, cdh->body, cdh->len); + fido_assert_set_rp(assert, rp_id); + /* XXX reuse cred as hmac salt */ + fido_assert_set_hmac_salt(assert, cred->body, cred->len); + + /* repeat memory operations to trigger reallocation paths */ + fido_assert_set_clientdata_hash(assert, cdh->body, cdh->len); + fido_assert_set_rp(assert, rp_id); + fido_assert_set_hmac_salt(assert, cred->body, cred->len); + + if (strlen(pin) == 0) + pin = NULL; + + fido_dev_get_assert(dev, assert, (opt & 1) ? NULL : pin); + + fido_dev_cancel(dev); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +verify_assert(int type, const unsigned char *cdh_ptr, size_t cdh_len, + const char *rp_id, const unsigned char *authdata_ptr, size_t authdata_len, + const unsigned char *sig_ptr, size_t sig_len, uint8_t up, uint8_t uv, + int ext, void *pk) +{ + fido_assert_t *assert = NULL; + int r; + + if ((assert = fido_assert_new()) == NULL) + return; + + fido_assert_set_clientdata_hash(assert, cdh_ptr, cdh_len); + fido_assert_set_rp(assert, rp_id); + fido_assert_set_count(assert, 1); + + if (fido_assert_set_authdata(assert, 0, authdata_ptr, + authdata_len) != FIDO_OK) { + fido_assert_set_authdata_raw(assert, 0, authdata_ptr, + authdata_len); + } + + if (up & 1) + fido_assert_set_up(assert, FIDO_OPT_TRUE); + if (uv & 1) + fido_assert_set_uv(assert, FIDO_OPT_TRUE); + + fido_assert_set_extensions(assert, ext); + fido_assert_set_sig(assert, 0, sig_ptr, sig_len); + + /* repeat memory operations to trigger reallocation paths */ + if (fido_assert_set_authdata(assert, 0, authdata_ptr, + authdata_len) != FIDO_OK) { + fido_assert_set_authdata_raw(assert, 0, authdata_ptr, + authdata_len); + } + fido_assert_set_sig(assert, 0, sig_ptr, sig_len); + + r = fido_assert_verify(assert, 0, type, pk); + consume(&r, sizeof(r)); + + fido_assert_free(&assert); +} + +/* + * Do a dummy conversion to exercise es256_pk_from_EVP_PKEY(). + */ +static void +es256_convert(const es256_pk_t *k) +{ + EVP_PKEY *pkey = NULL; + es256_pk_t *pk = NULL; + int r; + + if ((pkey = es256_pk_to_EVP_PKEY(k)) == NULL || + (pk = es256_pk_new()) == NULL) + goto out; + + r = es256_pk_from_EVP_PKEY(pk, pkey); + consume(&r, sizeof(r)); +out: + es256_pk_free(&pk); + EVP_PKEY_free(pkey); +} + +/* + * Do a dummy conversion to exercise es384_pk_from_EVP_PKEY(). + */ +static void +es384_convert(const es384_pk_t *k) +{ + EVP_PKEY *pkey = NULL; + es384_pk_t *pk = NULL; + int r; + + if ((pkey = es384_pk_to_EVP_PKEY(k)) == NULL || + (pk = es384_pk_new()) == NULL) + goto out; + + r = es384_pk_from_EVP_PKEY(pk, pkey); + consume(&r, sizeof(r)); +out: + es384_pk_free(&pk); + EVP_PKEY_free(pkey); +} + +/* + * Do a dummy conversion to exercise rs256_pk_from_EVP_PKEY(). + */ +static void +rs256_convert(const rs256_pk_t *k) +{ + EVP_PKEY *pkey = NULL; + rs256_pk_t *pk = NULL; + int r; + + if ((pkey = rs256_pk_to_EVP_PKEY(k)) == NULL || + (pk = rs256_pk_new()) == NULL) + goto out; + + r = rs256_pk_from_EVP_PKEY(pk, pkey); + consume(&r, sizeof(r)); +out: + rs256_pk_free(&pk); + EVP_PKEY_free(pkey); +} + +/* + * Do a dummy conversion to exercise eddsa_pk_from_EVP_PKEY(). + */ +static void +eddsa_convert(const eddsa_pk_t *k) +{ + EVP_PKEY *pkey = NULL; + eddsa_pk_t *pk = NULL; + int r; + + if ((pkey = eddsa_pk_to_EVP_PKEY(k)) == NULL || + (pk = eddsa_pk_new()) == NULL) + goto out; + + r = eddsa_pk_from_EVP_PKEY(pk, pkey); + consume(&r, sizeof(r)); +out: + if (pk) + eddsa_pk_free(&pk); + if (pkey) + EVP_PKEY_free(pkey); +} + +void +test(const struct param *p) +{ + fido_assert_t *assert = NULL; + es256_pk_t *es256_pk = NULL; + es384_pk_t *es384_pk = NULL; + rs256_pk_t *rs256_pk = NULL; + eddsa_pk_t *eddsa_pk = NULL; + uint8_t flags; + uint32_t sigcount; + int cose_alg = 0; + void *pk; + + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + switch (p->type & 3) { + case 0: + cose_alg = COSE_ES256; + + if ((es256_pk = es256_pk_new()) == NULL) + return; + + es256_pk_from_ptr(es256_pk, p->es256.body, p->es256.len); + pk = es256_pk; + + es256_convert(pk); + + break; + case 1: + cose_alg = COSE_RS256; + + if ((rs256_pk = rs256_pk_new()) == NULL) + return; + + rs256_pk_from_ptr(rs256_pk, p->rs256.body, p->rs256.len); + pk = rs256_pk; + + rs256_convert(pk); + + break; + case 2: + cose_alg = COSE_ES384; + + if ((es384_pk = es384_pk_new()) == NULL) + return; + + /* XXX reuse p->es256 as es384 */ + es384_pk_from_ptr(es384_pk, p->es256.body, p->es256.len); + pk = es384_pk; + + es384_convert(pk); + + break; + default: + cose_alg = COSE_EDDSA; + + if ((eddsa_pk = eddsa_pk_new()) == NULL) + return; + + eddsa_pk_from_ptr(eddsa_pk, p->eddsa.body, p->eddsa.len); + pk = eddsa_pk; + + eddsa_convert(pk); + + break; + } + + if ((assert = fido_assert_new()) == NULL) + goto out; + + set_wire_data(p->wire_data.body, p->wire_data.len); + + get_assert(assert, p->opt, &p->cdh, p->rp_id, p->ext, p->up, p->uv, + p->pin, p->cred_count, &p->cred); + + /* XXX +1 on purpose */ + for (size_t i = 0; i <= fido_assert_count(assert); i++) { + verify_assert(cose_alg, + fido_assert_clientdata_hash_ptr(assert), + fido_assert_clientdata_hash_len(assert), + fido_assert_rp_id(assert), + fido_assert_authdata_ptr(assert, i), + fido_assert_authdata_len(assert, i), + fido_assert_sig_ptr(assert, i), + fido_assert_sig_len(assert, i), p->up, p->uv, p->ext, pk); + consume(fido_assert_authdata_raw_ptr(assert, i), + fido_assert_authdata_raw_len(assert, i)); + consume(fido_assert_id_ptr(assert, i), + fido_assert_id_len(assert, i)); + consume(fido_assert_user_id_ptr(assert, i), + fido_assert_user_id_len(assert, i)); + consume(fido_assert_hmac_secret_ptr(assert, i), + fido_assert_hmac_secret_len(assert, i)); + consume_str(fido_assert_user_icon(assert, i)); + consume_str(fido_assert_user_name(assert, i)); + consume_str(fido_assert_user_display_name(assert, i)); + consume(fido_assert_blob_ptr(assert, i), + fido_assert_blob_len(assert, i)); + consume(fido_assert_largeblob_key_ptr(assert, i), + fido_assert_largeblob_key_len(assert, i)); + flags = fido_assert_flags(assert, i); + consume(&flags, sizeof(flags)); + sigcount = fido_assert_sigcount(assert, i); + consume(&sigcount, sizeof(sigcount)); + } + +out: + es256_pk_free(&es256_pk); + es384_pk_free(&es384_pk); + rs256_pk_free(&rs256_pk); + eddsa_pk_free(&eddsa_pk); + + fido_assert_free(&assert); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_byte(&p->uv); + mutate_byte(&p->up); + mutate_byte(&p->opt); + mutate_byte(&p->type); + mutate_byte(&p->cred_count); + mutate_int(&p->ext); + mutate_blob(&p->rs256); + mutate_blob(&p->es256); + mutate_blob(&p->eddsa); + mutate_blob(&p->cred); + mutate_blob(&p->cdh); + mutate_string(p->rp_id); + mutate_string(p->pin); + } + + if (flags & MUTATE_WIREDATA) { + if (p->opt & 1) { + p->wire_data.len = sizeof(dummy_wire_data_u2f); + memcpy(&p->wire_data.body, &dummy_wire_data_u2f, + p->wire_data.len); + } else { + p->wire_data.len = sizeof(dummy_wire_data_fido); + memcpy(&p->wire_data.body, &dummy_wire_data_fido, + p->wire_data.len); + } + mutate_blob(&p->wire_data); + } +} diff --git a/fuzz/fuzz_bio.c b/fuzz/fuzz_bio.c new file mode 100644 index 0000000..0c6b12c --- /dev/null +++ b/fuzz/fuzz_bio.c @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "dummy.h" + +#include "../openbsd-compat/openbsd-compat.h" + +/* Parameter set defining a FIDO2 credential management operation. */ +struct param { + char pin[MAXSTR]; + char name[MAXSTR]; + int seed; + struct blob id; + struct blob info_wire_data; + struct blob enroll_wire_data; + struct blob list_wire_data; + struct blob set_name_wire_data; + struct blob remove_wire_data; +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'getFingerprintSensorInfo' bio enrollment command. + */ +static const uint8_t dummy_info_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_BIO_INFO, +}; + +/* + * Collection of HID reports from an authenticator issued with FIDO2 + * 'enrollBegin' + 'enrollCaptureNextSample' bio enrollment commands. + */ +static const uint8_t dummy_enroll_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_BIO_ENROLL, +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'enumerateEnrollments' bio enrollment command. + */ +static const uint8_t dummy_list_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_BIO_ENUM, +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'setFriendlyName' bio enrollment command. + */ +static const uint8_t dummy_set_name_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_STATUS, +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'removeEnrollment' bio enrollment command. + */ +static const uint8_t dummy_remove_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_STATUS, +}; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 9 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_int(v[0], &p->seed) < 0 || + unpack_string(v[1], p->pin) < 0 || + unpack_string(v[2], p->name) < 0 || + unpack_blob(v[3], &p->id) < 0 || + unpack_blob(v[4], &p->info_wire_data) < 0 || + unpack_blob(v[5], &p->enroll_wire_data) < 0 || + unpack_blob(v[6], &p->list_wire_data) < 0 || + unpack_blob(v[7], &p->set_name_wire_data) < 0 || + unpack_blob(v[8], &p->remove_wire_data) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[9], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(9)) == NULL || + (argv[0] = pack_int(p->seed)) == NULL || + (argv[1] = pack_string(p->pin)) == NULL || + (argv[2] = pack_string(p->name)) == NULL || + (argv[3] = pack_blob(&p->id)) == NULL || + (argv[4] = pack_blob(&p->info_wire_data)) == NULL || + (argv[5] = pack_blob(&p->enroll_wire_data)) == NULL || + (argv[6] = pack_blob(&p->list_wire_data)) == NULL || + (argv[7] = pack_blob(&p->set_name_wire_data)) == NULL || + (argv[8] = pack_blob(&p->remove_wire_data)) == NULL) + goto fail; + + for (size_t i = 0; i < 9; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 9; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin)); + strlcpy(dummy.name, dummy_name, sizeof(dummy.name)); + + dummy.info_wire_data.len = sizeof(dummy_info_wire_data); + dummy.enroll_wire_data.len = sizeof(dummy_enroll_wire_data); + dummy.list_wire_data.len = sizeof(dummy_list_wire_data); + dummy.set_name_wire_data.len = sizeof(dummy_set_name_wire_data); + dummy.remove_wire_data.len = sizeof(dummy_remove_wire_data); + dummy.id.len = sizeof(dummy_id); + + memcpy(&dummy.info_wire_data.body, &dummy_info_wire_data, + dummy.info_wire_data.len); + memcpy(&dummy.enroll_wire_data.body, &dummy_enroll_wire_data, + dummy.enroll_wire_data.len); + memcpy(&dummy.list_wire_data.body, &dummy_list_wire_data, + dummy.list_wire_data.len); + memcpy(&dummy.set_name_wire_data.body, &dummy_set_name_wire_data, + dummy.set_name_wire_data.len); + memcpy(&dummy.remove_wire_data.body, &dummy_remove_wire_data, + dummy.remove_wire_data.len); + memcpy(&dummy.id.body, &dummy_id, dummy.id.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static fido_dev_t * +prepare_dev(void) +{ + fido_dev_t *dev; + bool x; + + if ((dev = open_dev(0)) == NULL) + return NULL; + + x = fido_dev_is_fido2(dev); + consume(&x, sizeof(x)); + x = fido_dev_supports_pin(dev); + consume(&x, sizeof(x)); + x = fido_dev_has_pin(dev); + consume(&x, sizeof(x)); + x = fido_dev_supports_uv(dev); + consume(&x, sizeof(x)); + x = fido_dev_has_uv(dev); + consume(&x, sizeof(x)); + + return dev; +} + +static void +get_info(const struct param *p) +{ + fido_dev_t *dev = NULL; + fido_bio_info_t *i = NULL; + uint8_t type; + uint8_t max_samples; + int r; + + set_wire_data(p->info_wire_data.body, p->info_wire_data.len); + + if ((dev = prepare_dev()) == NULL || (i = fido_bio_info_new()) == NULL) + goto done; + + r = fido_bio_dev_get_info(dev, i); + consume_str(fido_strerr(r)); + + type = fido_bio_info_type(i); + max_samples = fido_bio_info_max_samples(i); + consume(&type, sizeof(type)); + consume(&max_samples, sizeof(max_samples)); + +done: + if (dev) + fido_dev_close(dev); + + fido_dev_free(&dev); + fido_bio_info_free(&i); +} + +static void +consume_template(const fido_bio_template_t *t) +{ + consume_str(fido_bio_template_name(t)); + consume(fido_bio_template_id_ptr(t), fido_bio_template_id_len(t)); +} + +static void +consume_enroll(fido_bio_enroll_t *e) +{ + uint8_t last_status; + uint8_t remaining_samples; + + last_status = fido_bio_enroll_last_status(e); + remaining_samples = fido_bio_enroll_remaining_samples(e); + consume(&last_status, sizeof(last_status)); + consume(&remaining_samples, sizeof(remaining_samples)); +} + +static void +enroll(const struct param *p) +{ + fido_dev_t *dev = NULL; + fido_bio_template_t *t = NULL; + fido_bio_enroll_t *e = NULL; + size_t cnt = 0; + + set_wire_data(p->enroll_wire_data.body, p->enroll_wire_data.len); + + if ((dev = prepare_dev()) == NULL || + (t = fido_bio_template_new()) == NULL || + (e = fido_bio_enroll_new()) == NULL) + goto done; + + fido_bio_dev_enroll_begin(dev, t, e, (uint32_t)p->seed, p->pin); + + consume_template(t); + consume_enroll(e); + + while (fido_bio_enroll_remaining_samples(e) > 0 && cnt++ < 5) { + fido_bio_dev_enroll_continue(dev, t, e, p->seed); + consume_template(t); + consume_enroll(e); + } + +done: + if (dev) + fido_dev_close(dev); + + fido_dev_free(&dev); + fido_bio_template_free(&t); + fido_bio_enroll_free(&e); +} + +static void +list(const struct param *p) +{ + fido_dev_t *dev = NULL; + fido_bio_template_array_t *ta = NULL; + const fido_bio_template_t *t = NULL; + + set_wire_data(p->list_wire_data.body, p->list_wire_data.len); + + if ((dev = prepare_dev()) == NULL || + (ta = fido_bio_template_array_new()) == NULL) + goto done; + + fido_bio_dev_get_template_array(dev, ta, p->pin); + + /* +1 on purpose */ + for (size_t i = 0; i < fido_bio_template_array_count(ta) + 1; i++) + if ((t = fido_bio_template(ta, i)) != NULL) + consume_template(t); + +done: + if (dev) + fido_dev_close(dev); + + fido_dev_free(&dev); + fido_bio_template_array_free(&ta); +} + +static void +set_name(const struct param *p) +{ + fido_dev_t *dev = NULL; + fido_bio_template_t *t = NULL; + + set_wire_data(p->set_name_wire_data.body, p->set_name_wire_data.len); + + if ((dev = prepare_dev()) == NULL || + (t = fido_bio_template_new()) == NULL) + goto done; + + fido_bio_template_set_name(t, p->name); + fido_bio_template_set_id(t, p->id.body, p->id.len); + consume_template(t); + + fido_bio_dev_set_template_name(dev, t, p->pin); + +done: + if (dev) + fido_dev_close(dev); + + fido_dev_free(&dev); + fido_bio_template_free(&t); +} + +static void +del(const struct param *p) +{ + fido_dev_t *dev = NULL; + fido_bio_template_t *t = NULL; + int r; + + set_wire_data(p->remove_wire_data.body, p->remove_wire_data.len); + + if ((dev = prepare_dev()) == NULL || + (t = fido_bio_template_new()) == NULL) + goto done; + + r = fido_bio_template_set_id(t, p->id.body, p->id.len); + consume_template(t); + consume_str(fido_strerr(r)); + + fido_bio_dev_enroll_remove(dev, t, p->pin); + +done: + if (dev) + fido_dev_close(dev); + + fido_dev_free(&dev); + fido_bio_template_free(&t); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + get_info(p); + enroll(p); + list(p); + set_name(p); + del(p); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_blob(&p->id); + mutate_string(p->pin); + mutate_string(p->name); + } + + if (flags & MUTATE_WIREDATA) { + mutate_blob(&p->info_wire_data); + mutate_blob(&p->enroll_wire_data); + mutate_blob(&p->list_wire_data); + mutate_blob(&p->set_name_wire_data); + mutate_blob(&p->remove_wire_data); + } +} diff --git a/fuzz/fuzz_cred.c b/fuzz/fuzz_cred.c new file mode 100644 index 0000000..497298f --- /dev/null +++ b/fuzz/fuzz_cred.c @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "wiredata_u2f.h" +#include "dummy.h" + +#include "../openbsd-compat/openbsd-compat.h" + +/* Parameter set defining a FIDO2 make credential operation. */ +struct param { + char pin[MAXSTR]; + char rp_id[MAXSTR]; + char rp_name[MAXSTR]; + char user_icon[MAXSTR]; + char user_name[MAXSTR]; + char user_nick[MAXSTR]; + int ext; + int seed; + struct blob cdh; + struct blob excl_cred; + struct blob user_id; + struct blob wire_data; + uint8_t excl_count; + uint8_t rk; + uint8_t type; + uint8_t opt; + uint8_t uv; +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * make credential using the example parameters above. + */ +static const uint8_t dummy_wire_data_fido[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_CBOR_CRED, +}; + +/* + * Collection of HID reports from an authenticator issued with a U2F + * registration using the example parameters above. + */ +static const uint8_t dummy_wire_data_u2f[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_6985, + WIREDATA_CTAP_U2F_REGISTER, +}; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 17 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_byte(v[0], &p->rk) < 0 || + unpack_byte(v[1], &p->type) < 0 || + unpack_byte(v[2], &p->opt) < 0 || + unpack_byte(v[3], &p->uv) < 0 || + unpack_byte(v[4], &p->excl_count) < 0 || + unpack_int(v[5], &p->ext) < 0 || + unpack_int(v[6], &p->seed) < 0 || + unpack_string(v[7], p->pin) < 0 || + unpack_string(v[8], p->rp_id) < 0 || + unpack_string(v[9], p->rp_name) < 0 || + unpack_string(v[10], p->user_icon) < 0 || + unpack_string(v[11], p->user_name) < 0 || + unpack_string(v[12], p->user_nick) < 0 || + unpack_blob(v[13], &p->cdh) < 0 || + unpack_blob(v[14], &p->user_id) < 0 || + unpack_blob(v[15], &p->wire_data) < 0 || + unpack_blob(v[16], &p->excl_cred) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[17], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(17)) == NULL || + (argv[0] = pack_byte(p->rk)) == NULL || + (argv[1] = pack_byte(p->type)) == NULL || + (argv[2] = pack_byte(p->opt)) == NULL || + (argv[3] = pack_byte(p->uv)) == NULL || + (argv[4] = pack_byte(p->excl_count)) == NULL || + (argv[5] = pack_int(p->ext)) == NULL || + (argv[6] = pack_int(p->seed)) == NULL || + (argv[7] = pack_string(p->pin)) == NULL || + (argv[8] = pack_string(p->rp_id)) == NULL || + (argv[9] = pack_string(p->rp_name)) == NULL || + (argv[10] = pack_string(p->user_icon)) == NULL || + (argv[11] = pack_string(p->user_name)) == NULL || + (argv[12] = pack_string(p->user_nick)) == NULL || + (argv[13] = pack_blob(&p->cdh)) == NULL || + (argv[14] = pack_blob(&p->user_id)) == NULL || + (argv[15] = pack_blob(&p->wire_data)) == NULL || + (argv[16] = pack_blob(&p->excl_cred)) == NULL) + goto fail; + + for (size_t i = 0; i < 17; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 17; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + dummy.type = 1; + dummy.ext = FIDO_EXT_HMAC_SECRET; + + strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin)); + strlcpy(dummy.rp_id, dummy_rp_id, sizeof(dummy.rp_id)); + strlcpy(dummy.rp_name, dummy_rp_name, sizeof(dummy.rp_name)); + strlcpy(dummy.user_icon, dummy_user_icon, sizeof(dummy.user_icon)); + strlcpy(dummy.user_name, dummy_user_name, sizeof(dummy.user_name)); + strlcpy(dummy.user_nick, dummy_user_nick, sizeof(dummy.user_nick)); + + dummy.cdh.len = sizeof(dummy_cdh); + dummy.user_id.len = sizeof(dummy_user_id); + dummy.wire_data.len = sizeof(dummy_wire_data_fido); + + memcpy(&dummy.cdh.body, &dummy_cdh, dummy.cdh.len); + memcpy(&dummy.user_id.body, &dummy_user_id, dummy.user_id.len); + memcpy(&dummy.wire_data.body, &dummy_wire_data_fido, + dummy.wire_data.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static void +make_cred(fido_cred_t *cred, uint8_t opt, int type, const struct blob *cdh, + const char *rp_id, const char *rp_name, const struct blob *user_id, + const char *user_name, const char *user_nick, const char *user_icon, + int ext, uint8_t rk, uint8_t uv, const char *pin, uint8_t excl_count, + const struct blob *excl_cred) +{ + fido_dev_t *dev; + + if ((dev = open_dev(opt & 2)) == NULL) + return; + if (opt & 1) + fido_dev_force_u2f(dev); + + for (uint8_t i = 0; i < excl_count; i++) + fido_cred_exclude(cred, excl_cred->body, excl_cred->len); + + fido_cred_set_type(cred, type); + fido_cred_set_clientdata_hash(cred, cdh->body, cdh->len); + fido_cred_set_rp(cred, rp_id, rp_name); + fido_cred_set_user(cred, user_id->body, user_id->len, user_name, + user_nick, user_icon); + + if (ext & FIDO_EXT_HMAC_SECRET) + fido_cred_set_extensions(cred, FIDO_EXT_HMAC_SECRET); + if (ext & FIDO_EXT_CRED_BLOB) + fido_cred_set_blob(cred, user_id->body, user_id->len); + if (ext & FIDO_EXT_LARGEBLOB_KEY) + fido_cred_set_extensions(cred, FIDO_EXT_LARGEBLOB_KEY); + if (ext & FIDO_EXT_MINPINLEN) + fido_cred_set_pin_minlen(cred, strlen(pin)); + + if (rk & 1) + fido_cred_set_rk(cred, FIDO_OPT_TRUE); + if (uv & 1) + fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (user_id->len) + fido_cred_set_prot(cred, user_id->body[0] & 0x03); + + /* repeat memory operations to trigger reallocation paths */ + fido_cred_set_type(cred, type); + fido_cred_set_clientdata_hash(cred, cdh->body, cdh->len); + fido_cred_set_rp(cred, rp_id, rp_name); + fido_cred_set_user(cred, user_id->body, user_id->len, user_name, + user_nick, user_icon); + + if (strlen(pin) == 0) + pin = NULL; + + fido_dev_make_cred(dev, cred, (opt & 1) ? NULL : pin); + + fido_dev_cancel(dev); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +verify_cred(int type, const unsigned char *cdh_ptr, size_t cdh_len, + const char *rp_id, const char *rp_name, const unsigned char *authdata_ptr, + size_t authdata_len, const unsigned char *authdata_raw_ptr, + size_t authdata_raw_len, int ext, uint8_t rk, uint8_t uv, + const unsigned char *x5c_ptr, size_t x5c_len, const unsigned char *sig_ptr, + size_t sig_len, const unsigned char *attstmt_ptr, size_t attstmt_len, + const char *fmt, int prot, size_t minpinlen) +{ + fido_cred_t *cred; + uint8_t flags; + uint32_t sigcount; + int r; + + if ((cred = fido_cred_new()) == NULL) + return; + + fido_cred_set_type(cred, type); + fido_cred_set_clientdata_hash(cred, cdh_ptr, cdh_len); + fido_cred_set_rp(cred, rp_id, rp_name); + consume(authdata_ptr, authdata_len); + consume(authdata_raw_ptr, authdata_raw_len); + consume(x5c_ptr, x5c_len); + consume(sig_ptr, sig_len); + consume(attstmt_ptr, attstmt_len); + if (fido_cred_set_authdata(cred, authdata_ptr, authdata_len) != FIDO_OK) + fido_cred_set_authdata_raw(cred, authdata_raw_ptr, + authdata_raw_len); + fido_cred_set_extensions(cred, ext); + if (fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len) != FIDO_OK) { + fido_cred_set_x509(cred, x5c_ptr, x5c_len); + fido_cred_set_sig(cred, sig_ptr, sig_len); + } + fido_cred_set_prot(cred, prot); + fido_cred_set_pin_minlen(cred, minpinlen); + + if (rk & 1) + fido_cred_set_rk(cred, FIDO_OPT_TRUE); + if (uv & 1) + fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (fmt) + fido_cred_set_fmt(cred, fmt); + + /* repeat memory operations to trigger reallocation paths */ + if (fido_cred_set_authdata(cred, authdata_ptr, authdata_len) != FIDO_OK) + fido_cred_set_authdata_raw(cred, authdata_raw_ptr, + authdata_raw_len); + if (fido_cred_set_attstmt(cred, attstmt_ptr, attstmt_len) != FIDO_OK) { + fido_cred_set_x509(cred, x5c_ptr, x5c_len); + fido_cred_set_sig(cred, sig_ptr, sig_len); + } + fido_cred_set_x509(cred, x5c_ptr, x5c_len); + fido_cred_set_sig(cred, sig_ptr, sig_len); + + r = fido_cred_verify(cred); + consume(&r, sizeof(r)); + r = fido_cred_verify_self(cred); + consume(&r, sizeof(r)); + + consume(fido_cred_pubkey_ptr(cred), fido_cred_pubkey_len(cred)); + consume(fido_cred_id_ptr(cred), fido_cred_id_len(cred)); + consume(fido_cred_aaguid_ptr(cred), fido_cred_aaguid_len(cred)); + consume(fido_cred_user_id_ptr(cred), fido_cred_user_id_len(cred)); + consume_str(fido_cred_user_name(cred)); + consume_str(fido_cred_display_name(cred)); + consume(fido_cred_largeblob_key_ptr(cred), + fido_cred_largeblob_key_len(cred)); + + flags = fido_cred_flags(cred); + consume(&flags, sizeof(flags)); + sigcount = fido_cred_sigcount(cred); + consume(&sigcount, sizeof(sigcount)); + type = fido_cred_type(cred); + consume(&type, sizeof(type)); + minpinlen = fido_cred_pin_minlen(cred); + consume(&minpinlen, sizeof(minpinlen)); + + fido_cred_free(&cred); +} + +static void +test_cred(const struct param *p) +{ + fido_cred_t *cred = NULL; + int cose_alg = 0; + + if ((cred = fido_cred_new()) == NULL) + return; + + switch (p->type & 3) { + case 0: + cose_alg = COSE_ES256; + break; + case 1: + cose_alg = COSE_RS256; + break; + case 2: + cose_alg = COSE_ES384; + break; + default: + cose_alg = COSE_EDDSA; + break; + } + + set_wire_data(p->wire_data.body, p->wire_data.len); + + make_cred(cred, p->opt, cose_alg, &p->cdh, p->rp_id, p->rp_name, + &p->user_id, p->user_name, p->user_nick, p->user_icon, p->ext, + p->rk, p->uv, p->pin, p->excl_count, &p->excl_cred); + + verify_cred(cose_alg, + fido_cred_clientdata_hash_ptr(cred), + fido_cred_clientdata_hash_len(cred), fido_cred_rp_id(cred), + fido_cred_rp_name(cred), fido_cred_authdata_ptr(cred), + fido_cred_authdata_len(cred), fido_cred_authdata_raw_ptr(cred), + fido_cred_authdata_raw_len(cred), p->ext, p->rk, p->uv, + fido_cred_x5c_ptr(cred), fido_cred_x5c_len(cred), + fido_cred_sig_ptr(cred), fido_cred_sig_len(cred), + fido_cred_attstmt_ptr(cred), fido_cred_attstmt_len(cred), + fido_cred_fmt(cred), fido_cred_prot(cred), + fido_cred_pin_minlen(cred)); + + fido_cred_free(&cred); +} + +static void +test_touch(const struct param *p) +{ + fido_dev_t *dev; + int r; + int touched; + + set_wire_data(p->wire_data.body, p->wire_data.len); + + if ((dev = open_dev(p->opt & 2)) == NULL) + return; + if (p->opt & 1) + fido_dev_force_u2f(dev); + + r = fido_dev_get_touch_begin(dev); + consume_str(fido_strerr(r)); + r = fido_dev_get_touch_status(dev, &touched, -1); + consume_str(fido_strerr(r)); + consume(&touched, sizeof(touched)); + + fido_dev_cancel(dev); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +test_misc(const struct param *p) +{ + fido_cred_t *cred = NULL; + + if ((cred = fido_cred_new()) == NULL) + return; + + /* reuse user id as credential id */ + fido_cred_set_id(cred, p->user_id.body, p->user_id.len); + consume(fido_cred_id_ptr(cred), fido_cred_id_len(cred)); + fido_cred_free(&cred); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + test_cred(p); + test_touch(p); + test_misc(p); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_byte(&p->rk); + mutate_byte(&p->type); + mutate_byte(&p->opt); + mutate_byte(&p->uv); + mutate_byte(&p->excl_count); + mutate_int(&p->ext); + mutate_blob(&p->cdh); + mutate_blob(&p->user_id); + mutate_blob(&p->excl_cred); + mutate_string(p->pin); + mutate_string(p->user_icon); + mutate_string(p->user_name); + mutate_string(p->user_nick); + mutate_string(p->rp_id); + mutate_string(p->rp_name); + } + + if (flags & MUTATE_WIREDATA) { + if (p->opt & 1) { + p->wire_data.len = sizeof(dummy_wire_data_u2f); + memcpy(&p->wire_data.body, &dummy_wire_data_u2f, + p->wire_data.len); + } else { + p->wire_data.len = sizeof(dummy_wire_data_fido); + memcpy(&p->wire_data.body, &dummy_wire_data_fido, + p->wire_data.len); + } + mutate_blob(&p->wire_data); + } +} diff --git a/fuzz/fuzz_credman.c b/fuzz/fuzz_credman.c new file mode 100644 index 0000000..ef21475 --- /dev/null +++ b/fuzz/fuzz_credman.c @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2019-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "dummy.h" + +#include "../openbsd-compat/openbsd-compat.h" + +/* Parameter set defining a FIDO2 credential management operation. */ +struct param { + char pin[MAXSTR]; + char rp_id[MAXSTR]; + int seed; + struct blob cred_id; + struct blob del_wire_data; + struct blob meta_wire_data; + struct blob rk_wire_data; + struct blob rp_wire_data; +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'getCredsMetadata' credential management command. + */ +static const uint8_t dummy_meta_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_CREDMAN_META, +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'enumerateRPsBegin' credential management command. + */ +static const uint8_t dummy_rp_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_CREDMAN_RPLIST, +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'enumerateCredentialsBegin' credential management command. + */ +static const uint8_t dummy_rk_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_CREDMAN_RKLIST, +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'deleteCredential' credential management command. + */ +static const uint8_t dummy_del_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_STATUS, +}; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 8 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_int(v[0], &p->seed) < 0 || + unpack_string(v[1], p->pin) < 0 || + unpack_string(v[2], p->rp_id) < 0 || + unpack_blob(v[3], &p->cred_id) < 0 || + unpack_blob(v[4], &p->meta_wire_data) < 0 || + unpack_blob(v[5], &p->rp_wire_data) < 0 || + unpack_blob(v[6], &p->rk_wire_data) < 0 || + unpack_blob(v[7], &p->del_wire_data) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[8], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(8)) == NULL || + (argv[0] = pack_int(p->seed)) == NULL || + (argv[1] = pack_string(p->pin)) == NULL || + (argv[2] = pack_string(p->rp_id)) == NULL || + (argv[3] = pack_blob(&p->cred_id)) == NULL || + (argv[4] = pack_blob(&p->meta_wire_data)) == NULL || + (argv[5] = pack_blob(&p->rp_wire_data)) == NULL || + (argv[6] = pack_blob(&p->rk_wire_data)) == NULL || + (argv[7] = pack_blob(&p->del_wire_data)) == NULL) + goto fail; + + for (size_t i = 0; i < 8; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 8; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin)); + strlcpy(dummy.rp_id, dummy_rp_id, sizeof(dummy.rp_id)); + + dummy.meta_wire_data.len = sizeof(dummy_meta_wire_data); + dummy.rp_wire_data.len = sizeof(dummy_rp_wire_data); + dummy.rk_wire_data.len = sizeof(dummy_rk_wire_data); + dummy.del_wire_data.len = sizeof(dummy_del_wire_data); + dummy.cred_id.len = sizeof(dummy_cred_id); + + memcpy(&dummy.meta_wire_data.body, &dummy_meta_wire_data, + dummy.meta_wire_data.len); + memcpy(&dummy.rp_wire_data.body, &dummy_rp_wire_data, + dummy.rp_wire_data.len); + memcpy(&dummy.rk_wire_data.body, &dummy_rk_wire_data, + dummy.rk_wire_data.len); + memcpy(&dummy.del_wire_data.body, &dummy_del_wire_data, + dummy.del_wire_data.len); + memcpy(&dummy.cred_id.body, &dummy_cred_id, dummy.cred_id.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static fido_dev_t * +prepare_dev(void) +{ + fido_dev_t *dev; + bool x; + + if ((dev = open_dev(0)) == NULL) + return NULL; + + x = fido_dev_is_fido2(dev); + consume(&x, sizeof(x)); + x = fido_dev_supports_cred_prot(dev); + consume(&x, sizeof(x)); + x = fido_dev_supports_credman(dev); + consume(&x, sizeof(x)); + + return dev; +} + +static void +get_metadata(const struct param *p) +{ + fido_dev_t *dev; + fido_credman_metadata_t *metadata; + uint64_t existing; + uint64_t remaining; + + set_wire_data(p->meta_wire_data.body, p->meta_wire_data.len); + + if ((dev = prepare_dev()) == NULL) + return; + + if ((metadata = fido_credman_metadata_new()) == NULL) { + fido_dev_close(dev); + fido_dev_free(&dev); + return; + } + + fido_credman_get_dev_metadata(dev, metadata, p->pin); + + existing = fido_credman_rk_existing(metadata); + remaining = fido_credman_rk_remaining(metadata); + consume(&existing, sizeof(existing)); + consume(&remaining, sizeof(remaining)); + + fido_credman_metadata_free(&metadata); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +get_rp_list(const struct param *p) +{ + fido_dev_t *dev; + fido_credman_rp_t *rp; + + set_wire_data(p->rp_wire_data.body, p->rp_wire_data.len); + + if ((dev = prepare_dev()) == NULL) + return; + + if ((rp = fido_credman_rp_new()) == NULL) { + fido_dev_close(dev); + fido_dev_free(&dev); + return; + } + + fido_credman_get_dev_rp(dev, rp, p->pin); + + /* +1 on purpose */ + for (size_t i = 0; i < fido_credman_rp_count(rp) + 1; i++) { + consume(fido_credman_rp_id_hash_ptr(rp, i), + fido_credman_rp_id_hash_len(rp, i)); + consume_str(fido_credman_rp_id(rp, i)); + consume_str(fido_credman_rp_name(rp, i)); + } + + fido_credman_rp_free(&rp); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +get_rk_list(const struct param *p) +{ + fido_dev_t *dev; + fido_credman_rk_t *rk; + const fido_cred_t *cred; + int val; + + set_wire_data(p->rk_wire_data.body, p->rk_wire_data.len); + + if ((dev = prepare_dev()) == NULL) + return; + + if ((rk = fido_credman_rk_new()) == NULL) { + fido_dev_close(dev); + fido_dev_free(&dev); + return; + } + + fido_credman_get_dev_rk(dev, p->rp_id, rk, p->pin); + + /* +1 on purpose */ + for (size_t i = 0; i < fido_credman_rk_count(rk) + 1; i++) { + if ((cred = fido_credman_rk(rk, i)) == NULL) { + assert(i >= fido_credman_rk_count(rk)); + continue; + } + val = fido_cred_type(cred); + consume(&val, sizeof(val)); + consume(fido_cred_id_ptr(cred), fido_cred_id_len(cred)); + consume(fido_cred_pubkey_ptr(cred), fido_cred_pubkey_len(cred)); + consume(fido_cred_user_id_ptr(cred), + fido_cred_user_id_len(cred)); + consume_str(fido_cred_user_name(cred)); + consume_str(fido_cred_display_name(cred)); + val = fido_cred_prot(cred); + consume(&val, sizeof(val)); + } + + fido_credman_rk_free(&rk); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +del_rk(const struct param *p) +{ + fido_dev_t *dev; + + set_wire_data(p->del_wire_data.body, p->del_wire_data.len); + + if ((dev = prepare_dev()) == NULL) + return; + + fido_credman_del_dev_rk(dev, p->cred_id.body, p->cred_id.len, p->pin); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +set_rk(const struct param *p) +{ + fido_dev_t *dev = NULL; + fido_cred_t *cred = NULL; + const char *pin = p->pin; + int r0, r1, r2; + + set_wire_data(p->del_wire_data.body, p->del_wire_data.len); + + if ((dev = prepare_dev()) == NULL) + return; + if ((cred = fido_cred_new()) == NULL) + goto out; + r0 = fido_cred_set_id(cred, p->cred_id.body, p->cred_id.len); + r1 = fido_cred_set_user(cred, p->cred_id.body, p->cred_id.len, p->rp_id, + NULL, NULL); + if (strlen(pin) == 0) + pin = NULL; + r2 = fido_credman_set_dev_rk(dev, cred, pin); + consume(&r0, sizeof(r0)); + consume(&r1, sizeof(r1)); + consume(&r2, sizeof(r2)); +out: + fido_dev_close(dev); + fido_dev_free(&dev); + fido_cred_free(&cred); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + get_metadata(p); + get_rp_list(p); + get_rk_list(p); + del_rk(p); + set_rk(p); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_blob(&p->cred_id); + mutate_string(p->pin); + mutate_string(p->rp_id); + } + + if (flags & MUTATE_WIREDATA) { + mutate_blob(&p->meta_wire_data); + mutate_blob(&p->rp_wire_data); + mutate_blob(&p->rk_wire_data); + mutate_blob(&p->del_wire_data); + } +} diff --git a/fuzz/fuzz_hid.c b/fuzz/fuzz_hid.c new file mode 100644 index 0000000..daaadad --- /dev/null +++ b/fuzz/fuzz_hid.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2020-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "mutator_aux.h" +#include "dummy.h" + +extern int fido_hid_get_usage(const uint8_t *, size_t, uint32_t *); +extern int fido_hid_get_report_len(const uint8_t *, size_t, size_t *, size_t *); +extern void set_udev_parameters(const char *, const struct blob *); + +struct param { + int seed; + char uevent[MAXSTR]; + struct blob report_descriptor; + struct blob netlink_wiredata; +}; + +/* + * Sample HID report descriptor from the FIDO HID interface of a YubiKey 5. + */ +static const uint8_t dummy_report_descriptor[] = { + 0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09, + 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, + 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, + 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 0x91, + 0x02, 0xc0 +}; + +/* + * Sample uevent file from a Yubico Security Key. + */ +static const char dummy_uevent[] = + "DRIVER=hid-generic\n" + "HID_ID=0003:00001050:00000120\n" + "HID_NAME=Yubico Security Key by Yubico\n" + "HID_PHYS=usb-0000:00:14.0-3/input0\n" + "HID_UNIQ=\n" + "MODALIAS=hid:b0003g0001v00001050p00000120\n"; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 4 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_int(v[0], &p->seed) < 0 || + unpack_string(v[1], p->uevent) < 0 || + unpack_blob(v[2], &p->report_descriptor) < 0 || + unpack_blob(v[3], &p->netlink_wiredata) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[4], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(4)) == NULL || + (argv[0] = pack_int(p->seed)) == NULL || + (argv[1] = pack_string(p->uevent)) == NULL || + (argv[2] = pack_blob(&p->report_descriptor)) == NULL || + (argv[3] = pack_blob(&p->netlink_wiredata)) == NULL) + goto fail; + + for (size_t i = 0; i < 4; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 4; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + dummy.report_descriptor.len = sizeof(dummy_report_descriptor); + strlcpy(dummy.uevent, dummy_uevent, sizeof(dummy.uevent)); + memcpy(&dummy.report_descriptor.body, &dummy_report_descriptor, + dummy.report_descriptor.len); + dummy.netlink_wiredata.len = sizeof(dummy_netlink_wiredata); + memcpy(&dummy.netlink_wiredata.body, &dummy_netlink_wiredata, + dummy.netlink_wiredata.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + if (blob_len > len) + blob_len = len; + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static void +get_usage(const struct param *p) +{ + uint32_t usage_page = 0; + + fido_hid_get_usage(p->report_descriptor.body, p->report_descriptor.len, + &usage_page); + consume(&usage_page, sizeof(usage_page)); +} + +static void +get_report_len(const struct param *p) +{ + size_t report_in_len = 0; + size_t report_out_len = 0; + + fido_hid_get_report_len(p->report_descriptor.body, + p->report_descriptor.len, &report_in_len, &report_out_len); + consume(&report_in_len, sizeof(report_in_len)); + consume(&report_out_len, sizeof(report_out_len)); +} + +static void +manifest(const struct param *p) +{ + size_t ndevs, nfound; + fido_dev_info_t *devlist = NULL, *devlist_set = NULL; + int16_t vendor_id, product_id; + fido_dev_io_t io; + fido_dev_transport_t t; + + memset(&io, 0, sizeof(io)); + memset(&t, 0, sizeof(t)); + set_netlink_io_functions(fd_read, fd_write); + set_wire_data(p->netlink_wiredata.body, p->netlink_wiredata.len); + set_udev_parameters(p->uevent, &p->report_descriptor); + + ndevs = uniform_random(64); + if ((devlist = fido_dev_info_new(ndevs)) == NULL || + (devlist_set = fido_dev_info_new(1)) == NULL || + fido_dev_info_manifest(devlist, ndevs, &nfound) != FIDO_OK) + goto out; + for (size_t i = 0; i < nfound; i++) { + const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); + consume_str(fido_dev_info_path(di)); + consume_str(fido_dev_info_manufacturer_string(di)); + consume_str(fido_dev_info_product_string(di)); + vendor_id = fido_dev_info_vendor(di); + product_id = fido_dev_info_product(di); + consume(&vendor_id, sizeof(vendor_id)); + consume(&product_id, sizeof(product_id)); + fido_dev_info_set(devlist_set, 0, fido_dev_info_path(di), + fido_dev_info_manufacturer_string(di), + fido_dev_info_product_string(di), &io, &t); + } +out: + fido_dev_info_free(&devlist, ndevs); + fido_dev_info_free(&devlist_set, 1); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + get_usage(p); + get_report_len(p); + manifest(p); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_blob(&p->report_descriptor); + mutate_string(p->uevent); + } + + if (flags & MUTATE_WIREDATA) + mutate_blob(&p->netlink_wiredata); +} diff --git a/fuzz/fuzz_largeblob.c b/fuzz/fuzz_largeblob.c new file mode 100644 index 0000000..6cdc0c0 --- /dev/null +++ b/fuzz/fuzz_largeblob.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "dummy.h" + +#include "../openbsd-compat/openbsd-compat.h" + +/* Parameter set defining a FIDO2 "large blob" operation. */ +struct param { + char pin[MAXSTR]; + int seed; + struct blob key; + struct blob get_wiredata; + struct blob set_wiredata; +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'authenticatorLargeBlobs' 'get' command. + */ +static const uint8_t dummy_get_wiredata[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY +}; + +/* + * Collection of HID reports from an authenticator issued with a FIDO2 + * 'authenticatorLargeBlobs' 'set' command. + */ +static const uint8_t dummy_set_wiredata[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_PINTOKEN, + WIREDATA_CTAP_CBOR_STATUS +}; + +/* + * XXX this needs to match the encrypted blob embedded in + * WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY. + */ +static const uint8_t dummy_key[] = { + 0xa9, 0x1b, 0xc4, 0xdd, 0xfc, 0x9a, 0x93, 0x79, + 0x75, 0xba, 0xf7, 0x7f, 0x4d, 0x57, 0xfc, 0xa6, + 0xe1, 0xf8, 0x06, 0x43, 0x23, 0x99, 0x51, 0x32, + 0xce, 0x6e, 0x19, 0x84, 0x50, 0x13, 0x2d, 0x7b +}; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 5 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_int(v[0], &p->seed) < 0 || + unpack_string(v[1], p->pin) < 0 || + unpack_blob(v[2], &p->key) < 0 || + unpack_blob(v[3], &p->get_wiredata) < 0 || + unpack_blob(v[4], &p->set_wiredata) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[5], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(5)) == NULL || + (argv[0] = pack_int(p->seed)) == NULL || + (argv[1] = pack_string(p->pin)) == NULL || + (argv[2] = pack_blob(&p->key)) == NULL || + (argv[3] = pack_blob(&p->get_wiredata)) == NULL || + (argv[4] = pack_blob(&p->set_wiredata)) == NULL) + goto fail; + + for (size_t i = 0; i < 5; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 5; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + strlcpy(dummy.pin, dummy_pin, sizeof(dummy.pin)); + + dummy.get_wiredata.len = sizeof(dummy_get_wiredata); + dummy.set_wiredata.len = sizeof(dummy_set_wiredata); + dummy.key.len = sizeof(dummy_key); + + memcpy(&dummy.get_wiredata.body, &dummy_get_wiredata, + dummy.get_wiredata.len); + memcpy(&dummy.set_wiredata.body, &dummy_set_wiredata, + dummy.set_wiredata.len); + memcpy(&dummy.key.body, &dummy_key, dummy.key.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static fido_dev_t * +prepare_dev(void) +{ + fido_dev_t *dev; + + if ((dev = open_dev(0)) == NULL) + return NULL; + + return dev; +} + +static void +get_blob(const struct param *p, int array) +{ + fido_dev_t *dev; + u_char *ptr = NULL; + size_t len = 0; + + set_wire_data(p->get_wiredata.body, p->get_wiredata.len); + + if ((dev = prepare_dev()) == NULL) + return; + + if (array) + fido_dev_largeblob_get_array(dev, &ptr, &len); + else + fido_dev_largeblob_get(dev, p->key.body, p->key.len, &ptr, &len); + consume(ptr, len); + free(ptr); + + fido_dev_close(dev); + fido_dev_free(&dev); +} + + +static void +set_blob(const struct param *p, int op) +{ + fido_dev_t *dev; + const char *pin; + + set_wire_data(p->set_wiredata.body, p->set_wiredata.len); + + if ((dev = prepare_dev()) == NULL) + return; + pin = p->pin; + if (strlen(pin) == 0) + pin = NULL; + + switch (op) { + case 0: + fido_dev_largeblob_remove(dev, p->key.body, p->key.len, pin); + break; + case 1: + /* XXX reuse p->get_wiredata as the blob to be set */ + fido_dev_largeblob_set(dev, p->key.body, p->key.len, + p->get_wiredata.body, p->get_wiredata.len, pin); + break; + case 2: + /* XXX reuse p->get_wiredata as the body of the cbor array */ + fido_dev_largeblob_set_array(dev, p->get_wiredata.body, + p->get_wiredata.len, pin); + } + + fido_dev_close(dev); + fido_dev_free(&dev); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + get_blob(p, 0); + get_blob(p, 1); + set_blob(p, 0); + set_blob(p, 1); + set_blob(p, 2); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_blob(&p->key); + mutate_string(p->pin); + } + + if (flags & MUTATE_WIREDATA) { + mutate_blob(&p->get_wiredata); + mutate_blob(&p->set_wiredata); + } +} diff --git a/fuzz/fuzz_mgmt.c b/fuzz/fuzz_mgmt.c new file mode 100644 index 0000000..cbc313d --- /dev/null +++ b/fuzz/fuzz_mgmt.c @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "dummy.h" + +#include "../openbsd-compat/openbsd-compat.h" + +#define MAXRPID 64 + +struct param { + char pin1[MAXSTR]; + char pin2[MAXSTR]; + struct blob reset_wire_data; + struct blob info_wire_data; + struct blob set_pin_wire_data; + struct blob change_pin_wire_data; + struct blob retry_wire_data; + struct blob config_wire_data; + int seed; +}; + +static const uint8_t dummy_reset_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_CBOR_STATUS, +}; + +static const uint8_t dummy_info_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_INFO, +}; + +static const uint8_t dummy_set_pin_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_STATUS, +}; + +static const uint8_t dummy_change_pin_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_STATUS, +}; + +static const uint8_t dummy_retry_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_RETRIES, +}; + +static const uint8_t dummy_config_wire_data[] = { + WIREDATA_CTAP_INIT, + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_STATUS, +}; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 9 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_int(v[0], &p->seed) < 0 || + unpack_string(v[1], p->pin1) < 0 || + unpack_string(v[2], p->pin2) < 0 || + unpack_blob(v[3], &p->reset_wire_data) < 0 || + unpack_blob(v[4], &p->info_wire_data) < 0 || + unpack_blob(v[5], &p->set_pin_wire_data) < 0 || + unpack_blob(v[6], &p->change_pin_wire_data) < 0 || + unpack_blob(v[7], &p->retry_wire_data) < 0 || + unpack_blob(v[8], &p->config_wire_data) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[9], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(9)) == NULL || + (argv[0] = pack_int(p->seed)) == NULL || + (argv[1] = pack_string(p->pin1)) == NULL || + (argv[2] = pack_string(p->pin2)) == NULL || + (argv[3] = pack_blob(&p->reset_wire_data)) == NULL || + (argv[4] = pack_blob(&p->info_wire_data)) == NULL || + (argv[5] = pack_blob(&p->set_pin_wire_data)) == NULL || + (argv[6] = pack_blob(&p->change_pin_wire_data)) == NULL || + (argv[7] = pack_blob(&p->retry_wire_data)) == NULL || + (argv[8] = pack_blob(&p->config_wire_data)) == NULL) + goto fail; + + for (size_t i = 0; i < 9; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 9; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + strlcpy(dummy.pin1, dummy_pin1, sizeof(dummy.pin1)); + strlcpy(dummy.pin2, dummy_pin2, sizeof(dummy.pin2)); + + dummy.reset_wire_data.len = sizeof(dummy_reset_wire_data); + dummy.info_wire_data.len = sizeof(dummy_info_wire_data); + dummy.set_pin_wire_data.len = sizeof(dummy_set_pin_wire_data); + dummy.change_pin_wire_data.len = sizeof(dummy_change_pin_wire_data); + dummy.retry_wire_data.len = sizeof(dummy_retry_wire_data); + dummy.config_wire_data.len = sizeof(dummy_config_wire_data); + + memcpy(&dummy.reset_wire_data.body, &dummy_reset_wire_data, + dummy.reset_wire_data.len); + memcpy(&dummy.info_wire_data.body, &dummy_info_wire_data, + dummy.info_wire_data.len); + memcpy(&dummy.set_pin_wire_data.body, &dummy_set_pin_wire_data, + dummy.set_pin_wire_data.len); + memcpy(&dummy.change_pin_wire_data.body, &dummy_change_pin_wire_data, + dummy.change_pin_wire_data.len); + memcpy(&dummy.retry_wire_data.body, &dummy_retry_wire_data, + dummy.retry_wire_data.len); + memcpy(&dummy.config_wire_data.body, &dummy_config_wire_data, + dummy.config_wire_data.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static void +dev_reset(const struct param *p) +{ + fido_dev_t *dev; + + set_wire_data(p->reset_wire_data.body, p->reset_wire_data.len); + + if ((dev = open_dev(0)) == NULL) + return; + + fido_dev_reset(dev); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_get_cbor_info(const struct param *p) +{ + fido_dev_t *dev; + fido_cbor_info_t *ci; + uint64_t n; + uint8_t proto, major, minor, build, flags; + bool v; + + set_wire_data(p->info_wire_data.body, p->info_wire_data.len); + + if ((dev = open_dev(0)) == NULL) + return; + + proto = fido_dev_protocol(dev); + major = fido_dev_major(dev); + minor = fido_dev_minor(dev); + build = fido_dev_build(dev); + flags = fido_dev_flags(dev); + + consume(&proto, sizeof(proto)); + consume(&major, sizeof(major)); + consume(&minor, sizeof(minor)); + consume(&build, sizeof(build)); + consume(&flags, sizeof(flags)); + + if ((ci = fido_cbor_info_new()) == NULL) + goto out; + + fido_dev_get_cbor_info(dev, ci); + + for (size_t i = 0; i < fido_cbor_info_versions_len(ci); i++) { + char * const *sa = fido_cbor_info_versions_ptr(ci); + consume(sa[i], strlen(sa[i])); + } + + for (size_t i = 0; i < fido_cbor_info_extensions_len(ci); i++) { + char * const *sa = fido_cbor_info_extensions_ptr(ci); + consume(sa[i], strlen(sa[i])); + } + + for (size_t i = 0; i < fido_cbor_info_transports_len(ci); i++) { + char * const *sa = fido_cbor_info_transports_ptr(ci); + consume(sa[i], strlen(sa[i])); + } + + for (size_t i = 0; i < fido_cbor_info_options_len(ci); i++) { + char * const *sa = fido_cbor_info_options_name_ptr(ci); + const bool *va = fido_cbor_info_options_value_ptr(ci); + consume(sa[i], strlen(sa[i])); + consume(&va[i], sizeof(va[i])); + } + + /* +1 on purpose */ + for (size_t i = 0; i <= fido_cbor_info_algorithm_count(ci); i++) { + const char *type = fido_cbor_info_algorithm_type(ci, i); + int cose = fido_cbor_info_algorithm_cose(ci, i); + consume_str(type); + consume(&cose, sizeof(cose)); + } + + for (size_t i = 0; i < fido_cbor_info_certs_len(ci); i++) { + char * const *na = fido_cbor_info_certs_name_ptr(ci); + const uint64_t *va = fido_cbor_info_certs_value_ptr(ci); + consume(na[i], strlen(na[i])); + consume(&va[i], sizeof(va[i])); + } + + n = fido_cbor_info_maxmsgsiz(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_maxcredbloblen(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_maxcredcntlst(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_maxcredidlen(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_maxlargeblob(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_fwversion(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_minpinlen(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_maxrpid_minpinlen(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_uv_attempts(ci); + consume(&n, sizeof(n)); + n = fido_cbor_info_uv_modality(ci); + consume(&n, sizeof(n)); + n = (uint64_t)fido_cbor_info_rk_remaining(ci); + consume(&n, sizeof(n)); + + consume(fido_cbor_info_aaguid_ptr(ci), fido_cbor_info_aaguid_len(ci)); + consume(fido_cbor_info_protocols_ptr(ci), + fido_cbor_info_protocols_len(ci)); + + v = fido_cbor_info_new_pin_required(ci); + consume(&v, sizeof(v)); + +out: + fido_dev_close(dev); + fido_dev_free(&dev); + + fido_cbor_info_free(&ci); +} + +static void +dev_set_pin(const struct param *p) +{ + fido_dev_t *dev; + + set_wire_data(p->set_pin_wire_data.body, p->set_pin_wire_data.len); + + if ((dev = open_dev(0)) == NULL) + return; + + fido_dev_set_pin(dev, p->pin1, NULL); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_change_pin(const struct param *p) +{ + fido_dev_t *dev; + + set_wire_data(p->change_pin_wire_data.body, p->change_pin_wire_data.len); + + if ((dev = open_dev(0)) == NULL) + return; + + fido_dev_set_pin(dev, p->pin2, p->pin1); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_get_retry_count(const struct param *p) +{ + fido_dev_t *dev; + int n = 0; + + set_wire_data(p->retry_wire_data.body, p->retry_wire_data.len); + + if ((dev = open_dev(0)) == NULL) + return; + + fido_dev_get_retry_count(dev, &n); + consume(&n, sizeof(n)); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_get_uv_retry_count(const struct param *p) +{ + fido_dev_t *dev; + int n = 0; + + set_wire_data(p->retry_wire_data.body, p->retry_wire_data.len); + + if ((dev = open_dev(0)) == NULL) + return; + + fido_dev_get_uv_retry_count(dev, &n); + consume(&n, sizeof(n)); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_enable_entattest(const struct param *p) +{ + fido_dev_t *dev; + const char *pin; + int r; + + set_wire_data(p->config_wire_data.body, p->config_wire_data.len); + if ((dev = open_dev(0)) == NULL) + return; + pin = p->pin1; + if (strlen(pin) == 0) + pin = NULL; + r = fido_dev_enable_entattest(dev, pin); + consume_str(fido_strerr(r)); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_toggle_always_uv(const struct param *p) +{ + fido_dev_t *dev; + const char *pin; + int r; + + set_wire_data(p->config_wire_data.body, p->config_wire_data.len); + if ((dev = open_dev(0)) == NULL) + return; + pin = p->pin1; + if (strlen(pin) == 0) + pin = NULL; + r = fido_dev_toggle_always_uv(dev, pin); + consume_str(fido_strerr(r)); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_force_pin_change(const struct param *p) +{ + fido_dev_t *dev; + const char *pin; + int r; + + set_wire_data(p->config_wire_data.body, p->config_wire_data.len); + if ((dev = open_dev(0)) == NULL) + return; + pin = p->pin1; + if (strlen(pin) == 0) + pin = NULL; + r = fido_dev_force_pin_change(dev, pin); + consume_str(fido_strerr(r)); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_set_pin_minlen(const struct param *p) +{ + fido_dev_t *dev; + const char *pin; + int r; + + set_wire_data(p->config_wire_data.body, p->config_wire_data.len); + if ((dev = open_dev(0)) == NULL) + return; + pin = p->pin1; + if (strlen(pin) == 0) + pin = NULL; + r = fido_dev_set_pin_minlen(dev, strlen(p->pin2), pin); + consume_str(fido_strerr(r)); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +static void +dev_set_pin_minlen_rpid(const struct param *p) +{ + fido_dev_t *dev; + const char *rpid[MAXRPID]; + const char *pin; + size_t n; + int r; + + set_wire_data(p->config_wire_data.body, p->config_wire_data.len); + if ((dev = open_dev(0)) == NULL) + return; + n = uniform_random(MAXRPID); + for (size_t i = 0; i < n; i++) + rpid[i] = dummy_rp_id; + pin = p->pin1; + if (strlen(pin) == 0) + pin = NULL; + r = fido_dev_set_pin_minlen_rpid(dev, rpid, n, pin); + consume_str(fido_strerr(r)); + fido_dev_close(dev); + fido_dev_free(&dev); +} + +void +test(const struct param *p) +{ + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + dev_reset(p); + dev_get_cbor_info(p); + dev_set_pin(p); + dev_change_pin(p); + dev_get_retry_count(p); + dev_get_uv_retry_count(p); + dev_enable_entattest(p); + dev_toggle_always_uv(p); + dev_force_pin_change(p); + dev_set_pin_minlen(p); + dev_set_pin_minlen_rpid(p); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_string(p->pin1); + mutate_string(p->pin2); + } + + if (flags & MUTATE_WIREDATA) { + mutate_blob(&p->reset_wire_data); + mutate_blob(&p->info_wire_data); + mutate_blob(&p->set_pin_wire_data); + mutate_blob(&p->change_pin_wire_data); + mutate_blob(&p->retry_wire_data); + } +} diff --git a/fuzz/fuzz_netlink.c b/fuzz/fuzz_netlink.c new file mode 100644 index 0000000..4d28129 --- /dev/null +++ b/fuzz/fuzz_netlink.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "mutator_aux.h" +#include "dummy.h" + +struct param { + int seed; + int dev; + struct blob wiredata; +}; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 3 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_int(v[0], &p->seed) < 0 || + unpack_int(v[1], &p->dev) < 0 || + unpack_blob(v[2], &p->wiredata) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[3], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(3)) == NULL || + (argv[0] = pack_int(p->seed)) == NULL || + (argv[1] = pack_int(p->dev)) == NULL || + (argv[2] = pack_blob(&p->wiredata)) == NULL) + goto fail; + + for (size_t i = 0; i < 3; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 3; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + dummy.wiredata.len = sizeof(dummy_netlink_wiredata); + memcpy(&dummy.wiredata.body, &dummy_netlink_wiredata, + dummy.wiredata.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +void +test(const struct param *p) +{ + fido_nl_t *nl; + uint32_t target; + + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + set_netlink_io_functions(fd_read, fd_write); + set_wire_data(p->wiredata.body, p->wiredata.len); + + if ((nl = fido_nl_new()) == NULL) + return; + + consume(&nl->fd, sizeof(nl->fd)); + consume(&nl->nfc_type, sizeof(nl->nfc_type)); + consume(&nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)); + consume(&nl->saddr, sizeof(nl->saddr)); + + fido_nl_power_nfc(nl, (uint32_t)p->dev); + + if (fido_nl_get_nfc_target(nl, (uint32_t)p->dev, &target) == 0) + consume(&target, sizeof(target)); + + fido_nl_free(&nl); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) + mutate_int(&p->dev); + + if (flags & MUTATE_WIREDATA) + mutate_blob(&p->wiredata); +} diff --git a/fuzz/fuzz_pcsc.c b/fuzz/fuzz_pcsc.c new file mode 100644 index 0000000..cf6210b --- /dev/null +++ b/fuzz/fuzz_pcsc.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define _FIDO_INTERNAL + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <winscard.h> + +#include "mutator_aux.h" +#include "wiredata_fido2.h" +#include "dummy.h" + +#include "../src/extern.h" + +struct param { + int seed; + char path[MAXSTR]; + struct blob pcsc_list; + struct blob tx_apdu; + struct blob wiredata_init; + struct blob wiredata_msg; +}; + +static const uint8_t dummy_tx_apdu[] = { WIREDATA_CTAP_EXTENDED_APDU }; +static const uint8_t dummy_wiredata_init[] = { WIREDATA_CTAP_NFC_INIT }; +static const uint8_t dummy_wiredata_msg[] = { WIREDATA_CTAP_NFC_MSG }; + +struct param * +unpack(const uint8_t *ptr, size_t len) +{ + cbor_item_t *item = NULL, **v; + struct cbor_load_result cbor; + struct param *p; + int ok = -1; + + if ((p = calloc(1, sizeof(*p))) == NULL || + (item = cbor_load(ptr, len, &cbor)) == NULL || + cbor.read != len || + cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false || + cbor_array_size(item) != 6 || + (v = cbor_array_handle(item)) == NULL) + goto fail; + + if (unpack_int(v[0], &p->seed) < 0 || + unpack_string(v[1], p->path) < 0 || + unpack_blob(v[2], &p->pcsc_list) < 0 || + unpack_blob(v[3], &p->tx_apdu) < 0 || + unpack_blob(v[4], &p->wiredata_init) < 0 || + unpack_blob(v[5], &p->wiredata_msg) < 0) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + free(p); + p = NULL; + } + + if (item) + cbor_decref(&item); + + return p; +} + +size_t +pack(uint8_t *ptr, size_t len, const struct param *p) +{ + cbor_item_t *argv[6], *array = NULL; + size_t cbor_alloc_len, cbor_len = 0; + unsigned char *cbor = NULL; + + memset(argv, 0, sizeof(argv)); + + if ((array = cbor_new_definite_array(6)) == NULL || + (argv[0] = pack_int(p->seed)) == NULL || + (argv[1] = pack_string(p->path)) == NULL || + (argv[2] = pack_blob(&p->pcsc_list)) == NULL || + (argv[3] = pack_blob(&p->tx_apdu)) == NULL || + (argv[4] = pack_blob(&p->wiredata_init)) == NULL || + (argv[5] = pack_blob(&p->wiredata_msg)) == NULL) + goto fail; + + for (size_t i = 0; i < 6; i++) + if (cbor_array_push(array, argv[i]) == false) + goto fail; + + if ((cbor_len = cbor_serialize_alloc(array, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > len) { + cbor_len = 0; + goto fail; + } + + memcpy(ptr, cbor, cbor_len); +fail: + for (size_t i = 0; i < 6; i++) + if (argv[i]) + cbor_decref(&argv[i]); + + if (array) + cbor_decref(&array); + + free(cbor); + + return cbor_len; +} + +size_t +pack_dummy(uint8_t *ptr, size_t len) +{ + struct param dummy; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&dummy, 0, sizeof(dummy)); + + strlcpy(dummy.path, dummy_pcsc_path, sizeof(dummy.path)); + + dummy.pcsc_list.len = sizeof(dummy_pcsc_list); + memcpy(&dummy.pcsc_list.body, &dummy_pcsc_list, dummy.pcsc_list.len); + + dummy.tx_apdu.len = sizeof(dummy_tx_apdu); + memcpy(&dummy.tx_apdu.body, &dummy_tx_apdu, dummy.tx_apdu.len); + + dummy.wiredata_init.len = sizeof(dummy_wiredata_init); + memcpy(&dummy.wiredata_init.body, &dummy_wiredata_init, + dummy.wiredata_init.len); + + dummy.wiredata_msg.len = sizeof(dummy_wiredata_msg); + memcpy(&dummy.wiredata_msg.body, &dummy_wiredata_msg, + dummy.wiredata_msg.len); + + assert((blob_len = pack(blob, sizeof(blob), &dummy)) != 0); + + if (blob_len > len) { + memcpy(ptr, blob, len); + return len; + } + + memcpy(ptr, blob, blob_len); + + return blob_len; +} + +static void +test_manifest(void) +{ + size_t ndevs, nfound; + fido_dev_info_t *devlist = NULL; + int16_t vendor_id, product_id; + int r; + + r = fido_pcsc_manifest(NULL, 0, &nfound); + assert(r == FIDO_OK && nfound == 0); + r = fido_pcsc_manifest(NULL, 1, &nfound); + assert(r == FIDO_ERR_INVALID_ARGUMENT); + + ndevs = uniform_random(64); + if ((devlist = fido_dev_info_new(ndevs)) == NULL || + fido_pcsc_manifest(devlist, ndevs, &nfound) != FIDO_OK) + goto out; + + for (size_t i = 0; i < nfound; i++) { + const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); + consume_str(fido_dev_info_path(di)); + consume_str(fido_dev_info_manufacturer_string(di)); + consume_str(fido_dev_info_product_string(di)); + vendor_id = fido_dev_info_vendor(di); + product_id = fido_dev_info_product(di); + consume(&vendor_id, sizeof(vendor_id)); + consume(&product_id, sizeof(product_id)); + } + +out: + fido_dev_info_free(&devlist, ndevs); +} + +static void +test_tx(const char *path, const struct blob *apdu, uint8_t cmd, u_char *rx_buf, + size_t rx_len) +{ + fido_dev_t dev; + const u_char *tx_ptr = NULL; + size_t tx_len = 0; + int n; + + memset(&dev, 0, sizeof(dev)); + + if (fido_dev_set_pcsc(&dev) < 0) + return; + if ((dev.io_handle = fido_pcsc_open(path)) == NULL) + return; + + if (apdu) { + tx_ptr = apdu->body; + tx_len = apdu->len; + } + + fido_pcsc_tx(&dev, cmd, tx_ptr, tx_len); + + if ((n = fido_pcsc_rx(&dev, cmd, rx_buf, rx_len, -1)) >= 0) + consume(rx_buf, n); + + fido_pcsc_close(dev.io_handle); +} + +static void +test_misc(void) +{ + assert(fido_pcsc_open(NULL) == NULL); + assert(fido_pcsc_write(NULL, NULL, INT_MAX + 1LL) == -1); +} + +void +test(const struct param *p) +{ + u_char buf[512]; + + prng_init((unsigned int)p->seed); + fuzz_clock_reset(); + fido_init(FIDO_DEBUG); + fido_set_log_handler(consume_str); + + set_pcsc_parameters(&p->pcsc_list); + set_pcsc_io_functions(nfc_read, nfc_write, consume); + + set_wire_data(p->wiredata_init.body, p->wiredata_init.len); + test_manifest(); + + test_misc(); + + set_wire_data(p->wiredata_init.body, p->wiredata_init.len); + test_tx(p->path, NULL, CTAP_CMD_INIT, buf, uniform_random(20)); + + set_wire_data(p->wiredata_msg.body, p->wiredata_msg.len); + test_tx(p->path, &p->tx_apdu, CTAP_CMD_MSG, buf, sizeof(buf)); + + set_wire_data(p->wiredata_msg.body, p->wiredata_msg.len); + test_tx(p->path, &p->tx_apdu, CTAP_CMD_CBOR, buf, sizeof(buf)); + + set_wire_data(p->wiredata_msg.body, p->wiredata_msg.len); + test_tx(p->path, &p->tx_apdu, CTAP_CMD_LOCK, buf, sizeof(buf)); +} + +void +mutate(struct param *p, unsigned int seed, unsigned int flags) NO_MSAN +{ + if (flags & MUTATE_SEED) + p->seed = (int)seed; + + if (flags & MUTATE_PARAM) { + mutate_string(p->path); + mutate_blob(&p->pcsc_list); + mutate_blob(&p->tx_apdu); + } + + if (flags & MUTATE_WIREDATA) { + mutate_blob(&p->wiredata_init); + mutate_blob(&p->wiredata_msg); + } +} diff --git a/fuzz/libfuzzer.c b/fuzz/libfuzzer.c new file mode 100644 index 0000000..073ebe6 --- /dev/null +++ b/fuzz/libfuzzer.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> + +#include <err.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mutator_aux.h" + +extern int fuzz_save_corpus; + +static bool debug; +static unsigned int flags = MUTATE_ALL; +static unsigned long long test_fail; +static unsigned long long test_total; +static unsigned long long mutate_fail; +static unsigned long long mutate_total; + +int LLVMFuzzerInitialize(int *, char ***); +int LLVMFuzzerTestOneInput(const uint8_t *, size_t); +size_t LLVMFuzzerCustomMutator(uint8_t *, size_t, size_t, unsigned int); + +static int +save_seed(const char *opt) +{ + const char *path; + int fd = -1, status = 1; + void *buf = NULL; + const size_t buflen = MAXCORPUS; + size_t n; + struct param *p = NULL; + + if ((path = strchr(opt, '=')) == NULL || strlen(++path) == 0) { + warnx("usage: --fido-save-seed=<path>"); + goto fail; + } + + if ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644)) == -1) { + warn("open %s", path); + goto fail; + } + + if ((buf = malloc(buflen)) == NULL) { + warn("malloc"); + goto fail; + } + + n = pack_dummy(buf, buflen); + + if ((p = unpack(buf, n)) == NULL) { + warnx("unpack"); + goto fail; + } + + if (write(fd, buf, n) != (ssize_t)n) { + warn("write %s", path); + goto fail; + } + + status = 0; +fail: + if (fd != -1) + close(fd); + free(buf); + free(p); + + return status; +} + +static int +save_corpus(const struct param *p) +{ + uint8_t blob[MAXCORPUS], dgst[SHA256_DIGEST_LENGTH]; + size_t blob_len; + char path[PATH_MAX]; + int r, fd; + + if ((blob_len = pack(blob, sizeof(blob), p)) == 0 || + blob_len > sizeof(blob)) { + warnx("pack"); + return -1; + } + + if (SHA256(blob, blob_len, dgst) != dgst) { + warnx("sha256"); + return -1; + } + + if ((r = snprintf(path, sizeof(path), "saved_corpus_%02x%02x%02x%02x" + "%02x%02x%02x%02x", dgst[0], dgst[1], dgst[2], dgst[3], dgst[4], + dgst[5], dgst[6], dgst[7])) < 0 || (size_t)r >= sizeof(path)) { + warnx("snprintf"); + return -1; + } + + if ((fd = open(path, O_CREAT|O_TRUNC|O_WRONLY, 0644)) == -1) { + warn("open %s", path); + return -1; + } + + if (write(fd, blob, blob_len) != (ssize_t)blob_len) { + warn("write"); + r = -1; + } else { + warnx("wrote %s", path); + r = 0; + } + + close(fd); + + return r; +} + +static void +parse_mutate_flags(const char *opt, unsigned int *mutate_flags) +{ + const char *f; + + if ((f = strchr(opt, '=')) == NULL || strlen(++f) == 0) + errx(1, "usage: --fido-mutate=<flag>"); + + if (strcmp(f, "seed") == 0) + *mutate_flags |= MUTATE_SEED; + else if (strcmp(f, "param") == 0) + *mutate_flags |= MUTATE_PARAM; + else if (strcmp(f, "wiredata") == 0) + *mutate_flags |= MUTATE_WIREDATA; + else + errx(1, "--fido-mutate: unknown flag '%s'", f); +} + +int +LLVMFuzzerInitialize(int *argc, char ***argv) +{ + unsigned int mutate_flags = 0; + + for (int i = 0; i < *argc; i++) + if (strcmp((*argv)[i], "--fido-debug") == 0) { + debug = 1; + } else if (strncmp((*argv)[i], "--fido-save-seed=", 17) == 0) { + exit(save_seed((*argv)[i])); + } else if (strncmp((*argv)[i], "--fido-mutate=", 14) == 0) { + parse_mutate_flags((*argv)[i], &mutate_flags); + } + + if (mutate_flags) + flags = mutate_flags; + + return 0; +} + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + struct param *p; + + if (size > MAXCORPUS) + return 0; + + if (++test_total % 100000 == 0 && debug) { + double r = (double)test_fail/(double)test_total * 100.0; + fprintf(stderr, "%s: %llu/%llu (%.2f%%)\n", __func__, + test_fail, test_total, r); + } + + if ((p = unpack(data, size)) == NULL) + test_fail++; + else { + fuzz_save_corpus = 0; + test(p); + if (fuzz_save_corpus && save_corpus(p) < 0) + fprintf(stderr, "%s: failed to save corpus\n", + __func__); + free(p); + } + + return 0; +} + +size_t +LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t maxsize, + unsigned int seed) NO_MSAN +{ + struct param *p; + uint8_t blob[MAXCORPUS]; + size_t blob_len; + + memset(&p, 0, sizeof(p)); + +#ifdef WITH_MSAN + __msan_unpoison(data, maxsize); +#endif + + if (++mutate_total % 100000 == 0 && debug) { + double r = (double)mutate_fail/(double)mutate_total * 100.0; + fprintf(stderr, "%s: %llu/%llu (%.2f%%)\n", __func__, + mutate_fail, mutate_total, r); + } + + if ((p = unpack(data, size)) == NULL) { + mutate_fail++; + return pack_dummy(data, maxsize); + } + + mutate(p, seed, flags); + + if ((blob_len = pack(blob, sizeof(blob), p)) == 0 || + blob_len > sizeof(blob) || blob_len > maxsize) { + mutate_fail++; + free(p); + return 0; + } + + free(p); + + memcpy(data, blob, blob_len); + + return blob_len; +} diff --git a/fuzz/mutator_aux.c b/fuzz/mutator_aux.c new file mode 100644 index 0000000..64c633f --- /dev/null +++ b/fuzz/mutator_aux.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <cbor.h> +#include <errno.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "mutator_aux.h" + +int fido_nfc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int); +int fido_nfc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t); +size_t LLVMFuzzerMutate(uint8_t *, size_t, size_t); + +extern int prng_up; +static const uint8_t *wire_data_ptr = NULL; +static size_t wire_data_len = 0; + +void +consume(const void *body, size_t len) +{ + const volatile uint8_t *ptr = body; + volatile uint8_t x = 0; + +#ifdef WITH_MSAN + __msan_check_mem_is_initialized(body, len); +#endif + + while (len--) + x ^= *ptr++; + + (void)x; +} + +void +consume_str(const char *str) +{ + if (str != NULL) + consume(str, strlen(str) + 1); +} + +int +unpack_int(cbor_item_t *item, int *v) +{ + if (cbor_is_int(item) == false || + cbor_int_get_width(item) != CBOR_INT_64) + return -1; + + if (cbor_isa_uint(item)) + *v = (int)cbor_get_uint64(item); + else + *v = (int)(-cbor_get_uint64(item) - 1); + + return 0; +} + +int +unpack_string(cbor_item_t *item, char *v) +{ + size_t len; + + if (cbor_isa_bytestring(item) == false || + (len = cbor_bytestring_length(item)) >= MAXSTR) + return -1; + + memcpy(v, cbor_bytestring_handle(item), len); + v[len] = '\0'; + + return 0; +} + +int +unpack_byte(cbor_item_t *item, uint8_t *v) +{ + if (cbor_isa_uint(item) == false || + cbor_int_get_width(item) != CBOR_INT_8) + return -1; + + *v = cbor_get_uint8(item); + + return 0; +} + +int +unpack_blob(cbor_item_t *item, struct blob *v) +{ + if (cbor_isa_bytestring(item) == false || + (v->len = cbor_bytestring_length(item)) > sizeof(v->body)) + return -1; + + memcpy(v->body, cbor_bytestring_handle(item), v->len); + + return 0; +} + +cbor_item_t * +pack_int(int v) NO_MSAN +{ + if (v < 0) + return cbor_build_negint64((uint64_t)(-(int64_t)v - 1)); + else + return cbor_build_uint64((uint64_t)v); +} + +cbor_item_t * +pack_string(const char *v) NO_MSAN +{ + if (strlen(v) >= MAXSTR) + return NULL; + + return cbor_build_bytestring((const unsigned char *)v, strlen(v)); +} + +cbor_item_t * +pack_byte(uint8_t v) NO_MSAN +{ + return cbor_build_uint8(v); +} + +cbor_item_t * +pack_blob(const struct blob *v) NO_MSAN +{ + return cbor_build_bytestring(v->body, v->len); +} + +void +mutate_byte(uint8_t *b) +{ + LLVMFuzzerMutate(b, sizeof(*b), sizeof(*b)); +} + +void +mutate_int(int *i) +{ + LLVMFuzzerMutate((uint8_t *)i, sizeof(*i), sizeof(*i)); +} + +void +mutate_blob(struct blob *blob) +{ + blob->len = LLVMFuzzerMutate((uint8_t *)blob->body, blob->len, + sizeof(blob->body)); +} + +void +mutate_string(char *s) +{ + size_t n; + + n = LLVMFuzzerMutate((uint8_t *)s, strlen(s), MAXSTR - 1); + s[n] = '\0'; +} + +static int +buf_read(unsigned char *ptr, size_t len, int ms) +{ + size_t n; + + (void)ms; + + if (prng_up && uniform_random(400) < 1) { + errno = EIO; + return -1; + } + + if (wire_data_len < len) + n = wire_data_len; + else + n = len; + + memcpy(ptr, wire_data_ptr, n); + + wire_data_ptr += n; + wire_data_len -= n; + + return (int)n; +} + +static int +buf_write(const unsigned char *ptr, size_t len) +{ + consume(ptr, len); + + if (prng_up && uniform_random(400) < 1) { + errno = EIO; + return -1; + } + + return (int)len; +} + +static void * +hid_open(const char *path) +{ + (void)path; + + return (void *)HID_DEV_HANDLE; +} + +static void +hid_close(void *handle) +{ + assert(handle == (void *)HID_DEV_HANDLE); +} + +static int +hid_read(void *handle, unsigned char *ptr, size_t len, int ms) +{ + assert(handle == (void *)HID_DEV_HANDLE); + assert(len >= CTAP_MIN_REPORT_LEN && len <= CTAP_MAX_REPORT_LEN); + + return buf_read(ptr, len, ms); +} + +static int +hid_write(void *handle, const unsigned char *ptr, size_t len) +{ + assert(handle == (void *)HID_DEV_HANDLE); + assert(len >= CTAP_MIN_REPORT_LEN + 1 && + len <= CTAP_MAX_REPORT_LEN + 1); + + return buf_write(ptr, len); +} + +static void * +nfc_open(const char *path) +{ + (void)path; + + return (void *)NFC_DEV_HANDLE; +} + +static void +nfc_close(void *handle) +{ + assert(handle == (void *)NFC_DEV_HANDLE); +} + +int +nfc_read(void *handle, unsigned char *ptr, size_t len, int ms) +{ + assert(handle == (void *)NFC_DEV_HANDLE); + assert(len > 0 && len <= 264); + + return buf_read(ptr, len, ms); +} + +int +nfc_write(void *handle, const unsigned char *ptr, size_t len) +{ + assert(handle == (void *)NFC_DEV_HANDLE); + assert(len > 0 && len <= 256 + 2); + + return buf_write(ptr, len); +} + +ssize_t +fd_read(int fd, void *ptr, size_t len) +{ + assert(fd != -1); + + return buf_read(ptr, len, -1); +} + +ssize_t +fd_write(int fd, const void *ptr, size_t len) +{ + assert(fd != -1); + + return buf_write(ptr, len); +} + +fido_dev_t * +open_dev(int nfc) +{ + fido_dev_t *dev; + fido_dev_io_t io; + fido_dev_transport_t t; + + memset(&io, 0, sizeof(io)); + memset(&t, 0, sizeof(t)); + + if ((dev = fido_dev_new()) == NULL) + return NULL; + + if (nfc) { + io.open = nfc_open; + io.close = nfc_close; + io.read = nfc_read; + io.write = nfc_write; + } else { + io.open = hid_open; + io.close = hid_close; + io.read = hid_read; + io.write = hid_write; + } + + if (fido_dev_set_io_functions(dev, &io) != FIDO_OK) + goto fail; + + if (nfc) { + t.rx = fido_nfc_rx; + t.tx = fido_nfc_tx; + if (fido_dev_set_transport_functions(dev, &t) != FIDO_OK) + goto fail; + } + + if (fido_dev_set_timeout(dev, 300) != FIDO_OK || + fido_dev_open(dev, "nodev") != FIDO_OK) + goto fail; + + return dev; +fail: + fido_dev_free(&dev); + + return NULL; +} + +void +set_wire_data(const uint8_t *ptr, size_t len) +{ + wire_data_ptr = ptr; + wire_data_len = len; +} diff --git a/fuzz/mutator_aux.h b/fuzz/mutator_aux.h new file mode 100644 index 0000000..5ad5661 --- /dev/null +++ b/fuzz/mutator_aux.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _MUTATOR_AUX_H +#define _MUTATOR_AUX_H + +#include <sys/types.h> + +#include <stddef.h> +#include <stdint.h> +#include <cbor.h> + +#include "../src/fido.h" +#include "../src/fido/bio.h" +#include "../src/fido/config.h" +#include "../src/fido/credman.h" +#include "../src/fido/eddsa.h" +#include "../src/fido/es256.h" +#include "../src/fido/es384.h" +#include "../src/fido/rs256.h" +#include "../src/netlink.h" + +/* + * As of LLVM 10.0.0, MSAN support in libFuzzer was still experimental. + * We therefore have to be careful when using our custom mutator, or + * MSAN will flag uninitialised reads on memory populated by libFuzzer. + * Since there is no way to suppress MSAN without regenerating object + * code (in which case you might as well rebuild libFuzzer with MSAN), + * we adjust our mutator to make it less accurate while allowing + * fuzzing to proceed. + */ + +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) +# include <sanitizer/msan_interface.h> +# define NO_MSAN __attribute__((no_sanitize("memory"))) +# define WITH_MSAN 1 +# endif +#endif + +#if !defined(WITH_MSAN) +# define NO_MSAN +#endif + +#define MUTATE_SEED 0x01 +#define MUTATE_PARAM 0x02 +#define MUTATE_WIREDATA 0x04 +#define MUTATE_ALL (MUTATE_SEED | MUTATE_PARAM | MUTATE_WIREDATA) + +#define MAXSTR 1024 +#define MAXBLOB 3600 +#define MAXCORPUS 8192 + +#define HID_DEV_HANDLE 0x68696421 +#define NFC_DEV_HANDLE 0x6e666321 + +struct blob { + uint8_t body[MAXBLOB]; + size_t len; +}; + +struct param; + +struct param *unpack(const uint8_t *, size_t); +size_t pack(uint8_t *, size_t, const struct param *); +size_t pack_dummy(uint8_t *, size_t); +void mutate(struct param *, unsigned int, unsigned int); +void test(const struct param *); + +void consume(const void *, size_t); +void consume_str(const char *); + +int unpack_blob(cbor_item_t *, struct blob *); +int unpack_byte(cbor_item_t *, uint8_t *); +int unpack_int(cbor_item_t *, int *); +int unpack_string(cbor_item_t *, char *); + +cbor_item_t *pack_blob(const struct blob *); +cbor_item_t *pack_byte(uint8_t); +cbor_item_t *pack_int(int); +cbor_item_t *pack_string(const char *); + +void mutate_byte(uint8_t *); +void mutate_int(int *); +void mutate_blob(struct blob *); +void mutate_string(char *); + +ssize_t fd_read(int, void *, size_t); +ssize_t fd_write(int, const void *, size_t); + +int nfc_read(void *, unsigned char *, size_t, int); +int nfc_write(void *, const unsigned char *, size_t); + +fido_dev_t *open_dev(int); +void set_wire_data(const uint8_t *, size_t); + +void fuzz_clock_reset(void); +void prng_init(unsigned long); +unsigned long prng_uint32(void); + +uint32_t uniform_random(uint32_t); + +void set_pcsc_parameters(const struct blob *); +void set_pcsc_io_functions(int (*)(void *, u_char *, size_t, int), + int (*)(void *, const u_char *, size_t), void (*)(const void *, size_t)); + +#endif /* !_MUTATOR_AUX_H */ diff --git a/fuzz/pcsc.c b/fuzz/pcsc.c new file mode 100644 index 0000000..f6a3e9b --- /dev/null +++ b/fuzz/pcsc.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <winscard.h> + +#include "mutator_aux.h" + +static const struct blob *reader_list; +static int (*xread)(void *, u_char *, size_t, int); +static int (*xwrite)(void *, const u_char *, size_t); +static void (*xconsume)(const void *, size_t); + +LONG __wrap_SCardEstablishContext(DWORD, LPCVOID, LPCVOID, LPSCARDCONTEXT); +LONG __wrap_SCardListReaders(SCARDCONTEXT, LPCSTR, LPSTR, LPDWORD); +LONG __wrap_SCardReleaseContext(SCARDCONTEXT); +LONG __wrap_SCardConnect(SCARDCONTEXT, LPCSTR, DWORD, DWORD, LPSCARDHANDLE, + LPDWORD); +LONG __wrap_SCardDisconnect(SCARDHANDLE, DWORD); +LONG __wrap_SCardTransmit(SCARDHANDLE, const SCARD_IO_REQUEST *, LPCBYTE, + DWORD, SCARD_IO_REQUEST *, LPBYTE, LPDWORD); + +LONG +__wrap_SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1, + LPCVOID pvReserved2, LPSCARDCONTEXT phContext) +{ + assert(dwScope == SCARD_SCOPE_SYSTEM); + assert(pvReserved1 == NULL); + assert(pvReserved2 == NULL); + + *phContext = 1; + + if (uniform_random(400) < 1) + return SCARD_E_NO_SERVICE; + if (uniform_random(400) < 1) + return SCARD_E_NO_SMARTCARD; + if (uniform_random(400) < 1) + return SCARD_E_NO_MEMORY; + if (uniform_random(400) < 1) + *phContext = 0; + + return SCARD_S_SUCCESS; +} + +LONG +__wrap_SCardListReaders(SCARDCONTEXT hContext, LPCSTR mszGroups, + LPSTR mszReaders, LPDWORD pcchReaders) +{ + assert(hContext == 1); + assert(mszGroups == NULL); + assert(mszReaders != NULL); + assert(pcchReaders != 0); + + if (reader_list == NULL || uniform_random(400) < 1) + return SCARD_E_NO_READERS_AVAILABLE; + if (uniform_random(400) < 1) + return SCARD_E_NO_MEMORY; + + memcpy(mszReaders, reader_list->body, reader_list->len > *pcchReaders ? + *pcchReaders : reader_list->len); + *pcchReaders = (DWORD)reader_list->len; /* on purpose */ + + return SCARD_S_SUCCESS; +} + +LONG +__wrap_SCardReleaseContext(SCARDCONTEXT hContext) +{ + assert(hContext == 1); + + return SCARD_S_SUCCESS; +} + +LONG +__wrap_SCardConnect(SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode, + DWORD dwPreferredProtocols, LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol) +{ + uint32_t r; + + assert(hContext == 1); + xconsume(szReader, strlen(szReader) + 1); + assert(dwShareMode == SCARD_SHARE_SHARED); + assert(dwPreferredProtocols == SCARD_PROTOCOL_ANY); + assert(phCard != NULL); + assert(pdwActiveProtocol != NULL); + + if ((r = uniform_random(400)) < 1) + return SCARD_E_UNEXPECTED; + + *phCard = 1; + *pdwActiveProtocol = (r & 1) ? SCARD_PROTOCOL_T0 : SCARD_PROTOCOL_T1; + + if (uniform_random(400) < 1) + *pdwActiveProtocol = SCARD_PROTOCOL_RAW; + + return SCARD_S_SUCCESS; +} + +LONG +__wrap_SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition) +{ + assert(hCard == 1); + assert(dwDisposition == SCARD_LEAVE_CARD); + + return SCARD_S_SUCCESS; +} + +extern void consume(const void *body, size_t len); + +LONG +__wrap_SCardTransmit(SCARDHANDLE hCard, const SCARD_IO_REQUEST *pioSendPci, + LPCBYTE pbSendBuffer, DWORD cbSendLength, SCARD_IO_REQUEST *pioRecvPci, + LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength) +{ + void *ioh = (void *)NFC_DEV_HANDLE; + int n; + + assert(hCard == 1); + xconsume(pioSendPci, sizeof(*pioSendPci)); + xwrite(ioh, pbSendBuffer, cbSendLength); + assert(pioRecvPci == NULL); + + if (uniform_random(400) < 1 || + (n = xread(ioh, pbRecvBuffer, *pcbRecvLength, -1)) == -1) + return SCARD_E_UNEXPECTED; + *pcbRecvLength = (DWORD)n; + + return SCARD_S_SUCCESS; +} + +void +set_pcsc_parameters(const struct blob *reader_list_ptr) +{ + reader_list = reader_list_ptr; +} + +void +set_pcsc_io_functions(int (*read_f)(void *, u_char *, size_t, int), + int (*write_f)(void *, const u_char *, size_t), + void (*consume_f)(const void *, size_t)) +{ + xread = read_f; + xwrite = write_f; + xconsume = consume_f; +} diff --git a/fuzz/preload-fuzz.c b/fuzz/preload-fuzz.c new file mode 100644 index 0000000..f18848d --- /dev/null +++ b/fuzz/preload-fuzz.c @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * cc -fPIC -D_GNU_SOURCE -shared -o preload-fuzz.so preload-fuzz.c + * LD_PRELOAD=$(realpath preload-fuzz.so) + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <dlfcn.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define FUZZ_DEV_PREFIX "nodev" + +static int fd_fuzz = -1; +static int (*open_f)(const char *, int, mode_t); +static int (*close_f)(int); +static ssize_t (*write_f)(int, const void *, size_t); + +int +open(const char *path, int flags, ...) +{ + va_list ap; + mode_t mode; + + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + + if (open_f == NULL) { + open_f = dlsym(RTLD_NEXT, "open"); + if (open_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EACCES; + return (-1); + } + } + + if (strncmp(path, FUZZ_DEV_PREFIX, strlen(FUZZ_DEV_PREFIX)) != 0) + return (open_f(path, flags, mode)); + + if (fd_fuzz != -1) { + warnx("%s: fd_fuzz != -1", __func__); + errno = EACCES; + return (-1); + } + + if ((fd_fuzz = dup(STDIN_FILENO)) < 0) { + warn("%s: dup", __func__); + errno = EACCES; + return (-1); + } + + return (fd_fuzz); +} + +int +close(int fd) +{ + if (close_f == NULL) { + close_f = dlsym(RTLD_NEXT, "close"); + if (close_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EACCES; + return (-1); + } + } + + if (fd == fd_fuzz) + fd_fuzz = -1; + + return (close_f(fd)); +} + +ssize_t +write(int fd, const void *buf, size_t nbytes) +{ + if (write_f == NULL) { + write_f = dlsym(RTLD_NEXT, "write"); + if (write_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EBADF; + return (-1); + } + } + + if (fd != fd_fuzz) + return (write_f(fd, buf, nbytes)); + + return (nbytes); +} diff --git a/fuzz/preload-snoop.c b/fuzz/preload-snoop.c new file mode 100644 index 0000000..34d57ad --- /dev/null +++ b/fuzz/preload-snoop.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * cc -fPIC -D_GNU_SOURCE -shared -o preload-snoop.so preload-snoop.c + * LD_PRELOAD=$(realpath preload-snoop.so) + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <dlfcn.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define SNOOP_DEV_PREFIX "/dev/hidraw" + +struct fd_tuple { + int snoop_in; + int snoop_out; + int real_dev; +}; + +static struct fd_tuple *fd_tuple; +static int (*open_f)(const char *, int, mode_t); +static int (*close_f)(int); +static ssize_t (*read_f)(int, void *, size_t); +static ssize_t (*write_f)(int, const void *, size_t); + +static int +get_fd(const char *hid_path, const char *suffix) +{ + char *s = NULL; + char path[PATH_MAX]; + int fd; + int r; + + if ((s = strdup(hid_path)) == NULL) { + warnx("%s: strdup", __func__); + return (-1); + } + + for (size_t i = 0; i < strlen(s); i++) + if (s[i] == '/') + s[i] = '_'; + + if ((r = snprintf(path, sizeof(path), "%s-%s", s, suffix)) < 0 || + (size_t)r >= sizeof(path)) { + warnx("%s: snprintf", __func__); + free(s); + return (-1); + } + + free(s); + s = NULL; + + if ((fd = open_f(path, O_CREAT | O_WRONLY, 0644)) < 0) { + warn("%s: open", __func__); + return (-1); + } + + return (fd); +} + +int +open(const char *path, int flags, ...) +{ + va_list ap; + mode_t mode; + + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + + if (open_f == NULL) { + open_f = dlsym(RTLD_NEXT, "open"); + if (open_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EACCES; + return (-1); + } + } + + if (strncmp(path, SNOOP_DEV_PREFIX, strlen(SNOOP_DEV_PREFIX)) != 0) + return (open_f(path, flags, mode)); + + if (fd_tuple != NULL) { + warnx("%s: fd_tuple != NULL", __func__); + errno = EACCES; + return (-1); + } + + if ((fd_tuple = calloc(1, sizeof(*fd_tuple))) == NULL) { + warn("%s: calloc", __func__); + errno = ENOMEM; + return (-1); + } + + fd_tuple->snoop_in = -1; + fd_tuple->snoop_out = -1; + fd_tuple->real_dev = -1; + + if ((fd_tuple->snoop_in = get_fd(path, "in")) < 0 || + (fd_tuple->snoop_out = get_fd(path, "out")) < 0 || + (fd_tuple->real_dev = open_f(path, flags, mode)) < 0) { + warn("%s: get_fd/open", __func__); + goto fail; + } + + return (fd_tuple->real_dev); +fail: + if (fd_tuple->snoop_in != -1) + close(fd_tuple->snoop_in); + if (fd_tuple->snoop_out != -1) + close(fd_tuple->snoop_out); + if (fd_tuple->real_dev != -1) + close(fd_tuple->real_dev); + + free(fd_tuple); + fd_tuple = NULL; + + errno = EACCES; + + return (-1); +} + +int +close(int fd) +{ + if (close_f == NULL) { + close_f = dlsym(RTLD_NEXT, "close"); + if (close_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EBADF; + return (-1); + } + } + + if (fd_tuple == NULL || fd_tuple->real_dev != fd) + return (close_f(fd)); + + close_f(fd_tuple->snoop_in); + close_f(fd_tuple->snoop_out); + close_f(fd_tuple->real_dev); + + free(fd_tuple); + fd_tuple = NULL; + + return (0); +} + +ssize_t +read(int fd, void *buf, size_t nbytes) +{ + ssize_t n; + + if (read_f == NULL) { + read_f = dlsym(RTLD_NEXT, "read"); + if (read_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EBADF; + return (-1); + } + } + + if (write_f == NULL) { + write_f = dlsym(RTLD_NEXT, "write"); + if (write_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EBADF; + return (-1); + } + } + + if (fd_tuple == NULL || fd_tuple->real_dev != fd) + return (read_f(fd, buf, nbytes)); + + if ((n = read_f(fd, buf, nbytes)) < 0 || + write_f(fd_tuple->snoop_in, buf, n) != n) + return (-1); + + return (n); +} + +ssize_t +write(int fd, const void *buf, size_t nbytes) +{ + ssize_t n; + + if (write_f == NULL) { + write_f = dlsym(RTLD_NEXT, "write"); + if (write_f == NULL) { + warnx("%s: dlsym", __func__); + errno = EBADF; + return (-1); + } + } + + if (fd_tuple == NULL || fd_tuple->real_dev != fd) + return (write_f(fd, buf, nbytes)); + + if ((n = write_f(fd, buf, nbytes)) < 0 || + write_f(fd_tuple->snoop_out, buf, n) != n) + return (-1); + + return (n); +} diff --git a/fuzz/prng.c b/fuzz/prng.c new file mode 100644 index 0000000..61114ac --- /dev/null +++ b/fuzz/prng.c @@ -0,0 +1,113 @@ +/* + A C-program for MT19937, with initialization improved 2002/1/26. + Coded by Takuji Nishimura and Makoto Matsumoto. + + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include "mutator_aux.h" + +#define init_genrand prng_init +#define genrand_int32 prng_uint32 + +/* Period parameters */ +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UPPER_MASK 0x80000000UL /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffffUL /* least significant r bits */ + +int prng_up = 0; +static unsigned long mt[N]; /* the array for the state vector */ +static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */ + +/* initializes mt[N] with a seed */ +void init_genrand(unsigned long s) +{ + mt[0]= s & 0xffffffffUL; + for (mti=1; mti<N; mti++) { + mt[mti] = + (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) + + (unsigned long)mti); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + mt[mti] &= 0xffffffffUL; + /* for >32 bit machines */ + } + prng_up = 1; +} + +/* generates a random number on [0,0xffffffff]-interval */ +unsigned long genrand_int32(void) +{ + unsigned long y; + static unsigned long mag01[2]={0x0UL, MATRIX_A}; + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (mti >= N) { /* generate N words at one time */ + int kk; + + assert(mti != N+1); + + for (kk=0;kk<N-M;kk++) { + y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK); + mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL]; + } + for (;kk<N-1;kk++) { + y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK); + mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL]; + } + y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + + mti = 0; + } + + y = mt[mti++]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} diff --git a/fuzz/report.tgz b/fuzz/report.tgz Binary files differnew file mode 100644 index 0000000..9c01263 --- /dev/null +++ b/fuzz/report.tgz diff --git a/fuzz/summary.txt b/fuzz/summary.txt new file mode 100644 index 0000000..adda3ac --- /dev/null +++ b/fuzz/summary.txt @@ -0,0 +1,64 @@ +Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +fuzz/clock.c 24 1 95.83% 4 0 100.00% 35 1 97.14% +fuzz/pcsc.c 59 0 100.00% 8 0 100.00% 75 12 84.00% +fuzz/prng.c 31 0 100.00% 2 0 100.00% 35 1 97.14% +fuzz/udev.c 110 2 98.18% 17 0 100.00% 126 12 90.48% +fuzz/uniform_random.c 7 1 85.71% 1 0 100.00% 12 1 91.67% +fuzz/wrap.c 23 0 100.00% 3 0 100.00% 29 0 100.00% +openbsd-compat/explicit_bzero.c 4 0 100.00% 1 0 100.00% 7 0 100.00% +openbsd-compat/freezero.c 4 0 100.00% 1 0 100.00% 6 0 100.00% +openbsd-compat/recallocarray.c 41 7 82.93% 1 0 100.00% 36 7 80.56% +openbsd-compat/timingsafe_bcmp.c 4 0 100.00% 1 0 100.00% 7 0 100.00% +src/aes256.c 118 3 97.46% 8 0 100.00% 157 11 92.99% +src/assert.c 628 45 92.83% 63 4 93.65% 782 51 93.48% +src/authkey.c 52 0 100.00% 5 0 100.00% 66 0 100.00% +src/bio.c 451 20 95.57% 49 2 95.92% 587 24 95.91% +src/blob.c 53 2 96.23% 10 0 100.00% 83 4 95.18% +src/buf.c 8 1 87.50% 2 0 100.00% 16 1 93.75% +src/cbor.c 1070 12 98.88% 55 0 100.00% 1258 28 97.77% +src/compress.c 105 14 86.67% 5 0 100.00% 122 24 80.33% +src/config.c 112 0 100.00% 11 0 100.00% 154 0 100.00% +src/cred.c 653 36 94.49% 70 2 97.14% 853 39 95.43% +src/credman.c 422 10 97.63% 40 0 100.00% 557 20 96.41% +src/dev.c 332 65 80.42% 41 6 85.37% 378 80 78.84% +src/ecdh.c 117 2 98.29% 4 0 100.00% 146 5 96.58% +src/eddsa.c 88 5 94.32% 10 0 100.00% 114 9 92.11% +src/err.c 122 10 91.80% 1 0 100.00% 126 10 92.06% +src/es256.c 315 5 98.41% 19 0 100.00% 372 11 97.04% +src/es384.c 158 5 96.84% 11 0 100.00% 198 11 94.44% +src/hid.c 87 2 97.70% 14 0 100.00% 145 3 97.93% +src/hid_linux.c 184 73 60.33% 14 7 50.00% 263 115 56.27% +src/hid_unix.c 29 21 27.59% 2 0 100.00% 43 26 39.53% +src/info.c 232 0 100.00% 51 0 100.00% 409 0 100.00% +src/io.c 193 7 96.37% 13 0 100.00% 230 12 94.78% +src/iso7816.c 18 1 94.44% 5 0 100.00% 38 1 97.37% +src/largeblob.c 525 18 96.57% 30 0 100.00% 693 43 93.80% +src/log.c 39 5 87.18% 7 1 85.71% 63 7 88.89% +src/netlink.c 329 8 97.57% 40 0 100.00% 498 15 96.99% +src/nfc.c 155 5 96.77% 12 0 100.00% 244 15 93.85% +src/nfc_linux.c 172 77 55.23% 13 7 46.15% 242 126 47.93% +src/pcsc.c 204 1 99.51% 13 0 100.00% 282 3 98.94% +src/pin.c 426 3 99.30% 26 0 100.00% 514 4 99.22% +src/random.c 6 0 100.00% 1 0 100.00% 6 0 100.00% +src/reset.c 24 0 100.00% 3 0 100.00% 23 0 100.00% +src/rs1.c 22 2 90.91% 3 0 100.00% 36 6 83.33% +src/rs256.c 146 9 93.84% 13 0 100.00% 179 16 91.06% +src/time.c 43 3 93.02% 3 0 100.00% 43 2 95.35% +src/touch.c 67 0 100.00% 2 0 100.00% 79 0 100.00% +src/tpm.c 103 0 100.00% 9 0 100.00% 194 0 100.00% +src/types.c 29 0 100.00% 7 0 100.00% 56 0 100.00% +src/u2f.c 572 4 99.30% 17 0 100.00% 726 12 98.35% +src/util.c 14 1 92.86% 1 0 100.00% 14 1 92.86% + +Files which contain no functions: +fuzz/mutator_aux.h 0 0 - 0 0 - 0 0 - +openbsd-compat/openbsd-compat.h 0 0 - 0 0 - 0 0 - +openbsd-compat/time.h 0 0 - 0 0 - 0 0 - +src/extern.h 0 0 - 0 0 - 0 0 - +src/fallthrough.h 0 0 - 0 0 - 0 0 - +src/fido.h 0 0 - 0 0 - 0 0 - +src/fido/err.h 0 0 - 0 0 - 0 0 - +src/fido/param.h 0 0 - 0 0 - 0 0 - +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +TOTAL 8730 486 94.43% 742 29 96.09% 11357 769 93.23% diff --git a/fuzz/udev.c b/fuzz/udev.c new file mode 100644 index 0000000..3194012 --- /dev/null +++ b/fuzz/udev.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <linux/hidraw.h> +#include <linux/input.h> + +#include <assert.h> +#include <errno.h> +#include <libudev.h> +#include <stdlib.h> + +#include "mutator_aux.h" + +struct udev { + int magic; +}; + +struct udev_enumerate { + int magic; + struct udev_list_entry *list_entry; +}; + +struct udev_list_entry { + int magic; +}; + +struct udev_device { + int magic; + struct udev_device *parent; +}; + +#define UDEV_MAGIC 0x584492cc +#define UDEV_DEVICE_MAGIC 0x569180dd +#define UDEV_LIST_ENTRY_MAGIC 0x497422ee +#define UDEV_ENUM_MAGIC 0x583570ff + +#define ASSERT_TYPE(x, m) assert((x) != NULL && (x)->magic == (m)) +#define ASSERT_UDEV(x) ASSERT_TYPE((x), UDEV_MAGIC) +#define ASSERT_UDEV_ENUM(x) ASSERT_TYPE((x), UDEV_ENUM_MAGIC) +#define ASSERT_UDEV_LIST_ENTRY(x) ASSERT_TYPE((x), UDEV_LIST_ENTRY_MAGIC) +#define ASSERT_UDEV_DEVICE(x) ASSERT_TYPE((x), UDEV_DEVICE_MAGIC) + +static const char *uevent; +static const struct blob *report_descriptor; + +struct udev *__wrap_udev_new(void); +struct udev_device *__wrap_udev_device_get_parent_with_subsystem_devtype( + struct udev_device *, const char *, const char *); +struct udev_device *__wrap_udev_device_new_from_syspath(struct udev *, + const char *); +struct udev_enumerate *__wrap_udev_enumerate_new(struct udev *); +struct udev_list_entry *__wrap_udev_enumerate_get_list_entry( + struct udev_enumerate *); +struct udev_list_entry *__wrap_udev_list_entry_get_next( + struct udev_list_entry *); +const char *__wrap_udev_device_get_sysattr_value(struct udev_device *, + const char *); +const char *__wrap_udev_list_entry_get_name(struct udev_list_entry *); +const char *__wrap_udev_device_get_devnode(struct udev_device *); +const char *__wrap_udev_device_get_sysnum(struct udev_device *); +int __wrap_udev_enumerate_add_match_subsystem(struct udev_enumerate *, + const char *); +int __wrap_udev_enumerate_scan_devices(struct udev_enumerate *); +int __wrap_ioctl(int, unsigned long , ...); +void __wrap_udev_device_unref(struct udev_device *); +void __wrap_udev_enumerate_unref(struct udev_enumerate *); +void __wrap_udev_unref(struct udev *); +void set_udev_parameters(const char *, const struct blob *); + +struct udev_device * +__wrap_udev_device_get_parent_with_subsystem_devtype(struct udev_device *child, + const char *subsystem, const char *devtype) +{ + ASSERT_UDEV_DEVICE(child); + fido_log_debug("%s", subsystem); /* XXX consume */ + fido_log_debug("%s", devtype); /* XXX consume */ + if (child->parent != NULL) + return child->parent; + if ((child->parent = calloc(1, sizeof(*child->parent))) == NULL) + return NULL; + child->parent->magic = UDEV_DEVICE_MAGIC; + + return child->parent; +} + +const char * +__wrap_udev_device_get_sysattr_value(struct udev_device *udev_device, + const char *sysattr) +{ + ASSERT_UDEV_DEVICE(udev_device); + if (uniform_random(400) < 1) + return NULL; + if (!strcmp(sysattr, "manufacturer") || !strcmp(sysattr, "product")) + return "product info"; /* XXX randomise? */ + else if (!strcmp(sysattr, "uevent")) + return uevent; + + return NULL; +} + +const char * +__wrap_udev_list_entry_get_name(struct udev_list_entry *entry) +{ + ASSERT_UDEV_LIST_ENTRY(entry); + return uniform_random(400) < 1 ? NULL : "name"; /* XXX randomise? */ +} + +struct udev_device * +__wrap_udev_device_new_from_syspath(struct udev *udev, const char *syspath) +{ + struct udev_device *udev_device; + + ASSERT_UDEV(udev); + fido_log_debug("%s", syspath); + if ((udev_device = calloc(1, sizeof(*udev_device))) == NULL) + return NULL; + udev_device->magic = UDEV_DEVICE_MAGIC; + + return udev_device; +} + +const char * +__wrap_udev_device_get_devnode(struct udev_device *udev_device) +{ + ASSERT_UDEV_DEVICE(udev_device); + return uniform_random(400) < 1 ? NULL : "/dev/zero"; +} + +const char * +__wrap_udev_device_get_sysnum(struct udev_device *udev_device) +{ + ASSERT_UDEV_DEVICE(udev_device); + return uniform_random(400) < 1 ? NULL : "101010"; /* XXX randomise? */ +} + +void +__wrap_udev_device_unref(struct udev_device *udev_device) +{ + ASSERT_UDEV_DEVICE(udev_device); + if (udev_device->parent) { + ASSERT_UDEV_DEVICE(udev_device->parent); + free(udev_device->parent); + } + free(udev_device); +} + +struct udev * +__wrap_udev_new(void) +{ + struct udev *udev; + + if ((udev = calloc(1, sizeof(*udev))) == NULL) + return NULL; + udev->magic = UDEV_MAGIC; + + return udev; +} + +struct udev_enumerate * +__wrap_udev_enumerate_new(struct udev *udev) +{ + struct udev_enumerate *udev_enum; + + ASSERT_UDEV(udev); + if ((udev_enum = calloc(1, sizeof(*udev_enum))) == NULL) + return NULL; + udev_enum->magic = UDEV_ENUM_MAGIC; + + return udev_enum; +} + +int +__wrap_udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enum, + const char *subsystem) +{ + ASSERT_UDEV_ENUM(udev_enum); + fido_log_debug("%s:", subsystem); + return uniform_random(400) < 1 ? -EINVAL : 0; +} + +int +__wrap_udev_enumerate_scan_devices(struct udev_enumerate *udev_enum) +{ + ASSERT_UDEV_ENUM(udev_enum); + return uniform_random(400) < 1 ? -EINVAL : 0; +} + +struct udev_list_entry * +__wrap_udev_enumerate_get_list_entry(struct udev_enumerate *udev_enum) +{ + ASSERT_UDEV_ENUM(udev_enum); + if ((udev_enum->list_entry = calloc(1, + sizeof(*udev_enum->list_entry))) == NULL) + return NULL; + udev_enum->list_entry->magic = UDEV_LIST_ENTRY_MAGIC; + + return udev_enum->list_entry; +} + +struct udev_list_entry * +__wrap_udev_list_entry_get_next(struct udev_list_entry *udev_list_entry) +{ + ASSERT_UDEV_LIST_ENTRY(udev_list_entry); + return uniform_random(400) < 1 ? NULL : udev_list_entry; +} + +void +__wrap_udev_enumerate_unref(struct udev_enumerate *udev_enum) +{ + ASSERT_UDEV_ENUM(udev_enum); + if (udev_enum->list_entry) + ASSERT_UDEV_LIST_ENTRY(udev_enum->list_entry); + free(udev_enum->list_entry); + free(udev_enum); +} + +void +__wrap_udev_unref(struct udev *udev) +{ + ASSERT_UDEV(udev); + free(udev); +} + +int +__wrap_ioctl(int fd, unsigned long request, ...) +{ + va_list ap; + struct hidraw_report_descriptor *hrd; + + (void)fd; + + if (uniform_random(400) < 1) { + errno = EINVAL; + return -1; + } + + va_start(ap, request); + + switch (IOCTL_REQ(request)) { + case IOCTL_REQ(HIDIOCGRDESCSIZE): + *va_arg(ap, int *) = (int)report_descriptor->len; + break; + case IOCTL_REQ(HIDIOCGRDESC): + hrd = va_arg(ap, struct hidraw_report_descriptor *); + assert(hrd->size == report_descriptor->len); + memcpy(hrd->value, report_descriptor->body, hrd->size); + break; + default: + warnx("%s: unknown request 0x%lx", __func__, request); + abort(); + } + + va_end(ap); + + return 0; +} + +void +set_udev_parameters(const char *uevent_ptr, + const struct blob *report_descriptor_ptr) +{ + uevent = uevent_ptr; + report_descriptor = report_descriptor_ptr; +} diff --git a/fuzz/uniform_random.c b/fuzz/uniform_random.c new file mode 100644 index 0000000..357091c --- /dev/null +++ b/fuzz/uniform_random.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdint.h> +#include <stdlib.h> + +uint32_t uniform_random(uint32_t); +unsigned long prng_uint32(void); + +/* + * Calculate a uniformly distributed random number less than upper_bound + * avoiding "modulo bias". + * + * Uniformity is achieved by generating new random numbers until the one + * returned is outside the range [0, 2**32 % upper_bound). This + * guarantees the selected random number will be inside + * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound) + * after reduction modulo upper_bound. + */ +uint32_t +uniform_random(uint32_t upper_bound) +{ + uint32_t r, min; + + if (upper_bound < 2) + return 0; + + /* 2**32 % x == (2**32 - x) % x */ + min = -upper_bound % upper_bound; + + /* + * This could theoretically loop forever but each retry has + * p > 0.5 (worst case, usually far better) of selecting a + * number inside the range we need, so it should rarely need + * to re-roll. + */ + for (;;) { + r = (uint32_t)prng_uint32(); + if (r >= min) + break; + } + + return r % upper_bound; +} diff --git a/fuzz/wiredata_fido2.h b/fuzz/wiredata_fido2.h new file mode 100644 index 0000000..6c66c54 --- /dev/null +++ b/fuzz/wiredata_fido2.h @@ -0,0 +1,708 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _WIREDATA_FIDO2_H +#define _WIREDATA_FIDO2_H + +#define WIREDATA_CTAP_INIT \ + 0xff, 0xff, 0xff, 0xff, 0x86, 0x00, 0x11, 0x80, \ + 0x43, 0x56, 0x40, 0xb1, 0x4e, 0xd9, 0x2d, 0x00, \ + 0x22, 0x00, 0x02, 0x02, 0x05, 0x02, 0x01, 0x05, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_KEEPALIVE \ + 0x00, 0x22, 0x00, 0x02, 0xbb, 0x00, 0x01, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_INFO \ + 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0xb9, 0x00, \ + 0xa9, 0x01, 0x83, 0x66, 0x55, 0x32, 0x46, 0x5f, \ + 0x56, 0x32, 0x68, 0x46, 0x49, 0x44, 0x4f, 0x5f, \ + 0x32, 0x5f, 0x30, 0x6c, 0x46, 0x49, 0x44, 0x4f, \ + 0x5f, 0x32, 0x5f, 0x31, 0x5f, 0x50, 0x52, 0x45, \ + 0x02, 0x82, 0x6b, 0x63, 0x72, 0x65, 0x64, 0x50, \ + 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x6b, 0x68, \ + 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x65, 0x63, 0x72, \ + 0x00, 0x22, 0x00, 0x02, 0x00, 0x65, 0x74, 0x03, \ + 0x50, 0x19, 0x56, 0xe5, 0xbd, 0xa3, 0x74, 0x45, \ + 0xf1, 0xa8, 0x14, 0x35, 0x64, 0x03, 0xfd, 0xbc, \ + 0x18, 0x04, 0xa5, 0x62, 0x72, 0x6b, 0xf5, 0x62, \ + 0x75, 0x70, 0xf5, 0x64, 0x70, 0x6c, 0x61, 0x74, \ + 0xf4, 0x69, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, \ + 0x50, 0x69, 0x6e, 0xf4, 0x75, 0x63, 0x72, 0x65, \ + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4d, \ + 0x00, 0x22, 0x00, 0x02, 0x01, 0x67, 0x6d, 0x74, \ + 0x50, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0xf5, \ + 0x05, 0x19, 0x04, 0xb0, 0x06, 0x81, 0x01, 0x07, \ + 0x08, 0x08, 0x18, 0x80, 0x0a, 0x82, 0xa2, 0x63, \ + 0x61, 0x6c, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, \ + 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, \ + 0x2d, 0x6b, 0x65, 0x79, 0xa2, 0x63, 0x61, 0x6c, \ + 0x67, 0x27, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6a, \ + 0x00, 0x22, 0x00, 0x02, 0x02, 0x70, 0x75, 0x62, \ + 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_AUTHKEY \ + 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x51, 0x00, \ + 0xa1, 0x01, 0xa5, 0x01, 0x02, 0x03, 0x38, 0x18, \ + 0x20, 0x01, 0x21, 0x58, 0x20, 0x2a, 0xb8, 0x2d, \ + 0x36, 0x69, 0xab, 0x30, 0x9d, 0xe3, 0x5e, 0x9b, \ + 0xfb, 0x94, 0xfc, 0x1d, 0x92, 0x95, 0xaf, 0x01, \ + 0x47, 0xfe, 0x4b, 0x87, 0xe5, 0xcf, 0x3f, 0x05, \ + 0x0b, 0x39, 0xda, 0x17, 0x49, 0x22, 0x58, 0x20, \ + 0x15, 0x1b, 0xbe, 0x08, 0x78, 0x60, 0x4d, 0x3c, \ + 0x00, 0x22, 0x00, 0x02, 0x00, 0x3f, 0xf1, 0x60, \ + 0xa6, 0xd8, 0xf8, 0xed, 0xce, 0x4a, 0x30, 0x5d, \ + 0x1a, 0xaf, 0x80, 0xc4, 0x0a, 0xd2, 0x6f, 0x77, \ + 0x38, 0x12, 0x97, 0xaa, 0xbd, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_PINTOKEN \ + 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x14, 0x00, \ + 0xa1, 0x02, 0x50, 0xee, 0x40, 0x4c, 0x85, 0xd7, \ + 0xa1, 0x2f, 0x56, 0xc4, 0x4e, 0xc5, 0x93, 0x41, \ + 0xd0, 0x3b, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_STATUS \ + 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x01, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_RETRIES \ + 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0x04, 0x00, \ + 0xa1, 0x03, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_ASSERT \ + 0x00, 0x22, 0x00, 0x02, 0x90, 0x00, 0xcb, 0x00, \ + 0xa3, 0x01, 0xa2, 0x62, 0x69, 0x64, 0x58, 0x40, \ + 0x4a, 0x4c, 0x9e, 0xcc, 0x81, 0x7d, 0x42, 0x03, \ + 0x2b, 0x41, 0xd1, 0x38, 0xd3, 0x49, 0xb4, 0xfc, \ + 0xfb, 0xe4, 0x4e, 0xe4, 0xff, 0x76, 0x34, 0x16, \ + 0x68, 0x06, 0x9d, 0xa6, 0x01, 0x32, 0xb9, 0xff, \ + 0xc2, 0x35, 0x0d, 0x89, 0x43, 0x66, 0x12, 0xf8, \ + 0x8e, 0x5b, 0xde, 0xf4, 0xcc, 0xec, 0x9d, 0x03, \ + 0x00, 0x92, 0x00, 0x0e, 0x00, 0x85, 0xc2, 0xf5, \ + 0xe6, 0x8e, 0xeb, 0x3f, 0x3a, 0xec, 0xc3, 0x1d, \ + 0x04, 0x6e, 0xf3, 0x5b, 0x88, 0x64, 0x74, 0x79, \ + 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, \ + 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x02, 0x58, 0x25, \ + 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, 0x8c, 0x68, \ + 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, 0x60, 0x5b, \ + 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, 0x32, 0xc7, \ + 0x00, 0x92, 0x00, 0x0e, 0x01, 0x99, 0x5c, 0xf3, \ + 0xba, 0x83, 0x1d, 0x97, 0x63, 0x04, 0x00, 0x00, \ + 0x00, 0x09, 0x03, 0x58, 0x47, 0x30, 0x45, 0x02, \ + 0x21, 0x00, 0xcf, 0x3f, 0x36, 0x0e, 0x1f, 0x6f, \ + 0xd6, 0xa0, 0x9d, 0x13, 0xcf, 0x55, 0xf7, 0x49, \ + 0x8f, 0xc8, 0xc9, 0x03, 0x12, 0x76, 0x41, 0x75, \ + 0x7b, 0xb5, 0x0a, 0x90, 0xa5, 0x82, 0x26, 0xf1, \ + 0x6b, 0x80, 0x02, 0x20, 0x34, 0x9b, 0x7a, 0x82, \ + 0x00, 0x92, 0x00, 0x0e, 0x02, 0xd3, 0xe1, 0x79, \ + 0x49, 0x55, 0x41, 0x9f, 0xa4, 0x06, 0x06, 0xbd, \ + 0xc8, 0xb9, 0x2b, 0x5f, 0xe1, 0xa7, 0x99, 0x1c, \ + 0xa1, 0xfc, 0x7e, 0x3e, 0xd5, 0x85, 0x2e, 0x11, \ + 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_CRED \ + 0x00, 0x91, 0x00, 0x03, 0x90, 0x03, 0xe1, 0x00, \ + 0xa3, 0x01, 0x66, 0x70, 0x61, 0x63, 0x6b, 0x65, \ + 0x64, 0x02, 0x58, 0xc4, 0x49, 0x96, 0x0d, 0xe5, \ + 0x88, 0x0e, 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, \ + 0x64, 0x76, 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, \ + 0xa2, 0x86, 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, \ + 0x83, 0x1d, 0x97, 0x63, 0x45, 0x00, 0x00, 0x00, \ + 0x00, 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, \ + 0x00, 0x91, 0x00, 0x03, 0x00, 0x15, 0x80, 0x06, \ + 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, 0x40, \ + 0xed, 0x88, 0x48, 0xa1, 0xdb, 0x56, 0x4d, 0x0f, \ + 0x0d, 0xc8, 0x8f, 0x0f, 0xe9, 0x16, 0xb1, 0x78, \ + 0xa9, 0x40, 0x98, 0x71, 0xa0, 0xb3, 0xf2, 0xcf, \ + 0x05, 0x73, 0x6c, 0x12, 0xbf, 0x00, 0x96, 0xf3, \ + 0x7b, 0x93, 0xba, 0x49, 0xee, 0x23, 0xb4, 0x78, \ + 0x2e, 0xfb, 0xce, 0x27, 0xa8, 0xc2, 0x26, 0x78, \ + 0x00, 0x91, 0x00, 0x03, 0x01, 0xcc, 0x95, 0x2d, \ + 0x40, 0xdb, 0xd1, 0x40, 0x3d, 0x2b, 0xa3, 0x31, \ + 0xa0, 0x75, 0x82, 0x63, 0xf0, 0xa5, 0x01, 0x02, \ + 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x9d, \ + 0x95, 0xa1, 0xb5, 0xd6, 0x11, 0xbf, 0xe2, 0x28, \ + 0xa0, 0x7f, 0xca, 0x1e, 0xd9, 0x09, 0x0f, 0x0d, \ + 0xe7, 0x8e, 0x29, 0xe8, 0x2e, 0x11, 0xdb, 0x55, \ + 0x62, 0x13, 0xd7, 0x26, 0xc2, 0x7e, 0x2b, 0x22, \ + 0x00, 0x91, 0x00, 0x03, 0x02, 0x58, 0x20, 0xbe, \ + 0x74, 0x2a, 0xac, 0xde, 0x11, 0x40, 0x76, 0x31, \ + 0x0b, 0xed, 0x55, 0xde, 0xf3, 0x03, 0xe4, 0x1c, \ + 0xac, 0x42, 0x63, 0x8f, 0xe8, 0x30, 0x63, 0xb7, \ + 0x07, 0x4e, 0x5d, 0xfb, 0x17, 0x5e, 0x9b, 0x03, \ + 0xa3, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x63, 0x73, \ + 0x69, 0x67, 0x58, 0x48, 0x30, 0x46, 0x02, 0x21, \ + 0x00, 0xfb, 0xd1, 0x26, 0x76, 0x34, 0x74, 0xac, \ + 0x00, 0x91, 0x00, 0x03, 0x03, 0xf6, 0xd8, 0x5c, \ + 0x5d, 0xbc, 0xda, 0xe0, 0x43, 0xe0, 0xa5, 0x42, \ + 0x9f, 0xc7, 0xe2, 0x18, 0x3e, 0xe2, 0x2c, 0x94, \ + 0x78, 0xbf, 0x9c, 0xeb, 0x3e, 0x9d, 0x02, 0x21, \ + 0x00, 0xab, 0x21, 0x1b, 0xc4, 0x30, 0x69, 0xee, \ + 0x7f, 0x09, 0xe6, 0x6b, 0x99, 0x98, 0x34, 0x07, \ + 0x7b, 0x9a, 0x58, 0xb2, 0xe8, 0x77, 0xe0, 0xba, \ + 0x7d, 0xab, 0x65, 0xf8, 0xba, 0x2a, 0xcb, 0x9a, \ + 0x00, 0x91, 0x00, 0x03, 0x04, 0x41, 0x63, 0x78, \ + 0x35, 0x63, 0x81, 0x59, 0x02, 0xb3, 0x30, 0x82, \ + 0x02, 0xaf, 0x30, 0x82, 0x01, 0x97, 0xa0, 0x03, \ + 0x02, 0x01, 0x02, 0x02, 0x04, 0x48, 0x5b, 0x3d, \ + 0xb6, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, \ + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, \ + 0x30, 0x21, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, \ + 0x55, 0x04, 0x03, 0x0c, 0x16, 0x59, 0x75, 0x62, \ + 0x00, 0x91, 0x00, 0x03, 0x05, 0x69, 0x63, 0x6f, \ + 0x20, 0x46, 0x49, 0x44, 0x4f, 0x20, 0x50, 0x72, \ + 0x65, 0x76, 0x69, 0x65, 0x77, 0x20, 0x43, 0x41, \ + 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x34, \ + 0x31, 0x32, 0x31, 0x30, 0x35, 0x37, 0x31, 0x30, \ + 0x5a, 0x17, 0x0d, 0x31, 0x38, 0x31, 0x32, 0x33, \ + 0x31, 0x31, 0x30, 0x35, 0x37, 0x31, 0x30, 0x5a, \ + 0x30, 0x6f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, \ + 0x00, 0x91, 0x00, 0x03, 0x06, 0x55, 0x04, 0x06, \ + 0x13, 0x02, 0x53, 0x45, 0x31, 0x12, 0x30, 0x10, \ + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x09, 0x59, \ + 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x41, 0x42, \ + 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, \ + 0x0b, 0x0c, 0x19, 0x41, 0x75, 0x74, 0x68, 0x65, \ + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, \ + 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, \ + 0x00, 0x91, 0x00, 0x03, 0x07, 0x74, 0x69, 0x6f, \ + 0x6e, 0x31, 0x28, 0x30, 0x26, 0x06, 0x03, 0x55, \ + 0x04, 0x03, 0x0c, 0x1f, 0x59, 0x75, 0x62, 0x69, \ + 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, 0x45, \ + 0x45, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, \ + 0x20, 0x31, 0x32, 0x31, 0x33, 0x39, 0x33, 0x39, \ + 0x31, 0x32, 0x36, 0x30, 0x59, 0x30, 0x13, 0x06, \ + 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, \ + 0x00, 0x91, 0x00, 0x03, 0x08, 0x06, 0x08, 0x2a, \ + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, \ + 0x42, 0x00, 0x04, 0xfb, 0x2c, 0xdd, 0x30, 0x43, \ + 0x28, 0xc5, 0x72, 0x4a, 0x50, 0xcc, 0xe6, 0xf6, \ + 0x0b, 0xad, 0x7d, 0x27, 0xa9, 0x1b, 0x59, 0xe1, \ + 0xe6, 0x6f, 0x29, 0x7b, 0x89, 0xc9, 0xd4, 0x3d, \ + 0xc2, 0xb2, 0xc7, 0x78, 0x89, 0xb4, 0xf0, 0xff, \ + 0x9d, 0x02, 0x28, 0xcb, 0x94, 0x6d, 0xfc, 0xe0, \ + 0x00, 0x91, 0x00, 0x03, 0x09, 0x1b, 0x19, 0x58, \ + 0x9b, 0x67, 0x80, 0x4a, 0xac, 0x97, 0x7f, 0x28, \ + 0x18, 0x9c, 0xcd, 0xb3, 0x25, 0x74, 0xca, 0x28, \ + 0xa3, 0x6c, 0x30, 0x6a, 0x30, 0x22, 0x06, 0x09, \ + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, \ + 0x02, 0x04, 0x15, 0x31, 0x2e, 0x33, 0x2e, 0x36, \ + 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, \ + 0x31, 0x34, 0x38, 0x32, 0x2e, 0x31, 0x2e, 0x36, \ + 0x00, 0x91, 0x00, 0x03, 0x0a, 0x30, 0x13, 0x06, \ + 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, \ + 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, 0x03, 0x02, \ + 0x04, 0x30, 0x30, 0x21, 0x06, 0x0b, 0x2b, 0x06, \ + 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x01, 0x01, \ + 0x04, 0x04, 0x12, 0x04, 0x10, 0xf8, 0xa0, 0x11, \ + 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, 0x06, 0x17, \ + 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x30, 0x0c, 0x06, \ + 0x00, 0x91, 0x00, 0x03, 0x0b, 0x03, 0x55, 0x1d, \ + 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, \ + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, \ + 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, \ + 0x82, 0x01, 0x01, 0x00, 0x32, 0xf3, 0xe4, 0xbd, \ + 0x58, 0xd7, 0x42, 0x2b, 0xaf, 0x49, 0x99, 0x86, \ + 0x08, 0x1f, 0x0d, 0xa9, 0x3b, 0xc6, 0xaa, 0x1c, \ + 0x72, 0x11, 0xf9, 0x28, 0x53, 0xeb, 0xf3, 0xeb, \ + 0x00, 0x91, 0x00, 0x03, 0x0c, 0x73, 0xda, 0x69, \ + 0x3b, 0x06, 0xde, 0x31, 0x33, 0x8e, 0x5d, 0x02, \ + 0xec, 0xf6, 0x76, 0xe9, 0x5c, 0x42, 0xbe, 0xa5, \ + 0x8f, 0x25, 0xd3, 0x37, 0x3f, 0x77, 0xbb, 0x2a, \ + 0x9d, 0x7c, 0xb2, 0x3e, 0x11, 0x8c, 0x41, 0xd4, \ + 0x9a, 0x4c, 0x9a, 0xd8, 0xf3, 0xe2, 0xa4, 0xec, \ + 0x01, 0x77, 0x7a, 0x74, 0xa8, 0xc4, 0x12, 0x43, \ + 0xc3, 0x1e, 0xce, 0x20, 0x8f, 0x2d, 0x0f, 0x6e, \ + 0x00, 0x91, 0x00, 0x03, 0x0d, 0xbc, 0x61, 0x9b, \ + 0xe1, 0x84, 0xa1, 0x72, 0xf6, 0xa9, 0xac, 0xcb, \ + 0xf8, 0x73, 0x6d, 0x5b, 0xe2, 0x98, 0xb3, 0x6b, \ + 0xec, 0xe7, 0x1e, 0x77, 0x8d, 0x0a, 0x69, 0xaa, \ + 0xf9, 0x94, 0xb8, 0x63, 0x6d, 0xe8, 0xfa, 0xf6, \ + 0x2f, 0xd3, 0xce, 0x7f, 0x04, 0x4c, 0x32, 0x2c, \ + 0xf7, 0x26, 0x3e, 0x34, 0x99, 0xe6, 0xa5, 0xb2, \ + 0xb0, 0x2a, 0xbb, 0xad, 0x5b, 0xd9, 0xec, 0xe5, \ + 0x00, 0x91, 0x00, 0x03, 0x0e, 0xb0, 0x71, 0x4d, \ + 0x73, 0xbb, 0x94, 0x61, 0x49, 0x9c, 0x94, 0x2a, \ + 0x5f, 0x1d, 0xcc, 0xaf, 0x65, 0x03, 0x3b, 0x39, \ + 0x39, 0xd4, 0x47, 0xd9, 0xfc, 0xc4, 0x7b, 0x0b, \ + 0x16, 0xd8, 0xe9, 0x01, 0xfc, 0xec, 0x3f, 0x8c, \ + 0x1b, 0xc0, 0xc6, 0xac, 0x0b, 0x5d, 0x74, 0xc7, \ + 0xbb, 0x03, 0x05, 0x69, 0x17, 0xe9, 0x98, 0x1a, \ + 0x19, 0xb9, 0x09, 0x5c, 0xa1, 0xf4, 0xab, 0x9f, \ + 0x00, 0x91, 0x00, 0x03, 0x0f, 0x02, 0x7c, 0x28, \ + 0x0f, 0x8a, 0xf9, 0xed, 0x1d, 0x29, 0x3c, 0xf6, \ + 0xcc, 0x2f, 0x04, 0x6d, 0x9a, 0xd6, 0x62, 0xb4, \ + 0xa9, 0x6e, 0xb1, 0xca, 0xca, 0xac, 0x5e, 0x05, \ + 0x3e, 0x83, 0x91, 0x47, 0x7c, 0x1f, 0x8b, 0x60, \ + 0x01, 0xde, 0x65, 0x3a, 0xbf, 0xf2, 0xaa, 0xbb, \ + 0x55, 0x98, 0x86, 0x91, 0x7e, 0xad, 0x3b, 0x36, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_CREDMAN_META \ + 0x00, 0x12, 0x00, 0x04, 0x90, 0x00, 0x07, 0x00, \ + 0xa2, 0x01, 0x00, 0x02, 0x18, 0x19, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_CREDMAN_RPLIST \ + 0x00, 0x15, 0x00, 0x02, 0x90, 0x00, 0x37, 0x00, \ + 0xa3, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x6a, 0x79, \ + 0x75, 0x62, 0x69, 0x63, 0x6f, 0x2e, 0x63, 0x6f, \ + 0x6d, 0x04, 0x58, 0x20, 0x37, 0x82, 0x09, 0xb7, \ + 0x2d, 0xef, 0xcb, 0xa9, 0x1d, 0xcb, 0xf8, 0x54, \ + 0xed, 0xb4, 0xda, 0xa6, 0x48, 0x82, 0x8a, 0x2c, \ + 0xbd, 0x18, 0x0a, 0xfc, 0x77, 0xa7, 0x44, 0x34, \ + 0x65, 0x5a, 0x1c, 0x7d, 0x05, 0x03, 0x00, 0x00, \ + 0x00, 0x15, 0x00, 0x02, 0x90, 0x00, 0x36, 0x00, \ + 0xa2, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x6b, 0x79, \ + 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x2e, 0x6f, \ + 0x72, 0x67, 0x04, 0x58, 0x20, 0x12, 0x6b, 0xba, \ + 0x6a, 0x2d, 0x7a, 0x81, 0x84, 0x25, 0x7b, 0x74, \ + 0xdd, 0x1d, 0xdd, 0x46, 0xb6, 0x2a, 0x8c, 0xa2, \ + 0xa7, 0x83, 0xfe, 0xdb, 0x5b, 0x19, 0x48, 0x73, \ + 0x55, 0xb7, 0xe3, 0x46, 0x09, 0x00, 0x00, 0x00, \ + 0x00, 0x15, 0x00, 0x02, 0x90, 0x00, 0x37, 0x00, \ + 0xa2, 0x03, 0xa1, 0x62, 0x69, 0x64, 0x6c, 0x77, \ + 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, \ + 0x64, 0x65, 0x76, 0x04, 0x58, 0x20, 0xd6, 0x32, \ + 0x7d, 0x8c, 0x6a, 0x5d, 0xe6, 0xae, 0x0e, 0x33, \ + 0xd0, 0xa3, 0x31, 0xfb, 0x67, 0x77, 0xb9, 0x4e, \ + 0xf4, 0x73, 0x19, 0xfe, 0x7e, 0xfd, 0xfa, 0x82, \ + 0x70, 0x8e, 0x1f, 0xbb, 0xa2, 0x55, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_CREDMAN_RKLIST \ + 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xc5, 0x00, \ + 0xa5, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \ + 0xe4, 0xe1, 0x06, 0x31, 0xde, 0x00, 0x0f, 0x4f, \ + 0x12, 0x6e, 0xc9, 0x68, 0x2d, 0x43, 0x3f, 0xf1, \ + 0x02, 0x2c, 0x6e, 0xe6, 0x96, 0x10, 0xbf, 0x73, \ + 0x35, 0xc9, 0x20, 0x27, 0x06, 0xba, 0x39, 0x09, \ + 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \ + 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \ + 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \ + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \ + 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \ + 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x19, \ + 0xf7, 0x78, 0x0c, 0xa0, 0xbc, 0xb9, 0xa6, 0xd5, \ + 0x1e, 0xd7, 0x87, 0xfb, 0x6c, 0x80, 0x03, 0x64, \ + 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \ + 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \ + 0x00, 0x15, 0x00, 0x04, 0x01, 0xa5, 0x01, 0x02, \ + 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x81, \ + 0x6c, 0xdd, 0x8c, 0x8f, 0x8c, 0xc8, 0x43, 0xa7, \ + 0xbb, 0x79, 0x51, 0x09, 0xb1, 0xdf, 0xbe, 0xc4, \ + 0xa5, 0x54, 0x16, 0x9e, 0x58, 0x56, 0xb3, 0x0b, \ + 0x34, 0x4f, 0xa5, 0x6c, 0x05, 0xa2, 0x21, 0x22, \ + 0x58, 0x20, 0xcd, 0xc2, 0x0c, 0x99, 0x83, 0x5a, \ + 0x61, 0x73, 0xd8, 0xe0, 0x74, 0x23, 0x46, 0x64, \ + 0x00, 0x15, 0x00, 0x04, 0x02, 0x39, 0x4c, 0xb0, \ + 0xf4, 0x6c, 0x0a, 0x37, 0x72, 0xaa, 0xa8, 0xea, \ + 0x58, 0xd3, 0xd4, 0xe0, 0x51, 0xb2, 0x28, 0x09, \ + 0x05, 0x0a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xa0, 0x00, \ + 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \ + 0x56, 0xa1, 0x3c, 0x06, 0x2b, 0xad, 0xa2, 0x21, \ + 0x7d, 0xcd, 0x91, 0x08, 0x47, 0xa8, 0x8a, 0x06, \ + 0x06, 0xf6, 0x66, 0x91, 0xf6, 0xeb, 0x89, 0xe4, \ + 0xdf, 0x26, 0xbc, 0x46, 0x59, 0xc3, 0x7d, 0xc0, \ + 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \ + 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \ + 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \ + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \ + 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \ + 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0xd8, \ + 0x27, 0x4b, 0x25, 0xed, 0x19, 0xef, 0x11, 0xaf, \ + 0xa6, 0x89, 0x7b, 0x84, 0x50, 0xe7, 0x62, 0x64, \ + 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \ + 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \ + 0x00, 0x15, 0x00, 0x04, 0x01, 0xa4, 0x01, 0x01, \ + 0x03, 0x27, 0x20, 0x06, 0x21, 0x58, 0x20, 0x8d, \ + 0xfe, 0x45, 0xd5, 0x7d, 0xb6, 0x17, 0xab, 0x86, \ + 0x2d, 0x32, 0xf6, 0x85, 0xf0, 0x92, 0x76, 0xb7, \ + 0xce, 0x73, 0xca, 0x4e, 0x0e, 0xfd, 0xd5, 0xdb, \ + 0x2a, 0x1d, 0x55, 0x90, 0x96, 0x52, 0xc2, 0x0a, \ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xa0, 0x00, \ + 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \ + 0x04, 0x0e, 0x0f, 0xa0, 0xcd, 0x60, 0x35, 0x9a, \ + 0xba, 0x47, 0x0c, 0x10, 0xb6, 0x82, 0x6e, 0x2f, \ + 0x66, 0xb9, 0xa7, 0xcf, 0xd8, 0x47, 0xb4, 0x3d, \ + 0xfd, 0x77, 0x1a, 0x38, 0x22, 0xa1, 0xda, 0xa5, \ + 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \ + 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \ + 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \ + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \ + 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \ + 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x00, \ + 0x5d, 0xdf, 0xef, 0xe2, 0xf3, 0x06, 0xb2, 0xa5, \ + 0x46, 0x4d, 0x98, 0xbc, 0x14, 0x65, 0xc1, 0x64, \ + 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \ + 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \ + 0x00, 0x15, 0x00, 0x04, 0x01, 0xa4, 0x01, 0x01, \ + 0x03, 0x27, 0x20, 0x06, 0x21, 0x58, 0x20, 0x72, \ + 0x79, 0x14, 0x69, 0xdf, 0xcb, 0x64, 0x75, 0xee, \ + 0xd4, 0x45, 0x94, 0xbc, 0x48, 0x4d, 0x2a, 0x9f, \ + 0xc9, 0xf4, 0xb5, 0x1b, 0x05, 0xa6, 0x5b, 0x54, \ + 0x9a, 0xac, 0x6c, 0x2e, 0xc6, 0x90, 0x62, 0x0a, \ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xc3, 0x00, \ + 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \ + 0xce, 0x32, 0xd8, 0x79, 0xdd, 0x86, 0xa2, 0x42, \ + 0x7c, 0xc3, 0xe1, 0x95, 0x12, 0x93, 0x1a, 0x03, \ + 0xe6, 0x70, 0xb8, 0xff, 0xcd, 0xa5, 0xdf, 0x15, \ + 0xfc, 0x88, 0x2a, 0xf5, 0x44, 0xf1, 0x33, 0x9c, \ + 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \ + 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \ + 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \ + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \ + 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \ + 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0x0a, \ + 0x26, 0x5b, 0x7e, 0x1a, 0x2a, 0xba, 0x70, 0x5f, \ + 0x18, 0x26, 0x14, 0xb2, 0x71, 0xca, 0x98, 0x64, \ + 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \ + 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \ + 0x00, 0x15, 0x00, 0x04, 0x01, 0xa5, 0x01, 0x02, \ + 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0x8b, \ + 0x48, 0xf0, 0x69, 0xfb, 0x22, 0xfb, 0xf3, 0x86, \ + 0x57, 0x7c, 0xdd, 0x82, 0x2c, 0x1c, 0x0c, 0xdc, \ + 0x27, 0xe2, 0x6a, 0x4c, 0x1a, 0x10, 0x04, 0x27, \ + 0x51, 0x3e, 0x2a, 0x9d, 0x3a, 0xb6, 0xb5, 0x22, \ + 0x58, 0x20, 0x70, 0xfe, 0x91, 0x67, 0x64, 0x53, \ + 0x63, 0x83, 0x72, 0x31, 0xe9, 0xe5, 0x20, 0xb7, \ + 0x00, 0x15, 0x00, 0x04, 0x02, 0xee, 0xc9, 0xfb, \ + 0x63, 0xd7, 0xe4, 0x76, 0x39, 0x80, 0x82, 0x74, \ + 0xb8, 0xfa, 0x67, 0xf5, 0x1b, 0x8f, 0xe0, 0x0a, \ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x15, 0x00, 0x04, 0x90, 0x00, 0xc3, 0x00, \ + 0xa4, 0x06, 0xa3, 0x62, 0x69, 0x64, 0x58, 0x20, \ + 0xf9, 0xa3, 0x67, 0xbf, 0x5e, 0x80, 0x95, 0xdb, \ + 0x4c, 0xc5, 0x8f, 0x65, 0x36, 0xc5, 0xaf, 0xdd, \ + 0x90, 0x2e, 0x62, 0x68, 0x67, 0x9c, 0xa2, 0x26, \ + 0x2f, 0x2a, 0xf9, 0x3a, 0xda, 0x15, 0xf2, 0x27, \ + 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x62, 0x6f, \ + 0x62, 0x20, 0x62, 0x61, 0x6e, 0x61, 0x6e, 0x61, \ + 0x00, 0x15, 0x00, 0x04, 0x00, 0x6b, 0x64, 0x69, \ + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, \ + 0x65, 0x67, 0x62, 0x62, 0x61, 0x6e, 0x61, 0x6e, \ + 0x61, 0x07, 0xa2, 0x62, 0x69, 0x64, 0x50, 0xfb, \ + 0xa6, 0xbe, 0xc1, 0x01, 0xf6, 0x7a, 0x81, 0xf9, \ + 0xcd, 0x6d, 0x20, 0x41, 0x7a, 0x1c, 0x40, 0x64, \ + 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, \ + 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x08, \ + 0x00, 0x15, 0x00, 0x04, 0x01, 0xa5, 0x01, 0x02, \ + 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20, 0xda, \ + 0x2b, 0x53, 0xc3, 0xbe, 0x48, 0xf8, 0xab, 0xbd, \ + 0x06, 0x28, 0x46, 0xfa, 0x35, 0xab, 0xf9, 0xc5, \ + 0x2e, 0xfd, 0x3c, 0x38, 0x88, 0xb3, 0xe1, 0xa7, \ + 0xc5, 0xc6, 0xed, 0x72, 0x54, 0x37, 0x93, 0x22, \ + 0x58, 0x20, 0x12, 0x82, 0x32, 0x2d, 0xab, 0xbc, \ + 0x64, 0xb3, 0xed, 0xcc, 0xd5, 0x22, 0xec, 0x79, \ + 0x00, 0x15, 0x00, 0x04, 0x02, 0x4b, 0xe2, 0x4d, \ + 0x0c, 0x4b, 0x8d, 0x31, 0x4c, 0xb4, 0x0f, 0xd4, \ + 0xa9, 0xbe, 0x0c, 0xab, 0x9e, 0x0a, 0xc9, 0x0a, \ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_BIO_INFO \ + 0x00, 0x10, 0x00, 0x04, 0x90, 0x00, 0x06, 0x00, \ + 0xa2, 0x02, 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_BIO_ENROLL \ + 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0x90, 0x00, 0x0a, 0x00, \ + 0xa3, 0x04, 0x42, 0x68, 0x96, 0x05, 0x00, 0x06, \ + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0x90, 0x00, 0x06, 0x00, \ + 0xa2, 0x05, 0x00, 0x06, 0x01, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0xbb, 0x00, 0x01, 0x02, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x0a, 0x00, 0x05, 0x90, 0x00, 0x06, 0x00, \ + 0xa2, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_BIO_ENUM \ + 0x00, 0x10, 0x00, 0x0f, 0x90, 0x00, 0x2e, 0x00, \ + 0xa1, 0x07, 0x83, 0xa2, 0x01, 0x42, 0xce, 0xa3, \ + 0x02, 0x67, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, \ + 0x31, 0xa2, 0x01, 0x42, 0xbf, 0x5e, 0x02, 0x67, \ + 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x32, 0xa2, \ + 0x01, 0x42, 0x5e, 0xd2, 0x02, 0x67, 0x66, 0x69, \ + 0x6e, 0x67, 0x65, 0x72, 0x33, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_CBOR_LARGEBLOB_GET_ARRAY \ + 0x89, 0xc9, 0x8d, 0x28, 0x90, 0x01, 0xe6, 0x00, \ + 0xa1, 0x01, 0x59, 0x01, 0xe0, 0x81, 0xa3, 0x01, \ + 0x59, 0x01, 0xb8, 0xb3, 0x26, 0x24, 0x99, 0xde, \ + 0x06, 0x3f, 0xca, 0xde, 0x98, 0x8d, 0x9d, 0xc5, \ + 0x3f, 0x26, 0x6c, 0xc7, 0x40, 0x93, 0xc4, 0x88, \ + 0x06, 0x51, 0x4f, 0xb9, 0x61, 0xf2, 0xc9, 0x8d, \ + 0xbc, 0xce, 0x79, 0x08, 0xec, 0x90, 0xc5, 0x5b, \ + 0xe5, 0x0a, 0x72, 0x08, 0x7b, 0xe1, 0xf9, 0x16, \ + 0x89, 0xc9, 0x8d, 0x28, 0x00, 0x06, 0x8b, 0x76, \ + 0x32, 0xa0, 0xae, 0x55, 0xb2, 0x39, 0x71, 0xce, \ + 0x34, 0x4b, 0x6e, 0x6b, 0x89, 0xa6, 0x5e, 0x69, \ + 0x07, 0xac, 0xf6, 0x01, 0x3c, 0xba, 0x45, 0x7a, \ + 0x75, 0x25, 0x3a, 0xbd, 0x95, 0x22, 0x9d, 0xc3, \ + 0xe4, 0x42, 0x31, 0x5c, 0xb5, 0xf4, 0x64, 0x6a, \ + 0x56, 0x1d, 0xab, 0xc7, 0x6e, 0x96, 0x75, 0xe7, \ + 0xb3, 0x22, 0x0b, 0x82, 0xac, 0x57, 0x78, 0xdf, \ + 0x89, 0xc9, 0x8d, 0x28, 0x01, 0x57, 0x06, 0xc5, \ + 0x4b, 0x61, 0x0b, 0x4d, 0xa1, 0x66, 0xa0, 0x89, \ + 0xad, 0x19, 0x8f, 0xd8, 0x96, 0x55, 0x22, 0x5f, \ + 0xca, 0x2e, 0xc1, 0xd7, 0xbd, 0xa1, 0x83, 0x66, \ + 0x4d, 0x85, 0xcb, 0x01, 0x60, 0x3f, 0xf7, 0xf7, \ + 0xa3, 0x7a, 0xfa, 0x99, 0xa0, 0x1e, 0x25, 0x90, \ + 0xd0, 0xd0, 0x3b, 0x54, 0x90, 0x77, 0x94, 0xa6, \ + 0x88, 0xea, 0xc3, 0x6b, 0xa0, 0x59, 0x5e, 0x69, \ + 0x89, 0xc9, 0x8d, 0x28, 0x02, 0x78, 0x0b, 0x2b, \ + 0xab, 0x5b, 0x04, 0x2f, 0x78, 0x15, 0x86, 0x2b, \ + 0x0f, 0x63, 0xb2, 0xd7, 0xc9, 0xe9, 0xac, 0x0e, \ + 0xbc, 0x17, 0xe4, 0x19, 0x88, 0xe0, 0xe6, 0x13, \ + 0xf8, 0x15, 0x08, 0xa7, 0xe1, 0x6e, 0x71, 0x5c, \ + 0xef, 0x3e, 0xc1, 0x0f, 0x74, 0xdb, 0xdc, 0x52, \ + 0x9c, 0xfc, 0xe9, 0xa9, 0xf3, 0x0d, 0x52, 0xbc, \ + 0x0c, 0xe8, 0xba, 0xd1, 0x76, 0x46, 0x87, 0xb5, \ + 0x89, 0xc9, 0x8d, 0x28, 0x03, 0x30, 0xe6, 0x9d, \ + 0xa1, 0x2b, 0xa5, 0x9e, 0x3b, 0x86, 0xb3, 0x5f, \ + 0xe3, 0x81, 0xa6, 0x76, 0x32, 0x9d, 0xf9, 0xc5, \ + 0x07, 0x93, 0xb3, 0xdf, 0x64, 0xe2, 0x78, 0x9c, \ + 0x00, 0xc7, 0x86, 0x79, 0xd6, 0x67, 0xa2, 0xfb, \ + 0xf2, 0x8d, 0xea, 0xe9, 0xc8, 0xfc, 0x43, 0xd2, \ + 0x0f, 0x2f, 0x7d, 0x9d, 0xd3, 0x8f, 0x9c, 0xdd, \ + 0xa2, 0x9f, 0x42, 0x76, 0x40, 0xcc, 0x4a, 0xd0, \ + 0x89, 0xc9, 0x8d, 0x28, 0x04, 0xb4, 0x87, 0x18, \ + 0x06, 0xc3, 0xc7, 0x89, 0x98, 0x72, 0xcc, 0x1a, \ + 0xd1, 0xd8, 0x78, 0xb9, 0x75, 0x0b, 0x92, 0xe3, \ + 0xcc, 0xed, 0x38, 0x39, 0x4b, 0xa9, 0xcf, 0x30, \ + 0xd6, 0xb5, 0xa1, 0x3f, 0xfa, 0x4f, 0x29, 0x99, \ + 0xa9, 0x03, 0x77, 0xf6, 0x53, 0xfa, 0xd8, 0x32, \ + 0xce, 0xf4, 0xf6, 0x0a, 0x3c, 0xe8, 0x9c, 0x3d, \ + 0xaa, 0xe0, 0x7b, 0x2c, 0xa5, 0x28, 0xe1, 0xdd, \ + 0x89, 0xc9, 0x8d, 0x28, 0x05, 0x51, 0xbf, 0xe1, \ + 0xd4, 0xf5, 0x5e, 0x38, 0x2c, 0xec, 0xab, 0xdd, \ + 0xb8, 0x5c, 0x13, 0x43, 0x62, 0xc2, 0xb6, 0x02, \ + 0x18, 0xce, 0x9a, 0x62, 0x67, 0x6a, 0xeb, 0x99, \ + 0xf6, 0x2f, 0xf1, 0xf1, 0xec, 0x3e, 0x74, 0xfa, \ + 0xf8, 0x16, 0x43, 0xea, 0x1e, 0xef, 0x5d, 0x37, \ + 0x6c, 0x13, 0xf9, 0x7f, 0x65, 0x09, 0xab, 0x60, \ + 0x38, 0xda, 0x0f, 0xe7, 0xfa, 0x9e, 0x17, 0x10, \ + 0x89, 0xc9, 0x8d, 0x28, 0x06, 0xdc, 0x4c, 0x4d, \ + 0xae, 0x5c, 0xb4, 0x0d, 0x6b, 0x05, 0x6d, 0x25, \ + 0x3f, 0x78, 0x5d, 0xf3, 0x34, 0x33, 0xa4, 0x89, \ + 0x34, 0x0e, 0x88, 0x66, 0x40, 0x57, 0x6b, 0x34, \ + 0x83, 0xfd, 0x39, 0xe7, 0xfb, 0x84, 0x09, 0xb3, \ + 0x16, 0x8f, 0x80, 0xdf, 0x1b, 0xe0, 0x02, 0x4c, \ + 0xde, 0x31, 0x2a, 0x32, 0x58, 0x5b, 0xa3, 0x23, \ + 0x8e, 0x2a, 0xa6, 0xaf, 0x03, 0x19, 0x02, 0x7a, \ + 0x89, 0xc9, 0x8d, 0x28, 0x07, 0xf8, 0xbf, 0xa6, \ + 0xad, 0xf9, 0xd1, 0xdc, 0xbd, 0x6e, 0xb3, 0xc1, \ + 0xfb, 0x65, 0xd8, 0x5f, 0x2e, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_NFC_INIT \ + 0x55, 0x32, 0x46, 0x5f, 0x56, 0x32, 0x90, 0x00 + +#define WIREDATA_CTAP_NFC_MSG \ + 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00 + +#define WIREDATA_CTAP_EXTENDED_APDU \ + 0x00, 0xa4, 0x04, 0x00, 0x00, 0x02, 0x00, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, \ + 0x00 + +#endif /* _WIREDATA_FIDO2_H */ diff --git a/fuzz/wiredata_u2f.h b/fuzz/wiredata_u2f.h new file mode 100644 index 0000000..3be22d3 --- /dev/null +++ b/fuzz/wiredata_u2f.h @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _WIREDATA_U2F_H +#define _WIREDATA_U2F_H + +#define WIREDATA_CTAP_U2F_6985 \ + 0x00, 0x00, 0x99, 0x01, 0x83, 0x00, 0x02, 0x69, \ + 0x85, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_U2F_AUTH \ + 0x00, 0x00, 0x99, 0x01, 0x83, 0x00, 0x4e, 0x01, \ + 0x00, 0x00, 0x00, 0x2c, 0x30, 0x45, 0x02, 0x20, \ + 0x1c, 0xf5, 0x7c, 0xf6, 0xde, 0xbe, 0xe9, 0x86, \ + 0xee, 0x97, 0xb7, 0x64, 0xa3, 0x4e, 0x7a, 0x70, \ + 0x85, 0xd0, 0x66, 0xf9, 0xf0, 0xcd, 0x04, 0x5d, \ + 0x97, 0xf2, 0x3c, 0x22, 0xe3, 0x0e, 0x61, 0xc8, \ + 0x02, 0x21, 0x00, 0x97, 0xef, 0xae, 0x36, 0xe6, \ + 0x17, 0x9f, 0x5e, 0x2d, 0xd7, 0x8c, 0x34, 0xa7, \ + 0x00, 0x00, 0x99, 0x01, 0x00, 0xa1, 0xe9, 0xfb, \ + 0x8f, 0x86, 0x8c, 0xe3, 0x1e, 0xde, 0x3f, 0x4e, \ + 0x1b, 0xe1, 0x2f, 0x8f, 0x2f, 0xca, 0x42, 0x26, \ + 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#define WIREDATA_CTAP_U2F_REGISTER \ + 0x00, 0x00, 0x99, 0x01, 0x83, 0x03, 0x1e, 0x05, \ + 0x04, 0x9f, 0xa0, 0xf9, 0x0d, 0x4c, 0xf4, 0xae, \ + 0x96, 0x3c, 0xb7, 0x46, 0xb7, 0x5c, 0x9d, 0x8b, \ + 0x48, 0x19, 0xdf, 0xc4, 0xad, 0xea, 0xb2, 0x70, \ + 0x58, 0x72, 0xd9, 0xce, 0x75, 0xf5, 0xe6, 0x8e, \ + 0x0f, 0x9c, 0x0e, 0x2e, 0x62, 0x3e, 0x91, 0xd3, \ + 0x7b, 0x97, 0x46, 0x60, 0xb9, 0x57, 0x13, 0x97, \ + 0x26, 0xae, 0x0f, 0xb3, 0x8f, 0x2e, 0x9b, 0x3f, \ + 0x00, 0x00, 0x99, 0x01, 0x00, 0xa5, 0x55, 0xec, \ + 0x8c, 0x25, 0x7c, 0x65, 0xb7, 0x09, 0x40, 0x48, \ + 0xae, 0xa8, 0xcb, 0xa1, 0x91, 0xac, 0x40, 0x24, \ + 0xf2, 0x34, 0x6e, 0x3a, 0x8f, 0xa5, 0xb7, 0x48, \ + 0x54, 0x6e, 0xfb, 0xf4, 0x37, 0x88, 0x69, 0x79, \ + 0x6f, 0x12, 0xc1, 0x32, 0xdf, 0x15, 0x5d, 0x6e, \ + 0x82, 0x54, 0xc0, 0x6e, 0x56, 0x4f, 0x3a, 0x9c, \ + 0xc3, 0x96, 0x7a, 0xde, 0xa5, 0xfe, 0xec, 0xd1, \ + 0x00, 0x00, 0x99, 0x01, 0x01, 0x5a, 0x21, 0x85, \ + 0x0e, 0x25, 0x7b, 0x8d, 0x6e, 0x1d, 0x32, 0x29, \ + 0xdb, 0x21, 0xb0, 0xa3, 0x30, 0x82, 0x02, 0x4f, \ + 0x30, 0x82, 0x01, 0x37, 0xa0, 0x03, 0x02, 0x01, \ + 0x02, 0x02, 0x04, 0x2a, 0xd9, 0x6a, 0xf3, 0x30, \ + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, \ + 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x2e, \ + 0x31, 0x2c, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x04, \ + 0x00, 0x00, 0x99, 0x01, 0x02, 0x03, 0x13, 0x23, \ + 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, \ + 0x32, 0x46, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, \ + 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, \ + 0x6c, 0x20, 0x34, 0x35, 0x37, 0x32, 0x30, 0x30, \ + 0x36, 0x33, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x31, \ + 0x34, 0x30, 0x38, 0x30, 0x31, 0x30, 0x30, 0x30, \ + 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x32, 0x30, \ + 0x00, 0x00, 0x99, 0x01, 0x03, 0x35, 0x30, 0x30, \ + 0x39, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, \ + 0x30, 0x5a, 0x30, 0x31, 0x31, 0x2f, 0x30, 0x2d, \ + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x26, 0x59, \ + 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, \ + 0x46, 0x20, 0x45, 0x45, 0x20, 0x53, 0x65, 0x72, \ + 0x69, 0x61, 0x6c, 0x20, 0x32, 0x33, 0x39, 0x32, \ + 0x35, 0x37, 0x33, 0x34, 0x35, 0x31, 0x36, 0x35, \ + 0x00, 0x00, 0x99, 0x01, 0x04, 0x35, 0x30, 0x33, \ + 0x38, 0x37, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, \ + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, \ + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, \ + 0x07, 0x03, 0x42, 0x00, 0x04, 0x2f, 0xe1, 0xa2, \ + 0x3e, 0xbf, 0xa5, 0x5b, 0x3e, 0x46, 0x1d, 0x59, \ + 0xa4, 0x35, 0x22, 0xd7, 0x97, 0x48, 0x98, 0x1c, \ + 0xba, 0x6d, 0x28, 0x9a, 0x98, 0xf1, 0xbd, 0x7d, \ + 0x00, 0x00, 0x99, 0x01, 0x05, 0xff, 0x65, 0x66, \ + 0x80, 0xdb, 0xbb, 0xed, 0xbc, 0x2b, 0xae, 0x60, \ + 0x7e, 0x6e, 0xf7, 0x72, 0xf5, 0x76, 0xb0, 0x4d, \ + 0x54, 0xc4, 0xe5, 0xf3, 0x2f, 0x59, 0x6f, 0x26, \ + 0xe6, 0x11, 0x15, 0xc7, 0x27, 0x2c, 0xf6, 0xca, \ + 0x75, 0x94, 0xa3, 0x3b, 0x30, 0x39, 0x30, 0x22, \ + 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, \ + 0xc4, 0x0a, 0x02, 0x04, 0x15, 0x31, 0x2e, 0x33, \ + 0x00, 0x00, 0x99, 0x01, 0x06, 0x2e, 0x36, 0x2e, \ + 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x31, \ + 0x34, 0x38, 0x32, 0x2e, 0x31, 0x2e, 0x32, 0x30, \ + 0x13, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, \ + 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01, 0x04, 0x04, \ + 0x03, 0x02, 0x04, 0x30, 0x30, 0x0d, 0x06, 0x09, \ + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, \ + 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, \ + 0x00, 0x00, 0x99, 0x01, 0x07, 0x85, 0x6a, 0xfa, \ + 0x8b, 0xcf, 0x4f, 0x3f, 0x62, 0x5f, 0x29, 0x1b, \ + 0xc1, 0x15, 0x8e, 0x3c, 0x7e, 0xbd, 0x25, 0x52, \ + 0xbc, 0xf7, 0x57, 0x07, 0x53, 0xf5, 0x12, 0x1d, \ + 0xa6, 0xa5, 0x4d, 0x24, 0xcc, 0xcf, 0xae, 0x27, \ + 0xce, 0xd6, 0xab, 0x31, 0x12, 0x8c, 0x29, 0x7e, \ + 0x5b, 0x5b, 0x89, 0x05, 0xdd, 0xa0, 0x20, 0x17, \ + 0x93, 0x1f, 0x1f, 0x5f, 0x59, 0x25, 0x93, 0x59, \ + 0x00, 0x00, 0x99, 0x01, 0x08, 0x51, 0xfc, 0x00, \ + 0x4b, 0xcb, 0xe2, 0x0a, 0xdd, 0x7d, 0x8d, 0x05, \ + 0x2f, 0x95, 0x43, 0xb3, 0x49, 0x6c, 0x15, 0xb8, \ + 0x31, 0x0e, 0x10, 0xcb, 0xd9, 0xbb, 0x05, 0x38, \ + 0x27, 0x4f, 0x58, 0x3e, 0xad, 0x1f, 0x45, 0x12, \ + 0x88, 0xc3, 0xea, 0x76, 0xd0, 0x70, 0xad, 0x44, \ + 0xe5, 0x3a, 0xfe, 0xa8, 0xf2, 0x2d, 0x1f, 0x73, \ + 0x62, 0x5f, 0xf2, 0xd5, 0x89, 0xfe, 0x30, 0xdf, \ + 0x00, 0x00, 0x99, 0x01, 0x09, 0x26, 0x62, 0xcb, \ + 0x7c, 0xbb, 0x7c, 0x99, 0x61, 0x80, 0xad, 0xcf, \ + 0xa9, 0x8a, 0x4d, 0x01, 0x2c, 0xf3, 0x13, 0x46, \ + 0xcd, 0x11, 0x74, 0x6a, 0x58, 0x48, 0xe8, 0xbe, \ + 0xed, 0xf3, 0xe3, 0x0c, 0xcb, 0xd9, 0xc1, 0xdd, \ + 0x22, 0x16, 0x71, 0xb2, 0x83, 0x88, 0x61, 0xf6, \ + 0x5a, 0x45, 0x36, 0x23, 0xb5, 0x18, 0xd5, 0x56, \ + 0x7f, 0xa8, 0xf0, 0xa3, 0xce, 0x10, 0x5d, 0xf4, \ + 0x00, 0x00, 0x99, 0x01, 0x0a, 0xf1, 0x39, 0x53, \ + 0xe1, 0x14, 0xea, 0x59, 0xe0, 0xa7, 0xf2, 0xfe, \ + 0x66, 0x88, 0x67, 0x43, 0x2e, 0x52, 0xfd, 0x6a, \ + 0x2f, 0x64, 0xf7, 0x3c, 0x48, 0xcd, 0x9b, 0x38, \ + 0xf2, 0xdf, 0xba, 0x2c, 0x7a, 0x4b, 0x3b, 0x11, \ + 0x28, 0xdf, 0x26, 0xd6, 0x6a, 0x24, 0xf8, 0x95, \ + 0xdd, 0xa0, 0xb6, 0x11, 0x80, 0xf4, 0x14, 0x4f, \ + 0x6b, 0x70, 0x75, 0xc3, 0x18, 0xa4, 0x9a, 0xe0, \ + 0x00, 0x00, 0x99, 0x01, 0x0b, 0x8b, 0x58, 0xd3, \ + 0x6a, 0xdb, 0x1e, 0x30, 0x53, 0x67, 0x2b, 0x17, \ + 0xc5, 0xa1, 0x9f, 0x7f, 0x0a, 0x22, 0xf1, 0x0e, \ + 0x94, 0x30, 0x44, 0x02, 0x20, 0x07, 0x5c, 0x4f, \ + 0xd2, 0x83, 0xb6, 0x9f, 0x0a, 0x4a, 0x4d, 0x4b, \ + 0x08, 0x35, 0xeb, 0xc0, 0x7e, 0x4a, 0x14, 0x2e, \ + 0xc7, 0x8c, 0xd6, 0x64, 0x2f, 0xd3, 0x1e, 0xcc, \ + 0xb5, 0xe8, 0x42, 0xea, 0xf6, 0x02, 0x20, 0x6b, \ + 0x00, 0x00, 0x99, 0x01, 0x0c, 0x5a, 0xba, 0x4a, \ + 0xc8, 0xd7, 0x89, 0xcc, 0x77, 0xe6, 0xb9, 0xa3, \ + 0x34, 0xea, 0x06, 0x85, 0x72, 0xc6, 0x28, 0xa8, \ + 0x7a, 0xaa, 0x19, 0x88, 0x34, 0xbb, 0xdc, 0x64, \ + 0x90, 0x0a, 0xdb, 0x39, 0x90, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + +#endif /* !_WIREDATA_U2F_H */ diff --git a/fuzz/wrap.c b/fuzz/wrap.c new file mode 100644 index 0000000..6f40ea1 --- /dev/null +++ b/fuzz/wrap.c @@ -0,0 +1,700 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/random.h> +#include <sys/socket.h> + +#include <openssl/bn.h> +#include <openssl/evp.h> +#include <openssl/sha.h> + +#include <cbor.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <zlib.h> + +#include "mutator_aux.h" + +extern int prng_up; + +int fuzz_save_corpus; + +/* + * Build wrappers around functions of interest, and have them fail + * in a pseudo-random manner. A uniform probability of 0.25% (1/400) + * allows for a depth of log(0.5)/log(399/400) > 276 operations + * before simulated errors become statistically more likely. + */ + +#define WRAP(type, name, args, retval, param, prob) \ +extern type __wrap_##name args; \ +extern type __real_##name args; \ +type __wrap_##name args { \ + if (prng_up && uniform_random(400) < (prob)) { \ + return (retval); \ + } \ + \ + return (__real_##name param); \ +} + +WRAP(void *, + malloc, + (size_t size), + NULL, + (size), + 1 +) + +WRAP(void *, + calloc, + (size_t nmemb, size_t size), + NULL, + (nmemb, size), + 1 +) + +WRAP(void *, + realloc, + (void *ptr, size_t size), + NULL, + (ptr, size), + 1 +) + +WRAP(char *, + strdup, + (const char *s), + NULL, + (s), + 1 +) + +WRAP(ssize_t, + getrandom, + (void *buf, size_t buflen, unsigned int flags), + -1, + (buf, buflen, flags), + 1 +) + +WRAP(int, + EVP_Cipher, + (EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in, + unsigned int inl), + -1, + (ctx, out, in, inl), + 1 +) + +WRAP(int, + EVP_CIPHER_CTX_ctrl, + (EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr), + 0, + (ctx, type, arg, ptr), + 1 +) + +WRAP(EVP_CIPHER_CTX *, + EVP_CIPHER_CTX_new, + (void), + NULL, + (), + 1 +) + +WRAP(int, + EVP_CipherInit, + (EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, + const unsigned char *key, const unsigned char *iv, int enc), + 0, + (ctx, cipher, key, iv, enc), + 1 +) + +WRAP(RSA *, + EVP_PKEY_get0_RSA, + (EVP_PKEY *pkey), + NULL, + (pkey), + 1 +) + +WRAP(EC_KEY *, + EVP_PKEY_get0_EC_KEY, + (EVP_PKEY *pkey), + NULL, + (pkey), + 1 +) + +WRAP(int, + EVP_PKEY_get_raw_public_key, + (const EVP_PKEY *pkey, unsigned char *pub, size_t *len), + 0, + (pkey, pub, len), + 1 +) + +WRAP(EVP_MD_CTX *, + EVP_MD_CTX_new, + (void), + NULL, + (), + 1 +) + +WRAP(int, + EVP_DigestVerifyInit, + (EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, const EVP_MD *type, ENGINE *e, + EVP_PKEY *pkey), + 0, + (ctx, pctx, type, e, pkey), + 1 +) + +WRAP(int, + EVP_DigestInit_ex, + (EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl), + 0, + (ctx, type, impl), + 1 +) + +WRAP(int, + EVP_DigestUpdate, + (EVP_MD_CTX *ctx, const void *data, size_t count), + 0, + (ctx, data, count), + 1 +) + +WRAP(int, + EVP_DigestFinal_ex, + (EVP_MD_CTX *ctx, unsigned char *md, unsigned int *isize), + 0, + (ctx, md, isize), + 1 +) + +WRAP(BIGNUM *, + BN_bin2bn, + (const unsigned char *s, int len, BIGNUM *ret), + NULL, + (s, len, ret), + 1 +) + +WRAP(int, + BN_bn2bin, + (const BIGNUM *a, unsigned char *to), + -1, + (a, to), + 1 +) + +WRAP(BIGNUM *, + BN_CTX_get, + (BN_CTX *ctx), + NULL, + (ctx), + 1 +) + +WRAP(BN_CTX *, + BN_CTX_new, + (void), + NULL, + (), + 1 +) + +WRAP(BIGNUM *, + BN_new, + (void), + NULL, + (), + 1 +) + +WRAP(RSA *, + RSA_new, + (void), + NULL, + (), + 1 +) + +WRAP(int, + RSA_set0_key, + (RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d), + 0, + (r, n, e, d), + 1 +) + +WRAP(int, + RSA_pkey_ctx_ctrl, + (EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void *p2), + -1, + (ctx, optype, cmd, p1, p2), + 1 +) + +WRAP(EC_KEY *, + EC_KEY_new_by_curve_name, + (int nid), + NULL, + (nid), + 1 +) + +WRAP(const EC_GROUP *, + EC_KEY_get0_group, + (const EC_KEY *key), + NULL, + (key), + 1 +) + +WRAP(const BIGNUM *, + EC_KEY_get0_private_key, + (const EC_KEY *key), + NULL, + (key), + 1 +) + +WRAP(EC_POINT *, + EC_POINT_new, + (const EC_GROUP *group), + NULL, + (group), + 1 +) + +WRAP(int, + EC_POINT_get_affine_coordinates_GFp, + (const EC_GROUP *group, const EC_POINT *p, BIGNUM *x, BIGNUM *y, BN_CTX *ctx), + 0, + (group, p, x, y, ctx), + 1 +) + +WRAP(EVP_PKEY *, + EVP_PKEY_new, + (void), + NULL, + (), + 1 +) + +WRAP(int, + EVP_PKEY_assign, + (EVP_PKEY *pkey, int type, void *key), + 0, + (pkey, type, key), + 1 +) + +WRAP(int, + EVP_PKEY_keygen_init, + (EVP_PKEY_CTX *ctx), + 0, + (ctx), + 1 +) + +WRAP(int, + EVP_PKEY_keygen, + (EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey), + 0, + (ctx, ppkey), + 1 +) + +WRAP(int, + EVP_PKEY_paramgen_init, + (EVP_PKEY_CTX *ctx), + 0, + (ctx), + 1 +) + +WRAP(int, + EVP_PKEY_paramgen, + (EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey), + 0, + (ctx, ppkey), + 1 +) + +WRAP(EVP_PKEY *, + EVP_PKEY_new_raw_public_key, + (int type, ENGINE *e, const unsigned char *key, size_t keylen), + NULL, + (type, e, key, keylen), + 1 +) + +WRAP(EVP_PKEY_CTX *, + EVP_PKEY_CTX_new, + (EVP_PKEY *pkey, ENGINE *e), + NULL, + (pkey, e), + 1 +) + +WRAP(EVP_PKEY_CTX *, + EVP_PKEY_CTX_new_id, + (int id, ENGINE *e), + NULL, + (id, e), + 1 +) + +WRAP(int, + EVP_PKEY_derive, + (EVP_PKEY_CTX *ctx, unsigned char *key, size_t *pkeylen), + 0, + (ctx, key, pkeylen), + 1 +) + +WRAP(int, + EVP_PKEY_derive_init, + (EVP_PKEY_CTX *ctx), + 0, + (ctx), + 1 +) + +WRAP(int, + EVP_PKEY_derive_set_peer, + (EVP_PKEY_CTX *ctx, EVP_PKEY *peer), + 0, + (ctx, peer), + 1 +) + +WRAP(int, + EVP_PKEY_verify_init, + (EVP_PKEY_CTX *ctx), + 0, + (ctx), + 1 +) + +WRAP(int, + EVP_PKEY_CTX_ctrl, + (EVP_PKEY_CTX *ctx, int keytype, int optype, int cmd, int p1, void *p2), + -1, + (ctx, keytype, optype, cmd, p1, p2), + 1 +) + +WRAP(const EVP_MD *, + EVP_sha1, + (void), + NULL, + (), + 1 +) + +WRAP(const EVP_MD *, + EVP_sha256, + (void), + NULL, + (), + 1 +) + +WRAP(const EVP_CIPHER *, + EVP_aes_256_cbc, + (void), + NULL, + (), + 1 +) + +WRAP(const EVP_CIPHER *, + EVP_aes_256_gcm, + (void), + NULL, + (), + 1 +) + +WRAP(unsigned char *, + HMAC, + (const EVP_MD *evp_md, const void *key, int key_len, + const unsigned char *d, int n, unsigned char *md, + unsigned int *md_len), + NULL, + (evp_md, key, key_len, d, n, md, md_len), + 1 +) + +WRAP(HMAC_CTX *, + HMAC_CTX_new, + (void), + NULL, + (), + 1 +) + +WRAP(int, + HMAC_Init_ex, + (HMAC_CTX *ctx, const void *key, int key_len, const EVP_MD *md, + ENGINE *impl), + 0, + (ctx, key, key_len, md, impl), + 1 +) + +WRAP(int, + HMAC_Update, + (HMAC_CTX *ctx, const unsigned char *data, int len), + 0, + (ctx, data, len), + 1 +) + +WRAP(int, + HMAC_Final, + (HMAC_CTX *ctx, unsigned char *md, unsigned int *len), + 0, + (ctx, md, len), + 1 +) + +WRAP(unsigned char *, + SHA1, + (const unsigned char *d, size_t n, unsigned char *md), + NULL, + (d, n, md), + 1 +) + +WRAP(unsigned char *, + SHA256, + (const unsigned char *d, size_t n, unsigned char *md), + NULL, + (d, n, md), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_string, + (const char *val), + NULL, + (val), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_bytestring, + (cbor_data handle, size_t length), + NULL, + (handle, length), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_bool, + (bool value), + NULL, + (value), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_negint8, + (uint8_t value), + NULL, + (value), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_negint16, + (uint16_t value), + NULL, + (value), + 1 +) + +WRAP(cbor_item_t *, + cbor_load, + (cbor_data source, size_t source_size, struct cbor_load_result *result), + NULL, + (source, source_size, result), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_uint8, + (uint8_t value), + NULL, + (value), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_uint16, + (uint16_t value), + NULL, + (value), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_uint32, + (uint32_t value), + NULL, + (value), + 1 +) + +WRAP(cbor_item_t *, + cbor_build_uint64, + (uint64_t value), + NULL, + (value), + 1 +) + +WRAP(struct cbor_pair *, + cbor_map_handle, + (const cbor_item_t *item), + NULL, + (item), + 1 +) + +WRAP(cbor_item_t **, + cbor_array_handle, + (const cbor_item_t *item), + NULL, + (item), + 1 +) + +WRAP(bool, + cbor_array_push, + (cbor_item_t *array, cbor_item_t *pushee), + false, + (array, pushee), + 1 +) + +WRAP(bool, + cbor_map_add, + (cbor_item_t *item, struct cbor_pair pair), + false, + (item, pair), + 1 +) + +WRAP(cbor_item_t *, + cbor_new_definite_map, + (size_t size), + NULL, + (size), + 1 +) + +WRAP(cbor_item_t *, + cbor_new_definite_array, + (size_t size), + NULL, + (size), + 1 +) + +WRAP(cbor_item_t *, + cbor_new_definite_bytestring, + (void), + NULL, + (), + 1 +) + +WRAP(size_t, + cbor_serialize_alloc, + (const cbor_item_t *item, cbor_mutable_data *buffer, + size_t *buffer_size), + 0, + (item, buffer, buffer_size), + 1 +) + +WRAP(int, + fido_tx, + (fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms), + -1, + (d, cmd, buf, count, ms), + 1 +) + +WRAP(int, + bind, + (int sockfd, const struct sockaddr *addr, socklen_t addrlen), + -1, + (sockfd, addr, addrlen), + 1 +) + +WRAP(int, + deflateInit2_, + (z_streamp strm, int level, int method, int windowBits, int memLevel, + int strategy, const char *version, int stream_size), + Z_STREAM_ERROR, + (strm, level, method, windowBits, memLevel, strategy, version, + stream_size), + 1 +) + +int __wrap_deflate(z_streamp, int); +int __real_deflate(z_streamp, int); + +int +__wrap_deflate(z_streamp strm, int flush) +{ + if (prng_up && uniform_random(400) < 1) { + return Z_BUF_ERROR; + } + /* should never happen, but we check for it */ + if (prng_up && uniform_random(400) < 1) { + strm->avail_out = UINT_MAX; + return Z_STREAM_END; + } + + return __real_deflate(strm, flush); +} + +int __wrap_asprintf(char **, const char *, ...); + +int +__wrap_asprintf(char **strp, const char *fmt, ...) +{ + va_list ap; + int r; + + if (prng_up && uniform_random(400) < 1) { + *strp = (void *)0xdeadbeef; + return -1; + } + + va_start(ap, fmt); + r = vasprintf(strp, fmt, ap); + va_end(ap); + + return r; +} diff --git a/fuzz/wrapped.sym b/fuzz/wrapped.sym new file mode 100644 index 0000000..219a0d8 --- /dev/null +++ b/fuzz/wrapped.sym @@ -0,0 +1,102 @@ +asprintf +bind +BN_bin2bn +BN_bn2bin +BN_CTX_get +BN_CTX_new +BN_new +calloc +cbor_array_handle +cbor_array_push +cbor_build_bool +cbor_build_bytestring +cbor_build_negint16 +cbor_build_negint8 +cbor_build_string +cbor_build_uint16 +cbor_build_uint32 +cbor_build_uint64 +cbor_build_uint8 +cbor_load +cbor_map_add +cbor_map_handle +cbor_new_definite_array +cbor_new_definite_bytestring +cbor_new_definite_map +cbor_serialize_alloc +clock_gettime +deflate +deflateInit2_ +EC_KEY_get0_group +EC_KEY_get0_private_key +EC_KEY_new_by_curve_name +EC_POINT_get_affine_coordinates_GFp +EC_POINT_new +EVP_aes_256_cbc +EVP_aes_256_gcm +EVP_Cipher +EVP_CIPHER_CTX_ctrl +EVP_CIPHER_CTX_new +EVP_CipherInit +EVP_DigestFinal_ex +EVP_DigestInit_ex +EVP_DigestUpdate +EVP_DigestVerifyInit +EVP_MD_CTX_new +EVP_PKEY_assign +EVP_PKEY_CTX_ctrl +EVP_PKEY_CTX_new +EVP_PKEY_CTX_new_id +EVP_PKEY_derive +EVP_PKEY_derive_init +EVP_PKEY_derive_set_peer +EVP_PKEY_get0_EC_KEY +EVP_PKEY_get0_RSA +EVP_PKEY_get_raw_public_key +EVP_PKEY_keygen +EVP_PKEY_keygen_init +EVP_PKEY_new +EVP_PKEY_new_raw_public_key +EVP_PKEY_paramgen +EVP_PKEY_paramgen_init +EVP_PKEY_verify_init +EVP_sha1 +EVP_sha256 +fido_tx +getrandom +HMAC +HMAC_CTX_new +HMAC_Final +HMAC_Init_ex +HMAC_Update +ioctl +malloc +realloc +RSA_new +RSA_pkey_ctx_ctrl +RSA_set0_key +SCardConnect +SCardDisconnect +SCardEstablishContext +SCardListReaders +SCardReleaseContext +SCardTransmit +SHA1 +SHA256 +strdup +udev_device_get_devnode +udev_device_get_parent_with_subsystem_devtype +udev_device_get_sysattr_value +udev_device_get_sysnum +udev_device_new_from_syspath +udev_device_unref +udev_enumerate_add_match_subsystem +udev_enumerate_get_list_entry +udev_enumerate_new +udev_enumerate_scan_devices +udev_enumerate_unref +udev_list_entry_get_name +udev_list_entry_get_next +udev_new +udev_unref +usleep diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt new file mode 100644 index 0000000..6616e4e --- /dev/null +++ b/man/CMakeLists.txt @@ -0,0 +1,413 @@ +# Copyright (c) 2018-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +find_program(MANDOC_PATH mandoc) +find_program(GZIP_PATH gzip) + +message(STATUS "MANDOC_PATH: ${MANDOC_PATH}") +message(STATUS "GZIP_PATH: ${GZIP_PATH}") + +list(APPEND MAN_SOURCES + eddsa_pk_new.3 + es256_pk_new.3 + es384_pk_new.3 + fido2-assert.1 + fido2-cred.1 + fido2-token.1 + fido_init.3 + fido_assert_new.3 + fido_assert_allow_cred.3 + fido_assert_set_authdata.3 + fido_assert_verify.3 + fido_bio_dev_get_info.3 + fido_bio_enroll_new.3 + fido_bio_info_new.3 + fido_bio_template.3 + fido_cbor_info_new.3 + fido_cred_new.3 + fido_cred_exclude.3 + fido_credman_metadata_new.3 + fido_cred_set_authdata.3 + fido_cred_verify.3 + fido_dev_enable_entattest.3 + fido_dev_get_assert.3 + fido_dev_get_touch_begin.3 + fido_dev_info_manifest.3 + fido_dev_largeblob_get.3 + fido_dev_make_cred.3 + fido_dev_open.3 + fido_dev_set_io_functions.3 + fido_dev_set_pin.3 + fido_strerr.3 + rs256_pk_new.3 +) + +list(APPEND MAN_ALIAS + eddsa_pk_new eddsa_pk_free + eddsa_pk_new eddsa_pk_from_EVP_PKEY + eddsa_pk_new eddsa_pk_from_ptr + eddsa_pk_new eddsa_pk_to_EVP_PKEY + es256_pk_new es256_pk_free + es256_pk_new es256_pk_from_EC_KEY + es256_pk_new es256_pk_from_EVP_PKEY + es256_pk_new es256_pk_from_ptr + es256_pk_new es256_pk_to_EVP_PKEY + es384_pk_new es384_pk_free + es384_pk_new es384_pk_from_EC_KEY + es384_pk_new es384_pk_from_EVP_PKEY + es384_pk_new es384_pk_from_ptr + es384_pk_new es384_pk_to_EVP_PKEY + fido_assert_allow_cred fido_assert_empty_allow_list + fido_assert_new fido_assert_authdata_len + fido_assert_new fido_assert_authdata_ptr + fido_assert_new fido_assert_authdata_raw_len + fido_assert_new fido_assert_authdata_raw_ptr + fido_assert_new fido_assert_blob_len + fido_assert_new fido_assert_blob_ptr + fido_assert_new fido_assert_clientdata_hash_len + fido_assert_new fido_assert_clientdata_hash_ptr + fido_assert_new fido_assert_count + fido_assert_new fido_assert_flags + fido_assert_new fido_assert_free + fido_assert_new fido_assert_hmac_secret_len + fido_assert_new fido_assert_hmac_secret_ptr + fido_assert_new fido_assert_id_len + fido_assert_new fido_assert_id_ptr + fido_assert_new fido_assert_largeblob_key_len + fido_assert_new fido_assert_largeblob_key_ptr + fido_assert_new fido_assert_rp_id + fido_assert_new fido_assert_sigcount + fido_assert_new fido_assert_sig_len + fido_assert_new fido_assert_sig_ptr + fido_assert_new fido_assert_user_display_name + fido_assert_new fido_assert_user_icon + fido_assert_new fido_assert_user_id_len + fido_assert_new fido_assert_user_id_ptr + fido_assert_new fido_assert_user_name + fido_assert_set_authdata fido_assert_set_authdata_raw + fido_assert_set_authdata fido_assert_set_clientdata + fido_assert_set_authdata fido_assert_set_clientdata_hash + fido_assert_set_authdata fido_assert_set_count + fido_assert_set_authdata fido_assert_set_extensions + fido_assert_set_authdata fido_assert_set_hmac_salt + fido_assert_set_authdata fido_assert_set_hmac_secret + fido_assert_set_authdata fido_assert_set_rp + fido_assert_set_authdata fido_assert_set_sig + fido_assert_set_authdata fido_assert_set_up + fido_assert_set_authdata fido_assert_set_uv + fido_assert_set_authdata fido_assert_set_winhello_appid + fido_bio_dev_get_info fido_bio_dev_enroll_begin + fido_bio_dev_get_info fido_bio_dev_enroll_cancel + fido_bio_dev_get_info fido_bio_dev_enroll_continue + fido_bio_dev_get_info fido_bio_dev_enroll_remove + fido_bio_dev_get_info fido_bio_dev_get_template_array + fido_bio_dev_get_info fido_bio_dev_set_template_name + fido_bio_enroll_new fido_bio_enroll_free + fido_bio_enroll_new fido_bio_enroll_last_status + fido_bio_enroll_new fido_bio_enroll_remaining_samples + fido_bio_info_new fido_bio_info_free + fido_bio_info_new fido_bio_info_max_samples + fido_bio_info_new fido_bio_info_type + fido_bio_template fido_bio_template_array_count + fido_bio_template fido_bio_template_array_free + fido_bio_template fido_bio_template_array_new + fido_bio_template fido_bio_template_free + fido_bio_template fido_bio_template_id_len + fido_bio_template fido_bio_template_id_ptr + fido_bio_template fido_bio_template_name + fido_bio_template fido_bio_template_new + fido_bio_template fido_bio_template_set_id + fido_bio_template fido_bio_template_set_name + fido_cbor_info_new fido_cbor_info_aaguid_len + fido_cbor_info_new fido_cbor_info_aaguid_ptr + fido_cbor_info_new fido_cbor_info_algorithm_cose + fido_cbor_info_new fido_cbor_info_algorithm_count + fido_cbor_info_new fido_cbor_info_algorithm_type + fido_cbor_info_new fido_cbor_info_certs_len + fido_cbor_info_new fido_cbor_info_certs_name_ptr + fido_cbor_info_new fido_cbor_info_certs_value_ptr + fido_cbor_info_new fido_cbor_info_extensions_len + fido_cbor_info_new fido_cbor_info_extensions_ptr + fido_cbor_info_new fido_cbor_info_free + fido_cbor_info_new fido_cbor_info_fwversion + fido_cbor_info_new fido_cbor_info_maxcredbloblen + fido_cbor_info_new fido_cbor_info_maxcredcntlst + fido_cbor_info_new fido_cbor_info_maxcredidlen + fido_cbor_info_new fido_cbor_info_maxlargeblob + fido_cbor_info_new fido_cbor_info_maxmsgsiz + fido_cbor_info_new fido_cbor_info_maxrpid_minpinlen + fido_cbor_info_new fido_cbor_info_minpinlen + fido_cbor_info_new fido_cbor_info_new_pin_required + fido_cbor_info_new fido_cbor_info_options_len + fido_cbor_info_new fido_cbor_info_options_name_ptr + fido_cbor_info_new fido_cbor_info_options_value_ptr + fido_cbor_info_new fido_cbor_info_protocols_len + fido_cbor_info_new fido_cbor_info_protocols_ptr + fido_cbor_info_new fido_cbor_info_rk_remaining + fido_cbor_info_new fido_cbor_info_transports_len + fido_cbor_info_new fido_cbor_info_transports_ptr + fido_cbor_info_new fido_cbor_info_uv_attempts + fido_cbor_info_new fido_cbor_info_uv_modality + fido_cbor_info_new fido_cbor_info_versions_len + fido_cbor_info_new fido_cbor_info_versions_ptr + fido_cbor_info_new fido_dev_get_cbor_info + fido_cred_exclude fido_cred_empty_exclude_list + fido_cred_new fido_cred_aaguid_len + fido_cred_new fido_cred_aaguid_ptr + fido_cred_new fido_cred_attstmt_len + fido_cred_new fido_cred_attstmt_ptr + fido_cred_new fido_cred_authdata_len + fido_cred_new fido_cred_authdata_ptr + fido_cred_new fido_cred_authdata_raw_len + fido_cred_new fido_cred_authdata_raw_ptr + fido_cred_new fido_cred_clientdata_hash_len + fido_cred_new fido_cred_clientdata_hash_ptr + fido_cred_new fido_cred_display_name + fido_cred_new fido_cred_flags + fido_cred_new fido_cred_fmt + fido_cred_new fido_cred_free + fido_cred_new fido_cred_id_len + fido_cred_new fido_cred_id_ptr + fido_cred_new fido_cred_largeblob_key_len + fido_cred_new fido_cred_largeblob_key_ptr + fido_cred_new fido_cred_pin_minlen + fido_cred_new fido_cred_prot + fido_cred_new fido_cred_pubkey_len + fido_cred_new fido_cred_pubkey_ptr + fido_cred_new fido_cred_rp_id + fido_cred_new fido_cred_rp_name + fido_cred_new fido_cred_sigcount + fido_cred_new fido_cred_sig_len + fido_cred_new fido_cred_sig_ptr + fido_cred_new fido_cred_type + fido_cred_new fido_cred_user_id_len + fido_cred_new fido_cred_user_id_ptr + fido_cred_new fido_cred_user_name + fido_cred_new fido_cred_x5c_len + fido_cred_new fido_cred_x5c_ptr + fido_cred_verify fido_cred_verify_self + fido_credman_metadata_new fido_credman_del_dev_rk + fido_credman_metadata_new fido_credman_get_dev_metadata + fido_credman_metadata_new fido_credman_get_dev_rk + fido_credman_metadata_new fido_credman_get_dev_rp + fido_credman_metadata_new fido_credman_metadata_free + fido_credman_metadata_new fido_credman_rk + fido_credman_metadata_new fido_credman_rk_count + fido_credman_metadata_new fido_credman_rk_existing + fido_credman_metadata_new fido_credman_rk_free + fido_credman_metadata_new fido_credman_rk_new + fido_credman_metadata_new fido_credman_rk_remaining + fido_credman_metadata_new fido_credman_rp_count + fido_credman_metadata_new fido_credman_rp_free + fido_credman_metadata_new fido_credman_rp_id + fido_credman_metadata_new fido_credman_rp_id_hash_len + fido_credman_metadata_new fido_credman_rp_id_hash_ptr + fido_credman_metadata_new fido_credman_rp_name + fido_credman_metadata_new fido_credman_rp_new + fido_credman_metadata_new fido_credman_set_dev_rk + fido_cred_set_authdata fido_cred_set_attstmt + fido_cred_set_authdata fido_cred_set_authdata_raw + fido_cred_set_authdata fido_cred_set_blob + fido_cred_set_authdata fido_cred_set_clientdata + fido_cred_set_authdata fido_cred_set_clientdata_hash + fido_cred_set_authdata fido_cred_set_extensions + fido_cred_set_authdata fido_cred_set_fmt + fido_cred_set_authdata fido_cred_set_id + fido_cred_set_authdata fido_cred_set_pin_minlen + fido_cred_set_authdata fido_cred_set_prot + fido_cred_set_authdata fido_cred_set_rk + fido_cred_set_authdata fido_cred_set_rp + fido_cred_set_authdata fido_cred_set_sig + fido_cred_set_authdata fido_cred_set_type + fido_cred_set_authdata fido_cred_set_user + fido_cred_set_authdata fido_cred_set_uv + fido_cred_set_authdata fido_cred_set_x509 + fido_dev_enable_entattest fido_dev_toggle_always_uv + fido_dev_enable_entattest fido_dev_force_pin_change + fido_dev_enable_entattest fido_dev_set_pin_minlen + fido_dev_enable_entattest fido_dev_set_pin_minlen_rpid + fido_dev_get_touch_begin fido_dev_get_touch_status + fido_dev_info_manifest fido_dev_info_free + fido_dev_info_manifest fido_dev_info_manufacturer_string + fido_dev_info_manifest fido_dev_info_new + fido_dev_info_manifest fido_dev_info_path + fido_dev_info_manifest fido_dev_info_product + fido_dev_info_manifest fido_dev_info_product_string + fido_dev_info_manifest fido_dev_info_ptr + fido_dev_info_manifest fido_dev_info_set + fido_dev_info_manifest fido_dev_info_vendor + fido_dev_open fido_dev_build + fido_dev_open fido_dev_cancel + fido_dev_open fido_dev_close + fido_dev_open fido_dev_flags + fido_dev_open fido_dev_force_fido2 + fido_dev_open fido_dev_force_u2f + fido_dev_open fido_dev_free + fido_dev_open fido_dev_has_pin + fido_dev_open fido_dev_has_uv + fido_dev_open fido_dev_is_fido2 + fido_dev_open fido_dev_is_winhello + fido_dev_open fido_dev_major + fido_dev_open fido_dev_minor + fido_dev_open fido_dev_new + fido_dev_open fido_dev_new_with_info + fido_dev_open fido_dev_open_with_info + fido_dev_open fido_dev_protocol + fido_dev_open fido_dev_supports_cred_prot + fido_dev_open fido_dev_supports_credman + fido_dev_open fido_dev_supports_permissions + fido_dev_open fido_dev_supports_pin + fido_dev_open fido_dev_supports_uv + fido_dev_set_pin fido_dev_get_retry_count + fido_dev_set_pin fido_dev_get_uv_retry_count + fido_dev_set_pin fido_dev_reset + fido_dev_set_io_functions fido_dev_io_handle + fido_dev_set_io_functions fido_dev_set_sigmask + fido_dev_set_io_functions fido_dev_set_timeout + fido_dev_set_io_functions fido_dev_set_transport_functions + fido_dev_largeblob_get fido_dev_largeblob_set + fido_dev_largeblob_get fido_dev_largeblob_remove + fido_dev_largeblob_get fido_dev_largeblob_get_array + fido_dev_largeblob_get fido_dev_largeblob_set_array + fido_init fido_set_log_handler + rs256_pk_new rs256_pk_free + rs256_pk_new rs256_pk_from_ptr + rs256_pk_new rs256_pk_from_EVP_PKEY + rs256_pk_new rs256_pk_from_RSA + rs256_pk_new rs256_pk_to_EVP_PKEY +) + +list(LENGTH MAN_ALIAS MAN_ALIAS_LEN) +math(EXPR MAN_ALIAS_MAX "${MAN_ALIAS_LEN} - 2") + +# man_copy +foreach(f ${MAN_SOURCES}) + add_custom_command(OUTPUT ${f} + COMMAND cp -f ${PROJECT_SOURCE_DIR}/man/${f} . + DEPENDS ${f}) + list(APPEND COPY_FILES ${f}) +endforeach() + +# man_lint +foreach(f ${MAN_SOURCES}) + add_custom_command(OUTPUT ${f}.lint + COMMAND mandoc -T lint -W warning ${f} > ${f}.lint + DEPENDS ${f}) + list(APPEND LINT_FILES ${f}.lint) +endforeach() + +# man_html +foreach(f ${MAN_SOURCES}) + string(REGEX REPLACE "\\.[13]$" "" g ${f}) + add_custom_command(OUTPUT ${g}.html + COMMAND mandoc -T html -O man="%N.html",style=style.css -I os="Yubico AB" ${f} > ${g}.html + DEPENDS ${f}) + list(APPEND HTML_FILES ${g}.html) +endforeach() + +# man_html_partial +foreach(f ${MAN_SOURCES}) + string(REGEX REPLACE "\\.[13]$" "" g ${f}) + add_custom_command(OUTPUT ${g}.partial + COMMAND cat ${PROJECT_SOURCE_DIR}/man/dyc.css > ${g}.partial + COMMAND mandoc -T html -O man="%N.html",fragment ${f} >> ${g}.partial + DEPENDS ${f}) + list(APPEND HTML_PARTIAL_FILES ${g}.partial) +endforeach() + +# man_gzip +foreach(f ${MAN_SOURCES}) + add_custom_command(OUTPUT ${f}.gz + COMMAND gzip -cn ${f} > ${f}.gz + DEPENDS ${f}) + list(APPEND GZ_FILES ${f}.gz) +endforeach() + +macro(define_symlink_target NAME EXT) + foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2) + math(EXPR j "${i} + 1") + list(GET MAN_ALIAS ${i} SRC) + list(GET MAN_ALIAS ${j} DST) + add_custom_command(OUTPUT ${DST}.${EXT} + COMMAND ln -sf ${SRC}.${EXT} ${DST}.${EXT}) + list(APPEND ${NAME}_LINK_FILES ${DST}.${EXT}) + endforeach() + add_custom_target(${NAME} DEPENDS ${${NAME}_LINK_FILES}) +endmacro() + +add_custom_target(man_copy DEPENDS ${COPY_FILES}) +add_custom_target(man_lint DEPENDS ${LINT_FILES}) +add_custom_target(man_html DEPENDS ${HTML_FILES}) +add_custom_target(man_html_partial DEPENDS ${HTML_PARTIAL_FILES}) +add_custom_target(man_gzip DEPENDS ${GZ_FILES}) + +define_symlink_target(man_symlink 3) +define_symlink_target(man_symlink_html html) +define_symlink_target(man_symlink_html_partial partial) +define_symlink_target(man_symlink_gzip 3.gz) + +add_dependencies(man_symlink man_copy) +add_dependencies(man_lint man_symlink) +add_dependencies(man_html man_lint) +add_dependencies(man_symlink_html man_html) +add_dependencies(man_html_partial man_lint) +add_dependencies(man_symlink_html_partial man_html_partial) +add_custom_target(man ALL) + +if(MANDOC_PATH) + add_dependencies(man man_symlink_html) + add_dependencies(man_gzip man_lint) + install(FILES ${PROJECT_SOURCE_DIR}/man/style.css + DESTINATION "${CMAKE_INSTALL_DOCDIR}/html") + foreach(f ${MAN_SOURCES}) + string(REGEX REPLACE "\\.[13]$" "" f ${f}) + install(FILES ${PROJECT_BINARY_DIR}/man/${f}.html + DESTINATION "${CMAKE_INSTALL_DOCDIR}/html") + endforeach() + foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2) + math(EXPR j "${i} + 1") + list(GET MAN_ALIAS ${j} DST) + install(FILES ${PROJECT_BINARY_DIR}/man/${DST}.html + DESTINATION "${CMAKE_INSTALL_DOCDIR}/html") + endforeach() +endif() + +if(GZIP_PATH) + add_dependencies(man_gzip man_copy) + add_dependencies(man_symlink_gzip man_gzip) + add_dependencies(man man_symlink_gzip) + foreach(f ${MAN_SOURCES}) + if (${f} MATCHES ".1$") + install(FILES ${PROJECT_BINARY_DIR}/man/${f}.gz + DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") + elseif(${f} MATCHES ".3$") + install(FILES ${PROJECT_BINARY_DIR}/man/${f}.gz + DESTINATION "${CMAKE_INSTALL_MANDIR}/man3") + endif() + endforeach() + foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2) + math(EXPR j "${i} + 1") + list(GET MAN_ALIAS ${j} DST) + install(FILES ${PROJECT_BINARY_DIR}/man/${DST}.3.gz + DESTINATION "${CMAKE_INSTALL_MANDIR}/man3") + endforeach() +elseif(NOT MSVC) + add_dependencies(man man_symlink) + foreach(f ${MAN_SOURCES}) + if (${f} MATCHES ".1$") + install(FILES ${PROJECT_BINARY_DIR}/man/${f} + DESTINATION "${CMAKE_INSTALL_MANDIR}/man1") + elseif(${f} MATCHES ".3$") + install(FILES ${PROJECT_BINARY_DIR}/man/${f} + DESTINATION "${CMAKE_INSTALL_MANDIR}/man3") + endif() + endforeach() + foreach(i RANGE 0 ${MAN_ALIAS_MAX} 2) + math(EXPR j "${i} + 1") + list(GET MAN_ALIAS ${j} DST) + install(FILES ${PROJECT_BINARY_DIR}/man/${DST}.3 + DESTINATION "${CMAKE_INSTALL_MANDIR}/man3") + endforeach() +endif() diff --git a/man/NOTES b/man/NOTES new file mode 100644 index 0000000..5cba436 --- /dev/null +++ b/man/NOTES @@ -0,0 +1,7 @@ +To generate .partial files for https://developers.yubico.com/: + +$ make -C build man_symlink_html_partial +$ (cd build/man && pax -p p -r -w *.partial /tmp/partial) + +Use mandoc 1.14.4. Otherwise, adjust dyc.css to mandoc's HTML +output. diff --git a/man/check.sh b/man/check.sh new file mode 100755 index 0000000..d969a7a --- /dev/null +++ b/man/check.sh @@ -0,0 +1,43 @@ +#!/bin/sh -u + +# Copyright (c) 2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +T=$(mktemp -d) || exit 1 +find . -maxdepth 1 -type f -name '*.3' -print0 > "$T/files" + +xargs -0 awk '/^.Sh NAME/,/^.Nd/' < "$T/files" | \ + awk '/^.Nm/ { print $2 }' | sort -u > "$T/Nm" +xargs -0 awk '/^.Fn/ { print $2 }' < "$T/files" | sort -u > "$T/Fn" +(cd "$T" && diff -u Nm Fn) + +cut -c2- ../src/export.llvm | sort > "$T/exports" +(cd "$T" && diff -u Nm exports) + +awk '/^list\(APPEND MAN_SOURCES/,/^\)/' CMakeLists.txt | \ + awk '/.3$/ { print $1 }' | sort > "$T/listed_sources" +xargs -0 -n1 basename < "$T/files" | sort > "$T/actual_sources" +(cd "$T" && diff -u listed_sources actual_sources) + +awk '/^list\(APPEND MAN_ALIAS/,/^\)/' CMakeLists.txt | \ + sed '1d;$d' | awk '{ print $1, $2 }' | sort > "$T/listed_aliases" +xargs -0 grep -o "^.Fn [A-Za-z0-9_]* \"" < "$T/files" | \ + cut -c3- | sed 's/\.3:\.Fn//;s/ "//' | awk '$1 != $2' | \ + sort > "$T/actual_aliases" +(cd "$T" && diff -u listed_aliases actual_aliases) + +xargs -0 grep -hB1 "^.Fn [A-Za-z0-9_]* \"" < "$T/files" | \ + sed -E 's/^.F[tn] //;s/\*[^"\*]+"/\*"/g;s/ [^" \*]+"/"/g;/^--$/d' | \ + paste -d " " - - | sed 's/\* /\*/' | sort > "$T/documented_prototypes" +while read -r f; do + awk "/\/\*/ { next } /$f\(/,/;/" ../src/fido.h ../src/fido/*.h | \ + sed -E 's/^[ ]+//;s/[ ]+/ /' | tr '\n' ' ' | \ + sed 's/(/ "/;s/, /" "/g;s/);/"/;s/ $/\n/' +done < "$T/exports" | sort > "$T/actual_prototypes" +(cd "$T" && diff -u documented_prototypes actual_prototypes) + +(cd "$T" && rm files Nm Fn exports listed_sources actual_sources \ + listed_aliases actual_aliases documented_prototypes actual_prototypes) +rmdir -- "$T" diff --git a/man/dyc.css b/man/dyc.css new file mode 100644 index 0000000..1ff5b59 --- /dev/null +++ b/man/dyc.css @@ -0,0 +1,14 @@ +<style> + table.head, table.foot { width: 100%; } + td.head-rtitle, td.foot-os { text-align: right; } + td.head-vol { text-align: center; } + div.Pp { margin: 1ex 0ex; } + div.Nd, div.Bf, div.Op { display: inline; } + span.Pa, span.Ad { font-style: italic; } + span.Ms { font-weight: bold; } + dl.Bl-diag > dt { font-weight: bold; } + code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn, + code.Cd { font-weight: bold; font-family: monospace; } + var { font-family: monospace; } + .Sh { font-size: 1.5em; padding-top: 1em; padding-bottom: 1em; } +</style> diff --git a/man/eddsa_pk_new.3 b/man/eddsa_pk_new.3 new file mode 100644 index 0000000..428d724 --- /dev/null +++ b/man/eddsa_pk_new.3 @@ -0,0 +1,146 @@ +.\" Copyright (c) 2019-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 15 2022 $ +.Dt EDDSA_PK_NEW 3 +.Os +.Sh NAME +.Nm eddsa_pk_new , +.Nm eddsa_pk_free , +.Nm eddsa_pk_from_EVP_PKEY , +.Nm eddsa_pk_from_ptr , +.Nm eddsa_pk_to_EVP_PKEY +.Nd FIDO2 COSE EDDSA API +.Sh SYNOPSIS +.In openssl/evp.h +.In fido/eddsa.h +.Ft eddsa_pk_t * +.Fn eddsa_pk_new "void" +.Ft void +.Fn eddsa_pk_free "eddsa_pk_t **pkp" +.Ft int +.Fn eddsa_pk_from_EVP_PKEY "eddsa_pk_t *pk" "const EVP_PKEY *pkey" +.Ft int +.Fn eddsa_pk_from_ptr "eddsa_pk_t *pk" "const void *ptr" "size_t len" +.Ft EVP_PKEY * +.Fn eddsa_pk_to_EVP_PKEY "const eddsa_pk_t *pk" +.Sh DESCRIPTION +EDDSA is the name given in the CBOR Object Signing and Encryption +(COSE) RFC to EDDSA over Curve25519 with SHA-512. +The COSE EDDSA API of +.Em libfido2 +is an auxiliary API with routines to convert between the different +EDDSA public key types used in +.Em libfido2 +and +.Em OpenSSL . +.Pp +In +.Em libfido2 , +EDDSA public keys are abstracted by the +.Vt eddsa_pk_t +type. +.Pp +The +.Fn eddsa_pk_new +function returns a pointer to a newly allocated, empty +.Vt eddsa_pk_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn eddsa_pk_free +function releases the memory backing +.Fa *pkp , +where +.Fa *pkp +must have been previously allocated by +.Fn eddsa_pk_new . +On return, +.Fa *pkp +is set to NULL. +Either +.Fa pkp +or +.Fa *pkp +may be NULL, in which case +.Fn eddsa_pk_free +is a NOP. +.Pp +The +.Fn eddsa_pk_from_EVP_PKEY +function fills +.Fa pk +with the contents of +.Fa pkey . +No references to +.Fa pkey +are kept. +.Pp +The +.Fn eddsa_pk_from_ptr +function fills +.Fa pk +with the contents of +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +No references to +.Fa ptr +are kept. +.Pp +The +.Fn eddsa_pk_to_EVP_PKEY +function converts +.Fa pk +to a newly allocated +.Fa EVP_PKEY +type with a reference count of 1. +No internal references to the returned pointer are kept. +If an error occurs, +.Fn eddsa_pk_to_EVP_PKEY +returns NULL. +.Sh RETURN VALUES +The +.Fn eddsa_pk_from_EVP_PKEY +and +.Fn eddsa_pk_from_ptr +functions return +.Dv FIDO_OK +on success. +On error, a different error code defined in +.In fido/err.h +is returned. +.Sh SEE ALSO +.Xr es256_pk_new 3 , +.Xr es384_pk_new 3 , +.Xr fido_assert_verify 3 , +.Xr fido_cred_pubkey_ptr 3 , +.Xr rs256_pk_new 3 diff --git a/man/es256_pk_new.3 b/man/es256_pk_new.3 new file mode 100644 index 0000000..7d6be4d --- /dev/null +++ b/man/es256_pk_new.3 @@ -0,0 +1,164 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 15 2022 $ +.Dt ES256_PK_NEW 3 +.Os +.Sh NAME +.Nm es256_pk_new , +.Nm es256_pk_free , +.Nm es256_pk_from_EC_KEY , +.Nm es256_pk_from_EVP_PKEY , +.Nm es256_pk_from_ptr , +.Nm es256_pk_to_EVP_PKEY +.Nd FIDO2 COSE ES256 API +.Sh SYNOPSIS +.In openssl/ec.h +.In fido/es256.h +.Ft es256_pk_t * +.Fn es256_pk_new "void" +.Ft void +.Fn es256_pk_free "es256_pk_t **pkp" +.Ft int +.Fn es256_pk_from_EC_KEY "es256_pk_t *pk" "const EC_KEY *ec" +.Ft int +.Fn es256_pk_from_EVP_PKEY "es256_pk_t *pk" "const EVP_PKEY *pkey" +.Ft int +.Fn es256_pk_from_ptr "es256_pk_t *pk" "const void *ptr" "size_t len" +.Ft EVP_PKEY * +.Fn es256_pk_to_EVP_PKEY "const es256_pk_t *pk" +.Sh DESCRIPTION +ES256 is the name given in the CBOR Object Signing and Encryption +(COSE) RFC to ECDSA over P-256 with SHA-256. +The COSE ES256 API of +.Em libfido2 +is an auxiliary API with routines to convert between the different +ECDSA public key types used in +.Em libfido2 +and +.Em OpenSSL . +.Pp +In +.Em libfido2 , +ES256 public keys are abstracted by the +.Vt es256_pk_t +type. +.Pp +The +.Fn es256_pk_new +function returns a pointer to a newly allocated, empty +.Vt es256_pk_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn es256_pk_free +function releases the memory backing +.Fa *pkp , +where +.Fa *pkp +must have been previously allocated by +.Fn es256_pk_new . +On return, +.Fa *pkp +is set to NULL. +Either +.Fa pkp +or +.Fa *pkp +may be NULL, in which case +.Fn es256_pk_free +is a NOP. +.Pp +The +.Fn es256_pk_from_EC_KEY +function fills +.Fa pk +with the contents of +.Fa ec . +No references to +.Fa ec +are kept. +.Pp +The +.Fn es256_pk_from_EVP_PKEY +function fills +.Fa pk +with the contents of +.Fa pkey . +No references to +.Fa pkey +are kept. +.Pp +The +.Fn es256_pk_from_ptr +function fills +.Fa pk +with the contents of +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +The +.Fa ptr +pointer may point to an uncompressed point, or to the +concatenation of the x and y coordinates. +No references to +.Fa ptr +are kept. +.Pp +The +.Fn es256_pk_to_EVP_PKEY +function converts +.Fa pk +to a newly allocated +.Fa EVP_PKEY +type with a reference count of 1. +No internal references to the returned pointer are kept. +If an error occurs, +.Fn es256_pk_to_EVP_PKEY +returns NULL. +.Sh RETURN VALUES +The +.Fn es256_pk_from_EC_KEY , +.Fn es256_pk_from_EVP_PKEY , +and +.Fn es256_pk_from_ptr +functions return +.Dv FIDO_OK +on success. +On error, a different error code defined in +.In fido/err.h +is returned. +.Sh SEE ALSO +.Xr eddsa_pk_new 3 , +.Xr es384_pk_new 3 , +.Xr fido_assert_verify 3 , +.Xr fido_cred_pubkey_ptr 3 , +.Xr rs256_pk_new 3 diff --git a/man/es384_pk_new.3 b/man/es384_pk_new.3 new file mode 100644 index 0000000..e865913 --- /dev/null +++ b/man/es384_pk_new.3 @@ -0,0 +1,164 @@ +.\" Copyright (c) 2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 15 2022 $ +.Dt ES384_PK_NEW 3 +.Os +.Sh NAME +.Nm es384_pk_new , +.Nm es384_pk_free , +.Nm es384_pk_from_EC_KEY , +.Nm es384_pk_from_EVP_PKEY , +.Nm es384_pk_from_ptr , +.Nm es384_pk_to_EVP_PKEY +.Nd FIDO2 COSE ES384 API +.Sh SYNOPSIS +.In openssl/ec.h +.In fido/es384.h +.Ft es384_pk_t * +.Fn es384_pk_new "void" +.Ft void +.Fn es384_pk_free "es384_pk_t **pkp" +.Ft int +.Fn es384_pk_from_EC_KEY "es384_pk_t *pk" "const EC_KEY *ec" +.Ft int +.Fn es384_pk_from_EVP_PKEY "es384_pk_t *pk" "const EVP_PKEY *pkey" +.Ft int +.Fn es384_pk_from_ptr "es384_pk_t *pk" "const void *ptr" "size_t len" +.Ft EVP_PKEY * +.Fn es384_pk_to_EVP_PKEY "const es384_pk_t *pk" +.Sh DESCRIPTION +ES384 is the name given in the CBOR Object Signing and Encryption +(COSE) RFC to ECDSA over P-384 with SHA-384. +The COSE ES384 API of +.Em libfido2 +is an auxiliary API with routines to convert between the different +ECDSA public key types used in +.Em libfido2 +and +.Em OpenSSL . +.Pp +In +.Em libfido2 , +ES384 public keys are abstracted by the +.Vt es384_pk_t +type. +.Pp +The +.Fn es384_pk_new +function returns a pointer to a newly allocated, empty +.Vt es384_pk_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn es384_pk_free +function releases the memory backing +.Fa *pkp , +where +.Fa *pkp +must have been previously allocated by +.Fn es384_pk_new . +On return, +.Fa *pkp +is set to NULL. +Either +.Fa pkp +or +.Fa *pkp +may be NULL, in which case +.Fn es384_pk_free +is a NOP. +.Pp +The +.Fn es384_pk_from_EC_KEY +function fills +.Fa pk +with the contents of +.Fa ec . +No references to +.Fa ec +are kept. +.Pp +The +.Fn es384_pk_from_EVP_PKEY +function fills +.Fa pk +with the contents of +.Fa pkey . +No references to +.Fa pkey +are kept. +.Pp +The +.Fn es384_pk_from_ptr +function fills +.Fa pk +with the contents of +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +The +.Fa ptr +pointer may point to an uncompressed point, or to the +concatenation of the x and y coordinates. +No references to +.Fa ptr +are kept. +.Pp +The +.Fn es384_pk_to_EVP_PKEY +function converts +.Fa pk +to a newly allocated +.Fa EVP_PKEY +type with a reference count of 1. +No internal references to the returned pointer are kept. +If an error occurs, +.Fn es384_pk_to_EVP_PKEY +returns NULL. +.Sh RETURN VALUES +The +.Fn es384_pk_from_EC_KEY , +.Fn es384_pk_from_EVP_PKEY , +and +.Fn es384_pk_from_ptr +functions return +.Dv FIDO_OK +on success. +On error, a different error code defined in +.In fido/err.h +is returned. +.Sh SEE ALSO +.Xr eddsa_pk_new 3 , +.Xr es256_pk_new 3 , +.Xr fido_assert_verify 3 , +.Xr fido_cred_pubkey_ptr 3 , +.Xr rs256_pk_new 3 diff --git a/man/fido2-assert.1 b/man/fido2-assert.1 new file mode 100644 index 0000000..882b7ab --- /dev/null +++ b/man/fido2-assert.1 @@ -0,0 +1,286 @@ +.\" Copyright (c) 2018-2023 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 3 2023 $ +.Dt FIDO2-ASSERT 1 +.Os +.Sh NAME +.Nm fido2-assert +.Nd get/verify a FIDO2 assertion +.Sh SYNOPSIS +.Nm +.Fl G +.Op Fl bdhpruvw +.Op Fl t Ar option +.Op Fl i Ar input_file +.Op Fl o Ar output_file +.Ar device +.Nm +.Fl V +.Op Fl dhpv +.Op Fl i Ar input_file +.Ar key_file +.Op Ar type +.Sh DESCRIPTION +.Nm +gets or verifies a FIDO2 assertion. +.Pp +The input of +.Nm +is defined by the parameters of the assertion to be obtained/verified. +See the +.Sx INPUT FORMAT +section for details. +.Pp +The output of +.Nm +is defined by the result of the selected operation. +See the +.Sx OUTPUT FORMAT +section for details. +.Pp +If an assertion is successfully obtained or verified, +.Nm +exits 0. +Otherwise, +.Nm +exits 1. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl G +Tells +.Nm +to obtain a new assertion from +.Ar device . +.It Fl V +Tells +.Nm +to verify an assertion using the PEM-encoded public key in +.Ar key_file +of type +.Ar type , +where +.Ar type +may be +.Em es256 +(denoting ECDSA over NIST P-256 with SHA-256), +.Em rs256 +(denoting 2048-bit RSA with PKCS#1.5 padding and SHA-256), or +.Em eddsa +(denoting EDDSA over Curve25519 with SHA-512). +If +.Ar type +is not specified, +.Em es256 +is assumed. +.It Fl b +Request the credential's +.Dq largeBlobKey , +a 32-byte symmetric key associated with the asserted credential. +.It Fl h +If obtaining an assertion, enable the FIDO2 hmac-secret +extension. +If verifying an assertion, check whether the extension data bit was +signed by the authenticator. +.It Fl d +Causes +.Nm +to emit debugging output on +.Em stderr . +.It Fl i Ar input_file +Tells +.Nm +to read the parameters of the assertion from +.Ar input_file +instead of +.Em stdin . +.It Fl o Ar output_file +Tells +.Nm +to write output on +.Ar output_file +instead of +.Em stdout . +.It Fl p +If obtaining an assertion, request user presence. +If verifying an assertion, check whether the user presence bit was +signed by the authenticator. +.It Fl r +Obtain an assertion using a resident credential. +If +.Fl r +is specified, +.Nm +will not expect a credential id in its input, and may output +multiple assertions. +Resident credentials are called +.Dq discoverable credentials +in CTAP 2.1. +.It Fl t Ar option +Toggles a key/value +.Ar option , +where +.Ar option +is a string of the form +.Dq key=value . +The options supported at present are: +.Bl -tag -width Ds +.It Cm up Ns = Ns Ar true|false +Asks the authenticator for user presence to be enabled or disabled. +.It Cm uv Ns = Ns Ar true|false +Asks the authenticator for user verification to be enabled or +disabled. +.It Cm pin Ns = Ns Ar true|false +Tells +.Nm +whether to prompt for a PIN and request user verification. +.El +.Pp +The +.Fl t +option may be specified multiple times. +.It Fl u +Obtain an assertion using U2F. +By default, +.Nm +will use FIDO2 if supported by the authenticator, and fallback to +U2F otherwise. +.It Fl v +If obtaining an assertion, prompt the user for a PIN and request +user verification from the authenticator. +If verifying an assertion, check whether the user verification bit +was signed by the authenticator. +.It Fl w +Tells +.Nm +that the first line of input when obtaining an assertion shall be +interpreted as unhashed client data. +This is required by Windows Hello, which calculates the client data hash +internally. +.El +.Pp +If a +.Em tty +is available, +.Nm +will use it to obtain the PIN. +Otherwise, +.Em stdin +is used. +.Sh INPUT FORMAT +The input of +.Nm +consists of base64 blobs and UTF-8 strings separated +by newline characters ('\\n'). +.Pp +When obtaining an assertion, +.Nm +expects its input to consist of: +.Pp +.Bl -enum -offset indent -compact +.It +client data hash (base64 blob); +.It +relying party id (UTF-8 string); +.It +credential id, if credential not resident (base64 blob); +.It +hmac salt, if the FIDO2 hmac-secret extension is enabled +(base64 blob); +.El +.Pp +When verifying an assertion, +.Nm +expects its input to consist of: +.Pp +.Bl -enum -offset indent -compact +.It +client data hash (base64 blob); +.It +relying party id (UTF-8 string); +.It +authenticator data (base64 blob); +.It +assertion signature (base64 blob); +.El +.Pp +UTF-8 strings passed to +.Nm +must not contain embedded newline or NUL characters. +.Sh OUTPUT FORMAT +The output of +.Nm +consists of base64 blobs and UTF-8 strings separated +by newline characters ('\\n'). +.Pp +For each generated assertion, +.Nm +outputs: +.Pp +.Bl -enum -offset indent -compact +.It +client data hash (base64 blob); +.It +relying party id (UTF-8 string); +.It +authenticator data (base64 blob); +.It +assertion signature (base64 blob); +.It +user id, if credential resident (base64 blob); +.It +hmac secret, if the FIDO2 hmac-secret extension is enabled +(base64 blob); +.It +the credential's associated 32-byte symmetric key +.Pq Dq largeBlobKey , +if requested (base64 blob). +.El +.Pp +When verifying an assertion, +.Nm +produces no output. +.Sh EXAMPLES +Assuming +.Pa cred +contains a +.Em es256 +credential created according to the steps outlined in +.Xr fido2-cred 1 , +obtain an assertion from an authenticator at +.Pa /dev/hidraw5 +and verify it: +.Pp +.Dl $ echo assertion challenge | openssl sha256 -binary | base64 > assert_param +.Dl $ echo relying party >> assert_param +.Dl $ head -1 cred >> assert_param +.Dl $ tail -n +2 cred > pubkey +.Dl $ fido2-assert -G -i assert_param /dev/hidraw5 | fido2-assert -V pubkey es256 +.Sh SEE ALSO +.Xr fido2-cred 1 , +.Xr fido2-token 1 diff --git a/man/fido2-cred.1 b/man/fido2-cred.1 new file mode 100644 index 0000000..3f181db --- /dev/null +++ b/man/fido2-cred.1 @@ -0,0 +1,297 @@ +.\" Copyright (c) 2018-2023 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 3 2023 $ +.Dt FIDO2-CRED 1 +.Os +.Sh NAME +.Nm fido2-cred +.Nd make/verify a FIDO2 credential +.Sh SYNOPSIS +.Nm +.Fl M +.Op Fl bdhqruvw +.Op Fl c Ar cred_protect +.Op Fl i Ar input_file +.Op Fl o Ar output_file +.Ar device +.Op Ar type +.Nm +.Fl V +.Op Fl dhv +.Op Fl c Ar cred_protect +.Op Fl i Ar input_file +.Op Fl o Ar output_file +.Op Ar type +.Sh DESCRIPTION +.Nm +makes or verifies a FIDO2 credential. +.Pp +A credential +.Ar type +may be +.Em es256 +(denoting ECDSA over NIST P-256 with SHA-256), +.Em rs256 +(denoting 2048-bit RSA with PKCS#1.5 padding and SHA-256), or +.Em eddsa +(denoting EDDSA over Curve25519 with SHA-512). +If +.Ar type +is not specified, +.Em es256 +is assumed. +.Pp +When making a credential, the authenticator may require the user +to authenticate with a PIN. +If the +.Fl q +option is not specified, +.Nm +will prompt the user for the PIN. +If a +.Em tty +is available, +.Nm +will use it to obtain the PIN. +Otherwise, +.Em stdin +is used. +.Pp +The input of +.Nm +is defined by the parameters of the credential to be made/verified. +See the +.Sx INPUT FORMAT +section for details. +.Pp +The output of +.Nm +is defined by the result of the selected operation. +See the +.Sx OUTPUT FORMAT +section for details. +.Pp +If a credential is successfully created or verified, +.Nm +exits 0. +Otherwise, +.Nm +exits 1. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl M +Tells +.Nm +to make a new credential on +.Ar device . +.It Fl V +Tells +.Nm +to verify a credential. +.It Fl b +Request the credential's +.Dq largeBlobKey , +a 32-byte symmetric key associated with the generated credential. +.It Fl c Ar cred_protect +If making a credential, set the credential's protection level to +.Ar cred_protect , +where +.Ar cred_protect +is the credential's protection level in decimal notation. +Please refer to +.In fido/param.h +for the set of possible values. +If verifying a credential, check whether the credential's protection +level was signed by the authenticator as +.Ar cred_protect . +.It Fl d +Causes +.Nm +to emit debugging output on +.Em stderr . +.It Fl h +If making a credential, enable the FIDO2 hmac-secret extension. +If verifying a credential, check whether the extension data bit was +signed by the authenticator. +.It Fl i Ar input_file +Tells +.Nm +to read the parameters of the credential from +.Ar input_file +instead of +.Em stdin . +.It Fl o Ar output_file +Tells +.Nm +to write output on +.Ar output_file +instead of +.Em stdout . +.It Fl q +Tells +.Nm +to be quiet. +If a PIN is required and +.Fl q +is specified, +.Nm +will fail. +.It Fl r +Create a resident credential. +Resident credentials are called +.Dq discoverable credentials +in CTAP 2.1. +.It Fl u +Create a U2F credential. +By default, +.Nm +will use FIDO2 if supported by the authenticator, and fallback to +U2F otherwise. +.It Fl v +If making a credential, request user verification. +If verifying a credential, check whether the user verification bit +was signed by the authenticator. +.It Fl w +Tells +.Nm +that the first line of input when making a credential shall be +interpreted as unhashed client data. +This is required by Windows Hello, which calculates the client data hash +internally. +.El +.Sh INPUT FORMAT +The input of +.Nm +consists of base64 blobs and UTF-8 strings separated +by newline characters ('\\n'). +.Pp +When making a credential, +.Nm +expects its input to consist of: +.Pp +.Bl -enum -offset indent -compact +.It +client data hash (base64 blob); +.It +relying party id (UTF-8 string); +.It +user name (UTF-8 string); +.It +user id (base64 blob). +.El +.Pp +When verifying a credential, +.Nm +expects its input to consist of: +.Pp +.Bl -enum -offset indent -compact +.It +client data hash (base64 blob); +.It +relying party id (UTF-8 string); +.It +credential format (UTF-8 string); +.It +authenticator data (base64 blob); +.It +credential id (base64 blob); +.It +attestation signature (base64 blob); +.It +attestation certificate (optional, base64 blob). +.El +.Pp +UTF-8 strings passed to +.Nm +must not contain embedded newline or NUL characters. +.Sh OUTPUT FORMAT +The output of +.Nm +consists of base64 blobs, UTF-8 strings, and PEM-encoded public +keys separated by newline characters ('\\n'). +.Pp +Upon the successful generation of a credential, +.Nm +outputs: +.Pp +.Bl -enum -offset indent -compact +.It +client data hash (base64 blob); +.It +relying party id (UTF-8 string); +.It +credential format (UTF-8 string); +.It +authenticator data (base64 blob); +.It +credential id (base64 blob); +.It +attestation signature (base64 blob); +.It +attestation certificate, if present (base64 blob). +.It +the credential's associated 32-byte symmetric key +.Pq Dq largeBlobKey , +if present (base64 blob). +.El +.Pp +Upon the successful verification of a credential, +.Nm +outputs: +.Pp +.Bl -enum -offset indent -compact +.It +credential id (base64 blob); +.It +PEM-encoded credential key. +.El +.Sh EXAMPLES +Create a new +.Em es256 +credential on +.Pa /dev/hidraw5 , +verify it, and save the id and the public key of the credential in +.Em cred : +.Pp +.Dl $ echo credential challenge | openssl sha256 -binary | base64 > cred_param +.Dl $ echo relying party >> cred_param +.Dl $ echo user name >> cred_param +.Dl $ dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param +.Dl $ fido2-cred -M -i cred_param /dev/hidraw5 | fido2-cred -V -o cred +.Sh SEE ALSO +.Xr fido2-assert 1 , +.Xr fido2-token 1 +.Sh CAVEATS +Please note that +.Nm +handles Basic Attestation and Self Attestation transparently. +In the case of Basic Attestation, the validity of the authenticator's +attestation certificate is +.Em not +verified. diff --git a/man/fido2-token.1 b/man/fido2-token.1 new file mode 100644 index 0000000..65a228c --- /dev/null +++ b/man/fido2-token.1 @@ -0,0 +1,421 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: April 11 2022 $ +.Dt FIDO2-TOKEN 1 +.Os +.Sh NAME +.Nm fido2-token +.Nd find and manage a FIDO2 authenticator +.Sh SYNOPSIS +.Nm +.Fl C +.Op Fl d +.Ar device +.Nm +.Fl D +.Op Fl d +.Fl i +.Ar cred_id +.Ar device +.Nm +.Fl D +.Fl b +.Op Fl d +.Fl k Ar key_path +.Ar device +.Nm +.Fl D +.Fl b +.Op Fl d +.Fl n Ar rp_id +.Op Fl i Ar cred_id +.Ar device +.Nm +.Fl D +.Fl e +.Op Fl d +.Fl i +.Ar template_id +.Ar device +.Nm +.Fl D +.Fl u +.Op Fl d +.Ar device +.Nm +.Fl G +.Fl b +.Op Fl d +.Fl k Ar key_path +.Ar blob_path +.Ar device +.Nm +.Fl G +.Fl b +.Op Fl d +.Fl n Ar rp_id +.Op Fl i Ar cred_id +.Ar blob_path +.Ar device +.Nm +.Fl I +.Op Fl cd +.Op Fl k Ar rp_id Fl i Ar cred_id +.Ar device +.Nm +.Fl L +.Op Fl bder +.Op Fl k Ar rp_id +.Op device +.Nm +.Fl R +.Op Fl d +.Ar device +.Nm +.Fl S +.Op Fl adefu +.Ar device +.Nm +.Fl S +.Op Fl d +.Fl i Ar template_id +.Fl n Ar template_name +.Ar device +.Nm +.Fl S +.Op Fl d +.Fl l Ar pin_length +.Ar device +.Nm +.Fl S +.Fl b +.Op Fl d +.Fl k Ar key_path +.Ar blob_path +.Ar device +.Nm +.Fl S +.Fl b +.Op Fl d +.Fl n Ar rp_id +.Op Fl i Ar cred_id +.Ar blob_path +.Ar device +.Nm +.Fl S +.Fl c +.Op Fl d +.Fl i Ar cred_id +.Fl k Ar user_id +.Fl n Ar name +.Fl p Ar display_name +.Ar device +.Nm +.Fl S +.Fl m +.Ar rp_id +.Ar device +.Nm +.Fl V +.Sh DESCRIPTION +.Nm +manages a FIDO2 authenticator. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl C Ar device +Changes the PIN of +.Ar device . +The user will be prompted for the current and new PINs. +.It Fl D Fl i Ar id Ar device +Deletes the resident credential specified by +.Ar id +from +.Ar device , +where +.Ar id +is the credential's base64-encoded id. +The user will be prompted for the PIN. +.It Fl D Fl b Fl k Ar key_path Ar device +Deletes a +.Dq largeBlob +encrypted with +.Ar key_path +from +.Ar device , +where +.Ar key_path +holds the blob's base64-encoded 32-byte AES-256 GCM encryption key. +A PIN or equivalent user-verification gesture is required. +.It Fl D Fl b Fl n Ar rp_id Oo Fl i Ar cred_id Oc Ar device +Deletes a +.Dq largeBlob +corresponding to +.Ar rp_id +from +.Ar device . +If +.Ar rp_id +has multiple credentials enrolled on +.Ar device , +the credential ID must be specified using +.Fl i Ar cred_id , +where +.Ar cred_id +is a base64-encoded blob. +A PIN or equivalent user-verification gesture is required. +.It Fl D Fl e Fl i Ar id Ar device +Deletes the biometric enrollment specified by +.Ar id +from +.Ar device , +where +.Ar id +is the enrollment's template base64-encoded id. +The user will be prompted for the PIN. +.It Fl D Fl u Ar device +Disables the CTAP 2.1 +.Dq user verification always +feature on +.Ar device . +.It Fl G Fl b Fl k Ar key_path Ar blob_path Ar device +Gets a CTAP 2.1 +.Dq largeBlob +encrypted with +.Ar key_path +from +.Ar device , +where +.Ar key_path +holds the blob's base64-encoded 32-byte AES-256 GCM encryption key. +The blob is written to +.Ar blob_path . +A PIN or equivalent user-verification gesture is required. +.It Fl G Fl b Fl n Ar rp_id Oo Fl i Ar cred_id Oc Ar blob_path Ar device +Gets a CTAP 2.1 +.Dq largeBlob +associated with +.Ar rp_id +from +.Ar device . +If +.Ar rp_id +has multiple credentials enrolled on +.Ar device , +the credential ID must be specified using +.Fl i Ar cred_id , +where +.Ar cred_id +is a base64-encoded blob. +The blob is written to +.Ar blob_path . +A PIN or equivalent user-verification gesture is required. +.It Fl I Ar device +Retrieves information on +.Ar device . +.It Fl I Fl c Ar device +Retrieves resident credential metadata from +.Ar device . +The user will be prompted for the PIN. +.It Fl I Fl k Ar rp_id Fl i Ar cred_id Ar device +Prints the credential id (base64-encoded) and public key +(PEM encoded) of the resident credential specified by +.Ar rp_id +and +.Ar cred_id , +where +.Ar rp_id +is a UTF-8 relying party id, and +.Ar cred_id +is a base64-encoded credential id. +The user will be prompted for the PIN. +.It Fl L +Produces a list of authenticators found by the operating system. +.It Fl L Fl b Ar device +Produces a list of CTAP 2.1 +.Dq largeBlobs +on +.Ar device . +A PIN or equivalent user-verification gesture is required. +.It Fl L Fl e Ar device +Produces a list of biometric enrollments on +.Ar device . +The user will be prompted for the PIN. +.It Fl L Fl r Ar device +Produces a list of relying parties with resident credentials on +.Ar device . +The user will be prompted for the PIN. +.It Fl L Fl k Ar rp_id Ar device +Produces a list of resident credentials corresponding to +relying party +.Ar rp_id +on +.Ar device . +The user will be prompted for the PIN. +.It Fl R +Performs a reset on +.Ar device . +.Nm +will NOT prompt for confirmation. +.It Fl S +Sets the PIN of +.Ar device . +The user will be prompted for the PIN. +.It Fl S Fl a Ar device +Enables CTAP 2.1 Enterprise Attestation on +.Ar device . +.It Fl S Fl b Fl k Ar key_path Ar blob_path Ar device +Sets a CTAP 2.1 +.Dq largeBlob +encrypted with +.Ar key_path +on +.Ar device , +where +.Ar key_path +holds the blob's base64-encoded 32-byte AES-256 GCM encryption key. +The blob is read from +.Fa blob_path . +A PIN or equivalent user-verification gesture is required. +.It Fl S Fl b Fl n Ar rp_id Oo Fl i Ar cred_id Oc Ar blob_path Ar device +Sets a CTAP 2.1 +.Dq largeBlob +associated with +.Ar rp_id +on +.Ar device . +The blob is read from +.Fa blob_path . +If +.Ar rp_id +has multiple credentials enrolled on +.Ar device , +the credential ID must be specified using +.Fl i Ar cred_id , +where +.Ar cred_id +is a base64-encoded blob. +A PIN or equivalent user-verification gesture is required. +.It Fl S Fl c Fl i Ar cred_id Fl k Ar user_id Fl n Ar name Fl p Ar display_name Ar device +Sets the +.Ar name +and +.Ar display_name +attributes of the resident credential identified by +.Ar cred_id +and +.Ar user_id , +where +.Ar name +and +.Ar display_name +are UTF-8 strings and +.Ar cred_id +and +.Ar user_id +are base64-encoded blobs. +A PIN or equivalent user-verification gesture is required. +.It Fl S Fl e Ar device +Performs a new biometric enrollment on +.Ar device . +The user will be prompted for the PIN. +.It Fl S Fl e Fl i Ar template_id Fl n Ar template_name Ar device +Sets the friendly name of the biometric enrollment specified by +.Ar template_id +to +.Ar template_name +on +.Ar device , +where +.Ar template_id +is base64-encoded and +.Ar template_name +is a UTF-8 string. +The user will be prompted for the PIN. +.It Fl S Fl f Ar device +Forces a PIN change on +.Ar device . +The user will be prompted for the PIN. +.It Fl S Fl l Ar pin_length Ar device +Sets the minimum PIN length of +.Ar device +to +.Ar pin_length . +The user will be prompted for the PIN. +.It Fl S Fl m Ar rp_id Ar device +Sets the list of relying party IDs that are allowed to retrieve +the minimum PIN length of +.Ar device . +Multiple IDs may be specified, separated by commas. +The user will be prompted for the PIN. +.It Fl S Fl u Ar device +Enables the CTAP 2.1 +.Dq user verification always +feature on +.Ar device . +.It Fl V +Prints version information. +.It Fl d +Causes +.Nm +to emit debugging output on +.Em stderr . +.El +.Pp +If a +.Em tty +is available, +.Nm +will use it to prompt for PINs. +Otherwise, +.Em stdin +is used. +.Pp +.Nm +exits 0 on success and 1 on error. +.Sh SEE ALSO +.Xr fido2-assert 1 , +.Xr fido2-cred 1 +.Sh CAVEATS +The actual user-flow to perform a reset is outside the scope of the +FIDO2 specification, and may therefore vary depending on the +authenticator. +Yubico authenticators do not allow resets after 5 seconds from +power-up, and expect a reset to be confirmed by the user through +touch within 30 seconds. +.Pp +An authenticator's path may contain spaces. +.Pp +Resident credentials are called +.Dq discoverable credentials +in CTAP 2.1. +.Pp +Whether the CTAP 2.1 +.Dq user verification always +feature is activated or deactivated after an authenticator reset +is vendor-specific. diff --git a/man/fido_assert_allow_cred.3 b/man/fido_assert_allow_cred.3 new file mode 100644 index 0000000..6520137 --- /dev/null +++ b/man/fido_assert_allow_cred.3 @@ -0,0 +1,80 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: December 1 2022 $ +.Dt FIDO_ASSERT_ALLOW_CRED 3 +.Os +.Sh NAME +.Nm fido_assert_allow_cred , +.Nm fido_assert_empty_allow_list +.Nd manage allow lists in a FIDO2 assertion +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_assert_allow_cred "fido_assert_t *assert" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_empty_allow_list "fido_assert_t *assert" +.Sh DESCRIPTION +The +.Fn fido_assert_allow_cred +function adds +.Fa ptr +to the list of credentials allowed in +.Fa assert , +where +.Fa ptr +points to a credential ID of +.Fa len +bytes. +A copy of +.Fa ptr +is made, and no references to the passed pointer are kept. +If +.Fn fido_assert_allow_cred +fails, the existing list of allowed credentials is preserved. +.Pp +For the format of a FIDO2 credential ID, please refer to the +Web Authentication (webauthn) standard. +.Pp +The +.Fn fido_assert_empty_allow_list +function empties the list of credentials allowed in +.Fa assert . +.Sh RETURN VALUES +The error codes returned by +.Fn fido_assert_allow_cred +and +.Fn fido_assert_empty_allow_list +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_assert_new 3 , +.Xr fido_assert_set_authdata 3 , +.Xr fido_dev_get_assert 3 diff --git a/man/fido_assert_new.3 b/man/fido_assert_new.3 new file mode 100644 index 0000000..fdc74a9 --- /dev/null +++ b/man/fido_assert_new.3 @@ -0,0 +1,293 @@ +.\" Copyright (c) 2018-2023 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: June 19 2023 $ +.Dt FIDO_ASSERT_NEW 3 +.Os +.Sh NAME +.Nm fido_assert_new , +.Nm fido_assert_free , +.Nm fido_assert_count , +.Nm fido_assert_rp_id , +.Nm fido_assert_user_display_name , +.Nm fido_assert_user_icon , +.Nm fido_assert_user_name , +.Nm fido_assert_authdata_ptr , +.Nm fido_assert_authdata_raw_ptr , +.Nm fido_assert_blob_ptr , +.Nm fido_assert_clientdata_hash_ptr , +.Nm fido_assert_hmac_secret_ptr , +.Nm fido_assert_largeblob_key_ptr , +.Nm fido_assert_user_id_ptr , +.Nm fido_assert_sig_ptr , +.Nm fido_assert_id_ptr , +.Nm fido_assert_authdata_len , +.Nm fido_assert_authdata_raw_len , +.Nm fido_assert_blob_len , +.Nm fido_assert_clientdata_hash_len , +.Nm fido_assert_hmac_secret_len , +.Nm fido_assert_largeblob_key_len , +.Nm fido_assert_user_id_len , +.Nm fido_assert_sig_len , +.Nm fido_assert_id_len , +.Nm fido_assert_sigcount , +.Nm fido_assert_flags +.Nd FIDO2 assertion API +.Sh SYNOPSIS +.In fido.h +.Ft fido_assert_t * +.Fn fido_assert_new "void" +.Ft void +.Fn fido_assert_free "fido_assert_t **assert_p" +.Ft size_t +.Fn fido_assert_count "const fido_assert_t *assert" +.Ft const char * +.Fn fido_assert_rp_id "const fido_assert_t *assert" +.Ft const char * +.Fn fido_assert_user_display_name "const fido_assert_t *assert" "size_t idx" +.Ft const char * +.Fn fido_assert_user_icon "const fido_assert_t *assert" "size_t idx" +.Ft const char * +.Fn fido_assert_user_name "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_authdata_ptr "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_authdata_raw_ptr "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_clientdata_hash_ptr "const fido_assert_t *assert" +.Ft const unsigned char * +.Fn fido_assert_blob_ptr "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_hmac_secret_ptr "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_largeblob_key_ptr "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_user_id_ptr "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_sig_ptr "const fido_assert_t *assert" "size_t idx" +.Ft const unsigned char * +.Fn fido_assert_id_ptr "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_authdata_len "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_authdata_raw_len "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_clientdata_hash_len "const fido_assert_t *assert" +.Ft size_t +.Fn fido_assert_blob_len "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_hmac_secret_len "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_largeblob_key_len "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_user_id_len "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_sig_len "const fido_assert_t *assert" "size_t idx" +.Ft size_t +.Fn fido_assert_id_len "const fido_assert_t *assert" "size_t idx" +.Ft uint32_t +.Fn fido_assert_sigcount "const fido_assert_t *assert" "size_t idx" +.Ft uint8_t +.Fn fido_assert_flags "const fido_assert_t *assert" "size_t idx" +.Sh DESCRIPTION +A FIDO2 assertion is a collection of statements, each statement a +map between a challenge, a credential, a signature, and ancillary +attributes. +In +.Em libfido2 , +a FIDO2 assertion is abstracted by the +.Vt fido_assert_t +type. +The functions described in this page allow a +.Vt fido_assert_t +type to be allocated, deallocated, and inspected. +For other operations on +.Vt fido_assert_t , +please refer to +.Xr fido_assert_set_authdata 3 , +.Xr fido_assert_allow_cred 3 , +.Xr fido_assert_verify 3 , +and +.Xr fido_dev_get_assert 3 . +.Pp +The +.Fn fido_assert_new +function returns a pointer to a newly allocated, empty +.Vt fido_assert_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_assert_free +function releases the memory backing +.Fa *assert_p , +where +.Fa *assert_p +must have been previously allocated by +.Fn fido_assert_new . +On return, +.Fa *assert_p +is set to NULL. +Either +.Fa assert_p +or +.Fa *assert_p +may be NULL, in which case +.Fn fido_assert_free +is a NOP. +.Pp +The +.Fn fido_assert_count +function returns the number of statements in +.Fa assert . +.Pp +The +.Fn fido_assert_rp_id +function returns a pointer to a NUL-terminated string holding the +relying party ID of +.Fa assert . +.Pp +The +.Fn fido_assert_user_display_name , +.Fn fido_assert_user_icon , +and +.Fn fido_assert_user_name , +functions return pointers to the user display name, icon, and +name attributes of statement +.Fa idx +in +.Fa assert . +If not NULL, the values returned by these functions point to +NUL-terminated UTF-8 strings. +The user display name, icon, and name attributes will typically +only be returned by the authenticator if user verification was +performed by the authenticator and multiple resident/discoverable +credentials were involved in the assertion. +.Pp +The +.Fn fido_assert_authdata_ptr , +.Fn fido_assert_authdata_raw_ptr , +.Fn fido_assert_clientdata_hash_ptr , +.Fn fido_assert_id_ptr , +.Fn fido_assert_user_id_ptr , +.Fn fido_assert_sig_ptr , +.Fn fido_assert_sigcount , +and +.Fn fido_assert_flags +functions return pointers to the CBOR-encoded and raw authenticator data, +client data hash, credential ID, user ID, signature, signature +count, and authenticator data flags of statement +.Fa idx +in +.Fa assert . +.Pp +The +.Fn fido_assert_hmac_secret_ptr +function returns a pointer to the hmac-secret attribute of statement +.Fa idx +in +.Fa assert . +The HMAC Secret Extension +.Pq hmac-secret +is a CTAP 2.0 extension. +Note that the resulting hmac-secret varies according to whether +user verification was performed by the authenticator. +.Pp +The +.Fn fido_assert_blob_ptr +and +.Fn fido_assert_largeblob_key_ptr +functions return pointers to the +.Dq credBlob +and +.Dq largeBlobKey +attributes of statement +.Fa idx +in +.Fa assert . +Credential Blob +.Pq credBlob +and +Large Blob Key +.Pq largeBlobKey +are CTAP 2.1 extensions. +.Pp +The +.Fn fido_assert_authdata_len , +.Fn fido_assert_authdata_raw_len , +.Fn fido_assert_clientdata_hash_len , +.Fn fido_assert_id_len , +.Fn fido_assert_user_id_len , +.Fn fido_assert_sig_len , +.Fn fido_assert_hmac_secret_len , +.Fn fido_assert_blob_len , +and +.Fn fido_assert_largeblob_key_len +functions return the length of a given attribute. +.Pp +Please note that the first statement in +.Fa assert +has an +.Fa idx +(index) value of 0. +.Pp +The authenticator data and signature parts of an assertion +statement are typically passed to a FIDO2 server for verification. +.Sh RETURN VALUES +The authenticator data returned by +.Fn fido_assert_authdata_ptr +is a CBOR-encoded byte string, as obtained from the authenticator. +.Pp +The +.Fn fido_assert_rp_id , +.Fn fido_assert_user_display_name , +.Fn fido_assert_user_icon , +.Fn fido_assert_user_name , +.Fn fido_assert_authdata_ptr , +.Fn fido_assert_clientdata_hash_ptr , +.Fn fido_assert_id_ptr , +.Fn fido_assert_user_id_ptr , +.Fn fido_assert_sig_ptr , +.Fn fido_assert_hmac_secret_ptr , +.Fn fido_assert_blob_ptr , +and +.Fn fido_assert_largeblob_key_ptr +functions may return NULL if the respective field in +.Fa assert +is not set. +If not NULL, returned pointers are guaranteed to exist until any API +function that takes +.Fa assert +without the +.Em const +qualifier is invoked. +.Sh SEE ALSO +.Xr fido_assert_allow_cred 3 , +.Xr fido_assert_set_authdata 3 , +.Xr fido_assert_verify 3 , +.Xr fido_dev_get_assert 3 , +.Xr fido_dev_largeblob_get 3 diff --git a/man/fido_assert_set_authdata.3 b/man/fido_assert_set_authdata.3 new file mode 100644 index 0000000..503e2bf --- /dev/null +++ b/man/fido_assert_set_authdata.3 @@ -0,0 +1,314 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: April 8 2023 $ +.Dt FIDO_ASSERT_SET_AUTHDATA 3 +.Os +.Sh NAME +.Nm fido_assert_set_authdata , +.Nm fido_assert_set_authdata_raw , +.Nm fido_assert_set_clientdata , +.Nm fido_assert_set_clientdata_hash , +.Nm fido_assert_set_count , +.Nm fido_assert_set_extensions , +.Nm fido_assert_set_hmac_salt , +.Nm fido_assert_set_hmac_secret , +.Nm fido_assert_set_up , +.Nm fido_assert_set_uv , +.Nm fido_assert_set_rp , +.Nm fido_assert_set_sig , +.Nm fido_assert_set_winhello_appid +.Nd set parameters of a FIDO2 assertion +.Sh SYNOPSIS +.In fido.h +.Bd -literal +typedef enum { + FIDO_OPT_OMIT = 0, /* use authenticator's default */ + FIDO_OPT_FALSE, /* explicitly set option to false */ + FIDO_OPT_TRUE, /* explicitly set option to true */ +} fido_opt_t; +.Ed +.Ft int +.Fn fido_assert_set_authdata "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_set_authdata_raw "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_set_clientdata "fido_assert_t *assert" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_set_clientdata_hash "fido_assert_t *assert" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_set_count "fido_assert_t *assert" "size_t n" +.Ft int +.Fn fido_assert_set_extensions "fido_assert_t *assert" "int flags" +.Ft int +.Fn fido_assert_set_hmac_salt "fido_assert_t *assert" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_set_hmac_secret "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_set_up "fido_assert_t *assert" "fido_opt_t up" +.Ft int +.Fn fido_assert_set_uv "fido_assert_t *assert" "fido_opt_t uv" +.Ft int +.Fn fido_assert_set_rp "fido_assert_t *assert" "const char *id" +.Ft int +.Fn fido_assert_set_sig "fido_assert_t *assert" "size_t idx" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_assert_set_winhello_appid "fido_assert_t *assert" "const char *id" +.Sh DESCRIPTION +The +.Nm +set of functions define the various parameters of a FIDO2 +assertion, allowing a +.Fa fido_assert_t +type to be prepared for a subsequent call to +.Xr fido_dev_get_assert 3 +or +.Xr fido_assert_verify 3 . +For the complete specification of a FIDO2 assertion and the format +of its constituent parts, please refer to the Web Authentication +(webauthn) standard. +.Pp +The +.Fn fido_assert_set_count +function sets the number of assertion statements in +.Fa assert +to +.Fa n . +.Pp +The +.Fn fido_assert_set_authdata +and +.Fn fido_assert_set_sig +functions set the authenticator data and signature parts of the +statement with index +.Fa idx +of +.Fa assert +to +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +A copy of +.Fa ptr +is made, and no references to the passed pointer are kept. +Please note that the first assertion statement of +.Fa assert +has an +.Fa idx +of +.Em 0 . +The authenticator data passed to +.Fn fido_assert_set_authdata +must be a CBOR-encoded byte string, as obtained from +.Fn fido_assert_authdata_ptr . +Alternatively, a raw binary blob may be passed to +.Fn fido_assert_set_authdata_raw . +.Pp +The +.Fn fido_assert_set_clientdata_hash +function sets the client data hash of +.Fa assert +to +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +A copy of +.Fa ptr +is made, and no references to the passed pointer are kept. +.Pp +The +.Fn fido_assert_set_clientdata +function allows an application to set the client data hash of +.Fa assert +by specifying the assertion's unhashed client data. +This is required by Windows Hello, which calculates the client data +hash internally. +For compatibility with Windows Hello, applications should use +.Fn fido_assert_set_clientdata +instead of +.Fn fido_assert_set_clientdata_hash . +.Pp +The +.Fn fido_assert_set_rp +function sets the relying party +.Fa id +of +.Fa assert , +where +.Fa id +is a NUL-terminated UTF-8 string. +The content of +.Fa id +is copied, and no references to the passed pointer are kept. +.Pp +The +.Fn fido_assert_set_extensions +function sets the extensions of +.Fa assert +to the bitmask +.Fa flags . +At the moment, only the +.Dv FIDO_EXT_CRED_BLOB , +.Dv FIDO_EXT_HMAC_SECRET , +and +.Dv FIDO_EXT_LARGEBLOB_KEY +extensions are supported. +If +.Fa flags +is zero, the extensions of +.Fa assert +are cleared. +.Pp +The +.Fn fido_assert_set_hmac_salt +and +.Fn fido_assert_set_hmac_secret +functions set the hmac-salt and hmac-secret parts of +.Fa assert +to +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +A copy of +.Fa ptr +is made, and no references to the passed pointer are kept. +The HMAC Secret +.Pq hmac-secret +Extension is a CTAP 2.0 extension. +Note that the resulting hmac-secret varies according to whether +user verification was performed by the authenticator. +The +.Fn fido_assert_set_hmac_secret +function is normally only useful when writing tests. +.Pp +The +.Fn fido_assert_set_up +and +.Fn fido_assert_set_uv +functions set the +.Fa up +(user presence) and +.Fa uv +(user verification) +attributes of +.Fa assert . +Both are +.Dv FIDO_OPT_OMIT +by default, allowing the authenticator to use its default settings. +.Pp +The +.Fn fido_assert_set_winhello_appid +function sets the U2F application +.Fa id +.Pq Dq U2F AppID +of +.Fa assert , +where +.Fa id +is a NUL-terminated UTF-8 string. +The content of +.Fa id +is copied, and no references to the passed pointer are kept. +The +.Fn fido_assert_set_winhello_appid +function is a no-op unless +.Fa assert +is passed to +.Xr fido_dev_get_assert 3 +with a device +.Fa dev +on which +.Xr fido_dev_is_winhello 3 +holds true. +In this case, +.Em libfido2 +will instruct Windows Hello to try the assertion twice, +first with the +.Fa id +passed to +.Fn fido_assert_set_rp , +and a second time with the +.Fa id +passed to +.Fn fido_assert_set_winhello_appid . +If the second assertion succeeds, +.Xr fido_assert_rp_id 3 +will point to the U2F AppID once +.Xr fido_dev_get_assert 3 +completes. +This mechanism exists in Windows Hello to ensure U2F backwards +compatibility without the application inadvertently prompting the +user twice. +Note that +.Fn fido_assert_set_winhello_appid +is not needed on platforms offering CTAP primitives, since the +authenticator can be silently probed for the existence of U2F +credentials. +.Pp +Use of the +.Nm +set of functions may happen in two distinct situations: +when asking a FIDO2 device to produce a series of assertion +statements, prior to +.Xr fido_dev_get_assert 3 +(i.e, in the context of a FIDO2 client), or when verifying assertion +statements using +.Xr fido_assert_verify 3 +(i.e, in the context of a FIDO2 server). +.Pp +For a complete description of the generation of a FIDO2 assertion +and its verification, please refer to the FIDO2 specification. +An example of how to use the +.Nm +set of functions can be found in the +.Pa examples/assert.c +file shipped with +.Em libfido2 . +.Sh RETURN VALUES +The +.Nm +functions return +.Dv FIDO_OK +on success. +The error codes returned by the +.Nm +set of functions are defined in +.In fido/err.h . +.Sh SEE ALSO +.Xr fido_assert_allow_cred 3 , +.Xr fido_assert_verify 3 , +.Xr fido_dev_get_assert 3 , +.Xr fido_dev_is_winhello 3 diff --git a/man/fido_assert_verify.3 b/man/fido_assert_verify.3 new file mode 100644 index 0000000..1b79448 --- /dev/null +++ b/man/fido_assert_verify.3 @@ -0,0 +1,104 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 15 2022 $ +.Dt FIDO_ASSERT_VERIFY 3 +.Os +.Sh NAME +.Nm fido_assert_verify +.Nd verifies the signature of a FIDO2 assertion statement +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_assert_verify "const fido_assert_t *assert" "size_t idx" "int cose_alg" "const void *pk" +.Sh DESCRIPTION +The +.Fn fido_assert_verify +function verifies whether the signature contained in statement index +.Fa idx +of +.Fa assert +matches the parameters of the assertion. +Before using +.Fn fido_assert_verify +in a sensitive context, the reader is strongly encouraged to make +herself familiar with the FIDO2 assertion statement process +as defined in the Web Authentication (webauthn) standard. +.Pp +A brief description follows: +.Pp +The +.Fn fido_assert_verify +function verifies whether the client data hash, relying party ID, +user presence and user verification attributes of +.Fa assert +have been attested by the holder of the private counterpart of +the public key +.Fa pk +of COSE type +.Fa cose_alg , +where +.Fa cose_alg +is +.Dv COSE_ES256 , +.Dv COSE_ES384 , +.Dv COSE_RS256 , +or +.Dv COSE_EDDSA , +and +.Fa pk +points to a +.Vt es256_pk_t , +.Vt es384_pk_t , +.Vt rs256_pk_t , +or +.Vt eddsa_pk_t +type accordingly. +.Pp +Please note that the first statement in +.Fa assert +has an +.Fa idx +of 0. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_assert_verify +are defined in +.In fido/err.h . +If +statement +.Fa idx +of +.Fa assert +passes verification with +.Fa pk , +then +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_assert_new 3 , +.Xr fido_assert_set_authdata 3 diff --git a/man/fido_bio_dev_get_info.3 b/man/fido_bio_dev_get_info.3 new file mode 100644 index 0000000..b8fc104 --- /dev/null +++ b/man/fido_bio_dev_get_info.3 @@ -0,0 +1,145 @@ +.\" Copyright (c) 2019 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: September 13 2019 $ +.Dt FIDO_BIO_DEV_GET_INFO 3 +.Os +.Sh NAME +.Nm fido_bio_dev_get_info , +.Nm fido_bio_dev_enroll_begin , +.Nm fido_bio_dev_enroll_continue , +.Nm fido_bio_dev_enroll_cancel , +.Nm fido_bio_dev_enroll_remove , +.Nm fido_bio_dev_get_template_array , +.Nm fido_bio_dev_set_template_name +.Nd FIDO2 biometric authenticator API +.Sh SYNOPSIS +.In fido.h +.In fido/bio.h +.Ft int +.Fn fido_bio_dev_get_info "fido_dev_t *dev" "fido_bio_info_t *info" +.Ft int +.Fn fido_bio_dev_enroll_begin "fido_dev_t *dev" "fido_bio_template_t *template" "fido_bio_enroll_t *enroll" "uint32_t timeout_ms" "const char *pin" +.Ft int +.Fn fido_bio_dev_enroll_continue "fido_dev_t *dev" "const fido_bio_template_t *template" "fido_bio_enroll_t *enroll" "uint32_t timeout_ms" +.Ft int +.Fn fido_bio_dev_enroll_cancel "fido_dev_t *dev" +.Ft int +.Fn fido_bio_dev_enroll_remove "fido_dev_t *dev" "const fido_bio_template_t *template" "const char *pin" +.Ft int +.Fn fido_bio_dev_get_template_array "fido_dev_t *dev" "fido_bio_template_array_t *template_array" "const char *pin" +.Ft int +.Fn fido_bio_dev_set_template_name "fido_dev_t *dev" "const fido_bio_template_t *template" "const char *pin" +.Sh DESCRIPTION +The functions described in this page allow biometric +templates on a FIDO2 authenticator to be listed, created, +removed, and customised. +Please note that not all FIDO2 authenticators support biometric +enrollment. +For a description of the types involved, please refer to +.Xr fido_bio_info_new 3 , +.Xr fido_bio_enroll_new 3 , +and +.Xr fido_bio_template 3 . +.Pp +The +.Fn fido_bio_dev_get_info +function populates +.Fa info +with sensor information from +.Fa dev . +.Pp +The +.Fn fido_bio_dev_enroll_begin +function initiates a biometric enrollment on +.Fa dev , +instructing the authenticator to wait +.Fa timeout_ms +milliseconds. +On success, +.Fa template +and +.Fa enroll +will be populated with the newly created template's +information and enrollment status, respectively. +.Pp +The +.Fn fido_bio_dev_enroll_continue +function continues an ongoing enrollment on +.Fa dev , +instructing the authenticator to wait +.Fa timeout_ms +milliseconds. +On success, +.Fa enroll +will be updated to reflect the status of the biometric +enrollment. +.Pp +The +.Fn fido_bio_dev_enroll_cancel +function cancels an ongoing enrollment on +.Fa dev . +.Pp +The +.Fn fido_bio_dev_enroll_remove +function removes +.Fa template +from +.Fa dev . +.Pp +The +.Fn fido_bio_dev_get_template_array +function populates +.Fa template_array +with the templates currently enrolled on +.Fa dev . +.Pp +The +.Fn fido_bio_dev_set_template_name +function sets the friendly name of +.Fa template +on +.Fa dev . +.Sh RETURN VALUES +The error codes returned by +.Fn fido_bio_dev_get_info , +.Fn fido_bio_dev_enroll_begin , +.Fn fido_bio_dev_enroll_continue , +.Fn fido_bio_dev_enroll_cancel , +.Fn fido_bio_dev_enroll_remove , +.Fn fido_bio_dev_get_template_array , +and +.Fn fido_bio_dev_set_template_name +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_bio_enroll_new 3 , +.Xr fido_bio_info_new 3 , +.Xr fido_bio_template 3 diff --git a/man/fido_bio_enroll_new.3 b/man/fido_bio_enroll_new.3 new file mode 100644 index 0000000..536ba9a --- /dev/null +++ b/man/fido_bio_enroll_new.3 @@ -0,0 +1,118 @@ +.\" Copyright (c) 2019 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: September 13 2019 $ +.Dt FIDO_BIO_ENROLL_NEW 3 +.Os +.Sh NAME +.Nm fido_bio_enroll_new , +.Nm fido_bio_enroll_free , +.Nm fido_bio_enroll_last_status , +.Nm fido_bio_enroll_remaining_samples +.Nd FIDO2 biometric enrollment API +.Sh SYNOPSIS +.In fido.h +.In fido/bio.h +.Bd -literal +#define FIDO_BIO_ENROLL_FP_GOOD 0x00 +#define FIDO_BIO_ENROLL_FP_TOO_HIGH 0x01 +#define FIDO_BIO_ENROLL_FP_TOO_LOW 0x02 +#define FIDO_BIO_ENROLL_FP_TOO_LEFT 0x03 +#define FIDO_BIO_ENROLL_FP_TOO_RIGHT 0x04 +#define FIDO_BIO_ENROLL_FP_TOO_FAST 0x05 +#define FIDO_BIO_ENROLL_FP_TOO_SLOW 0x06 +#define FIDO_BIO_ENROLL_FP_POOR_QUALITY 0x07 +#define FIDO_BIO_ENROLL_FP_TOO_SKEWED 0x08 +#define FIDO_BIO_ENROLL_FP_TOO_SHORT 0x09 +#define FIDO_BIO_ENROLL_FP_MERGE_FAILURE 0x0a +#define FIDO_BIO_ENROLL_FP_EXISTS 0x0b +#define FIDO_BIO_ENROLL_FP_DATABASE_FULL 0x0c +#define FIDO_BIO_ENROLL_NO_USER_ACTIVITY 0x0d +#define FIDO_BIO_ENROLL_NO_USER_PRESENCE_TRANSITION 0x0e +.Ed +.Ft fido_bio_enroll_t * +.Fn fido_bio_enroll_new "void" +.Ft void +.Fn fido_bio_enroll_free "fido_bio_enroll_t **enroll_p" +.Ft uint8_t +.Fn fido_bio_enroll_last_status "const fido_bio_enroll_t *enroll" +.Ft uint8_t +.Fn fido_bio_enroll_remaining_samples "const fido_bio_enroll_t *enroll" +.Sh DESCRIPTION +Ongoing FIDO2 biometric enrollments are abstracted in +.Em libfido2 +by the +.Vt fido_bio_enroll_t +type. +.Pp +The functions described in this page allow a +.Vt fido_bio_enroll_t +type to be allocated, deallocated, and inspected. +For device operations on +.Vt fido_bio_enroll_t , +please refer to +.Xr fido_bio_dev_get_info 3 . +.Pp +The +.Fn fido_bio_enroll_new +function returns a pointer to a newly allocated, empty +.Vt fido_bio_enroll_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_bio_enroll_free +function releases the memory backing +.Fa *enroll_p , +where +.Fa *enroll_p +must have been previously allocated by +.Fn fido_bio_enroll_new . +On return, +.Fa *enroll_p +is set to NULL. +Either +.Fa enroll_p +or +.Fa *enroll_p +may be NULL, in which case +.Fn fido_bio_enroll_free +is a NOP. +.Pp +The +.Fn fido_bio_enroll_last_status +function returns the enrollment status of +.Fa enroll . +.Pp +The +.Fn fido_bio_enroll_remaining_samples +function returns the number of samples left for +.Fa enroll +to complete. +.Sh SEE ALSO +.Xr fido_bio_dev_get_info 3 , +.Xr fido_bio_template 3 diff --git a/man/fido_bio_info_new.3 b/man/fido_bio_info_new.3 new file mode 100644 index 0000000..4134306 --- /dev/null +++ b/man/fido_bio_info_new.3 @@ -0,0 +1,104 @@ +.\" Copyright (c) 2019 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: September 13 2019 $ +.Dt FIDO_BIO_INFO_NEW 3 +.Os +.Sh NAME +.Nm fido_bio_info_new , +.Nm fido_bio_info_free , +.Nm fido_bio_info_type , +.Nm fido_bio_info_max_samples +.Nd FIDO2 biometric sensor information API +.Sh SYNOPSIS +.In fido.h +.In fido/bio.h +.Ft fido_bio_info_t * +.Fn fido_bio_info_new "void" +.Ft void +.Fn fido_bio_info_free "fido_bio_info_t **info_p" +.Ft uint8_t +.Fn fido_bio_info_type "const fido_bio_info_t *info" +.Ft uint8_t +.Fn fido_bio_info_max_samples "const fido_bio_info_t *info" +.Sh DESCRIPTION +Biometric sensor metadata is abstracted in +.Em libfido2 +by the +.Vt fido_bio_info_t +type. +.Pp +The functions described in this page allow a +.Vt fido_bio_info_t +type to be allocated, deallocated, and inspected. +For device operations on +.Vt fido_bio_info_t , +please refer to +.Xr fido_bio_dev_get_info 3 . +.Pp +The +.Fn fido_bio_info_new +function returns a pointer to a newly allocated, empty +.Vt fido_bio_info_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_bio_info_free +function releases the memory backing +.Fa *info_p , +where +.Fa *info_p +must have been previously allocated by +.Fn fido_bio_info_new . +On return, +.Fa *info_p +is set to NULL. +Either +.Fa info_p +or +.Fa *info_p +may be NULL, in which case +.Fn fido_bio_info_free +is a NOP. +.Pp +The +.Fn fido_bio_info_type +function returns the fingerprint sensor type, which is +.Dv 1 +for touch sensors, and +.Dv 2 +for swipe sensors. +.Pp +The +.Fn fido_bio_info_max_samples +function returns the maximum number of successful samples +required for enrollment. +.Sh SEE ALSO +.Xr fido_bio_dev_get_info 3 , +.Xr fido_bio_enroll_new 3 , +.Xr fido_bio_template 3 diff --git a/man/fido_bio_template.3 b/man/fido_bio_template.3 new file mode 100644 index 0000000..a8ff8bc --- /dev/null +++ b/man/fido_bio_template.3 @@ -0,0 +1,202 @@ +.\" Copyright (c) 2019 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: September 13 2019 $ +.Dt FIDO_BIO_TEMPLATE 3 +.Os +.Sh NAME +.Nm fido_bio_template , +.Nm fido_bio_template_array_count , +.Nm fido_bio_template_array_free , +.Nm fido_bio_template_array_new , +.Nm fido_bio_template_free , +.Nm fido_bio_template_id_len , +.Nm fido_bio_template_id_ptr , +.Nm fido_bio_template_name , +.Nm fido_bio_template_new , +.Nm fido_bio_template_set_id , +.Nm fido_bio_template_set_name +.Nd FIDO2 biometric template API +.Sh SYNOPSIS +.In fido.h +.In fido/bio.h +.Ft fido_bio_template_t * +.Fn fido_bio_template_new "void" +.Ft void +.Fn fido_bio_template_free "fido_bio_template_t **template_p" +.Ft const char * +.Fn fido_bio_template_name "const fido_bio_template_t *template" +.Ft const unsigned char * +.Fn fido_bio_template_id_ptr "const fido_bio_template_t *template" +.Ft size_t +.Fn fido_bio_template_id_len "const fido_bio_template_t *template" +.Ft int +.Fn fido_bio_template_set_id "fido_bio_template_t *template" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_bio_template_set_name "fido_bio_template_t *template" "const char *name" +.Ft fido_bio_template_array_t * +.Fn fido_bio_template_array_new "void" +.Ft void +.Fn fido_bio_template_array_free "fido_bio_template_array_t **array_p" +.Ft size_t +.Fn fido_bio_template_array_count "const fido_bio_template_array_t *array" +.Ft const fido_bio_template_t * +.Fn fido_bio_template "const fido_bio_template_array_t *array" "size_t idx" +.Sh DESCRIPTION +Existing FIDO2 biometric enrollments are abstracted in +.Em libfido2 +by the +.Vt fido_bio_template_t +and +.Vt fido_bio_template_array_t +types. +.Pp +The functions described in this page allow a +.Vt fido_bio_template_t +type to be allocated, deallocated, changed, and inspected, +and a +.Vt fido_bio_template_array_t +type to be allocated, deallocated, and inspected. +For device operations on +.Vt fido_bio_template_t +and +.Vt fido_bio_template_array_t , +please refer to +.Xr fido_bio_dev_get_info 3 . +.Pp +The +.Fn fido_bio_template_new +function returns a pointer to a newly allocated, empty +.Vt fido_bio_template_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_bio_template_free +function releases the memory backing +.Fa *template_p , +where +.Fa *template_p +must have been previously allocated by +.Fn fido_bio_template_new . +On return, +.Fa *template_p +is set to NULL. +Either +.Fa template_p +or +.Fa *template_p +may be NULL, in which case +.Fn fido_bio_template_free +is a NOP. +.Pp +The +.Fn fido_bio_template_name +function returns a pointer to a NUL-terminated string containing +the friendly name of +.Fa template , +or NULL if +.Fa template +does not have a friendly name set. +.Pp +The +.Fn fido_bio_template_id_ptr +function returns a pointer to the template id of +.Fa template , +or NULL if +.Fa template +does not have an id. +The corresponding length can be obtained by +.Fn fido_bio_template_id_len . +.Pp +The +.Fn fido_bio_template_set_name +function sets the friendly name of +.Fa template +to +.Fa name . +If +.Fa name +is NULL, the friendly name of +.Fa template +is unset. +.Pp +The +.Fn fido_bio_template_array_new +function returns a pointer to a newly allocated, empty +.Vt fido_bio_template_array_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_bio_template_array_free +function releases the memory backing +.Fa *array_p , +where +.Fa *array_p +must have been previously allocated by +.Fn fido_bio_template_array_new . +On return, +.Fa *array_p +is set to NULL. +Either +.Fa array_p +or +.Fa *array_p +may be NULL, in which case +.Fn fido_bio_template_array_free +is a NOP. +.Pp +The +.Fn fido_bio_template_array_count +function returns the number of templates in +.Fa array . +.Pp +The +.Fn fido_bio_template +function returns a pointer to the template at index +.Fa idx +in +.Fa array . +Please note that the first template in +.Fa array +has an +.Fa idx +(index) value of 0. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_bio_template_set_id +and +.Fn fido_bio_template_set_name +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_bio_dev_get_info 3 , +.Xr fido_bio_enroll_new 3 diff --git a/man/fido_cbor_info_new.3 b/man/fido_cbor_info_new.3 new file mode 100644 index 0000000..a8168c0 --- /dev/null +++ b/man/fido_cbor_info_new.3 @@ -0,0 +1,389 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: April 22 2022 $ +.Dt FIDO_CBOR_INFO_NEW 3 +.Os +.Sh NAME +.Nm fido_cbor_info_new , +.Nm fido_cbor_info_free , +.Nm fido_dev_get_cbor_info , +.Nm fido_cbor_info_aaguid_ptr , +.Nm fido_cbor_info_extensions_ptr , +.Nm fido_cbor_info_protocols_ptr , +.Nm fido_cbor_info_transports_ptr , +.Nm fido_cbor_info_versions_ptr , +.Nm fido_cbor_info_options_name_ptr , +.Nm fido_cbor_info_options_value_ptr , +.Nm fido_cbor_info_algorithm_type , +.Nm fido_cbor_info_algorithm_cose , +.Nm fido_cbor_info_algorithm_count , +.Nm fido_cbor_info_certs_name_ptr , +.Nm fido_cbor_info_certs_value_ptr , +.Nm fido_cbor_info_certs_len , +.Nm fido_cbor_info_aaguid_len , +.Nm fido_cbor_info_extensions_len , +.Nm fido_cbor_info_protocols_len , +.Nm fido_cbor_info_transports_len , +.Nm fido_cbor_info_versions_len , +.Nm fido_cbor_info_options_len , +.Nm fido_cbor_info_maxmsgsiz , +.Nm fido_cbor_info_maxcredbloblen , +.Nm fido_cbor_info_maxcredcntlst , +.Nm fido_cbor_info_maxcredidlen , +.Nm fido_cbor_info_maxlargeblob , +.Nm fido_cbor_info_maxrpid_minpinlen , +.Nm fido_cbor_info_minpinlen , +.Nm fido_cbor_info_fwversion , +.Nm fido_cbor_info_uv_attempts , +.Nm fido_cbor_info_uv_modality , +.Nm fido_cbor_info_rk_remaining , +.Nm fido_cbor_info_new_pin_required +.Nd FIDO2 CBOR Info API +.Sh SYNOPSIS +.In fido.h +.Ft fido_cbor_info_t * +.Fn fido_cbor_info_new "void" +.Ft void +.Fn fido_cbor_info_free "fido_cbor_info_t **ci_p" +.Ft int +.Fn fido_dev_get_cbor_info "fido_dev_t *dev" "fido_cbor_info_t *ci" +.Ft const unsigned char * +.Fn fido_cbor_info_aaguid_ptr "const fido_cbor_info_t *ci" +.Ft char ** +.Fn fido_cbor_info_extensions_ptr "const fido_cbor_info_t *ci" +.Ft const uint8_t * +.Fn fido_cbor_info_protocols_ptr "const fido_cbor_info_t *ci" +.Ft char ** +.Fn fido_cbor_info_transports_ptr "const fido_cbor_info_t *ci" +.Ft char ** +.Fn fido_cbor_info_versions_ptr "const fido_cbor_info_t *ci" +.Ft char ** +.Fn fido_cbor_info_options_name_ptr "const fido_cbor_info_t *ci" +.Ft const bool * +.Fn fido_cbor_info_options_value_ptr "const fido_cbor_info_t *ci" +.Ft const char * +.Fn fido_cbor_info_algorithm_type "const fido_cbor_info_t *ci" "size_t idx" +.Ft int +.Fn fido_cbor_info_algorithm_cose "const fido_cbor_info_t *ci" "size_t idx" +.Ft size_t +.Fn fido_cbor_info_algorithm_count "const fido_cbor_info_t *ci" +.Ft char ** +.Fn fido_cbor_info_certs_name_ptr "const fido_cbor_info_t *ci" +.Ft const uint64_t * +.Fn fido_cbor_info_certs_value_ptr "const fido_cbor_info_t *ci" +.Ft size_t +.Fn fido_cbor_info_certs_len "const fido_cbor_info_t *ci" +.Ft size_t +.Fn fido_cbor_info_aaguid_len "const fido_cbor_info_t *ci" +.Ft size_t +.Fn fido_cbor_info_extensions_len "const fido_cbor_info_t *ci" +.Ft size_t +.Fn fido_cbor_info_protocols_len "const fido_cbor_info_t *ci" +.Ft size_t +.Fn fido_cbor_info_transports_len "const fido_cbor_info_t *ci" +.Ft size_t +.Fn fido_cbor_info_versions_len "const fido_cbor_info_t *ci" +.Ft size_t +.Fn fido_cbor_info_options_len "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_maxmsgsiz "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_maxcredbloblen "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_maxcredcntlst "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_maxcredidlen "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_maxlargeblob "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_maxrpid_minpinlen "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_minpinlen "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_fwversion "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_uv_attempts "const fido_cbor_info_t *ci" +.Ft uint64_t +.Fn fido_cbor_info_uv_modality "const fido_cbor_info_t *ci" +.Ft int64_t +.Fn fido_cbor_info_rk_remaining "const fido_cbor_info_t *ci" +.Ft bool +.Fn fido_cbor_info_new_pin_required "const fido_cbor_info_t *ci" +.Sh DESCRIPTION +The +.Fn fido_cbor_info_new +function returns a pointer to a newly allocated, empty +.Vt fido_cbor_info_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_cbor_info_free +function releases the memory backing +.Fa *ci_p , +where +.Fa *ci_p +must have been previously allocated by +.Fn fido_cbor_info_new . +On return, +.Fa *ci_p +is set to NULL. +Either +.Fa ci_p +or +.Fa *ci_p +may be NULL, in which case +.Fn fido_cbor_info_free +is a NOP. +.Pp +The +.Fn fido_dev_get_cbor_info +function transmits a +.Dv CTAP_CBOR_GETINFO +command to +.Fa dev +and fills +.Fa ci +with attributes retrieved from the command's response. +The +.Fn fido_dev_get_cbor_info +function may block. +.Pp +The +.Fn fido_cbor_info_aaguid_ptr , +.Fn fido_cbor_info_extensions_ptr , +.Fn fido_cbor_info_protocols_ptr , +.Fn fido_cbor_info_transports_ptr , +and +.Fn fido_cbor_info_versions_ptr +functions return pointers to the authenticator attestation GUID, +supported extensions, PIN protocol, transports, and CTAP version +strings of +.Fa ci . +The corresponding length of a given attribute can be +obtained by +.Fn fido_cbor_info_aaguid_len , +.Fn fido_cbor_info_extensions_len , +.Fn fido_cbor_info_protocols_len , +.Fn fido_cbor_info_transports_len , +or +.Fn fido_cbor_info_versions_len . +.Pp +The +.Fn fido_cbor_info_options_name_ptr +and +.Fn fido_cbor_info_options_value_ptr +functions return pointers to the array of option names and their +respective values +in +.Fa ci . +The length of the options array is returned by +.Fn fido_cbor_info_options_len . +.Pp +The +.Fn fido_cbor_info_algorithm_count +function returns the number of supported algorithms in +.Fa ci . +The +.Fn fido_cbor_info_algorithm_cose +function returns the COSE identifier of algorithm +.Fa idx +in +.Fa ci , +or 0 if the COSE identifier is unknown or unset. +The +.Fn fido_cbor_info_algorithm_type +function returns the type of algorithm +.Fa idx +in +.Fa ci , +or NULL if the type is unset. +Please note that the first algorithm in +.Fa ci +has an +.Fa idx +(index) value of 0. +.Pp +The +.Fn fido_cbor_info_certs_name_ptr +and +.Fn fido_cbor_info_certs_value_ptr +functions return pointers to the array of certification names and their +respective values +in +.Fa ci . +The length of the certifications array is returned by +.Fn fido_cbor_info_certs_len . +.Pp +The +.Fn fido_cbor_info_maxmsgsiz +function returns the maximum message size attribute of +.Fa ci . +.Pp +The +.Fn fido_cbor_info_maxcredbloblen +function returns the maximum +.Dq credBlob +length in bytes supported by the authenticator as reported in +.Fa ci . +.Pp +The +.Fn fido_cbor_info_maxcredcntlst +function returns the maximum supported number of credentials in +a single credential ID list as reported in +.Fa ci . +.Pp +The +.Fn fido_cbor_info_maxcredidlen +function returns the maximum supported length of a credential ID +as reported in +.Fa ci . +.Pp +The +.Fn fido_cbor_info_maxrpid_minpinlen +function returns the maximum number of RP IDs that may be passed to +.Xr fido_dev_set_pin_minlen_rpid 3 , +as reported in +.Fa ci . +The minimum PIN length attribute is a CTAP 2.1 addition. +If the attribute is not advertised by the authenticator, the +.Fn fido_cbor_info_maxrpid_minpinlen +function returns zero. +.Pp +The +.Fn fido_cbor_info_maxlargeblob +function returns the maximum length in bytes of an authenticator's +serialized largeBlob array as reported in +.Fa ci . +.Pp +The +.Fn fido_cbor_info_minpinlen +function returns the minimum PIN length enforced by the +authenticator as reported in +.Fa ci . +The minimum PIN length attribute is a CTAP 2.1 addition. +If the attribute is not advertised by the authenticator, the +.Fn fido_cbor_info_minpinlen +function returns zero. +.Pp +The +.Fn fido_cbor_info_fwversion +function returns the firmware version attribute of +.Fa ci . +.Pp +The +.Fn fido_cbor_info_uv_attempts +function returns the number of UV attempts that the platform may +attempt before falling back to PIN authentication. +If 1, then all +.Xr fido_dev_get_uv_retry_count 3 +retries are handled internally by the authenticator and the +platform may only attempt non-PIN UV once. +The UV attempts attribute is a CTAP 2.1 addition. +If the attribute is not advertised by the authenticator, +the +.Fn fido_cbor_info_uv_attempts +function returns zero. +.Pp +The +.Fn fido_cbor_info_uv_modality +function returns a bitmask representing different UV modes +supported by the authenticator, as defined in the FIDO Registry of +Predefined Values and reported in +.Fa ci . +See the +.Em FIDO_UV_MODE_* +definitions in +.In fido/param.h +for the set of values defined by libfido2 and a brief description +of each. +The UV modality attribute is a CTAP 2.1 addition. +If the attribute is not advertised by the authenticator, the +.Fn fido_cbor_info_uv_modality +function returns zero. +.Pp +The +.Fn fido_cbor_info_rk_remaining +function returns the estimated number of additional +resident/discoverable credentials that can be stored on the +authenticator as reported in +.Fa ci . +The estimated number of remaining resident credentials is a +CTAP 2.1 addition. +If the attribute is not advertised by the authenticator, the +.Fn fido_cbor_info_rk_remaining +function returns -1. +.Pp +The +.Fn fido_cbor_info_new_pin_required +function returns whether a new PIN is required by the authenticator +as reported in +.Fa ci . +If +.Fn fido_cbor_info_new_pin_required +returns true, operations requiring PIN authentication will fail +until a new PIN is set on the authenticator. +The +.Xr fido_dev_set_pin 3 +function can be used to set a new PIN. +.Pp +A complete example of how to use these functions can be found in the +.Pa example/info.c +file shipped with +.Em libfido2 . +.Sh RETURN VALUES +The +.Fn fido_cbor_info_aaguid_ptr , +.Fn fido_cbor_info_extensions_ptr , +.Fn fido_cbor_info_protocols_ptr , +.Fn fido_cbor_info_transports_ptr , +.Fn fido_cbor_info_versions_ptr , +.Fn fido_cbor_info_options_name_ptr , +and +.Fn fido_cbor_info_options_value_ptr +functions return NULL if the respective field in +.Fa ci +is absent. +If not NULL, returned pointers are guaranteed to exist until any +API function that takes +.Fa ci +without the +.Em const +qualifier is invoked. +.Sh SEE ALSO +.Xr fido_dev_get_uv_retry_count 3 , +.Xr fido_dev_open 3 , +.Xr fido_dev_set_pin 3 , +.Xr fido_dev_set_pin_minlen_rpid 3 +.Rs +.%D 2021-05-25 +.%O Review Draft, Version 2.2 +.%Q FIDO Alliance +.%R FIDO Registry of Predefined Values +.%U https://fidoalliance.org/specs/common-specs/fido-registry-v2.2-rd-20210525.html +.Re diff --git a/man/fido_cred_exclude.3 b/man/fido_cred_exclude.3 new file mode 100644 index 0000000..d5e840d --- /dev/null +++ b/man/fido_cred_exclude.3 @@ -0,0 +1,93 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: December 2 2022 $ +.Dt FIDO_CRED_EXCLUDE 3 +.Os +.Sh NAME +.Nm fido_cred_exclude , +.Nm fido_cred_empty_exclude_list +.Nd manage exclude lists in a FIDO2 credential +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_cred_exclude "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_empty_exclude_list "fido_cred_t *cred" +.Sh DESCRIPTION +The +.Fn fido_cred_exclude +function adds +.Fa ptr +to the list of credentials excluded by +.Fa cred , +where +.Fa ptr +points to a credential ID of +.Fa len +bytes. +A copy of +.Fa ptr +is made, and no references to the passed pointer are kept. +If +.Fn fido_cred_exclude +fails, the existing list of excluded credentials is preserved. +.Pp +If +.Nm +returns success and +.Fa cred +is later passed to +.Xr fido_dev_make_cred 3 +on a device that contains the credential +denoted by +.Fa ptr , +then +.Xr fido_dev_make_cred 3 +will fail. +.Pp +For the format of a FIDO2 credential ID, please refer to the +Web Authentication (webauthn) standard. +.Pp +The +.Fn fido_cred_empty_exclude_list +function empties the list of credentials excluded by +.Fa cred . +.Sh RETURN VALUES +The error codes returned by +.Fn fido_cred_exclude +and +.Fn fido_cred_empty_exclude_list +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_cred_new 3 , +.Xr fido_cred_set_authdata 3 , +.Xr fido_dev_make_cred 3 diff --git a/man/fido_cred_new.3 b/man/fido_cred_new.3 new file mode 100644 index 0000000..4f8b1be --- /dev/null +++ b/man/fido_cred_new.3 @@ -0,0 +1,316 @@ +.\" Copyright (c) 2018-2021 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_CRED_NEW 3 +.Os +.Sh NAME +.Nm fido_cred_new , +.Nm fido_cred_free , +.Nm fido_cred_pin_minlen , +.Nm fido_cred_prot , +.Nm fido_cred_fmt , +.Nm fido_cred_rp_id , +.Nm fido_cred_rp_name , +.Nm fido_cred_user_name , +.Nm fido_cred_display_name , +.Nm fido_cred_authdata_ptr , +.Nm fido_cred_authdata_raw_ptr , +.Nm fido_cred_clientdata_hash_ptr , +.Nm fido_cred_id_ptr , +.Nm fido_cred_aaguid_ptr , +.Nm fido_cred_largeblob_key_ptr , +.Nm fido_cred_pubkey_ptr , +.Nm fido_cred_sig_ptr , +.Nm fido_cred_user_id_ptr , +.Nm fido_cred_x5c_ptr , +.Nm fido_cred_attstmt_ptr , +.Nm fido_cred_authdata_len , +.Nm fido_cred_authdata_raw_len , +.Nm fido_cred_clientdata_hash_len , +.Nm fido_cred_id_len , +.Nm fido_cred_aaguid_len , +.Nm fido_cred_largeblob_key_len , +.Nm fido_cred_pubkey_len , +.Nm fido_cred_sig_len , +.Nm fido_cred_user_id_len , +.Nm fido_cred_x5c_len , +.Nm fido_cred_attstmt_len , +.Nm fido_cred_type , +.Nm fido_cred_flags , +.Nm fido_cred_sigcount +.Nd FIDO2 credential API +.Sh SYNOPSIS +.In fido.h +.Ft fido_cred_t * +.Fn fido_cred_new "void" +.Ft void +.Fn fido_cred_free "fido_cred_t **cred_p" +.Ft size_t +.Fn fido_cred_pin_minlen "const fido_cred_t *cred" +.Ft int +.Fn fido_cred_prot "const fido_cred_t *cred" +.Ft const char * +.Fn fido_cred_fmt "const fido_cred_t *cred" +.Ft const char * +.Fn fido_cred_rp_id "const fido_cred_t *cred" +.Ft const char * +.Fn fido_cred_rp_name "const fido_cred_t *cred" +.Ft const char * +.Fn fido_cred_user_name "const fido_cred_t *cred" +.Ft const char * +.Fn fido_cred_display_name "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_authdata_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_authdata_raw_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_clientdata_hash_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_id_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_aaguid_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_largeblob_key_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_pubkey_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_sig_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_user_id_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_x5c_ptr "const fido_cred_t *cred" +.Ft const unsigned char * +.Fn fido_cred_attstmt_ptr "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_authdata_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_authdata_raw_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_clientdata_hash_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_id_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_aaguid_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_largeblob_key_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_pubkey_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_sig_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_user_id_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_x5c_len "const fido_cred_t *cred" +.Ft size_t +.Fn fido_cred_attstmt_len "const fido_cred_t *cred" +.Ft int +.Fn fido_cred_type "const fido_cred_t *cred" +.Ft uint8_t +.Fn fido_cred_flags "const fido_cred_t *cred" +.Ft uint32_t +.Fn fido_cred_sigcount "const fido_cred_t *cred" +.Sh DESCRIPTION +FIDO2 credentials are abstracted in +.Em libfido2 +by the +.Vt fido_cred_t +type. +The functions described in this page allow a +.Vt fido_cred_t +type to be allocated, deallocated, and inspected. +For other operations on +.Vt fido_cred_t , +please refer to +.Xr fido_cred_set_authdata 3 , +.Xr fido_cred_exclude 3 , +.Xr fido_cred_verify 3 , +and +.Xr fido_dev_make_cred 3 . +.Pp +The +.Fn fido_cred_new +function returns a pointer to a newly allocated, empty +.Vt fido_cred_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_cred_free +function releases the memory backing +.Fa *cred_p , +where +.Fa *cred_p +must have been previously allocated by +.Fn fido_cred_new . +On return, +.Fa *cred_p +is set to NULL. +Either +.Fa cred_p +or +.Fa *cred_p +may be NULL, in which case +.Fn fido_cred_free +is a NOP. +.Pp +If the CTAP 2.1 +.Dv FIDO_EXT_MINPINLEN +extension is enabled on +.Fa cred , +then the +.Fn fido_cred_pin_minlen +function returns the minimum PIN length of +.Fa cred . +Otherwise, +.Fn fido_cred_pin_minlen +returns zero. +See +.Xr fido_cred_set_pin_minlen 3 +on how to enable this extension. +.Pp +If the CTAP 2.1 +.Dv FIDO_EXT_CRED_PROTECT +extension is enabled on +.Fa cred , +then the +.Fn fido_cred_prot +function returns the protection of +.Fa cred . +Otherwise, +.Fn fido_cred_prot +returns zero. +See +.Xr fido_cred_set_prot 3 +for the protection policies understood by +.Em libfido2 . +.Pp +The +.Fn fido_cred_fmt +function returns a pointer to a NUL-terminated string containing +the attestation statement format identifier of +.Fa cred , +or NULL if +.Fa cred +does not have a format set. +.Pp +The +.Fn fido_cred_rp_id , +.Fn fido_cred_rp_name , +.Fn fido_cred_user_name , +and +.Fn fido_cred_display_name +functions return pointers to NUL-terminated strings holding the +relying party ID, relying party name, user name, and user display +name attributes of +.Fa cred , +or NULL if the respective entry is not set. +.Pp +The +.Fn fido_cred_authdata_ptr , +.Fn fido_cred_authdata_raw_ptr , +.Fn fido_cred_clientdata_hash_ptr , +.Fn fido_cred_id_ptr , +.Fn fido_cred_aaguid_ptr , +.Fn fido_cred_largeblob_key_ptr , +.Fn fido_cred_pubkey_ptr , +.Fn fido_cred_sig_ptr , +.Fn fido_cred_user_id_ptr , +.Fn fido_cred_x5c_ptr , +and +.Fn fido_cred_attstmt_ptr +functions return pointers to the CBOR-encoded and raw authenticator +data, client data hash, ID, authenticator attestation GUID, +.Dq largeBlobKey , +public key, signature, user ID, x509 certificate, and attestation +statement parts of +.Fa cred , +or NULL if the respective entry is not set. +.Pp +The corresponding length can be obtained by +.Fn fido_cred_authdata_len , +.Fn fido_cred_authdata_raw_len , +.Fn fido_cred_clientdata_hash_len , +.Fn fido_cred_id_len , +.Fn fido_cred_aaguid_len , +.Fn fido_cred_largeblob_key_len , +.Fn fido_cred_pubkey_len , +.Fn fido_cred_sig_len , +.Fn fido_cred_user_id_len , +.Fn fido_cred_x5c_len , +and +.Fn fido_cred_attstmt_len . +.Pp +The authenticator data, x509 certificate, and signature parts of a +credential are typically passed to a FIDO2 server for verification. +.Pp +The +.Fn fido_cred_type +function returns the COSE algorithm of +.Fa cred . +.Pp +The +.Fn fido_cred_flags +function returns the authenticator data flags of +.Fa cred . +.Pp +The +.Fn fido_cred_sigcount +function returns the authenticator data signature counter of +.Fa cred . +.Sh RETURN VALUES +The authenticator data returned by +.Fn fido_cred_authdata_ptr +is a CBOR-encoded byte string, as obtained from the authenticator. +To obtain the decoded byte string, use +.Fn fido_cred_authdata_raw_ptr . +.Pp +If not NULL, pointers returned by +.Fn fido_cred_fmt , +.Fn fido_cred_authdata_ptr , +.Fn fido_cred_clientdata_hash_ptr , +.Fn fido_cred_id_ptr , +.Fn fido_cred_aaguid_ptr , +.Fn fido_cred_largeblob_key_ptr , +.Fn fido_cred_pubkey_ptr , +.Fn fido_cred_sig_ptr , +and +.Fn fido_cred_x5c_ptr +are guaranteed to exist until any API function that takes +.Fa cred +without the +.Em const +qualifier is invoked. +.Sh SEE ALSO +.Xr fido_cred_exclude 3 , +.Xr fido_cred_set_authdata 3 , +.Xr fido_cred_set_pin_minlen 3 , +.Xr fido_cred_set_prot 3 , +.Xr fido_cred_verify 3 , +.Xr fido_credman_metadata_new 3 , +.Xr fido_dev_largeblob_get 3 , +.Xr fido_dev_make_cred 3 diff --git a/man/fido_cred_set_authdata.3 b/man/fido_cred_set_authdata.3 new file mode 100644 index 0000000..e453832 --- /dev/null +++ b/man/fido_cred_set_authdata.3 @@ -0,0 +1,382 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 15 2022 $ +.Dt FIDO_CRED_SET_AUTHDATA 3 +.Os +.Sh NAME +.Nm fido_cred_set_authdata , +.Nm fido_cred_set_authdata_raw , +.Nm fido_cred_set_attstmt , +.Nm fido_cred_set_x509 , +.Nm fido_cred_set_sig , +.Nm fido_cred_set_id , +.Nm fido_cred_set_clientdata , +.Nm fido_cred_set_clientdata_hash , +.Nm fido_cred_set_rp , +.Nm fido_cred_set_user , +.Nm fido_cred_set_extensions , +.Nm fido_cred_set_blob , +.Nm fido_cred_set_pin_minlen , +.Nm fido_cred_set_prot , +.Nm fido_cred_set_rk , +.Nm fido_cred_set_uv , +.Nm fido_cred_set_fmt , +.Nm fido_cred_set_type +.Nd set parameters of a FIDO2 credential +.Sh SYNOPSIS +.In fido.h +.Bd -literal +typedef enum { + FIDO_OPT_OMIT = 0, /* use authenticator's default */ + FIDO_OPT_FALSE, /* explicitly set option to false */ + FIDO_OPT_TRUE, /* explicitly set option to true */ +} fido_opt_t; +.Ed +.Ft int +.Fn fido_cred_set_authdata "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_authdata_raw "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_attstmt "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_x509 "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_sig "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_id "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_clientdata "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_clientdata_hash "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_rp "fido_cred_t *cred" "const char *id" "const char *name" +.Ft int +.Fn fido_cred_set_user "fido_cred_t *cred" "const unsigned char *user_id" "size_t user_id_len" "const char *name" "const char *display_name" "const char *icon" +.Ft int +.Fn fido_cred_set_extensions "fido_cred_t *cred" "int flags" +.Ft int +.Fn fido_cred_set_blob "fido_cred_t *cred" "const unsigned char *ptr" "size_t len" +.Ft int +.Fn fido_cred_set_pin_minlen "fido_cred_t *cred" "size_t len" +.Ft int +.Fn fido_cred_set_prot "fido_cred_t *cred" "int prot" +.Ft int +.Fn fido_cred_set_rk "fido_cred_t *cred" "fido_opt_t rk" +.Ft int +.Fn fido_cred_set_uv "fido_cred_t *cred" "fido_opt_t uv" +.Ft int +.Fn fido_cred_set_fmt "fido_cred_t *cred" "const char *ptr" +.Ft int +.Fn fido_cred_set_type "fido_cred_t *cred" "int cose_alg" +.Sh DESCRIPTION +The +.Nm +set of functions define the various parameters of a FIDO2 +credential, allowing a +.Fa fido_cred_t +type to be prepared for a subsequent call to +.Xr fido_dev_make_cred 3 +or +.Xr fido_cred_verify 3 . +For the complete specification of a FIDO2 credential and the format +of its constituent parts, please refer to the Web Authentication +(webauthn) standard. +.Pp +The +.Fn fido_cred_set_authdata , +.Fn fido_cred_set_attstmt , +.Fn fido_cred_set_x509 , +.Fn fido_cred_set_sig , +.Fn fido_cred_set_id , +and +.Fn fido_cred_set_clientdata_hash +functions set the authenticator data, attestation statement, +attestation certificate, attestation signature, id, and client +data hash parts of +.Fa cred +to +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +A copy of +.Fa ptr +is made, and no references to the passed pointer are kept. +.Pp +The authenticator data passed to +.Fn fido_cred_set_authdata +must be a CBOR-encoded byte string, as obtained from +.Fn fido_cred_authdata_ptr . +Alternatively, a raw binary blob may be passed to +.Fn fido_cred_set_authdata_raw . +An application calling +.Fn fido_cred_set_authdata +does not need to call +.Fn fido_cred_set_id . +The latter is meant to be used in contexts where the +credential's authenticator data is not available. +.Pp +The attestation statement passed to +.Fn fido_cred_set_attstmt +must be a CBOR-encoded map, as obtained from +.Fn fido_cred_attstmt_ptr . +An application calling +.Fn fido_cred_set_attstmt +does not need to call +.Fn fido_cred_set_x509 +or +.Fn fido_cred_set_sig . +The latter two are meant to be used in contexts where the +credential's complete attestation statement is not available or +required. +.Pp +The +.Fn fido_cred_set_clientdata +function allows an application to set the client data hash of +.Fa cred +by specifying the credential's unhashed client data. +This is required by Windows Hello, which calculates the client data +hash internally. +For compatibility with Windows Hello, applications should use +.Fn fido_cred_set_clientdata +instead of +.Fn fido_cred_set_clientdata_hash . +.Pp +The +.Fn fido_cred_set_rp +function sets the relying party +.Fa id +and +.Fa name +parameters of +.Fa cred , +where +.Fa id +and +.Fa name +are NUL-terminated UTF-8 strings. +The contents of +.Fa id +and +.Fa name +are copied, and no references to the passed pointers are kept. +.Pp +The +.Fn fido_cred_set_user +function sets the user attributes of +.Fa cred , +where +.Fa user_id +points to +.Fa user_id_len +bytes and +.Fa name , +.Fa display_name , +and +.Fa icon +are NUL-terminated UTF-8 strings. +The contents of +.Fa user_id , +.Fa name , +.Fa display_name , +and +.Fa icon +are copied, and no references to the passed pointers are kept. +Previously set user attributes are flushed. +The +.Fa user_id , +.Fa name , +.Fa display_name , +and +.Fa icon +parameters may be NULL. +.Pp +The +.Fn fido_cred_set_extensions +function sets the extensions of +.Fa cred +to the bitmask +.Fa flags . +At the moment, only the +.Dv FIDO_EXT_CRED_BLOB , +.Dv FIDO_EXT_CRED_PROTECT , +.Dv FIDO_EXT_HMAC_SECRET , +.Dv FIDO_EXT_MINPINLEN , +and +.Dv FIDO_EXT_LARGEBLOB_KEY +extensions are supported. +If +.Fa flags +is zero, the extensions of +.Fa cred +are cleared. +.Pp +The +.Fn fido_cred_set_blob +function sets the +.Dq credBlob +to be stored with +.Fa cred +to the data pointed to by +.Fa ptr , +which must be +.Fa len +bytes long. +.Pp +The +.Fn fido_cred_set_pin_minlen +function enables the CTAP 2.1 +.Dv FIDO_EXT_MINPINLEN +extension on +.Fa cred +and sets the expected minimum PIN length of +.Fa cred +to +.Fa len , +where +.Fa len +is greater than zero. +If +.Fa len +is zero, the +.Dv FIDO_EXT_MINPINLEN +extension is disabled on +.Fa cred . +.Pp +The +.Fn fido_cred_set_prot +function enables the CTAP 2.1 +.Dv FIDO_EXT_CRED_PROTECT +extension on +.Fa cred +and sets the protection of +.Fa cred +to the scalar +.Fa prot . +At the moment, only the +.Dv FIDO_CRED_PROT_UV_OPTIONAL , +.Dv FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID , +and +.Dv FIDO_CRED_PROT_UV_REQUIRED +protections are supported. +If +.Fa prot +is zero, the protection of +.Fa cred +is cleared. +.Pp +The +.Fn fido_cred_set_rk +and +.Fn fido_cred_set_uv +functions set the +.Em rk +.Pq resident/discoverable key +and +.Em uv +.Pq user verification +attributes of +.Fa cred . +Both are +.Dv FIDO_OPT_OMIT +by default, allowing the authenticator to use its default settings. +.Pp +The +.Fn fido_cred_set_fmt +function sets the attestation statement format identifier of +.Fa cred +to +.Fa fmt , +where +.Fa fmt +must be +.Vt "packed" +.Pq the format used in FIDO2 , +.Vt "fido-u2f" +.Pq the format used in U2F , +.Vt "tpm" +.Pq the format used by TPM-based authenticators , +or +.Vt "none" . +A copy of +.Fa fmt +is made, and no references to the passed pointer are kept. +Note that not all authenticators support FIDO2 and therefore may only +be able to generate +.Vt fido-u2f +attestation statements. +.Pp +The +.Fn fido_cred_set_type +function sets the type of +.Fa cred to +.Fa cose_alg , +where +.Fa cose_alg +is +.Dv COSE_ES256 , +.Dv COSE_ES384 , +.Dv COSE_RS256 , +or +.Dv COSE_EDDSA . +The type of a credential may only be set once. +Note that not all authenticators support COSE_RS256, COSE_ES384, or +COSE_EDDSA. +.Pp +Use of the +.Nm +set of functions may happen in two distinct situations: +when generating a new credential on a FIDO2 device, prior to +.Xr fido_dev_make_cred 3 +(i.e, in the context of a FIDO2 client), or when validating +a generated credential using +.Xr fido_cred_verify 3 +(i.e, in the context of a FIDO2 server). +.Pp +For a complete description of the generation of a FIDO2 credential +and its verification, please refer to the FIDO2 specification. +A concrete utilisation example of the +.Nm +set of functions can be found in the +.Pa cred.c +example shipped with +.Em libfido2 . +.Sh RETURN VALUES +The error codes returned by the +.Nm +set of functions are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_cred_exclude 3 , +.Xr fido_cred_verify 3 , +.Xr fido_dev_make_cred 3 diff --git a/man/fido_cred_verify.3 b/man/fido_cred_verify.3 new file mode 100644 index 0000000..9548870 --- /dev/null +++ b/man/fido_cred_verify.3 @@ -0,0 +1,114 @@ +.\" Copyright (c) 2018-2021 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_CRED_VERIFY 3 +.Os +.Sh NAME +.Nm fido_cred_verify , +.Nm fido_cred_verify_self +.Nd verify the attestation signature of a FIDO2 credential +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_cred_verify "const fido_cred_t *cred" +.Ft int +.Fn fido_cred_verify_self "const fido_cred_t *cred" +.Sh DESCRIPTION +The +.Fn fido_cred_verify +and +.Fn fido_cred_verify_self +functions verify whether the attestation signature contained in +.Fa cred +matches the attributes of the credential. +Before using +.Fn fido_cred_verify +or +.Fn fido_cred_verify_self +in a sensitive context, the reader is strongly encouraged to make +herself familiar with the FIDO2 credential attestation process +as defined in the Web Authentication (webauthn) standard. +.Pp +The +.Fn fido_cred_verify +function verifies whether the client data hash, relying party ID, +credential ID, type, protection policy, minimum PIN length, and +resident/discoverable key and user verification attributes of +.Fa cred +have been attested by the holder of the private counterpart of +the public key contained in the credential's x509 certificate. +.Pp +Please note that the x509 certificate itself is not verified. +.Pp +The attestation statement formats supported by +.Fn fido_cred_verify +are +.Em packed , +.Em fido-u2f , +and +.Em tpm . +The attestation type implemented by +.Fn fido_cred_verify +is +.Em Basic Attestation . +.Pp +The +.Fn fido_cred_verify_self +function verifies whether the client data hash, relying party ID, +credential ID, type, protection policy, minimum PIN length, and +resident/discoverable key and user verification attributes of +.Fa cred +have been attested by the holder of the credential's private key. +.Pp +The attestation statement formats supported by +.Fn fido_cred_verify_self +are +.Em packed +and +.Em fido-u2f . +The attestation type implemented by +.Fn fido_cred_verify_self +is +.Em Self Attestation . +.Pp +Other attestation formats and types are not supported. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_cred_verify +and +.Fn fido_cred_verify_self +are defined in +.In fido/err.h . +If +.Fa cred +passes verification, then +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_cred_new 3 , +.Xr fido_cred_set_authdata 3 diff --git a/man/fido_credman_metadata_new.3 b/man/fido_credman_metadata_new.3 new file mode 100644 index 0000000..122020b --- /dev/null +++ b/man/fido_credman_metadata_new.3 @@ -0,0 +1,349 @@ +.\" Copyright (c) 2019-2021 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: June 28 2019 $ +.Dt FIDO_CREDMAN_METADATA_NEW 3 +.Os +.Sh NAME +.Nm fido_credman_metadata_new , +.Nm fido_credman_rk_new , +.Nm fido_credman_rp_new , +.Nm fido_credman_metadata_free , +.Nm fido_credman_rk_free , +.Nm fido_credman_rp_free , +.Nm fido_credman_rk_existing , +.Nm fido_credman_rk_remaining , +.Nm fido_credman_rk , +.Nm fido_credman_rk_count , +.Nm fido_credman_rp_id , +.Nm fido_credman_rp_name , +.Nm fido_credman_rp_count , +.Nm fido_credman_rp_id_hash_ptr , +.Nm fido_credman_rp_id_hash_len , +.Nm fido_credman_get_dev_metadata , +.Nm fido_credman_get_dev_rk , +.Nm fido_credman_set_dev_rk , +.Nm fido_credman_del_dev_rk , +.Nm fido_credman_get_dev_rp +.Nd FIDO2 credential management API +.Sh SYNOPSIS +.In fido.h +.In fido/credman.h +.Ft fido_credman_metadata_t * +.Fn fido_credman_metadata_new "void" +.Ft fido_credman_rk_t * +.Fn fido_credman_rk_new "void" +.Ft fido_credman_rp_t * +.Fn fido_credman_rp_new "void" +.Ft void +.Fn fido_credman_metadata_free "fido_credman_metadata_t **metadata_p" +.Ft void +.Fn fido_credman_rk_free "fido_credman_rk_t **rk_p" +.Ft void +.Fn fido_credman_rp_free "fido_credman_rp_t **rp_p" +.Ft uint64_t +.Fn fido_credman_rk_existing "const fido_credman_metadata_t *metadata" +.Ft uint64_t +.Fn fido_credman_rk_remaining "const fido_credman_metadata_t *metadata" +.Ft const fido_cred_t * +.Fn fido_credman_rk "const fido_credman_rk_t *rk" "size_t idx" +.Ft size_t +.Fn fido_credman_rk_count "const fido_credman_rk_t *rk" +.Ft const char * +.Fn fido_credman_rp_id "const fido_credman_rp_t *rp" "size_t idx" +.Ft const char * +.Fn fido_credman_rp_name "const fido_credman_rp_t *rp" "size_t idx" +.Ft size_t +.Fn fido_credman_rp_count "const fido_credman_rp_t *rp" +.Ft const unsigned char * +.Fn fido_credman_rp_id_hash_ptr "const fido_credman_rp_t *rp" "size_t idx" +.Ft size_t +.Fn fido_credman_rp_id_hash_len "const fido_credman_rp_t *" "size_t idx" +.Ft int +.Fn fido_credman_get_dev_metadata "fido_dev_t *dev" "fido_credman_metadata_t *metadata" "const char *pin" +.Ft int +.Fn fido_credman_get_dev_rk "fido_dev_t *dev" "const char *rp_id" "fido_credman_rk_t *rk" "const char *pin" +.Ft int +.Fn fido_credman_set_dev_rk "fido_dev_t *dev" "fido_cred_t *cred" "const char *pin" +.Ft int +.Fn fido_credman_del_dev_rk "fido_dev_t *dev" "const unsigned char *cred_id" "size_t cred_id_len" "const char *pin" +.Ft int +.Fn fido_credman_get_dev_rp "fido_dev_t *dev" "fido_credman_rp_t *rp" "const char *pin" +.Sh DESCRIPTION +The credential management API of +.Em libfido2 +allows resident credentials on a FIDO2 authenticator to be listed, +inspected, modified, and removed. +Please note that not all FIDO2 authenticators support credential +management. +To obtain information on what an authenticator supports, please +refer to +.Xr fido_cbor_info_new 3 . +.Pp +The +.Vt fido_credman_metadata_t +type abstracts credential management metadata. +.Pp +The +.Fn fido_credman_metadata_new +function returns a pointer to a newly allocated, empty +.Vt fido_credman_metadata_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_credman_metadata_free +function releases the memory backing +.Fa *metadata_p , +where +.Fa *metadata_p +must have been previously allocated by +.Fn fido_credman_metadata_new . +On return, +.Fa *metadata_p +is set to NULL. +Either +.Fa metadata_p +or +.Fa *metadata_p +may be NULL, in which case +.Fn fido_credman_metadata_free +is a NOP. +.Pp +The +.Fn fido_credman_get_dev_metadata +function populates +.Fa metadata +with information retrieved from +.Fa dev . +A valid +.Fa pin +must be provided. +.Pp +The +.Fn fido_credman_rk_existing +function inspects +.Fa metadata +and returns the number of resident credentials on the +authenticator. +The +.Fn fido_credman_rk_remaining +function inspects +.Fa metadata +and returns the estimated number of resident credentials that can +be created on the authenticator. +.Pp +The +.Vt fido_credman_rk_t +type abstracts the set of resident credentials belonging to a +given relying party. +.Pp +The +.Fn fido_credman_rk_new +function returns a pointer to a newly allocated, empty +.Vt fido_credman_rk_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_credman_rk_free +function releases the memory backing +.Fa *rk_p , +where +.Fa *rk_p +must have been previously allocated by +.Fn fido_credman_rk_new . +On return, +.Fa *rk_p +is set to NULL. +Either +.Fa rk_p +or +.Fa *rk_p +may be NULL, in which case +.Fn fido_credman_rk_free +is a NOP. +.Pp +The +.Fn fido_credman_get_dev_rk +function populates +.Fa rk +with the set of resident credentials belonging to +.Fa rp_id +in +.Fa dev . +A valid +.Fa pin +must be provided. +.Pp +The +.Fn fido_credman_rk_count +function returns the number of resident credentials in +.Fa rk . +The +.Fn fido_credman_rk +function returns a pointer to the credential at index +.Fa idx +in +.Fa rk . +Please note that the first credential in +.Fa rk +has an +.Fa idx +(index) value of 0. +.Pp +The +.Fn fido_credman_set_dev_rk +function updates the credential pointed to by +.Fa cred +in +.Fa dev . +The credential id and user id attributes of +.Fa cred +must be set. +See +.Xr fido_cred_set_id 3 +and +.Xr fido_cred_set_user 3 +for details. +Only a credential's user attributes (name, display name) +may be updated at this time. +.Pp +The +.Fn fido_credman_del_dev_rk +function deletes the resident credential identified by +.Fa cred_id +from +.Fa dev , +where +.Fa cred_id +points to +.Fa cred_id_len +bytes. +A valid +.Fa pin +must be provided. +.Pp +The +.Vt fido_credman_rp_t +type abstracts information about a relying party. +.Pp +The +.Fn fido_credman_rp_new +function returns a pointer to a newly allocated, empty +.Vt fido_credman_rp_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_credman_rp_free +function releases the memory backing +.Fa *rp_p , +where +.Fa *rp_p +must have been previously allocated by +.Fn fido_credman_rp_new . +On return, +.Fa *rp_p +is set to NULL. +Either +.Fa rp_p +or +.Fa *rp_p +may be NULL, in which case +.Fn fido_credman_rp_free +is a NOP. +.Pp +The +.Fn fido_credman_get_dev_rp +function populates +.Fa rp +with information about relying parties with resident credentials +in +.Fa dev . +A valid +.Fa pin +must be provided. +.Pp +The +.Fn fido_credman_rp_count +function returns the number of relying parties in +.Fa rp . +.Pp +The +.Fn fido_credman_rp_id +and +.Fn fido_credman_rp_name +functions return pointers to the id and name of relying party +.Fa idx +in +.Fa rp . +If not NULL, the values returned by these functions point to +NUL-terminated UTF-8 strings. +Please note that the first relying party in +.Fa rp +has an +.Fa idx +(index) value of 0. +.Pp +The +.Fn fido_credman_rp_id_hash_ptr +function returns a pointer to the hashed id of relying party +.Fa idx +in +.Fa rp . +The corresponding length can be obtained by +.Fn fido_credman_rp_id_hash_len . +Please note that the first relying party in +.Fa rp +has an +.Fa idx +(index) value of 0. +.Sh RETURN VALUES +The +.Fn fido_credman_get_dev_metadata , +.Fn fido_credman_get_dev_rk , +.Fn fido_credman_set_dev_rk , +.Fn fido_credman_del_dev_rk , +and +.Fn fido_credman_get_dev_rp +functions return +.Dv FIDO_OK +on success. +On error, a different error code defined in +.In fido/err.h +is returned. +Functions returning pointers are not guaranteed to succeed, and +should have their return values checked for NULL. +.Sh SEE ALSO +.Xr fido_cbor_info_new 3 , +.Xr fido_cred_new 3 , +.Xr fido_dev_supports_credman 3 +.Sh CAVEATS +Resident credentials are called +.Dq discoverable credentials +in CTAP 2.1. diff --git a/man/fido_dev_enable_entattest.3 b/man/fido_dev_enable_entattest.3 new file mode 100644 index 0000000..7617f22 --- /dev/null +++ b/man/fido_dev_enable_entattest.3 @@ -0,0 +1,149 @@ +.\" Copyright (c) 2020-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: March 30 2022 $ +.Dt FIDO_DEV_ENABLE_ENTATTEST 3 +.Os +.Sh NAME +.Nm fido_dev_enable_entattest , +.Nm fido_dev_toggle_always_uv , +.Nm fido_dev_force_pin_change , +.Nm fido_dev_set_pin_minlen , +.Nm fido_dev_set_pin_minlen_rpid +.Nd CTAP 2.1 configuration authenticator API +.Sh SYNOPSIS +.In fido.h +.In fido/config.h +.Ft int +.Fn fido_dev_enable_entattest "fido_dev_t *dev" "const char *pin" +.Ft int +.Fn fido_dev_toggle_always_uv "fido_dev_t *dev" "const char *pin" +.Ft int +.Fn fido_dev_force_pin_change "fido_dev_t *dev" "const char *pin" +.Ft int +.Fn fido_dev_set_pin_minlen "fido_dev_t *dev" "size_t len" "const char *pin" +.Ft int +.Fn fido_dev_set_pin_minlen_rpid "fido_dev_t *dev" "const char * const *rpid" "size_t n" "const char *pin" +.Sh DESCRIPTION +The functions described in this page allow configuration of a +CTAP 2.1 authenticator. +.Pp +The +.Fn fido_dev_enable_entattest +function enables the +.Em Enterprise Attestation +feature on +.Fa dev . +.Em Enterprise Attestation +instructs the authenticator to include uniquely identifying +information in subsequent attestation statements. +The +.Fa pin +parameter may be NULL if +.Fa dev +does not have a PIN set. +.Pp +The +.Fn fido_dev_toggle_always_uv +function toggles the +.Dq user verification always +feature on +.Fa dev . +When set, this toggle enforces user verification at the +authenticator level for all known credentials. +If +.Fa dev +supports U2F (CTAP1) and the user verification methods supported by +the authenticator do not allow protection of U2F credentials, the +U2F subsystem will be disabled by the authenticator. +The +.Fa pin +parameter may be NULL if +.Fa dev +does not have a PIN set. +.Pp +The +.Fn fido_dev_force_pin_change +function instructs +.Fa dev +to require a PIN change. +Subsequent PIN authentication attempts against +.Fa dev +will fail until its PIN is changed. +.Pp +The +.Fn fido_dev_set_pin_minlen +function sets the minimum PIN length of +.Fa dev +to +.Fa len . +Minimum PIN lengths may only be increased. +.Pp +The +.Fn fido_dev_set_pin_minlen_rpid +function sets the list of relying party identifiers +.Pq RP IDs +that are allowed to obtain the minimum PIN length of +.Fa dev +through the CTAP 2.1 +.Dv FIDO_EXT_MINPINLEN +extension. +The list of RP identifiers is denoted by +.Fa rpid , +a vector of +.Fa n +NUL-terminated UTF-8 strings. +A copy of +.Fa rpid +is made, and no reference to it or its contents is kept. +The maximum value of +.Fa n +supported by the authenticator can be obtained using +.Xr fido_cbor_info_maxrpid_minpinlen 3 . +.Pp +Configuration settings are reflected in the payload returned by the +authenticator in response to a +.Xr fido_dev_get_cbor_info 3 +call. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_dev_enable_entattest , +.Fn fido_dev_toggle_always_uv , +.Fn fido_dev_force_pin_change , +.Fn fido_dev_set_pin_minlen , +and +.Fn fido_dev_set_pin_minlen_rpid +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_cbor_info_maxrpid_minpinlen 3 , +.Xr fido_cred_pin_minlen 3 , +.Xr fido_dev_get_cbor_info 3 , +.Xr fido_dev_reset 3 diff --git a/man/fido_dev_get_assert.3 b/man/fido_dev_get_assert.3 new file mode 100644 index 0000000..bb2fc43 --- /dev/null +++ b/man/fido_dev_get_assert.3 @@ -0,0 +1,99 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 24 2018 $ +.Dt FIDO_DEV_GET_ASSERT 3 +.Os +.Sh NAME +.Nm fido_dev_get_assert +.Nd obtains an assertion from a FIDO2 device +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_dev_get_assert "fido_dev_t *dev" "fido_assert_t *assert" "const char *pin" +.Sh DESCRIPTION +The +.Fn fido_dev_get_assert +function asks the FIDO2 device represented by +.Fa dev +for an assertion according to the following parameters defined in +.Fa assert : +.Pp +.Bl -dash -compact +.It +.Nm relying party ID ; +.It +.Nm client data hash ; +.It +.Nm list of allowed credential IDs ; +.It +.Nm user presence and user verification attributes . +.El +.Pp +See +.Xr fido_assert_set_authdata 3 +for information on how these values are set. +.Pp +If a PIN is not needed to authenticate the request against +.Fa dev , +then +.Fa pin +may be NULL. +Otherwise +.Fa pin +must point to a NUL-terminated UTF-8 string. +.Pp +After a successful call to +.Fn fido_dev_get_assert , +the +.Xr fido_assert_count 3 , +.Xr fido_assert_user_display_name 3 , +.Xr fido_assert_user_icon 3 , +.Xr fido_assert_user_name 3 , +.Xr fido_assert_authdata_ptr 3 , +.Xr fido_assert_user_id_ptr 3 , +.Xr fido_assert_sig_ptr 3 , +and +.Xr fido_assert_sigcount 3 +functions may be invoked on +.Fa assert +to retrieve the various attributes of the generated assertion. +.Pp +Please note that +.Fn fido_dev_get_assert +is synchronous and will block if necessary. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_dev_get_assert +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_assert_new 3 , +.Xr fido_assert_set_authdata 3 diff --git a/man/fido_dev_get_touch_begin.3 b/man/fido_dev_get_touch_begin.3 new file mode 100644 index 0000000..f015eff --- /dev/null +++ b/man/fido_dev_get_touch_begin.3 @@ -0,0 +1,96 @@ +.\" Copyright (c) 2020 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: August 5 2020 $ +.Dt FIDO_DEV_GET_TOUCH_BEGIN 3 +.Os +.Sh NAME +.Nm fido_dev_get_touch_begin , +.Nm fido_dev_get_touch_status +.Nd asynchronously wait for touch on a FIDO2 authenticator +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_dev_get_touch_begin "fido_dev_t *dev" +.Ft int +.Fn fido_dev_get_touch_status "fido_dev_t *dev" "int *touched" "int ms" +.Sh DESCRIPTION +The functions described in this page allow an application to +asynchronously wait for touch on a FIDO2 authenticator. +This is useful when multiple authenticators are present and +the application needs to know which one to use. +.Pp +The +.Fn fido_dev_get_touch_begin +function initiates a touch request on +.Fa dev . +.Pp +The +.Fn fido_dev_get_touch_status +function continues an ongoing touch request on +.Fa dev , +blocking up to +.Fa ms +milliseconds. +On success, +.Fa touched +will be updated to reflect the touch request status. +If +.Fa touched +is 1, the device was touched, and the touch request is +terminated. +If +.Fa touched +is 0, the application may call +.Fn fido_dev_get_touch_status +to continue the touch request, or +.Fn fido_dev_cancel +to terminate it. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_dev_get_touch_begin +and +.Fn fido_dev_get_touch_status +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh EXAMPLES +Please refer to +.Em examples/select.c +in +.Em libfido2's +source tree. +.Sh SEE ALSO +.Xr fido_dev_cancel 3 +.Sh CAVEATS +The +.Fn fido_dev_get_touch_status +function will cause a command to be transmitted to U2F +authenticators. +These transmissions should not exceed a frequency of 5Hz. diff --git a/man/fido_dev_info_manifest.3 b/man/fido_dev_info_manifest.3 new file mode 100644 index 0000000..a70a3cb --- /dev/null +++ b/man/fido_dev_info_manifest.3 @@ -0,0 +1,211 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: March 30 2022 $ +.Dt FIDO_DEV_INFO_MANIFEST 3 +.Os +.Sh NAME +.Nm fido_dev_info_manifest , +.Nm fido_dev_info_new , +.Nm fido_dev_info_free , +.Nm fido_dev_info_ptr , +.Nm fido_dev_info_path , +.Nm fido_dev_info_product , +.Nm fido_dev_info_vendor , +.Nm fido_dev_info_manufacturer_string , +.Nm fido_dev_info_product_string , +.Nm fido_dev_info_set +.Nd FIDO2 device discovery functions +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_dev_info_manifest "fido_dev_info_t *devlist" "size_t ilen" "size_t *olen" +.Ft fido_dev_info_t * +.Fn fido_dev_info_new "size_t n" +.Ft void +.Fn fido_dev_info_free "fido_dev_info_t **devlist_p" "size_t n" +.Ft const fido_dev_info_t * +.Fn fido_dev_info_ptr "const fido_dev_info_t *devlist" "size_t i" +.Ft const char * +.Fn fido_dev_info_path "const fido_dev_info_t *di" +.Ft int16_t +.Fn fido_dev_info_product "const fido_dev_info_t *di" +.Ft int16_t +.Fn fido_dev_info_vendor "const fido_dev_info_t *di" +.Ft const char * +.Fn fido_dev_info_manufacturer_string "const fido_dev_info_t *di" +.Ft const char * +.Fn fido_dev_info_product_string "const fido_dev_info_t *di" +.Ft int +.Fn fido_dev_info_set "fido_dev_info_t *devlist" "size_t i" "const char *path" "const char *manufacturer" "const char *product" "const fido_dev_io_t *io" "const fido_dev_transport_t *transport" +.Sh DESCRIPTION +The +.Fn fido_dev_info_manifest +function fills +.Fa devlist +with up to +.Fa ilen +FIDO2 devices found by the underlying operating system. +Currently only USB HID devices are supported. +The number of discovered devices is returned in +.Fa olen , +where +.Fa olen +is an addressable pointer. +.Pp +The +.Fn fido_dev_info_new +function returns a pointer to a newly allocated, empty device list +with +.Fa n +available slots. +If memory is not available, NULL is returned. +.Pp +The +.Fn fido_dev_info_free +function releases the memory backing +.Fa *devlist_p , +where +.Fa *devlist_p +must have been previously allocated by +.Fn fido_dev_info_new . +The number +.Fa n +of allocated slots must also be provided. +On return, +.Fa *devlist_p +is set to NULL. +Either +.Fa devlist_p +or +.Fa *devlist_p +may be NULL, in which case +.Fn fido_dev_info_free +is a NOP. +.Pp +The +.Fn fido_dev_info_ptr +function returns a pointer to slot number +.Fa i +of +.Fa devlist . +It is the caller's responsibility to ensure that +.Fa i +is bounded. +Please note that the first slot has index 0. +.Pp +The +.Fn fido_dev_info_path +function returns the filesystem path or subsystem-specific identification +string of +.Fa di . +.Pp +The +.Fn fido_dev_info_product +function returns the product ID of +.Fa di . +.Pp +The +.Fn fido_dev_info_vendor +function returns the vendor ID of +.Fa di . +.Pp +The +.Fn fido_dev_info_manufacturer_string +function returns the manufacturer string of +.Fa di . +If +.Fa di +does not have an associated manufacturer string, +.Fn fido_dev_info_manufacturer_string +returns an empty string. +.Pp +The +.Fn fido_dev_info_product_string +function returns the product string of +.Fa di . +If +.Fa di +does not have an associated product string, +.Fn fido_dev_info_product_string +returns an empty string. +.Pp +An example of how to use the functions described in this document +can be found in the +.Pa examples/manifest.c +file shipped with +.Em libfido2 . +.Pp +The +.Fn fido_dev_info_set +function initializes an entry in a device list allocated by +.Fn fido_dev_info_new +with the specified path, manufacturer, and product strings, and with +the specified I/O handlers and, optionally, transport functions, as +described in +.Xr fido_dev_set_io_functions 3 . +The +.Fa io +argument must be specified; the +.Fa transport +argument may be +.Dv NULL . +The path, I/O handlers, and transport functions will be used +automatically by +.Xr fido_dev_new_with_info 3 +and +.Xr fido_dev_open_with_info 3 . +An application can use this, for example, to substitute mock FIDO2 +devices in testing for the real ones that +.Fn fido_dev_info_manifest +would discover. +.Sh RETURN VALUES +The +.Fn fido_dev_info_manifest +function always returns +.Dv FIDO_OK . +If a discovery error occurs, the +.Fa olen +pointer is set to 0. +.Pp +On success, the +.Fn fido_dev_info_set +function returns +.Dv FIDO_OK . +On error, a different error code defined in +.In fido/err.h +is returned. +.Pp +The pointers returned by +.Fn fido_dev_info_ptr , +.Fn fido_dev_info_path , +.Fn fido_dev_info_manufacturer_string , +and +.Fn fido_dev_info_product_string +are guaranteed to exist until +.Fn fido_dev_info_free +is called on the corresponding device list. diff --git a/man/fido_dev_largeblob_get.3 b/man/fido_dev_largeblob_get.3 new file mode 100644 index 0000000..12dd319 --- /dev/null +++ b/man/fido_dev_largeblob_get.3 @@ -0,0 +1,216 @@ +.\" Copyright (c) 2020 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: October 26 2020 $ +.Dt FIDO_LARGEBLOB_GET 3 +.Os +.Sh NAME +.Nm fido_dev_largeblob_get , +.Nm fido_dev_largeblob_set , +.Nm fido_dev_largeblob_remove , +.Nm fido_dev_largeblob_get_array , +.Nm fido_dev_largeblob_set_array +.Nd FIDO2 large blob API +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_dev_largeblob_get "fido_dev_t *dev" "const unsigned char *key_ptr" "size_t key_len" "unsigned char **blob_ptr" "size_t *blob_len" +.Ft int +.Fn fido_dev_largeblob_set "fido_dev_t *dev" "const unsigned char *key_ptr" "size_t key_len" "const unsigned char *blob_ptr" "size_t blob_len" "const char *pin" +.Ft int +.Fn fido_dev_largeblob_remove "fido_dev_t *dev" "const unsigned char *key_ptr" "size_t key_len" "const char *pin" +.Ft int +.Fn fido_dev_largeblob_get_array "fido_dev_t *dev" "unsigned char **cbor_ptr" "size_t *cbor_len" +.Ft int +.Fn fido_dev_largeblob_set_array "fido_dev_t *dev" "const unsigned char *cbor_ptr" "size_t cbor_len" "const char *pin" +.Sh DESCRIPTION +The +.Dq largeBlobs +API of +.Em libfido2 +allows binary blobs residing on a CTAP 2.1 authenticator to be +read, written, and inspected. +.Dq largeBlobs +is a CTAP 2.1 extension. +.Pp +.Dq largeBlobs +are stored as elements of a CBOR array. +Confidentiality is ensured by encrypting each element with a +distinct, credential-bound 256-bit AES-GCM key. +The array is otherwise shared between different credentials and +FIDO2 relying parties. +.Pp +Retrieval of a credential's encryption key is possible during +enrollment with +.Xr fido_cred_set_extensions 3 +and +.Xr fido_cred_largeblob_key_ptr 3 , +during assertion with +.Xr fido_assert_set_extensions 3 +and +.Xr fido_assert_largeblob_key_ptr 3 , +or, in the case of a resident credential, via +.Em libfido2's +credential management API. +.Pp +The +.Dq largeBlobs +CBOR array is opaque to the authenticator. +Management of the array is left at the discretion of FIDO2 clients. +For further details on CTAP 2.1's +.Dq largeBlobs +extension, please refer to the CTAP 2.1 spec. +.Pp +The +.Fn fido_dev_largeblob_get +function retrieves the authenticator's +.Dq largeBlobs +CBOR array and, on success, returns the first blob +.Pq iterating from array index zero +that can be decrypted by +.Fa key_ptr , +where +.Fa key_ptr +points to +.Fa key_len +bytes. +On success, +.Fn fido_dev_largeblob_get +sets +.Fa blob_ptr +to the body of the decrypted blob, and +.Fa blob_len +to the length of the decrypted blob in bytes. +It is the caller's responsibility to free +.Fa blob_ptr . +.Pp +The +.Fn fido_dev_largeblob_set +function uses +.Fa key_ptr +to encrypt +.Fa blob_ptr +and inserts the result in the authenticator's +.Dq largeBlobs +CBOR array. +Insertion happens at the end of the array if no existing element +can be decrypted by +.Fa key_ptr , +or at the position of the first element +.Pq iterating from array index zero +that can be decrypted by +.Fa key_ptr . +.Fa key_len +holds the length of +.Fa key_ptr +in bytes, and +.Fa blob_len +the length of +.Fa blob_ptr +in bytes. +A +.Fa pin +or equivalent user-verification gesture is required. +.Pp +The +.Fn fido_dev_largeblob_remove +function retrieves the authenticator's +.Dq largeBlobs +CBOR array and, on success, drops the first blob +.Pq iterating from array index zero +that can be decrypted by +.Fa key_ptr , +where +.Fa key_ptr +points to +.Fa key_len +bytes. +A +.Fa pin +or equivalent user-verification gesture is required. +.Pp +The +.Fn fido_dev_largeblob_get_array +function retrieves the authenticator's +.Dq largeBlobs +CBOR array and, on success, +sets +.Fa cbor_ptr +to the body of the CBOR array, and +.Fa cbor_len +to its corresponding length in bytes. +It is the caller's responsibility to free +.Fa cbor_ptr . +.Pp +Finally, the +.Fn fido_dev_largeblob_set_array +function sets the authenticator's +.Dq largeBlobs +CBOR array to the data pointed to by +.Fa cbor_ptr , +where +.Fa cbor_ptr +points to +.Fa cbor_len +bytes. +A +.Fa pin +or equivalent user-verification gesture is required. +.Sh RETURN VALUES +The functions +.Fn fido_dev_largeblob_set , +.Fn fido_dev_largeblob_get , +.Fn fido_dev_largeblob_remove , +.Fn fido_dev_largeblob_get_array , +and +.Fn fido_dev_largeblob_set_array +return +.Dv FIDO_OK +on success. +On error, an error code defined in +.In fido/err.h +is returned. +.Sh SEE ALSO +.Xr fido_assert_largeblob_key_len 3 , +.Xr fido_assert_largeblob_key_ptr 3 , +.Xr fido_assert_set_extensions 3 , +.Xr fido_cred_largeblob_key_len 3 , +.Xr fido_cred_largeblob_key_ptr 3 , +.Xr fido_cred_set_extensions 3 , +.Xr fido_credman_get_dev_rk 3 , +.Xr fido_credman_get_dev_rp 3 , +.Xr fido_dev_get_assert 3 , +.Xr fido_dev_make_cred 3 +.Sh CAVEATS +The +.Dq largeBlobs +extension is not meant to be used to store sensitive data. +When retrieved, a credential's +.Dq largeBlobs +encryption key is transmitted in the clear, and an authenticator's +.Dq largeBlobs +CBOR array can be read without user interaction or verification. diff --git a/man/fido_dev_make_cred.3 b/man/fido_dev_make_cred.3 new file mode 100644 index 0000000..b13f9a1 --- /dev/null +++ b/man/fido_dev_make_cred.3 @@ -0,0 +1,100 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 23 2018 $ +.Dt FIDO_DEV_MAKE_CRED 3 +.Os +.Sh NAME +.Nm fido_dev_make_cred +.Nd generates a new credential on a FIDO2 device +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_dev_make_cred "fido_dev_t *dev" "fido_cred_t *cred" "const char *pin" +.Sh DESCRIPTION +The +.Fn fido_dev_make_cred +function asks the FIDO2 device represented by +.Fa dev +to generate a new credential according to the following parameters +defined in +.Fa cred : +.Pp +.Bl -dash -compact +.It +.Nm type ; +.It +.Nm client data hash ; +.It +.Nm relying party ; +.It +.Nm user attributes ; +.It +.Nm list of excluded credential IDs ; +.It +.Nm resident/discoverable key and user verification attributes . +.El +.Pp +See +.Xr fido_cred_set_authdata 3 +for information on how these values are set. +.Pp +If a PIN is not needed to authenticate the request against +.Fa dev , +then +.Fa pin +may be NULL. +Otherwise +.Fa pin +must point to a NUL-terminated UTF-8 string. +.Pp +After a successful call to +.Fn fido_dev_make_cred , +the +.Xr fido_cred_authdata_ptr 3 , +.Xr fido_cred_pubkey_ptr 3 , +.Xr fido_cred_x5c_ptr 3 , +and +.Xr fido_cred_sig_ptr 3 +functions may be invoked on +.Fa cred +to retrieve the various parts of the generated credential. +.Pp +Please note that +.Fn fido_dev_make_cred +is synchronous and will block if necessary. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_dev_make_cred +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_cred_new 3 , +.Xr fido_cred_set_authdata 3 diff --git a/man/fido_dev_open.3 b/man/fido_dev_open.3 new file mode 100644 index 0000000..f839e26 --- /dev/null +++ b/man/fido_dev_open.3 @@ -0,0 +1,316 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_DEV_OPEN 3 +.Os +.Sh NAME +.Nm fido_dev_open , +.Nm fido_dev_open_with_info , +.Nm fido_dev_close , +.Nm fido_dev_cancel , +.Nm fido_dev_new , +.Nm fido_dev_new_with_info , +.Nm fido_dev_free , +.Nm fido_dev_force_fido2 , +.Nm fido_dev_force_u2f , +.Nm fido_dev_is_fido2 , +.Nm fido_dev_is_winhello , +.Nm fido_dev_supports_credman , +.Nm fido_dev_supports_cred_prot , +.Nm fido_dev_supports_permissions , +.Nm fido_dev_supports_pin , +.Nm fido_dev_supports_uv , +.Nm fido_dev_has_pin , +.Nm fido_dev_has_uv , +.Nm fido_dev_protocol , +.Nm fido_dev_build , +.Nm fido_dev_flags , +.Nm fido_dev_major , +.Nm fido_dev_minor +.Nd FIDO2 device open/close and related functions +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_dev_open "fido_dev_t *dev" "const char *path" +.Ft int +.Fn fido_dev_open_with_info "fido_dev_t *dev" +.Ft int +.Fn fido_dev_close "fido_dev_t *dev" +.Ft int +.Fn fido_dev_cancel "fido_dev_t *dev" +.Ft fido_dev_t * +.Fn fido_dev_new "void" +.Ft fido_dev_t * +.Fn fido_dev_new_with_info "const fido_dev_info_t *" +.Ft void +.Fn fido_dev_free "fido_dev_t **dev_p" +.Ft void +.Fn fido_dev_force_fido2 "fido_dev_t *dev" +.Ft void +.Fn fido_dev_force_u2f "fido_dev_t *dev" +.Ft bool +.Fn fido_dev_is_fido2 "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_is_winhello "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_supports_credman "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_supports_cred_prot "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_supports_permissions "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_supports_pin "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_supports_uv "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_has_pin "const fido_dev_t *dev" +.Ft bool +.Fn fido_dev_has_uv "const fido_dev_t *dev" +.Ft uint8_t +.Fn fido_dev_protocol "const fido_dev_t *dev" +.Ft uint8_t +.Fn fido_dev_build "const fido_dev_t *dev" +.Ft uint8_t +.Fn fido_dev_flags "const fido_dev_t *dev" +.Ft uint8_t +.Fn fido_dev_major "const fido_dev_t *dev" +.Ft uint8_t +.Fn fido_dev_minor "const fido_dev_t *dev" +.Sh DESCRIPTION +The +.Fn fido_dev_open +function opens the device pointed to by +.Fa path , +where +.Fa dev +is a freshly allocated or otherwise closed +.Vt fido_dev_t . +If +.Fa dev +claims to be FIDO2, +.Em libfido2 +will attempt to speak FIDO2 to +.Fa dev . +If that fails, +.Em libfido2 +will fallback to U2F unless the +.Dv FIDO_DISABLE_U2F_FALLBACK +flag was set in +.Xr fido_init 3 . +.Pp +The +.Fn fido_dev_open_with_info +function opens +.Fa dev +as previously allocated using +.Fn fido_dev_new_with_info . +.Pp +The +.Fn fido_dev_close +function closes the device represented by +.Fa dev . +If +.Fa dev +is already closed, +.Fn fido_dev_close +is a NOP. +.Pp +The +.Fn fido_dev_cancel +function cancels any pending requests on +.Fa dev . +.Pp +The +.Fn fido_dev_new +function returns a pointer to a newly allocated, empty +.Vt fido_dev_t . +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_dev_new_with_info +function returns a pointer to a newly allocated +.Vt fido_dev_t +with +.Vt fido_dev_info_t +parameters, for use with +.Xr fido_dev_info_manifest 3 +and +.Fn fido_dev_open_with_info . +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn fido_dev_free +function releases the memory backing +.Fa *dev_p , +where +.Fa *dev_p +must have been previously allocated by +.Fn fido_dev_new . +On return, +.Fa *dev_p +is set to NULL. +Either +.Fa dev_p +or +.Fa *dev_p +may be NULL, in which case +.Fn fido_dev_free +is a NOP. +.Pp +The +.Fn fido_dev_force_fido2 +function can be used to force CTAP2 communication with +.Fa dev , +where +.Fa dev +is an open device. +.Pp +The +.Fn fido_dev_force_u2f +function can be used to force CTAP1 (U2F) communication with +.Fa dev , +where +.Fa dev +is an open device. +.Pp +The +.Fn fido_dev_is_fido2 +function returns +.Dv true +if +.Fa dev +is a FIDO2 device. +.Pp +The +.Fn fido_dev_is_winhello +function returns +.Dv true +if +.Fa dev +is a Windows Hello device. +.Pp +The +.Fn fido_dev_supports_credman +function returns +.Dv true +if +.Fa dev +supports CTAP 2.1 Credential Management. +.Pp +The +.Fn fido_dev_supports_cred_prot +function returns +.Dv true +if +.Fa dev +supports CTAP 2.1 Credential Protection. +.Pp +The +.Fn fido_dev_supports_permissions +function returns +.Dv true +if +.Fa dev +supports CTAP 2.1 UV token permissions. +.Pp +The +.Fn fido_dev_supports_pin +function returns +.Dv true +if +.Fa dev +supports CTAP 2.0 Client PINs. +.Pp +The +.Fn fido_dev_supports_uv +function returns +.Dv true +if +.Fa dev +supports a built-in user verification method. +.Pp +The +.Fn fido_dev_has_pin +function returns +.Dv true +if +.Fa dev +has a CTAP 2.0 Client PIN set. +.Pp +The +.Fn fido_dev_has_uv +function returns +.Dv true +if +.Fa dev +supports built-in user verification and its user verification +feature is configured. +.Pp +The +.Fn fido_dev_protocol +function returns the CTAPHID protocol version identifier of +.Fa dev . +.Pp +The +.Fn fido_dev_build +function returns the CTAPHID build version number of +.Fa dev . +.Pp +The +.Fn fido_dev_flags +function returns the CTAPHID capabilities flags of +.Fa dev . +.Pp +The +.Fn fido_dev_major +function returns the CTAPHID major version number of +.Fa dev . +.Pp +The +.Fn fido_dev_minor +function returns the CTAPHID minor version number of +.Fa dev . +.Pp +For the format and meaning of the CTAPHID parameters returned by +functions above, please refer to the FIDO Client to Authenticator +Protocol (CTAP) specification. +.Sh RETURN VALUES +On success, +.Fn fido_dev_open , +.Fn fido_dev_open_with_info , +and +.Fn fido_dev_close +return +.Dv FIDO_OK . +On error, a different error code defined in +.In fido/err.h +is returned. +.Sh SEE ALSO +.Xr fido_dev_info_manifest 3 , +.Xr fido_dev_set_io_functions 3 , +.Xr fido_init 3 diff --git a/man/fido_dev_set_io_functions.3 b/man/fido_dev_set_io_functions.3 new file mode 100644 index 0000000..e3e10ba --- /dev/null +++ b/man/fido_dev_set_io_functions.3 @@ -0,0 +1,261 @@ +.\" Copyright (c) 2018-2021 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_DEV_SET_IO_FUNCTIONS 3 +.Os +.Sh NAME +.Nm fido_dev_set_io_functions , +.Nm fido_dev_set_sigmask , +.Nm fido_dev_set_timeout , +.Nm fido_dev_set_transport_functions , +.Nm fido_dev_io_handle +.Nd FIDO2 device I/O interface +.Sh SYNOPSIS +.In fido.h +.Bd -literal +typedef void *fido_dev_io_open_t(const char *); +typedef void fido_dev_io_close_t(void *); +typedef int fido_dev_io_read_t(void *, unsigned char *, size_t, int); +typedef int fido_dev_io_write_t(void *, const unsigned char *, size_t); + +typedef struct fido_dev_io { + fido_dev_io_open_t *open; + fido_dev_io_close_t *close; + fido_dev_io_read_t *read; + fido_dev_io_write_t *write; +} fido_dev_io_t; + +#ifdef _WIN32 +typedef int fido_sigset_t; +#else +typedef sigset_t fido_sigset_t; +#endif + +typedef int fido_dev_rx_t(struct fido_dev *, + uint8_t, unsigned char *, size_t, int); +typedef int fido_dev_tx_t(struct fido_dev *, + uint8_t, const unsigned char *, size_t); + +typedef struct fido_dev_transport { + fido_dev_rx_t *rx; + fido_dev_tx_t *tx; +} fido_dev_transport_t; +.Ed +.Pp +.Ft int +.Fn fido_dev_set_io_functions "fido_dev_t *dev" "const fido_dev_io_t *io" +.Ft int +.Fn fido_dev_set_sigmask "fido_dev_t *dev" "const fido_sigset_t *sigmask" +.Ft int +.Fn fido_dev_set_timeout "fido_dev_t *dev" "int ms" +.Ft int +.Fn fido_dev_set_transport_functions "fido_dev_t *dev" "const fido_dev_transport_t *t" +.Ft void * +.Fn fido_dev_io_handle "const fido_dev_t *dev" +.Sh DESCRIPTION +The +.Fn fido_dev_set_io_functions +function sets the I/O handlers used by +.Em libfido2 +to talk to +.Fa dev . +By default, these handlers are set to the operating system's native HID or NFC +interfaces. +They are defined as follows: +.Bl -tag -width Ds +.It Vt fido_dev_open_t +Receives a +.Vt const char * +holding a path and opens the corresponding device, returning a +non-NULL opaque pointer on success and NULL on error. +.It Vt fido_dev_close_t +Receives the opaque pointer returned by +.Vt fido_dev_open_t +and closes the device. +.It Vt fido_dev_read_t +Reads a single transmission unit (HID report, APDU) from a device. +The first parameter is the opaque pointer returned by +.Vt fido_dev_open_t . +The second parameter is the read buffer, and the third parameter +is the read buffer size. +The fourth parameter is the number of milliseconds the caller is +willing to sleep, should the call need to block. +If this value holds -1, +.Vt fido_dev_read_t +may block indefinitely. +On success, the number of bytes read is returned. +On error, -1 is returned. +.It Vt fido_dev_write_t +Writes a single transmission unit (HID report, APDU) to +.Fa dev . +The first parameter is the opaque pointer returned by +.Vt fido_dev_open_t . +The second parameter is the write buffer, and the third parameter +is the number of bytes to be written. +A +.Vt fido_dev_write_t +may block. +On success, the number of bytes written is returned. +On error, -1 is returned. +.El +.Pp +When calling +.Fn fido_dev_set_io_functions , +the +.Fa open , +.Fa close , +.Fa read , +and +.Fa write +fields of +.Fa io +may not be NULL. +.Pp +No references to +.Fa io +are held by +.Fn fido_dev_set_io_functions . +.Pp +The +.Fn fido_dev_set_sigmask +function may be used to specify a non-NULL signal mask +.Fa sigmask +to be used while +.Em libfido2's +default I/O handlers wait on +.Fa dev . +On UNIX-like operating systems, +.Vt fido_sigset_t +is defined as +.Vt sigset_t . +On Windows, +.Vt fido_sigset_t +is defined as +.Vt int +and +.Fn fido_dev_set_sigmask +is a no-op. +.Pp +No references to +.Fa sigmask +are held by +.Fn fido_dev_set_sigmask . +.Pp +The +.Fn fido_dev_set_timeout +function informs +.Em libfido2 +not to block for more than +.Fa ms +milliseconds while communicating with +.Fa dev . +If a timeout occurs, the corresponding +.Em fido_dev_* +function will fail with +.Dv FIDO_ERR_RX . +If +.Fa ms +is -1, +then +.Em libfido2 +may block indefinitely. +This is the default behaviour. +When using the Windows Hello backend, +.Fa ms +is used as a guidance and may be overwritten by the platform. +.Pp +The +.Fn fido_dev_set_transport_functions +function sets the transport functions used by +.Em libfido2 +to talk to +.Fa dev . +While the I/O handlers are responsible for sending and receiving +transmission units of initialization and continuation packets already +formatted by +.Em libfido2 , +the transport handlers are responsible for sending and receiving +the CTAPHID commands and data directly, as defined in the FIDO Client +to Authenticator Protocol (CTAP) standard. +They are defined as follows: +.Bl -tag -width Ds +.It Vt fido_dev_tx_t +Receives a device, a CTAPHID command to transmit, a data buffer to +transmit, and the length of the data buffer. +On success, 0 is returned. +On error, -1 is returned. +.It Vt fido_dev_rx_t +Receives a device, a CTAPHID command whose response the caller expects +to receive, a data buffer to receive into, the size of the data buffer +determining the maximum length of a response, and the maximum number of +milliseconds to wait for a response. +On success, the number of bytes read into the data buffer is returned. +On error, -1 is returned. +.El +.Pp +When transport functions are specified, +.Em libfido2 +will use them instead of the +.Dv read +and +.Dv write +functions of the I/O handlers. +However, the I/O handlers must still be specified to open and close the +device. +.Pp +The +.Fn fido_dev_io_handle +function returns the opaque pointer returned by the +.Dv open +function of the I/O handlers. +This is useful mainly for the transport functions, which unlike the I/O +handlers are passed the +.Vt fido_dev_t +pointer instead of the opaque I/O handle. +.Sh RETURN VALUES +On success, +.Fn fido_dev_set_io_functions , +.Fn fido_dev_set_transport_functions , +.Fn fido_dev_set_sigmask , +and +.Fn fido_dev_set_timeout +return +.Dv FIDO_OK . +On error, a different error code defined in +.In fido/err.h +is returned. +.Sh SEE ALSO +.Xr fido_dev_info_manifest 3 , +.Xr fido_dev_open 3 +.Rs +.%D 2021-06-15 +.%O Proposed Standard, Version 2.1 +.%Q FIDO Alliance +.%R Client to Authenticator Protocol (CTAP) +.%U https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html +.Re diff --git a/man/fido_dev_set_pin.3 b/man/fido_dev_set_pin.3 new file mode 100644 index 0000000..eec062d --- /dev/null +++ b/man/fido_dev_set_pin.3 @@ -0,0 +1,128 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_DEV_SET_PIN 3 +.Os +.Sh NAME +.Nm fido_dev_set_pin , +.Nm fido_dev_get_retry_count , +.Nm fido_dev_get_uv_retry_count , +.Nm fido_dev_reset +.Nd FIDO2 device management functions +.Sh SYNOPSIS +.In fido.h +.Ft int +.Fn fido_dev_set_pin "fido_dev_t *dev" "const char *pin" "const char *oldpin" +.Ft int +.Fn fido_dev_get_retry_count "fido_dev_t *dev" "int *retries" +.Ft int +.Fn fido_dev_get_uv_retry_count "fido_dev_t *dev" "int *retries" +.Ft int +.Fn fido_dev_reset "fido_dev_t *dev" +.Sh DESCRIPTION +The +.Fn fido_dev_set_pin +function sets the PIN of device +.Fa dev +to +.Fa pin , +where +.Fa pin +is a NUL-terminated UTF-8 string. +If +.Fa oldpin +is not NULL, the device's PIN is changed from +.Fa oldpin +to +.Fa pin , +where +.Fa pin +and +.Fa oldpin +are NUL-terminated UTF-8 strings. +.Pp +The +.Fn fido_dev_get_retry_count +function fills +.Fa retries +with the number of PIN retries left in +.Fa dev +before lock-out, where +.Fa retries +is an addressable pointer. +.Pp +The +.Fn fido_dev_get_uv_retry_count +function fills +.Fa retries +with the number of built-in UV retries left in +.Fa dev +before built-in UV is disabled, where +.Fa retries +is an addressable pointer. +.Pp +The +.Fn fido_dev_reset +function performs a reset on +.Fa dev , +resetting the device's PIN and erasing credentials stored on the +device. +.Pp +Please note that +.Fn fido_dev_set_pin , +.Fn fido_dev_get_retry_count , +.Fn fido_dev_get_uv_retry_count , +and +.Fn fido_dev_reset +are synchronous and will block if necessary. +.Sh RETURN VALUES +The error codes returned by +.Fn fido_dev_set_pin , +.Fn fido_dev_get_retry_count , +.Fn fido_dev_get_uv_retry_count , +and +.Fn fido_dev_reset +are defined in +.In fido/err.h . +On success, +.Dv FIDO_OK +is returned. +.Sh SEE ALSO +.Xr fido_cbor_info_uv_attempts 3 +.Sh CAVEATS +Regarding +.Fn fido_dev_reset , +the actual user-flow to perform a reset is outside the scope of the +FIDO2 specification, and may therefore vary depending on the +authenticator. +Yubico authenticators will return +.Dv FIDO_ERR_NOT_ALLOWED +if a reset is issued later than 5 seconds after power-up, and +.Dv FIDO_ERR_ACTION_TIMEOUT +if the user fails to confirm the reset by touching the key +within 30 seconds. diff --git a/man/fido_init.3 b/man/fido_init.3 new file mode 100644 index 0000000..12437e1 --- /dev/null +++ b/man/fido_init.3 @@ -0,0 +1,95 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_INIT 3 +.Os +.Sh NAME +.Nm fido_init , +.Nm fido_set_log_handler +.Nd initialise the FIDO2 library +.Sh SYNOPSIS +.In fido.h +.Bd -literal +typedef void fido_log_handler_t(const char *); +.Ed +.Pp +.Ft void +.Fn fido_init "int flags" +.Ft void +.Fn fido_set_log_handler "fido_log_handler_t *handler" +.Sh DESCRIPTION +The +.Fn fido_init +function initialises the +.Em libfido2 +library. +Its invocation must precede that of any other +.Em libfido2 +function in the context of the executing thread. +.Pp +If +.Dv FIDO_DEBUG +is set in +.Fa flags , +then +debug output will be emitted by +.Em libfido2 +on +.Em stderr . +Alternatively, the +.Ev FIDO_DEBUG +environment variable may be set. +.Pp +If +.Dv FIDO_DISABLE_U2F_FALLBACK +is set in +.Fa flags , +then +.Em libfido2 +will not fallback to U2F in +.Xr fido_dev_open 3 +if a device claims to support FIDO2 but fails to respond to +a CTAP 2.0 greeting. +.Pp +The +.Fn fido_set_log_handler +function causes +.Fa handler +to be called for each log line generated in the context of the +executing thread. +Lines passed to +.Fa handler +include a trailing newline character and are not printed by +.Em libfido2 +on +.Em stderr . +.Sh SEE ALSO +.Xr fido_assert_new 3 , +.Xr fido_cred_new 3 , +.Xr fido_dev_info_manifest 3 , +.Xr fido_dev_open 3 diff --git a/man/fido_strerr.3 b/man/fido_strerr.3 new file mode 100644 index 0000000..94b48bd --- /dev/null +++ b/man/fido_strerr.3 @@ -0,0 +1,50 @@ +.\" Copyright (c) 2018 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: May 25 2018 $ +.Dt FIDO_STRERR 3 +.Os +.Sh NAME +.Nm fido_strerr +.Nd FIDO2 error codes +.Sh SYNOPSIS +.In fido.h +.Ft const char * +.Fn fido_strerr "int n" +.Sh DESCRIPTION +The +.Fn fido_strerr +function translates the error code +.Fa n +into a readable string, +where +.Fa n +is an error code defined in +.In fido/err.h . +.Fn fido_strerr +never returns NULL. +Returned pointers point to static strings. diff --git a/man/rs256_pk_new.3 b/man/rs256_pk_new.3 new file mode 100644 index 0000000..0c0ab78 --- /dev/null +++ b/man/rs256_pk_new.3 @@ -0,0 +1,160 @@ +.\" Copyright (c) 2018-2022 Yubico AB. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions are +.\" met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +.\" A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +.\" HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +.\" SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +.\" LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +.\" OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd $Mdocdate: July 15 2022 $ +.Dt RS256_PK_NEW 3 +.Os +.Sh NAME +.Nm rs256_pk_new , +.Nm rs256_pk_free , +.Nm rs256_pk_from_RSA , +.Nm rs256_pk_from_EVP_PKEY , +.Nm rs256_pk_from_ptr , +.Nm rs256_pk_to_EVP_PKEY +.Nd FIDO2 COSE RS256 API +.Sh SYNOPSIS +.In openssl/rsa.h +.In fido/rs256.h +.Ft rs256_pk_t * +.Fn rs256_pk_new "void" +.Ft void +.Fn rs256_pk_free "rs256_pk_t **pkp" +.Ft int +.Fn rs256_pk_from_EVP_PKEY "rs256_pk_t *pk" "const EVP_PKEY *pkey" +.Ft int +.Fn rs256_pk_from_RSA "rs256_pk_t *pk" "const RSA *rsa" +.Ft int +.Fn rs256_pk_from_ptr "rs256_pk_t *pk" "const void *ptr" "size_t len" +.Ft EVP_PKEY * +.Fn rs256_pk_to_EVP_PKEY "const rs256_pk_t *pk" +.Sh DESCRIPTION +RS256 is the name given in the CBOR Object Signing and Encryption +(COSE) RFC to PKCS#1.5 2048-bit RSA with SHA-256. +The COSE RS256 API of +.Em libfido2 +is an auxiliary API with routines to convert between the different +RSA public key types used in +.Em libfido2 +and +.Em OpenSSL . +.Pp +In +.Em libfido2 , +RS256 public keys are abstracted by the +.Vt rs256_pk_t +type. +.Pp +The +.Fn rs256_pk_new +function returns a pointer to a newly allocated, empty +.Vt rs256_pk_t +type. +If memory cannot be allocated, NULL is returned. +.Pp +The +.Fn rs256_pk_free +function releases the memory backing +.Fa *pkp , +where +.Fa *pkp +must have been previously allocated by +.Fn rs256_pk_new . +On return, +.Fa *pkp +is set to NULL. +Either +.Fa pkp +or +.Fa *pkp +may be NULL, in which case +.Fn rs256_pk_free +is a NOP. +.Pp +The +.Fn rs256_pk_from_EVP_PKEY +function fills +.Fa pk +with the contents of +.Fa pkey . +No references to +.Fa pkey +are kept. +.Pp +The +.Fn rs256_pk_from_RSA +function fills +.Fa pk +with the contents of +.Fa rsa . +No references to +.Fa rsa +are kept. +.Pp +The +.Fn rs256_pk_from_ptr +function fills +.Fa pk +with the contents of +.Fa ptr , +where +.Fa ptr +points to +.Fa len +bytes. +No references to +.Fa ptr +are kept. +.Pp +The +.Fn rs256_pk_to_EVP_PKEY +function converts +.Fa pk +to a newly allocated +.Fa EVP_PKEY +type with a reference count of 1. +No internal references to the returned pointer are kept. +If an error occurs, +.Fn rs256_pk_to_EVP_PKEY +returns NULL. +.Sh RETURN VALUES +The +.Fn rs256_pk_from_EVP_PKEY , +.Fn rs256_pk_from_RSA , +and +.Fn rs256_pk_from_ptr +functions return +.Dv FIDO_OK +on success. +On error, a different error code defined in +.In fido/err.h +is returned. +.Sh SEE ALSO +.Xr eddsa_pk_new 3 , +.Xr es256_pk_new 3 , +.Xr es384_pk_new 3 , +.Xr fido_assert_verify 3 , +.Xr fido_cred_pubkey_ptr 3 diff --git a/man/style.css b/man/style.css new file mode 100644 index 0000000..8c223fa --- /dev/null +++ b/man/style.css @@ -0,0 +1,24 @@ +* { margin: 0; padding: 0; } + +body { + font-family: monospace; + font-size: 1em; + margin: 2% auto; + max-width: 54em; +} + +ul { margin-left: 1em; } +a { color: #009900; } +.Sh { font-size: 1em; padding-top: 1em; padding-bottom: 1em; } +.foot { padding-top: 1em; } + +table.head, table.foot { width: 100%; } +td.head-rtitle, td.foot-os { text-align: right; } +td.head-vol { text-align: center; } +div.Pp { margin: 1ex 0ex; } +div.Nd, div.Bf, div.Op { display: inline; } +span.Pa, span.Ad { font-style: italic; } +span.Ms { font-weight: bold; } +dl.Bl-diag > dt { font-weight: bold; } +code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn, +code.Cd { font-weight: bold; font-family: inherit; } diff --git a/openbsd-compat/bsd-asprintf.c b/openbsd-compat/bsd-asprintf.c new file mode 100644 index 0000000..fbcb867 --- /dev/null +++ b/openbsd-compat/bsd-asprintf.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2004 Darren Tucker. + * + * Based originally on asprintf.c from OpenBSD: + * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "openbsd-compat.h" + +#ifndef HAVE_ASPRINTF + +#include <errno.h> +#include <limits.h> /* for INT_MAX */ +#include <stdarg.h> +#include <stdio.h> /* for vsnprintf */ +#include <stdlib.h> + +#define VA_COPY(dest, src) va_copy(dest, src) + +#define INIT_SZ 128 + +int +vasprintf(char **str, const char *fmt, va_list ap) +{ + int ret; + va_list ap2; + char *string, *newstr; + size_t len; + + if ((string = malloc(INIT_SZ)) == NULL) + goto fail; + + VA_COPY(ap2, ap); + ret = vsnprintf(string, INIT_SZ, fmt, ap2); + va_end(ap2); + if (ret >= 0 && ret < INIT_SZ) { /* succeeded with initial alloc */ + *str = string; + } else if (ret == INT_MAX || ret < 0) { /* Bad length */ + free(string); + goto fail; + } else { /* bigger than initial, realloc allowing for nul */ + len = (size_t)ret + 1; + if ((newstr = realloc(string, len)) == NULL) { + free(string); + goto fail; + } + VA_COPY(ap2, ap); + ret = vsnprintf(newstr, len, fmt, ap2); + va_end(ap2); + if (ret < 0 || (size_t)ret >= len) { /* failed with realloc'ed string */ + free(newstr); + goto fail; + } + *str = newstr; + } + return (ret); + +fail: + *str = NULL; + errno = ENOMEM; + return (-1); +} + +int asprintf(char **str, const char *fmt, ...) +{ + va_list ap; + int ret; + + *str = NULL; + va_start(ap, fmt); + ret = vasprintf(str, fmt, ap); + va_end(ap); + + return ret; +} +#endif diff --git a/openbsd-compat/bsd-getline.c b/openbsd-compat/bsd-getline.c new file mode 100644 index 0000000..52b44f7 --- /dev/null +++ b/openbsd-compat/bsd-getline.c @@ -0,0 +1,115 @@ +/* $NetBSD: getline.c,v 1.1.1.6 2015/01/02 20:34:27 christos Exp $ */ + +/* NetBSD: getline.c,v 1.2 2014/09/16 17:23:50 christos Exp */ + +/*- + * Copyright (c) 2011 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* NETBSD ORIGINAL: external/bsd/file/dist/src/getline.c */ + +#include "openbsd-compat.h" + +#if 0 +#include "file.h" +#endif + +#if !HAVE_GETLINE +#include <stdlib.h> +#include <stdio.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> +#include <string.h> + +static ssize_t +getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp) +{ + char *ptr, *eptr; + + + if (*buf == NULL || *bufsiz == 0) { + if ((*buf = malloc(BUFSIZ)) == NULL) + return -1; + *bufsiz = BUFSIZ; + } + + for (ptr = *buf, eptr = *buf + *bufsiz;;) { + int c = fgetc(fp); + if (c == -1) { + if (feof(fp)) { + ssize_t diff = (ssize_t)(ptr - *buf); + if (diff != 0) { + *ptr = '\0'; + return diff; + } + } + return -1; + } + *ptr++ = (char)c; + if (c == delimiter) { + *ptr = '\0'; + return ptr - *buf; + } + if (ptr + 2 >= eptr) { + char *nbuf; + size_t nbufsiz = *bufsiz * 2; + ssize_t d = ptr - *buf; + if ((nbuf = realloc(*buf, nbufsiz)) == NULL) + return -1; + *buf = nbuf; + *bufsiz = nbufsiz; + eptr = nbuf + nbufsiz; + ptr = nbuf + d; + } + } +} + +ssize_t +getline(char **buf, size_t *bufsiz, FILE *fp) +{ + return getdelim(buf, bufsiz, '\n', fp); +} + +#endif + +#ifdef TEST +int +main(int argc, char *argv[]) +{ + char *p = NULL; + ssize_t len; + size_t n = 0; + + while ((len = getline(&p, &n, stdin)) != -1) + (void)printf("%" SIZE_T_FORMAT "d %s", len, p); + free(p); + return 0; +} +#endif diff --git a/openbsd-compat/bsd-getpagesize.c b/openbsd-compat/bsd-getpagesize.c new file mode 100644 index 0000000..903bfc3 --- /dev/null +++ b/openbsd-compat/bsd-getpagesize.c @@ -0,0 +1,27 @@ +/* Placed in the public domain */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_GETPAGESIZE) + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <limits.h> + +int +getpagesize(void) +{ +#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE) + long r = sysconf(_SC_PAGESIZE); + if (r > 0 && r < INT_MAX) + return (int)r; +#endif + /* + * This is at the lower end of common values and appropriate for + * our current use of getpagesize() in recallocarray(). + */ + return 4096; +} + +#endif /* !defined(HAVE_GETPAGESIZE) */ diff --git a/openbsd-compat/clock_gettime.c b/openbsd-compat/clock_gettime.c new file mode 100644 index 0000000..bbf978c --- /dev/null +++ b/openbsd-compat/clock_gettime.c @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_CLOCK_GETTIME) + +#if _WIN32 +int +clock_gettime(clockid_t clock_id, struct timespec *tp) +{ + ULONGLONG ms; + + if (clock_id != CLOCK_MONOTONIC) { + errno = EINVAL; + return (-1); + } + + ms = GetTickCount64(); + tp->tv_sec = ms / 1000L; + tp->tv_nsec = (ms % 1000L) * 1000000L; + + return (0); +} +#else +#error "please provide an implementation of clock_gettime() for your platform" +#endif /* _WIN32 */ + +#endif /* !defined(HAVE_CLOCK_GETTIME) */ diff --git a/openbsd-compat/endian_win32.c b/openbsd-compat/endian_win32.c new file mode 100644 index 0000000..756c0cb --- /dev/null +++ b/openbsd-compat/endian_win32.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "openbsd-compat.h" + +#if defined(_WIN32) && !defined(HAVE_ENDIAN_H) + +/* + * Hopefully, if the endianness differs from the end result, the compiler + * optimizes these functions with some type of bswap instruction. Or, + * otherwise, to just return the input value unmodified. GCC and clang + * both does these optimization at least. This should be preferred over + * relying on some BYTE_ORDER macro, which may or may not be defined. + */ + +uint32_t +htole32(uint32_t in) +{ + uint32_t out = 0; + uint8_t *b = (uint8_t *)&out; + + b[0] = (uint8_t)((in >> 0) & 0xff); + b[1] = (uint8_t)((in >> 8) & 0xff); + b[2] = (uint8_t)((in >> 16) & 0xff); + b[3] = (uint8_t)((in >> 24) & 0xff); + + return (out); +} + +uint64_t +htole64(uint64_t in) +{ + uint64_t out = 0; + uint8_t *b = (uint8_t *)&out; + + b[0] = (uint8_t)((in >> 0) & 0xff); + b[1] = (uint8_t)((in >> 8) & 0xff); + b[2] = (uint8_t)((in >> 16) & 0xff); + b[3] = (uint8_t)((in >> 24) & 0xff); + b[4] = (uint8_t)((in >> 32) & 0xff); + b[5] = (uint8_t)((in >> 40) & 0xff); + b[6] = (uint8_t)((in >> 48) & 0xff); + b[7] = (uint8_t)((in >> 56) & 0xff); + + return (out); +} + +#endif /* WIN32 && !HAVE_ENDIAN_H */ diff --git a/openbsd-compat/err.h b/openbsd-compat/err.h new file mode 100644 index 0000000..394c7bb --- /dev/null +++ b/openbsd-compat/err.h @@ -0,0 +1,85 @@ +/* + * Public domain + * err.h compatibility shim + */ + +#ifndef _COMPAT_ERR_H +#define _COMPAT_ERR_H + +#if !defined(HAVE_ERR_H) + +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#if defined(_MSC_VER) +__declspec(noreturn) +#else +__attribute__((noreturn)) +#endif +static inline void +err(int eval, const char *fmt, ...) +{ + int sverrno = errno; + va_list ap; + + va_start(ap, fmt); + if (fmt != NULL) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": "); + } + va_end(ap); + fprintf(stderr, "%s\n", strerror(sverrno)); + exit(eval); +} + +#if defined(_MSC_VER) +__declspec(noreturn) +#else +__attribute__((noreturn)) +#endif +static inline void +errx(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (fmt != NULL) + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(eval); +} + +static inline void +warn(const char *fmt, ...) +{ + int sverrno = errno; + va_list ap; + + va_start(ap, fmt); + if (fmt != NULL) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, ": "); + } + va_end(ap); + fprintf(stderr, "%s\n", strerror(sverrno)); +} + +static inline void +warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (fmt != NULL) + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +#endif /* !defined(HAVE_ERR_H) */ + +#endif /* _COMPAT_ERR_H */ diff --git a/openbsd-compat/explicit_bzero.c b/openbsd-compat/explicit_bzero.c new file mode 100644 index 0000000..ac64e69 --- /dev/null +++ b/openbsd-compat/explicit_bzero.c @@ -0,0 +1,57 @@ +/* OPENBSD ORIGINAL: lib/libc/string/explicit_bzero.c */ +/* $OpenBSD: explicit_bzero.c,v 1.1 2014/01/22 21:06:45 tedu Exp $ */ +/* + * Public domain. + * Written by Ted Unangst + */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_EXPLICIT_BZERO) && !defined(_WIN32) + +#include <string.h> + +/* + * explicit_bzero - don't let the compiler optimize away bzero + */ + +#ifdef HAVE_MEMSET_S + +void +explicit_bzero(void *p, size_t n) +{ + if (n == 0) + return; + (void)memset_s(p, n, 0, n); +} + +#else /* HAVE_MEMSET_S */ + +/* + * Indirect bzero through a volatile pointer to hopefully avoid + * dead-store optimisation eliminating the call. + */ +static void (* volatile ssh_bzero)(void *, size_t) = bzero; + +void +explicit_bzero(void *p, size_t n) +{ + if (n == 0) + return; + /* + * clang -fsanitize=memory needs to intercept memset-like functions + * to correctly detect memory initialisation. Make sure one is called + * directly since our indirection trick above successfully confuses it. + */ +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) + memset(p, 0, n); +# endif +#endif + + ssh_bzero(p, n); +} + +#endif /* HAVE_MEMSET_S */ + +#endif /* !defined(HAVE_EXPLICIT_BZERO) && !defined(_WIN32) */ diff --git a/openbsd-compat/explicit_bzero_win32.c b/openbsd-compat/explicit_bzero_win32.c new file mode 100644 index 0000000..8017aff --- /dev/null +++ b/openbsd-compat/explicit_bzero_win32.c @@ -0,0 +1,19 @@ +/* + * Public domain. + * Win32 explicit_bzero compatibility shim. + */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_EXPLICIT_BZERO) && defined(_WIN32) + +#include <windows.h> +#include <string.h> + +void +explicit_bzero(void *buf, size_t len) +{ + SecureZeroMemory(buf, len); +} + +#endif /* !defined(HAVE_EXPLICIT_BZERO) && defined(_WIN32) */ diff --git a/openbsd-compat/freezero.c b/openbsd-compat/freezero.c new file mode 100644 index 0000000..d1e0066 --- /dev/null +++ b/openbsd-compat/freezero.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008, 2010, 2011, 2016 Otto Moerbeek <otto@drijf.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "openbsd-compat.h" + +#ifndef HAVE_FREEZERO + +void +freezero(void *ptr, size_t sz) +{ + if (ptr == NULL) + return; + explicit_bzero(ptr, sz); + free(ptr); +} + +#endif /* HAVE_FREEZERO */ diff --git a/openbsd-compat/getopt.h b/openbsd-compat/getopt.h new file mode 100644 index 0000000..8eb1244 --- /dev/null +++ b/openbsd-compat/getopt.h @@ -0,0 +1,74 @@ +/* $OpenBSD: getopt.h,v 1.2 2008/06/26 05:42:04 ray Exp $ */ +/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +/* + * GNU-like getopt_long() and 4.4BSD getsubopt()/optreset extensions + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +int getopt_long(int, char * const *, const char *, + const struct option *, int *); +int getopt_long_only(int, char * const *, const char *, + const struct option *, int *); +#ifndef _GETOPT_DEFINED_ +#define _GETOPT_DEFINED_ +int getopt(int, char * const *, const char *); +int getsubopt(char **, char * const *, char **); + +extern char *optarg; /* getopt(3) external variables */ +extern int opterr; +extern int optind; +extern int optopt; +extern int optreset; +extern char *suboptarg; /* getsubopt(3) external variable */ +#endif + +#endif /* !_GETOPT_H_ */ diff --git a/openbsd-compat/getopt_long.c b/openbsd-compat/getopt_long.c new file mode 100644 index 0000000..dabbb46 --- /dev/null +++ b/openbsd-compat/getopt_long.c @@ -0,0 +1,523 @@ +/* $OpenBSD: getopt_long.c,v 1.25 2011/03/05 22:10:11 guenther Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* OPENBSD ORIGINAL: lib/libc/stdlib/getopt_long.c */ +#include "openbsd-compat.h" + +#if !defined(HAVE_GETOPT) + +#if 0 +#include <err.h> +#include <getopt.h> +#endif +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too) +{ + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match; + + current_argv = place; + match = -1; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* partial match */ + match = i; + else { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + if (posixly_correct == -1 || optreset) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + if (*options == '+' || *options == '-') + options++; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} + +#if 0 +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} +#endif + +#endif /* !defined(HAVE_GETOPT) */ diff --git a/openbsd-compat/openbsd-compat.h b/openbsd-compat/openbsd-compat.h new file mode 100644 index 0000000..9f1ea3e --- /dev/null +++ b/openbsd-compat/openbsd-compat.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _OPENBSD_COMPAT_H +#define _OPENBSD_COMPAT_H + +#if defined(_MSC_VER) +#include "types.h" +#endif + +#if defined(HAVE_ENDIAN_H) +#include <endian.h> +#endif + +#if defined(__APPLE__) && !defined(HAVE_ENDIAN_H) +#include <libkern/OSByteOrder.h> +#define be16toh(x) OSSwapBigToHostInt16((x)) +#define htobe16(x) OSSwapHostToBigInt16((x)) +#define be32toh(x) OSSwapBigToHostInt32((x)) +#define htobe32(x) OSSwapHostToBigInt32((x)) +#define htole32(x) OSSwapHostToLittleInt32((x)) +#define htole64(x) OSSwapHostToLittleInt64((x)) +#endif /* __APPLE__ && !HAVE_ENDIAN_H */ + +#if defined(_WIN32) && !defined(HAVE_ENDIAN_H) +#include <stdint.h> +#include <winsock2.h> +#if !defined(_MSC_VER) +#include <sys/param.h> +#endif +#define be16toh(x) ntohs((x)) +#define htobe16(x) htons((x)) +#define be32toh(x) ntohl((x)) +#define htobe32(x) htonl((x)) +uint32_t htole32(uint32_t); +uint64_t htole64(uint64_t); +#endif /* _WIN32 && !HAVE_ENDIAN_H */ + +#if (defined(__FreeBSD__) || defined(__MidnightBSD__)) && !defined(HAVE_ENDIAN_H) +#include <sys/endian.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#if !defined(HAVE_STRLCAT) +size_t strlcat(char *, const char *, size_t); +#endif + +#if !defined(HAVE_STRLCPY) +size_t strlcpy(char *, const char *, size_t); +#endif + +#if !defined(HAVE_STRSEP) +char *strsep(char **, const char *); +#endif + +#if !defined(HAVE_RECALLOCARRAY) +void *recallocarray(void *, size_t, size_t, size_t); +#endif + +#if !defined(HAVE_EXPLICIT_BZERO) +void explicit_bzero(void *, size_t); +#endif + +#if !defined(HAVE_FREEZERO) +void freezero(void *, size_t); +#endif + +#if !defined(HAVE_GETPAGESIZE) +int getpagesize(void); +#endif + +#if !defined(HAVE_TIMINGSAFE_BCMP) +int timingsafe_bcmp(const void *, const void *, size_t); +#endif + +#if !defined(HAVE_READPASSPHRASE) +#include "readpassphrase.h" +#else +#include <readpassphrase.h> +#endif + +#include <openssl/opensslv.h> + +#if !defined(HAVE_ERR_H) +#include "err.h" +#else +#include <err.h> +#endif + +#if !defined(HAVE_GETOPT) +#include "getopt.h" +#else +#include <unistd.h> +#endif + +#if !defined(HAVE_GETLINE) +#include <stdio.h> +ssize_t getline(char **, size_t *, FILE *); +#endif + +#if defined(_MSC_VER) +#define strerror_r(e, b, l) strerror_s((b), (l), (e)) +#endif + +#include "time.h" + +#if !defined(HAVE_POSIX_IOCTL) +#define IOCTL_REQ(x) (x) +#else +#define IOCTL_REQ(x) ((int)(x)) +#endif + +#if !defined(HAVE_ASPRINTF) +int asprintf(char **, const char *, ...); +#endif + +#endif /* !_OPENBSD_COMPAT_H */ diff --git a/openbsd-compat/posix_ioctl_check.c b/openbsd-compat/posix_ioctl_check.c new file mode 100644 index 0000000..599a3bf --- /dev/null +++ b/openbsd-compat/posix_ioctl_check.c @@ -0,0 +1,7 @@ +#include <sys/ioctl.h> + +int +posix_ioctl_check(int fd) +{ + return ioctl(fd, -1, 0); +} diff --git a/openbsd-compat/posix_win.c b/openbsd-compat/posix_win.c new file mode 100644 index 0000000..eac67c2 --- /dev/null +++ b/openbsd-compat/posix_win.c @@ -0,0 +1,61 @@ +/* + * Public domain + * + * File IO compatibility shims + * Brent Cook <bcook@openbsd.org> + */ + +#define NO_REDEF_POSIX_FUNCTIONS + +#include <windows.h> + +#include <errno.h> +#include <io.h> + +#include "posix_win.h" + +int +posix_open(const char *path, ...) +{ + va_list ap; + int mode = 0; + int flags; + + va_start(ap, path); + flags = va_arg(ap, int); + if (flags & O_CREAT) + mode = va_arg(ap, int); + va_end(ap); + + flags |= O_BINARY | O_NOINHERIT; + + return (open(path, flags, mode)); +} + +int +posix_close(int fd) +{ + return (close(fd)); +} + +ssize_t +posix_read(int fd, void *buf, size_t count) +{ + if (count > INT_MAX) { + errno = EINVAL; + return (-1); + } + + return (read(fd, buf, (unsigned int)count)); +} + +ssize_t +posix_write(int fd, const void *buf, size_t count) +{ + if (count > INT_MAX) { + errno = EINVAL; + return (-1); + } + + return (write(fd, buf, (unsigned int)count)); +} diff --git a/openbsd-compat/posix_win.h b/openbsd-compat/posix_win.h new file mode 100644 index 0000000..a1e0888 --- /dev/null +++ b/openbsd-compat/posix_win.h @@ -0,0 +1,47 @@ +/* + * Public domain + * + * BSD socket emulation code for Winsock2 + * Brent Cook <bcook@openbsd.org> + */ + +#ifndef _COMPAT_POSIX_WIN_H +#define _COMPAT_POSIX_WIN_H + +#ifdef _WIN32 + +#include <windows.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if _MSC_VER >= 1900 +#include <../ucrt/fcntl.h> +#else +#include <../include/fcntl.h> +#endif + +#include "types.h" + +int posix_open(const char *path, ...); + +int posix_close(int fd); + +ssize_t posix_read(int fd, void *buf, size_t count); + +ssize_t posix_write(int fd, const void *buf, size_t count); + +#ifndef NO_REDEF_POSIX_FUNCTIONS +#define open(path, ...) posix_open(path, __VA_ARGS__) +#define close(fd) posix_close(fd) +#define read(fd, buf, count) posix_read(fd, buf, count) +#define write(fd, buf, count) posix_write(fd, buf, count) +#endif + +#endif /* _WIN32 */ + +#endif /* !_COMPAT_POSIX_WIN_H */ diff --git a/openbsd-compat/readpassphrase.c b/openbsd-compat/readpassphrase.c new file mode 100644 index 0000000..8b84190 --- /dev/null +++ b/openbsd-compat/readpassphrase.c @@ -0,0 +1,214 @@ +/* $OpenBSD: readpassphrase.c,v 1.26 2016/10/18 12:47:18 millert Exp $ */ + +/* + * Copyright (c) 2000-2002, 2007, 2010 + * Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* OPENBSD ORIGINAL: lib/libc/gen/readpassphrase.c */ + +#include "openbsd-compat.h" + +#ifndef HAVE_READPASSPHRASE + +#include <termios.h> +#include <signal.h> +#include <ctype.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <paths.h> + +#ifndef _PATH_TTY +# define _PATH_TTY "/dev/tty" +#endif + +#ifndef TCSASOFT +/* If we don't have TCSASOFT define it so that ORing it it below is a no-op. */ +# define TCSASOFT 0 +#endif + +/* SunOS 4.x which lacks _POSIX_VDISABLE, but has VDISABLE */ +#if !defined(_POSIX_VDISABLE) && defined(VDISABLE) +# define _POSIX_VDISABLE VDISABLE +#endif + +static volatile sig_atomic_t signo[NSIG]; + +static void handler(int); + +char * +readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags) +{ + ssize_t nr; + int input, output, save_errno, i, need_restart; + char ch, *p, *end; + struct termios term, oterm; + struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm; + struct sigaction savetstp, savettin, savettou, savepipe; + + /* I suppose we could alloc on demand in this case (XXX). */ + if (bufsiz == 0) { + errno = EINVAL; + return(NULL); + } + +restart: + for (i = 0; i < NSIG; i++) + signo[i] = 0; + need_restart = 0; + /* + * Read and write to /dev/tty if available. If not, read from + * stdin and write to stderr unless a tty is required. + */ + if ((flags & RPP_STDIN) || + (input = output = open(_PATH_TTY, O_RDWR)) == -1) { + if (flags & RPP_REQUIRE_TTY) { + errno = ENOTTY; + return(NULL); + } + input = STDIN_FILENO; + output = STDERR_FILENO; + } + + /* + * Turn off echo if possible. + * If we are using a tty but are not the foreground pgrp this will + * generate SIGTTOU, so do it *before* installing the signal handlers. + */ + if (input != STDIN_FILENO && tcgetattr(input, &oterm) == 0) { + memcpy(&term, &oterm, sizeof(term)); + if (!(flags & RPP_ECHO_ON)) + term.c_lflag &= ~(ECHO | ECHONL); +#ifdef VSTATUS + if (term.c_cc[VSTATUS] != _POSIX_VDISABLE) + term.c_cc[VSTATUS] = _POSIX_VDISABLE; +#endif + (void)tcsetattr(input, TCSAFLUSH|TCSASOFT, &term); + } else { + memset(&term, 0, sizeof(term)); + term.c_lflag |= ECHO; + memset(&oterm, 0, sizeof(oterm)); + oterm.c_lflag |= ECHO; + } + + /* + * Catch signals that would otherwise cause the user to end + * up with echo turned off in the shell. Don't worry about + * things like SIGXCPU and SIGVTALRM for now. + */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* don't restart system calls */ + sa.sa_handler = handler; + (void)sigaction(SIGALRM, &sa, &savealrm); + (void)sigaction(SIGHUP, &sa, &savehup); + (void)sigaction(SIGINT, &sa, &saveint); + (void)sigaction(SIGPIPE, &sa, &savepipe); + (void)sigaction(SIGQUIT, &sa, &savequit); + (void)sigaction(SIGTERM, &sa, &saveterm); + (void)sigaction(SIGTSTP, &sa, &savetstp); + (void)sigaction(SIGTTIN, &sa, &savettin); + (void)sigaction(SIGTTOU, &sa, &savettou); + + if (!(flags & RPP_STDIN)) + (void)write(output, prompt, strlen(prompt)); + end = buf + bufsiz - 1; + p = buf; + while ((nr = read(input, &ch, 1)) == 1 && ch != '\n' && ch != '\r') { + if (p < end) { + if ((flags & RPP_SEVENBIT)) + ch &= 0x7f; + if (isalpha((unsigned char)ch)) { + if ((flags & RPP_FORCELOWER)) + ch = (char)tolower((unsigned char)ch); + if ((flags & RPP_FORCEUPPER)) + ch = (char)toupper((unsigned char)ch); + } + *p++ = ch; + } + } + *p = '\0'; + save_errno = errno; + if (!(term.c_lflag & ECHO)) + (void)write(output, "\n", 1); + + /* Restore old terminal settings and signals. */ + if (memcmp(&term, &oterm, sizeof(term)) != 0) { + const int sigttou = signo[SIGTTOU]; + + /* Ignore SIGTTOU generated when we are not the fg pgrp. */ + while (tcsetattr(input, TCSAFLUSH|TCSASOFT, &oterm) == -1 && + errno == EINTR && !signo[SIGTTOU]) + continue; + signo[SIGTTOU] = sigttou; + } + (void)sigaction(SIGALRM, &savealrm, NULL); + (void)sigaction(SIGHUP, &savehup, NULL); + (void)sigaction(SIGINT, &saveint, NULL); + (void)sigaction(SIGQUIT, &savequit, NULL); + (void)sigaction(SIGPIPE, &savepipe, NULL); + (void)sigaction(SIGTERM, &saveterm, NULL); + (void)sigaction(SIGTSTP, &savetstp, NULL); + (void)sigaction(SIGTTIN, &savettin, NULL); + (void)sigaction(SIGTTOU, &savettou, NULL); + if (input != STDIN_FILENO) + (void)close(input); + + /* + * If we were interrupted by a signal, resend it to ourselves + * now that we have restored the signal handlers. + */ + for (i = 0; i < NSIG; i++) { + if (signo[i]) { + kill(getpid(), i); + switch (i) { + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + need_restart = 1; + } + } + } + if (need_restart) + goto restart; + + if (save_errno) + errno = save_errno; + return(nr == -1 ? NULL : buf); +} + +#if 0 +char * +getpass(const char *prompt) +{ + static char buf[_PASSWORD_LEN + 1]; + + return(readpassphrase(prompt, buf, sizeof(buf), RPP_ECHO_OFF)); +} +#endif + +static void handler(int s) +{ + + signo[s] = 1; +} +#endif /* HAVE_READPASSPHRASE */ diff --git a/openbsd-compat/readpassphrase.h b/openbsd-compat/readpassphrase.h new file mode 100644 index 0000000..e4451f3 --- /dev/null +++ b/openbsd-compat/readpassphrase.h @@ -0,0 +1,44 @@ +/* $OpenBSD: readpassphrase.h,v 1.5 2003/06/17 21:56:23 millert Exp $ */ + +/* + * Copyright (c) 2000, 2002 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* OPENBSD ORIGINAL: include/readpassphrase.h */ + +#ifndef _READPASSPHRASE_H_ +#define _READPASSPHRASE_H_ + +#ifndef HAVE_READPASSPHRASE + +#include <stdlib.h> + +#define RPP_ECHO_OFF 0x00 /* Turn off echo (default). */ +#define RPP_ECHO_ON 0x01 /* Leave echo on. */ +#define RPP_REQUIRE_TTY 0x02 /* Fail if there is no tty. */ +#define RPP_FORCELOWER 0x04 /* Force input to lower case. */ +#define RPP_FORCEUPPER 0x08 /* Force input to upper case. */ +#define RPP_SEVENBIT 0x10 /* Strip the high bit from input. */ +#define RPP_STDIN 0x20 /* Read from stdin, not /dev/tty */ + +char * readpassphrase(const char *, char *, size_t, int); + +#endif /* HAVE_READPASSPHRASE */ + +#endif /* !_READPASSPHRASE_H_ */ diff --git a/openbsd-compat/readpassphrase_win32.c b/openbsd-compat/readpassphrase_win32.c new file mode 100644 index 0000000..968987c --- /dev/null +++ b/openbsd-compat/readpassphrase_win32.c @@ -0,0 +1,131 @@ +/* +* Author: Manoj Ampalam <manoj.ampalam@microsoft.com> +* +* Author: Bryan Berns <berns@uwalumni.com> +* Modified group detection use s4u token information +* +* Copyright(c) 2016 Microsoft Corp. +* All rights reserved +* +* Misc Unix POSIX routine implementations for Windows +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met : +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and / or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#define UMDF_USING_NTSTATUS +#define SECURITY_WIN32 +#include <windows.h> +#include <stdio.h> +#include <time.h> +#include <shlwapi.h> +#include <conio.h> +#include <lm.h> +#include <sddl.h> +#include <aclapi.h> +#include <ntsecapi.h> +#include <security.h> +#include <ntstatus.h> +#include <wchar.h> + +#include "openbsd-compat.h" + +#ifndef HAVE_READPASSPHRASE + +/*on error returns NULL and sets errno*/ +static wchar_t * +utf8_to_utf16(const char *utf8) +{ + int needed = 0; + wchar_t* utf16 = NULL; + if ((needed = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) == 0 || + (utf16 = malloc(needed * sizeof(wchar_t))) == NULL || + MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, needed) == 0) { + /* debug3("failed to convert utf8 payload:%s error:%d", utf8, GetLastError()); */ + errno = ENOMEM; + return NULL; + } + + return utf16; +} + +char * +readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags) +{ + size_t current_index = 0; + char ch; + wchar_t* wtmp = NULL; + + if (outBufLen == 0) { + errno = EINVAL; + return NULL; + } + + while (_kbhit()) (void)_getch(); + + wtmp = utf8_to_utf16(prompt); + if (wtmp == NULL) + errx(1, "unable to alloc memory"); + + _cputws(wtmp); + free(wtmp); + + while (current_index < outBufLen - 1) { + ch = (char)_getch(); + + if (ch == '\r') { + if (_kbhit()) (void)_getch(); /* read linefeed if its there */ + break; + } else if (ch == '\n') { + break; + } else if (ch == '\b') { /* backspace */ + if (current_index > 0) { + if (flags & RPP_ECHO_ON) + printf_s("%c \b", ch); + + current_index--; /* overwrite last character */ + } + } else if (ch == '\003') { /* exit on Ctrl+C */ + errx(1, ""); + } else { + if (flags & RPP_SEVENBIT) + ch &= 0x7f; + + if (isalpha((unsigned char)ch)) { + if(flags & RPP_FORCELOWER) + ch = (char)tolower((unsigned char)ch); + if(flags & RPP_FORCEUPPER) + ch = (char)toupper((unsigned char)ch); + } + + outBuf[current_index++] = ch; + if(flags & RPP_ECHO_ON) + printf_s("%c", ch); + } + } + + outBuf[current_index] = '\0'; + _cputs("\n"); + + return outBuf; +} + +#endif /* HAVE_READPASSPHRASE */ diff --git a/openbsd-compat/recallocarray.c b/openbsd-compat/recallocarray.c new file mode 100644 index 0000000..5d2f8d9 --- /dev/null +++ b/openbsd-compat/recallocarray.c @@ -0,0 +1,91 @@ +/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */ +/* + * Copyright (c) 2008, 2017 Otto Moerbeek <otto@drijf.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* OPENBSD ORIGINAL: lib/libc/stdlib/recallocarray.c */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_RECALLOCARRAY) + +#include <errno.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void * +recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) +{ + size_t oldsize, newsize; + void *newptr; + + if (ptr == NULL) + return calloc(newnmemb, size); + + if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + newnmemb > 0 && SIZE_MAX / newnmemb < size) { + errno = ENOMEM; + return NULL; + } + newsize = newnmemb * size; + + if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { + errno = EINVAL; + return NULL; + } + oldsize = oldnmemb * size; + + /* + * Don't bother too much if we're shrinking just a bit, + * we do not shrink for series of small steps, oh well. + */ + if (newsize <= oldsize) { + size_t d = oldsize - newsize; + + if (d < oldsize / 2 && d < (size_t)getpagesize()) { + memset((char *)ptr + newsize, 0, d); + return ptr; + } + } + + newptr = malloc(newsize); + if (newptr == NULL) + return NULL; + + if (newsize > oldsize) { + memcpy(newptr, ptr, oldsize); + memset((char *)newptr + oldsize, 0, newsize - oldsize); + } else + memcpy(newptr, ptr, newsize); + + explicit_bzero(ptr, oldsize); + free(ptr); + + return newptr; +} +/* DEF_WEAK(recallocarray); */ + +#endif /* !defined(HAVE_RECALLOCARRAY) */ diff --git a/openbsd-compat/strlcat.c b/openbsd-compat/strlcat.c new file mode 100644 index 0000000..44470de --- /dev/null +++ b/openbsd-compat/strlcat.c @@ -0,0 +1,63 @@ +/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/strlcat.c */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_STRLCAT) + +#include <sys/types.h> +#include <string.h> + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} + +#endif /* !defined(HAVE_STRLCAT) */ diff --git a/openbsd-compat/strlcpy.c b/openbsd-compat/strlcpy.c new file mode 100644 index 0000000..a8b18ea --- /dev/null +++ b/openbsd-compat/strlcpy.c @@ -0,0 +1,59 @@ +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/strlcpy.c */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_STRLCPY) + +#include <sys/types.h> +#include <string.h> + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +#endif /* !defined(HAVE_STRLCPY) */ diff --git a/openbsd-compat/strsep.c b/openbsd-compat/strsep.c new file mode 100644 index 0000000..578668c --- /dev/null +++ b/openbsd-compat/strsep.c @@ -0,0 +1,79 @@ +/* $OpenBSD: strsep.c,v 1.6 2005/08/08 08:05:37 espie Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/strsep.c */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_STRSEP) + +#include <string.h> +#include <stdio.h> + +/* + * Get next token from string *stringp, where tokens are possibly-empty + * strings separated by characters from delim. + * + * Writes NULs into the string at *stringp to end tokens. + * delim need not remain constant from call to call. + * On return, *stringp points past the last NUL written (if there might + * be further tokens), or is NULL (if there are definitely no more tokens). + * + * If *stringp is NULL, strsep returns NULL. + */ +char * +strsep(char **stringp, const char *delim) +{ + char *s; + const char *spanp; + int c, sc; + char *tok; + + if ((s = *stringp) == NULL) + return (NULL); + for (tok = s;;) { + c = *s++; + spanp = delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) + s = NULL; + else + s[-1] = 0; + *stringp = s; + return (tok); + } + } while (sc != 0); + } + /* NOTREACHED */ +} + +#endif /* !defined(HAVE_STRSEP) */ diff --git a/openbsd-compat/time.h b/openbsd-compat/time.h new file mode 100644 index 0000000..b125f73 --- /dev/null +++ b/openbsd-compat/time.h @@ -0,0 +1,61 @@ +/* + * Public domain + * sys/time.h compatibility shim + */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1900) +#include <../ucrt/time.h> +#elif defined(_MSC_VER) && (_MSC_VER < 1900) +#include <../include/time.h> +#else +#include <time.h> +#endif + +#ifndef _COMPAT_TIME_H +#define _COMPAT_TIME_H + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 0 +#endif + +#ifndef HAVE_CLOCK_GETTIME +typedef int clockid_t; +int clock_gettime(clockid_t, struct timespec *); +#endif + +#ifdef HAVE_TIMESPECSUB +#include <sys/time.h> +#endif + +#ifndef HAVE_TIMESPECSUB +#define timespecadd(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ + if ((vsp)->tv_nsec >= 1000000000L) { \ + (vsp)->tv_sec++; \ + (vsp)->tv_nsec -= 1000000000L; \ + } \ + } while (0) + +#define timespecsub(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ + if ((vsp)->tv_nsec < 0) { \ + (vsp)->tv_sec--; \ + (vsp)->tv_nsec += 1000000000L; \ + } \ + } while (0) + +#define timespeccmp(tsp, usp, cmp) \ + (((tsp)->tv_sec == (usp)->tv_sec) ? \ + ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ + ((tsp)->tv_sec cmp (usp)->tv_sec)) +#endif + +#endif /* _COMPAT_TIME_H */ diff --git a/openbsd-compat/timingsafe_bcmp.c b/openbsd-compat/timingsafe_bcmp.c new file mode 100644 index 0000000..3f7b9e5 --- /dev/null +++ b/openbsd-compat/timingsafe_bcmp.c @@ -0,0 +1,35 @@ +/* $OpenBSD: timingsafe_bcmp.c,v 1.1 2010/09/24 13:33:00 matthew Exp $ */ +/* + * Copyright (c) 2010 Damien Miller. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/timingsafe_bcmp.c */ + +#include "openbsd-compat.h" + +#if !defined(HAVE_TIMINGSAFE_BCMP) + +int +timingsafe_bcmp(const void *b1, const void *b2, size_t n) +{ + const unsigned char *p1 = b1, *p2 = b2; + int ret = 0; + + for (; n > 0; n--) + ret |= *p1++ ^ *p2++; + return (ret != 0); +} + +#endif /* !defined(HAVE_TIMINGSAFE_BCMP) */ diff --git a/openbsd-compat/types.h b/openbsd-compat/types.h new file mode 100644 index 0000000..6170230 --- /dev/null +++ b/openbsd-compat/types.h @@ -0,0 +1,69 @@ +/* + * Public domain + * sys/types.h compatibility shim + */ + +#ifdef _MSC_VER +#if _MSC_VER >= 1900 +#include <../ucrt/sys/types.h> +#else +#include <../include/sys/types.h> +#endif +#endif + +#ifndef _COMPAT_TYPES_H +#define _COMPAT_TYPES_H + +#include <stdint.h> + +#ifdef __MINGW32__ +#include <_bsd_types.h> +typedef uint32_t in_addr_t; +typedef uint32_t uid_t; +#endif + +#ifdef _MSC_VER +typedef unsigned char u_char; +typedef unsigned short u_short; +typedef unsigned int u_int; +typedef unsigned long u_long; + +#include <basetsd.h> +typedef SSIZE_T ssize_t; + +#ifndef SSIZE_MAX +#ifdef _WIN64 +#define SSIZE_MAX _I64_MAX +#else +#define SSIZE_MAX INT_MAX +#endif +#endif + +#endif + +#if !defined(HAVE_ATTRIBUTE__BOUNDED__) && !defined(__bounded__) +# define __bounded__(x, y, z) +#endif + +#ifdef _WIN32 +#define __warn_references(sym,msg) +#else + +#ifndef __warn_references + +#ifndef __STRING +#define __STRING(x) #x +#endif + +#if defined(__GNUC__) && defined (HAS_GNU_WARNING_LONG) +#define __warn_references(sym,msg) \ + __asm__(".section .gnu.warning." __STRING(sym) \ + "\n\t.ascii \"" msg "\"\n\t.text"); +#else +#define __warn_references(sym,msg) +#endif + +#endif /* __warn_references */ +#endif /* _WIN32 */ + +#endif /* !_COMPAT_TYPES_H */ diff --git a/regress/CMakeLists.txt b/regress/CMakeLists.txt new file mode 100644 index 0000000..246bffa --- /dev/null +++ b/regress/CMakeLists.txt @@ -0,0 +1,57 @@ +# Copyright (c) 2018-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +add_custom_target(regress) + +macro(add_regress_test NAME SOURCES LIB) + add_executable(${NAME} ${SOURCES}) + add_test(${NAME} ${NAME}) + add_dependencies(regress ${NAME}) + target_link_libraries(${NAME} ${LIB}) +endmacro() + +if(MSVC AND BUILD_SHARED_LIBS) + add_custom_command(TARGET regress POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "${CBOR_BIN_DIRS}/${CBOR_LIBRARIES}.dll" + "${CRYPTO_BIN_DIRS}/${CRYPTO_LIBRARIES}.dll" + "${ZLIB_BIN_DIRS}/${ZLIB_LIBRARIES}.dll" + "$<TARGET_FILE:${_FIDO2_LIBRARY}>" + "${CMAKE_CURRENT_BINARY_DIR}") +endif() + +if(CYGWIN AND BUILD_SHARED_LIBS) + add_custom_command(TARGET regress POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy + "$<TARGET_FILE:${_FIDO2_LIBRARY}>" + "${CMAKE_CURRENT_BINARY_DIR}") +endif() + +if(CMAKE_CROSSCOMPILING OR (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64" AND + CMAKE_GENERATOR_PLATFORM MATCHES "^ARM.*$")) + add_custom_command(TARGET regress POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E echo + "Cross-compilation detected. Skipping regress tests.") +else() + add_custom_command(TARGET regress POST_BUILD + COMMAND "${CMAKE_CTEST_COMMAND}" --output-on-failure + WORKING_DIRECTORY ${PROJECT_BINARY_DIR}) +endif() + +add_regress_test(regress_assert assert.c ${_FIDO2_LIBRARY}) +add_regress_test(regress_cred cred.c ${_FIDO2_LIBRARY}) +add_regress_test(regress_dev dev.c ${_FIDO2_LIBRARY}) +add_regress_test(regress_eddsa eddsa.c ${_FIDO2_LIBRARY}) +add_regress_test(regress_es256 es256.c ${_FIDO2_LIBRARY}) +add_regress_test(regress_es384 es384.c ${_FIDO2_LIBRARY}) +add_regress_test(regress_rs256 rs256.c ${_FIDO2_LIBRARY}) +if(BUILD_STATIC_LIBS) + add_regress_test(regress_compress compress.c fido2) +endif() + +if(MINGW) + # needed for nanosleep() in mingw + target_link_libraries(regress_dev winpthread) +endif() diff --git a/regress/assert.c b/regress/assert.c new file mode 100644 index 0000000..ad31903 --- /dev/null +++ b/regress/assert.c @@ -0,0 +1,685 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> + +#define _FIDO_INTERNAL + +#include <fido.h> +#include <fido/es256.h> +#include <fido/rs256.h> +#include <fido/eddsa.h> + +static int fake_dev_handle; + +static const unsigned char es256_pk[64] = { + 0x34, 0xeb, 0x99, 0x77, 0x02, 0x9c, 0x36, 0x38, + 0xbb, 0xc2, 0xae, 0xa0, 0xa0, 0x18, 0xc6, 0x64, + 0xfc, 0xe8, 0x49, 0x92, 0xd7, 0x74, 0x9e, 0x0c, + 0x46, 0x8c, 0x9d, 0xa6, 0xdf, 0x46, 0xf7, 0x84, + 0x60, 0x1e, 0x0f, 0x8b, 0x23, 0x85, 0x4a, 0x9a, + 0xec, 0xc1, 0x08, 0x9f, 0x30, 0xd0, 0x0d, 0xd7, + 0x76, 0x7b, 0x55, 0x48, 0x91, 0x7c, 0x4f, 0x0f, + 0x64, 0x1a, 0x1d, 0xf8, 0xbe, 0x14, 0x90, 0x8a, +}; + +static const unsigned char rs256_pk[259] = { + 0x9e, 0x54, 0x78, 0xb2, 0x51, 0xbe, 0x19, 0x7c, + 0xcb, 0x1a, 0x9a, 0xc3, 0x49, 0x2a, 0x2f, 0xfd, + 0x99, 0x64, 0x76, 0xc6, 0xdb, 0xca, 0x38, 0x3f, + 0xb0, 0x6a, 0xc9, 0xc0, 0x07, 0x9f, 0x5c, 0x4d, + 0xfc, 0xd1, 0x01, 0x7f, 0x69, 0x65, 0xab, 0x9c, + 0x2a, 0xc2, 0x95, 0xd9, 0x44, 0xf3, 0xea, 0x94, + 0x6b, 0x25, 0x66, 0x54, 0x81, 0xee, 0x24, 0x1d, + 0xe1, 0x7d, 0x7f, 0xbe, 0xea, 0x76, 0x90, 0x5c, + 0xbf, 0x59, 0x22, 0xd3, 0xa0, 0x68, 0x1a, 0x65, + 0x8b, 0x2f, 0xb6, 0xa8, 0x30, 0x2d, 0x26, 0x81, + 0xfa, 0x9e, 0x59, 0xec, 0x2f, 0xee, 0x59, 0x39, + 0xe2, 0x79, 0x19, 0x54, 0x54, 0xdf, 0x24, 0x83, + 0xee, 0x61, 0x5a, 0x66, 0x24, 0x2b, 0x7b, 0xfb, + 0x82, 0x66, 0xe4, 0x85, 0x18, 0x20, 0x76, 0xe5, + 0x4a, 0xb6, 0xcb, 0xec, 0x43, 0xbe, 0xfd, 0xb0, + 0x8f, 0xfd, 0x2f, 0x69, 0xda, 0x06, 0x9c, 0x09, + 0x68, 0x7a, 0x94, 0x6c, 0xb7, 0x51, 0x6d, 0x4c, + 0xf7, 0x13, 0xe8, 0xd5, 0x22, 0x6b, 0x1e, 0xba, + 0xb9, 0x85, 0xe8, 0x5f, 0xa1, 0x66, 0xe3, 0x20, + 0x75, 0x30, 0x11, 0xb5, 0xa3, 0xc3, 0xb0, 0x72, + 0x08, 0xff, 0xa3, 0xbb, 0xf1, 0x32, 0x0b, 0x06, + 0xc4, 0x12, 0xa3, 0x49, 0x30, 0x19, 0xb9, 0xfe, + 0x69, 0x0c, 0xd6, 0xe1, 0x58, 0x36, 0xe6, 0x41, + 0x22, 0x41, 0xbf, 0x96, 0x50, 0x35, 0x56, 0x0d, + 0x92, 0x8c, 0x34, 0xea, 0x28, 0x91, 0x88, 0x9e, + 0x8a, 0xaa, 0x36, 0xd0, 0x0f, 0xbe, 0x16, 0xde, + 0x9d, 0x5f, 0x7b, 0xda, 0x52, 0xf7, 0xf1, 0xb6, + 0x28, 0x10, 0x05, 0x8f, 0xb9, 0x19, 0x7a, 0xcf, + 0x18, 0x9b, 0x40, 0xcd, 0xff, 0x78, 0xea, 0x61, + 0x24, 0x3b, 0x80, 0x68, 0x04, 0x9b, 0x40, 0x07, + 0x98, 0xd4, 0x94, 0xd1, 0x18, 0x44, 0xa5, 0xed, + 0xee, 0x18, 0xc2, 0x25, 0x52, 0x66, 0x42, 0xdf, + 0x01, 0x00, 0x01, +}; + +static const unsigned char cdh[32] = { + 0xec, 0x8d, 0x8f, 0x78, 0x42, 0x4a, 0x2b, 0xb7, + 0x82, 0x34, 0xaa, 0xca, 0x07, 0xa1, 0xf6, 0x56, + 0x42, 0x1c, 0xb6, 0xf6, 0xb3, 0x00, 0x86, 0x52, + 0x35, 0x2d, 0xa2, 0x62, 0x4a, 0xbe, 0x89, 0x76, +}; + +static const unsigned char authdata[39] = { + 0x58, 0x25, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, + 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, + 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, + 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, + 0x97, 0x63, 0x00, 0x00, 0x00, 0x00, 0x03, +}; + +static const unsigned char sig[72] = { + 0x30, 0x46, 0x02, 0x21, 0x00, 0xf6, 0xd1, 0xa3, + 0xd5, 0x24, 0x2b, 0xde, 0xee, 0xa0, 0x90, 0x89, + 0xcd, 0xf8, 0x9e, 0xbd, 0x6b, 0x4d, 0x55, 0x79, + 0xe4, 0xc1, 0x42, 0x27, 0xb7, 0x9b, 0x9b, 0xa4, + 0x0a, 0xe2, 0x47, 0x64, 0x0e, 0x02, 0x21, 0x00, + 0xe5, 0xc9, 0xc2, 0x83, 0x47, 0x31, 0xc7, 0x26, + 0xe5, 0x25, 0xb2, 0xb4, 0x39, 0xa7, 0xfc, 0x3d, + 0x70, 0xbe, 0xe9, 0x81, 0x0d, 0x4a, 0x62, 0xa9, + 0xab, 0x4a, 0x91, 0xc0, 0x7d, 0x2d, 0x23, 0x1e, +}; + +static void * +dummy_open(const char *path) +{ + (void)path; + + return (&fake_dev_handle); +} + +static void +dummy_close(void *handle) +{ + assert(handle == &fake_dev_handle); +} + +static int +dummy_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + (void)handle; + (void)buf; + (void)len; + (void)ms; + + abort(); + /* NOTREACHED */ +} + +static int +dummy_write(void *handle, const unsigned char *buf, size_t len) +{ + (void)handle; + (void)buf; + (void)len; + + abort(); + /* NOTREACHED */ +} + +static fido_assert_t * +alloc_assert(void) +{ + fido_assert_t *a; + + a = fido_assert_new(); + assert(a != NULL); + + return (a); +} + +static void +free_assert(fido_assert_t *a) +{ + fido_assert_free(&a); + assert(a == NULL); +} + +static fido_dev_t * +alloc_dev(void) +{ + fido_dev_t *d; + + d = fido_dev_new(); + assert(d != NULL); + + return (d); +} + +static void +free_dev(fido_dev_t *d) +{ + fido_dev_free(&d); + assert(d == NULL); +} + +static es256_pk_t * +alloc_es256_pk(void) +{ + es256_pk_t *pk; + + pk = es256_pk_new(); + assert(pk != NULL); + + return (pk); +} + +static void +free_es256_pk(es256_pk_t *pk) +{ + es256_pk_free(&pk); + assert(pk == NULL); +} + +static rs256_pk_t * +alloc_rs256_pk(void) +{ + rs256_pk_t *pk; + + pk = rs256_pk_new(); + assert(pk != NULL); + + return (pk); +} + +static void +free_rs256_pk(rs256_pk_t *pk) +{ + rs256_pk_free(&pk); + assert(pk == NULL); +} + +static eddsa_pk_t * +alloc_eddsa_pk(void) +{ + eddsa_pk_t *pk; + + pk = eddsa_pk_new(); + assert(pk != NULL); + + return (pk); +} + +static void +free_eddsa_pk(eddsa_pk_t *pk) +{ + eddsa_pk_free(&pk); + assert(pk == NULL); +} + +static void +empty_assert(fido_dev_t *d, fido_assert_t *a, size_t idx) +{ + es256_pk_t *es256; + rs256_pk_t *rs256; + eddsa_pk_t *eddsa; + + assert(fido_assert_flags(a, idx) == 0); + assert(fido_assert_authdata_len(a, idx) == 0); + assert(fido_assert_authdata_ptr(a, idx) == NULL); + assert(fido_assert_authdata_raw_len(a, idx) == 0); + assert(fido_assert_authdata_raw_ptr(a, idx) == NULL); + assert(fido_assert_clientdata_hash_len(a) == 0); + assert(fido_assert_clientdata_hash_ptr(a) == NULL); + assert(fido_assert_id_len(a, idx) == 0); + assert(fido_assert_id_ptr(a, idx) == NULL); + assert(fido_assert_rp_id(a) == NULL); + assert(fido_assert_sig_len(a, idx) == 0); + assert(fido_assert_sig_ptr(a, idx) == NULL); + assert(fido_assert_user_display_name(a, idx) == NULL); + assert(fido_assert_user_icon(a, idx) == NULL); + assert(fido_assert_user_id_len(a, idx) == 0); + assert(fido_assert_user_id_ptr(a, idx) == NULL); + assert(fido_assert_user_name(a, idx) == NULL); + + es256 = alloc_es256_pk(); + rs256 = alloc_rs256_pk(); + eddsa = alloc_eddsa_pk(); + + fido_dev_force_u2f(d); + assert(fido_dev_get_assert(d, a, NULL) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_get_assert(d, a, "") == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_ES256, + NULL) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_ES256, + es256) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, -1, + es256) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_RS256, + rs256) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_EDDSA, + eddsa) == FIDO_ERR_INVALID_ARGUMENT); + + fido_dev_force_fido2(d); + assert(fido_dev_get_assert(d, a, NULL) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_get_assert(d, a, "") == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_ES256, + NULL) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_ES256, + es256) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, -1, + es256) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_RS256, + rs256) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_verify(a, idx, COSE_EDDSA, + eddsa) == FIDO_ERR_INVALID_ARGUMENT); + + free_es256_pk(es256); + free_rs256_pk(rs256); + free_eddsa_pk(eddsa); +} + +static void +empty_assert_tests(void) +{ + fido_assert_t *a; + fido_dev_t *d; + fido_dev_io_t io_f; + size_t i; + + memset(&io_f, 0, sizeof(io_f)); + + a = alloc_assert(); + d = alloc_dev(); + + io_f.open = dummy_open; + io_f.close = dummy_close; + io_f.read = dummy_read; + io_f.write = dummy_write; + + assert(fido_dev_set_io_functions(d, &io_f) == FIDO_OK); + + empty_assert(d, a, 0); + assert(fido_assert_count(a) == 0); + assert(fido_assert_set_count(a, 4) == FIDO_OK); + assert(fido_assert_count(a) == 4); + for (i = 0; i < 4; i++) { + empty_assert(d, a, i); + } + empty_assert(d, a, 10); + free_assert(a); + free_dev(d); +} + +static void +valid_assert(void) +{ + fido_assert_t *a; + es256_pk_t *es256; + rs256_pk_t *rs256; + eddsa_pk_t *eddsa; + + a = alloc_assert(); + es256 = alloc_es256_pk(); + rs256 = alloc_rs256_pk(); + eddsa = alloc_eddsa_pk(); + assert(es256_pk_from_ptr(es256, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_rp(a, "localhost") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, es256) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_RS256, rs256) == FIDO_ERR_INVALID_SIG); + assert(fido_assert_verify(a, 0, COSE_EDDSA, eddsa) == FIDO_ERR_INVALID_SIG); + free_assert(a); + free_es256_pk(es256); + free_rs256_pk(rs256); + free_eddsa_pk(eddsa); +} + +static void +no_cdh(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_rp(a, "localhost") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, + pk) == FIDO_ERR_INVALID_ARGUMENT); + free_assert(a); + free_es256_pk(pk); +} + +static void +no_rp(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, + pk) == FIDO_ERR_INVALID_ARGUMENT); + free_assert(a); + free_es256_pk(pk); +} + +static void +no_authdata(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_rp(a, "localhost") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, + pk) == FIDO_ERR_INVALID_ARGUMENT); + free_assert(a); + free_es256_pk(pk); +} + +static void +no_sig(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_rp(a, "localhost") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, + pk) == FIDO_ERR_INVALID_ARGUMENT); + free_assert(a); + free_es256_pk(pk); +} + +static void +junk_cdh(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + unsigned char *junk; + + junk = malloc(sizeof(cdh)); + assert(junk != NULL); + memcpy(junk, cdh, sizeof(cdh)); + junk[0] = (unsigned char)~junk[0]; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, junk, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_rp(a, "localhost") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, pk) == FIDO_ERR_INVALID_SIG); + free_assert(a); + free_es256_pk(pk); + free(junk); +} + +static void +junk_rp(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_rp(a, "potato") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, + pk) == FIDO_ERR_INVALID_PARAM); + free_assert(a); + free_es256_pk(pk); +} + +static void +junk_authdata(void) +{ + fido_assert_t *a; + unsigned char *junk; + + junk = malloc(sizeof(authdata)); + assert(junk != NULL); + memcpy(junk, authdata, sizeof(authdata)); + junk[0] = (unsigned char)~junk[0]; + + a = alloc_assert(); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, junk, + sizeof(authdata)) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_assert_authdata_ptr(a, 0) == NULL); + assert(fido_assert_authdata_len(a, 0) == 0); + assert(fido_assert_authdata_raw_ptr(a, 0) == NULL); + assert(fido_assert_authdata_raw_len(a, 0) == 0); + free_assert(a); + free(junk); +} + +static void +junk_sig(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + unsigned char *junk; + + junk = malloc(sizeof(sig)); + assert(junk != NULL); + memcpy(junk, sig, sizeof(sig)); + junk[0] = (unsigned char)~junk[0]; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_rp(a, "localhost") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, junk, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, pk) == FIDO_ERR_INVALID_SIG); + free_assert(a); + free_es256_pk(pk); + free(junk); +} + +static void +wrong_options(void) +{ + fido_assert_t *a; + es256_pk_t *pk; + + a = alloc_assert(); + pk = alloc_es256_pk(); + assert(es256_pk_from_ptr(pk, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert(fido_assert_set_clientdata_hash(a, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_assert_set_rp(a, "localhost") == FIDO_OK); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_set_up(a, FIDO_OPT_TRUE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_sig(a, 0, sig, sizeof(sig)) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, + pk) == FIDO_ERR_INVALID_PARAM); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_TRUE) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, + pk) == FIDO_ERR_INVALID_PARAM); + assert(fido_assert_set_up(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_set_uv(a, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_assert_verify(a, 0, COSE_ES256, pk) == FIDO_OK); + free_assert(a); + free_es256_pk(pk); +} + +/* cbor_serialize_alloc misuse */ +static void +bad_cbor_serialize(void) +{ + fido_assert_t *a; + + a = alloc_assert(); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert(fido_assert_authdata_len(a, 0) == sizeof(authdata)); + free_assert(a); +} + +/* rs256 <-> EVP_PKEY transformations */ +static void +rs256_PKEY(void) +{ + rs256_pk_t *pk1, *pk2; + EVP_PKEY *pkey; + + pk1 = alloc_rs256_pk(); + pk2 = alloc_rs256_pk(); + + assert(rs256_pk_from_ptr(pk1, rs256_pk, sizeof(rs256_pk)) == FIDO_OK); + assert((pkey = rs256_pk_to_EVP_PKEY(pk1)) != NULL); + assert(rs256_pk_from_EVP_PKEY(pk2, pkey) == FIDO_OK); + assert(memcmp(pk1, pk2, sizeof(*pk1)) == 0); + + free_rs256_pk(pk1); + free_rs256_pk(pk2); + EVP_PKEY_free(pkey); +} + +/* es256 <-> EVP_PKEY transformations */ +static void +es256_PKEY(void) +{ + es256_pk_t *pk1, *pk2; + EVP_PKEY *pkey; + + pk1 = alloc_es256_pk(); + pk2 = alloc_es256_pk(); + + assert(es256_pk_from_ptr(pk1, es256_pk, sizeof(es256_pk)) == FIDO_OK); + assert((pkey = es256_pk_to_EVP_PKEY(pk1)) != NULL); + assert(es256_pk_from_EVP_PKEY(pk2, pkey) == FIDO_OK); + assert(memcmp(pk1, pk2, sizeof(*pk1)) == 0); + + free_es256_pk(pk1); + free_es256_pk(pk2); + EVP_PKEY_free(pkey); +} + +static void +raw_authdata(void) +{ + fido_assert_t *a; + cbor_item_t *item; + struct cbor_load_result cbor_result; + const unsigned char *ptr; + unsigned char *cbor; + size_t len; + size_t cbor_len; + size_t alloclen; + + a = alloc_assert(); + assert(fido_assert_set_count(a, 1) == FIDO_OK); + assert(fido_assert_set_authdata(a, 0, authdata, + sizeof(authdata)) == FIDO_OK); + assert((ptr = fido_assert_authdata_ptr(a, 0)) != NULL); + assert((len = fido_assert_authdata_len(a, 0)) != 0); + assert((item = cbor_load(ptr, len, &cbor_result)) != NULL); + assert(cbor_result.read == len); + assert(cbor_isa_bytestring(item)); + assert((ptr = fido_assert_authdata_raw_ptr(a, 0)) != NULL); + assert((len = fido_assert_authdata_raw_len(a, 0)) != 0); + assert(cbor_bytestring_length(item) == len); + assert(memcmp(ptr, cbor_bytestring_handle(item), len) == 0); + assert((len = fido_assert_authdata_len(a, 0)) != 0); + assert((cbor_len = cbor_serialize_alloc(item, &cbor, &alloclen)) == len); + assert((ptr = cbor_bytestring_handle(item)) != NULL); + assert((len = cbor_bytestring_length(item)) != 0); + assert(fido_assert_set_authdata_raw(a, 0, ptr, len) == FIDO_OK); + assert((ptr = fido_assert_authdata_ptr(a, 0)) != NULL); + assert((len = fido_assert_authdata_len(a, 0)) != 0); + assert(len == cbor_len); + assert(memcmp(cbor, ptr, len) == 0); + assert(cbor_len == sizeof(authdata)); + assert(memcmp(cbor, authdata, cbor_len) == 0); + cbor_decref(&item); + free(cbor); + free_assert(a); +} + +int +main(void) +{ + fido_init(0); + + empty_assert_tests(); + valid_assert(); + no_cdh(); + no_rp(); + no_authdata(); + no_sig(); + junk_cdh(); + junk_rp(); + junk_authdata(); + junk_sig(); + wrong_options(); + bad_cbor_serialize(); + rs256_PKEY(); + es256_PKEY(); + raw_authdata(); + + exit(0); +} diff --git a/regress/compress.c b/regress/compress.c new file mode 100644 index 0000000..7afc8bb --- /dev/null +++ b/regress/compress.c @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> + +#include <openssl/sha.h> + +#define _FIDO_INTERNAL + +#include <fido.h> + +/* + * zlib compressed data (RFC1950); see https://www.ietf.org/rfc/rfc6713.txt + */ +static /* const */ unsigned char rfc1950_blob[694] = { + 0x78, 0x9c, 0xb5, 0x52, 0x3b, 0x6f, 0xdb, 0x30, + 0x10, 0xde, 0xf5, 0x2b, 0x0e, 0x99, 0x12, 0x40, + 0x75, 0x13, 0x4f, 0x45, 0x3b, 0xd1, 0x12, 0x6d, + 0x1d, 0x20, 0x8b, 0x2a, 0x49, 0xd9, 0xf5, 0x28, + 0x4b, 0x4c, 0x42, 0xc0, 0x12, 0x03, 0x3d, 0x12, + 0xe4, 0xdf, 0xf7, 0xc8, 0x3a, 0x88, 0xd3, 0x0c, + 0x9d, 0xea, 0xc1, 0x3e, 0xf3, 0x8e, 0xdf, 0xeb, + 0x98, 0xb8, 0xa7, 0xd7, 0xc1, 0x3e, 0x3c, 0x4e, + 0x70, 0xdd, 0xdc, 0xc0, 0xf2, 0xf6, 0xee, 0xdb, + 0x97, 0xe5, 0xed, 0x72, 0x09, 0x87, 0xf9, 0x68, + 0x1b, 0x07, 0x6c, 0xb5, 0x00, 0x76, 0x3a, 0x41, + 0x18, 0x19, 0x61, 0x30, 0xa3, 0x19, 0x9e, 0x4d, + 0xbb, 0x88, 0x22, 0x69, 0x5a, 0x3b, 0x4e, 0x83, + 0x3d, 0xce, 0x93, 0x75, 0x3d, 0xd4, 0x7d, 0x0b, + 0xf3, 0x68, 0xc0, 0xf6, 0x30, 0xba, 0x79, 0x68, + 0x4c, 0x38, 0x39, 0xda, 0xbe, 0x1e, 0x5e, 0xe1, + 0xde, 0x0d, 0xdd, 0x18, 0xc3, 0x8b, 0x9d, 0x1e, + 0xc1, 0x0d, 0xe1, 0xd7, 0xcd, 0x53, 0xd4, 0xb9, + 0xd6, 0xde, 0xdb, 0xa6, 0xf6, 0x00, 0x31, 0xd4, + 0x83, 0x81, 0x27, 0x33, 0x74, 0x76, 0x9a, 0x4c, + 0x0b, 0x4f, 0x83, 0x7b, 0xb6, 0x2d, 0x15, 0xd3, + 0x63, 0x3d, 0xd1, 0x97, 0x21, 0x90, 0xd3, 0xc9, + 0xbd, 0xd8, 0xfe, 0x01, 0x1a, 0xd7, 0xb7, 0xd6, + 0x5f, 0x1a, 0xfd, 0xa5, 0xa8, 0x33, 0xd3, 0xf7, + 0x28, 0x02, 0x80, 0xbb, 0x05, 0x7c, 0x54, 0x35, + 0x82, 0xbb, 0x7f, 0x93, 0xd3, 0xb8, 0xd6, 0x40, + 0x37, 0x8f, 0x13, 0x99, 0x98, 0x6a, 0x92, 0xe9, + 0x31, 0xeb, 0xa3, 0x7b, 0xf6, 0xad, 0x73, 0x06, + 0x1e, 0x84, 0x3e, 0xbd, 0x9b, 0x6c, 0x63, 0x62, + 0x9a, 0xb0, 0x23, 0x9c, 0x08, 0xcf, 0xc3, 0x5c, + 0x92, 0xf6, 0xed, 0x5f, 0x8a, 0x88, 0xb4, 0x39, + 0xd5, 0xb6, 0x33, 0xc3, 0xc2, 0x63, 0x2c, 0x3f, + 0x0b, 0x21, 0xc2, 0x8b, 0x30, 0xde, 0x84, 0x90, + 0xcb, 0x76, 0x26, 0x71, 0xff, 0x47, 0x0b, 0x91, + 0x9e, 0x51, 0xfc, 0x44, 0xeb, 0x9a, 0xb9, 0x33, + 0xfd, 0x54, 0xbf, 0xed, 0xeb, 0x2b, 0xad, 0xc2, + 0x51, 0x67, 0x80, 0xae, 0x9e, 0xcc, 0x60, 0xeb, + 0xd3, 0xf8, 0x1e, 0x7b, 0xd8, 0x15, 0x35, 0xcf, + 0x00, 0x97, 0x66, 0x68, 0xf9, 0x3a, 0x43, 0x05, + 0x4a, 0xac, 0xf5, 0x9e, 0x49, 0x0e, 0x54, 0x97, + 0x52, 0xec, 0x30, 0xe5, 0x29, 0xac, 0x0e, 0xa0, + 0x33, 0x0e, 0x89, 0x28, 0x0f, 0x12, 0x37, 0x99, + 0x86, 0x4c, 0xe4, 0x29, 0x97, 0x0a, 0x58, 0x91, + 0xd2, 0x69, 0xa1, 0x25, 0xae, 0x2a, 0x2d, 0xa4, + 0x8a, 0xae, 0x98, 0xa2, 0x9b, 0x57, 0xa1, 0xc1, + 0x8a, 0x03, 0xf0, 0x5f, 0xa5, 0xe4, 0x4a, 0x81, + 0x90, 0x80, 0xdb, 0x32, 0x47, 0x02, 0x23, 0x74, + 0xc9, 0x0a, 0x8d, 0x5c, 0xc5, 0x80, 0x45, 0x92, + 0x57, 0x29, 0x16, 0x9b, 0x18, 0x08, 0x00, 0x0a, + 0xa1, 0xa3, 0x1c, 0xb7, 0xa8, 0x69, 0x4c, 0x8b, + 0x38, 0x90, 0x7e, 0xbe, 0x06, 0x62, 0x0d, 0x5b, + 0x2e, 0x93, 0x8c, 0xfe, 0xb2, 0x15, 0xe6, 0xa8, + 0x0f, 0x81, 0x6f, 0x8d, 0xba, 0xf0, 0x5c, 0x6b, + 0x21, 0x23, 0x06, 0x25, 0x93, 0x1a, 0x93, 0x2a, + 0x67, 0x12, 0xca, 0x4a, 0x96, 0x42, 0x71, 0xf0, + 0xb6, 0x52, 0x54, 0x49, 0xce, 0x70, 0xcb, 0xd3, + 0x05, 0xb1, 0x13, 0x23, 0xf0, 0x1d, 0x2f, 0x34, + 0xa8, 0x8c, 0xe5, 0xf9, 0x47, 0x97, 0xd1, 0x1f, + 0x97, 0x5e, 0xfb, 0xa5, 0x47, 0x58, 0x71, 0xc8, + 0x91, 0xad, 0x72, 0xee, 0x99, 0x82, 0xcb, 0x14, + 0x25, 0x4f, 0xb4, 0xb7, 0xf3, 0x5e, 0x25, 0x94, + 0x1c, 0xe9, 0xcb, 0xe3, 0x48, 0x95, 0x3c, 0x41, + 0x2a, 0x28, 0x0c, 0x4e, 0x66, 0x98, 0x3c, 0xc4, + 0x67, 0x4c, 0xc5, 0x7f, 0x56, 0x34, 0x44, 0x4d, + 0x48, 0xd9, 0x96, 0x6d, 0xc8, 0xdb, 0xf5, 0x3f, + 0x22, 0xa1, 0x9d, 0x24, 0x95, 0xe4, 0x5b, 0xaf, + 0x99, 0x72, 0x50, 0xd5, 0x4a, 0x69, 0xd4, 0x95, + 0xe6, 0xb0, 0x11, 0x22, 0x0d, 0x41, 0x2b, 0x2e, + 0x77, 0x98, 0x70, 0xf5, 0x03, 0x72, 0xa1, 0x42, + 0x5a, 0x95, 0xe2, 0x71, 0x94, 0x32, 0xcd, 0x02, + 0x31, 0x41, 0x50, 0x54, 0xd4, 0xa6, 0x7a, 0x55, + 0x29, 0x0c, 0xa1, 0x61, 0xa1, 0xb9, 0x94, 0x55, + 0xa9, 0x51, 0x14, 0x37, 0xb4, 0xdf, 0x3d, 0xc5, + 0x42, 0x1a, 0x19, 0x5d, 0x4d, 0x43, 0xba, 0xa2, + 0xf0, 0x56, 0xe9, 0x91, 0x70, 0x21, 0x0f, 0x1e, + 0xd4, 0x67, 0x10, 0xc2, 0x8f, 0x61, 0x9f, 0x71, + 0x3a, 0x97, 0x3e, 0xd0, 0x90, 0x14, 0xf3, 0x11, + 0x28, 0x4a, 0x2c, 0xd1, 0x97, 0x63, 0xc4, 0x47, + 0x01, 0xea, 0xe8, 0xdd, 0x23, 0x14, 0x7c, 0x93, + 0xe3, 0x86, 0x17, 0x09, 0xf7, 0x5d, 0xe1, 0x51, + 0xf6, 0xa8, 0xf8, 0x0d, 0xed, 0x0a, 0x95, 0x1f, + 0xc0, 0x40, 0x4b, 0xdb, 0x27, 0xce, 0x2a, 0x58, + 0xf6, 0x3b, 0x22, 0x55, 0x51, 0x28, 0x2f, 0x5e, + 0x6c, 0x1c, 0x36, 0x09, 0xb8, 0x06, 0x96, 0xee, + 0xd0, 0xcb, 0x3e, 0x0f, 0xd3, 0xee, 0x15, 0x9e, + 0xdf, 0x49, 0x88, 0x2c, 0xc9, 0xce, 0x71, 0x2f, + 0xa2, 0xdf, 0xdf, 0xd7, 0x8e, 0x9c, +}; + +/* + * expected sha256 of rfc1950_blob after decompression + */ +static const unsigned char rfc1950_blob_hash[SHA256_DIGEST_LENGTH] = { + 0x61, 0xc0, 0x4e, 0x14, 0x01, 0xb6, 0xc5, 0x2d, + 0xba, 0x15, 0xf6, 0x27, 0x4c, 0xa1, 0xcc, 0xfc, + 0x39, 0xed, 0xd7, 0x12, 0xb6, 0x02, 0x3d, 0xb6, + 0xd9, 0x85, 0xd0, 0x10, 0x9f, 0xe9, 0x3e, 0x75, + +}; + +static const size_t rfc1950_blob_origsiz = 1322; + +static /* const */ unsigned char random_words[515] = { + 0x61, 0x74, 0x68, 0x69, 0x72, 0x73, 0x74, 0x20, + 0x54, 0x68, 0x6f, 0x20, 0x63, 0x6f, 0x74, 0x20, + 0x73, 0x70, 0x6f, 0x66, 0x66, 0x79, 0x20, 0x4a, + 0x61, 0x76, 0x61, 0x6e, 0x20, 0x62, 0x72, 0x65, + 0x64, 0x65, 0x73, 0x20, 0x4c, 0x41, 0x4d, 0x20, + 0x6d, 0x69, 0x73, 0x2d, 0x68, 0x75, 0x6d, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x20, 0x73, 0x70, 0x69, + 0x67, 0x6f, 0x74, 0x20, 0x72, 0x65, 0x76, 0x6f, + 0x6c, 0x74, 0x69, 0x6e, 0x67, 0x6c, 0x79, 0x20, + 0x49, 0x6f, 0x64, 0x61, 0x6d, 0x6f, 0x65, 0x62, + 0x61, 0x20, 0x68, 0x79, 0x70, 0x6f, 0x68, 0x79, + 0x64, 0x72, 0x6f, 0x63, 0x68, 0x6c, 0x6f, 0x72, + 0x69, 0x61, 0x20, 0x76, 0x6f, 0x6c, 0x75, 0x6d, + 0x65, 0x74, 0x74, 0x65, 0x20, 0x61, 0x63, 0x72, + 0x69, 0x64, 0x69, 0x6e, 0x65, 0x20, 0x68, 0x6f, + 0x77, 0x6c, 0x20, 0x45, 0x75, 0x72, 0x79, 0x67, + 0x61, 0x65, 0x61, 0x6e, 0x20, 0x63, 0x6f, 0x6e, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x6e, 0x69, 0x73, + 0x74, 0x20, 0x74, 0x65, 0x74, 0x72, 0x61, 0x70, + 0x6c, 0x6f, 0x69, 0x64, 0x20, 0x61, 0x75, 0x78, + 0x65, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x20, 0x72, + 0x69, 0x70, 0x65, 0x2d, 0x67, 0x72, 0x6f, 0x77, + 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, + 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x79, 0x63, + 0x6f, 0x63, 0x65, 0x63, 0x69, 0x64, 0x69, 0x75, + 0x6d, 0x20, 0x50, 0x65, 0x64, 0x65, 0x72, 0x73, + 0x6f, 0x6e, 0x20, 0x74, 0x72, 0x61, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x20, 0x4c, 0x65, 0x6e, 0x67, 0x6c, + 0x65, 0x6e, 0x20, 0x70, 0x72, 0x65, 0x73, 0x62, + 0x79, 0x74, 0x65, 0x72, 0x61, 0x74, 0x65, 0x20, + 0x6c, 0x65, 0x63, 0x79, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x63, 0x68, 0x61, 0x72, 0x61, 0x64, 0x72, + 0x69, 0x69, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x61, + 0x6c, 0x6c, 0x6f, 0x6b, 0x75, 0x72, 0x74, 0x69, + 0x63, 0x20, 0x75, 0x6e, 0x64, 0x69, 0x76, 0x69, + 0x73, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x20, 0x70, + 0x73, 0x79, 0x63, 0x68, 0x6f, 0x6b, 0x79, 0x6d, + 0x65, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x73, + 0x74, 0x61, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, + 0x6e, 0x65, 0x73, 0x73, 0x20, 0x63, 0x75, 0x6c, + 0x74, 0x69, 0x73, 0x68, 0x20, 0x52, 0x65, 0x69, + 0x63, 0x68, 0x73, 0x74, 0x61, 0x67, 0x20, 0x75, + 0x6e, 0x63, 0x68, 0x6c, 0x6f, 0x72, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x64, 0x20, 0x6c, 0x6f, 0x67, + 0x6f, 0x67, 0x72, 0x61, 0x70, 0x68, 0x65, 0x72, + 0x20, 0x4c, 0x61, 0x69, 0x74, 0x68, 0x20, 0x74, + 0x77, 0x6f, 0x2d, 0x66, 0x61, 0x63, 0x65, 0x20, + 0x4d, 0x75, 0x70, 0x68, 0x72, 0x69, 0x64, 0x20, + 0x70, 0x72, 0x6f, 0x72, 0x65, 0x63, 0x69, 0x70, + 0x72, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x6c, 0x69, 0x62, 0x72, 0x65, 0x74, 0x74, + 0x69, 0x73, 0x74, 0x20, 0x49, 0x62, 0x69, 0x62, + 0x69, 0x6f, 0x20, 0x72, 0x65, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x63, + 0x6f, 0x6e, 0x64, 0x69, 0x67, 0x6e, 0x6e, 0x65, + 0x73, 0x73, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, + 0x2d, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, + 0x64, 0x20, 0x73, 0x79, 0x6e, 0x61, 0x70, 0x74, + 0x65, 0x6e, 0x65, 0x20, 0x68, 0x6f, 0x6c, 0x6f, + 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x20, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x4d, + 0x49, 0x54, 0x53, 0x20, 0x4c, 0x75, 0x6b, 0x61, + 0x73, 0x68, 0x20, 0x48, 0x6f, 0x72, 0x73, 0x65, + 0x79, 0x20, 0x0a, +}; + +static void +rfc1950_inflate(void) +{ + fido_blob_t in, out, dgst; + + memset(&in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); + memset(&dgst, 0, sizeof(dgst)); + in.ptr = rfc1950_blob; + in.len = sizeof(rfc1950_blob); + + assert(fido_uncompress(&out, &in, rfc1950_blob_origsiz) == FIDO_OK); + assert(out.len == rfc1950_blob_origsiz); + assert(fido_sha256(&dgst, out.ptr, out.len) == 0); + assert(dgst.len == sizeof(rfc1950_blob_hash)); + assert(memcmp(rfc1950_blob_hash, dgst.ptr, dgst.len) == 0); + + free(out.ptr); + free(dgst.ptr); +} + +static void +rfc1951_inflate(void) +{ + fido_blob_t in, out, dgst; + + memset(&in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); + memset(&dgst, 0, sizeof(dgst)); + in.ptr = rfc1950_blob + 2; /* trim header */ + in.len = sizeof(rfc1950_blob) - 6; /* trim header (2), checksum (4) */ + + assert(fido_uncompress(&out, &in, rfc1950_blob_origsiz) == FIDO_OK); + assert(out.len == rfc1950_blob_origsiz); + assert(fido_sha256(&dgst, out.ptr, out.len) == 0); + assert(dgst.len == sizeof(rfc1950_blob_hash)); + assert(memcmp(rfc1950_blob_hash, dgst.ptr, dgst.len) == 0); + + free(out.ptr); + free(dgst.ptr); +} + +static void +rfc1951_reinflate(void) +{ + fido_blob_t in, out; + + memset(&in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); + in.ptr = random_words; + in.len = sizeof(random_words); + + assert(fido_compress(&out, &in) == FIDO_OK); + + in.ptr = out.ptr; + in.len = out.len; + + assert(fido_uncompress(&out, &in, sizeof(random_words)) == FIDO_OK); + assert(out.len == sizeof(random_words)); + assert(memcmp(out.ptr, random_words, out.len) == 0); + + free(in.ptr); + free(out.ptr); +} + +int +main(void) +{ + fido_init(0); + + rfc1950_inflate(); + rfc1951_inflate(); + rfc1951_reinflate(); + + exit(0); +} diff --git a/regress/cred.c b/regress/cred.c new file mode 100644 index 0000000..61e603d --- /dev/null +++ b/regress/cred.c @@ -0,0 +1,2185 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> + +#define _FIDO_INTERNAL + +#include <fido.h> + +static int fake_dev_handle; + +static const unsigned char cdh[32] = { + 0xf9, 0x64, 0x57, 0xe7, 0x2d, 0x97, 0xf6, 0xbb, + 0xdd, 0xd7, 0xfb, 0x06, 0x37, 0x62, 0xea, 0x26, + 0x20, 0x44, 0x8e, 0x69, 0x7c, 0x03, 0xf2, 0x31, + 0x2f, 0x99, 0xdc, 0xaf, 0x3e, 0x8a, 0x91, 0x6b, +}; + +static const unsigned char authdata[198] = { + 0x58, 0xc4, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, + 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, + 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, + 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, + 0x97, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, 0xf8, + 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, + 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, + 0x40, 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde, + 0xc5, 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d, + 0x53, 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e, + 0x7f, 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda, + 0x68, 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83, + 0x2c, 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76, + 0x90, 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6, + 0x3c, 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f, + 0x25, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, + 0x21, 0x58, 0x20, 0x17, 0x5b, 0x27, 0xa6, 0x56, + 0xb2, 0x26, 0x0c, 0x26, 0x0c, 0x55, 0x42, 0x78, + 0x17, 0x5d, 0x4c, 0xf8, 0xa2, 0xfd, 0x1b, 0xb9, + 0x54, 0xdf, 0xd5, 0xeb, 0xbf, 0x22, 0x64, 0xf5, + 0x21, 0x9a, 0xc6, 0x22, 0x58, 0x20, 0x87, 0x5f, + 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f, 0xeb, 0xe3, + 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6, 0x1c, 0x31, + 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1, 0xfe, 0x5d, + 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2, +}; + +static const unsigned char authdata_dupkeys[200] = { + 0x58, 0xc6, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, + 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, + 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, + 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, + 0x97, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, 0xf8, + 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, + 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, + 0x40, 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde, + 0xc5, 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d, + 0x53, 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e, + 0x7f, 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda, + 0x68, 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83, + 0x2c, 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76, + 0x90, 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6, + 0x3c, 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f, + 0x25, 0xa6, 0x01, 0x02, 0x01, 0x02, 0x03, 0x26, + 0x20, 0x01, 0x21, 0x58, 0x20, 0x17, 0x5b, 0x27, + 0xa6, 0x56, 0xb2, 0x26, 0x0c, 0x26, 0x0c, 0x55, + 0x42, 0x78, 0x17, 0x5d, 0x4c, 0xf8, 0xa2, 0xfd, + 0x1b, 0xb9, 0x54, 0xdf, 0xd5, 0xeb, 0xbf, 0x22, + 0x64, 0xf5, 0x21, 0x9a, 0xc6, 0x22, 0x58, 0x20, + 0x87, 0x5f, 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f, + 0xeb, 0xe3, 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6, + 0x1c, 0x31, 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1, + 0xfe, 0x5d, 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2, +}; + +static const unsigned char authdata_unsorted_keys[198] = { + 0x58, 0xc4, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, + 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, + 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, + 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, + 0x97, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, 0xf8, + 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, 0x80, + 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, 0x00, + 0x40, 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde, + 0xc5, 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d, + 0x53, 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e, + 0x7f, 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda, + 0x68, 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83, + 0x2c, 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76, + 0x90, 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6, + 0x3c, 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f, + 0x25, 0xa5, 0x03, 0x26, 0x01, 0x02, 0x20, 0x01, + 0x21, 0x58, 0x20, 0x17, 0x5b, 0x27, 0xa6, 0x56, + 0xb2, 0x26, 0x0c, 0x26, 0x0c, 0x55, 0x42, 0x78, + 0x17, 0x5d, 0x4c, 0xf8, 0xa2, 0xfd, 0x1b, 0xb9, + 0x54, 0xdf, 0xd5, 0xeb, 0xbf, 0x22, 0x64, 0xf5, + 0x21, 0x9a, 0xc6, 0x22, 0x58, 0x20, 0x87, 0x5f, + 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f, 0xeb, 0xe3, + 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6, 0x1c, 0x31, + 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1, 0xfe, 0x5d, + 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2, +}; + +const unsigned char authdata_tpm_rs256[362] = { + 0x59, 0x01, 0x67, 0x49, 0x96, 0x0d, 0xe5, 0x88, + 0x0e, 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, + 0x76, 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, + 0x86, 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, + 0x1d, 0x97, 0x63, 0x45, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x98, 0x70, 0x58, 0xca, 0xdc, 0x4b, 0x81, + 0xb6, 0xe1, 0x30, 0xde, 0x50, 0xdc, 0xbe, 0x96, + 0x00, 0x20, 0x89, 0x99, 0x6d, 0x5a, 0x00, 0x29, + 0xe5, 0x3e, 0x6a, 0x1c, 0x72, 0x6d, 0x71, 0x4a, + 0x4f, 0x03, 0x9b, 0x68, 0x17, 0xdb, 0x29, 0x1a, + 0x6b, 0x02, 0x6c, 0x26, 0xf9, 0xbd, 0xc3, 0x0e, + 0x38, 0x1a, 0xa4, 0x01, 0x03, 0x03, 0x39, 0x01, + 0x00, 0x20, 0x59, 0x01, 0x00, 0xc5, 0xb6, 0x9c, + 0x06, 0x1d, 0xcf, 0xb9, 0xf2, 0x5e, 0x99, 0x7d, + 0x6d, 0x73, 0xd8, 0x36, 0xc1, 0x4a, 0x90, 0x05, + 0x4d, 0x82, 0x57, 0xc1, 0xb6, 0x6a, 0xd1, 0x43, + 0x03, 0x85, 0xf8, 0x52, 0x4f, 0xd2, 0x27, 0x91, + 0x0b, 0xb5, 0x93, 0xa0, 0x68, 0xf8, 0x80, 0x1b, + 0xaa, 0x65, 0x97, 0x45, 0x11, 0x86, 0x34, 0xd6, + 0x67, 0xf8, 0xd5, 0x12, 0x79, 0x84, 0xee, 0x70, + 0x99, 0x00, 0x63, 0xa8, 0xb4, 0x43, 0x0b, 0x4c, + 0x57, 0x4a, 0xd6, 0x9b, 0x75, 0x63, 0x8a, 0x46, + 0x57, 0xdb, 0x14, 0xc8, 0x71, 0xd1, 0xb3, 0x07, + 0x68, 0x58, 0xbc, 0x55, 0x84, 0x80, 0x2a, 0xd2, + 0x36, 0x9f, 0xc1, 0x64, 0xa0, 0x11, 0x4b, 0xc9, + 0x32, 0x31, 0x3a, 0xd6, 0x87, 0x26, 0x1a, 0x3a, + 0x78, 0x3d, 0x89, 0xdb, 0x00, 0x28, 0x3b, 0xae, + 0x2b, 0x1b, 0x56, 0xe2, 0x8c, 0x4c, 0x63, 0xac, + 0x6e, 0x6c, 0xf7, 0xb5, 0x7d, 0x4d, 0x0b, 0x9f, + 0x06, 0xa0, 0x10, 0x35, 0x38, 0x20, 0x4d, 0xcc, + 0x07, 0xd7, 0x00, 0x4e, 0x86, 0xba, 0xfe, 0x8b, + 0xe4, 0x3f, 0x4a, 0xd6, 0xca, 0xbf, 0x67, 0x40, + 0x1a, 0xa4, 0xda, 0x82, 0x52, 0x15, 0xb8, 0x14, + 0x3a, 0x7c, 0xa9, 0x02, 0xc1, 0x01, 0x69, 0xc6, + 0x51, 0xd4, 0xbc, 0x1f, 0x95, 0xb2, 0xee, 0x1f, + 0xdd, 0xb5, 0x73, 0x16, 0x5e, 0x29, 0x3f, 0x47, + 0xac, 0x65, 0xfb, 0x63, 0x5c, 0xb9, 0xc8, 0x13, + 0x2d, 0xec, 0x85, 0xde, 0x71, 0x0d, 0x84, 0x93, + 0x74, 0x76, 0x91, 0xdd, 0x1d, 0x6d, 0x3d, 0xc7, + 0x36, 0x19, 0x19, 0x86, 0xde, 0x7c, 0xca, 0xd6, + 0xc6, 0x65, 0x7e, 0x4b, 0x24, 0x9c, 0xce, 0x92, + 0x6b, 0x1c, 0xe0, 0xa0, 0xa9, 0x6c, 0xc3, 0xed, + 0x4f, 0x2a, 0x54, 0x07, 0x00, 0x32, 0x5e, 0x1b, + 0x94, 0x37, 0xcd, 0xe2, 0x32, 0xa8, 0xd5, 0x2c, + 0xfb, 0x03, 0x9d, 0x79, 0xdf, 0x21, 0x43, 0x01, + 0x00, 0x01 +}; + +static const unsigned char authdata_tpm_es256[166] = { + 0x58, 0xa4, 0x49, 0x96, 0x0d, 0xe5, 0x88, 0x0e, + 0x8c, 0x68, 0x74, 0x34, 0x17, 0x0f, 0x64, 0x76, + 0x60, 0x5b, 0x8f, 0xe4, 0xae, 0xb9, 0xa2, 0x86, + 0x32, 0xc7, 0x99, 0x5c, 0xf3, 0xba, 0x83, 0x1d, + 0x97, 0x63, 0x45, 0x00, 0x00, 0x00, 0x00, 0x08, + 0x98, 0x70, 0x58, 0xca, 0xdc, 0x4b, 0x81, 0xb6, + 0xe1, 0x30, 0xde, 0x50, 0xdc, 0xbe, 0x96, 0x00, + 0x20, 0xa8, 0xdf, 0x03, 0xf7, 0xbf, 0x39, 0x51, + 0x94, 0x95, 0x8f, 0xa4, 0x84, 0x97, 0x30, 0xbc, + 0x3c, 0x7e, 0x1c, 0x99, 0x91, 0x4d, 0xae, 0x6d, + 0xfb, 0xdf, 0x53, 0xb5, 0xb6, 0x1f, 0x3a, 0x4e, + 0x6a, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, + 0x21, 0x58, 0x20, 0xfb, 0xd6, 0xba, 0x74, 0xe6, + 0x6e, 0x5c, 0x87, 0xef, 0x89, 0xa2, 0xe8, 0x3d, + 0x0b, 0xe9, 0x69, 0x2c, 0x07, 0x07, 0x7a, 0x8a, + 0x1e, 0xce, 0x12, 0xea, 0x3b, 0xb3, 0xf1, 0xf3, + 0xd9, 0xc3, 0xe6, 0x22, 0x58, 0x20, 0x3c, 0x68, + 0x51, 0x94, 0x54, 0x8d, 0xeb, 0x9f, 0xb2, 0x2c, + 0x66, 0x75, 0xb6, 0xb7, 0x55, 0x22, 0x0d, 0x87, + 0x59, 0xc4, 0x39, 0x91, 0x62, 0x17, 0xc2, 0xc3, + 0x53, 0xa5, 0x26, 0x97, 0x4f, 0x2d +}; + +static const unsigned char x509[742] = { + 0x30, 0x82, 0x02, 0xe2, 0x30, 0x81, 0xcb, 0x02, + 0x01, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, + 0x00, 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x13, 0x12, 0x59, 0x75, + 0x62, 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, + 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x41, + 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x35, + 0x31, 0x35, 0x31, 0x32, 0x35, 0x38, 0x35, 0x34, + 0x5a, 0x17, 0x0d, 0x31, 0x34, 0x30, 0x36, 0x31, + 0x34, 0x31, 0x32, 0x35, 0x38, 0x35, 0x34, 0x5a, + 0x30, 0x1d, 0x31, 0x1b, 0x30, 0x19, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x13, 0x12, 0x59, 0x75, 0x62, + 0x69, 0x63, 0x6f, 0x20, 0x55, 0x32, 0x46, 0x20, + 0x54, 0x65, 0x73, 0x74, 0x20, 0x45, 0x45, 0x30, + 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, + 0x00, 0x04, 0xdb, 0x0a, 0xdb, 0xf5, 0x21, 0xc7, + 0x5c, 0xce, 0x63, 0xdc, 0xa6, 0xe1, 0xe8, 0x25, + 0x06, 0x0d, 0x94, 0xe6, 0x27, 0x54, 0x19, 0x4f, + 0x9d, 0x24, 0xaf, 0x26, 0x1a, 0xbe, 0xad, 0x99, + 0x44, 0x1f, 0x95, 0xa3, 0x71, 0x91, 0x0a, 0x3a, + 0x20, 0xe7, 0x3e, 0x91, 0x5e, 0x13, 0xe8, 0xbe, + 0x38, 0x05, 0x7a, 0xd5, 0x7a, 0xa3, 0x7e, 0x76, + 0x90, 0x8f, 0xaf, 0xe2, 0x8a, 0x94, 0xb6, 0x30, + 0xeb, 0x9d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, + 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0x95, 0x40, + 0x6b, 0x50, 0x61, 0x7d, 0xad, 0x84, 0xa3, 0xb4, + 0xeb, 0x88, 0x0f, 0xe3, 0x30, 0x0f, 0x2d, 0xa2, + 0x0a, 0x00, 0xd9, 0x25, 0x04, 0xee, 0x72, 0xfa, + 0x67, 0xdf, 0x58, 0x51, 0x0f, 0x0b, 0x47, 0x02, + 0x9c, 0x3e, 0x41, 0x29, 0x4a, 0x93, 0xac, 0x29, + 0x85, 0x89, 0x2d, 0xa4, 0x7a, 0x81, 0x32, 0x28, + 0x57, 0x71, 0x01, 0xef, 0xa8, 0x42, 0x88, 0x16, + 0x96, 0x37, 0x91, 0xd5, 0xdf, 0xe0, 0x8f, 0xc9, + 0x3c, 0x8d, 0xb0, 0xcd, 0x89, 0x70, 0x82, 0xec, + 0x79, 0xd3, 0xc6, 0x78, 0x73, 0x29, 0x32, 0xe5, + 0xab, 0x6c, 0xbd, 0x56, 0x9f, 0xd5, 0x45, 0x91, + 0xce, 0xc1, 0xdd, 0x8d, 0x64, 0xdc, 0xe9, 0x9c, + 0x1f, 0x5e, 0x3c, 0xd2, 0xaf, 0x51, 0xa5, 0x82, + 0x18, 0xaf, 0xe0, 0x37, 0xe7, 0x32, 0x9e, 0x76, + 0x05, 0x77, 0x02, 0x7b, 0xe6, 0x24, 0xa0, 0x31, + 0x56, 0x1b, 0xfd, 0x19, 0xc5, 0x71, 0xd3, 0xf0, + 0x9e, 0xc0, 0x73, 0x05, 0x4e, 0xbc, 0x85, 0xb8, + 0x53, 0x9e, 0xef, 0xc5, 0xbc, 0x9c, 0x56, 0xa3, + 0xba, 0xd9, 0x27, 0x6a, 0xbb, 0xa9, 0x7a, 0x40, + 0xd7, 0x47, 0x8b, 0x55, 0x72, 0x6b, 0xe3, 0xfe, + 0x28, 0x49, 0x71, 0x24, 0xf4, 0x8f, 0xf4, 0x20, + 0x81, 0xea, 0x38, 0xff, 0x7c, 0x0a, 0x4f, 0xdf, + 0x02, 0x82, 0x39, 0x81, 0x82, 0x3b, 0xca, 0x09, + 0xdd, 0xca, 0xaa, 0x0f, 0x27, 0xf5, 0xa4, 0x83, + 0x55, 0x6c, 0x9a, 0x39, 0x9b, 0x15, 0x3a, 0x16, + 0x63, 0xdc, 0x5b, 0xf9, 0xac, 0x5b, 0xbc, 0xf7, + 0x9f, 0xbe, 0x0f, 0x8a, 0xa2, 0x3c, 0x31, 0x13, + 0xa3, 0x32, 0x48, 0xca, 0x58, 0x87, 0xf8, 0x7b, + 0xa0, 0xa1, 0x0a, 0x6a, 0x60, 0x96, 0x93, 0x5f, + 0x5d, 0x26, 0x9e, 0x63, 0x1d, 0x09, 0xae, 0x9a, + 0x41, 0xe5, 0xbd, 0x08, 0x47, 0xfe, 0xe5, 0x09, + 0x9b, 0x20, 0xfd, 0x12, 0xe2, 0xe6, 0x40, 0x7f, + 0xba, 0x4a, 0x61, 0x33, 0x66, 0x0d, 0x0e, 0x73, + 0xdb, 0xb0, 0xd5, 0xa2, 0x9a, 0x9a, 0x17, 0x0d, + 0x34, 0x30, 0x85, 0x6a, 0x42, 0x46, 0x9e, 0xff, + 0x34, 0x8f, 0x5f, 0x87, 0x6c, 0x35, 0xe7, 0xa8, + 0x4d, 0x35, 0xeb, 0xc1, 0x41, 0xaa, 0x8a, 0xd2, + 0xda, 0x19, 0xaa, 0x79, 0xa2, 0x5f, 0x35, 0x2c, + 0xa0, 0xfd, 0x25, 0xd3, 0xf7, 0x9d, 0x25, 0x18, + 0x2d, 0xfa, 0xb4, 0xbc, 0xbb, 0x07, 0x34, 0x3c, + 0x8d, 0x81, 0xbd, 0xf4, 0xe9, 0x37, 0xdb, 0x39, + 0xe9, 0xd1, 0x45, 0x5b, 0x20, 0x41, 0x2f, 0x2d, + 0x27, 0x22, 0xdc, 0x92, 0x74, 0x8a, 0x92, 0xd5, + 0x83, 0xfd, 0x09, 0xfb, 0x13, 0x9b, 0xe3, 0x39, + 0x7a, 0x6b, 0x5c, 0xfa, 0xe6, 0x76, 0x9e, 0xe0, + 0xe4, 0xe3, 0xef, 0xad, 0xbc, 0xfd, 0x42, 0x45, + 0x9a, 0xd4, 0x94, 0xd1, 0x7e, 0x8d, 0xa7, 0xd8, + 0x05, 0xd5, 0xd3, 0x62, 0xcf, 0x15, 0xcf, 0x94, + 0x7d, 0x1f, 0x5b, 0x58, 0x20, 0x44, 0x20, 0x90, + 0x71, 0xbe, 0x66, 0xe9, 0x9a, 0xab, 0x74, 0x32, + 0x70, 0x53, 0x1d, 0x69, 0xed, 0x87, 0x66, 0xf4, + 0x09, 0x4f, 0xca, 0x25, 0x30, 0xc2, 0x63, 0x79, + 0x00, 0x3c, 0xb1, 0x9b, 0x39, 0x3f, 0x00, 0xe0, + 0xa8, 0x88, 0xef, 0x7a, 0x51, 0x5b, 0xe7, 0xbd, + 0x49, 0x64, 0xda, 0x41, 0x7b, 0x24, 0xc3, 0x71, + 0x22, 0xfd, 0xd1, 0xd1, 0x20, 0xb3, 0x3f, 0x97, + 0xd3, 0x97, 0xb2, 0xaa, 0x18, 0x1c, 0x9e, 0x03, + 0x77, 0x7b, 0x5b, 0x7e, 0xf9, 0xa3, 0xa0, 0xd6, + 0x20, 0x81, 0x2c, 0x38, 0x8f, 0x9d, 0x25, 0xde, + 0xe9, 0xc8, 0xf5, 0xdd, 0x6a, 0x47, 0x9c, 0x65, + 0x04, 0x5a, 0x56, 0xe6, 0xc2, 0xeb, 0xf2, 0x02, + 0x97, 0xe1, 0xb9, 0xd8, 0xe1, 0x24, 0x76, 0x9f, + 0x23, 0x62, 0x39, 0x03, 0x4b, 0xc8, 0xf7, 0x34, + 0x07, 0x49, 0xd6, 0xe7, 0x4d, 0x9a, +}; + +const unsigned char sig[70] = { + 0x30, 0x44, 0x02, 0x20, 0x54, 0x92, 0x28, 0x3b, + 0x83, 0x33, 0x47, 0x56, 0x68, 0x79, 0xb2, 0x0c, + 0x84, 0x80, 0xcc, 0x67, 0x27, 0x8b, 0xfa, 0x48, + 0x43, 0x0d, 0x3c, 0xb4, 0x02, 0x36, 0x87, 0x97, + 0x3e, 0xdf, 0x2f, 0x65, 0x02, 0x20, 0x1b, 0x56, + 0x17, 0x06, 0xe2, 0x26, 0x0f, 0x6a, 0xe9, 0xa9, + 0x70, 0x99, 0x62, 0xeb, 0x3a, 0x04, 0x1a, 0xc4, + 0xa7, 0x03, 0x28, 0x56, 0x7c, 0xed, 0x47, 0x08, + 0x68, 0x73, 0x6a, 0xb6, 0x89, 0x0d, +}; + +const unsigned char pubkey[64] = { + 0x17, 0x5b, 0x27, 0xa6, 0x56, 0xb2, 0x26, 0x0c, + 0x26, 0x0c, 0x55, 0x42, 0x78, 0x17, 0x5d, 0x4c, + 0xf8, 0xa2, 0xfd, 0x1b, 0xb9, 0x54, 0xdf, 0xd5, + 0xeb, 0xbf, 0x22, 0x64, 0xf5, 0x21, 0x9a, 0xc6, + 0x87, 0x5f, 0x90, 0xe6, 0xfd, 0x71, 0x27, 0x9f, + 0xeb, 0xe3, 0x03, 0x44, 0xbc, 0x8d, 0x49, 0xc6, + 0x1c, 0x31, 0x3b, 0x72, 0xae, 0xd4, 0x53, 0xb1, + 0xfe, 0x5d, 0xe1, 0x30, 0xfc, 0x2b, 0x1e, 0xd2, +}; + +const unsigned char pubkey_tpm_rs256[259] = { + 0xc5, 0xb6, 0x9c, 0x06, 0x1d, 0xcf, 0xb9, 0xf2, + 0x5e, 0x99, 0x7d, 0x6d, 0x73, 0xd8, 0x36, 0xc1, + 0x4a, 0x90, 0x05, 0x4d, 0x82, 0x57, 0xc1, 0xb6, + 0x6a, 0xd1, 0x43, 0x03, 0x85, 0xf8, 0x52, 0x4f, + 0xd2, 0x27, 0x91, 0x0b, 0xb5, 0x93, 0xa0, 0x68, + 0xf8, 0x80, 0x1b, 0xaa, 0x65, 0x97, 0x45, 0x11, + 0x86, 0x34, 0xd6, 0x67, 0xf8, 0xd5, 0x12, 0x79, + 0x84, 0xee, 0x70, 0x99, 0x00, 0x63, 0xa8, 0xb4, + 0x43, 0x0b, 0x4c, 0x57, 0x4a, 0xd6, 0x9b, 0x75, + 0x63, 0x8a, 0x46, 0x57, 0xdb, 0x14, 0xc8, 0x71, + 0xd1, 0xb3, 0x07, 0x68, 0x58, 0xbc, 0x55, 0x84, + 0x80, 0x2a, 0xd2, 0x36, 0x9f, 0xc1, 0x64, 0xa0, + 0x11, 0x4b, 0xc9, 0x32, 0x31, 0x3a, 0xd6, 0x87, + 0x26, 0x1a, 0x3a, 0x78, 0x3d, 0x89, 0xdb, 0x00, + 0x28, 0x3b, 0xae, 0x2b, 0x1b, 0x56, 0xe2, 0x8c, + 0x4c, 0x63, 0xac, 0x6e, 0x6c, 0xf7, 0xb5, 0x7d, + 0x4d, 0x0b, 0x9f, 0x06, 0xa0, 0x10, 0x35, 0x38, + 0x20, 0x4d, 0xcc, 0x07, 0xd7, 0x00, 0x4e, 0x86, + 0xba, 0xfe, 0x8b, 0xe4, 0x3f, 0x4a, 0xd6, 0xca, + 0xbf, 0x67, 0x40, 0x1a, 0xa4, 0xda, 0x82, 0x52, + 0x15, 0xb8, 0x14, 0x3a, 0x7c, 0xa9, 0x02, 0xc1, + 0x01, 0x69, 0xc6, 0x51, 0xd4, 0xbc, 0x1f, 0x95, + 0xb2, 0xee, 0x1f, 0xdd, 0xb5, 0x73, 0x16, 0x5e, + 0x29, 0x3f, 0x47, 0xac, 0x65, 0xfb, 0x63, 0x5c, + 0xb9, 0xc8, 0x13, 0x2d, 0xec, 0x85, 0xde, 0x71, + 0x0d, 0x84, 0x93, 0x74, 0x76, 0x91, 0xdd, 0x1d, + 0x6d, 0x3d, 0xc7, 0x36, 0x19, 0x19, 0x86, 0xde, + 0x7c, 0xca, 0xd6, 0xc6, 0x65, 0x7e, 0x4b, 0x24, + 0x9c, 0xce, 0x92, 0x6b, 0x1c, 0xe0, 0xa0, 0xa9, + 0x6c, 0xc3, 0xed, 0x4f, 0x2a, 0x54, 0x07, 0x00, + 0x32, 0x5e, 0x1b, 0x94, 0x37, 0xcd, 0xe2, 0x32, + 0xa8, 0xd5, 0x2c, 0xfb, 0x03, 0x9d, 0x79, 0xdf, + 0x01, 0x00, 0x01, +}; + +const unsigned char pubkey_tpm_es256[64] = { + 0xfb, 0xd6, 0xba, 0x74, 0xe6, 0x6e, 0x5c, 0x87, + 0xef, 0x89, 0xa2, 0xe8, 0x3d, 0x0b, 0xe9, 0x69, + 0x2c, 0x07, 0x07, 0x7a, 0x8a, 0x1e, 0xce, 0x12, + 0xea, 0x3b, 0xb3, 0xf1, 0xf3, 0xd9, 0xc3, 0xe6, + 0x3c, 0x68, 0x51, 0x94, 0x54, 0x8d, 0xeb, 0x9f, + 0xb2, 0x2c, 0x66, 0x75, 0xb6, 0xb7, 0x55, 0x22, + 0x0d, 0x87, 0x59, 0xc4, 0x39, 0x91, 0x62, 0x17, + 0xc2, 0xc3, 0x53, 0xa5, 0x26, 0x97, 0x4f, 0x2d +}; + +const unsigned char id[64] = { + 0x53, 0xfb, 0xdf, 0xaa, 0xce, 0x63, 0xde, 0xc5, + 0xfe, 0x47, 0xe6, 0x52, 0xeb, 0xf3, 0x5d, 0x53, + 0xa8, 0xbf, 0x9d, 0xd6, 0x09, 0x6b, 0x5e, 0x7f, + 0xe0, 0x0d, 0x51, 0x30, 0x85, 0x6a, 0xda, 0x68, + 0x70, 0x85, 0xb0, 0xdb, 0x08, 0x0b, 0x83, 0x2c, + 0xef, 0x44, 0xe2, 0x36, 0x88, 0xee, 0x76, 0x90, + 0x6e, 0x7b, 0x50, 0x3e, 0x9a, 0xa0, 0xd6, 0x3c, + 0x34, 0xe3, 0x83, 0xe7, 0xd1, 0xbd, 0x9f, 0x25, +}; + +const unsigned char id_tpm_rs256[32] = { + 0x89, 0x99, 0x6d, 0x5a, 0x00, 0x29, 0xe5, 0x3e, + 0x6a, 0x1c, 0x72, 0x6d, 0x71, 0x4a, 0x4f, 0x03, + 0x9b, 0x68, 0x17, 0xdb, 0x29, 0x1a, 0x6b, 0x02, + 0x6c, 0x26, 0xf9, 0xbd, 0xc3, 0x0e, 0x38, 0x1a +}; + +const unsigned char id_tpm_es256[32] = { + 0xa8, 0xdf, 0x03, 0xf7, 0xbf, 0x39, 0x51, 0x94, + 0x95, 0x8f, 0xa4, 0x84, 0x97, 0x30, 0xbc, 0x3c, + 0x7e, 0x1c, 0x99, 0x91, 0x4d, 0xae, 0x6d, 0xfb, + 0xdf, 0x53, 0xb5, 0xb6, 0x1f, 0x3a, 0x4e, 0x6a +}; + +const unsigned char attstmt_tpm_rs256[4034] = { + 0xa6, 0x63, 0x61, 0x6c, 0x67, 0x39, 0xff, 0xfe, + 0x63, 0x73, 0x69, 0x67, 0x59, 0x01, 0x00, 0x1c, + 0x09, 0x0d, 0x35, 0x97, 0x22, 0xfc, 0xfe, 0xc0, + 0x58, 0x49, 0x9e, 0xd4, 0x7e, 0x6a, 0x7d, 0xdb, + 0x6d, 0x20, 0x95, 0x5c, 0x0b, 0xd0, 0xd5, 0x72, + 0x4f, 0x15, 0x22, 0x38, 0x97, 0xb2, 0x4b, 0xd0, + 0xef, 0x31, 0x7c, 0xf2, 0x42, 0x19, 0x41, 0xa1, + 0xe2, 0xc5, 0xca, 0xc6, 0x74, 0x95, 0xcf, 0xf9, + 0x41, 0x75, 0x0b, 0x56, 0x39, 0x82, 0x78, 0xf6, + 0x59, 0xf1, 0x09, 0x96, 0x9e, 0x38, 0x7f, 0x14, + 0x9b, 0xf5, 0x36, 0xbb, 0x92, 0x32, 0xc4, 0x64, + 0xe8, 0xff, 0xb4, 0xc7, 0xcf, 0xcd, 0x17, 0x48, + 0x0f, 0x83, 0xd9, 0x44, 0x03, 0x35, 0x26, 0xad, + 0x01, 0xb7, 0x57, 0x06, 0xb3, 0x9c, 0xa0, 0x6e, + 0x2f, 0x58, 0xcb, 0x5c, 0xaa, 0x7c, 0xea, 0x7e, + 0x3f, 0xbc, 0x76, 0xc9, 0x0e, 0x52, 0x39, 0x81, + 0xa9, 0x9e, 0x37, 0x14, 0x1f, 0x50, 0x6a, 0x4f, + 0xd7, 0xfc, 0xd4, 0xfa, 0xf2, 0x18, 0x60, 0xd5, + 0xc3, 0x57, 0x7d, 0x6d, 0x05, 0x28, 0x25, 0xc3, + 0xde, 0x86, 0x85, 0x06, 0x71, 0xfb, 0x84, 0xa2, + 0x07, 0xb6, 0x77, 0xc9, 0x68, 0x41, 0x53, 0x32, + 0x4c, 0xa8, 0x4b, 0xf7, 0x08, 0x84, 0x62, 0x6c, + 0x8a, 0xb6, 0xcf, 0xc1, 0xde, 0x6b, 0x61, 0xc8, + 0xdd, 0xc0, 0x13, 0x70, 0x22, 0x28, 0xe1, 0x0f, + 0x46, 0x02, 0xc6, 0xb1, 0xfa, 0x30, 0xcb, 0xec, + 0xd1, 0x82, 0xfa, 0x51, 0xcb, 0x71, 0x5e, 0x1f, + 0x1b, 0x5f, 0xe0, 0xb0, 0x02, 0x8a, 0x7c, 0x78, + 0xd1, 0xb7, 0x4d, 0x56, 0xb0, 0x92, 0x3e, 0xda, + 0xc7, 0xb1, 0x74, 0xcf, 0x6a, 0x40, 0xeb, 0x98, + 0x1c, 0x2e, 0xf2, 0x86, 0x76, 0xf8, 0x2e, 0x6a, + 0x9f, 0x77, 0x51, 0x64, 0xce, 0xdc, 0x12, 0x85, + 0x84, 0x6b, 0x01, 0xc8, 0xeb, 0xbc, 0x57, 0x6c, + 0x32, 0x26, 0xcb, 0xb2, 0x84, 0x02, 0x2a, 0x33, + 0x15, 0xd9, 0xe3, 0x15, 0xfc, 0x3a, 0x24, 0x63, + 0x76, 0x65, 0x72, 0x63, 0x32, 0x2e, 0x30, 0x63, + 0x78, 0x35, 0x63, 0x82, 0x59, 0x05, 0xc4, 0x30, + 0x82, 0x05, 0xc0, 0x30, 0x82, 0x03, 0xa8, 0xa0, + 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x78, 0xd9, + 0xa8, 0xb2, 0x64, 0xf9, 0x4d, 0x28, 0x82, 0xc0, + 0xd3, 0x1b, 0x40, 0x3c, 0xc8, 0xd9, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x41, 0x31, + 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53, 0x54, + 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44, 0x2d, + 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34, 0x41, + 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37, 0x41, + 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30, 0x30, + 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31, 0x41, + 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36, 0x30, + 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x30, 0x37, + 0x31, 0x35, 0x31, 0x31, 0x31, 0x32, 0x31, 0x33, + 0x5a, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x33, 0x32, + 0x31, 0x32, 0x30, 0x32, 0x39, 0x31, 0x35, 0x5a, + 0x30, 0x00, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, + 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, + 0x01, 0x01, 0x00, 0xca, 0xbe, 0x77, 0x9f, 0x45, + 0x97, 0x17, 0x8d, 0x01, 0xe1, 0x18, 0xcc, 0xf0, + 0xb5, 0xed, 0x9a, 0xb7, 0x36, 0xac, 0x05, 0x26, + 0xbe, 0x35, 0xd9, 0x5c, 0x00, 0x5c, 0x5d, 0x8b, + 0x6f, 0x2a, 0xb8, 0xf6, 0x02, 0x4f, 0x33, 0xfe, + 0x84, 0x45, 0x4c, 0x4f, 0x7a, 0xdb, 0xa9, 0x6a, + 0x62, 0x0f, 0x19, 0x35, 0x5d, 0xd2, 0x34, 0x1a, + 0x9d, 0x73, 0x55, 0xe5, 0x3e, 0x04, 0xa2, 0xd6, + 0xbe, 0xe7, 0x5a, 0xb9, 0x16, 0x6c, 0x55, 0x18, + 0xa8, 0x4b, 0xb2, 0x37, 0xb9, 0xa3, 0x87, 0xfc, + 0x76, 0xa8, 0x55, 0xc9, 0xe7, 0x30, 0xe5, 0x0e, + 0x3c, 0x7b, 0x74, 0xd2, 0x1e, 0xa8, 0x05, 0xd5, + 0xe2, 0xe3, 0xcb, 0xaf, 0x63, 0x33, 0x12, 0xaa, + 0xfd, 0x31, 0x32, 0x71, 0x4f, 0x41, 0x96, 0x05, + 0xb5, 0x69, 0x73, 0x45, 0xbe, 0x6f, 0x90, 0xd9, + 0x10, 0x36, 0xaf, 0x7a, 0x1c, 0xf1, 0x6d, 0x14, + 0xb0, 0x1e, 0xbb, 0xae, 0x1c, 0x35, 0xec, 0x1c, + 0xb5, 0x0e, 0xf6, 0x33, 0x98, 0x13, 0x4e, 0x44, + 0x7b, 0x5c, 0x97, 0x47, 0xed, 0x4f, 0xfe, 0xbd, + 0x08, 0xd2, 0xa9, 0xc6, 0xbe, 0x8c, 0x04, 0x9e, + 0xdc, 0x3d, 0xbe, 0x98, 0xe9, 0x2a, 0xb1, 0xf4, + 0xfa, 0x45, 0xf9, 0xc8, 0x9a, 0x55, 0x85, 0x26, + 0xfc, 0x5f, 0xad, 0x00, 0x8b, 0xc8, 0x41, 0xf2, + 0x86, 0x4e, 0xba, 0x55, 0x1c, 0xb2, 0x89, 0xe8, + 0x85, 0x6e, 0x1e, 0x02, 0x9f, 0x55, 0x70, 0xbe, + 0xfd, 0xe7, 0x9f, 0xba, 0x59, 0xa0, 0x2e, 0x9a, + 0x74, 0x11, 0xe7, 0xad, 0xa9, 0xc7, 0x7b, 0x58, + 0xc4, 0x16, 0xd3, 0x35, 0xcb, 0x61, 0x00, 0xec, + 0x36, 0x4a, 0xa3, 0x51, 0xa3, 0xdd, 0x61, 0xb6, + 0xd6, 0x29, 0xcb, 0x76, 0xe1, 0xab, 0x51, 0x3a, + 0xe8, 0xbf, 0xdb, 0x09, 0x4a, 0x39, 0x96, 0xd9, + 0xac, 0x8f, 0x6c, 0x62, 0xe0, 0x03, 0x23, 0x24, + 0xbe, 0xd4, 0x83, 0x02, 0x03, 0x01, 0x00, 0x01, + 0xa3, 0x82, 0x01, 0xf3, 0x30, 0x82, 0x01, 0xef, + 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, + 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80, + 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, + 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x6d, + 0x06, 0x03, 0x55, 0x1d, 0x20, 0x01, 0x01, 0xff, + 0x04, 0x63, 0x30, 0x61, 0x30, 0x5f, 0x06, 0x09, + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, + 0x1f, 0x30, 0x52, 0x30, 0x50, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30, + 0x44, 0x1e, 0x42, 0x00, 0x54, 0x00, 0x43, 0x00, + 0x50, 0x00, 0x41, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x54, 0x00, 0x72, 0x00, 0x75, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x6d, 0x00, 0x20, 0x00, 0x20, 0x00, 0x49, 0x00, + 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x69, 0x00, 0x74, 0x00, 0x79, 0x30, 0x10, 0x06, + 0x03, 0x55, 0x1d, 0x25, 0x04, 0x09, 0x30, 0x07, + 0x06, 0x05, 0x67, 0x81, 0x05, 0x08, 0x03, 0x30, + 0x59, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x01, 0x01, + 0xff, 0x04, 0x4f, 0x30, 0x4d, 0xa4, 0x4b, 0x30, + 0x49, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, 0x67, + 0x81, 0x05, 0x02, 0x01, 0x0c, 0x0b, 0x69, 0x64, + 0x3a, 0x35, 0x33, 0x35, 0x34, 0x34, 0x44, 0x32, + 0x30, 0x31, 0x17, 0x30, 0x15, 0x06, 0x05, 0x67, + 0x81, 0x05, 0x02, 0x02, 0x0c, 0x0c, 0x53, 0x54, + 0x33, 0x33, 0x48, 0x54, 0x50, 0x48, 0x41, 0x48, + 0x42, 0x34, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, + 0x67, 0x81, 0x05, 0x02, 0x03, 0x0c, 0x0b, 0x69, + 0x64, 0x3a, 0x30, 0x30, 0x34, 0x39, 0x30, 0x30, + 0x30, 0x34, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, + 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xb8, + 0x5f, 0xd5, 0x67, 0xca, 0x92, 0xc4, 0x0e, 0xcf, + 0x0c, 0xd8, 0x1f, 0x6d, 0x3f, 0x03, 0x55, 0x6f, + 0x38, 0xa6, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55, + 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xd4, 0x04, + 0x64, 0xfc, 0x6e, 0x50, 0x0a, 0x56, 0x48, 0x0f, + 0x05, 0xa9, 0x00, 0xb7, 0x1d, 0x5e, 0x57, 0x08, + 0xd5, 0xdc, 0x30, 0x81, 0xb2, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, + 0x81, 0xa5, 0x30, 0x81, 0xa2, 0x30, 0x81, 0x9f, + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x30, 0x02, 0x86, 0x81, 0x92, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x7a, 0x63, 0x73, + 0x70, 0x72, 0x6f, 0x64, 0x65, 0x75, 0x73, 0x61, + 0x69, 0x6b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x2e, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x2f, + 0x65, 0x75, 0x73, 0x2d, 0x73, 0x74, 0x6d, 0x2d, + 0x6b, 0x65, 0x79, 0x69, 0x64, 0x2d, 0x31, 0x61, + 0x64, 0x62, 0x39, 0x39, 0x34, 0x61, 0x62, 0x35, + 0x38, 0x62, 0x65, 0x35, 0x37, 0x61, 0x30, 0x63, + 0x63, 0x39, 0x62, 0x39, 0x30, 0x30, 0x65, 0x37, + 0x38, 0x35, 0x31, 0x65, 0x31, 0x61, 0x34, 0x33, + 0x63, 0x30, 0x38, 0x36, 0x36, 0x30, 0x2f, 0x61, + 0x62, 0x64, 0x36, 0x31, 0x35, 0x66, 0x32, 0x2d, + 0x31, 0x35, 0x38, 0x61, 0x2d, 0x34, 0x35, 0x38, + 0x65, 0x2d, 0x61, 0x31, 0x35, 0x35, 0x2d, 0x37, + 0x63, 0x34, 0x63, 0x38, 0x63, 0x62, 0x31, 0x33, + 0x63, 0x36, 0x35, 0x2e, 0x63, 0x65, 0x72, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, + 0x02, 0x01, 0x00, 0xa2, 0x10, 0xc5, 0xbf, 0x41, + 0xa6, 0xba, 0x8c, 0x72, 0xca, 0x0f, 0x3e, 0x5e, + 0x7f, 0xe2, 0xcb, 0x60, 0xb8, 0x3f, 0xfb, 0xde, + 0x03, 0xe2, 0xfe, 0x20, 0x29, 0xdf, 0x11, 0xf5, + 0xb0, 0x50, 0x6d, 0x32, 0xe8, 0x1b, 0x05, 0xad, + 0x6b, 0x60, 0xb5, 0xed, 0xf3, 0xa4, 0x4a, 0xea, + 0x09, 0xe5, 0x65, 0x7e, 0xe0, 0xd5, 0x3a, 0x6a, + 0xdb, 0x64, 0xb7, 0x07, 0x8f, 0xa1, 0x63, 0xb3, + 0x89, 0x8a, 0xac, 0x49, 0x97, 0xa0, 0x9a, 0xa3, + 0xd3, 0x3a, 0xc2, 0x13, 0xb2, 0xbb, 0xab, 0x0d, + 0xf2, 0x35, 0xc5, 0x03, 0xde, 0x1c, 0xad, 0x6a, + 0x03, 0x0a, 0x4c, 0xe1, 0x37, 0x8f, 0xbc, 0x13, + 0xc0, 0x9a, 0x17, 0xd4, 0x2e, 0x36, 0x17, 0x51, + 0x12, 0xb0, 0x79, 0xbf, 0x9b, 0xb3, 0xb0, 0x74, + 0x25, 0x81, 0x7e, 0x21, 0x31, 0xb7, 0xc2, 0x5e, + 0xfb, 0x36, 0xab, 0xf3, 0x7a, 0x5f, 0xa4, 0x5e, + 0x8f, 0x0c, 0xbd, 0xcf, 0xf5, 0x50, 0xe7, 0x0c, + 0x51, 0x55, 0x48, 0xe6, 0x15, 0xb6, 0xd4, 0xaf, + 0x95, 0x72, 0x56, 0x94, 0xf7, 0x0e, 0xd6, 0x90, + 0xe3, 0xd3, 0x5d, 0xbd, 0x93, 0xa1, 0xbd, 0x6c, + 0xe4, 0xf2, 0x39, 0x4d, 0x54, 0x74, 0xcf, 0xf5, + 0xeb, 0x70, 0xdb, 0x4f, 0x52, 0xcd, 0x39, 0x8f, + 0x11, 0x54, 0x28, 0x06, 0x29, 0x8f, 0x23, 0xde, + 0x9e, 0x2f, 0x7b, 0xb6, 0x5f, 0xa3, 0x89, 0x04, + 0x99, 0x0a, 0xf1, 0x2d, 0xf9, 0x66, 0xd3, 0x13, + 0x45, 0xbd, 0x6c, 0x22, 0x57, 0xf5, 0xb1, 0xb9, + 0xdf, 0x5b, 0x7b, 0x1a, 0x3a, 0xdd, 0x6b, 0xc7, + 0x35, 0x88, 0xed, 0xc4, 0x09, 0x70, 0x4e, 0x5f, + 0xb5, 0x3e, 0xd1, 0x0b, 0xd0, 0xca, 0xef, 0x0b, + 0xe9, 0x8b, 0x6f, 0xc3, 0x16, 0xc3, 0x3d, 0x79, + 0x06, 0xef, 0x81, 0xf0, 0x60, 0x0b, 0x32, 0xe3, + 0x86, 0x6b, 0x92, 0x38, 0x90, 0x62, 0xed, 0x84, + 0x3a, 0xb7, 0x45, 0x43, 0x2e, 0xd0, 0x3a, 0x71, + 0x9e, 0x80, 0xcc, 0x9c, 0xac, 0x27, 0x10, 0x91, + 0xb7, 0xb2, 0xbd, 0x41, 0x40, 0xa7, 0xb7, 0xcf, + 0xe7, 0x38, 0xca, 0x68, 0xdd, 0x62, 0x09, 0xff, + 0x68, 0xce, 0xba, 0xe2, 0x07, 0x49, 0x09, 0xe7, + 0x1f, 0xdf, 0xe6, 0x26, 0xe5, 0x0f, 0xa9, 0xbf, + 0x2a, 0x5b, 0x67, 0x92, 0xa1, 0x10, 0x53, 0xb2, + 0x7a, 0x07, 0x29, 0x9d, 0xfd, 0x6d, 0xb6, 0x3b, + 0x45, 0xc1, 0x94, 0xcb, 0x1c, 0xc3, 0xce, 0xf6, + 0x8a, 0x1a, 0x81, 0x66, 0xb0, 0xa5, 0x14, 0xc7, + 0x9e, 0x1f, 0x6e, 0xb6, 0xff, 0x8b, 0x90, 0x87, + 0x3a, 0x3f, 0xa8, 0xc2, 0x2d, 0x8f, 0x6f, 0xdb, + 0xb4, 0xc4, 0x14, 0x3c, 0x1d, 0x12, 0x1d, 0x6d, + 0xcf, 0xa6, 0x04, 0x6a, 0xa8, 0x13, 0x5e, 0xf2, + 0x5e, 0x77, 0x80, 0x6b, 0x85, 0x83, 0xfe, 0xbb, + 0xeb, 0x70, 0xcb, 0x5f, 0xe4, 0x95, 0xaa, 0x0f, + 0x61, 0x36, 0x7c, 0xbb, 0x22, 0x1e, 0xba, 0x98, + 0x43, 0x52, 0x33, 0xae, 0xed, 0x5d, 0x10, 0x2c, + 0xb3, 0xa9, 0x31, 0x8e, 0x60, 0x54, 0xaf, 0x40, + 0x6d, 0x2e, 0x18, 0xc2, 0x6a, 0xf4, 0x7b, 0x9a, + 0x73, 0x0f, 0x58, 0x69, 0x23, 0xbb, 0xc4, 0x84, + 0x53, 0x30, 0xe2, 0xd6, 0x1e, 0x10, 0xc1, 0xec, + 0x82, 0x13, 0xab, 0x53, 0x86, 0xa2, 0xb9, 0xda, + 0xbb, 0x3a, 0xa2, 0xbe, 0xb0, 0x10, 0x99, 0x0e, + 0xe5, 0x9c, 0xc9, 0xf1, 0xce, 0x76, 0x46, 0xea, + 0x86, 0xaa, 0x36, 0x83, 0x99, 0x09, 0x9b, 0x30, + 0xd3, 0x26, 0xc7, 0xdf, 0x66, 0xc7, 0xf0, 0xdd, + 0x08, 0x09, 0x15, 0x15, 0x21, 0x49, 0x46, 0xd8, + 0x8a, 0x66, 0xca, 0x62, 0x9c, 0x79, 0x1d, 0x81, + 0xea, 0x5d, 0x82, 0xb0, 0xa6, 0x6b, 0x5c, 0xf5, + 0xb8, 0x8c, 0xf6, 0x16, 0x01, 0x2c, 0xf8, 0x27, + 0xf8, 0xcf, 0x88, 0xfe, 0xf3, 0xa4, 0xfc, 0x17, + 0x97, 0xe7, 0x07, 0x59, 0x06, 0xef, 0x30, 0x82, + 0x06, 0xeb, 0x30, 0x82, 0x04, 0xd3, 0xa0, 0x03, + 0x02, 0x01, 0x02, 0x02, 0x13, 0x33, 0x00, 0x00, + 0x02, 0x39, 0xf9, 0xbb, 0x6a, 0x1d, 0x49, 0x64, + 0x47, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x02, 0x39, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, + 0x81, 0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, + 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, + 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, + 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, + 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, + 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x36, + 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x2d, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, + 0x66, 0x74, 0x20, 0x54, 0x50, 0x4d, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x32, 0x30, 0x31, 0x34, 0x30, 0x1e, + 0x17, 0x0d, 0x31, 0x39, 0x30, 0x33, 0x32, 0x31, + 0x32, 0x30, 0x32, 0x39, 0x31, 0x35, 0x5a, 0x17, + 0x0d, 0x32, 0x35, 0x30, 0x33, 0x32, 0x31, 0x32, + 0x30, 0x32, 0x39, 0x31, 0x35, 0x5a, 0x30, 0x41, + 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53, + 0x54, 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44, + 0x2d, 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34, + 0x41, 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37, + 0x41, 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30, + 0x30, 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31, + 0x41, 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36, + 0x30, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, + 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, + 0x01, 0x00, 0xdb, 0xe2, 0x23, 0xf9, 0x86, 0x8f, + 0xa9, 0x71, 0x9f, 0x8b, 0xf9, 0x7c, 0xe9, 0x45, + 0x2d, 0x59, 0x56, 0x5e, 0x96, 0xf4, 0xdd, 0x9a, + 0x12, 0xcd, 0x90, 0x1a, 0x0c, 0xb5, 0x03, 0xbf, + 0x09, 0xbe, 0xbf, 0xf7, 0x55, 0x52, 0xe8, 0x39, + 0x4c, 0xbe, 0x2a, 0x28, 0x88, 0x78, 0x39, 0xa7, + 0xcb, 0xf9, 0x4c, 0x55, 0xd2, 0x31, 0x96, 0x3b, + 0x48, 0xa2, 0xf3, 0xf6, 0xd3, 0x1a, 0x81, 0x7f, + 0x90, 0x62, 0xab, 0xec, 0x5a, 0xc7, 0xa0, 0x7f, + 0x81, 0x32, 0x27, 0x9b, 0x29, 0x75, 0x7d, 0x1e, + 0x96, 0xc5, 0xfa, 0x0e, 0x7c, 0xe0, 0x60, 0x96, + 0x7a, 0xca, 0x94, 0xba, 0xe6, 0xb2, 0x69, 0xdd, + 0xc4, 0x7d, 0xbb, 0xd3, 0xc4, 0xb4, 0x6e, 0x00, + 0x86, 0x1f, 0x9d, 0x25, 0xe8, 0xae, 0xc7, 0x10, + 0x84, 0xdc, 0xc0, 0x34, 0x24, 0x6e, 0xf7, 0xfc, + 0xdd, 0x3d, 0x32, 0x7a, 0x43, 0x96, 0xd6, 0xc8, + 0x7b, 0xf4, 0x9b, 0x3d, 0xa7, 0x1e, 0xba, 0x4d, + 0xd0, 0x3b, 0x3d, 0x84, 0x9a, 0xd1, 0x25, 0x22, + 0x5d, 0x00, 0x44, 0xb0, 0x59, 0xb7, 0x40, 0xc5, + 0xa3, 0x53, 0x53, 0xaf, 0x8f, 0x9e, 0xfd, 0x8f, + 0x1e, 0x02, 0xd3, 0x4f, 0xf7, 0x09, 0xce, 0xc5, + 0xc6, 0x71, 0x5c, 0xe9, 0xe8, 0x7a, 0xb5, 0x6b, + 0xa4, 0xbf, 0x0b, 0xd9, 0xb6, 0xfa, 0x24, 0xb0, + 0xcd, 0x52, 0x22, 0x1d, 0x7e, 0xe8, 0x15, 0x2f, + 0x1e, 0x5e, 0xa2, 0xec, 0xd3, 0xa8, 0x02, 0x77, + 0xb9, 0x55, 0x9a, 0xcf, 0xcc, 0xd7, 0x08, 0x20, + 0xa5, 0xda, 0x39, 0x9a, 0x30, 0x76, 0x90, 0x37, + 0xa7, 0x60, 0xdf, 0x18, 0x12, 0x65, 0x17, 0xaa, + 0xdd, 0x48, 0xd5, 0x12, 0x1d, 0x4c, 0x83, 0x5d, + 0x81, 0x07, 0x1d, 0x18, 0x81, 0x40, 0x55, 0x60, + 0x8f, 0xa3, 0x6b, 0x34, 0x1e, 0xd5, 0xe6, 0xcf, + 0x52, 0x73, 0x77, 0x4a, 0x50, 0x4f, 0x1b, 0x0f, + 0x39, 0xc3, 0x0d, 0x16, 0xf9, 0xbb, 0x4c, 0x77, + 0xf6, 0x4e, 0xac, 0x9c, 0xfe, 0xe8, 0xbb, 0x52, + 0xa5, 0x0a, 0x0e, 0x9b, 0xf0, 0x0d, 0xef, 0xfb, + 0x6f, 0x89, 0x34, 0x7d, 0x47, 0xec, 0x14, 0x6a, + 0xf4, 0x0a, 0xe1, 0x60, 0x44, 0x73, 0x7b, 0xa0, + 0xab, 0x5b, 0x8c, 0x43, 0xa6, 0x05, 0x42, 0x61, + 0x46, 0xaa, 0x1c, 0xf5, 0xec, 0x2c, 0x86, 0x85, + 0x21, 0x99, 0xdf, 0x45, 0x8e, 0xf4, 0xd1, 0x1e, + 0xfb, 0xcd, 0x9b, 0x94, 0x32, 0xe0, 0xa0, 0xcc, + 0x4f, 0xad, 0xae, 0x44, 0x8b, 0x86, 0x27, 0x91, + 0xfe, 0x60, 0x9f, 0xf2, 0x63, 0x30, 0x6c, 0x5d, + 0x8d, 0xbc, 0xab, 0xd4, 0xf5, 0xa2, 0xb2, 0x74, + 0xe8, 0xd4, 0x95, 0xf2, 0xd6, 0x03, 0x8b, 0xc9, + 0xa3, 0x52, 0xe7, 0x63, 0x05, 0x64, 0x50, 0xe5, + 0x0a, 0x6a, 0xa0, 0x6c, 0x50, 0xcd, 0x37, 0x98, + 0xa8, 0x87, 0x02, 0x38, 0x5b, 0x6c, 0x02, 0x69, + 0x3d, 0x1f, 0x95, 0x74, 0x4d, 0x46, 0x76, 0x2a, + 0x9d, 0x62, 0xd4, 0xc7, 0x1b, 0xf9, 0x31, 0xa6, + 0x51, 0xee, 0x7b, 0xc8, 0xe4, 0x6e, 0x3a, 0xcf, + 0x4f, 0x4f, 0x49, 0x8a, 0xf5, 0x4f, 0x25, 0x93, + 0x23, 0x02, 0xef, 0x79, 0xa6, 0x27, 0xbe, 0x5a, + 0xe7, 0x74, 0xb7, 0xd7, 0xa8, 0xc1, 0xae, 0x55, + 0x88, 0xa4, 0xc7, 0x4d, 0xb7, 0x62, 0xf0, 0xf9, + 0x5b, 0xbf, 0x47, 0x5b, 0xfe, 0xcc, 0x0b, 0x89, + 0x19, 0x65, 0x4b, 0x6f, 0xdf, 0x4f, 0x7d, 0x4d, + 0x96, 0x42, 0x0d, 0x2a, 0xa1, 0xbd, 0x3e, 0x70, + 0x92, 0xba, 0xc8, 0x59, 0xd5, 0x1d, 0x3a, 0x98, + 0x53, 0x75, 0xa6, 0x32, 0xc8, 0x72, 0x03, 0x46, + 0x5f, 0x5c, 0x13, 0xa4, 0xdb, 0xc7, 0x55, 0x35, + 0x22, 0x0d, 0xc6, 0x17, 0x85, 0xbd, 0x46, 0x4b, + 0xfa, 0x1e, 0x49, 0xc2, 0xfe, 0x1e, 0xf9, 0x62, + 0x89, 0x56, 0x84, 0xdf, 0xa0, 0xfb, 0xfd, 0x93, + 0xa4, 0x25, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, + 0x82, 0x01, 0x8e, 0x30, 0x82, 0x01, 0x8a, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, + 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x84, 0x30, + 0x1b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x14, + 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x82, 0x37, 0x15, 0x24, 0x06, 0x05, 0x67, + 0x81, 0x05, 0x08, 0x03, 0x30, 0x16, 0x06, 0x03, + 0x55, 0x1d, 0x20, 0x04, 0x0f, 0x30, 0x0d, 0x30, + 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, + 0x82, 0x37, 0x15, 0x1f, 0x30, 0x12, 0x06, 0x03, + 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, + 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, + 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, + 0x16, 0x04, 0x14, 0xb8, 0x5f, 0xd5, 0x67, 0xca, + 0x92, 0xc4, 0x0e, 0xcf, 0x0c, 0xd8, 0x1f, 0x6d, + 0x3f, 0x03, 0x55, 0x6f, 0x38, 0xa6, 0x51, 0x30, + 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, + 0x30, 0x16, 0x80, 0x14, 0x7a, 0x8c, 0x0a, 0xce, + 0x2f, 0x48, 0x62, 0x17, 0xe2, 0x94, 0xd1, 0xae, + 0x55, 0xc1, 0x52, 0xec, 0x71, 0x74, 0xa4, 0x56, + 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, + 0x69, 0x30, 0x67, 0x30, 0x65, 0xa0, 0x63, 0xa0, + 0x61, 0x86, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x6f, + 0x70, 0x73, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x4d, + 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, + 0x25, 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32, + 0x30, 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25, + 0x32, 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63, + 0x72, 0x6c, 0x30, 0x7d, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x71, + 0x30, 0x6f, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x61, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, + 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x70, 0x6b, 0x69, 0x6f, 0x70, 0x73, 0x2f, + 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x4d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x25, + 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32, 0x30, + 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25, 0x32, + 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63, 0x72, + 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, + 0x03, 0x82, 0x02, 0x01, 0x00, 0x41, 0xaa, 0xfe, + 0x28, 0x6c, 0xf7, 0x6b, 0x53, 0xde, 0x77, 0xc0, + 0x80, 0x50, 0x94, 0xd9, 0xdb, 0x46, 0x8e, 0x6a, + 0x93, 0xa9, 0x10, 0x37, 0x27, 0x1f, 0xf5, 0x70, + 0xf1, 0xa8, 0xcf, 0xa1, 0x45, 0x86, 0x2a, 0xdd, + 0x8f, 0xb8, 0xb5, 0xc1, 0xe6, 0xcf, 0x8a, 0xfa, + 0x32, 0xa1, 0x4b, 0xb7, 0xa4, 0xbf, 0x0a, 0x48, + 0xcb, 0x42, 0x63, 0x71, 0xc1, 0x96, 0xb9, 0x3a, + 0x37, 0x84, 0x0e, 0x24, 0x39, 0xeb, 0x58, 0xce, + 0x3d, 0xb7, 0xa9, 0x44, 0x92, 0x59, 0xb9, 0xff, + 0xdb, 0x18, 0xbe, 0x6a, 0x5e, 0xe7, 0xce, 0xef, + 0xb8, 0x40, 0x53, 0xaf, 0xc1, 0x9b, 0xfb, 0x42, + 0x99, 0x7e, 0x9d, 0x05, 0x2b, 0x71, 0x0a, 0x7a, + 0x7a, 0x44, 0xd1, 0x31, 0xca, 0xf0, 0x5f, 0x74, + 0x85, 0xa9, 0xe2, 0xbc, 0xc8, 0x0c, 0xad, 0x57, + 0xd1, 0xe9, 0x48, 0x90, 0x88, 0x57, 0x86, 0xd7, + 0xc5, 0xc9, 0xe6, 0xb2, 0x5e, 0x5f, 0x13, 0xdc, + 0x10, 0x7f, 0xdf, 0x63, 0x8a, 0xd5, 0x9e, 0x90, + 0xc2, 0x75, 0x53, 0x1e, 0x68, 0x17, 0x2b, 0x03, + 0x29, 0x15, 0x03, 0xc5, 0x8c, 0x66, 0x3e, 0xae, + 0xbd, 0x4a, 0x32, 0x7e, 0x59, 0x89, 0x0b, 0x84, + 0xc2, 0xd9, 0x90, 0xfa, 0x02, 0x22, 0x90, 0x8d, + 0x9c, 0xb6, 0x0c, 0x4d, 0xe1, 0x28, 0x76, 0xd7, + 0x82, 0xc3, 0x36, 0xc2, 0xa3, 0x2a, 0x52, 0xe5, + 0xfe, 0x3c, 0x8f, 0xe3, 0x4b, 0xda, 0x6a, 0xdb, + 0xc0, 0x7a, 0x3c, 0x57, 0xfa, 0x85, 0x8f, 0xfb, + 0x62, 0xc3, 0xa1, 0x38, 0xce, 0x84, 0xf2, 0xba, + 0x12, 0xf4, 0x30, 0x2a, 0x4a, 0x94, 0xa9, 0x35, + 0x2c, 0x7d, 0x11, 0xc7, 0x68, 0x1f, 0x47, 0xaa, + 0x57, 0x43, 0x06, 0x70, 0x79, 0x8c, 0xb6, 0x3b, + 0x5d, 0x57, 0xf3, 0xf3, 0xc0, 0x2c, 0xc5, 0xde, + 0x41, 0x99, 0xf6, 0xdd, 0x55, 0x8a, 0xe4, 0x13, + 0xca, 0xc9, 0xec, 0x69, 0x93, 0x13, 0x48, 0xf0, + 0x5f, 0xda, 0x2e, 0xfd, 0xfb, 0xa9, 0x1b, 0x92, + 0xde, 0x49, 0x71, 0x37, 0x8c, 0x3f, 0xc2, 0x08, + 0x0a, 0x83, 0x25, 0xf1, 0x6e, 0x0a, 0xe3, 0x55, + 0x85, 0x96, 0x9a, 0x2d, 0xa2, 0xc0, 0xa1, 0xee, + 0xfe, 0x23, 0x3b, 0x69, 0x22, 0x03, 0xfd, 0xcc, + 0x8a, 0xdd, 0xb4, 0x53, 0x8d, 0x84, 0xa6, 0xac, + 0xe0, 0x1e, 0x07, 0xe5, 0xd7, 0xf9, 0xcb, 0xb9, + 0xe3, 0x9a, 0xb7, 0x84, 0x70, 0xa1, 0x93, 0xd6, + 0x02, 0x1e, 0xfe, 0xdb, 0x28, 0x7c, 0xf7, 0xd4, + 0x62, 0x6f, 0x80, 0x75, 0xc8, 0xd8, 0x35, 0x26, + 0x0c, 0xcb, 0x84, 0xed, 0xbb, 0x95, 0xdf, 0x7f, + 0xd5, 0xbb, 0x00, 0x96, 0x97, 0x32, 0xe7, 0xba, + 0xe8, 0x29, 0xb5, 0x1a, 0x51, 0x81, 0xbb, 0x04, + 0xd1, 0x21, 0x76, 0x34, 0x6d, 0x1e, 0x93, 0x96, + 0x1f, 0x96, 0x53, 0x5f, 0x5c, 0x9e, 0xf3, 0x9d, + 0x82, 0x1c, 0x39, 0x36, 0x59, 0xae, 0xc9, 0x3c, + 0x53, 0x4a, 0x67, 0x65, 0x6e, 0xbf, 0xa6, 0xac, + 0x3e, 0xda, 0xb2, 0xa7, 0x63, 0x07, 0x17, 0xe1, + 0x5b, 0xda, 0x6a, 0x31, 0x9f, 0xfb, 0xb4, 0xea, + 0xa1, 0x97, 0x08, 0x6e, 0xb2, 0x68, 0xf3, 0x72, + 0x76, 0x99, 0xe8, 0x00, 0x46, 0x88, 0x26, 0xe1, + 0x3c, 0x07, 0x2b, 0x78, 0x49, 0xda, 0x79, 0x3a, + 0xbd, 0x6f, 0xca, 0x5c, 0xa0, 0xa8, 0xed, 0x34, + 0xcc, 0xdb, 0x13, 0xe2, 0x51, 0x9b, 0x3d, 0x03, + 0xac, 0xc7, 0xf6, 0x32, 0xe1, 0x11, 0x5d, 0xe1, + 0xc5, 0xfd, 0x9e, 0x7a, 0xcd, 0x06, 0xb9, 0xe6, + 0xfc, 0xe0, 0x03, 0x31, 0xf4, 0x4a, 0xa9, 0x3b, + 0x79, 0x01, 0xb0, 0x64, 0x68, 0x9f, 0x6e, 0x76, + 0xa1, 0xcc, 0xec, 0x17, 0x41, 0x9d, 0xd4, 0x5b, + 0x4e, 0x9d, 0xe5, 0x46, 0xd4, 0x6b, 0x60, 0x2a, + 0x23, 0xb5, 0x7a, 0x89, 0x7c, 0x27, 0x96, 0x65, + 0x97, 0x56, 0xec, 0x98, 0xe3, 0x67, 0x70, 0x75, + 0x62, 0x41, 0x72, 0x65, 0x61, 0x59, 0x01, 0x36, + 0x00, 0x01, 0x00, 0x0b, 0x00, 0x06, 0x04, 0x72, + 0x00, 0x20, 0x9d, 0xff, 0xcb, 0xf3, 0x6c, 0x38, + 0x3a, 0xe6, 0x99, 0xfb, 0x98, 0x68, 0xdc, 0x6d, + 0xcb, 0x89, 0xd7, 0x15, 0x38, 0x84, 0xbe, 0x28, + 0x03, 0x92, 0x2c, 0x12, 0x41, 0x58, 0xbf, 0xad, + 0x22, 0xae, 0x00, 0x10, 0x00, 0x10, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xc5, 0xb6, + 0x9c, 0x06, 0x1d, 0xcf, 0xb9, 0xf2, 0x5e, 0x99, + 0x7d, 0x6d, 0x73, 0xd8, 0x36, 0xc1, 0x4a, 0x90, + 0x05, 0x4d, 0x82, 0x57, 0xc1, 0xb6, 0x6a, 0xd1, + 0x43, 0x03, 0x85, 0xf8, 0x52, 0x4f, 0xd2, 0x27, + 0x91, 0x0b, 0xb5, 0x93, 0xa0, 0x68, 0xf8, 0x80, + 0x1b, 0xaa, 0x65, 0x97, 0x45, 0x11, 0x86, 0x34, + 0xd6, 0x67, 0xf8, 0xd5, 0x12, 0x79, 0x84, 0xee, + 0x70, 0x99, 0x00, 0x63, 0xa8, 0xb4, 0x43, 0x0b, + 0x4c, 0x57, 0x4a, 0xd6, 0x9b, 0x75, 0x63, 0x8a, + 0x46, 0x57, 0xdb, 0x14, 0xc8, 0x71, 0xd1, 0xb3, + 0x07, 0x68, 0x58, 0xbc, 0x55, 0x84, 0x80, 0x2a, + 0xd2, 0x36, 0x9f, 0xc1, 0x64, 0xa0, 0x11, 0x4b, + 0xc9, 0x32, 0x31, 0x3a, 0xd6, 0x87, 0x26, 0x1a, + 0x3a, 0x78, 0x3d, 0x89, 0xdb, 0x00, 0x28, 0x3b, + 0xae, 0x2b, 0x1b, 0x56, 0xe2, 0x8c, 0x4c, 0x63, + 0xac, 0x6e, 0x6c, 0xf7, 0xb5, 0x7d, 0x4d, 0x0b, + 0x9f, 0x06, 0xa0, 0x10, 0x35, 0x38, 0x20, 0x4d, + 0xcc, 0x07, 0xd7, 0x00, 0x4e, 0x86, 0xba, 0xfe, + 0x8b, 0xe4, 0x3f, 0x4a, 0xd6, 0xca, 0xbf, 0x67, + 0x40, 0x1a, 0xa4, 0xda, 0x82, 0x52, 0x15, 0xb8, + 0x14, 0x3a, 0x7c, 0xa9, 0x02, 0xc1, 0x01, 0x69, + 0xc6, 0x51, 0xd4, 0xbc, 0x1f, 0x95, 0xb2, 0xee, + 0x1f, 0xdd, 0xb5, 0x73, 0x16, 0x5e, 0x29, 0x3f, + 0x47, 0xac, 0x65, 0xfb, 0x63, 0x5c, 0xb9, 0xc8, + 0x13, 0x2d, 0xec, 0x85, 0xde, 0x71, 0x0d, 0x84, + 0x93, 0x74, 0x76, 0x91, 0xdd, 0x1d, 0x6d, 0x3d, + 0xc7, 0x36, 0x19, 0x19, 0x86, 0xde, 0x7c, 0xca, + 0xd6, 0xc6, 0x65, 0x7e, 0x4b, 0x24, 0x9c, 0xce, + 0x92, 0x6b, 0x1c, 0xe0, 0xa0, 0xa9, 0x6c, 0xc3, + 0xed, 0x4f, 0x2a, 0x54, 0x07, 0x00, 0x32, 0x5e, + 0x1b, 0x94, 0x37, 0xcd, 0xe2, 0x32, 0xa8, 0xd5, + 0x2c, 0xfb, 0x03, 0x9d, 0x79, 0xdf, 0x68, 0x63, + 0x65, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x58, + 0xa1, 0xff, 0x54, 0x43, 0x47, 0x80, 0x17, 0x00, + 0x22, 0x00, 0x0b, 0xdb, 0x1f, 0x74, 0x21, 0x4f, + 0xa9, 0x0d, 0x90, 0x64, 0xa2, 0x33, 0xbe, 0x3f, + 0xf1, 0x95, 0xb0, 0x4e, 0x3f, 0x02, 0xdc, 0xad, + 0xb0, 0x05, 0x13, 0xe6, 0x32, 0x5f, 0xed, 0x90, + 0x2c, 0xad, 0xc0, 0x00, 0x14, 0x58, 0x52, 0x07, + 0x5d, 0x64, 0x6c, 0x1f, 0xd1, 0x13, 0x7f, 0xc3, + 0x74, 0xf6, 0x4b, 0xe3, 0xa0, 0x2e, 0xb7, 0x71, + 0xda, 0x00, 0x00, 0x00, 0x00, 0x29, 0x3c, 0x64, + 0xdf, 0x95, 0x38, 0xba, 0x73, 0xe3, 0x57, 0x61, + 0xa0, 0x01, 0x24, 0x01, 0x08, 0xc9, 0xd6, 0xea, + 0x60, 0xe4, 0x00, 0x22, 0x00, 0x0b, 0xe1, 0x86, + 0xbb, 0x79, 0x27, 0xe5, 0x01, 0x19, 0x90, 0xb3, + 0xe9, 0x08, 0xb0, 0xee, 0xfa, 0x3a, 0x67, 0xa9, + 0xf3, 0xc8, 0x9e, 0x03, 0x41, 0x07, 0x75, 0x60, + 0xbc, 0x94, 0x0c, 0x2a, 0xb7, 0xad, 0x00, 0x22, + 0x00, 0x0b, 0x35, 0xb1, 0x72, 0xd6, 0x3c, 0xe9, + 0x85, 0xe8, 0x66, 0xed, 0x10, 0x7a, 0x5c, 0xa3, + 0xe6, 0xd9, 0x4d, 0xf0, 0x52, 0x69, 0x26, 0x14, + 0xb4, 0x36, 0x7e, 0xad, 0x76, 0x9e, 0x58, 0x68, + 0x3e, 0x91 +}; + +const unsigned char attstmt_tpm_es256[3841] = { + 0xa6, 0x63, 0x61, 0x6c, 0x67, 0x39, 0xff, 0xfe, + 0x63, 0x73, 0x69, 0x67, 0x59, 0x01, 0x00, 0x6d, + 0x11, 0x61, 0x1f, 0x45, 0xb9, 0x7f, 0x65, 0x6f, + 0x97, 0x46, 0xfe, 0xbb, 0x8a, 0x98, 0x07, 0xa3, + 0xbc, 0x67, 0x5c, 0xd7, 0x65, 0xa4, 0xf4, 0x6c, + 0x5b, 0x37, 0x75, 0xa4, 0x7f, 0x08, 0x52, 0xeb, + 0x1e, 0x12, 0xe2, 0x78, 0x8c, 0x7d, 0x94, 0xab, + 0x7b, 0xed, 0x05, 0x17, 0x67, 0x7e, 0xaa, 0x02, + 0x89, 0x6d, 0xe8, 0x6d, 0x43, 0x30, 0x99, 0xc6, + 0xf9, 0x59, 0xe5, 0x82, 0x3c, 0x56, 0x4e, 0x77, + 0x11, 0x25, 0xe4, 0x43, 0x6a, 0xae, 0x92, 0x4f, + 0x60, 0x92, 0x50, 0xf9, 0x65, 0x0e, 0x44, 0x38, + 0x3d, 0xf7, 0xaf, 0x66, 0x89, 0xc7, 0xe6, 0xe6, + 0x01, 0x07, 0x9e, 0x90, 0xfd, 0x6d, 0xaa, 0x35, + 0x51, 0x51, 0xbf, 0x54, 0x13, 0x95, 0xc2, 0x17, + 0xfa, 0x32, 0x0f, 0xa7, 0x82, 0x17, 0x58, 0x6c, + 0x3d, 0xea, 0x88, 0xd8, 0x64, 0xc7, 0xf8, 0xc2, + 0xd6, 0x1c, 0xbb, 0xea, 0x1e, 0xb3, 0xd9, 0x4c, + 0xa7, 0xce, 0x18, 0x1e, 0xcb, 0x42, 0x5f, 0xbf, + 0x44, 0xe7, 0xf1, 0x22, 0xe0, 0x5b, 0xeb, 0xff, + 0xb6, 0x1e, 0x6f, 0x60, 0x12, 0x16, 0x63, 0xfe, + 0xab, 0x5e, 0x31, 0x13, 0xdb, 0x72, 0xc6, 0x9a, + 0xf8, 0x8f, 0x19, 0x6b, 0x2e, 0xaf, 0x7d, 0xca, + 0x9f, 0xbc, 0x6b, 0x1a, 0x8b, 0x5e, 0xe3, 0x9e, + 0xaa, 0x8c, 0x79, 0x9c, 0x4e, 0xed, 0xe4, 0xff, + 0x3d, 0x12, 0x79, 0x90, 0x09, 0x61, 0x97, 0x67, + 0xbf, 0x04, 0xac, 0x37, 0xea, 0xa9, 0x1f, 0x9f, + 0x52, 0x64, 0x0b, 0xeb, 0xc3, 0x61, 0xd4, 0x13, + 0xb0, 0x84, 0xf1, 0x3c, 0x74, 0x83, 0xcc, 0xa8, + 0x1c, 0x14, 0xe6, 0x9d, 0xfe, 0xec, 0xee, 0xa1, + 0xd2, 0xc2, 0x0a, 0xa6, 0x36, 0x08, 0xbb, 0x17, + 0xa5, 0x7b, 0x53, 0x34, 0x0e, 0xc9, 0x09, 0xe5, + 0x10, 0xa6, 0x85, 0x01, 0x71, 0x66, 0xff, 0xd0, + 0x6d, 0x4b, 0x93, 0xdb, 0x81, 0x25, 0x01, 0x63, + 0x76, 0x65, 0x72, 0x63, 0x32, 0x2e, 0x30, 0x63, + 0x78, 0x35, 0x63, 0x82, 0x59, 0x05, 0xc4, 0x30, + 0x82, 0x05, 0xc0, 0x30, 0x82, 0x03, 0xa8, 0xa0, + 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x30, 0xcd, + 0xf2, 0x7e, 0x81, 0xc0, 0x43, 0x85, 0xa2, 0xd7, + 0x29, 0xef, 0xf7, 0x9f, 0xa5, 0x2b, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x41, 0x31, + 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53, 0x54, + 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44, 0x2d, + 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34, 0x41, + 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37, 0x41, + 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30, 0x30, + 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31, 0x41, + 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36, 0x30, + 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x31, + 0x30, 0x32, 0x31, 0x35, 0x30, 0x36, 0x35, 0x33, + 0x5a, 0x17, 0x0d, 0x32, 0x37, 0x30, 0x36, 0x30, + 0x33, 0x31, 0x39, 0x34, 0x30, 0x31, 0x36, 0x5a, + 0x30, 0x00, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, + 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, + 0x01, 0x01, 0x00, 0xdb, 0xd5, 0x9a, 0xfc, 0x09, + 0xa7, 0xc4, 0xa5, 0x5f, 0xbe, 0x5f, 0xa2, 0xeb, + 0xd6, 0x8e, 0xed, 0xc5, 0x67, 0xa6, 0xa7, 0xd9, + 0xb2, 0x46, 0xc6, 0xe0, 0xae, 0x0c, 0x02, 0x25, + 0x0a, 0xf2, 0xc5, 0x96, 0xdc, 0xb7, 0x0e, 0xb9, + 0x86, 0xd3, 0x51, 0xbb, 0x63, 0xf0, 0x4f, 0x8a, + 0x5e, 0xd7, 0xf7, 0xff, 0xbb, 0x29, 0xbd, 0x58, + 0xcf, 0x75, 0x02, 0x39, 0xcb, 0x80, 0xf1, 0xd4, + 0xb6, 0x75, 0x67, 0x2f, 0x27, 0x4d, 0x0c, 0xcc, + 0x18, 0x59, 0x87, 0xfa, 0x51, 0xd1, 0x80, 0xb5, + 0x1a, 0xac, 0xac, 0x29, 0x51, 0xcf, 0x27, 0xaa, + 0x74, 0xac, 0x3e, 0x59, 0x56, 0x67, 0xe4, 0x42, + 0xe8, 0x30, 0x35, 0xb2, 0xf6, 0x27, 0x91, 0x62, + 0x60, 0x42, 0x42, 0x12, 0xde, 0xfe, 0xdd, 0xee, + 0xe8, 0xa8, 0x82, 0xf9, 0xb1, 0x08, 0xd5, 0x8d, + 0x57, 0x9a, 0x29, 0xb9, 0xb4, 0xe9, 0x19, 0x1e, + 0x33, 0x7d, 0x37, 0xa0, 0xce, 0x2e, 0x53, 0x13, + 0x39, 0xb6, 0x12, 0x61, 0x63, 0xbf, 0xd3, 0x42, + 0xeb, 0x6f, 0xed, 0xc1, 0x8e, 0x26, 0xba, 0x7d, + 0x8b, 0x37, 0x7c, 0xbb, 0x42, 0x1e, 0x56, 0x76, + 0xda, 0xdb, 0x35, 0x6b, 0x80, 0xe1, 0x8e, 0x00, + 0xac, 0xd2, 0xfc, 0x22, 0x96, 0x14, 0x0c, 0xf4, + 0xe4, 0xc5, 0xad, 0x14, 0xb7, 0x4d, 0x46, 0x63, + 0x30, 0x79, 0x3a, 0x7c, 0x33, 0xb5, 0xe5, 0x2e, + 0xbb, 0x5f, 0xca, 0xf2, 0x75, 0xe3, 0x4e, 0x99, + 0x64, 0x1b, 0x26, 0x99, 0x60, 0x1a, 0x79, 0xcc, + 0x30, 0x2c, 0xb3, 0x4c, 0x59, 0xf7, 0x77, 0x59, + 0xd5, 0x90, 0x70, 0x21, 0x79, 0x8c, 0x1f, 0x79, + 0x0a, 0x12, 0x8b, 0x3b, 0x37, 0x2d, 0x97, 0x39, + 0x89, 0x92, 0x0c, 0x44, 0x7c, 0xe9, 0x9f, 0xce, + 0x6d, 0xad, 0xc5, 0xae, 0xea, 0x8e, 0x50, 0x22, + 0x37, 0xe0, 0xd1, 0x9e, 0xd6, 0xe6, 0xa8, 0xcc, + 0x21, 0xfb, 0xff, 0x02, 0x03, 0x01, 0x00, 0x01, + 0xa3, 0x82, 0x01, 0xf3, 0x30, 0x82, 0x01, 0xef, + 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, + 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80, + 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, + 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x6d, + 0x06, 0x03, 0x55, 0x1d, 0x20, 0x01, 0x01, 0xff, + 0x04, 0x63, 0x30, 0x61, 0x30, 0x5f, 0x06, 0x09, + 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, + 0x1f, 0x30, 0x52, 0x30, 0x50, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x02, 0x30, + 0x44, 0x1e, 0x42, 0x00, 0x54, 0x00, 0x43, 0x00, + 0x50, 0x00, 0x41, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x54, 0x00, 0x72, 0x00, 0x75, 0x00, 0x73, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x64, 0x00, 0x20, 0x00, + 0x20, 0x00, 0x50, 0x00, 0x6c, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, + 0x6d, 0x00, 0x20, 0x00, 0x20, 0x00, 0x49, 0x00, + 0x64, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x69, 0x00, 0x74, 0x00, 0x79, 0x30, 0x10, 0x06, + 0x03, 0x55, 0x1d, 0x25, 0x04, 0x09, 0x30, 0x07, + 0x06, 0x05, 0x67, 0x81, 0x05, 0x08, 0x03, 0x30, + 0x59, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x01, 0x01, + 0xff, 0x04, 0x4f, 0x30, 0x4d, 0xa4, 0x4b, 0x30, + 0x49, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, 0x67, + 0x81, 0x05, 0x02, 0x01, 0x0c, 0x0b, 0x69, 0x64, + 0x3a, 0x35, 0x33, 0x35, 0x34, 0x34, 0x44, 0x32, + 0x30, 0x31, 0x17, 0x30, 0x15, 0x06, 0x05, 0x67, + 0x81, 0x05, 0x02, 0x02, 0x0c, 0x0c, 0x53, 0x54, + 0x33, 0x33, 0x48, 0x54, 0x50, 0x48, 0x41, 0x48, + 0x42, 0x34, 0x31, 0x16, 0x30, 0x14, 0x06, 0x05, + 0x67, 0x81, 0x05, 0x02, 0x03, 0x0c, 0x0b, 0x69, + 0x64, 0x3a, 0x30, 0x30, 0x34, 0x39, 0x30, 0x30, + 0x30, 0x34, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, + 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x45, + 0x1a, 0xec, 0xfc, 0x91, 0x70, 0xf8, 0x83, 0x8b, + 0x9c, 0x47, 0x2f, 0x0b, 0x9f, 0x07, 0xf3, 0x2f, + 0x7c, 0xa2, 0x8a, 0x30, 0x1d, 0x06, 0x03, 0x55, + 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x55, 0xa6, + 0xee, 0xe3, 0x28, 0xdd, 0x40, 0x7f, 0x21, 0xd2, + 0x7b, 0x8c, 0x69, 0x2f, 0x8c, 0x08, 0x29, 0xbc, + 0x95, 0xb8, 0x30, 0x81, 0xb2, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, + 0x81, 0xa5, 0x30, 0x81, 0xa2, 0x30, 0x81, 0x9f, + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, + 0x30, 0x02, 0x86, 0x81, 0x92, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, 0x61, 0x7a, 0x63, 0x73, + 0x70, 0x72, 0x6f, 0x64, 0x65, 0x75, 0x73, 0x61, + 0x69, 0x6b, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x2e, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x73, 0x2e, 0x6e, 0x65, 0x74, 0x2f, + 0x65, 0x75, 0x73, 0x2d, 0x73, 0x74, 0x6d, 0x2d, + 0x6b, 0x65, 0x79, 0x69, 0x64, 0x2d, 0x31, 0x61, + 0x64, 0x62, 0x39, 0x39, 0x34, 0x61, 0x62, 0x35, + 0x38, 0x62, 0x65, 0x35, 0x37, 0x61, 0x30, 0x63, + 0x63, 0x39, 0x62, 0x39, 0x30, 0x30, 0x65, 0x37, + 0x38, 0x35, 0x31, 0x65, 0x31, 0x61, 0x34, 0x33, + 0x63, 0x30, 0x38, 0x36, 0x36, 0x30, 0x2f, 0x62, + 0x36, 0x63, 0x30, 0x64, 0x39, 0x38, 0x64, 0x2d, + 0x35, 0x37, 0x38, 0x61, 0x2d, 0x34, 0x62, 0x66, + 0x62, 0x2d, 0x61, 0x32, 0x64, 0x33, 0x2d, 0x65, + 0x64, 0x66, 0x65, 0x35, 0x66, 0x38, 0x32, 0x30, + 0x36, 0x30, 0x31, 0x2e, 0x63, 0x65, 0x72, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, + 0x02, 0x01, 0x00, 0x2a, 0x08, 0x30, 0x1f, 0xfd, + 0x8f, 0x80, 0x9b, 0x4b, 0x37, 0x82, 0x61, 0x86, + 0x36, 0x57, 0x90, 0xb5, 0x1d, 0x1f, 0xa3, 0xae, + 0x68, 0xac, 0xa7, 0x96, 0x6a, 0x25, 0x5e, 0xc5, + 0x82, 0x7c, 0x36, 0x64, 0x58, 0x11, 0xcb, 0xa5, + 0xee, 0xbf, 0xc4, 0xdb, 0xa0, 0xc7, 0x82, 0x3b, + 0xa3, 0x85, 0x9b, 0xc4, 0xee, 0x07, 0x36, 0xd7, + 0xc7, 0xb6, 0x23, 0xed, 0xc2, 0x73, 0xab, 0xbe, + 0xbe, 0xee, 0x63, 0x17, 0xf9, 0xd7, 0x7a, 0x23, + 0x7b, 0xf8, 0x09, 0x7a, 0xaa, 0x7f, 0x67, 0xc3, + 0x04, 0x84, 0x71, 0x9b, 0x06, 0x9c, 0x07, 0x42, + 0x4b, 0x65, 0x41, 0x56, 0x58, 0x14, 0x92, 0xb0, + 0xb9, 0xaf, 0xa1, 0x39, 0xd4, 0x08, 0x2d, 0x71, + 0xd5, 0x6c, 0x56, 0xb9, 0x2b, 0x1e, 0xf3, 0x93, + 0xa5, 0xe9, 0xb2, 0x9b, 0x4d, 0x05, 0x2b, 0xbc, + 0xd2, 0x20, 0x57, 0x3b, 0xa4, 0x01, 0x68, 0x8c, + 0x23, 0x20, 0x7d, 0xbb, 0x71, 0xe4, 0x2a, 0x24, + 0xba, 0x75, 0x0c, 0x89, 0x54, 0x22, 0xeb, 0x0e, + 0xb2, 0xf4, 0xc2, 0x1f, 0x02, 0xb7, 0xe3, 0x06, + 0x41, 0x15, 0x6b, 0xf3, 0xc8, 0x2d, 0x5b, 0xc2, + 0x21, 0x82, 0x3e, 0xe8, 0x95, 0x40, 0x39, 0x9e, + 0x91, 0x68, 0x33, 0x0c, 0x3d, 0x45, 0xef, 0x99, + 0x79, 0xe6, 0x32, 0xc9, 0x00, 0x84, 0x36, 0xfb, + 0x0a, 0x8d, 0x41, 0x1c, 0x32, 0x64, 0x06, 0x9e, + 0x0f, 0xb5, 0x04, 0xcc, 0x08, 0xb1, 0xb6, 0x2b, + 0xcf, 0x36, 0x0f, 0x73, 0x14, 0x8e, 0x25, 0x44, + 0xb3, 0x0c, 0x34, 0x14, 0x96, 0x0c, 0x8a, 0x65, + 0xa1, 0xde, 0x8e, 0xc8, 0x9d, 0xbe, 0x66, 0xdf, + 0x06, 0x91, 0xca, 0x15, 0x0f, 0x92, 0xd5, 0x2a, + 0x0b, 0xdc, 0x4c, 0x6a, 0xf3, 0x16, 0x4a, 0x3e, + 0xb9, 0x76, 0xbc, 0xfe, 0x62, 0xd4, 0xa8, 0xcd, + 0x94, 0x78, 0x0d, 0xdd, 0x94, 0xfd, 0x5e, 0x63, + 0x57, 0x27, 0x05, 0x9c, 0xd0, 0x80, 0x91, 0x91, + 0x79, 0xe8, 0x5e, 0x18, 0x64, 0x22, 0xe4, 0x2c, + 0x13, 0x65, 0xa4, 0x51, 0x5a, 0x1e, 0x3b, 0x71, + 0x2e, 0x70, 0x9f, 0xc4, 0xa5, 0x20, 0xcd, 0xef, + 0xd8, 0x3f, 0xa4, 0xf5, 0x89, 0x8a, 0xa5, 0x4f, + 0x76, 0x2d, 0x49, 0x56, 0x00, 0x8d, 0xde, 0x40, + 0xba, 0x24, 0x46, 0x51, 0x38, 0xad, 0xdb, 0xc4, + 0x04, 0xf4, 0x6e, 0xc0, 0x29, 0x48, 0x07, 0x6a, + 0x1b, 0x26, 0x32, 0x0a, 0xfb, 0xea, 0x71, 0x2a, + 0x11, 0xfc, 0x98, 0x7c, 0x44, 0x87, 0xbc, 0x06, + 0x3a, 0x4d, 0xbd, 0x91, 0x63, 0x4f, 0x26, 0x48, + 0x54, 0x47, 0x1b, 0xbd, 0xf0, 0xf1, 0x56, 0x05, + 0xc5, 0x0f, 0x8f, 0x20, 0xa5, 0xcc, 0xfb, 0x76, + 0xb0, 0xbd, 0x83, 0xde, 0x7f, 0x39, 0x4f, 0xcf, + 0x61, 0x74, 0x52, 0xa7, 0x1d, 0xf6, 0xb5, 0x5e, + 0x4a, 0x82, 0x20, 0xc1, 0x94, 0xaa, 0x2c, 0x33, + 0xd6, 0x0a, 0xf9, 0x8f, 0x92, 0xc6, 0x29, 0x80, + 0xf5, 0xa2, 0xb1, 0xff, 0xb6, 0x2b, 0xaa, 0x04, + 0x00, 0x72, 0xb4, 0x12, 0xbb, 0xb1, 0xf1, 0x3c, + 0x88, 0xa3, 0xab, 0x49, 0x17, 0x90, 0x80, 0x59, + 0xa2, 0x96, 0x41, 0x69, 0x74, 0x33, 0x8a, 0x28, + 0x33, 0x7e, 0xb3, 0x19, 0x92, 0x28, 0xc1, 0xf0, + 0xd1, 0x82, 0xd5, 0x42, 0xff, 0xe7, 0xa5, 0x3f, + 0x1e, 0xb6, 0x4a, 0x23, 0xcc, 0x6a, 0x7f, 0x15, + 0x15, 0x52, 0x25, 0xb1, 0xca, 0x21, 0x95, 0x11, + 0x53, 0x3e, 0x1f, 0x50, 0x33, 0x12, 0x7a, 0x62, + 0xce, 0xcc, 0x71, 0xc2, 0x5f, 0x34, 0x47, 0xc6, + 0x7c, 0x71, 0xfa, 0xa0, 0x54, 0x00, 0xb2, 0xdf, + 0xc5, 0x54, 0xac, 0x6c, 0x53, 0xef, 0x64, 0x6b, + 0x08, 0x82, 0xd8, 0x16, 0x1e, 0xca, 0x40, 0xf3, + 0x1f, 0xdf, 0x56, 0x63, 0x10, 0xbc, 0xd7, 0xa0, + 0xeb, 0xee, 0xd1, 0x95, 0xe5, 0xef, 0xf1, 0x6a, + 0x83, 0x2d, 0x5a, 0x59, 0x06, 0xef, 0x30, 0x82, + 0x06, 0xeb, 0x30, 0x82, 0x04, 0xd3, 0xa0, 0x03, + 0x02, 0x01, 0x02, 0x02, 0x13, 0x33, 0x00, 0x00, + 0x05, 0x23, 0xbf, 0xe8, 0xa1, 0x1a, 0x2a, 0x68, + 0xbd, 0x09, 0x00, 0x00, 0x00, 0x00, 0x05, 0x23, + 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, + 0x81, 0x8c, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, + 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, + 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, + 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, + 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, + 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, 0x6f, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, 0x36, + 0x30, 0x34, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, + 0x2d, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, + 0x66, 0x74, 0x20, 0x54, 0x50, 0x4d, 0x20, 0x52, + 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x20, 0x32, 0x30, 0x31, 0x34, 0x30, 0x1e, + 0x17, 0x0d, 0x32, 0x31, 0x30, 0x36, 0x30, 0x33, + 0x31, 0x39, 0x34, 0x30, 0x31, 0x36, 0x5a, 0x17, + 0x0d, 0x32, 0x37, 0x30, 0x36, 0x30, 0x33, 0x31, + 0x39, 0x34, 0x30, 0x31, 0x36, 0x5a, 0x30, 0x41, + 0x31, 0x3f, 0x30, 0x3d, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x13, 0x36, 0x45, 0x55, 0x53, 0x2d, 0x53, + 0x54, 0x4d, 0x2d, 0x4b, 0x45, 0x59, 0x49, 0x44, + 0x2d, 0x31, 0x41, 0x44, 0x42, 0x39, 0x39, 0x34, + 0x41, 0x42, 0x35, 0x38, 0x42, 0x45, 0x35, 0x37, + 0x41, 0x30, 0x43, 0x43, 0x39, 0x42, 0x39, 0x30, + 0x30, 0x45, 0x37, 0x38, 0x35, 0x31, 0x45, 0x31, + 0x41, 0x34, 0x33, 0x43, 0x30, 0x38, 0x36, 0x36, + 0x30, 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, + 0x00, 0x30, 0x82, 0x02, 0x0a, 0x02, 0x82, 0x02, + 0x01, 0x00, 0xdb, 0x03, 0x34, 0x82, 0xfa, 0x81, + 0x1c, 0x84, 0x0b, 0xa0, 0x0e, 0x60, 0xd8, 0x9d, + 0x84, 0xf4, 0x81, 0xc4, 0xe9, 0xff, 0xcf, 0xe9, + 0xa3, 0x57, 0x53, 0x60, 0xa8, 0x19, 0xce, 0xbe, + 0xe1, 0x97, 0xee, 0x5d, 0x8c, 0x9f, 0xe4, 0xbd, + 0xef, 0xbd, 0x94, 0x14, 0xe4, 0x74, 0x41, 0x02, + 0xe9, 0x03, 0x19, 0x9f, 0xdd, 0x48, 0x2d, 0xbd, + 0xca, 0x26, 0x47, 0x2c, 0x01, 0x31, 0x5f, 0x34, + 0xef, 0x59, 0x35, 0x48, 0x36, 0x3d, 0x1e, 0xdf, + 0xd8, 0x13, 0xf0, 0xd0, 0x67, 0xc1, 0xb0, 0x47, + 0x67, 0xa2, 0xd6, 0x62, 0xc8, 0xe1, 0x00, 0x36, + 0x8b, 0x45, 0xf6, 0x3b, 0x96, 0x60, 0xa0, 0x45, + 0x26, 0xcb, 0xc7, 0x0b, 0x5b, 0x97, 0xd1, 0xaf, + 0x54, 0x25, 0x7a, 0x67, 0xe4, 0x2a, 0xd8, 0x9d, + 0x53, 0x05, 0xbd, 0x12, 0xac, 0xa2, 0x8e, 0x95, + 0xb4, 0x2a, 0xca, 0x89, 0x93, 0x64, 0x97, 0x25, + 0xdc, 0x1f, 0xa9, 0xe0, 0x55, 0x07, 0x38, 0x1d, + 0xee, 0x02, 0x90, 0x22, 0xf5, 0xad, 0x4e, 0x5c, + 0xf8, 0xc5, 0x1f, 0x9e, 0x84, 0x7e, 0x13, 0x47, + 0x52, 0xa2, 0x36, 0xf9, 0xf6, 0xbf, 0x76, 0x9e, + 0x0f, 0xdd, 0x14, 0x99, 0xb9, 0xd8, 0x5a, 0x42, + 0x3d, 0xd8, 0xbf, 0xdd, 0xb4, 0x9b, 0xbf, 0x6a, + 0x9f, 0x89, 0x13, 0x75, 0xaf, 0x96, 0xd2, 0x72, + 0xdf, 0xb3, 0x80, 0x6f, 0x84, 0x1a, 0x9d, 0x06, + 0x55, 0x09, 0x29, 0xea, 0xa7, 0x05, 0x31, 0xec, + 0x47, 0x3a, 0xcf, 0x3f, 0x9c, 0x2c, 0xbd, 0xd0, + 0x7d, 0xe4, 0x75, 0x5b, 0x33, 0xbe, 0x12, 0x86, + 0x09, 0xcf, 0x66, 0x9a, 0xeb, 0xf8, 0xf8, 0x72, + 0x91, 0x88, 0x4a, 0x5e, 0x89, 0x62, 0x6a, 0x94, + 0xdc, 0x48, 0x37, 0x13, 0xd8, 0x91, 0x02, 0xe3, + 0x42, 0x41, 0x7c, 0x2f, 0xe3, 0xb6, 0x0f, 0xb4, + 0x96, 0x06, 0x80, 0xca, 0x28, 0x01, 0x6f, 0x4b, + 0xcd, 0x28, 0xd4, 0x2c, 0x94, 0x7e, 0x40, 0x7e, + 0xdf, 0x01, 0xe5, 0xf2, 0x33, 0xd4, 0xda, 0xf4, + 0x1a, 0x17, 0xf7, 0x5d, 0xcb, 0x66, 0x2c, 0x2a, + 0xeb, 0xe1, 0xb1, 0x4a, 0xc3, 0x85, 0x63, 0xb2, + 0xac, 0xd0, 0x3f, 0x1a, 0x8d, 0xa5, 0x0c, 0xee, + 0x4f, 0xde, 0x74, 0x9c, 0xe0, 0x5a, 0x10, 0xc7, + 0xb8, 0xe4, 0xec, 0xe7, 0x73, 0xa6, 0x41, 0x42, + 0x37, 0xe1, 0xdf, 0xb9, 0xc7, 0xb5, 0x14, 0xa8, + 0x80, 0x95, 0xa0, 0x12, 0x67, 0x99, 0xf5, 0xba, + 0x25, 0x0a, 0x74, 0x86, 0x71, 0x9c, 0x7f, 0x59, + 0x97, 0xd2, 0x3f, 0x10, 0xfe, 0x6a, 0xb9, 0xe4, + 0x47, 0x36, 0xfb, 0x0f, 0x50, 0xee, 0xfc, 0x87, + 0x99, 0x7e, 0x36, 0x64, 0x1b, 0xc7, 0x13, 0xb3, + 0x33, 0x18, 0x71, 0xa4, 0xc3, 0xb0, 0xfc, 0x45, + 0x37, 0x11, 0x40, 0xb3, 0xde, 0x2c, 0x9f, 0x0a, + 0xcd, 0xaf, 0x5e, 0xfb, 0xd5, 0x9c, 0xea, 0xd7, + 0x24, 0x19, 0x3a, 0x92, 0x80, 0xa5, 0x63, 0xc5, + 0x3e, 0xdd, 0x51, 0xd0, 0x9f, 0xb8, 0x5e, 0xd5, + 0xf1, 0xfe, 0xa5, 0x93, 0xfb, 0x7f, 0xd9, 0xb8, + 0xb7, 0x0e, 0x0d, 0x12, 0x71, 0xf0, 0x52, 0x9d, + 0xe9, 0xd0, 0xd2, 0x8b, 0x38, 0x8b, 0x85, 0x83, + 0x98, 0x24, 0x88, 0xe8, 0x42, 0x30, 0x83, 0x12, + 0xef, 0x09, 0x96, 0x2f, 0x21, 0x81, 0x05, 0x30, + 0x0c, 0xbb, 0xba, 0x21, 0x39, 0x16, 0x12, 0xe8, + 0x4b, 0x7b, 0x7a, 0x66, 0xb8, 0x22, 0x2c, 0x71, + 0xaf, 0x59, 0xa1, 0xfc, 0x61, 0xf1, 0xb4, 0x5e, + 0xfc, 0x43, 0x19, 0x45, 0x6e, 0xa3, 0x45, 0xe4, + 0xcb, 0x66, 0x5f, 0xe0, 0x57, 0xf6, 0x0a, 0x30, + 0xa3, 0xd6, 0x51, 0x24, 0xc9, 0x07, 0x55, 0x82, + 0x4a, 0x66, 0x0e, 0x9d, 0xb2, 0x2f, 0x84, 0x56, + 0x6c, 0x3e, 0x71, 0xef, 0x9b, 0x35, 0x4d, 0x72, + 0xdc, 0x46, 0x2a, 0xe3, 0x7b, 0x13, 0x20, 0xbf, + 0xab, 0x77, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, + 0x82, 0x01, 0x8e, 0x30, 0x82, 0x01, 0x8a, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, + 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x84, 0x30, + 0x1b, 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x14, + 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x82, 0x37, 0x15, 0x24, 0x06, 0x05, 0x67, + 0x81, 0x05, 0x08, 0x03, 0x30, 0x16, 0x06, 0x03, + 0x55, 0x1d, 0x20, 0x04, 0x0f, 0x30, 0x0d, 0x30, + 0x0b, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, + 0x82, 0x37, 0x15, 0x1f, 0x30, 0x12, 0x06, 0x03, + 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, + 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, + 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, + 0x16, 0x04, 0x14, 0x45, 0x1a, 0xec, 0xfc, 0x91, + 0x70, 0xf8, 0x83, 0x8b, 0x9c, 0x47, 0x2f, 0x0b, + 0x9f, 0x07, 0xf3, 0x2f, 0x7c, 0xa2, 0x8a, 0x30, + 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, + 0x30, 0x16, 0x80, 0x14, 0x7a, 0x8c, 0x0a, 0xce, + 0x2f, 0x48, 0x62, 0x17, 0xe2, 0x94, 0xd1, 0xae, + 0x55, 0xc1, 0x52, 0xec, 0x71, 0x74, 0xa4, 0x56, + 0x30, 0x70, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, + 0x69, 0x30, 0x67, 0x30, 0x65, 0xa0, 0x63, 0xa0, + 0x61, 0x86, 0x5f, 0x68, 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x6f, + 0x70, 0x73, 0x2f, 0x63, 0x72, 0x6c, 0x2f, 0x4d, + 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, + 0x25, 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32, + 0x30, 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30, + 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25, + 0x32, 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63, + 0x72, 0x6c, 0x30, 0x7d, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x71, + 0x30, 0x6f, 0x30, 0x6d, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x61, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, + 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, + 0x73, 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x70, 0x6b, 0x69, 0x6f, 0x70, 0x73, 0x2f, + 0x63, 0x65, 0x72, 0x74, 0x73, 0x2f, 0x4d, 0x69, + 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x25, + 0x32, 0x30, 0x54, 0x50, 0x4d, 0x25, 0x32, 0x30, + 0x52, 0x6f, 0x6f, 0x74, 0x25, 0x32, 0x30, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x25, 0x32, + 0x30, 0x32, 0x30, 0x31, 0x34, 0x2e, 0x63, 0x72, + 0x74, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, + 0x03, 0x82, 0x02, 0x01, 0x00, 0x48, 0x24, 0x32, + 0xe8, 0xd6, 0x38, 0xda, 0x65, 0xec, 0x1b, 0x18, + 0x8e, 0x37, 0x07, 0xd5, 0x18, 0x5a, 0xc8, 0xb9, + 0xbb, 0x24, 0x8a, 0x4d, 0xa1, 0x3c, 0x9e, 0x46, + 0x76, 0xcf, 0xa5, 0xdf, 0xd7, 0x61, 0xba, 0x05, + 0x89, 0x3c, 0x13, 0xc2, 0x1f, 0x71, 0xe3, 0xec, + 0x5d, 0x54, 0x9e, 0xd9, 0x01, 0x5a, 0x10, 0x3b, + 0x17, 0x75, 0xde, 0xa1, 0x45, 0xbf, 0x1d, 0x1b, + 0x41, 0x21, 0x42, 0x68, 0x22, 0x6b, 0xbb, 0xcb, + 0x11, 0x04, 0xd2, 0xae, 0x86, 0xcf, 0x73, 0x5a, + 0xf2, 0x80, 0x18, 0x00, 0xf0, 0xd6, 0x6c, 0x5a, + 0x1e, 0xb3, 0x4d, 0x30, 0x02, 0x4a, 0x6a, 0x03, + 0x36, 0x42, 0xde, 0xb2, 0x52, 0x55, 0xff, 0x71, + 0xeb, 0x7b, 0x8b, 0x55, 0x6c, 0xdf, 0x05, 0x35, + 0x47, 0x70, 0x53, 0xfb, 0x6c, 0xba, 0x06, 0xb2, + 0x61, 0x86, 0xdc, 0x2a, 0x64, 0x81, 0x24, 0x79, + 0x46, 0x73, 0x04, 0x55, 0x59, 0xed, 0xd6, 0x06, + 0x61, 0x15, 0xf9, 0x8d, 0x78, 0x39, 0x7b, 0x84, + 0x7a, 0x40, 0x45, 0x13, 0x1a, 0x91, 0x71, 0x8f, + 0xd1, 0x4f, 0x78, 0x10, 0x68, 0x9b, 0x15, 0x79, + 0x3f, 0x79, 0x2d, 0x9b, 0xc7, 0x5d, 0xa3, 0xcf, + 0xa9, 0x14, 0xb0, 0xc4, 0xdb, 0xa9, 0x45, 0x6a, + 0x6e, 0x60, 0x45, 0x0b, 0x14, 0x25, 0xc7, 0x74, + 0xd0, 0x36, 0xaf, 0xc5, 0xbd, 0x4f, 0x7b, 0xc0, + 0x04, 0x43, 0x85, 0xbb, 0x06, 0x36, 0x77, 0x26, + 0x02, 0x23, 0x0b, 0xf8, 0x57, 0x8f, 0x1f, 0x27, + 0x30, 0x95, 0xff, 0x83, 0x23, 0x2b, 0x49, 0x33, + 0x43, 0x62, 0x87, 0x5d, 0x27, 0x12, 0x1a, 0x68, + 0x7b, 0xba, 0x2d, 0xf6, 0xed, 0x2c, 0x26, 0xb5, + 0xbb, 0xe2, 0x6f, 0xc2, 0x61, 0x17, 0xfc, 0x72, + 0x14, 0x57, 0x2c, 0x2c, 0x5a, 0x92, 0x13, 0x41, + 0xc4, 0x7e, 0xb5, 0x64, 0x5b, 0x86, 0x57, 0x13, + 0x14, 0xff, 0xf5, 0x04, 0xb9, 0x3d, 0x2d, 0xc3, + 0xe9, 0x75, 0x1f, 0x68, 0x0b, 0xb5, 0x76, 0xe1, + 0x7d, 0xe3, 0xb0, 0x14, 0xa8, 0x45, 0x05, 0x98, + 0x81, 0x32, 0xc1, 0xf5, 0x49, 0x4d, 0x58, 0xa4, + 0xee, 0xd8, 0x84, 0xba, 0x65, 0x07, 0x8d, 0xf7, + 0x9a, 0xff, 0x7d, 0xa5, 0xbc, 0x9a, 0xed, 0x4a, + 0x5d, 0xa4, 0x97, 0x4b, 0x4d, 0x31, 0x90, 0xb5, + 0x7d, 0x28, 0x77, 0x25, 0x88, 0x1c, 0xbf, 0x78, + 0x22, 0xb2, 0xb5, 0x5c, 0x9a, 0xc9, 0x63, 0x17, + 0x96, 0xe9, 0xc2, 0x52, 0x30, 0xb8, 0x9b, 0x37, + 0x69, 0x1a, 0x6a, 0x66, 0x76, 0x18, 0xac, 0xc0, + 0x48, 0xee, 0x46, 0x5b, 0xbe, 0x6a, 0xd5, 0x72, + 0x07, 0xdc, 0x7d, 0x05, 0xbe, 0x76, 0x7d, 0xa5, + 0x5e, 0x53, 0xb5, 0x47, 0x80, 0x58, 0xf0, 0xaf, + 0x6f, 0x4e, 0xc0, 0xf1, 0x1e, 0x37, 0x64, 0x15, + 0x42, 0x96, 0x18, 0x3a, 0x89, 0xc8, 0x14, 0x48, + 0x89, 0x5c, 0x12, 0x88, 0x98, 0x0b, 0x7b, 0x4e, + 0xce, 0x1c, 0xda, 0xd5, 0xa4, 0xd3, 0x32, 0x32, + 0x74, 0x5b, 0xcc, 0xfd, 0x2b, 0x02, 0xfb, 0xae, + 0xd0, 0x5a, 0x4c, 0xc9, 0xc1, 0x35, 0x19, 0x90, + 0x5f, 0xca, 0x14, 0xeb, 0x4c, 0x17, 0xd7, 0xe3, + 0xe2, 0x5d, 0xb4, 0x49, 0xaa, 0xf0, 0x50, 0x87, + 0xc3, 0x20, 0x00, 0xda, 0xe9, 0x04, 0x80, 0x64, + 0xac, 0x9f, 0xcd, 0x26, 0x41, 0x48, 0xe8, 0x4c, + 0x46, 0xcc, 0x5b, 0xd7, 0xca, 0x4c, 0x1b, 0x43, + 0x43, 0x1e, 0xbd, 0x94, 0xe7, 0xa7, 0xa6, 0x86, + 0xe5, 0xd1, 0x78, 0x29, 0xa2, 0x40, 0xc5, 0xc5, + 0x47, 0xb6, 0x6d, 0x53, 0xde, 0xac, 0x97, 0x74, + 0x24, 0x57, 0xcc, 0x05, 0x93, 0xfd, 0x52, 0x35, + 0x29, 0xd5, 0xe0, 0xfa, 0x23, 0x0d, 0xd7, 0xaa, + 0x8b, 0x07, 0x4b, 0xf6, 0x64, 0xc7, 0xad, 0x3c, + 0xa1, 0xb5, 0xc5, 0x70, 0xaf, 0x46, 0xfe, 0x9a, + 0x82, 0x4d, 0x75, 0xb8, 0x6d, 0x67, 0x70, 0x75, + 0x62, 0x41, 0x72, 0x65, 0x61, 0x58, 0x76, 0x00, + 0x23, 0x00, 0x0b, 0x00, 0x04, 0x00, 0x72, 0x00, + 0x20, 0x9d, 0xff, 0xcb, 0xf3, 0x6c, 0x38, 0x3a, + 0xe6, 0x99, 0xfb, 0x98, 0x68, 0xdc, 0x6d, 0xcb, + 0x89, 0xd7, 0x15, 0x38, 0x84, 0xbe, 0x28, 0x03, + 0x92, 0x2c, 0x12, 0x41, 0x58, 0xbf, 0xad, 0x22, + 0xae, 0x00, 0x10, 0x00, 0x10, 0x00, 0x03, 0x00, + 0x10, 0x00, 0x20, 0xfb, 0xd6, 0xba, 0x74, 0xe6, + 0x6e, 0x5c, 0x87, 0xef, 0x89, 0xa2, 0xe8, 0x3d, + 0x0b, 0xe9, 0x69, 0x2c, 0x07, 0x07, 0x7a, 0x8a, + 0x1e, 0xce, 0x12, 0xea, 0x3b, 0xb3, 0xf1, 0xf3, + 0xd9, 0xc3, 0xe6, 0x00, 0x20, 0x3c, 0x68, 0x51, + 0x94, 0x54, 0x8d, 0xeb, 0x9f, 0xb2, 0x2c, 0x66, + 0x75, 0xb6, 0xb7, 0x55, 0x22, 0x0d, 0x87, 0x59, + 0xc4, 0x39, 0x91, 0x62, 0x17, 0xc2, 0xc3, 0x53, + 0xa5, 0x26, 0x97, 0x4f, 0x2d, 0x68, 0x63, 0x65, + 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x58, 0xa1, + 0xff, 0x54, 0x43, 0x47, 0x80, 0x17, 0x00, 0x22, + 0x00, 0x0b, 0x73, 0xbe, 0xb7, 0x40, 0x82, 0xc0, + 0x49, 0x9a, 0xf7, 0xf2, 0xd0, 0x79, 0x6c, 0x88, + 0xf3, 0x56, 0x7b, 0x7a, 0x7d, 0xcd, 0x70, 0xd1, + 0xbc, 0x41, 0x88, 0x48, 0x51, 0x03, 0xf3, 0x58, + 0x3e, 0xb8, 0x00, 0x14, 0x9f, 0x57, 0x39, 0x67, + 0xa8, 0x7b, 0xd8, 0xf6, 0x9e, 0x75, 0xc9, 0x85, + 0xab, 0xe3, 0x55, 0xc7, 0x9c, 0xf6, 0xd8, 0x4f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x1c, 0x12, + 0xfd, 0xc6, 0x05, 0xc6, 0x2b, 0xf5, 0xe9, 0x88, + 0x01, 0x1f, 0x70, 0x8d, 0x98, 0x2a, 0x04, 0x21, + 0x30, 0x00, 0x22, 0x00, 0x0b, 0xf4, 0xfd, 0x9a, + 0x33, 0x55, 0x21, 0x08, 0x27, 0x48, 0x55, 0x01, + 0x56, 0xf9, 0x0b, 0x4e, 0x47, 0x55, 0x08, 0x2e, + 0x3c, 0x91, 0x3d, 0x6e, 0x53, 0xcf, 0x08, 0xe9, + 0x0a, 0x4b, 0xc9, 0x7e, 0x99, 0x00, 0x22, 0x00, + 0x0b, 0x51, 0xd3, 0x38, 0xfe, 0xaa, 0xda, 0xc6, + 0x68, 0x84, 0x39, 0xe7, 0xb1, 0x03, 0x22, 0x5e, + 0xc4, 0xd3, 0xf1, 0x0c, 0xec, 0x35, 0x5d, 0x50, + 0xa3, 0x9d, 0xab, 0xa1, 0x7b, 0x61, 0x51, 0x8f, + 0x4e +}; + +/* + * Security Key By Yubico + * 5.1.X + * f8a011f3-8c0a-4d15-8006-17111f9edc7d + */ +const unsigned char aaguid[16] = { + 0xf8, 0xa0, 0x11, 0xf3, 0x8c, 0x0a, 0x4d, 0x15, + 0x80, 0x06, 0x17, 0x11, 0x1f, 0x9e, 0xdc, 0x7d, +}; + +/* + * Windows Hello by Microsoft + */ +const unsigned char aaguid_tpm[16] = { + 0x08, 0x98, 0x70, 0x58, 0xca, 0xdc, 0x4b, 0x81, + 0xb6, 0xe1, 0x30, 0xde, 0x50, 0xdc, 0xbe, 0x96, +}; + +const char rp_id[] = "localhost"; +const char rp_name[] = "sweet home localhost"; + +static void * +dummy_open(const char *path) +{ + (void)path; + + return (&fake_dev_handle); +} + +static void +dummy_close(void *handle) +{ + assert(handle == &fake_dev_handle); +} + +static int +dummy_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + (void)handle; + (void)buf; + (void)len; + (void)ms; + + abort(); + /* NOTREACHED */ +} + +static int +dummy_write(void *handle, const unsigned char *buf, size_t len) +{ + (void)handle; + (void)buf; + (void)len; + + abort(); + /* NOTREACHED */ +} + +static fido_cred_t * +alloc_cred(void) +{ + fido_cred_t *c; + + c = fido_cred_new(); + assert(c != NULL); + + return (c); +} + +static void +free_cred(fido_cred_t *c) +{ + fido_cred_free(&c); + assert(c == NULL); +} + +static fido_dev_t * +alloc_dev(void) +{ + fido_dev_t *d; + + d = fido_dev_new(); + assert(d != NULL); + + return (d); +} + +static void +free_dev(fido_dev_t *d) +{ + fido_dev_free(&d); + assert(d == NULL); +} + +static void +empty_cred(void) +{ + fido_cred_t *c; + fido_dev_t *d; + fido_dev_io_t io_f; + + c = alloc_cred(); + assert(fido_cred_authdata_len(c) == 0); + assert(fido_cred_authdata_ptr(c) == NULL); + assert(fido_cred_authdata_raw_len(c) == 0); + assert(fido_cred_authdata_raw_ptr(c) == NULL); + assert(fido_cred_clientdata_hash_len(c) == 0); + assert(fido_cred_clientdata_hash_ptr(c) == NULL); + assert(fido_cred_flags(c) == 0); + assert(fido_cred_fmt(c) == NULL); + assert(fido_cred_id_len(c) == 0); + assert(fido_cred_id_ptr(c) == NULL); + assert(fido_cred_prot(c) == 0); + assert(fido_cred_pubkey_len(c) == 0); + assert(fido_cred_pubkey_ptr(c) == NULL); + assert(fido_cred_rp_id(c) == NULL); + assert(fido_cred_rp_name(c) == NULL); + assert(fido_cred_sig_len(c) == 0); + assert(fido_cred_sig_ptr(c) == NULL); + assert(fido_cred_x5c_len(c) == 0); + assert(fido_cred_x5c_ptr(c) == NULL); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + + memset(&io_f, 0, sizeof(io_f)); + + io_f.open = dummy_open; + io_f.close = dummy_close; + io_f.read = dummy_read; + io_f.write = dummy_write; + + d = alloc_dev(); + + fido_dev_force_u2f(d); + assert(fido_dev_set_io_functions(d, &io_f) == FIDO_OK); + assert(fido_dev_make_cred(d, c, NULL) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_make_cred(d, c, "") == FIDO_ERR_UNSUPPORTED_OPTION); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + + fido_dev_force_fido2(d); + assert(fido_dev_set_io_functions(d, &io_f) == FIDO_OK); + assert(fido_dev_make_cred(d, c, NULL) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_make_cred(d, c, "") == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + + free_cred(c); + free_dev(d); +} + +static void +valid_cred(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_OK); + assert(fido_cred_prot(c) == 0); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +no_cdh(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +no_rp_id(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +no_rp_name(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, NULL) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_OK); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +no_authdata(void) +{ + fido_cred_t *c; + unsigned char *unset; + + unset = calloc(1, sizeof(aaguid)); + assert(unset != NULL); + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_pubkey_len(c) == 0); + assert(fido_cred_pubkey_ptr(c) == NULL); + assert(fido_cred_id_len(c) == 0); + assert(fido_cred_id_ptr(c) == NULL); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), unset, sizeof(aaguid)) == 0); + free_cred(c); + free(unset); +} + +static void +no_x509(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +no_sig(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +no_fmt(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +wrong_options(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_TRUE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_PARAM); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +junk_cdh(void) +{ + fido_cred_t *c; + unsigned char *junk; + + junk = malloc(sizeof(cdh)); + assert(junk != NULL); + memcpy(junk, cdh, sizeof(cdh)); + junk[0] = (unsigned char)~junk[0]; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, junk, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_SIG); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); + free(junk); +} + +static void +junk_fmt(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "junk") == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + free_cred(c); +} + +static void +junk_rp_id(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, "potato", rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_PARAM); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +junk_rp_name(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, "potato") == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_OK); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +junk_authdata(void) +{ + fido_cred_t *c; + unsigned char *junk; + unsigned char *unset; + + junk = malloc(sizeof(authdata)); + assert(junk != NULL); + memcpy(junk, authdata, sizeof(authdata)); + junk[0] = (unsigned char)~junk[0]; + + unset = calloc(1, sizeof(aaguid)); + assert(unset != NULL); + + c = alloc_cred(); + assert(fido_cred_set_authdata(c, junk, + sizeof(authdata)) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_authdata_len(c) == 0); + assert(fido_cred_authdata_ptr(c) == NULL); + assert(fido_cred_authdata_raw_len(c) == 0); + assert(fido_cred_authdata_raw_ptr(c) == NULL); + assert(fido_cred_flags(c) == 0); + assert(fido_cred_fmt(c) == NULL); + assert(fido_cred_id_len(c) == 0); + assert(fido_cred_id_ptr(c) == NULL); + assert(fido_cred_pubkey_len(c) == 0); + assert(fido_cred_pubkey_ptr(c) == NULL); + assert(fido_cred_rp_id(c) == NULL); + assert(fido_cred_rp_name(c) == NULL); + assert(fido_cred_sig_len(c) == 0); + assert(fido_cred_sig_ptr(c) == NULL); + assert(fido_cred_x5c_len(c) == 0); + assert(fido_cred_x5c_ptr(c) == NULL); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), unset, sizeof(aaguid)) == 0); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + free_cred(c); + free(junk); + free(unset); +} + +static void +junk_sig(void) +{ + fido_cred_t *c; + unsigned char *junk; + + junk = malloc(sizeof(sig)); + assert(junk != NULL); + memcpy(junk, sig, sizeof(sig)); + junk[0] = (unsigned char)~junk[0]; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, junk, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_SIG); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); + free(junk); +} + +static void +junk_x509(void) +{ + fido_cred_t *c; + unsigned char *junk; + + junk = malloc(sizeof(x509)); + assert(junk != NULL); + memcpy(junk, x509, sizeof(x509)); + junk[0] = (unsigned char)~junk[0]; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, junk, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_SIG); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); + free(junk); +} + +/* github issue #6 */ +static void +invalid_type(void) +{ + fido_cred_t *c; + unsigned char *unset; + + unset = calloc(1, sizeof(aaguid)); + assert(unset != NULL); + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_RS256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_pubkey_len(c) == 0); + assert(fido_cred_pubkey_ptr(c) == NULL); + assert(fido_cred_id_len(c) == 0); + assert(fido_cred_id_ptr(c) == NULL); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), unset, sizeof(aaguid)) == 0); + free_cred(c); + free(unset); +} + +/* cbor_serialize_alloc misuse */ +static void +bad_cbor_serialize(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_authdata_len(c) == sizeof(authdata)); + free_cred(c); +} + +static void +duplicate_keys(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata_dupkeys, + sizeof(authdata_dupkeys)) == FIDO_ERR_INVALID_ARGUMENT); + free_cred(c); +} + +static void +unsorted_keys(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata_unsorted_keys, + sizeof(authdata_unsorted_keys)) == FIDO_ERR_INVALID_ARGUMENT); + free_cred(c); +} + +static void +wrong_credprot(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_x509(c, x509, sizeof(x509)) == FIDO_OK); + assert(fido_cred_set_sig(c, sig, sizeof(sig)) == FIDO_OK); + assert(fido_cred_set_fmt(c, "packed") == FIDO_OK); + assert(fido_cred_set_prot(c, FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_PARAM); + free_cred(c); +} + +static void +raw_authdata(void) +{ + fido_cred_t *c; + cbor_item_t *item; + struct cbor_load_result cbor_result; + const unsigned char *ptr; + unsigned char *cbor; + size_t len; + size_t cbor_len; + size_t alloclen; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert((ptr = fido_cred_authdata_ptr(c)) != NULL); + assert((len = fido_cred_authdata_len(c)) != 0); + assert((item = cbor_load(ptr, len, &cbor_result)) != NULL); + assert(cbor_result.read == len); + assert(cbor_isa_bytestring(item)); + assert((ptr = fido_cred_authdata_raw_ptr(c)) != NULL); + assert((len = fido_cred_authdata_raw_len(c)) != 0); + assert(cbor_bytestring_length(item) == len); + assert(memcmp(ptr, cbor_bytestring_handle(item), len) == 0); + assert((len = fido_cred_authdata_len(c)) != 0); + assert((cbor_len = cbor_serialize_alloc(item, &cbor, &alloclen)) == len); + assert((ptr = cbor_bytestring_handle(item)) != NULL); + assert((len = cbor_bytestring_length(item)) != 0); + assert(fido_cred_set_authdata_raw(c, ptr, len) == FIDO_OK); + assert((ptr = fido_cred_authdata_ptr(c)) != NULL); + assert((len = fido_cred_authdata_len(c)) != 0); + assert(len == cbor_len); + assert(memcmp(cbor, ptr, len) == 0); + assert(cbor_len == sizeof(authdata)); + assert(memcmp(cbor, authdata, cbor_len) == 0); + cbor_decref(&item); + free(cbor); + free_cred(c); +} + +static void +fmt_none(void) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata_hash(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata, sizeof(authdata)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_fmt(c, "none") == FIDO_OK); + assert(fido_cred_verify(c) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_cred_prot(c) == 0); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey, sizeof(pubkey)) == 0); + assert(fido_cred_id_len(c) == sizeof(id)); + assert(memcmp(fido_cred_id_ptr(c), id, sizeof(id)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid, sizeof(aaguid)) == 0); + free_cred(c); +} + +static void +valid_tpm_rs256_cred(bool xfail) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_RS256) == FIDO_OK); + assert(fido_cred_set_clientdata(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata_tpm_rs256, sizeof(authdata_tpm_rs256)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_TRUE) == FIDO_OK); + assert(fido_cred_set_fmt(c, "tpm") == FIDO_OK); + assert(fido_cred_set_attstmt(c, attstmt_tpm_rs256, sizeof(attstmt_tpm_rs256)) == FIDO_OK); + // XXX: RHEL9 has deprecated SHA-1 for signing. + assert(fido_cred_verify(c) == (xfail ? FIDO_ERR_INVALID_SIG : FIDO_OK)); + assert(fido_cred_prot(c) == 0); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey_tpm_rs256)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey_tpm_rs256, sizeof(pubkey_tpm_rs256)) == 0); + assert(fido_cred_id_len(c) == sizeof(id_tpm_rs256)); + assert(memcmp(fido_cred_id_ptr(c), id_tpm_rs256, sizeof(id_tpm_rs256)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid_tpm)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid_tpm, sizeof(aaguid_tpm)) == 0); + free_cred(c); +} + +static void +valid_tpm_es256_cred(bool xfail) +{ + fido_cred_t *c; + + c = alloc_cred(); + assert(fido_cred_set_type(c, COSE_ES256) == FIDO_OK); + assert(fido_cred_set_clientdata(c, cdh, sizeof(cdh)) == FIDO_OK); + assert(fido_cred_set_rp(c, rp_id, rp_name) == FIDO_OK); + assert(fido_cred_set_authdata(c, authdata_tpm_es256, sizeof(authdata_tpm_es256)) == FIDO_OK); + assert(fido_cred_set_rk(c, FIDO_OPT_FALSE) == FIDO_OK); + assert(fido_cred_set_uv(c, FIDO_OPT_TRUE) == FIDO_OK); + assert(fido_cred_set_fmt(c, "tpm") == FIDO_OK); + assert(fido_cred_set_attstmt(c, attstmt_tpm_es256, sizeof(attstmt_tpm_es256)) == FIDO_OK); + // XXX: RHEL9 has deprecated SHA-1 for signing. + assert(fido_cred_verify(c) == (xfail ? FIDO_ERR_INVALID_SIG : FIDO_OK)); + assert(fido_cred_prot(c) == 0); + assert(fido_cred_pubkey_len(c) == sizeof(pubkey_tpm_es256)); + assert(memcmp(fido_cred_pubkey_ptr(c), pubkey_tpm_es256, sizeof(pubkey_tpm_es256)) == 0); + assert(fido_cred_id_len(c) == sizeof(id_tpm_es256)); + assert(memcmp(fido_cred_id_ptr(c), id_tpm_es256, sizeof(id_tpm_es256)) == 0); + assert(fido_cred_aaguid_len(c) == sizeof(aaguid_tpm)); + assert(memcmp(fido_cred_aaguid_ptr(c), aaguid_tpm, sizeof(aaguid_tpm)) == 0); + free_cred(c); +} + +int +main(void) +{ + bool xfail = getenv("FIDO_REGRESS_RS1_XFAIL") != NULL; + + fido_init(0); + + empty_cred(); + valid_cred(); + no_cdh(); + no_rp_id(); + no_rp_name(); + no_authdata(); + no_x509(); + no_sig(); + no_fmt(); + junk_cdh(); + junk_fmt(); + junk_rp_id(); + junk_rp_name(); + junk_authdata(); + junk_x509(); + junk_sig(); + wrong_options(); + invalid_type(); + bad_cbor_serialize(); + duplicate_keys(); + unsorted_keys(); + wrong_credprot(); + raw_authdata(); + fmt_none(); + valid_tpm_rs256_cred(xfail); + valid_tpm_es256_cred(xfail); + + exit(0); +} diff --git a/regress/dev.c b/regress/dev.c new file mode 100644 index 0000000..0ba552b --- /dev/null +++ b/regress/dev.c @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> +#include <time.h> + +#define _FIDO_INTERNAL + +#include <fido.h> + +#include "../fuzz/wiredata_fido2.h" + +#define REPORT_LEN (64 + 1) + +static uint8_t ctap_nonce[8]; +static uint8_t *wiredata_ptr; +static size_t wiredata_len; +static int fake_dev_handle; +static int initialised; +static long interval_ms; + +#if defined(_MSC_VER) +static int +nanosleep(const struct timespec *rqtp, struct timespec *rmtp) +{ + if (rmtp != NULL) { + errno = EINVAL; + return (-1); + } + + Sleep((DWORD)(rqtp->tv_sec * 1000) + (DWORD)(rqtp->tv_nsec / 1000000)); + + return (0); +} +#endif + +static void * +dummy_open(const char *path) +{ + (void)path; + + return (&fake_dev_handle); +} + +static void +dummy_close(void *handle) +{ + assert(handle == &fake_dev_handle); +} + +static int +dummy_read(void *handle, unsigned char *ptr, size_t len, int ms) +{ + struct timespec tv; + size_t n; + long d; + + assert(handle == &fake_dev_handle); + assert(ptr != NULL); + assert(len == REPORT_LEN - 1); + + if (wiredata_ptr == NULL) + return (-1); + + if (!initialised) { + assert(wiredata_len >= REPORT_LEN - 1); + memcpy(&wiredata_ptr[7], &ctap_nonce, sizeof(ctap_nonce)); + initialised = 1; + } + + if (ms >= 0 && ms < interval_ms) + d = ms; + else + d = interval_ms; + + if (d) { + tv.tv_sec = d / 1000; + tv.tv_nsec = (d % 1000) * 1000000; + if (nanosleep(&tv, NULL) == -1) + err(1, "nanosleep"); + } + + if (d != interval_ms) + return (-1); /* timeout */ + + if (wiredata_len < len) + n = wiredata_len; + else + n = len; + + memcpy(ptr, wiredata_ptr, n); + wiredata_ptr += n; + wiredata_len -= n; + + return ((int)n); +} + +static int +dummy_write(void *handle, const unsigned char *ptr, size_t len) +{ + struct timespec tv; + + assert(handle == &fake_dev_handle); + assert(ptr != NULL); + assert(len == REPORT_LEN); + + if (!initialised) + memcpy(&ctap_nonce, &ptr[8], sizeof(ctap_nonce)); + + if (interval_ms) { + tv.tv_sec = interval_ms / 1000; + tv.tv_nsec = (interval_ms % 1000) * 1000000; + if (nanosleep(&tv, NULL) == -1) + err(1, "nanosleep"); + } + + return ((int)len); +} + +static uint8_t * +wiredata_setup(const uint8_t *data, size_t len) +{ + const uint8_t ctap_init_data[] = { WIREDATA_CTAP_INIT }; + + assert(wiredata_ptr == NULL); + assert(SIZE_MAX - len > sizeof(ctap_init_data)); + assert((wiredata_ptr = malloc(sizeof(ctap_init_data) + len)) != NULL); + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:6386) +#endif + memcpy(wiredata_ptr, ctap_init_data, sizeof(ctap_init_data)); +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + if (len) + memcpy(wiredata_ptr + sizeof(ctap_init_data), data, len); + + wiredata_len = sizeof(ctap_init_data) + len; + + return (wiredata_ptr); +} + +static void +wiredata_clear(uint8_t **wiredata) +{ + free(*wiredata); + *wiredata = NULL; + wiredata_ptr = NULL; + wiredata_len = 0; + initialised = 0; +} + +/* gh#56 */ +static void +open_iff_ok(void) +{ + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_ERR_RX); + assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT); + + fido_dev_free(&dev); +} + +static void +reopen(void) +{ + const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO }; + uint8_t *wiredata; + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data)); + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_close(dev) == FIDO_OK); + wiredata_clear(&wiredata); + + wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data)); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_close(dev) == FIDO_OK); + fido_dev_free(&dev); + wiredata_clear(&wiredata); +} + +static void +double_open(void) +{ + const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO }; + uint8_t *wiredata; + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data)); + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_close(dev) == FIDO_OK); + fido_dev_free(&dev); + wiredata_clear(&wiredata); +} + +static void +double_close(void) +{ + const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO }; + uint8_t *wiredata; + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data)); + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_close(dev) == FIDO_OK); + assert(fido_dev_close(dev) == FIDO_ERR_INVALID_ARGUMENT); + fido_dev_free(&dev); + wiredata_clear(&wiredata); +} + +static void +is_fido2(void) +{ + const uint8_t cbor_info_data[] = { WIREDATA_CTAP_CBOR_INFO }; + uint8_t *wiredata; + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + wiredata = wiredata_setup(cbor_info_data, sizeof(cbor_info_data)); + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_is_fido2(dev) == true); + assert(fido_dev_supports_pin(dev) == true); + fido_dev_force_u2f(dev); + assert(fido_dev_is_fido2(dev) == false); + assert(fido_dev_supports_pin(dev) == false); + assert(fido_dev_close(dev) == FIDO_OK); + wiredata_clear(&wiredata); + + wiredata = wiredata_setup(NULL, 0); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_is_fido2(dev) == false); + assert(fido_dev_supports_pin(dev) == false); + fido_dev_force_fido2(dev); + assert(fido_dev_is_fido2(dev) == true); + assert(fido_dev_supports_pin(dev) == false); + assert(fido_dev_close(dev) == FIDO_OK); + fido_dev_free(&dev); + wiredata_clear(&wiredata); +} + +static void +has_pin(void) +{ + const uint8_t set_pin_data[] = { + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_CBOR_AUTHKEY, + WIREDATA_CTAP_CBOR_STATUS, + WIREDATA_CTAP_CBOR_STATUS + }; + uint8_t *wiredata; + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + wiredata = wiredata_setup(set_pin_data, sizeof(set_pin_data)); + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_has_pin(dev) == false); + assert(fido_dev_set_pin(dev, "top secret", NULL) == FIDO_OK); + assert(fido_dev_has_pin(dev) == true); + assert(fido_dev_reset(dev) == FIDO_OK); + assert(fido_dev_has_pin(dev) == false); + assert(fido_dev_close(dev) == FIDO_OK); + fido_dev_free(&dev); + wiredata_clear(&wiredata); +} + +static void +timeout_rx(void) +{ + const uint8_t timeout_rx_data[] = { + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_CBOR_STATUS + }; + uint8_t *wiredata; + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + wiredata = wiredata_setup(timeout_rx_data, sizeof(timeout_rx_data)); + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_set_timeout(dev, 3 * 1000) == FIDO_OK); + interval_ms = 1000; + assert(fido_dev_reset(dev) == FIDO_ERR_RX); + assert(fido_dev_close(dev) == FIDO_OK); + fido_dev_free(&dev); + wiredata_clear(&wiredata); + interval_ms = 0; +} + +static void +timeout_ok(void) +{ + const uint8_t timeout_ok_data[] = { + WIREDATA_CTAP_CBOR_INFO, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_KEEPALIVE, + WIREDATA_CTAP_CBOR_STATUS + }; + uint8_t *wiredata; + fido_dev_t *dev = NULL; + fido_dev_io_t io; + + memset(&io, 0, sizeof(io)); + + io.open = dummy_open; + io.close = dummy_close; + io.read = dummy_read; + io.write = dummy_write; + + wiredata = wiredata_setup(timeout_ok_data, sizeof(timeout_ok_data)); + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_io_functions(dev, &io) == FIDO_OK); + assert(fido_dev_open(dev, "dummy") == FIDO_OK); + assert(fido_dev_set_timeout(dev, 30 * 1000) == FIDO_OK); + interval_ms = 1000; + assert(fido_dev_reset(dev) == FIDO_OK); + assert(fido_dev_close(dev) == FIDO_OK); + fido_dev_free(&dev); + wiredata_clear(&wiredata); + interval_ms = 0; +} + +static void +timeout_misc(void) +{ + fido_dev_t *dev; + + assert((dev = fido_dev_new()) != NULL); + assert(fido_dev_set_timeout(dev, -2) == FIDO_ERR_INVALID_ARGUMENT); + assert(fido_dev_set_timeout(dev, 3 * 1000) == FIDO_OK); + assert(fido_dev_set_timeout(dev, -1) == FIDO_OK); + fido_dev_free(&dev); +} + +int +main(void) +{ + fido_init(0); + + open_iff_ok(); + reopen(); + double_open(); + double_close(); + is_fido2(); + has_pin(); + timeout_rx(); + timeout_ok(); + timeout_misc(); + + exit(0); +} diff --git a/regress/eddsa.c b/regress/eddsa.c new file mode 100644 index 0000000..f97f97c --- /dev/null +++ b/regress/eddsa.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> + +#define _FIDO_INTERNAL + +#include <fido.h> +#include <fido/eddsa.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> + +#define ASSERT_NOT_NULL(e) assert((e) != NULL) +#define ASSERT_NULL(e) assert((e) == NULL) +#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT) +#define ASSERT_OK(e) assert((e) == FIDO_OK) + +static const char ecdsa[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOwiq14c80b7C1Jzsx5w1zMvk2GgW\n" +"5kfGMOKXjwF/U+51ZfBDKehs3ivdeXAJBkxIh7E3iA32s+HyNqk+ntl9fg==\n" +"-----END PUBLIC KEY-----\n"; + +static const char eddsa[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MCowBQYDK2VwAyEADt/RHErAxAHxH9FUmsjOhQ2ALl6Y8nE0m3zQxkEE2iM=\n" +"-----END PUBLIC KEY-----\n"; + +static const unsigned char eddsa_raw[] = { + 0x0e, 0xdf, 0xd1, 0x1c, 0x4a, 0xc0, 0xc4, 0x01, + 0xf1, 0x1f, 0xd1, 0x54, 0x9a, 0xc8, 0xce, 0x85, + 0x0d, 0x80, 0x2e, 0x5e, 0x98, 0xf2, 0x71, 0x34, + 0x9b, 0x7c, 0xd0, 0xc6, 0x41, 0x04, 0xda, 0x23, +}; + +static EVP_PKEY * +EVP_PKEY_from_PEM(const char *ptr, size_t len) +{ + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) { + warnx("BIO_new"); + goto out; + } + if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) { + warnx("BIO_write"); + goto out; + } + if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL) + warnx("PEM_read_bio_PUBKEY"); +out: + BIO_free(bio); + + return pkey; +} + +static int +eddsa_pk_cmp(const char *ptr, size_t len) +{ + EVP_PKEY *pkA = NULL; + EVP_PKEY *pkB = NULL; + eddsa_pk_t *k = NULL; + int r, ok = -1; + + if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) { + warnx("EVP_PKEY_from_PEM"); + goto out; + } + if ((k = eddsa_pk_new()) == NULL) { + warnx("eddsa_pk_new"); + goto out; + } + if ((r = eddsa_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) { + warnx("eddsa_pk_from_EVP_PKEY: 0x%x", r); + goto out; + } + if ((pkB = eddsa_pk_to_EVP_PKEY(k)) == NULL) { + warnx("eddsa_pk_to_EVP_PKEY"); + goto out; + } + if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) { + warnx("EVP_PKEY_cmp: %d", r); + goto out; + } + + ok = 0; +out: + EVP_PKEY_free(pkA); + EVP_PKEY_free(pkB); + eddsa_pk_free(&k); + + return ok; +} + +static void +invalid_key(void) +{ + EVP_PKEY *pkey; + eddsa_pk_t *pk; + + ASSERT_NOT_NULL((pkey = EVP_PKEY_from_PEM(ecdsa, sizeof(ecdsa)))); + ASSERT_NOT_NULL((pk = eddsa_pk_new())); + ASSERT_INVAL(eddsa_pk_from_EVP_PKEY(pk, pkey)); + + EVP_PKEY_free(pkey); + eddsa_pk_free(&pk); +} + +static void +valid_key(void) +{ + EVP_PKEY *pkeyA = NULL; + EVP_PKEY *pkeyB = NULL; + eddsa_pk_t *pkA = NULL; + eddsa_pk_t *pkB = NULL; + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3070000f + /* incomplete support; test what we can */ + ASSERT_NULL(EVP_PKEY_from_PEM(eddsa, sizeof(eddsa))); + ASSERT_NOT_NULL((pkB = eddsa_pk_new())); + ASSERT_INVAL(eddsa_pk_from_ptr(pkB, eddsa_raw, sizeof(eddsa_raw))); + ASSERT_NULL(eddsa_pk_to_EVP_PKEY((const eddsa_pk_t *)eddsa_raw)); + assert(eddsa_pk_cmp(eddsa, sizeof(eddsa)) < 0); +#else + ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(eddsa, sizeof(eddsa)))); + ASSERT_NOT_NULL((pkA = eddsa_pk_new())); + ASSERT_NOT_NULL((pkB = eddsa_pk_new())); + ASSERT_OK(eddsa_pk_from_EVP_PKEY(pkA, pkeyA)); + ASSERT_OK(eddsa_pk_from_ptr(pkB, eddsa_raw, sizeof(eddsa_raw))); + ASSERT_NOT_NULL((pkeyB = eddsa_pk_to_EVP_PKEY((const eddsa_pk_t *)eddsa_raw))); + assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1); + assert(eddsa_pk_cmp(eddsa, sizeof(eddsa)) == 0); +#endif + + EVP_PKEY_free(pkeyA); + EVP_PKEY_free(pkeyB); + eddsa_pk_free(&pkA); + eddsa_pk_free(&pkB); +} + +int +main(void) +{ + fido_init(0); + + invalid_key(); + valid_key(); + + exit(0); +} diff --git a/regress/es256.c b/regress/es256.c new file mode 100644 index 0000000..3a62a41 --- /dev/null +++ b/regress/es256.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> + +#define _FIDO_INTERNAL + +#include <fido.h> +#include <fido/es256.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> + +#define ASSERT_NOT_NULL(e) assert((e) != NULL) +#define ASSERT_NULL(e) assert((e) == NULL) +#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT) +#define ASSERT_OK(e) assert((e) == FIDO_OK) + +static const char short_x[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAAeeHTZj4LEbt7Czs+u5gEZJfnGE\n" +"6Z+YLe4AYu7SoGY7IH/2jKifsA7w+lkURL4DL63oEjd3f8foH9bX4eaVug==\n" +"-----END PUBLIC KEY-----"; + +static const char short_y[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEL8CWUP1r0tpJ5QmkzLc69O74C/Ti\n" +"83hTiys/JFNVkp0ArW3pKt5jNRrgWSZYE4S/D3AMtpqifFXz/FLCzJqojQ==\n" +"-----END PUBLIC KEY-----\n"; + +static const char p256k1[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEU1y8c0Jg9FGr3vYChpEo9c4dpkijriYM\n" +"QzU/DeskC89hZjLNH1Sj8ra2MsBlVGGJTNPCZSyx8Jo7ERapxdN7UQ==\n" +"-----END PUBLIC KEY-----\n"; + +static const char p256v1[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOwiq14c80b7C1Jzsx5w1zMvk2GgW\n" +"5kfGMOKXjwF/U+51ZfBDKehs3ivdeXAJBkxIh7E3iA32s+HyNqk+ntl9fg==\n" +"-----END PUBLIC KEY-----\n"; + +static const unsigned char p256k1_raw[] = { + 0x04, 0x53, 0x5c, 0xbc, 0x73, 0x42, 0x60, 0xf4, + 0x51, 0xab, 0xde, 0xf6, 0x02, 0x86, 0x91, 0x28, + 0xf5, 0xce, 0x1d, 0xa6, 0x48, 0xa3, 0xae, 0x26, + 0x0c, 0x43, 0x35, 0x3f, 0x0d, 0xeb, 0x24, 0x0b, + 0xcf, 0x61, 0x66, 0x32, 0xcd, 0x1f, 0x54, 0xa3, + 0xf2, 0xb6, 0xb6, 0x32, 0xc0, 0x65, 0x54, 0x61, + 0x89, 0x4c, 0xd3, 0xc2, 0x65, 0x2c, 0xb1, 0xf0, + 0x9a, 0x3b, 0x11, 0x16, 0xa9, 0xc5, 0xd3, 0x7b, + 0x51, +}; + +static const unsigned char p256v1_raw[] = { + 0x04, 0x3b, 0x08, 0xaa, 0xd7, 0x87, 0x3c, 0xd1, + 0xbe, 0xc2, 0xd4, 0x9c, 0xec, 0xc7, 0x9c, 0x35, + 0xcc, 0xcb, 0xe4, 0xd8, 0x68, 0x16, 0xe6, 0x47, + 0xc6, 0x30, 0xe2, 0x97, 0x8f, 0x01, 0x7f, 0x53, + 0xee, 0x75, 0x65, 0xf0, 0x43, 0x29, 0xe8, 0x6c, + 0xde, 0x2b, 0xdd, 0x79, 0x70, 0x09, 0x06, 0x4c, + 0x48, 0x87, 0xb1, 0x37, 0x88, 0x0d, 0xf6, 0xb3, + 0xe1, 0xf2, 0x36, 0xa9, 0x3e, 0x9e, 0xd9, 0x7d, + 0x7e, +}; + +static EVP_PKEY * +EVP_PKEY_from_PEM(const char *ptr, size_t len) +{ + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) { + warnx("BIO_new"); + goto out; + } + if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) { + warnx("BIO_write"); + goto out; + } + if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL) + warnx("PEM_read_bio_PUBKEY"); +out: + BIO_free(bio); + + return pkey; +} + +static int +es256_pk_cmp(const char *ptr, size_t len) +{ + EVP_PKEY *pkA = NULL; + EVP_PKEY *pkB = NULL; + es256_pk_t *k = NULL; + int r, ok = -1; + + if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) { + warnx("EVP_PKEY_from_PEM"); + goto out; + } + if ((k = es256_pk_new()) == NULL) { + warnx("es256_pk_new"); + goto out; + } + if ((r = es256_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) { + warnx("es256_pk_from_EVP_PKEY: 0x%x", r); + goto out; + } + if ((pkB = es256_pk_to_EVP_PKEY(k)) == NULL) { + warnx("es256_pk_to_EVP_PKEY"); + goto out; + } + if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) { + warnx("EVP_PKEY_cmp: %d", r); + goto out; + } + + ok = 0; +out: + EVP_PKEY_free(pkA); + EVP_PKEY_free(pkB); + es256_pk_free(&k); + + return ok; +} + +static void +short_coord(void) +{ + assert(es256_pk_cmp(short_x, sizeof(short_x)) == 0); + assert(es256_pk_cmp(short_y, sizeof(short_y)) == 0); +} + +static void +invalid_curve(const unsigned char *raw, size_t raw_len) +{ + EVP_PKEY *pkey; + es256_pk_t *pk; + + ASSERT_NOT_NULL((pkey = EVP_PKEY_from_PEM(p256k1, sizeof(p256k1)))); + ASSERT_NOT_NULL((pk = es256_pk_new())); + ASSERT_INVAL(es256_pk_from_EVP_PKEY(pk, pkey)); + ASSERT_INVAL(es256_pk_from_ptr(pk, raw, raw_len)); + ASSERT_NULL(es256_pk_to_EVP_PKEY((const es256_pk_t *)raw)); + + EVP_PKEY_free(pkey); + es256_pk_free(&pk); +} + +static void +full_coord(void) +{ + assert(es256_pk_cmp(p256v1, sizeof(p256v1)) == 0); +} + +static void +valid_curve(const unsigned char *raw, size_t raw_len) +{ + EVP_PKEY *pkeyA; + EVP_PKEY *pkeyB; + es256_pk_t *pkA; + es256_pk_t *pkB; + + ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(p256v1, sizeof(p256v1)))); + ASSERT_NOT_NULL((pkA = es256_pk_new())); + ASSERT_NOT_NULL((pkB = es256_pk_new())); + ASSERT_OK(es256_pk_from_EVP_PKEY(pkA, pkeyA)); + ASSERT_OK(es256_pk_from_ptr(pkB, raw, raw_len)); + ASSERT_NOT_NULL((pkeyB = es256_pk_to_EVP_PKEY(pkB))); + assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1); + + EVP_PKEY_free(pkeyA); + EVP_PKEY_free(pkeyB); + es256_pk_free(&pkA); + es256_pk_free(&pkB); +} + +int +main(void) +{ + fido_init(0); + + short_coord(); + full_coord(); + + invalid_curve(p256k1_raw, sizeof(p256k1_raw)); /* uncompressed */ + invalid_curve(p256k1_raw + 1, sizeof(p256k1_raw) - 1); /* libfido2 */ + valid_curve(p256v1_raw, sizeof(p256v1_raw)); /* uncompressed */ + valid_curve(p256v1_raw + 1, sizeof(p256v1_raw) - 1); /* libfido2 */ + + exit(0); +} diff --git a/regress/es384.c b/regress/es384.c new file mode 100644 index 0000000..b55ce01 --- /dev/null +++ b/regress/es384.c @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> + +#define _FIDO_INTERNAL + +#include <fido.h> +#include <fido/es384.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> + +#define ASSERT_NOT_NULL(e) assert((e) != NULL) +#define ASSERT_NULL(e) assert((e) == NULL) +#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT) +#define ASSERT_OK(e) assert((e) == FIDO_OK) + +static const char short_x[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAAZ/VVCUmFU6aH9kJdDnUHCCglkatFTX\n" +"onMwIvNYyS8BW/HOoZiOQLs2Hg+qifwaP1pHKILzCVfFmWuZMhxhtmjNXFuOPDnS\n" +"Wa1PMdkCoWXA2BbXxnqL9v36gIOcFBil\n" +"-----END PUBLIC KEY-----"; + +static const char short_y[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEuDpRBAg87cnWVhxbWnaWlnj100w9pm5k\n" +"6T4eYToISaIhEK70TnGwULHX0+qHCYEGACOM7B/ZJbqjo6I7MIXaKZLemGi+tqvy\n" +"ajBAsTVSyrYBLQjTMMcaFmYmsxvFx7pK\n" +"-----END PUBLIC KEY-----\n"; + +static const char brainpoolP384r1[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MHowFAYHKoZIzj0CAQYJKyQDAwIIAQELA2IABFKswbBzqqyZ4h1zz8rivqHzJxAO\n" +"XC2aLyC9x5gwBM7GVu8k6jkX7VypRpg3yyCneiIQ+vVCNXgbDchJ0cPVuhwm3Zru\n" +"AK49dezUPahWF0YiJRFVeV+KyB/MEaaZvinzqw==\n" +"-----END PUBLIC KEY-----\n"; + +static const char secp384r1[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEdJN9DoqPtTNAOmjnECHBIqnJgyBW0rct\n" +"tbUSqQjb6UG2lldmrQJbgCP/ywuXvkkJl4yfXxOr0UP3rgcnqTVA1/46s2TG+R5u\n" +"NSQbCM1JPQuvTyFlAn5mdR8ZJJ8yPBQm\n" +"-----END PUBLIC KEY-----\n"; + +static const unsigned char brainpoolP384r1_raw[] = { + 0x04, 0x52, 0xac, 0xc1, 0xb0, 0x73, 0xaa, 0xac, + 0x99, 0xe2, 0x1d, 0x73, 0xcf, 0xca, 0xe2, 0xbe, + 0xa1, 0xf3, 0x27, 0x10, 0x0e, 0x5c, 0x2d, 0x9a, + 0x2f, 0x20, 0xbd, 0xc7, 0x98, 0x30, 0x04, 0xce, + 0xc6, 0x56, 0xef, 0x24, 0xea, 0x39, 0x17, 0xed, + 0x5c, 0xa9, 0x46, 0x98, 0x37, 0xcb, 0x20, 0xa7, + 0x7a, 0x22, 0x10, 0xfa, 0xf5, 0x42, 0x35, 0x78, + 0x1b, 0x0d, 0xc8, 0x49, 0xd1, 0xc3, 0xd5, 0xba, + 0x1c, 0x26, 0xdd, 0x9a, 0xee, 0x00, 0xae, 0x3d, + 0x75, 0xec, 0xd4, 0x3d, 0xa8, 0x56, 0x17, 0x46, + 0x22, 0x25, 0x11, 0x55, 0x79, 0x5f, 0x8a, 0xc8, + 0x1f, 0xcc, 0x11, 0xa6, 0x99, 0xbe, 0x29, 0xf3, + 0xab, +}; + +static const unsigned char secp384r1_raw[] = { + 0x04, 0x74, 0x93, 0x7d, 0x0e, 0x8a, 0x8f, 0xb5, + 0x33, 0x40, 0x3a, 0x68, 0xe7, 0x10, 0x21, 0xc1, + 0x22, 0xa9, 0xc9, 0x83, 0x20, 0x56, 0xd2, 0xb7, + 0x2d, 0xb5, 0xb5, 0x12, 0xa9, 0x08, 0xdb, 0xe9, + 0x41, 0xb6, 0x96, 0x57, 0x66, 0xad, 0x02, 0x5b, + 0x80, 0x23, 0xff, 0xcb, 0x0b, 0x97, 0xbe, 0x49, + 0x09, 0x97, 0x8c, 0x9f, 0x5f, 0x13, 0xab, 0xd1, + 0x43, 0xf7, 0xae, 0x07, 0x27, 0xa9, 0x35, 0x40, + 0xd7, 0xfe, 0x3a, 0xb3, 0x64, 0xc6, 0xf9, 0x1e, + 0x6e, 0x35, 0x24, 0x1b, 0x08, 0xcd, 0x49, 0x3d, + 0x0b, 0xaf, 0x4f, 0x21, 0x65, 0x02, 0x7e, 0x66, + 0x75, 0x1f, 0x19, 0x24, 0x9f, 0x32, 0x3c, 0x14, + 0x26, +}; + +static EVP_PKEY * +EVP_PKEY_from_PEM(const char *ptr, size_t len) +{ + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) { + warnx("BIO_new"); + goto out; + } + if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) { + warnx("BIO_write"); + goto out; + } + if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL) + warnx("PEM_read_bio_PUBKEY"); +out: + BIO_free(bio); + + return pkey; +} + +static int +es384_pk_cmp(const char *ptr, size_t len) +{ + EVP_PKEY *pkA = NULL; + EVP_PKEY *pkB = NULL; + es384_pk_t *k = NULL; + int r, ok = -1; + + if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) { + warnx("EVP_PKEY_from_PEM"); + goto out; + } + if ((k = es384_pk_new()) == NULL) { + warnx("es384_pk_new"); + goto out; + } + if ((r = es384_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) { + warnx("es384_pk_from_EVP_PKEY: 0x%x", r); + goto out; + } + if ((pkB = es384_pk_to_EVP_PKEY(k)) == NULL) { + warnx("es384_pk_to_EVP_PKEY"); + goto out; + } + if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) { + warnx("EVP_PKEY_cmp: %d", r); + goto out; + } + + ok = 0; +out: + EVP_PKEY_free(pkA); + EVP_PKEY_free(pkB); + es384_pk_free(&k); + + return ok; +} + +static void +short_coord(void) +{ + assert(es384_pk_cmp(short_x, sizeof(short_x)) == 0); + assert(es384_pk_cmp(short_y, sizeof(short_y)) == 0); +} + +static void +invalid_curve(const unsigned char *raw, size_t raw_len) +{ + EVP_PKEY *pkey; + es384_pk_t *pk; + + pkey = EVP_PKEY_from_PEM(brainpoolP384r1, sizeof(brainpoolP384r1)); + if (pkey == NULL) + return; /* assume no brainpool support in libcrypto */ + ASSERT_NOT_NULL((pk = es384_pk_new())); + ASSERT_INVAL(es384_pk_from_EVP_PKEY(pk, pkey)); + ASSERT_INVAL(es384_pk_from_ptr(pk, raw, raw_len)); + ASSERT_NULL(es384_pk_to_EVP_PKEY((const es384_pk_t *)raw)); + + EVP_PKEY_free(pkey); + es384_pk_free(&pk); +} + +static void +full_coord(void) +{ + assert(es384_pk_cmp(secp384r1, sizeof(secp384r1)) == 0); +} + +static void +valid_curve(const unsigned char *raw, size_t raw_len) +{ + EVP_PKEY *pkeyA; + EVP_PKEY *pkeyB; + es384_pk_t *pkA; + es384_pk_t *pkB; + + ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(secp384r1, sizeof(secp384r1)))); + ASSERT_NOT_NULL((pkA = es384_pk_new())); + ASSERT_NOT_NULL((pkB = es384_pk_new())); + ASSERT_OK(es384_pk_from_EVP_PKEY(pkA, pkeyA)); + ASSERT_OK(es384_pk_from_ptr(pkB, raw, raw_len)); + ASSERT_NOT_NULL((pkeyB = es384_pk_to_EVP_PKEY(pkB))); + assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1); + + EVP_PKEY_free(pkeyA); + EVP_PKEY_free(pkeyB); + es384_pk_free(&pkA); + es384_pk_free(&pkB); +} + +int +main(void) +{ + fido_init(0); + + short_coord(); + full_coord(); + + invalid_curve(brainpoolP384r1_raw, sizeof(brainpoolP384r1_raw)); /* uncompressed */ + invalid_curve(brainpoolP384r1_raw + 1, sizeof(brainpoolP384r1_raw) - 1); /* libfido2 */ + valid_curve(secp384r1_raw, sizeof(secp384r1_raw)); /* uncompressed */ + valid_curve(secp384r1_raw + 1, sizeof(secp384r1_raw) - 1); /* libfido2 */ + + exit(0); +} diff --git a/regress/rs256.c b/regress/rs256.c new file mode 100644 index 0000000..799396f --- /dev/null +++ b/regress/rs256.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef NDEBUG + +#include <assert.h> +#include <string.h> + +#define _FIDO_INTERNAL + +#include <fido.h> +#include <fido/rs256.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> + +#define ASSERT_NOT_NULL(e) assert((e) != NULL) +#define ASSERT_NULL(e) assert((e) == NULL) +#define ASSERT_INVAL(e) assert((e) == FIDO_ERR_INVALID_ARGUMENT) +#define ASSERT_OK(e) assert((e) == FIDO_OK) + +static char rsa1024[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCw92gn9Ku/bEfFj1AutaZyltpf\n" +"zzXrg70kQFymNq+spMt/HlxKiImw8TZU08zWW4ZLE/Ch4JYjMW6ETAdQFhSC63Ih\n" +"Wecui0JJ1f+2CsUVg+h7lO1877LZYUpdNiJrbqMb5Yc4N3FPtvdl3NoLIIQsF76H\n" +"VRvpjQgkWipRfZ97JQIDAQAB\n" +"-----END PUBLIC KEY-----"; + +static char rsa2048[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApvIq/55ZodBIxzo/8BnE\n" +"UQN1fo1hmJ6V20hQHSzJq5tHyxRCcvKikuJ1ZvR4RdZlEzdTdbEfMBdZ8sxve0/U\n" +"yYEjH92CG0vgTCYuUaFLJTaWZSvWa96G8Lw+V4VyNFDRCM7sflOaSVH5pAsz8OEc\n" +"TLZfM4NhnDsJAM+mQ6X7Tza0sczPchgDA+9KByXo/VIqyuBQs17rlKC2reMa8NkY\n" +"rBRQZJLNzi68d5/BHH1flGWE1l8wJ9dr1Ex93H/KdzX+7/28TWUC98nneUo8RfRx\n" +"FwUt/EInDMHOORCaCHSs28U/9IUyMjqLB1rxKhIp09yGXMiTrrT+p+Pcn8dO01HT\n" +"vQIDAQAB\n" +"-----END PUBLIC KEY-----"; + +static char rsa3072[] = \ +"-----BEGIN PUBLIC KEY-----\n" +"MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAwZunKrMs/o92AniLPNTF\n" +"Ta4EYfhy5NDmMvQvRFT/eTYItLrOTPmYMap68KLyZYmgz/AdaxAL/992QWre7XTY\n" +"gqLwtZT+WsSu7xPHWKTTXrlVohKBeLHQ0I7Zy0NSMUxhlJEMrBAjSyFAS86zWm5w\n" +"ctC3pNCqfUKugA07BVj+d5Mv5fziwgMR86kuhkVuMYfsR4IYwX4+va0pyLzxx624\n" +"s9nJ107g+A+3MUk4bAto3lruFeeZPUI2AFzFQbGg5By6VtvVi3gKQ7lUNtAr0Onu\n" +"I6Fb+yz8sbFcvDpJcu5CXW20GrKMVP4KY5pn2LCajWuZjBl/dXWayPfm4UX5Y2O4\n" +"73tzPpUBNwnEdz79His0v80Vmvjwn5IuF2jAoimrBNPJFFwCCuVNy8kgj2vllk1l\n" +"RvLOG6hf8VnlDb40QZS3QAQ09xFfF+xlVLb8cHH6wllaAGEM230TrmawpC7xpz4Z\n" +"sTuwJwI0AWEi//noMsRz2BuF2fCp//aORYJQU2S8kYk3AgMBAAE=\n" +"-----END PUBLIC KEY-----"; + +static const unsigned char rsa2048_raw[] = { + 0xa6, 0xf2, 0x2a, 0xff, 0x9e, 0x59, 0xa1, 0xd0, + 0x48, 0xc7, 0x3a, 0x3f, 0xf0, 0x19, 0xc4, 0x51, + 0x03, 0x75, 0x7e, 0x8d, 0x61, 0x98, 0x9e, 0x95, + 0xdb, 0x48, 0x50, 0x1d, 0x2c, 0xc9, 0xab, 0x9b, + 0x47, 0xcb, 0x14, 0x42, 0x72, 0xf2, 0xa2, 0x92, + 0xe2, 0x75, 0x66, 0xf4, 0x78, 0x45, 0xd6, 0x65, + 0x13, 0x37, 0x53, 0x75, 0xb1, 0x1f, 0x30, 0x17, + 0x59, 0xf2, 0xcc, 0x6f, 0x7b, 0x4f, 0xd4, 0xc9, + 0x81, 0x23, 0x1f, 0xdd, 0x82, 0x1b, 0x4b, 0xe0, + 0x4c, 0x26, 0x2e, 0x51, 0xa1, 0x4b, 0x25, 0x36, + 0x96, 0x65, 0x2b, 0xd6, 0x6b, 0xde, 0x86, 0xf0, + 0xbc, 0x3e, 0x57, 0x85, 0x72, 0x34, 0x50, 0xd1, + 0x08, 0xce, 0xec, 0x7e, 0x53, 0x9a, 0x49, 0x51, + 0xf9, 0xa4, 0x0b, 0x33, 0xf0, 0xe1, 0x1c, 0x4c, + 0xb6, 0x5f, 0x33, 0x83, 0x61, 0x9c, 0x3b, 0x09, + 0x00, 0xcf, 0xa6, 0x43, 0xa5, 0xfb, 0x4f, 0x36, + 0xb4, 0xb1, 0xcc, 0xcf, 0x72, 0x18, 0x03, 0x03, + 0xef, 0x4a, 0x07, 0x25, 0xe8, 0xfd, 0x52, 0x2a, + 0xca, 0xe0, 0x50, 0xb3, 0x5e, 0xeb, 0x94, 0xa0, + 0xb6, 0xad, 0xe3, 0x1a, 0xf0, 0xd9, 0x18, 0xac, + 0x14, 0x50, 0x64, 0x92, 0xcd, 0xce, 0x2e, 0xbc, + 0x77, 0x9f, 0xc1, 0x1c, 0x7d, 0x5f, 0x94, 0x65, + 0x84, 0xd6, 0x5f, 0x30, 0x27, 0xd7, 0x6b, 0xd4, + 0x4c, 0x7d, 0xdc, 0x7f, 0xca, 0x77, 0x35, 0xfe, + 0xef, 0xfd, 0xbc, 0x4d, 0x65, 0x02, 0xf7, 0xc9, + 0xe7, 0x79, 0x4a, 0x3c, 0x45, 0xf4, 0x71, 0x17, + 0x05, 0x2d, 0xfc, 0x42, 0x27, 0x0c, 0xc1, 0xce, + 0x39, 0x10, 0x9a, 0x08, 0x74, 0xac, 0xdb, 0xc5, + 0x3f, 0xf4, 0x85, 0x32, 0x32, 0x3a, 0x8b, 0x07, + 0x5a, 0xf1, 0x2a, 0x12, 0x29, 0xd3, 0xdc, 0x86, + 0x5c, 0xc8, 0x93, 0xae, 0xb4, 0xfe, 0xa7, 0xe3, + 0xdc, 0x9f, 0xc7, 0x4e, 0xd3, 0x51, 0xd3, 0xbd, + 0x01, 0x00, 0x01, +}; + +static EVP_PKEY * +EVP_PKEY_from_PEM(const char *ptr, size_t len) +{ + BIO *bio = NULL; + EVP_PKEY *pkey = NULL; + + if ((bio = BIO_new(BIO_s_mem())) == NULL) { + warnx("BIO_new"); + goto out; + } + if (len > INT_MAX || BIO_write(bio, ptr, (int)len) != (int)len) { + warnx("BIO_write"); + goto out; + } + if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL)) == NULL) + warnx("PEM_read_bio_PUBKEY"); +out: + BIO_free(bio); + + return pkey; +} + +static int +rs256_pk_cmp(const char *ptr, size_t len) +{ + EVP_PKEY *pkA = NULL; + EVP_PKEY *pkB = NULL; + rs256_pk_t *k = NULL; + int r, ok = -1; + + if ((pkA = EVP_PKEY_from_PEM(ptr, len)) == NULL) { + warnx("EVP_PKEY_from_PEM"); + goto out; + } + if ((k = rs256_pk_new()) == NULL) { + warnx("rs256_pk_new"); + goto out; + } + if ((r = rs256_pk_from_EVP_PKEY(k, pkA)) != FIDO_OK) { + warnx("rs256_pk_from_EVP_PKEY: 0x%x", r); + goto out; + } + if ((pkB = rs256_pk_to_EVP_PKEY(k)) == NULL) { + warnx("rs256_pk_to_EVP_PKEY"); + goto out; + } + if ((r = EVP_PKEY_cmp(pkA, pkB)) != 1) { + warnx("EVP_PKEY_cmp: %d", r); + goto out; + } + + ok = 0; +out: + EVP_PKEY_free(pkA); + EVP_PKEY_free(pkB); + rs256_pk_free(&k); + + return ok; +} + +static void +invalid_size(const char *pem) +{ + EVP_PKEY *pkey; + rs256_pk_t *pk; + + ASSERT_NOT_NULL((pkey = EVP_PKEY_from_PEM(pem, strlen(pem)))); + ASSERT_NOT_NULL((pk = rs256_pk_new())); + ASSERT_INVAL(rs256_pk_from_EVP_PKEY(pk, pkey)); + + EVP_PKEY_free(pkey); + rs256_pk_free(&pk); +} + +static void +valid_size(const char *pem, const unsigned char *raw, size_t raw_len) +{ + EVP_PKEY *pkeyA; + EVP_PKEY *pkeyB; + rs256_pk_t *pkA; + rs256_pk_t *pkB; + + ASSERT_NOT_NULL((pkeyA = EVP_PKEY_from_PEM(pem, strlen(pem)))); + ASSERT_NOT_NULL((pkA = rs256_pk_new())); + ASSERT_NOT_NULL((pkB = rs256_pk_new())); + ASSERT_OK(rs256_pk_from_EVP_PKEY(pkA, pkeyA)); + ASSERT_OK(rs256_pk_from_ptr(pkB, raw, raw_len)); + ASSERT_NOT_NULL((pkeyB = rs256_pk_to_EVP_PKEY(pkB))); + assert(EVP_PKEY_cmp(pkeyA, pkeyB) == 1); + assert(rs256_pk_cmp(pem, strlen(pem)) == 0); + + EVP_PKEY_free(pkeyA); + EVP_PKEY_free(pkeyB); + rs256_pk_free(&pkA); + rs256_pk_free(&pkB); +} + +int +main(void) +{ + fido_init(0); + + invalid_size(rsa1024); + invalid_size(rsa3072); + valid_size(rsa2048, rsa2048_raw, sizeof(rsa2048_raw)); + + exit(0); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..73493b1 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,158 @@ +# Copyright (c) 2018-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +add_definitions(-D_FIDO_INTERNAL) + +list(APPEND FIDO_SOURCES + aes256.c + assert.c + authkey.c + bio.c + blob.c + buf.c + cbor.c + compress.c + config.c + cred.c + credman.c + dev.c + ecdh.c + eddsa.c + err.c + es256.c + es384.c + hid.c + info.c + io.c + iso7816.c + largeblob.c + log.c + pin.c + random.c + reset.c + rs1.c + rs256.c + time.c + touch.c + tpm.c + types.c + u2f.c + util.c +) + +if(FUZZ) + list(APPEND FIDO_SOURCES ../fuzz/clock.c) + list(APPEND FIDO_SOURCES ../fuzz/pcsc.c) + list(APPEND FIDO_SOURCES ../fuzz/prng.c) + list(APPEND FIDO_SOURCES ../fuzz/udev.c) + list(APPEND FIDO_SOURCES ../fuzz/uniform_random.c) + list(APPEND FIDO_SOURCES ../fuzz/wrap.c) +endif() + +if(NFC_LINUX) + list(APPEND FIDO_SOURCES netlink.c nfc.c nfc_linux.c) +endif() + +if(USE_PCSC) + list(APPEND FIDO_SOURCES nfc.c pcsc.c) +endif() + +if(USE_HIDAPI) + list(APPEND FIDO_SOURCES hid_hidapi.c) + if(NOT WIN32 AND NOT APPLE) + list(APPEND FIDO_SOURCES hid_unix.c) + endif() +elseif(WIN32) + list(APPEND FIDO_SOURCES hid_win.c) + if(USE_WINHELLO) + list(APPEND FIDO_SOURCES winhello.c) + endif() +elseif(APPLE) + list(APPEND FIDO_SOURCES hid_osx.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + list(APPEND FIDO_SOURCES hid_linux.c hid_unix.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + list(APPEND FIDO_SOURCES hid_netbsd.c hid_unix.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + list(APPEND FIDO_SOURCES hid_openbsd.c hid_unix.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") + list(APPEND FIDO_SOURCES hid_freebsd.c hid_unix.c) +else() + message(FATAL_ERROR "please define a hid backend for your platform") +endif() + +if(NOT MSVC) + set_source_files_properties(${FIDO_SOURCES} + PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS}") +endif() + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/bsd-asprintf.c + ../openbsd-compat/bsd-getpagesize.c + ../openbsd-compat/clock_gettime.c + ../openbsd-compat/endian_win32.c + ../openbsd-compat/explicit_bzero.c + ../openbsd-compat/explicit_bzero_win32.c + ../openbsd-compat/freezero.c + ../openbsd-compat/recallocarray.c + ../openbsd-compat/strlcat.c + ../openbsd-compat/timingsafe_bcmp.c +) + +if(WIN32) + list(APPEND BASE_LIBRARIES wsock32 ws2_32 bcrypt setupapi hid) + if(USE_PCSC) + list(APPEND BASE_LIBRARIES winscard) + endif() +elseif(APPLE) + list(APPEND BASE_LIBRARIES "-framework CoreFoundation" + "-framework IOKit") + if(USE_PCSC) + list(APPEND BASE_LIBRARIES "-framework PCSC") + endif() +endif() + +list(APPEND TARGET_LIBRARIES + ${CBOR_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${UDEV_LIBRARIES} + ${BASE_LIBRARIES} + ${HIDAPI_LIBRARIES} + ${ZLIB_LIBRARIES} + ${PCSC_LIBRARIES} +) + +# static library +if(BUILD_STATIC_LIBS) + add_library(fido2 STATIC ${FIDO_SOURCES} ${COMPAT_SOURCES}) + if(WIN32 AND NOT MINGW) + set_target_properties(fido2 PROPERTIES OUTPUT_NAME fido2_static) + endif() + target_link_libraries(fido2 ${TARGET_LIBRARIES}) + install(TARGETS fido2 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + +# dynamic library +if(BUILD_SHARED_LIBS) + add_library(fido2_shared SHARED ${FIDO_SOURCES} ${COMPAT_SOURCES}) + set_target_properties(fido2_shared PROPERTIES OUTPUT_NAME fido2 + VERSION ${FIDO_VERSION} SOVERSION ${FIDO_MAJOR}) + target_link_libraries(fido2_shared ${TARGET_LIBRARIES}) + install(TARGETS fido2_shared + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +install(FILES fido.h DESTINATION include) +install(DIRECTORY fido DESTINATION include) + +if(NOT MSVC) + configure_file(libfido2.pc.in libfido2.pc @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libfido2.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +endif() diff --git a/src/aes256.c b/src/aes256.c new file mode 100644 index 0000000..dcf716d --- /dev/null +++ b/src/aes256.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +aes256_cbc(const fido_blob_t *key, const u_char *iv, const fido_blob_t *in, + fido_blob_t *out, int encrypt) +{ + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher; + int ok = -1; + + memset(out, 0, sizeof(*out)); + + if (key->len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key->len); + goto fail; + } + if (in->len > UINT_MAX || in->len % 16 || in->len == 0) { + fido_log_debug("%s: invalid input len %zu", __func__, in->len); + goto fail; + } + out->len = in->len; + if ((out->ptr = calloc(1, out->len)) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((ctx = EVP_CIPHER_CTX_new()) == NULL || + (cipher = EVP_aes_256_cbc()) == NULL) { + fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__); + goto fail; + } + if (EVP_CipherInit(ctx, cipher, key->ptr, iv, encrypt) == 0 || + EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)out->len) < 0) { + fido_log_debug("%s: EVP_Cipher", __func__); + goto fail; + } + + ok = 0; +fail: + if (ctx != NULL) + EVP_CIPHER_CTX_free(ctx); + if (ok < 0) + fido_blob_reset(out); + + return ok; +} + +static int +aes256_cbc_proto1(const fido_blob_t *key, const fido_blob_t *in, + fido_blob_t *out, int encrypt) +{ + u_char iv[16]; + + memset(&iv, 0, sizeof(iv)); + + return aes256_cbc(key, iv, in, out, encrypt); +} + +static int +aes256_cbc_fips(const fido_blob_t *secret, const fido_blob_t *in, + fido_blob_t *out, int encrypt) +{ + fido_blob_t key, cin, cout; + u_char iv[16]; + + memset(out, 0, sizeof(*out)); + + if (secret->len != 64) { + fido_log_debug("%s: invalid secret len %zu", __func__, + secret->len); + return -1; + } + if (in->len < sizeof(iv)) { + fido_log_debug("%s: invalid input len %zu", __func__, in->len); + return -1; + } + if (encrypt) { + if (fido_get_random(iv, sizeof(iv)) < 0) { + fido_log_debug("%s: fido_get_random", __func__); + return -1; + } + cin = *in; + } else { + memcpy(iv, in->ptr, sizeof(iv)); + cin.ptr = in->ptr + sizeof(iv); + cin.len = in->len - sizeof(iv); + } + key.ptr = secret->ptr + 32; + key.len = secret->len - 32; + if (aes256_cbc(&key, iv, &cin, &cout, encrypt) < 0) + return -1; + if (encrypt) { + if (cout.len > SIZE_MAX - sizeof(iv) || + (out->ptr = calloc(1, sizeof(iv) + cout.len)) == NULL) { + fido_blob_reset(&cout); + return -1; + } + out->len = sizeof(iv) + cout.len; + memcpy(out->ptr, iv, sizeof(iv)); + memcpy(out->ptr + sizeof(iv), cout.ptr, cout.len); + fido_blob_reset(&cout); + } else + *out = cout; + + return 0; +} + +static int +aes256_gcm(const fido_blob_t *key, const fido_blob_t *nonce, + const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out, + int encrypt) +{ + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher; + size_t textlen; + int ok = -1; + + memset(out, 0, sizeof(*out)); + + if (nonce->len != 12 || key->len != 32 || aad->len > UINT_MAX) { + fido_log_debug("%s: invalid params %zu, %zu, %zu", __func__, + nonce->len, key->len, aad->len); + goto fail; + } + if (in->len > UINT_MAX || in->len > SIZE_MAX - 16 || in->len < 16) { + fido_log_debug("%s: invalid input len %zu", __func__, in->len); + goto fail; + } + /* add tag to (on encrypt) or trim tag from the output (on decrypt) */ + out->len = encrypt ? in->len + 16 : in->len - 16; + if ((out->ptr = calloc(1, out->len)) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((ctx = EVP_CIPHER_CTX_new()) == NULL || + (cipher = EVP_aes_256_gcm()) == NULL) { + fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__); + goto fail; + } + if (EVP_CipherInit(ctx, cipher, key->ptr, nonce->ptr, encrypt) == 0) { + fido_log_debug("%s: EVP_CipherInit", __func__); + goto fail; + } + + if (encrypt) + textlen = in->len; + else { + textlen = in->len - 16; + /* point openssl at the mac tag */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, + in->ptr + in->len - 16) == 0) { + fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__); + goto fail; + } + } + /* the last EVP_Cipher() will either compute or verify the mac tag */ + if (EVP_Cipher(ctx, NULL, aad->ptr, (u_int)aad->len) < 0 || + EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)textlen) < 0 || + EVP_Cipher(ctx, NULL, NULL, 0) < 0) { + fido_log_debug("%s: EVP_Cipher", __func__); + goto fail; + } + if (encrypt) { + /* append the mac tag */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, + out->ptr + out->len - 16) == 0) { + fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__); + goto fail; + } + } + + ok = 0; +fail: + if (ctx != NULL) + EVP_CIPHER_CTX_free(ctx); + if (ok < 0) + fido_blob_reset(out); + + return ok; +} + +int +aes256_cbc_enc(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *in, fido_blob_t *out) +{ + return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret, + in, out, 1) : aes256_cbc_proto1(secret, in, out, 1); +} + +int +aes256_cbc_dec(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *in, fido_blob_t *out) +{ + return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret, + in, out, 0) : aes256_cbc_proto1(secret, in, out, 0); +} + +int +aes256_gcm_enc(const fido_blob_t *key, const fido_blob_t *nonce, + const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out) +{ + return aes256_gcm(key, nonce, aad, in, out, 1); +} + +int +aes256_gcm_dec(const fido_blob_t *key, const fido_blob_t *nonce, + const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out) +{ + return aes256_gcm(key, nonce, aad, in, out, 0); +} diff --git a/src/assert.c b/src/assert.c new file mode 100644 index 0000000..39d63bc --- /dev/null +++ b/src/assert.c @@ -0,0 +1,1170 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> + +#include "fido.h" +#include "fido/es256.h" +#include "fido/rs256.h" +#include "fido/eddsa.h" + +static int +adjust_assert_count(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_assert_t *assert = arg; + uint64_t n; + + /* numberOfCredentials; see section 6.2 */ + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 5) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + if (assert->stmt_len != 0 || assert->stmt_cnt != 1 || + (size_t)n < assert->stmt_cnt) { + fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu, n=%zu", + __func__, assert->stmt_len, assert->stmt_cnt, (size_t)n); + return (-1); + } + + if (fido_assert_set_count(assert, (size_t)n) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_count", __func__); + return (-1); + } + + assert->stmt_len = 0; /* XXX */ + + return (0); +} + +static int +parse_assert_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_assert_stmt *stmt = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* credential id */ + return (cbor_decode_cred_id(val, &stmt->id)); + case 2: /* authdata */ + if (fido_blob_decode(val, &stmt->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + return (-1); + } + return (cbor_decode_assert_authdata(val, &stmt->authdata_cbor, + &stmt->authdata, &stmt->authdata_ext)); + case 3: /* signature */ + return (fido_blob_decode(val, &stmt->sig)); + case 4: /* user attributes */ + return (cbor_decode_user(val, &stmt->user)); + case 7: /* large blob key */ + return (fido_blob_decode(val, &stmt->largeblob_key)); + default: /* ignore */ + fido_log_debug("%s: cbor type", __func__); + return (0); + } +} + +static int +fido_dev_get_assert_tx(fido_dev_t *dev, fido_assert_t *assert, + const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int *ms) +{ + fido_blob_t f; + fido_opt_t uv = assert->uv; + cbor_item_t *argv[7]; + const uint8_t cmd = CTAP_CBOR_ASSERT; + int r; + + memset(argv, 0, sizeof(argv)); + memset(&f, 0, sizeof(f)); + + /* do we have everything we need? */ + if (assert->rp_id == NULL || assert->cdh.ptr == NULL) { + fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__, + (void *)assert->rp_id, (void *)assert->cdh.ptr); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if ((argv[0] = cbor_build_string(assert->rp_id)) == NULL || + (argv[1] = fido_blob_encode(&assert->cdh)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* allowed credentials */ + if (assert->allow_list.len) { + const fido_blob_array_t *cl = &assert->allow_list; + if ((argv[2] = cbor_encode_pubkey_list(cl)) == NULL) { + fido_log_debug("%s: cbor_encode_pubkey_list", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + } + + if (assert->ext.mask) + if ((argv[3] = cbor_encode_assert_ext(dev, &assert->ext, ecdh, + pk)) == NULL) { + fido_log_debug("%s: cbor_encode_assert_ext", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* user verification */ + if (pin != NULL || (uv == FIDO_OPT_TRUE && + fido_dev_supports_permissions(dev))) { + if ((r = cbor_add_uv_params(dev, cmd, &assert->cdh, pk, ecdh, + pin, assert->rp_id, &argv[5], &argv[6], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + uv = FIDO_OPT_OMIT; + } + + /* options */ + if (assert->up != FIDO_OPT_OMIT || uv != FIDO_OPT_OMIT) + if ((argv[4] = cbor_encode_assert_opt(assert->up, uv)) == NULL) { + fido_log_debug("%s: cbor_encode_assert_opt", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* frame and transmit */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + + return (r); +} + +static int +fido_dev_get_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + fido_assert_reset_rx(assert); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* start with room for a single assertion */ + if ((assert->stmt = calloc(1, sizeof(fido_assert_stmt))) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + assert->stmt_len = 0; + assert->stmt_cnt = 1; + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, assert, + adjust_assert_count)) != FIDO_OK) { + fido_log_debug("%s: adjust_assert_count", __func__); + goto out; + } + + /* parse the first assertion */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &assert->stmt[0], + parse_assert_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_assert_reply", __func__); + goto out; + } + assert->stmt_len = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_get_next_assert_tx(fido_dev_t *dev, int *ms) +{ + const unsigned char cbor[] = { CTAP_CBOR_NEXT_ASSERT }; + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + return (FIDO_ERR_TX); + } + + return (FIDO_OK); +} + +static int +fido_get_next_assert_rx(fido_dev_t *dev, fido_assert_t *assert, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* sanity check */ + if (assert->stmt_len >= assert->stmt_cnt) { + fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu", __func__, + assert->stmt_len, assert->stmt_cnt); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, + &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_assert_reply", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_dev_get_assert_wait(fido_dev_t *dev, fido_assert_t *assert, + const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, int *ms) +{ + int r; + + if ((r = fido_dev_get_assert_tx(dev, assert, pk, ecdh, pin, + ms)) != FIDO_OK || + (r = fido_dev_get_assert_rx(dev, assert, ms)) != FIDO_OK) + return (r); + + while (assert->stmt_len < assert->stmt_cnt) { + if ((r = fido_get_next_assert_tx(dev, ms)) != FIDO_OK || + (r = fido_get_next_assert_rx(dev, assert, ms)) != FIDO_OK) + return (r); + assert->stmt_len++; + } + + return (FIDO_OK); +} + +static int +decrypt_hmac_secrets(const fido_dev_t *dev, fido_assert_t *assert, + const fido_blob_t *key) +{ + for (size_t i = 0; i < assert->stmt_cnt; i++) { + fido_assert_stmt *stmt = &assert->stmt[i]; + if (stmt->authdata_ext.hmac_secret_enc.ptr != NULL) { + if (aes256_cbc_dec(dev, key, + &stmt->authdata_ext.hmac_secret_enc, + &stmt->hmac_secret) < 0) { + fido_log_debug("%s: aes256_cbc_dec %zu", + __func__, i); + return (-1); + } + } + } + + return (0); +} + +int +fido_dev_get_assert(fido_dev_t *dev, fido_assert_t *assert, const char *pin) +{ + fido_blob_t *ecdh = NULL; + es256_pk_t *pk = NULL; + int ms = dev->timeout_ms; + int r; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_get_assert(dev, assert, pin, ms)); +#endif + + if (assert->rp_id == NULL || assert->cdh.ptr == NULL) { + fido_log_debug("%s: rp_id=%p, cdh.ptr=%p", __func__, + (void *)assert->rp_id, (void *)assert->cdh.ptr); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (fido_dev_is_fido2(dev) == false) { + if (pin != NULL || assert->ext.mask != 0) + return (FIDO_ERR_UNSUPPORTED_OPTION); + return (u2f_authenticate(dev, assert, &ms)); + } + + if (pin != NULL || (assert->uv == FIDO_OPT_TRUE && + fido_dev_supports_permissions(dev)) || + (assert->ext.mask & FIDO_EXT_HMAC_SECRET)) { + if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + } + + r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, &ms); + if (r == FIDO_OK && (assert->ext.mask & FIDO_EXT_HMAC_SECRET)) + if (decrypt_hmac_secrets(dev, assert, ecdh) < 0) { + fido_log_debug("%s: decrypt_hmac_secrets", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + +fail: + es256_pk_free(&pk); + fido_blob_free(&ecdh); + + return (r); +} + +int +fido_check_flags(uint8_t flags, fido_opt_t up, fido_opt_t uv) +{ + fido_log_debug("%s: flags=%02x", __func__, flags); + fido_log_debug("%s: up=%d, uv=%d", __func__, up, uv); + + if (up == FIDO_OPT_TRUE && + (flags & CTAP_AUTHDATA_USER_PRESENT) == 0) { + fido_log_debug("%s: CTAP_AUTHDATA_USER_PRESENT", __func__); + return (-1); /* user not present */ + } + + if (uv == FIDO_OPT_TRUE && + (flags & CTAP_AUTHDATA_USER_VERIFIED) == 0) { + fido_log_debug("%s: CTAP_AUTHDATA_USER_VERIFIED", __func__); + return (-1); /* user not verified */ + } + + return (0); +} + +static int +check_extensions(int authdata_ext, int ext) +{ + /* XXX: largeBlobKey is not part of extensions map */ + ext &= ~FIDO_EXT_LARGEBLOB_KEY; + if (authdata_ext != ext) { + fido_log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__, + authdata_ext, ext); + return (-1); + } + + return (0); +} + +static int +get_es256_hash(fido_blob_t *dgst, const fido_blob_t *clientdata, + const fido_blob_t *authdata) +{ + const EVP_MD *md; + EVP_MD_CTX *ctx = NULL; + + if (dgst->len < SHA256_DIGEST_LENGTH || + (md = EVP_sha256()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) { + EVP_MD_CTX_free(ctx); + return (-1); + } + dgst->len = SHA256_DIGEST_LENGTH; + + EVP_MD_CTX_free(ctx); + + return (0); +} + +static int +get_es384_hash(fido_blob_t *dgst, const fido_blob_t *clientdata, + const fido_blob_t *authdata) +{ + const EVP_MD *md; + EVP_MD_CTX *ctx = NULL; + + if (dgst->len < SHA384_DIGEST_LENGTH || + (md = EVP_sha384()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) { + EVP_MD_CTX_free(ctx); + return (-1); + } + dgst->len = SHA384_DIGEST_LENGTH; + + EVP_MD_CTX_free(ctx); + + return (0); +} + +static int +get_eddsa_hash(fido_blob_t *dgst, const fido_blob_t *clientdata, + const fido_blob_t *authdata) +{ + if (SIZE_MAX - authdata->len < clientdata->len || + dgst->len < authdata->len + clientdata->len) + return (-1); + + memcpy(dgst->ptr, authdata->ptr, authdata->len); + memcpy(dgst->ptr + authdata->len, clientdata->ptr, clientdata->len); + dgst->len = authdata->len + clientdata->len; + + return (0); +} + +int +fido_get_signed_hash(int cose_alg, fido_blob_t *dgst, + const fido_blob_t *clientdata, const fido_blob_t *authdata_cbor) +{ + cbor_item_t *item = NULL; + fido_blob_t authdata; + struct cbor_load_result cbor; + int ok = -1; + + fido_log_debug("%s: cose_alg=%d", __func__, cose_alg); + + if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len, + &cbor)) == NULL || cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) { + fido_log_debug("%s: authdata", __func__); + goto fail; + } + authdata.ptr = cbor_bytestring_handle(item); + authdata.len = cbor_bytestring_length(item); + + switch (cose_alg) { + case COSE_ES256: + case COSE_RS256: + ok = get_es256_hash(dgst, clientdata, &authdata); + break; + case COSE_ES384: + ok = get_es384_hash(dgst, clientdata, &authdata); + break; + case COSE_EDDSA: + ok = get_eddsa_hash(dgst, clientdata, &authdata); + break; + default: + fido_log_debug("%s: unknown cose_alg", __func__); + break; + } +fail: + if (item != NULL) + cbor_decref(&item); + + return (ok); +} + +int +fido_assert_verify(const fido_assert_t *assert, size_t idx, int cose_alg, + const void *pk) +{ + unsigned char buf[1024]; /* XXX */ + fido_blob_t dgst; + const fido_assert_stmt *stmt = NULL; + int ok = -1; + int r; + + dgst.ptr = buf; + dgst.len = sizeof(buf); + + if (idx >= assert->stmt_len || pk == NULL) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + stmt = &assert->stmt[idx]; + + /* do we have everything we need? */ + if (assert->cdh.ptr == NULL || assert->rp_id == NULL || + stmt->authdata_cbor.ptr == NULL || stmt->sig.ptr == NULL) { + fido_log_debug("%s: cdh=%p, rp_id=%s, authdata=%p, sig=%p", + __func__, (void *)assert->cdh.ptr, assert->rp_id, + (void *)stmt->authdata_cbor.ptr, (void *)stmt->sig.ptr); + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + if (fido_check_flags(stmt->authdata.flags, assert->up, + assert->uv) < 0) { + fido_log_debug("%s: fido_check_flags", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (check_extensions(stmt->authdata_ext.mask, assert->ext.mask) < 0) { + fido_log_debug("%s: check_extensions", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_check_rp_id(assert->rp_id, stmt->authdata.rp_id_hash) != 0) { + fido_log_debug("%s: fido_check_rp_id", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_get_signed_hash(cose_alg, &dgst, &assert->cdh, + &stmt->authdata_cbor) < 0) { + fido_log_debug("%s: fido_get_signed_hash", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + + switch (cose_alg) { + case COSE_ES256: + ok = es256_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + case COSE_ES384: + ok = es384_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + case COSE_RS256: + ok = rs256_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + case COSE_EDDSA: + ok = eddsa_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + default: + fido_log_debug("%s: unsupported cose_alg %d", __func__, + cose_alg); + r = FIDO_ERR_UNSUPPORTED_OPTION; + goto out; + } + + if (ok < 0) + r = FIDO_ERR_INVALID_SIG; + else + r = FIDO_OK; +out: + explicit_bzero(buf, sizeof(buf)); + + return (r); +} + +int +fido_assert_set_clientdata(fido_assert_t *assert, const unsigned char *data, + size_t data_len) +{ + if (!fido_blob_is_empty(&assert->cdh) || + fido_blob_set(&assert->cd, data, data_len) < 0) { + return (FIDO_ERR_INVALID_ARGUMENT); + } + if (fido_sha256(&assert->cdh, data, data_len) < 0) { + fido_blob_reset(&assert->cd); + return (FIDO_ERR_INTERNAL); + } + + return (FIDO_OK); +} + +int +fido_assert_set_clientdata_hash(fido_assert_t *assert, + const unsigned char *hash, size_t hash_len) +{ + if (!fido_blob_is_empty(&assert->cd) || + fido_blob_set(&assert->cdh, hash, hash_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_assert_set_hmac_salt(fido_assert_t *assert, const unsigned char *salt, + size_t salt_len) +{ + if ((salt_len != 32 && salt_len != 64) || + fido_blob_set(&assert->ext.hmac_salt, salt, salt_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_assert_set_hmac_secret(fido_assert_t *assert, size_t idx, + const unsigned char *secret, size_t secret_len) +{ + if (idx >= assert->stmt_len || (secret_len != 32 && secret_len != 64) || + fido_blob_set(&assert->stmt[idx].hmac_secret, secret, + secret_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_assert_set_rp(fido_assert_t *assert, const char *id) +{ + if (assert->rp_id != NULL) { + free(assert->rp_id); + assert->rp_id = NULL; + } + + if (id == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((assert->rp_id = strdup(id)) == NULL) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +#ifdef USE_WINHELLO +int +fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id) +{ + if (assert->appid != NULL) { + free(assert->appid); + assert->appid = NULL; + } + + if (id == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((assert->appid = strdup(id)) == NULL) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} +#else +int +fido_assert_set_winhello_appid(fido_assert_t *assert, const char *id) +{ + (void)assert; + (void)id; + + return (FIDO_ERR_UNSUPPORTED_EXTENSION); +} +#endif /* USE_WINHELLO */ + +int +fido_assert_allow_cred(fido_assert_t *assert, const unsigned char *ptr, + size_t len) +{ + fido_blob_t id; + fido_blob_t *list_ptr; + int r; + + memset(&id, 0, sizeof(id)); + + if (assert->allow_list.len == SIZE_MAX) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if (fido_blob_set(&id, ptr, len) < 0 || (list_ptr = + recallocarray(assert->allow_list.ptr, assert->allow_list.len, + assert->allow_list.len + 1, sizeof(fido_blob_t))) == NULL) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + list_ptr[assert->allow_list.len++] = id; + assert->allow_list.ptr = list_ptr; + + return (FIDO_OK); +fail: + free(id.ptr); + + return (r); +} + +int +fido_assert_empty_allow_list(fido_assert_t *assert) +{ + fido_free_blob_array(&assert->allow_list); + memset(&assert->allow_list, 0, sizeof(assert->allow_list)); + + return (FIDO_OK); +} + +int +fido_assert_set_extensions(fido_assert_t *assert, int ext) +{ + if (ext == 0) + assert->ext.mask = 0; + else { + if ((ext & FIDO_EXT_ASSERT_MASK) != ext) + return (FIDO_ERR_INVALID_ARGUMENT); + assert->ext.mask |= ext; + } + + return (FIDO_OK); +} + +int +fido_assert_set_options(fido_assert_t *assert, bool up, bool uv) +{ + assert->up = up ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + assert->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + + return (FIDO_OK); +} + +int +fido_assert_set_up(fido_assert_t *assert, fido_opt_t up) +{ + assert->up = up; + + return (FIDO_OK); +} + +int +fido_assert_set_uv(fido_assert_t *assert, fido_opt_t uv) +{ + assert->uv = uv; + + return (FIDO_OK); +} + +const unsigned char * +fido_assert_clientdata_hash_ptr(const fido_assert_t *assert) +{ + return (assert->cdh.ptr); +} + +size_t +fido_assert_clientdata_hash_len(const fido_assert_t *assert) +{ + return (assert->cdh.len); +} + +fido_assert_t * +fido_assert_new(void) +{ + return (calloc(1, sizeof(fido_assert_t))); +} + +void +fido_assert_reset_tx(fido_assert_t *assert) +{ + free(assert->rp_id); + free(assert->appid); + fido_blob_reset(&assert->cd); + fido_blob_reset(&assert->cdh); + fido_blob_reset(&assert->ext.hmac_salt); + fido_assert_empty_allow_list(assert); + memset(&assert->ext, 0, sizeof(assert->ext)); + assert->rp_id = NULL; + assert->appid = NULL; + assert->up = FIDO_OPT_OMIT; + assert->uv = FIDO_OPT_OMIT; +} + +static void +fido_assert_reset_extattr(fido_assert_extattr_t *ext) +{ + fido_blob_reset(&ext->hmac_secret_enc); + fido_blob_reset(&ext->blob); + memset(ext, 0, sizeof(*ext)); +} + +void +fido_assert_reset_rx(fido_assert_t *assert) +{ + for (size_t i = 0; i < assert->stmt_cnt; i++) { + free(assert->stmt[i].user.icon); + free(assert->stmt[i].user.name); + free(assert->stmt[i].user.display_name); + fido_blob_reset(&assert->stmt[i].user.id); + fido_blob_reset(&assert->stmt[i].id); + fido_blob_reset(&assert->stmt[i].hmac_secret); + fido_blob_reset(&assert->stmt[i].authdata_cbor); + fido_blob_reset(&assert->stmt[i].authdata_raw); + fido_blob_reset(&assert->stmt[i].largeblob_key); + fido_blob_reset(&assert->stmt[i].sig); + fido_assert_reset_extattr(&assert->stmt[i].authdata_ext); + memset(&assert->stmt[i], 0, sizeof(assert->stmt[i])); + } + free(assert->stmt); + assert->stmt = NULL; + assert->stmt_len = 0; + assert->stmt_cnt = 0; +} + +void +fido_assert_free(fido_assert_t **assert_p) +{ + fido_assert_t *assert; + + if (assert_p == NULL || (assert = *assert_p) == NULL) + return; + fido_assert_reset_tx(assert); + fido_assert_reset_rx(assert); + free(assert); + *assert_p = NULL; +} + +size_t +fido_assert_count(const fido_assert_t *assert) +{ + return (assert->stmt_len); +} + +const char * +fido_assert_rp_id(const fido_assert_t *assert) +{ + return (assert->rp_id); +} + +uint8_t +fido_assert_flags(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].authdata.flags); +} + +uint32_t +fido_assert_sigcount(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].authdata.sigcount); +} + +const unsigned char * +fido_assert_authdata_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].authdata_cbor.ptr); +} + +size_t +fido_assert_authdata_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].authdata_cbor.len); +} + +const unsigned char * +fido_assert_authdata_raw_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].authdata_raw.ptr); +} + +size_t +fido_assert_authdata_raw_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].authdata_raw.len); +} + +const unsigned char * +fido_assert_sig_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].sig.ptr); +} + +size_t +fido_assert_sig_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].sig.len); +} + +const unsigned char * +fido_assert_id_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].id.ptr); +} + +size_t +fido_assert_id_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].id.len); +} + +const unsigned char * +fido_assert_user_id_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].user.id.ptr); +} + +size_t +fido_assert_user_id_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].user.id.len); +} + +const char * +fido_assert_user_icon(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].user.icon); +} + +const char * +fido_assert_user_name(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].user.name); +} + +const char * +fido_assert_user_display_name(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].user.display_name); +} + +const unsigned char * +fido_assert_hmac_secret_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].hmac_secret.ptr); +} + +size_t +fido_assert_hmac_secret_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].hmac_secret.len); +} + +const unsigned char * +fido_assert_largeblob_key_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].largeblob_key.ptr); +} + +size_t +fido_assert_largeblob_key_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].largeblob_key.len); +} + +const unsigned char * +fido_assert_blob_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].authdata_ext.blob.ptr); +} + +size_t +fido_assert_blob_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].authdata_ext.blob.len); +} + +static void +fido_assert_clean_authdata(fido_assert_stmt *stmt) +{ + fido_blob_reset(&stmt->authdata_cbor); + fido_blob_reset(&stmt->authdata_raw); + fido_assert_reset_extattr(&stmt->authdata_ext); + memset(&stmt->authdata, 0, sizeof(stmt->authdata)); +} + +int +fido_assert_set_authdata(fido_assert_t *assert, size_t idx, + const unsigned char *ptr, size_t len) +{ + cbor_item_t *item = NULL; + fido_assert_stmt *stmt = NULL; + struct cbor_load_result cbor; + int r; + + if (idx >= assert->stmt_len || ptr == NULL || len == 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + stmt = &assert->stmt[idx]; + fido_assert_clean_authdata(stmt); + + if ((item = cbor_load(ptr, len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if (fido_blob_decode(item, &stmt->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor, + &stmt->authdata, &stmt->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_assert_authdata", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + if (r != FIDO_OK) + fido_assert_clean_authdata(stmt); + + return (r); +} + +int +fido_assert_set_authdata_raw(fido_assert_t *assert, size_t idx, + const unsigned char *ptr, size_t len) +{ + cbor_item_t *item = NULL; + fido_assert_stmt *stmt = NULL; + int r; + + if (idx >= assert->stmt_len || ptr == NULL || len == 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + stmt = &assert->stmt[idx]; + fido_assert_clean_authdata(stmt); + + if (fido_blob_set(&stmt->authdata_raw, ptr, len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((item = cbor_build_bytestring(ptr, len)) == NULL) { + fido_log_debug("%s: cbor_build_bytestring", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor, + &stmt->authdata, &stmt->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_assert_authdata", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + if (r != FIDO_OK) + fido_assert_clean_authdata(stmt); + + return (r); +} + +int +fido_assert_set_sig(fido_assert_t *a, size_t idx, const unsigned char *ptr, + size_t len) +{ + if (idx >= a->stmt_len || ptr == NULL || len == 0) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_blob_set(&a->stmt[idx].sig, ptr, len) < 0) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +/* XXX shrinking leaks memory; fortunately that shouldn't happen */ +int +fido_assert_set_count(fido_assert_t *assert, size_t n) +{ + void *new_stmt; + +#ifdef FIDO_FUZZ + if (n > UINT8_MAX) { + fido_log_debug("%s: n > UINT8_MAX", __func__); + return (FIDO_ERR_INTERNAL); + } +#endif + + new_stmt = recallocarray(assert->stmt, assert->stmt_cnt, n, + sizeof(fido_assert_stmt)); + if (new_stmt == NULL) + return (FIDO_ERR_INTERNAL); + + assert->stmt = new_stmt; + assert->stmt_cnt = n; + assert->stmt_len = n; + + return (FIDO_OK); +} diff --git a/src/authkey.c b/src/authkey.c new file mode 100644 index 0000000..761562b --- /dev/null +++ b/src/authkey.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +parse_authkey(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + es256_pk_t *authkey = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 1) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + return (es256_pk_decode(val, authkey)); +} + +static int +fido_dev_authkey_tx(fido_dev_t *dev, int *ms) +{ + fido_blob_t f; + cbor_item_t *argv[2]; + int r; + + fido_log_debug("%s: dev=%p", __func__, (void *)dev); + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + /* add command parameters */ + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(2)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* frame and transmit */ + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + + return (r); +} + +static int +fido_dev_authkey_rx(fido_dev_t *dev, es256_pk_t *authkey, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + fido_log_debug("%s: dev=%p, authkey=%p, ms=%d", __func__, (void *)dev, + (void *)authkey, *ms); + + memset(authkey, 0, sizeof(*authkey)); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = cbor_parse_reply(msg, (size_t)msglen, authkey, parse_authkey); +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_dev_authkey_wait(fido_dev_t *dev, es256_pk_t *authkey, int *ms) +{ + int r; + + if ((r = fido_dev_authkey_tx(dev, ms)) != FIDO_OK || + (r = fido_dev_authkey_rx(dev, authkey, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_dev_authkey(fido_dev_t *dev, es256_pk_t *authkey, int *ms) +{ + return (fido_dev_authkey_wait(dev, authkey, ms)); +} diff --git a/src/bio.c b/src/bio.c new file mode 100644 index 0000000..57db85f --- /dev/null +++ b/src/bio.c @@ -0,0 +1,894 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" +#include "fido/bio.h" +#include "fido/es256.h" + +#define CMD_ENROLL_BEGIN 0x01 +#define CMD_ENROLL_NEXT 0x02 +#define CMD_ENROLL_CANCEL 0x03 +#define CMD_ENUM 0x04 +#define CMD_SET_NAME 0x05 +#define CMD_ENROLL_REMOVE 0x06 +#define CMD_GET_INFO 0x07 + +static int +bio_prepare_hmac(uint8_t cmd, cbor_item_t **argv, size_t argc, + cbor_item_t **param, fido_blob_t *hmac_data) +{ + const uint8_t prefix[2] = { 0x01 /* modality */, cmd }; + int ok = -1; + size_t cbor_alloc_len; + size_t cbor_len; + unsigned char *cbor = NULL; + + if (argv == NULL || param == NULL) + return (fido_blob_set(hmac_data, prefix, sizeof(prefix))); + + if ((*param = cbor_flatten_vector(argv, argc)) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + goto fail; + } + + if ((cbor_len = cbor_serialize_alloc(*param, &cbor, + &cbor_alloc_len)) == 0 || cbor_len > SIZE_MAX - sizeof(prefix)) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + if ((hmac_data->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + memcpy(hmac_data->ptr, prefix, sizeof(prefix)); + memcpy(hmac_data->ptr + sizeof(prefix), cbor, cbor_len); + hmac_data->len = cbor_len + sizeof(prefix); + + ok = 0; +fail: + free(cbor); + + return (ok); +} + +static int +bio_tx(fido_dev_t *dev, uint8_t subcmd, cbor_item_t **sub_argv, size_t sub_argc, + const char *pin, const fido_blob_t *token, int *ms) +{ + cbor_item_t *argv[5]; + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL; + fido_blob_t f; + fido_blob_t hmac; + const uint8_t cmd = CTAP_CBOR_BIO_ENROLL_PRE; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(&hmac, 0, sizeof(hmac)); + memset(&argv, 0, sizeof(argv)); + + /* modality, subCommand */ + if ((argv[0] = cbor_build_uint8(1)) == NULL || + (argv[1] = cbor_build_uint8(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* subParams */ + if (pin || token) { + if (bio_prepare_hmac(subcmd, sub_argv, sub_argc, &argv[2], + &hmac) < 0) { + fido_log_debug("%s: bio_prepare_hmac", __func__); + goto fail; + } + } + + /* pinProtocol, pinAuth */ + if (pin) { + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + NULL, &argv[4], &argv[3], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + } else if (token) { + if ((argv[3] = cbor_encode_pin_opt(dev)) == NULL || + (argv[4] = cbor_encode_pin_auth(dev, token, &hmac)) == NULL) { + fido_log_debug("%s: encode pin", __func__); + goto fail; + } + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + es256_pk_free(&pk); + fido_blob_free(&ecdh); + free(f.ptr); + free(hmac.ptr); + + return (r); +} + +static void +bio_reset_template(fido_bio_template_t *t) +{ + free(t->name); + t->name = NULL; + fido_blob_reset(&t->id); +} + +static void +bio_reset_template_array(fido_bio_template_array_t *ta) +{ + for (size_t i = 0; i < ta->n_alloc; i++) + bio_reset_template(&ta->ptr[i]); + + free(ta->ptr); + ta->ptr = NULL; + memset(ta, 0, sizeof(*ta)); +} + +static int +decode_template(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_bio_template_t *t = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* id */ + return (fido_blob_decode(val, &t->id)); + case 2: /* name */ + return (cbor_string_copy(val, &t->name)); + } + + return (0); /* ignore */ +} + +static int +decode_template_array(const cbor_item_t *item, void *arg) +{ + fido_bio_template_array_t *ta = arg; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (ta->n_rx >= ta->n_alloc) { + fido_log_debug("%s: n_rx >= n_alloc", __func__); + return (-1); + } + + if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) { + fido_log_debug("%s: decode_template", __func__); + return (-1); + } + + ta->n_rx++; + + return (0); +} + +static int +bio_parse_template_array(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_bio_template_array_t *ta = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 7) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + if (cbor_isa_array(val) == false || + cbor_array_is_definite(val) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) { + fido_log_debug("%s: ptr != NULL || n_alloc != 0 || n_rx != 0", + __func__); + return (-1); + } + + if ((ta->ptr = calloc(cbor_array_size(val), sizeof(*ta->ptr))) == NULL) + return (-1); + + ta->n_alloc = cbor_array_size(val); + + if (cbor_array_iter(val, ta, decode_template_array) < 0) { + fido_log_debug("%s: decode_template_array", __func__); + return (-1); + } + + return (0); +} + +static int +bio_rx_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_template_array(ta); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, ta, + bio_parse_template_array)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_array" , __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +bio_get_template_array_wait(fido_dev_t *dev, fido_bio_template_array_t *ta, + const char *pin, int *ms) +{ + int r; + + if ((r = bio_tx(dev, CMD_ENUM, NULL, 0, pin, NULL, ms)) != FIDO_OK || + (r = bio_rx_template_array(dev, ta, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_bio_dev_get_template_array(fido_dev_t *dev, fido_bio_template_array_t *ta, + const char *pin) +{ + int ms = dev->timeout_ms; + + if (pin == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_get_template_array_wait(dev, ta, pin, &ms)); +} + +static int +bio_set_template_name_wait(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin, int *ms) +{ + cbor_item_t *argv[2]; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[0] = fido_blob_encode(&t->id)) == NULL || + (argv[1] = cbor_build_string(t->name)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL, + ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +int +fido_bio_dev_set_template_name(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin) +{ + int ms = dev->timeout_ms; + + if (pin == NULL || t->name == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_set_template_name_wait(dev, t, pin, &ms)); +} + +static void +bio_reset_enroll(fido_bio_enroll_t *e) +{ + e->remaining_samples = 0; + e->last_status = 0; + + if (e->token) + fido_blob_free(&e->token); +} + +static int +bio_parse_enroll_status(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_bio_enroll_t *e = arg; + uint64_t x; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 5: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + e->last_status = (uint8_t)x; + break; + case 6: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + e->remaining_samples = (uint8_t)x; + break; + default: + return (0); /* ignore */ + } + + return (0); +} + +static int +bio_parse_template_id(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_blob_t *id = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 4) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + return (fido_blob_decode(val, id)); +} + +static int +bio_rx_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t, + fido_bio_enroll_t *e, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_template(t); + + e->remaining_samples = 0; + e->last_status = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &t->id, + bio_parse_template_id)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_id", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +bio_enroll_begin_wait(fido_dev_t *dev, fido_bio_template_t *t, + fido_bio_enroll_t *e, uint32_t timo_ms, int *ms) +{ + cbor_item_t *argv[3]; + const uint8_t cmd = CMD_ENROLL_BEGIN; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[2] = cbor_build_uint(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK || + (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +int +fido_bio_dev_enroll_begin(fido_dev_t *dev, fido_bio_template_t *t, + fido_bio_enroll_t *e, uint32_t timo_ms, const char *pin) +{ + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL; + fido_blob_t *token = NULL; + int ms = dev->timeout_ms; + int r; + + if (pin == NULL || e->token != NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((token = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + + if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_BIO_ENROLL_PRE, pin, ecdh, + pk, NULL, token, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_get_uv_token", __func__); + goto fail; + } + + e->token = token; + token = NULL; +fail: + es256_pk_free(&pk); + fido_blob_free(&ecdh); + fido_blob_free(&token); + + if (r != FIDO_OK) + return (r); + + return (bio_enroll_begin_wait(dev, t, e, timo_ms, &ms)); +} + +static int +bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + e->remaining_samples = 0; + e->last_status = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +bio_enroll_continue_wait(fido_dev_t *dev, const fido_bio_template_t *t, + fido_bio_enroll_t *e, uint32_t timo_ms, int *ms) +{ + cbor_item_t *argv[3]; + const uint8_t cmd = CMD_ENROLL_NEXT; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[0] = fido_blob_encode(&t->id)) == NULL || + (argv[2] = cbor_build_uint(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK || + (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +int +fido_bio_dev_enroll_continue(fido_dev_t *dev, const fido_bio_template_t *t, + fido_bio_enroll_t *e, uint32_t timo_ms) +{ + int ms = dev->timeout_ms; + + if (e->token == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_enroll_continue_wait(dev, t, e, timo_ms, &ms)); +} + +static int +bio_enroll_cancel_wait(fido_dev_t *dev, int *ms) +{ + const uint8_t cmd = CMD_ENROLL_CANCEL; + int r; + + if ((r = bio_tx(dev, cmd, NULL, 0, NULL, NULL, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + return (r); + } + + return (FIDO_OK); +} + +int +fido_bio_dev_enroll_cancel(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + + return (bio_enroll_cancel_wait(dev, &ms)); +} + +static int +bio_enroll_remove_wait(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin, int *ms) +{ + cbor_item_t *argv[1]; + const uint8_t cmd = CMD_ENROLL_REMOVE; + int r = FIDO_ERR_INTERNAL; + + memset(&argv, 0, sizeof(argv)); + + if ((argv[0] = fido_blob_encode(&t->id)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 1, pin, NULL, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + + return (r); +} + +int +fido_bio_dev_enroll_remove(fido_dev_t *dev, const fido_bio_template_t *t, + const char *pin) +{ + int ms = dev->timeout_ms; + + return (bio_enroll_remove_wait(dev, t, pin, &ms)); +} + +static void +bio_reset_info(fido_bio_info_t *i) +{ + i->type = 0; + i->max_samples = 0; +} + +static int +bio_parse_info(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_bio_info_t *i = arg; + uint64_t x; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 2: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + i->type = (uint8_t)x; + break; + case 3: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + i->max_samples = (uint8_t)x; + break; + default: + return (0); /* ignore */ + } + + return (0); +} + +static int +bio_rx_info(fido_dev_t *dev, fido_bio_info_t *i, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_info(i); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, i, + bio_parse_info)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_info" , __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +bio_get_info_wait(fido_dev_t *dev, fido_bio_info_t *i, int *ms) +{ + int r; + + if ((r = bio_tx(dev, CMD_GET_INFO, NULL, 0, NULL, NULL, + ms)) != FIDO_OK || + (r = bio_rx_info(dev, i, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + return (r); + } + + return (FIDO_OK); +} + +int +fido_bio_dev_get_info(fido_dev_t *dev, fido_bio_info_t *i) +{ + int ms = dev->timeout_ms; + + return (bio_get_info_wait(dev, i, &ms)); +} + +const char * +fido_bio_template_name(const fido_bio_template_t *t) +{ + return (t->name); +} + +const unsigned char * +fido_bio_template_id_ptr(const fido_bio_template_t *t) +{ + return (t->id.ptr); +} + +size_t +fido_bio_template_id_len(const fido_bio_template_t *t) +{ + return (t->id.len); +} + +size_t +fido_bio_template_array_count(const fido_bio_template_array_t *ta) +{ + return (ta->n_rx); +} + +fido_bio_template_array_t * +fido_bio_template_array_new(void) +{ + return (calloc(1, sizeof(fido_bio_template_array_t))); +} + +fido_bio_template_t * +fido_bio_template_new(void) +{ + return (calloc(1, sizeof(fido_bio_template_t))); +} + +void +fido_bio_template_array_free(fido_bio_template_array_t **tap) +{ + fido_bio_template_array_t *ta; + + if (tap == NULL || (ta = *tap) == NULL) + return; + + bio_reset_template_array(ta); + free(ta); + *tap = NULL; +} + +void +fido_bio_template_free(fido_bio_template_t **tp) +{ + fido_bio_template_t *t; + + if (tp == NULL || (t = *tp) == NULL) + return; + + bio_reset_template(t); + free(t); + *tp = NULL; +} + +int +fido_bio_template_set_name(fido_bio_template_t *t, const char *name) +{ + free(t->name); + t->name = NULL; + + if (name && (t->name = strdup(name)) == NULL) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +int +fido_bio_template_set_id(fido_bio_template_t *t, const unsigned char *ptr, + size_t len) +{ + fido_blob_reset(&t->id); + + if (ptr && fido_blob_set(&t->id, ptr, len) < 0) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +const fido_bio_template_t * +fido_bio_template(const fido_bio_template_array_t *ta, size_t idx) +{ + if (idx >= ta->n_alloc) + return (NULL); + + return (&ta->ptr[idx]); +} + +fido_bio_enroll_t * +fido_bio_enroll_new(void) +{ + return (calloc(1, sizeof(fido_bio_enroll_t))); +} + +fido_bio_info_t * +fido_bio_info_new(void) +{ + return (calloc(1, sizeof(fido_bio_info_t))); +} + +uint8_t +fido_bio_info_type(const fido_bio_info_t *i) +{ + return (i->type); +} + +uint8_t +fido_bio_info_max_samples(const fido_bio_info_t *i) +{ + return (i->max_samples); +} + +void +fido_bio_enroll_free(fido_bio_enroll_t **ep) +{ + fido_bio_enroll_t *e; + + if (ep == NULL || (e = *ep) == NULL) + return; + + bio_reset_enroll(e); + + free(e); + *ep = NULL; +} + +void +fido_bio_info_free(fido_bio_info_t **ip) +{ + fido_bio_info_t *i; + + if (ip == NULL || (i = *ip) == NULL) + return; + + free(i); + *ip = NULL; +} + +uint8_t +fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *e) +{ + return (e->remaining_samples); +} + +uint8_t +fido_bio_enroll_last_status(const fido_bio_enroll_t *e) +{ + return (e->last_status); +} diff --git a/src/blob.c b/src/blob.c new file mode 100644 index 0000000..b431f49 --- /dev/null +++ b/src/blob.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +fido_blob_t * +fido_blob_new(void) +{ + return calloc(1, sizeof(fido_blob_t)); +} + +void +fido_blob_reset(fido_blob_t *b) +{ + freezero(b->ptr, b->len); + explicit_bzero(b, sizeof(*b)); +} + +int +fido_blob_set(fido_blob_t *b, const u_char *ptr, size_t len) +{ + fido_blob_reset(b); + + if (ptr == NULL || len == 0) { + fido_log_debug("%s: ptr=%p, len=%zu", __func__, + (const void *)ptr, len); + return -1; + } + + if ((b->ptr = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + return -1; + } + + memcpy(b->ptr, ptr, len); + b->len = len; + + return 0; +} + +int +fido_blob_append(fido_blob_t *b, const u_char *ptr, size_t len) +{ + u_char *tmp; + + if (ptr == NULL || len == 0) { + fido_log_debug("%s: ptr=%p, len=%zu", __func__, + (const void *)ptr, len); + return -1; + } + if (SIZE_MAX - b->len < len) { + fido_log_debug("%s: overflow", __func__); + return -1; + } + if ((tmp = realloc(b->ptr, b->len + len)) == NULL) { + fido_log_debug("%s: realloc", __func__); + return -1; + } + b->ptr = tmp; + memcpy(&b->ptr[b->len], ptr, len); + b->len += len; + + return 0; +} + +void +fido_blob_free(fido_blob_t **bp) +{ + fido_blob_t *b; + + if (bp == NULL || (b = *bp) == NULL) + return; + + fido_blob_reset(b); + free(b); + *bp = NULL; +} + +void +fido_free_blob_array(fido_blob_array_t *array) +{ + if (array->ptr == NULL) + return; + + for (size_t i = 0; i < array->len; i++) { + fido_blob_t *b = &array->ptr[i]; + freezero(b->ptr, b->len); + b->ptr = NULL; + } + + free(array->ptr); + array->ptr = NULL; + array->len = 0; +} + +cbor_item_t * +fido_blob_encode(const fido_blob_t *b) +{ + if (b == NULL || b->ptr == NULL) + return NULL; + + return cbor_build_bytestring(b->ptr, b->len); +} + +int +fido_blob_decode(const cbor_item_t *item, fido_blob_t *b) +{ + return cbor_bytestring_copy(item, &b->ptr, &b->len); +} + +int +fido_blob_is_empty(const fido_blob_t *b) +{ + return b->ptr == NULL || b->len == 0; +} + +int +fido_blob_serialise(fido_blob_t *b, const cbor_item_t *item) +{ + size_t alloc; + + if (!fido_blob_is_empty(b)) + return -1; + if ((b->len = cbor_serialize_alloc(item, &b->ptr, &alloc)) == 0) { + b->ptr = NULL; + return -1; + } + + return 0; +} diff --git a/src/blob.h b/src/blob.h new file mode 100644 index 0000000..7247185 --- /dev/null +++ b/src/blob.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _BLOB_H +#define _BLOB_H + +#include <cbor.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct fido_blob { + unsigned char *ptr; + size_t len; +} fido_blob_t; + +typedef struct fido_blob_array { + fido_blob_t *ptr; + size_t len; +} fido_blob_array_t; + +cbor_item_t *fido_blob_encode(const fido_blob_t *); +fido_blob_t *fido_blob_new(void); +int fido_blob_decode(const cbor_item_t *, fido_blob_t *); +int fido_blob_is_empty(const fido_blob_t *); +int fido_blob_set(fido_blob_t *, const u_char *, size_t); +int fido_blob_append(fido_blob_t *, const u_char *, size_t); +void fido_blob_free(fido_blob_t **); +void fido_blob_reset(fido_blob_t *); +void fido_free_blob_array(fido_blob_array_t *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_BLOB_H */ diff --git a/src/buf.c b/src/buf.c new file mode 100644 index 0000000..42b6df1 --- /dev/null +++ b/src/buf.c @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +int +fido_buf_read(const unsigned char **buf, size_t *len, void *dst, size_t count) +{ + if (count > *len) + return (-1); + + memcpy(dst, *buf, count); + *buf += count; + *len -= count; + + return (0); +} + +int +fido_buf_write(unsigned char **buf, size_t *len, const void *src, size_t count) +{ + if (count > *len) + return (-1); + + memcpy(*buf, src, count); + *buf += count; + *len -= count; + + return (0); +} diff --git a/src/cbor.c b/src/cbor.c new file mode 100644 index 0000000..ab99b34 --- /dev/null +++ b/src/cbor.c @@ -0,0 +1,1705 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/hmac.h> +#include <openssl/sha.h> +#include "fido.h" + +static int +check_key_type(cbor_item_t *item) +{ + if (item->type == CBOR_TYPE_UINT || item->type == CBOR_TYPE_NEGINT || + item->type == CBOR_TYPE_STRING) + return (0); + + fido_log_debug("%s: invalid type: %d", __func__, item->type); + + return (-1); +} + +/* + * Validate CTAP2 canonical CBOR encoding rules for maps. + */ +static int +ctap_check_cbor(cbor_item_t *prev, cbor_item_t *curr) +{ + size_t curr_len; + size_t prev_len; + + if (check_key_type(prev) < 0 || check_key_type(curr) < 0) + return (-1); + + if (prev->type != curr->type) { + if (prev->type < curr->type) + return (0); + fido_log_debug("%s: unsorted types", __func__); + return (-1); + } + + if (curr->type == CBOR_TYPE_UINT || curr->type == CBOR_TYPE_NEGINT) { + if (cbor_int_get_width(curr) >= cbor_int_get_width(prev) && + cbor_get_int(curr) > cbor_get_int(prev)) + return (0); + } else { + curr_len = cbor_string_length(curr); + prev_len = cbor_string_length(prev); + + if (curr_len > prev_len || (curr_len == prev_len && + memcmp(cbor_string_handle(prev), cbor_string_handle(curr), + curr_len) < 0)) + return (0); + } + + fido_log_debug("%s: invalid cbor", __func__); + + return (-1); +} + +int +cbor_map_iter(const cbor_item_t *item, void *arg, int(*f)(const cbor_item_t *, + const cbor_item_t *, void *)) +{ + struct cbor_pair *v; + size_t n; + + if ((v = cbor_map_handle(item)) == NULL) { + fido_log_debug("%s: cbor_map_handle", __func__); + return (-1); + } + + n = cbor_map_size(item); + + for (size_t i = 0; i < n; i++) { + if (v[i].key == NULL || v[i].value == NULL) { + fido_log_debug("%s: key=%p, value=%p for i=%zu", + __func__, (void *)v[i].key, (void *)v[i].value, i); + return (-1); + } + if (i && ctap_check_cbor(v[i - 1].key, v[i].key) < 0) { + fido_log_debug("%s: ctap_check_cbor", __func__); + return (-1); + } + if (f(v[i].key, v[i].value, arg) < 0) { + fido_log_debug("%s: iterator < 0 on i=%zu", __func__, + i); + return (-1); + } + } + + return (0); +} + +int +cbor_array_iter(const cbor_item_t *item, void *arg, int(*f)(const cbor_item_t *, + void *)) +{ + cbor_item_t **v; + size_t n; + + if ((v = cbor_array_handle(item)) == NULL) { + fido_log_debug("%s: cbor_array_handle", __func__); + return (-1); + } + + n = cbor_array_size(item); + + for (size_t i = 0; i < n; i++) + if (v[i] == NULL || f(v[i], arg) < 0) { + fido_log_debug("%s: iterator < 0 on i=%zu,%p", + __func__, i, (void *)v[i]); + return (-1); + } + + return (0); +} + +int +cbor_parse_reply(const unsigned char *blob, size_t blob_len, void *arg, + int(*parser)(const cbor_item_t *, const cbor_item_t *, void *)) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int r; + + if (blob_len < 1) { + fido_log_debug("%s: blob_len=%zu", __func__, blob_len); + r = FIDO_ERR_RX; + goto fail; + } + + if (blob[0] != FIDO_OK) { + fido_log_debug("%s: blob[0]=0x%02x", __func__, blob[0]); + r = blob[0]; + goto fail; + } + + if ((item = cbor_load(blob + 1, blob_len - 1, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + r = FIDO_ERR_RX_NOT_CBOR; + goto fail; + } + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + r = FIDO_ERR_RX_INVALID_CBOR; + goto fail; + } + + if (cbor_map_iter(item, arg, parser) < 0) { + fido_log_debug("%s: cbor_map_iter", __func__); + r = FIDO_ERR_RX_INVALID_CBOR; + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + return (r); +} + +void +cbor_vector_free(cbor_item_t **item, size_t len) +{ + for (size_t i = 0; i < len; i++) + if (item[i] != NULL) + cbor_decref(&item[i]); +} + +int +cbor_bytestring_copy(const cbor_item_t *item, unsigned char **buf, size_t *len) +{ + if (*buf != NULL || *len != 0) { + fido_log_debug("%s: dup", __func__); + return (-1); + } + + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + *len = cbor_bytestring_length(item); + if ((*buf = malloc(*len)) == NULL) { + *len = 0; + return (-1); + } + + memcpy(*buf, cbor_bytestring_handle(item), *len); + + return (0); +} + +int +cbor_string_copy(const cbor_item_t *item, char **str) +{ + size_t len; + + if (*str != NULL) { + fido_log_debug("%s: dup", __func__); + return (-1); + } + + if (cbor_isa_string(item) == false || + cbor_string_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if ((len = cbor_string_length(item)) == SIZE_MAX || + (*str = malloc(len + 1)) == NULL) + return (-1); + + memcpy(*str, cbor_string_handle(item), len); + (*str)[len] = '\0'; + + return (0); +} + +int +cbor_add_bytestring(cbor_item_t *item, const char *key, + const unsigned char *value, size_t value_len) +{ + struct cbor_pair pair; + int ok = -1; + + memset(&pair, 0, sizeof(pair)); + + if ((pair.key = cbor_build_string(key)) == NULL || + (pair.value = cbor_build_bytestring(value, value_len)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_log_debug("%s: cbor_map_add", __func__); + goto fail; + } + + ok = 0; +fail: + if (pair.key) + cbor_decref(&pair.key); + if (pair.value) + cbor_decref(&pair.value); + + return (ok); +} + +int +cbor_add_string(cbor_item_t *item, const char *key, const char *value) +{ + struct cbor_pair pair; + int ok = -1; + + memset(&pair, 0, sizeof(pair)); + + if ((pair.key = cbor_build_string(key)) == NULL || + (pair.value = cbor_build_string(value)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_log_debug("%s: cbor_map_add", __func__); + goto fail; + } + + ok = 0; +fail: + if (pair.key) + cbor_decref(&pair.key); + if (pair.value) + cbor_decref(&pair.value); + + return (ok); +} + +int +cbor_add_bool(cbor_item_t *item, const char *key, fido_opt_t value) +{ + struct cbor_pair pair; + int ok = -1; + + memset(&pair, 0, sizeof(pair)); + + if ((pair.key = cbor_build_string(key)) == NULL || + (pair.value = cbor_build_bool(value == FIDO_OPT_TRUE)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_log_debug("%s: cbor_map_add", __func__); + goto fail; + } + + ok = 0; +fail: + if (pair.key) + cbor_decref(&pair.key); + if (pair.value) + cbor_decref(&pair.value); + + return (ok); +} + +static int +cbor_add_uint8(cbor_item_t *item, const char *key, uint8_t value) +{ + struct cbor_pair pair; + int ok = -1; + + memset(&pair, 0, sizeof(pair)); + + if ((pair.key = cbor_build_string(key)) == NULL || + (pair.value = cbor_build_uint8(value)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_log_debug("%s: cbor_map_add", __func__); + goto fail; + } + + ok = 0; +fail: + if (pair.key) + cbor_decref(&pair.key); + if (pair.value) + cbor_decref(&pair.value); + + return (ok); +} + +static int +cbor_add_arg(cbor_item_t *item, uint8_t n, cbor_item_t *arg) +{ + struct cbor_pair pair; + int ok = -1; + + memset(&pair, 0, sizeof(pair)); + + if (arg == NULL) + return (0); /* empty argument */ + + if ((pair.key = cbor_build_uint8(n)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + pair.value = arg; + + if (!cbor_map_add(item, pair)) { + fido_log_debug("%s: cbor_map_add", __func__); + goto fail; + } + + ok = 0; +fail: + if (pair.key) + cbor_decref(&pair.key); + + return (ok); +} + +cbor_item_t * +cbor_flatten_vector(cbor_item_t *argv[], size_t argc) +{ + cbor_item_t *map; + uint8_t i; + + if (argc > UINT8_MAX - 1) + return (NULL); + + if ((map = cbor_new_definite_map(argc)) == NULL) + return (NULL); + + for (i = 0; i < argc; i++) + if (cbor_add_arg(map, (uint8_t)(i + 1), argv[i]) < 0) + break; + + if (i != argc) { + cbor_decref(&map); + map = NULL; + } + + return (map); +} + +int +cbor_build_frame(uint8_t cmd, cbor_item_t *argv[], size_t argc, fido_blob_t *f) +{ + cbor_item_t *flat = NULL; + unsigned char *cbor = NULL; + size_t cbor_len; + size_t cbor_alloc_len; + int ok = -1; + + if ((flat = cbor_flatten_vector(argv, argc)) == NULL) + goto fail; + + cbor_len = cbor_serialize_alloc(flat, &cbor, &cbor_alloc_len); + if (cbor_len == 0 || cbor_len == SIZE_MAX) { + fido_log_debug("%s: cbor_len=%zu", __func__, cbor_len); + goto fail; + } + + if ((f->ptr = malloc(cbor_len + 1)) == NULL) + goto fail; + + f->len = cbor_len + 1; + f->ptr[0] = cmd; + memcpy(f->ptr + 1, cbor, f->len - 1); + + ok = 0; +fail: + if (flat != NULL) + cbor_decref(&flat); + + free(cbor); + + return (ok); +} + +cbor_item_t * +cbor_encode_rp_entity(const fido_rp_t *rp) +{ + cbor_item_t *item = NULL; + + if ((item = cbor_new_definite_map(2)) == NULL) + return (NULL); + + if ((rp->id && cbor_add_string(item, "id", rp->id) < 0) || + (rp->name && cbor_add_string(item, "name", rp->name) < 0)) { + cbor_decref(&item); + return (NULL); + } + + return (item); +} + +cbor_item_t * +cbor_encode_user_entity(const fido_user_t *user) +{ + cbor_item_t *item = NULL; + const fido_blob_t *id = &user->id; + const char *display = user->display_name; + + if ((item = cbor_new_definite_map(4)) == NULL) + return (NULL); + + if ((id->ptr && cbor_add_bytestring(item, "id", id->ptr, id->len) < 0) || + (user->icon && cbor_add_string(item, "icon", user->icon) < 0) || + (user->name && cbor_add_string(item, "name", user->name) < 0) || + (display && cbor_add_string(item, "displayName", display) < 0)) { + cbor_decref(&item); + return (NULL); + } + + return (item); +} + +cbor_item_t * +cbor_encode_pubkey_param(int cose_alg) +{ + cbor_item_t *item = NULL; + cbor_item_t *body = NULL; + struct cbor_pair alg; + int ok = -1; + + memset(&alg, 0, sizeof(alg)); + + if ((item = cbor_new_definite_array(1)) == NULL || + (body = cbor_new_definite_map(2)) == NULL || + cose_alg > -1 || cose_alg < INT16_MIN) + goto fail; + + alg.key = cbor_build_string("alg"); + + if (-cose_alg - 1 > UINT8_MAX) + alg.value = cbor_build_negint16((uint16_t)(-cose_alg - 1)); + else + alg.value = cbor_build_negint8((uint8_t)(-cose_alg - 1)); + + if (alg.key == NULL || alg.value == NULL) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (cbor_map_add(body, alg) == false || + cbor_add_string(body, "type", "public-key") < 0 || + cbor_array_push(item, body) == false) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + if (item != NULL) { + cbor_decref(&item); + item = NULL; + } + } + + if (body != NULL) + cbor_decref(&body); + if (alg.key != NULL) + cbor_decref(&alg.key); + if (alg.value != NULL) + cbor_decref(&alg.value); + + return (item); +} + +cbor_item_t * +cbor_encode_pubkey(const fido_blob_t *pubkey) +{ + cbor_item_t *cbor_key = NULL; + + if ((cbor_key = cbor_new_definite_map(2)) == NULL || + cbor_add_bytestring(cbor_key, "id", pubkey->ptr, pubkey->len) < 0 || + cbor_add_string(cbor_key, "type", "public-key") < 0) { + if (cbor_key) + cbor_decref(&cbor_key); + return (NULL); + } + + return (cbor_key); +} + +cbor_item_t * +cbor_encode_pubkey_list(const fido_blob_array_t *list) +{ + cbor_item_t *array = NULL; + cbor_item_t *key = NULL; + + if ((array = cbor_new_definite_array(list->len)) == NULL) + goto fail; + + for (size_t i = 0; i < list->len; i++) { + if ((key = cbor_encode_pubkey(&list->ptr[i])) == NULL || + cbor_array_push(array, key) == false) + goto fail; + cbor_decref(&key); + } + + return (array); +fail: + if (key != NULL) + cbor_decref(&key); + if (array != NULL) + cbor_decref(&array); + + return (NULL); +} + +cbor_item_t * +cbor_encode_str_array(const fido_str_array_t *a) +{ + cbor_item_t *array = NULL; + cbor_item_t *entry = NULL; + + if ((array = cbor_new_definite_array(a->len)) == NULL) + goto fail; + + for (size_t i = 0; i < a->len; i++) { + if ((entry = cbor_build_string(a->ptr[i])) == NULL || + cbor_array_push(array, entry) == false) + goto fail; + cbor_decref(&entry); + } + + return (array); +fail: + if (entry != NULL) + cbor_decref(&entry); + if (array != NULL) + cbor_decref(&array); + + return (NULL); +} + +static int +cbor_encode_largeblob_key_ext(cbor_item_t *map) +{ + if (map == NULL || + cbor_add_bool(map, "largeBlobKey", FIDO_OPT_TRUE) < 0) + return (-1); + + return (0); +} + +cbor_item_t * +cbor_encode_cred_ext(const fido_cred_ext_t *ext, const fido_blob_t *blob) +{ + cbor_item_t *item = NULL; + size_t size = 0; + + if (ext->mask & FIDO_EXT_CRED_BLOB) + size++; + if (ext->mask & FIDO_EXT_HMAC_SECRET) + size++; + if (ext->mask & FIDO_EXT_CRED_PROTECT) + size++; + if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) + size++; + if (ext->mask & FIDO_EXT_MINPINLEN) + size++; + + if (size == 0 || (item = cbor_new_definite_map(size)) == NULL) + return (NULL); + + if (ext->mask & FIDO_EXT_CRED_BLOB) { + if (cbor_add_bytestring(item, "credBlob", blob->ptr, + blob->len) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_CRED_PROTECT) { + if (ext->prot < 0 || ext->prot > UINT8_MAX || + cbor_add_uint8(item, "credProtect", + (uint8_t)ext->prot) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_HMAC_SECRET) { + if (cbor_add_bool(item, "hmac-secret", FIDO_OPT_TRUE) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) { + if (cbor_encode_largeblob_key_ext(item) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_MINPINLEN) { + if (cbor_add_bool(item, "minPinLength", FIDO_OPT_TRUE) < 0) { + cbor_decref(&item); + return (NULL); + } + } + + return (item); +} + +cbor_item_t * +cbor_encode_cred_opt(fido_opt_t rk, fido_opt_t uv) +{ + cbor_item_t *item = NULL; + + if ((item = cbor_new_definite_map(2)) == NULL) + return (NULL); + if ((rk != FIDO_OPT_OMIT && cbor_add_bool(item, "rk", rk) < 0) || + (uv != FIDO_OPT_OMIT && cbor_add_bool(item, "uv", uv) < 0)) { + cbor_decref(&item); + return (NULL); + } + + return (item); +} + +cbor_item_t * +cbor_encode_assert_opt(fido_opt_t up, fido_opt_t uv) +{ + cbor_item_t *item = NULL; + + if ((item = cbor_new_definite_map(2)) == NULL) + return (NULL); + if ((up != FIDO_OPT_OMIT && cbor_add_bool(item, "up", up) < 0) || + (uv != FIDO_OPT_OMIT && cbor_add_bool(item, "uv", uv) < 0)) { + cbor_decref(&item); + return (NULL); + } + + return (item); +} + +cbor_item_t * +cbor_encode_pin_auth(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *data) +{ + const EVP_MD *md = NULL; + unsigned char dgst[SHA256_DIGEST_LENGTH]; + unsigned int dgst_len; + size_t outlen; + uint8_t prot; + fido_blob_t key; + + key.ptr = secret->ptr; + key.len = secret->len; + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + return (NULL); + } + + /* select hmac portion of the shared secret */ + if (prot == CTAP_PIN_PROTOCOL2 && key.len > 32) + key.len = 32; + + if ((md = EVP_sha256()) == NULL || HMAC(md, key.ptr, + (int)key.len, data->ptr, data->len, dgst, + &dgst_len) == NULL || dgst_len != SHA256_DIGEST_LENGTH) + return (NULL); + + outlen = (prot == CTAP_PIN_PROTOCOL1) ? 16 : dgst_len; + + return (cbor_build_bytestring(dgst, outlen)); +} + +cbor_item_t * +cbor_encode_pin_opt(const fido_dev_t *dev) +{ + uint8_t prot; + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + return (NULL); + } + + return (cbor_build_uint8(prot)); +} + +cbor_item_t * +cbor_encode_change_pin_auth(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *new_pin_enc, const fido_blob_t *pin_hash_enc) +{ + unsigned char dgst[SHA256_DIGEST_LENGTH]; + unsigned int dgst_len; + cbor_item_t *item = NULL; + const EVP_MD *md = NULL; + HMAC_CTX *ctx = NULL; + fido_blob_t key; + uint8_t prot; + size_t outlen; + + key.ptr = secret->ptr; + key.len = secret->len; + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + goto fail; + } + + if (prot == CTAP_PIN_PROTOCOL2 && key.len > 32) + key.len = 32; + + if ((ctx = HMAC_CTX_new()) == NULL || + (md = EVP_sha256()) == NULL || + HMAC_Init_ex(ctx, key.ptr, (int)key.len, md, NULL) == 0 || + HMAC_Update(ctx, new_pin_enc->ptr, new_pin_enc->len) == 0 || + HMAC_Update(ctx, pin_hash_enc->ptr, pin_hash_enc->len) == 0 || + HMAC_Final(ctx, dgst, &dgst_len) == 0 || + dgst_len != SHA256_DIGEST_LENGTH) { + fido_log_debug("%s: HMAC", __func__); + goto fail; + } + + outlen = (prot == CTAP_PIN_PROTOCOL1) ? 16 : dgst_len; + + if ((item = cbor_build_bytestring(dgst, outlen)) == NULL) { + fido_log_debug("%s: cbor_build_bytestring", __func__); + goto fail; + } + +fail: + HMAC_CTX_free(ctx); + + return (item); +} + +static int +cbor_encode_hmac_secret_param(const fido_dev_t *dev, cbor_item_t *item, + const fido_blob_t *ecdh, const es256_pk_t *pk, const fido_blob_t *salt) +{ + cbor_item_t *param = NULL; + cbor_item_t *argv[4]; + struct cbor_pair pair; + fido_blob_t *enc = NULL; + uint8_t prot; + int r; + + memset(argv, 0, sizeof(argv)); + memset(&pair, 0, sizeof(pair)); + + if (item == NULL || ecdh == NULL || pk == NULL || salt->ptr == NULL) { + fido_log_debug("%s: ecdh=%p, pk=%p, salt->ptr=%p", __func__, + (const void *)ecdh, (const void *)pk, + (const void *)salt->ptr); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (salt->len != 32 && salt->len != 64) { + fido_log_debug("%s: salt->len=%zu", __func__, salt->len); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((enc = fido_blob_new()) == NULL || + aes256_cbc_enc(dev, ecdh, salt, enc) < 0) { + fido_log_debug("%s: aes256_cbc_enc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* XXX not pin, but salt */ + if ((argv[0] = es256_pk_encode(pk, 1)) == NULL || + (argv[1] = fido_blob_encode(enc)) == NULL || + (argv[2] = cbor_encode_pin_auth(dev, ecdh, enc)) == NULL || + (prot != 1 && (argv[3] = cbor_build_uint8(prot)) == NULL)) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((param = cbor_flatten_vector(argv, nitems(argv))) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((pair.key = cbor_build_string("hmac-secret")) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + pair.value = param; + + if (!cbor_map_add(item, pair)) { + fido_log_debug("%s: cbor_map_add", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; + +fail: + cbor_vector_free(argv, nitems(argv)); + + if (param != NULL) + cbor_decref(¶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") && strcmp(type, "tpm")) { + fido_log_debug("%s: type=%s", __func__, type); + free(type); + return (-1); + } + + *fmt = type; + + return (0); +} + +struct cose_key { + int kty; + int alg; + int crv; +}; + +static int +find_cose_alg(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + struct cose_key *cose_key = arg; + + if (cbor_isa_uint(key) == true && + cbor_int_get_width(key) == CBOR_INT_8) { + switch (cbor_get_uint8(key)) { + case 1: + if (cbor_isa_uint(val) == false || + cbor_get_int(val) > INT_MAX || cose_key->kty != 0) { + fido_log_debug("%s: kty", __func__); + return (-1); + } + + cose_key->kty = (int)cbor_get_int(val); + + break; + case 3: + if (cbor_isa_negint(val) == false || + cbor_get_int(val) > INT_MAX || cose_key->alg != 0) { + fido_log_debug("%s: alg", __func__); + return (-1); + } + + cose_key->alg = -(int)cbor_get_int(val) - 1; + + break; + } + } else if (cbor_isa_negint(key) == true && + cbor_int_get_width(key) == CBOR_INT_8) { + if (cbor_get_uint8(key) == 0) { + /* get crv if not rsa, otherwise ignore */ + if (cbor_isa_uint(val) == true && + cbor_get_int(val) <= INT_MAX && + cose_key->crv == 0) + cose_key->crv = (int)cbor_get_int(val); + } + } + + return (0); +} + +static int +get_cose_alg(const cbor_item_t *item, int *cose_alg) +{ + struct cose_key cose_key; + + memset(&cose_key, 0, sizeof(cose_key)); + + *cose_alg = 0; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, &cose_key, find_cose_alg) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + switch (cose_key.alg) { + case COSE_ES256: + if (cose_key.kty != COSE_KTY_EC2 || + cose_key.crv != COSE_P256) { + fido_log_debug("%s: invalid kty/crv", __func__); + return (-1); + } + break; + case COSE_ES384: + if (cose_key.kty != COSE_KTY_EC2 || + cose_key.crv != COSE_P384) { + fido_log_debug("%s: invalid kty/crv", __func__); + return (-1); + } + break; + case COSE_EDDSA: + if (cose_key.kty != COSE_KTY_OKP || + cose_key.crv != COSE_ED25519) { + fido_log_debug("%s: invalid kty/crv", __func__); + return (-1); + } + break; + case COSE_RS256: + if (cose_key.kty != COSE_KTY_RSA) { + fido_log_debug("%s: invalid kty/crv", __func__); + return (-1); + } + break; + default: + fido_log_debug("%s: unknown alg %d", __func__, cose_key.alg); + + return (-1); + } + + *cose_alg = cose_key.alg; + + return (0); +} + +int +cbor_decode_pubkey(const cbor_item_t *item, int *type, void *key) +{ + if (get_cose_alg(item, type) < 0) { + fido_log_debug("%s: get_cose_alg", __func__); + return (-1); + } + + switch (*type) { + case COSE_ES256: + if (es256_pk_decode(item, key) < 0) { + fido_log_debug("%s: es256_pk_decode", __func__); + return (-1); + } + break; + case COSE_ES384: + if (es384_pk_decode(item, key) < 0) { + fido_log_debug("%s: es384_pk_decode", __func__); + return (-1); + } + break; + case COSE_RS256: + if (rs256_pk_decode(item, key) < 0) { + fido_log_debug("%s: rs256_pk_decode", __func__); + return (-1); + } + break; + case COSE_EDDSA: + if (eddsa_pk_decode(item, key) < 0) { + fido_log_debug("%s: eddsa_pk_decode", __func__); + return (-1); + } + break; + default: + fido_log_debug("%s: invalid cose_alg %d", __func__, *type); + return (-1); + } + + return (0); +} + +static int +decode_attcred(const unsigned char **buf, size_t *len, int cose_alg, + fido_attcred_t *attcred) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + uint16_t id_len; + int ok = -1; + + fido_log_xxd(*buf, *len, "%s", __func__); + + if (fido_buf_read(buf, len, &attcred->aaguid, + sizeof(attcred->aaguid)) < 0) { + fido_log_debug("%s: fido_buf_read aaguid", __func__); + return (-1); + } + + if (fido_buf_read(buf, len, &id_len, sizeof(id_len)) < 0) { + fido_log_debug("%s: fido_buf_read id_len", __func__); + return (-1); + } + + attcred->id.len = (size_t)be16toh(id_len); + if ((attcred->id.ptr = malloc(attcred->id.len)) == NULL) + return (-1); + + fido_log_debug("%s: attcred->id.len=%zu", __func__, attcred->id.len); + + if (fido_buf_read(buf, len, attcred->id.ptr, attcred->id.len) < 0) { + fido_log_debug("%s: fido_buf_read id", __func__); + return (-1); + } + + if ((item = cbor_load(*buf, *len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_decode_pubkey(item, &attcred->type, &attcred->pubkey) < 0) { + fido_log_debug("%s: cbor_decode_pubkey", __func__); + goto fail; + } + + if (attcred->type != cose_alg) { + fido_log_debug("%s: cose_alg mismatch (%d != %d)", __func__, + attcred->type, cose_alg); + goto fail; + } + + *buf += cbor.read; + *len -= cbor.read; + + ok = 0; +fail: + if (item != NULL) + cbor_decref(&item); + + return (ok); +} + +static int +decode_cred_extension(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_ext_t *authdata_ext = arg; + char *type = NULL; + int ok = -1; + + if (cbor_string_copy(key, &type) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (strcmp(type, "hmac-secret") == 0) { + if (cbor_decode_bool(val, NULL) < 0) { + fido_log_debug("%s: cbor_decode_bool", __func__); + goto out; + } + if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE) + authdata_ext->mask |= FIDO_EXT_HMAC_SECRET; + } else if (strcmp(type, "credProtect") == 0) { + if (cbor_isa_uint(val) == false || + cbor_int_get_width(val) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_CRED_PROTECT; + authdata_ext->prot = cbor_get_uint8(val); + } else if (strcmp(type, "credBlob") == 0) { + if (cbor_decode_bool(val, NULL) < 0) { + fido_log_debug("%s: cbor_decode_bool", __func__); + goto out; + } + if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE) + authdata_ext->mask |= FIDO_EXT_CRED_BLOB; + } else if (strcmp(type, "minPinLength") == 0) { + if (cbor_isa_uint(val) == false || + cbor_int_get_width(val) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_MINPINLEN; + authdata_ext->minpinlen = cbor_get_uint8(val); + } + + ok = 0; +out: + free(type); + + return (ok); +} + +static int +decode_cred_extensions(const unsigned char **buf, size_t *len, + fido_cred_ext_t *authdata_ext) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int ok = -1; + + memset(authdata_ext, 0, sizeof(*authdata_ext)); + + fido_log_xxd(*buf, *len, "%s", __func__); + + if ((item = cbor_load(*buf, *len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, authdata_ext, decode_cred_extension) < 0) { + fido_log_debug("%s: cbor type", __func__); + goto fail; + } + + *buf += cbor.read; + *len -= cbor.read; + + ok = 0; +fail: + if (item != NULL) + cbor_decref(&item); + + return (ok); +} + +static int +decode_assert_extension(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_assert_extattr_t *authdata_ext = arg; + char *type = NULL; + int ok = -1; + + if (cbor_string_copy(key, &type) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (strcmp(type, "hmac-secret") == 0) { + if (fido_blob_decode(val, &authdata_ext->hmac_secret_enc) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_HMAC_SECRET; + } else if (strcmp(type, "credBlob") == 0) { + if (fido_blob_decode(val, &authdata_ext->blob) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_CRED_BLOB; + } + + ok = 0; +out: + free(type); + + return (ok); +} + +static int +decode_assert_extensions(const unsigned char **buf, size_t *len, + fido_assert_extattr_t *authdata_ext) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int ok = -1; + + fido_log_xxd(*buf, *len, "%s", __func__); + + if ((item = cbor_load(*buf, *len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, authdata_ext, decode_assert_extension) < 0) { + fido_log_debug("%s: cbor type", __func__); + goto fail; + } + + *buf += cbor.read; + *len -= cbor.read; + + ok = 0; +fail: + if (item != NULL) + cbor_decref(&item); + + return (ok); +} + +int +cbor_decode_cred_authdata(const cbor_item_t *item, int cose_alg, + fido_blob_t *authdata_cbor, fido_authdata_t *authdata, + fido_attcred_t *attcred, fido_cred_ext_t *authdata_ext) +{ + const unsigned char *buf = NULL; + size_t len; + size_t alloc_len; + + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (authdata_cbor->ptr != NULL || + (authdata_cbor->len = cbor_serialize_alloc(item, + &authdata_cbor->ptr, &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + return (-1); + } + + buf = cbor_bytestring_handle(item); + len = cbor_bytestring_length(item); + fido_log_xxd(buf, len, "%s", __func__); + + if (fido_buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + return (-1); + } + + authdata->sigcount = be32toh(authdata->sigcount); + + if (attcred != NULL) { + if ((authdata->flags & CTAP_AUTHDATA_ATT_CRED) == 0 || + decode_attcred(&buf, &len, cose_alg, attcred) < 0) + return (-1); + } + + if (authdata_ext != NULL) { + if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0 && + decode_cred_extensions(&buf, &len, authdata_ext) < 0) + return (-1); + } + + /* XXX we should probably ensure that len == 0 at this point */ + + return (FIDO_OK); +} + +int +cbor_decode_assert_authdata(const cbor_item_t *item, fido_blob_t *authdata_cbor, + fido_authdata_t *authdata, fido_assert_extattr_t *authdata_ext) +{ + const unsigned char *buf = NULL; + size_t len; + size_t alloc_len; + + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (authdata_cbor->ptr != NULL || + (authdata_cbor->len = cbor_serialize_alloc(item, + &authdata_cbor->ptr, &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + return (-1); + } + + buf = cbor_bytestring_handle(item); + len = cbor_bytestring_length(item); + + fido_log_debug("%s: buf=%p, len=%zu", __func__, (const void *)buf, len); + + if (fido_buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + return (-1); + } + + authdata->sigcount = be32toh(authdata->sigcount); + + if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0) { + if (decode_assert_extensions(&buf, &len, authdata_ext) < 0) { + fido_log_debug("%s: decode_assert_extensions", + __func__); + return (-1); + } + } + + /* XXX we should probably ensure that len == 0 at this point */ + + return (FIDO_OK); +} + +static int +decode_x5c(const cbor_item_t *item, void *arg) +{ + fido_blob_t *x5c = arg; + + if (x5c->len) + return (0); /* ignore */ + + return (fido_blob_decode(item, x5c)); +} + +static int +decode_attstmt_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_attstmt_t *attstmt = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "alg")) { + if (cbor_isa_negint(val) == false || + cbor_get_int(val) > UINT16_MAX) { + fido_log_debug("%s: alg", __func__); + goto out; + } + attstmt->alg = -(int)cbor_get_int(val) - 1; + if (attstmt->alg != COSE_ES256 && attstmt->alg != COSE_ES384 && + attstmt->alg != COSE_RS256 && attstmt->alg != COSE_EDDSA && + attstmt->alg != COSE_RS1) { + fido_log_debug("%s: unsupported attstmt->alg=%d", + __func__, attstmt->alg); + goto out; + } + } else if (!strcmp(name, "sig")) { + if (fido_blob_decode(val, &attstmt->sig) < 0) { + fido_log_debug("%s: sig", __func__); + goto out; + } + } else if (!strcmp(name, "x5c")) { + if (cbor_isa_array(val) == false || + cbor_array_is_definite(val) == false || + cbor_array_iter(val, &attstmt->x5c, decode_x5c) < 0) { + fido_log_debug("%s: x5c", __func__); + goto out; + } + } else if (!strcmp(name, "certInfo")) { + if (fido_blob_decode(val, &attstmt->certinfo) < 0) { + fido_log_debug("%s: certinfo", __func__); + goto out; + } + } else if (!strcmp(name, "pubArea")) { + if (fido_blob_decode(val, &attstmt->pubarea) < 0) { + fido_log_debug("%s: pubarea", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_decode_attstmt(const cbor_item_t *item, fido_attstmt_t *attstmt) +{ + size_t alloc_len; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, attstmt, decode_attstmt_entry) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (attstmt->cbor.ptr != NULL || + (attstmt->cbor.len = cbor_serialize_alloc(item, + &attstmt->cbor.ptr, &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + return (-1); + } + + return (0); +} + +int +cbor_decode_uint64(const cbor_item_t *item, uint64_t *n) +{ + if (cbor_isa_uint(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + *n = cbor_get_int(item); + + return (0); +} + +static int +decode_cred_id_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_blob_t *id = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "id")) + if (fido_blob_decode(val, id) < 0) { + fido_log_debug("%s: cbor_bytestring_copy", __func__); + goto out; + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_decode_cred_id(const cbor_item_t *item, fido_blob_t *id) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, id, decode_cred_id_entry) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +static int +decode_user_entry(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_user_t *user = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "icon")) { + if (cbor_string_copy(val, &user->icon) < 0) { + fido_log_debug("%s: icon", __func__); + goto out; + } + } else if (!strcmp(name, "name")) { + if (cbor_string_copy(val, &user->name) < 0) { + fido_log_debug("%s: name", __func__); + goto out; + } + } else if (!strcmp(name, "displayName")) { + if (cbor_string_copy(val, &user->display_name) < 0) { + fido_log_debug("%s: display_name", __func__); + goto out; + } + } else if (!strcmp(name, "id")) { + if (fido_blob_decode(val, &user->id) < 0) { + fido_log_debug("%s: id", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_decode_user(const cbor_item_t *item, fido_user_t *user) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, user, decode_user_entry) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +static int +decode_rp_entity_entry(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_rp_t *rp = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "id")) { + if (cbor_string_copy(val, &rp->id) < 0) { + fido_log_debug("%s: id", __func__); + goto out; + } + } else if (!strcmp(name, "name")) { + if (cbor_string_copy(val, &rp->name) < 0) { + fido_log_debug("%s: name", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_decode_rp_entity(const cbor_item_t *item, fido_rp_t *rp) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, rp, decode_rp_entity_entry) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +int +cbor_decode_bool(const cbor_item_t *item, bool *v) +{ + if (cbor_isa_float_ctrl(item) == false || + cbor_float_get_width(item) != CBOR_FLOAT_0 || + cbor_is_bool(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (v != NULL) + *v = cbor_ctrl_value(item) == CBOR_CTRL_TRUE; + + return (0); +} + +cbor_item_t * +cbor_build_uint(const uint64_t value) +{ + if (value <= UINT8_MAX) + return cbor_build_uint8((uint8_t)value); + else if (value <= UINT16_MAX) + return cbor_build_uint16((uint16_t)value); + else if (value <= UINT32_MAX) + return cbor_build_uint32((uint32_t)value); + + return cbor_build_uint64(value); +} + +int +cbor_array_append(cbor_item_t **array, cbor_item_t *item) +{ + cbor_item_t **v, *ret; + size_t n; + + if ((v = cbor_array_handle(*array)) == NULL || + (n = cbor_array_size(*array)) == SIZE_MAX || + (ret = cbor_new_definite_array(n + 1)) == NULL) + return -1; + for (size_t i = 0; i < n; i++) { + if (cbor_array_push(ret, v[i]) == 0) { + cbor_decref(&ret); + return -1; + } + } + if (cbor_array_push(ret, item) == 0) { + cbor_decref(&ret); + return -1; + } + cbor_decref(array); + *array = ret; + + return 0; +} + +int +cbor_array_drop(cbor_item_t **array, size_t idx) +{ + cbor_item_t **v, *ret; + size_t n; + + if ((v = cbor_array_handle(*array)) == NULL || + (n = cbor_array_size(*array)) == 0 || idx >= n || + (ret = cbor_new_definite_array(n - 1)) == NULL) + return -1; + for (size_t i = 0; i < n; i++) { + if (i != idx && cbor_array_push(ret, v[i]) == 0) { + cbor_decref(&ret); + return -1; + } + } + cbor_decref(array); + *array = ret; + + return 0; +} diff --git a/src/compress.c b/src/compress.c new file mode 100644 index 0000000..3be6fd5 --- /dev/null +++ b/src/compress.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <zlib.h> +#include "fido.h" + +#define BOUND (1024UL * 1024UL) + +/* zlib inflate (raw + headers) */ +static int +rfc1950_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz) +{ + u_long ilen, olen; + int z; + + memset(out, 0, sizeof(*out)); + + if (in->len > ULONG_MAX || (ilen = (u_long)in->len) > BOUND || + origsiz > ULONG_MAX || (olen = (u_long)origsiz) > BOUND) { + fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__, + in->len, origsiz); + return FIDO_ERR_INVALID_ARGUMENT; + } + + if ((out->ptr = calloc(1, olen)) == NULL) + return FIDO_ERR_INTERNAL; + out->len = olen; + + if ((z = uncompress(out->ptr, &olen, in->ptr, ilen)) != Z_OK || + olen > SIZE_MAX || olen != out->len) { + fido_log_debug("%s: uncompress: %d, olen=%lu, out->len=%zu", + __func__, z, olen, out->len); + fido_blob_reset(out); + return FIDO_ERR_COMPRESS; + } + + return FIDO_OK; +} + +/* raw inflate */ +static int +rfc1951_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz) +{ + z_stream zs; + u_int ilen, olen; + int r, z; + + memset(&zs, 0, sizeof(zs)); + memset(out, 0, sizeof(*out)); + + if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND || + origsiz > UINT_MAX || (olen = (u_int)origsiz) > BOUND) { + fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__, + in->len, origsiz); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((z = inflateInit2(&zs, -MAX_WBITS)) != Z_OK) { + fido_log_debug("%s: inflateInit2: %d", __func__, z); + return FIDO_ERR_COMPRESS; + } + + if ((out->ptr = calloc(1, olen)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + out->len = olen; + zs.next_in = in->ptr; + zs.avail_in = ilen; + zs.next_out = out->ptr; + zs.avail_out = olen; + + if ((z = inflate(&zs, Z_FINISH)) != Z_STREAM_END) { + fido_log_debug("%s: inflate: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + goto fail; + } + if (zs.avail_out != 0) { + fido_log_debug("%s: %u != 0", __func__, zs.avail_out); + r = FIDO_ERR_COMPRESS; + goto fail; + } + + r = FIDO_OK; +fail: + if ((z = inflateEnd(&zs)) != Z_OK) { + fido_log_debug("%s: inflateEnd: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + } + if (r != FIDO_OK) + fido_blob_reset(out); + + return r; +} + +/* raw deflate */ +static int +rfc1951_deflate(fido_blob_t *out, const fido_blob_t *in) +{ + z_stream zs; + u_int ilen, olen; + int r, z; + + memset(&zs, 0, sizeof(zs)); + memset(out, 0, sizeof(*out)); + + if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND) { + fido_log_debug("%s: in->len=%zu", __func__, in->len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((z = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -MAX_WBITS, 8, Z_DEFAULT_STRATEGY)) != Z_OK) { + fido_log_debug("%s: deflateInit2: %d", __func__, z); + return FIDO_ERR_COMPRESS; + } + + olen = BOUND; + if ((out->ptr = calloc(1, olen)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + out->len = olen; + zs.next_in = in->ptr; + zs.avail_in = ilen; + zs.next_out = out->ptr; + zs.avail_out = olen; + + if ((z = deflate(&zs, Z_FINISH)) != Z_STREAM_END) { + fido_log_debug("%s: inflate: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + goto fail; + } + if (zs.avail_out >= out->len) { + fido_log_debug("%s: %u > %zu", __func__, zs.avail_out, + out->len); + r = FIDO_ERR_COMPRESS; + goto fail; + } + out->len -= zs.avail_out; + + r = FIDO_OK; +fail: + if ((z = deflateEnd(&zs)) != Z_OK) { + fido_log_debug("%s: deflateEnd: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + } + if (r != FIDO_OK) + fido_blob_reset(out); + + return r; +} + +int +fido_compress(fido_blob_t *out, const fido_blob_t *in) +{ + return rfc1951_deflate(out, in); +} + +int +fido_uncompress(fido_blob_t *out, const fido_blob_t *in, size_t origsiz) +{ + if (rfc1950_inflate(out, in, origsiz) == FIDO_OK) + return FIDO_OK; /* backwards compat with libfido2 < 1.11 */ + return rfc1951_inflate(out, in, origsiz); +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..5302e11 --- /dev/null +++ b/src/config.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" +#include "fido/config.h" +#include "fido/es256.h" + +#define CMD_ENABLE_ENTATTEST 0x01 +#define CMD_TOGGLE_ALWAYS_UV 0x02 +#define CMD_SET_PIN_MINLEN 0x03 + +static int +config_prepare_hmac(uint8_t subcmd, const cbor_item_t *item, fido_blob_t *hmac) +{ + uint8_t prefix[32 + 2 * sizeof(uint8_t)], cbor[128]; + size_t cbor_len = 0; + + memset(prefix, 0xff, sizeof(prefix)); + prefix[sizeof(prefix) - 2] = CTAP_CBOR_CONFIG; + prefix[sizeof(prefix) - 1] = subcmd; + + if (item != NULL) { + if ((cbor_len = cbor_serialize(item, cbor, sizeof(cbor))) == 0) { + fido_log_debug("%s: cbor_serialize", __func__); + return -1; + } + } + if ((hmac->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) { + fido_log_debug("%s: malloc", __func__); + return -1; + } + memcpy(hmac->ptr, prefix, sizeof(prefix)); + memcpy(hmac->ptr + sizeof(prefix), cbor, cbor_len); + hmac->len = cbor_len + sizeof(prefix); + + return 0; +} + +static int +config_tx(fido_dev_t *dev, uint8_t subcmd, cbor_item_t **paramv, size_t paramc, + const char *pin, int *ms) +{ + cbor_item_t *argv[4]; + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL, f, hmac; + const uint8_t cmd = CTAP_CBOR_CONFIG; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(&hmac, 0, sizeof(hmac)); + memset(&argv, 0, sizeof(argv)); + + /* subCommand */ + if ((argv[0] = cbor_build_uint8(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* subCommandParams */ + if (paramc != 0 && + (argv[1] = cbor_flatten_vector(paramv, paramc)) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + goto fail; + } + + /* pinProtocol, pinAuth */ + if (pin != NULL || + (fido_dev_supports_permissions(dev) && fido_dev_has_uv(dev))) { + if (config_prepare_hmac(subcmd, argv[1], &hmac) < 0) { + fido_log_debug("%s: config_prepare_hmac", __func__); + goto fail; + } + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + NULL, &argv[3], &argv[2], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + es256_pk_free(&pk); + fido_blob_free(&ecdh); + free(f.ptr); + free(hmac.ptr); + + return r; +} + +static int +config_enable_entattest_wait(fido_dev_t *dev, const char *pin, int *ms) +{ + int r; + + if ((r = config_tx(dev, CMD_ENABLE_ENTATTEST, NULL, 0, pin, + ms)) != FIDO_OK) + return r; + + return fido_rx_cbor_status(dev, ms); +} + +int +fido_dev_enable_entattest(fido_dev_t *dev, const char *pin) +{ + int ms = dev->timeout_ms; + + return (config_enable_entattest_wait(dev, pin, &ms)); +} + +static int +config_toggle_always_uv_wait(fido_dev_t *dev, const char *pin, int *ms) +{ + int r; + + if ((r = config_tx(dev, CMD_TOGGLE_ALWAYS_UV, NULL, 0, pin, + ms)) != FIDO_OK) + return r; + + return (fido_rx_cbor_status(dev, ms)); +} + +int +fido_dev_toggle_always_uv(fido_dev_t *dev, const char *pin) +{ + int ms = dev->timeout_ms; + + return config_toggle_always_uv_wait(dev, pin, &ms); +} + +static int +config_pin_minlen_tx(fido_dev_t *dev, size_t len, bool force, + const fido_str_array_t *rpid, const char *pin, int *ms) +{ + cbor_item_t *argv[3]; + int r; + + memset(argv, 0, sizeof(argv)); + + if ((rpid == NULL && len == 0 && !force) || len > UINT8_MAX) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if (len && (argv[0] = cbor_build_uint8((uint8_t)len)) == NULL) { + fido_log_debug("%s: cbor_encode_uint8", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (rpid != NULL && (argv[1] = cbor_encode_str_array(rpid)) == NULL) { + fido_log_debug("%s: cbor_encode_str_array", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (force && (argv[2] = cbor_build_bool(true)) == NULL) { + fido_log_debug("%s: cbor_build_bool", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = config_tx(dev, CMD_SET_PIN_MINLEN, argv, nitems(argv), + pin, ms)) != FIDO_OK) { + fido_log_debug("%s: config_tx", __func__); + goto fail; + } + +fail: + cbor_vector_free(argv, nitems(argv)); + + return r; +} + +static int +config_pin_minlen(fido_dev_t *dev, size_t len, bool force, + const fido_str_array_t *rpid, const char *pin, int *ms) +{ + int r; + + if ((r = config_pin_minlen_tx(dev, len, force, rpid, pin, + ms)) != FIDO_OK) + return r; + + return fido_rx_cbor_status(dev, ms); +} + +int +fido_dev_set_pin_minlen(fido_dev_t *dev, size_t len, const char *pin) +{ + int ms = dev->timeout_ms; + + return config_pin_minlen(dev, len, false, NULL, pin, &ms); +} + +int +fido_dev_force_pin_change(fido_dev_t *dev, const char *pin) +{ + int ms = dev->timeout_ms; + + return config_pin_minlen(dev, 0, true, NULL, pin, &ms); +} + +int +fido_dev_set_pin_minlen_rpid(fido_dev_t *dev, const char * const *rpid, + size_t n, const char *pin) +{ + fido_str_array_t sa; + int ms = dev->timeout_ms; + int r; + + memset(&sa, 0, sizeof(sa)); + if (fido_str_array_pack(&sa, rpid, n) < 0) { + fido_log_debug("%s: fido_str_array_pack", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + r = config_pin_minlen(dev, 0, false, &sa, pin, &ms); +fail: + fido_str_array_free(&sa); + + return r; +} diff --git a/src/cred.c b/src/cred.c new file mode 100644 index 0000000..4a7a725 --- /dev/null +++ b/src/cred.c @@ -0,0 +1,1230 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include <openssl/x509.h> + +#include "fido.h" +#include "fido/es256.h" + +#ifndef FIDO_MAXMSG_CRED +#define FIDO_MAXMSG_CRED 4096 +#endif + +static int +parse_makecred_reply(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_t *cred = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* fmt */ + return (cbor_decode_fmt(val, &cred->fmt)); + case 2: /* authdata */ + if (fido_blob_decode(val, &cred->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + return (-1); + } + return (cbor_decode_cred_authdata(val, cred->type, + &cred->authdata_cbor, &cred->authdata, &cred->attcred, + &cred->authdata_ext)); + case 3: /* attestation statement */ + return (cbor_decode_attstmt(val, &cred->attstmt)); + case 5: /* large blob key */ + return (fido_blob_decode(val, &cred->largeblob_key)); + default: /* ignore */ + fido_log_debug("%s: cbor type", __func__); + return (0); + } +} + +static int +fido_dev_make_cred_tx(fido_dev_t *dev, fido_cred_t *cred, const char *pin, + int *ms) +{ + fido_blob_t f; + fido_blob_t *ecdh = NULL; + fido_opt_t uv = cred->uv; + es256_pk_t *pk = NULL; + cbor_item_t *argv[9]; + const uint8_t cmd = CTAP_CBOR_MAKECRED; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if (cred->cdh.ptr == NULL || cred->type == 0) { + fido_log_debug("%s: cdh=%p, type=%d", __func__, + (void *)cred->cdh.ptr, cred->type); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if ((argv[0] = fido_blob_encode(&cred->cdh)) == NULL || + (argv[1] = cbor_encode_rp_entity(&cred->rp)) == NULL || + (argv[2] = cbor_encode_user_entity(&cred->user)) == NULL || + (argv[3] = cbor_encode_pubkey_param(cred->type)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* excluded credentials */ + if (cred->excl.len) + if ((argv[4] = cbor_encode_pubkey_list(&cred->excl)) == NULL) { + fido_log_debug("%s: cbor_encode_pubkey_list", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* extensions */ + if (cred->ext.mask) + if ((argv[5] = cbor_encode_cred_ext(&cred->ext, + &cred->blob)) == NULL) { + fido_log_debug("%s: cbor_encode_cred_ext", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* user verification */ + if (pin != NULL || (uv == FIDO_OPT_TRUE && + fido_dev_supports_permissions(dev))) { + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &cred->cdh, pk, ecdh, + pin, cred->rp.id, &argv[7], &argv[8], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + uv = FIDO_OPT_OMIT; + } + + /* options */ + if (cred->rk != FIDO_OPT_OMIT || uv != FIDO_OPT_OMIT) + if ((argv[6] = cbor_encode_cred_opt(cred->rk, uv)) == NULL) { + fido_log_debug("%s: cbor_encode_cred_opt", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + es256_pk_free(&pk); + fido_blob_free(&ecdh); + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + + return (r); +} + +static int +fido_dev_make_cred_rx(fido_dev_t *dev, fido_cred_t *cred, int *ms) +{ + unsigned char *reply; + int reply_len; + int r; + + fido_cred_reset_rx(cred); + + if ((reply = malloc(FIDO_MAXMSG_CRED)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, reply, FIDO_MAXMSG_CRED, + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, cred, + parse_makecred_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_makecred_reply", __func__); + goto fail; + } + + if (cred->fmt == NULL || fido_blob_is_empty(&cred->authdata_cbor) || + fido_blob_is_empty(&cred->attcred.id)) { + r = FIDO_ERR_INVALID_CBOR; + goto fail; + } + + r = FIDO_OK; +fail: + free(reply); + + if (r != FIDO_OK) + fido_cred_reset_rx(cred); + + return (r); +} + +static int +fido_dev_make_cred_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin, + int *ms) +{ + int r; + + if ((r = fido_dev_make_cred_tx(dev, cred, pin, ms)) != FIDO_OK || + (r = fido_dev_make_cred_rx(dev, cred, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_dev_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin) +{ + int ms = dev->timeout_ms; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_make_cred(dev, cred, pin, ms)); +#endif + if (fido_dev_is_fido2(dev) == false) { + if (pin != NULL || cred->rk == FIDO_OPT_TRUE || + cred->ext.mask != 0) + return (FIDO_ERR_UNSUPPORTED_OPTION); + return (u2f_register(dev, cred, &ms)); + } + + return (fido_dev_make_cred_wait(dev, cred, pin, &ms)); +} + +static int +check_extensions(const fido_cred_ext_t *authdata_ext, + const fido_cred_ext_t *ext) +{ + fido_cred_ext_t tmp; + + /* XXX: largeBlobKey is not part of the extensions map */ + memcpy(&tmp, ext, sizeof(tmp)); + tmp.mask &= ~FIDO_EXT_LARGEBLOB_KEY; + + return (timingsafe_bcmp(authdata_ext, &tmp, sizeof(*authdata_ext))); +} + +int +fido_check_rp_id(const char *id, const unsigned char *obtained_hash) +{ + unsigned char expected_hash[SHA256_DIGEST_LENGTH]; + + explicit_bzero(expected_hash, sizeof(expected_hash)); + + if (SHA256((const unsigned char *)id, strlen(id), + expected_hash) != expected_hash) { + fido_log_debug("%s: sha256", __func__); + return (-1); + } + + return (timingsafe_bcmp(expected_hash, obtained_hash, + SHA256_DIGEST_LENGTH)); +} + +static int +get_signed_hash_u2f(fido_blob_t *dgst, const unsigned char *rp_id, + size_t rp_id_len, const fido_blob_t *clientdata, const fido_blob_t *id, + const es256_pk_t *pk) +{ + const uint8_t zero = 0; + const uint8_t four = 4; /* uncompressed point */ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + int ok = -1; + + if (dgst->len < SHA256_DIGEST_LENGTH || + (md = EVP_sha256()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, &zero, sizeof(zero)) != 1 || + EVP_DigestUpdate(ctx, rp_id, rp_id_len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestUpdate(ctx, id->ptr, id->len) != 1 || + EVP_DigestUpdate(ctx, &four, sizeof(four)) != 1 || + EVP_DigestUpdate(ctx, pk->x, sizeof(pk->x)) != 1 || + EVP_DigestUpdate(ctx, pk->y, sizeof(pk->y)) != 1 || + EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) { + fido_log_debug("%s: sha256", __func__); + goto fail; + } + dgst->len = SHA256_DIGEST_LENGTH; + + ok = 0; +fail: + EVP_MD_CTX_free(ctx); + + return (ok); +} + +static int +verify_attstmt(const fido_blob_t *dgst, const fido_attstmt_t *attstmt) +{ + BIO *rawcert = NULL; + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; + int ok = -1; + + /* openssl needs ints */ + if (attstmt->x5c.len > INT_MAX) { + fido_log_debug("%s: x5c.len=%zu", __func__, attstmt->x5c.len); + return (-1); + } + + /* fetch key from x509 */ + if ((rawcert = BIO_new_mem_buf(attstmt->x5c.ptr, + (int)attstmt->x5c.len)) == NULL || + (cert = d2i_X509_bio(rawcert, NULL)) == NULL || + (pkey = X509_get_pubkey(cert)) == NULL) { + fido_log_debug("%s: x509 key", __func__); + goto fail; + } + + switch (attstmt->alg) { + case COSE_UNSPEC: + case COSE_ES256: + ok = es256_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_ES384: + ok = es384_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_RS256: + ok = rs256_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_RS1: + ok = rs1_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_EDDSA: + ok = eddsa_verify_sig(dgst, pkey, &attstmt->sig); + break; + default: + fido_log_debug("%s: unknown alg %d", __func__, attstmt->alg); + break; + } + +fail: + BIO_free(rawcert); + X509_free(cert); + EVP_PKEY_free(pkey); + + return (ok); +} + +int +fido_cred_verify(const fido_cred_t *cred) +{ + unsigned char buf[1024]; /* XXX */ + fido_blob_t dgst; + int cose_alg; + int r; + + dgst.ptr = buf; + dgst.len = sizeof(buf); + + /* do we have everything we need? */ + if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL || + cred->attstmt.x5c.ptr == NULL || cred->attstmt.sig.ptr == NULL || + cred->fmt == NULL || cred->attcred.id.ptr == NULL || + cred->rp.id == NULL) { + fido_log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, " + "fmt=%p id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr, + (void *)cred->authdata_cbor.ptr, + (void *)cred->attstmt.x5c.ptr, + (void *)cred->attstmt.sig.ptr, (void *)cred->fmt, + (void *)cred->attcred.id.ptr, cred->rp.id); + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + if (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) { + fido_log_debug("%s: fido_check_rp_id", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE, + cred->uv) < 0) { + fido_log_debug("%s: fido_check_flags", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (check_extensions(&cred->authdata_ext, &cred->ext) != 0) { + fido_log_debug("%s: check_extensions", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if ((cose_alg = cred->attstmt.alg) == COSE_UNSPEC) + cose_alg = COSE_ES256; /* backwards compat */ + + if (!strcmp(cred->fmt, "packed")) { + if (fido_get_signed_hash(cose_alg, &dgst, &cred->cdh, + &cred->authdata_cbor) < 0) { + fido_log_debug("%s: fido_get_signed_hash", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else if (!strcmp(cred->fmt, "fido-u2f")) { + if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash, + sizeof(cred->authdata.rp_id_hash), &cred->cdh, + &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) { + fido_log_debug("%s: get_signed_hash_u2f", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else if (!strcmp(cred->fmt, "tpm")) { + if (fido_get_signed_hash_tpm(&dgst, &cred->cdh, + &cred->authdata_raw, &cred->attstmt, &cred->attcred) < 0) { + fido_log_debug("%s: fido_get_signed_hash_tpm", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else { + fido_log_debug("%s: unknown fmt %s", __func__, cred->fmt); + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + if (verify_attstmt(&dgst, &cred->attstmt) < 0) { + fido_log_debug("%s: verify_attstmt", __func__); + r = FIDO_ERR_INVALID_SIG; + goto out; + } + + r = FIDO_OK; +out: + explicit_bzero(buf, sizeof(buf)); + + return (r); +} + +int +fido_cred_verify_self(const fido_cred_t *cred) +{ + unsigned char buf[1024]; /* XXX */ + fido_blob_t dgst; + int ok = -1; + int r; + + dgst.ptr = buf; + dgst.len = sizeof(buf); + + /* do we have everything we need? */ + if (cred->cdh.ptr == NULL || cred->authdata_cbor.ptr == NULL || + cred->attstmt.x5c.ptr != NULL || cred->attstmt.sig.ptr == NULL || + cred->fmt == NULL || cred->attcred.id.ptr == NULL || + cred->rp.id == NULL) { + fido_log_debug("%s: cdh=%p, authdata=%p, x5c=%p, sig=%p, " + "fmt=%p id=%p, rp.id=%s", __func__, (void *)cred->cdh.ptr, + (void *)cred->authdata_cbor.ptr, + (void *)cred->attstmt.x5c.ptr, + (void *)cred->attstmt.sig.ptr, (void *)cred->fmt, + (void *)cred->attcred.id.ptr, cred->rp.id); + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + if (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) { + fido_log_debug("%s: fido_check_rp_id", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE, + cred->uv) < 0) { + fido_log_debug("%s: fido_check_flags", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (check_extensions(&cred->authdata_ext, &cred->ext) != 0) { + fido_log_debug("%s: check_extensions", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (!strcmp(cred->fmt, "packed")) { + if (fido_get_signed_hash(cred->attcred.type, &dgst, &cred->cdh, + &cred->authdata_cbor) < 0) { + fido_log_debug("%s: fido_get_signed_hash", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else if (!strcmp(cred->fmt, "fido-u2f")) { + if (get_signed_hash_u2f(&dgst, cred->authdata.rp_id_hash, + sizeof(cred->authdata.rp_id_hash), &cred->cdh, + &cred->attcred.id, &cred->attcred.pubkey.es256) < 0) { + fido_log_debug("%s: get_signed_hash_u2f", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else { + fido_log_debug("%s: unknown fmt %s", __func__, cred->fmt); + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + switch (cred->attcred.type) { + case COSE_ES256: + ok = es256_pk_verify_sig(&dgst, &cred->attcred.pubkey.es256, + &cred->attstmt.sig); + break; + case COSE_ES384: + ok = es384_pk_verify_sig(&dgst, &cred->attcred.pubkey.es384, + &cred->attstmt.sig); + break; + case COSE_RS256: + ok = rs256_pk_verify_sig(&dgst, &cred->attcred.pubkey.rs256, + &cred->attstmt.sig); + break; + case COSE_EDDSA: + ok = eddsa_pk_verify_sig(&dgst, &cred->attcred.pubkey.eddsa, + &cred->attstmt.sig); + break; + default: + fido_log_debug("%s: unsupported cose_alg %d", __func__, + cred->attcred.type); + r = FIDO_ERR_UNSUPPORTED_OPTION; + goto out; + } + + if (ok < 0) + r = FIDO_ERR_INVALID_SIG; + else + r = FIDO_OK; + +out: + explicit_bzero(buf, sizeof(buf)); + + return (r); +} + +fido_cred_t * +fido_cred_new(void) +{ + return (calloc(1, sizeof(fido_cred_t))); +} + +static void +fido_cred_clean_authdata(fido_cred_t *cred) +{ + fido_blob_reset(&cred->authdata_cbor); + fido_blob_reset(&cred->authdata_raw); + fido_blob_reset(&cred->attcred.id); + + memset(&cred->authdata_ext, 0, sizeof(cred->authdata_ext)); + memset(&cred->authdata, 0, sizeof(cred->authdata)); + memset(&cred->attcred, 0, sizeof(cred->attcred)); +} + +static void +fido_cred_clean_attstmt(fido_attstmt_t *attstmt) +{ + fido_blob_reset(&attstmt->certinfo); + fido_blob_reset(&attstmt->pubarea); + fido_blob_reset(&attstmt->cbor); + fido_blob_reset(&attstmt->x5c); + fido_blob_reset(&attstmt->sig); + + memset(attstmt, 0, sizeof(*attstmt)); +} + +void +fido_cred_reset_tx(fido_cred_t *cred) +{ + fido_blob_reset(&cred->cd); + fido_blob_reset(&cred->cdh); + fido_blob_reset(&cred->user.id); + fido_blob_reset(&cred->blob); + + free(cred->rp.id); + free(cred->rp.name); + free(cred->user.icon); + free(cred->user.name); + free(cred->user.display_name); + fido_cred_empty_exclude_list(cred); + + memset(&cred->rp, 0, sizeof(cred->rp)); + memset(&cred->user, 0, sizeof(cred->user)); + memset(&cred->ext, 0, sizeof(cred->ext)); + + cred->type = 0; + cred->rk = FIDO_OPT_OMIT; + cred->uv = FIDO_OPT_OMIT; +} + +void +fido_cred_reset_rx(fido_cred_t *cred) +{ + free(cred->fmt); + cred->fmt = NULL; + fido_cred_clean_authdata(cred); + fido_cred_clean_attstmt(&cred->attstmt); + fido_blob_reset(&cred->largeblob_key); +} + +void +fido_cred_free(fido_cred_t **cred_p) +{ + fido_cred_t *cred; + + if (cred_p == NULL || (cred = *cred_p) == NULL) + return; + fido_cred_reset_tx(cred); + fido_cred_reset_rx(cred); + free(cred); + *cred_p = NULL; +} + +int +fido_cred_set_authdata(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int r = FIDO_ERR_INVALID_ARGUMENT; + + fido_cred_clean_authdata(cred); + + if (ptr == NULL || len == 0) + goto fail; + + if ((item = cbor_load(ptr, len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (fido_blob_decode(item, &cred->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto fail; + } + + if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor, + &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + if (r != FIDO_OK) + fido_cred_clean_authdata(cred); + + return (r); +} + +int +fido_cred_set_authdata_raw(fido_cred_t *cred, const unsigned char *ptr, + size_t len) +{ + cbor_item_t *item = NULL; + int r = FIDO_ERR_INVALID_ARGUMENT; + + fido_cred_clean_authdata(cred); + + if (ptr == NULL || len == 0) + goto fail; + + if (fido_blob_set(&cred->authdata_raw, ptr, len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((item = cbor_build_bytestring(ptr, len)) == NULL) { + fido_log_debug("%s: cbor_build_bytestring", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor, + &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + if (r != FIDO_OK) + fido_cred_clean_authdata(cred); + + return (r); +} + +int +fido_cred_set_id(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (fido_blob_set(&cred->attcred.id, ptr, len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_cred_set_x509(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (fido_blob_set(&cred->attstmt.x5c, ptr, len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_cred_set_sig(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (fido_blob_set(&cred->attstmt.sig, ptr, len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_cred_set_attstmt(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int r = FIDO_ERR_INVALID_ARGUMENT; + + fido_cred_clean_attstmt(&cred->attstmt); + + if (ptr == NULL || len == 0) + goto fail; + + if ((item = cbor_load(ptr, len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_decode_attstmt(item, &cred->attstmt) < 0) { + fido_log_debug("%s: cbor_decode_attstmt", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + if (r != FIDO_OK) + fido_cred_clean_attstmt(&cred->attstmt); + + return (r); +} + +int +fido_cred_exclude(fido_cred_t *cred, const unsigned char *id_ptr, size_t id_len) +{ + fido_blob_t id_blob; + fido_blob_t *list_ptr; + + memset(&id_blob, 0, sizeof(id_blob)); + + if (fido_blob_set(&id_blob, id_ptr, id_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (cred->excl.len == SIZE_MAX) { + free(id_blob.ptr); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if ((list_ptr = recallocarray(cred->excl.ptr, cred->excl.len, + cred->excl.len + 1, sizeof(fido_blob_t))) == NULL) { + free(id_blob.ptr); + return (FIDO_ERR_INTERNAL); + } + + list_ptr[cred->excl.len++] = id_blob; + cred->excl.ptr = list_ptr; + + return (FIDO_OK); +} + +int +fido_cred_empty_exclude_list(fido_cred_t *cred) +{ + fido_free_blob_array(&cred->excl); + memset(&cred->excl, 0, sizeof(cred->excl)); + + return (FIDO_OK); +} + +int +fido_cred_set_clientdata(fido_cred_t *cred, const unsigned char *data, + size_t data_len) +{ + if (!fido_blob_is_empty(&cred->cdh) || + fido_blob_set(&cred->cd, data, data_len) < 0) { + return (FIDO_ERR_INVALID_ARGUMENT); + } + if (fido_sha256(&cred->cdh, data, data_len) < 0) { + fido_blob_reset(&cred->cd); + return (FIDO_ERR_INTERNAL); + } + + return (FIDO_OK); +} + +int +fido_cred_set_clientdata_hash(fido_cred_t *cred, const unsigned char *hash, + size_t hash_len) +{ + if (!fido_blob_is_empty(&cred->cd) || + fido_blob_set(&cred->cdh, hash, hash_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_cred_set_rp(fido_cred_t *cred, const char *id, const char *name) +{ + fido_rp_t *rp = &cred->rp; + + if (rp->id != NULL) { + free(rp->id); + rp->id = NULL; + } + if (rp->name != NULL) { + free(rp->name); + rp->name = NULL; + } + + if (id != NULL && (rp->id = strdup(id)) == NULL) + goto fail; + if (name != NULL && (rp->name = strdup(name)) == NULL) + goto fail; + + return (FIDO_OK); +fail: + free(rp->id); + free(rp->name); + rp->id = NULL; + rp->name = NULL; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_cred_set_user(fido_cred_t *cred, const unsigned char *user_id, + size_t user_id_len, const char *name, const char *display_name, + const char *icon) +{ + fido_user_t *up = &cred->user; + + if (up->id.ptr != NULL) { + free(up->id.ptr); + up->id.ptr = NULL; + up->id.len = 0; + } + if (up->name != NULL) { + free(up->name); + up->name = NULL; + } + if (up->display_name != NULL) { + free(up->display_name); + up->display_name = NULL; + } + if (up->icon != NULL) { + free(up->icon); + up->icon = NULL; + } + + if (user_id != NULL && fido_blob_set(&up->id, user_id, user_id_len) < 0) + goto fail; + if (name != NULL && (up->name = strdup(name)) == NULL) + goto fail; + if (display_name != NULL && + (up->display_name = strdup(display_name)) == NULL) + goto fail; + if (icon != NULL && (up->icon = strdup(icon)) == NULL) + goto fail; + + return (FIDO_OK); +fail: + free(up->id.ptr); + free(up->name); + free(up->display_name); + free(up->icon); + + up->id.ptr = NULL; + up->id.len = 0; + up->name = NULL; + up->display_name = NULL; + up->icon = NULL; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_cred_set_extensions(fido_cred_t *cred, int ext) +{ + if (ext == 0) + cred->ext.mask = 0; + else { + if ((ext & FIDO_EXT_CRED_MASK) != ext) + return (FIDO_ERR_INVALID_ARGUMENT); + cred->ext.mask |= ext; + } + + return (FIDO_OK); +} + +int +fido_cred_set_options(fido_cred_t *cred, bool rk, bool uv) +{ + cred->rk = rk ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + cred->uv = uv ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + + return (FIDO_OK); +} + +int +fido_cred_set_rk(fido_cred_t *cred, fido_opt_t rk) +{ + cred->rk = rk; + + return (FIDO_OK); +} + +int +fido_cred_set_uv(fido_cred_t *cred, fido_opt_t uv) +{ + cred->uv = uv; + + return (FIDO_OK); +} + +int +fido_cred_set_prot(fido_cred_t *cred, int prot) +{ + if (prot == 0) { + cred->ext.mask &= ~FIDO_EXT_CRED_PROTECT; + cred->ext.prot = 0; + } else { + if (prot != FIDO_CRED_PROT_UV_OPTIONAL && + prot != FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID && + prot != FIDO_CRED_PROT_UV_REQUIRED) + return (FIDO_ERR_INVALID_ARGUMENT); + + cred->ext.mask |= FIDO_EXT_CRED_PROTECT; + cred->ext.prot = prot; + } + + return (FIDO_OK); +} + +int +fido_cred_set_pin_minlen(fido_cred_t *cred, size_t len) +{ + if (len == 0) + cred->ext.mask &= ~FIDO_EXT_MINPINLEN; + else + cred->ext.mask |= FIDO_EXT_MINPINLEN; + + cred->ext.minpinlen = len; + + return (FIDO_OK); +} + +int +fido_cred_set_blob(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (ptr == NULL || len == 0) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_blob_set(&cred->blob, ptr, len) < 0) + return (FIDO_ERR_INTERNAL); + + cred->ext.mask |= FIDO_EXT_CRED_BLOB; + + return (FIDO_OK); +} + +int +fido_cred_set_fmt(fido_cred_t *cred, const char *fmt) +{ + free(cred->fmt); + cred->fmt = NULL; + + if (fmt == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (strcmp(fmt, "packed") && strcmp(fmt, "fido-u2f") && + strcmp(fmt, "none") && strcmp(fmt, "tpm")) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((cred->fmt = strdup(fmt)) == NULL) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +int +fido_cred_set_type(fido_cred_t *cred, int cose_alg) +{ + if (cred->type != 0) + return (FIDO_ERR_INVALID_ARGUMENT); + if (cose_alg != COSE_ES256 && cose_alg != COSE_ES384 && + cose_alg != COSE_RS256 && cose_alg != COSE_EDDSA) + return (FIDO_ERR_INVALID_ARGUMENT); + + cred->type = cose_alg; + + return (FIDO_OK); +} + +int +fido_cred_type(const fido_cred_t *cred) +{ + return (cred->type); +} + +uint8_t +fido_cred_flags(const fido_cred_t *cred) +{ + return (cred->authdata.flags); +} + +uint32_t +fido_cred_sigcount(const fido_cred_t *cred) +{ + return (cred->authdata.sigcount); +} + +const unsigned char * +fido_cred_clientdata_hash_ptr(const fido_cred_t *cred) +{ + return (cred->cdh.ptr); +} + +size_t +fido_cred_clientdata_hash_len(const fido_cred_t *cred) +{ + return (cred->cdh.len); +} + +const unsigned char * +fido_cred_x5c_ptr(const fido_cred_t *cred) +{ + return (cred->attstmt.x5c.ptr); +} + +size_t +fido_cred_x5c_len(const fido_cred_t *cred) +{ + return (cred->attstmt.x5c.len); +} + +const unsigned char * +fido_cred_sig_ptr(const fido_cred_t *cred) +{ + return (cred->attstmt.sig.ptr); +} + +size_t +fido_cred_sig_len(const fido_cred_t *cred) +{ + return (cred->attstmt.sig.len); +} + +const unsigned char * +fido_cred_authdata_ptr(const fido_cred_t *cred) +{ + return (cred->authdata_cbor.ptr); +} + +size_t +fido_cred_authdata_len(const fido_cred_t *cred) +{ + return (cred->authdata_cbor.len); +} + +const unsigned char * +fido_cred_authdata_raw_ptr(const fido_cred_t *cred) +{ + return (cred->authdata_raw.ptr); +} + +size_t +fido_cred_authdata_raw_len(const fido_cred_t *cred) +{ + return (cred->authdata_raw.len); +} + +const unsigned char * +fido_cred_attstmt_ptr(const fido_cred_t *cred) +{ + return (cred->attstmt.cbor.ptr); +} + +size_t +fido_cred_attstmt_len(const fido_cred_t *cred) +{ + return (cred->attstmt.cbor.len); +} + +const unsigned char * +fido_cred_pubkey_ptr(const fido_cred_t *cred) +{ + const void *ptr; + + switch (cred->attcred.type) { + case COSE_ES256: + ptr = &cred->attcred.pubkey.es256; + break; + case COSE_ES384: + ptr = &cred->attcred.pubkey.es384; + break; + case COSE_RS256: + ptr = &cred->attcred.pubkey.rs256; + break; + case COSE_EDDSA: + ptr = &cred->attcred.pubkey.eddsa; + break; + default: + ptr = NULL; + break; + } + + return (ptr); +} + +size_t +fido_cred_pubkey_len(const fido_cred_t *cred) +{ + size_t len; + + switch (cred->attcred.type) { + case COSE_ES256: + len = sizeof(cred->attcred.pubkey.es256); + break; + case COSE_ES384: + len = sizeof(cred->attcred.pubkey.es384); + break; + case COSE_RS256: + len = sizeof(cred->attcred.pubkey.rs256); + break; + case COSE_EDDSA: + len = sizeof(cred->attcred.pubkey.eddsa); + break; + default: + len = 0; + break; + } + + return (len); +} + +const unsigned char * +fido_cred_id_ptr(const fido_cred_t *cred) +{ + return (cred->attcred.id.ptr); +} + +size_t +fido_cred_id_len(const fido_cred_t *cred) +{ + return (cred->attcred.id.len); +} + +const unsigned char * +fido_cred_aaguid_ptr(const fido_cred_t *cred) +{ + return (cred->attcred.aaguid); +} + +size_t +fido_cred_aaguid_len(const fido_cred_t *cred) +{ + return (sizeof(cred->attcred.aaguid)); +} + +int +fido_cred_prot(const fido_cred_t *cred) +{ + return (cred->ext.prot); +} + +size_t +fido_cred_pin_minlen(const fido_cred_t *cred) +{ + return (cred->ext.minpinlen); +} + +const char * +fido_cred_fmt(const fido_cred_t *cred) +{ + return (cred->fmt); +} + +const char * +fido_cred_rp_id(const fido_cred_t *cred) +{ + return (cred->rp.id); +} + +const char * +fido_cred_rp_name(const fido_cred_t *cred) +{ + return (cred->rp.name); +} + +const char * +fido_cred_user_name(const fido_cred_t *cred) +{ + return (cred->user.name); +} + +const char * +fido_cred_display_name(const fido_cred_t *cred) +{ + return (cred->user.display_name); +} + +const unsigned char * +fido_cred_user_id_ptr(const fido_cred_t *cred) +{ + return (cred->user.id.ptr); +} + +size_t +fido_cred_user_id_len(const fido_cred_t *cred) +{ + return (cred->user.id.len); +} + +const unsigned char * +fido_cred_largeblob_key_ptr(const fido_cred_t *cred) +{ + return (cred->largeblob_key.ptr); +} + +size_t +fido_cred_largeblob_key_len(const fido_cred_t *cred) +{ + return (cred->largeblob_key.len); +} diff --git a/src/credman.c b/src/credman.c new file mode 100644 index 0000000..c364242 --- /dev/null +++ b/src/credman.c @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> + +#include "fido.h" +#include "fido/credman.h" +#include "fido/es256.h" + +#define CMD_CRED_METADATA 0x01 +#define CMD_RP_BEGIN 0x02 +#define CMD_RP_NEXT 0x03 +#define CMD_RK_BEGIN 0x04 +#define CMD_RK_NEXT 0x05 +#define CMD_DELETE_CRED 0x06 +#define CMD_UPDATE_CRED 0x07 + +static int +credman_grow_array(void **ptr, size_t *n_alloc, const size_t *n_rx, size_t n, + size_t size) +{ + void *new_ptr; + +#ifdef FIDO_FUZZ + if (n > UINT8_MAX) { + fido_log_debug("%s: n > UINT8_MAX", __func__); + return (-1); + } +#endif + + if (n < *n_alloc) + return (0); + + /* sanity check */ + if (*n_rx > 0 || *n_rx > *n_alloc || n < *n_alloc) { + fido_log_debug("%s: n=%zu, n_rx=%zu, n_alloc=%zu", __func__, n, + *n_rx, *n_alloc); + return (-1); + } + + if ((new_ptr = recallocarray(*ptr, *n_alloc, n, size)) == NULL) + return (-1); + + *ptr = new_ptr; + *n_alloc = n; + + return (0); +} + +static int +credman_prepare_hmac(uint8_t cmd, const void *body, cbor_item_t **param, + fido_blob_t *hmac_data) +{ + cbor_item_t *param_cbor[3]; + const fido_cred_t *cred; + size_t n; + int ok = -1; + + memset(¶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, int *ms) +{ + fido_blob_t f; + fido_blob_t *ecdh = NULL; + fido_blob_t hmac; + es256_pk_t *pk = NULL; + cbor_item_t *argv[4]; + const uint8_t cmd = CTAP_CBOR_CRED_MGMT_PRE; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(&hmac, 0, sizeof(hmac)); + memset(&argv, 0, sizeof(argv)); + + if (fido_dev_is_fido2(dev) == false) { + fido_log_debug("%s: fido_dev_is_fido2", __func__); + r = FIDO_ERR_INVALID_COMMAND; + goto fail; + } + + /* subCommand */ + if ((argv[0] = cbor_build_uint8(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* pinProtocol, pinAuth */ + if (pin != NULL || uv == FIDO_OPT_TRUE) { + if (credman_prepare_hmac(subcmd, param, &argv[1], &hmac) < 0) { + fido_log_debug("%s: credman_prepare_hmac", __func__); + goto fail; + } + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + rp_id, &argv[3], &argv[2], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + es256_pk_free(&pk); + fido_blob_free(&ecdh); + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + free(hmac.ptr); + + return (r); +} + +static int +credman_parse_metadata(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_credman_metadata_t *metadata = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: + return (cbor_decode_uint64(val, &metadata->rk_existing)); + case 2: + return (cbor_decode_uint64(val, &metadata->rk_remaining)); + default: + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } +} + +static int +credman_rx_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + memset(metadata, 0, sizeof(*metadata)); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, metadata, + credman_parse_metadata)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_metadata", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_get_metadata_wait(fido_dev_t *dev, fido_credman_metadata_t *metadata, + const char *pin, int *ms) +{ + int r; + + if ((r = credman_tx(dev, CMD_CRED_METADATA, NULL, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = credman_rx_metadata(dev, metadata, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_credman_get_dev_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata, + const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_get_metadata_wait(dev, metadata, pin, &ms)); +} + +static int +credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_t *cred = arg; + uint64_t prot; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 6: + return (cbor_decode_user(val, &cred->user)); + case 7: + return (cbor_decode_cred_id(val, &cred->attcred.id)); + case 8: + if (cbor_decode_pubkey(val, &cred->attcred.type, + &cred->attcred.pubkey) < 0) + return (-1); + cred->type = cred->attcred.type; /* XXX */ + return (0); + case 10: + if (cbor_decode_uint64(val, &prot) < 0 || prot > INT_MAX || + fido_cred_set_prot(cred, (int)prot) != FIDO_OK) + return (-1); + return (0); + case 11: + return (fido_blob_decode(val, &cred->largeblob_key)); + default: + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } +} + +static void +credman_reset_rk(fido_credman_rk_t *rk) +{ + for (size_t i = 0; i < rk->n_alloc; i++) { + fido_cred_reset_tx(&rk->ptr[i]); + fido_cred_reset_rx(&rk->ptr[i]); + } + + free(rk->ptr); + rk->ptr = NULL; + memset(rk, 0, sizeof(*rk)); +} + +static int +credman_parse_rk_count(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_credman_rk_t *rk = arg; + uint64_t n; + + /* totalCredentials */ + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 9) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + if (credman_grow_array((void **)&rk->ptr, &rk->n_alloc, &rk->n_rx, + (size_t)n, sizeof(*rk->ptr)) < 0) { + fido_log_debug("%s: credman_grow_array", __func__); + return (-1); + } + + return (0); +} + +static int +credman_rx_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + credman_reset_rk(rk); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, rk, + credman_parse_rk_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk_count", __func__); + goto out; + } + + if (rk->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + r = FIDO_OK; + goto out; + } + + /* parse the first rk */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[0], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + goto out; + } + rk->n_rx = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* sanity check */ + if (rk->n_rx >= rk->n_alloc) { + fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx, + rk->n_alloc); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[rk->n_rx], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_get_rk_wait(fido_dev_t *dev, const char *rp_id, fido_credman_rk_t *rk, + const char *pin, int *ms) +{ + fido_blob_t rp_dgst; + uint8_t dgst[SHA256_DIGEST_LENGTH]; + int r; + + if (SHA256((const unsigned char *)rp_id, strlen(rp_id), dgst) != dgst) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + rp_dgst.ptr = dgst; + rp_dgst.len = sizeof(dgst); + + if ((r = credman_tx(dev, CMD_RK_BEGIN, &rp_dgst, pin, rp_id, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = credman_rx_rk(dev, rk, ms)) != FIDO_OK) + return (r); + + while (rk->n_rx < rk->n_alloc) { + if ((r = credman_tx(dev, CMD_RK_NEXT, NULL, NULL, NULL, + FIDO_OPT_FALSE, ms)) != FIDO_OK || + (r = credman_rx_next_rk(dev, rk, ms)) != FIDO_OK) + return (r); + rk->n_rx++; + } + + return (FIDO_OK); +} + +int +fido_credman_get_dev_rk(fido_dev_t *dev, const char *rp_id, + fido_credman_rk_t *rk, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_get_rk_wait(dev, rp_id, rk, pin, &ms)); +} + +static int +credman_del_rk_wait(fido_dev_t *dev, const unsigned char *cred_id, + size_t cred_id_len, const char *pin, int *ms) +{ + fido_blob_t cred; + int r; + + memset(&cred, 0, sizeof(cred)); + + if (fido_blob_set(&cred, cred_id, cred_id_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((r = credman_tx(dev, CMD_DELETE_CRED, &cred, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) + goto fail; + + r = FIDO_OK; +fail: + free(cred.ptr); + + return (r); +} + +int +fido_credman_del_dev_rk(fido_dev_t *dev, const unsigned char *cred_id, + size_t cred_id_len, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, &ms)); +} + +static int +credman_parse_rp(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + struct fido_credman_single_rp *rp = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 3: + return (cbor_decode_rp_entity(val, &rp->rp_entity)); + case 4: + return (fido_blob_decode(val, &rp->rp_id_hash)); + default: + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } +} + +static void +credman_reset_rp(fido_credman_rp_t *rp) +{ + for (size_t i = 0; i < rp->n_alloc; i++) { + free(rp->ptr[i].rp_entity.id); + free(rp->ptr[i].rp_entity.name); + rp->ptr[i].rp_entity.id = NULL; + rp->ptr[i].rp_entity.name = NULL; + fido_blob_reset(&rp->ptr[i].rp_id_hash); + } + + free(rp->ptr); + rp->ptr = NULL; + memset(rp, 0, sizeof(*rp)); +} + +static int +credman_parse_rp_count(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_credman_rp_t *rp = arg; + uint64_t n; + + /* totalRPs */ + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 5) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + if (credman_grow_array((void **)&rp->ptr, &rp->n_alloc, &rp->n_rx, + (size_t)n, sizeof(*rp->ptr)) < 0) { + fido_log_debug("%s: credman_grow_array", __func__); + return (-1); + } + + return (0); +} + +static int +credman_rx_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + credman_reset_rp(rp); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, rp, + credman_parse_rp_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp_count", __func__); + goto out; + } + + if (rp->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + r = FIDO_OK; + goto out; + } + + /* parse the first rp */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[0], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + goto out; + } + rp->n_rx = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* sanity check */ + if (rp->n_rx >= rp->n_alloc) { + fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx, + rp->n_alloc); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[rp->n_rx], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_get_rp_wait(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin, + int *ms) +{ + int r; + + if ((r = credman_tx(dev, CMD_RP_BEGIN, NULL, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = credman_rx_rp(dev, rp, ms)) != FIDO_OK) + return (r); + + while (rp->n_rx < rp->n_alloc) { + if ((r = credman_tx(dev, CMD_RP_NEXT, NULL, NULL, NULL, + FIDO_OPT_FALSE, ms)) != FIDO_OK || + (r = credman_rx_next_rp(dev, rp, ms)) != FIDO_OK) + return (r); + rp->n_rx++; + } + + return (FIDO_OK); +} + +int +fido_credman_get_dev_rp(fido_dev_t *dev, fido_credman_rp_t *rp, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_get_rp_wait(dev, rp, pin, &ms)); +} + +static int +credman_set_dev_rk_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin, + int *ms) +{ + int r; + + if ((r = credman_tx(dev, CMD_UPDATE_CRED, cred, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_credman_set_dev_rk(fido_dev_t *dev, fido_cred_t *cred, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_set_dev_rk_wait(dev, cred, pin, &ms)); +} + +fido_credman_rk_t * +fido_credman_rk_new(void) +{ + return (calloc(1, sizeof(fido_credman_rk_t))); +} + +void +fido_credman_rk_free(fido_credman_rk_t **rk_p) +{ + fido_credman_rk_t *rk; + + if (rk_p == NULL || (rk = *rk_p) == NULL) + return; + + credman_reset_rk(rk); + free(rk); + *rk_p = NULL; +} + +size_t +fido_credman_rk_count(const fido_credman_rk_t *rk) +{ + return (rk->n_rx); +} + +const fido_cred_t * +fido_credman_rk(const fido_credman_rk_t *rk, size_t idx) +{ + if (idx >= rk->n_alloc) + return (NULL); + + return (&rk->ptr[idx]); +} + +fido_credman_metadata_t * +fido_credman_metadata_new(void) +{ + return (calloc(1, sizeof(fido_credman_metadata_t))); +} + +void +fido_credman_metadata_free(fido_credman_metadata_t **metadata_p) +{ + fido_credman_metadata_t *metadata; + + if (metadata_p == NULL || (metadata = *metadata_p) == NULL) + return; + + free(metadata); + *metadata_p = NULL; +} + +uint64_t +fido_credman_rk_existing(const fido_credman_metadata_t *metadata) +{ + return (metadata->rk_existing); +} + +uint64_t +fido_credman_rk_remaining(const fido_credman_metadata_t *metadata) +{ + return (metadata->rk_remaining); +} + +fido_credman_rp_t * +fido_credman_rp_new(void) +{ + return (calloc(1, sizeof(fido_credman_rp_t))); +} + +void +fido_credman_rp_free(fido_credman_rp_t **rp_p) +{ + fido_credman_rp_t *rp; + + if (rp_p == NULL || (rp = *rp_p) == NULL) + return; + + credman_reset_rp(rp); + free(rp); + *rp_p = NULL; +} + +size_t +fido_credman_rp_count(const fido_credman_rp_t *rp) +{ + return (rp->n_rx); +} + +const char * +fido_credman_rp_id(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (NULL); + + return (rp->ptr[idx].rp_entity.id); +} + +const char * +fido_credman_rp_name(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (NULL); + + return (rp->ptr[idx].rp_entity.name); +} + +size_t +fido_credman_rp_id_hash_len(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (0); + + return (rp->ptr[idx].rp_id_hash.len); +} + +const unsigned char * +fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *rp, size_t idx) +{ + if (idx >= rp->n_alloc) + return (NULL); + + return (rp->ptr[idx].rp_id_hash.ptr); +} diff --git a/src/dev.c b/src/dev.c new file mode 100644 index 0000000..2d662a6 --- /dev/null +++ b/src/dev.c @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +#ifndef TLS +#define TLS +#endif + +static TLS bool disable_u2f_fallback; + +#ifdef FIDO_FUZZ +static void +set_random_report_len(fido_dev_t *dev) +{ + dev->rx_len = CTAP_MIN_REPORT_LEN + + uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); + dev->tx_len = CTAP_MIN_REPORT_LEN + + uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); +} +#endif + +static void +fido_dev_set_extension_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + char * const *ptr = fido_cbor_info_extensions_ptr(info); + size_t len = fido_cbor_info_extensions_len(info); + + for (size_t i = 0; i < len; i++) + if (strcmp(ptr[i], "credProtect") == 0) + dev->flags |= FIDO_DEV_CRED_PROT; +} + +static void +fido_dev_set_option_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + char * const *ptr = fido_cbor_info_options_name_ptr(info); + const bool *val = fido_cbor_info_options_value_ptr(info); + size_t len = fido_cbor_info_options_len(info); + + for (size_t i = 0; i < len; i++) + if (strcmp(ptr[i], "clientPin") == 0) { + dev->flags |= val[i] ? + FIDO_DEV_PIN_SET : FIDO_DEV_PIN_UNSET; + } else if (strcmp(ptr[i], "credMgmt") == 0 || + strcmp(ptr[i], "credentialMgmtPreview") == 0) { + if (val[i]) + dev->flags |= FIDO_DEV_CREDMAN; + } else if (strcmp(ptr[i], "uv") == 0) { + dev->flags |= val[i] ? + FIDO_DEV_UV_SET : FIDO_DEV_UV_UNSET; + } else if (strcmp(ptr[i], "pinUvAuthToken") == 0) { + if (val[i]) + dev->flags |= FIDO_DEV_TOKEN_PERMS; + } +} + +static void +fido_dev_set_protocol_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + const uint8_t *ptr = fido_cbor_info_protocols_ptr(info); + size_t len = fido_cbor_info_protocols_len(info); + + for (size_t i = 0; i < len; i++) + switch (ptr[i]) { + case CTAP_PIN_PROTOCOL1: + dev->flags |= FIDO_DEV_PIN_PROTOCOL1; + break; + case CTAP_PIN_PROTOCOL2: + dev->flags |= FIDO_DEV_PIN_PROTOCOL2; + break; + default: + fido_log_debug("%s: unknown protocol %u", __func__, + ptr[i]); + break; + } +} + +static void +fido_dev_set_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + fido_dev_set_extension_flags(dev, info); + fido_dev_set_option_flags(dev, info); + fido_dev_set_protocol_flags(dev, info); +} + +static int +fido_dev_open_tx(fido_dev_t *dev, const char *path, int *ms) +{ + int r; + + if (dev->io_handle != NULL) { + fido_log_debug("%s: handle=%p", __func__, dev->io_handle); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (dev->io.open == NULL || dev->io.close == NULL) { + fido_log_debug("%s: NULL open/close", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (dev->cid != CTAP_CID_BROADCAST) { + fido_log_debug("%s: cid=0x%x", __func__, dev->cid); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (fido_get_random(&dev->nonce, sizeof(dev->nonce)) < 0) { + fido_log_debug("%s: fido_get_random", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((dev->io_handle = dev->io.open(path)) == NULL) { + fido_log_debug("%s: dev->io.open", __func__); + return (FIDO_ERR_INTERNAL); + } + + if (dev->io_own) { + dev->rx_len = CTAP_MAX_REPORT_LEN; + dev->tx_len = CTAP_MAX_REPORT_LEN; + } else { + dev->rx_len = fido_hid_report_in_len(dev->io_handle); + dev->tx_len = fido_hid_report_out_len(dev->io_handle); + } + +#ifdef FIDO_FUZZ + set_random_report_len(dev); +#endif + + if (dev->rx_len < CTAP_MIN_REPORT_LEN || + dev->rx_len > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: invalid rx_len %zu", __func__, dev->rx_len); + r = FIDO_ERR_RX; + goto fail; + } + + if (dev->tx_len < CTAP_MIN_REPORT_LEN || + dev->tx_len > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: invalid tx_len %zu", __func__, dev->tx_len); + r = FIDO_ERR_TX; + goto fail; + } + + if (fido_tx(dev, CTAP_CMD_INIT, &dev->nonce, sizeof(dev->nonce), + ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + return (FIDO_OK); +fail: + dev->io.close(dev->io_handle); + dev->io_handle = NULL; + + return (r); +} + +static int +fido_dev_open_rx(fido_dev_t *dev, int *ms) +{ + fido_cbor_info_t *info = NULL; + int reply_len; + int r; + + if ((reply_len = fido_rx(dev, CTAP_CMD_INIT, &dev->attr, + sizeof(dev->attr), ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + +#ifdef FIDO_FUZZ + dev->attr.nonce = dev->nonce; +#endif + + if ((size_t)reply_len != sizeof(dev->attr) || + dev->attr.nonce != dev->nonce) { + fido_log_debug("%s: invalid nonce", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + dev->flags = 0; + dev->cid = dev->attr.cid; + + if (fido_dev_is_fido2(dev)) { + if ((info = fido_cbor_info_new()) == NULL) { + fido_log_debug("%s: fido_cbor_info_new", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = fido_dev_get_cbor_info_wait(dev, info, + ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_cbor_info_wait: %d", + __func__, r); + if (disable_u2f_fallback) + goto fail; + fido_log_debug("%s: falling back to u2f", __func__); + fido_dev_force_u2f(dev); + } else { + fido_dev_set_flags(dev, info); + } + } + + if (fido_dev_is_fido2(dev) && info != NULL) { + dev->maxmsgsize = fido_cbor_info_maxmsgsiz(info); + fido_log_debug("%s: FIDO_MAXMSG=%d, maxmsgsiz=%lu", __func__, + FIDO_MAXMSG, (unsigned long)dev->maxmsgsize); + } + + r = FIDO_OK; +fail: + fido_cbor_info_free(&info); + + if (r != FIDO_OK) { + dev->io.close(dev->io_handle); + dev->io_handle = NULL; + } + + return (r); +} + +static int +fido_dev_open_wait(fido_dev_t *dev, const char *path, int *ms) +{ + int r; + +#ifdef USE_WINHELLO + if (strcmp(path, FIDO_WINHELLO_PATH) == 0) + return (fido_winhello_open(dev)); +#endif + if ((r = fido_dev_open_tx(dev, path, ms)) != FIDO_OK || + (r = fido_dev_open_rx(dev, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +static void +run_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen, + const char *type, int (*manifest)(fido_dev_info_t *, size_t, size_t *)) +{ + size_t ndevs = 0; + int r; + + if (*olen >= ilen) { + fido_log_debug("%s: skipping %s", __func__, type); + return; + } + if ((r = manifest(devlist + *olen, ilen - *olen, &ndevs)) != FIDO_OK) + fido_log_debug("%s: %s: 0x%x", __func__, type, r); + fido_log_debug("%s: found %zu %s device%s", __func__, ndevs, type, + ndevs == 1 ? "" : "s"); + *olen += ndevs; +} + +int +fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + *olen = 0; + + run_manifest(devlist, ilen, olen, "hid", fido_hid_manifest); +#ifdef USE_NFC + run_manifest(devlist, ilen, olen, "nfc", fido_nfc_manifest); +#endif +#ifdef USE_PCSC + run_manifest(devlist, ilen, olen, "pcsc", fido_pcsc_manifest); +#endif +#ifdef USE_WINHELLO + run_manifest(devlist, ilen, olen, "winhello", fido_winhello_manifest); +#endif + + return (FIDO_OK); +} + +int +fido_dev_open_with_info(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + + if (dev->path == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (fido_dev_open_wait(dev, dev->path, &ms)); +} + +int +fido_dev_open(fido_dev_t *dev, const char *path) +{ + int ms = dev->timeout_ms; + +#ifdef USE_NFC + if (fido_is_nfc(path) && fido_dev_set_nfc(dev) < 0) { + fido_log_debug("%s: fido_dev_set_nfc", __func__); + return FIDO_ERR_INTERNAL; + } +#endif +#ifdef USE_PCSC + if (fido_is_pcsc(path) && fido_dev_set_pcsc(dev) < 0) { + fido_log_debug("%s: fido_dev_set_pcsc", __func__); + return FIDO_ERR_INTERNAL; + } +#endif + + return (fido_dev_open_wait(dev, path, &ms)); +} + +int +fido_dev_close(fido_dev_t *dev) +{ +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_close(dev)); +#endif + if (dev->io_handle == NULL || dev->io.close == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + dev->io.close(dev->io_handle); + dev->io_handle = NULL; + dev->cid = CTAP_CID_BROADCAST; + + return (FIDO_OK); +} + +int +fido_dev_set_sigmask(fido_dev_t *dev, const fido_sigset_t *sigmask) +{ + if (dev->io_handle == NULL || sigmask == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + +#ifdef USE_NFC + if (dev->transport.rx == fido_nfc_rx && dev->io.read == fido_nfc_read) + return (fido_nfc_set_sigmask(dev->io_handle, sigmask)); +#endif + if (dev->transport.rx == NULL && dev->io.read == fido_hid_read) + return (fido_hid_set_sigmask(dev->io_handle, sigmask)); + + return (FIDO_ERR_INVALID_ARGUMENT); +} + +int +fido_dev_cancel(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_cancel(dev)); +#endif + if (fido_dev_is_fido2(dev) == false) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_tx(dev, CTAP_CMD_CANCEL, NULL, 0, &ms) < 0) + return (FIDO_ERR_TX); + + return (FIDO_OK); +} + +int +fido_dev_set_io_functions(fido_dev_t *dev, const fido_dev_io_t *io) +{ + if (dev->io_handle != NULL) { + fido_log_debug("%s: non-NULL handle", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (io == NULL || io->open == NULL || io->close == NULL || + io->read == NULL || io->write == NULL) { + fido_log_debug("%s: NULL function", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + dev->io = *io; + dev->io_own = true; + + return (FIDO_OK); +} + +int +fido_dev_set_transport_functions(fido_dev_t *dev, const fido_dev_transport_t *t) +{ + if (dev->io_handle != NULL) { + fido_log_debug("%s: non-NULL handle", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + dev->transport = *t; + dev->io_own = true; + + return (FIDO_OK); +} + +void * +fido_dev_io_handle(const fido_dev_t *dev) +{ + + return (dev->io_handle); +} + +void +fido_init(int flags) +{ + if (flags & FIDO_DEBUG || getenv("FIDO_DEBUG") != NULL) + fido_log_init(); + + disable_u2f_fallback = (flags & FIDO_DISABLE_U2F_FALLBACK); +} + +fido_dev_t * +fido_dev_new(void) +{ + fido_dev_t *dev; + + if ((dev = calloc(1, sizeof(*dev))) == NULL) + return (NULL); + + dev->cid = CTAP_CID_BROADCAST; + dev->timeout_ms = -1; + dev->io = (fido_dev_io_t) { + &fido_hid_open, + &fido_hid_close, + &fido_hid_read, + &fido_hid_write, + }; + + return (dev); +} + +fido_dev_t * +fido_dev_new_with_info(const fido_dev_info_t *di) +{ + fido_dev_t *dev; + + if ((dev = calloc(1, sizeof(*dev))) == NULL) + return (NULL); + +#if 0 + if (di->io.open == NULL || di->io.close == NULL || + di->io.read == NULL || di->io.write == NULL) { + fido_log_debug("%s: NULL function", __func__); + fido_dev_free(&dev); + return (NULL); + } +#endif + + dev->io = di->io; + dev->io_own = di->transport.tx != NULL || di->transport.rx != NULL; + dev->transport = di->transport; + dev->cid = CTAP_CID_BROADCAST; + dev->timeout_ms = -1; + + if ((dev->path = strdup(di->path)) == NULL) { + fido_log_debug("%s: strdup", __func__); + fido_dev_free(&dev); + return (NULL); + } + + return (dev); +} + +void +fido_dev_free(fido_dev_t **dev_p) +{ + fido_dev_t *dev; + + if (dev_p == NULL || (dev = *dev_p) == NULL) + return; + + free(dev->path); + free(dev); + + *dev_p = NULL; +} + +uint8_t +fido_dev_protocol(const fido_dev_t *dev) +{ + return (dev->attr.protocol); +} + +uint8_t +fido_dev_major(const fido_dev_t *dev) +{ + return (dev->attr.major); +} + +uint8_t +fido_dev_minor(const fido_dev_t *dev) +{ + return (dev->attr.minor); +} + +uint8_t +fido_dev_build(const fido_dev_t *dev) +{ + return (dev->attr.build); +} + +uint8_t +fido_dev_flags(const fido_dev_t *dev) +{ + return (dev->attr.flags); +} + +bool +fido_dev_is_fido2(const fido_dev_t *dev) +{ + return (dev->attr.flags & FIDO_CAP_CBOR); +} + +bool +fido_dev_is_winhello(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_WINHELLO); +} + +bool +fido_dev_supports_pin(const fido_dev_t *dev) +{ + return (dev->flags & (FIDO_DEV_PIN_SET|FIDO_DEV_PIN_UNSET)); +} + +bool +fido_dev_has_pin(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_PIN_SET); +} + +bool +fido_dev_supports_cred_prot(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_CRED_PROT); +} + +bool +fido_dev_supports_credman(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_CREDMAN); +} + +bool +fido_dev_supports_uv(const fido_dev_t *dev) +{ + return (dev->flags & (FIDO_DEV_UV_SET|FIDO_DEV_UV_UNSET)); +} + +bool +fido_dev_has_uv(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_UV_SET); +} + +bool +fido_dev_supports_permissions(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_TOKEN_PERMS); +} + +void +fido_dev_force_u2f(fido_dev_t *dev) +{ + dev->attr.flags &= (uint8_t)~FIDO_CAP_CBOR; + dev->flags = 0; +} + +void +fido_dev_force_fido2(fido_dev_t *dev) +{ + dev->attr.flags |= FIDO_CAP_CBOR; +} + +uint8_t +fido_dev_get_pin_protocol(const fido_dev_t *dev) +{ + if (dev->flags & FIDO_DEV_PIN_PROTOCOL2) + return (CTAP_PIN_PROTOCOL2); + else if (dev->flags & FIDO_DEV_PIN_PROTOCOL1) + return (CTAP_PIN_PROTOCOL1); + + return (0); +} + +uint64_t +fido_dev_maxmsgsize(const fido_dev_t *dev) +{ + return (dev->maxmsgsize); +} + +int +fido_dev_set_timeout(fido_dev_t *dev, int ms) +{ + if (ms < -1) + return (FIDO_ERR_INVALID_ARGUMENT); + + dev->timeout_ms = ms; + + return (FIDO_OK); +} diff --git a/src/diff_exports.sh b/src/diff_exports.sh new file mode 100755 index 0000000..2e15cd0 --- /dev/null +++ b/src/diff_exports.sh @@ -0,0 +1,27 @@ +#!/bin/sh -u + +# Copyright (c) 2018 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +for f in export.gnu export.llvm export.msvc; do + if [ ! -f "${f}" ]; then + exit 1 + fi +done + +TMPDIR="$(mktemp -d)" +GNU="${TMPDIR}/gnu" +LLVM="${TMPDIR}/llvm" +MSVC="${TMPDIR}/msvc" + +awk '/^[^*{}]+;$/' export.gnu | tr -d '\t;' | sort > "${GNU}" +sed 's/^_//' export.llvm | sort > "${LLVM}" +grep -v '^EXPORTS$' export.msvc | sort > "${MSVC}" +diff -u "${GNU}" "${LLVM}" && diff -u "${MSVC}" "${LLVM}" +ERROR=$? +rm "${GNU}" "${LLVM}" "${MSVC}" +rmdir "${TMPDIR}" + +exit ${ERROR} diff --git a/src/ecdh.c b/src/ecdh.c new file mode 100644 index 0000000..878f976 --- /dev/null +++ b/src/ecdh.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/evp.h> +#include <openssl/sha.h> +#if defined(LIBRESSL_VERSION_NUMBER) +#include <openssl/hkdf.h> +#else +#include <openssl/kdf.h> +#endif + +#include "fido.h" +#include "fido/es256.h" + +#if defined(LIBRESSL_VERSION_NUMBER) +static int +hkdf_sha256(uint8_t *key, const char *info, const fido_blob_t *secret) +{ + const EVP_MD *md; + uint8_t salt[32]; + + memset(salt, 0, sizeof(salt)); + if ((md = EVP_sha256()) == NULL || + HKDF(key, SHA256_DIGEST_LENGTH, md, secret->ptr, secret->len, salt, + sizeof(salt), (const uint8_t *)info, strlen(info)) != 1) + return -1; + + return 0; +} +#else +static int +hkdf_sha256(uint8_t *key, char *info, fido_blob_t *secret) +{ + const EVP_MD *const_md; + EVP_MD *md = NULL; + EVP_PKEY_CTX *ctx = NULL; + size_t keylen = SHA256_DIGEST_LENGTH; + uint8_t salt[32]; + int ok = -1; + + memset(salt, 0, sizeof(salt)); + if (secret->len > INT_MAX || strlen(info) > INT_MAX) { + fido_log_debug("%s: invalid param", __func__); + goto fail; + } + if ((const_md = EVP_sha256()) == NULL || + (md = EVP_MD_meth_dup(const_md)) == NULL || + (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)) == NULL) { + fido_log_debug("%s: init", __func__); + goto fail; + } + if (EVP_PKEY_derive_init(ctx) < 1 || + EVP_PKEY_CTX_set_hkdf_md(ctx, md) < 1 || + EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, sizeof(salt)) < 1 || + EVP_PKEY_CTX_set1_hkdf_key(ctx, secret->ptr, (int)secret->len) < 1 || + EVP_PKEY_CTX_add1_hkdf_info(ctx, (void *)info, (int)strlen(info)) < 1) { + fido_log_debug("%s: EVP_PKEY_CTX", __func__); + goto fail; + } + if (EVP_PKEY_derive(ctx, key, &keylen) < 1) { + fido_log_debug("%s: EVP_PKEY_derive", __func__); + goto fail; + } + + ok = 0; +fail: + if (md != NULL) + EVP_MD_meth_free(md); + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + + return ok; +} +#endif /* defined(LIBRESSL_VERSION_NUMBER) */ + +static int +kdf(uint8_t prot, fido_blob_t *key, /* const */ fido_blob_t *secret) +{ + char hmac_info[] = "CTAP2 HMAC key"; /* const */ + char aes_info[] = "CTAP2 AES key"; /* const */ + + switch (prot) { + case CTAP_PIN_PROTOCOL1: + /* use sha256 on the resulting secret */ + key->len = SHA256_DIGEST_LENGTH; + if ((key->ptr = calloc(1, key->len)) == NULL || + SHA256(secret->ptr, secret->len, key->ptr) != key->ptr) { + fido_log_debug("%s: SHA256", __func__); + return -1; + } + break; + case CTAP_PIN_PROTOCOL2: + /* use two instances of hkdf-sha256 on the resulting secret */ + key->len = 2 * SHA256_DIGEST_LENGTH; + if ((key->ptr = calloc(1, key->len)) == NULL || + hkdf_sha256(key->ptr, hmac_info, secret) < 0 || + hkdf_sha256(key->ptr + SHA256_DIGEST_LENGTH, aes_info, + secret) < 0) { + fido_log_debug("%s: hkdf", __func__); + return -1; + } + break; + default: + fido_log_debug("%s: unknown pin protocol %u", __func__, prot); + return -1; + } + + return 0; +} + +static int +do_ecdh(const fido_dev_t *dev, const es256_sk_t *sk, const es256_pk_t *pk, + fido_blob_t **ecdh) +{ + EVP_PKEY *pk_evp = NULL; + EVP_PKEY *sk_evp = NULL; + EVP_PKEY_CTX *ctx = NULL; + fido_blob_t *secret = NULL; + int ok = -1; + + *ecdh = NULL; + if ((secret = fido_blob_new()) == NULL || + (*ecdh = fido_blob_new()) == NULL) + goto fail; + if ((pk_evp = es256_pk_to_EVP_PKEY(pk)) == NULL || + (sk_evp = es256_sk_to_EVP_PKEY(sk)) == NULL) { + fido_log_debug("%s: es256_to_EVP_PKEY", __func__); + goto fail; + } + if ((ctx = EVP_PKEY_CTX_new(sk_evp, NULL)) == NULL || + EVP_PKEY_derive_init(ctx) <= 0 || + EVP_PKEY_derive_set_peer(ctx, pk_evp) <= 0) { + fido_log_debug("%s: EVP_PKEY_derive_init", __func__); + goto fail; + } + if (EVP_PKEY_derive(ctx, NULL, &secret->len) <= 0 || + (secret->ptr = calloc(1, secret->len)) == NULL || + EVP_PKEY_derive(ctx, secret->ptr, &secret->len) <= 0) { + fido_log_debug("%s: EVP_PKEY_derive", __func__); + goto fail; + } + if (kdf(fido_dev_get_pin_protocol(dev), *ecdh, secret) < 0) { + fido_log_debug("%s: kdf", __func__); + goto fail; + } + + ok = 0; +fail: + if (pk_evp != NULL) + EVP_PKEY_free(pk_evp); + if (sk_evp != NULL) + EVP_PKEY_free(sk_evp); + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + if (ok < 0) + fido_blob_free(ecdh); + + fido_blob_free(&secret); + + return ok; +} + +int +fido_do_ecdh(fido_dev_t *dev, es256_pk_t **pk, fido_blob_t **ecdh, int *ms) +{ + es256_sk_t *sk = NULL; /* our private key */ + es256_pk_t *ak = NULL; /* authenticator's public key */ + int r; + + *pk = NULL; + *ecdh = NULL; + if ((sk = es256_sk_new()) == NULL || (*pk = es256_pk_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (es256_sk_create(sk) < 0 || es256_derive_pk(sk, *pk) < 0) { + fido_log_debug("%s: es256_derive_pk", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((ak = es256_pk_new()) == NULL || + fido_dev_authkey(dev, ak, ms) != FIDO_OK) { + fido_log_debug("%s: fido_dev_authkey", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (do_ecdh(dev, sk, ak, ecdh) < 0) { + fido_log_debug("%s: do_ecdh", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + es256_sk_free(&sk); + es256_pk_free(&ak); + + if (r != FIDO_OK) { + es256_pk_free(pk); + fido_blob_free(ecdh); + } + + return r; +} diff --git a/src/eddsa.c b/src/eddsa.c new file mode 100644 index 0000000..bdb53b1 --- /dev/null +++ b/src/eddsa.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/eddsa.h" + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3070000f +EVP_PKEY * +EVP_PKEY_new_raw_public_key(int type, ENGINE *e, const unsigned char *key, + size_t keylen) +{ + (void)type; + (void)e; + (void)key; + (void)keylen; + + fido_log_debug("%s: unimplemented", __func__); + + return (NULL); +} + +int +EVP_PKEY_get_raw_public_key(const EVP_PKEY *pkey, unsigned char *pub, + size_t *len) +{ + (void)pkey; + (void)pub; + (void)len; + + fido_log_debug("%s: unimplemented", __func__); + + return (0); +} +#endif /* LIBRESSL_VERSION_NUMBER */ + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3040000f +int +EVP_DigestVerify(EVP_MD_CTX *ctx, const unsigned char *sigret, size_t siglen, + const unsigned char *tbs, size_t tbslen) +{ + (void)ctx; + (void)sigret; + (void)siglen; + (void)tbs; + (void)tbslen; + + fido_log_debug("%s: unimplemented", __func__); + + return (0); +} +#endif /* LIBRESSL_VERSION_NUMBER < 0x3040000f */ + +static int +decode_coord(const cbor_item_t *item, void *xy, size_t xy_len) +{ + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false || + cbor_bytestring_length(item) != xy_len) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + memcpy(xy, cbor_bytestring_handle(item), xy_len); + + return (0); +} + +static int +decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + eddsa_pk_t *k = arg; + + if (cbor_isa_negint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) + return (0); /* ignore */ + + switch (cbor_get_uint8(key)) { + case 1: /* x coordinate */ + return (decode_coord(val, &k->x, sizeof(k->x))); + } + + return (0); /* ignore */ +} + +int +eddsa_pk_decode(const cbor_item_t *item, eddsa_pk_t *k) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, k, decode_pubkey_point) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +eddsa_pk_t * +eddsa_pk_new(void) +{ + return (calloc(1, sizeof(eddsa_pk_t))); +} + +void +eddsa_pk_free(eddsa_pk_t **pkp) +{ + eddsa_pk_t *pk; + + if (pkp == NULL || (pk = *pkp) == NULL) + return; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +eddsa_pk_from_ptr(eddsa_pk_t *pk, const void *ptr, size_t len) +{ + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + memcpy(pk, ptr, sizeof(*pk)); + + if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: eddsa_pk_to_EVP_PKEY", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + return (FIDO_OK); +} + +EVP_PKEY * +eddsa_pk_to_EVP_PKEY(const eddsa_pk_t *k) +{ + EVP_PKEY *pkey = NULL; + + if ((pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, k->x, + sizeof(k->x))) == NULL) + fido_log_debug("%s: EVP_PKEY_new_raw_public_key", __func__); + + return (pkey); +} + +int +eddsa_pk_from_EVP_PKEY(eddsa_pk_t *pk, const EVP_PKEY *pkey) +{ + size_t len = 0; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_ED25519) + return (FIDO_ERR_INVALID_ARGUMENT); + if (EVP_PKEY_get_raw_public_key(pkey, NULL, &len) != 1 || + len != sizeof(pk->x)) + return (FIDO_ERR_INTERNAL); + if (EVP_PKEY_get_raw_public_key(pkey, pk->x, &len) != 1 || + len != sizeof(pk->x)) + return (FIDO_ERR_INTERNAL); + + return (FIDO_OK); +} + +int +eddsa_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_MD_CTX *mdctx = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_ED25519) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + /* EVP_DigestVerify needs ints */ + if (dgst->len > INT_MAX || sig->len > INT_MAX) { + fido_log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__, + dgst->len, sig->len); + return (-1); + } + + if ((mdctx = EVP_MD_CTX_new()) == NULL) { + fido_log_debug("%s: EVP_MD_CTX_new", __func__); + goto fail; + } + + if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) { + fido_log_debug("%s: EVP_DigestVerifyInit", __func__); + goto fail; + } + + if (EVP_DigestVerify(mdctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_DigestVerify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_MD_CTX_free(mdctx); + + return (ok); +} + +int +eddsa_pk_verify_sig(const fido_blob_t *dgst, const eddsa_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL || + eddsa_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: eddsa_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/err.c b/src/err.c new file mode 100644 index 0000000..3a6f3e0 --- /dev/null +++ b/src/err.c @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido/err.h" + +const char * +fido_strerr(int n) +{ + switch (n) { + case FIDO_ERR_SUCCESS: + return "FIDO_ERR_SUCCESS"; + case FIDO_ERR_INVALID_COMMAND: + return "FIDO_ERR_INVALID_COMMAND"; + case FIDO_ERR_INVALID_PARAMETER: + return "FIDO_ERR_INVALID_PARAMETER"; + case FIDO_ERR_INVALID_LENGTH: + return "FIDO_ERR_INVALID_LENGTH"; + case FIDO_ERR_INVALID_SEQ: + return "FIDO_ERR_INVALID_SEQ"; + case FIDO_ERR_TIMEOUT: + return "FIDO_ERR_TIMEOUT"; + case FIDO_ERR_CHANNEL_BUSY: + return "FIDO_ERR_CHANNEL_BUSY"; + case FIDO_ERR_LOCK_REQUIRED: + return "FIDO_ERR_LOCK_REQUIRED"; + case FIDO_ERR_INVALID_CHANNEL: + return "FIDO_ERR_INVALID_CHANNEL"; + case FIDO_ERR_CBOR_UNEXPECTED_TYPE: + return "FIDO_ERR_CBOR_UNEXPECTED_TYPE"; + case FIDO_ERR_INVALID_CBOR: + return "FIDO_ERR_INVALID_CBOR"; + case FIDO_ERR_MISSING_PARAMETER: + return "FIDO_ERR_MISSING_PARAMETER"; + case FIDO_ERR_LIMIT_EXCEEDED: + return "FIDO_ERR_LIMIT_EXCEEDED"; + case FIDO_ERR_UNSUPPORTED_EXTENSION: + return "FIDO_ERR_UNSUPPORTED_EXTENSION"; + case FIDO_ERR_FP_DATABASE_FULL: + return "FIDO_ERR_FP_DATABASE_FULL"; + case FIDO_ERR_LARGEBLOB_STORAGE_FULL: + return "FIDO_ERR_LARGEBLOB_STORAGE_FULL"; + case FIDO_ERR_CREDENTIAL_EXCLUDED: + return "FIDO_ERR_CREDENTIAL_EXCLUDED"; + case FIDO_ERR_PROCESSING: + return "FIDO_ERR_PROCESSING"; + case FIDO_ERR_INVALID_CREDENTIAL: + return "FIDO_ERR_INVALID_CREDENTIAL"; + case FIDO_ERR_USER_ACTION_PENDING: + return "FIDO_ERR_USER_ACTION_PENDING"; + case FIDO_ERR_OPERATION_PENDING: + return "FIDO_ERR_OPERATION_PENDING"; + case FIDO_ERR_NO_OPERATIONS: + return "FIDO_ERR_NO_OPERATIONS"; + case FIDO_ERR_UNSUPPORTED_ALGORITHM: + return "FIDO_ERR_UNSUPPORTED_ALGORITHM"; + case FIDO_ERR_OPERATION_DENIED: + return "FIDO_ERR_OPERATION_DENIED"; + case FIDO_ERR_KEY_STORE_FULL: + return "FIDO_ERR_KEY_STORE_FULL"; + case FIDO_ERR_NOT_BUSY: + return "FIDO_ERR_NOT_BUSY"; + case FIDO_ERR_NO_OPERATION_PENDING: + return "FIDO_ERR_NO_OPERATION_PENDING"; + case FIDO_ERR_UNSUPPORTED_OPTION: + return "FIDO_ERR_UNSUPPORTED_OPTION"; + case FIDO_ERR_INVALID_OPTION: + return "FIDO_ERR_INVALID_OPTION"; + case FIDO_ERR_KEEPALIVE_CANCEL: + return "FIDO_ERR_KEEPALIVE_CANCEL"; + case FIDO_ERR_NO_CREDENTIALS: + return "FIDO_ERR_NO_CREDENTIALS"; + case FIDO_ERR_USER_ACTION_TIMEOUT: + return "FIDO_ERR_USER_ACTION_TIMEOUT"; + case FIDO_ERR_NOT_ALLOWED: + return "FIDO_ERR_NOT_ALLOWED"; + case FIDO_ERR_PIN_INVALID: + return "FIDO_ERR_PIN_INVALID"; + case FIDO_ERR_PIN_BLOCKED: + return "FIDO_ERR_PIN_BLOCKED"; + case FIDO_ERR_PIN_AUTH_INVALID: + return "FIDO_ERR_PIN_AUTH_INVALID"; + case FIDO_ERR_PIN_AUTH_BLOCKED: + return "FIDO_ERR_PIN_AUTH_BLOCKED"; + case FIDO_ERR_PIN_NOT_SET: + return "FIDO_ERR_PIN_NOT_SET"; + case FIDO_ERR_PIN_REQUIRED: + return "FIDO_ERR_PIN_REQUIRED"; + case FIDO_ERR_PIN_POLICY_VIOLATION: + return "FIDO_ERR_PIN_POLICY_VIOLATION"; + case FIDO_ERR_PIN_TOKEN_EXPIRED: + return "FIDO_ERR_PIN_TOKEN_EXPIRED"; + case FIDO_ERR_REQUEST_TOO_LARGE: + return "FIDO_ERR_REQUEST_TOO_LARGE"; + case FIDO_ERR_ACTION_TIMEOUT: + return "FIDO_ERR_ACTION_TIMEOUT"; + case FIDO_ERR_UP_REQUIRED: + return "FIDO_ERR_UP_REQUIRED"; + case FIDO_ERR_UV_BLOCKED: + return "FIDO_ERR_UV_BLOCKED"; + case FIDO_ERR_UV_INVALID: + return "FIDO_ERR_UV_INVALID"; + case FIDO_ERR_UNAUTHORIZED_PERM: + return "FIDO_ERR_UNAUTHORIZED_PERM"; + case FIDO_ERR_ERR_OTHER: + return "FIDO_ERR_ERR_OTHER"; + case FIDO_ERR_SPEC_LAST: + return "FIDO_ERR_SPEC_LAST"; + case FIDO_ERR_TX: + return "FIDO_ERR_TX"; + case FIDO_ERR_RX: + return "FIDO_ERR_RX"; + case FIDO_ERR_RX_NOT_CBOR: + return "FIDO_ERR_RX_NOT_CBOR"; + case FIDO_ERR_RX_INVALID_CBOR: + return "FIDO_ERR_RX_INVALID_CBOR"; + case FIDO_ERR_INVALID_PARAM: + return "FIDO_ERR_INVALID_PARAM"; + case FIDO_ERR_INVALID_SIG: + return "FIDO_ERR_INVALID_SIG"; + case FIDO_ERR_INVALID_ARGUMENT: + return "FIDO_ERR_INVALID_ARGUMENT"; + case FIDO_ERR_USER_PRESENCE_REQUIRED: + return "FIDO_ERR_USER_PRESENCE_REQUIRED"; + case FIDO_ERR_NOTFOUND: + return "FIDO_ERR_NOTFOUND"; + case FIDO_ERR_COMPRESS: + return "FIDO_ERR_COMPRESS"; + case FIDO_ERR_INTERNAL: + return "FIDO_ERR_INTERNAL"; + default: + return "FIDO_ERR_UNKNOWN"; + } +} diff --git a/src/es256.c b/src/es256.c new file mode 100644 index 0000000..17efb0a --- /dev/null +++ b/src/es256.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/ecdsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/es256.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get0_EC_KEY(x) EVP_PKEY_get0_EC_KEY((x)) +#else +#define get0_EC_KEY(x) EVP_PKEY_get0((x)) +#endif + +static const int es256_nid = NID_X9_62_prime256v1; + +static int +decode_coord(const cbor_item_t *item, void *xy, size_t xy_len) +{ + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false || + cbor_bytestring_length(item) != xy_len) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + memcpy(xy, cbor_bytestring_handle(item), xy_len); + + return (0); +} + +static int +decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + es256_pk_t *k = arg; + + if (cbor_isa_negint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) + return (0); /* ignore */ + + switch (cbor_get_uint8(key)) { + case 1: /* x coordinate */ + return (decode_coord(val, &k->x, sizeof(k->x))); + case 2: /* y coordinate */ + return (decode_coord(val, &k->y, sizeof(k->y))); + } + + return (0); /* ignore */ +} + +int +es256_pk_decode(const cbor_item_t *item, es256_pk_t *k) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, k, decode_pubkey_point) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +cbor_item_t * +es256_pk_encode(const es256_pk_t *pk, int ecdh) +{ + cbor_item_t *item = NULL; + struct cbor_pair argv[5]; + int alg; + int ok = -1; + + memset(argv, 0, sizeof(argv)); + + if ((item = cbor_new_definite_map(5)) == NULL) + goto fail; + + /* kty */ + if ((argv[0].key = cbor_build_uint8(1)) == NULL || + (argv[0].value = cbor_build_uint8(2)) == NULL || + !cbor_map_add(item, argv[0])) + goto fail; + + /* + * "The COSEAlgorithmIdentifier used is -25 (ECDH-ES + + * HKDF-256) although this is NOT the algorithm actually + * used. Setting this to a different value may result in + * compatibility issues." + */ + if (ecdh) + alg = COSE_ECDH_ES256; + else + alg = COSE_ES256; + + /* alg */ + if ((argv[1].key = cbor_build_uint8(3)) == NULL || + (argv[1].value = cbor_build_negint8((uint8_t)(-alg - 1))) == NULL || + !cbor_map_add(item, argv[1])) + goto fail; + + /* crv */ + if ((argv[2].key = cbor_build_negint8(0)) == NULL || + (argv[2].value = cbor_build_uint8(1)) == NULL || + !cbor_map_add(item, argv[2])) + goto fail; + + /* x */ + if ((argv[3].key = cbor_build_negint8(1)) == NULL || + (argv[3].value = cbor_build_bytestring(pk->x, + sizeof(pk->x))) == NULL || !cbor_map_add(item, argv[3])) + goto fail; + + /* y */ + if ((argv[4].key = cbor_build_negint8(2)) == NULL || + (argv[4].value = cbor_build_bytestring(pk->y, + sizeof(pk->y))) == NULL || !cbor_map_add(item, argv[4])) + goto fail; + + ok = 0; +fail: + if (ok < 0) { + if (item != NULL) { + cbor_decref(&item); + item = NULL; + } + } + + for (size_t i = 0; i < 5; i++) { + if (argv[i].key) + cbor_decref(&argv[i].key); + if (argv[i].value) + cbor_decref(&argv[i].value); + } + + return (item); +} + +es256_sk_t * +es256_sk_new(void) +{ + return (calloc(1, sizeof(es256_sk_t))); +} + +void +es256_sk_free(es256_sk_t **skp) +{ + es256_sk_t *sk; + + if (skp == NULL || (sk = *skp) == NULL) + return; + + freezero(sk, sizeof(*sk)); + *skp = NULL; +} + +es256_pk_t * +es256_pk_new(void) +{ + return (calloc(1, sizeof(es256_pk_t))); +} + +void +es256_pk_free(es256_pk_t **pkp) +{ + es256_pk_t *pk; + + if (pkp == NULL || (pk = *pkp) == NULL) + return; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +es256_pk_from_ptr(es256_pk_t *pk, const void *ptr, size_t len) +{ + const uint8_t *p = ptr; + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (len == sizeof(*pk) + 1 && *p == 0x04) + memcpy(pk, ++p, sizeof(*pk)); /* uncompressed format */ + else + memcpy(pk, ptr, sizeof(*pk)); /* libfido2 x||y format */ + + if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: es256_pk_to_EVP_PKEY", __func__); + explicit_bzero(pk, sizeof(*pk)); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + return (FIDO_OK); +} + +int +es256_pk_set_x(es256_pk_t *pk, const unsigned char *x) +{ + memcpy(pk->x, x, sizeof(pk->x)); + + return (0); +} + +int +es256_pk_set_y(es256_pk_t *pk, const unsigned char *y) +{ + memcpy(pk->y, y, sizeof(pk->y)); + + return (0); +} + +int +es256_sk_create(es256_sk_t *key) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY_CTX *kctx = NULL; + EVP_PKEY *p = NULL; + EVP_PKEY *k = NULL; + const EC_KEY *ec; + const BIGNUM *d; + int n; + int ok = -1; + + if ((pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) == NULL || + EVP_PKEY_paramgen_init(pctx) <= 0 || + EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, es256_nid) <= 0 || + EVP_PKEY_paramgen(pctx, &p) <= 0) { + fido_log_debug("%s: EVP_PKEY_paramgen", __func__); + goto fail; + } + + if ((kctx = EVP_PKEY_CTX_new(p, NULL)) == NULL || + EVP_PKEY_keygen_init(kctx) <= 0 || EVP_PKEY_keygen(kctx, &k) <= 0) { + fido_log_debug("%s: EVP_PKEY_keygen", __func__); + goto fail; + } + + if ((ec = EVP_PKEY_get0_EC_KEY(k)) == NULL || + (d = EC_KEY_get0_private_key(ec)) == NULL || + (n = BN_num_bytes(d)) < 0 || (size_t)n > sizeof(key->d) || + (n = BN_bn2bin(d, key->d)) < 0 || (size_t)n > sizeof(key->d)) { + fido_log_debug("%s: EC_KEY_get0_private_key", __func__); + goto fail; + } + + ok = 0; +fail: + if (p != NULL) + EVP_PKEY_free(p); + if (k != NULL) + EVP_PKEY_free(k); + if (pctx != NULL) + EVP_PKEY_CTX_free(pctx); + if (kctx != NULL) + EVP_PKEY_CTX_free(kctx); + + return (ok); +} + +EVP_PKEY * +es256_pk_to_EVP_PKEY(const es256_pk_t *k) +{ + BN_CTX *bnctx = NULL; + EC_KEY *ec = NULL; + EC_POINT *q = NULL; + EVP_PKEY *pkey = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + const EC_GROUP *g = NULL; + int ok = -1; + + if ((bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((x = BN_CTX_get(bnctx)) == NULL || + (y = BN_CTX_get(bnctx)) == NULL) + goto fail; + + if (BN_bin2bn(k->x, sizeof(k->x), x) == NULL || + BN_bin2bn(k->y, sizeof(k->y), y) == NULL) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL || + (g = EC_KEY_get0_group(ec)) == NULL) { + fido_log_debug("%s: EC_KEY init", __func__); + goto fail; + } + + if ((q = EC_POINT_new(g)) == NULL || + EC_POINT_set_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 || + EC_KEY_set_public_key(ec, q) == 0) { + fido_log_debug("%s: EC_KEY_set_public_key", __func__); + goto fail; + } + + if ((pkey = EVP_PKEY_new()) == NULL || + EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) { + fido_log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__); + goto fail; + } + + ec = NULL; /* at this point, ec belongs to evp */ + + ok = 0; +fail: + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + if (ec != NULL) + EC_KEY_free(ec); + if (q != NULL) + EC_POINT_free(q); + + if (ok < 0 && pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + + return (pkey); +} + +int +es256_pk_from_EC_KEY(es256_pk_t *pk, const EC_KEY *ec) +{ + BN_CTX *bnctx = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + const EC_POINT *q = NULL; + EC_GROUP *g = NULL; + size_t dx; + size_t dy; + int ok = FIDO_ERR_INTERNAL; + int nx; + int ny; + + if ((q = EC_KEY_get0_public_key(ec)) == NULL || + (g = EC_GROUP_new_by_curve_name(es256_nid)) == NULL || + (bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((x = BN_CTX_get(bnctx)) == NULL || + (y = BN_CTX_get(bnctx)) == NULL) + goto fail; + + if (EC_POINT_is_on_curve(g, q, bnctx) != 1) { + fido_log_debug("%s: EC_POINT_is_on_curve", __func__); + ok = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if (EC_POINT_get_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 || + (nx = BN_num_bytes(x)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_num_bytes(y)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: EC_POINT_get_affine_coordinates_GFp", + __func__); + goto fail; + } + + dx = sizeof(pk->x) - (size_t)nx; + dy = sizeof(pk->y) - (size_t)ny; + + if ((nx = BN_bn2bin(x, pk->x + dx)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_bn2bin(y, pk->y + dy)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: BN_bn2bin", __func__); + goto fail; + } + + ok = FIDO_OK; +fail: + EC_GROUP_free(g); + + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + return (ok); +} + +int +es256_pk_from_EVP_PKEY(es256_pk_t *pk, const EVP_PKEY *pkey) +{ + const EC_KEY *ec; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC || + (ec = get0_EC_KEY(pkey)) == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (es256_pk_from_EC_KEY(pk, ec)); +} + +EVP_PKEY * +es256_sk_to_EVP_PKEY(const es256_sk_t *k) +{ + BN_CTX *bnctx = NULL; + EC_KEY *ec = NULL; + EVP_PKEY *pkey = NULL; + BIGNUM *d = NULL; + int ok = -1; + + if ((bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((d = BN_CTX_get(bnctx)) == NULL || + BN_bin2bn(k->d, sizeof(k->d), d) == NULL) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL || + EC_KEY_set_private_key(ec, d) == 0) { + fido_log_debug("%s: EC_KEY_set_private_key", __func__); + goto fail; + } + + if ((pkey = EVP_PKEY_new()) == NULL || + EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) { + fido_log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__); + goto fail; + } + + ec = NULL; /* at this point, ec belongs to evp */ + + ok = 0; +fail: + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + if (ec != NULL) + EC_KEY_free(ec); + + if (ok < 0 && pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + + return (pkey); +} + +int +es256_derive_pk(const es256_sk_t *sk, es256_pk_t *pk) +{ + BIGNUM *d = NULL; + EC_KEY *ec = NULL; + EC_POINT *q = NULL; + const EC_GROUP *g = NULL; + int ok = -1; + + if ((d = BN_bin2bn(sk->d, (int)sizeof(sk->d), NULL)) == NULL || + (ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL || + (g = EC_KEY_get0_group(ec)) == NULL || + (q = EC_POINT_new(g)) == NULL) { + fido_log_debug("%s: get", __func__); + goto fail; + } + + if (EC_POINT_mul(g, q, d, NULL, NULL, NULL) == 0 || + EC_KEY_set_public_key(ec, q) == 0 || + es256_pk_from_EC_KEY(pk, ec) != FIDO_OK) { + fido_log_debug("%s: set", __func__); + goto fail; + } + + ok = 0; +fail: + if (d != NULL) + BN_clear_free(d); + if (q != NULL) + EC_POINT_free(q); + if (ec != NULL) + EC_KEY_free(ec); + + return (ok); +} + +int +es256_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + + return (ok); +} + +int +es256_pk_verify_sig(const fido_blob_t *dgst, const es256_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL || + es256_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: es256_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/es384.c b/src/es384.c new file mode 100644 index 0000000..013d285 --- /dev/null +++ b/src/es384.c @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/ecdsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/es384.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get0_EC_KEY(x) EVP_PKEY_get0_EC_KEY((x)) +#else +#define get0_EC_KEY(x) EVP_PKEY_get0((x)) +#endif + +static int +decode_coord(const cbor_item_t *item, void *xy, size_t xy_len) +{ + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false || + cbor_bytestring_length(item) != xy_len) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + memcpy(xy, cbor_bytestring_handle(item), xy_len); + + return (0); +} + +static int +decode_pubkey_point(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + es384_pk_t *k = arg; + + if (cbor_isa_negint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) + return (0); /* ignore */ + + switch (cbor_get_uint8(key)) { + case 1: /* x coordinate */ + return (decode_coord(val, &k->x, sizeof(k->x))); + case 2: /* y coordinate */ + return (decode_coord(val, &k->y, sizeof(k->y))); + } + + return (0); /* ignore */ +} + +int +es384_pk_decode(const cbor_item_t *item, es384_pk_t *k) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, k, decode_pubkey_point) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +es384_pk_t * +es384_pk_new(void) +{ + return (calloc(1, sizeof(es384_pk_t))); +} + +void +es384_pk_free(es384_pk_t **pkp) +{ + es384_pk_t *pk; + + if (pkp == NULL || (pk = *pkp) == NULL) + return; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +es384_pk_from_ptr(es384_pk_t *pk, const void *ptr, size_t len) +{ + const uint8_t *p = ptr; + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (len == sizeof(*pk) + 1 && *p == 0x04) + memcpy(pk, ++p, sizeof(*pk)); /* uncompressed format */ + else + memcpy(pk, ptr, sizeof(*pk)); /* libfido2 x||y format */ + + if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: es384_pk_to_EVP_PKEY", __func__); + explicit_bzero(pk, sizeof(*pk)); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + return (FIDO_OK); +} + +EVP_PKEY * +es384_pk_to_EVP_PKEY(const es384_pk_t *k) +{ + BN_CTX *bnctx = NULL; + EC_KEY *ec = NULL; + EC_POINT *q = NULL; + EVP_PKEY *pkey = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + const EC_GROUP *g = NULL; + int ok = -1; + + if ((bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((x = BN_CTX_get(bnctx)) == NULL || + (y = BN_CTX_get(bnctx)) == NULL) + goto fail; + + if (BN_bin2bn(k->x, sizeof(k->x), x) == NULL || + BN_bin2bn(k->y, sizeof(k->y), y) == NULL) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((ec = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL || + (g = EC_KEY_get0_group(ec)) == NULL) { + fido_log_debug("%s: EC_KEY init", __func__); + goto fail; + } + + if ((q = EC_POINT_new(g)) == NULL || + EC_POINT_set_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 || + EC_KEY_set_public_key(ec, q) == 0) { + fido_log_debug("%s: EC_KEY_set_public_key", __func__); + goto fail; + } + + if ((pkey = EVP_PKEY_new()) == NULL || + EVP_PKEY_assign_EC_KEY(pkey, ec) == 0) { + fido_log_debug("%s: EVP_PKEY_assign_EC_KEY", __func__); + goto fail; + } + + ec = NULL; /* at this point, ec belongs to evp */ + + ok = 0; +fail: + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + if (ec != NULL) + EC_KEY_free(ec); + if (q != NULL) + EC_POINT_free(q); + + if (ok < 0 && pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + + return (pkey); +} + +int +es384_pk_from_EC_KEY(es384_pk_t *pk, const EC_KEY *ec) +{ + BN_CTX *bnctx = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + const EC_POINT *q = NULL; + EC_GROUP *g = NULL; + size_t dx; + size_t dy; + int ok = FIDO_ERR_INTERNAL; + int nx; + int ny; + + if ((q = EC_KEY_get0_public_key(ec)) == NULL || + (g = EC_GROUP_new_by_curve_name(NID_secp384r1)) == NULL || + (bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((x = BN_CTX_get(bnctx)) == NULL || + (y = BN_CTX_get(bnctx)) == NULL) + goto fail; + + if (EC_POINT_is_on_curve(g, q, bnctx) != 1) { + fido_log_debug("%s: EC_POINT_is_on_curve", __func__); + ok = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if (EC_POINT_get_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 || + (nx = BN_num_bytes(x)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_num_bytes(y)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: EC_POINT_get_affine_coordinates_GFp", + __func__); + goto fail; + } + + dx = sizeof(pk->x) - (size_t)nx; + dy = sizeof(pk->y) - (size_t)ny; + + if ((nx = BN_bn2bin(x, pk->x + dx)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_bn2bin(y, pk->y + dy)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: BN_bn2bin", __func__); + goto fail; + } + + ok = FIDO_OK; +fail: + EC_GROUP_free(g); + + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + return (ok); +} + +int +es384_pk_from_EVP_PKEY(es384_pk_t *pk, const EVP_PKEY *pkey) +{ + const EC_KEY *ec; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC || + (ec = get0_EC_KEY(pkey)) == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (es384_pk_from_EC_KEY(pk, ec)); +} + +int +es384_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + + return (ok); +} + +int +es384_pk_verify_sig(const fido_blob_t *dgst, const es384_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL || + es384_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: es384_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/export.gnu b/src/export.gnu new file mode 100644 index 0000000..ea6ca7d --- /dev/null +++ b/src/export.gnu @@ -0,0 +1,268 @@ +{ + global: + eddsa_pk_free; + eddsa_pk_from_EVP_PKEY; + eddsa_pk_from_ptr; + eddsa_pk_new; + eddsa_pk_to_EVP_PKEY; + es256_pk_free; + es256_pk_from_EC_KEY; + es256_pk_from_EVP_PKEY; + es256_pk_from_ptr; + es256_pk_new; + es256_pk_to_EVP_PKEY; + es384_pk_free; + es384_pk_from_EC_KEY; + es384_pk_from_EVP_PKEY; + es384_pk_from_ptr; + es384_pk_new; + es384_pk_to_EVP_PKEY; + fido_assert_allow_cred; + fido_assert_authdata_len; + fido_assert_authdata_ptr; + fido_assert_authdata_raw_len; + fido_assert_authdata_raw_ptr; + fido_assert_blob_len; + fido_assert_blob_ptr; + fido_assert_clientdata_hash_len; + fido_assert_clientdata_hash_ptr; + fido_assert_count; + fido_assert_empty_allow_list; + fido_assert_flags; + fido_assert_free; + fido_assert_hmac_secret_len; + fido_assert_hmac_secret_ptr; + fido_assert_id_len; + fido_assert_id_ptr; + fido_assert_largeblob_key_len; + fido_assert_largeblob_key_ptr; + fido_assert_new; + fido_assert_rp_id; + fido_assert_set_authdata; + fido_assert_set_authdata_raw; + fido_assert_set_clientdata; + fido_assert_set_clientdata_hash; + fido_assert_set_count; + fido_assert_set_extensions; + fido_assert_set_hmac_salt; + fido_assert_set_hmac_secret; + fido_assert_set_options; + fido_assert_set_rp; + fido_assert_set_sig; + fido_assert_set_up; + fido_assert_set_uv; + fido_assert_set_winhello_appid; + fido_assert_sigcount; + fido_assert_sig_len; + fido_assert_sig_ptr; + fido_assert_user_display_name; + fido_assert_user_icon; + fido_assert_user_id_len; + fido_assert_user_id_ptr; + fido_assert_user_name; + fido_assert_verify; + fido_bio_dev_enroll_begin; + fido_bio_dev_enroll_cancel; + fido_bio_dev_enroll_continue; + fido_bio_dev_enroll_remove; + fido_bio_dev_get_info; + fido_bio_dev_get_template_array; + fido_bio_dev_set_template_name; + fido_bio_enroll_free; + fido_bio_enroll_last_status; + fido_bio_enroll_new; + fido_bio_enroll_remaining_samples; + fido_bio_info_free; + fido_bio_info_max_samples; + fido_bio_info_new; + fido_bio_info_type; + fido_bio_template; + fido_bio_template_array_count; + fido_bio_template_array_free; + fido_bio_template_array_new; + fido_bio_template_free; + fido_bio_template_id_len; + fido_bio_template_id_ptr; + fido_bio_template_name; + fido_bio_template_new; + fido_bio_template_set_id; + fido_bio_template_set_name; + fido_cbor_info_aaguid_len; + fido_cbor_info_aaguid_ptr; + fido_cbor_info_algorithm_cose; + fido_cbor_info_algorithm_count; + fido_cbor_info_algorithm_type; + fido_cbor_info_certs_len; + fido_cbor_info_certs_name_ptr; + fido_cbor_info_certs_value_ptr; + fido_cbor_info_extensions_len; + fido_cbor_info_extensions_ptr; + fido_cbor_info_free; + fido_cbor_info_fwversion; + fido_cbor_info_maxcredbloblen; + fido_cbor_info_maxcredcntlst; + fido_cbor_info_maxcredidlen; + fido_cbor_info_maxlargeblob; + fido_cbor_info_maxmsgsiz; + fido_cbor_info_maxrpid_minpinlen; + fido_cbor_info_minpinlen; + fido_cbor_info_new; + fido_cbor_info_new_pin_required; + fido_cbor_info_options_len; + fido_cbor_info_options_name_ptr; + fido_cbor_info_options_value_ptr; + fido_cbor_info_protocols_len; + fido_cbor_info_protocols_ptr; + fido_cbor_info_rk_remaining; + fido_cbor_info_transports_len; + fido_cbor_info_transports_ptr; + fido_cbor_info_uv_attempts; + fido_cbor_info_uv_modality; + fido_cbor_info_versions_len; + fido_cbor_info_versions_ptr; + fido_cred_attstmt_len; + fido_cred_attstmt_ptr; + fido_cred_authdata_len; + fido_cred_authdata_ptr; + fido_cred_authdata_raw_len; + fido_cred_authdata_raw_ptr; + fido_cred_clientdata_hash_len; + fido_cred_clientdata_hash_ptr; + fido_cred_display_name; + fido_cred_empty_exclude_list; + fido_cred_exclude; + fido_cred_flags; + fido_cred_largeblob_key_len; + fido_cred_largeblob_key_ptr; + fido_cred_sigcount; + fido_cred_fmt; + fido_cred_free; + fido_cred_id_len; + fido_cred_id_ptr; + fido_cred_aaguid_len; + fido_cred_aaguid_ptr; + fido_credman_del_dev_rk; + fido_credman_get_dev_metadata; + fido_credman_get_dev_rk; + fido_credman_get_dev_rp; + fido_credman_metadata_free; + fido_credman_metadata_new; + fido_credman_rk; + fido_credman_rk_count; + fido_credman_rk_existing; + fido_credman_rk_free; + fido_credman_rk_new; + fido_credman_rk_remaining; + fido_credman_rp_count; + fido_credman_rp_free; + fido_credman_rp_id; + fido_credman_rp_id_hash_len; + fido_credman_rp_id_hash_ptr; + fido_credman_rp_name; + fido_credman_rp_new; + fido_credman_set_dev_rk; + fido_cred_new; + fido_cred_pin_minlen; + fido_cred_prot; + fido_cred_pubkey_len; + fido_cred_pubkey_ptr; + fido_cred_rp_id; + fido_cred_rp_name; + fido_cred_set_attstmt; + fido_cred_set_authdata; + fido_cred_set_authdata_raw; + fido_cred_set_blob; + fido_cred_set_clientdata; + fido_cred_set_clientdata_hash; + fido_cred_set_extensions; + fido_cred_set_fmt; + fido_cred_set_id; + fido_cred_set_options; + fido_cred_set_pin_minlen; + fido_cred_set_prot; + fido_cred_set_rk; + fido_cred_set_rp; + fido_cred_set_sig; + fido_cred_set_type; + fido_cred_set_user; + fido_cred_set_uv; + fido_cred_set_x509; + fido_cred_sig_len; + fido_cred_sig_ptr; + fido_cred_type; + fido_cred_user_id_len; + fido_cred_user_id_ptr; + fido_cred_user_name; + fido_cred_verify; + fido_cred_verify_self; + fido_cred_x5c_len; + fido_cred_x5c_ptr; + fido_dev_build; + fido_dev_cancel; + fido_dev_close; + fido_dev_enable_entattest; + fido_dev_flags; + fido_dev_force_fido2; + fido_dev_force_pin_change; + fido_dev_force_u2f; + fido_dev_free; + fido_dev_get_assert; + fido_dev_get_cbor_info; + fido_dev_get_retry_count; + fido_dev_get_uv_retry_count; + fido_dev_get_touch_begin; + fido_dev_get_touch_status; + fido_dev_has_pin; + fido_dev_has_uv; + fido_dev_info_free; + fido_dev_info_manifest; + fido_dev_info_manufacturer_string; + fido_dev_info_new; + fido_dev_info_path; + fido_dev_info_product; + fido_dev_info_product_string; + fido_dev_info_ptr; + fido_dev_info_set; + fido_dev_info_vendor; + fido_dev_io_handle; + fido_dev_is_fido2; + fido_dev_is_winhello; + fido_dev_major; + fido_dev_make_cred; + fido_dev_minor; + fido_dev_new; + fido_dev_new_with_info; + fido_dev_open; + fido_dev_open_with_info; + fido_dev_protocol; + fido_dev_reset; + fido_dev_set_io_functions; + fido_dev_set_pin; + fido_dev_set_pin_minlen; + fido_dev_set_pin_minlen_rpid; + fido_dev_set_sigmask; + fido_dev_set_timeout; + fido_dev_set_transport_functions; + fido_dev_supports_cred_prot; + fido_dev_supports_credman; + fido_dev_supports_permissions; + fido_dev_supports_pin; + fido_dev_supports_uv; + fido_dev_toggle_always_uv; + fido_dev_largeblob_get; + fido_dev_largeblob_get_array; + fido_dev_largeblob_remove; + fido_dev_largeblob_set; + fido_dev_largeblob_set_array; + fido_init; + fido_set_log_handler; + fido_strerr; + rs256_pk_free; + rs256_pk_from_ptr; + rs256_pk_from_EVP_PKEY; + rs256_pk_from_RSA; + rs256_pk_new; + rs256_pk_to_EVP_PKEY; + local: + *; +}; diff --git a/src/export.llvm b/src/export.llvm new file mode 100644 index 0000000..2a92381 --- /dev/null +++ b/src/export.llvm @@ -0,0 +1,263 @@ +_eddsa_pk_free +_eddsa_pk_from_EVP_PKEY +_eddsa_pk_from_ptr +_eddsa_pk_new +_eddsa_pk_to_EVP_PKEY +_es256_pk_free +_es256_pk_from_EC_KEY +_es256_pk_from_EVP_PKEY +_es256_pk_from_ptr +_es256_pk_new +_es256_pk_to_EVP_PKEY +_es384_pk_free +_es384_pk_from_EC_KEY +_es384_pk_from_EVP_PKEY +_es384_pk_from_ptr +_es384_pk_new +_es384_pk_to_EVP_PKEY +_fido_assert_allow_cred +_fido_assert_authdata_len +_fido_assert_authdata_ptr +_fido_assert_authdata_raw_len +_fido_assert_authdata_raw_ptr +_fido_assert_blob_len +_fido_assert_blob_ptr +_fido_assert_clientdata_hash_len +_fido_assert_clientdata_hash_ptr +_fido_assert_count +_fido_assert_empty_allow_list +_fido_assert_flags +_fido_assert_free +_fido_assert_hmac_secret_len +_fido_assert_hmac_secret_ptr +_fido_assert_id_len +_fido_assert_id_ptr +_fido_assert_largeblob_key_len +_fido_assert_largeblob_key_ptr +_fido_assert_new +_fido_assert_rp_id +_fido_assert_set_authdata +_fido_assert_set_authdata_raw +_fido_assert_set_clientdata +_fido_assert_set_clientdata_hash +_fido_assert_set_count +_fido_assert_set_extensions +_fido_assert_set_hmac_salt +_fido_assert_set_hmac_secret +_fido_assert_set_options +_fido_assert_set_rp +_fido_assert_set_sig +_fido_assert_set_up +_fido_assert_set_uv +_fido_assert_set_winhello_appid +_fido_assert_sigcount +_fido_assert_sig_len +_fido_assert_sig_ptr +_fido_assert_user_display_name +_fido_assert_user_icon +_fido_assert_user_id_len +_fido_assert_user_id_ptr +_fido_assert_user_name +_fido_assert_verify +_fido_bio_dev_enroll_begin +_fido_bio_dev_enroll_cancel +_fido_bio_dev_enroll_continue +_fido_bio_dev_enroll_remove +_fido_bio_dev_get_info +_fido_bio_dev_get_template_array +_fido_bio_dev_set_template_name +_fido_bio_enroll_free +_fido_bio_enroll_last_status +_fido_bio_enroll_new +_fido_bio_enroll_remaining_samples +_fido_bio_info_free +_fido_bio_info_max_samples +_fido_bio_info_new +_fido_bio_info_type +_fido_bio_template +_fido_bio_template_array_count +_fido_bio_template_array_free +_fido_bio_template_array_new +_fido_bio_template_free +_fido_bio_template_id_len +_fido_bio_template_id_ptr +_fido_bio_template_name +_fido_bio_template_new +_fido_bio_template_set_id +_fido_bio_template_set_name +_fido_cbor_info_aaguid_len +_fido_cbor_info_aaguid_ptr +_fido_cbor_info_algorithm_cose +_fido_cbor_info_algorithm_count +_fido_cbor_info_algorithm_type +_fido_cbor_info_certs_len +_fido_cbor_info_certs_name_ptr +_fido_cbor_info_certs_value_ptr +_fido_cbor_info_extensions_len +_fido_cbor_info_extensions_ptr +_fido_cbor_info_free +_fido_cbor_info_fwversion +_fido_cbor_info_maxcredbloblen +_fido_cbor_info_maxcredcntlst +_fido_cbor_info_maxcredidlen +_fido_cbor_info_maxlargeblob +_fido_cbor_info_maxmsgsiz +_fido_cbor_info_maxrpid_minpinlen +_fido_cbor_info_minpinlen +_fido_cbor_info_new +_fido_cbor_info_new_pin_required +_fido_cbor_info_options_len +_fido_cbor_info_options_name_ptr +_fido_cbor_info_options_value_ptr +_fido_cbor_info_protocols_len +_fido_cbor_info_protocols_ptr +_fido_cbor_info_rk_remaining +_fido_cbor_info_transports_len +_fido_cbor_info_transports_ptr +_fido_cbor_info_uv_attempts +_fido_cbor_info_uv_modality +_fido_cbor_info_versions_len +_fido_cbor_info_versions_ptr +_fido_cred_attstmt_len +_fido_cred_attstmt_ptr +_fido_cred_authdata_len +_fido_cred_authdata_ptr +_fido_cred_authdata_raw_len +_fido_cred_authdata_raw_ptr +_fido_cred_clientdata_hash_len +_fido_cred_clientdata_hash_ptr +_fido_cred_display_name +_fido_cred_empty_exclude_list +_fido_cred_exclude +_fido_cred_flags +_fido_cred_largeblob_key_len +_fido_cred_largeblob_key_ptr +_fido_cred_sigcount +_fido_cred_fmt +_fido_cred_free +_fido_cred_id_len +_fido_cred_id_ptr +_fido_cred_aaguid_len +_fido_cred_aaguid_ptr +_fido_credman_del_dev_rk +_fido_credman_get_dev_metadata +_fido_credman_get_dev_rk +_fido_credman_get_dev_rp +_fido_credman_metadata_free +_fido_credman_metadata_new +_fido_credman_rk +_fido_credman_rk_count +_fido_credman_rk_existing +_fido_credman_rk_free +_fido_credman_rk_new +_fido_credman_rk_remaining +_fido_credman_rp_count +_fido_credman_rp_free +_fido_credman_rp_id +_fido_credman_rp_id_hash_len +_fido_credman_rp_id_hash_ptr +_fido_credman_rp_name +_fido_credman_rp_new +_fido_credman_set_dev_rk +_fido_cred_new +_fido_cred_pin_minlen +_fido_cred_prot +_fido_cred_pubkey_len +_fido_cred_pubkey_ptr +_fido_cred_rp_id +_fido_cred_rp_name +_fido_cred_set_attstmt +_fido_cred_set_authdata +_fido_cred_set_authdata_raw +_fido_cred_set_blob +_fido_cred_set_clientdata +_fido_cred_set_clientdata_hash +_fido_cred_set_extensions +_fido_cred_set_fmt +_fido_cred_set_id +_fido_cred_set_options +_fido_cred_set_pin_minlen +_fido_cred_set_prot +_fido_cred_set_rk +_fido_cred_set_rp +_fido_cred_set_sig +_fido_cred_set_type +_fido_cred_set_user +_fido_cred_set_uv +_fido_cred_set_x509 +_fido_cred_sig_len +_fido_cred_sig_ptr +_fido_cred_type +_fido_cred_user_id_len +_fido_cred_user_id_ptr +_fido_cred_user_name +_fido_cred_verify +_fido_cred_verify_self +_fido_cred_x5c_len +_fido_cred_x5c_ptr +_fido_dev_build +_fido_dev_cancel +_fido_dev_close +_fido_dev_enable_entattest +_fido_dev_flags +_fido_dev_force_fido2 +_fido_dev_force_pin_change +_fido_dev_force_u2f +_fido_dev_free +_fido_dev_get_assert +_fido_dev_get_cbor_info +_fido_dev_get_retry_count +_fido_dev_get_uv_retry_count +_fido_dev_get_touch_begin +_fido_dev_get_touch_status +_fido_dev_has_pin +_fido_dev_has_uv +_fido_dev_info_free +_fido_dev_info_manifest +_fido_dev_info_manufacturer_string +_fido_dev_info_new +_fido_dev_info_path +_fido_dev_info_product +_fido_dev_info_product_string +_fido_dev_info_ptr +_fido_dev_info_set +_fido_dev_info_vendor +_fido_dev_io_handle +_fido_dev_is_fido2 +_fido_dev_is_winhello +_fido_dev_major +_fido_dev_make_cred +_fido_dev_minor +_fido_dev_new +_fido_dev_new_with_info +_fido_dev_open +_fido_dev_open_with_info +_fido_dev_protocol +_fido_dev_reset +_fido_dev_set_io_functions +_fido_dev_set_pin +_fido_dev_set_pin_minlen +_fido_dev_set_pin_minlen_rpid +_fido_dev_set_sigmask +_fido_dev_set_timeout +_fido_dev_set_transport_functions +_fido_dev_supports_cred_prot +_fido_dev_supports_credman +_fido_dev_supports_permissions +_fido_dev_supports_pin +_fido_dev_supports_uv +_fido_dev_toggle_always_uv +_fido_dev_largeblob_get +_fido_dev_largeblob_get_array +_fido_dev_largeblob_remove +_fido_dev_largeblob_set +_fido_dev_largeblob_set_array +_fido_init +_fido_set_log_handler +_fido_strerr +_rs256_pk_free +_rs256_pk_from_ptr +_rs256_pk_from_EVP_PKEY +_rs256_pk_from_RSA +_rs256_pk_new +_rs256_pk_to_EVP_PKEY diff --git a/src/export.msvc b/src/export.msvc new file mode 100644 index 0000000..c5b2edc --- /dev/null +++ b/src/export.msvc @@ -0,0 +1,264 @@ +EXPORTS +eddsa_pk_free +eddsa_pk_from_EVP_PKEY +eddsa_pk_from_ptr +eddsa_pk_new +eddsa_pk_to_EVP_PKEY +es256_pk_free +es256_pk_from_EC_KEY +es256_pk_from_EVP_PKEY +es256_pk_from_ptr +es256_pk_new +es256_pk_to_EVP_PKEY +es384_pk_free +es384_pk_from_EC_KEY +es384_pk_from_EVP_PKEY +es384_pk_from_ptr +es384_pk_new +es384_pk_to_EVP_PKEY +fido_assert_allow_cred +fido_assert_authdata_len +fido_assert_authdata_ptr +fido_assert_authdata_raw_len +fido_assert_authdata_raw_ptr +fido_assert_blob_len +fido_assert_blob_ptr +fido_assert_clientdata_hash_len +fido_assert_clientdata_hash_ptr +fido_assert_count +fido_assert_empty_allow_list +fido_assert_flags +fido_assert_free +fido_assert_hmac_secret_len +fido_assert_hmac_secret_ptr +fido_assert_id_len +fido_assert_id_ptr +fido_assert_largeblob_key_len +fido_assert_largeblob_key_ptr +fido_assert_new +fido_assert_rp_id +fido_assert_set_authdata +fido_assert_set_authdata_raw +fido_assert_set_clientdata +fido_assert_set_clientdata_hash +fido_assert_set_count +fido_assert_set_extensions +fido_assert_set_hmac_salt +fido_assert_set_hmac_secret +fido_assert_set_options +fido_assert_set_rp +fido_assert_set_sig +fido_assert_set_up +fido_assert_set_uv +fido_assert_set_winhello_appid +fido_assert_sigcount +fido_assert_sig_len +fido_assert_sig_ptr +fido_assert_user_display_name +fido_assert_user_icon +fido_assert_user_id_len +fido_assert_user_id_ptr +fido_assert_user_name +fido_assert_verify +fido_bio_dev_enroll_begin +fido_bio_dev_enroll_cancel +fido_bio_dev_enroll_continue +fido_bio_dev_enroll_remove +fido_bio_dev_get_info +fido_bio_dev_get_template_array +fido_bio_dev_set_template_name +fido_bio_enroll_free +fido_bio_enroll_last_status +fido_bio_enroll_new +fido_bio_enroll_remaining_samples +fido_bio_info_free +fido_bio_info_max_samples +fido_bio_info_new +fido_bio_info_type +fido_bio_template +fido_bio_template_array_count +fido_bio_template_array_free +fido_bio_template_array_new +fido_bio_template_free +fido_bio_template_id_len +fido_bio_template_id_ptr +fido_bio_template_name +fido_bio_template_new +fido_bio_template_set_id +fido_bio_template_set_name +fido_cbor_info_aaguid_len +fido_cbor_info_aaguid_ptr +fido_cbor_info_algorithm_cose +fido_cbor_info_algorithm_count +fido_cbor_info_algorithm_type +fido_cbor_info_certs_len +fido_cbor_info_certs_name_ptr +fido_cbor_info_certs_value_ptr +fido_cbor_info_extensions_len +fido_cbor_info_extensions_ptr +fido_cbor_info_free +fido_cbor_info_fwversion +fido_cbor_info_maxcredbloblen +fido_cbor_info_maxcredcntlst +fido_cbor_info_maxcredidlen +fido_cbor_info_maxlargeblob +fido_cbor_info_maxmsgsiz +fido_cbor_info_maxrpid_minpinlen +fido_cbor_info_minpinlen +fido_cbor_info_new +fido_cbor_info_new_pin_required +fido_cbor_info_options_len +fido_cbor_info_options_name_ptr +fido_cbor_info_options_value_ptr +fido_cbor_info_protocols_len +fido_cbor_info_protocols_ptr +fido_cbor_info_rk_remaining +fido_cbor_info_transports_len +fido_cbor_info_transports_ptr +fido_cbor_info_uv_attempts +fido_cbor_info_uv_modality +fido_cbor_info_versions_len +fido_cbor_info_versions_ptr +fido_cred_attstmt_len +fido_cred_attstmt_ptr +fido_cred_authdata_len +fido_cred_authdata_ptr +fido_cred_authdata_raw_len +fido_cred_authdata_raw_ptr +fido_cred_clientdata_hash_len +fido_cred_clientdata_hash_ptr +fido_cred_display_name +fido_cred_empty_exclude_list +fido_cred_exclude +fido_cred_flags +fido_cred_largeblob_key_len +fido_cred_largeblob_key_ptr +fido_cred_sigcount +fido_cred_fmt +fido_cred_free +fido_cred_id_len +fido_cred_id_ptr +fido_cred_aaguid_len +fido_cred_aaguid_ptr +fido_credman_del_dev_rk +fido_credman_get_dev_metadata +fido_credman_get_dev_rk +fido_credman_get_dev_rp +fido_credman_metadata_free +fido_credman_metadata_new +fido_credman_rk +fido_credman_rk_count +fido_credman_rk_existing +fido_credman_rk_free +fido_credman_rk_new +fido_credman_rk_remaining +fido_credman_rp_count +fido_credman_rp_free +fido_credman_rp_id +fido_credman_rp_id_hash_len +fido_credman_rp_id_hash_ptr +fido_credman_rp_name +fido_credman_rp_new +fido_credman_set_dev_rk +fido_cred_new +fido_cred_pin_minlen +fido_cred_prot +fido_cred_pubkey_len +fido_cred_pubkey_ptr +fido_cred_rp_id +fido_cred_rp_name +fido_cred_set_attstmt +fido_cred_set_authdata +fido_cred_set_authdata_raw +fido_cred_set_blob +fido_cred_set_clientdata +fido_cred_set_clientdata_hash +fido_cred_set_extensions +fido_cred_set_fmt +fido_cred_set_id +fido_cred_set_options +fido_cred_set_pin_minlen +fido_cred_set_prot +fido_cred_set_rk +fido_cred_set_rp +fido_cred_set_sig +fido_cred_set_type +fido_cred_set_user +fido_cred_set_uv +fido_cred_set_x509 +fido_cred_sig_len +fido_cred_sig_ptr +fido_cred_type +fido_cred_user_id_len +fido_cred_user_id_ptr +fido_cred_user_name +fido_cred_verify +fido_cred_verify_self +fido_cred_x5c_len +fido_cred_x5c_ptr +fido_dev_build +fido_dev_cancel +fido_dev_close +fido_dev_enable_entattest +fido_dev_flags +fido_dev_force_fido2 +fido_dev_force_pin_change +fido_dev_force_u2f +fido_dev_free +fido_dev_get_assert +fido_dev_get_cbor_info +fido_dev_get_retry_count +fido_dev_get_uv_retry_count +fido_dev_get_touch_begin +fido_dev_get_touch_status +fido_dev_has_pin +fido_dev_has_uv +fido_dev_info_free +fido_dev_info_manifest +fido_dev_info_manufacturer_string +fido_dev_info_new +fido_dev_info_path +fido_dev_info_product +fido_dev_info_product_string +fido_dev_info_ptr +fido_dev_info_set +fido_dev_info_vendor +fido_dev_io_handle +fido_dev_is_fido2 +fido_dev_is_winhello +fido_dev_major +fido_dev_make_cred +fido_dev_minor +fido_dev_new +fido_dev_new_with_info +fido_dev_open +fido_dev_open_with_info +fido_dev_protocol +fido_dev_reset +fido_dev_set_io_functions +fido_dev_set_pin +fido_dev_set_pin_minlen +fido_dev_set_pin_minlen_rpid +fido_dev_set_sigmask +fido_dev_set_timeout +fido_dev_set_transport_functions +fido_dev_supports_cred_prot +fido_dev_supports_credman +fido_dev_supports_permissions +fido_dev_supports_pin +fido_dev_supports_uv +fido_dev_toggle_always_uv +fido_dev_largeblob_get +fido_dev_largeblob_get_array +fido_dev_largeblob_remove +fido_dev_largeblob_set +fido_dev_largeblob_set_array +fido_init +fido_set_log_handler +fido_strerr +rs256_pk_free +rs256_pk_from_ptr +rs256_pk_from_EVP_PKEY +rs256_pk_from_RSA +rs256_pk_new +rs256_pk_to_EVP_PKEY diff --git a/src/extern.h b/src/extern.h new file mode 100644 index 0000000..1bc95b2 --- /dev/null +++ b/src/extern.h @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _EXTERN_H +#define _EXTERN_H + +#ifdef __MINGW32__ +#include <sys/types.h> +#endif + +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif + +#include <stdint.h> + +#include "fido/types.h" +#include "blob.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* aes256 */ +int aes256_cbc_dec(const fido_dev_t *dev, const fido_blob_t *, + const fido_blob_t *, fido_blob_t *); +int aes256_cbc_enc(const fido_dev_t *dev, const fido_blob_t *, + const fido_blob_t *, fido_blob_t *); +int aes256_gcm_dec(const fido_blob_t *, const fido_blob_t *, + const fido_blob_t *, const fido_blob_t *, fido_blob_t *); +int aes256_gcm_enc(const fido_blob_t *, const fido_blob_t *, + const fido_blob_t *, const fido_blob_t *, fido_blob_t *); + +/* cbor encoding functions */ +cbor_item_t *cbor_build_uint(const uint64_t); +cbor_item_t *cbor_flatten_vector(cbor_item_t **, size_t); +cbor_item_t *cbor_encode_assert_opt(fido_opt_t, fido_opt_t); +cbor_item_t *cbor_encode_change_pin_auth(const fido_dev_t *, + const fido_blob_t *, const fido_blob_t *, const fido_blob_t *); +cbor_item_t *cbor_encode_cred_ext(const fido_cred_ext_t *, const fido_blob_t *); +cbor_item_t *cbor_encode_assert_ext(fido_dev_t *, + const fido_assert_ext_t *, const fido_blob_t *, const es256_pk_t *); +cbor_item_t *cbor_encode_cred_opt(fido_opt_t, fido_opt_t); +cbor_item_t *cbor_encode_pin_auth(const fido_dev_t *, const fido_blob_t *, + const fido_blob_t *); +cbor_item_t *cbor_encode_pin_opt(const fido_dev_t *); +cbor_item_t *cbor_encode_pubkey(const fido_blob_t *); +cbor_item_t *cbor_encode_pubkey_list(const fido_blob_array_t *); +cbor_item_t *cbor_encode_pubkey_param(int); +cbor_item_t *cbor_encode_rp_entity(const fido_rp_t *); +cbor_item_t *cbor_encode_str_array(const fido_str_array_t *); +cbor_item_t *cbor_encode_user_entity(const fido_user_t *); +cbor_item_t *es256_pk_encode(const es256_pk_t *, int); + +/* cbor decoding functions */ +int cbor_decode_attstmt(const cbor_item_t *, fido_attstmt_t *); +int cbor_decode_bool(const cbor_item_t *, bool *); +int cbor_decode_cred_authdata(const cbor_item_t *, int, fido_blob_t *, + fido_authdata_t *, fido_attcred_t *, fido_cred_ext_t *); +int cbor_decode_assert_authdata(const cbor_item_t *, fido_blob_t *, + fido_authdata_t *, fido_assert_extattr_t *); +int cbor_decode_cred_id(const cbor_item_t *, fido_blob_t *); +int cbor_decode_fmt(const cbor_item_t *, char **); +int cbor_decode_pubkey(const cbor_item_t *, int *, void *); +int cbor_decode_rp_entity(const cbor_item_t *, fido_rp_t *); +int cbor_decode_uint64(const cbor_item_t *, uint64_t *); +int cbor_decode_user(const cbor_item_t *, fido_user_t *); +int es256_pk_decode(const cbor_item_t *, es256_pk_t *); +int es384_pk_decode(const cbor_item_t *, es384_pk_t *); +int rs256_pk_decode(const cbor_item_t *, rs256_pk_t *); +int eddsa_pk_decode(const cbor_item_t *, eddsa_pk_t *); + +/* auxiliary cbor routines */ +int cbor_add_bool(cbor_item_t *, const char *, fido_opt_t); +int cbor_add_bytestring(cbor_item_t *, const char *, const unsigned char *, + size_t); +int cbor_add_string(cbor_item_t *, const char *, const char *); +int cbor_array_iter(const cbor_item_t *, void *, int(*)(const cbor_item_t *, + void *)); +int cbor_build_frame(uint8_t, cbor_item_t *[], size_t, fido_blob_t *); +int cbor_bytestring_copy(const cbor_item_t *, unsigned char **, size_t *); +int cbor_map_iter(const cbor_item_t *, void *, int(*)(const cbor_item_t *, + const cbor_item_t *, void *)); +int cbor_string_copy(const cbor_item_t *, char **); +int cbor_parse_reply(const unsigned char *, size_t, void *, + int(*)(const cbor_item_t *, const cbor_item_t *, void *)); +int cbor_add_uv_params(fido_dev_t *, uint8_t, const fido_blob_t *, + const es256_pk_t *, const fido_blob_t *, const char *, const char *, + cbor_item_t **, cbor_item_t **, int *); +void cbor_vector_free(cbor_item_t **, size_t); +int cbor_array_append(cbor_item_t **, cbor_item_t *); +int cbor_array_drop(cbor_item_t **, size_t); + +/* deflate */ +int fido_compress(fido_blob_t *, const fido_blob_t *); +int fido_uncompress(fido_blob_t *, const fido_blob_t *, size_t); + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +/* buf */ +int fido_buf_read(const unsigned char **, size_t *, void *, size_t); +int fido_buf_write(unsigned char **, size_t *, const void *, size_t); + +/* hid i/o */ +void *fido_hid_open(const char *); +void fido_hid_close(void *); +int fido_hid_read(void *, unsigned char *, size_t, int); +int fido_hid_write(void *, const unsigned char *, size_t); +int fido_hid_get_usage(const uint8_t *, size_t, uint32_t *); +int fido_hid_get_report_len(const uint8_t *, size_t, size_t *, size_t *); +int fido_hid_unix_open(const char *); +int fido_hid_unix_wait(int, int, const fido_sigset_t *); +int fido_hid_set_sigmask(void *, const fido_sigset_t *); +size_t fido_hid_report_in_len(void *); +size_t fido_hid_report_out_len(void *); + +/* nfc i/o */ +bool fido_is_nfc(const char *); +bool nfc_is_fido(const char *); +void *fido_nfc_open(const char *); +void fido_nfc_close(void *); +int fido_nfc_read(void *, unsigned char *, size_t, int); +int fido_nfc_write(void *, const unsigned char *, size_t); +int fido_nfc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int); +int fido_nfc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t); +int fido_nfc_set_sigmask(void *, const fido_sigset_t *); +int fido_dev_set_nfc(fido_dev_t *); + +/* pcsc i/o */ +bool fido_is_pcsc(const char *); +void *fido_pcsc_open(const char *); +void fido_pcsc_close(void *); +int fido_pcsc_read(void *, unsigned char *, size_t, int); +int fido_pcsc_write(void *, const unsigned char *, size_t); +int fido_pcsc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int); +int fido_pcsc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t); +int fido_dev_set_pcsc(fido_dev_t *); + +/* windows hello */ +int fido_winhello_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_winhello_open(fido_dev_t *); +int fido_winhello_close(fido_dev_t *); +int fido_winhello_cancel(fido_dev_t *); +int fido_winhello_get_assert(fido_dev_t *, fido_assert_t *, const char *, int); +int fido_winhello_get_cbor_info(fido_dev_t *, fido_cbor_info_t *); +int fido_winhello_make_cred(fido_dev_t *, fido_cred_t *, const char *, int); + +/* generic i/o */ +int fido_rx_cbor_status(fido_dev_t *, int *); +int fido_rx(fido_dev_t *, uint8_t, void *, size_t, int *); +int fido_tx(fido_dev_t *, uint8_t, const void *, size_t, int *); + +/* log */ +#ifdef FIDO_NO_DIAGNOSTIC +#define fido_log_init(...) do { /* nothing */ } while (0) +#define fido_log_debug(...) do { /* nothing */ } while (0) +#define fido_log_xxd(...) do { /* nothing */ } while (0) +#define fido_log_error(...) do { /* nothing */ } while (0) +#else +#ifdef __GNUC__ +void fido_log_init(void); +void fido_log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void fido_log_xxd(const void *, size_t, const char *, ...) + __attribute__((__format__ (printf, 3, 4))); +void fido_log_error(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +#else +void fido_log_init(void); +void fido_log_debug(const char *, ...); +void fido_log_xxd(const void *, size_t, const char *, ...); +void fido_log_error(int, const char *, ...); +#endif /* __GNUC__ */ +#endif /* FIDO_NO_DIAGNOSTIC */ + +/* u2f */ +int u2f_register(fido_dev_t *, fido_cred_t *, int *); +int u2f_authenticate(fido_dev_t *, fido_assert_t *, int *); +int u2f_get_touch_begin(fido_dev_t *, int *); +int u2f_get_touch_status(fido_dev_t *, int *, int *); + +/* unexposed fido ops */ +uint8_t fido_dev_get_pin_protocol(const fido_dev_t *); +int fido_dev_authkey(fido_dev_t *, es256_pk_t *, int *); +int fido_dev_get_cbor_info_wait(fido_dev_t *, fido_cbor_info_t *, int *); +int fido_dev_get_uv_token(fido_dev_t *, uint8_t, const char *, + const fido_blob_t *, const es256_pk_t *, const char *, fido_blob_t *, + int *); +uint64_t fido_dev_maxmsgsize(const fido_dev_t *); +int fido_do_ecdh(fido_dev_t *, es256_pk_t **, fido_blob_t **, int *); + +/* types */ +void fido_algo_array_free(fido_algo_array_t *); +void fido_byte_array_free(fido_byte_array_t *); +void fido_cert_array_free(fido_cert_array_t *); +void fido_opt_array_free(fido_opt_array_t *); +void fido_str_array_free(fido_str_array_t *); +void fido_algo_free(fido_algo_t *); +int fido_str_array_pack(fido_str_array_t *, const char * const *, size_t); + +/* misc */ +void fido_assert_reset_rx(fido_assert_t *); +void fido_assert_reset_tx(fido_assert_t *); +void fido_cred_reset_rx(fido_cred_t *); +void fido_cred_reset_tx(fido_cred_t *); +void fido_cbor_info_reset(fido_cbor_info_t *); +int fido_blob_serialise(fido_blob_t *, const cbor_item_t *); +int fido_check_flags(uint8_t, fido_opt_t, fido_opt_t); +int fido_check_rp_id(const char *, const unsigned char *); +int fido_get_random(void *, size_t); +int fido_sha256(fido_blob_t *, const u_char *, size_t); +int fido_time_now(struct timespec *); +int fido_time_delta(const struct timespec *, int *); +int fido_to_uint64(const char *, int, uint64_t *); + +/* crypto */ +int es256_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int es384_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int rs256_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int eddsa_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int rs1_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int es256_pk_verify_sig(const fido_blob_t *, const es256_pk_t *, + const fido_blob_t *); +int es384_pk_verify_sig(const fido_blob_t *, const es384_pk_t *, + const fido_blob_t *); +int rs256_pk_verify_sig(const fido_blob_t *, const rs256_pk_t *, + const fido_blob_t *); +int eddsa_pk_verify_sig(const fido_blob_t *, const eddsa_pk_t *, + const fido_blob_t *); +int fido_get_signed_hash(int, fido_blob_t *, const fido_blob_t *, + const fido_blob_t *); +int fido_get_signed_hash_tpm(fido_blob_t *, const fido_blob_t *, + const fido_blob_t *, const fido_attstmt_t *, const fido_attcred_t *); + +/* device manifest functions */ +int fido_hid_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_nfc_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_pcsc_manifest(fido_dev_info_t *, size_t, size_t *); + +/* fuzzing instrumentation */ +#ifdef FIDO_FUZZ +uint32_t uniform_random(uint32_t); +#endif + +/* internal device capability flags */ +#define FIDO_DEV_PIN_SET 0x001 +#define FIDO_DEV_PIN_UNSET 0x002 +#define FIDO_DEV_CRED_PROT 0x004 +#define FIDO_DEV_CREDMAN 0x008 +#define FIDO_DEV_PIN_PROTOCOL1 0x010 +#define FIDO_DEV_PIN_PROTOCOL2 0x020 +#define FIDO_DEV_UV_SET 0x040 +#define FIDO_DEV_UV_UNSET 0x080 +#define FIDO_DEV_TOKEN_PERMS 0x100 +#define FIDO_DEV_WINHELLO 0x200 + +/* miscellanea */ +#define FIDO_DUMMY_CLIENTDATA "" +#define FIDO_DUMMY_RP_ID "localhost" +#define FIDO_DUMMY_USER_NAME "dummy" +#define FIDO_DUMMY_USER_ID 1 +#define FIDO_WINHELLO_PATH "windows://hello" +#define FIDO_NFC_PREFIX "nfc:" +#define FIDO_PCSC_PREFIX "pcsc:" + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_EXTERN_H */ diff --git a/src/fallthrough.h b/src/fallthrough.h new file mode 100644 index 0000000..bdfd30f --- /dev/null +++ b/src/fallthrough.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _FALLTHROUGH_H +#define _FALLTHROUGH_H + +#if defined(__GNUC__) +#if __has_attribute(fallthrough) +#define FALLTHROUGH __attribute__((fallthrough)); +#endif +#endif /* __GNUC__ */ + +#ifndef FALLTHROUGH +#define FALLTHROUGH /* FALLTHROUGH */ +#endif + +#endif /* !_FALLTHROUGH_H */ diff --git a/src/fido.h b/src/fido.h new file mode 100644 index 0000000..914e377 --- /dev/null +++ b/src/fido.h @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_H +#define _FIDO_H + +#include <openssl/ec.h> +#include <openssl/evp.h> + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include <sys/types.h> + +#include <cbor.h> +#include <limits.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "blob.h" +#include "iso7816.h" +#include "extern.h" +#endif + +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +fido_assert_t *fido_assert_new(void); +fido_cred_t *fido_cred_new(void); +fido_dev_t *fido_dev_new(void); +fido_dev_t *fido_dev_new_with_info(const fido_dev_info_t *); +fido_dev_info_t *fido_dev_info_new(size_t); +fido_cbor_info_t *fido_cbor_info_new(void); +void *fido_dev_io_handle(const fido_dev_t *); + +void fido_assert_free(fido_assert_t **); +void fido_cbor_info_free(fido_cbor_info_t **); +void fido_cred_free(fido_cred_t **); +void fido_dev_force_fido2(fido_dev_t *); +void fido_dev_force_u2f(fido_dev_t *); +void fido_dev_free(fido_dev_t **); +void fido_dev_info_free(fido_dev_info_t **, size_t); + +/* fido_init() flags. */ +#define FIDO_DEBUG 0x01 +#define FIDO_DISABLE_U2F_FALLBACK 0x02 + +void fido_init(int); +void fido_set_log_handler(fido_log_handler_t *); + +const unsigned char *fido_assert_authdata_ptr(const fido_assert_t *, size_t); +const unsigned char *fido_assert_authdata_raw_ptr(const fido_assert_t *, + size_t); +const unsigned char *fido_assert_clientdata_hash_ptr(const fido_assert_t *); +const unsigned char *fido_assert_hmac_secret_ptr(const fido_assert_t *, size_t); +const unsigned char *fido_assert_id_ptr(const fido_assert_t *, size_t); +const unsigned char *fido_assert_largeblob_key_ptr(const fido_assert_t *, size_t); +const unsigned char *fido_assert_sig_ptr(const fido_assert_t *, size_t); +const unsigned char *fido_assert_user_id_ptr(const fido_assert_t *, size_t); +const unsigned char *fido_assert_blob_ptr(const fido_assert_t *, size_t); + +char **fido_cbor_info_certs_name_ptr(const fido_cbor_info_t *); +char **fido_cbor_info_extensions_ptr(const fido_cbor_info_t *); +char **fido_cbor_info_options_name_ptr(const fido_cbor_info_t *); +char **fido_cbor_info_transports_ptr(const fido_cbor_info_t *); +char **fido_cbor_info_versions_ptr(const fido_cbor_info_t *); +const bool *fido_cbor_info_options_value_ptr(const fido_cbor_info_t *); +const char *fido_assert_rp_id(const fido_assert_t *); +const char *fido_assert_user_display_name(const fido_assert_t *, size_t); +const char *fido_assert_user_icon(const fido_assert_t *, size_t); +const char *fido_assert_user_name(const fido_assert_t *, size_t); +const char *fido_cbor_info_algorithm_type(const fido_cbor_info_t *, size_t); +const char *fido_cred_display_name(const fido_cred_t *); +const char *fido_cred_fmt(const fido_cred_t *); +const char *fido_cred_rp_id(const fido_cred_t *); +const char *fido_cred_rp_name(const fido_cred_t *); +const char *fido_cred_user_name(const fido_cred_t *); +const char *fido_dev_info_manufacturer_string(const fido_dev_info_t *); +const char *fido_dev_info_path(const fido_dev_info_t *); +const char *fido_dev_info_product_string(const fido_dev_info_t *); +const fido_dev_info_t *fido_dev_info_ptr(const fido_dev_info_t *, size_t); +const uint8_t *fido_cbor_info_protocols_ptr(const fido_cbor_info_t *); +const uint64_t *fido_cbor_info_certs_value_ptr(const fido_cbor_info_t *); +const unsigned char *fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *); +const unsigned char *fido_cred_aaguid_ptr(const fido_cred_t *); +const unsigned char *fido_cred_attstmt_ptr(const fido_cred_t *); +const unsigned char *fido_cred_authdata_ptr(const fido_cred_t *); +const unsigned char *fido_cred_authdata_raw_ptr(const fido_cred_t *); +const unsigned char *fido_cred_clientdata_hash_ptr(const fido_cred_t *); +const unsigned char *fido_cred_id_ptr(const fido_cred_t *); +const unsigned char *fido_cred_largeblob_key_ptr(const fido_cred_t *); +const unsigned char *fido_cred_pubkey_ptr(const fido_cred_t *); +const unsigned char *fido_cred_sig_ptr(const fido_cred_t *); +const unsigned char *fido_cred_user_id_ptr(const fido_cred_t *); +const unsigned char *fido_cred_x5c_ptr(const fido_cred_t *); + +int fido_assert_allow_cred(fido_assert_t *, const unsigned char *, size_t); +int fido_assert_empty_allow_list(fido_assert_t *); +int fido_assert_set_authdata(fido_assert_t *, size_t, const unsigned char *, + size_t); +int fido_assert_set_authdata_raw(fido_assert_t *, size_t, const unsigned char *, + size_t); +int fido_assert_set_clientdata(fido_assert_t *, const unsigned char *, size_t); +int fido_assert_set_clientdata_hash(fido_assert_t *, const unsigned char *, + size_t); +int fido_assert_set_count(fido_assert_t *, size_t); +int fido_assert_set_extensions(fido_assert_t *, int); +int fido_assert_set_hmac_salt(fido_assert_t *, const unsigned char *, size_t); +int fido_assert_set_hmac_secret(fido_assert_t *, size_t, const unsigned char *, + size_t); +int fido_assert_set_options(fido_assert_t *, bool, bool); +int fido_assert_set_rp(fido_assert_t *, const char *); +int fido_assert_set_up(fido_assert_t *, fido_opt_t); +int fido_assert_set_uv(fido_assert_t *, fido_opt_t); +int fido_assert_set_sig(fido_assert_t *, size_t, const unsigned char *, size_t); +int fido_assert_set_winhello_appid(fido_assert_t *, const char *); +int fido_assert_verify(const fido_assert_t *, size_t, int, const void *); +int fido_cbor_info_algorithm_cose(const fido_cbor_info_t *, size_t); +int fido_cred_empty_exclude_list(fido_cred_t *); +int fido_cred_exclude(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_prot(const fido_cred_t *); +int fido_cred_set_attstmt(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_authdata(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_authdata_raw(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_blob(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_clientdata(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_clientdata_hash(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_extensions(fido_cred_t *, int); +int fido_cred_set_fmt(fido_cred_t *, const char *); +int fido_cred_set_id(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_options(fido_cred_t *, bool, bool); +int fido_cred_set_pin_minlen(fido_cred_t *, size_t); +int fido_cred_set_prot(fido_cred_t *, int); +int fido_cred_set_rk(fido_cred_t *, fido_opt_t); +int fido_cred_set_rp(fido_cred_t *, const char *, const char *); +int fido_cred_set_sig(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_type(fido_cred_t *, int); +int fido_cred_set_uv(fido_cred_t *, fido_opt_t); +int fido_cred_type(const fido_cred_t *); +int fido_cred_set_user(fido_cred_t *, const unsigned char *, size_t, + const char *, const char *, const char *); +int fido_cred_set_x509(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_verify(const fido_cred_t *); +int fido_cred_verify_self(const fido_cred_t *); +#ifdef _FIDO_SIGSET_DEFINED +int fido_dev_set_sigmask(fido_dev_t *, const fido_sigset_t *); +#endif +int fido_dev_cancel(fido_dev_t *); +int fido_dev_close(fido_dev_t *); +int fido_dev_get_assert(fido_dev_t *, fido_assert_t *, const char *); +int fido_dev_get_cbor_info(fido_dev_t *, fido_cbor_info_t *); +int fido_dev_get_retry_count(fido_dev_t *, int *); +int fido_dev_get_uv_retry_count(fido_dev_t *, int *); +int fido_dev_get_touch_begin(fido_dev_t *); +int fido_dev_get_touch_status(fido_dev_t *, int *, int); +int fido_dev_info_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_dev_info_set(fido_dev_info_t *, size_t, const char *, const char *, + const char *, const fido_dev_io_t *, const fido_dev_transport_t *); +int fido_dev_make_cred(fido_dev_t *, fido_cred_t *, const char *); +int fido_dev_open_with_info(fido_dev_t *); +int fido_dev_open(fido_dev_t *, const char *); +int fido_dev_reset(fido_dev_t *); +int fido_dev_set_io_functions(fido_dev_t *, const fido_dev_io_t *); +int fido_dev_set_pin(fido_dev_t *, const char *, const char *); +int fido_dev_set_transport_functions(fido_dev_t *, const fido_dev_transport_t *); +int fido_dev_set_timeout(fido_dev_t *, int); + +size_t fido_assert_authdata_len(const fido_assert_t *, size_t); +size_t fido_assert_authdata_raw_len(const fido_assert_t *, size_t); +size_t fido_assert_clientdata_hash_len(const fido_assert_t *); +size_t fido_assert_count(const fido_assert_t *); +size_t fido_assert_hmac_secret_len(const fido_assert_t *, size_t); +size_t fido_assert_id_len(const fido_assert_t *, size_t); +size_t fido_assert_largeblob_key_len(const fido_assert_t *, size_t); +size_t fido_assert_sig_len(const fido_assert_t *, size_t); +size_t fido_assert_user_id_len(const fido_assert_t *, size_t); +size_t fido_assert_blob_len(const fido_assert_t *, size_t); +size_t fido_cbor_info_aaguid_len(const fido_cbor_info_t *); +size_t fido_cbor_info_algorithm_count(const fido_cbor_info_t *); +size_t fido_cbor_info_certs_len(const fido_cbor_info_t *); +size_t fido_cbor_info_extensions_len(const fido_cbor_info_t *); +size_t fido_cbor_info_options_len(const fido_cbor_info_t *); +size_t fido_cbor_info_protocols_len(const fido_cbor_info_t *); +size_t fido_cbor_info_transports_len(const fido_cbor_info_t *); +size_t fido_cbor_info_versions_len(const fido_cbor_info_t *); +size_t fido_cred_aaguid_len(const fido_cred_t *); +size_t fido_cred_attstmt_len(const fido_cred_t *); +size_t fido_cred_authdata_len(const fido_cred_t *); +size_t fido_cred_authdata_raw_len(const fido_cred_t *); +size_t fido_cred_clientdata_hash_len(const fido_cred_t *); +size_t fido_cred_id_len(const fido_cred_t *); +size_t fido_cred_largeblob_key_len(const fido_cred_t *); +size_t fido_cred_pin_minlen(const fido_cred_t *); +size_t fido_cred_pubkey_len(const fido_cred_t *); +size_t fido_cred_sig_len(const fido_cred_t *); +size_t fido_cred_user_id_len(const fido_cred_t *); +size_t fido_cred_x5c_len(const fido_cred_t *); + +uint8_t fido_assert_flags(const fido_assert_t *, size_t); +uint32_t fido_assert_sigcount(const fido_assert_t *, size_t); +uint8_t fido_cred_flags(const fido_cred_t *); +uint32_t fido_cred_sigcount(const fido_cred_t *); +uint8_t fido_dev_protocol(const fido_dev_t *); +uint8_t fido_dev_major(const fido_dev_t *); +uint8_t fido_dev_minor(const fido_dev_t *); +uint8_t fido_dev_build(const fido_dev_t *); +uint8_t fido_dev_flags(const fido_dev_t *); +int16_t fido_dev_info_vendor(const fido_dev_info_t *); +int16_t fido_dev_info_product(const fido_dev_info_t *); +uint64_t fido_cbor_info_fwversion(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredbloblen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredidlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxlargeblob(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxrpid_minpinlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_minpinlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_uv_attempts(const fido_cbor_info_t *); +uint64_t fido_cbor_info_uv_modality(const fido_cbor_info_t *); +int64_t fido_cbor_info_rk_remaining(const fido_cbor_info_t *); + +bool fido_dev_has_pin(const fido_dev_t *); +bool fido_dev_has_uv(const fido_dev_t *); +bool fido_dev_is_fido2(const fido_dev_t *); +bool fido_dev_is_winhello(const fido_dev_t *); +bool fido_dev_supports_credman(const fido_dev_t *); +bool fido_dev_supports_cred_prot(const fido_dev_t *); +bool fido_dev_supports_permissions(const fido_dev_t *); +bool fido_dev_supports_pin(const fido_dev_t *); +bool fido_dev_supports_uv(const fido_dev_t *); +bool fido_cbor_info_new_pin_required(const fido_cbor_info_t *); + +int fido_dev_largeblob_get(fido_dev_t *, const unsigned char *, size_t, + unsigned char **, size_t *); +int fido_dev_largeblob_set(fido_dev_t *, const unsigned char *, size_t, + const unsigned char *, size_t, const char *); +int fido_dev_largeblob_remove(fido_dev_t *, const unsigned char *, size_t, + const char *); +int fido_dev_largeblob_get_array(fido_dev_t *, unsigned char **, size_t *); +int fido_dev_largeblob_set_array(fido_dev_t *, const unsigned char *, size_t, + const char *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_H */ diff --git a/src/fido/bio.h b/src/fido/bio.h new file mode 100644 index 0000000..f5039e0 --- /dev/null +++ b/src/fido/bio.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_BIO_H +#define _FIDO_BIO_H + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include <fido.h> +#include <fido/err.h> +#include <fido/param.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef _FIDO_INTERNAL +struct fido_bio_template { + fido_blob_t id; + char *name; +}; + +struct fido_bio_template_array { + struct fido_bio_template *ptr; + size_t n_alloc; /* number of allocated entries */ + size_t n_rx; /* number of populated entries */ +}; + +struct fido_bio_enroll { + uint8_t remaining_samples; + uint8_t last_status; + fido_blob_t *token; +}; + +struct fido_bio_info { + uint8_t type; + uint8_t max_samples; +}; +#endif + +typedef struct fido_bio_template fido_bio_template_t; +typedef struct fido_bio_template_array fido_bio_template_array_t; +typedef struct fido_bio_enroll fido_bio_enroll_t; +typedef struct fido_bio_info fido_bio_info_t; + +#define FIDO_BIO_ENROLL_FP_GOOD 0x00 +#define FIDO_BIO_ENROLL_FP_TOO_HIGH 0x01 +#define FIDO_BIO_ENROLL_FP_TOO_LOW 0x02 +#define FIDO_BIO_ENROLL_FP_TOO_LEFT 0x03 +#define FIDO_BIO_ENROLL_FP_TOO_RIGHT 0x04 +#define FIDO_BIO_ENROLL_FP_TOO_FAST 0x05 +#define FIDO_BIO_ENROLL_FP_TOO_SLOW 0x06 +#define FIDO_BIO_ENROLL_FP_POOR_QUALITY 0x07 +#define FIDO_BIO_ENROLL_FP_TOO_SKEWED 0x08 +#define FIDO_BIO_ENROLL_FP_TOO_SHORT 0x09 +#define FIDO_BIO_ENROLL_FP_MERGE_FAILURE 0x0a +#define FIDO_BIO_ENROLL_FP_EXISTS 0x0b +#define FIDO_BIO_ENROLL_FP_DATABASE_FULL 0x0c +#define FIDO_BIO_ENROLL_NO_USER_ACTIVITY 0x0d +#define FIDO_BIO_ENROLL_NO_USER_PRESENCE_TRANSITION 0x0e + +const char *fido_bio_template_name(const fido_bio_template_t *); +const fido_bio_template_t *fido_bio_template(const fido_bio_template_array_t *, + size_t); +const unsigned char *fido_bio_template_id_ptr(const fido_bio_template_t *); +fido_bio_enroll_t *fido_bio_enroll_new(void); +fido_bio_info_t *fido_bio_info_new(void); +fido_bio_template_array_t *fido_bio_template_array_new(void); +fido_bio_template_t *fido_bio_template_new(void); +int fido_bio_dev_enroll_begin(fido_dev_t *, fido_bio_template_t *, + fido_bio_enroll_t *, uint32_t, const char *); +int fido_bio_dev_enroll_cancel(fido_dev_t *); +int fido_bio_dev_enroll_continue(fido_dev_t *, const fido_bio_template_t *, + fido_bio_enroll_t *, uint32_t); +int fido_bio_dev_enroll_remove(fido_dev_t *, const fido_bio_template_t *, + const char *); +int fido_bio_dev_get_info(fido_dev_t *, fido_bio_info_t *); +int fido_bio_dev_get_template_array(fido_dev_t *, fido_bio_template_array_t *, + const char *); +int fido_bio_dev_set_template_name(fido_dev_t *, const fido_bio_template_t *, + const char *); +int fido_bio_template_set_id(fido_bio_template_t *, const unsigned char *, + size_t); +int fido_bio_template_set_name(fido_bio_template_t *, const char *); +size_t fido_bio_template_array_count(const fido_bio_template_array_t *); +size_t fido_bio_template_id_len(const fido_bio_template_t *); +uint8_t fido_bio_enroll_last_status(const fido_bio_enroll_t *); +uint8_t fido_bio_enroll_remaining_samples(const fido_bio_enroll_t *); +uint8_t fido_bio_info_max_samples(const fido_bio_info_t *); +uint8_t fido_bio_info_type(const fido_bio_info_t *); +void fido_bio_enroll_free(fido_bio_enroll_t **); +void fido_bio_info_free(fido_bio_info_t **); +void fido_bio_template_array_free(fido_bio_template_array_t **); +void fido_bio_template_free(fido_bio_template_t **); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_BIO_H */ diff --git a/src/fido/config.h b/src/fido/config.h new file mode 100644 index 0000000..cba286f --- /dev/null +++ b/src/fido/config.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_CONFIG_H +#define _FIDO_CONFIG_H + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include <fido.h> +#include <fido/err.h> +#include <fido/param.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int fido_dev_enable_entattest(fido_dev_t *, const char *); +int fido_dev_force_pin_change(fido_dev_t *, const char *); +int fido_dev_toggle_always_uv(fido_dev_t *, const char *); +int fido_dev_set_pin_minlen(fido_dev_t *, size_t, const char *); +int fido_dev_set_pin_minlen_rpid(fido_dev_t *, const char * const *, size_t, + const char *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_CONFIG_H */ diff --git a/src/fido/credman.h b/src/fido/credman.h new file mode 100644 index 0000000..9f9dff1 --- /dev/null +++ b/src/fido/credman.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019-2021 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_CREDMAN_H +#define _FIDO_CREDMAN_H + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include <fido.h> +#include <fido/err.h> +#include <fido/param.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef _FIDO_INTERNAL +struct fido_credman_metadata { + uint64_t rk_existing; + uint64_t rk_remaining; +}; + +struct fido_credman_single_rp { + fido_rp_t rp_entity; + fido_blob_t rp_id_hash; +}; + +struct fido_credman_rp { + struct fido_credman_single_rp *ptr; + size_t n_alloc; /* number of allocated entries */ + size_t n_rx; /* number of populated entries */ +}; + +struct fido_credman_rk { + fido_cred_t *ptr; + size_t n_alloc; /* number of allocated entries */ + size_t n_rx; /* number of populated entries */ +}; +#endif + +typedef struct fido_credman_metadata fido_credman_metadata_t; +typedef struct fido_credman_rk fido_credman_rk_t; +typedef struct fido_credman_rp fido_credman_rp_t; + +const char *fido_credman_rp_id(const fido_credman_rp_t *, size_t); +const char *fido_credman_rp_name(const fido_credman_rp_t *, size_t); + +const fido_cred_t *fido_credman_rk(const fido_credman_rk_t *, size_t); +const unsigned char *fido_credman_rp_id_hash_ptr(const fido_credman_rp_t *, + size_t); + +fido_credman_metadata_t *fido_credman_metadata_new(void); +fido_credman_rk_t *fido_credman_rk_new(void); +fido_credman_rp_t *fido_credman_rp_new(void); + +int fido_credman_del_dev_rk(fido_dev_t *, const unsigned char *, size_t, + const char *); +int fido_credman_get_dev_metadata(fido_dev_t *, fido_credman_metadata_t *, + const char *); +int fido_credman_get_dev_rk(fido_dev_t *, const char *, fido_credman_rk_t *, + const char *); +int fido_credman_get_dev_rp(fido_dev_t *, fido_credman_rp_t *, const char *); +int fido_credman_set_dev_rk(fido_dev_t *, fido_cred_t *, const char *); + +size_t fido_credman_rk_count(const fido_credman_rk_t *); +size_t fido_credman_rp_count(const fido_credman_rp_t *); +size_t fido_credman_rp_id_hash_len(const fido_credman_rp_t *, size_t); + +uint64_t fido_credman_rk_existing(const fido_credman_metadata_t *); +uint64_t fido_credman_rk_remaining(const fido_credman_metadata_t *); + +void fido_credman_metadata_free(fido_credman_metadata_t **); +void fido_credman_rk_free(fido_credman_rk_t **); +void fido_credman_rp_free(fido_credman_rp_t **); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_CREDMAN_H */ diff --git a/src/fido/eddsa.h b/src/fido/eddsa.h new file mode 100644 index 0000000..5c0b681 --- /dev/null +++ b/src/fido/eddsa.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_EDDSA_H +#define _FIDO_EDDSA_H + +#include <openssl/ec.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +eddsa_pk_t *eddsa_pk_new(void); +void eddsa_pk_free(eddsa_pk_t **); +EVP_PKEY *eddsa_pk_to_EVP_PKEY(const eddsa_pk_t *); + +int eddsa_pk_from_EVP_PKEY(eddsa_pk_t *, const EVP_PKEY *); +int eddsa_pk_from_ptr(eddsa_pk_t *, const void *, size_t); + +#ifdef _FIDO_INTERNAL + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3070000f +#define EVP_PKEY_ED25519 EVP_PKEY_NONE +int EVP_PKEY_get_raw_public_key(const EVP_PKEY *, unsigned char *, size_t *); +EVP_PKEY *EVP_PKEY_new_raw_public_key(int, ENGINE *, const unsigned char *, + size_t); +int EVP_DigestVerify(EVP_MD_CTX *, const unsigned char *, size_t, + const unsigned char *, size_t); +#endif /* LIBRESSL_VERSION_NUMBER */ + +#endif /* _FIDO_INTERNAL */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_EDDSA_H */ diff --git a/src/fido/err.h b/src/fido/err.h new file mode 100644 index 0000000..7db25f2 --- /dev/null +++ b/src/fido/err.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_ERR_H +#define _FIDO_ERR_H + +#define FIDO_ERR_SUCCESS 0x00 +#define FIDO_ERR_INVALID_COMMAND 0x01 +#define FIDO_ERR_INVALID_PARAMETER 0x02 +#define FIDO_ERR_INVALID_LENGTH 0x03 +#define FIDO_ERR_INVALID_SEQ 0x04 +#define FIDO_ERR_TIMEOUT 0x05 +#define FIDO_ERR_CHANNEL_BUSY 0x06 +#define FIDO_ERR_LOCK_REQUIRED 0x0a +#define FIDO_ERR_INVALID_CHANNEL 0x0b +#define FIDO_ERR_CBOR_UNEXPECTED_TYPE 0x11 +#define FIDO_ERR_INVALID_CBOR 0x12 +#define FIDO_ERR_MISSING_PARAMETER 0x14 +#define FIDO_ERR_LIMIT_EXCEEDED 0x15 +#define FIDO_ERR_UNSUPPORTED_EXTENSION 0x16 +#define FIDO_ERR_FP_DATABASE_FULL 0x17 +#define FIDO_ERR_LARGEBLOB_STORAGE_FULL 0x18 +#define FIDO_ERR_CREDENTIAL_EXCLUDED 0x19 +#define FIDO_ERR_PROCESSING 0x21 +#define FIDO_ERR_INVALID_CREDENTIAL 0x22 +#define FIDO_ERR_USER_ACTION_PENDING 0x23 +#define FIDO_ERR_OPERATION_PENDING 0x24 +#define FIDO_ERR_NO_OPERATIONS 0x25 +#define FIDO_ERR_UNSUPPORTED_ALGORITHM 0x26 +#define FIDO_ERR_OPERATION_DENIED 0x27 +#define FIDO_ERR_KEY_STORE_FULL 0x28 +#define FIDO_ERR_NOT_BUSY 0x29 +#define FIDO_ERR_NO_OPERATION_PENDING 0x2a +#define FIDO_ERR_UNSUPPORTED_OPTION 0x2b +#define FIDO_ERR_INVALID_OPTION 0x2c +#define FIDO_ERR_KEEPALIVE_CANCEL 0x2d +#define FIDO_ERR_NO_CREDENTIALS 0x2e +#define FIDO_ERR_USER_ACTION_TIMEOUT 0x2f +#define FIDO_ERR_NOT_ALLOWED 0x30 +#define FIDO_ERR_PIN_INVALID 0x31 +#define FIDO_ERR_PIN_BLOCKED 0x32 +#define FIDO_ERR_PIN_AUTH_INVALID 0x33 +#define FIDO_ERR_PIN_AUTH_BLOCKED 0x34 +#define FIDO_ERR_PIN_NOT_SET 0x35 +#define FIDO_ERR_PIN_REQUIRED 0x36 +#define FIDO_ERR_PIN_POLICY_VIOLATION 0x37 +#define FIDO_ERR_PIN_TOKEN_EXPIRED 0x38 +#define FIDO_ERR_REQUEST_TOO_LARGE 0x39 +#define FIDO_ERR_ACTION_TIMEOUT 0x3a +#define FIDO_ERR_UP_REQUIRED 0x3b +#define FIDO_ERR_UV_BLOCKED 0x3c +#define FIDO_ERR_UV_INVALID 0x3f +#define FIDO_ERR_UNAUTHORIZED_PERM 0x40 +#define FIDO_ERR_ERR_OTHER 0x7f +#define FIDO_ERR_SPEC_LAST 0xdf + +/* defined internally */ +#define FIDO_OK FIDO_ERR_SUCCESS +#define FIDO_ERR_TX -1 +#define FIDO_ERR_RX -2 +#define FIDO_ERR_RX_NOT_CBOR -3 +#define FIDO_ERR_RX_INVALID_CBOR -4 +#define FIDO_ERR_INVALID_PARAM -5 +#define FIDO_ERR_INVALID_SIG -6 +#define FIDO_ERR_INVALID_ARGUMENT -7 +#define FIDO_ERR_USER_PRESENCE_REQUIRED -8 +#define FIDO_ERR_INTERNAL -9 +#define FIDO_ERR_NOTFOUND -10 +#define FIDO_ERR_COMPRESS -11 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +const char *fido_strerr(int); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _FIDO_ERR_H */ diff --git a/src/fido/es256.h b/src/fido/es256.h new file mode 100644 index 0000000..0450de2 --- /dev/null +++ b/src/fido/es256.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_ES256_H +#define _FIDO_ES256_H + +#include <openssl/ec.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +es256_pk_t *es256_pk_new(void); +void es256_pk_free(es256_pk_t **); +EVP_PKEY *es256_pk_to_EVP_PKEY(const es256_pk_t *); + +int es256_pk_from_EC_KEY(es256_pk_t *, const EC_KEY *); +int es256_pk_from_EVP_PKEY(es256_pk_t *, const EVP_PKEY *); +int es256_pk_from_ptr(es256_pk_t *, const void *, size_t); + +#ifdef _FIDO_INTERNAL +es256_sk_t *es256_sk_new(void); +void es256_sk_free(es256_sk_t **); +EVP_PKEY *es256_sk_to_EVP_PKEY(const es256_sk_t *); + +int es256_derive_pk(const es256_sk_t *, es256_pk_t *); +int es256_sk_create(es256_sk_t *); + +int es256_pk_set_x(es256_pk_t *, const unsigned char *); +int es256_pk_set_y(es256_pk_t *, const unsigned char *); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_ES256_H */ diff --git a/src/fido/es384.h b/src/fido/es384.h new file mode 100644 index 0000000..b4b4ca7 --- /dev/null +++ b/src/fido/es384.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_ES384_H +#define _FIDO_ES384_H + +#include <openssl/ec.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +es384_pk_t *es384_pk_new(void); +void es384_pk_free(es384_pk_t **); +EVP_PKEY *es384_pk_to_EVP_PKEY(const es384_pk_t *); + +int es384_pk_from_EC_KEY(es384_pk_t *, const EC_KEY *); +int es384_pk_from_EVP_PKEY(es384_pk_t *, const EVP_PKEY *); +int es384_pk_from_ptr(es384_pk_t *, const void *, size_t); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_ES384_H */ diff --git a/src/fido/param.h b/src/fido/param.h new file mode 100644 index 0000000..511370b --- /dev/null +++ b/src/fido/param.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_PARAM_H +#define _FIDO_PARAM_H + +/* Authentication data flags. */ +#define CTAP_AUTHDATA_USER_PRESENT 0x01 +#define CTAP_AUTHDATA_USER_VERIFIED 0x04 +#define CTAP_AUTHDATA_ATT_CRED 0x40 +#define CTAP_AUTHDATA_EXT_DATA 0x80 + +/* CTAPHID command opcodes. */ +#define CTAP_CMD_PING 0x01 +#define CTAP_CMD_MSG 0x03 +#define CTAP_CMD_LOCK 0x04 +#define CTAP_CMD_INIT 0x06 +#define CTAP_CMD_WINK 0x08 +#define CTAP_CMD_CBOR 0x10 +#define CTAP_CMD_CANCEL 0x11 +#define CTAP_KEEPALIVE 0x3b +#define CTAP_FRAME_INIT 0x80 + +/* CTAPHID CBOR command opcodes. */ +#define CTAP_CBOR_MAKECRED 0x01 +#define CTAP_CBOR_ASSERT 0x02 +#define CTAP_CBOR_GETINFO 0x04 +#define CTAP_CBOR_CLIENT_PIN 0x06 +#define CTAP_CBOR_RESET 0x07 +#define CTAP_CBOR_NEXT_ASSERT 0x08 +#define CTAP_CBOR_LARGEBLOB 0x0c +#define CTAP_CBOR_CONFIG 0x0d +#define CTAP_CBOR_BIO_ENROLL_PRE 0x40 +#define CTAP_CBOR_CRED_MGMT_PRE 0x41 + +/* Supported CTAP PIN/UV Auth Protocols. */ +#define CTAP_PIN_PROTOCOL1 1 +#define CTAP_PIN_PROTOCOL2 2 + +/* U2F command opcodes. */ +#define U2F_CMD_REGISTER 0x01 +#define U2F_CMD_AUTH 0x02 + +/* U2F command flags. */ +#define U2F_AUTH_SIGN 0x03 +#define U2F_AUTH_CHECK 0x07 + +/* ISO7816-4 status words. */ +#define SW1_MORE_DATA 0x61 +#define SW_CONDITIONS_NOT_SATISFIED 0x6985 +#define SW_WRONG_DATA 0x6a80 +#define SW_NO_ERROR 0x9000 + +/* HID Broadcast channel ID. */ +#define CTAP_CID_BROADCAST 0xffffffff + +#define CTAP_INIT_HEADER_LEN 7 +#define CTAP_CONT_HEADER_LEN 5 + +/* Maximum length of a CTAP HID report in bytes. */ +#define CTAP_MAX_REPORT_LEN 64 + +/* Minimum length of a CTAP HID report in bytes. */ +#define CTAP_MIN_REPORT_LEN (CTAP_INIT_HEADER_LEN + 1) + +/* Randomness device on UNIX-like platforms. */ +#ifndef FIDO_RANDOM_DEV +#define FIDO_RANDOM_DEV "/dev/urandom" +#endif + +/* Maximum message size in bytes. */ +#ifndef FIDO_MAXMSG +#define FIDO_MAXMSG 2048 +#endif + +/* CTAP capability bits. */ +#define FIDO_CAP_WINK 0x01 /* if set, device supports CTAP_CMD_WINK */ +#define FIDO_CAP_CBOR 0x04 /* if set, device supports CTAP_CMD_CBOR */ +#define FIDO_CAP_NMSG 0x08 /* if set, device doesn't support CTAP_CMD_MSG */ + +/* Supported COSE algorithms. */ +#define COSE_UNSPEC 0 +#define COSE_ES256 -7 +#define COSE_EDDSA -8 +#define COSE_ECDH_ES256 -25 +#define COSE_ES384 -35 +#define COSE_RS256 -257 +#define COSE_RS1 -65535 + +/* Supported COSE types. */ +#define COSE_KTY_OKP 1 +#define COSE_KTY_EC2 2 +#define COSE_KTY_RSA 3 + +/* Supported curves. */ +#define COSE_P256 1 +#define COSE_P384 2 +#define COSE_ED25519 6 + +/* Supported extensions. */ +#define FIDO_EXT_HMAC_SECRET 0x01 +#define FIDO_EXT_CRED_PROTECT 0x02 +#define FIDO_EXT_LARGEBLOB_KEY 0x04 +#define FIDO_EXT_CRED_BLOB 0x08 +#define FIDO_EXT_MINPINLEN 0x10 + +/* Supported credential protection policies. */ +#define FIDO_CRED_PROT_UV_OPTIONAL 0x01 +#define FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID 0x02 +#define FIDO_CRED_PROT_UV_REQUIRED 0x03 + +#ifdef _FIDO_INTERNAL +#define FIDO_EXT_ASSERT_MASK (FIDO_EXT_HMAC_SECRET|FIDO_EXT_LARGEBLOB_KEY| \ + FIDO_EXT_CRED_BLOB) +#define FIDO_EXT_CRED_MASK (FIDO_EXT_HMAC_SECRET|FIDO_EXT_CRED_PROTECT| \ + FIDO_EXT_LARGEBLOB_KEY|FIDO_EXT_CRED_BLOB| \ + FIDO_EXT_MINPINLEN) +#endif /* _FIDO_INTERNAL */ + +/* Recognised UV modes. */ +#define FIDO_UV_MODE_TUP 0x0001 /* internal test of user presence */ +#define FIDO_UV_MODE_FP 0x0002 /* internal fingerprint check */ +#define FIDO_UV_MODE_PIN 0x0004 /* internal pin check */ +#define FIDO_UV_MODE_VOICE 0x0008 /* internal voice recognition */ +#define FIDO_UV_MODE_FACE 0x0010 /* internal face recognition */ +#define FIDO_UV_MODE_LOCATION 0x0020 /* internal location check */ +#define FIDO_UV_MODE_EYE 0x0040 /* internal eyeprint check */ +#define FIDO_UV_MODE_DRAWN 0x0080 /* internal drawn pattern check */ +#define FIDO_UV_MODE_HAND 0x0100 /* internal handprint verification */ +#define FIDO_UV_MODE_NONE 0x0200 /* TUP/UV not required */ +#define FIDO_UV_MODE_ALL 0x0400 /* all supported UV modes required */ +#define FIDO_UV_MODE_EXT_PIN 0x0800 /* external pin verification */ +#define FIDO_UV_MODE_EXT_DRAWN 0x1000 /* external drawn pattern check */ + +#endif /* !_FIDO_PARAM_H */ diff --git a/src/fido/rs256.h b/src/fido/rs256.h new file mode 100644 index 0000000..6f8c781 --- /dev/null +++ b/src/fido/rs256.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_RS256_H +#define _FIDO_RS256_H + +#include <openssl/rsa.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +rs256_pk_t *rs256_pk_new(void); +void rs256_pk_free(rs256_pk_t **); +EVP_PKEY *rs256_pk_to_EVP_PKEY(const rs256_pk_t *); + +int rs256_pk_from_EVP_PKEY(rs256_pk_t *, const EVP_PKEY *); +int rs256_pk_from_RSA(rs256_pk_t *, const RSA *); +int rs256_pk_from_ptr(rs256_pk_t *, const void *, size_t); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_RS256_H */ diff --git a/src/fido/types.h b/src/fido/types.h new file mode 100644 index 0000000..01d6820 --- /dev/null +++ b/src/fido/types.h @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_TYPES_H +#define _FIDO_TYPES_H + +#ifdef __MINGW32__ +#include <sys/types.h> +#endif + +#include <signal.h> +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct fido_dev; + +typedef void *fido_dev_io_open_t(const char *); +typedef void fido_dev_io_close_t(void *); +typedef int fido_dev_io_read_t(void *, unsigned char *, size_t, int); +typedef int fido_dev_io_write_t(void *, const unsigned char *, size_t); +typedef int fido_dev_rx_t(struct fido_dev *, uint8_t, unsigned char *, size_t, int); +typedef int fido_dev_tx_t(struct fido_dev *, uint8_t, const unsigned char *, size_t); + +typedef struct fido_dev_io { + fido_dev_io_open_t *open; + fido_dev_io_close_t *close; + fido_dev_io_read_t *read; + fido_dev_io_write_t *write; +} fido_dev_io_t; + +typedef struct fido_dev_transport { + fido_dev_rx_t *rx; + fido_dev_tx_t *tx; +} fido_dev_transport_t; + +typedef enum { + FIDO_OPT_OMIT = 0, /* use authenticator's default */ + FIDO_OPT_FALSE, /* explicitly set option to false */ + FIDO_OPT_TRUE, /* explicitly set option to true */ +} fido_opt_t; + +typedef void fido_log_handler_t(const char *); + +#undef _FIDO_SIGSET_DEFINED +#define _FIDO_SIGSET_DEFINED +#ifdef _WIN32 +typedef int fido_sigset_t; +#elif defined(SIG_BLOCK) +typedef sigset_t fido_sigset_t; +#else +#undef _FIDO_SIGSET_DEFINED +#endif + +#ifdef _FIDO_INTERNAL +#include "packed.h" +#include "blob.h" + +/* COSE ES256 (ECDSA over P-256 with SHA-256) public key */ +typedef struct es256_pk { + unsigned char x[32]; + unsigned char y[32]; +} es256_pk_t; + +/* COSE ES256 (ECDSA over P-256 with SHA-256) (secret) key */ +typedef struct es256_sk { + unsigned char d[32]; +} es256_sk_t; + +/* COSE ES384 (ECDSA over P-384 with SHA-384) public key */ +typedef struct es384_pk { + unsigned char x[48]; + unsigned char y[48]; +} es384_pk_t; + +/* COSE RS256 (2048-bit RSA with PKCS1 padding and SHA-256) public key */ +typedef struct rs256_pk { + unsigned char n[256]; + unsigned char e[3]; +} rs256_pk_t; + +/* COSE EDDSA (ED25519) */ +typedef struct eddsa_pk { + unsigned char x[32]; +} eddsa_pk_t; + +PACKED_TYPE(fido_authdata_t, +struct fido_authdata { + unsigned char rp_id_hash[32]; /* sha256 of fido_rp.id */ + uint8_t flags; /* user present/verified */ + uint32_t sigcount; /* signature counter */ + /* actually longer */ +}) + +PACKED_TYPE(fido_attcred_raw_t, +struct fido_attcred_raw { + unsigned char aaguid[16]; /* credential's aaguid */ + uint16_t id_len; /* credential id length */ + uint8_t body[]; /* credential id + pubkey */ +}) + +typedef struct fido_attcred { + unsigned char aaguid[16]; /* credential's aaguid */ + fido_blob_t id; /* credential id */ + int type; /* credential's cose algorithm */ + union { /* credential's public key */ + es256_pk_t es256; + es384_pk_t es384; + rs256_pk_t rs256; + eddsa_pk_t eddsa; + } pubkey; +} fido_attcred_t; + +typedef struct fido_attstmt { + fido_blob_t certinfo; /* tpm attestation TPMS_ATTEST structure */ + fido_blob_t pubarea; /* tpm attestation TPMT_PUBLIC structure */ + fido_blob_t cbor; /* cbor-encoded attestation statement */ + fido_blob_t x5c; /* attestation certificate */ + fido_blob_t sig; /* attestation signature */ + int alg; /* attestation algorithm (cose) */ +} fido_attstmt_t; + +typedef struct fido_rp { + char *id; /* relying party id */ + char *name; /* relying party name */ +} fido_rp_t; + +typedef struct fido_user { + fido_blob_t id; /* required */ + char *icon; /* optional */ + char *name; /* optional */ + char *display_name; /* required */ +} fido_user_t; + +typedef struct fido_cred_ext { + int mask; /* enabled extensions */ + int prot; /* protection policy */ + size_t minpinlen; /* minimum pin length */ +} fido_cred_ext_t; + +typedef struct fido_cred { + fido_blob_t cd; /* client data */ + fido_blob_t cdh; /* client data hash */ + fido_rp_t rp; /* relying party */ + fido_user_t user; /* user entity */ + fido_blob_array_t excl; /* list of credential ids to exclude */ + fido_opt_t rk; /* resident key */ + fido_opt_t uv; /* user verification */ + fido_cred_ext_t ext; /* extensions */ + int type; /* cose algorithm */ + char *fmt; /* credential format */ + fido_cred_ext_t authdata_ext; /* decoded extensions */ + fido_blob_t authdata_cbor; /* cbor-encoded payload */ + fido_blob_t authdata_raw; /* cbor-decoded payload */ + fido_authdata_t authdata; /* decoded authdata payload */ + fido_attcred_t attcred; /* returned credential (key + id) */ + fido_attstmt_t attstmt; /* attestation statement (x509 + sig) */ + fido_blob_t largeblob_key; /* decoded large blob key */ + fido_blob_t blob; /* CTAP 2.1 credBlob */ +} fido_cred_t; + +typedef struct fido_assert_extattr { + int mask; /* decoded extensions */ + fido_blob_t hmac_secret_enc; /* hmac secret, encrypted */ + fido_blob_t blob; /* decoded CTAP 2.1 credBlob */ +} fido_assert_extattr_t; + +typedef struct _fido_assert_stmt { + fido_blob_t id; /* credential id */ + fido_user_t user; /* user attributes */ + fido_blob_t hmac_secret; /* hmac secret */ + fido_assert_extattr_t authdata_ext; /* decoded extensions */ + fido_blob_t authdata_cbor; /* raw cbor payload */ + fido_blob_t authdata_raw; /* raw authdata */ + fido_authdata_t authdata; /* decoded authdata payload */ + fido_blob_t sig; /* signature of cdh + authdata */ + fido_blob_t largeblob_key; /* decoded large blob key */ +} fido_assert_stmt; + +typedef struct fido_assert_ext { + int mask; /* enabled extensions */ + fido_blob_t hmac_salt; /* optional hmac-secret salt */ +} fido_assert_ext_t; + +typedef struct fido_assert { + char *rp_id; /* relying party id */ + char *appid; /* winhello u2f appid */ + fido_blob_t cd; /* client data */ + fido_blob_t cdh; /* client data hash */ + fido_blob_array_t allow_list; /* list of allowed credentials */ + fido_opt_t up; /* user presence */ + fido_opt_t uv; /* user verification */ + fido_assert_ext_t ext; /* enabled extensions */ + fido_assert_stmt *stmt; /* array of expected assertions */ + size_t stmt_cnt; /* number of allocated assertions */ + size_t stmt_len; /* number of received assertions */ +} fido_assert_t; + +typedef struct fido_opt_array { + char **name; + bool *value; + size_t len; +} fido_opt_array_t; + +typedef struct fido_str_array { + char **ptr; + size_t len; +} fido_str_array_t; + +typedef struct fido_byte_array { + uint8_t *ptr; + size_t len; +} fido_byte_array_t; + +typedef struct fido_algo { + char *type; + int cose; +} fido_algo_t; + +typedef struct fido_algo_array { + fido_algo_t *ptr; + size_t len; +} fido_algo_array_t; + +typedef struct fido_cert_array { + char **name; + uint64_t *value; + size_t len; +} fido_cert_array_t; + +typedef struct fido_cbor_info { + fido_str_array_t versions; /* supported versions: fido2|u2f */ + fido_str_array_t extensions; /* list of supported extensions */ + fido_str_array_t transports; /* list of supported transports */ + unsigned char aaguid[16]; /* aaguid */ + fido_opt_array_t options; /* list of supported options */ + uint64_t maxmsgsiz; /* maximum message size */ + fido_byte_array_t protocols; /* supported pin protocols */ + fido_algo_array_t algorithms; /* list of supported algorithms */ + uint64_t maxcredcntlst; /* max credentials in list */ + uint64_t maxcredidlen; /* max credential ID length */ + uint64_t fwversion; /* firmware version */ + uint64_t maxcredbloblen; /* max credBlob length */ + uint64_t maxlargeblob; /* max largeBlob array length */ + uint64_t maxrpid_minlen; /* max rpid in set_pin_minlen_rpid */ + uint64_t minpinlen; /* min pin len enforced */ + uint64_t uv_attempts; /* platform uv attempts */ + uint64_t uv_modality; /* bitmask of supported uv types */ + int64_t rk_remaining; /* remaining resident credentials */ + bool new_pin_reqd; /* new pin required */ + fido_cert_array_t certs; /* associated certifications */ +} fido_cbor_info_t; + +typedef struct fido_dev_info { + char *path; /* device path */ + int16_t vendor_id; /* 2-byte vendor id */ + int16_t product_id; /* 2-byte product id */ + char *manufacturer; /* manufacturer string */ + char *product; /* product string */ + fido_dev_io_t io; /* i/o functions */ + fido_dev_transport_t transport; /* transport functions */ +} fido_dev_info_t; + +PACKED_TYPE(fido_ctap_info_t, +/* defined in section 8.1.9.1.3 (CTAPHID_INIT) of the fido2 ctap spec */ +struct fido_ctap_info { + uint64_t nonce; /* echoed nonce */ + uint32_t cid; /* channel id */ + uint8_t protocol; /* ctaphid protocol id */ + uint8_t major; /* major version number */ + uint8_t minor; /* minor version number */ + uint8_t build; /* build version number */ + uint8_t flags; /* capabilities flags; see FIDO_CAP_* */ +}) + +typedef struct fido_dev { + uint64_t nonce; /* issued nonce */ + fido_ctap_info_t attr; /* device attributes */ + uint32_t cid; /* assigned channel id */ + char *path; /* device path */ + void *io_handle; /* abstract i/o handle */ + fido_dev_io_t io; /* i/o functions */ + bool io_own; /* device has own io/transport */ + size_t rx_len; /* length of HID input reports */ + size_t tx_len; /* length of HID output reports */ + int flags; /* internal flags; see FIDO_DEV_* */ + fido_dev_transport_t transport; /* transport functions */ + uint64_t maxmsgsize; /* max message size */ + int timeout_ms; /* read timeout in ms */ +} fido_dev_t; + +#else +typedef struct fido_assert fido_assert_t; +typedef struct fido_cbor_info fido_cbor_info_t; +typedef struct fido_cred fido_cred_t; +typedef struct fido_dev fido_dev_t; +typedef struct fido_dev_info fido_dev_info_t; +typedef struct es256_pk es256_pk_t; +typedef struct es256_sk es256_sk_t; +typedef struct es384_pk es384_pk_t; +typedef struct rs256_pk rs256_pk_t; +typedef struct eddsa_pk eddsa_pk_t; +#endif /* _FIDO_INTERNAL */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_TYPES_H */ diff --git a/src/hid.c b/src/hid.c new file mode 100644 index 0000000..662bd44 --- /dev/null +++ b/src/hid.c @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) +{ + *key = tag & 0xfc; + if ((*key & 0xf0) == 0xf0) { + fido_log_debug("%s: *key=0x%02x", __func__, *key); + return (-1); + } + + *key_len = tag & 0x3; + if (*key_len == 3) { + *key_len = 4; + } + + return (0); +} + +static int +get_key_val(const void *body, size_t key_len, uint32_t *val) +{ + const uint8_t *ptr = body; + + switch (key_len) { + case 0: + *val = 0; + break; + case 1: + *val = ptr[0]; + break; + case 2: + *val = (uint32_t)((ptr[1] << 8) | ptr[0]); + break; + default: + fido_log_debug("%s: key_len=%zu", __func__, key_len); + return (-1); + } + + return (0); +} + +int +fido_hid_get_usage(const uint8_t *report_ptr, size_t report_len, + uint32_t *usage_page) +{ + const uint8_t *ptr = report_ptr; + size_t len = report_len; + + while (len > 0) { + const uint8_t tag = ptr[0]; + ptr++; + len--; + + uint8_t key; + size_t key_len; + uint32_t key_val; + + if (get_key_len(tag, &key, &key_len) < 0 || key_len > len || + get_key_val(ptr, key_len, &key_val) < 0) { + return (-1); + } + + if (key == 0x4) { + *usage_page = key_val; + } + + ptr += key_len; + len -= key_len; + } + + return (0); +} + +int +fido_hid_get_report_len(const uint8_t *report_ptr, size_t report_len, + size_t *report_in_len, size_t *report_out_len) +{ + const uint8_t *ptr = report_ptr; + size_t len = report_len; + uint32_t report_size = 0; + + while (len > 0) { + const uint8_t tag = ptr[0]; + ptr++; + len--; + + uint8_t key; + size_t key_len; + uint32_t key_val; + + if (get_key_len(tag, &key, &key_len) < 0 || key_len > len || + get_key_val(ptr, key_len, &key_val) < 0) { + return (-1); + } + + if (key == 0x94) { + report_size = key_val; + } else if (key == 0x80) { + *report_in_len = (size_t)report_size; + } else if (key == 0x90) { + *report_out_len = (size_t)report_size; + } + + ptr += key_len; + len -= key_len; + } + + return (0); +} + +fido_dev_info_t * +fido_dev_info_new(size_t n) +{ + return (calloc(n, sizeof(fido_dev_info_t))); +} + +static void +fido_dev_info_reset(fido_dev_info_t *di) +{ + free(di->path); + free(di->manufacturer); + free(di->product); + memset(di, 0, sizeof(*di)); +} + +void +fido_dev_info_free(fido_dev_info_t **devlist_p, size_t n) +{ + fido_dev_info_t *devlist; + + if (devlist_p == NULL || (devlist = *devlist_p) == NULL) + return; + + for (size_t i = 0; i < n; i++) + fido_dev_info_reset(&devlist[i]); + + free(devlist); + + *devlist_p = NULL; +} + +const fido_dev_info_t * +fido_dev_info_ptr(const fido_dev_info_t *devlist, size_t i) +{ + return (&devlist[i]); +} + +int +fido_dev_info_set(fido_dev_info_t *devlist, size_t i, + const char *path, const char *manufacturer, const char *product, + const fido_dev_io_t *io, const fido_dev_transport_t *transport) +{ + char *path_copy = NULL, *manu_copy = NULL, *prod_copy = NULL; + int r; + + if (path == NULL || manufacturer == NULL || product == NULL || + io == NULL) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + if ((path_copy = strdup(path)) == NULL || + (manu_copy = strdup(manufacturer)) == NULL || + (prod_copy = strdup(product)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + fido_dev_info_reset(&devlist[i]); + devlist[i].path = path_copy; + devlist[i].manufacturer = manu_copy; + devlist[i].product = prod_copy; + devlist[i].io = *io; + if (transport) + devlist[i].transport = *transport; + r = FIDO_OK; +out: + if (r != FIDO_OK) { + free(prod_copy); + free(manu_copy); + free(path_copy); + } + return (r); +} + +const char * +fido_dev_info_path(const fido_dev_info_t *di) +{ + return (di->path); +} + +int16_t +fido_dev_info_vendor(const fido_dev_info_t *di) +{ + return (di->vendor_id); +} + +int16_t +fido_dev_info_product(const fido_dev_info_t *di) +{ + return (di->product_id); +} + +const char * +fido_dev_info_manufacturer_string(const fido_dev_info_t *di) +{ + return (di->manufacturer); +} + +const char * +fido_dev_info_product_string(const fido_dev_info_t *di) +{ + return (di->product); +} diff --git a/src/hid_freebsd.c b/src/hid_freebsd.c new file mode 100644 index 0000000..2bbe80b --- /dev/null +++ b/src/hid_freebsd.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/param.h> + +#include <dev/usb/usb_ioctl.h> +#include <dev/usb/usbhid.h> +#if __FreeBSD_version >= 1300500 +#include <dev/hid/hidraw.h> +#define USE_HIDRAW /* see usbhid(4) and hidraw(4) on FreeBSD 13+ */ +#endif + +#include <errno.h> +#include <unistd.h> + +#include "fido.h" + +#if defined(__MidnightBSD__) +#define UHID_VENDOR "MidnightBSD" +#else +#define UHID_VENDOR "FreeBSD" +#endif + +#define MAX_UHID 64 + +struct hid_freebsd { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +static bool +is_fido(int fd) +{ + char buf[64]; + struct usb_gen_descriptor ugd; + uint32_t usage_page = 0; + + memset(&buf, 0, sizeof(buf)); + memset(&ugd, 0, sizeof(ugd)); + + ugd.ugd_report_type = UHID_FEATURE_REPORT; + ugd.ugd_data = buf; + ugd.ugd_maxlen = sizeof(buf); + + if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + return (false); + } + if (ugd.ugd_actlen > sizeof(buf) || fido_hid_get_usage(ugd.ugd_data, + ugd.ugd_actlen, &usage_page) < 0) { + fido_log_debug("%s: fido_hid_get_usage", __func__); + return (false); + } + + return (usage_page == 0xf1d0); +} + +#ifdef USE_HIDRAW +static int +copy_info_hidraw(fido_dev_info_t *di, const char *path) +{ + int fd = -1; + int ok = -1; + struct usb_device_info udi; + struct hidraw_devinfo devinfo; + char rawname[129]; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + memset(&devinfo, 0, sizeof(devinfo)); + memset(rawname, 0, sizeof(rawname)); + + if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0) + goto fail; + + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + if (ioctl(fd, IOCTL_REQ(HIDIOCGRAWINFO), &devinfo) == -1 || + ioctl(fd, IOCTL_REQ(HIDIOCGRAWNAME(128)), rawname) == -1 || + (di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(UHID_VENDOR)) == NULL || + (di->product = strdup(rawname)) == NULL) + goto fail; + di->vendor_id = devinfo.vendor; + di->product_id = devinfo.product; + } else { + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + } + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close %s", __func__, path); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} +#endif /* USE_HIDRAW */ + +static int +copy_info_uhid(fido_dev_info_t *di, const char *path) +{ + int fd = -1; + int ok = -1; + struct usb_device_info udi; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + + if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0) + goto fail; + + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + strlcpy(udi.udi_vendor, UHID_VENDOR, sizeof(udi.udi_vendor)); + strlcpy(udi.udi_product, "uhid(4)", sizeof(udi.udi_product)); + udi.udi_vendorNo = 0x0b5d; /* stolen from PCI_VENDOR_OPENBSD */ + } + + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close %s", __func__, path); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + char path[64]; + size_t i; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL || olen == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + *olen = 0; + +#ifdef USE_HIDRAW + for (i = 0; i < MAX_UHID && *olen < ilen; i++) { + snprintf(path, sizeof(path), "/dev/hidraw%zu", i); + if (copy_info_hidraw(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*olen); + } + } + /* hidraw(4) is preferred over uhid(4) */ + if (*olen != 0) + return (FIDO_OK); +#endif /* USE_HIDRAW */ + + for (i = 0; i < MAX_UHID && *olen < ilen; i++) { + snprintf(path, sizeof(path), "/dev/uhid%zu", i); + if (copy_info_uhid(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*olen); + } + } + + return (FIDO_OK); +} + +void * +fido_hid_open(const char *path) +{ + char buf[64]; + struct hid_freebsd *ctx; + struct usb_gen_descriptor ugd; + int r; + + memset(&buf, 0, sizeof(buf)); + memset(&ugd, 0, sizeof(ugd)); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); + + if ((ctx->fd = fido_hid_unix_open(path)) == -1) { + free(ctx); + return (NULL); + } + + ugd.ugd_report_type = UHID_FEATURE_REPORT; + ugd.ugd_data = buf; + ugd.ugd_maxlen = sizeof(buf); + + /* + * N.B. if ctx->fd is an hidraw(4) device, the ioctl() below puts it in + * uhid(4) compat mode, which we need to keep fido_hid_write() as-is. + */ + if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) || + ugd.ugd_actlen > sizeof(buf) || + fido_hid_get_report_len(ugd.ugd_data, ugd.ugd_actlen, + &ctx->report_in_len, &ctx->report_out_len) < 0) { + if (r == -1) + fido_log_error(errno, "%s: ioctl", __func__); + fido_log_debug("%s: using default report sizes", __func__); + ctx->report_in_len = CTAP_MAX_REPORT_LEN; + ctx->report_out_len = CTAP_MAX_REPORT_LEN; + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_freebsd *ctx = handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_freebsd *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_freebsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_freebsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len - 1) { + fido_log_debug("%s: %zd != %zu", __func__, r, len - 1); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_freebsd *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_freebsd *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_hidapi.c b/src/hid_hidapi.c new file mode 100644 index 0000000..fed6f69 --- /dev/null +++ b/src/hid_hidapi.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2019 Google LLC. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifdef __linux__ +#include <sys/ioctl.h> +#include <linux/hidraw.h> +#include <linux/input.h> +#include <fcntl.h> +#endif + +#include <errno.h> +#include <hidapi.h> +#include <wchar.h> + +#include "fido.h" + +struct hid_hidapi { + void *handle; + size_t report_in_len; + size_t report_out_len; +}; + +static size_t +fido_wcslen(const wchar_t *wcs) +{ + size_t l = 0; + while (*wcs++ != L'\0') + l++; + return l; +} + +static char * +wcs_to_cs(const wchar_t *wcs) +{ + char *cs; + size_t i; + + if (wcs == NULL || (cs = calloc(fido_wcslen(wcs) + 1, 1)) == NULL) + return NULL; + + for (i = 0; i < fido_wcslen(wcs); i++) { + if (wcs[i] >= 128) { + /* give up on parsing non-ASCII text */ + free(cs); + return strdup("hidapi device"); + } + cs[i] = (char)wcs[i]; + } + + return cs; +} + +static int +copy_info(fido_dev_info_t *di, const struct hid_device_info *d) +{ + memset(di, 0, sizeof(*di)); + + if (d->path != NULL) + di->path = strdup(d->path); + else + di->path = strdup(""); + + if (d->manufacturer_string != NULL) + di->manufacturer = wcs_to_cs(d->manufacturer_string); + else + di->manufacturer = strdup(""); + + if (d->product_string != NULL) + di->product = wcs_to_cs(d->product_string); + else + di->product = strdup(""); + + if (di->path == NULL || + di->manufacturer == NULL || + di->product == NULL) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + return -1; + } + + di->product_id = (int16_t)d->product_id; + di->vendor_id = (int16_t)d->vendor_id; + di->io = (fido_dev_io_t) { + &fido_hid_open, + &fido_hid_close, + &fido_hid_read, + &fido_hid_write, + }; + + return 0; +} + +#ifdef __linux__ +static int +get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd) +{ + int fd; + int s = -1; + int ok = -1; + + if ((fd = fido_hid_unix_open(path)) == -1) { + fido_log_debug("%s: fido_hid_unix_open", __func__); + return -1; + } + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) < 0 || s < 0 || + (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__); + goto fail; + } + + hrd->size = (unsigned)s; + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) < 0) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__); + goto fail; + } + + ok = 0; +fail: + if (fd != -1) + close(fd); + + return ok; +} + +static bool +is_fido(const struct hid_device_info *hdi) +{ + uint32_t usage_page = 0; + struct hidraw_report_descriptor *hrd; + + if ((hrd = calloc(1, sizeof(*hrd))) == NULL || + get_report_descriptor(hdi->path, hrd) < 0 || + fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0) + usage_page = 0; + + free(hrd); + + return usage_page == 0xf1d0; +} +#elif defined(_WIN32) || defined(__APPLE__) +static bool +is_fido(const struct hid_device_info *hdi) +{ + return hdi->usage_page == 0xf1d0; +} +#else +static bool +is_fido(const struct hid_device_info *hdi) +{ + (void)hdi; + fido_log_debug("%s: assuming FIDO HID", __func__); + return true; +} +#endif + +void * +fido_hid_open(const char *path) +{ + struct hid_hidapi *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + return (NULL); + } + + if ((ctx->handle = hid_open_path(path)) == NULL) { + free(ctx); + return (NULL); + } + + ctx->report_in_len = ctx->report_out_len = CTAP_MAX_REPORT_LEN; + + return ctx; +} + +void +fido_hid_close(void *handle) +{ + struct hid_hidapi *ctx = handle; + + hid_close(ctx->handle); + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + (void)handle; + (void)sigmask; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_hidapi *ctx = handle; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return -1; + } + + return hid_read_timeout(ctx->handle, buf, len, ms); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_hidapi *ctx = handle; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return -1; + } + + return hid_write(ctx->handle, buf, len); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + struct hid_device_info *hdi; + + *olen = 0; + + if (ilen == 0) + return FIDO_OK; /* nothing to do */ + if (devlist == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + if ((hdi = hid_enumerate(0, 0)) == NULL) + return FIDO_OK; /* nothing to do */ + + for (struct hid_device_info *d = hdi; d != NULL; d = d->next) { + if (is_fido(d) == false) + continue; + if (copy_info(&devlist[*olen], d) == 0) { + if (++(*olen) == ilen) + break; + } + } + + hid_free_enumeration(hdi); + + return FIDO_OK; +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_hidapi *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_hidapi *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_linux.c b/src/hid_linux.c new file mode 100644 index 0000000..841a95b --- /dev/null +++ b/src/hid_linux.c @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/file.h> +#include <sys/ioctl.h> + +#include <linux/hidraw.h> +#include <linux/input.h> + +#include <errno.h> +#include <libudev.h> +#include <time.h> +#include <unistd.h> + +#include "fido.h" + +struct hid_linux { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +static int +get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd) +{ + int s = -1; + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__); + return (-1); + } + + if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) { + fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s); + return (-1); + } + + hrd->size = (unsigned)s; + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__); + return (-1); + } + + return (0); +} + +static bool +is_fido(const char *path) +{ + int fd = -1; + uint32_t usage_page = 0; + struct hidraw_report_descriptor *hrd = NULL; + + if ((hrd = calloc(1, sizeof(*hrd))) == NULL || + (fd = fido_hid_unix_open(path)) == -1) + goto out; + if (get_report_descriptor(fd, hrd) < 0 || + fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0) + usage_page = 0; + +out: + free(hrd); + + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + return (usage_page == 0xf1d0); +} + +static int +parse_uevent(const char *uevent, int *bus, int16_t *vendor_id, + int16_t *product_id) +{ + char *cp; + char *p; + char *s; + int ok = -1; + short unsigned int x; + short unsigned int y; + short unsigned int z; + + if ((s = cp = strdup(uevent)) == NULL) + return (-1); + + while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') { + if (strncmp(p, "HID_ID=", 7) == 0) { + if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) { + *bus = (int)x; + *vendor_id = (int16_t)y; + *product_id = (int16_t)z; + ok = 0; + break; + } + } + } + + free(s); + + return (ok); +} + +static char * +get_parent_attr(struct udev_device *dev, const char *subsystem, + const char *devtype, const char *attr) +{ + struct udev_device *parent; + const char *value; + + if ((parent = udev_device_get_parent_with_subsystem_devtype(dev, + subsystem, devtype)) == NULL || (value = + udev_device_get_sysattr_value(parent, attr)) == NULL) + return (NULL); + + return (strdup(value)); +} + +static char * +get_usb_attr(struct udev_device *dev, const char *attr) +{ + return (get_parent_attr(dev, "usb", "usb_device", attr)); +} + +static int +copy_info(fido_dev_info_t *di, struct udev *udev, + struct udev_list_entry *udev_entry) +{ + const char *name; + const char *path; + char *uevent = NULL; + struct udev_device *dev = NULL; + int bus = 0; + int ok = -1; + + memset(di, 0, sizeof(*di)); + + if ((name = udev_list_entry_get_name(udev_entry)) == NULL || + (dev = udev_device_new_from_syspath(udev, name)) == NULL || + (path = udev_device_get_devnode(dev)) == NULL || + is_fido(path) == 0) + goto fail; + + if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL || + parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) { + fido_log_debug("%s: uevent", __func__); + goto fail; + } + +#ifndef FIDO_HID_ANY + if (bus != BUS_USB) { + fido_log_debug("%s: bus", __func__); + goto fail; + } +#endif + + di->path = strdup(path); + if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL) + di->manufacturer = strdup(""); + if ((di->product = get_usb_attr(dev, "product")) == NULL) + di->product = strdup(""); + if (di->path == NULL || di->manufacturer == NULL || di->product == NULL) + goto fail; + + ok = 0; +fail: + if (dev != NULL) + udev_device_unref(dev); + + free(uevent); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + struct udev *udev = NULL; + struct udev_enumerate *udev_enum = NULL; + struct udev_list_entry *udev_list; + struct udev_list_entry *udev_entry; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((udev = udev_new()) == NULL || + (udev_enum = udev_enumerate_new(udev)) == NULL) + goto fail; + + if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 || + udev_enumerate_scan_devices(udev_enum) < 0) + goto fail; + + if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) { + r = FIDO_OK; /* zero hidraw devices */ + goto fail; + } + + udev_list_entry_foreach(udev_entry, udev_list) { + if (copy_info(&devlist[*olen], udev, udev_entry) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (udev_enum != NULL) + udev_enumerate_unref(udev_enum); + if (udev != NULL) + udev_unref(udev); + + return (r); +} + +void * +fido_hid_open(const char *path) +{ + struct hid_linux *ctx; + struct hidraw_report_descriptor *hrd; + struct timespec tv_pause; + long interval_ms, retries = 0; + bool looped; + +retry: + looped = false; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL || + (ctx->fd = fido_hid_unix_open(path)) == -1) { + free(ctx); + return (NULL); + } + + while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) { + if (errno != EWOULDBLOCK) { + fido_log_error(errno, "%s: flock", __func__); + fido_hid_close(ctx); + return (NULL); + } + looped = true; + if (retries++ >= 20) { + fido_log_debug("%s: flock timeout", __func__); + fido_hid_close(ctx); + return (NULL); + } + interval_ms = retries * 100000000L; + tv_pause.tv_sec = interval_ms / 1000000000L; + tv_pause.tv_nsec = interval_ms % 1000000000L; + if (nanosleep(&tv_pause, NULL) == -1) { + fido_log_error(errno, "%s: nanosleep", __func__); + fido_hid_close(ctx); + return (NULL); + } + } + + if (looped) { + fido_log_debug("%s: retrying", __func__); + fido_hid_close(ctx); + goto retry; + } + + if ((hrd = calloc(1, sizeof(*hrd))) == NULL || + get_report_descriptor(ctx->fd, hrd) < 0 || + fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len, + &ctx->report_out_len) < 0 || ctx->report_in_len == 0 || + ctx->report_out_len == 0) { + fido_log_debug("%s: using default report sizes", __func__); + ctx->report_in_len = CTAP_MAX_REPORT_LEN; + ctx->report_out_len = CTAP_MAX_REPORT_LEN; + } + + free(hrd); + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_linux *ctx = handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_linux *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_linux *ctx = handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_linux *ctx = handle; + ssize_t r; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if ((r = write(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_linux *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_linux *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_netbsd.c b/src/hid_netbsd.c new file mode 100644 index 0000000..d5b9fad --- /dev/null +++ b/src/hid_netbsd.c @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/ioctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "fido.h" + +#define MAX_UHID 64 + +struct hid_netbsd { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +/* Hack to make this work with newer kernels even if /usr/include is old. */ +#if __NetBSD_Version__ < 901000000 /* 9.1 */ +#define USB_HID_GET_RAW _IOR('h', 1, int) +#define USB_HID_SET_RAW _IOW('h', 2, int) +#endif + +static bool +is_fido(int fd) +{ + struct usb_ctl_report_desc ucrd; + uint32_t usage_page = 0; + int raw = 1; + + memset(&ucrd, 0, sizeof(ucrd)); + + if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + return (false); + } + + if (ucrd.ucrd_size < 0 || + (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) || + fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size, + &usage_page) < 0) { + fido_log_debug("%s: fido_hid_get_usage", __func__); + return (false); + } + + if (usage_page != 0xf1d0) + return (false); + + /* + * This step is not strictly necessary -- NetBSD puts fido + * devices into raw mode automatically by default, but in + * principle that might change, and this serves as a test to + * verify that we're running on a kernel with support for raw + * mode at all so we don't get confused issuing writes that try + * to set the report descriptor rather than transfer data on + * the output interrupt pipe as we need. + */ + if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) { + fido_log_error(errno, "%s: unable to set raw", __func__); + return (false); + } + + return (true); +} + +static int +copy_info(fido_dev_info_t *di, const char *path) +{ + int fd = -1; + int ok = -1; + struct usb_device_info udi; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + + if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0) + goto fail; + + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + goto fail; + } + + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + char path[64]; + size_t i; + + *olen = 0; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL || olen == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) { + snprintf(path, sizeof(path), "/dev/uhid%zu", i); + if (copy_info(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*olen); + } + } + + return (FIDO_OK); +} + +/* + * Workaround for NetBSD (as of 201910) bug that loses + * sync of DATA0/DATA1 sequence bit across uhid open/close. + * Send pings until we get a response - early pings with incorrect + * sequence bits will be ignored as duplicate packets by the device. + */ +static int +terrible_ping_kludge(struct hid_netbsd *ctx) +{ + u_char data[256]; + int i, n; + struct pollfd pfd; + + if (sizeof(data) < ctx->report_out_len + 1) + return -1; + for (i = 0; i < 4; i++) { + memset(data, 0, sizeof(data)); + /* broadcast channel ID */ + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + data[4] = 0xff; + /* Ping command */ + data[5] = 0x81; + /* One byte ping only, Vasili */ + data[6] = 0; + data[7] = 1; + fido_log_debug("%s: send ping %d", __func__, i); + if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1) + return -1; + fido_log_debug("%s: wait reply", __func__); + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = ctx->fd; + pfd.events = POLLIN; + if ((n = poll(&pfd, 1, 100)) == -1) { + fido_log_error(errno, "%s: poll", __func__); + return -1; + } else if (n == 0) { + fido_log_debug("%s: timed out", __func__); + continue; + } + if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1) + return -1; + /* + * Ping isn't always supported on the broadcast channel, + * so we might get an error, but we don't care - we're + * synched now. + */ + fido_log_xxd(data, ctx->report_out_len, "%s: got reply", + __func__); + return 0; + } + fido_log_debug("%s: no response", __func__); + return -1; +} + +void * +fido_hid_open(const char *path) +{ + struct hid_netbsd *ctx; + struct usb_ctl_report_desc ucrd; + int r; + + memset(&ucrd, 0, sizeof(ucrd)); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL || + (ctx->fd = fido_hid_unix_open(path)) == -1) { + free(ctx); + return (NULL); + } + + if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 || + ucrd.ucrd_size < 0 || + (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) || + fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size, + &ctx->report_in_len, &ctx->report_out_len) < 0) { + if (r == -1) + fido_log_error(errno, "%s: ioctl", __func__); + fido_log_debug("%s: using default report sizes", __func__); + ctx->report_in_len = CTAP_MAX_REPORT_LEN; + ctx->report_out_len = CTAP_MAX_REPORT_LEN; + } + + /* + * NetBSD has a bug that causes it to lose + * track of the DATA0/DATA1 sequence toggle across uhid device + * open and close. This is a terrible hack to work around it. + */ + if (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) { + fido_hid_close(ctx); + return NULL; + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_netbsd *ctx = handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_netbsd *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_netbsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_error(errno, "%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_netbsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len - 1) { + fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_netbsd *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_netbsd *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_openbsd.c b/src/hid_openbsd.c new file mode 100644 index 0000000..2d08aca --- /dev/null +++ b/src/hid_openbsd.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2019 Google LLC. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <sys/ioctl.h> +#include <dev/usb/usb.h> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <unistd.h> + +#include "fido.h" + +#define MAX_UHID 64 + +struct hid_openbsd { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +static int +copy_info(fido_dev_info_t *di, const char *path) +{ + int fd = -1, ok = -1; + struct usb_device_info udi; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + + if ((fd = fido_hid_unix_open(path)) == -1) + goto fail; + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + fido_log_error(errno, "%s: ioctl %s", __func__, path); + goto fail; + } + + fido_log_debug("%s: %s: bus = 0x%02x, addr = 0x%02x", __func__, path, + udi.udi_bus, udi.udi_addr); + fido_log_debug("%s: %s: vendor = \"%s\", product = \"%s\"", __func__, + path, udi.udi_vendor, udi.udi_product); + fido_log_debug("%s: %s: productNo = 0x%04x, vendorNo = 0x%04x, " + "releaseNo = 0x%04x", __func__, path, udi.udi_productNo, + udi.udi_vendorNo, udi.udi_releaseNo); + + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close %s", __func__, path); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + size_t i; + char path[64]; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL || olen == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) { + snprintf(path, sizeof(path), "/dev/fido/%zu", i); + if (copy_info(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*olen); + } + } + + return (FIDO_OK); +} + +/* + * Workaround for OpenBSD <=6.6-current (as of 201910) bug that loses + * sync of DATA0/DATA1 sequence bit across uhid open/close. + * Send pings until we get a response - early pings with incorrect + * sequence bits will be ignored as duplicate packets by the device. + */ +static int +terrible_ping_kludge(struct hid_openbsd *ctx) +{ + u_char data[256]; + int i, n; + struct pollfd pfd; + + if (sizeof(data) < ctx->report_out_len + 1) + return -1; + for (i = 0; i < 4; i++) { + memset(data, 0, sizeof(data)); + /* broadcast channel ID */ + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + data[4] = 0xff; + /* Ping command */ + data[5] = 0x81; + /* One byte ping only, Vasili */ + data[6] = 0; + data[7] = 1; + fido_log_debug("%s: send ping %d", __func__, i); + if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1) + return -1; + fido_log_debug("%s: wait reply", __func__); + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = ctx->fd; + pfd.events = POLLIN; + if ((n = poll(&pfd, 1, 100)) == -1) { + fido_log_error(errno, "%s: poll", __func__); + return -1; + } else if (n == 0) { + fido_log_debug("%s: timed out", __func__); + continue; + } + if (fido_hid_read(ctx, data, ctx->report_out_len, 250) == -1) + return -1; + /* + * Ping isn't always supported on the broadcast channel, + * so we might get an error, but we don't care - we're + * synched now. + */ + fido_log_xxd(data, ctx->report_out_len, "%s: got reply", + __func__); + return 0; + } + fido_log_debug("%s: no response", __func__); + return -1; +} + +void * +fido_hid_open(const char *path) +{ + struct hid_openbsd *ret = NULL; + + if ((ret = calloc(1, sizeof(*ret))) == NULL || + (ret->fd = fido_hid_unix_open(path)) == -1) { + free(ret); + return (NULL); + } + ret->report_in_len = ret->report_out_len = CTAP_MAX_REPORT_LEN; + fido_log_debug("%s: inlen = %zu outlen = %zu", __func__, + ret->report_in_len, ret->report_out_len); + + /* + * OpenBSD (as of 201910) has a bug that causes it to lose + * track of the DATA0/DATA1 sequence toggle across uhid device + * open and close. This is a terrible hack to work around it. + */ + if (terrible_ping_kludge(ret) != 0) { + fido_hid_close(ret); + return NULL; + } + + return (ret); +} + +void +fido_hid_close(void *handle) +{ + struct hid_openbsd *ctx = (struct hid_openbsd *)handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_openbsd *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_openbsd *ctx = (struct hid_openbsd *)handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: invalid len: got %zu, want %zu", __func__, + len, ctx->report_in_len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)len); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_openbsd *ctx = (struct hid_openbsd *)handle; + ssize_t r; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: invalid len: got %zu, want %zu", __func__, + len, ctx->report_out_len); + return (-1); + } + + if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len - 1) { + fido_log_debug("%s: %zd != %zu", __func__, r, len - 1); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_openbsd *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_openbsd *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_osx.c b/src/hid_osx.c new file mode 100644 index 0000000..9309762 --- /dev/null +++ b/src/hid_osx.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> + +#include <Availability.h> +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/hid/IOHIDKeys.h> +#include <IOKit/hid/IOHIDManager.h> + +#include "fido.h" + +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000 +#define kIOMainPortDefault kIOMasterPortDefault +#endif + +#define IOREG "ioreg://" + +struct hid_osx { + IOHIDDeviceRef ref; + CFStringRef loop_id; + int report_pipe[2]; + size_t report_in_len; + size_t report_out_len; + unsigned char report[CTAP_MAX_REPORT_LEN]; +}; + +static int +get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v) +{ + CFTypeRef ref; + + if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL || + CFGetTypeID(ref) != CFNumberGetTypeID()) { + fido_log_debug("%s: IOHIDDeviceGetProperty", __func__); + return (-1); + } + + if (CFNumberGetType(ref) != kCFNumberSInt32Type && + CFNumberGetType(ref) != kCFNumberSInt64Type) { + fido_log_debug("%s: CFNumberGetType", __func__); + return (-1); + } + + if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) { + fido_log_debug("%s: CFNumberGetValue", __func__); + return (-1); + } + + return (0); +} + +static int +get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len) +{ + CFTypeRef ref; + + memset(buf, 0, len); + + if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL || + CFGetTypeID(ref) != CFStringGetTypeID()) { + fido_log_debug("%s: IOHIDDeviceGetProperty", __func__); + return (-1); + } + + if (CFStringGetCString(ref, buf, (long)len, + kCFStringEncodingUTF8) == false) { + fido_log_debug("%s: CFStringGetCString", __func__); + return (-1); + } + + return (0); +} + +static int +get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len) +{ + CFStringRef key; + int32_t v; + + if (dir == 0) + key = CFSTR(kIOHIDMaxInputReportSizeKey); + else + key = CFSTR(kIOHIDMaxOutputReportSizeKey); + + if (get_int32(dev, key, &v) < 0) { + fido_log_debug("%s: get_int32/%d", __func__, dir); + return (-1); + } + + if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: report_len=%zu", __func__, *report_len); + return (-1); + } + + return (0); +} + +static int +get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id) +{ + int32_t vendor; + int32_t product; + + if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 || + vendor > UINT16_MAX) { + fido_log_debug("%s: get_int32 vendor", __func__); + return (-1); + } + + if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 || + product > UINT16_MAX) { + fido_log_debug("%s: get_int32 product", __func__); + return (-1); + } + + *vendor_id = (int16_t)vendor; + *product_id = (int16_t)product; + + return (0); +} + +static int +get_str(IOHIDDeviceRef dev, char **manufacturer, char **product) +{ + char buf[512]; + int ok = -1; + + *manufacturer = NULL; + *product = NULL; + + if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0) + *manufacturer = strdup(""); + else + *manufacturer = strdup(buf); + + if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0) + *product = strdup(""); + else + *product = strdup(buf); + + if (*manufacturer == NULL || *product == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) { + free(*manufacturer); + free(*product); + *manufacturer = NULL; + *product = NULL; + } + + return (ok); +} + +static char * +get_path(IOHIDDeviceRef dev) +{ + io_service_t s; + uint64_t id; + char *path; + + if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) { + fido_log_debug("%s: IOHIDDeviceGetService", __func__); + return (NULL); + } + + if (IORegistryEntryGetRegistryEntryID(s, &id) != KERN_SUCCESS) { + fido_log_debug("%s: IORegistryEntryGetRegistryEntryID", + __func__); + return (NULL); + } + + if (asprintf(&path, "%s%llu", IOREG, (unsigned long long)id) == -1) { + fido_log_error(errno, "%s: asprintf", __func__); + return (NULL); + } + + return (path); +} + +static bool +is_fido(IOHIDDeviceRef dev) +{ + char buf[32]; + uint32_t usage_page; + + if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey), + (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0) + return (false); + + if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) { + fido_log_debug("%s: get_utf8 transport", __func__); + return (false); + } + +#ifndef FIDO_HID_ANY + if (strcasecmp(buf, "usb") != 0) { + fido_log_debug("%s: transport", __func__); + return (false); + } +#endif + + return (true); +} + +static int +copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev) +{ + memset(di, 0, sizeof(*di)); + + if (is_fido(dev) == false) + return (-1); + + if (get_id(dev, &di->vendor_id, &di->product_id) < 0 || + get_str(dev, &di->manufacturer, &di->product) < 0 || + (di->path = get_path(dev)) == NULL) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + return (-1); + } + + return (0); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + IOHIDManagerRef manager = NULL; + CFSetRef devset = NULL; + size_t devcnt; + CFIndex n; + IOHIDDeviceRef *devs = NULL; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((manager = IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDManagerOptionNone)) == NULL) { + fido_log_debug("%s: IOHIDManagerCreate", __func__); + goto fail; + } + + IOHIDManagerSetDeviceMatching(manager, NULL); + + if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) { + fido_log_debug("%s: IOHIDManagerCopyDevices", __func__); + goto fail; + } + + if ((n = CFSetGetCount(devset)) < 0) { + fido_log_debug("%s: CFSetGetCount", __func__); + goto fail; + } + + devcnt = (size_t)n; + + if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + + CFSetGetValues(devset, (void *)devs); + + for (size_t i = 0; i < devcnt; i++) { + if (copy_info(&devlist[*olen], devs[i]) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (manager != NULL) + CFRelease(manager); + if (devset != NULL) + CFRelease(devset); + + free(devs); + + return (r); +} + +static void +report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type, + uint32_t id, uint8_t *ptr, CFIndex len) +{ + struct hid_osx *ctx = context; + ssize_t r; + + (void)dev; + + if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput || + id != 0 || len < 0 || (size_t)len != ctx->report_in_len) { + fido_log_debug("%s: io error", __func__); + return; + } + + if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return; + } + + if (r < 0 || (size_t)r != (size_t)len) { + fido_log_debug("%s: %zd != %zu", __func__, r, (size_t)len); + return; + } +} + +static void +removal_callback(void *context, IOReturn result, void *sender) +{ + (void)context; + (void)result; + (void)sender; + + CFRunLoopStop(CFRunLoopGetCurrent()); +} + +static int +set_nonblock(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1) { + fido_log_error(errno, "%s: fcntl F_GETFL", __func__); + return (-1); + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + fido_log_error(errno, "%s: fcntl F_SETFL", __func__); + return (-1); + } + + return (0); +} + +static int +disable_sigpipe(int fd) +{ + int disabled = 1; + + if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) { + fido_log_error(errno, "%s: fcntl F_SETNOSIGPIPE", __func__); + return (-1); + } + + return (0); +} + +static io_registry_entry_t +get_ioreg_entry(const char *path) +{ + uint64_t id; + + if (strncmp(path, IOREG, strlen(IOREG)) != 0) + return (IORegistryEntryFromPath(kIOMainPortDefault, path)); + + if (fido_to_uint64(path + strlen(IOREG), 10, &id) == -1) { + fido_log_debug("%s: fido_to_uint64", __func__); + return (MACH_PORT_NULL); + } + + return (IOServiceGetMatchingService(kIOMainPortDefault, + IORegistryEntryIDMatching(id))); +} + +void * +fido_hid_open(const char *path) +{ + struct hid_osx *ctx; + io_registry_entry_t entry = MACH_PORT_NULL; + char loop_id[32]; + int ok = -1; + int r; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + + ctx->report_pipe[0] = -1; + ctx->report_pipe[1] = -1; + + if (pipe(ctx->report_pipe) == -1) { + fido_log_error(errno, "%s: pipe", __func__); + goto fail; + } + + if (set_nonblock(ctx->report_pipe[0]) < 0 || + set_nonblock(ctx->report_pipe[1]) < 0) { + fido_log_debug("%s: set_nonblock", __func__); + goto fail; + } + + if (disable_sigpipe(ctx->report_pipe[1]) < 0) { + fido_log_debug("%s: disable_sigpipe", __func__); + goto fail; + } + + if ((entry = get_ioreg_entry(path)) == MACH_PORT_NULL) { + fido_log_debug("%s: get_ioreg_entry: %s", __func__, path); + goto fail; + } + + if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault, + entry)) == NULL) { + fido_log_debug("%s: IOHIDDeviceCreate", __func__); + goto fail; + } + + if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 || + get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) { + fido_log_debug("%s: get_report_len", __func__); + goto fail; + } + + if (ctx->report_in_len > sizeof(ctx->report)) { + fido_log_debug("%s: report_in_len=%zu", __func__, + ctx->report_in_len); + goto fail; + } + + if (IOHIDDeviceOpen(ctx->ref, + kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) { + fido_log_debug("%s: IOHIDDeviceOpen", __func__); + goto fail; + } + + if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p", + (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) { + fido_log_debug("%s: snprintf", __func__); + goto fail; + } + + if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id, + kCFStringEncodingASCII)) == NULL) { + fido_log_debug("%s: CFStringCreateWithCString", __func__); + goto fail; + } + + IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report, + (long)ctx->report_in_len, &report_callback, ctx); + IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx); + + ok = 0; +fail: + if (entry != MACH_PORT_NULL) + IOObjectRelease(entry); + + if (ok < 0 && ctx != NULL) { + if (ctx->ref != NULL) + CFRelease(ctx->ref); + if (ctx->loop_id != NULL) + CFRelease(ctx->loop_id); + if (ctx->report_pipe[0] != -1) + close(ctx->report_pipe[0]); + if (ctx->report_pipe[1] != -1) + close(ctx->report_pipe[1]); + free(ctx); + ctx = NULL; + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_osx *ctx = handle; + + IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report, + (long)ctx->report_in_len, NULL, ctx); + IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, ctx); + + if (IOHIDDeviceClose(ctx->ref, + kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) + fido_log_debug("%s: IOHIDDeviceClose", __func__); + + CFRelease(ctx->ref); + CFRelease(ctx->loop_id); + + explicit_bzero(ctx->report, sizeof(ctx->report)); + close(ctx->report_pipe[0]); + close(ctx->report_pipe[1]); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + (void)handle; + (void)sigmask; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_osx *ctx = handle; + ssize_t r; + + explicit_bzero(buf, len); + explicit_bzero(ctx->report, sizeof(ctx->report)); + + if (len != ctx->report_in_len || len > sizeof(ctx->report)) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetCurrent(), + ctx->loop_id); + + if (ms == -1) + ms = 5000; /* wait 5 seconds by default */ + + CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true); + + IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetCurrent(), + ctx->loop_id); + + if ((r = read(ctx->report_pipe[0], buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)len); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_osx *ctx = handle; + + if (len != ctx->report_out_len + 1 || len > LONG_MAX) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1, + (long)(len - 1)) != kIOReturnSuccess) { + fido_log_debug("%s: IOHIDDeviceSetReport", __func__); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_osx *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_osx *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_unix.c b/src/hid_unix.c new file mode 100644 index 0000000..e53882d --- /dev/null +++ b/src/hid_unix.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +#include "fido.h" + +#ifdef __NetBSD__ +#define ppoll pollts +#endif + +int +fido_hid_unix_open(const char *path) +{ + int fd; + struct stat st; + + if ((fd = open(path, O_RDWR)) == -1) { + if (errno != ENOENT && errno != ENXIO) + fido_log_error(errno, "%s: open %s", __func__, path); + return (-1); + } + + if (fstat(fd, &st) == -1) { + fido_log_error(errno, "%s: fstat %s", __func__, path); + if (close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + return (-1); + } + + if (S_ISCHR(st.st_mode) == 0) { + fido_log_debug("%s: S_ISCHR %s", __func__, path); + if (close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + return (-1); + } + + return (fd); +} + +int +fido_hid_unix_wait(int fd, int ms, const fido_sigset_t *sigmask) +{ + struct timespec ts; + struct pollfd pfd; + int r; + + memset(&pfd, 0, sizeof(pfd)); + pfd.events = POLLIN; + pfd.fd = fd; + +#ifdef FIDO_FUZZ + return (0); +#endif + if (ms > -1) { + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + } + + if ((r = ppoll(&pfd, 1, ms > -1 ? &ts : NULL, sigmask)) < 1) { + if (r == -1) + fido_log_error(errno, "%s: ppoll", __func__); + return (-1); + } + + return (0); +} diff --git a/src/hid_win.c b/src/hid_win.c new file mode 100644 index 0000000..bc98a17 --- /dev/null +++ b/src/hid_win.c @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2019-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <fcntl.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <windows.h> +#include <setupapi.h> +#include <initguid.h> +#include <devpkey.h> +#include <devpropdef.h> +#include <hidclass.h> +#include <hidsdi.h> +#include <wchar.h> + +#include "fido.h" + +#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 6 +WINSETUPAPI WINBOOL WINAPI SetupDiGetDevicePropertyW(HDEVINFO, + PSP_DEVINFO_DATA, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, + DWORD, PDWORD, DWORD); +#endif + +#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 8 +DEFINE_DEVPROPKEY(DEVPKEY_Device_Parent, 0x4340a6c5, 0x93fa, 0x4706, 0x97, + 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7, 8); +#endif + +struct hid_win { + HANDLE dev; + OVERLAPPED overlap; + int report_pending; + size_t report_in_len; + size_t report_out_len; + unsigned char report[1 + CTAP_MAX_REPORT_LEN]; +}; + +static bool +is_fido(HANDLE dev) +{ + PHIDP_PREPARSED_DATA data = NULL; + HIDP_CAPS caps; + int fido = 0; + + if (HidD_GetPreparsedData(dev, &data) == false) { + fido_log_debug("%s: HidD_GetPreparsedData", __func__); + goto fail; + } + + if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) { + fido_log_debug("%s: HidP_GetCaps", __func__); + goto fail; + } + + fido = (uint16_t)caps.UsagePage == 0xf1d0; +fail: + if (data != NULL) + HidD_FreePreparsedData(data); + + return (fido); +} + +static int +get_report_len(HANDLE dev, int dir, size_t *report_len) +{ + PHIDP_PREPARSED_DATA data = NULL; + HIDP_CAPS caps; + USHORT v; + int ok = -1; + + if (HidD_GetPreparsedData(dev, &data) == false) { + fido_log_debug("%s: HidD_GetPreparsedData/%d", __func__, dir); + goto fail; + } + + if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) { + fido_log_debug("%s: HidP_GetCaps/%d", __func__, dir); + goto fail; + } + + if (dir == 0) + v = caps.InputReportByteLength; + else + v = caps.OutputReportByteLength; + + if ((*report_len = (size_t)v) == 0) { + fido_log_debug("%s: report_len == 0", __func__); + goto fail; + } + + ok = 0; +fail: + if (data != NULL) + HidD_FreePreparsedData(data); + + return (ok); +} + +static int +get_id(HANDLE dev, int16_t *vendor_id, int16_t *product_id) +{ + HIDD_ATTRIBUTES attr; + + attr.Size = sizeof(attr); + + if (HidD_GetAttributes(dev, &attr) == false) { + fido_log_debug("%s: HidD_GetAttributes", __func__); + return (-1); + } + + *vendor_id = (int16_t)attr.VendorID; + *product_id = (int16_t)attr.ProductID; + + return (0); +} + +static int +get_manufacturer(HANDLE dev, char **manufacturer) +{ + wchar_t buf[512]; + int utf8_len; + int ok = -1; + + *manufacturer = NULL; + + if (HidD_GetManufacturerString(dev, &buf, sizeof(buf)) == false) { + fido_log_debug("%s: HidD_GetManufacturerString", __func__); + goto fail; + } + + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, + -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + if ((*manufacturer = malloc((size_t)utf8_len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1, + *manufacturer, utf8_len, NULL, NULL) != utf8_len) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) { + free(*manufacturer); + *manufacturer = NULL; + } + + return (ok); +} + +static int +get_product(HANDLE dev, char **product) +{ + wchar_t buf[512]; + int utf8_len; + int ok = -1; + + *product = NULL; + + if (HidD_GetProductString(dev, &buf, sizeof(buf)) == false) { + fido_log_debug("%s: HidD_GetProductString", __func__); + goto fail; + } + + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, + -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + if ((*product = malloc((size_t)utf8_len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1, + *product, utf8_len, NULL, NULL) != utf8_len) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) { + free(*product); + *product = NULL; + } + + return (ok); +} + +static char * +get_path(HDEVINFO devinfo, SP_DEVICE_INTERFACE_DATA *ifdata) +{ + SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL; + char *path = NULL; + DWORD len = 0; + + /* + * "Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail + * with a NULL DeviceInterfaceDetailData pointer, a + * DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize + * variable. In response to such a call, this function returns the + * required buffer size at RequiredSize and fails with GetLastError + * returning ERROR_INSUFFICIENT_BUFFER." + */ + if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, NULL, 0, &len, + NULL) != false || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1", + __func__); + goto fail; + } + + if ((ifdetail = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + ifdetail->cbSize = sizeof(*ifdetail); + + if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, ifdetail, len, + NULL, NULL) == false) { + fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2", + __func__); + goto fail; + } + + if ((path = strdup(ifdetail->DevicePath)) == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + +fail: + free(ifdetail); + + return (path); +} + +#ifndef FIDO_HID_ANY +static bool +hid_ok(HDEVINFO devinfo, DWORD idx) +{ + SP_DEVINFO_DATA devinfo_data; + wchar_t *parent = NULL; + DWORD parent_type = DEVPROP_TYPE_STRING; + DWORD len = 0; + bool ok = false; + + memset(&devinfo_data, 0, sizeof(devinfo_data)); + devinfo_data.cbSize = sizeof(devinfo_data); + + if (SetupDiEnumDeviceInfo(devinfo, idx, &devinfo_data) == false) { + fido_log_debug("%s: SetupDiEnumDeviceInfo", __func__); + goto fail; + } + + if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, + &DEVPKEY_Device_Parent, &parent_type, NULL, 0, &len, 0) != false || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + fido_log_debug("%s: SetupDiGetDevicePropertyW 1", __func__); + goto fail; + } + + if ((parent = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, + &DEVPKEY_Device_Parent, &parent_type, (PBYTE)parent, len, NULL, + 0) == false) { + fido_log_debug("%s: SetupDiGetDevicePropertyW 2", __func__); + goto fail; + } + + ok = wcsncmp(parent, L"USB\\", 4) == 0; +fail: + free(parent); + + return (ok); +} +#endif + +static int +copy_info(fido_dev_info_t *di, HDEVINFO devinfo, DWORD idx, + SP_DEVICE_INTERFACE_DATA *ifdata) +{ + HANDLE dev = INVALID_HANDLE_VALUE; + int ok = -1; + + memset(di, 0, sizeof(*di)); + + if ((di->path = get_path(devinfo, ifdata)) == NULL) { + fido_log_debug("%s: get_path", __func__); + goto fail; + } + + fido_log_debug("%s: path=%s", __func__, di->path); + +#ifndef FIDO_HID_ANY + if (hid_ok(devinfo, idx) == false) { + fido_log_debug("%s: hid_ok", __func__); + goto fail; + } +#endif + + dev = CreateFileA(di->path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (dev == INVALID_HANDLE_VALUE) { + fido_log_debug("%s: CreateFileA", __func__); + goto fail; + } + + if (is_fido(dev) == false) { + fido_log_debug("%s: is_fido", __func__); + goto fail; + } + + if (get_id(dev, &di->vendor_id, &di->product_id) < 0) { + fido_log_debug("%s: get_id", __func__); + goto fail; + } + + if (get_manufacturer(dev, &di->manufacturer) < 0) { + fido_log_debug("%s: get_manufacturer", __func__); + di->manufacturer = strdup(""); + } + + if (get_product(dev, &di->product) < 0) { + fido_log_debug("%s: get_product", __func__); + di->product = strdup(""); + } + + if (di->manufacturer == NULL || di->product == NULL) { + fido_log_debug("%s: manufacturer/product", __func__); + goto fail; + } + + ok = 0; +fail: + if (dev != INVALID_HANDLE_VALUE) + CloseHandle(dev); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + GUID hid_guid = GUID_DEVINTERFACE_HID; + HDEVINFO devinfo = INVALID_HANDLE_VALUE; + SP_DEVICE_INTERFACE_DATA ifdata; + DWORD idx; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + if (devlist == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL, + DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)) == INVALID_HANDLE_VALUE) { + fido_log_debug("%s: SetupDiGetClassDevsA", __func__); + goto fail; + } + + ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (idx = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, + idx, &ifdata) == true; idx++) { + if (copy_info(&devlist[*olen], devinfo, idx, &ifdata) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (devinfo != INVALID_HANDLE_VALUE) + SetupDiDestroyDeviceInfoList(devinfo); + + return (r); +} + +void * +fido_hid_open(const char *path) +{ + struct hid_win *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); + + ctx->dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (ctx->dev == INVALID_HANDLE_VALUE) { + free(ctx); + return (NULL); + } + + if ((ctx->overlap.hEvent = CreateEventA(NULL, FALSE, FALSE, + NULL)) == NULL) { + fido_log_debug("%s: CreateEventA", __func__); + fido_hid_close(ctx); + return (NULL); + } + + if (get_report_len(ctx->dev, 0, &ctx->report_in_len) < 0 || + get_report_len(ctx->dev, 1, &ctx->report_out_len) < 0) { + fido_log_debug("%s: get_report_len", __func__); + fido_hid_close(ctx); + return (NULL); + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_win *ctx = handle; + + if (ctx->overlap.hEvent != NULL) { + if (ctx->report_pending) { + fido_log_debug("%s: report_pending", __func__); + if (CancelIoEx(ctx->dev, &ctx->overlap) == 0) + fido_log_debug("%s CancelIoEx: 0x%lx", + __func__, (u_long)GetLastError()); + } + CloseHandle(ctx->overlap.hEvent); + } + + explicit_bzero(ctx->report, sizeof(ctx->report)); + CloseHandle(ctx->dev); + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + (void)handle; + (void)sigmask; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_win *ctx = handle; + DWORD n; + + if (len != ctx->report_in_len - 1 || len > sizeof(ctx->report) - 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (ctx->report_pending == 0) { + memset(&ctx->report, 0, sizeof(ctx->report)); + ResetEvent(ctx->overlap.hEvent); + if (ReadFile(ctx->dev, ctx->report, (DWORD)(len + 1), &n, + &ctx->overlap) == 0 && GetLastError() != ERROR_IO_PENDING) { + CancelIo(ctx->dev); + fido_log_debug("%s: ReadFile", __func__); + return (-1); + } + ctx->report_pending = 1; + } + + if (ms > -1 && WaitForSingleObject(ctx->overlap.hEvent, + (DWORD)ms) != WAIT_OBJECT_0) + return (0); + + ctx->report_pending = 0; + + if (GetOverlappedResult(ctx->dev, &ctx->overlap, &n, TRUE) == 0) { + fido_log_debug("%s: GetOverlappedResult", __func__); + return (-1); + } + + if (n != len + 1) { + fido_log_debug("%s: expected %zu, got %zu", __func__, + len + 1, (size_t)n); + return (-1); + } + + memcpy(buf, ctx->report + 1, len); + explicit_bzero(ctx->report, sizeof(ctx->report)); + + return ((int)len); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_win *ctx = handle; + OVERLAPPED overlap; + DWORD n; + + memset(&overlap, 0, sizeof(overlap)); + + if (len != ctx->report_out_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (WriteFile(ctx->dev, buf, (DWORD)len, NULL, &overlap) == 0 && + GetLastError() != ERROR_IO_PENDING) { + fido_log_debug("%s: WriteFile", __func__); + return (-1); + } + + if (GetOverlappedResult(ctx->dev, &overlap, &n, TRUE) == 0) { + fido_log_debug("%s: GetOverlappedResult", __func__); + return (-1); + } + + if (n != len) { + fido_log_debug("%s: expected %zu, got %zu", __func__, len, + (size_t)n); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_win *ctx = handle; + + return (ctx->report_in_len - 1); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_win *ctx = handle; + + return (ctx->report_out_len - 1); +} diff --git a/src/info.c b/src/info.c new file mode 100644 index 0000000..cd30828 --- /dev/null +++ b/src/info.c @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +decode_string(const cbor_item_t *item, void *arg) +{ + fido_str_array_t *a = arg; + const size_t i = a->len; + + /* keep ptr[x] and len consistent */ + if (cbor_string_copy(item, &a->ptr[i]) < 0) { + fido_log_debug("%s: cbor_string_copy", __func__); + return (-1); + } + + a->len++; + + return (0); +} + +static int +decode_string_array(const cbor_item_t *item, fido_str_array_t *v) +{ + v->ptr = NULL; + v->len = 0; + + if (cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + v->ptr = calloc(cbor_array_size(item), sizeof(char *)); + if (v->ptr == NULL) + return (-1); + + if (cbor_array_iter(item, v, decode_string) < 0) { + fido_log_debug("%s: decode_string", __func__); + return (-1); + } + + return (0); +} + +static int +decode_aaguid(const cbor_item_t *item, unsigned char *aaguid, size_t aaguid_len) +{ + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false || + cbor_bytestring_length(item) != aaguid_len) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + memcpy(aaguid, cbor_bytestring_handle(item), aaguid_len); + + return (0); +} + +static int +decode_option(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_opt_array_t *o = arg; + const size_t i = o->len; + + if (cbor_decode_bool(val, NULL) < 0) { + fido_log_debug("%s: cbor_decode_bool", __func__); + return (0); /* ignore */ + } + + if (cbor_string_copy(key, &o->name[i]) < 0) { + fido_log_debug("%s: cbor_string_copy", __func__); + return (0); /* ignore */ + } + + /* keep name/value and len consistent */ + o->value[i] = cbor_ctrl_value(val) == CBOR_CTRL_TRUE; + o->len++; + + return (0); +} + +static int +decode_options(const cbor_item_t *item, fido_opt_array_t *o) +{ + o->name = NULL; + o->value = NULL; + o->len = 0; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + o->name = calloc(cbor_map_size(item), sizeof(char *)); + o->value = calloc(cbor_map_size(item), sizeof(bool)); + if (o->name == NULL || o->value == NULL) + return (-1); + + return (cbor_map_iter(item, o, decode_option)); +} + +static int +decode_protocol(const cbor_item_t *item, void *arg) +{ + fido_byte_array_t *p = arg; + const size_t i = p->len; + + if (cbor_isa_uint(item) == false || + cbor_int_get_width(item) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + /* keep ptr[x] and len consistent */ + p->ptr[i] = cbor_get_uint8(item); + p->len++; + + return (0); +} + +static int +decode_protocols(const cbor_item_t *item, fido_byte_array_t *p) +{ + p->ptr = NULL; + p->len = 0; + + if (cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + p->ptr = calloc(cbor_array_size(item), sizeof(uint8_t)); + if (p->ptr == NULL) + return (-1); + + if (cbor_array_iter(item, p, decode_protocol) < 0) { + fido_log_debug("%s: decode_protocol", __func__); + return (-1); + } + + return (0); +} + +static int +decode_algorithm_entry(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_algo_t *alg = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "alg")) { + if (cbor_isa_negint(val) == false || + cbor_get_int(val) > INT_MAX || alg->cose != 0) { + fido_log_debug("%s: alg", __func__); + goto out; + } + alg->cose = -(int)cbor_get_int(val) - 1; + } else if (!strcmp(name, "type")) { + if (cbor_string_copy(val, &alg->type) < 0) { + fido_log_debug("%s: type", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +static int +decode_algorithm(const cbor_item_t *item, void *arg) +{ + fido_algo_array_t *aa = arg; + const size_t i = aa->len; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + memset(&aa->ptr[i], 0, sizeof(aa->ptr[i])); + + if (cbor_map_iter(item, &aa->ptr[i], decode_algorithm_entry) < 0) { + fido_log_debug("%s: decode_algorithm_entry", __func__); + fido_algo_free(&aa->ptr[i]); + return (-1); + } + + /* keep ptr[x] and len consistent */ + aa->len++; + + return (0); +} + +static int +decode_algorithms(const cbor_item_t *item, fido_algo_array_t *aa) +{ + aa->ptr = NULL; + aa->len = 0; + + if (cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + aa->ptr = calloc(cbor_array_size(item), sizeof(fido_algo_t)); + if (aa->ptr == NULL) + return (-1); + + if (cbor_array_iter(item, aa, decode_algorithm) < 0) { + fido_log_debug("%s: decode_algorithm", __func__); + return (-1); + } + + return (0); +} + +static int +decode_cert(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cert_array_t *c = arg; + const size_t i = c->len; + + if (cbor_is_int(val) == false) { + fido_log_debug("%s: cbor_is_int", __func__); + return (0); /* ignore */ + } + + if (cbor_string_copy(key, &c->name[i]) < 0) { + fido_log_debug("%s: cbor_string_copy", __func__); + return (0); /* ignore */ + } + + /* keep name/value and len consistent */ + c->value[i] = cbor_get_int(val); + c->len++; + + return (0); +} + +static int +decode_certs(const cbor_item_t *item, fido_cert_array_t *c) +{ + c->name = NULL; + c->value = NULL; + c->len = 0; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + c->name = calloc(cbor_map_size(item), sizeof(char *)); + c->value = calloc(cbor_map_size(item), sizeof(uint64_t)); + if (c->name == NULL || c->value == NULL) + return (-1); + + return (cbor_map_iter(item, c, decode_cert)); +} + +static int +parse_reply_element(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cbor_info_t *ci = arg; + uint64_t x; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* versions */ + return (decode_string_array(val, &ci->versions)); + case 2: /* extensions */ + return (decode_string_array(val, &ci->extensions)); + case 3: /* aaguid */ + return (decode_aaguid(val, ci->aaguid, sizeof(ci->aaguid))); + case 4: /* options */ + return (decode_options(val, &ci->options)); + case 5: /* maxMsgSize */ + return (cbor_decode_uint64(val, &ci->maxmsgsiz)); + case 6: /* pinProtocols */ + return (decode_protocols(val, &ci->protocols)); + case 7: /* maxCredentialCountInList */ + return (cbor_decode_uint64(val, &ci->maxcredcntlst)); + case 8: /* maxCredentialIdLength */ + return (cbor_decode_uint64(val, &ci->maxcredidlen)); + case 9: /* transports */ + return (decode_string_array(val, &ci->transports)); + case 10: /* algorithms */ + return (decode_algorithms(val, &ci->algorithms)); + case 11: /* maxSerializedLargeBlobArray */ + return (cbor_decode_uint64(val, &ci->maxlargeblob)); + case 12: /* forcePINChange */ + return (cbor_decode_bool(val, &ci->new_pin_reqd)); + case 13: /* minPINLength */ + return (cbor_decode_uint64(val, &ci->minpinlen)); + case 14: /* fwVersion */ + return (cbor_decode_uint64(val, &ci->fwversion)); + case 15: /* maxCredBlobLen */ + return (cbor_decode_uint64(val, &ci->maxcredbloblen)); + case 16: /* maxRPIDsForSetMinPINLength */ + return (cbor_decode_uint64(val, &ci->maxrpid_minlen)); + case 17: /* preferredPlatformUvAttempts */ + return (cbor_decode_uint64(val, &ci->uv_attempts)); + case 18: /* uvModality */ + return (cbor_decode_uint64(val, &ci->uv_modality)); + case 19: /* certifications */ + return (decode_certs(val, &ci->certs)); + case 20: /* remainingDiscoverableCredentials */ + if (cbor_decode_uint64(val, &x) < 0 || x > INT64_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + ci->rk_remaining = (int64_t)x; + return (0); + default: /* ignore */ + fido_log_debug("%s: cbor type: 0x%02x", __func__, cbor_get_uint8(key)); + return (0); + } +} + +static int +fido_dev_get_cbor_info_tx(fido_dev_t *dev, int *ms) +{ + const unsigned char cbor[] = { CTAP_CBOR_GETINFO }; + + fido_log_debug("%s: dev=%p", __func__, (void *)dev); + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + return (FIDO_ERR_TX); + } + + return (FIDO_OK); +} + +static int +fido_dev_get_cbor_info_rx(fido_dev_t *dev, fido_cbor_info_t *ci, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + fido_log_debug("%s: dev=%p, ci=%p, ms=%d", __func__, (void *)dev, + (void *)ci, *ms); + + fido_cbor_info_reset(ci); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = cbor_parse_reply(msg, (size_t)msglen, ci, parse_reply_element); +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +int +fido_dev_get_cbor_info_wait(fido_dev_t *dev, fido_cbor_info_t *ci, int *ms) +{ + int r; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_get_cbor_info(dev, ci)); +#endif + if ((r = fido_dev_get_cbor_info_tx(dev, ms)) != FIDO_OK || + (r = fido_dev_get_cbor_info_rx(dev, ci, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_dev_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci) +{ + int ms = dev->timeout_ms; + + return (fido_dev_get_cbor_info_wait(dev, ci, &ms)); +} + +/* + * get/set functions for fido_cbor_info_t; always at the end of the file + */ + +fido_cbor_info_t * +fido_cbor_info_new(void) +{ + fido_cbor_info_t *ci; + + if ((ci = calloc(1, sizeof(fido_cbor_info_t))) == NULL) + return (NULL); + + fido_cbor_info_reset(ci); + + return (ci); +} + +void +fido_cbor_info_reset(fido_cbor_info_t *ci) +{ + fido_str_array_free(&ci->versions); + fido_str_array_free(&ci->extensions); + fido_str_array_free(&ci->transports); + fido_opt_array_free(&ci->options); + fido_byte_array_free(&ci->protocols); + fido_algo_array_free(&ci->algorithms); + fido_cert_array_free(&ci->certs); + ci->rk_remaining = -1; +} + +void +fido_cbor_info_free(fido_cbor_info_t **ci_p) +{ + fido_cbor_info_t *ci; + + if (ci_p == NULL || (ci = *ci_p) == NULL) + return; + fido_cbor_info_reset(ci); + free(ci); + *ci_p = NULL; +} + +char ** +fido_cbor_info_versions_ptr(const fido_cbor_info_t *ci) +{ + return (ci->versions.ptr); +} + +size_t +fido_cbor_info_versions_len(const fido_cbor_info_t *ci) +{ + return (ci->versions.len); +} + +char ** +fido_cbor_info_extensions_ptr(const fido_cbor_info_t *ci) +{ + return (ci->extensions.ptr); +} + +size_t +fido_cbor_info_extensions_len(const fido_cbor_info_t *ci) +{ + return (ci->extensions.len); +} + +char ** +fido_cbor_info_transports_ptr(const fido_cbor_info_t *ci) +{ + return (ci->transports.ptr); +} + +size_t +fido_cbor_info_transports_len(const fido_cbor_info_t *ci) +{ + return (ci->transports.len); +} + +const unsigned char * +fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *ci) +{ + return (ci->aaguid); +} + +size_t +fido_cbor_info_aaguid_len(const fido_cbor_info_t *ci) +{ + return (sizeof(ci->aaguid)); +} + +char ** +fido_cbor_info_options_name_ptr(const fido_cbor_info_t *ci) +{ + return (ci->options.name); +} + +const bool * +fido_cbor_info_options_value_ptr(const fido_cbor_info_t *ci) +{ + return (ci->options.value); +} + +size_t +fido_cbor_info_options_len(const fido_cbor_info_t *ci) +{ + return (ci->options.len); +} + +uint64_t +fido_cbor_info_maxcredbloblen(const fido_cbor_info_t *ci) +{ + return (ci->maxcredbloblen); +} + +uint64_t +fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *ci) +{ + return (ci->maxmsgsiz); +} + +uint64_t +fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *ci) +{ + return (ci->maxcredcntlst); +} + +uint64_t +fido_cbor_info_maxcredidlen(const fido_cbor_info_t *ci) +{ + return (ci->maxcredidlen); +} + +uint64_t +fido_cbor_info_maxlargeblob(const fido_cbor_info_t *ci) +{ + return (ci->maxlargeblob); +} + +uint64_t +fido_cbor_info_fwversion(const fido_cbor_info_t *ci) +{ + return (ci->fwversion); +} + +uint64_t +fido_cbor_info_minpinlen(const fido_cbor_info_t *ci) +{ + return (ci->minpinlen); +} + +uint64_t +fido_cbor_info_maxrpid_minpinlen(const fido_cbor_info_t *ci) +{ + return (ci->maxrpid_minlen); +} + +uint64_t +fido_cbor_info_uv_attempts(const fido_cbor_info_t *ci) +{ + return (ci->uv_attempts); +} + +uint64_t +fido_cbor_info_uv_modality(const fido_cbor_info_t *ci) +{ + return (ci->uv_modality); +} + +int64_t +fido_cbor_info_rk_remaining(const fido_cbor_info_t *ci) +{ + return (ci->rk_remaining); +} + +const uint8_t * +fido_cbor_info_protocols_ptr(const fido_cbor_info_t *ci) +{ + return (ci->protocols.ptr); +} + +size_t +fido_cbor_info_protocols_len(const fido_cbor_info_t *ci) +{ + return (ci->protocols.len); +} + +size_t +fido_cbor_info_algorithm_count(const fido_cbor_info_t *ci) +{ + return (ci->algorithms.len); +} + +const char * +fido_cbor_info_algorithm_type(const fido_cbor_info_t *ci, size_t idx) +{ + if (idx >= ci->algorithms.len) + return (NULL); + + return (ci->algorithms.ptr[idx].type); +} + +int +fido_cbor_info_algorithm_cose(const fido_cbor_info_t *ci, size_t idx) +{ + if (idx >= ci->algorithms.len) + return (0); + + return (ci->algorithms.ptr[idx].cose); +} + +bool +fido_cbor_info_new_pin_required(const fido_cbor_info_t *ci) +{ + return (ci->new_pin_reqd); +} + +char ** +fido_cbor_info_certs_name_ptr(const fido_cbor_info_t *ci) +{ + return (ci->certs.name); +} + +const uint64_t * +fido_cbor_info_certs_value_ptr(const fido_cbor_info_t *ci) +{ + return (ci->certs.value); +} + +size_t +fido_cbor_info_certs_len(const fido_cbor_info_t *ci) +{ + return (ci->certs.len); +} diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..a9715b5 --- /dev/null +++ b/src/io.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" +#include "packed.h" + +PACKED_TYPE(frame_t, +struct frame { + uint32_t cid; /* channel id */ + union { + uint8_t type; + struct { + uint8_t cmd; + uint8_t bcnth; + uint8_t bcntl; + uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_INIT_HEADER_LEN]; + } init; + struct { + uint8_t seq; + uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_CONT_HEADER_LEN]; + } cont; + } body; +}) + +#ifndef MIN +#define MIN(x, y) ((x) > (y) ? (y) : (x)) +#endif + +static int +tx_pkt(fido_dev_t *d, const void *pkt, size_t len, int *ms) +{ + struct timespec ts; + int n; + + if (fido_time_now(&ts) != 0) + return (-1); + + n = d->io.write(d->io_handle, pkt, len); + + if (fido_time_delta(&ts, ms) != 0) + return (-1); + + return (n); +} + +static int +tx_empty(fido_dev_t *d, uint8_t cmd, int *ms) +{ + struct frame *fp; + unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; + int n; + + memset(&pkt, 0, sizeof(pkt)); + fp = (struct frame *)(pkt + 1); + fp->cid = d->cid; + fp->body.init.cmd = CTAP_FRAME_INIT | cmd; + + if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 || + (size_t)n != len) + return (-1); + + return (0); +} + +static size_t +tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms) +{ + struct frame *fp; + unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; + int n; + + if (d->tx_len - CTAP_INIT_HEADER_LEN > sizeof(fp->body.init.data)) + return (0); + + memset(&pkt, 0, sizeof(pkt)); + fp = (struct frame *)(pkt + 1); + fp->cid = d->cid; + fp->body.init.cmd = CTAP_FRAME_INIT | cmd; + fp->body.init.bcnth = (count >> 8) & 0xff; + fp->body.init.bcntl = count & 0xff; + count = MIN(count, d->tx_len - CTAP_INIT_HEADER_LEN); + memcpy(&fp->body.init.data, buf, count); + + if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 || + (size_t)n != len) + return (0); + + return (count); +} + +static size_t +tx_frame(fido_dev_t *d, uint8_t seq, const void *buf, size_t count, int *ms) +{ + struct frame *fp; + unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; + int n; + + if (d->tx_len - CTAP_CONT_HEADER_LEN > sizeof(fp->body.cont.data)) + return (0); + + memset(&pkt, 0, sizeof(pkt)); + fp = (struct frame *)(pkt + 1); + fp->cid = d->cid; + fp->body.cont.seq = seq; + count = MIN(count, d->tx_len - CTAP_CONT_HEADER_LEN); + memcpy(&fp->body.cont.data, buf, count); + + if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 || + (size_t)n != len) + return (0); + + return (count); +} + +static int +tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count, int *ms) +{ + size_t n, sent; + + if ((sent = tx_preamble(d, cmd, buf, count, ms)) == 0) { + fido_log_debug("%s: tx_preamble", __func__); + return (-1); + } + + for (uint8_t seq = 0; sent < count; sent += n) { + if (seq & 0x80) { + fido_log_debug("%s: seq & 0x80", __func__); + return (-1); + } + if ((n = tx_frame(d, seq++, buf + sent, count - sent, + ms)) == 0) { + fido_log_debug("%s: tx_frame", __func__); + return (-1); + } + } + + return (0); +} + +static int +transport_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms) +{ + struct timespec ts; + int n; + + if (fido_time_now(&ts) != 0) + return (-1); + + n = d->transport.tx(d, cmd, buf, count); + + if (fido_time_delta(&ts, ms) != 0) + return (-1); + + return (n); +} + +int +fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms) +{ + fido_log_debug("%s: dev=%p, cmd=0x%02x", __func__, (void *)d, cmd); + fido_log_xxd(buf, count, "%s", __func__); + + if (d->transport.tx != NULL) + return (transport_tx(d, cmd, buf, count, ms)); + if (d->io_handle == NULL || d->io.write == NULL || count > UINT16_MAX) { + fido_log_debug("%s: invalid argument", __func__); + return (-1); + } + + return (count == 0 ? tx_empty(d, cmd, ms) : tx(d, cmd, buf, count, ms)); +} + +static int +rx_frame(fido_dev_t *d, struct frame *fp, int *ms) +{ + struct timespec ts; + int n; + + memset(fp, 0, sizeof(*fp)); + + if (fido_time_now(&ts) != 0) + return (-1); + + if (d->rx_len > sizeof(*fp) || (n = d->io.read(d->io_handle, + (unsigned char *)fp, d->rx_len, *ms)) < 0 || (size_t)n != d->rx_len) + return (-1); + + return (fido_time_delta(&ts, ms)); +} + +static int +rx_preamble(fido_dev_t *d, uint8_t cmd, struct frame *fp, int *ms) +{ + do { + if (rx_frame(d, fp, ms) < 0) + return (-1); +#ifdef FIDO_FUZZ + fp->cid = d->cid; +#endif + } while (fp->cid != d->cid || (fp->cid == d->cid && + fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE))); + + if (d->rx_len > sizeof(*fp)) + return (-1); + + fido_log_xxd(fp, d->rx_len, "%s", __func__); +#ifdef FIDO_FUZZ + fp->body.init.cmd = (CTAP_FRAME_INIT | cmd); +#endif + + if (fp->cid != d->cid || fp->body.init.cmd != (CTAP_FRAME_INIT | cmd)) { + fido_log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)", + __func__, fp->cid, d->cid, fp->body.init.cmd, cmd); + return (-1); + } + + return (0); +} + +static int +rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int *ms) +{ + struct frame f; + size_t r, payload_len, init_data_len, cont_data_len; + + if (d->rx_len <= CTAP_INIT_HEADER_LEN || + d->rx_len <= CTAP_CONT_HEADER_LEN) + return (-1); + + init_data_len = d->rx_len - CTAP_INIT_HEADER_LEN; + cont_data_len = d->rx_len - CTAP_CONT_HEADER_LEN; + + if (init_data_len > sizeof(f.body.init.data) || + cont_data_len > sizeof(f.body.cont.data)) + return (-1); + + if (rx_preamble(d, cmd, &f, ms) < 0) { + fido_log_debug("%s: rx_preamble", __func__); + return (-1); + } + + payload_len = (size_t)((f.body.init.bcnth << 8) | f.body.init.bcntl); + fido_log_debug("%s: payload_len=%zu", __func__, payload_len); + + if (count < payload_len) { + fido_log_debug("%s: count < payload_len", __func__); + return (-1); + } + + if (payload_len < init_data_len) { + memcpy(buf, f.body.init.data, payload_len); + return ((int)payload_len); + } + + memcpy(buf, f.body.init.data, init_data_len); + r = init_data_len; + + for (int seq = 0; r < payload_len; seq++) { + if (rx_frame(d, &f, ms) < 0) { + fido_log_debug("%s: rx_frame", __func__); + return (-1); + } + + fido_log_xxd(&f, d->rx_len, "%s", __func__); +#ifdef FIDO_FUZZ + f.cid = d->cid; + f.body.cont.seq = (uint8_t)seq; +#endif + + if (f.cid != d->cid || f.body.cont.seq != seq) { + fido_log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)", + __func__, f.cid, d->cid, f.body.cont.seq, seq); + return (-1); + } + + if (payload_len - r > cont_data_len) { + memcpy(buf + r, f.body.cont.data, cont_data_len); + r += cont_data_len; + } else { + memcpy(buf + r, f.body.cont.data, payload_len - r); + r += payload_len - r; /* break */ + } + } + + return ((int)r); +} + +static int +transport_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms) +{ + struct timespec ts; + int n; + + if (fido_time_now(&ts) != 0) + return (-1); + + n = d->transport.rx(d, cmd, buf, count, *ms); + + if (fido_time_delta(&ts, ms) != 0) + return (-1); + + return (n); +} + +int +fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms) +{ + int n; + + fido_log_debug("%s: dev=%p, cmd=0x%02x, ms=%d", __func__, (void *)d, + cmd, *ms); + + if (d->transport.rx != NULL) + return (transport_rx(d, cmd, buf, count, ms)); + if (d->io_handle == NULL || d->io.read == NULL || count > UINT16_MAX) { + fido_log_debug("%s: invalid argument", __func__); + return (-1); + } + if ((n = rx(d, cmd, buf, count, ms)) >= 0) + fido_log_xxd(buf, (size_t)n, "%s", __func__); + + return (n); +} + +int +fido_rx_cbor_status(fido_dev_t *d, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(d, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0 || + (size_t)msglen < 1) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = msg[0]; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} diff --git a/src/iso7816.c b/src/iso7816.c new file mode 100644 index 0000000..5bba106 --- /dev/null +++ b/src/iso7816.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +iso7816_apdu_t * +iso7816_new(uint8_t cla, uint8_t ins, uint8_t p1, uint16_t payload_len) +{ + iso7816_apdu_t *apdu; + size_t alloc_len; + + alloc_len = sizeof(iso7816_apdu_t) + payload_len + 2; /* le1 le2 */ + if ((apdu = calloc(1, alloc_len)) == NULL) + return NULL; + apdu->alloc_len = alloc_len; + apdu->payload_len = payload_len; + apdu->payload_ptr = apdu->payload; + apdu->header.cla = cla; + apdu->header.ins = ins; + apdu->header.p1 = p1; + apdu->header.lc2 = (uint8_t)((payload_len >> 8) & 0xff); + apdu->header.lc3 = (uint8_t)(payload_len & 0xff); + + return apdu; +} + +void +iso7816_free(iso7816_apdu_t **apdu_p) +{ + iso7816_apdu_t *apdu; + + if (apdu_p == NULL || (apdu = *apdu_p) == NULL) + return; + freezero(apdu, apdu->alloc_len); + *apdu_p = NULL; +} + +int +iso7816_add(iso7816_apdu_t *apdu, const void *buf, size_t cnt) +{ + if (cnt > apdu->payload_len || cnt > UINT16_MAX) + return -1; + memcpy(apdu->payload_ptr, buf, cnt); + apdu->payload_ptr += cnt; + apdu->payload_len = (uint16_t)(apdu->payload_len - cnt); + + return 0; +} + +const unsigned char * +iso7816_ptr(const iso7816_apdu_t *apdu) +{ + return (const unsigned char *)&apdu->header; +} + +size_t +iso7816_len(const iso7816_apdu_t *apdu) +{ + return apdu->alloc_len - offsetof(iso7816_apdu_t, header) - + (sizeof(iso7816_apdu_t) - offsetof(iso7816_apdu_t, payload)); +} diff --git a/src/iso7816.h b/src/iso7816.h new file mode 100644 index 0000000..7545719 --- /dev/null +++ b/src/iso7816.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _ISO7816_H +#define _ISO7816_H + +#include <stdint.h> +#include <stdlib.h> + +#include "packed.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +PACKED_TYPE(iso7816_header_t, +struct iso7816_header { + uint8_t cla; + uint8_t ins; + uint8_t p1; + uint8_t p2; + uint8_t lc1; + uint8_t lc2; + uint8_t lc3; +}) + +typedef struct iso7816_apdu { + size_t alloc_len; + uint16_t payload_len; + uint8_t *payload_ptr; + iso7816_header_t header; + uint8_t payload[]; +} iso7816_apdu_t; + +const unsigned char *iso7816_ptr(const iso7816_apdu_t *); +int iso7816_add(iso7816_apdu_t *, const void *, size_t); +iso7816_apdu_t *iso7816_new(uint8_t, uint8_t, uint8_t, uint16_t); +size_t iso7816_len(const iso7816_apdu_t *); +void iso7816_free(iso7816_apdu_t **); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_ISO7816_H */ diff --git a/src/largeblob.c b/src/largeblob.c new file mode 100644 index 0000000..c1f2e62 --- /dev/null +++ b/src/largeblob.c @@ -0,0 +1,902 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> + +#include "fido.h" +#include "fido/es256.h" + +#define LARGEBLOB_DIGEST_LENGTH 16 +#define LARGEBLOB_NONCE_LENGTH 12 +#define LARGEBLOB_TAG_LENGTH 16 + +typedef struct largeblob { + size_t origsiz; + fido_blob_t ciphertext; + fido_blob_t nonce; +} largeblob_t; + +static largeblob_t * +largeblob_new(void) +{ + return calloc(1, sizeof(largeblob_t)); +} + +static void +largeblob_reset(largeblob_t *blob) +{ + fido_blob_reset(&blob->ciphertext); + fido_blob_reset(&blob->nonce); + blob->origsiz = 0; +} + +static void +largeblob_free(largeblob_t **blob_ptr) +{ + largeblob_t *blob; + + if (blob_ptr == NULL || (blob = *blob_ptr) == NULL) + return; + largeblob_reset(blob); + free(blob); + *blob_ptr = NULL; +} + +static int +largeblob_aad(fido_blob_t *aad, uint64_t size) +{ + uint8_t buf[4 + sizeof(uint64_t)]; + + buf[0] = 0x62; /* b */ + buf[1] = 0x6c; /* l */ + buf[2] = 0x6f; /* o */ + buf[3] = 0x62; /* b */ + size = htole64(size); + memcpy(&buf[4], &size, sizeof(uint64_t)); + + return fido_blob_set(aad, buf, sizeof(buf)); +} + +static fido_blob_t * +largeblob_decrypt(const largeblob_t *blob, const fido_blob_t *key) +{ + fido_blob_t *plaintext = NULL, *aad = NULL; + int ok = -1; + + if ((plaintext = fido_blob_new()) == NULL || + (aad = fido_blob_new()) == NULL) { + fido_log_debug("%s: fido_blob_new", __func__); + goto fail; + } + if (largeblob_aad(aad, blob->origsiz) < 0) { + fido_log_debug("%s: largeblob_aad", __func__); + goto fail; + } + if (aes256_gcm_dec(key, &blob->nonce, aad, &blob->ciphertext, + plaintext) < 0) { + fido_log_debug("%s: aes256_gcm_dec", __func__); + goto fail; + } + + ok = 0; +fail: + fido_blob_free(&aad); + + if (ok < 0) + fido_blob_free(&plaintext); + + return plaintext; +} + +static int +largeblob_get_nonce(largeblob_t *blob) +{ + uint8_t buf[LARGEBLOB_NONCE_LENGTH]; + int ok = -1; + + if (fido_get_random(buf, sizeof(buf)) < 0) { + fido_log_debug("%s: fido_get_random", __func__); + goto fail; + } + if (fido_blob_set(&blob->nonce, buf, sizeof(buf)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + goto fail; + } + + ok = 0; +fail: + explicit_bzero(buf, sizeof(buf)); + + return ok; +} + +static int +largeblob_seal(largeblob_t *blob, const fido_blob_t *body, + const fido_blob_t *key) +{ + fido_blob_t *plaintext = NULL, *aad = NULL; + int ok = -1; + + if ((plaintext = fido_blob_new()) == NULL || + (aad = fido_blob_new()) == NULL) { + fido_log_debug("%s: fido_blob_new", __func__); + goto fail; + } + if (fido_compress(plaintext, body) != FIDO_OK) { + fido_log_debug("%s: fido_compress", __func__); + goto fail; + } + if (largeblob_aad(aad, body->len) < 0) { + fido_log_debug("%s: largeblob_aad", __func__); + goto fail; + } + if (largeblob_get_nonce(blob) < 0) { + fido_log_debug("%s: largeblob_get_nonce", __func__); + goto fail; + } + if (aes256_gcm_enc(key, &blob->nonce, aad, plaintext, + &blob->ciphertext) < 0) { + fido_log_debug("%s: aes256_gcm_enc", __func__); + goto fail; + } + blob->origsiz = body->len; + + ok = 0; +fail: + fido_blob_free(&plaintext); + fido_blob_free(&aad); + + return ok; +} + +static int +largeblob_get_tx(fido_dev_t *dev, size_t offset, size_t count, int *ms) +{ + fido_blob_t f; + cbor_item_t *argv[3]; + int r; + + memset(argv, 0, sizeof(argv)); + memset(&f, 0, sizeof(f)); + + if ((argv[0] = cbor_build_uint(count)) == NULL || + (argv[2] = cbor_build_uint(offset)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + + return r; +} + +static int +parse_largeblob_reply(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 1) { + fido_log_debug("%s: cbor type", __func__); + return 0; /* ignore */ + } + + return fido_blob_decode(val, arg); +} + +static int +largeblob_get_rx(fido_dev_t *dev, fido_blob_t **chunk, int *ms) +{ + unsigned char *msg; + int msglen, r; + + *chunk = NULL; + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + if ((*chunk = fido_blob_new()) == NULL) { + fido_log_debug("%s: fido_blob_new", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + if ((r = cbor_parse_reply(msg, (size_t)msglen, *chunk, + parse_largeblob_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_largeblob_reply", __func__); + goto out; + } + + r = FIDO_OK; +out: + if (r != FIDO_OK) + fido_blob_free(chunk); + + freezero(msg, FIDO_MAXMSG); + + return r; +} + +static cbor_item_t * +largeblob_array_load(const uint8_t *ptr, size_t len) +{ + struct cbor_load_result cbor; + cbor_item_t *item; + + if (len < LARGEBLOB_DIGEST_LENGTH) { + fido_log_debug("%s: len", __func__); + return NULL; + } + len -= LARGEBLOB_DIGEST_LENGTH; + if ((item = cbor_load(ptr, len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + return NULL; + } + if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) { + fido_log_debug("%s: cbor type", __func__); + cbor_decref(&item); + return NULL; + } + + return item; +} + +static size_t +get_chunklen(fido_dev_t *dev) +{ + uint64_t maxchunklen; + + if ((maxchunklen = fido_dev_maxmsgsize(dev)) > SIZE_MAX) + maxchunklen = SIZE_MAX; + if (maxchunklen > FIDO_MAXMSG) + maxchunklen = FIDO_MAXMSG; + maxchunklen = maxchunklen > 64 ? maxchunklen - 64 : 0; + + return (size_t)maxchunklen; +} + +static int +largeblob_do_decode(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + largeblob_t *blob = arg; + uint64_t origsiz; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return 0; /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* ciphertext */ + if (fido_blob_decode(val, &blob->ciphertext) < 0 || + blob->ciphertext.len < LARGEBLOB_TAG_LENGTH) + return -1; + return 0; + case 2: /* nonce */ + if (fido_blob_decode(val, &blob->nonce) < 0 || + blob->nonce.len != LARGEBLOB_NONCE_LENGTH) + return -1; + return 0; + case 3: /* origSize */ + if (!cbor_isa_uint(val) || + (origsiz = cbor_get_int(val)) > SIZE_MAX) + return -1; + blob->origsiz = (size_t)origsiz; + return 0; + default: /* ignore */ + fido_log_debug("%s: cbor type", __func__); + return 0; + } +} + +static int +largeblob_decode(largeblob_t *blob, const cbor_item_t *item) +{ + if (!cbor_isa_map(item) || !cbor_map_is_definite(item)) { + fido_log_debug("%s: cbor type", __func__); + return -1; + } + if (cbor_map_iter(item, blob, largeblob_do_decode) < 0) { + fido_log_debug("%s: cbor_map_iter", __func__); + return -1; + } + if (fido_blob_is_empty(&blob->ciphertext) || + fido_blob_is_empty(&blob->nonce) || blob->origsiz == 0) { + fido_log_debug("%s: incomplete blob", __func__); + return -1; + } + + return 0; +} + +static cbor_item_t * +largeblob_encode(const fido_blob_t *body, const fido_blob_t *key) +{ + largeblob_t *blob; + cbor_item_t *argv[3], *item = NULL; + + memset(argv, 0, sizeof(argv)); + if ((blob = largeblob_new()) == NULL || + largeblob_seal(blob, body, key) < 0) { + fido_log_debug("%s: largeblob_seal", __func__); + goto fail; + } + if ((argv[0] = fido_blob_encode(&blob->ciphertext)) == NULL || + (argv[1] = fido_blob_encode(&blob->nonce)) == NULL || + (argv[2] = cbor_build_uint(blob->origsiz)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + item = cbor_flatten_vector(argv, nitems(argv)); +fail: + cbor_vector_free(argv, nitems(argv)); + largeblob_free(&blob); + + return item; +} + +static int +largeblob_array_lookup(fido_blob_t *out, size_t *idx, const cbor_item_t *item, + const fido_blob_t *key) +{ + cbor_item_t **v; + fido_blob_t *plaintext = NULL; + largeblob_t blob; + int r; + + memset(&blob, 0, sizeof(blob)); + if (idx != NULL) + *idx = 0; + if ((v = cbor_array_handle(item)) == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + for (size_t i = 0; i < cbor_array_size(item); i++) { + if (largeblob_decode(&blob, v[i]) < 0 || + (plaintext = largeblob_decrypt(&blob, key)) == NULL) { + fido_log_debug("%s: largeblob_decode", __func__); + largeblob_reset(&blob); + continue; + } + if (idx != NULL) + *idx = i; + break; + } + if (plaintext == NULL) { + fido_log_debug("%s: not found", __func__); + return FIDO_ERR_NOTFOUND; + } + if (out != NULL) + r = fido_uncompress(out, plaintext, blob.origsiz); + else + r = FIDO_OK; + + fido_blob_free(&plaintext); + largeblob_reset(&blob); + + return r; +} + +static int +largeblob_array_digest(u_char out[LARGEBLOB_DIGEST_LENGTH], const u_char *data, + size_t len) +{ + u_char dgst[SHA256_DIGEST_LENGTH]; + + if (data == NULL || len == 0) + return -1; + if (SHA256(data, len, dgst) != dgst) + return -1; + memcpy(out, dgst, LARGEBLOB_DIGEST_LENGTH); + + return 0; +} + +static int +largeblob_array_check(const fido_blob_t *array) +{ + u_char expected_hash[LARGEBLOB_DIGEST_LENGTH]; + size_t body_len; + + fido_log_xxd(array->ptr, array->len, __func__); + if (array->len < sizeof(expected_hash)) { + fido_log_debug("%s: len %zu", __func__, array->len); + return -1; + } + body_len = array->len - sizeof(expected_hash); + if (largeblob_array_digest(expected_hash, array->ptr, body_len) < 0) { + fido_log_debug("%s: largeblob_array_digest", __func__); + return -1; + } + + return timingsafe_bcmp(expected_hash, array->ptr + body_len, + sizeof(expected_hash)); +} + +static int +largeblob_get_array(fido_dev_t *dev, cbor_item_t **item, int *ms) +{ + fido_blob_t *array, *chunk = NULL; + size_t n; + int r; + + *item = NULL; + if ((n = get_chunklen(dev)) == 0) + return FIDO_ERR_INVALID_ARGUMENT; + if ((array = fido_blob_new()) == NULL) + return FIDO_ERR_INTERNAL; + do { + fido_blob_free(&chunk); + if ((r = largeblob_get_tx(dev, array->len, n, ms)) != FIDO_OK || + (r = largeblob_get_rx(dev, &chunk, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_wait %zu/%zu", + __func__, array->len, n); + goto fail; + } + if (fido_blob_append(array, chunk->ptr, chunk->len) < 0) { + fido_log_debug("%s: fido_blob_append", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + } while (chunk->len == n); + + if (largeblob_array_check(array) != 0) + *item = cbor_new_definite_array(0); /* per spec */ + else + *item = largeblob_array_load(array->ptr, array->len); + if (*item == NULL) + r = FIDO_ERR_INTERNAL; + else + r = FIDO_OK; +fail: + fido_blob_free(&array); + fido_blob_free(&chunk); + + return r; +} + +static int +prepare_hmac(size_t offset, const u_char *data, size_t len, fido_blob_t *hmac) +{ + uint8_t buf[32 + 2 + sizeof(uint32_t) + SHA256_DIGEST_LENGTH]; + uint32_t u32_offset; + + if (data == NULL || len == 0) { + fido_log_debug("%s: invalid data=%p, len=%zu", __func__, + (const void *)data, len); + return -1; + } + if (offset > UINT32_MAX) { + fido_log_debug("%s: invalid offset=%zu", __func__, offset); + return -1; + } + + memset(buf, 0xff, 32); + buf[32] = CTAP_CBOR_LARGEBLOB; + buf[33] = 0x00; + u32_offset = htole32((uint32_t)offset); + memcpy(&buf[34], &u32_offset, sizeof(uint32_t)); + if (SHA256(data, len, &buf[38]) != &buf[38]) { + fido_log_debug("%s: SHA256", __func__); + return -1; + } + + return fido_blob_set(hmac, buf, sizeof(buf)); +} + +static int +largeblob_set_tx(fido_dev_t *dev, const fido_blob_t *token, const u_char *chunk, + size_t chunk_len, size_t offset, size_t totalsiz, int *ms) +{ + fido_blob_t *hmac = NULL, f; + cbor_item_t *argv[6]; + int r; + + memset(argv, 0, sizeof(argv)); + memset(&f, 0, sizeof(f)); + + if ((argv[1] = cbor_build_bytestring(chunk, chunk_len)) == NULL || + (argv[2] = cbor_build_uint(offset)) == NULL || + (offset == 0 && (argv[3] = cbor_build_uint(totalsiz)) == NULL)) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (token != NULL) { + if ((hmac = fido_blob_new()) == NULL || + prepare_hmac(offset, chunk, chunk_len, hmac) < 0 || + (argv[4] = cbor_encode_pin_auth(dev, token, hmac)) == NULL || + (argv[5] = cbor_encode_pin_opt(dev)) == NULL) { + fido_log_debug("%s: cbor_encode_pin_auth", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + } + if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + fido_blob_free(&hmac); + free(f.ptr); + + return r; +} + +static int +largeblob_get_uv_token(fido_dev_t *dev, const char *pin, fido_blob_t **token, + int *ms) +{ + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL; + int r; + + if ((*token = fido_blob_new()) == NULL) + return FIDO_ERR_INTERNAL; + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_LARGEBLOB, pin, ecdh, pk, + NULL, *token, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_get_uv_token", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (r != FIDO_OK) + fido_blob_free(token); + + fido_blob_free(&ecdh); + es256_pk_free(&pk); + + return r; +} + +static int +largeblob_set_array(fido_dev_t *dev, const cbor_item_t *item, const char *pin, + int *ms) +{ + unsigned char dgst[SHA256_DIGEST_LENGTH]; + fido_blob_t cbor, *token = NULL; + size_t chunklen, maxchunklen, totalsize; + int r; + + memset(&cbor, 0, sizeof(cbor)); + + if ((maxchunklen = get_chunklen(dev)) == 0) { + fido_log_debug("%s: maxchunklen=%zu", __func__, maxchunklen); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) { + fido_log_debug("%s: cbor type", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if ((fido_blob_serialise(&cbor, item)) < 0) { + fido_log_debug("%s: fido_blob_serialise", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (cbor.len > SIZE_MAX - sizeof(dgst)) { + fido_log_debug("%s: cbor.len=%zu", __func__, cbor.len); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if (SHA256(cbor.ptr, cbor.len, dgst) != dgst) { + fido_log_debug("%s: SHA256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + totalsize = cbor.len + sizeof(dgst) - 16; /* the first 16 bytes only */ + if (pin != NULL || fido_dev_supports_permissions(dev)) { + if ((r = largeblob_get_uv_token(dev, pin, &token, + ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_uv_token", __func__); + goto fail; + } + } + for (size_t offset = 0; offset < cbor.len; offset += chunklen) { + if ((chunklen = cbor.len - offset) > maxchunklen) + chunklen = maxchunklen; + if ((r = largeblob_set_tx(dev, token, cbor.ptr + offset, + chunklen, offset, totalsize, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: body", __func__); + goto fail; + } + } + if ((r = largeblob_set_tx(dev, token, dgst, sizeof(dgst) - 16, cbor.len, + totalsize, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: dgst", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&token); + fido_blob_reset(&cbor); + + return r; +} + +static int +largeblob_add(fido_dev_t *dev, const fido_blob_t *key, cbor_item_t *item, + const char *pin, int *ms) +{ + cbor_item_t *array = NULL; + size_t idx; + int r; + + if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + goto fail; + } + + switch (r = largeblob_array_lookup(NULL, &idx, array, key)) { + case FIDO_OK: + if (!cbor_array_replace(array, idx, item)) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + break; + case FIDO_ERR_NOTFOUND: + if (cbor_array_append(&array, item) < 0) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + break; + default: + fido_log_debug("%s: largeblob_array_lookup", __func__); + goto fail; + } + + if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_set_array", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (array != NULL) + cbor_decref(&array); + + return r; +} + +static int +largeblob_drop(fido_dev_t *dev, const fido_blob_t *key, const char *pin, + int *ms) +{ + cbor_item_t *array = NULL; + size_t idx; + int r; + + if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + goto fail; + } + if ((r = largeblob_array_lookup(NULL, &idx, array, key)) != FIDO_OK) { + fido_log_debug("%s: largeblob_array_lookup", __func__); + goto fail; + } + if (cbor_array_drop(&array, idx) < 0) { + fido_log_debug("%s: cbor_array_drop", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_set_array", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (array != NULL) + cbor_decref(&array); + + return r; +} + +int +fido_dev_largeblob_get(fido_dev_t *dev, const unsigned char *key_ptr, + size_t key_len, unsigned char **blob_ptr, size_t *blob_len) +{ + cbor_item_t *item = NULL; + fido_blob_t key, body; + int ms = dev->timeout_ms; + int r; + + memset(&key, 0, sizeof(key)); + memset(&body, 0, sizeof(body)); + + if (key_len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (blob_ptr == NULL || blob_len == NULL) { + fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%p", __func__, + (const void *)blob_ptr, (const void *)blob_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + *blob_ptr = NULL; + *blob_len = 0; + if (fido_blob_set(&key, key_ptr, key_len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return FIDO_ERR_INTERNAL; + } + if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + goto fail; + } + if ((r = largeblob_array_lookup(&body, NULL, item, &key)) != FIDO_OK) + fido_log_debug("%s: largeblob_array_lookup", __func__); + else { + *blob_ptr = body.ptr; + *blob_len = body.len; + } +fail: + if (item != NULL) + cbor_decref(&item); + + fido_blob_reset(&key); + + return r; +} + +int +fido_dev_largeblob_set(fido_dev_t *dev, const unsigned char *key_ptr, + size_t key_len, const unsigned char *blob_ptr, size_t blob_len, + const char *pin) +{ + cbor_item_t *item = NULL; + fido_blob_t key, body; + int ms = dev->timeout_ms; + int r; + + memset(&key, 0, sizeof(key)); + memset(&body, 0, sizeof(body)); + + if (key_len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (blob_ptr == NULL || blob_len == 0) { + fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%zu", __func__, + (const void *)blob_ptr, blob_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (fido_blob_set(&key, key_ptr, key_len) < 0 || + fido_blob_set(&body, blob_ptr, blob_len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((item = largeblob_encode(&body, &key)) == NULL) { + fido_log_debug("%s: largeblob_encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = largeblob_add(dev, &key, item, pin, &ms)) != FIDO_OK) + fido_log_debug("%s: largeblob_add", __func__); +fail: + if (item != NULL) + cbor_decref(&item); + + fido_blob_reset(&key); + fido_blob_reset(&body); + + return r; +} + +int +fido_dev_largeblob_remove(fido_dev_t *dev, const unsigned char *key_ptr, + size_t key_len, const char *pin) +{ + fido_blob_t key; + int ms = dev->timeout_ms; + int r; + + memset(&key, 0, sizeof(key)); + + if (key_len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (fido_blob_set(&key, key_ptr, key_len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return FIDO_ERR_INTERNAL; + } + if ((r = largeblob_drop(dev, &key, pin, &ms)) != FIDO_OK) + fido_log_debug("%s: largeblob_drop", __func__); + + fido_blob_reset(&key); + + return r; +} + +int +fido_dev_largeblob_get_array(fido_dev_t *dev, unsigned char **cbor_ptr, + size_t *cbor_len) +{ + cbor_item_t *item = NULL; + fido_blob_t cbor; + int ms = dev->timeout_ms; + int r; + + memset(&cbor, 0, sizeof(cbor)); + + if (cbor_ptr == NULL || cbor_len == NULL) { + fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%p", __func__, + (const void *)cbor_ptr, (const void *)cbor_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + *cbor_ptr = NULL; + *cbor_len = 0; + if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + return r; + } + if (fido_blob_serialise(&cbor, item) < 0) { + fido_log_debug("%s: fido_blob_serialise", __func__); + r = FIDO_ERR_INTERNAL; + } else { + *cbor_ptr = cbor.ptr; + *cbor_len = cbor.len; + } + + cbor_decref(&item); + + return r; +} + +int +fido_dev_largeblob_set_array(fido_dev_t *dev, const unsigned char *cbor_ptr, + size_t cbor_len, const char *pin) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor_result; + int ms = dev->timeout_ms; + int r; + + if (cbor_ptr == NULL || cbor_len == 0) { + fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%zu", __func__, + (const void *)cbor_ptr, cbor_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((r = largeblob_set_array(dev, item, pin, &ms)) != FIDO_OK) + fido_log_debug("%s: largeblob_set_array", __func__); + + cbor_decref(&item); + + return r; +} diff --git a/src/libfido2.pc.in b/src/libfido2.pc.in new file mode 100644 index 0000000..03d0606 --- /dev/null +++ b/src/libfido2.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: A FIDO2 library +URL: https://github.com/yubico/libfido2 +Version: @FIDO_VERSION@ +Requires: libcrypto +Libs: -L${libdir} -lfido2 +Cflags: -I${includedir} diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..e54f8fc --- /dev/null +++ b/src/log.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef _GNU_SOURCE /* XSI strerror_r() */ + +#include <stdarg.h> +#include <stdio.h> + +#include "fido.h" + +#ifndef FIDO_NO_DIAGNOSTIC + +#define XXDLEN 32 +#define XXDROW 128 +#define LINELEN 256 + +#ifndef TLS +#define TLS +#endif + +static TLS int logging; +static TLS fido_log_handler_t *log_handler; + +static void +log_on_stderr(const char *str) +{ + fprintf(stderr, "%s", str); +} + +static void +do_log(const char *suffix, const char *fmt, va_list args) +{ + char line[LINELEN], body[LINELEN]; + + vsnprintf(body, sizeof(body), fmt, args); + + if (suffix != NULL) + snprintf(line, sizeof(line), "%.180s: %.70s\n", body, suffix); + else + snprintf(line, sizeof(line), "%.180s\n", body); + + log_handler(line); +} + +void +fido_log_init(void) +{ + logging = 1; + log_handler = log_on_stderr; +} + +void +fido_log_debug(const char *fmt, ...) +{ + va_list args; + + if (!logging || log_handler == NULL) + return; + + va_start(args, fmt); + do_log(NULL, fmt, args); + va_end(args); +} + +void +fido_log_xxd(const void *buf, size_t count, const char *fmt, ...) +{ + const uint8_t *ptr = buf; + char row[XXDROW], xxd[XXDLEN]; + va_list args; + + if (!logging || log_handler == NULL) + return; + + snprintf(row, sizeof(row), "buf=%p, len=%zu", buf, count); + va_start(args, fmt); + do_log(row, fmt, args); + va_end(args); + *row = '\0'; + + for (size_t i = 0; i < count; i++) { + *xxd = '\0'; + if (i % 16 == 0) + snprintf(xxd, sizeof(xxd), "%04zu: %02x", i, *ptr++); + else + snprintf(xxd, sizeof(xxd), " %02x", *ptr++); + strlcat(row, xxd, sizeof(row)); + if (i % 16 == 15 || i == count - 1) { + fido_log_debug("%s", row); + *row = '\0'; + } + } +} + +void +fido_log_error(int errnum, const char *fmt, ...) +{ + char errstr[LINELEN]; + va_list args; + + if (!logging || log_handler == NULL) + return; + if (strerror_r(errnum, errstr, sizeof(errstr)) != 0) + snprintf(errstr, sizeof(errstr), "error %d", errnum); + + va_start(args, fmt); + do_log(errstr, fmt, args); + va_end(args); +} + +void +fido_set_log_handler(fido_log_handler_t *handler) +{ + if (handler != NULL) + log_handler = handler; +} + +#endif /* !FIDO_NO_DIAGNOSTIC */ diff --git a/src/netlink.c b/src/netlink.c new file mode 100644 index 0000000..2a9216c --- /dev/null +++ b/src/netlink.c @@ -0,0 +1,785 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/socket.h> + +#include <linux/genetlink.h> +#include <linux/netlink.h> +#include <linux/nfc.h> + +#include <errno.h> +#include <limits.h> + +#include "fido.h" +#include "netlink.h" + +#ifdef FIDO_FUZZ +static ssize_t (*fuzz_read)(int, void *, size_t); +static ssize_t (*fuzz_write)(int, const void *, size_t); +# define READ fuzz_read +# define WRITE fuzz_write +#else +# define READ read +# define WRITE write +#endif + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#define NETLINK_POLL_MS 100 + +/* XXX avoid signed NLA_ALIGNTO */ +#undef NLA_HDRLEN +#define NLA_HDRLEN NLMSG_ALIGN(sizeof(struct nlattr)) + +typedef struct nlmsgbuf { + size_t siz; /* alloc size */ + size_t len; /* of payload */ + unsigned char *ptr; /* in payload */ + union { + struct nlmsghdr nlmsg; + char buf[NLMSG_HDRLEN]; /* align */ + } u; + unsigned char payload[]; +} nlmsgbuf_t; + +typedef struct genlmsgbuf { + union { + struct genlmsghdr genl; + char buf[GENL_HDRLEN]; /* align */ + } u; +} genlmsgbuf_t; + +typedef struct nlamsgbuf { + size_t siz; /* alloc size */ + size_t len; /* of payload */ + unsigned char *ptr; /* in payload */ + union { + struct nlattr nla; + char buf[NLA_HDRLEN]; /* align */ + } u; + unsigned char payload[]; +} nlamsgbuf_t; + +typedef struct nl_family { + uint16_t id; + uint32_t mcastgrp; +} nl_family_t; + +typedef struct nl_poll { + uint32_t dev; + unsigned int eventcnt; +} nl_poll_t; + +typedef struct nl_target { + int found; + uint32_t *value; +} nl_target_t; + +static const void * +nlmsg_ptr(const nlmsgbuf_t *m) +{ + return (&m->u.nlmsg); +} + +static size_t +nlmsg_len(const nlmsgbuf_t *m) +{ + return (m->u.nlmsg.nlmsg_len); +} + +static uint16_t +nlmsg_type(const nlmsgbuf_t *m) +{ + return (m->u.nlmsg.nlmsg_type); +} + +static nlmsgbuf_t * +nlmsg_new(uint16_t type, uint16_t flags, size_t len) +{ + nlmsgbuf_t *m; + size_t siz; + + if (len > SIZE_MAX - sizeof(*m) || + (siz = sizeof(*m) + len) > UINT16_MAX || + (m = calloc(1, siz)) == NULL) + return (NULL); + + m->siz = siz; + m->len = len; + m->ptr = m->payload; + m->u.nlmsg.nlmsg_type = type; + m->u.nlmsg.nlmsg_flags = NLM_F_REQUEST | flags; + m->u.nlmsg.nlmsg_len = NLMSG_HDRLEN; + + return (m); +} + +static nlamsgbuf_t * +nla_from_buf(const unsigned char **ptr, size_t *len) +{ + nlamsgbuf_t h, *a; + size_t nlalen, skip; + + if (*len < sizeof(h.u)) + return (NULL); + + memset(&h, 0, sizeof(h)); + memcpy(&h.u, *ptr, sizeof(h.u)); + + if ((nlalen = h.u.nla.nla_len) < sizeof(h.u) || nlalen > *len || + nlalen - sizeof(h.u) > UINT16_MAX || + nlalen > SIZE_MAX - sizeof(*a) || + (skip = NLMSG_ALIGN(nlalen)) > *len || + (a = calloc(1, sizeof(*a) + nlalen - sizeof(h.u))) == NULL) + return (NULL); + + memcpy(&a->u, *ptr, nlalen); + a->siz = sizeof(*a) + nlalen - sizeof(h.u); + a->ptr = a->payload; + a->len = nlalen - sizeof(h.u); + *ptr += skip; + *len -= skip; + + return (a); +} + +static nlamsgbuf_t * +nla_getattr(nlamsgbuf_t *a) +{ + return (nla_from_buf((void *)&a->ptr, &a->len)); +} + +static uint16_t +nla_type(const nlamsgbuf_t *a) +{ + return (a->u.nla.nla_type); +} + +static nlamsgbuf_t * +nlmsg_getattr(nlmsgbuf_t *m) +{ + return (nla_from_buf((void *)&m->ptr, &m->len)); +} + +static int +nla_read(nlamsgbuf_t *a, void *buf, size_t cnt) +{ + if (cnt > a->u.nla.nla_len || + fido_buf_read((void *)&a->ptr, &a->len, buf, cnt) < 0) + return (-1); + + a->u.nla.nla_len = (uint16_t)(a->u.nla.nla_len - cnt); + + return (0); +} + +static nlmsgbuf_t * +nlmsg_from_buf(const unsigned char **ptr, size_t *len) +{ + nlmsgbuf_t h, *m; + size_t msglen, skip; + + if (*len < sizeof(h.u)) + return (NULL); + + memset(&h, 0, sizeof(h)); + memcpy(&h.u, *ptr, sizeof(h.u)); + + if ((msglen = h.u.nlmsg.nlmsg_len) < sizeof(h.u) || msglen > *len || + msglen - sizeof(h.u) > UINT16_MAX || + (skip = NLMSG_ALIGN(msglen)) > *len || + (m = nlmsg_new(0, 0, msglen - sizeof(h.u))) == NULL) + return (NULL); + + memcpy(&m->u, *ptr, msglen); + *ptr += skip; + *len -= skip; + + return (m); +} + +static int +nlmsg_read(nlmsgbuf_t *m, void *buf, size_t cnt) +{ + if (cnt > m->u.nlmsg.nlmsg_len || + fido_buf_read((void *)&m->ptr, &m->len, buf, cnt) < 0) + return (-1); + + m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len - cnt); + + return (0); +} + +static int +nlmsg_write(nlmsgbuf_t *m, const void *buf, size_t cnt) +{ + if (cnt > UINT32_MAX - m->u.nlmsg.nlmsg_len || + fido_buf_write(&m->ptr, &m->len, buf, cnt) < 0) + return (-1); + + m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len + cnt); + + return (0); +} + +static int +nlmsg_set_genl(nlmsgbuf_t *m, uint8_t cmd) +{ + genlmsgbuf_t g; + + memset(&g, 0, sizeof(g)); + g.u.genl.cmd = cmd; + g.u.genl.version = NFC_GENL_VERSION; + + return (nlmsg_write(m, &g, sizeof(g))); +} + +static int +nlmsg_get_genl(nlmsgbuf_t *m, uint8_t cmd) +{ + genlmsgbuf_t g; + + memset(&g, 0, sizeof(g)); + + if (nlmsg_read(m, &g, sizeof(g)) < 0 || g.u.genl.cmd != cmd) + return (-1); + + return (0); +} + +static int +nlmsg_get_status(nlmsgbuf_t *m) +{ + int status; + + if (nlmsg_read(m, &status, sizeof(status)) < 0 || status == INT_MIN) + return (-1); + if (status < 0) + status = -status; + + return (status); +} + +static int +nlmsg_setattr(nlmsgbuf_t *m, uint16_t type, const void *ptr, size_t len) +{ + int r; + char *padding; + size_t skip; + nlamsgbuf_t a; + + if ((skip = NLMSG_ALIGN(len)) > UINT16_MAX - sizeof(a.u) || + skip < len || (padding = calloc(1, skip - len)) == NULL) + return (-1); + + memset(&a, 0, sizeof(a)); + a.u.nla.nla_type = type; + a.u.nla.nla_len = (uint16_t)(len + sizeof(a.u)); + r = nlmsg_write(m, &a.u, sizeof(a.u)) < 0 || + nlmsg_write(m, ptr, len) < 0 || + nlmsg_write(m, padding, skip - len) < 0 ? -1 : 0; + + free(padding); + + return (r); +} + +static int +nlmsg_set_u16(nlmsgbuf_t *m, uint16_t type, uint16_t val) +{ + return (nlmsg_setattr(m, type, &val, sizeof(val))); +} + +static int +nlmsg_set_u32(nlmsgbuf_t *m, uint16_t type, uint32_t val) +{ + return (nlmsg_setattr(m, type, &val, sizeof(val))); +} + +static int +nlmsg_set_str(nlmsgbuf_t *m, uint16_t type, const char *val) +{ + return (nlmsg_setattr(m, type, val, strlen(val) + 1)); +} + +static int +nla_get_u16(nlamsgbuf_t *a, uint16_t *v) +{ + return (nla_read(a, v, sizeof(*v))); +} + +static int +nla_get_u32(nlamsgbuf_t *a, uint32_t *v) +{ + return (nla_read(a, v, sizeof(*v))); +} + +static char * +nla_get_str(nlamsgbuf_t *a) +{ + size_t n; + char *s = NULL; + + if ((n = a->len) < 1 || a->ptr[n - 1] != '\0' || + (s = calloc(1, n)) == NULL || nla_read(a, s, n) < 0) { + free(s); + return (NULL); + } + s[n - 1] = '\0'; + + return (s); +} + +static int +nlmsg_tx(int fd, const nlmsgbuf_t *m) +{ + ssize_t r; + + if ((r = WRITE(fd, nlmsg_ptr(m), nlmsg_len(m))) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + if (r < 0 || (size_t)r != nlmsg_len(m)) { + fido_log_debug("%s: %zd != %zu", __func__, r, nlmsg_len(m)); + return (-1); + } + fido_log_xxd(nlmsg_ptr(m), nlmsg_len(m), "%s", __func__); + + return (0); +} + +static ssize_t +nlmsg_rx(int fd, unsigned char *ptr, size_t len, int ms) +{ + ssize_t r; + + if (len > SSIZE_MAX) { + fido_log_debug("%s: len", __func__); + return (-1); + } + if (fido_hid_unix_wait(fd, ms, NULL) < 0) { + fido_log_debug("%s: fido_hid_unix_wait", __func__); + return (-1); + } + if ((r = READ(fd, ptr, len)) == -1) { + fido_log_error(errno, "%s: read %zd", __func__, r); + return (-1); + } + fido_log_xxd(ptr, (size_t)r, "%s", __func__); + + return (r); +} + +static int +nlmsg_iter(nlmsgbuf_t *m, void *arg, int (*parser)(nlamsgbuf_t *, void *)) +{ + nlamsgbuf_t *a; + int r; + + while ((a = nlmsg_getattr(m)) != NULL) { + r = parser(a, arg); + free(a); + if (r < 0) { + fido_log_debug("%s: parser", __func__); + return (-1); + } + } + + return (0); +} + +static int +nla_iter(nlamsgbuf_t *g, void *arg, int (*parser)(nlamsgbuf_t *, void *)) +{ + nlamsgbuf_t *a; + int r; + + while ((a = nla_getattr(g)) != NULL) { + r = parser(a, arg); + free(a); + if (r < 0) { + fido_log_debug("%s: parser", __func__); + return (-1); + } + } + + return (0); +} + +static int +nl_parse_reply(const uint8_t *blob, size_t blob_len, uint16_t msg_type, + uint8_t genl_cmd, void *arg, int (*parser)(nlamsgbuf_t *, void *)) +{ + nlmsgbuf_t *m; + int r; + + while (blob_len) { + if ((m = nlmsg_from_buf(&blob, &blob_len)) == NULL) { + fido_log_debug("%s: nlmsg", __func__); + return (-1); + } + if (nlmsg_type(m) == NLMSG_ERROR) { + r = nlmsg_get_status(m); + free(m); + return (r); + } + if (nlmsg_type(m) != msg_type || + nlmsg_get_genl(m, genl_cmd) < 0) { + fido_log_debug("%s: skipping", __func__); + free(m); + continue; + } + if (parser != NULL && nlmsg_iter(m, arg, parser) < 0) { + fido_log_debug("%s: nlmsg_iter", __func__); + free(m); + return (-1); + } + free(m); + } + + return (0); +} + +static int +parse_mcastgrp(nlamsgbuf_t *a, void *arg) +{ + nl_family_t *family = arg; + char *name; + + switch (nla_type(a)) { + case CTRL_ATTR_MCAST_GRP_NAME: + if ((name = nla_get_str(a)) == NULL || + strcmp(name, NFC_GENL_MCAST_EVENT_NAME) != 0) { + free(name); + return (-1); /* XXX skip? */ + } + free(name); + return (0); + case CTRL_ATTR_MCAST_GRP_ID: + if (family->mcastgrp) + break; + if (nla_get_u32(a, &family->mcastgrp) < 0) { + fido_log_debug("%s: group", __func__); + return (-1); + } + return (0); + } + + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + + return (0); +} + +static int +parse_mcastgrps(nlamsgbuf_t *a, void *arg) +{ + return (nla_iter(a, arg, parse_mcastgrp)); +} + +static int +parse_family(nlamsgbuf_t *a, void *arg) +{ + nl_family_t *family = arg; + + switch (nla_type(a)) { + case CTRL_ATTR_FAMILY_ID: + if (family->id) + break; + if (nla_get_u16(a, &family->id) < 0) { + fido_log_debug("%s: id", __func__); + return (-1); + } + return (0); + case CTRL_ATTR_MCAST_GROUPS: + return (nla_iter(a, family, parse_mcastgrps)); + } + + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + + return (0); +} + +static int +nl_get_nfc_family(int fd, uint16_t *type, uint32_t *mcastgrp) +{ + nlmsgbuf_t *m; + uint8_t reply[512]; + nl_family_t family; + ssize_t r; + int ok; + + if ((m = nlmsg_new(GENL_ID_CTRL, 0, 64)) == NULL || + nlmsg_set_genl(m, CTRL_CMD_GETFAMILY) < 0 || + nlmsg_set_u16(m, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL) < 0 || + nlmsg_set_str(m, CTRL_ATTR_FAMILY_NAME, NFC_GENL_NAME) < 0 || + nlmsg_tx(fd, m) < 0) { + free(m); + return (-1); + } + free(m); + memset(&family, 0, sizeof(family)); + if ((r = nlmsg_rx(fd, reply, sizeof(reply), -1)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + if ((ok = nl_parse_reply(reply, (size_t)r, GENL_ID_CTRL, + CTRL_CMD_NEWFAMILY, &family, parse_family)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + if (family.id == 0 || family.mcastgrp == 0) { + fido_log_debug("%s: missing attr", __func__); + return (-1); + } + *type = family.id; + *mcastgrp = family.mcastgrp; + + return (0); +} + +static int +parse_target(nlamsgbuf_t *a, void *arg) +{ + nl_target_t *t = arg; + + if (t->found || nla_type(a) != NFC_ATTR_TARGET_INDEX) { + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + return (0); + } + if (nla_get_u32(a, t->value) < 0) { + fido_log_debug("%s: target", __func__); + return (-1); + } + t->found = 1; + + return (0); +} + +int +fido_nl_power_nfc(fido_nl_t *nl, uint32_t dev) +{ + nlmsgbuf_t *m; + uint8_t reply[512]; + ssize_t r; + int ok; + + if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL || + nlmsg_set_genl(m, NFC_CMD_DEV_UP) < 0 || + nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 || + nlmsg_tx(nl->fd, m) < 0) { + free(m); + return (-1); + } + free(m); + if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_CMD_DEV_UP, NULL, NULL)) != 0 && ok != EALREADY) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + + return (0); +} + +static int +nl_nfc_poll(fido_nl_t *nl, uint32_t dev) +{ + nlmsgbuf_t *m; + uint8_t reply[512]; + ssize_t r; + int ok; + + if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL || + nlmsg_set_genl(m, NFC_CMD_START_POLL) < 0 || + nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 || + nlmsg_set_u32(m, NFC_ATTR_PROTOCOLS, NFC_PROTO_ISO14443_MASK) < 0 || + nlmsg_tx(nl->fd, m) < 0) { + free(m); + return (-1); + } + free(m); + if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_CMD_START_POLL, NULL, NULL)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + + return (0); +} + +static int +nl_dump_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target, int ms) +{ + nlmsgbuf_t *m; + nl_target_t t; + uint8_t reply[512]; + ssize_t r; + int ok; + + if ((m = nlmsg_new(nl->nfc_type, NLM_F_DUMP, 64)) == NULL || + nlmsg_set_genl(m, NFC_CMD_GET_TARGET) < 0 || + nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 || + nlmsg_tx(nl->fd, m) < 0) { + free(m); + return (-1); + } + free(m); + if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), ms)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + memset(&t, 0, sizeof(t)); + t.value = target; + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_CMD_GET_TARGET, &t, parse_target)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + if (!t.found) { + fido_log_debug("%s: target not found", __func__); + return (-1); + } + + return (0); +} + +static int +parse_nfc_event(nlamsgbuf_t *a, void *arg) +{ + nl_poll_t *ctx = arg; + uint32_t dev; + + if (nla_type(a) != NFC_ATTR_DEVICE_INDEX) { + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + return (0); + } + if (nla_get_u32(a, &dev) < 0) { + fido_log_debug("%s: dev", __func__); + return (-1); + } + if (dev == ctx->dev) + ctx->eventcnt++; + else + fido_log_debug("%s: ignoring dev 0x%x", __func__, dev); + + return (0); +} + +int +fido_nl_get_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target) +{ + uint8_t reply[512]; + nl_poll_t ctx; + ssize_t r; + int ok; + + if (nl_nfc_poll(nl, dev) < 0) { + fido_log_debug("%s: nl_nfc_poll", __func__); + return (-1); + } +#ifndef FIDO_FUZZ + if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, + &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) { + fido_log_error(errno, "%s: setsockopt add", __func__); + return (-1); + } +#endif + r = nlmsg_rx(nl->fd, reply, sizeof(reply), NETLINK_POLL_MS); +#ifndef FIDO_FUZZ + if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, + &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) { + fido_log_error(errno, "%s: setsockopt drop", __func__); + return (-1); + } +#endif + if (r < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + memset(&ctx, 0, sizeof(ctx)); + ctx.dev = dev; + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_EVENT_TARGETS_FOUND, &ctx, parse_nfc_event)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + if (ctx.eventcnt == 0) { + fido_log_debug("%s: dev 0x%x not observed", __func__, dev); + return (-1); + } + if (nl_dump_nfc_target(nl, dev, target, -1) < 0) { + fido_log_debug("%s: nl_dump_nfc_target", __func__); + return (-1); + } + + return (0); +} + +void +fido_nl_free(fido_nl_t **nlp) +{ + fido_nl_t *nl; + + if (nlp == NULL || (nl = *nlp) == NULL) + return; + if (nl->fd != -1 && close(nl->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(nl); + *nlp = NULL; +} + +fido_nl_t * +fido_nl_new(void) +{ + fido_nl_t *nl; + int ok = -1; + + if ((nl = calloc(1, sizeof(*nl))) == NULL) + return (NULL); + if ((nl->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, + NETLINK_GENERIC)) == -1) { + fido_log_error(errno, "%s: socket", __func__); + goto fail; + } + nl->saddr.nl_family = AF_NETLINK; + if (bind(nl->fd, (struct sockaddr *)&nl->saddr, + sizeof(nl->saddr)) == -1) { + fido_log_error(errno, "%s: bind", __func__); + goto fail; + } + if (nl_get_nfc_family(nl->fd, &nl->nfc_type, &nl->nfc_mcastgrp) < 0) { + fido_log_debug("%s: nl_get_nfc_family", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) + fido_nl_free(&nl); + + return (nl); +} + +#ifdef FIDO_FUZZ +void +set_netlink_io_functions(ssize_t (*read_f)(int, void *, size_t), + ssize_t (*write_f)(int, const void *, size_t)) +{ + fuzz_read = read_f; + fuzz_write = write_f; +} +#endif diff --git a/src/netlink.h b/src/netlink.h new file mode 100644 index 0000000..c600b52 --- /dev/null +++ b/src/netlink.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _FIDO_NETLINK_H +#define _FIDO_NETLINK_H + +#include <sys/socket.h> + +#include <linux/genetlink.h> +#include <linux/netlink.h> +#include <linux/nfc.h> + +#include <stdlib.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct fido_nl { + int fd; + uint16_t nfc_type; + uint32_t nfc_mcastgrp; + struct sockaddr_nl saddr; +} fido_nl_t; + +fido_nl_t *fido_nl_new(void); +void fido_nl_free(struct fido_nl **); +int fido_nl_power_nfc(struct fido_nl *, uint32_t); +int fido_nl_get_nfc_target(struct fido_nl *, uint32_t , uint32_t *); + +#ifdef FIDO_FUZZ +void set_netlink_io_functions(ssize_t (*)(int, void *, size_t), + ssize_t (*)(int, const void *, size_t)); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_NETLINK_H */ diff --git a/src/nfc.c b/src/nfc.c new file mode 100644 index 0000000..2e97d5f --- /dev/null +++ b/src/nfc.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <stdio.h> +#include <string.h> + +#include "fido.h" +#include "fido/param.h" +#include "iso7816.h" + +#define TX_CHUNK_SIZE 240 + +static const uint8_t aid[] = { 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01 }; +static const uint8_t v_u2f[] = { 'U', '2', 'F', '_', 'V', '2' }; +static const uint8_t v_fido[] = { 'F', 'I', 'D', 'O', '_', '2', '_', '0' }; + +static int +tx_short_apdu(fido_dev_t *d, const iso7816_header_t *h, const uint8_t *payload, + uint8_t payload_len, uint8_t cla_flags) +{ + uint8_t apdu[5 + UINT8_MAX + 1]; + uint8_t sw[2]; + size_t apdu_len; + int ok = -1; + + memset(&apdu, 0, sizeof(apdu)); + apdu[0] = h->cla | cla_flags; + apdu[1] = h->ins; + apdu[2] = h->p1; + apdu[3] = h->p2; + apdu[4] = payload_len; + memcpy(&apdu[5], payload, payload_len); + apdu_len = (size_t)(5 + payload_len + 1); + + if (d->io.write(d->io_handle, apdu, apdu_len) < 0) { + fido_log_debug("%s: write", __func__); + goto fail; + } + + if (cla_flags & 0x10) { + if (d->io.read(d->io_handle, sw, sizeof(sw), -1) != 2) { + fido_log_debug("%s: read", __func__); + goto fail; + } + if ((sw[0] << 8 | sw[1]) != SW_NO_ERROR) { + fido_log_debug("%s: unexpected sw", __func__); + goto fail; + } + } + + ok = 0; +fail: + explicit_bzero(apdu, sizeof(apdu)); + + return ok; +} + +static int +nfc_do_tx(fido_dev_t *d, const uint8_t *apdu_ptr, size_t apdu_len) +{ + iso7816_header_t h; + + if (fido_buf_read(&apdu_ptr, &apdu_len, &h, sizeof(h)) < 0) { + fido_log_debug("%s: header", __func__); + return -1; + } + if (apdu_len < 2) { + fido_log_debug("%s: apdu_len %zu", __func__, apdu_len); + return -1; + } + + apdu_len -= 2; /* trim le1 le2 */ + + while (apdu_len > TX_CHUNK_SIZE) { + if (tx_short_apdu(d, &h, apdu_ptr, TX_CHUNK_SIZE, 0x10) < 0) { + fido_log_debug("%s: chain", __func__); + return -1; + } + apdu_ptr += TX_CHUNK_SIZE; + apdu_len -= TX_CHUNK_SIZE; + } + + if (tx_short_apdu(d, &h, apdu_ptr, (uint8_t)apdu_len, 0) < 0) { + fido_log_debug("%s: tx_short_apdu", __func__); + return -1; + } + + return 0; +} + +int +fido_nfc_tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count) +{ + iso7816_apdu_t *apdu = NULL; + const uint8_t *ptr; + size_t len; + int ok = -1; + + switch (cmd) { + case CTAP_CMD_INIT: /* select */ + if ((apdu = iso7816_new(0, 0xa4, 0x04, sizeof(aid))) == NULL || + iso7816_add(apdu, aid, sizeof(aid)) < 0) { + fido_log_debug("%s: iso7816", __func__); + goto fail; + } + break; + case CTAP_CMD_CBOR: /* wrap cbor */ + if (count > UINT16_MAX || (apdu = iso7816_new(0x80, 0x10, 0x00, + (uint16_t)count)) == NULL || + iso7816_add(apdu, buf, count) < 0) { + fido_log_debug("%s: iso7816", __func__); + goto fail; + } + break; + case CTAP_CMD_MSG: /* already an apdu */ + break; + default: + fido_log_debug("%s: cmd=%02x", __func__, cmd); + goto fail; + } + + if (apdu != NULL) { + ptr = iso7816_ptr(apdu); + len = iso7816_len(apdu); + } else { + ptr = buf; + len = count; + } + + if (nfc_do_tx(d, ptr, len) < 0) { + fido_log_debug("%s: nfc_do_tx", __func__); + goto fail; + } + + ok = 0; +fail: + iso7816_free(&apdu); + + return ok; +} + +static int +rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms) +{ + fido_ctap_info_t *attr = (fido_ctap_info_t *)buf; + uint8_t f[64]; + int n; + + if (count != sizeof(*attr)) { + fido_log_debug("%s: count=%zu", __func__, count); + return -1; + } + + memset(attr, 0, sizeof(*attr)); + + if ((n = d->io.read(d->io_handle, f, sizeof(f), ms)) < 2 || + (f[n - 2] << 8 | f[n - 1]) != SW_NO_ERROR) { + fido_log_debug("%s: read", __func__); + return -1; + } + + n -= 2; + + if (n == sizeof(v_u2f) && memcmp(f, v_u2f, sizeof(v_u2f)) == 0) + attr->flags = FIDO_CAP_CBOR; + else if (n == sizeof(v_fido) && memcmp(f, v_fido, sizeof(v_fido)) == 0) + attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; + else { + fido_log_debug("%s: unknown version string", __func__); +#ifdef FIDO_FUZZ + attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; +#else + return -1; +#endif + } + + memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce)); /* XXX */ + + return (int)count; +} + +static int +tx_get_response(fido_dev_t *d, uint8_t count) +{ + uint8_t apdu[5]; + + memset(apdu, 0, sizeof(apdu)); + apdu[1] = 0xc0; /* GET_RESPONSE */ + apdu[4] = count; + + if (d->io.write(d->io_handle, apdu, sizeof(apdu)) < 0) { + fido_log_debug("%s: write", __func__); + return -1; + } + + return 0; +} + +static int +rx_apdu(fido_dev_t *d, uint8_t sw[2], unsigned char **buf, size_t *count, int *ms) +{ + uint8_t f[256 + 2]; + struct timespec ts; + int n, ok = -1; + + if (fido_time_now(&ts) != 0) + goto fail; + + if ((n = d->io.read(d->io_handle, f, sizeof(f), *ms)) < 2) { + fido_log_debug("%s: read", __func__); + goto fail; + } + + if (fido_time_delta(&ts, ms) != 0) + goto fail; + + if (fido_buf_write(buf, count, f, (size_t)(n - 2)) < 0) { + fido_log_debug("%s: fido_buf_write", __func__); + goto fail; + } + + memcpy(sw, f + n - 2, 2); + + ok = 0; +fail: + explicit_bzero(f, sizeof(f)); + + return ok; +} + +static int +rx_msg(fido_dev_t *d, unsigned char *buf, size_t count, int ms) +{ + uint8_t sw[2]; + const size_t bufsiz = count; + + if (rx_apdu(d, sw, &buf, &count, &ms) < 0) { + fido_log_debug("%s: preamble", __func__); + return -1; + } + + while (sw[0] == SW1_MORE_DATA) + if (tx_get_response(d, sw[1]) < 0 || + rx_apdu(d, sw, &buf, &count, &ms) < 0) { + fido_log_debug("%s: chain", __func__); + return -1; + } + + if (fido_buf_write(&buf, &count, sw, sizeof(sw)) < 0) { + fido_log_debug("%s: sw", __func__); + return -1; + } + + if (bufsiz - count > INT_MAX) { + fido_log_debug("%s: bufsiz", __func__); + return -1; + } + + return (int)(bufsiz - count); +} + +static int +rx_cbor(fido_dev_t *d, unsigned char *buf, size_t count, int ms) +{ + int r; + + if ((r = rx_msg(d, buf, count, ms)) < 2) + return -1; + + return r - 2; +} + +int +fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms) +{ + switch (cmd) { + case CTAP_CMD_INIT: + return rx_init(d, buf, count, ms); + case CTAP_CMD_CBOR: + return rx_cbor(d, buf, count, ms); + case CTAP_CMD_MSG: + return rx_msg(d, buf, count, ms); + default: + fido_log_debug("%s: cmd=%02x", __func__, cmd); + return -1; + } +} + +bool +nfc_is_fido(const char *path) +{ + bool fido = false; + fido_dev_t *d; + int r; + + if ((d = fido_dev_new()) == NULL) { + fido_log_debug("%s: fido_dev_new", __func__); + goto fail; + } + /* fido_dev_open selects the fido applet */ + if ((r = fido_dev_open(d, path)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_open: 0x%x", __func__, r); + goto fail; + } + if ((r = fido_dev_close(d)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_close: 0x%x", __func__, r); + goto fail; + + } + + fido = true; +fail: + fido_dev_free(&d); + + return fido; +} + +#ifdef USE_NFC +bool +fido_is_nfc(const char *path) +{ + return strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) == 0; +} + +int +fido_dev_set_nfc(fido_dev_t *d) +{ + if (d->io_handle != NULL) { + fido_log_debug("%s: device open", __func__); + return -1; + } + d->io_own = true; + d->io = (fido_dev_io_t) { + fido_nfc_open, + fido_nfc_close, + fido_nfc_read, + fido_nfc_write, + }; + d->transport = (fido_dev_transport_t) { + fido_nfc_rx, + fido_nfc_tx, + }; + + return 0; +} +#endif /* USE_NFC */ diff --git a/src/nfc_linux.c b/src/nfc_linux.c new file mode 100644 index 0000000..4b69eb1 --- /dev/null +++ b/src/nfc_linux.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/socket.h> + +#include <linux/nfc.h> + +#include <errno.h> +#include <libudev.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "fido.h" +#include "fido/param.h" +#include "netlink.h" +#include "iso7816.h" + +struct nfc_linux { + int fd; + uint32_t dev; + uint32_t target; + sigset_t sigmask; + const sigset_t *sigmaskp; + struct fido_nl *nl; +}; + +static char * +get_parent_attr(struct udev_device *dev, const char *subsystem, + const char *devtype, const char *attr) +{ + struct udev_device *parent; + const char *value; + + if ((parent = udev_device_get_parent_with_subsystem_devtype(dev, + subsystem, devtype)) == NULL || (value = + udev_device_get_sysattr_value(parent, attr)) == NULL) + return NULL; + + return strdup(value); +} + +static char * +get_usb_attr(struct udev_device *dev, const char *attr) +{ + return get_parent_attr(dev, "usb", "usb_device", attr); +} + +static int +copy_info(fido_dev_info_t *di, struct udev *udev, + struct udev_list_entry *udev_entry) +{ + const char *name; + char *str; + struct udev_device *dev = NULL; + uint64_t id; + int ok = -1; + + memset(di, 0, sizeof(*di)); + + if ((name = udev_list_entry_get_name(udev_entry)) == NULL || + (dev = udev_device_new_from_syspath(udev, name)) == NULL) + goto fail; + if (asprintf(&di->path, "%s/%s", FIDO_NFC_PREFIX, name) == -1) { + di->path = NULL; + goto fail; + } + if (nfc_is_fido(di->path) == false) { + fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path); + goto fail; + } + if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL) + di->manufacturer = strdup(""); + if ((di->product = get_usb_attr(dev, "product")) == NULL) + di->product = strdup(""); + if (di->manufacturer == NULL || di->product == NULL) + goto fail; + /* XXX assumes USB for vendor/product info */ + if ((str = get_usb_attr(dev, "idVendor")) != NULL && + fido_to_uint64(str, 16, &id) == 0 && id <= UINT16_MAX) + di->vendor_id = (int16_t)id; + free(str); + if ((str = get_usb_attr(dev, "idProduct")) != NULL && + fido_to_uint64(str, 16, &id) == 0 && id <= UINT16_MAX) + di->product_id = (int16_t)id; + free(str); + + ok = 0; +fail: + if (dev != NULL) + udev_device_unref(dev); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return ok; +} + +static int +sysnum_from_syspath(const char *path) +{ + struct udev *udev = NULL; + struct udev_device *dev = NULL; + const char *str; + uint64_t idx64; + int idx = -1; + + if ((udev = udev_new()) != NULL && + (dev = udev_device_new_from_syspath(udev, path)) != NULL && + (str = udev_device_get_sysnum(dev)) != NULL && + fido_to_uint64(str, 10, &idx64) == 0 && idx64 < INT_MAX) + idx = (int)idx64; + + if (dev != NULL) + udev_device_unref(dev); + if (udev != NULL) + udev_unref(udev); + + return idx; +} + +int +fido_nfc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + struct udev *udev = NULL; + struct udev_enumerate *udev_enum = NULL; + struct udev_list_entry *udev_list; + struct udev_list_entry *udev_entry; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return FIDO_OK; + + if (devlist == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + + if ((udev = udev_new()) == NULL || + (udev_enum = udev_enumerate_new(udev)) == NULL) + goto fail; + + if (udev_enumerate_add_match_subsystem(udev_enum, "nfc") < 0 || + udev_enumerate_scan_devices(udev_enum) < 0) + goto fail; + + if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) { + r = FIDO_OK; /* zero nfc devices */ + goto fail; + } + + udev_list_entry_foreach(udev_entry, udev_list) { + if (copy_info(&devlist[*olen], udev, udev_entry) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_nfc_open, + fido_nfc_close, + fido_nfc_read, + fido_nfc_write, + }; + devlist[*olen].transport = (fido_dev_transport_t) { + fido_nfc_rx, + fido_nfc_tx, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (udev_enum != NULL) + udev_enumerate_unref(udev_enum); + if (udev != NULL) + udev_unref(udev); + + return r; +} + +static int +nfc_target_connect(struct nfc_linux *ctx) +{ + struct sockaddr_nfc sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_family = AF_NFC; + sa.dev_idx = ctx->dev; + sa.target_idx = ctx->target; + sa.nfc_protocol = NFC_PROTO_ISO14443; + + if ((ctx->fd = socket(AF_NFC, SOCK_SEQPACKET | SOCK_CLOEXEC, + NFC_SOCKPROTO_RAW)) == -1) { + fido_log_error(errno, "%s: socket", __func__); + return -1; + } + if (connect(ctx->fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + fido_log_error(errno, "%s: connect", __func__); + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + ctx->fd = -1; + return -1; + } + + return 0; +} + +static void +nfc_free(struct nfc_linux **ctx_p) +{ + struct nfc_linux *ctx; + + if (ctx_p == NULL || (ctx = *ctx_p) == NULL) + return; + if (ctx->fd != -1 && close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + if (ctx->nl != NULL) + fido_nl_free(&ctx->nl); + + free(ctx); + *ctx_p = NULL; +} + +static struct nfc_linux * +nfc_new(uint32_t dev) +{ + struct nfc_linux *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL || + (ctx->nl = fido_nl_new()) == NULL) { + nfc_free(&ctx); + return NULL; + } + + ctx->fd = -1; + ctx->dev = dev; + + return ctx; +} + +void * +fido_nfc_open(const char *path) +{ + struct nfc_linux *ctx = NULL; + int idx; + + if (strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) != 0) { + fido_log_debug("%s: bad prefix", __func__); + goto fail; + } + if ((idx = sysnum_from_syspath(path + strlen(FIDO_NFC_PREFIX))) < 0 || + (ctx = nfc_new((uint32_t)idx)) == NULL) { + fido_log_debug("%s: nfc_new", __func__); + goto fail; + } + if (fido_nl_power_nfc(ctx->nl, ctx->dev) < 0 || + fido_nl_get_nfc_target(ctx->nl, ctx->dev, &ctx->target) < 0 || + nfc_target_connect(ctx) < 0) { + fido_log_debug("%s: netlink", __func__); + goto fail; + } + + return ctx; +fail: + nfc_free(&ctx); + return NULL; +} + +void +fido_nfc_close(void *handle) +{ + struct nfc_linux *ctx = handle; + + nfc_free(&ctx); +} + +int +fido_nfc_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct nfc_linux *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return FIDO_OK; +} + +int +fido_nfc_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct nfc_linux *ctx = handle; + struct iovec iov[2]; + uint8_t preamble; + ssize_t r; + + memset(&iov, 0, sizeof(iov)); + iov[0].iov_base = &preamble; + iov[0].iov_len = sizeof(preamble); + iov[1].iov_base = buf; + iov[1].iov_len = len; + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fido_hid_unix_wait", __func__); + return -1; + } + if ((r = readv(ctx->fd, iov, nitems(iov))) == -1) { + fido_log_error(errno, "%s: read", __func__); + return -1; + } + if (r < 1) { + fido_log_debug("%s: %zd < 1", __func__, r); + return -1; + } + if (preamble != 0x00) { + fido_log_debug("%s: preamble", __func__); + return -1; + } + + r--; + fido_log_xxd(buf, (size_t)r, "%s", __func__); + + return (int)r; +} + +int +fido_nfc_write(void *handle, const unsigned char *buf, size_t len) +{ + struct nfc_linux *ctx = handle; + ssize_t r; + + fido_log_xxd(buf, len, "%s", __func__); + + if (len > INT_MAX) { + fido_log_debug("%s: len", __func__); + return -1; + } + if ((r = write(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return -1; + } + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return -1; + } + + return (int)r; +} diff --git a/src/packed.h b/src/packed.h new file mode 100644 index 0000000..5f53ae5 --- /dev/null +++ b/src/packed.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _PACKED_H +#define _PACKED_H + +#if defined(__GNUC__) +#define PACKED_TYPE(type, def) \ + typedef def __attribute__ ((__packed__)) type; +#elif defined(_MSC_VER) +#define PACKED_TYPE(type, def) \ + __pragma(pack(push, 1)) \ + typedef def type; \ + __pragma(pack(pop)) +#else +#error "please provide a way to define packed types on your platform" +#endif + +#endif /* !_PACKED_H */ diff --git a/src/pcsc.c b/src/pcsc.c new file mode 100644 index 0000000..d7bd6c6 --- /dev/null +++ b/src/pcsc.c @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2022 Micro Focus or one of its affiliates. + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#if __APPLE__ +#include <PCSC/wintypes.h> +#include <PCSC/winscard.h> +#else +#include <winscard.h> +#endif /* __APPLE__ */ + +#include <errno.h> + +#include "fido.h" +#include "fido/param.h" +#include "iso7816.h" + +#if defined(_WIN32) && !defined(__MINGW32__) +#define SCardConnect SCardConnectA +#define SCardListReaders SCardListReadersA +#endif + +#ifndef SCARD_PROTOCOL_Tx +#define SCARD_PROTOCOL_Tx SCARD_PROTOCOL_ANY +#endif + +#define BUFSIZE 1024 /* in bytes; passed to SCardListReaders() */ +#define APDULEN 264 /* 261 rounded up to the nearest multiple of 8 */ +#define READERS 8 /* maximum number of readers */ + +struct pcsc { + SCARDCONTEXT ctx; + SCARDHANDLE h; + SCARD_IO_REQUEST req; + uint8_t rx_buf[APDULEN]; + size_t rx_len; +}; + +static LONG +list_readers(SCARDCONTEXT ctx, char **buf) +{ + LONG s; + DWORD len; + + len = BUFSIZE; + if ((*buf = calloc(1, len)) == NULL) + goto fail; + if ((s = SCardListReaders(ctx, NULL, *buf, &len)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardListReaders 0x%lx", __func__, (long)s); + goto fail; + } + /* sanity check "multi-string" */ + if (len > BUFSIZE || len < 2) { + fido_log_debug("%s: bogus len=%u", __func__, (unsigned)len); + goto fail; + } + if ((*buf)[len - 1] != 0 || (*buf)[len - 2] != '\0') { + fido_log_debug("%s: bogus buf", __func__); + goto fail; + } + return (LONG)SCARD_S_SUCCESS; +fail: + free(*buf); + *buf = NULL; + + return (LONG)SCARD_E_NO_READERS_AVAILABLE; +} + +static char * +get_reader(SCARDCONTEXT ctx, const char *path) +{ + char *reader = NULL, *buf = NULL; + const char prefix[] = FIDO_PCSC_PREFIX "//slot"; + uint64_t n; + + if (path == NULL) + goto out; + if (strncmp(path, prefix, strlen(prefix)) != 0 || + fido_to_uint64(path + strlen(prefix), 10, &n) < 0 || + n > READERS - 1) { + fido_log_debug("%s: invalid path %s", __func__, path); + goto out; + } + if (list_readers(ctx, &buf) != SCARD_S_SUCCESS) { + fido_log_debug("%s: list_readers", __func__); + goto out; + } + for (const char *name = buf; *name != 0; name += strlen(name) + 1) { + if (n == 0) { + reader = strdup(name); + goto out; + } + n--; + } + fido_log_debug("%s: failed to find reader %s", __func__, path); +out: + free(buf); + + return reader; +} + +static int +prepare_io_request(DWORD prot, SCARD_IO_REQUEST *req) +{ + switch (prot) { + case SCARD_PROTOCOL_T0: + req->dwProtocol = SCARD_PCI_T0->dwProtocol; + req->cbPciLength = SCARD_PCI_T0->cbPciLength; + break; + case SCARD_PROTOCOL_T1: + req->dwProtocol = SCARD_PCI_T1->dwProtocol; + req->cbPciLength = SCARD_PCI_T1->cbPciLength; + break; + default: + fido_log_debug("%s: unknown protocol %lu", __func__, + (u_long)prot); + return -1; + } + + return 0; +} + +static int +copy_info(fido_dev_info_t *di, SCARDCONTEXT ctx, const char *reader, size_t idx) +{ + SCARDHANDLE h = 0; + SCARD_IO_REQUEST req; + DWORD prot = 0; + LONG s; + int ok = -1; + + memset(di, 0, sizeof(*di)); + memset(&req, 0, sizeof(req)); + + if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); + goto fail; + } + if (prepare_io_request(prot, &req) < 0) { + fido_log_debug("%s: prepare_io_request", __func__); + goto fail; + } + if (asprintf(&di->path, "%s//slot%zu", FIDO_PCSC_PREFIX, idx) == -1) { + di->path = NULL; + fido_log_debug("%s: asprintf", __func__); + goto fail; + } + if (nfc_is_fido(di->path) == false) { + fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path); + goto fail; + } + if ((di->manufacturer = strdup("PC/SC")) == NULL || + (di->product = strdup(reader)) == NULL) + goto fail; + + ok = 0; +fail: + if (h != 0) + SCardDisconnect(h, SCARD_LEAVE_CARD); + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return ok; +} + +int +fido_pcsc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + SCARDCONTEXT ctx = 0; + char *buf = NULL; + LONG s; + size_t idx = 0; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return FIDO_OK; + if (devlist == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + + if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, + &ctx)) != SCARD_S_SUCCESS || ctx == 0) { + fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, + (long)s); + if (s == (LONG)SCARD_E_NO_SERVICE || + s == (LONG)SCARD_E_NO_SMARTCARD) + r = FIDO_OK; /* suppress error */ + goto out; + } + if ((s = list_readers(ctx, &buf)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: list_readers 0x%lx", __func__, (long)s); + if (s == (LONG)SCARD_E_NO_READERS_AVAILABLE) + r = FIDO_OK; /* suppress error */ + goto out; + } + + for (const char *name = buf; *name != 0; name += strlen(name) + 1) { + if (idx == READERS) { + fido_log_debug("%s: stopping at %zu readers", __func__, + idx); + r = FIDO_OK; + goto out; + } + if (copy_info(&devlist[*olen], ctx, name, idx++) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_pcsc_open, + fido_pcsc_close, + fido_pcsc_read, + fido_pcsc_write, + }; + devlist[*olen].transport = (fido_dev_transport_t) { + fido_pcsc_rx, + fido_pcsc_tx, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +out: + free(buf); + if (ctx != 0) + SCardReleaseContext(ctx); + + return r; +} + +void * +fido_pcsc_open(const char *path) +{ + char *reader = NULL; + struct pcsc *dev = NULL; + SCARDCONTEXT ctx = 0; + SCARDHANDLE h = 0; + SCARD_IO_REQUEST req; + DWORD prot = 0; + LONG s; + + memset(&req, 0, sizeof(req)); + + if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, + &ctx)) != SCARD_S_SUCCESS || ctx == 0) { + fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, + (long)s); + goto fail; + + } + if ((reader = get_reader(ctx, path)) == NULL) { + fido_log_debug("%s: get_reader(%s)", __func__, path); + goto fail; + } + if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); + goto fail; + } + if (prepare_io_request(prot, &req) < 0) { + fido_log_debug("%s: prepare_io_request", __func__); + goto fail; + } + if ((dev = calloc(1, sizeof(*dev))) == NULL) + goto fail; + + dev->ctx = ctx; + dev->h = h; + dev->req = req; + ctx = 0; + h = 0; +fail: + if (h != 0) + SCardDisconnect(h, SCARD_LEAVE_CARD); + if (ctx != 0) + SCardReleaseContext(ctx); + free(reader); + + return dev; +} + +void +fido_pcsc_close(void *handle) +{ + struct pcsc *dev = handle; + + if (dev->h != 0) + SCardDisconnect(dev->h, SCARD_LEAVE_CARD); + if (dev->ctx != 0) + SCardReleaseContext(dev->ctx); + + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + free(dev); +} + +int +fido_pcsc_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct pcsc *dev = handle; + int r; + + (void)ms; + if (dev->rx_len == 0 || dev->rx_len > len || + dev->rx_len > sizeof(dev->rx_buf)) { + fido_log_debug("%s: rx_len", __func__); + return -1; + } + fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: reading", __func__); + memcpy(buf, dev->rx_buf, dev->rx_len); + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + r = (int)dev->rx_len; + dev->rx_len = 0; + + return r; +} + +int +fido_pcsc_write(void *handle, const unsigned char *buf, size_t len) +{ + struct pcsc *dev = handle; + DWORD n; + LONG s; + + if (len > INT_MAX) { + fido_log_debug("%s: len", __func__); + return -1; + } + + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + dev->rx_len = 0; + n = (DWORD)sizeof(dev->rx_buf); + + fido_log_xxd(buf, len, "%s: writing", __func__); + + if ((s = SCardTransmit(dev->h, &dev->req, buf, (DWORD)len, NULL, + dev->rx_buf, &n)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardTransmit 0x%lx", __func__, (long)s); + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + return -1; + } + dev->rx_len = (size_t)n; + + fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: read", __func__); + + return (int)len; +} + +int +fido_pcsc_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count) +{ + return fido_nfc_tx(d, cmd, buf, count); +} + +int +fido_pcsc_rx(fido_dev_t *d, uint8_t cmd, u_char *buf, size_t count, int ms) +{ + return fido_nfc_rx(d, cmd, buf, count, ms); +} + +bool +fido_is_pcsc(const char *path) +{ + return strncmp(path, FIDO_PCSC_PREFIX, strlen(FIDO_PCSC_PREFIX)) == 0; +} + +int +fido_dev_set_pcsc(fido_dev_t *d) +{ + if (d->io_handle != NULL) { + fido_log_debug("%s: device open", __func__); + return -1; + } + d->io_own = true; + d->io = (fido_dev_io_t) { + fido_pcsc_open, + fido_pcsc_close, + fido_pcsc_read, + fido_pcsc_write, + }; + d->transport = (fido_dev_transport_t) { + fido_pcsc_rx, + fido_pcsc_tx, + }; + + return 0; +} diff --git a/src/pin.c b/src/pin.c new file mode 100644 index 0000000..c3dd927 --- /dev/null +++ b/src/pin.c @@ -0,0 +1,723 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include "fido.h" +#include "fido/es256.h" + +#define CTAP21_UV_TOKEN_PERM_MAKECRED 0x01 +#define CTAP21_UV_TOKEN_PERM_ASSERT 0x02 +#define CTAP21_UV_TOKEN_PERM_CRED_MGMT 0x04 +#define CTAP21_UV_TOKEN_PERM_BIO 0x08 +#define CTAP21_UV_TOKEN_PERM_LARGEBLOB 0x10 +#define CTAP21_UV_TOKEN_PERM_CONFIG 0x20 + +int +fido_sha256(fido_blob_t *digest, const u_char *data, size_t data_len) +{ + if ((digest->ptr = calloc(1, SHA256_DIGEST_LENGTH)) == NULL) + return (-1); + + digest->len = SHA256_DIGEST_LENGTH; + + if (SHA256(data, data_len, digest->ptr) != digest->ptr) { + fido_blob_reset(digest); + return (-1); + } + + return (0); +} + +static int +pin_sha256_enc(const fido_dev_t *dev, const fido_blob_t *shared, + const fido_blob_t *pin, fido_blob_t **out) +{ + fido_blob_t *ph = NULL; + int r; + + if ((*out = fido_blob_new()) == NULL || + (ph = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (fido_sha256(ph, pin->ptr, pin->len) < 0 || ph->len < 16) { + fido_log_debug("%s: SHA256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + ph->len = 16; /* first 16 bytes */ + + if (aes256_cbc_enc(dev, shared, ph, *out) < 0) { + fido_log_debug("%s: aes256_cbc_enc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&ph); + + return (r); +} + +static int +pad64(const char *pin, fido_blob_t **ppin) +{ + size_t pin_len; + size_t ppin_len; + + pin_len = strlen(pin); + if (pin_len < 4 || pin_len > 63) { + fido_log_debug("%s: invalid pin length", __func__); + return (FIDO_ERR_PIN_POLICY_VIOLATION); + } + + if ((*ppin = fido_blob_new()) == NULL) + return (FIDO_ERR_INTERNAL); + + ppin_len = (pin_len + 63U) & ~63U; + if (ppin_len < pin_len || + ((*ppin)->ptr = calloc(1, ppin_len)) == NULL) { + fido_blob_free(ppin); + return (FIDO_ERR_INTERNAL); + } + + memcpy((*ppin)->ptr, pin, pin_len); + (*ppin)->len = ppin_len; + + return (FIDO_OK); +} + +static int +pin_pad64_enc(const fido_dev_t *dev, const fido_blob_t *shared, + const char *pin, fido_blob_t **out) +{ + fido_blob_t *ppin = NULL; + int r; + + if ((r = pad64(pin, &ppin)) != FIDO_OK) { + fido_log_debug("%s: pad64", __func__); + goto fail; + } + + if ((*out = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (aes256_cbc_enc(dev, shared, ppin, *out) < 0) { + fido_log_debug("%s: aes256_cbc_enc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&ppin); + + return (r); +} + +static cbor_item_t * +encode_uv_permission(uint8_t cmd) +{ + switch (cmd) { + case CTAP_CBOR_ASSERT: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_ASSERT)); + case CTAP_CBOR_BIO_ENROLL_PRE: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_BIO)); + case CTAP_CBOR_CONFIG: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_CONFIG)); + case CTAP_CBOR_MAKECRED: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_MAKECRED)); + case CTAP_CBOR_CRED_MGMT_PRE: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_CRED_MGMT)); + case CTAP_CBOR_LARGEBLOB: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_LARGEBLOB)); + default: + fido_log_debug("%s: cmd 0x%02x", __func__, cmd); + return (NULL); + } +} + +static int +ctap20_uv_token_tx(fido_dev_t *dev, const char *pin, const fido_blob_t *ecdh, + const es256_pk_t *pk, int *ms) +{ + fido_blob_t f; + fido_blob_t *p = NULL; + fido_blob_t *phe = NULL; + cbor_item_t *argv[6]; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if (pin == NULL) { + fido_log_debug("%s: NULL pin", __func__); + r = FIDO_ERR_PIN_REQUIRED; + goto fail; + } + + if ((p = fido_blob_new()) == NULL || fido_blob_set(p, + (const unsigned char *)pin, strlen(pin)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if ((r = pin_sha256_enc(dev, ecdh, p, &phe)) != FIDO_OK) { + fido_log_debug("%s: pin_sha256_enc", __func__); + goto fail; + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(5)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (argv[5] = fido_blob_encode(phe)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + fido_blob_free(&p); + fido_blob_free(&phe); + free(f.ptr); + + return (r); +} + +static int +ctap21_uv_token_tx(fido_dev_t *dev, const char *pin, const fido_blob_t *ecdh, + const es256_pk_t *pk, uint8_t cmd, const char *rpid, int *ms) +{ + fido_blob_t f; + fido_blob_t *p = NULL; + fido_blob_t *phe = NULL; + cbor_item_t *argv[10]; + uint8_t subcmd; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if (pin != NULL) { + if ((p = fido_blob_new()) == NULL || fido_blob_set(p, + (const unsigned char *)pin, strlen(pin)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if ((r = pin_sha256_enc(dev, ecdh, p, &phe)) != FIDO_OK) { + fido_log_debug("%s: pin_sha256_enc", __func__); + goto fail; + } + subcmd = 9; /* getPinUvAuthTokenUsingPinWithPermissions */ + } else { + if (fido_dev_has_uv(dev) == false) { + fido_log_debug("%s: fido_dev_has_uv", __func__); + r = FIDO_ERR_PIN_REQUIRED; + goto fail; + } + subcmd = 6; /* getPinUvAuthTokenUsingUvWithPermissions */ + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(subcmd)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (phe != NULL && (argv[5] = fido_blob_encode(phe)) == NULL) || + (argv[8] = encode_uv_permission(cmd)) == NULL || + (rpid != NULL && (argv[9] = cbor_build_string(rpid)) == NULL)) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + fido_blob_free(&p); + fido_blob_free(&phe); + free(f.ptr); + + return (r); +} + +static int +parse_uv_token(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_blob_t *token = arg; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 2) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + return (fido_blob_decode(val, token)); +} + +static int +uv_token_rx(fido_dev_t *dev, const fido_blob_t *ecdh, fido_blob_t *token, + int *ms) +{ + fido_blob_t *aes_token = NULL; + unsigned char *msg = NULL; + int msglen; + int r; + + if ((aes_token = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, aes_token, + parse_uv_token)) != FIDO_OK) { + fido_log_debug("%s: parse_uv_token", __func__); + goto fail; + } + + if (aes256_cbc_dec(dev, ecdh, aes_token, token) < 0) { + fido_log_debug("%s: aes256_cbc_dec", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&aes_token); + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +uv_token_wait(fido_dev_t *dev, uint8_t cmd, const char *pin, + const fido_blob_t *ecdh, const es256_pk_t *pk, const char *rpid, + fido_blob_t *token, int *ms) +{ + int r; + + if (ecdh == NULL || pk == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_dev_supports_permissions(dev)) + r = ctap21_uv_token_tx(dev, pin, ecdh, pk, cmd, rpid, ms); + else + r = ctap20_uv_token_tx(dev, pin, ecdh, pk, ms); + if (r != FIDO_OK) + return (r); + + return (uv_token_rx(dev, ecdh, token, ms)); +} + +int +fido_dev_get_uv_token(fido_dev_t *dev, uint8_t cmd, const char *pin, + const fido_blob_t *ecdh, const es256_pk_t *pk, const char *rpid, + fido_blob_t *token, int *ms) +{ + return (uv_token_wait(dev, cmd, pin, ecdh, pk, rpid, token, ms)); +} + +static int +fido_dev_change_pin_tx(fido_dev_t *dev, const char *pin, const char *oldpin, + int *ms) +{ + fido_blob_t f; + fido_blob_t *ppine = NULL; + fido_blob_t *ecdh = NULL; + fido_blob_t *opin = NULL; + fido_blob_t *opinhe = NULL; + cbor_item_t *argv[6]; + es256_pk_t *pk = NULL; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if ((opin = fido_blob_new()) == NULL || fido_blob_set(opin, + (const unsigned char *)oldpin, strlen(oldpin)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + + /* pad and encrypt new pin */ + if ((r = pin_pad64_enc(dev, ecdh, pin, &ppine)) != FIDO_OK) { + fido_log_debug("%s: pin_pad64_enc", __func__); + goto fail; + } + + /* hash and encrypt old pin */ + if ((r = pin_sha256_enc(dev, ecdh, opin, &opinhe)) != FIDO_OK) { + fido_log_debug("%s: pin_sha256_enc", __func__); + goto fail; + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(4)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (argv[3] = cbor_encode_change_pin_auth(dev, ecdh, ppine, opinhe)) == NULL || + (argv[4] = fido_blob_encode(ppine)) == NULL || + (argv[5] = fido_blob_encode(opinhe)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + es256_pk_free(&pk); + fido_blob_free(&ppine); + fido_blob_free(&ecdh); + fido_blob_free(&opin); + fido_blob_free(&opinhe); + free(f.ptr); + + return (r); + +} + +static int +fido_dev_set_pin_tx(fido_dev_t *dev, const char *pin, int *ms) +{ + fido_blob_t f; + fido_blob_t *ppine = NULL; + fido_blob_t *ecdh = NULL; + cbor_item_t *argv[5]; + es256_pk_t *pk = NULL; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + + if ((r = pin_pad64_enc(dev, ecdh, pin, &ppine)) != FIDO_OK) { + fido_log_debug("%s: pin_pad64_enc", __func__); + goto fail; + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(3)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (argv[3] = cbor_encode_pin_auth(dev, ecdh, ppine)) == NULL || + (argv[4] = fido_blob_encode(ppine)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + es256_pk_free(&pk); + fido_blob_free(&ppine); + fido_blob_free(&ecdh); + free(f.ptr); + + return (r); +} + +static int +fido_dev_set_pin_wait(fido_dev_t *dev, const char *pin, const char *oldpin, + int *ms) +{ + int r; + + if (oldpin != NULL) { + if ((r = fido_dev_change_pin_tx(dev, pin, oldpin, + ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_change_pin_tx", __func__); + return (r); + } + } else { + if ((r = fido_dev_set_pin_tx(dev, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_set_pin_tx", __func__); + return (r); + } + } + + if ((r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_rx_cbor_status", __func__); + return (r); + } + + if (dev->flags & FIDO_DEV_PIN_UNSET) { + dev->flags &= ~FIDO_DEV_PIN_UNSET; + dev->flags |= FIDO_DEV_PIN_SET; + } + + return (FIDO_OK); +} + +int +fido_dev_set_pin(fido_dev_t *dev, const char *pin, const char *oldpin) +{ + int ms = dev->timeout_ms; + + return (fido_dev_set_pin_wait(dev, pin, oldpin, &ms)); +} + +static int +parse_retry_count(const uint8_t keyval, const cbor_item_t *key, + const cbor_item_t *val, void *arg) +{ + int *retries = arg; + uint64_t n; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != keyval) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > INT_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + *retries = (int)n; + + return (0); +} + +static int +parse_pin_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + return (parse_retry_count(3, key, val, arg)); +} + +static int +parse_uv_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + return (parse_retry_count(5, key, val, arg)); +} + +static int +fido_dev_get_retry_count_tx(fido_dev_t *dev, uint8_t subcmd, int *ms) +{ + fido_blob_t f; + cbor_item_t *argv[2]; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if ((argv[0] = cbor_build_uint8(1)) == NULL || + (argv[1] = cbor_build_uint8(subcmd)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + + return (r); +} + +static int +fido_dev_get_pin_retry_count_rx(fido_dev_t *dev, int *retries, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + *retries = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, retries, + parse_pin_retry_count)) != FIDO_OK) { + fido_log_debug("%s: parse_pin_retry_count", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_dev_get_pin_retry_count_wait(fido_dev_t *dev, int *retries, int *ms) +{ + int r; + + if ((r = fido_dev_get_retry_count_tx(dev, 1, ms)) != FIDO_OK || + (r = fido_dev_get_pin_retry_count_rx(dev, retries, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_dev_get_retry_count(fido_dev_t *dev, int *retries) +{ + int ms = dev->timeout_ms; + + return (fido_dev_get_pin_retry_count_wait(dev, retries, &ms)); +} + +static int +fido_dev_get_uv_retry_count_rx(fido_dev_t *dev, int *retries, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + *retries = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, retries, + parse_uv_retry_count)) != FIDO_OK) { + fido_log_debug("%s: parse_uv_retry_count", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_dev_get_uv_retry_count_wait(fido_dev_t *dev, int *retries, int *ms) +{ + int r; + + if ((r = fido_dev_get_retry_count_tx(dev, 7, ms)) != FIDO_OK || + (r = fido_dev_get_uv_retry_count_rx(dev, retries, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_dev_get_uv_retry_count(fido_dev_t *dev, int *retries) +{ + int ms = dev->timeout_ms; + + return (fido_dev_get_uv_retry_count_wait(dev, retries, &ms)); +} + +int +cbor_add_uv_params(fido_dev_t *dev, uint8_t cmd, const fido_blob_t *hmac_data, + const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, + const char *rpid, cbor_item_t **auth, cbor_item_t **opt, int *ms) +{ + fido_blob_t *token = NULL; + int r; + + if ((token = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((r = fido_dev_get_uv_token(dev, cmd, pin, ecdh, pk, rpid, + token, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_get_uv_token", __func__); + goto fail; + } + + if ((*auth = cbor_encode_pin_auth(dev, token, hmac_data)) == NULL || + (*opt = cbor_encode_pin_opt(dev)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&token); + + return (r); +} diff --git a/src/random.c b/src/random.c new file mode 100644 index 0000000..9688d35 --- /dev/null +++ b/src/random.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_RANDOM_H +#include <sys/random.h> +#endif + +#include <fcntl.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "fido.h" + +#if defined(_WIN32) +#include <windows.h> + +#include <winternl.h> +#include <winerror.h> +#include <stdio.h> +#include <bcrypt.h> +#include <sal.h> + +int +fido_get_random(void *buf, size_t len) +{ + NTSTATUS status; + + status = BCryptGenRandom(NULL, buf, (ULONG)len, + BCRYPT_USE_SYSTEM_PREFERRED_RNG); + + if (!NT_SUCCESS(status)) + return (-1); + + return (0); +} +#elif defined(HAVE_ARC4RANDOM_BUF) +int +fido_get_random(void *buf, size_t len) +{ + arc4random_buf(buf, len); + return (0); +} +#elif defined(HAVE_GETRANDOM) +int +fido_get_random(void *buf, size_t len) +{ + ssize_t r; + + if ((r = getrandom(buf, len, 0)) < 0 || (size_t)r != len) + return (-1); + + return (0); +} +#elif defined(HAVE_DEV_URANDOM) +int +fido_get_random(void *buf, size_t len) +{ + int fd = -1; + int ok = -1; + ssize_t r; + + if ((fd = open(FIDO_RANDOM_DEV, O_RDONLY)) < 0) + goto fail; + if ((r = read(fd, buf, len)) < 0 || (size_t)r != len) + goto fail; + + ok = 0; +fail: + if (fd != -1) + close(fd); + + return (ok); +} +#else +#error "please provide an implementation of fido_get_random() for your platform" +#endif /* _WIN32 */ diff --git a/src/reset.c b/src/reset.c new file mode 100644 index 0000000..4e09dbb --- /dev/null +++ b/src/reset.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +fido_dev_reset_tx(fido_dev_t *dev, int *ms) +{ + const unsigned char cbor[] = { CTAP_CBOR_RESET }; + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + return (FIDO_ERR_TX); + } + + return (FIDO_OK); +} + +static int +fido_dev_reset_wait(fido_dev_t *dev, int *ms) +{ + int r; + + if ((r = fido_dev_reset_tx(dev, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) + return (r); + + if (dev->flags & FIDO_DEV_PIN_SET) { + dev->flags &= ~FIDO_DEV_PIN_SET; + dev->flags |= FIDO_DEV_PIN_UNSET; + } + + return (FIDO_OK); +} + +int +fido_dev_reset(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + + return (fido_dev_reset_wait(dev, &ms)); +} diff --git a/src/rs1.c b/src/rs1.c new file mode 100644 index 0000000..03636b5 --- /dev/null +++ b/src/rs1.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/rsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050200fL +static EVP_MD * +rs1_get_EVP_MD(void) +{ + const EVP_MD *from; + EVP_MD *to = NULL; + + if ((from = EVP_sha1()) != NULL && (to = malloc(sizeof(*to))) != NULL) + memcpy(to, from, sizeof(*to)); + + return (to); +} + +static void +rs1_free_EVP_MD(EVP_MD *md) +{ + freezero(md, sizeof(*md)); +} +#elif OPENSSL_VERSION_NUMBER >= 0x30000000 +static EVP_MD * +rs1_get_EVP_MD(void) +{ + return (EVP_MD_fetch(NULL, "SHA-1", NULL)); +} + +static void +rs1_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_free(md); +} +#else +static EVP_MD * +rs1_get_EVP_MD(void) +{ + const EVP_MD *md; + + if ((md = EVP_sha1()) == NULL) + return (NULL); + + return (EVP_MD_meth_dup(md)); +} + +static void +rs1_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_meth_free(md); +} +#endif /* LIBRESSL_VERSION_NUMBER */ + +int +rs1_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_MD *md = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((md = rs1_get_EVP_MD()) == NULL) { + fido_log_debug("%s: rs1_get_EVP_MD", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) != 1 || + EVP_PKEY_CTX_set_signature_md(pctx, md) != 1) { + fido_log_debug("%s: EVP_PKEY_CTX", __func__); + goto fail; + } + + if (EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + rs1_free_EVP_MD(md); + + return (ok); +} diff --git a/src/rs256.c b/src/rs256.c new file mode 100644 index 0000000..59ceb94 --- /dev/null +++ b/src/rs256.c @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/rs256.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get0_RSA(x) EVP_PKEY_get0_RSA((x)) +#else +#define get0_RSA(x) EVP_PKEY_get0((x)) +#endif + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050200fL +static EVP_MD * +rs256_get_EVP_MD(void) +{ + const EVP_MD *from; + EVP_MD *to = NULL; + + if ((from = EVP_sha256()) != NULL && (to = malloc(sizeof(*to))) != NULL) + memcpy(to, from, sizeof(*to)); + + return (to); +} + +static void +rs256_free_EVP_MD(EVP_MD *md) +{ + freezero(md, sizeof(*md)); +} +#elif OPENSSL_VERSION_NUMBER >= 0x30000000 +static EVP_MD * +rs256_get_EVP_MD(void) +{ + return (EVP_MD_fetch(NULL, "SHA2-256", NULL)); +} + +static void +rs256_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_free(md); +} +#else +static EVP_MD * +rs256_get_EVP_MD(void) +{ + const EVP_MD *md; + + if ((md = EVP_sha256()) == NULL) + return (NULL); + + return (EVP_MD_meth_dup(md)); +} + +static void +rs256_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_meth_free(md); +} +#endif /* LIBRESSL_VERSION_NUMBER */ + +static int +decode_bignum(const cbor_item_t *item, void *ptr, size_t len) +{ + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false || + cbor_bytestring_length(item) != len) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + memcpy(ptr, cbor_bytestring_handle(item), len); + + return (0); +} + +static int +decode_rsa_pubkey(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + rs256_pk_t *k = arg; + + if (cbor_isa_negint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) + return (0); /* ignore */ + + switch (cbor_get_uint8(key)) { + case 0: /* modulus */ + return (decode_bignum(val, &k->n, sizeof(k->n))); + case 1: /* public exponent */ + return (decode_bignum(val, &k->e, sizeof(k->e))); + } + + return (0); /* ignore */ +} + +int +rs256_pk_decode(const cbor_item_t *item, rs256_pk_t *k) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, k, decode_rsa_pubkey) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +rs256_pk_t * +rs256_pk_new(void) +{ + return (calloc(1, sizeof(rs256_pk_t))); +} + +void +rs256_pk_free(rs256_pk_t **pkp) +{ + rs256_pk_t *pk; + + if (pkp == NULL || (pk = *pkp) == NULL) + return; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +rs256_pk_from_ptr(rs256_pk_t *pk, const void *ptr, size_t len) +{ + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + memcpy(pk, ptr, sizeof(*pk)); + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: rs256_pk_to_EVP_PKEY", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + return (FIDO_OK); +} + +EVP_PKEY * +rs256_pk_to_EVP_PKEY(const rs256_pk_t *k) +{ + RSA *rsa = NULL; + EVP_PKEY *pkey = NULL; + BIGNUM *n = NULL; + BIGNUM *e = NULL; + int ok = -1; + + if ((n = BN_new()) == NULL || (e = BN_new()) == NULL) + goto fail; + + if (BN_bin2bn(k->n, sizeof(k->n), n) == NULL || + BN_bin2bn(k->e, sizeof(k->e), e) == NULL) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((rsa = RSA_new()) == NULL || RSA_set0_key(rsa, n, e, NULL) == 0) { + fido_log_debug("%s: RSA_set0_key", __func__); + goto fail; + } + + /* at this point, n and e belong to rsa */ + n = NULL; + e = NULL; + + if (RSA_bits(rsa) != 2048) { + fido_log_debug("%s: invalid key length", __func__); + goto fail; + } + + if ((pkey = EVP_PKEY_new()) == NULL || + EVP_PKEY_assign_RSA(pkey, rsa) == 0) { + fido_log_debug("%s: EVP_PKEY_assign_RSA", __func__); + goto fail; + } + + rsa = NULL; /* at this point, rsa belongs to evp */ + + ok = 0; +fail: + if (n != NULL) + BN_free(n); + if (e != NULL) + BN_free(e); + if (rsa != NULL) + RSA_free(rsa); + if (ok < 0 && pkey != NULL) { + EVP_PKEY_free(pkey); + pkey = NULL; + } + + return (pkey); +} + +int +rs256_pk_from_RSA(rs256_pk_t *pk, const RSA *rsa) +{ + const BIGNUM *n = NULL; + const BIGNUM *e = NULL; + const BIGNUM *d = NULL; + int k; + + if (RSA_bits(rsa) != 2048) { + fido_log_debug("%s: invalid key length", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + RSA_get0_key(rsa, &n, &e, &d); + + if (n == NULL || e == NULL) { + fido_log_debug("%s: RSA_get0_key", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((k = BN_num_bytes(n)) < 0 || (size_t)k > sizeof(pk->n) || + (k = BN_num_bytes(e)) < 0 || (size_t)k > sizeof(pk->e)) { + fido_log_debug("%s: invalid key", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((k = BN_bn2bin(n, pk->n)) < 0 || (size_t)k > sizeof(pk->n) || + (k = BN_bn2bin(e, pk->e)) < 0 || (size_t)k > sizeof(pk->e)) { + fido_log_debug("%s: BN_bn2bin", __func__); + return (FIDO_ERR_INTERNAL); + } + + return (FIDO_OK); +} + +int +rs256_pk_from_EVP_PKEY(rs256_pk_t *pk, const EVP_PKEY *pkey) +{ + const RSA *rsa; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA || + (rsa = get0_RSA(pkey)) == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (rs256_pk_from_RSA(pk, rsa)); +} + +int +rs256_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_MD *md = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((md = rs256_get_EVP_MD()) == NULL) { + fido_log_debug("%s: rs256_get_EVP_MD", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) != 1 || + EVP_PKEY_CTX_set_signature_md(pctx, md) != 1) { + fido_log_debug("%s: EVP_PKEY_CTX", __func__); + goto fail; + } + + if (EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + rs256_free_EVP_MD(md); + + return (ok); +} + +int +rs256_pk_verify_sig(const fido_blob_t *dgst, const rs256_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL || + rs256_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: rs256_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/time.c b/src/time.c new file mode 100644 index 0000000..fd0e4e3 --- /dev/null +++ b/src/time.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include "fido.h" + +static int +timespec_to_ms(const struct timespec *ts) +{ + int64_t x, y; + + if (ts->tv_sec < 0 || ts->tv_nsec < 0 || + ts->tv_nsec >= 1000000000LL) + return -1; + + if ((uint64_t)ts->tv_sec >= INT64_MAX / 1000LL) + return -1; + + x = ts->tv_sec * 1000LL; + y = ts->tv_nsec / 1000000LL; + + if (INT64_MAX - x < y || x + y > INT_MAX) + return -1; + + return (int)(x + y); +} + +int +fido_time_now(struct timespec *ts_now) +{ + if (clock_gettime(CLOCK_MONOTONIC, ts_now) != 0) { + fido_log_error(errno, "%s: clock_gettime", __func__); + return -1; + } + + return 0; +} + +int +fido_time_delta(const struct timespec *ts_start, int *ms_remain) +{ + struct timespec ts_end, ts_delta; + int ms; + + if (*ms_remain < 0) + return 0; + + if (clock_gettime(CLOCK_MONOTONIC, &ts_end) != 0) { + fido_log_error(errno, "%s: clock_gettime", __func__); + return -1; + } + + if (timespeccmp(&ts_end, ts_start, <)) { + fido_log_debug("%s: timespeccmp", __func__); + return -1; + } + + timespecsub(&ts_end, ts_start, &ts_delta); + + if ((ms = timespec_to_ms(&ts_delta)) < 0) { + fido_log_debug("%s: timespec_to_ms", __func__); + return -1; + } + + if (ms > *ms_remain) + ms = *ms_remain; + + *ms_remain -= ms; + + return 0; +} diff --git a/src/touch.c b/src/touch.c new file mode 100644 index 0000000..6844e2c --- /dev/null +++ b/src/touch.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include "fido.h" + +int +fido_dev_get_touch_begin(fido_dev_t *dev) +{ + fido_blob_t f; + cbor_item_t *argv[9]; + const char *clientdata = FIDO_DUMMY_CLIENTDATA; + const uint8_t user_id = FIDO_DUMMY_USER_ID; + unsigned char cdh[SHA256_DIGEST_LENGTH]; + fido_rp_t rp; + fido_user_t user; + int ms = dev->timeout_ms; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + memset(cdh, 0, sizeof(cdh)); + memset(&rp, 0, sizeof(rp)); + memset(&user, 0, sizeof(user)); + + if (fido_dev_is_fido2(dev) == false) + return (u2f_get_touch_begin(dev, &ms)); + + if (SHA256((const void *)clientdata, strlen(clientdata), cdh) != cdh) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((rp.id = strdup(FIDO_DUMMY_RP_ID)) == NULL || + (user.name = strdup(FIDO_DUMMY_USER_NAME)) == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + + if (fido_blob_set(&user.id, &user_id, sizeof(user_id)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + goto fail; + } + + if ((argv[0] = cbor_build_bytestring(cdh, sizeof(cdh))) == NULL || + (argv[1] = cbor_encode_rp_entity(&rp)) == NULL || + (argv[2] = cbor_encode_user_entity(&user)) == NULL || + (argv[3] = cbor_encode_pubkey_param(COSE_ES256)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if (fido_dev_supports_pin(dev)) { + if ((argv[7] = cbor_new_definite_bytestring()) == NULL || + (argv[8] = cbor_encode_pin_opt(dev)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + } + + if (cbor_build_frame(CTAP_CBOR_MAKECRED, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, &ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + free(rp.id); + free(user.name); + free(user.id.ptr); + + return (r); +} + +int +fido_dev_get_touch_status(fido_dev_t *dev, int *touched, int ms) +{ + int r; + + *touched = 0; + + if (fido_dev_is_fido2(dev) == false) + return (u2f_get_touch_status(dev, touched, &ms)); + + switch ((r = fido_rx_cbor_status(dev, &ms))) { + case FIDO_ERR_PIN_AUTH_INVALID: + case FIDO_ERR_PIN_INVALID: + case FIDO_ERR_PIN_NOT_SET: + case FIDO_ERR_SUCCESS: + *touched = 1; + break; + case FIDO_ERR_RX: + /* ignore */ + break; + default: + fido_log_debug("%s: fido_rx_cbor_status", __func__); + return (r); + } + + return (FIDO_OK); +} diff --git a/src/tpm.c b/src/tpm.c new file mode 100644 index 0000000..3e09bca --- /dev/null +++ b/src/tpm.c @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2021 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Trusted Platform Module (TPM) 2.0 attestation support. Documentation + * references are relative to revision 01.38 of the TPM 2.0 specification. + */ + +#include <openssl/sha.h> + +#include "packed.h" +#include "fido.h" + +/* Part 1, 4.89: TPM_GENERATED_VALUE */ +#define TPM_MAGIC 0xff544347 + +/* Part 2, 6.3: TPM_ALG_ID */ +#define TPM_ALG_RSA 0x0001 +#define TPM_ALG_SHA256 0x000b +#define TPM_ALG_NULL 0x0010 +#define TPM_ALG_ECC 0x0023 + +/* Part 2, 6.4: TPM_ECC_CURVE */ +#define TPM_ECC_P256 0x0003 + +/* Part 2, 6.9: TPM_ST_ATTEST_CERTIFY */ +#define TPM_ST_CERTIFY 0x8017 + +/* Part 2, 8.3: TPMA_OBJECT */ +#define TPMA_RESERVED 0xfff8f309 /* reserved bits; must be zero */ +#define TPMA_FIXED 0x00000002 /* object has fixed hierarchy */ +#define TPMA_CLEAR 0x00000004 /* object persists */ +#define TPMA_FIXED_P 0x00000010 /* object has fixed parent */ +#define TPMA_SENSITIVE 0x00000020 /* data originates within tpm */ +#define TPMA_SIGN 0x00040000 /* object may sign */ + +/* Part 2, 10.4.2: TPM2B_DIGEST */ +PACKED_TYPE(tpm_sha256_digest_t, +struct tpm_sha256_digest { + uint16_t size; /* sizeof(body) */ + uint8_t body[32]; +}) + +/* Part 2, 10.4.3: TPM2B_DATA */ +PACKED_TYPE(tpm_sha1_data_t, +struct tpm_sha1_data { + uint16_t size; /* sizeof(body) */ + uint8_t body[20]; +}) + +/* Part 2, 10.5.3: TPM2B_NAME */ +PACKED_TYPE(tpm_sha256_name_t, +struct tpm_sha256_name { + uint16_t size; /* sizeof(alg) + sizeof(body) */ + uint16_t alg; /* TPM_ALG_SHA256 */ + uint8_t body[32]; +}) + +/* Part 2, 10.11.1: TPMS_CLOCK_INFO */ +PACKED_TYPE(tpm_clock_info_t, +struct tpm_clock_info { + uint64_t timestamp_ms; + uint32_t reset_count; /* obfuscated by tpm */ + uint32_t restart_count; /* obfuscated by tpm */ + uint8_t safe; /* 1 if timestamp_ms is current */ +}) + +/* Part 2, 10.12.8 TPMS_ATTEST */ +PACKED_TYPE(tpm_sha1_attest_t, +struct tpm_sha1_attest { + uint32_t magic; /* TPM_MAGIC */ + uint16_t type; /* TPM_ST_ATTEST_CERTIFY */ + tpm_sha256_name_t signer; /* full tpm path of signing key */ + tpm_sha1_data_t data; /* signed sha1 */ + tpm_clock_info_t clock; + uint64_t fwversion; /* obfuscated by tpm */ + tpm_sha256_name_t name; /* sha256 of tpm_rs256_pubarea_t */ + tpm_sha256_name_t qual_name; /* full tpm path of attested key */ +}) + +/* Part 2, 11.2.4.5: TPM2B_PUBLIC_KEY_RSA */ +PACKED_TYPE(tpm_rs256_key_t, +struct tpm_rs256_key { + uint16_t size; /* sizeof(body) */ + uint8_t body[256]; +}) + +/* Part 2, 11.2.5.1: TPM2B_ECC_PARAMETER */ +PACKED_TYPE(tpm_es256_coord_t, +struct tpm_es256_coord { + uint16_t size; /* sizeof(body) */ + uint8_t body[32]; +}) + +/* Part 2, 11.2.5.2: TPMS_ECC_POINT */ +PACKED_TYPE(tpm_es256_point_t, +struct tpm_es256_point { + tpm_es256_coord_t x; + tpm_es256_coord_t y; +}) + +/* Part 2, 12.2.3.5: TPMS_RSA_PARMS */ +PACKED_TYPE(tpm_rs256_param_t, +struct tpm_rs256_param { + uint16_t symmetric; /* TPM_ALG_NULL */ + uint16_t scheme; /* TPM_ALG_NULL */ + uint16_t keybits; /* 2048 */ + uint32_t exponent; /* zero (meaning 2^16 + 1) */ +}) + +/* Part 2, 12.2.3.6: TPMS_ECC_PARMS */ +PACKED_TYPE(tpm_es256_param_t, +struct tpm_es256_param { + uint16_t symmetric; /* TPM_ALG_NULL */ + uint16_t scheme; /* TPM_ALG_NULL */ + uint16_t curve_id; /* TPM_ECC_P256 */ + uint16_t kdf; /* TPM_ALG_NULL */ +}) + +/* Part 2, 12.2.4: TPMT_PUBLIC */ +PACKED_TYPE(tpm_rs256_pubarea_t, +struct tpm_rs256_pubarea { + uint16_t alg; /* TPM_ALG_RSA */ + uint16_t hash; /* TPM_ALG_SHA256 */ + uint32_t attr; + tpm_sha256_digest_t policy; /* must be present? */ + tpm_rs256_param_t param; + tpm_rs256_key_t key; +}) + +/* Part 2, 12.2.4: TPMT_PUBLIC */ +PACKED_TYPE(tpm_es256_pubarea_t, +struct tpm_es256_pubarea { + uint16_t alg; /* TPM_ALG_ECC */ + uint16_t hash; /* TPM_ALG_SHA256 */ + uint32_t attr; + tpm_sha256_digest_t policy; /* must be present? */ + tpm_es256_param_t param; + tpm_es256_point_t point; +}) + +static int +get_signed_sha1(tpm_sha1_data_t *dgst, const fido_blob_t *authdata, + const fido_blob_t *clientdata) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + int ok = -1; + + if ((dgst->size = sizeof(dgst->body)) != SHA_DIGEST_LENGTH || + (md = EVP_sha1()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestFinal_ex(ctx, dgst->body, NULL) != 1) { + fido_log_debug("%s: sha1", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_MD_CTX_free(ctx); + + return (ok); +} + +static int +get_signed_name(tpm_sha256_name_t *name, const fido_blob_t *pubarea) +{ + name->alg = TPM_ALG_SHA256; + name->size = sizeof(name->alg) + sizeof(name->body); + if (sizeof(name->body) != SHA256_DIGEST_LENGTH || + SHA256(pubarea->ptr, pubarea->len, name->body) != name->body) { + fido_log_debug("%s: sha256", __func__); + return -1; + } + + return 0; +} + +static void +bswap_rs256_pubarea(tpm_rs256_pubarea_t *x) +{ + x->alg = htobe16(x->alg); + x->hash = htobe16(x->hash); + x->attr = htobe32(x->attr); + x->policy.size = htobe16(x->policy.size); + x->param.symmetric = htobe16(x->param.symmetric); + x->param.scheme = htobe16(x->param.scheme); + x->param.keybits = htobe16(x->param.keybits); + x->key.size = htobe16(x->key.size); +} + +static void +bswap_es256_pubarea(tpm_es256_pubarea_t *x) +{ + x->alg = htobe16(x->alg); + x->hash = htobe16(x->hash); + x->attr = htobe32(x->attr); + x->policy.size = htobe16(x->policy.size); + x->param.symmetric = htobe16(x->param.symmetric); + x->param.scheme = htobe16(x->param.scheme); + x->param.curve_id = htobe16(x->param.curve_id); + x->param.kdf = htobe16(x->param.kdf); + x->point.x.size = htobe16(x->point.x.size); + x->point.y.size = htobe16(x->point.y.size); +} + +static void +bswap_sha1_certinfo(tpm_sha1_attest_t *x) +{ + x->magic = htobe32(x->magic); + x->type = htobe16(x->type); + x->signer.size = htobe16(x->signer.size); + x->data.size = htobe16(x->data.size); + x->name.alg = htobe16(x->name.alg); + x->name.size = htobe16(x->name.size); +} + +static int +check_rs256_pubarea(const fido_blob_t *buf, const rs256_pk_t *pk) +{ + const tpm_rs256_pubarea_t *actual; + tpm_rs256_pubarea_t expected; + int ok; + + if (buf->len != sizeof(*actual)) { + fido_log_debug("%s: buf->len=%zu", __func__, buf->len); + return -1; + } + actual = (const void *)buf->ptr; + + memset(&expected, 0, sizeof(expected)); + expected.alg = TPM_ALG_RSA; + expected.hash = TPM_ALG_SHA256; + expected.attr = be32toh(actual->attr); + expected.attr &= ~(TPMA_RESERVED|TPMA_CLEAR); + expected.attr |= (TPMA_FIXED|TPMA_FIXED_P|TPMA_SENSITIVE|TPMA_SIGN); + expected.policy = actual->policy; + expected.policy.size = sizeof(expected.policy.body); + expected.param.symmetric = TPM_ALG_NULL; + expected.param.scheme = TPM_ALG_NULL; + expected.param.keybits = 2048; + expected.param.exponent = 0; /* meaning 2^16+1 */ + expected.key.size = sizeof(expected.key.body); + memcpy(&expected.key.body, &pk->n, sizeof(expected.key.body)); + bswap_rs256_pubarea(&expected); + + ok = timingsafe_bcmp(&expected, actual, sizeof(expected)); + explicit_bzero(&expected, sizeof(expected)); + + return ok != 0 ? -1 : 0; +} + +static int +check_es256_pubarea(const fido_blob_t *buf, const es256_pk_t *pk) +{ + const tpm_es256_pubarea_t *actual; + tpm_es256_pubarea_t expected; + int ok; + + if (buf->len != sizeof(*actual)) { + fido_log_debug("%s: buf->len=%zu", __func__, buf->len); + return -1; + } + actual = (const void *)buf->ptr; + + memset(&expected, 0, sizeof(expected)); + expected.alg = TPM_ALG_ECC; + expected.hash = TPM_ALG_SHA256; + expected.attr = be32toh(actual->attr); + expected.attr &= ~(TPMA_RESERVED|TPMA_CLEAR); + expected.attr |= (TPMA_FIXED|TPMA_FIXED_P|TPMA_SENSITIVE|TPMA_SIGN); + expected.policy = actual->policy; + expected.policy.size = sizeof(expected.policy.body); + expected.param.symmetric = TPM_ALG_NULL; + expected.param.scheme = TPM_ALG_NULL; /* TCG Alg. Registry, 5.2.4 */ + expected.param.curve_id = TPM_ECC_P256; + expected.param.kdf = TPM_ALG_NULL; + expected.point.x.size = sizeof(expected.point.x.body); + expected.point.y.size = sizeof(expected.point.y.body); + memcpy(&expected.point.x.body, &pk->x, sizeof(expected.point.x.body)); + memcpy(&expected.point.y.body, &pk->y, sizeof(expected.point.y.body)); + bswap_es256_pubarea(&expected); + + ok = timingsafe_bcmp(&expected, actual, sizeof(expected)); + explicit_bzero(&expected, sizeof(expected)); + + return ok != 0 ? -1 : 0; +} + +static int +check_sha1_certinfo(const fido_blob_t *buf, const fido_blob_t *clientdata_hash, + const fido_blob_t *authdata_raw, const fido_blob_t *pubarea) +{ + const tpm_sha1_attest_t *actual; + tpm_sha1_attest_t expected; + tpm_sha1_data_t signed_data; + tpm_sha256_name_t signed_name; + int ok = -1; + + memset(&signed_data, 0, sizeof(signed_data)); + memset(&signed_name, 0, sizeof(signed_name)); + + if (get_signed_sha1(&signed_data, authdata_raw, clientdata_hash) < 0 || + get_signed_name(&signed_name, pubarea) < 0) { + fido_log_debug("%s: get_signed_sha1/name", __func__); + goto fail; + } + if (buf->len != sizeof(*actual)) { + fido_log_debug("%s: buf->len=%zu", __func__, buf->len); + goto fail; + } + actual = (const void *)buf->ptr; + + memset(&expected, 0, sizeof(expected)); + expected.magic = TPM_MAGIC; + expected.type = TPM_ST_CERTIFY; + expected.signer = actual->signer; + expected.signer.size = sizeof(expected.signer.alg) + + sizeof(expected.signer.body); + expected.data = signed_data; + expected.clock = actual->clock; + expected.clock.safe = 1; + expected.fwversion = actual->fwversion; + expected.name = signed_name; + expected.qual_name = actual->qual_name; + bswap_sha1_certinfo(&expected); + + ok = timingsafe_bcmp(&expected, actual, sizeof(expected)); +fail: + explicit_bzero(&expected, sizeof(expected)); + explicit_bzero(&signed_data, sizeof(signed_data)); + explicit_bzero(&signed_name, sizeof(signed_name)); + + return ok != 0 ? -1 : 0; +} + +int +fido_get_signed_hash_tpm(fido_blob_t *dgst, const fido_blob_t *clientdata_hash, + const fido_blob_t *authdata_raw, const fido_attstmt_t *attstmt, + const fido_attcred_t *attcred) +{ + const fido_blob_t *pubarea = &attstmt->pubarea; + const fido_blob_t *certinfo = &attstmt->certinfo; + + if (attstmt->alg != COSE_RS1) { + fido_log_debug("%s: unsupported alg %d", __func__, + attstmt->alg); + return -1; + } + + switch (attcred->type) { + case COSE_ES256: + if (check_es256_pubarea(pubarea, &attcred->pubkey.es256) < 0) { + fido_log_debug("%s: check_es256_pubarea", __func__); + return -1; + } + break; + case COSE_RS256: + if (check_rs256_pubarea(pubarea, &attcred->pubkey.rs256) < 0) { + fido_log_debug("%s: check_rs256_pubarea", __func__); + return -1; + } + break; + default: + fido_log_debug("%s: unsupported type %d", __func__, + attcred->type); + return -1; + } + + if (check_sha1_certinfo(certinfo, clientdata_hash, authdata_raw, + pubarea) < 0) { + fido_log_debug("%s: check_sha1_certinfo", __func__); + return -1; + } + + if (dgst->len < SHA_DIGEST_LENGTH || + SHA1(certinfo->ptr, certinfo->len, dgst->ptr) != dgst->ptr) { + fido_log_debug("%s: sha1", __func__); + return -1; + } + dgst->len = SHA_DIGEST_LENGTH; + + return 0; +} diff --git a/src/types.c b/src/types.c new file mode 100644 index 0000000..f31f8da --- /dev/null +++ b/src/types.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +void +fido_str_array_free(fido_str_array_t *sa) +{ + for (size_t i = 0; i < sa->len; i++) + free(sa->ptr[i]); + + free(sa->ptr); + sa->ptr = NULL; + sa->len = 0; +} + +void +fido_opt_array_free(fido_opt_array_t *oa) +{ + for (size_t i = 0; i < oa->len; i++) + free(oa->name[i]); + + free(oa->name); + free(oa->value); + oa->name = NULL; + oa->value = NULL; + oa->len = 0; +} + +void +fido_byte_array_free(fido_byte_array_t *ba) +{ + free(ba->ptr); + + ba->ptr = NULL; + ba->len = 0; +} + +void +fido_algo_free(fido_algo_t *a) +{ + free(a->type); + a->type = NULL; + a->cose = 0; +} + +void +fido_algo_array_free(fido_algo_array_t *aa) +{ + for (size_t i = 0; i < aa->len; i++) + fido_algo_free(&aa->ptr[i]); + + free(aa->ptr); + aa->ptr = NULL; + aa->len = 0; +} + +void +fido_cert_array_free(fido_cert_array_t *ca) +{ + for (size_t i = 0; i < ca->len; i++) + free(ca->name[i]); + + free(ca->name); + free(ca->value); + ca->name = NULL; + ca->value = NULL; + ca->len = 0; +} + +int +fido_str_array_pack(fido_str_array_t *sa, const char * const *v, size_t n) +{ + if ((sa->ptr = calloc(n, sizeof(char *))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + for (size_t i = 0; i < n; i++) { + if ((sa->ptr[i] = strdup(v[i])) == NULL) { + fido_log_debug("%s: strdup", __func__); + return -1; + } + sa->len++; + } + + return 0; +} diff --git a/src/u2f.c b/src/u2f.c new file mode 100644 index 0000000..b1f7bce --- /dev/null +++ b/src/u2f.c @@ -0,0 +1,959 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include <openssl/x509.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> + +#include "fido.h" +#include "fido/es256.h" +#include "fallthrough.h" + +#define U2F_PACE_MS (100) + +#if defined(_MSC_VER) +static int +usleep(unsigned int usec) +{ + Sleep(usec / 1000); + + return (0); +} +#endif + +static int +delay_ms(unsigned int ms, int *ms_remain) +{ + if (*ms_remain > -1 && (unsigned int)*ms_remain < ms) + ms = (unsigned int)*ms_remain; + + if (ms > UINT_MAX / 1000) { + fido_log_debug("%s: ms=%u", __func__, ms); + return (-1); + } + + if (usleep(ms * 1000) < 0) { + fido_log_error(errno, "%s: usleep", __func__); + return (-1); + } + + if (*ms_remain > -1) + *ms_remain -= (int)ms; + + return (0); +} + +static int +sig_get(fido_blob_t *sig, const unsigned char **buf, size_t *len) +{ + sig->len = *len; /* consume the whole buffer */ + if ((sig->ptr = calloc(1, sig->len)) == NULL || + fido_buf_read(buf, len, sig->ptr, sig->len) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + fido_blob_reset(sig); + return (-1); + } + + return (0); +} + +static int +x5c_get(fido_blob_t *x5c, const unsigned char **buf, size_t *len) +{ + X509 *cert = NULL; + int ok = -1; + + if (*len > LONG_MAX) { + fido_log_debug("%s: invalid len %zu", __func__, *len); + goto fail; + } + + /* find out the certificate's length */ + const unsigned char *end = *buf; + if ((cert = d2i_X509(NULL, &end, (long)*len)) == NULL || end <= *buf || + (x5c->len = (size_t)(end - *buf)) >= *len) { + fido_log_debug("%s: d2i_X509", __func__); + goto fail; + } + + /* read accordingly */ + if ((x5c->ptr = calloc(1, x5c->len)) == NULL || + fido_buf_read(buf, len, x5c->ptr, x5c->len) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + goto fail; + } + + ok = 0; +fail: + if (cert != NULL) + X509_free(cert); + + if (ok < 0) + fido_blob_reset(x5c); + + return (ok); +} + +static int +authdata_fake(const char *rp_id, uint8_t flags, uint32_t sigcount, + fido_blob_t *fake_cbor_ad) +{ + fido_authdata_t ad; + cbor_item_t *item = NULL; + size_t alloc_len; + + memset(&ad, 0, sizeof(ad)); + + if (SHA256((const void *)rp_id, strlen(rp_id), + ad.rp_id_hash) != ad.rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + return (-1); + } + + ad.flags = flags; /* XXX translate? */ + ad.sigcount = sigcount; + + if ((item = cbor_build_bytestring((const unsigned char *)&ad, + sizeof(ad))) == NULL) { + fido_log_debug("%s: cbor_build_bytestring", __func__); + return (-1); + } + + if (fake_cbor_ad->ptr != NULL || + (fake_cbor_ad->len = cbor_serialize_alloc(item, &fake_cbor_ad->ptr, + &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + cbor_decref(&item); + return (-1); + } + + cbor_decref(&item); + + return (0); +} + +/* TODO: use u2f_get_touch_begin & u2f_get_touch_status instead */ +static int +send_dummy_register(fido_dev_t *dev, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char *reply = NULL; + unsigned char challenge[SHA256_DIGEST_LENGTH]; + unsigned char application[SHA256_DIGEST_LENGTH]; + int r; + + /* dummy challenge & application */ + memset(&challenge, 0xff, sizeof(challenge)); + memset(&application, 0xff, sizeof(application)); + + if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * + SHA256_DIGEST_LENGTH)) == NULL || + iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 || + iso7816_add(apdu, &application, sizeof(application)) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if (fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, ms) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (delay_ms(U2F_PACE_MS, ms) != 0) { + fido_log_debug("%s: delay_ms", __func__); + r = FIDO_ERR_RX; + goto fail; + } + } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + return (r); +} + +static int +key_lookup(fido_dev_t *dev, const char *rp_id, const fido_blob_t *key_id, + int *found, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char *reply = NULL; + unsigned char challenge[SHA256_DIGEST_LENGTH]; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + uint8_t key_id_len; + int r; + + if (key_id->len > UINT8_MAX || rp_id == NULL) { + fido_log_debug("%s: key_id->len=%zu, rp_id=%p", __func__, + key_id->len, (const void *)rp_id); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + memset(&challenge, 0xff, sizeof(challenge)); + memset(&rp_id_hash, 0, sizeof(rp_id_hash)); + + if (SHA256((const void *)rp_id, strlen(rp_id), + rp_id_hash) != rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + key_id_len = (uint8_t)key_id->len; + + if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_CHECK, (uint16_t)(2 * + SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL || + iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 || + iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 || + iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 || + iso7816_add(apdu, key_id->ptr, key_id_len) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if (fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, ms) != 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + switch ((reply[0] << 8) | reply[1]) { + case SW_CONDITIONS_NOT_SATISFIED: + *found = 1; /* key exists */ + break; + case SW_WRONG_DATA: + *found = 0; /* key does not exist */ + break; + default: + /* unexpected sw */ + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + return (r); +} + +static int +parse_auth_reply(fido_blob_t *sig, fido_blob_t *ad, const char *rp_id, + const unsigned char *reply, size_t len) +{ + uint8_t flags; + uint32_t sigcount; + + if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) { + fido_log_debug("%s: unexpected sw", __func__); + return (FIDO_ERR_RX); + } + + len -= 2; + + if (fido_buf_read(&reply, &len, &flags, sizeof(flags)) < 0 || + fido_buf_read(&reply, &len, &sigcount, sizeof(sigcount)) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + return (FIDO_ERR_RX); + } + + if (sig_get(sig, &reply, &len) < 0) { + fido_log_debug("%s: sig_get", __func__); + return (FIDO_ERR_RX); + } + + if (authdata_fake(rp_id, flags, sigcount, ad) < 0) { + fido_log_debug("%s; authdata_fake", __func__); + return (FIDO_ERR_RX); + } + + return (FIDO_OK); +} + +static int +do_auth(fido_dev_t *dev, const fido_blob_t *cdh, const char *rp_id, + const fido_blob_t *key_id, fido_blob_t *sig, fido_blob_t *ad, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char *reply = NULL; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + int reply_len; + uint8_t key_id_len; + int r; + +#ifdef FIDO_FUZZ + *ms = 0; /* XXX */ +#endif + + if (cdh->len != SHA256_DIGEST_LENGTH || key_id->len > UINT8_MAX || + rp_id == NULL) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + memset(&rp_id_hash, 0, sizeof(rp_id_hash)); + + if (SHA256((const void *)rp_id, strlen(rp_id), + rp_id_hash) != rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + key_id_len = (uint8_t)key_id->len; + + if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_SIGN, (uint16_t)(2 * + SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL || + iso7816_add(apdu, cdh->ptr, cdh->len) < 0 || + iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 || + iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 || + iso7816_add(apdu, key_id->ptr, key_id_len) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply, + FIDO_MAXMSG, ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (delay_ms(U2F_PACE_MS, ms) != 0) { + fido_log_debug("%s: delay_ms", __func__); + r = FIDO_ERR_RX; + goto fail; + } + } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); + + if ((r = parse_auth_reply(sig, ad, rp_id, reply, + (size_t)reply_len)) != FIDO_OK) { + fido_log_debug("%s: parse_auth_reply", __func__); + goto fail; + } + +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + return (r); +} + +static int +cbor_blob_from_ec_point(const uint8_t *ec_point, size_t ec_point_len, + fido_blob_t *cbor_blob) +{ + es256_pk_t *pk = NULL; + cbor_item_t *pk_cbor = NULL; + size_t alloc_len; + int ok = -1; + + /* only handle uncompressed points */ + if (ec_point_len != 65 || ec_point[0] != 0x04) { + fido_log_debug("%s: unexpected format", __func__); + goto fail; + } + + if ((pk = es256_pk_new()) == NULL || + es256_pk_set_x(pk, &ec_point[1]) < 0 || + es256_pk_set_y(pk, &ec_point[33]) < 0) { + fido_log_debug("%s: es256_pk_set", __func__); + goto fail; + } + + if ((pk_cbor = es256_pk_encode(pk, 0)) == NULL) { + fido_log_debug("%s: es256_pk_encode", __func__); + goto fail; + } + + if ((cbor_blob->len = cbor_serialize_alloc(pk_cbor, &cbor_blob->ptr, + &alloc_len)) != 77) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + ok = 0; +fail: + es256_pk_free(&pk); + + if (pk_cbor) + cbor_decref(&pk_cbor); + + return (ok); +} + +static int +encode_cred_attstmt(int cose_alg, const fido_blob_t *x5c, + const fido_blob_t *sig, fido_blob_t *out) +{ + cbor_item_t *item = NULL; + cbor_item_t *x5c_cbor = NULL; + const uint8_t alg_cbor = (uint8_t)(-cose_alg - 1); + struct cbor_pair kv[3]; + size_t alloc_len; + int ok = -1; + + memset(&kv, 0, sizeof(kv)); + memset(out, 0, sizeof(*out)); + + if ((item = cbor_new_definite_map(3)) == NULL) { + fido_log_debug("%s: cbor_new_definite_map", __func__); + goto fail; + } + + if ((kv[0].key = cbor_build_string("alg")) == NULL || + (kv[0].value = cbor_build_negint8(alg_cbor)) == NULL || + !cbor_map_add(item, kv[0])) { + fido_log_debug("%s: alg", __func__); + goto fail; + } + + if ((kv[1].key = cbor_build_string("sig")) == NULL || + (kv[1].value = fido_blob_encode(sig)) == NULL || + !cbor_map_add(item, kv[1])) { + fido_log_debug("%s: sig", __func__); + goto fail; + } + + if ((kv[2].key = cbor_build_string("x5c")) == NULL || + (kv[2].value = cbor_new_definite_array(1)) == NULL || + (x5c_cbor = fido_blob_encode(x5c)) == NULL || + !cbor_array_push(kv[2].value, x5c_cbor) || + !cbor_map_add(item, kv[2])) { + fido_log_debug("%s: x5c", __func__); + goto fail; + } + + if ((out->len = cbor_serialize_alloc(item, &out->ptr, + &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + ok = 0; +fail: + if (item != NULL) + cbor_decref(&item); + if (x5c_cbor != NULL) + cbor_decref(&x5c_cbor); + + for (size_t i = 0; i < nitems(kv); i++) { + if (kv[i].key) + cbor_decref(&kv[i].key); + if (kv[i].value) + cbor_decref(&kv[i].value); + } + + return (ok); +} + +static int +encode_cred_authdata(const char *rp_id, const uint8_t *kh, uint8_t kh_len, + const uint8_t *pubkey, size_t pubkey_len, fido_blob_t *out) +{ + fido_authdata_t authdata; + fido_attcred_raw_t attcred_raw; + fido_blob_t pk_blob; + fido_blob_t authdata_blob; + cbor_item_t *authdata_cbor = NULL; + unsigned char *ptr; + size_t len; + size_t alloc_len; + int ok = -1; + + memset(&pk_blob, 0, sizeof(pk_blob)); + memset(&authdata, 0, sizeof(authdata)); + memset(&authdata_blob, 0, sizeof(authdata_blob)); + memset(out, 0, sizeof(*out)); + + if (rp_id == NULL) { + fido_log_debug("%s: NULL rp_id", __func__); + goto fail; + } + + if (cbor_blob_from_ec_point(pubkey, pubkey_len, &pk_blob) < 0) { + fido_log_debug("%s: cbor_blob_from_ec_point", __func__); + goto fail; + } + + if (SHA256((const void *)rp_id, strlen(rp_id), + authdata.rp_id_hash) != authdata.rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + goto fail; + } + + authdata.flags = (CTAP_AUTHDATA_ATT_CRED | CTAP_AUTHDATA_USER_PRESENT); + authdata.sigcount = 0; + + memset(&attcred_raw.aaguid, 0, sizeof(attcred_raw.aaguid)); + attcred_raw.id_len = htobe16(kh_len); + + len = authdata_blob.len = sizeof(authdata) + sizeof(attcred_raw) + + kh_len + pk_blob.len; + ptr = authdata_blob.ptr = calloc(1, authdata_blob.len); + + fido_log_debug("%s: ptr=%p, len=%zu", __func__, (void *)ptr, len); + + if (authdata_blob.ptr == NULL) + goto fail; + + if (fido_buf_write(&ptr, &len, &authdata, sizeof(authdata)) < 0 || + fido_buf_write(&ptr, &len, &attcred_raw, sizeof(attcred_raw)) < 0 || + fido_buf_write(&ptr, &len, kh, kh_len) < 0 || + fido_buf_write(&ptr, &len, pk_blob.ptr, pk_blob.len) < 0) { + fido_log_debug("%s: fido_buf_write", __func__); + goto fail; + } + + if ((authdata_cbor = fido_blob_encode(&authdata_blob)) == NULL) { + fido_log_debug("%s: fido_blob_encode", __func__); + goto fail; + } + + if ((out->len = cbor_serialize_alloc(authdata_cbor, &out->ptr, + &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + ok = 0; +fail: + if (authdata_cbor) + cbor_decref(&authdata_cbor); + + fido_blob_reset(&pk_blob); + fido_blob_reset(&authdata_blob); + + return (ok); +} + +static int +parse_register_reply(fido_cred_t *cred, const unsigned char *reply, size_t len) +{ + fido_blob_t x5c; + fido_blob_t sig; + fido_blob_t ad; + fido_blob_t stmt; + uint8_t dummy; + uint8_t pubkey[65]; + uint8_t kh_len = 0; + uint8_t *kh = NULL; + int r; + + memset(&x5c, 0, sizeof(x5c)); + memset(&sig, 0, sizeof(sig)); + memset(&ad, 0, sizeof(ad)); + memset(&stmt, 0, sizeof(stmt)); + r = FIDO_ERR_RX; + + /* status word */ + if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) { + fido_log_debug("%s: unexpected sw", __func__); + goto fail; + } + + len -= 2; + + /* reserved byte */ + if (fido_buf_read(&reply, &len, &dummy, sizeof(dummy)) < 0 || + dummy != 0x05) { + fido_log_debug("%s: reserved byte", __func__); + goto fail; + } + + /* pubkey + key handle */ + if (fido_buf_read(&reply, &len, &pubkey, sizeof(pubkey)) < 0 || + fido_buf_read(&reply, &len, &kh_len, sizeof(kh_len)) < 0 || + (kh = calloc(1, kh_len)) == NULL || + fido_buf_read(&reply, &len, kh, kh_len) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + goto fail; + } + + /* x5c + sig */ + if (x5c_get(&x5c, &reply, &len) < 0 || + sig_get(&sig, &reply, &len) < 0) { + fido_log_debug("%s: x5c || sig", __func__); + goto fail; + } + + /* attstmt */ + if (encode_cred_attstmt(COSE_ES256, &x5c, &sig, &stmt) < 0) { + fido_log_debug("%s: encode_cred_attstmt", __func__); + goto fail; + } + + /* authdata */ + if (encode_cred_authdata(cred->rp.id, kh, kh_len, pubkey, + sizeof(pubkey), &ad) < 0) { + fido_log_debug("%s: encode_cred_authdata", __func__); + goto fail; + } + + if (fido_cred_set_fmt(cred, "fido-u2f") != FIDO_OK || + fido_cred_set_authdata(cred, ad.ptr, ad.len) != FIDO_OK || + fido_cred_set_attstmt(cred, stmt.ptr, stmt.len) != FIDO_OK) { + fido_log_debug("%s: fido_cred_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + freezero(kh, kh_len); + fido_blob_reset(&x5c); + fido_blob_reset(&sig); + fido_blob_reset(&ad); + fido_blob_reset(&stmt); + + return (r); +} + +int +u2f_register(fido_dev_t *dev, fido_cred_t *cred, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + unsigned char *reply = NULL; + int reply_len; + int found; + int r; + + if (cred->rk == FIDO_OPT_TRUE || cred->uv == FIDO_OPT_TRUE) { + fido_log_debug("%s: rk=%d, uv=%d", __func__, cred->rk, + cred->uv); + return (FIDO_ERR_UNSUPPORTED_OPTION); + } + + if (cred->type != COSE_ES256 || cred->cdh.ptr == NULL || + cred->rp.id == NULL || cred->cdh.len != SHA256_DIGEST_LENGTH) { + fido_log_debug("%s: type=%d, cdh=(%p,%zu)" , __func__, + cred->type, (void *)cred->cdh.ptr, cred->cdh.len); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + for (size_t i = 0; i < cred->excl.len; i++) { + if ((r = key_lookup(dev, cred->rp.id, &cred->excl.ptr[i], + &found, ms)) != FIDO_OK) { + fido_log_debug("%s: key_lookup", __func__); + return (r); + } + if (found) { + if ((r = send_dummy_register(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: send_dummy_register", + __func__); + return (r); + } + return (FIDO_ERR_CREDENTIAL_EXCLUDED); + } + } + + memset(&rp_id_hash, 0, sizeof(rp_id_hash)); + + if (SHA256((const void *)cred->rp.id, strlen(cred->rp.id), + rp_id_hash) != rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * + SHA256_DIGEST_LENGTH)) == NULL || + iso7816_add(apdu, cred->cdh.ptr, cred->cdh.len) < 0 || + iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply, + FIDO_MAXMSG, ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (delay_ms(U2F_PACE_MS, ms) != 0) { + fido_log_debug("%s: delay_ms", __func__); + r = FIDO_ERR_RX; + goto fail; + } + } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); + + if ((r = parse_register_reply(cred, reply, + (size_t)reply_len)) != FIDO_OK) { + fido_log_debug("%s: parse_register_reply", __func__); + goto fail; + } +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + return (r); +} + +static int +u2f_authenticate_single(fido_dev_t *dev, const fido_blob_t *key_id, + fido_assert_t *fa, size_t idx, int *ms) +{ + fido_blob_t sig; + fido_blob_t ad; + int found; + int r; + + memset(&sig, 0, sizeof(sig)); + memset(&ad, 0, sizeof(ad)); + + if ((r = key_lookup(dev, fa->rp_id, key_id, &found, ms)) != FIDO_OK) { + fido_log_debug("%s: key_lookup", __func__); + goto fail; + } + + if (!found) { + fido_log_debug("%s: not found", __func__); + r = FIDO_ERR_CREDENTIAL_EXCLUDED; + goto fail; + } + + if (fido_blob_set(&fa->stmt[idx].id, key_id->ptr, key_id->len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (fa->up == FIDO_OPT_FALSE) { + fido_log_debug("%s: checking for key existence only", __func__); + r = FIDO_ERR_USER_PRESENCE_REQUIRED; + goto fail; + } + + if ((r = do_auth(dev, &fa->cdh, fa->rp_id, key_id, &sig, &ad, + ms)) != FIDO_OK) { + fido_log_debug("%s: do_auth", __func__); + goto fail; + } + + if (fido_assert_set_authdata(fa, idx, ad.ptr, ad.len) != FIDO_OK || + fido_assert_set_sig(fa, idx, sig.ptr, sig.len) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_reset(&sig); + fido_blob_reset(&ad); + + return (r); +} + +int +u2f_authenticate(fido_dev_t *dev, fido_assert_t *fa, int *ms) +{ + size_t nfound = 0; + size_t nauth_ok = 0; + int r; + + if (fa->uv == FIDO_OPT_TRUE || fa->allow_list.ptr == NULL) { + fido_log_debug("%s: uv=%d, allow_list=%p", __func__, fa->uv, + (void *)fa->allow_list.ptr); + return (FIDO_ERR_UNSUPPORTED_OPTION); + } + + if ((r = fido_assert_set_count(fa, fa->allow_list.len)) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_count", __func__); + return (r); + } + + for (size_t i = 0; i < fa->allow_list.len; i++) { + switch ((r = u2f_authenticate_single(dev, + &fa->allow_list.ptr[i], fa, nfound, ms))) { + case FIDO_OK: + nauth_ok++; + FALLTHROUGH + case FIDO_ERR_USER_PRESENCE_REQUIRED: + nfound++; + break; + default: + if (r != FIDO_ERR_CREDENTIAL_EXCLUDED) { + fido_log_debug("%s: u2f_authenticate_single", + __func__); + return (r); + } + /* ignore credentials that don't exist */ + } + } + + fa->stmt_len = nfound; + + if (nfound == 0) + return (FIDO_ERR_NO_CREDENTIALS); + if (nauth_ok == 0) + return (FIDO_ERR_USER_PRESENCE_REQUIRED); + + return (FIDO_OK); +} + +int +u2f_get_touch_begin(fido_dev_t *dev, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + const char *clientdata = FIDO_DUMMY_CLIENTDATA; + const char *rp_id = FIDO_DUMMY_RP_ID; + unsigned char *reply = NULL; + unsigned char clientdata_hash[SHA256_DIGEST_LENGTH]; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + int r; + + memset(&clientdata_hash, 0, sizeof(clientdata_hash)); + memset(&rp_id_hash, 0, sizeof(rp_id_hash)); + + if (SHA256((const void *)clientdata, strlen(clientdata), + clientdata_hash) != clientdata_hash || SHA256((const void *)rp_id, + strlen(rp_id), rp_id_hash) != rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * + SHA256_DIGEST_LENGTH)) == NULL || + iso7816_add(apdu, clientdata_hash, sizeof(clientdata_hash)) < 0 || + iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (dev->attr.flags & FIDO_CAP_WINK) { + fido_tx(dev, CTAP_CMD_WINK, NULL, 0, ms); + fido_rx(dev, CTAP_CMD_WINK, reply, FIDO_MAXMSG, ms); + } + + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + return (r); +} + +int +u2f_get_touch_status(fido_dev_t *dev, int *touched, int *ms) +{ + unsigned char *reply; + int reply_len; + int r; + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, + ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_OK; /* ignore */ + goto out; + } + + switch ((reply[reply_len - 2] << 8) | reply[reply_len - 1]) { + case SW_CONDITIONS_NOT_SATISFIED: + if ((r = u2f_get_touch_begin(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: u2f_get_touch_begin", __func__); + goto out; + } + *touched = 0; + break; + case SW_NO_ERROR: + *touched = 1; + break; + default: + fido_log_debug("%s: unexpected sw", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = FIDO_OK; +out: + freezero(reply, FIDO_MAXMSG); + + return (r); +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..25281bb --- /dev/null +++ b/src/util.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> + +#include "fido.h" + +int +fido_to_uint64(const char *str, int base, uint64_t *out) +{ + char *ep; + unsigned long long ull; + + errno = 0; + ull = strtoull(str, &ep, base); + if (str == ep || *ep != '\0') + return -1; + else if (ull == ULLONG_MAX && errno == ERANGE) + return -1; + else if (ull > UINT64_MAX) + return -1; + *out = (uint64_t)ull; + + return 0; +} diff --git a/src/webauthn.h b/src/webauthn.h new file mode 100644 index 0000000..153609b --- /dev/null +++ b/src/webauthn.h @@ -0,0 +1,1149 @@ +// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#ifndef __WEBAUTHN_H_
+#define __WEBAUTHN_H_
+
+#pragma once
+
+#include <winapifamily.h>
+
+#ifdef _MSC_VER
+#pragma region Desktop Family or OneCore Family
+#endif
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef WINAPI
+#define WINAPI __stdcall
+#endif
+
+#ifndef INITGUID
+#define INITGUID
+#include <guiddef.h>
+#undef INITGUID
+#else
+#include <guiddef.h>
+#endif
+
+//+------------------------------------------------------------------------------------------
+// API Version Information.
+// Caller should check for WebAuthNGetApiVersionNumber to check the presence of relevant APIs
+// and features for their usage.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_API_VERSION_1 1
+// WEBAUTHN_API_VERSION_1 : Baseline Version
+// Data Structures and their sub versions:
+// - WEBAUTHN_RP_ENTITY_INFORMATION : 1
+// - WEBAUTHN_USER_ENTITY_INFORMATION : 1
+// - WEBAUTHN_CLIENT_DATA : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETER : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETERS : Not Applicable
+// - WEBAUTHN_CREDENTIAL : 1
+// - WEBAUTHN_CREDENTIALS : Not Applicable
+// - WEBAUTHN_CREDENTIAL_EX : 1
+// - WEBAUTHN_CREDENTIAL_LIST : Not Applicable
+// - WEBAUTHN_EXTENSION : Not Applicable
+// - WEBAUTHN_EXTENSIONS : Not Applicable
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 3
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 4
+// - WEBAUTHN_COMMON_ATTESTATION : 1
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 3
+// - WEBAUTHN_ASSERTION : 1
+// Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// APIs:
+// - WebAuthNGetApiVersionNumber
+// - WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable
+// - WebAuthNAuthenticatorMakeCredential
+// - WebAuthNAuthenticatorGetAssertion
+// - WebAuthNFreeCredentialAttestation
+// - WebAuthNFreeAssertion
+// - WebAuthNGetCancellationId
+// - WebAuthNCancelCurrentOperation
+// - WebAuthNGetErrorName
+// - WebAuthNGetW3CExceptionDOMError
+// Transports:
+// - WEBAUTHN_CTAP_TRANSPORT_USB
+// - WEBAUTHN_CTAP_TRANSPORT_NFC
+// - WEBAUTHN_CTAP_TRANSPORT_BLE
+// - WEBAUTHN_CTAP_TRANSPORT_INTERNAL
+
+#define WEBAUTHN_API_VERSION_2 2
+// WEBAUTHN_API_VERSION_2 : Delta From WEBAUTHN_API_VERSION_1
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+//
+
+#define WEBAUTHN_API_VERSION_3 3
+// WEBAUTHN_API_VERSION_3 : Delta From WEBAUTHN_API_VERSION_2
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 4
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 5
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 4
+// - WEBAUTHN_ASSERTION : 2
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+//
+
+#define WEBAUTHN_API_VERSION_4 4
+// WEBAUTHN_API_VERSION_4 : Delta From WEBAUTHN_API_VERSION_3
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 5
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 6
+// - WEBAUTHN_ASSERTION : 3
+// - WEBAUTHN_CREDENTIAL_DETAILS : 1
+// APIs:
+// - WebAuthNGetPlatformCredentialList
+// - WebAuthNFreePlatformCredentialList
+// - WebAuthNDeletePlatformCredential
+//
+
+#define WEBAUTHN_API_VERSION_5 5
+// WEBAUTHN_API_VERSION_5 : Delta From WEBAUTHN_API_VERSION_4
+// Data Structures and their sub versions:
+// - WEBAUTHN_CREDENTIAL_DETAILS : 2
+// Extension Changes:
+// - Enabled LARGE_BLOB Support
+//
+
+#define WEBAUTHN_API_VERSION_6 6
+// WEBAUTHN_API_VERSION_6 : Delta From WEBAUTHN_API_VERSION_5
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 6
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 5
+// - WEBAUTHN_ASSERTION : 4
+// Transports:
+// - WEBAUTHN_CTAP_TRANSPORT_HYBRID
+
+#define WEBAUTHN_API_VERSION_7 7
+// WEBAUTHN_API_VERSION_7 : Delta From WEBAUTHN_API_VERSION_6
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 7
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 7
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 6
+// - WEBAUTHN_ASSERTION : 5
+
+#define WEBAUTHN_API_CURRENT_VERSION WEBAUTHN_API_VERSION_7
+
+//+------------------------------------------------------------------------------------------
+// Information about an RP Entity
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_RP_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the RP. This field is required.
+ PCWSTR pwszId;
+
+ // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site".
+ // This field is required.
+ PCWSTR pwszName;
+
+ // Optional URL pointing to RP's logo.
+ PCWSTR pwszIcon;
+} WEBAUTHN_RP_ENTITY_INFORMATION, *PWEBAUTHN_RP_ENTITY_INFORMATION;
+typedef const WEBAUTHN_RP_ENTITY_INFORMATION *PCWEBAUTHN_RP_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about an User Entity
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_MAX_USER_ID_LENGTH 64
+
+#define WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_USER_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the User. This field is required.
+ DWORD cbId;
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Contains a detailed name for this account, such as "john.p.smith@example.com".
+ PCWSTR pwszName;
+
+ // Optional URL that can be used to retrieve an image containing the user's current avatar,
+ // or a data URI that contains the image data.
+ PCWSTR pwszIcon;
+
+ // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith".
+ PCWSTR pwszDisplayName;
+} WEBAUTHN_USER_ENTITY_INFORMATION, *PWEBAUTHN_USER_ENTITY_INFORMATION;
+typedef const WEBAUTHN_USER_ENTITY_INFORMATION *PCWEBAUTHN_USER_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about client data.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_HASH_ALGORITHM_SHA_256 L"SHA-256"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_384 L"SHA-384"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_512 L"SHA-512"
+
+#define WEBAUTHN_CLIENT_DATA_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CLIENT_DATA {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Size of the pbClientDataJSON field.
+ DWORD cbClientDataJSON;
+ // UTF-8 encoded JSON serialization of the client data.
+ _Field_size_bytes_(cbClientDataJSON)
+ PBYTE pbClientDataJSON;
+
+ // Hash algorithm ID used to hash the pbClientDataJSON field.
+ LPCWSTR pwszHashAlgId;
+} WEBAUTHN_CLIENT_DATA, *PWEBAUTHN_CLIENT_DATA;
+typedef const WEBAUTHN_CLIENT_DATA *PCWEBAUTHN_CLIENT_DATA;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential parameters.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY L"public-key"
+
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256 -7
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384 -35
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P521_WITH_SHA512 -36
+
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256 -257
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA384 -258
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA512 -259
+
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA256 -37
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA384 -38
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA512 -39
+
+#define WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Well-known credential type specifying a credential to create.
+ LPCWSTR pwszCredentialType;
+
+ // Well-known COSE algorithm specifying the algorithm to use for the credential.
+ LONG lAlg;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETER, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETER *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
+ DWORD cCredentialParameters;
+ _Field_size_(cCredentialParameters)
+ PWEBAUTHN_COSE_CREDENTIAL_PARAMETER pCredentialParameters;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential.
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_CREDENTIAL_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+} WEBAUTHN_CREDENTIAL, *PWEBAUTHN_CREDENTIAL;
+typedef const WEBAUTHN_CREDENTIAL *PCWEBAUTHN_CREDENTIAL;
+
+typedef struct _WEBAUTHN_CREDENTIALS {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL pCredentials;
+} WEBAUTHN_CREDENTIALS, *PWEBAUTHN_CREDENTIALS;
+typedef const WEBAUTHN_CREDENTIALS *PCWEBAUTHN_CREDENTIALS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential with extra information, such as, dwTransports
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_TRANSPORT_USB 0x00000001
+#define WEBAUTHN_CTAP_TRANSPORT_NFC 0x00000002
+#define WEBAUTHN_CTAP_TRANSPORT_BLE 0x00000004
+#define WEBAUTHN_CTAP_TRANSPORT_TEST 0x00000008
+#define WEBAUTHN_CTAP_TRANSPORT_INTERNAL 0x00000010
+#define WEBAUTHN_CTAP_TRANSPORT_HYBRID 0x00000020
+#define WEBAUTHN_CTAP_TRANSPORT_FLAGS_MASK 0x0000003F
+
+#define WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL_EX {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+
+ // Transports. 0 implies no transport restrictions.
+ DWORD dwTransports;
+} WEBAUTHN_CREDENTIAL_EX, *PWEBAUTHN_CREDENTIAL_EX;
+typedef const WEBAUTHN_CREDENTIAL_EX *PCWEBAUTHN_CREDENTIAL_EX;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential list with extra information
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CREDENTIAL_LIST {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL_EX *ppCredentials;
+} WEBAUTHN_CREDENTIAL_LIST, *PWEBAUTHN_CREDENTIAL_LIST;
+typedef const WEBAUTHN_CREDENTIAL_LIST *PCWEBAUTHN_CREDENTIAL_LIST;
+
+//+------------------------------------------------------------------------------------------
+// Information about linked devices
+//-------------------------------------------------------------------------------------------
+
+#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1 1
+#define CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_CURRENT_VERSION CTAPCBOR_HYBRID_STORAGE_LINKED_DATA_VERSION_1
+
+typedef struct _CTAPCBOR_HYBRID_STORAGE_LINKED_DATA
+{
+ // Version
+ DWORD dwVersion;
+
+ // Contact Id
+ DWORD cbContactId;
+ _Field_size_bytes_(cbContactId)
+ PBYTE pbContactId;
+
+ // Link Id
+ DWORD cbLinkId;
+ _Field_size_bytes_(cbLinkId)
+ PBYTE pbLinkId;
+
+ // Link secret
+ DWORD cbLinkSecret;
+ _Field_size_bytes_(cbLinkSecret)
+ PBYTE pbLinkSecret;
+
+ // Authenticator Public Key
+ DWORD cbPublicKey;
+ _Field_size_bytes_(cbPublicKey)
+ PBYTE pbPublicKey;
+
+ // Authenticator Name
+ PCWSTR pwszAuthenticatorName;
+
+ // Tunnel server domain
+ WORD wEncodedTunnelServerDomain;
+} CTAPCBOR_HYBRID_STORAGE_LINKED_DATA, *PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA;
+typedef const CTAPCBOR_HYBRID_STORAGE_LINKED_DATA *PCCTAPCBOR_HYBRID_STORAGE_LINKED_DATA;
+
+//+------------------------------------------------------------------------------------------
+// Credential Information for WebAuthNGetPlatformCredentialList API
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2 2
+#define WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbCredentialID.
+ DWORD cbCredentialID;
+ _Field_size_bytes_(cbCredentialID)
+ PBYTE pbCredentialID;
+
+ // RP Info
+ PWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation;
+
+ // User Info
+ PWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation;
+
+ // Removable or not.
+ BOOL bRemovable;
+
+ //
+ // The following fields have been added in WEBAUTHN_CREDENTIAL_DETAILS_VERSION_2
+ //
+
+ // Backed Up or not.
+ BOOL bBackedUp;
+} WEBAUTHN_CREDENTIAL_DETAILS, *PWEBAUTHN_CREDENTIAL_DETAILS;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS *PCWEBAUTHN_CREDENTIAL_DETAILS;
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS_LIST {
+ DWORD cCredentialDetails;
+ _Field_size_(cCredentialDetails)
+ PWEBAUTHN_CREDENTIAL_DETAILS *ppCredentialDetails;
+} WEBAUTHN_CREDENTIAL_DETAILS_LIST, *PWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS_LIST *PCWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 1
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_CURRENT_VERSION WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1
+
+typedef struct _WEBAUTHN_GET_CREDENTIALS_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Optional.
+ LPCWSTR pwszRpId;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+} WEBAUTHN_GET_CREDENTIALS_OPTIONS, *PWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+typedef const WEBAUTHN_GET_CREDENTIALS_OPTIONS *PCWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+
+//+------------------------------------------------------------------------------------------
+// PRF values.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH 32
+
+// SALT values below by default are converted into RAW Hmac-Secret values as per PRF extension.
+// - SHA-256(UTF8Encode("WebAuthn PRF") || 0x00 || Value)
+//
+// Set WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG in dwFlags in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
+// if caller wants to provide RAW Hmac-Secret SALT values directly. In that case,
+// values if provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size.
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT {
+ // Size of pbFirst.
+ DWORD cbFirst;
+ _Field_size_bytes_(cbFirst)
+ PBYTE pbFirst; // Required
+
+ // Size of pbSecond.
+ DWORD cbSecond;
+ _Field_size_bytes_(cbSecond)
+ PBYTE pbSecond;
+} WEBAUTHN_HMAC_SECRET_SALT, *PWEBAUTHN_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_HMAC_SECRET_SALT *PCWEBAUTHN_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT {
+ // Size of pbCredID.
+ DWORD cbCredID;
+ _Field_size_bytes_(cbCredID)
+ PBYTE pbCredID; // Required
+
+ // PRF Values for above credential
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecretSalt; // Required
+} WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT, *PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT *PCWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT_VALUES {
+ PWEBAUTHN_HMAC_SECRET_SALT pGlobalHmacSalt;
+
+ DWORD cCredWithHmacSecretSaltList;
+ _Field_size_(cCredWithHmacSecretSaltList)
+ PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT pCredWithHmacSecretSaltList;
+} WEBAUTHN_HMAC_SECRET_SALT_VALUES, *PWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+typedef const WEBAUTHN_HMAC_SECRET_SALT_VALUES *PCWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+
+//+------------------------------------------------------------------------------------------
+// Hmac-Secret extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET L"hmac-secret"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credential
+// was successfully created with HMAC_SECRET.
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credProtect extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_USER_VERIFICATION_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL 1
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIRED 3
+
+typedef struct _WEBAUTHN_CRED_PROTECT_EXTENSION_IN {
+ // One of the above WEBAUTHN_USER_VERIFICATION_* values
+ DWORD dwCredProtect;
+ // Set the following to TRUE to require authenticator support for the credProtect extension
+ BOOL bRequireCredProtect;
+} WEBAUTHN_CRED_PROTECT_EXTENSION_IN, *PWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+typedef const WEBAUTHN_CRED_PROTECT_EXTENSION_IN *PCWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT L"credProtect"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+// MakeCredential Input Type: WEBAUTHN_CRED_PROTECT_EXTENSION_IN.
+// - pvExtension must point to a WEBAUTHN_CRED_PROTECT_EXTENSION_IN struct
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with one of the above WEBAUTHN_USER_VERIFICATION_* values
+// if credential was successfully created with CRED_PROTECT.
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credBlob extension
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CRED_BLOB_EXTENSION {
+ // Size of pbCredBlob.
+ DWORD cbCredBlob;
+ _Field_size_bytes_(cbCredBlob)
+ PBYTE pbCredBlob;
+} WEBAUTHN_CRED_BLOB_EXTENSION, *PWEBAUTHN_CRED_BLOB_EXTENSION;
+typedef const WEBAUTHN_CRED_BLOB_EXTENSION *PCWEBAUTHN_CRED_BLOB_EXTENSION;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB L"credBlob"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// MakeCredential Input Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension must point to a WEBAUTHN_CRED_BLOB_EXTENSION struct
+// - cbExtension must contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credBlob was successfully created
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the credBlob.
+// - cbExtension must contain the sizeof(BOOL).
+// GetAssertion Output Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension will point to a WEBAUTHN_CRED_BLOB_EXTENSION struct if the authenticator
+// returns the credBlob in the signed extensions
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+
+//+------------------------------------------------------------------------------------------
+// minPinLength extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH L"minPinLength"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the minPinLength.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with the minimum pin length if returned by the authenticator
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// Information about Extensions.
+//-------------------------------------------------------------------------------------------
+typedef struct _WEBAUTHN_EXTENSION {
+ LPCWSTR pwszExtensionIdentifier;
+ DWORD cbExtension;
+ PVOID pvExtension;
+} WEBAUTHN_EXTENSION, *PWEBAUTHN_EXTENSION;
+typedef const WEBAUTHN_EXTENSION *PCWEBAUTHN_EXTENSION;
+
+typedef struct _WEBAUTHN_EXTENSIONS {
+ DWORD cExtensions;
+ _Field_size_(cExtensions)
+ PWEBAUTHN_EXTENSION pExtensions;
+} WEBAUTHN_EXTENSIONS, *PWEBAUTHN_EXTENSIONS;
+typedef const WEBAUTHN_EXTENSIONS *PCWEBAUTHN_EXTENSIONS;
+
+//+------------------------------------------------------------------------------------------
+// Options.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY 0
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM 1
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM 2
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2 3
+
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED 1
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED 3
+
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY 0
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE 1
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT 2
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT 3
+
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_NONE 0
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED 1
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED 2
+
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_NONE 0
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED 1
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED 2
+
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6 6
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7 7
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Credentials used for exclusion.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // Optional. Require key to be resident or not. Defaulting to FALSE.
+ BOOL bRequireResidentKey;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Attestation Conveyance Preference.
+ DWORD dwAttestationConveyancePreference;
+
+ // Reserved for future Use
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3
+ //
+
+ // Exclude Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pExcludeCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4
+ //
+
+ // Enterprise Attestation
+ DWORD dwEnterpriseAttestation;
+
+ // Large Blob Support: none, required or preferred
+ //
+ // NTE_INVALID_PARAMETER when large blob required or preferred and
+ // bRequireResidentKey isn't set to TRUE
+ DWORD dwLargeBlobSupport;
+
+ // Optional. Prefer key to be resident. Defaulting to FALSE. When TRUE,
+ // overrides the above bRequireResidentKey.
+ BOOL bPreferResidentKey;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5
+ //
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_6
+ //
+
+ // Enable PRF
+ BOOL bEnablePrf;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_7
+ //
+
+ // Optional. Linked Device Connection Info.
+ PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice;
+
+ // Size of pbJsonExt
+ DWORD cbJsonExt;
+ _Field_size_bytes_(cbJsonExt)
+ PBYTE pbJsonExt;
+} WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET 1
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET 2
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_DELETE 3
+
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 6
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7 7
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7
+
+/*
+ Information about flags.
+*/
+
+#define WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG 0x00100000
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Allowed Credentials List.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Flags
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2
+ //
+
+ // Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased.
+ PCWSTR pwszU2fAppId;
+
+ // If the following is non-NULL, then, set to TRUE if the above pwszU2fAppid was used instead of
+ // PCWSTR pwszRpId;
+ BOOL *pbU2fAppId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4
+ //
+
+ // Allow Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pAllowCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5
+ //
+
+ DWORD dwCredLargeBlobOperation;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6
+ //
+
+ // PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec.
+ PWEBAUTHN_HMAC_SECRET_SALT_VALUES pHmacSecretSaltValues;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_7
+ //
+
+ // Optional. Linked Device Connection Info.
+ PCTAPCBOR_HYBRID_STORAGE_LINKED_DATA pLinkedDevice;
+
+ // Optional. Allowlist MUST contain 1 credential applicable for Hybrid transport.
+ BOOL bAutoFill;
+
+ // Size of pbJsonExt
+ DWORD cbJsonExt;
+ _Field_size_bytes_(cbJsonExt)
+ PBYTE pbJsonExt;
+} WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+
+
+//+------------------------------------------------------------------------------------------
+// Attestation Info.
+//
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_ATTESTATION_DECODE_NONE 0
+#define WEBAUTHN_ATTESTATION_DECODE_COMMON 1
+// WEBAUTHN_ATTESTATION_DECODE_COMMON supports format types
+// L"packed"
+// L"fido-u2f"
+
+#define WEBAUTHN_ATTESTATION_VER_TPM_2_0 L"2.0"
+
+typedef struct _WEBAUTHN_X5C {
+ // Length of X.509 encoded certificate
+ DWORD cbData;
+ // X.509 encoded certificate bytes
+ _Field_size_bytes_(cbData)
+ PBYTE pbData;
+} WEBAUTHN_X5C, *PWEBAUTHN_X5C;
+
+// Supports either Self or Full Basic Attestation
+
+// Note, new fields will be added to the following data structure to
+// support additional attestation format types, such as, TPM.
+// When fields are added, the dwVersion will be incremented.
+//
+// Therefore, your code must make the following check:
+// "if (dwVersion >= WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION)"
+
+#define WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COMMON_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Hash and Padding Algorithm
+ //
+ // The following won't be set for "fido-u2f" which assumes "ES256".
+ PCWSTR pwszAlg;
+ LONG lAlg; // COSE algorithm
+
+ // Signature that was generated for this attestation.
+ DWORD cbSignature;
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation.
+ // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate.
+ DWORD cX5c;
+ _Field_size_(cX5c)
+ PWEBAUTHN_X5C pX5c;
+
+ // Following are also set for tpm
+ PCWSTR pwszVer; // L"2.0"
+ DWORD cbCertInfo;
+ _Field_size_bytes_(cbCertInfo)
+ PBYTE pbCertInfo;
+ DWORD cbPubArea;
+ _Field_size_bytes_(cbPubArea)
+ PBYTE pbPubArea;
+} WEBAUTHN_COMMON_ATTESTATION, *PWEBAUTHN_COMMON_ATTESTATION;
+typedef const WEBAUTHN_COMMON_ATTESTATION *PCWEBAUTHN_COMMON_ATTESTATION;
+
+#define WEBAUTHN_ATTESTATION_TYPE_PACKED L"packed"
+#define WEBAUTHN_ATTESTATION_TYPE_U2F L"fido-u2f"
+#define WEBAUTHN_ATTESTATION_TYPE_TPM L"tpm"
+#define WEBAUTHN_ATTESTATION_TYPE_NONE L"none"
+
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 2
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 3
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 4
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5 5
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6 6
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
+
+typedef struct _WEBAUTHN_CREDENTIAL_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Attestation format type
+ PCWSTR pwszFormatType;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this credential.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of CBOR encoded attestation information
+ //0 => encoded as CBOR null value.
+ DWORD cbAttestation;
+ //Encoded CBOR attestation information
+ _Field_size_bytes_(cbAttestation)
+ PBYTE pbAttestation;
+
+ DWORD dwAttestationDecodeType;
+ // Following depends on the dwAttestationDecodeType
+ // WEBAUTHN_ATTESTATION_DECODE_NONE
+ // NULL - not able to decode the CBOR attestation information
+ // WEBAUTHN_ATTESTATION_DECODE_COMMON
+ // PWEBAUTHN_COMMON_ATTESTATION;
+ PVOID pvAttestationDecode;
+
+ // The CBOR encoded Attestation Object to be returned to the RP.
+ DWORD cbAttestationObject;
+ _Field_size_bytes_(cbAttestationObject)
+ PBYTE pbAttestationObject;
+
+ // The CredentialId bytes extracted from the Authenticator Data.
+ // Used by Edge to return to the RP.
+ DWORD cbCredentialId;
+ _Field_size_bytes_(cbCredentialId)
+ PBYTE pbCredentialId;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
+ //
+
+ // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
+ // the transport that was used.
+ DWORD dwUsedTransport;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
+ //
+
+ BOOL bEpAtt;
+ BOOL bLargeBlobSupported;
+ BOOL bResidentKey;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
+ //
+
+ BOOL bPrfEnabled;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
+ //
+
+ DWORD cbUnsignedExtensionOutputs;
+ _Field_size_bytes_(cbUnsignedExtensionOutputs)
+ PBYTE pbUnsignedExtensionOutputs;
+} WEBAUTHN_CREDENTIAL_ATTESTATION, *PWEBAUTHN_CREDENTIAL_ATTESTATION;
+typedef const WEBAUTHN_CREDENTIAL_ATTESTATION *PCWEBAUTHN_CREDENTIAL_ATTESTATION;
+
+
+//+------------------------------------------------------------------------------------------
+// authenticatorGetAssertion output.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS 1
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_SUPPORTED 2
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_DATA 3
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_PARAMETER 4
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_FOUND 5
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_MULTIPLE_CREDENTIALS 6
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_LACK_OF_SPACE 7
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_PLATFORM_ERROR 8
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_AUTHENTICATOR_ERROR 9
+
+#define WEBAUTHN_ASSERTION_VERSION_1 1
+#define WEBAUTHN_ASSERTION_VERSION_2 2
+#define WEBAUTHN_ASSERTION_VERSION_3 3
+#define WEBAUTHN_ASSERTION_VERSION_4 4
+#define WEBAUTHN_ASSERTION_VERSION_5 5
+#define WEBAUTHN_ASSERTION_CURRENT_VERSION WEBAUTHN_ASSERTION_VERSION_5
+
+typedef struct _WEBAUTHN_ASSERTION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this assertion.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of pbSignature.
+ DWORD cbSignature;
+ // Signature that was generated for this assertion.
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Credential that was used for this assertion.
+ WEBAUTHN_CREDENTIAL Credential;
+
+ // Size of User Id
+ DWORD cbUserId;
+ // UserId
+ _Field_size_bytes_(cbUserId)
+ PBYTE pbUserId;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ DWORD dwCredLargeBlobStatus;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3
+ //
+
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_4
+ //
+
+ // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
+ // the transport that was used.
+ DWORD dwUsedTransport;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_5
+ //
+
+ DWORD cbUnsignedExtensionOutputs;
+ _Field_size_bytes_(cbUnsignedExtensionOutputs)
+ PBYTE pbUnsignedExtensionOutputs;
+} WEBAUTHN_ASSERTION, *PWEBAUTHN_ASSERTION;
+typedef const WEBAUTHN_ASSERTION *PCWEBAUTHN_ASSERTION;
+
+//+------------------------------------------------------------------------------------------
+// APIs.
+//-------------------------------------------------------------------------------------------
+
+DWORD
+WINAPI
+WebAuthNGetApiVersionNumber();
+
+HRESULT
+WINAPI
+WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(
+ _Out_ BOOL *pbIsUserVerifyingPlatformAuthenticatorAvailable);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorMakeCredential(
+ _In_ HWND hWnd,
+ _In_ PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation,
+ _In_ PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation,
+ _In_ PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS pPubKeyCredParams,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS pWebAuthNMakeCredentialOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_ATTESTATION *ppWebAuthNCredentialAttestation);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorGetAssertion(
+ _In_ HWND hWnd,
+ _In_ LPCWSTR pwszRpId,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS pWebAuthNGetAssertionOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_ASSERTION *ppWebAuthNAssertion);
+
+void
+WINAPI
+WebAuthNFreeCredentialAttestation(
+ _In_opt_ PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation);
+
+void
+WINAPI
+WebAuthNFreeAssertion(
+ _In_ PWEBAUTHN_ASSERTION pWebAuthNAssertion);
+
+HRESULT
+WINAPI
+WebAuthNGetCancellationId(
+ _Out_ GUID* pCancellationId);
+
+HRESULT
+WINAPI
+WebAuthNCancelCurrentOperation(
+ _In_ const GUID* pCancellationId);
+
+// Returns NTE_NOT_FOUND when credentials are not found.
+HRESULT
+WINAPI
+WebAuthNGetPlatformCredentialList(
+ _In_ PCWEBAUTHN_GET_CREDENTIALS_OPTIONS pGetCredentialsOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST *ppCredentialDetailsList);
+
+void
+WINAPI
+WebAuthNFreePlatformCredentialList(
+ _In_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList);
+
+HRESULT
+WINAPI
+WebAuthNDeletePlatformCredential(
+ _In_ DWORD cbCredentialId,
+ _In_reads_bytes_(cbCredentialId) const BYTE *pbCredentialId
+ );
+
+//
+// Returns the following Error Names:
+// L"Success" - S_OK
+// L"InvalidStateError" - NTE_EXISTS
+// L"ConstraintError" - HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED),
+// NTE_NOT_SUPPORTED,
+// NTE_TOKEN_KEYSET_STORAGE_FULL
+// L"NotSupportedError" - NTE_INVALID_PARAMETER
+// L"NotAllowedError" - NTE_DEVICE_NOT_FOUND,
+// NTE_NOT_FOUND,
+// HRESULT_FROM_WIN32(ERROR_CANCELLED),
+// NTE_USER_CANCELLED,
+// HRESULT_FROM_WIN32(ERROR_TIMEOUT)
+// L"UnknownError" - All other hr values
+//
+PCWSTR
+WINAPI
+WebAuthNGetErrorName(
+ _In_ HRESULT hr);
+
+HRESULT
+WINAPI
+WebAuthNGetW3CExceptionDOMError(
+ _In_ HRESULT hr);
+
+
+#ifdef __cplusplus
+} // Balance extern "C" above
+#endif
+
+#endif // WINAPI_FAMILY_PARTITION
+#ifdef _MSC_VER
+#pragma endregion
+#endif
+
+#endif // __WEBAUTHN_H_
diff --git a/src/winhello.c b/src/winhello.c new file mode 100644 index 0000000..ff969a4 --- /dev/null +++ b/src/winhello.c @@ -0,0 +1,1075 @@ +/* + * Copyright (c) 2021-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <stdlib.h> +#include <windows.h> + +#include "fido.h" +#include "webauthn.h" + +#ifndef NTE_INVALID_PARAMETER +#define NTE_INVALID_PARAMETER _HRESULT_TYPEDEF_(0x80090027) +#endif +#ifndef NTE_NOT_SUPPORTED +#define NTE_NOT_SUPPORTED _HRESULT_TYPEDEF_(0x80090029) +#endif +#ifndef NTE_DEVICE_NOT_FOUND +#define NTE_DEVICE_NOT_FOUND _HRESULT_TYPEDEF_(0x80090035) +#endif +#ifndef NTE_USER_CANCELLED +#define NTE_USER_CANCELLED _HRESULT_TYPEDEF_(0x80090036L) +#endif + +#define MAXCHARS 128 +#define MAXCREDS 128 +#define MAXMSEC 6000 * 1000 +#define VENDORID 0x045e +#define PRODID 0x0001 + +struct winhello_assert { + WEBAUTHN_CLIENT_DATA cd; + WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS opt; + WEBAUTHN_ASSERTION *assert; + wchar_t *rp_id; + wchar_t *appid; +}; + +struct winhello_cred { + WEBAUTHN_RP_ENTITY_INFORMATION rp; + WEBAUTHN_USER_ENTITY_INFORMATION user; + WEBAUTHN_COSE_CREDENTIAL_PARAMETER alg; + WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose; + WEBAUTHN_CLIENT_DATA cd; + WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS opt; + WEBAUTHN_CREDENTIAL_ATTESTATION *att; + wchar_t *rp_id; + wchar_t *rp_name; + wchar_t *user_name; + wchar_t *user_icon; + wchar_t *display_name; +}; + +typedef DWORD WINAPI webauthn_get_api_version_t(void); +typedef PCWSTR WINAPI webauthn_strerr_t(HRESULT); +typedef HRESULT WINAPI webauthn_get_assert_t(HWND, LPCWSTR, + PCWEBAUTHN_CLIENT_DATA, + PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, + PWEBAUTHN_ASSERTION *); +typedef HRESULT WINAPI webauthn_make_cred_t(HWND, + PCWEBAUTHN_RP_ENTITY_INFORMATION, + PCWEBAUTHN_USER_ENTITY_INFORMATION, + PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS, + PCWEBAUTHN_CLIENT_DATA, + PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, + PWEBAUTHN_CREDENTIAL_ATTESTATION *); +typedef void WINAPI webauthn_free_assert_t(PWEBAUTHN_ASSERTION); +typedef void WINAPI webauthn_free_attest_t(PWEBAUTHN_CREDENTIAL_ATTESTATION); + +static TLS BOOL webauthn_loaded; +static TLS HMODULE webauthn_handle; +static TLS webauthn_get_api_version_t *webauthn_get_api_version; +static TLS webauthn_strerr_t *webauthn_strerr; +static TLS webauthn_get_assert_t *webauthn_get_assert; +static TLS webauthn_make_cred_t *webauthn_make_cred; +static TLS webauthn_free_assert_t *webauthn_free_assert; +static TLS webauthn_free_attest_t *webauthn_free_attest; + +static int +webauthn_load(void) +{ + DWORD n = 1; + + if (webauthn_loaded || webauthn_handle != NULL) { + fido_log_debug("%s: already loaded", __func__); + return -1; + } + if ((webauthn_handle = LoadLibrary(TEXT("webauthn.dll"))) == NULL) { + fido_log_debug("%s: LoadLibrary", __func__); + return -1; + } + + if ((webauthn_get_api_version = + (webauthn_get_api_version_t *)GetProcAddress(webauthn_handle, + "WebAuthNGetApiVersionNumber")) == NULL) { + fido_log_debug("%s: WebAuthNGetApiVersionNumber", __func__); + /* WebAuthNGetApiVersionNumber might not exist */ + } + if (webauthn_get_api_version != NULL && + (n = webauthn_get_api_version()) < 1) { + fido_log_debug("%s: unsupported api %lu", __func__, (u_long)n); + goto fail; + } + fido_log_debug("%s: api version %lu", __func__, (u_long)n); + if ((webauthn_strerr = + (webauthn_strerr_t *)GetProcAddress(webauthn_handle, + "WebAuthNGetErrorName")) == NULL) { + fido_log_debug("%s: WebAuthNGetErrorName", __func__); + goto fail; + } + if ((webauthn_get_assert = + (webauthn_get_assert_t *)GetProcAddress(webauthn_handle, + "WebAuthNAuthenticatorGetAssertion")) == NULL) { + fido_log_debug("%s: WebAuthNAuthenticatorGetAssertion", + __func__); + goto fail; + } + if ((webauthn_make_cred = + (webauthn_make_cred_t *)GetProcAddress(webauthn_handle, + "WebAuthNAuthenticatorMakeCredential")) == NULL) { + fido_log_debug("%s: WebAuthNAuthenticatorMakeCredential", + __func__); + goto fail; + } + if ((webauthn_free_assert = + (webauthn_free_assert_t *)GetProcAddress(webauthn_handle, + "WebAuthNFreeAssertion")) == NULL) { + fido_log_debug("%s: WebAuthNFreeAssertion", __func__); + goto fail; + } + if ((webauthn_free_attest = + (webauthn_free_attest_t *)GetProcAddress(webauthn_handle, + "WebAuthNFreeCredentialAttestation")) == NULL) { + fido_log_debug("%s: WebAuthNFreeCredentialAttestation", + __func__); + goto fail; + } + + webauthn_loaded = true; + + return 0; +fail: + fido_log_debug("%s: GetProcAddress", __func__); + webauthn_get_api_version = NULL; + webauthn_strerr = NULL; + webauthn_get_assert = NULL; + webauthn_make_cred = NULL; + webauthn_free_assert = NULL; + webauthn_free_attest = NULL; + FreeLibrary(webauthn_handle); + webauthn_handle = NULL; + + return -1; +} + +static wchar_t * +to_utf16(const char *utf8) +{ + int nch; + wchar_t *utf16; + + if (utf8 == NULL) { + fido_log_debug("%s: NULL", __func__); + return NULL; + } + if ((nch = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) < 1 || + (size_t)nch > MAXCHARS) { + fido_log_debug("%s: MultiByteToWideChar %d", __func__, nch); + return NULL; + } + if ((utf16 = calloc((size_t)nch, sizeof(*utf16))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return NULL; + } + if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, nch) != nch) { + fido_log_debug("%s: MultiByteToWideChar", __func__); + free(utf16); + return NULL; + } + + return utf16; +} + +static int +to_fido(HRESULT hr) +{ + switch (hr) { + case NTE_NOT_SUPPORTED: + return FIDO_ERR_UNSUPPORTED_OPTION; + case NTE_INVALID_PARAMETER: + return FIDO_ERR_INVALID_PARAMETER; + case NTE_TOKEN_KEYSET_STORAGE_FULL: + return FIDO_ERR_KEY_STORE_FULL; + case NTE_DEVICE_NOT_FOUND: + case NTE_NOT_FOUND: + return FIDO_ERR_NOT_ALLOWED; + case __HRESULT_FROM_WIN32(ERROR_CANCELLED): + case NTE_USER_CANCELLED: + return FIDO_ERR_OPERATION_DENIED; + default: + fido_log_debug("%s: hr=0x%lx", __func__, (u_long)hr); + return FIDO_ERR_INTERNAL; + } +} + +static int +pack_cd(WEBAUTHN_CLIENT_DATA *out, const fido_blob_t *in) +{ + if (in->ptr == NULL) { + fido_log_debug("%s: NULL", __func__); + return -1; + } + if (in->len > ULONG_MAX) { + fido_log_debug("%s: in->len=%zu", __func__, in->len); + return -1; + } + out->dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION; + out->cbClientDataJSON = (DWORD)in->len; + out->pbClientDataJSON = in->ptr; + out->pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256; + + return 0; +} + +static int +pack_credlist(WEBAUTHN_CREDENTIALS *out, const fido_blob_array_t *in) +{ + WEBAUTHN_CREDENTIAL *c; + + if (in->len == 0) { + return 0; /* nothing to do */ + } + if (in->len > MAXCREDS) { + fido_log_debug("%s: in->len=%zu", __func__, in->len); + return -1; + } + if ((out->pCredentials = calloc(in->len, sizeof(*c))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + out->cCredentials = (DWORD)in->len; + for (size_t i = 0; i < in->len; i++) { + if (in->ptr[i].len > ULONG_MAX) { + fido_log_debug("%s: %zu", __func__, in->ptr[i].len); + return -1; + } + c = &out->pCredentials[i]; + c->dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION; + c->cbId = (DWORD)in->ptr[i].len; + c->pbId = in->ptr[i].ptr; + c->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + } + + return 0; +} + +static int +set_cred_uv(DWORD *out, fido_opt_t uv, const char *pin) +{ + if (pin) { + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + return 0; + } + + switch (uv) { + case FIDO_OPT_OMIT: + case FIDO_OPT_FALSE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED; + break; + case FIDO_OPT_TRUE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + break; + } + + return 0; +} + +static int +set_assert_uv(DWORD *out, fido_opt_t uv, const char *pin) +{ + if (pin) { + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + return 0; + } + + switch (uv) { + case FIDO_OPT_OMIT: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED; + break; + case FIDO_OPT_FALSE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED; + break; + case FIDO_OPT_TRUE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + break; + } + + return 0; +} + +static int +pack_rp(wchar_t **id, wchar_t **name, WEBAUTHN_RP_ENTITY_INFORMATION *out, + const fido_rp_t *in) +{ + /* keep non-const copies of pwsz* for free() */ + out->dwVersion = WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION; + if ((out->pwszId = *id = to_utf16(in->id)) == NULL) { + fido_log_debug("%s: id", __func__); + return -1; + } + if (in->name && (out->pwszName = *name = to_utf16(in->name)) == NULL) { + fido_log_debug("%s: name", __func__); + return -1; + } + return 0; +} + +static int +pack_user(wchar_t **name, wchar_t **icon, wchar_t **display_name, + WEBAUTHN_USER_ENTITY_INFORMATION *out, const fido_user_t *in) +{ + if (in->id.ptr == NULL || in->id.len > ULONG_MAX) { + fido_log_debug("%s: id", __func__); + return -1; + } + out->dwVersion = WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION; + out->cbId = (DWORD)in->id.len; + out->pbId = in->id.ptr; + /* keep non-const copies of pwsz* for free() */ + if (in->name != NULL) { + if ((out->pwszName = *name = to_utf16(in->name)) == NULL) { + fido_log_debug("%s: name", __func__); + return -1; + } + } + if (in->icon != NULL) { + if ((out->pwszIcon = *icon = to_utf16(in->icon)) == NULL) { + fido_log_debug("%s: icon", __func__); + return -1; + } + } + if (in->display_name != NULL) { + if ((out->pwszDisplayName = *display_name = + to_utf16(in->display_name)) == NULL) { + fido_log_debug("%s: display_name", __func__); + return -1; + } + } + + return 0; +} + +static int +pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg, + WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *cose, int type) +{ + switch (type) { + case COSE_ES256: + alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256; + break; + case COSE_ES384: + alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384; + break; + case COSE_EDDSA: + alg->lAlg = -8; /* XXX */; + break; + case COSE_RS256: + alg->lAlg = WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256; + break; + default: + fido_log_debug("%s: type %d", __func__, type); + return -1; + } + alg->dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION; + alg->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + cose->cCredentialParameters = 1; + cose->pCredentialParameters = alg; + + return 0; +} + +static int +pack_cred_ext(WEBAUTHN_EXTENSIONS *out, const fido_cred_ext_t *in) +{ + WEBAUTHN_EXTENSION *e; + WEBAUTHN_CRED_PROTECT_EXTENSION_IN *p; + BOOL *b; + size_t n = 0, i = 0; + + if (in->mask == 0) { + return 0; /* nothing to do */ + } + if (in->mask & ~(FIDO_EXT_HMAC_SECRET | FIDO_EXT_CRED_PROTECT)) { + fido_log_debug("%s: mask 0x%x", __func__, in->mask); + return -1; + } + if (in->mask & FIDO_EXT_HMAC_SECRET) + n++; + if (in->mask & FIDO_EXT_CRED_PROTECT) + n++; + if ((out->pExtensions = calloc(n, sizeof(*e))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + out->cExtensions = (DWORD)n; + if (in->mask & FIDO_EXT_HMAC_SECRET) { + /* + * NOTE: webauthn.dll ignores requests to enable hmac-secret + * unless a discoverable credential is also requested. + */ + if ((b = calloc(1, sizeof(*b))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + *b = true; + e = &out->pExtensions[i]; + e->pwszExtensionIdentifier = + WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET; + e->pvExtension = b; + e->cbExtension = sizeof(*b); + i++; + } + if (in->mask & FIDO_EXT_CRED_PROTECT) { + if ((p = calloc(1, sizeof(*p))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + p->dwCredProtect = (DWORD)in->prot; + p->bRequireCredProtect = true; + e = &out->pExtensions[i]; + e->pwszExtensionIdentifier = + WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT; + e->pvExtension = p; + e->cbExtension = sizeof(*p); + i++; + } + + return 0; +} + +static int +pack_assert_ext(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *out, + const fido_assert_ext_t *in) +{ + WEBAUTHN_HMAC_SECRET_SALT_VALUES *v; + WEBAUTHN_HMAC_SECRET_SALT *s; + + if (in->mask == 0) { + return 0; /* nothing to do */ + } + if (in->mask != FIDO_EXT_HMAC_SECRET) { + fido_log_debug("%s: mask 0x%x", __func__, in->mask); + return -1; + } + if (in->hmac_salt.ptr == NULL || + in->hmac_salt.len != WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH) { + fido_log_debug("%s: salt %p/%zu", __func__, + (const void *)in->hmac_salt.ptr, in->hmac_salt.len); + return -1; + } + if ((v = calloc(1, sizeof(*v))) == NULL || + (s = calloc(1, sizeof(*s))) == NULL) { + free(v); + fido_log_debug("%s: calloc", __func__); + return -1; + } + s->cbFirst = (DWORD)in->hmac_salt.len; + s->pbFirst = in->hmac_salt.ptr; + v->pGlobalHmacSalt = s; + out->pHmacSecretSaltValues = v; + out->dwFlags |= WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG; + out->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6; + + return 0; +} + +static int +pack_appid(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt, const char *id, + wchar_t **appid) +{ + if (id == NULL) + return 0; /* nothing to do */ + if ((opt->pbU2fAppId = calloc(1, sizeof(*opt->pbU2fAppId))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + if ((*appid = to_utf16(id)) == NULL) { + fido_log_debug("%s: to_utf16", __func__); + return -1; + } + fido_log_debug("%s: using %s", __func__, id); + opt->pwszU2fAppId = *appid; + opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2; + + return 0; +} + +static void +unpack_appid(fido_assert_t *assert, + const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt) +{ + if (assert->appid == NULL || opt->pbU2fAppId == NULL) + return; /* nothing to do */ + if (*opt->pbU2fAppId == false) { + fido_log_debug("%s: not used", __func__); + return; + } + fido_log_debug("%s: %s -> %s", __func__, assert->rp_id, assert->appid); + free(assert->rp_id); + assert->rp_id = assert->appid; + assert->appid = NULL; +} + +static int +unpack_assert_authdata(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + int r; + + if ((r = fido_assert_set_authdata_raw(assert, 0, wa->pbAuthenticatorData, + wa->cbAuthenticatorData)) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_authdata_raw: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_assert_sig(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + int r; + + if ((r = fido_assert_set_sig(assert, 0, wa->pbSignature, + wa->cbSignature)) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_sig: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_cred_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + if (fido_blob_set(&assert->stmt[0].id, wa->Credential.pbId, + wa->Credential.cbId) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return -1; + } + + return 0; +} + +static int +unpack_user_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + if (wa->cbUserId == 0) + return 0; /* user id absent */ + if (fido_blob_set(&assert->stmt[0].user.id, wa->pbUserId, + wa->cbUserId) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return -1; + } + + return 0; +} + +static int +unpack_hmac_secret(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + if (wa->dwVersion < WEBAUTHN_ASSERTION_VERSION_3) { + fido_log_debug("%s: dwVersion %u", __func__, + (unsigned)wa->dwVersion); + return 0; /* proceed without hmac-secret */ + } + if (wa->pHmacSecret == NULL || + wa->pHmacSecret->cbFirst == 0 || + wa->pHmacSecret->pbFirst == NULL) { + fido_log_debug("%s: hmac-secret absent", __func__); + return 0; /* proceed without hmac-secret */ + } + if (wa->pHmacSecret->cbSecond != 0 || + wa->pHmacSecret->pbSecond != NULL) { + fido_log_debug("%s: 64-byte hmac-secret", __func__); + return 0; /* proceed without hmac-secret */ + } + if (!fido_blob_is_empty(&assert->stmt[0].hmac_secret)) { + fido_log_debug("%s: fido_blob_is_empty", __func__); + return -1; + } + if (fido_blob_set(&assert->stmt[0].hmac_secret, + wa->pHmacSecret->pbFirst, wa->pHmacSecret->cbFirst) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return -1; + } + + return 0; +} + +static int +translate_fido_assert(struct winhello_assert *ctx, const fido_assert_t *assert, + const char *pin, int ms) +{ + WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt; + + /* not supported by webauthn.h */ + if (assert->up == FIDO_OPT_FALSE) { + fido_log_debug("%s: up %d", __func__, assert->up); + return FIDO_ERR_UNSUPPORTED_OPTION; + } + if ((ctx->rp_id = to_utf16(assert->rp_id)) == NULL) { + fido_log_debug("%s: rp_id", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cd(&ctx->cd, &assert->cd) < 0) { + fido_log_debug("%s: pack_cd", __func__); + return FIDO_ERR_INTERNAL; + } + /* options */ + opt = &ctx->opt; + opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1; + opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms; + if (pack_appid(opt, assert->appid, &ctx->appid) < 0) { + fido_log_debug("%s: pack_appid" , __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_credlist(&opt->CredentialList, &assert->allow_list) < 0) { + fido_log_debug("%s: pack_credlist", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_assert_ext(opt, &assert->ext) < 0) { + fido_log_debug("%s: pack_assert_ext", __func__); + return FIDO_ERR_UNSUPPORTED_EXTENSION; + } + if (set_assert_uv(&opt->dwUserVerificationRequirement, assert->uv, + pin) < 0) { + fido_log_debug("%s: set_assert_uv", __func__); + return FIDO_ERR_INTERNAL; + } + + return FIDO_OK; +} + +static int +translate_winhello_assert(fido_assert_t *assert, + const struct winhello_assert *ctx) +{ + const WEBAUTHN_ASSERTION *wa = ctx->assert; + const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt = &ctx->opt; + int r; + + if (assert->stmt_len > 0) { + fido_log_debug("%s: stmt_len=%zu", __func__, assert->stmt_len); + return FIDO_ERR_INTERNAL; + } + if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_count: %s", __func__, + fido_strerr(r)); + return FIDO_ERR_INTERNAL; + } + unpack_appid(assert, opt); + if (unpack_assert_authdata(assert, wa) < 0) { + fido_log_debug("%s: unpack_assert_authdata", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_assert_sig(assert, wa) < 0) { + fido_log_debug("%s: unpack_assert_sig", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_cred_id(assert, wa) < 0) { + fido_log_debug("%s: unpack_cred_id", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_user_id(assert, wa) < 0) { + fido_log_debug("%s: unpack_user_id", __func__); + return FIDO_ERR_INTERNAL; + } + if (assert->ext.mask & FIDO_EXT_HMAC_SECRET && + unpack_hmac_secret(assert, wa) < 0) { + fido_log_debug("%s: unpack_hmac_secret", __func__); + return FIDO_ERR_INTERNAL; + } + + return FIDO_OK; +} + +static int +translate_fido_cred(struct winhello_cred *ctx, const fido_cred_t *cred, + const char *pin, int ms) +{ + WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *opt; + + if (pack_rp(&ctx->rp_id, &ctx->rp_name, &ctx->rp, &cred->rp) < 0) { + fido_log_debug("%s: pack_rp", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_user(&ctx->user_name, &ctx->user_icon, &ctx->display_name, + &ctx->user, &cred->user) < 0) { + fido_log_debug("%s: pack_user", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cose(&ctx->alg, &ctx->cose, cred->type) < 0) { + fido_log_debug("%s: pack_cose", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cd(&ctx->cd, &cred->cd) < 0) { + fido_log_debug("%s: pack_cd", __func__); + return FIDO_ERR_INTERNAL; + } + /* options */ + opt = &ctx->opt; + opt->dwVersion = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1; + opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms; + opt->dwAttestationConveyancePreference = + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT; + if (pack_credlist(&opt->CredentialList, &cred->excl) < 0) { + fido_log_debug("%s: pack_credlist", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cred_ext(&opt->Extensions, &cred->ext) < 0) { + fido_log_debug("%s: pack_cred_ext", __func__); + return FIDO_ERR_UNSUPPORTED_EXTENSION; + } + if (set_cred_uv(&opt->dwUserVerificationRequirement, (cred->ext.mask & + FIDO_EXT_CRED_PROTECT) ? FIDO_OPT_TRUE : cred->uv, pin) < 0) { + fido_log_debug("%s: set_cred_uv", __func__); + return FIDO_ERR_INTERNAL; + } + if (cred->rk == FIDO_OPT_TRUE) { + opt->bRequireResidentKey = true; + } + + return FIDO_OK; +} + +static int +decode_attobj(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_t *cred = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto fail; + } + + if (!strcmp(name, "fmt")) { + if (cbor_decode_fmt(val, &cred->fmt) < 0) { + fido_log_debug("%s: cbor_decode_fmt", __func__); + goto fail; + } + } else if (!strcmp(name, "attStmt")) { + if (cbor_decode_attstmt(val, &cred->attstmt) < 0) { + fido_log_debug("%s: cbor_decode_attstmt", __func__); + goto fail; + } + } else if (!strcmp(name, "authData")) { + if (fido_blob_decode(val, &cred->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto fail; + } + if (cbor_decode_cred_authdata(val, cred->type, + &cred->authdata_cbor, &cred->authdata, &cred->attcred, + &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata", + __func__); + goto fail; + } + } + + ok = 0; +fail: + free(name); + + return (ok); +} + +static int +translate_winhello_cred(fido_cred_t *cred, + const WEBAUTHN_CREDENTIAL_ATTESTATION *att) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int r = FIDO_ERR_INTERNAL; + + if (att->pbAttestationObject == NULL) { + fido_log_debug("%s: pbAttestationObject", __func__); + goto fail; + } + if ((item = cbor_load(att->pbAttestationObject, + att->cbAttestationObject, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, cred, decode_attobj) < 0) { + fido_log_debug("%s: cbor type", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + return r; +} + +static int +winhello_get_assert(HWND w, struct winhello_assert *ctx) +{ + HRESULT hr; + int r = FIDO_OK; + + if ((hr = webauthn_get_assert(w, ctx->rp_id, &ctx->cd, &ctx->opt, + &ctx->assert)) != S_OK) { + r = to_fido(hr); + fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr), + fido_strerr(r)); + } + + return r; +} + +static int +winhello_make_cred(HWND w, struct winhello_cred *ctx) +{ + HRESULT hr; + int r = FIDO_OK; + + if ((hr = webauthn_make_cred(w, &ctx->rp, &ctx->user, &ctx->cose, + &ctx->cd, &ctx->opt, &ctx->att)) != S_OK) { + r = to_fido(hr); + fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr), + fido_strerr(r)); + } + + return r; +} + +static void +winhello_assert_free(struct winhello_assert *ctx) +{ + if (ctx == NULL) + return; + if (ctx->assert != NULL) + webauthn_free_assert(ctx->assert); + + free(ctx->rp_id); + free(ctx->appid); + free(ctx->opt.CredentialList.pCredentials); + if (ctx->opt.pHmacSecretSaltValues != NULL) + free(ctx->opt.pHmacSecretSaltValues->pGlobalHmacSalt); + free(ctx->opt.pHmacSecretSaltValues); + free(ctx); +} + +static void +winhello_cred_free(struct winhello_cred *ctx) +{ + if (ctx == NULL) + return; + if (ctx->att != NULL) + webauthn_free_attest(ctx->att); + + free(ctx->rp_id); + free(ctx->rp_name); + free(ctx->user_name); + free(ctx->user_icon); + free(ctx->display_name); + free(ctx->opt.CredentialList.pCredentials); + for (size_t i = 0; i < ctx->opt.Extensions.cExtensions; i++) { + WEBAUTHN_EXTENSION *e; + e = &ctx->opt.Extensions.pExtensions[i]; + free(e->pvExtension); + } + free(ctx->opt.Extensions.pExtensions); + free(ctx); +} + +int +fido_winhello_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + fido_dev_info_t *di; + + if (ilen == 0) { + return FIDO_OK; + } + if (devlist == NULL) { + return FIDO_ERR_INVALID_ARGUMENT; + } + if (!webauthn_loaded && webauthn_load() < 0) { + fido_log_debug("%s: webauthn_load", __func__); + return FIDO_OK; /* not an error */ + } + + di = &devlist[*olen]; + memset(di, 0, sizeof(*di)); + di->path = strdup(FIDO_WINHELLO_PATH); + di->manufacturer = strdup("Microsoft Corporation"); + di->product = strdup("Windows Hello"); + di->vendor_id = VENDORID; + di->product_id = PRODID; + if (di->path == NULL || di->manufacturer == NULL || + di->product == NULL) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + return FIDO_ERR_INTERNAL; + } + ++(*olen); + + return FIDO_OK; +} + +int +fido_winhello_open(fido_dev_t *dev) +{ + if (!webauthn_loaded && webauthn_load() < 0) { + fido_log_debug("%s: webauthn_load", __func__); + return FIDO_ERR_INTERNAL; + } + if (dev->flags != 0) + return FIDO_ERR_INVALID_ARGUMENT; + dev->attr.flags = FIDO_CAP_CBOR | FIDO_CAP_WINK; + dev->flags = FIDO_DEV_WINHELLO | FIDO_DEV_CRED_PROT | FIDO_DEV_PIN_SET; + + return FIDO_OK; +} + +int +fido_winhello_close(fido_dev_t *dev) +{ + memset(dev, 0, sizeof(*dev)); + + return FIDO_OK; +} + +int +fido_winhello_cancel(fido_dev_t *dev) +{ + (void)dev; + + return FIDO_ERR_INTERNAL; +} + +int +fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert, + const char *pin, int ms) +{ + HWND w; + struct winhello_assert *ctx; + int r = FIDO_ERR_INTERNAL; + + (void)dev; + + fido_assert_reset_rx(assert); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((w = GetForegroundWindow()) == NULL) { + fido_log_debug("%s: GetForegroundWindow", __func__); + if ((w = GetTopWindow(NULL)) == NULL) { + fido_log_debug("%s: GetTopWindow", __func__); + goto fail; + } + } + if ((r = translate_fido_assert(ctx, assert, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: translate_fido_assert", __func__); + goto fail; + } + if ((r = winhello_get_assert(w, ctx)) != FIDO_OK) { + fido_log_debug("%s: winhello_get_assert", __func__); + goto fail; + } + if ((r = translate_winhello_assert(assert, ctx)) != FIDO_OK) { + fido_log_debug("%s: translate_winhello_assert", __func__); + goto fail; + } + +fail: + winhello_assert_free(ctx); + + return r; +} + +int +fido_winhello_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci) +{ + const char *v[3] = { "U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE" }; + const char *e[2] = { "credProtect", "hmac-secret" }; + const char *t[2] = { "nfc", "usb" }; + const char *o[4] = { "rk", "up", "uv", "plat" }; + + (void)dev; + + fido_cbor_info_reset(ci); + + if (fido_str_array_pack(&ci->versions, v, nitems(v)) < 0 || + fido_str_array_pack(&ci->extensions, e, nitems(e)) < 0 || + fido_str_array_pack(&ci->transports, t, nitems(t)) < 0) { + fido_log_debug("%s: fido_str_array_pack", __func__); + return FIDO_ERR_INTERNAL; + } + if ((ci->options.name = calloc(nitems(o), sizeof(char *))) == NULL || + (ci->options.value = calloc(nitems(o), sizeof(bool))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return FIDO_ERR_INTERNAL; + } + for (size_t i = 0; i < nitems(o); i++) { + if ((ci->options.name[i] = strdup(o[i])) == NULL) { + fido_log_debug("%s: strdup", __func__); + return FIDO_ERR_INTERNAL; + } + ci->options.value[i] = true; + ci->options.len++; + } + + return FIDO_OK; +} + +int +fido_winhello_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin, + int ms) +{ + HWND w; + struct winhello_cred *ctx; + int r = FIDO_ERR_INTERNAL; + + (void)dev; + + fido_cred_reset_rx(cred); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((w = GetForegroundWindow()) == NULL) { + fido_log_debug("%s: GetForegroundWindow", __func__); + if ((w = GetTopWindow(NULL)) == NULL) { + fido_log_debug("%s: GetTopWindow", __func__); + goto fail; + } + } + if ((r = translate_fido_cred(ctx, cred, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: translate_fido_cred", __func__); + goto fail; + } + if ((r = winhello_make_cred(w, ctx)) != FIDO_OK) { + fido_log_debug("%s: winhello_make_cred", __func__); + goto fail; + } + if ((r = translate_winhello_cred(cred, ctx->att)) != FIDO_OK) { + fido_log_debug("%s: translate_winhello_cred", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + winhello_cred_free(ctx); + + return r; +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..e1f4366 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,74 @@ +# Copyright (c) 2018-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/bsd-getpagesize.c + ../openbsd-compat/explicit_bzero.c + ../openbsd-compat/freezero.c + ../openbsd-compat/recallocarray.c + ../openbsd-compat/strlcat.c + ../openbsd-compat/strlcpy.c + ../openbsd-compat/strsep.c +) + +if(WIN32 AND NOT CYGWIN AND NOT MSYS) + list(APPEND COMPAT_SOURCES + ../openbsd-compat/bsd-getline.c + ../openbsd-compat/endian_win32.c + ../openbsd-compat/explicit_bzero_win32.c + ../openbsd-compat/getopt_long.c + ../openbsd-compat/readpassphrase_win32.c + ) + if (BUILD_SHARED_LIBS) + list(APPEND COMPAT_SOURCES ../openbsd-compat/posix_win.c) + endif() +else() + list(APPEND COMPAT_SOURCES ../openbsd-compat/readpassphrase.c) +endif() + +if(NOT MSVC) + set_source_files_properties(assert_get.c assert_verify.c base64.c bio.c + config.c cred_make.c cred_verify.c credman.c fido2-assert.c + fido2-cred.c fido2-token.c pin.c token.c util.c + PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS}") +endif() + +add_executable(fido2-cred + fido2-cred.c + cred_make.c + cred_verify.c + base64.c + util.c + ${COMPAT_SOURCES} +) + +add_executable(fido2-assert + fido2-assert.c + assert_get.c + assert_verify.c + base64.c + util.c + ${COMPAT_SOURCES} +) + +add_executable(fido2-token + fido2-token.c + base64.c + bio.c + config.c + credman.c + largeblob.c + pin.c + token.c + util.c + ${COMPAT_SOURCES} +) + +target_link_libraries(fido2-cred ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY}) +target_link_libraries(fido2-assert ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY}) +target_link_libraries(fido2-token ${CRYPTO_LIBRARIES} ${_FIDO2_LIBRARY}) + +install(TARGETS fido2-cred fido2-assert fido2-token + DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/tools/assert_get.c b/tools/assert_get.c new file mode 100644 index 0000000..32d40b1 --- /dev/null +++ b/tools/assert_get.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +struct toggle { + fido_opt_t up; + fido_opt_t uv; + fido_opt_t pin; +}; + +static const char * +opt2str(fido_opt_t v) +{ + switch (v) { + case FIDO_OPT_OMIT: + return "omit"; + case FIDO_OPT_TRUE: + return "true"; + case FIDO_OPT_FALSE: + return "false"; + default: + return "unknown"; + } +} + +static void +parse_toggle(const char *str, struct toggle *opt) +{ + fido_opt_t *k; + fido_opt_t v; + char *assignment; + char *key; + char *val; + + if ((assignment = strdup(str)) == NULL) + err(1, "strdup"); + if ((val = strchr(assignment, '=')) == NULL) + errx(1, "invalid assignment '%s'", assignment); + + key = assignment; + *val++ = '\0'; + + if (!strcmp(val, "true")) + v = FIDO_OPT_TRUE; + else if (!strcmp(val, "false")) + v = FIDO_OPT_FALSE; + else + errx(1, "unknown value '%s'", val); + + if (!strcmp(key, "up")) + k = &opt->up; + else if (!strcmp(key, "uv")) + k = &opt->uv; + else if (!strcmp(key, "pin")) + k = &opt->pin; + else + errx(1, "unknown key '%s'", key); + + free(assignment); + + *k = v; +} + +static fido_assert_t * +prepare_assert(FILE *in_f, int flags, const struct toggle *opt) +{ + fido_assert_t *assert = NULL; + struct blob cdh; + struct blob id; + struct blob hmac_salt; + char *rpid = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&id, 0, sizeof(id)); + memset(&hmac_salt, 0, sizeof(hmac_salt)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + if ((flags & FLAG_RK) == 0) + r |= base64_read(in_f, &id); + if (flags & FLAG_HMAC) + r |= base64_read(in_f, &hmac_salt); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data%s:\n", + flags & FLAG_CD ? "" : " hash"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + if ((flags & FLAG_RK) == 0) { + fprintf(stderr, "credential id:\n"); + xxd(id.ptr, id.len); + } + fprintf(stderr, "up=%s\n", opt2str(opt->up)); + fprintf(stderr, "uv=%s\n", opt2str(opt->uv)); + fprintf(stderr, "pin=%s\n", opt2str(opt->pin)); + } + + if ((assert = fido_assert_new()) == NULL) + errx(1, "fido_assert_new"); + + if (flags & FLAG_CD) + r = fido_assert_set_clientdata(assert, cdh.ptr, cdh.len); + else + r = fido_assert_set_clientdata_hash(assert, cdh.ptr, cdh.len); + + if (r != FIDO_OK || (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK) + errx(1, "fido_assert_set: %s", fido_strerr(r)); + if ((r = fido_assert_set_up(assert, opt->up)) != FIDO_OK) + errx(1, "fido_assert_set_up: %s", fido_strerr(r)); + if ((r = fido_assert_set_uv(assert, opt->uv)) != FIDO_OK) + errx(1, "fido_assert_set_uv: %s", fido_strerr(r)); + + if (flags & FLAG_HMAC) { + if ((r = fido_assert_set_extensions(assert, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s", + fido_strerr(r)); + if ((r = fido_assert_set_hmac_salt(assert, hmac_salt.ptr, + hmac_salt.len)) != FIDO_OK) + errx(1, "fido_assert_set_hmac_salt: %s", + fido_strerr(r)); + } + if (flags & FLAG_LARGEBLOB) { + if ((r = fido_assert_set_extensions(assert, + FIDO_EXT_LARGEBLOB_KEY)) != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s", fido_strerr(r)); + } + if ((flags & FLAG_RK) == 0) { + if ((r = fido_assert_allow_cred(assert, id.ptr, + id.len)) != FIDO_OK) + errx(1, "fido_assert_allow_cred: %s", fido_strerr(r)); + } + + free(hmac_salt.ptr); + free(cdh.ptr); + free(id.ptr); + free(rpid); + + return (assert); +} + +static void +print_assert(FILE *out_f, const fido_assert_t *assert, size_t idx, int flags) +{ + char *cdh = NULL; + char *authdata = NULL; + char *sig = NULL; + char *user_id = NULL; + char *hmac_secret = NULL; + char *key = NULL; + int r; + + r = base64_encode(fido_assert_clientdata_hash_ptr(assert), + fido_assert_clientdata_hash_len(assert), &cdh); + r |= base64_encode(fido_assert_authdata_ptr(assert, idx), + fido_assert_authdata_len(assert, 0), &authdata); + r |= base64_encode(fido_assert_sig_ptr(assert, idx), + fido_assert_sig_len(assert, idx), &sig); + if (flags & FLAG_RK) + r |= base64_encode(fido_assert_user_id_ptr(assert, idx), + fido_assert_user_id_len(assert, idx), &user_id); + if (flags & FLAG_HMAC) + r |= base64_encode(fido_assert_hmac_secret_ptr(assert, idx), + fido_assert_hmac_secret_len(assert, idx), &hmac_secret); + if (flags & FLAG_LARGEBLOB) + r |= base64_encode(fido_assert_largeblob_key_ptr(assert, idx), + fido_assert_largeblob_key_len(assert, idx), &key); + if (r < 0) + errx(1, "output error"); + + fprintf(out_f, "%s\n", cdh); + fprintf(out_f, "%s\n", fido_assert_rp_id(assert)); + fprintf(out_f, "%s\n", authdata); + fprintf(out_f, "%s\n", sig); + if (flags & FLAG_RK) + fprintf(out_f, "%s\n", user_id); + if (hmac_secret) { + fprintf(out_f, "%s\n", hmac_secret); + explicit_bzero(hmac_secret, strlen(hmac_secret)); + } + if (key) { + fprintf(out_f, "%s\n", key); + explicit_bzero(key, strlen(key)); + } + + free(key); + free(hmac_secret); + free(cdh); + free(authdata); + free(sig); + free(user_id); +} + +int +assert_get(int argc, char **argv) +{ + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + struct toggle opt; + char prompt[1024]; + char pin[128]; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int flags = 0; + int ch; + int r; + + opt.up = opt.uv = opt.pin = FIDO_OPT_OMIT; + + while ((ch = getopt(argc, argv, "bdhi:o:prt:uvw")) != -1) { + switch (ch) { + case 'b': + flags |= FLAG_LARGEBLOB; + break; + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'o': + out_path = optarg; + break; + case 'p': + opt.up = FIDO_OPT_TRUE; + break; + case 'r': + flags |= FLAG_RK; + break; + case 't' : + parse_toggle(optarg, &opt); + break; + case 'u': + flags |= FLAG_U2F; + break; + case 'v': + /* -v implies both pin and uv for historical reasons */ + opt.pin = FIDO_OPT_TRUE; + opt.uv = FIDO_OPT_TRUE; + break; + case 'w': + flags |= FLAG_CD; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + in_f = open_read(in_path); + out_f = open_write(out_path); + + fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0); + + assert = prepare_assert(in_f, flags, &opt); + + dev = open_dev(argv[0]); + if (flags & FLAG_U2F) + fido_dev_force_u2f(dev); + + if (opt.pin == FIDO_OPT_TRUE) { + r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", + argv[0]); + if (r < 0 || (size_t)r >= sizeof(prompt)) + errx(1, "snprintf"); + if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF)) + errx(1, "readpassphrase"); + if (strlen(pin) < 4 || strlen(pin) > 63) { + explicit_bzero(pin, sizeof(pin)); + errx(1, "invalid PIN length"); + } + r = fido_dev_get_assert(dev, assert, pin); + } else + r = fido_dev_get_assert(dev, assert, NULL); + + explicit_bzero(pin, sizeof(pin)); + + if (r != FIDO_OK) + errx(1, "fido_dev_get_assert: %s", fido_strerr(r)); + + if (flags & FLAG_RK) { + for (size_t idx = 0; idx < fido_assert_count(assert); idx++) + print_assert(out_f, assert, idx, flags); + } else { + if (fido_assert_count(assert) != 1) + errx(1, "fido_assert_count: %zu", + fido_assert_count(assert)); + print_assert(out_f, assert, 0, flags); + } + + fido_dev_close(dev); + fido_dev_free(&dev); + fido_assert_free(&assert); + + fclose(in_f); + fclose(out_f); + in_f = NULL; + out_f = NULL; + + exit(0); +} diff --git a/tools/assert_verify.c b/tools/assert_verify.c new file mode 100644 index 0000000..4cc2e86 --- /dev/null +++ b/tools/assert_verify.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <fido/es256.h> +#include <fido/es384.h> +#include <fido/rs256.h> +#include <fido/eddsa.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static fido_assert_t * +prepare_assert(FILE *in_f, int flags) +{ + fido_assert_t *assert = NULL; + struct blob cdh; + struct blob authdata; + struct blob sig; + char *rpid = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&authdata, 0, sizeof(authdata)); + memset(&sig, 0, sizeof(sig)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + r |= base64_read(in_f, &authdata); + r |= base64_read(in_f, &sig); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + fprintf(stderr, "authenticator data:\n"); + xxd(authdata.ptr, authdata.len); + fprintf(stderr, "signature:\n"); + xxd(sig.ptr, sig.len); + } + + if ((assert = fido_assert_new()) == NULL) + errx(1, "fido_assert_new"); + if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK) + errx(1, "fido_assert_count: %s", fido_strerr(r)); + + if ((r = fido_assert_set_clientdata_hash(assert, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_assert_set_rp(assert, rpid)) != FIDO_OK || + (r = fido_assert_set_authdata(assert, 0, authdata.ptr, + authdata.len)) != FIDO_OK || + (r = fido_assert_set_sig(assert, 0, sig.ptr, sig.len)) != FIDO_OK) + errx(1, "fido_assert_set: %s", fido_strerr(r)); + + if (flags & FLAG_UP) { + if ((r = fido_assert_set_up(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_up: %s", fido_strerr(r)); + } + if (flags & FLAG_UV) { + if ((r = fido_assert_set_uv(assert, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_assert_set_uv: %s", fido_strerr(r)); + } + if (flags & FLAG_HMAC) { + if ((r = fido_assert_set_extensions(assert, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_assert_set_extensions: %s", + fido_strerr(r)); + } + + free(cdh.ptr); + free(authdata.ptr); + free(sig.ptr); + free(rpid); + + return (assert); +} + +static void * +load_pubkey(int type, const char *file) +{ + EC_KEY *ec = NULL; + RSA *rsa = NULL; + EVP_PKEY *eddsa = NULL; + es256_pk_t *es256_pk = NULL; + es384_pk_t *es384_pk = NULL; + rs256_pk_t *rs256_pk = NULL; + eddsa_pk_t *eddsa_pk = NULL; + void *pk = NULL; + + switch (type) { + case COSE_ES256: + if ((ec = read_ec_pubkey(file)) == NULL) + errx(1, "read_ec_pubkey"); + if ((es256_pk = es256_pk_new()) == NULL) + errx(1, "es256_pk_new"); + if (es256_pk_from_EC_KEY(es256_pk, ec) != FIDO_OK) + errx(1, "es256_pk_from_EC_KEY"); + pk = es256_pk; + EC_KEY_free(ec); + break; + case COSE_ES384: + if ((ec = read_ec_pubkey(file)) == NULL) + errx(1, "read_ec_pubkey"); + if ((es384_pk = es384_pk_new()) == NULL) + errx(1, "es384_pk_new"); + if (es384_pk_from_EC_KEY(es384_pk, ec) != FIDO_OK) + errx(1, "es384_pk_from_EC_KEY"); + pk = es384_pk; + EC_KEY_free(ec); + break; + case COSE_RS256: + if ((rsa = read_rsa_pubkey(file)) == NULL) + errx(1, "read_rsa_pubkey"); + if ((rs256_pk = rs256_pk_new()) == NULL) + errx(1, "rs256_pk_new"); + if (rs256_pk_from_RSA(rs256_pk, rsa) != FIDO_OK) + errx(1, "rs256_pk_from_RSA"); + pk = rs256_pk; + RSA_free(rsa); + break; + case COSE_EDDSA: + if ((eddsa = read_eddsa_pubkey(file)) == NULL) + errx(1, "read_eddsa_pubkey"); + if ((eddsa_pk = eddsa_pk_new()) == NULL) + errx(1, "eddsa_pk_new"); + if (eddsa_pk_from_EVP_PKEY(eddsa_pk, eddsa) != FIDO_OK) + errx(1, "eddsa_pk_from_EVP_PKEY"); + pk = eddsa_pk; + EVP_PKEY_free(eddsa); + break; + default: + errx(1, "invalid type %d", type); + } + + return (pk); +} + +int +assert_verify(int argc, char **argv) +{ + fido_assert_t *assert = NULL; + void *pk = NULL; + char *in_path = NULL; + FILE *in_f = NULL; + int type = COSE_ES256; + int flags = 0; + int ch; + int r; + + while ((ch = getopt(argc, argv, "dhi:pv")) != -1) { + switch (ch) { + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'p': + flags |= FLAG_UP; + break; + case 'v': + flags |= FLAG_UV; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1 || argc > 2) + usage(); + + in_f = open_read(in_path); + + if (argc > 1 && cose_type(argv[1], &type) < 0) + errx(1, "unknown type %s", argv[1]); + + fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0); + + pk = load_pubkey(type, argv[0]); + assert = prepare_assert(in_f, flags); + if ((r = fido_assert_verify(assert, 0, type, pk)) != FIDO_OK) + errx(1, "fido_assert_verify: %s", fido_strerr(r)); + fido_assert_free(&assert); + + fclose(in_f); + in_f = NULL; + + exit(0); +} diff --git a/tools/base64.c b/tools/base64.c new file mode 100644 index 0000000..2cfa98d --- /dev/null +++ b/tools/base64.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bio.h> +#include <openssl/evp.h> + +#include <limits.h> +#include <stdint.h> +#include <string.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +base64_encode(const void *ptr, size_t len, char **out) +{ + BIO *bio_b64 = NULL; + BIO *bio_mem = NULL; + char *b64_ptr = NULL; + long b64_len; + int n; + int ok = -1; + + if (ptr == NULL || out == NULL || len > INT_MAX) + return (-1); + + *out = NULL; + + if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL) + goto fail; + if ((bio_mem = BIO_new(BIO_s_mem())) == NULL) + goto fail; + + BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL); + BIO_push(bio_b64, bio_mem); + + n = BIO_write(bio_b64, ptr, (int)len); + if (n < 0 || (size_t)n != len) + goto fail; + + if (BIO_flush(bio_b64) < 0) + goto fail; + + b64_len = BIO_get_mem_data(bio_b64, &b64_ptr); + if (b64_len < 0 || (size_t)b64_len == SIZE_MAX || b64_ptr == NULL) + goto fail; + if ((*out = calloc(1, (size_t)b64_len + 1)) == NULL) + goto fail; + + memcpy(*out, b64_ptr, (size_t)b64_len); + ok = 0; + +fail: + BIO_free(bio_b64); + BIO_free(bio_mem); + + return (ok); +} + +int +base64_decode(const char *in, void **ptr, size_t *len) +{ + BIO *bio_mem = NULL; + BIO *bio_b64 = NULL; + size_t alloc_len; + int n; + int ok = -1; + + if (in == NULL || ptr == NULL || len == NULL || strlen(in) > INT_MAX) + return (-1); + + *ptr = NULL; + *len = 0; + + if ((bio_b64 = BIO_new(BIO_f_base64())) == NULL) + goto fail; + if ((bio_mem = BIO_new_mem_buf((const void *)in, -1)) == NULL) + goto fail; + + BIO_set_flags(bio_b64, BIO_FLAGS_BASE64_NO_NL); + BIO_push(bio_b64, bio_mem); + + alloc_len = strlen(in); + if ((*ptr = calloc(1, alloc_len)) == NULL) + goto fail; + + n = BIO_read(bio_b64, *ptr, (int)alloc_len); + if (n <= 0 || BIO_eof(bio_b64) == 0) + goto fail; + + *len = (size_t)n; + ok = 0; + +fail: + BIO_free(bio_b64); + BIO_free(bio_mem); + + if (ok < 0) { + free(*ptr); + *ptr = NULL; + *len = 0; + } + + return (ok); +} + +int +base64_read(FILE *f, struct blob *out) +{ + char *line = NULL; + size_t linesize = 0; + ssize_t n; + + out->ptr = NULL; + out->len = 0; + + if ((n = getline(&line, &linesize, f)) <= 0 || + (size_t)n != strlen(line)) { + free(line); /* XXX should be free'd _even_ if getline() fails */ + return (-1); + } + + if (base64_decode(line, (void **)&out->ptr, &out->len) < 0) { + free(line); + return (-1); + } + + free(line); + + return (0); +} diff --git a/tools/bio.c b/tools/bio.c new file mode 100644 index 0000000..7a1406d --- /dev/null +++ b/tools/bio.c @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <fido/bio.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static int +print_template(const fido_bio_template_array_t *ta, size_t idx) +{ + const fido_bio_template_t *t = NULL; + char *id = NULL; + + if ((t = fido_bio_template(ta, idx)) == NULL) { + warnx("fido_bio_template"); + return -1; + } + if (base64_encode(fido_bio_template_id_ptr(t), + fido_bio_template_id_len(t), &id) < 0) { + warnx("output error"); + return -1; + } + + printf("%02u: %s %s\n", (unsigned)idx, id, fido_bio_template_name(t)); + free(id); + + return 0; +} + +int +bio_list(const char *path) +{ + fido_bio_template_array_t *ta = NULL; + fido_dev_t *dev = NULL; + char *pin = NULL; + int r, ok = 1; + + if ((ta = fido_bio_template_array_new()) == NULL) + errx(1, "fido_bio_template_array_new"); + dev = open_dev(path); + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_bio_dev_get_template_array(dev, ta, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + if (r != FIDO_OK) { + warnx("fido_bio_dev_get_template_array: %s", fido_strerr(r)); + goto out; + } + for (size_t i = 0; i < fido_bio_template_array_count(ta); i++) + if (print_template(ta, i) < 0) + goto out; + + ok = 0; +out: + fido_bio_template_array_free(&ta); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +bio_set_name(const char *path, const char *id, const char *name) +{ + fido_bio_template_t *t = NULL; + fido_dev_t *dev = NULL; + char *pin = NULL; + void *id_blob_ptr = NULL; + size_t id_blob_len = 0; + int r, ok = 1; + + if ((t = fido_bio_template_new()) == NULL) + errx(1, "fido_bio_template_new"); + if (base64_decode(id, &id_blob_ptr, &id_blob_len) < 0) + errx(1, "base64_decode"); + if ((r = fido_bio_template_set_name(t, name)) != FIDO_OK) + errx(1, "fido_bio_template_set_name: %s", fido_strerr(r)); + if ((r = fido_bio_template_set_id(t, id_blob_ptr, + id_blob_len)) != FIDO_OK) + errx(1, "fido_bio_template_set_id: %s", fido_strerr(r)); + + dev = open_dev(path); + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_bio_dev_set_template_name(dev, t, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + if (r != FIDO_OK) { + warnx("fido_bio_dev_set_template_name: %s", fido_strerr(r)); + goto out; + } + + ok = 0; +out: + free(id_blob_ptr); + fido_bio_template_free(&t); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +static const char * +enroll_strerr(uint8_t n) +{ + switch (n) { + case FIDO_BIO_ENROLL_FP_GOOD: + return "Sample ok"; + case FIDO_BIO_ENROLL_FP_TOO_HIGH: + return "Sample too high"; + case FIDO_BIO_ENROLL_FP_TOO_LOW: + return "Sample too low"; + case FIDO_BIO_ENROLL_FP_TOO_LEFT: + return "Sample too left"; + case FIDO_BIO_ENROLL_FP_TOO_RIGHT: + return "Sample too right"; + case FIDO_BIO_ENROLL_FP_TOO_FAST: + return "Sample too fast"; + case FIDO_BIO_ENROLL_FP_TOO_SLOW: + return "Sample too slow"; + case FIDO_BIO_ENROLL_FP_POOR_QUALITY: + return "Poor quality sample"; + case FIDO_BIO_ENROLL_FP_TOO_SKEWED: + return "Sample too skewed"; + case FIDO_BIO_ENROLL_FP_TOO_SHORT: + return "Sample too short"; + case FIDO_BIO_ENROLL_FP_MERGE_FAILURE: + return "Sample merge failure"; + case FIDO_BIO_ENROLL_FP_EXISTS: + return "Sample exists"; + case FIDO_BIO_ENROLL_FP_DATABASE_FULL: + return "Fingerprint database full"; + case FIDO_BIO_ENROLL_NO_USER_ACTIVITY: + return "No user activity"; + case FIDO_BIO_ENROLL_NO_USER_PRESENCE_TRANSITION: + return "No user presence transition"; + default: + return "Unknown error"; + } +} + +int +bio_enroll(const char *path) +{ + fido_bio_template_t *t = NULL; + fido_bio_enroll_t *e = NULL; + fido_dev_t *dev = NULL; + char *pin = NULL; + int r, ok = 1; + + if ((t = fido_bio_template_new()) == NULL) + errx(1, "fido_bio_template_new"); + if ((e = fido_bio_enroll_new()) == NULL) + errx(1, "fido_bio_enroll_new"); + + dev = open_dev(path); + if ((pin = get_pin(path)) == NULL) + goto out; + printf("Touch your security key.\n"); + r = fido_bio_dev_enroll_begin(dev, t, e, 10000, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + if (r != FIDO_OK) { + warnx("fido_bio_dev_enroll_begin: %s", fido_strerr(r)); + goto out; + } + printf("%s.\n", enroll_strerr(fido_bio_enroll_last_status(e))); + + while (fido_bio_enroll_remaining_samples(e) > 0) { + printf("Touch your security key (%u sample%s left).\n", + (unsigned)fido_bio_enroll_remaining_samples(e), + plural(fido_bio_enroll_remaining_samples(e))); + if ((r = fido_bio_dev_enroll_continue(dev, t, e, + 10000)) != FIDO_OK) { + fido_dev_cancel(dev); + warnx("fido_bio_dev_enroll_continue: %s", + fido_strerr(r)); + goto out; + } + printf("%s.\n", enroll_strerr(fido_bio_enroll_last_status(e))); + } + + ok = 0; +out: + fido_bio_template_free(&t); + fido_bio_enroll_free(&e); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +bio_delete(const char *path, const char *id) +{ + fido_bio_template_t *t = NULL; + fido_dev_t *dev = NULL; + char *pin = NULL; + void *id_blob_ptr = NULL; + size_t id_blob_len = 0; + int r, ok = 1; + + if ((t = fido_bio_template_new()) == NULL) + errx(1, "fido_bio_template_new"); + if (base64_decode(id, &id_blob_ptr, &id_blob_len) < 0) + errx(1, "base64_decode"); + if ((r = fido_bio_template_set_id(t, id_blob_ptr, + id_blob_len)) != FIDO_OK) + errx(1, "fido_bio_template_set_id: %s", fido_strerr(r)); + + dev = open_dev(path); + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_bio_dev_enroll_remove(dev, t, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + if (r != FIDO_OK) { + warnx("fido_bio_dev_enroll_remove: %s", fido_strerr(r)); + goto out; + } + + ok = 0; +out: + free(id_blob_ptr); + fido_bio_template_free(&t); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +static const char * +type_str(uint8_t t) +{ + switch (t) { + case 1: + return "touch"; + case 2: + return "swipe"; + default: + return "unknown"; + } +} + +void +bio_info(fido_dev_t *dev) +{ + fido_bio_info_t *i = NULL; + + if ((i = fido_bio_info_new()) == NULL) { + warnx("fido_bio_info_new"); + return; + } + if (fido_bio_dev_get_info(dev, i) != FIDO_OK) { + fido_bio_info_free(&i); + return; + } + + printf("sensor type: %u (%s)\n", (unsigned)fido_bio_info_type(i), + type_str(fido_bio_info_type(i))); + printf("max samples: %u\n", (unsigned)fido_bio_info_max_samples(i)); + + fido_bio_info_free(&i); +} diff --git a/tools/config.c b/tools/config.c new file mode 100644 index 0000000..49253e8 --- /dev/null +++ b/tools/config.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <fido.h> +#include <fido/config.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +config_entattest(char *path) +{ + fido_dev_t *dev; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + if ((r = fido_dev_enable_entattest(dev, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_enable_entattest(dev, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_dev_enable_entattest: %s (0x%x)", + fido_strerr(r), r); + goto out; + } + + ok = 0; +out: + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +config_always_uv(char *path, int toggle) +{ + fido_dev_t *dev; + char *pin = NULL; + int v, r, ok = 1; + + dev = open_dev(path); + if (get_devopt(dev, "alwaysUv", &v) < 0) { + warnx("%s: getdevopt", __func__); + goto out; + } + if (v == -1) { + warnx("%s: option not found", __func__); + goto out; + } + if (v == toggle) { + ok = 0; + goto out; + } + if ((r = fido_dev_toggle_always_uv(dev, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_toggle_always_uv(dev, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_dev_toggle_always_uv: %s (0x%x)", + fido_strerr(r), r); + goto out; + } + + ok = 0; +out: + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +config_pin_minlen(char *path, const char *pinlen) +{ + fido_dev_t *dev; + char *pin = NULL; + int len, r, ok = 1; + + dev = open_dev(path); + if ((len = base10(pinlen)) < 0 || len > 63) { + warnx("%s: len > 63", __func__); + goto out; + } + if ((r = fido_dev_set_pin_minlen(dev, (size_t)len, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_set_pin_minlen(dev, (size_t)len, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_dev_set_pin_minlen: %s (0x%x)", fido_strerr(r), r); + goto out; + } + + ok = 0; +out: + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +config_force_pin_change(char *path) +{ + fido_dev_t *dev; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + if ((r = fido_dev_force_pin_change(dev, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_force_pin_change(dev, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_dev_force_pin_change: %s (0x%x)", fido_strerr(r), r); + goto out; + } + + ok = 0; +out: + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +config_pin_minlen_rpid(char *path, const char *rpids) +{ + fido_dev_t *dev; + char *otmp, *tmp, *cp; + char *pin = NULL, **rpid = NULL; + int r, ok = 1; + size_t n; + + if ((tmp = strdup(rpids)) == NULL) + err(1, "strdup"); + otmp = tmp; + for (n = 0; (cp = strsep(&tmp, ",")) != NULL; n++) { + if (n == SIZE_MAX || (rpid = recallocarray(rpid, n, n + 1, + sizeof(*rpid))) == NULL) + err(1, "recallocarray"); + if ((rpid[n] = strdup(cp)) == NULL) + err(1, "strdup"); + if (*rpid[n] == '\0') + errx(1, "empty rpid"); + } + free(otmp); + if (rpid == NULL || n == 0) + errx(1, "could not parse rp_id"); + dev = open_dev(path); + if ((r = fido_dev_set_pin_minlen_rpid(dev, (const char * const *)rpid, + n, NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_set_pin_minlen_rpid(dev, (const char * const *)rpid, + n, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_dev_set_pin_minlen_rpid: %s (0x%x)", + fido_strerr(r), r); + goto out; + } + + ok = 0; +out: + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} diff --git a/tools/cred_make.c b/tools/cred_make.c new file mode 100644 index 0000000..66c8b52 --- /dev/null +++ b/tools/cred_make.c @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static fido_cred_t * +prepare_cred(FILE *in_f, int type, int flags) +{ + fido_cred_t *cred = NULL; + struct blob cdh; + struct blob uid; + char *rpid = NULL; + char *uname = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&uid, 0, sizeof(uid)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + r |= string_read(in_f, &uname); + r |= base64_read(in_f, &uid); + if (r < 0) + errx(1, "input error"); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data%s:\n", + flags & FLAG_CD ? "" : " hash"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + fprintf(stderr, "user name: %s\n", uname); + fprintf(stderr, "user id:\n"); + xxd(uid.ptr, uid.len); + } + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + + if (flags & FLAG_CD) + r = fido_cred_set_clientdata(cred, cdh.ptr, cdh.len); + else + r = fido_cred_set_clientdata_hash(cred, cdh.ptr, cdh.len); + + if (r != FIDO_OK || (r = fido_cred_set_type(cred, type)) != FIDO_OK || + (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK || + (r = fido_cred_set_user(cred, uid.ptr, uid.len, uname, NULL, + NULL)) != FIDO_OK) + errx(1, "fido_cred_set: %s", fido_strerr(r)); + + if (flags & FLAG_RK) { + if ((r = fido_cred_set_rk(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_rk: %s", fido_strerr(r)); + } + if (flags & FLAG_UV) { + if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_uv: %s", fido_strerr(r)); + } + if (flags & FLAG_HMAC) { + if ((r = fido_cred_set_extensions(cred, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s", fido_strerr(r)); + } + if (flags & FLAG_LARGEBLOB) { + if ((r = fido_cred_set_extensions(cred, + FIDO_EXT_LARGEBLOB_KEY)) != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s", fido_strerr(r)); + } + + free(cdh.ptr); + free(uid.ptr); + free(rpid); + free(uname); + + return (cred); +} + +static void +print_attcred(FILE *out_f, const fido_cred_t *cred) +{ + char *cdh = NULL; + char *authdata = NULL; + char *id = NULL; + char *sig = NULL; + char *x5c = NULL; + char *key = NULL; + int r; + + r = base64_encode(fido_cred_clientdata_hash_ptr(cred), + fido_cred_clientdata_hash_len(cred), &cdh); + r |= base64_encode(fido_cred_authdata_ptr(cred), + fido_cred_authdata_len(cred), &authdata); + r |= base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred), + &id); + r |= base64_encode(fido_cred_sig_ptr(cred), fido_cred_sig_len(cred), + &sig); + if (fido_cred_x5c_ptr(cred) != NULL) + r |= base64_encode(fido_cred_x5c_ptr(cred), + fido_cred_x5c_len(cred), &x5c); + if (fido_cred_largeblob_key_ptr(cred) != NULL) + r |= base64_encode(fido_cred_largeblob_key_ptr(cred), + fido_cred_largeblob_key_len(cred), &key); + if (r < 0) + errx(1, "output error"); + + fprintf(out_f, "%s\n", cdh); + fprintf(out_f, "%s\n", fido_cred_rp_id(cred)); + fprintf(out_f, "%s\n", fido_cred_fmt(cred)); + fprintf(out_f, "%s\n", authdata); + fprintf(out_f, "%s\n", id); + fprintf(out_f, "%s\n", sig); + if (x5c != NULL) + fprintf(out_f, "%s\n", x5c); + if (key != NULL) { + fprintf(out_f, "%s\n", key); + explicit_bzero(key, strlen(key)); + } + + free(cdh); + free(authdata); + free(id); + free(sig); + free(x5c); + free(key); +} + +int +cred_make(int argc, char **argv) +{ + fido_dev_t *dev = NULL; + fido_cred_t *cred = NULL; + char prompt[1024]; + char pin[128]; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int type = COSE_ES256; + int flags = 0; + int cred_protect = -1; + int ch; + int r; + + while ((ch = getopt(argc, argv, "bc:dhi:o:qruvw")) != -1) { + switch (ch) { + case 'b': + flags |= FLAG_LARGEBLOB; + break; + case 'c': + if ((cred_protect = base10(optarg)) < 0) + errx(1, "-c: invalid argument '%s'", optarg); + break; + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'o': + out_path = optarg; + break; + case 'q': + flags |= FLAG_QUIET; + break; + case 'r': + flags |= FLAG_RK; + break; + case 'u': + flags |= FLAG_U2F; + break; + case 'v': + flags |= FLAG_UV; + break; + case 'w': + flags |= FLAG_CD; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc < 1 || argc > 2) + usage(); + + in_f = open_read(in_path); + out_f = open_write(out_path); + + if (argc > 1 && cose_type(argv[1], &type) < 0) + errx(1, "unknown type %s", argv[1]); + + fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0); + + cred = prepare_cred(in_f, type, flags); + + dev = open_dev(argv[0]); + if (flags & FLAG_U2F) + fido_dev_force_u2f(dev); + + if (cred_protect > 0) { + r = fido_cred_set_prot(cred, cred_protect); + if (r != FIDO_OK) { + errx(1, "fido_cred_set_prot: %s", fido_strerr(r)); + } + } + + r = fido_dev_make_cred(dev, cred, NULL); + if (r == FIDO_ERR_PIN_REQUIRED && !(flags & FLAG_QUIET)) { + r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", + argv[0]); + if (r < 0 || (size_t)r >= sizeof(prompt)) + errx(1, "snprintf"); + if (!readpassphrase(prompt, pin, sizeof(pin), RPP_ECHO_OFF)) + errx(1, "readpassphrase"); + if (strlen(pin) < 4 || strlen(pin) > 63) { + explicit_bzero(pin, sizeof(pin)); + errx(1, "invalid PIN length"); + } + r = fido_dev_make_cred(dev, cred, pin); + } + + explicit_bzero(pin, sizeof(pin)); + if (r != FIDO_OK) + errx(1, "fido_dev_make_cred: %s", fido_strerr(r)); + print_attcred(out_f, cred); + + fido_dev_close(dev); + fido_dev_free(&dev); + fido_cred_free(&cred); + + fclose(in_f); + fclose(out_f); + in_f = NULL; + out_f = NULL; + + exit(0); +} diff --git a/tools/cred_verify.c b/tools/cred_verify.c new file mode 100644 index 0000000..3eae435 --- /dev/null +++ b/tools/cred_verify.c @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static fido_cred_t * +prepare_cred(FILE *in_f, int type, int flags) +{ + fido_cred_t *cred = NULL; + struct blob cdh; + struct blob authdata; + struct blob id; + struct blob sig; + struct blob x5c; + char *rpid = NULL; + char *fmt = NULL; + int r; + + memset(&cdh, 0, sizeof(cdh)); + memset(&authdata, 0, sizeof(authdata)); + memset(&id, 0, sizeof(id)); + memset(&sig, 0, sizeof(sig)); + memset(&x5c, 0, sizeof(x5c)); + + r = base64_read(in_f, &cdh); + r |= string_read(in_f, &rpid); + r |= string_read(in_f, &fmt); + r |= base64_read(in_f, &authdata); + r |= base64_read(in_f, &id); + r |= base64_read(in_f, &sig); + if (r < 0) + errx(1, "input error"); + + (void)base64_read(in_f, &x5c); + + if (flags & FLAG_DEBUG) { + fprintf(stderr, "client data hash:\n"); + xxd(cdh.ptr, cdh.len); + fprintf(stderr, "relying party id: %s\n", rpid); + fprintf(stderr, "format: %s\n", fmt); + fprintf(stderr, "authenticator data:\n"); + xxd(authdata.ptr, authdata.len); + fprintf(stderr, "credential id:\n"); + xxd(id.ptr, id.len); + fprintf(stderr, "signature:\n"); + xxd(sig.ptr, sig.len); + fprintf(stderr, "x509:\n"); + xxd(x5c.ptr, x5c.len); + } + + if ((cred = fido_cred_new()) == NULL) + errx(1, "fido_cred_new"); + + if ((r = fido_cred_set_type(cred, type)) != FIDO_OK || + (r = fido_cred_set_clientdata_hash(cred, cdh.ptr, + cdh.len)) != FIDO_OK || + (r = fido_cred_set_rp(cred, rpid, NULL)) != FIDO_OK || + (r = fido_cred_set_authdata(cred, authdata.ptr, + authdata.len)) != FIDO_OK || + (r = fido_cred_set_sig(cred, sig.ptr, sig.len)) != FIDO_OK || + (r = fido_cred_set_fmt(cred, fmt)) != FIDO_OK) + errx(1, "fido_cred_set: %s", fido_strerr(r)); + + if (x5c.ptr != NULL) { + if ((r = fido_cred_set_x509(cred, x5c.ptr, x5c.len)) != FIDO_OK) + errx(1, "fido_cred_set_x509: %s", fido_strerr(r)); + } + + if (flags & FLAG_UV) { + if ((r = fido_cred_set_uv(cred, FIDO_OPT_TRUE)) != FIDO_OK) + errx(1, "fido_cred_set_uv: %s", fido_strerr(r)); + } + if (flags & FLAG_HMAC) { + if ((r = fido_cred_set_extensions(cred, + FIDO_EXT_HMAC_SECRET)) != FIDO_OK) + errx(1, "fido_cred_set_extensions: %s", fido_strerr(r)); + } + + free(cdh.ptr); + free(authdata.ptr); + free(id.ptr); + free(sig.ptr); + free(x5c.ptr); + free(rpid); + free(fmt); + + return (cred); +} + +int +cred_verify(int argc, char **argv) +{ + fido_cred_t *cred = NULL; + char *in_path = NULL; + char *out_path = NULL; + FILE *in_f = NULL; + FILE *out_f = NULL; + int type = COSE_ES256; + int flags = 0; + int cred_prot = -1; + int ch; + int r; + + while ((ch = getopt(argc, argv, "c:dhi:o:v")) != -1) { + switch (ch) { + case 'c': + if ((cred_prot = base10(optarg)) < 0) + errx(1, "-c: invalid argument '%s'", optarg); + break; + case 'd': + flags |= FLAG_DEBUG; + break; + case 'h': + flags |= FLAG_HMAC; + break; + case 'i': + in_path = optarg; + break; + case 'o': + out_path = optarg; + break; + case 'v': + flags |= FLAG_UV; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc > 1) + usage(); + + in_f = open_read(in_path); + out_f = open_write(out_path); + + if (argc > 0 && cose_type(argv[0], &type) < 0) + errx(1, "unknown type %s", argv[0]); + + fido_init((flags & FLAG_DEBUG) ? FIDO_DEBUG : 0); + cred = prepare_cred(in_f, type, flags); + + if (cred_prot > 0) { + r = fido_cred_set_prot(cred, cred_prot); + if (r != FIDO_OK) { + errx(1, "fido_cred_set_prot: %s", fido_strerr(r)); + } + } + + if (fido_cred_x5c_ptr(cred) == NULL) { + if ((r = fido_cred_verify_self(cred)) != FIDO_OK) + errx(1, "fido_cred_verify_self: %s", fido_strerr(r)); + } else { + if ((r = fido_cred_verify(cred)) != FIDO_OK) + errx(1, "fido_cred_verify: %s", fido_strerr(r)); + } + + print_cred(out_f, type, cred); + fido_cred_free(&cred); + + fclose(in_f); + fclose(out_f); + in_f = NULL; + out_f = NULL; + + exit(0); +} diff --git a/tools/credman.c b/tools/credman.c new file mode 100644 index 0000000..a0a3149 --- /dev/null +++ b/tools/credman.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <fido/credman.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +credman_get_metadata(fido_dev_t *dev, const char *path) +{ + fido_credman_metadata_t *metadata = NULL; + char *pin = NULL; + int r, ok = 1; + + if ((metadata = fido_credman_metadata_new()) == NULL) { + warnx("fido_credman_metadata_new"); + goto out; + } + if ((r = fido_credman_get_dev_metadata(dev, metadata, + NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_get_dev_metadata(dev, metadata, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_credman_get_dev_metadata: %s", fido_strerr(r)); + goto out; + } + + printf("existing rk(s): %u\n", + (unsigned)fido_credman_rk_existing(metadata)); + printf("remaining rk(s): %u\n", + (unsigned)fido_credman_rk_remaining(metadata)); + + ok = 0; +out: + fido_credman_metadata_free(&metadata); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +static int +print_rp(fido_credman_rp_t *rp, size_t idx) +{ + char *rp_id_hash = NULL; + + if (base64_encode(fido_credman_rp_id_hash_ptr(rp, idx), + fido_credman_rp_id_hash_len(rp, idx), &rp_id_hash) < 0) { + warnx("output error"); + return -1; + } + printf("%02u: %s %s\n", (unsigned)idx, rp_id_hash, + fido_credman_rp_id(rp, idx)); + free(rp_id_hash); + + return 0; +} + +int +credman_list_rp(const char *path) +{ + fido_credman_rp_t *rp = NULL; + fido_dev_t *dev = NULL; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + if ((rp = fido_credman_rp_new()) == NULL) { + warnx("fido_credman_rp_new"); + goto out; + } + if ((r = fido_credman_get_dev_rp(dev, rp, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_get_dev_rp(dev, rp, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_credman_get_dev_rp: %s", fido_strerr(r)); + goto out; + } + for (size_t i = 0; i < fido_credman_rp_count(rp); i++) + if (print_rp(rp, i) < 0) + goto out; + + ok = 0; +out: + fido_credman_rp_free(&rp); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +static int +print_rk(const fido_credman_rk_t *rk, size_t idx) +{ + const fido_cred_t *cred; + char *id = NULL; + char *user_id = NULL; + const char *type; + const char *prot; + + if ((cred = fido_credman_rk(rk, idx)) == NULL) { + warnx("fido_credman_rk"); + return -1; + } + if (base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred), + &id) < 0 || base64_encode(fido_cred_user_id_ptr(cred), + fido_cred_user_id_len(cred), &user_id) < 0) { + warnx("output error"); + return -1; + } + + type = cose_string(fido_cred_type(cred)); + prot = prot_string(fido_cred_prot(cred)); + + printf("%02u: %s %s %s %s %s\n", (unsigned)idx, id, + fido_cred_display_name(cred), user_id, type, prot); + + free(user_id); + free(id); + + return 0; +} + +int +credman_list_rk(const char *path, const char *rp_id) +{ + fido_dev_t *dev = NULL; + fido_credman_rk_t *rk = NULL; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + if ((rk = fido_credman_rk_new()) == NULL) { + warnx("fido_credman_rk_new"); + goto out; + } + if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_get_dev_rk(dev, rp_id, rk, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_credman_get_dev_rk: %s", fido_strerr(r)); + goto out; + } + for (size_t i = 0; i < fido_credman_rk_count(rk); i++) + if (print_rk(rk, i) < 0) + goto out; + + ok = 0; +out: + fido_credman_rk_free(&rk); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +credman_print_rk(fido_dev_t *dev, const char *path, const char *rp_id, + const char *cred_id) +{ + fido_credman_rk_t *rk = NULL; + const fido_cred_t *cred = NULL; + char *pin = NULL; + void *cred_id_ptr = NULL; + size_t cred_id_len = 0; + int r, ok = 1; + + if ((rk = fido_credman_rk_new()) == NULL) { + warnx("fido_credman_rk_new"); + goto out; + } + if (base64_decode(cred_id, &cred_id_ptr, &cred_id_len) < 0) { + warnx("base64_decode"); + goto out; + } + if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_get_dev_rk(dev, rp_id, rk, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_credman_get_dev_rk: %s", fido_strerr(r)); + goto out; + } + + for (size_t i = 0; i < fido_credman_rk_count(rk); i++) { + if ((cred = fido_credman_rk(rk, i)) == NULL || + fido_cred_id_ptr(cred) == NULL) { + warnx("output error"); + goto out; + } + if (cred_id_len != fido_cred_id_len(cred) || + memcmp(cred_id_ptr, fido_cred_id_ptr(cred), cred_id_len)) + continue; + print_cred(stdout, fido_cred_type(cred), cred); + ok = 0; + goto out; + } + + warnx("credential not found"); +out: + free(cred_id_ptr); + fido_credman_rk_free(&rk); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +credman_delete_rk(const char *path, const char *id) +{ + fido_dev_t *dev = NULL; + char *pin = NULL; + void *id_ptr = NULL; + size_t id_len = 0; + int r, ok = 1; + + dev = open_dev(path); + if (base64_decode(id, &id_ptr, &id_len) < 0) { + warnx("base64_decode"); + goto out; + } + if ((r = fido_credman_del_dev_rk(dev, id_ptr, id_len, + NULL)) != FIDO_OK && should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_del_dev_rk(dev, id_ptr, id_len, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_credman_del_dev_rk: %s", fido_strerr(r)); + goto out; + } + + ok = 0; +out: + free(id_ptr); + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +credman_update_rk(const char *path, const char *user_id, const char *cred_id, + const char *name, const char *display_name) +{ + fido_dev_t *dev = NULL; + fido_cred_t *cred = NULL; + char *pin = NULL; + void *user_id_ptr = NULL; + void *cred_id_ptr = NULL; + size_t user_id_len = 0; + size_t cred_id_len = 0; + int r, ok = 1; + + dev = open_dev(path); + if (base64_decode(user_id, &user_id_ptr, &user_id_len) < 0 || + base64_decode(cred_id, &cred_id_ptr, &cred_id_len) < 0) { + warnx("base64_decode"); + goto out; + } + if ((cred = fido_cred_new()) == NULL) { + warnx("fido_cred_new"); + goto out; + } + if ((r = fido_cred_set_id(cred, cred_id_ptr, cred_id_len)) != FIDO_OK) { + warnx("fido_cred_set_id: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_cred_set_user(cred, user_id_ptr, user_id_len, name, + display_name, NULL)) != FIDO_OK) { + warnx("fido_cred_set_user: %s", fido_strerr(r)); + goto out; + } + if ((r = fido_credman_set_dev_rk(dev, cred, NULL)) != FIDO_OK && + should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_set_dev_rk(dev, cred, pin); + freezero(pin, PINBUF_LEN); + pin = NULL; + } + if (r != FIDO_OK) { + warnx("fido_credman_set_dev_rk: %s", fido_strerr(r)); + goto out; + } + + ok = 0; +out: + free(user_id_ptr); + free(cred_id_ptr); + fido_dev_close(dev); + fido_dev_free(&dev); + fido_cred_free(&cred); + + exit(ok); +} diff --git a/tools/extern.h b/tools/extern.h new file mode 100644 index 0000000..b806ddd --- /dev/null +++ b/tools/extern.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _EXTERN_H_ +#define _EXTERN_H_ + +#include <sys/types.h> + +#include <openssl/ec.h> + +#include <fido.h> +#include <stddef.h> +#include <stdio.h> + +struct blob { + unsigned char *ptr; + size_t len; +}; + +#define TOKEN_OPT "CDGILPRSVabcdefi:k:l:m:n:p:ru" + +#define FLAG_DEBUG 0x001 +#define FLAG_QUIET 0x002 +#define FLAG_RK 0x004 +#define FLAG_UV 0x008 +#define FLAG_U2F 0x010 +#define FLAG_HMAC 0x020 +#define FLAG_UP 0x040 +#define FLAG_LARGEBLOB 0x080 +#define FLAG_CD 0x100 + +#define PINBUF_LEN 256 + +EC_KEY *read_ec_pubkey(const char *); +fido_dev_t *open_dev(const char *); +FILE *open_read(const char *); +FILE *open_write(const char *); +char *get_pin(const char *); +const char *plural(size_t); +const char *cose_string(int); +const char *prot_string(int); +int assert_get(int, char **); +int assert_verify(int, char **); +int base64_decode(const char *, void **, size_t *); +int base64_encode(const void *, size_t, char **); +int base64_read(FILE *, struct blob *); +int bio_delete(const char *, const char *); +int bio_enroll(const char *); +void bio_info(fido_dev_t *); +int bio_list(const char *); +int bio_set_name(const char *, const char *, const char *); +int blob_clean(const char *); +int blob_list(const char *); +int blob_delete(const char *, const char *, const char *, const char *); +int blob_get(const char *, const char *, const char *, const char *, + const char *); +int blob_set(const char *, const char *, const char *, const char *, + const char *); +int config_always_uv(char *, int); +int config_entattest(char *); +int config_force_pin_change(char *); +int config_pin_minlen(char *, const char *); +int config_pin_minlen_rpid(char *, const char *); +int cose_type(const char *, int *); +int cred_make(int, char **); +int cred_verify(int, char **); +int credman_delete_rk(const char *, const char *); +int credman_update_rk(const char *, const char *, const char *, const char *, + const char *); +int credman_get_metadata(fido_dev_t *, const char *); +int credman_list_rk(const char *, const char *); +int credman_list_rp(const char *); +int credman_print_rk(fido_dev_t *, const char *, const char *, const char *); +int get_devopt(fido_dev_t *, const char *, int *); +int pin_change(char *); +int pin_set(char *); +int should_retry_with_pin(const fido_dev_t *, int); +int string_read(FILE *, char **); +int token_config(int, char **, char *); +int token_delete(int, char **, char *); +int token_get(int, char **, char *); +int token_info(int, char **, char *); +int token_list(int, char **, char *); +int token_reset(char *); +int token_set(int, char **, char *); +int write_es256_pubkey(FILE *, const void *, size_t); +int write_es384_pubkey(FILE *, const void *, size_t); +int write_rsa_pubkey(FILE *, const void *, size_t); +int read_file(const char *, u_char **, size_t *); +int write_file(const char *, const u_char *, size_t); +RSA *read_rsa_pubkey(const char *); +EVP_PKEY *read_eddsa_pubkey(const char *); +int write_eddsa_pubkey(FILE *, const void *, size_t); +void print_cred(FILE *, int, const fido_cred_t *); +void usage(void); +void xxd(const void *, size_t); +int base10(const char *); + +#endif /* _EXTERN_H_ */ diff --git a/tools/fido2-assert.c b/tools/fido2-assert.c new file mode 100644 index 0000000..351ed4f --- /dev/null +++ b/tools/fido2-assert.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Example usage: + * + * $ echo assertion challenge | openssl sha256 -binary | base64 > assert_param + * $ echo relying party >> assert_param + * $ head -1 cred >> assert_param # credential id + * $ tail -n +2 cred > pubkey # credential pubkey + * $ fido2-assert -G -i assert_param /dev/hidraw5 | fido2-assert -V pubkey rs256 + * + * See blurb in fido2-cred.c on how to obtain cred. + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-assert -G [-bdhpruvw] [-t option] [-i input_file] [-o output_file] device\n" +" fido2-assert -V [-dhpv] [-i input_file] key_file [type]\n" + ); + + exit(1); +} + +int +main(int argc, char **argv) +{ + if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-') + usage(); + + switch (argv[1][1]) { + case 'G': + return (assert_get(--argc, ++argv)); + case 'V': + return (assert_verify(--argc, ++argv)); + } + + usage(); + + /* NOTREACHED */ +} diff --git a/tools/fido2-attach.sh b/tools/fido2-attach.sh new file mode 100755 index 0000000..ef02db6 --- /dev/null +++ b/tools/fido2-attach.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Copyright (c) 2020 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +DEV="" + +while [ -z "${DEV}" ]; do + sleep .5 + DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')" +done + +printf '%s\n' "${DEV}" diff --git a/tools/fido2-cred.c b/tools/fido2-cred.c new file mode 100644 index 0000000..76081c6 --- /dev/null +++ b/tools/fido2-cred.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2023 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Example usage: + * + * $ echo credential challenge | openssl sha256 -binary | base64 > cred_param + * $ echo relying party >> cred_param + * $ echo user name >> cred_param + * $ dd if=/dev/urandom bs=1 count=32 | base64 >> cred_param + * $ fido2-cred -M -i cred_param /dev/hidraw5 | fido2-cred -V -o cred + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-cred -M [-bdhqruvw] [-c cred_protect] [-i input_file] [-o output_file] device [type]\n" +" fido2-cred -V [-dhv] [-c cred_protect] [-i input_file] [-o output_file] [type]\n" + ); + + exit(1); +} + +int +main(int argc, char **argv) +{ + if (argc < 2 || strlen(argv[1]) != 2 || argv[1][0] != '-') + usage(); + + switch (argv[1][1]) { + case 'M': + return (cred_make(--argc, ++argv)); + case 'V': + return (cred_verify(--argc, ++argv)); + } + + usage(); + + /* NOTREACHED */ +} diff --git a/tools/fido2-detach.sh b/tools/fido2-detach.sh new file mode 100755 index 0000000..140278f --- /dev/null +++ b/tools/fido2-detach.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# Copyright (c) 2020 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')" + +while [ -n "${DEV}" ]; do + sleep .5 + DEV="$(fido2-token -L | sed 's/^\(.*\): .*$/\1/;q')" +done diff --git a/tools/fido2-token.c b/tools/fido2-token.c new file mode 100644 index 0000000..412c2f9 --- /dev/null +++ b/tools/fido2-token.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static int action; + +void +usage(void) +{ + fprintf(stderr, +"usage: fido2-token -C [-d] device\n" +" fido2-token -Db [-k key_path] [-i cred_id -n rp_id] device\n" +" fido2-token -Dei template_id device\n" +" fido2-token -Du device\n" +" fido2-token -Gb [-k key_path] [-i cred_id -n rp_id] blob_path device\n" +" fido2-token -I [-cd] [-k rp_id -i cred_id] device\n" +" fido2-token -L [-bder] [-k rp_id] [device]\n" +" fido2-token -R [-d] device\n" +" fido2-token -S [-adefu] [-l pin_length] [-i template_id -n template_name] device\n" +" fido2-token -Sb [-k key_path] [-i cred_id -n rp_id] blob_path device\n" +" fido2-token -Sc -i cred_id -k user_id -n name -p display_name device\n" +" fido2-token -Sm rp_id device\n" +" fido2-token -V\n" + ); + + exit(1); +} + +static void +setaction(int ch) +{ + if (action) + usage(); + action = ch; +} + +int +main(int argc, char **argv) +{ + int ch; + int flags = 0; + char *device; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'a': + case 'b': + case 'c': + case 'e': + case 'f': + case 'i': + case 'k': + case 'l': + case 'm': + case 'n': + case 'p': + case 'r': + case 'u': + break; /* ignore */ + case 'd': + flags = FIDO_DEBUG; + break; + default: + setaction(ch); + break; + } + } + + if (argc - optind < 1) + device = NULL; + else + device = argv[argc - 1]; + + fido_init(flags); + + switch (action) { + case 'C': + return (pin_change(device)); + case 'D': + return (token_delete(argc, argv, device)); + case 'G': + return (token_get(argc, argv, device)); + case 'I': + return (token_info(argc, argv, device)); + case 'L': + return (token_list(argc, argv, device)); + case 'R': + return (token_reset(device)); + case 'S': + return (token_set(argc, argv, device)); + case 'V': + fprintf(stderr, "%d.%d.%d\n", _FIDO_MAJOR, _FIDO_MINOR, + _FIDO_PATCH); + exit(0); + } + + usage(); + + /* NOTREACHED */ +} diff --git a/tools/fido2-unprot.sh b/tools/fido2-unprot.sh new file mode 100755 index 0000000..7d8c779 --- /dev/null +++ b/tools/fido2-unprot.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +# Copyright (c) 2020 Fabian Henneke. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + + +if [ $(uname) != "Linux" ] ; then + echo "Can only run on Linux" + exit 1 +fi + +TOKEN_VERSION=$(${FIDO_TOOLS_PREFIX}fido2-token -V 2>&1) +if [ $? -ne 0 ] ; then + echo "Please install libfido2 1.5.0 or higher" + exit +fi + +TOKEN_VERSION_MAJOR=$(echo "$TOKEN_VERSION" | cut -d. -f1) +TOKEN_VERSION_MINOR=$(echo "$TOKEN_VERSION" | cut -d. -f2) +if [ $TOKEN_VERSION_MAJOR -eq 0 -o $TOKEN_VERSION_MAJOR -eq 1 -a $TOKEN_VERSION_MINOR -lt 5 ] ; then + echo "Please install libfido2 1.5.0 or higher (current version: $TOKEN_VERSION)" + exit 1 +fi + +set -e + +TOKEN_OUTPUT=$(${FIDO_TOOLS_PREFIX}fido2-token -L) +DEV_PATH_NAMES=$(echo "$TOKEN_OUTPUT" | sed -r 's/^(.*): .*\((.*)\)$/\1 \2/g') +DEV_COUNT=$(echo "$DEV_PATH_NAMES" | wc -l) + +for i in $(seq 1 $DEV_COUNT) +do + DEV_PATH_NAME=$(echo "$DEV_PATH_NAMES" | sed "${i}q;d") + DEV_PATH=$(echo "$DEV_PATH_NAME" | cut -d' ' -f1) + DEV_NAME=$(echo "$DEV_PATH_NAME" | cut -d' ' -f1 --complement) + DEV_PRETTY=$(echo "$DEV_NAME (at '$DEV_PATH')") + if expr match "$(${FIDO_TOOLS_PREFIX}fido2-token -I $DEV_PATH)" ".* credMgmt.* clientPin.*\|.* clientPin.* credMgmt.*" > /dev/null ; then + printf "Enter PIN for $DEV_PRETTY once (ignore further prompts): " + stty -echo + read PIN + stty echo + printf "\n" + RESIDENT_RPS=$(echo "${PIN}\n" | setsid -w ${FIDO_TOOLS_PREFIX}fido2-token -L -r $DEV_PATH | cut -d' ' -f3) + printf "\n" + RESIDENT_RPS_COUNT=$(echo "$RESIDENT_RPS" | wc -l) + FOUND=0 + for j in $(seq 1 $DEV_RESIDENT_RPS_COUNT) + do + RESIDENT_RP=$(echo "$RESIDENT_RPS" | sed "${j}q;d") + UNPROT_CREDS=$(echo "${PIN}\n" | setsid -w ${FIDO_TOOLS_PREFIX}fido2-token -L -k $RESIDENT_RP $DEV_PATH | grep ' uvopt$' | cut -d' ' -f2,3,4) + printf "\n" + UNPROT_CREDS_COUNT=$(echo "$UNPROT_CREDS" | wc -l) + if [ $UNPROT_CREDS_COUNT -gt 0 ] ; then + FOUND=1 + echo "Unprotected credentials on $DEV_PRETTY for '$RESIDENT_RP':" + echo "$UNPROT_CREDS" + fi + done + if [ $FOUND -eq 0 ] ; then + echo "No unprotected credentials on $DEV_PRETTY" + fi + else + echo "$DEV_PRETTY cannot enumerate credentials" + echo "Discovering unprotected SSH credentials only..." + STUB_HASH=$(echo -n "" | openssl sha256 -binary | base64) + printf "$STUB_HASH\nssh:\n" | ${FIDO_TOOLS_PREFIX}fido2-assert -G -r -t up=false $DEV_PATH 2> /dev/null || ASSERT_EXIT_CODE=$? + if [ $ASSERT_EXIT_CODE -eq 0 ] ; then + echo "Found an unprotected SSH credential on $DEV_PRETTY!" + else + echo "No unprotected SSH credentials (default settings) on $DEV_PRETTY" + fi + fi + printf "\n" +done diff --git a/tools/include_check.sh b/tools/include_check.sh new file mode 100755 index 0000000..70abada --- /dev/null +++ b/tools/include_check.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# Copyright (c) 2019 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +check() { + for f in $(find $1 -maxdepth 1 -name '*.h'); do + echo "#include \"$f\"" | \ + cc $CFLAGS -Isrc -xc -c - -o /dev/null 2>&1 + echo "$f $CFLAGS $?" + done +} + +check examples +check fuzz +check openbsd-compat +CFLAGS="${CFLAGS} -D_FIDO_INTERNAL" check src +check src/fido.h +check src/fido +check tools diff --git a/tools/largeblob.c b/tools/largeblob.c new file mode 100644 index 0000000..78b97ab --- /dev/null +++ b/tools/largeblob.c @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2020-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <fido.h> +#include <fido/credman.h> + +#include <cbor.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <zlib.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +#define BOUND (1024UL * 1024UL) + +struct rkmap { + fido_credman_rp_t *rp; /* known rps */ + fido_credman_rk_t **rk; /* rk per rp */ +}; + +static void +free_rkmap(struct rkmap *map) +{ + if (map->rp != NULL) { + for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) + fido_credman_rk_free(&map->rk[i]); + fido_credman_rp_free(&map->rp); + } + free(map->rk); +} + +static int +map_known_rps(fido_dev_t *dev, const char *path, struct rkmap *map) +{ + const char *rp_id; + char *pin = NULL; + size_t n; + int r, ok = -1; + + if ((map->rp = fido_credman_rp_new()) == NULL) { + warnx("%s: fido_credman_rp_new", __func__); + goto out; + } + if ((pin = get_pin(path)) == NULL) + goto out; + if ((r = fido_credman_get_dev_rp(dev, map->rp, pin)) != FIDO_OK) { + warnx("fido_credman_get_dev_rp: %s", fido_strerr(r)); + goto out; + } + if ((n = fido_credman_rp_count(map->rp)) > UINT8_MAX) { + warnx("%s: fido_credman_rp_count > UINT8_MAX", __func__); + goto out; + } + if ((map->rk = calloc(n, sizeof(*map->rk))) == NULL) { + warnx("%s: calloc", __func__); + goto out; + } + for (size_t i = 0; i < n; i++) { + if ((rp_id = fido_credman_rp_id(map->rp, i)) == NULL) { + warnx("%s: fido_credman_rp_id %zu", __func__, i); + goto out; + } + if ((map->rk[i] = fido_credman_rk_new()) == NULL) { + warnx("%s: fido_credman_rk_new", __func__); + goto out; + } + if ((r = fido_credman_get_dev_rk(dev, rp_id, map->rk[i], + pin)) != FIDO_OK) { + warnx("%s: fido_credman_get_dev_rk %s: %s", __func__, + rp_id, fido_strerr(r)); + goto out; + } + } + + ok = 0; +out: + freezero(pin, PINBUF_LEN); + + return ok; +} + +static int +lookup_key(const char *path, fido_dev_t *dev, const char *rp_id, + const struct blob *cred_id, char **pin, struct blob *key) +{ + fido_credman_rk_t *rk = NULL; + const fido_cred_t *cred = NULL; + size_t i, n; + int r, ok = -1; + + if ((rk = fido_credman_rk_new()) == NULL) { + warnx("%s: fido_credman_rk_new", __func__); + goto out; + } + if ((r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin)) != FIDO_OK && + *pin == NULL && should_retry_with_pin(dev, r)) { + if ((*pin = get_pin(path)) == NULL) + goto out; + r = fido_credman_get_dev_rk(dev, rp_id, rk, *pin); + } + if (r != FIDO_OK) { + warnx("%s: fido_credman_get_dev_rk: %s", __func__, + fido_strerr(r)); + goto out; + } + if ((n = fido_credman_rk_count(rk)) == 0) { + warnx("%s: rp id not found", __func__); + goto out; + } + if (n == 1 && cred_id->len == 0) { + /* use the credential we found */ + cred = fido_credman_rk(rk, 0); + } else { + if (cred_id->len == 0) { + warnx("%s: multiple credentials found", __func__); + goto out; + } + for (i = 0; i < n; i++) { + const fido_cred_t *x = fido_credman_rk(rk, i); + if (fido_cred_id_len(x) <= cred_id->len && + !memcmp(fido_cred_id_ptr(x), cred_id->ptr, + fido_cred_id_len(x))) { + cred = x; + break; + } + } + } + if (cred == NULL) { + warnx("%s: credential not found", __func__); + goto out; + } + if (fido_cred_largeblob_key_ptr(cred) == NULL) { + warnx("%s: no associated blob key", __func__); + goto out; + } + key->len = fido_cred_largeblob_key_len(cred); + if ((key->ptr = malloc(key->len)) == NULL) { + warnx("%s: malloc", __func__); + goto out; + } + memcpy(key->ptr, fido_cred_largeblob_key_ptr(cred), key->len); + + ok = 0; +out: + fido_credman_rk_free(&rk); + + return ok; +} + +static int +load_key(const char *keyf, const char *cred_id64, const char *rp_id, + const char *path, fido_dev_t *dev, char **pin, struct blob *key) +{ + struct blob cred_id; + FILE *fp; + int r; + + memset(&cred_id, 0, sizeof(cred_id)); + + if (keyf != NULL) { + if (rp_id != NULL || cred_id64 != NULL) + usage(); + fp = open_read(keyf); + if ((r = base64_read(fp, key)) < 0) + warnx("%s: base64_read %s", __func__, keyf); + fclose(fp); + return r; + } + if (rp_id == NULL) + usage(); + if (cred_id64 != NULL && base64_decode(cred_id64, (void *)&cred_id.ptr, + &cred_id.len) < 0) { + warnx("%s: base64_decode %s", __func__, cred_id64); + return -1; + } + r = lookup_key(path, dev, rp_id, &cred_id, pin, key); + free(cred_id.ptr); + + return r; +} + +int +blob_set(const char *path, const char *keyf, const char *rp_id, + const char *cred_id64, const char *blobf) +{ + fido_dev_t *dev; + struct blob key, blob; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + memset(&key, 0, sizeof(key)); + memset(&blob, 0, sizeof(blob)); + + if (read_file(blobf, &blob.ptr, &blob.len) < 0 || + load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0) + goto out; + if ((r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr, + blob.len, pin)) != FIDO_OK && should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_largeblob_set(dev, key.ptr, key.len, blob.ptr, + blob.len, pin); + } + if (r != FIDO_OK) { + warnx("fido_dev_largeblob_set: %s", fido_strerr(r)); + goto out; + } + + ok = 0; /* success */ +out: + freezero(key.ptr, key.len); + freezero(blob.ptr, blob.len); + freezero(pin, PINBUF_LEN); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +blob_get(const char *path, const char *keyf, const char *rp_id, + const char *cred_id64, const char *blobf) +{ + fido_dev_t *dev; + struct blob key, blob; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + memset(&key, 0, sizeof(key)); + memset(&blob, 0, sizeof(blob)); + + if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0) + goto out; + if ((r = fido_dev_largeblob_get(dev, key.ptr, key.len, &blob.ptr, + &blob.len)) != FIDO_OK) { + warnx("fido_dev_largeblob_get: %s", fido_strerr(r)); + goto out; + } + if (write_file(blobf, blob.ptr, blob.len) < 0) + goto out; + + ok = 0; /* success */ +out: + freezero(key.ptr, key.len); + freezero(blob.ptr, blob.len); + freezero(pin, PINBUF_LEN); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +int +blob_delete(const char *path, const char *keyf, const char *rp_id, + const char *cred_id64) +{ + fido_dev_t *dev; + struct blob key; + char *pin = NULL; + int r, ok = 1; + + dev = open_dev(path); + memset(&key, 0, sizeof(key)); + + if (load_key(keyf, cred_id64, rp_id, path, dev, &pin, &key) < 0) + goto out; + if ((r = fido_dev_largeblob_remove(dev, key.ptr, key.len, + pin)) != FIDO_OK && should_retry_with_pin(dev, r)) { + if ((pin = get_pin(path)) == NULL) + goto out; + r = fido_dev_largeblob_remove(dev, key.ptr, key.len, pin); + } + if (r != FIDO_OK) { + warnx("fido_dev_largeblob_remove: %s", fido_strerr(r)); + goto out; + } + + ok = 0; /* success */ +out: + freezero(key.ptr, key.len); + freezero(pin, PINBUF_LEN); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} + +static int +try_decompress(const struct blob *in, uint64_t origsiz, int wbits) +{ + struct blob out; + z_stream zs; + u_int ilen, olen; + int ok = -1; + + memset(&zs, 0, sizeof(zs)); + memset(&out, 0, sizeof(out)); + + if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND) + return -1; + if (origsiz > SIZE_MAX || origsiz > UINT_MAX || + (olen = (u_int)origsiz) > BOUND) + return -1; + if (inflateInit2(&zs, wbits) != Z_OK) + return -1; + + if ((out.ptr = calloc(1, olen)) == NULL) + goto fail; + + out.len = olen; + zs.next_in = in->ptr; + zs.avail_in = ilen; + zs.next_out = out.ptr; + zs.avail_out = olen; + + if (inflate(&zs, Z_FINISH) != Z_STREAM_END) + goto fail; + if (zs.avail_out != 0) + goto fail; + + ok = 0; +fail: + if (inflateEnd(&zs) != Z_OK) + ok = -1; + + freezero(out.ptr, out.len); + + return ok; +} + +static int +decompress(const struct blob *plaintext, uint64_t origsiz) +{ + if (try_decompress(plaintext, origsiz, MAX_WBITS) == 0) /* rfc1950 */ + return 0; + return try_decompress(plaintext, origsiz, -MAX_WBITS); /* rfc1951 */ +} + +static int +decode(const struct blob *ciphertext, const struct blob *nonce, + uint64_t origsiz, const fido_cred_t *cred) +{ + uint8_t aad[4 + sizeof(uint64_t)]; + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher; + struct blob plaintext; + uint64_t tmp; + int ok = -1; + + memset(&plaintext, 0, sizeof(plaintext)); + + if (nonce->len != 12) + return -1; + if (cred == NULL || + fido_cred_largeblob_key_ptr(cred) == NULL || + fido_cred_largeblob_key_len(cred) != 32) + return -1; + if (ciphertext->len > UINT_MAX || + ciphertext->len > SIZE_MAX - 16 || + ciphertext->len < 16) + return -1; + plaintext.len = ciphertext->len - 16; + if ((plaintext.ptr = calloc(1, plaintext.len)) == NULL) + return -1; + if ((ctx = EVP_CIPHER_CTX_new()) == NULL || + (cipher = EVP_aes_256_gcm()) == NULL || + EVP_CipherInit(ctx, cipher, fido_cred_largeblob_key_ptr(cred), + nonce->ptr, 0) == 0) + goto out; + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, + ciphertext->ptr + ciphertext->len - 16) == 0) + goto out; + aad[0] = 0x62; /* b */ + aad[1] = 0x6c; /* l */ + aad[2] = 0x6f; /* o */ + aad[3] = 0x62; /* b */ + tmp = htole64(origsiz); + memcpy(&aad[4], &tmp, sizeof(uint64_t)); + if (EVP_Cipher(ctx, NULL, aad, (u_int)sizeof(aad)) < 0 || + EVP_Cipher(ctx, plaintext.ptr, ciphertext->ptr, + (u_int)plaintext.len) < 0 || + EVP_Cipher(ctx, NULL, NULL, 0) < 0) + goto out; + if (decompress(&plaintext, origsiz) < 0) + goto out; + + ok = 0; +out: + freezero(plaintext.ptr, plaintext.len); + + if (ctx != NULL) + EVP_CIPHER_CTX_free(ctx); + + return ok; +} + +static const fido_cred_t * +try_rp(const fido_credman_rk_t *rk, const struct blob *ciphertext, + const struct blob *nonce, uint64_t origsiz) +{ + const fido_cred_t *cred; + + for (size_t i = 0; i < fido_credman_rk_count(rk); i++) + if ((cred = fido_credman_rk(rk, i)) != NULL && + decode(ciphertext, nonce, origsiz, cred) == 0) + return cred; + + return NULL; +} + +static int +decode_cbor_blob(struct blob *out, const cbor_item_t *item) +{ + if (out->ptr != NULL || + cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) + return -1; + out->len = cbor_bytestring_length(item); + if ((out->ptr = malloc(out->len)) == NULL) + return -1; + memcpy(out->ptr, cbor_bytestring_handle(item), out->len); + + return 0; +} + +static int +decode_blob_entry(const cbor_item_t *item, struct blob *ciphertext, + struct blob *nonce, uint64_t *origsiz) +{ + struct cbor_pair *v; + + if (item == NULL) + return -1; + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + (v = cbor_map_handle(item)) == NULL) + return -1; + if (cbor_map_size(item) > UINT8_MAX) + return -1; + + for (size_t i = 0; i < cbor_map_size(item); i++) { + if (cbor_isa_uint(v[i].key) == false || + cbor_int_get_width(v[i].key) != CBOR_INT_8) + continue; /* ignore */ + switch (cbor_get_uint8(v[i].key)) { + case 1: /* ciphertext */ + if (decode_cbor_blob(ciphertext, v[i].value) < 0) + return -1; + break; + case 2: /* nonce */ + if (decode_cbor_blob(nonce, v[i].value) < 0) + return -1; + break; + case 3: /* origSize */ + if (*origsiz != 0 || + cbor_isa_uint(v[i].value) == false || + (*origsiz = cbor_get_int(v[i].value)) > SIZE_MAX) + return -1; + } + } + if (ciphertext->ptr == NULL || nonce->ptr == NULL || *origsiz == 0) + return -1; + + return 0; +} + +static void +print_blob_entry(size_t idx, const cbor_item_t *item, const struct rkmap *map) +{ + struct blob ciphertext, nonce; + const fido_cred_t *cred = NULL; + const char *rp_id = NULL; + char *cred_id = NULL; + uint64_t origsiz = 0; + + memset(&ciphertext, 0, sizeof(ciphertext)); + memset(&nonce, 0, sizeof(nonce)); + + if (decode_blob_entry(item, &ciphertext, &nonce, &origsiz) < 0) { + printf("%02zu: <skipped: bad cbor>\n", idx); + goto out; + } + for (size_t i = 0; i < fido_credman_rp_count(map->rp); i++) { + if ((cred = try_rp(map->rk[i], &ciphertext, &nonce, + origsiz)) != NULL) { + rp_id = fido_credman_rp_id(map->rp, i); + break; + } + } + if (cred == NULL) { + if ((cred_id = strdup("<unknown>")) == NULL) { + printf("%02zu: <skipped: strdup failed>\n", idx); + goto out; + } + } else { + if (base64_encode(fido_cred_id_ptr(cred), + fido_cred_id_len(cred), &cred_id) < 0) { + printf("%02zu: <skipped: base64_encode failed>\n", idx); + goto out; + } + } + if (rp_id == NULL) + rp_id = "<unknown>"; + + printf("%02zu: %4zu %4zu %s %s\n", idx, ciphertext.len, + (size_t)origsiz, cred_id, rp_id); +out: + free(ciphertext.ptr); + free(nonce.ptr); + free(cred_id); +} + +static cbor_item_t * +get_cbor_array(fido_dev_t *dev) +{ + struct cbor_load_result cbor_result; + cbor_item_t *item = NULL; + u_char *cbor_ptr = NULL; + size_t cbor_len; + int r, ok = -1; + + if ((r = fido_dev_largeblob_get_array(dev, &cbor_ptr, + &cbor_len)) != FIDO_OK) { + warnx("%s: fido_dev_largeblob_get_array: %s", __func__, + fido_strerr(r)); + goto out; + } + if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) { + warnx("%s: cbor_load", __func__); + goto out; + } + if (cbor_result.read != cbor_len) { + warnx("%s: cbor_result.read (%zu) != cbor_len (%zu)", __func__, + cbor_result.read, cbor_len); + /* continue */ + } + if (cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false) { + warnx("%s: cbor type", __func__); + goto out; + } + if (cbor_array_size(item) > UINT8_MAX) { + warnx("%s: cbor_array_size > UINT8_MAX", __func__); + goto out; + } + if (cbor_array_size(item) == 0) { + ok = 0; /* nothing to do */ + goto out; + } + + printf("total map size: %zu byte%s\n", cbor_len, plural(cbor_len)); + + ok = 0; +out: + if (ok < 0 && item != NULL) { + cbor_decref(&item); + item = NULL; + } + free(cbor_ptr); + + return item; +} + +int +blob_list(const char *path) +{ + struct rkmap map; + fido_dev_t *dev = NULL; + cbor_item_t *item = NULL, **v; + int ok = 1; + + memset(&map, 0, sizeof(map)); + dev = open_dev(path); + if (map_known_rps(dev, path, &map) < 0 || + (item = get_cbor_array(dev)) == NULL) + goto out; + if (cbor_array_size(item) == 0) { + ok = 0; /* nothing to do */ + goto out; + } + if ((v = cbor_array_handle(item)) == NULL) { + warnx("%s: cbor_array_handle", __func__); + goto out; + } + for (size_t i = 0; i < cbor_array_size(item); i++) + print_blob_entry(i, v[i], &map); + + ok = 0; /* success */ +out: + free_rkmap(&map); + + if (item != NULL) + cbor_decref(&item); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(ok); +} diff --git a/tools/pin.c b/tools/pin.c new file mode 100644 index 0000000..8b2697e --- /dev/null +++ b/tools/pin.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +int +pin_set(char *path) +{ + fido_dev_t *dev = NULL; + char prompt[1024]; + char pin1[128]; + char pin2[128]; + int r; + int status = 1; + + dev = open_dev(path); + + r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: "); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + if (strcmp(pin1, pin2) != 0) { + fprintf(stderr, "PINs do not match. Try again.\n"); + goto out; + } + + if (strlen(pin1) < 4 || strlen(pin1) > 63) { + fprintf(stderr, "invalid PIN length\n"); + goto out; + } + + if ((r = fido_dev_set_pin(dev, pin1, NULL)) != FIDO_OK) { + warnx("fido_dev_set_pin: %s", fido_strerr(r)); + goto out; + } + + fido_dev_close(dev); + fido_dev_free(&dev); + + status = 0; +out: + explicit_bzero(pin1, sizeof(pin1)); + explicit_bzero(pin2, sizeof(pin2)); + + exit(status); +} + +int +pin_change(char *path) +{ + fido_dev_t *dev = NULL; + char prompt[1024]; + char pin0[128]; + char pin1[128]; + char pin2[128]; + int r; + int status = 1; + + if (path == NULL) + usage(); + + dev = open_dev(path); + + r = snprintf(prompt, sizeof(prompt), "Enter current PIN for %s: ", path); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin0, sizeof(pin0), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + if (strlen(pin0) < 4 || strlen(pin0) > 63) { + warnx("invalid PIN length"); + goto out; + } + + r = snprintf(prompt, sizeof(prompt), "Enter new PIN for %s: ", path); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin1, sizeof(pin1), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + r = snprintf(prompt, sizeof(prompt), "Enter the same PIN again: "); + if (r < 0 || (size_t)r >= sizeof(prompt)) { + warnx("snprintf"); + goto out; + } + + if (!readpassphrase(prompt, pin2, sizeof(pin2), RPP_ECHO_OFF)) { + warnx("readpassphrase"); + goto out; + } + + if (strcmp(pin1, pin2) != 0) { + fprintf(stderr, "PINs do not match. Try again.\n"); + goto out; + } + + if (strlen(pin1) < 4 || strlen(pin1) > 63) { + fprintf(stderr, "invalid PIN length\n"); + goto out; + } + + if ((r = fido_dev_set_pin(dev, pin1, pin0)) != FIDO_OK) { + warnx("fido_dev_set_pin: %s", fido_strerr(r)); + goto out; + } + + fido_dev_close(dev); + fido_dev_free(&dev); + + status = 0; +out: + explicit_bzero(pin0, sizeof(pin0)); + explicit_bzero(pin1, sizeof(pin1)); + explicit_bzero(pin2, sizeof(pin2)); + + exit(status); +} diff --git a/tools/test.sh b/tools/test.sh new file mode 100755 index 0000000..67b757e --- /dev/null +++ b/tools/test.sh @@ -0,0 +1,304 @@ +#!/bin/sh -ex + +# Copyright (c) 2021-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +# usage: ./test.sh "$(mktemp -d fido2test-XXXXXXXX)" device + +# Please note that this test script: +# - is incomplete; +# - assumes CTAP 2.1-like hmac-secret; +# - should pass as-is on a YubiKey with a PIN set; +# - may otherwise require set +e above; +# - can be executed with UV=1 to run additional UV tests; +# - was last tested on 2022-01-11 with firmware 5.4.3. + +cd "$1" +DEV="$2" +TYPE="es256" +#TYPE="es384" +#TYPE="eddsa" + +make_cred() { + sed /^$/d > cred_param << EOF +$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64) +$1 +some user name +$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64) +EOF + fido2-cred -M $2 "${DEV}" "${TYPE}" > "$3" < cred_param +} + +verify_cred() { + fido2-cred -V $1 "${TYPE}" > cred_out < "$2" + head -1 cred_out > "$3" + tail -n +2 cred_out > "$4" +} + +get_assert() { + sed /^$/d > assert_param << EOF +$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64) +$1 +$(cat $3) +$(cat $4) +EOF + fido2-assert -G $2 "${DEV}" > "$5" < assert_param +} + +verify_assert() { + fido2-assert -V $1 "$2" "${TYPE}" < "$3" +} + +dd if=/dev/urandom bs=32 count=1 | base64 > hmac-salt + +# u2f +if [ "x${TYPE}" = "xes256" ]; then + make_cred no.tld "-u" u2f + ! make_cred no.tld "-ru" /dev/null + ! make_cred no.tld "-uc1" /dev/null + ! make_cred no.tld "-uc2" /dev/null + verify_cred "--" u2f u2f-cred u2f-pubkey + ! verify_cred "-h" u2f /dev/null /dev/null + ! verify_cred "-v" u2f /dev/null /dev/null + verify_cred "-c0" u2f /dev/null /dev/null + ! verify_cred "-c1" u2f /dev/null /dev/null + ! verify_cred "-c2" u2f /dev/null /dev/null + ! verify_cred "-c3" u2f /dev/null /dev/null +fi + +# wrap (non-resident) +make_cred no.tld "--" wrap +verify_cred "--" wrap wrap-cred wrap-pubkey +! verify_cred "-h" wrap /dev/null /dev/null +! verify_cred "-v" wrap /dev/null /dev/null +verify_cred "-c0" wrap /dev/null /dev/null +! verify_cred "-c1" wrap /dev/null /dev/null +! verify_cred "-c2" wrap /dev/null /dev/null +! verify_cred "-c3" wrap /dev/null /dev/null + +# wrap (non-resident) + hmac-secret +make_cred no.tld "-h" wrap-hs +! verify_cred "--" wrap-hs /dev/null /dev/null +verify_cred "-h" wrap-hs wrap-hs-cred wrap-hs-pubkey +! verify_cred "-v" wrap-hs /dev/null /dev/null +verify_cred "-hc0" wrap-hs /dev/null /dev/null +! verify_cred "-c0" wrap-hs /dev/null /dev/null +! verify_cred "-c1" wrap-hs /dev/null /dev/null +! verify_cred "-c2" wrap-hs /dev/null /dev/null +! verify_cred "-c3" wrap-hs /dev/null /dev/null + +# resident +make_cred no.tld "-r" rk +verify_cred "--" rk rk-cred rk-pubkey +! verify_cred "-h" rk /dev/null /dev/null +! verify_cred "-v" rk /dev/null /dev/null +verify_cred "-c0" rk /dev/null /dev/null +! verify_cred "-c1" rk /dev/null /dev/null +! verify_cred "-c2" rk /dev/null /dev/null +! verify_cred "-c3" rk /dev/null /dev/null + +# resident + hmac-secret +make_cred no.tld "-hr" rk-hs +! verify_cred "--" rk-hs rk-hs-cred rk-hs-pubkey +verify_cred "-h" rk-hs /dev/null /dev/null +! verify_cred "-v" rk-hs /dev/null /dev/null +verify_cred "-hc0" rk-hs /dev/null /dev/null +! verify_cred "-c0" rk-hs /dev/null /dev/null +! verify_cred "-c1" rk-hs /dev/null /dev/null +! verify_cred "-c2" rk-hs /dev/null /dev/null +! verify_cred "-c3" rk-hs /dev/null /dev/null + +# u2f +if [ "x${TYPE}" = "xes256" ]; then + get_assert no.tld "-u" u2f-cred /dev/null u2f-assert + ! get_assert no.tld "-u -t up=false" u2f-cred /dev/null /dev/null + verify_assert "--" u2f-pubkey u2f-assert + verify_assert "-p" u2f-pubkey u2f-assert +fi + +# wrap (non-resident) +get_assert no.tld "--" wrap-cred /dev/null wrap-assert +verify_assert "--" wrap-pubkey wrap-assert +get_assert no.tld "-t pin=true" wrap-cred /dev/null wrap-assert +verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-v" wrap-pubkey wrap-assert +get_assert no.tld "-t pin=false" wrap-cred /dev/null wrap-assert +verify_assert "--" wrap-pubkey wrap-assert +get_assert no.tld "-t up=true" wrap-cred /dev/null wrap-assert +verify_assert "-p" wrap-pubkey wrap-assert +get_assert no.tld "-t up=true -t pin=true" wrap-cred /dev/null wrap-assert +verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-p" wrap-pubkey wrap-assert +verify_assert "-v" wrap-pubkey wrap-assert +verify_assert "-pv" wrap-pubkey wrap-assert +get_assert no.tld "-t up=true -t pin=false" wrap-cred /dev/null wrap-assert +verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-p" wrap-pubkey wrap-assert +get_assert no.tld "-t up=false" wrap-cred /dev/null wrap-assert +verify_assert "--" wrap-pubkey wrap-assert +! verify_assert "-p" wrap-pubkey wrap-assert +get_assert no.tld "-t up=false -t pin=true" wrap-cred /dev/null wrap-assert +! verify_assert "-p" wrap-pubkey wrap-assert +verify_assert "-v" wrap-pubkey wrap-assert +! verify_assert "-pv" wrap-pubkey wrap-assert +get_assert no.tld "-t up=false -t pin=false" wrap-cred /dev/null wrap-assert +! verify_assert "-p" wrap-pubkey wrap-assert +get_assert no.tld "-h" wrap-cred hmac-salt wrap-assert +! verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-h" wrap-pubkey wrap-assert +get_assert no.tld "-h -t pin=true" wrap-cred hmac-salt wrap-assert +! verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-h" wrap-pubkey wrap-assert +verify_assert "-hv" wrap-pubkey wrap-assert +get_assert no.tld "-h -t pin=false" wrap-cred hmac-salt wrap-assert +! verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-h" wrap-pubkey wrap-assert +get_assert no.tld "-h -t up=true" wrap-cred hmac-salt wrap-assert +! verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-h" wrap-pubkey wrap-assert +verify_assert "-hp" wrap-pubkey wrap-assert +get_assert no.tld "-h -t up=true -t pin=true" wrap-cred hmac-salt wrap-assert +! verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-h" wrap-pubkey wrap-assert +verify_assert "-hp" wrap-pubkey wrap-assert +verify_assert "-hv" wrap-pubkey wrap-assert +verify_assert "-hpv" wrap-pubkey wrap-assert +get_assert no.tld "-h -t up=true -t pin=false" wrap-cred hmac-salt wrap-assert +! verify_assert "--" wrap-pubkey wrap-assert +verify_assert "-h" wrap-pubkey wrap-assert +verify_assert "-hp" wrap-pubkey wrap-assert +! get_assert no.tld "-h -t up=false" wrap-cred hmac-salt wrap-assert +! get_assert no.tld "-h -t up=false -t pin=true" wrap-cred hmac-salt wrap-assert +! get_assert no.tld "-h -t up=false -t pin=false" wrap-cred hmac-salt wrap-assert + +if [ "x${UV}" != "x" ]; then + get_assert no.tld "-t uv=true" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t uv=true -t pin=true" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t uv=true -t pin=false" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t uv=false" wrap-cred /dev/null wrap-assert + verify_assert "--" wrap-pubkey wrap-assert + get_assert no.tld "-t uv=false -t pin=true" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t uv=false -t pin=false" wrap-cred /dev/null wrap-assert + verify_assert "--" wrap-pubkey wrap-assert + get_assert no.tld "-t up=true -t uv=true" wrap-cred /dev/null wrap-assert + verify_assert "-pv" wrap-pubkey wrap-assert + get_assert no.tld "-t up=true -t uv=true -t pin=true" wrap-cred /dev/null wrap-assert + verify_assert "-pv" wrap-pubkey wrap-assert + get_assert no.tld "-t up=true -t uv=true -t pin=false" wrap-cred /dev/null wrap-assert + verify_assert "-pv" wrap-pubkey wrap-assert + get_assert no.tld "-t up=true -t uv=false" wrap-cred /dev/null wrap-assert + verify_assert "-p" wrap-pubkey wrap-assert + get_assert no.tld "-t up=true -t uv=false -t pin=true" wrap-cred /dev/null wrap-assert + verify_assert "-pv" wrap-pubkey wrap-assert + get_assert no.tld "-t up=true -t uv=false -t pin=false" wrap-cred /dev/null wrap-assert + verify_assert "-p" wrap-pubkey wrap-assert + get_assert no.tld "-t up=false -t uv=true" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t up=false -t uv=true -t pin=true" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t up=false -t uv=true -t pin=false" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t up=false -t uv=false" wrap-cred /dev/null wrap-assert + ! verify_assert "--" wrap-pubkey wrap-assert + get_assert no.tld "-t up=false -t uv=false -t pin=true" wrap-cred /dev/null wrap-assert + verify_assert "-v" wrap-pubkey wrap-assert + get_assert no.tld "-t up=false -t uv=false -t pin=false" wrap-cred /dev/null wrap-assert + ! verify_assert "--" wrap-pubkey wrap-assert + get_assert no.tld "-h -t uv=true" wrap-cred hmac-salt wrap-assert + verify_assert "-hv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert + verify_assert "-hv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert + verify_assert "-hv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t uv=false" wrap-cred hmac-salt wrap-assert + verify_assert "-h" wrap-pubkey wrap-assert + get_assert no.tld "-h -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert + verify_assert "-hv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert + verify_assert "-h" wrap-pubkey wrap-assert + get_assert no.tld "-h -t up=true -t uv=true" wrap-cred hmac-salt wrap-assert + verify_assert "-hpv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t up=true -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert + verify_assert "-hpv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t up=true -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert + verify_assert "-hpv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t up=true -t uv=false" wrap-cred hmac-salt wrap-assert + verify_assert "-hp" wrap-pubkey wrap-assert + get_assert no.tld "-h -t up=true -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert + verify_assert "-hpv" wrap-pubkey wrap-assert + get_assert no.tld "-h -t up=true -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert + verify_assert "-hp" wrap-pubkey wrap-assert + ! get_assert no.tld "-h -t up=false -t uv=true" wrap-cred hmac-salt wrap-assert + ! get_assert no.tld "-h -t up=false -t uv=true -t pin=true" wrap-cred hmac-salt wrap-assert + ! get_assert no.tld "-h -t up=false -t uv=true -t pin=false" wrap-cred hmac-salt wrap-assert + ! get_assert no.tld "-h -t up=false -t uv=false" wrap-cred hmac-salt wrap-assert + ! get_assert no.tld "-h -t up=false -t uv=false -t pin=true" wrap-cred hmac-salt wrap-assert + ! get_assert no.tld "-h -t up=false -t uv=false -t pin=false" wrap-cred hmac-salt wrap-assert +fi + +# resident +get_assert no.tld "-r" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t pin=true" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t pin=false" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t up=true" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t up=true -t pin=true" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t up=true -t pin=false" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t up=false" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t up=false -t pin=true" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -t up=false -t pin=false" /dev/null /dev/null wrap-assert +get_assert no.tld "-r -h" /dev/null hmac-salt wrap-assert +get_assert no.tld "-r -h -t pin=true" /dev/null hmac-salt wrap-assert +get_assert no.tld "-r -h -t pin=false" /dev/null hmac-salt wrap-assert +get_assert no.tld "-r -h -t up=true" /dev/null hmac-salt wrap-assert +get_assert no.tld "-r -h -t up=true -t pin=true" /dev/null hmac-salt wrap-assert +get_assert no.tld "-r -h -t up=true -t pin=false" /dev/null hmac-salt wrap-assert +! get_assert no.tld "-r -h -t up=false" /dev/null hmac-salt wrap-assert +! get_assert no.tld "-r -h -t up=false -t pin=true" /dev/null hmac-salt wrap-assert +! get_assert no.tld "-r -h -t up=false -t pin=false" /dev/null hmac-salt wrap-assert + +if [ "x${UV}" != "x" ]; then + get_assert no.tld "-r -t uv=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t uv=true -t pin=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t uv=true -t pin=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t uv=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t uv=false -t pin=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t uv=false -t pin=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=true -t uv=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=true -t uv=true -t pin=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=true -t uv=true -t pin=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=true -t uv=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=true -t uv=false -t pin=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=true -t uv=false -t pin=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=false -t uv=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=false -t uv=true -t pin=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=false -t uv=true -t pin=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=false -t uv=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=false -t uv=false -t pin=true" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -t up=false -t uv=false -t pin=false" /dev/null /dev/null wrap-assert + get_assert no.tld "-r -h -t uv=true" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t uv=false" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t up=true -t uv=true" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t up=true -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t up=true -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t up=true -t uv=false" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t up=true -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert + get_assert no.tld "-r -h -t up=true -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert + ! get_assert no.tld "-r -h -t up=false -t uv=true" /dev/null hmac-salt wrap-assert + ! get_assert no.tld "-r -h -t up=false -t uv=true -t pin=true" /dev/null hmac-salt wrap-assert + ! get_assert no.tld "-r -h -t up=false -t uv=true -t pin=false" /dev/null hmac-salt wrap-assert + ! get_assert no.tld "-r -h -t up=false -t uv=false" /dev/null hmac-salt wrap-assert + ! get_assert no.tld "-r -h -t up=false -t uv=false -t pin=true" /dev/null hmac-salt wrap-assert + ! get_assert no.tld "-r -h -t up=false -t uv=false -t pin=false" /dev/null hmac-salt wrap-assert +fi + +exit 0 diff --git a/tools/token.c b/tools/token.c new file mode 100644 index 0000000..366d5a1 --- /dev/null +++ b/tools/token.c @@ -0,0 +1,729 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <fido.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "../openbsd-compat/openbsd-compat.h" +#include "extern.h" + +static void +format_flags(char *ret, size_t retlen, uint8_t flags) +{ + memset(ret, 0, retlen); + + if (flags & FIDO_CAP_WINK) { + if (strlcat(ret, "wink,", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, "nowink,", retlen) >= retlen) + goto toolong; + } + + if (flags & FIDO_CAP_CBOR) { + if (strlcat(ret, " cbor,", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, " nocbor,", retlen) >= retlen) + goto toolong; + } + + if (flags & FIDO_CAP_NMSG) { + if (strlcat(ret, " nomsg", retlen) >= retlen) + goto toolong; + } else { + if (strlcat(ret, " msg", retlen) >= retlen) + goto toolong; + } + + return; +toolong: + strlcpy(ret, "toolong", retlen); +} + +static void +print_attr(const fido_dev_t *dev) +{ + char flags_txt[128]; + + printf("proto: 0x%02x\n", fido_dev_protocol(dev)); + printf("major: 0x%02x\n", fido_dev_major(dev)); + printf("minor: 0x%02x\n", fido_dev_minor(dev)); + printf("build: 0x%02x\n", fido_dev_build(dev)); + + format_flags(flags_txt, sizeof(flags_txt), fido_dev_flags(dev)); + printf("caps: 0x%02x (%s)\n", fido_dev_flags(dev), flags_txt); +} + +static void +print_str_array(const char *label, char * const *sa, size_t len) +{ + if (len == 0) + return; + + printf("%s strings: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s", i > 0 ? ", " : "", sa[i]); + + printf("\n"); +} + +static void +print_opt_array(const char *label, char * const *name, const bool *value, + size_t len) +{ + if (len == 0) + return; + + printf("%s: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s%s", i > 0 ? ", " : "", + value[i] ? "" : "no", name[i]); + + printf("\n"); +} + +static void +print_cert_array(const char *label, char * const *name, const uint64_t *value, + size_t len) +{ + if (len == 0) + return; + + printf("%s: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%s %llu", i > 0 ? ", " : "", name[i], + (unsigned long long)value[i]); + + printf("\n"); +} + +static void +print_algorithms(const fido_cbor_info_t *ci) +{ + const char *cose, *type; + size_t len; + + if ((len = fido_cbor_info_algorithm_count(ci)) == 0) + return; + + printf("algorithms: "); + + for (size_t i = 0; i < len; i++) { + cose = type = "unknown"; + switch (fido_cbor_info_algorithm_cose(ci, i)) { + case COSE_ES256: + cose = "es256"; + break; + case COSE_ES384: + cose = "es384"; + break; + case COSE_RS256: + cose = "rs256"; + break; + case COSE_EDDSA: + cose = "eddsa"; + break; + } + if (fido_cbor_info_algorithm_type(ci, i) != NULL) + type = fido_cbor_info_algorithm_type(ci, i); + printf("%s%s (%s)", i > 0 ? ", " : "", cose, type); + } + + printf("\n"); +} + +static void +print_aaguid(const unsigned char *buf, size_t buflen) +{ + printf("aaguid: "); + + while (buflen--) + printf("%02x", *buf++); + + printf("\n"); +} + +static void +print_maxmsgsiz(uint64_t maxmsgsiz) +{ + printf("maxmsgsiz: %d\n", (int)maxmsgsiz); +} + +static void +print_maxcredcntlst(uint64_t maxcredcntlst) +{ + printf("maxcredcntlst: %d\n", (int)maxcredcntlst); +} + +static void +print_maxcredidlen(uint64_t maxcredidlen) +{ + printf("maxcredlen: %d\n", (int)maxcredidlen); +} + +static void +print_maxlargeblob(uint64_t maxlargeblob) +{ + printf("maxlargeblob: %d\n", (int)maxlargeblob); +} + +static void +print_maxrpid_minpinlen(uint64_t maxrpid) +{ + if (maxrpid > 0) + printf("maxrpids in minpinlen: %d\n", (int)maxrpid); +} + +static void +print_minpinlen(uint64_t minpinlen) +{ + if (minpinlen > 0) + printf("minpinlen: %d\n", (int)minpinlen); +} + +static void +print_uv_attempts(uint64_t uv_attempts) +{ + if (uv_attempts > 0) + printf("platform uv attempt(s): %d\n", (int)uv_attempts); +} + +static void +print_uv_modality(uint64_t uv_modality) +{ + uint64_t mode; + bool printed = false; + + if (uv_modality == 0) + return; + + printf("uv modality: 0x%x (", (int)uv_modality); + + for (size_t i = 0; i < 64; i++) { + mode = 1ULL << i; + if ((uv_modality & mode) == 0) + continue; + if (printed) + printf(", "); + switch (mode) { + case FIDO_UV_MODE_TUP: + printf("test of user presence"); + break; + case FIDO_UV_MODE_FP: + printf("fingerprint check"); + break; + case FIDO_UV_MODE_PIN: + printf("pin check"); + break; + case FIDO_UV_MODE_VOICE: + printf("voice recognition"); + break; + case FIDO_UV_MODE_FACE: + printf("face recognition"); + break; + case FIDO_UV_MODE_LOCATION: + printf("location check"); + break; + case FIDO_UV_MODE_EYE: + printf("eyeprint check"); + break; + case FIDO_UV_MODE_DRAWN: + printf("drawn pattern check"); + break; + case FIDO_UV_MODE_HAND: + printf("handprint verification"); + break; + case FIDO_UV_MODE_NONE: + printf("none"); + break; + case FIDO_UV_MODE_ALL: + printf("all required"); + break; + case FIDO_UV_MODE_EXT_PIN: + printf("external pin"); + break; + case FIDO_UV_MODE_EXT_DRAWN: + printf("external drawn pattern check"); + break; + default: + printf("unknown 0x%llx", (unsigned long long)mode); + break; + } + printed = true; + } + + printf(")\n"); +} + +static void +print_rk_remaining(int64_t rk_remaining) +{ + if (rk_remaining != -1) + printf("remaining rk(s): %d\n", (int)rk_remaining); +} + +static void +print_fwversion(uint64_t fwversion) +{ + printf("fwversion: 0x%x\n", (int)fwversion); +} + +static void +print_byte_array(const char *label, const uint8_t *ba, size_t len) +{ + if (len == 0) + return; + + printf("%s: ", label); + + for (size_t i = 0; i < len; i++) + printf("%s%u", i > 0 ? ", " : "", (unsigned)ba[i]); + + printf("\n"); +} + +int +token_info(int argc, char **argv, char *path) +{ + char *cred_id = NULL; + char *rp_id = NULL; + fido_cbor_info_t *ci = NULL; + fido_dev_t *dev = NULL; + int ch; + int credman = 0; + int r; + int retrycnt; + + optind = 1; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'c': + credman = 1; + break; + case 'i': + cred_id = optarg; + break; + case 'k': + rp_id = optarg; + break; + default: + break; /* ignore */ + } + } + + if (path == NULL || (credman && (cred_id != NULL || rp_id != NULL))) + usage(); + + dev = open_dev(path); + + if (credman) + return (credman_get_metadata(dev, path)); + if (cred_id && rp_id) + return (credman_print_rk(dev, path, rp_id, cred_id)); + if (cred_id || rp_id) + usage(); + + print_attr(dev); + + if (fido_dev_is_fido2(dev) == false) + goto end; + if ((ci = fido_cbor_info_new()) == NULL) + errx(1, "fido_cbor_info_new"); + if ((r = fido_dev_get_cbor_info(dev, ci)) != FIDO_OK) + errx(1, "fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r); + + /* print supported protocol versions */ + print_str_array("version", fido_cbor_info_versions_ptr(ci), + fido_cbor_info_versions_len(ci)); + + /* print supported extensions */ + print_str_array("extension", fido_cbor_info_extensions_ptr(ci), + fido_cbor_info_extensions_len(ci)); + + /* print supported transports */ + print_str_array("transport", fido_cbor_info_transports_ptr(ci), + fido_cbor_info_transports_len(ci)); + + /* print supported algorithms */ + print_algorithms(ci); + + /* print aaguid */ + print_aaguid(fido_cbor_info_aaguid_ptr(ci), + fido_cbor_info_aaguid_len(ci)); + + /* print supported options */ + print_opt_array("options", fido_cbor_info_options_name_ptr(ci), + fido_cbor_info_options_value_ptr(ci), + fido_cbor_info_options_len(ci)); + + /* print certifications */ + print_cert_array("certifications", fido_cbor_info_certs_name_ptr(ci), + fido_cbor_info_certs_value_ptr(ci), + fido_cbor_info_certs_len(ci)); + + /* print firmware version */ + print_fwversion(fido_cbor_info_fwversion(ci)); + + /* print maximum message size */ + print_maxmsgsiz(fido_cbor_info_maxmsgsiz(ci)); + + /* print maximum number of credentials allowed in credential lists */ + print_maxcredcntlst(fido_cbor_info_maxcredcntlst(ci)); + + /* print maximum length of a credential ID */ + print_maxcredidlen(fido_cbor_info_maxcredidlen(ci)); + + /* print maximum length of serialized largeBlob array */ + print_maxlargeblob(fido_cbor_info_maxlargeblob(ci)); + + /* print maximum number of RP IDs in fido_dev_set_pin_minlen_rpid() */ + print_maxrpid_minpinlen(fido_cbor_info_maxrpid_minpinlen(ci)); + + /* print estimated number of resident credentials */ + print_rk_remaining(fido_cbor_info_rk_remaining(ci)); + + /* print minimum pin length */ + print_minpinlen(fido_cbor_info_minpinlen(ci)); + + /* print supported pin protocols */ + print_byte_array("pin protocols", fido_cbor_info_protocols_ptr(ci), + fido_cbor_info_protocols_len(ci)); + + if (fido_dev_get_retry_count(dev, &retrycnt) != FIDO_OK) + printf("pin retries: undefined\n"); + else + printf("pin retries: %d\n", retrycnt); + + printf("pin change required: %s\n", + fido_cbor_info_new_pin_required(ci) ? "true" : "false"); + + if (fido_dev_get_uv_retry_count(dev, &retrycnt) != FIDO_OK) + printf("uv retries: undefined\n"); + else + printf("uv retries: %d\n", retrycnt); + + /* print platform uv attempts */ + print_uv_attempts(fido_cbor_info_uv_attempts(ci)); + + /* print supported uv mechanisms */ + print_uv_modality(fido_cbor_info_uv_modality(ci)); + + bio_info(dev); + + fido_cbor_info_free(&ci); +end: + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +token_reset(char *path) +{ + fido_dev_t *dev = NULL; + int r; + + if (path == NULL) + usage(); + + dev = open_dev(path); + if ((r = fido_dev_reset(dev)) != FIDO_OK) + errx(1, "fido_dev_reset: %s", fido_strerr(r)); + + fido_dev_close(dev); + fido_dev_free(&dev); + + exit(0); +} + +int +token_get(int argc, char **argv, char *path) +{ + char *id = NULL; + char *key = NULL; + char *name = NULL; + int blob = 0; + int ch; + + optind = 1; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'b': + blob = 1; + break; + case 'i': + id = optarg; + break; + case 'k': + key = optarg; + break; + case 'n': + name = optarg; + break; + default: + break; /* ignore */ + } + } + + argc -= optind; + argv += optind; + + if (blob == 0 || argc != 2) + usage(); + + return blob_get(path, key, name, id, argv[0]); +} + +int +token_set(int argc, char **argv, char *path) +{ + char *id = NULL; + char *key = NULL; + char *len = NULL; + char *display_name = NULL; + char *name = NULL; + char *rpid = NULL; + int blob = 0; + int cred = 0; + int ch; + int enroll = 0; + int ea = 0; + int uv = 0; + bool force = false; + + optind = 1; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'a': + ea = 1; + break; + case 'b': + blob = 1; + break; + case 'c': + cred = 1; + break; + case 'e': + enroll = 1; + break; + case 'f': + force = true; + break; + case 'i': + id = optarg; + break; + case 'k': + key = optarg; + break; + case 'l': + len = optarg; + break; + case 'p': + display_name = optarg; + break; + case 'm': + rpid = optarg; + break; + case 'n': + name = optarg; + break; + case 'u': + uv = 1; + break; + default: + break; /* ignore */ + } + } + + argc -= optind; + argv += optind; + + if (path == NULL) + usage(); + + if (blob) { + if (argc != 2) + usage(); + return (blob_set(path, key, name, id, argv[0])); + } + + if (cred) { + if (!id || !key) + usage(); + if (!name && !display_name) + usage(); + return (credman_update_rk(path, key, id, name, display_name)); + } + + if (enroll) { + if (ea || uv) + usage(); + if (id && name) + return (bio_set_name(path, id, name)); + if (!id && !name) + return (bio_enroll(path)); + usage(); + } + + if (ea) { + if (uv) + usage(); + return (config_entattest(path)); + } + + if (len) + return (config_pin_minlen(path, len)); + if (rpid) + return (config_pin_minlen_rpid(path, rpid)); + if (force) + return (config_force_pin_change(path)); + if (uv) + return (config_always_uv(path, 1)); + + return (pin_set(path)); +} + +int +token_list(int argc, char **argv, char *path) +{ + fido_dev_info_t *devlist; + size_t ndevs; + const char *rp_id = NULL; + int blobs = 0; + int enrolls = 0; + int keys = 0; + int rplist = 0; + int ch; + int r; + + optind = 1; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'b': + blobs = 1; + break; + case 'e': + enrolls = 1; + break; + case 'k': + keys = 1; + rp_id = optarg; + break; + case 'r': + rplist = 1; + break; + default: + break; /* ignore */ + } + } + + if (blobs || enrolls || keys || rplist) { + if (path == NULL) + usage(); + if (blobs) + return (blob_list(path)); + if (enrolls) + return (bio_list(path)); + if (keys) + return (credman_list_rk(path, rp_id)); + if (rplist) + return (credman_list_rp(path)); + /* NOTREACHED */ + } + + if ((devlist = fido_dev_info_new(64)) == NULL) + errx(1, "fido_dev_info_new"); + if ((r = fido_dev_info_manifest(devlist, 64, &ndevs)) != FIDO_OK) + errx(1, "fido_dev_info_manifest: %s (0x%x)", fido_strerr(r), r); + + for (size_t i = 0; i < ndevs; i++) { + const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); + printf("%s: vendor=0x%04x, product=0x%04x (%s %s)\n", + fido_dev_info_path(di), + (uint16_t)fido_dev_info_vendor(di), + (uint16_t)fido_dev_info_product(di), + fido_dev_info_manufacturer_string(di), + fido_dev_info_product_string(di)); + } + + fido_dev_info_free(&devlist, ndevs); + + exit(0); +} + +int +token_delete(int argc, char **argv, char *path) +{ + char *id = NULL; + char *key = NULL; + char *name = NULL; + int blob = 0; + int ch; + int enroll = 0; + int uv = 0; + + optind = 1; + + while ((ch = getopt(argc, argv, TOKEN_OPT)) != -1) { + switch (ch) { + case 'b': + blob = 1; + break; + case 'e': + enroll = 1; + break; + case 'i': + id = optarg; + break; + case 'k': + key = optarg; + break; + case 'n': + name = optarg; + break; + case 'u': + uv = 1; + break; + default: + break; /* ignore */ + } + } + + if (path == NULL) + usage(); + + if (blob) + return (blob_delete(path, key, name, id)); + + if (id) { + if (uv) + usage(); + if (enroll == 0) + return (credman_delete_rk(path, id)); + return (bio_delete(path, id)); + } + + if (uv == 0) + usage(); + + return (config_always_uv(path, 0)); +} diff --git a/tools/util.c b/tools/util.c new file mode 100644 index 0000000..0e518bb --- /dev/null +++ b/tools/util.c @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <openssl/ec.h> +#include <openssl/evp.h> +#include <openssl/pem.h> + +#include <fido.h> +#include <fido/es256.h> +#include <fido/es384.h> +#include <fido/rs256.h> +#include <fido/eddsa.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../openbsd-compat/openbsd-compat.h" +#ifdef _MSC_VER +#include "../openbsd-compat/posix_win.h" +#endif + +#include "extern.h" + +char * +get_pin(const char *path) +{ + char *pin; + char prompt[1024]; + int r, ok = -1; + + if ((pin = calloc(1, PINBUF_LEN)) == NULL) { + warn("%s: calloc", __func__); + return NULL; + } + if ((r = snprintf(prompt, sizeof(prompt), "Enter PIN for %s: ", + path)) < 0 || (size_t)r >= sizeof(prompt)) { + warn("%s: snprintf", __func__); + goto out; + } + if (!readpassphrase(prompt, pin, PINBUF_LEN, RPP_ECHO_OFF)) { + warnx("%s: readpassphrase", __func__); + goto out; + } + + ok = 0; +out: + if (ok < 0) { + freezero(pin, PINBUF_LEN); + pin = NULL; + } + + return pin; +} + +FILE * +open_write(const char *file) +{ + int fd; + FILE *f; + + if (file == NULL || strcmp(file, "-") == 0) + return (stdout); + if ((fd = open(file, O_WRONLY | O_CREAT, 0600)) < 0) + err(1, "open %s", file); + if ((f = fdopen(fd, "w")) == NULL) + err(1, "fdopen %s", file); + + return (f); +} + +FILE * +open_read(const char *file) +{ + int fd; + FILE *f; + + if (file == NULL || strcmp(file, "-") == 0) { +#ifdef FIDO_FUZZ + setvbuf(stdin, NULL, _IONBF, 0); +#endif + return (stdin); + } + if ((fd = open(file, O_RDONLY)) < 0) + err(1, "open %s", file); + if ((f = fdopen(fd, "r")) == NULL) + err(1, "fdopen %s", file); + + return (f); +} + +int +base10(const char *str) +{ + char *ep; + long long ll; + + ll = strtoll(str, &ep, 10); + if (str == ep || *ep != '\0') + return (-1); + else if (ll == LLONG_MIN && errno == ERANGE) + return (-1); + else if (ll == LLONG_MAX && errno == ERANGE) + return (-1); + else if (ll < 0 || ll > INT_MAX) + return (-1); + + return ((int)ll); +} + +void +xxd(const void *buf, size_t count) +{ + const uint8_t *ptr = buf; + size_t i; + + fprintf(stderr, " "); + + for (i = 0; i < count; i++) { + fprintf(stderr, "%02x ", *ptr++); + if ((i + 1) % 16 == 0 && i + 1 < count) + fprintf(stderr, "\n "); + } + + fprintf(stderr, "\n"); + fflush(stderr); +} + +int +string_read(FILE *f, char **out) +{ + char *line = NULL; + size_t linesize = 0; + ssize_t n; + + *out = NULL; + + if ((n = getline(&line, &linesize, f)) <= 0 || + (size_t)n != strlen(line)) { + free(line); + return (-1); + } + + line[n - 1] = '\0'; /* trim \n */ + *out = line; + + return (0); +} + +fido_dev_t * +open_dev(const char *path) +{ + fido_dev_t *dev; + int r; + + if ((dev = fido_dev_new()) == NULL) + errx(1, "fido_dev_new"); + + r = fido_dev_open(dev, path); + if (r != FIDO_OK) + errx(1, "fido_dev_open %s: %s", path, fido_strerr(r)); + + return (dev); +} + +int +get_devopt(fido_dev_t *dev, const char *name, int *val) +{ + fido_cbor_info_t *cbor_info; + char * const *names; + const bool *values; + int r, ok = -1; + + if ((cbor_info = fido_cbor_info_new()) == NULL) { + warnx("fido_cbor_info_new"); + goto out; + } + + if ((r = fido_dev_get_cbor_info(dev, cbor_info)) != FIDO_OK) { + warnx("fido_dev_get_cbor_info: %s (0x%x)", fido_strerr(r), r); + goto out; + } + + if ((names = fido_cbor_info_options_name_ptr(cbor_info)) == NULL || + (values = fido_cbor_info_options_value_ptr(cbor_info)) == NULL) { + warnx("fido_dev_get_cbor_info: NULL name/value pointer"); + goto out; + } + + *val = -1; + for (size_t i = 0; i < fido_cbor_info_options_len(cbor_info); i++) + if (strcmp(names[i], name) == 0) { + *val = values[i]; + break; + } + + ok = 0; +out: + fido_cbor_info_free(&cbor_info); + + return (ok); +} + +EC_KEY * +read_ec_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + EC_KEY *ec = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + if ((ec = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) { + warnx("EVP_PKEY_get1_EC_KEY"); + goto fail; + } + +fail: + if (fp) { + fclose(fp); + } + if (pkey) { + EVP_PKEY_free(pkey); + } + + return (ec); +} + +int +write_es256_pubkey(FILE *f, const void *ptr, size_t len) +{ + EVP_PKEY *pkey = NULL; + es256_pk_t *pk = NULL; + int ok = -1; + + if ((pk = es256_pk_new()) == NULL) { + warnx("es256_pk_new"); + goto fail; + } + + if (es256_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("es256_pk_from_ptr"); + goto fail; + } + + if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("es256_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(f, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + es256_pk_free(&pk); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +int +write_es384_pubkey(FILE *f, const void *ptr, size_t len) +{ + EVP_PKEY *pkey = NULL; + es384_pk_t *pk = NULL; + int ok = -1; + + if ((pk = es384_pk_new()) == NULL) { + warnx("es384_pk_new"); + goto fail; + } + + if (es384_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("es384_pk_from_ptr"); + goto fail; + } + + if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("es384_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(f, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + es384_pk_free(&pk); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +RSA * +read_rsa_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + RSA *rsa = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + if ((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL) { + warnx("EVP_PKEY_get1_RSA"); + goto fail; + } + +fail: + if (fp) { + fclose(fp); + } + if (pkey) { + EVP_PKEY_free(pkey); + } + + return (rsa); +} + +int +write_rsa_pubkey(FILE *f, const void *ptr, size_t len) +{ + EVP_PKEY *pkey = NULL; + rs256_pk_t *pk = NULL; + int ok = -1; + + if ((pk = rs256_pk_new()) == NULL) { + warnx("rs256_pk_new"); + goto fail; + } + + if (rs256_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("rs256_pk_from_ptr"); + goto fail; + } + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("rs256_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(f, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + rs256_pk_free(&pk); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +EVP_PKEY * +read_eddsa_pubkey(const char *path) +{ + FILE *fp = NULL; + EVP_PKEY *pkey = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + warn("fopen"); + goto fail; + } + + if ((pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL)) == NULL) { + warnx("PEM_read_PUBKEY"); + goto fail; + } + +fail: + if (fp) { + fclose(fp); + } + + return (pkey); +} + +int +write_eddsa_pubkey(FILE *f, const void *ptr, size_t len) +{ + EVP_PKEY *pkey = NULL; + eddsa_pk_t *pk = NULL; + int ok = -1; + + if ((pk = eddsa_pk_new()) == NULL) { + warnx("eddsa_pk_new"); + goto fail; + } + + if (eddsa_pk_from_ptr(pk, ptr, len) != FIDO_OK) { + warnx("eddsa_pk_from_ptr"); + goto fail; + } + + if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) { + warnx("eddsa_pk_to_EVP_PKEY"); + goto fail; + } + + if (PEM_write_PUBKEY(f, pkey) == 0) { + warnx("PEM_write_PUBKEY"); + goto fail; + } + + ok = 0; +fail: + eddsa_pk_free(&pk); + + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return (ok); +} + +void +print_cred(FILE *out_f, int type, const fido_cred_t *cred) +{ + char *id; + int r; + + r = base64_encode(fido_cred_id_ptr(cred), fido_cred_id_len(cred), &id); + if (r < 0) + errx(1, "output error"); + + fprintf(out_f, "%s\n", id); + + switch (type) { + case COSE_ES256: + write_es256_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + break; + case COSE_ES384: + write_es384_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + break; + case COSE_RS256: + write_rsa_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + break; + case COSE_EDDSA: + write_eddsa_pubkey(out_f, fido_cred_pubkey_ptr(cred), + fido_cred_pubkey_len(cred)); + break; + default: + errx(1, "print_cred: unknown type"); + } + + free(id); +} + +int +cose_type(const char *str, int *type) +{ + if (strcmp(str, "es256") == 0) + *type = COSE_ES256; + else if (strcmp(str, "es384") == 0) + *type = COSE_ES384; + else if (strcmp(str, "rs256") == 0) + *type = COSE_RS256; + else if (strcmp(str, "eddsa") == 0) + *type = COSE_EDDSA; + else { + *type = 0; + return (-1); + } + + return (0); +} + +const char * +cose_string(int type) +{ + switch (type) { + case COSE_ES256: + return ("es256"); + case COSE_ES384: + return ("es384"); + case COSE_RS256: + return ("rs256"); + case COSE_EDDSA: + return ("eddsa"); + default: + return ("unknown"); + } +} + +const char * +prot_string(int prot) +{ + switch (prot) { + case FIDO_CRED_PROT_UV_OPTIONAL: + return ("uvopt"); + case FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID: + return ("uvopt+id"); + case FIDO_CRED_PROT_UV_REQUIRED: + return ("uvreq"); + default: + return ("unknown"); + } +} + +int +read_file(const char *path, u_char **ptr, size_t *len) +{ + int fd, ok = -1; + struct stat st; + ssize_t n; + + *ptr = NULL; + *len = 0; + + if ((fd = open(path, O_RDONLY)) < 0) { + warn("%s: open %s", __func__, path); + goto fail; + } + if (fstat(fd, &st) < 0) { + warn("%s: stat %s", __func__, path); + goto fail; + } + if (st.st_size < 0) { + warnx("%s: stat %s: invalid size", __func__, path); + goto fail; + } + *len = (size_t)st.st_size; + if ((*ptr = malloc(*len)) == NULL) { + warn("%s: malloc", __func__); + goto fail; + } + if ((n = read(fd, *ptr, *len)) < 0) { + warn("%s: read", __func__); + goto fail; + } + if ((size_t)n != *len) { + warnx("%s: read", __func__); + goto fail; + } + + ok = 0; +fail: + if (fd != -1) { + close(fd); + } + if (ok < 0) { + free(*ptr); + *ptr = NULL; + *len = 0; + } + + return ok; +} + +int +write_file(const char *path, const u_char *ptr, size_t len) +{ + int fd, ok = -1; + ssize_t n; + + if ((fd = open(path, O_WRONLY | O_CREAT, 0600)) < 0) { + warn("%s: open %s", __func__, path); + goto fail; + } + if ((n = write(fd, ptr, len)) < 0) { + warn("%s: write", __func__); + goto fail; + } + if ((size_t)n != len) { + warnx("%s: write", __func__); + goto fail; + } + + ok = 0; +fail: + if (fd != -1) { + close(fd); + } + + return ok; +} + +const char * +plural(size_t x) +{ + return x == 1 ? "" : "s"; +} + +int +should_retry_with_pin(const fido_dev_t *dev, int r) +{ + if (fido_dev_has_pin(dev) == false) { + return 0; + } + + switch (r) { + case FIDO_ERR_PIN_REQUIRED: + case FIDO_ERR_UNAUTHORIZED_PERM: + case FIDO_ERR_UV_BLOCKED: + case FIDO_ERR_UV_INVALID: + return 1; + } + + return 0; +} diff --git a/udev/70-u2f.rules b/udev/70-u2f.rules new file mode 100644 index 0000000..6df852b --- /dev/null +++ b/udev/70-u2f.rules @@ -0,0 +1,270 @@ +# Copyright (c) 2020 Yubico AB. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# SPDX-License-Identifier: BSD-2-Clause + +# This file is automatically generated, and should be used with udev 188 +# or newer. + +ACTION!="add|change", GOTO="fido_end" + +# ellipticSecure MIRKey by STMicroelectronics +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a2ac", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by STMicroelectronics +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a2ca", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by STMicroelectronics +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="cdab", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Infineon FIDO by Infineon Technologies +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="058b", ATTRS{idProduct}=="022d", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Kensington VeriMark by Synaptics Inc. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="06cb", ATTRS{idProduct}=="0088", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# FS ePass FIDO by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0850", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0852", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0853", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0854", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0856", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0858", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# FS MultiPass FIDO U2F by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="085a", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="085b", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="085d", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# BioPass FIDO2 K33 by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0866", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# BioPass FIDO2 K43 by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0867", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Hypersecu HyperFIDO by Feitian Technologies Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="096e", ATTRS{idProduct}=="0880", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey NEO FIDO by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0113", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey NEO OTP+FIDO by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0114", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey NEO FIDO+CCID by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0115", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey NEO OTP+FIDO+CCID by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0116", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Security Key by Yubico by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0120", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Unknown product by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0121", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Gnubby U2F by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0200", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey 4 FIDO by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0402", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey 4 OTP+FIDO by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0403", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey 4 FIDO+CCID by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0406", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey 4 OTP+FIDO+CCID by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0407", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# YubiKey Plus by Yubico AB +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0410", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# U2F Zero by Silicon Laboratories, Inc. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="8acf", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# SoloKeys SoloHacker by pid.codes +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="5070", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# SoloKeys SoloBoot by pid.codes +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="50b0", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# SatoshiLabs TREZOR by pid.codes +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# SoloKeys v2 by pid.codes +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="beee", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Google Titan U2F by Google Inc. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="5026", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# VASCO SecureClick by VASCO Data Security NV +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1a44", ATTRS{idProduct}=="00bb", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# OnlyKey (FIDO2/U2F) by OpenMoko, Inc. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="60fc", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Neowave Keydo AES by NEOWAVE +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1e0d", ATTRS{idProduct}=="f1ae", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Neowave Keydo by NEOWAVE +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1e0d", ATTRS{idProduct}=="f1d0", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Thethis Key by Shenzhen Excelsecu Data Technology Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1ea8", ATTRS{idProduct}=="f025", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# ExcelSecu FIDO2 Security Key by Shenzhen Excelsecu Data Technology Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1ea8", ATTRS{idProduct}=="fc25", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# GoTrust Idem Key by NXP Semiconductors +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="f143", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Nitrokey FIDO U2F by Clay Logic +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="4287", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Nitrokey FIDO2 by Clay Logic +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b1", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Nitrokey 3C NFC by Clay Logic +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b2", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Safetech SafeKey by Clay Logic +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b3", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# CanoKey by Clay Logic +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42d4", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# JaCarta U2F by Aladdin Software Security R.D. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="24dc", ATTRS{idProduct}=="0101", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# JaCarta U2F by Aladdin Software Security R.D. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="24dc", ATTRS{idProduct}=="0501", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Happlink Security Key by Plug‐up +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2581", ATTRS{idProduct}=="f1d0", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Bluink Key by Bluink Ltd +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2abe", ATTRS{idProduct}=="1002", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Blue by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0000", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S Old firmware by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano X Old firmware by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Blue by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0011", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Blue Legacy by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0015", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S HID+U2F by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1005", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S HID+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1011", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S HID+U2F+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="1015", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano X HID+U2F by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4005", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano X HID+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4011", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano X HID+U2F+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="4015", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S+ HID+U2F by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="5005", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S+ HID+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="5011", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Nano S+ HID+U2F+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="5015", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Stax HID+U2F by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="6005", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger Stax HID+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="6011", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Ledger stax HID+U2F+WEBUSB by LEDGER +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="6015", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Hypersecu HyperFIDO by Hypersecu Information Systems, Inc. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2ccf", ATTRS{idProduct}=="0880", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# TrustKey Solutions FIDO2 G310 by eWBM Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="4a1a", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# TrustKey Solutions FIDO2 G310H/G320H by eWBM Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="4a2a", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# TrustKey Solutions FIDO2 G320 by eWBM Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="4c2a", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# eWBM FIDO2 Goldengate G500 by eWBM Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="5c2f", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# TrustKey Solutions FIDO2 T120 by eWBM Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="a6e9", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# TrustKey Solutions FIDO2 T110 by eWBM Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="a7f9", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# eWBM FIDO2 Goldengate G450 by eWBM Co., Ltd. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="311f", ATTRS{idProduct}=="f47c", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Idem Key by GoTrustID Inc. +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="32a3", ATTRS{idProduct}=="3201", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# Longmai mFIDO by Unknown vendor +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="4c4d", ATTRS{idProduct}=="f703", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +# SatoshiLabs TREZOR by SatoshiLabs +KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", TAG+="uaccess", GROUP="plugdev", MODE="0660" + +LABEL="fido_end" diff --git a/udev/CMakeLists.txt b/udev/CMakeLists.txt new file mode 100644 index 0000000..abddb80 --- /dev/null +++ b/udev/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2018 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +if(UDEV_RULES_DIR) + install(FILES 70-u2f.rules DESTINATION ${UDEV_RULES_DIR}) +endif() diff --git a/udev/check.sh b/udev/check.sh new file mode 100755 index 0000000..804a884 --- /dev/null +++ b/udev/check.sh @@ -0,0 +1,32 @@ +#!/bin/sh -u + +# Copyright (c) 2020 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +sort_by_id() { + awk '{ printf "%d\n", $3 }' | sort -Cnu +} + +if ! grep '^vendor' "$1" | sort_by_id; then + echo unsorted vendor section 1>&2 + exit 1 +fi + +VENDORS=$(grep '^vendor' "$1" | awk '{ print $2 }') +PRODUCTS=$(grep '^product' "$1" | awk '{ print $2 }' | uniq) + +if [ "${VENDORS}" != "${PRODUCTS}" ]; then + echo vendors: "$(echo "${VENDORS}" | tr '\n' ',')" 1>&2 + echo products: "$(echo "${PRODUCTS}" | tr '\n' ',')" 1>&2 + echo vendors and products in different order 1>&2 + exit 2 +fi + +for v in ${VENDORS}; do + if ! grep "^product ${v}" "$1" | sort_by_id; then + echo "${v}": unsorted product section 1>&2 + exit 3 + fi +done diff --git a/udev/fidodevs b/udev/fidodevs new file mode 100644 index 0000000..ba62696 --- /dev/null +++ b/udev/fidodevs @@ -0,0 +1,137 @@ +# Copyright (c) 2020 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +# After modifying this file, regenerate 70-u2f.rules: +# ./genrules.awk fidodevs > 70-u2f.rules + +# List of known vendors. Sorted by vendor ID. + +vendor STMICRO 0x0483 STMicroelectronics +vendor INFINEON 0x058b Infineon Technologies +vendor SYNAPTICS 0x06cb Synaptics Inc. +vendor FEITIAN 0x096e Feitian Technologies Co., Ltd. +vendor YUBICO 0x1050 Yubico AB +vendor SILICON 0x10c4 Silicon Laboratories, Inc. +vendor PIDCODES 0x1209 pid.codes +vendor GOOGLE 0x18d1 Google Inc. +vendor VASCO 0x1a44 VASCO Data Security NV +vendor OPENMOKO 0x1d50 OpenMoko, Inc. +vendor NEOWAVE 0x1e0d NEOWAVE +vendor EXCELSECU 0x1ea8 Shenzhen Excelsecu Data Technology Co., Ltd. +vendor NXP 0x1fc9 NXP Semiconductors +vendor CLAYLOGIC 0x20a0 Clay Logic +vendor ALLADIN 0x24dc Aladdin Software Security R.D. +vendor PLUGUP 0x2581 Plug‐up +vendor BLUINK 0x2abe Bluink Ltd +vendor LEDGER 0x2c97 LEDGER +vendor HYPERSECU 0x2ccf Hypersecu Information Systems, Inc. +vendor EWBM 0x311f eWBM Co., Ltd. +vendor GOTRUST 0x32a3 GoTrustID Inc. +vendor UNKNOWN1 0x4c4d Unknown vendor +vendor SATOSHI 0x534c SatoshiLabs + +# List of known products. Grouped by vendor; sorted by product ID. + +product STMICRO 0xa2ac ellipticSecure MIRKey +product STMICRO 0xa2ca Unknown product +product STMICRO 0xcdab Unknown product + +product INFINEON 0x022d Infineon FIDO + +product SYNAPTICS 0x0088 Kensington VeriMark + +product FEITIAN 0x0850 FS ePass FIDO +product FEITIAN 0x0852 Unknown product +product FEITIAN 0x0853 Unknown product +product FEITIAN 0x0854 Unknown product +product FEITIAN 0x0856 Unknown product +product FEITIAN 0x0858 Unknown product +product FEITIAN 0x085a FS MultiPass FIDO U2F +product FEITIAN 0x085b Unknown product +product FEITIAN 0x085d Unknown product +product FEITIAN 0x0866 BioPass FIDO2 K33 +product FEITIAN 0x0867 BioPass FIDO2 K43 +product FEITIAN 0x0880 Hypersecu HyperFIDO + +product YUBICO 0x0113 YubiKey NEO FIDO +product YUBICO 0x0114 YubiKey NEO OTP+FIDO +product YUBICO 0x0115 YubiKey NEO FIDO+CCID +product YUBICO 0x0116 YubiKey NEO OTP+FIDO+CCID +product YUBICO 0x0120 Security Key by Yubico +product YUBICO 0x0121 Unknown product +product YUBICO 0x0200 Gnubby U2F +product YUBICO 0x0402 YubiKey 4 FIDO +product YUBICO 0x0403 YubiKey 4 OTP+FIDO +product YUBICO 0x0406 YubiKey 4 FIDO+CCID +product YUBICO 0x0407 YubiKey 4 OTP+FIDO+CCID +product YUBICO 0x0410 YubiKey Plus + +product SILICON 0x8acf U2F Zero + +product PIDCODES 0x5070 SoloKeys SoloHacker +product PIDCODES 0x50b0 SoloKeys SoloBoot +product PIDCODES 0x53c1 SatoshiLabs TREZOR +product PIDCODES 0xbeee SoloKeys v2 + +product GOOGLE 0x5026 Google Titan U2F + +product VASCO 0x00bb VASCO SecureClick + +product OPENMOKO 0x60fc OnlyKey (FIDO2/U2F) + +product NEOWAVE 0xf1ae Neowave Keydo AES +product NEOWAVE 0xf1d0 Neowave Keydo + +product EXCELSECU 0xf025 Thethis Key +product EXCELSECU 0xfc25 ExcelSecu FIDO2 Security Key + +product NXP 0xf143 GoTrust Idem Key + +product CLAYLOGIC 0x4287 Nitrokey FIDO U2F +product CLAYLOGIC 0x42b1 Nitrokey FIDO2 +product CLAYLOGIC 0x42b2 Nitrokey 3C NFC +product CLAYLOGIC 0x42b3 Safetech SafeKey +product CLAYLOGIC 0x42d4 CanoKey + +product ALLADIN 0x0101 JaCarta U2F +product ALLADIN 0x0501 JaCarta U2F + +product PLUGUP 0xf1d0 Happlink Security Key + +product BLUINK 0x1002 Bluink Key + +product LEDGER 0x0000 Ledger Blue +product LEDGER 0x0001 Ledger Nano S Old firmware +product LEDGER 0x0004 Ledger Nano X Old firmware +product LEDGER 0x0011 Ledger Blue +product LEDGER 0x0015 Ledger Blue Legacy +product LEDGER 0x1005 Ledger Nano S HID+U2F +product LEDGER 0x1011 Ledger Nano S HID+WEBUSB +product LEDGER 0x1015 Ledger Nano S HID+U2F+WEBUSB +product LEDGER 0x4005 Ledger Nano X HID+U2F +product LEDGER 0x4011 Ledger Nano X HID+WEBUSB +product LEDGER 0x4015 Ledger Nano X HID+U2F+WEBUSB +product LEDGER 0x5005 Ledger Nano S+ HID+U2F +product LEDGER 0x5011 Ledger Nano S+ HID+WEBUSB +product LEDGER 0x5015 Ledger Nano S+ HID+U2F+WEBUSB +product LEDGER 0x6005 Ledger Stax HID+U2F +product LEDGER 0x6011 Ledger Stax HID+WEBUSB +product LEDGER 0x6015 Ledger stax HID+U2F+WEBUSB + +product HYPERSECU 0x0880 Hypersecu HyperFIDO + +product EWBM 0x4a1a TrustKey Solutions FIDO2 G310 +product EWBM 0x4a2a TrustKey Solutions FIDO2 G310H/G320H +product EWBM 0x4c2a TrustKey Solutions FIDO2 G320 +product EWBM 0x5c2f eWBM FIDO2 Goldengate G500 +product EWBM 0xa6e9 TrustKey Solutions FIDO2 T120 +product EWBM 0xa7f9 TrustKey Solutions FIDO2 T110 +product EWBM 0xf47c eWBM FIDO2 Goldengate G450 + +product GOTRUST 0x3201 Idem Key + +product UNKNOWN1 0xf703 Longmai mFIDO + +product SATOSHI 0x0001 SatoshiLabs TREZOR diff --git a/udev/genrules.awk b/udev/genrules.awk new file mode 100755 index 0000000..3dad667 --- /dev/null +++ b/udev/genrules.awk @@ -0,0 +1,79 @@ +#!/usr/bin/awk -f + +# Copyright (c) 2020 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +NR == 1 { + print "# Copyright (c) 2020 Yubico AB. All rights reserved." + print "#" + print "# Redistribution and use in source and binary forms, with or without" + print "# modification, are permitted provided that the following conditions are" + print "# met:" + print "# " + print "# 1. Redistributions of source code must retain the above copyright" + print "# notice, this list of conditions and the following disclaimer." + print "# 2. Redistributions in binary form must reproduce the above copyright" + print "# notice, this list of conditions and the following disclaimer in" + print "# the documentation and/or other materials provided with the" + print "# distribution." + print "# " + print "# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS" + print "# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT" + print "# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR" + print "# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT" + print "# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL," + print "# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT" + print "# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE," + print "# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY" + print "# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT" + print "# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE" + print "# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + print "#" + print "# SPDX-License-Identifier: BSD-2-Clause" + print "" + print "# This file is automatically generated, and should be used with udev 188" + print "# or newer." + print "" + print "ACTION!=\"add|change\", GOTO=\"fido_end\"" + + next +} + +$1 == "vendor" { + sub("0x", "", $3) + vendors[$2, "id"] = $3 + + f = 4 + while (f <= NF) { + vendors[$2, "name"] = vendors[$2, "name"] " " $f + f++ + } +} + +$1 == "product" { + sub("0x", "", $3) + name = "" + + f = 4 + while (f <= NF) { + name = name " " $f + f++ + } + + line = "\n#" name " by" vendors[$2, "name"]"\n" + line = line"KERNEL==\"hidraw*\"" + line = line", SUBSYSTEM==\"hidraw\"" + line = line", ATTRS{idVendor}==\""vendors[$2, "id"]"\"" + line = line", ATTRS{idProduct}==\""$3"\"" + line = line", TAG+=\"uaccess\"" + line = line", GROUP=\"plugdev\"" + line = line", MODE=\"0660\"" + + print line +} + +END { + print "\nLABEL=\"fido_end\"" +} diff --git a/windows/build.ps1 b/windows/build.ps1 new file mode 100644 index 0000000..ff43d8b --- /dev/null +++ b/windows/build.ps1 @@ -0,0 +1,243 @@ +# Copyright (c) 2021-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +param( + [string]$CMakePath = "C:\Program Files\CMake\bin\cmake.exe", + [string]$GitPath = "C:\Program Files\Git\bin\git.exe", + [string]$SevenZPath = "C:\Program Files\7-Zip\7z.exe", + [string]$GPGPath = "C:\Program Files (x86)\GnuPG\bin\gpg.exe", + [string]$WinSDK = "", + [string]$Config = "Release", + [string]$Arch = "x64", + [string]$Type = "dynamic", + [string]$Fido2Flags = "" +) + +$ErrorActionPreference = "Stop" +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +. "$PSScriptRoot\const.ps1" + +Function ExitOnError() { + if ($LastExitCode -ne 0) { + throw "A command exited with status $LastExitCode" + } +} + +Function GitClone(${REPO}, ${BRANCH}, ${DIR}) { + Write-Host "Cloning ${REPO}..." + & $Git -c advice.detachedHead=false clone --quiet --depth=1 ` + --branch "${BRANCH}" "${REPO}" "${DIR}" + Write-Host "${REPO}'s ${BRANCH} HEAD is:" + & $Git -C "${DIR}" show -s HEAD +} + +# Find Git. +$Git = $(Get-Command git -ErrorAction Ignore | ` + Select-Object -ExpandProperty Source) +if ([string]::IsNullOrEmpty($Git)) { + $Git = $GitPath +} +if (-Not (Test-Path $Git)) { + throw "Unable to find Git at $Git" +} + +# Find CMake. +$CMake = $(Get-Command cmake -ErrorAction Ignore | ` + Select-Object -ExpandProperty Source) +if ([string]::IsNullOrEmpty($CMake)) { + $CMake = $CMakePath +} +if (-Not (Test-Path $CMake)) { + throw "Unable to find CMake at $CMake" +} + +# Find 7z. +$SevenZ = $(Get-Command 7z -ErrorAction Ignore | ` + Select-Object -ExpandProperty Source) +if ([string]::IsNullOrEmpty($SevenZ)) { + $SevenZ = $SevenZPath +} +if (-Not (Test-Path $SevenZ)) { + throw "Unable to find 7z at $SevenZ" +} + +# Find GPG. +$GPG = $(Get-Command gpg -ErrorAction Ignore | ` + Select-Object -ExpandProperty Source) +if ([string]::IsNullOrEmpty($GPG)) { + $GPG = $GPGPath +} +if (-Not (Test-Path $GPG)) { + throw "Unable to find GPG at $GPG" +} + +# Override CMAKE_SYSTEM_VERSION if $WinSDK is set. +if (-Not ([string]::IsNullOrEmpty($WinSDK))) { + $CMAKE_SYSTEM_VERSION = "-DCMAKE_SYSTEM_VERSION='$WinSDK'" +} else { + $CMAKE_SYSTEM_VERSION = '' +} + +Write-Host "WinSDK: $WinSDK" +Write-Host "Config: $Config" +Write-Host "Arch: $Arch" +Write-Host "Type: $Type" +Write-Host "Git: $Git" +Write-Host "CMake: $CMake" +Write-Host "7z: $SevenZ" +Write-Host "GPG: $GPG" + +# Create build directories. +New-Item -Type Directory "${BUILD}" -Force +New-Item -Type Directory "${BUILD}\${Arch}" -Force +New-Item -Type Directory "${BUILD}\${Arch}\${Type}" -Force +New-Item -Type Directory "${STAGE}\${LIBRESSL}" -Force +New-Item -Type Directory "${STAGE}\${LIBCBOR}" -Force +New-Item -Type Directory "${STAGE}\${ZLIB}" -Force + +# Create output directories. +New-Item -Type Directory "${OUTPUT}" -Force +New-Item -Type Directory "${OUTPUT}\${Arch}" -Force +New-Item -Type Directory "${OUTPUT}\${Arch}\${Type}" -force + +# Fetch and verify dependencies. +Push-Location ${BUILD} +try { + if (-Not (Test-Path .\${LIBRESSL})) { + if (-Not (Test-Path .\${LIBRESSL}.tar.gz -PathType leaf)) { + Invoke-WebRequest ${LIBRESSL_URL}/${LIBRESSL}.tar.gz ` + -OutFile .\${LIBRESSL}.tar.gz + } + if (-Not (Test-Path .\${LIBRESSL}.tar.gz.asc -PathType leaf)) { + Invoke-WebRequest ${LIBRESSL_URL}/${LIBRESSL}.tar.gz.asc ` + -OutFile .\${LIBRESSL}.tar.gz.asc + } + + Copy-Item "$PSScriptRoot\libressl.gpg" -Destination "${BUILD}" + & $GPG --list-keys + & $GPG --quiet --no-default-keyring --keyring ./libressl.gpg ` + --verify .\${LIBRESSL}.tar.gz.asc .\${LIBRESSL}.tar.gz + if ($LastExitCode -ne 0) { + throw "GPG signature verification failed" + } + & $SevenZ e .\${LIBRESSL}.tar.gz + & $SevenZ x .\${LIBRESSL}.tar + Remove-Item -Force .\${LIBRESSL}.tar + } + if (-Not (Test-Path .\${LIBCBOR})) { + GitClone "${LIBCBOR_GIT}" "${LIBCBOR_BRANCH}" ".\${LIBCBOR}" + } + if (-Not (Test-Path .\${ZLIB})) { + GitClone "${ZLIB_GIT}" "${ZLIB_BRANCH}" ".\${ZLIB}" + } +} catch { + throw "Failed to fetch and verify dependencies" +} finally { + Pop-Location +} + +# Build LibreSSL. +Push-Location ${STAGE}\${LIBRESSL} +try { + & $CMake ..\..\..\${LIBRESSL} -A "${Arch}" ` + -DBUILD_SHARED_LIBS="${SHARED}" -DLIBRESSL_TESTS=OFF ` + -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" ` + -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" ` + -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; ` + ExitOnError + & $CMake --build . --config ${Config} --verbose; ExitOnError + & $CMake --build . --config ${Config} --target install --verbose; ` + ExitOnError +} catch { + throw "Failed to build LibreSSL" +} finally { + Pop-Location +} + +# Build libcbor. +Push-Location ${STAGE}\${LIBCBOR} +try { + & $CMake ..\..\..\${LIBCBOR} -A "${Arch}" ` + -DWITH_EXAMPLES=OFF ` + -DBUILD_SHARED_LIBS="${SHARED}" ` + -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG} /wd4703" ` + -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE} /wd4703" ` + -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; ` + ExitOnError + & $CMake --build . --config ${Config} --verbose; ExitOnError + & $CMake --build . --config ${Config} --target install --verbose; ` + ExitOnError +} catch { + throw "Failed to build libcbor" +} finally { + Pop-Location +} + +# Build zlib. +Push-Location ${STAGE}\${ZLIB} +try { + & $CMake ..\..\..\${ZLIB} -A "${Arch}" ` + -DBUILD_SHARED_LIBS="${SHARED}" ` + -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG}" ` + -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE}" ` + -DCMAKE_MSVC_RUNTIME_LIBRARY="${CMAKE_MSVC_RUNTIME_LIBRARY}" ` + -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; ` + ExitOnError + & $CMake --build . --config ${Config} --verbose; ExitOnError + & $CMake --build . --config ${Config} --target install --verbose; ` + ExitOnError + # Patch up zlib's various names. + if ("${Type}" -eq "Dynamic") { + ((Get-ChildItem -Path "${PREFIX}/lib") -Match "zlib[d]?.lib") | + Copy-Item -Destination "${PREFIX}/lib/zlib1.lib" -Force + ((Get-ChildItem -Path "${PREFIX}/bin") -Match "zlibd1.dll") | + Copy-Item -Destination "${PREFIX}/bin/zlib1.dll" -Force + } else { + ((Get-ChildItem -Path "${PREFIX}/lib") -Match "zlibstatic[d]?.lib") | + Copy-item -Destination "${PREFIX}/lib/zlib1.lib" -Force + } +} catch { + throw "Failed to build zlib" +} finally { + Pop-Location +} + +# Build libfido2. +Push-Location ${STAGE} +try { + & $CMake ..\..\.. -A "${Arch}" ` + -DCMAKE_BUILD_TYPE="${Config}" ` + -DBUILD_SHARED_LIBS="${SHARED}" ` + -DCBOR_INCLUDE_DIRS="${PREFIX}\include" ` + -DCBOR_LIBRARY_DIRS="${PREFIX}\lib" ` + -DCBOR_BIN_DIRS="${PREFIX}\bin" ` + -DZLIB_INCLUDE_DIRS="${PREFIX}\include" ` + -DZLIB_LIBRARY_DIRS="${PREFIX}\lib" ` + -DZLIB_BIN_DIRS="${PREFIX}\bin" ` + -DCRYPTO_INCLUDE_DIRS="${PREFIX}\include" ` + -DCRYPTO_LIBRARY_DIRS="${PREFIX}\lib" ` + -DCRYPTO_BIN_DIRS="${PREFIX}\bin" ` + -DCRYPTO_LIBRARIES="${CRYPTO_LIBRARIES}" ` + -DCMAKE_C_FLAGS_DEBUG="${CFLAGS_DEBUG} ${Fido2Flags}" ` + -DCMAKE_C_FLAGS_RELEASE="${CFLAGS_RELEASE} ${Fido2Flags}" ` + -DCMAKE_INSTALL_PREFIX="${PREFIX}" "${CMAKE_SYSTEM_VERSION}"; ` + ExitOnError + & $CMake --build . --config ${Config} --verbose; ExitOnError + & $CMake --build . --config ${Config} --target regress --verbose; ` + ExitOnError + & $CMake --build . --config ${Config} --target install --verbose; ` + ExitOnError + # Copy DLLs. + if ("${SHARED}" -eq "ON") { + "cbor.dll", "${CRYPTO_LIBRARIES}.dll", "zlib1.dll" | ` + %{ Copy-Item "${PREFIX}\bin\$_" ` + -Destination "examples\${Config}" } + } +} catch { + throw "Failed to build libfido2" +} finally { + Pop-Location +} diff --git a/windows/const.ps1 b/windows/const.ps1 new file mode 100644 index 0000000..7fac21e --- /dev/null +++ b/windows/const.ps1 @@ -0,0 +1,48 @@ +# Copyright (c) 2021-2023 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +# LibreSSL coordinates. +New-Variable -Name 'LIBRESSL_URL' ` + -Value 'https://cloudflare.cdn.openbsd.org/pub/OpenBSD/LibreSSL' ` + -Option Constant +New-Variable -Name 'LIBRESSL' -Value 'libressl-3.7.3' -Option Constant +New-Variable -Name 'CRYPTO_LIBRARIES' -Value 'crypto-50' -Option Constant + +# libcbor coordinates. +New-Variable -Name 'LIBCBOR' -Value 'libcbor-0.10.2' -Option Constant +New-Variable -Name 'LIBCBOR_BRANCH' -Value 'v0.10.2' -Option Constant +New-Variable -Name 'LIBCBOR_GIT' -Value 'https://github.com/pjk/libcbor' ` + -Option Constant + +# zlib coordinates. +New-Variable -Name 'ZLIB' -Value 'zlib-1.3' -Option Constant +New-Variable -Name 'ZLIB_BRANCH' -Value 'v1.3' -Option Constant +New-Variable -Name 'ZLIB_GIT' -Value 'https://github.com/madler/zlib' ` + -Option Constant + +# Work directories. +New-Variable -Name 'BUILD' -Value "$PSScriptRoot\..\build" -Option Constant +New-Variable -Name 'OUTPUT' -Value "$PSScriptRoot\..\output" -Option Constant + +# Prefixes. +New-Variable -Name 'STAGE' -Value "${BUILD}\${Arch}\${Type}" -Option Constant +New-Variable -Name 'PREFIX' -Value "${OUTPUT}\${Arch}\${Type}" -Option Constant + +# Build flags. +if ("${Type}" -eq "dynamic") { + New-Variable -Name 'RUNTIME' -Value '/MD' -Option Constant + New-Variable -Name 'SHARED' -Value 'ON' -Option Constant + New-Variable -Name 'CMAKE_MSVC_RUNTIME_LIBRARY' -Option Constant ` + -Value 'MultiThreaded$<$<CONFIG:Debug>:Debug>DLL' +} else { + New-Variable -Name 'RUNTIME' -Value '/MT' -Option Constant + New-Variable -Name 'SHARED' -Value 'OFF' -Option Constant + New-Variable -Name 'CMAKE_MSVC_RUNTIME_LIBRARY' -Option Constant ` + -Value 'MultiThreaded$<$<CONFIG:Debug>:Debug>' +} +New-Variable -Name 'CFLAGS_DEBUG' -Value "${RUNTIME}d /Zi /guard:cf /sdl" ` + -Option Constant +New-Variable -Name 'CFLAGS_RELEASE' -Value "${RUNTIME} /Zi /guard:cf /sdl" ` + -Option Constant diff --git a/windows/cygwin.gpg b/windows/cygwin.gpg Binary files differnew file mode 100755 index 0000000..1e87237 --- /dev/null +++ b/windows/cygwin.gpg diff --git a/windows/cygwin.ps1 b/windows/cygwin.ps1 new file mode 100755 index 0000000..0681830 --- /dev/null +++ b/windows/cygwin.ps1 @@ -0,0 +1,70 @@ +# Copyright (c) 2021 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +param( + [string]$GPGPath = "C:\Program Files (x86)\GnuPG\bin\gpg.exe", + [string]$Config = "Release" +) + +$ErrorActionPreference = "Stop" +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +# Cygwin coordinates. +$URL = 'https://www.cygwin.com' +$Setup = 'setup-x86_64.exe' +$Mirror = 'https://mirrors.kernel.org/sourceware/cygwin/' +$Packages = 'gcc-core,pkg-config,cmake,make,libcbor-devel,libssl-devel,zlib-devel' + +# Work directories. +$Cygwin = "$PSScriptRoot\..\cygwin" +$Root = "${Cygwin}\root" + +# Find GPG. +$GPG = $(Get-Command gpg -ErrorAction Ignore | ` + Select-Object -ExpandProperty Source) +if ([string]::IsNullOrEmpty($GPG)) { + $GPG = $GPGPath +} +if (-Not (Test-Path $GPG)) { + throw "Unable to find GPG at $GPG" +} + +Write-Host "Config: $Config" +Write-Host "GPG: $GPG" + +# Create work directories. +New-Item -Type Directory "${Cygwin}" -Force +New-Item -Type Directory "${Root}" -Force + +# Fetch and verify Cygwin. +try { + if (-Not (Test-Path ${Cygwin}\${Setup} -PathType leaf)) { + Invoke-WebRequest ${URL}/${Setup} ` + -OutFile ${Cygwin}\${Setup} + } + if (-Not (Test-Path ${Cygwin}\${Setup}.sig -PathType leaf)) { + Invoke-WebRequest ${URL}/${Setup}.sig ` + -OutFile ${Cygwin}\${Setup}.sig + } + & $GPG --list-keys + & $GPG --quiet --no-default-keyring ` + --keyring ${PSScriptRoot}/cygwin.gpg ` + --verify ${Cygwin}\${Setup}.sig ${Cygwin}\${Setup} + if ($LastExitCode -ne 0) { + throw "GPG signature verification failed" + } +} catch { + throw "Failed to fetch and verify Cygwin" +} + +# Bootstrap Cygwin. +Start-Process "${Cygwin}\${Setup}" -Wait -NoNewWindow ` + -ArgumentList "-dnNOqW -s ${Mirror} -R ${Root} -P ${Packages}" + +# Build libfido2. +$Env:PATH = "${Root}\bin\;" + $Env:PATH +cmake "-DCMAKE_BUILD_TYPE=${Config}" -B "build-${Config}" +make -C "build-${Config}" +make -C "build-${Config}" regress diff --git a/windows/libressl.gpg b/windows/libressl.gpg Binary files differnew file mode 100644 index 0000000..87d5dad --- /dev/null +++ b/windows/libressl.gpg diff --git a/windows/release.ps1 b/windows/release.ps1 new file mode 100644 index 0000000..cc5f635 --- /dev/null +++ b/windows/release.ps1 @@ -0,0 +1,97 @@ +# Copyright (c) 2021-2022 Yubico AB. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. +# SPDX-License-Identifier: BSD-2-Clause + +$ErrorActionPreference = "Stop" +$Architectures = @('x64', 'Win32', 'ARM64', 'ARM') +$InstallPrefixes = @('Win64', 'Win32', 'ARM64', 'ARM') +$Types = @('dynamic', 'static') +$Config = 'Release' +$SDK = '143' + +. "$PSScriptRoot\const.ps1" + +foreach ($Arch in $Architectures) { + foreach ($Type in $Types) { + ./build.ps1 -Arch ${Arch} -Type ${Type} -Config ${Config} + } +} + +foreach ($InstallPrefix in $InstallPrefixes) { + foreach ($Type in $Types) { + New-Item -Type Directory ` + "${OUTPUT}/pkg/${InstallPrefix}/${Config}/v${SDK}/${Type}" + } +} + +Function Package-Headers() { + Copy-Item "${OUTPUT}\x64\dynamic\include" -Destination "${OUTPUT}\pkg" ` + -Recurse -ErrorAction Stop +} + +Function Package-Dynamic(${SRC}, ${DEST}) { + Copy-Item "${SRC}\bin\cbor.dll" "${DEST}" + Copy-Item "${SRC}\lib\cbor.lib" "${DEST}" + Copy-Item "${SRC}\bin\zlib1.dll" "${DEST}" + Copy-Item "${SRC}\lib\zlib1.lib" "${DEST}" + Copy-Item "${SRC}\bin\${CRYPTO_LIBRARIES}.dll" "${DEST}" + Copy-Item "${SRC}\lib\${CRYPTO_LIBRARIES}.lib" "${DEST}" + Copy-Item "${SRC}\bin\fido2.dll" "${DEST}" + Copy-Item "${SRC}\lib\fido2.lib" "${DEST}" +} + +Function Package-Static(${SRC}, ${DEST}) { + Copy-Item "${SRC}/lib/cbor.lib" "${DEST}" + Copy-Item "${SRC}/lib/zlib1.lib" "${DEST}" + Copy-Item "${SRC}/lib/${CRYPTO_LIBRARIES}.lib" "${DEST}" + Copy-Item "${SRC}/lib/fido2_static.lib" "${DEST}/fido2.lib" +} + +Function Package-PDBs(${SRC}, ${DEST}) { + Copy-Item "${SRC}\${LIBRESSL}\crypto\crypto_obj.dir\${Config}\crypto_obj.pdb" ` + "${DEST}\${CRYPTO_LIBRARIES}.pdb" + Copy-Item "${SRC}\${LIBCBOR}\src\cbor.dir\${Config}\vc${SDK}.pdb" ` + "${DEST}\cbor.pdb" + Copy-Item "${SRC}\${ZLIB}\zlib.dir\${Config}\vc${SDK}.pdb" ` + "${DEST}\zlib1.pdb" + Copy-Item "${SRC}\src\fido2_shared.dir\${Config}\vc${SDK}.pdb" ` + "${DEST}\fido2.pdb" +} + +Function Package-StaticPDBs(${SRC}, ${DEST}) { + Copy-Item "${SRC}\${LIBRESSL}\crypto\crypto_obj.dir\${Config}\crypto_obj.pdb" ` + "${DEST}\${CRYPTO_LIBRARIES}.pdb" + Copy-Item "${SRC}\${LIBCBOR}\src\${Config}\cbor.pdb" ` + "${DEST}\cbor.pdb" + Copy-Item "${SRC}\${ZLIB}\${Config}\zlibstatic.pdb" ` + "${DEST}\zlib1.pdb" + Copy-Item "${SRC}\src\${Config}\fido2_static.pdb" ` + "${DEST}\fido2.pdb" +} + +Function Package-Tools(${SRC}, ${DEST}) { + Copy-Item "${SRC}\tools\${Config}\fido2-assert.exe" ` + "${DEST}\fido2-assert.exe" + Copy-Item "${SRC}\tools\${Config}\fido2-cred.exe" ` + "${DEST}\fido2-cred.exe" + Copy-Item "${SRC}\tools\${Config}\fido2-token.exe" ` + "${DEST}\fido2-token.exe" +} + +Package-Headers + +for ($i = 0; $i -lt $Architectures.Length; $i++) { + $Arch = $Architectures[$i] + $InstallPrefix = $InstallPrefixes[$i] + Package-Dynamic "${OUTPUT}\${Arch}\dynamic" ` + "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\dynamic" + Package-PDBs "${BUILD}\${Arch}\dynamic" ` + "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\dynamic" + Package-Tools "${BUILD}\${Arch}\dynamic" ` + "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\dynamic" + Package-Static "${OUTPUT}\${Arch}\static" ` + "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\static" + Package-StaticPDBs "${BUILD}\${Arch}\static" ` + "${OUTPUT}\pkg\${InstallPrefix}\${Config}\v${SDK}\static" +} |