use figment::value::magic::{Either, RelativePathBuf};
use serde::{Deserialize, Serialize};
use indexmap::IndexSet;
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
pub struct TlsConfig {
pub(crate) certs: Either<RelativePathBuf, Vec<u8>>,
pub(crate) key: Either<RelativePathBuf, Vec<u8>>,
#[serde(default = "CipherSuite::default_set")]
pub(crate) ciphers: IndexSet<CipherSuite>,
#[serde(default)]
pub(crate) prefer_server_cipher_order: bool,
#[serde(default)]
#[cfg(feature = "mtls")]
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
pub(crate) mutual: Option<MutualTls>,
}
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
#[cfg(feature = "mtls")]
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
pub struct MutualTls {
pub(crate) ca_certs: Either<RelativePathBuf, Vec<u8>>,
#[serde(default)]
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
pub mandatory: bool,
}
#[allow(non_camel_case_types)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash, Deserialize, Serialize)]
#[cfg_attr(nightly, doc(cfg(feature = "tls")))]
#[non_exhaustive]
pub enum CipherSuite {
TLS_CHACHA20_POLY1305_SHA256,
TLS_AES_256_GCM_SHA384,
TLS_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}
impl CipherSuite {
pub const DEFAULT_SET: [CipherSuite; 9] = [
CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_AES_256_GCM_SHA384,
CipherSuite::TLS_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
];
pub const TLS_V13_SET: [CipherSuite; 3] = [
CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_AES_256_GCM_SHA384,
CipherSuite::TLS_AES_128_GCM_SHA256,
];
pub const TLS_V12_SET: [CipherSuite; 6] = [
CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
];
fn default_set() -> IndexSet<Self> {
Self::DEFAULT_SET.iter().copied().collect()
}
}
impl TlsConfig {
fn default() -> Self {
TlsConfig {
certs: Either::Right(vec![]),
key: Either::Right(vec![]),
ciphers: CipherSuite::default_set(),
prefer_server_cipher_order: false,
#[cfg(feature = "mtls")]
mutual: None,
}
}
pub fn from_paths<C, K>(certs: C, key: K) -> Self
where C: AsRef<std::path::Path>, K: AsRef<std::path::Path>
{
TlsConfig {
certs: Either::Left(certs.as_ref().to_path_buf().into()),
key: Either::Left(key.as_ref().to_path_buf().into()),
..TlsConfig::default()
}
}
pub fn from_bytes(certs: &[u8], key: &[u8]) -> Self {
TlsConfig {
certs: Either::Right(certs.to_vec()),
key: Either::Right(key.to_vec()),
..TlsConfig::default()
}
}
pub fn with_ciphers<I>(mut self, ciphers: I) -> Self
where I: IntoIterator<Item = CipherSuite>
{
self.ciphers = ciphers.into_iter().collect();
self
}
pub fn with_preferred_server_cipher_order(mut self, prefer_server_order: bool) -> Self {
self.prefer_server_cipher_order = prefer_server_order;
self
}
#[cfg(feature = "mtls")]
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
pub fn with_mutual(mut self, config: MutualTls) -> Self {
self.mutual = Some(config);
self
}
pub fn certs(&self) -> either::Either<std::path::PathBuf, &[u8]> {
match &self.certs {
Either::Left(path) => either::Either::Left(path.relative()),
Either::Right(bytes) => either::Either::Right(&bytes),
}
}
pub fn key(&self) -> either::Either<std::path::PathBuf, &[u8]> {
match &self.key {
Either::Left(path) => either::Either::Left(path.relative()),
Either::Right(bytes) => either::Either::Right(&bytes),
}
}
pub fn ciphers(&self) -> impl Iterator<Item = CipherSuite> + '_ {
self.ciphers.iter().copied()
}
pub fn prefer_server_cipher_order(&self) -> bool {
self.prefer_server_cipher_order
}
#[cfg(feature = "mtls")]
#[cfg_attr(nightly, doc(cfg(feature = "mtls")))]
pub fn mutual(&self) -> Option<&MutualTls> {
self.mutual.as_ref()
}
}
#[cfg(feature = "mtls")]
impl MutualTls {
pub fn from_path<C: AsRef<std::path::Path>>(ca_certs: C) -> Self {
MutualTls {
ca_certs: Either::Left(ca_certs.as_ref().to_path_buf().into()),
mandatory: Default::default()
}
}
pub fn from_bytes(ca_certs: &[u8]) -> Self {
MutualTls {
ca_certs: Either::Right(ca_certs.to_vec()),
mandatory: Default::default()
}
}
pub fn mandatory(mut self, mandatory: bool) -> Self {
self.mandatory = mandatory;
self
}
pub fn ca_certs(&self) -> either::Either<std::path::PathBuf, &[u8]> {
match &self.ca_certs {
Either::Left(path) => either::Either::Left(path.relative()),
Either::Right(bytes) => either::Either::Right(&bytes),
}
}
}
#[cfg(feature = "tls")]
mod with_tls_feature {
use std::fs;
use std::io::{self, Error};
use crate::http::tls::Config;
use crate::http::tls::rustls::SupportedCipherSuite as RustlsCipher;
use crate::http::tls::rustls::cipher_suite;
use yansi::Paint;
use super::{Either, RelativePathBuf, TlsConfig, CipherSuite};
type Reader = Box<dyn std::io::BufRead + Sync + Send>;
fn to_reader(value: &Either<RelativePathBuf, Vec<u8>>) -> io::Result<Reader> {
match value {
Either::Left(path) => {
let path = path.relative();
let file = fs::File::open(&path).map_err(move |e| {
let source = figment::Source::File(path);
let msg = format!("error reading TLS file `{}`: {}", source.primary(), e);
Error::new(e.kind(), msg)
})?;
Ok(Box::new(io::BufReader::new(file)))
}
Either::Right(vec) => Ok(Box::new(io::Cursor::new(vec.clone()))),
}
}
impl TlsConfig {
pub(crate) fn to_native_config(&self) -> io::Result<Config<Reader>> {
Ok(Config {
cert_chain: to_reader(&self.certs)?,
private_key: to_reader(&self.key)?,
ciphersuites: self.rustls_ciphers().collect(),
prefer_server_order: self.prefer_server_cipher_order,
#[cfg(not(feature = "mtls"))]
mandatory_mtls: false,
#[cfg(not(feature = "mtls"))]
ca_certs: None,
#[cfg(feature = "mtls")]
mandatory_mtls: self.mutual.as_ref().map_or(false, |m| m.mandatory),
#[cfg(feature = "mtls")]
ca_certs: match self.mutual {
Some(ref mtls) => Some(to_reader(&mtls.ca_certs)?),
None => None
},
})
}
fn rustls_ciphers(&self) -> impl Iterator<Item = RustlsCipher> + '_ {
self.ciphers().map(|ciphersuite| match ciphersuite {
CipherSuite::TLS_CHACHA20_POLY1305_SHA256 =>
cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_AES_256_GCM_SHA384 =>
cipher_suite::TLS13_AES_256_GCM_SHA384,
CipherSuite::TLS_AES_128_GCM_SHA256 =>
cipher_suite::TLS13_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 =>
cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 =>
cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 =>
cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 =>
cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 =>
cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 =>
cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
})
}
}
}