1use super::chain::get_lets_encrypt_chain_certificate;
16use super::self_signed::{
17 SelfSignedCertificate, add_self_signed_certificate,
18 get_self_signed_certificate,
19};
20use super::{
21 Certificate, Error, LOG_TARGET, Result, parse_leaf_chain_certificates,
22};
23use pingap_config::CertificateConf;
24use pingap_config::Hashable;
25use pingora::tls::pkey::{PKey, Private};
26use pingora::tls::x509::X509;
27use std::sync::Arc;
28use tracing::info;
29
30const LETS_ENCRYPT: &str = "lets_encrypt";
32const ERROR_CERTIFICATE: &str = "certificate";
33const ERROR_X509: &str = "x509_from_pem";
34const ERROR_PRIVATE_KEY: &str = "private_key_from_pem";
35const ERROR_CA: &str = "ca";
36
37#[derive(Debug, Clone, Default)]
39pub struct TlsCertificate {
40 pub name: Option<String>,
42 pub chain_certificates: Option<Vec<X509>>,
44 pub certificate: Option<(X509, PKey<Private>)>,
46 pub domains: Vec<String>,
48 pub info: Option<Certificate>,
50 pub hash_key: String,
52 pub is_ca: bool,
54 pub buffer_days: u16,
56}
57
58impl TryFrom<&CertificateConf> for TlsCertificate {
59 type Error = Error;
60 fn try_from(value: &CertificateConf) -> Result<Self, Self::Error> {
61 let (info, x509_certificates) = parse_leaf_chain_certificates(
63 value.tls_cert.clone().unwrap_or_default().as_str(),
64 value.tls_key.clone().unwrap_or_default().as_str(),
65 )
66 .map_err(|e| Error::Invalid {
67 message: e.to_string(),
68 category: ERROR_CERTIFICATE.to_string(),
69 })?;
70 let category = if value.acme.is_some() {
71 LETS_ENCRYPT
72 } else {
73 ""
74 };
75 let hash_key = value.hash_key();
76 if x509_certificates.is_empty() {
77 return Err(Error::Invalid {
78 message: "x509 certificates is empty".to_string(),
79 category: ERROR_CERTIFICATE.to_string(),
80 });
81 }
82 let cert = x509_certificates[0].clone();
83 let mut chain_certificates = None;
84 if x509_certificates.len() > 1 {
85 chain_certificates = Some(x509_certificates[1..].to_vec());
86 } else if category == LETS_ENCRYPT
87 && let Some(chain_certificate) = get_lets_encrypt_chain_certificate(
88 info.get_issuer_common_name().as_str(),
89 )
90 {
91 chain_certificates = Some(vec![chain_certificate]);
92 }
93
94 let key = PKey::private_key_from_pem(&info.get_key()).map_err(|e| {
95 Error::Invalid {
96 category: ERROR_PRIVATE_KEY.to_string(),
97 message: e.to_string(),
98 }
99 })?;
100 Ok(TlsCertificate {
101 hash_key,
102 chain_certificates,
103 domains: info.domains.clone(),
104 certificate: Some((cert, key)),
105 info: Some(info),
106 is_ca: value.is_ca.unwrap_or_default(),
107 buffer_days: value.buffer_days.unwrap_or_default(),
108 ..Default::default()
109 })
110 }
111}
112
113fn new_certificate_with_ca(
122 root_ca: &TlsCertificate,
123 cn: &str,
124) -> Result<(X509, PKey<Private>, i64)> {
125 let Some(info) = &root_ca.info else {
126 return Err(Error::Invalid {
127 message: "root ca is invalid".to_string(),
128 category: ERROR_CA.to_string(),
129 });
130 };
131 let binding = info.get_cert();
132 let ca_pem = std::string::String::from_utf8_lossy(&binding);
133
134 let ca_params = rcgen::CertificateParams::from_ca_cert_pem(&ca_pem)
135 .map_err(|e| Error::Invalid {
136 message: e.to_string(),
137 category: ERROR_CA.to_string(),
138 })?;
139
140 let binding = info.get_key();
141 let ca_key = std::string::String::from_utf8_lossy(&binding);
142
143 let ca_kp =
144 rcgen::KeyPair::from_pem(&ca_key).map_err(|e| Error::Invalid {
145 message: e.to_string(),
146 category: ERROR_CA.to_string(),
147 })?;
148 let not_before = time::OffsetDateTime::now_utc() - time::Duration::days(1);
149 let two_years_from_now =
150 time::OffsetDateTime::now_utc() + time::Duration::days(365 * 2);
151 let not_after = ca_params.not_after.min(two_years_from_now);
152 let ca_cert =
153 ca_params.self_signed(&ca_kp).map_err(|e| Error::Invalid {
154 message: e.to_string(),
155 category: ERROR_CA.to_string(),
156 })?;
157
158 let mut params = rcgen::CertificateParams::new(vec![cn.to_string()])
159 .map_err(|e| Error::Invalid {
160 message: e.to_string(),
161 category: ERROR_CA.to_string(),
162 })?;
163 let mut dn = rcgen::DistinguishedName::new();
164 dn.push(rcgen::DnType::CommonName, cn.to_string());
165 if let Some(organ) = ca_cert
166 .params()
167 .distinguished_name
168 .get(&rcgen::DnType::OrganizationName)
169 {
170 dn.push(rcgen::DnType::OrganizationName, organ.clone());
171 };
172 if let Some(unit) = ca_cert
173 .params()
174 .distinguished_name
175 .get(&rcgen::DnType::OrganizationalUnitName)
176 {
177 dn.push(rcgen::DnType::OrganizationalUnitName, unit.clone());
178 };
179
180 params.distinguished_name = dn;
181 params.not_before = not_before;
182 params.not_after = not_after;
183
184 let cert_key = rcgen::KeyPair::generate().map_err(|e| Error::Invalid {
185 message: e.to_string(),
186 category: ERROR_CA.to_string(),
187 })?;
188
189 let cert = params.signed_by(&cert_key, &ca_cert, &ca_kp).map_err(|e| {
190 Error::Invalid {
191 message: e.to_string(),
192 category: ERROR_CA.to_string(),
193 }
194 })?;
195
196 let cert =
197 X509::from_pem(cert.pem().as_bytes()).map_err(|e| Error::Invalid {
198 category: ERROR_X509.to_string(),
199 message: e.to_string(),
200 })?;
201
202 let key = PKey::private_key_from_pem(cert_key.serialize_pem().as_bytes())
203 .map_err(|e| Error::Invalid {
204 category: ERROR_PRIVATE_KEY.to_string(),
205 message: e.to_string(),
206 })?;
207
208 Ok((cert, key, not_after.unix_timestamp()))
209}
210
211impl TlsCertificate {
212 pub fn get_self_signed_certificate(
222 &self,
223 server_name: &str,
224 ) -> Result<Arc<SelfSignedCertificate>> {
225 let cn = Self::format_common_name(server_name);
227 let cache_key = format!("{:?}:{}", self.name, cn);
229
230 if let Some(cert) = get_self_signed_certificate(&cache_key) {
232 return Ok(cert);
233 }
234
235 let (cert, key, not_after) = new_certificate_with_ca(self, &cn)?;
237 info!(
238 target: LOG_TARGET,
239 ca_common_name = self.name,
240 common_name = cn,
241 "create new self signed certificate"
242 );
243 Ok(add_self_signed_certificate(cache_key, cert, key, not_after))
244 }
245
246 fn format_common_name(server_name: &str) -> String {
255 let parts: Vec<&str> = server_name.split('.').collect();
256 if parts.len() > 2 {
259 format!("*.{}", parts[1..].join("."))
260 } else {
261 server_name.to_string()
262 }
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::TlsCertificate;
269 use pingap_config::CertificateConf;
270 use pretty_assertions::assert_eq;
271
272 #[test]
273 fn test_format_common_name() {
274 assert_eq!(
275 "*.example.com",
276 TlsCertificate::format_common_name("subdomain.example.com")
277 );
278 assert_eq!(
279 "example.com",
280 TlsCertificate::format_common_name("example.com")
281 );
282 }
283
284 #[test]
285 fn test_get_self_signed_certificate() {
286 let pem = r#"-----BEGIN CERTIFICATE-----
288MIIENzCCAp+gAwIBAgIRALESVNFwfk4BBxPnZLHdLaMwDQYJKoZIhvcNAQELBQAw
289bTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSEwHwYDVQQLDBh0cmVl
290QGFub255bW91cyAoVHJlZVhpZSkxKDAmBgNVBAMMH21rY2VydCB0cmVlQGFub255
291bW91cyAoVHJlZVhpZSkwHhcNMjUwMTI4MDczODE4WhcNMjcwNDI4MDczODE4WjBd
292MScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxMjAwBgNV
293BAsMKXRyZWVAVHJlZVhpZXMtTWFjQm9vay1Qcm8ubG9jYWwgKFRyZWVYaWUpMIIB
294IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv/Wt6HfiFrIOnW3eZx7A+iF7
295tywiyqRYX0CoecStk4n0H2s0V2nk6zmPwEvF1Qxd4OjUkwrVtIWCNyC3SXzoG+62
296dYMMCRmDZGqiUPaZjKcpObsxIcWIt1lO6mqZaf7hPNPtAb3gO4lLOgy4Ipv7q0Oy
297BY2myg7X9xOTzXmI6va8XSdGHsoilpic/mF95BE3D7FINx3j12HAwnMGY5/xPAFp
298QTa/3zoE22TCUpZHb1v9X3N2olPCUWRNbgCFWl5vKpfvqLlP19th1jhr2DkUVeWs
299RXYaB2ULwkNKkdhO0ka3hZipu6C3qDmfssfkm+lVhUvgUYWElaKDZG88ia26rQID
300AQABo2IwYDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYD
301VR0jBBgwFoAU210uBCmUnt7NVBOP3t3kjNq6XlEwGAYDVR0RBBEwD4INKi5leGFt
302cGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAYEATOEErFNYpxuxFkO/fDoUvuD9c9n3
303UetdrQ3u1E5EeYy+LaCWjEtDLf8t2NYKfuqQGxWgkdQYU6GIF4pbuZeARrReoind
3044SRSaA4Zwc8BvmA+UeCgm0uGAY2B3FQ6oUK7sY+wtIr0ob6nGLUtstZVesvA3elG
305xVcUM5tmBlm2rjLjvumIsfNK7VdKUY6yV2Z50nXNDkpT+achL1sJVMUIRokUezNB
306Pn1UjgblgpjuIA0A+e1XIm0Co/1JtJv8FOfUWGFE0oYJNSqp0sX51ZZ+5flV/3nS
307inJvDyFSfTsSNlgEeFb0Ek8XmFpQqFXd8O20owgkcO/XFCkovFzuPdJwQ0hxTAzU
308yOFUVc2HLxISKWJmyZ2XCoSrZgHjnOxdqY187J9Xv2T7P59H4JYvSB90iwqKLKTW
309GEVKsWU+0lbeWGwpAe46HfSg3xl/zoL62SCNsC0ruoJofprLDF0e6vxJQy9s4Dp6
310DiHunXjaGjAc2C1GAdLdkLDokENUTFp9nZJv
311-----END CERTIFICATE-----"#;
312 let key = r#"-----BEGIN PRIVATE KEY-----
313MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC/9a3od+IWsg6d
314bd5nHsD6IXu3LCLKpFhfQKh5xK2TifQfazRXaeTrOY/AS8XVDF3g6NSTCtW0hYI3
315ILdJfOgb7rZ1gwwJGYNkaqJQ9pmMpyk5uzEhxYi3WU7qaplp/uE80+0BveA7iUs6
316DLgim/urQ7IFjabKDtf3E5PNeYjq9rxdJ0YeyiKWmJz+YX3kETcPsUg3HePXYcDC
317cwZjn/E8AWlBNr/fOgTbZMJSlkdvW/1fc3aiU8JRZE1uAIVaXm8ql++ouU/X22HW
318OGvYORRV5axFdhoHZQvCQ0qR2E7SRreFmKm7oLeoOZ+yx+Sb6VWFS+BRhYSVooNk
319bzyJrbqtAgMBAAECggEBAJIME8KY43UtB52TZ/DBH0Wvj/bvJ5FRtMLT6NqsXvuv
320rALzh6EyOi8VXl+JxvyvKgXiX0l4pttv8ICM7aaF1/rYhg2mJNQPiz4tO02qMW0o
321CV+ZImp1Ze1Jj5cef5Z7i1bCTsJSenYRoSCLaNU8JCBLovhCq7Fz1bBwPrXIT/mj
322aoDF68eWuxefM0EiUh0xqNSF/eglyXOIt6Fz6p3gMvTKwYsSM02CXaui6rNbU5aN
323YJ5M6Sem/FqtwIyb48UHMvI26ajVwtMSiNwR2XXV6gxg8xWFpjU+Uh3u1Qxacj2K
324aW5jBFiLQegcZetGvL+z65VYV/cKHkYl4PhULep3CmkCgYEA45BpCntOVP/2ThUP
325oXTDJnRJ8YfiYgP41zPAfK8daeOCuJRNhGXUr8fI/PiAmLIRbs8hJ8TeZTa8nzSE
3260zBJ7CEbjSXbKR6UDCvm85QPGnog7fDpkjk2qcFRbXFZbolerMzHuQfUvFH/t48Q
327SScn64aSq/Ymjkz9O0jPJ4xrK7sCgYEA1/JQEkUdbFsOO/mRA+YEtS1C359UD5Au
328n413sI/D/C8dbTveQ4lNnd5s/JLZEvBCIX6+mczvm7ZAKLv4k9uYHw6mzdEQbUNR
329uf6BNbAeRUxoN90qWzSVqkK9S4vs8x3zWkREqSJeSwW7Kh2DAz8HM4u36gxQhz2V
330+eHz0a1Q6LcCgYEAoQOV/y+eDjCJ81edlq0KQ9Q2Waq++IE8+fAJO2+gTUMIRFfS
331vWJb6gBfavbd7qzX/uKZ4AzBGzZuoetELDXXqDcIyodFmcOkFzSdFi3lveM6F4HF
332kove7J/3YIu6LqcOERBYJMiwsosGd7fHWytUaKbwcrIZN8iryN3MjXwifG8CgYEA
333qt0oq/wR1t2JOr0yF+KVUQGaCzSXH6VWrpoR3Rsz2EMzRm37ZHasekA2/fX3WjvO
334J5CQoUL9R7iBtXldqygyikhehTVpiPqeHMuaUu+iU/Sr9Z/CVt4ZmdkqzC7P8mF9
335Xqvro+P0tem3+Q/WzOe++/MON1s9EHUTSN+Wuw4mmasCgYBqzSZro05J6U+74g5X
3361QRl97OzlCgzaIWLHlv9nZzHivIrQxPtK1QStFiJOeQTq5XqdLywkGHWHsgBYQhl
337iama6sNZgokeRWVL1QJBaC2q0312AG8xeOZ7oWqfAtfxGpjhvNpgPJfZi8NA7+WE
338kknq2XUsBMCyIW1BqgLVEyeNxg==
339-----END PRIVATE KEY-----"#;
340 let cert = TlsCertificate::try_from(&CertificateConf {
342 tls_cert: Some(pem.to_string()),
343 tls_key: Some(key.to_string()),
344 ..Default::default()
345 })
346 .unwrap();
347
348 let server_name = format!("{}.test.example.com", nanoid::nanoid!(10));
349 let cert = cert.get_self_signed_certificate(&server_name).unwrap();
350 assert_eq!(
351 r#"[commonName = "*.test.example.com", organizationName = "mkcert development certificate", organizationalUnitName = "tree@TreeXies-MacBook-Pro.local (TreeXie)"]"#,
352 format!("{:?}", cert.x509.subject_name())
353 );
354 }
355}