rustls-mitm 0.1.0

SNI-based certificate resolver for rustls that generates per-host TLS certificates on the fly, enabling man-in-the-middle TLS interception
Documentation
use rcgen::{CertificateParams, IsCa, KeyPair, KeyUsagePurpose};
use rustls::pki_types::{CertificateDer, PrivateKeyDer};

use crate::Error;

/// Wraps a CA certificate and key pair used to sign per-host certificates.
pub struct CertificateAuthority {
    pub(crate) cert: rcgen::Certificate,
    pub(crate) key: KeyPair,
}

impl CertificateAuthority {
    /// Create from PEM-encoded strings.
    pub fn from_pem(cert_pem: &str, key_pem: &str) -> Result<Self, Error> {
        let key = KeyPair::from_pem(key_pem)?;
        let params = CertificateParams::from_ca_cert_pem(cert_pem)?;
        let cert = params.self_signed(&key)?;
        Ok(Self { cert, key })
    }

    /// Create from PEM files on disk.
    pub fn from_pem_files(
        cert_path: impl AsRef<std::path::Path>,
        key_path: impl AsRef<std::path::Path>,
    ) -> Result<Self, Error> {
        let cert_pem = std::fs::read_to_string(cert_path)?;
        let key_pem = std::fs::read_to_string(key_path)?;
        Self::from_pem(&cert_pem, &key_pem)
    }

    /// Generate a new self-signed CA certificate and key pair with CN `"MITM CA"`.
    pub fn generate() -> Result<Self, Error> {
        Self::generate_with_cn("MITM CA")
    }

    /// Generate a new self-signed CA certificate and key pair with the given common name.
    pub fn generate_with_cn(cn: &str) -> Result<Self, Error> {
        let mut params = CertificateParams::default();
        params.is_ca = IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
        params
            .distinguished_name
            .push(rcgen::DnType::CommonName, cn);
        params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign];

        let key = KeyPair::generate()?;
        let cert = params.self_signed(&key)?;
        Ok(Self { cert, key })
    }

    /// Write the CA certificate and key as PEM files to disk.
    pub fn to_pem_files(
        &self,
        cert_path: impl AsRef<std::path::Path>,
        key_path: impl AsRef<std::path::Path>,
    ) -> Result<(), Error> {
        std::fs::write(cert_path, self.cert.pem())?;
        std::fs::write(key_path, self.key.serialize_pem())?;
        Ok(())
    }

    /// Return the CA certificate in PEM format.
    pub fn cert_pem(&self) -> String {
        self.cert.pem()
    }

    /// Return the CA private key in PEM format.
    pub fn key_pem(&self) -> String {
        self.key.serialize_pem()
    }

    /// Generate a leaf certificate for the given hostname, signed by this CA.
    pub fn generate_cert(
        &self,
        hostname: &str,
    ) -> Result<(CertificateDer<'static>, PrivateKeyDer<'static>), Error> {
        let mut params = CertificateParams::new(vec![hostname.to_string()])?;
        params.is_ca = IsCa::NoCa;
        params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
        params
            .distinguished_name
            .push(rcgen::DnType::CommonName, hostname);

        let key_pair = KeyPair::generate()?;
        let key_der = PrivateKeyDer::Pkcs8(key_pair.serialized_der().to_vec().into());
        let cert = params.signed_by(&key_pair, &self.cert, &self.key)?;
        let cert_der = cert.der().clone();

        Ok((cert_der, key_der))
    }
}