From f50957626b4271b8dde69c30d926f6fefc0a4f27 Mon Sep 17 00:00:00 2001 From: pedro martelletto Date: Thu, 24 Feb 2022 14:13:23 +0100 Subject: [PATCH] experimental support for PC/SC NFC readers allows communication with PC/SC NFC readers on Windows, macOS, and Linux. disabled by default; enable with cmake -DUSE_PCSC=ON. based on code contribution from Micro Focus in issue #481; thanks! fixes #481 and #249. --- CMakeLists.txt | 18 +++ src/CMakeLists.txt | 13 +- src/dev.c | 10 ++ src/extern.h | 12 ++ src/nfc.c | 2 + src/pcsc.c | 351 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 src/pcsc.c diff --git a/CMakeLists.txt b/CMakeLists.txt index cdaca4a3..47911102 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ option(BUILD_TOOLS "Build tool programs" ON) option(FUZZ "Enable fuzzing instrumentation" OFF) option(LIBFUZZER "Build libfuzzer harnesses" OFF) option(USE_HIDAPI "Use hidapi as the HID backend" OFF) +option(USE_PCSC "Enable experimental PCSC support" OFF) option(USE_WINHELLO "Abstract Windows Hello as a FIDO device" ON) option(NFC_LINUX "Enable NFC support on Linux" ON) @@ -237,6 +238,12 @@ else() add_compile_options(-Wno-unused-parameter) endif() + # If building with PCSC, look for pcsc-lite. + if(USE_PCSC AND NOT (APPLE OR CYGWIN OR MSYS OR MINGW)) + pkg_search_module(PCSC libpcsclite REQUIRED) + set(PCSC_LIBRARIES pcsclite) + endif() + if(USE_HIDAPI) add_definitions(-DUSE_HIDAPI) pkg_search_module(HIDAPI hidapi${HIDAPI_SUFFIX} REQUIRED) @@ -309,6 +316,10 @@ elseif(WIN32) endif() add_definitions(-DTLS=${TLS}) +if(USE_PCSC) + add_definitions(-DUSE_PCSC) +endif() + # export list if(APPLE AND (CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "AppleClang")) @@ -349,12 +360,14 @@ include_directories(${PROJECT_SOURCE_DIR}/src) include_directories(${CBOR_INCLUDE_DIRS}) include_directories(${CRYPTO_INCLUDE_DIRS}) include_directories(${HIDAPI_INCLUDE_DIRS}) +include_directories(${PCSC_INCLUDE_DIRS}) include_directories(${UDEV_INCLUDE_DIRS}) include_directories(${ZLIB_INCLUDE_DIRS}) link_directories(${CBOR_LIBRARY_DIRS}) link_directories(${CRYPTO_LIBRARY_DIRS}) link_directories(${HIDAPI_LIBRARY_DIRS}) +link_directories(${PCSC_LIBRARY_DIRS}) link_directories(${UDEV_LIBRARY_DIRS}) link_directories(${ZLIB_LIBRARY_DIRS}) @@ -392,6 +405,10 @@ if(USE_HIDAPI) message(STATUS "HIDAPI_LIBRARY_DIRS: ${HIDAPI_LIBRARY_DIRS}") message(STATUS "HIDAPI_VERSION: ${HIDAPI_VERSION}") endif() +message(STATUS "PCSC_INCLUDE_DIRS: ${PCSC_INCLUDE_DIRS}") +message(STATUS "PCSC_LIBRARIES: ${PCSC_LIBRARIES}") +message(STATUS "PCSC_LIBRARY_DIRS: ${PCSC_LIBRARY_DIRS}") +message(STATUS "PCSC_VERSION: ${PCSC_VERSION}") message(STATUS "LIBFUZZER: ${LIBFUZZER}") message(STATUS "TLS: ${TLS}") message(STATUS "UDEV_INCLUDE_DIRS: ${UDEV_INCLUDE_DIRS}") @@ -400,6 +417,7 @@ message(STATUS "UDEV_LIBRARY_DIRS: ${UDEV_LIBRARY_DIRS}") message(STATUS "UDEV_RULES_DIR: ${UDEV_RULES_DIR}") message(STATUS "UDEV_VERSION: ${UDEV_VERSION}") message(STATUS "USE_HIDAPI: ${USE_HIDAPI}") +message(STATUS "USE_PCSC: ${USE_PCSC}") message(STATUS "USE_WINHELLO: ${USE_WINHELLO}") message(STATUS "NFC_LINUX: ${NFC_LINUX}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9133aa84..fe591e32 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,9 @@ endif() if(NFC_LINUX) list(APPEND FIDO_SOURCES netlink.c nfc.c nfc_linux.c) endif() +if(USE_PCSC) + list(APPEND FIDO_SOURCES nfc.c pcsc.c) +endif() if(USE_HIDAPI) list(APPEND FIDO_SOURCES hid_hidapi.c) @@ -93,8 +96,15 @@ list(APPEND COMPAT_SOURCES if(WIN32) list(APPEND BASE_LIBRARIES wsock32 ws2_32 bcrypt setupapi hid) + if(USE_PCSC) + list(APPEND BASE_LIBRARIES winscard) + endif() elseif(APPLE) - list(APPEND BASE_LIBRARIES "-framework CoreFoundation" "-framework IOKit") + list(APPEND BASE_LIBRARIES "-framework CoreFoundation" + "-framework IOKit") + if(USE_PCSC) + list(APPEND BASE_LIBRARIES "-framework PCSC") + endif() endif() list(APPEND TARGET_LIBRARIES @@ -104,6 +114,7 @@ list(APPEND TARGET_LIBRARIES ${BASE_LIBRARIES} ${HIDAPI_LIBRARIES} ${ZLIB_LIBRARIES} + ${PCSC_LIBRARIES} ) # static library diff --git a/src/dev.c b/src/dev.c index f6dff4bf..81f4cebc 100644 --- a/src/dev.c +++ b/src/dev.c @@ -314,6 +314,10 @@ fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) if (fido_dev_register_manifest_func(fido_winhello_manifest) != FIDO_OK) return (FIDO_ERR_INTERNAL); #endif +#ifdef USE_PCSC + if (fido_dev_register_manifest_func(fido_pcsc_manifest) != FIDO_OK) + return (FIDO_ERR_INTERNAL); +#endif for (curr = manifest_funcs; curr != NULL; curr = curr->next) { curr_olen = 0; @@ -351,6 +355,12 @@ fido_dev_open(fido_dev_t *dev, const char *path) return FIDO_ERR_INTERNAL; } #endif +#ifdef USE_PCSC + if (fido_is_pcsc(path) && fido_dev_set_pcsc(dev) < 0) { + fido_log_debug("%s: fido_dev_set_pcsc", __func__); + return FIDO_ERR_INTERNAL; + } +#endif return (fido_dev_open_wait(dev, path, &ms)); } diff --git a/src/extern.h b/src/extern.h index 24dbf1a1..d8c1995a 100644 --- a/src/extern.h +++ b/src/extern.h @@ -128,6 +128,16 @@ int fido_nfc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t); int fido_nfc_set_sigmask(void *, const fido_sigset_t *); int fido_dev_set_nfc(fido_dev_t *); +/* pcsc i/o */ +bool fido_is_pcsc(const char *); +void *fido_pcsc_open(const char *); +void fido_pcsc_close(void *); +int fido_pcsc_read(void *, unsigned char *, size_t, int); +int fido_pcsc_write(void *, const unsigned char *, size_t); +int fido_pcsc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int); +int fido_pcsc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t); +int fido_dev_set_pcsc(fido_dev_t *); + /* windows hello */ int fido_winhello_manifest(fido_dev_info_t *, size_t, size_t *); int fido_winhello_open(fido_dev_t *); @@ -222,6 +232,7 @@ int fido_get_signed_hash_tpm(fido_blob_t *, const fido_blob_t *, /* device manifest functions */ int fido_hid_manifest(fido_dev_info_t *, size_t, size_t *); int fido_nfc_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_pcsc_manifest(fido_dev_info_t *, size_t, size_t *); /* device manifest registration */ typedef int (*dev_manifest_func_t)(fido_dev_info_t *, size_t, size_t *); @@ -252,6 +263,7 @@ uint32_t uniform_random(uint32_t); #define FIDO_DUMMY_USER_ID 1 #define FIDO_WINHELLO_PATH "windows://hello" #define FIDO_NFC_PREFIX "nfc:" +#define FIDO_PCSC_PREFIX "pcsc:" #ifdef __cplusplus } /* extern "C" */ diff --git a/src/nfc.c b/src/nfc.c index 9b7e9e5e..8e1221bb 100644 --- a/src/nfc.c +++ b/src/nfc.c @@ -289,6 +289,7 @@ fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms } } +#ifdef USE_NFC bool fido_is_nfc(const char *path) { @@ -316,3 +317,4 @@ fido_dev_set_nfc(fido_dev_t *d) return 0; } +#endif /* USE_NFC */ diff --git a/src/pcsc.c b/src/pcsc.c new file mode 100644 index 00000000..ab292737 --- /dev/null +++ b/src/pcsc.c @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2022 Micro Focus or one of its affiliates. + * Copyright (c) 2022 Yubico AB. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +#if __APPLE__ +#include +#include +#else +#include +#endif /* __APPLE__ */ + +#include "fido.h" +#include "fido/param.h" +#include "iso7816.h" + +#if defined(_WIN32) && !defined(__MINGW32__) +#define SCardConnect SCardConnectA +#define SCardListReaders SCardListReadersA +#endif + +#ifndef SCARD_PROTOCOL_Tx +#define SCARD_PROTOCOL_Tx SCARD_PROTOCOL_ANY +#endif + +struct pcsc { + SCARDCONTEXT ctx; + SCARDHANDLE h; + SCARD_IO_REQUEST req; + uint8_t rx_buf[FIDO_MAXMSG]; + size_t rx_len; +}; + +static char * +get_reader(const char *path) +{ + char *o = NULL, *p; + char *reader = NULL; + const char prefix[] = FIDO_PCSC_PREFIX "{"; + + if (path == NULL) + goto out; + if ((o = p = strdup(path)) == NULL || + strncmp(p, prefix, strlen(prefix)) != 0) + goto out; + p += strlen(prefix); + if (strlen(p) == 0 || p[strlen(p) - 1] != '}') + goto out; + p[strlen(p) - 1] = '\0'; + reader = strdup(p); +out: + free(o); + + return reader; +} + +static int +prepare_io_request(DWORD prot, SCARD_IO_REQUEST *req) +{ + switch (prot) { + case SCARD_PROTOCOL_T0: + req->dwProtocol = SCARD_PCI_T0->dwProtocol; + req->cbPciLength = SCARD_PCI_T0->cbPciLength; + break; + case SCARD_PROTOCOL_T1: + req->dwProtocol = SCARD_PCI_T1->dwProtocol; + req->cbPciLength = SCARD_PCI_T1->cbPciLength; + break; + default: + fido_log_debug("%s: unknown protocol %lu", __func__, + (u_long)prot); + return -1; + } + + return 0; +} + +static int +copy_info(fido_dev_info_t *di, SCARDCONTEXT ctx, const char *reader) +{ + SCARDHANDLE h = 0; + SCARD_IO_REQUEST req; + DWORD prot = 0; + LONG s; + char path[512]; + int r, ok = -1; + + memset(di, 0, sizeof(*di)); + memset(&req, 0, sizeof(req)); + + if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); + goto fail; + } + if (prepare_io_request(prot, &req) < 0) { + fido_log_debug("%s: prepare_io_request", __func__); + goto fail; + } + if ((r = snprintf(path, sizeof(path), "%s{%s}", FIDO_PCSC_PREFIX, + reader)) < 0 || (size_t)r >= sizeof(path)) { + fido_log_debug("%s: snprintf", __func__); + goto fail; + } + di->path = strdup(path); + di->manufacturer = strdup("PC/SC"); + di->product = strdup(reader); + if (di->path == NULL || di->manufacturer == NULL || di->product == NULL) + goto fail; + + ok = 0; +fail: + if (h != 0) + SCardDisconnect(h, SCARD_LEAVE_CARD); + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return ok; +} + +int +fido_pcsc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + SCARDCONTEXT ctx = 0; + char *buf = NULL; + const char *reader; + DWORD len; + LONG s; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return FIDO_OK; + if (devlist == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + + if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, + &ctx)) != SCARD_S_SUCCESS || ctx == 0) { + fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, + (long)s); + if (s == (LONG)SCARD_E_NO_SERVICE || + s == (LONG)SCARD_E_NO_SMARTCARD) + r = FIDO_OK; /* suppress error */ + goto out; + } + len = 1024; /* XXX */ + if ((buf = calloc(1, len)) == NULL) + goto out; + if ((s = SCardListReaders(ctx, NULL, buf, + &len)) != SCARD_S_SUCCESS || buf == NULL) { + fido_log_debug("%s: SCardListReaders 0x%lx", __func__, (long)s); + if (s == (LONG)SCARD_E_NO_READERS_AVAILABLE) + r = FIDO_OK; /* suppress error */ + goto out; + } + /* sanity check "multi-string" */ + if (len < 2 || buf[len - 1] != 0 || buf[len - 2] != '\0') { + fido_log_debug("%s: can't parse buf returned by " + "SCardListReaders", __func__); + goto out; + } + + for (reader = buf; *reader != 0; reader += strlen(reader) + 1) { + if (copy_info(&devlist[*olen], ctx, reader) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_pcsc_open, + fido_pcsc_close, + fido_pcsc_read, + fido_pcsc_write, + }; + devlist[*olen].transport = (fido_dev_transport_t) { + fido_pcsc_rx, + fido_pcsc_tx, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +out: + free(buf); + if (ctx != 0) + SCardReleaseContext(ctx); + + return r; +} + +void * +fido_pcsc_open(const char *path) +{ + char *reader; + struct pcsc *dev = NULL; + SCARDCONTEXT ctx = 0; + SCARDHANDLE h = 0; + SCARD_IO_REQUEST req; + DWORD prot = 0; + LONG s; + + memset(&req, 0, sizeof(req)); + + if ((reader = get_reader(path)) == NULL) { + fido_log_debug("%s: get_reader(%s)", __func__, path); + goto fail; + } + if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, + &ctx)) != SCARD_S_SUCCESS || ctx == 0) { + fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, + (long)s); + goto fail; + + } + if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); + goto fail; + } + if (prepare_io_request(prot, &req) < 0) { + fido_log_debug("%s: prepare_io_request", __func__); + goto fail; + } + if ((dev = calloc(1, sizeof(*dev))) == NULL) + goto fail; + + dev->ctx = ctx; + dev->h = h; + dev->req = req; + ctx = 0; + h = 0; +fail: + if (h != 0) + SCardDisconnect(h, SCARD_LEAVE_CARD); + if (ctx != 0) + SCardReleaseContext(ctx); + free(reader); + + return dev; +} + +void +fido_pcsc_close(void *handle) +{ + struct pcsc *dev = handle; + + if (dev->h != 0) + SCardDisconnect(dev->h, SCARD_LEAVE_CARD); + if (dev->ctx != 0) + SCardReleaseContext(dev->ctx); + + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + free(dev); +} + +int +fido_pcsc_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct pcsc *dev = handle; + int r; + + (void)ms; + if (dev->rx_len == 0 || dev->rx_len > len || + dev->rx_len > sizeof(dev->rx_buf)) { + fido_log_debug("%s: rx_len", __func__); + return -1; + } + fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: reading", __func__); + memcpy(buf, dev->rx_buf, dev->rx_len); + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + r = (int)dev->rx_len; + dev->rx_len = 0; + + return r; +} + +int +fido_pcsc_write(void *handle, const unsigned char *buf, size_t len) +{ + struct pcsc *dev = handle; + DWORD n; + LONG s; + + if (len > INT_MAX) { + fido_log_debug("%s: len", __func__); + return -1; + } + if (dev->rx_len) { + fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: dropping %zu bytes " + "from input buffer", __func__, dev->rx_len); + } + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + dev->rx_len = 0; + n = (DWORD)sizeof(dev->rx_buf); + fido_log_xxd(buf, len, "%s: writing", __func__); + if ((s = SCardTransmit(dev->h, &dev->req, buf, (DWORD)len, NULL, + dev->rx_buf, &n)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardTransmit 0x%lx", __func__, (long)s); + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + return -1; + } + dev->rx_len = (size_t)n; + fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: read", __func__); + + return (int)len; +} + +int +fido_pcsc_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count) +{ + return fido_nfc_tx(d, cmd, buf, count); +} + +int +fido_pcsc_rx(fido_dev_t *d, uint8_t cmd, u_char *buf, size_t count, int ms) +{ + return fido_nfc_rx(d, cmd, buf, count, ms); +} + +bool +fido_is_pcsc(const char *path) +{ + return strncmp(path, FIDO_PCSC_PREFIX, strlen(FIDO_PCSC_PREFIX)) == 0; +} + +int +fido_dev_set_pcsc(fido_dev_t *d) +{ + if (d->io_handle != NULL) { + fido_log_debug("%s: device open", __func__); + return -1; + } + d->io_own = true; + d->io = (fido_dev_io_t) { + fido_pcsc_open, + fido_pcsc_close, + fido_pcsc_read, + fido_pcsc_write, + }; + d->transport = (fido_dev_transport_t) { + fido_pcsc_rx, + fido_pcsc_tx, + }; + + return 0; +}