11use crate :: proto:: ctap2:: cbor;
22use crate :: proto:: ctap2:: Ctap2ClientPinRequest ;
33use crate :: transport:: Channel ;
4- pub use crate :: webauthn:: error:: { CtapError , Error } ;
4+ pub use crate :: webauthn:: error:: { CtapError , Error , PlatformError } ;
55use crate :: webauthn:: handle_errors;
66use crate :: webauthn:: pin_uv_auth_token:: { user_verification, UsedPinUvAuthToken } ;
77use crate :: {
4545 C : Channel ,
4646{
4747 async fn toggle_always_uv ( & mut self , timeout : Duration ) -> Result < ( ) , Error > {
48+ let info = self . ctap2_get_info ( ) . await ?;
49+ // CTAP 2.1 6.2.5: toggleAlwaysUv is gated on the alwaysUv option only.
50+ if !info. option_enabled ( "authnrCfg" ) || !info. option_exists ( "alwaysUv" ) {
51+ return Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ;
52+ }
53+
4854 let mut req = Ctap2AuthenticatorConfigRequest :: new_toggle_always_uv ( ) ;
4955
5056 loop {
6672 }
6773
6874 async fn enable_enterprise_attestation ( & mut self , timeout : Duration ) -> Result < ( ) , Error > {
75+ let info = self . ctap2_get_info ( ) . await ?;
76+ if !info. option_enabled ( "authnrCfg" ) || !info. option_exists ( "ep" ) {
77+ return Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ;
78+ }
79+
6980 let mut req = Ctap2AuthenticatorConfigRequest :: new_enable_enterprise_attestation ( ) ;
7081
7182 loop {
@@ -91,6 +102,11 @@ where
91102 new_pin_length : u64 ,
92103 timeout : Duration ,
93104 ) -> Result < ( ) , Error > {
105+ let info = self . ctap2_get_info ( ) . await ?;
106+ if !info. option_enabled ( "authnrCfg" ) || !info. option_exists ( "setMinPINLength" ) {
107+ return Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ;
108+ }
109+
94110 let mut req = Ctap2AuthenticatorConfigRequest :: new_set_min_pin_length ( new_pin_length) ;
95111
96112 loop {
@@ -112,6 +128,11 @@ where
112128 }
113129
114130 async fn force_change_pin ( & mut self , force : bool , timeout : Duration ) -> Result < ( ) , Error > {
131+ let info = self . ctap2_get_info ( ) . await ?;
132+ if !info. option_enabled ( "authnrCfg" ) || !info. option_exists ( "setMinPINLength" ) {
133+ return Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ;
134+ }
135+
115136 let mut req = Ctap2AuthenticatorConfigRequest :: new_force_change_pin ( force) ;
116137
117138 loop {
@@ -137,6 +158,18 @@ where
137158 rpids : Vec < String > ,
138159 timeout : Duration ,
139160 ) -> Result < ( ) , Error > {
161+ let info = self . ctap2_get_info ( ) . await ?;
162+ if !info. option_enabled ( "authnrCfg" )
163+ || !info. option_exists ( "setMinPINLength" )
164+ || !info. supports_extension ( "minPinLength" )
165+ {
166+ return Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ;
167+ }
168+ let max_rpids = u64:: from ( info. max_rpids_for_setminpinlength . unwrap_or ( u32:: MAX ) ) ;
169+ if rpids. len ( ) as u64 > max_rpids {
170+ return Err ( Error :: Platform ( PlatformError :: RequestTooLarge ) ) ;
171+ }
172+
140173 let mut req = Ctap2AuthenticatorConfigRequest :: new_set_min_pin_length_rpids ( rpids) ;
141174 loop {
142175 let uv_auth_used = user_verification (
@@ -201,3 +234,93 @@ impl Ctap2UserVerifiableRequest for Ctap2AuthenticatorConfigRequest {
201234 false
202235 }
203236}
237+
238+ #[ cfg( test) ]
239+ mod test {
240+ use std:: collections:: HashMap ;
241+ use std:: time:: Duration ;
242+
243+ use super :: { AuthenticatorConfig , Error , PlatformError } ;
244+ use crate :: proto:: ctap2:: cbor:: { self , CborRequest , CborResponse } ;
245+ use crate :: proto:: ctap2:: { Ctap2CommandCode , Ctap2GetInfoResponse } ;
246+ use crate :: transport:: mock:: channel:: MockChannel ;
247+
248+ const TIMEOUT : Duration = Duration :: from_secs ( 1 ) ;
249+
250+ fn push_get_info ( channel : & mut MockChannel , info : & Ctap2GetInfoResponse ) {
251+ let req = CborRequest :: new ( Ctap2CommandCode :: AuthenticatorGetInfo ) ;
252+ let resp = CborResponse :: new_success_from_slice ( & cbor:: to_vec ( info) . unwrap ( ) ) ;
253+ channel. push_command_pair ( req, resp) ;
254+ }
255+
256+ #[ tokio:: test]
257+ async fn toggle_always_uv_rejected_when_authnr_cfg_absent ( ) {
258+ let mut channel = MockChannel :: new ( ) ;
259+ push_get_info (
260+ & mut channel,
261+ & Ctap2GetInfoResponse {
262+ options : Some ( HashMap :: from ( [ ( "alwaysUv" . to_string ( ) , false ) ] ) ) ,
263+ ..Default :: default ( )
264+ } ,
265+ ) ;
266+
267+ let result = channel. toggle_always_uv ( TIMEOUT ) . await ;
268+ assert_eq ! ( result, Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ) ;
269+ }
270+
271+ #[ tokio:: test]
272+ async fn toggle_always_uv_rejected_when_always_uv_option_absent ( ) {
273+ let mut channel = MockChannel :: new ( ) ;
274+ push_get_info (
275+ & mut channel,
276+ & Ctap2GetInfoResponse {
277+ options : Some ( HashMap :: from ( [ ( "authnrCfg" . to_string ( ) , true ) ] ) ) ,
278+ ..Default :: default ( )
279+ } ,
280+ ) ;
281+
282+ let result = channel. toggle_always_uv ( TIMEOUT ) . await ;
283+ assert_eq ! ( result, Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ) ;
284+ }
285+
286+ #[ tokio:: test]
287+ async fn set_min_pin_length_rpids_rejected_when_extension_absent ( ) {
288+ let mut channel = MockChannel :: new ( ) ;
289+ push_get_info (
290+ & mut channel,
291+ & Ctap2GetInfoResponse {
292+ options : Some ( HashMap :: from ( [
293+ ( "authnrCfg" . to_string ( ) , true ) ,
294+ ( "setMinPINLength" . to_string ( ) , true ) ,
295+ ] ) ) ,
296+ ..Default :: default ( )
297+ } ,
298+ ) ;
299+
300+ let result = channel
301+ . set_min_pin_length_rpids ( vec ! [ "example.com" . to_string( ) ] , TIMEOUT )
302+ . await ;
303+ assert_eq ! ( result, Err ( Error :: Platform ( PlatformError :: NotSupported ) ) ) ;
304+ }
305+
306+ #[ tokio:: test]
307+ async fn set_min_pin_length_rpids_rejected_when_too_many_rpids ( ) {
308+ let mut channel = MockChannel :: new ( ) ;
309+ push_get_info (
310+ & mut channel,
311+ & Ctap2GetInfoResponse {
312+ options : Some ( HashMap :: from ( [
313+ ( "authnrCfg" . to_string ( ) , true ) ,
314+ ( "setMinPINLength" . to_string ( ) , true ) ,
315+ ] ) ) ,
316+ extensions : Some ( vec ! [ "minPinLength" . to_string( ) ] ) ,
317+ max_rpids_for_setminpinlength : Some ( 1 ) ,
318+ ..Default :: default ( )
319+ } ,
320+ ) ;
321+
322+ let rpids = vec ! [ "example.com" . to_string( ) , "example.org" . to_string( ) ] ;
323+ let result = channel. set_min_pin_length_rpids ( rpids, TIMEOUT ) . await ;
324+ assert_eq ! ( result, Err ( Error :: Platform ( PlatformError :: RequestTooLarge ) ) ) ;
325+ }
326+ }
0 commit comments