Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  mod.rs   Sprache: unbekannt

 
pub mod attestation;
pub mod client_data;
#[allow(dead_code)] // TODO(MS): Remove me asap
pub mod commands;
pub mod preflight;
pub mod server;
pub(crate) mod utils;

use crate::authenticatorservice::{RegisterArgs, SignArgs};
use crate::crypto::COSEAlgorithm;
use crate::ctap2::client_data::ClientDataHash;
use crate::ctap2::commands::authenticator_config::{
    AuthConfigCommand, AuthConfigResult, AuthenticatorConfig,
};
use crate::ctap2::commands::bio_enrollment::{
    BioEnrollment, BioEnrollmentCommand, BioEnrollmentResult, FingerprintSensorInfo,
};
use crate::ctap2::commands::client_pin::{
    ChangeExistingPin, Pin, PinError, PinUvAuthTokenPermission, SetNewPin,
};
use crate::ctap2::commands::credential_management::{
    CredManagementCommand, CredentialList, CredentialListEntry, CredentialManagement,
    CredentialManagementResult, CredentialRpListEntry,
};
use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionOptions};
use crate::ctap2::commands::make_credentials::{
    dummy_make_credentials_cmd, MakeCredentials, MakeCredentialsOptions,
};
use crate::ctap2::commands::reset::Reset;
use crate::ctap2::commands::{
    repackage_pin_errors, CommandError, PinUvAuthCommand, PinUvAuthResult, RequestCtap2, StatusCode,
};
use crate::ctap2::preflight::{
    do_credential_list_filtering_ctap1, do_credential_list_filtering_ctap2,
    silently_discover_credentials,
};
use crate::ctap2::server::{
    CredentialProtectionPolicy, RelyingParty, ResidentKeyRequirement, UserVerificationRequirement,
};
use crate::errors::{AuthenticatorError, UnsupportedOption};
use crate::statecallback::StateCallback;
use crate::status_update::{send_status, BioEnrollmentCmd, CredManagementCmd, InteractiveUpdate};
use crate::transport::device_selector::{Device, DeviceSelectorEvent};
use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, FidoDeviceIO, FidoProtocol};
use crate::{ManageResult, ResetResult, StatusPinUv, StatusUpdate};
use std::sync::mpsc::{channel, RecvError, Sender};
use std::thread;
use std::time::Duration;

use self::commands::get_info::AuthenticatorVersion;

macro_rules! unwrap_option {
    ($item: expr, $callback: expr) => {
        match $item {
            Some(r) => r,
            None => {
                $callback.call(Err(AuthenticatorError::Platform));
                return false;
            }
        }
    };
}

macro_rules! unwrap_result {
    ($item: expr, $callback: expr) => {
        match $item {
            Ok(r) => r,
            Err(e) => {
                $callback.call(Err(e.into()));
                return false;
            }
        }
    };
}

macro_rules! handle_errors {
    ($error: expr, $status: expr, $callback: expr, $pin_uv_auth_result: expr, $skip_uv: expr) => {
        let mut _dummy_skip_puap = false;
        let mut _dummy_cached_puat = false;
        handle_errors!(
            $error,
            $status,
            $callback,
            $pin_uv_auth_result,
            $skip_uv,
            _dummy_skip_puap,
            _dummy_cached_puat
        )
    };
    ($error: expr, $status: expr, $callback: expr, $pin_uv_auth_result: expr, $skip_uv: expr, $skip_puap: expr) => {
        let mut _dummy_cached_puat = false;
        handle_errors!(
            $error,
            $status,
            $callback,
            $pin_uv_auth_result,
            $skip_uv,
            $skip_puap,
            _dummy_cached_puat
        )
    };
    ($error: expr, $status: expr, $callback: expr, $pin_uv_auth_result: expr, $skip_uv: expr, $skip_puap: expr, $cached_puat: expr) => {
        match $error {
            HIDError::Command(CommandError::StatusCode(StatusCode::ChannelBusy, _)) => {
                // Channel busy. Client SHOULD retry the request after a short delay.
                thread::sleep(Duration::from_millis(100));
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::OperationDenied, _))
            | HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _))
                if matches!($pin_uv_auth_result, PinUvAuthResult::UsingInternalUv) =>
            {
                // This should only happen for CTAP2.0 tokens that use internal UV and failed
                // (e.g. wrong fingerprint used), while doing GetAssertion or MakeCredentials.
                send_status(
                    &$status,
                    StatusUpdate::PinUvError(StatusPinUv::InvalidUv(None)),
                );
                $skip_puap = false;
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::PinRequired, _))
                if matches!($pin_uv_auth_result, PinUvAuthResult::UsingInternalUv) =>
            {
                // This should only happen for CTAP2.0 tokens that use internal UV and failed
                // repeatedly, so that we have to fall back to PINs
                $skip_uv = true;
                $skip_puap = false;
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::UvBlocked, _))
                if matches!(
                    $pin_uv_auth_result,
                    PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(..)
                ) =>
            {
                // This should only happen for CTAP2.1 tokens that use internal UV and failed
                // repeatedly, so that we have to fall back to PINs
                $skip_uv = true;
                $skip_puap = false;
                continue;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::CredentialExcluded, _)) => {
                $callback.call(Err(AuthenticatorError::CredentialExcluded));
                break;
            }
            HIDError::Command(CommandError::StatusCode(StatusCode::PinAuthInvalid, _))
                if $cached_puat =>
            {
                // We used the cached PUAT, but it was invalid. So we just try again
                // without the cached one and potentially trigger a new PIN/UV entry from
                // the user. This happens e.g. if the PUAT expires, or we get an all-zeros
                // PUAT from outside for whatever reason, etc.
                $cached_puat = false;
                $skip_puap = false;
                continue;
            }
            e => {
                warn!("error happened: {e}");
                $callback.call(Err(AuthenticatorError::HIDError(e)));
                break;
            }
        }
    };
}

