1use crate::config::{ApiKeySettings, AuthSettings, JwtSettings, MtlsSettings};
14use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
15use constant_time_eq::constant_time_eq;
16use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header};
17use serde::{Deserialize, Serialize};
18use sha2::{Digest, Sha256};
19use std::collections::HashMap;
20use std::fs;
21use std::path::Path;
22use std::sync::Arc;
23use thiserror::Error;
24use tracing::{debug, info, warn};
25use x509_parser::prelude::*;
26
27#[derive(Error, Debug)]
29pub enum AuthError {
30 #[error("Authentication failed: {0}")]
31 AuthenticationFailed(String),
32
33 #[error("Invalid credentials")]
34 InvalidCredentials,
35
36 #[error("Certificate validation failed: {0}")]
37 CertificateError(String),
38
39 #[error("JWT validation failed: {0}")]
40 JwtError(String),
41
42 #[error("API key validation failed: {0}")]
43 ApiKeyError(String),
44
45 #[error("Configuration error: {0}")]
46 ConfigError(String),
47
48 #[error("IO error: {0}")]
49 Io(#[from] std::io::Error),
50
51 #[error("JSON error: {0}")]
52 Json(#[from] serde_json::Error),
53
54 #[error("No authentication provided")]
55 NoAuthProvided,
56
57 #[error("Authentication method not enabled: {0}")]
58 MethodNotEnabled(String),
59}
60
61pub type AuthResult<T> = Result<T, AuthError>;
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65pub struct Principal {
66 pub id: String,
68
69 pub name: String,
71
72 pub auth_method: AuthMethod,
74
75 pub attributes: HashMap<String, String>,
77}
78
79impl Principal {
80 pub fn new(id: String, name: String, auth_method: AuthMethod) -> Self {
82 Self {
83 id,
84 name,
85 auth_method,
86 attributes: HashMap::new(),
87 }
88 }
89
90 pub fn with_attribute(mut self, key: String, value: String) -> Self {
92 self.attributes.insert(key, value);
93 self
94 }
95
96 pub fn get_attribute(&self, key: &str) -> Option<&String> {
98 self.attributes.get(key)
99 }
100
101 pub fn has_role(&self, role: &str) -> bool {
103 self.get_attribute("role")
104 .map(|r| r == role)
105 .unwrap_or(false)
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub enum AuthMethod {
112 MutualTls,
114 Jwt,
116 ApiKey,
118}
119
120impl std::fmt::Display for AuthMethod {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 AuthMethod::MutualTls => write!(f, "mTLS"),
124 AuthMethod::Jwt => write!(f, "JWT"),
125 AuthMethod::ApiKey => write!(f, "API Key"),
126 }
127 }
128}
129
130#[derive(Debug, Serialize, Deserialize)]
132struct JwtClaims {
133 sub: String,
135 exp: usize,
137 iat: Option<usize>,
139 iss: Option<String>,
141 aud: Option<String>,
143 name: Option<String>,
145 roles: Option<Vec<String>>,
147 #[serde(flatten)]
149 attributes: HashMap<String, serde_json::Value>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154struct ApiKeyEntry {
155 id: String,
157 name: String,
159 #[serde(skip_serializing_if = "Option::is_none")]
161 key_hash: Option<String>,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 key: Option<String>,
165 user_id: String,
167 #[serde(default)]
169 roles: Vec<String>,
170 #[serde(default)]
172 attributes: HashMap<String, String>,
173}
174
175pub struct Authenticator {
177 config: Arc<AuthSettings>,
178 mtls_validator: Option<MtlsValidator>,
179 jwt_validator: Option<JwtValidator>,
180 api_key_validator: Option<ApiKeyValidator>,
181}
182
183impl Authenticator {
184 pub fn new(config: AuthSettings) -> AuthResult<Self> {
186 let config = Arc::new(config);
187
188 let mtls_validator = if config.mtls.enabled {
190 Some(MtlsValidator::new(config.mtls.clone())?)
191 } else {
192 None
193 };
194
195 let jwt_validator = if config.jwt.enabled {
197 Some(JwtValidator::new(config.jwt.clone())?)
198 } else {
199 None
200 };
201
202 let api_key_validator = if config.api_key.enabled {
204 Some(ApiKeyValidator::new(config.api_key.clone())?)
205 } else {
206 None
207 };
208
209 Ok(Self {
210 config,
211 mtls_validator,
212 jwt_validator,
213 api_key_validator,
214 })
215 }
216
217 pub fn authenticate_certificate(&self, cert_der: &[u8]) -> AuthResult<Principal> {
219 if !self.config.methods.contains(&"mtls".to_string()) {
220 return Err(AuthError::MethodNotEnabled("mTLS".to_string()));
221 }
222
223 let validator = self
224 .mtls_validator
225 .as_ref()
226 .ok_or_else(|| AuthError::MethodNotEnabled("mTLS".to_string()))?;
227
228 validator.validate_certificate(cert_der)
229 }
230
231 pub fn authenticate_jwt(&self, token: &str) -> AuthResult<Principal> {
233 if !self.config.methods.contains(&"jwt".to_string()) {
234 return Err(AuthError::MethodNotEnabled("JWT".to_string()));
235 }
236
237 let validator = self
238 .jwt_validator
239 .as_ref()
240 .ok_or_else(|| AuthError::MethodNotEnabled("JWT".to_string()))?;
241
242 validator.validate_token(token)
243 }
244
245 pub fn authenticate_api_key(&self, key: &str) -> AuthResult<Principal> {
247 if !self.config.methods.contains(&"api_key".to_string()) {
248 return Err(AuthError::MethodNotEnabled("API Key".to_string()));
249 }
250
251 let validator = self
252 .api_key_validator
253 .as_ref()
254 .ok_or_else(|| AuthError::MethodNotEnabled("API Key".to_string()))?;
255
256 validator.validate_key(key)
257 }
258
259 pub fn is_enabled(&self) -> bool {
261 self.config.enabled
262 }
263
264 pub fn is_method_enabled(&self, method: &str) -> bool {
266 self.config.methods.contains(&method.to_string())
267 }
268}
269
270struct MtlsValidator {
272 config: MtlsSettings,
273 ca_certs: Vec<Vec<u8>>,
274}
275
276impl MtlsValidator {
277 fn new(config: MtlsSettings) -> AuthResult<Self> {
278 let ca_certs = if let Some(ref ca_dir) = config.ca_certs_dir {
279 Self::load_ca_certificates(ca_dir)?
280 } else {
281 Vec::new()
282 };
283
284 Ok(Self { config, ca_certs })
285 }
286
287 fn load_ca_certificates(dir: &Path) -> AuthResult<Vec<Vec<u8>>> {
288 let mut certs = Vec::new();
289
290 if !dir.exists() {
291 return Err(AuthError::ConfigError(format!(
292 "CA certificates directory does not exist: {}",
293 dir.display()
294 )));
295 }
296
297 for entry_result in fs::read_dir(dir)? {
298 let entry = entry_result?;
299 let path = entry.path();
300
301 if path.is_file() {
302 if let Some(ext) = path.extension() {
303 if ext == "crt" || ext == "pem" || ext == "der" {
304 let cert_data = fs::read(&path)?;
305 certs.push(cert_data);
306 debug!("Loaded CA certificate: {}", path.display());
307 }
308 }
309 }
310 }
311
312 info!("Loaded {} CA certificates", certs.len());
313 Ok(certs)
314 }
315
316 fn validate_certificate(&self, cert_der: &[u8]) -> AuthResult<Principal> {
317 let (_, cert) = X509Certificate::from_der(cert_der).map_err(|e| {
319 AuthError::CertificateError(format!("Failed to parse certificate: {}", e))
320 })?;
321
322 let now = std::time::SystemTime::now();
324 let not_before = cert.validity().not_before.to_datetime();
325 let not_after = cert.validity().not_after.to_datetime();
326
327 if now < not_before {
328 return Err(AuthError::CertificateError(
329 "Certificate not yet valid".to_string(),
330 ));
331 }
332
333 if now > not_after {
334 return Err(AuthError::CertificateError(
335 "Certificate has expired".to_string(),
336 ));
337 }
338
339 let subject = cert.subject();
341 let cn = subject
342 .iter_common_name()
343 .next()
344 .and_then(|cn| cn.as_str().ok())
345 .ok_or_else(|| AuthError::CertificateError("No CN in certificate".to_string()))?;
346
347 let organization = subject
348 .iter_organization()
349 .next()
350 .and_then(|o| o.as_str().ok());
351
352 if !self.config.allowed_organizations.is_empty() {
354 let org = organization.ok_or_else(|| {
355 AuthError::CertificateError("Certificate has no organization".to_string())
356 })?;
357
358 if !self.config.allowed_organizations.contains(&org.to_string()) {
359 return Err(AuthError::CertificateError(format!(
360 "Organization '{}' not allowed",
361 org
362 )));
363 }
364 }
365
366 let mut principal = Principal::new(cn.to_string(), cn.to_string(), AuthMethod::MutualTls);
368
369 if let Some(org) = organization {
370 principal = principal.with_attribute("organization".to_string(), org.to_string());
371 }
372
373 debug!("Successfully authenticated certificate for user: {}", cn);
374 Ok(principal)
375 }
376}
377
378struct JwtValidator {
380 config: JwtSettings,
381 decoding_key: DecodingKey,
382 validation: Validation,
383}
384
385impl std::fmt::Debug for JwtValidator {
386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387 f.debug_struct("JwtValidator")
388 .field("config", &self.config)
389 .field("decoding_key", &"<redacted>")
390 .field("validation", &"<validation>")
391 .finish()
392 }
393}
394
395impl JwtValidator {
396 fn new(config: JwtSettings) -> AuthResult<Self> {
397 let algorithm = match config.algorithm.as_str() {
398 "HS256" => Algorithm::HS256,
399 "HS384" => Algorithm::HS384,
400 "HS512" => Algorithm::HS512,
401 "RS256" => Algorithm::RS256,
402 "RS384" => Algorithm::RS384,
403 "RS512" => Algorithm::RS512,
404 "ES256" => Algorithm::ES256,
405 "ES384" => Algorithm::ES384,
406 "EdDSA" => Algorithm::EdDSA,
407 _ => {
408 return Err(AuthError::ConfigError(format!(
409 "Unsupported JWT algorithm: {}",
410 config.algorithm
411 )));
412 }
413 };
414
415 let decoding_key = match algorithm {
416 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
417 let secret = config.secret.as_ref().ok_or_else(|| {
418 AuthError::ConfigError("JWT secret not configured".to_string())
419 })?;
420 DecodingKey::from_secret(secret.as_bytes())
421 }
422 Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => {
423 let public_key_path = config.public_key_path.as_ref().ok_or_else(|| {
424 AuthError::ConfigError("JWT RSA public key path not configured".to_string())
425 })?;
426 let pem = fs::read_to_string(public_key_path)?;
427 DecodingKey::from_rsa_pem(pem.as_bytes()).map_err(|e| {
428 AuthError::ConfigError(format!("Failed to load RSA public key: {}", e))
429 })?
430 }
431 Algorithm::ES256 | Algorithm::ES384 => {
432 let ec_key_path = config.ec_public_key_path.as_ref().ok_or_else(|| {
433 AuthError::ConfigError("JWT EC public key path not configured".to_string())
434 })?;
435 let pem = fs::read_to_string(ec_key_path)?;
436 DecodingKey::from_ec_pem(pem.as_bytes()).map_err(|e| {
437 AuthError::ConfigError(format!("Failed to load EC public key: {}", e))
438 })?
439 }
440 Algorithm::EdDSA => {
441 let ed_key_path = config.ed_public_key_path.as_ref().ok_or_else(|| {
442 AuthError::ConfigError("JWT Ed25519 public key path not configured".to_string())
443 })?;
444 let pem = fs::read_to_string(ed_key_path)?;
445 DecodingKey::from_ed_pem(pem.as_bytes()).map_err(|e| {
446 AuthError::ConfigError(format!("Failed to load Ed25519 public key: {}", e))
447 })?
448 }
449 _ => {
450 return Err(AuthError::ConfigError(
451 "Algorithm not implemented".to_string(),
452 ));
453 }
454 };
455
456 let mut validation = Validation::new(algorithm);
457 validation.validate_exp = true;
458
459 if let Some(ref issuer) = config.issuer {
460 validation.set_issuer(&[issuer]);
461 }
462
463 if let Some(ref audience) = config.audience {
464 validation.set_audience(&[audience]);
465 }
466
467 Ok(Self {
468 config,
469 decoding_key,
470 validation,
471 })
472 }
473
474 fn validate_token(&self, token: &str) -> AuthResult<Principal> {
475 let token_data = decode::<JwtClaims>(token, &self.decoding_key, &self.validation)
477 .map_err(|e| AuthError::JwtError(format!("Token validation failed: {}", e)))?;
478
479 let claims = token_data.claims;
480
481 let name = claims.name.unwrap_or_else(|| claims.sub.clone());
483 let mut principal = Principal::new(claims.sub, name, AuthMethod::Jwt);
484
485 if let Some(roles) = claims.roles {
487 principal = principal.with_attribute("roles".to_string(), roles.join(","));
488 }
489
490 for (key, value) in claims.attributes {
492 if let Some(s) = value.as_str() {
493 principal = principal.with_attribute(key, s.to_string());
494 }
495 }
496
497 debug!(
498 "Successfully authenticated JWT token for user: {}",
499 principal.name
500 );
501 Ok(principal)
502 }
503}
504
505struct ApiKeyValidator {
507 config: ApiKeySettings,
508 keys: HashMap<String, ApiKeyEntry>,
509}
510
511impl ApiKeyValidator {
512 fn new(config: ApiKeySettings) -> AuthResult<Self> {
513 let keys_file = config
514 .keys_file
515 .as_ref()
516 .ok_or_else(|| AuthError::ConfigError("API keys file not configured".to_string()))?;
517
518 let keys = Self::load_keys(keys_file, config.hash_keys)?;
519
520 info!("Loaded {} API keys", keys.len());
521
522 Ok(Self { config, keys })
523 }
524
525 fn load_keys(path: &Path, hash_keys: bool) -> AuthResult<HashMap<String, ApiKeyEntry>> {
526 if !path.exists() {
527 return Err(AuthError::ConfigError(format!(
528 "API keys file does not exist: {}",
529 path.display()
530 )));
531 }
532
533 let contents = fs::read_to_string(path)?;
534 let entries: Vec<ApiKeyEntry> = serde_json::from_str(&contents)?;
535
536 let mut keys = HashMap::new();
537 for entry in entries {
538 let key_value = if hash_keys {
539 entry
540 .key_hash
541 .clone()
542 .ok_or_else(|| AuthError::ConfigError("Missing key_hash".to_string()))?
543 } else {
544 entry
545 .key
546 .clone()
547 .ok_or_else(|| AuthError::ConfigError("Missing key".to_string()))?
548 };
549
550 keys.insert(key_value, entry);
551 }
552
553 Ok(keys)
554 }
555
556 fn validate_key(&self, key: &str) -> AuthResult<Principal> {
557 let entry = if self.config.hash_keys {
558 let hash = Self::hash_key(key);
562 self.keys.get(&hash).ok_or(AuthError::InvalidCredentials)?
563 } else {
564 let key_bytes = key.as_bytes();
569 self.keys
570 .iter()
571 .find(|(stored, _)| constant_time_eq(stored.as_bytes(), key_bytes))
572 .map(|(_, e)| e)
573 .ok_or(AuthError::InvalidCredentials)?
574 };
575
576 let mut principal = Principal::new(
578 entry.user_id.clone(),
579 entry.name.clone(),
580 AuthMethod::ApiKey,
581 );
582
583 if !entry.roles.is_empty() {
585 principal = principal.with_attribute("roles".to_string(), entry.roles.join(","));
586 }
587
588 for (key, value) in &entry.attributes {
590 principal = principal.with_attribute(key.clone(), value.clone());
591 }
592
593 debug!(
594 "Successfully authenticated API key for user: {}",
595 entry.user_id
596 );
597 Ok(principal)
598 }
599
600 fn hash_key(key: &str) -> String {
601 let mut hasher = Sha256::new();
602 hasher.update(key.as_bytes());
603 let result = hasher.finalize();
604 BASE64.encode(result)
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611 use jsonwebtoken::{EncodingKey, Header, encode};
612 use std::env;
613 use std::io::Write;
614
615 fn hmac_jwt_settings(algorithm: &str, secret: &str) -> JwtSettings {
617 JwtSettings {
618 enabled: true,
619 secret: Some(secret.to_string()),
620 public_key_path: None,
621 ec_public_key_path: None,
622 ed_public_key_path: None,
623 algorithm: algorithm.to_string(),
624 expiration_secs: 3600,
625 issuer: None,
626 audience: None,
627 }
628 }
629
630 fn make_claims(sub: &str) -> JwtClaims {
632 let now = std::time::SystemTime::now()
633 .duration_since(std::time::UNIX_EPOCH)
634 .expect("system time before epoch")
635 .as_secs() as usize;
636 JwtClaims {
637 sub: sub.to_string(),
638 exp: now + 3600,
639 iat: Some(now),
640 iss: None,
641 aud: None,
642 name: Some(format!("User {}", sub)),
643 roles: Some(vec!["admin".to_string()]),
644 attributes: HashMap::new(),
645 }
646 }
647
648 fn make_expired_claims(sub: &str) -> JwtClaims {
650 let now = std::time::SystemTime::now()
651 .duration_since(std::time::UNIX_EPOCH)
652 .expect("system time before epoch")
653 .as_secs() as usize;
654 JwtClaims {
655 sub: sub.to_string(),
656 exp: now.saturating_sub(3600), iat: Some(now.saturating_sub(7200)),
658 iss: None,
659 aud: None,
660 name: Some(format!("User {}", sub)),
661 roles: None,
662 attributes: HashMap::new(),
663 }
664 }
665
666 fn write_temp_key(name: &str, content: &[u8]) -> std::path::PathBuf {
668 let dir = std::env::temp_dir().join("amaters_jwt_test");
669 std::fs::create_dir_all(&dir).expect("failed to create temp dir");
670 let path = dir.join(name);
671 let mut file = std::fs::File::create(&path).expect("failed to create temp file");
672 file.write_all(content).expect("failed to write temp file");
673 path
674 }
675
676 fn rsa_test_keys() -> (&'static [u8], &'static [u8]) {
680 (
682 include_bytes!("../tests/fixtures/rsa_private.pem"),
683 include_bytes!("../tests/fixtures/rsa_public.pem"),
684 )
685 }
686
687 fn ec256_test_keys() -> (&'static [u8], &'static [u8]) {
688 (
689 include_bytes!("../tests/fixtures/ec256_private.pem"),
690 include_bytes!("../tests/fixtures/ec256_public.pem"),
691 )
692 }
693
694 fn ec384_test_keys() -> (&'static [u8], &'static [u8]) {
695 (
696 include_bytes!("../tests/fixtures/ec384_private.pem"),
697 include_bytes!("../tests/fixtures/ec384_public.pem"),
698 )
699 }
700
701 fn ed25519_test_keys() -> (&'static [u8], &'static [u8]) {
702 (
703 include_bytes!("../tests/fixtures/ed25519_private.pem"),
704 include_bytes!("../tests/fixtures/ed25519_public.pem"),
705 )
706 }
707
708 #[test]
709 fn test_principal_creation() {
710 let principal = Principal::new(
711 "user123".to_string(),
712 "John Doe".to_string(),
713 AuthMethod::Jwt,
714 );
715
716 assert_eq!(principal.id, "user123");
717 assert_eq!(principal.name, "John Doe");
718 assert_eq!(principal.auth_method, AuthMethod::Jwt);
719 }
720
721 #[test]
722 fn test_principal_attributes() {
723 let principal = Principal::new(
724 "user123".to_string(),
725 "John Doe".to_string(),
726 AuthMethod::Jwt,
727 )
728 .with_attribute("role".to_string(), "admin".to_string())
729 .with_attribute("department".to_string(), "Engineering".to_string());
730
731 assert_eq!(principal.get_attribute("role"), Some(&"admin".to_string()));
732 assert!(principal.has_role("admin"));
733 assert!(!principal.has_role("user"));
734 }
735
736 #[test]
737 fn test_api_key_hashing() {
738 let key = "test-api-key-12345";
739 let hash1 = ApiKeyValidator::hash_key(key);
740 let hash2 = ApiKeyValidator::hash_key(key);
741
742 assert_eq!(hash1, hash2); assert!(!hash1.is_empty());
744 }
745
746 #[test]
747 fn test_authenticator_creation() {
748 let config = AuthSettings {
749 enabled: true,
750 methods: vec!["mtls".to_string()],
751 mtls: MtlsSettings {
752 enabled: true,
753 ca_certs_dir: Some(env::temp_dir()),
754 crl_path: None,
755 verify_cn: true,
756 allowed_organizations: Vec::new(),
757 },
758 jwt: JwtSettings::default(),
759 api_key: ApiKeySettings::default(),
760 reject_unauthenticated: true,
761 };
762
763 let auth_result = Authenticator::new(config);
764 assert!(auth_result.is_ok());
765 }
766
767 #[test]
768 fn test_jwt_validator_creation_missing_secret() {
769 let config = JwtSettings {
770 enabled: true,
771 secret: None,
772 public_key_path: None,
773 ec_public_key_path: None,
774 ed_public_key_path: None,
775 algorithm: "HS256".to_string(),
776 expiration_secs: 3600,
777 issuer: None,
778 audience: None,
779 };
780
781 let result = JwtValidator::new(config);
782 assert!(result.is_err());
783 }
784
785 #[test]
786 fn test_auth_method_display() {
787 assert_eq!(format!("{}", AuthMethod::MutualTls), "mTLS");
788 assert_eq!(format!("{}", AuthMethod::Jwt), "JWT");
789 assert_eq!(format!("{}", AuthMethod::ApiKey), "API Key");
790 }
791
792 #[test]
795 fn test_jwt_hs256_validation() {
796 let secret = "super-secret-key-for-hs256-testing";
797 let config = hmac_jwt_settings("HS256", secret);
798 let validator = JwtValidator::new(config).expect("HS256 validator creation failed");
799
800 let claims = make_claims("user-hs256");
801 let header = Header::new(Algorithm::HS256);
802 let encoding_key = EncodingKey::from_secret(secret.as_bytes());
803 let token = encode(&header, &claims, &encoding_key).expect("HS256 token encoding failed");
804
805 let principal = validator
806 .validate_token(&token)
807 .expect("HS256 token validation failed");
808 assert_eq!(principal.id, "user-hs256");
809 assert_eq!(principal.auth_method, AuthMethod::Jwt);
810 }
811
812 #[test]
813 fn test_jwt_rs256_validation() {
814 let (private_pem, public_pem) = rsa_test_keys();
815 let pub_path = write_temp_key("rs256_pub.pem", public_pem);
816
817 let config = JwtSettings {
818 enabled: true,
819 secret: None,
820 public_key_path: Some(pub_path),
821 ec_public_key_path: None,
822 ed_public_key_path: None,
823 algorithm: "RS256".to_string(),
824 expiration_secs: 3600,
825 issuer: None,
826 audience: None,
827 };
828 let validator = JwtValidator::new(config).expect("RS256 validator creation failed");
829
830 let claims = make_claims("user-rs256");
831 let header = Header::new(Algorithm::RS256);
832 let encoding_key =
833 EncodingKey::from_rsa_pem(private_pem).expect("RS256 encoding key creation failed");
834 let token = encode(&header, &claims, &encoding_key).expect("RS256 token encoding failed");
835
836 let principal = validator
837 .validate_token(&token)
838 .expect("RS256 token validation failed");
839 assert_eq!(principal.id, "user-rs256");
840 }
841
842 #[test]
843 fn test_jwt_rs384_validation() {
844 let (private_pem, public_pem) = rsa_test_keys();
845 let pub_path = write_temp_key("rs384_pub.pem", public_pem);
846
847 let config = JwtSettings {
848 enabled: true,
849 secret: None,
850 public_key_path: Some(pub_path),
851 ec_public_key_path: None,
852 ed_public_key_path: None,
853 algorithm: "RS384".to_string(),
854 expiration_secs: 3600,
855 issuer: None,
856 audience: None,
857 };
858 let validator = JwtValidator::new(config).expect("RS384 validator creation failed");
859
860 let claims = make_claims("user-rs384");
861 let header = Header::new(Algorithm::RS384);
862 let encoding_key =
863 EncodingKey::from_rsa_pem(private_pem).expect("RS384 encoding key creation failed");
864 let token = encode(&header, &claims, &encoding_key).expect("RS384 token encoding failed");
865
866 let principal = validator
867 .validate_token(&token)
868 .expect("RS384 token validation failed");
869 assert_eq!(principal.id, "user-rs384");
870 }
871
872 #[test]
873 fn test_jwt_rs512_validation() {
874 let (private_pem, public_pem) = rsa_test_keys();
875 let pub_path = write_temp_key("rs512_pub.pem", public_pem);
876
877 let config = JwtSettings {
878 enabled: true,
879 secret: None,
880 public_key_path: Some(pub_path),
881 ec_public_key_path: None,
882 ed_public_key_path: None,
883 algorithm: "RS512".to_string(),
884 expiration_secs: 3600,
885 issuer: None,
886 audience: None,
887 };
888 let validator = JwtValidator::new(config).expect("RS512 validator creation failed");
889
890 let claims = make_claims("user-rs512");
891 let header = Header::new(Algorithm::RS512);
892 let encoding_key =
893 EncodingKey::from_rsa_pem(private_pem).expect("RS512 encoding key creation failed");
894 let token = encode(&header, &claims, &encoding_key).expect("RS512 token encoding failed");
895
896 let principal = validator
897 .validate_token(&token)
898 .expect("RS512 token validation failed");
899 assert_eq!(principal.id, "user-rs512");
900 }
901
902 #[test]
903 fn test_jwt_es256_validation() {
904 let (private_pem, public_pem) = ec256_test_keys();
905 let pub_path = write_temp_key("es256_pub.pem", public_pem);
906
907 let config = JwtSettings {
908 enabled: true,
909 secret: None,
910 public_key_path: None,
911 ec_public_key_path: Some(pub_path),
912 ed_public_key_path: None,
913 algorithm: "ES256".to_string(),
914 expiration_secs: 3600,
915 issuer: None,
916 audience: None,
917 };
918 let validator = JwtValidator::new(config).expect("ES256 validator creation failed");
919
920 let claims = make_claims("user-es256");
921 let header = Header::new(Algorithm::ES256);
922 let encoding_key =
923 EncodingKey::from_ec_pem(private_pem).expect("ES256 encoding key creation failed");
924 let token = encode(&header, &claims, &encoding_key).expect("ES256 token encoding failed");
925
926 let principal = validator
927 .validate_token(&token)
928 .expect("ES256 token validation failed");
929 assert_eq!(principal.id, "user-es256");
930 }
931
932 #[test]
933 fn test_jwt_es384_validation() {
934 let (private_pem, public_pem) = ec384_test_keys();
935 let pub_path = write_temp_key("es384_pub.pem", public_pem);
936
937 let config = JwtSettings {
938 enabled: true,
939 secret: None,
940 public_key_path: None,
941 ec_public_key_path: Some(pub_path),
942 ed_public_key_path: None,
943 algorithm: "ES384".to_string(),
944 expiration_secs: 3600,
945 issuer: None,
946 audience: None,
947 };
948 let validator = JwtValidator::new(config).expect("ES384 validator creation failed");
949
950 let claims = make_claims("user-es384");
951 let header = Header::new(Algorithm::ES384);
952 let encoding_key =
953 EncodingKey::from_ec_pem(private_pem).expect("ES384 encoding key creation failed");
954 let token = encode(&header, &claims, &encoding_key).expect("ES384 token encoding failed");
955
956 let principal = validator
957 .validate_token(&token)
958 .expect("ES384 token validation failed");
959 assert_eq!(principal.id, "user-es384");
960 }
961
962 #[test]
963 fn test_jwt_eddsa_validation() {
964 let (private_pem, public_pem) = ed25519_test_keys();
965 let pub_path = write_temp_key("eddsa_pub.pem", public_pem);
966
967 let config = JwtSettings {
968 enabled: true,
969 secret: None,
970 public_key_path: None,
971 ec_public_key_path: None,
972 ed_public_key_path: Some(pub_path),
973 algorithm: "EdDSA".to_string(),
974 expiration_secs: 3600,
975 issuer: None,
976 audience: None,
977 };
978 let validator = JwtValidator::new(config).expect("EdDSA validator creation failed");
979
980 let claims = make_claims("user-eddsa");
981 let header = Header::new(Algorithm::EdDSA);
982 let encoding_key =
983 EncodingKey::from_ed_pem(private_pem).expect("EdDSA encoding key creation failed");
984 let token = encode(&header, &claims, &encoding_key).expect("EdDSA token encoding failed");
985
986 let principal = validator
987 .validate_token(&token)
988 .expect("EdDSA token validation failed");
989 assert_eq!(principal.id, "user-eddsa");
990 }
991
992 #[test]
993 fn test_jwt_algorithm_mismatch() {
994 let secret = "test-mismatch-secret";
996 let config = hmac_jwt_settings("HS256", secret);
997 let validator = JwtValidator::new(config).expect("HS256 validator creation failed");
998
999 let (private_pem, _) = rsa_test_keys();
1000 let claims = make_claims("user-mismatch");
1001 let header = Header::new(Algorithm::RS256);
1002 let encoding_key =
1003 EncodingKey::from_rsa_pem(private_pem).expect("RS256 encoding key creation failed");
1004 let token = encode(&header, &claims, &encoding_key).expect("RS256 token encoding failed");
1005
1006 let result = validator.validate_token(&token);
1007 assert!(result.is_err(), "Algorithm mismatch should fail validation");
1008 }
1009
1010 #[test]
1011 fn test_jwt_missing_ec_key_path() {
1012 let config = JwtSettings {
1013 enabled: true,
1014 secret: None,
1015 public_key_path: None,
1016 ec_public_key_path: None, ed_public_key_path: None,
1018 algorithm: "ES256".to_string(),
1019 expiration_secs: 3600,
1020 issuer: None,
1021 audience: None,
1022 };
1023
1024 let result = JwtValidator::new(config);
1025 assert!(result.is_err());
1026 let err_msg = format!("{}", result.expect_err("should be an error"));
1027 assert!(
1028 err_msg.contains("EC public key path not configured"),
1029 "Expected EC key path error, got: {}",
1030 err_msg
1031 );
1032 }
1033
1034 #[test]
1035 fn test_jwt_missing_ed_key_path() {
1036 let config = JwtSettings {
1037 enabled: true,
1038 secret: None,
1039 public_key_path: None,
1040 ec_public_key_path: None,
1041 ed_public_key_path: None, algorithm: "EdDSA".to_string(),
1043 expiration_secs: 3600,
1044 issuer: None,
1045 audience: None,
1046 };
1047
1048 let result = JwtValidator::new(config);
1049 assert!(result.is_err());
1050 let err_msg = format!("{}", result.expect_err("should be an error"));
1051 assert!(
1052 err_msg.contains("Ed25519 public key path not configured"),
1053 "Expected Ed25519 key path error, got: {}",
1054 err_msg
1055 );
1056 }
1057
1058 #[test]
1059 fn test_jwt_invalid_ec_key() {
1060 let corrupt_path = write_temp_key("corrupt_ec.pem", b"NOT A VALID PEM KEY");
1062
1063 let config = JwtSettings {
1064 enabled: true,
1065 secret: None,
1066 public_key_path: None,
1067 ec_public_key_path: Some(corrupt_path),
1068 ed_public_key_path: None,
1069 algorithm: "ES256".to_string(),
1070 expiration_secs: 3600,
1071 issuer: None,
1072 audience: None,
1073 };
1074
1075 let result = JwtValidator::new(config);
1076 assert!(result.is_err(), "Corrupt EC key should fail");
1077 let err_msg = format!("{}", result.expect_err("should be an error"));
1078 assert!(
1079 err_msg.contains("Failed to load EC public key"),
1080 "Expected EC key load error, got: {}",
1081 err_msg
1082 );
1083 }
1084
1085 #[test]
1086 fn test_jwt_expired_token() {
1087 let secret = "expiration-test-secret";
1088 let config = hmac_jwt_settings("HS256", secret);
1089 let validator = JwtValidator::new(config).expect("HS256 validator creation failed");
1090
1091 let claims = make_expired_claims("user-expired");
1092 let header = Header::new(Algorithm::HS256);
1093 let encoding_key = EncodingKey::from_secret(secret.as_bytes());
1094 let token = encode(&header, &claims, &encoding_key).expect("Expired token encoding failed");
1095
1096 let result = validator.validate_token(&token);
1097 assert!(result.is_err(), "Expired token should fail validation");
1098 let err_msg = format!("{}", result.expect_err("should be an error"));
1099 assert!(
1100 err_msg.contains("Token validation failed"),
1101 "Expected token validation error, got: {}",
1102 err_msg
1103 );
1104 }
1105
1106 #[test]
1107 fn test_jwt_unsupported_algorithm() {
1108 let config = JwtSettings {
1109 enabled: true,
1110 secret: Some("secret".to_string()),
1111 public_key_path: None,
1112 ec_public_key_path: None,
1113 ed_public_key_path: None,
1114 algorithm: "UNSUPPORTED".to_string(),
1115 expiration_secs: 3600,
1116 issuer: None,
1117 audience: None,
1118 };
1119
1120 let result = JwtValidator::new(config);
1121 assert!(result.is_err());
1122 let err_msg = format!("{}", result.expect_err("should be an error"));
1123 assert!(
1124 err_msg.contains("Unsupported JWT algorithm"),
1125 "Expected unsupported algorithm error, got: {}",
1126 err_msg
1127 );
1128 }
1129}