@@ -4,7 +4,7 @@ use libwebauthn::ops::webauthn::{
44 GetAssertionRequest , GetAssertionResponse , MakeCredentialRequest , ResidentKeyRequirement ,
55 UserVerificationRequirement ,
66} ;
7- use libwebauthn:: proto:: ctap2:: preflight:: ctap2_preflight;
7+ use libwebauthn:: proto:: ctap2:: preflight:: { ctap2_preflight, ctap2_preflight_with_appid } ;
88use libwebauthn:: proto:: ctap2:: {
99 Ctap2CredentialType , Ctap2PublicKeyCredentialDescriptor , Ctap2PublicKeyCredentialRpEntity ,
1010 Ctap2PublicKeyCredentialType , Ctap2PublicKeyCredentialUserEntity ,
@@ -36,12 +36,21 @@ async fn make_credential_call(
3636 channel : & mut HidChannel < ' _ > ,
3737 user_id : & [ u8 ] ,
3838 exclude_list : Option < Vec < Ctap2PublicKeyCredentialDescriptor > > ,
39+ ) -> Result < ( Ctap2PublicKeyCredentialDescriptor , [ u8 ; 32 ] ) , Error > {
40+ make_credential_call_with_rp ( channel, user_id, exclude_list, "example.org" ) . await
41+ }
42+
43+ async fn make_credential_call_with_rp (
44+ channel : & mut HidChannel < ' _ > ,
45+ user_id : & [ u8 ] ,
46+ exclude_list : Option < Vec < Ctap2PublicKeyCredentialDescriptor > > ,
47+ rp_id : & str ,
3948) -> Result < ( Ctap2PublicKeyCredentialDescriptor , [ u8 ; 32 ] ) , Error > {
4049 let challenge: [ u8 ; 32 ] = thread_rng ( ) . gen ( ) ;
4150 let make_credentials_request = MakeCredentialRequest {
42- origin : "example.org" . to_owned ( ) ,
51+ origin : rp_id . to_owned ( ) ,
4352 challenge : Vec :: from ( challenge) ,
44- relying_party : Ctap2PublicKeyCredentialRpEntity :: new ( "example.org" , "example.org" ) ,
53+ relying_party : Ctap2PublicKeyCredentialRpEntity :: new ( rp_id , rp_id ) ,
4554 user : Ctap2PublicKeyCredentialUserEntity :: new ( user_id, "mario.rossi" , "Mario Rossi" ) ,
4655 resident_key : Some ( ResidentKeyRequirement :: Discouraged ) ,
4756 user_verification : UserVerificationRequirement :: Preferred ,
@@ -239,6 +248,58 @@ async fn preflight_nonsense_allow_list() {
239248 . await ;
240249}
241250
251+ #[ test( tokio:: test) ]
252+ async fn preflight_with_appid_exclude_finds_legacy_credential ( ) {
253+ // Register a credential under a "legacy" relying party id (standing
254+ // in for a U2F AppID), then run preflight against a different rpId
255+ // while passing the legacy rpId as `appid_exclude`. The credential
256+ // should be detected, matching WebAuthn L3 §10.1.2 semantics.
257+ let mut device = get_virtual_device ( ) ;
258+ let mut channel = device. channel ( ) . await . unwrap ( ) ;
259+
260+ let user_id: [ u8 ; 32 ] = thread_rng ( ) . gen ( ) ;
261+ let _state_recv = channel. get_ux_update_receiver ( ) ;
262+
263+ // First, register the legacy credential.
264+ let ( legacy_credential, _) =
265+ make_credential_call_with_rp ( & mut channel, & user_id, None , "legacy.example.org" )
266+ . await
267+ . expect ( "Failed to register legacy credential" ) ;
268+
269+ // Preflight under "example.org" without appid_exclude: legacy
270+ // credential is not detected.
271+ let hash: [ u8 ; 32 ] = thread_rng ( ) . gen ( ) ;
272+ let filtered_no_appid = ctap2_preflight_with_appid (
273+ & mut channel,
274+ std:: slice:: from_ref ( & legacy_credential) ,
275+ & hash,
276+ "example.org" ,
277+ None ,
278+ )
279+ . await ;
280+ assert ! (
281+ filtered_no_appid. is_empty( ) ,
282+ "Without appid_exclude, the legacy credential should not be detected"
283+ ) ;
284+
285+ // Preflight again, this time providing the legacy rpId as
286+ // appid_exclude. The credential must be detected.
287+ let filtered_with_appid = ctap2_preflight_with_appid (
288+ & mut channel,
289+ std:: slice:: from_ref ( & legacy_credential) ,
290+ & hash,
291+ "example.org" ,
292+ Some ( "legacy.example.org" ) ,
293+ )
294+ . await ;
295+ assert_eq ! (
296+ filtered_with_appid. len( ) ,
297+ 1 ,
298+ "With appid_exclude set, the legacy credential should be detected"
299+ ) ;
300+ assert_eq ! ( filtered_with_appid[ 0 ] . id, legacy_credential. id) ;
301+ }
302+
242303#[ test( tokio:: test) ]
243304async fn preflight_mixed_allow_list ( ) {
244305 // Get assertion with a mixed allow_list that contains 2 real ones. Should remove the two fake ones in preflight
0 commit comments