spin_trigger_http/
tls.rs

1use anyhow::Context;
2use rustls_pki_types::pem::PemObject;
3use std::{
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) -> anyhow::Result<Vec<rustls_pki_types::CertificateDer<'static>>> {
39    rustls_pki_types::CertificateDer::pem_file_iter(&path)
40        .and_then(Iterator::collect)
41        .with_context(|| {
42            format!(
43                "failed to load certificate(s) from '{}'",
44                path.as_ref().display()
45            )
46        })
47}
48
49// parse and return the first private key from the provided file
50fn load_key(path: impl AsRef<Path>) -> anyhow::Result<rustls_pki_types::PrivateKeyDer<'static>> {
51    rustls_pki_types::PrivateKeyDer::from_pem_file(&path).with_context(|| {
52        format!(
53            "failed to load private key from '{}'",
54            path.as_ref().display()
55        )
56    })
57}
58
59#[cfg(test)]
60mod tests {
61    use rustls_pki_types::pem;
62
63    use super::*;
64
65    const TESTDATA_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/testdata");
66
67    #[test]
68    fn test_read_non_existing_cert() {
69        let path = Path::new(TESTDATA_DIR).join("non-existing-file.pem");
70        match load_certs(path).unwrap_err().downcast().unwrap() {
71            pem::Error::Io(err) => assert_eq!(err.kind(), std::io::ErrorKind::NotFound),
72            other => panic!("expected Error::Io error got {other:?}"),
73        }
74    }
75
76    #[test]
77    fn test_read_invalid_cert() {
78        let path = Path::new(TESTDATA_DIR).join("invalid-cert.pem");
79        match load_certs(path).unwrap_err().downcast().unwrap() {
80            pem::Error::MissingSectionEnd { .. } => (),
81            other => panic!("expected Error::MissingSectionEnd got {other:?}"),
82        }
83    }
84
85    #[test]
86    fn test_read_valid_cert() {
87        let path = Path::new(TESTDATA_DIR).join("valid-cert.pem");
88        let certs = load_certs(path).unwrap();
89        assert_eq!(certs.len(), 2);
90    }
91
92    #[test]
93    fn test_read_non_existing_private_key() {
94        let path = Path::new(TESTDATA_DIR).join("non-existing-file.pem");
95        match load_key(path).unwrap_err().downcast().unwrap() {
96            pem::Error::Io(err) => assert_eq!(err.kind(), std::io::ErrorKind::NotFound),
97            other => panic!("expected Error::Io error got {other:?}"),
98        }
99    }
100
101    #[test]
102    fn test_read_invalid_private_key() {
103        let path = Path::new(TESTDATA_DIR).join("invalid-private-key.pem");
104        match load_key(path).unwrap_err().downcast().unwrap() {
105            pem::Error::MissingSectionEnd { .. } => (),
106            other => panic!("expected Error::MissingSectionEnd got {other:?}"),
107        }
108    }
109
110    #[test]
111    fn test_read_valid_private_key() {
112        let path = Path::new(TESTDATA_DIR).join("valid-private-key.pem");
113        load_key(path).unwrap();
114    }
115}