macro_rules! handle_errors {
($error: expr, $status: expr, $callback: expr, $pin_uv_auth_result: expr, $skip_uv: expr) => { letmut _dummy_skip_puap = false; letmut _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) => { letmut _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: &dynFn() -> 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: &dynFn() -> 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);
}
if dev.get_protocol() == FidoProtocol::CTAP2 { let info = match dev.get_authenticator_info() {
Some(info) => info,
None => {
callback.call(Err(HIDError::DeviceNotInitialized.into())); returnfalse;
}
};
// 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,
))); returnfalse;
} if args.user_verification_req == UserVerificationRequirement::Required {
callback.call(Err(AuthenticatorError::UnsupportedOption(
UnsupportedOption::UserVerification,
))); returnfalse;
} if !args
.pub_cred_params
.iter()
.any(|x| x.alg == COSEAlgorithm::ES256)
{
callback.call(Err(AuthenticatorError::UnsupportedOption(
UnsupportedOption::PubCredParams,
))); returnfalse;
}
}
// 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,
))); returnfalse;
}
letmut skip_uv = false; letmut 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)); returnfalse;
}
}
pubfn sign<Dev: FidoDevice>(
dev: &mut Dev,
args: SignArgs,
status: Sender<crate::StatusUpdate>,
callback: StateCallback<crate::Result<crate::SignResult>>,
alive: &dynFn() -> 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,
))); returnfalse;
} if args.allow_list.is_empty() {
callback.call(Err(AuthenticatorError::UnsupportedOption(
UnsupportedOption::EmptyAllowList,
))); returnfalse;
}
}
letmut allow_list = args.allow_list; letmut rp_id = RelyingParty::from(args.relying_party_id); let client_data_hash = ClientDataHash(args.client_data_hash); iflet 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;
}
}
}
// 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())); returnfalse;
}
// 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)); returnfalse;
}
};
debug!("------------------------------------------------------------------");
debug!("{get_assertion:?} using {pin_uv_auth_result:?}");
debug!("------------------------------------------------------------------");
send_status(&status, crate::StatusUpdate::PresenceRequired); letmut 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))); returntrue;
} 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))); returntrue;
}
_ => {
callback.call(Err(AuthenticatorError::CancelledByUser)); returntrue;
}
}
} false
}
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()));
}
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 { letmut res; letmut was_invalid = false; letmut 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,
};
iflet 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(())));
}
if authinfo.options.bio_enroll.is_none()
&& authinfo.options.user_verification_mgmt_preview.is_none()
{
callback.call(Err(AuthenticatorError::HIDError(
HIDError::UnsupportedCommand,
))); returnfalse;
}
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 letmut 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;
letmut skip_puap = false; letmut cached_puat = false; // If we were provided with a cached puat from the outside letmut 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);
}
_ => {}
} letmut 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 = iflet 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);
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,
))); returnfalse;
}
// If puap is provided, we can skip puap-determination (i.e. PIN entry) letmut 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,
),
}; letmut credential_result = CredentialList::new(); letmut remaining_rps = 0; letmut remaining_cred_ids = 0; letmut current_rp = 0; letmut skip_puap = false; letmut cached_puat = false; // If we were provided with a cached puat from the outside letmut 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);
}
_ => {}
} letmut 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),
)),
),
); returntrue;
}
}
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),
)),
),
); returntrue;
}
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; letmut 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),
)),
),
); returntrue;
} else { continue;
}
}
CredManagementCommand::DeleteCredential(_) => {
send_status(
&status,
StatusUpdate::InteractiveManagement(
InteractiveUpdate::CredentialManagementUpdate((
CredentialManagementResult::DeleteSucess,
Some(pin_uv_auth_result),
)),
),
); returntrue;
}
CredManagementCommand::UpdateUserInformation(_) => {
send_status(
&status,
StatusUpdate::InteractiveManagement(
InteractiveUpdate::CredentialManagementUpdate((
CredentialManagementResult::UpdateSuccess,
Some(pin_uv_auth_result),
)),
),
); returntrue;
}
};
}
Err(e) => {
handle_errors!(
e,
status,
callback,
pin_uv_auth_result,
skip_uv,
skip_puap,
cached_puat
);
}
}
} false
}
if authinfo.options.authnr_cfg != Some(true) {
callback.call(Err(AuthenticatorError::HIDError(
HIDError::UnsupportedCommand,
))); returnfalse;
}
letmut skip_puap = false; letmut cached_puat = false; // If we were provided with a cached puat from the outside letmut 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);
}
_ => {}
} letmut 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!("------------------------------------------------------------------");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.