fn ask_user_for_pin(
    was_invalid: bool,
    retries: Option<u8>,
    status: &Sender<StatusUpdate>,
) -> Result<Pin, AuthenticatorError> {
    info!("PIN Error that requires user interaction detected. Sending it back and waiting for a reply");
    let (tx, rx) = channel();
    if was_invalid {
        send_status(
            status,
            crate::StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx, retries)),
        );
    } else {
        send_status(
            status,
            crate::StatusUpdate::PinUvError(StatusPinUv::PinRequired(tx)),
        );
    }
    match rx.recv() {
        Ok(pin) => Ok(pin),
        Err(RecvError) => {
            // recv() can only fail, if the other side is dropping the Sender.
            info!("Callback dropped the channel. Aborting.");
            Err(AuthenticatorError::CancelledByUser)
        }
    }
}

/// Try to fetch PinUvAuthToken from the device and derive from it PinUvAuthParam.
/// Prefer UV, fallback to PIN.
/// Prefer newer pinUvAuth-methods, if supported by the device.
fn get_pin_uv_auth_param<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2>(
    cmd: &mut T,
    dev: &mut Dev,
    permission: PinUvAuthTokenPermission,
    skip_uv: bool,
    uv_req: UserVerificationRequirement,
    alive: &dyn Fn() -> bool,
    pin: &Option<Pin>,
) -> Result<PinUvAuthResult, AuthenticatorError> {
    // CTAP 2.1 is very specific that the request should either include pinUvAuthParam
    // OR uv=true, but not both at the same time. We now have to decide which (if either)
    // to send. We may omit both values. Will never send an explicit uv=false, because
    //  a) this is the default, and
    //  b) some CTAP 2.0 authenticators return UnsupportedOption when uv=false.

    // We ensure both pinUvAuthParam and uv are not set to start.
    cmd.set_pin_uv_auth_param(None)?;
    cmd.set_uv_option(None);

    // Skip user verification if we're using CTAP1 or if the device does not support CTAP2.
    let info = match (dev.get_protocol(), dev.get_authenticator_info()) {
        (FidoProtocol::CTAP2, Some(info)) => info,
        _ => return Ok(PinUvAuthResult::DeviceIsCtap1),
    };

    // Only use UV, if the device supports it and we don't skip it
    // which happens as a fallback, if UV-usage failed too many times
    // Note: In theory, we could also repeatedly query GetInfo here and check
    //       if uv is set to Some(true), as tokens should set it to Some(false)
    //       if UV is blocked (too many failed attempts). But the CTAP2.0-spec is
    //       vague and I don't trust all tokens to implement it that way. So we
    //       keep track of it ourselves, using `skip_uv`.
    let supports_uv = info.options.user_verification == Some(true);
    let supports_pin = info.options.client_pin.is_some();
    let pin_configured = info.options.client_pin == Some(true);

    // Check if the combination of device-protection and request-options
    // are allowing for 'discouraged', meaning no auth required.
    if cmd.can_skip_user_verification(info, uv_req) {
        return Ok(PinUvAuthResult::NoAuthRequired);
    }

    // Device does not support any (remaining) auth-method
    if (skip_uv || !supports_uv) && !supports_pin {
        if supports_uv && uv_req == UserVerificationRequirement::Required {
            // We should always set the uv option in the Required case, but the CTAP 2.1 spec
            // says 'Platforms MUST NOT include the "uv" option key if the authenticator does
            // not support built-in user verification.' This is to work around some CTAP 2.0
            // authenticators which incorrectly error out with CTAP2_ERR_UNSUPPORTED_OPTION
            // when the "uv" option is set. The RP that requested UV will (hopefully) reject our
            // response in the !supports_uv case.
            cmd.set_uv_option(Some(true));
        }
        return Ok(PinUvAuthResult::NoAuthTypeSupported);
    }

    // Device supports PINs, but a PIN is not configured. Signal that we
    // can complete the operation if the user sets a PIN first.
    if (skip_uv || !supports_uv) && !pin_configured {
        return Err(AuthenticatorError::PinError(PinError::PinNotSet));
    }

    if info.options.pin_uv_auth_token == Some(true) {
        if !skip_uv && supports_uv {
            // CTAP 2.1 - UV
            let pin_auth_token = dev
                .get_pin_uv_auth_token_using_uv_with_permissions(permission, cmd.get_rp_id(), alive)
                .map_err(|e| repackage_pin_errors(dev, e))?;
            cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
            Ok(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(pin_auth_token))
        } else {
            // CTAP 2.1 - PIN
            // We did not take the `!skip_uv && supports_uv` branch, so we have
            // `(skip_uv || !supports_uv)`. Moreover we did not exit early in the
            // `(skip_uv || !supports_uv) && !pin_configured` case. So we have
            // `pin_configured`.
            let pin_auth_token = dev
                .get_pin_uv_auth_token_using_pin_with_permissions(
                    pin,
                    permission,
                    cmd.get_rp_id(),
                    alive,
                )
                .map_err(|e| repackage_pin_errors(dev, e))?;
            cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
            Ok(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(pin_auth_token))
        }
    } else {
        // CTAP 2.0 fallback
        if !skip_uv && supports_uv && pin.is_none() {
            // If the device supports internal user-verification (e.g. fingerprints),
            // skip PIN-stuff

            // We may need the shared secret for HMAC-extension, so we
            // have to establish one
            if info.supports_hmac_secret() {
                let _shared_secret = dev.establish_shared_secret(alive)?;
            }
            // CTAP 2.1, Section 6.1.1, Step 1.1.2.1.2.
            cmd.set_uv_option(Some(true));
            return Ok(PinUvAuthResult::UsingInternalUv);
        }

        let pin_auth_token = dev
            .get_pin_token(pin, alive)
            .map_err(|e| repackage_pin_errors(dev, e))?;
        cmd.set_pin_uv_auth_param(Some(pin_auth_token.clone()))?;
        Ok(PinUvAuthResult::SuccessGetPinToken(pin_auth_token))
    }
}

