diff --git a/Cargo.toml b/Cargo.toml index c76584bd..3bd541fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ libc = "0.2" tempfile = "3.1.0" [target.'cfg(target_os = "windows")'.dependencies] -schannel = "0.1.16" +schannel = "0.1.19" [target.'cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))'.dependencies] log = "0.4.5" diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index ef254a3e..0068467c 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -11,13 +11,238 @@ use self::openssl::ssl::{ SslVerifyMode, }; use self::openssl::x509::{store::X509StoreBuilder, X509VerifyResult, X509}; +use std::borrow; +use std::collections::HashSet; use std::error; use std::fmt; use std::io; use std::sync::Once; use self::openssl::pkey::Private; -use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; +use { + CipherSuiteSet, Protocol, TlsAcceptorBuilder, TlsBulkEncryptionAlgorithm, TlsConnectorBuilder, + TlsHashAlgorithm, TlsKeyExchangeAlgorithm, TlsSignatureAlgorithm, +}; + +const CIPHER_STRING_SUFFIX: &[&str] = &[ + "!aNULL", + "!eNULL", + "!IDEA", + "!SEED", + "!SRP", + "!PSK", + "@STRENGTH", +]; + +fn cartesian_product( + xs: impl IntoIterator>, + ys: impl IntoIterator + Clone, +) -> Vec> { + xs.into_iter() + .flat_map(move |x| ys.clone().into_iter().map(move |y| [&x, &[y][..]].concat())) + .collect() +} + +/// AES-GCM ciphersuites aren't included in AES128 or AES256. However, specifying `AESGCM` in the +/// cipher string doesn't allow us to specify the bitwidth of the AES cipher used, nor does it +/// allow us to specify the bitwidth of the SHA algorithm. +fn expand_gcm_algorithms(cipher_suites: &CipherSuiteSet) -> Vec<&'static str> { + let first = cipher_suites + .key_exchange + .iter() + .flat_map(|alg| -> &[&str] { + match alg { + TlsKeyExchangeAlgorithm::Dhe => &[ + "DHE-RSA-AES128-GCM-SHA256", + "DHE-RSA-AES256-GCM-SHA384", + "DHE-DSS-AES128-GCM-SHA256", + "DHE-DSS-AES256-GCM-SHA384", + ], + TlsKeyExchangeAlgorithm::Ecdhe => &[ + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384", + ], + TlsKeyExchangeAlgorithm::Rsa => &["AES128-GCM-SHA256", "AES256-GCM-SHA384"], + TlsKeyExchangeAlgorithm::__NonExhaustive => unreachable!(), + } + }) + .copied(); + let rest: &[HashSet<_>] = &[ + cipher_suites + .signature + .iter() + .flat_map(|alg| -> &[&str] { + match alg { + TlsSignatureAlgorithm::Dss => &[ + "DH-DSS-AES128-GCM-SHA256", + "DH-DSS-AES256-GCM-SHA384", + "DHE-DSS-AES128-GCM-SHA256", + "DHE-DSS-AES256-GCM-SHA384", + ], + TlsSignatureAlgorithm::Ecdsa => &[ + "ECDH-ECDSA-AES128-GCM-SHA256", + "ECDH-ECDSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384", + ], + TlsSignatureAlgorithm::Rsa => &[ + "AES128-GCM-SHA256", + "AES256-GCM-SHA384", + "DH-RSA-AES128-GCM-SHA256", + "DH-RSA-AES256-GCM-SHA384", + "DHE-RSA-AES128-GCM-SHA256", + "DHE-RSA-AES256-GCM-SHA384", + "ECDH-RSA-AES128-GCM-SHA256", + "ECDH-RSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES256-GCM-SHA384", + ], + TlsSignatureAlgorithm::__NonExhaustive => unreachable!(), + } + }) + .copied() + .collect(), + cipher_suites + .bulk_encryption + .iter() + .flat_map(|alg| -> &[&str] { + match alg { + TlsBulkEncryptionAlgorithm::Aes128 => &[ + "AES128-GCM-SHA256", + "DH-RSA-AES128-GCM-SHA256", + "DH-DSS-AES128-GCM-SHA256", + "DHE-RSA-AES128-GCM-SHA256", + "DHE-DSS-AES128-GCM-SHA256", + "ECDH-RSA-AES128-GCM-SHA256", + "ECDH-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", + ], + TlsBulkEncryptionAlgorithm::Aes256 => &[ + "AES256-GCM-SHA384", + "DH-RSA-AES256-GCM-SHA384", + "DH-DSS-AES256-GCM-SHA384", + "DHE-RSA-AES256-GCM-SHA384", + "DHE-DSS-AES256-GCM-SHA384", + "ECDH-RSA-AES256-GCM-SHA384", + "ECDH-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-AES256-GCM-SHA384", + ], + TlsBulkEncryptionAlgorithm::Des => &[], + TlsBulkEncryptionAlgorithm::Rc2 => &[], + TlsBulkEncryptionAlgorithm::Rc4 => &[], + TlsBulkEncryptionAlgorithm::TripleDes => &[], + TlsBulkEncryptionAlgorithm::__NonExhaustive => unreachable!(), + } + }) + .copied() + .collect(), + cipher_suites + .hash + .iter() + .flat_map(|alg| -> &[&str] { + match alg { + TlsHashAlgorithm::Md5 => &[], + TlsHashAlgorithm::Sha1 => &[], + TlsHashAlgorithm::Sha256 => &[ + "AES128-GCM-SHA256", + "DH-RSA-AES128-GCM-SHA256", + "DH-DSS-AES128-GCM-SHA256", + "DHE-RSA-AES128-GCM-SHA256", + "DHE-DSS-AES128-GCM-SHA256", + "ECDH-RSA-AES128-GCM-SHA256", + "ECDH-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256", + ], + TlsHashAlgorithm::Sha384 => &[ + "AES256-GCM-SHA384", + "DH-RSA-AES256-GCM-SHA384", + "DH-DSS-AES256-GCM-SHA384", + "DHE-RSA-AES256-GCM-SHA384", + "DHE-DSS-AES256-GCM-SHA384", + "ECDH-RSA-AES256-GCM-SHA384", + "ECDH-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + "ECDHE-ECDSA-AES256-GCM-SHA384", + ], + TlsHashAlgorithm::__NonExhaustive => unreachable!(), + } + }) + .copied() + .collect(), + ]; + + first + .filter(|alg| rest.iter().all(|algs| algs.contains(alg))) + .collect() +} + +fn expand_algorithms(cipher_suites: &CipherSuiteSet) -> String { + let mut cipher_suite_strings: Vec> = vec![]; + + cipher_suite_strings.extend(cipher_suites.key_exchange.iter().map(|alg| { + vec![match alg { + TlsKeyExchangeAlgorithm::Dhe => "DHE", + TlsKeyExchangeAlgorithm::Ecdhe => "ECDHE", + TlsKeyExchangeAlgorithm::Rsa => "kRSA", + TlsKeyExchangeAlgorithm::__NonExhaustive => unreachable!(), + }] + })); + + cipher_suite_strings = cartesian_product( + cipher_suite_strings, + cipher_suites.signature.iter().map(|alg| match alg { + TlsSignatureAlgorithm::Dss => "aDSS", + TlsSignatureAlgorithm::Ecdsa => "aECDSA", + TlsSignatureAlgorithm::Rsa => "aRSA", + TlsSignatureAlgorithm::__NonExhaustive => unreachable!(), + }), + ); + cipher_suite_strings = cartesian_product( + cipher_suite_strings, + cipher_suites.bulk_encryption.iter().map(|alg| match alg { + TlsBulkEncryptionAlgorithm::Aes128 => "AES128", + TlsBulkEncryptionAlgorithm::Aes256 => "AES256", + TlsBulkEncryptionAlgorithm::Des => "DES", + TlsBulkEncryptionAlgorithm::Rc2 => "RC2", + TlsBulkEncryptionAlgorithm::Rc4 => "RC4", + TlsBulkEncryptionAlgorithm::TripleDes => "3DES", + TlsBulkEncryptionAlgorithm::__NonExhaustive => unreachable!(), + }), + ); + cipher_suite_strings = cartesian_product( + cipher_suite_strings, + cipher_suites.hash.iter().map(|alg| match alg { + TlsHashAlgorithm::Md5 => "MD5", + TlsHashAlgorithm::Sha1 => "SHA1", + TlsHashAlgorithm::Sha256 => "SHA256", + TlsHashAlgorithm::Sha384 => "SHA384", + TlsHashAlgorithm::__NonExhaustive => unreachable!(), + }), + ); + + // GCM first, as `@STRENGTH` sorts purely on bitwidth, and otherwise respects the initial + // ordering. GCM is generally preferred over CBC for performance and security reasons. + expand_gcm_algorithms(cipher_suites) + .into_iter() + .map(borrow::Cow::Borrowed) + .chain( + cipher_suite_strings + .into_iter() + .map(|parts| borrow::Cow::Owned(parts.join("+"))), + ) + .chain( + CIPHER_STRING_SUFFIX + .iter() + .map(|s| borrow::Cow::Borrowed(*s)), + ) + .collect::>() + .join(":") +} #[cfg(have_min_max_version)] fn supported_protocols( @@ -262,6 +487,9 @@ impl TlsConnector { connector.add_extra_chain_cert(cert.to_owned())?; } } + if let Some(ref cipher_suites) = builder.cipher_suites { + connector.set_cipher_list(&expand_algorithms(cipher_suites))?; + } supported_protocols(builder.min_protocol, builder.max_protocol, &mut connector)?; if builder.disable_built_in_roots { @@ -452,3 +680,38 @@ impl io::Write for TlsStream { self.0.flush() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expand_algorithms_basic() { + assert_eq!( + expand_algorithms(&CipherSuiteSet { + key_exchange: vec![TlsKeyExchangeAlgorithm::Dhe, TlsKeyExchangeAlgorithm::Ecdhe], + signature: vec![TlsSignatureAlgorithm::Rsa], + bulk_encryption: vec![ + TlsBulkEncryptionAlgorithm::Aes128, + TlsBulkEncryptionAlgorithm::Aes256 + ], + hash: vec![TlsHashAlgorithm::Sha256, TlsHashAlgorithm::Sha384], + }), + "\ + DHE-RSA-AES128-GCM-SHA256:\ + DHE-RSA-AES256-GCM-SHA384:\ + ECDHE-RSA-AES128-GCM-SHA256:\ + ECDHE-RSA-AES256-GCM-SHA384:\ + DHE+aRSA+AES128+SHA256:\ + DHE+aRSA+AES128+SHA384:\ + DHE+aRSA+AES256+SHA256:\ + DHE+aRSA+AES256+SHA384:\ + ECDHE+aRSA+AES128+SHA256:\ + ECDHE+aRSA+AES128+SHA384:\ + ECDHE+aRSA+AES256+SHA256:\ + ECDHE+aRSA+AES256+SHA384:\ + !aNULL:!eNULL:!IDEA:!SEED:!SRP:!PSK:@STRENGTH\ + ", + ); + } +} diff --git a/src/imp/schannel.rs b/src/imp/schannel.rs index dbf74a84..1df2a860 100644 --- a/src/imp/schannel.rs +++ b/src/imp/schannel.rs @@ -2,14 +2,86 @@ extern crate schannel; use self::schannel::cert_context::{CertContext, HashAlgorithm}; use self::schannel::cert_store::{CertAdd, CertStore, Memory, PfxImportOptions}; -use self::schannel::schannel_cred::{Direction, Protocol, SchannelCred}; +use self::schannel::schannel_cred::{Algorithm, Direction, Protocol, SchannelCred}; use self::schannel::tls_stream; use std::error; use std::fmt; use std::io; use std::str; -use {TlsAcceptorBuilder, TlsConnectorBuilder}; +use { + CipherSuiteSet, TlsAcceptorBuilder, TlsBulkEncryptionAlgorithm, TlsConnectorBuilder, + TlsHashAlgorithm, TlsKeyExchangeAlgorithm, TlsSignatureAlgorithm, +}; + +impl From for Algorithm { + fn from(other: TlsKeyExchangeAlgorithm) -> Self { + match other { + TlsKeyExchangeAlgorithm::Dhe => Algorithm::DhEphem, + TlsKeyExchangeAlgorithm::Ecdhe => Algorithm::EcdhEphem, + TlsKeyExchangeAlgorithm::Rsa => Algorithm::RsaKeyx, + TlsKeyExchangeAlgorithm::__NonExhaustive => unreachable!(), + } + } +} + +impl From for Algorithm { + fn from(other: TlsSignatureAlgorithm) -> Self { + match other { + TlsSignatureAlgorithm::Dss => Algorithm::DssSign, + TlsSignatureAlgorithm::Ecdsa => Algorithm::Ecdsa, + TlsSignatureAlgorithm::Rsa => Algorithm::RsaSign, + TlsSignatureAlgorithm::__NonExhaustive => unreachable!(), + } + } +} + +impl From for Algorithm { + fn from(other: TlsBulkEncryptionAlgorithm) -> Self { + match other { + TlsBulkEncryptionAlgorithm::Aes128 => Algorithm::Aes128, + TlsBulkEncryptionAlgorithm::Aes256 => Algorithm::Aes256, + TlsBulkEncryptionAlgorithm::Des => Algorithm::Des, + TlsBulkEncryptionAlgorithm::Rc2 => Algorithm::Rc2, + TlsBulkEncryptionAlgorithm::Rc4 => Algorithm::Rc4, + TlsBulkEncryptionAlgorithm::TripleDes => Algorithm::TripleDes, + TlsBulkEncryptionAlgorithm::__NonExhaustive => unreachable!(), + } + } +} + +impl From for Algorithm { + fn from(other: TlsHashAlgorithm) -> Self { + match other { + TlsHashAlgorithm::Md5 => Algorithm::Md5, + TlsHashAlgorithm::Sha1 => Algorithm::Sha1, + TlsHashAlgorithm::Sha256 => Algorithm::Sha256, + TlsHashAlgorithm::Sha384 => Algorithm::Sha384, + TlsHashAlgorithm::__NonExhaustive => unreachable!(), + } + } +} + +fn expand_algorithms(cipher_suites: &CipherSuiteSet) -> Vec { + let mut ret = vec![]; + ret.extend( + cipher_suites + .key_exchange + .iter() + .copied() + .map(Algorithm::from), + ); + ret.extend(cipher_suites.signature.iter().copied().map(Algorithm::from)); + ret.extend( + cipher_suites + .bulk_encryption + .iter() + .copied() + .map(Algorithm::from), + ); + ret.extend(cipher_suites.hash.iter().copied().map(Algorithm::from)); + ret +} const SEC_E_NO_CREDENTIALS: u32 = 0x8009030E; @@ -190,6 +262,7 @@ pub struct TlsConnector { disable_built_in_roots: bool, #[cfg(feature = "alpn")] alpn: Vec, + supported_algorithms: Vec, } impl TlsConnector { @@ -211,6 +284,10 @@ impl TlsConnector { disable_built_in_roots: builder.disable_built_in_roots, #[cfg(feature = "alpn")] alpn: builder.alpn.clone(), + supported_algorithms: match &builder.cipher_suites { + Some(cipher_suites) => expand_algorithms(cipher_suites), + None => vec![], + }, }) } @@ -223,6 +300,9 @@ impl TlsConnector { if let Some(cert) = self.cert.as_ref() { builder.cert(cert.clone()); } + if !self.supported_algorithms.is_empty() { + builder.supported_algorithms(&self.supported_algorithms); + } let cred = builder.acquire(Direction::Outbound)?; let mut builder = tls_stream::Builder::new(); builder diff --git a/src/imp/security_framework.rs b/src/imp/security_framework.rs index 249d07e6..eccd402b 100644 --- a/src/imp/security_framework.rs +++ b/src/imp/security_framework.rs @@ -5,6 +5,7 @@ extern crate tempfile; use self::security_framework::base; use self::security_framework::certificate::SecCertificate; +use self::security_framework::cipher_suite::CipherSuite; use self::security_framework::identity::SecIdentity; use self::security_framework::import_export::{ImportedIdentity, Pkcs12ImportOptions}; use self::security_framework::secure_transport::{ @@ -12,6 +13,7 @@ use self::security_framework::secure_transport::{ }; use self::security_framework_sys::base::{errSecIO, errSecParam}; use self::tempfile::TempDir; +use std::collections::HashSet; use std::error; use std::fmt; use std::io; @@ -30,7 +32,10 @@ use self::security_framework::os::macos::import_export::{ #[cfg(not(target_os = "ios"))] use self::security_framework::os::macos::keychain::{self, KeychainSettings, SecKeychain}; -use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; +use { + CipherSuiteSet, Protocol, TlsAcceptorBuilder, TlsBulkEncryptionAlgorithm, TlsConnectorBuilder, + TlsHashAlgorithm, TlsKeyExchangeAlgorithm, TlsSignatureAlgorithm, +}; static SET_AT_EXIT: Once = Once::new(); @@ -39,6 +44,431 @@ lazy_static! { static ref TEMP_KEYCHAIN: Mutex> = Mutex::new(None); } +const CIPHERS_3DES: &[CipherSuite] = &[ + CipherSuite::TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::SSL_RSA_WITH_3DES_EDE_CBC_MD5, +]; + +const CIPHERS_AES_128: &[CipherSuite] = &[ + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, +]; + +const CIPHERS_AES_256: &[CipherSuite] = &[ + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DH_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DH_DSS_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, +]; + +const CIPHERS_DES: &[CipherSuite] = &[ + CipherSuite::SSL_RSA_WITH_DES_CBC_SHA, + CipherSuite::SSL_DH_DSS_WITH_DES_CBC_SHA, + CipherSuite::SSL_DH_RSA_WITH_DES_CBC_SHA, + CipherSuite::SSL_DHE_DSS_WITH_DES_CBC_SHA, + CipherSuite::SSL_DHE_RSA_WITH_DES_CBC_SHA, + CipherSuite::SSL_RSA_WITH_DES_CBC_MD5, +]; + +const CIPHERS_DH_EPHEM: &[CipherSuite] = &[ + CipherSuite::SSL_DHE_DSS_WITH_DES_CBC_SHA, + CipherSuite::SSL_DHE_RSA_WITH_DES_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, +]; + +const CIPHERS_DSS: &[CipherSuite] = &[ + CipherSuite::SSL_DH_DSS_WITH_DES_CBC_SHA, + CipherSuite::SSL_DHE_DSS_WITH_DES_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DH_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_256_GCM_SHA384, +]; + +const CIPHERS_ECDHE: &[CipherSuite] = &[ + CipherSuite::TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, +]; + +const CIPHERS_ECDSA: &[CipherSuite] = &[ + CipherSuite::TLS_ECDH_ECDSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, +]; + +const CIPHERS_MD5: &[CipherSuite] = &[ + CipherSuite::SSL_RSA_WITH_RC4_128_MD5, + CipherSuite::TLS_RSA_WITH_RC4_128_MD5, + CipherSuite::SSL_RSA_WITH_RC2_CBC_MD5, + CipherSuite::SSL_RSA_WITH_IDEA_CBC_MD5, + CipherSuite::SSL_RSA_WITH_DES_CBC_MD5, + CipherSuite::SSL_RSA_WITH_3DES_EDE_CBC_MD5, +]; + +const CIPHERS_RC2: &[CipherSuite] = &[CipherSuite::SSL_RSA_WITH_RC2_CBC_MD5]; + +const CIPHERS_RC4: &[CipherSuite] = &[ + CipherSuite::SSL_RSA_WITH_RC4_128_MD5, + CipherSuite::SSL_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_RSA_WITH_RC4_128_MD5, + CipherSuite::TLS_RSA_WITH_RC4_128_SHA, +]; + +const CIPHERS_RSA_KEYX: &[CipherSuite] = &[ + CipherSuite::SSL_RSA_WITH_RC4_128_MD5, + CipherSuite::SSL_RSA_WITH_RC4_128_SHA, + CipherSuite::SSL_RSA_WITH_IDEA_CBC_SHA, + CipherSuite::SSL_RSA_WITH_DES_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_RSA_WITH_RC4_128_MD5, + CipherSuite::TLS_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::SSL_RSA_WITH_RC2_CBC_MD5, + CipherSuite::SSL_RSA_WITH_IDEA_CBC_MD5, + CipherSuite::SSL_RSA_WITH_DES_CBC_MD5, + CipherSuite::SSL_RSA_WITH_3DES_EDE_CBC_MD5, +]; + +const CIPHERS_RSA_SIGN: &[CipherSuite] = &[ + CipherSuite::SSL_RSA_WITH_RC4_128_MD5, + CipherSuite::SSL_RSA_WITH_RC4_128_SHA, + CipherSuite::SSL_RSA_WITH_IDEA_CBC_SHA, + CipherSuite::SSL_RSA_WITH_DES_CBC_SHA, + CipherSuite::SSL_DH_RSA_WITH_DES_CBC_SHA, + CipherSuite::SSL_DHE_RSA_WITH_DES_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_RSA_WITH_RC4_128_MD5, + CipherSuite::TLS_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DH_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::SSL_RSA_WITH_RC2_CBC_MD5, + CipherSuite::SSL_RSA_WITH_IDEA_CBC_MD5, + CipherSuite::SSL_RSA_WITH_DES_CBC_MD5, + CipherSuite::SSL_RSA_WITH_3DES_EDE_CBC_MD5, +]; + +const CIPHERS_SHA1: &[CipherSuite] = &[ + CipherSuite::SSL_RSA_WITH_RC4_128_SHA, + CipherSuite::SSL_RSA_WITH_IDEA_CBC_SHA, + CipherSuite::SSL_RSA_WITH_DES_CBC_SHA, + CipherSuite::SSL_DH_DSS_WITH_DES_CBC_SHA, + CipherSuite::SSL_DH_RSA_WITH_DES_CBC_SHA, + CipherSuite::SSL_DHE_DSS_WITH_DES_CBC_SHA, + CipherSuite::SSL_DHE_RSA_WITH_DES_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_RSA_WITH_RC4_128_SHA, + CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite::TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, +]; + +const CIPHERS_SHA256: &[CipherSuite] = &[ + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DH_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DH_DSS_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DH_anon_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, +]; + +const CIPHERS_SHA384: &[CipherSuite] = &[ + CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DH_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_DH_DSS_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, +]; + +fn key_exchange_alg_to_cipher_suites(alg: &TlsKeyExchangeAlgorithm) -> &'static [CipherSuite] { + match alg { + TlsKeyExchangeAlgorithm::Dhe => CIPHERS_DH_EPHEM, + TlsKeyExchangeAlgorithm::Ecdhe => CIPHERS_ECDHE, + TlsKeyExchangeAlgorithm::Rsa => CIPHERS_RSA_KEYX, + TlsKeyExchangeAlgorithm::__NonExhaustive => unreachable!(), + } +} + +fn signature_alg_to_cipher_suites(alg: &TlsSignatureAlgorithm) -> &'static [CipherSuite] { + match alg { + TlsSignatureAlgorithm::Dss => CIPHERS_DSS, + TlsSignatureAlgorithm::Ecdsa => CIPHERS_ECDSA, + TlsSignatureAlgorithm::Rsa => CIPHERS_RSA_SIGN, + TlsSignatureAlgorithm::__NonExhaustive => unreachable!(), + } +} + +fn bulk_encryption_alg_to_cipher_suites( + alg: &TlsBulkEncryptionAlgorithm, +) -> &'static [CipherSuite] { + match alg { + TlsBulkEncryptionAlgorithm::Aes128 => CIPHERS_AES_128, + TlsBulkEncryptionAlgorithm::Aes256 => CIPHERS_AES_256, + TlsBulkEncryptionAlgorithm::Des => CIPHERS_DES, + TlsBulkEncryptionAlgorithm::Rc2 => CIPHERS_RC2, + TlsBulkEncryptionAlgorithm::Rc4 => CIPHERS_RC4, + TlsBulkEncryptionAlgorithm::TripleDes => CIPHERS_3DES, + TlsBulkEncryptionAlgorithm::__NonExhaustive => unreachable!(), + } +} + +fn hash_alg_to_cipher_suites(alg: &TlsHashAlgorithm) -> &'static [CipherSuite] { + match alg { + TlsHashAlgorithm::Md5 => CIPHERS_MD5, + TlsHashAlgorithm::Sha1 => CIPHERS_SHA1, + TlsHashAlgorithm::Sha256 => CIPHERS_SHA256, + TlsHashAlgorithm::Sha384 => CIPHERS_SHA384, + TlsHashAlgorithm::__NonExhaustive => unreachable!(), + } +} + +fn expand_algorithms(cipher_suites: &CipherSuiteSet) -> Vec { + let current: HashSet<_> = cipher_suites + .key_exchange + .iter() + .flat_map(key_exchange_alg_to_cipher_suites) + .copied() + .collect(); + + let mut next = HashSet::new(); + for suite in cipher_suites + .signature + .iter() + .flat_map(signature_alg_to_cipher_suites) + { + if current.contains(suite) { + next.insert(suite); + } + } + + let current = next; + let mut next = HashSet::new(); + for suite in cipher_suites + .bulk_encryption + .iter() + .flat_map(bulk_encryption_alg_to_cipher_suites) + { + if current.contains(suite) { + next.insert(suite); + } + } + + let current = next; + let mut ret = Vec::new(); + for suite in cipher_suites + .hash + .iter() + .flat_map(hash_alg_to_cipher_suites) + { + if current.contains(suite) { + ret.push(*suite); + } + } + + ret +} + fn convert_protocol(protocol: Protocol) -> SslProtocol { match protocol { Protocol::Sslv3 => SslProtocol::SSL3, @@ -266,6 +696,7 @@ pub struct TlsConnector { disable_built_in_roots: bool, #[cfg(feature = "alpn")] alpn: Vec, + cipher_suites: Vec, } impl TlsConnector { @@ -285,6 +716,10 @@ impl TlsConnector { disable_built_in_roots: builder.disable_built_in_roots, #[cfg(feature = "alpn")] alpn: builder.alpn.clone(), + cipher_suites: match &builder.cipher_suites { + Some(cipher_suites) => expand_algorithms(&cipher_suites), + None => vec![], + }, }) } @@ -307,6 +742,9 @@ impl TlsConnector { builder.danger_accept_invalid_hostnames(self.danger_accept_invalid_hostnames); builder.danger_accept_invalid_certs(self.danger_accept_invalid_certs); builder.trust_anchor_certificates_only(self.disable_built_in_roots); + if !self.cipher_suites.is_empty() { + builder.whitelist_ciphers(&self.cipher_suites); + } #[cfg(feature = "alpn")] { @@ -572,3 +1010,37 @@ extern "C" { fn CC_SHA384(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; fn CC_SHA512(data: *const u8, len: CC_LONG, md: *mut u8) -> *mut u8; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expand_algorithms_basic() { + assert_eq!( + expand_algorithms(&CipherSuiteSet { + key_exchange: vec![TlsKeyExchangeAlgorithm::Dhe, TlsKeyExchangeAlgorithm::Ecdhe], + signature: vec![TlsSignatureAlgorithm::Rsa], + bulk_encryption: vec![ + TlsBulkEncryptionAlgorithm::Aes128, + TlsBulkEncryptionAlgorithm::Aes256, + ], + hash: vec![TlsHashAlgorithm::Sha256, TlsHashAlgorithm::Sha384], + }) + .into_iter() + .collect::>(), + vec![ + CipherSuite::TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + ] + .into_iter() + .collect::>(), + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 171e8c7e..1bdfe9e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -316,6 +316,137 @@ pub enum Protocol { __NonExhaustive, } +/// TLS cipher suite key-exchange algorithms. +#[derive(Debug, Copy, Clone)] +pub enum TlsKeyExchangeAlgorithm { + /// Diffie-Hellman ephemeral key exchange algorithm. + Dhe, + /// Ephemeral elliptic curve Diffie-Hellman key exchange algorithm. + Ecdhe, + /// RSA public key exchange algorithm. + Rsa, + #[doc(hidden)] + __NonExhaustive, +} + +/// TLS cipher suite message signature algorithms. +#[derive(Debug, Copy, Clone)] +pub enum TlsSignatureAlgorithm { + /// DSA public key signature algorithm. + Dss, + /// Elliptic curve digital signature algorithm. + Ecdsa, + /// RSA public key signature algorithm. + Rsa, + #[doc(hidden)] + __NonExhaustive, +} + +/// TLS cipher suite bulk encryption algorithms. +#[derive(Debug, Copy, Clone)] +pub enum TlsBulkEncryptionAlgorithm { + /// 128 bit AES. + Aes128, + /// 256 bit AES. + Aes256, + /// DES encryption algorithm. + Des, + /// RC2 block encryption algorithm. + Rc2, + /// RC4 stream encryption algorithm. + Rc4, + /// Triple DES encryption algorithm. + TripleDes, + #[doc(hidden)] + __NonExhaustive, +} + +/// TLS cipher suite bulk encryption algorithms. +#[derive(Debug, Copy, Clone)] +pub enum TlsHashAlgorithm { + /// MD5 hashing algorithm. + Md5, + /// SHA hashing algorithm. + Sha1, + /// 256 bit SHA hashing algorithm. + Sha256, + /// 384 bit SHA hashing algorithm. + Sha384, + // TODO: Not supported by macOS Security Framework. + // /// 512 bit SHA hashing algorithm. + // Sha512, + #[doc(hidden)] + __NonExhaustive, +} + +/// Represents a collection of cipher suites, specified by a cartesian product of their separate +/// algorithm components. +/// +/// Note that the `Default` here is consistent across platforms and is not equivalent to the +/// platform-specific defaults on each individual platform. [`CipherSuiteSet::default`] corresponds +/// to DHE, ECDHE, and RSA for key exchange, ECDSA and RSA for signature, AES-128 and AES-256 for +/// bulk encryption, and SHA1, SHA256, and SHA384 for hashing. +#[derive(Debug, Clone)] +pub struct CipherSuiteSet { + key_exchange: Vec, + signature: Vec, + bulk_encryption: Vec, + hash: Vec, +} + +impl Default for CipherSuiteSet { + fn default() -> Self { + // Based on the default TLS cipher suites in rust-openssl. + Self { + key_exchange: vec![ + TlsKeyExchangeAlgorithm::Dhe, + TlsKeyExchangeAlgorithm::Ecdhe, + TlsKeyExchangeAlgorithm::Rsa, + ], + signature: vec![TlsSignatureAlgorithm::Ecdsa, TlsSignatureAlgorithm::Rsa], + bulk_encryption: vec![ + TlsBulkEncryptionAlgorithm::Aes128, + TlsBulkEncryptionAlgorithm::Aes256, + ], + hash: vec![ + TlsHashAlgorithm::Sha1, + TlsHashAlgorithm::Sha256, + TlsHashAlgorithm::Sha384, + ], + } + } +} + +impl CipherSuiteSet { + /// Allow only ciphersuites with the specified key exchange algorithms. + pub fn key_exchange_algorithms(mut self, algs: &[TlsKeyExchangeAlgorithm]) -> Self { + assert!(!algs.is_empty(), "cannot specify 0 algorithms"); + self.key_exchange = algs.to_vec(); + self + } + + /// Allow only ciphersuites with the specified signature algorithms. + pub fn signature_algorithms(mut self, algs: &[TlsSignatureAlgorithm]) -> Self { + assert!(!algs.is_empty(), "cannot specify 0 algorithms"); + self.signature = algs.to_vec(); + self + } + + /// Allow only ciphersuites with the specified bulk encryption algorithms. + pub fn bulk_encryption_algorithms(mut self, algs: &[TlsBulkEncryptionAlgorithm]) -> Self { + assert!(!algs.is_empty(), "cannot specify 0 algorithms"); + self.bulk_encryption = algs.to_vec(); + self + } + + /// Allow only ciphersuites with the specified hash algorithms. + pub fn hash_algorithms(mut self, algs: &[TlsHashAlgorithm]) -> Self { + assert!(!algs.is_empty(), "cannot specify 0 algorithms"); + self.hash = algs.to_vec(); + self + } +} + /// A builder for `TlsConnector`s. pub struct TlsConnectorBuilder { identity: Option, @@ -328,6 +459,7 @@ pub struct TlsConnectorBuilder { disable_built_in_roots: bool, #[cfg(feature = "alpn")] alpn: Vec, + cipher_suites: Option, } impl TlsConnectorBuilder { @@ -428,6 +560,15 @@ impl TlsConnectorBuilder { self } + /// Sets the set of cipher suites that will be used in this TLS connection. + pub fn supported_cipher_suites( + &mut self, + cipher_suites: CipherSuiteSet, + ) -> &mut TlsConnectorBuilder { + self.cipher_suites = Some(cipher_suites); + self + } + /// Creates a new `TlsConnector`. pub fn build(&self) -> Result { let connector = imp::TlsConnector::new(self)?; @@ -476,6 +617,7 @@ impl TlsConnector { disable_built_in_roots: false, #[cfg(feature = "alpn")] alpn: vec![], + cipher_suites: None, } } diff --git a/src/test.rs b/src/test.rs index b4e2de34..9cb9bd56 100644 --- a/src/test.rs +++ b/src/test.rs @@ -415,3 +415,31 @@ fn alpn_google_none() { let alpn = p!(socket.negotiated_alpn()); assert_eq!(alpn, None); } + +#[test] +fn badssl_cipher_suites_no_rsa() { + let builder = p!(TlsConnector::builder() + .supported_cipher_suites( + // Oddly, on Windows, allowing RSA key exchange, but not RSA signature algorithms still + // allows a successful TLS connection, despite there being no non-RSA signature cipher + // suites in the Mozilla Intermediate set AFAICT. Removing RSA from the key exchange + // algorithms causes this test to work as expected. + CipherSuiteSet::default() + .key_exchange_algorithms(&[TlsKeyExchangeAlgorithm::Ecdhe]) + .signature_algorithms(&[TlsSignatureAlgorithm::Ecdsa]) + ) + .build()); + let s = p!(TcpStream::connect("mozilla-intermediate.badssl.com:443")); + assert!(builder + .connect("mozilla-intermediate.badssl.com", s) + .is_err()); +} + +#[test] +fn badssl_cipher_suites_default() { + let builder = p!(TlsConnector::builder() + .supported_cipher_suites(CipherSuiteSet::default()) + .build()); + let s = p!(TcpStream::connect("mozilla-intermediate.badssl.com:443")); + p!(builder.connect("mozilla-intermediate.badssl.com", s)); +}