use crate::{
blob::Blob,
config::setopt::{EasyHandle, SetOpt, SetOptError, SetOptProxy},
error::{Error, ErrorKind},
info::curl_info,
};
use curl::easy::{SslOpt, SslVersion};
use std::{fmt, path::PathBuf};
mod identity;
mod trust;
pub use self::{
identity::{Identity, PrivateKey},
trust::{TrustStore, issuer::Issuer},
};
#[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
compile_error!("`tls` feature is enabled, but no TLS backend was selected");
#[cfg(all(feature = "native-tls", feature = "rustls-tls"))]
compile_error!("multiple TLS engines cannot be enabled at the same time");
#[derive(Debug, Default)]
#[must_use = "builders have no effect if unused"]
pub struct TlsConfigBuilder {
trust_store: TrustStore,
issuer: Option<Issuer>,
identity: Option<Identity>,
ciphers: Option<String>,
min_version: Option<ProtocolVersion>,
max_version: Option<ProtocolVersion>,
danger_accept_invalid_certs: bool,
danger_accept_invalid_hosts: bool,
danger_accept_revoked_certs: bool,
}
impl TlsConfigBuilder {
pub fn trust_store(mut self, store: TrustStore) -> Self {
self.trust_store = store;
self
}
pub fn issuer(mut self, cert: Issuer) -> Self {
self.issuer = Some(cert);
self
}
pub fn identity(mut self, identity: Identity) -> Self {
self.identity = Some(identity);
self
}
pub fn min_version(mut self, version: ProtocolVersion) -> Self {
self.min_version = Some(version);
self
}
pub fn max_version(mut self, version: ProtocolVersion) -> Self {
self.max_version = Some(version);
self
}
pub fn ciphers<I, T>(mut self, ciphers: I) -> Self
where
I: IntoIterator<Item = T>,
T: AsRef<str>,
{
let mut iter = ciphers.into_iter();
if let Some(first) = iter.next() {
let mut ciphers = first.as_ref().to_owned();
for cipher in iter {
ciphers.push(':');
ciphers.push_str(cipher.as_ref());
}
self.ciphers = Some(ciphers);
} else {
self.ciphers = None;
}
self
}
#[cfg(feature = "tls-insecure")]
pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
self.danger_accept_invalid_certs = accept;
self
}
#[cfg(feature = "tls-insecure")]
pub fn danger_accept_invalid_hosts(mut self, accept: bool) -> Self {
self.danger_accept_invalid_hosts = accept;
self
}
#[cfg(feature = "tls-insecure")]
pub fn danger_accept_revoked_certs(mut self, accept: bool) -> Self {
self.danger_accept_revoked_certs = accept;
self
}
pub fn build(self) -> TlsConfig {
TlsConfig {
curl_flags: {
let mut options = SslOpt::new();
options.no_revoke(self.danger_accept_revoked_certs);
self.trust_store.configure_ssl_options(&mut options);
options
},
trust_store: self.trust_store,
issuer: self.issuer,
identity: self.identity,
ciphers: self.ciphers,
min_version: self
.min_version
.as_ref()
.map(ProtocolVersion::as_curl_ssl_version),
max_version: self
.max_version
.as_ref()
.map(ProtocolVersion::as_curl_ssl_version),
danger_accept_invalid_certs: self.danger_accept_invalid_certs,
danger_accept_invalid_hosts: self.danger_accept_invalid_hosts,
}
}
}
#[derive(Clone, Debug)]
pub struct TlsConfig {
trust_store: TrustStore,
issuer: Option<Issuer>,
identity: Option<Identity>,
ciphers: Option<String>,
min_version: Option<SslVersion>,
max_version: Option<SslVersion>,
danger_accept_invalid_certs: bool,
danger_accept_invalid_hosts: bool,
curl_flags: SslOpt,
}
impl TlsConfig {
pub fn new() -> Self {
Self::builder().build()
}
#[inline]
pub fn builder() -> TlsConfigBuilder {
TlsConfigBuilder::default()
}
}
impl Default for TlsConfig {
fn default() -> Self {
Self::new()
}
}
impl SetOpt for TlsConfig {
fn set_opt(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
if let Some(ciphers) = self.ciphers.as_ref() {
easy.ssl_cipher_list(ciphers)?;
}
easy.ssl_options(&self.curl_flags)?;
easy.ssl_verify_peer(!self.danger_accept_invalid_certs)?;
easy.ssl_verify_host(!self.danger_accept_invalid_hosts)?;
if TlsEngine::Rustls.is_available()
&& !matches!(
self.max_version,
Some(SslVersion::Tlsv12)
| Some(SslVersion::Tlsv13)
| Some(SslVersion::Default)
| None
)
{
return Err(Error::new(
ErrorKind::TlsEngine,
MaximumTlsVersionNotSupportedByEngineError {
max_requested: self.max_version.unwrap(),
engine: TlsEngine::Rustls,
},
)
.into());
}
easy.ssl_min_max_version(
self.min_version.unwrap_or(SslVersion::Default),
self.max_version.unwrap_or(SslVersion::Default),
)?;
self.trust_store.set_opt(easy)?;
if let Some(issuer) = self.issuer.as_ref() {
issuer.set_opt(easy)?;
}
if let Some(identity) = self.identity.as_ref() {
identity.set_opt(easy)?;
}
Ok(())
}
}
impl SetOptProxy for TlsConfig {
fn set_opt_proxy(&self, easy: &mut EasyHandle) -> Result<(), SetOptError> {
if let Some(ciphers) = self.ciphers.as_ref() {
easy.proxy_ssl_cipher_list(ciphers)?;
}
easy.proxy_ssl_options(&self.curl_flags)?;
easy.proxy_ssl_verify_peer(self.danger_accept_invalid_certs)?;
easy.proxy_ssl_verify_host(self.danger_accept_invalid_hosts)?;
easy.proxy_ssl_min_max_version(
self.min_version.unwrap_or(SslVersion::Default),
self.max_version.unwrap_or(SslVersion::Default),
)?;
self.trust_store.set_opt_proxy(easy)?;
if let Some(issuer) = self.issuer.as_ref() {
issuer.set_opt_proxy(easy)?;
}
if let Some(identity) = self.identity.as_ref() {
identity.set_opt_proxy(easy)?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ProtocolVersion {
Sslv2,
Sslv3,
Tlsv10,
Tlsv11,
Tlsv12,
Tlsv13,
}
impl ProtocolVersion {
const fn as_curl_ssl_version(&self) -> SslVersion {
match self {
Self::Sslv2 => SslVersion::Sslv2,
Self::Sslv3 => SslVersion::Sslv3,
Self::Tlsv10 => SslVersion::Tlsv10,
Self::Tlsv11 => SslVersion::Tlsv11,
Self::Tlsv12 => SslVersion::Tlsv12,
Self::Tlsv13 => SslVersion::Tlsv13,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
enum TlsEngine {
Rustls,
Schannel,
SecureTransport,
}
impl TlsEngine {
fn is_available(&self) -> bool {
if let Some(version) = curl_info().ssl_version() {
match self {
TlsEngine::Rustls => version.contains("rustls/"),
TlsEngine::Schannel => version.contains("Schannel"),
TlsEngine::SecureTransport => version.contains("SecureTransport"),
}
} else {
false
}
}
}
#[derive(Clone, Debug)]
struct MaximumTlsVersionNotSupportedByEngineError {
max_requested: SslVersion,
engine: TlsEngine,
}
impl std::error::Error for MaximumTlsVersionNotSupportedByEngineError {}
impl fmt::Display for MaximumTlsVersionNotSupportedByEngineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"TLS version {:?} is not supported by the {:?} TLS engine",
self.max_requested, self.engine
)
}
}
#[derive(Clone, Debug)]
enum PathOrBlob {
Path(PathBuf),
Blob(Blob),
}