/// PUAP, as per spec: PinUvAuthParam
/// Determines, if we need to establish a PinUvAuthParam, based on the
/// capabilities of the device and the incoming request.
/// If it is needed, tries to establish one and save it inside the Request.
/// Returns Ok() if we can proceed with sending the actual Request to
/// the device, Err() otherwise.
/// Handles asking the user for a PIN, if needed and sending StatusUpdates
/// regarding PIN and UV usage.
#[allow(clippy::too_many_arguments)]
fn determine_puap_if_needed<Dev: FidoDevice, T: PinUvAuthCommand + RequestCtap2>(
    cmd: &mut T,
    dev: &mut Dev,
    mut skip_uv: bool,
    permission: PinUvAuthTokenPermission,
    uv_req: UserVerificationRequirement,
    status: &Sender<StatusUpdate>,
    alive: &dyn Fn() -> bool,
    pin: &mut Option<Pin>,
) -> Result<PinUvAuthResult, AuthenticatorError> {
    while alive() {
        debug!("-----------------------------------------------------------------");
        debug!("Getting pinUvAuthParam");
        match get_pin_uv_auth_param(cmd, dev, permission, skip_uv, uv_req, alive, pin) {
            Ok(r) => {
                return Ok(r);
            }

            Err(AuthenticatorError::PinError(PinError::PinRequired)) => {
                let new_pin = ask_user_for_pin(false, None, status)?;
                *pin = Some(new_pin);
                skip_uv = true;
                continue;
            }
            Err(AuthenticatorError::PinError(PinError::InvalidPin(retries))) => {
                let new_pin = ask_user_for_pin(true, retries, status)?;
                *pin = Some(new_pin);
                continue;
            }
            Err(AuthenticatorError::PinError(PinError::InvalidUv(retries))) => {
                if retries == Some(0) {
                    skip_uv = true;
                }
                send_status(
                    status,
                    StatusUpdate::PinUvError(StatusPinUv::InvalidUv(retries)),
                )
            }
            Err(e @ AuthenticatorError::PinError(PinError::PinAuthBlocked)) => {
                send_status(
                    status,
                    StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked),
                );
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
            Err(e @ AuthenticatorError::PinError(PinError::PinBlocked)) => {
                send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinBlocked));
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
            Err(e @ AuthenticatorError::PinError(PinError::PinNotSet)) => {
                send_status(status, StatusUpdate::PinUvError(StatusPinUv::PinNotSet));
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
            Err(AuthenticatorError::PinError(PinError::UvBlocked)) => {
                skip_uv = true;
                send_status(status, StatusUpdate::PinUvError(StatusPinUv::UvBlocked))
            }
            // Used for CTAP2.0 UV (fingerprints)
            Err(AuthenticatorError::PinError(PinError::PinAuthInvalid)) => {
                skip_uv = true;
                send_status(
                    status,
                    StatusUpdate::PinUvError(StatusPinUv::InvalidUv(None)),
                )
            }
            Err(e) => {
                error!("Error when determining pinAuth: {:?}", e);
                return Err(e);
            }
        }
    }
    Err(AuthenticatorError::CancelledByUser)
}

pub fn register<Dev: FidoDevice>(
    dev: &mut Dev,
    args: RegisterArgs,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::RegisterResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let mut options = MakeCredentialsOptions::default();

    if dev.get_protocol() == FidoProtocol::CTAP2 {
        let info = match dev.get_authenticator_info() {
            Some(info) => info,
            None => {
                callback.call(Err(HIDError::DeviceNotInitialized.into()));
                return false;
            }
        };

        // Set options based on the arguments and the device info.
        // The user verification option will be set in `determine_puap_if_needed`.
        options.resident_key = match args.resident_key_req {
            ResidentKeyRequirement::Required => Some(true),
            ResidentKeyRequirement::Preferred => {
                // Use a resident key if the authenticator supports it
                Some(info.options.resident_key)
            }
            ResidentKeyRequirement::Discouraged => Some(false),
        }
    } else {
        // Check that the request can be processed by a CTAP1 device.
        // See CTAP 2.1 Section 10.2. Some additional checks are performed in
        // MakeCredentials::RequestCtap1
        if args.resident_key_req == ResidentKeyRequirement::Required {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::ResidentKey,
            )));
            return false;
        }
        if args.user_verification_req == UserVerificationRequirement::Required {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::UserVerification,
            )));
            return false;
        }
        if !args
            .pub_cred_params
            .iter()
            .any(|x| x.alg == COSEAlgorithm::ES256)
        {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::PubCredParams,
            )));
            return false;
        }
    }

    // Client extension processing for credProtect:
    // "When enforceCredentialProtectionPolicy is true, and credentialProtectionPolicy's value is
    // [not "Optional"], the platform SHOULD NOT create the credential in a way that does not
    // implement the requested protection policy. (For example, by creating it on an authenticator
    // that does not support this extension.)"
    let dev_supports_cred_protect = dev
        .get_authenticator_info()
        .map_or(false, |info| info.supports_cred_protect());
    if args.extensions.enforce_credential_protection_policy == Some(true)
        && args.extensions.credential_protection_policy
            != Some(CredentialProtectionPolicy::UserVerificationOptional)
        && !dev_supports_cred_protect
    {
        callback.call(Err(AuthenticatorError::UnsupportedOption(
            UnsupportedOption::CredProtect,
        )));
        return false;
    }

    let mut makecred = MakeCredentials::new(
        ClientDataHash(args.client_data_hash),
        args.relying_party,
        Some(args.user),
        args.pub_cred_params,
        args.exclude_list,
        options,
        args.extensions.into(),
    );

    let mut skip_uv = false;
    let mut pin = args.pin;
    while alive() {
        // Requesting both because pre-flighting (credential list filtering)
        // can potentially send GetAssertion-commands
        let permissions =
            PinUvAuthTokenPermission::MakeCredential | PinUvAuthTokenPermission::GetAssertion;

        let pin_uv_auth_result = unwrap_result!(
            determine_puap_if_needed(
                &mut makecred,
                dev,
                skip_uv,
                permissions,
                args.user_verification_req,
                &status,
                alive,
                &mut pin,
            ),
            callback
        );
        // Do "pre-flight": Filter the exclude-list
        if dev.get_protocol() == FidoProtocol::CTAP2 {
            makecred.exclude_list = unwrap_result!(
                do_credential_list_filtering_ctap2(
                    dev,
                    &makecred.exclude_list,
                    &makecred.rp,
                    pin_uv_auth_result.get_pin_uv_auth_token(),
                ),
                callback
            );
        } else {
            let key_handle = do_credential_list_filtering_ctap1(
                dev,
                &makecred.exclude_list,
                &makecred.rp,
                &makecred.client_data_hash,
            );
            // That handle was already registered with the token
            if key_handle.is_some() {
                // Now we need to send a dummy registration request, to make the token blink
                // Spec says "dummy appid and invalid challenge". We use the same, as we do for
                // making the token blink upon device selection.
                send_status(&status, crate::StatusUpdate::PresenceRequired);
                let msg = dummy_make_credentials_cmd();
                let _ = dev.send_msg_cancellable(&msg, alive); // Ignore answer, return "CredentialExcluded"
                callback.call(Err(AuthenticatorError::CredentialExcluded));
                return false;
            }
        }

        debug!("------------------------------------------------------------------");
        debug!("{makecred:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");
        send_status(&status, crate::StatusUpdate::PresenceRequired);
        let resp = dev.send_msg_cancellable(&makecred, alive);
        match resp {
            Ok(result) => {
                callback.call(Ok(result));
                return true;
            }
            Err(e) => {
                handle_errors!(e, status, callback, pin_uv_auth_result, skip_uv);
            }
        }
    }
    false
}

