use super::TlsEngine;
use crate::{
blob::Blob,
config::setopt::{EasyHandle, SetOpt, SetOptError, SetOptProxy},
error::{Error, ErrorKind},
handler::BlobOptions,
info::curl_version,
};
use curl::easy::SslOpt;
use curl_sys::{
CURLOPT_CAINFO, CURLOPT_CAINFO_BLOB, CURLOPT_CAPATH, CURLOPT_PROXY_CAINFO,
CURLOPT_PROXY_CAINFO_BLOB, CURLOPT_PROXY_CAPATH,
};
use std::{env, fmt, os::raw::c_char, path::PathBuf, ptr, sync::LazyLock};
pub(super) mod issuer;
#[cfg(feature = "trust-webpki-roots")]
mod webpki_roots;
#[derive(Clone)]
pub struct TrustStore(Repr);
#[derive(Clone)]
enum Repr {
NoOp,
Unset,
NativeCa,
FilePath(PathBuf),
PemBundle(Blob),
}
impl TrustStore {
pub fn native() -> Self {
static NATIVE_STORE: LazyLock<TrustStore> = LazyLock::new(TrustStore::new_native);
NATIVE_STORE.clone()
}
fn new_native() -> Self {
curl::init();
if let Some(path) = env::var_os("SSL_CERT_FILE") {
if !path.is_empty() {
tracing::debug!(
?path,
"using certificate bundle from SSL_CERT_FILE environment variable",
);
return TrustStore::from_file(path);
}
}
if TlsEngine::Rustls.is_available() && curl_version() >= (8, 13, 0) {
tracing::debug!("using platform verifier with rustls");
return TrustStore(Repr::NativeCa);
}
if TlsEngine::Schannel.is_available() || TlsEngine::SecureTransport.is_available() {
return TrustStore(Repr::Unset);
}
TrustStore(Repr::NoOp)
}
pub fn from_file<P: Into<PathBuf>>(path: P) -> Self {
Self(Repr::FilePath(path.into()))
}
pub fn builder() -> TrustStoreBuilder {
TrustStoreBuilder { pem: Vec::new() }
}
pub(super) fn configure_ssl_options(&self, ssl_opt: &mut SslOpt) {
if let Repr::NativeCa = &self.0 {
ssl_opt.native_ca(true);
}
}
}
impl Default for TrustStore {
#[cfg(feature = "rustls-tls-webpki-roots")]
fn default() -> Self {
Self::webpki_roots()
}
#[cfg(not(feature = "rustls-tls-webpki-roots"))]
fn default() -> Self {
Self::native()
}
}
#[derive(Clone, Debug)]
pub struct TrustStoreBuilder {
pem: Vec<u8>,
}
impl TrustStoreBuilder {
pub fn certificate_from_pem<B: AsRef<[u8]>>(mut self, pem: B) -> Self {
self.pem.extend_from_slice(pem.as_ref());
self
}
pub fn certificate_from_der<B: AsRef<[u8]>>(mut self, der: B) -> Self {
let der = der.as_ref();
let label = "CERTIFICATE";
let line_ending = Default::default();
let len = pem_rfc7468::encoded_len(label, line_ending, der).unwrap();
let existing_len = self.pem.len();
self.pem.resize(existing_len + len, 0);
pem_rfc7468::encode(label, line_ending, der, &mut self.pem[existing_len..]).unwrap();
self
}
pub fn build(self) -> TrustStore {
TrustStore(Repr::PemBundle(Blob::new(self.pem)))
}
}
impl SetOpt for TrustStore {
fn set_opt(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
match &self.0 {
Repr::NativeCa | Repr::NoOp => {}
Repr::FilePath(path) => {
easy.cainfo(path)?;
}
Repr::PemBundle(blob) => unsafe {
easy.setopt_blob_nocopy(CURLOPT_CAINFO_BLOB, blob)?;
},
Repr::Unset => {
unsafe {
curl_sys::curl_easy_setopt(
easy.raw(),
CURLOPT_CAINFO,
ptr::null_mut::<c_char>(),
);
curl_sys::curl_easy_setopt(
easy.raw(),
CURLOPT_CAPATH,
ptr::null_mut::<c_char>(),
);
}
}
};
Ok(())
}
}
impl SetOptProxy for TrustStore {
fn set_opt_proxy(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
match &self.0 {
Repr::NativeCa | Repr::NoOp => {}
Repr::FilePath(path) => {
if let Some(path) = path.to_str() {
easy.proxy_cainfo(path)?;
} else {
return Err(Error::new(
ErrorKind::InvalidTlsConfiguration,
CertificatePathNotUtf8Error { path: path.clone() },
)
.into());
}
}
Repr::PemBundle(blob) => unsafe {
easy.setopt_blob_nocopy(CURLOPT_PROXY_CAINFO_BLOB, blob)?;
},
Repr::Unset => {
unsafe {
curl_sys::curl_easy_setopt(
easy.raw(),
CURLOPT_PROXY_CAINFO,
ptr::null_mut::<c_char>(),
);
curl_sys::curl_easy_setopt(
easy.raw(),
CURLOPT_PROXY_CAPATH,
ptr::null_mut::<c_char>(),
);
}
}
};
Ok(())
}
}
impl fmt::Debug for TrustStore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Repr::FilePath(path) => f.debug_tuple("TrustStore::FilePath").field(path).finish(),
Repr::PemBundle { .. } => f.debug_tuple("TrustStore::PemBundle").finish(),
_ => f.debug_tuple("TrustStore").finish(),
}
}
}
#[derive(Clone, Debug)]
struct CertificatePathNotUtf8Error {
path: PathBuf,
}
impl std::error::Error for CertificatePathNotUtf8Error {}
impl fmt::Display for CertificatePathNotUtf8Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"certificate path is not valid UTF-8: {}",
self.path.display()
)
}
}