From 32721767831e21d15639ed751383d376171a8d68 Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Mon, 20 Nov 2017 16:42:53 +0100 Subject: [PATCH] Linux-part of #47: Implement per-device threads, don't use the KeyHandleMatcher --- src/lib.rs | 2 +- src/linux/devicemap.rs | 50 ---------- src/linux/mod.rs | 191 +++++++++++++++++---------------------- src/linux/monitor.rs | 160 +++++++++++++++++--------------- src/linux/transaction.rs | 43 +++++++++ src/macos/mod.rs | 4 +- 6 files changed, 216 insertions(+), 234 deletions(-) delete mode 100644 src/linux/devicemap.rs create mode 100644 src/linux/transaction.rs diff --git a/src/lib.rs b/src/lib.rs index 17b4fec7..2385c0bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ pub mod platform; #[path = "stub/mod.rs"] pub mod platform; -#[cfg(not(any(target_os = "macos")))] +#[cfg(target_os = "windows")] mod khmatcher; #[macro_use] diff --git a/src/linux/devicemap.rs b/src/linux/devicemap.rs deleted file mode 100644 index f078f6c1..00000000 --- a/src/linux/devicemap.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* 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::hash_map::IterMut; -use std::collections::HashMap; -use std::ffi::OsString; - -use platform::device::Device; -use platform::monitor::Event; -use u2fprotocol::u2f_init_device; - -pub struct DeviceMap { - map: HashMap, -} - -impl DeviceMap { - pub fn new() -> Self { - Self { map: HashMap::new() } - } - - pub fn iter_mut(&mut self) -> IterMut { - self.map.iter_mut() - } - - pub fn process_event(&mut self, event: Event) { - match event { - Event::Add(path) => self.add(path), - Event::Remove(path) => self.remove(path), - } - } - - fn add(&mut self, path: OsString) { - if self.map.contains_key(&path) { - return; - } - - // Create and try to open the device. - if let Ok(mut dev) = Device::new(path.clone()) { - if dev.is_u2f() && u2f_init_device(&mut dev) { - self.map.insert(path, dev); - } - } - } - - fn remove(&mut self, path: OsString) { - // Ignore errors. - let _ = self.map.remove(&path); - } -} diff --git a/src/linux/mod.rs b/src/linux/mod.rs index f06c3ace..2251d80e 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -6,27 +6,24 @@ use std::time::Duration; use std::thread; mod device; -mod devicemap; mod hidraw; mod monitor; +mod transaction; use consts::PARAMETER_SIZE; -use khmatcher::KeyHandleMatcher; -use runloop::RunLoop; +use platform::device::Device; +use platform::transaction::Transaction; use util::{io_err, OnceCallback}; -use u2fprotocol::{u2f_is_keyhandle_valid, u2f_register, u2f_sign}; - -use self::devicemap::DeviceMap; -use self::monitor::Monitor; +use u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign}; +#[derive(Default)] pub struct PlatformManager { - // Handle to the thread loop. - thread: Option, + transaction: Option, } impl PlatformManager { pub fn new() -> Self { - Self { thread: None } + Default::default() } pub fn register( @@ -42,48 +39,42 @@ impl PlatformManager { let cbc = callback.clone(); - let thread = RunLoop::new_with_timeout( - move |alive| { - let mut devices = DeviceMap::new(); - let monitor = try_or!(Monitor::new(), |e| callback.call(Err(e))); - let mut matches = KeyHandleMatcher::new(&key_handles); - - while alive() && monitor.alive() { - // Add/remove devices. - for event in monitor.events() { - devices.process_event(event); - } - - // Query newly added devices. - matches.update(devices.iter_mut(), |device, key_handle| { - u2f_is_keyhandle_valid(device, &challenge, &application, key_handle) - .unwrap_or(false /* no match on failure */) - }); - - // Iterate all devices that don't match any of the handles - // in the exclusion list and try to register. - for (path, device) in devices.iter_mut() { - if matches.get(path).is_empty() { - if let Ok(bytes) = u2f_register(device, &challenge, &application) { - callback.call(Ok(bytes)); - return; - } - } - } - - // Wait a little before trying again. - thread::sleep(Duration::from_millis(100)); + let transaction = Transaction::new(timeout, cbc.clone(), move |path, alive| { + // Create a new device. + let dev = &mut match Device::new(path) { + Ok(dev) => dev, + _ => return, + }; + + // Try initializing it. + if !dev.is_u2f() || !u2f_init_device(dev) { + return; + } + + // Iterate the exclude list and see if there are any matches. + // Abort the state machine if we found a valid key handle. + if key_handles.iter().any(|key_handle| { + u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle) + .unwrap_or(false) /* no match on failure */ + }) + { + return; + } + + while alive() { + if let Ok(bytes) = u2f_register(dev, &challenge, &application) { + callback.call(Ok(bytes)); + break; } - callback.call(Err(io_err("aborted or timed out"))); - }, - timeout, - ); + // Sleep a bit before trying again. + thread::sleep(Duration::from_millis(100)); + } + }); - self.thread = Some(try_or!( - thread, - |_| cbc.call(Err(io_err("couldn't create runloop"))) - )); + self.transaction = Some(try_or!(transaction, |_| { + cbc.call(Err(io_err("couldn't create transaction"))) + })); } pub fn sign( @@ -99,74 +90,60 @@ impl PlatformManager { let cbc = callback.clone(); - let thread = RunLoop::new_with_timeout( - move |alive| { - let mut devices = DeviceMap::new(); - let monitor = try_or!(Monitor::new(), |e| callback.call(Err(e))); - let mut matches = KeyHandleMatcher::new(&key_handles); - - while alive() && monitor.alive() { - // Add/remove devices. - for event in monitor.events() { - devices.process_event(event); + let transaction = Transaction::new(timeout, cbc.clone(), move |path, alive| { + // Create a new device. + let dev = &mut match Device::new(path) { + Ok(dev) => dev, + _ => return, + }; + + // Try initializing it. + if !dev.is_u2f() || !u2f_init_device(dev) { + return; + } + + // Find all matching key handles. + let key_handles = key_handles + .iter() + .filter(|key_handle| { + u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle) + .unwrap_or(false) /* no match on failure */ + }) + .collect::>(); + + while alive() { + // If the device matches none of the given key handles + // then just make it blink with bogus data. + if key_handles.is_empty() { + let blank = vec![0u8; PARAMETER_SIZE]; + if let Ok(_) = u2f_register(dev, &blank, &blank) { + callback.call(Err(io_err("invalid key"))); + break; } - - // Query newly added devices. - matches.update(devices.iter_mut(), |device, key_handle| { - u2f_is_keyhandle_valid(device, &challenge, &application, key_handle) - .unwrap_or(false /* no match on failure */) - }); - - // Iterate all devices. - for (path, device) in devices.iter_mut() { - let key_handles = matches.get(path); - - // If the device matches none of the given key handles - // then just make it blink with bogus data. - if key_handles.is_empty() { - let blank = vec![0u8; PARAMETER_SIZE]; - if let Ok(_) = u2f_register(device, &blank, &blank) { - callback.call(Err(io_err("invalid key"))); - return; - } - - continue; - } - - // Otherwise, try to sign. - for key_handle in key_handles { - if let Ok(bytes) = u2f_sign( - device, - &challenge, - &application, - key_handle, - ) - { - callback.call(Ok((key_handle.to_vec(), bytes))); - return; - } + } else { + // Otherwise, try to sign. + for key_handle in &key_handles { + if let Ok(bytes) = u2f_sign(dev, &challenge, &application, key_handle) { + callback.call(Ok((key_handle.to_vec(), bytes))); + break; } } - - // Wait a little before trying again. - thread::sleep(Duration::from_millis(100)); } - callback.call(Err(io_err("aborted or timed out"))); - }, - timeout, - ); + // Sleep a bit before trying again. + thread::sleep(Duration::from_millis(100)); + } + }); - self.thread = Some(try_or!( - thread, - |_| cbc.call(Err(io_err("couldn't create runloop"))) - )); + self.transaction = Some(try_or!(transaction, |_| { + cbc.call(Err(io_err("couldn't create transaction"))) + })); } // This blocks. pub fn cancel(&mut self) { - if let Some(thread) = self.thread.take() { - thread.cancel(); + if let Some(mut transaction) = self.transaction.take() { + transaction.cancel(); } } } diff --git a/src/linux/monitor.rs b/src/linux/monitor.rs index 49a75cf4..b8f39583 100644 --- a/src/linux/monitor.rs +++ b/src/linux/monitor.rs @@ -2,18 +2,15 @@ * 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; use libudev::EventType; - -use libc::{c_int, c_short, c_ulong}; - +use runloop::RunLoop; +use std::collections::HashMap; use std::ffi::OsString; use std::io; use std::os::unix::io::AsRawFd; -use std::sync::mpsc::{channel, Receiver, TryIter}; - -use runloop::RunLoop; -use util::to_io_err; +use std::sync::Arc; const UDEV_SUBSYSTEM: &'static str = "hidraw"; const POLLIN: c_short = 0x0001; @@ -31,90 +28,105 @@ fn poll(fds: &mut Vec<::libc::pollfd>) -> io::Result<()> { } } -pub enum Event { - Add(OsString), - Remove(OsString), +pub struct Monitor +where + F: Fn(OsString, &Fn() -> bool) + Sync, +{ + runloops: HashMap, + new_device_cb: Arc, } -impl Event { - fn from_udev(event: libudev::Event) -> Option { - let path = event.device().devnode().map( - |dn| dn.to_owned().into_os_string(), - ); - - match (event.event_type(), path) { - (EventType::Add, Some(path)) => Some(Event::Add(path)), - (EventType::Remove, Some(path)) => Some(Event::Remove(path)), - _ => None, +impl Monitor +where + F: Fn(OsString, &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 struct Monitor { - // Receive events from the thread. - rx: Receiver, - // Handle to the thread loop. - thread: RunLoop, -} - -impl Monitor { - pub fn new() -> io::Result { - let (tx, rx) = channel(); + pub fn run(&mut self, alive: &Fn() -> bool) -> io::Result<()> { + let ctx = libudev::Context::new()?; - let thread = RunLoop::new(move |alive| -> io::Result<()> { - let ctx = libudev::Context::new()?; - let mut enumerator = libudev::Enumerator::new(&ctx)?; - enumerator.match_subsystem(UDEV_SUBSYSTEM)?; + 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()) { - tx.send(Event::Add(path)).map_err(to_io_err)?; - } + // 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, - }, - ]; - - // Loop until we're stopped by the controlling thread, or fail. - while alive() { - // Wait for new events, break on failure. - poll(&mut fds)?; - - // Send the event over. - let udev_event = socket.receive_event(); - if let Some(event) = udev_event.and_then(Event::from_udev) { - tx.send(event).map_err(to_io_err)?; - } + 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); } + } - Ok(()) - })?; + // Remove all tracked devices. + self.remove_all_devices(); - Ok(Self { rx, thread }) + Ok(()) } - pub fn events<'a>(&'a self) -> TryIter<'a, Event> { - self.rx.try_iter() + 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 */ } + } } - pub fn alive(&self) -> bool { - self.thread.alive() + 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); + } } -} -impl Drop for Monitor { - fn drop(&mut self) { - self.thread.cancel(); + 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/src/linux/transaction.rs b/src/linux/transaction.rs new file mode 100644 index 00000000..d3a11d51 --- /dev/null +++ b/src/linux/transaction.rs @@ -0,0 +1,43 @@ +/* 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 platform::monitor::Monitor; +use runloop::RunLoop; +use std::ffi::OsString; +use std::io; +use util::{io_err, OnceCallback}; + +pub struct Transaction { + // Handle to the thread loop. + thread: Option, +} + +impl Transaction { + pub fn new(timeout: u64, callback: OnceCallback, new_device_cb: F) -> io::Result + where + F: Fn(OsString, &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), |e| callback.call(Err(e))); + + // Send an error, if the callback wasn't called already. + callback.call(Err(io_err("aborted or timed out"))); + }, + timeout, + )?; + + Ok(Self { thread: Some(thread) }) + } + + pub fn cancel(&mut self) { + // This must never be None. + self.thread.take().unwrap().cancel(); + } +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 53c0f5cb..5ec201a4 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -51,7 +51,7 @@ impl PlatformManager { return; } - // Iterate the exlude list and see if there are any matches. + // Iterate the exclude list and see if there are any matches. // Abort the state machine if we found a valid key handle. if key_handles.iter().any(|key_handle| { u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle) @@ -108,7 +108,7 @@ impl PlatformManager { u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle) .unwrap_or(false) /* no match on failure */ }) - .collect::>>(); + .collect::>(); while alive() { // If the device matches none of the given key handles