Skip to main content

prosa_utils/config/
ssl.rs

1//! Definition of SSL configuration
2
3use serde::{Deserialize, Serialize};
4use std::{collections::HashMap, fmt, io, path::Path, time::Duration};
5
6use super::ConfigError;
7
8#[cfg(feature = "config-openssl")]
9pub mod openssl;
10
11/// Trait to define an SSL store with custom SSL objects
12pub trait SslStore<C, S> {
13    /// Method to read certificates from its path. Get all certificates in subfolders
14    fn get_file_certificates(path: &std::path::Path) -> Result<Vec<C>, ConfigError>;
15
16    /// Method to get a cert store
17    ///
18    /// ```
19    /// use prosa_utils::config::ssl::{Store, SslStore};
20    ///
21    /// let store = Store::File { path: "./target".into() };
22    /// # #[cfg(feature="config-openssl")]
23    /// let ssl_store = store.get_store().unwrap();
24    /// ```
25    fn get_store(&self) -> Result<S, ConfigError>;
26
27    /// Method to get all OpenSSL certificate with their names as key
28    ///
29    /// ```
30    /// use prosa_utils::config::ssl::{Store, SslStore};
31    ///
32    /// let store = Store::File { path: "./target".into() };
33    /// # #[cfg(feature="config-openssl")]
34    /// let certs_map = store.get_certs().unwrap();
35    ///
36    /// // No cert in target
37    /// # #[cfg(feature="config-openssl")]
38    /// assert!(certs_map.is_empty());
39    /// ```
40    fn get_certs(&self) -> Result<HashMap<String, C>, ConfigError>;
41}
42
43/// SSL configuration object for store certificates
44#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
45#[serde(untagged)]
46pub enum Store {
47    /// Will use the system trusted certificates
48    #[default]
49    System,
50    /// Store path that contain certificate(s)
51    File {
52        /// Path of the store (can be directory, file, glob pattern)
53        path: String,
54    },
55    /// Store certs that contain PEMs
56    Cert {
57        /// List of string PEMs for certificates
58        certs: Vec<String>,
59    },
60}
61
62impl TryFrom<&Path> for Store {
63    type Error = io::Error;
64
65    /// Initialize a Store with a certificate path
66    ///
67    /// ```
68    /// use std::path::Path;
69    /// use prosa_utils::config::ssl::Store;
70    ///
71    /// let store = Store::try_from(Path::new("cert.pem")).expect("Path should be valid");
72    /// assert_eq!(store, Store::File{ path: "cert.pem".into() });
73    /// ```
74    fn try_from(path: &Path) -> io::Result<Self> {
75        path.to_str()
76            .map(|s| Store::File {
77                path: s.to_string(),
78            })
79            .ok_or(io::Error::new(
80                io::ErrorKind::InvalidFilename,
81                "Invalid store path",
82            ))
83    }
84}
85
86impl fmt::Display for Store {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        match self {
89            Store::System => write!(f, "System store"),
90            Store::File { path } => write!(f, "Store cert path [{path}]"),
91            Store::Cert { certs: _ } => write!(f, "Store cert list"),
92        }?;
93
94        #[cfg(feature = "config-openssl")]
95        {
96            writeln!(f, ":")?;
97            let certs: HashMap<String, ::openssl::x509::X509> =
98                self.get_certs().unwrap_or_default();
99            for (name, cert) in certs {
100                if f.alternate() {
101                    writeln!(f, "{name}:\n{cert:#?}")?;
102                } else {
103                    writeln!(f, "{name}")?;
104                }
105            }
106        }
107
108        Ok(())
109    }
110}
111
112/// Trait to define SSL configuration context for socket
113pub trait SslConfigContext<C, S> {
114    /// Method to init an SSL context for a client socket
115    ///
116    /// ```
117    /// use prosa_utils::config::ssl::{Store, SslConfig, SslConfigContext as _};
118    ///
119    /// let mut client_config = SslConfig::default();
120    /// client_config.set_store(Store::File { path: "./target".into() });
121    /// # #[cfg(feature="config-openssl")]
122    /// if let Ok(mut ssl_context_builder) = client_config.init_tls_client_context() {
123    ///     let ssl_context = ssl_context_builder.build();
124    /// }
125    /// ```
126    fn init_tls_client_context(&self) -> Result<C, ConfigError>;
127
128    /// Method to init an SSL context for a server socket
129    ///
130    /// ```
131    /// use prosa_utils::config::ssl::{SslConfig, SslConfigContext as _};
132    ///
133    /// let server_config = SslConfig::new_pkcs12("server.pkcs12".into());
134    /// # #[cfg(feature="config-openssl")]
135    /// if let Ok(mut ssl_context_builder) = server_config.init_tls_server_context(None) {
136    ///     let ssl_context = ssl_context_builder.build();
137    /// }
138    /// ```
139    fn init_tls_server_context(&self, host: Option<&str>) -> Result<S, ConfigError>;
140}
141
142/// SSL configuration for sockets.
143///
144/// Its [`Debug`](fmt::Debug) implementation deliberately omits the private-key or PKCS#12
145/// passphrase.
146///
147/// Client SSL socket
148/// ```
149/// use std::io;
150/// use std::pin::Pin;
151/// use tokio::net::TcpStream;
152/// use tokio_openssl::SslStream;
153/// use prosa_utils::config::ssl::{SslConfig, SslConfigContext};
154///
155/// # #[cfg(feature="config-openssl")]
156/// async fn client() -> Result<(), io::Error> {
157///     let mut stream = TcpStream::connect("localhost:4443").await?;
158///
159///     let client_config = SslConfig::default();
160///     if let Ok(mut ssl_context_builder) = client_config.init_tls_client_context() {
161///         let ssl = ssl_context_builder.build().configure().unwrap().into_ssl("localhost").unwrap();
162///         let mut stream = SslStream::new(ssl, stream).unwrap();
163///         Pin::new(&mut stream)
164///             .connect()
165///             .await
166///             .map_err(|e| io::Error::other(format!("Can't connect the client: {e}")))?;
167///
168///         // SSL stream ...
169///     }
170///
171///     Ok(())
172/// }
173/// ```
174///
175/// Server SSL socket
176/// ```
177/// use std::io;
178/// use std::pin::Pin;
179/// use tokio::net::TcpListener;
180/// use tokio_openssl::SslStream;
181/// # #[cfg(feature="config-openssl")]
182/// use openssl::ssl::{Ssl, SslVerifyMode};
183/// use prosa_utils::config::ssl::{SslConfig, SslConfigContext};
184///
185/// # #[cfg(feature="config-openssl")]
186/// async fn server() -> Result<(), io::Error> {
187///     let listener = TcpListener::bind("0.0.0.0:4443").await?;
188///
189///     let server_config = SslConfig::new_cert_key("cert.pem".into(), "cert.key".into(), Some("passphrase".into()));
190///     if let Ok(mut ssl_context_builder) = server_config.init_tls_server_context(None) {
191///         ssl_context_builder.set_verify(SslVerifyMode::NONE);
192///         let ssl_context = ssl_context_builder.build();
193///
194///         loop {
195///             let (stream, cli_addr) = listener.accept().await?;
196///             let ssl = Ssl::new(&ssl_context.context()).unwrap();
197///             let mut stream = SslStream::new(ssl, stream).unwrap();
198///             if let Err(e) = Pin::new(&mut stream).accept().await {
199///                 eprintln!("Can't accept the client {cli_addr}: {e}");
200///                 continue;
201///             }
202///
203///             // SSL stream ...
204///         }
205///     }
206///
207///     Ok(())
208/// }
209/// ```
210#[derive(Clone, PartialEq, Deserialize, Serialize)]
211pub struct SslConfig {
212    /// SSL store certificate to verify the remote certificate
213    store: Option<Store>,
214    /// PKCS12 object for certificate
215    pkcs12: Option<String>,
216    /// certificate
217    cert: Option<String>,
218    /// private key
219    key: Option<String>,
220    /// passphrase for private key or pkcs12
221    passphrase: Option<String>,
222    #[serde(default)]
223    /// ALPN list send by the client, or order of ALPN accepted by the server
224    alpn: Vec<String>,
225    #[serde(skip_serializing)]
226    #[serde(default = "SslConfig::default_modern_security")]
227    /// Security level. If `true`, it'll use the [modern version 5 of Mozilla's](https://wiki.mozilla.org/Security/Server_Side_TLS) TLS recommendations.
228    pub modern_security: bool,
229    #[serde(skip_serializing)]
230    #[serde(default = "SslConfig::default_ssl_timeout")]
231    /// SSL operation timeout in milliseconds
232    pub ssl_timeout: u64,
233}
234
235impl SslConfig {
236    fn default_modern_security() -> bool {
237        true
238    }
239
240    fn default_ssl_timeout() -> u64 {
241        3000
242    }
243
244    /// Method to create an ssl configuration from a pkcs12 manually
245    /// Should be use with config instead of building it manually
246    pub fn new_pkcs12(pkcs12_path: String) -> SslConfig {
247        SslConfig {
248            store: None,
249            pkcs12: Some(pkcs12_path),
250            cert: None,
251            key: None,
252            passphrase: None,
253            alpn: Vec::default(),
254            modern_security: Self::default_modern_security(),
255            ssl_timeout: Self::default_ssl_timeout(),
256        }
257    }
258
259    /// Method to create an ssl configuration from a certificate and its key manually
260    /// Should be use with config instead of building it manually
261    pub fn new_cert_key(
262        cert_path: String,
263        key_path: String,
264        passphrase: Option<String>,
265    ) -> SslConfig {
266        SslConfig {
267            store: None,
268            pkcs12: None,
269            cert: Some(cert_path),
270            key: Some(key_path),
271            passphrase,
272            alpn: Vec::default(),
273            modern_security: Self::default_modern_security(),
274            ssl_timeout: Self::default_ssl_timeout(),
275        }
276    }
277
278    /// Method to create an ssl configuration that will generate a self signed certificate and write it's certificate to the _cert_path_
279    /// Should be use with config instead of building it manually
280    pub fn new_self_cert(cert_path: String) -> SslConfig {
281        SslConfig {
282            store: None,
283            pkcs12: None,
284            cert: Some(cert_path),
285            key: None,
286            passphrase: None,
287            alpn: Vec::default(),
288            modern_security: Self::default_modern_security(),
289            ssl_timeout: Self::default_ssl_timeout(),
290        }
291    }
292
293    /// Getter of the SSL timeout
294    pub fn get_ssl_timeout(&self) -> Duration {
295        Duration::from_millis(self.ssl_timeout)
296    }
297
298    /// Setter of the store certificate
299    pub fn set_store(&mut self, store: Store) {
300        self.store = Some(store);
301    }
302
303    /// Setter of the ALPN list send by the client, or order of ALPN accepted by the server
304    pub fn set_alpn(&mut self, alpn: Vec<String>) {
305        self.alpn = alpn;
306    }
307}
308
309impl Default for SslConfig {
310    fn default() -> SslConfig {
311        SslConfig {
312            store: None,
313            pkcs12: None,
314            cert: None,
315            key: None,
316            passphrase: None,
317            alpn: Vec::default(),
318            modern_security: Self::default_modern_security(),
319            ssl_timeout: Self::default_ssl_timeout(),
320        }
321    }
322}
323
324impl fmt::Debug for SslConfig {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        f.debug_struct("SslConfig")
327            .field("store", &self.store)
328            .field("pkcs12", &self.pkcs12)
329            .field("cert", &self.cert)
330            .field("key", &self.key)
331            .field("alpn", &self.alpn)
332            .field("modern_security", &self.modern_security)
333            .field("ssl_timeout", &self.ssl_timeout)
334            .finish()
335    }
336}
337
338#[cfg(feature = "config-openssl")]
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_store() {
345        let inline_store_le_x1_x2 = Store::Cert {
346            certs: vec![
347                "-----BEGIN CERTIFICATE-----
348MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
349TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
350cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
351WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
352ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
353MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
354h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
3550TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
356A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
357T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
358B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
359B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
360KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
361OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
362jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
363qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
364rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
365HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
366hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
367ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3683BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
369NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
370ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
371TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
372jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
373oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
3744RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
375mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
376emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
377-----END CERTIFICATE-----"
378                    .to_string(),
379                "-----BEGIN CERTIFICATE-----
380MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
381CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
382R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
383MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
384ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
385EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
386+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
387ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
388AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
389zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
390tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
391/q4AaOeMSQ+2b1tbFfLn
392-----END CERTIFICATE-----"
393                    .to_string(),
394            ],
395        };
396        assert!(format!("{inline_store_le_x1_x2}").contains("ISRG Root X"));
397
398        let config_store_le_x1_x2: Store = serde_yaml::from_str(
399            "certs:
400  - |
401    -----BEGIN CERTIFICATE-----
402    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
431    -----END CERTIFICATE-----
432  - |
433    -----BEGIN CERTIFICATE-----
434    MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
435    CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
436    R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
437    MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
438    ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
439    EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
440    +1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
441    ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
442    AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
443    zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
444    tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
445    /q4AaOeMSQ+2b1tbFfLn
446    -----END CERTIFICATE-----",
447        )
448        .expect("SSL certificate configuration should be read");
449        assert!(format!("{config_store_le_x1_x2}").contains("ISRG Root X"));
450
451        let config_store_file: Store = serde_yaml::from_str("path: \"/opt\"")
452            .expect("Certificate configuration path should be read");
453        assert_eq!(
454            Store::File {
455                path: "/opt".to_string()
456            },
457            config_store_file
458        );
459    }
460
461    #[test]
462    fn test_tls_server_context() {
463        let ssl_config = SslConfig::default();
464        assert_eq!(
465            "SslConfig { store: None, pkcs12: None, cert: None, key: None, alpn: [], modern_security: true, ssl_timeout: 3000 }",
466            format!("{ssl_config:?}")
467        );
468        let ssl_acceptor = ssl_config
469            .init_tls_server_context(None)
470            .expect("The TLS server context should be init")
471            .build();
472
473        // Check for self signed certificate
474        assert!(ssl_acceptor.context().private_key().is_some());
475        assert!(ssl_acceptor.context().certificate().is_some());
476    }
477
478    #[test]
479    fn ssl_config_debug_redacts_passphrase() {
480        let ssl_config = SslConfig::new_cert_key(
481            "cert.pem".into(),
482            "key.pem".into(),
483            Some("sensitive-passphrase".into()),
484        );
485
486        let debug = format!("{ssl_config:?}");
487        assert!(debug.contains("cert.pem"));
488        assert!(debug.contains("key.pem"));
489        assert!(!debug.contains("sensitive-passphrase"));
490        assert!(!debug.contains("passphrase"));
491    }
492}