Skip to main content

forge/openssl/
formatter.rs

1use crate::error::ConversionError;
2use crate::openssl::ParsedPfx;
3use openssl::x509::X509;
4
5/// Formatter for converting certificates and keys to PEM format
6pub struct PemFormatter;
7
8impl PemFormatter {
9    /// Convert private key to PEM format
10    pub fn private_key_to_pem(parsed: &ParsedPfx) -> Result<Vec<u8>, ConversionError> {
11        let pem_data = parsed
12            .private_key
13            .private_key_to_pem_pkcs8()
14            .map_err(ConversionError::from)?;
15
16        // Validate the generated PEM
17        if !Self::validate_pem(&pem_data) {
18            return Err(ConversionError::InvalidFormat(
19                "Generated private key PEM is invalid".to_string(),
20            ));
21        }
22
23        Ok(pem_data)
24    }
25
26    /// Convert certificate to PEM format
27    pub fn certificate_to_pem(parsed: &ParsedPfx) -> Result<Vec<u8>, ConversionError> {
28        let pem_data = parsed.certificate.to_pem().map_err(ConversionError::from)?;
29
30        // Validate the generated PEM
31        if !Self::validate_pem(&pem_data) {
32            return Err(ConversionError::InvalidFormat(
33                "Generated certificate PEM is invalid".to_string(),
34            ));
35        }
36
37        Ok(pem_data)
38    }
39
40    /// Convert a single certificate to PEM format
41    pub fn cert_to_pem(cert: &X509) -> Result<Vec<u8>, ConversionError> {
42        let pem_data = cert.to_pem().map_err(ConversionError::from)?;
43
44        // Validate the generated PEM
45        if !Self::validate_pem(&pem_data) {
46            return Err(ConversionError::InvalidFormat(
47                "Generated certificate PEM is invalid".to_string(),
48            ));
49        }
50
51        Ok(pem_data)
52    }
53
54    /// Convert certificate chain to PEM format (all certificates concatenated)
55    pub fn chain_to_pem(parsed: &ParsedPfx) -> Result<Vec<u8>, ConversionError> {
56        let mut chain_pem = Self::certificate_to_pem(parsed)?;
57
58        for cert in &parsed.chain {
59            let cert_pem = Self::cert_to_pem(cert)?;
60            chain_pem.extend_from_slice(&cert_pem);
61        }
62
63        // Validate the complete chain PEM
64        if !Self::validate_pem(&chain_pem) {
65            return Err(ConversionError::InvalidFormat(
66                "Generated certificate chain PEM is invalid".to_string(),
67            ));
68        }
69
70        Ok(chain_pem)
71    }
72
73    /// Convert individual chain certificates to PEM format
74    pub fn chain_certs_to_pem(parsed: &ParsedPfx) -> Result<Vec<Vec<u8>>, ConversionError> {
75        parsed.chain.iter().map(Self::cert_to_pem).collect()
76    }
77
78    /// Create a combined PEM with private key and certificate(s)
79    pub fn combined_to_pem(
80        parsed: &ParsedPfx,
81        include_chain: bool,
82    ) -> Result<Vec<u8>, ConversionError> {
83        let mut combined = Self::private_key_to_pem(parsed)?;
84
85        if include_chain && parsed.has_chain() {
86            let chain_pem = Self::chain_to_pem(parsed)?;
87            combined.extend_from_slice(&chain_pem);
88        } else {
89            let cert_pem = Self::certificate_to_pem(parsed)?;
90            combined.extend_from_slice(&cert_pem);
91        }
92
93        // Validate the combined PEM
94        if !Self::validate_pem(&combined) {
95            return Err(ConversionError::InvalidFormat(
96                "Generated combined PEM is invalid".to_string(),
97            ));
98        }
99
100        Ok(combined)
101    }
102
103    /// Validate PEM format (basic check)
104    pub fn validate_pem(pem_data: &[u8]) -> bool {
105        let pem_str = match std::str::from_utf8(pem_data) {
106            Ok(s) => s,
107            Err(_) => return false,
108        };
109
110        // Check for PEM markers - should contain at least one BEGIN/END pair
111        let has_begin = pem_str.contains("-----BEGIN");
112        let has_end = pem_str.contains("-----END");
113
114        if !has_begin || !has_end {
115            return false;
116        }
117
118        // Count BEGIN and END markers - they should match
119        let begin_count = pem_str.matches("-----BEGIN").count();
120        let end_count = pem_str.matches("-----END").count();
121
122        begin_count == end_count && begin_count > 0
123    }
124}