@@ -237,7 +237,7 @@ impl Ctap2MakeCredentialsRequestExtensions {
237237 if let Some ( cred_protection) = requested_extensions. cred_protect . as_ref ( ) {
238238 if cred_protection. enforce_policy
239239 && cred_protection. policy != CredentialProtectionPolicy :: UserVerificationOptional
240- && !info. is_uv_protected ( )
240+ && !info. supports_extension ( "credProtect" )
241241 {
242242 return Err ( Error :: Ctap ( CtapError :: UnsupportedExtension ) ) ;
243243 }
@@ -496,7 +496,9 @@ pub struct Ctap2MakeCredentialsResponseExtensions {
496496mod tests {
497497 use super :: * ;
498498 use crate :: ops:: webauthn:: MakeCredentialLargeBlobExtensionInput ;
499- use crate :: ops:: webauthn:: { MakeCredentialPrfInput , MakeCredentialRequest } ;
499+ use crate :: ops:: webauthn:: {
500+ CredentialProtectionExtension , MakeCredentialPrfInput , MakeCredentialRequest ,
501+ } ;
500502 use std:: collections:: HashMap ;
501503 use std:: time:: Duration ;
502504
@@ -579,6 +581,78 @@ mod tests {
579581 assert_eq ! ( extensions. large_blob_key, None ) ;
580582 }
581583
584+ fn requested_with_cred_protect (
585+ policy : CredentialProtectionPolicy ,
586+ enforce_policy : bool ,
587+ ) -> MakeCredentialsRequestExtensions {
588+ MakeCredentialsRequestExtensions {
589+ cred_protect : Some ( CredentialProtectionExtension {
590+ policy,
591+ enforce_policy,
592+ } ) ,
593+ ..MakeCredentialsRequestExtensions :: default ( )
594+ }
595+ }
596+
597+ #[ test]
598+ fn cred_protect_enforced_above_optional_without_support_returns_unsupported_extension ( ) {
599+ // UV-protected but credProtect not advertised: must fail, not silently drop the policy.
600+ let info = info_with_options ( & [ ( "clientPin" , true ) ] ) ;
601+ let requested =
602+ requested_with_cred_protect ( CredentialProtectionPolicy :: UserVerificationRequired , true ) ;
603+ let result =
604+ Ctap2MakeCredentialsRequestExtensions :: from_webauthn_request ( & requested, & info) ;
605+ assert ! ( matches!(
606+ result,
607+ Err ( Error :: Ctap ( CtapError :: UnsupportedExtension ) )
608+ ) ) ;
609+ }
610+
611+ #[ test]
612+ fn cred_protect_enforced_above_optional_with_support_carries_policy ( ) {
613+ // credProtect advertised but no PIN/UV set yet: still honour the policy.
614+ let info = info_with_extensions ( & [ "credProtect" ] ) ;
615+ let requested =
616+ requested_with_cred_protect ( CredentialProtectionPolicy :: UserVerificationRequired , true ) ;
617+ let extensions =
618+ Ctap2MakeCredentialsRequestExtensions :: from_webauthn_request ( & requested, & info)
619+ . unwrap ( ) ;
620+ assert_eq ! (
621+ extensions. cred_protect,
622+ Some ( Ctap2CredentialProtectionPolicy :: Required )
623+ ) ;
624+ }
625+
626+ #[ test]
627+ fn cred_protect_enforced_optional_is_never_rejected ( ) {
628+ let info = Ctap2GetInfoResponse :: default ( ) ;
629+ let requested =
630+ requested_with_cred_protect ( CredentialProtectionPolicy :: UserVerificationOptional , true ) ;
631+ let extensions =
632+ Ctap2MakeCredentialsRequestExtensions :: from_webauthn_request ( & requested, & info)
633+ . unwrap ( ) ;
634+ assert_eq ! (
635+ extensions. cred_protect,
636+ Some ( Ctap2CredentialProtectionPolicy :: Optional )
637+ ) ;
638+ }
639+
640+ #[ test]
641+ fn cred_protect_not_enforced_above_optional_is_never_rejected ( ) {
642+ let info = Ctap2GetInfoResponse :: default ( ) ;
643+ let requested = requested_with_cred_protect (
644+ CredentialProtectionPolicy :: UserVerificationRequired ,
645+ false ,
646+ ) ;
647+ let extensions =
648+ Ctap2MakeCredentialsRequestExtensions :: from_webauthn_request ( & requested, & info)
649+ . unwrap ( ) ;
650+ assert_eq ! (
651+ extensions. cred_protect,
652+ Some ( Ctap2CredentialProtectionPolicy :: Required )
653+ ) ;
654+ }
655+
582656 fn info_with_extensions ( exts : & [ & str ] ) -> Ctap2GetInfoResponse {
583657 Ctap2GetInfoResponse {
584658 extensions : Some ( exts. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ) ,
0 commit comments