From 173bfbf7886608a4a7abbfac6a42ac4bf4a3432d Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Sun, 20 Sep 2020 16:14:20 +0100 Subject: New upstream version 1.5.0 --- src/CMakeLists.txt | 17 ++- src/assert.c | 2 +- src/cbor.c | 25 ++-- src/cred.c | 12 ++ src/credman.c | 8 +- src/dev.c | 207 ++++++++++++++++++++++++++- src/diff_exports.sh | 29 ++-- src/err.c | 4 + src/es256.c | 2 +- src/export.gnu | 9 ++ src/export.llvm | 9 ++ src/export.msvc | 9 ++ src/extern.h | 20 +++ src/fido.h | 23 ++- src/fido/err.h | 2 + src/fido/param.h | 12 +- src/fido/types.h | 20 ++- src/hid_hidapi.c | 226 +++++++++++++++++++++++++++-- src/hid_linux.c | 308 ++++++++++++++++++++++++++++++---------- src/hid_openbsd.c | 91 ++++-------- src/hid_osx.c | 323 ++++++++++++++++++++++++++++++------------ src/hid_win.c | 398 +++++++++++++++++++++++++++++++++++++++------------- src/info.c | 16 +++ src/io.c | 94 ++++++++----- src/iso7816.c | 6 +- src/pin.c | 10 +- src/u2f.c | 100 +++++++++++-- 27 files changed, 1520 insertions(+), 462 deletions(-) (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3cf62e8..ad02524 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,19 +37,24 @@ if(FUZZ) endif() if(USE_HIDAPI) - list(APPEND COMPAT_SOURCES hid_hidapi.c) + list(APPEND FIDO_SOURCES hid_hidapi.c) elseif(WIN32) - list(APPEND COMPAT_SOURCES hid_win.c) + list(APPEND FIDO_SOURCES hid_win.c) elseif(APPLE) - list(APPEND COMPAT_SOURCES hid_osx.c) + list(APPEND FIDO_SOURCES hid_osx.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") - list(APPEND COMPAT_SOURCES hid_linux.c) + list(APPEND FIDO_SOURCES hid_linux.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") - list(APPEND COMPAT_SOURCES hid_openbsd.c) + list(APPEND FIDO_SOURCES hid_openbsd.c) else() message(FATAL_ERROR "please define a hid backend for your platform") endif() +if(NOT MSVC) + set_source_files_properties(${FIDO_SOURCES} PROPERTIES COMPILE_FLAGS + "-Wconversion -Wsign-conversion") +endif() + list(APPEND COMPAT_SOURCES ../openbsd-compat/bsd-getpagesize.c ../openbsd-compat/explicit_bzero.c @@ -94,7 +99,7 @@ elseif(APPLE) "-framework IOKit") endif() set_target_properties(fido2_shared PROPERTIES OUTPUT_NAME fido2 - VERSION ${LIB_VERSION} SOVERSION ${LIB_SOVERSION}) + VERSION ${FIDO_VERSION} SOVERSION ${FIDO_MAJOR}) install(TARGETS fido2_shared ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/src/assert.c b/src/assert.c index b71d00e..1746387 100644 --- a/src/assert.c +++ b/src/assert.c @@ -313,7 +313,7 @@ fido_dev_get_assert(fido_dev_t *dev, fido_assert_t *assert, const char *pin) goto fail; } } - + r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, -1); if (r == FIDO_OK && assert->ext & FIDO_EXT_HMAC_SECRET) if (decrypt_hmac_secrets(assert, ecdh) < 0) { diff --git a/src/cbor.c b/src/cbor.c index 3928325..b30da50 100644 --- a/src/cbor.c +++ b/src/cbor.c @@ -386,7 +386,7 @@ cbor_flatten_vector(cbor_item_t *argv[], size_t argc) return (NULL); for (i = 0; i < argc; i++) - if (cbor_add_arg(map, i + 1, argv[i]) < 0) + if (cbor_add_arg(map, (uint8_t)(i + 1), argv[i]) < 0) break; if (i != argc) { @@ -583,7 +583,9 @@ cbor_encode_extensions(const fido_cred_ext_t *ext) } } if (ext->mask & FIDO_EXT_CRED_PROTECT) { - if (cbor_add_uint8(item, "credProtect", ext->prot) < 0) { + if (ext->prot < 0 || ext->prot > UINT8_MAX || + cbor_add_uint8(item, "credProtect", + (uint8_t)ext->prot) < 0) { cbor_decref(&item); return (NULL); } @@ -634,7 +636,7 @@ cbor_encode_pin_auth(const fido_blob_t *hmac_key, const fido_blob_t *data) unsigned int dgst_len; if ((md = EVP_sha256()) == NULL || HMAC(md, hmac_key->ptr, - (int)hmac_key->len, data->ptr, (int)data->len, dgst, + (int)hmac_key->len, data->ptr, data->len, dgst, &dgst_len) == NULL || dgst_len != SHA256_DIGEST_LENGTH) return (NULL); @@ -696,7 +698,6 @@ cbor_encode_change_pin_auth(const fido_blob_t *key, const fido_blob_t *new_pin, fido_blob_t *npe = NULL; /* new pin, encrypted */ fido_blob_t *ph = NULL; /* pin hash */ fido_blob_t *phe = NULL; /* pin hash, encrypted */ - int ok = -1; if ((npe = fido_blob_new()) == NULL || (ph = fido_blob_new()) == NULL || @@ -735,8 +736,8 @@ cbor_encode_change_pin_auth(const fido_blob_t *key, const fido_blob_t *new_pin, if ((ctx = HMAC_CTX_new()) == NULL || (md = EVP_sha256()) == NULL || HMAC_Init_ex(ctx, key->ptr, (int)key->len, md, NULL) == 0 || - HMAC_Update(ctx, npe->ptr, (int)npe->len) == 0 || - HMAC_Update(ctx, phe->ptr, (int)phe->len) == 0 || + HMAC_Update(ctx, npe->ptr, npe->len) == 0 || + HMAC_Update(ctx, phe->ptr, phe->len) == 0 || HMAC_Final(ctx, dgst, &dgst_len) == 0 || dgst_len != 32) { fido_log_debug("%s: HMAC", __func__); goto fail; @@ -748,7 +749,6 @@ cbor_encode_change_pin_auth(const fido_blob_t *key, const fido_blob_t *new_pin, goto fail; } - ok = 0; fail: fido_blob_free(&npe); fido_blob_free(&ph); @@ -759,13 +759,6 @@ fail: HMAC_CTX_free(ctx); #endif - if (ok < 0) { - if (item != NULL) { - cbor_decref(&item); - item = NULL; - } - } - return (item); } @@ -787,7 +780,7 @@ cbor_encode_set_pin_auth(const fido_blob_t *key, const fido_blob_t *pin) } if ((md = EVP_sha256()) == NULL || key->len != 32 || HMAC(md, key->ptr, - (int)key->len, pe->ptr, (int)pe->len, dgst, &dgst_len) == NULL || + (int)key->len, pe->ptr, pe->len, dgst, &dgst_len) == NULL || dgst_len != SHA256_DIGEST_LENGTH) { fido_log_debug("%s: HMAC", __func__); goto fail; @@ -1292,7 +1285,7 @@ cbor_decode_cred_authdata(const cbor_item_t *item, int cose_alg, } if (authdata_ext != NULL) { - if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0 && + if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0 && decode_extensions(&buf, &len, authdata_ext) < 0) return (-1); } diff --git a/src/cred.c b/src/cred.c index 4ecbba8..9f902fa 100644 --- a/src/cred.c +++ b/src/cred.c @@ -967,6 +967,18 @@ fido_cred_id_len(const fido_cred_t *cred) return (cred->attcred.id.len); } +const unsigned char * +fido_cred_aaguid_ptr(const fido_cred_t *cred) +{ + return (cred->attcred.aaguid); +} + +size_t +fido_cred_aaguid_len(const fido_cred_t *cred) +{ + return (sizeof(cred->attcred.aaguid)); +} + int fido_cred_prot(const fido_cred_t *cred) { diff --git a/src/credman.c b/src/credman.c index a382185..4219807 100644 --- a/src/credman.c +++ b/src/credman.c @@ -230,7 +230,8 @@ fido_credman_get_dev_metadata(fido_dev_t *dev, fido_credman_metadata_t *metadata static int credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg) { - fido_cred_t *cred = arg; + fido_cred_t *cred = arg; + uint64_t prot; if (cbor_isa_uint(key) == false || cbor_int_get_width(key) != CBOR_INT_8) { @@ -249,6 +250,11 @@ credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg) return (-1); cred->type = cred->attcred.type; /* XXX */ return (0); + case 10: + if (cbor_decode_uint64(val, &prot) < 0 || prot > INT_MAX || + fido_cred_set_prot(cred, (int)prot) != FIDO_OK) + return (-1); + return (0); default: fido_log_debug("%s: cbor type", __func__); return (0); /* ignore */ diff --git a/src/dev.c b/src/dev.c index 51b9935..3463ae4 100644 --- a/src/dev.c +++ b/src/dev.c @@ -10,6 +10,8 @@ #include #endif +#include + #include #include #include @@ -106,10 +108,49 @@ find_manifest_func_node(dev_manifest_func_t f, dev_manifest_func_node_t **curr, } } +#ifdef FIDO_FUZZ +static void +set_random_report_len(fido_dev_t *dev) +{ + dev->rx_len = CTAP_MIN_REPORT_LEN + + uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); + dev->tx_len = CTAP_MIN_REPORT_LEN + + uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); +} +#endif + +static void +fido_dev_set_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + char * const *ptr; + const bool *val; + size_t len; + + ptr = fido_cbor_info_extensions_ptr(info); + len = fido_cbor_info_extensions_len(info); + + for (size_t i = 0; i < len; i++) + if (strcmp(ptr[i], "credProtect") == 0) + dev->flags |= FIDO_DEV_CRED_PROT; + + ptr = fido_cbor_info_options_name_ptr(info); + val = fido_cbor_info_options_value_ptr(info); + len = fido_cbor_info_options_len(info); + + for (size_t i = 0; i < len; i++) + if (strcmp(ptr[i], "clientPin") == 0) { + if (val[i] == true) + dev->flags |= FIDO_DEV_PIN_SET; + else + dev->flags |= FIDO_DEV_PIN_UNSET; + } +} + static int fido_dev_open_tx(fido_dev_t *dev, const char *path) { - const uint8_t cmd = CTAP_CMD_INIT; + const uint8_t cmd = CTAP_CMD_INIT; + int r; if (dev->io_handle != NULL) { fido_log_debug("%s: handle=%p", __func__, dev->io_handle); @@ -131,14 +172,44 @@ fido_dev_open_tx(fido_dev_t *dev, const char *path) return (FIDO_ERR_INTERNAL); } + if (dev->io_own) { + dev->rx_len = CTAP_MAX_REPORT_LEN; + dev->tx_len = CTAP_MAX_REPORT_LEN; + } else { + dev->rx_len = fido_hid_report_in_len(dev->io_handle); + dev->tx_len = fido_hid_report_out_len(dev->io_handle); + } + +#ifdef FIDO_FUZZ + set_random_report_len(dev); +#endif + + if (dev->rx_len < CTAP_MIN_REPORT_LEN || + dev->rx_len > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: invalid rx_len %zu", __func__, dev->rx_len); + r = FIDO_ERR_RX; + goto fail; + } + + if (dev->tx_len < CTAP_MIN_REPORT_LEN || + dev->tx_len > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: invalid tx_len %zu", __func__, dev->tx_len); + r = FIDO_ERR_TX; + goto fail; + } + if (fido_tx(dev, cmd, &dev->nonce, sizeof(dev->nonce)) < 0) { fido_log_debug("%s: fido_tx", __func__); - dev->io.close(dev->io_handle); - dev->io_handle = NULL; - return (FIDO_ERR_TX); + r = FIDO_ERR_TX; + goto fail; } return (FIDO_OK); +fail: + dev->io.close(dev->io_handle); + dev->io_handle = NULL; + + return (r); } static int @@ -166,6 +237,7 @@ fido_dev_open_rx(fido_dev_t *dev, int ms) goto fail; } + dev->flags = 0; dev->cid = dev->attr.cid; if (fido_dev_is_fido2(dev)) { @@ -177,6 +249,8 @@ fido_dev_open_rx(fido_dev_t *dev, int ms) if (fido_dev_get_cbor_info_wait(dev, info, ms) != FIDO_OK) { fido_log_debug("%s: falling back to u2f", __func__); fido_dev_force_u2f(dev); + } else { + fido_dev_set_flags(dev, info); } } @@ -303,12 +377,114 @@ fido_dev_close(fido_dev_t *dev) int fido_dev_cancel(fido_dev_t *dev) { + if (fido_dev_is_fido2(dev) == false) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_tx(dev, CTAP_CMD_CANCEL, NULL, 0) < 0) return (FIDO_ERR_TX); return (FIDO_OK); } +int +fido_dev_get_touch_begin(fido_dev_t *dev) +{ + fido_blob_t f; + cbor_item_t *argv[9]; + const char *clientdata = FIDO_DUMMY_CLIENTDATA; + const uint8_t user_id = FIDO_DUMMY_USER_ID; + unsigned char cdh[SHA256_DIGEST_LENGTH]; + fido_rp_t rp; + fido_user_t user; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + memset(cdh, 0, sizeof(cdh)); + memset(&rp, 0, sizeof(rp)); + memset(&user, 0, sizeof(user)); + + if (fido_dev_is_fido2(dev) == false) + return (u2f_get_touch_begin(dev)); + + if (SHA256((const void *)clientdata, strlen(clientdata), cdh) != cdh) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((rp.id = strdup(FIDO_DUMMY_RP_ID)) == NULL || + (user.name = strdup(FIDO_DUMMY_USER_NAME)) == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + + if (fido_blob_set(&user.id, &user_id, sizeof(user_id)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + goto fail; + } + + if ((argv[0] = cbor_build_bytestring(cdh, sizeof(cdh))) == NULL || + (argv[1] = cbor_encode_rp_entity(&rp)) == NULL || + (argv[2] = cbor_encode_user_entity(&user)) == NULL || + (argv[3] = cbor_encode_pubkey_param(COSE_ES256)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if (fido_dev_supports_pin(dev)) { + if ((argv[7] = cbor_new_definite_bytestring()) == NULL || + (argv[8] = cbor_encode_pin_opt()) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + } + + if (cbor_build_frame(CTAP_CBOR_MAKECRED, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + free(rp.id); + free(user.name); + free(user.id.ptr); + + return (r); +} + +int +fido_dev_get_touch_status(fido_dev_t *dev, int *touched, int ms) +{ + int r; + + *touched = 0; + + if (fido_dev_is_fido2(dev) == false) + return (u2f_get_touch_status(dev, touched, ms)); + + switch ((r = fido_rx_cbor_status(dev, ms))) { + case FIDO_ERR_PIN_AUTH_INVALID: + case FIDO_ERR_PIN_INVALID: + case FIDO_ERR_PIN_NOT_SET: + case FIDO_ERR_SUCCESS: + *touched = 1; + break; + case FIDO_ERR_RX: + /* ignore */ + break; + default: + fido_log_debug("%s: fido_rx_cbor_status", __func__); + return (r); + } + + return (FIDO_OK); +} + int fido_dev_set_io_functions(fido_dev_t *dev, const fido_dev_io_t *io) { @@ -324,6 +500,7 @@ fido_dev_set_io_functions(fido_dev_t *dev, const fido_dev_io_t *io) } dev->io = *io; + dev->io_own = true; return (FIDO_OK); } @@ -337,6 +514,7 @@ fido_dev_set_transport_functions(fido_dev_t *dev, const fido_dev_transport_t *t) } dev->transport = *t; + dev->io_own = true; return (FIDO_OK); } @@ -446,10 +624,29 @@ fido_dev_is_fido2(const fido_dev_t *dev) return (dev->attr.flags & FIDO_CAP_CBOR); } +bool +fido_dev_supports_pin(const fido_dev_t *dev) +{ + return (dev->flags & (FIDO_DEV_PIN_SET|FIDO_DEV_PIN_UNSET)); +} + +bool +fido_dev_has_pin(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_PIN_SET); +} + +bool +fido_dev_supports_cred_prot(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_CRED_PROT); +} + void fido_dev_force_u2f(fido_dev_t *dev) { - dev->attr.flags &= ~FIDO_CAP_CBOR; + dev->attr.flags &= (uint8_t)~FIDO_CAP_CBOR; + dev->flags = 0; } void diff --git a/src/diff_exports.sh b/src/diff_exports.sh index 7920f47..9cff009 100755 --- a/src/diff_exports.sh +++ b/src/diff_exports.sh @@ -1,23 +1,26 @@ -#!/bin/bash -u +#!/bin/sh -u # Copyright (c) 2018 Yubico AB. All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -[[ ! -f export.gnu || ! -f export.llvm || ! -f export.msvc ]] && exit 1 +for f in export.gnu export.llvm export.msvc; do + if [ ! -f "${f}" ]; then + exit 1 + fi +done -TMPDIR=$(mktemp -d) -GNU=${TMPDIR}/gnu -LLVM=${TMPDIR}/llvm -MSVC=${TMPDIR}/msvc +TMPDIR="$(mktemp -d)" +GNU="${TMPDIR}/gnu" +LLVM="${TMPDIR}/llvm" +MSVC="${TMPDIR}/msvc" -egrep -o $'([^*{}\t]+);$' export.gnu | tr -d ';' | sort > ${GNU} -sed 's/^_//g' export.llvm | sort > ${LLVM} -egrep -v "^EXPORTS$" export.msvc | sort > ${MSVC} -diff -u ${GNU} ${LLVM} && diff -u ${MSVC} ${LLVM} +awk '/^[^*{}]+;$/' export.gnu | tr -d '\t;' | sort > "${GNU}" +sed 's/^_//' export.llvm | sort > "${LLVM}" +grep -v '^EXPORTS$' export.msvc | sort > "${MSVC}" +diff -u "${GNU}" "${LLVM}" && diff -u "${MSVC}" "${LLVM}" ERROR=$? - -rm ${GNU} ${LLVM} ${MSVC} -rmdir ${TMPDIR} +rm "${GNU}" "${LLVM}" "${MSVC}" +rmdir "${TMPDIR}" exit ${ERROR} diff --git a/src/err.c b/src/err.c index 6261bfc..19cda21 100644 --- a/src/err.c +++ b/src/err.c @@ -38,6 +38,8 @@ fido_strerr(int n) return "FIDO_ERR_LIMIT_EXCEEDED"; case FIDO_ERR_UNSUPPORTED_EXTENSION: return "FIDO_ERR_UNSUPPORTED_EXTENSION"; + case FIDO_ERR_FP_DATABASE_FULL: + return "FIDO_ERR_FP_DATABASE_FULL"; case FIDO_ERR_CREDENTIAL_EXCLUDED: return "FIDO_ERR_CREDENTIAL_EXCLUDED"; case FIDO_ERR_PROCESSING: @@ -94,6 +96,8 @@ fido_strerr(int n) return "FIDO_ERR_ACTION_TIMEOUT"; case FIDO_ERR_UP_REQUIRED: return "FIDO_ERR_UP_REQUIRED"; + case FIDO_ERR_UV_BLOCKED: + return "FIDO_ERR_UV_BLOCKED"; case FIDO_ERR_ERR_OTHER: return "FIDO_ERR_ERR_OTHER"; case FIDO_ERR_SPEC_LAST: diff --git a/src/es256.c b/src/es256.c index 020ecaa..5b4e6d6 100644 --- a/src/es256.c +++ b/src/es256.c @@ -92,7 +92,7 @@ es256_pk_encode(const es256_pk_t *pk, int ecdh) /* alg */ if ((argv[1].key = cbor_build_uint8(3)) == NULL || - (argv[1].value = cbor_build_negint8(-alg - 1)) == NULL || + (argv[1].value = cbor_build_negint8((uint8_t)(-alg - 1))) == NULL || !cbor_map_add(item, argv[1])) goto fail; diff --git a/src/export.gnu b/src/export.gnu index cbfa69f..007b5b9 100644 --- a/src/export.gnu +++ b/src/export.gnu @@ -76,6 +76,8 @@ fido_cbor_info_extensions_ptr; fido_cbor_info_free; fido_cbor_info_maxmsgsiz; + fido_cbor_info_maxcredcntlst; + fido_cbor_info_maxcredidlen; fido_cbor_info_fwversion; fido_cbor_info_new; fido_cbor_info_options_len; @@ -96,6 +98,8 @@ fido_cred_free; fido_cred_id_len; fido_cred_id_ptr; + fido_cred_aaguid_len; + fido_cred_aaguid_ptr; fido_credman_del_dev_rk; fido_credman_get_dev_metadata; fido_credman_get_dev_rk; @@ -155,6 +159,9 @@ fido_dev_get_assert; fido_dev_get_cbor_info; fido_dev_get_retry_count; + fido_dev_get_touch_begin; + fido_dev_get_touch_status; + fido_dev_has_pin; fido_dev_info_free; fido_dev_info_manifest; fido_dev_info_manufacturer_string; @@ -175,6 +182,8 @@ fido_dev_set_io_functions; fido_dev_set_pin; fido_dev_set_transport_functions; + fido_dev_supports_cred_prot; + fido_dev_supports_pin; fido_init; fido_set_log_handler; fido_strerr; diff --git a/src/export.llvm b/src/export.llvm index abde2e9..ffbc157 100644 --- a/src/export.llvm +++ b/src/export.llvm @@ -74,6 +74,8 @@ _fido_cbor_info_extensions_len _fido_cbor_info_extensions_ptr _fido_cbor_info_free _fido_cbor_info_maxmsgsiz +_fido_cbor_info_maxcredcntlst +_fido_cbor_info_maxcredidlen _fido_cbor_info_fwversion _fido_cbor_info_new _fido_cbor_info_options_len @@ -94,6 +96,8 @@ _fido_cred_fmt _fido_cred_free _fido_cred_id_len _fido_cred_id_ptr +_fido_cred_aaguid_len +_fido_cred_aaguid_ptr _fido_credman_del_dev_rk _fido_credman_get_dev_metadata _fido_credman_get_dev_rk @@ -153,6 +157,9 @@ _fido_dev_free _fido_dev_get_assert _fido_dev_get_cbor_info _fido_dev_get_retry_count +_fido_dev_get_touch_begin +_fido_dev_get_touch_status +_fido_dev_has_pin _fido_dev_info_free _fido_dev_info_manifest _fido_dev_info_manufacturer_string @@ -173,6 +180,8 @@ _fido_dev_reset _fido_dev_set_io_functions _fido_dev_set_pin _fido_dev_set_transport_functions +_fido_dev_supports_cred_prot +_fido_dev_supports_pin _fido_init _fido_set_log_handler _fido_strerr diff --git a/src/export.msvc b/src/export.msvc index 06ec69a..1a2a0b7 100644 --- a/src/export.msvc +++ b/src/export.msvc @@ -75,6 +75,8 @@ fido_cbor_info_extensions_len fido_cbor_info_extensions_ptr fido_cbor_info_free fido_cbor_info_maxmsgsiz +fido_cbor_info_maxcredcntlst +fido_cbor_info_maxcredidlen fido_cbor_info_fwversion fido_cbor_info_new fido_cbor_info_options_len @@ -95,6 +97,8 @@ fido_cred_fmt fido_cred_free fido_cred_id_len fido_cred_id_ptr +fido_cred_aaguid_len +fido_cred_aaguid_ptr fido_credman_del_dev_rk fido_credman_get_dev_metadata fido_credman_get_dev_rk @@ -154,6 +158,9 @@ fido_dev_free fido_dev_get_assert fido_dev_get_cbor_info fido_dev_get_retry_count +fido_dev_get_touch_begin +fido_dev_get_touch_status +fido_dev_has_pin fido_dev_info_free fido_dev_info_manifest fido_dev_info_manufacturer_string @@ -174,6 +181,8 @@ fido_dev_reset fido_dev_set_io_functions fido_dev_set_pin fido_dev_set_transport_functions +fido_dev_supports_cred_prot +fido_dev_supports_pin fido_init fido_set_log_handler fido_strerr diff --git a/src/extern.h b/src/extern.h index fc0a49d..4c036cb 100644 --- a/src/extern.h +++ b/src/extern.h @@ -88,6 +88,8 @@ void *fido_hid_open(const char *); void fido_hid_close(void *); int fido_hid_read(void *, unsigned char *, size_t, int); int fido_hid_write(void *, const unsigned char *, size_t); +size_t fido_hid_report_in_len(void *); +size_t fido_hid_report_out_len(void *); /* generic i/o */ int fido_rx_cbor_status(fido_dev_t *, int); @@ -115,6 +117,8 @@ void fido_log_xxd(const void *, size_t); /* u2f */ int u2f_register(fido_dev_t *, fido_cred_t *, int); int u2f_authenticate(fido_dev_t *, fido_assert_t *, int); +int u2f_get_touch_begin(fido_dev_t *); +int u2f_get_touch_status(fido_dev_t *, int *, int); /* unexposed fido ops */ int fido_dev_authkey(fido_dev_t *, es256_pk_t *); @@ -149,6 +153,22 @@ typedef int (*dev_manifest_func_t)(fido_dev_info_t *, size_t, size_t *); int fido_dev_register_manifest_func(const dev_manifest_func_t); void fido_dev_unregister_manifest_func(const dev_manifest_func_t); +/* fuzzing instrumentation */ +#ifdef FIDO_FUZZ +uint32_t uniform_random(uint32_t); +#endif + +/* internal device capability flags */ +#define FIDO_DEV_PIN_SET 0x01 +#define FIDO_DEV_PIN_UNSET 0x02 +#define FIDO_DEV_CRED_PROT 0x04 + +/* miscellanea */ +#define FIDO_DUMMY_CLIENTDATA "" +#define FIDO_DUMMY_RP_ID "localhost" +#define FIDO_DUMMY_USER_NAME "dummy" +#define FIDO_DUMMY_USER_ID 1 + #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ diff --git a/src/fido.h b/src/fido.h index e41de89..baa2928 100644 --- a/src/fido.h +++ b/src/fido.h @@ -32,6 +32,12 @@ extern "C" { #endif /* __cplusplus */ +#ifdef _MSC_VER +#define FIDO_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define FIDO_DEPRECATED(reason) __attribute__((__deprecated__(reason))) +#endif + fido_assert_t *fido_assert_new(void); fido_cred_t *fido_cred_new(void); fido_dev_t *fido_dev_new(void); @@ -82,6 +88,7 @@ const unsigned char *fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *); const unsigned char *fido_cred_authdata_ptr(const fido_cred_t *); const unsigned char *fido_cred_clientdata_hash_ptr(const fido_cred_t *); const unsigned char *fido_cred_id_ptr(const fido_cred_t *); +const unsigned char *fido_cred_aaguid_ptr(const fido_cred_t *); const unsigned char *fido_cred_user_id_ptr(const fido_cred_t *); const unsigned char *fido_cred_pubkey_ptr(const fido_cred_t *); const unsigned char *fido_cred_sig_ptr(const fido_cred_t *); @@ -97,8 +104,8 @@ int fido_assert_set_clientdata_hash(fido_assert_t *, const unsigned char *, int fido_assert_set_count(fido_assert_t *, size_t); int fido_assert_set_extensions(fido_assert_t *, int); int fido_assert_set_hmac_salt(fido_assert_t *, const unsigned char *, size_t); -int fido_assert_set_options(fido_assert_t *, bool, bool) - __attribute__((__deprecated__("use fido_assert_set_up/fido_assert_set_uv"))); +FIDO_DEPRECATED("use fido_assert_set_up/fido_assert_set_uv") +int fido_assert_set_options(fido_assert_t *, bool, bool); int fido_assert_set_rp(fido_assert_t *, const char *); int fido_assert_set_up(fido_assert_t *, fido_opt_t); int fido_assert_set_uv(fido_assert_t *, fido_opt_t); @@ -111,8 +118,8 @@ int fido_cred_set_authdata_raw(fido_cred_t *, const unsigned char *, size_t); int fido_cred_set_clientdata_hash(fido_cred_t *, const unsigned char *, size_t); int fido_cred_set_extensions(fido_cred_t *, int); int fido_cred_set_fmt(fido_cred_t *, const char *); -int fido_cred_set_options(fido_cred_t *, bool, bool) - __attribute__((__deprecated__("use fido_cred_set_rk/fido_cred_set_uv"))); +FIDO_DEPRECATED("use fido_cred_set_rk/fido_cred_set_uv") +int fido_cred_set_options(fido_cred_t *, bool, bool); int fido_cred_set_prot(fido_cred_t *, int); int fido_cred_set_rk(fido_cred_t *, fido_opt_t); int fido_cred_set_rp(fido_cred_t *, const char *, const char *); @@ -130,6 +137,8 @@ int fido_dev_close(fido_dev_t *); int fido_dev_get_assert(fido_dev_t *, fido_assert_t *, const char *); int fido_dev_get_cbor_info(fido_dev_t *, fido_cbor_info_t *); int fido_dev_get_retry_count(fido_dev_t *, int *); +int fido_dev_get_touch_begin(fido_dev_t *); +int fido_dev_get_touch_status(fido_dev_t *, int *, int); int fido_dev_info_manifest(fido_dev_info_t *, size_t, size_t *); int fido_dev_make_cred(fido_dev_t *, fido_cred_t *, const char *); int fido_dev_open_with_info(fido_dev_t *); @@ -154,6 +163,7 @@ size_t fido_cbor_info_versions_len(const fido_cbor_info_t *); size_t fido_cred_authdata_len(const fido_cred_t *); size_t fido_cred_clientdata_hash_len(const fido_cred_t *); size_t fido_cred_id_len(const fido_cred_t *); +size_t fido_cred_aaguid_len(const fido_cred_t *); size_t fido_cred_user_id_len(const fido_cred_t *); size_t fido_cred_pubkey_len(const fido_cred_t *); size_t fido_cred_sig_len(const fido_cred_t *); @@ -170,9 +180,14 @@ uint8_t fido_dev_flags(const fido_dev_t *); int16_t fido_dev_info_vendor(const fido_dev_info_t *); int16_t fido_dev_info_product(const fido_dev_info_t *); uint64_t fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredidlen(const fido_cbor_info_t *); uint64_t fido_cbor_info_fwversion(const fido_cbor_info_t *); +bool fido_dev_has_pin(const fido_dev_t *); bool fido_dev_is_fido2(const fido_dev_t *); +bool fido_dev_supports_pin(const fido_dev_t *); +bool fido_dev_supports_cred_prot(const fido_dev_t *); #ifdef __cplusplus } /* extern "C" */ diff --git a/src/fido/err.h b/src/fido/err.h index d7453fc..253914f 100644 --- a/src/fido/err.h +++ b/src/fido/err.h @@ -21,6 +21,7 @@ #define FIDO_ERR_MISSING_PARAMETER 0x14 #define FIDO_ERR_LIMIT_EXCEEDED 0x15 #define FIDO_ERR_UNSUPPORTED_EXTENSION 0x16 +#define FIDO_ERR_FP_DATABASE_FULL 0x17 #define FIDO_ERR_CREDENTIAL_EXCLUDED 0x19 #define FIDO_ERR_PROCESSING 0x21 #define FIDO_ERR_INVALID_CREDENTIAL 0x22 @@ -49,6 +50,7 @@ #define FIDO_ERR_REQUEST_TOO_LARGE 0x39 #define FIDO_ERR_ACTION_TIMEOUT 0x3a #define FIDO_ERR_UP_REQUIRED 0x3b +#define FIDO_ERR_UV_BLOCKED 0x3c #define FIDO_ERR_ERR_OTHER 0x7f #define FIDO_ERR_SPEC_LAST 0xdf diff --git a/src/fido/param.h b/src/fido/param.h index 7d3c0cc..14ee74e 100644 --- a/src/fido/param.h +++ b/src/fido/param.h @@ -50,8 +50,14 @@ /* HID Broadcast channel ID. */ #define CTAP_CID_BROADCAST 0xffffffff -/* Expected size of a HID report in bytes. */ -#define CTAP_RPT_SIZE 64 +#define CTAP_INIT_HEADER_LEN 7 +#define CTAP_CONT_HEADER_LEN 5 + +/* Maximum length of a CTAP HID report in bytes. */ +#define CTAP_MAX_REPORT_LEN 64 + +/* Minimum length of a CTAP HID report in bytes. */ +#define CTAP_MIN_REPORT_LEN (CTAP_INIT_HEADER_LEN + 1) /* Randomness device on UNIX-like platforms. */ #ifndef FIDO_RANDOM_DEV @@ -60,7 +66,7 @@ /* Maximum message size in bytes. */ #ifndef FIDO_MAXMSG -#define FIDO_MAXMSG 1200 +#define FIDO_MAXMSG 2048 #endif /* CTAP capability bits. */ diff --git a/src/fido/types.h b/src/fido/types.h index 5df5e36..cce1a44 100644 --- a/src/fido/types.h +++ b/src/fido/types.h @@ -175,13 +175,15 @@ typedef struct fido_byte_array { } fido_byte_array_t; typedef struct fido_cbor_info { - fido_str_array_t versions; /* supported versions: fido2|u2f */ - fido_str_array_t extensions; /* list of supported extensions */ - unsigned char aaguid[16]; /* aaguid */ - fido_opt_array_t options; /* list of supported options */ - uint64_t maxmsgsiz; /* maximum message size */ - fido_byte_array_t protocols; /* supported pin protocols */ - uint64_t fwversion; /* firmware version */ + fido_str_array_t versions; /* supported versions: fido2|u2f */ + fido_str_array_t extensions; /* list of supported extensions */ + unsigned char aaguid[16]; /* aaguid */ + fido_opt_array_t options; /* list of supported options */ + uint64_t maxmsgsiz; /* maximum message size */ + fido_byte_array_t protocols; /* supported pin protocols */ + uint64_t maxcredcntlst; /* max number of credentials in list */ + uint64_t maxcredidlen; /* max credential ID length */ + uint64_t fwversion; /* firmware version */ } fido_cbor_info_t; typedef struct fido_dev_info { @@ -213,6 +215,10 @@ typedef struct fido_dev { char *path; /* device path */ void *io_handle; /* abstract i/o handle */ fido_dev_io_t io; /* i/o functions */ + bool io_own; /* device has own io/transport */ + size_t rx_len; /* length of HID input reports */ + size_t tx_len; /* length of HID output reports */ + int flags; /* internal flags; see FIDO_DEV_* */ fido_dev_transport_t transport; /* transport functions */ } fido_dev_t; diff --git a/src/hid_hidapi.c b/src/hid_hidapi.c index 915621f..898fd9e 100644 --- a/src/hid_hidapi.c +++ b/src/hid_hidapi.c @@ -4,14 +4,26 @@ * license that can be found in the LICENSE file. */ -#include +#ifdef __linux__ +#include +#include +#include +#include +#endif +#include #include #include #include #include "fido.h" +struct hid_hidapi { + void *handle; + size_t report_in_len; + size_t report_out_len; +}; + static size_t fido_wcslen(const wchar_t *wcs) { @@ -27,7 +39,7 @@ wcs_to_cs(const wchar_t *wcs) char *cs; size_t i; - if (wcs == NULL || (cs = calloc(fido_wcslen(wcs) + 1, 1)) == NULL) + if (wcs == NULL || (cs = calloc(fido_wcslen(wcs) + 1, 1)) == NULL) return NULL; for (i = 0; i < fido_wcslen(wcs); i++) { @@ -68,11 +80,12 @@ copy_info(fido_dev_info_t *di, const struct hid_device_info *d) free(di->path); free(di->manufacturer); free(di->product); + explicit_bzero(di, sizeof(*di)); return -1; } - di->product_id = d->product_id; - di->vendor_id = d->vendor_id; + di->product_id = (int16_t)d->product_id; + di->vendor_id = (int16_t)d->vendor_id; di->io = (fido_dev_io_t) { &fido_hid_open, &fido_hid_close, @@ -83,28 +96,199 @@ copy_info(fido_dev_info_t *di, const struct hid_device_info *d) return 0; } +#ifdef __linux__ +static int +get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) +{ + *key = tag & 0xfc; + if ((*key & 0xf0) == 0xf0) { + fido_log_debug("%s: *key=0x%02x", __func__, *key); + return -1; + } + + *key_len = tag & 0x3; + if (*key_len == 3) { + *key_len = 4; + } + + return 0; +} + +static int +get_key_val(const void *body, size_t key_len, uint32_t *val) +{ + const uint8_t *ptr = body; + + switch (key_len) { + case 0: + *val = 0; + break; + case 1: + *val = ptr[0]; + break; + case 2: + *val = (uint32_t)((ptr[1] << 8) | ptr[0]); + break; + default: + fido_log_debug("%s: key_len=%zu", __func__, key_len); + return -1; + } + + return 0; +} + +static int +get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page, + uint32_t *usage) +{ + const uint8_t *ptr = hrd->value; + size_t len = hrd->size; + + while (len > 0) { + const uint8_t tag = ptr[0]; + + ptr++; + len--; + + uint8_t key; + size_t key_len; + uint32_t key_val; + + if (get_key_len(tag, &key, &key_len) < 0 || key_len > len || + get_key_val(ptr, key_len, &key_val) < 0) { + return -1; + } + + if (key == 0x4) { + *usage_page = key_val; + } else if (key == 0x8) { + *usage = key_val; + } + + ptr += key_len; + len -= key_len; + } + + return 0; +} + +static int +get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd) +{ + int fd; + int s = -1; + int ok = -1; + + if ((fd = open(path, O_RDONLY)) < 0) { + fido_log_debug("%s: open", __func__); + return -1; + } + + if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 || + (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) { + fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__); + goto fail; + } + + hrd->size = (unsigned)s; + + if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) { + fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__); + goto fail; + } + + ok = 0; +fail: + if (fd != -1) + close(fd); + + return ok; +} + +static bool +is_fido(const struct hid_device_info *hdi) +{ + uint32_t usage = 0; + uint32_t usage_page = 0; + struct hidraw_report_descriptor hrd; + + memset(&hrd, 0, sizeof(hrd)); + + if (get_report_descriptor(hdi->path, &hrd) < 0 || + get_usage_info(&hrd, &usage_page, &usage) < 0) { + return false; + } + + return usage_page == 0xf1d0; +} +#elif defined(_WIN32) || defined(__APPLE__) +static bool +is_fido(const struct hid_device_info *hdi) +{ + return hdi->usage_page == 0xf1d0; +} +#else +static bool +is_fido(const struct hid_device_info *hdi) +{ + (void)hdi; + fido_log_debug("%s: assuming FIDO HID", __func__); + return true; +} +#endif + void * fido_hid_open(const char *path) { - return hid_open_path(path); + struct hid_hidapi *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + return (NULL); + } + + if ((ctx->handle = hid_open_path(path)) == NULL) { + free(ctx); + return (NULL); + } + + ctx->report_in_len = ctx->report_out_len = CTAP_MAX_REPORT_LEN; + + return ctx; } void -fido_hid_close(void *hid_dev_handle) +fido_hid_close(void *handle) { - hid_close(hid_dev_handle); + struct hid_hidapi *ctx = handle; + + hid_close(ctx->handle); + free(ctx); } int -fido_hid_read(void *hid_dev_handle, unsigned char *buf, size_t len, int ms) +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) { - return hid_read_timeout(hid_dev_handle, buf, len, ms); + struct hid_hidapi *ctx = handle; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return -1; + } + + return hid_read_timeout(ctx->handle, buf, len, ms); } int -fido_hid_write(void *hid_dev_handle, const unsigned char *buf, size_t len) +fido_hid_write(void *handle, const unsigned char *buf, size_t len) { - return hid_write(hid_dev_handle, buf, len); + struct hid_hidapi *ctx = handle; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return -1; + } + + return hid_write(ctx->handle, buf, len); } int @@ -122,10 +306,8 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) return FIDO_OK; /* nothing to do */ for (struct hid_device_info *d = hdi; d != NULL; d = d->next) { -#if defined(_WIN32) || defined(__APPLE__) - if (d->usage_page != 0xf1d0) + if (is_fido(d) == false) continue; -#endif if (copy_info(&devlist[*olen], d) == 0) { if (++(*olen) == ilen) break; @@ -136,3 +318,19 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) return FIDO_OK; } + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_hidapi *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_hidapi *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_linux.c b/src/hid_linux.c index 99c5afb..9788012 100644 --- a/src/hid_linux.c +++ b/src/hid_linux.c @@ -8,16 +8,22 @@ #include #include +#include +#include #include #include +#include #include #include -#include #include "fido.h" -#define REPORT_LEN 65 +struct hid_linux { + int fd; + size_t report_in_len; + size_t report_out_len; +}; static int get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) @@ -63,11 +69,8 @@ static int get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page, uint32_t *usage) { - const uint8_t *ptr; - size_t len; - - ptr = hrd->value; - len = hrd->size; + const uint8_t *ptr = hrd->value; + size_t len = hrd->size; while (len > 0) { const uint8_t tag = ptr[0]; @@ -97,80 +100,113 @@ get_usage_info(const struct hidraw_report_descriptor *hrd, uint32_t *usage_page, } static int -get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd) +get_report_sizes(const struct hidraw_report_descriptor *hrd, + size_t *report_in_len, size_t *report_out_len) { - int s = -1; - int fd; - int ok = -1; + const uint8_t *ptr = hrd->value; + size_t len = hrd->size; + uint32_t report_size = 0; - if ((fd = open(path, O_RDONLY)) < 0) { - fido_log_debug("%s: open", __func__); - return (-1); + while (len > 0) { + const uint8_t tag = ptr[0]; + ptr++; + len--; + + uint8_t key; + size_t key_len; + uint32_t key_val; + + if (get_key_len(tag, &key, &key_len) < 0 || key_len > len || + get_key_val(ptr, key_len, &key_val) < 0) { + return (-1); + } + + if (key == 0x94) { + report_size = key_val; + } else if (key == 0x80) { + *report_in_len = (size_t)report_size; + } else if (key == 0x90) { + *report_out_len = (size_t)report_size; + } + + ptr += key_len; + len -= key_len; } + return (0); +} + +static int +get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd) +{ + int s = -1; + if (ioctl(fd, HIDIOCGRDESCSIZE, &s) < 0 || s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) { fido_log_debug("%s: ioctl HIDIOCGRDESCSIZE", __func__); - goto fail; + return (-1); } - hrd->size = s; + hrd->size = (unsigned)s; if (ioctl(fd, HIDIOCGRDESC, hrd) < 0) { fido_log_debug("%s: ioctl HIDIOCGRDESC", __func__); - goto fail; + return (-1); } - ok = 0; -fail: - if (fd != -1) - close(fd); - - return (ok); + return (0); } static bool is_fido(const char *path) { + int fd; uint32_t usage = 0; uint32_t usage_page = 0; struct hidraw_report_descriptor hrd; memset(&hrd, 0, sizeof(hrd)); - if (get_report_descriptor(path, &hrd) < 0 || + if ((fd = open(path, O_RDONLY)) == -1) { + fido_log_debug("%s: open", __func__); + return (false); + } + + if (get_report_descriptor(fd, &hrd) < 0 || get_usage_info(&hrd, &usage_page, &usage) < 0) { + close(fd); return (false); } + close(fd); + return (usage_page == 0xf1d0); } static int -parse_uevent(struct udev_device *dev, int16_t *vendor_id, int16_t *product_id) +parse_uevent(const char *uevent, int *bus, int16_t *vendor_id, + int16_t *product_id) { - const char *uevent; char *cp; char *p; char *s; int ok = -1; short unsigned int x; short unsigned int y; - - if ((uevent = udev_device_get_sysattr_value(dev, "uevent")) == NULL) - return (-1); + short unsigned int z; if ((s = cp = strdup(uevent)) == NULL) return (-1); - for ((p = strsep(&cp, "\n")); p && *p != '\0'; (p = strsep(&cp, "\n"))) { + while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') { if (strncmp(p, "HID_ID=", 7) == 0) { - if (sscanf(p + 7, "%*x:%hx:%hx", &x, &y) == 2) { - *vendor_id = (int16_t)x; - *product_id = (int16_t)y; + if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) { + *bus = (int)x; + *vendor_id = (int16_t)y; + *product_id = (int16_t)z; ok = 0; + break; } - break; } } @@ -179,17 +215,36 @@ parse_uevent(struct udev_device *dev, int16_t *vendor_id, int16_t *product_id) return (ok); } +static char * +get_parent_attr(struct udev_device *dev, const char *subsystem, + const char *devtype, const char *attr) +{ + struct udev_device *parent; + const char *value; + + if ((parent = udev_device_get_parent_with_subsystem_devtype(dev, + subsystem, devtype)) == NULL || (value = + udev_device_get_sysattr_value(parent, attr)) == NULL) + return (NULL); + + return (strdup(value)); +} + +static char * +get_usb_attr(struct udev_device *dev, const char *attr) +{ + return (get_parent_attr(dev, "usb", "usb_device", attr)); +} + static int copy_info(fido_dev_info_t *di, struct udev *udev, struct udev_list_entry *udev_entry) { const char *name; const char *path; - const char *manufacturer; - const char *product; + char *uevent = NULL; struct udev_device *dev = NULL; - struct udev_device *hid_parent; - struct udev_device *usb_parent; + int bus = 0; int ok = -1; memset(di, 0, sizeof(*di)); @@ -200,28 +255,24 @@ copy_info(fido_dev_info_t *di, struct udev *udev, is_fido(path) == 0) goto fail; - if ((hid_parent = udev_device_get_parent_with_subsystem_devtype(dev, - "hid", NULL)) == NULL) - goto fail; - - if ((usb_parent = udev_device_get_parent_with_subsystem_devtype(dev, - "usb", "usb_device")) == NULL) + if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL || + parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) { + fido_log_debug("%s: uevent", __func__); goto fail; + } - if (parse_uevent(hid_parent, &di->vendor_id, &di->product_id) < 0 || - (manufacturer = udev_device_get_sysattr_value(usb_parent, - "manufacturer")) == NULL || - (product = udev_device_get_sysattr_value(usb_parent, - "product")) == NULL) +#ifndef FIDO_HID_ANY + if (bus != BUS_USB) { + fido_log_debug("%s: bus", __func__); goto fail; + } +#endif di->path = strdup(path); - di->manufacturer = strdup(manufacturer); - di->product = strdup(product); + di->manufacturer = get_usb_attr(dev, "manufacturer"); + di->product = get_usb_attr(dev, "product"); - if (di->path == NULL || - di->manufacturer == NULL || - di->product == NULL) + if (di->path == NULL || di->manufacturer == NULL || di->product == NULL) goto fail; ok = 0; @@ -229,6 +280,8 @@ fail: if (dev != NULL) udev_device_unref(dev); + free(uevent); + if (ok < 0) { free(di->path); free(di->manufacturer); @@ -261,9 +314,13 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) goto fail; if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 || - udev_enumerate_scan_devices(udev_enum) < 0 || - (udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) + udev_enumerate_scan_devices(udev_enum) < 0) + goto fail; + + if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) { + r = FIDO_OK; /* zero hidraw devices */ goto fail; + } udev_list_entry_foreach(udev_entry, udev_list) { if (copy_info(&devlist[*olen], udev, udev_entry) == 0) { @@ -291,60 +348,157 @@ fail: void * fido_hid_open(const char *path) { - int *fd; + struct hid_linux *ctx; + struct hidraw_report_descriptor hrd; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); - if ((fd = malloc(sizeof(*fd))) == NULL || - (*fd = open(path, O_RDWR)) < 0) { - free(fd); + if ((ctx->fd = open(path, O_RDWR)) < 0) { + free(ctx); return (NULL); } - return (fd); + if (get_report_descriptor(ctx->fd, &hrd) < 0 || get_report_sizes(&hrd, + &ctx->report_in_len, &ctx->report_out_len) < 0 || + ctx->report_in_len == 0 || ctx->report_out_len == 0) { + fido_log_debug("%s: using default report sizes", __func__); + ctx->report_in_len = CTAP_MAX_REPORT_LEN; + ctx->report_out_len = CTAP_MAX_REPORT_LEN; + } + + return (ctx); } void fido_hid_close(void *handle) { - int *fd = handle; + struct hid_linux *ctx = handle; + + close(ctx->fd); + free(ctx); +} + +static int +timespec_to_ms(const struct timespec *ts, int upper_bound) +{ + int64_t x; + int64_t y; + + if (ts->tv_sec < 0 || ts->tv_sec > INT64_MAX / 1000LL || + ts->tv_nsec < 0 || ts->tv_nsec / 1000000LL > INT64_MAX) + return (upper_bound); + + x = ts->tv_sec * 1000LL; + y = ts->tv_nsec / 1000000LL; + + if (INT64_MAX - x < y || x + y > upper_bound) + return (upper_bound); + + return (int)(x + y); +} + +static int +waitfd(int fd, int ms) +{ + struct timespec ts_start; + struct timespec ts_now; + struct timespec ts_delta; + struct pollfd pfd; + int ms_remain; + int r; + + if (ms < 0) + return (0); + + memset(&pfd, 0, sizeof(pfd)); + pfd.events = POLLIN; + pfd.fd = fd; + + if (clock_gettime(CLOCK_MONOTONIC, &ts_start) != 0) { + fido_log_debug("%s: clock_gettime: %s", __func__, + strerror(errno)); + return (-1); + } + + for (ms_remain = ms; ms_remain > 0;) { + if ((r = poll(&pfd, 1, ms_remain)) > 0) + return (0); + else if (r == 0) + break; + else if (errno != EINTR) { + fido_log_debug("%s: poll: %s", __func__, + strerror(errno)); + return (-1); + } + /* poll interrupted - subtract time already waited */ + if (clock_gettime(CLOCK_MONOTONIC, &ts_now) != 0) { + fido_log_debug("%s: clock_gettime: %s", __func__, + strerror(errno)); + return (-1); + } + timespecsub(&ts_now, &ts_start, &ts_delta); + ms_remain = ms - timespec_to_ms(&ts_delta, ms); + } - close(*fd); - free(fd); + return (-1); } int fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) { - int *fd = handle; - ssize_t r; + struct hid_linux *ctx = handle; + ssize_t r; - (void)ms; /* XXX */ + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } - if (len != REPORT_LEN - 1) { - fido_log_debug("%s: invalid len", __func__); + if (waitfd(ctx->fd, ms) < 0) { + fido_log_debug("%s: fd not ready", __func__); return (-1); } - if ((r = read(*fd, buf, len)) < 0 || r != REPORT_LEN - 1) + if ((r = read(ctx->fd, buf, len)) < 0 || (size_t)r != len) { + fido_log_debug("%s: read", __func__); return (-1); + } - return (REPORT_LEN - 1); + return ((int)r); } int fido_hid_write(void *handle, const unsigned char *buf, size_t len) { - int *fd = handle; - ssize_t r; + struct hid_linux *ctx = handle; + ssize_t r; - if (len != REPORT_LEN) { - fido_log_debug("%s: invalid len", __func__); + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); return (-1); } - if ((r = write(*fd, buf, len)) < 0 || r != REPORT_LEN) { + if ((r = write(ctx->fd, buf, len)) < 0 || (size_t)r != len) { fido_log_debug("%s: write", __func__); return (-1); } - return (REPORT_LEN); + return ((int)r); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_linux *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_linux *ctx = handle; + + return (ctx->report_out_len); } diff --git a/src/hid_openbsd.c b/src/hid_openbsd.c index 2b31dba..319f7b8 100644 --- a/src/hid_openbsd.c +++ b/src/hid_openbsd.c @@ -8,19 +8,16 @@ #include #include -#include #include #include #include #include -#include #include #include "fido.h" #define MAX_UHID 64 -#define MAX_REPORT_LEN (sizeof(((struct usb_ctl_report *)(NULL))->ucr_data)) struct hid_openbsd { int fd; @@ -33,11 +30,8 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) { size_t i; char path[64]; - int is_fido, fd; + int fd; struct usb_device_info udi; - report_desc_t rdesc = NULL; - hid_data_t hdata = NULL; - hid_item_t hitem; fido_dev_info_t *di; if (ilen == 0) @@ -47,7 +41,7 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) return (FIDO_ERR_INVALID_ARGUMENT); for (i = *olen = 0; i < MAX_UHID && *olen < ilen; i++) { - snprintf(path, sizeof(path), "/dev/uhid%zu", i); + snprintf(path, sizeof(path), "/dev/fido/%zu", i); if ((fd = open(path, O_RDWR)) == -1) { if (errno != ENOENT && errno != ENXIO) { fido_log_debug("%s: open %s: %s", __func__, @@ -55,6 +49,7 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) } continue; } + memset(&udi, 0, sizeof(udi)); if (ioctl(fd, USB_GET_DEVICEINFO, &udi) != 0) { fido_log_debug("%s: get device info %s: %s", __func__, @@ -62,35 +57,8 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) close(fd); continue; } - if ((rdesc = hid_get_report_desc(fd)) == NULL) { - fido_log_debug("%s: failed to get report descriptor: %s", - __func__, path); - close(fd); - continue; - } - if ((hdata = hid_start_parse(rdesc, - 1<vendor_id = udi.udi_vendorNo; - di->product_id = udi.udi_productNo; + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; (*olen)++; } @@ -184,42 +152,15 @@ void * fido_hid_open(const char *path) { struct hid_openbsd *ret = NULL; - report_desc_t rdesc = NULL; - int len, usb_report_id = 0; if ((ret = calloc(1, sizeof(*ret))) == NULL || (ret->fd = open(path, O_RDWR)) < 0) { free(ret); return (NULL); } - if (ioctl(ret->fd, USB_GET_REPORT_ID, &usb_report_id) != 0) { - fido_log_debug("%s: failed to get report ID: %s", __func__, - strerror(errno)); - goto fail; - } - if ((rdesc = hid_get_report_desc(ret->fd)) == NULL) { - fido_log_debug("%s: failed to get report descriptor", __func__); - goto fail; - } - if ((len = hid_report_size(rdesc, hid_input, usb_report_id)) <= 0 || - (size_t)len > MAX_REPORT_LEN) { - fido_log_debug("%s: bad input report size %d", __func__, len); - goto fail; - } - ret->report_in_len = (size_t)len; - if ((len = hid_report_size(rdesc, hid_output, usb_report_id)) <= 0 || - (size_t)len > MAX_REPORT_LEN) { - fido_log_debug("%s: bad output report size %d", __func__, len); - fail: - hid_dispose_report_desc(rdesc); - close(ret->fd); - free(ret); - return NULL; - } - ret->report_out_len = (size_t)len; - hid_dispose_report_desc(rdesc); - fido_log_debug("%s: USB report ID %d, inlen = %zu outlen = %zu", - __func__, usb_report_id, ret->report_in_len, ret->report_out_len); + ret->report_in_len = ret->report_out_len = CTAP_MAX_REPORT_LEN; + fido_log_debug("%s: inlen = %zu outlen = %zu", __func__, + ret->report_in_len, ret->report_out_len); /* * OpenBSD (as of 201910) has a bug that causes it to lose @@ -281,3 +222,19 @@ fido_hid_write(void *handle, const unsigned char *buf, size_t len) } return ((int)len); } + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_openbsd *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_openbsd *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_osx.c b/src/hid_osx.c index 5c40747..6be5cd7 100644 --- a/src/hid_osx.c +++ b/src/hid_osx.c @@ -19,11 +19,13 @@ #include "fido.h" -#define REPORT_LEN 65 - -struct dev { +struct hid_osx { IOHIDDeviceRef ref; CFStringRef loop_id; + int report_pipe[2]; + size_t report_in_len; + size_t report_out_len; + unsigned char report[CTAP_MAX_REPORT_LEN]; }; static int @@ -64,7 +66,8 @@ get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len) return (-1); } - if (CFStringGetCString(ref, buf, len, kCFStringEncodingUTF8) == false) { + if (CFStringGetCString(ref, buf, (long)len, + kCFStringEncodingUTF8) == false) { fido_log_debug("%s: CFStringGetCString", __func__); return (-1); } @@ -72,30 +75,35 @@ get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len) return (0); } -static bool -is_fido(IOHIDDeviceRef dev) +static int +get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len) { - uint32_t usage_page; - int32_t report_len; + CFStringRef key; + int32_t v; - if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey), - (int32_t *)&usage_page) != 0 || usage_page != 0xf1d0) - return (false); + if (dir == 0) + key = CFSTR(kIOHIDMaxInputReportSizeKey); + else + key = CFSTR(kIOHIDMaxOutputReportSizeKey); - if (get_int32(dev, CFSTR(kIOHIDMaxInputReportSizeKey), - &report_len) < 0 || report_len != REPORT_LEN - 1) { - fido_log_debug("%s: unsupported report len", __func__); - return (false); + if (get_int32(dev, key, &v) < 0) { + fido_log_debug("%s: get_int32/%d", __func__, dir); + return (-1); } - return (true); + if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: report_len=%zu", __func__, *report_len); + return (-1); + } + + return (0); } static int get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id) { int32_t vendor; - int32_t product; + int32_t product; if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 || vendor > UINT16_MAX) { @@ -175,6 +183,31 @@ get_path(IOHIDDeviceRef dev) return (strdup(path)); } +static bool +is_fido(IOHIDDeviceRef dev) +{ + char buf[32]; + uint32_t usage_page; + + if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey), + (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0) + return (false); + + if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) { + fido_log_debug("%s: get_utf8 transport", __func__); + return (false); + } + +#ifndef FIDO_HID_ANY + if (strcasecmp(buf, "usb") != 0) { + fido_log_debug("%s: transport", __func__); + return (false); + } +#endif + + return (true); +} + static int copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev) { @@ -199,11 +232,12 @@ copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev) int fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) { - IOHIDManagerRef manager = NULL; - CFSetRef devset = NULL; - CFIndex devcnt; - IOHIDDeviceRef *devs = NULL; - int r = FIDO_ERR_INTERNAL; + IOHIDManagerRef manager = NULL; + CFSetRef devset = NULL; + size_t devcnt; + CFIndex n; + IOHIDDeviceRef *devs = NULL; + int r = FIDO_ERR_INTERNAL; *olen = 0; @@ -226,11 +260,13 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) goto fail; } - if ((devcnt = CFSetGetCount(devset)) < 0) { + if ((n = CFSetGetCount(devset)) < 0) { fido_log_debug("%s: CFSetGetCount", __func__); goto fail; } + devcnt = (size_t)n; + if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) { fido_log_debug("%s: calloc", __func__); goto fail; @@ -238,7 +274,7 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) CFSetGetValues(devset, (void *)devs); - for (CFIndex i = 0; i < devcnt; i++) { + for (size_t i = 0; i < devcnt; i++) { if (copy_info(&devlist[*olen], devs[i]) == 0) { devlist[*olen].io = (fido_dev_io_t) { fido_hid_open, @@ -263,157 +299,258 @@ fail: return (r); } +static void +report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type, + uint32_t id, uint8_t *ptr, CFIndex len) +{ + struct hid_osx *ctx = context; + ssize_t r; + + (void)dev; + + if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput || + id != 0 || len < 0 || (size_t)len != ctx->report_in_len) { + fido_log_debug("%s: io error", __func__); + return; + } + + if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) < 0 || + (size_t)r != (size_t)len) { + fido_log_debug("%s: write", __func__); + return; + } +} + +static void +removal_callback(void *context, IOReturn result, void *sender) +{ + (void)context; + (void)result; + (void)sender; + + CFRunLoopStop(CFRunLoopGetMain()); +} + +static int +set_nonblock(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1) { + fido_log_debug("%s: fcntl F_GETFL", __func__); + return (-1); + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + fido_log_debug("%s: fcntl, F_SETFL", __func__); + return (-1); + } + + return (0); +} + +static int +disable_sigpipe(int fd) +{ + int disabled = 1; + + if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) { + fido_log_debug("%s: fcntl F_SETNOSIGPIPE", __func__); + return (-1); + } + + return (0); +} + void * fido_hid_open(const char *path) { + struct hid_osx *ctx; io_registry_entry_t entry = MACH_PORT_NULL; - struct dev *dev = NULL; + char loop_id[32]; int ok = -1; int r; - char loop_id[32]; - if ((dev = calloc(1, sizeof(*dev))) == NULL) { + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { fido_log_debug("%s: calloc", __func__); goto fail; } + ctx->report_pipe[0] = -1; + ctx->report_pipe[1] = -1; + + if (pipe(ctx->report_pipe) == -1) { + fido_log_debug("%s: pipe", __func__); + goto fail; + } + + if (set_nonblock(ctx->report_pipe[0]) < 0 || + set_nonblock(ctx->report_pipe[1]) < 0) { + fido_log_debug("%s: set_nonblock", __func__); + goto fail; + } + + if (disable_sigpipe(ctx->report_pipe[1]) < 0) { + fido_log_debug("%s: disable_sigpipe", __func__); + goto fail; + } + if ((entry = IORegistryEntryFromPath(kIOMasterPortDefault, path)) == MACH_PORT_NULL) { fido_log_debug("%s: IORegistryEntryFromPath", __func__); goto fail; } - if ((dev->ref = IOHIDDeviceCreate(kCFAllocatorDefault, + if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault, entry)) == NULL) { fido_log_debug("%s: IOHIDDeviceCreate", __func__); goto fail; } - if (IOHIDDeviceOpen(dev->ref, + if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 || + get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) { + fido_log_debug("%s: get_report_len", __func__); + goto fail; + } + + if (ctx->report_in_len > sizeof(ctx->report)) { + fido_log_debug("%s: report_in_len=%zu", __func__, + ctx->report_in_len); + goto fail; + } + + if (IOHIDDeviceOpen(ctx->ref, kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) { fido_log_debug("%s: IOHIDDeviceOpen", __func__); goto fail; } if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p", - (void *)dev->ref)) < 0 || (size_t)r >= sizeof(loop_id)) { + (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) { fido_log_debug("%s: snprintf", __func__); goto fail; } - if ((dev->loop_id = CFStringCreateWithCString(NULL, loop_id, + if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id, kCFStringEncodingASCII)) == NULL) { fido_log_debug("%s: CFStringCreateWithCString", __func__); goto fail; } + IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report, + (long)ctx->report_in_len, &report_callback, ctx); + IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx); + IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetMain(), + ctx->loop_id); + ok = 0; fail: if (entry != MACH_PORT_NULL) IOObjectRelease(entry); - if (ok < 0 && dev != NULL) { - if (dev->ref != NULL) - CFRelease(dev->ref); - if (dev->loop_id != NULL) - CFRelease(dev->loop_id); - free(dev); - dev = NULL; + if (ok < 0 && ctx != NULL) { + if (ctx->ref != NULL) + CFRelease(ctx->ref); + if (ctx->loop_id != NULL) + CFRelease(ctx->loop_id); + if (ctx->report_pipe[0] != -1) + close(ctx->report_pipe[0]); + if (ctx->report_pipe[1] != -1) + close(ctx->report_pipe[1]); + free(ctx); + ctx = NULL; } - return (dev); + return (ctx); } void fido_hid_close(void *handle) { - struct dev *dev = handle; + struct hid_osx *ctx = handle; + + IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report, + (long)ctx->report_in_len, NULL, ctx); + IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, NULL); + IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetMain(), + ctx->loop_id); - if (IOHIDDeviceClose(dev->ref, + if (IOHIDDeviceClose(ctx->ref, kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) fido_log_debug("%s: IOHIDDeviceClose", __func__); - CFRelease(dev->ref); - CFRelease(dev->loop_id); - - free(dev); -} + CFRelease(ctx->ref); + CFRelease(ctx->loop_id); -static void -read_callback(void *context, IOReturn result, void *dev, IOHIDReportType type, - uint32_t report_id, uint8_t *report, CFIndex report_len) -{ - (void)context; - (void)dev; - (void)report; + explicit_bzero(ctx->report, sizeof(ctx->report)); + close(ctx->report_pipe[0]); + close(ctx->report_pipe[1]); - if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput || - report_id != 0 || report_len != REPORT_LEN - 1) { - fido_log_debug("%s: io error", __func__); - } -} - -static void -removal_callback(void *context, IOReturn result, void *sender) -{ - (void)context; - (void)result; - (void)sender; - - CFRunLoopStop(CFRunLoopGetCurrent()); + free(ctx); } int fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) { - struct dev *dev = handle; - CFRunLoopRunResult r; + struct hid_osx *ctx = handle; + ssize_t r; - (void)ms; /* XXX */ + explicit_bzero(buf, len); + explicit_bzero(ctx->report, sizeof(ctx->report)); - if (len != REPORT_LEN - 1) { - fido_log_debug("%s: invalid len", __func__); + if (len != ctx->report_in_len || len > sizeof(ctx->report)) { + fido_log_debug("%s: len %zu", __func__, len); return (-1); } - explicit_bzero(buf, len); - - IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len, - &read_callback, NULL); - IOHIDDeviceRegisterRemovalCallback(dev->ref, &removal_callback, dev); - IOHIDDeviceScheduleWithRunLoop(dev->ref, CFRunLoopGetCurrent(), - dev->loop_id); + if (ms == -1) + ms = 5000; /* wait 5 seconds by default */ - r = CFRunLoopRunInMode(dev->loop_id, 0.3, true); + if (CFRunLoopGetCurrent() != CFRunLoopGetMain()) + fido_log_debug("%s: CFRunLoopGetCurrent != CFRunLoopGetMain", + __func__); - IOHIDDeviceRegisterInputReportCallback(dev->ref, buf, len, NULL, NULL); - IOHIDDeviceRegisterRemovalCallback(dev->ref, NULL, NULL); - IOHIDDeviceUnscheduleFromRunLoop(dev->ref, CFRunLoopGetCurrent(), - dev->loop_id); + CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true); - if (r != kCFRunLoopRunHandledSource) { - fido_log_debug("%s: CFRunLoopRunInMode=%d", __func__, (int)r); + if ((r = read(ctx->report_pipe[0], buf, len)) < 0 || (size_t)r != len) { + fido_log_debug("%s: read", __func__); return (-1); } - return (REPORT_LEN - 1); + return ((int)len); } int fido_hid_write(void *handle, const unsigned char *buf, size_t len) { - struct dev *dev = handle; + struct hid_osx *ctx = handle; - if (len != REPORT_LEN) { - fido_log_debug("%s: invalid len", __func__); + if (len != ctx->report_out_len + 1 || len > LONG_MAX) { + fido_log_debug("%s: len %zu", __func__, len); return (-1); } - if (IOHIDDeviceSetReport(dev->ref, kIOHIDReportTypeOutput, 0, buf + 1, - len - 1) != kIOReturnSuccess) { + if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1, + (long)(len - 1)) != kIOReturnSuccess) { fido_log_debug("%s: IOHIDDeviceSetReport", __func__); return (-1); } - return (REPORT_LEN); + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_osx *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_osx *ctx = handle; + + return (ctx->report_out_len); } diff --git a/src/hid_win.c b/src/hid_win.c index f970589..018b4d9 100644 --- a/src/hid_win.c +++ b/src/hid_win.c @@ -14,19 +14,39 @@ #include #include #include +#include +#include #include #include #include "fido.h" -#define REPORT_LEN 65 +#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 6 +WINSETUPAPI WINBOOL WINAPI SetupDiGetDevicePropertyW(HDEVINFO, + PSP_DEVINFO_DATA, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, + DWORD, PDWORD, DWORD); +#endif + +#if defined(__MINGW32__) +DEFINE_DEVPROPKEY(DEVPKEY_Device_Parent, 0x4340a6c5, 0x93fa, 0x4706, 0x97, + 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7, 8); +#endif + +struct hid_win { + HANDLE dev; + OVERLAPPED overlap; + int report_pending; + size_t report_in_len; + size_t report_out_len; + unsigned char report[1 + CTAP_MAX_REPORT_LEN]; +}; static bool is_fido(HANDLE dev) { PHIDP_PREPARSED_DATA data = NULL; HIDP_CAPS caps; - uint16_t usage_page = 0; + int fido = 0; if (HidD_GetPreparsedData(dev, &data) == false) { fido_log_debug("%s: HidD_GetPreparsedData", __func__); @@ -38,18 +58,48 @@ is_fido(HANDLE dev) goto fail; } - if (caps.OutputReportByteLength != REPORT_LEN || - caps.InputReportByteLength != REPORT_LEN) { - fido_log_debug("%s: unsupported report len", __func__); + fido = (uint16_t)caps.UsagePage == 0xf1d0; +fail: + if (data != NULL) + HidD_FreePreparsedData(data); + + return (fido); +} + +static int +get_report_len(HANDLE dev, int dir, size_t *report_len) +{ + PHIDP_PREPARSED_DATA data = NULL; + HIDP_CAPS caps; + USHORT v; + int ok = -1; + + if (HidD_GetPreparsedData(dev, &data) == false) { + fido_log_debug("%s: HidD_GetPreparsedData/%d", __func__, dir); goto fail; } - usage_page = caps.UsagePage; + if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) { + fido_log_debug("%s: HidP_GetCaps/%d", __func__, dir); + goto fail; + } + + if (dir == 0) + v = caps.InputReportByteLength; + else + v = caps.OutputReportByteLength; + + if ((*report_len = (size_t)v) == 0) { + fido_log_debug("%s: report_len == 0", __func__); + goto fail; + } + + ok = 0; fail: if (data != NULL) HidD_FreePreparsedData(data); - return (usage_page == 0xf1d0); + return (ok); } static int @@ -59,13 +109,14 @@ get_int(HANDLE dev, int16_t *vendor_id, int16_t *product_id) attr.Size = sizeof(attr); - if (HidD_GetAttributes(dev, &attr) == false) { + if (HidD_GetAttributes(dev, &attr) == false || + attr.VendorID > INT16_MAX || attr.ProductID > INT16_MAX) { fido_log_debug("%s: HidD_GetAttributes", __func__); return (-1); } - *vendor_id = attr.VendorID; - *product_id = attr.ProductID; + *vendor_id = (int16_t)attr.VendorID; + *product_id = (int16_t)attr.ProductID; return (0); } @@ -91,7 +142,7 @@ get_str(HANDLE dev, char **manufacturer, char **product) goto fail; } - if ((*manufacturer = malloc(utf8_len)) == NULL) { + if ((*manufacturer = malloc((size_t)utf8_len)) == NULL) { fido_log_debug("%s: malloc", __func__); goto fail; } @@ -113,7 +164,7 @@ get_str(HANDLE dev, char **manufacturer, char **product) goto fail; } - if ((*product = malloc(utf8_len)) == NULL) { + if ((*product = malloc((size_t)utf8_len)) == NULL) { fido_log_debug("%s: malloc", __func__); goto fail; } @@ -136,25 +187,138 @@ fail: return (ok); } +static char * +get_path(HDEVINFO devinfo, SP_DEVICE_INTERFACE_DATA *ifdata) +{ + SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL; + char *path = NULL; + DWORD len = 0; + + /* + * "Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail + * with a NULL DeviceInterfaceDetailData pointer, a + * DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize + * variable. In response to such a call, this function returns the + * required buffer size at RequiredSize and fails with GetLastError + * returning ERROR_INSUFFICIENT_BUFFER." + */ + if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, NULL, 0, &len, + NULL) != false || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1", + __func__); + goto fail; + } + + if ((ifdetail = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + ifdetail->cbSize = sizeof(*ifdetail); + + if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, ifdetail, len, + NULL, NULL) == false) { + fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2", + __func__); + goto fail; + } + + if ((path = strdup(ifdetail->DevicePath)) == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + +fail: + free(ifdetail); + + return (path); +} + +#ifndef FIDO_HID_ANY +static bool +hid_ok(HDEVINFO devinfo, DWORD idx) +{ + SP_DEVINFO_DATA devinfo_data; + wchar_t *parent = NULL; + DWORD parent_type = DEVPROP_TYPE_STRING; + DWORD len = 0; + bool ok = false; + + memset(&devinfo_data, 0, sizeof(devinfo_data)); + devinfo_data.cbSize = sizeof(devinfo_data); + + if (SetupDiEnumDeviceInfo(devinfo, idx, &devinfo_data) == false) { + fido_log_debug("%s: SetupDiEnumDeviceInfo", __func__); + goto fail; + } + + if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, + &DEVPKEY_Device_Parent, &parent_type, NULL, 0, &len, 0) != false || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + fido_log_debug("%s: SetupDiGetDevicePropertyW 1", __func__); + goto fail; + } + + if ((parent = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, + &DEVPKEY_Device_Parent, &parent_type, (PBYTE)parent, len, NULL, + 0) == false) { + fido_log_debug("%s: SetupDiGetDevicePropertyW 2", __func__); + goto fail; + } + + ok = wcsncmp(parent, L"USB\\", 4) == 0; +fail: + free(parent); + + return (ok); +} +#endif + static int -copy_info(fido_dev_info_t *di, const char *path) +copy_info(fido_dev_info_t *di, HDEVINFO devinfo, DWORD idx, + SP_DEVICE_INTERFACE_DATA *ifdata) { HANDLE dev = INVALID_HANDLE_VALUE; int ok = -1; memset(di, 0, sizeof(*di)); - dev = CreateFileA(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (dev == INVALID_HANDLE_VALUE || is_fido(dev) == 0) + if ((di->path = get_path(devinfo, ifdata)) == NULL) { + fido_log_debug("%s: get_path", __func__); goto fail; + } - if (get_int(dev, &di->vendor_id, &di->product_id) < 0 || - get_str(dev, &di->manufacturer, &di->product) < 0) + fido_log_debug("%s: path=%s", __func__, di->path); + +#ifndef FIDO_HID_ANY + if (hid_ok(devinfo, idx) == false) { + fido_log_debug("%s: hid_ok", __func__); goto fail; + } +#endif - if ((di->path = strdup(path)) == NULL) + dev = CreateFileA(di->path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (dev == INVALID_HANDLE_VALUE) { + fido_log_debug("%s: CreateFileA", __func__); goto fail; + } + + if (is_fido(dev) == false) { + fido_log_debug("%s: is_fido", __func__); + goto fail; + } + + if (get_int(dev, &di->vendor_id, &di->product_id) < 0 || + get_str(dev, &di->manufacturer, &di->product) < 0) { + fido_log_debug("%s: get_int/get_str", __func__); + goto fail; + } ok = 0; fail: @@ -174,66 +338,30 @@ fail: int fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) { - GUID hid_guid = GUID_DEVINTERFACE_HID; - HDEVINFO devinfo = INVALID_HANDLE_VALUE; - SP_DEVICE_INTERFACE_DATA ifdata; - SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL; - DWORD len = 0; - DWORD idx = 0; - int r = FIDO_ERR_INTERNAL; + GUID hid_guid = GUID_DEVINTERFACE_HID; + HDEVINFO devinfo = INVALID_HANDLE_VALUE; + SP_DEVICE_INTERFACE_DATA ifdata; + DWORD idx; + int r = FIDO_ERR_INTERNAL; *olen = 0; if (ilen == 0) return (FIDO_OK); /* nothing to do */ - if (devlist == NULL) return (FIDO_ERR_INVALID_ARGUMENT); - devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL, - DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); - if (devinfo == INVALID_HANDLE_VALUE) { + if ((devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL, + DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)) == INVALID_HANDLE_VALUE) { fido_log_debug("%s: SetupDiGetClassDevsA", __func__); goto fail; } ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - while (SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, idx++, - &ifdata) == true) { - /* - * "Get the required buffer size. Call - * SetupDiGetDeviceInterfaceDetail with a NULL - * DeviceInterfaceDetailData pointer, a - * DeviceInterfaceDetailDataSize of zero, and a valid - * RequiredSize variable. In response to such a call, this - * function returns the required buffer size at RequiredSize - * and fails with GetLastError returning - * ERROR_INSUFFICIENT_BUFFER." - */ - if (SetupDiGetDeviceInterfaceDetailA(devinfo, &ifdata, NULL, 0, - &len, NULL) != false || - GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1", - __func__); - goto fail; - } - - if ((ifdetail = malloc(len)) == NULL) { - fido_log_debug("%s: malloc", __func__); - goto fail; - } - - ifdetail->cbSize = sizeof(*ifdetail); - - if (SetupDiGetDeviceInterfaceDetailA(devinfo, &ifdata, ifdetail, - len, NULL, NULL) == false) { - fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2", - __func__); - goto fail; - } - - if (copy_info(&devlist[*olen], ifdetail->DevicePath) == 0) { + for (idx = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, + idx, &ifdata) == true; idx++) { + if (copy_info(&devlist[*olen], devinfo, idx, &ifdata) == 0) { devlist[*olen].io = (fido_dev_io_t) { fido_hid_open, fido_hid_close, @@ -243,9 +371,6 @@ fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) if (++(*olen) == ilen) break; } - - free(ifdetail); - ifdetail = NULL; } r = FIDO_OK; @@ -253,78 +378,153 @@ fail: if (devinfo != INVALID_HANDLE_VALUE) SetupDiDestroyDeviceInfoList(devinfo); - free(ifdetail); - return (r); } void * fido_hid_open(const char *path) { - HANDLE dev; + struct hid_win *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); - dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, + ctx->dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); + FILE_FLAG_OVERLAPPED, NULL); + + if (ctx->dev == INVALID_HANDLE_VALUE) { + free(ctx); + return (NULL); + } - if (dev == INVALID_HANDLE_VALUE) + if ((ctx->overlap.hEvent = CreateEventA(NULL, FALSE, FALSE, + NULL)) == NULL) { + fido_log_debug("%s: CreateEventA", __func__); + fido_hid_close(ctx); return (NULL); + } - return (dev); + if (get_report_len(ctx->dev, 0, &ctx->report_in_len) < 0 || + get_report_len(ctx->dev, 1, &ctx->report_out_len) < 0) { + fido_log_debug("%s: get_report_len", __func__); + fido_hid_close(ctx); + return (NULL); + } + + return (ctx); } void fido_hid_close(void *handle) { - CloseHandle(handle); + struct hid_win *ctx = handle; + + if (ctx->overlap.hEvent != NULL) { + if (ctx->report_pending) { + fido_log_debug("%s: report_pending", __func__); + CancelIo(ctx->dev); + } + CloseHandle(ctx->overlap.hEvent); + } + + explicit_bzero(ctx->report, sizeof(ctx->report)); + CloseHandle(ctx->dev); + free(ctx); } int fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) { - DWORD n; - int r = -1; - uint8_t report[REPORT_LEN]; + struct hid_win *ctx = handle; + DWORD n; - (void)ms; /* XXX */ + if (len != ctx->report_in_len - 1 || len > sizeof(ctx->report) - 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (ctx->report_pending == 0) { + memset(&ctx->report, 0, sizeof(ctx->report)); + ResetEvent(ctx->overlap.hEvent); + if (ReadFile(ctx->dev, ctx->report, (DWORD)(len + 1), &n, + &ctx->overlap) == 0 && GetLastError() != ERROR_IO_PENDING) { + CancelIo(ctx->dev); + fido_log_debug("%s: ReadFile", __func__); + return (-1); + } + ctx->report_pending = 1; + } - memset(report, 0, sizeof(report)); + if (ms > -1 && WaitForSingleObject(ctx->overlap.hEvent, + (DWORD)ms) != WAIT_OBJECT_0) + return (0); - if (len != sizeof(report) - 1) { - fido_log_debug("%s: invalid len", __func__); + ctx->report_pending = 0; + + if (GetOverlappedResult(ctx->dev, &ctx->overlap, &n, TRUE) == 0) { + fido_log_debug("%s: GetOverlappedResult", __func__); return (-1); } - if (ReadFile(handle, report, sizeof(report), &n, NULL) == false || - n != sizeof(report)) { - fido_log_debug("%s: ReadFile", __func__); - goto fail; + if (n != len + 1) { + fido_log_debug("%s: expected %zu, got %zu", __func__, + len + 1, (size_t)n); + return (-1); } - r = sizeof(report) - 1; - memcpy(buf, report + 1, len); - -fail: - explicit_bzero(report, sizeof(report)); + memcpy(buf, ctx->report + 1, len); + explicit_bzero(ctx->report, sizeof(ctx->report)); - return (r); + return ((int)len); } int fido_hid_write(void *handle, const unsigned char *buf, size_t len) { - DWORD n; + struct hid_win *ctx = handle; + OVERLAPPED overlap; + DWORD n; + + memset(&overlap, 0, sizeof(overlap)); - if (len != REPORT_LEN) { - fido_log_debug("%s: invalid len", __func__); + if (len != ctx->report_out_len) { + fido_log_debug("%s: len %zu", __func__, len); return (-1); } - if (WriteFile(handle, buf, (DWORD)len, &n, NULL) == false || - n != REPORT_LEN) { + if (WriteFile(ctx->dev, buf, (DWORD)len, NULL, &overlap) == 0 && + GetLastError() != ERROR_IO_PENDING) { fido_log_debug("%s: WriteFile", __func__); return (-1); } - return (REPORT_LEN); + if (GetOverlappedResult(ctx->dev, &overlap, &n, TRUE) == 0) { + fido_log_debug("%s: GetOverlappedResult", __func__); + return (-1); + } + + if (n != len) { + fido_log_debug("%s: expected %zu, got %zu", __func__, len, + (size_t)n); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_win *ctx = handle; + + return (ctx->report_in_len - 1); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_win *ctx = handle; + + return (ctx->report_out_len - 1); } diff --git a/src/info.c b/src/info.c index 8e256fa..3199ac2 100644 --- a/src/info.c +++ b/src/info.c @@ -217,6 +217,10 @@ parse_reply_element(const cbor_item_t *key, const cbor_item_t *val, void *arg) return (cbor_decode_uint64(val, &ci->maxmsgsiz)); case 6: /* pinProtocols */ return (decode_protocols(val, &ci->protocols)); + case 7: /* maxCredentialCountInList */ + return (cbor_decode_uint64(val, &ci->maxcredcntlst)); + case 8: /* maxCredentialIdLength */ + return (cbor_decode_uint64(val, &ci->maxcredidlen)); case 14: /* fwVersion */ return (cbor_decode_uint64(val, &ci->fwversion)); default: /* ignore */ @@ -398,6 +402,18 @@ fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *ci) return (ci->maxmsgsiz); } +uint64_t +fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *ci) +{ + return (ci->maxcredcntlst); +} + +uint64_t +fido_cbor_info_maxcredidlen(const fido_cbor_info_t *ci) +{ + return (ci->maxcredidlen); +} + uint64_t fido_cbor_info_fwversion(const fido_cbor_info_t *ci) { diff --git a/src/io.c b/src/io.c index af2f49a..9d2de88 100644 --- a/src/io.c +++ b/src/io.c @@ -20,11 +20,11 @@ struct frame { uint8_t cmd; uint8_t bcnth; uint8_t bcntl; - uint8_t data[CTAP_RPT_SIZE - 7]; + uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_INIT_HEADER_LEN]; } init; struct { uint8_t seq; - uint8_t data[CTAP_RPT_SIZE - 5]; + uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_CONT_HEADER_LEN]; } cont; } body; }) @@ -38,6 +38,7 @@ tx_empty(fido_dev_t *d, uint8_t cmd) { struct frame *fp; unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; int n; memset(&pkt, 0, sizeof(pkt)); @@ -45,8 +46,8 @@ tx_empty(fido_dev_t *d, uint8_t cmd) fp->cid = d->cid; fp->body.init.cmd = CTAP_FRAME_INIT | cmd; - n = d->io.write(d->io_handle, pkt, sizeof(pkt)); - if (n < 0 || (size_t)n != sizeof(pkt)) + if (len > sizeof(pkt) || (n = d->io.write(d->io_handle, pkt, + len)) < 0 || (size_t)n != len) return (-1); return (0); @@ -57,19 +58,23 @@ tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count) { struct frame *fp; unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; int n; + if (d->tx_len - CTAP_INIT_HEADER_LEN > sizeof(fp->body.init.data)) + return (0); + memset(&pkt, 0, sizeof(pkt)); fp = (struct frame *)(pkt + 1); fp->cid = d->cid; fp->body.init.cmd = CTAP_FRAME_INIT | cmd; fp->body.init.bcnth = (count >> 8) & 0xff; fp->body.init.bcntl = count & 0xff; - count = MIN(count, sizeof(fp->body.init.data)); + count = MIN(count, d->tx_len - CTAP_INIT_HEADER_LEN); memcpy(&fp->body.init.data, buf, count); - n = d->io.write(d->io_handle, pkt, sizeof(pkt)); - if (n < 0 || (size_t)n != sizeof(pkt)) + if (len > sizeof(pkt) || (n = d->io.write(d->io_handle, pkt, + len)) < 0 || (size_t)n != len) return (0); return (count); @@ -80,17 +85,21 @@ tx_frame(fido_dev_t *d, uint8_t seq, const void *buf, size_t count) { struct frame *fp; unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; int n; + if (d->tx_len - CTAP_CONT_HEADER_LEN > sizeof(fp->body.cont.data)) + return (0); + memset(&pkt, 0, sizeof(pkt)); fp = (struct frame *)(pkt + 1); fp->cid = d->cid; fp->body.cont.seq = seq; - count = MIN(count, sizeof(fp->body.cont.data)); + count = MIN(count, d->tx_len - CTAP_CONT_HEADER_LEN); memcpy(&fp->body.cont.data, buf, count); - n = d->io.write(d->io_handle, pkt, sizeof(pkt)); - if (n < 0 || (size_t)n != sizeof(pkt)) + if (len > sizeof(pkt) || (n = d->io.write(d->io_handle, pkt, + len)) < 0 || (size_t)n != len) return (0); return (count); @@ -129,16 +138,12 @@ fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count) if (d->transport.tx != NULL) return (d->transport.tx(d, cmd, buf, count)); - if (d->io_handle == NULL || d->io.write == NULL || count > UINT16_MAX) { fido_log_debug("%s: invalid argument", __func__); return (-1); } - if (count == 0) - return (tx_empty(d, cmd)); - - return (tx(d, cmd, buf, count)); + return (count == 0 ? tx_empty(d, cmd) : tx(d, cmd, buf, count)); } static int @@ -146,8 +151,10 @@ rx_frame(fido_dev_t *d, struct frame *fp, int ms) { int n; - n = d->io.read(d->io_handle, (unsigned char *)fp, sizeof(*fp), ms); - if (n < 0 || (size_t)n != sizeof(*fp)) + memset(fp, 0, sizeof(*fp)); + + if (d->rx_len > sizeof(*fp) || (n = d->io.read(d->io_handle, + (unsigned char *)fp, d->rx_len, ms)) < 0 || (size_t)n != d->rx_len) return (-1); return (0); @@ -165,8 +172,11 @@ rx_preamble(fido_dev_t *d, uint8_t cmd, struct frame *fp, int ms) } while (fp->cid == d->cid && fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE)); + if (d->rx_len > sizeof(*fp)) + return (-1); + fido_log_debug("%s: initiation frame at %p", __func__, (void *)fp); - fido_log_xxd(fp, sizeof(*fp)); + fido_log_xxd(fp, d->rx_len); #ifdef FIDO_FUZZ fp->body.init.cmd = (CTAP_FRAME_INIT | cmd); @@ -185,30 +195,41 @@ static int rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms) { struct frame f; - uint16_t r, payload_len; + size_t r, payload_len, init_data_len, cont_data_len; + + if (d->rx_len <= CTAP_INIT_HEADER_LEN || + d->rx_len <= CTAP_CONT_HEADER_LEN) + return (-1); + + init_data_len = d->rx_len - CTAP_INIT_HEADER_LEN; + cont_data_len = d->rx_len - CTAP_CONT_HEADER_LEN; + + if (init_data_len > sizeof(f.body.init.data) || + cont_data_len > sizeof(f.body.cont.data)) + return (-1); if (rx_preamble(d, cmd, &f, ms) < 0) { fido_log_debug("%s: rx_preamble", __func__); return (-1); } - payload_len = (f.body.init.bcnth << 8) | f.body.init.bcntl; - fido_log_debug("%s: payload_len=%zu", __func__, (size_t)payload_len); + payload_len = (size_t)((f.body.init.bcnth << 8) | f.body.init.bcntl); + fido_log_debug("%s: payload_len=%zu", __func__, payload_len); - if (count < (size_t)payload_len) { + if (count < payload_len) { fido_log_debug("%s: count < payload_len", __func__); return (-1); } - if (payload_len < sizeof(f.body.init.data)) { + if (payload_len < init_data_len) { memcpy(buf, f.body.init.data, payload_len); - return (payload_len); + return ((int)payload_len); } - memcpy(buf, f.body.init.data, sizeof(f.body.init.data)); - r = sizeof(f.body.init.data); + memcpy(buf, f.body.init.data, init_data_len); + r = init_data_len; - for (int seq = 0; (size_t)r < payload_len; seq++) { + for (int seq = 0; r < payload_len; seq++) { if (rx_frame(d, &f, ms) < 0) { fido_log_debug("%s: rx_frame", __func__); return (-1); @@ -216,11 +237,11 @@ rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms) fido_log_debug("%s: continuation frame at %p", __func__, (void *)&f); - fido_log_xxd(&f, sizeof(f)); + fido_log_xxd(&f, d->rx_len); #ifdef FIDO_FUZZ f.cid = d->cid; - f.body.cont.seq = seq; + f.body.cont.seq = (uint8_t)seq; #endif if (f.cid != d->cid || f.body.cont.seq != seq) { @@ -229,17 +250,16 @@ rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms) return (-1); } - if ((size_t)(payload_len - r) > sizeof(f.body.cont.data)) { - memcpy(buf + r, f.body.cont.data, - sizeof(f.body.cont.data)); - r += sizeof(f.body.cont.data); + if (payload_len - r > cont_data_len) { + memcpy(buf + r, f.body.cont.data, cont_data_len); + r += cont_data_len; } else { memcpy(buf + r, f.body.cont.data, payload_len - r); - r += (payload_len - r); /* break */ + r += payload_len - r; /* break */ } } - return (r); + return ((int)r); } int @@ -252,15 +272,13 @@ fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int ms) if (d->transport.rx != NULL) return (d->transport.rx(d, cmd, buf, count, ms)); - if (d->io_handle == NULL || d->io.read == NULL || count > UINT16_MAX) { fido_log_debug("%s: invalid argument", __func__); return (-1); } - if ((n = rx(d, cmd, buf, count, ms)) >= 0) { fido_log_debug("%s: buf=%p, len=%d", __func__, (void *)buf, n); - fido_log_xxd(buf, n); + fido_log_xxd(buf, (size_t)n); } return (n); diff --git a/src/iso7816.c b/src/iso7816.c index a3fd280..4fe6329 100644 --- a/src/iso7816.c +++ b/src/iso7816.c @@ -23,8 +23,8 @@ iso7816_new(uint8_t ins, uint8_t p1, uint16_t payload_len) apdu->payload_ptr = apdu->payload; apdu->header.ins = ins; apdu->header.p1 = p1; - apdu->header.lc2 = (payload_len >> 8) & 0xff; - apdu->header.lc3 = payload_len & 0xff; + apdu->header.lc2 = (uint8_t)((payload_len >> 8) & 0xff); + apdu->header.lc3 = (uint8_t)(payload_len & 0xff); return (apdu); } @@ -51,7 +51,7 @@ iso7816_add(iso7816_apdu_t *apdu, const void *buf, size_t cnt) memcpy(apdu->payload_ptr, buf, cnt); apdu->payload_ptr += cnt; - apdu->payload_len -= (uint16_t)cnt; + apdu->payload_len = (uint16_t)(apdu->payload_len - cnt); return (0); } diff --git a/src/pin.c b/src/pin.c index 36acbe4..8b23ae3 100644 --- a/src/pin.c +++ b/src/pin.c @@ -53,7 +53,7 @@ fido_dev_get_pin_token_tx(fido_dev_t *dev, const char *pin, if ((argv[0] = cbor_build_uint8(1)) == NULL || (argv[1] = cbor_build_uint8(5)) == NULL || - (argv[2] = es256_pk_encode(pk, 0)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || (argv[5] = cbor_encode_pin_hash_enc(ecdh, p)) == NULL) { fido_log_debug("%s: cbor encode", __func__); r = FIDO_ERR_INTERNAL; @@ -89,7 +89,7 @@ fido_dev_get_uv_token_tx(fido_dev_t *dev, const es256_pk_t *pk) if ((argv[0] = cbor_build_uint8(1)) == NULL || (argv[1] = cbor_build_uint8(6)) == NULL || - (argv[2] = es256_pk_encode(pk, 0)) == NULL) { + (argv[2] = es256_pk_encode(pk, 1)) == NULL) { fido_log_debug("%s: cbor encode", __func__); r = FIDO_ERR_INTERNAL; goto fail; @@ -240,7 +240,7 @@ pad64(const char *pin, fido_blob_t **ppin) if ((*ppin = fido_blob_new()) == NULL) return (FIDO_ERR_INTERNAL); - ppin_len = (pin_len + 63) & ~63; + ppin_len = (pin_len + 63U) & ~63U; if (ppin_len < pin_len || ((*ppin)->ptr = calloc(1, ppin_len)) == NULL) { fido_blob_free(ppin); return (FIDO_ERR_INTERNAL); @@ -285,7 +285,7 @@ fido_dev_change_pin_tx(fido_dev_t *dev, const char *pin, const char *oldpin) if ((argv[0] = cbor_build_uint8(1)) == NULL || (argv[1] = cbor_build_uint8(4)) == NULL || - (argv[2] = es256_pk_encode(pk, 0)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || (argv[3] = cbor_encode_change_pin_auth(ecdh, ppin, opin)) == NULL || (argv[4] = cbor_encode_pin_enc(ecdh, ppin)) == NULL || (argv[5] = cbor_encode_pin_hash_enc(ecdh, opin)) == NULL) { @@ -339,7 +339,7 @@ fido_dev_set_pin_tx(fido_dev_t *dev, const char *pin) if ((argv[0] = cbor_build_uint8(1)) == NULL || (argv[1] = cbor_build_uint8(3)) == NULL || - (argv[2] = es256_pk_encode(pk, 0)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || (argv[3] = cbor_encode_set_pin_auth(ecdh, ppin)) == NULL || (argv[4] = cbor_encode_pin_enc(ecdh, ppin)) == NULL) { fido_log_debug("%s: cbor encode", __func__); diff --git a/src/u2f.c b/src/u2f.c index 19a959d..3c6ea82 100644 --- a/src/u2f.c +++ b/src/u2f.c @@ -122,6 +122,7 @@ authdata_fake(const char *rp_id, uint8_t flags, uint32_t sigcount, return (0); } +/* TODO: use u2f_get_touch_begin & u2f_get_touch_status instead */ static int send_dummy_register(fido_dev_t *dev, int ms) { @@ -160,7 +161,7 @@ send_dummy_register(fido_dev_t *dev, int ms) r = FIDO_ERR_RX; goto fail; } - if (usleep((ms == -1 ? 100 : ms) * 1000) < 0) { + if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { fido_log_debug("%s: usleep", __func__); r = FIDO_ERR_RX; goto fail; @@ -204,8 +205,8 @@ key_lookup(fido_dev_t *dev, const char *rp_id, const fido_blob_t *key_id, key_id_len = (uint8_t)key_id->len; - if ((apdu = iso7816_new(U2F_CMD_AUTH, U2F_AUTH_CHECK, 2 * - SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len)) == NULL || + if ((apdu = iso7816_new(U2F_CMD_AUTH, U2F_AUTH_CHECK, (uint16_t)(2 * + SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL || iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 || iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 || iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 || @@ -312,8 +313,8 @@ do_auth(fido_dev_t *dev, const fido_blob_t *cdh, const char *rp_id, key_id_len = (uint8_t)key_id->len; - if ((apdu = iso7816_new(U2F_CMD_AUTH, U2F_AUTH_SIGN, 2 * - SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len)) == NULL || + if ((apdu = iso7816_new(U2F_CMD_AUTH, U2F_AUTH_SIGN, (uint16_t)(2 * + SHA256_DIGEST_LENGTH + sizeof(key_id_len) + key_id_len))) == NULL || iso7816_add(apdu, cdh->ptr, cdh->len) < 0 || iso7816_add(apdu, &rp_id_hash, sizeof(rp_id_hash)) < 0 || iso7816_add(apdu, &key_id_len, sizeof(key_id_len)) < 0 || @@ -336,7 +337,7 @@ do_auth(fido_dev_t *dev, const fido_blob_t *cdh, const char *rp_id, r = FIDO_ERR_RX; goto fail; } - if (usleep((ms == -1 ? 100 : ms) * 1000) < 0) { + if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { fido_log_debug("%s: usleep", __func__); r = FIDO_ERR_RX; goto fail; @@ -643,7 +644,7 @@ u2f_register(fido_dev_t *dev, fido_cred_t *cred, int ms) r = FIDO_ERR_RX; goto fail; } - if (usleep((ms == -1 ? 100 : ms) * 1000) < 0) { + if (usleep((unsigned)(ms == -1 ? 100 : ms) * 1000) < 0) { fido_log_debug("%s: usleep", __func__); r = FIDO_ERR_RX; goto fail; @@ -726,8 +727,8 @@ fail: int u2f_authenticate(fido_dev_t *dev, fido_assert_t *fa, int ms) { - int nfound = 0; - int nauth_ok = 0; + size_t nfound = 0; + size_t nauth_ok = 0; int r; if (fa->uv == FIDO_OPT_TRUE || fa->allow_list.ptr == NULL) { @@ -769,3 +770,84 @@ u2f_authenticate(fido_dev_t *dev, fido_assert_t *fa, int ms) return (FIDO_OK); } + +int +u2f_get_touch_begin(fido_dev_t *dev) +{ + iso7816_apdu_t *apdu = NULL; + const char *clientdata = FIDO_DUMMY_CLIENTDATA; + const char *rp_id = FIDO_DUMMY_RP_ID; + unsigned char clientdata_hash[SHA256_DIGEST_LENGTH]; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + unsigned char reply[FIDO_MAXMSG]; + int r; + + memset(&clientdata_hash, 0, sizeof(clientdata_hash)); + memset(&rp_id_hash, 0, sizeof(rp_id_hash)); + + if (SHA256((const void *)clientdata, strlen(clientdata), + clientdata_hash) != clientdata_hash || SHA256((const void *)rp_id, + strlen(rp_id), rp_id_hash) != rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((apdu = iso7816_new(U2F_CMD_REGISTER, 0, 2 * + SHA256_DIGEST_LENGTH)) == NULL || + iso7816_add(apdu, clientdata_hash, sizeof(clientdata_hash)) < 0 || + iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (dev->attr.flags & FIDO_CAP_WINK) { + fido_tx(dev, CTAP_CMD_WINK, NULL, 0); + fido_rx(dev, CTAP_CMD_WINK, &reply, sizeof(reply), 200); + } + + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu)) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + + return (r); +} + +int +u2f_get_touch_status(fido_dev_t *dev, int *touched, int ms) +{ + unsigned char reply[FIDO_MAXMSG]; + int reply_len; + int r; + + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, &reply, sizeof(reply), + ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + return (FIDO_OK); /* ignore */ + } + + switch ((reply[reply_len - 2] << 8) | reply[reply_len - 1]) { + case SW_CONDITIONS_NOT_SATISFIED: + if ((r = u2f_get_touch_begin(dev)) != FIDO_OK) { + fido_log_debug("%s: u2f_get_touch_begin", __func__); + return (r); + } + *touched = 0; + break; + case SW_NO_ERROR: + *touched = 1; + break; + default: + fido_log_debug("%s: unexpected sw", __func__); + return (FIDO_ERR_RX); + } + + return (FIDO_OK); +} -- cgit v1.2.3