1use rustls_pemfile::private_key;
2use std::{
3 fs, io,
4 path::{Path, PathBuf},
5 sync::Arc,
6};
7use tokio_rustls::{rustls, TlsAcceptor};
8
9#[derive(Clone)]
13pub struct TlsConfig {
14 pub cert_path: PathBuf,
16 pub key_path: PathBuf,
18}
19
20impl TlsConfig {
21 pub(super) fn server_config(&self) -> anyhow::Result<TlsAcceptor> {
23 let certs = load_certs(&self.cert_path)?;
24 let private_key = load_key(&self.key_path)?;
25
26 let cfg = rustls::ServerConfig::builder()
27 .with_no_client_auth()
28 .with_single_cert(certs, private_key)
29 .map_err(|e| anyhow::anyhow!("{}", e))?;
30
31 Ok(Arc::new(cfg).into())
32 }
33}
34
35fn load_certs(
37 path: impl AsRef<Path>,
38) -> io::Result<Vec<rustls_pki_types::CertificateDer<'static>>> {
39 rustls_pemfile::certs(&mut io::BufReader::new(fs::File::open(path).map_err(
40 |err| {
41 io::Error::new(
42 io::ErrorKind::InvalidInput,
43 format!("failed to read cert file {:?}", err),
44 )
45 },
46 )?))
47 .collect()
48}
49
50fn load_key(path: impl AsRef<Path>) -> io::Result<rustls_pki_types::PrivateKeyDer<'static>> {
52 private_key(&mut io::BufReader::new(fs::File::open(path).map_err(
53 |err| {
54 io::Error::new(
55 io::ErrorKind::InvalidInput,
56 format!("failed to read private key file {:?}", err),
57 )
58 },
59 )?))
60 .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid private key"))
61 .transpose()
62 .ok_or_else(|| {
63 io::Error::new(
64 io::ErrorKind::InvalidInput,
65 "private key file contains no private keys",
66 )
67 })?
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 const TESTDATA_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/testdata");
75
76 #[test]
77 fn test_read_non_existing_cert() {
78 let path = Path::new(TESTDATA_DIR).join("non-existing-file.pem");
79
80 let certs = load_certs(path);
81 assert!(certs.is_err());
82 assert_eq!(certs.err().unwrap().to_string(), "failed to read cert file Os { code: 2, kind: NotFound, message: \"No such file or directory\" }");
83 }
84
85 #[test]
86 fn test_read_invalid_cert() {
87 let path = Path::new(TESTDATA_DIR).join("invalid-cert.pem");
88
89 let certs = load_certs(path);
90 assert!(certs.is_err());
91 assert_eq!(
92 certs.err().unwrap().to_string(),
93 "section end \"-----END CERTIFICATE-----\" missing"
94 );
95 }
96
97 #[test]
98 fn test_read_valid_cert() {
99 let path = Path::new(TESTDATA_DIR).join("valid-cert.pem");
100
101 let certs = load_certs(path);
102 assert!(certs.is_ok());
103 assert_eq!(certs.unwrap().len(), 2);
104 }
105
106 #[test]
107 fn test_read_non_existing_private_key() {
108 let path = Path::new(TESTDATA_DIR).join("non-existing-file.pem");
109
110 let keys = load_key(path);
111 assert!(keys.is_err());
112 assert_eq!(keys.err().unwrap().to_string(), "failed to read private key file Os { code: 2, kind: NotFound, message: \"No such file or directory\" }");
113 }
114
115 #[test]
116 fn test_read_invalid_private_key() {
117 let path = Path::new(TESTDATA_DIR).join("invalid-private-key.pem");
118
119 let keys = load_key(path);
120 assert!(keys.is_err());
121 assert_eq!(keys.err().unwrap().to_string(), "invalid private key");
122 }
123
124 #[test]
125 fn test_read_valid_private_key() {
126 let path = Path::new(TESTDATA_DIR).join("valid-private-key.pem");
127
128 let keys = load_key(path);
129 assert!(keys.is_ok());
130 }
131}