Skip to content

Commit

Permalink
Add support for D-Bus virtual devices
Browse files Browse the repository at this point in the history
  • Loading branch information
ueno committed Jan 6, 2021
1 parent b1c128b commit 49105be
Show file tree
Hide file tree
Showing 7 changed files with 496 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -52,6 +53,8 @@ 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.7", optional = true }
zvariant = { version = "2.4", optional = true }

[dev-dependencies]
base64 = "^0.10"
Expand Down
9 changes: 9 additions & 0 deletions examples/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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::<u64>("timeout", 15) {
Ok(timeout_s) => {
println!("Using {}s as the timeout", &timeout_s);
Expand Down
8 changes: 8 additions & 0 deletions src/authenticatorservice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,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,
Expand Down
341 changes: 341 additions & 0 deletions src/virtualdevices/dbus/dbus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
/* 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<OwnedObjectPath>;

fn get_assertion(
&self,
client_data_hash: &[u8],
rp: &str,
key_handle: &[u8],
options: HashMap<&str, &Value>,
) -> Result<OwnedObjectPath>;
}

#[dbus_proxy(
interface = "org.freedesktop.fido2.Request",
default_service = "org.freedesktop.fido2"
)]
trait Request {
fn cancel(&self) -> Result<()>;

#[dbus_proxy(signal)]
fn error(&self) -> Result<(u32, String)>;

#[dbus_proxy(signal)]
fn completed(&self) -> Result<HashMap<String, OwnedValue>>;
}

pub struct DeviceManagerState<'c> {
pub devices: Vec<DeviceProxy<'c>>,
}

impl<'c> DeviceManagerState<'c> {
pub fn new() -> Arc<Mutex<DeviceManagerState<'c>>> {
Arc::new(Mutex::new(DeviceManagerState { devices: vec![] }))
}
}

pub fn serve(
state: Arc<Mutex<DeviceManagerState<'static>>>,
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
.inner()
.connect_signal("InterfacesAdded", move |message| {
let (object_path, _): (
zvariant::OwnedObjectPath,
HashMap<&str, HashMap<&str, zvariant::Value>>,
) = message.body()?;
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);
Ok(())
})?;

let stateclone = state.clone();

object_manager
.inner()
.connect_signal("InterfacesRemoved", move |message| {
let (object_path, _): (zvariant::OwnedObjectPath, Vec<&str>) = message.body()?;
let mut lock = stateclone.lock().unwrap();
if let Some(index) = lock
.devices
.iter()
.position(|device| device.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);
}

loop {
object_manager.next_signal()?;
}
}

fn array_to_vec<'a, T>(value: &'a Value) -> Vec<T>
where
T: TryFrom<Value<'a>>,
{
let array: &zvariant::Array = value.downcast_ref().unwrap();
<Vec<T>>::try_from(array.clone()).unwrap()
}

fn dev_info() -> 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,
}
}

pub fn register(
device: &DeviceProxy,
challenge: Vec<u8>,
application: crate::AppId,
) -> crate::Result<RegisterResult> {
// 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);

if let Ok(request_path) = device.make_credential(
"es256",
&challenge.as_slice(),
rp,
"user".as_bytes(),
&"user",
options,
) {
if let Ok(request) =
RequestProxy::new_for_path(device.inner().connection(), request_path.as_str())
{
let (register_tx, register_rx) = channel();

let register_tx_clone = register_tx.clone();
request
.inner()
.connect_signal("Error", move |message| {
let (_code, _cause): (u32, &str) = message.body().expect("invalid signature");

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
.inner()
.connect_signal("Completed", move |message| {
let body: HashMap<String, OwnedValue> =
message.body().expect("invalid signature");

let mut public_key = array_to_vec(body.get("publicKey").unwrap());
let mut key_handle = array_to_vec(body.get("credentialID").unwrap());
let mut certificate = array_to_vec(body.get("x5c").unwrap());
let mut signature = array_to_vec(body.get("signature").unwrap());

let mut response = Vec::new();
response.push(0x05u8);
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);

let register_result = (response, dev_info());
register_tx_clone
.send(Ok(register_result))
.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(register_result @ Ok(_)) = register_rx.recv() {
register_result
} 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(
device: &DeviceProxy,
challenge: Vec<u8>,
application: crate::AppId,
key_handle: Vec<u8>,
) -> crate::Result<SignResult> {
// 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);

if let Ok(request_path) =
device.get_assertion(&challenge.as_slice(), rp, &key_handle.as_slice(), options)
{
if let Ok(request) =
RequestProxy::new_for_path(device.inner().connection(), request_path.as_str())
{
let (sign_tx, sign_rx) = channel();

let sign_tx_clone = sign_tx.clone();
request
.inner()
.connect_signal("Error", move |message| {
let (_code, _cause): (u32, &str) = message.body().expect("invalid signature");

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
.inner()
.connect_signal("Completed", move |message| {
let body: HashMap<String, OwnedValue> =
message.body().expect("invalid signature");

let value = body.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(body.get("signature").unwrap());

let mut response = Vec::new();
response.push(0x00u8);
response.append(&mut counter);
response.append(&mut signature);

let sign_result = (
application.clone(),
key_handle.clone(),
response,
dev_info(),
);
sign_tx_clone
.send(Ok(sign_result))
.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(sign_result @ Ok(_)) = sign_rx.recv() {
sign_result
} else {
Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::Unknown,
))
}
} else {
Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::Unknown,
))
}
} else {
Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::Unknown,
))
}
}
Loading

0 comments on commit 49105be

Please sign in to comment.