Spaces:
Build error
Build error
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 | |
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()) | |
} | |
} | |
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. | |
pub enum Error { | |
OpenFile( io::Error, String), | |
ReadFile( io::Error, String), | |
Io( io::Error), | |
NoServerCert, | |
NoPrivateKey, | |
InvalidPrivateKey, | |
Sign( rustls::Error), | |
ClientCertVerifier( VerifierBuilderError), | |
} | |