Skip to content

Commit 49105be

Browse files
committed
Add support for D-Bus virtual devices
1 parent b1c128b commit 49105be

File tree

7 files changed

+496
-0
lines changed

7 files changed

+496
-0
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ maintenance = { status = "actively-developed" }
1616
[features]
1717
binding-recompile = ["bindgen"]
1818
webdriver = ["base64", "bytes", "warp", "tokio", "serde", "serde_json"]
19+
dbus = ["zbus", "zvariant"]
1920

2021
[target.'cfg(target_os = "linux")'.dependencies]
2122
libudev = "^0.2"
@@ -52,6 +53,8 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
5253
serde_json = { version = "1.0", optional = true }
5354
bytes = { version = "0.5", optional = true, features = ["serde"] }
5455
base64 = { version = "^0.10", optional = true }
56+
zbus = { version = "1.7", optional = true }
57+
zvariant = { version = "2.4", optional = true }
5558

5659
[dev-dependencies]
5760
base64 = "^0.10"

examples/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ fn main() {
4242
opts.optflag("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms");
4343
#[cfg(feature = "webdriver")]
4444
opts.optflag("w", "webdriver", "enable WebDriver virtual bus");
45+
#[cfg(feature = "dbus")]
46+
opts.optflag("d", "dbus", "enable access to remote token through D-Bus");
4547

4648
opts.optflag("h", "help", "print this help menu").optopt(
4749
"t",
@@ -74,6 +76,13 @@ fn main() {
7476
}
7577
}
7678

79+
#[cfg(feature = "dbus")]
80+
{
81+
if matches.opt_present("dbus") {
82+
manager.add_dbus();
83+
}
84+
}
85+
7786
let timeout_ms = match matches.opt_get_default::<u64>("timeout", 15) {
7887
Ok(timeout_s) => {
7988
println!("Using {}s as the timeout", &timeout_s);

src/authenticatorservice.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ impl AuthenticatorService {
9595
}
9696
}
9797

98+
#[cfg(feature = "dbus")]
99+
pub fn add_dbus(&mut self) {
100+
match crate::virtualdevices::dbus::VirtualManager::new() {
101+
Ok(token) => self.add_transport(Box::new(token)),
102+
Err(e) => error!("Could not add D-Bus transport: {}", e),
103+
}
104+
}
105+
98106
pub fn register(
99107
&mut self,
100108
flags: crate::RegisterFlags,

src/virtualdevices/dbus/dbus.rs

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use std::collections::HashMap;
6+
use std::convert::TryFrom;
7+
use std::io;
8+
use std::sync::mpsc::channel;
9+
use std::sync::{Arc, Mutex};
10+
use zbus::{dbus_proxy, Result};
11+
use zvariant::{OwnedObjectPath, OwnedValue, Value};
12+
13+
use crate::errors;
14+
use crate::{RegisterResult, SignResult};
15+
16+
#[dbus_proxy(
17+
interface = "org.freedesktop.fido2.Device",
18+
default_service = "org.freedesktop.fido2"
19+
)]
20+
trait Device {
21+
fn make_credential(
22+
&self,
23+
sign_type: &str,
24+
client_data_hash: &[u8],
25+
rp: &str,
26+
user_id: &[u8],
27+
user: &str,
28+
options: HashMap<&str, &Value>,
29+
) -> Result<OwnedObjectPath>;
30+
31+
fn get_assertion(
32+
&self,
33+
client_data_hash: &[u8],
34+
rp: &str,
35+
key_handle: &[u8],
36+
options: HashMap<&str, &Value>,
37+
) -> Result<OwnedObjectPath>;
38+
}
39+
40+
#[dbus_proxy(
41+
interface = "org.freedesktop.fido2.Request",
42+
default_service = "org.freedesktop.fido2"
43+
)]
44+
trait Request {
45+
fn cancel(&self) -> Result<()>;
46+
47+
#[dbus_proxy(signal)]
48+
fn error(&self) -> Result<(u32, String)>;
49+
50+
#[dbus_proxy(signal)]
51+
fn completed(&self) -> Result<HashMap<String, OwnedValue>>;
52+
}
53+
54+
pub struct DeviceManagerState<'c> {
55+
pub devices: Vec<DeviceProxy<'c>>,
56+
}
57+
58+
impl<'c> DeviceManagerState<'c> {
59+
pub fn new() -> Arc<Mutex<DeviceManagerState<'c>>> {
60+
Arc::new(Mutex::new(DeviceManagerState { devices: vec![] }))
61+
}
62+
}
63+
64+
pub fn serve(
65+
state: Arc<Mutex<DeviceManagerState<'static>>>,
66+
connection: zbus::Connection,
67+
) -> Result<()> {
68+
let object_manager_connection = zbus::Connection::new_session()?;
69+
70+
let object_manager = zbus::fdo::ObjectManagerProxy::new_for(
71+
&object_manager_connection,
72+
"org.freedesktop.fido2",
73+
"/org/freedesktop/fido2/Device",
74+
)?;
75+
76+
let connectionclone = connection.clone();
77+
let stateclone = state.clone();
78+
79+
object_manager
80+
.inner()
81+
.connect_signal("InterfacesAdded", move |message| {
82+
let (object_path, _): (
83+
zvariant::OwnedObjectPath,
84+
HashMap<&str, HashMap<&str, zvariant::Value>>,
85+
) = message.body()?;
86+
let device = DeviceProxy::new_for_owned_path(
87+
connectionclone.clone(),
88+
object_path.as_str().to_string(),
89+
)?;
90+
let mut lock = stateclone.lock().unwrap();
91+
lock.devices.push(device);
92+
Ok(())
93+
})?;
94+
95+
let stateclone = state.clone();
96+
97+
object_manager
98+
.inner()
99+
.connect_signal("InterfacesRemoved", move |message| {
100+
let (object_path, _): (zvariant::OwnedObjectPath, Vec<&str>) = message.body()?;
101+
let mut lock = stateclone.lock().unwrap();
102+
if let Some(index) = lock
103+
.devices
104+
.iter()
105+
.position(|device| device.path() == object_path.as_str())
106+
{
107+
lock.devices.remove(index);
108+
}
109+
Ok(())
110+
})?;
111+
112+
let objects = object_manager.get_managed_objects()?;
113+
for object_path in objects.keys().next() {
114+
let device =
115+
DeviceProxy::new_for_owned_path(connection.clone(), object_path.as_str().to_string())?;
116+
117+
let mut lock = state.lock().unwrap();
118+
lock.devices.push(device);
119+
}
120+
121+
loop {
122+
object_manager.next_signal()?;
123+
}
124+
}
125+
126+
fn array_to_vec<'a, T>(value: &'a Value) -> Vec<T>
127+
where
128+
T: TryFrom<Value<'a>>,
129+
{
130+
let array: &zvariant::Array = value.downcast_ref().unwrap();
131+
<Vec<T>>::try_from(array.clone()).unwrap()
132+
}
133+
134+
fn dev_info() -> crate::u2ftypes::U2FDeviceInfo {
135+
crate::u2ftypes::U2FDeviceInfo {
136+
vendor_name: b"Mozilla".to_vec(),
137+
device_name: b"Authenticator D-Bus Token".to_vec(),
138+
version_interface: 0,
139+
version_major: 1,
140+
version_minor: 2,
141+
version_build: 3,
142+
cap_flags: 0,
143+
}
144+
}
145+
146+
pub fn register(
147+
device: &DeviceProxy,
148+
challenge: Vec<u8>,
149+
application: crate::AppId,
150+
) -> crate::Result<RegisterResult> {
151+
// rp must be in valid UTF-8
152+
let rp = std::str::from_utf8(&*application)
153+
.map_err(|_| errors::AuthenticatorError::InvalidRelyingPartyInput)?;
154+
// Forcibly use CTAP1 to avoid PIN requirement
155+
let mut options = HashMap::new();
156+
let force_u2f = Value::new(true);
157+
options.insert("forceU2F", &force_u2f);
158+
159+
if let Ok(request_path) = device.make_credential(
160+
"es256",
161+
&challenge.as_slice(),
162+
rp,
163+
"user".as_bytes(),
164+
&"user",
165+
options,
166+
) {
167+
if let Ok(request) =
168+
RequestProxy::new_for_path(device.inner().connection(), request_path.as_str())
169+
{
170+
let (register_tx, register_rx) = channel();
171+
172+
let register_tx_clone = register_tx.clone();
173+
request
174+
.inner()
175+
.connect_signal("Error", move |message| {
176+
let (_code, _cause): (u32, &str) = message.body().expect("invalid signature");
177+
178+
register_tx_clone
179+
.send(Err(errors::AuthenticatorError::U2FToken(
180+
errors::U2FTokenError::Unknown,
181+
)))
182+
.map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into()))
183+
})
184+
.unwrap();
185+
186+
let register_tx_clone = register_tx.clone();
187+
request
188+
.inner()
189+
.connect_signal("Completed", move |message| {
190+
let body: HashMap<String, OwnedValue> =
191+
message.body().expect("invalid signature");
192+
193+
let mut public_key = array_to_vec(body.get("publicKey").unwrap());
194+
let mut key_handle = array_to_vec(body.get("credentialID").unwrap());
195+
let mut certificate = array_to_vec(body.get("x5c").unwrap());
196+
let mut signature = array_to_vec(body.get("signature").unwrap());
197+
198+
let mut response = Vec::new();
199+
response.push(0x05u8);
200+
response.push(0x04u8);
201+
response.append(&mut public_key);
202+
response.push(key_handle.len() as u8);
203+
response.append(&mut key_handle);
204+
response.append(&mut certificate);
205+
response.append(&mut signature);
206+
207+
let register_result = (response, dev_info());
208+
register_tx_clone
209+
.send(Ok(register_result))
210+
.map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into()))
211+
})
212+
.unwrap();
213+
214+
loop {
215+
match request.next_signal() {
216+
Ok(None) => break,
217+
Ok(_) => {}
218+
_ => {
219+
return Err(errors::AuthenticatorError::U2FToken(
220+
errors::U2FTokenError::Unknown,
221+
))
222+
}
223+
}
224+
}
225+
226+
if let Ok(register_result @ Ok(_)) = register_rx.recv() {
227+
register_result
228+
} else {
229+
Err(errors::AuthenticatorError::U2FToken(
230+
errors::U2FTokenError::Unknown,
231+
))
232+
}
233+
} else {
234+
Err(errors::AuthenticatorError::U2FToken(
235+
errors::U2FTokenError::Unknown,
236+
))
237+
}
238+
} else {
239+
Err(errors::AuthenticatorError::U2FToken(
240+
errors::U2FTokenError::Unknown,
241+
))
242+
}
243+
}
244+
245+
pub fn sign(
246+
device: &DeviceProxy,
247+
challenge: Vec<u8>,
248+
application: crate::AppId,
249+
key_handle: Vec<u8>,
250+
) -> crate::Result<SignResult> {
251+
// rp must be in valid UTF-8
252+
let rp = std::str::from_utf8(&*application)
253+
.map_err(|_| errors::AuthenticatorError::InvalidRelyingPartyInput)?;
254+
// Forcibly use CTAP1 to avoid PIN requirement
255+
let mut options = HashMap::new();
256+
let force_u2f = Value::new(true);
257+
options.insert("forceU2F", &force_u2f);
258+
259+
if let Ok(request_path) =
260+
device.get_assertion(&challenge.as_slice(), rp, &key_handle.as_slice(), options)
261+
{
262+
if let Ok(request) =
263+
RequestProxy::new_for_path(device.inner().connection(), request_path.as_str())
264+
{
265+
let (sign_tx, sign_rx) = channel();
266+
267+
let sign_tx_clone = sign_tx.clone();
268+
request
269+
.inner()
270+
.connect_signal("Error", move |message| {
271+
let (_code, _cause): (u32, &str) = message.body().expect("invalid signature");
272+
273+
sign_tx_clone
274+
.send(Err(errors::AuthenticatorError::U2FToken(
275+
errors::U2FTokenError::Unknown,
276+
)))
277+
.map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into()))
278+
})
279+
.unwrap();
280+
281+
let sign_tx_clone = sign_tx.clone();
282+
request
283+
.inner()
284+
.connect_signal("Completed", move |message| {
285+
let body: HashMap<String, OwnedValue> =
286+
message.body().expect("invalid signature");
287+
288+
let value = body.get("sigCount").unwrap();
289+
let mut counter = u32::try_from(&*value)
290+
.and_then(|value| Ok(value.to_be_bytes().to_vec()))
291+
.map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into()))?;
292+
293+
let mut signature = array_to_vec(body.get("signature").unwrap());
294+
295+
let mut response = Vec::new();
296+
response.push(0x00u8);
297+
response.append(&mut counter);
298+
response.append(&mut signature);
299+
300+
let sign_result = (
301+
application.clone(),
302+
key_handle.clone(),
303+
response,
304+
dev_info(),
305+
);
306+
sign_tx_clone
307+
.send(Ok(sign_result))
308+
.map_err(|_| zbus::Error::Io(io::ErrorKind::Other.into()))
309+
})
310+
.unwrap();
311+
312+
loop {
313+
match request.next_signal() {
314+
Ok(None) => break,
315+
Ok(_) => {}
316+
_ => {
317+
return Err(errors::AuthenticatorError::U2FToken(
318+
errors::U2FTokenError::Unknown,
319+
))
320+
}
321+
}
322+
}
323+
324+
if let Ok(sign_result @ Ok(_)) = sign_rx.recv() {
325+
sign_result
326+
} else {
327+
Err(errors::AuthenticatorError::U2FToken(
328+
errors::U2FTokenError::Unknown,
329+
))
330+
}
331+
} else {
332+
Err(errors::AuthenticatorError::U2FToken(
333+
errors::U2FTokenError::Unknown,
334+
))
335+
}
336+
} else {
337+
Err(errors::AuthenticatorError::U2FToken(
338+
errors::U2FTokenError::Unknown,
339+
))
340+
}
341+
}

0 commit comments

Comments
 (0)