test_token.rs (36763B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use authenticator::authenticatorservice::{RegisterArgs, SignArgs}; 6 use authenticator::crypto::{ecdsa_p256_sha256_sign_raw, COSEAlgorithm, COSEKey, SharedSecret}; 7 use authenticator::ctap2::{ 8 attestation::{ 9 AAGuid, AttestationObject, AttestationStatement, AttestationStatementPacked, 10 AttestedCredentialData, AuthenticatorData, AuthenticatorDataFlags, Extension, 11 HmacSecretResponse, 12 }, 13 client_data::ClientDataHash, 14 commands::{ 15 client_pin::{ClientPIN, ClientPinResponse, PINSubcommand}, 16 get_assertion::{ 17 GetAssertion, GetAssertionResponse, GetAssertionResult, HmacGetSecretOrPrf, 18 HmacSecretExtension, 19 }, 20 get_info::{AuthenticatorInfo, AuthenticatorOptions, AuthenticatorVersion}, 21 get_version::{GetVersion, U2FInfo}, 22 make_credentials::{HmacCreateSecretOrPrf, MakeCredentials, MakeCredentialsResult}, 23 reset::Reset, 24 selection::Selection, 25 RequestCtap1, RequestCtap2, StatusCode, 26 }, 27 preflight::CheckKeyHandle, 28 server::{ 29 AuthenticatorAttachment, CredentialProtectionPolicy, PublicKeyCredentialDescriptor, 30 PublicKeyCredentialUserEntity, RelyingParty, 31 }, 32 }; 33 use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError}; 34 use authenticator::{ctap2, statecallback::StateCallback}; 35 use authenticator::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice}; 36 use authenticator::{RegisterResult, SignResult, StatusUpdate}; 37 use base64::Engine; 38 use moz_task::RunnableBuilder; 39 use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_OK}; 40 use nsstring::{nsACString, nsAString, nsCString, nsString}; 41 use rand::{thread_rng, RngCore}; 42 use std::cell::{Ref, RefCell}; 43 use std::collections::{hash_map::Entry, HashMap}; 44 use std::ops::{Deref, DerefMut}; 45 use std::sync::atomic::{AtomicU32, Ordering}; 46 use std::sync::mpsc::Sender; 47 use std::sync::{Arc, Mutex}; 48 use thin_vec::ThinVec; 49 use xpcom::interfaces::{nsICredentialParameters, nsIWebAuthnAutoFillEntry}; 50 use xpcom::{xpcom_method, RefPtr}; 51 52 // All TestTokens use this fixed, randomly generated, AAGUID 53 const VIRTUAL_TOKEN_AAGUID: AAGuid = AAGuid([ 54 0x68, 0xe1, 0x00, 0xa5, 0x0b, 0x47, 0x91, 0x04, 0xb8, 0x54, 0x97, 0xa9, 0xba, 0x51, 0x06, 0x38, 55 ]); 56 57 #[derive(Debug)] 58 struct TestTokenCredential { 59 id: Vec<u8>, 60 privkey: Vec<u8>, 61 user_handle: Vec<u8>, 62 sign_count: AtomicU32, 63 is_discoverable_credential: bool, 64 rp: RelyingParty, 65 credential_protection_policy: CredentialProtectionPolicy, 66 } 67 68 impl TestTokenCredential { 69 fn assert( 70 &self, 71 client_data_hash: &ClientDataHash, 72 flags: AuthenticatorDataFlags, 73 ) -> Result<GetAssertionResponse, HIDError> { 74 let credentials = Some(PublicKeyCredentialDescriptor { 75 id: self.id.clone(), 76 transports: vec![], 77 }); 78 79 let auth_data = AuthenticatorData { 80 rp_id_hash: self.rp.hash(), 81 flags, 82 counter: self.sign_count.fetch_add(1, Ordering::Relaxed), 83 credential_data: None, 84 extensions: Extension::default(), 85 }; 86 87 let user = Some(PublicKeyCredentialUserEntity { 88 id: self.user_handle.clone(), 89 ..Default::default() 90 }); 91 92 let mut data = auth_data.to_vec(); 93 data.extend_from_slice(client_data_hash.as_ref()); 94 let signature = 95 ecdsa_p256_sha256_sign_raw(&self.privkey, &data).or(Err(HIDError::DeviceError))?; 96 97 Ok(GetAssertionResponse { 98 credentials, 99 auth_data, 100 signature, 101 user, 102 number_of_credentials: Some(1), 103 }) 104 } 105 } 106 107 #[derive(Debug)] 108 struct TestToken { 109 protocol: FidoProtocol, 110 transport: String, 111 versions: Vec<AuthenticatorVersion>, 112 has_resident_key: bool, 113 has_user_verification: bool, 114 is_user_consenting: bool, 115 is_user_verified: bool, 116 // This is modified in `make_credentials` which takes a &TestToken, but we only allow one transaction at a time. 117 credentials: RefCell<Vec<TestTokenCredential>>, 118 pin_token: [u8; 32], 119 shared_secret: Option<SharedSecret>, 120 authenticator_info: Option<AuthenticatorInfo>, 121 } 122 123 impl TestToken { 124 fn new( 125 versions: Vec<AuthenticatorVersion>, 126 transport: String, 127 has_resident_key: bool, 128 has_user_verification: bool, 129 is_user_consenting: bool, 130 is_user_verified: bool, 131 ) -> TestToken { 132 let mut pin_token = [0u8; 32]; 133 thread_rng().fill_bytes(&mut pin_token); 134 Self { 135 protocol: FidoProtocol::CTAP2, 136 transport, 137 versions, 138 has_resident_key, 139 has_user_verification, 140 is_user_consenting, 141 is_user_verified, 142 credentials: RefCell::new(vec![]), 143 pin_token, 144 shared_secret: None, 145 authenticator_info: None, 146 } 147 } 148 149 fn insert_credential( 150 &self, 151 id: &[u8], 152 privkey: &[u8], 153 rp: &RelyingParty, 154 is_discoverable_credential: bool, 155 user_handle: &[u8], 156 sign_count: u32, 157 credential_protection_policy: CredentialProtectionPolicy, 158 ) { 159 let c = TestTokenCredential { 160 id: id.to_vec(), 161 privkey: privkey.to_vec(), 162 rp: rp.clone(), 163 is_discoverable_credential, 164 user_handle: user_handle.to_vec(), 165 sign_count: AtomicU32::new(sign_count), 166 credential_protection_policy, 167 }; 168 169 let mut credlist = self.credentials.borrow_mut(); 170 171 match credlist.binary_search_by_key(&id, |probe| &probe.id) { 172 Ok(_) => {} 173 Err(idx) => credlist.insert(idx, c), 174 } 175 } 176 177 fn get_credentials(&self) -> Ref<'_, Vec<TestTokenCredential>> { 178 self.credentials.borrow() 179 } 180 181 fn delete_credential(&mut self, id: &[u8]) -> bool { 182 let mut credlist = self.credentials.borrow_mut(); 183 if let Ok(idx) = credlist.binary_search_by_key(&id, |probe| &probe.id) { 184 credlist.remove(idx); 185 return true; 186 } 187 188 false 189 } 190 191 fn delete_all_credentials(&mut self) { 192 self.credentials.borrow_mut().clear(); 193 } 194 195 fn has_credential(&self, id: &[u8]) -> bool { 196 self.credentials 197 .borrow() 198 .binary_search_by_key(&id, |probe| &probe.id) 199 .is_ok() 200 } 201 202 fn max_supported_version(&self) -> AuthenticatorVersion { 203 self.authenticator_info 204 .as_ref() 205 .map_or(AuthenticatorVersion::U2F_V2, |info| { 206 info.max_supported_version() 207 }) 208 } 209 } 210 211 impl FidoDevice for TestToken { 212 fn pre_init(&mut self) -> Result<(), HIDError> { 213 Ok(()) 214 } 215 216 fn should_try_ctap2(&self) -> bool { 217 true 218 } 219 220 fn initialized(&self) -> bool { 221 true 222 } 223 224 fn is_u2f(&mut self) -> bool { 225 true 226 } 227 228 fn get_shared_secret(&self) -> Option<&SharedSecret> { 229 self.shared_secret.as_ref() 230 } 231 232 fn set_shared_secret(&mut self, shared_secret: SharedSecret) { 233 self.shared_secret = Some(shared_secret); 234 } 235 236 fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> { 237 self.authenticator_info.as_ref() 238 } 239 240 fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { 241 self.authenticator_info = Some(authenticator_info); 242 } 243 244 fn get_protocol(&self) -> FidoProtocol { 245 self.protocol 246 } 247 248 fn downgrade_to_ctap1(&mut self) { 249 self.protocol = FidoProtocol::CTAP1 250 } 251 } 252 253 impl FidoDeviceIO for TestToken { 254 fn send_msg_cancellable<Out, Req: RequestCtap1<Output = Out> + RequestCtap2<Output = Out>>( 255 &mut self, 256 msg: &Req, 257 keep_alive: &dyn Fn() -> bool, 258 ) -> Result<Out, HIDError> { 259 if !self.initialized() { 260 return Err(HIDError::DeviceNotInitialized); 261 } 262 263 match self.get_protocol() { 264 FidoProtocol::CTAP1 => self.send_ctap1_cancellable(msg, keep_alive), 265 FidoProtocol::CTAP2 => self.send_cbor_cancellable(msg, keep_alive), 266 } 267 } 268 269 fn send_cbor_cancellable<Req: RequestCtap2>( 270 &mut self, 271 msg: &Req, 272 _keep_alive: &dyn Fn() -> bool, 273 ) -> Result<Req::Output, HIDError> { 274 msg.send_to_virtual_device(self) 275 } 276 277 fn send_ctap1_cancellable<Req: RequestCtap1>( 278 &mut self, 279 msg: &Req, 280 _keep_alive: &dyn Fn() -> bool, 281 ) -> Result<Req::Output, HIDError> { 282 msg.send_to_virtual_device(self) 283 } 284 } 285 286 impl VirtualFidoDevice for TestToken { 287 fn check_key_handle(&self, req: &CheckKeyHandle) -> Result<(), HIDError> { 288 let credlist = self.credentials.borrow(); 289 let req_rp_hash = req.rp.hash(); 290 let eligible_cred_iter = credlist.iter().filter(|x| x.rp.hash() == req_rp_hash); 291 for credential in eligible_cred_iter { 292 if req.key_handle == credential.id { 293 return Ok(()); 294 } 295 } 296 Err(HIDError::DeviceError) 297 } 298 299 fn client_pin(&self, req: &ClientPIN) -> Result<ClientPinResponse, HIDError> { 300 match req.subcommand { 301 PINSubcommand::GetKeyAgreement => { 302 // We don't need to save, or even know, the private key for the public key returned 303 // here because we have access to the shared secret derived on the client side. 304 let (_private, public) = COSEKey::generate(COSEAlgorithm::ECDH_ES_HKDF256) 305 .map_err(|_| HIDError::DeviceError)?; 306 Ok(ClientPinResponse { 307 key_agreement: Some(public), 308 ..Default::default() 309 }) 310 } 311 PINSubcommand::GetPinUvAuthTokenUsingUvWithPermissions => { 312 // TODO: permissions 313 if !self.is_user_consenting || !self.is_user_verified { 314 return Err(HIDError::Command(CommandError::StatusCode( 315 StatusCode::OperationDenied, 316 None, 317 ))); 318 } 319 let secret = match self.shared_secret.as_ref() { 320 Some(secret) => secret, 321 _ => return Err(HIDError::DeviceError), 322 }; 323 let encrypted_pin_token = match secret.encrypt(&self.pin_token) { 324 Ok(token) => token, 325 _ => return Err(HIDError::DeviceError), 326 }; 327 Ok(ClientPinResponse { 328 pin_token: Some(encrypted_pin_token), 329 ..Default::default() 330 }) 331 } 332 _ => Err(HIDError::UnsupportedCommand), 333 } 334 } 335 336 fn get_assertion(&self, req: &GetAssertion) -> Result<Vec<GetAssertionResult>, HIDError> { 337 // Algorithm 6.2.2 from CTAP 2.1 338 // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg 339 340 // 1. zero length pinUvAuthParam 341 // (not implemented) 342 343 // 2. Validate pinUvAuthParam 344 // Handled by caller 345 346 // 3. Initialize "uv" and "up" bits to false 347 let mut flags = AuthenticatorDataFlags::empty(); 348 349 // 4. Handle all options 350 // 4.1 and 4.2 351 let effective_uv_opt = 352 req.options.user_verification.unwrap_or(false) && req.pin_uv_auth_param.is_none(); 353 354 // 4.3 355 if effective_uv_opt && !self.has_user_verification { 356 return Err(HIDError::Command(CommandError::StatusCode( 357 StatusCode::InvalidOption, 358 None, 359 ))); 360 } 361 362 // 4.4 rk 363 // (not implemented, we don't encode it) 364 365 // 4.5 366 let effective_up_opt = req.options.user_presence.unwrap_or(true); 367 368 // 5. alwaysUv 369 // (not implemented) 370 371 // 6. User verification 372 // TODO: Permissions, (maybe) validate pinUvAuthParam 373 if self.is_user_verified && (effective_uv_opt || req.pin_uv_auth_param.is_some()) { 374 flags |= AuthenticatorDataFlags::USER_VERIFIED; 375 } 376 377 // 7. Locate credentials 378 let credlist = self.credentials.borrow(); 379 let req_rp_hash = req.rp.hash(); 380 let eligible_cred_iter = credlist.iter().filter(|x| x.rp.hash() == req_rp_hash); 381 382 // 8. Set up=true if evidence of user interaction was provided in step 6. 383 // (not applicable, we use pinUvAuthParam) 384 385 // 9. User presence test 386 if effective_up_opt { 387 if self.is_user_consenting { 388 flags |= AuthenticatorDataFlags::USER_PRESENT; 389 } else { 390 return Err(HIDError::Command(CommandError::StatusCode( 391 StatusCode::UpRequired, 392 None, 393 ))); 394 } 395 } 396 397 // 10. Extensions 398 let hmac_secret_response = match &req.extensions.hmac_secret { 399 Some(HmacGetSecretOrPrf::Prf(HmacSecretExtension { 400 salt1, salt2: None, .. 401 })) => { 402 // Not much point in using an actual PRF here, the identity function 403 // will work since salt1 is guaranteed to be 32 bytes. 404 let mut eval = vec![0u8; 32]; 405 eval[..].copy_from_slice(salt1); 406 self.get_shared_secret() 407 .map(|secret| secret.encrypt(&eval).ok()) 408 .flatten() 409 } 410 Some(HmacGetSecretOrPrf::Prf(HmacSecretExtension { 411 salt1, 412 salt2: Some(salt2), 413 .. 414 })) => { 415 // Likewise, the identity function is fine for tests. 416 let mut eval = vec![0u8; 64]; 417 eval[0..32].copy_from_slice(salt1); 418 eval[32..64].copy_from_slice(salt2); 419 self.get_shared_secret() 420 .map(|secret| secret.encrypt(&eval).ok()) 421 .flatten() 422 } 423 _ => None, 424 }; 425 426 let mut assertions: Vec<GetAssertionResult> = vec![]; 427 if !req.allow_list.is_empty() { 428 // 11. Non-discoverable credential case 429 // return at most one assertion matching an allowed credential ID 430 for credential in eligible_cred_iter { 431 if !self.is_user_verified 432 && credential.credential_protection_policy 433 == CredentialProtectionPolicy::UserVerificationRequired 434 { 435 // Enforce the credential protection policy given that we have an allow list. 436 continue; 437 } 438 if req.allow_list.iter().any(|x| x.id == credential.id) { 439 let mut assertion: GetAssertionResponse = 440 credential.assert(&req.client_data_hash, flags)?; 441 if req.allow_list.len() == 1 442 && self.max_supported_version() == AuthenticatorVersion::FIDO_2_0 443 { 444 // CTAP 2.0 authenticators are allowed to omit the credential ID in the 445 // response if the allow list contains exactly one entry. This behavior is 446 // a common source of bugs, e.g. Bug 1864504, so we'll exercise it here. 447 assertion.credentials = None; 448 } 449 assertion.auth_data.extensions = Extension::default(); 450 assertion.auth_data.extensions.hmac_secret = match &hmac_secret_response { 451 Some(resp) => Some(HmacSecretResponse::Secret(resp.clone())), 452 None => None, 453 }; 454 assertions.push(GetAssertionResult { 455 assertion: assertion.into(), 456 attachment: AuthenticatorAttachment::Unknown, 457 extensions: Default::default(), 458 }); 459 break; 460 } 461 } 462 } else { 463 // 12. Discoverable credential case 464 // return any number of assertions from credentials bound to this RP ID 465 for credential in eligible_cred_iter.filter(|x| x.is_discoverable_credential) { 466 if !(self.is_user_verified 467 || credential.credential_protection_policy 468 == CredentialProtectionPolicy::UserVerificationOptional) 469 { 470 // Enforce the credential protection policy given that we do not have an allow list. 471 continue; 472 } 473 let mut assertion: GetAssertionResponse = 474 credential.assert(&req.client_data_hash, flags)?.into(); 475 assertion.auth_data.extensions = Extension::default(); 476 assertion.auth_data.extensions.hmac_secret = match &hmac_secret_response { 477 Some(resp) => Some(HmacSecretResponse::Secret(resp.clone())), 478 None => None, 479 }; 480 assertions.push(GetAssertionResult { 481 assertion: assertion.into(), 482 attachment: AuthenticatorAttachment::Unknown, 483 extensions: Default::default(), 484 }); 485 } 486 } 487 488 if assertions.is_empty() { 489 return Err(HIDError::Command(CommandError::StatusCode( 490 StatusCode::NoCredentials, 491 None, 492 ))); 493 } 494 495 Ok(assertions) 496 } 497 498 fn get_info(&self) -> Result<AuthenticatorInfo, HIDError> { 499 // This is a CTAP2.1 device with internal user verification support 500 Ok(AuthenticatorInfo { 501 versions: self.versions.clone(), 502 options: AuthenticatorOptions { 503 platform_device: self.transport == "internal", 504 resident_key: self.has_resident_key, 505 pin_uv_auth_token: Some(self.has_user_verification), 506 user_verification: Some(self.has_user_verification), 507 ..Default::default() 508 }, 509 ..Default::default() 510 }) 511 } 512 513 fn get_version(&self, _req: &GetVersion) -> Result<U2FInfo, HIDError> { 514 Err(HIDError::UnsupportedCommand) 515 } 516 517 fn make_credentials(&self, req: &MakeCredentials) -> Result<MakeCredentialsResult, HIDError> { 518 // Algorithm 6.1.2 from CTAP 2.1 519 // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg 520 521 // 1. zero length pinUvAuthParam 522 // (not implemented) 523 524 // 2. Validate pinUvAuthParam 525 // Handled by caller 526 527 // 3. Validate pubKeyCredParams 528 if !req 529 .pub_cred_params 530 .iter() 531 .any(|x| x.alg == COSEAlgorithm::ES256) 532 { 533 return Err(HIDError::Command(CommandError::StatusCode( 534 StatusCode::UnsupportedAlgorithm, 535 None, 536 ))); 537 } 538 539 // 4. initialize "uv" and "up" bits to false 540 let mut flags = AuthenticatorDataFlags::empty(); 541 542 // 5. process all options 543 544 // 5.1 and 5.2 545 let effective_uv_opt = 546 req.options.user_verification.unwrap_or(false) && req.pin_uv_auth_param.is_none(); 547 548 // 5.3 549 if effective_uv_opt && !self.has_user_verification { 550 return Err(HIDError::Command(CommandError::StatusCode( 551 StatusCode::InvalidOption, 552 None, 553 ))); 554 } 555 556 // 5.4 557 if req.options.resident_key.unwrap_or(false) && !self.has_resident_key { 558 return Err(HIDError::Command(CommandError::StatusCode( 559 StatusCode::UnsupportedOption, 560 None, 561 ))); 562 } 563 564 // 5.6 and 5.7 565 // Nothing to do. We don't provide a way to set up=false. 566 567 // 6. alwaysUv option ID 568 // (not implemented) 569 570 // 7. and 8. makeCredUvNotRqd option ID 571 // (not implemented) 572 573 // 9. enterprise attestation 574 // (not implemented) 575 576 // 11. User verification 577 // TODO: Permissions, (maybe) validate pinUvAuthParam 578 if self.is_user_verified { 579 flags |= AuthenticatorDataFlags::USER_VERIFIED; 580 } 581 582 // 12. exclude list 583 // TODO: credProtect 584 if req.exclude_list.iter().any(|x| self.has_credential(&x.id)) { 585 return Err(HIDError::Command(CommandError::StatusCode( 586 StatusCode::CredentialExcluded, 587 None, 588 ))); 589 } 590 591 // 13. Set up=true if evidence of user interaction was provided in step 11. 592 // (not applicable, we use pinUvAuthParam) 593 594 // 14. User presence test 595 if self.is_user_consenting { 596 flags |= AuthenticatorDataFlags::USER_PRESENT; 597 } else { 598 return Err(HIDError::Command(CommandError::StatusCode( 599 StatusCode::UpRequired, 600 None, 601 ))); 602 } 603 604 // 15. process extensions 605 let mut extensions = Extension::default(); 606 if req.extensions.min_pin_length == Some(true) { 607 // a real authenticator would 608 // 1) return an actual minimum pin length, and 609 // 2) check the RP ID against an allowlist before providing any data 610 extensions.min_pin_length = Some(4); 611 } 612 613 extensions.cred_protect = req.extensions.cred_protect; 614 if let Some(req_hmac_or_prf) = &req.extensions.hmac_secret { 615 match req_hmac_or_prf { 616 HmacCreateSecretOrPrf::HmacCreateSecret(true) | HmacCreateSecretOrPrf::Prf => { 617 extensions.hmac_secret = Some(HmacSecretResponse::Confirmed(true)); 618 } 619 _ => (), 620 } 621 } 622 623 if extensions.has_some() { 624 flags |= AuthenticatorDataFlags::EXTENSION_DATA; 625 } 626 627 // 16. Generate a new credential. 628 let (private, public) = 629 COSEKey::generate(COSEAlgorithm::ES256).map_err(|_| HIDError::DeviceError)?; 630 let counter = 0; 631 632 // 17. and 18. Store credential 633 // 634 // All of the credentials that we create are "resident"---we store the private key locally, 635 // and use a random value for the credential ID. The `req.options.resident_key` field 636 // determines whether we make the credential "discoverable". 637 let mut id = [0u8; 32]; 638 thread_rng().fill_bytes(&mut id); 639 self.insert_credential( 640 &id, 641 &private, 642 &req.rp, 643 req.options.resident_key.unwrap_or(false), 644 &req.user.clone().unwrap_or_default().id, 645 counter, 646 req.extensions 647 .cred_protect 648 .unwrap_or(CredentialProtectionPolicy::UserVerificationOptional), 649 ); 650 651 // 19. Generate attestation statement 652 flags |= AuthenticatorDataFlags::ATTESTED; 653 654 let auth_data = AuthenticatorData { 655 rp_id_hash: req.rp.hash(), 656 flags, 657 counter, 658 credential_data: Some(AttestedCredentialData { 659 aaguid: VIRTUAL_TOKEN_AAGUID, 660 credential_id: id.to_vec(), 661 credential_public_key: public, 662 }), 663 extensions, 664 }; 665 666 let mut data = auth_data.to_vec(); 667 data.extend_from_slice(req.client_data_hash.as_ref()); 668 669 let sig = ecdsa_p256_sha256_sign_raw(&private, &data).or(Err(HIDError::DeviceError))?; 670 671 let att_stmt = AttestationStatement::Packed(AttestationStatementPacked { 672 alg: COSEAlgorithm::ES256, 673 sig: sig.as_slice().into(), 674 attestation_cert: vec![], 675 }); 676 677 let result = MakeCredentialsResult { 678 attachment: AuthenticatorAttachment::Unknown, 679 att_obj: AttestationObject { 680 att_stmt, 681 auth_data, 682 }, 683 extensions: Default::default(), 684 }; 685 Ok(result) 686 } 687 688 fn reset(&self, _req: &Reset) -> Result<(), HIDError> { 689 Err(HIDError::UnsupportedCommand) 690 } 691 692 fn selection(&self, _req: &Selection) -> Result<(), HIDError> { 693 Err(HIDError::UnsupportedCommand) 694 } 695 } 696 697 #[xpcom(implement(nsICredentialParameters), atomic)] 698 struct CredentialParameters { 699 credential_id: Vec<u8>, 700 is_resident_credential: bool, 701 rp_id: String, 702 private_key: Vec<u8>, 703 user_handle: Vec<u8>, 704 sign_count: u32, 705 } 706 707 impl CredentialParameters { 708 xpcom_method!(get_credential_id => GetCredentialId() -> nsACString); 709 fn get_credential_id(&self) -> Result<nsCString, nsresult> { 710 Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD 711 .encode(&self.credential_id) 712 .into()) 713 } 714 715 xpcom_method!(get_is_resident_credential => GetIsResidentCredential() -> bool); 716 fn get_is_resident_credential(&self) -> Result<bool, nsresult> { 717 Ok(self.is_resident_credential) 718 } 719 720 xpcom_method!(get_rp_id => GetRpId() -> nsACString); 721 fn get_rp_id(&self) -> Result<nsCString, nsresult> { 722 Ok(nsCString::from(&self.rp_id)) 723 } 724 725 xpcom_method!(get_private_key => GetPrivateKey() -> nsACString); 726 fn get_private_key(&self) -> Result<nsCString, nsresult> { 727 Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD 728 .encode(&self.private_key) 729 .into()) 730 } 731 732 xpcom_method!(get_user_handle => GetUserHandle() -> nsACString); 733 fn get_user_handle(&self) -> Result<nsCString, nsresult> { 734 Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD 735 .encode(&self.user_handle) 736 .into()) 737 } 738 739 xpcom_method!(get_sign_count => GetSignCount() -> u32); 740 fn get_sign_count(&self) -> Result<u32, nsresult> { 741 Ok(self.sign_count) 742 } 743 } 744 745 #[xpcom(implement(nsIWebAuthnAutoFillEntry), atomic)] 746 struct WebAuthnAutoFillEntry { 747 rp: String, 748 credential_id: Vec<u8>, 749 } 750 751 impl WebAuthnAutoFillEntry { 752 xpcom_method!(get_provider => GetProvider() -> u8); 753 fn get_provider(&self) -> Result<u8, nsresult> { 754 Ok(nsIWebAuthnAutoFillEntry::PROVIDER_TEST_TOKEN) 755 } 756 757 xpcom_method!(get_user_name => GetUserName() -> nsAString); 758 fn get_user_name(&self) -> Result<nsString, nsresult> { 759 Ok(nsString::from("Test User")) 760 } 761 762 xpcom_method!(get_rp_id => GetRpId() -> nsAString); 763 fn get_rp_id(&self) -> Result<nsString, nsresult> { 764 Ok(nsString::from(&self.rp)) 765 } 766 767 xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>); 768 fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> { 769 Ok(self.credential_id.as_slice().into()) 770 } 771 } 772 773 #[derive(Default)] 774 pub(crate) struct TestTokenManager { 775 state: Arc<Mutex<HashMap<String, TestToken>>>, 776 } 777 778 impl TestTokenManager { 779 pub fn new() -> Self { 780 Default::default() 781 } 782 783 pub fn add_virtual_authenticator( 784 &self, 785 protocol: AuthenticatorVersion, 786 transport: String, 787 has_resident_key: bool, 788 has_user_verification: bool, 789 is_user_consenting: bool, 790 is_user_verified: bool, 791 ) -> Result<String, nsresult> { 792 let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 793 let token = TestToken::new( 794 vec![protocol], 795 transport, 796 has_resident_key, 797 has_user_verification, 798 is_user_consenting, 799 is_user_verified, 800 ); 801 loop { 802 let mut id = [0u8; 32]; 803 thread_rng().fill_bytes(&mut id); 804 let id = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&id); 805 match guard.deref_mut().entry(id.clone()) { 806 Entry::Occupied(_) => continue, 807 Entry::Vacant(v) => { 808 v.insert(token); 809 return Ok(id); 810 } 811 }; 812 } 813 } 814 815 pub fn remove_virtual_authenticator(&self, authenticator_id: &str) -> Result<(), nsresult> { 816 let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 817 guard 818 .deref_mut() 819 .remove(authenticator_id) 820 .ok_or(NS_ERROR_INVALID_ARG)?; 821 Ok(()) 822 } 823 824 pub fn add_credential( 825 &self, 826 authenticator_id: &str, 827 id: &[u8], 828 privkey: &[u8], 829 user_handle: &[u8], 830 sign_count: u32, 831 rp_id: String, 832 is_resident_credential: bool, 833 ) -> Result<(), nsresult> { 834 let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 835 let token = guard 836 .deref_mut() 837 .get_mut(authenticator_id) 838 .ok_or(NS_ERROR_INVALID_ARG)?; 839 let rp = RelyingParty::from(rp_id); 840 token.insert_credential( 841 id, 842 privkey, 843 &rp, 844 is_resident_credential, 845 user_handle, 846 sign_count, 847 CredentialProtectionPolicy::UserVerificationOptional, 848 ); 849 Ok(()) 850 } 851 852 pub fn get_credentials( 853 &self, 854 authenticator_id: &str, 855 ) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> { 856 let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 857 let token = guard 858 .get_mut(authenticator_id) 859 .ok_or(NS_ERROR_INVALID_ARG)?; 860 let credentials = token.get_credentials(); 861 let mut credentials_parameters = ThinVec::with_capacity(credentials.len()); 862 for credential in credentials.deref() { 863 // CTAP1 credentials are not currently supported here. 864 let credential_parameters = CredentialParameters::allocate(InitCredentialParameters { 865 credential_id: credential.id.clone(), 866 is_resident_credential: credential.is_discoverable_credential, 867 rp_id: credential.rp.id.clone(), 868 private_key: credential.privkey.clone(), 869 user_handle: credential.user_handle.clone(), 870 sign_count: credential.sign_count.load(Ordering::Relaxed), 871 }) 872 .query_interface::<nsICredentialParameters>() 873 .ok_or(NS_ERROR_FAILURE)?; 874 credentials_parameters.push(Some(credential_parameters)); 875 } 876 Ok(credentials_parameters) 877 } 878 879 pub fn remove_credential(&self, authenticator_id: &str, id: &[u8]) -> Result<(), nsresult> { 880 let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 881 let token = guard 882 .deref_mut() 883 .get_mut(authenticator_id) 884 .ok_or(NS_ERROR_INVALID_ARG)?; 885 if token.delete_credential(id) { 886 Ok(()) 887 } else { 888 Err(NS_ERROR_INVALID_ARG) 889 } 890 } 891 892 pub fn remove_all_credentials(&self, authenticator_id: &str) -> Result<(), nsresult> { 893 let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 894 let token = guard 895 .deref_mut() 896 .get_mut(authenticator_id) 897 .ok_or(NS_ERROR_INVALID_ARG)?; 898 token.delete_all_credentials(); 899 Ok(()) 900 } 901 902 pub fn set_user_verified( 903 &self, 904 authenticator_id: &str, 905 is_user_verified: bool, 906 ) -> Result<(), nsresult> { 907 let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 908 let token = guard 909 .deref_mut() 910 .get_mut(authenticator_id) 911 .ok_or(NS_ERROR_INVALID_ARG)?; 912 token.is_user_verified = is_user_verified; 913 Ok(()) 914 } 915 916 pub fn register( 917 &self, 918 _timeout_ms: u64, 919 ctap_args: RegisterArgs, 920 status: Sender<StatusUpdate>, 921 callback: StateCallback<Result<RegisterResult, AuthenticatorError>>, 922 ) { 923 if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") { 924 return; 925 } 926 927 let state_obj = self.state.clone(); 928 929 // Registration doesn't currently block, but it might in a future version, so we run it on 930 // a background thread. 931 let _ = RunnableBuilder::new("TestTokenManager::register", move || { 932 // TODO(Bug 1854278) We should actually run one thread per token here 933 // and attempt to fulfill this request in parallel. 934 for token in state_obj.lock().unwrap().values_mut() { 935 let _ = token.init(); 936 if ctap2::register( 937 token, 938 ctap_args.clone(), 939 status.clone(), 940 callback.clone(), 941 &|| true, 942 ) { 943 // callback was called 944 return; 945 } 946 } 947 948 // Send an error, if the callback wasn't called already. 949 callback.call(Err(AuthenticatorError::U2FToken(U2FTokenError::NotAllowed))); 950 }) 951 .may_block(true) 952 .dispatch_background_task(); 953 } 954 955 pub fn sign( 956 &self, 957 _timeout_ms: u64, 958 ctap_args: SignArgs, 959 status: Sender<StatusUpdate>, 960 callback: StateCallback<Result<SignResult, AuthenticatorError>>, 961 ) { 962 if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") { 963 return; 964 } 965 966 let state_obj = self.state.clone(); 967 968 // Signing can block during signature selection, so we need to run it on a background thread. 969 let _ = RunnableBuilder::new("TestTokenManager::sign", move || { 970 // TODO(Bug 1854278) We should actually run one thread per token here 971 // and attempt to fulfill this request in parallel. 972 for token in state_obj.lock().unwrap().values_mut() { 973 let _ = token.init(); 974 if ctap2::sign( 975 token, 976 ctap_args.clone(), 977 status.clone(), 978 callback.clone(), 979 &|| true, 980 ) { 981 // callback was called 982 return; 983 } 984 } 985 986 // Send an error, if the callback wasn't called already. 987 callback.call(Err(AuthenticatorError::U2FToken(U2FTokenError::NotAllowed))); 988 }) 989 .may_block(true) 990 .dispatch_background_task(); 991 } 992 993 pub fn has_user_verifying_platform_authenticator(&self) -> bool { 994 if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") { 995 return false; 996 } 997 998 for token in self.state.lock().unwrap().values_mut() { 999 let _ = token.init(); 1000 if token.transport.as_str() == "internal" && token.has_user_verification { 1001 return true; 1002 } 1003 } 1004 1005 false 1006 } 1007 1008 pub fn get_autofill_entries( 1009 &self, 1010 rp_id: &str, 1011 credential_filter: &Vec<PublicKeyCredentialDescriptor>, 1012 ) -> Result<ThinVec<Option<RefPtr<nsIWebAuthnAutoFillEntry>>>, nsresult> { 1013 let guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; 1014 let mut entries = ThinVec::new(); 1015 1016 for token in guard.values() { 1017 let credentials = token.get_credentials(); 1018 for credential in credentials.deref() { 1019 // The relying party ID must match. 1020 if !rp_id.eq(&credential.rp.id) { 1021 continue; 1022 } 1023 // Only discoverable credentials are admissible. 1024 if !credential.is_discoverable_credential { 1025 continue; 1026 } 1027 // Only credentials listed in the credential filter (if it is 1028 // non-empty) are admissible. 1029 if credential_filter.len() > 0 1030 && credential_filter 1031 .iter() 1032 .find(|cred| cred.id == credential.id) 1033 .is_none() 1034 { 1035 continue; 1036 } 1037 let entry = WebAuthnAutoFillEntry::allocate(InitWebAuthnAutoFillEntry { 1038 rp: credential.rp.id.clone(), 1039 credential_id: credential.id.clone(), 1040 }) 1041 .query_interface::<nsIWebAuthnAutoFillEntry>() 1042 .ok_or(NS_ERROR_FAILURE)?; 1043 entries.push(Some(entry)); 1044 } 1045 } 1046 Ok(entries) 1047 } 1048 }