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