diff --git a/Cargo.toml b/Cargo.toml index f6527951..308e54d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ maintenance = { status = "actively-developed" } [features] binding-recompile = ["bindgen"] webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"] +dbus = ["zbus", "zvariant"] [target.'cfg(target_os = "linux")'.dependencies] libudev = "^0.2" @@ -45,15 +46,17 @@ log = "0.4" libc = "0.2" runloop = "0.1.0" bitflags = "1.0" +sha2 = "^0.8.2" tokio = { version = "0.2", optional = true, features = ["macros"] } warp = { version = "0.2.4", optional = true } serde = { version = "1.0", optional = true, features = ["derive"] } serde_json = { version = "1.0", optional = true } bytes = { version = "0.5", optional = true, features = ["serde"] } base64 = { version = "^0.10", optional = true } +zbus = { version = "1.8", optional = true } +zvariant = { version = "2.4", optional = true } [dev-dependencies] -sha2 = "^0.8.2" base64 = "^0.10" env_logger = "^0.6" getopts = "^0.2" diff --git a/examples/main.rs b/examples/main.rs index 3922a25d..4fab20cb 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use authenticator::{ - authenticatorservice::AuthenticatorService, statecallback::StateCallback, + authenticatorservice::AuthenticatorService, statecallback::StateCallback, AppId, AuthenticatorTransports, KeyHandle, RegisterFlags, SignFlags, StatusUpdate, }; use getopts::Options; @@ -42,6 +42,8 @@ fn main() { opts.optflag("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms"); #[cfg(feature = "webdriver")] opts.optflag("w", "webdriver", "enable WebDriver virtual bus"); + #[cfg(feature = "dbus")] + opts.optflag("d", "dbus", "enable access to remote token through D-Bus"); opts.optflag("h", "help", "print this help menu").optopt( "t", @@ -74,6 +76,13 @@ fn main() { } } + #[cfg(feature = "dbus")] + { + if matches.opt_present("dbus") { + manager.add_dbus(); + } + } + let timeout_ms = match matches.opt_get_default::("timeout", 15) { Ok(timeout_s) => { println!("Using {}s as the timeout", &timeout_s); @@ -96,9 +105,8 @@ fn main() { challenge.input(challenge_str.as_bytes()); let chall_bytes = challenge.result().to_vec(); - let mut application = Sha256::default(); - application.input(b"http://demo.yubico.com"); - let app_bytes = application.result().to_vec(); + let app_bytes = b"http://demo.yubico.com".to_vec(); + let app_id: AppId = app_bytes.into(); let flags = RegisterFlags::empty(); @@ -131,7 +139,7 @@ fn main() { flags, timeout_ms, chall_bytes.clone(), - app_bytes.clone(), + app_id.clone(), vec![], status_tx.clone(), callback, @@ -163,7 +171,7 @@ fn main() { flags, timeout_ms, chall_bytes, - vec![app_bytes], + vec![app_id.clone()], vec![key_handle], status_tx, callback, diff --git a/src/authenticatorservice.rs b/src/authenticatorservice.rs index fcb05dc6..cb6218b0 100644 --- a/src/authenticatorservice.rs +++ b/src/authenticatorservice.rs @@ -71,6 +71,8 @@ impl AuthenticatorService { /// Add any detected platform transports pub fn add_detected_transports(&mut self) { self.add_u2f_usb_hid_platform_transports(); + #[cfg(feature = "dbus")] + self.add_dbus(); } fn add_transport(&mut self, boxed_token: Box) { @@ -95,6 +97,14 @@ impl AuthenticatorService { } } + #[cfg(feature = "dbus")] + pub fn add_dbus(&mut self) { + match crate::virtualdevices::dbus::VirtualManager::new() { + Ok(token) => self.add_transport(Box::new(token)), + Err(e) => error!("Could not add D-Bus transport: {}", e), + } + } + pub fn register( &mut self, flags: crate::RegisterFlags, @@ -105,7 +115,7 @@ impl AuthenticatorService { status: Sender, callback: StateCallback>, ) -> crate::Result<()> { - if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + if challenge.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } @@ -167,12 +177,6 @@ impl AuthenticatorService { 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); diff --git a/src/capi.rs b/src/capi.rs index ea712350..89749617 100644 --- a/src/capi.rs +++ b/src/capi.rs @@ -82,7 +82,7 @@ pub unsafe extern "C" fn rust_u2f_app_ids_add( id_ptr: *const u8, id_len: usize, ) { - (*ids).push(from_raw(id_ptr, id_len)); + (*ids).push(from_raw(id_ptr, id_len).into()); } /// # Safety @@ -232,7 +232,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_register( 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 application = from_raw(application_ptr, application_len).into(); let key_handles = (*khs).clone(); let (status_tx, status_rx) = channel::(); @@ -333,7 +333,7 @@ pub unsafe extern "C" fn rust_u2f_mgr_sign( 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_APPID, app_id.to_u2f()); 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]); diff --git a/src/lib.rs b/src/lib.rs index cfe82deb..80a00c8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,7 +104,8 @@ pub struct KeyHandle { pub transports: AuthenticatorTransports, } -pub type AppId = Vec; +#[derive(Clone, Debug)] +pub struct AppId(Vec); pub type RegisterResult = (Vec, u2ftypes::U2FDeviceInfo); pub type SignResult = (AppId, Vec, Vec, u2ftypes::U2FDeviceInfo); diff --git a/src/manager.rs b/src/manager.rs index 06f972db..01fd9b5c 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -121,7 +121,7 @@ impl AuthenticatorTransport for U2FManager { status: Sender, callback: StateCallback>, ) -> crate::Result<()> { - if challenge.len() != PARAMETER_SIZE || application.len() != PARAMETER_SIZE { + if challenge.len() != PARAMETER_SIZE { return Err(AuthenticatorError::InvalidRelyingPartyInput); } @@ -161,12 +161,6 @@ impl AuthenticatorTransport for U2FManager { 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); diff --git a/src/statemachine.rs b/src/statemachine.rs index 32552f8d..5a67c57f 100644 --- a/src/statemachine.rs +++ b/src/statemachine.rs @@ -112,12 +112,14 @@ impl StateMachine { }, ); + let app_id_hash = application.to_u2f(); + // 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) + && u2f_is_keyhandle_valid(dev, &challenge, &app_id_hash, &key_handle.credential) .unwrap_or(false) /* no match on failure */ }); @@ -130,7 +132,7 @@ impl StateMachine { ))); break; } - } else if let Ok(bytes) = u2f_register(dev, &challenge, &application) { + } else if let Ok(bytes) = u2f_register(dev, &challenge, &app_id_hash) { let dev_info = dev.get_device_info(); send_status( &status_mutex, @@ -201,7 +203,9 @@ impl StateMachine { // 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) + let app_id: crate::AppId = app_id.clone().into(); + let app_id_hash = app_id.to_u2f(); + u2f_is_keyhandle_valid(dev, &challenge, &app_id_hash, &key_handle.credential) .unwrap_or(false) /* no match on failure */ }); @@ -225,6 +229,8 @@ impl StateMachine { }, ); + let app_id_hash = app_id.to_u2f(); + 'outer: while alive() { // If the device matches none of the given key handles // then just make it blink with bogus data. @@ -239,7 +245,8 @@ impl StateMachine { } else { // Otherwise, try to sign. for key_handle in &valid_handles { - if let Ok(bytes) = u2f_sign(dev, &challenge, app_id, &key_handle.credential) + if let Ok(bytes) = + u2f_sign(dev, &challenge, &app_id_hash, &key_handle.credential) { let dev_info = dev.get_device_info(); send_status( diff --git a/src/util.rs b/src/util.rs index 4ccb3c97..3f569694 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,7 +4,11 @@ extern crate libc; +use sha2::Digest; use std::io; +use std::ops::Deref; + +use crate::AppId; macro_rules! try_or { ($val:expr, $or:expr) => { @@ -65,3 +69,25 @@ pub fn from_unix_result(rv: T) -> io::Result { pub fn io_err(msg: &str) -> io::Error { io::Error::new(io::ErrorKind::Other, msg) } + +impl AppId { + pub fn to_u2f(&self) -> Vec { + let mut sha256 = sha2::Sha256::default(); + sha256.input(&self.0); + sha256.result().to_vec() + } +} + +impl Deref for AppId { + type Target = Vec; + + fn deref(&self) -> &Vec { + &self.0 + } +} + +impl From> for AppId { + fn from(v: Vec) -> Self { + Self(v) + } +} diff --git a/src/virtualdevices/dbus/dbus.rs b/src/virtualdevices/dbus/dbus.rs new file mode 100644 index 00000000..774d2df4 --- /dev/null +++ b/src/virtualdevices/dbus/dbus.rs @@ -0,0 +1,343 @@ +/* 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::convert::TryFrom; +use std::io; +use std::sync::mpsc::channel; +use std::sync::{Arc, Mutex}; +use zbus::{dbus_proxy, Result}; +use zvariant::{OwnedObjectPath, OwnedValue, Value}; + +use crate::errors; +use crate::{RegisterResult, SignResult}; + +#[dbus_proxy( + interface = "org.freedesktop.fido2.Device", + default_service = "org.freedesktop.fido2" +)] +trait Device { + fn make_credential( + &self, + sign_type: &str, + client_data_hash: &[u8], + rp: &str, + user_id: &[u8], + user: &str, + options: HashMap<&str, &Value>, + ) -> Result; + + fn get_assertion( + &self, + client_data_hash: &[u8], + rp: &str, + key_handle: &[u8], + options: HashMap<&str, &Value>, + ) -> Result; +} + +#[dbus_proxy( + interface = "org.freedesktop.fido2.Request", + default_service = "org.freedesktop.fido2" +)] +trait Request { + fn cancel(&self) -> Result<()>; + + #[dbus_proxy(signal)] + fn error(&self, code: u32, cause: &str) -> Result<()>; + + #[dbus_proxy(signal)] + fn completed(&self, options: HashMap<&str, Value>) -> Result<()>; +} + +pub struct Device<'c>(DeviceProxy<'c>); + +pub struct DeviceManagerState<'c> { + pub devices: Vec>, +} + +impl<'c> DeviceManagerState<'c> { + pub fn new() -> Arc>> { + Arc::new(Mutex::new(DeviceManagerState { devices: vec![] })) + } +} + +pub fn serve( + state: Arc>>, + connection: zbus::Connection, +) -> Result<()> { + let object_manager_connection = zbus::Connection::new_session()?; + + let object_manager = zbus::fdo::ObjectManagerProxy::new_for( + &object_manager_connection, + "org.freedesktop.fido2", + "/org/freedesktop/fido2/Device", + )?; + + let connectionclone = connection.clone(); + let stateclone = state.clone(); + + object_manager.connect_interfaces_added(move |object_path, _| { + let device = DeviceProxy::new_for_owned_path( + connectionclone.clone(), + object_path.as_str().to_string(), + )?; + let mut lock = stateclone.lock().unwrap(); + lock.devices.push(Device(device)); + Ok(()) + })?; + + let stateclone = state.clone(); + + object_manager.connect_interfaces_removed(move |object_path, _| { + let mut lock = stateclone.lock().unwrap(); + if let Some(index) = lock + .devices + .iter() + .position(|device| device.0.path() == object_path.as_str()) + { + lock.devices.remove(index); + } + Ok(()) + })?; + + let objects = object_manager.get_managed_objects()?; + for object_path in objects.keys().next() { + let device = + DeviceProxy::new_for_owned_path(connection.clone(), object_path.as_str().to_string())?; + + let mut lock = state.lock().unwrap(); + lock.devices.push(Device(device)); + } + + loop { + object_manager.next_signal()?; + } +} + +fn array_to_vec<'a, T>(value: &'a Value) -> Vec +where + T: TryFrom>, +{ + let array: &zvariant::Array = value.downcast_ref().unwrap(); + >::try_from(array.clone()).unwrap() +} + +impl<'c> Device<'c> { + pub fn register( + &self, + challenge: Vec, + application: crate::AppId, + ) -> crate::Result { + // rp must be in valid UTF-8 + let rp = std::str::from_utf8(&*application) + .map_err(|_| errors::AuthenticatorError::InvalidRelyingPartyInput)?; + + // Forcibly use CTAP1 to avoid PIN requirement + let mut options = HashMap::new(); + let force_u2f = Value::new(true); + options.insert("forceU2F", &force_u2f); + + // Require user presence + let up = Value::new(true); + options.insert("up", &up); + + if let Ok(request_path) = self.0.make_credential( + "es256", + &challenge.as_slice(), + rp, + "user".as_bytes(), + &"user", + options, + ) { + if let Ok(request) = + RequestProxy::new_for_path(self.0.connection(), request_path.as_str()) + { + let (register_tx, register_rx) = channel(); + + let register_tx_clone = register_tx.clone(); + request + .connect_error(move |_, _| { + register_tx_clone + .send(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + ))) + .map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into())) + }) + .unwrap(); + + let register_tx_clone = register_tx.clone(); + request + .connect_completed(move |options| { + let mut public_key = array_to_vec(options.get("publicKey").unwrap()); + let mut key_handle = array_to_vec(options.get("credentialID").unwrap()); + let mut certificate = array_to_vec(options.get("x5c").unwrap()); + let mut signature = array_to_vec(options.get("signature").unwrap()); + + let mut response = Vec::new(); + response.push(0x05u8); + // Indicate the public key is in the ANSI X9.62 + // uncompressed format. + response.push(0x04u8); + response.append(&mut public_key); + response.push(key_handle.len() as u8); + response.append(&mut key_handle); + response.append(&mut certificate); + response.append(&mut signature); + + register_tx_clone + .send(Ok(response)) + .map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into())) + }) + .unwrap(); + + loop { + match request.next_signal() { + Ok(None) => break, + Ok(_) => {} + _ => { + return Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } + } + + if let Ok(Ok(response)) = register_rx.recv() { + Ok((response, self.dev_info())) + } else { + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } else { + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } else { + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } + + pub fn sign( + &self, + challenge: Vec, + application: crate::AppId, + key_handle: Vec, + ) -> crate::Result { + // rp must be in valid UTF-8 + let rp = std::str::from_utf8(&*application) + .map_err(|_| errors::AuthenticatorError::InvalidRelyingPartyInput)?; + + // Forcibly use CTAP1 to avoid PIN requirement + let mut options = HashMap::new(); + let force_u2f = Value::new(true); + options.insert("forceU2F", &force_u2f); + + // Require user presence + let up = Value::new(true); + options.insert("up", &up); + + if let Ok(request_path) = + self.0 + .get_assertion(&challenge.as_slice(), rp, &key_handle.as_slice(), options) + { + if let Ok(request) = + RequestProxy::new_for_path(self.0.connection(), request_path.as_str()) + { + let (sign_tx, sign_rx) = channel(); + + let sign_tx_clone = sign_tx.clone(); + request + .connect_error(move |_, _| { + sign_tx_clone + .send(Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + ))) + .map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into())) + }) + .unwrap(); + + let sign_tx_clone = sign_tx.clone(); + request + .connect_completed(move |options| { + // FIXME: The server actually sends an array of + // assertions, though zbus seems to pick only the first + // assertion. + let value = options.get("assertions").unwrap(); + let assertion: &zvariant::Dict = value.downcast_ref().unwrap(); + let assertion = + >::try_from(assertion.clone()).unwrap(); + + let value = assertion.get("sigCount").unwrap(); + let mut counter = u32::try_from(&*value) + .and_then(|value| Ok(value.to_be_bytes().to_vec())) + .map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into()))?; + + let mut signature = array_to_vec(assertion.get("signature").unwrap()); + + let mut response = Vec::new(); + response.push(0x01u8); // user presense + response.append(&mut counter); + response.append(&mut signature); + + sign_tx_clone + .send(Ok(response)) + .map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into())) + }) + .unwrap(); + + loop { + match request.next_signal() { + Ok(None) => break, + Ok(_) => {} + _ => { + return Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } + } + + if let Ok(Ok(response)) = sign_rx.recv() { + Ok(( + application.clone(), + key_handle.clone(), + response, + self.dev_info(), + )) + } else { + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } else { + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } else { + Err(errors::AuthenticatorError::U2FToken( + errors::U2FTokenError::Unknown, + )) + } + } + + // FIXME: add a method to the protocol that retrieves actual device + // information. + fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo { + crate::u2ftypes::U2FDeviceInfo { + vendor_name: b"Mozilla".to_vec(), + device_name: b"Authenticator D-Bus Token".to_vec(), + version_interface: 0, + version_major: 1, + version_minor: 2, + version_build: 3, + cap_flags: 0, + } + } +} diff --git a/src/virtualdevices/dbus/mod.rs b/src/virtualdevices/dbus/mod.rs new file mode 100644 index 00000000..9e48a1de --- /dev/null +++ b/src/virtualdevices/dbus/mod.rs @@ -0,0 +1,4 @@ +mod dbus; +mod virtualmanager; + +pub use virtualmanager::VirtualManager; diff --git a/src/virtualdevices/dbus/virtualmanager.rs b/src/virtualdevices/dbus/virtualmanager.rs new file mode 100644 index 00000000..a1abffb1 --- /dev/null +++ b/src/virtualdevices/dbus/virtualmanager.rs @@ -0,0 +1,127 @@ +/* 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::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; +use std::{io, thread}; + +use crate::authenticatorservice::AuthenticatorTransport; +use crate::errors; +use crate::statecallback::StateCallback; +use crate::virtualdevices::dbus::dbus; + +pub struct VirtualManager { + state: Arc>>, + rloop: Option, +} + +impl VirtualManager { + pub fn new() -> io::Result { + let connection = zbus::Connection::new_session().unwrap(); + let connectionclone = connection.clone(); + + let state = dbus::DeviceManagerState::new(); + let stateclone = state.clone(); + + let builder = thread::Builder::new().name("D-Bus Signal Receiver".into()); + builder.spawn(move || dbus::serve(stateclone, connectionclone))?; + + Ok(Self { state, rloop: None }) + } +} + +impl AuthenticatorTransport for VirtualManager { + fn register( + &mut self, + _flags: crate::RegisterFlags, + timeout: u64, + challenge: Vec, + application: crate::AppId, + _key_handles: Vec, + _status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + // Abort any prior register/sign calls. + self.cancel()?; + + let state = self.state.clone(); + let rloop = try_or!( + RunLoop::new_with_timeout( + move |alive| { + while alive() { + let state_obj = state.lock().unwrap(); + + for device in &*state_obj.devices { + let register_result = device.register(challenge, application); + 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, + app_ids: Vec, + key_handles: Vec, + _status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + // Abort any prior register/sign calls. + self.cancel()?; + + let state = self.state.clone(); + let app_ids = app_ids.clone(); + let key_handles = key_handles.clone(); + let rloop = try_or!( + RunLoop::new_with_timeout( + move |alive| { + while alive() { + let state_obj = state.lock().unwrap(); + + for (index, app_id) in app_ids.iter().enumerate() { + for device in &*state_obj.devices { + let sign_result = device.sign( + challenge, + app_id.clone(), + key_handles[index].credential.clone(), + ); + 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!("D-Bus operation cancelled."); + r.cancel(); + } + Ok(()) + } +} diff --git a/src/virtualdevices/mod.rs b/src/virtualdevices/mod.rs index 5c0a9d39..a8a5e82f 100644 --- a/src/virtualdevices/mod.rs +++ b/src/virtualdevices/mod.rs @@ -5,4 +5,7 @@ #[cfg(feature = "webdriver")] pub mod webdriver; +#[cfg(feature = "dbus")] +pub mod dbus; + pub mod software_u2f; diff --git a/src/virtualdevices/software_u2f.rs b/src/virtualdevices/software_u2f.rs index a88e74de..9040d604 100644 --- a/src/virtualdevices/software_u2f.rs +++ b/src/virtualdevices/software_u2f.rs @@ -34,7 +34,12 @@ impl SoftwareU2FToken { _app_ids: Vec, _key_handles: Vec, ) -> crate::Result { - Ok((vec![0u8; 0], vec![0u8; 0], vec![0u8; 0], self.dev_info())) + Ok(( + vec![0u8; 0].into(), + vec![0u8; 0], + vec![0u8; 0], + self.dev_info(), + )) } pub fn dev_info(&self) -> crate::u2ftypes::U2FDeviceInfo {