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/hid_win.c | 398 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 299 insertions(+), 99 deletions(-) (limited to 'src/hid_win.c') 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); } -- cgit v1.2.3