diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/authenticator/src/windows | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/authenticator/src/windows')
5 files changed, 523 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/windows/device.rs b/third_party/rust/authenticator/src/windows/device.rs new file mode 100644 index 0000000000..183ba71f44 --- /dev/null +++ b/third_party/rust/authenticator/src/windows/device.rs @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::fs::{File, OpenOptions}; +use std::io; +use std::io::{Read, Write}; +use std::os::windows::io::AsRawHandle; + +use super::winapi::DeviceCapabilities; +use crate::consts::{CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE}; +use crate::u2ftypes::{U2FDevice, U2FDeviceInfo}; + +#[derive(Debug)] +pub struct Device { + path: String, + file: File, + cid: [u8; 4], + dev_info: Option<U2FDeviceInfo>, +} + +impl Device { + pub fn new(path: String) -> io::Result<Self> { + let file = OpenOptions::new().read(true).write(true).open(&path)?; + Ok(Self { + path, + file, + cid: CID_BROADCAST, + dev_info: None, + }) + } + + pub fn is_u2f(&self) -> bool { + match DeviceCapabilities::new(self.file.as_raw_handle()) { + Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE, + _ => false, + } + } +} + +impl PartialEq for Device { + fn eq(&self, other: &Device) -> bool { + self.path == other.path + } +} + +impl Read for Device { + fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> { + // Windows always includes the report ID. + let mut input = [0u8; MAX_HID_RPT_SIZE + 1]; + let _ = self.file.read(&mut input)?; + bytes.clone_from_slice(&input[1..]); + Ok(bytes.len() as usize) + } +} + +impl Write for Device { + fn write(&mut self, bytes: &[u8]) -> io::Result<usize> { + self.file.write(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.file.flush() + } +} + +impl U2FDevice for Device { + fn get_cid<'a>(&'a self) -> &'a [u8; 4] { + &self.cid + } + + fn set_cid(&mut self, cid: [u8; 4]) { + self.cid = cid; + } + + fn in_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn out_rpt_size(&self) -> usize { + MAX_HID_RPT_SIZE + } + + fn get_property(&self, _prop_name: &str) -> io::Result<String> { + Err(io::Error::new(io::ErrorKind::Other, "Not implemented")) + } + + fn get_device_info(&self) -> U2FDeviceInfo { + // unwrap is okay, as dev_info must have already been set, else + // a programmer error + self.dev_info.clone().unwrap() + } + + fn set_device_info(&mut self, dev_info: U2FDeviceInfo) { + self.dev_info = Some(dev_info); + } +} diff --git a/third_party/rust/authenticator/src/windows/mod.rs b/third_party/rust/authenticator/src/windows/mod.rs new file mode 100644 index 0000000000..09135391dd --- /dev/null +++ b/third_party/rust/authenticator/src/windows/mod.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub mod device; +pub mod transaction; + +mod monitor; +mod winapi; diff --git a/third_party/rust/authenticator/src/windows/monitor.rs b/third_party/rust/authenticator/src/windows/monitor.rs new file mode 100644 index 0000000000..4c977bde03 --- /dev/null +++ b/third_party/rust/authenticator/src/windows/monitor.rs @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::platform::winapi::DeviceInfoSet; +use runloop::RunLoop; +use std::collections::{HashMap, HashSet}; +use std::io; +use std::iter::FromIterator; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +pub struct Monitor<F> +where + F: Fn(String, &dyn Fn() -> bool) + Sync, +{ + runloops: HashMap<String, RunLoop>, + new_device_cb: Arc<F>, +} + +impl<F> Monitor<F> +where + F: Fn(String, &dyn Fn() -> bool) + Send + Sync + 'static, +{ + pub fn new(new_device_cb: F) -> Self { + Self { + runloops: HashMap::new(), + new_device_cb: Arc::new(new_device_cb), + } + } + + pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> { + let mut stored = HashSet::new(); + + while alive() { + let device_info_set = DeviceInfoSet::new()?; + let devices = HashSet::from_iter(device_info_set.devices()); + + // Remove devices that are gone. + for path in stored.difference(&devices) { + self.remove_device(path); + } + + // Add devices that were plugged in. + for path in devices.difference(&stored) { + self.add_device(path); + } + + // Remember the new set. + stored = devices; + + // Wait a little before looking for devices again. + thread::sleep(Duration::from_millis(100)); + } + + // Remove all tracked devices. + self.remove_all_devices(); + + Ok(()) + } + + fn add_device(&mut self, path: &String) { + let f = self.new_device_cb.clone(); + let path = path.clone(); + let key = path.clone(); + + let runloop = RunLoop::new(move |alive| { + if alive() { + f(path, alive); + } + }); + + if let Ok(runloop) = runloop { + self.runloops.insert(key, runloop); + } + } + + fn remove_device(&mut self, path: &String) { + if let Some(runloop) = self.runloops.remove(path) { + runloop.cancel(); + } + } + + fn remove_all_devices(&mut self) { + while !self.runloops.is_empty() { + let path = self.runloops.keys().next().unwrap().clone(); + self.remove_device(&path); + } + } +} diff --git a/third_party/rust/authenticator/src/windows/transaction.rs b/third_party/rust/authenticator/src/windows/transaction.rs new file mode 100644 index 0000000000..74e856b690 --- /dev/null +++ b/third_party/rust/authenticator/src/windows/transaction.rs @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::errors; +use crate::platform::monitor::Monitor; +use crate::statecallback::StateCallback; +use runloop::RunLoop; + +pub struct Transaction { + // Handle to the thread loop. + thread: Option<RunLoop>, +} + +impl Transaction { + pub fn new<F, T>( + timeout: u64, + callback: StateCallback<crate::Result<T>>, + new_device_cb: F, + ) -> crate::Result<Self> + where + F: Fn(String, &dyn Fn() -> bool) + Sync + Send + 'static, + T: 'static, + { + let thread = RunLoop::new_with_timeout( + move |alive| { + // Create a new device monitor. + let mut monitor = Monitor::new(new_device_cb); + + // Start polling for new devices. + try_or!(monitor.run(alive), |_| callback + .call(Err(errors::AuthenticatorError::Platform))); + + // Send an error, if the callback wasn't called already. + callback.call(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::NotAllowed, + ))); + }, + timeout, + ) + .map_err(|_| errors::AuthenticatorError::Platform)?; + + Ok(Self { + thread: Some(thread), + }) + } + + pub fn cancel(&mut self) { + // This must never be None. + self.thread.take().unwrap().cancel(); + } +} diff --git a/third_party/rust/authenticator/src/windows/winapi.rs b/third_party/rust/authenticator/src/windows/winapi.rs new file mode 100644 index 0000000000..d3388cebfc --- /dev/null +++ b/third_party/rust/authenticator/src/windows/winapi.rs @@ -0,0 +1,274 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::io; +use std::mem; +use std::ptr; +use std::slice; + +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; + +use crate::util::io_err; + +extern crate libc; +extern crate winapi; + +use crate::platform::winapi::winapi::shared::{guiddef, minwindef, ntdef, windef}; +use crate::platform::winapi::winapi::shared::{hidclass, hidpi, hidusage}; +use crate::platform::winapi::winapi::um::{handleapi, setupapi}; + +#[link(name = "setupapi")] +extern "system" { + fn SetupDiGetClassDevsW( + ClassGuid: *const guiddef::GUID, + Enumerator: ntdef::PCSTR, + hwndParent: windef::HWND, + flags: minwindef::DWORD, + ) -> setupapi::HDEVINFO; + + fn SetupDiDestroyDeviceInfoList(DeviceInfoSet: setupapi::HDEVINFO) -> minwindef::BOOL; + + fn SetupDiEnumDeviceInterfaces( + DeviceInfoSet: setupapi::HDEVINFO, + DeviceInfoData: setupapi::PSP_DEVINFO_DATA, + InterfaceClassGuid: *const guiddef::GUID, + MemberIndex: minwindef::DWORD, + DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA, + ) -> minwindef::BOOL; + + fn SetupDiGetDeviceInterfaceDetailW( + DeviceInfoSet: setupapi::HDEVINFO, + DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA, + DeviceInterfaceDetailData: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W, + DeviceInterfaceDetailDataSize: minwindef::DWORD, + RequiredSize: minwindef::PDWORD, + DeviceInfoData: setupapi::PSP_DEVINFO_DATA, + ) -> minwindef::BOOL; +} + +#[link(name = "hid")] +extern "system" { + fn HidD_GetPreparsedData( + HidDeviceObject: ntdef::HANDLE, + PreparsedData: *mut hidpi::PHIDP_PREPARSED_DATA, + ) -> ntdef::BOOLEAN; + + fn HidD_FreePreparsedData(PreparsedData: hidpi::PHIDP_PREPARSED_DATA) -> ntdef::BOOLEAN; + + fn HidP_GetCaps( + PreparsedData: hidpi::PHIDP_PREPARSED_DATA, + Capabilities: hidpi::PHIDP_CAPS, + ) -> ntdef::NTSTATUS; +} + +macro_rules! offset_of { + ($ty:ty, $field:ident) => { + unsafe { &(*(0 as *const $ty)).$field as *const _ as usize } + }; +} + +fn from_wide_ptr(ptr: *const u16, len: usize) -> String { + assert!(!ptr.is_null() && len % 2 == 0); + let slice = unsafe { slice::from_raw_parts(ptr, len / 2) }; + OsString::from_wide(slice).to_string_lossy().into_owned() +} + +pub struct DeviceInfoSet { + set: setupapi::HDEVINFO, +} + +impl DeviceInfoSet { + pub fn new() -> io::Result<Self> { + let flags = setupapi::DIGCF_PRESENT | setupapi::DIGCF_DEVICEINTERFACE; + let set = unsafe { + SetupDiGetClassDevsW( + &hidclass::GUID_DEVINTERFACE_HID, + ptr::null_mut(), + ptr::null_mut(), + flags, + ) + }; + if set == handleapi::INVALID_HANDLE_VALUE { + return Err(io_err("SetupDiGetClassDevsW failed!")); + } + + Ok(Self { set }) + } + + pub fn get(&self) -> setupapi::HDEVINFO { + self.set + } + + pub fn devices(&self) -> DeviceInfoSetIter { + DeviceInfoSetIter::new(self) + } +} + +impl Drop for DeviceInfoSet { + fn drop(&mut self) { + let _ = unsafe { SetupDiDestroyDeviceInfoList(self.set) }; + } +} + +pub struct DeviceInfoSetIter<'a> { + set: &'a DeviceInfoSet, + index: minwindef::DWORD, +} + +impl<'a> DeviceInfoSetIter<'a> { + fn new(set: &'a DeviceInfoSet) -> Self { + Self { set, index: 0 } + } +} + +impl<'a> Iterator for DeviceInfoSetIter<'a> { + type Item = String; + + fn next(&mut self) -> Option<Self::Item> { + let mut device_interface_data = + mem::MaybeUninit::<setupapi::SP_DEVICE_INTERFACE_DATA>::zeroed(); + unsafe { + (*device_interface_data.as_mut_ptr()).cbSize = + mem::size_of::<setupapi::SP_DEVICE_INTERFACE_DATA>() as minwindef::UINT; + } + + let rv = unsafe { + SetupDiEnumDeviceInterfaces( + self.set.get(), + ptr::null_mut(), + &hidclass::GUID_DEVINTERFACE_HID, + self.index, + device_interface_data.as_mut_ptr(), + ) + }; + if rv == 0 { + return None; // We're past the last device index. + } + + // Determine the size required to hold a detail struct. + let mut required_size = 0; + unsafe { + SetupDiGetDeviceInterfaceDetailW( + self.set.get(), + device_interface_data.as_mut_ptr(), + ptr::null_mut(), + required_size, + &mut required_size, + ptr::null_mut(), + ) + }; + if required_size == 0 { + return None; // An error occurred. + } + + let detail = DeviceInterfaceDetailData::new(required_size as usize); + if detail.is_none() { + return None; // malloc() failed. + } + + let detail = detail.unwrap(); + let rv = unsafe { + SetupDiGetDeviceInterfaceDetailW( + self.set.get(), + device_interface_data.as_mut_ptr(), + detail.get(), + required_size, + ptr::null_mut(), + ptr::null_mut(), + ) + }; + if rv == 0 { + return None; // An error occurred. + } + + self.index += 1; + Some(detail.path()) + } +} + +struct DeviceInterfaceDetailData { + data: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W, + path_len: usize, +} + +impl DeviceInterfaceDetailData { + fn new(size: usize) -> Option<Self> { + let mut cb_size = mem::size_of::<setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W>(); + if cfg!(target_pointer_width = "32") { + cb_size = 4 + 2; // 4-byte uint + default TCHAR size. size_of is inaccurate. + } + + if size < cb_size { + warn!("DeviceInterfaceDetailData is too small. {}", size); + return None; + } + + let data = unsafe { libc::malloc(size) as setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W }; + if data.is_null() { + return None; + } + + // Set total size of the structure. + unsafe { (*data).cbSize = cb_size as minwindef::UINT }; + + // Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`. + let offset = offset_of!(setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath); + + Some(Self { + data, + path_len: size - offset, + }) + } + + fn get(&self) -> setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W { + self.data + } + + fn path(&self) -> String { + unsafe { from_wide_ptr((*self.data).DevicePath.as_ptr(), self.path_len - 2) } + } +} + +impl Drop for DeviceInterfaceDetailData { + fn drop(&mut self) { + unsafe { libc::free(self.data as *mut libc::c_void) }; + } +} + +pub struct DeviceCapabilities { + caps: hidpi::HIDP_CAPS, +} + +impl DeviceCapabilities { + pub fn new(handle: ntdef::HANDLE) -> io::Result<Self> { + let mut preparsed_data = ptr::null_mut(); + let rv = unsafe { HidD_GetPreparsedData(handle, &mut preparsed_data) }; + if rv == 0 || preparsed_data.is_null() { + return Err(io_err("HidD_GetPreparsedData failed!")); + } + + let mut caps = mem::MaybeUninit::<hidpi::HIDP_CAPS>::uninit(); + unsafe { + let rv = HidP_GetCaps(preparsed_data, caps.as_mut_ptr()); + HidD_FreePreparsedData(preparsed_data); + + if rv != hidpi::HIDP_STATUS_SUCCESS { + return Err(io_err("HidP_GetCaps failed!")); + } + + Ok(Self { + caps: caps.assume_init(), + }) + } + } + + pub fn usage(&self) -> hidusage::USAGE { + self.caps.Usage + } + + pub fn usage_page(&self) -> hidusage::USAGE { + self.caps.UsagePage + } +} |