pub fn sign<Dev: FidoDevice>(
    dev: &mut Dev,
    args: SignArgs,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::SignResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    if dev.get_protocol() == FidoProtocol::CTAP1 {
        // Check that the request can be processed by a CTAP1 device.
        // See CTAP 2.1 Section 10.3. Some additional checks are performed in
        // GetAssertion::RequestCtap1
        if args.user_verification_req == UserVerificationRequirement::Required {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::UserVerification,
            )));
            return false;
        }
        if args.allow_list.is_empty() {
            callback.call(Err(AuthenticatorError::UnsupportedOption(
                UnsupportedOption::EmptyAllowList,
            )));
            return false;
        }
    }

    let mut allow_list = args.allow_list;
    let mut rp_id = RelyingParty::from(args.relying_party_id);
    let client_data_hash = ClientDataHash(args.client_data_hash);
    if let Some(ref app_id) = args.extensions.app_id {
        if !allow_list.is_empty() {
            // Try to silently discover U2F credentials that require the FIDO App ID extension. If
            // any are found, we should use the alternate RP ID instead of the provided RP ID.
            let alt_rp_id = RelyingParty::from(app_id);
            let silent_creds =
                silently_discover_credentials(dev, &allow_list, &alt_rp_id, &client_data_hash);
            if !silent_creds.is_empty() {
                allow_list = silent_creds;
                rp_id = alt_rp_id;
            }
        }
    }

    let mut get_assertion = GetAssertion::new(
        client_data_hash,
        rp_id,
        allow_list,
        GetAssertionOptions {
            user_presence: Some(args.user_presence_req),
            user_verification: None,
        },
        args.extensions.into(),
    );

    let mut skip_uv = false;
    let mut pin = args.pin;
    while alive() {
        let pin_uv_auth_result = unwrap_result!(
            determine_puap_if_needed(
                &mut get_assertion,
                dev,
                skip_uv,
                PinUvAuthTokenPermission::GetAssertion,
                args.user_verification_req,
                &status,
                alive,
                &mut pin,
            ),
            callback
        );

        // Do "pre-flight": Filter the allow-list
        let original_allow_list_was_empty = get_assertion.allow_list.is_empty();
        if dev.get_protocol() == FidoProtocol::CTAP2 {
            get_assertion.allow_list = unwrap_result!(
                do_credential_list_filtering_ctap2(
                    dev,
                    &get_assertion.allow_list,
                    &get_assertion.rp,
                    pin_uv_auth_result.get_pin_uv_auth_token(),
                ),
                callback
            );
        } else {
            let key_handle = do_credential_list_filtering_ctap1(
                dev,
                &get_assertion.allow_list,
                &get_assertion.rp,
                &get_assertion.client_data_hash,
            );
            match key_handle {
                Some(key_handle) => {
                    get_assertion.allow_list = vec![key_handle];
                }
                None => {
                    get_assertion.allow_list.clear();
                }
            }
        }

        // If the incoming list was not empty, but the filtered list is, we have to error out
        if !original_allow_list_was_empty && get_assertion.allow_list.is_empty() {
            // We have to collect a user interaction
            send_status(&status, crate::StatusUpdate::PresenceRequired);
            let msg = dummy_make_credentials_cmd();
            let _ = dev.send_msg_cancellable(&msg, alive); // Ignore answer, return "NoCredentials"
            callback.call(Err(HIDError::Command(CommandError::StatusCode(
                StatusCode::NoCredentials,
                None,
            ))
            .into()));
            return false;
        }

        // Use the shared secret in the extensions, if requested
        get_assertion = match get_assertion.process_hmac_secret_and_prf_extension(
            dev.get_shared_secret().map(|s| (s, &pin_uv_auth_result)),
        ) {
            Ok(value) => value,
            Err(e) => {
                callback.call(Err(e));
                return false;
            }
        };

        debug!("------------------------------------------------------------------");
        debug!("{get_assertion:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");
        send_status(&status, crate::StatusUpdate::PresenceRequired);
        let mut results = match dev.send_msg_cancellable(&get_assertion, alive) {
            Ok(results) => results,
            Err(e) => {
                handle_errors!(e, status, callback, pin_uv_auth_result, skip_uv);
            }
        };
        if results.len() == 1 {
            callback.call(Ok(results.swap_remove(0)));
            return true;
        }
        let (tx, rx) = channel();
        let user_entities = results
            .iter()
            .filter_map(|x| x.assertion.user.clone())
            .collect();
        send_status(
            &status,
            crate::StatusUpdate::SelectResultNotice(tx, user_entities),
        );
        match rx.recv() {
            Ok(Some(index)) if index < results.len() => {
                callback.call(Ok(results.swap_remove(index)));
                return true;
            }
            _ => {
                callback.call(Err(AuthenticatorError::CancelledByUser));
                return true;
            }
        }
    }
    false
}

