1use super::CertificateProvider;
16use super::DynamicCertificates;
17use super::{Error, LOG_TARGET, TlsCertificate};
18use ahash::AHashMap;
19use async_trait::async_trait;
20use pingap_config::CertificateConf;
21use pingora::listeners::tls::TlsSettings;
22use pingora::tls::ext;
23use pingora::tls::pkey::{PKey, Private};
24use pingora::tls::ssl::SslVersion;
25use pingora::tls::ssl::{NameType, SslRef};
26use pingora::tls::x509::X509;
27use std::borrow::Cow;
28use std::collections::HashMap;
29use std::sync::Arc;
30use tracing::{debug, error, info};
31
32type Result<T, E = Error> = std::result::Result<T, E>;
33
34pub static DEFAULT_SERVER_NAME: &str = "*";
38
39pub fn parse_certificates(
46 certificate_configs: &HashMap<String, CertificateConf>,
47) -> (DynamicCertificates, Vec<(String, String)>) {
48 let mut dynamic_certs = AHashMap::new();
49 let mut errors = vec![];
50
51 let mut cert_cache: AHashMap<String, Arc<TlsCertificate>> = AHashMap::new();
53
54 for (name, conf) in certificate_configs.iter() {
55 if conf.tls_cert.is_none() || conf.tls_key.is_none() {
56 continue;
57 }
58
59 let cert_arc = match cert_cache.get(name) {
60 Some(cert) => cert.clone(),
61 None => match TlsCertificate::try_from(conf) {
62 Ok(mut cert) => {
63 cert.name = Some(name.clone());
64 let arc_cert = Arc::new(cert);
65 cert_cache.insert(name.clone(), arc_cert.clone());
66 arc_cert
67 },
68 Err(e) => {
69 errors.push((name.clone(), e.to_string()));
70 continue;
71 },
72 },
73 };
74
75 let domains_to_serve: Cow<[String]> = if let Some(value) = &conf.domains
77 {
78 Cow::Owned(value.split(',').map(|s| s.trim().to_string()).collect())
79 } else {
80 Cow::Borrowed(&cert_arc.domains)
81 };
82
83 for domain in domains_to_serve.iter() {
84 dynamic_certs.insert(domain.to_string(), cert_arc.clone());
85 }
86
87 if conf.is_default.unwrap_or_default() {
88 dynamic_certs
89 .insert(DEFAULT_SERVER_NAME.to_string(), cert_arc.clone());
90 }
91 }
92 (dynamic_certs, errors)
93}
94
95#[derive(Debug)]
100pub struct TlsSettingParams {
101 pub server_name: String,
102 pub enabled_h2: bool, pub cipher_list: Option<String>, pub cipher_suites: Option<String>, pub tls_min_version: Option<String>, pub tls_max_version: Option<String>, }
108
109#[inline]
120fn ssl_certificate(
121 ssl: &mut SslRef,
122 cert: &X509,
123 key: &PKey<Private>,
124 chain_certificates: &Option<Vec<X509>>,
125) {
126 if let Err(e) = ext::ssl_use_certificate(ssl, cert) {
128 error!(target: LOG_TARGET, error = %e, "ssl use certificate fail");
129 }
130 if let Err(e) = ext::ssl_use_private_key(ssl, key) {
132 error!(target: LOG_TARGET, error = %e, "ssl use private key fail");
133 }
134 if let Some(chain_certificates) = chain_certificates {
136 for chain in chain_certificates.iter() {
137 if let Err(e) = ext::ssl_add_chain_cert(ssl, chain) {
138 error!(target: LOG_TARGET, error = %e, "ssl add chain cert fail");
139 }
140 }
141 }
142}
143
144fn convert_tls_version(version: &Option<String>) -> Option<SslVersion> {
145 if let Some(version) = &version {
146 let version = match version.to_lowercase().as_str() {
147 "tlsv1.1" => SslVersion::TLS1_1,
148 "tlsv1.3" => SslVersion::TLS1_3,
149 _ => SslVersion::TLS1_2,
150 };
151 return Some(version);
152 }
153 None
154}
155
156#[derive(Clone)]
165pub struct GlobalCertificate {
166 provider: Arc<dyn CertificateProvider>,
167}
168
169impl GlobalCertificate {
170 pub fn new(provider: Arc<dyn CertificateProvider>) -> Self {
171 Self { provider }
172 }
173 pub fn new_tls_settings(
175 &self,
176 params: &TlsSettingParams,
177 ) -> Result<TlsSettings> {
178 let name = params.server_name.clone();
179 let mut tls_settings = TlsSettings::with_callbacks(Box::new(
180 self.clone(),
181 ))
182 .map_err(|e| Error::Invalid {
183 category: "new_tls_settings".to_string(),
184 message: e.to_string(),
185 })?;
186 if params.enabled_h2 {
187 tls_settings.enable_h2();
188 }
189 if let Some(cipher_list) = ¶ms.cipher_list
190 && let Err(e) = tls_settings.set_cipher_list(cipher_list)
191 {
192 error!(target: LOG_TARGET, error = %e, name, "set cipher list fail");
193 }
194 if let Some(cipher_suites) = ¶ms.cipher_suites
195 && let Err(e) = tls_settings.set_ciphersuites(cipher_suites)
196 {
197 error!(target: LOG_TARGET, error = %e, name, "set cipher suites fail");
198 }
199 if let Some(version) = convert_tls_version(¶ms.tls_min_version) {
200 if let Err(e) = tls_settings.set_min_proto_version(Some(version)) {
201 error!(target: LOG_TARGET, error = %e, name, "set tls min proto version fail");
202 }
203 if version == pingora::tls::ssl::SslVersion::TLS1_1 {
204 tls_settings.set_security_level(0);
205 tls_settings
206 .clear_options(pingora::tls::ssl::SslOptions::NO_TLSV1_1);
207 }
208 }
209 if let Err(e) = tls_settings
210 .set_max_proto_version(convert_tls_version(¶ms.tls_max_version))
211 {
212 error!(target: LOG_TARGET, error = %e, name, "set tls max proto version fail");
213 }
214
215 if let Some(min_version) = tls_settings.min_proto_version() {
216 info!(
217 target: LOG_TARGET,
218 name,
219 min_version = format!("{min_version:?}"),
220 "tls proto"
221 );
222 }
223 if let Some(max_version) = tls_settings.max_proto_version() {
224 info!(
225 target: LOG_TARGET,
226 name,
227 max_version = format!("{max_version:?}"),
228 "tls proto"
229 );
230 }
231
232 Ok(tls_settings)
233 }
234}
235
236#[async_trait]
237impl pingora::listeners::TlsAccept for GlobalCertificate {
238 async fn certificate_callback(&self, ssl: &mut SslRef) {
239 let sni = ssl
248 .servername(NameType::HOST_NAME)
249 .unwrap_or(DEFAULT_SERVER_NAME);
250 debug!(
252 target: LOG_TARGET,
253 ssl = format!("{ssl:?}"),
254 server_name = sni
255 );
256
257 let dynamic_certificate = self.provider.get(sni);
259
260 let Some(d) = dynamic_certificate else {
261 error!(
262 target: LOG_TARGET,
263 sni,
264 ssl = format!("{ssl:?}"),
265 "no match certificate"
266 );
267 return;
268 };
269
270 if d.is_ca {
272 match d.get_self_signed_certificate(sni) {
273 Ok(result) => {
274 ssl_certificate(
275 ssl,
276 &result.x509,
277 &result.key,
278 &d.chain_certificates,
279 );
280 },
281 Err(err) => {
282 error!(target: LOG_TARGET, error = %err, "get self signed cert fail");
283 },
284 };
285 return;
286 }
287
288 if let Some((cert, key)) = &d.certificate {
289 ssl_certificate(ssl, cert, key, &d.chain_certificates);
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use pingap_config::CertificateConf;
298 use pretty_assertions::assert_eq;
299
300 fn get_tls_pem() -> (String, String) {
301 (
303 r###"-----BEGIN CERTIFICATE-----
304MIID/TCCAmWgAwIBAgIQJUGCkB1VAYha6fGExkx0KTANBgkqhkiG9w0BAQsFADBV
305MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExFTATBgNVBAsMDHZpY2Fu
306c29AdHJlZTEcMBoGA1UEAwwTbWtjZXJ0IHZpY2Fuc29AdHJlZTAeFw0yNDA3MDYw
307MjIzMzZaFw0yNjEwMDYwMjIzMzZaMEAxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9w
308bWVudCBjZXJ0aWZpY2F0ZTEVMBMGA1UECwwMdmljYW5zb0B0cmVlMIIBIjANBgkq
309hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv5dbylSPQNARrpT/Rn7qZf6JmH3cueMp
310YdOpctuPYeefT0Jdgp67bg17fU5pfyR2BWYdwyvHCNmKqLdYPx/J69hwTiVFMOcw
311lVQJjbzSy8r5r2cSBMMsRaAZopRDnPy7Ls7Ji+AIT4vshUgL55eR7ACuIJpdtUYm
312TzMx9PTA0BUDkit6z7bTMaEbjDmciIBDfepV4goHmvyBJoYMIjnAwnTFRGRs/QJN
313d2ikFq999fRINzTDbRDP1K0Kk6+zYoFAiCMs9lEDymu3RmiWXBXpINR/Sv8CXtz2
3149RTVwTkjyiMOPY99qBfaZTiy+VCjcwTGKPyus1axRMff4xjgOBewOwIDAQABo14w
315XDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgw
316FoAUhU5Igu3uLUabIqUhUpVXjk1JVtkwFAYDVR0RBA0wC4IJcGluZ2FwLmlvMA0G
317CSqGSIb3DQEBCwUAA4IBgQDBimRKrqnEG65imKriM2QRCEfdB6F/eP9HYvPswuAP
318tvQ6m19/74qbtkd6vjnf6RhMbj9XbCcAJIhRdnXmS0vsBrLDsm2q98zpg6D04F2E
319L++xTiKU6F5KtejXcTHHe23ZpmD2XilwcVDeGFu5BEiFoRH9dmqefGZn3NIwnIeD
320Yi31/cL7BoBjdWku5Qm2nCSWqy12ywbZtQCbgbzb8Me5XZajeGWKb8r6D0Nb+9I9
321OG7dha1L3kxerI5VzVKSiAdGU0C+WcuxfsKAP8ajb1TLOlBaVyilfqmiF457yo/2
322PmTYzMc80+cQWf7loJPskyWvQyfmAnSUX0DI56avXH8LlQ57QebllOtKgMiCo7cr
323CCB2C+8hgRNG9ZmW1KU8rxkzoddHmSB8d6+vFqOajxGdyOV+aX00k3w6FgtHOoKD
324Ztdj1N0eTfn02pibVcXXfwESPUzcjERaMAGg1hoH1F4Gxg0mqmbySAuVRqNLnXp5
325CRVQZGgOQL6WDg3tUUDXYOs=
326-----END CERTIFICATE-----"###
327 .to_string(),
328 r###"-----BEGIN PRIVATE KEY-----
329MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/l1vKVI9A0BGu
330lP9Gfupl/omYfdy54ylh06ly249h559PQl2CnrtuDXt9Tml/JHYFZh3DK8cI2Yqo
331t1g/H8nr2HBOJUUw5zCVVAmNvNLLyvmvZxIEwyxFoBmilEOc/LsuzsmL4AhPi+yF
332SAvnl5HsAK4gml21RiZPMzH09MDQFQOSK3rPttMxoRuMOZyIgEN96lXiCgea/IEm
333hgwiOcDCdMVEZGz9Ak13aKQWr3319Eg3NMNtEM/UrQqTr7NigUCIIyz2UQPKa7dG
334aJZcFekg1H9K/wJe3Pb1FNXBOSPKIw49j32oF9plOLL5UKNzBMYo/K6zVrFEx9/j
335GOA4F7A7AgMBAAECggEAWNDkx2XtxsDuAX2m3VpGdSPLS3rFURMCgwwpGEq6LEvA
336qXB9gujswHbVkWBBPaR8ZcJR98EaknquccoUyaaF56Q9Y6yZZ7M07XS4vREUs06T
3378wEX9Ec6BcjTOW/77BGpAGjyO7qOf7nA2oRsqF62Ua57CjglSryLU9nKxeCUZaEa
338HWbpn/AVieddIBdCSK1ANFgXb1ySA3Rh2IaMggql1n2+gk2s4qyAScarNSz0PDps
339v65iK1ZAABmQEItsklBE8XddIK0BE5ciaLShK+BLX/bnPjCle2QGdDOtbNKfn3Ab
3408gMmY9q4/isO0i8njeNWtgrmOKpL8ETxbzCDGwqdEQKBgQDxe3nuxeDJSXUaj4Vl
341LMJ+jln8AZTEegt5T0lm3kke4vJTyQAjwCtWrxB8xario5uWwf0Np/NvLvqJI7e4
342+KIJF/5Vy15QngUHJ0c5D8Fm0DufWI9btuZDG3EYeqs4NRbc1Vu+QBziwZXvemkU
3432hHwnVYn3lc2WKgiEXcLf2SAQwKBgQDLHAkc9JzWOnj6YIb/WWLGQxu7kVW6T3Fr
344f+c4IZN9IhbjxrRilMG0Z/kQDX8dD2b3suOD+QjBZ1rJR34xDVGPPhbHx+3j+2rK
345piUZLPAqk+vODHlx9ST9V7RklZnsitQpxZLI5OhylIKXkTk6I92jDUJNRF9ooeoV
346zi2FHQasqQKBgFJg0g7PeEiSg51k+peyNkNgInhivbJtA/8FOkAaco1T1GEav65y
347fxZaMGCwOgSI1aoPUVlYQyZZu2QPSDyUrQo3Ii94ahtMXOC82IIxysNdJAnO91DN
348Sy33bZRxPHm3Oq5pJpv3WSNN8O06MCDJ57bSpbKCGfRTOEAu/xJwCgPrAoGBALtv
349GN3WwvFTrpboA0yb8XIjNfGHMkSn0XQx6W+8VH5SuirjEU40FvnkRUzSF676qrwF
350Ir6ET9cjCP3ccxDTSKPW2XDuCJOuTaPLZUrxVIUGUsKocl5+qu78Q+XaxNwsVZRi
3511o176SLr+APlKZmExaEVuEzTvvQxD3Ol/A3udl1ZAoGBAKztzGZc2YG5nw62kJ8J
3521XBrQG1rWuAMgrVbo/aDnPs04E31tPEOrZ2m7pKr/uGmf74OQeQrUaQ0+A5YZxrD
353vmkKQHwfyX6cFGxuXwyCZa7q1E83qFNLPSZ0ZF8DHiJqeunLchxYm4uA4Y8BO1jK
354aqcrKJfS+xaKWxXPiNlpBMG5
355-----END PRIVATE KEY-----"###
356 .to_string(),
357 )
358 }
360
361 #[test]
362 fn test_convert_tls_version() {
363 assert_eq!(
364 SslVersion::TLS1_1,
365 convert_tls_version(&Some("tlsv1.1".to_string())).unwrap()
366 );
367 assert_eq!(
368 SslVersion::TLS1_2,
369 convert_tls_version(&Some("tlsv1.2".to_string())).unwrap()
370 );
371 assert_eq!(
372 SslVersion::TLS1_3,
373 convert_tls_version(&Some("tlsv1.3".to_string())).unwrap()
374 );
375 }
376
377 #[test]
378 fn test_parse_certificate() {
379 let (tls_cert, tls_key) = get_tls_pem();
380 let cert_info = CertificateConf {
381 tls_cert: Some(tls_cert),
382 tls_key: Some(tls_key),
383 is_default: Some(true),
384 ..Default::default()
385 };
386 let dynamic_certificate: TlsCertificate =
387 (&cert_info).try_into().unwrap();
388 let info = dynamic_certificate.info.unwrap_or_default();
389
390 assert_eq!("pingap.io", dynamic_certificate.domains.join(","));
391 assert_eq!(
392 "O=mkcert development CA, OU=vicanso@tree, CN=mkcert vicanso@tree",
393 info.issuer
394 );
395 assert_eq!(1720232616, info.not_before);
396 assert_eq!(1791253416, info.not_after);
397 assert_eq!(true, dynamic_certificate.certificate.is_some());
398 }
399}