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 }