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