torrust-actix 4.1.2

A rich, fast and efficient Bittorrent Tracker.
use super::certificate_store::{CertificateBundle, CertificateError, CertificateStore, ServerIdentifier};
use parking_lot::RwLock;
use rustls::server::{ClientHello, ResolvesServerCert};
use rustls::sign::CertifiedKey;
use std::sync::Arc;

pub struct DynamicCertificateResolver {
    store: Arc<CertificateStore>,
    server_id: ServerIdentifier,
    cached_key: RwLock<Option<Arc<CertifiedKey>>>,
}

impl std::fmt::Debug for DynamicCertificateResolver {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DynamicCertificateResolver")
            .field("server_id", &self.server_id)
            .field("has_cached_key", &self.cached_key.read().is_some())
            .finish()
    }
}

impl DynamicCertificateResolver {
    pub fn new(
        store: Arc<CertificateStore>,
        server_id: ServerIdentifier,
    ) -> Result<Self, CertificateError> {
        let resolver = Self {
            store,
            server_id,
            cached_key: RwLock::new(None),
        };
        resolver.refresh_cache()?;
        Ok(resolver)
    }

    pub fn server_id(&self) -> &ServerIdentifier {
        &self.server_id
    }

    pub fn refresh_cache(&self) -> Result<(), CertificateError> {
        let bundle = self
            .store
            .get_certificate(&self.server_id)
            .ok_or_else(|| CertificateError::ServerNotFound(self.server_id.clone()))?;
        let certified_key = Self::bundle_to_certified_key(&bundle)?;
        *self.cached_key.write() = Some(Arc::new(certified_key));
        log::info!(
            "[CERTIFICATE] Refreshed certificate cache for {}",
            self.server_id
        );
        Ok(())
    }

    pub fn has_certificate(&self) -> bool {
        self.cached_key.read().is_some()
    }

    fn bundle_to_certified_key(bundle: &Arc<CertificateBundle>) -> Result<CertifiedKey, CertificateError> {
        let signing_key = rustls::crypto::ring::sign::any_supported_type(&bundle.key)
            .map_err(|e| CertificateError::CertifiedKeyError(format!("{}", e)))?;
        Ok(CertifiedKey::new(bundle.certs.clone(), signing_key))
    }
}

impl ResolvesServerCert for DynamicCertificateResolver {
    fn resolve(&self, _client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
        self.cached_key.read().clone()
    }
}

pub fn create_server_config_with_resolver(
    resolver: Arc<DynamicCertificateResolver>,
) -> rustls::ServerConfig {
    rustls::ServerConfig::builder()
        .with_no_client_auth()
        .with_cert_resolver(resolver)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ssl::certificate_store::create_certificate_store;

    #[test]
    fn test_resolver_creation_without_cert() {
        let store = create_certificate_store();
        let server_id = ServerIdentifier::HttpTracker("0.0.0.0:443".to_string());
        let result = DynamicCertificateResolver::new(store, server_id);
        assert!(result.is_err());
    }

    #[test]
    fn test_server_identifier_methods() {
        let server_id = ServerIdentifier::ApiServer("0.0.0.0:8443".to_string());
        assert_eq!(server_id.bind_address(), "0.0.0.0:8443");
        assert_eq!(server_id.server_type(), "api");
    }
}