File size: 6,763 Bytes
d8435ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use std::fmt::Debug;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::sync::Arc;
use std::time::{Duration, Instant};

use parking_lot::RwLock;
use rustls::client::VerifierBuilderError;
use rustls::pki_types::CertificateDer;
use rustls::server::{ClientHello, ResolvesServerCert, WebPkiClientVerifier};
use rustls::sign::CertifiedKey;
use rustls::{crypto, RootCertStore, ServerConfig};
use rustls_pemfile::Item;

use crate::settings::{Settings, TlsConfig};

type Result<T> = std::result::Result<T, Error>;

/// A TTL based rotating server certificate resolver
#[derive(Debug)]
struct RotatingCertificateResolver {
    /// TLS configuration used for loading/refreshing certified key
    tls_config: TlsConfig,

    /// TTL for each rotation
    ttl: Option<Duration>,

    /// Current certified key
    key: RwLock<CertifiedKeyWithAge>,
}

impl RotatingCertificateResolver {
    pub fn new(tls_config: TlsConfig, ttl: Option<Duration>) -> Result<Self> {
        let certified_key = load_certified_key(&tls_config)?;

        Ok(Self {
            tls_config,
            ttl,
            key: RwLock::new(CertifiedKeyWithAge::from(certified_key)),
        })
    }

    /// Get certificate key or refresh
    ///
    /// The key is automatically refreshed when the TTL is reached.
    /// If refreshing fails, an error is logged and the old key is persisted.
    fn get_key_or_refresh(&self) -> Arc<CertifiedKey> {
        // Get read-only lock to the key. If TTL is not configured or is not expired, return key.
        let key = self.key.read();
        let ttl = match self.ttl {
            Some(ttl) if key.is_expired(ttl) => ttl,
            _ => return key.key.clone(),
        };
        drop(key);

        // If TTL is expired:
        // - get read-write lock to the key
        // - *re-check that TTL is expired* (to avoid refreshing the key multiple times from concurrent threads)
        // - refresh and return the key
        let mut key = self.key.write();
        if key.is_expired(ttl) {
            if let Err(err) = key.refresh(&self.tls_config) {
                log::error!("Failed to refresh server TLS certificate, keeping current: {err}");
            }
        }

        key.key.clone()
    }
}

impl ResolvesServerCert for RotatingCertificateResolver {
    fn resolve(&self, _client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
        Some(self.get_key_or_refresh())
    }
}

#[derive(Debug)]
struct CertifiedKeyWithAge {
    /// Last time the certificate was updated/replaced
    last_update: Instant,

    /// Current certified key
    key: Arc<CertifiedKey>,
}

impl CertifiedKeyWithAge {
    pub fn from(key: Arc<CertifiedKey>) -> Self {
        Self {
            last_update: Instant::now(),
            key,
        }
    }

    pub fn refresh(&mut self, tls_config: &TlsConfig) -> Result<()> {
        *self = Self::from(load_certified_key(tls_config)?);
        Ok(())
    }

    pub fn age(&self) -> Duration {
        self.last_update.elapsed()
    }

    pub fn is_expired(&self, ttl: Duration) -> bool {
        self.age() >= ttl
    }
}

/// Load TLS configuration and construct certified key.
fn load_certified_key(tls_config: &TlsConfig) -> Result<Arc<CertifiedKey>> {
    // Load certificates
    let certs: Vec<CertificateDer> = with_buf_read(&tls_config.cert, |rd| {
        rustls_pemfile::read_all(rd).collect::<io::Result<Vec<_>>>()
    })?
    .into_iter()
    .filter_map(|item| match item {
        Item::X509Certificate(data) => Some(data),
        _ => None,
    })
    .collect();
    if certs.is_empty() {
        return Err(Error::NoServerCert);
    }

    // Load private key
    let private_key_item =
        with_buf_read(&tls_config.key, rustls_pemfile::read_one)?.ok_or(Error::NoPrivateKey)?;
    let private_key = match private_key_item {
        Item::Pkcs1Key(pkey) => rustls_pki_types::PrivateKeyDer::from(pkey),
        Item::Pkcs8Key(pkey) => rustls_pki_types::PrivateKeyDer::from(pkey),
        Item::Sec1Key(pkey) => rustls_pki_types::PrivateKeyDer::from(pkey),
        _ => return Err(Error::InvalidPrivateKey),
    };
    let signing_key = crypto::ring::sign::any_supported_type(&private_key).map_err(Error::Sign)?;

    // Construct certified key
    let certified_key = CertifiedKey::new(certs, signing_key);
    Ok(Arc::new(certified_key))
}

/// Generate an actix server configuration with TLS
///
/// Uses TLS settings as configured in configuration by user.
pub fn actix_tls_server_config(settings: &Settings) -> Result<ServerConfig> {
    let config = ServerConfig::builder();
    let tls_config = settings
        .tls
        .clone()
        .ok_or_else(Settings::tls_config_is_undefined_error)
        .map_err(Error::Io)?;

    // Verify client CA or not
    let config = if settings.service.verify_https_client_certificate {
        let mut root_cert_store = RootCertStore::empty();
        let ca_certs: Vec<CertificateDer> = with_buf_read(&tls_config.ca_cert, |rd| {
            rustls_pemfile::certs(rd).collect()
        })?;
        root_cert_store.add_parsable_certificates(ca_certs);
        let client_cert_verifier = WebPkiClientVerifier::builder(root_cert_store.into())
            .build()
            .map_err(Error::ClientCertVerifier)?;
        config.with_client_cert_verifier(client_cert_verifier)
    } else {
        config.with_no_client_auth()
    };

    // Configure rotating certificate resolver
    let ttl = match tls_config.cert_ttl {
        None | Some(0) => None,
        Some(seconds) => Some(Duration::from_secs(seconds)),
    };
    let cert_resolver = RotatingCertificateResolver::new(tls_config, ttl)?;
    let config = config.with_cert_resolver(Arc::new(cert_resolver));

    Ok(config)
}

fn with_buf_read<T>(path: &str, f: impl FnOnce(&mut dyn BufRead) -> io::Result<T>) -> Result<T> {
    let file = File::open(path).map_err(|err| Error::OpenFile(err, path.into()))?;
    let mut reader = BufReader::new(file);
    let dyn_reader: &mut dyn BufRead = &mut reader;
    f(dyn_reader).map_err(|err| Error::ReadFile(err, path.into()))
}

/// Actix TLS errors.
#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("TLS file could not be opened: {1}")]
    OpenFile(#[source] io::Error, String),
    #[error("TLS file could not be read: {1}")]
    ReadFile(#[source] io::Error, String),
    #[error("general TLS IO error")]
    Io(#[source] io::Error),
    #[error("no server certificate found")]
    NoServerCert,
    #[error("no private key found")]
    NoPrivateKey,
    #[error("invalid private key")]
    InvalidPrivateKey,
    #[error("TLS signing error")]
    Sign(#[source] rustls::Error),
    #[error("client certificate verification")]
    ClientCertVerifier(#[source] VerifierBuilderError),
}