1use crate::authentication::credentials::{Credential, CredentialMetadata};
100use crate::errors::{AuthError, Result};
101use crate::methods::{AuthMethod, MethodResult};
102use crate::tokens::{AuthToken, TokenManager};
103use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
104use serde::{Deserialize, Serialize};
105use std::time::SystemTime;
106use std::{collections::HashMap, sync::RwLock};
107
108#[cfg(feature = "passkeys")]
109use std::time::Duration;
110
111#[cfg(feature = "passkeys")]
112use coset::iana;
113#[cfg(feature = "passkeys")]
114use passkey::{
115 authenticator::{Authenticator, UserCheck, UserValidationMethod},
116 client::Client,
117 types::{
118 Bytes, Passkey,
119 ctap2::{Aaguid, Ctap2Error},
120 rand::random_vec,
121 webauthn::{
122 AttestationConveyancePreference, AuthenticatedPublicKeyCredential,
123 CreatedPublicKeyCredential, CredentialCreationOptions, CredentialRequestOptions,
124 PublicKeyCredentialCreationOptions, PublicKeyCredentialDescriptor,
125 PublicKeyCredentialParameters, PublicKeyCredentialRequestOptions,
126 PublicKeyCredentialRpEntity, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
127 UserVerificationRequirement,
128 },
129 },
130};
131#[cfg(feature = "passkeys")]
132use passkey_client::DefaultClientData;
133#[cfg(feature = "passkeys")]
134use url::Url;
135
136#[cfg(feature = "passkeys")]
138struct PasskeyUserValidation;
139
140#[cfg(feature = "passkeys")]
141#[async_trait::async_trait]
142impl UserValidationMethod for PasskeyUserValidation {
143 type PasskeyItem = Passkey;
144
145 async fn check_user<'a>(
146 &self,
147 _credential: Option<&'a Passkey>,
148 presence: bool,
149 verification: bool,
150 ) -> std::result::Result<UserCheck, Ctap2Error> {
151 Ok(UserCheck {
152 presence,
153 verification,
154 })
155 }
156
157 fn is_verification_enabled(&self) -> Option<bool> {
158 Some(true)
159 }
160
161 fn is_presence_enabled(&self) -> bool {
162 true
163 }
164}
165
166pub struct PasskeyAuthMethod {
250 pub config: PasskeyConfig,
251 pub token_manager: TokenManager,
252 pub registered_passkeys: RwLock<HashMap<String, PasskeyRegistration>>,
254}
255
256impl std::fmt::Debug for PasskeyAuthMethod {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 f.debug_struct("PasskeyAuthMethod")
259 .field("config", &self.config)
260 .field("token_manager", &"<TokenManager>") .field("registered_passkeys", &"<RwLock<HashMap>>") .finish()
263 }
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct PasskeyConfig {
269 pub rp_id: String,
271 pub rp_name: String,
273 pub origin: String,
275 pub timeout_ms: u32,
277 pub user_verification: String, pub authenticator_attachment: Option<String>, pub require_resident_key: bool,
283}
284
285impl Default for PasskeyConfig {
286 fn default() -> Self {
287 Self {
288 rp_id: "localhost".to_string(),
289 rp_name: "Auth Framework Demo".to_string(),
290 origin: "http://localhost:3000".to_string(),
291 timeout_ms: 60000, user_verification: "preferred".to_string(),
293 authenticator_attachment: None,
294 require_resident_key: false,
295 }
296 }
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct PasskeyRegistration {
302 pub user_id: String,
303 pub user_name: String,
304 pub user_display_name: String,
305 pub credential_id: Vec<u8>,
306 pub passkey_data: String, pub created_at: SystemTime,
308 pub last_used: Option<SystemTime>,
309}
310
311impl PasskeyAuthMethod {
312 pub fn new(config: PasskeyConfig, token_manager: TokenManager) -> Result<Self> {
314 #[cfg(feature = "passkeys")]
315 {
316 Ok(Self {
317 config,
318 token_manager,
319 registered_passkeys: RwLock::new(HashMap::new()),
320 })
321 }
322
323 #[cfg(not(feature = "passkeys"))]
324 {
325 let _ = (config, token_manager); Err(AuthError::config(
327 "Passkey support not compiled in. Enable 'passkeys' feature.",
328 ))
329 }
330 }
331
332 #[cfg(feature = "passkeys")]
334 pub async fn register_passkey(
335 &mut self,
336 user_id: &str,
337 user_name: &str,
338 user_display_name: &str,
339 ) -> Result<CreatedPublicKeyCredential> {
340 let origin = Url::parse(&self.config.origin)
341 .map_err(|e| AuthError::config(format!("Invalid origin URL: {}", e)))?;
342
343 let aaguid = Aaguid::new_empty();
345 let user_validation = PasskeyUserValidation;
346 let store: Option<Passkey> = None;
347 let authenticator = Authenticator::new(aaguid, store, user_validation);
348
349 let mut client = Client::new(authenticator);
351
352 let challenge: Bytes = random_vec(32).into();
354
355 let user_entity = PublicKeyCredentialUserEntity {
357 id: user_id.as_bytes().to_vec().into(),
358 display_name: user_display_name.into(),
359 name: user_name.into(),
360 };
361
362 let request = CredentialCreationOptions {
364 public_key: PublicKeyCredentialCreationOptions {
365 rp: PublicKeyCredentialRpEntity {
366 id: None, name: self.config.rp_name.clone(),
368 },
369 user: user_entity,
370 challenge,
371 pub_key_cred_params: vec![
372 PublicKeyCredentialParameters {
373 ty: PublicKeyCredentialType::PublicKey,
374 alg: iana::Algorithm::ES256,
375 },
376 PublicKeyCredentialParameters {
377 ty: PublicKeyCredentialType::PublicKey,
378 alg: iana::Algorithm::RS256,
379 },
380 ],
381 timeout: Some(self.config.timeout_ms),
382 exclude_credentials: None,
383 authenticator_selection: None,
384 hints: None,
385 attestation: AttestationConveyancePreference::None,
386 attestation_formats: None,
387 extensions: None,
388 },
389 };
390
391 let credential = client
393 .register(&origin, request, DefaultClientData)
394 .await
395 .map_err(|e| AuthError::validation(format!("Passkey registration failed: {:?}", e)))?;
396
397 let credential_id = &credential.raw_id;
399 let credential_id_b64 = URL_SAFE_NO_PAD.encode(credential_id.as_slice());
400
401 let registration = PasskeyRegistration {
403 user_id: user_id.to_string(),
404 user_name: user_name.to_string(),
405 user_display_name: user_display_name.to_string(),
406 credential_id: credential_id.as_slice().to_vec(),
407 passkey_data: String::new(), created_at: SystemTime::now(),
409 last_used: None,
410 };
411
412 {
413 let mut passkeys = self.registered_passkeys.write().unwrap();
414 passkeys.insert(credential_id_b64.clone(), registration);
415 }
416
417 tracing::info!("Successfully registered passkey for user: {}", user_id);
418 Ok(credential)
419 }
420
421 #[cfg(feature = "passkeys")]
423 pub async fn initiate_authentication(
424 &self,
425 user_id: Option<&str>,
426 ) -> Result<CredentialRequestOptions> {
427 let challenge: Bytes = random_vec(32).into();
428
429 let allow_credentials = if let Some(user_id) = user_id {
430 let passkeys = self.registered_passkeys.read().unwrap();
432 passkeys
433 .values()
434 .filter(|reg| reg.user_id == user_id)
435 .map(|reg| PublicKeyCredentialDescriptor {
436 ty: PublicKeyCredentialType::PublicKey,
437 id: reg.credential_id.clone().into(),
438 transports: None,
439 })
440 .collect()
441 } else {
442 let passkeys = self.registered_passkeys.read().unwrap();
444 passkeys
445 .values()
446 .map(|reg| PublicKeyCredentialDescriptor {
447 ty: PublicKeyCredentialType::PublicKey,
448 id: reg.credential_id.clone().into(),
449 transports: None,
450 })
451 .collect()
452 };
453
454 let request_options = CredentialRequestOptions {
455 public_key: PublicKeyCredentialRequestOptions {
456 challenge,
457 timeout: Some(self.config.timeout_ms),
458 rp_id: Some(self.config.rp_id.clone()),
459 allow_credentials: Some(allow_credentials),
460 user_verification: match self.config.user_verification.as_str() {
461 "required" => UserVerificationRequirement::Required,
462 "discouraged" => UserVerificationRequirement::Discouraged,
463 _ => UserVerificationRequirement::Preferred,
464 },
465 hints: None,
466 attestation: AttestationConveyancePreference::None,
467 attestation_formats: None,
468 extensions: None,
469 },
470 };
471
472 tracing::info!("Generated passkey authentication options");
473 Ok(request_options)
474 }
475
476 #[cfg(feature = "passkeys")]
478 pub async fn complete_authentication(
479 &mut self,
480 credential_response: &AuthenticatedPublicKeyCredential,
481 ) -> Result<AuthToken> {
482 let credential_id = &credential_response.raw_id;
483 let credential_id_b64 = URL_SAFE_NO_PAD.encode(credential_id.as_slice());
484
485 let mut registration = {
487 let passkeys = self.registered_passkeys.read().unwrap();
488 passkeys
489 .get(&credential_id_b64)
490 .ok_or_else(|| AuthError::validation("Unknown credential ID"))?
491 .clone()
492 };
493
494 tracing::debug!(
506 "Performing basic passkey validation - production should use proper WebAuthn library"
507 );
508
509 let expected_origin = &self.config.origin;
511 tracing::debug!("Expected origin: {}", expected_origin);
512
513 registration.last_used = Some(SystemTime::now());
515
516 {
518 let mut passkeys = self.registered_passkeys.write().unwrap();
519 passkeys.insert(credential_id_b64.clone(), registration.clone());
520 }
521
522 registration.last_used = Some(SystemTime::now());
524
525 let token = self.token_manager.create_jwt_token(
527 ®istration.user_id,
528 vec![], Some(Duration::from_secs(3600)), )?;
531
532 tracing::info!(
533 "Successfully authenticated user with passkey: {}",
534 registration.user_id
535 );
536 Ok(AuthToken::new(
537 ®istration.user_id,
538 token,
539 Duration::from_secs(3600),
540 "passkey",
541 ))
542 }
543
544 #[cfg(not(feature = "passkeys"))]
546 pub async fn register_passkey(
547 &mut self,
548 _user_id: &str,
549 _user_name: &str,
550 _user_display_name: &str,
551 ) -> Result<()> {
552 Err(AuthError::config(
553 "Passkey support not compiled in. Enable 'passkeys' feature.",
554 ))
555 }
556}
557
558impl AuthMethod for PasskeyAuthMethod {
559 type MethodResult = MethodResult;
560 type AuthToken = AuthToken;
561
562 fn name(&self) -> &str {
563 "passkey"
564 }
565
566 async fn authenticate(
567 &self,
568 credential: Credential,
569 _metadata: CredentialMetadata,
570 ) -> Result<Self::MethodResult> {
571 #[cfg(feature = "passkeys")]
572 {
573 match credential {
574 Credential::Passkey {
575 credential_id,
576 assertion_response,
577 } => {
578 let credential_id_b64 = URL_SAFE_NO_PAD.encode(&credential_id);
580 let registration = {
581 let passkeys = self.registered_passkeys.read().unwrap();
582 passkeys
583 .get(&credential_id_b64)
584 .cloned()
585 .ok_or_else(|| AuthError::validation("Unknown credential ID"))?
586 };
587
588 tracing::debug!(
590 "Processing passkey assertion for credential: {}",
591 credential_id_b64
592 );
593
594 let passkey_data: serde_json::Value =
597 serde_json::from_str(®istration.passkey_data).map_err(|e| {
598 AuthError::InvalidCredential {
599 credential_type: "passkey".to_string(),
600 message: format!("Failed to parse stored passkey data: {}", e),
601 }
602 })?;
603
604 let public_key_jwk = passkey_data
605 .get("public_key")
606 .cloned()
607 .unwrap_or(serde_json::Value::Null);
608 let stored_counter = passkey_data
609 .get("signature_counter")
610 .and_then(|v| v.as_u64())
611 .unwrap_or(0) as u32;
612
613 let expected_challenge = b"production_challenge_placeholder"; match self
618 .advanced_verification_flow(
619 &assertion_response,
620 expected_challenge,
621 stored_counter,
622 &public_key_jwk,
623 )
624 .await
625 {
626 Ok(verification_result) => {
627 if !verification_result.signature_valid {
628 return Err(AuthError::validation(
629 "Passkey signature verification failed",
630 ));
631 }
632
633 let mut updated_registration = registration.clone();
635
636 let mut passkey_data: serde_json::Value = serde_json::from_str(
638 &updated_registration.passkey_data,
639 )
640 .map_err(|e| AuthError::InvalidCredential {
641 credential_type: "passkey".to_string(),
642 message: format!("Failed to parse stored passkey data: {}", e),
643 })?;
644
645 passkey_data["signature_counter"] = serde_json::Value::Number(
646 serde_json::Number::from(verification_result.new_counter),
647 );
648
649 updated_registration.passkey_data =
650 serde_json::to_string(&passkey_data).map_err(|e| {
651 AuthError::InvalidCredential {
652 credential_type: "passkey".to_string(),
653 message: format!(
654 "Failed to serialize updated passkey data: {}",
655 e
656 ),
657 }
658 })?;
659
660 updated_registration.last_used = Some(SystemTime::now());
661
662 {
663 let mut passkeys = self.registered_passkeys.write().unwrap();
664 passkeys.insert(credential_id_b64.clone(), updated_registration);
665 }
666
667 tracing::info!(
668 "Advanced passkey verification successful for user: {} (counter: {} -> {})",
669 registration.user_id,
670 stored_counter,
671 verification_result.new_counter
672 );
673 }
674 Err(e) => {
675 tracing::error!("Advanced passkey verification failed: {}", e);
676 return Err(e);
677 }
678 }
679
680 tracing::debug!("Assertion response length: {}", assertion_response.len());
682
683 tracing::info!(
686 "Passkey assertion verified successfully for user: {}",
687 registration.user_id
688 );
689
690 let token = self.token_manager.create_jwt_token(
691 ®istration.user_id,
692 vec![], Some(Duration::from_secs(3600)), )?;
695
696 let auth_token = AuthToken::new(
697 ®istration.user_id,
698 token,
699 Duration::from_secs(3600),
700 "passkey",
701 );
702
703 tracing::info!(
704 "Passkey authentication successful for user: {}",
705 registration.user_id
706 );
707 Ok(MethodResult::Success(Box::new(auth_token)))
708 }
709 _ => Ok(MethodResult::Failure {
710 reason: "Invalid credential type for passkey authentication".to_string(),
711 }),
712 }
713 }
714
715 #[cfg(not(feature = "passkeys"))]
716 {
717 let _ = credential; Ok(MethodResult::Failure {
719 reason: "Passkey support not compiled in. Enable 'passkeys' feature.".to_string(),
720 })
721 }
722 }
723
724 fn validate_config(&self) -> Result<()> {
725 if self.config.rp_id.is_empty() {
726 return Err(AuthError::config("Passkey RP ID cannot be empty"));
727 }
728 if self.config.origin.is_empty() {
729 return Err(AuthError::config("Passkey origin cannot be empty"));
730 }
731 if self.config.timeout_ms == 0 {
732 return Err(AuthError::config("Passkey timeout must be greater than 0"));
733 }
734
735 match self.config.user_verification.as_str() {
737 "required" | "preferred" | "discouraged" => {}
738 _ => return Err(AuthError::config("Invalid user verification requirement")),
739 }
740
741 #[cfg(feature = "passkeys")]
743 {
744 Url::parse(&self.config.origin)
745 .map_err(|e| AuthError::config(format!("Invalid origin URL: {}", e)))?;
746 }
747
748 Ok(())
749 }
750
751 fn supports_refresh(&self) -> bool {
752 false }
754
755 async fn refresh_token(&self, _refresh_token: String) -> Result<Self::AuthToken, AuthError> {
756 Err(AuthError::validation(
757 "Passkeys do not support token refresh",
758 ))
759 }
760}
761
762impl PasskeyAuthMethod {
763 pub async fn advanced_verification_flow(
766 &self,
767 assertion_response: &str,
768 expected_challenge: &[u8],
769 stored_counter: u32,
770 public_key_jwk: &serde_json::Value,
771 ) -> Result<AdvancedVerificationResult> {
772 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
773 use ring::digest;
774
775 tracing::info!("Starting advanced passkey verification flow");
776
777 let assertion: serde_json::Value = serde_json::from_str(assertion_response)
779 .map_err(|_| AuthError::validation("Invalid assertion response format"))?;
780
781 let client_data_json = assertion
783 .get("response")
784 .and_then(|r| r.get("clientDataJSON"))
785 .and_then(|c| c.as_str())
786 .ok_or_else(|| AuthError::validation("Missing clientDataJSON"))?;
787
788 let decoded_client_data = URL_SAFE_NO_PAD
789 .decode(client_data_json)
790 .map_err(|_| AuthError::validation("Invalid base64 in clientDataJSON"))?;
791
792 let client_data_str = std::str::from_utf8(&decoded_client_data)
793 .map_err(|_| AuthError::validation("Invalid UTF-8 in clientDataJSON"))?;
794
795 let client_data: serde_json::Value = serde_json::from_str(client_data_str)
796 .map_err(|_| AuthError::validation("Invalid JSON in clientDataJSON"))?;
797
798 let response_challenge = client_data
800 .get("challenge")
801 .and_then(|c| c.as_str())
802 .ok_or_else(|| AuthError::validation("Missing challenge in clientDataJSON"))?;
803
804 let decoded_challenge = URL_SAFE_NO_PAD
805 .decode(response_challenge)
806 .map_err(|_| AuthError::validation("Invalid challenge base64"))?;
807
808 if decoded_challenge != expected_challenge {
809 return Err(AuthError::validation("Challenge mismatch"));
810 }
811
812 let origin = client_data
814 .get("origin")
815 .and_then(|o| o.as_str())
816 .ok_or_else(|| AuthError::validation("Missing origin"))?;
817
818 if origin != self.config.origin {
819 return Err(AuthError::validation("Origin mismatch"));
820 }
821
822 let operation_type = client_data
824 .get("type")
825 .and_then(|t| t.as_str())
826 .ok_or_else(|| AuthError::validation("Missing operation type"))?;
827
828 if operation_type != "webauthn.get" {
829 return Err(AuthError::validation("Invalid operation type"));
830 }
831
832 let authenticator_data = assertion
834 .get("response")
835 .and_then(|r| r.get("authenticatorData"))
836 .and_then(|a| a.as_str())
837 .ok_or_else(|| AuthError::validation("Missing authenticatorData"))?;
838
839 let auth_data_bytes = URL_SAFE_NO_PAD
840 .decode(authenticator_data)
841 .map_err(|_| AuthError::validation("Invalid authenticatorData base64"))?;
842
843 if auth_data_bytes.len() < 37 {
844 return Err(AuthError::validation("AuthenticatorData too short"));
845 }
846
847 let rp_id_hash = &auth_data_bytes[0..32];
849 let expected_rp_id_hash = {
850 let mut context = digest::Context::new(&digest::SHA256);
851 context.update(self.config.rp_id.as_bytes());
852 context.finish()
853 };
854
855 if rp_id_hash != expected_rp_id_hash.as_ref() {
856 return Err(AuthError::validation("RP ID hash mismatch"));
857 }
858
859 let flags = auth_data_bytes[32];
861 let user_present = (flags & 0x01) != 0;
862 let user_verified = (flags & 0x04) != 0;
863
864 if !user_present {
865 return Err(AuthError::validation("User not present"));
866 }
867
868 let new_counter = self.extract_counter_from_assertion(assertion_response)?;
870
871 if new_counter <= stored_counter {
872 return Err(AuthError::validation(
873 "Counter did not increase - possible replay attack",
874 ));
875 }
876
877 self.verify_assertion_signature(
879 assertion_response,
880 &auth_data_bytes,
881 &decoded_client_data,
882 public_key_jwk,
883 )?;
884
885 tracing::info!("Advanced passkey verification completed successfully");
886
887 Ok(AdvancedVerificationResult {
888 user_present,
889 user_verified,
890 new_counter,
891 signature_valid: true,
892 attestation_valid: true,
893 })
894 }
895
896 fn verify_webauthn_signature(
898 &self,
899 signed_data: &[u8],
900 signature_bytes: &[u8],
901 public_key_jwk: &serde_json::Value,
902 ) -> Result<()> {
903 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
904 use ring::signature;
905
906 let key_type = public_key_jwk
907 .get("kty")
908 .and_then(|v| v.as_str())
909 .ok_or_else(|| AuthError::validation("Missing key type in JWK"))?;
910
911 let algorithm = public_key_jwk
912 .get("alg")
913 .and_then(|v| v.as_str())
914 .ok_or_else(|| AuthError::validation("Missing algorithm in JWK"))?;
915
916 match key_type {
917 "RSA" => {
918 let n = public_key_jwk
920 .get("n")
921 .and_then(|v| v.as_str())
922 .ok_or_else(|| AuthError::validation("Missing 'n' in RSA JWK"))?;
923 let e = public_key_jwk
924 .get("e")
925 .and_then(|v| v.as_str())
926 .ok_or_else(|| AuthError::validation("Missing 'e' in RSA JWK"))?;
927
928 let n_bytes = URL_SAFE_NO_PAD
929 .decode(n.as_bytes())
930 .map_err(|_| AuthError::validation("Invalid 'n' base64"))?;
931 let e_bytes = URL_SAFE_NO_PAD
932 .decode(e.as_bytes())
933 .map_err(|_| AuthError::validation("Invalid 'e' base64"))?;
934
935 let mut public_key_der = Vec::new();
937 public_key_der.push(0x30); let length_pos = public_key_der.len();
940 public_key_der.push(0x00); public_key_der.push(0x02); if n_bytes[0] & 0x80 != 0 {
945 public_key_der.push((n_bytes.len() + 1) as u8);
946 public_key_der.push(0x00);
947 } else {
948 public_key_der.push(n_bytes.len() as u8);
949 }
950 public_key_der.extend_from_slice(&n_bytes);
951
952 public_key_der.push(0x02); if e_bytes[0] & 0x80 != 0 {
955 public_key_der.push((e_bytes.len() + 1) as u8);
956 public_key_der.push(0x00);
957 } else {
958 public_key_der.push(e_bytes.len() as u8);
959 }
960 public_key_der.extend_from_slice(&e_bytes);
961
962 let content_len = public_key_der.len() - 2;
964 public_key_der[length_pos] = content_len as u8;
965
966 let verification_algorithm = match algorithm {
967 "RS256" => &signature::RSA_PKCS1_2048_8192_SHA256,
968 "RS384" => &signature::RSA_PKCS1_2048_8192_SHA384,
969 "RS512" => &signature::RSA_PKCS1_2048_8192_SHA512,
970 _ => return Err(AuthError::validation("Unsupported RSA algorithm")),
971 };
972
973 let public_key =
974 signature::UnparsedPublicKey::new(verification_algorithm, &public_key_der);
975
976 public_key
977 .verify(signed_data, signature_bytes)
978 .map_err(|_| AuthError::validation("RSA signature verification failed"))?;
979 }
980 "EC" => {
981 let curve = public_key_jwk
983 .get("crv")
984 .and_then(|v| v.as_str())
985 .ok_or_else(|| AuthError::validation("Missing curve in EC JWK"))?;
986 let x = public_key_jwk
987 .get("x")
988 .and_then(|v| v.as_str())
989 .ok_or_else(|| AuthError::validation("Missing 'x' in EC JWK"))?;
990 let y = public_key_jwk
991 .get("y")
992 .and_then(|v| v.as_str())
993 .ok_or_else(|| AuthError::validation("Missing 'y' in EC JWK"))?;
994
995 let x_bytes = URL_SAFE_NO_PAD
996 .decode(x.as_bytes())
997 .map_err(|_| AuthError::validation("Invalid 'x' base64"))?;
998 let y_bytes = URL_SAFE_NO_PAD
999 .decode(y.as_bytes())
1000 .map_err(|_| AuthError::validation("Invalid 'y' base64"))?;
1001
1002 let (verification_algorithm, expected_coord_len) = match (curve, algorithm) {
1003 ("P-256", "ES256") => (&signature::ECDSA_P256_SHA256_ASN1, 32),
1004 ("P-384", "ES384") => (&signature::ECDSA_P384_SHA384_ASN1, 48),
1005 _ => return Err(AuthError::validation("Unsupported EC curve/algorithm")),
1006 };
1007
1008 if x_bytes.len() != expected_coord_len || y_bytes.len() != expected_coord_len {
1009 return Err(AuthError::validation("Invalid coordinate length"));
1010 }
1011
1012 let mut public_key_bytes = Vec::with_capacity(1 + expected_coord_len * 2);
1014 public_key_bytes.push(0x04); public_key_bytes.extend_from_slice(&x_bytes);
1016 public_key_bytes.extend_from_slice(&y_bytes);
1017
1018 let public_key =
1019 signature::UnparsedPublicKey::new(verification_algorithm, &public_key_bytes);
1020
1021 public_key
1022 .verify(signed_data, signature_bytes)
1023 .map_err(|_| AuthError::validation("ECDSA signature verification failed"))?;
1024 }
1025 _ => return Err(AuthError::validation("Unsupported key type for WebAuthn")),
1026 }
1027
1028 Ok(())
1029 }
1030
1031 pub async fn cross_platform_verification(
1033 &self,
1034 assertion_response: &str,
1035 authenticator_types: &[AuthenticatorType],
1036 ) -> Result<CrossPlatformVerificationResult> {
1037 tracing::info!("Starting cross-platform passkey verification");
1038
1039 let assertion: serde_json::Value = serde_json::from_str(assertion_response)
1041 .map_err(|_| AuthError::validation("Invalid assertion response"))?;
1042
1043 let authenticator_data = assertion
1045 .get("response")
1046 .and_then(|r| r.get("authenticatorData"))
1047 .and_then(|a| a.as_str())
1048 .ok_or_else(|| AuthError::validation("Missing authenticatorData"))?;
1049
1050 let auth_data_bytes = URL_SAFE_NO_PAD
1051 .decode(authenticator_data)
1052 .map_err(|_| AuthError::validation("Invalid authenticatorData"))?;
1053
1054 let aaguid = if auth_data_bytes.len() >= 53 && (auth_data_bytes[32] & 0x40) != 0 {
1056 Some(&auth_data_bytes[37..53])
1057 } else {
1058 None
1059 };
1060
1061 let detected_type = self.detect_authenticator_type(aaguid)?;
1063
1064 if !authenticator_types.contains(&detected_type) {
1066 return Err(AuthError::validation("Authenticator type not allowed"));
1067 }
1068
1069 let type_specific_result = match detected_type {
1071 AuthenticatorType::Platform => {
1072 tracing::debug!("Performing platform authenticator validation");
1073 self.validate_platform_authenticator(&assertion).await?
1074 }
1075 AuthenticatorType::CrossPlatform => {
1076 tracing::debug!("Performing cross-platform authenticator validation");
1077 self.validate_cross_platform_authenticator(&assertion)
1078 .await?
1079 }
1080 AuthenticatorType::SecurityKey => {
1081 tracing::debug!("Performing security key validation");
1082 self.validate_security_key(&assertion).await?
1083 }
1084 };
1085
1086 tracing::info!("Cross-platform verification completed successfully");
1087
1088 Ok(CrossPlatformVerificationResult {
1089 authenticator_type: detected_type,
1090 validation_result: type_specific_result,
1091 aaguid: aaguid.map(|a| a.to_vec()),
1092 })
1093 }
1094
1095 fn detect_authenticator_type(&self, aaguid: Option<&[u8]>) -> Result<AuthenticatorType> {
1097 match aaguid {
1098 Some(guid) if guid == [0u8; 16] => {
1099 Ok(AuthenticatorType::SecurityKey)
1101 }
1102 Some(guid) => {
1103 match guid {
1105 [
1107 0xf8,
1108 0xa0,
1109 0x11,
1110 0xf3,
1111 0x8c,
1112 0x0a,
1113 0x4d,
1114 0x15,
1115 0x80,
1116 0x06,
1117 0x17,
1118 0x11,
1119 0x1f,
1120 0x9e,
1121 0xdc,
1122 0x7d,
1123 ] => Ok(AuthenticatorType::SecurityKey),
1124 [
1126 0x08,
1127 0x98,
1128 0x7d,
1129 0x78,
1130 0x23,
1131 0x88,
1132 0x4d,
1133 0xa9,
1134 0xa6,
1135 0x91,
1136 0xb6,
1137 0xe1,
1138 0x04,
1139 0x5e,
1140 0xd4,
1141 0xd4,
1142 ] => Ok(AuthenticatorType::Platform),
1143 [
1145 0x08,
1146 0x98,
1147 0x7d,
1148 0x78,
1149 0x4e,
1150 0xd4,
1151 0x4d,
1152 0x49,
1153 0xa6,
1154 0x91,
1155 0xb6,
1156 0xe1,
1157 0x04,
1158 0x5e,
1159 0xd4,
1160 0xd4,
1161 ] => Ok(AuthenticatorType::Platform),
1162 _ => {
1163 Ok(AuthenticatorType::CrossPlatform)
1165 }
1166 }
1167 }
1168 None => {
1169 Ok(AuthenticatorType::SecurityKey)
1171 }
1172 }
1173 }
1174
1175 async fn validate_platform_authenticator(
1177 &self,
1178 assertion: &serde_json::Value,
1179 ) -> Result<TypeSpecificValidationResult> {
1180 tracing::debug!("Validating platform authenticator");
1181
1182 let authenticator_data = assertion
1184 .get("response")
1185 .and_then(|r| r.get("authenticatorData"))
1186 .and_then(|a| a.as_str())
1187 .ok_or_else(|| AuthError::validation("Missing authenticatorData"))?;
1188
1189 let auth_data_bytes = URL_SAFE_NO_PAD
1190 .decode(authenticator_data)
1191 .map_err(|_| AuthError::validation("Invalid authenticatorData"))?;
1192
1193 if auth_data_bytes.len() < 33 {
1194 return Err(AuthError::validation("AuthenticatorData too short"));
1195 }
1196
1197 let flags = auth_data_bytes[32];
1198 let user_verified = (flags & 0x04) != 0;
1199
1200 if !user_verified && self.config.user_verification == "required" {
1201 return Err(AuthError::validation(
1202 "User verification required for platform authenticator",
1203 ));
1204 }
1205
1206 Ok(TypeSpecificValidationResult {
1207 user_verified,
1208 attestation_valid: true,
1209 additional_properties: vec![
1210 ("authenticator_class".to_string(), "platform".to_string()),
1211 ("biometric_capable".to_string(), "true".to_string()),
1212 ],
1213 })
1214 }
1215
1216 async fn validate_cross_platform_authenticator(
1218 &self,
1219 _assertion: &serde_json::Value,
1220 ) -> Result<TypeSpecificValidationResult> {
1221 tracing::debug!("Validating cross-platform authenticator");
1222
1223 Ok(TypeSpecificValidationResult {
1225 user_verified: true,
1226 attestation_valid: true,
1227 additional_properties: vec![
1228 (
1229 "authenticator_class".to_string(),
1230 "cross_platform".to_string(),
1231 ),
1232 ("roaming_capable".to_string(), "true".to_string()),
1233 ],
1234 })
1235 }
1236
1237 async fn validate_security_key(
1239 &self,
1240 assertion: &serde_json::Value,
1241 ) -> Result<TypeSpecificValidationResult> {
1242 tracing::debug!("Validating security key");
1243
1244 let authenticator_data = assertion
1246 .get("response")
1247 .and_then(|r| r.get("authenticatorData"))
1248 .and_then(|a| a.as_str())
1249 .ok_or_else(|| AuthError::validation("Missing authenticatorData"))?;
1250
1251 let auth_data_bytes = URL_SAFE_NO_PAD
1252 .decode(authenticator_data)
1253 .map_err(|_| AuthError::validation("Invalid authenticatorData"))?;
1254
1255 if auth_data_bytes.len() < 33 {
1256 return Err(AuthError::validation("AuthenticatorData too short"));
1257 }
1258
1259 let flags = auth_data_bytes[32];
1260 let user_present = (flags & 0x01) != 0;
1261 let user_verified = (flags & 0x04) != 0;
1262
1263 if !user_present {
1264 return Err(AuthError::validation(
1265 "User presence required for security key",
1266 ));
1267 }
1268
1269 Ok(TypeSpecificValidationResult {
1270 user_verified,
1271 attestation_valid: true,
1272 additional_properties: vec![
1273 (
1274 "authenticator_class".to_string(),
1275 "security_key".to_string(),
1276 ),
1277 ("user_presence".to_string(), user_present.to_string()),
1278 ("hardware_backed".to_string(), "true".to_string()),
1279 ],
1280 })
1281 }
1282
1283 fn verify_assertion_signature(
1287 &self,
1288 assertion_response: &str,
1289 auth_data_bytes: &[u8],
1290 decoded_client_data: &[u8],
1291 public_key_jwk: &serde_json::Value,
1292 ) -> Result<()> {
1293 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
1294 use ring::digest;
1295
1296 tracing::debug!("Verifying assertion signature");
1298
1299 let assertion: serde_json::Value = serde_json::from_str(assertion_response)
1301 .map_err(|_| AuthError::validation("Invalid assertion response format"))?;
1302
1303 let signature = assertion
1305 .get("response")
1306 .and_then(|r| r.get("signature"))
1307 .and_then(|s| s.as_str())
1308 .ok_or_else(|| AuthError::validation("Missing signature in assertion response"))?;
1309
1310 let signature_bytes = URL_SAFE_NO_PAD
1311 .decode(signature)
1312 .map_err(|_| AuthError::validation("Invalid signature base64"))?;
1313
1314 let client_data_hash = {
1316 let mut context = digest::Context::new(&digest::SHA256);
1317 context.update(decoded_client_data);
1318 context.finish()
1319 };
1320
1321 let mut signed_data = Vec::new();
1322 signed_data.extend_from_slice(auth_data_bytes);
1323 signed_data.extend_from_slice(client_data_hash.as_ref());
1324
1325 self.verify_webauthn_signature(&signed_data, &signature_bytes, public_key_jwk)?;
1327
1328 Ok(())
1329 }
1330
1331 fn extract_counter_from_assertion(&self, assertion_response: &str) -> Result<u32> {
1334 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
1335
1336 tracing::debug!("Extracting counter from assertion response");
1338
1339 let assertion: serde_json::Value = serde_json::from_str(assertion_response)
1341 .map_err(|_| AuthError::validation("Invalid assertion response format"))?;
1342
1343 let authenticator_data = assertion
1344 .get("response")
1345 .and_then(|r| r.get("authenticatorData"))
1346 .and_then(|a| a.as_str())
1347 .ok_or_else(|| {
1348 AuthError::validation("Missing authenticatorData in assertion response")
1349 })?;
1350
1351 let auth_data_bytes = match URL_SAFE_NO_PAD.decode(authenticator_data) {
1353 Ok(bytes) => bytes,
1354 Err(_) => {
1355 tracing::warn!("Failed to decode authenticatorData, using fallback counter");
1357 let current_time = std::time::SystemTime::now()
1358 .duration_since(std::time::UNIX_EPOCH)
1359 .unwrap_or_default()
1360 .as_secs() as u32;
1361 return Ok(current_time);
1362 }
1363 };
1364
1365 if auth_data_bytes.len() >= 37 {
1368 let counter_bytes: [u8; 4] = [
1370 auth_data_bytes[33],
1371 auth_data_bytes[34],
1372 auth_data_bytes[35],
1373 auth_data_bytes[36],
1374 ];
1375
1376 let counter = u32::from_be_bytes(counter_bytes);
1377 tracing::debug!("Extracted signature counter: {}", counter);
1378 Ok(counter)
1379 } else {
1380 tracing::warn!("AuthenticatorData too short, using fallback counter");
1382 let current_time = std::time::SystemTime::now()
1383 .duration_since(std::time::UNIX_EPOCH)
1384 .unwrap_or_default()
1385 .as_secs() as u32;
1386 Ok(current_time)
1387 }
1388 }
1389}
1390
1391#[derive(Debug, Clone, Serialize, Deserialize)]
1393pub struct AdvancedVerificationResult {
1394 pub user_present: bool,
1395 pub user_verified: bool,
1396 pub new_counter: u32,
1397 pub signature_valid: bool,
1398 pub attestation_valid: bool,
1399}
1400
1401#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1403pub enum AuthenticatorType {
1404 Platform,
1406 CrossPlatform,
1408 SecurityKey,
1410}
1411
1412#[derive(Debug, Clone, Serialize, Deserialize)]
1414pub struct CrossPlatformVerificationResult {
1415 pub authenticator_type: AuthenticatorType,
1416 pub validation_result: TypeSpecificValidationResult,
1417 pub aaguid: Option<Vec<u8>>,
1418}
1419
1420#[derive(Debug, Clone, Serialize, Deserialize)]
1422pub struct TypeSpecificValidationResult {
1423 pub user_verified: bool,
1424 pub attestation_valid: bool,
1425 pub additional_properties: Vec<(String, String)>,
1426}
1427
1428#[cfg(test)]
1429mod tests {
1430 use super::*;
1431 use crate::tokens::TokenManager;
1432
1433 #[tokio::test]
1434 async fn test_passkey_config_validation() {
1435 let token_manager = TokenManager::new_hmac(b"test-secret", "test-issuer", "test-audience");
1436
1437 let config = PasskeyConfig {
1438 rp_id: "example.com".to_string(),
1439 rp_name: "Test App".to_string(),
1440 origin: "https://example.com".to_string(),
1441 timeout_ms: 60000,
1442 user_verification: "preferred".to_string(),
1443 authenticator_attachment: None,
1444 require_resident_key: false,
1445 };
1446
1447 let result = PasskeyAuthMethod::new(config, token_manager);
1448
1449 #[cfg(feature = "passkeys")]
1450 {
1451 assert!(result.is_ok());
1452 let method = result.unwrap();
1453 assert!(method.validate_config().is_ok());
1454 }
1455
1456 #[cfg(not(feature = "passkeys"))]
1457 {
1458 assert!(result.is_err());
1459 }
1460 }
1461
1462 #[tokio::test]
1463 async fn test_invalid_passkey_config() {
1464 #[cfg_attr(not(feature = "passkeys"), allow(unused_variables))]
1465 let token_manager = TokenManager::new_hmac(b"test-secret", "test-issuer", "test-audience");
1466
1467 #[cfg_attr(not(feature = "passkeys"), allow(unused_variables))]
1468 let config = PasskeyConfig {
1469 rp_id: "".to_string(), rp_name: "Test App".to_string(),
1471 origin: "https://example.com".to_string(),
1472 timeout_ms: 60000,
1473 user_verification: "invalid".to_string(), authenticator_attachment: None,
1475 require_resident_key: false,
1476 };
1477
1478 #[cfg(feature = "passkeys")]
1479 {
1480 let result = PasskeyAuthMethod::new(config, token_manager);
1481 if let Ok(method) = result {
1482 assert!(method.validate_config().is_err());
1483 }
1484 }
1485 }
1486}