gecko-dev/third_party/rust/authenticator/src/statemachine.rs

284 строки
9.8 KiB
Rust

/* 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::consts::PARAMETER_SIZE;
use crate::errors;
use crate::platform::device::Device;
use crate::platform::transaction::Transaction;
use crate::statecallback::StateCallback;
use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
use crate::u2ftypes::U2FDevice;
use std::sync::mpsc::Sender;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
fn is_valid_transport(transports: crate::AuthenticatorTransports) -> bool {
transports.is_empty() || transports.contains(crate::AuthenticatorTransports::USB)
}
fn find_valid_key_handles<'a, F>(
app_ids: &'a [crate::AppId],
key_handles: &'a [crate::KeyHandle],
mut is_valid: F,
) -> (&'a crate::AppId, Vec<&'a crate::KeyHandle>)
where
F: FnMut(&Vec<u8>, &crate::KeyHandle) -> bool,
{
// Try all given app_ids in order.
for app_id in app_ids {
// Find all valid key handles for the current app_id.
let valid_handles = key_handles
.iter()
.filter(|key_handle| is_valid(app_id, key_handle))
.collect::<Vec<_>>();
// If there's at least one, stop.
if !valid_handles.is_empty() {
return (app_id, valid_handles);
}
}
(&app_ids[0], vec![])
}
fn send_status(status_mutex: &Mutex<Sender<crate::StatusUpdate>>, msg: crate::StatusUpdate) {
match status_mutex.lock() {
Ok(s) => match s.send(msg) {
Ok(_) => {}
Err(e) => error!("Couldn't send status: {:?}", e),
},
Err(e) => {
error!("Couldn't obtain status mutex: {:?}", e);
}
};
}
#[derive(Default)]
pub struct StateMachine {
transaction: Option<Transaction>,
}
impl StateMachine {
pub fn new() -> Self {
Default::default()
}
pub fn register(
&mut self,
flags: crate::RegisterFlags,
timeout: u64,
challenge: Vec<u8>,
application: crate::AppId,
key_handles: Vec<crate::KeyHandle>,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let status_mutex = Mutex::new(status);
let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// We currently support none of the authenticator selection
// criteria because we can't ask tokens whether they do support
// those features. If flags are set, ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceAvailable {
dev_info: dev.get_device_info(),
},
);
// 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)
.unwrap_or(false) /* no match on failure */
});
while alive() {
if excluded {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::InvalidState,
)));
break;
}
} else if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
let dev_info = dev.get_device_info();
send_status(
&status_mutex,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok((bytes, dev_info)));
break;
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceUnavailable {
dev_info: dev.get_device_info(),
},
);
});
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
pub fn sign(
&mut self,
flags: crate::SignFlags,
timeout: u64,
challenge: Vec<u8>,
app_ids: Vec<crate::AppId>,
key_handles: Vec<crate::KeyHandle>,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let status_mutex = Mutex::new(status);
let transaction = Transaction::new(timeout, cbc.clone(), move |info, alive| {
// Create a new device.
let dev = &mut match Device::new(info) {
Ok(dev) => dev,
_ => return,
};
// Try initializing it.
if !dev.is_u2f() || !u2f_init_device(dev) {
return;
}
// We currently don't support user verification because we can't
// ask tokens whether they do support that. If the flag is set,
// ignore all tokens for now.
//
// Technically, this is a ConstraintError because we shouldn't talk
// to this authenticator in the first place. But the result is the
// same anyway.
if !flags.is_empty() {
return;
}
// For each appId, try all key handles. If there's at least one
// 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)
.unwrap_or(false) /* no match on failure */
});
// Aggregate distinct transports from all given credentials.
let transports = key_handles
.iter()
.fold(crate::AuthenticatorTransports::empty(), |t, k| {
t | k.transports
});
// We currently only support USB. If the RP specifies transports
// and doesn't include USB it's probably lying.
if !is_valid_transport(transports) {
return;
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceAvailable {
dev_info: dev.get_device_info(),
},
);
'outer: while alive() {
// If the device matches none of the given key handles
// then just make it blink with bogus data.
if valid_handles.is_empty() {
let blank = vec![0u8; PARAMETER_SIZE];
if u2f_register(dev, &blank, &blank).is_ok() {
callback.call(Err(errors::AuthenticatorError::U2FToken(
errors::U2FTokenError::InvalidState,
)));
break;
}
} else {
// Otherwise, try to sign.
for key_handle in &valid_handles {
if let Ok(bytes) = u2f_sign(dev, &challenge, app_id, &key_handle.credential)
{
let dev_info = dev.get_device_info();
send_status(
&status_mutex,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok((
app_id.clone(),
key_handle.credential.clone(),
bytes,
dev_info,
)));
break 'outer;
}
}
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
send_status(
&status_mutex,
crate::StatusUpdate::DeviceUnavailable {
dev_info: dev.get_device_info(),
},
);
});
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
// This blocks.
pub fn cancel(&mut self) {
if let Some(mut transaction) = self.transaction.take() {
transaction.cancel();
}
}
}