summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/authenticator/src')
-rw-r--r--third_party/rust/authenticator/src/authenticatorservice.rs635
-rw-r--r--third_party/rust/authenticator/src/capi.rs377
-rw-r--r--third_party/rust/authenticator/src/consts.rs80
-rw-r--r--third_party/rust/authenticator/src/errors.rs96
-rw-r--r--third_party/rust/authenticator/src/freebsd/device.rs112
-rw-r--r--third_party/rust/authenticator/src/freebsd/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/freebsd/monitor.rs133
-rw-r--r--third_party/rust/authenticator/src/freebsd/transaction.rs53
-rw-r--r--third_party/rust/authenticator/src/freebsd/uhid.rs92
-rw-r--r--third_party/rust/authenticator/src/hidproto.rs253
-rw-r--r--third_party/rust/authenticator/src/lib.rs129
-rw-r--r--third_party/rust/authenticator/src/linux/device.rs112
-rw-r--r--third_party/rust/authenticator/src/linux/hidraw.rs80
-rw-r--r--third_party/rust/authenticator/src/linux/hidwrapper.h12
-rw-r--r--third_party/rust/authenticator/src/linux/hidwrapper.rs48
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_armle.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_mips64le.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_mipsle.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_x86.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/ioctl_x86_64.rs5
-rw-r--r--third_party/rust/authenticator/src/linux/mod.rs12
-rw-r--r--third_party/rust/authenticator/src/linux/monitor.rs168
-rw-r--r--third_party/rust/authenticator/src/linux/transaction.rs53
-rw-r--r--third_party/rust/authenticator/src/macos/device.rs156
-rw-r--r--third_party/rust/authenticator/src/macos/iokit.rs292
-rw-r--r--third_party/rust/authenticator/src/macos/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/macos/monitor.rs175
-rw-r--r--third_party/rust/authenticator/src/macos/transaction.rs92
-rw-r--r--third_party/rust/authenticator/src/manager.rs197
-rw-r--r--third_party/rust/authenticator/src/netbsd/device.rs159
-rw-r--r--third_party/rust/authenticator/src/netbsd/fd.rs47
-rw-r--r--third_party/rust/authenticator/src/netbsd/mod.rs10
-rw-r--r--third_party/rust/authenticator/src/netbsd/monitor.rs87
-rw-r--r--third_party/rust/authenticator/src/netbsd/transaction.rs53
-rw-r--r--third_party/rust/authenticator/src/netbsd/uhid.rs77
-rw-r--r--third_party/rust/authenticator/src/openbsd/device.rs148
-rw-r--r--third_party/rust/authenticator/src/openbsd/mod.rs8
-rw-r--r--third_party/rust/authenticator/src/openbsd/monitor.rs111
-rw-r--r--third_party/rust/authenticator/src/openbsd/transaction.rs52
-rw-r--r--third_party/rust/authenticator/src/statecallback.rs166
-rw-r--r--third_party/rust/authenticator/src/statemachine.rs283
-rw-r--r--third_party/rust/authenticator/src/stub/device.rs65
-rw-r--r--third_party/rust/authenticator/src/stub/mod.rs11
-rw-r--r--third_party/rust/authenticator/src/stub/transaction.rs31
-rw-r--r--third_party/rust/authenticator/src/u2fhid-capi.h111
-rw-r--r--third_party/rust/authenticator/src/u2fprotocol.rs457
-rw-r--r--third_party/rust/authenticator/src/u2ftypes.rs256
-rw-r--r--third_party/rust/authenticator/src/util.rs67
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/mod.rs8
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/software_u2f.rs58
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs140
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs157
-rw-r--r--third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs965
-rw-r--r--third_party/rust/authenticator/src/windows/device.rs97
-rw-r--r--third_party/rust/authenticator/src/windows/mod.rs9
-rw-r--r--third_party/rust/authenticator/src/windows/monitor.rs91
-rw-r--r--third_party/rust/authenticator/src/windows/transaction.rs52
-rw-r--r--third_party/rust/authenticator/src/windows/winapi.rs274
65 files changed, 7489 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/authenticatorservice.rs b/third_party/rust/authenticator/src/authenticatorservice.rs
new file mode 100644
index 0000000000..fcb05dc63c
--- /dev/null
+++ b/third_party/rust/authenticator/src/authenticatorservice.rs
@@ -0,0 +1,635 @@
+/* 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::sync::{mpsc::Sender, Arc, Mutex};
+
+use crate::consts::PARAMETER_SIZE;
+use crate::errors::*;
+use crate::statecallback::StateCallback;
+
+pub trait AuthenticatorTransport {
+ /// The implementation of this method must return quickly and should
+ /// report its status via the status and callback methods
+ fn register(
+ &mut self,
+ flags: crate::RegisterFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ application: crate::AppId,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::RegisterResult>>,
+ ) -> crate::Result<()>;
+
+ /// The implementation of this method must return quickly and should
+ /// report its status via the status and callback methods
+ fn sign(
+ &mut self,
+ flags: crate::SignFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ app_ids: Vec<crate::AppId>,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::SignResult>>,
+ ) -> crate::Result<()>;
+
+ fn cancel(&mut self) -> crate::Result<()>;
+}
+
+pub struct AuthenticatorService {
+ transports: Vec<Arc<Mutex<Box<dyn AuthenticatorTransport + Send>>>>,
+}
+
+fn clone_and_configure_cancellation_callback<T>(
+ mut callback: StateCallback<T>,
+ transports_to_cancel: Vec<Arc<Mutex<Box<dyn AuthenticatorTransport + Send>>>>,
+) -> StateCallback<T> {
+ callback.add_uncloneable_observer(Box::new(move || {
+ debug!(
+ "Callback observer is running, cancelling \
+ {} unchosen transports...",
+ transports_to_cancel.len()
+ );
+ for transport_mutex in &transports_to_cancel {
+ if let Err(e) = transport_mutex.lock().unwrap().cancel() {
+ error!("Cancellation failed: {:?}", e);
+ }
+ }
+ }));
+ callback
+}
+
+impl AuthenticatorService {
+ pub fn new() -> crate::Result<Self> {
+ Ok(Self {
+ transports: Vec::new(),
+ })
+ }
+
+ /// Add any detected platform transports
+ pub fn add_detected_transports(&mut self) {
+ self.add_u2f_usb_hid_platform_transports();
+ }
+
+ fn add_transport(&mut self, boxed_token: Box<dyn AuthenticatorTransport + Send>) {
+ self.transports.push(Arc::new(Mutex::new(boxed_token)))
+ }
+
+ pub fn add_u2f_usb_hid_platform_transports(&mut self) {
+ match crate::U2FManager::new() {
+ Ok(token) => self.add_transport(Box::new(token)),
+ Err(e) => error!("Could not add U2F HID transport: {}", e),
+ }
+ }
+
+ #[cfg(feature = "webdriver")]
+ pub fn add_webdriver_virtual_bus(&mut self) {
+ match crate::virtualdevices::webdriver::VirtualManager::new() {
+ Ok(token) => {
+ println!("WebDriver ready, listening at {}", &token.url());
+ self.add_transport(Box::new(token));
+ }
+ Err(e) => error!("Could not add WebDriver virtual bus: {}", e),
+ }
+ }
+
+ pub fn register(
+ &mut self,
+ flags: crate::RegisterFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ application: crate::AppId,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::RegisterResult>>,
+ ) -> crate::Result<()> {
+ if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+
+ for key_handle in &key_handles {
+ if key_handle.credential.len() > 256 {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+ }
+
+ let iterable_transports = self.transports.clone();
+ if iterable_transports.is_empty() {
+ return Err(AuthenticatorError::NoConfiguredTransports);
+ }
+
+ debug!(
+ "register called with {} transports, iterable is {}",
+ self.transports.len(),
+ iterable_transports.len()
+ );
+
+ for (idx, transport_mutex) in iterable_transports.iter().enumerate() {
+ let mut transports_to_cancel = iterable_transports.clone();
+ transports_to_cancel.remove(idx);
+
+ debug!(
+ "register transports_to_cancel {}",
+ transports_to_cancel.len()
+ );
+
+ transport_mutex.lock().unwrap().register(
+ flags,
+ timeout,
+ challenge.clone(),
+ application.clone(),
+ key_handles.clone(),
+ status.clone(),
+ clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
+ )?;
+ }
+
+ Ok(())
+ }
+
+ pub fn sign(
+ &mut self,
+ flags: crate::SignFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ app_ids: Vec<crate::AppId>,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::SignResult>>,
+ ) -> crate::Result<()> {
+ if challenge.len() != PARAMETER_SIZE {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+
+ if app_ids.is_empty() {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+
+ for app_id in &app_ids {
+ if app_id.len() != PARAMETER_SIZE {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+ }
+
+ for key_handle in &key_handles {
+ if key_handle.credential.len() > 256 {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+ }
+
+ let iterable_transports = self.transports.clone();
+ if iterable_transports.is_empty() {
+ return Err(AuthenticatorError::NoConfiguredTransports);
+ }
+
+ for (idx, transport_mutex) in iterable_transports.iter().enumerate() {
+ let mut transports_to_cancel = iterable_transports.clone();
+ transports_to_cancel.remove(idx);
+
+ transport_mutex.lock().unwrap().sign(
+ flags,
+ timeout,
+ challenge.clone(),
+ app_ids.clone(),
+ key_handles.clone(),
+ status.clone(),
+ clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel),
+ )?;
+ }
+
+ Ok(())
+ }
+
+ pub fn cancel(&mut self) -> crate::Result<()> {
+ if self.transports.is_empty() {
+ return Err(AuthenticatorError::NoConfiguredTransports);
+ }
+
+ for transport_mutex in &mut self.transports {
+ transport_mutex.lock().unwrap().cancel()?;
+ }
+
+ Ok(())
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Tests
+////////////////////////////////////////////////////////////////////////
+
+#[cfg(test)]
+mod tests {
+ use super::{AuthenticatorService, AuthenticatorTransport};
+ use crate::consts::PARAMETER_SIZE;
+ use crate::statecallback::StateCallback;
+ use crate::{AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate};
+ use std::sync::atomic::{AtomicBool, Ordering};
+ use std::sync::mpsc::{channel, Sender};
+ use std::sync::Arc;
+ use std::{io, thread};
+
+ fn init() {
+ let _ = env_logger::builder().is_test(true).try_init();
+ }
+
+ pub struct TestTransportDriver {
+ consent: bool,
+ was_cancelled: Arc<AtomicBool>,
+ }
+
+ impl TestTransportDriver {
+ pub fn new(consent: bool) -> io::Result<Self> {
+ Ok(Self {
+ consent,
+ was_cancelled: Arc::new(AtomicBool::new(false)),
+ })
+ }
+ }
+
+ impl TestTransportDriver {
+ fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo {
+ crate::u2ftypes::U2FDeviceInfo {
+ vendor_name: String::from("Mozilla").into_bytes(),
+ device_name: String::from("Test Transport Token").into_bytes(),
+ version_interface: 0,
+ version_major: 1,
+ version_minor: 2,
+ version_build: 3,
+ cap_flags: 0,
+ }
+ }
+ }
+
+ impl AuthenticatorTransport for TestTransportDriver {
+ fn register(
+ &mut self,
+ _flags: crate::RegisterFlags,
+ _timeout: u64,
+ _challenge: Vec<u8>,
+ _application: crate::AppId,
+ _key_handles: Vec<crate::KeyHandle>,
+ _status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::RegisterResult>>,
+ ) -> crate::Result<()> {
+ if self.consent {
+ let rv = Ok((vec![0u8; 16], self.dev_info()));
+ thread::spawn(move || callback.call(rv));
+ }
+ Ok(())
+ }
+
+ fn sign(
+ &mut self,
+ _flags: crate::SignFlags,
+ _timeout: u64,
+ _challenge: Vec<u8>,
+ _app_ids: Vec<crate::AppId>,
+ _key_handles: Vec<crate::KeyHandle>,
+ _status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::SignResult>>,
+ ) -> crate::Result<()> {
+ if self.consent {
+ let rv = Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info()));
+ thread::spawn(move || callback.call(rv));
+ }
+ Ok(())
+ }
+
+ fn cancel(&mut self) -> crate::Result<()> {
+ self.was_cancelled
+ .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
+ .map_or(
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::InvalidState,
+ )),
+ |_| Ok(()),
+ )
+ }
+ }
+
+ fn mk_key() -> KeyHandle {
+ KeyHandle {
+ credential: vec![0],
+ transports: AuthenticatorTransports::USB,
+ }
+ }
+
+ fn mk_challenge() -> Vec<u8> {
+ vec![0x11; PARAMETER_SIZE]
+ }
+
+ fn mk_appid() -> Vec<u8> {
+ vec![0x22; PARAMETER_SIZE]
+ }
+
+ #[test]
+ fn test_no_challenge() {
+ init();
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let mut s = AuthenticatorService::new().unwrap();
+ s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
+
+ assert_matches!(
+ s.register(
+ RegisterFlags::empty(),
+ 1_000,
+ vec![],
+ mk_appid(),
+ vec![mk_key()],
+ status_tx.clone(),
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::InvalidRelyingPartyInput
+ );
+
+ assert_matches!(
+ s.sign(
+ SignFlags::empty(),
+ 1_000,
+ vec![],
+ vec![mk_appid()],
+ vec![mk_key()],
+ status_tx,
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::InvalidRelyingPartyInput
+ );
+ }
+
+ #[test]
+ fn test_no_appids() {
+ init();
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let mut s = AuthenticatorService::new().unwrap();
+ s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
+
+ assert_matches!(
+ s.register(
+ RegisterFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ vec![],
+ vec![mk_key()],
+ status_tx.clone(),
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::InvalidRelyingPartyInput
+ );
+
+ assert_matches!(
+ s.sign(
+ SignFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ vec![],
+ vec![mk_key()],
+ status_tx,
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::InvalidRelyingPartyInput
+ );
+ }
+
+ #[test]
+ fn test_no_keys() {
+ init();
+ // No Keys is a resident-key use case. For U2F this would time out,
+ // but the actual reactions are up to the service implementation.
+ // This test yields OKs.
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let mut s = AuthenticatorService::new().unwrap();
+ s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
+
+ assert_matches!(
+ s.register(
+ RegisterFlags::empty(),
+ 100,
+ mk_challenge(),
+ mk_appid(),
+ vec![],
+ status_tx.clone(),
+ StateCallback::new(Box::new(move |_rv| {})),
+ ),
+ Ok(())
+ );
+
+ assert_matches!(
+ s.sign(
+ SignFlags::empty(),
+ 100,
+ mk_challenge(),
+ vec![mk_appid()],
+ vec![],
+ status_tx,
+ StateCallback::new(Box::new(move |_rv| {})),
+ ),
+ Ok(())
+ );
+ }
+
+ #[test]
+ fn test_large_keys() {
+ init();
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let large_key = KeyHandle {
+ credential: vec![0; 257],
+ transports: AuthenticatorTransports::USB,
+ };
+
+ let mut s = AuthenticatorService::new().unwrap();
+ s.add_transport(Box::new(TestTransportDriver::new(true).unwrap()));
+
+ assert_matches!(
+ s.register(
+ RegisterFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ mk_appid(),
+ vec![large_key.clone()],
+ status_tx.clone(),
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::InvalidRelyingPartyInput
+ );
+
+ assert_matches!(
+ s.sign(
+ SignFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ vec![mk_appid()],
+ vec![large_key],
+ status_tx,
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::InvalidRelyingPartyInput
+ );
+ }
+
+ #[test]
+ fn test_no_transports() {
+ init();
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let mut s = AuthenticatorService::new().unwrap();
+ assert_matches!(
+ s.register(
+ RegisterFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ mk_appid(),
+ vec![mk_key()],
+ status_tx.clone(),
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::NoConfiguredTransports
+ );
+
+ assert_matches!(
+ s.sign(
+ SignFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ vec![mk_appid()],
+ vec![mk_key()],
+ status_tx,
+ StateCallback::new(Box::new(move |_rv| {})),
+ )
+ .unwrap_err(),
+ crate::errors::AuthenticatorError::NoConfiguredTransports
+ );
+
+ assert_matches!(
+ s.cancel().unwrap_err(),
+ crate::errors::AuthenticatorError::NoConfiguredTransports
+ );
+ }
+
+ #[test]
+ fn test_cancellation_register() {
+ init();
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let mut s = AuthenticatorService::new().unwrap();
+ let ttd_one = TestTransportDriver::new(true).unwrap();
+ let ttd_two = TestTransportDriver::new(false).unwrap();
+ let ttd_three = TestTransportDriver::new(false).unwrap();
+
+ let was_cancelled_one = ttd_one.was_cancelled.clone();
+ let was_cancelled_two = ttd_two.was_cancelled.clone();
+ let was_cancelled_three = ttd_three.was_cancelled.clone();
+
+ s.add_transport(Box::new(ttd_one));
+ s.add_transport(Box::new(ttd_two));
+ s.add_transport(Box::new(ttd_three));
+
+ let callback = StateCallback::new(Box::new(move |_rv| {}));
+ assert!(s
+ .register(
+ RegisterFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ mk_appid(),
+ vec![],
+ status_tx,
+ callback.clone(),
+ )
+ .is_ok());
+ callback.wait();
+
+ assert_eq!(was_cancelled_one.load(Ordering::SeqCst), false);
+ assert_eq!(was_cancelled_two.load(Ordering::SeqCst), true);
+ assert_eq!(was_cancelled_three.load(Ordering::SeqCst), true);
+ }
+
+ #[test]
+ fn test_cancellation_sign() {
+ init();
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let mut s = AuthenticatorService::new().unwrap();
+ let ttd_one = TestTransportDriver::new(true).unwrap();
+ let ttd_two = TestTransportDriver::new(false).unwrap();
+ let ttd_three = TestTransportDriver::new(false).unwrap();
+
+ let was_cancelled_one = ttd_one.was_cancelled.clone();
+ let was_cancelled_two = ttd_two.was_cancelled.clone();
+ let was_cancelled_three = ttd_three.was_cancelled.clone();
+
+ s.add_transport(Box::new(ttd_one));
+ s.add_transport(Box::new(ttd_two));
+ s.add_transport(Box::new(ttd_three));
+
+ let callback = StateCallback::new(Box::new(move |_rv| {}));
+ assert!(s
+ .sign(
+ SignFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ vec![mk_appid()],
+ vec![mk_key()],
+ status_tx,
+ callback.clone(),
+ )
+ .is_ok());
+ callback.wait();
+
+ assert_eq!(was_cancelled_one.load(Ordering::SeqCst), false);
+ assert_eq!(was_cancelled_two.load(Ordering::SeqCst), true);
+ assert_eq!(was_cancelled_three.load(Ordering::SeqCst), true);
+ }
+
+ #[test]
+ fn test_cancellation_race() {
+ init();
+ let (status_tx, _) = channel::<StatusUpdate>();
+
+ let mut s = AuthenticatorService::new().unwrap();
+ // Let both of these race which one provides consent.
+ let ttd_one = TestTransportDriver::new(true).unwrap();
+ let ttd_two = TestTransportDriver::new(true).unwrap();
+
+ let was_cancelled_one = ttd_one.was_cancelled.clone();
+ let was_cancelled_two = ttd_two.was_cancelled.clone();
+
+ s.add_transport(Box::new(ttd_one));
+ s.add_transport(Box::new(ttd_two));
+
+ let callback = StateCallback::new(Box::new(move |_rv| {}));
+ assert!(s
+ .register(
+ RegisterFlags::empty(),
+ 1_000,
+ mk_challenge(),
+ mk_appid(),
+ vec![],
+ status_tx,
+ callback.clone(),
+ )
+ .is_ok());
+ callback.wait();
+
+ let one = was_cancelled_one.load(Ordering::SeqCst);
+ let two = was_cancelled_two.load(Ordering::SeqCst);
+ assert_eq!(
+ one ^ two,
+ true,
+ "asserting that one={} xor two={} is true",
+ one,
+ two
+ );
+ }
+}
diff --git a/third_party/rust/authenticator/src/capi.rs b/third_party/rust/authenticator/src/capi.rs
new file mode 100644
index 0000000000..ea7123503f
--- /dev/null
+++ b/third_party/rust/authenticator/src/capi.rs
@@ -0,0 +1,377 @@
+/* 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::authenticatorservice::AuthenticatorService;
+use crate::errors;
+use crate::statecallback::StateCallback;
+use crate::{RegisterResult, SignResult};
+use libc::size_t;
+use rand::{thread_rng, Rng};
+use std::collections::HashMap;
+use std::sync::mpsc::channel;
+use std::thread;
+use std::{ptr, slice};
+
+type U2FAppIds = Vec<crate::AppId>;
+type U2FKeyHandles = Vec<crate::KeyHandle>;
+type U2FCallback = extern "C" fn(u64, *mut U2FResult);
+
+pub enum U2FResult {
+ Success(HashMap<u8, Vec<u8>>),
+ Error(errors::AuthenticatorError),
+}
+
+const RESBUF_ID_REGISTRATION: u8 = 0;
+const RESBUF_ID_KEYHANDLE: u8 = 1;
+const RESBUF_ID_SIGNATURE: u8 = 2;
+const RESBUF_ID_APPID: u8 = 3;
+const RESBUF_ID_VENDOR_NAME: u8 = 4;
+const RESBUF_ID_DEVICE_NAME: u8 = 5;
+const RESBUF_ID_FIRMWARE_MAJOR: u8 = 6;
+const RESBUF_ID_FIRMWARE_MINOR: u8 = 7;
+const RESBUF_ID_FIRMWARE_BUILD: u8 = 8;
+
+// Generates a new 64-bit transaction id with collision probability 2^-32.
+fn new_tid() -> u64 {
+ thread_rng().gen::<u64>()
+}
+
+unsafe fn from_raw(ptr: *const u8, len: usize) -> Vec<u8> {
+ slice::from_raw_parts(ptr, len).to_vec()
+}
+
+/// # Safety
+///
+/// The handle returned by this method must be freed by the caller.
+#[no_mangle]
+pub extern "C" fn rust_u2f_mgr_new() -> *mut AuthenticatorService {
+ if let Ok(mut mgr) = AuthenticatorService::new() {
+ mgr.add_detected_transports();
+ Box::into_raw(Box::new(mgr))
+ } else {
+ ptr::null_mut()
+ }
+}
+
+/// # Safety
+///
+/// This method must not be called on a handle twice, and the handle is unusable
+/// after.
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_mgr_free(mgr: *mut AuthenticatorService) {
+ if !mgr.is_null() {
+ Box::from_raw(mgr);
+ }
+}
+
+/// # Safety
+///
+/// The handle returned by this method must be freed by the caller.
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_app_ids_new() -> *mut U2FAppIds {
+ Box::into_raw(Box::new(vec![]))
+}
+
+/// # Safety
+///
+/// This method must be used on an actual U2FAppIds handle
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_app_ids_add(
+ ids: *mut U2FAppIds,
+ id_ptr: *const u8,
+ id_len: usize,
+) {
+ (*ids).push(from_raw(id_ptr, id_len));
+}
+
+/// # Safety
+///
+/// This method must not be called on a handle twice, and the handle is unusable
+/// after.
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_app_ids_free(ids: *mut U2FAppIds) {
+ if !ids.is_null() {
+ Box::from_raw(ids);
+ }
+}
+
+/// # Safety
+///
+/// The handle returned by this method must be freed by the caller.
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_khs_new() -> *mut U2FKeyHandles {
+ Box::into_raw(Box::new(vec![]))
+}
+
+/// # Safety
+///
+/// This method must be used on an actual U2FKeyHandles handle
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_khs_add(
+ khs: *mut U2FKeyHandles,
+ key_handle_ptr: *const u8,
+ key_handle_len: usize,
+ transports: u8,
+) {
+ (*khs).push(crate::KeyHandle {
+ credential: from_raw(key_handle_ptr, key_handle_len),
+ transports: crate::AuthenticatorTransports::from_bits_truncate(transports),
+ });
+}
+
+/// # Safety
+///
+/// This method must not be called on a handle twice, and the handle is unusable
+/// after.
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_khs_free(khs: *mut U2FKeyHandles) {
+ if !khs.is_null() {
+ Box::from_raw(khs);
+ }
+}
+
+/// # Safety
+///
+/// This method must be used on an actual U2FResult handle
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_result_error(res: *const U2FResult) -> u8 {
+ if res.is_null() {
+ return errors::U2FTokenError::Unknown as u8;
+ }
+
+ if let U2FResult::Error(ref err) = *res {
+ return err.as_u2f_errorcode();
+ }
+
+ 0 /* No error, the request succeeded. */
+}
+
+/// # Safety
+///
+/// This method must be used before rust_u2f_resbuf_copy
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_resbuf_length(
+ res: *const U2FResult,
+ bid: u8,
+ len: *mut size_t,
+) -> bool {
+ if res.is_null() {
+ return false;
+ }
+
+ if let U2FResult::Success(ref bufs) = *res {
+ if let Some(buf) = bufs.get(&bid) {
+ *len = buf.len();
+ return true;
+ }
+ }
+
+ false
+}
+
+/// # Safety
+///
+/// This method does not ensure anything about dst before copying, so
+/// ensure it is long enough (using rust_u2f_resbuf_length)
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_resbuf_copy(
+ res: *const U2FResult,
+ bid: u8,
+ dst: *mut u8,
+) -> bool {
+ if res.is_null() {
+ return false;
+ }
+
+ if let U2FResult::Success(ref bufs) = *res {
+ if let Some(buf) = bufs.get(&bid) {
+ ptr::copy_nonoverlapping(buf.as_ptr(), dst, buf.len());
+ return true;
+ }
+ }
+
+ false
+}
+
+/// # Safety
+///
+/// This method should not be called on U2FResult handles after they've been
+/// freed or a double-free will occur
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_res_free(res: *mut U2FResult) {
+ if !res.is_null() {
+ Box::from_raw(res);
+ }
+}
+
+/// # Safety
+///
+/// This method should not be called on AuthenticatorService handles after
+/// they've been freed
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_mgr_register(
+ mgr: *mut AuthenticatorService,
+ flags: u64,
+ timeout: u64,
+ callback: U2FCallback,
+ challenge_ptr: *const u8,
+ challenge_len: usize,
+ application_ptr: *const u8,
+ application_len: usize,
+ khs: *const U2FKeyHandles,
+) -> u64 {
+ if mgr.is_null() {
+ return 0;
+ }
+
+ // Check buffers.
+ if challenge_ptr.is_null() || application_ptr.is_null() {
+ return 0;
+ }
+
+ let flags = crate::RegisterFlags::from_bits_truncate(flags);
+ let challenge = from_raw(challenge_ptr, challenge_len);
+ let application = from_raw(application_ptr, application_len);
+ let key_handles = (*khs).clone();
+
+ let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
+ thread::spawn(move || loop {
+ // Issue https://github.com/mozilla/authenticator-rs/issues/132 will
+ // plumb the status channel through to the actual C API signatures
+ match status_rx.recv() {
+ Ok(_) => {}
+ Err(_recv_error) => return,
+ }
+ });
+
+ let tid = new_tid();
+
+ let state_callback = StateCallback::<crate::Result<RegisterResult>>::new(Box::new(move |rv| {
+ let result = match rv {
+ Ok((registration, dev_info)) => {
+ let mut bufs = HashMap::new();
+ bufs.insert(RESBUF_ID_REGISTRATION, registration);
+ bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name);
+ bufs.insert(RESBUF_ID_DEVICE_NAME, dev_info.device_name);
+ bufs.insert(RESBUF_ID_FIRMWARE_MAJOR, vec![dev_info.version_major]);
+ bufs.insert(RESBUF_ID_FIRMWARE_MINOR, vec![dev_info.version_minor]);
+ bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]);
+ U2FResult::Success(bufs)
+ }
+ Err(e) => U2FResult::Error(e),
+ };
+
+ callback(tid, Box::into_raw(Box::new(result)));
+ }));
+
+ let res = (*mgr).register(
+ flags,
+ timeout,
+ challenge,
+ application,
+ key_handles,
+ status_tx,
+ state_callback,
+ );
+
+ if res.is_ok() {
+ tid
+ } else {
+ 0
+ }
+}
+
+/// # Safety
+///
+/// This method should not be called on AuthenticatorService handles after
+/// they've been freed
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_mgr_sign(
+ mgr: *mut AuthenticatorService,
+ flags: u64,
+ timeout: u64,
+ callback: U2FCallback,
+ challenge_ptr: *const u8,
+ challenge_len: usize,
+ app_ids: *const U2FAppIds,
+ khs: *const U2FKeyHandles,
+) -> u64 {
+ if mgr.is_null() || khs.is_null() {
+ return 0;
+ }
+
+ // Check buffers.
+ if challenge_ptr.is_null() {
+ return 0;
+ }
+
+ // Need at least one app_id.
+ if (*app_ids).is_empty() {
+ return 0;
+ }
+
+ let flags = crate::SignFlags::from_bits_truncate(flags);
+ let challenge = from_raw(challenge_ptr, challenge_len);
+ let app_ids = (*app_ids).clone();
+ let key_handles = (*khs).clone();
+
+ let (status_tx, status_rx) = channel::<crate::StatusUpdate>();
+ thread::spawn(move || loop {
+ // Issue https://github.com/mozilla/authenticator-rs/issues/132 will
+ // plumb the status channel through to the actual C API signatures
+ match status_rx.recv() {
+ Ok(_) => {}
+ Err(_recv_error) => return,
+ }
+ });
+
+ let tid = new_tid();
+ let state_callback = StateCallback::<crate::Result<SignResult>>::new(Box::new(move |rv| {
+ let result = match rv {
+ Ok((app_id, key_handle, signature, dev_info)) => {
+ let mut bufs = HashMap::new();
+ bufs.insert(RESBUF_ID_KEYHANDLE, key_handle);
+ bufs.insert(RESBUF_ID_SIGNATURE, signature);
+ bufs.insert(RESBUF_ID_APPID, app_id);
+ bufs.insert(RESBUF_ID_VENDOR_NAME, dev_info.vendor_name);
+ bufs.insert(RESBUF_ID_DEVICE_NAME, dev_info.device_name);
+ bufs.insert(RESBUF_ID_FIRMWARE_MAJOR, vec![dev_info.version_major]);
+ bufs.insert(RESBUF_ID_FIRMWARE_MINOR, vec![dev_info.version_minor]);
+ bufs.insert(RESBUF_ID_FIRMWARE_BUILD, vec![dev_info.version_build]);
+ U2FResult::Success(bufs)
+ }
+ Err(e) => U2FResult::Error(e),
+ };
+
+ callback(tid, Box::into_raw(Box::new(result)));
+ }));
+
+ let res = (*mgr).sign(
+ flags,
+ timeout,
+ challenge,
+ app_ids,
+ key_handles,
+ status_tx,
+ state_callback,
+ );
+
+ if res.is_ok() {
+ tid
+ } else {
+ 0
+ }
+}
+
+/// # Safety
+///
+/// This method should not be called AuthenticatorService handles after they've
+/// been freed
+#[no_mangle]
+pub unsafe extern "C" fn rust_u2f_mgr_cancel(mgr: *mut AuthenticatorService) {
+ if !mgr.is_null() {
+ // Ignore return value.
+ let _ = (*mgr).cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/consts.rs b/third_party/rust/authenticator/src/consts.rs
new file mode 100644
index 0000000000..5f27bb9378
--- /dev/null
+++ b/third_party/rust/authenticator/src/consts.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/. */
+
+// Allow dead code in this module, since it's all packet consts anyways.
+#![allow(dead_code)]
+
+pub const MAX_HID_RPT_SIZE: usize = 64;
+pub const U2FAPDUHEADER_SIZE: usize = 7;
+pub const CID_BROADCAST: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
+pub const TYPE_MASK: u8 = 0x80;
+pub const TYPE_INIT: u8 = 0x80;
+pub const TYPE_CONT: u8 = 0x80;
+
+// Size of header in U2F Init USB HID Packets
+pub const INIT_HEADER_SIZE: usize = 7;
+// Size of header in U2F Cont USB HID Packets
+pub const CONT_HEADER_SIZE: usize = 5;
+
+pub const PARAMETER_SIZE: usize = 32;
+
+pub const FIDO_USAGE_PAGE: u16 = 0xf1d0; // FIDO alliance HID usage page
+pub const FIDO_USAGE_U2FHID: u16 = 0x01; // U2FHID usage for top-level collection
+pub const FIDO_USAGE_DATA_IN: u8 = 0x20; // Raw IN data report
+pub const FIDO_USAGE_DATA_OUT: u8 = 0x21; // Raw OUT data report
+
+// General pub constants
+
+pub const U2FHID_IF_VERSION: u32 = 2; // Current interface implementation version
+pub const U2FHID_FRAME_TIMEOUT: u32 = 500; // Default frame timeout in ms
+pub const U2FHID_TRANS_TIMEOUT: u32 = 3000; // Default message timeout in ms
+
+// U2FHID native commands
+pub const U2FHID_PING: u8 = TYPE_INIT | 0x01; // Echo data through local processor only
+pub const U2FHID_MSG: u8 = TYPE_INIT | 0x03; // Send U2F message frame
+pub const U2FHID_LOCK: u8 = TYPE_INIT | 0x04; // Send lock channel command
+pub const U2FHID_INIT: u8 = TYPE_INIT | 0x06; // Channel initialization
+pub const U2FHID_WINK: u8 = TYPE_INIT | 0x08; // Send device identification wink
+pub const U2FHID_ERROR: u8 = TYPE_INIT | 0x3f; // Error response
+
+// U2FHID_MSG commands
+pub const U2F_VENDOR_FIRST: u8 = TYPE_INIT | 0x40; // First vendor defined command
+pub const U2F_VENDOR_LAST: u8 = TYPE_INIT | 0x7f; // Last vendor defined command
+pub const U2F_REGISTER: u8 = 0x01; // Registration command
+pub const U2F_AUTHENTICATE: u8 = 0x02; // Authenticate/sign command
+pub const U2F_VERSION: u8 = 0x03; // Read version string command
+
+pub const YKPIV_INS_GET_VERSION: u8 = 0xfd; // Get firmware version, yubico ext
+
+// U2F_REGISTER command defines
+pub const U2F_REGISTER_ID: u8 = 0x05; // Version 2 registration identifier
+pub const U2F_REGISTER_HASH_ID: u8 = 0x00; // Version 2 hash identintifier
+
+// U2F_AUTHENTICATE command defines
+pub const U2F_REQUEST_USER_PRESENCE: u8 = 0x03; // Verify user presence and sign
+pub const U2F_CHECK_IS_REGISTERED: u8 = 0x07; // Check if the key handle is registered
+
+// U2FHID_INIT command defines
+pub const INIT_NONCE_SIZE: usize = 8; // Size of channel initialization challenge
+pub const CAPFLAG_WINK: u8 = 0x01; // Device supports WINK command
+pub const CAPFLAG_LOCK: u8 = 0x02; // Device supports LOCK command
+
+// Low-level error codes. Return as negatives.
+
+pub const ERR_NONE: u8 = 0x00; // No error
+pub const ERR_INVALID_CMD: u8 = 0x01; // Invalid command
+pub const ERR_INVALID_PAR: u8 = 0x02; // Invalid parameter
+pub const ERR_INVALID_LEN: u8 = 0x03; // Invalid message length
+pub const ERR_INVALID_SEQ: u8 = 0x04; // Invalid message sequencing
+pub const ERR_MSG_TIMEOUT: u8 = 0x05; // Message has timed out
+pub const ERR_CHANNEL_BUSY: u8 = 0x06; // Channel busy
+pub const ERR_LOCK_REQUIRED: u8 = 0x0a; // Command requires channel lock
+pub const ERR_INVALID_CID: u8 = 0x0b; // Command not allowed on this cid
+pub const ERR_OTHER: u8 = 0x7f; // Other unspecified error
+
+// These are ISO 7816-4 defined response status words.
+pub const SW_NO_ERROR: [u8; 2] = [0x90, 0x00];
+pub const SW_CONDITIONS_NOT_SATISFIED: [u8; 2] = [0x69, 0x85];
+pub const SW_WRONG_DATA: [u8; 2] = [0x6A, 0x80];
+pub const SW_WRONG_LENGTH: [u8; 2] = [0x67, 0x00];
diff --git a/third_party/rust/authenticator/src/errors.rs b/third_party/rust/authenticator/src/errors.rs
new file mode 100644
index 0000000000..ee63cfacf3
--- /dev/null
+++ b/third_party/rust/authenticator/src/errors.rs
@@ -0,0 +1,96 @@
+/* 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::fmt;
+use std::io;
+use std::sync::mpsc;
+
+// This composite error type is patterned from Phil Daniels' blog:
+// https://www.philipdaniels.com/blog/2019/defining-rust-error-types/
+
+#[derive(Debug)]
+pub enum AuthenticatorError {
+ // Errors from external libraries...
+ Io(io::Error),
+ // Errors raised by us...
+ InvalidRelyingPartyInput,
+ NoConfiguredTransports,
+ Platform,
+ InternalError(String),
+ U2FToken(U2FTokenError),
+ Custom(String),
+}
+
+impl AuthenticatorError {
+ pub fn as_u2f_errorcode(&self) -> u8 {
+ match *self {
+ AuthenticatorError::U2FToken(ref err) => *err as u8,
+ _ => U2FTokenError::Unknown as u8,
+ }
+ }
+}
+
+impl std::error::Error for AuthenticatorError {}
+
+impl fmt::Display for AuthenticatorError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ AuthenticatorError::Io(ref err) => err.fmt(f),
+ AuthenticatorError::InvalidRelyingPartyInput => {
+ write!(f, "invalid input from relying party")
+ }
+ AuthenticatorError::NoConfiguredTransports => write!(
+ f,
+ "no transports were configured in the authenticator service"
+ ),
+ AuthenticatorError::Platform => write!(f, "unknown platform error"),
+ AuthenticatorError::InternalError(ref err) => write!(f, "internal error: {}", err),
+ AuthenticatorError::U2FToken(ref err) => {
+ write!(f, "A u2f token error occurred {:?}", err)
+ }
+ AuthenticatorError::Custom(ref err) => write!(f, "A custom error occurred {:?}", err),
+ }
+ }
+}
+
+impl From<io::Error> for AuthenticatorError {
+ fn from(err: io::Error) -> AuthenticatorError {
+ AuthenticatorError::Io(err)
+ }
+}
+
+impl<T> From<mpsc::SendError<T>> for AuthenticatorError {
+ fn from(err: mpsc::SendError<T>) -> AuthenticatorError {
+ AuthenticatorError::InternalError(err.to_string())
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum U2FTokenError {
+ Unknown = 1,
+ NotSupported = 2,
+ InvalidState = 3,
+ ConstraintError = 4,
+ NotAllowed = 5,
+}
+
+impl U2FTokenError {
+ fn as_str(&self) -> &str {
+ match *self {
+ U2FTokenError::Unknown => "unknown",
+ U2FTokenError::NotSupported => "not supported",
+ U2FTokenError::InvalidState => "invalid state",
+ U2FTokenError::ConstraintError => "constraint error",
+ U2FTokenError::NotAllowed => "not allowed",
+ }
+ }
+}
+
+impl std::fmt::Display for U2FTokenError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.as_str())
+ }
+}
+
+impl std::error::Error for U2FTokenError {}
diff --git a/third_party/rust/authenticator/src/freebsd/device.rs b/third_party/rust/authenticator/src/freebsd/device.rs
new file mode 100644
index 0000000000..32069cd5f9
--- /dev/null
+++ b/third_party/rust/authenticator/src/freebsd/device.rs
@@ -0,0 +1,112 @@
+/* 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, OsString};
+use std::io;
+use std::io::{Read, Write};
+use std::os::unix::prelude::*;
+
+use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
+use crate::platform::uhid;
+use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
+use crate::util::from_unix_result;
+
+#[derive(Debug)]
+pub struct Device {
+ path: OsString,
+ fd: libc::c_int,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+}
+
+impl Device {
+ pub fn new(path: OsString) -> io::Result<Self> {
+ let cstr = CString::new(path.as_bytes())?;
+ let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
+ let fd = from_unix_result(fd)?;
+ Ok(Self {
+ path,
+ fd,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ })
+ }
+
+ pub fn is_u2f(&self) -> bool {
+ uhid::is_u2f_device(self.fd)
+ }
+}
+
+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 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 U2FDevice for Device {
+ fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
+ &self.cid
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ self.cid = cid;
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ MAX_HID_RPT_SIZE
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ MAX_HID_RPT_SIZE
+ }
+
+ fn get_property(&self, _prop_name: &str) -> io::Result<String> {
+ Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
+ }
+
+ fn get_device_info(&self) -> U2FDeviceInfo {
+ // unwrap is okay, as dev_info must have already been set, else
+ // a programmer error
+ self.dev_info.clone().unwrap()
+ }
+
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
+ self.dev_info = Some(dev_info);
+ }
+}
diff --git a/third_party/rust/authenticator/src/freebsd/mod.rs b/third_party/rust/authenticator/src/freebsd/mod.rs
new file mode 100644
index 0000000000..7ed5727157
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/freebsd/monitor.rs b/third_party/rust/authenticator/src/freebsd/monitor.rs
new file mode 100644
index 0000000000..d9153e2ef2
--- /dev/null
+++ b/third_party/rust/authenticator/src/freebsd/monitor.rs
@@ -0,0 +1,133 @@
+/* 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 devd_rs;
+use std::collections::HashMap;
+use std::ffi::OsString;
+use std::sync::Arc;
+use std::{fs, io};
+
+use runloop::RunLoop;
+
+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, &dyn Fn() -> bool) + Sync,
+{
+ runloops: HashMap<OsString, RunLoop>,
+ new_device_cb: Arc<F>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(OsString, &dyn Fn() -> bool) + Send + Sync + 'static,
+{
+ pub fn new(new_device_cb: F) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
+ let mut ctx = devd_rs::Context::new().map_err(convert_error)?;
+
+ // Iterate all existing devices.
+ for dev in fs::read_dir("/dev")? {
+ if let Ok(dev) = dev {
+ let filename_ = dev.file_name();
+ let filename = filename_.to_str().unwrap_or("");
+ if filename.starts_with("uhid") {
+ self.add_device(("/dev/".to_owned() + filename).into());
+ }
+ }
+ }
+
+ // 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(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) => {
+ 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 key = path.clone();
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(path, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: OsString) {
+ 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/freebsd/transaction.rs b/third_party/rust/authenticator/src/freebsd/transaction.rs
new file mode 100644
index 0000000000..e7cd00f184
--- /dev/null
+++ b/third_party/rust/authenticator/src/freebsd/transaction.rs
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::errors;
+use crate::platform::monitor::Monitor;
+use crate::statecallback::StateCallback;
+use runloop::RunLoop;
+use std::ffi::OsString;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: Option<RunLoop>,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(OsString, &dyn Fn() -> bool) + Sync + Send + 'static,
+ T: 'static,
+ {
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb);
+
+ // Start polling for new devices.
+ try_or!(monitor.run(alive), |_| callback
+ .call(Err(errors::AuthenticatorError::Platform)));
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotAllowed,
+ )));
+ },
+ timeout,
+ )
+ .map_err(|_| errors::AuthenticatorError::Platform)?;
+
+ Ok(Self {
+ thread: Some(thread),
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ // This must never be None.
+ self.thread.take().unwrap().cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/freebsd/uhid.rs b/third_party/rust/authenticator/src/freebsd/uhid.rs
new file mode 100644
index 0000000000..deb197ea8f
--- /dev/null
+++ b/third_party/rust/authenticator/src/freebsd/uhid.rs
@@ -0,0 +1,92 @@
+/* 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::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::with_capacity(desc.ugd_actlen as usize);
+ unsafe {
+ value.set_len(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/hidproto.rs b/third_party/rust/authenticator/src/hidproto.rs
new file mode 100644
index 0000000000..5679e6e5d7
--- /dev/null
+++ b/third_party/rust/authenticator/src/hidproto.rs
@@ -0,0 +1,253 @@
+/* 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)
+)]
+
+use std::io;
+use std::mem;
+
+use crate::consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, 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
+}
+
+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 != None {
+ 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/lib.rs b/third_party/rust/authenticator/src/lib.rs
new file mode 100644
index 0000000000..cfe82deb2b
--- /dev/null
+++ b/third_party/rust/authenticator/src/lib.rs
@@ -0,0 +1,129 @@
+/* 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/. */
+
+#[macro_use]
+mod util;
+
+#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
+pub mod hidproto;
+
+#[cfg(any(target_os = "linux"))]
+extern crate libudev;
+
+#[cfg(any(target_os = "linux"))]
+#[path = "linux/mod.rs"]
+pub mod platform;
+
+#[cfg(any(target_os = "freebsd"))]
+extern crate devd_rs;
+
+#[cfg(any(target_os = "freebsd"))]
+#[path = "freebsd/mod.rs"]
+pub mod platform;
+
+#[cfg(any(target_os = "netbsd"))]
+#[path = "netbsd/mod.rs"]
+pub mod platform;
+
+#[cfg(any(target_os = "openbsd"))]
+#[path = "openbsd/mod.rs"]
+pub mod platform;
+
+#[cfg(any(target_os = "macos"))]
+extern crate core_foundation;
+
+#[cfg(any(target_os = "macos"))]
+#[path = "macos/mod.rs"]
+pub mod platform;
+
+#[cfg(any(target_os = "windows"))]
+#[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"
+)))]
+#[path = "stub/mod.rs"]
+pub mod platform;
+
+extern crate libc;
+#[macro_use]
+extern crate log;
+extern crate rand;
+extern crate runloop;
+
+#[macro_use]
+extern crate bitflags;
+
+pub mod authenticatorservice;
+mod consts;
+mod statemachine;
+mod u2fprotocol;
+mod u2ftypes;
+
+mod manager;
+pub use crate::manager::U2FManager;
+
+mod capi;
+pub use crate::capi::*;
+
+pub mod errors;
+pub mod statecallback;
+mod virtualdevices;
+
+// Keep this in sync with the constants in u2fhid-capi.h.
+bitflags! {
+ pub struct RegisterFlags: u64 {
+ const REQUIRE_RESIDENT_KEY = 1;
+ const REQUIRE_USER_VERIFICATION = 2;
+ const REQUIRE_PLATFORM_ATTACHMENT = 4;
+ }
+}
+bitflags! {
+ pub struct SignFlags: u64 {
+ const REQUIRE_USER_VERIFICATION = 1;
+ }
+}
+bitflags! {
+ pub struct AuthenticatorTransports: u8 {
+ const USB = 1;
+ const NFC = 2;
+ const BLE = 4;
+ }
+}
+
+#[derive(Clone)]
+pub struct KeyHandle {
+ pub credential: Vec<u8>,
+ pub transports: AuthenticatorTransports,
+}
+
+pub type AppId = Vec<u8>;
+pub type RegisterResult = (Vec<u8>, u2ftypes::U2FDeviceInfo);
+pub type SignResult = (AppId, Vec<u8>, Vec<u8>, u2ftypes::U2FDeviceInfo);
+
+pub type Result<T> = std::result::Result<T, errors::AuthenticatorError>;
+
+#[derive(Debug, Clone)]
+pub enum StatusUpdate {
+ DeviceAvailable { dev_info: u2ftypes::U2FDeviceInfo },
+ DeviceUnavailable { dev_info: u2ftypes::U2FDeviceInfo },
+ Success { dev_info: u2ftypes::U2FDeviceInfo },
+}
+
+#[cfg(test)]
+#[macro_use]
+extern crate assert_matches;
+
+#[cfg(fuzzing)]
+pub use consts::*;
+#[cfg(fuzzing)]
+pub use u2fprotocol::*;
+#[cfg(fuzzing)]
+pub use u2ftypes::*;
diff --git a/third_party/rust/authenticator/src/linux/device.rs b/third_party/rust/authenticator/src/linux/device.rs
new file mode 100644
index 0000000000..4a0d58d6d7
--- /dev/null
+++ b/third_party/rust/authenticator/src/linux/device.rs
@@ -0,0 +1,112 @@
+/* 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, OsString};
+use std::io;
+use std::io::{Read, Write};
+use std::os::unix::prelude::*;
+
+use crate::consts::CID_BROADCAST;
+use crate::platform::{hidraw, monitor};
+use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
+use crate::util::from_unix_result;
+
+#[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>,
+}
+
+impl Device {
+ pub fn new(path: OsString) -> io::Result<Self> {
+ let cstr = CString::new(path.as_bytes())?;
+ let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_RDWR) };
+ let fd = from_unix_result(fd)?;
+ let (in_rpt_size, out_rpt_size) = hidraw::read_hid_rpt_sizes_or_defaults(fd);
+ Ok(Self {
+ path,
+ fd,
+ in_rpt_size,
+ out_rpt_size,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ })
+ }
+
+ pub fn is_u2f(&self) -> bool {
+ hidraw::is_u2f_device(self.fd)
+ }
+}
+
+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 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 bufp = buf.as_ptr() as *const libc::c_void;
+ let rv = unsafe { libc::write(self.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 U2FDevice for Device {
+ 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);
+ }
+}
diff --git a/third_party/rust/authenticator/src/linux/hidraw.rs b/third_party/rust/authenticator/src/linux/hidraw.rs
new file mode 100644
index 0000000000..662ea83298
--- /dev/null
+++ b/third_party/rust/authenticator/src/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::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/linux/hidwrapper.h b/third_party/rust/authenticator/src/linux/hidwrapper.h
new file mode 100644
index 0000000000..ce77e0f1ca
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/hidwrapper.rs b/third_party/rust/authenticator/src/linux/hidwrapper.rs
new file mode 100644
index 0000000000..ea1a39051b
--- /dev/null
+++ b/third_party/rust/authenticator/src/linux/hidwrapper.rs
@@ -0,0 +1,48 @@
+#![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");
diff --git a/third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs b/third_party/rust/authenticator/src/linux/ioctl_aarch64le.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_armle.rs b/third_party/rust/authenticator/src/linux/ioctl_armle.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_mips64le.rs b/third_party/rust/authenticator/src/linux/ioctl_mips64le.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_mipsbe.rs b/third_party/rust/authenticator/src/linux/ioctl_mipsbe.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_mipsle.rs b/third_party/rust/authenticator/src/linux/ioctl_mipsle.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_powerpc64be.rs b/third_party/rust/authenticator/src/linux/ioctl_powerpc64be.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_powerpc64le.rs b/third_party/rust/authenticator/src/linux/ioctl_powerpc64le.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_powerpcbe.rs b/third_party/rust/authenticator/src/linux/ioctl_powerpcbe.rs
new file mode 100644
index 0000000000..1ca187fa1f
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_s390xbe.rs b/third_party/rust/authenticator/src/linux/ioctl_s390xbe.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_x86.rs b/third_party/rust/authenticator/src/linux/ioctl_x86.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/ioctl_x86_64.rs b/third_party/rust/authenticator/src/linux/ioctl_x86_64.rs
new file mode 100644
index 0000000000..a784e9bf46
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/mod.rs b/third_party/rust/authenticator/src/linux/mod.rs
new file mode 100644
index 0000000000..c4d490ecee
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/linux/monitor.rs b/third_party/rust/authenticator/src/linux/monitor.rs
new file mode 100644
index 0000000000..595e2f3ddf
--- /dev/null
+++ b/third_party/rust/authenticator/src/linux/monitor.rs
@@ -0,0 +1,168 @@
+/* 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 libc::{c_int, c_short, c_ulong};
+use libudev::EventType;
+use runloop::RunLoop;
+use std::collections::HashMap;
+use std::ffi::OsString;
+use std::io;
+use std::os::unix::io::AsRawFd;
+use std::sync::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((&mut 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(OsString, &dyn Fn() -> bool) + Sync,
+{
+ runloops: HashMap<OsString, RunLoop>,
+ new_device_cb: Arc<F>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(OsString, &dyn Fn() -> bool) + Send + Sync + 'static,
+{
+ pub fn new(new_device_cb: F) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
+ let ctx = libudev::Context::new()?;
+
+ let mut enumerator = libudev::Enumerator::new(&ctx)?;
+ enumerator.match_subsystem(UDEV_SUBSYSTEM)?;
+
+ // Iterate all existing devices.
+ for dev in enumerator.scan_devices()? {
+ if let Some(path) = dev.devnode().map(|p| p.to_owned().into_os_string()) {
+ 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().into_os_string());
+
+ match (event.event_type(), path) {
+ (EventType::Add, Some(path)) => {
+ self.add_device(path);
+ }
+ (EventType::Remove, Some(path)) => {
+ self.remove_device(&path);
+ }
+ _ => { /* ignore other types and failures */ }
+ }
+ }
+
+ fn add_device(&mut self, path: OsString) {
+ let f = self.new_device_cb.clone();
+ let key = path.clone();
+
+ debug!("Adding device {}", path.to_string_lossy());
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(path, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: &OsString) {
+ 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: &OsString, 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/linux/transaction.rs b/third_party/rust/authenticator/src/linux/transaction.rs
new file mode 100644
index 0000000000..e7cd00f184
--- /dev/null
+++ b/third_party/rust/authenticator/src/linux/transaction.rs
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::errors;
+use crate::platform::monitor::Monitor;
+use crate::statecallback::StateCallback;
+use runloop::RunLoop;
+use std::ffi::OsString;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: Option<RunLoop>,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(OsString, &dyn Fn() -> bool) + Sync + Send + 'static,
+ T: 'static,
+ {
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb);
+
+ // Start polling for new devices.
+ try_or!(monitor.run(alive), |_| callback
+ .call(Err(errors::AuthenticatorError::Platform)));
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotAllowed,
+ )));
+ },
+ timeout,
+ )
+ .map_err(|_| errors::AuthenticatorError::Platform)?;
+
+ Ok(Self {
+ thread: Some(thread),
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ // This must never be None.
+ self.thread.take().unwrap().cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/macos/device.rs b/third_party/rust/authenticator/src/macos/device.rs
new file mode 100644
index 0000000000..425a27959b
--- /dev/null
+++ b/third_party/rust/authenticator/src/macos/device.rs
@@ -0,0 +1,156 @@
+/* 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::{CID_BROADCAST, MAX_HID_RPT_SIZE};
+use crate::platform::iokit::*;
+use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
+use core_foundation::base::*;
+use core_foundation::string::*;
+use std::convert::TryInto;
+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: Receiver<Vec<u8>>,
+ dev_info: Option<U2FDeviceInfo>,
+}
+
+impl Device {
+ pub fn new(dev_ids: (IOHIDDeviceRef, Receiver<Vec<u8>>)) -> io::Result<Self> {
+ let (device_ref, report_rx) = dev_ids;
+ Ok(Self {
+ device_ref,
+ cid: CID_BROADCAST,
+ report_rx,
+ dev_info: None,
+ })
+ }
+
+ pub fn is_u2f(&self) -> bool {
+ true
+ }
+
+ 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 PartialEq for Device {
+ fn eq(&self, other_device: &Device) -> bool {
+ self.device_ref == other_device.device_ref
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, mut bytes: &mut [u8]) -> io::Result<usize> {
+ let timeout = Duration::from_secs(READ_TIMEOUT);
+ let data = match self.report_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)
+ }
+}
+
+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 U2FDevice for Device {
+ 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);
+ }
+}
diff --git a/third_party/rust/authenticator/src/macos/iokit.rs b/third_party/rust/authenticator/src/macos/iokit.rs
new file mode 100644
index 0000000000..656cdb045d
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/macos/mod.rs b/third_party/rust/authenticator/src/macos/mod.rs
new file mode 100644
index 0000000000..44e85094d0
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/macos/monitor.rs b/third_party/rust/authenticator/src/macos/monitor.rs
new file mode 100644
index 0000000000..189366f9d1
--- /dev/null
+++ b/third_party/rust/authenticator/src/macos/monitor.rs
@@ -0,0 +1,175 @@
+/* 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::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>>), &dyn Fn() -> bool) + Sync,
+{
+ manager: IOHIDManagerRef,
+ // Keep alive until the monitor goes away.
+ _matcher: IOHIDDeviceMatcher,
+ map: HashMap<IOHIDDeviceRef, DeviceData>,
+ new_device_cb: F,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync + 'static,
+{
+ pub fn new(new_device_cb: F) -> 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(),
+ }
+ }
+
+ 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) {
+ // 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 { ref 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 (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), 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>>), &dyn Fn() -> bool) + Sync,
+{
+ fn drop(&mut self) {
+ unsafe { CFRelease(self.manager as *mut c_void) };
+ }
+}
diff --git a/third_party/rust/authenticator/src/macos/transaction.rs b/third_party/rust/authenticator/src/macos/transaction.rs
new file mode 100644
index 0000000000..697730a41a
--- /dev/null
+++ b/third_party/rust/authenticator/src/macos/transaction.rs
@@ -0,0 +1,92 @@
+/* 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::platform::iokit::{CFRunLoopEntryObserver, IOHIDDeviceRef, SendableRunLoop};
+use crate::platform::monitor::Monitor;
+use crate::statecallback::StateCallback;
+use core_foundation::runloop::*;
+use std::os::raw::c_void;
+use std::sync::mpsc::{channel, Receiver, 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<()>>,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn((IOHIDDeviceRef, Receiver<Vec<u8>>), &dyn Fn() -> bool) + Sync + Send + 'static,
+ T: 'static,
+ {
+ let (tx, rx) = channel();
+ let timeout = (timeout as f64) / 1000.0;
+
+ 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);
+ 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),
+ })
+ }
+
+ 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()) };
+
+ // This must never be None. Ignore return value.
+ let _ = self.thread.take().unwrap().join();
+ }
+}
diff --git a/third_party/rust/authenticator/src/manager.rs b/third_party/rust/authenticator/src/manager.rs
new file mode 100644
index 0000000000..06f972dba4
--- /dev/null
+++ b/third_party/rust/authenticator/src/manager.rs
@@ -0,0 +1,197 @@
+/* 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::sync::mpsc::{channel, RecvTimeoutError, Sender};
+use std::time::Duration;
+
+use crate::authenticatorservice::AuthenticatorTransport;
+use crate::consts::PARAMETER_SIZE;
+use crate::errors::*;
+use crate::statecallback::StateCallback;
+use crate::statemachine::StateMachine;
+use runloop::RunLoop;
+
+enum QueueAction {
+ Register {
+ flags: crate::RegisterFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ application: crate::AppId,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::RegisterResult>>,
+ },
+ Sign {
+ flags: crate::SignFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ app_ids: Vec<crate::AppId>,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::SignResult>>,
+ },
+ Cancel,
+}
+
+pub struct U2FManager {
+ queue: RunLoop,
+ tx: Sender<QueueAction>,
+}
+
+impl U2FManager {
+ pub fn new() -> io::Result<Self> {
+ let (tx, rx) = channel();
+
+ // Start a new work queue thread.
+ let queue = RunLoop::new(move |alive| {
+ let mut sm = StateMachine::new();
+
+ while alive() {
+ match rx.recv_timeout(Duration::from_millis(50)) {
+ Ok(QueueAction::Register {
+ flags,
+ timeout,
+ challenge,
+ application,
+ key_handles,
+ status,
+ callback,
+ }) => {
+ // This must not block, otherwise we can't cancel.
+ sm.register(
+ flags,
+ timeout,
+ challenge,
+ application,
+ key_handles,
+ status,
+ callback,
+ );
+ }
+ Ok(QueueAction::Sign {
+ flags,
+ timeout,
+ challenge,
+ app_ids,
+ key_handles,
+ status,
+ callback,
+ }) => {
+ // This must not block, otherwise we can't cancel.
+ sm.sign(
+ flags,
+ timeout,
+ challenge,
+ app_ids,
+ key_handles,
+ status,
+ callback,
+ );
+ }
+ Ok(QueueAction::Cancel) => {
+ // Cancelling must block so that we don't start a new
+ // polling thread before the old one has shut down.
+ sm.cancel();
+ }
+ Err(RecvTimeoutError::Disconnected) => {
+ break;
+ }
+ _ => { /* continue */ }
+ }
+ }
+
+ // Cancel any ongoing activity.
+ sm.cancel();
+ })?;
+
+ Ok(Self { queue, tx })
+ }
+}
+
+impl AuthenticatorTransport for U2FManager {
+ fn register(
+ &mut self,
+ flags: crate::RegisterFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ application: crate::AppId,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::RegisterResult>>,
+ ) -> crate::Result<()> {
+ if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+
+ for key_handle in &key_handles {
+ if key_handle.credential.len() > 256 {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+ }
+
+ let action = QueueAction::Register {
+ flags,
+ timeout,
+ challenge,
+ application,
+ key_handles,
+ status,
+ callback,
+ };
+ Ok(self.tx.send(action)?)
+ }
+
+ fn sign(
+ &mut self,
+ flags: crate::SignFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ app_ids: Vec<crate::AppId>,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::SignResult>>,
+ ) -> crate::Result<()> {
+ if challenge.len() != PARAMETER_SIZE {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+
+ if app_ids.is_empty() {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+
+ for app_id in &app_ids {
+ if app_id.len() != PARAMETER_SIZE {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+ }
+
+ for key_handle in &key_handles {
+ if key_handle.credential.len() > 256 {
+ return Err(AuthenticatorError::InvalidRelyingPartyInput);
+ }
+ }
+
+ let action = QueueAction::Sign {
+ flags,
+ timeout,
+ challenge,
+ app_ids,
+ key_handles,
+ status,
+ callback,
+ };
+ Ok(self.tx.send(action)?)
+ }
+
+ fn cancel(&mut self) -> crate::Result<()> {
+ Ok(self.tx.send(QueueAction::Cancel)?)
+ }
+}
+
+impl Drop for U2FManager {
+ fn drop(&mut self) {
+ self.queue.cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/netbsd/device.rs b/third_party/rust/authenticator/src/netbsd/device.rs
new file mode 100644
index 0000000000..92e7c22ea1
--- /dev/null
+++ b/third_party/rust/authenticator/src/netbsd/device.rs
@@ -0,0 +1,159 @@
+/* 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::io::Read;
+use std::io::Write;
+use std::mem;
+
+use crate::consts::CID_BROADCAST;
+use crate::consts::MAX_HID_RPT_SIZE;
+use crate::platform::fd::Fd;
+use crate::platform::uhid;
+use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
+use crate::util::io_err;
+
+#[derive(Debug)]
+pub struct Device {
+ fd: Fd,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+}
+
+impl Device {
+ pub fn new(fd: Fd) -> io::Result<Self> {
+ Ok(Self {
+ fd,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ })
+ }
+
+ pub 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 let Err(_) = self.ping() {
+ return false;
+ }
+ true
+ }
+
+ 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
+
+ self.write(&buf[..])?;
+
+ // 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
+ self.read(&mut buf[..])?;
+
+ return Ok(());
+ }
+
+ Err(io_err("no response from device"))
+ }
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ self.fd == other.fd
+ }
+}
+
+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 U2FDevice for Device {
+ fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
+ &self.cid
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ self.cid = cid;
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ MAX_HID_RPT_SIZE
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ MAX_HID_RPT_SIZE
+ }
+
+ fn get_property(&self, _prop_name: &str) -> io::Result<String> {
+ Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
+ }
+
+ fn get_device_info(&self) -> U2FDeviceInfo {
+ // unwrap is okay, as dev_info must have already been set, else
+ // a programmer error
+ self.dev_info.clone().unwrap()
+ }
+
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
+ self.dev_info = Some(dev_info);
+ }
+}
diff --git a/third_party/rust/authenticator/src/netbsd/fd.rs b/third_party/rust/authenticator/src/netbsd/fd.rs
new file mode 100644
index 0000000000..c011b7fcc8
--- /dev/null
+++ b/third_party/rust/authenticator/src/netbsd/fd.rs
@@ -0,0 +1,47 @@
+/* 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::io;
+use std::mem;
+use std::os::raw::c_int;
+use std::os::unix::io::RawFd;
+
+#[derive(Debug)]
+pub struct Fd {
+ pub fileno: RawFd,
+}
+
+impl Fd {
+ pub fn open(path: &str, 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)
+ }
+}
diff --git a/third_party/rust/authenticator/src/netbsd/mod.rs b/third_party/rust/authenticator/src/netbsd/mod.rs
new file mode 100644
index 0000000000..a0eabb6e06
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/netbsd/monitor.rs b/third_party/rust/authenticator/src/netbsd/monitor.rs
new file mode 100644
index 0000000000..c78cff6ee1
--- /dev/null
+++ b/third_party/rust/authenticator/src/netbsd/monitor.rs
@@ -0,0 +1,87 @@
+/* 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::collections::HashMap;
+use std::ffi::OsString;
+use std::io;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+
+use runloop::RunLoop;
+
+use crate::platform::fd::Fd;
+
+// 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;
+
+pub struct Monitor<F>
+where
+ F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static,
+{
+ runloops: HashMap<OsString, RunLoop>,
+ new_device_cb: Arc<F>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(Fd, &dyn Fn() -> bool) + Send + Sync + 'static,
+{
+ pub fn new(new_device_cb: F) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
+ while alive() {
+ for n in 0..100 {
+ let uhidpath = format!("/dev/uhid{}", n);
+ match Fd::open(&uhidpath, libc::O_RDWR | libc::O_CLOEXEC) {
+ Ok(uhid) => {
+ self.add_device(uhid, OsString::from(&uhidpath));
+ }
+ Err(ref err) => match err.raw_os_error() {
+ Some(libc::EBUSY) => continue,
+ Some(libc::ENOENT) => break,
+ _ => self.remove_device(OsString::from(&uhidpath)),
+ },
+ }
+ }
+ thread::sleep(Duration::from_millis(POLL_TIMEOUT));
+ }
+ self.remove_all_devices();
+ Ok(())
+ }
+
+ fn add_device(&mut self, fd: Fd, path: OsString) {
+ let f = self.new_device_cb.clone();
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(fd, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(path.clone(), runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: OsString) {
+ 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/netbsd/transaction.rs b/third_party/rust/authenticator/src/netbsd/transaction.rs
new file mode 100644
index 0000000000..21ac212569
--- /dev/null
+++ b/third_party/rust/authenticator/src/netbsd/transaction.rs
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::errors;
+use crate::platform::fd::Fd;
+use crate::platform::monitor::Monitor;
+use crate::statecallback::StateCallback;
+use runloop::RunLoop;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: Option<RunLoop>,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(Fd, &dyn Fn() -> bool) + Sync + Send + 'static,
+ T: 'static,
+ {
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb);
+
+ // Start polling for new devices.
+ try_or!(monitor.run(alive), |_| callback
+ .call(Err(errors::AuthenticatorError::Platform)));
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotAllowed,
+ )));
+ },
+ timeout,
+ )
+ .map_err(|_| errors::AuthenticatorError::Platform)?;
+
+ Ok(Self {
+ thread: Some(thread),
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ // This must never be None.
+ self.thread.take().unwrap().cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/netbsd/uhid.rs b/third_party/rust/authenticator/src/netbsd/uhid.rs
new file mode 100644
index 0000000000..f8d711553d
--- /dev/null
+++ b/third_party/rust/authenticator/src/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::hidproto::has_fido_usage;
+use crate::hidproto::ReportDescriptor;
+use crate::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/openbsd/device.rs b/third_party/rust/authenticator/src/openbsd/device.rs
new file mode 100644
index 0000000000..2238e034e2
--- /dev/null
+++ b/third_party/rust/authenticator/src/openbsd/device.rs
@@ -0,0 +1,148 @@
+/* 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::OsString;
+use std::io;
+use std::io::{Read, Result, Write};
+use std::mem;
+
+use crate::consts::{CID_BROADCAST, MAX_HID_RPT_SIZE};
+use crate::platform::monitor::FidoDev;
+use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
+use crate::util::{from_unix_result, io_err};
+
+#[derive(Debug)]
+pub struct Device {
+ path: OsString,
+ fd: libc::c_int,
+ cid: [u8; 4],
+ out_len: usize,
+ dev_info: Option<U2FDeviceInfo>,
+}
+
+impl Device {
+ pub fn new(fido: FidoDev) -> Result<Self> {
+ debug!("device found: {:?}", fido);
+ Ok(Self {
+ path: fido.os_path,
+ fd: fido.fd,
+ cid: CID_BROADCAST,
+ out_len: 64,
+ dev_info: None,
+ })
+ }
+
+ pub 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 ping(&mut self) -> Result<()> {
+ let capacity = 256;
+
+ for _ in 0..10 {
+ let mut data = vec![0u8; capacity];
+
+ // Send 1 byte ping
+ 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 Read for Device {
+ fn read(&mut self, buf: &mut [u8]) -> 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]) -> 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) -> Result<()> {
+ Ok(())
+ }
+}
+
+impl U2FDevice for Device {
+ 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);
+ }
+}
diff --git a/third_party/rust/authenticator/src/openbsd/mod.rs b/third_party/rust/authenticator/src/openbsd/mod.rs
new file mode 100644
index 0000000000..fa02132e67
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/openbsd/monitor.rs b/third_party/rust/authenticator/src/openbsd/monitor.rs
new file mode 100644
index 0000000000..2f3930497f
--- /dev/null
+++ b/third_party/rust/authenticator/src/openbsd/monitor.rs
@@ -0,0 +1,111 @@
+/* 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::collections::HashMap;
+use std::ffi::{CString, OsString};
+use std::io;
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::io::RawFd;
+use std::path::PathBuf;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+
+use crate::util::from_unix_result;
+use runloop::RunLoop;
+
+const POLL_TIMEOUT: u64 = 500;
+
+#[derive(Debug)]
+pub struct FidoDev {
+ pub fd: RawFd,
+ pub os_path: OsString,
+}
+
+pub struct Monitor<F>
+where
+ F: Fn(FidoDev, &dyn Fn() -> bool) + Sync,
+{
+ runloops: HashMap<OsString, RunLoop>,
+ new_device_cb: Arc<F>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(FidoDev, &dyn Fn() -> bool) + Send + Sync + 'static,
+{
+ pub fn new(new_device_cb: F) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
+ // 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.
+ self.add_device(FidoDev { 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: FidoDev) {
+ if !self.runloops.contains_key(&fido.os_path) {
+ let f = self.new_device_cb.clone();
+ let key = fido.os_path.clone();
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(fido, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+ }
+
+ fn remove_device(&mut self, path: OsString) {
+ 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/openbsd/transaction.rs b/third_party/rust/authenticator/src/openbsd/transaction.rs
new file mode 100644
index 0000000000..4b85db2785
--- /dev/null
+++ b/third_party/rust/authenticator/src/openbsd/transaction.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::errors;
+use crate::platform::monitor::{FidoDev, Monitor};
+use crate::statecallback::StateCallback;
+use runloop::RunLoop;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: Option<RunLoop>,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(FidoDev, &dyn Fn() -> bool) + Sync + Send + 'static,
+ T: 'static,
+ {
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb);
+
+ // Start polling for new devices.
+ try_or!(monitor.run(alive), |_| callback
+ .call(Err(errors::AuthenticatorError::Platform)));
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotAllowed,
+ )));
+ },
+ timeout,
+ )
+ .map_err(|_| errors::AuthenticatorError::Platform)?;
+
+ Ok(Self {
+ thread: Some(thread),
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ // This must never be None.
+ self.thread.take().unwrap().cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/statecallback.rs b/third_party/rust/authenticator/src/statecallback.rs
new file mode 100644
index 0000000000..ce1caf3e7c
--- /dev/null
+++ b/third_party/rust/authenticator/src/statecallback.rs
@@ -0,0 +1,166 @@
+/* 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::sync::{Arc, Condvar, Mutex};
+
+pub struct StateCallback<T> {
+ callback: Arc<Mutex<Option<Box<dyn Fn(T) + Send>>>>,
+ observer: Arc<Mutex<Option<Box<dyn Fn() + Send>>>>,
+ condition: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl<T> StateCallback<T> {
+ // This is used for the Condvar, which requires this kind of construction
+ #[allow(clippy::mutex_atomic)]
+ pub fn new(cb: Box<dyn Fn(T) + Send>) -> Self {
+ Self {
+ callback: Arc::new(Mutex::new(Some(cb))),
+ observer: Arc::new(Mutex::new(None)),
+ condition: Arc::new((Mutex::new(true), Condvar::new())),
+ }
+ }
+
+ pub fn add_uncloneable_observer(&mut self, obs: Box<dyn Fn() + Send>) {
+ let mut opt = self.observer.lock().unwrap();
+ if opt.is_some() {
+ error!("Replacing an already-set observer.")
+ }
+ opt.replace(obs);
+ }
+
+ pub fn call(&self, rv: T) {
+ if let Some(cb) = self.callback.lock().unwrap().take() {
+ cb(rv);
+
+ if let Some(obs) = self.observer.lock().unwrap().take() {
+ obs();
+ }
+ }
+
+ let (lock, cvar) = &*self.condition;
+ let mut pending = lock.lock().unwrap();
+ *pending = false;
+ cvar.notify_all();
+ }
+
+ pub fn wait(&self) {
+ let (lock, cvar) = &*self.condition;
+ let _useless_guard = cvar
+ .wait_while(lock.lock().unwrap(), |pending| *pending)
+ .unwrap();
+ }
+}
+
+impl<T> Clone for StateCallback<T> {
+ fn clone(&self) -> Self {
+ Self {
+ callback: self.callback.clone(),
+ observer: Arc::new(Mutex::new(None)),
+ condition: self.condition.clone(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::StateCallback;
+ use std::sync::atomic::{AtomicUsize, Ordering};
+ use std::sync::{Arc, Barrier};
+ use std::thread;
+
+ #[test]
+ fn test_statecallback_is_single_use() {
+ let counter = Arc::new(AtomicUsize::new(0));
+ let counter_clone = counter.clone();
+ let sc = StateCallback::new(Box::new(move |_| {
+ counter_clone.fetch_add(1, Ordering::SeqCst);
+ }));
+
+ assert_eq!(counter.load(Ordering::SeqCst), 0);
+ for _ in 0..10 {
+ sc.call(());
+ assert_eq!(counter.load(Ordering::SeqCst), 1);
+ }
+
+ for _ in 0..10 {
+ sc.clone().call(());
+ assert_eq!(counter.load(Ordering::SeqCst), 1);
+ }
+ }
+
+ #[test]
+ fn test_statecallback_observer_is_single_use() {
+ let counter = Arc::new(AtomicUsize::new(0));
+ let counter_clone = counter.clone();
+ let mut sc = StateCallback::<()>::new(Box::new(move |_| {}));
+
+ sc.add_uncloneable_observer(Box::new(move || {
+ counter_clone.fetch_add(1, Ordering::SeqCst);
+ }));
+
+ assert_eq!(counter.load(Ordering::SeqCst), 0);
+ for _ in 0..10 {
+ sc.call(());
+ assert_eq!(counter.load(Ordering::SeqCst), 1);
+ }
+
+ for _ in 0..10 {
+ sc.clone().call(());
+ assert_eq!(counter.load(Ordering::SeqCst), 1);
+ }
+ }
+
+ #[test]
+ fn test_statecallback_observer_only_runs_for_completing_callback() {
+ let cb_counter = Arc::new(AtomicUsize::new(0));
+ let cb_counter_clone = cb_counter.clone();
+ let sc = StateCallback::new(Box::new(move |_| {
+ cb_counter_clone.fetch_add(1, Ordering::SeqCst);
+ }));
+
+ let obs_counter = Arc::new(AtomicUsize::new(0));
+
+ for _ in 0..10 {
+ let obs_counter_clone = obs_counter.clone();
+ let mut c = sc.clone();
+ c.add_uncloneable_observer(Box::new(move || {
+ obs_counter_clone.fetch_add(1, Ordering::SeqCst);
+ }));
+
+ c.call(());
+
+ assert_eq!(cb_counter.load(Ordering::SeqCst), 1);
+ assert_eq!(obs_counter.load(Ordering::SeqCst), 1);
+ }
+ }
+
+ #[test]
+ #[allow(clippy::redundant_clone)]
+ fn test_statecallback_observer_unclonable() {
+ let mut sc = StateCallback::<()>::new(Box::new(move |_| {}));
+ sc.add_uncloneable_observer(Box::new(move || {}));
+
+ assert!(sc.observer.lock().unwrap().is_some());
+ // This is deliberate, to force an extra clone
+ assert!(sc.clone().observer.lock().unwrap().is_none());
+ }
+
+ #[test]
+ fn test_statecallback_wait() {
+ let sc = StateCallback::<()>::new(Box::new(move |_| {}));
+ let barrier = Arc::new(Barrier::new(2));
+
+ {
+ let c = sc.clone();
+ let b = barrier.clone();
+ thread::spawn(move || {
+ b.wait();
+ c.call(());
+ });
+ }
+
+ barrier.wait();
+ sc.wait();
+ }
+}
diff --git a/third_party/rust/authenticator/src/statemachine.rs b/third_party/rust/authenticator/src/statemachine.rs
new file mode 100644
index 0000000000..32552f8d0a
--- /dev/null
+++ b/third_party/rust/authenticator/src/statemachine.rs
@@ -0,0 +1,283 @@
+/* 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::PARAMETER_SIZE;
+use crate::errors;
+use crate::platform::device::Device;
+use crate::platform::transaction::Transaction;
+use crate::statecallback::StateCallback;
+use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
+use crate::u2ftypes::U2FDevice;
+
+use std::sync::mpsc::Sender;
+use std::sync::Mutex;
+use std::thread;
+use std::time::Duration;
+
+fn is_valid_transport(transports: crate::AuthenticatorTransports) -> bool {
+ transports.is_empty() || transports.contains(crate::AuthenticatorTransports::USB)
+}
+
+fn find_valid_key_handles<'a, F>(
+ app_ids: &'a [crate::AppId],
+ key_handles: &'a [crate::KeyHandle],
+ mut is_valid: F,
+) -> (&'a crate::AppId, Vec<&'a crate::KeyHandle>)
+where
+ F: FnMut(&Vec<u8>, &crate::KeyHandle) -> bool,
+{
+ // Try all given app_ids in order.
+ for app_id in app_ids {
+ // Find all valid key handles for the current app_id.
+ let valid_handles = key_handles
+ .iter()
+ .filter(|key_handle| is_valid(app_id, key_handle))
+ .collect::<Vec<_>>();
+
+ // If there's at least one, stop.
+ if !valid_handles.is_empty() {
+ return (app_id, valid_handles);
+ }
+ }
+
+ (&app_ids[0], vec![])
+}
+
+fn send_status(status_mutex: &Mutex<Sender<crate::StatusUpdate>>, msg: crate::StatusUpdate) {
+ match status_mutex.lock() {
+ Ok(s) => match s.send(msg) {
+ Ok(_) => {}
+ Err(e) => error!("Couldn't send status: {:?}", e),
+ },
+ Err(e) => {
+ error!("Couldn't obtain status mutex: {:?}", e);
+ }
+ };
+}
+
+#[derive(Default)]
+pub struct StateMachine {
+ transaction: Option<Transaction>,
+}
+
+impl StateMachine {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ pub fn register(
+ &mut self,
+ flags: crate::RegisterFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ application: crate::AppId,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::RegisterResult>>,
+ ) {
+ // Abort any prior register/sign calls.
+ self.cancel();
+
+ let cbc = callback.clone();
+ let status_mutex = Mutex::new(status);
+
+ let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
+ // Create a new device.
+ let dev = &mut match Device::new(info) {
+ Ok(dev) => dev,
+ _ => return,
+ };
+
+ // Try initializing it.
+ if !dev.is_u2f() || !u2f_init_device(dev) {
+ return;
+ }
+
+ // We currently support none of the authenticator selection
+ // criteria because we can't ask tokens whether they do support
+ // those features. If flags are set, ignore all tokens for now.
+ //
+ // Technically, this is a ConstraintError because we shouldn't talk
+ // to this authenticator in the first place. But the result is the
+ // same anyway.
+ if !flags.is_empty() {
+ return;
+ }
+
+ send_status(
+ &status_mutex,
+ crate::StatusUpdate::DeviceAvailable {
+ dev_info: dev.get_device_info(),
+ },
+ );
+
+ // Iterate the exclude list and see if there are any matches.
+ // If so, we'll keep polling the device anyway to test for user
+ // consent, to be consistent with CTAP2 device behavior.
+ let excluded = key_handles.iter().any(|key_handle| {
+ is_valid_transport(key_handle.transports)
+ && u2f_is_keyhandle_valid(dev, &challenge, &application, &key_handle.credential)
+ .unwrap_or(false) /* no match on failure */
+ });
+
+ while alive() {
+ if excluded {
+ let blank = vec![0u8; PARAMETER_SIZE];
+ if u2f_register(dev, &blank, &blank).is_ok() {
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::InvalidState,
+ )));
+ break;
+ }
+ } else if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
+ let dev_info = dev.get_device_info();
+ send_status(
+ &status_mutex,
+ crate::StatusUpdate::Success {
+ dev_info: dev.get_device_info(),
+ },
+ );
+ callback.call(Ok((bytes, dev_info)));
+ break;
+ }
+
+ // Sleep a bit before trying again.
+ thread::sleep(Duration::from_millis(100));
+ }
+
+ send_status(
+ &status_mutex,
+ crate::StatusUpdate::DeviceUnavailable {
+ dev_info: dev.get_device_info(),
+ },
+ );
+ });
+
+ self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
+ }
+
+ pub fn sign(
+ &mut self,
+ flags: crate::SignFlags,
+ timeout: u64,
+ challenge: Vec<u8>,
+ app_ids: Vec<crate::AppId>,
+ key_handles: Vec<crate::KeyHandle>,
+ status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::SignResult>>,
+ ) {
+ // Abort any prior register/sign calls.
+ self.cancel();
+
+ let cbc = callback.clone();
+
+ let status_mutex = Mutex::new(status);
+
+ let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
+ // Create a new device.
+ let dev = &mut match Device::new(info) {
+ Ok(dev) => dev,
+ _ => return,
+ };
+
+ // Try initializing it.
+ if !dev.is_u2f() || !u2f_init_device(dev) {
+ return;
+ }
+
+ // We currently don't support user verification because we can't
+ // ask tokens whether they do support that. If the flag is set,
+ // ignore all tokens for now.
+ //
+ // Technically, this is a ConstraintError because we shouldn't talk
+ // to this authenticator in the first place. But the result is the
+ // same anyway.
+ if !flags.is_empty() {
+ return;
+ }
+
+ // For each appId, try all key handles. If there's at least one
+ // valid key handle for an appId, we'll use that appId below.
+ let (app_id, valid_handles) =
+ find_valid_key_handles(&app_ids, &key_handles, |app_id, key_handle| {
+ u2f_is_keyhandle_valid(dev, &challenge, app_id, &key_handle.credential)
+ .unwrap_or(false) /* no match on failure */
+ });
+
+ // Aggregate distinct transports from all given credentials.
+ let transports = key_handles
+ .iter()
+ .fold(crate::AuthenticatorTransports::empty(), |t, k| {
+ t | k.transports
+ });
+
+ // We currently only support USB. If the RP specifies transports
+ // and doesn't include USB it's probably lying.
+ if !is_valid_transport(transports) {
+ return;
+ }
+
+ send_status(
+ &status_mutex,
+ crate::StatusUpdate::DeviceAvailable {
+ dev_info: dev.get_device_info(),
+ },
+ );
+
+ 'outer: while alive() {
+ // If the device matches none of the given key handles
+ // then just make it blink with bogus data.
+ if valid_handles.is_empty() {
+ let blank = vec![0u8; PARAMETER_SIZE];
+ if u2f_register(dev, &blank, &blank).is_ok() {
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::InvalidState,
+ )));
+ break;
+ }
+ } else {
+ // Otherwise, try to sign.
+ for key_handle in &valid_handles {
+ if let Ok(bytes) = u2f_sign(dev, &challenge, app_id, &key_handle.credential)
+ {
+ let dev_info = dev.get_device_info();
+ send_status(
+ &status_mutex,
+ crate::StatusUpdate::Success {
+ dev_info: dev.get_device_info(),
+ },
+ );
+ callback.call(Ok((
+ app_id.clone(),
+ key_handle.credential.clone(),
+ bytes,
+ dev_info,
+ )));
+ break 'outer;
+ }
+ }
+ }
+
+ // Sleep a bit before trying again.
+ thread::sleep(Duration::from_millis(100));
+ }
+
+ send_status(
+ &status_mutex,
+ crate::StatusUpdate::DeviceUnavailable {
+ dev_info: dev.get_device_info(),
+ },
+ );
+ });
+
+ self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
+ }
+
+ // This blocks.
+ pub fn cancel(&mut self) {
+ if let Some(mut transaction) = self.transaction.take() {
+ transaction.cancel();
+ }
+ }
+}
diff --git a/third_party/rust/authenticator/src/stub/device.rs b/third_party/rust/authenticator/src/stub/device.rs
new file mode 100644
index 0000000000..283da8ed66
--- /dev/null
+++ b/third_party/rust/authenticator/src/stub/device.rs
@@ -0,0 +1,65 @@
+/* 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::u2ftypes::{U2FDevice, U2FDeviceInfo};
+use std::io;
+use std::io::{Read, Write};
+
+pub struct Device {}
+
+impl Device {
+ pub fn new(path: String) -> io::Result<Self> {
+ panic!("not implemented");
+ }
+
+ pub fn is_u2f(&self) -> bool {
+ panic!("not implemented");
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ panic!("not implemented");
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ panic!("not implemented");
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ panic!("not implemented");
+ }
+}
+
+impl U2FDevice for Device {
+ fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
+ panic!("not implemented");
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ panic!("not implemented");
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ panic!("not implemented");
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ panic!("not implemented");
+ }
+
+ fn get_property(&self, prop_name: &str) -> io::Result<String> {
+ panic!("not implemented")
+ }
+
+ fn get_device_info(&self) -> U2FDeviceInfo {
+ panic!("not implemented")
+ }
+
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
+ panic!("not implemented")
+ }
+}
diff --git a/third_party/rust/authenticator/src/stub/mod.rs b/third_party/rust/authenticator/src/stub/mod.rs
new file mode 100644
index 0000000000..0fab62d495
--- /dev/null
+++ b/third_party/rust/authenticator/src/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/stub/transaction.rs b/third_party/rust/authenticator/src/stub/transaction.rs
new file mode 100644
index 0000000000..bdf48ef56d
--- /dev/null
+++ b/third_party/rust/authenticator/src/stub/transaction.rs
@@ -0,0 +1,31 @@
+/* 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;
+
+pub struct Transaction {}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(String, &dyn Fn() -> bool),
+ {
+ 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/u2fhid-capi.h b/third_party/rust/authenticator/src/u2fhid-capi.h
new file mode 100644
index 0000000000..bb81b28f5f
--- /dev/null
+++ b/third_party/rust/authenticator/src/u2fhid-capi.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=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/. */
+
+#ifndef __U2FHID_CAPI
+#define __U2FHID_CAPI
+#include <stdlib.h>
+#include "nsString.h"
+
+extern "C" {
+
+const uint8_t U2F_RESBUF_ID_REGISTRATION = 0;
+const uint8_t U2F_RESBUF_ID_KEYHANDLE = 1;
+const uint8_t U2F_RESBUF_ID_SIGNATURE = 2;
+const uint8_t U2F_RESBUF_ID_APPID = 3;
+const uint8_t U2F_RESBUF_ID_VENDOR_NAME = 4;
+const uint8_t U2F_RESBUF_ID_DEVICE_NAME = 5;
+const uint8_t U2F_RESBUF_ID_FIRMWARE_MAJOR = 6;
+const uint8_t U2F_RESBUF_ID_FIRMWARE_MINOR = 7;
+const uint8_t U2F_RESBUF_ID_FIRMWARE_BUILD = 8;
+
+const uint64_t U2F_FLAG_REQUIRE_RESIDENT_KEY = 1;
+const uint64_t U2F_FLAG_REQUIRE_USER_VERIFICATION = 2;
+const uint64_t U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT = 4;
+
+const uint8_t U2F_AUTHENTICATOR_TRANSPORT_USB = 1;
+const uint8_t U2F_AUTHENTICATOR_TRANSPORT_NFC = 2;
+const uint8_t U2F_AUTHENTICATOR_TRANSPORT_BLE = 4;
+const uint8_t CTAP_AUTHENTICATOR_TRANSPORT_INTERNAL = 8;
+
+const uint8_t U2F_ERROR_UKNOWN = 1;
+const uint8_t U2F_ERROR_NOT_SUPPORTED = 2;
+const uint8_t U2F_ERROR_INVALID_STATE = 3;
+const uint8_t U2F_ERROR_CONSTRAINT = 4;
+const uint8_t U2F_ERROR_NOT_ALLOWED = 5;
+
+// NOTE: Preconditions
+// * All rust_u2f_mgr* pointers must refer to pointers which are returned
+// by rust_u2f_mgr_new, and must be freed with rust_u2f_mgr_free.
+// * All rust_u2f_khs* pointers must refer to pointers which are returned
+// by rust_u2f_khs_new, and must be freed with rust_u2f_khs_free.
+// * All rust_u2f_res* pointers must refer to pointers passed to the
+// register() and sign() callbacks. They can be null on failure.
+
+// The `rust_u2f_mgr` opaque type is equivalent to the rust type `U2FManager`
+struct rust_u2f_manager;
+
+// The `rust_u2f_app_ids` opaque type is equivalent to the rust type `U2FAppIds`
+struct rust_u2f_app_ids;
+
+// The `rust_u2f_key_handles` opaque type is equivalent to the rust type
+// `U2FKeyHandles`
+struct rust_u2f_key_handles;
+
+// The `rust_u2f_res` opaque type is equivalent to the rust type `U2FResult`
+struct rust_u2f_result;
+
+// The callback passed to register() and sign().
+typedef void (*rust_u2f_callback)(uint64_t, rust_u2f_result*);
+
+/// U2FManager functions.
+
+rust_u2f_manager* rust_u2f_mgr_new();
+/* unsafe */ void rust_u2f_mgr_free(rust_u2f_manager* mgr);
+
+uint64_t rust_u2f_mgr_register(rust_u2f_manager* mgr, uint64_t flags,
+ uint64_t timeout, rust_u2f_callback,
+ const uint8_t* challenge_ptr,
+ size_t challenge_len,
+ const uint8_t* application_ptr,
+ size_t application_len,
+ const rust_u2f_key_handles* khs);
+
+uint64_t rust_u2f_mgr_sign(rust_u2f_manager* mgr, uint64_t flags,
+ uint64_t timeout, rust_u2f_callback,
+ const uint8_t* challenge_ptr, size_t challenge_len,
+ const rust_u2f_app_ids* app_ids,
+ const rust_u2f_key_handles* khs);
+
+void rust_u2f_mgr_cancel(rust_u2f_manager* mgr);
+
+/// U2FAppIds functions.
+
+rust_u2f_app_ids* rust_u2f_app_ids_new();
+void rust_u2f_app_ids_add(rust_u2f_app_ids* ids, const uint8_t* id,
+ size_t id_len);
+/* unsafe */ void rust_u2f_app_ids_free(rust_u2f_app_ids* ids);
+
+/// U2FKeyHandles functions.
+
+rust_u2f_key_handles* rust_u2f_khs_new();
+void rust_u2f_khs_add(rust_u2f_key_handles* khs, const uint8_t* key_handle,
+ size_t key_handle_len, uint8_t transports);
+/* unsafe */ void rust_u2f_khs_free(rust_u2f_key_handles* khs);
+
+/// U2FResult functions.
+
+// Returns 0 for success, or the U2F_ERROR error code >= 1.
+uint8_t rust_u2f_result_error(const rust_u2f_result* res);
+
+// Call this before `[..]_copy()` to allocate enough space.
+bool rust_u2f_resbuf_length(const rust_u2f_result* res, uint8_t bid,
+ size_t* len);
+bool rust_u2f_resbuf_copy(const rust_u2f_result* res, uint8_t bid,
+ uint8_t* dst);
+/* unsafe */ void rust_u2f_res_free(rust_u2f_result* res);
+}
+
+#endif // __U2FHID_CAPI
diff --git a/third_party/rust/authenticator/src/u2fprotocol.rs b/third_party/rust/authenticator/src/u2fprotocol.rs
new file mode 100644
index 0000000000..ca9f704387
--- /dev/null
+++ b/third_party/rust/authenticator/src/u2fprotocol.rs
@@ -0,0 +1,457 @@
+/* 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::needless_lifetimes))]
+
+extern crate std;
+
+use rand::{thread_rng, RngCore};
+use std::ffi::CString;
+use std::io;
+use std::io::{Read, Write};
+
+use crate::consts::*;
+use crate::u2ftypes::*;
+use crate::util::io_err;
+
+////////////////////////////////////////////////////////////////////////
+// Device Commands
+////////////////////////////////////////////////////////////////////////
+
+pub fn u2f_init_device<T>(dev: &mut T) -> bool
+where
+ T: U2FDevice + Read + Write,
+{
+ let mut nonce = [0u8; 8];
+ thread_rng().fill_bytes(&mut nonce);
+
+ // Initialize the device and check its version.
+ init_device(dev, &nonce).is_ok() && is_v2_device(dev).unwrap_or(false)
+}
+
+pub fn u2f_register<T>(dev: &mut T, challenge: &[u8], application: &[u8]) -> io::Result<Vec<u8>>
+where
+ T: U2FDevice + Read + Write,
+{
+ if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Invalid parameter sizes",
+ ));
+ }
+
+ let mut register_data = Vec::with_capacity(2 * PARAMETER_SIZE);
+ register_data.extend(challenge);
+ register_data.extend(application);
+
+ let flags = U2F_REQUEST_USER_PRESENCE;
+ let (resp, status) = send_apdu(dev, U2F_REGISTER, flags, &register_data)?;
+ status_word_to_result(status, resp)
+}
+
+pub fn u2f_sign<T>(
+ dev: &mut T,
+ challenge: &[u8],
+ application: &[u8],
+ key_handle: &[u8],
+) -> io::Result<Vec<u8>>
+where
+ T: U2FDevice + Read + Write,
+{
+ if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Invalid parameter sizes",
+ ));
+ }
+
+ if key_handle.len() > 256 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Key handle too large",
+ ));
+ }
+
+ let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len());
+ sign_data.extend(challenge);
+ sign_data.extend(application);
+ sign_data.push(key_handle.len() as u8);
+ sign_data.extend(key_handle);
+
+ let flags = U2F_REQUEST_USER_PRESENCE;
+ let (resp, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?;
+ status_word_to_result(status, resp)
+}
+
+pub fn u2f_is_keyhandle_valid<T>(
+ dev: &mut T,
+ challenge: &[u8],
+ application: &[u8],
+ key_handle: &[u8],
+) -> io::Result<bool>
+where
+ T: U2FDevice + Read + Write,
+{
+ if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Invalid parameter sizes",
+ ));
+ }
+
+ if key_handle.len() > 256 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Key handle too large",
+ ));
+ }
+
+ let mut sign_data = Vec::with_capacity(2 * PARAMETER_SIZE + 1 + key_handle.len());
+ sign_data.extend(challenge);
+ sign_data.extend(application);
+ sign_data.push(key_handle.len() as u8);
+ sign_data.extend(key_handle);
+
+ let flags = U2F_CHECK_IS_REGISTERED;
+ let (_, status) = send_apdu(dev, U2F_AUTHENTICATE, flags, &sign_data)?;
+ Ok(status == SW_CONDITIONS_NOT_SATISFIED)
+}
+
+////////////////////////////////////////////////////////////////////////
+// Internal Device Commands
+////////////////////////////////////////////////////////////////////////
+
+fn init_device<T>(dev: &mut T, nonce: &[u8]) -> io::Result<()>
+where
+ T: U2FDevice + Read + Write,
+{
+ assert_eq!(nonce.len(), INIT_NONCE_SIZE);
+ let raw = sendrecv(dev, U2FHID_INIT, nonce)?;
+ let rsp = U2FHIDInitResp::read(&raw, nonce)?;
+ dev.set_cid(rsp.cid);
+
+ let vendor = dev
+ .get_property("Manufacturer")
+ .unwrap_or_else(|_| String::from("Unknown Vendor"));
+ let product = dev
+ .get_property("Product")
+ .unwrap_or_else(|_| String::from("Unknown Device"));
+
+ dev.set_device_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,
+ });
+
+ Ok(())
+}
+
+fn is_v2_device<T>(dev: &mut T) -> io::Result<bool>
+where
+ T: U2FDevice + Read + Write,
+{
+ let (data, status) = send_apdu(dev, U2F_VERSION, 0x00, &[])?;
+ let actual = CString::new(data)?;
+ let expected = CString::new("U2F_V2")?;
+ status_word_to_result(status, actual == expected)
+}
+
+////////////////////////////////////////////////////////////////////////
+// Error Handling
+////////////////////////////////////////////////////////////////////////
+
+fn status_word_to_result<T>(status: [u8; 2], val: T) -> io::Result<T> {
+ use self::io::ErrorKind::{InvalidData, InvalidInput};
+
+ match status {
+ SW_NO_ERROR => Ok(val),
+ SW_WRONG_DATA => Err(io::Error::new(InvalidData, "wrong data")),
+ SW_WRONG_LENGTH => Err(io::Error::new(InvalidInput, "wrong length")),
+ SW_CONDITIONS_NOT_SATISFIED => Err(io_err("conditions not satisfied")),
+ _ => Err(io_err(&format!("failed with status {:?}", status))),
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Device Communication Functions
+////////////////////////////////////////////////////////////////////////
+
+pub fn sendrecv<T>(dev: &mut T, cmd: u8, send: &[u8]) -> io::Result<Vec<u8>>
+where
+ T: U2FDevice + Read + Write,
+{
+ // Send initialization packet.
+ let mut count = U2FHIDInit::write(dev, cmd, send)?;
+
+ // Send continuation packets.
+ let mut sequence = 0u8;
+ while count < send.len() {
+ count += U2FHIDCont::write(dev, sequence, &send[count..])?;
+ sequence += 1;
+ }
+
+ // 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 mut data = U2FHIDInit::read(dev)?;
+
+ let mut sequence = 0u8;
+ while data.len() < data.capacity() {
+ let max = data.capacity() - data.len();
+ data.extend_from_slice(&U2FHIDCont::read(dev, sequence, max)?);
+ sequence += 1;
+ }
+
+ Ok(data)
+}
+
+fn send_apdu<T>(dev: &mut T, cmd: u8, p1: u8, send: &[u8]) -> io::Result<(Vec<u8>, [u8; 2])>
+where
+ T: U2FDevice + Read + Write,
+{
+ let apdu = U2FAPDUHeader::serialize(cmd, p1, send)?;
+ let mut data = sendrecv(dev, U2FHID_MSG, &apdu)?;
+
+ if data.len() < 2 {
+ return Err(io_err("unexpected response"));
+ }
+
+ let split_at = data.len() - 2;
+ let status = data.split_off(split_at);
+ Ok((data, [status[0], status[1]]))
+}
+
+////////////////////////////////////////////////////////////////////////
+// Tests
+////////////////////////////////////////////////////////////////////////
+
+#[cfg(test)]
+mod tests {
+ use rand::{thread_rng, RngCore};
+
+ use super::{init_device, send_apdu, sendrecv, U2FDevice};
+ use crate::consts::{CID_BROADCAST, SW_NO_ERROR, U2FHID_INIT, U2FHID_MSG, U2FHID_PING};
+
+ mod platform {
+ use std::io;
+ use std::io::{Read, Write};
+
+ use crate::consts::CID_BROADCAST;
+ use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
+
+ const IN_HID_RPT_SIZE: usize = 64;
+ const OUT_HID_RPT_SIZE: usize = 64;
+
+ pub struct TestDevice {
+ cid: [u8; 4],
+ reads: Vec<[u8; IN_HID_RPT_SIZE]>,
+ writes: Vec<[u8; OUT_HID_RPT_SIZE + 1]>,
+ dev_info: Option<U2FDeviceInfo>,
+ }
+
+ impl TestDevice {
+ pub fn new() -> TestDevice {
+ TestDevice {
+ cid: CID_BROADCAST,
+ reads: vec![],
+ writes: vec![],
+ dev_info: None,
+ }
+ }
+
+ 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);
+ }
+ }
+
+ impl Write for TestDevice {
+ 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!");
+ 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 TestDevice {
+ 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 TestDevice {
+ fn drop(&mut self) {
+ assert!(self.reads.is_empty());
+ assert!(self.writes.is_empty());
+ }
+ }
+
+ impl U2FDevice for TestDevice {
+ fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
+ &self.cid
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ self.cid = cid;
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ 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!("{} not implemented", prop_name))
+ }
+ 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);
+ }
+ }
+ }
+
+ #[test]
+ fn test_init_device() {
+ let mut device = platform::TestDevice::new();
+ let nonce = vec![0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01];
+
+ // channel id
+ let mut cid = [0u8; 4];
+ thread_rng().fill_bytes(&mut cid);
+
+ // init packet
+ let mut msg = CID_BROADCAST.to_vec();
+ msg.extend(vec![U2FHID_INIT, 0x00, 0x08]); // cmd + bcnt
+ msg.extend_from_slice(&nonce);
+ device.add_write(&msg, 0);
+
+ // init_resp packet
+ let mut msg = CID_BROADCAST.to_vec();
+ msg.extend(vec![U2FHID_INIT, 0x00, 0x11]); // cmd + bcnt
+ msg.extend_from_slice(&nonce);
+ msg.extend_from_slice(&cid); // new channel id
+ msg.extend(vec![0x02, 0x04, 0x01, 0x08, 0x01]); // versions + flags
+ device.add_read(&msg, 0);
+
+ init_device(&mut device, &nonce).unwrap();
+ assert_eq!(device.get_cid(), &cid);
+
+ let dev_info = device.get_device_info();
+ assert_eq!(dev_info.version_interface, 0x02);
+ assert_eq!(dev_info.version_major, 0x04);
+ assert_eq!(dev_info.version_minor, 0x01);
+ assert_eq!(dev_info.version_build, 0x08);
+ assert_eq!(dev_info.cap_flags, 0x01);
+ }
+
+ #[test]
+ fn test_sendrecv_multiple() {
+ let mut device = platform::TestDevice::new();
+ let cid = [0x01, 0x02, 0x03, 0x04];
+ device.set_cid(cid);
+
+ // init packet
+ let mut msg = cid.to_vec();
+ msg.extend(vec![U2FHID_PING, 0x00, 0xe4]); // cmd + length = 228
+ // write msg, append [1u8; 57], 171 bytes remain
+ device.add_write(&msg, 1);
+ device.add_read(&msg, 1);
+
+ // cont packet
+ let mut msg = cid.to_vec();
+ msg.push(0x00); // seq = 0
+ // write msg, append [1u8; 59], 112 bytes remaining
+ device.add_write(&msg, 1);
+ device.add_read(&msg, 1);
+
+ // cont packet
+ let mut msg = cid.to_vec();
+ msg.push(0x01); // seq = 1
+ // write msg, append [1u8; 59], 53 bytes remaining
+ device.add_write(&msg, 1);
+ device.add_read(&msg, 1);
+
+ // cont packet
+ let mut msg = cid.to_vec();
+ msg.push(0x02); // seq = 2
+ msg.extend_from_slice(&[1u8; 53]);
+ // write msg, append remaining 53 bytes.
+ device.add_write(&msg, 0);
+ device.add_read(&msg, 0);
+
+ let data = [1u8; 228];
+ let d = sendrecv(&mut device, U2FHID_PING, &data).unwrap();
+ assert_eq!(d.len(), 228);
+ assert_eq!(d, &data[..]);
+ }
+
+ #[test]
+ fn test_sendapdu() {
+ let cid = [0x01, 0x02, 0x03, 0x04];
+ let data = [0x01, 0x02, 0x03, 0x04, 0x05];
+ let mut device = platform::TestDevice::new();
+ device.set_cid(cid);
+
+ let mut msg = cid.to_vec();
+ // sendrecv header
+ msg.extend(vec![U2FHID_MSG, 0x00, 0x0e]); // len = 14
+ // apdu header
+ msg.extend(vec![0x00, U2FHID_PING, 0xaa, 0x00, 0x00, 0x00, 0x05]);
+ // apdu data
+ msg.extend_from_slice(&data);
+ device.add_write(&msg, 0);
+
+ // Send data back
+ let mut msg = cid.to_vec();
+ msg.extend(vec![U2FHID_MSG, 0x00, 0x07]);
+ msg.extend_from_slice(&data);
+ msg.extend_from_slice(&SW_NO_ERROR);
+ device.add_read(&msg, 0);
+
+ let (result, status) = send_apdu(&mut device, U2FHID_PING, 0xaa, &data).unwrap();
+ assert_eq!(result, &data);
+ assert_eq!(status, SW_NO_ERROR);
+ }
+
+ #[test]
+ fn test_get_property() {
+ let device = platform::TestDevice::new();
+
+ assert_eq!(device.get_property("a").unwrap(), "a not implemented");
+ }
+}
diff --git a/third_party/rust/authenticator/src/u2ftypes.rs b/third_party/rust/authenticator/src/u2ftypes.rs
new file mode 100644
index 0000000000..8360e8adbd
--- /dev/null
+++ b/third_party/rust/authenticator/src/u2ftypes.rs
@@ -0,0 +1,256 @@
+/* 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::{cmp, fmt, io, str};
+
+use crate::consts::*;
+use crate::util::io_err;
+
+pub fn to_hex(data: &[u8], joiner: &str) -> String {
+ let parts: Vec<String> = data.iter().map(|byte| format!("{:02x}", byte)).collect();
+ parts.join(joiner)
+}
+
+pub fn trace_hex(data: &[u8]) {
+ if log_enabled!(log::Level::Trace) {
+ trace!("USB send: {}", to_hex(data, ""));
+ }
+}
+
+// Trait for representing U2F HID Devices. Requires getters/setters for the
+// channel ID, created during device initialization.
+pub trait U2FDevice {
+ fn get_cid(&self) -> &[u8; 4];
+ fn set_cid(&mut self, cid: [u8; 4]);
+
+ fn in_rpt_size(&self) -> usize;
+ fn in_init_data_size(&self) -> usize {
+ self.in_rpt_size() - INIT_HEADER_SIZE
+ }
+ fn in_cont_data_size(&self) -> usize {
+ self.in_rpt_size() - CONT_HEADER_SIZE
+ }
+
+ fn out_rpt_size(&self) -> usize;
+ fn out_init_data_size(&self) -> usize {
+ self.out_rpt_size() - INIT_HEADER_SIZE
+ }
+ fn out_cont_data_size(&self) -> usize {
+ self.out_rpt_size() - CONT_HEADER_SIZE
+ }
+
+ fn get_property(&self, prop_name: &str) -> io::Result<String>;
+ fn get_device_info(&self) -> U2FDeviceInfo;
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo);
+}
+
+// Init structure for U2F Communications. Tells the receiver what channel
+// communication is happening on, what command is running, and how much data to
+// expect to receive over all.
+//
+// Spec at https://fidoalliance.org/specs/fido-u2f-v1.
+// 0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.html#message--and-packet-structure
+pub struct U2FHIDInit {}
+
+impl U2FHIDInit {
+ pub fn read<T>(dev: &mut T) -> io::Result<Vec<u8>>
+ where
+ T: U2FDevice + io::Read,
+ {
+ let mut frame = vec![0u8; dev.in_rpt_size()];
+ let mut count = dev.read(&mut frame)?;
+
+ while dev.get_cid() != &frame[..4] {
+ count = dev.read(&mut frame)?;
+ }
+
+ if count != dev.in_rpt_size() {
+ return Err(io_err("invalid init packet"));
+ }
+
+ let cap = (frame[5] as usize) << 8 | (frame[6] as usize);
+ let mut data = Vec::with_capacity(cap);
+
+ let len = cmp::min(cap, dev.in_init_data_size());
+ data.extend_from_slice(&frame[7..7 + len]);
+
+ Ok(data)
+ }
+
+ pub fn write<T>(dev: &mut T, cmd: u8, data: &[u8]) -> io::Result<usize>
+ where
+ T: U2FDevice + io::Write,
+ {
+ if data.len() > 0xffff {
+ return Err(io_err("payload length > 2^16"));
+ }
+
+ let mut frame = vec![0u8; dev.out_rpt_size() + 1];
+ frame[1..5].copy_from_slice(dev.get_cid());
+ frame[5] = cmd;
+ frame[6] = (data.len() >> 8) as u8;
+ frame[7] = data.len() as u8;
+
+ let count = cmp::min(data.len(), dev.out_init_data_size());
+ frame[8..8 + count].copy_from_slice(&data[..count]);
+ trace_hex(&frame);
+
+ if dev.write(&frame)? != frame.len() {
+ return Err(io_err("device write failed"));
+ }
+
+ Ok(count)
+ }
+}
+
+// Continuation structure for U2F Communications. After an Init structure is
+// sent, continuation structures are used to transmit all extra data that
+// wouldn't fit in the initial packet. The sequence number increases with every
+// packet, until all data is received.
+//
+// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
+// html#message--and-packet-structure
+pub struct U2FHIDCont {}
+
+impl U2FHIDCont {
+ pub fn read<T>(dev: &mut T, seq: u8, max: usize) -> io::Result<Vec<u8>>
+ where
+ T: U2FDevice + io::Read,
+ {
+ let mut frame = vec![0u8; dev.in_rpt_size()];
+ let mut count = dev.read(&mut frame)?;
+
+ while dev.get_cid() != &frame[..4] {
+ count = dev.read(&mut frame)?;
+ }
+
+ if count != dev.in_rpt_size() {
+ return Err(io_err("invalid cont packet"));
+ }
+
+ if seq != frame[4] {
+ return Err(io_err("invalid sequence number"));
+ }
+
+ let max = cmp::min(max, dev.in_cont_data_size());
+ Ok(frame[5..5 + max].to_vec())
+ }
+
+ pub fn write<T>(dev: &mut T, seq: u8, data: &[u8]) -> io::Result<usize>
+ where
+ T: U2FDevice + io::Write,
+ {
+ let mut frame = vec![0u8; dev.out_rpt_size() + 1];
+ frame[1..5].copy_from_slice(dev.get_cid());
+ frame[5] = seq;
+
+ let count = cmp::min(data.len(), dev.out_cont_data_size());
+ frame[6..6 + count].copy_from_slice(&data[..count]);
+ trace_hex(&frame);
+
+ if dev.write(&frame)? != frame.len() {
+ return Err(io_err("device write failed"));
+ }
+
+ Ok(count)
+ }
+}
+
+// Reply sent after initialization command. Contains information about U2F USB
+// Key versioning, as well as the communication channel to be used for all
+// further requests.
+//
+// https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-hid-protocol.
+// html#u2fhid_init
+pub struct U2FHIDInitResp {
+ pub cid: [u8; 4],
+ pub version_interface: u8,
+ pub version_major: u8,
+ pub version_minor: u8,
+ pub version_build: u8,
+ pub cap_flags: u8,
+}
+
+impl U2FHIDInitResp {
+ pub fn read(data: &[u8], nonce: &[u8]) -> io::Result<U2FHIDInitResp> {
+ assert_eq!(nonce.len(), INIT_NONCE_SIZE);
+
+ if data.len() != INIT_NONCE_SIZE + 9 {
+ return Err(io_err("invalid init response"));
+ }
+
+ if nonce != &data[..INIT_NONCE_SIZE] {
+ return Err(io_err("invalid nonce"));
+ }
+
+ let rsp = U2FHIDInitResp {
+ cid: [
+ data[INIT_NONCE_SIZE],
+ data[INIT_NONCE_SIZE + 1],
+ data[INIT_NONCE_SIZE + 2],
+ data[INIT_NONCE_SIZE + 3],
+ ],
+ version_interface: data[INIT_NONCE_SIZE + 4],
+ version_major: data[INIT_NONCE_SIZE + 5],
+ version_minor: data[INIT_NONCE_SIZE + 6],
+ version_build: data[INIT_NONCE_SIZE + 7],
+ cap_flags: data[INIT_NONCE_SIZE + 8],
+ };
+
+ Ok(rsp)
+ }
+}
+
+// https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit
+// https://fidoalliance.org/specs/fido-u2f-v1.
+// 0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html#u2f-message-framing
+pub struct U2FAPDUHeader {}
+
+impl U2FAPDUHeader {
+ pub fn serialize(ins: u8, p1: u8, data: &[u8]) -> io::Result<Vec<u8>> {
+ if data.len() > 0xffff {
+ return Err(io_err("payload length > 2^16"));
+ }
+
+ // Size of header + data + 2 zero bytes for maximum return size.
+ let mut bytes = vec![0u8; U2FAPDUHEADER_SIZE + data.len() + 2];
+ // cla is always 0 for our requirements
+ bytes[1] = ins;
+ bytes[2] = p1;
+ // p2 is always 0, at least, for our requirements.
+ // lc[0] should always be 0.
+ bytes[5] = (data.len() >> 8) as u8;
+ bytes[6] = data.len() as u8;
+ bytes[7..7 + data.len()].copy_from_slice(data);
+
+ Ok(bytes)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct U2FDeviceInfo {
+ pub vendor_name: Vec<u8>,
+ pub device_name: Vec<u8>,
+ pub version_interface: u8,
+ pub version_major: u8,
+ pub version_minor: u8,
+ pub version_build: u8,
+ pub cap_flags: u8,
+}
+
+impl fmt::Display for U2FDeviceInfo {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "Vendor: {}, Device: {}, Interface: {}, Firmware: v{}.{}.{}, Capabilities: {}",
+ str::from_utf8(&self.vendor_name).unwrap(),
+ str::from_utf8(&self.device_name).unwrap(),
+ &self.version_interface,
+ &self.version_major,
+ &self.version_minor,
+ &self.version_build,
+ to_hex(&[self.cap_flags], ":"),
+ )
+ }
+}
diff --git a/third_party/rust/authenticator/src/util.rs b/third_party/rust/authenticator/src/util.rs
new file mode 100644
index 0000000000..4ccb3c9703
--- /dev/null
+++ b/third_party/rust/authenticator/src/util.rs
@@ -0,0 +1,67 @@
+/* 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;
+
+macro_rules! try_or {
+ ($val:expr, $or:expr) => {
+ match $val {
+ Ok(v) => v,
+ Err(e) => {
+ return $or(e);
+ }
+ }
+ };
+}
+
+pub trait Signed {
+ fn is_negative(&self) -> bool;
+}
+
+impl Signed for i32 {
+ fn is_negative(&self) -> bool {
+ *self < (0 as i32)
+ }
+}
+
+impl Signed for usize {
+ fn is_negative(&self) -> bool {
+ (*self as isize) < (0 as isize)
+ }
+}
+
+#[cfg(any(target_os = "linux"))]
+pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
+ if rv.is_negative() {
+ let errno = unsafe { *libc::__errno_location() };
+ Err(io::Error::from_raw_os_error(errno))
+ } else {
+ Ok(rv)
+ }
+}
+
+#[cfg(any(target_os = "freebsd"))]
+pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
+ if rv.is_negative() {
+ let errno = unsafe { *libc::__error() };
+ Err(io::Error::from_raw_os_error(errno))
+ } else {
+ Ok(rv)
+ }
+}
+
+#[cfg(any(target_os = "openbsd"))]
+pub fn from_unix_result<T: Signed>(rv: T) -> io::Result<T> {
+ if rv.is_negative() {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(rv)
+ }
+}
+
+pub fn io_err(msg: &str) -> io::Error {
+ io::Error::new(io::ErrorKind::Other, msg)
+}
diff --git a/third_party/rust/authenticator/src/virtualdevices/mod.rs b/third_party/rust/authenticator/src/virtualdevices/mod.rs
new file mode 100644
index 0000000000..5c0a9d39fc
--- /dev/null
+++ b/third_party/rust/authenticator/src/virtualdevices/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/. */
+
+#[cfg(feature = "webdriver")]
+pub mod webdriver;
+
+pub mod software_u2f;
diff --git a/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs b/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs
new file mode 100644
index 0000000000..a88e74de50
--- /dev/null
+++ b/third_party/rust/authenticator/src/virtualdevices/software_u2f.rs
@@ -0,0 +1,58 @@
+/* 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 struct SoftwareU2FToken {}
+
+// This is simply for platforms that aren't using the U2F Token, usually for builds
+// without --feature webdriver
+#[allow(dead_code)]
+
+impl SoftwareU2FToken {
+ pub fn new() -> SoftwareU2FToken {
+ Self {}
+ }
+
+ pub fn register(
+ &self,
+ _flags: crate::RegisterFlags,
+ _timeout: u64,
+ _challenge: Vec<u8>,
+ _application: crate::AppId,
+ _key_handles: Vec<crate::KeyHandle>,
+ ) -> crate::Result<crate::RegisterResult> {
+ Ok((vec![0u8; 16], self.dev_info()))
+ }
+
+ /// The implementation of this method must return quickly and should
+ /// report its status via the status and callback methods
+ pub fn sign(
+ &self,
+ _flags: crate::SignFlags,
+ _timeout: u64,
+ _challenge: Vec<u8>,
+ _app_ids: Vec<crate::AppId>,
+ _key_handles: Vec<crate::KeyHandle>,
+ ) -> crate::Result<crate::SignResult> {
+ Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info()))
+ }
+
+ pub fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo {
+ crate::u2ftypes::U2FDeviceInfo {
+ vendor_name: b"Mozilla".to_vec(),
+ device_name: b"Authenticator Webdriver Token".to_vec(),
+ version_interface: 0,
+ version_major: 1,
+ version_minor: 2,
+ version_build: 3,
+ cap_flags: 0,
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////
+// Tests
+////////////////////////////////////////////////////////////////////////
+
+#[cfg(test)]
+mod tests {}
diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/mod.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/mod.rs
new file mode 100644
index 0000000000..b1ef27d813
--- /dev/null
+++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/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/. */
+
+mod testtoken;
+mod virtualmanager;
+mod web_api;
+
+pub use virtualmanager::VirtualManager;
diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs
new file mode 100644
index 0000000000..9bf60bbaf5
--- /dev/null
+++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/testtoken.rs
@@ -0,0 +1,140 @@
+/* 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::virtualdevices::software_u2f::SoftwareU2FToken;
+use crate::{RegisterFlags, RegisterResult, SignFlags, SignResult};
+
+pub enum TestWireProtocol {
+ CTAP1,
+ CTAP2,
+}
+
+impl TestWireProtocol {
+ pub fn to_webdriver_string(&self) -> String {
+ match self {
+ TestWireProtocol::CTAP1 => "ctap1/u2f".to_string(),
+ TestWireProtocol::CTAP2 => "ctap2".to_string(),
+ }
+ }
+}
+
+pub struct TestTokenCredential {
+ pub credential: Vec<u8>,
+ pub privkey: Vec<u8>,
+ pub user_handle: Vec<u8>,
+ pub sign_count: u64,
+ pub is_resident_credential: bool,
+ pub rp_id: String,
+}
+
+pub struct TestToken {
+ pub id: u64,
+ pub protocol: TestWireProtocol,
+ pub transport: String,
+ pub is_user_consenting: bool,
+ pub has_user_verification: bool,
+ pub is_user_verified: bool,
+ pub has_resident_key: bool,
+ pub u2f_impl: Option<SoftwareU2FToken>,
+ pub credentials: Vec<TestTokenCredential>,
+}
+
+impl TestToken {
+ pub fn new(
+ id: u64,
+ protocol: TestWireProtocol,
+ transport: String,
+ is_user_consenting: bool,
+ has_user_verification: bool,
+ is_user_verified: bool,
+ has_resident_key: bool,
+ ) -> TestToken {
+ match protocol {
+ TestWireProtocol::CTAP1 => Self {
+ id,
+ protocol,
+ transport,
+ is_user_consenting,
+ has_user_verification,
+ is_user_verified,
+ has_resident_key,
+ u2f_impl: Some(SoftwareU2FToken::new()),
+ credentials: Vec::new(),
+ },
+ _ => unreachable!(),
+ }
+ }
+
+ pub fn insert_credential(
+ &mut self,
+ credential: &[u8],
+ privkey: &[u8],
+ rp_id: String,
+ is_resident_credential: bool,
+ user_handle: &[u8],
+ sign_count: u64,
+ ) {
+ let c = TestTokenCredential {
+ credential: credential.to_vec(),
+ privkey: privkey.to_vec(),
+ rp_id,
+ is_resident_credential,
+ user_handle: user_handle.to_vec(),
+ sign_count,
+ };
+
+ match self
+ .credentials
+ .binary_search_by_key(&credential, |probe| &probe.credential)
+ {
+ Ok(_) => {}
+ Err(idx) => self.credentials.insert(idx, c),
+ }
+ }
+
+ pub fn delete_credential(&mut self, credential: &[u8]) -> bool {
+ debug!("Asking to delete credential",);
+ if let Ok(idx) = self
+ .credentials
+ .binary_search_by_key(&credential, |probe| &probe.credential)
+ {
+ debug!("Asking to delete credential from idx {}", idx);
+ self.credentials.remove(idx);
+ return true;
+ }
+
+ false
+ }
+
+ pub fn register(&self) -> crate::Result<RegisterResult> {
+ if self.u2f_impl.is_some() {
+ return self.u2f_impl.as_ref().unwrap().register(
+ RegisterFlags::empty(),
+ 10_000,
+ vec![0; 32],
+ vec![0; 32],
+ vec![],
+ );
+ }
+ Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::Unknown,
+ ))
+ }
+
+ pub fn sign(&self) -> crate::Result<SignResult> {
+ if self.u2f_impl.is_some() {
+ return self.u2f_impl.as_ref().unwrap().sign(
+ SignFlags::empty(),
+ 10_000,
+ vec![0; 32],
+ vec![vec![0; 32]],
+ vec![],
+ );
+ }
+ Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::Unknown,
+ ))
+ }
+}
diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs
new file mode 100644
index 0000000000..dc26df07ee
--- /dev/null
+++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/virtualmanager.rs
@@ -0,0 +1,157 @@
+/* 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 runloop::RunLoop;
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+use std::ops::Deref;
+use std::sync::mpsc::Sender;
+use std::sync::{Arc, Mutex};
+use std::vec;
+use std::{io, string, thread};
+
+use crate::authenticatorservice::AuthenticatorTransport;
+use crate::errors;
+use crate::statecallback::StateCallback;
+use crate::virtualdevices::webdriver::{testtoken, web_api};
+
+pub struct VirtualManagerState {
+ pub authenticator_counter: u64,
+ pub tokens: vec::Vec<testtoken::TestToken>,
+}
+
+impl VirtualManagerState {
+ pub fn new() -> Arc<Mutex<VirtualManagerState>> {
+ Arc::new(Mutex::new(VirtualManagerState {
+ authenticator_counter: 0,
+ tokens: vec![],
+ }))
+ }
+}
+
+pub struct VirtualManager {
+ addr: SocketAddr,
+ state: Arc<Mutex<VirtualManagerState>>,
+ rloop: Option<RunLoop>,
+}
+
+impl VirtualManager {
+ pub fn new() -> io::Result<Self> {
+ let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080);
+ let state = VirtualManagerState::new();
+ let stateclone = state.clone();
+
+ let builder = thread::Builder::new().name("WebDriver Command Server".into());
+ builder.spawn(move || {
+ web_api::serve(stateclone, addr);
+ })?;
+
+ Ok(Self {
+ addr,
+ state,
+ rloop: None,
+ })
+ }
+
+ pub fn url(&self) -> string::String {
+ format!("http://{}/webauthn/authenticator", &self.addr)
+ }
+}
+
+impl AuthenticatorTransport for VirtualManager {
+ fn register(
+ &mut self,
+ _flags: crate::RegisterFlags,
+ timeout: u64,
+ _challenge: Vec<u8>,
+ _application: crate::AppId,
+ _key_handles: Vec<crate::KeyHandle>,
+ _status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::RegisterResult>>,
+ ) -> crate::Result<()> {
+ if self.rloop.is_some() {
+ error!("WebDriver state error, prior operation never cancelled.");
+ return Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::Unknown,
+ ));
+ }
+
+ let state = self.state.clone();
+ let rloop = try_or!(
+ RunLoop::new_with_timeout(
+ move |alive| {
+ while alive() {
+ let state_obj = state.lock().unwrap();
+
+ for token in state_obj.tokens.deref() {
+ if token.is_user_consenting {
+ let register_result = token.register();
+ thread::spawn(move || {
+ callback.call(register_result);
+ });
+ return;
+ }
+ }
+ }
+ },
+ timeout
+ ),
+ |_| Err(errors::AuthenticatorError::Platform)
+ );
+
+ self.rloop = Some(rloop);
+ Ok(())
+ }
+
+ fn sign(
+ &mut self,
+ _flags: crate::SignFlags,
+ timeout: u64,
+ _challenge: Vec<u8>,
+ _app_ids: Vec<crate::AppId>,
+ _key_handles: Vec<crate::KeyHandle>,
+ _status: Sender<crate::StatusUpdate>,
+ callback: StateCallback<crate::Result<crate::SignResult>>,
+ ) -> crate::Result<()> {
+ if self.rloop.is_some() {
+ error!("WebDriver state error, prior operation never cancelled.");
+ return Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::Unknown,
+ ));
+ }
+
+ let state = self.state.clone();
+ let rloop = try_or!(
+ RunLoop::new_with_timeout(
+ move |alive| {
+ while alive() {
+ let state_obj = state.lock().unwrap();
+
+ for token in state_obj.tokens.deref() {
+ if token.is_user_consenting {
+ let sign_result = token.sign();
+ thread::spawn(move || {
+ callback.call(sign_result);
+ });
+ return;
+ }
+ }
+ }
+ },
+ timeout
+ ),
+ |_| Err(errors::AuthenticatorError::Platform)
+ );
+
+ self.rloop = Some(rloop);
+ Ok(())
+ }
+
+ fn cancel(&mut self) -> crate::Result<()> {
+ if let Some(r) = self.rloop.take() {
+ debug!("WebDriver operation cancelled.");
+ r.cancel();
+ }
+ Ok(())
+ }
+}
diff --git a/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs b/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs
new file mode 100644
index 0000000000..9093938664
--- /dev/null
+++ b/third_party/rust/authenticator/src/virtualdevices/webdriver/web_api.rs
@@ -0,0 +1,965 @@
+/* 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 serde::{Deserialize, Serialize};
+use std::net::SocketAddr;
+use std::string;
+use std::sync::{Arc, Mutex};
+use warp::Filter;
+
+use crate::virtualdevices::webdriver::{testtoken, virtualmanager::VirtualManagerState};
+
+fn default_as_false() -> bool {
+ false
+}
+fn default_as_true() -> bool {
+ false
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq)]
+pub struct AuthenticatorConfiguration {
+ protocol: string::String,
+ transport: string::String,
+ #[serde(rename = "hasResidentKey")]
+ #[serde(default = "default_as_false")]
+ has_resident_key: bool,
+ #[serde(rename = "hasUserVerification")]
+ #[serde(default = "default_as_false")]
+ has_user_verification: bool,
+ #[serde(rename = "isUserConsenting")]
+ #[serde(default = "default_as_true")]
+ is_user_consenting: bool,
+ #[serde(rename = "isUserVerified")]
+ #[serde(default = "default_as_false")]
+ is_user_verified: bool,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq)]
+pub struct CredentialParameters {
+ #[serde(rename = "credentialId")]
+ credential_id: String,
+ #[serde(rename = "isResidentCredential")]
+ is_resident_credential: bool,
+ #[serde(rename = "rpId")]
+ rp_id: String,
+ #[serde(rename = "privateKey")]
+ private_key: String,
+ #[serde(rename = "userHandle")]
+ #[serde(default)]
+ user_handle: String,
+ #[serde(rename = "signCount")]
+ sign_count: u64,
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq)]
+pub struct UserVerificationParameters {
+ #[serde(rename = "isUserVerified")]
+ is_user_verified: bool,
+}
+
+impl CredentialParameters {
+ fn new_from_test_token_credential(tc: &testtoken::TestTokenCredential) -> CredentialParameters {
+ let credential_id = base64::encode_config(&tc.credential, base64::URL_SAFE);
+
+ let private_key = base64::encode_config(&tc.privkey, base64::URL_SAFE);
+
+ let user_handle = base64::encode_config(&tc.user_handle, base64::URL_SAFE);
+
+ CredentialParameters {
+ credential_id,
+ is_resident_credential: tc.is_resident_credential,
+ rp_id: tc.rp_id.clone(),
+ private_key,
+ user_handle,
+ sign_count: tc.sign_count,
+ }
+ }
+}
+
+fn with_state(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = (Arc<Mutex<VirtualManagerState>>,), Error = std::convert::Infallible> + Clone
+{
+ warp::any().map(move || state.clone())
+}
+
+fn authenticator_add(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator")
+ .and(warp::post())
+ .and(warp::body::json())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_add)
+}
+
+fn authenticator_delete(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator" / u64)
+ .and(warp::delete())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_delete)
+}
+
+fn authenticator_set_uv(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator" / u64 / "uv")
+ .and(warp::post())
+ .and(warp::body::json())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_set_uv)
+}
+
+// This is not part of the specification, but it's useful for debugging
+fn authenticator_get(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator" / u64)
+ .and(warp::get())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_get)
+}
+
+fn authenticator_credential_add(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator" / u64 / "credential")
+ .and(warp::post())
+ .and(warp::body::json())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_credential_add)
+}
+
+fn authenticator_credential_delete(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator" / u64 / "credentials" / String)
+ .and(warp::delete())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_credential_delete)
+}
+
+fn authenticator_credentials_get(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator" / u64 / "credentials")
+ .and(warp::get())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_credentials_get)
+}
+
+fn authenticator_credentials_clear(
+ state: Arc<Mutex<VirtualManagerState>>,
+) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("webauthn" / "authenticator" / u64 / "credentials")
+ .and(warp::delete())
+ .and(with_state(state))
+ .and_then(handlers::authenticator_credentials_clear)
+}
+
+mod handlers {
+ use super::{CredentialParameters, UserVerificationParameters};
+ use crate::virtualdevices::webdriver::{
+ testtoken, virtualmanager::VirtualManagerState, web_api::AuthenticatorConfiguration,
+ };
+ use serde::Serialize;
+ use std::convert::Infallible;
+ use std::ops::DerefMut;
+ use std::sync::{Arc, Mutex};
+ use std::vec;
+ use warp::http::{uri, StatusCode};
+
+ #[derive(Serialize)]
+ struct JsonSuccess {}
+
+ impl JsonSuccess {
+ pub fn blank() -> JsonSuccess {
+ JsonSuccess {}
+ }
+ }
+
+ #[derive(Serialize)]
+ struct JsonError {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ line: Option<u32>,
+ error: String,
+ details: String,
+ }
+
+ impl JsonError {
+ pub fn new(error: &str, line: u32, details: &str) -> JsonError {
+ JsonError {
+ details: details.to_string(),
+ error: error.to_string(),
+ line: Some(line),
+ }
+ }
+ pub fn from_status_code(code: StatusCode) -> JsonError {
+ JsonError {
+ details: code.canonical_reason().unwrap().to_string(),
+ line: None,
+ error: "".to_string(),
+ }
+ }
+ pub fn from_error(error: &str) -> JsonError {
+ JsonError {
+ details: "".to_string(),
+ error: error.to_string(),
+ line: None,
+ }
+ }
+ }
+
+ macro_rules! reply_error {
+ ($status:expr) => {
+ warp::reply::with_status(
+ warp::reply::json(&JsonError::from_status_code($status)),
+ $status,
+ )
+ };
+ }
+
+ macro_rules! try_json {
+ ($val:expr, $status:expr) => {
+ match $val {
+ Ok(v) => v,
+ Err(e) => {
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&JsonError::new(
+ $status.canonical_reason().unwrap(),
+ line!(),
+ &e.to_string(),
+ )),
+ $status,
+ ));
+ }
+ }
+ };
+ }
+
+ pub fn validate_rp_id(rp_id: &str) -> crate::Result<()> {
+ if let Ok(uri) = rp_id.parse::<uri::Uri>().map_err(|_| {
+ crate::errors::AuthenticatorError::U2FToken(crate::errors::U2FTokenError::Unknown)
+ }) {
+ if uri.scheme().is_none()
+ && uri.path_and_query().is_none()
+ && uri.port().is_none()
+ && uri.host().is_some()
+ && uri.authority().unwrap() == uri.host().unwrap()
+ // Don't try too hard to ensure it's a valid domain, just
+ // ensure there's a label delim in there somewhere
+ && uri.host().unwrap().find('.').is_some()
+ {
+ return Ok(());
+ }
+ }
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ }
+
+ pub async fn authenticator_add(
+ auth: AuthenticatorConfiguration,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let protocol = match auth.protocol.as_str() {
+ "ctap1/u2f" => testtoken::TestWireProtocol::CTAP1,
+ "ctap2" => testtoken::TestWireProtocol::CTAP2,
+ _ => {
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&JsonError::from_error(
+ format!("unknown protocol: {}", auth.protocol).as_str(),
+ )),
+ StatusCode::BAD_REQUEST,
+ ))
+ }
+ };
+
+ let mut state_lock = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+ let mut state_obj = state_lock.deref_mut();
+ state_obj.authenticator_counter += 1;
+
+ let tt = testtoken::TestToken::new(
+ state_obj.authenticator_counter,
+ protocol,
+ auth.transport,
+ auth.is_user_consenting,
+ auth.has_user_verification,
+ auth.is_user_verified,
+ auth.has_resident_key,
+ );
+
+ match state_obj
+ .tokens
+ .binary_search_by_key(&state_obj.authenticator_counter, |probe| probe.id)
+ {
+ Ok(_) => panic!("unexpected repeat of authenticator_id"),
+ Err(idx) => state_obj.tokens.insert(idx, tt),
+ }
+
+ #[derive(Serialize)]
+ struct AddResult {
+ #[serde(rename = "authenticatorId")]
+ authenticator_id: u64,
+ }
+
+ Ok(warp::reply::with_status(
+ warp::reply::json(&AddResult {
+ authenticator_id: state_obj.authenticator_counter,
+ }),
+ StatusCode::CREATED,
+ ))
+ }
+
+ pub async fn authenticator_delete(
+ id: u64,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+ match state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
+ Ok(idx) => state_obj.tokens.remove(idx),
+ Err(_) => {
+ return Ok(reply_error!(StatusCode::NOT_FOUND));
+ }
+ };
+
+ Ok(warp::reply::with_status(
+ warp::reply::json(&JsonSuccess::blank()),
+ StatusCode::OK,
+ ))
+ }
+
+ pub async fn authenticator_get(
+ id: u64,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+ if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
+ let tt = &mut state_obj.tokens[idx];
+
+ let data = AuthenticatorConfiguration {
+ protocol: tt.protocol.to_webdriver_string(),
+ transport: tt.transport.clone(),
+ has_resident_key: tt.has_resident_key,
+ has_user_verification: tt.has_user_verification,
+ is_user_consenting: tt.is_user_consenting,
+ is_user_verified: tt.is_user_verified,
+ };
+
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&data),
+ StatusCode::OK,
+ ));
+ }
+
+ Ok(reply_error!(StatusCode::NOT_FOUND))
+ }
+
+ pub async fn authenticator_set_uv(
+ id: u64,
+ uv: UserVerificationParameters,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+
+ if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
+ let tt = &mut state_obj.tokens[idx];
+ tt.is_user_verified = uv.is_user_verified;
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&JsonSuccess::blank()),
+ StatusCode::OK,
+ ));
+ }
+
+ Ok(reply_error!(StatusCode::NOT_FOUND))
+ }
+
+ pub async fn authenticator_credential_add(
+ id: u64,
+ auth: CredentialParameters,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let credential = try_json!(
+ base64::decode_config(&auth.credential_id, base64::URL_SAFE),
+ StatusCode::BAD_REQUEST
+ );
+
+ let privkey = try_json!(
+ base64::decode_config(&auth.private_key, base64::URL_SAFE),
+ StatusCode::BAD_REQUEST
+ );
+
+ let userhandle = try_json!(
+ base64::decode_config(&auth.user_handle, base64::URL_SAFE),
+ StatusCode::BAD_REQUEST
+ );
+
+ try_json!(validate_rp_id(&auth.rp_id), StatusCode::BAD_REQUEST);
+
+ let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+ if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
+ let tt = &mut state_obj.tokens[idx];
+
+ tt.insert_credential(
+ &credential,
+ &privkey,
+ auth.rp_id,
+ auth.is_resident_credential,
+ &userhandle,
+ auth.sign_count,
+ );
+
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&JsonSuccess::blank()),
+ StatusCode::CREATED,
+ ));
+ }
+
+ Ok(reply_error!(StatusCode::NOT_FOUND))
+ }
+
+ pub async fn authenticator_credential_delete(
+ id: u64,
+ credential_id: String,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let credential = try_json!(
+ base64::decode_config(&credential_id, base64::URL_SAFE),
+ StatusCode::BAD_REQUEST
+ );
+
+ let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+
+ debug!("Asking to delete {}", &credential_id);
+
+ if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
+ let tt = &mut state_obj.tokens[idx];
+ debug!("Asking to delete from token {}", tt.id);
+ if tt.delete_credential(&credential) {
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&JsonSuccess::blank()),
+ StatusCode::OK,
+ ));
+ }
+ }
+
+ Ok(reply_error!(StatusCode::NOT_FOUND))
+ }
+
+ pub async fn authenticator_credentials_get(
+ id: u64,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+
+ if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
+ let tt = &mut state_obj.tokens[idx];
+ let mut creds: vec::Vec<CredentialParameters> = vec![];
+ for ttc in &tt.credentials {
+ creds.push(CredentialParameters::new_from_test_token_credential(ttc));
+ }
+
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&creds),
+ StatusCode::OK,
+ ));
+ }
+
+ Ok(reply_error!(StatusCode::NOT_FOUND))
+ }
+
+ pub async fn authenticator_credentials_clear(
+ id: u64,
+ state: Arc<Mutex<VirtualManagerState>>,
+ ) -> Result<impl warp::Reply, Infallible> {
+ let mut state_obj = try_json!(state.lock(), StatusCode::INTERNAL_SERVER_ERROR);
+ if let Ok(idx) = state_obj.tokens.binary_search_by_key(&id, |probe| probe.id) {
+ let tt = &mut state_obj.tokens[idx];
+
+ tt.credentials.clear();
+
+ return Ok(warp::reply::with_status(
+ warp::reply::json(&JsonSuccess::blank()),
+ StatusCode::OK,
+ ));
+ }
+
+ Ok(reply_error!(StatusCode::NOT_FOUND))
+ }
+}
+
+#[tokio::main]
+pub async fn serve(state: Arc<Mutex<VirtualManagerState>>, addr: SocketAddr) {
+ let routes = authenticator_add(state.clone())
+ .or(authenticator_delete(state.clone()))
+ .or(authenticator_get(state.clone()))
+ .or(authenticator_set_uv(state.clone()))
+ .or(authenticator_credential_add(state.clone()))
+ .or(authenticator_credential_delete(state.clone()))
+ .or(authenticator_credentials_get(state.clone()))
+ .or(authenticator_credentials_clear(state.clone()));
+
+ warp::serve(routes).run(addr).await;
+}
+
+#[cfg(test)]
+mod tests {
+ use super::handlers::validate_rp_id;
+ use super::testtoken::*;
+ use super::*;
+ use crate::virtualdevices::webdriver::virtualmanager::VirtualManagerState;
+ use bytes::Buf;
+ use std::sync::{Arc, Mutex};
+ use warp::http::StatusCode;
+
+ fn init() {
+ let _ = env_logger::builder().is_test(true).try_init();
+ }
+
+ #[test]
+ fn test_validate_rp_id() {
+ init();
+
+ assert_matches!(
+ validate_rp_id(&String::from("http://example.com")),
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ );
+ assert_matches!(
+ validate_rp_id(&String::from("https://example.com")),
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ );
+ assert_matches!(
+ validate_rp_id(&String::from("example.com:443")),
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ );
+ assert_matches!(
+ validate_rp_id(&String::from("example.com/path")),
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ );
+ assert_matches!(
+ validate_rp_id(&String::from("example.com:443/path")),
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ );
+ assert_matches!(
+ validate_rp_id(&String::from("user:pass@example.com")),
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ );
+ assert_matches!(
+ validate_rp_id(&String::from("com")),
+ Err(crate::errors::AuthenticatorError::U2FToken(
+ crate::errors::U2FTokenError::Unknown,
+ ))
+ );
+ assert_matches!(validate_rp_id(&String::from("example.com")), Ok(()));
+ }
+
+ fn mk_state_with_token_list(ids: &[u64]) -> Arc<Mutex<VirtualManagerState>> {
+ let state = VirtualManagerState::new();
+
+ {
+ let mut state_obj = state.lock().unwrap();
+ for id in ids {
+ state_obj.tokens.push(TestToken::new(
+ *id,
+ TestWireProtocol::CTAP1,
+ "internal".to_string(),
+ true,
+ true,
+ true,
+ true,
+ ));
+ }
+
+ state_obj.tokens.sort_by_key(|probe| probe.id)
+ }
+
+ state
+ }
+
+ fn assert_success_rsp_blank(body: &bytes::Bytes) {
+ assert_eq!(String::from_utf8_lossy(body.bytes()), r#"{}"#)
+ }
+
+ fn assert_creds_equals_test_token_params(
+ a: &[CredentialParameters],
+ b: &[TestTokenCredential],
+ ) {
+ assert_eq!(a.len(), b.len());
+
+ for (i, j) in a.iter().zip(b.iter()) {
+ assert_eq!(
+ i.credential_id,
+ base64::encode_config(&j.credential, base64::URL_SAFE)
+ );
+ assert_eq!(
+ i.user_handle,
+ base64::encode_config(&j.user_handle, base64::URL_SAFE)
+ );
+ assert_eq!(
+ i.private_key,
+ base64::encode_config(&j.privkey, base64::URL_SAFE)
+ );
+ assert_eq!(i.rp_id, j.rp_id);
+ assert_eq!(i.sign_count, j.sign_count);
+ assert_eq!(i.is_resident_credential, j.is_resident_credential);
+ }
+ }
+
+ #[tokio::test]
+ async fn test_authenticator_add() {
+ init();
+ let filter = authenticator_add(mk_state_with_token_list(&[]));
+
+ {
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator")
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+
+ let valid_add = AuthenticatorConfiguration {
+ protocol: "ctap1/u2f".to_string(),
+ transport: "usb".to_string(),
+ has_resident_key: false,
+ has_user_verification: false,
+ is_user_consenting: false,
+ is_user_verified: false,
+ };
+
+ {
+ let mut invalid = valid_add.clone();
+ invalid.protocol = "unknown".to_string();
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator")
+ .json(&invalid)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ assert!(String::from_utf8_lossy(res.body().bytes())
+ .contains(&String::from("unknown protocol: unknown")));
+ }
+
+ {
+ let mut unknown = valid_add.clone();
+ unknown.transport = "unknown".to_string();
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator")
+ .json(&unknown)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_success());
+ assert_eq!(
+ String::from_utf8_lossy(res.body().bytes()),
+ r#"{"authenticatorId":1}"#
+ )
+ }
+
+ {
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator")
+ .json(&valid_add)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_success());
+ assert_eq!(
+ String::from_utf8_lossy(res.body().bytes()),
+ r#"{"authenticatorId":2}"#
+ )
+ }
+ }
+
+ #[tokio::test]
+ async fn test_authenticator_delete() {
+ init();
+ let filter = authenticator_delete(mk_state_with_token_list(&[32]));
+
+ {
+ let res = warp::test::request()
+ .method("DELETE")
+ .path("/webauthn/authenticator/3")
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+
+ {
+ let res = warp::test::request()
+ .method("DELETE")
+ .path("/webauthn/authenticator/32")
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_success());
+ assert_success_rsp_blank(res.body());
+ }
+
+ {
+ let res = warp::test::request()
+ .method("DELETE")
+ .path("/webauthn/authenticator/42")
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+ }
+
+ #[tokio::test]
+ async fn test_authenticator_change_uv() {
+ init();
+ let state = mk_state_with_token_list(&[1]);
+ let filter = authenticator_set_uv(state.clone());
+
+ {
+ let state_obj = state.lock().unwrap();
+ assert_eq!(true, state_obj.tokens[0].is_user_verified);
+ }
+
+ {
+ // Empty POST is bad
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/uv")
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+
+ {
+ // Unexpected POST structure is bad
+ #[derive(Serialize)]
+ struct Unexpected {
+ id: u64,
+ }
+ let unexpected = Unexpected { id: 4 };
+
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/uv")
+ .json(&unexpected)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+
+ {
+ let param_false = UserVerificationParameters {
+ is_user_verified: false,
+ };
+
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/uv")
+ .json(&param_false)
+ .reply(&filter)
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_success_rsp_blank(res.body());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(false, state_obj.tokens[0].is_user_verified);
+ }
+
+ {
+ let param_false = UserVerificationParameters {
+ is_user_verified: true,
+ };
+
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/uv")
+ .json(&param_false)
+ .reply(&filter)
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_success_rsp_blank(res.body());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(true, state_obj.tokens[0].is_user_verified);
+ }
+ }
+
+ #[tokio::test]
+ async fn test_authenticator_credentials() {
+ init();
+ let state = mk_state_with_token_list(&[1]);
+ let filter = authenticator_credential_add(state.clone())
+ .or(authenticator_credential_delete(state.clone()))
+ .or(authenticator_credentials_get(state.clone()))
+ .or(authenticator_credentials_clear(state.clone()));
+
+ let valid_add_credential = CredentialParameters {
+ credential_id: r"c3VwZXIgcmVhZGVy".to_string(),
+ is_resident_credential: true,
+ rp_id: "valid.rpid".to_string(),
+ private_key: base64::encode_config(b"hello internet~", base64::URL_SAFE),
+ user_handle: base64::encode_config(b"hello internet~", base64::URL_SAFE),
+ sign_count: 0,
+ };
+
+ {
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/credential")
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+
+ {
+ let mut invalid = valid_add_credential.clone();
+ invalid.credential_id = "!@#$ invalid base64".to_string();
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/credential")
+ .json(&invalid)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+
+ {
+ let mut invalid = valid_add_credential.clone();
+ invalid.rp_id = "example".to_string();
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/credential")
+ .json(&invalid)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+ }
+
+ {
+ let mut invalid = valid_add_credential.clone();
+ invalid.rp_id = "https://example.com".to_string();
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/credential")
+ .json(&invalid)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_client_error());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(0, state_obj.tokens[0].credentials.len());
+ }
+
+ {
+ let mut no_user_handle = valid_add_credential.clone();
+ no_user_handle.user_handle = "".to_string();
+ no_user_handle.credential_id = "YQo=".to_string();
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/credential")
+ .json(&no_user_handle)
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_success());
+ assert_success_rsp_blank(res.body());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(1, state_obj.tokens[0].credentials.len());
+ let c = &state_obj.tokens[0].credentials[0];
+ assert!(c.user_handle.is_empty());
+ }
+
+ {
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/credential")
+ .json(&valid_add_credential)
+ .reply(&filter)
+ .await;
+ assert_eq!(res.status(), StatusCode::CREATED);
+ assert_success_rsp_blank(res.body());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(2, state_obj.tokens[0].credentials.len());
+ let c = &state_obj.tokens[0].credentials[1];
+ assert!(!c.user_handle.is_empty());
+ }
+
+ {
+ // Duplicate, should still be two credentials
+ let res = warp::test::request()
+ .method("POST")
+ .path("/webauthn/authenticator/1/credential")
+ .json(&valid_add_credential)
+ .reply(&filter)
+ .await;
+ assert_eq!(res.status(), StatusCode::CREATED);
+ assert_success_rsp_blank(res.body());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(2, state_obj.tokens[0].credentials.len());
+ }
+
+ {
+ let res = warp::test::request()
+ .method("GET")
+ .path("/webauthn/authenticator/1/credentials")
+ .reply(&filter)
+ .await;
+ assert_eq!(res.status(), 200);
+ let (_, body) = res.into_parts();
+ let cred = serde_json::de::from_slice::<Vec<CredentialParameters>>(&body).unwrap();
+
+ let state_obj = state.lock().unwrap();
+ assert_creds_equals_test_token_params(&cred, &state_obj.tokens[0].credentials);
+ }
+
+ {
+ let res = warp::test::request()
+ .method("DELETE")
+ .path("/webauthn/authenticator/1/credentials/YmxhbmsK")
+ .reply(&filter)
+ .await;
+ assert_eq!(res.status(), 404);
+ }
+
+ {
+ let res = warp::test::request()
+ .method("DELETE")
+ .path("/webauthn/authenticator/1/credentials/c3VwZXIgcmVhZGVy")
+ .reply(&filter)
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_success_rsp_blank(res.body());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(1, state_obj.tokens[0].credentials.len());
+ }
+
+ {
+ let res = warp::test::request()
+ .method("DELETE")
+ .path("/webauthn/authenticator/1/credentials")
+ .reply(&filter)
+ .await;
+ assert!(res.status().is_success());
+ assert_success_rsp_blank(res.body());
+
+ let state_obj = state.lock().unwrap();
+ assert_eq!(0, state_obj.tokens[0].credentials.len());
+ }
+ }
+}
diff --git a/third_party/rust/authenticator/src/windows/device.rs b/third_party/rust/authenticator/src/windows/device.rs
new file mode 100644
index 0000000000..183ba71f44
--- /dev/null
+++ b/third_party/rust/authenticator/src/windows/device.rs
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fs::{File, OpenOptions};
+use std::io;
+use std::io::{Read, Write};
+use std::os::windows::io::AsRawHandle;
+
+use super::winapi::DeviceCapabilities;
+use crate::consts::{CID_BROADCAST, FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID, MAX_HID_RPT_SIZE};
+use crate::u2ftypes::{U2FDevice, U2FDeviceInfo};
+
+#[derive(Debug)]
+pub struct Device {
+ path: String,
+ file: File,
+ cid: [u8; 4],
+ dev_info: Option<U2FDeviceInfo>,
+}
+
+impl Device {
+ pub fn new(path: String) -> io::Result<Self> {
+ let file = OpenOptions::new().read(true).write(true).open(&path)?;
+ Ok(Self {
+ path,
+ file,
+ cid: CID_BROADCAST,
+ dev_info: None,
+ })
+ }
+
+ pub fn is_u2f(&self) -> bool {
+ match DeviceCapabilities::new(self.file.as_raw_handle()) {
+ Ok(caps) => caps.usage() == FIDO_USAGE_U2FHID && caps.usage_page() == FIDO_USAGE_PAGE,
+ _ => false,
+ }
+ }
+}
+
+impl PartialEq for Device {
+ fn eq(&self, other: &Device) -> bool {
+ self.path == other.path
+ }
+}
+
+impl Read for Device {
+ fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
+ // Windows always includes the report ID.
+ let mut input = [0u8; MAX_HID_RPT_SIZE + 1];
+ let _ = self.file.read(&mut input)?;
+ bytes.clone_from_slice(&input[1..]);
+ Ok(bytes.len() as usize)
+ }
+}
+
+impl Write for Device {
+ fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
+ self.file.write(bytes)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.file.flush()
+ }
+}
+
+impl U2FDevice for Device {
+ fn get_cid<'a>(&'a self) -> &'a [u8; 4] {
+ &self.cid
+ }
+
+ fn set_cid(&mut self, cid: [u8; 4]) {
+ self.cid = cid;
+ }
+
+ fn in_rpt_size(&self) -> usize {
+ MAX_HID_RPT_SIZE
+ }
+
+ fn out_rpt_size(&self) -> usize {
+ MAX_HID_RPT_SIZE
+ }
+
+ fn get_property(&self, _prop_name: &str) -> io::Result<String> {
+ Err(io::Error::new(io::ErrorKind::Other, "Not implemented"))
+ }
+
+ fn get_device_info(&self) -> U2FDeviceInfo {
+ // unwrap is okay, as dev_info must have already been set, else
+ // a programmer error
+ self.dev_info.clone().unwrap()
+ }
+
+ fn set_device_info(&mut self, dev_info: U2FDeviceInfo) {
+ self.dev_info = Some(dev_info);
+ }
+}
diff --git a/third_party/rust/authenticator/src/windows/mod.rs b/third_party/rust/authenticator/src/windows/mod.rs
new file mode 100644
index 0000000000..09135391dd
--- /dev/null
+++ b/third_party/rust/authenticator/src/windows/mod.rs
@@ -0,0 +1,9 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod device;
+pub mod transaction;
+
+mod monitor;
+mod winapi;
diff --git a/third_party/rust/authenticator/src/windows/monitor.rs b/third_party/rust/authenticator/src/windows/monitor.rs
new file mode 100644
index 0000000000..4c977bde03
--- /dev/null
+++ b/third_party/rust/authenticator/src/windows/monitor.rs
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::platform::winapi::DeviceInfoSet;
+use runloop::RunLoop;
+use std::collections::{HashMap, HashSet};
+use std::io;
+use std::iter::FromIterator;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+
+pub struct Monitor<F>
+where
+ F: Fn(String, &dyn Fn() -> bool) + Sync,
+{
+ runloops: HashMap<String, RunLoop>,
+ new_device_cb: Arc<F>,
+}
+
+impl<F> Monitor<F>
+where
+ F: Fn(String, &dyn Fn() -> bool) + Send + Sync + 'static,
+{
+ pub fn new(new_device_cb: F) -> Self {
+ Self {
+ runloops: HashMap::new(),
+ new_device_cb: Arc::new(new_device_cb),
+ }
+ }
+
+ pub fn run(&mut self, alive: &dyn Fn() -> bool) -> io::Result<()> {
+ let mut stored = HashSet::new();
+
+ while alive() {
+ let device_info_set = DeviceInfoSet::new()?;
+ let devices = HashSet::from_iter(device_info_set.devices());
+
+ // Remove devices that are gone.
+ for path in stored.difference(&devices) {
+ self.remove_device(path);
+ }
+
+ // Add devices that were plugged in.
+ for path in devices.difference(&stored) {
+ self.add_device(path);
+ }
+
+ // Remember the new set.
+ stored = devices;
+
+ // Wait a little before looking for devices again.
+ thread::sleep(Duration::from_millis(100));
+ }
+
+ // Remove all tracked devices.
+ self.remove_all_devices();
+
+ Ok(())
+ }
+
+ fn add_device(&mut self, path: &String) {
+ let f = self.new_device_cb.clone();
+ let path = path.clone();
+ let key = path.clone();
+
+ let runloop = RunLoop::new(move |alive| {
+ if alive() {
+ f(path, alive);
+ }
+ });
+
+ if let Ok(runloop) = runloop {
+ self.runloops.insert(key, runloop);
+ }
+ }
+
+ fn remove_device(&mut self, path: &String) {
+ if let Some(runloop) = self.runloops.remove(path) {
+ runloop.cancel();
+ }
+ }
+
+ fn remove_all_devices(&mut self) {
+ while !self.runloops.is_empty() {
+ let path = self.runloops.keys().next().unwrap().clone();
+ self.remove_device(&path);
+ }
+ }
+}
diff --git a/third_party/rust/authenticator/src/windows/transaction.rs b/third_party/rust/authenticator/src/windows/transaction.rs
new file mode 100644
index 0000000000..74e856b690
--- /dev/null
+++ b/third_party/rust/authenticator/src/windows/transaction.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::errors;
+use crate::platform::monitor::Monitor;
+use crate::statecallback::StateCallback;
+use runloop::RunLoop;
+
+pub struct Transaction {
+ // Handle to the thread loop.
+ thread: Option<RunLoop>,
+}
+
+impl Transaction {
+ pub fn new<F, T>(
+ timeout: u64,
+ callback: StateCallback<crate::Result<T>>,
+ new_device_cb: F,
+ ) -> crate::Result<Self>
+ where
+ F: Fn(String, &dyn Fn() -> bool) + Sync + Send + 'static,
+ T: 'static,
+ {
+ let thread = RunLoop::new_with_timeout(
+ move |alive| {
+ // Create a new device monitor.
+ let mut monitor = Monitor::new(new_device_cb);
+
+ // Start polling for new devices.
+ try_or!(monitor.run(alive), |_| callback
+ .call(Err(errors::AuthenticatorError::Platform)));
+
+ // Send an error, if the callback wasn't called already.
+ callback.call(Err(errors::AuthenticatorError::U2FToken(
+ errors::U2FTokenError::NotAllowed,
+ )));
+ },
+ timeout,
+ )
+ .map_err(|_| errors::AuthenticatorError::Platform)?;
+
+ Ok(Self {
+ thread: Some(thread),
+ })
+ }
+
+ pub fn cancel(&mut self) {
+ // This must never be None.
+ self.thread.take().unwrap().cancel();
+ }
+}
diff --git a/third_party/rust/authenticator/src/windows/winapi.rs b/third_party/rust/authenticator/src/windows/winapi.rs
new file mode 100644
index 0000000000..d3388cebfc
--- /dev/null
+++ b/third_party/rust/authenticator/src/windows/winapi.rs
@@ -0,0 +1,274 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::io;
+use std::mem;
+use std::ptr;
+use std::slice;
+
+use std::ffi::OsString;
+use std::os::windows::ffi::OsStringExt;
+
+use crate::util::io_err;
+
+extern crate libc;
+extern crate winapi;
+
+use crate::platform::winapi::winapi::shared::{guiddef, minwindef, ntdef, windef};
+use crate::platform::winapi::winapi::shared::{hidclass, hidpi, hidusage};
+use crate::platform::winapi::winapi::um::{handleapi, setupapi};
+
+#[link(name = "setupapi")]
+extern "system" {
+ fn SetupDiGetClassDevsW(
+ ClassGuid: *const guiddef::GUID,
+ Enumerator: ntdef::PCSTR,
+ hwndParent: windef::HWND,
+ flags: minwindef::DWORD,
+ ) -> setupapi::HDEVINFO;
+
+ fn SetupDiDestroyDeviceInfoList(DeviceInfoSet: setupapi::HDEVINFO) -> minwindef::BOOL;
+
+ fn SetupDiEnumDeviceInterfaces(
+ DeviceInfoSet: setupapi::HDEVINFO,
+ DeviceInfoData: setupapi::PSP_DEVINFO_DATA,
+ InterfaceClassGuid: *const guiddef::GUID,
+ MemberIndex: minwindef::DWORD,
+ DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA,
+ ) -> minwindef::BOOL;
+
+ fn SetupDiGetDeviceInterfaceDetailW(
+ DeviceInfoSet: setupapi::HDEVINFO,
+ DeviceInterfaceData: setupapi::PSP_DEVICE_INTERFACE_DATA,
+ DeviceInterfaceDetailData: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
+ DeviceInterfaceDetailDataSize: minwindef::DWORD,
+ RequiredSize: minwindef::PDWORD,
+ DeviceInfoData: setupapi::PSP_DEVINFO_DATA,
+ ) -> minwindef::BOOL;
+}
+
+#[link(name = "hid")]
+extern "system" {
+ fn HidD_GetPreparsedData(
+ HidDeviceObject: ntdef::HANDLE,
+ PreparsedData: *mut hidpi::PHIDP_PREPARSED_DATA,
+ ) -> ntdef::BOOLEAN;
+
+ fn HidD_FreePreparsedData(PreparsedData: hidpi::PHIDP_PREPARSED_DATA) -> ntdef::BOOLEAN;
+
+ fn HidP_GetCaps(
+ PreparsedData: hidpi::PHIDP_PREPARSED_DATA,
+ Capabilities: hidpi::PHIDP_CAPS,
+ ) -> ntdef::NTSTATUS;
+}
+
+macro_rules! offset_of {
+ ($ty:ty, $field:ident) => {
+ unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
+ };
+}
+
+fn from_wide_ptr(ptr: *const u16, len: usize) -> String {
+ assert!(!ptr.is_null() && len % 2 == 0);
+ let slice = unsafe { slice::from_raw_parts(ptr, len / 2) };
+ OsString::from_wide(slice).to_string_lossy().into_owned()
+}
+
+pub struct DeviceInfoSet {
+ set: setupapi::HDEVINFO,
+}
+
+impl DeviceInfoSet {
+ pub fn new() -> io::Result<Self> {
+ let flags = setupapi::DIGCF_PRESENT | setupapi::DIGCF_DEVICEINTERFACE;
+ let set = unsafe {
+ SetupDiGetClassDevsW(
+ &hidclass::GUID_DEVINTERFACE_HID,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ flags,
+ )
+ };
+ if set == handleapi::INVALID_HANDLE_VALUE {
+ return Err(io_err("SetupDiGetClassDevsW failed!"));
+ }
+
+ Ok(Self { set })
+ }
+
+ pub fn get(&self) -> setupapi::HDEVINFO {
+ self.set
+ }
+
+ pub fn devices(&self) -> DeviceInfoSetIter {
+ DeviceInfoSetIter::new(self)
+ }
+}
+
+impl Drop for DeviceInfoSet {
+ fn drop(&mut self) {
+ let _ = unsafe { SetupDiDestroyDeviceInfoList(self.set) };
+ }
+}
+
+pub struct DeviceInfoSetIter<'a> {
+ set: &'a DeviceInfoSet,
+ index: minwindef::DWORD,
+}
+
+impl<'a> DeviceInfoSetIter<'a> {
+ fn new(set: &'a DeviceInfoSet) -> Self {
+ Self { set, index: 0 }
+ }
+}
+
+impl<'a> Iterator for DeviceInfoSetIter<'a> {
+ type Item = String;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut device_interface_data =
+ mem::MaybeUninit::<setupapi::SP_DEVICE_INTERFACE_DATA>::zeroed();
+ unsafe {
+ (*device_interface_data.as_mut_ptr()).cbSize =
+ mem::size_of::<setupapi::SP_DEVICE_INTERFACE_DATA>() as minwindef::UINT;
+ }
+
+ let rv = unsafe {
+ SetupDiEnumDeviceInterfaces(
+ self.set.get(),
+ ptr::null_mut(),
+ &hidclass::GUID_DEVINTERFACE_HID,
+ self.index,
+ device_interface_data.as_mut_ptr(),
+ )
+ };
+ if rv == 0 {
+ return None; // We're past the last device index.
+ }
+
+ // Determine the size required to hold a detail struct.
+ let mut required_size = 0;
+ unsafe {
+ SetupDiGetDeviceInterfaceDetailW(
+ self.set.get(),
+ device_interface_data.as_mut_ptr(),
+ ptr::null_mut(),
+ required_size,
+ &mut required_size,
+ ptr::null_mut(),
+ )
+ };
+ if required_size == 0 {
+ return None; // An error occurred.
+ }
+
+ let detail = DeviceInterfaceDetailData::new(required_size as usize);
+ if detail.is_none() {
+ return None; // malloc() failed.
+ }
+
+ let detail = detail.unwrap();
+ let rv = unsafe {
+ SetupDiGetDeviceInterfaceDetailW(
+ self.set.get(),
+ device_interface_data.as_mut_ptr(),
+ detail.get(),
+ required_size,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ )
+ };
+ if rv == 0 {
+ return None; // An error occurred.
+ }
+
+ self.index += 1;
+ Some(detail.path())
+ }
+}
+
+struct DeviceInterfaceDetailData {
+ data: setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W,
+ path_len: usize,
+}
+
+impl DeviceInterfaceDetailData {
+ fn new(size: usize) -> Option<Self> {
+ let mut cb_size = mem::size_of::<setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W>();
+ if cfg!(target_pointer_width = "32") {
+ cb_size = 4 + 2; // 4-byte uint + default TCHAR size. size_of is inaccurate.
+ }
+
+ if size < cb_size {
+ warn!("DeviceInterfaceDetailData is too small. {}", size);
+ return None;
+ }
+
+ let data = unsafe { libc::malloc(size) as setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W };
+ if data.is_null() {
+ return None;
+ }
+
+ // Set total size of the structure.
+ unsafe { (*data).cbSize = cb_size as minwindef::UINT };
+
+ // Compute offset of `SP_DEVICE_INTERFACE_DETAIL_DATA_W.DevicePath`.
+ let offset = offset_of!(setupapi::SP_DEVICE_INTERFACE_DETAIL_DATA_W, DevicePath);
+
+ Some(Self {
+ data,
+ path_len: size - offset,
+ })
+ }
+
+ fn get(&self) -> setupapi::PSP_DEVICE_INTERFACE_DETAIL_DATA_W {
+ self.data
+ }
+
+ fn path(&self) -> String {
+ unsafe { from_wide_ptr((*self.data).DevicePath.as_ptr(), self.path_len - 2) }
+ }
+}
+
+impl Drop for DeviceInterfaceDetailData {
+ fn drop(&mut self) {
+ unsafe { libc::free(self.data as *mut libc::c_void) };
+ }
+}
+
+pub struct DeviceCapabilities {
+ caps: hidpi::HIDP_CAPS,
+}
+
+impl DeviceCapabilities {
+ pub fn new(handle: ntdef::HANDLE) -> io::Result<Self> {
+ let mut preparsed_data = ptr::null_mut();
+ let rv = unsafe { HidD_GetPreparsedData(handle, &mut preparsed_data) };
+ if rv == 0 || preparsed_data.is_null() {
+ return Err(io_err("HidD_GetPreparsedData failed!"));
+ }
+
+ let mut caps = mem::MaybeUninit::<hidpi::HIDP_CAPS>::uninit();
+ unsafe {
+ let rv = HidP_GetCaps(preparsed_data, caps.as_mut_ptr());
+ HidD_FreePreparsedData(preparsed_data);
+
+ if rv != hidpi::HIDP_STATUS_SUCCESS {
+ return Err(io_err("HidP_GetCaps failed!"));
+ }
+
+ Ok(Self {
+ caps: caps.assume_init(),
+ })
+ }
+ }
+
+ pub fn usage(&self) -> hidusage::USAGE {
+ self.caps.Usage
+ }
+
+ pub fn usage_page(&self) -> hidusage::USAGE {
+ self.caps.UsagePage
+ }
+}