use std::sync::Arc;
use irontide_wire::ssl::{self, SslConfig};
use crate::settings::Settings;
pub(crate) struct SslManager {
our_cert_pem: Vec<u8>,
our_key_pem: Vec<u8>,
}
impl SslManager {
pub fn new(settings: &Settings) -> crate::Result<Self> {
let (cert_pem, key_pem) = if let (Some(cert_path), Some(key_path)) =
(&settings.ssl_cert_path, &settings.ssl_key_path)
{
let cert = std::fs::read(cert_path).map_err(|e| {
crate::Error::Config(format!(
"failed to read SSL cert {}: {e}",
cert_path.display()
))
})?;
let key = std::fs::read(key_path).map_err(|e| {
crate::Error::Config(format!(
"failed to read SSL key {}: {e}",
key_path.display()
))
})?;
(cert, key)
} else {
let (cert, key) = ssl::generate_self_signed_cert()
.map_err(|e| crate::Error::Config(format!("SSL cert generation: {e}")))?;
if let Some(ref dir) = settings.resume_data_dir {
let cert_path = dir.join("ssl_cert.pem");
let key_path = dir.join("ssl_key.pem");
let _ = std::fs::create_dir_all(dir);
let _ = std::fs::write(&cert_path, &cert);
let _ = std::fs::write(&key_path, &key);
}
(cert, key)
};
Ok(Self {
our_cert_pem: cert_pem,
our_key_pem: key_pem,
})
}
pub fn from_pem(cert_pem: Vec<u8>, key_pem: Vec<u8>) -> Self {
Self {
our_cert_pem: cert_pem,
our_key_pem: key_pem,
}
}
pub fn config_for_torrent(&self, ca_cert_pem: &[u8]) -> SslConfig {
SslConfig {
ca_cert_pem: ca_cert_pem.to_vec(),
our_cert_pem: self.our_cert_pem.clone(),
our_key_pem: self.our_key_pem.clone(),
}
}
pub fn client_config(
&self,
ca_cert_pem: &[u8],
) -> Result<Arc<rustls::ClientConfig>, irontide_wire::Error> {
let config = self.config_for_torrent(ca_cert_pem);
ssl::build_client_config(&config)
}
pub fn server_config(
&self,
ca_cert_pem: &[u8],
) -> Result<Arc<rustls::ServerConfig>, irontide_wire::Error> {
let config = self.config_for_torrent(ca_cert_pem);
ssl::build_server_config(&config)
}
pub fn cert_pem(&self) -> &[u8] {
&self.our_cert_pem
}
pub fn key_pem(&self) -> &[u8] {
&self.our_key_pem
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ssl_manager_generates_cert() {
let settings = Settings::default();
let mgr = SslManager::new(&settings).unwrap();
assert!(!mgr.cert_pem().is_empty());
assert!(!mgr.key_pem().is_empty());
assert!(mgr.cert_pem().starts_with(b"-----BEGIN CERTIFICATE-----"));
assert!(
mgr.key_pem().starts_with(b"-----BEGIN PRIVATE KEY-----")
|| mgr
.key_pem()
.starts_with(b"-----BEGIN RSA PRIVATE KEY-----")
|| mgr.key_pem().starts_with(b"-----BEGIN EC PRIVATE KEY-----")
);
}
#[test]
fn ssl_manager_builds_config() {
let settings = Settings::default();
let mgr = SslManager::new(&settings).unwrap();
let ca_cert = mgr.cert_pem().to_vec();
let client_cfg = mgr.client_config(&ca_cert).unwrap();
assert!(Arc::strong_count(&client_cfg) >= 1);
let server_cfg = mgr.server_config(&ca_cert).unwrap();
assert!(Arc::strong_count(&server_cfg) >= 1);
let ssl_config = mgr.config_for_torrent(&ca_cert);
assert_eq!(ssl_config.ca_cert_pem, ca_cert);
assert_eq!(ssl_config.our_cert_pem, mgr.cert_pem());
assert_eq!(ssl_config.our_key_pem, mgr.key_pem());
}
#[test]
fn ssl_manager_from_pem_round_trip() {
let settings = Settings::default();
let original = SslManager::new(&settings).unwrap();
let restored =
SslManager::from_pem(original.cert_pem().to_vec(), original.key_pem().to_vec());
assert_eq!(restored.cert_pem(), original.cert_pem());
assert_eq!(restored.key_pem(), original.key_pem());
let ca_cert = restored.cert_pem().to_vec();
restored.client_config(&ca_cert).unwrap();
restored.server_config(&ca_cert).unwrap();
}
}