1#![warn(missing_docs)]
17
18use rand::prelude::*;
19use std::time::Duration;
20use url::Url;
21
22use crate::attestation::{
23 verify_android_key_attestation, verify_apple_anonymous_attestation,
24 verify_attestation_ca_chain, verify_fidou2f_attestation, verify_packed_attestation,
25 verify_tpm_attestation,
26};
27use crate::constants::CHALLENGE_SIZE_BYTES;
28use crate::crypto::compute_sha256;
29use crate::error::WebauthnError;
30use crate::internals::*;
31use crate::proto::*;
32
33#[derive(Debug, Clone)]
51pub struct WebauthnCore {
52 rp_name: String,
53 rp_id: String,
54 rp_id_hash: [u8; 32],
55 allowed_origins: Vec<Url>,
56 authenticator_timeout: Duration,
57 require_valid_counter_value: bool,
58 #[allow(unused)]
59 ignore_unsupported_attestation_formats: bool,
60 allow_cross_origin: bool,
61 allow_subdomains_origin: bool,
62 allow_any_port: bool,
63}
64
65pub struct ChallengeRegisterBuilder {
67 user_unique_id: Vec<u8>,
68 user_name: String,
69 user_display_name: String,
70 attestation: AttestationConveyancePreference,
71 policy: UserVerificationPolicy,
72 exclude_credentials: Option<Vec<CredentialID>>,
73 extensions: Option<RequestRegistrationExtensions>,
74 credential_algorithms: Vec<COSEAlgorithm>,
75 require_resident_key: bool,
76 authenticator_attachment: Option<AuthenticatorAttachment>,
77 attestation_formats: Option<Vec<AttestationFormat>>,
78 reject_synchronised_authenticators: bool,
79 hints: Option<Vec<PublicKeyCredentialHints>>,
80}
81
82#[derive(Debug)]
84pub struct ChallengeAuthenticateBuilder {
85 creds: Vec<Credential>,
86 policy: UserVerificationPolicy,
87 extensions: Option<RequestAuthenticationExtensions>,
88 allow_backup_eligible_upgrade: bool,
89 hints: Option<Vec<PublicKeyCredentialHints>>,
90}
91
92impl ChallengeRegisterBuilder {
93 pub fn attestation(mut self, value: AttestationConveyancePreference) -> Self {
95 self.attestation = value;
96 self
97 }
98
99 pub fn user_verification_policy(mut self, value: UserVerificationPolicy) -> Self {
101 self.policy = value;
102 self
103 }
104
105 pub fn exclude_credentials(mut self, value: Option<Vec<CredentialID>>) -> Self {
109 self.exclude_credentials = value;
110 self
111 }
112
113 pub fn extensions(mut self, value: Option<RequestRegistrationExtensions>) -> Self {
115 self.extensions = value;
116 self
117 }
118
119 pub fn credential_algorithms(mut self, value: Vec<COSEAlgorithm>) -> Self {
121 self.credential_algorithms = value;
122 self
123 }
124
125 pub fn require_resident_key(mut self, value: bool) -> Self {
128 self.require_resident_key = value;
129 self
130 }
131
132 pub fn authenticator_attachment(mut self, value: Option<AuthenticatorAttachment>) -> Self {
134 self.authenticator_attachment = value;
135 self
136 }
137
138 pub fn reject_synchronised_authenticators(mut self, value: bool) -> Self {
140 self.reject_synchronised_authenticators = value;
141 self
142 }
143
144 pub fn hints(mut self, hints: Option<Vec<PublicKeyCredentialHints>>) -> Self {
146 self.hints = hints;
147 self
148 }
149
150 pub fn attestation_formats(
152 mut self,
153 attestation_formats: Option<Vec<AttestationFormat>>,
154 ) -> Self {
155 self.attestation_formats = attestation_formats;
156 self
157 }
158}
159
160impl ChallengeAuthenticateBuilder {
161 pub fn extensions(mut self, value: Option<RequestAuthenticationExtensions>) -> Self {
163 self.extensions = value;
164 self
165 }
166
167 pub fn allow_backup_eligible_upgrade(mut self, value: bool) -> Self {
172 self.allow_backup_eligible_upgrade = value;
173 self
174 }
175
176 pub fn hints(mut self, hints: Option<Vec<PublicKeyCredentialHints>>) -> Self {
178 self.hints = hints;
179 self
180 }
181}
182
183impl WebauthnCore {
184 pub fn new_unsafe_experts_only(
201 rp_name: &str,
202 rp_id: &str,
203 allowed_origins: Vec<Url>,
204 authenticator_timeout: Duration,
205 allow_subdomains_origin: Option<bool>,
206 allow_any_port: Option<bool>,
207 ) -> Self {
208 let rp_id_hash = compute_sha256(rp_id.as_bytes());
209 WebauthnCore {
210 rp_name: rp_name.to_string(),
211 rp_id: rp_id.to_string(),
212 rp_id_hash,
213 allowed_origins,
214 authenticator_timeout,
215 require_valid_counter_value: true,
216 ignore_unsupported_attestation_formats: true,
217 allow_cross_origin: false,
218 allow_subdomains_origin: allow_subdomains_origin.unwrap_or(false),
219 allow_any_port: allow_any_port.unwrap_or(false),
220 }
221 }
222
223 pub fn get_allowed_origins(&self) -> &[Url] {
225 &self.allowed_origins
226 }
227
228 fn generate_challenge(&self) -> Challenge {
229 let mut rng = rand::thread_rng();
230 Challenge::new(rng.gen::<[u8; CHALLENGE_SIZE_BYTES]>().to_vec())
231 }
232
233 pub fn new_challenge_register_builder(
237 &self,
238 user_unique_id: &[u8],
239 user_name: &str,
240 user_display_name: &str,
241 ) -> Result<ChallengeRegisterBuilder, WebauthnError> {
242 if user_unique_id.is_empty() || user_display_name.is_empty() || user_name.is_empty() {
243 return Err(WebauthnError::InvalidUsername);
244 }
245
246 Ok(ChallengeRegisterBuilder {
247 user_unique_id: user_unique_id.to_vec(),
248 user_name: user_name.to_string(),
249 user_display_name: user_display_name.to_string(),
250 credential_algorithms: COSEAlgorithm::secure_algs(),
251 attestation: Default::default(),
252 policy: UserVerificationPolicy::Preferred,
253 exclude_credentials: Default::default(),
254 extensions: Default::default(),
255 require_resident_key: Default::default(),
256 authenticator_attachment: Default::default(),
257 reject_synchronised_authenticators: Default::default(),
258 hints: Default::default(),
259 attestation_formats: Default::default(),
260 })
261 }
262
263 #[allow(clippy::too_many_arguments)]
276 pub fn generate_challenge_register(
277 &self,
278 challenge_builder: ChallengeRegisterBuilder,
279 ) -> Result<(CreationChallengeResponse, RegistrationState), WebauthnError> {
280 let ChallengeRegisterBuilder {
281 user_unique_id,
282 user_name,
283 user_display_name,
284 attestation,
285 policy,
286 exclude_credentials,
287 extensions,
288 credential_algorithms,
289 require_resident_key,
290 authenticator_attachment,
291 attestation_formats,
292 reject_synchronised_authenticators,
293 hints,
294 } = challenge_builder;
295
296 let challenge = self.generate_challenge();
297
298 let resident_key = if require_resident_key {
299 Some(ResidentKeyRequirement::Required)
300 } else {
301 Some(ResidentKeyRequirement::Discouraged)
302 };
303
304 let timeout_millis =
305 u32::try_from(self.authenticator_timeout.as_millis()).expect("Timeout too large");
306
307 let c = CreationChallengeResponse {
308 public_key: PublicKeyCredentialCreationOptions {
309 rp: RelyingParty {
310 name: self.rp_name.clone(),
311 id: self.rp_id.clone(),
312 },
313 user: User {
314 id: user_unique_id.into(),
315 name: user_name,
316 display_name: user_display_name,
317 },
318 challenge: challenge.clone().into(),
319 pub_key_cred_params: credential_algorithms
320 .iter()
321 .map(|alg| PubKeyCredParams {
322 type_: "public-key".to_string(),
323 alg: *alg as i64,
324 })
325 .collect(),
326 timeout: Some(timeout_millis),
327 hints,
328 attestation: Some(attestation),
329 exclude_credentials: exclude_credentials.as_ref().map(|creds| {
330 creds
331 .iter()
332 .cloned()
333 .map(|id| PublicKeyCredentialDescriptor {
334 type_: "public-key".to_string(),
335 id: id.as_ref().into(),
336 transports: None,
337 })
338 .collect()
339 }),
340 authenticator_selection: Some(AuthenticatorSelectionCriteria {
341 authenticator_attachment,
342 resident_key,
343 require_resident_key,
344 user_verification: policy,
345 }),
346 extensions: extensions.clone(),
347 attestation_formats,
348 },
349 };
350
351 let wr = RegistrationState {
352 policy,
353 exclude_credentials: exclude_credentials.unwrap_or_else(|| Vec::with_capacity(0)),
354 challenge: challenge.into(),
355 credential_algorithms,
356 require_resident_key,
358 authenticator_attachment,
359 extensions: extensions.unwrap_or_default(),
360 allow_synchronised_authenticators: !reject_synchronised_authenticators,
361 };
362
363 Ok((c, wr))
365 }
366
367 pub fn register_credential(
379 &self,
380 reg: &RegisterPublicKeyCredential,
381 state: &RegistrationState,
382 attestation_cas: Option<&AttestationCaList>,
383 ) -> Result<Credential, WebauthnError> {
385 trace!(?state);
387 trace!(?reg);
388
389 let RegistrationState {
390 policy,
391 exclude_credentials,
392 challenge,
393 credential_algorithms,
394 require_resident_key: _,
395 authenticator_attachment: _,
396 extensions,
397 allow_synchronised_authenticators,
398 } = state;
399 let chal: &ChallengeRef = challenge.into();
400
401 let credential = self.register_credential_internal(
403 reg,
404 *policy,
405 chal,
406 exclude_credentials,
407 credential_algorithms,
408 attestation_cas,
409 false,
410 extensions,
411 *allow_synchronised_authenticators,
412 )?;
413
414 Ok(credential)
429 }
430
431 #[allow(clippy::too_many_arguments)]
432 pub(crate) fn register_credential_internal(
433 &self,
434 reg: &RegisterPublicKeyCredential,
435 policy: UserVerificationPolicy,
436 chal: &ChallengeRef,
437 exclude_credentials: &[CredentialID],
438 credential_algorithms: &[COSEAlgorithm],
439 attestation_cas: Option<&AttestationCaList>,
440 danger_disable_certificate_time_checks: bool,
441 req_extn: &RequestRegistrationExtensions,
442 allow_synchronised_authenticators: bool,
443 ) -> Result<Credential, WebauthnError> {
444 if attestation_cas
446 .as_ref()
447 .map(|l| l.is_empty())
448 .unwrap_or(false)
449 {
450 return Err(WebauthnError::MissingAttestationCaList);
451 }
452
453 let data = AuthenticatorAttestationResponse::try_from(®.response)?;
470
471 if data.client_data_json.type_ != "webauthn.create" {
475 return Err(WebauthnError::InvalidClientDataType);
476 }
477
478 if data.client_data_json.challenge.as_slice() != chal.as_ref() {
481 return Err(WebauthnError::MismatchedChallenge);
482 }
483
484 if !self.allowed_origins.iter().any(|origin| {
486 Self::origins_match(
487 self.allow_subdomains_origin,
488 self.allow_any_port,
489 &data.client_data_json.origin,
490 origin,
491 )
492 }) {
493 return Err(WebauthnError::InvalidRPOrigin);
494 }
495
496 if !self.allow_cross_origin && data.client_data_json.cross_origin.unwrap_or(false) {
500 return Err(WebauthnError::CredentialCrossOrigin);
501 }
502
503 let client_data_json_hash = compute_sha256(data.client_data_json_bytes.as_slice());
520
521 if data.attestation_object.auth_data.rp_id_hash != self.rp_id_hash {
533 return Err(WebauthnError::InvalidRPIDHash);
534 }
535
536 if !data.attestation_object.auth_data.user_present {
538 return Err(WebauthnError::UserNotPresent);
539 }
540
541 if matches!(policy, UserVerificationPolicy::Required)
547 && !data.attestation_object.auth_data.user_verified
548 {
549 return Err(WebauthnError::UserNotVerified);
550 }
551
552 debug!(
568 "extensions: {:?}",
569 data.attestation_object.auth_data.extensions
570 );
571
572 let attest_format = AttestationFormat::try_from(data.attestation_object.fmt.as_str())
589 .map_err(|()| WebauthnError::AttestationNotSupported)?;
590
591 let acd = data
596 .attestation_object
597 .auth_data
598 .acd
599 .as_ref()
600 .ok_or(WebauthnError::MissingAttestationCredentialData)?;
601
602 debug!("attestation is: {:?}", &attest_format);
604 debug!("attested credential data is: {:?}", &acd);
605
606 let (attestation_data, attestation_metadata) = match attest_format {
607 AttestationFormat::FIDOU2F => (
608 verify_fidou2f_attestation(acd, &data.attestation_object, &client_data_json_hash)?,
609 AttestationMetadata::None,
610 ),
611 AttestationFormat::Packed => {
612 verify_packed_attestation(acd, &data.attestation_object, &client_data_json_hash)?
613 }
614 AttestationFormat::Tpm => {
616 verify_tpm_attestation(acd, &data.attestation_object, &client_data_json_hash)?
617 }
618 AttestationFormat::AppleAnonymous => verify_apple_anonymous_attestation(
620 acd,
621 &data.attestation_object,
622 &client_data_json_hash,
623 )?,
624 AttestationFormat::AndroidKey => verify_android_key_attestation(
626 acd,
627 &data.attestation_object,
628 &client_data_json_hash,
629 )?,
630 AttestationFormat::AndroidSafetyNet => {
631 return Err(WebauthnError::AttestationNotSupported)
632 }
633 AttestationFormat::None => (ParsedAttestationData::None, AttestationMetadata::None),
634 };
635
636 let credential: Credential = Credential::new(
637 acd,
638 &data.attestation_object.auth_data,
639 COSEKey::try_from(&acd.credential_pk)?,
640 policy,
641 ParsedAttestation {
642 data: attestation_data,
643 metadata: attestation_metadata,
644 },
645 req_extn,
646 ®.extensions,
647 attest_format,
648 &data.transports,
649 );
650
651 let attested_ca_crt = if let Some(ca_list) = attestation_cas {
672 let ca_crt = verify_attestation_ca_chain(
674 &credential.attestation.data,
675 ca_list,
676 danger_disable_certificate_time_checks,
677 )?;
678
679 let ca_crt = ca_crt.ok_or_else(|| {
684 warn!("device attested with a certificate not present in attestation ca chain");
685 WebauthnError::AttestationNotVerifiable
686 })?;
687 Some(ca_crt)
688 } else {
689 None
690 };
691
692 debug!("attested_ca_crt = {:?}", attested_ca_crt);
693
694 if let Some(att_ca_crt) = attested_ca_crt {
697 if att_ca_crt.blanket_allow() {
698 trace!("CA allows all associated keys.");
699 } else {
700 match &credential.attestation.metadata {
701 AttestationMetadata::Packed { aaguid }
702 | AttestationMetadata::Tpm { aaguid, .. } => {
703 if !att_ca_crt.aaguids().contains_key(aaguid) {
705 return Err(WebauthnError::AttestationUntrustedAaguid);
706 }
707 }
708 _ => {
709 return Err(WebauthnError::AttestationFormatMissingAaguid);
711 }
712 }
713 }
714 };
715
716 let alg_valid = credential_algorithms
718 .iter()
719 .any(|alg| alg == &credential.cred.type_);
720
721 if !alg_valid {
722 error!(
723 "Authenticator ignored requested algorithm set - {:?} - {:?}",
724 credential.cred.type_, credential_algorithms
725 );
726 return Err(WebauthnError::CredentialAlteredAlgFromRequest);
727 }
728
729 if !allow_synchronised_authenticators && credential.backup_eligible {
731 error!("Credential counter is 0 - may indicate that it is a passkey and not bound to hardware.");
732 return Err(WebauthnError::CredentialMayNotBeHardwareBound);
733 }
734
735 if credential.backup_state && !credential.backup_eligible {
738 error!("Credential indicates it is backed up, but has not declared valid backup eligibility");
739 return Err(WebauthnError::CredentialMayNotBeHardwareBound);
740 }
741
742 let excluded = exclude_credentials
744 .iter()
745 .any(|credid| credid.as_slice() == credential.cred_id.as_slice());
746
747 if excluded {
748 return Err(WebauthnError::CredentialAlteredAlgFromRequest);
749 }
750
751 Ok(credential)
766 }
767
768 pub(crate) fn verify_credential_internal(
770 &self,
771 rsp: &PublicKeyCredential,
772 policy: UserVerificationPolicy,
773 chal: &ChallengeRef,
774 cred: &Credential,
775 appid: &Option<String>,
776 allow_backup_eligible_upgrade: bool,
777 ) -> Result<AuthenticatorData<Authentication>, WebauthnError> {
778 let data = AuthenticatorAssertionResponse::try_from(&rsp.response).map_err(|e| {
785 debug!("AuthenticatorAssertionResponse::try_from -> {:?}", e);
786 e
787 })?;
788
789 let c = &data.client_data;
790
791 if c.type_ != "webauthn.get" {
800 return Err(WebauthnError::InvalidClientDataType);
801 }
802
803 if c.challenge.as_slice() != chal.as_ref() {
806 return Err(WebauthnError::MismatchedChallenge);
807 }
808
809 if !self.allowed_origins.iter().any(|origin| {
811 Self::origins_match(
812 self.allow_subdomains_origin,
813 self.allow_any_port,
814 &c.origin,
815 origin,
816 )
817 }) {
818 return Err(WebauthnError::InvalidRPOrigin);
819 }
820
821 let has_appid_enabled = rsp.extensions.appid.unwrap_or(false);
830
831 let appid_hash = if has_appid_enabled {
832 appid.as_ref().map(|id| compute_sha256(id.as_bytes()))
833 } else {
834 None
835 };
836
837 if !(data.authenticator_data.rp_id_hash == self.rp_id_hash
838 || Some(&data.authenticator_data.rp_id_hash) == appid_hash.as_ref())
839 {
840 return Err(WebauthnError::InvalidRPIDHash);
841 }
842
843 if !data.authenticator_data.user_present {
845 return Err(WebauthnError::UserNotPresent);
846 }
847
848 match (&policy, &cred.registration_policy) {
854 (_, UserVerificationPolicy::Required) | (UserVerificationPolicy::Required, _) => {
855 if !data.authenticator_data.user_verified {
857 return Err(WebauthnError::UserNotVerified);
858 }
859 }
860 (_, UserVerificationPolicy::Preferred) => {
861 if cred.user_verified && !data.authenticator_data.user_verified {
864 debug!("Token registered UV=preferred, enforcing UV policy.");
865 return Err(WebauthnError::UserNotVerified);
866 }
867 }
868 _ => {}
872 }
873
874 if cred.backup_eligible != data.authenticator_data.backup_eligible {
880 if allow_backup_eligible_upgrade
881 && !cred.backup_eligible
882 && data.authenticator_data.backup_eligible
883 {
884 debug!("Credential backup eligibility has changed!");
885 } else {
886 error!("Credential backup eligibility has changed!");
887 return Err(WebauthnError::CredentialBackupEligibilityInconsistent);
888 }
889 }
890
891 if data.authenticator_data.backup_state && !cred.backup_eligible {
894 error!("Credential indicates it is backed up, but has not declared valid backup eligibility");
895 return Err(WebauthnError::CredentialMayNotBeHardwareBound);
896 }
897
898 debug!("extensions: {:?}", data.authenticator_data.extensions);
911
912 let client_data_json_hash = compute_sha256(data.client_data_bytes.as_slice());
914
915 let verification_data: Vec<u8> = data
921 .authenticator_data_bytes
922 .iter()
923 .chain(client_data_json_hash.iter())
924 .copied()
925 .collect();
926
927 let verified = cred
928 .cred
929 .verify_signature(&data.signature, &verification_data)?;
930
931 if !verified {
932 return Err(WebauthnError::AuthenticationFailure);
933 }
934
935 Ok(data.authenticator_data)
936 }
937
938 pub fn new_challenge_authenticate_builder(
945 &self,
946 creds: Vec<Credential>,
947 policy: Option<UserVerificationPolicy>,
948 ) -> Result<ChallengeAuthenticateBuilder, WebauthnError> {
949 let policy = if let Some(policy) = policy {
950 policy
951 } else {
952 let policy = creds
953 .first()
954 .map(|cred| cred.registration_policy.to_owned())
955 .ok_or(WebauthnError::CredentialNotFound)?;
956
957 for cred in creds.iter() {
958 if cred.registration_policy != policy {
959 return Err(WebauthnError::InconsistentUserVerificationPolicy);
960 }
961 }
962
963 policy
964 };
965
966 Ok(ChallengeAuthenticateBuilder {
967 creds,
968 policy,
969 extensions: Default::default(),
970 allow_backup_eligible_upgrade: Default::default(),
971 hints: Default::default(),
972 })
973 }
974
975 pub fn generate_challenge_authenticate(
986 &self,
987 challenge_builder: ChallengeAuthenticateBuilder,
988 ) -> Result<(RequestChallengeResponse, AuthenticationState), WebauthnError> {
989 let ChallengeAuthenticateBuilder {
990 creds,
991 policy,
992 extensions,
993 allow_backup_eligible_upgrade,
994 hints,
995 } = challenge_builder;
996
997 let chal = self.generate_challenge();
998
999 let ac = creds
1001 .iter()
1002 .map(|cred| AllowCredentials {
1003 type_: "public-key".to_string(),
1004 id: cred.cred_id.as_ref().into(),
1005 transports: cred.transports.clone(),
1006 })
1007 .collect();
1008
1009 let appid = extensions.as_ref().and_then(|e| e.appid.clone());
1011
1012 let timeout_millis =
1013 u32::try_from(self.authenticator_timeout.as_millis()).expect("Timeout too large");
1014
1015 let r = RequestChallengeResponse {
1018 public_key: PublicKeyCredentialRequestOptions {
1019 challenge: chal.clone().into(),
1020 timeout: Some(timeout_millis),
1021 rp_id: self.rp_id.clone(),
1022 allow_credentials: ac,
1023 user_verification: policy,
1024 extensions,
1025 hints,
1026 },
1027 mediation: None,
1028 };
1029 let st = AuthenticationState {
1030 credentials: creds,
1031 policy,
1032 challenge: chal.into(),
1033 appid,
1034 allow_backup_eligible_upgrade,
1035 };
1036 Ok((r, st))
1037 }
1038
1039 pub fn authenticate_credential(
1050 &self,
1051 rsp: &PublicKeyCredential,
1052 state: &AuthenticationState,
1053 ) -> Result<AuthenticationResult, WebauthnError> {
1054 let AuthenticationState {
1060 credentials: creds,
1061 policy,
1062 challenge: chal,
1063 appid,
1064 allow_backup_eligible_upgrade,
1065 } = state;
1066 let chal: &ChallengeRef = chal.into();
1067
1068 let cred = {
1076 let mut found_cred: Option<&Credential> = None;
1095 for cred in creds {
1096 if cred.cred_id.as_slice() == rsp.raw_id.as_slice() {
1097 found_cred = Some(cred);
1098 break;
1099 }
1100 }
1101
1102 found_cred.ok_or(WebauthnError::CredentialNotFound)?
1103 };
1104
1105 let auth_data = self.verify_credential_internal(
1130 rsp,
1131 *policy,
1132 chal,
1133 cred,
1134 appid,
1135 *allow_backup_eligible_upgrade,
1136 )?;
1137 let mut needs_update = false;
1138 let counter = auth_data.counter;
1139 let user_verified = auth_data.user_verified;
1140 let backup_state = auth_data.backup_state;
1141 let backup_eligible = auth_data.backup_eligible;
1142
1143 let extensions = process_authentication_extensions(&auth_data.extensions);
1144
1145 if backup_state != cred.backup_state {
1146 needs_update = true;
1147 }
1148
1149 if backup_eligible != cred.backup_eligible {
1150 needs_update = true;
1151 }
1152
1153 if counter > 0 || cred.counter > 0 {
1156 let counter_shows_compromise = auth_data.counter <= cred.counter;
1166
1167 if counter > cred.counter {
1168 needs_update = true;
1169 }
1170
1171 if self.require_valid_counter_value && counter_shows_compromise {
1172 return Err(WebauthnError::CredentialPossibleCompromise);
1173 }
1174 }
1175
1176 Ok(AuthenticationResult {
1177 cred_id: cred.cred_id.clone(),
1178 needs_update,
1179 user_verified,
1180 backup_eligible,
1181 backup_state,
1182 counter,
1183 extensions,
1184 })
1185 }
1186
1187 fn origins_match(
1188 allow_subdomains_origin: bool,
1189 allow_any_port: bool,
1190 ccd_url: &url::Url,
1191 cnf_url: &url::Url,
1192 ) -> bool {
1193 if ccd_url == cnf_url {
1194 return true;
1195 }
1196 if allow_subdomains_origin {
1197 match (ccd_url.origin(), cnf_url.origin()) {
1198 (
1199 url::Origin::Tuple(ccd_scheme, ccd_host, ccd_port),
1200 url::Origin::Tuple(cnf_scheme, cnf_host, cnf_port),
1201 ) => {
1202 if ccd_scheme != cnf_scheme {
1203 debug!("{} != {}", ccd_url, cnf_url);
1204 return false;
1205 }
1206
1207 if !allow_any_port && ccd_port != cnf_port {
1208 debug!("{} != {}", ccd_url, cnf_url);
1209 return false;
1210 }
1211
1212 let valid = match (ccd_host, cnf_host) {
1213 (url::Host::Domain(ccd_domain), url::Host::Domain(cnf_domain)) => {
1214 ccd_domain.ends_with(&cnf_domain)
1215 }
1216 (a, b) => a == b,
1217 };
1218
1219 if valid {
1220 true
1221 } else {
1222 debug!("Domain/IP in origin do not match");
1223 false
1224 }
1225 }
1226 _ => {
1227 debug!("Origin is opaque");
1228 false
1229 }
1230 }
1231 } else if ccd_url.origin() != cnf_url.origin() || !ccd_url.origin().is_tuple() {
1232 if ccd_url.host() == cnf_url.host()
1233 && ccd_url.scheme() == cnf_url.scheme()
1234 && allow_any_port
1235 {
1236 true
1237 } else {
1238 debug!("{} != {}", ccd_url, cnf_url);
1239 false
1240 }
1241 } else {
1242 true
1243 }
1244 }
1245
1246 pub fn rp_name(&self) -> &str {
1248 self.rp_name.as_str()
1249 }
1250}
1251
1252#[cfg(test)]
1253mod tests {
1254 #![allow(clippy::panic)]
1255
1256 use crate::constants::CHALLENGE_SIZE_BYTES;
1257 use crate::core::{CreationChallengeResponse, RegistrationState, WebauthnError};
1258 use crate::internals::*;
1259 use crate::proto::*;
1260 use crate::WebauthnCore as Webauthn;
1261 use base64::{engine::general_purpose::STANDARD, Engine};
1262 use base64urlsafedata::{Base64UrlSafeData, HumanBinaryData};
1263 use std::time::Duration;
1264 use url::Url;
1265
1266 use webauthn_rs_device_catalog::data::{
1267 android::ANDROID_SOFTWARE_ROOT_CA, apple::APPLE_WEBAUTHN_ROOT_CA_PEM,
1268 google::GOOGLE_SAFETYNET_CA_OLD,
1269 microsoft::MICROSOFT_TPM_ROOT_CERTIFICATE_AUTHORITY_2014_PEM,
1270 yubico::YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
1271 };
1272
1273 const AUTHENTICATOR_TIMEOUT: Duration = Duration::from_secs(60);
1274
1275 #[test]
1278 fn test_registration_yk() {
1279 let _ = tracing_subscriber::fmt::try_init();
1280 let wan = Webauthn::new_unsafe_experts_only(
1281 "http://127.0.0.1:8080/auth",
1282 "127.0.0.1",
1283 vec![Url::parse("http://127.0.0.1:8080").unwrap()],
1284 AUTHENTICATOR_TIMEOUT,
1285 None,
1286 None,
1287 );
1288 let zero_chal = Challenge::new((0..CHALLENGE_SIZE_BYTES).map(|_| 0).collect::<Vec<u8>>());
1292
1293 let rsp = r#"
1299 {
1300 "id":"0xYE4bQ_HZM51-XYwp7WHJu8RfeA2Oz3_9HnNIZAKqRTz9gsUlF3QO7EqcJ0pgLSwDcq6cL1_aQpTtKLeGu6Ig",
1301 "rawId":"0xYE4bQ_HZM51-XYwp7WHJu8RfeA2Oz3_9HnNIZAKqRTz9gsUlF3QO7EqcJ0pgLSwDcq6cL1_aQpTtKLeGu6Ig",
1302 "response":{
1303 "attestationObject":"o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIhALjRb43YFcbJ3V9WiYPpIrZkhgzAM6KTR8KIjwCXejBCAiAO5Lvp1VW4dYBhBDv7HZIrxZb1SwKKYOLfFRXykRxMqGN4NWOBWQLBMIICvTCCAaWgAwIBAgIEGKxGwDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNDEzOTQzNDg4MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeeo7LHxJcBBiIwzSP-tg5SkxcdSD8QC-hZ1rD4OXAwG1Rs3Ubs_K4-PzD4Hp7WK9Jo1MHr03s7y-kqjCrutOOqNsMGowIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBSAwIQYLKwYBBAGC5RwBAQQEEgQQy2lIHo_3QDmT7AonKaFUqDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCXnQOX2GD4LuFdMRx5brr7Ivqn4ITZurTGG7tX8-a0wYpIN7hcPE7b5IND9Nal2bHO2orh_tSRKSFzBY5e4cvda9rAdVfGoOjTaCW6FZ5_ta2M2vgEhoz5Do8fiuoXwBa1XCp61JfIlPtx11PXm5pIS2w3bXI7mY0uHUMGvxAzta74zKXLslaLaSQibSKjWKt9h-SsXy4JGqcVefOlaQlJfXL1Tga6wcO0QTu6Xq-Uw7ZPNPnrpBrLauKDd202RlN4SP7ohL3d9bG6V5hUz_3OusNEBZUn5W3VmPj1ZnFavkMB3RkRMOa58MZAORJT4imAPzrvJ0vtv94_y71C6tZ5aGF1dGhEYXRhWMQSyhe0mvIolDbzA-AWYDCiHlJdJm4gkmdDOAGo_UBxoEEAAAAAAAAAAAAAAAAAAAAAAAAAAABA0xYE4bQ_HZM51-XYwp7WHJu8RfeA2Oz3_9HnNIZAKqRTz9gsUlF3QO7EqcJ0pgLSwDcq6cL1_aQpTtKLeGu6IqUBAgMmIAEhWCCe1KvqpcVWN416_QZc8vJynt3uo3_WeJ2R4uj6kJbaiiJYIDC5ssxxummKviGgLoP9ZLFb836A9XfRO7op18QY3i5m",
1304 "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovLzEyNy4wLjAuMTo4MDgwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9"
1305 },
1306 "type":"public-key"}
1307 "#;
1308 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(rsp).unwrap();
1310
1311 let result = wan.register_credential_internal(
1313 &rsp_d,
1314 UserVerificationPolicy::Preferred,
1315 &zero_chal,
1316 &[],
1317 &[COSEAlgorithm::ES256],
1318 Some(&YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM.try_into().unwrap()),
1319 false,
1320 &RequestRegistrationExtensions::default(),
1321 true,
1322 );
1323 trace!("{:?}", result);
1324 assert!(result.is_ok());
1325 }
1326
1327 #[test]
1329 fn test_registration_duo_go() {
1330 let _ = tracing_subscriber::fmt::try_init();
1331 let wan = Webauthn::new_unsafe_experts_only(
1332 "webauthn.io",
1333 "webauthn.io",
1334 vec![Url::parse("https://webauthn.io").unwrap()],
1335 AUTHENTICATOR_TIMEOUT,
1336 None,
1337 None,
1338 );
1339
1340 let chal = Challenge::new(
1341 STANDARD
1342 .decode("+Ri5NZTzJ8b6mvW3TVScLotEoALfgBa2Bn4YSaIObHc=")
1343 .unwrap(),
1344 );
1345
1346 let rsp = r#"
1347 {
1348 "id": "FOxcmsqPLNCHtyILvbNkrtHMdKAeqSJXYZDbeFd0kc5Enm8Kl6a0Jp0szgLilDw1S4CjZhe9Z2611EUGbjyEmg",
1349 "rawId": "FOxcmsqPLNCHtyILvbNkrtHMdKAeqSJXYZDbeFd0kc5Enm8Kl6a0Jp0szgLilDw1S4CjZhe9Z2611EUGbjyEmg",
1350 "response": {
1351 "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEYwRAIgfyIhwZj-fkEVyT1GOK8chDHJR2chXBLSRg6bTCjODmwCIHH6GXI_BQrcR-GHg5JfazKVQdezp6_QWIFfT4ltTCO2Y3g1Y4FZAlMwggJPMIIBN6ADAgECAgQSNtF_MA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAxMS8wLQYDVQQDDCZZdWJpY28gVTJGIEVFIFNlcmlhbCAyMzkyNTczNDEwMzI0MTA4NzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNNlqR5emeDVtDnA2a-7h_QFjkfdErFE7bFNKzP401wVE-QNefD5maviNnGVk4HJ3CsHhYuCrGNHYgTM9zTWriGjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS41MBMGCysGAQQBguUcAgEBBAQDAgUgMA0GCSqGSIb3DQEBCwUAA4IBAQAiG5uzsnIk8T6-oyLwNR6vRklmo29yaYV8jiP55QW1UnXdTkEiPn8mEQkUac-Sn6UmPmzHdoGySG2q9B-xz6voVQjxP2dQ9sgbKd5gG15yCLv6ZHblZKkdfWSrUkrQTrtaziGLFSbxcfh83vUjmOhDLFC5vxV4GXq2674yq9F2kzg4nCS4yXrO4_G8YWR2yvQvE2ffKSjQJlXGO5080Ktptplv5XN4i5lS-AKrT5QRVbEJ3B4g7G0lQhdYV-6r4ZtHil8mF4YNMZ0-RaYPxAaYNWkFYdzOZCaIdQbXRZefgGfbMUiAC2gwWN7fiPHV9eu82NYypGU32OijG9BjhGt_aGF1dGhEYXRhWMR0puqSE8mcL3SyJJKzIM9AJiqUwalQoDl_KSULYIQe8EEAAAAAAAAAAAAAAAAAAAAAAAAAAABAFOxcmsqPLNCHtyILvbNkrtHMdKAeqSJXYZDbeFd0kc5Enm8Kl6a0Jp0szgLilDw1S4CjZhe9Z2611EUGbjyEmqUBAgMmIAEhWCD_ap3Q9zU8OsGe967t48vyRxqn8NfFTk307mC1WsH2ISJYIIcqAuW3MxhU0uDtaSX8-Ftf_zeNJLdCOEjZJGHsrLxH",
1352 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiItUmk1TlpUeko4YjZtdlczVFZTY0xvdEVvQUxmZ0JhMkJuNFlTYUlPYkhjIiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5pbyIsInR5cGUiOiJ3ZWJhdXRobi5jcmVhdGUifQ"
1353 },
1354 "type": "public-key"
1355 }
1356 "#;
1357 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(rsp).unwrap();
1358 let result = wan.register_credential_internal(
1359 &rsp_d,
1360 UserVerificationPolicy::Preferred,
1361 chal.as_ref(),
1362 &[],
1363 &[COSEAlgorithm::ES256],
1364 None,
1365 false,
1366 &RequestRegistrationExtensions::default(),
1367 true,
1368 );
1369 trace!("{:?}", result);
1370 assert!(result.is_ok());
1371 }
1372
1373 #[test]
1374 fn test_registration_packed_attestation() {
1375 let _ = tracing_subscriber::fmt::try_init();
1376 let wan = Webauthn::new_unsafe_experts_only(
1377 "localhost:8443/auth",
1378 "localhost",
1379 vec![Url::parse("https://localhost:8443").unwrap()],
1380 AUTHENTICATOR_TIMEOUT,
1381 None,
1382 None,
1383 );
1384
1385 let chal = Challenge::new(
1386 STANDARD
1387 .decode("lP6mWNAtG+/Vv15iM7lb/XRkdWMvVQ+lTyKwZuOg1Vo=")
1388 .unwrap(),
1389 );
1390
1391 let rsp = r#"{
1394 "id":"ATk_7QKbi_ntSdp16LXeU6RDf9YnRLIDTCqEjJFzc6rKBhbqoSYccxNa",
1395 "rawId":"ATk_7QKbi_ntSdp16LXeU6RDf9YnRLIDTCqEjJFzc6rKBhbqoSYccxNa",
1396 "response":{
1397 "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgLXPjBtVEhBH3KdUDFFk3LAd9EtHogllIf48vjX4wgfECIQCXOymmfg12FPMXEdwpSjjtmrvki4K8y0uYxqWN5Bw6DGhhdXRoRGF0YViuSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFXaqejq3OAAI1vMYKZIsLJfHwVQMAKgE5P-0Cm4v57Unadei13lOkQ3_WJ0SyA0wqhIyRc3OqygYW6qEmHHMTWqUBAgMmIAEhWCDNRS_Gw52ow5PNrC9OdFTFNudDmZO6Y3wmM9N8e0tJICJYIC09iIH5_RrT5tbS0PIw3srdAxYDMGao7yWgu0JFIEzT",
1398 "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJsUDZtV05BdEctX1Z2MTVpTTdsYl9YUmtkV012VlEtbFR5S3dadU9nMVZvIiwiZXh0cmFfa2V5c19tYXlfYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"
1399 },
1400 "type":"public-key"
1401 }
1402 "#;
1403 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(rsp).unwrap();
1404 let result = wan.register_credential_internal(
1405 &rsp_d,
1406 UserVerificationPolicy::Preferred,
1407 &chal,
1408 &[],
1409 &[COSEAlgorithm::ES256],
1410 None,
1411 false,
1412 &RequestRegistrationExtensions::default(),
1413 false,
1414 );
1415 assert!(result.is_ok());
1416 }
1417
1418 #[test]
1419 fn test_registration_packed_attestaion_fails_with_bad_cred_protect() {
1420 let _ = tracing_subscriber::fmt::try_init();
1421 let wan = Webauthn::new_unsafe_experts_only(
1422 "localhost:8080/auth",
1423 "localhost",
1424 vec![Url::parse("http://localhost:8080").unwrap()],
1425 AUTHENTICATOR_TIMEOUT,
1426 None,
1427 None,
1428 );
1429
1430 let chal = Challenge::new(vec![
1431 125, 119, 194, 67, 227, 22, 152, 134, 220, 143, 75, 119, 197, 165, 115, 149, 187, 153,
1432 211, 51, 215, 128, 225, 56, 110, 80, 52, 235, 149, 146, 101, 202,
1433 ]);
1434
1435 let rsp = r#"{
1436 "id":"9KJylaUgVoWF2cF2qX5an7ZtPBFeRMXy-jMSGgNWCogxiyctVFtIcDKmkVmfKOgllffKJMyl4gFeDm8KaltrDw",
1437 "rawId":"9KJylaUgVoWF2cF2qX5an7ZtPBFeRMXy-jMSGgNWCogxiyctVFtIcDKmkVmfKOgllffKJMyl4gFeDm8KaltrDw",
1438 "response":{
1439 "attestationObject":"o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEYwRAIgZEq9euYGkqTP4VMBs-5fruhwAPSyKjOlr2THNZGvZ3gCIHww2gAgZXvZcIwcSiUF3fHhaNL0uj8V5rOLHyGRJz81Y3g1Y4FZAsEwggK9MIIBpaADAgECAgQej4c0MA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjBuMQswCQYDVQQGEwJTRTESMBAGA1UECgwJWXViaWNvIEFCMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMScwJQYDVQQDDB5ZdWJpY28gVTJGIEVFIFNlcmlhbCA1MTI3MjI3NDAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASoefgjOO0UlLrAcEvMf8Zj0bJxcVl2JDEBx2BRFdfBUp4oHBxnMi04S1zVXdPpgY1f2FwirzJuDGT8IK_jPyNmo2wwajAiBgkrBgEEAYLECgIEFTEuMy42LjEuNC4xLjQxNDgyLjEuNzATBgsrBgEEAYLlHAIBAQQEAwIEMDAhBgsrBgEEAYLlHAEBBAQSBBAvwFefgRNH6rEWu1qNuSAqMAwGA1UdEwEB_wQCMAAwDQYJKoZIhvcNAQELBQADggEBAIaT_2LfDVd51HSNf8jRAicxio5YDmo6V8EI6U4Dw4Vos2aJT85WJL5KPv1_NBGLPZk3Q_eSoZiRYMj8muCwTj357hXj6IwE_IKo3L9YGOEI3MKWhXeuef9mK5RzTj3sRZcwXXPm5V7ivrnNlnjKCTXlM-tjj44m-ruBfNpEH76YMYMq5fbirZkvnrvbTGIji4-NerSB1tMmO82_nkpXVQNwmIrVgTRA-gMsrbZyPK3Y-Ne6gJ91tDz_oKW5rdFCMu-dnhSBJjgjPEykqHO5-KyY4yuhkWdgbhWQn83bSi3_va5GICSfmmZGrIHkgy0RGf6_qnMaiC2iWneCfUbRkBdoYXV0aERhdGFY0kmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjxQAAAAEvwFefgRNH6rEWu1qNuSAqAED0onKVpSBWhYXZwXapflqftm08EV5ExfL6MxIaA1YKiDGLJy1UW0hwMqaRWZ8o6CWV98okzKXiAV4ObwpqW2sPpQECAyYgASFYIB_nQH-kBm4OmDfqezjFDr_t0Psz6JrylkEPWHFs2UB-Ilgg7xkwKc-IHHIwPI8EJ5ycM1zvWDnm4bCarn1LAWAU3Dqha2NyZWRQcm90ZWN0Aw",
1440 "clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZlhmQ1EtTVdtSWJjajB0M3hhVnpsYnVaMHpQWGdPRTRibEEwNjVXU1pjbyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9"
1441 },
1442 "type":"public-key"
1443 }"#;
1444 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(rsp).unwrap();
1445
1446 trace!("{:?}", rsp_d);
1447
1448 let result = wan.register_credential_internal(
1449 &rsp_d,
1450 UserVerificationPolicy::Required,
1451 &chal,
1452 &[],
1453 &[COSEAlgorithm::ES256],
1454 None,
1455 false,
1456 &RequestRegistrationExtensions::default(),
1457 false,
1458 );
1459 trace!("{:?}", result);
1460 assert!(result.is_ok());
1461 }
1462
1463 #[test]
1464 fn test_registration_packed_attestaion_works_with_valid_fido_aaguid_extension() {
1465 let _ = tracing_subscriber::fmt::try_init();
1466 let wan = Webauthn::new_unsafe_experts_only(
1467 "webauthn.firstyear.id.au",
1468 "webauthn.firstyear.id.au",
1469 vec![Url::parse("https://webauthn.firstyear.id.au/compat_test").unwrap()],
1470 AUTHENTICATOR_TIMEOUT,
1471 None,
1472 None,
1473 );
1474
1475 let chal: HumanBinaryData =
1476 serde_json::from_str("\"qabSCYW_PPKKBAW5_qEsPF3Q3prQeYBORfDMArsoKdg\"").unwrap();
1477 let chal = Challenge::from(chal);
1478
1479 let rsp = r#"{
1480 "id": "eKSmfhLUwwmJpuD2IKaTopbbWKFv-qZAE4LXa2FGmTtRpvioMpeFhI8RqdsOGlBoQxJehEQyWyu7ECwPkVL5Hg",
1481 "rawId": "eKSmfhLUwwmJpuD2IKaTopbbWKFv-qZAE4LXa2FGmTtRpvioMpeFhI8RqdsOGlBoQxJehEQyWyu7ECwPkVL5Hg",
1482 "response": {
1483 "attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIgW2gYNWvUDgxl8LB7rflbuJw_zvJCT5ddfDZNROTy0JYCIQDxuy3JLSHDIrEFYqDifFA_ZHttNfRqJAPgH4hedttVIWN4NWOBWQLBMIICvTCCAaWgAwIBAgIEHo-HNDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNTEyNzIyNzQwMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqHn4IzjtFJS6wHBLzH_GY9GycXFZdiQxAcdgURXXwVKeKBwcZzItOEtc1V3T6YGNX9hcIq8ybgxk_CCv4z8jZqNsMGowIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBDAwIQYLKwYBBAGC5RwBAQQEEgQQL8BXn4ETR-qxFrtajbkgKjAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCGk_9i3w1XedR0jX_I0QInMYqOWA5qOlfBCOlOA8OFaLNmiU_OViS-Sj79fzQRiz2ZN0P3kqGYkWDI_JrgsE49-e4V4-iMBPyCqNy_WBjhCNzCloV3rnn_ZiuUc0497EWXMF1z5uVe4r65zZZ4ygk15TPrY4-OJvq7gXzaRB--mDGDKuX24q2ZL56720xiI4uPjXq0gdbTJjvNv55KV1UDcJiK1YE0QPoDLK22cjyt2PjXuoCfdbQ8_6Clua3RQjLvnZ4UgSY4IzxMpKhzufismOMroZFnYG4VkJ_N20ot_72uRiAkn5pmRqyB5IMtERn-v6pzGogtolp3gn1G0ZAXaGF1dGhEYXRhWMRqubvw35oW-R27M7uxMvr50Xx4LEgmxuxw7O5Y2X71KkUAAAACL8BXn4ETR-qxFrtajbkgKgBAeKSmfhLUwwmJpuD2IKaTopbbWKFv-qZAE4LXa2FGmTtRpvioMpeFhI8RqdsOGlBoQxJehEQyWyu7ECwPkVL5HqUBAgMmIAEhWCBT_WnxT3SKAIGfnEKUi7xtZmnlcZRV-63N21154_r-xyJYIGuwu6BK1zp6D6EQ94VOcK1DuFWr58xI_PbeP5F1Nfe6",
1484 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJxYWJTQ1lXX1BQS0tCQVc1X3FFc1BGM1EzcHJRZVlCT1JmRE1BcnNvS2RnIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"
1485 },
1486 "type": "public-key"
1487 }"#;
1488
1489 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(rsp).unwrap();
1490
1491 trace!("{:?}", rsp_d);
1492
1493 let result = wan.register_credential_internal(
1494 &rsp_d,
1495 UserVerificationPolicy::Required,
1496 &chal,
1497 &[],
1498 &[COSEAlgorithm::ES256],
1499 None,
1500 false,
1501 &RequestRegistrationExtensions::default(),
1502 false,
1503 );
1504 trace!("{:?}", result);
1505 assert!(result.is_ok());
1506 }
1507
1508 #[test]
1509 fn test_registration_packed_attestaion_fails_with_invalid_fido_aaguid_extension() {
1510 let _ = tracing_subscriber::fmt::try_init();
1511 let wan = Webauthn::new_unsafe_experts_only(
1512 "webauthn.firstyear.id.au",
1513 "webauthn.firstyear.id.au",
1514 vec![Url::parse("https://webauthn.firstyear.id.au/compat_test").unwrap()],
1515 AUTHENTICATOR_TIMEOUT,
1516 None,
1517 None,
1518 );
1519
1520 let chal: HumanBinaryData =
1521 serde_json::from_str("\"qabSCYW_PPKKBAW5_qEsPF3Q3prQeYBORfDMArsoKdg\"").unwrap();
1522 let chal = Challenge::from(chal);
1523
1524 let rsp = r#"{
1525 "id": "eKSmfhLUwwmJpuD2IKaTopbbWKFv-qZAE4LXa2FGmTtRpvioMpeFhI8RqdsOGlBoQxJehEQyWyu7ECwPkVL5Hg",
1526 "rawId": "eKSmfhLUwwmJpuD2IKaTopbbWKFv-qZAE4LXa2FGmTtRpvioMpeFhI8RqdsOGlBoQxJehEQyWyu7ECwPkVL5Hg",
1527 "response": {
1528 "attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIgW2gYNWvUDgxl8LB7rflbuJw_zvJCT5ddfDZNROTy0JYCIQDxuy3JLSHDIrEFYqDifFA_ZHttNfRqJAPgH4hedttVIWN4NWOBWQLBMIICvTCCAaWgAwIBAgIEHo-HNDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNTEyNzIyNzQwMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqHn4IzjtFJS6wHBLzH_GY9GycXFZdiQxAcdgURXXwVKeKBwcZzItOEtc1V3T6YGNX9hcIq8ybgxk_CCv4z8jZqNsMGowIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBDAwIQYLKwYBBAGC5RwBAQQEEgQQXXXXXXXXXXXXXXXXXXXXXjAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCGk_9i3w1XedR0jX_I0QInMYqOWA5qOlfBCOlOA8OFaLNmiU_OViS-Sj79fzQRiz2ZN0P3kqGYkWDI_JrgsE49-e4V4-iMBPyCqNy_WBjhCNzCloV3rnn_ZiuUc0497EWXMF1z5uVe4r65zZZ4ygk15TPrY4-OJvq7gXzaRB--mDGDKuX24q2ZL56720xiI4uPjXq0gdbTJjvNv55KV1UDcJiK1YE0QPoDLK22cjyt2PjXuoCfdbQ8_6Clua3RQjLvnZ4UgSY4IzxMpKhzufismOMroZFnYG4VkJ_N20ot_72uRiAkn5pmRqyB5IMtERn-v6pzGogtolp3gn1G0ZAXaGF1dGhEYXRhWMRqubvw35oW-R27M7uxMvr50Xx4LEgmxuxw7O5Y2X71KkUAAAACL8BXn4ETR-qxFrtajbkgKgBAeKSmfhLUwwmJpuD2IKaTopbbWKFv-qZAE4LXa2FGmTtRpvioMpeFhI8RqdsOGlBoQxJehEQyWyu7ECwPkVL5HqUBAgMmIAEhWCBT_WnxT3SKAIGfnEKUi7xtZmnlcZRV-63N21154_r-xyJYIGuwu6BK1zp6D6EQ94VOcK1DuFWr58xI_PbeP5F1Nfe6",
1529 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJxYWJTQ1lXX1BQS0tCQVc1X3FFc1BGM1EzcHJRZVlCT1JmRE1BcnNvS2RnIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"
1530 },
1531 "type": "public-key"
1532 }"#;
1533
1534 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(rsp).unwrap();
1535
1536 trace!("{:?}", rsp_d);
1537
1538 let result = wan.register_credential_internal(
1539 &rsp_d,
1540 UserVerificationPolicy::Required,
1541 &chal,
1542 &[],
1543 &[COSEAlgorithm::ES256],
1544 None,
1545 false,
1546 &RequestRegistrationExtensions::default(),
1547 false,
1548 );
1549 trace!("{:?}", result);
1550 assert!(matches!(
1551 result,
1552 Err(WebauthnError::AttestationCertificateAAGUIDMismatch)
1553 ));
1554 }
1555
1556 #[test]
1557 fn test_authentication() {
1558 let _ = tracing_subscriber::fmt::try_init();
1559 let wan = Webauthn::new_unsafe_experts_only(
1560 "localhost:8080/auth",
1561 "localhost",
1562 vec![Url::parse("http://localhost:8080").unwrap()],
1563 AUTHENTICATOR_TIMEOUT,
1564 None,
1565 None,
1566 );
1567
1568 let zero_chal = Challenge::new(vec![
1572 90, 5, 243, 254, 68, 239, 221, 101, 20, 214, 76, 60, 134, 111, 142, 26, 129, 146, 225,
1573 144, 135, 95, 253, 219, 18, 161, 199, 216, 251, 213, 167, 195,
1574 ]);
1575
1576 let cred = Credential {
1578 cred_id: HumanBinaryData::from(vec![
1579 106, 223, 133, 124, 161, 172, 56, 141, 181, 18, 27, 66, 187, 181, 113, 251, 187,
1580 123, 20, 169, 41, 80, 236, 138, 92, 137, 4, 4, 16, 255, 188, 47, 158, 202, 111,
1581 192, 117, 110, 152, 245, 95, 22, 200, 172, 71, 154, 40, 181, 212, 64, 80, 17, 238,
1582 238, 21, 13, 27, 145, 140, 27, 208, 101, 166, 81,
1583 ]),
1584 cred: COSEKey {
1585 type_: COSEAlgorithm::ES256,
1586 key: COSEKeyType::EC_EC2(COSEEC2Key {
1587 curve: ECDSACurve::SECP256R1,
1588 x: [
1589 46, 121, 76, 233, 118, 208, 250, 74, 227, 182, 8, 145, 45, 46, 5, 9, 199,
1590 186, 84, 83, 7, 237, 130, 73, 16, 90, 17, 54, 33, 255, 54, 56,
1591 ]
1592 .to_vec()
1593 .into(),
1594 y: [
1595 117, 105, 1, 23, 253, 223, 67, 135, 253, 219, 253, 223, 17, 247, 91, 197,
1596 205, 225, 143, 59, 47, 138, 70, 120, 74, 155, 177, 177, 166, 233, 48, 71,
1597 ]
1598 .to_vec()
1599 .into(),
1600 }),
1601 },
1602 counter: 1,
1603 transports: None,
1604 user_verified: false,
1605 backup_eligible: false,
1606 backup_state: false,
1607 registration_policy: UserVerificationPolicy::Discouraged_DO_NOT_USE,
1608 extensions: RegisteredExtensions::none(),
1609 attestation: ParsedAttestation {
1610 data: ParsedAttestationData::None,
1611 metadata: AttestationMetadata::None,
1612 },
1613 attestation_format: AttestationFormat::None,
1614 };
1615
1616 let rsp = r#"
1620 {
1621 "id":"at-FfKGsOI21EhtCu7Vx-7t7FKkpUOyKXIkEBBD_vC-eym_AdW6Y9V8WyKxHmii11EBQEe7uFQ0bkYwb0GWmUQ",
1622 "rawId":"at-FfKGsOI21EhtCu7Vx-7t7FKkpUOyKXIkEBBD_vC-eym_AdW6Y9V8WyKxHmii11EBQEe7uFQ0bkYwb0GWmUQ",
1623 "response":{
1624 "authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAFA",
1625 "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXZ1h6X2tUdjNXVVUxa3c4aG0tT0dvR1M0WkNIWF8zYkVxSEgyUHZWcDhNIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9",
1626 "signature":"MEYCIQDmLVOqv85cdRup4Fr8Pf9zC4AWO-XKBJqa8xPwYFCCMAIhAOiExLoyes0xipmUmq0BVlqJaCKLn_MFKG9GIDsCGq_-",
1627 "userHandle":null
1628 },
1629 "type":"public-key"
1630 }
1631 "#;
1632 let rsp_d: PublicKeyCredential = serde_json::from_str(rsp).unwrap();
1633
1634 let r = wan.verify_credential_internal(
1636 &rsp_d,
1637 UserVerificationPolicy::Discouraged_DO_NOT_USE,
1638 &zero_chal,
1639 &cred,
1640 &None,
1641 false,
1642 );
1643 trace!("RESULT: {:?}", r);
1644 assert!(r.is_ok());
1645
1646 let rsp = r#"
1648 {
1649 "id":"at-FfKGsOI21EhtCu7Vx-7t7FKkpUOyKXIkEBBD_vC-eym_AdW6Y9V8WyKxHmii11EBQEe7uFQ0bkYwb0GWmUQ",
1650 "rawId":"at-FfKGsOI21EhtCu7Vx-7t7FKkpUOyKXIkEBBD_vC-eym_AdW6Y9V8WyKxHmii11EBQEe7uFQ0bkYwb0GWmUQ",
1651 "extensions": {
1652 "appid": true
1653 },
1654 "response":{
1655 "authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAFA",
1656 "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJXZ1h6X2tUdjNXVVUxa3c4aG0tT0dvR1M0WkNIWF8zYkVxSEgyUHZWcDhNIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9",
1657 "signature":"MEYCIQDmLVOqv85cdRup4Fr8Pf9zC4AWO-XKBJqa8xPwYFCCMAIhAOiExLoyes0xipmUmq0BVlqJaCKLn_MFKG9GIDsCGq_-",
1658 "userHandle":null
1659 },
1660 "type":"public-key"
1661 }
1662 "#;
1663 let rsp_d: PublicKeyCredential = serde_json::from_str(rsp).unwrap();
1664
1665 let r = wan.verify_credential_internal(
1667 &rsp_d,
1668 UserVerificationPolicy::Discouraged_DO_NOT_USE,
1669 &zero_chal,
1670 &cred,
1671 &Some(String::from("https://unused.local")),
1672 false,
1673 );
1674 trace!("RESULT: {:?}", r);
1675 assert!(r.is_ok());
1676 }
1677
1678 #[test]
1679 fn test_authentication_appid() {
1680 let _ = tracing_subscriber::fmt::try_init();
1681 let wan = Webauthn::new_unsafe_experts_only(
1682 "https://testing.local",
1683 "testing.local",
1684 vec![Url::parse("https://testing.local").unwrap()],
1685 AUTHENTICATOR_TIMEOUT,
1686 None,
1687 None,
1688 );
1689
1690 let zero_chal = Challenge::new(vec![
1694 160, 127, 213, 174, 150, 36, 228, 190, 41, 61, 216, 14, 171, 191, 75, 203, 99, 59, 4,
1695 252, 49, 90, 235, 36, 220, 165, 159, 201, 58, 225, 248, 142,
1696 ]);
1697
1698 let cred = Credential {
1700 counter: 1,
1701 transports: None,
1702 cred_id: HumanBinaryData::from(vec![
1703 179, 64, 237, 0, 28, 248, 197, 30, 213, 228, 250, 139, 28, 11, 156, 130, 69, 242,
1704 21, 48, 84, 77, 103, 163, 66, 204, 167, 147, 82, 214, 212,
1705 ]),
1706 cred: COSEKey {
1707 type_: COSEAlgorithm::ES256,
1708 key: COSEKeyType::EC_EC2(COSEEC2Key {
1709 curve: ECDSACurve::SECP256R1,
1710 x: [
1711 187, 71, 18, 101, 166, 110, 166, 38, 116, 119, 74, 4, 183, 104, 24, 46,
1712 245, 24, 227, 143, 161, 136, 37, 186, 140, 221, 228, 115, 81, 175, 50, 51,
1713 ]
1714 .to_vec()
1715 .into(),
1716 y: [
1717 13, 59, 59, 158, 149, 197, 116, 228, 99, 12, 235, 185, 190, 110, 251, 154,
1718 226, 143, 75, 26, 44, 136, 244, 245, 243, 4, 40, 223, 22, 253, 224, 95,
1719 ]
1720 .to_vec()
1721 .into(),
1722 }),
1723 },
1724 user_verified: false,
1725 backup_eligible: false,
1726 backup_state: false,
1727 registration_policy: UserVerificationPolicy::Discouraged_DO_NOT_USE,
1728 extensions: RegisteredExtensions::none(),
1729 attestation: ParsedAttestation {
1730 data: ParsedAttestationData::None,
1731 metadata: AttestationMetadata::None,
1732 },
1733 attestation_format: AttestationFormat::None,
1734 };
1735
1736 let rsp = r#"
1740 {
1741 "id": "z077A6SzdvA3rDRG6tfnTf9TMFVtfLYe1mh27mRXgBZU6TXA_nCJAi6WnLLq1p3d0yj3n62_4yJMu80o4O8kkw",
1742 "rawId": "z077A6SzdvA3rDRG6tfnTf9TMFVtfLYe1mh27mRXgBZU6TXA_nCJAi6WnLLq1p3d0yj3n62_4yJMu80o4O8kkw",
1743 "type": "public-key",
1744 "extensions": {
1745 "appid": true
1746 },
1747 "response": {
1748 "authenticatorData": "UN6WJxNDzSGdqrQoPbqYbsZdIxtC9vfU9iGe5i1pCTYBAAAAuQ",
1749 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJvSF9WcnBZazVMNHBQZGdPcTc5THkyTTdCUHd4V3VzazNLV2Z5VHJoLUk0IiwiY2xpZW50RXh0ZW5zaW9ucyI6eyJhcHBpZCI6Imh0dHBzOi8vdGVzdGluZy5sb2NhbC9hcHAtaWQuanNvbiJ9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vdGVzdGluZy5sb2NhbCIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ",
1750 "signature": "MEUCIEw2O8LYZj6IKbjP6FuvdcL2MoDBY6LqJWuhteje3H7eAiEAvzRLSg70tkrGnZqpQIZyv-zaizNpCtyr4U3SZ-E2-f4"
1751 }
1752 }
1753 "#;
1754 let rsp_d: PublicKeyCredential = serde_json::from_str(rsp).unwrap();
1755
1756 let r = wan.verify_credential_internal(
1758 &rsp_d,
1759 UserVerificationPolicy::Discouraged_DO_NOT_USE,
1760 &zero_chal,
1761 &cred,
1762 &Some(String::from("https://testing.local/app-id.json")),
1763 false,
1764 );
1765 trace!("RESULT: {:?}", r);
1766 assert!(r.is_ok());
1767
1768 let rsp = r#"
1770 {
1771 "id": "z077A6SzdvA3rDRG6tfnTf9TMFVtfLYe1mh27mRXgBZU6TXA_nCJAi6WnLLq1p3d0yj3n62_4yJMu80o4O8kkw",
1772 "rawId": "z077A6SzdvA3rDRG6tfnTf9TMFVtfLYe1mh27mRXgBZU6TXA_nCJAi6WnLLq1p3d0yj3n62_4yJMu80o4O8kkw",
1773 "type": "public-key",
1774 "extensions": {
1775 "appid": false
1776 },
1777 "response": {
1778 "authenticatorData": "UN6WJxNDzSGdqrQoPbqYbsZdIxtC9vfU9iGe5i1pCTYBAAAAuQ",
1779 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJvSF9WcnBZazVMNHBQZGdPcTc5THkyTTdCUHd4V3VzazNLV2Z5VHJoLUk0IiwiY2xpZW50RXh0ZW5zaW9ucyI6eyJhcHBpZCI6Imh0dHBzOi8vdGVzdGluZy5sb2NhbC9hcHAtaWQuanNvbiJ9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vdGVzdGluZy5sb2NhbCIsInR5cGUiOiJ3ZWJhdXRobi5nZXQifQ",
1780 "signature": "MEUCIEw2O8LYZj6IKbjP6FuvdcL2MoDBY6LqJWuhteje3H7eAiEAvzRLSg70tkrGnZqpQIZyv-zaizNpCtyr4U3SZ-E2-f4"
1781 }
1782 }
1783 "#;
1784 let rsp_d: PublicKeyCredential = serde_json::from_str(rsp).unwrap();
1785
1786 let r = wan.verify_credential_internal(
1788 &rsp_d,
1789 UserVerificationPolicy::Discouraged_DO_NOT_USE,
1790 &zero_chal,
1791 &cred,
1792 &None,
1793 false,
1794 );
1795 trace!("RESULT: {:?}", r);
1796 assert!(r.is_err());
1797 }
1798
1799 #[test]
1800 fn test_registration_ipados_5ci() {
1801 let _ = tracing_subscriber::fmt()
1802 .with_max_level(tracing::Level::TRACE)
1803 .try_init();
1804 let wan = Webauthn::new_unsafe_experts_only(
1805 "https://172.20.0.141:8443/auth",
1806 "172.20.0.141",
1807 vec![Url::parse("https://172.20.0.141:8443").unwrap()],
1808 AUTHENTICATOR_TIMEOUT,
1809 None,
1810 None,
1811 );
1812
1813 let chal = Challenge::new(
1814 STANDARD
1815 .decode("tvR1m+d/ohXrwVxQjMgH8KnovHZ7BRWhZmDN4TVMpNU=")
1816 .unwrap(),
1817 );
1818
1819 let rsp_d = RegisterPublicKeyCredential {
1820 id: "uZcVDBVS68E_MtAgeQpElJxldF_6cY9sSvbWqx_qRh8wiu42lyRBRmh5yFeD_r9k130dMbFHBHI9RTFgdJQIzQ".to_string(),
1821 raw_id: Base64UrlSafeData::from(
1822 STANDARD.decode("uZcVDBVS68E/MtAgeQpElJxldF/6cY9sSvbWqx/qRh8wiu42lyRBRmh5yFeD/r9k130dMbFHBHI9RTFgdJQIzQ==").unwrap()
1823 ),
1824 response: AuthenticatorAttestationResponseRaw {
1825 attestation_object: Base64UrlSafeData::from(
1826 STANDARD.decode("o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIhAKAZODmj+uF5qXsDY2NFol3apRjld544KRUpHzwfk5cbAiBnp2gHmamr2xr46ilQuhzIR9BwMlwtxWd6IT2QEYeo7WN4NWOBWQLBMIICvTCCAaWgAwIBAgIEK/F8eDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNzM3MjQ2MzI4MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdMLHhCPIcS6bSPJZWGb8cECuTN8H13fVha8Ek5nt+pI8vrSflxb59Vp4bDQlH8jzXj3oW1ZwUDjHC6EnGWB5i6NsMGowIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCAiQwIQYLKwYBBAGC5RwBAQQEEgQQxe9V/62aS5+1gK3rr+Am0DAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCLbpN2nXhNbunZANJxAn/Cd+S4JuZsObnUiLnLLS0FPWa01TY8F7oJ8bE+aFa4kTe6NQQfi8+yiZrQ8N+JL4f7gNdQPSrH+r3iFd4SvroDe1jaJO4J9LeiFjmRdcVa+5cqNF4G1fPCofvw9W4lKnObuPakr0x/icdVq1MXhYdUtQk6Zr5mBnc4FhN9qi7DXqLHD5G7ZFUmGwfIcD2+0m1f1mwQS8yRD5+/aDCf3vutwddoi3crtivzyromwbKklR4qHunJ75LGZLZA8pJ/mXnUQ6TTsgRqPvPXgQPbSyGMf2z/DIPbQqCD/Bmc4dj9o6LozheBdDtcZCAjSPTAd/uiaGF1dGhEYXRhWMS3tF916xTswLEZrAO3fy8EzMmvvR8f5wWM7F5+4KJ0ikEAAAACxe9V/62aS5+1gK3rr+Am0ABAuZcVDBVS68E/MtAgeQpElJxldF/6cY9sSvbWqx/qRh8wiu42lyRBRmh5yFeD/r9k130dMbFHBHI9RTFgdJQIzaUBAgMmIAEhWCDCfn9t/BeDFfwG32Ms/owb5hFeBYUcaCmQRauVoRrI8yJYII97t5wYshX4dZ+iRas0vPwaOwYvZ1wTOnVn+QDbCF/E").unwrap()
1827 ),
1828 client_data_json: Base64UrlSafeData::from(
1829 STANDARD.decode("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwib3JpZ2luIjoiaHR0cHM6XC9cLzE3Mi4yMC4wLjE0MTo4NDQzIiwiY2hhbGxlbmdlIjoidHZSMW0tZF9vaFhyd1Z4UWpNZ0g4S25vdkhaN0JSV2habURONFRWTXBOVSJ9").unwrap()
1830 ),
1831 transports: None,
1832 },
1833 type_: "public-key".to_string(),
1834 extensions: RegistrationExtensionsClientOutputs::default(),
1835 };
1836
1837 let result = wan.register_credential_internal(
1839 &rsp_d,
1840 UserVerificationPolicy::Preferred,
1841 &chal,
1842 &[],
1843 &[COSEAlgorithm::ES256],
1844 Some(&AttestationCaList::default()),
1846 false,
1847 &RequestRegistrationExtensions::default(),
1848 false,
1849 );
1850 trace!("{:?}", result);
1851 assert!(matches!(
1852 result,
1853 Err(WebauthnError::MissingAttestationCaList)
1854 ));
1855
1856 let result = wan.register_credential_internal(
1857 &rsp_d,
1858 UserVerificationPolicy::Preferred,
1859 &chal,
1860 &[],
1861 &[COSEAlgorithm::ES256],
1862 Some(&(APPLE_WEBAUTHN_ROOT_CA_PEM.try_into().unwrap())),
1864 false,
1865 &RequestRegistrationExtensions::default(),
1866 false,
1867 );
1868 trace!("{:?}", result);
1869 assert!(matches!(
1870 result,
1871 Err(WebauthnError::AttestationChainNotTrusted(_))
1872 ));
1873
1874 let mut att_ca_builder = AttestationCaListBuilder::new();
1877 att_ca_builder
1878 .insert_device_pem(
1879 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
1880 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
1881 "yk 5 fips".to_string(),
1882 Default::default(),
1883 )
1884 .expect("Failed to build att ca list");
1885 let att_ca_list: AttestationCaList = att_ca_builder.build();
1886
1887 let result = wan.register_credential_internal(
1888 &rsp_d,
1889 UserVerificationPolicy::Preferred,
1890 &chal,
1891 &[],
1892 &[COSEAlgorithm::ES256],
1893 Some(&att_ca_list),
1894 false,
1895 &RequestRegistrationExtensions::default(),
1896 false,
1897 );
1898 trace!("{:?}", result);
1899 assert!(matches!(
1900 result,
1901 Err(WebauthnError::AttestationUntrustedAaguid)
1902 ));
1903
1904 let mut att_ca_builder = AttestationCaListBuilder::new();
1905 att_ca_builder
1906 .insert_device_pem(
1907 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
1908 uuid::uuid!("c5ef55ff-ad9a-4b9f-b580-adebafe026d0"),
1909 "yk 5 ci".to_string(),
1910 Default::default(),
1911 )
1912 .expect("Failed to build att ca list");
1913 let att_ca_list: AttestationCaList = att_ca_builder.build();
1914
1915 let result = wan.register_credential_internal(
1916 &rsp_d,
1917 UserVerificationPolicy::Preferred,
1918 &chal,
1919 &[],
1920 &[COSEAlgorithm::ES256],
1921 Some(&att_ca_list),
1922 false,
1923 &RequestRegistrationExtensions::default(),
1924 false,
1925 );
1926 trace!("{:?}", result);
1927 assert!(result.is_ok());
1928 }
1929
1930 #[test]
1931 fn test_deserialise_ipados_5ci() {
1932 let _ = tracing_subscriber::fmt()
1934 .with_max_level(tracing::Level::TRACE)
1935 .try_init();
1936 let ser_cred = r#"{"cred_id":"uZcVDBVS68E_MtAgeQpElJxldF_6cY9sSvbWqx_qRh8wiu42lyRBRmh5yFeD_r9k130dMbFHBHI9RTFgdJQIzQ","cred":{"type_":"ES256","key":{"EC_EC2":{"curve":"SECP256R1","x":[194,126,127,109,252,23,131,21,252,6,223,99,44,254,140,27,230,17,94,5,133,28,104,41,144,69,171,149,161,26,200,243],"y":[143,123,183,156,24,178,21,248,117,159,162,69,171,52,188,252,26,59,6,47,103,92,19,58,117,103,249,0,219,8,95,196]}}},"counter":2,"user_verified":false,"backup_eligible":false,"backup_state":false,"registration_policy":"preferred","extensions":{"cred_protect":"NotRequested","hmac_create_secret":"NotRequested"},"attestation":{"data":{"Basic":["MIICvTCCAaWgAwIBAgIEK_F8eDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbjELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEnMCUGA1UEAwweWXViaWNvIFUyRiBFRSBTZXJpYWwgNzM3MjQ2MzI4MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdMLHhCPIcS6bSPJZWGb8cECuTN8H13fVha8Ek5nt-pI8vrSflxb59Vp4bDQlH8jzXj3oW1ZwUDjHC6EnGWB5i6NsMGowIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCAiQwIQYLKwYBBAGC5RwBAQQEEgQQxe9V_62aS5-1gK3rr-Am0DAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCLbpN2nXhNbunZANJxAn_Cd-S4JuZsObnUiLnLLS0FPWa01TY8F7oJ8bE-aFa4kTe6NQQfi8-yiZrQ8N-JL4f7gNdQPSrH-r3iFd4SvroDe1jaJO4J9LeiFjmRdcVa-5cqNF4G1fPCofvw9W4lKnObuPakr0x_icdVq1MXhYdUtQk6Zr5mBnc4FhN9qi7DXqLHD5G7ZFUmGwfIcD2-0m1f1mwQS8yRD5-_aDCf3vutwddoi3crtivzyromwbKklR4qHunJ75LGZLZA8pJ_mXnUQ6TTsgRqPvPXgQPbSyGMf2z_DIPbQqCD_Bmc4dj9o6LozheBdDtcZCAjSPTAd_ui"]},"metadata":"None"},"attestation_format":"Packed"}"#;
1937 let cred: Credential = serde_json::from_str(ser_cred).unwrap();
1938 trace!("{:?}", cred);
1939 }
1940
1941 #[test]
1942 fn test_win_hello_attest_none() {
1943 let _ = tracing_subscriber::fmt::try_init();
1944 let wan = Webauthn::new_unsafe_experts_only(
1945 "https://etools-dev.example.com:8080/auth",
1946 "etools-dev.example.com",
1947 vec![Url::parse("https://etools-dev.example.com:8080").unwrap()],
1948 AUTHENTICATOR_TIMEOUT,
1949 None,
1950 None,
1951 );
1952 let chal = Challenge::new(vec![
1953 21, 9, 50, 208, 90, 167, 153, 94, 74, 98, 161, 84, 247, 161, 61, 104, 10, 82, 33, 27,
1954 99, 94, 34, 156, 84, 85, 31, 240, 9, 188, 136, 52,
1955 ]);
1956
1957 let rsp_d = RegisterPublicKeyCredential {
1958 id: "KwlEDOBCBc9P1YU3NWihYLCeY-I9KGMhPap9vwHbVoI".to_string(),
1959 raw_id: Base64UrlSafeData::from(vec![
1960 43, 9, 68, 12, 224, 66, 5, 207, 79, 213, 133, 55, 53, 104, 161, 96, 176, 158, 99,
1961 226, 61, 40, 99, 33, 61, 170, 125, 191, 1, 219, 86, 130,
1962 ]),
1963 response: AuthenticatorAttestationResponseRaw {
1964 attestation_object: Base64UrlSafeData::from(vec![
1965 163, 99, 102, 109, 116, 100, 110, 111, 110, 101, 103, 97, 116, 116, 83, 116,
1966 109, 116, 160, 104, 97, 117, 116, 104, 68, 97, 116, 97, 89, 1, 103, 108, 41,
1967 129, 232, 231, 178, 172, 146, 198, 102, 0, 255, 160, 250, 221, 227, 137, 40,
1968 196, 142, 208, 221, 115, 246, 47, 198, 69, 45, 165, 107, 42, 27, 69, 0, 0, 0,
1969 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 43, 9, 68, 12, 224,
1970 66, 5, 207, 79, 213, 133, 55, 53, 104, 161, 96, 176, 158, 99, 226, 61, 40, 99,
1971 33, 61, 170, 125, 191, 1, 219, 86, 130, 164, 1, 3, 3, 57, 1, 0, 32, 89, 1, 0,
1972 166, 163, 131, 233, 97, 64, 136, 207, 111, 39, 80, 80, 230, 19, 46, 59, 12,
1973 247, 151, 113, 167, 157, 140, 198, 227, 168, 159, 211, 232, 112, 116, 209, 54,
1974 148, 26, 156, 56, 88, 56, 27, 116, 102, 237, 88, 99, 81, 65, 79, 133, 242, 192,
1975 25, 28, 45, 116, 131, 129, 253, 185, 91, 35, 129, 35, 193, 44, 64, 86, 87, 137,
1976 44, 19, 74, 239, 72, 178, 243, 11, 195, 135, 194, 216, 109, 62, 84, 172, 16,
1977 182, 82, 140, 170, 1, 255, 91, 80, 73, 100, 1, 117, 61, 148, 179, 95, 199, 169,
1978 228, 244, 174, 69, 54, 185, 15, 107, 5, 0, 110, 155, 28, 243, 114, 32, 176,
1979 220, 93, 196, 172, 158, 22, 3, 154, 18, 148, 20, 132, 94, 166, 45, 24, 27, 8,
1980 255, 108, 31, 230, 196, 122, 125, 240, 215, 219, 118, 80, 224, 146, 92, 80,
1981 219, 91, 211, 88, 45, 28, 133, 135, 83, 244, 212, 29, 121, 132, 104, 189, 3,
1982 98, 42, 180, 10, 249, 232, 59, 172, 204, 109, 64, 206, 139, 76, 247, 230, 40,
1983 36, 71, 79, 11, 139, 84, 211, 153, 125, 108, 108, 55, 195, 205, 5, 90, 248, 72,
1984 42, 94, 40, 136, 193, 89, 3, 102, 109, 30, 65, 117, 76, 103, 150, 4, 44, 155,
1985 104, 207, 126, 92, 16, 161, 175, 223, 119, 246, 169, 127, 72, 13, 83, 129, 12,
1986 164, 102, 42, 141, 173, 102, 140, 52, 57, 43, 115, 12, 238, 89, 33, 67, 1, 0,
1987 1,
1988 ]),
1989 client_data_json: Base64UrlSafeData::from(vec![
1990 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
1991 46, 99, 114, 101, 97, 116, 101, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110,
1992 103, 101, 34, 58, 34, 70, 81, 107, 121, 48, 70, 113, 110, 109, 86, 53, 75, 89,
1993 113, 70, 85, 57, 54, 69, 57, 97, 65, 112, 83, 73, 82, 116, 106, 88, 105, 75,
1994 99, 86, 70, 85, 102, 56, 65, 109, 56, 105, 68, 81, 34, 44, 34, 111, 114, 105,
1995 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 101, 116, 111,
1996 111, 108, 115, 45, 100, 101, 118, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99,
1997 111, 109, 58, 56, 48, 56, 48, 34, 44, 34, 99, 114, 111, 115, 115, 79, 114, 105,
1998 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 125,
1999 ]),
2000 transports: None,
2001 },
2002 type_: "public-key".to_string(),
2003 extensions: RegistrationExtensionsClientOutputs::default(),
2004 };
2005
2006 let result = wan.register_credential_internal(
2007 &rsp_d,
2008 UserVerificationPolicy::Required,
2009 &chal,
2010 &[],
2011 &[COSEAlgorithm::RS256],
2012 None,
2013 false,
2014 &RequestRegistrationExtensions::default(),
2015 true,
2016 );
2017 trace!("{:?}", result);
2018 assert!(result.is_ok());
2019 let cred = result.unwrap();
2020
2021 let chal = Challenge::new(vec![
2022 189, 116, 126, 107, 74, 29, 210, 181, 99, 178, 173, 214, 166, 212, 124, 219, 29, 169,
2023 9, 58, 26, 27, 120, 246, 87, 173, 169, 210, 241, 153, 150, 189,
2024 ]);
2025
2026 let rsp_d = PublicKeyCredential {
2027 id: "KwlEDOBCBc9P1YU3NWihYLCeY-I9KGMhPap9vwHbVoI".to_string(),
2028 raw_id: Base64UrlSafeData::from(vec![
2029 43, 9, 68, 12, 224, 66, 5, 207, 79, 213, 133, 55, 53, 104, 161, 96, 176, 158, 99,
2030 226, 61, 40, 99, 33, 61, 170, 125, 191, 1, 219, 86, 130,
2031 ]),
2032 response: AuthenticatorAssertionResponseRaw {
2033 authenticator_data: Base64UrlSafeData::from(vec![
2034 108, 41, 129, 232, 231, 178, 172, 146, 198, 102, 0, 255, 160, 250, 221, 227,
2035 137, 40, 196, 142, 208, 221, 115, 246, 47, 198, 69, 45, 165, 107, 42, 27, 5, 0,
2036 0, 0, 1,
2037 ]),
2038 client_data_json: Base64UrlSafeData::from(vec![
2039 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
2040 46, 103, 101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110, 103, 101, 34,
2041 58, 34, 118, 88, 82, 45, 97, 48, 111, 100, 48, 114, 86, 106, 115, 113, 51, 87,
2042 112, 116, 82, 56, 50, 120, 50, 112, 67, 84, 111, 97, 71, 51, 106, 50, 86, 54,
2043 50, 112, 48, 118, 71, 90, 108, 114, 48, 34, 44, 34, 111, 114, 105, 103, 105,
2044 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 101, 116, 111, 111, 108,
2045 115, 45, 100, 101, 118, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109,
2046 58, 56, 48, 56, 48, 34, 44, 34, 99, 114, 111, 115, 115, 79, 114, 105, 103, 105,
2047 110, 34, 58, 102, 97, 108, 115, 101, 125,
2048 ]),
2049 signature: Base64UrlSafeData::from(vec![
2050 77, 253, 152, 83, 184, 198, 5, 16, 68, 51, 178, 5, 228, 20, 148, 168, 182, 3,
2051 201, 59, 162, 181, 96, 221, 67, 136, 230, 61, 252, 0, 38, 244, 143, 98, 100,
2052 14, 226, 223, 234, 58, 72, 9, 230, 190, 0, 189, 176, 101, 172, 176, 146, 25,
2053 221, 117, 79, 13, 176, 99, 208, 211, 135, 15, 60, 245, 106, 232, 195, 215, 37,
2054 70, 136, 198, 25, 186, 156, 226, 77, 216, 85, 100, 139, 73, 73, 173, 210, 244,
2055 116, 84, 108, 180, 138, 115, 15, 187, 140, 198, 110, 218, 78, 238, 99, 131,
2056 210, 229, 242, 184, 133, 219, 177, 235, 96, 187, 143, 82, 243, 88, 120, 214,
2057 182, 118, 88, 198, 157, 233, 83, 206, 165, 187, 111, 83, 211, 68, 147, 137,
2058 176, 28, 173, 36, 66, 87, 225, 252, 195, 101, 181, 44, 119, 198, 48, 210, 186,
2059 188, 190, 20, 78, 14, 49, 67, 144, 131, 76, 85, 70, 95, 130, 137, 132, 168, 33,
2060 196, 113, 83, 59, 38, 46, 1, 167, 107, 200, 168, 242, 6, 106, 141, 203, 123,
2061 203, 50, 69, 173, 6, 183, 117, 118, 229, 188, 39, 120, 188, 48, 54, 117, 223,
2062 15, 153, 122, 4, 24, 218, 56, 251, 173, 166, 113, 240, 231, 175, 21, 28, 228,
2063 248, 10, 1, 73, 222, 52, 57, 72, 51, 44, 131, 206, 4, 243, 66, 100, 61, 113,
2064 237, 221, 115, 182, 37, 187, 29, 250, 103, 178, 104, 69, 153, 47, 212, 76, 200,
2065 242,
2066 ]),
2067 user_handle: Some(Base64UrlSafeData::from(vec![109, 99, 104, 97, 110])),
2068 },
2069 extensions: AuthenticationExtensionsClientOutputs::default(),
2070 type_: "public-key".to_string(),
2071 };
2072
2073 let r = wan.verify_credential_internal(
2074 &rsp_d,
2075 UserVerificationPolicy::Required,
2076 &chal,
2077 &cred,
2078 &None,
2079 false,
2080 );
2081 trace!("RESULT: {:?}", r);
2082 assert!(r.is_ok());
2083 }
2084
2085 #[test]
2086 fn test_win_hello_attest_tpm() {
2087 let _ = tracing_subscriber::fmt::try_init();
2088 let wan = Webauthn::new_unsafe_experts_only(
2089 "https://etools-dev.example.com:8080/auth",
2090 "etools-dev.example.com",
2091 vec![Url::parse("https://etools-dev.example.com:8080").unwrap()],
2092 AUTHENTICATOR_TIMEOUT,
2093 None,
2094 None,
2095 );
2096
2097 let chal = Challenge::new(vec![
2098 34, 92, 189, 180, 54, 92, 96, 184, 1, 200, 155, 91, 42, 168, 156, 94, 254, 223, 49,
2099 169, 171, 179, 2, 71, 90, 123, 180, 244, 37, 182, 17, 52,
2100 ]);
2101
2102 let rsp_d = RegisterPublicKeyCredential {
2103 id: "0_n4aTCbomLUQXr07c7Ea-J0iNvdYmW0bUGuN6-ceGA".to_string(),
2104 raw_id: Base64UrlSafeData::from(vec![
2105 211, 249, 248, 105, 48, 155, 162, 98, 212, 65, 122, 244, 237, 206, 196, 107, 226,
2106 116, 136, 219, 221, 98, 101, 180, 109, 65, 174, 55, 175, 156, 120, 96,
2107 ]),
2108 response: AuthenticatorAttestationResponseRaw {
2109 attestation_object: Base64UrlSafeData::from(vec![
2110 163, 99, 102, 109, 116, 99, 116, 112, 109, 103, 97, 116, 116, 83, 116, 109,
2111 116, 166, 99, 97, 108, 103, 57, 255, 254, 99, 115, 105, 103, 89, 1, 0, 5, 3,
2112 162, 216, 151, 57, 210, 103, 145, 121, 161, 186, 63, 232, 221, 255, 89, 37, 17,
2113 59, 155, 241, 77, 30, 35, 201, 30, 140, 84, 214, 250, 185, 47, 248, 58, 89,
2114 177, 187, 231, 202, 220, 45, 167, 126, 243, 194, 94, 33, 39, 205, 163, 51, 40,
2115 171, 35, 118, 196, 244, 247, 143, 166, 193, 223, 94, 244, 157, 121, 220, 22,
2116 94, 163, 15, 151, 223, 214, 131, 105, 202, 40, 16, 176, 11, 154, 102, 100, 212,
2117 174, 103, 166, 92, 90, 154, 224, 20, 165, 106, 127, 53, 91, 230, 217, 199, 172,
2118 195, 203, 242, 41, 158, 64, 252, 65, 9, 155, 160, 63, 40, 94, 94, 64, 145, 173,
2119 71, 85, 173, 2, 199, 18, 148, 88, 223, 93, 154, 203, 197, 170, 142, 35, 249,
2120 146, 107, 146, 2, 14, 54, 39, 151, 181, 10, 176, 216, 117, 25, 196, 2, 205,
2121 159, 140, 155, 56, 89, 87, 31, 135, 93, 97, 78, 95, 176, 228, 72, 237, 130,
2122 171, 23, 66, 232, 35, 115, 218, 105, 168, 6, 253, 121, 161, 129, 44, 78, 252,
2123 44, 11, 23, 172, 66, 37, 214, 113, 128, 28, 33, 209, 66, 34, 32, 196, 153, 80,
2124 87, 243, 162, 7, 25, 62, 252, 243, 174, 31, 168, 98, 123, 100, 2, 143, 134, 36,
2125 154, 236, 18, 128, 175, 185, 189, 177, 51, 53, 216, 190, 43, 63, 35, 84, 14,
2126 64, 249, 23, 9, 125, 147, 160, 176, 137, 30, 174, 245, 148, 189, 99, 118, 101,
2127 114, 99, 50, 46, 48, 99, 120, 53, 99, 130, 89, 5, 189, 48, 130, 5, 185, 48,
2128 130, 3, 161, 160, 3, 2, 1, 2, 2, 16, 88, 191, 48, 69, 71, 45, 69, 233, 150,
2129 144, 71, 177, 166, 190, 225, 202, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1,
2130 1, 11, 5, 0, 48, 66, 49, 64, 48, 62, 6, 3, 85, 4, 3, 19, 55, 78, 67, 85, 45,
2131 73, 78, 84, 67, 45, 75, 69, 89, 73, 68, 45, 54, 67, 65, 57, 68, 70, 54, 50, 65,
2132 49, 65, 65, 69, 50, 51, 69, 48, 70, 69, 66, 55, 67, 51, 70, 53, 69, 66, 56, 69,
2133 54, 49, 69, 67, 65, 67, 49, 55, 67, 66, 55, 48, 30, 23, 13, 50, 48, 48, 56, 49,
2134 49, 49, 54, 50, 50, 49, 54, 90, 23, 13, 50, 53, 48, 51, 50, 49, 50, 48, 51, 48,
2135 48, 50, 90, 48, 0, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1,
2136 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 197, 166, 58,
2137 190, 204, 104, 240, 65, 135, 183, 96, 7, 143, 26, 55, 77, 107, 12, 171, 56, 2,
2138 145, 240, 201, 220, 75, 161, 201, 223, 24, 207, 126, 10, 118, 48, 201, 191, 6,
2139 187, 227, 178, 255, 229, 252, 127, 199, 215, 76, 221, 180, 123, 111, 178, 141,
2140 58, 235, 87, 27, 29, 24, 52, 235, 235, 181, 241, 28, 109, 223, 48, 137, 54, 21,
2141 113, 155, 105, 39, 210, 237, 238, 172, 146, 195, 173, 170, 137, 201, 36, 212,
2142 77, 179, 246, 142, 19, 198, 242, 48, 161, 199, 209, 113, 228, 182, 205, 115, 8,
2143 29, 255, 6, 29, 87, 118, 157, 115, 116, 171, 64, 105, 248, 91, 128, 220, 98,
2144 209, 126, 157, 177, 227, 101, 26, 26, 239, 72, 162, 135, 177, 177, 130, 16,
2145 239, 79, 140, 1, 29, 26, 38, 57, 7, 96, 218, 94, 110, 49, 251, 102, 130, 28,
2146 128, 227, 105, 117, 184, 13, 29, 229, 137, 151, 164, 116, 179, 101, 134, 253,
2147 159, 165, 90, 245, 195, 156, 105, 87, 147, 61, 219, 46, 29, 191, 252, 201, 117,
2148 54, 207, 6, 157, 96, 161, 26, 39, 172, 229, 85, 225, 172, 220, 252, 242, 129,
2149 34, 7, 227, 8, 7, 112, 42, 34, 73, 125, 6, 241, 100, 14, 214, 125, 179, 63,
2150 106, 150, 111, 19, 235, 59, 24, 141, 217, 140, 125, 91, 73, 152, 206, 174, 0,
2151 237, 72, 250, 207, 138, 119, 143, 203, 206, 115, 97, 89, 211, 219, 245, 2, 3,
2152 1, 0, 1, 163, 130, 1, 235, 48, 130, 1, 231, 48, 14, 6, 3, 85, 29, 15, 1, 1,
2153 255, 4, 4, 3, 2, 7, 128, 48, 12, 6, 3, 85, 29, 19, 1, 1, 255, 4, 2, 48, 0, 48,
2154 109, 6, 3, 85, 29, 32, 1, 1, 255, 4, 99, 48, 97, 48, 95, 6, 9, 43, 6, 1, 4, 1,
2155 130, 55, 21, 31, 48, 82, 48, 80, 6, 8, 43, 6, 1, 5, 5, 7, 2, 2, 48, 68, 30, 66,
2156 0, 84, 0, 67, 0, 80, 0, 65, 0, 32, 0, 32, 0, 84, 0, 114, 0, 117, 0, 115, 0,
2157 116, 0, 101, 0, 100, 0, 32, 0, 32, 0, 80, 0, 108, 0, 97, 0, 116, 0, 102, 0,
2158 111, 0, 114, 0, 109, 0, 32, 0, 32, 0, 73, 0, 100, 0, 101, 0, 110, 0, 116, 0,
2159 105, 0, 116, 0, 121, 48, 16, 6, 3, 85, 29, 37, 4, 9, 48, 7, 6, 5, 103, 129, 5,
2160 8, 3, 48, 80, 6, 3, 85, 29, 17, 1, 1, 255, 4, 70, 48, 68, 164, 66, 48, 64, 49,
2161 22, 48, 20, 6, 5, 103, 129, 5, 2, 1, 12, 11, 105, 100, 58, 52, 57, 52, 69, 53,
2162 52, 52, 51, 49, 14, 48, 12, 6, 5, 103, 129, 5, 2, 2, 12, 3, 83, 80, 84, 49, 22,
2163 48, 20, 6, 5, 103, 129, 5, 2, 3, 12, 11, 105, 100, 58, 48, 48, 48, 50, 48, 48,
2164 48, 48, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, 22, 128, 20, 147, 147, 77, 66, 14,
2165 183, 179, 161, 2, 110, 122, 113, 35, 6, 16, 82, 232, 88, 88, 179, 48, 29, 6, 3,
2166 85, 29, 14, 4, 22, 4, 20, 168, 251, 63, 173, 250, 64, 138, 217, 186, 126, 231,
2167 77, 242, 159, 198, 195, 60, 109, 251, 231, 48, 129, 179, 6, 8, 43, 6, 1, 5, 5,
2168 7, 1, 1, 4, 129, 166, 48, 129, 163, 48, 129, 160, 6, 8, 43, 6, 1, 5, 5, 7, 48,
2169 2, 134, 129, 147, 104, 116, 116, 112, 58, 47, 47, 97, 122, 99, 115, 112, 114,
2170 111, 100, 110, 99, 117, 97, 105, 107, 112, 117, 98, 108, 105, 115, 104, 46, 98,
2171 108, 111, 98, 46, 99, 111, 114, 101, 46, 119, 105, 110, 100, 111, 119, 115, 46,
2172 110, 101, 116, 47, 110, 99, 117, 45, 105, 110, 116, 99, 45, 107, 101, 121, 105,
2173 100, 45, 54, 99, 97, 57, 100, 102, 54, 50, 97, 49, 97, 97, 101, 50, 51, 101,
2174 48, 102, 101, 98, 55, 99, 51, 102, 53, 101, 98, 56, 101, 54, 49, 101, 99, 97,
2175 99, 49, 55, 99, 98, 55, 47, 100, 56, 101, 48, 50, 49, 56, 101, 45, 55, 55, 101,
2176 98, 45, 52, 51, 98, 56, 45, 97, 57, 56, 49, 45, 51, 48, 53, 99, 101, 99, 99,
2177 53, 99, 98, 97, 54, 46, 99, 101, 114, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13,
2178 1, 1, 11, 5, 0, 3, 130, 2, 1, 0, 4, 128, 111, 190, 0, 94, 133, 167, 0, 61, 237,
2179 232, 184, 182, 255, 238, 77, 189, 198, 248, 63, 5, 5, 202, 60, 98, 125, 121,
2180 175, 177, 82, 252, 85, 154, 80, 32, 167, 198, 224, 128, 251, 145, 5, 32, 101,
2181 218, 186, 38, 255, 178, 63, 167, 51, 205, 62, 195, 167, 219, 144, 6, 11, 70,
2182 14, 59, 177, 178, 116, 254, 131, 199, 231, 75, 204, 62, 116, 231, 40, 47, 112,
2183 138, 24, 194, 154, 46, 30, 25, 149, 75, 139, 119, 164, 65, 187, 215, 24, 139,
2184 160, 76, 210, 124, 16, 77, 27, 225, 70, 251, 137, 3, 176, 229, 248, 51, 108,
2185 163, 125, 36, 240, 181, 104, 49, 102, 42, 44, 172, 14, 255, 46, 131, 47, 7,
2186 180, 126, 84, 104, 151, 134, 42, 81, 159, 58, 126, 37, 224, 145, 122, 27, 111,
2187 213, 236, 124, 97, 181, 112, 75, 29, 33, 34, 7, 210, 170, 139, 63, 18, 193, 98,
2188 94, 186, 138, 225, 215, 44, 242, 91, 77, 201, 60, 66, 4, 27, 22, 85, 228, 223,
2189 59, 42, 242, 163, 164, 219, 75, 174, 91, 118, 115, 29, 216, 53, 37, 124, 161,
2190 194, 15, 117, 147, 50, 98, 205, 196, 137, 1, 244, 26, 124, 236, 181, 184, 5,
2191 98, 64, 191, 209, 189, 64, 0, 11, 214, 153, 64, 2, 36, 116, 237, 238, 124, 47,
2192 47, 182, 246, 20, 105, 12, 168, 188, 192, 215, 26, 228, 86, 69, 212, 42, 69,
2193 121, 238, 73, 155, 154, 133, 203, 30, 108, 94, 184, 214, 91, 67, 79, 22, 118,
2194 63, 100, 249, 23, 90, 142, 72, 94, 238, 91, 154, 32, 191, 51, 192, 44, 197,
2195 212, 173, 119, 159, 156, 71, 96, 239, 37, 68, 73, 247, 102, 88, 203, 172, 113,
2196 250, 74, 247, 129, 79, 19, 235, 145, 95, 158, 214, 44, 38, 28, 244, 218, 86,
2197 202, 93, 73, 196, 209, 133, 138, 77, 42, 58, 221, 99, 112, 13, 73, 47, 22, 108,
2198 162, 144, 47, 36, 208, 114, 146, 87, 77, 24, 78, 66, 148, 86, 91, 169, 104,
2199 104, 106, 137, 126, 172, 10, 213, 37, 25, 179, 175, 253, 243, 212, 175, 240,
2200 103, 8, 180, 190, 108, 198, 199, 40, 171, 227, 161, 232, 53, 147, 109, 244, 93,
2201 113, 237, 64, 179, 160, 78, 35, 34, 8, 136, 179, 185, 176, 219, 4, 198, 38,
2202 175, 6, 12, 227, 55, 168, 192, 122, 115, 119, 95, 205, 244, 105, 116, 238, 137,
2203 228, 32, 4, 9, 219, 246, 49, 131, 190, 64, 37, 85, 108, 239, 164, 173, 90, 254,
2204 146, 255, 252, 188, 232, 40, 184, 108, 69, 153, 81, 182, 17, 174, 194, 52, 246,
2205 178, 77, 47, 50, 167, 56, 17, 83, 31, 65, 119, 143, 160, 113, 254, 71, 33, 166,
2206 88, 53, 128, 195, 6, 193, 50, 144, 78, 242, 155, 234, 231, 20, 144, 132, 177,
2207 159, 161, 94, 154, 205, 133, 78, 20, 214, 141, 230, 33, 115, 192, 148, 87, 151,
2208 95, 71, 175, 89, 6, 240, 48, 130, 6, 236, 48, 130, 4, 212, 160, 3, 2, 1, 2, 2,
2209 19, 51, 0, 0, 2, 113, 82, 34, 55, 131, 10, 123, 56, 174, 0, 0, 0, 0, 2, 113,
2210 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 129, 140, 49, 11,
2211 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 83, 49, 19, 48, 17, 6, 3, 85, 4, 8, 19, 10,
2212 87, 97, 115, 104, 105, 110, 103, 116, 111, 110, 49, 16, 48, 14, 6, 3, 85, 4, 7,
2213 19, 7, 82, 101, 100, 109, 111, 110, 100, 49, 30, 48, 28, 6, 3, 85, 4, 10, 19,
2214 21, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 67, 111, 114, 112, 111, 114,
2215 97, 116, 105, 111, 110, 49, 54, 48, 52, 6, 3, 85, 4, 3, 19, 45, 77, 105, 99,
2216 114, 111, 115, 111, 102, 116, 32, 84, 80, 77, 32, 82, 111, 111, 116, 32, 67,
2217 101, 114, 116, 105, 102, 105, 99, 97, 116, 101, 32, 65, 117, 116, 104, 111,
2218 114, 105, 116, 121, 32, 50, 48, 49, 52, 48, 30, 23, 13, 49, 57, 48, 51, 50, 49,
2219 50, 48, 51, 48, 48, 50, 90, 23, 13, 50, 53, 48, 51, 50, 49, 50, 48, 51, 48, 48,
2220 50, 90, 48, 66, 49, 64, 48, 62, 6, 3, 85, 4, 3, 19, 55, 78, 67, 85, 45, 73, 78,
2221 84, 67, 45, 75, 69, 89, 73, 68, 45, 54, 67, 65, 57, 68, 70, 54, 50, 65, 49, 65,
2222 65, 69, 50, 51, 69, 48, 70, 69, 66, 55, 67, 51, 70, 53, 69, 66, 56, 69, 54, 49,
2223 69, 67, 65, 67, 49, 55, 67, 66, 55, 48, 130, 2, 34, 48, 13, 6, 9, 42, 134, 72,
2224 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 2, 15, 0, 48, 130, 2, 10, 2, 130, 2, 1, 0,
2225 152, 43, 107, 173, 177, 53, 163, 163, 93, 154, 248, 108, 222, 80, 5, 122, 87,
2226 236, 252, 225, 50, 52, 121, 17, 29, 232, 18, 63, 7, 156, 177, 34, 151, 214, 92,
2227 55, 149, 204, 232, 129, 50, 154, 105, 128, 221, 190, 157, 193, 52, 48, 65, 151,
2228 90, 250, 48, 160, 25, 134, 46, 36, 77, 126, 48, 129, 230, 125, 172, 189, 156,
2229 247, 147, 31, 239, 20, 230, 78, 4, 146, 123, 54, 173, 175, 211, 248, 18, 125,
2230 83, 110, 37, 67, 147, 152, 0, 121, 176, 166, 87, 248, 31, 3, 155, 235, 53, 134,
2231 8, 105, 212, 244, 239, 170, 41, 94, 183, 81, 143, 34, 193, 123, 125, 187, 48,
2232 149, 59, 99, 240, 15, 38, 108, 172, 200, 222, 70, 62, 98, 80, 163, 32, 19, 26,
2233 181, 191, 156, 139, 248, 190, 110, 129, 56, 196, 50, 16, 89, 143, 150, 41, 172,
2234 239, 136, 65, 145, 0, 93, 222, 226, 117, 208, 183, 116, 85, 166, 93, 247, 23,
2235 39, 167, 130, 47, 73, 113, 26, 102, 197, 100, 212, 176, 34, 143, 98, 105, 5,
2236 206, 194, 120, 190, 201, 49, 102, 199, 25, 161, 230, 11, 189, 87, 188, 102,
2237 171, 44, 55, 193, 180, 208, 172, 250, 214, 194, 36, 148, 113, 206, 80, 159,
2238 124, 135, 247, 246, 51, 10, 194, 204, 232, 44, 33, 64, 183, 63, 209, 225, 72,
2239 195, 193, 71, 101, 174, 241, 42, 217, 92, 214, 117, 199, 101, 75, 42, 145, 145,
2240 187, 113, 150, 138, 28, 61, 122, 159, 86, 152, 41, 83, 65, 80, 158, 165, 195,
2241 96, 255, 135, 34, 90, 161, 69, 173, 74, 198, 147, 96, 85, 40, 100, 128, 191,
2242 135, 11, 27, 86, 149, 149, 18, 103, 182, 110, 255, 71, 47, 227, 240, 14, 66,
2243 137, 251, 211, 221, 191, 34, 157, 152, 230, 121, 195, 41, 148, 176, 219, 134,
2244 62, 178, 181, 89, 7, 166, 111, 81, 85, 222, 85, 218, 96, 48, 120, 135, 99, 119,
2245 60, 170, 236, 34, 41, 173, 19, 91, 140, 28, 220, 20, 140, 71, 236, 117, 13,
2246 209, 248, 147, 130, 77, 125, 11, 109, 142, 43, 95, 221, 245, 154, 72, 250, 152,
2247 36, 107, 77, 175, 133, 247, 233, 77, 225, 123, 53, 217, 16, 39, 218, 44, 7, 97,
2248 89, 15, 241, 7, 15, 186, 204, 227, 132, 181, 120, 62, 216, 232, 84, 45, 142,
2249 241, 86, 209, 254, 255, 208, 45, 88, 242, 239, 198, 31, 54, 159, 135, 142, 17,
2250 52, 142, 58, 126, 81, 118, 231, 23, 209, 48, 11, 80, 194, 124, 248, 205, 80,
2251 187, 12, 166, 123, 89, 175, 201, 212, 239, 172, 77, 151, 107, 127, 92, 161, 37,
2252 246, 209, 253, 166, 8, 230, 153, 14, 54, 111, 173, 212, 8, 42, 60, 177, 191,
2253 97, 130, 28, 51, 178, 40, 129, 46, 179, 24, 45, 26, 25, 59, 61, 94, 4, 145,
2254 149, 42, 63, 49, 247, 136, 126, 5, 206, 102, 177, 28, 26, 86, 148, 35, 2, 3, 1,
2255 0, 1, 163, 130, 1, 142, 48, 130, 1, 138, 48, 14, 6, 3, 85, 29, 15, 1, 1, 255,
2256 4, 4, 3, 2, 2, 132, 48, 27, 6, 3, 85, 29, 37, 4, 20, 48, 18, 6, 9, 43, 6, 1, 4,
2257 1, 130, 55, 21, 36, 6, 5, 103, 129, 5, 8, 3, 48, 22, 6, 3, 85, 29, 32, 4, 15,
2258 48, 13, 48, 11, 6, 9, 43, 6, 1, 4, 1, 130, 55, 21, 31, 48, 18, 6, 3, 85, 29,
2259 19, 1, 1, 255, 4, 8, 48, 6, 1, 1, 255, 2, 1, 0, 48, 29, 6, 3, 85, 29, 14, 4,
2260 22, 4, 20, 147, 147, 77, 66, 14, 183, 179, 161, 2, 110, 122, 113, 35, 6, 16,
2261 82, 232, 88, 88, 179, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, 22, 128, 20, 122,
2262 140, 10, 206, 47, 72, 98, 23, 226, 148, 209, 174, 85, 193, 82, 236, 113, 116,
2263 164, 86, 48, 112, 6, 3, 85, 29, 31, 4, 105, 48, 103, 48, 101, 160, 99, 160, 97,
2264 134, 95, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 109, 105, 99, 114,
2265 111, 115, 111, 102, 116, 46, 99, 111, 109, 47, 112, 107, 105, 111, 112, 115,
2266 47, 99, 114, 108, 47, 77, 105, 99, 114, 111, 115, 111, 102, 116, 37, 50, 48,
2267 84, 80, 77, 37, 50, 48, 82, 111, 111, 116, 37, 50, 48, 67, 101, 114, 116, 105,
2268 102, 105, 99, 97, 116, 101, 37, 50, 48, 65, 117, 116, 104, 111, 114, 105, 116,
2269 121, 37, 50, 48, 50, 48, 49, 52, 46, 99, 114, 108, 48, 125, 6, 8, 43, 6, 1, 5,
2270 5, 7, 1, 1, 4, 113, 48, 111, 48, 109, 6, 8, 43, 6, 1, 5, 5, 7, 48, 2, 134, 97,
2271 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 109, 105, 99, 114, 111, 115,
2272 111, 102, 116, 46, 99, 111, 109, 47, 112, 107, 105, 111, 112, 115, 47, 99, 101,
2273 114, 116, 115, 47, 77, 105, 99, 114, 111, 115, 111, 102, 116, 37, 50, 48, 84,
2274 80, 77, 37, 50, 48, 82, 111, 111, 116, 37, 50, 48, 67, 101, 114, 116, 105, 102,
2275 105, 99, 97, 116, 101, 37, 50, 48, 65, 117, 116, 104, 111, 114, 105, 116, 121,
2276 37, 50, 48, 50, 48, 49, 52, 46, 99, 114, 116, 48, 13, 6, 9, 42, 134, 72, 134,
2277 247, 13, 1, 1, 11, 5, 0, 3, 130, 2, 1, 0, 73, 235, 166, 7, 16, 89, 131, 50, 67,
2278 31, 113, 176, 9, 16, 209, 146, 232, 124, 220, 236, 23, 249, 16, 213, 246, 244,
2279 231, 147, 248, 141, 93, 158, 160, 222, 177, 160, 115, 201, 16, 11, 228, 151,
2280 21, 209, 62, 191, 38, 153, 95, 178, 20, 202, 150, 24, 170, 85, 100, 155, 108,
2281 120, 203, 242, 149, 237, 71, 252, 71, 149, 245, 18, 222, 155, 246, 56, 226,
2282 116, 245, 175, 196, 187, 121, 2, 212, 117, 193, 222, 154, 201, 133, 16, 232,
2283 171, 149, 255, 214, 198, 212, 197, 65, 34, 27, 55, 16, 54, 91, 251, 95, 52,
2284 141, 113, 235, 119, 147, 78, 1, 254, 195, 123, 240, 11, 79, 183, 139, 167, 223,
2285 99, 172, 242, 229, 252, 48, 126, 146, 1, 170, 111, 216, 195, 26, 9, 183, 178,
2286 32, 197, 94, 57, 33, 1, 165, 51, 121, 63, 4, 53, 36, 195, 106, 69, 23, 244, 74,
2287 0, 52, 93, 45, 232, 15, 144, 228, 162, 61, 32, 73, 156, 147, 11, 69, 235, 123,
2288 172, 207, 162, 228, 234, 160, 234, 193, 35, 189, 70, 229, 126, 3, 63, 178, 15,
2289 224, 235, 103, 203, 74, 37, 37, 146, 94, 43, 123, 179, 63, 216, 150, 144, 199,
2290 224, 255, 121, 132, 38, 60, 0, 171, 31, 236, 168, 254, 171, 146, 116, 99, 43,
2291 235, 186, 249, 176, 135, 195, 160, 51, 39, 252, 205, 76, 22, 189, 141, 240,
2292 196, 2, 116, 193, 211, 79, 70, 63, 14, 37, 53, 170, 224, 243, 135, 251, 85,
2293 142, 154, 99, 122, 59, 0, 96, 215, 6, 202, 198, 137, 50, 122, 35, 194, 17, 128,
2294 215, 129, 249, 220, 85, 224, 26, 24, 8, 200, 198, 13, 105, 32, 81, 8, 34, 198,
2295 33, 222, 79, 161, 60, 167, 105, 246, 195, 242, 5, 126, 69, 23, 54, 78, 166,
2296 185, 253, 107, 152, 165, 14, 8, 158, 205, 81, 113, 18, 61, 101, 94, 9, 36, 203,
2297 232, 130, 211, 230, 45, 209, 3, 100, 5, 159, 67, 152, 26, 95, 188, 125, 92,
2298 141, 251, 62, 72, 40, 203, 116, 89, 14, 141, 8, 120, 232, 19, 235, 85, 35, 101,
2299 24, 247, 149, 197, 215, 100, 22, 37, 144, 62, 173, 79, 123, 198, 63, 136, 236,
2300 81, 242, 90, 231, 189, 41, 204, 131, 14, 150, 67, 108, 88, 123, 210, 157, 216,
2301 251, 32, 193, 91, 82, 3, 107, 199, 180, 155, 243, 12, 23, 77, 162, 231, 227,
2302 120, 72, 35, 94, 105, 168, 102, 35, 27, 0, 203, 104, 19, 212, 75, 177, 173, 38,
2303 68, 156, 147, 228, 80, 215, 121, 250, 163, 49, 245, 155, 2, 15, 160, 49, 117,
2304 74, 100, 43, 119, 37, 26, 23, 96, 188, 144, 155, 211, 185, 166, 123, 250, 211,
2305 242, 193, 122, 67, 159, 35, 66, 33, 153, 122, 233, 160, 181, 188, 114, 250, 70,
2306 165, 98, 31, 165, 84, 126, 45, 106, 164, 221, 57, 100, 151, 23, 81, 46, 118,
2307 251, 43, 100, 201, 204, 121, 103, 112, 117, 98, 65, 114, 101, 97, 89, 1, 54, 0,
2308 1, 0, 11, 0, 6, 4, 114, 0, 32, 157, 255, 203, 243, 108, 56, 58, 230, 153, 251,
2309 152, 104, 220, 109, 203, 137, 215, 21, 56, 132, 190, 40, 3, 146, 44, 18, 65,
2310 88, 191, 173, 34, 174, 0, 16, 0, 16, 8, 0, 0, 0, 0, 0, 1, 0, 220, 20, 243, 114,
2311 251, 142, 90, 236, 17, 204, 181, 223, 8, 72, 230, 209, 122, 44, 90, 55, 96,
2312 134, 69, 16, 125, 139, 112, 81, 154, 230, 133, 211, 129, 37, 75, 208, 222, 70,
2313 210, 239, 209, 188, 152, 93, 222, 222, 154, 169, 217, 160, 90, 243, 135, 151,
2314 25, 87, 240, 178, 106, 119, 150, 89, 23, 223, 158, 88, 107, 72, 101, 61, 184,
2315 132, 19, 110, 144, 107, 22, 178, 252, 206, 50, 207, 11, 177, 137, 35, 139, 68,
2316 212, 148, 121, 249, 50, 35, 89, 52, 47, 26, 23, 6, 15, 115, 155, 127, 59, 168,
2317 208, 196, 78, 125, 205, 0, 98, 43, 223, 233, 65, 137, 103, 2, 227, 35, 81, 107,
2318 247, 230, 186, 111, 27, 4, 57, 42, 220, 32, 29, 181, 159, 6, 176, 182, 94, 191,
2319 222, 212, 235, 60, 101, 83, 86, 217, 203, 151, 251, 254, 219, 204, 195, 10, 74,
2320 147, 5, 27, 167, 127, 117, 149, 245, 157, 92, 124, 2, 196, 214, 107, 246, 228,
2321 171, 229, 100, 212, 67, 88, 215, 75, 33, 183, 199, 51, 171, 210, 213, 65, 45,
2322 96, 96, 226, 29, 130, 254, 58, 92, 252, 133, 207, 105, 63, 156, 208, 149, 142,
2323 9, 83, 1, 193, 217, 244, 35, 137, 43, 138, 137, 140, 82, 231, 195, 145, 213,
2324 230, 185, 245, 104, 105, 62, 142, 124, 34, 9, 157, 167, 188, 243, 112, 104,
2325 248, 63, 50, 19, 53, 173, 69, 12, 39, 252, 9, 69, 223, 104, 99, 101, 114, 116,
2326 73, 110, 102, 111, 88, 161, 255, 84, 67, 71, 128, 23, 0, 34, 0, 11, 174, 74,
2327 152, 70, 1, 87, 191, 156, 96, 74, 177, 221, 37, 132, 6, 8, 101, 35, 124, 216,
2328 85, 173, 85, 195, 115, 137, 194, 247, 145, 61, 82, 40, 0, 20, 234, 98, 144, 49,
2329 146, 39, 99, 47, 44, 82, 115, 48, 64, 40, 152, 224, 227, 42, 63, 133, 0, 0, 0,
2330 2, 219, 215, 137, 38, 187, 106, 183, 8, 100, 145, 106, 200, 1, 86, 5, 220, 81,
2331 118, 234, 131, 141, 0, 34, 0, 11, 239, 53, 112, 255, 253, 12, 189, 168, 16,
2332 253, 10, 149, 108, 7, 31, 212, 143, 21, 153, 7, 7, 153, 99, 73, 205, 97, 90,
2333 110, 182, 120, 4, 250, 0, 34, 0, 11, 249, 72, 224, 84, 16, 96, 147, 197, 167,
2334 195, 110, 181, 77, 207, 147, 16, 34, 64, 139, 185, 120, 190, 196, 209, 213, 29,
2335 1, 136, 76, 235, 223, 247, 104, 97, 117, 116, 104, 68, 97, 116, 97, 89, 1, 103,
2336 108, 41, 129, 232, 231, 178, 172, 146, 198, 102, 0, 255, 160, 250, 221, 227,
2337 137, 40, 196, 142, 208, 221, 115, 246, 47, 198, 69, 45, 165, 107, 42, 27, 69,
2338 0, 0, 0, 0, 8, 152, 112, 88, 202, 220, 75, 129, 182, 225, 48, 222, 80, 220,
2339 190, 150, 0, 32, 211, 249, 248, 105, 48, 155, 162, 98, 212, 65, 122, 244, 237,
2340 206, 196, 107, 226, 116, 136, 219, 221, 98, 101, 180, 109, 65, 174, 55, 175,
2341 156, 120, 96, 164, 1, 3, 3, 57, 1, 0, 32, 89, 1, 0, 220, 20, 243, 114, 251,
2342 142, 90, 236, 17, 204, 181, 223, 8, 72, 230, 209, 122, 44, 90, 55, 96, 134, 69,
2343 16, 125, 139, 112, 81, 154, 230, 133, 211, 129, 37, 75, 208, 222, 70, 210, 239,
2344 209, 188, 152, 93, 222, 222, 154, 169, 217, 160, 90, 243, 135, 151, 25, 87,
2345 240, 178, 106, 119, 150, 89, 23, 223, 158, 88, 107, 72, 101, 61, 184, 132, 19,
2346 110, 144, 107, 22, 178, 252, 206, 50, 207, 11, 177, 137, 35, 139, 68, 212, 148,
2347 121, 249, 50, 35, 89, 52, 47, 26, 23, 6, 15, 115, 155, 127, 59, 168, 208, 196,
2348 78, 125, 205, 0, 98, 43, 223, 233, 65, 137, 103, 2, 227, 35, 81, 107, 247, 230,
2349 186, 111, 27, 4, 57, 42, 220, 32, 29, 181, 159, 6, 176, 182, 94, 191, 222, 212,
2350 235, 60, 101, 83, 86, 217, 203, 151, 251, 254, 219, 204, 195, 10, 74, 147, 5,
2351 27, 167, 127, 117, 149, 245, 157, 92, 124, 2, 196, 214, 107, 246, 228, 171,
2352 229, 100, 212, 67, 88, 215, 75, 33, 183, 199, 51, 171, 210, 213, 65, 45, 96,
2353 96, 226, 29, 130, 254, 58, 92, 252, 133, 207, 105, 63, 156, 208, 149, 142, 9,
2354 83, 1, 193, 217, 244, 35, 137, 43, 138, 137, 140, 82, 231, 195, 145, 213, 230,
2355 185, 245, 104, 105, 62, 142, 124, 34, 9, 157, 167, 188, 243, 112, 104, 248, 63,
2356 50, 19, 53, 173, 69, 12, 39, 252, 9, 69, 223, 33, 67, 1, 0, 1,
2357 ]),
2358 client_data_json: Base64UrlSafeData::from(vec![
2359 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
2360 46, 99, 114, 101, 97, 116, 101, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110,
2361 103, 101, 34, 58, 34, 73, 108, 121, 57, 116, 68, 90, 99, 89, 76, 103, 66, 121,
2362 74, 116, 98, 75, 113, 105, 99, 88, 118, 55, 102, 77, 97, 109, 114, 115, 119,
2363 74, 72, 87, 110, 117, 48, 57, 67, 87, 50, 69, 84, 81, 34, 44, 34, 111, 114,
2364 105, 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 101, 116,
2365 111, 111, 108, 115, 45, 100, 101, 118, 46, 101, 120, 97, 109, 112, 108, 101,
2366 46, 99, 111, 109, 58, 56, 48, 56, 48, 34, 44, 34, 99, 114, 111, 115, 115, 79,
2367 114, 105, 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 125,
2368 ]),
2369 transports: None,
2370 },
2371 type_: "public-key".to_string(),
2372 extensions: RegistrationExtensionsClientOutputs::default(),
2373 };
2374
2375 let result = wan.register_credential_internal(
2376 &rsp_d,
2377 UserVerificationPolicy::Required,
2378 &chal,
2379 &[],
2380 &[COSEAlgorithm::RS256],
2381 Some(
2382 &(MICROSOFT_TPM_ROOT_CERTIFICATE_AUTHORITY_2014_PEM
2383 .try_into()
2384 .unwrap()),
2385 ),
2386 false,
2387 &RequestRegistrationExtensions::default(),
2388 true,
2389 );
2390 trace!("{:?}", result);
2391 assert!(matches!(
2392 result,
2393 Err(WebauthnError::CredentialInsecureCryptography)
2394 ))
2395 }
2396
2397 fn register_userid(
2398 user_unique_id: &[u8],
2399 user_name: &str,
2400 user_display_name: &str,
2401 ) -> Result<(CreationChallengeResponse, RegistrationState), WebauthnError> {
2402 #![allow(clippy::unwrap_used)]
2403
2404 let wan = Webauthn::new_unsafe_experts_only(
2405 "https://etools-dev.example.com:8080/auth",
2406 "etools-dev.example.com",
2407 vec![Url::parse("https://etools-dev.example.com:8080").unwrap()],
2408 AUTHENTICATOR_TIMEOUT,
2409 None,
2410 None,
2411 );
2412
2413 let builder =
2414 wan.new_challenge_register_builder(user_unique_id, user_name, user_display_name)?;
2415
2416 let builder = builder
2417 .user_verification_policy(UserVerificationPolicy::Required)
2418 .attestation(AttestationConveyancePreference::None)
2419 .exclude_credentials(None)
2420 .extensions(None)
2421 .credential_algorithms(COSEAlgorithm::secure_algs())
2422 .require_resident_key(false)
2423 .authenticator_attachment(None);
2424
2425 wan.generate_challenge_register(builder)
2426 }
2427
2428 #[test]
2429 fn test_registration_userid_states() {
2430 assert!(matches!(
2431 register_userid(&[], "an name", "an name"),
2432 Err(WebauthnError::InvalidUsername)
2433 ));
2434 assert!(matches!(
2435 register_userid(&[0, 1, 2, 3], "an name", ""),
2436 Err(WebauthnError::InvalidUsername)
2437 ));
2438 assert!(matches!(
2439 register_userid(&[0, 1, 2, 3], "", "an_name"),
2440 Err(WebauthnError::InvalidUsername)
2441 ));
2442 assert!(register_userid(&[0, 1, 2, 3], "fizzbuzz", "an name").is_ok());
2443 }
2444
2445 #[test]
2446 fn test_touchid_attest_apple_anonymous() {
2447 let _ = tracing_subscriber::fmt::try_init();
2448 let wan = Webauthn::new_unsafe_experts_only(
2449 "https://spectral.local:8443/auth",
2450 "spectral.local",
2451 vec![Url::parse("https://spectral.local:8443").unwrap()],
2452 AUTHENTICATOR_TIMEOUT,
2453 None,
2454 None,
2455 );
2456
2457 let chal = Challenge::new(vec![
2458 37, 54, 228, 239, 39, 164, 32, 163, 153, 67, 12, 29, 25, 110, 205, 120, 50, 31, 198,
2459 182, 10, 208, 251, 238, 99, 27, 46, 123, 239, 134, 244, 210,
2460 ]);
2461
2462 let rsp_d = RegisterPublicKeyCredential {
2463 id: "u_tliFf-aXRLg9XIz-SuQ0XBlbE".to_string(),
2464 raw_id: Base64UrlSafeData::from(vec![
2465 187, 251, 101, 136, 87, 254, 105, 116, 75, 131, 213, 200, 207, 228, 174, 67, 69,
2466 193, 149, 177,
2467 ]),
2468 response: AuthenticatorAttestationResponseRaw {
2469 attestation_object: Base64UrlSafeData::from(vec![
2470 163, 99, 102, 109, 116, 101, 97, 112, 112, 108, 101, 103, 97, 116, 116, 83,
2471 116, 109, 116, 162, 99, 97, 108, 103, 38, 99, 120, 53, 99, 130, 89, 2, 71, 48,
2472 130, 2, 67, 48, 130, 1, 201, 160, 3, 2, 1, 2, 2, 6, 1, 118, 69, 82, 254, 167,
2473 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 48, 72, 49, 28, 48, 26, 6, 3, 85,
2474 4, 3, 12, 19, 65, 112, 112, 108, 101, 32, 87, 101, 98, 65, 117, 116, 104, 110,
2475 32, 67, 65, 32, 49, 49, 19, 48, 17, 6, 3, 85, 4, 10, 12, 10, 65, 112, 112, 108,
2476 101, 32, 73, 110, 99, 46, 49, 19, 48, 17, 6, 3, 85, 4, 8, 12, 10, 67, 97, 108,
2477 105, 102, 111, 114, 110, 105, 97, 48, 30, 23, 13, 50, 48, 49, 50, 48, 56, 48,
2478 50, 50, 55, 49, 53, 90, 23, 13, 50, 48, 49, 50, 49, 49, 48, 50, 50, 55, 49, 53,
2479 90, 48, 129, 145, 49, 73, 48, 71, 6, 3, 85, 4, 3, 12, 64, 57, 97, 97, 57, 48,
2480 99, 55, 99, 57, 51, 54, 97, 52, 101, 49, 98, 98, 56, 54, 56, 57, 54, 53, 102,
2481 49, 52, 55, 97, 52, 51, 57, 57, 102, 49, 52, 48, 99, 102, 52, 48, 57, 98, 52,
2482 51, 52, 102, 57, 48, 53, 57, 98, 50, 100, 52, 102, 53, 97, 51, 99, 102, 99, 48,
2483 57, 50, 49, 26, 48, 24, 6, 3, 85, 4, 11, 12, 17, 65, 65, 65, 32, 67, 101, 114,
2484 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 49, 19, 48, 17, 6, 3, 85, 4,
2485 10, 12, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 19, 48, 17, 6, 3,
2486 85, 4, 8, 12, 10, 67, 97, 108, 105, 102, 111, 114, 110, 105, 97, 48, 89, 48,
2487 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3,
2488 66, 0, 4, 212, 248, 99, 135, 245, 78, 94, 245, 231, 22, 62, 226, 45, 40, 215,
2489 4, 251, 188, 180, 125, 22, 236, 133, 161, 234, 78, 251, 105, 11, 119, 148, 144,
2490 105, 249, 199, 167, 152, 173, 94, 147, 57, 2, 250, 21, 5, 51, 116, 174, 217,
2491 39, 160, 35, 12, 249, 120, 237, 52, 148, 171, 134, 138, 205, 26, 173, 163, 85,
2492 48, 83, 48, 12, 6, 3, 85, 29, 19, 1, 1, 255, 4, 2, 48, 0, 48, 14, 6, 3, 85, 29,
2493 15, 1, 1, 255, 4, 4, 3, 2, 4, 240, 48, 51, 6, 9, 42, 134, 72, 134, 247, 99,
2494 100, 8, 2, 4, 38, 48, 36, 161, 34, 4, 32, 168, 226, 160, 197, 61, 146, 15, 234,
2495 100, 124, 22, 29, 34, 18, 171, 91, 253, 122, 81, 241, 182, 105, 240, 209, 130,
2496 176, 179, 61, 84, 183, 78, 190, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 3,
2497 104, 0, 48, 101, 2, 48, 14, 242, 134, 73, 12, 48, 2, 103, 184, 132, 187, 132,
2498 124, 204, 63, 148, 168, 78, 225, 227, 161, 240, 147, 187, 90, 216, 65, 159, 90,
2499 106, 102, 249, 56, 156, 201, 214, 182, 15, 173, 187, 167, 243, 127, 234, 138,
2500 41, 50, 62, 2, 49, 0, 198, 15, 10, 182, 142, 103, 84, 7, 18, 0, 231, 130, 214,
2501 26, 64, 58, 17, 118, 66, 14, 198, 244, 58, 211, 2, 97, 236, 163, 116, 124, 73,
2502 166, 69, 69, 112, 107, 228, 83, 104, 91, 205, 20, 203, 250, 126, 29, 190, 42,
2503 89, 2, 56, 48, 130, 2, 52, 48, 130, 1, 186, 160, 3, 2, 1, 2, 2, 16, 86, 37, 83,
2504 149, 199, 167, 251, 64, 235, 226, 40, 216, 38, 8, 83, 182, 48, 10, 6, 8, 42,
2505 134, 72, 206, 61, 4, 3, 3, 48, 75, 49, 31, 48, 29, 6, 3, 85, 4, 3, 12, 22, 65,
2506 112, 112, 108, 101, 32, 87, 101, 98, 65, 117, 116, 104, 110, 32, 82, 111, 111,
2507 116, 32, 67, 65, 49, 19, 48, 17, 6, 3, 85, 4, 10, 12, 10, 65, 112, 112, 108,
2508 101, 32, 73, 110, 99, 46, 49, 19, 48, 17, 6, 3, 85, 4, 8, 12, 10, 67, 97, 108,
2509 105, 102, 111, 114, 110, 105, 97, 48, 30, 23, 13, 50, 48, 48, 51, 49, 56, 49,
2510 56, 51, 56, 48, 49, 90, 23, 13, 51, 48, 48, 51, 49, 51, 48, 48, 48, 48, 48, 48,
2511 90, 48, 72, 49, 28, 48, 26, 6, 3, 85, 4, 3, 12, 19, 65, 112, 112, 108, 101, 32,
2512 87, 101, 98, 65, 117, 116, 104, 110, 32, 67, 65, 32, 49, 49, 19, 48, 17, 6, 3,
2513 85, 4, 10, 12, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 19, 48, 17,
2514 6, 3, 85, 4, 8, 12, 10, 67, 97, 108, 105, 102, 111, 114, 110, 105, 97, 48, 118,
2515 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0, 34, 3, 98, 0, 4,
2516 131, 46, 135, 47, 38, 20, 145, 129, 2, 37, 185, 245, 252, 214, 187, 99, 120,
2517 181, 245, 95, 63, 203, 4, 91, 199, 53, 153, 52, 117, 253, 84, 144, 68, 223,
2518 155, 254, 25, 33, 23, 101, 198, 154, 29, 218, 5, 11, 56, 212, 80, 131, 64, 26,
2519 67, 79, 178, 77, 17, 45, 86, 195, 225, 207, 191, 203, 152, 145, 254, 192, 105,
2520 96, 129, 190, 249, 108, 188, 119, 200, 141, 221, 175, 70, 165, 174, 225, 221,
2521 81, 91, 90, 250, 171, 147, 190, 156, 11, 38, 145, 163, 102, 48, 100, 48, 18, 6,
2522 3, 85, 29, 19, 1, 1, 255, 4, 8, 48, 6, 1, 1, 255, 2, 1, 0, 48, 31, 6, 3, 85,
2523 29, 35, 4, 24, 48, 22, 128, 20, 38, 215, 100, 217, 197, 120, 194, 90, 103, 209,
2524 167, 222, 107, 18, 208, 27, 99, 241, 198, 215, 48, 29, 6, 3, 85, 29, 14, 4, 22,
2525 4, 20, 235, 174, 130, 196, 255, 161, 172, 91, 81, 212, 207, 36, 97, 5, 0, 190,
2526 99, 189, 119, 136, 48, 14, 6, 3, 85, 29, 15, 1, 1, 255, 4, 4, 3, 2, 1, 6, 48,
2527 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 3, 3, 104, 0, 48, 101, 2, 49, 0, 221,
2528 139, 26, 52, 129, 165, 250, 217, 219, 180, 231, 101, 123, 132, 30, 20, 76, 39,
2529 183, 91, 135, 106, 65, 134, 194, 177, 71, 87, 80, 51, 114, 39, 239, 229, 84,
2530 69, 126, 246, 72, 149, 12, 99, 46, 92, 72, 62, 112, 193, 2, 48, 44, 138, 96,
2531 68, 220, 32, 31, 207, 229, 155, 195, 77, 41, 48, 193, 72, 120, 81, 217, 96,
2532 237, 106, 117, 241, 235, 74, 202, 190, 56, 205, 37, 184, 151, 208, 200, 5, 190,
2533 240, 199, 247, 139, 7, 165, 113, 198, 232, 14, 7, 104, 97, 117, 116, 104, 68,
2534 97, 116, 97, 88, 152, 218, 20, 177, 242, 169, 30, 45, 223, 21, 45, 254, 74, 34,
2535 125, 188, 96, 11, 1, 71, 41, 58, 94, 252, 180, 169, 243, 209, 21, 231, 138,
2536 182, 91, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20,
2537 187, 251, 101, 136, 87, 254, 105, 116, 75, 131, 213, 200, 207, 228, 174, 67,
2538 69, 193, 149, 177, 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 212, 248, 99, 135, 245,
2539 78, 94, 245, 231, 22, 62, 226, 45, 40, 215, 4, 251, 188, 180, 125, 22, 236,
2540 133, 161, 234, 78, 251, 105, 11, 119, 148, 144, 34, 88, 32, 105, 249, 199, 167,
2541 152, 173, 94, 147, 57, 2, 250, 21, 5, 51, 116, 174, 217, 39, 160, 35, 12, 249,
2542 120, 237, 52, 148, 171, 134, 138, 205, 26, 173,
2543 ]),
2544 client_data_json: Base64UrlSafeData::from(vec![
2545 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
2546 46, 99, 114, 101, 97, 116, 101, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110,
2547 103, 101, 34, 58, 34, 74, 84, 98, 107, 55, 121, 101, 107, 73, 75, 79, 90, 81,
2548 119, 119, 100, 71, 87, 55, 78, 101, 68, 73, 102, 120, 114, 89, 75, 48, 80, 118,
2549 117, 89, 120, 115, 117, 101, 45, 45, 71, 57, 78, 73, 34, 44, 34, 111, 114, 105,
2550 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 112, 101,
2551 99, 116, 114, 97, 108, 46, 108, 111, 99, 97, 108, 58, 56, 52, 52, 51, 34, 125,
2552 ]),
2553 transports: None,
2554 },
2555 type_: "public-key".to_string(),
2556 extensions: RegistrationExtensionsClientOutputs::default(),
2557 };
2558
2559 let mut att_ca_builder = AttestationCaListBuilder::new();
2561 att_ca_builder
2562 .insert_device_pem(
2563 APPLE_WEBAUTHN_ROOT_CA_PEM,
2564 uuid::uuid!("c5ef55ff-ad9a-4b9f-b580-adebafe026d0"),
2565 "yk 5 ci".to_string(),
2566 Default::default(),
2567 )
2568 .expect("Failed to build att ca list");
2569 let att_ca_list: AttestationCaList = att_ca_builder.build();
2570
2571 let result = wan.register_credential_internal(
2572 &rsp_d,
2573 UserVerificationPolicy::Required,
2574 &chal,
2575 &[],
2576 &[
2577 COSEAlgorithm::ES256,
2578 COSEAlgorithm::ES384,
2579 COSEAlgorithm::ES512,
2580 COSEAlgorithm::RS256,
2581 COSEAlgorithm::RS384,
2582 COSEAlgorithm::RS512,
2583 COSEAlgorithm::PS256,
2584 COSEAlgorithm::PS384,
2585 COSEAlgorithm::PS512,
2586 COSEAlgorithm::EDDSA,
2587 ],
2588 Some(&att_ca_list),
2589 true,
2591 &RequestRegistrationExtensions::default(),
2592 false,
2594 );
2595 debug!("{:?}", result);
2596 assert!(matches!(
2597 result,
2598 Err(WebauthnError::AttestationFormatMissingAaguid)
2599 ));
2600
2601 let result = wan.register_credential_internal(
2602 &rsp_d,
2603 UserVerificationPolicy::Required,
2604 &chal,
2605 &[],
2606 &[
2607 COSEAlgorithm::ES256,
2608 COSEAlgorithm::ES384,
2609 COSEAlgorithm::ES512,
2610 COSEAlgorithm::RS256,
2611 COSEAlgorithm::RS384,
2612 COSEAlgorithm::RS512,
2613 COSEAlgorithm::PS256,
2614 COSEAlgorithm::PS384,
2615 COSEAlgorithm::PS512,
2616 COSEAlgorithm::EDDSA,
2617 ],
2618 Some(&(APPLE_WEBAUTHN_ROOT_CA_PEM.try_into().unwrap())),
2619 true,
2621 &RequestRegistrationExtensions::default(),
2622 false,
2624 );
2625 debug!("{:?}", result);
2626 assert!(result.is_ok());
2627
2628 let result = wan.register_credential_internal(
2629 &rsp_d,
2630 UserVerificationPolicy::Required,
2631 &chal,
2632 &[],
2633 &[
2634 COSEAlgorithm::ES256,
2635 COSEAlgorithm::ES384,
2636 COSEAlgorithm::ES512,
2637 COSEAlgorithm::RS256,
2638 COSEAlgorithm::RS384,
2639 COSEAlgorithm::RS512,
2640 COSEAlgorithm::PS256,
2641 COSEAlgorithm::PS384,
2642 COSEAlgorithm::PS512,
2643 COSEAlgorithm::EDDSA,
2644 ],
2645 Some(&(APPLE_WEBAUTHN_ROOT_CA_PEM.try_into().unwrap())),
2646 true,
2648 &RequestRegistrationExtensions::default(),
2649 true,
2651 );
2652 debug!("{:?}", result);
2653 assert!(result.is_ok());
2654 }
2655
2656 #[test]
2657 fn test_touchid_attest_apple_anonymous_fails_with_invalid_nonce_extension() {
2658 let _ = tracing_subscriber::fmt::try_init();
2659 let wan = Webauthn::new_unsafe_experts_only(
2660 "https://spectral.local:8443/auth",
2661 "spectral.local",
2662 vec![Url::parse("https://spectral.local:8443").unwrap()],
2663 AUTHENTICATOR_TIMEOUT,
2664 None,
2665 None,
2666 );
2667
2668 let chal = Challenge::new(vec![
2669 37, 54, 228, 239, 39, 164, 32, 163, 153, 67, 12, 29, 25, 110, 205, 120, 50, 31, 198,
2670 182, 10, 208, 251, 238, 99, 27, 46, 123, 239, 134, 244, 210,
2671 ]);
2672
2673 let rsp_d = RegisterPublicKeyCredential {
2674 id: "u_tliFf-aXRLg9XIz-SuQ0XBlbE".to_string(),
2675 raw_id: Base64UrlSafeData::from(vec![
2676 187, 251, 101, 136, 87, 254, 105, 116, 75, 131, 213, 200, 207, 228, 174, 67, 69,
2677 193, 149, 177,
2678 ]),
2679 response: AuthenticatorAttestationResponseRaw {
2680 attestation_object: Base64UrlSafeData::from(vec![
2681 163, 99, 102, 109, 116, 101, 97, 112, 112, 108, 101, 103, 97, 116, 116, 83,
2682 116, 109, 116, 162, 99, 97, 108, 103, 38, 99, 120, 53, 99, 130, 89, 2, 71, 48,
2683 130, 2, 67, 48, 130, 1, 201, 160, 3, 2, 1, 2, 2, 6, 1, 118, 69, 82, 254, 167,
2684 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 2, 48, 72, 49, 28, 48, 26, 6, 3, 85,
2685 4, 3, 12, 19, 65, 112, 112, 108, 101, 32, 87, 101, 98, 65, 117, 116, 104, 110,
2686 32, 67, 65, 32, 49, 49, 19, 48, 17, 6, 3, 85, 4, 10, 12, 10, 65, 112, 112, 108,
2687 101, 32, 73, 110, 99, 46, 49, 19, 48, 17, 6, 3, 85, 4, 8, 12, 10, 67, 97, 108,
2688 105, 102, 111, 114, 110, 105, 97, 48, 30, 23, 13, 50, 48, 49, 50, 48, 56, 48,
2689 50, 50, 55, 49, 53, 90, 23, 13, 50, 48, 49, 50, 49, 49, 48, 50, 50, 55, 49, 53,
2690 90, 48, 129, 145, 49, 73, 48, 71, 6, 3, 85, 4, 3, 12, 64, 57, 97, 97, 57, 48,
2691 99, 55, 99, 57, 51, 54, 97, 52, 101, 49, 98, 98, 56, 54, 56, 57, 54, 53, 102,
2692 49, 52, 55, 97, 52, 51, 57, 57, 102, 49, 52, 48, 99, 102, 52, 48, 57, 98, 52,
2693 51, 52, 102, 57, 48, 53, 57, 98, 50, 100, 52, 102, 53, 97, 51, 99, 102, 99, 48,
2694 57, 50, 49, 26, 48, 24, 6, 3, 85, 4, 11, 12, 17, 65, 65, 65, 32, 67, 101, 114,
2695 116, 105, 102, 105, 99, 97, 116, 105, 111, 110, 49, 19, 48, 17, 6, 3, 85, 4,
2696 10, 12, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 19, 48, 17, 6, 3,
2697 85, 4, 8, 12, 10, 67, 97, 108, 105, 102, 111, 114, 110, 105, 97, 48, 89, 48,
2698 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3,
2699 66, 0, 4, 212, 248, 99, 135, 245, 78, 94, 245, 231, 22, 62, 226, 45, 40, 215,
2700 4, 251, 188, 180, 125, 22, 236, 133, 161, 234, 78, 251, 105, 11, 119, 148, 144,
2701 105, 249, 199, 167, 152, 173, 94, 147, 57, 2, 250, 21, 5, 51, 116, 174, 217,
2702 39, 160, 35, 12, 249, 120, 237, 52, 148, 171, 134, 138, 205, 26, 173, 163, 85,
2703 48, 83, 48, 12, 6, 3, 85, 29, 19, 1, 1, 255, 4, 2, 48, 0, 48, 14, 6, 3, 85, 29,
2704 15, 1, 1, 255, 4, 4, 3, 2, 4, 240, 48, 51, 6, 9, 42, 134, 72, 134, 247, 99,
2705 100, 8, 2, 4, 38, 48, 36, 161, 34, 4, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2706 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 10, 6, 8, 42,
2707 134, 72, 206, 61, 4, 3, 2, 3, 104, 0, 48, 101, 2, 48, 14, 242, 134, 73, 12, 48,
2708 2, 103, 184, 132, 187, 132, 124, 204, 63, 148, 168, 78, 225, 227, 161, 240,
2709 147, 187, 90, 216, 65, 159, 90, 106, 102, 249, 56, 156, 201, 214, 182, 15, 173,
2710 187, 167, 243, 127, 234, 138, 41, 50, 62, 2, 49, 0, 198, 15, 10, 182, 142, 103,
2711 84, 7, 18, 0, 231, 130, 214, 26, 64, 58, 17, 118, 66, 14, 198, 244, 58, 211, 2,
2712 97, 236, 163, 116, 124, 73, 166, 69, 69, 112, 107, 228, 83, 104, 91, 205, 20,
2713 203, 250, 126, 29, 190, 42, 89, 2, 56, 48, 130, 2, 52, 48, 130, 1, 186, 160, 3,
2714 2, 1, 2, 2, 16, 86, 37, 83, 149, 199, 167, 251, 64, 235, 226, 40, 216, 38, 8,
2715 83, 182, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 3, 48, 75, 49, 31, 48, 29,
2716 6, 3, 85, 4, 3, 12, 22, 65, 112, 112, 108, 101, 32, 87, 101, 98, 65, 117, 116,
2717 104, 110, 32, 82, 111, 111, 116, 32, 67, 65, 49, 19, 48, 17, 6, 3, 85, 4, 10,
2718 12, 10, 65, 112, 112, 108, 101, 32, 73, 110, 99, 46, 49, 19, 48, 17, 6, 3, 85,
2719 4, 8, 12, 10, 67, 97, 108, 105, 102, 111, 114, 110, 105, 97, 48, 30, 23, 13,
2720 50, 48, 48, 51, 49, 56, 49, 56, 51, 56, 48, 49, 90, 23, 13, 51, 48, 48, 51, 49,
2721 51, 48, 48, 48, 48, 48, 48, 90, 48, 72, 49, 28, 48, 26, 6, 3, 85, 4, 3, 12, 19,
2722 65, 112, 112, 108, 101, 32, 87, 101, 98, 65, 117, 116, 104, 110, 32, 67, 65,
2723 32, 49, 49, 19, 48, 17, 6, 3, 85, 4, 10, 12, 10, 65, 112, 112, 108, 101, 32,
2724 73, 110, 99, 46, 49, 19, 48, 17, 6, 3, 85, 4, 8, 12, 10, 67, 97, 108, 105, 102,
2725 111, 114, 110, 105, 97, 48, 118, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6,
2726 5, 43, 129, 4, 0, 34, 3, 98, 0, 4, 131, 46, 135, 47, 38, 20, 145, 129, 2, 37,
2727 185, 245, 252, 214, 187, 99, 120, 181, 245, 95, 63, 203, 4, 91, 199, 53, 153,
2728 52, 117, 253, 84, 144, 68, 223, 155, 254, 25, 33, 23, 101, 198, 154, 29, 218,
2729 5, 11, 56, 212, 80, 131, 64, 26, 67, 79, 178, 77, 17, 45, 86, 195, 225, 207,
2730 191, 203, 152, 145, 254, 192, 105, 96, 129, 190, 249, 108, 188, 119, 200, 141,
2731 221, 175, 70, 165, 174, 225, 221, 81, 91, 90, 250, 171, 147, 190, 156, 11, 38,
2732 145, 163, 102, 48, 100, 48, 18, 6, 3, 85, 29, 19, 1, 1, 255, 4, 8, 48, 6, 1, 1,
2733 255, 2, 1, 0, 48, 31, 6, 3, 85, 29, 35, 4, 24, 48, 22, 128, 20, 38, 215, 100,
2734 217, 197, 120, 194, 90, 103, 209, 167, 222, 107, 18, 208, 27, 99, 241, 198,
2735 215, 48, 29, 6, 3, 85, 29, 14, 4, 22, 4, 20, 235, 174, 130, 196, 255, 161, 172,
2736 91, 81, 212, 207, 36, 97, 5, 0, 190, 99, 189, 119, 136, 48, 14, 6, 3, 85, 29,
2737 15, 1, 1, 255, 4, 4, 3, 2, 1, 6, 48, 10, 6, 8, 42, 134, 72, 206, 61, 4, 3, 3,
2738 3, 104, 0, 48, 101, 2, 49, 0, 221, 139, 26, 52, 129, 165, 250, 217, 219, 180,
2739 231, 101, 123, 132, 30, 20, 76, 39, 183, 91, 135, 106, 65, 134, 194, 177, 71,
2740 87, 80, 51, 114, 39, 239, 229, 84, 69, 126, 246, 72, 149, 12, 99, 46, 92, 72,
2741 62, 112, 193, 2, 48, 44, 138, 96, 68, 220, 32, 31, 207, 229, 155, 195, 77, 41,
2742 48, 193, 72, 120, 81, 217, 96, 237, 106, 117, 241, 235, 74, 202, 190, 56, 205,
2743 37, 184, 151, 208, 200, 5, 190, 240, 199, 247, 139, 7, 165, 113, 198, 232, 14,
2744 7, 104, 97, 117, 116, 104, 68, 97, 116, 97, 88, 152, 218, 20, 177, 242, 169,
2745 30, 45, 223, 21, 45, 254, 74, 34, 125, 188, 96, 11, 1, 71, 41, 58, 94, 252,
2746 180, 169, 243, 209, 21, 231, 138, 182, 91, 69, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2747 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 187, 251, 101, 136, 87, 254, 105, 116, 75,
2748 131, 213, 200, 207, 228, 174, 67, 69, 193, 149, 177, 165, 1, 2, 3, 38, 32, 1,
2749 33, 88, 32, 212, 248, 99, 135, 245, 78, 94, 245, 231, 22, 62, 226, 45, 40, 215,
2750 4, 251, 188, 180, 125, 22, 236, 133, 161, 234, 78, 251, 105, 11, 119, 148, 144,
2751 34, 88, 32, 105, 249, 199, 167, 152, 173, 94, 147, 57, 2, 250, 21, 5, 51, 116,
2752 174, 217, 39, 160, 35, 12, 249, 120, 237, 52, 148, 171, 134, 138, 205, 26, 173,
2753 ]),
2754 client_data_json: Base64UrlSafeData::from(vec![
2755 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
2756 46, 99, 114, 101, 97, 116, 101, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110,
2757 103, 101, 34, 58, 34, 74, 84, 98, 107, 55, 121, 101, 107, 73, 75, 79, 90, 81,
2758 119, 119, 100, 71, 87, 55, 78, 101, 68, 73, 102, 120, 114, 89, 75, 48, 80, 118,
2759 117, 89, 120, 115, 117, 101, 45, 45, 71, 57, 78, 73, 34, 44, 34, 111, 114, 105,
2760 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 112, 101,
2761 99, 116, 114, 97, 108, 46, 108, 111, 99, 97, 108, 58, 56, 52, 52, 51, 34, 125,
2762 ]),
2763 transports: None,
2764 },
2765 type_: "public-key".to_string(),
2766 extensions: RegistrationExtensionsClientOutputs::default(),
2767 };
2768
2769 let result = wan.register_credential_internal(
2770 &rsp_d,
2771 UserVerificationPolicy::Required,
2772 &chal,
2773 &[],
2774 &[
2775 COSEAlgorithm::ES256,
2776 COSEAlgorithm::ES384,
2777 COSEAlgorithm::ES512,
2778 COSEAlgorithm::RS256,
2779 COSEAlgorithm::RS384,
2780 COSEAlgorithm::RS512,
2781 COSEAlgorithm::PS256,
2782 COSEAlgorithm::PS384,
2783 COSEAlgorithm::PS512,
2784 COSEAlgorithm::EDDSA,
2785 ],
2786 Some(&(APPLE_WEBAUTHN_ROOT_CA_PEM.try_into().unwrap())),
2787 true,
2789 &RequestRegistrationExtensions::default(),
2790 false,
2791 );
2792 debug!("{:?}", result);
2793 assert!(matches!(
2794 result,
2795 Err(WebauthnError::AttestationCertificateNonceMismatch)
2796 ));
2797 }
2798
2799 #[test]
2800 fn test_uv_consistency() {
2801 let _ = tracing_subscriber::fmt::try_init();
2802 let wan = Webauthn::new_unsafe_experts_only(
2803 "http://127.0.0.1:8080/auth",
2804 "127.0.0.1",
2805 vec![Url::parse("http://127.0.0.1:8080").unwrap()],
2806 AUTHENTICATOR_TIMEOUT,
2807 None,
2808 None,
2809 );
2810
2811 let mut creds = vec![
2813 Credential {
2814 cred_id: HumanBinaryData::from(vec![
2815 205, 198, 18, 130, 133, 220, 73, 23, 199, 211, 240, 143, 220, 154, 172, 117,
2816 91, 18, 164, 211, 24, 147, 16, 203, 118, 76, 33, 65, 31, 92, 236, 211, 79, 67,
2817 240, 30, 65, 247, 46, 134, 19, 136, 170, 209, 11, 115, 37, 12, 88, 244, 244,
2818 240, 148, 132, 191, 165, 150, 166, 252, 39, 97, 137, 21, 186,
2819 ]),
2820 cred: COSEKey {
2821 type_: COSEAlgorithm::ES256,
2822 key: COSEKeyType::EC_EC2(COSEEC2Key {
2823 curve: ECDSACurve::SECP256R1,
2824 x: [
2825 131, 160, 173, 103, 102, 41, 186, 183, 60, 175, 136, 103, 167, 145,
2826 239, 235, 216, 80, 109, 26, 218, 187, 146, 77, 5, 173, 143, 33, 126,
2827 119, 197, 116,
2828 ]
2829 .to_vec()
2830 .into(),
2831 y: [
2832 59, 202, 240, 192, 92, 25, 186, 100, 135, 111, 53, 194, 234, 134, 249,
2833 249, 30, 22, 70, 58, 81, 250, 141, 38, 217, 9, 44, 121, 162, 230, 197,
2834 87,
2835 ]
2836 .to_vec()
2837 .into(),
2838 }),
2839 },
2840 counter: 0,
2841 transports: None,
2842 user_verified: false,
2843 backup_eligible: false,
2844 backup_state: false,
2845 registration_policy: UserVerificationPolicy::Discouraged_DO_NOT_USE,
2846 extensions: RegisteredExtensions::none(),
2847 attestation: ParsedAttestation {
2848 data: ParsedAttestationData::None,
2849 metadata: AttestationMetadata::None,
2850 },
2851 attestation_format: AttestationFormat::None,
2852 },
2853 Credential {
2854 cred_id: HumanBinaryData::from(vec![
2855 211, 204, 163, 253, 101, 149, 83, 136, 242, 175, 211, 104, 215, 131, 122, 175,
2856 187, 84, 13, 3, 21, 24, 11, 138, 50, 137, 55, 225, 180, 109, 49, 28, 98, 8, 28,
2857 181, 149, 241, 106, 124, 110, 149, 154, 198, 23, 8, 8, 4, 41, 69, 236, 203,
2858 122, 120, 204, 174, 28, 58, 171, 43, 218, 81, 195, 177,
2859 ]),
2860 cred: COSEKey {
2861 type_: COSEAlgorithm::ES256,
2862 key: COSEKeyType::EC_EC2(COSEEC2Key {
2863 curve: ECDSACurve::SECP256R1,
2864 x: [
2865 87, 236, 127, 24, 222, 164, 79, 139, 67, 77, 159, 33, 76, 155, 161,
2866 155, 234, 151, 203, 142, 136, 87, 77, 177, 27, 67, 248, 104, 233, 156,
2867 15, 51,
2868 ]
2869 .to_vec()
2870 .into(),
2871 y: [
2872 21, 29, 94, 187, 68, 148, 156, 253, 117, 226, 40, 88, 53, 61, 209, 227,
2873 12, 164, 136, 185, 148, 125, 86, 21, 22, 52, 195, 192, 6, 6, 176, 179,
2874 ]
2875 .to_vec()
2876 .into(),
2877 }),
2878 },
2879 counter: 1,
2880 transports: None,
2881 user_verified: true,
2882 backup_eligible: false,
2883 backup_state: false,
2884 registration_policy: UserVerificationPolicy::Required,
2885 extensions: RegisteredExtensions::none(),
2886 attestation: ParsedAttestation {
2887 data: ParsedAttestationData::None,
2888 metadata: AttestationMetadata::None,
2889 },
2890 attestation_format: AttestationFormat::None,
2891 },
2892 ];
2893 assert!(
2896 wan.new_challenge_authenticate_builder(creds.clone(), None)
2897 .unwrap_err()
2898 == WebauthnError::InconsistentUserVerificationPolicy
2899 );
2900
2901 {
2905 creds
2906 .get_mut(0)
2907 .map(|cred| {
2908 cred.user_verified = true;
2909 cred.registration_policy = UserVerificationPolicy::Required;
2910 })
2911 .unwrap();
2912 creds
2913 .get_mut(1)
2914 .map(|cred| {
2915 cred.user_verified = true;
2916 cred.registration_policy = UserVerificationPolicy::Required;
2917 })
2918 .unwrap();
2919 }
2920
2921 let builder = wan
2922 .new_challenge_authenticate_builder(creds.clone(), None)
2923 .expect("Unable to create authenticate builder");
2924
2925 let r = wan.generate_challenge_authenticate(builder);
2926 debug!("{:?}", r);
2927 assert!(r.is_ok());
2928
2929 {
2933 creds
2934 .get_mut(0)
2935 .map(|cred| {
2936 cred.user_verified = true;
2937 cred.registration_policy = UserVerificationPolicy::Discouraged_DO_NOT_USE;
2938 })
2939 .unwrap();
2940 creds
2941 .get_mut(1)
2942 .map(|cred| {
2943 cred.user_verified = false;
2944 cred.registration_policy = UserVerificationPolicy::Discouraged_DO_NOT_USE;
2945 })
2946 .unwrap();
2947 }
2948
2949 let builder = wan
2950 .new_challenge_authenticate_builder(creds.clone(), None)
2951 .expect("Unable to create authenticate builder");
2952
2953 let r = wan.generate_challenge_authenticate(builder);
2954
2955 debug!("{:?}", r);
2956 assert!(r.is_ok());
2957 }
2958
2959 #[test]
2960 fn test_subdomain_origin() {
2961 let _ = tracing_subscriber::fmt::try_init();
2962 let wan = Webauthn::new_unsafe_experts_only(
2963 "rp_name",
2964 "idm.example.com",
2965 vec![Url::parse("https://idm.example.com:8080").unwrap()],
2966 AUTHENTICATOR_TIMEOUT,
2967 Some(true),
2968 None,
2969 );
2970
2971 let id =
2972 "zIQDbMsgDg89LbWHAMLrpgI4w5Bz5Hy8U6F-gaUmda1fgwgn6NzhXQFJwEDfowsiY0NTgdU2jjAG2PmzaD5aWA".to_string();
2973 let raw_id = Base64UrlSafeData::from(vec![
2974 204, 132, 3, 108, 203, 32, 14, 15, 61, 45, 181, 135, 0, 194, 235, 166, 2, 56, 195, 144,
2975 115, 228, 124, 188, 83, 161, 126, 129, 165, 38, 117, 173, 95, 131, 8, 39, 232, 220,
2976 225, 93, 1, 73, 192, 64, 223, 163, 11, 34, 99, 67, 83, 129, 213, 54, 142, 48, 6, 216,
2977 249, 179, 104, 62, 90, 88,
2978 ]);
2979
2980 let chal = Challenge::new(vec![
2981 174, 237, 157, 66, 159, 70, 216, 148, 130, 184, 54, 89, 38, 149, 217, 32, 161, 42, 99,
2982 227, 50, 124, 208, 164, 221, 38, 202, 210, 140, 102, 116, 84,
2983 ]);
2984
2985 let rsp_d = RegisterPublicKeyCredential {
2986 id: id.clone(),
2987 raw_id: raw_id.clone(),
2988 response: AuthenticatorAttestationResponseRaw {
2989 attestation_object: Base64UrlSafeData::from(vec![
2990 163, 99, 102, 109, 116, 104, 102, 105, 100, 111, 45, 117, 50, 102, 103, 97,
2991 116, 116, 83, 116, 109, 116, 162, 99, 115, 105, 103, 88, 70, 48, 68, 2, 32,
2992 125, 195, 114, 22, 37, 221, 215, 19, 15, 177, 53, 167, 63, 179, 235, 152, 8,
2993 204, 65, 203, 37, 196, 223, 76, 226, 35, 234, 182, 102, 156, 93, 50, 2, 32, 20,
2994 177, 103, 196, 47, 107, 19, 76, 35, 2, 14, 186, 197, 229, 113, 38, 83, 252, 17,
2995 164, 221, 19, 27, 34, 193, 155, 205, 220, 133, 53, 47, 223, 99, 120, 53, 99,
2996 129, 89, 2, 193, 48, 130, 2, 189, 48, 130, 1, 165, 160, 3, 2, 1, 2, 2, 4, 24,
2997 172, 70, 192, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 46,
2998 49, 44, 48, 42, 6, 3, 85, 4, 3, 19, 35, 89, 117, 98, 105, 99, 111, 32, 85, 50,
2999 70, 32, 82, 111, 111, 116, 32, 67, 65, 32, 83, 101, 114, 105, 97, 108, 32, 52,
3000 53, 55, 50, 48, 48, 54, 51, 49, 48, 32, 23, 13, 49, 52, 48, 56, 48, 49, 48, 48,
3001 48, 48, 48, 48, 90, 24, 15, 50, 48, 53, 48, 48, 57, 48, 52, 48, 48, 48, 48, 48,
3002 48, 90, 48, 110, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 83, 69, 49, 18, 48, 16,
3003 6, 3, 85, 4, 10, 12, 9, 89, 117, 98, 105, 99, 111, 32, 65, 66, 49, 34, 48, 32,
3004 6, 3, 85, 4, 11, 12, 25, 65, 117, 116, 104, 101, 110, 116, 105, 99, 97, 116,
3005 111, 114, 32, 65, 116, 116, 101, 115, 116, 97, 116, 105, 111, 110, 49, 39, 48,
3006 37, 6, 3, 85, 4, 3, 12, 30, 89, 117, 98, 105, 99, 111, 32, 85, 50, 70, 32, 69,
3007 69, 32, 83, 101, 114, 105, 97, 108, 32, 52, 49, 51, 57, 52, 51, 52, 56, 56, 48,
3008 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1,
3009 7, 3, 66, 0, 4, 121, 234, 59, 44, 124, 73, 112, 16, 98, 35, 12, 210, 63, 235,
3010 96, 229, 41, 49, 113, 212, 131, 241, 0, 190, 133, 157, 107, 15, 131, 151, 3, 1,
3011 181, 70, 205, 212, 110, 207, 202, 227, 227, 243, 15, 129, 233, 237, 98, 189,
3012 38, 141, 76, 30, 189, 55, 179, 188, 190, 146, 168, 194, 174, 235, 78, 58, 163,
3013 108, 48, 106, 48, 34, 6, 9, 43, 6, 1, 4, 1, 130, 196, 10, 2, 4, 21, 49, 46, 51,
3014 46, 54, 46, 49, 46, 52, 46, 49, 46, 52, 49, 52, 56, 50, 46, 49, 46, 55, 48, 19,
3015 6, 11, 43, 6, 1, 4, 1, 130, 229, 28, 2, 1, 1, 4, 4, 3, 2, 5, 32, 48, 33, 6, 11,
3016 43, 6, 1, 4, 1, 130, 229, 28, 1, 1, 4, 4, 18, 4, 16, 203, 105, 72, 30, 143,
3017 247, 64, 57, 147, 236, 10, 39, 41, 161, 84, 168, 48, 12, 6, 3, 85, 29, 19, 1,
3018 1, 255, 4, 2, 48, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0,
3019 3, 130, 1, 1, 0, 151, 157, 3, 151, 216, 96, 248, 46, 225, 93, 49, 28, 121, 110,
3020 186, 251, 34, 250, 167, 224, 132, 217, 186, 180, 198, 27, 187, 87, 243, 230,
3021 180, 193, 138, 72, 55, 184, 92, 60, 78, 219, 228, 131, 67, 244, 214, 165, 217,
3022 177, 206, 218, 138, 225, 254, 212, 145, 41, 33, 115, 5, 142, 94, 225, 203, 221,
3023 107, 218, 192, 117, 87, 198, 160, 232, 211, 104, 37, 186, 21, 158, 127, 181,
3024 173, 140, 218, 248, 4, 134, 140, 249, 14, 143, 31, 138, 234, 23, 192, 22, 181,
3025 92, 42, 122, 212, 151, 200, 148, 251, 113, 215, 83, 215, 155, 154, 72, 75, 108,
3026 55, 109, 114, 59, 153, 141, 46, 29, 67, 6, 191, 16, 51, 181, 174, 248, 204,
3027 165, 203, 178, 86, 139, 105, 36, 34, 109, 34, 163, 88, 171, 125, 135, 228, 172,
3028 95, 46, 9, 26, 167, 21, 121, 243, 165, 105, 9, 73, 125, 114, 245, 78, 6, 186,
3029 193, 195, 180, 65, 59, 186, 94, 175, 148, 195, 182, 79, 52, 249, 235, 164, 26,
3030 203, 106, 226, 131, 119, 109, 54, 70, 83, 120, 72, 254, 232, 132, 189, 221,
3031 245, 177, 186, 87, 152, 84, 207, 253, 206, 186, 195, 68, 5, 149, 39, 229, 109,
3032 213, 152, 248, 245, 102, 113, 90, 190, 67, 1, 221, 25, 17, 48, 230, 185, 240,
3033 198, 64, 57, 18, 83, 226, 41, 128, 63, 58, 239, 39, 75, 237, 191, 222, 63, 203,
3034 189, 66, 234, 214, 121, 104, 97, 117, 116, 104, 68, 97, 116, 97, 88, 196, 239,
3035 115, 241, 111, 91, 226, 27, 23, 185, 145, 15, 75, 208, 190, 109, 73, 186, 119,
3036 107, 122, 2, 224, 117, 140, 139, 132, 92, 21, 148, 105, 187, 55, 65, 0, 0, 0,
3037 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 204, 132, 3, 108,
3038 203, 32, 14, 15, 61, 45, 181, 135, 0, 194, 235, 166, 2, 56, 195, 144, 115, 228,
3039 124, 188, 83, 161, 126, 129, 165, 38, 117, 173, 95, 131, 8, 39, 232, 220, 225,
3040 93, 1, 73, 192, 64, 223, 163, 11, 34, 99, 67, 83, 129, 213, 54, 142, 48, 6,
3041 216, 249, 179, 104, 62, 90, 88, 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 169, 47,
3042 103, 25, 132, 175, 84, 4, 152, 225, 66, 5, 83, 201, 162, 184, 13, 204, 129,
3043 162, 225, 184, 248, 76, 21, 9, 140, 51, 233, 28, 21, 189, 34, 88, 32, 152, 216,
3044 30, 49, 240, 214, 59, 66, 44, 67, 110, 41, 126, 83, 131, 50, 13, 175, 237, 57,
3045 225, 87, 38, 132, 17, 54, 52, 22, 0, 142, 54, 255,
3046 ]),
3047 client_data_json: Base64UrlSafeData::from(vec![
3048 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
3049 46, 99, 114, 101, 97, 116, 101, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110,
3050 103, 101, 34, 58, 34, 114, 117, 50, 100, 81, 112, 57, 71, 50, 74, 83, 67, 117,
3051 68, 90, 90, 74, 112, 88, 90, 73, 75, 69, 113, 89, 45, 77, 121, 102, 78, 67,
3052 107, 51, 83, 98, 75, 48, 111, 120, 109, 100, 70, 81, 34, 44, 34, 111, 114, 105,
3053 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 105, 100, 109,
3054 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 58, 56, 48, 56, 48, 34,
3055 44, 34, 99, 114, 111, 115, 115, 79, 114, 105, 103, 105, 110, 34, 58, 102, 97,
3056 108, 115, 101, 125,
3057 ]),
3058 transports: None,
3059 },
3060 type_: "public-key".to_string(),
3061 extensions: RegistrationExtensionsClientOutputs::default(),
3062 };
3063
3064 let cred = wan
3065 .register_credential_internal(
3066 &rsp_d,
3067 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3068 &chal,
3069 &[],
3070 &[COSEAlgorithm::ES256],
3071 None,
3072 false,
3073 &RequestRegistrationExtensions::default(),
3074 true,
3075 )
3076 .expect("Failed to register credential");
3077
3078 let chal = Challenge::new(vec![
3082 127, 52, 208, 243, 214, 88, 79, 34, 12, 226, 145, 217, 217, 241, 99, 228, 171, 232,
3083 226, 26, 191, 32, 122, 4, 164, 217, 49, 134, 85, 161, 116, 32,
3084 ]);
3085
3086 let rsp_d = PublicKeyCredential {
3087 id,
3088 raw_id,
3089 response: AuthenticatorAssertionResponseRaw {
3090 authenticator_data: Base64UrlSafeData::from(vec![
3091 239, 115, 241, 111, 91, 226, 27, 23, 185, 145, 15, 75, 208, 190, 109, 73, 186,
3092 119, 107, 122, 2, 224, 117, 140, 139, 132, 92, 21, 148, 105, 187, 55, 1, 0, 0,
3093 3, 237,
3094 ]),
3095 client_data_json: Base64UrlSafeData::from(vec![
3096 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
3097 46, 103, 101, 116, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110, 103, 101, 34,
3098 58, 34, 102, 122, 84, 81, 56, 57, 90, 89, 84, 121, 73, 77, 52, 112, 72, 90, 50,
3099 102, 70, 106, 53, 75, 118, 111, 52, 104, 113, 95, 73, 72, 111, 69, 112, 78,
3100 107, 120, 104, 108, 87, 104, 100, 67, 65, 34, 44, 34, 111, 114, 105, 103, 105,
3101 110, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 117, 98, 46, 105,
3102 100, 109, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 58, 56, 48,
3103 56, 48, 34, 44, 34, 99, 114, 111, 115, 115, 79, 114, 105, 103, 105, 110, 34,
3104 58, 102, 97, 108, 115, 101, 125,
3105 ]),
3106 signature: Base64UrlSafeData::from(vec![
3107 48, 69, 2, 32, 113, 175, 47, 74, 251, 87, 115, 175, 144, 222, 52, 128, 21, 250,
3108 35, 239, 213, 162, 75, 45, 110, 28, 15, 103, 138, 234, 106, 219, 34, 198, 74,
3109 74, 2, 33, 0, 204, 144, 147, 62, 250, 6, 11, 19, 239, 90, 108, 6, 126, 165,
3110 157, 41, 223, 251, 81, 22, 202, 121, 126, 133, 192, 81, 71, 193, 220, 208, 25,
3111 127,
3112 ]),
3113 user_handle: Some(Base64UrlSafeData::from(vec![])),
3114 },
3115 extensions: AuthenticationExtensionsClientOutputs::default(),
3116 type_: "public-key".to_string(),
3117 };
3118
3119 let r = wan.verify_credential_internal(
3120 &rsp_d,
3121 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3122 &chal,
3123 &cred,
3124 &None,
3125 false,
3126 );
3127 trace!("RESULT: {:?}", r);
3128 assert!(r.is_ok());
3129 }
3130
3131 #[test]
3132 fn test_yk5bio_fallback_alg_attest_none() {
3133 let _ = tracing_subscriber::fmt::try_init();
3134 let wan = Webauthn::new_unsafe_experts_only(
3135 "http://localhost:8080/auth",
3136 "localhost",
3137 vec![Url::parse("http://localhost:8080").unwrap()],
3138 AUTHENTICATOR_TIMEOUT,
3139 None,
3140 None,
3141 );
3142
3143 let chal: HumanBinaryData =
3144 serde_json::from_str("\"NE6dm0mgUe47-X0Yf5nRdhYokY3A8XAzs10KBLGlVY0\"").unwrap();
3145 let chal = Challenge::from(chal);
3146
3147 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{
3148 "id": "k8-N3sbgQe_ze58s5b955iLRrqcizmms-YOqFQTQbBbbJLStt9CaR3vUYXEajy4O22fAgdyY1aOvc6HW9o1ikqiSWee2CxXXJe2DE40byI4-m4oesHfmz4urfMxkIrAd_4i8pgWHNLVlTSMtAzhCXH16Yw4uUsdsntv1HpYiu94",
3149 "rawId": "k8-N3sbgQe_ze58s5b955iLRrqcizmms-YOqFQTQbBbbJLStt9CaR3vUYXEajy4O22fAgdyY1aOvc6HW9o1ikqiSWee2CxXXJe2DE40byI4-m4oesHfmz4urfMxkIrAd_4i8pgWHNLVlTSMtAzhCXH16Yw4uUsdsntv1HpYiu94",
3150 "response": {
3151 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjhSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAQAAAAAAAAAAAAAAAAAAAAAAgJPPjd7G4EHv83ufLOW_eeYi0a6nIs5prPmDqhUE0GwW2yS0rbfQmkd71GFxGo8uDttnwIHcmNWjr3Oh1vaNYpKoklnntgsV1yXtgxONG8iOPpuKHrB35s-Lq3zMZCKwHf-IvKYFhzS1ZU0jLQM4Qlx9emMOLlLHbJ7b9R6WIrvepAEBAycgBiFYICgd3qEI_iQqhYAi0y47WqeU2Bf2kVY4Mq02t1zgTzkV",
3152 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiTkU2ZG0wbWdVZTQ3LVgwWWY1blJkaFlva1kzQThYQXpzMTBLQkxHbFZZMCIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0"
3153 },
3154 "type": "public-key"
3155 }"#).unwrap();
3156
3157 debug!("{:?}", rsp_d);
3158
3159 let result = wan.register_credential_internal(
3160 &rsp_d,
3161 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3162 &chal,
3163 &[],
3164 &[COSEAlgorithm::EDDSA],
3165 None,
3166 false,
3167 &RequestRegistrationExtensions::default(),
3168 false,
3169 );
3170 debug!("{:?}", result);
3171 assert!(result.is_ok());
3172 }
3173
3174 #[test]
3175 fn test_solokey_fallback_alg_attest_none() {
3176 let _ = tracing_subscriber::fmt::try_init();
3177 let wan = Webauthn::new_unsafe_experts_only(
3178 "https://webauthn.firstyear.id.au",
3179 "webauthn.firstyear.id.au",
3180 vec![Url::parse("https://webauthn.firstyear.id.au").unwrap()],
3181 AUTHENTICATOR_TIMEOUT,
3182 None,
3183 None,
3184 );
3185
3186 let chal: HumanBinaryData =
3187 serde_json::from_str("\"rRPXQ7lps3xBQzX3dDAor9fHwH_ff55gUU-8wwZVK-g\"").unwrap();
3188 let chal = Challenge::from(chal);
3189
3190 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{
3191 "id": "owBY6NCpGj_5nAM427VzsWjmifVdW10z3Ov8fyN5BPX5cxyR2umlVN5h7oGUos-9RPeoYBuCRBkSyAK6jM0gkZ0RLrHrCGRTwfk5p1NQ2ucX_cAh0uel-TkBpyWE-dxqXyk-WLlhSA4LKEdlmyTVqiDAGG7CRHdDn0oAufgq0za7-Crt6cWPKwzmkTGHsMAaEqEaQzHjo1D-pb_WkJJfYp5SZ52ZdTj5eKx7htT5QIogb70lwTKv82ix8PZskqiV-L4j5EroU-xXl7sxKlVtmkS8tSlHpyU-h8fZcFmmW4lr6cBOACd5aNEgR88BTFqQQZ97RORZ7J9sagJQJ63Jj-CZTqGBewVu2jazgA",
3192 "rawId": "owBY6NCpGj_5nAM427VzsWjmifVdW10z3Ov8fyN5BPX5cxyR2umlVN5h7oGUos-9RPeoYBuCRBkSyAK6jM0gkZ0RLrHrCGRTwfk5p1NQ2ucX_cAh0uel-TkBpyWE-dxqXyk-WLlhSA4LKEdlmyTVqiDAGG7CRHdDn0oAufgq0za7-Crt6cWPKwzmkTGHsMAaEqEaQzHjo1D-pb_WkJJfYp5SZ52ZdTj5eKx7htT5QIogb70lwTKv82ix8PZskqiV-L4j5EroU-xXl7sxKlVtmkS8tSlHpyU-h8fZcFmmW4lr6cBOACd5aNEgR88BTFqQQZ97RORZ7J9sagJQJ63Jj-CZTqGBewVu2jazgA",
3193 "response": {
3194 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBbWq5u_Dfmhb5Hbszu7Ey-vnRfHgsSCbG7HDs7ljZfvUqRQAAAEoAAAAAAAAAAAAAAAAAAAAAAQyjAFjo0KkaP_mcAzjbtXOxaOaJ9V1bXTPc6_x_I3kE9flzHJHa6aVU3mHugZSiz71E96hgG4JEGRLIArqMzSCRnREusesIZFPB-TmnU1Da5xf9wCHS56X5OQGnJYT53GpfKT5YuWFIDgsoR2WbJNWqIMAYbsJEd0OfSgC5-CrTNrv4Ku3pxY8rDOaRMYewwBoSoRpDMeOjUP6lv9aQkl9inlJnnZl1OPl4rHuG1PlAiiBvvSXBMq_zaLHw9mySqJX4viPkSuhT7FeXuzEqVW2aRLy1KUenJT6Hx9lwWaZbiWvpwE4AJ3lo0SBHzwFMWpBBn3tE5Fnsn2xqAlAnrcmP4JlOoYF7BW7aNrOApAEBAycgBiFYIKfpbghX95Ey_8DV4Ots95iyCRWa7OElliqsg9tdnRur",
3195 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiclJQWFE3bHBzM3hCUXpYM2REQW9yOWZId0hfZmY1NWdVVS04d3daVkstZyIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4uZmlyc3R5ZWFyLmlkLmF1IiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ"
3196 },
3197 "type": "public-key"
3198 }"#).unwrap();
3199
3200 debug!("{:?}", rsp_d);
3201
3202 let result = wan.register_credential_internal(
3203 &rsp_d,
3204 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3205 &chal,
3206 &[],
3207 &[COSEAlgorithm::EDDSA],
3208 None,
3209 false,
3210 &RequestRegistrationExtensions::default(),
3211 false,
3212 );
3213 debug!("{:?}", result);
3214 assert!(result.is_ok());
3215 }
3216
3217 #[test]
3219 #[ignore]
3220 fn test_google_pixel_3a_direct_attestation() {
3221 let _ = tracing_subscriber::fmt::try_init();
3222 let wan = Webauthn::new_unsafe_experts_only(
3223 "https://webauthn.firstyear.id.au",
3224 "webauthn.firstyear.id.au",
3225 vec![Url::parse("https://webauthn.firstyear.id.au").unwrap()],
3226 AUTHENTICATOR_TIMEOUT,
3227 None,
3228 None,
3229 );
3230 let chal: HumanBinaryData =
3231 serde_json::from_str("\"Y0j5PX0VXeKb2150k6sAh1QNRBJ3iTv8WBsUfgn_pRs\"").unwrap();
3232 let chal = Challenge::from(chal);
3233
3234 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{
3235 "id": "Afx3PxBAXAercQxfFjvHGt3OnTHtXjfNcuCxI-XVaeAtLkohnHQ_mJ2Ocgj2Bhhkv3neczncwaH1nkVpwitUxyQ",
3236 "rawId": "Afx3PxBAXAercQxfFjvHGt3OnTHtXjfNcuCxI-XVaeAtLkohnHQ_mJ2Ocgj2Bhhkv3neczncwaH1nkVpwitUxyQ",
3237 "response": {
3238 "attestationObject": "o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaTIxNDgxNTA0NGhyZXNwb25zZVkgcmV5SmhiR2NpT2lKU1V6STFOaUlzSW5nMVl5STZXeUpOU1VsR1dVUkRRMEpGYVdkQmQwbENRV2RKVWtGT2FHTkhiRGN3UWpWaFNVTlJRVUZCUVVWQ2JpOUZkMFJSV1VwTGIxcEphSFpqVGtGUlJVeENVVUYzVW1wRlRFMUJhMGRCTVZWRlFtaE5RMVpXVFhoSmFrRm5RbWRPVmtKQmIxUkhWV1IyWWpKa2MxcFRRbFZqYmxaNlpFTkNWRnBZU2pKaFYwNXNZM2xDVFZSRlRYaEZla0ZTUW1kT1ZrSkJUVlJEYTJSVlZYbENSRkZUUVhoU1JGRjNTR2hqVGsxcVNYZE5WRWt4VFZSQmQwMUVUVEJYYUdOT1RXcEpkMDVFU1RGTlZFRjNUVVJOZWxkcVFXUk5Vbk4zUjFGWlJGWlJVVVJGZUVwb1pFaFNiR016VVhWWlZ6VnJZMjA1Y0ZwRE5XcGlNakIzWjJkRmFVMUJNRWREVTNGSFUwbGlNMFJSUlVKQlVWVkJRVFJKUWtSM1FYZG5aMFZMUVc5SlFrRlJRMWsxYkhwR1kwaHNaVEZFVEd4MFRrcG9iRk5qYm5GV1VuTllRMWQ2TmpGR2J5OUdSMHRzWW0wMGJHSTVZemR5V1hwWlRtOU1UV3hVV0d0YWFVczBSMUpGZG5acVozZE1kMk0zVEVNNFRUWjZiM0pHY1dFNWFqTjZORzB2VFhWa1EyRkdWblIzTUVGVmJtVnFhbFpTYUZSaVdrVkthV3M0VVVWaWFIZzFZWHBDVGxOd00yZ3JSemcyTlV4YUszbG5SR1JrTUZaYVMyUnhOVE5MUWpscU1FWTRlV0pyWkhaVlkxTnpMMjB6UjAxcVYwVkJhWEEwVjI1eVJGazVSa3hhWm5ncmNFTndRVTVQUVdKVVRuWmphV2xMUVhkUGExRkhSRVZKTVVaeFZFTjFTVzVhYVVoU2RtMXBaazlSYzA5dVUwVjRTWFV6YzFjM2RsRmpSWFJVWWtZclZWcDRhR3BpU0RWRmRtSmtiMFZ1WVV4Tk5sUkNTbmwxYkRkMGVsZDFhalJaTkZoVVkydDJaRk5EYm5KQlUzZHpaM2xST1hWT09YZG9VSFpCVm01NFIxWkNXRWxGVkVWMFZVRTRiWGxRTkROVVMzTktRV2ROUWtGQlIycG5aMHAzVFVsSlEySkVRVTlDWjA1V1NGRTRRa0ZtT0VWQ1FVMURRbUZCZDBWM1dVUldVakJzUWtGM2QwTm5XVWxMZDFsQ1FsRlZTRUYzUlhkRVFWbEVWbEl3VkVGUlNDOUNRVWwzUVVSQlpFSm5UbFpJVVRSRlJtZFJWWEZXVFRKVlRWcFdRVXMxUTNsUldUWkdSM0owVTBrM01YTXliM2RJZDFsRVZsSXdha0pDWjNkR2IwRlZTbVZKV1VSeVNsaHJXbEZ4TldSU1pHaHdRMFF6YkU5NmRVcEpkMkpSV1VsTGQxbENRbEZWU0VGUlJVVlpWRUptVFVOdlIwTkRjMGRCVVZWR1FucEJRbWhvTlc5a1NGSjNUMms0ZG1JeVRucGpRelYzWVRKcmRWb3lPWFphZVRsdVpFaE5lRnBFVW5CaWJsRjNUVkZaU1V0M1dVSkNVVlZJVFVGTFIwcFhhREJrU0VFMlRIazVkMkV5YTNWYU1qbDJXbms1ZVZwWVFuWk1NazVzWTI1U2Vrd3laREJqZWtaclRrTTFhMXBZU1hkSVVWbEVWbEl3VWtKQ1dYZEdTVWxUV1ZoU01GcFlUakJNYlVaMVdraEtkbUZYVVhWWk1qbDBUVU5GUjBFeFZXUkpRVkZoVFVKbmQwTkJXVWRhTkVWTlFWRkpRazFCZDBkRGFYTkhRVkZSUWpGdWEwTkNVVTEzVUhkWlJGWlNNR1pDUkdkM1RtcEJNRzlFUzJkTlNWbDFZVWhTTUdORWIzWk1NazU1WWtoTmRXTkhkSEJNYldSMllqSmpkbG96VW5wTlYxRXdZVmMxTUV3eFNUTlBSMWt4WldwT2NVNHpiRzVNYlU1NVlrUkRRMEZSVFVkRGFYTkhRVkZSUWpGdWEwTkNRVWxGWjJaUlJXZG1SVUUzZDBJeFFVWkhhbk5RV0RsQldHMWpWbTB5TkU0emFWQkVTMUkyZWtKemJua3ZaV1ZwUlV0aFJHWTNWV2wzV0d4QlFVRkNabkJFYkVSQlNVRkJRVkZFUVVWWmQxSkJTV2RKTkRWc1VIRXdOVmRXZUVsNmJ6RlZiR2hvVTBWMmNrbHZRVlkxUlhGME1DdHNWa1Z1YVd4WWNUaFZRMGxEVjNCSFJrZzVSQzlFZVdabllXZFhNeTh5WjBWMVNGcGFPRXRIU3psQ09VcGFla0pEU2l0Q2RsTmxRVWhaUVV0WWJTczRTalExVDFOSWQxWnVUMlpaTmxZek5XSTFXR1phZUdkRGRtbzFWRll3YlZoRFZtUjRORkZCUVVGR0sydFBWVXcwWjBGQlFrRk5RVko2UWtaQmFVVkJiMk50Vm1SamJFTkVNbUpHVUU5T2IxWXlNWFJpT0VkelpWZGtNa1p0TTFkVFIzRlhUVEIzUkRCQ2MwTkpSV1YwUkhsd05YcGpialU0YWpob1VrUlNieTlXVlVkMFp6TnRkaklyV1RaS1JqUnFibnBDVWt0RlVVMUJNRWREVTNGSFUwbGlNMFJSUlVKRGQxVkJRVFJKUWtGUlFVbHViSGh1U1VsMlEwdHJWbWxLWlRWaWRFVTJUVkJaUVdwNE0wZElXakZMTDNwc2RIQnpaVTFTVVRoaVJsVkxUVVpNVTFOeE4zVk9SbEJSY2pkUFZ6Tm9RMmhuVEVORFZtOUZla2MwWW5GR2RVMTRWMklyU0hRNVVFaDBSbmhXV0hwaVowcDVhbUoyUkRkSVUwOVVjV3M0UVZreFlTOU9VVFYxYW5ORFRGTktORVJtTmxKa2FFZ3ZUM1p3ZEdWUU0wNW1iRlZYVGsxSlFrVjJNRlYyTVhSMlRFVm1VVWRYTUdoVFltYzJUQzlJUjJkQlkxZDFURGRzTmk5UVdFbEZkVEpsVERkcllVZEdVbWhKTW1KcU5FcE9PVmxGU0VkdWRtaGpSM0ExTlhsQ016ZG9TWGd4YkRoVk56VllPV2hJTVU4MlRVMXRlblpLTURWeGRGaERjMVJZVVdsbGFrUXdWSFI0VkdwSFZpdFdTM1J3VEZoSlEzQlVabmhPYzNCQ2VrTk1hRGt4U1V4dE1uQkhORlk1Wkd0dFJWWnZPVEIwU25wS1NTOUJTelpoVUdadloyTktiMEpuYm5CVE9GVlpkMEZPYlZORElpd2lUVWxKUm1wRVEwTkJNMU5uUVhkSlFrRm5TVTVCWjBOUGMyZEplazV0VjB4YVRUTmliWHBCVGtKbmEzRm9hMmxIT1hjd1FrRlJjMFpCUkVKSVRWRnpkME5SV1VSV1VWRkhSWGRLVmxWNlJXbE5RMEZIUVRGVlJVTm9UVnBTTWpsMldqSjRiRWxHVW5sa1dFNHdTVVpPYkdOdVduQlpNbFo2U1VWNFRWRjZSVlZOUWtsSFFURlZSVUY0VFV4U01WSlVTVVpLZG1JelVXZFZha1YzU0doalRrMXFRWGRQUkVWNlRVUkJkMDFFVVhsWGFHTk9UV3BqZDA5VVRYZE5SRUYzVFVSUmVWZHFRa2ROVVhOM1ExRlpSRlpSVVVkRmQwcFdWWHBGYVUxRFFVZEJNVlZGUTJoTldsSXlPWFphTW5oc1NVWlNlV1JZVGpCSlJrNXNZMjVhY0ZreVZucEpSWGhOVVhwRlZFMUNSVWRCTVZWRlFYaE5TMUl4VWxSSlJVNUNTVVJHUlU1RVEwTkJVMGwzUkZGWlNrdHZXa2xvZG1OT1FWRkZRa0pSUVVSblowVlFRVVJEUTBGUmIwTm5aMFZDUVV0MlFYRnhVRU5GTWpkc01IYzVla000WkZSUVNVVTRPV0pCSzNoVWJVUmhSemQ1TjFabVVUUmpLMjFQVjJoc1ZXVmlWVkZ3U3pCNWRqSnlOamM0VWtwRmVFc3dTRmRFYW1WeEsyNU1TVWhPTVVWdE5XbzJja0ZTV21sNGJYbFNVMnBvU1ZJd1MwOVJVRWRDVFZWc1pITmhlblJKU1VvM1R6Qm5Memd5Y1dvdmRrZEViQzh2TTNRMGRGUnhlR2xTYUV4UmJsUk1XRXBrWlVJck1rUm9hMlJWTmtsSlozZzJkMDQzUlRWT1kxVklNMUpqYzJWcVkzRnFPSEExVTJveE9YWkNiVFpwTVVab2NVeEhlVzFvVFVaeWIxZFdWVWRQTTNoMFNVZzVNV1J6WjNrMFpVWkxZMlpMVmt4WFN6TnZNakU1TUZFd1RHMHZVMmxMYlV4aVVrbzFRWFUwZVRGbGRVWktiVEpLVFRsbFFqZzBSbXR4WVROcGRuSllWMVZsVm5SNVpUQkRVV1JMZG5OWk1rWnJZWHAyZUhSNGRuVnpURXA2VEZkWlNHczFOWHBqVWtGaFkwUkJNbE5sUlhSQ1lsRm1SREZ4YzBOQmQwVkJRV0ZQUTBGWVdYZG5aMFo1VFVFMFIwRXhWV1JFZDBWQ0wzZFJSVUYzU1VKb2FrRmtRbWRPVmtoVFZVVkdha0ZWUW1kbmNrSm5SVVpDVVdORVFWRlpTVXQzV1VKQ1VWVklRWGRKZDBWbldVUldVakJVUVZGSUwwSkJaM2RDWjBWQ0wzZEpRa0ZFUVdSQ1owNVdTRkUwUlVablVWVktaVWxaUkhKS1dHdGFVWEUxWkZKa2FIQkRSRE5zVDNwMVNrbDNTSGRaUkZaU01HcENRbWQzUm05QlZUVkxPSEpLYmtWaFN6Qm5ibWhUT1ZOYWFYcDJPRWxyVkdOVU5IZGhRVmxKUzNkWlFrSlJWVWhCVVVWRldFUkNZVTFEV1VkRFEzTkhRVkZWUmtKNlFVSm9hSEJ2WkVoU2QwOXBPSFppTWs1NlkwTTFkMkV5YTNWYU1qbDJXbms1Ym1SSVRubE5WRUYzUW1kbmNrSm5SVVpDVVdOM1FXOVphMkZJVWpCalJHOTJURE5DY21GVE5XNWlNamx1VEROS2JHTkhPSFpaTWxaNVpFaE5kbG96VW5wamFrVjFXa2RXZVUxRVVVZEJNVlZrU0hkUmRFMURjM2RMWVVGdWIwTlhSMGt5YURCa1NFRTJUSGs1YW1OdGQzVmpSM1J3VEcxa2RtSXlZM1phTTFKNlkycEZkbG96VW5wamFrVjFXVE5LYzAxRk1FZEJNVlZrU1VGU1IwMUZVWGREUVZsSFdqUkZUVUZSU1VKTlJHZEhRMmx6UjBGUlVVSXhibXREUWxGTmQwdHFRVzlDWjJkeVFtZEZSa0pSWTBOQlVsbGpZVWhTTUdOSVRUWk1lVGwzWVRKcmRWb3lPWFphZVRsNVdsaENkbU15YkRCaU0wbzFUSHBCVGtKbmEzRm9hMmxIT1hjd1FrRlJjMFpCUVU5RFFXZEZRVWxXVkc5NU1qUnFkMWhWY2pCeVFWQmpPVEkwZG5WVFZtSkxVWFZaZHpOdVRHWnNUR1pNYURWQldWZEZaVlpzTDBSMU1UaFJRVmRWVFdSalNqWnZMM0ZHV21Kb1dHdENTREJRVG1OM09UZDBhR0ZtTWtKbGIwUlpXVGxEYXk5aUsxVkhiSFZvZURBMmVtUTBSVUptTjBnNVVEZzBibTV5ZDNCU0t6UkhRa1JhU3l0WWFETkpNSFJ4U25reWNtZFBjVTVFWm14eU5VbE5VVGhhVkZkQk0zbHNkR0ZyZWxOQ1MxbzJXSEJHTUZCd2NYbERVblp3TDA1RFIzWXlTMWd5VkhWUVEwcDJjMk53TVM5dE1uQldWSFI1UW1wWlVGSlJLMUYxUTFGSFFVcExhblJPTjFJMVJFWnlabFJ4VFZkMldXZFdiSEJEU2tKcmQyeDFOeXMzUzFrelkxUkpabnBGTjJOdFFVeHphMDFMVGt4MVJIb3JVbnBEWTNOWlZITldZVlUzVm5BemVFdzJNRTlaYUhGR2EzVkJUMDk0UkZvMmNFaFBhamtyVDBwdFdXZFFiVTlVTkZnekt6ZE1OVEZtV0VwNVVrZzVTMlpNVWxBMmJsUXpNVVExYm0xelIwRlBaMW95Tmk4NFZEbG9jMEpYTVhWdk9XcDFOV1phVEZwWVZsWlROVWd3U0hsSlFrMUZTM2xIVFVsUWFFWlhjbXgwTDJoR1V6STRUakY2WVV0Sk1GcENSMFF6WjFsblJFeGlhVVJVT1daSFdITjBjR3NyUm0xak5HOXNWbXhYVUhwWVpUZ3hkbVJ2Ulc1R1luSTFUVEkzTWtoa1owcFhieXRYYUZRNVFsbE5NRXBwSzNka1ZtMXVVbVptV0dkc2IwVnZiSFZVVG1OWGVtTTBNV1JHY0dkS2RUaG1Sak5NUnpCbmJESnBZbE5aYVVOcE9XRTJhSFpWTUZSd2NHcEtlVWxYV0doclNsUmpUVXBzVUhKWGVERldlWFJGVlVkeVdESnNNRXBFZDFKcVZ5ODJOVFp5TUV0V1FqQXllRWhTUzNadE1scExTVEF6Vkdkc1RFbHdiVlpEU3pOclFrdHJTMDV3UWs1clJuUTRjbWhoWm1ORFMwOWlPVXA0THpsMGNFNUdiRkZVYkRkQ016bHlTbXhLVjJ0U01UZFJibHB4Vm5CMFJtVlFSazlTYjFwdFJucE5QU0lzSWsxSlNVWlpha05EUWtWeFowRjNTVUpCWjBsUlpEY3dUbUpPY3pJclVuSnhTVkV2UlRoR2FsUkVWRUZPUW1kcmNXaHJhVWM1ZHpCQ1FWRnpSa0ZFUWxoTlVYTjNRMUZaUkZaUlVVZEZkMHBEVWxSRldrMUNZMGRCTVZWRlEyaE5VVkl5ZUhaWmJVWnpWVEpzYm1KcFFuVmthVEY2V1ZSRlVVMUJORWRCTVZWRlEzaE5TRlZ0T1haa1EwSkVVVlJGWWsxQ2EwZEJNVlZGUVhoTlUxSXllSFpaYlVaelZUSnNibUpwUWxOaU1qa3dTVVZPUWsxQ05GaEVWRWwzVFVSWmVFOVVRWGROUkVFd1RXeHZXRVJVU1RSTlJFVjVUMFJCZDAxRVFUQk5iRzkzVW5wRlRFMUJhMGRCTVZWRlFtaE5RMVpXVFhoSmFrRm5RbWRPVmtKQmIxUkhWV1IyWWpKa2MxcFRRbFZqYmxaNlpFTkNWRnBZU2pKaFYwNXNZM2xDVFZSRlRYaEdSRUZUUW1kT1ZrSkJUVlJETUdSVlZYbENVMkl5T1RCSlJrbDRUVWxKUTBscVFVNUNaMnR4YUd0cFJ6bDNNRUpCVVVWR1FVRlBRMEZuT0VGTlNVbERRMmRMUTBGblJVRjBhRVZEYVhnM2FtOVlaV0pQT1hrdmJFUTJNMnhoWkVGUVMwZzVaM1pzT1UxbllVTmpabUl5YWtndk56Wk9kVGhoYVRaWWJEWlBUVk12YTNJNWNrZzFlbTlSWkhObWJrWnNPVGQyZFdaTGFqWmlkMU5wVmpadWNXeExjaXREVFc1NU5sTjRia2RRWWpFMWJDczRRWEJsTmpKcGJUbE5XbUZTZHpGT1JVUlFhbFJ5UlZSdk9HZFpZa1YyY3k5QmJWRXpOVEZyUzFOVmFrSTJSekF3YWpCMVdVOUVVREJuYlVoMU9ERkpPRVV6UTNkdWNVbHBjblUyZWpGcldqRnhLMUJ6UVdWM2JtcEllR2R6U0VFemVUWnRZbGQzV2tSeVdGbG1hVmxoVWxGTk9YTkliV3RzUTJsMFJETTRiVFZoWjBrdmNHSnZVRWRwVlZVck5rUlBiMmR5UmxwWlNuTjFRalpxUXpVeE1YQjZjbkF4V210cU5WcFFZVXMwT1d3NFMwVnFPRU00VVUxQlRGaE1NekpvTjAweFlrdDNXVlZJSzBVMFJYcE9hM1JOWnpaVVR6aFZjRzEyVFhKVmNITjVWWEYwUldvMVkzVklTMXBRWm0xbmFFTk9Oa296UTJsdmFqWlBSMkZMTDBkUU5VRm1iRFF2V0hSalpDOXdNbWd2Y25Nek4wVlBaVnBXV0hSTU1HMDNPVmxDTUdWelYwTnlkVTlETjFoR2VGbHdWbkU1VDNNMmNFWk1TMk4zV25CRVNXeFVhWEo0V2xWVVVVRnpObkY2YTIwd05uQTVPR2MzUWtGbEsyUkVjVFprYzI4ME9UbHBXVWcyVkV0WUx6RlpOMFI2YTNabmRHUnBlbXByV0ZCa2MwUjBVVU4yT1ZWM0szZHdPVlUzUkdKSFMyOW5VR1ZOWVROTlpDdHdkbVY2TjFjek5VVnBSWFZoS3l0MFoza3ZRa0pxUmtaR2VUTnNNMWRHY0U4NVMxZG5lamQ2Y0cwM1FXVkxTblE0VkRFeFpHeGxRMlpsV0d0clZVRkxTVUZtTlhGdlNXSmhjSE5hVjNkd1ltdE9SbWhJWVhneWVFbFFSVVJuWm1jeFlYcFdXVGd3V21OR2RXTjBURGRVYkV4dVRWRXZNR3hWVkdKcFUzY3hia2cyT1UxSE5ucFBNR0k1WmpaQ1VXUm5RVzFFTURaNVN6VTJiVVJqV1VKYVZVTkJkMFZCUVdGUFEwRlVaM2RuWjBVd1RVRTBSMEV4VldSRWQwVkNMM2RSUlVGM1NVSm9ha0ZRUW1kT1ZraFNUVUpCWmpoRlFsUkJSRUZSU0M5TlFqQkhRVEZWWkVSblVWZENRbFJyY25semJXTlNiM0pUUTJWR1RERktiVXhQTDNkcFVrNTRVR3BCWmtKblRsWklVMDFGUjBSQlYyZENVbWRsTWxsaFVsRXlXSGx2YkZGTU16QkZlbFJUYnk4dmVqbFRla0puUW1kbmNrSm5SVVpDVVdOQ1FWRlNWVTFHU1hkS1VWbEpTM2RaUWtKUlZVaE5RVWRIUjFkb01HUklRVFpNZVRsMldUTk9kMHh1UW5KaFV6VnVZakk1Ymt3eVpIcGpha1YzUzFGWlNVdDNXVUpDVVZWSVRVRkxSMGhYYURCa1NFRTJUSGs1ZDJFeWEzVmFNamwyV25rNWJtTXpTWGhNTW1SNlkycEZkVmt6U2pCTlJFbEhRVEZWWkVoM1VYSk5RMnQzU2paQmJHOURUMGRKVjJnd1pFaEJOa3g1T1dwamJYZDFZMGQwY0V4dFpIWmlNbU4yV2pOT2VVMVRPVzVqTTBsNFRHMU9lV0pFUVRkQ1owNVdTRk5CUlU1RVFYbE5RV2RIUW0xbFFrUkJSVU5CVkVGSlFtZGFibWRSZDBKQlowbDNSRkZaVEV0M1dVSkNRVWhYWlZGSlJrRjNTWGRFVVZsTVMzZFpRa0pCU0ZkbFVVbEdRWGROZDBSUldVcExiMXBKYUhaalRrRlJSVXhDVVVGRVoyZEZRa0ZFVTJ0SWNrVnZiemxETUdSb1pXMU5XRzlvTm1SR1UxQnphbUprUWxwQ2FVeG5PVTVTTTNRMVVDdFVORlo0Wm5FM2RuRm1UUzlpTlVFelVta3habmxLYlRsaWRtaGtSMkZLVVROaU1uUTJlVTFCV1U0dmIyeFZZWHB6WVV3cmVYbEZiamxYY0hKTFFWTlBjMmhKUVhKQmIzbGFiQ3QwU21GdmVERXhPR1psYzNOdFdHNHhhRWxXZHpReGIyVlJZVEYyTVhabk5FWjJOelI2VUd3MkwwRm9VM0ozT1ZVMWNFTmFSWFEwVjJrMGQxTjBlalprVkZvdlEweEJUbmc0VEZwb01VbzNVVXBXYWpKbWFFMTBabFJLY2psM05Ib3pNRm95TURsbVQxVXdhVTlOZVN0eFpIVkNiWEIyZGxsMVVqZG9Xa3cyUkhWd2MzcG1ibmN3VTJ0bWRHaHpNVGhrUnpsYVMySTFPVlZvZG0xaFUwZGFVbFppVGxGd2MyY3pRbHBzZG1sa01HeEpTMDh5WkRGNGIzcGpiRTk2WjJwWVVGbHZka3BLU1hWc2RIcHJUWFV6TkhGUllqbFRlaTk1YVd4eVlrTm5hamc5SWwxOS5leUp1YjI1alpTSTZJbko1VFZZMVRpdEpTVzlPYzBnNE9YTk1NbXhCWkRKRWIxaEVUMFZaVFRsQlZGQjJSblJuZW1KVGIwMDlJaXdpZEdsdFpYTjBZVzF3VFhNaU9qRTJORFEzTXpnek9EVTNPVFFzSW1Gd2ExQmhZMnRoWjJWT1lXMWxJam9pWTI5dExtZHZiMmRzWlM1aGJtUnliMmxrTG1kdGN5SXNJbUZ3YTBScFoyVnpkRk5vWVRJMU5pSTZJbVpaUlVSV2VVbHFPRFJ4V2xkd1dXazBRMUJ6VlU4MlN6aHVZbU5RWWs0dlkwczJXREl3UTJSM09GVTlJaXdpWTNSelVISnZabWxzWlUxaGRHTm9JanAwY25WbExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUk0VURGelZ6QkZVRXBqYzJ4M04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYTnBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpTd2laWFpoYkhWaGRHbHZibFI1Y0dVaU9pSkNRVk5KUXl4SVFWSkVWMEZTUlY5Q1FVTkxSVVFpZlEuYXZwSHpzT2VCUlEydUVLLXdNc2oyam5BX19iY19nd2dWYTladlAxMGhrbC1fYVZYb2I5aF9PN2JwTlpZRWR6VjI1VVR4X1BQRzFPMHpiNG9oLUo0TDZwam0yMGZZclRXTndZeGJaLWxYamRZcW1YWmsybkxLMnJTWkZNOWxyVTJGOXJvOUdSOEtsN3JzenpxazBQa3N1NkFybzgtRTRlWGoxQ3ZGYnB6cEQ1VUVZeXp0M0JaUE9KWTZYVVU1LXd2azV1UFl2OWhCeG5jNEdPYXdRelJiY3l3Ukh6N2g1NWMwV2dqVUNpOFc2SD04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYTnBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpTd2laWFpoYkhWaGRHbHZibFI1Y0dVaU9pSkNRVk5KUXl4SVFWSkVWMEZTUlY5Q1FVTkxSVVFpZlEuYXZwSHpzT2VCUlEydUVLLXdNc2oyam5BX19iY19nd2dWYTladlAxMGhrbC1fYVZYb2I5aF9PN2JwTlpZRWR6VjI1VVR4X1BQRzFPMHpiNG9oLUo0TDZwam0yMGZZclRXTndZeGJaLWxYamRZcW1YWmsybkxLMnJTWkZNOWxyVTJGOXJvOUdSOEtsN3JzenpxazBQa3N1NkFybzgtRTRlWGoxQ3ZGYnB6cEQ1VUVZeXp0M0JaUE9KWTZYVVU1LXd2azV1UFl2OWhCeG5jNEdPYXdRelJiY3l3Ukh6N2g1NWMwV2dqVUNpOFc2SD04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZjhXQnNVZmduX3BScyIsIm9yaWdpbiI6Imh0dHBzOlwvXC93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uYW5kcm9pZC5jaHJvbWUifQ"
3239 },
3240 "type": "public-key"
3241 }"#).unwrap();
3242
3243 debug!("{:?}", rsp_d);
3244
3245 let result = wan.register_credential_internal(
3246 &rsp_d,
3247 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3248 &chal,
3249 &[],
3250 &[COSEAlgorithm::ES256],
3251 None,
3252 false,
3253 &RequestRegistrationExtensions::default(),
3254 false,
3255 );
3256 debug!("{:?}", result);
3257 assert!(result.is_err());
3259 }
3260
3261 #[test]
3262 fn test_google_pixel_3a_none_attestation() {
3263 let _ = tracing_subscriber::fmt::try_init();
3264 let wan = Webauthn::new_unsafe_experts_only(
3265 "https://webauthn.firstyear.id.au",
3266 "webauthn.firstyear.id.au",
3267 vec![Url::parse("https://webauthn.firstyear.id.au").unwrap()],
3268 AUTHENTICATOR_TIMEOUT,
3269 None,
3270 None,
3271 );
3272
3273 let chal: HumanBinaryData =
3274 serde_json::from_str("\"55Wztjbgks9UkS5jYthawNFik0HSiYuCSB5pzNbT6k0\"").unwrap();
3275 let chal = Challenge::from(chal);
3276
3277 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{
3278 "id": "AfzEi3UOVveYjwUwIFO3QuN9V0fomECvAYrD_8S5FAsUJqtGbwpgB9bEfphVOURzFQoEszkuULIj5fMvnTkt6cs",
3279 "rawId": "AfzEi3UOVveYjwUwIFO3QuN9V0fomECvAYrD_8S5FAsUJqtGbwpgB9bEfphVOURzFQoEszkuULIj5fMvnTkt6cs",
3280 "response": {
3281 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFarm78N-aFvkduzO7sTL6-dF8eCxIJsbscOzuWNl-9SpFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQH8xIt1Dlb3mI8FMCBTt0LjfVdH6JhArwGKw__EuRQLFCarRm8KYAfWxH6YVTlEcxUKBLM5LlCyI-XzL505LenLpQECAyYgASFYII2OFisY2sjerzLYjLYvHsQh8V7cnpRcSL4A77wKqcRTIlggm7s0CUKEmkBBFp7Nng-9_pZ5Dm9y39uy6QJmDLgmgho",
3282 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiNTVXenRqYmdrczlVa1M1all0aGF3TkZpazBIU2lZdUNTQjVwek5iVDZrMCIsIm9yaWdpbiI6Imh0dHBzOlwvXC93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uYW5kcm9pZC5jaHJvbWUifQ"
3283 },
3284 "type": "public-key"
3285 }"#).unwrap();
3286
3287 debug!("{:?}", rsp_d);
3288
3289 let result = wan.register_credential_internal(
3290 &rsp_d,
3291 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3292 &chal,
3293 &[],
3294 &[COSEAlgorithm::ES256],
3295 None,
3296 false,
3297 &RequestRegistrationExtensions::default(),
3298 true,
3299 );
3300 debug!("{:?}", result);
3301 assert!(result.is_ok());
3302 }
3303
3304 #[test]
3305 fn test_google_pixel_3a_ignores_requested_algo() {
3306 let _ = tracing_subscriber::fmt::try_init();
3307 let wan = Webauthn::new_unsafe_experts_only(
3308 "https://webauthn.firstyear.id.au",
3309 "webauthn.firstyear.id.au",
3310 vec![Url::parse("https://webauthn.firstyear.id.au").unwrap()],
3311 AUTHENTICATOR_TIMEOUT,
3312 None,
3313 None,
3314 );
3315
3316 let chal: HumanBinaryData =
3317 serde_json::from_str("\"t_We131NpwllyPL0x26bzZgkF5f_XvA7Ocb4b98zlxM\"").unwrap();
3318 let chal = Challenge::from(chal);
3319
3320 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{
3321 "id": "AfJfonHsXY_f7_gFmV1dI473Ce--_g0tHhdXUoh7JmMn0gzhYUtU9bFqpCgSljjwJxEXkjzb-11ulePZyI0RiyQ",
3322 "rawId": "AfJfonHsXY_f7_gFmV1dI473Ce--_g0tHhdXUoh7JmMn0gzhYUtU9bFqpCgSljjwJxEXkjzb-11ulePZyI0RiyQ",
3323 "response": {
3324 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFarm78N-aFvkduzO7sTL6-dF8eCxIJsbscOzuWNl-9SpFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHyX6Jx7F2P3-_4BZldXSOO9wnvvv4NLR4XV1KIeyZjJ9IM4WFLVPWxaqQoEpY48CcRF5I82_tdbpXj2ciNEYskpQECAyYgASFYIE_9awy66uhXZ6hIzPAW2AzIrTMZ7kyC2jtZe0zuH_pOIlggFbNKhOSt8-prIx0snKRqcxULtc2u1rzUUf47g1PxTcU",
3325 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidF9XZTEzMU5wd2xseVBMMHgyNmJ6WmdrRjVmX1h2QTdPY2I0Yjk4emx4TSIsIm9yaWdpbiI6Imh0dHBzOlwvXC93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJvcmcubW96aWxsYS5maXJlZm94In0"
3326 },
3327 "type": "public-key"
3328 }"#).unwrap();
3329
3330 debug!("{:?}", rsp_d);
3331
3332 let result = wan.register_credential_internal(
3333 &rsp_d,
3334 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3335 &chal,
3336 &[],
3337 &[
3338 COSEAlgorithm::RS256,
3339 COSEAlgorithm::EDDSA,
3340 COSEAlgorithm::INSECURE_RS1,
3341 ],
3342 None,
3343 false,
3344 &RequestRegistrationExtensions::default(),
3345 false,
3346 );
3347 debug!("{:?}", result);
3348 assert!(result.is_err());
3349 }
3350
3351 #[test]
3353 fn test_firefox_98_hello_incorrectly_truncates_aaguid() {
3354 let _ = tracing_subscriber::fmt::try_init();
3355 let wan = Webauthn::new_unsafe_experts_only(
3356 "https://webauthn.firstyear.id.au",
3357 "webauthn.firstyear.id.au",
3358 vec![Url::parse("https://webauthn.firstyear.id.au").unwrap()],
3359 AUTHENTICATOR_TIMEOUT,
3360 None,
3361 None,
3362 );
3363
3364 let chal: HumanBinaryData =
3365 serde_json::from_str("\"FKVseWmr5DxQ_H9iTyoTgRPIClLspXO0XbOKQfMuaFc\"").unwrap();
3366 let chal = Challenge::from(chal);
3367
3368 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{
3369 "id": "6h7wVk2n4Buulhd5fiShGb0BBViIgvDoVO3xhn0A0Mg",
3370 "rawId": "6h7wVk2n4Buulhd5fiShGb0BBViIgvDoVO3xhn0A0Mg",
3371 "response": {
3372 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBWGq5u_Dfmhb5Hbszu7Ey-vnRfHgsSCbG7HDs7ljZfvUqRQAAAAAAACDqHvBWTafgG66WF3l-JKEZvQEFWIiC8OhU7fGGfQDQyKQBAwM5AQAgWQEAt86lR2w_hmnhDr6tvJD5hmIuWt0QkG1sphC8aqeOHuIWnbcBWnxNUrKQibJxEGJilM20s-_w-aUjDoV5MYu4NBgguFHju-qA-qe1sjhqY7UkMkx4Z1KGMeiZNNGgk5Gtmu0xjaq-1RohB3TKADeWTularHWzG6q6sJHgC-qKKa67Rmwr0T4a4S3VjLvjvSPILx88nLJvwqO1rDb5cLOgL5CEjtRijR6SNeN05uBhz2ePn5mMo2lN73pHsMGPo68pGWIWWsb2sC_aBF2eA02Me2jldIgSzMy3y8xsTIg6r_xF105pC8jOPsQVN2TJDxN9zVEuxpY_mUsqGOAFGR-SiyFDAQAB",
3373 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJGS1ZzZVdtcjVEeFFfSDlpVHlvVGdSUElDbExzcFhPMFhiT0tRZk11YUZjIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0"
3374 },
3375 "type": "public-key"
3376 }"#).unwrap();
3377
3378 debug!("{:?}", rsp_d);
3379
3380 let result = wan.register_credential_internal(
3381 &rsp_d,
3382 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3383 &chal,
3384 &[],
3385 &[
3386 COSEAlgorithm::RS256,
3387 COSEAlgorithm::EDDSA,
3388 COSEAlgorithm::INSECURE_RS1,
3389 ],
3390 None,
3391 false,
3392 &RequestRegistrationExtensions::default(),
3393 false,
3394 );
3395 debug!("{:?}", result);
3396 assert!(matches!(result, Err(WebauthnError::ParseNOMFailure)));
3397 }
3398
3399 #[test]
3400 fn test_edge_touchid_rk_verified() {
3401 let _ = tracing_subscriber::fmt::try_init();
3402 let wan = Webauthn::new_unsafe_experts_only(
3403 "http://localhost:8080/auth",
3404 "localhost",
3405 vec![Url::parse("http://localhost:8080").unwrap()],
3406 AUTHENTICATOR_TIMEOUT,
3407 None,
3408 None,
3409 );
3410
3411 let chal: HumanBinaryData = HumanBinaryData::from(vec![
3412 108, 33, 62, 167, 162, 234, 36, 63, 176, 231, 161, 58, 41, 233, 117, 157, 210, 244,
3413 123, 28, 194, 100, 34, 68, 32, 1, 183, 240, 100, 225, 182, 48,
3414 ]);
3415 let chal = Challenge::from(chal);
3416
3417 let rsp_d: RegisterPublicKeyCredential = RegisterPublicKeyCredential {
3418 id: "AWtT-NSYHNmZjP2R9JAbBmwf3sWMxs_L4_O2XoIvI8HY-rGPjA".to_string(),
3419 raw_id: Base64UrlSafeData::from(vec![
3420 1, 107, 83, 248, 212, 152, 28, 217, 153, 140, 253, 145, 244, 144, 27, 6, 108, 31,
3421 222, 197, 140, 198, 207, 203, 227, 243, 182, 94, 130, 47, 35, 193, 216, 250, 177,
3422 143, 140,
3423 ]),
3424 response: AuthenticatorAttestationResponseRaw {
3425 attestation_object: Base64UrlSafeData::from(vec![
3426 163, 99, 102, 109, 116, 102, 112, 97, 99, 107, 101, 100, 103, 97, 116, 116, 83,
3427 116, 109, 116, 162, 99, 97, 108, 103, 38, 99, 115, 105, 103, 88, 72, 48, 70, 2,
3428 33, 0, 234, 66, 128, 149, 10, 78, 90, 6, 183, 58, 163, 114, 112, 146, 47, 204,
3429 176, 27, 86, 218, 77, 135, 121, 88, 40, 94, 115, 7, 221, 248, 13, 37, 2, 33, 0,
3430 187, 63, 74, 17, 114, 129, 51, 239, 145, 128, 216, 117, 39, 191, 130, 6, 239,
3431 79, 15, 80, 58, 52, 18, 24, 57, 174, 125, 198, 248, 46, 138, 177, 104, 97, 117,
3432 116, 104, 68, 97, 116, 97, 88, 169, 73, 150, 13, 229, 136, 14, 140, 104, 116,
3433 52, 23, 15, 100, 118, 96, 91, 143, 228, 174, 185, 162, 134, 50, 199, 153, 92,
3434 243, 186, 131, 29, 151, 99, 69, 98, 76, 219, 31, 173, 206, 0, 2, 53, 188, 198,
3435 10, 100, 139, 11, 37, 241, 240, 85, 3, 0, 37, 1, 107, 83, 248, 212, 152, 28,
3436 217, 153, 140, 253, 145, 244, 144, 27, 6, 108, 31, 222, 197, 140, 198, 207,
3437 203, 227, 243, 182, 94, 130, 47, 35, 193, 216, 250, 177, 143, 140, 165, 1, 2,
3438 3, 38, 32, 1, 33, 88, 32, 143, 255, 51, 238, 28, 38, 130, 245, 24, 48, 164,
3439 117, 49, 102, 142, 103, 25, 46, 253, 137, 228, 16, 220, 131, 17, 229, 52, 165,
3440 75, 224, 218, 237, 34, 88, 32, 115, 152, 43, 120, 40, 171, 135, 110, 112, 253,
3441 28, 142, 154, 9, 9, 149, 94, 254, 147, 235, 38, 4, 215, 26, 217, 51, 245, 151,
3442 148, 192, 141, 169,
3443 ]),
3444 client_data_json: Base64UrlSafeData::from(vec![
3445 123, 34, 116, 121, 112, 101, 34, 58, 34, 119, 101, 98, 97, 117, 116, 104, 110,
3446 46, 99, 114, 101, 97, 116, 101, 34, 44, 34, 99, 104, 97, 108, 108, 101, 110,
3447 103, 101, 34, 58, 34, 98, 67, 69, 45, 112, 54, 76, 113, 74, 68, 45, 119, 53,
3448 54, 69, 54, 75, 101, 108, 49, 110, 100, 76, 48, 101, 120, 122, 67, 90, 67, 74,
3449 69, 73, 65, 71, 51, 56, 71, 84, 104, 116, 106, 65, 34, 44, 34, 111, 114, 105,
3450 103, 105, 110, 34, 58, 34, 104, 116, 116, 112, 58, 47, 47, 108, 111, 99, 97,
3451 108, 104, 111, 115, 116, 58, 56, 48, 56, 48, 34, 44, 34, 99, 114, 111, 115,
3452 115, 79, 114, 105, 103, 105, 110, 34, 58, 102, 97, 108, 115, 101, 125,
3453 ]),
3454 transports: None,
3455 },
3456 type_: "public-key".to_string(),
3457 extensions: RegistrationExtensionsClientOutputs::default(),
3458 };
3459
3460 debug!("{:?}", rsp_d);
3461
3462 let result = wan.register_credential_internal(
3463 &rsp_d,
3464 UserVerificationPolicy::Required,
3465 &chal,
3466 &[],
3467 &[COSEAlgorithm::ES256],
3468 None,
3469 true,
3473 &RequestRegistrationExtensions::default(),
3474 true,
3475 );
3476 debug!("{:?}", result);
3477 let cred = result.unwrap();
3478 assert!(matches!(
3479 cred.attestation.data,
3480 ParsedAttestationData::Self_
3481 ));
3482 }
3483
3484 #[test]
3485 fn test_google_safetynet() {
3486 #[allow(unused)]
3487 let _request = r#"{"publicKey": {
3488 "challenge": "dfo+HlqJp3MLK+J5TLxxmvXJieS3zGwdk9G9H9bPezg=",
3489 "rp": {
3490 "name": "webauthn.io",
3491 "id": "webauthn.io"
3492 },
3493 "user": {
3494 "name": "safetynetter",
3495 "displayName": "safetynetter",
3496 "id": "wDkAAAAAAAAAAA=="
3497 },
3498 "pubKeyCredParams": [
3499 {
3500 "type": "public-key",
3501 "alg": -7
3502 }
3503 ],
3504 "authenticatorSelection": {
3505 "authenticatorAttachment": "platform",
3506 "userVerification": "preferred"
3507 },
3508 "timeout": 60000,
3509 "attestation": "direct"
3510 }
3511 }"#;
3512
3513 let response = r#"{
3514 "id":"AUiVU3Mk3uJomfHcJcu6ScwUHRysE2e6IgaTNAzQ34TP0OPifi2LgGD_5hzxRhOfQTB1fW6k63C8tk-MwywpNVI",
3515 "rawId":"AUiVU3Mk3uJomfHcJcu6ScwUHRysE2e6IgaTNAzQ34TP0OPifi2LgGD_5hzxRhOfQTB1fW6k63C8tk-MwywpNVI",
3516 "type":"public-key",
3517 "response":{
3518 "attestationObject":"o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDE1MTgwMDM3aHJlc3BvbnNlWRS9ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxHYTJwRFEwSkljV2RCZDBsQ1FXZEpVVkpZY205T01GcFBaRkpyUWtGQlFVRkJRVkIxYm5wQlRrSm5hM0ZvYTJsSE9YY3dRa0ZSYzBaQlJFSkRUVkZ6ZDBOUldVUldVVkZIUlhkS1ZsVjZSV1ZOUW5kSFFURlZSVU5vVFZaU01qbDJXako0YkVsR1VubGtXRTR3U1VaT2JHTnVXbkJaTWxaNlRWSk5kMFZSV1VSV1VWRkVSWGR3U0ZaR1RXZFJNRVZuVFZVNGVFMUNORmhFVkVVMFRWUkJlRTFFUVROTlZHc3dUbFp2V0VSVVJUVk5WRUYzVDFSQk0wMVVhekJPVm05M1lrUkZURTFCYTBkQk1WVkZRbWhOUTFaV1RYaEZla0ZTUW1kT1ZrSkJaMVJEYTA1b1lrZHNiV0l6U25WaFYwVjRSbXBCVlVKblRsWkNRV05VUkZVeGRtUlhOVEJaVjJ4MVNVWmFjRnBZWTNoRmVrRlNRbWRPVmtKQmIxUkRhMlIyWWpKa2MxcFRRazFVUlUxNFIzcEJXa0puVGxaQ1FVMVVSVzFHTUdSSFZucGtRelZvWW0xU2VXSXliR3RNYlU1MllsUkRRMEZUU1hkRVVWbEtTMjlhU1doMlkwNUJVVVZDUWxGQlJHZG5SVkJCUkVORFFWRnZRMmRuUlVKQlRtcFlhM293WlVzeFUwVTBiU3N2UnpWM1QyOHJXRWRUUlVOeWNXUnVPRGh6UTNCU04yWnpNVFJtU3pCU2FETmFRMWxhVEVaSWNVSnJOa0Z0V2xaM01rczVSa2N3VHpseVVsQmxVVVJKVmxKNVJUTXdVWFZ1VXpsMVowaEROR1ZuT1c5MmRrOXRLMUZrV2pKd09UTllhSHAxYmxGRmFGVlhXRU40UVVSSlJVZEtTek5UTW1GQlpucGxPVGxRVEZNeU9XaE1ZMUYxV1ZoSVJHRkROMDlhY1U1dWIzTnBUMGRwWm5NNGRqRnFhVFpJTDNob2JIUkRXbVV5YkVvck4wZDFkSHBsZUV0d2VIWndSUzkwV2xObVlsazVNRFZ4VTJ4Q2FEbG1jR293TVRWamFtNVJSbXRWYzBGVmQyMUxWa0ZWZFdWVmVqUjBTMk5HU3pSd1pYWk9UR0Y0UlVGc0swOXJhV3hOZEVsWlJHRmpSRFZ1Wld3MGVFcHBlWE0wTVROb1lXZHhWekJYYUdnMVJsQXpPV2hIYXpsRkwwSjNVVlJxWVhwVGVFZGtkbGd3YlRaNFJsbG9hQzh5VmsxNVdtcFVORXQ2VUVwRlEwRjNSVUZCWVU5RFFXeG5kMmRuU2xWTlFUUkhRVEZWWkVSM1JVSXZkMUZGUVhkSlJtOUVRVlJDWjA1V1NGTlZSVVJFUVV0Q1oyZHlRbWRGUmtKUlkwUkJWRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRVTFDTUVkQk1WVmtSR2RSVjBKQ1VYRkNVWGRIVjI5S1FtRXhiMVJMY1hWd2J6UlhObmhVTm1veVJFRm1RbWRPVmtoVFRVVkhSRUZYWjBKVFdUQm1hSFZGVDNaUWJTdDRaMjU0YVZGSE5rUnlabEZ1T1V0NlFtdENaMmR5UW1kRlJrSlJZMEpCVVZKWlRVWlpkMHAzV1VsTGQxbENRbEZWU0UxQlIwZEhNbWd3WkVoQk5reDVPWFpaTTA1M1RHNUNjbUZUTlc1aU1qbHVUREprTUdONlJuWk5WRUZ5UW1kbmNrSm5SVVpDVVdOM1FXOVpabUZJVWpCalJHOTJURE5DY21GVE5XNWlNamx1VERKa2VtTnFTWFpTTVZKVVRWVTRlRXh0VG5sa1JFRmtRbWRPVmtoU1JVVkdha0ZWWjJoS2FHUklVbXhqTTFGMVdWYzFhMk50T1hCYVF6VnFZakl3ZDBsUldVUldVakJuUWtKdmQwZEVRVWxDWjFwdVoxRjNRa0ZuU1hkRVFWbExTM2RaUWtKQlNGZGxVVWxHUVhwQmRrSm5UbFpJVWpoRlMwUkJiVTFEVTJkSmNVRm5hR2cxYjJSSVVuZFBhVGgyV1ROS2MweHVRbkpoVXpWdVlqSTVia3d3WkZWVmVrWlFUVk0xYW1OdGQzZG5aMFZGUW1kdmNrSm5SVVZCWkZvMVFXZFJRMEpKU0RGQ1NVaDVRVkJCUVdSM1EydDFVVzFSZEVKb1dVWkpaVGRGTmt4TldqTkJTMUJFVjFsQ1VHdGlNemRxYW1RNE1FOTVRVE5qUlVGQlFVRlhXbVJFTTFCTVFVRkJSVUYzUWtsTlJWbERTVkZEVTFwRFYyVk1Tblp6YVZaWE5rTm5LMmRxTHpsM1dWUktVbnAxTkVocGNXVTBaVmswWXk5dGVYcHFaMGxvUVV4VFlta3ZWR2g2WTNweGRHbHFNMlJyTTNaaVRHTkpWek5NYkRKQ01HODNOVWRSWkdoTmFXZGlRbWRCU0ZWQlZtaFJSMjFwTDFoM2RYcFVPV1ZIT1ZKTVNTdDRNRm95ZFdKNVdrVldla0UzTlZOWlZtUmhTakJPTUVGQlFVWnRXRkU1ZWpWQlFVRkNRVTFCVW1wQ1JVRnBRbU5EZDBFNWFqZE9WRWRZVURJM09IbzBhSEl2ZFVOSWFVRkdUSGx2UTNFeVN6QXJlVXhTZDBwVlltZEpaMlk0WjBocWRuQjNNbTFDTVVWVGFuRXlUMll6UVRCQlJVRjNRMnR1UTJGRlMwWlZlVm8zWmk5UmRFbDNSRkZaU2t0dldrbG9kbU5PUVZGRlRFSlJRVVJuWjBWQ1FVazVibFJtVWt0SlYyZDBiRmRzTTNkQ1REVTFSVlJXTm10aGVuTndhRmN4ZVVGak5VUjFiVFpZVHpReGExcDZkMG8yTVhkS2JXUlNVbFF2VlhORFNYa3hTMFYwTW1Nd1JXcG5iRzVLUTBZeVpXRjNZMFZYYkV4UldUSllVRXg1Um1wclYxRk9ZbE5vUWpGcE5GY3lUbEpIZWxCb2RETnRNV0kwT1doaWMzUjFXRTAyZEZnMVEzbEZTRzVVYURoQ2IyMDBMMWRzUm1sb2VtaG5iamd4Ukd4a2IyZDZMMHN5VlhkTk5sTTJRMEl2VTBWNGEybFdabllyZW1KS01ISnFkbWM1TkVGc1pHcFZabFYzYTBrNVZrNU5ha1ZRTldVNGVXUkNNMjlNYkRabmJIQkRaVVkxWkdkbVUxZzBWVGw0TXpWdmFpOUpTV1F6VlVVdlpGQndZaTl4WjBkMmMydG1aR1Y2ZEcxVmRHVXZTMU50Y21sM1kyZFZWMWRsV0daVVlra3plbk5wYTNkYVltdHdiVkpaUzIxcVVHMW9kalJ5YkdsNlIwTkhkRGhRYmpod2NUaE5Na3RFWmk5UU0ydFdiM1F6WlRFNFVUMGlMQ0pOU1VsRlUycERRMEY2UzJkQmQwbENRV2RKVGtGbFR6QnRjVWRPYVhGdFFrcFhiRkYxUkVGT1FtZHJjV2hyYVVjNWR6QkNRVkZ6UmtGRVFrMU5VMEYzU0dkWlJGWlJVVXhGZUdSSVlrYzVhVmxYZUZSaFYyUjFTVVpLZG1JelVXZFJNRVZuVEZOQ1UwMXFSVlJOUWtWSFFURlZSVU5vVFV0U01uaDJXVzFHYzFVeWJHNWlha1ZVVFVKRlIwRXhWVVZCZUUxTFVqSjRkbGx0Um5OVk1teHVZbXBCWlVaM01IaE9la0V5VFZSVmQwMUVRWGRPUkVwaFJuY3dlVTFVUlhsTlZGVjNUVVJCZDA1RVNtRk5SVWw0UTNwQlNrSm5UbFpDUVZsVVFXeFdWRTFTTkhkSVFWbEVWbEZSUzBWNFZraGlNamx1WWtkVloxWklTakZqTTFGblZUSldlV1J0YkdwYVdFMTRSWHBCVWtKblRsWkNRVTFVUTJ0a1ZWVjVRa1JSVTBGNFZIcEZkMmRuUldsTlFUQkhRMU54UjFOSllqTkVVVVZDUVZGVlFVRTBTVUpFZDBGM1oyZEZTMEZ2U1VKQlVVUlJSMDA1UmpGSmRrNHdOWHByVVU4NUszUk9NWEJKVW5aS2VucDVUMVJJVnpWRWVrVmFhRVF5WlZCRGJuWlZRVEJSYXpJNFJtZEpRMlpMY1VNNVJXdHpRelJVTW1aWFFsbHJMMnBEWmtNelVqTldXazFrVXk5a1RqUmFTME5GVUZwU2NrRjZSSE5wUzFWRWVsSnliVUpDU2pWM2RXUm5lbTVrU1UxWlkweGxMMUpIUjBac05YbFBSRWxMWjJwRmRpOVRTa2d2VlV3clpFVmhiSFJPTVRGQ2JYTkxLMlZSYlUxR0t5dEJZM2hIVG1oeU5UbHhUUzg1YVd3M01Va3laRTQ0UmtkbVkyUmtkM1ZoWldvMFlsaG9jREJNWTFGQ1ltcDRUV05KTjBwUU1HRk5NMVEwU1N0RWMyRjRiVXRHYzJKcWVtRlVUa001ZFhwd1JteG5UMGxuTjNKU01qVjRiM2x1VlhoMk9IWk9iV3R4TjNwa1VFZElXR3Q0VjFrM2IwYzVhaXRLYTFKNVFrRkNhemRZY2twbWIzVmpRbHBGY1VaS1NsTlFhemRZUVRCTVMxY3dXVE42Tlc5Nk1rUXdZekYwU2t0M1NFRm5UVUpCUVVkcVoyZEZlazFKU1VKTWVrRlBRbWRPVmtoUk9FSkJaamhGUWtGTlEwRlpXWGRJVVZsRVZsSXdiRUpDV1hkR1FWbEpTM2RaUWtKUlZVaEJkMFZIUTBOelIwRlJWVVpDZDAxRFRVSkpSMEV4VldSRmQwVkNMM2RSU1UxQldVSkJaamhEUVZGQmQwaFJXVVJXVWpCUFFrSlpSVVpLYWxJclJ6UlJOamdyWWpkSFEyWkhTa0ZpYjA5ME9VTm1NSEpOUWpoSFFURlZaRWwzVVZsTlFtRkJSa3AyYVVJeFpHNUlRamRCWVdkaVpWZGlVMkZNWkM5alIxbFpkVTFFVlVkRFEzTkhRVkZWUmtKM1JVSkNRMnQzU25wQmJFSm5aM0pDWjBWR1FsRmpkMEZaV1ZwaFNGSXdZMFJ2ZGt3eU9XcGpNMEYxWTBkMGNFeHRaSFppTW1OMldqTk9lVTFxUVhsQ1owNVdTRkk0UlV0NlFYQk5RMlZuU21GQmFtaHBSbTlrU0ZKM1QyazRkbGt6U25OTWJrSnlZVk0xYm1JeU9XNU1NbVI2WTJwSmRsb3pUbmxOYVRWcVkyMTNkMUIzV1VSV1VqQm5Ra1JuZDA1cVFUQkNaMXB1WjFGM1FrRm5TWGRMYWtGdlFtZG5ja0puUlVaQ1VXTkRRVkpaWTJGSVVqQmpTRTAyVEhrNWQyRXlhM1ZhTWpsMlduazVlVnBZUW5aak1td3dZak5LTlV4NlFVNUNaMnR4YUd0cFJ6bDNNRUpCVVhOR1FVRlBRMEZSUlVGSGIwRXJUbTV1TnpoNU5uQlNhbVE1V0d4UlYwNWhOMGhVWjJsYUwzSXpVazVIYTIxVmJWbElVRkZ4TmxOamRHazVVRVZoYW5aM1VsUXlhVmRVU0ZGeU1ESm1aWE54VDNGQ1dUSkZWRlYzWjFwUksyeHNkRzlPUm5ab2MwODVkSFpDUTA5SllYcHdjM2RYUXpsaFNqbDRhblUwZEZkRVVVZzRUbFpWTmxsYVdpOVlkR1ZFVTBkVk9WbDZTbkZRYWxrNGNUTk5SSGh5ZW0xeFpYQkNRMlkxYnpodGR5OTNTalJoTWtjMmVIcFZjalpHWWpaVU9FMWpSRTh5TWxCTVVrdzJkVE5OTkZSNmN6TkJNazB4YWpaaWVXdEtXV2s0ZDFkSlVtUkJka3RNVjFwMUwyRjRRbFppZWxsdGNXMTNhMjAxZWt4VFJGYzFia2xCU21KRlRFTlJRMXAzVFVnMU5uUXlSSFp4YjJaNGN6WkNRbU5EUmtsYVZWTndlSFUyZURaMFpEQldOMU4yU2tORGIzTnBjbE50U1dGMGFpODVaRk5UVmtSUmFXSmxkRGh4THpkVlN6UjJORnBWVGpnd1lYUnVXbm94ZVdjOVBTSmRmUS5leUp1YjI1alpTSTZJazlGTDJkV09FYzRXazFKTW1ORUsyRk1lRzB2VGt4a1dVMHdjemxsVDB0V1NYUlhOblZTVDI5d1prRTlJaXdpZEdsdFpYTjBZVzF3VFhNaU9qRTFOVE13TWpnd05ETTFNamtzSW1Gd2ExQmhZMnRoWjJWT1lXMWxJam9pWTI5dExtZHZiMmRzWlM1aGJtUnliMmxrTG1kdGN5SXNJbUZ3YTBScFoyVnpkRk5vWVRJMU5pSTZJbGRVYkd4aVVuVXhZbFEyYlZoeWRXRmlXVWQ1WmtvMFJGUTVVR1I0YnpGUFMwb3ZWRTQzTVZWU1lXODlJaXdpWTNSelVISnZabWxzWlUxaGRHTm9JanAwY25WbExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUk0VURGelZ6QkZVRXBqYzJ4M04xVjZVbk5wV0V3Mk5IY3JUelV3UldRclVrSkpRM1JoZVRGbk1qUk5QU0pkTENKaVlYTnBZMGx1ZEdWbmNtbDBlU0k2ZEhKMVpYMC56V3ViaWlraGt5alhETUJpV080ajZEdnVBZWdpSUh1WGhaNWQtTEh3Z1VBZFVSMWxNTU0tZ0Y4VklmSEdYcFZNZ1hhN3plR0l5NEROU19uNTdBZ2c0eE5lTVhQMHRpMVJ4QktVVlJKeUc1OXVoejJJbDBtZkl1UVZNckRpSHBiWjdYb2tKcG1jZlUyWU9QbmppcjlWUjlsVlRZUHVHV1phT01ua1kyRnlvbTRGZzhrNFA3dEtWWllzTXNERWR3ZVdOdTM5MS1mcXdKWUxQUWNjQ0ZiNURCRWc0SlMwa05pWG8zLWc3MTFWVGd2Z284WDMyMS03NWw5MnN6UWpDeDQ3aDFzY243ZmE1TkJhTkdfanVPZjV0QnhFbl9uY3N1TjR3RVRnT0JJVHFVN0xZWmxTVEtUX2lYODFncUJOOWtuWGMtQ0NVZUh1LThvLUdmekh1Y1BsSEFoYXV0aERhdGFYxXSm6pITyZwvdLIkkrMgz0AmKpTBqVCgOX8pJQtghB7wRQAAAAC5P9lh8uZGL7EiggAiR954AEEBSJVTcyTe4miZ8dwly7pJzBQdHKwTZ7oiBpM0DNDfhM_Q4-J-LYuAYP_mHPFGE59BMHV9bqTrcLy2T4zDLCk1UqUBAgMmIAEhWCC0eleNTLgwWxaVBqV139T6hONseRz7HgXRIVS9bPxIjSJYIJ1MfwUhvkSEjeiNJ6y5-w8PuuwMAvfgpN7F4Q2EW79v",
3519 "clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZGZvLUhscUpwM01MSy1KNVRMeHhtdlhKaWVTM3pHd2RrOUc5SDliUGV6ZyIsIm9yaWdpbiI6Imh0dHBzOlwvXC93ZWJhdXRobi5pbyIsImFuZHJvaWRQYWNrYWdlTmFtZSI6ImNvbS5hbmRyb2lkLmNocm9tZSJ9"}}"#;
3520
3521 let _ = tracing_subscriber::fmt::try_init();
3522 let wan = Webauthn::new_unsafe_experts_only(
3523 "webauthn.io",
3524 "webauthn.io",
3525 vec![Url::parse("https://webauthn.io").unwrap()],
3526 AUTHENTICATOR_TIMEOUT,
3527 None,
3528 None,
3529 );
3530
3531 let chal: HumanBinaryData =
3532 serde_json::from_str("\"dfo+HlqJp3MLK+J5TLxxmvXJieS3zGwdk9G9H9bPezg=\"").unwrap();
3533 let chal = Challenge::from(chal);
3534
3535 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(response).unwrap();
3536
3537 debug!("{:?}", rsp_d);
3538
3539 let result = wan.register_credential_internal(
3540 &rsp_d,
3541 UserVerificationPolicy::Required,
3542 &chal,
3543 &[],
3544 &[COSEAlgorithm::ES256],
3545 Some(&(GOOGLE_SAFETYNET_CA_OLD.try_into().unwrap())),
3546 true,
3547 &RequestRegistrationExtensions::default(),
3548 true,
3549 );
3550 dbg!(&result);
3551 assert_eq!(result, Err(WebauthnError::AttestationNotSupported));
3552 }
3553
3554 #[test]
3555 fn test_google_android_key() {
3556 let chal: HumanBinaryData =
3557 serde_json::from_str("\"Tf65bS6D5temh2BwvptqgBPb25iZDRxjwC5ans91IIJDrcrOpnWTK4LVgFjeUV4GDMe44w8SI5NsZssIXTUvDg\"").unwrap();
3558
3559 let response = r#"{
3560 "rawId": "AZD7huwZVx7aW1efRa6Uq3JTQNorj3qA9yrLINXEcgvCQYtWiSQa1eOIVrXfCmip6MzP8KaITOvRLjy3TUHO7_c",
3561 "id": "AZD7huwZVx7aW1efRa6Uq3JTQNorj3qA9yrLINXEcgvCQYtWiSQa1eOIVrXfCmip6MzP8KaITOvRLjy3TUHO7_c",
3562 "response": {
3563 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiVGY2NWJTNkQ1dGVtaDJCd3ZwdHFnQlBiMjVpWkRSeGp3QzVhbnM5MUlJSkRyY3JPcG5XVEs0TFZnRmplVVY0R0RNZTQ0dzhTSTVOc1pzc0lYVFV2RGciLCJvcmlnaW4iOiJodHRwczpcL1wvd2ViYXV0aG4ub3JnIiwiYW5kcm9pZFBhY2thZ2VOYW1lIjoiY29tLmFuZHJvaWQuY2hyb21lIn0",
3564 "attestationObject": "o2NmbXRrYW5kcm9pZC1rZXlnYXR0U3RtdKNjYWxnJmNzaWdYRjBEAiAsp6jPtimcSgc-fgIsVwgqRsZX6eU7KKbkVGWa0CRJlgIgH5yuf_laPyNy4PlS6e8ZHjs57iztxGiTqO7G91sdlWBjeDVjg1kCzjCCAsowggJwoAMCAQICAQEwCgYIKoZIzj0EAwIwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxOzA5BgNVBAMMMkFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gSW50ZXJtZWRpYXRlMB4XDTE4MTIwMjA5MTAyNVoXDTI4MTIwMjA5MTAyNVowHzEdMBsGA1UEAwwUQW5kcm9pZCBLZXlzdG9yZSBLZXkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ4SaIP3ibDSwCIORpYJ3g9_5OICxZUCIqt-vV6JZVJoXQ8S1JFzyaFz5EFQ2fNT6-5SE5wWTZRAR_A3M52IcaPo4IBMTCCAS0wCwYDVR0PBAQDAgeAMIH8BgorBgEEAdZ5AgERBIHtMIHqAgECCgEAAgEBCgEBBCAqQ4LXu9idi1vfF3LP7MoUOSSHuf1XHy63K9-X3gbUtgQAMIGCv4MQCAIGAWduLuFwv4MRCAIGAbDqja1wv4MSCAIGAbDqja1wv4U9CAIGAWduLt_ov4VFTgRMMEoxJDAiBB1jb20uZ29vZ2xlLmF0dGVzdGF0aW9uZXhhbXBsZQIBATEiBCBa0F7CIcj4OiJhJ97FV1AMPldLxgElqdwhywvkoAZglTAzoQUxAwIBAqIDAgEDowQCAgEApQUxAwIBBKoDAgEBv4N4AwIBF7-DeQMCAR6_hT4DAgEAMB8GA1UdIwQYMBaAFD_8rNYasTqegSC41SUcxWW7HpGpMAoGCCqGSM49BAMCA0gAMEUCIGd3OQiTgFX9Y07kE-qvwh2Kx6lEG9-Xr2ORT5s7AK_-AiEAucDIlFjCUo4rJfqIxNY93HXhvID7lNzGIolS0E-BJBhZAnwwggJ4MIICHqADAgECAgIQATAKBggqhkjOPQQDAjCBmDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTATBgNVBAoMDEdvb2dsZSwgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEzMDEGA1UEAwwqQW5kcm9pZCBLZXlzdG9yZSBTb2Z0d2FyZSBBdHRlc3RhdGlvbiBSb290MB4XDTE2MDExMTAwNDYwOVoXDTI2MDEwODAwNDYwOVowgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxOzA5BgNVBAMMMkFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gSW50ZXJtZWRpYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6555-EJjWazLKpFMiYbMcK2QZpOCqXMmE_6sy_ghJ0whdJdKKv6luU1_ZtTgZRBmNbxTt6CjpnFYPts-Ea4QFKNmMGQwHQYDVR0OBBYEFD_8rNYasTqegSC41SUcxWW7HpGpMB8GA1UdIwQYMBaAFMit6XdMRcOjzw0WEOR5QzohWjDPMBIGA1UdEwEB_wQIMAYBAf8CAQAwDgYDVR0PAQH_BAQDAgKEMAoGCCqGSM49BAMCA0gAMEUCIEuKm3vugrzAM4euL8CJmLTdw42rJypFn2kMx8OS1A-OAiEA7toBXbb0MunUhDtiTJQE7zp8zL1e-yK75_65dz9ZP_tZAo8wggKLMIICMqADAgECAgkAogWe0Q5DW1cwCgYIKoZIzj0EAwIwgZgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYDVQQKDAxHb29nbGUsIEluYy4xEDAOBgNVBAsMB0FuZHJvaWQxMzAxBgNVBAMMKkFuZHJvaWQgS2V5c3RvcmUgU29mdHdhcmUgQXR0ZXN0YXRpb24gUm9vdDAeFw0xNjAxMTEwMDQzNTBaFw0zNjAxMDYwMDQzNTBaMIGYMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEVMBMGA1UECgwMR29vZ2xlLCBJbmMuMRAwDgYDVQQLDAdBbmRyb2lkMTMwMQYDVQQDDCpBbmRyb2lkIEtleXN0b3JlIFNvZnR3YXJlIEF0dGVzdGF0aW9uIFJvb3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATuXV7H4cDbbQOmfua2G-xNal1qaC4P_39JDn13H0Qibb2xr_oWy8etxXfSVpyqt7AtVAFdPkMrKo7XTuxIdUGko2MwYTAdBgNVHQ4EFgQUyK3pd0xFw6PPDRYQ5HlDOiFaMM8wHwYDVR0jBBgwFoAUyK3pd0xFw6PPDRYQ5HlDOiFaMM8wDwYDVR0TAQH_BAUwAwEB_zAOBgNVHQ8BAf8EBAMCAoQwCgYIKoZIzj0EAwIDRwAwRAIgNSGj74s0Rh6c1WDzHViJIGrco2VB9g2ezooZjGZIYHsCIE0L81HZMHx9W9o1NB2oRxtjpYVlPK1PJKfnTa9BffG_aGF1dGhEYXRhWMWVaQiPHs7jIylUA129ENfK45EwWidRtVm7j9fLsim91EUAAAAAKPN9K5K4QcSwKoYM73zANABBAVUvAmX241vMKYd7ZBdmkNWaYcNYhoSZCJjFRGmROb6I4ygQUVmH6k9IMwcbZGeAQ4v4WMNphORudwje5h7ty9ClAQIDJiABIVggOEmiD94mw0sAiDkaWCd4Pf-TiAsWVAiKrfr1eiWVSaEiWCB0PEtSRc8mhc-RBUNnzU-vuUhOcFk2UQEfwNzOdiHGjw"
3565 },
3566 "type": "public-key"}"#;
3567
3568 let _ = tracing_subscriber::fmt::try_init();
3569 let wan = Webauthn::new_unsafe_experts_only(
3570 "webauthn.org",
3571 "webauthn.org",
3572 vec![Url::parse("https://webauthn.org").unwrap()],
3573 AUTHENTICATOR_TIMEOUT,
3574 None,
3575 None,
3576 );
3577
3578 let chal = Challenge::from(chal);
3579
3580 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(response).unwrap();
3581
3582 debug!("{:?}", rsp_d);
3583
3584 let result = wan.register_credential_internal(
3585 &rsp_d,
3586 UserVerificationPolicy::Required,
3587 &chal,
3588 &[],
3589 &[COSEAlgorithm::ES256],
3590 Some(&(ANDROID_SOFTWARE_ROOT_CA.try_into().unwrap())),
3591 true,
3592 &RequestRegistrationExtensions::default(),
3593 true,
3594 );
3595 dbg!(&result);
3596 assert!(result.is_ok());
3597
3598 match result.unwrap().attestation.metadata {
3599 AttestationMetadata::AndroidKey {
3600 is_km_tee,
3601 is_attest_tee,
3602 } => {
3603 assert!(is_km_tee);
3604 assert!(!is_attest_tee);
3605 }
3606 _ => panic!("invalid metadata"),
3607 }
3608 }
3609
3610 #[test]
3611 fn test_origins_match_localhost_port() {
3612 let collected = url::Url::parse("http://localhost:3000").unwrap();
3613 let config = url::Url::parse("http://localhost:8000").unwrap();
3614
3615 let result = super::WebauthnCore::origins_match(false, true, &collected, &config);
3616 dbg!(result);
3617 assert!(result);
3618
3619 let result = super::WebauthnCore::origins_match(true, false, &collected, &config);
3620 assert!(!result);
3621 }
3622
3623 #[test]
3624 fn test_tpm_ecc_aseigler() {
3625 let chal: HumanBinaryData =
3626 serde_json::from_str("\"E2YebMmG9992XialpFL1lkPptOIBPeKsphNkt1JcbKk\"").unwrap();
3627
3628 let response = r#"{
3629 "id": "BoLAd0jIDI0ztrH1N45XQ_0w_N5ndt3hpNixQi3J2No",
3630 "rawId": "BoLAd0jIDI0ztrH1N45XQ_0w_N5ndt3hpNixQi3J2No",
3631 "response": {
3632 "attestationObject": "o2NmbXRjdHBtZ2F0dFN0bXSmY2FsZzn__mNzaWdZAQAzaz3HmrpCUlkEV2iv-TF2_y0MD7MVc0rLyuD_Ah3X9vx3G21WgeI89PyyvEYw3yEUUdO7sn6YxubMfuePpuSawYKAeSbw3O4LkMDC2fqZmlLyTfoC8L1_8vExv6mWPN7H5U6E_K7IZ38H3mO736ie-mDyoXxalj4WkA9zjKXJM5t7GhHQAqtDaX4HmM47pFH25atgQnoLdB0MTzh6jgYjIiDrMSOqhrQYskiaX_LFfKTiWfviwMOYcMA8FkRPc05LKvPTxp-bx_ghHrd_gIAUA3MjfElVYCVfveMnI61ZwARnf0cTrFp7vfga85YeAXaLOu29JifjodW6DsjL_dnXY3ZlcmMyLjBjeDVjglkFtTCCBbEwggOZoAMCAQICEAaSyUKea0mgpfZbwvZ7byMwDQYJKoZIhvcNAQELBQAwQTE_MD0GA1UEAxM2RVVTLU5UQy1LRVlJRC0yM0Y0RTIyQUQzQkUzNzRBNDQ5NzcyOTU0QUEyODNBRUQ3NTI1NzJFMB4XDTIxMTEyNTIxMzA1NFoXDTI3MDYwMzE3NTE0N1owADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANwiGFmQdIOYto4qGegANWT-LdSr5T5_tj7E_aKtLSNP8bqc6eP11VvCi9ZFnbjiFxi1NdY2GAbUDb3zr1PnZpOcwvn1gh704PLtkZYFkwvFRvm5bIvtsuqYgn71MCup1GCTeJ3EcylidbVpmwX5s9XK5vyRsMpQ1TxPwxPq32toIBcQ3pgZyb9Ic_m1IfWE_hC_XlwZzqfFnFL7XszCGwJmziFjML9VeBrdv0dkrDWMv1sNI1PDDm_JQ8iZwZ83At3qsgnmwN4zudOMUPRMJBNeiVBj9GjW7tV9tSG2Oa_F_JUo0b1Gr_y08PSMhAckj6ZaR8_EBppoty9CbTm65nsCAwEAAaOCAeQwggHgMA4GA1UdDwEB_wQEAwIHgDAMBgNVHRMBAf8EAjAAMG0GA1UdIAEB_wRjMGEwXwYJKwYBBAGCNxUfMFIwUAYIKwYBBQUHAgIwRB5CAFQAQwBQAEEAIAAgAFQAcgB1AHMAdABlAGQAIAAgAFAAbABhAHQAZgBvAHIAbQAgACAASQBkAGUAbgB0AGkAdAB5MBAGA1UdJQQJMAcGBWeBBQgDMEoGA1UdEQEB_wRAMD6kPDA6MTgwDgYFZ4EFAgMMBWlkOjcyMBAGBWeBBQICDAdOUENUNzV4MBQGBWeBBQIBDAtpZDo0RTU0NDMwMDAfBgNVHSMEGDAWgBTTjd-fy_wwa14b1TQrBpJk2U7fpTAdBgNVHQ4EFgQUeq9wlX_04m4THgx-yMSO7QwViv8wgbIGCCsGAQUFBwEBBIGlMIGiMIGfBggrBgEFBQcwAoaBkmh0dHA6Ly9hemNzcHJvZGV1c2Fpa3B1Ymxpc2guYmxvYi5jb3JlLndpbmRvd3MubmV0L2V1cy1udGMta2V5aWQtMjNmNGUyMmFkM2JlMzc0YTQ0OTc3Mjk1NGFhMjgzYWVkNzUyNTcyZS8xMzY0YTJkMy1hZTU0LTQ3YjktODdmMy0zMjA1NDE5NDc0MGUuY2VyMA0GCSqGSIb3DQEBCwUAA4ICAQCiPgQwqysYPQpMiRDpxbsx24d1xVX_kiUwwcQJE3mSYvwe4tnaQSHjlfB3OkpDMjotxFl33oUMxxScjSrgp_1o6rdkiO6QvPMgsqDMX4w-dmWn00akwNbMasTxg39Ceqtocw4i-R9AlNwndpe3QUIt8xkQ5dhlcIF8lc1dXmgz4mkMAtOi3VgaNvHTsRF9pLbTczJss608X8b4gHqM4t7lfIcRB8DvSyfXc7T3k21-4_3jvAb2HRoCCAyv8_XXn1UwkWTrXMLUSiE1p5Sl8ba8I_86Hsemsc0aflwRZrrY2pC3aaA3QbbfAyskiaFPw-ZibY9p0_QVq1XhAKa-dDd70mWvTGKQdrqfZI_SC5zccvDAm6aefAfnYBY2fV92ZFriihA2ULcJaESz3X3JkiK4eO1k0T2uf9-rL4lUEADibwpnsZOBeNWBsztvXDmcZGR_MSoRIQygKMw2U7AproqBPDRDFwhS5yc9UHvD6dMZ3PLx4i_eo-BLr-QJ2HARoyK8KuV0xLEq3XyjWdfZDbAueUVgtic14wK9jiSbhycRT2WV3-QU8KPm5_QCt_eBPwY81a-q84jm2ue_ok8-LYrmWpvihqRhFhK9MLVS96QaHeeuDehYNDWsSIVCr9jB-lchueZ-kZqwyl_4pPMrM7wLXBOR-bV5_pAPv3u_RvQmhVkG7zCCBuswggTToAMCAQICEzMAAAQHrjuoB9SvW8wAAAAABAcwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDAeFw0yMTA2MDMxNzUxNDdaFw0yNzA2MDMxNzUxNDdaMEExPzA9BgNVBAMTNkVVUy1OVEMtS0VZSUQtMjNGNEUyMkFEM0JFMzc0QTQ0OTc3Mjk1NEFBMjgzQUVENzUyNTcyRTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMkPU9X8JhPBwDxmFm84D31b8xN5NQz0XR8Nji_-Z8v3WtC4lSdEwJUwqvZkj5OQ3wPA_6haONcCHzqTZhyz1aheOPhXmEeWFWjEiJFj07crEZb9wM4rM1fdcf3vCQNSSDlogC5AM-tITx31hm0YffIrzM3n70fNBBfvlw8t-yhZVOavj7l29gKsyvkR0IadruvLVWWVeH9rueHVrOwlU4wUJpjD41d4U87M3FgUGK2YacQxT0BPHzaOCTE9YhylG5fA_eCF7Q1SxAe347uIaS6I3GhAootzJy9XYeFp_uhc1Yp2hMh5wdeRkm15WKb7tE9T4vwHp0VCQEkUQn1ClN_s7PpfKNFp-DB9ez0Fh7tqag6AssrKE6LgOjfWDWUcgzgIiFLvv9Gx797IZj8LDazK1iGSqI2D8zmmxnGG47MevfY8q2udJW1G4nOcjw49x6XZHmnT3VpVKcTDbI9bEsyc2R9vngftF9FgnEVdyt-QRqE0UqEXJmjLhcxBMeyFZJd_bEAutSBpWugPk10IPFRkXppsuHMZFHJVP96IWwVmm6Q4mX018K996XDubAGblbhvPzJ9NFL_e7xM2ev3rAalz2CzSLYs48EXym7dqGTnP7F9DaF2O0IHT0GQ951wFVoGmA-IYsTMVsdlhVaImCuHgahu1W94H6BvtDkGGku7AgMBAAGjggGOMIIBijAOBgNVHQ8BAf8EBAMCAoQwGwYDVR0lBBQwEgYJKwYBBAGCNxUkBgVngQUIAzAWBgNVHSAEDzANMAsGCSsGAQQBgjcVHzASBgNVHRMBAf8ECDAGAQH_AgEAMB0GA1UdDgQWBBTTjd-fy_wwa14b1TQrBpJk2U7fpTAfBgNVHSMEGDAWgBR6jArOL0hiF-KU0a5VwVLscXSkVjBwBgNVHR8EaTBnMGWgY6Bhhl9odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUUE0lMjBSb290JTIwQ2VydGlmaWNhdGUlMjBBdXRob3JpdHklMjAyMDE0LmNybDB9BggrBgEFBQcBAQRxMG8wbQYIKwYBBQUHMAKGYWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcnQwDQYJKoZIhvcNAQELBQADggIBAIQJqhFB71eZzZMq0w866QXDKlHcGyIa_IkTK4p5ejIdIA7FJ8neeVToAKUt9ULEb1Od2ir1y5Qx5Zp_edf4F8aikn-yw61hNB3FQ4iSV49eqEMe2Fx6OMBmHRWGtUjAlf5g_N2Qc6rHela2d69nQbpSF3Nq7AESguXxnoqZ-4CGUW0jC_b93sTd5fESHs_iwFX-zWKCwCXerqCuI3PqYWOlbCnftYhsI1CD638wJxw4YFXdSmOrF8dDnd6tlH_0qCZrBX-k4N-8QgK1-BDYIxmvUBnpLFDDitB2dP6YIglY0VcjkPd3BDmodHknG4GQeAvJKHpqF91Y3K1rOWvn4JqzHFvL3JgXgL7LbC_h9EF50HeHayPCToTS8Pmg_4dfUaCwNlxPvu9GvjrDKDNNEV5T73iWMV_GQbVsx6JULAljCthYLo-55mONDcr1x7kakXlQT-yIdIQ57Ix8eHz_qkJkvWxbw8vOgrXhkLK0jGAvW_YSkTV7G9_TYDJ--8IjPPHC1bexKq72-L7KetwH6LbWHGeYkJnaZ1zqeN4USxyJn8K4uhwnjSeK2sZ942zn5EnZnjd85yfdkPLcQY8xtYiWNjc_PprTrjhLyMO71VdMkTDiTTtDha37qywNISPV7vBv8YDiDjX8ElsWbTHTC0XgBp0h-RkjaRKI5C4eTUebZ3B1YkFyZWFYdgAjAAsABAByACCd_8vzbDg65pn7mGjcbcuJ1xU4hL4oA5IsEkFYv60irgAQABAAAwAQACCweOEk52r8mnJ6y9bsGcM3V4dL1LWt8I67Jjx5mcrFuAAgjwd_jaCEEOAJLV97kX3VgbxzopPYMC4NqEFjD0m55PpoY2VydEluZm9Yof9UQ0eAFwAiAAvgBLotxyAAbygBG4efe84V0SVYnO6xLrYaC1oyLgTt3QAUjcjAdORvuzxCfLBU7KNxPFSPE84AAAAUHn9jxccO2yRJARoXARNN0IPNWxnEACIACxfcHNQuRgb_05OKyBrS_1kY5IYxOl67gTlqkHd4g6slACIAC7tcXSHNTw8ANLeZd3PKooKsgrMIlGD47aunn05BcquwaGF1dGhEYXRhWKRqubvw35oW-R27M7uxMvr50Xx4LEgmxuxw7O5Y2X71KkUAAAAACJhwWMrcS4G24TDeUNy-lgAgBoLAd0jIDI0ztrH1N45XQ_0w_N5ndt3hpNixQi3J2NqlAQIDJiABIVggsHjhJOdq_JpyesvW7BnDN1eHS9S1rfCOuyY8eZnKxbgiWCCPB3-NoIQQ4AktX3uRfdWBvHOik9gwLg2oQWMPSbnk-g",
3633 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiRTJZZWJNbUc5OTkyWGlhbHBGTDFsa1BwdE9JQlBlS3NwaE5rdDFKY2JLayIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4uZmlyc3R5ZWFyLmlkLmF1IiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0"
3634 },
3635 "type": "public-key"}"#;
3636
3637 let _ = tracing_subscriber::fmt::try_init();
3638 let wan = Webauthn::new_unsafe_experts_only(
3639 "webauthn.firstyear.id.au",
3640 "webauthn.firstyear.id.au",
3641 vec![Url::parse("https://webauthn.firstyear.id.au").unwrap()],
3642 AUTHENTICATOR_TIMEOUT,
3643 None,
3644 None,
3645 );
3646
3647 let chal = Challenge::from(chal);
3648
3649 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(response).unwrap();
3650
3651 debug!("{:?}", rsp_d);
3652
3653 let result = wan.register_credential_internal(
3654 &rsp_d,
3655 UserVerificationPolicy::Preferred,
3656 &chal,
3657 &[],
3658 &[COSEAlgorithm::ES256],
3659 None,
3660 true,
3661 &RequestRegistrationExtensions::default(),
3662 true,
3663 );
3664
3665 assert!(matches!(
3666 result,
3667 Err(WebauthnError::CredentialInsecureCryptography)
3668 ))
3669 }
3670
3671 #[test]
3672 fn test_ios_origin_matches() {
3673 assert!(Webauthn::origins_match(
3674 false,
3675 false,
3676 &Url::parse("ios:bundle-id:com.foo.bar").unwrap(),
3677 &Url::parse("ios:bundle-id:com.foo.bar").unwrap(),
3678 ));
3679
3680 assert!(!Webauthn::origins_match(
3681 false,
3682 false,
3683 &Url::parse("ios:bundle-id:com.foo.bar").unwrap(),
3684 &Url::parse("ios:bundle-id:com.foo.baz").unwrap(),
3685 ));
3686 }
3687
3688 #[test]
3689 fn test_solokey_v2_a_sealed_attestation() {
3690 let chal: HumanBinaryData =
3691 serde_json::from_str("\"VEP2Y5lrFKvfNZCt-js1BivzIRjDCXERNRswVPGT1tw\"").unwrap();
3692 let response = r#"{
3693 "id": "owBYr08K20VJPLwjmm6fiIPE9iqvr31mfxoi1S-gj3mrvsmeSSUd70rMHJpbMBxnm7MlTX8hPpXz2NKVkEVrVGrrJOayYhdthzPeRqPQsFj_f2qkhJrt3xSIzDb6ZzS1hcME5xE76_XKdbH9-ZEUztxN9lR8GjX5TO9e1WsEfeY6yriqKRZ-xgA3BU081GOZWZ00cggWPEEmll1gkYepDDjrwH0a2CXaV-oSs50rRIuD9JkBTKCqEYK6IG-CBMtTEwJQA042FkAQ_RpWpziVVyXfWA",
3694 "rawId": "owBYr08K20VJPLwjmm6fiIPE9iqvr31mfxoi1S-gj3mrvsmeSSUd70rMHJpbMBxnm7MlTX8hPpXz2NKVkEVrVGrrJOayYhdthzPeRqPQsFj_f2qkhJrt3xSIzDb6ZzS1hcME5xE76_XKdbH9-ZEUztxN9lR8GjX5TO9e1WsEfeY6yriqKRZ-xgA3BU081GOZWZ00cggWPEEmll1gkYepDDjrwH0a2CXaV-oSs50rRIuD9JkBTKCqEYK6IG-CBMtTEwJQA042FkAQ_RpWpziVVyXfWA",
3695 "response": {
3696 "attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEcwRQIhAPdns_NNqPklDOJLgahVz9Ul9yGWelzagMgTc9PSgAliAiAi058w6Dq4C_-44qlEcqoKFldVCGcQxnWh6tL2IXj-mmN4NWOBWQKqMIICpjCCAkygAwIBAgIUfWe3F4mJfmOVopPF8mmAKxBb0igwCgYIKoZIzj0EAwIwLTERMA8GA1UECgwIU29sb0tleXMxCzAJBgNVBAYTAkNIMQswCQYDVQQDDAJGMTAgFw0yMTA1MjMwMDUyMDBaGA8yMDcxMDUxMTAwNTIwMFowgYMxCzAJBgNVBAYTAlVTMREwDwYDVQQKDAhTb2xvS2V5czEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjE9MDsGA1UEAww0U29sbyAyIE5GQytVU0ItQSA4NjUyQUJFOUZCRDg0ODEwQTg0MEQ2RkM0NDJBOEMyQyBCMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABArSyTVT7sDxX0rom6XoIcg8qwMStGV3SjoGRNMqHBSAh2sr4EllUzA1F8yEX5XvUPN_M6DQlqEFGw18UodOjBqjgfAwge0wHQYDVR0OBBYEFBiTdxTWyNCRuzSieBflmHPSJbS1MB8GA1UdIwQYMBaAFEFrtkvvohkN5GJf_SkElrmCKbT4MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcwAoYWaHR0cDovL2kuczJwa2kubmV0L2YxLzAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8vYy5zMnBraS5uZXQvcjEvMCEGCysGAQQBguUcAQEEBBIEEIZSq-n72EgQqEDW_EQqjCwwEwYLKwYBBAGC5RwCAQEEBAMCBDAwCgYIKoZIzj0EAwIDSAAwRQIgMsLnUg5Px2FehxIUNiaey8qeT1FGtlJ1s3LEUGOks-8CIQDNEv5aupDvYxn2iqWSNysv4qpdoqSMytRQ7ctfuJDWN2hhdXRoRGF0YVkBV2q5u_Dfmhb5Hbszu7Ey-vnRfHgsSCbG7HDs7ljZfvUqRQAAAAOGUqvp-9hIEKhA1vxEKowsANOjAFivTwrbRUk8vCOabp-Ig8T2Kq-vfWZ_GiLVL6CPeau-yZ5JJR3vSswcmlswHGebsyVNfyE-lfPY0pWQRWtUausk5rJiF22HM95Go9CwWP9_aqSEmu3fFIjMNvpnNLWFwwTnETvr9cp1sf35kRTO3E32VHwaNflM717VawR95jrKuKopFn7GADcFTTzUY5lZnTRyCBY8QSaWXWCRh6kMOOvAfRrYJdpX6hKznStEi4P0mQFMoKoRgrogb4IEy1MTAlADTjYWQBD9GlanOJVXJd9YowFjT0tQAycgZ0VkMjU1MTkhmCAYTBQYsBg8DBhNGCEY3BgxGDIY6xhdABiiGEoYVhiKGHgYMxgcGOIYdRiiGJMLAhgZGIkYNQkY2A0",
3697 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJWRVAyWTVsckZLdmZOWkN0LWpzMUJpdnpJUmpEQ1hFUk5Sc3dWUEdUMXR3Iiwib3JpZ2luIjoiaHR0cHM6Ly93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0",
3698 "transports": null
3699 },
3700 "type": "public-key",
3701 "extensions": {}
3702 }"#;
3703
3704 let _ = tracing_subscriber::fmt::try_init();
3705 let wan = Webauthn::new_unsafe_experts_only(
3706 "webauthn.firstyear.id.au",
3707 "webauthn.firstyear.id.au",
3708 vec![Url::parse("https://webauthn.firstyear.id.au").unwrap()],
3709 AUTHENTICATOR_TIMEOUT,
3710 None,
3711 None,
3712 );
3713
3714 let chal = Challenge::from(chal);
3715
3716 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(response).unwrap();
3717
3718 debug!(?rsp_d);
3719
3720 let result = wan.register_credential_internal(
3721 &rsp_d,
3722 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3723 &chal,
3724 &[],
3725 &[COSEAlgorithm::EDDSA],
3726 None,
3727 true,
3728 &RequestRegistrationExtensions::default(),
3729 true,
3730 );
3731
3732 debug!(?result);
3733
3734 assert!(matches!(
3736 result,
3737 Err(WebauthnError::AttestationStatementSigInvalid)
3738 ))
3739 }
3740
3741 #[test]
3742 fn test_solokey_v2_a_sealed_ed25519_invalid_cbor() {
3743 let chal: HumanBinaryData =
3744 serde_json::from_str("\"KlJqz0evSPAw8cTWpup6SkYJw-RTziV0BBuMH8R-zVM\"").unwrap();
3745
3746 let response = r#"{
3747 "id": "owBYn6_Ys3wJqEeCM84k1tMrasG4oPkmzCza-UvzwU5a3V_piE5ZglKlAPMikNcz2LHMMxrlE7CZo6bJZ-QNijw97HdJT8fxky1CW78Yt5yvyYAkPurVqIp0_18ngp3HHu9vL35C7bczMQdJEv3tWjD7XZvzlZlewTiFcSjbnSNROmxxTWUFJM9T8Hsito3g8sDSwc16ogiaPidHoK33fCxVhwFMPCVPuOjlRzLXxUXzAlDXFCg6QebXOL-9KnXq1JsZ",
3748 "rawId": "owBYn6_Ys3wJqEeCM84k1tMrasG4oPkmzCza-UvzwU5a3V_piE5ZglKlAPMikNcz2LHMMxrlE7CZo6bJZ-QNijw97HdJT8fxky1CW78Yt5yvyYAkPurVqIp0_18ngp3HHu9vL35C7bczMQdJEv3tWjD7XZvzlZlewTiFcSjbnSNROmxxTWUFJM9T8Hsito3g8sDSwc16ogiaPidHoK33fCxVhwFMPCVPuOjlRzLXxUXzAlDXFCg6QebXOL-9KnXq1JsZ",
3749 "response": {
3750 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVkBTEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjRQAAABMAAAAAAAAAAAAAAAAAAAAAAMOjAFifr9izfAmoR4IzziTW0ytqwbig-SbMLNr5S_PBTlrdX-mITlmCUqUA8yKQ1zPYscwzGuUTsJmjpsln5A2KPD3sd0lPx_GTLUJbvxi3nK_JgCQ-6tWoinT_XyeCncce728vfkLttzMxB0kS_e1aMPtdm_OVmV7BOIVxKNudI1E6bHFNZQUkz1PweyK2jeDywNLBzXqiCJo-J0egrfd8LFWHAUw8JU-46OVHMtfFRfMCUNcUKDpB5tc4v70qderUmxmjAWNPS1ADJyBnRWQyNTUxOSGYIBhtGCEYqxgkGEwYPRhZGKIYaBjWGNoYIRjCEhifGPUYVBj7GDgY0hhNGLQYrxiEGE4VGJkYQxheGJoYUhip",
3751 "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJLbEpxejBldlNQQXc4Y1RXcHVwNlNrWUp3LVJUemlWMEJCdU1IOFItelZNIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
3752 "transports": null
3753 },
3754 "type": "public-key",
3755 "extensions": {}
3756 }"#;
3757
3758 let _ = tracing_subscriber::fmt::try_init();
3759 let wan = Webauthn::new_unsafe_experts_only(
3760 "localhost",
3761 "localhost",
3762 vec![Url::parse("http://localhost:8080/").unwrap()],
3763 AUTHENTICATOR_TIMEOUT,
3764 None,
3765 None,
3766 );
3767
3768 let chal = Challenge::from(chal);
3769
3770 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(response).unwrap();
3771
3772 debug!(?rsp_d);
3773
3774 let reg_extn = RequestRegistrationExtensions {
3775 cred_protect: Some(CredProtect {
3776 credential_protection_policy:
3777 CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList,
3778 enforce_credential_protection_policy: Some(false),
3779 }),
3780 uvm: Some(true),
3781 cred_props: Some(true),
3782 min_pin_length: Some(true),
3783 hmac_create_secret: Some(true),
3784 };
3785
3786 let result = wan.register_credential_internal(
3787 &rsp_d,
3788 UserVerificationPolicy::Discouraged_DO_NOT_USE,
3789 &chal,
3790 &[],
3791 &[COSEAlgorithm::EDDSA],
3792 None,
3793 true,
3794 ®_extn,
3795 true,
3796 );
3797
3798 debug!(?result);
3799
3800 assert!(matches!(
3801 result,
3802 Err(WebauthnError::COSEKeyInvalidCBORValue)
3803 ))
3804 }
3805
3806 #[test]
3807 fn test_macos_openssl_key_segfault() {
3808 let chal: HumanBinaryData =
3809 serde_json::from_str("\"1L_qrRoR5OaxUbbBEJwBQxKI-aYXkwJVgusq4xOA9nM\"").unwrap();
3810
3811 let response = r#"{
3812 "id": "SdnLJ3MQEUY7NR3lJbMc9cLyVBU",
3813 "rawId": "SdnLJ3MQEUY7NR3lJbMc9cLyVBU",
3814 "response": {
3815 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAPv8MAcVTk7MjAtuAgVX170AFEnZyydzEBFGOzUd5SWzHPXC8lQVpQECAyYgASFYIDiIJ80DjQhQuDLArW-zqUxmjuDc0O6Se1FmEPmbVz8aIlgg0EQYr2a-wBEi-7ZzwHC6j-m4I3kLA86jSYFAjW0OMcA",
3816 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiMUxfcXJSb1I1T2F4VWJiQkVKd0JReEtJLWFZWGt3SlZndXNxNHhPQTluTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODEwMCIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9",
3817 "transports": [
3818 "hybrid",
3819 "internal"
3820 ]
3821 },
3822 "type": "public-key",
3823 "extensions": {
3824 "cred_props": {
3825 "rk": true
3826 }
3827 }
3828 }"#;
3829
3830 let _ = tracing_subscriber::fmt::try_init();
3831 let wan = Webauthn::new_unsafe_experts_only(
3832 "localhost",
3833 "localhost",
3834 vec![Url::parse("http://localhost:8100/").unwrap()],
3835 AUTHENTICATOR_TIMEOUT,
3836 None,
3837 None,
3838 );
3839
3840 let chal = Challenge::from(chal);
3841
3842 let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(response).unwrap();
3843
3844 debug!(?rsp_d);
3845
3846 let reg_extn = RequestRegistrationExtensions {
3847 cred_protect: Some(CredProtect {
3848 credential_protection_policy: CredentialProtectionPolicy::UserVerificationRequired,
3849 enforce_credential_protection_policy: Some(false),
3850 }),
3851 uvm: Some(true),
3852 cred_props: Some(true),
3853 min_pin_length: None,
3854 hmac_create_secret: None,
3855 };
3856
3857 let result = wan.register_credential_internal(
3858 &rsp_d,
3859 UserVerificationPolicy::Required,
3860 &chal,
3861 &[],
3862 &[COSEAlgorithm::ES256, COSEAlgorithm::RS256],
3863 None,
3864 true,
3865 ®_extn,
3866 true,
3867 );
3868
3869 debug!(?result);
3870
3871 assert!(result.is_ok())
3872 }
3873}