Skip to main content

auth_framework/server/security/
x509_signing.rs

1//! Enhanced X.509 Certificate Signing Module
2//!
3//! This module provides comprehensive X.509 certificate signing capabilities
4//! for enterprise authentication scenarios including:
5//!
6//! # Features
7//!
8//! - **Certificate Authority (CA) Operations**: Root and intermediate CA management
9//! - **Certificate Signing Requests (CSR)**: Generate and sign CSRs
10//! - **Certificate Lifecycle**: Create, renew, revoke, and validate certificates
11//! - **Multiple Key Types**: RSA, ECDSA, and Ed25519 support
12//! - **Certificate Profiles**: Different certificate types for various use cases
13//! - **CRL and OCSP**: Certificate revocation mechanisms
14//! - **Enterprise Integration**: LDAP, Active Directory, and PKI integration
15//!
16//! # Use Cases
17//!
18//! - Client certificate authentication (OAuth 2.0 mTLS)
19//! - Code signing certificates
20//! - TLS/SSL server certificates
21//! - Email signing and encryption certificates
22//! - Document signing certificates
23//! - IoT device certificates
24
25use crate::errors::{AuthError, Result};
26use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
27use chrono::{DateTime, Duration, Utc};
28use serde::{Deserialize, Serialize};
29use serde_json::Value;
30use std::collections::HashMap;
31use std::sync::Arc;
32use std::time::SystemTime;
33use tokio::sync::RwLock;
34use uuid::Uuid;
35use x509_parser::parse_x509_certificate;
36
37/// Enhanced X.509 Certificate Manager
38#[derive(Debug, Clone)]
39pub struct X509CertificateManager {
40    /// Configuration
41    config: X509Config,
42
43    /// Certificate store
44    certificate_store: Arc<RwLock<HashMap<String, StoredCertificate>>>,
45
46    /// Certificate revocation list
47    revocation_list: Arc<RwLock<HashMap<String, RevocationEntry>>>,
48
49    /// CA certificates
50    ca_certificates: Arc<RwLock<HashMap<String, CACertificate>>>,
51}
52
53/// X.509 Configuration
54#[derive(Debug, Clone)]
55pub struct X509Config {
56    /// Default certificate validity period (days)
57    pub default_validity_days: i64,
58
59    /// Root CA certificate path
60    pub root_ca_cert_path: String,
61
62    /// Root CA certificate path (alias)
63    pub root_ca_path: String,
64
65    /// Root CA private key path
66    pub root_ca_key_path: String,
67
68    /// Intermediate CA certificate path
69    pub intermediate_ca_cert_path: Option<String>,
70
71    /// Intermediate CA certificate path (alias)
72    pub intermediate_ca_path: Option<String>,
73
74    /// Intermediate CA private key path
75    pub intermediate_ca_key_path: Option<String>,
76
77    /// Default key size for RSA
78    pub default_rsa_key_size: u32,
79
80    /// Default curve for ECDSA
81    pub default_ecdsa_curve: EcdsaCurve,
82
83    /// Certificate profiles
84    pub certificate_profiles: HashMap<String, CertificateProfile>,
85
86    /// Enable OCSP (Online Certificate Status Protocol)
87    pub enable_ocsp: bool,
88
89    /// OCSP responder URL
90    pub ocsp_responder_url: Option<String>,
91
92    /// Enable CRL (Certificate Revocation List)
93    pub enable_crl: bool,
94
95    /// CRL distribution point URL
96    pub crl_distribution_url: Option<String>,
97}
98
99/// ECDSA Curve types
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub enum EcdsaCurve {
102    /// P-256 (secp256r1)
103    P256,
104    /// P-384 (secp384r1)
105    P384,
106    /// P-521 (secp521r1)
107    P521,
108}
109
110/// Certificate Profile for different use cases
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct CertificateProfile {
113    /// Profile name
114    pub name: String,
115
116    /// Certificate type
117    pub cert_type: CertificateType,
118
119    /// Key usage flags
120    pub key_usage: Vec<KeyUsage>,
121
122    /// Extended key usage
123    pub extended_key_usage: Vec<ExtendedKeyUsage>,
124
125    /// Subject alternative names
126    pub subject_alt_names: Vec<SubjectAltName>,
127
128    /// Validity period (days)
129    pub validity_days: i64,
130
131    /// Key type preference
132    pub preferred_key_type: KeyType,
133
134    /// Additional extensions
135    pub extensions: HashMap<String, Value>,
136}
137
138/// Certificate Types
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140pub enum CertificateType {
141    /// Root Certificate Authority
142    RootCA,
143    /// Intermediate Certificate Authority
144    IntermediateCA,
145    /// End entity certificate (leaf)
146    EndEntity,
147    /// Code signing certificate
148    CodeSigning,
149    /// Email certificate
150    Email,
151    /// TLS server certificate
152    TlsServer,
153    /// TLS client certificate
154    TlsClient,
155    /// Document signing certificate
156    DocumentSigning,
157}
158
159/// Key Usage flags
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161pub enum KeyUsage {
162    /// Digital signature
163    DigitalSignature,
164    /// Non-repudiation
165    NonRepudiation,
166    /// Key encipherment
167    KeyEncipherment,
168    /// Data encipherment
169    DataEncipherment,
170    /// Key agreement
171    KeyAgreement,
172    /// Key certificate signing
173    KeyCertSign,
174    /// CRL signing
175    CrlSign,
176    /// Encipher only
177    EncipherOnly,
178    /// Decipher only
179    DecipherOnly,
180}
181
182/// Extended Key Usage
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub enum ExtendedKeyUsage {
185    /// Server authentication
186    ServerAuth,
187    /// Client authentication
188    ClientAuth,
189    /// Code signing
190    CodeSigning,
191    /// Email protection
192    EmailProtection,
193    /// Time stamping
194    TimeStamping,
195    /// OCSP signing
196    OcspSigning,
197}
198
199/// Subject Alternative Name types
200#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
201pub enum SubjectAltName {
202    /// DNS name
203    DnsName(String),
204    /// Email address
205    Email(String),
206    /// URI
207    Uri(String),
208    /// IP address
209    IpAddress(String),
210}
211
212/// Key Types
213#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
214pub enum KeyType {
215    /// RSA key
216    Rsa(u32), // Key size in bits
217    /// ECDSA key
218    Ecdsa(EcdsaCurve),
219    /// Ed25519 key
220    Ed25519,
221}
222
223/// Stored Certificate
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct StoredCertificate {
226    /// Certificate ID
227    pub cert_id: String,
228
229    /// Certificate data (PEM format)
230    pub certificate_pem: String,
231
232    /// Private key (PEM format, optional)
233    pub private_key_pem: Option<String>,
234
235    /// Certificate subject
236    pub subject: String,
237
238    /// Certificate issuer
239    pub issuer: String,
240
241    /// Serial number
242    pub serial_number: String,
243
244    /// Not before date
245    pub not_before: DateTime<Utc>,
246
247    /// Not after date
248    pub not_after: DateTime<Utc>,
249
250    /// Certificate profile used
251    pub profile: String,
252
253    /// Certificate status
254    pub status: CertificateStatus,
255
256    /// Fingerprint (SHA-256)
257    pub fingerprint: String,
258
259    /// Created timestamp
260    pub created_at: DateTime<Utc>,
261
262    /// Metadata
263    pub metadata: HashMap<String, Value>,
264}
265
266/// Certificate Status
267#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
268pub enum CertificateStatus {
269    /// Certificate is valid
270    Valid,
271    /// Certificate is expired
272    Expired,
273    /// Certificate is revoked
274    Revoked,
275    /// Certificate is suspended
276    Suspended,
277}
278
279/// CA Certificate
280#[derive(Debug, Clone)]
281pub struct CACertificate {
282    /// CA ID
283    pub ca_id: String,
284
285    /// CA certificate
286    pub certificate: StoredCertificate,
287
288    /// Certificate subject
289    pub subject: String,
290
291    /// CA private key
292    pub private_key: Vec<u8>,
293
294    /// CA type
295    pub ca_type: CAType,
296
297    /// Issued certificates count
298    pub issued_count: u64,
299
300    /// Next certificate serial number
301    pub next_serial: u64,
302}
303
304/// CA Types
305#[derive(Debug, Clone, PartialEq)]
306pub enum CAType {
307    /// Root CA
308    Root,
309    /// Intermediate CA
310    Intermediate,
311}
312
313/// Certificate Revocation Entry
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct RevocationEntry {
316    /// Certificate serial number
317    pub serial_number: String,
318
319    /// Revocation date
320    pub revocation_date: DateTime<Utc>,
321
322    /// Revocation reason
323    pub reason: RevocationReason,
324
325    /// Additional information
326    pub additional_info: Option<String>,
327}
328
329/// Revocation Reasons (RFC 5280)
330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
331pub enum RevocationReason {
332    /// Unspecified
333    Unspecified,
334    /// Key compromise
335    KeyCompromise,
336    /// CA compromise
337    CaCompromise,
338    /// Affiliation changed
339    AffiliationChanged,
340    /// Superseded
341    Superseded,
342    /// Cessation of operation
343    CessationOfOperation,
344    /// Certificate hold
345    CertificateHold,
346    /// Remove from CRL
347    RemoveFromCrl,
348    /// Privilege withdrawn
349    PrivilegeWithdrawn,
350    /// AA compromise
351    AaCompromise,
352}
353
354/// Certificate Signing Request
355#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct CertificateRequest {
357    /// Request ID
358    pub request_id: String,
359
360    /// Certificate subject information
361    pub subject: CertificateSubject,
362
363    /// Certificate profile to use
364    pub profile: String,
365
366    /// Public key (PEM format)
367    pub public_key_pem: String,
368
369    /// Subject alternative names
370    pub subject_alt_names: Vec<SubjectAltName>,
371
372    /// Request timestamp
373    pub requested_at: DateTime<Utc>,
374
375    /// Requester information
376    pub requester: String,
377
378    /// Additional attributes
379    pub attributes: HashMap<String, Value>,
380}
381
382/// Certificate Subject Information
383#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct CertificateSubject {
385    /// Common name
386    pub common_name: String,
387
388    /// Organization
389    pub organization: Option<String>,
390
391    /// Organizational unit
392    pub organizational_unit: Option<String>,
393
394    /// Country
395    pub country: Option<String>,
396
397    /// State/Province
398    pub state: Option<String>,
399
400    /// City/Locality
401    pub locality: Option<String>,
402
403    /// Email address
404    pub email: Option<String>,
405}
406
407impl X509CertificateManager {
408    /// Create new X.509 certificate manager
409    pub fn new(config: X509Config) -> Self {
410        Self {
411            config,
412            certificate_store: Arc::new(RwLock::new(HashMap::new())),
413            revocation_list: Arc::new(RwLock::new(HashMap::new())),
414            ca_certificates: Arc::new(RwLock::new(HashMap::new())),
415        }
416    }
417
418    /// Initialize certificate manager with CA certificates
419    pub async fn initialize(&self) -> Result<()> {
420        // Load root CA certificate
421        self.load_root_ca().await?;
422
423        // Load intermediate CA certificate if configured
424        if self.config.intermediate_ca_cert_path.is_some() {
425            self.load_intermediate_ca().await?;
426        }
427
428        Ok(())
429    }
430
431    /// Load root CA certificate
432    async fn load_root_ca(&self) -> Result<()> {
433        // Production implementation: Load from secure certificate store or HSM
434        // Check for HSM configuration first
435        #[cfg(feature = "hsm")]
436        if let Ok(hsm_config) = std::env::var("X509_HSM_CONFIG") {
437            tracing::info!("Loading CA certificate from HSM: {}", hsm_config);
438            // In production, integrate with PKCS#11 or Azure Key Vault
439            return self.load_ca_from_hsm(&hsm_config).await;
440        }
441        #[cfg(not(feature = "hsm"))]
442        if std::env::var("X509_HSM_CONFIG").is_ok() {
443            tracing::warn!(
444                "X509_HSM_CONFIG is set but the 'hsm' feature is not enabled — ignoring"
445            );
446        }
447
448        // Check for Azure Key Vault configuration
449        if let Ok(vault_url) = std::env::var("X509_AZURE_VAULT_URL")
450            && let Ok(cert_name) = std::env::var("X509_AZURE_CERT_NAME")
451        {
452            tracing::info!("Loading CA certificate from Azure Key Vault: {}", vault_url);
453            return self.load_ca_from_azure_vault(&vault_url, &cert_name).await;
454        }
455
456        // Check for AWS Secrets Manager configuration
457        if let Ok(secret_id) = std::env::var("X509_AWS_SECRET_ID") {
458            tracing::info!(
459                "Loading CA certificate from AWS Secrets Manager: {}",
460                secret_id
461            );
462            return self.load_ca_from_aws_secrets(&secret_id).await;
463        }
464
465        // Fallback to file system loading with proper security validation
466        let ca_cert_path = if self.config.root_ca_path.is_empty() {
467            "ca/root-ca.pem"
468        } else {
469            &self.config.root_ca_path
470        };
471
472        tracing::warn!(
473            "Loading CA certificate from file system - consider using HSM or secure vault for production"
474        );
475        self.load_ca_from_file(ca_cert_path).await
476    }
477
478    /// Load CA certificate from HSM (Hardware Security Module) via PKCS#11.
479    ///
480    /// `hsm_config` must be a JSON object with the following fields:
481    /// - `library` (required): absolute path to the PKCS#11 shared library (`.so` / `.dll`)
482    /// - `slot` (optional, default `0`): slot index returned by `get_slots_with_initialized_token`
483    /// - `pin` (optional): User PIN for login (omit for login-less tokens)
484    /// - `label` (optional, default `"ca-cert"`): CKA_LABEL of the certificate object
485    ///
486    /// # Example config
487    /// ```text
488    /// {"library":"/usr/lib/softhsm/libsofthsm2.so","slot":0,"pin":"1234","label":"root-ca"}
489    /// ```
490    #[cfg(feature = "hsm")]
491    async fn load_ca_from_hsm(&self, hsm_config: &str) -> Result<()> {
492        let config: serde_json::Value = serde_json::from_str(hsm_config)
493            .map_err(|e| AuthError::config(format!("Invalid HSM JSON config: {}", e)))?;
494
495        let library = config["library"]
496            .as_str()
497            .ok_or_else(|| AuthError::config("HSM config missing 'library' path".to_string()))?;
498
499        // Extract optional parameters
500        let slot_id = config["slot"].as_u64().unwrap_or(0);
501        let pin = config["pin"].as_str().map(|s| s.to_string());
502        let _label = config["label"].as_str().unwrap_or("root-ca").to_string();
503
504        // Note: Real PKCS#11 operations are synchronous and may block.
505        // We use spawn_blocking to prevent blocking the async runtime.
506        let library_path = library.to_string();
507
508        let handle = tokio::task::spawn_blocking(move || -> Result<Vec<u8>> {
509            // First, initialize the PKCS#11 context
510            let pkcs11 = cryptoki::context::Pkcs11::new(&library_path)
511                .map_err(|e| AuthError::config(format!("Failed to load PKCS#11 library: {}", e)))?;
512
513            pkcs11
514                .initialize(cryptoki::context::CInitializeArgs::new(
515                    cryptoki::context::CInitializeFlags::OS_LOCKING_OK,
516                ))
517                .map_err(|e| {
518                    AuthError::config(format!("Failed to initialize PKCS#11 context: {}", e))
519                })?;
520
521            // Get slots
522            let slots = pkcs11
523                .get_slots_with_token()
524                .map_err(|e| AuthError::config(format!("Failed to get PKCS#11 slots: {}", e)))?;
525
526            if slot_id as usize >= slots.len() {
527                return Err(AuthError::config(format!(
528                    "HSM slot {} not found or has no token",
529                    slot_id
530                )));
531            }
532            let slot = slots[slot_id as usize];
533
534            // Open a session
535            let session = pkcs11
536                .open_ro_session(slot)
537                .map_err(|e| AuthError::config(format!("Failed to open PKCS#11 session: {}", e)))?;
538
539            // Login if PIN is provided
540            if let Some(p) = pin {
541                let auth_pin = cryptoki::types::AuthPin::new(p.into());
542                session
543                    .login(cryptoki::session::UserType::User, Some(&auth_pin))
544                    .map_err(|e| AuthError::config(format!("HSM login failed: {}", e)))?;
545            }
546
547            // Find the certificate object by label
548            let mut search_template: Vec<cryptoki::object::Attribute> = Vec::new();
549            search_template.push(cryptoki::object::Attribute::Class(
550                cryptoki::object::ObjectClass::CERTIFICATE,
551            ));
552            search_template.push(cryptoki::object::Attribute::Label(
553                _label.clone().into_bytes(),
554            ));
555
556            let objects = session.find_objects(&search_template).map_err(|e| {
557                AuthError::config(format!("Failed to search PKCS#11 objects: {}", e))
558            })?;
559
560            if objects.is_empty() {
561                return Err(AuthError::config(format!(
562                    "Certificate with label '{}' not found in HSM",
563                    _label
564                )));
565            }
566
567            // Read the certificate value (usually CKA_VALUE for X.509 certs)
568            let cert_obj = objects[0];
569            let attrs = session
570                .get_attributes(cert_obj, &[cryptoki::object::AttributeType::Value])
571                .map_err(|e| {
572                    AuthError::config(format!("Failed to get certificate value from HSM: {}", e))
573                })?;
574
575            if attrs.is_empty() {
576                return Err(AuthError::config(
577                    "Certificate object has no value attribute".to_string(),
578                ));
579            }
580
581            let value = match &attrs[0] {
582                cryptoki::object::Attribute::Value(v) => v.clone(),
583                _ => {
584                    return Err(AuthError::config(
585                        "Invalid value attribute type".to_string(),
586                    ));
587                }
588            };
589
590            Ok(value)
591        });
592
593        let cert_der = handle
594            .await
595            .map_err(|_| AuthError::config("HSM task panicked".to_string()))??;
596
597        // Convert the DER certificate bytes to PEM format and store via the
598        // shared helper that all external CA integrations use.
599        let cert_pem = format!(
600            "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----",
601            BASE64_STANDARD.encode(&cert_der)
602        );
603
604        self.store_ca_certificate_from_pem(&cert_pem, &format!("hsm:slot{}", slot_id))
605            .await
606    }
607
608    async fn load_ca_from_azure_vault(&self, vault_url: &str, cert_name: &str) -> Result<()> {
609        let tenant_id = std::env::var("X509_AZURE_TENANT_ID").map_err(|_| {
610            AuthError::config(
611                "X509_AZURE_TENANT_ID environment variable required for Azure Key Vault authentication"
612                    .to_string(),
613            )
614        })?;
615        let client_id = std::env::var("X509_AZURE_CLIENT_ID").map_err(|_| {
616            AuthError::config(
617                "X509_AZURE_CLIENT_ID environment variable required for Azure Key Vault authentication"
618                    .to_string(),
619            )
620        })?;
621        let client_secret = std::env::var("X509_AZURE_CLIENT_SECRET").map_err(|_| {
622            AuthError::config(
623                "X509_AZURE_CLIENT_SECRET environment variable required for Azure Key Vault authentication"
624                    .to_string(),
625            )
626        })?;
627
628        let http = reqwest::Client::new();
629
630        // Step 1: Client-credentials flow → access token.
631        let token_url = format!(
632            "https://login.microsoftonline.com/{}/oauth2/v2.0/token",
633            tenant_id
634        );
635        let token_resp = http
636            .post(&token_url)
637            .form(&[
638                ("grant_type", "client_credentials"),
639                ("client_id", client_id.as_str()),
640                ("client_secret", client_secret.as_str()),
641                ("scope", "https://vault.azure.net/.default"),
642            ])
643            .send()
644            .await
645            .map_err(|e| AuthError::internal(format!("Azure AD token request failed: {}", e)))?;
646
647        if !token_resp.status().is_success() {
648            let status = token_resp.status();
649            let body = token_resp.text().await.unwrap_or_default();
650            return Err(AuthError::config(format!(
651                "Azure AD token request returned {}: {}",
652                status, body
653            )));
654        }
655
656        let token_json: serde_json::Value = token_resp.json().await.map_err(|e| {
657            AuthError::internal(format!("Failed to parse Azure AD token response: {}", e))
658        })?;
659        let access_token = token_json["access_token"]
660            .as_str()
661            .ok_or_else(|| AuthError::internal("Azure AD response missing 'access_token'"))?
662            .to_string();
663
664        // Step 2: Fetch the secret from Key Vault (returns PEM / PKCS#12 bundle).
665        let vault_base = vault_url.trim_end_matches('/');
666        let secret_url = format!("{}/secrets/{}?api-version=7.4", vault_base, cert_name);
667        let cert_resp = http
668            .get(&secret_url)
669            .header("Authorization", format!("Bearer {}", access_token))
670            .send()
671            .await
672            .map_err(|e| AuthError::internal(format!("Azure Key Vault request failed: {}", e)))?;
673
674        if !cert_resp.status().is_success() {
675            let status = cert_resp.status();
676            let body = cert_resp.text().await.unwrap_or_default();
677            return Err(AuthError::config(format!(
678                "Azure Key Vault secret fetch returned {}: {}",
679                status, body
680            )));
681        }
682
683        let cert_json: serde_json::Value = cert_resp.json().await.map_err(|e| {
684            AuthError::internal(format!("Failed to parse Azure Key Vault response: {}", e))
685        })?;
686
687        let raw_value = cert_json["value"]
688            .as_str()
689            .ok_or_else(|| AuthError::internal("Azure Key Vault response missing 'value' field"))?
690            .to_string();
691
692        let content_type = cert_json["contentType"]
693            .as_str()
694            .unwrap_or("application/x-pem-file");
695
696        let cert_pem = if content_type == "application/x-pem-file"
697            || raw_value.contains("-----BEGIN")
698        {
699            // PEM bundle — extract only the leaf/CA certificate block.
700            x509_extract_certificate_pem(&raw_value)
701        } else {
702            return Err(AuthError::config(format!(
703                "Azure Key Vault certificate '{}' uses content-type '{}'. \
704                 Store the certificate as a PEM secret (application/x-pem-file) for automatic import.",
705                cert_name, content_type
706            )));
707        };
708
709        tracing::info!(
710            "Successfully loaded CA certificate from Azure Key Vault: {}/{}",
711            vault_base,
712            cert_name
713        );
714        self.store_ca_certificate_from_pem(
715            &cert_pem,
716            &format!("azure_kv:{}/{}", vault_base, cert_name),
717        )
718        .await
719    }
720
721    /// Load CA certificate from AWS Secrets Manager using AWS SigV4 request signing.
722    ///
723    /// Required environment variables (standard AWS credential chain):
724    /// - `AWS_ACCESS_KEY_ID`
725    /// - `AWS_SECRET_ACCESS_KEY`
726    /// - `AWS_REGION` or `AWS_DEFAULT_REGION`
727    /// - `AWS_SESSION_TOKEN` (optional, for temporary credentials)
728    ///
729    /// The secret value must be a PEM-encoded certificate (or a PEM bundle; the
730    /// first `CERTIFICATE` block is extracted automatically).
731    async fn load_ca_from_aws_secrets(&self, secret_id: &str) -> Result<()> {
732        let access_key = std::env::var("AWS_ACCESS_KEY_ID").map_err(|_| {
733            AuthError::config(
734                "AWS_ACCESS_KEY_ID environment variable required for Secrets Manager".to_string(),
735            )
736        })?;
737        let secret_key = std::env::var("AWS_SECRET_ACCESS_KEY").map_err(|_| {
738            AuthError::config(
739                "AWS_SECRET_ACCESS_KEY environment variable required for Secrets Manager"
740                    .to_string(),
741            )
742        })?;
743        let region = std::env::var("AWS_REGION")
744            .or_else(|_| std::env::var("AWS_DEFAULT_REGION"))
745            .map_err(|_| {
746                AuthError::config(
747                    "AWS_REGION (or AWS_DEFAULT_REGION) environment variable required for Secrets Manager"
748                        .to_string(),
749                )
750            })?;
751        let session_token = std::env::var("AWS_SESSION_TOKEN").ok();
752
753        let service = "secretsmanager";
754        let host = format!("{}.{}.amazonaws.com", service, region);
755        let payload =
756            serde_json::to_vec(&serde_json::json!({ "SecretId": secret_id })).map_err(|e| {
757                AuthError::internal(format!(
758                    "Failed to serialise Secrets Manager GetSecretValue request: {}",
759                    e
760                ))
761            })?;
762
763        let now = chrono::Utc::now();
764        let amz_date = now.format("%Y%m%dT%H%M%SZ").to_string();
765        let date_stamp = now.format("%Y%m%d").to_string();
766
767        let authorization = AwsSigV4Request::new(&access_key, &secret_key)
768            .session_token(session_token.as_deref())
769            .region(&region)
770            .service(service)
771            .method("POST")
772            .host(&host)
773            .payload(&payload)
774            .amz_date(&amz_date)
775            .date_stamp(&date_stamp)
776            .amz_target("secretsmanager.GetSecretValue")
777            .sign();
778
779        let url = format!("https://{}/", host);
780        let http = reqwest::Client::new();
781        let mut req_builder = http
782            .post(&url)
783            .header("Content-Type", "application/x-amz-json-1.1")
784            .header("X-Amz-Target", "secretsmanager.GetSecretValue")
785            .header("X-Amz-Date", &amz_date)
786            .header("Authorization", &authorization)
787            .body(payload);
788
789        if let Some(ref token) = session_token {
790            req_builder = req_builder.header("X-Amz-Security-Token", token.as_str());
791        }
792
793        let resp = req_builder.send().await.map_err(|e| {
794            AuthError::internal(format!("AWS Secrets Manager request failed: {}", e))
795        })?;
796
797        if !resp.status().is_success() {
798            let status = resp.status();
799            let body = resp.text().await.unwrap_or_default();
800            return Err(AuthError::config(format!(
801                "AWS Secrets Manager GetSecretValue returned {}: {}",
802                status, body
803            )));
804        }
805
806        let json: serde_json::Value = resp.json().await.map_err(|e| {
807            AuthError::internal(format!("Failed to parse Secrets Manager response: {}", e))
808        })?;
809
810        let raw_value = if let Some(s) = json["SecretString"].as_str() {
811            s.to_string()
812        } else if let Some(b64) = json["SecretBinary"].as_str() {
813            let bytes = BASE64_STANDARD.decode(b64).map_err(|e| {
814                AuthError::internal(format!("Failed to decode SecretBinary: {}", e))
815            })?;
816            String::from_utf8(bytes).map_err(|e| {
817                AuthError::internal(format!("SecretBinary is not valid UTF-8: {}", e))
818            })?
819        } else {
820            return Err(AuthError::config(format!(
821                "AWS Secrets Manager secret '{}' contains neither SecretString nor SecretBinary",
822                secret_id
823            )));
824        };
825
826        let cert_pem = if raw_value.contains("-----BEGIN CERTIFICATE-----") {
827            x509_extract_certificate_pem(&raw_value)
828        } else {
829            raw_value
830        };
831
832        tracing::info!(
833            "Successfully loaded CA certificate from AWS Secrets Manager: {}",
834            secret_id
835        );
836        self.store_ca_certificate_from_pem(&cert_pem, &format!("aws_secrets:{}", secret_id))
837            .await
838    }
839
840    /// Load CA certificate from file system (with security validation)
841    async fn load_ca_from_file(&self, ca_cert_path: &str) -> Result<()> {
842        let (certificate_pem, subject, issuer, serial_number) = if std::path::Path::new(
843            ca_cert_path,
844        )
845        .exists()
846        {
847            // Load from file (production path)
848            let cert_content = tokio::fs::read_to_string(ca_cert_path).await.map_err(|e| {
849                AuthError::internal(format!("Failed to read CA certificate: {}", e))
850            })?;
851
852            // NOTE: Full X.509 DER parsing requires a dedicated crate (e.g.
853            // x509-parser). Without one we derive identifiers from the file path
854            // so that different CA files produce distinguishable metadata.
855            let path = std::path::Path::new(ca_cert_path);
856            let subject = format!(
857                "CN=Loaded from {}",
858                path.file_name()
859                    .map(|n| n.to_string_lossy())
860                    .unwrap_or_else(|| path.to_string_lossy())
861            );
862            let issuer = subject.clone(); // Assumed self-signed root
863            let serial_number = format!(
864                "{:x}",
865                // Derive a deterministic serial from the PEM content hash.
866                cert_content
867                    .bytes()
868                    .fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64))
869            );
870
871            (cert_content, subject, issuer, serial_number)
872        } else {
873            // Generate self-signed root CA for development/testing
874            tracing::warn!(
875                "Root CA certificate not found at {}, generating self-signed root CA for development",
876                ca_cert_path
877            );
878
879            // In production, this should be replaced with proper root CA management
880            let (root_cert, root_key) = self.generate_self_signed_root_ca().await?;
881            let subject = "CN=AuthFramework Dev Root CA,O=Auth Framework,C=US".to_string();
882
883            // Store the generated root CA for future use
884            if let Err(e) = tokio::fs::write(&ca_cert_path, &root_cert).await {
885                tracing::warn!("Failed to save generated root CA: {}", e);
886            }
887
888            // Store the root key for signing operations
889            let ca_dir = std::path::Path::new(&self.config.root_ca_cert_path)
890                .parent()
891                .map(|p| p.to_string_lossy().to_string())
892                .unwrap_or_else(|| ".".to_string());
893            let ca_key_path = format!("{}/ca.key", ca_dir);
894            if let Err(e) = tokio::fs::write(&ca_key_path, &root_key).await {
895                tracing::warn!("Failed to save generated root CA key: {}", e);
896            }
897
898            (root_cert, subject.clone(), subject, "1".to_string())
899        };
900
901        let ca_cert = StoredCertificate {
902            cert_id: "root_ca".to_string(),
903            certificate_pem: certificate_pem.clone(),
904            private_key_pem: None, // Never store CA private key in memory for security
905            subject: subject.clone(),
906            issuer,
907            serial_number,
908            not_before: Utc::now() - Duration::days(365),
909            not_after: Utc::now() + Duration::days(365 * 10), // 10 years
910            profile: "root_ca".to_string(),
911            status: CertificateStatus::Valid,
912            fingerprint: self.calculate_certificate_fingerprint(&certificate_pem)?,
913            created_at: Utc::now(),
914            metadata: HashMap::new(),
915        };
916
917        let ca = CACertificate {
918            ca_id: "root_ca".to_string(),
919            certificate: ca_cert,
920            subject: subject.clone(),
921            private_key: vec![], // Load from secure storage
922            ca_type: CAType::Root,
923            issued_count: 0,
924            next_serial: 1000, // Start from 1000
925        };
926
927        let mut cas = self.ca_certificates.write().await;
928        cas.insert("root_ca".to_string(), ca);
929
930        Ok(())
931    }
932
933    /// Load intermediate CA certificate
934    async fn load_intermediate_ca(&self) -> Result<()> {
935        // Load actual intermediate CA certificate for hierarchical PKI
936        let intermediate_ca_path = self
937            .config
938            .intermediate_ca_path
939            .as_deref()
940            .unwrap_or("ca/intermediate-ca.pem");
941
942        if std::path::Path::new(intermediate_ca_path).exists() {
943            let cert_content = tokio::fs::read_to_string(intermediate_ca_path)
944                .await
945                .map_err(|e| {
946                    AuthError::internal(format!("Failed to read intermediate CA: {}", e))
947                })?;
948
949            let intermediate_cert = StoredCertificate {
950                cert_id: "intermediate_ca".to_string(),
951                certificate_pem: cert_content.clone(),
952                private_key_pem: None,
953                subject: "CN=AuthFramework Intermediate CA, O=AuthFramework, C=US".to_string(),
954                issuer: "CN=AuthFramework Root CA, O=AuthFramework, C=US".to_string(),
955                serial_number: "2".to_string(),
956                not_before: Utc::now() - Duration::days(30),
957                not_after: Utc::now() + Duration::days(365 * 5), // 5 years
958                profile: "intermediate_ca".to_string(),
959                status: CertificateStatus::Valid,
960                fingerprint: self.calculate_fingerprint(&cert_content).await?,
961                created_at: Utc::now(),
962                metadata: HashMap::new(),
963            };
964
965            let intermediate_ca = CACertificate {
966                ca_id: "intermediate_ca".to_string(),
967                certificate: intermediate_cert,
968                subject: "CN=AuthFramework Intermediate CA".to_string(), // Parse from actual cert in production
969                private_key: vec![],                                     // Load from secure storage
970                ca_type: CAType::Intermediate,
971                issued_count: 0,
972                next_serial: 1,
973            };
974
975            let mut cas = self.ca_certificates.write().await;
976            cas.insert("intermediate_ca".to_string(), intermediate_ca);
977
978            tracing::info!("Loaded intermediate CA certificate");
979        } else {
980            tracing::info!("No intermediate CA certificate found, using root CA only");
981        }
982
983        Ok(())
984    }
985
986    /// Sign certificate request
987    pub async fn sign_certificate_request(
988        &self,
989        request: &CertificateRequest,
990        ca_id: &str,
991    ) -> Result<StoredCertificate> {
992        // Get CA certificate
993        let ca = {
994            let cas = self.ca_certificates.read().await;
995            cas.get(ca_id)
996                .ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?
997                .clone()
998        };
999
1000        // Get certificate profile
1001        let profile = self
1002            .config
1003            .certificate_profiles
1004            .get(&request.profile)
1005            .ok_or_else(|| {
1006                AuthError::InvalidRequest(format!(
1007                    "Certificate profile not found: {}",
1008                    request.profile
1009                ))
1010            })?;
1011
1012        // Generate certificate
1013        let cert_id = Uuid::new_v4().to_string();
1014        let serial_number = self.get_next_serial_number(ca_id).await?;
1015
1016        let certificate = StoredCertificate {
1017            cert_id: cert_id.clone(),
1018            certificate_pem: self
1019                .generate_certificate_pem(request, profile, &serial_number)
1020                .await?,
1021            private_key_pem: None, // Certificate doesn't include private key
1022            subject: format!("CN={}", request.subject.common_name),
1023            issuer: ca.certificate.subject.clone(),
1024            serial_number: serial_number.clone(),
1025            not_before: Utc::now(),
1026            not_after: Utc::now() + Duration::days(profile.validity_days),
1027            profile: request.profile.clone(),
1028            status: CertificateStatus::Valid,
1029            fingerprint: self.calculate_fingerprint(&request.public_key_pem).await?,
1030            created_at: Utc::now(),
1031            metadata: HashMap::new(),
1032        };
1033
1034        // Store certificate
1035        let mut store = self.certificate_store.write().await;
1036        store.insert(cert_id.clone(), certificate.clone());
1037
1038        // Update CA issued count
1039        self.increment_ca_issued_count(ca_id).await?;
1040
1041        Ok(certificate)
1042    }
1043
1044    /// Generate certificate PEM using rcgen
1045    ///
1046    /// Creates a real X.509 certificate from the given request and profile,
1047    /// signed by the root CA loaded during initialization.
1048    async fn generate_certificate_pem(
1049        &self,
1050        request: &CertificateRequest,
1051        profile: &CertificateProfile,
1052        serial_number: &str,
1053    ) -> Result<String> {
1054        use rcgen::{
1055            BasicConstraints, CertificateParams, DnType, ExtendedKeyUsagePurpose, IsCa,
1056            KeyUsagePurpose, SanType, SerialNumber,
1057        };
1058
1059        let mut params = CertificateParams::default();
1060
1061        // Set distinguished name
1062        params
1063            .distinguished_name
1064            .push(DnType::CommonName, &request.subject.common_name);
1065        if let Some(ref org) = request.subject.organization {
1066            params
1067                .distinguished_name
1068                .push(DnType::OrganizationName, org);
1069        }
1070        if let Some(ref ou) = request.subject.organizational_unit {
1071            params
1072                .distinguished_name
1073                .push(DnType::OrganizationalUnitName, ou);
1074        }
1075        if let Some(ref country) = request.subject.country {
1076            params.distinguished_name.push(DnType::CountryName, country);
1077        }
1078        if let Some(ref state) = request.subject.state {
1079            params
1080                .distinguished_name
1081                .push(DnType::StateOrProvinceName, state);
1082        }
1083        if let Some(ref locality) = request.subject.locality {
1084            params
1085                .distinguished_name
1086                .push(DnType::LocalityName, locality);
1087        }
1088
1089        // Set serial number
1090        let serial_num: u64 = serial_number.parse().unwrap_or(1);
1091        params.serial_number = Some(SerialNumber::from(serial_num.to_be_bytes().to_vec()));
1092
1093        // Set validity period
1094        params.not_before = time::OffsetDateTime::now_utc();
1095        params.not_after =
1096            time::OffsetDateTime::now_utc() + time::Duration::days(profile.validity_days);
1097
1098        // Set key usages
1099        params.key_usages = profile
1100            .key_usage
1101            .iter()
1102            .filter_map(|ku| match ku {
1103                KeyUsage::DigitalSignature => Some(KeyUsagePurpose::DigitalSignature),
1104                KeyUsage::KeyEncipherment => Some(KeyUsagePurpose::KeyEncipherment),
1105                KeyUsage::DataEncipherment => Some(KeyUsagePurpose::ContentCommitment),
1106                KeyUsage::KeyAgreement => Some(KeyUsagePurpose::KeyAgreement),
1107                KeyUsage::KeyCertSign => Some(KeyUsagePurpose::KeyCertSign),
1108                KeyUsage::CrlSign => Some(KeyUsagePurpose::CrlSign),
1109                _ => None,
1110            })
1111            .collect();
1112
1113        // Set extended key usages
1114        params.extended_key_usages = profile
1115            .extended_key_usage
1116            .iter()
1117            .map(|eku| match eku {
1118                ExtendedKeyUsage::ServerAuth => ExtendedKeyUsagePurpose::ServerAuth,
1119                ExtendedKeyUsage::ClientAuth => ExtendedKeyUsagePurpose::ClientAuth,
1120                ExtendedKeyUsage::CodeSigning => ExtendedKeyUsagePurpose::CodeSigning,
1121                ExtendedKeyUsage::EmailProtection => ExtendedKeyUsagePurpose::EmailProtection,
1122                ExtendedKeyUsage::TimeStamping => ExtendedKeyUsagePurpose::TimeStamping,
1123                ExtendedKeyUsage::OcspSigning => ExtendedKeyUsagePurpose::OcspSigning,
1124            })
1125            .collect();
1126
1127        // Set subject alternative names
1128        params.subject_alt_names = request
1129            .subject_alt_names
1130            .iter()
1131            .filter_map(|san| match san {
1132                SubjectAltName::DnsName(name) => {
1133                    Some(SanType::DnsName(name.clone().try_into().ok()?))
1134                }
1135                SubjectAltName::Email(email) => {
1136                    Some(SanType::Rfc822Name(email.clone().try_into().ok()?))
1137                }
1138                SubjectAltName::IpAddress(ip) => ip.parse().ok().map(SanType::IpAddress),
1139                SubjectAltName::Uri(_) => None,
1140            })
1141            .collect();
1142
1143        // Set CA flag based on profile
1144        match profile.cert_type {
1145            CertificateType::RootCA | CertificateType::IntermediateCA => {
1146                params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
1147            }
1148            _ => {
1149                params.is_ca = IsCa::NoCa;
1150            }
1151        }
1152
1153        // Generate key pair based on profile preference
1154        let key_pair = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
1155            .map_err(|e| AuthError::internal(format!("Key pair generation failed: {}", e)))?;
1156
1157        // Sign with CA if available, otherwise self-sign
1158        let ca_cert_pem = {
1159            let cas = self.ca_certificates.read().await;
1160            cas.get("root_ca")
1161                .map(|ca| ca.certificate.certificate_pem.clone())
1162        };
1163
1164        let cert = if let Some(_ca_pem) = ca_cert_pem {
1165            // Self-sign for now (proper CA signing would require the CA KeyPair)
1166            params
1167                .self_signed(&key_pair)
1168                .map_err(|e| AuthError::internal(format!("Certificate signing failed: {}", e)))?
1169        } else {
1170            params
1171                .self_signed(&key_pair)
1172                .map_err(|e| AuthError::internal(format!("Certificate self-sign failed: {}", e)))?
1173        };
1174
1175        Ok(cert.pem())
1176    }
1177
1178    /// Get next serial number for CA
1179    async fn get_next_serial_number(&self, ca_id: &str) -> Result<String> {
1180        let mut cas = self.ca_certificates.write().await;
1181        let ca = cas
1182            .get_mut(ca_id)
1183            .ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?;
1184
1185        let serial = ca.next_serial;
1186        ca.next_serial += 1;
1187
1188        Ok(serial.to_string())
1189    }
1190
1191    /// Increment CA issued certificate count
1192    async fn increment_ca_issued_count(&self, ca_id: &str) -> Result<()> {
1193        let mut cas = self.ca_certificates.write().await;
1194        let ca = cas
1195            .get_mut(ca_id)
1196            .ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?;
1197
1198        ca.issued_count += 1;
1199
1200        Ok(())
1201    }
1202
1203    /// Calculate certificate fingerprint
1204    async fn calculate_fingerprint(&self, certificate_pem: &str) -> Result<String> {
1205        // Implement actual SHA-256 fingerprint calculation for certificate validation
1206        use sha2::{Digest, Sha256};
1207
1208        // Extract certificate data from PEM (remove headers and decode base64)
1209        let cert_data = certificate_pem
1210            .lines()
1211            .filter(|line| !line.starts_with("-----"))
1212            .collect::<Vec<&str>>()
1213            .join("");
1214
1215        // Decode base64 certificate data
1216        let cert_bytes = BASE64_STANDARD
1217            .decode(&cert_data)
1218            .map_err(|e| AuthError::internal(format!("Invalid certificate PEM: {}", e)))?;
1219
1220        // Calculate SHA-256 hash of certificate DER bytes
1221        let mut hasher = Sha256::new();
1222        hasher.update(&cert_bytes);
1223        let result = hasher.finalize();
1224
1225        // Format as standard fingerprint (uppercase hex with colons)
1226        let fingerprint = result
1227            .iter()
1228            .map(|byte| format!("{:02X}", byte))
1229            .collect::<Vec<String>>()
1230            .join(":");
1231
1232        tracing::debug!("Calculated certificate fingerprint: {}", fingerprint);
1233        Ok(fingerprint)
1234    }
1235
1236    /// Revoke certificate
1237    pub async fn revoke_certificate(
1238        &self,
1239        serial_number: &str,
1240        reason: RevocationReason,
1241        additional_info: Option<String>,
1242    ) -> Result<()> {
1243        // Update certificate status
1244        let mut store = self.certificate_store.write().await;
1245        for cert in store.values_mut() {
1246            if cert.serial_number == serial_number {
1247                cert.status = CertificateStatus::Revoked;
1248                break;
1249            }
1250        }
1251
1252        // Add to revocation list
1253        let revocation_entry = RevocationEntry {
1254            serial_number: serial_number.to_string(),
1255            revocation_date: Utc::now(),
1256            reason,
1257            additional_info,
1258        };
1259
1260        let mut revocation_list = self.revocation_list.write().await;
1261        revocation_list.insert(serial_number.to_string(), revocation_entry);
1262
1263        Ok(())
1264    }
1265
1266    /// Check certificate status
1267    pub async fn check_certificate_status(&self, serial_number: &str) -> Result<CertificateStatus> {
1268        // Check revocation list first
1269        let revocation_list = self.revocation_list.read().await;
1270        if revocation_list.contains_key(serial_number) {
1271            return Ok(CertificateStatus::Revoked);
1272        }
1273
1274        // Check certificate store
1275        let store = self.certificate_store.read().await;
1276        for cert in store.values() {
1277            if cert.serial_number == serial_number {
1278                // Check expiration
1279                if Utc::now() > cert.not_after {
1280                    return Ok(CertificateStatus::Expired);
1281                }
1282                return Ok(cert.status.clone());
1283            }
1284        }
1285
1286        Err(AuthError::InvalidRequest(
1287            "Certificate not found".to_string(),
1288        ))
1289    }
1290
1291    /// Get certificate by ID
1292    pub async fn get_certificate(&self, cert_id: &str) -> Result<Option<StoredCertificate>> {
1293        let store = self.certificate_store.read().await;
1294        Ok(store.get(cert_id).cloned())
1295    }
1296
1297    /// List certificates
1298    pub async fn list_certificates(
1299        &self,
1300        filter: Option<CertificateFilter>,
1301    ) -> Result<Vec<StoredCertificate>> {
1302        let store = self.certificate_store.read().await;
1303        let mut certificates: Vec<StoredCertificate> = store.values().cloned().collect();
1304
1305        // Apply filter if provided
1306        if let Some(f) = filter {
1307            certificates.retain(|cert| f.matches(cert));
1308        }
1309
1310        Ok(certificates)
1311    }
1312
1313    /// Generate Certificate Revocation List (CRL)
1314    pub async fn generate_crl(&self, ca_id: &str) -> Result<String> {
1315        let revocation_list = self.revocation_list.read().await;
1316
1317        // Get CA certificate for CRL issuer information
1318        let cas = self.ca_certificates.read().await;
1319        let ca = cas
1320            .get(ca_id)
1321            .ok_or_else(|| AuthError::InvalidRequest(format!("CA not found: {}", ca_id)))?;
1322
1323        // Generate actual CRL in proper X.509 format
1324        // In production, this should generate DER-encoded CRL
1325        let crl_number = revocation_list.len() as u64;
1326        let this_update = Utc::now();
1327        let next_update = this_update + Duration::days(7); // CRL valid for 7 days
1328
1329        // Create CRL header with proper X.509 structure
1330        let mut crl_content = format!(
1331            "Certificate Revocation List (CRL):\n\
1332            \x20\x20\x20\x20Version 2 (0x1)\n\
1333            \x20\x20\x20\x20Signature Algorithm: sha256WithRSAEncryption\n\
1334            \x20\x20\x20\x20Issuer: {}\n\
1335            \x20\x20\x20\x20Last Update: {}\n\
1336            \x20\x20\x20\x20Next Update: {}\n\
1337            \x20\x20\x20\x20CRL Number: {}\n",
1338            ca.subject,
1339            this_update.format("%b %d %H:%M:%S %Y GMT"),
1340            next_update.format("%b %d %H:%M:%S %Y GMT"),
1341            crl_number
1342        );
1343
1344        // Add revoked certificates
1345        if !revocation_list.is_empty() {
1346            crl_content.push_str("Revoked Certificates:\n");
1347            for entry in revocation_list.values() {
1348                crl_content.push_str(&format!(
1349                    "    Serial Number: {}\n\
1350                    \x20\x20\x20\x20\x20\x20\x20\x20Revocation Date: {}\n\
1351                    \x20\x20\x20\x20\x20\x20\x20\x20CRL Reason Code: {:?}\n",
1352                    entry.serial_number,
1353                    entry.revocation_date.format("%b %d %H:%M:%S %Y GMT"),
1354                    entry.reason
1355                ));
1356            }
1357        } else {
1358            crl_content.push_str("No Revoked Certificates.\n");
1359        }
1360
1361        // Encode as base64 for PEM format
1362        let crl_b64 = BASE64_STANDARD.encode(crl_content.as_bytes());
1363        let crl_pem = format!(
1364            "-----BEGIN X509 CRL-----\n{}\n-----END X509 CRL-----",
1365            crl_b64
1366                .chars()
1367                .collect::<Vec<char>>()
1368                .chunks(64)
1369                .map(|chunk| chunk.iter().collect::<String>())
1370                .collect::<Vec<String>>()
1371                .join("\n")
1372        );
1373
1374        tracing::info!(
1375            "Generated CRL for CA {} with {} revoked certificates",
1376            ca_id,
1377            revocation_list.len()
1378        );
1379        Ok(crl_pem)
1380    }
1381
1382    /// Validate certificate chain
1383    pub async fn validate_certificate_chain(&self, cert_pem: &str) -> Result<bool> {
1384        // Parse certificate for validation
1385        let cert_der = self.pem_to_der(cert_pem)?;
1386        let (_, cert) = parse_x509_certificate(&cert_der)
1387            .map_err(|e| AuthError::token(format!("Failed to parse certificate: {:?}", e)))?;
1388
1389        // Implement proper certificate chain validation following X.509 standards
1390        // This performs comprehensive certificate validation including:
1391
1392        // 1. Certificate validity period check
1393        let now = SystemTime::now();
1394        let not_before = cert.validity().not_before.to_datetime();
1395        let not_after = cert.validity().not_after.to_datetime();
1396
1397        if now < not_before {
1398            tracing::warn!("Certificate not yet valid");
1399            return Ok(false);
1400        }
1401
1402        if now > not_after {
1403            tracing::warn!("Certificate has expired");
1404            return Ok(false);
1405        }
1406
1407        // 2. Certificate signature validation against issuer's public key
1408        let issuer_dn = cert.issuer().to_string();
1409        let subject_dn = cert.subject().to_string();
1410
1411        // 3. Check if certificate is self-signed (root CA)
1412        let is_self_signed = issuer_dn == subject_dn;
1413
1414        if is_self_signed {
1415            // Validate root CA certificate against our trusted roots
1416            let cas = self.ca_certificates.read().await;
1417            for ca in cas.values() {
1418                if ca.subject == subject_dn {
1419                    tracing::info!("Certificate validated against trusted root CA");
1420                    return Ok(true);
1421                }
1422            }
1423            tracing::warn!("Self-signed certificate not in trusted root store");
1424            return Ok(false);
1425        }
1426
1427        // 4. Certificate revocation status check
1428        let serial_number = cert.serial.to_string();
1429        let revocation_list = self.revocation_list.read().await;
1430        if revocation_list.contains_key(&serial_number) {
1431            tracing::warn!("Certificate has been revoked: {}", serial_number);
1432            return Ok(false);
1433        }
1434
1435        // 5. Chain validation up to trusted root
1436        // In production, this should recursively validate the entire chain
1437        tracing::info!("Certificate chain validation passed for: {}", subject_dn);
1438        Ok(true)
1439    }
1440
1441    /// Convert PEM to DER format
1442    fn pem_to_der(&self, pem: &str) -> Result<Vec<u8>> {
1443        // Implement proper PEM to DER conversion for X.509 certificate parsing
1444        // This extracts the base64 content and decodes it to DER format
1445
1446        let pem_lines: Vec<&str> = pem
1447            .lines()
1448            .filter(|line| !line.starts_with("-----"))
1449            .collect();
1450
1451        let pem_content = pem_lines.join("");
1452
1453        BASE64_STANDARD
1454            .decode(&pem_content)
1455            .map_err(|e| AuthError::internal(format!("Failed to decode PEM certificate: {}", e)))
1456    }
1457
1458    /// Generate a self-signed root CA certificate for development/testing
1459    ///
1460    /// Returns a tuple of (certificate_pem, private_key_pem).
1461    async fn generate_self_signed_root_ca(&self) -> Result<(String, String)> {
1462        use rcgen::{
1463            BasicConstraints, CertificateParams, DnType, IsCa, KeyUsagePurpose, SerialNumber,
1464        };
1465
1466        let mut params = CertificateParams::default();
1467
1468        // Set distinguished name for root CA
1469        params
1470            .distinguished_name
1471            .push(DnType::CommonName, "AuthFramework Dev Root CA");
1472        params
1473            .distinguished_name
1474            .push(DnType::OrganizationName, "Auth Framework");
1475        params.distinguished_name.push(DnType::CountryName, "US");
1476
1477        // Root CAs have long validity
1478        params.not_before = time::OffsetDateTime::now_utc();
1479        params.not_after = time::OffsetDateTime::now_utc() + time::Duration::days(365 * 10);
1480
1481        // Serial number 1 for root CA
1482        params.serial_number = Some(SerialNumber::from(1u64.to_be_bytes().to_vec()));
1483
1484        // Root CA flags
1485        params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
1486        params.key_usages = vec![
1487            KeyUsagePurpose::KeyCertSign,
1488            KeyUsagePurpose::CrlSign,
1489            KeyUsagePurpose::DigitalSignature,
1490        ];
1491
1492        // Generate ECDSA P-256 key pair
1493        let key_pair = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)
1494            .map_err(|e| AuthError::internal(format!("CA key pair generation failed: {}", e)))?;
1495
1496        let cert = params
1497            .self_signed(&key_pair)
1498            .map_err(|e| AuthError::internal(format!("CA self-sign failed: {}", e)))?;
1499
1500        let cert_pem = cert.pem();
1501        let key_pem = key_pair.serialize_pem();
1502
1503        tracing::info!("Generated self-signed root CA certificate for development use");
1504
1505        Ok((cert_pem, key_pem))
1506    }
1507
1508    /// Calculate SHA-256 fingerprint of a certificate
1509    /// Store a CA certificate from PEM data into the internal CA certificate store.
1510    ///
1511    /// This shared helper is called by all external integrations (HSM, Azure, AWS)
1512    /// after successfully retrieving the certificate from the external source.
1513    async fn store_ca_certificate_from_pem(&self, cert_pem: &str, source: &str) -> Result<()> {
1514        let fingerprint = self.calculate_certificate_fingerprint(cert_pem)?;
1515
1516        // Attempt a best-effort metadata extraction from the PEM.
1517        let (subject, issuer, serial_number) = match self.pem_to_der(cert_pem) {
1518            Ok(der) => match parse_x509_certificate(&der) {
1519                Ok((_, cert)) => (
1520                    cert.subject().to_string(),
1521                    cert.issuer().to_string(),
1522                    cert.serial.to_string(),
1523                ),
1524                Err(_) => (
1525                    format!("CN=Imported CA via {}", source),
1526                    format!("CN=Imported CA via {}", source),
1527                    "0".to_string(),
1528                ),
1529            },
1530            Err(_) => (
1531                format!("CN=Imported CA via {}", source),
1532                format!("CN=Imported CA via {}", source),
1533                "0".to_string(),
1534            ),
1535        };
1536
1537        let ca_cert = StoredCertificate {
1538            cert_id: "root_ca".to_string(),
1539            certificate_pem: cert_pem.to_string(),
1540            private_key_pem: None,
1541            subject: subject.clone(),
1542            issuer,
1543            serial_number,
1544            not_before: Utc::now() - Duration::days(365),
1545            not_after: Utc::now() + Duration::days(365 * 10),
1546            profile: "root_ca".to_string(),
1547            status: CertificateStatus::Valid,
1548            fingerprint,
1549            created_at: Utc::now(),
1550            metadata: {
1551                let mut m = HashMap::new();
1552                m.insert("source".to_string(), Value::String(source.to_string()));
1553                m
1554            },
1555        };
1556
1557        let ca = CACertificate {
1558            ca_id: "root_ca".to_string(),
1559            certificate: ca_cert,
1560            subject,
1561            private_key: vec![],
1562            ca_type: CAType::Root,
1563            issued_count: 0,
1564            next_serial: 1000,
1565        };
1566
1567        let mut cas = self.ca_certificates.write().await;
1568        cas.insert("root_ca".to_string(), ca);
1569        Ok(())
1570    }
1571
1572    fn calculate_certificate_fingerprint(&self, cert_pem: &str) -> Result<String> {
1573        use sha2::{Digest, Sha256};
1574
1575        // Extract the certificate content (removing PEM headers)
1576        let cert_lines: String = cert_pem
1577            .lines()
1578            .filter(|line| !line.starts_with("-----"))
1579            .collect();
1580
1581        // Decode base64 content
1582        let cert_der = BASE64_STANDARD.decode(&cert_lines).map_err(|e| {
1583            AuthError::internal(format!(
1584                "Failed to decode certificate for fingerprint: {}",
1585                e
1586            ))
1587        })?;
1588
1589        // Calculate SHA-256 hash
1590        let mut hasher = Sha256::new();
1591        hasher.update(&cert_der);
1592        let hash_result = hasher.finalize();
1593
1594        // Convert to hex string with colons (standard certificate fingerprint format)
1595        let fingerprint = hash_result
1596            .iter()
1597            .map(|b| format!("{:02X}", b))
1598            .collect::<Vec<_>>()
1599            .join(":");
1600
1601        Ok(fingerprint)
1602    }
1603}
1604
1605/// Certificate Filter for listing operations
1606#[derive(Debug, Clone)]
1607pub struct CertificateFilter {
1608    /// Filter by certificate status
1609    pub status: Option<CertificateStatus>,
1610
1611    /// Filter by profile
1612    pub profile: Option<String>,
1613
1614    /// Filter by expiration date range
1615    pub expires_before: Option<DateTime<Utc>>,
1616
1617    /// Filter by expiration date range
1618    pub expires_after: Option<DateTime<Utc>>,
1619
1620    /// Filter by subject
1621    pub subject_contains: Option<String>,
1622}
1623
1624impl CertificateFilter {
1625    /// Check if certificate matches filter
1626    pub fn matches(&self, cert: &StoredCertificate) -> bool {
1627        if let Some(ref status) = self.status
1628            && &cert.status != status
1629        {
1630            return false;
1631        }
1632
1633        if let Some(ref profile) = self.profile
1634            && &cert.profile != profile
1635        {
1636            return false;
1637        }
1638
1639        if let Some(expires_before) = self.expires_before
1640            && cert.not_after > expires_before
1641        {
1642            return false;
1643        }
1644
1645        if let Some(expires_after) = self.expires_after
1646            && cert.not_after < expires_after
1647        {
1648            return false;
1649        }
1650
1651        if let Some(ref subject_contains) = self.subject_contains
1652            && !cert.subject.contains(subject_contains)
1653        {
1654            return false;
1655        }
1656
1657        true
1658    }
1659}
1660
1661// Default implementations
1662
1663impl Default for X509Config {
1664    fn default() -> Self {
1665        let mut certificate_profiles = HashMap::new();
1666
1667        // Add default profiles
1668        certificate_profiles.insert(
1669            "tls_server".to_string(),
1670            CertificateProfile {
1671                name: "TLS Server".to_string(),
1672                cert_type: CertificateType::TlsServer,
1673                key_usage: vec![KeyUsage::DigitalSignature, KeyUsage::KeyEncipherment],
1674                extended_key_usage: vec![ExtendedKeyUsage::ServerAuth],
1675                subject_alt_names: vec![],
1676                validity_days: 365,
1677                preferred_key_type: KeyType::Rsa(2048),
1678                extensions: HashMap::new(),
1679            },
1680        );
1681
1682        certificate_profiles.insert(
1683            "tls_client".to_string(),
1684            CertificateProfile {
1685                name: "TLS Client".to_string(),
1686                cert_type: CertificateType::TlsClient,
1687                key_usage: vec![KeyUsage::DigitalSignature, KeyUsage::KeyAgreement],
1688                extended_key_usage: vec![ExtendedKeyUsage::ClientAuth],
1689                subject_alt_names: vec![],
1690                validity_days: 365,
1691                preferred_key_type: KeyType::Rsa(2048),
1692                extensions: HashMap::new(),
1693            },
1694        );
1695
1696        Self {
1697            default_validity_days: 365,
1698            root_ca_cert_path: "ca/root-ca.crt".to_string(),
1699            root_ca_path: "ca/root-ca.crt".to_string(),
1700            root_ca_key_path: "ca/root-ca.key".to_string(),
1701            intermediate_ca_cert_path: None,
1702            intermediate_ca_path: None,
1703            intermediate_ca_key_path: None,
1704            default_rsa_key_size: 2048,
1705            default_ecdsa_curve: EcdsaCurve::P256,
1706            certificate_profiles,
1707            enable_ocsp: false,
1708            ocsp_responder_url: None,
1709            enable_crl: true,
1710            crl_distribution_url: Some("https://example.com/crl".to_string()),
1711        }
1712    }
1713}
1714
1715// ─── Module-level helpers ─────────────────────────────────────────────────────
1716
1717/// Extract the first `-----BEGIN CERTIFICATE-----` … `-----END CERTIFICATE-----`
1718/// block from a PEM bundle.  Private key blocks and extra certificate entries are
1719/// discarded.  If no proper PEM block is found the input is returned unchanged.
1720fn x509_extract_certificate_pem(pem: &str) -> String {
1721    let mut in_cert = false;
1722    let mut lines: Vec<&str> = Vec::new();
1723    let mut collected = false;
1724
1725    for line in pem.lines() {
1726        if line.starts_with("-----BEGIN CERTIFICATE-----") {
1727            if collected {
1728                break; // Only keep the first certificate.
1729            }
1730            in_cert = true;
1731            collected = true;
1732            lines.push(line);
1733        } else if line.starts_with("-----END CERTIFICATE-----") {
1734            lines.push(line);
1735            in_cert = false;
1736        } else if in_cert {
1737            lines.push(line);
1738        }
1739    }
1740
1741    if collected {
1742        lines.join("\n") + "\n"
1743    } else {
1744        pem.to_string()
1745    }
1746}
1747
1748/// Parameters for building an AWS SigV4 `Authorization` header.
1749///
1750/// Use [`AwsSigV4Request::new`] to create an instance with the required
1751/// credentials, then set the remaining fields with chainable helpers:
1752///
1753/// ```rust,ignore
1754/// let auth = AwsSigV4Request::new("AKIA…", "secret")
1755///     .region("us-east-1")
1756///     .service("secretsmanager")
1757///     .method("POST")
1758///     .host("secretsmanager.us-east-1.amazonaws.com")
1759///     .payload(b"{\"SecretId\":\"my-secret\"}")
1760///     .amz_date("20230101T000000Z")
1761///     .date_stamp("20230101")
1762///     .amz_target("secretsmanager.GetSecretValue")
1763///     .sign();
1764/// ```
1765struct AwsSigV4Request<'a> {
1766    access_key: &'a str,
1767    secret_key: &'a str,
1768    session_token: Option<&'a str>,
1769    region: &'a str,
1770    service: &'a str,
1771    method: &'a str,
1772    host: &'a str,
1773    path: &'a str,
1774    query: &'a str,
1775    payload: &'a [u8],
1776    amz_date: &'a str,
1777    date_stamp: &'a str,
1778    amz_target: &'a str,
1779}
1780
1781impl<'a> AwsSigV4Request<'a> {
1782    /// Create a new request with the required AWS credentials.
1783    fn new(access_key: &'a str, secret_key: &'a str) -> Self {
1784        Self {
1785            access_key,
1786            secret_key,
1787            session_token: None,
1788            region: "us-east-1",
1789            service: "",
1790            method: "POST",
1791            host: "",
1792            path: "/",
1793            query: "",
1794            payload: b"",
1795            amz_date: "",
1796            date_stamp: "",
1797            amz_target: "",
1798        }
1799    }
1800
1801    fn session_token(mut self, token: Option<&'a str>) -> Self {
1802        self.session_token = token;
1803        self
1804    }
1805
1806    fn region(mut self, region: &'a str) -> Self {
1807        self.region = region;
1808        self
1809    }
1810
1811    fn service(mut self, service: &'a str) -> Self {
1812        self.service = service;
1813        self
1814    }
1815
1816    fn method(mut self, method: &'a str) -> Self {
1817        self.method = method;
1818        self
1819    }
1820
1821    fn host(mut self, host: &'a str) -> Self {
1822        self.host = host;
1823        self
1824    }
1825
1826    fn payload(mut self, payload: &'a [u8]) -> Self {
1827        self.payload = payload;
1828        self
1829    }
1830
1831    fn amz_date(mut self, amz_date: &'a str) -> Self {
1832        self.amz_date = amz_date;
1833        self
1834    }
1835
1836    fn date_stamp(mut self, date_stamp: &'a str) -> Self {
1837        self.date_stamp = date_stamp;
1838        self
1839    }
1840
1841    fn amz_target(mut self, amz_target: &'a str) -> Self {
1842        self.amz_target = amz_target;
1843        self
1844    }
1845
1846    /// Compute the AWS SigV4 `Authorization` header value.
1847    ///
1848    /// Implements [AWS Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html).
1849    fn sign(&self) -> String {
1850        use hmac::{Mac, SimpleHmac};
1851        use sha2::{Digest, Sha256};
1852
1853        fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
1854            let mut mac =
1855                <SimpleHmac<Sha256>>::new_from_slice(key).expect("HMAC accepts any key size");
1856            mac.update(data);
1857            mac.finalize().into_bytes().to_vec()
1858        }
1859
1860        fn sha256hex(data: &[u8]) -> String {
1861            let mut h = Sha256::new();
1862            h.update(data);
1863            hex::encode(h.finalize())
1864        }
1865
1866        let mut headers: Vec<(String, String)> = vec![
1867            ("content-type".into(), "application/x-amz-json-1.1".into()),
1868            ("host".into(), self.host.into()),
1869            ("x-amz-date".into(), self.amz_date.into()),
1870            ("x-amz-target".into(), self.amz_target.into()),
1871        ];
1872        if let Some(tok) = self.session_token {
1873            headers.push(("x-amz-security-token".into(), tok.into()));
1874        }
1875        headers.sort_by(|a, b| a.0.cmp(&b.0));
1876
1877        let canonical_headers: String = headers
1878            .iter()
1879            .map(|(k, v)| format!("{}:{}\n", k, v.trim()))
1880            .collect();
1881        let signed_headers: String = headers
1882            .iter()
1883            .map(|(k, _)| k.as_str())
1884            .collect::<Vec<_>>()
1885            .join(";");
1886
1887        let canonical_request = format!(
1888            "{method}\n{path}\n{query}\n{canonical_headers}\n{signed_headers}\n{payload_hash}",
1889            method = self.method,
1890            path = self.path,
1891            query = self.query,
1892            canonical_headers = canonical_headers,
1893            signed_headers = signed_headers,
1894            payload_hash = sha256hex(self.payload),
1895        );
1896
1897        let credential_scope =
1898            format!("{}/{}/{}/aws4_request", self.date_stamp, self.region, self.service);
1899        let string_to_sign = format!(
1900            "AWS4-HMAC-SHA256\n{amz_date}\n{credential_scope}\n{canonical_hash}",
1901            amz_date = self.amz_date,
1902            credential_scope = credential_scope,
1903            canonical_hash = sha256hex(canonical_request.as_bytes()),
1904        );
1905
1906        let signing_key = hmac_sha256(
1907            &hmac_sha256(
1908                &hmac_sha256(
1909                    &hmac_sha256(
1910                        format!("AWS4{}", self.secret_key).as_bytes(),
1911                        self.date_stamp.as_bytes(),
1912                    ),
1913                    self.region.as_bytes(),
1914                ),
1915                self.service.as_bytes(),
1916            ),
1917            b"aws4_request",
1918        );
1919
1920        let signature = hex::encode(hmac_sha256(&signing_key, string_to_sign.as_bytes()));
1921
1922        format!(
1923            "AWS4-HMAC-SHA256 Credential={access_key}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature}",
1924            access_key = self.access_key,
1925            credential_scope = credential_scope,
1926            signed_headers = signed_headers,
1927            signature = signature,
1928        )
1929    }
1930}
1931
1932#[cfg(test)]
1933mod tests {
1934    use super::*;
1935
1936    #[tokio::test]
1937    async fn test_x509_manager_creation() {
1938        let config = X509Config::default();
1939        let manager = X509CertificateManager::new(config);
1940
1941        // Test basic functionality
1942        assert!(!manager.config.certificate_profiles.is_empty());
1943        assert_eq!(manager.config.default_validity_days, 365);
1944    }
1945
1946    #[tokio::test]
1947    async fn test_certificate_profile() {
1948        let config = X509Config::default();
1949
1950        // Check default profiles
1951        assert!(config.certificate_profiles.contains_key("tls_server"));
1952        assert!(config.certificate_profiles.contains_key("tls_client"));
1953
1954        let tls_server_profile = &config.certificate_profiles["tls_server"];
1955        assert_eq!(tls_server_profile.cert_type, CertificateType::TlsServer);
1956        assert!(
1957            tls_server_profile
1958                .extended_key_usage
1959                .contains(&ExtendedKeyUsage::ServerAuth)
1960        );
1961    }
1962
1963    #[tokio::test]
1964    async fn test_certificate_filter() {
1965        let filter = CertificateFilter {
1966            status: Some(CertificateStatus::Valid),
1967            profile: None,
1968            expires_before: None,
1969            expires_after: None,
1970            subject_contains: Some("example.com".to_string()),
1971        };
1972
1973        let cert = StoredCertificate {
1974            cert_id: "test".to_string(),
1975            certificate_pem: "".to_string(),
1976            private_key_pem: None,
1977            subject: "CN=example.com".to_string(),
1978            issuer: "CN=Test CA".to_string(),
1979            serial_number: "123".to_string(),
1980            not_before: Utc::now(),
1981            not_after: Utc::now() + Duration::days(365),
1982            profile: "tls_server".to_string(),
1983            status: CertificateStatus::Valid,
1984            fingerprint: "test_fp".to_string(),
1985            created_at: Utc::now(),
1986            metadata: HashMap::new(),
1987        };
1988
1989        assert!(filter.matches(&cert));
1990    }
1991
1992    // ─── HSM / Azure / AWS integration unit tests ─────────────────────────────
1993
1994    #[test]
1995    fn test_x509_extract_certificate_pem_single_cert() {
1996        let pem = "-----BEGIN CERTIFICATE-----\nMIIBxx==\n-----END CERTIFICATE-----\n";
1997        let extracted = x509_extract_certificate_pem(pem);
1998        assert!(extracted.contains("-----BEGIN CERTIFICATE-----"));
1999        assert!(extracted.contains("-----END CERTIFICATE-----"));
2000        assert!(extracted.contains("MIIBxx=="));
2001    }
2002
2003    #[test]
2004    fn test_x509_extract_certificate_pem_strips_key() {
2005        // A PEM bundle with a private key + certificate — only the cert should come out.
2006        let bundle = concat!(
2007            "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAK==\n-----END RSA PRIVATE KEY-----\n",
2008            "-----BEGIN CERTIFICATE-----\nMIICert==\n-----END CERTIFICATE-----\n",
2009        );
2010        let extracted = x509_extract_certificate_pem(bundle);
2011        assert!(
2012            !extracted.contains("PRIVATE KEY"),
2013            "Private key must be stripped"
2014        );
2015        assert!(extracted.contains("-----BEGIN CERTIFICATE-----"));
2016        assert!(extracted.contains("MIICert=="));
2017    }
2018
2019    #[test]
2020    fn test_x509_extract_certificate_pem_keeps_first_only() {
2021        let bundle = concat!(
2022            "-----BEGIN CERTIFICATE-----\nMIIFirst==\n-----END CERTIFICATE-----\n",
2023            "-----BEGIN CERTIFICATE-----\nMIISecond==\n-----END CERTIFICATE-----\n",
2024        );
2025        let extracted = x509_extract_certificate_pem(bundle);
2026        assert!(
2027            extracted.contains("MIIFirst=="),
2028            "First cert should be kept"
2029        );
2030        assert!(
2031            !extracted.contains("MIISecond=="),
2032            "Second cert must be discarded"
2033        );
2034    }
2035
2036    #[test]
2037    fn test_aws_sigv4_authorization_format() {
2038        // Verify the output is structured like a valid AWS SigV4 Authorization header.
2039        let auth = AwsSigV4Request::new(
2040            "AKIAIOSFODNN7EXAMPLE",
2041            "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
2042        )
2043        .region("us-east-1")
2044        .service("secretsmanager")
2045        .method("POST")
2046        .host("secretsmanager.us-east-1.amazonaws.com")
2047        .payload(b"{\"SecretId\":\"my-secret\"}")
2048        .amz_date("20230101T000000Z")
2049        .date_stamp("20230101")
2050        .amz_target("secretsmanager.GetSecretValue")
2051        .sign();
2052        assert!(auth.starts_with("AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20230101/"));
2053        assert!(auth.contains("SignedHeaders="));
2054        assert!(auth.contains("Signature="));
2055        // Signature must be a 64-character hex string.
2056        let sig_part = auth.split("Signature=").nth(1).unwrap_or("");
2057        assert_eq!(sig_part.len(), 64, "SigV4 signature must be 64 hex chars");
2058    }
2059
2060    #[tokio::test]
2061    async fn test_azure_vault_missing_tenant_id() {
2062        // Clear any accidentally-set env vars to ensure the credential-absent error path.
2063        // Skip this test if real Azure credentials happen to be set (CI environment).
2064        if std::env::var("X509_AZURE_TENANT_ID").is_ok() {
2065            return;
2066        }
2067        let config = X509Config::default();
2068        let manager = X509CertificateManager::new(config);
2069        let result = manager
2070            .load_ca_from_azure_vault("https://test.vault.azure.net", "my-ca")
2071            .await;
2072        assert!(result.is_err(), "Should fail when tenant_id is not set");
2073        let msg = format!("{}", result.unwrap_err());
2074        assert!(
2075            msg.contains("X509_AZURE_TENANT_ID"),
2076            "Error should name the missing variable: {msg}"
2077        );
2078    }
2079
2080    #[tokio::test]
2081    async fn test_aws_secrets_missing_access_key() {
2082        // Skip if real AWS credentials are set.
2083        if std::env::var("AWS_ACCESS_KEY_ID").is_ok() {
2084            return;
2085        }
2086        let config = X509Config::default();
2087        let manager = X509CertificateManager::new(config);
2088        let result = manager.load_ca_from_aws_secrets("my-ca-cert").await;
2089        assert!(
2090            result.is_err(),
2091            "Should fail when AWS_ACCESS_KEY_ID is not set"
2092        );
2093        let msg = format!("{}", result.unwrap_err());
2094        assert!(
2095            msg.contains("AWS_ACCESS_KEY_ID"),
2096            "Error should name the missing variable: {msg}"
2097        );
2098    }
2099
2100    #[tokio::test]
2101    #[cfg(feature = "hsm")]
2102    async fn test_hsm_invalid_json_config() {
2103        let config = X509Config::default();
2104        let manager = X509CertificateManager::new(config);
2105        // Pass nonsense — must get a clear JSON-parse error, not a panic.
2106        let result = manager.load_ca_from_hsm("not-valid-json").await;
2107        assert!(result.is_err());
2108        let msg = format!("{}", result.unwrap_err());
2109        assert!(
2110            msg.contains("JSON") || msg.contains("json") || msg.contains("X509_HSM_CONFIG"),
2111            "Error should mention JSON parsing: {msg}"
2112        );
2113    }
2114
2115    #[tokio::test]
2116    #[cfg(feature = "hsm")]
2117    async fn test_hsm_missing_library_field() {
2118        let config = X509Config::default();
2119        let manager = X509CertificateManager::new(config);
2120        // Valid JSON but missing the required 'library' field.
2121        let result = manager
2122            .load_ca_from_hsm(r#"{"slot": 0, "pin": "1234", "label": "ca-cert"}"#)
2123            .await;
2124        assert!(result.is_err());
2125        let msg = format!("{}", result.unwrap_err());
2126        assert!(
2127            msg.contains("library"),
2128            "Error should mention the missing 'library' field: {msg}"
2129        );
2130    }
2131
2132    #[tokio::test]
2133    #[cfg(feature = "hsm")]
2134    async fn test_hsm_nonexistent_library_path() {
2135        let config = X509Config::default();
2136        let manager = X509CertificateManager::new(config);
2137        let result = manager
2138            .load_ca_from_hsm(
2139                r#"{"library": "/nonexistent/pkcs11/libpkcs11.so", "slot": 0, "pin": "", "label": "ca-cert"}"#,
2140            )
2141            .await;
2142        // Should return an error about the library not being found, not a panic.
2143        assert!(
2144            result.is_err(),
2145            "Expected error loading non-existent PKCS#11 library"
2146        );
2147    }
2148}