-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for D-Bus virtual devices
- Loading branch information
Showing
7 changed files
with
496 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
)) | ||
} | ||
} |
Oops, something went wrong.