webauthn_rs_core/
core.rs

1//! Webauthn-rs - Webauthn for Rust Server Applications
2//!
3//! Webauthn is a standard allowing communication between servers, browsers and authenticators
4//! to allow strong, passwordless, cryptographic authentication to be performed. Webauthn
5//! is able to operate with many authenticator types, such as U2F.
6//!
7//! ⚠️  ⚠️  ⚠️  THIS IS UNSAFE. AVOID USING THIS DIRECTLY ⚠️  ⚠️  ⚠️
8//!
9//! If possible, use the `webauthn-rs` crate, and it's safe wrapper instead!
10//!
11//! Webauthn as a standard has many traps that in the worst cases, may lead to
12//! bypasses and full account compromises. Many of the features of webauthn are
13//! NOT security policy, but user interface hints. Many options can NOT be
14//! enforced. `webauthn-rs` handles these correctly. USE `webauthn-rs` INSTEAD.
15
16#![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/// The Core Webauthn handler.
34///
35/// It provides 4 interfaces methods for registering and then authenticating credentials.
36/// * generate_challenge_register
37/// * register_credential
38/// * generate_challenge_authenticate
39/// * authenticate_credential
40///
41/// Each of these is described in turn, but they will all map to routes in your application.
42/// The generate functions return Json challenges that are intended to be processed by the client
43/// browser, and the register and authenticate will receive Json that is processed and verified.
44///
45/// These functions return state that you must store and handle correctly for the authentication
46/// or registration to proceed correctly.
47///
48/// As a result, it's very important you read the function descriptions to understand the process
49/// as much as possible.
50#[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
65/// A builder allowing customisation of a client registration challenge.
66pub 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/// A builder allowing customisation of a client authentication challenge.
83#[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    /// Set the attestation conveyance preference. Defaults to None.
94    pub fn attestation(mut self, value: AttestationConveyancePreference) -> Self {
95        self.attestation = value;
96        self
97    }
98
99    /// Request a user verification policy. Defaults to Preferred.
100    pub fn user_verification_policy(mut self, value: UserVerificationPolicy) -> Self {
101        self.policy = value;
102        self
103    }
104
105    /// A list of credentials that the users agent will exclude from the registration.
106    /// This is not a security control - it exists to help guide the user and the UI
107    /// to avoid duplicate registration of credentials.
108    pub fn exclude_credentials(mut self, value: Option<Vec<CredentialID>>) -> Self {
109        self.exclude_credentials = value;
110        self
111    }
112
113    /// Define extensions to be requested in the request. Defaults to None.
114    pub fn extensions(mut self, value: Option<RequestRegistrationExtensions>) -> Self {
115        self.extensions = value;
116        self
117    }
118
119    /// Define the set of allowed credential algorithms. Defaults to the secure list.
120    pub fn credential_algorithms(mut self, value: Vec<COSEAlgorithm>) -> Self {
121        self.credential_algorithms = value;
122        self
123    }
124
125    /// A flag to require that the authenticator must create a resident key. Defaults
126    /// to false.
127    pub fn require_resident_key(mut self, value: bool) -> Self {
128        self.require_resident_key = value;
129        self
130    }
131
132    /// Set the authenticator attachement preference. Defaults to None.
133    pub fn authenticator_attachment(mut self, value: Option<AuthenticatorAttachment>) -> Self {
134        self.authenticator_attachment = value;
135        self
136    }
137
138    /// Flag that synchronised authenticators should not be allowed to register. Defaults to false.
139    pub fn reject_synchronised_authenticators(mut self, value: bool) -> Self {
140        self.reject_synchronised_authenticators = value;
141        self
142    }
143
144    /// Add the set of hints for which public keys may satisfy this request.
145    pub fn hints(mut self, hints: Option<Vec<PublicKeyCredentialHints>>) -> Self {
146        self.hints = hints;
147        self
148    }
149
150    /// Add the set of attestation formats that will be accepted in this operation
151    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    /// Define extensions to be requested in the request. Defaults to None.
162    pub fn extensions(mut self, value: Option<RequestAuthenticationExtensions>) -> Self {
163        self.extensions = value;
164        self
165    }
166
167    /// Allow authenticators to modify their backup eligibility. This can occur
168    /// where some formerly hardware bound devices become roaming ones. If in
169    /// double leave this value as default (false, rejects credentials that
170    /// post-create move from single device to roaming).
171    pub fn allow_backup_eligible_upgrade(mut self, value: bool) -> Self {
172        self.allow_backup_eligible_upgrade = value;
173        self
174    }
175
176    /// Add the set of hints for which public keys may satisfy this request.
177    pub fn hints(mut self, hints: Option<Vec<PublicKeyCredentialHints>>) -> Self {
178        self.hints = hints;
179        self
180    }
181}
182
183impl WebauthnCore {
184    /// ⚠️  ⚠️  ⚠️  THIS IS UNSAFE. AVOID USING THIS DIRECTLY ⚠️  ⚠️  ⚠️
185    ///
186    /// If possible, use the `webauthn-rs` crate, and it's safe wrapper instead!
187    ///
188    /// Webauthn as a standard has many traps that in the worst cases, may lead to
189    /// bypasses and full account compromises. Many of the features of webauthn are
190    /// NOT security policy, but user interface hints. Many options can NOT be
191    /// enforced. `webauthn-rs` handles these correctly. USE `webauthn-rs` INSTEAD.
192    ///
193    /// If you still choose to continue, and use this directly, be aware that:
194    ///
195    /// * This function signature MAY change WITHOUT NOTICE and WITHIN MINOR VERSIONS
196    /// * You MUST understand the webauthn specification in excruciating detail to understand the traps within it
197    /// * That you are responsible for UPHOLDING many invariants within webauthn that are NOT DOCUMENTED in the webauthn specification
198    ///
199    /// Seriously. Use `webauthn-rs` instead.
200    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    /// Get the currently configured origins
224    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    /// Generate a new challenge builder for client registration. This is the first step in
234    /// the lifecycle of a credential. This function will return a register builder
235    /// allowing you to customise the parameters that will be sent to the client.
236    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    /// Generate a new challenge for client registration from the parameters defined by the
264    /// `ChallengeRegisterBuilder`.
265    ///
266    /// This function will return the
267    /// `CreationChallengeResponse` which is suitable for serde json serialisation
268    /// to be sent to the client.
269    /// The client (generally a web browser) will pass this JSON
270    /// structure to the `navigator.credentials.create()` javascript function for registration.
271    ///
272    /// It also returns a RegistrationState, that you *must*
273    /// persist. It is strongly advised you associate this RegistrationState with the
274    /// UserId of the requester.
275    #[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            // We can potentially enforce these!
357            require_resident_key,
358            authenticator_attachment,
359            extensions: extensions.unwrap_or_default(),
360            allow_synchronised_authenticators: !reject_synchronised_authenticators,
361        };
362
363        // This should have an opaque type of username + chal + policy
364        Ok((c, wr))
365    }
366
367    /// Process a credential registration response. This is the output of
368    /// `navigator.credentials.create()` which is sent to the webserver from the client.
369    ///
370    /// Given the username you also must provide the associated RegistrationState for this
371    /// operation to proceed.
372    ///
373    /// On success this returns a new Credential that you must persist and associate with the
374    /// user.
375    ///
376    /// You need to provide a closure that is able to check if any credential of the
377    /// same id has already been persisted by your server.
378    pub fn register_credential(
379        &self,
380        reg: &RegisterPublicKeyCredential,
381        state: &RegistrationState,
382        attestation_cas: Option<&AttestationCaList>,
383        // does_exist_fn: impl Fn(&CredentialID) -> Result<bool, ()>,
384    ) -> Result<Credential, WebauthnError> {
385        // Decompose our registration state which contains everything we need to proceed.
386        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        // send to register_credential_internal
402        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        // Check that the credentialId is not yet registered to any other user. If registration is
415        // requested for a credential that is already registered to a different user, the Relying
416        // Party SHOULD fail this registration ceremony, or it MAY decide to accept the registration,
417        // e.g. while deleting the older registration.
418
419        /*
420        let cred_exist_result = does_exist_fn(&credential.0.cred_id)
421            .map_err(|_| WebauthnError::CredentialExistCheckError)?;
422
423        if cred_exist_result {
424            return Err(WebauthnError::CredentialAlreadyExists);
425        }
426        */
427
428        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        // Internal management - if the attestation ca list is some, but is empty, we need to fail!
445        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        // ======================================================================
454        // References:
455        // Level 2: https://www.w3.org/TR/webauthn-2/#sctn-registering-a-new-credential
456
457        // Steps 1 through 4 are performed by the Client, not the RP.
458
459        // Let JSONtext be the result of running UTF-8 decode on the value of response.clientDataJSON.
460        //
461        //   This is done by the calling webserver to give us reg aka JSONText in this case.
462        //
463        // Let C, the client data claimed as collected during the credential creation, be the result
464        // of running an implementation-specific JSON parser on JSONtext.
465        //
466        // Now, we actually do a much larger conversion in one shot
467        // here, where we get the AuthenticatorAttestationResponse
468
469        let data = AuthenticatorAttestationResponse::try_from(&reg.response)?;
470
471        // trace!("data: {:?}", data);
472
473        // Verify that the value of C.type is webauthn.create.
474        if data.client_data_json.type_ != "webauthn.create" {
475            return Err(WebauthnError::InvalidClientDataType);
476        }
477
478        // Verify that the value of C.challenge matches the challenge that was sent to the
479        // authenticator in the create() call.
480        if data.client_data_json.challenge.as_slice() != chal.as_ref() {
481            return Err(WebauthnError::MismatchedChallenge);
482        }
483
484        // Verify that the client's origin matches one of our allowed origins..
485        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        // ATM most browsers do not send this value, so we must default to
497        // `false`. See [WebauthnConfig::allow_cross_origin] doc-comment for
498        // more.
499        if !self.allow_cross_origin && data.client_data_json.cross_origin.unwrap_or(false) {
500            return Err(WebauthnError::CredentialCrossOrigin);
501        }
502
503        // Verify that the value of C.tokenBinding.status matches the state of Token Binding for the
504        // TLS connection over which the assertion was obtained. If Token Binding was used on that
505        // TLS connection, also verify that C.tokenBinding.id matches the base64url encoding of the
506        // Token Binding ID for the connection.
507        //
508        //  This could be reasonably complex to do, given that we could be behind a load balancer
509        // or we may not directly know the status of TLS inside this api. I'm open to creative
510        // suggestions on this topic!
511        //
512
513        // Let hash be the result of computing a hash over response.clientDataJSON using SHA-256
514        //
515        //   clarifying point - this is the hash of bytes.
516        //
517        // First you have to decode this from base64!!! This really could just be implementation
518        // specific though ...
519        let client_data_json_hash = compute_sha256(data.client_data_json_bytes.as_slice());
520
521        // Perform CBOR decoding on the attestationObject field of the AuthenticatorAttestationResponse
522        // structure to obtain the attestation statement format fmt, the authenticator data authData,
523        // and the attestation statement attStmt.
524        //
525        // Done as part of try_from
526
527        // Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the
528        // Relying Party.
529        //
530        //  NOW: Remember that RP ID https://w3c.github.io/webauthn/#rp-id is NOT THE SAME as the RP name
531        // it's actually derived from the RP origin.
532        if data.attestation_object.auth_data.rp_id_hash != self.rp_id_hash {
533            return Err(WebauthnError::InvalidRPIDHash);
534        }
535
536        // Verify that the User Present bit of the flags in authData is set.
537        if !data.attestation_object.auth_data.user_present {
538            return Err(WebauthnError::UserNotPresent);
539        }
540
541        // TODO: Is it possible to verify the attachement policy and resident
542        // key requirement here?
543
544        // If user verification is required for this registration, verify that the User Verified bit
545        // of the flags in authData is set.
546        if matches!(policy, UserVerificationPolicy::Required)
547            && !data.attestation_object.auth_data.user_verified
548        {
549            return Err(WebauthnError::UserNotVerified);
550        }
551
552        // Verify that the "alg" parameter in the credential public key in authData matches the alg
553        // attribute of one of the items in options.pubKeyCredParams.
554        //
555        // WARNING: This is actually done after attestation as the credential public key
556        // is NOT available yet!
557
558        // Verify that the values of the client extension outputs in clientExtensionResults and the
559        // authenticator extension outputs in the extensions in authData are as expected,
560        // considering the client extension input values that were given as the extensions option in
561        // the create() call. In particular, any extension identifier values in the
562        // clientExtensionResults and the extensions in authData MUST be also be present as
563        // extension identifier values in the extensions member of options, i.e., no extensions are
564        // present that were not requested. In the general case, the meaning of "are as expected" is
565        // specific to the Relying Party and which extensions are in use.
566
567        debug!(
568            "extensions: {:?}",
569            data.attestation_object.auth_data.extensions
570        );
571
572        // Only packed, tpm and apple allow extensions to be verified!
573
574        // Determine the attestation statement format by performing a USASCII case-sensitive match on
575        // fmt against the set of supported WebAuthn Attestation Statement Format Identifier values.
576        // An up-to-date list of registered WebAuthn Attestation Statement Format Identifier values
577        // is maintained in the IANA registry of the same name [WebAuthn-Registries].
578        // ( https://www.rfc-editor.org/rfc/rfc8809 )
579        //
580        //  https://w3c.github.io/webauthn-3/#packed-attestation
581        //  https://w3c.github.io/webauthn-3/#tpm-attestation
582        //  https://w3c.github.io/webauthn-3/#android-key-attestation
583        //  https://w3c.github.io/webauthn-3/#android-safetynet-attestation
584        //  https://w3c.github.io/webauthn-3/#fido-u2f-attestation
585        //  https://w3c.github.io/webauthn-3/#none-attestation
586        //  https://www.w3.org/TR/webauthn-3/#sctn-apple-anonymous-attestation
587        //
588        let attest_format = AttestationFormat::try_from(data.attestation_object.fmt.as_str())
589            .map_err(|()| WebauthnError::AttestationNotSupported)?;
590
591        // Verify that attStmt is a correct attestation statement, conveying a valid attestation
592        // signature, by using the attestation statement format fmt’s verification procedure given
593        // attStmt, authData and the hash of the serialized client data.
594
595        let acd = data
596            .attestation_object
597            .auth_data
598            .acd
599            .as_ref()
600            .ok_or(WebauthnError::MissingAttestationCredentialData)?;
601
602        // Now, match based on the attest_format
603        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            // AttestationMetadata::None,
615            AttestationFormat::Tpm => {
616                verify_tpm_attestation(acd, &data.attestation_object, &client_data_json_hash)?
617            }
618            // AttestationMetadata::None,
619            AttestationFormat::AppleAnonymous => verify_apple_anonymous_attestation(
620                acd,
621                &data.attestation_object,
622                &client_data_json_hash,
623            )?,
624            // AttestationMetadata::None,
625            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            &reg.extensions,
647            attest_format,
648            &data.transports,
649        );
650
651        // Now based on result ...
652
653        // If validation is successful, obtain a list of acceptable trust anchors (attestation
654        // root certificates or ECDAA-Issuer public keys) for that attestation type and attestation
655        // statement format fmt, from a trusted source or from policy. For example, the FIDO Metadata
656        // Service [FIDOMetadataService] provides one way to obtain such information, using the
657        // aaguid in the attestedCredentialData in authData.
658        //
659        // Assess the attestation trustworthiness using the outputs of the verification procedure in step 19, as follows:
660        //
661        // * If no attestation was provided, verify that None attestation is acceptable under Relying Party policy.
662        // * If self attestation was used, verify that self attestation is acceptable under Relying Party policy.
663        // * Otherwise, use the X.509 certificates returned as the attestation trust path from the verification
664        //     procedure to verify that the attestation public key either correctly chains up to an acceptable
665        //     root certificate, or is itself an acceptable certificate (i.e., it and the root certificate
666        //     obtained in Step 20 may be the same).
667
668        // If the attestation statement attStmt successfully verified but is not trustworthy per step 21 above,
669        // the Relying Party SHOULD fail the registration ceremony.
670
671        let attested_ca_crt = if let Some(ca_list) = attestation_cas {
672            // If given a set of ca's assert that our attestation actually matched one.
673            let ca_crt = verify_attestation_ca_chain(
674                &credential.attestation.data,
675                ca_list,
676                danger_disable_certificate_time_checks,
677            )?;
678
679            // It may seem odd to unwrap the option and make this not verified at this point,
680            // but in this case because we have the ca_list and none was the result (which happens)
681            // in some cases, we need to map that through. But we need verify_attesation_ca_chain
682            // to still return these option types due to re-attestation in the future.
683            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        // Assert that the aaguid of the device, is within the authority of this CA (if
695        // a list of aaguids was provided, and the ca blanket allows verification).
696        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 not present, fail.
704                        if !att_ca_crt.aaguids().contains_key(aaguid) {
705                            return Err(WebauthnError::AttestationUntrustedAaguid);
706                        }
707                    }
708                    _ => {
709                        // Fail
710                        return Err(WebauthnError::AttestationFormatMissingAaguid);
711                    }
712                }
713            }
714        };
715
716        // Verify that the credential public key alg is one of the allowed algorithms.
717        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        // OUT OF SPEC - Allow rejection of synchronised credentials if desired by the caller.
730        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        // OUT OF SPEC - It is invalid for a credential to indicate it is backed up
736        // but not that it is elligible for backup
737        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        // OUT OF SPEC - exclude any credential that is in our exclude list.
743        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        // If the attestation statement attStmt verified successfully and is found to be trustworthy,
752        // then register the new credential with the account that was denoted in options.user:
753        //
754        // For us, we return the credential for the caller to persist.
755        // If trust failed, we have already returned an Err before this point.
756
757        // Associate the credentialId with the transport hints returned by calling
758        // credential.response.getTransports(). This value SHOULD NOT be modified before or after
759        // storing it. It is RECOMMENDED to use this value to populate the transports of the
760        // allowCredentials option in future get() calls to help the client know how to find a
761        // suitable authenticator.
762        //
763        // Done as part of credential construction if the transports are valid/trusted.
764
765        Ok(credential)
766    }
767
768    // https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion
769    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        // Steps 1 through 7 are performed by the caller of this fn.
779
780        // Let cData, authData and sig denote the value of credential’s response's clientDataJSON,
781        // authenticatorData, and signature respectively.
782        //
783        // Let JSONtext be the result of running UTF-8 decode on the value of cData.
784        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        /*
792        Let C, the client data claimed as used for the signature, be the result of running an
793        implementation-specific JSON parser on JSONtext.
794            Note: C may be any implementation-specific data structure representation, as long as
795            C’s components are referenceable, as required by this algorithm.
796        */
797
798        // Verify that the value of C.type is the string webauthn.get.
799        if c.type_ != "webauthn.get" {
800            return Err(WebauthnError::InvalidClientDataType);
801        }
802
803        // Verify that the value of C.challenge matches the challenge that was sent to the
804        // authenticator in the PublicKeyCredentialRequestOptions passed to the get() call.
805        if c.challenge.as_slice() != chal.as_ref() {
806            return Err(WebauthnError::MismatchedChallenge);
807        }
808
809        // Verify that the value of C.origin matches one of our allowed origins.
810        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        // Verify that the value of C.tokenBinding.status matches the state of Token Binding for the
822        // TLS connection over which the attestation was obtained. If Token Binding was used on that
823        // TLS connection, also verify that C.tokenBinding.id matches the base64url encoding of the
824        // Token Binding ID for the connection.
825
826        // Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party.
827        // Note that if we have an appid stored in the state and the client indicates it has used the appid extension,
828        // we also check the hash against this appid in addition to the Relying Party
829        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        // Verify that the User Present bit of the flags in authData is set.
844        if !data.authenticator_data.user_present {
845            return Err(WebauthnError::UserNotPresent);
846        }
847
848        // If user verification is required for this assertion, verify that the User Verified bit of
849        // the flags in authData is set.
850        //
851        // We also check the historical policy too. See designs/authentication-use-cases.md
852
853        match (&policy, &cred.registration_policy) {
854            (_, UserVerificationPolicy::Required) | (UserVerificationPolicy::Required, _) => {
855                // If we requested required at registration or now, enforce that.
856                if !data.authenticator_data.user_verified {
857                    return Err(WebauthnError::UserNotVerified);
858                }
859            }
860            (_, UserVerificationPolicy::Preferred) => {
861                // If we asked for Preferred at registration, we MAY have established to the user
862                // that they are required to enter a pin, so we SHOULD enforce this.
863                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            // Pass - we can not know if verification was requested to the client in the past correctly.
869            // This means we can't know what it's behaviour is at the moment.
870            // We must allow unverified tokens now.
871            _ => {}
872        }
873
874        // OUT OF SPEC - if the backup eligibility of this device has changed, this may represent
875        // a compromise of the credential, tampering with the device, or some other change to its
876        // risk profile from when it was originally enrolled. Reject the authentication if this
877        // situation occurs.
878
879        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        // OUT OF SPEC - It is invalid for a credential to indicate it is backed up
892        // but not that it is elligible for backup
893        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        // Verify that the values of the client extension outputs in clientExtensionResults and the
899        // authenticator extension outputs in the extensions in authData are as expected, considering
900        // the client extension input values that were given as the extensions option in the get()
901        // call. In particular, any extension identifier values in the clientExtensionResults and
902        // the extensions in authData MUST be also be present as extension identifier values in the
903        // extensions member of options, i.e., no extensions are present that were not requested. In
904        // the general case, the meaning of "are as expected" is specific to the Relying Party and
905        // which extensions are in use.
906        //
907        // Note: Since all extensions are OPTIONAL for both the client and the authenticator, the
908        // Relying Party MUST be prepared to handle cases where none or not all of the requested
909        // extensions were acted upon.
910        debug!("extensions: {:?}", data.authenticator_data.extensions);
911
912        // Let hash be the result of computing a hash over the cData using SHA-256.
913        let client_data_json_hash = compute_sha256(data.client_data_bytes.as_slice());
914
915        // Using the credential public key, verify that sig is a valid signature
916        // over the binary concatenation of authData and hash.
917        // Note: This verification step is compatible with signatures generated by FIDO U2F
918        // authenticators. See §6.1.2 FIDO U2F Signature Format Compatibility.
919
920        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    /// Generate a new challenge builder for client authentication. This is the first
939    /// step in authentication of a credential. This function will return an
940    /// authentication builder allowing you to customise the parameters that will be
941    /// sent to the client.
942    ///
943    /// If creds is an empty `Vec` this implies a discoverable authentication attempt.
944    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    /// Generate a new challenge for client authentication from the parameters defined by the
976    /// [ChallengeAuthenticateBuilder].
977    ///
978    /// This function will return:
979    ///
980    /// * a [RequestChallengeResponse], which is sent to the client (and can be serialised as JSON).
981    ///   A web application would then pass the structure to the browser's navigator.credentials.create() API to trigger authentication.
982    ///
983    /// * an [AuthenticationState], which must be persisted on the server side. Your application
984    ///   must associate the state with a private session ID, to prevent use in other sessions.
985    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        // Get the user's existing creds if any.
1000        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        // Extract the appid from the extensions to store it in the AuthenticationState
1010        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        // Store the chal associated to the user.
1016        // Now put that into the correct challenge format
1017        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    /// Process an authenticate response from the authenticator and browser. This
1040    /// is the output of `navigator.credentials.get()`, which is processed by this
1041    /// function. If the authentication fails, appropriate errors will be returned.
1042    ///
1043    /// This requires the associated AuthenticationState that was created by
1044    /// generate_challenge_authenticate
1045    ///
1046    /// On successful authentication, an Ok result is returned. The Ok may contain the CredentialID
1047    /// and associated counter, which you *should* update for security purposes. If the Ok returns
1048    /// `None` then the credential does not have a counter.
1049    pub fn authenticate_credential(
1050        &self,
1051        rsp: &PublicKeyCredential,
1052        state: &AuthenticationState,
1053    ) -> Result<AuthenticationResult, WebauthnError> {
1054        // Steps 1 through 4 are client side.
1055
1056        // https://w3c.github.io/webauthn/#verifying-assertion
1057        // Lookup challenge
1058
1059        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        // If the allowCredentials option was given when this authentication ceremony was initiated,
1069        // verify that credential.id identifies one of the public key credentials that were listed in allowCredentials.
1070        //
1071        // We always supply allowCredentials in this library, so we expect creds as a vec of credentials
1072        // that would be equivalent to what was allowed.
1073        // trace!("rsp: {:?}", rsp);
1074
1075        let cred = {
1076            // Identify the user being authenticated and verify that this user is the owner of the public
1077            // key credential source credentialSource identified by credential.id:
1078            //
1079            //  If the user was identified before the authentication ceremony was initiated, e.g., via a
1080            //  username or cookie,
1081            //      verify that the identified user is the owner of credentialSource. If
1082            //      credential.response.userHandle is present, let userHandle be its value. Verify that
1083            //      userHandle also maps to the same user.
1084
1085            //  If the user was not identified before the authentication ceremony was initiated,
1086            //      verify that credential.response.userHandle is present, and that the user identified
1087            //      by this value is the owner of credentialSource.
1088            //
1089            //      Note: User-less mode is handled by calling `AuthenticationState::set_allowed_credentials`
1090            //      after the caller extracts the userHandle and verifies the credential Source
1091
1092            // Using credential’s id attribute (or the corresponding rawId, if base64url encoding is
1093            // inappropriate for your use case), look up the corresponding credential public key.
1094            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        // Identify the user being authenticated and verify that this user is the owner of the public
1106        // key credential source credentialSource identified by credential.id:
1107
1108        //  - If the user was identified before the authentication ceremony was initiated, e.g.,
1109        //  via a username or cookie,
1110        //      verify that the identified user is the owner of credentialSource. If
1111        //      response.userHandle is present, let userHandle be its value. Verify that
1112        //      userHandle also maps to the same user.
1113
1114        // - If the user was not identified before the authentication ceremony was initiated,
1115        //      verify that response.userHandle is present, and that the user identified by this
1116        //      value is the owner of credentialSource.
1117
1118        // Using credential.id (or credential.rawId, if base64url encoding is inappropriate for your
1119        // use case), look up the corresponding credential public key and let credentialPublicKey be
1120        // that credential public key.
1121
1122        // * Due to the design of this library, in the majority of workflows the user MUST be known
1123        // before we begin, else we would not have the allowed Credentials list. When we proceed to
1124        // allowing resident keys (client side discoverable) such as username-less, then we will need
1125        // to consider how to proceed here. For now, username-less is such a hot-mess due to RK handling
1126        // being basicly non-existant, that there is no point. As a result, we have already enforced
1127        // these conditions.
1128
1129        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 the signature counter value authData.signCount is nonzero or the value stored in
1154        // conjunction with credential’s id attribute is nonzero, then run the following sub-step:
1155        if counter > 0 || cred.counter > 0 {
1156            // greater than the signature counter value stored in conjunction with credential’s id attribute.
1157            //       Update the stored signature counter value, associated with credential’s id attribute,
1158            //       to be the value of authData.signCount.
1159            // less than or equal to the signature counter value stored in conjunction with credential’s id attribute.
1160            //      This is a signal that the authenticator may be cloned, i.e. at least two copies
1161            //      of the credential private key may exist and are being used in parallel. Relying
1162            //      Parties should incorporate this information into their risk scoring. Whether the
1163            //      Relying Party updates the stored signature counter value in this case, or not,
1164            //      or fails the authentication ceremony or not, is Relying Party-specific.
1165            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    /// Returns the RP name
1247    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 the crypto operations of the webauthn impl
1276
1277    #[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        // Generated by a yubico 5
1289        // Make a "fake" challenge, where we know what the values should be ....
1290
1291        let zero_chal = Challenge::new((0..CHALLENGE_SIZE_BYTES).map(|_| 0).collect::<Vec<u8>>());
1292
1293        // This is the json challenge this would generate in this case, with the rp etc.
1294        // {"publicKey":{"rp":{"name":"http://127.0.0.1:8080/auth"},"user":{"id":"xxx","name":"xxx","displayName":"xxx"},"challenge":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","pubKeyCredParams":[{"type":"public-key","alg":-7}],"timeout":6000,"attestation":"direct"}}
1295
1296        // And this is the response, from a real device. Let's register it!
1297
1298        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        // turn it into our "deserialised struct"
1309        let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(rsp).unwrap();
1310
1311        // Now register, providing our fake challenge.
1312        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    // These are vectors from https://github.com/duo-labs/webauthn
1328    #[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        // Example generated using navigator.credentials.create on Chrome Version 77.0.3865.120
1392        // using Touch ID on MacBook running MacOS 10.15
1393        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        // Generated by a yubico 5
1569        // Make a "fake" challenge, where we know what the values should be ....
1570
1571        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        // Create the fake credential that we know is associated
1577        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        // Persist it to our fake db.
1617
1618        // Captured authentication attempt
1619        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        // Now verify it!
1635        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        // Captured authentication attempt, this mentions the appid extension has been used, but we still provide a valid RPID
1647        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        // Now verify it, as the RPID is valid, the appid should be ignored
1666        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        // Generated by a yubico 5
1691        // Make a "fake" challenge, where we know what the values should be ....
1692
1693        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        // Create the fake credential that we know is associated
1699        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        // Persist it to our fake db.
1737
1738        // Captured authentication attempt, this client has used the appid extension
1739        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        // Now verify it!
1757        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        // Captured authentication attempt, this client has NOT used the appid extension, but is providing the appid anyway
1769        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        // This will verify against the RPID while the client provided an appid, so it should fail
1787        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        // Assert this fails when the attestaion is missing.
1838        let result = wan.register_credential_internal(
1839            &rsp_d,
1840            UserVerificationPolicy::Preferred,
1841            &chal,
1842            &[],
1843            &[COSEAlgorithm::ES256],
1844            // This is what introduces the failure!
1845            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            // Exclude the matching CA!
1863            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        // Assert this fails when the attestaion ca is correct, but the aaguid is missing.
1875
1876        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        // This is to test migration between the x/y byte array to base64 format.
1933        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        // Attempt to request an AAGUID, but this format does not provide one.
2560        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            // Must disable time checks because the submission is limited to 5 days.
2590            true,
2591            &RequestRegistrationExtensions::default(),
2592            // Don't allow passkeys
2593            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            // Must disable time checks because the submission is limited to 5 days.
2620            true,
2621            &RequestRegistrationExtensions::default(),
2622            // Don't allow passkeys
2623            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            // Must disable time checks because the submission is limited to 5 days.
2647            true,
2648            &RequestRegistrationExtensions::default(),
2649            // Allow them.
2650            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            // Must disable time checks because the submission is limited to 5 days.
2788            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        // Given two credentials with differening policy
2812        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        // Ensure we get a bad result.
2894
2895        assert!(
2896            wan.new_challenge_authenticate_builder(creds.clone(), None)
2897                .unwrap_err()
2898                == WebauthnError::InconsistentUserVerificationPolicy
2899        );
2900
2901        // now mutate to different states to check.
2902        // cred 0 verified + uv::req
2903        // cred 1 verified + uv::req
2904        {
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        // now mutate to different states to check.
2930        // cred 0 verified + uv::dc
2931        // cred 1 verified + uv::dc
2932        {
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        // In this we visit from "https://sub.idm.example.com:8080" which is an effective domain
3079        // of the origin.
3080
3081        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    // ⚠️  Currently IGNORED as it appears that pixel 3a send INVALID attestation requests.
3218    #[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        // Currently UNSUPPORTED as openssl doesn't have eddsa management utils that we need.
3258        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    /// See https://github.com/kanidm/webauthn-rs/issues/105
3352    #[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            // Some(&AttestationCaList {
3470            //    cas: vec![AttestationCa::apple_webauthn_root_ca()],
3471            // }),
3472            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        // This is a known fault, solokeys emit invalid attestation with EDDSA
3735        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            &reg_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            &reg_extn,
3866            true,
3867        );
3868
3869        debug!(?result);
3870
3871        assert!(result.is_ok())
3872    }
3873}