diff options
Diffstat (limited to 'third_party/rust/authenticator/src/openbsd')
4 files changed, 319 insertions, 0 deletions
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(); + } +} |