Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions src/ctap2/commands/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,13 +489,19 @@ impl PinUvAuthCommand for GetAssertion {
info: &AuthenticatorInfo,
uv_req: UserVerificationRequirement,
) -> bool {
let supports_uv = info.options.user_verification == Some(true);
let pin_configured = info.options.client_pin == Some(true);
let device_protected = supports_uv || pin_configured;
if uv_req == UserVerificationRequirement::Required
|| info.options.always_uv.unwrap_or(false)
{
// The RP requires UV, or the authenticator always requires UV (CTAP 2.1 §7.2.2).
return false;
}

let uv_discouraged = uv_req == UserVerificationRequirement::Discouraged;
let always_uv = info.options.always_uv == Some(true);

!always_uv && (!device_protected || uv_discouraged)
// The RP "prefers enforcing UV" (CTAP 2.1 §6.2.1 step 1.1) or
// "prefers UV ... if possible" (WebAuthn-3 §5.8.6). UV is "possible" on
// authenticators that support it, but it might not be configured.
uv_discouraged || !info.supports_uv()
}

fn get_pin_uv_auth_param(&self) -> Option<&PinUvAuthParam> {
Expand Down
74 changes: 57 additions & 17 deletions src/ctap2/commands/get_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,47 @@ pub struct AuthenticatorOptions {
#[serde(rename = "up", default = "true_val")]
pub user_presence: bool,

/// Indicates that the device is capable of verifying the user within
/// itself. For example, devices with UI, biometrics fall into this
/// category.
/// If present and set to true, it indicates that the device is capable of
/// user verification within itself and has been configured.
/// If present and set to false, it indicates that the device is capable of
/// user verification within itself and has not been yet configured. For
/// example, a biometric device that has not yet been configured will
/// return this parameter set to false.
/// If absent, it indicates that the device is not capable of user
/// verification within itself.
/// A device that can only do Client PIN will not return the "uv" parameter.
/// If a device is capable of verifying the user within itself as well as
/// able to do Client PIN, it will return both "uv" and the Client PIN
/// option.
// TODO(MS): My Token (key-ID FIDO2) does return Some(false) here, even though
// it has no built-in verification method. Not to be trusted...
/// In CTAP 2.1+, indicates that the authenticator supports
/// [a built-in user verification method][0].
///
/// For example, devices with UI, biometrics fall into this category.
///
/// * If `Some(true)`, it indicates that the device is capable of built-in user verification and
/// its user verification feature is presently configured.
///
/// * If `Some(false)`, it indicates that the authenticator is capable of built-in user
/// verification and its user verification feature is not presently configured.
///
/// For example, an authenticator featuring a built-in biometric user verification feature
/// that is not presently configured will return this option set to `Some(false)`.
///
/// * If `None`, it indicates that the authenticator does not have a built-in user verification
/// capability.
///
/// A device that can only do Client PIN will return `None`.
///
/// If a device is capable of both built-in user verification and Client PIN, the authenticator
/// will return both the "uv" and [the "clientPin"][Self::client_pin] option ids.
///
/// ### Caveats
///
/// [CTAP 2.0][1] gives the `uv` option a completely different meaning to CTAP 2.1+:
///
/// > Indicates that the device is capable of verifying the user as part of the
/// > `authenticatorGetAssertion` request. Default: `false`
///
/// Some CTAP 2.1-PRE authenticators that _only_ support client PIN erroneously return
/// `Some(false)` (eg: Key-ID FIDO2).
///
/// ### References
///
/// * [CTAP 2.0][1] (different to CTAP 2.1 and later)
/// * [CTAP 2.1](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#getinfo-uv)
/// * [CTAP 2.2](https://fidoalliance.org/specs/fido-v2.2-ps-20250714/fido-client-to-authenticator-protocol-v2.2-ps-20250714.html#getinfo-uv)
/// * [CTAP 2.3](https://fidoalliance.org/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html#getinfo-uv)
///
/// [0]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#built-in-user-verification-method
/// [1]: https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#authenticatorgetinfo-0x04
#[serde(rename = "uv")]
pub user_verification: Option<bool>,

Expand Down Expand Up @@ -369,9 +393,25 @@ impl AuthenticatorInfo {
AuthenticatorVersion::U2F_V2
}

/// `true` if the device has been configured with [some form of user verification][0]
/// (ie: a [client PIN][1] is set and/or [built-in user verification][2] is configured).
///
/// [0]: https://fidoalliance.org/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html#some-form-of-user-verification
/// [1]: AuthenticatorOptions::client_pin
/// [2]: AuthenticatorOptions::user_verification
pub fn device_is_protected(&self) -> bool {
self.options.client_pin == Some(true) || self.options.user_verification == Some(true)
}

/// `true` if the device supports [some form of user verification][0].
///
/// This is a mandatory feature on CTAP 2.1+ authenticators that support [resident keys][1]. It
///
/// [0]: https://fidoalliance.org/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html#some-form-of-user-verification
/// [1]: AuthenticatorOptions::resident_key
pub fn supports_uv(&self) -> bool {
self.options.client_pin.is_some() || self.options.user_verification.is_some()
}
}

impl CtapResponse for AuthenticatorInfo {}
Expand Down
47 changes: 28 additions & 19 deletions src/ctap2/commands/make_credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,27 +498,36 @@ impl PinUvAuthCommand for MakeCredentials {
info: &AuthenticatorInfo,
uv_req: UserVerificationRequirement,
) -> bool {
// TODO(MS): Handle here the case where we NEED a UV, the device supports PINs, but hasn't set a PIN.
// For this, the user has to be prompted to set a PIN first (see https://github.com/mozilla/authenticator-rs/issues/223)

let supports_uv = info.options.user_verification == Some(true);
let pin_configured = info.options.client_pin == Some(true);

// CTAP 2.0 authenticators require user verification if the device is protected
let device_protected = supports_uv || pin_configured;

// CTAP 2.1 authenticators may allow the creation of non-discoverable credentials without
// user verification. This is only relevant if the relying party has not requested user
// verification.
let make_cred_uv_not_required = info.options.make_cred_uv_not_rqd == Some(true)
&& self.options.resident_key != Some(true)
&& uv_req == UserVerificationRequirement::Discouraged;
// TODO(MS): Handle setting up a PIN where needed
// (see https://github.com/mozilla/authenticator-rs/issues/223)
if uv_req == UserVerificationRequirement::Required
|| info.options.always_uv.unwrap_or(false)
{
// The RP requires UV, or the authenticator always requires UV (CTAP 2.1 §7.2.2).
return false;
}

// Alternatively, CTAP 2.1 authenticators may require user verification regardless of the
// RP's requirement.
let always_uv = info.options.always_uv == Some(true);
// `true` if we'd plan to not use UV (ie: uv option is false and pinUvAuthParam unset)
let uv_discouraged = uv_req == UserVerificationRequirement::Discouraged;
let make_cred_uv_not_required = info.options.make_cred_uv_not_rqd == Some(true);
let rk = self.options.resident_key == Some(true);

if info.device_is_protected() && (!make_cred_uv_not_required || rk) {
// CTAP 2.3 §6.1.2 Step 7 only requires UV for RKs if the device is protected and
// supports make_cred_uv_not_rqd.
// https://fidoalliance.org/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html#ref-for-getinfo-makecreduvnotrqd%E2%91%A1
//
// CTAP 2.3 §6.1.2 Step 8 and CTAP 2.0 §5.1 Step 5 require UV if the device is protected
// and does not support make_cred_uv_not_rqd.
// https://fidoalliance.org/specs/fido-v2.3-ps-20260226/fido-client-to-authenticator-protocol-v2.3-ps-20260226.html#ref-for-getinfo-makecreduvnotrqd%E2%91%A2
// https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html#authenticatorMakeCredential:~:text=If%20pinAuth%20parameter%20is%20not%20present%20and%20clientPin%20been%20set%20on%20the%20authenticator%2C
return false;
}

!always_uv && (!device_protected || make_cred_uv_not_required)
// The RP "prefers enforcing UV" (CTAP 2.1 §6.1.1 step 1.1) or
// "prefers UV ... if possible" (WebAuthn-3 §5.8.6). UV is "possible" on
// authenticators that support it, but it might not be configured.
uv_discouraged || !info.supports_uv()
}

fn get_pin_uv_auth_param(&self) -> Option<&PinUvAuthParam> {
Expand Down
Loading