webauthn_rp

WebAuthn RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

tests.rs (33169B)


      1 use super::{
      2     AuthenticatorAttachment, AuthenticatorSelectionCriteria, ClientCredentialCreationOptions,
      3     CoseAlgorithmIdentifier, CoseAlgorithmIdentifiers, CredProtect, CredentialMediationRequirement,
      4     ExtensionInfo, ExtensionOwned, ExtensionReq, FIVE_MINUTES, FourToSixtyThree, NonZeroU32,
      5     PublicKeyCredentialCreationOptionsOwned, PublicKeyCredentialUserEntityOwned,
      6     ResidentKeyRequirement, UserVerificationRequirement,
      7 };
      8 use serde_json::Error;
      9 #[expect(
     10     clippy::panic_in_result_fn,
     11     clippy::unwrap_used,
     12     reason = "OK in tests"
     13 )]
     14 #[expect(
     15     clippy::cognitive_complexity,
     16     clippy::too_many_lines,
     17     reason = "a lot to test"
     18 )]
     19 #[test]
     20 fn client_options() -> Result<(), Error> {
     21     let mut err =
     22         serde_json::from_str::<ClientCredentialCreationOptions<16>>(r#"{"bob":true}"#).unwrap_err();
     23     assert_eq!(
     24         err.to_string().get(..56),
     25         Some("unknown field `bob`, expected `mediation` or `publicKey`")
     26     );
     27     err = serde_json::from_str::<ClientCredentialCreationOptions<1>>(
     28         r#"{"mediation":"required","mediation":"required"}"#,
     29     )
     30     .unwrap_err();
     31     assert_eq!(
     32         err.to_string().get(..27),
     33         Some("duplicate field `mediation`")
     34     );
     35     let mut options = serde_json::from_str::<ClientCredentialCreationOptions<1>>("{}")?;
     36     assert!(matches!(
     37         options.mediation,
     38         CredentialMediationRequirement::Required
     39     ));
     40     assert!(options.public_key.rp_id.is_none());
     41     assert!(options.public_key.user.name.is_none());
     42     assert!(options.public_key.user.id.is_none());
     43     assert!(options.public_key.user.display_name.is_none());
     44     assert_eq!(
     45         options.public_key.pub_key_cred_params.0,
     46         CoseAlgorithmIdentifiers::ALL.0
     47     );
     48     assert_eq!(options.public_key.timeout, FIVE_MINUTES);
     49     assert_eq!(
     50         options
     51             .public_key
     52             .authenticator_selection
     53             .authenticator_attachment,
     54         AuthenticatorAttachment::None,
     55     );
     56     assert!(matches!(
     57         options.public_key.authenticator_selection.resident_key,
     58         ResidentKeyRequirement::Discouraged
     59     ));
     60     assert!(matches!(
     61         options.public_key.authenticator_selection.user_verification,
     62         UserVerificationRequirement::Preferred
     63     ));
     64     assert!(options.public_key.extensions.cred_props.is_none());
     65     assert!(matches!(
     66         options.public_key.extensions.cred_protect,
     67         CredProtect::None
     68     ));
     69     assert!(options.public_key.extensions.min_pin_length.is_none());
     70     assert!(options.public_key.extensions.prf.is_none());
     71     options = serde_json::from_str::<ClientCredentialCreationOptions<1>>(
     72         r#"{"mediation":null,"publicKey":null}"#,
     73     )?;
     74     assert!(matches!(
     75         options.mediation,
     76         CredentialMediationRequirement::Required
     77     ));
     78     assert!(options.public_key.rp_id.is_none());
     79     assert!(options.public_key.user.name.is_none());
     80     assert!(options.public_key.user.id.is_none());
     81     assert!(options.public_key.user.display_name.is_none());
     82     assert_eq!(
     83         options.public_key.pub_key_cred_params.0,
     84         CoseAlgorithmIdentifiers::ALL.0
     85     );
     86     assert_eq!(options.public_key.timeout, FIVE_MINUTES);
     87     assert_eq!(
     88         options
     89             .public_key
     90             .authenticator_selection
     91             .authenticator_attachment,
     92         AuthenticatorAttachment::None,
     93     );
     94     assert!(matches!(
     95         options.public_key.authenticator_selection.resident_key,
     96         ResidentKeyRequirement::Discouraged
     97     ));
     98     assert!(matches!(
     99         options.public_key.authenticator_selection.user_verification,
    100         UserVerificationRequirement::Preferred
    101     ));
    102     assert!(options.public_key.extensions.cred_props.is_none());
    103     assert!(matches!(
    104         options.public_key.extensions.cred_protect,
    105         CredProtect::None
    106     ));
    107     assert!(options.public_key.extensions.min_pin_length.is_none());
    108     assert!(options.public_key.extensions.prf.is_none());
    109     options = serde_json::from_str::<ClientCredentialCreationOptions<1>>(r#"{"publicKey":{}}"#)?;
    110     assert!(options.public_key.rp_id.is_none());
    111     assert!(options.public_key.user.name.is_none());
    112     assert!(options.public_key.user.id.is_none());
    113     assert!(options.public_key.user.display_name.is_none());
    114     assert_eq!(
    115         options.public_key.pub_key_cred_params.0,
    116         CoseAlgorithmIdentifiers::ALL.0
    117     );
    118     assert_eq!(options.public_key.timeout, FIVE_MINUTES);
    119     assert_eq!(
    120         options
    121             .public_key
    122             .authenticator_selection
    123             .authenticator_attachment,
    124         AuthenticatorAttachment::None,
    125     );
    126     assert!(matches!(
    127         options.public_key.authenticator_selection.resident_key,
    128         ResidentKeyRequirement::Discouraged
    129     ));
    130     assert!(matches!(
    131         options.public_key.authenticator_selection.user_verification,
    132         UserVerificationRequirement::Preferred
    133     ));
    134     assert!(options.public_key.extensions.cred_props.is_none());
    135     assert!(matches!(
    136         options.public_key.extensions.cred_protect,
    137         CredProtect::None
    138     ));
    139     assert!(options.public_key.extensions.min_pin_length.is_none());
    140     assert!(options.public_key.extensions.prf.is_none());
    141     options = serde_json::from_str::<ClientCredentialCreationOptions<1>>(
    142         r#"{"mediation":"conditional","publicKey":{"rp":{"name":"Example.com","id":"example.com"},"user":{"name":"bob","displayName":"Bob","id":"AQ"},"timeout":300000,"excludeCredentials":[],"attestation":"none","attestationFormats":["none"],"authenticatorSelection":{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"},"extensions":{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}},"pubKeyCredParams":[{"type":"public-key","alg":-8}],"hints":["security-key"],"challenge":null}}"#,
    143     )?;
    144     assert!(matches!(
    145         options.mediation,
    146         CredentialMediationRequirement::Conditional
    147     ));
    148     assert!(
    149         options
    150             .public_key
    151             .rp_id
    152             .is_some_and(|val| val.as_ref() == "example.com")
    153     );
    154     assert!(options.public_key.user.name.is_some_and(|val| val == "bob"));
    155     assert!(
    156         options
    157             .public_key
    158             .user
    159             .display_name
    160             .is_some_and(|val| val == "Bob")
    161     );
    162     assert!(
    163         options
    164             .public_key
    165             .user
    166             .id
    167             .is_some_and(|val| val.as_ref() == [1; 1])
    168     );
    169     assert_eq!(
    170         options.public_key.pub_key_cred_params.0,
    171         CoseAlgorithmIdentifiers::ALL
    172             .remove(CoseAlgorithmIdentifier::Mldsa87)
    173             .remove(CoseAlgorithmIdentifier::Mldsa65)
    174             .remove(CoseAlgorithmIdentifier::Mldsa44)
    175             .remove(CoseAlgorithmIdentifier::Es256)
    176             .remove(CoseAlgorithmIdentifier::Es384)
    177             .remove(CoseAlgorithmIdentifier::Rs256)
    178             .0
    179     );
    180     assert_eq!(options.public_key.timeout, FIVE_MINUTES);
    181     assert_eq!(
    182         options
    183             .public_key
    184             .authenticator_selection
    185             .authenticator_attachment,
    186         AuthenticatorAttachment::CrossPlatform,
    187     );
    188     assert!(matches!(
    189         options.public_key.authenticator_selection.resident_key,
    190         ResidentKeyRequirement::Required
    191     ));
    192     assert!(matches!(
    193         options.public_key.authenticator_selection.user_verification,
    194         UserVerificationRequirement::Required
    195     ));
    196     assert!(
    197         options
    198             .public_key
    199             .extensions
    200             .cred_props
    201             .is_some_and(|req| matches!(req, ExtensionReq::Allow))
    202     );
    203     assert!(
    204         matches!(options.public_key.extensions.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
    205     );
    206     assert!(
    207         options
    208             .public_key
    209             .extensions
    210             .min_pin_length
    211             .is_some_and(|min| min.0 == FourToSixtyThree::Four
    212                 && matches!(min.1, ExtensionInfo::AllowEnforceValue))
    213     );
    214     assert!(
    215         options
    216             .public_key
    217             .extensions
    218             .prf
    219             .is_some_and(|prf| prf.first.is_empty()
    220                 && prf.second.is_some_and(|p| p.is_empty())
    221                 && matches!(prf.ext_req, ExtensionReq::Allow))
    222     );
    223     Ok(())
    224 }
    225 #[expect(
    226     clippy::panic_in_result_fn,
    227     clippy::unwrap_used,
    228     reason = "OK in tests"
    229 )]
    230 #[expect(
    231     clippy::cognitive_complexity,
    232     clippy::too_many_lines,
    233     reason = "a lot to test"
    234 )]
    235 #[test]
    236 fn key_options() -> Result<(), Error> {
    237     let mut err =
    238         serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<16>>(r#"{"bob":true}"#)
    239             .unwrap_err();
    240     assert_eq!(
    241         err.to_string().get(..201),
    242         Some(
    243             "unknown field `bob`, expected one of `rp`, `user`, `challenge`, `pubKeyCredParams`, `timeout`, `excludeCredentials`, `authenticatorSelection`, `hints`, `extensions`, `attestation`, `attestationFormats`"
    244         )
    245     );
    246     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    247         r#"{"attestation":"none","attestation":"none"}"#,
    248     )
    249     .unwrap_err();
    250     assert_eq!(
    251         err.to_string().get(..29),
    252         Some("duplicate field `attestation`")
    253     );
    254     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    255         r#"{"challenge":"AAAAAAAAAAAAAAAAAAAAAA"}"#,
    256     )
    257     .unwrap_err();
    258     assert_eq!(
    259         err.to_string().get(..41),
    260         Some("invalid type: Option value, expected null")
    261     );
    262     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    263         r#"{"excludeCredentials":[{"type":"public-key","transports":["usb"],"id":"AAAAAAAAAAAAAAAAAAAAAA"}]}"#,
    264     )
    265     .unwrap_err();
    266     assert_eq!(err.to_string().get(..19), Some("trailing characters"));
    267     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    268         r#"{"attestation":"foo"}"#,
    269     )
    270     .unwrap_err();
    271     assert_eq!(
    272         err.to_string().get(..27),
    273         Some("invalid value: string \"foo\"")
    274     );
    275     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    276         r#"{"attestationFormats":["none","none"]}"#,
    277     )
    278     .unwrap_err();
    279     assert_eq!(
    280         err.to_string().get(..96),
    281         Some(
    282             "attestationFormats must be an empty sequence or contain exactly one string whose value is 'none'"
    283         )
    284     );
    285     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    286         r#"{"attestationFormats":["foo"]}"#,
    287     )
    288     .unwrap_err();
    289     assert_eq!(
    290         err.to_string().get(..42),
    291         Some("invalid value: string \"foo\", expected none")
    292     );
    293     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(r#"{"timeout":0}"#)
    294         .unwrap_err();
    295     assert_eq!(
    296         err.to_string().get(..50),
    297         Some("invalid value: integer `0`, expected a nonzero u32")
    298     );
    299     err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    300         r#"{"timeout":4294967296}"#,
    301     )
    302     .unwrap_err();
    303     assert_eq!(
    304         err.to_string().get(..59),
    305         Some("invalid value: integer `4294967296`, expected a nonzero u32")
    306     );
    307     let mut key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>("{}")?;
    308     assert!(key.rp_id.is_none());
    309     assert!(key.user.name.is_none());
    310     assert!(key.user.id.is_none());
    311     assert!(key.user.display_name.is_none());
    312     assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
    313     assert_eq!(key.timeout, FIVE_MINUTES);
    314     assert_eq!(
    315         key.authenticator_selection.authenticator_attachment,
    316         AuthenticatorAttachment::None,
    317     );
    318     assert!(matches!(
    319         key.authenticator_selection.resident_key,
    320         ResidentKeyRequirement::Discouraged
    321     ));
    322     assert!(matches!(
    323         key.authenticator_selection.user_verification,
    324         UserVerificationRequirement::Preferred
    325     ));
    326     assert!(key.extensions.cred_props.is_none());
    327     assert!(matches!(key.extensions.cred_protect, CredProtect::None));
    328     assert!(key.extensions.min_pin_length.is_none());
    329     assert!(key.extensions.prf.is_none());
    330     key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    331         r#"{"rp":null,"user":null,"timeout":null,"excludeCredentials":null,"attestation":null,"attestationFormats":null,"authenticatorSelection":null,"extensions":null,"pubKeyCredParams":null,"hints":null,"challenge":null}"#,
    332     )?;
    333     assert!(key.rp_id.is_none());
    334     assert!(key.user.name.is_none());
    335     assert!(key.user.id.is_none());
    336     assert!(key.user.display_name.is_none());
    337     assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
    338     assert_eq!(key.timeout, FIVE_MINUTES);
    339     assert_eq!(
    340         key.authenticator_selection.authenticator_attachment,
    341         AuthenticatorAttachment::None,
    342     );
    343     assert!(matches!(
    344         key.authenticator_selection.resident_key,
    345         ResidentKeyRequirement::Discouraged
    346     ));
    347     assert!(matches!(
    348         key.authenticator_selection.user_verification,
    349         UserVerificationRequirement::Preferred
    350     ));
    351     assert!(key.extensions.cred_props.is_none());
    352     assert!(matches!(key.extensions.cred_protect, CredProtect::None));
    353     assert!(key.extensions.min_pin_length.is_none());
    354     assert!(key.extensions.prf.is_none());
    355     key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    356         r#"{"rp":{},"user":{},"excludeCredentials":[],"attestationFormats":[],"authenticatorSelection":{},"extensions":{},"pubKeyCredParams":[],"hints":[]}"#,
    357     )?;
    358     assert!(key.rp_id.is_none());
    359     assert!(key.user.name.is_none());
    360     assert!(key.user.id.is_none());
    361     assert!(key.user.display_name.is_none());
    362     assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
    363     assert_eq!(
    364         key.authenticator_selection.authenticator_attachment,
    365         AuthenticatorAttachment::None,
    366     );
    367     assert!(matches!(
    368         key.authenticator_selection.resident_key,
    369         ResidentKeyRequirement::Discouraged
    370     ));
    371     assert!(matches!(
    372         key.authenticator_selection.user_verification,
    373         UserVerificationRequirement::Preferred
    374     ));
    375     assert!(key.extensions.cred_props.is_none());
    376     assert!(matches!(key.extensions.cred_protect, CredProtect::None));
    377     assert!(key.extensions.min_pin_length.is_none());
    378     assert!(key.extensions.prf.is_none());
    379     key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    380         r#"{"rp":{"name":null,"id":null},"user":{"name":null,"id":null,"displayName":null},"authenticatorSelection":{"residentKey":null,"requireResidentKey":null,"userVerification":null,"authenticatorAttachment":null},"extensions":{"credProps":null,"credentialProtectionPolicy":null,"enforceCredentialProtectionPolicy":null,"minPinLength":null,"prf":null}}"#,
    381     )?;
    382     assert!(key.rp_id.is_none());
    383     assert!(key.user.name.is_none());
    384     assert!(key.user.id.is_none());
    385     assert!(key.user.display_name.is_none());
    386     assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
    387     assert_eq!(
    388         key.authenticator_selection.authenticator_attachment,
    389         AuthenticatorAttachment::None,
    390     );
    391     assert!(matches!(
    392         key.authenticator_selection.resident_key,
    393         ResidentKeyRequirement::Discouraged
    394     ));
    395     assert!(matches!(
    396         key.authenticator_selection.user_verification,
    397         UserVerificationRequirement::Preferred
    398     ));
    399     assert!(key.extensions.cred_props.is_none());
    400     assert!(matches!(key.extensions.cred_protect, CredProtect::None));
    401     assert!(key.extensions.min_pin_length.is_none());
    402     assert!(key.extensions.prf.is_none());
    403     key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    404         r#"{"rp":{"name":"Example.com","id":"example.com"},"user":{"name":"bob","displayName":"Bob","id":"AQ"},"timeout":300000,"excludeCredentials":[],"attestation":"none","attestationFormats":["none"],"authenticatorSelection":{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"},"extensions":{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}},"pubKeyCredParams":[{"type":"public-key","alg":-8}],"hints":["security-key"],"challenge":null}"#,
    405     )?;
    406     assert!(key.rp_id.is_some_and(|val| val.as_ref() == "example.com"));
    407     assert!(key.user.name.is_some_and(|val| val == "bob"));
    408     assert!(key.user.display_name.is_some_and(|val| val == "Bob"));
    409     assert!(key.user.id.is_some_and(|val| val.as_ref() == [1; 1]));
    410     assert_eq!(
    411         key.pub_key_cred_params.0,
    412         CoseAlgorithmIdentifiers::ALL
    413             .remove(CoseAlgorithmIdentifier::Mldsa87)
    414             .remove(CoseAlgorithmIdentifier::Mldsa65)
    415             .remove(CoseAlgorithmIdentifier::Mldsa44)
    416             .remove(CoseAlgorithmIdentifier::Es256)
    417             .remove(CoseAlgorithmIdentifier::Es384)
    418             .remove(CoseAlgorithmIdentifier::Rs256)
    419             .0
    420     );
    421     assert_eq!(key.timeout, FIVE_MINUTES);
    422     assert_eq!(
    423         key.authenticator_selection.authenticator_attachment,
    424         AuthenticatorAttachment::CrossPlatform,
    425     );
    426     assert!(matches!(
    427         key.authenticator_selection.resident_key,
    428         ResidentKeyRequirement::Required
    429     ));
    430     assert!(matches!(
    431         key.authenticator_selection.user_verification,
    432         UserVerificationRequirement::Required
    433     ));
    434     assert!(
    435         key.extensions
    436             .cred_props
    437             .is_some_and(|req| matches!(req, ExtensionReq::Allow))
    438     );
    439     assert!(
    440         matches!(key.extensions.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
    441     );
    442     assert!(
    443         key.extensions
    444             .min_pin_length
    445             .is_some_and(|min| min.0 == FourToSixtyThree::Four
    446                 && matches!(min.1, ExtensionInfo::AllowEnforceValue))
    447     );
    448     assert!(key.extensions.prf.is_some_and(|prf| prf.first.is_empty()
    449         && prf.second.is_some_and(|p| p.is_empty())
    450         && matches!(prf.ext_req, ExtensionReq::Allow)));
    451     key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<1>>(
    452         r#"{"timeout":4294967295}"#,
    453     )?;
    454     assert_eq!(key.timeout, NonZeroU32::MAX);
    455     Ok(())
    456 }
    457 #[expect(
    458     clippy::panic_in_result_fn,
    459     clippy::unwrap_used,
    460     reason = "OK in tests"
    461 )]
    462 #[expect(clippy::cognitive_complexity, reason = "a lot to test")]
    463 #[test]
    464 fn extension() -> Result<(), Error> {
    465     let mut err = serde_json::from_str::<ExtensionOwned>(r#"{"bob":true}"#).unwrap_err();
    466     assert_eq!(
    467         err.to_string().get(..138),
    468         Some(
    469             "unknown field `bob`, expected one of `credProps`, `credentialProtectionPolicy`, `enforceCredentialProtectionPolicy`, `minPinLength`, `prf`"
    470         )
    471     );
    472     err = serde_json::from_str::<ExtensionOwned>(r#"{"credProps":true,"credProps":true}"#)
    473         .unwrap_err();
    474     assert_eq!(
    475         err.to_string().get(..27),
    476         Some("duplicate field `credProps`")
    477     );
    478     err = serde_json::from_str::<ExtensionOwned>(r#"{"enforceCredentialProtectionPolicy":null}"#)
    479         .unwrap_err();
    480     assert_eq!(
    481         err.to_string().get(..84),
    482         Some(
    483             "'enforceCredentialProtectionPolicy' must not exist when 'credentialProtectionPolicy'"
    484         )
    485     );
    486     err = serde_json::from_str::<ExtensionOwned>(
    487         r#"{"enforceCredentialProtectionPolicy":false,"credentialProtectionPolicy":null}"#,
    488     )
    489     .unwrap_err();
    490     assert_eq!(
    491         err.to_string().get(..103),
    492         Some(
    493             "'enforceCredentialProtectionPolicy' must be null or not exist when 'credentialProtectionPolicy' is null"
    494         )
    495     );
    496     let mut ext = serde_json::from_str::<ExtensionOwned>(
    497         r#"{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}}"#,
    498     )?;
    499     assert!(
    500         ext.cred_props
    501             .is_some_and(|props| matches!(props, ExtensionReq::Allow))
    502     );
    503     assert!(
    504         matches!(ext.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
    505     );
    506     assert!(
    507         ext.min_pin_length
    508             .is_some_and(|min| min.0 == FourToSixtyThree::Four
    509                 && matches!(min.1, ExtensionInfo::AllowEnforceValue))
    510     );
    511     assert!(ext.prf.is_some_and(|prf| prf.first.is_empty()
    512         && prf.second.is_some_and(|v| v.is_empty())
    513         && matches!(prf.ext_req, ExtensionReq::Allow)));
    514     ext = serde_json::from_str::<ExtensionOwned>(
    515         r#"{"credProps":null,"credentialProtectionPolicy":null,"enforceCredentialProtectionPolicy":null,"minPinLength":null,"prf":null}"#,
    516     )?;
    517     assert!(ext.cred_props.is_none());
    518     assert!(matches!(ext.cred_protect, CredProtect::None));
    519     assert!(ext.min_pin_length.is_none());
    520     assert!(ext.prf.is_none());
    521     ext = serde_json::from_str::<ExtensionOwned>("{}")?;
    522     assert!(ext.cred_props.is_none());
    523     assert!(matches!(ext.cred_protect, CredProtect::None));
    524     assert!(ext.min_pin_length.is_none());
    525     assert!(ext.prf.is_none());
    526     ext = serde_json::from_str::<ExtensionOwned>(r#"{"credentialProtectionPolicy":null}"#)?;
    527     assert!(matches!(ext.cred_protect, CredProtect::None));
    528     ext = serde_json::from_str::<ExtensionOwned>(
    529         r#"{"credentialProtectionPolicy":"userVerificationOptional"}"#,
    530     )?;
    531     assert!(
    532         matches!(ext.cred_protect, CredProtect::UserVerificationOptional(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
    533     );
    534     ext = serde_json::from_str::<ExtensionOwned>(
    535         r#"{"credentialProtectionPolicy":"userVerificationOptionalWithCredentialIDList","enforceCredentialProtectionPolicy":null}"#,
    536     )?;
    537     assert!(
    538         matches!(ext.cred_protect, CredProtect::UserVerificationOptionalWithCredentialIdList(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
    539     );
    540     Ok(())
    541 }
    542 #[expect(
    543     clippy::panic_in_result_fn,
    544     clippy::unwrap_used,
    545     reason = "OK in tests"
    546 )]
    547 #[test]
    548 fn user_entity() -> Result<(), Error> {
    549     let mut err = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<16>>(r#"{"bob":true}"#)
    550         .unwrap_err();
    551     assert_eq!(
    552         err.to_string().get(..64),
    553         Some("unknown field `bob`, expected one of `id`, `name`, `displayName`")
    554     );
    555     err = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>(
    556         r#"{"name":"bob","name":"bob"}"#,
    557     )
    558     .unwrap_err();
    559     assert_eq!(err.to_string().get(..22), Some("duplicate field `name`"));
    560     let mut user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>(
    561         r#"{"id":"AQ","name":"bob","displayName":"Bob"}"#,
    562     )?;
    563     assert!(
    564         user.id
    565             .is_some_and(|val| val.as_slice() == [1; 1].as_slice())
    566     );
    567     assert!(user.name.is_some_and(|val| val == "bob"));
    568     assert!(user.display_name.is_some_and(|val| val == "Bob"));
    569     user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>(
    570         r#"{"id":null,"name":null,"displayName":null}"#,
    571     )?;
    572     assert!(user.name.is_none());
    573     assert!(user.display_name.is_none());
    574     assert!(user.id.is_none());
    575     user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<1>>("{}")?;
    576     assert!(user.name.is_none());
    577     assert!(user.display_name.is_none());
    578     assert!(user.id.is_none());
    579     Ok(())
    580 }
    581 #[expect(
    582     clippy::panic_in_result_fn,
    583     clippy::unwrap_used,
    584     reason = "OK in tests"
    585 )]
    586 #[expect(
    587     clippy::cognitive_complexity,
    588     clippy::too_many_lines,
    589     reason = "a lot to test"
    590 )]
    591 #[test]
    592 fn auth_crit() -> Result<(), Error> {
    593     let mut err = serde_json::from_str::<AuthenticatorSelectionCriteria>("null").unwrap_err();
    594     assert_eq!(
    595         err.to_string().get(..59),
    596         Some("invalid type: null, expected AuthenticatorSelectionCriteria")
    597     );
    598     err = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    599         r#"{"residentKey":"required","requireResidentKey":false}"#,
    600     )
    601     .unwrap_err();
    602     assert_eq!(
    603         err.to_string().get(..62),
    604         Some("'residentKey' is 'required', but 'requireResidentKey' is false")
    605     );
    606     err = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    607         r#"{"residentKey":"preferred","requireResidentKey":true}"#,
    608     )
    609     .unwrap_err();
    610     assert_eq!(
    611         err.to_string().get(..65),
    612         Some("'residentKey' is not 'required', but 'requireResidentKey' is true")
    613     );
    614     err = serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"residentKey":"prefered"}"#)
    615         .unwrap_err();
    616     assert_eq!(
    617         err.to_string().get(..84),
    618         Some(
    619             "invalid value: string \"prefered\", expected 'required', 'discouraged', or 'preferred'"
    620         )
    621     );
    622     err = serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"bob":true}"#).unwrap_err();
    623     assert_eq!(
    624         err.to_string().get(..119),
    625         Some(
    626             "unknown field `bob`, expected one of `authenticatorAttachment`, `residentKey`, `requireResidentKey`, `userVerification`"
    627         )
    628     );
    629     err = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    630         r#"{"requireResidentKey":true,"requireResidentKey":true}"#,
    631     )
    632     .unwrap_err();
    633     assert_eq!(
    634         err.to_string().get(..36),
    635         Some("duplicate field `requireResidentKey`")
    636     );
    637     let mut crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    638         r#"{"authenticatorAttachment":"platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"}"#,
    639     )?;
    640     assert_eq!(
    641         crit.authenticator_attachment,
    642         AuthenticatorAttachment::Platform,
    643     );
    644     assert!(matches!(
    645         crit.resident_key,
    646         ResidentKeyRequirement::Required
    647     ));
    648     assert!(matches!(
    649         crit.user_verification,
    650         UserVerificationRequirement::Required
    651     ));
    652     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    653         r#"{"authenticatorAttachment":null,"residentKey":null,"requireResidentKey":null,"userVerification":null}"#,
    654     )?;
    655     assert_eq!(crit.authenticator_attachment, AuthenticatorAttachment::None,);
    656     assert!(matches!(
    657         crit.resident_key,
    658         ResidentKeyRequirement::Discouraged
    659     ));
    660     assert!(matches!(
    661         crit.user_verification,
    662         UserVerificationRequirement::Preferred
    663     ));
    664     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>("{}")?;
    665     assert_eq!(crit.authenticator_attachment, AuthenticatorAttachment::None,);
    666     assert!(matches!(
    667         crit.resident_key,
    668         ResidentKeyRequirement::Discouraged
    669     ));
    670     assert!(matches!(
    671         crit.user_verification,
    672         UserVerificationRequirement::Preferred
    673     ));
    674     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    675         r#"{"residentKey":"preferred","requireResidentKey":false}"#,
    676     )?;
    677     assert_eq!(crit.authenticator_attachment, AuthenticatorAttachment::None,);
    678     assert!(matches!(
    679         crit.resident_key,
    680         ResidentKeyRequirement::Preferred
    681     ));
    682     assert!(matches!(
    683         crit.user_verification,
    684         UserVerificationRequirement::Preferred
    685     ));
    686     crit =
    687         serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"residentKey":"preferred"}"#)?;
    688     assert!(matches!(
    689         crit.resident_key,
    690         ResidentKeyRequirement::Preferred
    691     ));
    692     crit =
    693         serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"requireResidentKey":true}"#)?;
    694     assert!(matches!(
    695         crit.resident_key,
    696         ResidentKeyRequirement::Required
    697     ));
    698     crit =
    699         serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"requireResidentKey":false}"#)?;
    700     assert!(matches!(
    701         crit.resident_key,
    702         ResidentKeyRequirement::Discouraged
    703     ));
    704     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"residentKey":"required"}"#)?;
    705     assert!(matches!(
    706         crit.resident_key,
    707         ResidentKeyRequirement::Required
    708     ));
    709     crit =
    710         serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"residentKey":"discouraged"}"#)?;
    711     assert!(matches!(
    712         crit.resident_key,
    713         ResidentKeyRequirement::Discouraged
    714     ));
    715     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    716         r#"{"residentKey":"discouraged","requireResidentKey":null}"#,
    717     )?;
    718     assert!(matches!(
    719         crit.resident_key,
    720         ResidentKeyRequirement::Discouraged
    721     ));
    722     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    723         r#"{"residentKey":"required","requireResidentKey":null}"#,
    724     )?;
    725     assert!(matches!(
    726         crit.resident_key,
    727         ResidentKeyRequirement::Required
    728     ));
    729     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    730         r#"{"residentKey":null,"requireResidentKey":true}"#,
    731     )?;
    732     assert!(matches!(
    733         crit.resident_key,
    734         ResidentKeyRequirement::Required
    735     ));
    736     crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
    737         r#"{"residentKey":null,"requireResidentKey":false}"#,
    738     )?;
    739     assert!(matches!(
    740         crit.resident_key,
    741         ResidentKeyRequirement::Discouraged
    742     ));
    743     Ok(())
    744 }
    745 #[expect(
    746     clippy::panic_in_result_fn,
    747     clippy::unwrap_used,
    748     reason = "OK in tests"
    749 )]
    750 #[test]
    751 fn cose_algs() -> Result<(), Error> {
    752     let mut err = serde_json::from_str::<CoseAlgorithmIdentifiers>("null").unwrap_err();
    753     assert_eq!(
    754         err.to_string().get(..53),
    755         Some("invalid type: null, expected CoseAlgorithmIdentifiers")
    756     );
    757     err = serde_json::from_str::<CoseAlgorithmIdentifiers>("[null]").unwrap_err();
    758     assert_eq!(
    759         err.to_string().get(..37),
    760         Some("invalid type: null, expected PubParam")
    761     );
    762     err = serde_json::from_str::<CoseAlgorithmIdentifiers>("[{}]").unwrap_err();
    763     assert_eq!(err.to_string().get(..19), Some("missing field `alg`"));
    764     err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
    765         r#"[{"type":"public-key","alg":-7,"foo":true}]"#,
    766     )
    767     .unwrap_err();
    768     assert_eq!(
    769         err.to_string().get(..45),
    770         Some("unknown field `foo`, expected `type` or `alg`")
    771     );
    772     err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
    773         r#"[{"type":"public-key","alg":-7,"alg":-7}]"#,
    774     )
    775     .unwrap_err();
    776     assert_eq!(err.to_string().get(..21), Some("duplicate field `alg`"));
    777     err = serde_json::from_str::<CoseAlgorithmIdentifiers>(r#"[{"type":"public-key","alg":null}]"#)
    778         .unwrap_err();
    779     assert_eq!(
    780         err.to_string().get(..52),
    781         Some("invalid type: null, expected CoseAlgorithmIdentifier")
    782     );
    783     err = serde_json::from_str::<CoseAlgorithmIdentifiers>(r#"[{"type":null,"alg":-8}]"#)
    784         .unwrap_err();
    785     assert_eq!(
    786         err.to_string().get(..39),
    787         Some("invalid type: null, expected public-key")
    788     );
    789     err = serde_json::from_str::<CoseAlgorithmIdentifiers>(r#"[{"type":"public-key","alg":-6}]"#)
    790         .unwrap_err();
    791     assert_eq!(
    792         err.to_string().get(..73),
    793         Some("invalid value: integer `-6`, expected -50, -49, -48, -8, -7, -35, or -257")
    794     );
    795     err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
    796         r#"[{"type":"public-key","alg":-7},{"type":"public-key","alg":-7}]"#,
    797     )
    798     .unwrap_err();
    799     assert_eq!(
    800         err.to_string().get(..49),
    801         Some("pubKeyCredParams contained duplicate Es256 values")
    802     );
    803     err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
    804         r#"[{"type":"public-key","alg":-7},{"type":"public-key","alg":-8}]"#,
    805     )
    806     .unwrap_err();
    807     assert_eq!(
    808         err.to_string().get(..79),
    809         Some("pubKeyCredParams contained Eddsa, but it was preceded by Es256, Es384, or Rs256")
    810     );
    811     let mut alg = serde_json::from_str::<CoseAlgorithmIdentifiers>(
    812         r#"[{"type":"public-key","alg":-8},{"alg":-7}]"#,
    813     )?;
    814     assert!(alg.contains(CoseAlgorithmIdentifier::Eddsa));
    815     assert!(alg.contains(CoseAlgorithmIdentifier::Es256));
    816     assert!(!alg.contains(CoseAlgorithmIdentifier::Mldsa87));
    817     assert!(!alg.contains(CoseAlgorithmIdentifier::Mldsa65));
    818     assert!(!alg.contains(CoseAlgorithmIdentifier::Mldsa44));
    819     assert!(!alg.contains(CoseAlgorithmIdentifier::Es384));
    820     assert!(!alg.contains(CoseAlgorithmIdentifier::Rs256));
    821     alg = serde_json::from_str::<CoseAlgorithmIdentifiers>("[]")?;
    822     assert!(alg.contains(CoseAlgorithmIdentifier::Mldsa87));
    823     assert!(alg.contains(CoseAlgorithmIdentifier::Mldsa65));
    824     assert!(alg.contains(CoseAlgorithmIdentifier::Mldsa44));
    825     assert!(alg.contains(CoseAlgorithmIdentifier::Eddsa));
    826     assert!(alg.contains(CoseAlgorithmIdentifier::Es256));
    827     assert!(alg.contains(CoseAlgorithmIdentifier::Es384));
    828     assert!(alg.contains(CoseAlgorithmIdentifier::Rs256));
    829     Ok(())
    830 }