spin_trigger_http/
tls.rs

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// TODO: dedupe with spin-factor-outbound-networking (spin-tls crate?)
10
11/// TLS configuration for the server.
12#[derive(Clone)]
13pub struct TlsConfig {
14    /// Path to TLS certificate.
15    pub cert_path: PathBuf,
16    /// Path to TLS key.
17    pub key_path: PathBuf,
18}
19
20impl TlsConfig {
21    // Creates a TLS acceptor from server config.
22    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
35// load_certs parse and return the certs from the provided file
36fn 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
50// parse and return the first private key from the provided file
51fn 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}