summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/src/statemachine.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/authenticator/src/statemachine.rs')
-rw-r--r--third_party/rust/authenticator/src/statemachine.rs829
1 files changed, 829 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/src/statemachine.rs b/third_party/rust/authenticator/src/statemachine.rs
new file mode 100644
index 0000000000..e6a27a7550
--- /dev/null
+++ b/third_party/rust/authenticator/src/statemachine.rs
@@ -0,0 +1,829 @@
+/* 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
+ loop {
+ 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()?;
+ break;
+ }
+ 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) => {
+ break;
+ }
+ 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)));
+ return 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::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, |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)))
+ }
+ // TODO(baloo): if key_handle is invalid for this device, it
+ // should reply something like:
+ // CTAP2_ERR_INVALID_CREDENTIAL
+ // have to check
+ 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))));
+ }
+
+ // 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,
+ &current_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))));
+ }
+}