diff --git a/libwebauthn/examples/bio_enrollment_hid.rs b/libwebauthn/examples/bio_enrollment_hid.rs index 628a2ae..7f00df0 100644 --- a/libwebauthn/examples/bio_enrollment_hid.rs +++ b/libwebauthn/examples/bio_enrollment_hid.rs @@ -46,17 +46,15 @@ impl Display for Operation { fn get_supported_options(info: &Ctap2GetInfoResponse) -> Vec { let mut configure_ops = vec![]; - if let Some(options) = &info.options { - if options.get("bioEnroll").is_some() { - configure_ops.push(Operation::GetModality); - configure_ops.push(Operation::GetFingerprintSensorInfo); - if options.get("bioEnroll") == Some(&true) { - configure_ops.push(Operation::EnumerateEnrollments); - configure_ops.push(Operation::RemoveEnrollment); - configure_ops.push(Operation::RenameEnrollment); - } - configure_ops.push(Operation::AddNewEnrollment); + if info.supports_bio_enrollment() { + configure_ops.push(Operation::GetModality); + configure_ops.push(Operation::GetFingerprintSensorInfo); + if info.has_bio_enrollments() { + configure_ops.push(Operation::EnumerateEnrollments); + configure_ops.push(Operation::RemoveEnrollment); + configure_ops.push(Operation::RenameEnrollment); } + configure_ops.push(Operation::AddNewEnrollment); } configure_ops } @@ -155,7 +153,7 @@ pub async fn main() -> Result<(), Box> { }; println!("Which enrollment do you want to remove?"); for (id, enrollment) in enrollments.iter().enumerate() { - println!("{id} {enrollment:?}") + println!("({id}) {enrollment:?}") } let idx = ask_for_user_input(enrollments.len()); channel @@ -186,7 +184,7 @@ pub async fn main() -> Result<(), Box> { }; println!("Which enrollment do you want to rename?"); for (id, enrollment) in enrollments.iter().enumerate() { - println!("{id} {enrollment:?}") + println!("({id}) {enrollment:?}") } let idx = ask_for_user_input(enrollments.len()); print!("New name: "); diff --git a/libwebauthn/examples/cred_management.rs b/libwebauthn/examples/cred_management.rs index 04184d9..e24e8b2 100644 --- a/libwebauthn/examples/cred_management.rs +++ b/libwebauthn/examples/cred_management.rs @@ -133,11 +133,9 @@ pub async fn main() -> Result<(), WebAuthnError> { let mut channel = device.channel().await?; let info = channel.ctap2_get_info().await?; - if let Some(options) = &info.options { - if options.get("credMgmt") != Some(&true) { - println!("Your token does not support credential management."); - return Err(WebAuthnError::Ctap(CtapError::InvalidCommand)); - } + if !info.supports_credential_management() { + println!("Your token does not support credential management."); + return Err(WebAuthnError::Ctap(CtapError::InvalidCommand)); } let options = [ diff --git a/libwebauthn/src/management/authenticator_config.rs b/libwebauthn/src/management/authenticator_config.rs index 25755e3..7ecf7d7 100644 --- a/libwebauthn/src/management/authenticator_config.rs +++ b/libwebauthn/src/management/authenticator_config.rs @@ -223,4 +223,8 @@ impl Ctap2UserVerifiableRequest for Ctap2AuthenticatorConfigRequest { fn can_use_uv(&self, info: &Ctap2GetInfoResponse) -> bool { info.option_enabled("uvAcfg") } + + fn handle_legacy_preview(&mut self, _info: &Ctap2GetInfoResponse) { + // No-op + } } diff --git a/libwebauthn/src/management/bio_enrollment.rs b/libwebauthn/src/management/bio_enrollment.rs index bed867b..18244e0 100644 --- a/libwebauthn/src/management/bio_enrollment.rs +++ b/libwebauthn/src/management/bio_enrollment.rs @@ -70,6 +70,7 @@ pub trait BioEnrollment { pub struct Ctap2BioEnrollmentFingerprintSensorInfo { pub fingerprint_kind: Ctap2BioEnrollmentFingerprintKind, pub max_capture_samples_required_for_enroll: Option, + /// Not returned/supported by BioEnrollmentPreview pub max_template_friendly_name: Option, } @@ -333,4 +334,17 @@ impl Ctap2UserVerifiableRequest for Ctap2BioEnrollmentRequest { fn can_use_uv(&self, info: &Ctap2GetInfoResponse) -> bool { info.option_enabled("uvBioEnroll") } + + fn handle_legacy_preview(&mut self, info: &Ctap2GetInfoResponse) { + if let Some(options) = &info.options { + // According to Spec, we would also need to verify the token only + // supports FIDO_2_1_PRE, but let's be a bit less strict here and + // accept it simply reporting preview-support, but not the real one. + if options.get("bioEnroll") != Some(&true) + && options.get("userVerificationMgmtPreview") == Some(&true) + { + self.use_legacy_preview = true; + } + } + } } diff --git a/libwebauthn/src/management/credential_management.rs b/libwebauthn/src/management/credential_management.rs index 2168feb..c2510a1 100644 --- a/libwebauthn/src/management/credential_management.rs +++ b/libwebauthn/src/management/credential_management.rs @@ -185,7 +185,7 @@ where resp.credential_id.unwrap(), resp.public_key.unwrap(), resp.cred_protect.unwrap(), - resp.large_blob_key.unwrap(), + resp.large_blob_key.map(|x| x.into_vec()), ); let total_creds = resp.total_credentials.unwrap(); Ok((cred, total_creds)) @@ -219,7 +219,7 @@ where resp.credential_id.unwrap(), resp.public_key.unwrap(), resp.cred_protect.unwrap(), - resp.large_blob_key.unwrap(), + resp.large_blob_key.map(|x| x.into_vec()), ); Ok(cred) } @@ -270,6 +270,11 @@ where ) .await?; + // Preview mode does not support "updateUserInfo" subcommand + if req.use_legacy_preview { + return Err(Error::Ctap(CtapError::InvalidCommand)); + } + // On success, this is an all-empty Ctap2AuthenticatorConfigResponse handle_errors!( self, @@ -317,4 +322,17 @@ impl Ctap2UserVerifiableRequest for Ctap2CredentialManagementRequest { fn can_use_uv(&self, _info: &Ctap2GetInfoResponse) -> bool { true } + + fn handle_legacy_preview(&mut self, info: &Ctap2GetInfoResponse) { + if let Some(options) = &info.options { + // According to Spec, we would also need to verify the token only + // supports FIDO_2_1_PRE, but let's be a bit less strict here and + // accept it simply reporting preview-support, but not the real one. + if options.get("credMgmt") != Some(&true) + && options.get("credentialMgmtPreview") == Some(&true) + { + self.use_legacy_preview = true; + } + } + } } diff --git a/libwebauthn/src/proto/ctap2/cbor/request.rs b/libwebauthn/src/proto/ctap2/cbor/request.rs index 4bb2c9c..7303b1e 100644 --- a/libwebauthn/src/proto/ctap2/cbor/request.rs +++ b/libwebauthn/src/proto/ctap2/cbor/request.rs @@ -77,8 +77,13 @@ impl From<&Ctap2AuthenticatorConfigRequest> for CborRequest { impl From<&Ctap2BioEnrollmentRequest> for CborRequest { fn from(request: &Ctap2BioEnrollmentRequest) -> CborRequest { + let command = if request.use_legacy_preview { + Ctap2CommandCode::AuthenticatorBioEnrollmentPreview + } else { + Ctap2CommandCode::AuthenticatorBioEnrollment + }; CborRequest { - command: Ctap2CommandCode::AuthenticatorBioEnrollment, + command, encoded_data: to_vec(request).unwrap(), } } @@ -86,8 +91,13 @@ impl From<&Ctap2BioEnrollmentRequest> for CborRequest { impl From<&Ctap2CredentialManagementRequest> for CborRequest { fn from(request: &Ctap2CredentialManagementRequest) -> CborRequest { + let command = if request.use_legacy_preview { + Ctap2CommandCode::AuthenticatorCredentialManagementPreview + } else { + Ctap2CommandCode::AuthenticatorCredentialManagement + }; CborRequest { - command: Ctap2CommandCode::AuthenticatorCredentialManagement, + command, encoded_data: to_vec(request).unwrap(), } } diff --git a/libwebauthn/src/proto/ctap2/model.rs b/libwebauthn/src/proto/ctap2/model.rs index 6aa9919..57bdadf 100644 --- a/libwebauthn/src/proto/ctap2/model.rs +++ b/libwebauthn/src/proto/ctap2/model.rs @@ -49,7 +49,9 @@ pub enum Ctap2CommandCode { AuthenticatorClientPin = 0x06, AuthenticatorGetNextAssertion = 0x08, AuthenticatorBioEnrollment = 0x09, + AuthenticatorBioEnrollmentPreview = 0x40, AuthenticatorCredentialManagement = 0x0A, + AuthenticatorCredentialManagementPreview = 0x41, AuthenticatorSelection = 0x0B, AuthenticatorConfig = 0x0D, } @@ -198,6 +200,7 @@ pub trait Ctap2UserVerifiableRequest { fn permissions(&self) -> Ctap2AuthTokenPermissionRole; fn permissions_rpid(&self) -> Option<&str>; fn can_use_uv(&self, info: &Ctap2GetInfoResponse) -> bool; + fn handle_legacy_preview(&mut self, info: &Ctap2GetInfoResponse); } #[derive(Debug, Clone, Copy)] diff --git a/libwebauthn/src/proto/ctap2/model/bio_enrollment.rs b/libwebauthn/src/proto/ctap2/model/bio_enrollment.rs index c2fce6a..12b2fb6 100644 --- a/libwebauthn/src/proto/ctap2/model/bio_enrollment.rs +++ b/libwebauthn/src/proto/ctap2/model/bio_enrollment.rs @@ -36,6 +36,9 @@ pub struct Ctap2BioEnrollmentRequest { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub get_modality: Option, + + #[serde(skip)] + pub use_legacy_preview: bool, } #[repr(u32)] @@ -166,6 +169,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, // Get's filled in later uv_auth_param: None, // Get's filled in later get_modality: Some(true), + use_legacy_preview: false, } } @@ -177,6 +181,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, // Get's filled in later uv_auth_param: None, // Get's filled in later get_modality: None, + use_legacy_preview: false, } } @@ -188,6 +193,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, // Get's filled in later uv_auth_param: None, // Get's filled in later get_modality: None, + use_legacy_preview: false, } } @@ -203,6 +209,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, // Get's filled in later uv_auth_param: None, // Get's filled in later get_modality: None, + use_legacy_preview: false, } } @@ -218,6 +225,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, // Get's filled in later uv_auth_param: None, // Get's filled in later get_modality: None, + use_legacy_preview: false, } } @@ -239,6 +247,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, // Get's filled in later uv_auth_param: None, // Get's filled in later get_modality: None, + use_legacy_preview: false, } } @@ -256,6 +265,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, uv_auth_param: None, get_modality: None, + use_legacy_preview: false, } } @@ -267,6 +277,7 @@ impl Ctap2BioEnrollmentRequest { protocol: None, uv_auth_param: None, get_modality: None, + use_legacy_preview: false, } } } diff --git a/libwebauthn/src/proto/ctap2/model/credential_management.rs b/libwebauthn/src/proto/ctap2/model/credential_management.rs index c907cfa..315624e 100644 --- a/libwebauthn/src/proto/ctap2/model/credential_management.rs +++ b/libwebauthn/src/proto/ctap2/model/credential_management.rs @@ -29,6 +29,9 @@ pub struct Ctap2CredentialManagementRequest { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub uv_auth_param: Option, + + #[serde(skip)] + pub use_legacy_preview: bool, } #[repr(u32)] @@ -128,6 +131,7 @@ impl Ctap2CredentialManagementRequest { subcommand_params: None, protocol: None, uv_auth_param: None, + use_legacy_preview: false, } } @@ -137,6 +141,7 @@ impl Ctap2CredentialManagementRequest { subcommand_params: None, protocol: None, uv_auth_param: None, + use_legacy_preview: false, } } @@ -146,6 +151,7 @@ impl Ctap2CredentialManagementRequest { subcommand_params: None, protocol: None, uv_auth_param: None, + use_legacy_preview: false, } } @@ -159,6 +165,7 @@ impl Ctap2CredentialManagementRequest { }), protocol: None, uv_auth_param: None, + use_legacy_preview: false, } } @@ -170,6 +177,7 @@ impl Ctap2CredentialManagementRequest { subcommand_params: None, protocol: None, uv_auth_param: None, + use_legacy_preview: false, } } @@ -183,6 +191,7 @@ impl Ctap2CredentialManagementRequest { }), protocol: None, uv_auth_param: None, + use_legacy_preview: false, } } @@ -199,6 +208,7 @@ impl Ctap2CredentialManagementRequest { }), protocol: None, uv_auth_param: None, + use_legacy_preview: false, } } } @@ -227,7 +237,8 @@ pub struct Ctap2CredentialData { pub credential_id: Ctap2PublicKeyCredentialDescriptor, pub public_key: PublicKey, pub cred_protect: u64, - pub large_blob_key: ByteBuf, + /// This is not there in the Preview mode + pub large_blob_key: Option>, } impl Ctap2CredentialData { @@ -236,7 +247,7 @@ impl Ctap2CredentialData { credential_id: Ctap2PublicKeyCredentialDescriptor, public_key: PublicKey, cred_protect: u64, - large_blob_key: ByteBuf, + large_blob_key: Option>, ) -> Self { Self { user, diff --git a/libwebauthn/src/proto/ctap2/model/get_assertion.rs b/libwebauthn/src/proto/ctap2/model/get_assertion.rs index 1f9466e..2ebea01 100644 --- a/libwebauthn/src/proto/ctap2/model/get_assertion.rs +++ b/libwebauthn/src/proto/ctap2/model/get_assertion.rs @@ -201,4 +201,8 @@ impl Ctap2UserVerifiableRequest for Ctap2GetAssertionRequest { fn can_use_uv(&self, _info: &Ctap2GetInfoResponse) -> bool { true } + + fn handle_legacy_preview(&mut self, _info: &Ctap2GetInfoResponse) { + // No-op + } } diff --git a/libwebauthn/src/proto/ctap2/model/get_info.rs b/libwebauthn/src/proto/ctap2/model/get_info.rs index e2a7bf4..51ebc0b 100644 --- a/libwebauthn/src/proto/ctap2/model/get_info.rs +++ b/libwebauthn/src/proto/ctap2/model/get_info.rs @@ -164,6 +164,26 @@ impl Ctap2GetInfoResponse { self.versions.iter().any(|v| v == "FIDO_2_1") } + pub fn supports_credential_management(&self) -> bool { + self.option_enabled("credMgmt") || self.option_enabled("credentialMgmtPreview") + } + + pub fn supports_bio_enrollment(&self) -> bool { + if let Some(options) = &self.options { + return options.get("bioEnroll").is_some() + || options.get("userVerificationMgmtPreview").is_some(); + } + false + } + + pub fn has_bio_enrollments(&self) -> bool { + if let Some(options) = &self.options { + return options.get("bioEnroll") == Some(&true) + || options.get("userVerificationMgmtPreview") == Some(&true); + } + false + } + /// Implements check for "Protected by some form of User Verification": /// Either or both clientPin or built-in user verification methods are supported and enabled. /// I.e., in the authenticatorGetInfo response the pinUvAuthToken option ID is present and set to true, diff --git a/libwebauthn/src/proto/ctap2/model/make_credential.rs b/libwebauthn/src/proto/ctap2/model/make_credential.rs index 0a0a75f..8b826bf 100644 --- a/libwebauthn/src/proto/ctap2/model/make_credential.rs +++ b/libwebauthn/src/proto/ctap2/model/make_credential.rs @@ -175,6 +175,10 @@ impl Ctap2UserVerifiableRequest for Ctap2MakeCredentialRequest { fn can_use_uv(&self, _info: &Ctap2GetInfoResponse) -> bool { true } + + fn handle_legacy_preview(&mut self, _info: &Ctap2GetInfoResponse) { + // No-op + } } impl TryFrom<&Ctap2MakeCredentialResponse> for Ctap2PublicKeyCredentialDescriptor { diff --git a/libwebauthn/src/webauthn.rs b/libwebauthn/src/webauthn.rs index e28eaef..0cab4a5 100644 --- a/libwebauthn/src/webauthn.rs +++ b/libwebauthn/src/webauthn.rs @@ -264,6 +264,7 @@ where R: Ctap2UserVerifiableRequest, { let get_info_response = channel.ctap2_get_info().await?; + ctap2_request.handle_legacy_preview(&get_info_response); let uv_proto = select_uv_proto(&get_info_response).await?; let token_identifier = Ctap2AuthTokenPermission::new( uv_proto.version(),