// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/credential_provider/gaiacp/credential_provider_broker_win.h"
#define INITGUID
// clang-format off
#include <windows.h>
#include <hidclass.h>
#include <hidsdi.h>
#include <hidpi.h>
#include <setupapi.h>
// clang-format on
#include <optional>
#include "base/memory/free_deleter.h"
#include "base/strings/string_util.h"
#include "base/win/scoped_devinfo.h"
#include "mojo/public/cpp/platform/platform_handle.h"
using mojo::PlatformHandle;
namespace credential_provider {
namespace {
struct PreparsedDataScopedTraits {
static PHIDP_PREPARSED_DATA InvalidValue() { return nullptr; }
static void Free(PHIDP_PREPARSED_DATA h) { HidD_FreePreparsedData(h); }
};
using ScopedPreparsedData =
base::ScopedGeneric<PHIDP_PREPARSED_DATA, PreparsedDataScopedTraits>;
// Opens the HID device handle with the input |device_path| and
// returns a ScopedHandle.
base::win::ScopedHandle OpenHidDevice(const std::wstring& device_path) {
base::win::ScopedHandle file(
CreateFile(device_path.c_str(), GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, nullptr));
if (!file.IsValid() && GetLastError() == ERROR_ACCESS_DENIED) {
file.Set(CreateFile(device_path.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
}
return file;
}
// Gets the usage page for the input device |handle|.
std::optional<uint16_t> GetUsagePage(HANDLE handle) {
ScopedPreparsedData scoped_preparsed_data;
if (!HidD_GetPreparsedData(
handle, ScopedPreparsedData::Receiver(scoped_preparsed_data).get()) ||
!scoped_preparsed_data.is_valid()) {
return std::nullopt;
}
HIDP_CAPS capabilities = {};
if (HidP_GetCaps(scoped_preparsed_data.get(), &capabilities) !=
HIDP_STATUS_SUCCESS) {
return std::nullopt;
}
return capabilities.UsagePage;
}
// Extracts the device path from |device_info_set| and |device_interface_data|
// input fields.
std::optional<std::wstring> GetDevicePath(
HDEVINFO device_info_set,
PSP_DEVICE_INTERFACE_DATA device_interface_data) {
DWORD required_size = 0;
// Determine the required size of detail struct.
SetupDiGetDeviceInterfaceDetail(device_info_set, device_interface_data,
nullptr, 0, &required_size, nullptr);
std::unique_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA, base::FreeDeleter>
device_interface_detail_data(
static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(malloc(required_size)));
device_interface_detail_data->cbSize =
sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
// Get the detailed data for this device.
if (!SetupDiGetDeviceInterfaceDetail(device_info_set, device_interface_data,
device_interface_detail_data.get(),
required_size, nullptr, nullptr)) {
return std::nullopt;
}
// Extract the device path and compare it with input device path
// and ignore it if it doesn't match the input device path.
return std::wstring(device_interface_detail_data->DevicePath);
}
} // namespace
constexpr uint16_t FIDO_USAGE_PAGE = 0xf1d0; // FIDO alliance HID usage page
CredentialProviderBrokerWin::CredentialProviderBrokerWin() = default;
CredentialProviderBrokerWin::~CredentialProviderBrokerWin() = default;
void CredentialProviderBrokerWin::OpenDevice(
const std::u16string& input_device_path,
OpenDeviceCallback callback) {
base::win::ScopedDevInfo device_info_set(
SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, nullptr, nullptr,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
if (device_info_set.get() != INVALID_HANDLE_VALUE) {
SP_DEVICE_INTERFACE_DATA device_interface_data = {};
device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
for (int device_index = 0; SetupDiEnumDeviceInterfaces(
device_info_set.get(), nullptr, &GUID_DEVINTERFACE_HID,
device_index, &device_interface_data);
++device_index) {
std::optional<std::wstring> device_path =
GetDevicePath(device_info_set.get(), &device_interface_data);
if (!device_path)
continue;
DCHECK(base::IsStringASCII(device_path.value()));
if (!base::EqualsCaseInsensitiveASCII(
device_path.value(), base::AsWStringView(input_device_path))) {
continue;
}
base::win::ScopedHandle device_handle =
OpenHidDevice(device_path.value());
if (!device_handle.IsValid())
break;
std::optional<uint16_t> usage_page = GetUsagePage(device_handle.Get());
// Only if the input device path is corresponding to a FIDO
// device, we will return appropriate device handle. Otherwise,
// return an invalid device handle.
if (usage_page && usage_page.value() == FIDO_USAGE_PAGE) {
std::move(callback).Run(PlatformHandle(std::move(device_handle)));
return;
}
break;
}
}
// Return default PlatformHandle when we can't find any appropriate device
// handle.
std::move(callback).Run(PlatformHandle());
}
} // namespace credential_provider