yubico-piv-tool-2.7.1/0000775000175000017500000000000014731067305014136 5ustar winniewinnieyubico-piv-tool-2.7.1/CMakeLists.txt0000664000175000017500000001622214731066755016711 0ustar winniewinnie# 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: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * 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 # 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. cmake_minimum_required (VERSION 3.5) # policy CMP0025 is to get AppleClang identifier rather than Clang for both # this matters since the apple compiler accepts different flags. cmake_policy(SET CMP0025 NEW) cmake_policy(SET CMP0042 NEW) cmake_policy(SET CMP0054 NEW) include(CheckFunctionExists) set (CMAKE_C_STANDARD 11) project (yubico-piv-tool) set (yubico_piv_tool_VERSION_MAJOR 2) set (yubico_piv_tool_VERSION_MINOR 7) set (yubico_piv_tool_VERSION_PATCH 1) set (VERSION "${yubico_piv_tool_VERSION_MAJOR}.${yubico_piv_tool_VERSION_MINOR}.${yubico_piv_tool_VERSION_PATCH}") set (SO_VERSION 2) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/") include(${CMAKE_SOURCE_DIR}/cmake/options.cmake) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() include(CheckCCompilerFlag) set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_definitions(-DOPENSSL_API_COMPAT=0x10000000L) if(WIN32) add_definitions(-DWIN32_LEAN_AND_MEAN) set(_WIN32 ${WIN32}) endif() if(MSVC) if((NOT GETOPT_LIB_DIR) OR (NOT GETOPT_INCLUDE_DIR)) message(FATAL_ERROR "please provide definitions for " "GETOPT_LIB_DIR and GETOPT_INCLUDE_DIR when building " "under msvc") endif() if(SUPRESS_MSVC_WARNINGS) set(MSVC_DISABLED_WARNINGS_LIST "C4706" # assignment within conditional expression; "C4996" # The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name "C4244" # Conversion from 'int' to 'unsigned char', possible loss of data "C4152" # Nonstandard extension, function/data pointer conversion in expression (openssl/applink.c) "C4068" # Unknown pragma "C4100" # 'param': unreferenced formal parameter (unsupported pkcs11 functions) "C4267" # Conversion from 'size_t' to 'CK_ULONG' "C4312" # Conversion from 'unsigned int' to 'void *' of greater size (ykcs11/utils.c:noop_create_mutex) "C5105" # Macro expansion producing 'defined' has undefined behavior ) # 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 ${MSVC_DISABLED_WARNINGS_STR}") endif(SUPRESS_MSVC_WARNINGS) set(BUILD_SHARED_LIBS ON) set(GENERATE_MAN_PAGES OFF) set(LIBCRYPTO_LIBRARIES ${LIBCRYPTO_LIBRARIES} bcrypt) else() find_package (PkgConfig REQUIRED) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-missing-braces") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wformat -Wformat-security") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshadow -Wpointer-arith") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-prototypes") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wbad-function-cast") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-all") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-pointer-sign") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g2") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer") endif() # Use -Wshorten-64-to-32 if available. check_c_compiler_flag("-Wshorten-64-to-32" HAVE_SHORTEN_64_TO_32) if(HAVE_SHORTEN_64_TO_32) # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshorten-64-to-32") endif() # Avoid https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 if(CMAKE_COMPILER_IS_GNUCC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-result") endif() include(${CMAKE_SOURCE_DIR}/cmake/openssl.cmake) find_libcrypto() include_directories(${LIBCRYPTO_INCLUDE_DIRS}) enable_testing() find_package(codecov) # explicit_bzero check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO) if(HAVE_EXPLICIT_BZERO) add_definitions(-DHAVE_EXPLICIT_BZERO) endif() add_subdirectory (lib) if(NOT BUILD_ONLY_LIB) add_subdirectory(ykcs11) add_subdirectory(tool) endif() coverage_evaluate() message("Build summary:") message("") message(" Project name: ${CMAKE_PROJECT_NAME}") message(" Version: ${VERSION}") message(" Host type: ${CMAKE_SYSTEM_NAME}") message(" Install prefix: ${CMAKE_PREFIX_PATH}") message(" Compiler: ${CMAKE_C_COMPILER}") message(" Compiler ID: ${CMAKE_C_COMPILER_ID}") message(" CFLAGS: ${CMAKE_C_FLAGS}") message(" CFLAGS_DEBUG: ${CMAKE_C_FLAGS_DEBUG}") message(" CPPFLAGS: ${CMAKE_CXX_FLAGS}") message(" Warnings: ${WARN_FLAGS}") message(" Build type: ${CMAKE_BUILD_TYPE}") message(" Backend: ${BACKEND}") message(" PCSC") message(" CFLAGS: ${PCSC_CFLAGS}") message(" LIBS: ${PCSC_LIBRARIES}") message(" Winscard") message(" LIBS: ${PCSC_WIN_LIBS}") message(" Mac PCSC") message(" LIBS: ${PCSC_MACOSX_LIBS}") message(" Custom PCSC") message(" LIBS: ${PCSC_CUSTOM_LIBS}") message("") message(" Install prefix: ${CMAKE_INSTALL_PREFIX}") message(" Install targets") message(" Libraries ${YKPIV_INSTALL_LIB_DIR}") message(" Includes ${YKPIV_INSTALL_INC_DIR}") message(" Binaries ${YKPIV_INSTALL_BIN_DIR}") message(" Manuals ${YKPIV_INSTALL_MAN_DIR}") message(" Pkg-config ${YKPIV_INSTALL_PKGCONFIG_DIR}") message("") message(" YKCS11 debug: ${ENABLE_YKCS11_DBG}") if(ENABLE_HARDWARE_TESTS) message(" Hardware tests: Enabled. *** WARNING: RUNNING THE TESTS WILL ERASE ALL DATA ON CONNECTED YUBIKEYS *** ") else(ENABLE_HARDWARE_TESTS) message(" Hardware tests: Disabled") endif(ENABLE_HARDWARE_TESTS)yubico-piv-tool-2.7.1/README0000664000175000017500000002076714731066755015042 0ustar winniewinnie== Yubico PIV Tool === Introduction The Yubico PIV tool is used for interacting with the Personal Identity Verification (PIV) application on a https://www.yubico.com[YubiKey]. With it you may generate keys on the device, importing keys and certificates, and create certificate requests, and other operations. A shared library and a command-line tool is included. ==== Usage guides For information and examples on what you can do with a PIV enabled YubiKey, see https://developers.yubico.com/PIV/ === License In general the project is covered by the following BSD license. The file ykcs11/pkcs11.h has additional copyright and licensing information, please see it for more information. ---- Copyright (c) 2014-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: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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 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. ---- === Building on POSIX platforms Either clone from Git or download and unpackage the tarball, then make sure you have the pre-requisites installed and build following the steps below from the `yubico-piv-tool` directory. Please make sure to have recent versions of the following packages installed on your system. cmake libtool libssl-dev pkg-config check libpcsclite-dev gengetopt help2man zlib-devel Help2man is used to generate the manpages. Gengetopt version 2.22.6 or later is needed for command line parameter handling. The link:https://github.com/Yubico/yubico-piv-tool/tree/master/vagrant/development[Vagrant VM] has all these dependencies preinstalled. Please note that these package names are debian based. Other POSIX plarforms might have different names. For example, `libssl-dev` can probably be replaced with `openssl-devel` and `libpcsclite-dev` can probably be replaced by `pcsc-lite-devel` on a Redhat platform. Also note that Gengetopt might not be available on all plarforms and might need to be built from source (See https://www.gnu.org/software/gengetopt/gengetopt.html#Installation) After installation of all dependencies, run the following: $ cd yubico-piv-tool $ mkdir build; cd build $ cmake .. $ make $ sudo make install On macos, you might need to point out homebrew openssl version when running pkg-config. $ PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig" cmake .. To statically link to OpenSSL (the `libcrypto` library), use the cmake option `-DOPENSSL_STATIC_LINK=ON` Don't forget you might need to be root for the last command. On Linux it might be needed to update your linked libraries after install sudo ldconfig The backend to use is decided at compile time, see the summary at the end of the `cmake` output. Use `--with-backend=foo` to choose backend, replacing `foo` with the backend you want to use. The backends available are "pcsc", "macscard" and "winscard" using the PCSC interface, with slightly different shared library linkage and header file names: "pcsc" is used under GNU-like systems, "macscard" under Mac OS X, and "winscard" is used under Windows. In most situations, running `cmake` should automatically find the proper backend to use. === Building on Windows Building on Windows requires MSBuild or Visual Studio and the MSVC compiler. It also requires building the binaries from the https://developers.yubico.com/yubico-piv-tool/Releases/[source release] package and not from the source checked out from the repository on GitHub. This is because some files that are part of the command line shell are generated but they cannot, currently, be generated on Windows. Those files are, however, included in the source release package. On Windows, `getopt` is needed to read command line arguments. The easiest way to install `getopt` is with the https://docs.microsoft.com/en-us/cpp/build/vcpkg?view=msvc-160[`vcpkg` package manager]. The path to `getopt` DLL library and include file need to be specified as a command line argument to `cmake`. Also the path to OpenSSL needs to be specified either as a command line argument to `cmake` or by setting the environment variable `OPENSSL_ROOT_DIR` The command line examples bellow are for `PowerShell` and the prerequisites were installed from source (using `vcpkg`). $ env:OPENSSL_ROOT_DIR ="PATH/TO/OPENSSL_DIR" $ mkdir build; cd build $ cmake -A -DGETOPT_LIB_DIR="PATH/TO/GETOPT_DIR/lib" -DGETOPT_INCLUDE_DIR="PATH/TO/GETOPT_DIR/include .. $ cmake --build . To run the tests, `check` is used. The path to the `check` directory needs to be specified as a command line argument to `cmake`. Also the path to `check` binaries, `OpenSSL` binaries, `libykpiv.dll` and `libykcs11.dll` need to be in the `PATH` $ env:OPENSSL_ROOT_DIR ="PATH/TO/OPENSSL_DIR" $ mkdir build; cd build $ cmake -A -DGETOPT_LIB_DIR="PATH/TO/GETOPT_DIR/lib" -DGETOPT_INCLUDE_DIR="PATH/TO/GETOPT_DIR/include -DCHECK_PATH="PATH/TO/CHECK_DIR" .. $ cmake --build . $ $env:Path +=";PATH/TO//CHECK_DIR/bin;PATH/TO/OPENSSL_DIR/bin;PATH/TO/build\lib\Debug;PATH/TO/build\ykcs11\Debug" $ ctest.exe -C Debug For building on 32 bits system, use `Win32` as ARCH. For building on 64 bits systems, use `x64` as ARCH. ==== Coverage Code coverage is provided courtesy of lcov and https://github.com/RWTH-HPC/CMake-codecov[CMake-codecov]. This currently only works with `make`. Enable coverage with $ cmake -DENABLE_COVERAGE=1 .. You can then build the project normally and run some executables (for example running the tests with `make test`). At this point coverage evaluation can be generated with gcov/lcov related targets. For example $ make lcov will generate a single HTML report in `./lcov/html/all_targets/index.html` === Portability The main development platform is Debian GNU/Linux. The project compiles on Windows using MSVC and the PCSC backend. It can also be built for Mac OS X, also using the PCSC backend. === Example Usage For a list of all available options --help can be given. For more information on exactly what happens --verbose or --verbose=2 may be added. Generate a new ECC-P256 key on device in slot 9a, will print the public key on stdout: $ yubico-piv-tool -s9a -AECCP256 -agenerate Generate a certificate request with public key from stdin, will print the resulting request on stdout: $ yubico-piv-tool -s9a -S'/CN=foo/OU=test/O=example.com/' -averify-pin -arequest Generate a self-signed certificate with public key from stdin, will print the certificate, for later import, on stdout: $ yubico-piv-tool -s9a -S'/CN=bar/OU=test/O=example.com/' -averify-pin -aselfsign Import a certificate from stdin: $ yubico-piv-tool -s9a -aimport-certificate Set a random chuid, import a key and import a certificate from a PKCS12 file, into slot 9c: $ yubico-piv-tool -s9c -itest.pfx -KPKCS12 -aset-chuid -aimport-key \ -aimport-cert Change the management key used for administrative authentication: $ yubico-piv-tool -aset-mgm-key Delete a certificate in slot 9a, with management key being asked for: $ yubico-piv-tool -adelete-certificate -s9a -k Show some information on certificates and other data: $ yubico-piv-tool -astatus Read out the certificate from a slot and then run a signature test: $ yubico-piv-tool -aread-cert -s9a $ yubico-piv-tool -averify-pin -atest-signature -s9a Import a key into slot 85 (only available on YubiKey 4 & 5) and set the touch policy (also only available on YubiKey 4 & 5): $ yubico-piv-tool -aimport-key -s85 --touch-policy=always -ikey.pem yubico-piv-tool-2.7.1/COPYING0000664000175000017500000000236114731066755015203 0ustar winniewinnieRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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 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. yubico-piv-tool-2.7.1/vagrant/0000775000175000017500000000000014731066755015610 5ustar winniewinnieyubico-piv-tool-2.7.1/vagrant/development/0000775000175000017500000000000014731066755020132 5ustar winniewinnieyubico-piv-tool-2.7.1/vagrant/development/provision.sh0000664000175000017500000000066414731066755022524 0ustar winniewinnie#! /usr/bin/env bash # Install development dependencies sudo apt-get update -qq sudo apt-get install -qq software-properties-common sudo add-apt-repository -y ppa:yubico/stable sudo apt-get update -qq && apt-get -qq upgrade sudo apt-get install -qq \ cmake \ check \ gengetopt \ help2man \ libpcsclite-dev \ libssl-dev \ libtool \ libykpiv1 \ pkg-config \ zlib1g-dev \ virtualbox-guest-dkms yubico-piv-tool-2.7.1/vagrant/development/Vagrantfile0000664000175000017500000000220314731066755022314 0ustar winniewinnie# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| # Use Ubuntu 18.04 Bionic as a base box. config.vm.box = "generic/ubuntu1804" # Install dependencies needed for yubikey-piv-manager development. config.vm.provision "shell", path: "provision.sh" # Sync repository to /vagrant config.vm.synced_folder '../..', '/vagrant' # VirtualBox configuration config.vm.provider "virtualbox" do |vb| vb.name = "yubikey-piv-tool_development" end # Uncomment this to add a USB filter for YubiKeys. # This will connect the YubiKey to the VM when re-inserted. # This filter uses VirtualBox as provider. # Modify the paramters as needed depending on the device. FILTER_NAME="YubiKey 4" MANUFACTURER="Yubico" VENDOR_ID="0x1050" PRODUCT_ID="0x0407" PRODUCT="Yubikey 4 OTP+U2F+CCID" config.vm.provider "virtualbox" do |vb| vb.customize ['modifyvm', :id, '--usb', 'on'] vb.customize ['usbfilter', 'add', '0', '--target', :id, '--name', FILTER_NAME, '--manufacturer', MANUFACTURER, '--vendorid', VENDOR_ID, '--productid', PRODUCT_ID, '--product', PRODUCT] end end yubico-piv-tool-2.7.1/vagrant/development/README.md0000664000175000017500000000072514731066755021415 0ustar winniewinnieVagrant VM for development === Usage: alice@work $ cd yubico-piv-tool/vagrant/development alice@work $ vagrant up alice@work $ vagrant ssh ubuntu@ubuntu-xenial $ cd /vagrant ubuntu@ubuntu-xenial $ mkdir build; cd build ubuntu@ubuntu-xenial $ cmake .. ubuntu@ubuntu-xenial $ make ubuntu@ubuntu-xenial $ sudo make install ubuntu@ubuntu-xenial $ yubico-piv-tool --help ubuntu@ubuntu-xenial $ exit alice@work $ vagrant destroy yubico-piv-tool-2.7.1/NEWS0000664000175000017500000003333214731066755014651 0ustar winniewinnieyubico-piv-tool NEWS -- History of user-visible changes. -*- outline -*- * Version 2.7.1 (released 2024-12-19) ** ykpiv: Fix type casting issues affecting systems using Big-endian architecture * Version 2.7.0 (released 2024-12-19) ** ykpiv: cmd: Add support for communication over a secure channel according to SCP11b specifications ** ykpiv: cmd: Add support for device global reset * Version 2.6.1 (released 2024-09-12) ** cmd: Fix performing bio verification ** ykcs11: Fix handling ED25519 and X25519 keys * Version 2.6.0 (released 2024-08-21) ** cmd: Add support for biometric verification and match policy ** ykcs11: Add support for PKCS11 3.0 ** ykpiv: cmd: ykcs11: Improve error traceability ** ykpiv: cmd: ykcs11: Fix minor bugs ** build: Make building with zlib optional * Version 2.5.2 (released 2024-05-07) ** cmd: Fix signing selfsigned certificate for ED25519 key. * Version 2.5.1 (released 2024-02-14) ** ykpiv: cmd: ykcs11: Fix buffer size for key import. * Version 2.5.0 (released 2024-01-31) ** ykpiv: cmd: ykcs11: Add support for RSA3072 and RSA4096 key types. Available in firmware 5.7.0 and newer ** ykpiv: cmd: Add support for ED25519 and X25519 key types. Available in firmware 5.7.0 and newer ** ykpiv: cmd: Add support for deleting keys. Available in firmware 5.7.0 and newer ** ykpiv: cmd: Add support for moving keys between slots. Available in firmware 5.7.0 and newer * Version 2.4.2 (released 2023-12-07) ** ykpiv: Fix potential type casting bug. * Version 2.4.1 (released 2023-12-05) ** ykpiv: ykcs11: Fix building on certain architectures. Existing builds are not affected. * Version 2.4.0 (released 2023-11-30) ** ykpiv: cmd: Add support for compressing certificate upon import ** ykcs11: Increase maximum number of slots to handle overflow ** ykcs11: Add support for CKA_COPYABLE and CKA_DESTROYABLE attributes * Version 2.3.1 (released 2023-02-07) ** ykpiv: Add support for T=0 smartcards ** ykpiv: ykcs11: Minor code optimization ** ykpiv: ykcs11: Improve logging ** ykpiv: ykcs11: Improve error handling ** ykpiv: ykcs11: Fix minor bugs ** ykcs11: Add support for several PKCS11 Attributes ** ykcs11: Add support for CKM_ECDSA_SHA512 mechanism ** ykcs11: Fix incorrect value for public key attributes CKA_PRIVATE, CKA_SENSITIVE, CKA_ALWAYS_SENSITIVE, CKA_EXTRACTABLE and CKA_NEVER_EXTRACTABLE ** doc: Minor documentation improvement * Version 2.3.0 (released 2022-03-01) ** ykpiv: Add support for AES management keys ** ykpiv: Better handling of connection reset ** ykpiv: Add support for T=0 protocol ** ykcs11: Support YubiKeys in NFC readers ** ykcs11: Support touch and PIN policies for imported private keys ** ykcs11: Support touch and PIN policy when generating keys ** ykcs11: Set length to -1 on function fail ** ykcs11: Ignore CKA_NAME_HASH_ALGORITHM and CKA_HASH_OF_SUBJECT_PUBLIC_KEY for certificates ** cmd: Support attestation in selfsign certificates ** build: Compile cleanly with openssl 1.1 and 3 * Version 2.2.1 (released 2021-09-07) ** ykpiv: Minor bug fixes ** ykcs11: Improved handling of object attributes ** ykcs11: Update flags for EC related mechanisms ** ykcs11: Minor bug fixes ** test: Improved testing ** doc: Improved documentation * Version 2.2.0 (released 2021-01-20) ** ykpiv: Increased SO version ** ykpiv: Fixed minor memory leaks ** ykpiv: Improved error handling ** ykpiv: Improved handling of PCSC card validation ** ykcs11: Updated Cryptoki version ** ykcs11: Support for CKM_ECDH1_DERIVE mechanism info ** ykcs11: Support for destroying ECDH derived keys ** ykcs11: Improved handling of PIN after device re-connection ** ykcs11: Improved debug logging ** cmd: Improved parsing of certificate Distinguished Name to allow an escape character ** cmd: Warning to discourage generating RSA1024 keys ** build: Use of platform standard installation path when building yubico-piv-tool ** tests: Improved testing * Version 2.1.1 (released 2020-07-20) ** Fixed missing dependency when building debian package * Version 2.1.0 (released 2020-07-08) ** Replaced building with autotool with building with cmake ** Security update for https://www.yubico.com/support/security-advisories/ysa-2020-02/[YSA-2020-02] ** ykpiv: Fixed potential memory leaks ** ykpiv: Use PIN-protected MGMT key if the device is configured that way ** ykpiv: Added attestation to CSR if requested ** ykpiv: Fixed compatibility with LibreSSL ** ykcs11: Improved handling of error codes ** ykcs11: Improved handling of examples in the PKCS11 specifications ** ykcs11: Added the possibility to have debug output as a runtime setting ** ykcs11: Added support to unblock PIN with PUK ** ykcs11: Make C_SetPIN backwards compatible while also allowing unblock PIN ** tests: Improved tests * Version 2.0.0 (released 2020-01-29) ** ykpiv: Added ykpiv_get_metadata and ykpiv_util_parse_metadata to read and parse private key metadata (supported from YK 5.3). ** ykpiv: Fixed PCSC transaction handling when re-selecting PIV due to external card reset events. ** ykpiv: Improved error reporting. ** ykpiv: Correctly report YK5 devices, and NEO and YK5 over NFC. ** ykpiv: MGM KEY (SO PIN) is cached (in addition to PIN). ** ykpiv: Fixed resetting of cached serial / version when an application re-uses ykpiv_state. ** ykpiv: ykpiv_get_pin_retries selects a different applet before re-selecting PIV since just re-selecting PIV is a no-op on YK5. ** ykcs11: Shared library exports all PKCS11 functions per the spec (For applications that don't use C_GetFunctionList). ** ykcs11: Support for up to 16 simultaneous sessions, with support for multi-threaded access (if requested when calling C_Initialize). ** ykcs11: Support for resetting the PIV application via C_initToken. Requires knowledge of the MGMT KEY (SO PIN) per the PKCS11 spec. ** ykcs11: Support for public-key operations not supported by PIV (C_Verify, C_Encrypt), implemented using OpenSSL. ** ykcs11: Support for attestations, exposed as session objects of certificate class. Generated when opening the first session to a slot. ** ykcs11: Support for forked processes on Linux and MacOS. ** ykcs11: Support for RSA signatures using PKCS or PSS padding with optional digesting by the library. Raw signatures are also supported. ** ykcs11: Support for ECDSA signatures with optional digesting by the library. Raw signatures are also supported. ** ykcs11: Support for RSA encryption / decryption with PKCS or OAEP padding. ** ykcs11: Makes use of key metadata when available (YK 5.3 and above), providing access to keys even if certificates are not present. ** ykcs11: Supports SHA1, SHA256, SHA384 and SHA512 digesting, plus SHA224 digesting for ECDSA signatures and for the MGF1 digest in PSS / OAEP, implemented using OpenSSL. ** ykcs11: Supports C_Login with context-specific user type. This allows use cases that require both SO PIN and normal PIN in the same session. * Version 1.7.0 (released 2019-04-03) ** Add ykpiv_get_serial() to API. ** Add version and serial to status output. ** FASC-N fixes for CHUID. ** ykcs11: Fix ECDSA signatures. ** Make selfsigned X.509 extensions have correct extensions to match openssl. ** Security fixes. ** Documentation fixes. ** Try to clear memory that might contain secrets. * Version 1.6.2 (released 2018-09-14) ** Compare reader names case insensitive. ** Fix certificate and certificate request signatures with OpenSSL 1.1. * Version 1.6.1 (released 2018-08-17) ** Compilation warning fixes for OpenSSL 1.1 builds. ** Fix length when encoding exactly 0xff bytes. ** Check length of objects correctly before storing in buffer. ** Check length of certificate correctly when storing. * Version 1.6.0 (released 2018-08-08) ** Security release to mitigate https://www.yubico.com/support/security-advisories/ysa-2018-03/[YSA-2018-03]. ** Allow builiding against LibreSSL. ** Bugfixes in OpenSSL 1.1 code. ** Fix compilation warnings. ** Fix ykcs11 key generation to work with OpenSSL 1.1. ** Ykcs11 compatibility fixes. * Version 1.5.0 (released 2017-11-29) ** API additions: Higher-level "util" API added to libykpiv. ** Added ykpiv_attest(), ykpiv_get_pin_retries(), ykpiv_set_pin_retries() ** Added functions for using existing PCSC card handle. ** Support using custom memory allocator. ** Documentation updates. 'make doxygen' for HTML format. ** Expanded automated tests for hardware devices, moved to 'make hwcheck'. ** OpenSSL 1.1 support ** Moderate internal refactoring. Many small bugs fixed. * Version 1.4.4 (released 2017-10-17) ** Documentation updates. ** Add pin caching to work around disconnect problems. ** Disable RSA key generation on YubiKey 4 before 4.3.5. See https://yubi.co/ysa201701/ for details. * Version 1.4.3 (released 2017-04-18) ** Encode RSA x509 certificates correctly. ** Documentation updates. ** In ykcs11 return CKA_MODULUS correctly for private keys. ** In ykcs11 fix for signature size approximation. ** Fix PSS signatures in ykcs11. ** Add a CLI flag --stdin-input to make batch execution easier. * Version 1.4.2 (released 2016-08-12) ** Clarify license headers and clean up YKCS11 licensing. Now uses pkcs11.h from the Scute project. ** Don't install ykcs11-version.h. ** No cflags in ykcs11.pc. ** Unimplemented YKCS11 functions now return CKR_FUNCTION_FAILED. * Version 1.4.1 (released 2016-08-11) ** Documentation updates ** Add possibility to export certificates in SSH format. ** Make certificate serial number random by default. * Version 1.4.0 (released 2016-05-03) ** Add attest action When used on a slot with a generated key, outputs a signed x509 certificate for that slot showing that the key was generated in hardware. Available in firmware 4.3.0 and newer. ** Add cached parameter for touch-policy With cached, the touch is valid for an additional 15s. Available in firmware 4.3.0 and newer. ** Enforce a minimum PIN length of 6 characters. ** Fix a bug with list-readers action where it fell through processing into write-object. * Version 1.3.1 (released 2016-04-19) ** Fix a bug where unblock pin would instead change puk, introduced in 1.3.0. ** Clarifications with help texts. * Version 1.3.0 (released 2016-02-19) ** Fixed extraction of RSA modulus and exponent for pkcs11. ** Implemented C_SetPIN for pkcs11. ** Add generic write and read object actions for the tool. Supports hex/binary/base64 formats ** Add ykpiv_change_pin(), ykpiv_change_puk() and ykpiv_unblock_pin() ** Print CCC with status action. ** Address bugs with pkcs11 on windows. ** Add --valid-days and --serial to tool for selfsign-certificate action. ** Ask for password for pkcs12 if none is given. * Version 1.2.2 (released 2015-12-08) ** Fix old buffer overflow in change-pin functionality. * Version 1.2.1 (released 2015-12-08) ** Fix issue with big certificates and status. * Version 1.2.0 (released 2015-12-07) ** On OSX use @loader_path instead of @executable_path for ykcs11. ** Add ykpiv_import_private_key to libykpiv. ** Raise buffer sizes to support bigger objects. ** Change behavior of action status, only list populated slots. ** Add retired keys to ykcs11. ** In ykcs11 support login with non null terminated pin. ** Add a new action set-ccc to yubico-piv-tool to set the CCC. * Version 1.1.2 (released 2015-11-13) ** Properly handle DER encoding in ECDSA signatures. * Version 1.1.1 (released 2015-11-11) ** Make sure SCardContext is properly acquired and released. * Version 1.1.0 (released 2015-11-06) ** Add support for new YubiKey 4. ** Add ykcs11. * Version 1.0.3 (released 2015-10-01) ** Correct wording on unblock-pin action. ** Show pin retries correctly. ** Use a bigger buffer for receiving data. * Version 1.0.2 (released 2015-09-04) ** Query for different passwords/pins on stdin if they're not supplied. ** If a reader fails continue trying matching readers. ** Authentication failed is supposed to be 0x63cX not 0x630X. * Version 1.0.1 (released 2015-07-10) ** Project relicensed to 2-clause BSD license ** Minor fixes found with clang scan-build * Version 1.0.0 (released 2015-06-23) ** Add a test-decipher action. ** Check that e is 0x10001 on importing rsa keys ** Use PCSC transactions when sending and receiving data * Version 0.1.6 (released 2015-03-23) ** Add a read-certificate action to the tool. ** Add a status action to the tool. ** Fix a library bug so NULL can be passed to ykpiv_verify() ** Add a test-signature action to the tool. * Version 0.1.5 (released 2015-02-04) ** Revert the check for parity and just set parity before the weak check. * Version 0.1.4 (released 2015-02-02) ** Prompt for input if input is stdin. ** Mark all bits of the signature as used is certs and requests. ** Correct error for unblock-pin. ** Fix hex decode to decode capital letters and return error. ** Check parity of new management keys. * Version 0.1.3 (released 2014-12-18) ** Add format DER for importing certificates. ** Make sure diagnostic feedback ends up on stderr. ** Add positive feedback for a couple of actions. * Version 0.1.2 (released 2014-11-14) ** Fix an issue where shorter component of RSA keys where not packed correctly. * Version 0.1.1 (released 2014-11-10) ** Correct broken CHUID that made windows work inconsistently. ** Add support for compressed certificates. ** Fix broken unblock-pin action. ** Don't try to accept to short keys for mgm key. ** Only do applet authentication if needed. ** Add --hash for selecting what hash to use for signatures. ** Add hidden --sign command. Should probably not be used. ** Fix for signature algorithm in selfsigned cert. * Version 0.1.0 (released 2014-08-25) ** Break out functionality into a library. ** More testing. * Version 0.0.3 (released 2014-05-26) ** Add delete-certificate action. ** Fix minor bugs. * Version 0.0.2 (released 2014-02-19) ** Fix an offset bug with CHUID. ** Do full mutual auth with the applet. * Version 0.0.1 (released 2014-02-11) ** Initial release. yubico-piv-tool-2.7.1/aes_cmac/0000775000175000017500000000000014731066755015701 5ustar winniewinnieyubico-piv-tool-2.7.1/aes_cmac/aes_cmac.c0000664000175000017500000000763314731066755017611 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ // AES-CMAC implementation as defined in SP-800-38B // AES key length can be one of 128, 192, 256 // Output length is one full block (16 bytes) #include #include "aes_cmac.h" #include "insecure_memzero.h" static const uint8_t zero[AES_BLOCK_SIZE] = {0}; static void do_pad(uint8_t *data, uint8_t len) { for (uint8_t i = len; i < AES_BLOCK_SIZE; i++) if (i == len) data[i] = 0x80; else data[i] = 0x00; } static void do_xor(const uint8_t *a, uint8_t *b) { for (uint8_t i = 0; i < AES_BLOCK_SIZE; i++) { b[i] ^= a[i]; } } static void do_shift_one_bit_left(const uint8_t *a, uint8_t *b, uint8_t *carry) { for (int8_t i = AES_BLOCK_SIZE - 1; i >= 0; i--) { b[i] = (a[i] << 1) | *carry; *carry = a[i] >> 7; } } static void cmac_generate_subkey(const uint8_t *key, uint8_t *subkey) { uint8_t carry = 0; do_shift_one_bit_left(key, subkey, &carry); subkey[AES_BLOCK_SIZE - 1] ^= 0x87 >> (8 - (carry * 8)); } int aes_cmac_encrypt(aes_cmac_context_t *ctx, const uint8_t *message, const uint32_t message_len, uint8_t *mac) { uint8_t M[AES_BLOCK_SIZE] = {0}; const uint8_t *ptr = message; memcpy(mac, zero, AES_BLOCK_SIZE); uint8_t n_blocks; if (message_len == 0) n_blocks = 0; else n_blocks = (message_len + (AES_BLOCK_SIZE - 1)) / AES_BLOCK_SIZE - 1; uint32_t out_len = AES_BLOCK_SIZE; for (uint8_t i = 0; i < n_blocks; i++) { int rc = aes_cbc_encrypt(ptr, AES_BLOCK_SIZE, mac, &out_len, mac, AES_BLOCK_SIZE, ctx->aes_ctx); if (rc) { return rc; } ptr += AES_BLOCK_SIZE; } uint8_t remaining_bytes = (message_len % AES_BLOCK_SIZE); if (remaining_bytes == 0) { if (message != NULL && message_len != 0) { memcpy(M, ptr, AES_BLOCK_SIZE); do_xor(ctx->k1, M); } else { do_pad(M, 0); do_xor(ctx->k2, M); } } else { memcpy(M, ptr, remaining_bytes); do_pad(M, remaining_bytes); do_xor(ctx->k2, M); } return aes_cbc_encrypt(M, AES_BLOCK_SIZE, mac, &out_len, mac, AES_BLOCK_SIZE, ctx->aes_ctx); } int aes_cmac_init(aes_context *aes_ctx, aes_cmac_context_t *ctx) { uint8_t L[AES_BLOCK_SIZE] = {0}; ctx->aes_ctx = aes_ctx; uint32_t out_len = AES_BLOCK_SIZE; int rc = aes_encrypt(zero, AES_BLOCK_SIZE, L, &out_len, ctx->aes_ctx); if (rc) { return rc; } cmac_generate_subkey(L, ctx->k1); cmac_generate_subkey(ctx->k1, ctx->k2); return 0; } void aes_cmac_destroy(aes_cmac_context_t *ctx) { if (ctx) { insecure_memzero(ctx, sizeof(aes_cmac_context_t)); } } yubico-piv-tool-2.7.1/aes_cmac/insecure_memzero.h0000664000175000017500000000361014731066755021425 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef YUBICO_PIV_TOOL_INSECURE_MEMZERO_H #define YUBICO_PIV_TOOL_INSECURE_MEMZERO_H #ifdef _WIN32 #include #define insecure_memzero(buf, len) SecureZeroMemory(buf, len) #elif HAVE_MEMSET_S #include #define insecure_memzero(buf, len) memset_s(buf, len, 0, len) #elif HAVE_EXPLICIT_BZERO #include #define insecure_memzero(buf, len) explicit_bzero(buf, len) #else #include #define insecure_memzero(buf, len) OPENSSL_cleanse(buf, len) #endif #endif //YUBICO_PIV_TOOL_INSECURE_MEMZERO_H yubico-piv-tool-2.7.1/aes_cmac/aes_cmac.h0000664000175000017500000000404114731066755017604 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef YUBICO_PIV_TOOL_AES_CMAC_H #define YUBICO_PIV_TOOL_AES_CMAC_H #include "aes.h" typedef struct { aes_context *aes_ctx; uint8_t k1[AES_BLOCK_SIZE]; uint8_t k2[AES_BLOCK_SIZE]; } aes_cmac_context_t; #ifndef _WIN32 #define YH_INTERNAL __attribute__((visibility("hidden"))) #else #define YH_INTERNAL #endif int YH_INTERNAL aes_cmac_init(aes_context *aes_ctx, aes_cmac_context_t *ctx); int YH_INTERNAL aes_cmac_encrypt(aes_cmac_context_t *ctx, const uint8_t *message, const uint32_t message_len, uint8_t *mac); void YH_INTERNAL aes_cmac_destroy(aes_cmac_context_t *ctx); #endif //YUBICO_PIV_TOOL_AES_CMAC_H yubico-piv-tool-2.7.1/aes_cmac/aes.h0000664000175000017500000000624114731066755016625 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef YUBICO_PIV_TOOL_AES_H #define YUBICO_PIV_TOOL_AES_H #include #ifdef _WIN32 #include #include #include #else #include #endif #ifndef AES_BLOCK_SIZE // Defined in openssl/aes.h #define AES_BLOCK_SIZE 16 #endif #ifdef __cplusplus extern "C" { #endif typedef struct { #ifdef _WIN32 BCRYPT_ALG_HANDLE hAlgCBC; BCRYPT_ALG_HANDLE hAlgECB; BCRYPT_KEY_HANDLE hKeyCBC; BCRYPT_KEY_HANDLE hKeyECB; PBYTE pbKeyCBCObj; PBYTE pbKeyECBObj; size_t cbKeyObj; #else EVP_CIPHER_CTX *ctx; unsigned char key_algo; uint8_t key[EVP_MAX_KEY_LENGTH]; #endif } aes_context; #ifndef _WIN32 #define YH_INTERNAL __attribute__((visibility("hidden"))) #else #define YH_INTERNAL #endif int YH_INTERNAL aes_set_key(const uint8_t *key, uint32_t key_len, unsigned char key_algo, aes_context *ctx); int YH_INTERNAL aes_encrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, aes_context *ctx); int YH_INTERNAL aes_decrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, aes_context *ctx); int YH_INTERNAL aes_cbc_encrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, const uint8_t *iv, uint32_t iv_len, aes_context *ctx); int YH_INTERNAL aes_cbc_decrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, const uint8_t *iv, uint32_t iv_len, aes_context *ctx); uint32_t YH_INTERNAL aes_blocksize(aes_context *key); int YH_INTERNAL aes_add_padding(uint8_t *in, uint32_t max_len, uint32_t *len); void YH_INTERNAL aes_remove_padding(uint8_t *in, uint32_t *len); int YH_INTERNAL aes_destroy(aes_context *ctx); #ifdef __cplusplus } #endif #endif //YUBICO_PIV_TOOL_AES_H yubico-piv-tool-2.7.1/aes_cmac/aes.c0000664000175000017500000002754014731066755016625 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include "ykpiv.h" #include "aes.h" #include "insecure_memzero.h" #include #include #include #ifdef _WIN32 #include #endif #ifdef _WIN32 static LPCWSTR bcrypt_algo(unsigned char algo) { switch (algo) { case YKPIV_ALGO_3DES: return BCRYPT_3DES_ALGORITHM; case YKPIV_ALGO_AES128: case YKPIV_ALGO_AES192: case YKPIV_ALGO_AES256: return BCRYPT_AES_ALGORITHM; default: return NULL; } } static NTSTATUS init_ctx(aes_context *ctx, unsigned char key_algo) { NTSTATUS status = STATUS_SUCCESS; BCRYPT_ALG_HANDLE hAlgCBC = 0; BCRYPT_ALG_HANDLE hAlgECB = 0; DWORD cbKeyObj = 0; DWORD cbData = 0; if (!ctx) { return STATUS_INVALID_PARAMETER; } if (ctx->hAlgCBC) { return STATUS_SUCCESS; } /* clear the context, to "reset" */ insecure_memzero(ctx, sizeof(aes_context)); if (!BCRYPT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hAlgCBC, bcrypt_algo(key_algo), NULL, 0))) { goto cleanup; } if (!BCRYPT_SUCCESS(status = BCryptSetProperty(hAlgCBC, BCRYPT_CHAINING_MODE, (PBYTE) BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC), 0))) { goto cleanup; } if (!BCRYPT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hAlgECB, bcrypt_algo(key_algo), NULL, 0))) { goto cleanup; } if (!BCRYPT_SUCCESS(status = BCryptSetProperty(hAlgECB, BCRYPT_CHAINING_MODE, (PBYTE) BCRYPT_CHAIN_MODE_ECB, sizeof(BCRYPT_CHAIN_MODE_ECB), 0))) { goto cleanup; } if (!BCRYPT_SUCCESS(status = BCryptGetProperty(hAlgCBC, BCRYPT_OBJECT_LENGTH, (PBYTE) &cbKeyObj, sizeof(DWORD), &cbData, 0))) { goto cleanup; } ctx->hAlgCBC = hAlgCBC; hAlgCBC = 0; ctx->hAlgECB = hAlgECB; hAlgECB = 0; ctx->cbKeyObj = cbKeyObj; cleanup: if (hAlgCBC) { BCryptCloseAlgorithmProvider(hAlgCBC, 0); } if (hAlgECB) { BCryptCloseAlgorithmProvider(hAlgECB, 0); } return status; } static NTSTATUS import_key(BCRYPT_ALG_HANDLE hAlg, BCRYPT_KEY_HANDLE *phKey, PBYTE *ppbKeyObj, DWORD cbKeyObj, const uint8_t *key, size_t key_len) { NTSTATUS status = STATUS_SUCCESS; PBYTE pbKeyObj = NULL; BCRYPT_KEY_HANDLE hKey = 0; PBYTE pbKeyBlob = NULL; DWORD cbKeyBlob = 0; if (!phKey || !ppbKeyObj) { return STATUS_INVALID_PARAMETER; } /* close existing key first */ if (*phKey) { BCryptDestroyKey(*phKey); *phKey = 0; } /* free existing key object */ if (*ppbKeyObj) { free(*ppbKeyObj); *ppbKeyObj = NULL; } /* allocate new key object */ if (!(pbKeyObj = (PBYTE) malloc(cbKeyObj))) { status = STATUS_NO_MEMORY; goto cleanup; } cbKeyBlob = (DWORD) (sizeof(BCRYPT_KEY_DATA_BLOB_HEADER) + key_len); if (!(pbKeyBlob = (PBYTE) malloc(cbKeyBlob))) { status = STATUS_NO_MEMORY; goto cleanup; } /* set up BCrypt Key Blob for import */ ((BCRYPT_KEY_DATA_BLOB_HEADER *) pbKeyBlob)->dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC; ((BCRYPT_KEY_DATA_BLOB_HEADER *) pbKeyBlob)->dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1; ((BCRYPT_KEY_DATA_BLOB_HEADER *) pbKeyBlob)->cbKeyData = (DWORD) key_len; memcpy(pbKeyBlob + sizeof(BCRYPT_KEY_DATA_BLOB_HEADER), key, key_len); if (!BCRYPT_SUCCESS(status = BCryptImportKey(hAlg, NULL, BCRYPT_KEY_DATA_BLOB, &hKey, pbKeyObj, cbKeyObj, pbKeyBlob, cbKeyBlob, 0))) { goto cleanup; } /* set output params */ *phKey = hKey; hKey = 0; *ppbKeyObj = pbKeyObj; pbKeyObj = 0; cleanup: if (hKey) { BCryptDestroyKey(hKey); } if (pbKeyObj) { free(pbKeyObj); } if (pbKeyBlob) { free(pbKeyBlob); } return !BCRYPT_SUCCESS(status); } #else static const EVP_CIPHER *aes_ecb(unsigned char algo) { switch (algo) { case YKPIV_ALGO_3DES: return EVP_des_ede3_ecb(); case YKPIV_ALGO_AES128: return EVP_aes_128_ecb(); case YKPIV_ALGO_AES192: return EVP_aes_192_ecb(); case YKPIV_ALGO_AES256: return EVP_aes_256_ecb(); default: return NULL; } } static const EVP_CIPHER *aes_cbc(unsigned char algo) { switch (algo) { case YKPIV_ALGO_3DES: return EVP_des_ede3_cbc(); case YKPIV_ALGO_AES128: return EVP_aes_128_cbc(); case YKPIV_ALGO_AES192: return EVP_aes_192_cbc(); case YKPIV_ALGO_AES256: return EVP_aes_256_cbc(); default: return NULL; } } static int aes_encrypt_ex(const EVP_CIPHER *cipher, const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, const uint8_t *iv, aes_context *ctx, int enc) { if (EVP_CipherInit_ex(ctx->ctx, cipher, NULL, ctx->key, iv, enc) != 1) { return -1; } if (EVP_CIPHER_CTX_set_padding(ctx->ctx, 0) != 1) { return -2; } int update_len = in_len; if (EVP_CipherUpdate(ctx->ctx, out, &update_len, in, in_len) != 1) { return -3; } int final_len = in_len - update_len; if (EVP_CipherFinal_ex(ctx->ctx, out + update_len, &final_len) != 1) { return -4; } if (update_len + final_len != update_len) { return -5; } *out_len = update_len; return 0; } #endif int aes_set_key(const uint8_t *key, uint32_t key_len, unsigned char key_algo, aes_context *ctx) { #ifdef _WIN32 NTSTATUS status = STATUS_SUCCESS; if (!BCRYPT_SUCCESS(status = init_ctx(ctx, key_algo))) { return -1; } if (!BCRYPT_SUCCESS(status = import_key(ctx->hAlgCBC, &(ctx->hKeyCBC), &(ctx->pbKeyCBCObj), ctx->cbKeyObj, key, key_len))) { return -2; } if (!BCRYPT_SUCCESS(status = import_key(ctx->hAlgECB, &(ctx->hKeyECB), &(ctx->pbKeyECBObj), ctx->cbKeyObj, key, key_len))) { return -3; } #else if (key == NULL || aes_ecb(key_algo) == NULL) { return -1; } if (!ctx->ctx) { ctx->ctx = EVP_CIPHER_CTX_new(); if (!ctx->ctx) { return -2; } } ctx->key_algo = key_algo; memcpy(ctx->key, key, key_len); #endif return 0; } int aes_encrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, aes_context *ctx) { #ifdef _WIN32 NTSTATUS status = STATUS_SUCCESS; ULONG cbResult = 0; if (!BCRYPT_SUCCESS(status = BCryptEncrypt(ctx->hKeyECB, (PUCHAR) in, in_len, NULL, NULL, 0, out, in_len, &cbResult, 0))) { return -1; } if (cbResult != aes_blocksize(ctx)) { return -2; } *out_len = in_len; return 0; #else return aes_encrypt_ex(aes_ecb(ctx->key_algo), in, in_len, out, out_len, NULL, ctx, 1); #endif } int aes_decrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, aes_context *ctx) { #ifdef _WIN32 NTSTATUS status = STATUS_SUCCESS; ULONG cbResult = 0; if (!BCRYPT_SUCCESS(status = BCryptDecrypt(ctx->hKeyECB, (PUCHAR) in, in_len, NULL, NULL, 0, out, in_len, &cbResult, 0))) { return -1; } if (cbResult != aes_blocksize(ctx)) { return -2; } *out_len = in_len; return 0; #else return aes_encrypt_ex(aes_ecb(ctx->key_algo), in, in_len, out, out_len, NULL, ctx, 0); #endif } int aes_cbc_encrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, const uint8_t *iv, uint32_t iv_len, aes_context *ctx) { #ifdef _WIN32 NTSTATUS status = STATUS_SUCCESS; ULONG cbResult = 0; if (!BCRYPT_SUCCESS(status = BCryptEncrypt(ctx->hKeyCBC, (PUCHAR) in, in_len, NULL, iv, iv_len, out, in_len, &cbResult, 0))) { return -1; } if (cbResult != in_len) { return -2; } *out_len = in_len; return 0; #else return aes_encrypt_ex(aes_cbc(ctx->key_algo), in, in_len, out, out_len, iv, ctx, 1); #endif } int aes_cbc_decrypt(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t *out_len, const uint8_t *iv, uint32_t iv_len, aes_context *ctx) { #ifdef _WIN32 NTSTATUS status = STATUS_SUCCESS; ULONG cbResult = 0; if (!BCRYPT_SUCCESS(status = BCryptDecrypt(ctx->hKeyCBC, (PUCHAR) in, in_len, NULL, iv, iv_len, out, in_len, &cbResult, 0))) { return -1; } if (cbResult != in_len) { return -2; } *out_len = in_len; return 0; #else return aes_encrypt_ex(aes_cbc(ctx->key_algo), in, in_len, out, out_len, iv, ctx, 0); #endif } uint32_t aes_blocksize(aes_context *key) { if (!key) { return 0; } #ifdef _WIN32 DWORD size = 0; ULONG len = 0; if(!BCRYPT_SUCCESS(BCryptGetProperty(key->hKeyECB, BCRYPT_BLOCK_LENGTH, (PUCHAR)&size, sizeof(size), &len, 0))) { return 0; } return size; #else return EVP_CIPHER_block_size(aes_ecb(key->key_algo)); #endif } int aes_add_padding(uint8_t *in, uint32_t max_len, uint32_t *len) { uint32_t new_len = *len; if (in) { if (new_len >= max_len) { return -1; } in[new_len] = 0x80; } new_len++; while (new_len % AES_BLOCK_SIZE != 0) { if (in) { if (new_len >= max_len) { return -2; } in[new_len] = 0x00; } new_len++; } *len = new_len; return 0; } void aes_remove_padding(uint8_t *in, uint32_t *len) { while ((*len) > 1 && in[(*len) - 1] == 0) { (*len)--; } if (*len > 0) (*len)--; } int aes_destroy(aes_context *ctx) { if (!ctx) { return 0; } #ifdef _WIN32 if (ctx->hKeyCBC) { BCryptDestroyKey(ctx->hKeyCBC); } if (ctx->pbKeyCBCObj) { free(ctx->pbKeyCBCObj); } if (ctx->hKeyECB) { BCryptDestroyKey(ctx->hKeyECB); } if (ctx->pbKeyECBObj) { free(ctx->pbKeyECBObj); } if (ctx->hAlgCBC) { BCryptCloseAlgorithmProvider(ctx->hAlgCBC, 0); } if (ctx->hAlgECB) { BCryptCloseAlgorithmProvider(ctx->hAlgECB, 0); } #else EVP_CIPHER_CTX_free(ctx->ctx); #endif insecure_memzero(ctx, sizeof(aes_context)); return 0; } yubico-piv-tool-2.7.1/lib/0000775000175000017500000000000014731067277014714 5ustar winniewinnieyubico-piv-tool-2.7.1/lib/CMakeLists.txt0000664000175000017500000001010514731066755017451 0ustar winniewinnie# 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: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * 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 # 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. message("lib/CMakeList.txt") include(${CMAKE_SOURCE_DIR}/cmake/pcscd.cmake) find_pcscd() set(YKPIV_VERSION_STRING "${yubico_piv_tool_VERSION_MAJOR}.${yubico_piv_tool_VERSION_MINOR}.${yubico_piv_tool_VERSION_PATCH}") set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) set(SOURCE ykpiv.c util.c version.c error.c internal.c ecdh.c scp11_util.c ../aes_cmac/aes.c ../aes_cmac/aes_cmac.c ../common/openssl-compat.c ../common/util.c) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/common ) if (ENABLE_CERT_COMPRESS) add_definitions(-DUSE_CERT_COMPRESS="1") find_library(ZLIB zlib PATHS ${ZLIB_LIB_DIR}) include_directories(${ZLIB_INCL_DIR}) find_package(ZLIB REQUIRED) set(ZLIB_LIBS "ZLIB::ZLIB") endif() if(WIN32) set(ADDITIONAL_LIBRARY ws2_32) endif () # static library if(BUILD_STATIC_LIB) add_library(ykpiv STATIC ${SOURCE}) target_link_libraries(ykpiv ${LIBCRYPTO_LIBRARIES} ${PCSC_LIBRARIES} ${ZLIB_LIBS} ${ADDITIONAL_LIBRARY}) set_target_properties (ykpiv PROPERTIES COMPILE_FLAGS "-DSTATIC ") if(WIN32) set_target_properties(ykpiv PROPERTIES OUTPUT_NAME ykpiv_static) endif() add_coverage(ykpiv) endif(BUILD_STATIC_LIB) # dynamic library add_library(ykpiv_shared SHARED ${SOURCE}) target_link_libraries(ykpiv_shared ${LIBCRYPTO_LIBRARIES} ${PCSC_LIBRARIES} ${ZLIB_LIBS} ${ADDITIONAL_LIBRARY}) set_target_properties(ykpiv_shared PROPERTIES SOVERSION ${SO_VERSION} VERSION ${VERSION}) if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set_target_properties(ykpiv_shared PROPERTIES INSTALL_RPATH "${YKPIV_INSTALL_LIB_DIR}") endif() if(WIN32) set_target_properties(ykpiv_shared PROPERTIES OUTPUT_NAME libykpiv) else(WIN32) set_target_properties(ykpiv_shared PROPERTIES OUTPUT_NAME ykpiv) endif(WIN32) add_coverage(ykpiv_shared) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ykpiv.pc.in ${CMAKE_CURRENT_SOURCE_DIR}/ykpiv.pc @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ykpiv-config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/ykpiv-config.h @ONLY) install( TARGETS ykpiv_shared ARCHIVE DESTINATION ${YKPIV_INSTALL_LIB_DIR} LIBRARY DESTINATION ${YKPIV_INSTALL_LIB_DIR} RUNTIME DESTINATION ${YKPIV_INSTALL_BIN_DIR}) if(BUILD_STATIC_LIB) install( TARGETS ykpiv ARCHIVE DESTINATION ${YKPIV_INSTALL_LIB_DIR} LIBRARY DESTINATION ${YKPIV_INSTALL_LIB_DIR} RUNTIME DESTINATION ${YKPIV_INSTALL_BIN_DIR}) endif(BUILD_STATIC_LIB) install(FILES ykpiv.h DESTINATION ${YKPIV_INSTALL_INC_DIR}/ykpiv) install(FILES ykpiv-config.h DESTINATION ${YKPIV_INSTALL_INC_DIR}/ykpiv) install(FILES ykpiv.pc DESTINATION ${YKPIV_INSTALL_PKGCONFIG_DIR}) add_subdirectory(tests) yubico-piv-tool-2.7.1/lib/ecdh.c0000664000175000017500000002220414731066755015763 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include #ifdef _WIN32 #include #include #else #include #include #endif #include "ecdh.h" #ifdef _WIN32 static const uint8_t n_P256[] = "\xff\xff\xff\xff\x00\x00\x00\x00" "\xff\xff\xff\xff\xff\xff\xff\xff" "\xbc\xe6\xfa\xad\xa7\x17\x9e\x84" "\xf3\xb9\xca\xc2\xfc\x63\x25\x51"; static const BCRYPT_ALG_HANDLE curves[] = {NULL, BCRYPT_ECDH_P256_ALG_HANDLE}; static const ULONG lengths[] = {0, 256}; int ecdh_curve_p256(void) { return 1; } static int bn_cmp(const uint8_t *a, const uint8_t *b, size_t cb) { for (size_t i = 0; i < cb; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return 0; } static int validate_privkey(int curve, const uint8_t *privkey, size_t cb_privkey) { return curve == 1 && cb_privkey == 32 && bn_cmp(privkey, n_P256, 32) < 0; } int ecdh_calculate_public_key(int curve, const uint8_t *privkey, size_t cb_privkey, uint8_t *pubkey, size_t cb_pubkey) { int rc = 0; if (validate_privkey(curve, privkey, cb_privkey)) { uint8_t buf[256]; BCRYPT_ECCKEY_BLOB *blob = (BCRYPT_ECCKEY_BLOB *) buf; blob->dwMagic = BCRYPT_ECDH_PRIVATE_P256_MAGIC; blob->cbKey = cb_privkey; memset(buf + sizeof(BCRYPT_ECCKEY_BLOB), 0, 2 * cb_privkey); memcpy(buf + sizeof(BCRYPT_ECCKEY_BLOB) + 2 * cb_privkey, privkey, cb_privkey); BCRYPT_KEY_HANDLE key; NTSTATUS status = BCryptImportKeyPair(curves[curve], NULL, BCRYPT_ECCPRIVATE_BLOB, &key, buf, sizeof(BCRYPT_ECCKEY_BLOB) + 3 * cb_privkey, BCRYPT_NO_KEY_VALIDATION); if (BCRYPT_SUCCESS(status)) { ULONG cb; status = BCryptExportKey(key, NULL, BCRYPT_ECCPUBLIC_BLOB, buf, sizeof(buf), &cb, 0); if (BCRYPT_SUCCESS(status) && cb_pubkey > 2 * blob->cbKey) { *pubkey = 4; memcpy(pubkey + 1, buf + sizeof(BCRYPT_ECCKEY_BLOB), 2 * blob->cbKey); rc = 1 + 2 * blob->cbKey; } BCryptDestroyKey(key); } } return rc; } int ecdh_generate_keypair(int curve, uint8_t *privkey, size_t cb_privkey, uint8_t *pubkey, size_t cb_pubkey) { int rc = 0; BCRYPT_KEY_HANDLE key; NTSTATUS status = BCryptGenerateKeyPair(curves[curve], &key, lengths[curve], 0); if (BCRYPT_SUCCESS(status)) { status = BCryptFinalizeKeyPair(key, 0); if (BCRYPT_SUCCESS(status)) { uint8_t buf[256]; ULONG cb; status = BCryptExportKey(key, NULL, BCRYPT_ECCPRIVATE_BLOB, buf, sizeof(buf), &cb, 0); BCRYPT_ECCKEY_BLOB *blob = (BCRYPT_ECCKEY_BLOB *) buf; if (BCRYPT_SUCCESS(status) && cb_privkey >= blob->cbKey && cb_pubkey > 2 * blob->cbKey) { *pubkey = 4; memcpy(pubkey + 1, buf + sizeof(BCRYPT_ECCKEY_BLOB), 2 * blob->cbKey); memcpy(privkey, buf + sizeof(BCRYPT_ECCKEY_BLOB) + 2 * blob->cbKey, blob->cbKey); rc = blob->cbKey; } } BCryptDestroyKey(key); } return rc; } int ecdh_calculate_secret(int curve, const uint8_t *privkey, size_t cb_privkey, const uint8_t *pubkey, size_t cb_pubkey, uint8_t *secret, size_t cb_secret) { int rc = 0; uint8_t buf[256]; BCRYPT_ECCKEY_BLOB *blob = (BCRYPT_ECCKEY_BLOB *) buf; blob->dwMagic = BCRYPT_ECDH_PRIVATE_P256_MAGIC; blob->cbKey = cb_privkey; memset(buf + sizeof(BCRYPT_ECCKEY_BLOB), 0, 2 * cb_privkey); memcpy(buf + sizeof(BCRYPT_ECCKEY_BLOB) + 2 * cb_privkey, privkey, cb_privkey); BCRYPT_KEY_HANDLE priv; NTSTATUS status = BCryptImportKeyPair(curves[curve], NULL, BCRYPT_ECCPRIVATE_BLOB, &priv, buf, sizeof(BCRYPT_ECCKEY_BLOB) + 3 * cb_privkey, BCRYPT_NO_KEY_VALIDATION); if (BCRYPT_SUCCESS(status)) { blob->dwMagic = BCRYPT_ECDH_PUBLIC_P256_MAGIC; blob->cbKey = cb_privkey; memcpy(buf + sizeof(BCRYPT_ECCKEY_BLOB), pubkey + 1, cb_pubkey - 1); BCRYPT_KEY_HANDLE pub; status = BCryptImportKeyPair(curves[curve], NULL, BCRYPT_ECCPUBLIC_BLOB, &pub, buf, sizeof(BCRYPT_ECCKEY_BLOB) + 2 * cb_privkey, 0); if (BCRYPT_SUCCESS(status)) { BCRYPT_SECRET_HANDLE sec; status = BCryptSecretAgreement(priv, pub, &sec, 0); if (BCRYPT_SUCCESS(status)) { ULONG cb; status = BCryptDeriveKey(sec, BCRYPT_KDF_RAW_SECRET, NULL, secret, cb_secret, &cb, 0); if (BCRYPT_SUCCESS(status)) { // BCRYPT_KDF_RAW_SECRET returns little-endian so reverse the array for (ULONG c = 0; c < cb / 2; c++) { uint8_t t = secret[c]; secret[c] = secret[cb - c - 1]; secret[cb - c - 1] = t; } rc = cb; } BCryptDestroySecret(sec); } BCryptDestroyKey(pub); } BCryptDestroyKey(priv); } return rc; } #else int ecdh_curve_p256(void) { return NID_X9_62_prime256v1; } int ecdh_calculate_public_key(int curve, const uint8_t *privkey, size_t cb_privkey, uint8_t *pubkey, size_t cb_pubkey) { BN_CTX *ctx = BN_CTX_new(); BIGNUM *order = BN_new(); BIGNUM *pvt = BN_bin2bn(privkey, cb_privkey, NULL); EC_GROUP *group = EC_GROUP_new_by_curve_name(curve); EC_POINT *pub = NULL; size_t cb = 0; if (ctx == NULL || order == NULL || pvt == NULL || group == NULL) { goto err; } EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); if (BN_is_zero(pvt) || !EC_GROUP_get_order(group, order, ctx) || BN_cmp(pvt, order) >= 0) { goto err; } pub = EC_POINT_new(group); if (pub == NULL || !EC_POINT_mul(group, pub, pvt, NULL, NULL, ctx)) { goto err; } cb = EC_POINT_point2oct(group, pub, POINT_CONVERSION_UNCOMPRESSED, pubkey, cb_pubkey, ctx); err: EC_POINT_free(pub); EC_GROUP_free(group); BN_free(pvt); BN_free(order); BN_CTX_free(ctx); return (int) cb; } int ecdh_generate_keypair(int curve, uint8_t *privkey, size_t cb_privkey, uint8_t *pubkey, size_t cb_pubkey) { EC_KEY *key = EC_KEY_new_by_curve_name(curve); if (key == NULL || !EC_KEY_generate_key(key)) { EC_KEY_free(key); return 0; } int len = BN_bn2binpad(EC_KEY_get0_private_key(key), privkey, cb_privkey); if (len <= 0) { EC_KEY_free(key); return 0; } size_t cb = EC_POINT_point2oct(EC_KEY_get0_group(key), EC_KEY_get0_public_key(key), POINT_CONVERSION_UNCOMPRESSED, pubkey, cb_pubkey, NULL); if (cb == 0 || cb > cb_pubkey) { EC_KEY_free(key); return 0; } EC_KEY_free(key); return len; } int ecdh_calculate_secret(int curve, const uint8_t *privkey, size_t cb_privkey, const uint8_t *pubkey, size_t cb_pubkey, uint8_t *secret, size_t cb_secret) { EC_KEY *priv = EC_KEY_new_by_curve_name(curve); EC_KEY *pub = EC_KEY_new_by_curve_name(curve); EC_POINT *point = NULL; int len = 0; if (priv == NULL || pub == NULL || !EC_KEY_set_private_key(priv, BN_bin2bn(privkey, cb_privkey, NULL))) { goto err; } point = EC_POINT_new(EC_KEY_get0_group(pub)); if (point == NULL || !EC_POINT_oct2point(EC_KEY_get0_group(pub), point, pubkey, cb_pubkey, NULL)) { goto err; } if (!EC_KEY_set_public_key(pub, point) || !EC_KEY_check_key(pub)) { goto err; } len = ECDH_compute_key(secret, cb_secret, EC_KEY_get0_public_key(pub), priv, NULL); err: EC_POINT_free(point); EC_KEY_free(pub); EC_KEY_free(priv); return len > 0 ? len : 0; } #endif yubico-piv-tool-2.7.1/lib/internal.h0000664000175000017500000002144414731066755016706 0ustar winniewinnie/* * Copyright (c) 2014-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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef YKPIV_INTERNAL_H #define YKPIV_INTERNAL_H #include "ykpiv-config.h" #include "ykpiv.h" #include #ifdef BACKEND_PCSC #ifdef HAVE_PCSC_WINSCARD_H # include # include #else # include #endif #endif // Typedef DWORD (defined by pcsc lib) to pcsc_word to make it clear that this // is not the Windows meaning of DWORD, but the PCSC library's meaning. This // differs: Windows defines a DWORD as 32-bits, but pcsclite defines it as // 'unsigned long' on x86_64 Linux, which is often 64-bits. typedef DWORD pcsc_word; typedef LONG pcsc_long; #ifdef __cplusplus extern "C" { #endif #define DES_LEN_DES 8 #define DES_LEN_3DES DES_LEN_DES*3 #define READER_LEN 32 #define MAX_READERS 16 // the object size is restricted to the firmware's message buffer size, which // always contains 0x5C + 1 byte len + 3 byte id + 0x53 + 3 byte len = 9 bytes, // so while the message buffer == CB_BUF_MAX, the maximum object we can store // is CB_BUF_MAX - 9 #define CB_OBJ_MAX_NEO (CB_BUF_MAX_NEO - 9) #define CB_OBJ_MAX_YK4 (CB_BUF_MAX_YK4 - 9) #define CB_OBJ_MAX CB_OBJ_MAX_YK4 #define CB_BUF_MAX_NEO 2048 #define CB_BUF_MAX_YK4 3072 #define CB_BUF_MAX CB_BUF_MAX_YK4 #define CB_ATR_MAX 33 #define CHREF_ACT_CHANGE_PIN 0 #define CHREF_ACT_UNBLOCK_PIN 1 #define CHREF_ACT_CHANGE_PUK 2 #define TAG_ADMIN 0x80 #define TAG_ADMIN_FLAGS_1 0x81 #define TAG_ADMIN_SALT 0x82 #define TAG_ADMIN_TIMESTAMP 0x83 #define TAG_PROTECTED 0x88 #define TAG_PROTECTED_FLAGS_1 0x81 #define TAG_PROTECTED_MGM 0x89 #define TAG_MSCMAP 0x81 #define TAG_MSROOTS_END 0x82 #define TAG_MSROOTS_MID 0x83 #define TAG_RSA_MODULUS 0x81 #define TAG_RSA_EXP 0x82 #define TAG_ECC_POINT 0x86 #define CB_ECC_POINTP256 65 #define CB_ECC_POINTP384 97 #define CB_ECC_POINT25519 32 #define YKPIV_OBJ_ADMIN_DATA 0x5fff00 #define YKPIV_OBJ_ATTESTATION 0x5fff01 #define YKPIV_OBJ_MSCMAP 0x5fff10 #define YKPIV_OBJ_MSROOTS1 0x5fff11 #define YKPIV_OBJ_MSROOTS2 0x5fff12 #define YKPIV_OBJ_MSROOTS3 0x5fff13 #define YKPIV_OBJ_MSROOTS4 0x5fff14 #define YKPIV_OBJ_MSROOTS5 0x5fff15 #define ADMIN_FLAGS_1_PUK_BLOCKED 0x01 #define ADMIN_FLAGS_1_PROTECTED_MGM 0x02 #define CB_ADMIN_SALT 16 #define CB_ADMIN_TIMESTAMP 4 #define ITER_MGM_PBKDF2 10000 #define PROTECTED_FLAGS_1_PUK_NOBLOCK 0x01 #define CB_OBJ_TAG_MIN 2 // 1 byte tag + 1 byte len #define CB_OBJ_TAG_MAX (CB_OBJ_TAG_MIN + 2) // 1 byte tag + 3 bytes len #define CB_PIN_MAX 8 #define SCP11_SESSION_KEY_LEN 16 #define SCP11_MAC_LEN 16 #define SCP11_HALF_MAC_LEN 8 #define SCP11B_KID 0x13 #define SCP11B_KVN 0x1 #define SCP11_KEY_USAGE 0x3c #define SCP11_KEY_TYPE 0x88 #define SCP11_CERTIFICATE_STORE_TAG 0xBF21 #define SCP11_ePK_SD_ECKA_TAG 0x5F49 typedef enum { PKCS5_OK = 0, PKCS5_GENERAL_ERROR = -1 } pkcs5_rc; typedef enum { PRNG_OK = 0, PRNG_GENERAL_ERROR = -1 } prng_rc; typedef struct _ykpiv_version_t { uint8_t major; uint8_t minor; uint8_t patch; } ykpiv_version_t; typedef struct _ykpiv_scp11_state { uint8_t security_level; uint32_t enc_counter; uint8_t senc[SCP11_SESSION_KEY_LEN]; uint8_t smac[SCP11_SESSION_KEY_LEN]; uint8_t srmac[SCP11_SESSION_KEY_LEN]; uint8_t mac_chain[SCP11_MAC_LEN]; } ykpiv_scp11_state; struct ykpiv_state { SCARDCONTEXT context; SCARDHANDLE card; pcsc_word protocol; char reader[2048]; int tries; char *pin; uint8_t *mgm_key; uint32_t mgm_len; ykpiv_allocator allocator; uint32_t model; ykpiv_version_t ver; uint32_t serial; ykpiv_scp11_state scp11_state; }; union u_APDU { struct { unsigned char cla; unsigned char ins; unsigned char p1; unsigned char p2; unsigned char lc; unsigned char data[YKPIV_OBJ_MAX_SIZE - 5]; // Max message bytes - first bytes in apdu - Le } st; unsigned char raw[YKPIV_OBJ_MAX_SIZE]; // Max message size the yubikey can receive }; typedef union u_APDU APDU; pkcs5_rc pkcs5_pbkdf2_sha1(const uint8_t* password, const size_t cb_password, const uint8_t* salt, const size_t cb_salt, uint64_t iterations, const uint8_t* key, const size_t cb_key); bool yk_des_is_weak_key(const unsigned char *key, const size_t cb_key); prng_rc _ykpiv_prng_generate(unsigned char *buffer, const size_t cb_req); ykpiv_rc _ykpiv_begin_transaction(ykpiv_state *state); ykpiv_rc _ykpiv_end_transaction(ykpiv_state *state); ykpiv_rc _ykpiv_ensure_application_selected(ykpiv_state *state, bool scp11); ykpiv_rc _ykpiv_select_application(ykpiv_state *state, bool scp11); size_t _ykpiv_get_length_size(size_t length); size_t _ykpiv_set_length(unsigned char *buffer, size_t length); size_t _ykpiv_get_length(const unsigned char *buffer, const unsigned char* end, size_t *len); void* _ykpiv_alloc(ykpiv_state *state, size_t size); void* _ykpiv_realloc(ykpiv_state *state, void *address, size_t size); void _ykpiv_free(ykpiv_state *state, void *data); ykpiv_rc _ykpiv_save_object(ykpiv_state *state, int object_id, unsigned char *indata, size_t len); ykpiv_rc _ykpiv_fetch_object(ykpiv_state *state, int object_id, unsigned char *data, unsigned long *len); ykpiv_rc _ykpiv_send_apdu(ykpiv_state *state, APDU *apdu, unsigned char *data, unsigned long *recv_len, int *sw); ykpiv_rc _ykpiv_transfer_data( ykpiv_state *state, const unsigned char *templ, const unsigned char *in_data, unsigned long in_len, unsigned char *out_data, unsigned long *out_len, int *sw); /* authentication functions not ready for public api */ ykpiv_rc ykpiv_auth_getchallenge(ykpiv_state *state, ykpiv_metadata *metadata, uint8_t *challenge, unsigned long *challenge_len); ykpiv_rc ykpiv_auth_verifyresponse(ykpiv_state *state, ykpiv_metadata *metadata, uint8_t *response, unsigned long response_len); ykpiv_rc ykpiv_auth_deauthenticate(ykpiv_state *state); ykpiv_rc ykpiv_auth_get_verified(ykpiv_state *state); ykpiv_rc ykpiv_auth_verify(ykpiv_state* state, uint8_t* pin, size_t* p_pin_len, int *tries, bool force_select, bool bio, bool verify_spin); typedef enum _setting_source_t { SETTING_SOURCE_USER, SETTING_SOURCE_ADMIN, SETTING_SOURCE_DEFAULT } setting_source_t; typedef struct _setting_bool_t { bool value; setting_source_t source; } setting_bool_t; setting_bool_t setting_get_bool(const char *sz_setting, bool f_default); typedef enum _yc_log_level_t { YC_LOG_LEVEL_ERROR, YC_LOG_LEVEL_WARN, YC_LOG_LEVEL_INFO, YC_LOG_LEVEL_VERBOSE, YC_LOG_LEVEL_DEBUG } yc_log_level_t; void yc_log_event(const char *sz_source, uint32_t id, yc_log_level_t level, const char *sz_format, ...); void _ykpiv_set_debug(void (*dbg)(const char *)); void _ykpiv_debug(const char *file, int line, const char *func, int lvl, const char *fmt, ...); #define DBG(fmt, ...) _ykpiv_debug(__FILE__, __LINE__, __FUNCTION__, 1, fmt, ##__VA_ARGS__) #define DBG2(fmt, ...) _ykpiv_debug(__FILE__, __LINE__, __FUNCTION__, 2, fmt, ##__VA_ARGS__) #define DBG3(fmt, ...) _ykpiv_debug(__FILE__, __LINE__, __FUNCTION__, 3, fmt, ##__VA_ARGS__) #ifdef _WIN32 #include #define yc_memzero SecureZeroMemory #elif defined(HAVE_EXPLICIT_BZERO) #include #define yc_memzero explicit_bzero #elif defined(__linux__) #include #define yc_memzero OPENSSL_cleanse #else #define __STDC_WANT_LIB_EXT1__ 1 #include #define yc_memzero(_p, _n) (void)memset_s(_p, (rsize_t)_n, 0, (rsize_t)_n) #endif #ifdef __cplusplus } #endif #endif yubico-piv-tool-2.7.1/lib/ykpiv.pc0000664000175000017500000000316514731067061016376 0ustar winniewinnie# Copyright (c) 2014-2016 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: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * 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 # 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. prefix=/usr/local exec_prefix=/usr/local libdir=/usr/local/lib includedir=/usr/local/include Name: yubico-piv-tool Description: Yubico PIV C Library URL: https://www.yubico.com/ Version: 2.7.1 Requires.private: libcrypto Libs: -L${libdir} -lykpiv Cflags: -I${includedir}/ykpiv yubico-piv-tool-2.7.1/lib/util.c0000664000175000017500000015553614731066755016054 0ustar winniewinnie /* * Copyright (c) 2014-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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include #include #include #include #include #ifdef USE_CERT_COMPRESS #include #endif #include "internal.h" #include "ykpiv.h" #define MAX(a,b) (a) > (b) ? (a) : (b) #define MIN(a,b) (a) < (b) ? (a) : (b) /* * Format defined in SP-800-73-4, Appendix A, Table 9 * * FASC-N containing S9999F9999F999999F0F1F0000000000300001E encoded in * 4-bit BCD with 1 bit parity. run through the tools/fasc.pl script to get * bytes. This CHUID has an expiry of 2030-01-01. * * Defined fields: * - 0x30: FASC-N (hard-coded) * - 0x34: Card UUID / GUID (settable) * - 0x35: Exp. Date (hard-coded) * - 0x3e: Signature (hard-coded, empty) * - 0xfe: Error Detection Code (hard-coded) */ const uint8_t CHUID_TMPL[] = { 0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58, 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x08, 0x32, 0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00, }; #define CHUID_GUID_OFFS 29 #define TAG_CHUID_UUID 0x34 // f0: Card Identifier // - 0xa000000116 == GSC-IS RID // - 0xff == Manufacturer ID (dummy) // - 0x02 == Card type (javaCard) // - next 14 bytes: card ID const uint8_t CCC_TMPL[] = { 0xf0, 0x15, 0xa0, 0x00, 0x00, 0x01, 0x16, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x01, 0x21, 0xf2, 0x01, 0x21, 0xf3, 0x00, 0xf4, 0x01, 0x00, 0xf5, 0x01, 0x10, 0xf6, 0x00, 0xf7, 0x00, 0xfa, 0x00, 0xfb, 0x00, 0xfc, 0x00, 0xfd, 0x00, 0xfe, 0x00 }; #define CCC_ID_OFFS 9 static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf, size_t *buf_len); static ykpiv_rc _write_certificate(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len, uint8_t certinfo); static ykpiv_rc _read_metadata(ykpiv_state *state, uint8_t tag, uint8_t* data, size_t* pcb_data); static ykpiv_rc _write_metadata(ykpiv_state *state, uint8_t tag, uint8_t *data, size_t cb_data); static ykpiv_rc _get_metadata_item(uint8_t *data, size_t cb_data, uint8_t tag, uint8_t **pp_item, size_t *pcb_item); static ykpiv_rc _set_metadata_item(uint8_t *data, size_t *pcb_data, size_t cb_data_max, uint8_t tag, uint8_t *p_item, size_t cb_item); static size_t _obj_size_max(ykpiv_state *state) { return (state && state->model == DEVTYPE_NEOr3) ? CB_OBJ_MAX_NEO : CB_OBJ_MAX; } static unsigned long get_length_size(unsigned long length) { if (length < 0x80) { return 1; } else if (length < 0x100) { return 2; } else { return 3; } } /* ** YKPIV Utility API - aggregate functions and slightly nicer interface */ ykpiv_rc ykpiv_util_get_cardid(ykpiv_state *state, ykpiv_cardid *cardid) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX] = {0}; unsigned long len = sizeof(buf); uint8_t *p_temp = NULL; size_t offs, cb_temp = 0; uint8_t tag = 0; if (!cardid) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; if ((res = _ykpiv_fetch_object(state, YKPIV_OBJ_CHUID, buf, &len)) == YKPIV_OK) { p_temp = buf; while (p_temp < (buf + len)) { tag = *p_temp++; offs = _ykpiv_get_length(p_temp, buf + len, &cb_temp); if (!offs) { res = YKPIV_PARSE_ERROR; goto Cleanup; } p_temp += offs; if (tag == TAG_CHUID_UUID) { /* found card uuid */ if (cb_temp < YKPIV_CARDID_SIZE || p_temp + YKPIV_CARDID_SIZE > buf + len) { res = YKPIV_SIZE_ERROR; goto Cleanup; } res = YKPIV_OK; memcpy(cardid->data, p_temp, YKPIV_CARDID_SIZE); goto Cleanup; } p_temp += cb_temp; } /* not found, not malformed */ res = YKPIV_GENERIC_ERROR; } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_set_cardid(ykpiv_state *state, const ykpiv_cardid *cardid) { ykpiv_rc res = YKPIV_OK; uint8_t id[YKPIV_CARDID_SIZE] = {0}; uint8_t buf[sizeof(CHUID_TMPL)] = {0}; size_t len = 0; if (!state) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (!cardid) { if (PRNG_OK != _ykpiv_prng_generate(id, sizeof(id))) { return YKPIV_RANDOMNESS_ERROR; } } else { memcpy(id, cardid->data, sizeof(id)); } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; memcpy(buf, CHUID_TMPL, sizeof(CHUID_TMPL)); memcpy(buf + CHUID_GUID_OFFS, id, sizeof(id)); len = sizeof(CHUID_TMPL); res = _ykpiv_save_object(state, YKPIV_OBJ_CHUID, buf, len); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_get_cccid(ykpiv_state *state, ykpiv_cccid *ccc) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX] = {0}; unsigned long len = sizeof(buf); if (!ccc) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_fetch_object(state, YKPIV_OBJ_CAPABILITY, buf, &len); if (YKPIV_OK == res) { if (len != sizeof(CCC_TMPL)) { res = YKPIV_GENERIC_ERROR; } else { memcpy(ccc->data, buf + CCC_ID_OFFS, YKPIV_CCCID_SIZE); } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_set_cccid(ykpiv_state *state, const ykpiv_cccid *ccc) { ykpiv_rc res = YKPIV_OK; uint8_t id[YKPIV_CCCID_SIZE] = {0}; uint8_t buf[sizeof(CCC_TMPL)] = {0}; size_t len = 0; if (!state) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (!ccc) { if (PRNG_OK != _ykpiv_prng_generate(id, sizeof(id))) { return YKPIV_RANDOMNESS_ERROR; } } else { memcpy(id, ccc->data, sizeof(id)); } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; len = sizeof(CCC_TMPL); memcpy(buf, CCC_TMPL, len); memcpy(buf + CCC_ID_OFFS, id, YKPIV_CCCID_SIZE); res = _ykpiv_save_object(state, YKPIV_OBJ_CAPABILITY, buf, len); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_devmodel ykpiv_util_devicemodel(ykpiv_state *state) { if (!state || !state->context || (state->context == (SCARDCONTEXT)-1)) { return DEVTYPE_UNKNOWN; } return state->model; } ykpiv_rc ykpiv_util_list_keys(ykpiv_state *state, uint8_t *key_count, ykpiv_key **data, size_t *data_len) { ykpiv_rc res = YKPIV_OK; ykpiv_key *pKey = NULL; uint8_t *pData = NULL; uint8_t *pTemp = NULL; size_t cbData = 0; size_t offset = 0; uint8_t buf[CB_BUF_MAX] = {0}; size_t cbBuf = 0; size_t i = 0; size_t cbRealloc = 0; const size_t CB_PAGE = 4096; const uint8_t SLOTS[] = { YKPIV_KEY_AUTHENTICATION, YKPIV_KEY_SIGNATURE, YKPIV_KEY_KEYMGM, YKPIV_KEY_RETIRED1, YKPIV_KEY_RETIRED2, YKPIV_KEY_RETIRED3, YKPIV_KEY_RETIRED4, YKPIV_KEY_RETIRED5, YKPIV_KEY_RETIRED6, YKPIV_KEY_RETIRED7, YKPIV_KEY_RETIRED8, YKPIV_KEY_RETIRED9, YKPIV_KEY_RETIRED10, YKPIV_KEY_RETIRED11, YKPIV_KEY_RETIRED12, YKPIV_KEY_RETIRED13, YKPIV_KEY_RETIRED14, YKPIV_KEY_RETIRED15, YKPIV_KEY_RETIRED16, YKPIV_KEY_RETIRED17, YKPIV_KEY_RETIRED18, YKPIV_KEY_RETIRED19, YKPIV_KEY_RETIRED20, YKPIV_KEY_CARDAUTH }; if ((NULL == data) || (NULL == data_len) || (NULL == key_count)) { return YKPIV_ARGUMENT_ERROR; } uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; // init return parameters *key_count = 0; *data = NULL; *data_len = 0; // allocate initial page of buffer if (NULL == (pData = _ykpiv_alloc(state, CB_PAGE))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } cbData = CB_PAGE; for (i = 0; i < sizeof(SLOTS); i++) { cbBuf = sizeof(buf); res = _read_certificate(state, SLOTS[i], buf, &cbBuf); if ((res == YKPIV_OK) && (cbBuf > 0)) { // add current slot to result, grow result buffer if necessary cbRealloc = (sizeof(ykpiv_key) + cbBuf - 1) > (cbData - offset) ? MAX((sizeof(ykpiv_key) + cbBuf - 1) - (cbData - offset), CB_PAGE) : 0; if (0 != cbRealloc) { if (!(pTemp = _ykpiv_realloc(state, pData, cbData + cbRealloc))) { /* realloc failed, pData will be freed in cleanup */ res = YKPIV_MEMORY_ERROR; goto Cleanup; } yc_memzero(pTemp + cbData, cbRealloc); // clear newly allocated memory pData = pTemp; pTemp = NULL; } cbData += cbRealloc; // If ykpiv_key is misaligned or results in padding, this causes problems // in the array we return. If this becomes a problem, we'll probably want // to go with a flat byte array. pKey = (ykpiv_key*)(pData + offset); pKey->slot = SLOTS[i]; pKey->cert_len = (uint16_t)cbBuf; memcpy(pKey->cert, buf, cbBuf); offset += sizeof(ykpiv_key) + cbBuf - 1; (*key_count)++; } } *data = (ykpiv_key*)pData; pData = NULL; if (data_len) { *data_len = offset; } res = YKPIV_OK; Cleanup: if (pData) { _ykpiv_free(state, pData); } _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_free(ykpiv_state *state, void *data) { if (!data) return YKPIV_OK; if (!state || (!(state->allocator.pfn_free))) return YKPIV_ARGUMENT_ERROR; _ykpiv_free(state, data); return YKPIV_OK; } ykpiv_rc ykpiv_util_read_cert(ykpiv_state *state, uint8_t slot, uint8_t **data, size_t *data_len) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_BUF_MAX] = {0}; size_t cbBuf = sizeof(buf); if ((NULL == data )|| (NULL == data_len)) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; *data = 0; *data_len = 0; if (YKPIV_OK == (res = _read_certificate(state, slot, buf, &cbBuf))) { /* handle those who write empty certificate blobs to PIV objects */ if (cbBuf == 0) { *data = NULL; *data_len = 0; goto Cleanup; } if (!(*data = _ykpiv_alloc(state, cbBuf))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(*data, buf, cbBuf); *data_len = cbBuf; } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_write_cert(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len, uint8_t certinfo) { ykpiv_rc res = YKPIV_OK; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _write_certificate(state, slot, data, data_len, certinfo); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_delete_cert(ykpiv_state *state, uint8_t slot) { return ykpiv_util_write_cert(state, slot, NULL, 0, YKPIV_CERTINFO_UNCOMPRESSED); } ykpiv_rc ykpiv_util_block_puk(ykpiv_state *state) { ykpiv_rc res = YKPIV_OK; uint8_t puk[] = { 0x30, 0x42, 0x41, 0x44, 0x46, 0x30, 0x30, 0x44 }; int tries = -1; uint8_t data[CB_BUF_MAX] = {0}; size_t cb_data = sizeof(data); uint8_t *p_item = NULL; size_t cb_item = 0; uint8_t flags = 0; if (!state) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; while (tries != 0) { if (YKPIV_OK == (res = ykpiv_change_puk(state, (const char*)puk, sizeof(puk), (const char*)puk, sizeof(puk), &tries))) { /* did we accidentally choose the correct PUK?, change our puk and try again */ puk[0]++; } else { /* depending on the firmware, tries may not be set to zero when the PUK is blocked, */ /* instead, the return code will be PIN_LOCKED and tries will be unset */ if (YKPIV_PIN_LOCKED == res) { tries = 0; res = YKPIV_OK; } } } /* attempt to set the puk blocked flag in admin data */ if (YKPIV_OK == _read_metadata(state, TAG_ADMIN, data, &cb_data)) { if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_FLAGS_1, &p_item, &cb_item)) { if (sizeof(flags) == cb_item) { memcpy(&flags, p_item, cb_item); } else { DBG("admin flags exist, but are incorrect size = %lu", (unsigned long)cb_item); } } } flags |= ADMIN_FLAGS_1_PUK_BLOCKED; if (YKPIV_OK != _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_FLAGS_1, (uint8_t*)&flags, sizeof(flags))) { DBG("could not set admin flags"); } else { if (YKPIV_OK != _write_metadata(state, TAG_ADMIN, data, cb_data)) { DBG("could not write admin metadata"); } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_read_mscmap(ykpiv_state *state, ykpiv_container **containers, size_t *n_containers) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_BUF_MAX] = {0}; unsigned long cbBuf = sizeof(buf); size_t offs, len = 0; uint8_t *ptr = NULL; if ((NULL == containers) || (NULL == n_containers)) { res = YKPIV_ARGUMENT_ERROR; goto Cleanup; } uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; *containers = 0; *n_containers = 0; if (YKPIV_OK == (res = _ykpiv_fetch_object(state, YKPIV_OBJ_MSCMAP, buf, &cbBuf))) { ptr = buf; /* check that object contents are at least large enough to read the header */ if (cbBuf < CB_OBJ_TAG_MIN) { res = YKPIV_OK; goto Cleanup; } if (*ptr++ == TAG_MSCMAP) { offs = _ykpiv_get_length(ptr, buf + cbBuf, &len); if(!offs) { res = YKPIV_OK; goto Cleanup; } ptr += offs; if (NULL == (*containers = _ykpiv_alloc(state, len))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } /* should check if container map isn't corrupt */ memcpy(*containers, ptr, len); *n_containers = len / sizeof(ykpiv_container); } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_write_mscmap(ykpiv_state *state, ykpiv_container *containers, size_t n_containers) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX] = {0}; size_t offset = 0; size_t req_len = 0; size_t data_len = n_containers * sizeof(ykpiv_container); uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; // check if data and data_len are zero, this means that // we intend to delete the object if ((NULL == containers) || (0 == n_containers)) { // if either containers or n_containers are non-zero, return an error, // that we only delete strictly when both are set properly if ((NULL != containers) || (0 != n_containers)) { res = YKPIV_ARGUMENT_ERROR; } else { res = _ykpiv_save_object(state, YKPIV_OBJ_MSCMAP, NULL, 0); } goto Cleanup; } // encode object data for storage // calculate the required length of the encoded object req_len = 1 /* data tag */ + (unsigned long)_ykpiv_set_length(buf, data_len) + data_len; if (req_len > _obj_size_max(state)) { res = YKPIV_SIZE_ERROR; goto Cleanup; } buf[offset++] = TAG_MSCMAP; offset += _ykpiv_set_length(buf + offset, data_len); memcpy(buf + offset, (uint8_t*)containers, data_len); offset += data_len; // write onto device res = _ykpiv_save_object(state, YKPIV_OBJ_MSCMAP, buf, offset); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_read_msroots(ykpiv_state *state, uint8_t **data, size_t *data_len) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_BUF_MAX] = {0}; unsigned long cbBuf = sizeof(buf); size_t offs, len = 0; uint8_t *ptr = NULL; int object_id = 0; uint8_t tag = 0; uint8_t *pData = NULL; uint8_t *pTemp = NULL; size_t cbData = 0; size_t cbRealloc = 0; size_t offset = 0; if (!data || !data_len) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; *data = 0; *data_len = 0; // allocate first page cbData = _obj_size_max(state); if (NULL == (pData = _ykpiv_alloc(state, cbData))) { res = YKPIV_MEMORY_ERROR; goto Cleanup; } for (object_id = YKPIV_OBJ_MSROOTS1; object_id <= YKPIV_OBJ_MSROOTS5; object_id++) { cbBuf = sizeof(buf); if (YKPIV_OK != (res = _ykpiv_fetch_object(state, object_id, buf, &cbBuf))) { goto Cleanup; } ptr = buf; if (cbBuf < CB_OBJ_TAG_MIN) { res = YKPIV_OK; goto Cleanup; } tag = *ptr++; if (((TAG_MSROOTS_MID != tag) && (TAG_MSROOTS_END != tag)) || ((YKPIV_OBJ_MSROOTS5 == object_id) && (TAG_MSROOTS_END != tag))) { // the current object doesn't contain a valid part of a msroots file res = YKPIV_OK; // treat condition as object isn't found goto Cleanup; } offs = _ykpiv_get_length(ptr, buf + cbBuf, &len); if(!offs) { res = YKPIV_OK; goto Cleanup; } ptr += offs; cbRealloc = len > (cbData - offset) ? len - (cbData - offset) : 0; if (0 != cbRealloc) { if (!(pTemp = _ykpiv_realloc(state, pData, cbData + cbRealloc))) { /* realloc failed, pData will be freed in cleanup */ res = YKPIV_MEMORY_ERROR; goto Cleanup; } pData = pTemp; pTemp = NULL; } cbData += cbRealloc; memcpy(pData + offset, ptr, len); offset += len; if (TAG_MSROOTS_END == tag) { break; } } // return data *data = pData; pData = NULL; *data_len = offset; res = YKPIV_OK; Cleanup: if (pData) { _ykpiv_free(state, pData); } _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_write_msroots(ykpiv_state *state, uint8_t *data, size_t data_len) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX] = {0}; size_t offset = 0; size_t data_offset = 0; size_t data_chunk = 0; size_t n_objs = 0; unsigned int i = 0; size_t cb_obj_max = _obj_size_max(state); uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; // check if either data and data_len are zero, this means that // we intend to delete the object if ((NULL == data) || (0 == data_len)) { // if either data or data_len are non-zero, return an error, // that we only delete strictly when both are set properly if ((NULL != data) || (0 != data_len)) { res = YKPIV_ARGUMENT_ERROR; } else { // it should be sufficient to just delete the first object, though // to be complete we should erase all of the MSROOTS objects res = _ykpiv_save_object(state, YKPIV_OBJ_MSROOTS1, NULL, 0); } goto Cleanup; } // calculate number of objects required to store blob n_objs = (data_len / (cb_obj_max - CB_OBJ_TAG_MAX)) + 1; // we're allowing 5 objects to be used to span the msroots file if (n_objs > 5) { res = YKPIV_SIZE_ERROR; goto Cleanup; } for (i = 0; i < n_objs; i++) { offset = 0; data_chunk = MIN(cb_obj_max - CB_OBJ_TAG_MAX, data_len - data_offset); /* encode object data for storage */ buf[offset++] = (i == (n_objs - 1)) ? TAG_MSROOTS_END : TAG_MSROOTS_MID; offset += _ykpiv_set_length(buf + offset, data_chunk); memcpy(buf + offset, data + data_offset, data_chunk); offset += data_chunk; /* write onto device */ res = _ykpiv_save_object(state, (int)(YKPIV_OBJ_MSROOTS1 + i), buf, offset); if (YKPIV_OK != res) { goto Cleanup; } data_offset += data_chunk; } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_generate_key(ykpiv_state *state, uint8_t slot, uint8_t algorithm, uint8_t pin_policy, uint8_t touch_policy, uint8_t **modulus, size_t *modulus_len, uint8_t **exp, size_t *exp_len, uint8_t **point, size_t *point_len) { ykpiv_rc res = YKPIV_OK; unsigned char in_data[11] = {0}; unsigned char *in_ptr = in_data; unsigned char data[1024] = {0}; unsigned char templ[] = { 0, YKPIV_INS_GENERATE_ASYMMETRIC, 0, 0 }; unsigned long recv_len = sizeof(data); int sw = 0; uint8_t *ptr_modulus = NULL; size_t cb_modulus = 0; uint8_t *ptr_exp = NULL; size_t cb_exp = 0; uint8_t *ptr_point = NULL; size_t cb_point = 0; size_t offs; setting_bool_t setting_roca = { 0 }; const char sz_setting_roca[] = "Enable_Unsafe_Keygen_ROCA"; const char sz_roca_format[] = "YubiKey serial number %u is affected by vulnerability " "CVE-2017-15361 (ROCA) and should be replaced. On-chip key generation %s " "See YSA-2017-01 " "for additional information on device replacement and mitigation assistance.\n"; const char sz_roca_allow_user[] = "was permitted by an end-user configuration setting, but is not recommended."; const char sz_roca_allow_admin[] = "was permitted by an administrator configuration setting, but is not recommended."; const char sz_roca_block_user[] = "was blocked due to an end-user configuration setting."; const char sz_roca_block_admin[] = "was blocked due to an administrator configuration setting."; const char sz_roca_default[] = "was permitted by default, but is not recommended. " "The default behavior will change in a future Yubico release."; if (!state) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if ((algorithm == YKPIV_ALGO_RSA3072 || algorithm == YKPIV_ALGO_RSA4096 || YKPIV_IS_25519(algorithm)) && !is_version_compatible(state, 5, 7, 0)) { DBG("RSA3072, RSA4096, ED25519 and X25519 keys are only supported in YubiKey version 5.7.0 and newer"); return YKPIV_NOT_SUPPORTED; } if ((algorithm == YKPIV_ALGO_RSA1024 || algorithm == YKPIV_ALGO_RSA2048) && !is_version_compatible(state, 4, 3, 5)) { const char *psz_msg = NULL; setting_roca = setting_get_bool(sz_setting_roca, true); switch (setting_roca.source) { case SETTING_SOURCE_ADMIN: psz_msg = setting_roca.value ? sz_roca_allow_admin : sz_roca_block_admin; break; case SETTING_SOURCE_USER: psz_msg = setting_roca.value ? sz_roca_allow_user : sz_roca_block_user; break; default: case SETTING_SOURCE_DEFAULT: psz_msg = sz_roca_default; break; } DBG(sz_roca_format, state->serial, psz_msg); yc_log_event("YubiKey PIV Library", 1, setting_roca.value ? YC_LOG_LEVEL_WARN : YC_LOG_LEVEL_ERROR, sz_roca_format, state->serial, psz_msg); if (!setting_roca.value) { return YKPIV_NOT_SUPPORTED; } } switch (algorithm) { case YKPIV_ALGO_RSA1024: case YKPIV_ALGO_RSA2048: case YKPIV_ALGO_RSA3072: case YKPIV_ALGO_RSA4096: if (!modulus || !modulus_len || !exp || !exp_len) { DBG("Invalid output parameter for RSA algorithm"); return YKPIV_ARGUMENT_ERROR; } *modulus = NULL; *modulus_len = 0; *exp = NULL; *exp_len = 0; break; case YKPIV_ALGO_ECCP256: case YKPIV_ALGO_ECCP384: case YKPIV_ALGO_ED25519: case YKPIV_ALGO_X25519: if (!point || !point_len) { DBG("Invalid output parameter for ECC algorithm"); return YKPIV_ARGUMENT_ERROR; } *point = NULL; *point_len = 0; break; default: DBG("Invalid algorithm specified"); return YKPIV_GENERIC_ERROR; } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; templ[3] = slot; *in_ptr++ = 0xac; *in_ptr++ = 3; *in_ptr++ = YKPIV_ALGO_TAG; *in_ptr++ = 1; *in_ptr++ = algorithm; if (in_data[4] == 0) { res = YKPIV_ALGORITHM_ERROR; DBG("Unexpected algorithm"); goto Cleanup; } if (pin_policy != YKPIV_PINPOLICY_DEFAULT) { in_data[1] += 3; *in_ptr++ = YKPIV_PINPOLICY_TAG; *in_ptr++ = 1; *in_ptr++ = pin_policy; } if (touch_policy != YKPIV_TOUCHPOLICY_DEFAULT) { in_data[1] += 3; *in_ptr++ = YKPIV_TOUCHPOLICY_TAG; *in_ptr++ = 1; *in_ptr++ = touch_policy; } if (YKPIV_OK != (res = _ykpiv_transfer_data(state, templ, in_data, (unsigned long)(in_ptr - in_data), data, &recv_len, &sw))) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { DBG("Failed to generate new key"); goto Cleanup; } if (YKPIV_IS_RSA(algorithm)) { size_t len; unsigned char *data_ptr = data + 2 + _ykpiv_get_length(data + 2, data + recv_len, &len); if (*data_ptr != TAG_RSA_MODULUS) { DBG("Failed to parse public key structure (modulus)."); res = YKPIV_PARSE_ERROR; goto Cleanup; } data_ptr++; offs = _ykpiv_get_length(data_ptr, data + recv_len, &len); if(!offs) { DBG("Failed to parse public key structure (modulus length)."); res = YKPIV_PARSE_ERROR; goto Cleanup; } data_ptr += offs; cb_modulus = len; if (NULL == (ptr_modulus = _ykpiv_alloc(state, cb_modulus))) { DBG("Failed to allocate memory for modulus."); res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(ptr_modulus, data_ptr, cb_modulus); data_ptr += len; if (*data_ptr != TAG_RSA_EXP) { DBG("Failed to parse public key structure (public exponent)."); res = YKPIV_PARSE_ERROR; goto Cleanup; } data_ptr++; offs = _ykpiv_get_length(data_ptr, data + recv_len, &len); if(!offs) { DBG("Failed to parse public key structure (public exponent length)."); res = YKPIV_PARSE_ERROR; goto Cleanup; } data_ptr += offs; cb_exp = len; if (NULL == (ptr_exp = _ykpiv_alloc(state, cb_exp))) { DBG("Failed to allocate memory for public exponent."); res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(ptr_exp, data_ptr, cb_exp); // set output parameters *modulus = ptr_modulus; ptr_modulus = NULL; *modulus_len = cb_modulus; *exp = ptr_exp; ptr_exp = NULL; *exp_len = cb_exp; } else if (YKPIV_IS_EC(algorithm) || YKPIV_IS_25519(algorithm)) { unsigned char *data_ptr = data + 3; size_t len = 0; if (YKPIV_ALGO_ECCP256 == algorithm) { len = CB_ECC_POINTP256; } else if (YKPIV_ALGO_ECCP384 == algorithm) { len = CB_ECC_POINTP384; } else if (YKPIV_IS_25519(algorithm)) { len = CB_ECC_POINT25519; } if (*data_ptr++ != TAG_ECC_POINT) { DBG("Failed to parse public key structure."); res = YKPIV_PARSE_ERROR; goto Cleanup; } if (*data_ptr++ != len) { /* the curve point should always be determined by the curve */ DBG("Unexpected length."); res = YKPIV_ALGORITHM_ERROR; goto Cleanup; } cb_point = len; if (NULL == (ptr_point = _ykpiv_alloc(state, cb_point))) { DBG("Failed to allocate memory for public point."); res = YKPIV_MEMORY_ERROR; goto Cleanup; } memcpy(ptr_point, data_ptr, cb_point); // set output parameters *point = ptr_point; ptr_point = NULL; *point_len = cb_point; } else { DBG("Wrong algorithm."); res = YKPIV_ALGORITHM_ERROR; goto Cleanup; } Cleanup: if (ptr_modulus) { _ykpiv_free(state, ptr_modulus); } if (ptr_exp) { _ykpiv_free(state, ptr_exp); } if (ptr_point) { _ykpiv_free(state, ptr_point); } _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_get_config(ykpiv_state *state, ykpiv_config *config) { ykpiv_rc res = YKPIV_OK; uint8_t data[CB_BUF_MAX] = { 0 }; size_t cb_data = sizeof(data); uint8_t *p_item = NULL; size_t cb_item = 0; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (NULL == config) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; // initialize default values config->puk_blocked = false; config->puk_noblock_on_upgrade = false; config->pin_last_changed = 0; config->mgm_type = YKPIV_CONFIG_MGM_MANUAL; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; /* recover admin data */ if (YKPIV_OK == _read_metadata(state, TAG_ADMIN, data, &cb_data)) { if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_FLAGS_1, &p_item, &cb_item)) { if (*p_item & ADMIN_FLAGS_1_PUK_BLOCKED) config->puk_blocked = true; if (*p_item & ADMIN_FLAGS_1_PROTECTED_MGM) config->mgm_type = YKPIV_CONFIG_MGM_PROTECTED; } if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_SALT, &p_item, &cb_item)) { if (config->mgm_type != YKPIV_CONFIG_MGM_MANUAL) { DBG("conflicting types of mgm key administration configured"); config->mgm_type = YKPIV_CONFIG_MGM_INVALID; } else { config->mgm_type = YKPIV_CONFIG_MGM_DERIVED; } } if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_ADMIN_TIMESTAMP, &p_item, &cb_item)) { if (CB_ADMIN_TIMESTAMP != cb_item) { DBG("pin timestamp in admin metadata is an invalid size"); } else { memcpy(&(config->pin_last_changed), p_item, cb_item); } } } /* recover protected data */ cb_data = sizeof(data); if (YKPIV_OK == _read_metadata(state, TAG_PROTECTED, data, &cb_data)) { if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_PROTECTED_FLAGS_1, &p_item, &cb_item)) { if (*p_item & PROTECTED_FLAGS_1_PUK_NOBLOCK) config->puk_noblock_on_upgrade = true; } if (YKPIV_OK == _get_metadata_item(data, cb_data, TAG_PROTECTED_MGM, &p_item, &cb_item)) { if(sizeof(config->mgm_key) >= cb_item) { memcpy(config->mgm_key, p_item, cb_item); config->mgm_len = cb_item; if (config->mgm_type != YKPIV_CONFIG_MGM_PROTECTED) { DBG("conflicting types of mgm key administration configured - protected mgm exists"); config->mgm_type = YKPIV_CONFIG_MGM_INVALID; } } else { DBG("protected data contains mgm, but is the wrong size = %lu", (unsigned long)cb_item); config->mgm_type = YKPIV_CONFIG_MGM_INVALID; } } } else { if (config->mgm_type == YKPIV_CONFIG_MGM_PROTECTED) { DBG("admin data indicates protected mgm present, but the object cannot be read"); config->mgm_type = YKPIV_CONFIG_MGM_INVALID; } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_set_pin_last_changed(ykpiv_state *state) { ykpiv_rc res = YKPIV_OK; ykpiv_rc ykrc = YKPIV_OK; uint8_t data[CB_BUF_MAX] = { 0 }; size_t cb_data = sizeof(data); time_t tnow = 0; if (NULL == state) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; /* recover admin data */ if (YKPIV_OK != (ykrc = _read_metadata(state, TAG_ADMIN, data, &cb_data))) { cb_data = 0; /* set current metadata blob size to zero, we'll add the timestamp to the blank blob */ } tnow = time(NULL); if (YKPIV_OK != (res = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_TIMESTAMP, (uint8_t*)&tnow, CB_ADMIN_TIMESTAMP))) { DBG("could not set pin timestamp, err = %d", res); } else { if (YKPIV_OK != (res = _write_metadata(state, TAG_ADMIN, data, cb_data))) { /* Note: this can fail if authenticate() wasn't called previously - expected behavior */ DBG("could not write admin data, err = %d", res); } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_get_derived_mgm(ykpiv_state *state, const uint8_t *pin, const size_t pin_len, ykpiv_mgm *mgm) { ykpiv_rc res = YKPIV_OK; pkcs5_rc p5rc = PKCS5_OK; uint8_t data[CB_BUF_MAX] = { 0 }; size_t cb_data = sizeof(data); uint8_t *p_item = NULL; size_t cb_item = 0; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if ((NULL == pin) || (0 == pin_len) || (NULL == mgm)) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; /* recover management key */ if (YKPIV_OK == (res = _read_metadata(state, TAG_ADMIN, data, &cb_data))) { if (YKPIV_OK == (res = _get_metadata_item(data, cb_data, TAG_ADMIN_SALT, &p_item, &cb_item))) { if (cb_item != CB_ADMIN_SALT) { DBG("derived mgm salt exists, but is incorrect size = %lu", (unsigned long)cb_item); res = YKPIV_GENERIC_ERROR; goto Cleanup; } mgm->len = DES_LEN_3DES; if (PKCS5_OK != (p5rc = pkcs5_pbkdf2_sha1(pin, pin_len, p_item, cb_item, ITER_MGM_PBKDF2, mgm->data, mgm->len))) { DBG("pbkdf2 failure, err = %d", p5rc); res = YKPIV_GENERIC_ERROR; goto Cleanup; } } } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_get_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm) { ykpiv_rc res = YKPIV_OK; uint8_t data[CB_BUF_MAX] = { 0 }; size_t cb_data = sizeof(data); uint8_t *p_item = NULL; size_t cb_item = 0; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (NULL == mgm) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; if (YKPIV_OK != (res = _read_metadata(state, TAG_PROTECTED, data, &cb_data))) { DBG("could not read protected data, err = %d", res); goto Cleanup; } if (YKPIV_OK != (res = _get_metadata_item(data, cb_data, TAG_PROTECTED_MGM, &p_item, &cb_item))) { DBG("could not read protected mgm from metadata, err = %d", res); goto Cleanup; } if (cb_item > sizeof(mgm->data)) { DBG("protected data contains mgm, but is the wrong size = %lu", (unsigned long)cb_item); res = YKPIV_AUTHENTICATION_ERROR; goto Cleanup; } mgm->len = cb_item; memcpy(mgm->data, p_item, cb_item); Cleanup: yc_memzero(data, sizeof(data)); _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_update_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm) { ykpiv_rc res = YKPIV_OK; uint8_t data[CB_BUF_MAX] = {0}; size_t cb_data = sizeof(data); uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) goto Cleanup; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; if (YKPIV_OK != (res = _read_metadata(state, TAG_PROTECTED, data, &cb_data))) { cb_data = 0; /* set current metadata blob size to zero, we'll add to the blank blob */ } if (YKPIV_OK != (res = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_PROTECTED_MGM, mgm->data, mgm->len))) { DBG("could not set protected mgm item, err = %d", res); } else { if (YKPIV_OK != (res = _write_metadata(state, TAG_PROTECTED, data, cb_data))) { DBG("could not write protected data, err = %d", res); goto Cleanup; } } Cleanup: _ykpiv_end_transaction(state); return res; } /* to set a generated mgm, pass NULL for mgm, or set mgm.data to all zeroes */ ykpiv_rc ykpiv_util_set_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm) { ykpiv_rc res = YKPIV_OK; ykpiv_rc ykrc = YKPIV_OK; prng_rc prngrc = PRNG_OK; bool fGenerate = false; size_t mgm_len = DES_LEN_3DES; uint8_t mgm_key[sizeof(mgm->data)] = { 0 }; size_t i = 0; uint8_t data[CB_BUF_MAX] = { 0 }; size_t cb_data = sizeof(data); uint8_t *p_item = NULL; size_t cb_item = 0; uint8_t flags_1 = 0; if (NULL == state) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; fGenerate = true; if (mgm) { mgm_len = mgm->len; memcpy(mgm_key, mgm->data, mgm->len); for (i = 0; i < mgm_len; i++) { if (mgm_key[i] != 0) { fGenerate = false; break; } } } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) goto Cleanup; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; /* try to set the mgm key as long as we don't encounter a fatal error */ do { if (fGenerate) { /* generate a new mgm key */ if (PRNG_OK != (prngrc = _ykpiv_prng_generate(mgm_key, mgm_len))) { DBG("could not generate new mgm, err = %d", prngrc); res = YKPIV_RANDOMNESS_ERROR; goto Cleanup; } } if (YKPIV_OK != (ykrc = ykpiv_set_mgmkey3(state, mgm_key, mgm_len, YKPIV_ALGO_AUTO, YKPIV_TOUCHPOLICY_AUTO))) { /* ** if _set_mgmkey fails with YKPIV_KEY_ERROR, it means the generated key is weak ** otherwise, log a warning, since the device mgm key is corrupt or we're in ** a state where we can't set the mgm key */ if (YKPIV_KEY_ERROR != ykrc) { DBG("could not set new derived mgm key, err = %d", ykrc); res = ykrc; goto Cleanup; } } else { /* _set_mgmkey succeeded, stop generating */ fGenerate = false; } } while (fGenerate); /* set output mgm */ if (mgm) { memcpy(mgm->data, mgm_key, mgm_len); } /* after this point, we've set the mgm key, so the function should succeed, regardless of being able to set the metadata */ /* set the new mgm key in protected data */ if (YKPIV_OK != (ykrc = _read_metadata(state, TAG_PROTECTED, data, &cb_data))) { cb_data = 0; /* set current metadata blob size to zero, we'll add to the blank blob */ } if (YKPIV_OK != (ykrc = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_PROTECTED_MGM, mgm_key, mgm_len))) { DBG("could not set protected mgm item, err = %d", ykrc); } else { if (YKPIV_OK != (ykrc = _write_metadata(state, TAG_PROTECTED, data, cb_data))) { DBG("could not write protected data, err = %d", ykrc); goto Cleanup; } } /* set the protected mgm flag in admin data */ cb_data = sizeof(data); if (YKPIV_OK != (ykrc = _read_metadata(state, TAG_ADMIN, data, &cb_data))) { cb_data = 0; } else { if (YKPIV_OK != (ykrc = _get_metadata_item(data, cb_data, TAG_ADMIN_FLAGS_1, &p_item, &cb_item))) { /* flags are not set */ DBG("admin data exists, but flags are not present"); } if (cb_item == sizeof(flags_1)) { memcpy(&flags_1, p_item, cb_item); } else { DBG("admin data flags are an incorrect size = %lu", (unsigned long)cb_item); } /* remove any existing salt */ if (YKPIV_OK != (ykrc = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_SALT, NULL, 0))) { DBG("could not unset derived mgm salt, err = %d", ykrc); } } flags_1 |= ADMIN_FLAGS_1_PROTECTED_MGM; if (YKPIV_OK != (ykrc = _set_metadata_item(data, &cb_data, CB_OBJ_MAX, TAG_ADMIN_FLAGS_1, &flags_1, sizeof(flags_1)))) { DBG("could not set admin flags item, err = %d", ykrc); } else { if (YKPIV_OK != (ykrc = _write_metadata(state, TAG_ADMIN, data, cb_data))) { DBG("could not write admin data, err = %d", ykrc); goto Cleanup; } } Cleanup: yc_memzero(data, sizeof(data)); yc_memzero(mgm_key, sizeof(mgm_key)); _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_util_reset(ykpiv_state *state) { unsigned char templ[] = {0, YKPIV_INS_RESET, 0, 0}; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); ykpiv_rc res; int sw; /* note: the reset function is only available when both pins are blocked. */ res = ykpiv_transfer_data(state, templ, NULL, 0, data, &recv_len, &sw); if(res != YKPIV_OK) { return res; } return ykpiv_translate_sw_ex(__FUNCTION__, sw); } uint32_t ykpiv_util_slot_object(uint8_t slot) { int object_id = -1; switch (slot) { case YKPIV_KEY_AUTHENTICATION: object_id = YKPIV_OBJ_AUTHENTICATION; break; case YKPIV_KEY_SIGNATURE: object_id = YKPIV_OBJ_SIGNATURE; break; case YKPIV_KEY_KEYMGM: object_id = YKPIV_OBJ_KEY_MANAGEMENT; break; case YKPIV_KEY_CARDAUTH: object_id = YKPIV_OBJ_CARD_AUTH; break; case YKPIV_KEY_ATTESTATION: object_id = YKPIV_OBJ_ATTESTATION; break; default: if ((slot >= YKPIV_KEY_RETIRED1) && (slot <= YKPIV_KEY_RETIRED20)) { object_id = YKPIV_OBJ_RETIRED1 + (slot - YKPIV_KEY_RETIRED1); } break; } return (uint32_t)object_id; } ykpiv_rc ykpiv_util_get_certdata(uint8_t *buf, size_t buf_len, uint8_t* certdata, size_t *certdata_len) { uint8_t compress_info = YKPIV_CERTINFO_UNCOMPRESSED; uint8_t *certptr = 0; size_t cert_len = 0; uint8_t *ptr = buf; while (ptr < buf + buf_len) { uint8_t tag = *ptr++; size_t len = 0; size_t offs = _ykpiv_get_length(ptr, buf + buf_len, &len); if(!offs) { DBG("Found invalid length for tag 0x%02x.", tag); goto invalid_tlv; } ptr += offs; // move to after length bytes switch (tag) { case TAG_CERT: certptr = ptr; cert_len = len; DBG("Found TAG_CERT with length %zu", cert_len); break; case TAG_CERT_COMPRESS: if(len != 1) { DBG("Found TAG_CERT_COMPRESS with invalid length %zu", len); goto invalid_tlv; } compress_info = *ptr; DBG("Found TAG_CERT_COMPRESS with length %zu value 0x%02x", len, compress_info); break; case TAG_CERT_LRC: { // basically ignore it DBG("Found TAG_CERT_LRC with length %zu", len); break; } default: DBG("Unknown cert tag 0x%02x", tag); goto invalid_tlv; break; } ptr += len; // move to after value bytes } invalid_tlv: if(certptr == 0 || cert_len == 0 || ptr != buf + buf_len || compress_info > YKPIV_CERTINFO_GZIP) { DBG("Invalid TLV encoding, treating as a raw certificate"); certptr = buf; cert_len = buf_len; } if (compress_info == YKPIV_CERTINFO_GZIP) { #ifdef USE_CERT_COMPRESS z_stream zs; zs.zalloc = Z_NULL; zs.zfree = Z_NULL; zs.opaque = Z_NULL; zs.avail_in = (uInt) cert_len; zs.next_in = (Bytef *) certptr; zs.avail_out = (uInt) *certdata_len; zs.next_out = (Bytef *) certdata; if (inflateInit2(&zs, MAX_WBITS | 16) != Z_OK) { DBG("Failed to initialize certificate decompression"); *certdata_len = 0; return YKPIV_INVALID_OBJECT; } int res = inflate(&zs, Z_FINISH); if (res != Z_STREAM_END) { *certdata_len = 0; if (res == Z_BUF_ERROR) { DBG("Failed to decompress certificate. Allocated buffer is too small"); return YKPIV_SIZE_ERROR; } DBG("Failed to decompress certificate"); return YKPIV_INVALID_OBJECT; } if (inflateEnd(&zs) != Z_OK) { DBG("Failed to finish certificate decompression"); *certdata_len = 0; return YKPIV_INVALID_OBJECT; } *certdata_len = zs.total_out; #else DBG("Found compressed certificate. Decompressing certificate not supported"); *certdata_len = 0; return YKPIV_PARSE_ERROR; #endif } else { if (*certdata_len < cert_len) { DBG("Buffer too small"); *certdata_len = 0; return YKPIV_SIZE_ERROR; } memmove(certdata, certptr, cert_len); *certdata_len = cert_len; } return YKPIV_OK; } ykpiv_rc ykpiv_util_write_certdata(uint8_t *rawdata, size_t rawdata_len, uint8_t compress_info, uint8_t* certdata, size_t *certdata_len) { size_t offset = 0; size_t buf_len = 0; unsigned long len_bytes = get_length_size((unsigned long)rawdata_len); // calculate the required length of the encoded object buf_len = 1 /* cert tag */ + 3 /* compression tag + data*/ + 2 /* lrc */; buf_len += len_bytes + rawdata_len; if (buf_len > *certdata_len) { DBG("Buffer too small"); *certdata_len = 0; return YKPIV_SIZE_ERROR; } memmove(certdata + len_bytes + 1, rawdata, rawdata_len); certdata[offset++] = TAG_CERT; offset += _ykpiv_set_length(certdata+offset, rawdata_len); offset += rawdata_len; certdata[offset++] = TAG_CERT_COMPRESS; certdata[offset++] = 1; certdata[offset++] = compress_info; certdata[offset++] = TAG_CERT_LRC; certdata[offset++] = 0; *certdata_len = offset; return YKPIV_OK; } static ykpiv_rc _read_certificate(ykpiv_state *state, uint8_t slot, uint8_t *buf, size_t *buf_len) { ykpiv_rc res = YKPIV_OK; int object_id = (int)ykpiv_util_slot_object(slot); if (-1 == object_id) return YKPIV_INVALID_OBJECT; unsigned char data[YKPIV_OBJ_MAX_SIZE] = {0}; unsigned long data_len = sizeof (data); if (YKPIV_OK == (res = _ykpiv_fetch_object(state, object_id, data, &data_len))) { if ((res = ykpiv_util_get_certdata(data, data_len, buf, buf_len)) != YKPIV_OK) { DBG("Failed to get certificate data"); return res; } } else { *buf_len = 0; } return res; } static ykpiv_rc _write_certificate(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len, uint8_t certinfo) { uint8_t buf[CB_OBJ_MAX] = {0}; size_t buf_len = sizeof(buf); int object_id = (int)ykpiv_util_slot_object(slot); if (-1 == object_id) return YKPIV_INVALID_OBJECT; // check if data or data_len are zero, this means that we intend to delete the object if ((NULL == data) || (0 == data_len)) { // if either data or data_len are non-zero, return an error, // that we only delete strictly when both are set properly if ((NULL != data) || (0 != data_len)) { return YKPIV_ARGUMENT_ERROR; } return _ykpiv_save_object(state, object_id, NULL, 0); } // encode certificate data for storage ykpiv_rc res = YKPIV_OK; if ( (res=ykpiv_util_write_certdata(data, data_len, certinfo, buf, &buf_len)) != YKPIV_OK) { return res; } // write onto device return _ykpiv_save_object(state, object_id, buf, buf_len); } /* ** PIV Manager data helper functions ** ** These functions allow the PIV Manager to extend the YKPIV_OBJ_ADMIN_DATA object without having to change ** this implementation. New items may be added without modifying these functions. Data items are picked ** from the pivman_data buffer by tag, and replaced either in place if length allows or the data object is ** expanded to fit a new/updated data item. */ /* ** _get_metadata_item ** ** Parses the metadata blob, specified by data, looking for the specified tag. If found, the item is ** returned in pp_item and its size in pcb_item. ** ** If the item is not found, this function returns YKPIV_GENERIC_ERROR. */ static ykpiv_rc _get_metadata_item(uint8_t *data, size_t cb_data, uint8_t tag, uint8_t **pp_item, size_t *pcb_item) { uint8_t *p_temp = data; size_t offs, cb_temp = 0; uint8_t tag_temp = 0; bool found = false; if (!data || !pp_item || !pcb_item) return YKPIV_ARGUMENT_ERROR; *pp_item = NULL; *pcb_item = 0; while (p_temp < (data + cb_data)) { tag_temp = *p_temp++; offs = _ykpiv_get_length(p_temp, data + cb_data, &cb_temp); if (!offs) { return YKPIV_PARSE_ERROR; } p_temp += offs; if (tag_temp == tag) { // found tag found = true; break; } p_temp += cb_temp; } if (found) { *pp_item = p_temp; *pcb_item = cb_temp; } return found ? YKPIV_OK : YKPIV_GENERIC_ERROR; } ykpiv_rc ykpiv_util_parse_metadata(uint8_t *data, size_t data_len, ykpiv_metadata *metadata) { uint8_t *p = 0; size_t cb = 0; uint32_t cnt = 0; ykpiv_rc rc = _get_metadata_item(data, data_len, YKPIV_METADATA_ALGORITHM_TAG, &p, &cb); if(rc == YKPIV_OK && cb == 1) { metadata->algorithm = p[0]; cnt++; } rc = _get_metadata_item(data, data_len, YKPIV_METADATA_POLICY_TAG, &p, &cb); if(rc == YKPIV_OK && cb == 2) { metadata->pin_policy = p[0]; metadata->touch_policy = p[1]; cnt++; } rc = _get_metadata_item(data, data_len, YKPIV_METADATA_ORIGIN_TAG, &p, &cb); if(rc == YKPIV_OK && cb == 1) { metadata->origin = p[0]; cnt++; } rc = _get_metadata_item(data, data_len, YKPIV_METADATA_PUBKEY_TAG, &p, &cb); if(rc == YKPIV_OK && cb > 0 && cb <= sizeof(metadata->pubkey)) { metadata->pubkey_len = cb; memcpy(metadata->pubkey, p, cb); cnt++; } return cnt ? YKPIV_OK : YKPIV_PARSE_ERROR; } /* ** _set_metadata_item ** ** Adds or replaces a data item encoded in a metadata blob, specified by tag to the existing ** metadata blob (data) until it reaches the a maximum buffer size (cb_data_max). ** ** If adding/replacing the item would exceed cb_data_max, this function returns YKPIV_GENERIC_ERROR. ** ** The new size of the blob is returned in pcb_data. */ static ykpiv_rc _set_metadata_item(uint8_t *data, size_t *pcb_data, size_t cb_data_max, uint8_t tag, uint8_t *p_item, size_t cb_item) { uint8_t *p_temp = data; size_t cb_temp = 0; uint8_t tag_temp = 0; size_t cb_len = 0; uint8_t *p_next = NULL; long cb_moved = 0; /* must be signed to have negative offsets */ if (!data || !pcb_data) return YKPIV_ARGUMENT_ERROR; while (p_temp < (data + *pcb_data)) { tag_temp = *p_temp++; cb_len = _ykpiv_get_length(p_temp, data + *pcb_data, &cb_temp); if(!cb_len) { return YKPIV_PARSE_ERROR; } p_temp += cb_len; if (tag_temp == tag) { /* found tag */ /* check length, if it matches, overwrite */ if (cb_temp == cb_item) { memcpy(p_temp, p_item, cb_item); return YKPIV_OK; } /* length doesn't match, expand/shrink to fit */ p_next = p_temp + cb_temp; cb_moved = (long)cb_item - (long)cb_temp + ((long)(cb_item != 0 ? (long)_ykpiv_get_length_size(cb_item) : -1l /* for tag, if deleting */) - (long)cb_len); /* accounts for different length encoding */ /* length would cause buffer overflow, return error */ if ((size_t)(*pcb_data + cb_moved) > cb_data_max) { return YKPIV_GENERIC_ERROR; } /* move remaining data */ memmove(p_next + cb_moved, p_next, *pcb_data - (size_t)(p_next - data)); *pcb_data += cb_moved; /* re-encode item and insert */ if (cb_item != 0) { p_temp -= cb_len; p_temp += _ykpiv_set_length(p_temp, cb_item); memcpy(p_temp, p_item, cb_item); } return YKPIV_OK; } /* if tag found */ p_temp += cb_temp; } if (cb_item == 0) { /* we've been asked to delete an existing item that isn't in the blob */ return YKPIV_OK; } // we did not find an existing tag, append p_temp = data + *pcb_data; cb_len = _ykpiv_get_length_size(cb_item); // length would cause buffer overflow, return error if (*pcb_data + 1 + cb_len + cb_item > cb_data_max) { return YKPIV_GENERIC_ERROR; } *p_temp++ = tag; p_temp += _ykpiv_set_length(p_temp, cb_item); memcpy(p_temp, p_item, cb_item); *pcb_data += 1 + cb_len + cb_item; return YKPIV_OK; } /* ** _read_metadata ** ** Reads admin or protected data (specified by tag) from its associated object. ** ** The data stored in the object is parsed to ensure it has the correct tag and valid length. ** ** data must point to a buffer of at least CB_BUF_MAX bytes, and pcb_data should point to ** the size of data. ** ** To read from protected data, the pin must be verified prior to calling this function. */ static ykpiv_rc _read_metadata(ykpiv_state *state, uint8_t tag, uint8_t* data, size_t* pcb_data) { ykpiv_rc res = YKPIV_OK; uint8_t *p_temp = NULL; unsigned long cb_temp = 0; size_t offs; int obj_id = 0; if (!data || !pcb_data || (CB_BUF_MAX > *pcb_data)) return YKPIV_ARGUMENT_ERROR; switch (tag) { case TAG_ADMIN: obj_id = YKPIV_OBJ_ADMIN_DATA; break; case TAG_PROTECTED: obj_id = YKPIV_OBJ_PRINTED; break; default: return YKPIV_INVALID_OBJECT; } cb_temp = (unsigned long)*pcb_data; *pcb_data = 0; if (YKPIV_OK != (res = _ykpiv_fetch_object(state, obj_id, data, &cb_temp))) { return res; } if (cb_temp < CB_OBJ_TAG_MIN) return YKPIV_PARSE_ERROR; p_temp = data; if (tag != *p_temp++) return YKPIV_PARSE_ERROR; offs = _ykpiv_get_length(p_temp, data + cb_temp, pcb_data); if (!offs) { *pcb_data = 0; return YKPIV_PARSE_ERROR; } p_temp += offs; memmove(data, p_temp, *pcb_data); return YKPIV_OK; } /* ** _write_metadata ** ** Writes admin/protected data, specified by tag to its associated object. ** ** To delete the metadata, set data to NULL and cb_data to 0. ** ** To write protected data, the pin must be verified prior to calling this function. */ static ykpiv_rc _write_metadata(ykpiv_state *state, uint8_t tag, uint8_t *data, size_t cb_data) { ykpiv_rc res = YKPIV_OK; uint8_t buf[CB_OBJ_MAX] = { 0 }; uint8_t *pTemp = buf; int obj_id = 0; if (cb_data > (_obj_size_max(state) - CB_OBJ_TAG_MAX)) { return YKPIV_GENERIC_ERROR; } switch (tag) { case TAG_ADMIN: obj_id = YKPIV_OBJ_ADMIN_DATA; break; case TAG_PROTECTED: obj_id = YKPIV_OBJ_PRINTED; break; default: return YKPIV_INVALID_OBJECT; } if (!data || (0 == cb_data)) { // deleting metadata res = _ykpiv_save_object(state, obj_id, NULL, 0); } else { *pTemp++ = tag; pTemp += _ykpiv_set_length(pTemp, cb_data); memcpy(pTemp, data, cb_data); pTemp += cb_data; res = _ykpiv_save_object(state, obj_id, buf, (size_t)(pTemp - buf)); } return res; }yubico-piv-tool-2.7.1/lib/ykpiv-config.h.in0000664000175000017500000000713314731066755020103 0ustar winniewinnie/* * Copyright (c) 2014-2016,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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef YKPIV_VERSION_H #define YKPIV_VERSION_H #ifdef __cplusplus extern "C" { #endif /** * YKPIV_VERSION_STRING * * Pre-processor symbol with a string that describe the header file * version number. Used together with ykneomgr_check_version() to verify * header file and run-time library consistency. */ #cmakedefine YKPIV_VERSION_STRING "@VERSION@" /** * YKPIV_VERSION_NUMBER * * Pre-processor symbol with a hexadecimal value describing the header * file version number. For example, when the header version is 1.2.3 * this symbol will have the value 0x01020300. The last two digits * are only used between public releases, and will otherwise be 00. */ #cmakedefine YKPIV_VERSION_NUMBER @YKPIV_VERSION_NUMBER@ /** * YKPIV_VERSION_MAJOR * * Pre-processor symbol with a decimal value that describe the major * level of the header file version number. For example, when the * header version is 1.2.3 this symbol will be 1. */ #define YKPIV_VERSION_MAJOR @YKPIV_VERSION_MAJOR@ /** * YKPIV_VERSION_MINOR * * Pre-processor symbol with a decimal value that describe the minor * level of the header file version number. For example, when the * header version is 1.2.3 this symbol will be 2. */ #define YKPIV_VERSION_MINOR @YKPIV_VERSION_MINOR@ /** * YKPIV_VERSION_PATCH * * Pre-processor symbol with a decimal value that describe the patch * level of the header file version number. For example, when the * header version is 1.2.3 this symbol will be 3. */ #define YKPIV_VERSION_PATCH @YKPIV_VERSION_PATCH@ /** * _WIN32 * * Pre-processor symbol that describes the Windows system architecture. */ #cmakedefine _WIN32 @_WIN32@ /** * BACKEND_PCSC * * Pre-processor symbol that describes the available PCSC backend. * If PCSC was not found on the system, some functionality will be missing. */ #cmakedefine BACKEND_PCSC @BACKEND_PCSC@ /** * HAVE_PCSC_WINSCARD_H * * Pre-processor symbol indicating whether the file PCSC/winscard.h * exists on the system or not. */ #cmakedefine HAVE_PCSC_WINSCARD_H @HAVE_PCSC_WINSCARD_H@ const char *ykpiv_check_version (const char *req_version); #ifdef __cplusplus } #endif #endif yubico-piv-tool-2.7.1/lib/ykpiv.c0000664000175000017500000027142314731066755016233 0ustar winniewinnie /* * Copyright (c) 2014-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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ /** @file */ #include #include #include #include #include #include #include "internal.h" #include "ykpiv.h" #include "scp11_util.h" #include "ecdh.h" #include "../common/util.h" #include "../aes_cmac/aes.h" /** * DISABLE_PIN_CACHE - disable in-RAM cache of PIN * * By default, the PIN is cached in RAM when provided to \p ykpiv_verify() or * changed with \p ykpiv_change_pin(). If the USB connection is lost between * calls, the device will be re-authenticated on the next call using the cached * PIN. The PIN is cleared with a call to \p ykpiv_done(). * * The PIN cache prevents problems with long-running applications losing their * authentication in some cases, such as when a laptop sleeps. * * The cache can be disabled by setting this define to 1 if it is not desired * to store the PIN in RAM. * */ #ifndef DISABLE_PIN_CACHE #define DISABLE_PIN_CACHE 0 #endif /** * DISABLE_MGM_KEY_CACHE - disable in-RAM cache of MGM_KEY (SO PIN) * * By default, the MGM_KEY is cached in RAM when provided to \p ykpiv_authenticate() or * changed with \p ykpiv_set_mgmkey(). If the USB connection is lost between * calls, the device will be re-authenticated on the next call using the cached * MGM_KEY. The MGM_KEY is cleared with a call to \p ykpiv_done(). * * The MGM_KEY cache prevents problems with long-running applications losing their * authentication in some cases, such as when a laptop sleeps. * * The cache can be disabled by setting this define to 1 if it is not desired * to store the MGM_KEY in RAM. * */ #ifndef DISABLE_MGM_KEY_CACHE #define DISABLE_MGM_KEY_CACHE 0 #endif /** * ENABLE_APPLICATION_RESELECT - re-select application for all public API calls * * If this is enabled, every public call (prefixed with \r ykpiv_) will check * that the PIV application is currently selected, or re-select it if it is * not. * * Auto re-selection allows a long-running PIV application to cooperate on * a system that may simultaneously use the non-PIV applications of connected * devices. * * This is \b DANGEROUS - with this enabled, slots with the policy * \p YKPIV_PINPOLICY_ALWAYS will not be accessible. * */ #ifndef ENABLE_APPLICATION_RESELECTION #define ENABLE_APPLICATION_RESELECTION 0 #endif /** * ENABLE_IMPLICIT_TRANSACTIONS - call SCardBeginTransaction for all public API calls * * If this is enabled, every public call (prefixed with \r ykpiv_) will call * SCardBeginTransaction on entry and SCardEndTransaction on exit. * * For applications that do not do their own transaction management, like the piv tool * itself, retaining the default setting of enabled can allow other applications and * threads to make calls to CCID that can interfere with multi-block data sent to the * card via SCardTransmitData. */ #ifndef ENABLE_IMPLICIT_TRANSACTIONS #define ENABLE_IMPLICIT_TRANSACTIONS 1 #endif /** * Platform specific definitions */ #ifdef _MSC_VER #define strncasecmp _strnicmp #endif #define YKPIV_MGM_DEFAULT "\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08" static ykpiv_rc _cache_pin(ykpiv_state *state, const char *pin, size_t len); static ykpiv_rc _cache_mgm_key(ykpiv_state *state, unsigned const char *key, size_t len); static ykpiv_rc _ykpiv_get_serial(ykpiv_state *state); static ykpiv_rc _ykpiv_get_version(ykpiv_state *state); static ykpiv_rc _ykpiv_verify(ykpiv_state *state, char *pin, size_t *pin_len, bool bio, bool verify_spin); static ykpiv_rc _ykpiv_authenticate2(ykpiv_state *state, unsigned const char *key, size_t len); static ykpiv_rc _ykpiv_auth_deauthenticate(ykpiv_state *state); static unsigned const char piv_aid[] = { 0xa0, 0x00, 0x00, 0x03, 0x08 }; static unsigned const char yk_aid[] = { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01, 0x01 }; static unsigned const char mgmt_aid[] = { 0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; static unsigned const char sd_aid[] = { 0xa0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00, 0x00 }; //INTERNAL AUTHENTICATE Data Field as defined in GPC 2.3 F SCP11 v1.4 PublicRelease // TLV: '0xA6' = Control Reference Template (Key Agreement), 13 bytes, the following 4 TLVs // TLV: '0x90' = SCP identifier and parameters, 2 bytes, SCP11 ID (0x11), SCP11b ID (0x00) // TLV: '0x95' = Key Usage Qualifier, 1 byte, 0x3c = Secure messaging with C-MAC, R-MAC, and encryption // TLV: '0x80' = Key type, 1 byte, AES key (0x88) // TLV: '0x81' = Key length, 1 byte, 16 // TLV: '0x5F49' = ePK.OCE.ECKA static unsigned const char scp11_keyagreement_template[] = { 0xa6, 13, 0x90, 2, 0x11, 0x00, 0x95, 1, SCP11_KEY_USAGE, 0x80, 1, SCP11_KEY_TYPE, 0x81, 1, SCP11_SESSION_KEY_LEN, SCP11_ePK_SD_ECKA_TAG >> 8, SCP11_ePK_SD_ECKA_TAG & 0xff }; static void* _default_alloc(void *data, size_t cb) { (void)data; return calloc(cb, 1); } static void * _default_realloc(void *data, void *p, size_t cb) { (void)data; return realloc(p, cb); } static void _default_free(void *data, void *p) { (void)data; free(p); } ykpiv_allocator _default_allocator = { .pfn_alloc = _default_alloc, .pfn_realloc = _default_realloc, .pfn_free = _default_free, .alloc_data = 0 }; /* Memory helper functions */ void* _ykpiv_alloc(ykpiv_state *state, size_t size) { if (!state || !(state->allocator.pfn_alloc)) return NULL; return state->allocator.pfn_alloc(state->allocator.alloc_data, size); } void* _ykpiv_realloc(ykpiv_state *state, void *address, size_t size) { if (!state || !(state->allocator.pfn_realloc)) return NULL; return state->allocator.pfn_realloc(state->allocator.alloc_data, address, size); } void _ykpiv_free(ykpiv_state *state, void *data) { if (!data || !state || !(state->allocator.pfn_free)) return; state->allocator.pfn_free(state->allocator.alloc_data, data); } size_t _ykpiv_get_length_size(size_t length) { if(length < 0x80) { return 1; } else if(length < 0x100) { return 2; } else { return 3; } } size_t _ykpiv_set_length(unsigned char *buffer, size_t length) { if(length < 0x80) { *buffer++ = (unsigned char)length; return 1; } else if(length < 0x100) { *buffer++ = 0x81; *buffer++ = (unsigned char)length; return 2; } else { *buffer++ = 0x82; *buffer++ = (length >> 8) & 0xff; *buffer++ = length & 0xff; return 3; } } size_t _ykpiv_get_length(const unsigned char *buffer, const unsigned char* end, size_t *len) { if(buffer + 1 <= end && buffer[0] < 0x80) { *len = buffer[0]; return buffer + 1 + *len <= end ? 1 : 0; } else if(buffer + 2 <= end && buffer[0] == 0x81) { *len = buffer[1]; return buffer + 2 + *len <= end ? 2 : 0; } else if(buffer + 3 <= end && buffer[0] == 0x82) { size_t tmp = buffer[1]; *len = (tmp << 8) + buffer[2]; return buffer + 3 + *len <= end ? 3 : 0; } *len = 0; return 0; } static unsigned char *set_object(int object_id, unsigned char *buffer) { *buffer++ = 0x5c; if(object_id == YKPIV_OBJ_DISCOVERY) { *buffer++ = 1; *buffer++ = YKPIV_OBJ_DISCOVERY; } else if(object_id > 0xffff && object_id <= 0xffffff) { *buffer++ = 3; *buffer++ = (object_id >> 16) & 0xff; *buffer++ = (object_id >> 8) & 0xff; *buffer++ = object_id & 0xff; } else { return NULL; } return buffer; } static ykpiv_rc pcsc_to_yrc(pcsc_long rc) { switch(rc) { case SCARD_E_NO_SERVICE: case SCARD_E_SERVICE_STOPPED: return YKPIV_PCSC_SERVICE_ERROR; default: return YKPIV_PCSC_ERROR; } } static void ykpiv_stderr_debug(const char *buf) { fprintf(stderr, "%s\n", buf); } static void (*ykpiv_debug)(const char *) = ykpiv_stderr_debug; static int ykpiv_verbose = 0; void _ykpiv_set_debug(void (*dbg)(const char *)) { ykpiv_debug = dbg ? dbg : ykpiv_stderr_debug; } void _ykpiv_debug(const char *file, int line, const char *func, int lvl, const char *format, ...) { if(lvl <= ykpiv_verbose) { char buf[8192]; #ifdef _WIN32 const char *name = strrchr(file, '\\'); #else const char *name = strrchr(file, '/'); #endif if(snprintf(buf, sizeof(buf), "DBG %s:%d (%s): ", name ? name + 1 : file, line, func) < 0) { buf[0] = 0; } size_t len = strlen(buf); va_list args; va_start(args, format); if(vsnprintf(buf + len, sizeof(buf) - len, format, args) < 0) { buf[len] = 0; } if(format[0] && format[strlen(format) - 1] == '@') { // Format ends with marker, expect two extra args len = strlen(buf) - 1; // Overwrite the marker uint8_t *p = va_arg(args, uint8_t *); size_t n = va_arg(args, size_t); for(size_t i = 0; i < n; i++) { if(snprintf(buf + len, sizeof(buf) - len, "%02x", p[i]) < 0) { buf[len] = 0; } len = strlen(buf); } if(snprintf(buf + len, sizeof(buf) - len, " (%zu)", n) < 0) { buf[len] = 0; } } va_end(args); ykpiv_debug(buf); } } ykpiv_rc ykpiv_init_with_allocator(ykpiv_state **state, int verbose, const ykpiv_allocator *allocator) { ykpiv_state *s; if (NULL == state) { return YKPIV_ARGUMENT_ERROR; } if (NULL == allocator || !allocator->pfn_alloc || !allocator->pfn_realloc || !allocator->pfn_free) { return YKPIV_MEMORY_ERROR; } s = allocator->pfn_alloc(allocator->alloc_data, sizeof(ykpiv_state)); if (NULL == s) { return YKPIV_MEMORY_ERROR; } ykpiv_verbose = verbose; memset(s, 0, sizeof(ykpiv_state)); s->allocator = *allocator; s->context = (SCARDCONTEXT)-1; *state = s; return YKPIV_OK; } ykpiv_rc ykpiv_init(ykpiv_state **state, int verbose) { return ykpiv_init_with_allocator(state, verbose, &_default_allocator); } static ykpiv_rc _ykpiv_done(ykpiv_state *state, bool disconnect) { if (disconnect) ykpiv_disconnect(state); _cache_pin(state, NULL, 0); _cache_mgm_key(state, NULL, 0); _ykpiv_free(state, state); return YKPIV_OK; } ykpiv_rc ykpiv_done_with_external_card(ykpiv_state *state) { return _ykpiv_done(state, false); } ykpiv_rc ykpiv_done(ykpiv_state *state) { return _ykpiv_done(state, true); } ykpiv_rc ykpiv_disconnect(ykpiv_state *state) { if(state->card) { DBG("Disconnect card #%u.", state->serial); pcsc_long rc = SCardDisconnect(state->card, SCARD_RESET_CARD); if(rc != SCARD_S_SUCCESS) { DBG("SCardDisconnect failed on card #%u rc=%lx", state->serial, (long)rc); } state->card = 0; } if(SCardIsValidContext(state->context) == SCARD_S_SUCCESS) { SCardReleaseContext(state->context); state->context = (SCARDCONTEXT)-1; } state->serial = 0; state->ver.major = 0; state->ver.minor = 0; state->ver.patch = 0; return YKPIV_OK; } typedef struct { uint8_t tag; size_t length; uint8_t *value; } _tlv; static uint8_t next_tlv(uint8_t *ptr, uint8_t *end, _tlv *tlv) { if(ptr + 1 > end) { DBG("Tag offset is not within range"); return 0; } tlv->tag = *ptr; size_t len = _ykpiv_get_length(ptr + 1, end, &tlv->length); if(len == 0) { DBG("Length index not within data range"); return 0; } if (ptr + 1 + len + tlv->length > end) { DBG("Tag value too long for available data"); return 0; } tlv->value = ptr + 1 + len; return tlv->tag; } static ykpiv_rc skip_next_tlv(uint8_t **ptr, uint8_t *end, uint8_t expected_tag, const char *tag_str) { _tlv tlv = {0}; if(next_tlv(*ptr, end, &tlv) != expected_tag) { DBG("Failed to parse data. Expected tag for %s was %x, found %x", tag_str, expected_tag, tlv.tag); return YKPIV_PARSE_ERROR; } *ptr = tlv.value + tlv.length; return YKPIV_OK; } static uint8_t * get_pubkey_offset(uint8_t *cert_ptr, uint8_t *cert_end, size_t pubkey_len, uint8_t *algo, size_t algo_len) { //DER structure: //subjectPublicKeyInfo SubjectPublicKeyInfo SEQUENCE (2 elem) // algorithm AlgorithmIdentifier SEQUENCE (2 elem) // algorithm OBJECT IDENTIFIER 1.2.840.10045.2.1 ecPublicKey (ANSI X9.62 public key type) // parameters ANY OBJECT IDENTIFIER 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve) // subjectPublicKey BIT STRING (520 bit) //subjectPublicKeyInfo SubjectPublicKeyInfo SEQUENCE (2 elem) _tlv pubkey_info = {0}; if (next_tlv(cert_ptr, cert_end, &pubkey_info) != 0x30) { DBG("Failed to parse certificate. Expected tag for subjectPublicKeyInfo SEQUENCE was 0x30, found %x", pubkey_info.tag); return 0; } // algorithm AlgorithmIdentifier SEQUENCE (2 elem) // algorithm OBJECT IDENTIFIER 1.2.840.10045.2.1 ecPublicKey (ANSI X9.62 public key type) // parameters ANY OBJECT IDENTIFIER 1.2.840.10045.3.1.7 prime256v1 (ANSI X9.62 named elliptic curve) _tlv algo_oid = {0}; if (next_tlv(pubkey_info.value, cert_end, &algo_oid) != 0x30) { DBG("Failed to parse certificate. Expected tag for subjectPublicKeyInfo.algorithm SEQUENCE was 0x30, found %x", algo_oid.tag); return NULL; } if (algo_oid.length != algo_len) { DBG("Failed to parse certificate. Unexpected length of public key algorithm data"); return 0; } if (memcmp(algo, algo_oid.value, algo_len) != 0) { DBG("Failed to parse certificate. Unexpected public key algorithm data"); return 0; } // subjectPublicKey BIT STRING (520 bit) _tlv pubkey = {0}; if (next_tlv(algo_oid.value + algo_oid.length, cert_end, &pubkey) != 0x03) { DBG("Failed to parse certificate. Expected tag for subjectPublicKeyInfo.algorithm SEQUENCE was 0x30, found %x", algo_oid.tag); return NULL; } if (*pubkey.value == 0) { pubkey.value++; pubkey.length--; } if (pubkey.length != pubkey_len) { DBG("Failed to parse certificate. Unexpected length of public key data"); return 0; } return pubkey.value; } static ykpiv_rc scp11_get_sd_pubkey(ykpiv_state *state, uint8_t *pubkey, size_t *pubkey_len, uint8_t *algo, size_t algo_len) { ykpiv_rc rc; // Select the globalplatform application unsigned char templ[] = {0x00, YKPIV_INS_SELECT_APPLICATION, 0x04, 0x00}; unsigned long recv_len; int sw = 0; if ((rc = _ykpiv_transfer_data(state, templ, sd_aid, sizeof(sd_aid), NULL, &recv_len, &sw)) != YKPIV_OK) { *pubkey_len = 0; return rc; } rc = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (rc != YKPIV_OK) { DBG("Failed selecting application"); *pubkey_len = 0; return rc; } unsigned char apdu[] = {0x00, GP_INS_GET_DATA, SCP11_CERTIFICATE_STORE_TAG >> 8, SCP11_CERTIFICATE_STORE_TAG & 0xff}; unsigned char apdu_data[6] = {0xa6, 0x04, 0x83, 0x2, SCP11B_KID, SCP11B_KVN}; unsigned char certchain[YKPIV_OBJ_MAX_SIZE] = {0}; unsigned long certchain_len = sizeof(certchain); if ((rc = _ykpiv_transfer_data(state, apdu, apdu_data, sizeof(apdu_data), certchain, &certchain_len, &sw)) != YKPIV_OK) { DBG("Failed to communicate with device. res: %d sw: %08x", rc, sw); *pubkey_len = 0; return rc; } rc = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (rc != YKPIV_OK) { DBG("Failed to get SCP11 SD public key (CERT.SD.ECKA). res: %d sw: %08x", rc, sw); *pubkey_len = 0; return rc; } // Find the last certificate in the chain // Good resources about parsing certificate data: https://lapo.it/asn1js and https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/ uint8_t *ptr = certchain; uint8_t *end = certchain + certchain_len; size_t len = 0; do { ptr += len; // skip the previous certificate if (*ptr != 0x30) { DBG("Failed to parse data as certificate chain. Data does not start with a SEQUENCE in DER format"); *pubkey_len = 0; return YKPIV_PARSE_ERROR; } ptr++; // skip the next 0x30 tag ptr += _ykpiv_get_length(ptr, end, &len); // skip the length bytes } while (ptr + len < end); // if we haven't reached the end of the data, skip to the next certificate // Now we go into the TBScertificate data if (*ptr != 0x30) { DBG("Failed to parse data as certificate. Data does not start with a SEQUENCE in DER format"); return YKPIV_PARSE_ERROR; } ptr++; // skip the 0x30 tag starting the TBSCertificate ptr += _ykpiv_get_length(ptr, end, &len); // skip the length bytes for the TBSCertificate if(*ptr == 0xa0) { // If certificate version is present, skip it if ((rc = skip_next_tlv(&ptr, end, 0xa0, "Certificate Version")) != YKPIV_OK) { return rc; } } if ((rc = skip_next_tlv(&ptr, end, 0x02, "SerialNumber")) != YKPIV_OK) { return rc; } if ((rc = skip_next_tlv(&ptr, end, 0x30, "Signature Algorithm SEQUENCE")) != YKPIV_OK) { return rc; } if ((rc = skip_next_tlv(&ptr, end, 0x30, "Issuer SEQUENCE")) != YKPIV_OK) { return rc; } if ((rc = skip_next_tlv(&ptr, end, 0x30, "Validity SEQUENCE")) != YKPIV_OK) { return rc; } if ((rc = skip_next_tlv(&ptr, end, 0x30, "Subject SEQUENCE")) != YKPIV_OK) { return rc; } if ((ptr = get_pubkey_offset(ptr, end, *pubkey_len, algo, algo_len)) == 0) { DBG("Failed to find public key in certificate data"); return YKPIV_PARSE_ERROR; } memcpy(pubkey, ptr, *pubkey_len); return YKPIV_OK; } static ykpiv_rc scp11_derive_session_keys(uint8_t *oce_privkey, size_t oce_privkey_len, uint8_t *sde_pubkey, size_t sde_pubkey_len, uint8_t *sd_pubkey, size_t sd_pubkey_len, uint8_t *session_keys) { size_t ecdh_len = SCP11_SESSION_KEY_LEN * 2; uint8_t sh_see[SCP11_SESSION_KEY_LEN * 2] = {0}; uint8_t sh_ses[SCP11_SESSION_KEY_LEN * 2] = {0}; size_t len = ecdh_calculate_secret(ecdh_curve_p256(), oce_privkey, oce_privkey_len, sde_pubkey, sde_pubkey_len, sh_see, ecdh_len); if (len != ecdh_len) { DBG("Failed to derive ECDH shared key (ShSee). ECDH length does not match. Expected %d. Found %d", ecdh_len, len); return YKPIV_AUTHENTICATION_ERROR; } len = ecdh_calculate_secret(ecdh_curve_p256(), oce_privkey, oce_privkey_len, sd_pubkey, sd_pubkey_len, sh_ses, ecdh_len); if (len != ecdh_len) { DBG("Failed to derive ECDH shared key (SHSes). ECDH length does not match. Expected %d. Found %d", ecdh_len, len); return YKPIV_AUTHENTICATION_ERROR; } // Hash data: sh_see + sh_ses + 4 bytes counter with initial value 0 + shared_data uint8_t shared_data[] = {SCP11_KEY_USAGE, SCP11_KEY_TYPE, SCP11_SESSION_KEY_LEN}; size_t hash_data_len = (ecdh_len * 2) + 4 + sizeof(shared_data); uint8_t hash_data[(SCP11_SESSION_KEY_LEN * 4) + 7] = {0}; memcpy(hash_data, sh_see, ecdh_len); memcpy(hash_data + ecdh_len, sh_ses, ecdh_len); memcpy(hash_data + hash_data_len - sizeof(shared_data), shared_data, sizeof(shared_data)); // We will only need to increase the counter 3 times (to the value 3) so it will only occupy 1 byte size_t counter_index = (ecdh_len * 2) + 3; // We need 5 keys, 16 bytes each. Each iteration produces 32 bytes, so we need to run 3 iteration to get at least 5 for (int i = 0; i < 3; i++) { hash_data[counter_index]++; SHA256(hash_data, hash_data_len, session_keys + (i * ecdh_len)); } return YKPIV_OK; } static ykpiv_rc scp11_internal_authenticate(ykpiv_state *state, uint8_t *data, size_t data_len, uint8_t* epubkey_sd, size_t* epubkey_sd_len, uint8_t* receipt) { uint8_t apdu[] = {0x80, GP_INS_INTERNAL_AUTHENTICATE, SCP11B_KVN, SCP11B_KID}; ykpiv_rc rc; uint8_t recv[YKPIV_OBJ_MAX_SIZE] = {0}; unsigned long recv_len = sizeof(recv); int sw = 0; if ((rc = _ykpiv_transfer_data(state, apdu, data, data_len, recv, &recv_len, &sw)) != YKPIV_OK) { return rc; } rc = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (rc != YKPIV_OK) { DBG("Failed to get SCP11b public key. res: %d sw: %08x", rc, sw); return rc; } if ((recv[0] != (SCP11_ePK_SD_ECKA_TAG >> 8)) || (recv[1] != (SCP11_ePK_SD_ECKA_TAG & 0xff))) { DBG("Received response for INTERNAL AUTHENTICATE command does not start with 0x5F49 (ePK.SD.ECKA)"); return YKPIV_AUTHENTICATION_ERROR; } uint8_t *ptr = recv + 2; if (*ptr > *epubkey_sd_len) { DBG("Buffer size too small. Found key length %d", *ptr); return YKPIV_SIZE_ERROR; } *epubkey_sd_len = *ptr; ptr++; memcpy(epubkey_sd, ptr, *epubkey_sd_len); ptr += *epubkey_sd_len; if (*ptr != 0x86) { // Receipt TLV tag DBG("Received response for INTERNAL AUTHENTICATE command does not contains no receipt"); return YKPIV_AUTHENTICATION_ERROR; } ptr++; if (*ptr != SCP11_MAC_LEN) { DBG("Wrong receipt length. Expected 16. Found %d\n", (*ptr)); return YKPIV_AUTHENTICATION_ERROR; } ptr++; memcpy(receipt, ptr, SCP11_MAC_LEN); return YKPIV_OK; } static ykpiv_rc scp11_verify_channel(uint8_t *verification_key, uint8_t *receipt, uint8_t *apdu_data, uint32_t apdu_data_len, uint8_t *epubkey_sd, size_t epubkey_sd_len) { uint32_t ka_data_len = apdu_data_len + epubkey_sd_len + 3; uint8_t *ka_data = malloc(ka_data_len); if (!ka_data) { DBG("Failed to allocate memory for key agreement data"); return YKPIV_MEMORY_ERROR; } memcpy(ka_data, apdu_data, apdu_data_len); ka_data[apdu_data_len] = SCP11_ePK_SD_ECKA_TAG >> 8; ka_data[apdu_data_len + 1] = SCP11_ePK_SD_ECKA_TAG & 0xff; ka_data[apdu_data_len + 2] = epubkey_sd_len; memcpy(ka_data + apdu_data_len + 3, epubkey_sd, epubkey_sd_len); ykpiv_rc rc = YKPIV_OK; uint8_t mac_out[SCP11_MAC_LEN] = {0}; if ((rc = scp11_mac_data(verification_key, NULL, ka_data, ka_data_len, mac_out)) != YKPIV_OK) { DBG("Failed to calculate CMAC value"); goto sc_verify_cleanup; } if (memcmp(mac_out, receipt, SCP11_MAC_LEN) != 0) { DBG("Failed to verify SCP11 connection"); rc = YKPIV_AUTHENTICATION_ERROR; goto sc_verify_cleanup; } sc_verify_cleanup: if (ka_data) { free(ka_data); } return rc; } static ykpiv_rc scp11_open_secure_channel(ykpiv_state *state) { ykpiv_rc rc; //DER encode: // 0x06 tag for OID followed by the length the OID (7 bytes) followed by the OID representing EC PublicKey // 0x06 tag for OID followed by the length the OID (8 bytes) followed by the OID representing prime256v1 curve uint8_t sd_pubkey_algo[] = {0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}; uint8_t oce_privkey[32] = {0}; size_t oce_privkey_len = sizeof(oce_privkey); uint8_t oce_pubkey[65] = {0}; size_t oce_pubkey_len = sizeof(oce_pubkey); uint8_t sd_pubkey[65] = {0}; size_t sd_pubkey_len = sizeof(sd_pubkey); uint8_t data[1024] = {0}; size_t data_len = sizeof(data); if ((rc = scp11_get_sd_pubkey(state, sd_pubkey, &sd_pubkey_len, sd_pubkey_algo, sizeof(sd_pubkey_algo))) != YKPIV_OK) { DBG("Failed to get SD public key (PK.SD.ECKA)"); return rc; } // Select the PIV application unsigned char select_templ[] = {0x00, YKPIV_INS_SELECT_APPLICATION, 0x04, 0x00}; unsigned long recv_len; int sw = 0; if ((rc = _ykpiv_transfer_data(state, select_templ, piv_aid, sizeof(piv_aid), NULL, &recv_len, &sw)) != YKPIV_OK) { return rc; } if ((rc = ykpiv_translate_sw_ex(__FUNCTION__, sw)) != YKPIV_OK) { DBG("Failed selecting application"); return rc; } if (!ecdh_generate_keypair(ecdh_curve_p256(), oce_privkey, sizeof(oce_privkey), oce_pubkey, sizeof(oce_pubkey))) { DBG("Failed to generate the OCE ephemeral keypair"); return YKPIV_AUTHENTICATION_ERROR; } data_len = sizeof(scp11_keyagreement_template) + 1 + oce_pubkey_len; if (sizeof(data_len) + 5 > YKPIV_OBJ_MAX_SIZE) { // Total APDU length DBG("Message too long"); return YKPIV_SIZE_ERROR; } memcpy(data, scp11_keyagreement_template, sizeof(scp11_keyagreement_template)); data[sizeof(scp11_keyagreement_template)] = oce_pubkey_len; memcpy(data + sizeof(scp11_keyagreement_template) + 1, oce_pubkey, oce_pubkey_len); uint8_t sde_pubkey[512] = {0}; size_t sde_pubkey_len = sizeof(sde_pubkey); uint8_t receipt[SCP11_MAC_LEN] = {0}; if ((rc = scp11_internal_authenticate(state, data, data_len, sde_pubkey, &sde_pubkey_len, receipt)) != YKPIV_OK) { DBG("Failed to do SCP11 internal authentication"); return rc; } uint8_t session_keys[SCP11_SESSION_KEY_LEN * 6] = {0}; if ((rc = scp11_derive_session_keys(oce_privkey, oce_privkey_len, sde_pubkey, sde_pubkey_len, sd_pubkey, sd_pubkey_len, session_keys)) != YKPIV_OK) { DBG("Failed to derive SCP11 session keys"); return rc; } if ((rc = scp11_verify_channel(session_keys, receipt, data, data_len, sde_pubkey, sde_pubkey_len)) != YKPIV_OK) { DBG("Failed to verify SCP11 session"); return rc; } state->scp11_state.security_level = SCP11_KEY_USAGE; memcpy(state->scp11_state.senc, session_keys + SCP11_SESSION_KEY_LEN, SCP11_SESSION_KEY_LEN); memcpy(state->scp11_state.smac, session_keys + (SCP11_SESSION_KEY_LEN * 2), SCP11_SESSION_KEY_LEN); memcpy(state->scp11_state.srmac, session_keys + (SCP11_SESSION_KEY_LEN * 3), SCP11_SESSION_KEY_LEN); memcpy(state->scp11_state.mac_chain, receipt, SCP11_MAC_LEN); state->scp11_state.enc_counter = 1; DBG("SCardConnect succeeded for 'Yubico YubiKey OTP+FIDO+CCID', protocol=2"); return YKPIV_OK; } ykpiv_rc _ykpiv_select_application(ykpiv_state *state, bool scp11) { ykpiv_rc res = YKPIV_OK; if(scp11) { res = scp11_open_secure_channel(state); } else { unsigned char templ[] = {0x00, YKPIV_INS_SELECT_APPLICATION, 0x04, 0x00}; unsigned long recv_len; int sw = 0; if ((res = _ykpiv_transfer_data(state, templ, piv_aid, sizeof(piv_aid), NULL, &recv_len, &sw)) != YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); } if (res != YKPIV_OK) { DBG("Failed selecting application"); return res; } /* now that the PIV application is selected, retrieve the version * and serial number. Previously the NEO/YK4 required switching * to the yk applet to retrieve the serial, YK5 implements this * as a PIV applet command. Unfortunately, this change requires * that we retrieve the version number first, so that get_serial * can determine how to get the serial number, which for the NEO/Yk4 * will result in another selection of the PIV applet. */ // This stores the number of PIN retries left in state _ykpiv_verify(state, NULL, 0, false, false); // WRONG_PIN or PIN_LOCKED is expected on successful query. res = _ykpiv_get_version(state); if (res != YKPIV_OK) { DBG("Failed to retrieve version: '%s'", ykpiv_strerror(res)); return res; } res = _ykpiv_get_serial(state); if (res != YKPIV_OK) { DBG("Failed to retrieve serial number: '%s'", ykpiv_strerror(res)); res = YKPIV_OK; } return res; } ykpiv_rc _ykpiv_ensure_application_selected(ykpiv_state *state, bool scp11) { ykpiv_rc res = YKPIV_OK; #if ENABLE_APPLICATION_RESELECTION if (NULL == state) { return YKPIV_ARGUMENT_ERROR; } res = _ykpiv_verify(state, NULL, 0, false, false); if ((YKPIV_OK != res) && (YKPIV_WRONG_PIN != res) && (YKPIV_PIN_LOCKED != res)) { DBG("Failed to detect PIV application: '%s'", ykpiv_strerror(res)); res = _ykpiv_select_application(state, scp11); } else { res = YKPIV_OK; } return res; #else (void)state; return res; #endif } static ykpiv_rc _ykpiv_connect(ykpiv_state *state, uintptr_t context, uintptr_t card) { if (NULL == state) { return YKPIV_ARGUMENT_ERROR; } // if the context has changed, and the new context is not valid, return an error if ((context != state->context) && (SCARD_S_SUCCESS != SCardIsValidContext(context))) { return YKPIV_PCSC_ERROR; } // if card handle has changed, determine if handle is valid (less efficient, but complete) if ((card != state->card)) { char reader[CB_BUF_MAX] = {0}; pcsc_word reader_len = sizeof(reader); uint8_t atr[CB_ATR_MAX] = {0}; pcsc_word atr_len = sizeof(atr); // Cannot set the reader len to NULL. Confirmed in OSX 10.10, so we have to retrieve it even though we don't need it. pcsc_long rc = SCardStatus(card, reader, &reader_len, NULL, &(state->protocol), atr, &atr_len); if (rc != SCARD_S_SUCCESS) { DBG("SCardStatus failed: rc=%lx", (long)rc); return pcsc_to_yrc(rc); } if(atr_len + 1 == sizeof(YKPIV_ATR_NEO_R3) && !memcmp(atr, YKPIV_ATR_NEO_R3, atr_len)) state->model = DEVTYPE_NEOr3; else if(atr_len + 1 == sizeof(YKPIV_ATR_NEO_R3_NFC) && !memcmp(atr, YKPIV_ATR_NEO_R3_NFC, atr_len)) state->model = DEVTYPE_NEOr3; else if(atr_len + 1 == sizeof(YKPIV_ATR_YK4) && !memcmp(atr, YKPIV_ATR_YK4, atr_len)) state->model = DEVTYPE_YK4; else if(atr_len + 1 == sizeof(YKPIV_ATR_YK5_P1) && !memcmp(atr, YKPIV_ATR_YK5_P1, atr_len)) state->model = DEVTYPE_YK5; else if(atr_len + 1 == sizeof(YKPIV_ATR_YK5) && !memcmp(atr, YKPIV_ATR_YK5, atr_len)) state->model = DEVTYPE_YK5; else if(atr_len + 1 == sizeof(YKPIV_ATR_YK5_NFC) && !memcmp(atr, YKPIV_ATR_YK5_NFC, atr_len)) state->model = DEVTYPE_YK5; else state->model = DEVTYPE_UNKNOWN; } state->context = context; state->card = card; /* ** Do not select the applet here, as we need to accommodate commands that are ** sensitive to re-select (custom apdu/auth). All commands that can handle explicit ** selection already check the applet state and select accordingly anyway. ** ykpiv_verify_select is supplied for those who want to select explicitly. ** ** The applet _is_ selected by ykpiv_connect(), but is not selected when bypassing ** it with ykpiv_connect_with_external_card(). */ return YKPIV_OK; } ykpiv_rc ykpiv_connect_with_external_card(ykpiv_state *state, uintptr_t context, uintptr_t card) { return _ykpiv_connect(state, context, card); } ykpiv_rc ykpiv_validate(ykpiv_state *state, const char *wanted) { if(state->card) { DBG("Validate reader '%s'.", wanted); char reader[CB_BUF_MAX] = {0}; pcsc_word reader_len = sizeof(reader); uint8_t atr[CB_ATR_MAX] = {0}; pcsc_word atr_len = sizeof(atr); pcsc_long rc = SCardStatus(state->card, reader, &reader_len, NULL, NULL, atr, &atr_len); if(rc != SCARD_S_SUCCESS) { DBG("SCardStatus failed on reader '%s', rc=%lx", wanted, (long)rc); rc = SCardDisconnect(state->card, SCARD_RESET_CARD); if(rc != SCARD_S_SUCCESS) { DBG("SCardDisconnect failed on reader '%s', rc=%lx", wanted, (long)rc); } state->card = 0; state->serial = 0; state->ver.major = 0; state->ver.minor = 0; state->ver.patch = 0; _cache_pin(state, NULL, 0); _cache_mgm_key(state, NULL, 0); return pcsc_to_yrc(rc); } if (strcmp(wanted, reader)) { DBG("Disconnecting incorrect reader '%s' (wanted '%s'), rc=%lx", reader, wanted, (long)rc); rc = SCardDisconnect(state->card, SCARD_RESET_CARD); if(rc != SCARD_S_SUCCESS) { DBG("SCardDisconnect failed on reader '%s' (wanted '%s'), rc=%lx", reader, wanted, (long)rc); } state->card = 0; state->serial = 0; state->ver.major = 0; state->ver.minor = 0; state->ver.patch = 0; _cache_pin(state, NULL, 0); _cache_mgm_key(state, NULL, 0); return YKPIV_GENERIC_ERROR; } return YKPIV_OK; } return YKPIV_ARGUMENT_ERROR; } ykpiv_rc ykpiv_connect(ykpiv_state *state, const char *wanted) { return ykpiv_connect_ex(state, wanted, false); } ykpiv_rc ykpiv_connect_ex(ykpiv_state *state, const char *wanted, bool scp11) { char reader_buf[2048] = {0}; size_t num_readers = sizeof(reader_buf); pcsc_long rc; char *reader_ptr; ykpiv_rc ret; SCARDHANDLE card = (SCARDHANDLE)-1; if(wanted && *wanted == '@') { wanted++; // Skip the '@' DBG("Connect reader '%s'.", wanted); if(SCardIsValidContext(state->context) != SCARD_S_SUCCESS) { rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &state->context); if (rc != SCARD_S_SUCCESS) { DBG("SCardEstablishContext failed, rc=%lx", (long)rc); return pcsc_to_yrc(rc); } } rc = SCardConnect(state->context, wanted, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, &card, &state->protocol); if(rc != SCARD_S_SUCCESS) { DBG("SCardConnect failed for '%s', rc=%lx", wanted, (long)rc); SCardReleaseContext(state->context); state->context = (SCARDCONTEXT)-1; return pcsc_to_yrc(rc); } else { DBG("SCardConnect succeeded for '%s', protocol=%lx", wanted, (unsigned long)state->protocol); } strncpy(state->reader, wanted, sizeof(state->reader)); state->reader[sizeof(state->reader) - 1] = 0; } else { ret = ykpiv_list_readers(state, reader_buf, &num_readers); if(ret != YKPIV_OK) { return ret; } for(reader_ptr = reader_buf; *reader_ptr != '\0'; reader_ptr += strlen(reader_ptr) + 1) { if(wanted) { char *ptr = reader_ptr; bool found = false; do { if(strlen(ptr) < strlen(wanted)) { break; } if(strncasecmp(ptr, wanted, strlen(wanted)) == 0) { found = true; break; } } while(*ptr++); if(found == false) { DBG("Skipping reader '%s' since it doesn't match '%s'.", reader_ptr, wanted); continue; } } DBG("Connect reader '%s' matching '%s'.", reader_ptr, wanted); rc = SCardConnect(state->context, reader_ptr, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, &card, &state->protocol); if(rc == SCARD_S_SUCCESS) { strncpy(state->reader, reader_ptr, sizeof(state->reader)); state->reader[sizeof(state->reader) - 1] = 0; DBG("SCardConnect succeeded for '%s', protocol=%lx", reader_ptr, (unsigned long)state->protocol); break; } DBG("SCardConnect failed for '%s', rc=%lx", reader_ptr, (long)rc); } if(*reader_ptr == '\0') { DBG("No usable reader found matching '%s'.", wanted); SCardReleaseContext(state->context); state->context = (SCARDCONTEXT)-1; return YKPIV_PCSC_ERROR; } } // at this point, card should not equal state->card, to allow _ykpiv_connect() to determine device type if (YKPIV_OK == _ykpiv_connect(state, state->context, card)) { state->scp11_state.security_level = 0; /* * Select applet. This is done here instead of in _ykpiv_connect() because * you may not want to select the applet when connecting to a card handle that * was supplied by an external library. */ if (YKPIV_OK != (ret = _ykpiv_begin_transaction(state))) return ret; #if ENABLE_APPLICATION_RESELECTION ret = _ykpiv_ensure_application_selected(state, scp11); #else ret = _ykpiv_select_application(state, scp11); #endif _ykpiv_end_transaction(state); return ret; } return YKPIV_GENERIC_ERROR; } ykpiv_rc ykpiv_list_readers(ykpiv_state *state, char *readers, size_t *len) { pcsc_word num_readers = (pcsc_word)*len; pcsc_long rc; if(SCardIsValidContext(state->context) != SCARD_S_SUCCESS) { rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &state->context); if (rc != SCARD_S_SUCCESS) { DBG("SCardEstablishContext failed, rc=%lx", (long)rc); return pcsc_to_yrc(rc); } } rc = SCardListReaders(state->context, NULL, readers, &num_readers); if (rc != SCARD_S_SUCCESS) { DBG("SCardListReaders failed, rc=%lx", (long)rc); if(rc == SCARD_E_NO_READERS_AVAILABLE || rc == SCARD_E_SERVICE_STOPPED) { *readers = 0; *len = 1; return YKPIV_OK; } SCardReleaseContext(state->context); state->context = (SCARDCONTEXT)-1; return pcsc_to_yrc(rc); } *len = num_readers; return YKPIV_OK; } ykpiv_rc _ykpiv_begin_transaction(ykpiv_state *state) { #if ENABLE_IMPLICIT_TRANSACTIONS int retries = 0; pcsc_long rc = SCardBeginTransaction(state->card); if (rc != SCARD_S_SUCCESS) { retries++; DBG("SCardBeginTransaction on card #%u failed, rc=%lx", state->serial, (long)rc); if (SCardIsValidContext(state->context) != SCARD_S_SUCCESS || (rc != SCARD_W_RESET_CARD && rc != SCARD_W_REMOVED_CARD)) { pcsc_long rc2 = SCardDisconnect(state->card, SCARD_RESET_CARD); DBG("SCardDisconnect on card #%u rc=%lx", state->serial, (long)rc2); state->card = 0; } if (SCardIsValidContext(state->context) != SCARD_S_SUCCESS || rc == SCARD_E_NO_SERVICE) { rc = SCardReleaseContext(state->context); DBG("SCardReleaseContext on card #%u rc=%lx", state->serial, (long)rc); state->context = 0; rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &state->context); DBG("SCardEstablishContext on card #%u rc=%lx", state->serial, (long)rc); if(rc != SCARD_S_SUCCESS) { return pcsc_to_yrc(rc); } } if(state->card) { rc = SCardReconnect(state->card, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, SCARD_RESET_CARD, &state->protocol); DBG("SCardReconnect on card #%u rc=%lx", state->serial, (long)rc); if(rc != SCARD_S_SUCCESS) { return pcsc_to_yrc(rc); } } else { rc = SCardConnect(state->context, state->reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, &state->card, &state->protocol); DBG("SCardConnect on reader %s card #%u rc=%lx", state->reader, state->serial, (long)rc); if(rc != SCARD_S_SUCCESS) { return pcsc_to_yrc(rc); } } rc = SCardBeginTransaction(state->card); if (rc != SCARD_S_SUCCESS) { DBG("SCardBeginTransaction on card #%u failed, rc=%lx", state->serial, (long)rc); return pcsc_to_yrc(rc); } } if(retries) { uint32_t serial = state->serial; state->serial = 0; state->ver.major = 0; state->ver.minor = 0; state->ver.patch = 0; ykpiv_rc res; if ((res = _ykpiv_select_application(state, state->scp11_state.security_level)) != YKPIV_OK) return res; if(state->serial != serial) { DBG("Card #%u detected, was expecting card #%u", state->serial, serial); return YKPIV_GENERIC_ERROR; } if(state->mgm_key) { if((res = _ykpiv_authenticate2(state, state->mgm_key, state->mgm_len)) != YKPIV_OK) return res; } if (state->pin) { size_t pin_len = strlen(state->pin); if((res = _ykpiv_verify(state, state->pin, &pin_len, false, false)) != YKPIV_OK) return res; // De-authenticate always-authenticate keys by running an arbitrary command unsigned char data[80] = {0}; unsigned long recv_len = sizeof(data); if((res = _ykpiv_fetch_object(state, YKPIV_OBJ_DISCOVERY, data, &recv_len)) != YKPIV_OK) return res; } } #endif /* ENABLE_IMPLICIT_TRANSACTIONS */ return YKPIV_OK; } ykpiv_rc _ykpiv_end_transaction(ykpiv_state *state) { #if ENABLE_IMPLICIT_TRANSACTIONS pcsc_long rc = SCardEndTransaction(state->card, SCARD_LEAVE_CARD); if(rc != SCARD_S_SUCCESS) { DBG("SCardEndTransaction on card #%u failed, rc=%lx", state->serial, (long)rc); // Ending the transaction can only fail because it's already ended - it's ended now either way so we don't fail here } #endif /* ENABLE_IMPLICIT_TRANSACTIONS */ return YKPIV_OK; } ykpiv_rc ykpiv_translate_sw(int sw) { return ykpiv_translate_sw_ex(__FUNCTION__, sw); } ykpiv_rc ykpiv_translate_sw_ex(const char *whence, int sw) { switch(sw) { case SW_SUCCESS: DBG2("%s: SW_SUCCESS", whence); return YKPIV_OK; case SW_ERR_SECURITY_STATUS: DBG("%s: SW_ERR_SECURITY_STATUS", whence); return YKPIV_AUTHENTICATION_ERROR; case SW_ERR_AUTH_BLOCKED: DBG("%s: SW_ERR_AUTH_BLOCKED", whence); return YKPIV_PIN_LOCKED; case SW_ERR_INCORRECT_PARAM: DBG("%s: SW_ERR_INCORRECT_PARAM", whence); return YKPIV_ARGUMENT_ERROR; case SW_ERR_FILE_NOT_FOUND: DBG("%s: SW_ERR_FILE_NOT_FOUND", whence); return YKPIV_INVALID_OBJECT; case SW_ERR_REFERENCE_NOT_FOUND: DBG("%s: SW_ERR_REFERENCE_NOT_FOUND", whence); return YKPIV_KEY_ERROR; case SW_ERR_INCORRECT_SLOT: DBG("%s: SW_ERR_INCORRECT_SLOT", whence); return YKPIV_KEY_ERROR; case SW_ERR_NOT_SUPPORTED: DBG("%s: SW_ERR_NOT_SUPPORTED", whence); return YKPIV_NOT_SUPPORTED; case SW_ERR_CONDITIONS_OF_USE: DBG("%s: SW_ERR_CONDITIONS_OF_USE", whence); return YKPIV_GENERIC_ERROR; case SW_ERR_NO_INPUT_DATA: DBG("%s: SW_ERR_NO_INPUT_DATA", whence); return YKPIV_ARGUMENT_ERROR; case SW_ERR_VERIFY_FAIL_NO_RETRY: DBG("%s: SW_ERR_VERIFY_FAIL_NO_RETRY", whence); return YKPIV_AUTHENTICATION_ERROR; case SW_ERR_MEMORY_ERROR: DBG("%s: SW_ERR_MEMORY_ERROR", whence); return YKPIV_MEMORY_ERROR; case SW_ERR_WRONG_LENGTH: DBG("%s: SW_ERR_WRONG_LENGTH", whence); return YKPIV_PARSE_ERROR; case SW_ERR_DATA_INVALID: DBG("%s: SW_ERR_DATA_INVALID", whence); return YKPIV_PARSE_ERROR; case SW_ERR_COMMAND_NOT_ALLOWED: DBG("%s: SW_ERR_COMMAND_NOT_ALLOWED", whence); return YKPIV_NOT_SUPPORTED; case SW_ERR_NO_SPACE: DBG("%s: SW_ERR_NO_SPACE", whence); return YKPIV_SIZE_ERROR; case SW_ERR_CLASS_NOT_SUPPORTED: DBG("%s: SW_ERR_CLASS_NOT_SUPPORTED", whence); return YKPIV_NOT_SUPPORTED; case SW_ERR_COMMAND_ABORTED: DBG("%s: SW_ERR_COMMAND_ABORTED", whence); return YKPIV_GENERIC_ERROR; default: DBG("%s: SW_%04x", whence, sw); return YKPIV_GENERIC_ERROR; } } static const SCARD_IO_REQUEST* _pci(pcsc_word protocol) { switch (protocol) { case SCARD_PROTOCOL_T0: return SCARD_PCI_T0; case SCARD_PROTOCOL_T1: return SCARD_PCI_T1; case SCARD_PROTOCOL_RAW: return SCARD_PCI_RAW; default: return NULL; } } static ykpiv_rc _ykpiv_transmit(ykpiv_state *state, const unsigned char *send_data, pcsc_word send_len, unsigned char *recv_data, pcsc_word *recv_len, int *sw) { DBG("> @", send_data, (size_t)send_len); pcsc_long rc = SCardTransmit(state->card, _pci(state->protocol), send_data, send_len, NULL, recv_data, recv_len); if(rc != SCARD_S_SUCCESS) { DBG("SCardTransmit on card #%u failed, rc=%lx", state->serial, (long)rc); *sw = 0; return pcsc_to_yrc(rc); } DBG("< @", recv_data, (size_t)*recv_len); if(*recv_len >= 2) { *sw = (recv_data[*recv_len - 2] << 8) | recv_data[*recv_len - 1]; *recv_len -= 2; } else { *sw = 0; } return YKPIV_OK; } static ykpiv_rc scp11_prepare_transfer(ykpiv_scp11_state *state, APDU *apdu, const uint8_t *apdu_data, uint32_t apdu_data_len, size_t *apdu_len) { ykpiv_rc rc = YKPIV_OK; uint8_t enc[YKPIV_OBJ_MAX_SIZE] = {0}; uint32_t enc_len = sizeof(enc); if ((rc = scp11_encrypt_data(state->senc, state->enc_counter++, apdu_data, apdu_data_len, enc, &enc_len)) != YKPIV_OK) { DBG("Failed to perform AES ECD encryption on APDU"); return rc; } uint8_t cla = apdu->st.cla | 0x04; APDU maced_apdu = {cla, apdu->st.ins, apdu->st.p1, apdu->st.p2, 0}; maced_apdu.st.data[0] = (enc_len + SCP11_HALF_MAC_LEN) >> 8; maced_apdu.st.data[1] = (enc_len + SCP11_HALF_MAC_LEN) & 0xff; memcpy(maced_apdu.st.data + 2, enc, enc_len); uint8_t mac[SCP11_MAC_LEN] = {0}; if ((rc = scp11_mac_data(state->smac, state->mac_chain, maced_apdu.raw, 7 + enc_len, mac)) != YKPIV_OK) { DBG("Failed to calculate APDU mac value"); return rc; } apdu->st.cla = cla; apdu->st.lc = 0; apdu->st.data[0] = (enc_len + SCP11_HALF_MAC_LEN) >> 8; apdu->st.data[1] = (enc_len + SCP11_HALF_MAC_LEN) & 0xff; memcpy(apdu->st.data + 2, enc, enc_len); memcpy(apdu->st.data + 2 + enc_len, mac, SCP11_HALF_MAC_LEN); *apdu_len = enc_len + SCP11_HALF_MAC_LEN + 7; memcpy(state->mac_chain, mac, SCP11_MAC_LEN); return rc; } static ykpiv_rc scp11_decrypt_response(ykpiv_scp11_state *state, uint8_t *data, uint32_t data_len, uint8_t *dec, uint32_t *dec_len, int sw) { if (data_len == 0) { DBG("No response data to decrypt"); return YKPIV_OK; } ykpiv_rc rc = YKPIV_OK; if ((rc = scp11_unmac_data(state->srmac, state->mac_chain, data, data_len, sw)) != YKPIV_OK) { DBG("Failed to verify response MAC"); return rc; } if ((rc = scp11_decrypt_data(state->senc, state->enc_counter - 1, data, data_len - SCP11_HALF_MAC_LEN, dec, dec_len)) != YKPIV_OK) { DBG("Failed to decrypt response"); return rc; } return rc; } ykpiv_rc _ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ, const unsigned char *in_data, unsigned long in_len, unsigned char *out_data, unsigned long *out_len, int *sw) { unsigned long max_out = *out_len; *out_len = 0; do { APDU apdu = {templ[0], templ[1], templ[2], templ[3], 0xff}; unsigned char data[YKPIV_OBJ_MAX_SIZE] = {0}; ykpiv_rc res = YKPIV_OK; pcsc_word apdu_len; if (state->scp11_state.security_level) { size_t apdu_length; if((res = scp11_prepare_transfer(&state->scp11_state, &apdu, in_data, in_len, &apdu_length)) != YKPIV_OK) { return res; } in_len = 0; apdu_len = apdu_length; } else { if(in_len > 0xff) { apdu.st.cla |= 0x10; } else { apdu.st.lc = (unsigned char)in_len; } apdu_len = apdu.st.lc + 5; if(apdu.st.lc) { memcpy(apdu.st.data, in_data, apdu.st.lc); in_data += apdu.st.lc; in_len -= apdu.st.lc; // Add Le for T=1 if (state->protocol == SCARD_PROTOCOL_T1) { apdu.st.data[apdu.st.lc] = 0; apdu_len++; } } } Retry: DBG("Going to send %u bytes in this go.", apdu_len); pcsc_word recv_len = sizeof(data); if((res = _ykpiv_transmit(state, apdu.raw, apdu_len, data, &recv_len, sw)) != YKPIV_OK) { return res; } // Case 2S.3 — Process aborted; Ne not accepted, Na indicated if((*sw & 0xff00) == 0x6c00) { apdu.st.lc = *sw & 0xff; DBG3("The card indicates we must retry with Le = %u.", apdu.st.lc); goto Retry; } if (*sw != SW_SUCCESS && (*sw & 0xff00) != 0x6100) { return YKPIV_OK; } if (out_data) { if (state->scp11_state.security_level) { uint8_t dec[2048] = {0}; uint32_t dec_len = sizeof(dec); if ((res = scp11_decrypt_response(&state->scp11_state, data, recv_len, dec, &dec_len, *sw)) != YKPIV_OK) { return res; } if (*out_len + dec_len > max_out) { DBG("Output buffer to small, wanted to write %lu, max was %lu.", *out_len + dec_len, max_out); return YKPIV_SIZE_ERROR; } memcpy(out_data, dec, dec_len); out_data += dec_len; *out_len += dec_len; } else { if (*out_len + recv_len > max_out) { DBG("Output buffer to small, wanted to write %lu, max was %lu.", *out_len + recv_len, max_out); return YKPIV_SIZE_ERROR; } memcpy(out_data, data, recv_len); out_data += recv_len; *out_len += recv_len; } } } while (in_len); while((*sw & 0xff00) == 0x6100) { unsigned char apdu[] = {0, YKPIV_INS_GET_RESPONSE_APDU, 0, 0, *sw & 0xff}; unsigned char data[258] = {0}; DBG3("The card indicates there is %u bytes more data for us.", apdu[4] ? apdu[4] : 0x100); pcsc_word recv_len = sizeof(data); ykpiv_rc res = _ykpiv_transmit(state, apdu, sizeof(apdu), data, &recv_len, sw); if (res != YKPIV_OK) { return res; } else if (*sw != SW_SUCCESS && (*sw & 0xff00) != 0x6100) { return YKPIV_OK; } if (out_data) { if (state->scp11_state.security_level) { DBG("Reading response in chunks is not supported through encrypted sessions"); return YKPIV_NOT_SUPPORTED; } else { if (*out_len + recv_len > max_out) { DBG("Output buffer to small, wanted to write %lu, max was %lu.", *out_len + recv_len, max_out); return YKPIV_SIZE_ERROR; } memcpy(out_data, data, recv_len); out_data += recv_len; *out_len += recv_len; } } } return YKPIV_OK; } ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ, const unsigned char *in_data, long in_len, unsigned char *out_data, unsigned long *out_len, int *sw) { ykpiv_rc res; if ((res = _ykpiv_begin_transaction(state)) != YKPIV_OK) { *out_len = 0; return res; } res = _ykpiv_transfer_data(state, templ, in_data, in_len, out_data, out_len, sw); _ykpiv_end_transaction(state); return res; } ykpiv_rc _ykpiv_send_apdu(ykpiv_state *state, APDU *apdu, unsigned char *data, unsigned long *recv_len, int *sw) { return _ykpiv_transfer_data(state, apdu->raw, apdu->st.data, apdu->st.lc, data, recv_len, sw); } static ykpiv_rc _ykpiv_get_metadata(ykpiv_state *state, const unsigned char key, unsigned char *data, unsigned long *data_len) { ykpiv_rc res; unsigned char templ[] = {0, YKPIV_INS_GET_METADATA, 0, key}; int sw = 0; if (state == NULL || data == NULL || data_len == NULL) { return YKPIV_ARGUMENT_ERROR; } if ((res = _ykpiv_transfer_data(state, templ, NULL, 0, data, data_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); Cleanup: return res; } ykpiv_rc ykpiv_authenticate(ykpiv_state *state, unsigned const char *key) { return ykpiv_authenticate2(state, key, DES_LEN_3DES); } ykpiv_rc ykpiv_authenticate2(ykpiv_state *state, unsigned const char *key, size_t len) { ykpiv_rc res; if (NULL == state) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_authenticate2(state, key, len); Cleanup: _ykpiv_end_transaction(state); return res; } static ykpiv_rc _ykpiv_authenticate2(ykpiv_state *state, unsigned const char *key, size_t len) { if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (NULL == key) { key = (unsigned const char*)YKPIV_MGM_DEFAULT; len = DES_LEN_3DES; } ykpiv_metadata metadata = {YKPIV_ALGO_3DES}; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); ykpiv_rc res = _ykpiv_get_metadata(state, YKPIV_KEY_CARDMGM, data, &recv_len); if (res == YKPIV_OK) { res = ykpiv_util_parse_metadata(data, recv_len, &metadata); if (res != YKPIV_OK) { return res; } } /* set up our key */ aes_context mgm_key = {0}; int drc = aes_set_key(key, (uint32_t)len, metadata.algorithm, &mgm_key); if (drc) { DBG("%s: cipher_import_key: %d", ykpiv_strerror(YKPIV_ALGORITHM_ERROR), drc); res = YKPIV_ALGORITHM_ERROR; goto Cleanup; } /* get a challenge from the card */ { int sw = 0; APDU apdu = {0}; recv_len = sizeof(data); apdu.st.ins = YKPIV_INS_AUTHENTICATE; apdu.st.p1 = metadata.algorithm; apdu.st.p2 = YKPIV_KEY_CARDMGM; /* management key */ apdu.st.lc = 0x04; apdu.st.data[0] = 0x7c; apdu.st.data[1] = 0x02; apdu.st.data[2] = 0x80; apdu.st.data[3] = 0x00; if ((res = _ykpiv_send_apdu(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { goto Cleanup; } } uint8_t *challenge = data + 4; uint32_t challenge_len = recv_len - 4; uint32_t mgm_blocksize = aes_blocksize(&mgm_key); if(challenge_len != mgm_blocksize) { // Only management key block size allowed DBG("%s: management key block size is %u but received %u bytes challenge", ykpiv_strerror(YKPIV_PARSE_ERROR), mgm_blocksize, challenge_len); res = YKPIV_PARSE_ERROR; goto Cleanup; } /* send a response to the cards challenge and a challenge of our own. */ { int sw = 0; APDU apdu = {0}; apdu.st.ins = YKPIV_INS_AUTHENTICATE; apdu.st.p1 = metadata.algorithm; apdu.st.p2 = YKPIV_KEY_CARDMGM; /* management key */ unsigned char *dataptr = apdu.st.data; *dataptr++ = 0x7c; *dataptr++ = 2 + challenge_len + 2 + challenge_len; *dataptr++ = 0x80; *dataptr++ = challenge_len; uint32_t out_len = challenge_len; drc = aes_decrypt(challenge, challenge_len, dataptr, &out_len, &mgm_key); if (drc) { DBG("%s: cipher_decrypt: %d", ykpiv_strerror(YKPIV_AUTHENTICATION_ERROR), drc); res = YKPIV_AUTHENTICATION_ERROR; goto Cleanup; } dataptr += out_len; *dataptr++ = 0x81; *dataptr++ = challenge_len; challenge = dataptr; if (PRNG_OK != _ykpiv_prng_generate(challenge, challenge_len)) { DBG("%s: Failed getting randomness for authentication.", ykpiv_strerror(YKPIV_RANDOMNESS_ERROR)); res = YKPIV_RANDOMNESS_ERROR; goto Cleanup; } dataptr += challenge_len; apdu.st.lc = (unsigned char)(dataptr - apdu.st.data); recv_len = sizeof(data); if ((res = _ykpiv_send_apdu(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { goto Cleanup; } /* compare the response from the card with our challenge */ out_len = challenge_len; drc = aes_encrypt(challenge, challenge_len, challenge, &out_len, &mgm_key); if (drc) { DBG("%s: cipher_encrypt: %d", ykpiv_strerror(YKPIV_AUTHENTICATION_ERROR), drc); res = YKPIV_AUTHENTICATION_ERROR; goto Cleanup; } if (memcmp(data + 4, challenge, challenge_len) == 0) { _cache_mgm_key(state, key, len); res = YKPIV_OK; } else { res = YKPIV_AUTHENTICATION_ERROR; } } Cleanup: aes_destroy(&mgm_key); return res; } ykpiv_rc ykpiv_set_mgmkey(ykpiv_state *state, const unsigned char *new_key) { return ykpiv_set_mgmkey2(state, new_key, YKPIV_TOUCHPOLICY_DEFAULT); } ykpiv_rc ykpiv_set_mgmkey2(ykpiv_state *state, const unsigned char *new_key, const unsigned char touch) { return ykpiv_set_mgmkey3(state, new_key, DES_LEN_3DES, YKPIV_ALGO_3DES, touch); } ykpiv_rc ykpiv_set_mgmkey3(ykpiv_state *state, const unsigned char *new_key, size_t len, unsigned char algo, unsigned char touch) { unsigned char data[256] = {0}; ykpiv_rc res = YKPIV_OK; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; if(algo == YKPIV_ALGO_AUTO || touch == YKPIV_TOUCHPOLICY_AUTO) { ykpiv_metadata metadata = {YKPIV_ALGO_3DES}; unsigned long data_len = sizeof(data); res = _ykpiv_get_metadata(state, YKPIV_KEY_CARDMGM, data, &data_len); if (res == YKPIV_OK) { res = ykpiv_util_parse_metadata(data, data_len, &metadata); if (res != YKPIV_OK) { goto Cleanup; } } if(algo == YKPIV_ALGO_AUTO) { algo = metadata.algorithm; } if(touch == YKPIV_TOUCHPOLICY_AUTO) { touch = metadata.touch_policy; } } if (algo == YKPIV_ALGO_3DES && yk_des_is_weak_key(new_key, len)) { DBG("Wont set new key since it's weak (or has odd parity) @", new_key, len); res = YKPIV_KEY_ERROR; goto Cleanup; } APDU apdu = {0}; apdu.st.ins = YKPIV_INS_SET_MGMKEY; apdu.st.p1 = 0xff; if (touch <= YKPIV_TOUCHPOLICY_NEVER) { apdu.st.p2 = 0xff; } else if(touch == YKPIV_TOUCHPOLICY_ALWAYS) { apdu.st.p2 = 0xfe; } else { DBG("Invalid touch policy for card management key (slot %02x).", YKPIV_KEY_CARDMGM); res = YKPIV_GENERIC_ERROR; goto Cleanup; } apdu.st.lc = (unsigned char)(len + 3); apdu.st.data[0] = algo; apdu.st.data[1] = YKPIV_KEY_CARDMGM; apdu.st.data[2] = (unsigned char)len; memcpy(apdu.st.data + 3, new_key, len); int sw = 0; unsigned long recv_len = sizeof(data); if ((res = _ykpiv_send_apdu(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res == YKPIV_OK) { _cache_mgm_key(state, new_key, len); goto Cleanup; } Cleanup: yc_memzero(&apdu, sizeof(APDU)); _ykpiv_end_transaction(state); return res; } static char hex_translate[] = "0123456789abcdef"; ykpiv_rc ykpiv_hex_decode(const char *hex_in, size_t in_len, unsigned char *hex_out, size_t *out_len) { size_t i; bool first = true; if(*out_len < in_len / 2) { return YKPIV_SIZE_ERROR; } else if(in_len % 2 != 0) { return YKPIV_SIZE_ERROR; } *out_len = in_len / 2; for(i = 0; i < in_len; i++) { char *ind_ptr = strchr(hex_translate, tolower(*hex_in++)); int index = 0; if(ind_ptr) { index = (int)(ind_ptr - hex_translate); } else { return YKPIV_PARSE_ERROR; } if(first) { *hex_out = index << 4; } else { *hex_out++ |= index; } first = !first; } return YKPIV_OK; } static ykpiv_rc _general_authenticate(ykpiv_state *state, const unsigned char *sign_in, size_t in_len, unsigned char *out, size_t *out_len, unsigned char algorithm, unsigned char key, bool decipher) { unsigned char indata[YKPIV_OBJ_MAX_SIZE] = {0}; unsigned char *dataptr = indata; unsigned char data[YKPIV_OBJ_MAX_SIZE] = {0}; unsigned char templ[] = {0, YKPIV_INS_AUTHENTICATE, algorithm, key}; unsigned long recv_len = sizeof(data); size_t key_len = 0; int sw = 0; size_t bytes, offs; size_t len = 0; ykpiv_rc res; switch(algorithm) { case YKPIV_ALGO_RSA1024: key_len = 128; // fall through case YKPIV_ALGO_RSA2048: if(key_len == 0) { key_len = 256; } case YKPIV_ALGO_RSA3072: if(key_len == 0) { key_len = 384; } case YKPIV_ALGO_RSA4096: if(key_len == 0) { key_len = 512; } if(in_len != key_len) { return YKPIV_SIZE_ERROR; } break; case YKPIV_ALGO_ECCP256: key_len = 32; // fall through case YKPIV_ALGO_ECCP384: if(key_len == 0) { key_len = 48; } if(!decipher && in_len > key_len) { DBG("Data to sign truncated to EC key length (%zu bytes)", key_len); in_len = key_len; } else if(decipher && in_len != (key_len * 2) + 1) { return YKPIV_SIZE_ERROR; } break; case YKPIV_ALGO_X25519: if(!decipher) { DBG("Signing with x25519 keys is not supported"); return YKPIV_NOT_SUPPORTED; } if(in_len != 32) { return YKPIV_SIZE_ERROR; } break; case YKPIV_ALGO_ED25519: if(decipher) { DBG("Deciphering with ed25519 keys is not supported"); return YKPIV_NOT_SUPPORTED; } break; default: return YKPIV_ALGORITHM_ERROR; } bytes = _ykpiv_get_length_size(in_len); *dataptr++ = 0x7c; dataptr += _ykpiv_set_length(dataptr, in_len + bytes + 3); *dataptr++ = 0x82; *dataptr++ = 0x00; *dataptr++ = !YKPIV_IS_RSA(algorithm) && decipher ? 0x85 : 0x81; dataptr += _ykpiv_set_length(dataptr, in_len); if(dataptr - indata + in_len > sizeof(indata)) { return YKPIV_SIZE_ERROR; } memcpy(dataptr, sign_in, in_len); dataptr += in_len; if((res = _ykpiv_transfer_data(state, templ, indata, (unsigned long)(dataptr - indata), data, &recv_len, &sw)) != YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if(res != YKPIV_OK) { DBG("Sign command failed"); return res; } /* skip the first 7c tag */ if(data[0] != 0x7c) { DBG("Failed parsing signature reply."); return YKPIV_PARSE_ERROR; } dataptr = data + 1; offs = _ykpiv_get_length(dataptr, data + recv_len, &len); dataptr += offs; /* skip the 82 tag */ if(!offs || *dataptr != 0x82) { DBG("Failed parsing signature reply."); return YKPIV_PARSE_ERROR; } dataptr++; offs = _ykpiv_get_length(dataptr, data + recv_len, &len); dataptr += offs; if(!offs || len > *out_len) { DBG("Wrong size on output buffer."); return YKPIV_PARSE_ERROR; } *out_len = len; memcpy(out, dataptr, len); return YKPIV_OK; } ykpiv_rc ykpiv_sign_data(ykpiv_state *state, const unsigned char *raw_in, size_t in_len, unsigned char *sign_out, size_t *out_len, unsigned char algorithm, unsigned char key) { ykpiv_rc res = YKPIV_OK; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; /* don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS */ /*if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup;*/ res = _general_authenticate(state, raw_in, in_len, sign_out, out_len, algorithm, key, false); /* Cleanup: */ _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_decipher_data(ykpiv_state *state, const unsigned char *in, size_t in_len, unsigned char *out, size_t *out_len, unsigned char algorithm, unsigned char key) { ykpiv_rc res = YKPIV_OK; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; /* don't attempt to reselect in crypt operations to avoid problems with PIN_ALWAYS */ /*if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state))) goto Cleanup;*/ res = _general_authenticate(state, in, in_len, out, out_len, algorithm, key, true); /* Cleanup: */ _ykpiv_end_transaction(state); return res; } static ykpiv_rc _ykpiv_get_version(ykpiv_state *state) { unsigned char templ[] = {0x00, YKPIV_INS_GET_VERSION, 0x00, 0x00}; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); int sw = 0; ykpiv_rc res; if (!state) { return YKPIV_ARGUMENT_ERROR; } /* get version from state if already retrieved from device */ if (state->ver.major || state->ver.minor || state->ver.patch) { return YKPIV_OK; } /* get version from device */ if((res = _ykpiv_transfer_data(state, templ, NULL, 0, data, &recv_len, &sw)) != YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if(res == YKPIV_OK) { /* check that we received enough data for the verson number */ if (recv_len < 3) { return YKPIV_SIZE_ERROR; } state->ver.major = data[0]; state->ver.minor = data[1]; state->ver.patch = data[2]; } return res; } ykpiv_rc ykpiv_get_version(ykpiv_state *state, char *version, size_t len) { ykpiv_rc res; uint8_t scp11 = state->scp11_state.security_level; if ((res = _ykpiv_begin_transaction(state)) < YKPIV_OK) return res; if ((res = _ykpiv_ensure_application_selected(state, scp11)) < YKPIV_OK) goto Cleanup; if ((res = _ykpiv_get_version(state)) >= YKPIV_OK) { int result = snprintf(version, len, "%d.%d.%d", state->ver.major, state->ver.minor, state->ver.patch); if(result <= 0 || result >= (int)len) { res = YKPIV_SIZE_ERROR; } } Cleanup: _ykpiv_end_transaction(state); return res; } /* caller must make sure that this is wrapped in a transaction for synchronized operation */ static ykpiv_rc _ykpiv_get_serial(ykpiv_state *state) { ykpiv_rc res = YKPIV_OK; uint8_t select_templ[] = {0x00, YKPIV_INS_SELECT_APPLICATION, 0x04, 0x00}; uint8_t data[256] = {0}; unsigned long recv_len = sizeof(data); int sw = 0; if (!state) { return YKPIV_ARGUMENT_ERROR; } /* get serial from state if already retrieved from device */ if (state->serial != 0) { return YKPIV_OK; } if (state->ver.major > 0 && state->ver.major < 5) { /* get serial from neo/yk4 devices using the otp applet */ uint8_t temp[256] = {0}; recv_len = sizeof(temp); if ((res = _ykpiv_transfer_data(state, select_templ, yk_aid, sizeof(yk_aid), temp, &recv_len, &sw)) < YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { DBG("Failed selecting yk application"); goto Cleanup; } uint8_t yk_get_serial_templ[] = {0x00, 0x01, 0x10, 0x00}; recv_len = sizeof(data); if ((res = _ykpiv_transfer_data(state, yk_get_serial_templ, NULL, 0, data, &recv_len, &sw)) < YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { DBG("Failed retrieving serial number"); goto Cleanup; } recv_len = sizeof(temp); if((res = _ykpiv_transfer_data(state, select_templ, piv_aid, sizeof(piv_aid), temp, &recv_len, &sw)) < YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if(res != YKPIV_OK) { DBG("Failed selecting piv application"); } } else { /* get serial from yk5 and later devices using the YKPIV_INS_GET_SERIAL command */ uint8_t yk5_get_serial_templ[] = {0x00, YKPIV_INS_GET_SERIAL, 0x00, 0x00}; if ((res = _ykpiv_transfer_data(state, yk5_get_serial_templ, NULL, 0, data, &recv_len, &sw)) != YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if(res != YKPIV_OK) { DBG("Failed retrieving serial number"); } } /* check that we received enough data for the serial number */ if (recv_len < 4) { return YKPIV_SIZE_ERROR; } state->serial = data[0]; state->serial <<= 8; state->serial += data[1]; state->serial <<= 8; state->serial += data[2]; state->serial <<= 8; state->serial += data[3]; Cleanup: return res; } ykpiv_rc ykpiv_get_serial(ykpiv_state *state, uint32_t *p_serial) { ykpiv_rc res = YKPIV_OK; if (!state || !p_serial) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if ((res = _ykpiv_begin_transaction(state)) != YKPIV_OK) return res; if ((res = _ykpiv_ensure_application_selected(state, scp11)) != YKPIV_OK) goto Cleanup; res = _ykpiv_get_serial(state); *p_serial = state->serial; Cleanup: _ykpiv_end_transaction(state); return res; } static ykpiv_rc _cache_pin(ykpiv_state *state, const char *pin, size_t len) { #if DISABLE_PIN_CACHE // Some embedded applications of this library may not want to keep the PIN // data in RAM for security reasons. return YKPIV_OK; #else if (!state) return YKPIV_ARGUMENT_ERROR; if (pin && state->pin == pin) { return YKPIV_OK; } if (state->pin) { yc_memzero(state->pin, strnlen(state->pin, CB_PIN_MAX)); _ykpiv_free(state, state->pin); state->pin = NULL; } if (pin && len > 0) { state->pin = _ykpiv_alloc(state, len * sizeof(char) + 1); if (state->pin == NULL) { return YKPIV_MEMORY_ERROR; } memcpy(state->pin, pin, len); state->pin[len] = 0; } return YKPIV_OK; #endif } static ykpiv_rc _cache_mgm_key(ykpiv_state *state, unsigned const char *key, size_t len) { #if DISABLE_MGM_KEY_CACHE // Some embedded applications of this library may not want to keep the MGM_KEY // data in RAM for security reasons. return YKPIV_OK; #else if (!state) return YKPIV_ARGUMENT_ERROR; if (key && state->mgm_key == key) { return YKPIV_OK; } if (state->mgm_key) { yc_memzero(state->mgm_key, state->mgm_len); _ykpiv_free(state, state->mgm_key); state->mgm_key = NULL; state->mgm_len = 0; } if (key) { state->mgm_key = _ykpiv_alloc(state, len); if (state->mgm_key == NULL) { return YKPIV_MEMORY_ERROR; } memcpy(state->mgm_key, key, len); state->mgm_len = (uint32_t)len; } return YKPIV_OK; #endif } static ykpiv_rc _verify_pin_apdu(char *pin, size_t *p_pin_len, bool verify_spin, APDU *apdu) { if (p_pin_len && (*p_pin_len > CB_PIN_MAX)) { return YKPIV_SIZE_ERROR; } apdu->st.ins = YKPIV_INS_VERIFY; apdu->st.p1 = 0x00; apdu->st.p2 = 0x80; apdu->st.lc = pin ? 0x08 : 0x00; if (pin) { if (p_pin_len && (*p_pin_len > 0)) { memcpy(apdu->st.data, pin, *p_pin_len); if (*p_pin_len < CB_PIN_MAX) { memset(apdu->st.data + *p_pin_len, 0xff, CB_PIN_MAX - *p_pin_len); } } else if (verify_spin && p_pin_len) { apdu->st.data[0] = 0x01; apdu->st.data[1] = (uint8_t)*p_pin_len; memcpy(apdu->st.data + 2, pin, *p_pin_len); } } return YKPIV_OK; } static ykpiv_rc _verify_bio_apdu(char *pin, size_t *p_pin_len, bool verify_spin, APDU *apdu) { if (verify_spin && (!pin || !p_pin_len || *p_pin_len != 16)) { return YKPIV_WRONG_PIN; } apdu->st.ins = YKPIV_INS_VERIFY; apdu->st.p1 = 0x00; apdu->st.p2 = 0x96; apdu->st.lc = verify_spin ? (uint8_t)(*p_pin_len + 2) : 0x02; if (pin) { if (verify_spin && p_pin_len) { apdu->st.data[0] = 0x01; apdu->st.data[1] = (uint8_t) *p_pin_len; memcpy(apdu->st.data + 2, pin, *p_pin_len); } else { memcpy(apdu->st.data, "\x02\x00", 2); } } else { if (verify_spin) { apdu->st.lc = 0; } else { memcpy(apdu->st.data, "\x03\x00", 2); } } return YKPIV_OK; } static ykpiv_rc _ykpiv_verify(ykpiv_state *state, char *pin, size_t *p_pin_len, bool bio, bool verify_spin) { if (!bio && p_pin_len && (*p_pin_len > CB_PIN_MAX)) { return YKPIV_SIZE_ERROR; } if (bio && verify_spin && (!pin || !p_pin_len || *p_pin_len != 16)) { return YKPIV_WRONG_PIN; } APDU apdu = {0}; if(bio) { _verify_bio_apdu(pin, p_pin_len, verify_spin, &apdu); } else { _verify_pin_apdu(pin, p_pin_len, verify_spin, &apdu); } int sw = 0; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); ykpiv_rc res = _ykpiv_send_apdu(state, &apdu, data, &recv_len, &sw); yc_memzero(&apdu, sizeof(apdu)); if (res != YKPIV_OK) { state->tries = -1; return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res == YKPIV_OK) { if (!bio && pin && p_pin_len) { // Intentionally ignore errors. If the PIN fails to save, it will only // be a problem if a reconnect is attempted. Failure deferred until then. _cache_pin(state, pin, *p_pin_len); } else if (bio && !verify_spin && pin && p_pin_len && *p_pin_len >= 16 && (recv_len >= 16)) { memcpy(pin, data, 16); *p_pin_len = 16; } state->tries = -1; return YKPIV_OK; } else { if (bio && !verify_spin && p_pin_len) { *p_pin_len = 0; } if ((sw >> 8) == 0x63) { if (pin) _cache_pin(state, NULL, 0); state->tries = (sw & 0xf); return YKPIV_WRONG_PIN; } else if (sw == SW_ERR_AUTH_BLOCKED) { if (pin) _cache_pin(state, NULL, 0); state->tries = 0; return YKPIV_PIN_LOCKED; } else { state->tries = -1; return res; } } } static ykpiv_rc _ykpiv_verify_select(ykpiv_state *state, char *pin, size_t* p_pin_len, int *tries, bool force_select, bool bio, bool verify_spin) { ykpiv_rc res = YKPIV_OK; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) { return res; } if (force_select) { if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) { goto Cleanup; } } res = _ykpiv_verify(state, pin, p_pin_len, bio, verify_spin); if(tries) *tries = state->tries; Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_verify(ykpiv_state *state, const char *pin, int *tries) { size_t pin_len = pin ? strlen(pin) : 0; return _ykpiv_verify_select(state, (char*)pin, &pin_len, tries, false, false, false); } ykpiv_rc ykpiv_verify_bio(ykpiv_state *state, uint8_t *spin, size_t *p_spin_len, int *tries, bool verify_spin) { return _ykpiv_verify_select(state, spin, p_spin_len, tries, false, true, verify_spin); } ykpiv_rc ykpiv_verify_select(ykpiv_state *state, const char *pin, const size_t pin_len, int *tries, bool force_select) { size_t temp_pin_len = pin_len; return _ykpiv_verify_select(state, (char*)pin, &temp_pin_len, tries, force_select, false, false); } ykpiv_rc ykpiv_get_pin_retries(ykpiv_state *state, int *tries) { ykpiv_rc res; if (NULL == state || NULL == tries) { return YKPIV_ARGUMENT_ERROR; } // Just get the stored value if we get the magic flag if(*tries == YKPIV_RETRIES_MAX) { *tries = state->tries; return YKPIV_OK; } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; // Force a re-select to unverify, because once verified the spec dictates that // subsequent verify calls will return a "verification not needed" instead of // the number of tries left... res = _ykpiv_auth_deauthenticate(state); if (res != YKPIV_OK) goto Cleanup; res = _ykpiv_select_application(state, state->scp11_state.security_level); if (res != YKPIV_OK) goto Cleanup; *tries = state->tries; Cleanup: _ykpiv_end_transaction(state); return (res == YKPIV_WRONG_PIN || res == YKPIV_PIN_LOCKED) ? YKPIV_OK : res; } ykpiv_rc ykpiv_set_pin_retries(ykpiv_state *state, int pin_tries, int puk_tries) { ykpiv_rc res = YKPIV_OK; unsigned char templ[] = {0, YKPIV_INS_SET_PIN_RETRIES, 0, 0}; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); int sw = 0; // Special case: if either retry count is 0, it's a successful no-op if (pin_tries == 0 || puk_tries == 0) { return YKPIV_OK; } if (pin_tries > 0xff || puk_tries > 0xff || pin_tries < 1 || puk_tries < 1) { return YKPIV_RANGE_ERROR; } templ[2] = (unsigned char)pin_tries; templ[3] = (unsigned char)puk_tries; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_transfer_data(state, templ, NULL, 0, data, &recv_len, &sw); if (res == YKPIV_OK) { res = ykpiv_translate_sw_ex(__FUNCTION__, sw); } Cleanup: _ykpiv_end_transaction(state); return res; } static ykpiv_rc _ykpiv_change_pin(ykpiv_state *state, int action, const char * current_pin, size_t current_pin_len, const char * new_pin, size_t new_pin_len, int *tries) { int sw = 0; unsigned char templ[] = {0, YKPIV_INS_CHANGE_REFERENCE, 0, 0x80}; unsigned char indata[0x10] = {0}; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); ykpiv_rc res; if (current_pin_len > CB_PIN_MAX) { return YKPIV_SIZE_ERROR; } if (new_pin_len > CB_PIN_MAX) { return YKPIV_SIZE_ERROR; } if(action == CHREF_ACT_UNBLOCK_PIN) { templ[1] = YKPIV_INS_RESET_RETRY; } else if(action == CHREF_ACT_CHANGE_PUK) { templ[3] = 0x81; } memcpy(indata, current_pin, current_pin_len); if(current_pin_len < CB_PIN_MAX) { memset(indata + current_pin_len, 0xff, CB_PIN_MAX - current_pin_len); } memcpy(indata + CB_PIN_MAX, new_pin, new_pin_len); if(new_pin_len < CB_PIN_MAX) { memset(indata + CB_PIN_MAX + new_pin_len, 0xff, CB_PIN_MAX - new_pin_len); } res = _ykpiv_transfer_data(state, templ, indata, sizeof(indata), data, &recv_len, &sw); yc_memzero(indata, sizeof(indata)); if(res != YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if(res != YKPIV_OK) { if((sw >> 8) == 0x63) { if (tries) *tries = sw & 0xf; return YKPIV_WRONG_PIN; } else { DBG("Failed changing pin"); } } return res; } ykpiv_rc ykpiv_change_pin(ykpiv_state *state, const char * current_pin, size_t current_pin_len, const char * new_pin, size_t new_pin_len, int *tries) { ykpiv_rc res = YKPIV_GENERIC_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_change_pin(state, CHREF_ACT_CHANGE_PIN, current_pin, current_pin_len, new_pin, new_pin_len, tries); if (res == YKPIV_OK && new_pin != NULL) { // Intentionally ignore errors. If the PIN fails to save, it will only // be a problem if a reconnect is attempted. Failure deferred until then. _cache_pin(state, new_pin, new_pin_len); } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_change_puk(ykpiv_state *state, const char * current_puk, size_t current_puk_len, const char * new_puk, size_t new_puk_len, int *tries) { ykpiv_rc res = YKPIV_GENERIC_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_change_pin(state, CHREF_ACT_CHANGE_PUK, current_puk, current_puk_len, new_puk, new_puk_len, tries); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_unblock_pin(ykpiv_state *state, const char * puk, size_t puk_len, const char * new_pin, size_t new_pin_len, int *tries) { ykpiv_rc res = YKPIV_GENERIC_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_change_pin(state, CHREF_ACT_UNBLOCK_PIN, puk, puk_len, new_pin, new_pin_len, tries); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_fetch_object(ykpiv_state *state, int object_id, unsigned char *data, unsigned long *len) { ykpiv_rc res; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_fetch_object(state, object_id, data, len); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc _ykpiv_fetch_object(ykpiv_state *state, int object_id, unsigned char *data, unsigned long *len) { int sw = 0; unsigned char indata[5] = {0}; unsigned char *inptr = indata; unsigned char templ[] = {0, YKPIV_INS_GET_DATA, 0x3f, 0xff}; ykpiv_rc res; inptr = set_object(object_id, inptr); if(inptr == NULL) { return YKPIV_INVALID_OBJECT; } if((res = _ykpiv_transfer_data(state, templ, indata, (unsigned long)(inptr - indata), data, len, &sw)) != YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if(res == YKPIV_OK) { size_t outlen = 0; size_t offs = _ykpiv_get_length(data + 1, data + *len, &outlen); if(!offs) { return YKPIV_PARSE_ERROR; } if(outlen + offs + 1 != *len) { DBG("Invalid length indicated in object, total objlen is %lu, indicated length is %lu.", *len, (unsigned long)outlen); return YKPIV_SIZE_ERROR; } memmove(data, data + 1 + offs, outlen); *len = (unsigned long)outlen; } else { DBG("Failed to get data for object %x", object_id); } return res; } ykpiv_rc ykpiv_save_object(ykpiv_state *state, int object_id, unsigned char *indata, size_t len) { ykpiv_rc res; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_save_object(state, object_id, indata, len); Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc _ykpiv_save_object( ykpiv_state *state, int object_id, unsigned char *indata, size_t len) { unsigned char data[CB_BUF_MAX] = {0}; unsigned char *dataptr = data; unsigned char templ[] = {0, YKPIV_INS_PUT_DATA, 0x3f, 0xff}; int sw = 0; ykpiv_rc res; unsigned long outlen = 0; dataptr = set_object(object_id, dataptr); if(dataptr == NULL) { return YKPIV_INVALID_OBJECT; } *dataptr++ = 0x53; dataptr += _ykpiv_set_length(dataptr, len); if(dataptr + len > data + sizeof(data)) { return YKPIV_SIZE_ERROR; } if(indata) memcpy(dataptr, indata, len); dataptr += len; if((res = _ykpiv_transfer_data(state, templ, data, (unsigned long)(dataptr - data), NULL, &outlen, &sw)) != YKPIV_OK) { return res; } return ykpiv_translate_sw_ex(__FUNCTION__, sw); } ykpiv_rc ykpiv_import_private_key(ykpiv_state *state, const unsigned char key, unsigned char algorithm, const unsigned char *p, size_t p_len, const unsigned char *q, size_t q_len, const unsigned char *dp, size_t dp_len, const unsigned char *dq, size_t dq_len, const unsigned char *qinv, size_t qinv_len, const unsigned char *ec_data, unsigned char ec_data_len, const unsigned char pin_policy, const unsigned char touch_policy) { unsigned char key_data[2048] = {0}; unsigned char *in_ptr = key_data; unsigned char templ[] = {0, YKPIV_INS_IMPORT_KEY, algorithm, key}; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); size_t elem_len = 0; int sw = 0; const unsigned char *params[5] = {0}; size_t lens[5] = {0}; unsigned char n_params; unsigned char param_tag; ykpiv_rc res; if (state == NULL) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (key == YKPIV_KEY_CARDMGM || key < YKPIV_KEY_RETIRED1 || (key > YKPIV_KEY_RETIRED20 && key < YKPIV_KEY_AUTHENTICATION) || (key > YKPIV_KEY_CARDAUTH && key != YKPIV_KEY_ATTESTATION)) { return YKPIV_KEY_ERROR; } if (pin_policy != YKPIV_PINPOLICY_DEFAULT && pin_policy != YKPIV_PINPOLICY_NEVER && pin_policy != YKPIV_PINPOLICY_ONCE && pin_policy != YKPIV_PINPOLICY_ALWAYS && pin_policy != YKPIV_PINPOLICY_MATCH_ONCE && pin_policy != YKPIV_PINPOLICY_MATCH_ALWAYS) return YKPIV_GENERIC_ERROR; if (touch_policy != YKPIV_TOUCHPOLICY_DEFAULT && touch_policy != YKPIV_TOUCHPOLICY_NEVER && touch_policy != YKPIV_TOUCHPOLICY_ALWAYS && touch_policy != YKPIV_TOUCHPOLICY_CACHED) return YKPIV_GENERIC_ERROR; if (YKPIV_IS_RSA(algorithm)) { if ((algorithm == YKPIV_ALGO_RSA3072 || algorithm == YKPIV_ALGO_RSA4096) && !is_version_compatible(state, 5, 7, 0)) { DBG("RSA3072 and RSA4096 keys are only supported in YubiKey version 5.7.0 and above"); return YKPIV_NOT_SUPPORTED; } switch (algorithm) { case YKPIV_ALGO_RSA1024: elem_len = 64; break; case YKPIV_ALGO_RSA2048: elem_len = 128; break; case YKPIV_ALGO_RSA3072: elem_len = 192; break; case YKPIV_ALGO_RSA4096: elem_len = 256; break; } params[0] = p; lens[0] = p_len; params[1] = q; lens[1] = q_len; params[2] = dp; lens[2] = dp_len; params[3] = dq; lens[3] = dq_len; params[4] = qinv; lens[4] = qinv_len; param_tag = 0x01; n_params = 5; } else if (YKPIV_IS_EC(algorithm)) { if (algorithm == YKPIV_ALGO_ECCP256) elem_len = 32; if (algorithm == YKPIV_ALGO_ECCP384) elem_len = 48; params[0] = ec_data; lens[0] = ec_data_len; param_tag = 0x06; n_params = 1; } else if (YKPIV_IS_25519(algorithm)) { elem_len = 32; params[0] = ec_data; lens[0] = ec_data_len; if (algorithm == YKPIV_ALGO_ED25519) { param_tag = 0x07; } else { param_tag = 0x08; } n_params = 1; } else { return YKPIV_ALGORITHM_ERROR; } for (int i = 0; i < n_params; i++) { if(params[i] == NULL || lens[i] > elem_len) { res = YKPIV_ARGUMENT_ERROR; goto Cleanup; } size_t padding = elem_len - lens[i]; *in_ptr++ = param_tag + i; in_ptr += _ykpiv_set_length(in_ptr, elem_len + padding); memset(in_ptr, 0, padding); in_ptr += padding; memcpy(in_ptr, params[i], lens[i]); in_ptr += lens[i]; } if (pin_policy != YKPIV_PINPOLICY_DEFAULT) { *in_ptr++ = YKPIV_PINPOLICY_TAG; *in_ptr++ = 0x01; *in_ptr++ = pin_policy; } if (touch_policy != YKPIV_TOUCHPOLICY_DEFAULT) { *in_ptr++ = YKPIV_TOUCHPOLICY_TAG; *in_ptr++ = 0x01; *in_ptr++ = touch_policy; } if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; if ((res = _ykpiv_transfer_data(state, templ, key_data, (unsigned long)(in_ptr - key_data), data, &recv_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { goto Cleanup; } Cleanup: yc_memzero(key_data, sizeof(key_data)); _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_attest(ykpiv_state *state, const unsigned char key, unsigned char *data, size_t *data_len) { ykpiv_rc res; unsigned char templ[] = {0, YKPIV_INS_ATTEST, key, 0}; int sw = 0; unsigned long ul_data_len; if (state == NULL || data == NULL || data_len == NULL) { return YKPIV_ARGUMENT_ERROR; } uint8_t scp11 = state->scp11_state.security_level; ul_data_len = (unsigned long)*data_len; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; if ((res = _ykpiv_transfer_data(state, templ, NULL, 0, data, &ul_data_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { goto Cleanup; } if (data[0] != 0x30) { res = YKPIV_GENERIC_ERROR; goto Cleanup; } *data_len = (size_t)ul_data_len; Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_get_metadata(ykpiv_state *state, const unsigned char key, unsigned char *data, size_t *data_len) { ykpiv_rc res; unsigned long ul_data_len; if (state == NULL || data == NULL || data_len == NULL) { return YKPIV_ARGUMENT_ERROR; } uint8_t scp11 = state->scp11_state.security_level; ul_data_len = (unsigned long)*data_len; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; res = _ykpiv_get_metadata(state, key, data, &ul_data_len); Cleanup: *data_len = ul_data_len; _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_auth_getchallenge(ykpiv_state *state, ykpiv_metadata *metadata, uint8_t *challenge, unsigned long *challenge_len) { ykpiv_rc res; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (NULL == metadata) return YKPIV_ARGUMENT_ERROR; if (NULL == challenge) return YKPIV_ARGUMENT_ERROR; if (NULL == challenge_len) return YKPIV_ARGUMENT_ERROR; uint8_t scp11 = state->scp11_state.security_level; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; if (YKPIV_OK != (res = _ykpiv_ensure_application_selected(state, scp11))) goto Cleanup; metadata->algorithm = YKPIV_ALGO_3DES; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); res = _ykpiv_get_metadata(state, YKPIV_KEY_CARDMGM, data, &recv_len); if (res == YKPIV_OK) { res = ykpiv_util_parse_metadata(data, recv_len, metadata); if (res != YKPIV_OK) { goto Cleanup; } } /* get a challenge from the card */ APDU apdu = {0}; apdu.st.ins = YKPIV_INS_AUTHENTICATE; apdu.st.p1 = metadata->algorithm; apdu.st.p2 = YKPIV_KEY_CARDMGM; /* management key */ apdu.st.lc = 0x04; apdu.st.data[0] = 0x7c; apdu.st.data[1] = 0x02; apdu.st.data[2] = 0x81; //0x80; apdu.st.data[3] = 0x00; int sw = 0; recv_len = sizeof(data); if ((res = _ykpiv_send_apdu(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { goto Cleanup; } if(*challenge_len >= recv_len - 4) { *challenge_len = recv_len - 4; memcpy(challenge, data + 4, *challenge_len); } else { *challenge_len = recv_len - 4; res = YKPIV_SIZE_ERROR; } Cleanup: _ykpiv_end_transaction(state); return res; } ykpiv_rc ykpiv_auth_verifyresponse(ykpiv_state *state, ykpiv_metadata *metadata, uint8_t *response, unsigned long response_len) { ykpiv_rc res; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (NULL == metadata) return YKPIV_ARGUMENT_ERROR; if (NULL == response) return YKPIV_ARGUMENT_ERROR; if (16 < response_len) return YKPIV_ARGUMENT_ERROR; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; /* note: do not select the applet here, as it resets the challenge state */ unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); /* send the response to the card. */ APDU apdu = {0}; apdu.st.ins = YKPIV_INS_AUTHENTICATE; apdu.st.p1 = metadata->algorithm; apdu.st.p2 = YKPIV_KEY_CARDMGM; /* management key */ unsigned char *dataptr = apdu.st.data; *dataptr++ = 0x7c; *dataptr++ = (unsigned char)(2 + response_len); *dataptr++ = 0x82; *dataptr++ = (unsigned char)response_len; memcpy(dataptr, response, response_len); dataptr += response_len; apdu.st.lc = (unsigned char)(dataptr - apdu.st.data); int sw = 0; if ((res = _ykpiv_send_apdu(state, &apdu, data, &recv_len, &sw)) != YKPIV_OK) { goto Cleanup; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { goto Cleanup; } Cleanup: yc_memzero(&apdu, sizeof(apdu)); _ykpiv_end_transaction(state); return res; } /* deauthenticates the user pin and mgm key */ ykpiv_rc ykpiv_auth_deauthenticate(ykpiv_state *state) { ykpiv_rc res = YKPIV_OK; if (NULL == state) return YKPIV_ARGUMENT_ERROR; if (YKPIV_OK != (res = _ykpiv_begin_transaction(state))) return res; res = _ykpiv_auth_deauthenticate(state); _ykpiv_end_transaction(state); return res; } /* deauthenticates the user pin and mgm key */ static ykpiv_rc _ykpiv_auth_deauthenticate(ykpiv_state *state) { ykpiv_rc res = YKPIV_OK; unsigned char templ[] = {0x00, YKPIV_INS_SELECT_APPLICATION, 0x04, 0x00}; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); const unsigned char *aid; unsigned long aid_len; int sw = 0; if (!state) { return YKPIV_ARGUMENT_ERROR; } // Once mgmt_aid is selected on NEO we can't select piv_aid again... So we use yk_aid. // But... YK 5 below 5.3 doesn't allow access to yk_aid, so still use mgmt_aid on non-NEO devices if (state->ver.major < 4 && state->ver.major != 0) { aid = yk_aid; aid_len = sizeof(yk_aid); } else { aid = mgmt_aid; aid_len = sizeof(mgmt_aid); } if ((res = _ykpiv_transfer_data(state, templ, aid, aid_len, data, &recv_len, &sw)) < YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { DBG("Failed selecting mgmt/yk application"); } return res; } bool is_version_compatible(ykpiv_state *state, uint8_t major, uint8_t minor, uint8_t patch) { #ifdef DEBUG_YK if (state->ver.major == 0) { return true; } #endif return state->ver.major > major || (state->ver.major == major && state->ver.minor >= minor) || (state->ver.major == major && state->ver.minor == minor && state->ver.patch >= patch); } // if to_slot is set to 0xff, the key will be deleted ykpiv_rc ykpiv_move_key(ykpiv_state *state, const unsigned char from_slot, const unsigned char to_slot) { if(!is_version_compatible(state, 5, 7, 0)) { DBG("Move key operation available with firmware version 5.7.0 or higher"); return YKPIV_NOT_SUPPORTED; } ykpiv_rc res = YKPIV_OK; unsigned char data[256] = {0}; unsigned long recv_len = sizeof(data); int sw = 0; unsigned char adpu[] = {0, YKPIV_INS_MOVE_KEY, to_slot, from_slot}; DBG("Moving key from slot %x to slot %x", from_slot, to_slot); if ((res = _ykpiv_transfer_data(state, adpu, NULL, 0, data, &recv_len, &sw)) != YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { DBG("Failed to move key"); } else { DBG("Key moved from slot %x to %x", from_slot, to_slot); } return res; } ykpiv_rc ykpiv_auth_get_verified(ykpiv_state* state) { ykpiv_rc res = YKPIV_OK; if (NULL == state) { return YKPIV_ARGUMENT_ERROR; } res = _ykpiv_verify(state, NULL, 0, false, false); if (res != YKPIV_OK) { res = YKPIV_AUTHENTICATION_ERROR; } return res; } ykpiv_rc ykpiv_auth_verify(ykpiv_state* state, uint8_t* pin, size_t* p_pin_len, int *tries, bool force_select, bool bio, bool verify_spin) { return _ykpiv_verify_select(state, pin, p_pin_len, tries, force_select, bio, verify_spin); } ykpiv_rc ykpiv_global_reset(ykpiv_state *state) { ykpiv_rc res = YKPIV_OK; unsigned char mgm_templ[] = {0x00, YKPIV_INS_SELECT_APPLICATION, 0x04, 0x00}; unsigned char recv[256] = {0}; unsigned long recv_len = sizeof(recv); int sw = 0; if ((res = _ykpiv_transfer_data(state, mgm_templ, mgmt_aid, sizeof(mgmt_aid), recv, &recv_len, &sw)) < YKPIV_OK) { return res; } res = ykpiv_translate_sw_ex(__FUNCTION__, sw); if (res != YKPIV_OK) { DBG("Failed selecting mgmt/yk application"); return res; } unsigned char reset_templ[] = {0, MGM_INS_GLOBAL_RESET, 0, 0}; recv_len = 0; sw = 0; res = ykpiv_transfer_data(state, reset_templ, NULL, 0, NULL, &recv_len, &sw); if (res != YKPIV_OK) { return res; } return ykpiv_translate_sw_ex(__FUNCTION__, sw); }yubico-piv-tool-2.7.1/lib/scp11_util.c0000664000175000017500000001464314731066755017054 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include #include "internal.h" #include "ykpiv.h" #include "scp11_util.h" #include "../aes_cmac/aes_cmac.h" #ifdef _WIN32 #include #else #include #endif static ykpiv_rc compute_full_mac_ex(const uint8_t *data, uint32_t data_len, aes_context *aes_ctx, uint8_t *mac) { aes_cmac_context_t ctx = {0}; if (aes_cmac_init(aes_ctx, &ctx)) { DBG("aes_cmac_init failed"); return YKPIV_AUTHENTICATION_ERROR; } int drc = aes_cmac_encrypt(&ctx, data, data_len, mac); if (drc) { DBG("%s: aes_cmac_encrypt: %d", ykpiv_strerror(YKPIV_AUTHENTICATION_ERROR), drc); aes_cmac_destroy(&ctx); return YKPIV_AUTHENTICATION_ERROR; } aes_cmac_destroy(&ctx); return YKPIV_OK; } static ykpiv_rc compute_full_mac(const uint8_t *data, uint32_t data_len, const uint8_t *key, uint32_t key_len, uint8_t *mac) { aes_context aes_ctx = {0}; int drc = aes_set_key(key, key_len, YKPIV_ALGO_AES128, &aes_ctx); if (drc) { DBG("%s: aes_set_key: %d", ykpiv_strerror(YKPIV_KEY_ERROR), drc); return YKPIV_KEY_ERROR; } ykpiv_rc rc = compute_full_mac_ex(data, data_len, &aes_ctx, mac); aes_destroy(&aes_ctx); return rc; } ykpiv_rc scp11_mac_data(uint8_t *key, uint8_t *mac_chain, uint8_t *data, uint32_t data_len, uint8_t *mac_out) { int res; if(mac_chain) { uint8_t buf[YKPIV_OBJ_MAX_SIZE] = {0}; memcpy(buf, mac_chain, SCP11_MAC_LEN); memcpy(buf + SCP11_MAC_LEN, data, data_len); size_t buf_len = SCP11_MAC_LEN + data_len; res = compute_full_mac(buf, buf_len, key, AES_BLOCK_SIZE, mac_out); } else { res = compute_full_mac(data, data_len, key, AES_BLOCK_SIZE, mac_out); } return res; } ykpiv_rc scp11_unmac_data(uint8_t *key, uint8_t *mac_chain, uint8_t *data, uint32_t data_len, uint16_t sw) { uint8_t resp[YKPIV_OBJ_MAX_SIZE] = {0}; memcpy(resp, data, (data_len - SCP11_HALF_MAC_LEN)); resp[data_len - SCP11_HALF_MAC_LEN] = sw >> 8; resp[data_len - SCP11_HALF_MAC_LEN + 1] = sw & 0xff; uint8_t rmac[SCP11_MAC_LEN] = {0}; ykpiv_rc rc = scp11_mac_data(key, mac_chain, resp, data_len - SCP11_HALF_MAC_LEN + 2, rmac); if (rc != YKPIV_OK) { DBG("Failed to calculate rmac"); return rc; } if (memcmp(rmac, data + data_len - SCP11_HALF_MAC_LEN, SCP11_HALF_MAC_LEN) != 0) { DBG("Response MAC and message MAC mismatch"); return YKPIV_AUTHENTICATION_ERROR; } return YKPIV_OK; } static ykpiv_rc get_iv(aes_context *key, uint32_t counter, uint8_t *iv, bool decrypt) { uint8_t iv_data[AES_BLOCK_SIZE] = {0}; if (decrypt) { iv_data[0] = 0x80; } uint32_t c = htonl(counter); memcpy(iv_data + AES_BLOCK_SIZE - sizeof(int), &c, sizeof(int)); uint32_t len = AES_BLOCK_SIZE; int drc = aes_encrypt(iv_data, sizeof(iv_data), iv, &len, key); if (drc) { DBG("%s: cipher_encrypt: %d", ykpiv_strerror(YKPIV_KEY_ERROR), drc); return YKPIV_KEY_ERROR; } return YKPIV_OK; } ykpiv_rc scp11_encrypt_data(uint8_t *key, uint32_t counter, const uint8_t *data, uint32_t data_len, uint8_t *enc, uint32_t *enc_len) { ykpiv_rc rc; aes_context enc_key = {0}; int drc = aes_set_key(key, SCP11_SESSION_KEY_LEN, YKPIV_ALGO_AES128, &enc_key); if (drc) { DBG("%s: cipher_import_key: %d", ykpiv_strerror(YKPIV_KEY_ERROR), drc); rc = YKPIV_KEY_ERROR; goto enc_clean; } uint8_t iv[AES_BLOCK_SIZE] = {0}; if ((rc = get_iv(&enc_key, counter, iv, false)) != YKPIV_OK) { DBG("Failed to calculate encryption IV"); goto enc_clean; } size_t pad_len = AES_BLOCK_SIZE - (data_len % AES_BLOCK_SIZE); uint8_t padded[YKPIV_OBJ_MAX_SIZE] = {0}; memcpy(padded, data, data_len); if((drc = aes_add_padding(padded, data_len + pad_len, &data_len)) != 0) { DBG("%s: aes_add_padding: %d", ykpiv_strerror(YKPIV_MEMORY_ERROR), drc); rc = YKPIV_MEMORY_ERROR; goto enc_clean; } if ((drc = aes_cbc_encrypt(padded, data_len, enc, enc_len, iv, AES_BLOCK_SIZE, &enc_key)) != 0) { DBG("%s: cipher_encrypt: %d", ykpiv_strerror(YKPIV_KEY_ERROR), drc); rc = YKPIV_KEY_ERROR; goto enc_clean; } enc_clean: aes_destroy(&enc_key); return rc; } ykpiv_rc scp11_decrypt_data(uint8_t *key, uint32_t counter, uint8_t *enc, uint32_t enc_len, uint8_t *data, uint32_t *data_len) { if(enc_len <= 0) { DBG("No data to decrypt"); *data_len = 0; return YKPIV_OK; } ykpiv_rc rc; aes_context dec_key = {0}; int drc = aes_set_key(key, SCP11_SESSION_KEY_LEN, YKPIV_ALGO_AES128, &dec_key); if (drc) { DBG("%s: cipher_import_key: %d", ykpiv_strerror(YKPIV_KEY_ERROR), drc); rc = YKPIV_KEY_ERROR; goto aes_dec_clean; } uint8_t iv[AES_BLOCK_SIZE] = {0}; if ((rc = get_iv(&dec_key, counter, iv, true)) != YKPIV_OK) { DBG("Failed to calculate decryption IV"); goto aes_dec_clean; } drc = aes_cbc_decrypt(enc, enc_len, data, data_len, iv, AES_BLOCK_SIZE, &dec_key); if (drc) { DBG("%s: cipher_decrypt: %d", ykpiv_strerror(YKPIV_KEY_ERROR), drc); rc = YKPIV_KEY_ERROR; goto aes_dec_clean; } aes_remove_padding(data, data_len); aes_dec_clean: aes_destroy(&dec_key); return rc; }yubico-piv-tool-2.7.1/lib/ykpiv-config.h0000664000175000017500000000671314731067061017470 0ustar winniewinnie/* * Copyright (c) 2014-2016,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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef YKPIV_VERSION_H #define YKPIV_VERSION_H #ifdef __cplusplus extern "C" { #endif /** * YKPIV_VERSION_STRING * * Pre-processor symbol with a string that describe the header file * version number. Used together with ykneomgr_check_version() to verify * header file and run-time library consistency. */ #define YKPIV_VERSION_STRING "2.7.1" /** * YKPIV_VERSION_NUMBER * * Pre-processor symbol with a hexadecimal value describing the header * file version number. For example, when the header version is 1.2.3 * this symbol will have the value 0x01020300. The last two digits * are only used between public releases, and will otherwise be 00. */ /* #undef YKPIV_VERSION_NUMBER */ /** * YKPIV_VERSION_MAJOR * * Pre-processor symbol with a decimal value that describe the major * level of the header file version number. For example, when the * header version is 1.2.3 this symbol will be 1. */ #define YKPIV_VERSION_MAJOR /** * YKPIV_VERSION_MINOR * * Pre-processor symbol with a decimal value that describe the minor * level of the header file version number. For example, when the * header version is 1.2.3 this symbol will be 2. */ #define YKPIV_VERSION_MINOR /** * YKPIV_VERSION_PATCH * * Pre-processor symbol with a decimal value that describe the patch * level of the header file version number. For example, when the * header version is 1.2.3 this symbol will be 3. */ #define YKPIV_VERSION_PATCH /** * _WIN32 * * Pre-processor symbol that describes the Windows system architecture. */ /* #undef _WIN32 */ /** * BACKEND_PCSC * * Pre-processor symbol that describes the available PCSC backend. * If PCSC was not found on the system, some functionality will be missing. */ #define BACKEND_PCSC ON /** * HAVE_PCSC_WINSCARD_H * * Pre-processor symbol indicating whether the file PCSC/winscard.h * exists on the system or not. */ /* #undef HAVE_PCSC_WINSCARD_H */ const char *ykpiv_check_version (const char *req_version); #ifdef __cplusplus } #endif #endif yubico-piv-tool-2.7.1/lib/version.c0000664000175000017500000001013314731066755016543 0ustar winniewinnie/* * Copyright (c) 2014-2017,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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include "ykpiv-config.h" #include #include /* From https://article.gmane.org/gmane.os.freebsd.devel.hackers/23606 */ static int my_strverscmp (const char *s1, const char *s2) { static const char *digits = "0123456789"; size_t p1, p2; p1 = strcspn (s1, digits); p2 = strcspn (s2, digits); while (p1 == p2 && s1[p1] != '\0' && s2[p2] != '\0') { int ret, lz1, lz2; /* Different prefix */ if ((ret = strncmp (s1, s2, p1)) != 0) return ret; s1 += p1; s2 += p2; lz1 = lz2 = 0; if (*s1 == '0') lz1 = 1; if (*s2 == '0') lz2 = 1; if (lz1 > lz2) return -1; else if (lz1 < lz2) return 1; else if (lz1 == 1) { /* * If the common prefix for s1 and s2 consists only of zeros, then the * "longer" number has to compare less. Otherwise the comparison needs * to be numerical (just fallthrough). See * https://refspecs.linuxfoundation.org/LSB_2.0.1/LSB-generic/ * https://refspecs.linuxfoundation.org/LSB_2.0.1/LSB-generic/LSB-generic/baselib-strverscmp.html */ while (*s1 == '0' && *s2 == '0') { ++s1; ++s2; } p1 = strspn (s1, digits); p2 = strspn (s2, digits); /* Catch empty strings */ if (p1 == 0 && p2 > 0) return 1; else if (p2 == 0 && p1 > 0) return -1; /* Prefixes are not same */ if (*s1 != *s2 && *s1 != '0' && *s2 != '0') { if (p1 < p2) return 1; else if (p1 > p2) return -1; } else { if (p1 < p2) ret = strncmp (s1, s2, p1); else if (p1 > p2) ret = strncmp (s1, s2, p2); if (ret != 0) return ret; } } p1 = strspn (s1, digits); p2 = strspn (s2, digits); if (p1 < p2) return -1; else if (p1 > p2) return 1; else if ((ret = strncmp (s1, s2, p1)) != 0) return ret; /* Numbers are equal or not present, try with next ones. */ s1 += p1; s2 += p2; p1 = strcspn (s1, digits); p2 = strcspn (s2, digits); } return strcmp (s1, s2); } /** * ykpiv_check_version: * @req_version: Required version number, or NULL. * * Check that the version of the library is at minimum the requested * one and return the version string; return NULL if the condition is * not satisfied. If a NULL is passed to this function, no check is * done, but the version string is simply returned. * * See %YKPIV_VERSION_STRING for a suitable @req_version string. * * Return value: Version string of run-time library, or NULL if the * run-time library does not meet the required version number. */ const char * ykpiv_check_version (const char *req_version) { if (!req_version || my_strverscmp (req_version, YKPIV_VERSION_STRING) <= 0) return YKPIV_VERSION_STRING; return NULL; } yubico-piv-tool-2.7.1/lib/scp11_util.h0000664000175000017500000000366614731066755017064 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef YUBICO_PIV_TOOL_AES_UTIL_H #define YUBICO_PIV_TOOL_AES_UTIL_H #include "ykpiv.h" ykpiv_rc scp11_mac_data(uint8_t *key, uint8_t *mac_chain, uint8_t *data, uint32_t data_len, uint8_t *mac_out); ykpiv_rc scp11_unmac_data(uint8_t *key, uint8_t *mac_chain, uint8_t *data, uint32_t data_len, uint16_t sw); ykpiv_rc scp11_encrypt_data(uint8_t *key, uint32_t counter, const uint8_t *data, uint32_t data_len, uint8_t *enc, uint32_t *enc_len); ykpiv_rc scp11_decrypt_data(uint8_t *key, uint32_t counter, uint8_t *enc, uint32_t enc_len, uint8_t *data, uint32_t *data_len); #endif //YUBICO_PIV_TOOL_AES_UTIL_H yubico-piv-tool-2.7.1/lib/tests/0000775000175000017500000000000014731067277016056 5ustar winniewinnieyubico-piv-tool-2.7.1/lib/tests/test-config.h0000664000175000017500000000346714731067061020452 0ustar winniewinnie/* * 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef TEST_CONFIG_H #define TEST_CONFIG_H #ifdef __cplusplus extern "C" { #endif /** * _WIN32 * * Pre-processor symbol that describes the Windows system architecture. */ /* #undef _WIN32 */ /** * HW_TESTS * * Pre-processor symbol indicating whether tests that require a YubiKEY are * enabled or disabled. * Default value is disabled */ /* #undef HW_TESTS */ #ifdef __cplusplus } #endif #endif yubico-piv-tool-2.7.1/lib/tests/CMakeLists.txt0000664000175000017500000000610614731066755020621 0ustar winniewinnie# 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: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * 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 # 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. message("lib/tests/CMakeList.txt") if(CMAKE_C_COMPILER_ID MATCHES Clang) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE") elseif(NOT MSVC) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE -pie") endif() include(check) find_check() if(NOT DEFINED SKIP_TESTS) set(SOURCE_BASIC basic.c) set(SOURCE_API api.c ../../aes_cmac/aes.c) set(SOURCE_PARSE_KEY parse_key.c) set(SOURCE_AES aes.c) add_executable (test_basic ${SOURCE_BASIC}) add_executable(test_api ${SOURCE_API}) add_executable(test_parse_key ${SOURCE_PARSE_KEY}) add_executable(test_aes ${SOURCE_AES}) target_link_libraries(test_basic ykpiv_shared ${LIBCRYPTO_LDFLAGS} ${LIBCHECK_LDFLAGS}) target_link_libraries(test_api ykpiv_shared ${LIBCRYPTO_LDFLAGS} ${LIBCHECK_LDFLAGS}) target_link_libraries(test_parse_key ykpiv_shared ${LIBCRYPTO_LDFLAGS} ${LIBCHECK_LDFLAGS}) target_link_libraries(test_aes ykpiv_shared ${LIBCRYPTO_LDFLAGS} ${LIBCHECK_LDFLAGS}) if(${ENABLE_HARDWARE_TESTS}) set(HW_TESTS 1) endif(${ENABLE_HARDWARE_TESTS}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test-config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/test-config.h @ONLY) add_test( NAME basic COMMAND test_basic WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lib/tests/ ) add_test( NAME api COMMAND test_api WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lib/tests/ ) set_property(TEST api APPEND PROPERTY ENVIRONMENT "YKPIV_ENV_HWTESTS_CONFIRMED=${HW_TESTS}") add_test( NAME parse_key COMMAND test_parse_key WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lib/tests/ ) add_test( NAME aes COMMAND test_aes WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lib/tests/ ) endif(NOT DEFINED SKIP_TESTS)yubico-piv-tool-2.7.1/lib/tests/basic.c0000664000175000017500000000547614731066755017317 0ustar winniewinnie/* * Copyright (c) 2014-2016 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include "ykpiv.h" #include #include #include #include #include START_TEST(test_version_string) { if (strcmp(YKPIV_VERSION_STRING, ykpiv_check_version(NULL)) != 0) { ck_abort_msg("version mismatch %s != %s\n", YKPIV_VERSION_STRING, ykpiv_check_version(NULL)); } if (ykpiv_check_version(YKPIV_VERSION_STRING) == NULL) { ck_abort_msg("version NULL?\n"); } if (ykpiv_check_version("99.99.99") != NULL) { ck_abort_msg("version not NULL?\n"); } fprintf(stderr, "ykpiv version: header %s library %s\n", YKPIV_VERSION_STRING, ykpiv_check_version (NULL)); } END_TEST START_TEST(test_strerror) { const char *s; if (ykpiv_strerror(YKPIV_OK) == NULL) { ck_abort_msg("ykpiv_strerror NULL\n"); } s = ykpiv_strerror_name(YKPIV_OK); if (s == NULL || strcmp(s, "YKPIV_OK") != 0) { ck_abort_msg("ykpiv_strerror_name %s\n", s); } } END_TEST static Suite *basic_suite(void) { Suite *s; TCase *tc; s = suite_create("libykpiv basic"); tc = tcase_create("basic"); tcase_add_test(tc, test_version_string); tcase_add_test(tc, test_strerror); suite_add_tcase(s, tc); return s; } int main(void) { int number_failed; Suite *s; SRunner *sr; s = basic_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } yubico-piv-tool-2.7.1/lib/tests/aes.c0000664000175000017500000001550614731066755017001 0ustar winniewinnie/* * Copyright (c) 2024 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include #include #include "../scp11_util.h" struct enc_test_data { uint8_t enc_key[16]; uint8_t counter; uint8_t plain_text[5]; uint8_t enc_text[16]; } enc_data[] = { {{0x3a, 0x37, 0x9f, 0x12, 0x97, 0x13, 0xae, 0xfe, 0x54, 0x94, 0xa9, 0xe1, 0x27, 0x7a, 0x5b, 0x96}, 8, {0x5c, 0x03, 0x5f, 0xc1, 0x02}, {0x93, 0xdd, 0x4e, 0x94, 0xdc, 0x00, 0xed, 0x23, 0x10, 0x2b, 0xc9, 0x94, 0x12, 0x90, 0xfa, 0x14}}, {{0x47, 0x2c, 0xe4, 0xc2, 0xc4, 0x31, 0x84, 0xed, 0xb6, 0x15, 0xb6, 0xc4, 0x94, 0x8a, 0x97, 0x4b}, 8, {0x5c, 0x03, 0x5f, 0xc1, 0x02}, {0x36, 0xd7, 0xcf, 0xf9, 0x1e, 0xe6, 0x3a, 0x74, 0x7a, 0x65, 0x67, 0xe8, 0xf4, 0x9e, 0xa0, 0x0d}} }; struct dec_test_data { uint8_t dec_key[16]; uint8_t counter; uint8_t enc_text[64]; uint8_t plain_text[64]; size_t plain_text_len; } dec_data[] = { {{0xd1, 0xad, 0x52, 0xc5, 0xe8, 0x4d, 0xcc, 0x33, 0x28, 0x26, 0xe5, 0x06, 0xc8, 0x57, 0xef, 0x52}, 8, {0x57, 0x89, 0x96, 0x44, 0xd4, 0xc8, 0x3e, 0x55, 0xf4, 0xe7, 0xdd, 0xc2, 0x03, 0x14, 0xbd, 0xc7, 0xb4, 0xe5, 0xae, 0x30, 0x8f, 0x27, 0x06, 0xbb, 0xc9, 0x16, 0x58, 0x94, 0x5f, 0xf1, 0x3f, 0xf5, 0x6c, 0x21, 0xa0, 0x2c, 0x26, 0xc7, 0xb6, 0xc4, 0x15, 0xbc, 0x90, 0x43, 0x84, 0x8b, 0xa6, 0x9d, 0x82, 0x34, 0xe6, 0x07, 0x42, 0xcb, 0xd8, 0x6b, 0x04, 0xdd, 0xd0, 0x99, 0x42, 0xf2, 0x85, 0x32}, {0x53, 0x3b, 0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, 0x83, 0x68, 0x58, 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, 0xeb, 0x34, 0x10, 0x19, 0x9e, 0xe7, 0x77, 0x16, 0x57, 0xcf, 0xde, 0x06, 0x1b, 0x74, 0x57, 0x15, 0x1a, 0xc4, 0x6d, 0x35, 0x08, 0x32, 0x30, 0x33, 0x30, 0x30, 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00}, 61} }; struct mac_test_data { uint8_t mac_key[16]; uint8_t mac_chain[16]; uint8_t input_text[23]; uint8_t mac[16]; } mac_data[] = { {{0x65, 0xdc, 0x5e, 0x18, 0xac, 0xe9, 0xd3, 0xf2, 0x79, 0x95, 0xff, 0x14, 0xa8, 0x3a, 0xb1, 0x3c}, {0x2f, 0x4d, 0x61, 0x4d, 0x4c, 0x70, 0xa9, 0x17, 0xa6, 0xba, 0x4e, 0x2c, 0x1b, 0x1b, 0xef, 0x08}, {0x04, 0xcb, 0x3f, 0xff, 0x00, 0x00, 0x18, 0x93, 0xdd, 0x4e, 0x94, 0xdc, 0x00, 0xed, 0x23, 0x10, 0x2b, 0xc9, 0x94, 0x12, 0x90, 0xfa, 0x14}, {0x25, 0xcc, 0xda, 0xba, 0x6f, 0x57, 0x01, 0x4d, 0xd2, 0x4b, 0x83, 0x24, 0x95, 0xb0, 0xe8, 0x67}}, {{0x60, 0x50, 0x15, 0xd4, 0x93, 0xcc, 0x7c, 0x14, 0x28, 0x2c, 0x1a, 0x15, 0x7a, 0x56, 0x29, 0x2a}, {0x03, 0x56, 0x2b, 0xde, 0xd1, 0x79, 0x65, 0xc6, 0xf9, 0xaa, 0x04, 0x3e, 0xa4, 0x3e, 0x84, 0x61}, {0x04, 0xcb, 0x3f, 0xff, 0x00, 0x00, 0x18, 0xdb, 0x5d, 0x97, 0x4a, 0x25, 0xb5, 0x97, 0xc7, 0x32, 0x37, 0x69, 0x35, 0x29, 0x94, 0x4c, 0x04}, {0x7e, 0x22, 0x04, 0xe4, 0x58, 0xea, 0xfb, 0xb4, 0xc8, 0xe7, 0x8a, 0x4a, 0x48, 0x4f, 0x6f, 0x3d}} }; static int encryption(uint8_t *key, uint8_t counter, uint8_t *plaintext, uint32_t plaintext_len, uint8_t *enc, size_t enc_len) { uint8_t e[255] = {0}; uint32_t e_len = sizeof(e); ykpiv_rc rc = scp11_encrypt_data(key, counter, plaintext, plaintext_len, e, &e_len); ck_assert(rc == YKPIV_OK); ck_assert(e_len == enc_len); ck_assert(memcmp(e, enc, enc_len) == 0); return EXIT_SUCCESS; } static int decryption(uint8_t *key, uint8_t counter, uint8_t *enc, uint32_t enc_len, uint8_t *dec, size_t dec_len) { uint8_t d[255] = {0}; uint32_t d_len = sizeof(d); ykpiv_rc rc = scp11_decrypt_data(key, counter, enc, enc_len, d, &d_len); ck_assert(rc == YKPIV_OK); ck_assert(d_len == dec_len); ck_assert(memcmp(d, dec, dec_len) == 0); return EXIT_SUCCESS; } static int mac(uint8_t *mac_key, uint8_t *mac_chain, uint8_t *data, uint32_t data_len, uint8_t *mac) { uint8_t m[255] = {0}; ykpiv_rc rc = scp11_mac_data(mac_key, mac_chain, data, data_len, m); ck_assert(rc == YKPIV_OK); ck_assert(memcmp(m, mac, 16) == 0); return EXIT_SUCCESS; } START_TEST(test_encryption) { int res = encryption(enc_data[_i].enc_key, enc_data[_i].counter, enc_data[_i].plain_text, sizeof(enc_data[_i].plain_text), enc_data[_i].enc_text, sizeof(enc_data[_i].enc_text)); ck_assert(res == EXIT_SUCCESS); } END_TEST START_TEST(test_decryption) { int res = decryption(dec_data[_i].dec_key, dec_data[_i].counter, dec_data[_i].enc_text, sizeof(dec_data[_i].enc_text), dec_data[_i].plain_text, dec_data[_i].plain_text_len); ck_assert(res == EXIT_SUCCESS); } END_TEST START_TEST(test_mac) { int res = mac(mac_data[_i].mac_key, mac_data[_i].mac_chain, mac_data[_i].input_text, sizeof(mac_data[_i].input_text), mac_data[_i].mac); ck_assert(res == EXIT_SUCCESS); } END_TEST static Suite *aes_suite(void) { Suite *s; TCase *tc; s = suite_create("libykpiv aes"); tc = tcase_create("aes"); tcase_add_loop_test(tc, test_encryption, 0, sizeof(enc_data) / sizeof(struct enc_test_data)); tcase_add_loop_test(tc, test_decryption, 0, sizeof(dec_data) / sizeof(struct dec_test_data)); tcase_add_loop_test(tc, test_mac, 0, sizeof(mac_data) / sizeof(struct mac_test_data)); suite_add_tcase(s, tc); return s; } int main(void) { int number_failed; Suite *s; SRunner *sr; s = aes_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } yubico-piv-tool-2.7.1/lib/tests/parse_key.c0000664000175000017500000000663714731066755020220 0ustar winniewinnie /* * Copyright (c) 2014-2017,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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include #include #include #include #include "ykpiv.h" struct key { const char text[49]; const unsigned char formatted[24]; int valid; } keys[] = { {"010203040506070801020304050607080102030405060708", {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, 1}, {"a1a2a3a4a5a6a7a8a1a2a3a4a5a6a7a8a1a2a3a4a5a6a7a8", {0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, 1}, {"A1A2A3A4A5A6A7A8A1A2A3A4A5A6A7A8A1A2A3A4A5A6A7A8", {0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8}, 1}, {"This is not something considered valid hex......", {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0}, }; static int parse_key(const char *text, const unsigned char *expected, int valid) { unsigned char key[24] = {0}; size_t len = sizeof(key); ykpiv_rc res = ykpiv_hex_decode(text, strlen(text), key, &len); if (valid) { ck_assert(res == YKPIV_OK); ck_assert(memcmp(expected, key, 24) == 0); } else { ck_assert(res != YKPIV_OK); } return EXIT_SUCCESS; } START_TEST(test_parse_key) { int res = parse_key(keys[_i].text, keys[_i].formatted, keys[_i].valid); ck_assert(res == EXIT_SUCCESS); } END_TEST static Suite *parsekey_suite(void) { Suite *s; TCase *tc; s = suite_create("libykpiv parsekey"); tc = tcase_create("parsekey"); tcase_add_loop_test(tc, test_parse_key, 0, sizeof(keys) / sizeof(struct key)); suite_add_tcase(s, tc); return s; } int main(void) { int number_failed; Suite *s; SRunner *sr; s = parsekey_suite(); sr = srunner_create(s); srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } yubico-piv-tool-2.7.1/lib/tests/test-config.h.in0000664000175000017500000000351314731066755021060 0ustar winniewinnie/* * 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #ifndef TEST_CONFIG_H #define TEST_CONFIG_H #ifdef __cplusplus extern "C" { #endif /** * _WIN32 * * Pre-processor symbol that describes the Windows system architecture. */ #cmakedefine _WIN32 @_WIN32@ /** * HW_TESTS * * Pre-processor symbol indicating whether tests that require a YubiKEY are * enabled or disabled. * Default value is disabled */ #cmakedefine HW_TESTS @HW_TESTS@ #ifdef __cplusplus } #endif #endif yubico-piv-tool-2.7.1/lib/tests/api.c0000664000175000017500000011555014731066755017002 0ustar winniewinnie/* * Copyright (c) 2014-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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ #include "ykpiv.h" #include "internal.h" #include "util.h" #include "../aes_cmac/aes.h" #include "../../common/openssl-compat.h" #include "test-config.h" #include #include #include #include #include #ifdef _WIN32 #define dprintf(fd, ...) fprintf(stdout, __VA_ARGS__) #endif // only defined in libcheck 0.11+ (linux distros still shipping 0.10) #ifndef ck_assert_ptr_nonnull #define ck_assert_ptr_nonnull(a) ck_assert((a) != NULL) #endif #ifndef ck_assert_mem_eq #define ck_assert_mem_eq(a,b,n) ck_assert(memcmp((a), (b), (n)) == 0) #endif // only defined in libcheck 0.10+ (RHEL7 is still shipping 0.9) #ifndef ck_assert_ptr_eq #define ck_assert_ptr_eq(a,b) ck_assert((void *)(a) == (void *)(b)) #endif ykpiv_state *g_state; const uint8_t g_cert[] = { "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" "0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK0123456789ABCDEFGHIK" }; #if HW_TESTS static int destruction_confirmed(void) { char *confirmed = getenv("YKPIV_ENV_HWTESTS_CONFIRMED"); if (confirmed && confirmed[0] == '1') { #ifdef _WIN32 return 1; #else return system("../../../tools/confirm.sh") == 0; #endif } // Use dprintf() to write directly to stdout, since cmake eats the standard stdout/stderr pointers. dprintf(0, "\n***\n*** Hardware tests skipped.\n***\n\n"); return 0; } static void setup(void) { ykpiv_rc res; // Require user confirmation to continue, since this test suite will clear // any data stored on connected keys. if (!destruction_confirmed()) exit(77); // exit code 77 == skipped tests res = ykpiv_init(&g_state, true); ck_assert_int_eq(res, YKPIV_OK); ck_assert_ptr_nonnull(g_state); res = ykpiv_connect(g_state, NULL); ck_assert_int_eq(res, YKPIV_OK); } static void teardown(void) { ykpiv_rc res; // This is the expected case, if the allocator test ran, since it de-inits. if (NULL == g_state) return; res = ykpiv_disconnect(g_state); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_done(g_state); ck_assert_int_eq(res, YKPIV_OK); } START_TEST(test_devicemodel) { ykpiv_rc res; ykpiv_devmodel model; char version[256] = {0}; char reader_buf[2048] = {0}; size_t num_readers = sizeof(reader_buf); res = ykpiv_get_version(g_state, version, sizeof(version)); ck_assert_int_eq(res, YKPIV_OK); fprintf(stderr, "Version: %s\n", version); model = ykpiv_util_devicemodel(g_state); fprintf(stdout, "Model: %x\n", model); ck_assert(model == DEVTYPE_YK5 || model == DEVTYPE_YK4 || model == DEVTYPE_NEOr3); res = ykpiv_list_readers(g_state, reader_buf, &num_readers); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_gt(num_readers, 0); ck_assert_int_eq(strncmp(reader_buf, "Yubico", 6), 0); if (model == DEVTYPE_YK5) { ck_assert(version[0] == '5'); // Verify app version 5.x ck_assert(version[1] == '.'); } else if (model == DEVTYPE_YK4) { ck_assert(version[0] == '4'); // Verify app version 4.x ck_assert(version[1] == '.'); } else { ck_assert(version[0] == '1'); // Verify app version 1.x ck_assert(version[1] == '.'); } } END_TEST START_TEST(test_get_set_cardid) { ykpiv_rc res; ykpiv_cardid set_id; ykpiv_cardid get_id; memset(&set_id.data, 'i', sizeof(set_id.data)); memset(&get_id.data, 0, sizeof(get_id.data)); res = ykpiv_util_set_cardid(g_state, &set_id); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_util_get_cardid(g_state, &get_id); ck_assert_int_eq(res, YKPIV_OK); ck_assert_mem_eq(&set_id.data, &get_id.data, sizeof(set_id.data)); } END_TEST START_TEST(test_list_readers) { ykpiv_rc res; char reader_buf[2048] = {0}; size_t num_readers = sizeof(reader_buf); char *reader_ptr; res = ykpiv_list_readers(g_state, reader_buf, &num_readers); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_gt(num_readers, 0); for(reader_ptr = reader_buf; *reader_ptr != '\0'; reader_ptr += strlen(reader_ptr) + 1) { fprintf(stdout, "Found device: %s\n", reader_ptr); } } END_TEST START_TEST(test_read_write_list_delete_cert) { ykpiv_rc res; uint8_t *read_cert = NULL; size_t read_cert_len = 0; { res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len); ck_assert_int_eq(res, YKPIV_OK); ck_assert_ptr_nonnull(read_cert); ck_assert_int_eq(read_cert_len, sizeof(g_cert)); ck_assert_mem_eq(g_cert, read_cert, sizeof(g_cert)); res = ykpiv_util_free(g_state, read_cert); ck_assert_int_eq(res, YKPIV_OK); } { ykpiv_key *keys = NULL; size_t data_len; uint8_t key_count; res = ykpiv_util_list_keys(g_state, &key_count, &keys, &data_len); ck_assert_int_eq(res, YKPIV_OK); ck_assert_ptr_nonnull(keys); ck_assert_int_gt(key_count, 0); res = ykpiv_util_free(g_state, keys); ck_assert_int_eq(res, YKPIV_OK); } { res = ykpiv_util_delete_cert(g_state, YKPIV_KEY_AUTHENTICATION); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len); ck_assert_int_eq(res, YKPIV_INVALID_OBJECT); res = ykpiv_util_free(g_state, read_cert); ck_assert_int_eq(res, YKPIV_OK); } } END_TEST #include #include #include #include // RSA2048 private key, generated with: `openssl genrsa 2048 -out private.pem` static const char *private_key_pem = "-----BEGIN RSA PRIVATE KEY-----\n" "MIIJKAIBAAKCAgEAvPae/qsMe8ClDmjVFuNQyZu8L2yzGGRud+m1jkPDN/1f9Tu7\n" "8HoJmjN+1jeYyNa39v7C4YN9fZq/7isyJY/aFCbV1ODyTjWZIliEog3FgGjhE9KL\n" "Sm0A+bLLzCxJExVmQm1ZRPxZQbZVq/IQG6QU76CxVthV9NeS0X5RkX91bzREru27\n" "S4cdPd443ftWOcMcXughUD7Y81mg2neNqTgrw75Xq42i+x8dHexMwrwo7y3vzhka\n" "4Wfwa9v3nvo1BV+wtL0+YuNt9pdGDa4WcGTTwmF4AjFGb20bYTmpCeatEgPLH7K/\n" "pxP+jE4aGA8z+eYjAmY9gSxbqx2HUAQlNIhOLg8EBNtajXZlfwKroAosxgCftJHL\n" "HWQoEfcUiJD2UI7NcCX6QUeB6sIgqo5CzIOEeN5UUSXo6+EKPsp0D89+yJhQnLRk\n" "lsaG9prtFbj6PHpqIUYYmZNU6V14IEzut4twKdfLu+wsDCvsYV89I/yQv420CElM\n" "t68G6wrM2COC4g9wJNyJ8JMUVYC1kfiWEQI2UwAFdrLinOfkSyELa93SVZEDUTrv\n" "hhryv2CUp5SDWwLYH/4iAfox+kyksNNvtqdnODXyDm+ApEYKgA8rCx9dZ/pOoTW+\n" "2az7H1yLlD3mK7yRU/++vGs3Kw9THB7/MuYQuRvTyrQq2Jm057gj72WWyccCAwEA\n" "AQKCAgArPPNcqp8MoiQii/JWbmVJ/Iyu/VxttG1imuOkTfUZlqyiXKzAdexEkIvx\n" "UH9xVVB7AAhvubq5RvOr985dsfDgs5IyR9ap9rG3njGbMzOCEn2OH5snyJF0kWj4\n" "qxl9eGQRxxuqIWP7GVG5KoZtDLqNqmNpz867W6iIrzLS7Cte6sLclCFLQvt58KNq\n" "h9xPE0omnU8iIX9bD6My2jBcDDJXc/JzmtE0TQZIlo1p8cwcDpLUwgHYmgP1ajva\n" "8L25IRA6CyN/VTMQPcUV1EPmK+wYilz/g27uiDS/poX7cgEgIiYUdr5L6NNSH3zx\n" "DGmEQRi5r9Na/19qZDNWJ9yrjJT2qD0U4Om3apIdvs2DQ0t+qkE9RA6aYWLhfeeC\n" "WdCilqONxoJy7E09k8ImaR91/r+QPysHzsx2L2V0xhiJo5sWsILn3GK4+UILU2NT\n" "JrGcCmqL3YjouZrFnHtgwVuRNV/xUv52uRPIwBJV2BKb4NnSegLbbKKym21EMRmo\n" "gNz/8iYphdrTS6tqsEIKmb4JzkPHVbbm8BJkBsOjXqRhFczaZ0JniFpzctjVo6C9\n" "xTcf+nwUbFksSEH0SJFyCHDRCDOGQecA8yJ8RqPmKHs/z1DQ/L505jML0/jqniuY\n" "vFHp2hhRFja+xDMXopDrMFtxmyZeRkTnVQgDwj6C3cjs4whyIQKCAQEA6TaPK/c4\n" "5+PenS+qjUNW+VqibckZn5B6qLEjPHC4e85AjA90PJriRYw5lecfw1jY4imIWj21\n" "MlqkAMRuaiiqj3td61l4pRN/n5HhhyKE6bNOuxCDCvwA5244q42VLgosGbm/SGzG\n" "Xswpbee0nwNXBR/Iu/s8utY9fdTT5z/0hd4IMU7NmaEZ1psDG/0o2ykru8UnLcCj\n" "0cCsgsPDl6Ew1mKWNM5ht+1sqTp2JvgNZ4Z8zHxgHC0wC9YFU8X4NNp/+6iyTmfj\n" "fYPszq3lfGVDUwTroqWGrgAix0LlDsbPnYqoayG9OIiCEpZJ+J1oj7mZO5zvLtSO\n" "t/2UBQ8A4XbXGwKCAQEAz206LMh0X10Wt+quhrKiwirKE/aRzPg7uQg7LQCRdoUE\n" "aPP+tP9PfGEwy3aGnChdStf457qyjbXiSi0Bids70EQQtIOMjDJyllFT2CvvFJir\n" "e5YDgan5v/ltUdJxfa1weq08xFgzF/tP3p2uZs9iDJ6I5g1pxzFMi7VGXELqAEg7\n" "vPqn82UOzo4vD8zPohLcrI1kozlBp1GJ9RMDq6FVASb/ztpnArv6ExYoUAehKPDU\n" "AqPHIFp6dA9KkfupIA1TjSmx/sJQgPXMMeuPBlAoPvVH91eQvgdeytmJA6Xpif3O\n" "osBIjc+ThHp8f7jR8N6T0At4IiFataI1PUs9qLPmxQKCAQBCwPo0RHyGa8RBy+4O\n" "p1LS5y2NLT3nXYyukp2aZE16KqxxKs9DtbXE4IFvNgvyd5EFE4xTAEzIUAeXrKJK\n" "Qr+neFGG10JgRfeG7lPWwXu4BToo823/C+kaVYNlH46u8fxzlKZ7DZ+ubNQDAIrD\n" "5UnYTqO/owdcF4zcYroQ/E56rvY7Xuoc6m8h7ZbzQQCb0uoQwjsXrod1t6fpei2X\n" "Tm1TQD7seJKh+hTbT7+YIfJ8SpOYWJWOGyUgji9SLl2Ai3aMy1nWdYg5WjTDaCVC\n" "+R1POx5TnPuy/Jj33l8AXsn4t0LD/5FRCEnrFhewUSYn1aFV3fLcvbzoT246EHRZ\n" "FRI3AoIBAQDO54lL+nf6WAS9WB7WxYGMZNpFp4IwDrykCQ3eCd8Pdge8GQZMzQ8g\n" "ZmIh0gzb33ePnHbvz08kA/XBP7t1I3Y6fGqdZUrg3cFnJ6CW1Nwak18aW70Lrd0u\n" "HUNqhpwmXMcB16PxxnjQxyIYUPkSHHMVW136/A4zX32XLi8NAMIhnevYyb6WDowC\n" "hdlzzTyf0mjExhVIq2hN2gvepiTXIoqEJ76rOzfdhlwghc2YZsPe7rrMF0odf6L9\n" "+fLMQ1ekXSamfJzMHk/nE0en0+xKw9IhWtF6a6I5q2hmty7wsKKPvthLh7nXmuLv\n" "Fq7xSA5CUgLnV0lx4gt1emPYzCCpEypxAoIBADtuc1mzU/Momo8GMoSUOrOvTKam\n" "zGafwLfxKhevqQaajlUhgaerYfJ5zxITmWk73p4d0Hin8OHpyO+NP49hPs0th8eW\n" "FfhmZN/g9alKM39vJd69GyghQLdXkPeUVVt6sTWijmc9/Q991+Gq97xB/pT7NF58\n" "p92BYPWLy5dItn3OGZeI6FJSGZGHgd1Xu+k0qsAAqaTuQ5MEzsklUpNbgQVmMX5V\n" "TY5Ns7jqhserbjwSFt2wc3N9oUEsaTQTA6OyF1MzS50w/oVXRj6FIti1HpuEg9PT\n" "yEaZ9BmaMWkVLEqUxWW+robyb6VpjayYfv53ZcQZmUdzgc/0ByUa84xmCZg=\n" "-----END RSA PRIVATE KEY-----\n"; // Certificate signed with key above: // `openssl req -x509 -key private.pem -out cert.pem -subj "/CN=bar/OU=test/O=example.com/" -new` static const char *certificate_pem = "-----BEGIN CERTIFICATE-----\n" "MIIFRzCCAy+gAwIBAgIUU+jDEMBfkBpcmygX0QnZB4AyyeowDQYJKoZIhvcNAQEL\n" "BQAwMzEMMAoGA1UEAwwDYmFyMQ0wCwYDVQQLDAR0ZXN0MRQwEgYDVQQKDAtleGFt\n" "cGxlLmNvbTAeFw0yNDAyMDkxNDM5NDlaFw0yNDAzMTAxNDM5NDlaMDMxDDAKBgNV\n" "BAMMA2JhcjENMAsGA1UECwwEdGVzdDEUMBIGA1UECgwLZXhhbXBsZS5jb20wggIi\n" "MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC89p7+qwx7wKUOaNUW41DJm7wv\n" "bLMYZG536bWOQ8M3/V/1O7vwegmaM37WN5jI1rf2/sLhg319mr/uKzIlj9oUJtXU\n" "4PJONZkiWISiDcWAaOET0otKbQD5ssvMLEkTFWZCbVlE/FlBtlWr8hAbpBTvoLFW\n" "2FX015LRflGRf3VvNESu7btLhx093jjd+1Y5wxxe6CFQPtjzWaDad42pOCvDvler\n" "jaL7Hx0d7EzCvCjvLe/OGRrhZ/Br2/ee+jUFX7C0vT5i4232l0YNrhZwZNPCYXgC\n" "MUZvbRthOakJ5q0SA8sfsr+nE/6MThoYDzP55iMCZj2BLFurHYdQBCU0iE4uDwQE\n" "21qNdmV/AqugCizGAJ+0kcsdZCgR9xSIkPZQjs1wJfpBR4HqwiCqjkLMg4R43lRR\n" "Jejr4Qo+ynQPz37ImFCctGSWxob2mu0VuPo8emohRhiZk1TpXXggTO63i3Ap18u7\n" "7CwMK+xhXz0j/JC/jbQISUy3rwbrCszYI4LiD3Ak3InwkxRVgLWR+JYRAjZTAAV2\n" "suKc5+RLIQtr3dJVkQNROu+GGvK/YJSnlINbAtgf/iIB+jH6TKSw02+2p2c4NfIO\n" "b4CkRgqADysLH11n+k6hNb7ZrPsfXIuUPeYrvJFT/768azcrD1McHv8y5hC5G9PK\n" "tCrYmbTnuCPvZZbJxwIDAQABo1MwUTAdBgNVHQ4EFgQU6bj+/AsV7xO0lYOeUDQO\n" "+xcsZF0wHwYDVR0jBBgwFoAU6bj+/AsV7xO0lYOeUDQO+xcsZF0wDwYDVR0TAQH/\n" "BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAsaleHaVa9YvX0gYmoAveif6K/Nlv\n" "J72bAg9612jS1LbNNe1rsvHs45+LojtF8BC5+3kJa5+H7QE/vI2zJyfnY9dwDfWP\n" "0sWlOEZD/csNsVPFw1dxjy73kE49Ec+9eY0PlSSi1pdgipFNZRXqn2gpTKXnNceO\n" "XJtFqZ2MD+JPTye0TevKN1qC6p3TV3OtXG+8Wr+Gv6O+FJfNisxoCbIm5zp2sr0j\n" "GLLBEe89fnAe1B1LbsopdqA4waBN6qIiVkyDGEFOOnMPehXoM+5vkEUnr3GsA2fC\n" "1t7FUR2Np1/ncMGnuGM4aeoQGWLi0KXvHmZJgo05/n9/wveU2POWHaJvUL5wzZsp\n" "+OxSyDZagNeri6rq6E6n+R2q/sXardhQWSZW9khkN/3jsdTc3p5zVTH0ahGs/mt0\n" "NhXErJOk2Ot/7BN3uuIA0enc1/58TmJN9z1FBP1oRE+HpRXmBAb1TDslPSvPf1tL\n" "Aydd0+qSrKrR7KJknr8mzSHalWmXDhdm0h5ZteWo5RBOMkb/Kdr5Htp44ioi0JgS\n" "tVnCq0VDvDQlRKvewkux4DDB+ZmTZEvIHQq5cOD37h09VPDT5AmYMnug9HMDiOT7\n" "W+nnb5bVpw+cpKbcpMz7xiz1TGjHKm7wovJIgGe+M6P3ZcRvWfi7yYaL8U/JJChp\n" "CuRM0YVggUE4so4=\n" "-----END CERTIFICATE-----\n"; static void import_key(unsigned char slot, unsigned char pin_policy) { ykpiv_rc res; { unsigned char pp = pin_policy; unsigned char tp = YKPIV_TOUCHPOLICY_DEFAULT; EVP_PKEY *private_key = NULL; BIO *bio = NULL; RSA *rsa_private_key = NULL; unsigned char e[3] = {0}; unsigned char p[256] = {0}; unsigned char q[256] = {0}; unsigned char dmp1[256] = {0}; unsigned char dmq1[256] = {0}; unsigned char iqmp[256] = {0}; int element_len = 256; const BIGNUM *bn_e, *bn_p, *bn_q, *bn_dmp1, *bn_dmq1, *bn_iqmp; int e_len, p_len, q_len, dmp1_len, dmq1_len, iqmp_len; bio = BIO_new_mem_buf(private_key_pem, strlen(private_key_pem)); private_key = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); ck_assert_ptr_nonnull(private_key); BIO_free(bio); rsa_private_key = EVP_PKEY_get1_RSA(private_key); ck_assert_ptr_nonnull(rsa_private_key); RSA_get0_key(rsa_private_key, NULL, &bn_e, NULL); RSA_get0_factors(rsa_private_key, &bn_p, &bn_q); RSA_get0_crt_params(rsa_private_key, &bn_dmp1, &bn_dmq1, &bn_iqmp); e_len = sizeof(e); ck_assert(set_component(e, bn_e, &e_len)); p_len = element_len; ck_assert(set_component(p, bn_p, &p_len)); q_len = element_len; ck_assert(set_component(q, bn_q, &q_len)); dmp1_len = element_len; ck_assert(set_component(dmp1, bn_dmp1, &dmp1_len)); dmq1_len = element_len; ck_assert(set_component(dmq1, bn_dmq1, &dmq1_len)); iqmp_len = element_len; ck_assert(set_component(iqmp, bn_iqmp, &iqmp_len)); // Try wrong algorithm, fail. res = ykpiv_import_private_key(g_state, slot, YKPIV_ALGO_RSA1024, p, p_len, q, q_len, dmp1, dmp1_len, dmq1, dmq1_len, iqmp, iqmp_len, NULL, 0, pp, tp); ck_assert_int_eq(res, YKPIV_ALGORITHM_ERROR); // Try right algorithm res = ykpiv_import_private_key(g_state, slot, YKPIV_ALGO_RSA4096, p, p_len, q, q_len, dmp1, dmp1_len, dmq1, dmq1_len, iqmp, iqmp_len, NULL, 0, pp, tp); ck_assert_int_eq(res, YKPIV_OK); RSA_free(rsa_private_key); EVP_PKEY_free(private_key); } // Use imported key to decrypt a thing. See that it works. { BIO *bio = NULL; X509 *cert = NULL; EVP_PKEY *pub_key = NULL; unsigned char secret[64] = {0}; unsigned char secret2[64] = {0}; unsigned char data[512] = {0}; int len; size_t len2 = sizeof(data); RSA *rsa = NULL; bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem)); cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); ck_assert_ptr_nonnull(cert); BIO_free(bio); pub_key = X509_get_pubkey(cert); ck_assert_ptr_nonnull(pub_key); rsa = EVP_PKEY_get1_RSA(pub_key); ck_assert_ptr_nonnull(rsa); EVP_PKEY_free(pub_key); ck_assert_int_gt(RAND_bytes(secret, sizeof(secret)), 0); len = RSA_public_encrypt(sizeof(secret), secret, data, rsa, RSA_PKCS1_PADDING); ck_assert_int_ge(len, 0); res = ykpiv_verify(g_state, "123456", NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_decipher_data(g_state, data, (size_t)len, data, &len2, YKPIV_ALGO_RSA4096, slot); ck_assert_int_eq(res, YKPIV_OK); len = RSA_padding_check_PKCS1_type_2(secret2, sizeof(secret2), data + 1, len2 - 1, RSA_size(rsa)); ck_assert_int_eq(len, sizeof(secret)); ck_assert_int_eq(memcmp(secret, secret2, sizeof(secret)), 0); RSA_free(rsa); X509_free(cert); } } START_TEST(test_import_key) { ykpiv_rc res; import_key(0x9a, YKPIV_PINPOLICY_DEFAULT); // Verify certificate { BIO *bio = NULL; X509 *cert = NULL; RSA *rsa = NULL; EVP_PKEY *pub_key = NULL; const EVP_MD *md = EVP_sha256(); EVP_MD_CTX *mdctx; unsigned char signature[2048] = {0}; unsigned char encoded[2048] = {0}; unsigned char data[2048] = {0}; unsigned char signinput[2048] = {0}; unsigned char rand[128] = {0}; size_t sig_len = sizeof(signature); size_t padlen = 512; unsigned int enc_len; unsigned int data_len; bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem)); cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); ck_assert_ptr_nonnull(cert); BIO_free(bio); pub_key = X509_get_pubkey(cert); ck_assert_ptr_nonnull(pub_key); rsa = EVP_PKEY_get1_RSA(pub_key); ck_assert_ptr_nonnull(rsa); EVP_PKEY_free(pub_key); ck_assert_int_gt(RAND_bytes(rand, sizeof(rand)), 0); mdctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(mdctx, md, NULL); EVP_DigestUpdate(mdctx, rand, 128); EVP_DigestFinal_ex(mdctx, data, &data_len); prepare_rsa_signature(data, data_len, encoded, &enc_len, EVP_MD_type(md)); ck_assert_int_ne(RSA_padding_add_PKCS1_type_1(signinput, padlen, encoded, enc_len), 0); res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA4096, 0x9a); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(RSA_verify(EVP_MD_type(md), data, data_len, signature, sig_len, rsa), 1); RSA_free(rsa); X509_free(cert); EVP_MD_CTX_destroy(mdctx); } // Verify that imported key can not be attested { unsigned char attest[4096] = {0}; size_t attest_len = sizeof(attest); ykpiv_devmodel model; model = ykpiv_util_devicemodel(g_state); res = ykpiv_attest(g_state, 0x9a, attest, &attest_len); if (model != DEVTYPE_NEOr3) { ck_assert_int_eq(res, YKPIV_ARGUMENT_ERROR); } else { ck_assert_int_eq(res, YKPIV_NOT_SUPPORTED); } } } END_TEST START_TEST(test_pin_policy_always) { ykpiv_rc res; { ykpiv_devmodel model; model = ykpiv_util_devicemodel(g_state); // Only works with YK4. NEO should skip. if (model == DEVTYPE_NEOr3) { fprintf(stderr, "WARNING: Not supported with Yubikey NEO. Test skipped.\n"); return; } } import_key(0x9e, YKPIV_PINPOLICY_ALWAYS); // Verify certificate { BIO *bio = NULL; X509 *cert = NULL; RSA *rsa = NULL; EVP_PKEY *pub_key = NULL; const EVP_MD *md = EVP_sha256(); EVP_MD_CTX *mdctx; unsigned char signature[1024] = {0}; unsigned char encoded[1024] = {0}; unsigned char data[1024] = {0}; unsigned char signinput[1024] = {0}; unsigned char rand[128] = {0}; size_t sig_len = sizeof(signature); size_t padlen = 256; unsigned int enc_len; unsigned int data_len; bio = BIO_new_mem_buf(certificate_pem, strlen(certificate_pem)); cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); ck_assert_ptr_nonnull(cert); BIO_free(bio); pub_key = X509_get_pubkey(cert); ck_assert_ptr_nonnull(pub_key); rsa = EVP_PKEY_get1_RSA(pub_key); ck_assert_ptr_nonnull(rsa); EVP_PKEY_free(pub_key); ck_assert_int_gt(RAND_bytes(rand, sizeof(rand)), 0); mdctx = EVP_MD_CTX_create(); EVP_DigestInit_ex(mdctx, md, NULL); EVP_DigestUpdate(mdctx, rand, 128); EVP_DigestFinal_ex(mdctx, data, &data_len); prepare_rsa_signature(data, data_len, encoded, &enc_len, EVP_MD_type(md)); ck_assert_int_ne(RSA_padding_add_PKCS1_type_1(signinput, padlen, encoded, enc_len), 0); // Sign without verify: fail res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA4096, 0x9e); ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); // Sign with verify: pass res = ykpiv_verify(g_state, "123456", NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA4096, 0x9e); ck_assert_int_eq(res, YKPIV_OK); // Sign again without verify: fail res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA4096, 0x9e); ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); // Sign again with verify: pass res = ykpiv_verify(g_state, "123456", NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_sign_data(g_state, signinput, padlen, signature, &sig_len, YKPIV_ALGO_RSA4096, 0x9e); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(RSA_verify(EVP_MD_type(md), data, data_len, signature, sig_len, rsa), 1); RSA_free(rsa); X509_free(cert); EVP_MD_CTX_destroy(mdctx); } } END_TEST START_TEST(test_generate_key) { ykpiv_rc res; uint8_t *mod, *exp; size_t mod_len, exp_len; res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_util_generate_key(g_state, YKPIV_KEY_AUTHENTICATION, YKPIV_ALGO_RSA2048, YKPIV_PINPOLICY_DEFAULT, YKPIV_TOUCHPOLICY_DEFAULT, &mod, &mod_len, &exp, &exp_len, NULL, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_util_free(g_state, mod); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_util_free(g_state, exp); ck_assert_int_eq(res, YKPIV_OK); // Verify that imported key can be attested { ykpiv_devmodel model; unsigned char attest[2048] = {0}; size_t attest_len = sizeof(attest); model = ykpiv_util_devicemodel(g_state); res = ykpiv_attest(g_state, YKPIV_KEY_AUTHENTICATION, attest, &attest_len); // Only works with YK4. NEO should error. if (model != DEVTYPE_NEOr3) { ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_gt(attest_len, 0); } else { ck_assert_int_eq(res, YKPIV_NOT_SUPPORTED); } } } END_TEST static void test_authenticate_helper(bool full) { ykpiv_rc res; int crc; aes_context cipher = {0}; const char *default_mgm_key = "010203040506070801020304050607080102030405060708"; const char *mgm_key = "112233445566778811223344556677881122334455667788"; const char *mgm_key_16 = "11223344556677881122334455667788"; const char *mgm_key_32 = "1122334455667788112233445566778811223344556677881122334455667788"; const char *weak_des_key = "FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE"; unsigned char key[32] = {0}; size_t key_len = sizeof(key); unsigned char data[256]; size_t data_len = sizeof(data); // Try default key, succeed res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate(g_state, key); ck_assert_int_eq(res, YKPIV_OK); if(!full) { return; } // Try new key, fail. key_len = sizeof(key); res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate(g_state, key); ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); // Verify same key works twice key_len = sizeof(key); res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate(g_state, key); ck_assert_int_eq(res, YKPIV_OK); // Change to new key key_len = sizeof(key); res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey(g_state, key); ck_assert_int_eq(res, YKPIV_OK); // Try new key, succeed. key_len = sizeof(key); res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate(g_state, key); ck_assert_int_eq(res, YKPIV_OK); // Change back to default key key_len = sizeof(key); res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey(g_state, key); ck_assert_int_eq(res, YKPIV_OK); // Try default key, succeed key_len = sizeof(key); res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate(g_state, key); ck_assert_int_eq(res, YKPIV_OK); // Try to set a weak key, fail key_len = sizeof(key); res = ykpiv_hex_decode(weak_des_key, strlen(weak_des_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey(g_state, key); ck_assert_int_eq(res, YKPIV_KEY_ERROR); // Try default key, succeed key_len = sizeof(key); res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate(g_state, key); ck_assert_int_eq(res, YKPIV_OK); // Test external auth data_len = sizeof(data); ykpiv_metadata metadata = {0}; res = ykpiv_auth_getchallenge(g_state, &metadata, data, &data_len); ck_assert_int_eq(res, YKPIV_OK); crc = aes_set_key(key, (uint32_t) key_len, YKPIV_ALGO_3DES, &cipher); ck_assert_int_eq(crc, 0); uint32_t cipher_len = (uint32_t)data_len; crc = aes_encrypt(data, cipher_len, data, &cipher_len, &cipher); data_len = cipher_len; ck_assert_int_eq(crc, 0); crc = aes_destroy(&cipher); ck_assert_int_eq(crc, 0); res = ykpiv_auth_verifyresponse(g_state, &metadata, data, data_len); ck_assert_int_eq(res, YKPIV_OK); // Metadata support implies AES support for YKPIV_KEY_CARDMGM data_len = sizeof(data); res = ykpiv_get_metadata(g_state, YKPIV_KEY_CARDMGM, data, &data_len); if(YKPIV_OK == res) { // AES 128 key key_len = sizeof(key); res = ykpiv_hex_decode(mgm_key_16, strlen(mgm_key_16), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey3(g_state, key, key_len, YKPIV_ALGO_AES128, YKPIV_TOUCHPOLICY_DEFAULT); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate2(g_state, key, key_len); ck_assert_int_eq(res, YKPIV_OK); // AES 192 key key_len = sizeof(key); res = ykpiv_hex_decode(mgm_key, strlen(mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey3(g_state, key, key_len, YKPIV_ALGO_AES192, YKPIV_TOUCHPOLICY_DEFAULT); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate2(g_state, key, key_len); ck_assert_int_eq(res, YKPIV_OK); // AES 256 key key_len = sizeof(key); res = ykpiv_hex_decode(mgm_key_32, strlen(mgm_key_32), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey3(g_state, key, key_len, YKPIV_ALGO_AES256, YKPIV_TOUCHPOLICY_DEFAULT); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate2(g_state, key, key_len); ck_assert_int_eq(res, YKPIV_OK); // A weak DES key should work fine as an AES 192 key key_len = sizeof(key); res = ykpiv_hex_decode(weak_des_key, strlen(weak_des_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey3(g_state, key, key_len, YKPIV_ALGO_AES192, YKPIV_TOUCHPOLICY_DEFAULT); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate2(g_state, key, key_len); ck_assert_int_eq(res, YKPIV_OK); // Default mgm key should work fine as an AES 192 key key_len = sizeof(key); res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey3(g_state, key, key_len, YKPIV_ALGO_AES192, YKPIV_TOUCHPOLICY_DEFAULT); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate2(g_state, key, key_len); ck_assert_int_eq(res, YKPIV_OK); // Restore default 3DES mgmt key key_len = sizeof(key); res = ykpiv_hex_decode(default_mgm_key, strlen(default_mgm_key), key, &key_len); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_set_mgmkey3(g_state, key, key_len, YKPIV_ALGO_3DES, YKPIV_TOUCHPOLICY_DEFAULT); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_authenticate2(g_state, key, key_len); ck_assert_int_eq(res, YKPIV_OK); } else { fprintf(stderr, "Device does not support metadata. AES MGMT key tests skipped.\n"); } } START_TEST(test_authenticate) { test_authenticate_helper(true); } END_TEST START_TEST(test_change_pin) { ykpiv_rc res; res = ykpiv_verify(g_state, "123456", NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_change_pin(g_state, "123456", 6, "ABCDEF", 6, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_verify(g_state, "123456", NULL); ck_assert_int_eq(res, YKPIV_WRONG_PIN); res = ykpiv_verify(g_state, "ABCDEF", NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_change_pin(g_state, "ABCDEF", 6, "123456", 6, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_verify(g_state, "ABCDEF", NULL); ck_assert_int_eq(res, YKPIV_WRONG_PIN); res = ykpiv_verify(g_state, "123456", NULL); ck_assert_int_eq(res, YKPIV_OK); } END_TEST START_TEST(test_change_puk) { ykpiv_rc res; res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_change_puk(g_state, "12345678", 8, "ABCDEFGH", 8, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL); ck_assert_int_eq(res, YKPIV_WRONG_PIN); res = ykpiv_unblock_pin(g_state, "ABCDEFGH", 8, "123456", 6, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_change_puk(g_state, "ABCDEFGH", 8, "12345678", 8, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_unblock_pin(g_state, "ABCDEFGH", 8, "123456", 6, NULL); ck_assert_int_eq(res, YKPIV_WRONG_PIN); res = ykpiv_unblock_pin(g_state, "12345678", 8, "123456", 6, NULL); ck_assert_int_eq(res, YKPIV_OK); } END_TEST static int block_and_reset() { ykpiv_rc res; int tries = 100; int tries_until_blocked; tries_until_blocked = 0; while (tries) { res = ykpiv_verify(g_state, "AAAAAA", &tries); tries_until_blocked++; if (res == YKPIV_PIN_LOCKED) break; ck_assert_int_eq(res, YKPIV_WRONG_PIN); } // Verify no PIN retries remaining tries = 100; res = ykpiv_get_pin_retries(g_state, &tries); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(tries, 0); tries = 100; while (tries) { res = ykpiv_change_puk(g_state, "AAAAAAAA", 8, "AAAAAAAA", 8, &tries); if (res == YKPIV_PIN_LOCKED) break; ck_assert_int_eq(res, YKPIV_WRONG_PIN); } res = ykpiv_util_reset(g_state); ck_assert_int_eq(res, YKPIV_OK); return tries_until_blocked; } START_TEST(test_reset) { ykpiv_rc res; int tries = 100; int tries_until_blocked; ykpiv_devmodel model; model = ykpiv_util_devicemodel(g_state); // Block and reset, with default PIN retries tries_until_blocked = block_and_reset(); ck_assert_int_eq(tries_until_blocked, 3); // Authenticate and increase PIN retries test_authenticate_helper(false); res = ykpiv_verify(g_state, "123456", &tries); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(tries, -1); res = ykpiv_set_pin_retries(g_state, 8, 3); ck_assert_int_eq(res, YKPIV_OK); // Block and reset again, verifying increased PIN retries tries_until_blocked = block_and_reset(); ck_assert_int_eq(tries_until_blocked, 8); // Note: defaults back to 3 retries after reset // Verify default (3) PIN retries remaining tries = 0; res = ykpiv_get_pin_retries(g_state, &tries); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(tries, 3); // Verify still (3) PIN retries remaining tries = 0; res = ykpiv_get_pin_retries(g_state, &tries); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(tries, 3); // Try wrong PIN res = ykpiv_verify(g_state, "AAAAAA", &tries); ck_assert_int_eq(res, YKPIV_WRONG_PIN); ck_assert_int_eq(tries, 2); // Verify 2 PIN retries remaining tries = 0; res = ykpiv_get_pin_retries(g_state, &tries); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(tries, 2); // Verify correct PIN tries = 100; res = ykpiv_verify(g_state, "123456", &tries); ck_assert_int_eq(res, YKPIV_OK); ck_assert_int_eq(tries, -1); // Verify back to 3 PIN retries remaining tries = 0; res = ykpiv_get_pin_retries(g_state, &tries); ck_assert_int_eq(res, YKPIV_OK); if(model == DEVTYPE_NEO || model == DEVTYPE_NEOr3) { ck_assert_int_eq(tries, 0); } else { ck_assert_int_eq(tries, 3); } } END_TEST struct t_alloc_data{ uint32_t count; } g_alloc_data; static void* _test_alloc(void *data, size_t cb) { ck_assert_ptr_eq(data, &g_alloc_data); ((struct t_alloc_data*)data)->count++; return calloc(cb, 1); } static void * _test_realloc(void *data, void *p, size_t cb) { ck_assert_ptr_eq(data, &g_alloc_data); return realloc(p, cb); } static void _test_free(void *data, void *p) { fflush(stderr); ck_assert_ptr_eq(data, &g_alloc_data); ((struct t_alloc_data*)data)->count--; free(p); } ykpiv_allocator test_allocator_cbs = { .pfn_alloc = _test_alloc, .pfn_realloc = _test_realloc, .pfn_free = _test_free, .alloc_data = &g_alloc_data }; static uint8_t *alloc_auth_cert() { ykpiv_rc res; uint8_t *read_cert = NULL; size_t read_cert_len = 0; res = ykpiv_util_write_cert(g_state, YKPIV_KEY_AUTHENTICATION, (uint8_t*)g_cert, sizeof(g_cert), YKPIV_CERTINFO_UNCOMPRESSED); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_util_read_cert(g_state, YKPIV_KEY_AUTHENTICATION, &read_cert, &read_cert_len); ck_assert_int_eq(res, YKPIV_OK); ck_assert_ptr_nonnull(read_cert); ck_assert_int_eq(read_cert_len, sizeof(g_cert)); ck_assert_mem_eq(g_cert, read_cert, sizeof(g_cert)); return read_cert; } START_TEST(test_allocator) { ykpiv_rc res; uint8_t *cert1, *cert2; res = ykpiv_done(g_state); ck_assert_int_eq(res, YKPIV_OK); g_state = NULL; res = ykpiv_init_with_allocator(&g_state, false, &test_allocator_cbs); ck_assert_int_eq(res, YKPIV_OK); ck_assert_ptr_nonnull(g_state); // Verify we can communicate with device and make some allocations res = ykpiv_connect(g_state, NULL); ck_assert_int_eq(res, YKPIV_OK); test_authenticate_helper(false); cert1 = alloc_auth_cert(); cert2 = alloc_auth_cert(); // Verify allocations went through custom allocator, and still live ck_assert_int_gt(g_alloc_data.count, 1); // Free and shutdown everything ykpiv_util_free(g_state, cert2); ykpiv_util_free(g_state, cert1); res = ykpiv_disconnect(g_state); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_done(g_state); ck_assert_int_eq(res, YKPIV_OK); // Verify equal number of frees as allocations ck_assert_int_eq(g_alloc_data.count, 0); // Clear g_state so teardown() is skipped g_state = NULL; } END_TEST START_TEST(test_pin_cache) { ykpiv_rc res; ykpiv_state *local_state; unsigned char data[256] = {0}; unsigned char data_in[256] = {0}; int len = sizeof(data); size_t len2 = sizeof(data); import_key(0x9a, YKPIV_PINPOLICY_DEFAULT); // Disconnect and reconnect to device to guarantee it is not authed res = ykpiv_disconnect(g_state); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_done(g_state); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_init(&g_state, true); ck_assert_int_eq(res, YKPIV_OK); ck_assert_ptr_nonnull(g_state); res = ykpiv_connect(g_state, NULL); ck_assert_int_eq(res, YKPIV_OK); // Verify decryption does not work without auth res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); ck_assert_int_eq(res, YKPIV_AUTHENTICATION_ERROR); // Verify decryption does work when authed res = ykpiv_verify_select(g_state, "123456", 6, NULL, true); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); ck_assert_int_eq(res, YKPIV_OK); // Verify PIN policy allows continuing to decrypt without re-verifying res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); ck_assert_int_eq(res, YKPIV_OK); // Create a new ykpiv state, connect, and close it. // This forces a card reset from another context, so the original global // context will require a reconnect for its next transaction. res = ykpiv_init(&local_state, true); ck_assert_int_eq(res, YKPIV_OK); ck_assert_ptr_nonnull(local_state); res = ykpiv_connect(local_state, NULL); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_disconnect(local_state); ck_assert_int_eq(res, YKPIV_OK); res = ykpiv_done(local_state); ck_assert_int_eq(res, YKPIV_OK); // Verify we are still authenticated on the global context. This will // require an automatic reconnect and re-verify with the cached PIN. // // Note that you can verify that this fails by rebuilding with // DISABLE_PIN_CACHE set to 1. res = ykpiv_decipher_data(g_state, data_in, (size_t)len, data, &len2, YKPIV_ALGO_RSA2048, 0x9a); ck_assert_int_eq(res, YKPIV_OK); } END_TEST #endif static Suite *test_suite(void) { Suite *s; TCase *tc; s = suite_create("libykpiv api"); tc = tcase_create("api"); #if HW_TESTS tcase_add_unchecked_fixture(tc, setup, teardown); // Must be first: Reset device. Tests run serially, and depend on a clean slate. tcase_add_test(tc, test_reset); // Authenticate after reset. tcase_add_test(tc, test_authenticate); // Test API functionality tcase_add_test(tc, test_change_pin); tcase_add_test(tc, test_change_puk); tcase_add_test(tc, test_devicemodel); tcase_add_test(tc, test_get_set_cardid); tcase_add_test(tc, test_list_readers); tcase_add_test(tc, test_read_write_list_delete_cert); tcase_add_test(tc, test_import_key); tcase_add_test(tc, test_pin_policy_always); tcase_add_test(tc, test_generate_key); tcase_add_test(tc, test_pin_cache); // Must be last: tear down and re-test with custom memory allocator tcase_add_test(tc, test_allocator); #endif suite_add_tcase(s, tc); return s; } int main(void) { int number_failed; Suite *s; SRunner *sr; s = test_suite(); sr = srunner_create(s); srunner_set_fork_status(sr, CK_NOFORK); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } yubico-piv-tool-2.7.1/lib/ykpiv.h0000664000175000017500000007210714731066755016236 0ustar winniewinnie/* * Copyright (c) 2014-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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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 * 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. * */ /** * @mainpage * * See ykpiv.h * * @file ykpiv.h * libykpiv API */ #ifndef YKPIV_H #define YKPIV_H #include #include #include #include "ykpiv-config.h" #ifdef __cplusplus extern "C" { #endif typedef struct ykpiv_state ykpiv_state; typedef enum { YKPIV_OK = 0, YKPIV_MEMORY_ERROR = -1, YKPIV_PCSC_ERROR = -2, YKPIV_SIZE_ERROR = -3, YKPIV_APPLET_ERROR = -4, YKPIV_AUTHENTICATION_ERROR = -5, YKPIV_RANDOMNESS_ERROR = -6, YKPIV_GENERIC_ERROR = -7, YKPIV_KEY_ERROR = -8, YKPIV_PARSE_ERROR = -9, YKPIV_WRONG_PIN = -10, YKPIV_INVALID_OBJECT = -11, YKPIV_ALGORITHM_ERROR = -12, YKPIV_PIN_LOCKED = -13, YKPIV_ARGUMENT_ERROR = -14, //i.e. invalid input argument YKPIV_RANGE_ERROR = -15, //i.e. value range error YKPIV_NOT_SUPPORTED = -16, YKPIV_PCSC_SERVICE_ERROR = -17, } ykpiv_rc; typedef void* (*ykpiv_pfn_alloc)(void* alloc_data, size_t size); typedef void* (*ykpiv_pfn_realloc)(void* alloc_data, void* address, size_t size); typedef void (*ykpiv_pfn_free)(void* alloc_data, void* address); typedef struct ykpiv_allocator { ykpiv_pfn_alloc pfn_alloc; ykpiv_pfn_realloc pfn_realloc; ykpiv_pfn_free pfn_free; void * alloc_data; } ykpiv_allocator; const char *ykpiv_strerror(ykpiv_rc err); const char *ykpiv_strerror_name(ykpiv_rc err); ykpiv_rc ykpiv_init(ykpiv_state **state, int verbose); ykpiv_rc ykpiv_init_with_allocator(ykpiv_state **state, int verbose, const ykpiv_allocator *allocator); ykpiv_rc ykpiv_done(ykpiv_state *state); ykpiv_rc ykpiv_validate(ykpiv_state *state, const char *wanted); ykpiv_rc ykpiv_connect(ykpiv_state *state, const char *wanted); ykpiv_rc ykpiv_connect_ex(ykpiv_state *state, const char *wanted, bool scp11); ykpiv_rc ykpiv_list_readers(ykpiv_state *state, char *readers, size_t *len); ykpiv_rc ykpiv_disconnect(ykpiv_state *state); ykpiv_rc ykpiv_translate_sw(int sw); ykpiv_rc ykpiv_translate_sw_ex(const char *whence, int sw); ykpiv_rc ykpiv_transfer_data(ykpiv_state *state, const unsigned char *templ, const unsigned char *in_data, long in_len, unsigned char *out_data, unsigned long *out_len, int *sw); ykpiv_rc ykpiv_authenticate(ykpiv_state *state, const unsigned char *key); ykpiv_rc ykpiv_set_mgmkey(ykpiv_state *state, const unsigned char *new_key); ykpiv_rc ykpiv_hex_decode(const char *hex_in, size_t in_len, unsigned char *hex_out, size_t *out_len); ykpiv_rc ykpiv_sign_data(ykpiv_state *state, const unsigned char *sign_in, size_t in_len, unsigned char *sign_out, size_t *out_len, unsigned char algorithm, unsigned char key); ykpiv_rc ykpiv_decipher_data(ykpiv_state *state, const unsigned char *enc_in, size_t in_len, unsigned char *enc_out, size_t *out_len, unsigned char algorithm, unsigned char key); ykpiv_rc ykpiv_get_version(ykpiv_state *state, char *version, size_t len); ykpiv_rc ykpiv_verify(ykpiv_state *state, const char *pin, int *tries); ykpiv_rc ykpiv_verify_bio(ykpiv_state *state, uint8_t* spin, size_t* spin_len, int *tries, bool verify_spin); ykpiv_rc ykpiv_change_pin(ykpiv_state *state, const char * current_pin, size_t current_pin_len, const char * new_pin, size_t new_pin_len, int *tries); ykpiv_rc ykpiv_change_puk(ykpiv_state *state, const char * current_puk, size_t current_puk_len, const char * new_puk, size_t new_puk_len, int *tries); ykpiv_rc ykpiv_unblock_pin(ykpiv_state *state, const char * puk, size_t puk_len, const char * new_pin, size_t new_pin_len, int *tries); ykpiv_rc ykpiv_fetch_object(ykpiv_state *state, int object_id, unsigned char *data, unsigned long *len); ykpiv_rc ykpiv_authenticate2(ykpiv_state *state, unsigned const char *key, size_t len); ykpiv_rc ykpiv_set_mgmkey2(ykpiv_state *state, const unsigned char *new_key, const unsigned char touch); ykpiv_rc ykpiv_set_mgmkey3(ykpiv_state *state, const unsigned char *new_key, size_t len, unsigned char algorithm, unsigned char touch); ykpiv_rc ykpiv_save_object(ykpiv_state *state, int object_id, unsigned char *indata, size_t len); ykpiv_rc ykpiv_import_private_key(ykpiv_state *state, const unsigned char key, unsigned char algorithm, const unsigned char *p, size_t p_len, const unsigned char *q, size_t q_len, const unsigned char *dp, size_t dp_len, const unsigned char *dq, size_t dq_len, const unsigned char *qinv, size_t qinv_len, const unsigned char *ec_data, unsigned char ec_data_len, const unsigned char pin_policy, const unsigned char touch_policy); ykpiv_rc ykpiv_attest(ykpiv_state *state, const unsigned char key, unsigned char *data, size_t *data_len); ykpiv_rc ykpiv_get_metadata(ykpiv_state *state, const unsigned char key, unsigned char *data, size_t *data_len); bool is_version_compatible(ykpiv_state *state, uint8_t major, uint8_t minor, uint8_t patch); ykpiv_rc ykpiv_move_key(ykpiv_state *state, const unsigned char from_slot, const unsigned char to_slot); ykpiv_rc ykpiv_global_reset(ykpiv_state *state); /** * Return the number of PIN attempts remaining before PIN is locked. * * **NOTE:** If PIN is already verified, calling ykpiv_get_pin_retries() will unverify the PIN. * * @param state State handle from ykpiv_init() * @param tries [out] Number of attempts remaining * * @return Error code */ ykpiv_rc ykpiv_get_pin_retries(ykpiv_state *state, int *tries); /** * Set number of attempts before locking for PIN and PUK codes. * * **NOTE:** If either \p pin_tries or \p puk_tries is 0, ykpiv_set_pin_retries() immediately returns YKPIV_OK. * * @param state State handle from ykpiv_init() * @param pin_tries Number of attempts to permit for PIN code * @param puk_tries Number of attempts to permit for PUK code * * @return Error code */ ykpiv_rc ykpiv_set_pin_retries(ykpiv_state *state, int pin_tries, int puk_tries); /** * Variant of ykpiv_connect() that accepts a card context obtained externally. * * Not for generic use. Use ykpiv_connect() instead. * * @param state State handle * @param context Card context returned from SCardConnect() or equivalent. * @param card Card ID returned from SCardConnect() or equivalent. * * @return Error code */ ykpiv_rc ykpiv_connect_with_external_card(ykpiv_state *state, uintptr_t context, uintptr_t card); /** * Variant of ykpiv_done() for external cards connected with ykpiv_connect_with_external_card() * * Card is not disconnected, unlike with normal calls to ykpiv_done(). * * @param state State handle * * @return Error code */ ykpiv_rc ykpiv_done_with_external_card(ykpiv_state *state); /** * Variant of ykpiv_verify() that optionally selects the PIV applet first. * * @param state State handle * @param pin PIN code to verify with * @param pin_len Length of \p pin * @param tries [out] Number of attempts remaining (if non-NULL) * @param bio if true verify using fingerprint * @param tpin if true set temporary pin, otherwise verify with temp pin * @param force_select Whether to select the PIV applet before verifying. * * @return Error code */ ykpiv_rc ykpiv_verify_select(ykpiv_state *state, const char *pin, const size_t pin_len, int *tries, bool force_select); /** * Get serial number * * The card must be connected to call this function. * * @param state [in] State handle * @param p_serial [out] uint32 to store retrieved serial number * * @return ykpiv_rc error code * */ ykpiv_rc ykpiv_get_serial(ykpiv_state *state, uint32_t* p_serial); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //// //// //// High-level Util API //// //// //// Util api always allocates data on your behalf, if data = 0, *data != 0, //// or data_len = 0 an invalid parameter will be returned; to free data, call //// ykpiv_util_free(). //// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// typedef uint32_t ykpiv_devmodel; /** * Card identifier */ #define YKPIV_CARDID_SIZE 16 typedef struct { uint8_t data[YKPIV_CARDID_SIZE]; } ykpiv_cardid; /** * Card Capability */ #define YKPIV_CCCID_SIZE 14 typedef struct { uint8_t data[YKPIV_CCCID_SIZE]; } ykpiv_cccid; #pragma pack(push, 1) typedef struct _ykpiv_key { uint8_t slot; uint16_t cert_len; uint8_t cert[1]; } ykpiv_key; typedef struct _ykpiv_container { wchar_t name[40]; uint8_t slot; uint8_t key_spec; uint16_t key_size_bits; uint8_t flags; uint8_t pin_id; uint8_t associated_echd_container; uint8_t cert_fingerprint[20]; } ykpiv_container; #pragma pack(pop) typedef enum { YKPIV_CONFIG_MGM_INVALID = -1, YKPIV_CONFIG_MGM_MANUAL = 0, YKPIV_CONFIG_MGM_DERIVED = 1, YKPIV_CONFIG_MGM_PROTECTED = 2 } ykpiv_config_mgm_type; #pragma pack(push, 1) typedef struct _ykpiv_config { uint8_t puk_blocked; uint8_t puk_noblock_on_upgrade; uint32_t pin_last_changed; ykpiv_config_mgm_type mgm_type; size_t mgm_len; uint8_t mgm_key[32]; } ykpiv_config; typedef struct _ykpiv_mgm { size_t len; uint8_t data[32]; } ykpiv_mgm; #pragma pack(pop) typedef struct _ykpiv_metadata { uint8_t algorithm; uint8_t pin_policy; uint8_t touch_policy; uint8_t origin; size_t pubkey_len; uint8_t pubkey[1024]; } ykpiv_metadata; /** * Free allocated data * * Frees a buffer previously allocated by one of the other \p ykpiv_util functions. * * @param state State handle * @param data Buffer previously allocated by a \p ykpiv_util function * * @return ypiv_rc error code */ ykpiv_rc ykpiv_util_free(ykpiv_state *state, void *data); /** * Returns a list of all saved certificates. * * \p data should be freed with \p ykpiv_util_free() after use. * * @param state State handle * @param key_count [out] Number of certificates returned * @param data [out] Set to a dynamically allocated list of certificates. * @param data_len [out] Set to size of \p data in bytes * * @return Error code */ ykpiv_rc ykpiv_util_list_keys(ykpiv_state *state, uint8_t *key_count, ykpiv_key **data, size_t *data_len); /** * Read a certificate stored in the given slot * * \p data should be freed with \p ykpiv_util_free() after use. * * @param state State handle * @param slot Slot to read from * @param data Pointer to buffer to store the read data * @param data_len Pointer to size of input buffer, in bytes. Update to length of read data after call. * * @return Error code */ ykpiv_rc ykpiv_util_read_cert(ykpiv_state *state, uint8_t slot, uint8_t **data, size_t *data_len); /** * Decompresses a certificate if it was compressed * * @param buf Fetched certificate data * @param buf_len Length of fetched certificate data * @param certdata Raw certificate bytes * @param certdata_len Length of raw certificate bytes * * @return Error code */ ykpiv_rc ykpiv_util_get_certdata(uint8_t *buf, size_t buf_len, uint8_t* certdata, size_t *certdata_len); /** * Construct cert data to store * * @param data Raw certificate data * @param data_len Length of raw certificate data * @param compress_info Certificate compression state * @param certdata Constructed certificate data * @param certdata_len Length of constructed certificate data * * @return Error code */ ykpiv_rc ykpiv_util_write_certdata(uint8_t *data, size_t data_len, uint8_t compress_info, uint8_t* certdata, size_t *certdata_len); /** * Write a certificate to a given slot * * \p certinfo should be \p YKPIV_CERTINFO_UNCOMPRESSED for uncompressed certificates, which is the most * common case, or \p YKPIV_CERTINFO_GZIP if the certificate in \p data is already compressed with gzip. * * @param state State handle * @param slot Slot to write to * @param data Buffer of data to write * @param data_len Number of bytes to write * @param certinfo Hint about type of certificate. Use the \p YKPIV_CERTINFO* defines. * * @return Error code */ ykpiv_rc ykpiv_util_write_cert(ykpiv_state *state, uint8_t slot, uint8_t *data, size_t data_len, uint8_t certinfo); /** * Delete the certificate stored in the given slot * * @param state State handle * @param slot Slot to delete certificate from * * @return Error code */ ykpiv_rc ykpiv_util_delete_cert(ykpiv_state *state, uint8_t slot); /** * Generate key in given slot with specified parameters * * \p modulus, \p exp, and \p point should be freed with \p ykpiv_util_free() after use. * * If algorithm is RSA1024 or RSA2048, the modulus, modulus_len, exp, and exp_len output parameters must be supplied. They are filled with with public modulus (big-endian), its size, the public exponent (big-endian), and its size respectively. * * If algorithm is ECCP256 or ECCP384, the point and point_len output parameters must be supplied. They are filled with the public point (uncompressed octet-string encoded per SEC1 section 2.3.4) * * If algorithm is ECCP256, the curve is always ANSI X9.62 Prime 256v1 * * If algorithm is ECCP384, the curve is always secp384r1 * * @param state State handle * @param slot Slot to generate key in * @param algorithm Key algorithm, specified as one of the \p YKPIV_ALGO_* options * @param pin_policy Per-slot PIN policy, specified as one of the \p YKPIV_PINPOLICY_* options * @param touch_policy Per-slot touch policy, specified as one of the \p YKPIV_TOUCHPOLICY_* options. * @param modulus [out] RSA public modulus (RSA-only) * @param modulus_len [out] Size of \p modulus (RSA-only) * @param exp [out] RSA public exponent (RSA-only) * @param exp_len [out] Size of \p exp (RSA-only) * @param point [out] Public curve point (ECC-only) * @param point_len [out] Size of \p point (ECC-only) * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_generate_key(ykpiv_state *state, uint8_t slot, uint8_t algorithm, uint8_t pin_policy, uint8_t touch_policy, uint8_t **modulus, size_t *modulus_len, uint8_t **exp, size_t *exp_len, uint8_t **point, size_t *point_len); /** * Get current PIV applet administration configuration state * * @param state State handle * @param config [out] ykpiv_config struct filled with current applet data * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_get_config(ykpiv_state *state, ykpiv_config *config); /** * Set last pin changed time to current time * * The applet must be authenticated to call this function * * @param state State handle * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_set_pin_last_changed(ykpiv_state *state); /** * Get Derived MGM key * * @param state State handle * @param pin PIN used to derive mgm key * @param pin_len Length of pin in bytes * @param mgm [out] Protected MGM key * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_get_derived_mgm(ykpiv_state *state, const uint8_t *pin, const size_t pin_len, ykpiv_mgm *mgm); /** * Get Protected MGM key * * The user pin must be verified to call this function * * @param state State handle * @param mgm [out] Protected MGM key * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_get_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm); /** * Update Protected MGM key. Should only be used when mgm_type is YKPIV_CONFIG_MGM_PROTECTED. * * The user pin must be verified to call this function * * @param state State handle * @param mgm [in] Protected MGM key * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_update_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm); /** * Set Protected MGM key * * The applet must be authenticated and the user pin verified to call this function * * If \p mgm is NULL or \p mgm.data is all zeroes, generate MGM, otherwise set specified key. * * @param state State handle * @param mgm [in, out] Input: NULL or new MGM key. Output: Generated MGM key * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_set_protected_mgm(ykpiv_state *state, ykpiv_mgm *mgm); /** * Reset PIV applet * * The user PIN and PUK must be blocked to call this function. * * @param state State handle * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_reset(ykpiv_state *state); /** * Get card identifier * * Gets the card identifier from the Cardholder Unique Identifier (CHUID). * * ID can be set with \p ykpiv_util_set_cardid(). * * @param state State handle * @param cardid [out] Unique Card ID stored in the CHUID * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_get_cardid(ykpiv_state *state, ykpiv_cardid *cardid); /** * Set card identifier * * Set the card identifier in the Cardholder Unique Identifier (CHUID). * * The card must be authenticated to call this function. * * See also: \p ykpiv_util_set_cccid() * * @param state State handle * @param cardid Unique Card ID to set. If NULL, randomly generate. * * @return ypiv_rc error code * */ ykpiv_rc ykpiv_util_set_cardid(ykpiv_state *state, const ykpiv_cardid *cardid); /** * Get card capabilities identifier * * Gets the card identifier from the Card Capability Container (CCC). * * ID can be set with \p ykpiv_util_set_cccid(). * * @param state State handle * @param ccc [out] Unique Card ID stored in the CCC * * @return ykpiv_rc error code */ ykpiv_rc ykpiv_util_get_cccid(ykpiv_state *state, ykpiv_cccid *ccc); /** * Set card capabilities identifier * * Sets the card identifier in the Card Capability Container (CCC). * * The card must be authenticated to call this function. * * See also: \p ykpiv_util_set_cardid() * * @param state state * @param ccc Unique Card ID to set. If NULL, randomly generate. * * @return ykpiv_rc error code * */ ykpiv_rc ykpiv_util_set_cccid(ykpiv_state *state, const ykpiv_cccid *ccc); /** * Get device model * * The card must be connected to call this function. * * @param state State handle * * @return Device model * */ ykpiv_devmodel ykpiv_util_devicemodel(ykpiv_state *state); /** * Block PUK * * Utility function to block the PUK. * * To set the PUK blocked flag in the admin data, the applet must be authenticated. * * @param state State handle * * @return Error code * */ ykpiv_rc ykpiv_util_block_puk(ykpiv_state *state); /** * Object ID of given slot. * * @param slot Key slot */ uint32_t ykpiv_util_slot_object(uint8_t slot); ykpiv_rc ykpiv_util_read_mscmap(ykpiv_state *state, ykpiv_container **containers, size_t *n_containers); ykpiv_rc ykpiv_util_write_mscmap(ykpiv_state *state, ykpiv_container *containers, size_t n_containers); ykpiv_rc ykpiv_util_read_msroots(ykpiv_state *state, uint8_t **data, size_t *data_len); ykpiv_rc ykpiv_util_write_msroots(ykpiv_state *state, uint8_t *data, size_t data_len); ykpiv_rc ykpiv_util_parse_metadata(uint8_t *data, size_t data_len, ykpiv_metadata *metadata); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //// //// //// Defines //// //// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// #define YKPIV_ALGO_TAG 0x80 #define YKPIV_ALGO_3DES 0x03 #define YKPIV_ALGO_AES128 0x08 #define YKPIV_ALGO_AES192 0x0a #define YKPIV_ALGO_AES256 0x0c #define YKPIV_ALGO_RSA1024 0x06 #define YKPIV_ALGO_RSA2048 0x07 #define YKPIV_ALGO_RSA3072 0x05 #define YKPIV_ALGO_RSA4096 0x16 #define YKPIV_ALGO_ECCP256 0x11 #define YKPIV_ALGO_ECCP384 0x14 #define YKPIV_ALGO_ED25519 0xE0 #define YKPIV_ALGO_X25519 0xE1 #define YKPIV_ALGO_AUTO 0xff #define YKPIV_KEY_AUTHENTICATION 0x9a #define YKPIV_KEY_CARDMGM 0x9b #define YKPIV_KEY_SIGNATURE 0x9c #define YKPIV_KEY_KEYMGM 0x9d #define YKPIV_KEY_CARDAUTH 0x9e #define YKPIV_KEY_RETIRED1 0x82 #define YKPIV_KEY_RETIRED2 0x83 #define YKPIV_KEY_RETIRED3 0x84 #define YKPIV_KEY_RETIRED4 0x85 #define YKPIV_KEY_RETIRED5 0x86 #define YKPIV_KEY_RETIRED6 0x87 #define YKPIV_KEY_RETIRED7 0x88 #define YKPIV_KEY_RETIRED8 0x89 #define YKPIV_KEY_RETIRED9 0x8a #define YKPIV_KEY_RETIRED10 0x8b #define YKPIV_KEY_RETIRED11 0x8c #define YKPIV_KEY_RETIRED12 0x8d #define YKPIV_KEY_RETIRED13 0x8e #define YKPIV_KEY_RETIRED14 0x8f #define YKPIV_KEY_RETIRED15 0x90 #define YKPIV_KEY_RETIRED16 0x91 #define YKPIV_KEY_RETIRED17 0x92 #define YKPIV_KEY_RETIRED18 0x93 #define YKPIV_KEY_RETIRED19 0x94 #define YKPIV_KEY_RETIRED20 0x95 #define YKPIV_KEY_ATTESTATION 0xf9 #define YKPIV_OBJ_CAPABILITY 0x5fc107 #define YKPIV_OBJ_CHUID 0x5fc102 #define YKPIV_OBJ_AUTHENTICATION 0x5fc105 /* cert for 9a key */ #define YKPIV_OBJ_FINGERPRINTS 0x5fc103 #define YKPIV_OBJ_SECURITY 0x5fc106 #define YKPIV_OBJ_FACIAL 0x5fc108 #define YKPIV_OBJ_PRINTED 0x5fc109 #define YKPIV_OBJ_SIGNATURE 0x5fc10a /* cert for 9c key */ #define YKPIV_OBJ_KEY_MANAGEMENT 0x5fc10b /* cert for 9d key */ #define YKPIV_OBJ_CARD_AUTH 0x5fc101 /* cert for 9e key */ #define YKPIV_OBJ_DISCOVERY 0x7e #define YKPIV_OBJ_KEY_HISTORY 0x5fc10c #define YKPIV_OBJ_IRIS 0x5fc121 #define YKPIV_OBJ_BITGT 0x7f61 #define YKPIV_OBJ_SM_SIGNER 0x5fc122 #define YKPIV_OBJ_PC_REF_DATA 0x5fc123 #define YKPIV_OBJ_RETIRED1 0x5fc10d #define YKPIV_OBJ_RETIRED2 0x5fc10e #define YKPIV_OBJ_RETIRED3 0x5fc10f #define YKPIV_OBJ_RETIRED4 0x5fc110 #define YKPIV_OBJ_RETIRED5 0x5fc111 #define YKPIV_OBJ_RETIRED6 0x5fc112 #define YKPIV_OBJ_RETIRED7 0x5fc113 #define YKPIV_OBJ_RETIRED8 0x5fc114 #define YKPIV_OBJ_RETIRED9 0x5fc115 #define YKPIV_OBJ_RETIRED10 0x5fc116 #define YKPIV_OBJ_RETIRED11 0x5fc117 #define YKPIV_OBJ_RETIRED12 0x5fc118 #define YKPIV_OBJ_RETIRED13 0x5fc119 #define YKPIV_OBJ_RETIRED14 0x5fc11a #define YKPIV_OBJ_RETIRED15 0x5fc11b #define YKPIV_OBJ_RETIRED16 0x5fc11c #define YKPIV_OBJ_RETIRED17 0x5fc11d #define YKPIV_OBJ_RETIRED18 0x5fc11e #define YKPIV_OBJ_RETIRED19 0x5fc11f #define YKPIV_OBJ_RETIRED20 0x5fc120 #define YKPIV_OBJ_ATTESTATION 0x5fff01 #define TAG_CERT 0x70 #define TAG_CERT_COMPRESS 0x71 #define TAG_CERT_LRC 0xFE #define YKPIV_OBJ_MAX_SIZE 3072 #define YKPIV_INS_VERIFY 0x20 #define YKPIV_INS_CHANGE_REFERENCE 0x24 #define YKPIV_INS_RESET_RETRY 0x2c #define YKPIV_INS_GENERATE_ASYMMETRIC 0x47 #define YKPIV_INS_AUTHENTICATE 0x87 #define YKPIV_INS_GET_DATA 0xcb #define YKPIV_INS_PUT_DATA 0xdb #define YKPIV_INS_MOVE_KEY 0xf6 #define YKPIV_INS_SELECT_APPLICATION 0xa4 #define YKPIV_INS_GET_RESPONSE_APDU 0xc0 #define GP_INS_GET_DATA 0xca #define GP_INS_INTERNAL_AUTHENTICATE 0x88 /* sw is status words, see NIST special publication 800-73-4, section 5.6 */ #define SW_SUCCESS 0x9000 #define SW_ERR_SECURITY_STATUS 0x6982 #define SW_ERR_AUTH_BLOCKED 0x6983 #define SW_ERR_CONDITIONS_OF_USE 0x6985 // CONDITIONS_NOT_SATISFIED #define SW_ERR_INCORRECT_PARAM 0x6a80 #define SW_ERR_FILE_NOT_FOUND 0x6a82 #define SW_ERR_REFERENCE_NOT_FOUND 0x6a88 /* this is a custom sw for yubikey */ #define SW_ERR_INCORRECT_SLOT 0x6b00 // WRONG_PARAMETERS_P1P2 #define SW_ERR_NOT_SUPPORTED 0x6d00 // INVALID_INSTRUCTION #define SW_ERR_NO_INPUT_DATA 0x6285 #define SW_ERR_VERIFY_FAIL_NO_RETRY 0x63C0 #define SW_ERR_MEMORY_ERROR 0x6581 #define SW_ERR_WRONG_LENGTH 0x6700 #define SW_ERR_DATA_INVALID 0x6984 #define SW_ERR_COMMAND_NOT_ALLOWED 0x6986 #define SW_ERR_NO_SPACE 0x6A84 #define SW_ERR_CLASS_NOT_SUPPORTED 0x6E00 #define SW_ERR_COMMAND_ABORTED 0x6F00 /* Yubico vendor specific instructions */ #define YKPIV_INS_SET_MGMKEY 0xff #define YKPIV_INS_IMPORT_KEY 0xfe #define YKPIV_INS_GET_VERSION 0xfd #define YKPIV_INS_RESET 0xfb #define YKPIV_INS_SET_PIN_RETRIES 0xfa #define YKPIV_INS_ATTEST 0xf9 #define YKPIV_INS_GET_SERIAL 0xf8 #define YKPIV_INS_GET_METADATA 0xf7 #define MGM_INS_GLOBAL_RESET 0x1f #define YKPIV_PINPOLICY_TAG 0xaa #define YKPIV_PINPOLICY_DEFAULT 0 #define YKPIV_PINPOLICY_NEVER 1 #define YKPIV_PINPOLICY_ONCE 2 #define YKPIV_PINPOLICY_ALWAYS 3 #define YKPIV_PINPOLICY_MATCH_ONCE 4 #define YKPIV_PINPOLICY_MATCH_ALWAYS 5 #define YKPIV_TOUCHPOLICY_TAG 0xab #define YKPIV_TOUCHPOLICY_DEFAULT 0 #define YKPIV_TOUCHPOLICY_NEVER 1 #define YKPIV_TOUCHPOLICY_ALWAYS 2 #define YKPIV_TOUCHPOLICY_CACHED 3 #define YKPIV_TOUCHPOLICY_AUTO 255 #define YKPIV_METADATA_ALGORITHM_TAG 0x01 // See values for YKPIV_ALGO_TAG #define YKPIV_METADATA_POLICY_TAG 0x02 // Two bytes, see values for YKPIV_PINPOLICY_TAG and YKPIV_TOUCHPOLICY_TAG #define YKPIV_METADATA_ORIGIN_TAG 0x03 #define YKPIV_METADATA_ORIGIN_GENERATED 0x01 #define YKPIV_METADATA_ORIGIN_IMPORTED 0x02 #define YKPIV_METADATA_PUBKEY_TAG 0x04 // RSA: DER-encoded sequence N, E; EC: Uncompressed EC point X, Y #define YKPIV_IS_EC(a) ((a == YKPIV_ALGO_ECCP256 || a == YKPIV_ALGO_ECCP384)) #define YKPIV_IS_RSA(a) ((a == YKPIV_ALGO_RSA1024 || a == YKPIV_ALGO_RSA2048 || a == YKPIV_ALGO_RSA3072 || a == YKPIV_ALGO_RSA4096)) #define YKPIV_IS_25519(a) ((a == YKPIV_ALGO_ED25519 || a == YKPIV_ALGO_X25519)) #define YKPIV_MIN_PIN_LEN 6 #define YKPIV_MAX_PIN_LEN 8 #define YKPIV_MIN_MGM_KEY_LEN 32 #define YKPIV_MAX_MGM_KEY_LEN 64 #define YKPIV_RETRIES_DEFAULT 3 #define YKPIV_RETRIES_MAX 0xff #define YKPIV_CERTINFO_UNCOMPRESSED 0 #define YKPIV_CERTINFO_GZIP 1 #define YKPIV_OID_FIRMWARE_VERSION "1.3.6.1.4.1.41482.3.3" #define YKPIV_OID_SERIAL_NUMBER "1.3.6.1.4.1.41482.3.7" #define YKPIV_OID_USAGE_POLICY "1.3.6.1.4.1.41482.3.8" #define YKPIV_OID_FORM_FACTOR "1.3.6.1.4.1.41482.3.9" #define YKPIV_ATR_NEO_R3 "\x3b\xfc\x13\x00\x00\x81\x31\xfe\x15\x59\x75\x62\x69\x6b\x65\x79\x4e\x45\x4f\x72\x33\xe1" #define YKPIV_ATR_NEO_R3_NFC "\x3b\x8c\x80\x01\x59\x75\x62\x69\x6b\x65\x79\x4e\x45\x4f\x72\x33\x58" #define YKPIV_ATR_YK4 "\x3b\xf8\x13\x00\x00\x81\x31\xfe\x15\x59\x75\x62\x69\x6b\x65\x79\x34\xd4" #define YKPIV_ATR_YK5_P1 "\x3b\xf8\x13\x00\x00\x81\x31\xfe\x15\x01\x59\x75\x62\x69\x4b\x65\x79\xc1" #define YKPIV_ATR_YK5 "\x3b\xfd\x13\x00\x00\x81\x31\xfe\x15\x80\x73\xc0\x21\xc0\x57\x59\x75\x62\x69\x4b\x65\x79\x40" #define YKPIV_ATR_YK5_NFC "\x3b\x8d\x80\x01\x80\x73\xc0\x21\xc0\x57\x59\x75\x62\x69\x4b\x65\x79\xf9" #define DEVTYPE_UNKNOWN 0x00000000 #define DEVTYPE_NEO 0x4E450000 //"NE" #define DEVTYPE_YK 0x594B0000 //"YK" #define DEVTYPE_NEOr3 (DEVTYPE_NEO | 0x00007233) //"r3" #define DEVTYPE_YK4 (DEVTYPE_YK | 0x00000034) // "4" #define DEVTYPE_YK5 (DEVTYPE_YK | 0x00000035) // "5" #define DEVYTPE_YK5 DEVTYPE_YK5 // Keep old typo for backwards compatibility #ifdef __cplusplus } #endif #endif yubico-piv-tool-2.7.1/lib/Doxyfile0000664000175000017500000032175314731066755016435 0ustar winniewinnie# Doxyfile 1.8.13 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = "libykpiv" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = doxygen-doc # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 0. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = YES # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO, these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = lib # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.idl \ *.ddl \ *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.cs \ *.d \ *.php \ *.php4 \ *.php5 \ *.phtml \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ *.py \ *.pyw \ *.f90 \ *.f95 \ *.f03 \ *.f08 \ *.f \ *.for \ *.tcl \ *.vhd \ *.vhdl \ *.ucf \ *.qsf # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = internal.h internal.c # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /