isahc 2.0.0

The practical HTTP client that is fun to use.
Documentation
use super::PathOrBlob;
use crate::{
    blob::Blob,
    config::setopt::{EasyHandle, SetOpt, SetOptError, SetOptProxy},
    handler::BlobOptions,
};
use curl_sys::{
    CURLOPT_PROXY_SSLCERT_BLOB, CURLOPT_PROXY_SSLKEY_BLOB, CURLOPT_SSLCERT_BLOB,
    CURLOPT_SSLKEY_BLOB,
};
use std::path::PathBuf;

/// A cryptographic identity used to authenticate the client with a server.
///
/// Holds a X.509 certificate along with potentially other certificates in its
/// chain of trust and a corresponding private key. This collection of
/// certificates is used to authenticate the client to the server if the server
/// requests client authentication during the SSL/TLS handshake. This process is
/// also known as *mutual TLS* (mTLS).
#[derive(Clone, Debug)]
pub struct Identity {
    /// The format of the client certificate.
    format: CertFormat,

    /// The certificate data, either a path or a blob.
    data: PathOrBlob,

    /// Private key corresponding to the SSL/TLS certificate.
    private_key: Option<PrivateKey>,

    /// Password to decrypt the certificate file.
    password: Option<String>,
}

impl Identity {
    /// Use a PEM-encoded certificate stored in the given byte buffer.
    ///
    /// The certificate object takes ownership of the byte buffer. If a borrowed
    /// type is supplied, such as `&[u8]`, then the bytes will be copied.
    ///
    /// The certificate is not parsed or validated here. If the certificate is
    /// malformed or the format is not supported by the underlying SSL/TLS
    /// engine, an error will be returned when attempting to send a request
    /// using the offending certificate.
    pub fn from_pem<B>(bytes: B, private_key: Option<PrivateKey>) -> Self
    where
        B: AsRef<[u8]> + Send + Sync + 'static,
    {
        Self {
            format: CertFormat::Pem,
            data: PathOrBlob::Blob(Blob::new(bytes)),
            private_key,
            password: None,
        }
    }

    /// Use a DER-encoded certificate stored in the given byte buffer.
    ///
    /// The certificate object takes ownership of the byte buffer. If a borrowed
    /// type is supplied, such as `&[u8]`, then the bytes will be copied.
    ///
    /// The certificate is not parsed or validated here. If the certificate is
    /// malformed or the format is not supported by the underlying SSL/TLS
    /// engine, an error will be returned when attempting to send a request
    /// using the offending certificate.
    pub fn from_der<B>(bytes: B, private_key: Option<PrivateKey>) -> Self
    where
        B: AsRef<[u8]> + Send + Sync + 'static,
    {
        Self {
            format: CertFormat::Der,
            data: PathOrBlob::Blob(Blob::new(bytes)),
            private_key,
            password: None,
        }
    }

    /// Use a certificate and private key from a PKCS #12 archive stored in the
    /// given byte buffer.
    ///
    /// The certificate object takes ownership of the byte buffer. If a borrowed
    /// type is supplied, such as `&[u8]`, then the bytes will be copied.
    ///
    /// The certificate is not parsed or validated here. If the certificate is
    /// malformed or the format is not supported by the underlying SSL/TLS
    /// engine, an error will be returned when attempting to send a request
    /// using the offending certificate.
    pub fn from_pkcs12<B>(bytes: B, password: Option<String>) -> Self
    where
        B: AsRef<[u8]> + Send + Sync + 'static,
    {
        Self {
            format: CertFormat::Pkcs12,
            data: PathOrBlob::Blob(Blob::new(bytes)),
            private_key: None,
            password,
        }
    }

    /// Get a certificate from a PEM-encoded file.
    ///
    /// The certificate file is not loaded or validated here. If the file does
    /// not exist or the format is not supported by the underlying SSL/TLS
    /// engine, an error will be returned when attempting to send a request
    /// using the offending certificate.
    pub fn from_pem_file<P>(path: P, private_key: Option<PrivateKey>) -> Self
    where
        P: Into<PathBuf>,
    {
        Self {
            format: CertFormat::Pem,
            data: PathOrBlob::Path(path.into()),
            private_key,
            password: None,
        }
    }

    /// Get a certificate from a DER-encoded file.
    ///
    /// The certificate file is not loaded or validated here. If the file does
    /// not exist or the format is not supported by the underlying SSL/TLS
    /// engine, an error will be returned when attempting to send a request
    /// using the offending certificate.
    pub fn from_der_file<P>(path: P, private_key: Option<PrivateKey>) -> Self
    where
        P: Into<PathBuf>,
    {
        Self {
            format: CertFormat::Der,
            data: PathOrBlob::Path(path.into()),
            private_key,
            password: None,
        }
    }

    /// Get a certificate and private key from a PKCS #12-encoded file.
    ///
    /// The certificate file is not loaded or validated here. If the file does
    /// not exist or the format is not supported by the underlying SSL/TLS
    /// engine, an error will be returned when attempting to send a request
    /// using the offending certificate.
    pub fn from_pkcs12_file<P>(path: P, password: Option<String>) -> Self
    where
        P: Into<PathBuf>,
    {
        Self {
            format: CertFormat::Pkcs12,
            data: PathOrBlob::Path(path.into()),
            private_key: None,
            password,
        }
    }
}

impl SetOpt for Identity {
    fn set_opt(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
        easy.ssl_cert_type(self.format.as_str())?;

        match &self.data {
            PathOrBlob::Path(path) => easy.ssl_cert(path.as_path()),
            PathOrBlob::Blob(bytes) => unsafe {
                easy.setopt_blob_nocopy(CURLOPT_SSLCERT_BLOB, bytes)
            },
        }?;

        if let Some(key) = self.private_key.as_ref() {
            key.set_opt(easy)?;
        }

        if let Some(password) = self.password.as_ref() {
            easy.key_password(password)?;
        }

        Ok(())
    }
}

impl SetOptProxy for Identity {
    fn set_opt_proxy(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
        easy.proxy_sslcert_type(self.format.as_str())?;

        match &self.data {
            PathOrBlob::Path(path) => easy.proxy_sslcert(path.to_str().unwrap()),
            PathOrBlob::Blob(bytes) => unsafe {
                easy.setopt_blob_nocopy(CURLOPT_PROXY_SSLCERT_BLOB, bytes)
            },
        }?;

        if let Some(key) = self.private_key.as_ref() {
            key.set_opt_proxy(easy)?;
        }

        if let Some(password) = self.password.as_ref() {
            easy.proxy_key_password(password)?;
        }

        Ok(())
    }
}

/// A private key file.
#[derive(Clone, Debug)]
pub struct PrivateKey {
    /// The format of the private key.
    format: CertFormat,

    /// The certificate data, either a path or a blob.
    data: PathOrBlob,

    /// Password to decrypt the key file.
    password: Option<String>,
}

impl PrivateKey {
    /// Use a PEM-encoded private key.
    ///
    /// The private key can be supplied as any type which can be referenced as
    /// contiguous bytes. This could be a simple [`String`], or a pre-parsed PEM
    /// object that allows access to its underlying bytes. The `PrivateKey`
    /// object takes ownership of the bytes regardless.
    ///
    /// The key is not parsed or validated here. If the key is malformed or the
    /// format is not supported by the underlying SSL/TLS engine, an error will
    /// be returned when attempting to send a request using the offending key.
    pub fn from_pem<B, P>(bytes: B, password: P) -> Self
    where
        B: AsRef<[u8]> + Send + Sync + 'static,
        P: Into<Option<String>>,
    {
        Self {
            format: CertFormat::Pem,
            data: PathOrBlob::Blob(Blob::new(bytes)),
            password: password.into(),
        }
    }

    /// Use a DER-encoded private key.
    ///
    /// The private key can be supplied as any type which can be referenced as
    /// contiguous bytes. This could be a simple `Vec<u8>`, or a pre-parsed DER
    /// object that allows access to its underlying bytes. The `PrivateKey`
    /// object takes ownership of the bytes regardless.
    ///
    /// The key is not parsed or validated here. If the key is malformed or the
    /// format is not supported by the underlying SSL/TLS engine, an error will
    /// be returned when attempting to send a request using the offending key.
    pub fn from_der<B, P>(bytes: B, password: P) -> Self
    where
        B: AsRef<[u8]> + Send + Sync + 'static,
        P: Into<Option<String>>,
    {
        Self {
            format: CertFormat::Der,
            data: PathOrBlob::Blob(Blob::new(bytes)),
            password: password.into(),
        }
    }

    /// Get a PEM-encoded private key file.
    ///
    /// The key file is not loaded or validated here. If the file does not exist
    /// or the format is not supported by the underlying SSL/TLS engine, an
    /// error will be returned when attempting to send a request using the
    /// offending key.
    pub fn from_pem_file(path: impl Into<PathBuf>, password: impl Into<Option<String>>) -> Self {
        Self {
            format: CertFormat::Pem,
            data: PathOrBlob::Path(path.into()),
            password: password.into(),
        }
    }

    /// Get a DER-encoded private key file.
    ///
    /// The key file is not loaded or validated here. If the file does not exist
    /// or the format is not supported by the underlying SSL/TLS engine, an
    /// error will be returned when attempting to send a request using the
    /// offending key.
    pub fn from_der_file(path: impl Into<PathBuf>, password: impl Into<Option<String>>) -> Self {
        Self {
            format: CertFormat::Der,
            data: PathOrBlob::Path(path.into()),
            password: password.into(),
        }
    }
}

impl SetOpt for PrivateKey {
    fn set_opt(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
        easy.ssl_key_type(self.format.as_str())?;

        match &self.data {
            PathOrBlob::Path(path) => easy.ssl_key(path.as_path()),
            PathOrBlob::Blob(blob) => unsafe { easy.setopt_blob_nocopy(CURLOPT_SSLKEY_BLOB, blob) },
        }?;

        if let Some(password) = self.password.as_ref() {
            easy.key_password(password)?;
        }

        Ok(())
    }
}

impl SetOptProxy for PrivateKey {
    fn set_opt_proxy(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
        easy.proxy_sslkey_type(self.format.as_str())?;

        match &self.data {
            PathOrBlob::Path(path) => easy.proxy_sslkey(path.to_str().unwrap()),
            PathOrBlob::Blob(blob) => unsafe {
                easy.setopt_blob_nocopy(CURLOPT_PROXY_SSLKEY_BLOB, blob)
            },
        }?;

        if let Some(password) = self.password.as_ref() {
            easy.proxy_key_password(password)?;
        }

        Ok(())
    }
}

/// Possible formats for certificates supported by curl.
#[derive(Clone, Copy, Debug)]
enum CertFormat {
    Pem,
    Der,
    Pkcs12,
}

impl CertFormat {
    fn as_str(&self) -> &'static str {
        match self {
            CertFormat::Pem => "PEM",
            CertFormat::Der => "DER",
            CertFormat::Pkcs12 => "P12",
        }
    }
}