synd-client 0.4.0

syndicationd API client
Documentation
use std::sync::Arc;

use rustls::{
    ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme,
    client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
    pki_types::{CertificateDer, ServerName, UnixTime, pem::PemObject as _},
};
use tokio_tungstenite::Connector;

use crate::SyndApiError;

#[derive(Clone, Debug, Default)]
pub(super) struct ClientTls {
    root_certificates: Vec<CertificateDer<'static>>,
    accept_invalid_certificates: bool,
}

impl ClientTls {
    pub(super) fn add_root_certificate_pem(&mut self, pem: &[u8]) -> Result<(), SyndApiError> {
        let certificates = CertificateDer::pem_slice_iter(pem)
            .collect::<Result<Vec<_>, _>>()
            .map_err(|error| SyndApiError::InvalidRootCertificate {
                message: error.to_string(),
            })?;
        if certificates.is_empty() {
            return Err(SyndApiError::InvalidRootCertificate {
                message: "PEM input does not contain a certificate".to_owned(),
            });
        }
        self.root_certificates.extend(certificates);
        Ok(())
    }

    pub(super) fn accept_invalid_certificates(&mut self, accept: bool) {
        self.accept_invalid_certificates = accept;
    }

    pub(super) fn configure_http(
        &self,
        mut builder: reqwest::ClientBuilder,
    ) -> Result<reqwest::ClientBuilder, SyndApiError> {
        for certificate in &self.root_certificates {
            let certificate = reqwest::Certificate::from_der(certificate.as_ref())
                .map_err(SyndApiError::BuildRequest)?;
            builder = builder.add_root_certificate(certificate);
        }
        Ok(builder.danger_accept_invalid_certs(self.accept_invalid_certificates))
    }

    pub(super) fn websocket_connector(&self) -> Result<Option<Connector>, SyndApiError> {
        if self.root_certificates.is_empty() && !self.accept_invalid_certificates {
            return Ok(None);
        }

        let provider = rustls::crypto::ring::default_provider();
        let supported_schemes = provider
            .signature_verification_algorithms
            .supported_schemes();
        let mut roots = RootCertStore::empty();
        roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
        for certificate in &self.root_certificates {
            roots.add(certificate.clone()).map_err(|error| {
                SyndApiError::InvalidRootCertificate {
                    message: error.to_string(),
                }
            })?;
        }

        let mut config = ClientConfig::builder_with_provider(Arc::new(provider))
            .with_safe_default_protocol_versions()
            .map_err(|error| SyndApiError::TlsConfiguration {
                message: error.to_string(),
            })?
            .with_root_certificates(roots)
            .with_no_client_auth();
        if self.accept_invalid_certificates {
            config
                .dangerous()
                .set_certificate_verifier(Arc::new(NoCertificateVerification {
                    supported_schemes,
                }));
        }

        Ok(Some(Connector::Rustls(Arc::new(config))))
    }
}

#[derive(Debug)]
struct NoCertificateVerification {
    supported_schemes: Vec<SignatureScheme>,
}

impl ServerCertVerifier for NoCertificateVerification {
    fn verify_server_cert(
        &self,
        _end_entity: &CertificateDer<'_>,
        _intermediates: &[CertificateDer<'_>],
        _server_name: &ServerName<'_>,
        _ocsp_response: &[u8],
        _now: UnixTime,
    ) -> Result<ServerCertVerified, rustls::Error> {
        Ok(ServerCertVerified::assertion())
    }

    fn verify_tls12_signature(
        &self,
        _message: &[u8],
        _certificate: &CertificateDer<'_>,
        _signature: &DigitallySignedStruct,
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
        Ok(HandshakeSignatureValid::assertion())
    }

    fn verify_tls13_signature(
        &self,
        _message: &[u8],
        _certificate: &CertificateDer<'_>,
        _signature: &DigitallySignedStruct,
    ) -> Result<HandshakeSignatureValid, rustls::Error> {
        Ok(HandshakeSignatureValid::assertion())
    }

    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
        self.supported_schemes.clone()
    }
}