@@ -63,8 +63,8 @@ bitflags! {
6363 const USER_PRESENT = 0x01 ;
6464 const RFU_1 = 0x02 ;
6565 const USER_VERIFIED = 0x04 ;
66- const RFU_2_1 = 0x08 ;
67- const RFU_2_2 = 0x10 ;
66+ const BACKUP_ELIGIBILITY = 0x08 ;
67+ const BACKUP_STATE = 0x10 ;
6868 const RFU_2_3 = 0x20 ;
6969 const ATTESTED_CREDENTIALS = 0x40 ;
7070 const EXTENSION_DATA = 0x80 ;
@@ -107,6 +107,19 @@ pub struct AuthenticatorData<T> {
107107 pub raw : Option < Vec < u8 > > ,
108108}
109109
110+ impl < T > AuthenticatorData < T > {
111+ /// Backup Eligibility (BE): the credential may be backed up or synced.
112+ pub fn backup_eligible ( & self ) -> bool {
113+ self . flags
114+ . contains ( AuthenticatorDataFlags :: BACKUP_ELIGIBILITY )
115+ }
116+
117+ /// Backup State (BS): the credential is currently backed up.
118+ pub fn backed_up ( & self ) -> bool {
119+ self . flags . contains ( AuthenticatorDataFlags :: BACKUP_STATE )
120+ }
121+ }
122+
110123impl < T > AuthenticatorData < T >
111124where
112125 T : Clone + Serialize ,
@@ -320,9 +333,11 @@ mod tests {
320333 0xe2 , 0x75 , 0x1e , 0x68 , 0x2f , 0xab , 0x9f , 0x2d , 0x30 , 0xab , 0x13 , 0xd2 , 0x12 , 0x55 ,
321334 0x86 , 0xce , 0x19 , 0x47 ,
322335 ] ;
323- let flag_bits = 0b1100_0101 ;
336+ let flag_bits = 0b1101_1101 ;
324337 let flags = AuthenticatorDataFlags :: USER_PRESENT
325338 | AuthenticatorDataFlags :: USER_VERIFIED
339+ | AuthenticatorDataFlags :: BACKUP_ELIGIBILITY
340+ | AuthenticatorDataFlags :: BACKUP_STATE
326341 | AuthenticatorDataFlags :: ATTESTED_CREDENTIALS
327342 | AuthenticatorDataFlags :: EXTENSION_DATA ;
328343 assert_eq ! ( flag_bits, flags. bits( ) ) ;
@@ -388,6 +403,14 @@ mod tests {
388403 cbor:: from_slice ( authdata_wrapped. as_slice ( ) ) . unwrap ( ) ;
389404 assert_eq ! ( auth_data. rp_id_hash, auth_data_reparsed. rp_id_hash) ;
390405 assert_eq ! ( auth_data. flags. bits( ) , auth_data_reparsed. flags. bits( ) ) ;
406+ assert ! ( auth_data_reparsed
407+ . flags
408+ . contains( AuthenticatorDataFlags :: BACKUP_ELIGIBILITY ) ) ;
409+ assert ! ( auth_data_reparsed
410+ . flags
411+ . contains( AuthenticatorDataFlags :: BACKUP_STATE ) ) ;
412+ assert ! ( auth_data_reparsed. backup_eligible( ) ) ;
413+ assert ! ( auth_data_reparsed. backed_up( ) ) ;
391414 assert_eq ! (
392415 auth_data. signature_count,
393416 auth_data_reparsed. signature_count
@@ -468,7 +491,10 @@ mod tests {
468491 // signature verification. The bytes must round-trip unchanged.
469492 use crate :: proto:: ctap2:: Ctap2MakeCredentialsResponseExtensions ;
470493
471- let flags = AuthenticatorDataFlags :: USER_PRESENT | AuthenticatorDataFlags :: EXTENSION_DATA ;
494+ let flags = AuthenticatorDataFlags :: USER_PRESENT
495+ | AuthenticatorDataFlags :: BACKUP_ELIGIBILITY
496+ | AuthenticatorDataFlags :: BACKUP_STATE
497+ | AuthenticatorDataFlags :: EXTENSION_DATA ;
472498 let mut input = [ 0x11u8 ; 32 ] . to_vec ( ) ;
473499 input. push ( flags. bits ( ) ) ;
474500 input. extend_from_slice ( & [ 0x00 , 0x00 , 0x00 , 0x07 ] ) ; // signCount
@@ -486,10 +512,38 @@ mod tests {
486512 let parsed: AuthenticatorData < Ctap2MakeCredentialsResponseExtensions > =
487513 cbor:: from_slice ( & wrapped) . unwrap ( ) ;
488514
515+ assert ! ( parsed. backup_eligible( ) ) ;
516+ assert ! ( parsed. backed_up( ) ) ;
517+
489518 assert_eq ! (
490519 parsed. to_response_bytes( ) . unwrap( ) ,
491520 input,
492521 "authenticatorData must be preserved byte-for-byte"
493522 ) ;
494523 }
524+
525+ #[ test]
526+ fn backup_flags_are_distinct_bits ( ) {
527+ // BE and BS are separate bits per WebAuthn L3 section 6.1. Each accessor
528+ // must read its own bit, so a swap or a wrong wiring is caught.
529+ assert_eq ! ( AuthenticatorDataFlags :: BACKUP_ELIGIBILITY . bits( ) , 0x08 ) ;
530+ assert_eq ! ( AuthenticatorDataFlags :: BACKUP_STATE . bits( ) , 0x10 ) ;
531+
532+ let with_flags = |flags| AuthenticatorData :: < ( ) > {
533+ rp_id_hash : [ 0u8 ; 32 ] ,
534+ flags,
535+ signature_count : 0 ,
536+ attested_credential : None ,
537+ extensions : None ,
538+ raw : None ,
539+ } ;
540+
541+ let be_only = with_flags ( AuthenticatorDataFlags :: BACKUP_ELIGIBILITY ) ;
542+ assert ! ( be_only. backup_eligible( ) ) ;
543+ assert ! ( !be_only. backed_up( ) ) ;
544+
545+ let bs_only = with_flags ( AuthenticatorDataFlags :: BACKUP_STATE ) ;
546+ assert ! ( !bs_only. backup_eligible( ) ) ;
547+ assert ! ( bs_only. backed_up( ) ) ;
548+ }
495549}
0 commit comments