ferriskey 0.3.0

Rust client for Valkey, built for FlowFabric. Forked from glide-core (valkey-glide).
Documentation
use rustls::RootCertStore;
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{CertificateDer, PrivateKeyDer};

use crate::value::Result;

/// Structure to hold mTLS client _certificate_ and _key_ binaries in PEM format
///
#[derive(Clone)]
pub struct ClientTlsConfig {
    /// client certificate byte stream in PEM format
    pub client_cert: Vec<u8>,
    /// client key byte stream in PEM format
    pub client_key: Vec<u8>,
}

/// Structure to hold TLS certificates
/// - `client_tls`: binaries of clientkey and certificate within a `ClientTlsConfig` structure if mTLS is used
/// - `root_cert`: binary CA certificate in PEM format if CA is not in local truststore
///
#[derive(Clone)]
pub struct TlsCertificates {
    /// 'ClientTlsConfig' containing client certificate and key if mTLS is to be used
    pub client_tls: Option<ClientTlsConfig>,
    /// root certificate byte stream in PEM format if the local truststore is *not* to be used
    pub root_cert: Option<Vec<u8>>,
}

/// Retrieve TLS connection parameters from certificates.
///
/// Parses the provided TLS certificates and returns connection parameters
/// that can be used to establish secure connections.
pub fn retrieve_tls_certificates(certificates: TlsCertificates) -> Result<TlsConnParams> {
    let TlsCertificates {
        client_tls,
        root_cert,
    } = certificates;

    let client_tls_params = if let Some(ClientTlsConfig {
        client_cert,
        client_key,
    }) = client_tls
    {
        // Parse certificates using rustls-pki-types v1.9.0+ API
        let certs = CertificateDer::pem_slice_iter(&client_cert);
        let client_cert_chain = certs
            .collect::<std::result::Result<Vec<_>, _>>()
            .map_err(|e| std::io::Error::other(format!("Failed to parse certificate: {}", e)))?;

        // Parse private key using rustls-pki-types v1.9.0+ API
        let client_key = PrivateKeyDer::from_pem_slice(&client_key)
            .map_err(|e| std::io::Error::other(format!("Failed to parse private key: {}", e)))?;

        Some(ClientTlsParams {
            client_cert_chain,
            client_key,
        })
    } else {
        None
    };

    let root_cert_store = if let Some(root_cert) = root_cert {
        // Parse root certificates using rustls-pki-types v1.9.0+ API
        let certs = CertificateDer::pem_slice_iter(&root_cert);
        let mut root_cert_store = RootCertStore::empty();
        for result in certs {
            let cert = result
                .map_err(|e| std::io::Error::other(format!("Failed to parse root certificate: {}", e)))?;
            if root_cert_store.add(cert.to_owned()).is_err() {
                return Err(std::io::Error::other("Unable to parse TLS trust anchors").into());
            }
        }

        Some(root_cert_store)
    } else {
        None
    };

    Ok(TlsConnParams {
        client_tls_params,
        root_cert_store,
    })
}

#[derive(Debug)]
pub struct ClientTlsParams {
    pub(crate) client_cert_chain: Vec<CertificateDer<'static>>,
    pub(crate) client_key: PrivateKeyDer<'static>,
}

/// [`PrivateKeyDer`] does not implement `Clone` so we need to implement it manually.
impl Clone for ClientTlsParams {
    fn clone(&self) -> Self {
        Self {
            client_cert_chain: self.client_cert_chain.clone(),
            client_key: self.client_key.clone_key(),
        }
    }
}

/// TLS connection parameters containing client certificates and root certificate store.
#[derive(Debug, Clone)]
pub struct TlsConnParams {
    pub(crate) client_tls_params: Option<ClientTlsParams>,
    pub(crate) root_cert_store: Option<RootCertStore>,
}