/* 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::ctap2::commands::client_pin::{ChangeExistingPin, Pin, PinError, SetNewPin};
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult};
use crate::ctap2::commands::make_credentials::{MakeCredentials, MakeCredentialsResult};
use crate::ctap2::commands::reset::Reset;
use crate::ctap2::commands::{
repackage_pin_errors, CommandError, PinAuthCommand, Request, StatusCode,
};
use crate::errors::{self, AuthenticatorError, UnsupportedOption};
use crate::statecallback::StateCallback;
use crate::transport::device_selector::{
BlinkResult, Device, DeviceBuildParameters, DeviceCommand, DeviceSelectorEvent,
};
use crate::transport::platform::transaction::Transaction;
use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, Nonce};
use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign};
use crate::u2ftypes::U2FDevice;
use crate::{send_status, RegisterResult, SignResult, StatusUpdate};
use std::sync::mpsc::{channel, Sender};
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![])
}
#[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 transaction = Transaction::new(
timeout,
cbc.clone(),
status,
move |info, _, status, 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,
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,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok(RegisterResult::CTAP1(bytes, dev_info)));
break;
}
// Sleep a bit before trying again.
thread::sleep(Duration::from_millis(100));
}
send_status(
&status,
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 transaction = Transaction::new(
timeout,
cbc.clone(),
status,
move |info, _, status, 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,
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,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
callback.call(Ok(SignResult::CTAP1(
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,
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();
}
}
}
#[derive(Default)]
// TODO(MS): To be renamed to `StateMachine` once U2FManager and the original StateMachine can be removed.
pub struct StateMachineCtap2 {
transaction: Option<Transaction>,
}
impl StateMachineCtap2 {
pub fn new() -> Self {
Default::default()
}
fn init_and_select(
info: DeviceBuildParameters,
selector: &Sender<DeviceSelectorEvent>,
ctap2_only: bool,
) -> Option<Device> {
// Create a new device.
let mut dev = match Device::new(info) {
Ok(dev) => dev,
Err((e, id)) => {
info!("error happened with device: {}", e);
selector.send(DeviceSelectorEvent::NotAToken(id)).ok()?;
return None;
}
};
// Try initializing it.
if let Err(e) = dev.init(Nonce::CreateRandom) {
warn!("error while initializing device: {}", e);
selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok();
return None;
}
if ctap2_only && dev.get_authenticator_info().is_none() {
info!("Device does not support CTAP2");
selector.send(DeviceSelectorEvent::NotAToken(dev.id())).ok();
return None;
}
let write_only_clone = match dev.clone_device_as_write_only() {
Ok(x) => x,
Err(_) => {
// There is probably something seriously wrong here, if this happens.
// So `NotAToken()` is probably too weak a response here.
warn!("error while cloning device: {:?}", dev.id());
selector
.send(DeviceSelectorEvent::NotAToken(dev.id()))
.ok()?;
return None;
}
};
let (tx, rx) = channel();
selector
.send(DeviceSelectorEvent::ImAToken((write_only_clone, tx)))
.ok()?;
// Blocking recv. DeviceSelector will tell us what to do
match rx.recv() {
Ok(DeviceCommand::Blink) => match dev.block_and_blink() {
BlinkResult::DeviceSelected => {
// User selected us. Let DeviceSelector know, so it can cancel all other
// outstanding open blink-requests.
selector
.send(DeviceSelectorEvent::SelectedToken(dev.id()))
.ok()?;
}
BlinkResult::Cancelled => {
info!("Device {:?} was not selected", dev.id());
return None;
}
},
Ok(DeviceCommand::Removed) => {
info!("Device {:?} was removed", dev.id());
return None;
}
Ok(DeviceCommand::Continue) => {
// Just continue
}
Err(_) => {
warn!("Error when trying to receive messages from DeviceSelector! Exiting.");
return None;
}
}
Some(dev)
}
fn ask_user_for_pin<U>(
error: PinError,
status: &Sender<StatusUpdate>,
callback: &StateCallback<crate::Result<U>>,
) -> Result<Pin, ()> {
info!("PIN Error that requires user interaction detected. Sending it back and waiting for a reply");
let (tx, rx) = channel();
send_status(status, crate::StatusUpdate::PinError(error.clone(), tx));
match rx.recv() {
Ok(pin) => Ok(pin),
Err(_) => {
// recv() can only fail, if the other side is dropping the Sender. We are using this as a trick
// to let the callback decide if this PinError is recoverable (e.g. with User input) or not (e.g.
// locked token). If it is deemed unrecoverable, we error out the 'normal' way with the same error.
error!("Callback dropped the channel, so we forward the error to the results-callback: {:?}", error);
callback.call(Err(AuthenticatorError::PinError(error)));
Err(())
}
}
}
fn determine_pin_auth<T: PinAuthCommand, U>(
cmd: &mut T,
dev: &mut Device,
status: &Sender<StatusUpdate>,
callback: &StateCallback<crate::Result<U>>,
) -> Result<(), ()> {
loop {
match cmd.determine_pin_auth(dev) {
Ok(_) => {
break;
}
Err(AuthenticatorError::PinError(e)) => {
let pin = Self::ask_user_for_pin(e, status, callback)?;
cmd.set_pin(Some(pin));
continue;
}
Err(e) => {
error!("Error when determining pinAuth: {:?}", e);
callback.call(Err(e));
return Err(());
}
};
}
// CTAP 2.0 spec is a bit vague here, but CTAP 2.1 is very specific, that the request
// should either include pinAuth OR uv=true, but not both at the same time.
// Do not set user_verification, if pinAuth is provided
if cmd.pin_auth().is_some() {
cmd.unset_uv_option();
}
Ok(())
}
pub fn register(
&mut self,
timeout: u64,
params: MakeCredentials,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::RegisterResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
cbc.clone(),
status,
move |info, selector, status, _alive| {
let mut dev = match Self::init_and_select(info, &selector, false) {
None => {
return;
}
Some(dev) => dev,
};
info!("Device {:?} continues with the register process", dev.id());
// TODO(baloo): not sure about this, have to ask
// 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;
//}
// TODO(baloo): not sure about this, have to ask
// 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 */
//});
// TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me
// to modify "params" directly.
let mut makecred = params.clone();
if params.is_ctap2_request() {
// First check if extensions have been requested that are not supported by the device
if let Some(true) = params.extensions.hmac_secret {
if let Some(auth) = dev.get_authenticator_info() {
if !auth.supports_hmac_secret() {
callback.call(Err(AuthenticatorError::UnsupportedOption(
UnsupportedOption::HmacSecret,
)));
return;
}
}
}
// Second, ask for PIN and get the shared secret
if Self::determine_pin_auth(&mut makecred, &mut dev, &status, &callback)
.is_err()
{
return;
}
}
debug!("------------------------------------------------------------------");
debug!("{:?}", makecred);
debug!("------------------------------------------------------------------");
let resp = dev.send_msg(&makecred);
if resp.is_ok() {
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
// The DeviceSelector could already be dead, but it might also wait
// for us to respond, in order to cancel all other tokens in case
// we skipped the "blinking"-action and went straight for the actual
// request.
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
}
match resp {
Ok(MakeCredentialsResult::CTAP2(attestation, client_data)) => {
callback.call(Ok(RegisterResult::CTAP2(attestation, client_data)))
}
Ok(MakeCredentialsResult::CTAP1(data)) => {
callback.call(Ok(RegisterResult::CTAP1(data, dev.get_device_info())))
}
Err(HIDError::Command(CommandError::StatusCode(
StatusCode::ChannelBusy,
_,
))) => {}
Err(e) => {
warn!("error happened: {}", e);
callback.call(Err(AuthenticatorError::HIDError(e)));
}
}
},
);
self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e))));
}
pub fn sign(
&mut self,
timeout: u64,
params: GetAssertion,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
callback.clone(),
status,
move |info, selector, status, _alive| {
let mut dev = match Self::init_and_select(info, &selector, false) {
None => {
return;
}
Some(dev) => dev,
};
info!("Device {:?} continues with the signing process", dev.id());
// TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me
// to modify "params" directly.
let mut getassertion = params.clone();
if params.is_ctap2_request() {
// First check if extensions have been requested that are not supported by the device
if params.extensions.hmac_secret.is_some() {
if let Some(auth) = dev.get_authenticator_info() {
if !auth.supports_hmac_secret() {
callback.call(Err(AuthenticatorError::UnsupportedOption(
UnsupportedOption::HmacSecret,
)));
return;
}
}
}
// Second, ask for PIN and get the shared secret
if Self::determine_pin_auth(&mut getassertion, &mut dev, &status, &callback)
.is_err()
{
return;
}
// Third, use the shared secret in the extensions, if requested
if let Some(extension) = getassertion.extensions.hmac_secret.as_mut() {
if let Some(secret) = dev.get_shared_secret() {
match extension.calculate(secret) {
Ok(x) => x,
Err(e) => {
callback.call(Err(e));
return;
}
}
}
}
}
debug!("------------------------------------------------------------------");
debug!("{:?}", getassertion);
debug!("------------------------------------------------------------------");
let resp = dev.send_msg(&getassertion);
if resp.is_ok() {
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
// The DeviceSelector could already be dead, but it might also wait
// for us to respond, in order to cancel all other tokens in case
// we skipped the "blinking"-action and went straight for the actual
// request.
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
}
match resp {
Ok(GetAssertionResult::CTAP1(resp)) => {
let app_id = getassertion.rp.hash().as_ref().to_vec();
let key_handle = getassertion.allow_list[0].id.clone();
callback.call(Ok(SignResult::CTAP1(
app_id,
key_handle,
resp,
dev.get_device_info(),
)))
}
Ok(GetAssertionResult::CTAP2(assertion, client_data)) => {
callback.call(Ok(SignResult::CTAP2(assertion, client_data)))
}
Err(HIDError::Command(CommandError::StatusCode(
StatusCode::ChannelBusy,
_,
))) => {}
Err(e) => {
warn!("error happened: {}", e);
callback.call(Err(AuthenticatorError::HIDError(e)));
}
}
},
);
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
}
// This blocks.
pub fn cancel(&mut self) {
if let Some(mut transaction) = self.transaction.take() {
info!("Statemachine was cancelled. Cancelling transaction now.");
transaction.cancel();
}
}
pub fn reset(
&mut self,
timeout: u64,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
callback.clone(),
status,
move |info, selector, status, _alive| {
let reset = Reset {};
let mut dev = match Self::init_and_select(info, &selector, true) {
None => {
return;
}
Some(dev) => dev,
};
info!("Device {:?} continues with the reset process", dev.id());
debug!("------------------------------------------------------------------");
debug!("{:?}", reset);
debug!("------------------------------------------------------------------");
let resp = dev.send_cbor(&reset);
if resp.is_ok() {
send_status(
&status,
crate::StatusUpdate::Success {
dev_info: dev.get_device_info(),
},
);
// The DeviceSelector could already be dead, but it might also wait
// for us to respond, in order to cancel all other tokens in case
// we skipped the "blinking"-action and went straight for the actual
// request.
let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id()));
}
match resp {
Ok(()) => callback.call(Ok(())),
Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {}
Err(HIDError::Command(CommandError::StatusCode(
StatusCode::ChannelBusy,
_,
))) => {}
Err(e) => {
warn!("error happened: {}", e);
callback.call(Err(AuthenticatorError::HIDError(e)));
}
}
},
);
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
}
pub fn set_pin(
&mut self,
timeout: u64,
new_pin: Pin,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::ResetResult>>,
) {
// Abort any prior register/sign calls.
self.cancel();
let cbc = callback.clone();
let transaction = Transaction::new(
timeout,
callback.clone(),
status,
move |info, selector, status, _alive| {
let mut dev = match Self::init_and_select(info, &selector, true) {
None => {
return;
}
Some(dev) => dev,
};
let (mut shared_secret, authinfo) = match dev.establish_shared_secret() {
Ok(s) => s,
Err(e) => {
callback.call(Err(AuthenticatorError::HIDError(e)));
return;
}
};
// With CTAP2.1 we will have an adjustable required length for PINs
if new_pin.as_bytes().len() < 4 {
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
return;
}
if new_pin.as_bytes().len() > 64 {
callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong(
new_pin.as_bytes().len(),
))));
return;
}
// Check if a client-pin is already set, or if a new one should be created
let res = if authinfo.options.client_pin.unwrap_or_default() {
let mut res;
let mut error = PinError::PinRequired;
loop {
let current_pin = match Self::ask_user_for_pin(error, &status, &callback) {
Ok(pin) => pin,
_ => {
return;
}
};
res = ChangeExistingPin::new(
&authinfo,
&shared_secret,
¤t_pin,
&new_pin,
)
.map_err(HIDError::Command)
.and_then(|msg| dev.send_cbor(&msg))
.map_err(AuthenticatorError::HIDError)
.map_err(|e| repackage_pin_errors(&mut dev, e));
if let Err(AuthenticatorError::PinError(e)) = res {
error = e;
// We need to re-establish the shared secret for the next round.
match dev.establish_shared_secret() {
Ok((s, _)) => {
shared_secret = s;
}
Err(e) => {
callback.call(Err(AuthenticatorError::HIDError(e)));
return;
}
};
continue;
} else {
break;
}
}
res
} else {
SetNewPin::new(&authinfo, &shared_secret, &new_pin)
.map_err(HIDError::Command)
.and_then(|msg| dev.send_cbor(&msg))
.map_err(AuthenticatorError::HIDError)
};
callback.call(res);
},
);
self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e))));
}
}