summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/src/transport
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/authenticator/src/transport')
-rw-r--r--third_party/rust/authenticator/src/transport/device_selector.rs477
-rw-r--r--third_party/rust/authenticator/src/transport/errors.rs98
-rw-r--r--third_party/rust/authenticator/src/transport/freebsd/device.rs235
-rw-r--r--third_party/rust/authenticator/src/transport/freebsd/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/transport/freebsd/monitor.rs161
-rw-r--r--third_party/rust/authenticator/src/transport/freebsd/transaction.rs69
-rw-r--r--third_party/rust/authenticator/src/transport/freebsd/uhid.rs89
-rw-r--r--third_party/rust/authenticator/src/transport/hid.rs252
-rw-r--r--third_party/rust/authenticator/src/transport/hidproto.rs257
-rw-r--r--third_party/rust/authenticator/src/transport/linux/device.rs181
-rw-r--r--third_party/rust/authenticator/src/transport/linux/hidraw.rs80
-rw-r--r--third_party/rust/authenticator/src/transport/linux/hidwrapper.h12
-rw-r--r--third_party/rust/authenticator/src/transport/linux/hidwrapper.rs54
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_aarch64le.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_armle.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_loongarch64.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_mips64le.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_mipsbe.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_mipsle.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64be.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64le.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_powerpcbe.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_riscv64.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_s390xbe.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_x86.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/ioctl_x86_64.rs5
-rw-r--r--third_party/rust/authenticator/src/transport/linux/mod.rs12
-rw-r--r--third_party/rust/authenticator/src/transport/linux/monitor.rs194
-rw-r--r--third_party/rust/authenticator/src/transport/linux/transaction.rs69
-rw-r--r--third_party/rust/authenticator/src/transport/macos/device.rs226
-rw-r--r--third_party/rust/authenticator/src/transport/macos/iokit.rs292
-rw-r--r--third_party/rust/authenticator/src/transport/macos/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/transport/macos/monitor.rs212
-rw-r--r--third_party/rust/authenticator/src/transport/macos/transaction.rs107
-rw-r--r--third_party/rust/authenticator/src/transport/mock/device.rs328
-rw-r--r--third_party/rust/authenticator/src/transport/mock/mod.rs6
-rw-r--r--third_party/rust/authenticator/src/transport/mock/transaction.rs35
-rw-r--r--third_party/rust/authenticator/src/transport/mod.rs369
-rw-r--r--third_party/rust/authenticator/src/transport/netbsd/device.rs248
-rw-r--r--third_party/rust/authenticator/src/transport/netbsd/fd.rs62
-rw-r--r--third_party/rust/authenticator/src/transport/netbsd/mod.rs10
-rw-r--r--third_party/rust/authenticator/src/transport/netbsd/monitor.rs132
-rw-r--r--third_party/rust/authenticator/src/transport/netbsd/transaction.rs69
-rw-r--r--third_party/rust/authenticator/src/transport/netbsd/uhid.rs77
-rw-r--r--third_party/rust/authenticator/src/transport/openbsd/device.rs224
-rw-r--r--third_party/rust/authenticator/src/transport/openbsd/mod.rs8
-rw-r--r--third_party/rust/authenticator/src/transport/openbsd/monitor.rs138
-rw-r--r--third_party/rust/authenticator/src/transport/openbsd/transaction.rs69
-rw-r--r--third_party/rust/authenticator/src/transport/stub/device.rs115
-rw-r--r--third_party/rust/authenticator/src/transport/stub/mod.rs11
-rw-r--r--third_party/rust/authenticator/src/transport/stub/transaction.rs52
-rw-r--r--third_party/rust/authenticator/src/transport/windows/device.rs173
-rw-r--r--third_party/rust/authenticator/src/transport/windows/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/transport/windows/monitor.rs125
-rw-r--r--third_party/rust/authenticator/src/transport/windows/transaction.rs69
-rw-r--r--third_party/rust/authenticator/src/transport/windows/winapi.rs263
56 files changed, 5752 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/transport/device_selector.rs b/third_party/rust/authenticator/src/transport/device_selector.rs
new file mode 100644
index 0000000000..f745b456d9
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/device_selector.rs
@@ -0,0 +1,477 @@
+use crate::transport::hid::HIDDevice;
+
+pub use crate::transport::platform::device::Device;
+
+use runloop::RunLoop;
+use std::collections::{HashMap, HashSet};
+use std::sync::mpsc::{channel, RecvTimeoutError, Sender};
+use std::time::Duration;
+
+pub type DeviceID = <Device as HIDDevice>::Id;
+pub type DeviceBuildParameters = <Device as HIDDevice>::BuildParameters;
+
+trait DeviceSelectorEventMarker {}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum BlinkResult {
+ DeviceSelected,
+ Cancelled,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum DeviceCommand {
+ Blink,
+ Cancel,
+ Continue,
+ Removed,
+}
+
+#[derive(Debug)]
+pub enum DeviceSelectorEvent {
+ Cancel,
+ Timeout,
+ DevicesAdded(Vec<DeviceID>),
+ DeviceRemoved(DeviceID),
+ NotAToken(DeviceID),
+ ImAToken((DeviceID, Sender<DeviceCommand>)),
+ SelectedToken(DeviceID),
+}
+
+pub struct DeviceSelector {
+ /// How to send a message to the event loop
+ sender: Sender<DeviceSelectorEvent>,
+ /// Thread of the event loop
+ runloop: RunLoop,
+}
+
+impl DeviceSelector {
+ pub fn run() -> Self {
+ let (selector_send, selector_rec) = channel();
+ // let new_device_callback = Arc::new(new_device_cb);
+ let runloop = RunLoop::new(move |alive| {
+ let mut blinking = false;
+ // Device was added, but we wait for its response, if it is a token or not
+ // We save both a write-only copy of the device (for cancellation) and it's thread
+ let mut waiting_for_response = HashSet::new();
+ // Device IDs of devices that responded with "ImAToken" mapping to channels that are
+ // waiting to receive a DeviceCommand
+ let mut tokens = HashMap::new();
+ while alive() {
+ let d = Duration::from_secs(100);
+ let res = match selector_rec.recv_timeout(d) {
+ Err(RecvTimeoutError::Disconnected) => {
+ break;
+ }
+ Err(RecvTimeoutError::Timeout) => DeviceSelectorEvent::Timeout,
+ Ok(res) => res,
+ };
+
+ match res {
+ DeviceSelectorEvent::Timeout | DeviceSelectorEvent::Cancel => {
+ /* TODO */
+ Self::cancel_all(tokens, None);
+ break;
+ }
+ DeviceSelectorEvent::SelectedToken(ref id) => {
+ Self::cancel_all(tokens, Some(id));
+ break; // We are done here. The selected device continues without us.
+ }
+ DeviceSelectorEvent::DevicesAdded(ids) => {
+ for id in ids {
+ debug!("Device added event: {:?}", id);
+ waiting_for_response.insert(id);
+ }
+ continue;
+ }
+ DeviceSelectorEvent::DeviceRemoved(ref id) => {
+ debug!("Device removed event: {:?}", id);
+ if !waiting_for_response.remove(id) {
+ // Note: We _could_ check here if we had multiple tokens and are already blinking
+ // and the removal of this one leads to only one token left. So we could in theory
+ // stop blinking and select it right away. At the moment, I think this is a
+ // too surprising behavior and therefore, we let the remaining device keep on blinking
+ // since the user could add yet another device, instead of using the remaining one.
+ tokens.iter().for_each(|(dev_id, tx)| {
+ if dev_id == id {
+ let _ = tx.send(DeviceCommand::Removed);
+ }
+ });
+ tokens.retain(|dev_id, _| dev_id != id);
+ if tokens.is_empty() {
+ blinking = false;
+ continue;
+ }
+ }
+ // We are already blinking, so no need to run the code below this match
+ // that figures out if we should blink or not. In fact, currently, we do
+ // NOT want to run this code again, because if you have 2 blinking tokens
+ // and one got removed, we WANT the remaining one to continue blinking.
+ // This is a design choice, because I currently think it is the "less surprising"
+ // option to the user.
+ if blinking {
+ continue;
+ }
+ }
+ DeviceSelectorEvent::NotAToken(ref id) => {
+ debug!("Device not a token event: {:?}", id);
+ waiting_for_response.remove(id);
+ }
+ DeviceSelectorEvent::ImAToken((id, tx)) => {
+ let _ = waiting_for_response.remove(&id);
+ if blinking {
+ // We are already blinking, so this new device should blink too.
+ if tx.send(DeviceCommand::Blink).is_ok() {
+ tokens.insert(id, tx.clone());
+ }
+ continue;
+ } else {
+ tokens.insert(id, tx.clone());
+ }
+ }
+ }
+
+ // All known devices told us, whether they are tokens or not and we have at least one token
+ if waiting_for_response.is_empty() && !tokens.is_empty() {
+ if tokens.len() == 1 {
+ let (dev_id, tx) = tokens.drain().next().unwrap(); // We just checked that it can't be empty
+ if tx.send(DeviceCommand::Continue).is_err() {
+ // Device thread died in the meantime (which shouldn't happen).
+ // Tokens is empty, so we just start over again
+ continue;
+ }
+ Self::cancel_all(tokens, Some(&dev_id));
+ break; // We are done here
+ } else {
+ blinking = true;
+
+ tokens.iter().for_each(|(_dev, tx)| {
+ // A send operation can only fail if the receiving end of a channel is disconnected, implying that the data could never be received.
+ // We ignore errors here for now, but should probably remove the device in such a case (even though it theoretically can't happen)
+ let _ = tx.send(DeviceCommand::Blink);
+ });
+ }
+ }
+ }
+ });
+ Self {
+ runloop: runloop.unwrap(), // TODO
+ sender: selector_send,
+ }
+ }
+
+ pub fn clone_sender(&self) -> Sender<DeviceSelectorEvent> {
+ self.sender.clone()
+ }
+
+ fn cancel_all(tokens: HashMap<DeviceID, Sender<DeviceCommand>>, exclude: Option<&DeviceID>) {
+ for (dev_id, tx) in tokens.iter() {
+ if Some(dev_id) != exclude {
+ let _ = tx.send(DeviceCommand::Cancel);
+ }
+ }
+ }
+
+ pub fn stop(&mut self) {
+ // We ignore a possible error here, since we don't really care
+ let _ = self.sender.send(DeviceSelectorEvent::Cancel);
+ self.runloop.cancel();
+ }
+}
+
+#[cfg(test)]
+pub mod tests {
+ use super::*;
+ use crate::{
+ consts::Capability,
+ ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorOptions},
+ transport::FidoDevice,
+ u2ftypes::U2FDeviceInfo,
+ };
+
+ pub(crate) fn gen_info(id: String) -> U2FDeviceInfo {
+ U2FDeviceInfo {
+ vendor_name: String::from("ExampleVendor").into_bytes(),
+ device_name: id.into_bytes(),
+ version_interface: 1,
+ version_major: 3,
+ version_minor: 2,
+ version_build: 1,
+ cap_flags: Capability::WINK | Capability::CBOR | Capability::NMSG,
+ }
+ }
+
+ pub(crate) fn make_device_simple_u2f(dev: &mut Device) {
+ dev.set_device_info(gen_info(dev.id()));
+ dev.set_cid([1, 2, 3, 4]); // Need to set something other than broadcast
+ dev.downgrade_to_ctap1();
+ dev.create_channel();
+ }
+
+ pub(crate) fn make_device_with_pin(dev: &mut Device) {
+ dev.set_device_info(gen_info(dev.id()));
+ dev.set_cid([1, 2, 3, 4]); // Need to set something other than broadcast
+ dev.create_channel();
+ let info = AuthenticatorInfo {
+ options: AuthenticatorOptions {
+ client_pin: Some(true),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+ dev.set_authenticator_info(info);
+ }
+
+ fn send_i_am_token(dev: &Device, selector: &DeviceSelector) {
+ selector
+ .sender
+ .send(DeviceSelectorEvent::ImAToken((
+ dev.id(),
+ dev.sender.clone().unwrap(),
+ )))
+ .unwrap();
+ }
+
+ fn send_no_token(dev: &Device, selector: &DeviceSelector) {
+ selector
+ .sender
+ .send(DeviceSelectorEvent::NotAToken(dev.id()))
+ .unwrap()
+ }
+
+ fn remove_device(dev: &Device, selector: &DeviceSelector) {
+ selector
+ .sender
+ .send(DeviceSelectorEvent::DeviceRemoved(dev.id()))
+ .unwrap();
+ assert_eq!(
+ dev.receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Removed
+ );
+ }
+
+ fn add_devices<'a, T>(iter: T, selector: &DeviceSelector)
+ where
+ T: Iterator<Item = &'a Device>,
+ {
+ selector
+ .sender
+ .send(DeviceSelectorEvent::DevicesAdded(
+ iter.map(|f| f.id()).collect(),
+ ))
+ .unwrap();
+ }
+
+ #[test]
+ fn test_device_selector_one_token_no_late_adds() {
+ let mut devices = vec![
+ Device::new("device selector 1").unwrap(),
+ Device::new("device selector 2").unwrap(),
+ Device::new("device selector 3").unwrap(),
+ Device::new("device selector 4").unwrap(),
+ ];
+
+ // Make those actual tokens. The rest is interpreted as non-u2f-devices
+ make_device_with_pin(&mut devices[2]);
+ let selector = DeviceSelector::run();
+
+ // Adding all
+ add_devices(devices.iter(), &selector);
+ devices.iter_mut().for_each(|d| {
+ if !d.is_u2f() {
+ send_no_token(d, &selector);
+ }
+ });
+
+ send_i_am_token(&devices[2], &selector);
+
+ assert_eq!(
+ devices[2].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Continue
+ );
+ }
+
+ // This test is mostly for testing stop() and clone_sender()
+ #[test]
+ fn test_device_selector_stop() {
+ let device = Device::new("device selector 1").unwrap();
+
+ let mut selector = DeviceSelector::run();
+
+ // Adding all
+ selector
+ .clone_sender()
+ .send(DeviceSelectorEvent::DevicesAdded(vec![device.id()]))
+ .unwrap();
+
+ selector
+ .clone_sender()
+ .send(DeviceSelectorEvent::NotAToken(device.id()))
+ .unwrap();
+ selector.stop();
+ }
+
+ #[test]
+ fn test_device_selector_all_pins_with_late_add() {
+ let mut devices = vec![
+ Device::new("device selector 1").unwrap(),
+ Device::new("device selector 2").unwrap(),
+ Device::new("device selector 3").unwrap(),
+ Device::new("device selector 4").unwrap(),
+ Device::new("device selector 5").unwrap(),
+ Device::new("device selector 6").unwrap(),
+ ];
+
+ // Make those actual tokens. The rest is interpreted as non-u2f-devices
+ make_device_with_pin(&mut devices[2]);
+ make_device_with_pin(&mut devices[4]);
+ make_device_with_pin(&mut devices[5]);
+
+ let selector = DeviceSelector::run();
+
+ // Adding all, except the last one (we simulate that this one is not yet plugged in)
+ add_devices(devices.iter().take(5), &selector);
+
+ // Interleave tokens and non-tokens
+ send_i_am_token(&devices[2], &selector);
+
+ devices.iter_mut().for_each(|d| {
+ if !d.is_u2f() {
+ send_no_token(d, &selector);
+ }
+ });
+
+ send_i_am_token(&devices[4], &selector);
+
+ // We added 2 devices that are tokens. They should get the blink-command now
+ assert_eq!(
+ devices[2].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+ assert_eq!(
+ devices[4].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+
+ // Plug in late device
+ send_i_am_token(&devices[5], &selector);
+ assert_eq!(
+ devices[5].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+ }
+
+ #[test]
+ fn test_device_selector_no_pins_late_mixed_adds() {
+ // Multiple tokes, none of them support a PIN
+ let mut devices = vec![
+ Device::new("device selector 1").unwrap(),
+ Device::new("device selector 2").unwrap(),
+ Device::new("device selector 3").unwrap(),
+ Device::new("device selector 4").unwrap(),
+ Device::new("device selector 5").unwrap(),
+ Device::new("device selector 6").unwrap(),
+ Device::new("device selector 7").unwrap(),
+ ];
+
+ // Make those actual tokens. The rest is interpreted as non-u2f-devices
+ make_device_simple_u2f(&mut devices[2]);
+ make_device_simple_u2f(&mut devices[4]);
+ make_device_simple_u2f(&mut devices[5]);
+
+ let selector = DeviceSelector::run();
+
+ // Adding all, except the last one (we simulate that this one is not yet plugged in)
+ add_devices(devices.iter().take(5), &selector);
+
+ // Interleave tokens and non-tokens
+ send_i_am_token(&devices[2], &selector);
+
+ devices.iter_mut().for_each(|d| {
+ if !d.is_u2f() {
+ send_no_token(d, &selector);
+ }
+ });
+
+ send_i_am_token(&devices[4], &selector);
+
+ // We added 2 devices that are tokens. They should get the blink-command now
+ assert_eq!(
+ devices[2].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+ assert_eq!(
+ devices[4].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+
+ // Plug in late device
+ send_i_am_token(&devices[5], &selector);
+ assert_eq!(
+ devices[5].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+ // Remove device again
+ remove_device(&devices[5], &selector);
+
+ // Now we add a token that has a PIN, it should not get "Continue" but "Blink"
+ make_device_with_pin(&mut devices[6]);
+ send_i_am_token(&devices[6], &selector);
+ assert_eq!(
+ devices[6].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+ }
+
+ #[test]
+ fn test_device_selector_mixed_pins_remove_all() {
+ // Multiple tokes, none of them support a PIN, so we should get Continue-commands
+ // for all of them
+ let mut devices = vec![
+ Device::new("device selector 1").unwrap(),
+ Device::new("device selector 2").unwrap(),
+ Device::new("device selector 3").unwrap(),
+ Device::new("device selector 4").unwrap(),
+ Device::new("device selector 5").unwrap(),
+ Device::new("device selector 6").unwrap(),
+ ];
+
+ // Make those actual tokens. The rest is interpreted as non-u2f-devices
+ make_device_with_pin(&mut devices[2]);
+ make_device_with_pin(&mut devices[4]);
+ make_device_with_pin(&mut devices[5]);
+
+ let selector = DeviceSelector::run();
+
+ // Adding all, except the last one (we simulate that this one is not yet plugged in)
+ add_devices(devices.iter(), &selector);
+
+ devices.iter_mut().for_each(|d| {
+ if d.is_u2f() {
+ send_i_am_token(d, &selector);
+ } else {
+ send_no_token(d, &selector);
+ }
+ });
+
+ for idx in [2, 4, 5] {
+ assert_eq!(
+ devices[idx].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Blink
+ );
+ }
+
+ // Remove all tokens
+ for idx in [2, 4, 5] {
+ remove_device(&devices[idx], &selector);
+ }
+
+ // Adding one again
+ send_i_am_token(&devices[4], &selector);
+
+ // This should now get a "Continue" instead of "Blinking", because it's the only device
+ assert_eq!(
+ devices[4].receiver.as_ref().unwrap().recv().unwrap(),
+ DeviceCommand::Continue
+ );
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/errors.rs b/third_party/rust/authenticator/src/transport/errors.rs
new file mode 100644
index 0000000000..451c27d6e8
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/errors.rs
@@ -0,0 +1,98 @@
+use crate::consts::{SW_CONDITIONS_NOT_SATISFIED, SW_NO_ERROR, SW_WRONG_DATA, SW_WRONG_LENGTH};
+use crate::ctap2::commands::CommandError;
+use std::fmt;
+use std::io;
+use std::path;
+
+#[allow(unused)]
+#[derive(Debug, PartialEq, Eq)]
+pub enum ApduErrorStatus {
+ ConditionsNotSatisfied,
+ WrongData,
+ WrongLength,
+ Unknown([u8; 2]),
+}
+
+impl ApduErrorStatus {
+ pub fn from(status: [u8; 2]) -> Result<(), ApduErrorStatus> {
+ match status {
+ s if s == SW_NO_ERROR => Ok(()),
+ s if s == SW_CONDITIONS_NOT_SATISFIED => Err(ApduErrorStatus::ConditionsNotSatisfied),
+ s if s == SW_WRONG_DATA => Err(ApduErrorStatus::WrongData),
+ s if s == SW_WRONG_LENGTH => Err(ApduErrorStatus::WrongLength),
+ other => Err(ApduErrorStatus::Unknown(other)),
+ }
+ }
+}
+
+impl fmt::Display for ApduErrorStatus {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ ApduErrorStatus::ConditionsNotSatisfied => write!(f, "Apdu: condition not satisfied"),
+ ApduErrorStatus::WrongData => write!(f, "Apdu: wrong data"),
+ ApduErrorStatus::WrongLength => write!(f, "Apdu: wrong length"),
+ ApduErrorStatus::Unknown(ref u) => write!(f, "Apdu: unknown error: {u:?}"),
+ }
+ }
+}
+
+#[allow(unused)]
+#[derive(Debug)]
+pub enum HIDError {
+ /// Transport replied with a status not expected
+ DeviceError,
+ UnexpectedInitReplyLen,
+ NonceMismatch,
+ DeviceNotInitialized,
+ DeviceNotSupported,
+ UnsupportedCommand,
+ UnexpectedVersion,
+ IO(Option<path::PathBuf>, io::Error),
+ UnexpectedCmd(u8),
+ Command(CommandError),
+ ApduStatus(ApduErrorStatus),
+}
+
+impl From<io::Error> for HIDError {
+ fn from(e: io::Error) -> HIDError {
+ HIDError::IO(None, e)
+ }
+}
+
+impl From<CommandError> for HIDError {
+ fn from(e: CommandError) -> HIDError {
+ HIDError::Command(e)
+ }
+}
+
+impl From<ApduErrorStatus> for HIDError {
+ fn from(e: ApduErrorStatus) -> HIDError {
+ HIDError::ApduStatus(e)
+ }
+}
+
+impl fmt::Display for HIDError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ HIDError::UnexpectedInitReplyLen => {
+ write!(f, "Error: Unexpected reply len when initilizaling")
+ }
+ HIDError::NonceMismatch => write!(f, "Error: Nonce mismatch"),
+ HIDError::DeviceError => write!(f, "Error: device returned error"),
+ HIDError::DeviceNotInitialized => write!(f, "Error: using not initiliazed device"),
+ HIDError::DeviceNotSupported => {
+ write!(f, "Error: requested operation is not available on device")
+ }
+ HIDError::UnexpectedVersion => write!(f, "Error: Unexpected protocol version"),
+ HIDError::UnsupportedCommand => {
+ write!(f, "Error: command is not supported on this device")
+ }
+ HIDError::IO(ref p, ref e) => write!(f, "Error: Ioerror({p:?}): {e}"),
+ HIDError::Command(ref e) => write!(f, "Error: Error issuing command: {e}"),
+ HIDError::UnexpectedCmd(s) => write!(f, "Error: Unexpected status: {s}"),
+ HIDError::ApduStatus(ref status) => {
+ write!(f, "Error: Unexpected apdu status: {status:?}")
+ }
+ }
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/freebsd/device.rs b/third_party/rust/authenticator/src/transport/freebsd/device.rs
new file mode 100644
index 0000000000..0e8c7d20a7
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/freebsd/device.rs
@@ -0,0 +1,235 @@
+/* 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/. */
+
+extern crate libc;
+
+use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
+use crate::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::transport::hid::HIDDevice;
+use crate::transport::platform::uhid;
+use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
+use crate::u2ftypes::U2FDeviceInfo;
+use crate::util::from_unix_result;
+use crate::util::io_err;
+use std::ffi::{CString, OsString};
+use std::hash::{Hash, Hasher};
+use std::io::{self, Read, Write};
+use std::mem;
+use std::os::unix::prelude::*;
+
+#[derive(Debug)]
+pub struct Device {
+ path: OsString,
+ fd: libc::c_int,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+ secret: Option<SharedSecret>,
+ authenticator_info: Option<AuthenticatorInfo>,
+ protocol: FidoProtocol,
+}
+
+impl Device {
+ fn ping(&mut self) -> io::Result<()> {
+ for i in 0..10 {
+ let mut buf = vec![0u8; 1 + MAX_HID_RPT_SIZE];
+
+ buf[0] = 0; // report number
+ buf[1] = 0xff; // CID_BROADCAST
+ buf[2] = 0xff;
+ buf[3] = 0xff;
+ buf[4] = 0xff;
+ buf[5] = 0x81; // ping
+ buf[6] = 0;
+ buf[7] = 1; // one byte
+
+ if self.write(&buf)? != buf.len() {
+ return Err(io_err("write ping failed"));
+ }
+
+ // Wait for response
+ let mut pfd: libc::pollfd = unsafe { mem::zeroed() };
+ pfd.fd = self.fd;
+ pfd.events = libc::POLLIN;
+ let nfds = unsafe { libc::poll(&mut pfd, 1, 100) };
+ if nfds == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ if nfds == 0 {
+ debug!("device timeout {}", i);
+ continue;
+ }
+
+ // Read response. When reports come in they are all
+ // exactly the same size, with no report id byte because
+ // there is only one report.
+ let n = self.read(&mut buf[1..])?;
+ if n != buf.len() - 1 {
+ return Err(io_err("read pong failed"));
+ }
+
+ return Ok(());
+ }
+
+ Err(io_err("no response from device"))
+ }
+}
+
+impl Drop for Device {
+ fn drop(&mut self) {
+ // Close the fd, ignore any errors.
+ let _ = unsafe { libc::close(self.fd) };
+ }
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ self.path == other.path
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.path.hash(state);
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let bufp = buf.as_mut_ptr() as *mut libc::c_void;
+ let rv = unsafe { libc::read(self.fd, bufp, buf.len()) };
+ from_unix_result(rv as usize)
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let report_id = buf[0] as i64;
+ // Skip report number when not using numbered reports.
+ let start = if report_id == 0x0 { 1 } else { 0 };
+ let data = &buf[start..];
+
+ let data_ptr = data.as_ptr() as *const libc::c_void;
+ let rv = unsafe { libc::write(self.fd, data_ptr, data.len()) };
+ from_unix_result(rv as usize + 1)
+ }
+
+ // USB HID writes don't buffer, so this will be a nop.
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl HIDDevice for Device {
+ type BuildParameters = OsString;
+ type Id = OsString;
+
+ fn new(path: OsString) -> Result<Self, (HIDError, Self::Id)> {
+ let cstr =
+ CString::new(path.as_bytes()).map_err(|_| (HIDError::DeviceError, path.clone()))?;
+ let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
+ let fd = from_unix_result(fd).map_err(|e| (e.into(), path.clone()))?;
+ let mut res = Self {
+ path,
+ fd,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ secret: None,
+ authenticator_info: None,
+ protocol: FidoProtocol::CTAP2,
+ };
+ if res.is_u2f() {
+ info!("new device {:?}", res.path);
+ Ok(res)
+ } else {
+ Err((HIDError::DeviceNotSupported, res.path.clone()))
+ }
+ }
+
+ fn id(&self) -> Self::Id {
+ self.path.clone()
+ }
+
+ fn get_cid(&self) -> &[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);
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ HIDDevice::pre_init(self)
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ HIDDevice::get_device_info(self)
+ .cap_flags
+ .contains(Capability::CBOR)
+ }
+
+ fn initialized(&self) -> bool {
+ // During successful init, the broadcast channel id gets repplaced by an actual one
+ self.cid != CID_BROADCAST
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ if !uhid::is_u2f_device(self.fd) {
+ return false;
+ }
+ if self.ping().is_err() {
+ return false;
+ }
+ true
+ }
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ self.secret.as_ref()
+ }
+
+ fn set_shared_secret(&mut self, secret: SharedSecret) {
+ self.secret = Some(secret);
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1;
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/freebsd/mod.rs b/third_party/rust/authenticator/src/transport/freebsd/mod.rs
new file mode 100644
index 0000000000..7ed5727157
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/freebsd/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 uhid;
diff --git a/third_party/rust/authenticator/src/transport/freebsd/monitor.rs b/third_party/rust/authenticator/src/transport/freebsd/monitor.rs
new file mode 100644
index 0000000000..340ebef836
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/freebsd/monitor.rs
@@ -0,0 +1,161 @@
+/* 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::transport::device_selector::DeviceSelectorEvent;
+use devd_rs;
+use runloop::RunLoop;
+use std::collections::HashMap;
+use std::error::Error;
+use std::ffi::OsString;
+use std::sync::{mpsc::Sender, Arc};
+use std::{fs, io};
+
+const POLL_TIMEOUT: usize = 100;
+
+pub enum Event {
+ Add(OsString),
+ Remove(OsString),
+}
+
+impl Event {
+ fn from_devd(event: devd_rs::Event) -> Option<Self> {
+ match event {
+ devd_rs::Event::Attach {
+ ref dev,
+ parent: _,
+ location: _,
+ } if dev.starts_with("uhid") => Some(Event::Add(("/dev/".to_owned() + dev).into())),
+ devd_rs::Event::Detach {
+ ref dev,
+ parent: _,
+ location: _,
+ } if dev.starts_with("uhid") => Some(Event::Remove(("/dev/".to_owned() + dev).into())),
+ _ => None,
+ }
+ }
+}
+
+fn convert_error(e: devd_rs::Error) -> io::Error {
+ e.into()
+}
+
+pub struct Monitor<F>
+where
+ F: Fn(OsString, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ + Sync,
+{
+ runloops: HashMap<OsString, RunLoop>,
+ new_device_cb: Arc<F>,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(OsString, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ + Send
+ + Sync
+ + 'static,
+{
+ pub fn new(
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+ ) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ selector_sender,
+ status_sender,
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
+ let mut ctx = devd_rs::Context::new().map_err(convert_error)?;
+
+ let mut initial_devs = Vec::new();
+ // Iterate all existing devices.
+ for dev in (fs::read_dir("/dev")?).flatten() {
+ let filename_ = dev.file_name();
+ let filename = filename_.to_str().unwrap_or("");
+ if filename.starts_with("uhid") {
+ let path = OsString::from("/dev/".to_owned() + filename);
+ initial_devs.push(path.clone());
+ self.add_device(path);
+ }
+ }
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(initial_devs));
+
+ // Loop until we're stopped by the controlling thread, or fail.
+ while alive() {
+ // Wait for new events, break on failure.
+ match ctx.wait_for_event(POLL_TIMEOUT) {
+ Err(devd_rs::Error::Timeout) => (),
+ Err(e) => return Err(convert_error(e).into()),
+ Ok(event) => {
+ if let Some(event) = Event::from_devd(event) {
+ self.process_event(event);
+ }
+ }
+ }
+ }
+
+ // Remove all tracked devices.
+ self.remove_all_devices();
+
+ Ok(())
+ }
+
+ fn process_event(&mut self, event: Event) {
+ match event {
+ Event::Add(path) => {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(vec![path.clone()]));
+ self.add_device(path);
+ }
+ Event::Remove(path) => {
+ self.remove_device(path);
+ }
+ }
+ }
+
+ fn add_device(&mut self, path: OsString) {
+ let f = self.new_device_cb.clone();
+ let selector_sender = self.selector_sender.clone();
+ let status_sender = self.status_sender.clone();
+ let key = path.clone();
+ debug!("Adding device {}", key.to_string_lossy());
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(path, selector_sender, status_sender, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: OsString) {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
+
+ debug!("Removing device {}", path.to_string_lossy());
+ 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/transport/freebsd/transaction.rs b/third_party/rust/authenticator/src/transport/freebsd/transaction.rs
new file mode 100644
index 0000000000..6b15f6751a
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/freebsd/transaction.rs
@@ -0,0 +1,69 @@
+/* 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::statecallback::StateCallback;
+use crate::transport::device_selector::{
+ DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
+};
+use crate::transport::platform::monitor::Monitor;
+use runloop::RunLoop;
+use std::sync::mpsc::Sender;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: RunLoop,
+ device_selector: DeviceSelector,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ status: Sender<crate::StatusUpdate>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ let device_selector = DeviceSelector::run();
+ let selector_sender = device_selector.clone_sender();
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
+
+ // 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,
+ device_selector,
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ info!("Transaction was cancelled.");
+ self.device_selector.stop();
+ self.thread.cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/freebsd/uhid.rs b/third_party/rust/authenticator/src/transport/freebsd/uhid.rs
new file mode 100644
index 0000000000..681b09a768
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/freebsd/uhid.rs
@@ -0,0 +1,89 @@
+/* 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/. */
+
+extern crate libc;
+
+use std::io;
+use std::os::unix::io::RawFd;
+use std::ptr;
+
+use crate::transport::hidproto::*;
+use crate::util::from_unix_result;
+
+#[allow(non_camel_case_types)]
+#[repr(C)]
+#[derive(Debug)]
+pub struct GenDescriptor {
+ ugd_data: *mut u8,
+ ugd_lang_id: u16,
+ ugd_maxlen: u16,
+ ugd_actlen: u16,
+ ugd_offset: u16,
+ ugd_config_index: u8,
+ ugd_string_index: u8,
+ ugd_iface_index: u8,
+ ugd_altif_index: u8,
+ ugd_endpt_index: u8,
+ ugd_report_index: u8,
+ reserved: [u8; 16],
+}
+
+impl Default for GenDescriptor {
+ fn default() -> GenDescriptor {
+ GenDescriptor {
+ ugd_data: ptr::null_mut(),
+ ugd_lang_id: 0,
+ ugd_maxlen: 65535,
+ ugd_actlen: 0,
+ ugd_offset: 0,
+ ugd_config_index: 0,
+ ugd_string_index: 0,
+ ugd_iface_index: 0,
+ ugd_altif_index: 0,
+ ugd_endpt_index: 0,
+ ugd_report_index: 0,
+ reserved: [0; 16],
+ }
+ }
+}
+
+const IOWR: u32 = 0x40000000 | 0x80000000;
+
+const IOCPARM_SHIFT: u32 = 13;
+const IOCPARM_MASK: u32 = (1 << IOCPARM_SHIFT) - 1;
+
+const TYPESHIFT: u32 = 8;
+const SIZESHIFT: u32 = 16;
+
+macro_rules! ioctl {
+ ($dir:expr, $name:ident, $ioty:expr, $nr:expr, $size:expr; $ty:ty) => {
+ pub unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
+ let ioc = ($dir as u32)
+ | (($size as u32 & IOCPARM_MASK) << SIZESHIFT)
+ | (($ioty as u32) << TYPESHIFT)
+ | ($nr as u32);
+ from_unix_result(libc::ioctl(fd, ioc as libc::c_ulong, val))
+ }
+ };
+}
+
+// https://github.com/freebsd/freebsd/blob/master/sys/dev/usb/usb_ioctl.h
+ioctl!(IOWR, usb_get_report_desc, b'U', 21, 32; /*struct*/ GenDescriptor);
+
+fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
+ let mut desc = GenDescriptor::default();
+ let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
+ desc.ugd_maxlen = desc.ugd_actlen;
+ let mut value = vec![0; desc.ugd_actlen as usize];
+ desc.ugd_data = value.as_mut_ptr();
+ let _ = unsafe { usb_get_report_desc(fd, &mut desc)? };
+ Ok(ReportDescriptor { value })
+}
+
+pub fn is_u2f_device(fd: RawFd) -> bool {
+ match read_report_descriptor(fd) {
+ Ok(desc) => has_fido_usage(desc),
+ Err(_) => false, // Upon failure, just say it's not a U2F device.
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/hid.rs b/third_party/rust/authenticator/src/transport/hid.rs
new file mode 100644
index 0000000000..339cd05d71
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/hid.rs
@@ -0,0 +1,252 @@
+use super::TestDevice;
+use crate::consts::{HIDCmd, CID_BROADCAST};
+use crate::ctap2::commands::{CommandError, RequestCtap1, RequestCtap2, Retryable, StatusCode};
+use crate::transport::errors::{ApduErrorStatus, HIDError};
+use crate::transport::{FidoDevice, FidoDeviceIO, FidoProtocol};
+use crate::u2ftypes::{U2FDeviceInfo, U2FHIDCont, U2FHIDInit, U2FHIDInitResp};
+use crate::util::io_err;
+use rand::{thread_rng, RngCore};
+use std::cmp::Eq;
+use std::fmt;
+use std::hash::Hash;
+use std::io;
+use std::io::{Read, Write};
+use std::thread;
+use std::time::Duration;
+
+pub trait HIDDevice: FidoDevice + Read + Write {
+ type BuildParameters: Sized;
+ type Id: fmt::Debug + PartialEq + Eq + Hash + Sized;
+
+ // Open device, verify that it is indeed a CTAP device and potentially read initial values
+ fn new(parameters: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)>;
+ fn id(&self) -> Self::Id;
+
+ fn get_device_info(&self) -> U2FDeviceInfo;
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo);
+
+ // Channel ID management
+ fn get_cid(&self) -> &[u8; 4];
+ fn set_cid(&mut self, cid: [u8; 4]);
+
+ // HID report sizes
+ fn in_rpt_size(&self) -> usize;
+ fn out_rpt_size(&self) -> usize;
+
+ fn get_property(&self, prop_name: &str) -> io::Result<String>;
+
+ // Initialize on a protocol-level
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ if self.initialized() {
+ return Ok(());
+ }
+
+ let mut nonce = [0u8; 8];
+ thread_rng().fill_bytes(&mut nonce);
+
+ // Send Init to broadcast address to create a new channel
+ self.set_cid(CID_BROADCAST);
+ let (cmd, raw) = HIDDevice::sendrecv(self, HIDCmd::Init, &nonce, &|| true)?;
+ if cmd != HIDCmd::Init {
+ return Err(HIDError::DeviceError);
+ }
+
+ let rsp = U2FHIDInitResp::read(&raw, &nonce)?;
+ // Set the new Channel ID
+ self.set_cid(rsp.cid);
+
+ let vendor = self
+ .get_property("Manufacturer")
+ .unwrap_or_else(|_| String::from("Unknown Vendor"));
+ let product = self
+ .get_property("Product")
+ .unwrap_or_else(|_| String::from("Unknown Device"));
+
+ let info = U2FDeviceInfo {
+ vendor_name: vendor.as_bytes().to_vec(),
+ device_name: product.as_bytes().to_vec(),
+ version_interface: rsp.version_interface,
+ version_major: rsp.version_major,
+ version_minor: rsp.version_minor,
+ version_build: rsp.version_build,
+ cap_flags: rsp.cap_flags,
+ };
+ debug!("{:?}: {:?}", self.id(), info);
+ self.set_device_info(info);
+
+ // A CTAPHID host SHALL accept a response size that is longer than the
+ // anticipated size to allow for future extensions of the protocol, yet
+ // maintaining backwards compatibility. Future versions will maintain
+ // the response structure of the current version, but additional fields
+ // may be added.
+
+ Ok(())
+ }
+
+ fn sendrecv(
+ &mut self,
+ cmd: HIDCmd,
+ send: &[u8],
+ keep_alive: &dyn Fn() -> bool,
+ ) -> io::Result<(HIDCmd, Vec<u8>)> {
+ self.u2f_write(cmd.into(), send)?;
+ debug!("sent to Device {:?} cmd={:?}: {:?}", self.id(), cmd, send);
+ loop {
+ let (cmd, data) = self.u2f_read()?;
+ if cmd != HIDCmd::Keepalive {
+ debug!(
+ "got from Device {:?} status={:?}: {:?}",
+ self.id(),
+ cmd,
+ data
+ );
+ return Ok((cmd, data));
+ }
+ // The authenticator might send us HIDCmd::Keepalive messages indefinitely, e.g. if
+ // it's waiting for user presence. The keep_alive function is used to cancel the
+ // transaction.
+ if !keep_alive() {
+ break;
+ }
+ }
+
+ // If this is a CTAP2 device we can tell the authenticator to cancel the transaction on its
+ // side as well. There's nothing to do for U2F/CTAP1 devices.
+ if self.get_protocol() == FidoProtocol::CTAP2 {
+ self.u2f_write(u8::from(HIDCmd::Cancel), &[])?;
+ }
+ // For CTAP2 devices we expect to read
+ // (HIDCmd::Cbor, [CTAP2_ERR_KEEPALIVE_CANCEL])
+ // for U2F/CTAP1 we expect to read
+ // (HIDCmd::Keepalive, [status]).
+ self.u2f_read()
+ }
+
+ fn u2f_write(&mut self, cmd: u8, send: &[u8]) -> io::Result<()> {
+ let mut count = U2FHIDInit::write(self, cmd, send)?;
+
+ // Send continuation packets.
+ let mut sequence = 0u8;
+ while count < send.len() {
+ count += U2FHIDCont::write(self, sequence, &send[count..])?;
+ sequence += 1;
+ }
+
+ Ok(())
+ }
+
+ fn u2f_read(&mut self) -> io::Result<(HIDCmd, Vec<u8>)> {
+ // Now we read. This happens in 2 chunks: The initial packet, which has
+ // the size we expect overall, then continuation packets, which will
+ // fill in data until we have everything.
+ let (cmd, data) = {
+ let (cmd, mut data) = U2FHIDInit::read(self)?;
+
+ trace!("init frame data read: {:04X?}", &data);
+ let mut sequence = 0u8;
+ while data.len() < data.capacity() {
+ let max = data.capacity() - data.len();
+ data.extend_from_slice(&U2FHIDCont::read(self, sequence, max)?);
+ sequence += 1;
+ }
+ (cmd, data)
+ };
+ trace!("u2f_read({:?}) cmd={:?}: {:04X?}", self.id(), cmd, &data);
+ Ok((cmd, data))
+ }
+}
+
+#[cfg(not(test))]
+impl<T: HIDDevice> TestDevice for T {}
+
+impl<T: HIDDevice + TestDevice> FidoDeviceIO for T {
+ fn send_msg_cancellable<Out, Req: RequestCtap1<Output = Out> + RequestCtap2<Output = Out>>(
+ &mut self,
+ msg: &Req,
+ keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Out, HIDError> {
+ if !self.initialized() {
+ return Err(HIDError::DeviceNotInitialized);
+ }
+
+ match self.get_protocol() {
+ FidoProtocol::CTAP1 => self.send_ctap1_cancellable(msg, keep_alive),
+ FidoProtocol::CTAP2 => self.send_cbor_cancellable(msg, keep_alive),
+ }
+ }
+
+ fn send_cbor_cancellable<Req: RequestCtap2>(
+ &mut self,
+ msg: &Req,
+ keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Req::Output, HIDError> {
+ debug!("sending {:?} to {:?}", msg, self);
+ #[cfg(test)]
+ {
+ if self.skip_serialization() {
+ return self.send_ctap2_unserialized(msg);
+ }
+ }
+
+ let mut data = msg.wire_format()?;
+ let mut buf: Vec<u8> = Vec::with_capacity(data.len() + 1);
+ // CTAP2 command
+ buf.push(msg.command() as u8);
+ // payload
+ buf.append(&mut data);
+ let buf = buf;
+
+ let (cmd, resp) = self.sendrecv(HIDCmd::Cbor, &buf, keep_alive)?;
+ if cmd == HIDCmd::Cbor {
+ Ok(msg.handle_response_ctap2(self, &resp)?)
+ } else {
+ Err(HIDError::UnexpectedCmd(cmd.into()))
+ }
+ }
+
+ fn send_ctap1_cancellable<Req: RequestCtap1>(
+ &mut self,
+ msg: &Req,
+ keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Req::Output, HIDError> {
+ debug!("sending {:?} to {:?}", msg, self);
+ #[cfg(test)]
+ {
+ if self.skip_serialization() {
+ return self.send_ctap1_unserialized(msg);
+ }
+ }
+ let (data, add_info) = msg.ctap1_format()?;
+
+ while keep_alive() {
+ // sendrecv will not block with a CTAP1 device
+ let (cmd, mut data) = self.sendrecv(HIDCmd::Msg, &data, &|| true)?;
+ if cmd == HIDCmd::Msg {
+ if data.len() < 2 {
+ return Err(io_err("Unexpected Response: shorter than expected").into());
+ }
+ let split_at = data.len() - 2;
+ let status = data.split_off(split_at);
+ // This will bubble up error if status != no error
+ let status = ApduErrorStatus::from([status[0], status[1]]);
+
+ match msg.handle_response_ctap1(self, status, &data, &add_info) {
+ Ok(out) => return Ok(out),
+ Err(Retryable::Retry) => {
+ // sleep 100ms then loop again
+ // TODO(baloo): meh, use tokio instead?
+ thread::sleep(Duration::from_millis(100));
+ }
+ Err(Retryable::Error(e)) => return Err(e),
+ }
+ } else {
+ return Err(HIDError::UnexpectedCmd(cmd.into()));
+ }
+ }
+
+ Err(HIDError::Command(CommandError::StatusCode(
+ StatusCode::KeepaliveCancel,
+ None,
+ )))
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/hidproto.rs b/third_party/rust/authenticator/src/transport/hidproto.rs
new file mode 100644
index 0000000000..5a0a9d2605
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/hidproto.rs
@@ -0,0 +1,257 @@
+/* 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/. */
+
+// Shared code for platforms that use raw HID access (Linux, FreeBSD, etc.)
+
+#![cfg_attr(
+ feature = "cargo-clippy",
+ allow(clippy::cast_lossless, clippy::needless_lifetimes)
+)]
+
+#[cfg(target_os = "linux")]
+use std::io;
+use std::mem;
+
+use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
+#[cfg(target_os = "linux")]
+use crate::consts::{INIT_HEADER_SIZE, MAX_HID_RPT_SIZE};
+
+// The 4 MSBs (the tag) are set when it's a long item.
+const HID_MASK_LONG_ITEM_TAG: u8 = 0b1111_0000;
+// The 2 LSBs denote the size of a short item.
+const HID_MASK_SHORT_ITEM_SIZE: u8 = 0b0000_0011;
+// The 6 MSBs denote the tag (4) and type (2).
+const HID_MASK_ITEM_TAGTYPE: u8 = 0b1111_1100;
+// tag=0000, type=10 (local)
+const HID_ITEM_TAGTYPE_USAGE: u8 = 0b0000_1000;
+// tag=0000, type=01 (global)
+const HID_ITEM_TAGTYPE_USAGE_PAGE: u8 = 0b0000_0100;
+// tag=1000, type=00 (main)
+const HID_ITEM_TAGTYPE_INPUT: u8 = 0b1000_0000;
+// tag=1001, type=00 (main)
+const HID_ITEM_TAGTYPE_OUTPUT: u8 = 0b1001_0000;
+// tag=1001, type=01 (global)
+const HID_ITEM_TAGTYPE_REPORT_COUNT: u8 = 0b1001_0100;
+
+pub struct ReportDescriptor {
+ pub value: Vec<u8>,
+}
+
+impl ReportDescriptor {
+ fn iter(self) -> ReportDescriptorIterator {
+ ReportDescriptorIterator::new(self)
+ }
+}
+
+#[derive(Debug)]
+pub enum Data {
+ UsagePage { data: u32 },
+ Usage { data: u32 },
+ Input,
+ Output,
+ ReportCount { data: u32 },
+}
+
+pub struct ReportDescriptorIterator {
+ desc: ReportDescriptor,
+ pos: usize,
+}
+
+impl ReportDescriptorIterator {
+ fn new(desc: ReportDescriptor) -> Self {
+ Self { desc, pos: 0 }
+ }
+
+ fn next_item(&mut self) -> Option<Data> {
+ let item = get_hid_item(&self.desc.value[self.pos..]);
+ if item.is_none() {
+ self.pos = self.desc.value.len(); // Close, invalid data.
+ return None;
+ }
+
+ let (tag_type, key_len, data) = item.unwrap();
+
+ // Advance if we have a valid item.
+ self.pos += key_len + data.len();
+
+ // We only check short items.
+ if key_len > 1 {
+ return None; // Check next item.
+ }
+
+ // Short items have max. length of 4 bytes.
+ assert!(data.len() <= mem::size_of::<u32>());
+
+ // Convert data bytes to a uint.
+ let data = read_uint_le(data);
+ match tag_type {
+ HID_ITEM_TAGTYPE_USAGE_PAGE => Some(Data::UsagePage { data }),
+ HID_ITEM_TAGTYPE_USAGE => Some(Data::Usage { data }),
+ HID_ITEM_TAGTYPE_INPUT => Some(Data::Input),
+ HID_ITEM_TAGTYPE_OUTPUT => Some(Data::Output),
+ HID_ITEM_TAGTYPE_REPORT_COUNT => Some(Data::ReportCount { data }),
+ _ => None,
+ }
+ }
+}
+
+impl Iterator for ReportDescriptorIterator {
+ type Item = Data;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.pos >= self.desc.value.len() {
+ return None;
+ }
+
+ self.next_item().or_else(|| self.next())
+ }
+}
+
+fn get_hid_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
+ if (buf[0] & HID_MASK_LONG_ITEM_TAG) == HID_MASK_LONG_ITEM_TAG {
+ get_hid_long_item(buf)
+ } else {
+ get_hid_short_item(buf)
+ }
+}
+
+fn get_hid_long_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
+ // A valid long item has at least three bytes.
+ if buf.len() < 3 {
+ return None;
+ }
+
+ let len = buf[1] as usize;
+
+ // Ensure that there are enough bytes left in the buffer.
+ if len > buf.len() - 3 {
+ return None;
+ }
+
+ Some((buf[2], 3 /* key length */, &buf[3..]))
+}
+
+fn get_hid_short_item<'a>(buf: &'a [u8]) -> Option<(u8, usize, &'a [u8])> {
+ // This is a short item. The bottom two bits of the key
+ // contain the length of the data section (value) for this key.
+ let len = match buf[0] & HID_MASK_SHORT_ITEM_SIZE {
+ s @ 0..=2 => s as usize,
+ _ => 4, /* _ == 3 */
+ };
+
+ // Ensure that there are enough bytes left in the buffer.
+ if len > buf.len() - 1 {
+ return None;
+ }
+
+ Some((
+ buf[0] & HID_MASK_ITEM_TAGTYPE,
+ 1, /* key length */
+ &buf[1..=len],
+ ))
+}
+
+fn read_uint_le(buf: &[u8]) -> u32 {
+ assert!(buf.len() <= 4);
+ // Parse the number in little endian byte order.
+ buf.iter()
+ .rev()
+ .fold(0, |num, b| (num << 8) | (u32::from(*b)))
+}
+
+pub fn has_fido_usage(desc: ReportDescriptor) -> bool {
+ let mut usage_page = None;
+ let mut usage = None;
+
+ for data in desc.iter() {
+ match data {
+ Data::UsagePage { data } => usage_page = Some(data),
+ Data::Usage { data } => usage = Some(data),
+ _ => {}
+ }
+
+ // Check the values we found.
+ if let (Some(usage_page), Some(usage)) = (usage_page, usage) {
+ return usage_page == u32::from(FIDO_USAGE_PAGE)
+ && usage == u32::from(FIDO_USAGE_U2FHID);
+ }
+ }
+
+ false
+}
+
+#[cfg(target_os = "linux")]
+pub fn read_hid_rpt_sizes(desc: ReportDescriptor) -> io::Result<(usize, usize)> {
+ let mut in_rpt_count = None;
+ let mut out_rpt_count = None;
+ let mut last_rpt_count = None;
+
+ for data in desc.iter() {
+ match data {
+ Data::ReportCount { data } => {
+ if last_rpt_count.is_some() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Duplicate HID_ReportCount",
+ ));
+ }
+ last_rpt_count = Some(data as usize);
+ }
+ Data::Input => {
+ if last_rpt_count.is_none() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "HID_Input should be preceded by HID_ReportCount",
+ ));
+ }
+ if in_rpt_count.is_some() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Duplicate HID_ReportCount",
+ ));
+ }
+ in_rpt_count = last_rpt_count;
+ last_rpt_count = None
+ }
+ Data::Output => {
+ if last_rpt_count.is_none() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "HID_Output should be preceded by HID_ReportCount",
+ ));
+ }
+ if out_rpt_count.is_some() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Duplicate HID_ReportCount",
+ ));
+ }
+ out_rpt_count = last_rpt_count;
+ last_rpt_count = None;
+ }
+ _ => {}
+ }
+ }
+
+ match (in_rpt_count, out_rpt_count) {
+ (Some(in_count), Some(out_count)) => {
+ if in_count > INIT_HEADER_SIZE
+ && in_count <= MAX_HID_RPT_SIZE
+ && out_count > INIT_HEADER_SIZE
+ && out_count <= MAX_HID_RPT_SIZE
+ {
+ Ok((in_count, out_count))
+ } else {
+ Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Report size is too small or too large",
+ ))
+ }
+ }
+ _ => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Failed to extract report sizes from report descriptor",
+ )),
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/linux/device.rs b/third_party/rust/authenticator/src/transport/linux/device.rs
new file mode 100644
index 0000000000..6abd462d48
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/device.rs
@@ -0,0 +1,181 @@
+/* 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/. */
+
+extern crate libc;
+use crate::consts::{Capability, CID_BROADCAST};
+use crate::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::transport::hid::HIDDevice;
+use crate::transport::platform::{hidraw, monitor};
+use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
+use crate::u2ftypes::U2FDeviceInfo;
+use crate::util::from_unix_result;
+use std::fs::OpenOptions;
+use std::hash::{Hash, Hasher};
+use std::io;
+use std::io::{Read, Write};
+use std::os::unix::io::AsRawFd;
+use std::path::PathBuf;
+
+#[derive(Debug)]
+pub struct Device {
+ path: PathBuf,
+ fd: std::fs::File,
+ in_rpt_size: usize,
+ out_rpt_size: usize,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+ secret: Option<SharedSecret>,
+ authenticator_info: Option<AuthenticatorInfo>,
+ protocol: FidoProtocol,
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ // The path should be the only identifying member for a device
+ // If the path is the same, its the same device
+ self.path == other.path
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // The path should be the only identifying member for a device
+ // If the path is the same, its the same device
+ self.path.hash(state);
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let bufp = buf.as_mut_ptr() as *mut libc::c_void;
+ let rv = unsafe { libc::read(self.fd.as_raw_fd(), bufp, buf.len()) };
+ from_unix_result(rv as usize)
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let bufp = buf.as_ptr() as *const libc::c_void;
+ let rv = unsafe { libc::write(self.fd.as_raw_fd(), bufp, buf.len()) };
+ from_unix_result(rv as usize)
+ }
+
+ // USB HID writes don't buffer, so this will be a nop.
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl HIDDevice for Device {
+ type BuildParameters = PathBuf;
+ type Id = PathBuf;
+
+ fn new(path: PathBuf) -> Result<Self, (HIDError, Self::Id)> {
+ debug!("Opening device {:?}", path);
+ let fd = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&path)
+ .map_err(|e| (HIDError::IO(Some(path.clone()), e), path.clone()))?;
+ let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd.as_raw_fd());
+ let mut res = Self {
+ path,
+ fd,
+ in_rpt_size,
+ out_rpt_size,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ secret: None,
+ authenticator_info: None,
+ protocol: FidoProtocol::CTAP2,
+ };
+ if res.is_u2f() {
+ info!("new device {:?}", res.path);
+ Ok(res)
+ } else {
+ Err((HIDError::DeviceNotSupported, res.path))
+ }
+ }
+
+ fn id(&self) -> Self::Id {
+ self.path.clone()
+ }
+
+ fn get_cid(&self) -> &[u8; 4] {
+ &self.cid
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ self.cid = cid;
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ self.in_rpt_size
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ self.out_rpt_size
+ }
+
+ fn get_property(&self, prop_name: &str) -> io::Result<String> {
+ monitor::get_property_linux(&self.path, prop_name)
+ }
+
+ 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);
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ HIDDevice::pre_init(self)
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ HIDDevice::get_device_info(self)
+ .cap_flags
+ .contains(Capability::CBOR)
+ }
+
+ fn initialized(&self) -> bool {
+ // During successful init, the broadcast channel id gets replaced by an actual one
+ self.cid != CID_BROADCAST
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ hidraw::is_u2f_device(self.fd.as_raw_fd())
+ }
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ self.secret.as_ref()
+ }
+
+ fn set_shared_secret(&mut self, secret: SharedSecret) {
+ self.secret = Some(secret);
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1;
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/linux/hidraw.rs b/third_party/rust/authenticator/src/transport/linux/hidraw.rs
new file mode 100644
index 0000000000..16d687f358
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/hidraw.rs
@@ -0,0 +1,80 @@
+/* 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/. */
+#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
+
+extern crate libc;
+
+use std::io;
+use std::os::unix::io::RawFd;
+
+use super::hidwrapper::{_HIDIOCGRDESC, _HIDIOCGRDESCSIZE};
+use crate::consts::MAX_HID_RPT_SIZE;
+use crate::transport::hidproto::*;
+use crate::util::{from_unix_result, io_err};
+
+#[allow(non_camel_case_types)]
+#[repr(C)]
+pub struct LinuxReportDescriptor {
+ size: ::libc::c_int,
+ value: [u8; 4096],
+}
+
+const HID_MAX_DESCRIPTOR_SIZE: usize = 4096;
+
+#[cfg(not(target_env = "musl"))]
+type IocType = libc::c_ulong;
+#[cfg(target_env = "musl")]
+type IocType = libc::c_int;
+
+pub unsafe fn hidiocgrdescsize(
+ fd: libc::c_int,
+ val: *mut ::libc::c_int,
+) -> io::Result<libc::c_int> {
+ from_unix_result(libc::ioctl(fd, _HIDIOCGRDESCSIZE as IocType, val))
+}
+
+pub unsafe fn hidiocgrdesc(
+ fd: libc::c_int,
+ val: *mut LinuxReportDescriptor,
+) -> io::Result<libc::c_int> {
+ from_unix_result(libc::ioctl(fd, _HIDIOCGRDESC as IocType, val))
+}
+
+pub fn is_u2f_device(fd: RawFd) -> bool {
+ match read_report_descriptor(fd) {
+ Ok(desc) => has_fido_usage(desc),
+ Err(_) => false, // Upon failure, just say it's not a U2F device.
+ }
+}
+
+pub fn read_hid_rpt_sizes_or_defaults(fd: RawFd) -> (usize, usize) {
+ let default_rpt_sizes = (MAX_HID_RPT_SIZE, MAX_HID_RPT_SIZE);
+ let desc = read_report_descriptor(fd);
+ if let Ok(desc) = desc {
+ if let Ok(rpt_sizes) = read_hid_rpt_sizes(desc) {
+ rpt_sizes
+ } else {
+ default_rpt_sizes
+ }
+ } else {
+ default_rpt_sizes
+ }
+}
+
+fn read_report_descriptor(fd: RawFd) -> io::Result<ReportDescriptor> {
+ let mut desc = LinuxReportDescriptor {
+ size: 0,
+ value: [0; HID_MAX_DESCRIPTOR_SIZE],
+ };
+
+ let _ = unsafe { hidiocgrdescsize(fd, &mut desc.size)? };
+ if desc.size == 0 || desc.size as usize > desc.value.len() {
+ return Err(io_err("unexpected hidiocgrdescsize() result"));
+ }
+
+ let _ = unsafe { hidiocgrdesc(fd, &mut desc)? };
+ let mut value = Vec::from(&desc.value[..]);
+ value.truncate(desc.size as usize);
+ Ok(ReportDescriptor { value })
+}
diff --git a/third_party/rust/authenticator/src/transport/linux/hidwrapper.h b/third_party/rust/authenticator/src/transport/linux/hidwrapper.h
new file mode 100644
index 0000000000..ce77e0f1ca
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/hidwrapper.h
@@ -0,0 +1,12 @@
+#include<sys/ioctl.h>
+#include<linux/hidraw.h>
+
+/* we define these constants to work around the fact that bindgen
+ can't deal with the _IOR macro function. We let cpp deal with it
+ for us. */
+
+const __u32 _HIDIOCGRDESCSIZE = HIDIOCGRDESCSIZE;
+#undef HIDIOCGRDESCSIZE
+
+const __u32 _HIDIOCGRDESC = HIDIOCGRDESC;
+#undef HIDIOCGRDESC
diff --git a/third_party/rust/authenticator/src/transport/linux/hidwrapper.rs b/third_party/rust/authenticator/src/transport/linux/hidwrapper.rs
new file mode 100644
index 0000000000..bc8582c5b1
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/hidwrapper.rs
@@ -0,0 +1,54 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+// sadly we need this file so we can avoid the suprious warnings that
+// would come with bindgen, as well as to avoid cluttering the mod.rs
+// with spurious architecture specific modules.
+
+#[cfg(target_arch = "x86")]
+include!("ioctl_x86.rs");
+
+#[cfg(target_arch = "x86_64")]
+include!("ioctl_x86_64.rs");
+
+#[cfg(all(target_arch = "mips", target_endian = "little"))]
+include!("ioctl_mipsle.rs");
+
+#[cfg(all(target_arch = "mips", target_endian = "big"))]
+include!("ioctl_mipsbe.rs");
+
+#[cfg(all(target_arch = "mips64", target_endian = "little"))]
+include!("ioctl_mips64le.rs");
+
+#[cfg(all(target_arch = "powerpc", target_endian = "little"))]
+include!("ioctl_powerpcle.rs");
+
+#[cfg(all(target_arch = "powerpc", target_endian = "big"))]
+include!("ioctl_powerpcbe.rs");
+
+#[cfg(all(target_arch = "powerpc64", target_endian = "little"))]
+include!("ioctl_powerpc64le.rs");
+
+#[cfg(all(target_arch = "powerpc64", target_endian = "big"))]
+include!("ioctl_powerpc64be.rs");
+
+#[cfg(all(target_arch = "arm", target_endian = "little"))]
+include!("ioctl_armle.rs");
+
+#[cfg(all(target_arch = "arm", target_endian = "big"))]
+include!("ioctl_armbe.rs");
+
+#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
+include!("ioctl_aarch64le.rs");
+
+#[cfg(all(target_arch = "aarch64", target_endian = "big"))]
+include!("ioctl_aarch64be.rs");
+
+#[cfg(all(target_arch = "s390x", target_endian = "big"))]
+include!("ioctl_s390xbe.rs");
+
+#[cfg(all(target_arch = "riscv64", target_endian = "little"))]
+include!("ioctl_riscv64.rs");
+
+#[cfg(all(target_arch = "loongarch64", target_endian = "little"))]
+include!("ioctl_loongarch64.rs");
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_aarch64le.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_aarch64le.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_aarch64le.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
+pub const _HIDIOCGRDESC: __u32 = 2416199682;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_armle.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_armle.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_armle.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
+pub const _HIDIOCGRDESC: __u32 = 2416199682;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_loongarch64.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_loongarch64.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_loongarch64.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
+pub const _HIDIOCGRDESC: __u32 = 2416199682;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_mips64le.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_mips64le.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_mips64le.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401;
+pub const _HIDIOCGRDESC: __u32 = 1342457858;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_mipsbe.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_mipsbe.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_mipsbe.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401;
+pub const _HIDIOCGRDESC: __u32 = 1342457858;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_mipsle.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_mipsle.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_mipsle.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401;
+pub const _HIDIOCGRDESC: __u32 = 1342457858;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64be.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64be.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64be.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401;
+pub const _HIDIOCGRDESC: __u32 = 1342457858;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64le.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64le.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpc64le.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401;
+pub const _HIDIOCGRDESC: __u32 = 1342457858;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_powerpcbe.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpcbe.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_powerpcbe.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 1074022401;
+pub const _HIDIOCGRDESC: __u32 = 1342457858;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_riscv64.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_riscv64.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_riscv64.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
+pub const _HIDIOCGRDESC: __u32 = 2416199682;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_s390xbe.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_s390xbe.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_s390xbe.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
+pub const _HIDIOCGRDESC: __u32 = 2416199682;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_x86.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_x86.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_x86.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
+pub const _HIDIOCGRDESC: __u32 = 2416199682;
diff --git a/third_party/rust/authenticator/src/transport/linux/ioctl_x86_64.rs b/third_party/rust/authenticator/src/transport/linux/ioctl_x86_64.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/ioctl_x86_64.rs
@@ -0,0 +1,5 @@
+/* automatically generated by rust-bindgen */
+
+pub type __u32 = ::std::os::raw::c_uint;
+pub const _HIDIOCGRDESCSIZE: __u32 = 2147764225;
+pub const _HIDIOCGRDESC: __u32 = 2416199682;
diff --git a/third_party/rust/authenticator/src/transport/linux/mod.rs b/third_party/rust/authenticator/src/transport/linux/mod.rs
new file mode 100644
index 0000000000..c4d490ecee
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/mod.rs
@@ -0,0 +1,12 @@
+/* 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/. */
+
+#![allow(clippy::unreadable_literal)]
+
+pub mod device;
+pub mod transaction;
+
+mod hidraw;
+mod hidwrapper;
+mod monitor;
diff --git a/third_party/rust/authenticator/src/transport/linux/monitor.rs b/third_party/rust/authenticator/src/transport/linux/monitor.rs
new file mode 100644
index 0000000000..ee88622de9
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/monitor.rs
@@ -0,0 +1,194 @@
+/* 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::transport::device_selector::DeviceSelectorEvent;
+use libc::{c_int, c_short, c_ulong};
+use libudev::EventType;
+use runloop::RunLoop;
+use std::collections::HashMap;
+use std::error::Error;
+use std::io;
+use std::os::unix::io::AsRawFd;
+use std::path::PathBuf;
+use std::sync::{mpsc::Sender, Arc};
+
+const UDEV_SUBSYSTEM: &str = "hidraw";
+const POLLIN: c_short = 0x0001;
+const POLL_TIMEOUT: c_int = 100;
+
+fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> {
+ let nfds = fds.len() as c_ulong;
+
+ let rv = unsafe { ::libc::poll((fds[..]).as_mut_ptr(), nfds, POLL_TIMEOUT) };
+
+ if rv < 0 {
+ Err(io::Error::from_raw_os_error(rv))
+ } else {
+ Ok(())
+ }
+}
+
+pub struct Monitor<F>
+where
+ F: Fn(PathBuf, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ + Sync,
+{
+ runloops: HashMap<PathBuf, RunLoop>,
+ new_device_cb: Arc<F>,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(PathBuf, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ + Send
+ + Sync
+ + 'static,
+{
+ pub fn new(
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+ ) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ selector_sender,
+ status_sender,
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
+ let ctx = libudev::Context::new()?;
+
+ let mut enumerator = libudev::Enumerator::new(&ctx)?;
+ enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
+
+ // Iterate all existing devices.
+ let paths: Vec<PathBuf> = enumerator
+ .scan_devices()?
+ .filter_map(|dev| dev.devnode().map(|p| p.to_owned()))
+ .collect();
+
+ // Add them all in one go to avoid race conditions in DeviceSelector
+ // (8 devices should be added, but the first returns already before all
+ // others are known to DeviceSelector)
+ self.selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(paths.clone()))?;
+ for path in paths {
+ self.add_device(path);
+ }
+
+ let mut monitor = libudev::Monitor::new(&ctx)?;
+ monitor.match_subsystem(UDEV_SUBSYSTEM)?;
+
+ // Start listening for new devices.
+ let mut socket = monitor.listen()?;
+ let mut fds = vec![::libc::pollfd {
+ fd: socket.as_raw_fd(),
+ events: POLLIN,
+ revents: 0,
+ }];
+
+ while alive() {
+ // Wait for new events, break on failure.
+ poll(&mut fds)?;
+
+ if let Some(event) = socket.receive_event() {
+ self.process_event(&event);
+ }
+ }
+
+ // Remove all tracked devices.
+ self.remove_all_devices();
+
+ Ok(())
+ }
+
+ fn process_event(&mut self, event: &libudev::Event) {
+ let path = event.device().devnode().map(|dn| dn.to_owned());
+
+ match (event.event_type(), path) {
+ (EventType::Add, Some(path)) => {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(vec![path.clone()]));
+ self.add_device(path);
+ }
+ (EventType::Remove, Some(path)) => {
+ self.remove_device(&path);
+ }
+ _ => { /* ignore other types and failures */ }
+ }
+ }
+
+ fn add_device(&mut self, path: PathBuf) {
+ let f = self.new_device_cb.clone();
+ let key = path.clone();
+ let selector_sender = self.selector_sender.clone();
+ let status_sender = self.status_sender.clone();
+ debug!("Adding device {}", path.to_string_lossy());
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(path, selector_sender, status_sender, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: &PathBuf) {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
+
+ debug!("Removing device {}", path.to_string_lossy());
+ 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);
+ }
+ }
+}
+
+pub fn get_property_linux(path: &PathBuf, prop_name: &str) -> io::Result<String> {
+ let ctx = libudev::Context::new()?;
+
+ let mut enumerator = libudev::Enumerator::new(&ctx)?;
+ enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
+
+ // Iterate all existing devices, since we don't have a syspath
+ // and libudev-rs doesn't implement opening by devnode.
+ for dev in enumerator.scan_devices()? {
+ if dev.devnode().is_some() && dev.devnode().unwrap() == path {
+ debug!(
+ "get_property_linux Querying property {} from {}",
+ prop_name,
+ dev.syspath().display()
+ );
+
+ let value = dev
+ .attribute_value(prop_name)
+ .ok_or(io::ErrorKind::Other)?
+ .to_string_lossy();
+
+ debug!("get_property_linux Fetched Result, {}={}", prop_name, value);
+ return Ok(value.to_string());
+ }
+ }
+
+ Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Unable to find device",
+ ))
+}
diff --git a/third_party/rust/authenticator/src/transport/linux/transaction.rs b/third_party/rust/authenticator/src/transport/linux/transaction.rs
new file mode 100644
index 0000000000..6b15f6751a
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/linux/transaction.rs
@@ -0,0 +1,69 @@
+/* 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::statecallback::StateCallback;
+use crate::transport::device_selector::{
+ DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
+};
+use crate::transport::platform::monitor::Monitor;
+use runloop::RunLoop;
+use std::sync::mpsc::Sender;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: RunLoop,
+ device_selector: DeviceSelector,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ status: Sender<crate::StatusUpdate>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ let device_selector = DeviceSelector::run();
+ let selector_sender = device_selector.clone_sender();
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
+
+ // 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,
+ device_selector,
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ info!("Transaction was cancelled.");
+ self.device_selector.stop();
+ self.thread.cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/macos/device.rs b/third_party/rust/authenticator/src/transport/macos/device.rs
new file mode 100644
index 0000000000..9acce3aa29
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/macos/device.rs
@@ -0,0 +1,226 @@
+/* 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/. */
+
+extern crate log;
+
+use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
+use crate::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::transport::hid::HIDDevice;
+use crate::transport::platform::iokit::*;
+use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
+use crate::u2ftypes::U2FDeviceInfo;
+use core_foundation::base::*;
+use core_foundation::string::*;
+use std::convert::TryInto;
+use std::fmt;
+use std::hash::{Hash, Hasher};
+use std::io;
+use std::io::{Read, Write};
+use std::sync::mpsc::{Receiver, RecvTimeoutError};
+use std::time::Duration;
+
+const READ_TIMEOUT: u64 = 15;
+
+pub struct Device {
+ device_ref: IOHIDDeviceRef,
+ cid: [u8; 4],
+ report_rx: Option<Receiver<Vec<u8>>>,
+ dev_info: Option<U2FDeviceInfo>,
+ secret: Option<SharedSecret>,
+ authenticator_info: Option<AuthenticatorInfo>,
+ protocol: FidoProtocol,
+}
+
+impl Device {
+ unsafe fn get_property_macos(&self, prop_name: &str) -> io::Result<String> {
+ let prop_ref = IOHIDDeviceGetProperty(
+ self.device_ref,
+ CFString::new(prop_name).as_concrete_TypeRef(),
+ );
+ if prop_ref.is_null() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("IOHIDDeviceGetProperty received nullptr for property {prop_name}"),
+ ));
+ }
+
+ if CFGetTypeID(prop_ref) != CFStringGetTypeID() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!("IOHIDDeviceGetProperty returned non-string type for property {prop_name}"),
+ ));
+ }
+
+ Ok(CFString::from_void(prop_ref).to_string())
+ }
+}
+
+impl fmt::Debug for Device {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Device").field("cid", &self.cid).finish()
+ }
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other_device: &Device) -> bool {
+ self.device_ref == other_device.device_ref
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // The path should be the only identifying member for a device
+ // If the path is the same, its the same device
+ self.device_ref.hash(state);
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize> {
+ if let Some(rx) = &self.report_rx {
+ let timeout = Duration::from_secs(READ_TIMEOUT);
+ let data = match rx.recv_timeout(timeout) {
+ Ok(v) => v,
+ Err(e) if e == RecvTimeoutError::Timeout => {
+ return Err(io::Error::new(io::ErrorKind::TimedOut, e));
+ }
+ Err(e) => {
+ return Err(io::Error::new(io::ErrorKind::UnexpectedEof, e));
+ }
+ };
+ bytes.write(&data)
+ } else {
+ Err(io::Error::from(io::ErrorKind::Unsupported))
+ }
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
+ assert_eq!(bytes.len(), self.out_rpt_size() + 1);
+
+ let report_id = i64::from(bytes[0]);
+ // Skip report number when not using numbered reports.
+ let start = if report_id == 0x0 { 1 } else { 0 };
+ let data = &bytes[start..];
+
+ let result = unsafe {
+ IOHIDDeviceSetReport(
+ self.device_ref,
+ kIOHIDReportTypeOutput,
+ report_id.try_into().unwrap(),
+ data.as_ptr(),
+ data.len() as CFIndex,
+ )
+ };
+ if result != 0 {
+ warn!("set_report sending failure = {0:X}", result);
+ return Err(io::Error::from_raw_os_error(result));
+ }
+ trace!("set_report sending success = {0:X}", result);
+
+ Ok(bytes.len())
+ }
+
+ // USB HID writes don't buffer, so this will be a nop.
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl HIDDevice for Device {
+ type BuildParameters = (IOHIDDeviceRef, Receiver<Vec<u8>>);
+ type Id = IOHIDDeviceRef;
+
+ fn new(dev_ids: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
+ let (device_ref, report_rx) = dev_ids;
+ Ok(Self {
+ device_ref,
+ cid: CID_BROADCAST,
+ report_rx: Some(report_rx),
+ dev_info: None,
+ secret: None,
+ authenticator_info: None,
+ protocol: FidoProtocol::CTAP2,
+ })
+ }
+
+ fn id(&self) -> Self::Id {
+ self.device_ref
+ }
+
+ fn get_cid(&self) -> &[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> {
+ unsafe { self.get_property_macos(prop_name) }
+ }
+
+ 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);
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ HIDDevice::pre_init(self)
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ HIDDevice::get_device_info(self)
+ .cap_flags
+ .contains(Capability::CBOR)
+ }
+
+ fn initialized(&self) -> bool {
+ self.cid != CID_BROADCAST
+ }
+ fn is_u2f(&mut self) -> bool {
+ true
+ }
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ self.secret.as_ref()
+ }
+
+ fn set_shared_secret(&mut self, secret: SharedSecret) {
+ self.secret = Some(secret);
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1;
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/macos/iokit.rs b/third_party/rust/authenticator/src/transport/macos/iokit.rs
new file mode 100644
index 0000000000..656cdb045d
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/macos/iokit.rs
@@ -0,0 +1,292 @@
+/* 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/. */
+
+#![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
+
+extern crate libc;
+
+use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
+use core_foundation::array::*;
+use core_foundation::base::*;
+use core_foundation::dictionary::*;
+use core_foundation::number::*;
+use core_foundation::runloop::*;
+use core_foundation::string::*;
+use std::ops::Deref;
+use std::os::raw::c_void;
+
+type IOOptionBits = u32;
+
+pub type IOReturn = libc::c_int;
+
+pub type IOHIDManagerRef = *mut __IOHIDManager;
+pub type IOHIDManagerOptions = IOOptionBits;
+
+pub type IOHIDDeviceCallback = extern "C" fn(
+ context: *mut c_void,
+ result: IOReturn,
+ sender: *mut c_void,
+ device: IOHIDDeviceRef,
+);
+
+pub type IOHIDReportType = IOOptionBits;
+pub type IOHIDReportCallback = extern "C" fn(
+ context: *mut c_void,
+ result: IOReturn,
+ sender: IOHIDDeviceRef,
+ report_type: IOHIDReportType,
+ report_id: u32,
+ report: *mut u8,
+ report_len: CFIndex,
+);
+
+pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0;
+
+pub const kIOHIDReportTypeOutput: IOHIDReportType = 1;
+
+#[repr(C)]
+pub struct __IOHIDManager {
+ __private: c_void,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
+pub struct IOHIDDeviceRef(*const c_void);
+
+unsafe impl Send for IOHIDDeviceRef {}
+unsafe impl Sync for IOHIDDeviceRef {}
+
+pub struct SendableRunLoop(CFRunLoopRef);
+
+impl SendableRunLoop {
+ pub fn new(runloop: CFRunLoopRef) -> Self {
+ // Keep the CFRunLoop alive for as long as we are.
+ unsafe { CFRetain(runloop as *mut c_void) };
+
+ SendableRunLoop(runloop)
+ }
+}
+
+unsafe impl Send for SendableRunLoop {}
+
+impl Deref for SendableRunLoop {
+ type Target = CFRunLoopRef;
+
+ fn deref(&self) -> &CFRunLoopRef {
+ &self.0
+ }
+}
+
+impl Drop for SendableRunLoop {
+ fn drop(&mut self) {
+ unsafe { CFRelease(self.0 as *mut c_void) };
+ }
+}
+
+#[repr(C)]
+pub struct CFRunLoopObserverContext {
+ pub version: CFIndex,
+ pub info: *mut c_void,
+ pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>,
+ pub release: Option<extern "C" fn(info: *const c_void)>,
+ pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>,
+}
+
+impl CFRunLoopObserverContext {
+ pub fn new(context: *mut c_void) -> Self {
+ Self {
+ version: 0 as CFIndex,
+ info: context,
+ retain: None,
+ release: None,
+ copyDescription: None,
+ }
+ }
+}
+
+pub struct CFRunLoopEntryObserver {
+ observer: CFRunLoopObserverRef,
+ // Keep alive until the observer goes away.
+ context_ptr: *mut CFRunLoopObserverContext,
+}
+
+impl CFRunLoopEntryObserver {
+ pub fn new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self {
+ let context = CFRunLoopObserverContext::new(context);
+ let context_ptr = Box::into_raw(Box::new(context));
+
+ let observer = unsafe {
+ CFRunLoopObserverCreate(
+ kCFAllocatorDefault,
+ kCFRunLoopEntry,
+ false as Boolean,
+ 0,
+ callback,
+ context_ptr,
+ )
+ };
+
+ Self {
+ observer,
+ context_ptr,
+ }
+ }
+
+ pub fn add_to_current_runloop(&self) {
+ unsafe {
+ CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode)
+ };
+ }
+}
+
+impl Drop for CFRunLoopEntryObserver {
+ fn drop(&mut self) {
+ unsafe {
+ CFRelease(self.observer as *mut c_void);
+
+ // Drop the CFRunLoopObserverContext.
+ let _ = Box::from_raw(self.context_ptr);
+ };
+ }
+}
+
+pub struct IOHIDDeviceMatcher {
+ pub dict: CFDictionary<CFString, CFNumber>,
+}
+
+impl IOHIDDeviceMatcher {
+ pub fn new() -> Self {
+ let dict = CFDictionary::<CFString, CFNumber>::from_CFType_pairs(&[
+ (
+ CFString::from_static_string("DeviceUsage"),
+ CFNumber::from(i32::from(FIDO_USAGE_U2FHID)),
+ ),
+ (
+ CFString::from_static_string("DeviceUsagePage"),
+ CFNumber::from(i32::from(FIDO_USAGE_PAGE)),
+ ),
+ ]);
+ Self { dict }
+ }
+}
+
+#[link(name = "IOKit", kind = "framework")]
+extern "C" {
+ // CFRunLoop
+ pub fn CFRunLoopObserverCreate(
+ allocator: CFAllocatorRef,
+ activities: CFOptionFlags,
+ repeats: Boolean,
+ order: CFIndex,
+ callout: CFRunLoopObserverCallBack,
+ context: *mut CFRunLoopObserverContext,
+ ) -> CFRunLoopObserverRef;
+
+ // IOHIDManager
+ pub fn IOHIDManagerCreate(
+ allocator: CFAllocatorRef,
+ options: IOHIDManagerOptions,
+ ) -> IOHIDManagerRef;
+ pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef);
+ pub fn IOHIDManagerRegisterDeviceMatchingCallback(
+ manager: IOHIDManagerRef,
+ callback: IOHIDDeviceCallback,
+ context: *mut c_void,
+ );
+ pub fn IOHIDManagerRegisterDeviceRemovalCallback(
+ manager: IOHIDManagerRef,
+ callback: IOHIDDeviceCallback,
+ context: *mut c_void,
+ );
+ pub fn IOHIDManagerRegisterInputReportCallback(
+ manager: IOHIDManagerRef,
+ callback: IOHIDReportCallback,
+ context: *mut c_void,
+ );
+ pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
+ pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
+ pub fn IOHIDManagerScheduleWithRunLoop(
+ manager: IOHIDManagerRef,
+ runLoop: CFRunLoopRef,
+ runLoopMode: CFStringRef,
+ );
+
+ // IOHIDDevice
+ pub fn IOHIDDeviceSetReport(
+ device: IOHIDDeviceRef,
+ reportType: IOHIDReportType,
+ reportID: CFIndex,
+ report: *const u8,
+ reportLength: CFIndex,
+ ) -> IOReturn;
+ pub fn IOHIDDeviceGetProperty(device: IOHIDDeviceRef, key: CFStringRef) -> CFTypeRef;
+}
+
+////////////////////////////////////////////////////////////////////////
+// Tests
+////////////////////////////////////////////////////////////////////////
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::os::raw::c_void;
+ use std::ptr;
+ use std::sync::mpsc::{channel, Sender};
+ use std::thread;
+
+ extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
+ let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) };
+
+ // Send the current runloop to the receiver to unblock it.
+ let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() }));
+ }
+
+ #[test]
+ fn test_sendable_runloop() {
+ let (tx, rx) = channel();
+
+ let thread = thread::spawn(move || {
+ // Send the runloop to the owning thread.
+ let context = &tx as *const _ as *mut c_void;
+ let obs = CFRunLoopEntryObserver::new(observe, context);
+ obs.add_to_current_runloop();
+
+ unsafe {
+ // We need some source for the runloop to run.
+ let manager = IOHIDManagerCreate(kCFAllocatorDefault, 0);
+ assert!(!manager.is_null());
+
+ IOHIDManagerScheduleWithRunLoop(
+ manager,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode,
+ );
+ IOHIDManagerSetDeviceMatching(manager, ptr::null_mut());
+
+ let rv = IOHIDManagerOpen(manager, 0);
+ assert_eq!(rv, 0);
+
+ // This will run until `CFRunLoopStop()` is called.
+ CFRunLoopRun();
+
+ let rv = IOHIDManagerClose(manager, 0);
+ assert_eq!(rv, 0);
+
+ CFRelease(manager as *mut c_void);
+ }
+ });
+
+ // Block until we enter the CFRunLoop.
+ let runloop: SendableRunLoop = rx.recv().expect("failed to receive runloop");
+
+ // Stop the runloop.
+ unsafe { CFRunLoopStop(*runloop) };
+
+ // Stop the thread.
+ thread.join().expect("failed to join the thread");
+
+ // Try to stop the runloop again (without crashing).
+ unsafe { CFRunLoopStop(*runloop) };
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/macos/mod.rs b/third_party/rust/authenticator/src/transport/macos/mod.rs
new file mode 100644
index 0000000000..44e85094d0
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/macos/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 iokit;
+mod monitor;
diff --git a/third_party/rust/authenticator/src/transport/macos/monitor.rs b/third_party/rust/authenticator/src/transport/macos/monitor.rs
new file mode 100644
index 0000000000..32200ee7a4
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/macos/monitor.rs
@@ -0,0 +1,212 @@
+/* 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/. */
+
+extern crate libc;
+extern crate log;
+
+use crate::transport::device_selector::DeviceSelectorEvent;
+use crate::transport::platform::iokit::*;
+use crate::util::io_err;
+use core_foundation::base::*;
+use core_foundation::runloop::*;
+use runloop::RunLoop;
+use std::collections::HashMap;
+use std::os::raw::c_void;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::{io, slice};
+
+struct DeviceData {
+ tx: Sender<Vec<u8>>,
+ runloop: RunLoop,
+}
+
+pub struct Monitor<F>
+where
+ F: Fn(
+ (IOHIDDeviceRef, Receiver<Vec<u8>>),
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ manager: IOHIDManagerRef,
+ // Keep alive until the monitor goes away.
+ _matcher: IOHIDDeviceMatcher,
+ map: HashMap<IOHIDDeviceRef, DeviceData>,
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(
+ (IOHIDDeviceRef, Receiver<Vec<u8>>),
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ pub fn new(
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+ ) -> Self {
+ let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
+
+ // Match FIDO devices only.
+ let _matcher = IOHIDDeviceMatcher::new();
+ unsafe { IOHIDManagerSetDeviceMatching(manager, _matcher.dict.as_concrete_TypeRef()) };
+
+ Self {
+ manager,
+ _matcher,
+ new_device_cb,
+ map: HashMap::new(),
+ selector_sender,
+ status_sender,
+ }
+ }
+
+ pub fn start(&mut self) -> io::Result<()> {
+ let context = self as *mut Self as *mut c_void;
+
+ unsafe {
+ IOHIDManagerRegisterDeviceMatchingCallback(
+ self.manager,
+ Monitor::<F>::on_device_matching,
+ context,
+ );
+ IOHIDManagerRegisterDeviceRemovalCallback(
+ self.manager,
+ Monitor::<F>::on_device_removal,
+ context,
+ );
+ IOHIDManagerRegisterInputReportCallback(
+ self.manager,
+ Monitor::<F>::on_input_report,
+ context,
+ );
+
+ IOHIDManagerScheduleWithRunLoop(
+ self.manager,
+ CFRunLoopGetCurrent(),
+ kCFRunLoopDefaultMode,
+ );
+
+ let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone);
+ if rv == 0 {
+ Ok(())
+ } else {
+ Err(io_err(&format!("Couldn't open HID Manager, rv={rv}")))
+ }
+ }
+ }
+
+ pub fn stop(&mut self) {
+ // Remove all devices.
+ while !self.map.is_empty() {
+ let device_ref = *self.map.keys().next().unwrap();
+ self.remove_device(device_ref);
+ }
+
+ // Close the manager and its devices.
+ unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
+ }
+
+ fn remove_device(&mut self, device_ref: IOHIDDeviceRef) {
+ if let Some(DeviceData { tx, runloop }) = self.map.remove(&device_ref) {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DeviceRemoved(device_ref));
+ // Dropping `tx` will make Device::read() fail eventually.
+ drop(tx);
+
+ // Wait until the runloop stopped.
+ runloop.cancel();
+ }
+ }
+
+ extern "C" fn on_input_report(
+ context: *mut c_void,
+ _: IOReturn,
+ device_ref: IOHIDDeviceRef,
+ _: IOHIDReportType,
+ _: u32,
+ report: *mut u8,
+ report_len: CFIndex,
+ ) {
+ let this = unsafe { &mut *(context as *mut Self) };
+ let mut send_failed = false;
+
+ // Ignore the report if we can't find a device for it.
+ if let Some(DeviceData { tx, .. }) = this.map.get(&device_ref) {
+ let data = unsafe { slice::from_raw_parts(report, report_len as usize).to_vec() };
+ send_failed = tx.send(data).is_err();
+ }
+
+ // Remove the device if sending fails.
+ if send_failed {
+ this.remove_device(device_ref);
+ }
+ }
+
+ extern "C" fn on_device_matching(
+ context: *mut c_void,
+ _: IOReturn,
+ _: *mut c_void,
+ device_ref: IOHIDDeviceRef,
+ ) {
+ let this = unsafe { &mut *(context as *mut Self) };
+ let _ = this
+ .selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(vec![device_ref]));
+ let selector_sender = this.selector_sender.clone();
+ let status_sender = this.status_sender.clone();
+ let (tx, rx) = channel();
+ let f = &this.new_device_cb;
+
+ // Create a new per-device runloop.
+ let runloop = RunLoop::new(move |alive| {
+ // Ensure that the runloop is still alive.
+ if alive() {
+ f((device_ref, rx), selector_sender, status_sender, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ this.map.insert(device_ref, DeviceData { tx, runloop });
+ }
+ }
+
+ extern "C" fn on_device_removal(
+ context: *mut c_void,
+ _: IOReturn,
+ _: *mut c_void,
+ device_ref: IOHIDDeviceRef,
+ ) {
+ let this = unsafe { &mut *(context as *mut Self) };
+ this.remove_device(device_ref);
+ }
+}
+
+impl<F> Drop for Monitor<F>
+where
+ F: Fn(
+ (IOHIDDeviceRef, Receiver<Vec<u8>>),
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ fn drop(&mut self) {
+ unsafe { CFRelease(self.manager as *mut c_void) };
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/macos/transaction.rs b/third_party/rust/authenticator/src/transport/macos/transaction.rs
new file mode 100644
index 0000000000..d9709e7364
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/macos/transaction.rs
@@ -0,0 +1,107 @@
+/* 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/. */
+
+extern crate libc;
+
+use crate::errors;
+use crate::statecallback::StateCallback;
+use crate::transport::device_selector::{
+ DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
+};
+use crate::transport::platform::iokit::{CFRunLoopEntryObserver, SendableRunLoop};
+use crate::transport::platform::monitor::Monitor;
+use core_foundation::runloop::*;
+use std::os::raw::c_void;
+use std::sync::mpsc::{channel, Sender};
+use std::thread;
+
+// A transaction will run the given closure in a new thread, thereby using a
+// separate per-thread state machine for each HID. It will either complete or
+// fail through user action, timeout, or be cancelled when overridden by a new
+// transaction.
+pub struct Transaction {
+ runloop: Option<SendableRunLoop>,
+ thread: Option<thread::JoinHandle<()>>,
+ device_selector: DeviceSelector,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ status: Sender<crate::StatusUpdate>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ let (tx, rx) = channel();
+ let timeout = (timeout as f64) / 1000.0;
+ let device_selector = DeviceSelector::run();
+ let selector_sender = device_selector.clone_sender();
+ let builder = thread::Builder::new();
+ let thread = builder
+ .spawn(move || {
+ // Add a runloop observer that will be notified when we enter the
+ // runloop and tx.send() the current runloop to the owning thread.
+ // We need to ensure the runloop was entered before unblocking
+ // Transaction::new(), so we can always properly cancel.
+ let context = &tx as *const _ as *mut c_void;
+ let obs = CFRunLoopEntryObserver::new(Transaction::observe, context);
+ obs.add_to_current_runloop();
+
+ // Create a new HID device monitor and start polling.
+ let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
+ try_or!(monitor.start(), |_| callback
+ .call(Err(errors::AuthenticatorError::Platform)));
+
+ // This will block until completion, abortion, or timeout.
+ unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, 0) };
+
+ // Close the monitor and its devices.
+ monitor.stop();
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotAllowed,
+ )));
+ })
+ .map_err(|_| errors::AuthenticatorError::Platform)?;
+
+ // Block until we enter the CFRunLoop.
+ let runloop = rx
+ .recv()
+ .map_err(|_| errors::AuthenticatorError::Platform)?;
+
+ Ok(Self {
+ runloop: Some(runloop),
+ thread: Some(thread),
+ device_selector,
+ })
+ }
+
+ extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
+ let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) };
+
+ // Send the current runloop to the receiver to unblock it.
+ let _ = tx.send(SendableRunLoop::new(unsafe { CFRunLoopGetCurrent() }));
+ }
+
+ pub fn cancel(&mut self) {
+ // This must never be None. This won't block.
+ unsafe { CFRunLoopStop(*self.runloop.take().unwrap()) };
+
+ self.device_selector.stop();
+ // This must never be None. Ignore return value.
+ let _ = self.thread.take().unwrap().join();
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/mock/device.rs b/third_party/rust/authenticator/src/transport/mock/device.rs
new file mode 100644
index 0000000000..ac4e156d8a
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/mock/device.rs
@@ -0,0 +1,328 @@
+/* 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::consts::{Capability, HIDCmd, CID_BROADCAST};
+use crate::crypto::SharedSecret;
+use crate::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::ctap2::commands::{CtapResponse, RequestCtap1, RequestCtap2};
+use crate::transport::device_selector::DeviceCommand;
+use crate::transport::TestDevice;
+use crate::transport::{hid::HIDDevice, FidoDevice, FidoProtocol, HIDError};
+use crate::u2ftypes::{U2FDeviceInfo, U2FHIDInitResp};
+use std::any::Any;
+use std::collections::VecDeque;
+use std::hash::{Hash, Hasher};
+use std::io::{self, Read, Write};
+use std::sync::mpsc::{channel, Receiver, Sender};
+
+pub(crate) const IN_HID_RPT_SIZE: usize = 64;
+const OUT_HID_RPT_SIZE: usize = 64;
+
+#[derive(Debug)]
+pub struct Device {
+ pub id: String,
+ pub cid: [u8; 4],
+ pub reads: Vec<[u8; IN_HID_RPT_SIZE]>,
+ pub writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>,
+ pub dev_info: Option<U2FDeviceInfo>,
+ pub authenticator_info: Option<AuthenticatorInfo>,
+ pub sender: Option<Sender<DeviceCommand>>,
+ pub receiver: Option<Receiver<DeviceCommand>>,
+ pub protocol: FidoProtocol,
+ skip_serialization: bool,
+ pub upcoming_requests: VecDeque<Vec<u8>>,
+ pub upcoming_responses: VecDeque<Result<Box<dyn Any>, HIDError>>,
+}
+
+impl Device {
+ pub fn add_write(&mut self, packet: &[u8], fill_value: u8) {
+ // Add one to deal with record index check
+ let mut write = [fill_value; OUT_HID_RPT_SIZE + 1];
+ // Make sure we start with a 0, for HID record index
+ write[0] = 0;
+ // Clone packet data in at 1, since front is padded with HID record index
+ write[1..=packet.len()].clone_from_slice(packet);
+ self.writes.push(write);
+ }
+
+ pub fn add_read(&mut self, packet: &[u8], fill_value: u8) {
+ let mut read = [fill_value; IN_HID_RPT_SIZE];
+ read[..packet.len()].clone_from_slice(packet);
+ self.reads.push(read);
+ }
+
+ pub fn add_upcoming_ctap2_request(&mut self, msg: &impl RequestCtap2) {
+ self.upcoming_requests
+ .push_back(msg.wire_format().expect("Failed to serialize CTAP request"));
+ }
+
+ pub fn add_upcoming_ctap1_request(&mut self, msg: &impl RequestCtap1) {
+ let (upcoming, _) = msg
+ .ctap1_format()
+ .expect("Failed to serialize CTAP request");
+ self.upcoming_requests.push_back(upcoming);
+ }
+
+ pub fn add_upcoming_ctap_response(&mut self, msg: impl CtapResponse) {
+ self.upcoming_responses.push_back(Ok(Box::new(msg)));
+ }
+
+ pub fn add_upcoming_ctap_error(&mut self, msg: HIDError) {
+ self.upcoming_responses.push_back(Err(msg));
+ }
+
+ pub fn create_channel(&mut self) {
+ let (tx, rx) = channel();
+ self.sender = Some(tx);
+ self.receiver = Some(rx);
+ }
+
+ pub fn new_skipping_serialization(id: &str) -> Result<Self, (HIDError, String)> {
+ Ok(Device {
+ id: id.to_string(),
+ cid: CID_BROADCAST,
+ reads: vec![],
+ writes: vec![],
+ dev_info: None,
+ authenticator_info: None,
+ sender: None,
+ receiver: None,
+ protocol: FidoProtocol::CTAP2,
+ skip_serialization: true,
+ upcoming_requests: VecDeque::new(),
+ upcoming_responses: VecDeque::new(),
+ })
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
+ // Pop a vector from the expected writes, check for quality
+ // against bytes array.
+ assert!(
+ !self.writes.is_empty(),
+ "Ran out of expected write values! Wanted to write {:?}",
+ bytes
+ );
+ let check = self.writes.remove(0);
+ assert_eq!(check.len(), bytes.len());
+ assert_eq!(&check, bytes);
+ Ok(bytes.len())
+ }
+
+ // nop
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
+ assert!(!self.reads.is_empty(), "Ran out of read values!");
+ let check = self.reads.remove(0);
+ assert_eq!(check.len(), bytes.len());
+ bytes.clone_from_slice(&check);
+ Ok(check.len())
+ }
+}
+
+impl Drop for Device {
+ fn drop(&mut self) {
+ if !std::thread::panicking() {
+ assert!(self.reads.is_empty());
+ assert!(self.writes.is_empty());
+ }
+ }
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ self.id == other.id
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.id.hash(state);
+ }
+}
+
+impl HIDDevice for Device {
+ type Id = String;
+ type BuildParameters = &'static str; // None used
+
+ fn id(&self) -> Self::Id {
+ self.id.clone()
+ }
+
+ fn new(id: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
+ Ok(Device {
+ id: id.to_string(),
+ cid: CID_BROADCAST,
+ reads: vec![],
+ writes: vec![],
+ dev_info: None,
+ authenticator_info: None,
+ sender: None,
+ receiver: None,
+ protocol: FidoProtocol::CTAP2,
+ skip_serialization: false,
+ upcoming_requests: VecDeque::new(),
+ upcoming_responses: VecDeque::new(),
+ })
+ }
+
+ fn get_cid(&self) -> &[u8; 4] {
+ &self.cid
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ self.cid = cid;
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ IN_HID_RPT_SIZE
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ OUT_HID_RPT_SIZE
+ }
+
+ fn get_property(&self, prop_name: &str) -> io::Result<String> {
+ Ok(format!("{prop_name} not implemented"))
+ }
+
+ fn get_device_info(&self) -> U2FDeviceInfo {
+ self.dev_info.clone().unwrap()
+ }
+
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
+ self.dev_info = Some(dev_info);
+ }
+
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ if self.initialized() {
+ return Ok(());
+ }
+
+ let nonce = [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
+
+ // Send Init to broadcast address to create a new channel
+ self.set_cid(CID_BROADCAST);
+ let (cmd, raw) = HIDDevice::sendrecv(self, HIDCmd::Init, &nonce, &|| true)?;
+ if cmd != HIDCmd::Init {
+ return Err(HIDError::DeviceError);
+ }
+
+ let rsp = U2FHIDInitResp::read(&raw, &nonce)?;
+
+ // Set the new Channel ID
+ self.set_cid(rsp.cid);
+
+ let info = U2FDeviceInfo {
+ vendor_name: "Test vendor".as_bytes().to_vec(),
+ device_name: "Test device".as_bytes().to_vec(),
+ version_interface: rsp.version_interface,
+ version_major: rsp.version_major,
+ version_minor: rsp.version_minor,
+ version_build: rsp.version_build,
+ cap_flags: rsp.cap_flags,
+ };
+ debug!("{:?}: {:?}", self.id(), info);
+ self.set_device_info(info);
+
+ Ok(())
+ }
+}
+
+impl TestDevice for Device {
+ fn skip_serialization(&self) -> bool {
+ self.skip_serialization
+ }
+
+ fn send_ctap1_unserialized<Req: RequestCtap1>(
+ &mut self,
+ msg: &Req,
+ ) -> Result<Req::Output, HIDError> {
+ let expected = self
+ .upcoming_requests
+ .pop_front()
+ .expect("No expected CTAP1 command left");
+ let (incoming, _) = msg.ctap1_format().expect("Can't serialize CTAP1 request");
+ assert_eq!(expected, incoming);
+ let response = self
+ .upcoming_responses
+ .pop_front()
+ .expect("No response given!");
+ response.map(|x| {
+ *x.downcast()
+ .expect("Failed to downcast given CTAP response")
+ })
+ }
+
+ fn send_ctap2_unserialized<Req: RequestCtap2>(
+ &mut self,
+ msg: &Req,
+ ) -> Result<Req::Output, HIDError> {
+ let expected = self
+ .upcoming_requests
+ .pop_front()
+ .expect("No expected CTAP2 command left");
+ let incoming = msg.wire_format().expect("Can't serialize CTAP2 request");
+ assert_eq!(expected, incoming);
+ let response = self
+ .upcoming_responses
+ .pop_front()
+ .expect("No response given!");
+ response.map(|x| {
+ *x.downcast()
+ .expect("Failed to downcast given CTAP response")
+ })
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ HIDDevice::pre_init(self)
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ HIDDevice::get_device_info(self)
+ .cap_flags
+ .contains(Capability::CBOR)
+ }
+
+ fn initialized(&self) -> bool {
+ self.get_cid() != &CID_BROADCAST
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ self.sender.is_some()
+ }
+
+ fn get_shared_secret(&self) -> std::option::Option<&SharedSecret> {
+ None
+ }
+
+ fn set_shared_secret(&mut self, _: SharedSecret) {
+ // Nothing
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1;
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/mock/mod.rs b/third_party/rust/authenticator/src/transport/mock/mod.rs
new file mode 100644
index 0000000000..d0e200a7ef
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/mock/mod.rs
@@ -0,0 +1,6 @@
+/* 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;
diff --git a/third_party/rust/authenticator/src/transport/mock/transaction.rs b/third_party/rust/authenticator/src/transport/mock/transaction.rs
new file mode 100644
index 0000000000..e19b1cb56f
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/mock/transaction.rs
@@ -0,0 +1,35 @@
+/* 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::statecallback::StateCallback;
+use crate::transport::device_selector::{DeviceBuildParameters, DeviceSelectorEvent};
+use std::sync::mpsc::Sender;
+
+pub struct Transaction {}
+
+impl Transaction {
+ pub fn new<F, T>(
+ _timeout: u64,
+ _callback: StateCallback<crate::Result<T>>,
+ _status: Sender<crate::StatusUpdate>,
+ _new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ Ok(Self {})
+ }
+
+ pub fn cancel(&mut self) {
+ info!("Transaction was cancelled.");
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/mod.rs b/third_party/rust/authenticator/src/transport/mod.rs
new file mode 100644
index 0000000000..318934ed36
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/mod.rs
@@ -0,0 +1,369 @@
+use crate::crypto::{PinUvAuthProtocol, PinUvAuthToken, SharedSecret};
+use crate::ctap2::commands::client_pin::{
+ ClientPIN, ClientPinResponse, GetKeyAgreement, GetPinToken,
+ GetPinUvAuthTokenUsingPinWithPermissions, GetPinUvAuthTokenUsingUvWithPermissions,
+ PinUvAuthTokenPermission,
+};
+use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult};
+use crate::ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorVersion, GetInfo};
+use crate::ctap2::commands::get_version::{GetVersion, U2FInfo};
+use crate::ctap2::commands::make_credentials::{
+ dummy_make_credentials_cmd, MakeCredentials, MakeCredentialsResult,
+};
+use crate::ctap2::commands::reset::Reset;
+use crate::ctap2::commands::selection::Selection;
+use crate::ctap2::commands::{CommandError, RequestCtap1, RequestCtap2, StatusCode};
+use crate::ctap2::preflight::CheckKeyHandle;
+use crate::transport::device_selector::BlinkResult;
+use crate::transport::errors::HIDError;
+
+use crate::Pin;
+use std::convert::TryFrom;
+use std::fmt;
+
+pub mod device_selector;
+pub mod errors;
+pub mod hid;
+
+#[cfg(all(
+ any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"),
+ not(test)
+))]
+pub mod hidproto;
+
+#[cfg(all(target_os = "linux", not(test)))]
+#[path = "linux/mod.rs"]
+pub mod platform;
+
+#[cfg(all(target_os = "freebsd", not(test)))]
+#[path = "freebsd/mod.rs"]
+pub mod platform;
+
+#[cfg(all(target_os = "netbsd", not(test)))]
+#[path = "netbsd/mod.rs"]
+pub mod platform;
+
+#[cfg(all(target_os = "openbsd", not(test)))]
+#[path = "openbsd/mod.rs"]
+pub mod platform;
+
+#[cfg(all(target_os = "macos", not(test)))]
+#[path = "macos/mod.rs"]
+pub mod platform;
+
+#[cfg(all(target_os = "windows", not(test)))]
+#[path = "windows/mod.rs"]
+pub mod platform;
+
+#[cfg(not(any(
+ target_os = "linux",
+ target_os = "freebsd",
+ target_os = "openbsd",
+ target_os = "netbsd",
+ target_os = "macos",
+ target_os = "windows",
+ test
+)))]
+#[path = "stub/mod.rs"]
+pub mod platform;
+
+#[cfg(test)]
+#[path = "mock/mod.rs"]
+pub mod platform;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum FidoProtocol {
+ CTAP1,
+ CTAP2,
+}
+
+pub trait FidoDeviceIO {
+ fn send_msg<Out, Req: RequestCtap1<Output = Out> + RequestCtap2<Output = Out>>(
+ &mut self,
+ msg: &Req,
+ ) -> Result<Out, HIDError> {
+ self.send_msg_cancellable(msg, &|| true)
+ }
+
+ fn send_cbor<Req: RequestCtap2>(&mut self, msg: &Req) -> Result<Req::Output, HIDError> {
+ self.send_cbor_cancellable(msg, &|| true)
+ }
+
+ fn send_ctap1<Req: RequestCtap1>(&mut self, msg: &Req) -> Result<Req::Output, HIDError> {
+ self.send_ctap1_cancellable(msg, &|| true)
+ }
+
+ fn send_msg_cancellable<Out, Req: RequestCtap1<Output = Out> + RequestCtap2<Output = Out>>(
+ &mut self,
+ msg: &Req,
+ keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Out, HIDError>;
+
+ fn send_cbor_cancellable<Req: RequestCtap2>(
+ &mut self,
+ msg: &Req,
+ keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Req::Output, HIDError>;
+
+ fn send_ctap1_cancellable<Req: RequestCtap1>(
+ &mut self,
+ msg: &Req,
+ keep_alive: &dyn Fn() -> bool,
+ ) -> Result<Req::Output, HIDError>;
+}
+
+pub trait TestDevice {
+ #[cfg(test)]
+ fn skip_serialization(&self) -> bool;
+ #[cfg(test)]
+ fn send_ctap1_unserialized<Req: RequestCtap1>(
+ &mut self,
+ msg: &Req,
+ ) -> Result<Req::Output, HIDError>;
+ #[cfg(test)]
+ fn send_ctap2_unserialized<Req: RequestCtap2>(
+ &mut self,
+ msg: &Req,
+ ) -> Result<Req::Output, HIDError>;
+}
+
+pub trait FidoDevice: FidoDeviceIO
+where
+ Self: Sized,
+ Self: fmt::Debug,
+{
+ fn pre_init(&mut self) -> Result<(), HIDError>;
+ fn initialized(&self) -> bool;
+
+ // Check if the device is actually a token
+ fn is_u2f(&mut self) -> bool;
+ fn should_try_ctap2(&self) -> bool;
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo>;
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo);
+ fn refresh_authenticator_info(&mut self) -> Option<&AuthenticatorInfo> {
+ let command = GetInfo::default();
+ if let Ok(info) = self.send_cbor(&command) {
+ debug!("Refreshed authenticator info: {:?}", info);
+ self.set_authenticator_info(info);
+ }
+ self.get_authenticator_info()
+ }
+
+ // `get_protocol()` indicates whether we're using CTAP1 or CTAP2.
+ // Prior to initializing the device, `get_protocol()` should return CTAP2 unless
+ // there's a reason to believe that the device does not support CTAP2 (e.g. if
+ // it's a HID device and it does not have the CBOR capability).
+ fn get_protocol(&self) -> FidoProtocol;
+
+ // We do not provide a generic `set_protocol(..)` function as this would have complicated
+ // interactions with the AuthenticatorInfo state.
+ fn downgrade_to_ctap1(&mut self);
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret>;
+ fn set_shared_secret(&mut self, secret: SharedSecret);
+
+ fn init(&mut self) -> Result<(), HIDError> {
+ self.pre_init()?;
+
+ if self.should_try_ctap2() {
+ let command = GetInfo::default();
+ if let Ok(info) = self.send_cbor(&command) {
+ debug!("{:?}", info);
+ if info.max_supported_version() == AuthenticatorVersion::U2F_V2 {
+ self.downgrade_to_ctap1();
+ }
+ self.set_authenticator_info(info);
+ return Ok(());
+ }
+ }
+
+ self.downgrade_to_ctap1();
+ // We want to return an error here if this device doesn't support CTAP1,
+ // so we send a U2F_VERSION command.
+ let command = GetVersion::default();
+ self.send_ctap1(&command)?;
+ Ok(())
+ }
+
+ fn block_and_blink(&mut self, keep_alive: &dyn Fn() -> bool) -> BlinkResult {
+ let supports_select_cmd = self.get_protocol() == FidoProtocol::CTAP2
+ && self.get_authenticator_info().map_or(false, |i| {
+ i.versions.contains(&AuthenticatorVersion::FIDO_2_1)
+ });
+ let resp = if supports_select_cmd {
+ let msg = Selection {};
+ self.send_cbor_cancellable(&msg, keep_alive)
+ } else {
+ // We need to fake a blink-request, because FIDO2.0 forgot to specify one
+ // See: https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#using-pinToken-in-authenticatorMakeCredential
+ let msg = dummy_make_credentials_cmd();
+ info!("Trying to blink: {:?}", &msg);
+ // We don't care about the Ok-value, just if it is Ok or not
+ self.send_msg_cancellable(&msg, keep_alive).map(|_| ())
+ };
+
+ match resp {
+ // Spec only says PinInvalid or PinNotSet should be returned on the fake touch-request,
+ // but Yubikeys for example return PinAuthInvalid. A successful return is also possible
+ // for CTAP1-tokens so we catch those here as well.
+ Ok(_)
+ | Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinInvalid, _)))
+ | Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _)))
+ | Err(HIDError::Command(CommandError::StatusCode(StatusCode::PinNotSet, _))) => {
+ BlinkResult::DeviceSelected
+ }
+ // We cancelled the receive, because another device was selected.
+ Err(HIDError::Command(CommandError::StatusCode(StatusCode::KeepaliveCancel, _)))
+ | Err(HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, _)))
+ | Err(HIDError::Command(CommandError::StatusCode(StatusCode::UserActionTimeout, _))) => {
+ // TODO: Repeat the request, if it is a UserActionTimeout?
+ debug!("Device {:?} got cancelled", &self);
+ BlinkResult::Cancelled
+ }
+ // Something unexpected happened, so we assume this device is not usable and
+ // interpreting this equivalent to being cancelled.
+ e => {
+ info!("Device {:?} received unexpected answer, so we assume an error occurred and we are NOT using this device (assuming the request was cancelled): {:?}", &self, e);
+ BlinkResult::Cancelled
+ }
+ }
+ }
+
+ fn establish_shared_secret(
+ &mut self,
+ alive: &dyn Fn() -> bool,
+ ) -> Result<SharedSecret, HIDError> {
+ // CTAP1 devices don't support establishing a shared secret
+ let info = match (self.get_protocol(), self.get_authenticator_info()) {
+ (FidoProtocol::CTAP2, Some(info)) => info,
+ _ => return Err(HIDError::UnsupportedCommand),
+ };
+
+ let pin_protocol = PinUvAuthProtocol::try_from(info)?;
+
+ // Not reusing the shared secret here, if it exists, since we might start again
+ // with a different PIN (e.g. if the last one was wrong)
+ let pin_command = GetKeyAgreement::new(pin_protocol.clone());
+ let resp = self.send_cbor_cancellable(&pin_command, alive)?;
+ if let Some(device_key_agreement_key) = resp.key_agreement {
+ let shared_secret = pin_protocol
+ .encapsulate(&device_key_agreement_key)
+ .map_err(CommandError::from)?;
+ self.set_shared_secret(shared_secret.clone());
+ Ok(shared_secret)
+ } else {
+ Err(HIDError::Command(CommandError::MissingRequiredField(
+ "key_agreement",
+ )))
+ }
+ }
+
+ /// CTAP 2.0-only version:
+ /// "Getting pinUvAuthToken using getPinToken (superseded)"
+ fn get_pin_token(
+ &mut self,
+ pin: &Option<Pin>,
+ alive: &dyn Fn() -> bool,
+ ) -> Result<PinUvAuthToken, HIDError> {
+ // Asking the user for PIN before establishing the shared secret
+ let pin = pin
+ .as_ref()
+ .ok_or(CommandError::StatusCode(StatusCode::PinRequired, None))?;
+
+ // Not reusing the shared secret here, if it exists, since we might start again
+ // with a different PIN (e.g. if the last one was wrong)
+ let shared_secret = self.establish_shared_secret(alive)?;
+
+ let pin_command = GetPinToken::new(&shared_secret, pin);
+ let resp = self.send_cbor_cancellable(&pin_command, alive)?;
+ if let Some(encrypted_pin_token) = resp.pin_token {
+ // CTAP 2.1 spec:
+ // If authenticatorClientPIN's getPinToken subcommand is invoked, default permissions
+ // of `mc` and `ga` (value 0x03) are granted for the returned pinUvAuthToken.
+ let default_permissions = PinUvAuthTokenPermission::default();
+ let pin_token = shared_secret
+ .decrypt_pin_token(default_permissions, encrypted_pin_token.as_ref())
+ .map_err(CommandError::from)?;
+ Ok(pin_token)
+ } else {
+ Err(HIDError::Command(CommandError::MissingRequiredField(
+ "pin_token",
+ )))
+ }
+ }
+
+ fn get_pin_uv_auth_token_using_uv_with_permissions(
+ &mut self,
+ permission: PinUvAuthTokenPermission,
+ rp_id: Option<&String>,
+ alive: &dyn Fn() -> bool,
+ ) -> Result<PinUvAuthToken, HIDError> {
+ // Explicitly not reusing the shared secret here
+ let shared_secret = self.establish_shared_secret(alive)?;
+ let pin_command = GetPinUvAuthTokenUsingUvWithPermissions::new(
+ &shared_secret,
+ permission,
+ rp_id.cloned(),
+ );
+
+ let resp = self.send_cbor_cancellable(&pin_command, alive)?;
+
+ if let Some(encrypted_pin_token) = resp.pin_token {
+ let pin_token = shared_secret
+ .decrypt_pin_token(permission, encrypted_pin_token.as_ref())
+ .map_err(CommandError::from)?;
+ Ok(pin_token)
+ } else {
+ Err(HIDError::Command(CommandError::MissingRequiredField(
+ "pin_token",
+ )))
+ }
+ }
+
+ fn get_pin_uv_auth_token_using_pin_with_permissions(
+ &mut self,
+ pin: &Option<Pin>,
+ permission: PinUvAuthTokenPermission,
+ rp_id: Option<&String>,
+ alive: &dyn Fn() -> bool,
+ ) -> Result<PinUvAuthToken, HIDError> {
+ // Asking the user for PIN before establishing the shared secret
+ let pin = pin
+ .as_ref()
+ .ok_or(CommandError::StatusCode(StatusCode::PinRequired, None))?;
+
+ // Not reusing the shared secret here, if it exists, since we might start again
+ // with a different PIN (e.g. if the last one was wrong)
+ let shared_secret = self.establish_shared_secret(alive)?;
+ let pin_command = GetPinUvAuthTokenUsingPinWithPermissions::new(
+ &shared_secret,
+ pin,
+ permission,
+ rp_id.cloned(),
+ );
+
+ let resp = self.send_cbor_cancellable(&pin_command, alive)?;
+
+ if let Some(encrypted_pin_token) = resp.pin_token {
+ let pin_token = shared_secret
+ .decrypt_pin_token(permission, encrypted_pin_token.as_ref())
+ .map_err(CommandError::from)?;
+ Ok(pin_token)
+ } else {
+ Err(HIDError::Command(CommandError::MissingRequiredField(
+ "pin_token",
+ )))
+ }
+ }
+}
+
+pub trait VirtualFidoDevice: FidoDevice {
+ fn check_key_handle(&self, req: &CheckKeyHandle) -> Result<(), HIDError>;
+ fn client_pin(&self, req: &ClientPIN) -> Result<ClientPinResponse, HIDError>;
+ fn get_assertion(&self, req: &GetAssertion) -> Result<Vec<GetAssertionResult>, HIDError>;
+ fn get_info(&self) -> Result<AuthenticatorInfo, HIDError>;
+ fn get_version(&self, req: &GetVersion) -> Result<U2FInfo, HIDError>;
+ fn make_credentials(&self, req: &MakeCredentials) -> Result<MakeCredentialsResult, HIDError>;
+ fn reset(&self, req: &Reset) -> Result<(), HIDError>;
+ fn selection(&self, req: &Selection) -> Result<(), HIDError>;
+}
diff --git a/third_party/rust/authenticator/src/transport/netbsd/device.rs b/third_party/rust/authenticator/src/transport/netbsd/device.rs
new file mode 100644
index 0000000000..4b426b3b9b
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/netbsd/device.rs
@@ -0,0 +1,248 @@
+/* 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/. */
+
+extern crate libc;
+use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
+use crate::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::transport::hid::HIDDevice;
+use crate::transport::platform::fd::Fd;
+use crate::transport::platform::monitor::WrappedOpenDevice;
+use crate::transport::platform::uhid;
+use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
+use crate::u2ftypes::U2FDeviceInfo;
+use crate::util::io_err;
+use std::ffi::OsString;
+use std::hash::{Hash, Hasher};
+use std::io::{self, Read, Write};
+use std::mem;
+
+#[derive(Debug)]
+pub struct Device {
+ path: OsString,
+ fd: Fd,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+ secret: Option<SharedSecret>,
+ authenticator_info: Option<AuthenticatorInfo>,
+ protocol: FidoProtocol,
+}
+
+impl Device {
+ fn ping(&mut self) -> io::Result<()> {
+ for i in 0..10 {
+ let mut buf = vec![0u8; 1 + MAX_HID_RPT_SIZE];
+
+ buf[0] = 0; // report number
+ buf[1] = 0xff; // CID_BROADCAST
+ buf[2] = 0xff;
+ buf[3] = 0xff;
+ buf[4] = 0xff;
+ buf[5] = 0x81; // ping
+ buf[6] = 0;
+ buf[7] = 1; // one byte
+
+ // Write ping request. Each write to the device contains
+ // exactly one report id byte[*] followed by exactly as
+ // many bytes as are in a report, and will be consumed all
+ // at once by /dev/uhidN. So we use plain write, not
+ // write_all to issue writes in a loop.
+ //
+ // [*] This is only for the internal authenticator-rs API,
+ // not for the USB HID protocol, which for a device with
+ // only one report id excludes the report id byte from the
+ // interrupt in/out pipe transfer format.
+ if self.write(&buf)? != buf.len() {
+ return Err(io_err("write ping failed"));
+ }
+
+ // Wait for response
+ let mut pfd: libc::pollfd = unsafe { mem::zeroed() };
+ pfd.fd = self.fd.fileno;
+ pfd.events = libc::POLLIN;
+ let nfds = unsafe { libc::poll(&mut pfd, 1, 100) };
+ if nfds == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ if nfds == 0 {
+ debug!("device timeout {}", i);
+ continue;
+ }
+
+ // Read response. When reports come in they are all
+ // exactly the same size, with no report id byte because
+ // there is only one report.
+ let n = self.read(&mut buf[1..])?;
+ if n != buf.len() - 1 {
+ return Err(io_err("read pong failed"));
+ }
+
+ return Ok(());
+ }
+
+ Err(io_err("no response from device"))
+ }
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ self.fd == other.fd
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.fd.hash(state);
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let bufp = buf.as_mut_ptr() as *mut libc::c_void;
+ let nread = unsafe { libc::read(self.fd.fileno, bufp, buf.len()) };
+ if nread == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(nread as usize)
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // Always skip the first byte (report number)
+ let data = &buf[1..];
+ let data_ptr = data.as_ptr() as *const libc::c_void;
+ let nwrit = unsafe { libc::write(self.fd.fileno, data_ptr, data.len()) };
+ if nwrit == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ // Pretend we wrote the report number byte
+ Ok(nwrit as usize + 1)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl HIDDevice for Device {
+ type BuildParameters = WrappedOpenDevice;
+ type Id = OsString;
+
+ fn new(fido: WrappedOpenDevice) -> Result<Self, (HIDError, Self::Id)> {
+ debug!("device found: {:?}", fido);
+ let mut res = Self {
+ path: fido.os_path,
+ fd: fido.fd,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ secret: None,
+ authenticator_info: None,
+ protocol: FidoProtocol::CTAP2,
+ };
+ if res.is_u2f() {
+ info!("new device {:?}", res.path);
+ Ok(res)
+ } else {
+ Err((HIDError::DeviceNotSupported, res.path.clone()))
+ }
+ }
+
+ fn id(&self) -> Self::Id {
+ self.path.clone()
+ }
+
+ fn get_cid(&self) -> &[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);
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ HIDDevice::pre_init(self)
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ HIDDevice::get_device_info(self)
+ .cap_flags
+ .contains(Capability::CBOR)
+ }
+
+ fn initialized(&self) -> bool {
+ // During successful init, the broadcast channel id gets repplaced by an actual one
+ self.cid != CID_BROADCAST
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ if !uhid::is_u2f_device(&self.fd) {
+ return false;
+ }
+ // This step is not strictly necessary -- NetBSD puts fido
+ // devices into raw mode automatically by default, but in
+ // principle that might change, and this serves as a test to
+ // verify that we're running on a kernel with support for raw
+ // mode at all so we don't get confused issuing writes that try
+ // to set the report descriptor rather than transfer data on
+ // the output interrupt pipe as we need.
+ match uhid::hid_set_raw(&self.fd, true) {
+ Ok(_) => (),
+ Err(_) => return false,
+ }
+ if self.ping().is_err() {
+ return false;
+ }
+ true
+ }
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ self.secret.as_ref()
+ }
+
+ fn set_shared_secret(&mut self, secret: SharedSecret) {
+ self.secret = Some(secret);
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1;
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/netbsd/fd.rs b/third_party/rust/authenticator/src/transport/netbsd/fd.rs
new file mode 100644
index 0000000000..d45410843b
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/netbsd/fd.rs
@@ -0,0 +1,62 @@
+/* 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/. */
+
+extern crate libc;
+
+use std::ffi::CString;
+use std::ffi::OsStr;
+use std::hash::{Hash, Hasher};
+use std::io;
+use std::mem;
+use std::os::raw::c_int;
+use std::os::unix::{ffi::OsStrExt, io::RawFd};
+
+#[derive(Debug)]
+pub struct Fd {
+ pub fileno: RawFd,
+}
+
+impl Fd {
+ pub fn open(path: &OsStr, flags: c_int) -> io::Result<Fd> {
+ let cpath = CString::new(path.as_bytes())?;
+ let rv = unsafe { libc::open(cpath.as_ptr(), flags) };
+ if rv == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(Fd { fileno: rv })
+ }
+}
+
+impl Drop for Fd {
+ fn drop(&mut self) {
+ unsafe { libc::close(self.fileno) };
+ }
+}
+
+impl PartialEq for Fd {
+ fn eq(&self, other: &Fd) -> bool {
+ let mut st: libc::stat = unsafe { mem::zeroed() };
+ let mut sto: libc::stat = unsafe { mem::zeroed() };
+ if unsafe { libc::fstat(self.fileno, &mut st) } == -1 {
+ return false;
+ }
+ if unsafe { libc::fstat(other.fileno, &mut sto) } == -1 {
+ return false;
+ }
+ (st.st_dev == sto.st_dev) & (st.st_ino == sto.st_ino)
+ }
+}
+
+impl Eq for Fd {}
+
+impl Hash for Fd {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ let mut st: libc::stat = unsafe { mem::zeroed() };
+ if unsafe { libc::fstat(self.fileno, &mut st) } == -1 {
+ return;
+ }
+ st.st_dev.hash(state);
+ st.st_ino.hash(state);
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/netbsd/mod.rs b/third_party/rust/authenticator/src/transport/netbsd/mod.rs
new file mode 100644
index 0000000000..a0eabb6e06
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/netbsd/mod.rs
@@ -0,0 +1,10 @@
+/* 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 fd;
+mod monitor;
+mod uhid;
diff --git a/third_party/rust/authenticator/src/transport/netbsd/monitor.rs b/third_party/rust/authenticator/src/transport/netbsd/monitor.rs
new file mode 100644
index 0000000000..c521bdea8b
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/netbsd/monitor.rs
@@ -0,0 +1,132 @@
+/* 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::transport::device_selector::DeviceSelectorEvent;
+use crate::transport::platform::fd::Fd;
+use runloop::RunLoop;
+use std::collections::HashMap;
+use std::error::Error;
+use std::ffi::OsString;
+use std::sync::{mpsc::Sender, Arc};
+use std::thread;
+use std::time::Duration;
+
+// XXX Should use drvctl, but it doesn't do pubsub properly yet so
+// DRVGETEVENT requires write access to /dev/drvctl. Instead, for now,
+// just poll every 500ms.
+const POLL_TIMEOUT: u64 = 500;
+
+#[derive(Debug)]
+pub struct WrappedOpenDevice {
+ pub fd: Fd,
+ pub os_path: OsString,
+}
+
+pub struct Monitor<F>
+where
+ F: Fn(
+ WrappedOpenDevice,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync,
+{
+ runloops: HashMap<OsString, RunLoop>,
+ new_device_cb: Arc<F>,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(
+ WrappedOpenDevice,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ pub fn new(
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+ ) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ selector_sender,
+ status_sender,
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
+ // Loop until we're stopped by the controlling thread, or fail.
+ while alive() {
+ for n in 0..100 {
+ let uhidpath = OsString::from(format!("/dev/uhid{n}"));
+ match Fd::open(&uhidpath, libc::O_RDWR | libc::O_CLOEXEC) {
+ Ok(uhid) => {
+ // The device is available if it can be opened.
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(vec![uhidpath.clone()]));
+ self.add_device(WrappedOpenDevice {
+ fd: uhid,
+ os_path: uhidpath,
+ });
+ }
+ Err(ref err) => match err.raw_os_error() {
+ Some(libc::EBUSY) => continue,
+ Some(libc::ENOENT) => break,
+ _ => self.remove_device(uhidpath),
+ },
+ }
+ }
+ thread::sleep(Duration::from_millis(POLL_TIMEOUT));
+ }
+
+ // Remove all tracked devices.
+ self.remove_all_devices();
+
+ Ok(())
+ }
+
+ fn add_device(&mut self, fido: WrappedOpenDevice) {
+ let f = self.new_device_cb.clone();
+ let selector_sender = self.selector_sender.clone();
+ let status_sender = self.status_sender.clone();
+ let key = fido.os_path.clone();
+ debug!("Adding device {}", key.to_string_lossy());
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(fido, selector_sender, status_sender, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: OsString) {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
+
+ debug!("Removing device {}", path.to_string_lossy());
+ 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/transport/netbsd/transaction.rs b/third_party/rust/authenticator/src/transport/netbsd/transaction.rs
new file mode 100644
index 0000000000..6b15f6751a
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/netbsd/transaction.rs
@@ -0,0 +1,69 @@
+/* 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::statecallback::StateCallback;
+use crate::transport::device_selector::{
+ DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
+};
+use crate::transport::platform::monitor::Monitor;
+use runloop::RunLoop;
+use std::sync::mpsc::Sender;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: RunLoop,
+ device_selector: DeviceSelector,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ status: Sender<crate::StatusUpdate>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ let device_selector = DeviceSelector::run();
+ let selector_sender = device_selector.clone_sender();
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
+
+ // 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,
+ device_selector,
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ info!("Transaction was cancelled.");
+ self.device_selector.stop();
+ self.thread.cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/netbsd/uhid.rs b/third_party/rust/authenticator/src/transport/netbsd/uhid.rs
new file mode 100644
index 0000000000..ea183db998
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/netbsd/uhid.rs
@@ -0,0 +1,77 @@
+/* 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/. */
+
+extern crate libc;
+
+use std::io;
+use std::mem;
+use std::os::raw::c_int;
+use std::os::raw::c_uchar;
+
+use crate::transport::hidproto::has_fido_usage;
+use crate::transport::hidproto::ReportDescriptor;
+use crate::transport::platform::fd::Fd;
+use crate::util::io_err;
+
+/* sys/ioccom.h */
+
+const IOCPARM_MASK: u32 = 0x1fff;
+const IOCPARM_SHIFT: u32 = 16;
+const IOCGROUP_SHIFT: u32 = 8;
+
+//const IOC_VOID: u32 = 0x20000000;
+const IOC_OUT: u32 = 0x40000000;
+const IOC_IN: u32 = 0x80000000;
+//const IOC_INOUT: u32 = IOC_IN|IOC_OUT;
+
+macro_rules! ioctl {
+ ($dir:expr, $name:ident, $group:expr, $nr:expr, $ty:ty) => {
+ unsafe fn $name(fd: libc::c_int, val: *mut $ty) -> io::Result<libc::c_int> {
+ let ioc = ($dir as u32)
+ | ((mem::size_of::<$ty>() as u32 & IOCPARM_MASK) << IOCPARM_SHIFT)
+ | (($group as u32) << IOCGROUP_SHIFT)
+ | ($nr as u32);
+ let rv = libc::ioctl(fd, ioc as libc::c_ulong, val);
+ if rv == -1 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(rv)
+ }
+ };
+}
+
+#[allow(non_camel_case_types)]
+#[repr(C)]
+struct usb_ctl_report_desc {
+ ucrd_size: c_int,
+ ucrd_data: [c_uchar; 1024],
+}
+
+ioctl!(IOC_OUT, usb_get_report_desc, b'U', 21, usb_ctl_report_desc);
+
+fn read_report_descriptor(fd: &Fd) -> io::Result<ReportDescriptor> {
+ let mut desc = unsafe { mem::zeroed() };
+ unsafe { usb_get_report_desc(fd.fileno, &mut desc) }?;
+ if desc.ucrd_size < 0 {
+ return Err(io_err("negative report descriptor size"));
+ }
+ let size = desc.ucrd_size as usize;
+ let value = Vec::from(&desc.ucrd_data[..size]);
+ Ok(ReportDescriptor { value })
+}
+
+pub fn is_u2f_device(fd: &Fd) -> bool {
+ match read_report_descriptor(fd) {
+ Ok(desc) => has_fido_usage(desc),
+ Err(_) => false,
+ }
+}
+
+ioctl!(IOC_IN, usb_hid_set_raw_ioctl, b'h', 2, c_int);
+
+pub fn hid_set_raw(fd: &Fd, raw: bool) -> io::Result<()> {
+ let mut raw_int: c_int = if raw { 1 } else { 0 };
+ unsafe { usb_hid_set_raw_ioctl(fd.fileno, &mut raw_int) }?;
+ Ok(())
+}
diff --git a/third_party/rust/authenticator/src/transport/openbsd/device.rs b/third_party/rust/authenticator/src/transport/openbsd/device.rs
new file mode 100644
index 0000000000..f11ded7984
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/openbsd/device.rs
@@ -0,0 +1,224 @@
+/* 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/. */
+
+extern crate libc;
+use crate::consts::{Capability, CID_BROADCAST, MAX_HID_RPT_SIZE};
+use crate::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::transport::hid::HIDDevice;
+use crate::transport::platform::monitor::WrappedOpenDevice;
+use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
+use crate::u2ftypes::U2FDeviceInfo;
+use crate::util::{from_unix_result, io_err};
+use std::ffi::{CString, OsString};
+use std::hash::{Hash, Hasher};
+use std::io::{self, Read, Write};
+use std::mem;
+use std::os::unix::ffi::OsStrExt;
+
+#[derive(Debug)]
+pub struct Device {
+ path: OsString,
+ fd: libc::c_int,
+ in_rpt_size: usize,
+ out_rpt_size: usize,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+ secret: Option<SharedSecret>,
+ authenticator_info: Option<AuthenticatorInfo>,
+ protocol: FidoProtocol,
+}
+
+impl Device {
+ fn ping(&mut self) -> io::Result<()> {
+ let capacity = 256;
+
+ for _ in 0..10 {
+ let mut data = vec![0u8; capacity];
+
+ // Send 1 byte ping
+ // self.write_all requires Device to be mut. This can't be done at the moment,
+ // and this is a workaround anyways, so writing by hand instead.
+ self.write_all(&[0, 0xff, 0xff, 0xff, 0xff, 0x81, 0, 1])?;
+
+ // Wait for response
+ let mut pfd: libc::pollfd = unsafe { mem::zeroed() };
+ pfd.fd = self.fd;
+ pfd.events = libc::POLLIN;
+ if from_unix_result(unsafe { libc::poll(&mut pfd, 1, 100) })? == 0 {
+ debug!("device {:?} timeout", self.path);
+ continue;
+ }
+
+ // Read response
+ self.read(&mut data[..])?;
+
+ return Ok(());
+ }
+
+ Err(io_err("no response from device"))
+ }
+}
+
+impl Drop for Device {
+ fn drop(&mut self) {
+ // Close the fd, ignore any errors.
+ let _ = unsafe { libc::close(self.fd) };
+ debug!("device {:?} closed", self.path);
+ }
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ self.path == other.path
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // The path should be the only identifying member for a device
+ // If the path is the same, its the same device
+ self.path.hash(state);
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let buf_ptr = buf.as_mut_ptr() as *mut libc::c_void;
+ let rv = unsafe { libc::read(self.fd, buf_ptr, buf.len()) };
+ from_unix_result(rv as usize)
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // Always skip the first byte (report number)
+ let data = &buf[1..];
+ let data_ptr = data.as_ptr() as *const libc::c_void;
+ let rv = unsafe { libc::write(self.fd, data_ptr, data.len()) };
+ Ok(from_unix_result(rv as usize)? + 1)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl HIDDevice for Device {
+ type BuildParameters = WrappedOpenDevice;
+ type Id = OsString;
+
+ fn new(fido: WrappedOpenDevice) -> Result<Self, (HIDError, Self::Id)> {
+ debug!("device found: {:?}", fido);
+ let mut res = Self {
+ path: fido.os_path,
+ fd: fido.fd,
+ in_rpt_size: MAX_HID_RPT_SIZE,
+ out_rpt_size: MAX_HID_RPT_SIZE,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ secret: None,
+ authenticator_info: None,
+ protocol: FidoProtocol::CTAP2,
+ };
+ if res.is_u2f() {
+ info!("new device {:?}", res.path);
+ Ok(res)
+ } else {
+ Err((HIDError::DeviceNotSupported, res.path.clone()))
+ }
+ }
+
+ fn id(&self) -> Self::Id {
+ self.path.clone()
+ }
+
+ fn get_cid(&self) -> &[u8; 4] {
+ &self.cid
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ self.cid = cid;
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ self.in_rpt_size
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ self.out_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);
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ HIDDevice::pre_init(self)
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ HIDDevice::get_device_info(self)
+ .cap_flags
+ .contains(Capability::CBOR)
+ }
+
+ fn initialized(&self) -> bool {
+ // During successful init, the broadcast channel id gets repplaced by an actual one
+ self.cid != CID_BROADCAST
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ debug!("device {:?} is U2F/FIDO", self.path);
+
+ // From OpenBSD's libfido2 in 6.6-current:
+ // "OpenBSD (as of 201910) has a bug that causes it to lose
+ // track of the DATA0/DATA1 sequence toggle across uhid device
+ // open and close. This is a terrible hack to work around it."
+ match self.ping() {
+ Ok(_) => true,
+ Err(err) => {
+ debug!("device {:?} is not responding: {}", self.path, err);
+ false
+ }
+ }
+ }
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ self.secret.as_ref()
+ }
+
+ fn set_shared_secret(&mut self, secret: SharedSecret) {
+ self.secret = Some(secret);
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1;
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/openbsd/mod.rs b/third_party/rust/authenticator/src/transport/openbsd/mod.rs
new file mode 100644
index 0000000000..fa02132e67
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/openbsd/mod.rs
@@ -0,0 +1,8 @@
+/* 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;
diff --git a/third_party/rust/authenticator/src/transport/openbsd/monitor.rs b/third_party/rust/authenticator/src/transport/openbsd/monitor.rs
new file mode 100644
index 0000000000..0ea5f3d0b8
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/openbsd/monitor.rs
@@ -0,0 +1,138 @@
+/* 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::transport::device_selector::DeviceSelectorEvent;
+use crate::util::from_unix_result;
+use runloop::RunLoop;
+use std::collections::HashMap;
+use std::error::Error;
+use std::ffi::{CString, OsString};
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::io::RawFd;
+use std::path::PathBuf;
+use std::sync::{mpsc::Sender, Arc};
+use std::thread;
+use std::time::Duration;
+
+const POLL_TIMEOUT: u64 = 500;
+
+#[derive(Debug)]
+pub struct WrappedOpenDevice {
+ pub fd: RawFd,
+ pub os_path: OsString,
+}
+
+pub struct Monitor<F>
+where
+ F: Fn(
+ WrappedOpenDevice,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync,
+{
+ runloops: HashMap<OsString, RunLoop>,
+ new_device_cb: Arc<F>,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(
+ WrappedOpenDevice,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Send
+ + Sync
+ + 'static,
+{
+ pub fn new(
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+ ) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ selector_sender,
+ status_sender,
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
+ // Loop until we're stopped by the controlling thread, or fail.
+ while alive() {
+ // Iterate the first 10 fido(4) devices.
+ for path in (0..10)
+ .map(|unit| PathBuf::from(&format!("/dev/fido/{}", unit)))
+ .filter(|path| path.exists())
+ {
+ let os_path = path.as_os_str().to_os_string();
+ let cstr = CString::new(os_path.as_bytes())?;
+
+ // Try to open the device.
+ let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
+ match from_unix_result(fd) {
+ Ok(fd) => {
+ // The device is available if it can be opened.
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(vec![os_path.clone()]));
+ self.add_device(WrappedOpenDevice { fd, os_path });
+ }
+ Err(ref err) if err.raw_os_error() == Some(libc::EBUSY) => {
+ // The device is available but currently in use.
+ }
+ _ => {
+ // libc::ENODEV or any other error.
+ self.remove_device(os_path);
+ }
+ }
+ }
+
+ thread::sleep(Duration::from_millis(POLL_TIMEOUT));
+ }
+
+ // Remove all tracked devices.
+ self.remove_all_devices();
+
+ Ok(())
+ }
+
+ fn add_device(&mut self, fido: WrappedOpenDevice) {
+ if !self.runloops.contains_key(&fido.os_path) {
+ let f = self.new_device_cb.clone();
+ let key = fido.os_path.clone();
+ let selector_sender = self.selector_sender.clone();
+ let status_sender = self.status_sender.clone();
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(fido, selector_sender, status_sender, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+ }
+
+ fn remove_device(&mut self, path: OsString) {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
+ 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/transport/openbsd/transaction.rs b/third_party/rust/authenticator/src/transport/openbsd/transaction.rs
new file mode 100644
index 0000000000..6b15f6751a
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/openbsd/transaction.rs
@@ -0,0 +1,69 @@
+/* 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::statecallback::StateCallback;
+use crate::transport::device_selector::{
+ DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
+};
+use crate::transport::platform::monitor::Monitor;
+use runloop::RunLoop;
+use std::sync::mpsc::Sender;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: RunLoop,
+ device_selector: DeviceSelector,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ status: Sender<crate::StatusUpdate>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ let device_selector = DeviceSelector::run();
+ let selector_sender = device_selector.clone_sender();
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
+
+ // 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,
+ device_selector,
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ info!("Transaction was cancelled.");
+ self.device_selector.stop();
+ self.thread.cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/stub/device.rs b/third_party/rust/authenticator/src/transport/stub/device.rs
new file mode 100644
index 0000000000..29d8a3ab9b
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/stub/device.rs
@@ -0,0 +1,115 @@
+/* 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::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::transport::hid::HIDDevice;
+use crate::transport::{FidoDevice, FidoProtocol};
+use crate::transport::{HIDError, SharedSecret};
+use crate::u2ftypes::U2FDeviceInfo;
+use std::hash::Hash;
+use std::io;
+use std::io::{Read, Write};
+use std::path::PathBuf;
+
+#[derive(Debug, Hash, PartialEq, Eq)]
+pub struct Device {}
+
+impl Read for Device {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ unimplemented!();
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ unimplemented!();
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ unimplemented!();
+ }
+}
+
+impl HIDDevice for Device {
+ type BuildParameters = PathBuf;
+ type Id = PathBuf;
+
+ fn new(parameters: Self::BuildParameters) -> Result<Self, (HIDError, Self::Id)> {
+ unimplemented!();
+ }
+
+ fn id(&self) -> Self::Id {
+ unimplemented!()
+ }
+
+ fn get_cid(&self) -> &[u8; 4] {
+ unimplemented!();
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ unimplemented!();
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ unimplemented!();
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ unimplemented!();
+ }
+
+ fn get_property(&self, prop_name: &str) -> io::Result<String> {
+ unimplemented!();
+ }
+
+ fn get_device_info(&self) -> U2FDeviceInfo {
+ unimplemented!();
+ }
+
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
+ unimplemented!();
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ unimplemented!();
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ unimplemented!();
+ }
+
+ fn initialized(&self) -> bool {
+ unimplemented!();
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ unimplemented!()
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ unimplemented!()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ unimplemented!()
+ }
+
+ fn set_shared_secret(&mut self, secret: SharedSecret) {
+ unimplemented!()
+ }
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ unimplemented!()
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ unimplemented!()
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ unimplemented!()
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/stub/mod.rs b/third_party/rust/authenticator/src/transport/stub/mod.rs
new file mode 100644
index 0000000000..0fab62d495
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/stub/mod.rs
@@ -0,0 +1,11 @@
+/* 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/. */
+
+// No-op module to permit compiling token HID support for Android, where
+// no results are returned.
+
+#![allow(unused_variables)]
+
+pub mod device;
+pub mod transaction;
diff --git a/third_party/rust/authenticator/src/transport/stub/transaction.rs b/third_party/rust/authenticator/src/transport/stub/transaction.rs
new file mode 100644
index 0000000000..d471c94da8
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/stub/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::statecallback::StateCallback;
+use crate::transport::device_selector::{
+ DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
+};
+use std::path::PathBuf;
+use std::sync::mpsc::Sender;
+
+pub struct Transaction {}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ _status: Sender<crate::StatusUpdate>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ // Just to silence "unused"-warnings
+ let mut device_selector = DeviceSelector::run();
+ let _ = DeviceSelectorEvent::DevicesAdded(vec![]);
+ let _ = DeviceSelectorEvent::DeviceRemoved(PathBuf::new());
+ let _ = device_selector.clone_sender();
+ device_selector.stop();
+
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotSupported,
+ )));
+
+ Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotSupported,
+ ))
+ }
+
+ pub fn cancel(&mut self) {
+ /* No-op. */
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/windows/device.rs b/third_party/rust/authenticator/src/transport/windows/device.rs
new file mode 100644
index 0000000000..bda5d385cc
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/windows/device.rs
@@ -0,0 +1,173 @@
+/* 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 super::winapi::DeviceCapabilities;
+use crate::consts::{
+ Capability, CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE,
+};
+use crate::ctap2::commands::get_info::AuthenticatorInfo;
+use crate::transport::hid::HIDDevice;
+use crate::transport::{FidoDevice, FidoProtocol, HIDError, SharedSecret};
+use crate::u2ftypes::U2FDeviceInfo;
+use std::fs::{File, OpenOptions};
+use std::hash::{Hash, Hasher};
+use std::io::{self, Read, Write};
+use std::os::windows::io::AsRawHandle;
+
+#[derive(Debug)]
+pub struct Device {
+ path: String,
+ file: File,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+ secret: Option<SharedSecret>,
+ authenticator_info: Option<AuthenticatorInfo>,
+ protocol: FidoProtocol,
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ self.path == other.path
+ }
+}
+
+impl Eq for Device {}
+
+impl Hash for Device {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ // The path should be the only identifying member for a device
+ // If the path is the same, its the same device
+ self.path.hash(state);
+ }
+}
+
+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())
+ }
+}
+
+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 HIDDevice for Device {
+ type BuildParameters = String;
+ type Id = String;
+
+ fn new(path: String) -> Result<Self, (HIDError, Self::Id)> {
+ debug!("Opening device {:?}", path);
+ let file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&path)
+ .map_err(|e| (HIDError::IO(Some(path.clone().into()), e), path.clone()))?;
+ let mut res = Self {
+ path,
+ file,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ secret: None,
+ authenticator_info: None,
+ protocol: FidoProtocol::CTAP2,
+ };
+ if res.is_u2f() {
+ info!("new device {:?}", res.path);
+ Ok(res)
+ } else {
+ Err((HIDError::DeviceNotSupported, res.path))
+ }
+ }
+
+ fn id(&self) -> Self::Id {
+ self.path.clone()
+ }
+ fn get_cid(&self) -> &[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);
+ }
+}
+
+impl FidoDevice for Device {
+ fn pre_init(&mut self) -> Result<(), HIDError> {
+ HIDDevice::pre_init(self)
+ }
+
+ fn should_try_ctap2(&self) -> bool {
+ HIDDevice::get_device_info(self)
+ .cap_flags
+ .contains(Capability::CBOR)
+ }
+
+ fn initialized(&self) -> bool {
+ // During successful init, the broadcast channel id gets repplaced by an actual one
+ self.cid != CID_BROADCAST
+ }
+
+ fn is_u2f(&mut self) -> bool {
+ match DeviceCapabilities::new(self.file.as_raw_handle()) {
+ Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE,
+ _ => false,
+ }
+ }
+
+ fn get_shared_secret(&self) -> Option<&SharedSecret> {
+ self.secret.as_ref()
+ }
+
+ fn set_shared_secret(&mut self, secret: SharedSecret) {
+ self.secret = Some(secret);
+ }
+
+ fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
+ self.authenticator_info.as_ref()
+ }
+
+ fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
+ self.authenticator_info = Some(authenticator_info);
+ }
+
+ fn get_protocol(&self) -> FidoProtocol {
+ self.protocol
+ }
+
+ fn downgrade_to_ctap1(&mut self) {
+ self.protocol = FidoProtocol::CTAP1;
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/windows/mod.rs b/third_party/rust/authenticator/src/transport/windows/mod.rs
new file mode 100644
index 0000000000..09135391dd
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/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/transport/windows/monitor.rs b/third_party/rust/authenticator/src/transport/windows/monitor.rs
new file mode 100644
index 0000000000..c73c012b05
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/windows/monitor.rs
@@ -0,0 +1,125 @@
+/* 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::transport::device_selector::{DeviceID, DeviceSelectorEvent};
+use crate::transport::platform::winapi::DeviceInfoSet;
+use runloop::RunLoop;
+use std::collections::{HashMap, HashSet};
+use std::error::Error;
+use std::iter::FromIterator;
+use std::sync::{mpsc::Sender, Arc};
+use std::thread;
+use std::time::Duration;
+
+pub struct Monitor<F>
+where
+ F: Fn(String, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ + Sync,
+{
+ runloops: HashMap<String, RunLoop>,
+ new_device_cb: Arc<F>,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(String, Sender<DeviceSelectorEvent>, Sender<crate::StatusUpdate>, &dyn Fn() -> bool)
+ + Send
+ + Sync
+ + 'static,
+{
+ pub fn new(
+ new_device_cb: F,
+ selector_sender: Sender<DeviceSelectorEvent>,
+ status_sender: Sender<crate::StatusUpdate>,
+ ) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ selector_sender,
+ status_sender,
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> Result<(), Box<dyn Error>> {
+ let mut current = HashSet::new();
+ let mut previous;
+
+ while alive() {
+ let device_info_set = DeviceInfoSet::new()?;
+ previous = current;
+ current = HashSet::from_iter(device_info_set.devices());
+
+ // Remove devices that are gone.
+ for path in previous.difference(&current) {
+ self.remove_device(path);
+ }
+
+ let added: Vec<String> = current.difference(&previous).cloned().collect();
+
+ // We have to notify additions in batches to avoid
+ // arbitrarily selecting the first added device.
+ if !added.is_empty()
+ && self
+ .selector_sender
+ .send(DeviceSelectorEvent::DevicesAdded(added.clone()))
+ .is_err()
+ {
+ // Send only fails if the receiver hung up. We should exit the loop.
+ break;
+ }
+
+ // Add devices that were plugged in.
+ for path in added {
+ self.add_device(&path);
+ }
+
+ // 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: &DeviceID) {
+ let f = self.new_device_cb.clone();
+ let path = path.clone();
+ let key = path.clone();
+ let selector_sender = self.selector_sender.clone();
+ let status_sender = self.status_sender.clone();
+ debug!("Adding device {}", path);
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(path, selector_sender, status_sender, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: &DeviceID) {
+ let _ = self
+ .selector_sender
+ .send(DeviceSelectorEvent::DeviceRemoved(path.clone()));
+
+ debug!("Removing device {}", path);
+ 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/transport/windows/transaction.rs b/third_party/rust/authenticator/src/transport/windows/transaction.rs
new file mode 100644
index 0000000000..6b15f6751a
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/windows/transaction.rs
@@ -0,0 +1,69 @@
+/* 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::statecallback::StateCallback;
+use crate::transport::device_selector::{
+ DeviceBuildParameters, DeviceSelector, DeviceSelectorEvent,
+};
+use crate::transport::platform::monitor::Monitor;
+use runloop::RunLoop;
+use std::sync::mpsc::Sender;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: RunLoop,
+ device_selector: DeviceSelector,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ status: Sender<crate::StatusUpdate>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(
+ DeviceBuildParameters,
+ Sender<DeviceSelectorEvent>,
+ Sender<crate::StatusUpdate>,
+ &dyn Fn() -> bool,
+ ) + Sync
+ + Send
+ + 'static,
+ T: 'static,
+ {
+ let device_selector = DeviceSelector::run();
+ let selector_sender = device_selector.clone_sender();
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb, selector_sender, status);
+
+ // 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,
+ device_selector,
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ info!("Transaction was cancelled.");
+ self.device_selector.stop();
+ self.thread.cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/transport/windows/winapi.rs b/third_party/rust/authenticator/src/transport/windows/winapi.rs
new file mode 100644
index 0000000000..44b4489811
--- /dev/null
+++ b/third_party/rust/authenticator/src/transport/windows/winapi.rs
@@ -0,0 +1,263 @@
+/* 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 winapi::shared::{guiddef, minwindef, ntdef, windef};
+use winapi::shared::{hidclass, hidpi, hidusage};
+use 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;
+}
+
+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)?;
+ 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 = memoffset::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(ptr::addr_of!((*self.data).DevicePath[0]), 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
+ }
+}