pub(crate) fn reset_helper<T: From<ResetResult>>(
    dev: &mut Device,
    selector: Sender<DeviceSelectorEvent>,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<T>>,
    keep_alive: &dyn Fn() -> bool,
) {
    let reset = Reset {};
    info!("Device {:?} continues with the reset process", dev.id());

    debug!("------------------------------------------------------------------");
    debug!("{:?}", reset);
    debug!("------------------------------------------------------------------");
    send_status(&status, crate::StatusUpdate::PresenceRequired);
    let resp = dev.send_cbor_cancellable(&reset, keep_alive);
    if resp.is_ok() {
        // 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(T::from(()))),
        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)));
        }
    }
}

pub(crate) fn set_or_change_pin_helper<T: From<()>>(
    dev: &mut Device,
    mut current_pin: Option<Pin>,
    new_pin: Pin,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<T>>,
    alive: &dyn Fn() -> bool,
) {
    let mut shared_secret = match dev.establish_shared_secret(alive) {
        Ok(s) => s,
        Err(e) => {
            callback.call(Err(AuthenticatorError::HIDError(e)));
            return;
        }
    };

    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i.clone(),
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return;
        }
    };

    // If the device has a min PIN use that, otherwise default to 4 according to Spec
    if new_pin.as_bytes().len() < authinfo.min_pin_length.unwrap_or(4) as usize {
        callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort)));
        return;
    }

    // As per Spec: "Maximum PIN Length: UTF-8 representation MUST NOT exceed 63 bytes"
    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 Some(true) == authinfo.options.client_pin {
        let mut res;
        let mut was_invalid = false;
        let mut retries = None;
        loop {
            // current_pin will only be Some() in the interactive mode (running `manage()`)
            // In case that PIN is wrong, we want to avoid an endless-loop here with re-trying
            // that wrong PIN all the time. So we `take()` it, and only test it once.
            // If that PIN is wrong, we fall back to the "ask_user_for_pin"-method.
            let curr_pin = match current_pin.take() {
                None => match ask_user_for_pin(was_invalid, retries, &status) {
                    Ok(pin) => pin,
                    Err(e) => {
                        callback.call(Err(e));
                        return;
                    }
                },
                Some(pin) => pin,
            };

            res = ChangeExistingPin::new(&authinfo, &shared_secret, &curr_pin, &new_pin)
                .map_err(HIDError::Command)
                .and_then(|msg| dev.send_cbor_cancellable(&msg, alive))
                .map_err(|e| repackage_pin_errors(dev, e));

            if let Err(AuthenticatorError::PinError(PinError::InvalidPin(r))) = res {
                was_invalid = true;
                retries = r;
                // We need to re-establish the shared secret for the next round.
                match dev.establish_shared_secret(alive) {
                    Ok(s) => {
                        shared_secret = s;
                    }
                    Err(e) => {
                        callback.call(Err(AuthenticatorError::HIDError(e)));
                        return;
                    }
                };

                continue;
            } else {
                break;
            }
        }
        res
    } else {
        dev.send_cbor_cancellable(&SetNewPin::new(&shared_secret, &new_pin), alive)
            .map_err(AuthenticatorError::HIDError)
    };
    // the callback is expecting `Result<(), AuthenticatorError>`, but `ChangeExistingPin`
    // and `SetNewPin` return the default `ClientPinResponse` on success. Just discard it.
    callback.call(res.map(|_| T::from(())));
}

