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