pub(crate) fn bio_enrollment(
    dev: &mut Device,
    puat_result: Option<PinUvAuthResult>,
    command: BioEnrollmentCmd,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::ManageResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i,
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return false;
        }
    };

    if authinfo.options.bio_enroll.is_none()
        && authinfo.options.user_verification_mgmt_preview.is_none()
    {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    let use_legacy_preview = authinfo.options.bio_enroll.is_none();

    // We are not allowed to request the BE-permission using UV, so we have to skip UV
    let mut skip_uv = authinfo.options.uv_bio_enroll != Some(true);
    // Currently not used, but if we want, we can just set the value here.
    let timeout = None;

    let mut bio_cmd = match &command {
        BioEnrollmentCmd::StartNewEnrollment(_name) => BioEnrollment::new(
            BioEnrollmentCommand::EnrollBegin(timeout),
            use_legacy_preview,
        ),
        BioEnrollmentCmd::DeleteEnrollment(id) => BioEnrollment::new(
            BioEnrollmentCommand::RemoveEnrollment(id.clone()),
            use_legacy_preview,
        ),
        BioEnrollmentCmd::ChangeName(id, name) => BioEnrollment::new(
            BioEnrollmentCommand::SetFriendlyName((id.clone(), name.clone())),
            use_legacy_preview,
        ),
        BioEnrollmentCmd::GetEnrollments => BioEnrollment::new(
            BioEnrollmentCommand::EnumerateEnrollments,
            use_legacy_preview,
        ),
        BioEnrollmentCmd::GetFingerprintSensorInfo => BioEnrollment::new(
            BioEnrollmentCommand::GetFingerprintSensorInfo,
            use_legacy_preview,
        ),
    };

    let mut skip_puap = false;
    let mut cached_puat = false; // If we were provided with a cached puat from the outside
    let mut pin_uv_auth_result = puat_result
        .clone()
        .unwrap_or(PinUvAuthResult::NoAuthRequired);
    // See, if we have a cached PUAT with matching permissions.
    match puat_result {
        Some(PinUvAuthResult::SuccessGetPinToken(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
            if !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1) // Only 2.1 has a permission-system
                || use_legacy_preview // Preview doesn't use permissions
                || t.permissions
                    .contains(PinUvAuthTokenPermission::BioEnrollment) =>
        {
            skip_puap = true;
            cached_puat = true;
            unwrap_result!(bio_cmd.set_pin_uv_auth_param(Some(t)), callback);
        }
        _ => {}
    }
    let mut pin = None;
    while alive() {
        if !skip_puap {
            pin_uv_auth_result = unwrap_result!(
                determine_puap_if_needed(
                    &mut bio_cmd,
                    dev,
                    skip_uv,
                    PinUvAuthTokenPermission::BioEnrollment,
                    UserVerificationRequirement::Preferred,
                    &status,
                    alive,
                    &mut pin,
                ),
                callback
            );
        }

        debug!("------------------------------------------------------------------");
        debug!("{bio_cmd:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");

        let resp = dev.send_cbor_cancellable(&bio_cmd, alive);
        match resp {
            Ok(result) => {
                skip_puap = true;
                match bio_cmd.subcommand {
                    BioEnrollmentCommand::EnrollBegin(..)
                    | BioEnrollmentCommand::EnrollCaptureNextSample(..) => {
                        let template_id =
                            if let BioEnrollmentCommand::EnrollCaptureNextSample((id, ..)) =
                                bio_cmd.subcommand
                            {
                                id
                            } else {
                                unwrap_option!(result.template_id, callback)
                            };
                        let last_enroll_sample_status =
                            unwrap_option!(result.last_enroll_sample_status, callback);
                        let remaining_samples = unwrap_option!(result.remaining_samples, callback);

                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::SampleStatus(
                                        last_enroll_sample_status,
                                        remaining_samples,
                                    ),
                                    Some(pin_uv_auth_result.clone()),
                                )),
                            ),
                        );

                        if remaining_samples == 0 {
                            if let BioEnrollmentCmd::StartNewEnrollment(Some(ref name)) = command {
                                bio_cmd.subcommand = BioEnrollmentCommand::SetFriendlyName((
                                    template_id.to_vec(),
                                    name.clone(),
                                ));
                                // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                                // of the command has changed, and that is part of the PUAP-calculation
                                unwrap_result!(
                                    bio_cmd.set_pin_uv_auth_param(
                                        pin_uv_auth_result.get_pin_uv_auth_token()
                                    ),
                                    callback
                                );
                                continue;
                            } else {
                                let auth_info =
                                    unwrap_option!(dev.refresh_authenticator_info(), callback);
                                send_status(
                                    &status,
                                    StatusUpdate::InteractiveManagement(
                                        InteractiveUpdate::BioEnrollmentUpdate((
                                            BioEnrollmentResult::AddSuccess(auth_info.clone()),
                                            Some(pin_uv_auth_result),
                                        )),
                                    ),
                                );
                                return true;
                            }
                        } else {
                            bio_cmd.subcommand = BioEnrollmentCommand::EnrollCaptureNextSample((
                                template_id,
                                timeout,
                            ));
                            // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                            // of the command has changed, and that is part of the PUAP-calculation
                            unwrap_result!(
                                bio_cmd.set_pin_uv_auth_param(
                                    pin_uv_auth_result.get_pin_uv_auth_token()
                                ),
                                callback
                            );
                            continue;
                        }
                    }
                    BioEnrollmentCommand::EnumerateEnrollments => {
                        let list = result.template_infos.iter().map(|x| x.into()).collect();
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::EnrollmentList(list),
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    BioEnrollmentCommand::SetFriendlyName(_) => {
                        let res = match command {
                            BioEnrollmentCmd::StartNewEnrollment(..) => {
                                let auth_info =
                                    unwrap_option!(dev.refresh_authenticator_info(), callback);
                                BioEnrollmentResult::AddSuccess(auth_info.clone())
                            }
                            _ => BioEnrollmentResult::UpdateSuccess,
                        };
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    res,
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    BioEnrollmentCommand::RemoveEnrollment(_) => {
                        let auth_info = unwrap_option!(dev.refresh_authenticator_info(), callback);
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::DeleteSuccess(auth_info.clone()),
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    BioEnrollmentCommand::CancelCurrentEnrollment => {
                        callback.call(Ok(ManageResult::Success));
                        return true;
                    }
                    BioEnrollmentCommand::GetFingerprintSensorInfo => {
                        let fingerprint_kind = unwrap_option!(result.fingerprint_kind, callback);
                        let max_capture_samples_required_for_enroll = unwrap_option!(
                            result.max_capture_samples_required_for_enroll,
                            callback
                        );
                        // FIDO_2_1_PRE-devices do not report this field. So we leave it optional.
                        let max_template_friendly_name = result.max_template_friendly_name;
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::BioEnrollmentUpdate((
                                    BioEnrollmentResult::FingerprintSensorInfo(
                                        FingerprintSensorInfo {
                                            fingerprint_kind,
                                            max_capture_samples_required_for_enroll,
                                            max_template_friendly_name,
                                        },
                                    ),
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                };
            }
            Err(e) => {
                handle_errors!(
                    e,
                    status,
                    callback,
                    pin_uv_auth_result,
                    skip_uv,
                    skip_puap,
                    cached_puat
                );
            }
        }
    }
    false
}

pub(crate) fn credential_management(
    dev: &mut Device,
    puat_result: Option<PinUvAuthResult>,
    command: CredManagementCmd,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::ManageResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let mut skip_uv = false;
    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i.clone(),
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return false;
        }
    };

    if authinfo.options.cred_mgmt != Some(true)
        && authinfo.options.credential_mgmt_preview != Some(true)
    {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    let use_legacy_preview = authinfo.options.cred_mgmt != Some(true);

    // FIDO_2_1_PRE-devices do not support UpdateUserInformation.
    if use_legacy_preview
        && !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1)
        && matches!(command, CredManagementCmd::UpdateUserInformation(..))
    {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    // If puap is provided, we can skip puap-determination (i.e. PIN entry)
    let mut cred_management = match command {
        CredManagementCmd::GetCredentials => {
            CredentialManagement::new(CredManagementCommand::GetCredsMetadata, use_legacy_preview)
        }
        CredManagementCmd::DeleteCredential(cred_id) => CredentialManagement::new(
            CredManagementCommand::DeleteCredential(cred_id),
            use_legacy_preview,
        ),
        CredManagementCmd::UpdateUserInformation(cred_id, user) => CredentialManagement::new(
            CredManagementCommand::UpdateUserInformation((cred_id, user)),
            use_legacy_preview,
        ),
    };
    let mut credential_result = CredentialList::new();
    let mut remaining_rps = 0;
    let mut remaining_cred_ids = 0;
    let mut current_rp = 0;
    let mut skip_puap = false;
    let mut cached_puat = false; // If we were provided with a cached puat from the outside
    let mut pin_uv_auth_result = puat_result
        .clone()
        .unwrap_or(PinUvAuthResult::NoAuthRequired);
    // See, if we have a cached PUAT with matching permissions.
    match puat_result {
        Some(PinUvAuthResult::SuccessGetPinToken(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
            if !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1) // Only 2.1 has a permission-system
                || use_legacy_preview // Preview doesn't use permissions
                || t.permissions
                    .contains(PinUvAuthTokenPermission::CredentialManagement) =>
        {
            skip_puap = true;
            cached_puat = true;
            unwrap_result!(cred_management.set_pin_uv_auth_param(Some(t)), callback);
        }
        _ => {}
    }
    let mut pin = None;
    while alive() {
        if !skip_puap {
            pin_uv_auth_result = unwrap_result!(
                determine_puap_if_needed(
                    &mut cred_management,
                    dev,
                    skip_uv,
                    PinUvAuthTokenPermission::CredentialManagement,
                    UserVerificationRequirement::Preferred,
                    &status,
                    alive,
                    &mut pin,
                ),
                callback
            );
        }

        debug!("------------------------------------------------------------------");
        debug!("{cred_management:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");

        let resp = dev.send_cbor_cancellable(&cred_management, alive);
        match resp {
            Ok(result) => {
                skip_puap = true;
                match cred_management.subcommand {
                    CredManagementCommand::GetCredsMetadata => {
                        let existing_resident_credentials_count =
                            unwrap_option!(result.existing_resident_credentials_count, callback);
                        let max_possible_remaining_resident_credentials_count = unwrap_option!(
                            result.max_possible_remaining_resident_credentials_count,
                            callback
                        );
                        credential_result.existing_resident_credentials_count =
                            existing_resident_credentials_count;
                        credential_result.max_possible_remaining_resident_credentials_count =
                            max_possible_remaining_resident_credentials_count;
                        if existing_resident_credentials_count > 0 {
                            cred_management.subcommand = CredManagementCommand::EnumerateRPsBegin;
                            // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                            // of the command has changed, and that is part of the PUAP-calculation
                            unwrap_result!(
                                cred_management.set_pin_uv_auth_param(
                                    pin_uv_auth_result.get_pin_uv_auth_token()
                                ),
                                callback
                            );
                            continue;
                        } else {
                            // This token doesn't have any resident keys, but its not an error,
                            // so we send an empty list.
                            send_status(
                                &status,
                                StatusUpdate::InteractiveManagement(
                                    InteractiveUpdate::CredentialManagementUpdate((
                                        CredentialManagementResult::CredentialList(
                                            credential_result,
                                        ),
                                        Some(pin_uv_auth_result),
                                    )),
                                ),
                            );
                            return true;
                        }
                    }
                    CredManagementCommand::EnumerateRPsBegin
                    | CredManagementCommand::EnumerateRPsGetNextRP => {
                        if matches!(
                            cred_management.subcommand,
                            CredManagementCommand::EnumerateRPsBegin
                        ) {
                            let total_rps = unwrap_option!(result.total_rps, callback);
                            if total_rps == 0 {
                                // This token doesn't have any RPs, but its not an error,
                                // so we return an Ok with an empty list.
                                send_status(
                                    &status,
                                    StatusUpdate::InteractiveManagement(
                                        InteractiveUpdate::CredentialManagementUpdate((
                                            CredentialManagementResult::CredentialList(
                                                credential_result,
                                            ),
                                            Some(pin_uv_auth_result),
                                        )),
                                    ),
                                );
                                return true;
                            }
                            remaining_rps = total_rps - 1;
                        } else {
                            remaining_rps -= 1;
                        }

                        let rp = unwrap_option!(result.rp, callback);
                        let rp_id_hash = unwrap_option!(result.rp_id_hash, callback);
                        let rp_res = CredentialRpListEntry {
                            rp,
                            rp_id_hash,
                            credentials: vec![],
                        };
                        credential_result.credential_list.push(rp_res);
                        if remaining_rps > 0 {
                            cred_management.subcommand =
                                CredManagementCommand::EnumerateRPsGetNextRP;
                        } else {
                            // We have queried all RPs, now start querying the corresponding credentials for each RP
                            cred_management.subcommand =
                                CredManagementCommand::EnumerateCredentialsBegin(
                                    credential_result.credential_list[0].rp_id_hash.clone(),
                                );
                        }
                        // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                        // of the command has changed, and that is part of the PUAP-calculation
                        unwrap_result!(
                            cred_management
                                .set_pin_uv_auth_param(pin_uv_auth_result.get_pin_uv_auth_token()),
                            callback
                        );
                        continue;
                    }
                    CredManagementCommand::EnumerateCredentialsBegin(..)
                    | CredManagementCommand::EnumerateCredentialsGetNextCredential => {
                        let user = unwrap_option!(result.user, callback);
                        let credential_id = unwrap_option!(result.credential_id, callback);
                        let public_key = unwrap_option!(result.public_key, callback);
                        let cred_protect = unwrap_option!(result.cred_protect, callback);
                        let large_blob_key = result.large_blob_key;

                        if matches!(
                            cred_management.subcommand,
                            CredManagementCommand::EnumerateCredentialsBegin(..)
                        ) {
                            remaining_cred_ids =
                                unwrap_option!(result.total_credentials, callback) - 1;
                        } else {
                            remaining_cred_ids -= 1;
                        }
                        // We might have to change the global variable, but need the unmodified below
                        let current_rp_backup = current_rp;
                        let mut we_are_done = false;
                        if remaining_cred_ids > 0 {
                            cred_management.subcommand =
                                CredManagementCommand::EnumerateCredentialsGetNextCredential;
                        } else {
                            current_rp += 1;
                            // We have all credentials from this RP. Starting with the next RP.
                            if current_rp < credential_result.credential_list.len() {
                                cred_management.subcommand =
                                    CredManagementCommand::EnumerateCredentialsBegin(
                                        credential_result.credential_list[current_rp]
                                            .rp_id_hash
                                            .clone(),
                                    );
                                // We have to regenerate PUAP here. PUAT hasn't changed, but the content
                                // of the command has changed, and that is part of the PUAP-calculation
                                unwrap_result!(
                                    cred_management.set_pin_uv_auth_param(
                                        pin_uv_auth_result.get_pin_uv_auth_token()
                                    ),
                                    callback
                                );
                            } else {
                                // Finally done iterating over all RPs and their Credentials
                                we_are_done = true;
                            }
                        }
                        let key = CredentialListEntry {
                            user,
                            credential_id,
                            public_key,
                            cred_protect,
                            large_blob_key,
                        };
                        credential_result.credential_list[current_rp_backup]
                            .credentials
                            .push(key);
                        if we_are_done {
                            send_status(
                                &status,
                                StatusUpdate::InteractiveManagement(
                                    InteractiveUpdate::CredentialManagementUpdate((
                                        CredentialManagementResult::CredentialList(
                                            credential_result,
                                        ),
                                        Some(pin_uv_auth_result),
                                    )),
                                ),
                            );
                            return true;
                        } else {
                            continue;
                        }
                    }
                    CredManagementCommand::DeleteCredential(_) => {
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::CredentialManagementUpdate((
                                    CredentialManagementResult::DeleteSucess,
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                    CredManagementCommand::UpdateUserInformation(_) => {
                        send_status(
                            &status,
                            StatusUpdate::InteractiveManagement(
                                InteractiveUpdate::CredentialManagementUpdate((
                                    CredentialManagementResult::UpdateSuccess,
                                    Some(pin_uv_auth_result),
                                )),
                            ),
                        );
                        return true;
                    }
                };
            }
            Err(e) => {
                handle_errors!(
                    e,
                    status,
                    callback,
                    pin_uv_auth_result,
                    skip_uv,
                    skip_puap,
                    cached_puat
                );
            }
        }
    }
    false
}

pub(crate) fn configure_authenticator(
    dev: &mut Device,
    puat_result: Option<PinUvAuthResult>,
    cfg_subcommand: AuthConfigCommand,
    status: Sender<crate::StatusUpdate>,
    callback: StateCallback<crate::Result<crate::ManageResult>>,
    alive: &dyn Fn() -> bool,
) -> bool {
    let mut authcfg = AuthenticatorConfig::new(cfg_subcommand);
    let mut skip_uv = false;
    let authinfo = match dev.get_authenticator_info() {
        Some(i) => i.clone(),
        None => {
            callback.call(Err(HIDError::DeviceNotInitialized.into()));
            return false;
        }
    };

    if authinfo.options.authnr_cfg != Some(true) {
        callback.call(Err(AuthenticatorError::HIDError(
            HIDError::UnsupportedCommand,
        )));
        return false;
    }

    let mut skip_puap = false;
    let mut cached_puat = false; // If we were provided with a cached puat from the outside
    let mut pin_uv_auth_result = puat_result
        .clone()
        .unwrap_or(PinUvAuthResult::NoAuthRequired);
    match puat_result {
        Some(PinUvAuthResult::SuccessGetPinToken(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
        | Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
            if t.permissions
                .contains(PinUvAuthTokenPermission::AuthenticatorConfiguration) =>
        {
            skip_puap = true;
            cached_puat = true;
            unwrap_result!(authcfg.set_pin_uv_auth_param(Some(t)), callback);
        }
        _ => {}
    }
    let mut pin = None;
    while alive() {
        // We can use the AuthenticatorConfiguration-command only in two cases:
        // 1. The device also supports the uv_acfg-permission (otherwise we can't establish a PUAP)
        // 2. The device is NOT protected by PIN/UV (yet). This allows organizations to configure
        //    the token, before handing them out.
        // If authinfo.options.uv_acfg is not supported, this will return UnauthorizedPermission
        if !skip_puap {
            pin_uv_auth_result = unwrap_result!(
                determine_puap_if_needed(
                    &mut authcfg,
                    dev,
                    skip_uv,
                    PinUvAuthTokenPermission::AuthenticatorConfiguration,
                    UserVerificationRequirement::Preferred,
                    &status,
                    alive,
                    &mut pin,
                ),
                callback
            );
        }

        debug!("------------------------------------------------------------------");
        debug!("{authcfg:?} using {pin_uv_auth_result:?}");
        debug!("------------------------------------------------------------------");

        let resp = dev.send_cbor_cancellable(&authcfg, alive);
        match resp {
            Ok(()) => {
                let auth_info = unwrap_option!(dev.refresh_authenticator_info(), callback);
                send_status(
                    &status,
                    StatusUpdate::InteractiveManagement(InteractiveUpdate::AuthConfigUpdate((
                        AuthConfigResult::Success(auth_info.clone()),
                        Some(pin_uv_auth_result),
                    ))),
                );
                return true;
            }
            Err(e) => {
                handle_errors!(
                    e,
                    status,
                    callback,
                    pin_uv_auth_result,
                    skip_uv,
                    skip_puap,
                    cached_puat
                );
            }
        }
    }
    false
}

[ Dauer der Verarbeitung: 0.42 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge