aboutsummaryrefslogtreecommitdiff
path: root/openssl/src/ssl/connector.rs
diff options
context:
space:
mode:
Diffstat (limited to 'openssl/src/ssl/connector.rs')
-rw-r--r--openssl/src/ssl/connector.rs452
1 files changed, 452 insertions, 0 deletions
diff --git a/openssl/src/ssl/connector.rs b/openssl/src/ssl/connector.rs
new file mode 100644
index 00000000..0d92529d
--- /dev/null
+++ b/openssl/src/ssl/connector.rs
@@ -0,0 +1,452 @@
+use std::io::{Read, Write};
+
+use dh::Dh;
+use error::ErrorStack;
+use ssl::{self, SslMethod, SslContextBuilder, SslContext, Ssl, SSL_VERIFY_PEER, SslStream,
+ HandshakeError};
+use pkey::PKeyRef;
+use x509::X509Ref;
+
+// Serialized form of DH_get_2048_256
+#[cfg(any(ossl101, all(test, any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))))]
+const DHPARAM_PEM: &'static str = r#"
+-----BEGIN DH PARAMETERS-----
+MIICCQKCAQEAh6jmHbS2Zjz/u9GcZRlZmYzu9ghmDdDyXSzu1ENeOwDgDfjx1hlX
+1Pr330VhsqowFsPZETQJb6o79Cltgw6afCCeDGSXUXq9WoqdMGvPZ+2R+eZyW0dY
+wCLgse9Cdb97bFv8EdRfkIi5QfVOseWbuLw5oL8SMH9cT9twxYGyP3a2Osrhyqa3
+kC1SUmc1SIoO8TxtmlG/pKs62DR3llJNjvahZ7WkGCXZZ+FE5RQFZCUcysuD5rSG
+9rPKP3lxUGAmwLhX9omWKFbe1AEKvQvmIcOjlgpU5xDDdfJjddcBQQOktUMwwZiv
+EmEW0iduEXFfaTh3+tfvCcrbCUrpHhoVlwKCAQA/syybcxNNCy53UGZg7b1ITKex
+jyHvIFQH9Hk6GguhJRDbwVB3vkY//0/tSqwLtVW+OmwbDGtHsbw3c79+jG9ikBIo
++MKMuxilWuMTQQAKZQGW+THHelfy3fRj5ensFEt3feYqqrioYorDdtKC1u04ZOZ5
+gkKOvIMdFDSPby+Rk7UEWvJ2cWTh38lnwfs/LlWkvRv/6DucgNBSuYXRguoK2yo7
+cxPT/hTISEseBSWIubfSu9LfAWGZ7NBuFVfNCRWzNTu7ZODsN3/QKDcN+StSx4kU
+KM3GfrYYS1I9HbJGwy9jB4SQ8A741kfRSNR5VFFeIyfP75jFgmZLTA9sxBZZ
+-----END DH PARAMETERS-----
+"#;
+
+fn ctx(method: SslMethod) -> Result<SslContextBuilder, ErrorStack> {
+ let mut ctx = try!(SslContextBuilder::new(method));
+
+ let mut opts = ssl::SSL_OP_ALL;
+ opts |= ssl::SSL_OP_NO_TICKET;
+ opts |= ssl::SSL_OP_NO_COMPRESSION;
+ opts &= !ssl::SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
+ opts &= !ssl::SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+ opts |= ssl::SSL_OP_NO_SSLV2;
+ opts |= ssl::SSL_OP_NO_SSLV3;
+ opts |= ssl::SSL_OP_SINGLE_DH_USE;
+ opts |= ssl::SSL_OP_SINGLE_ECDH_USE;
+ opts |= ssl::SSL_OP_CIPHER_SERVER_PREFERENCE;
+ ctx.set_options(opts);
+
+ let mode = ssl::SSL_MODE_AUTO_RETRY | ssl::SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER |
+ ssl::SSL_MODE_ENABLE_PARTIAL_WRITE;
+ ctx.set_mode(mode);
+
+ Ok(ctx)
+}
+
+/// A builder for `SslConnector`s.
+pub struct SslConnectorBuilder(SslContextBuilder);
+
+impl SslConnectorBuilder {
+ /// Creates a new builder for TLS connections.
+ ///
+ /// The default configuration is subject to change, and is currently derived from Python.
+ pub fn new(method: SslMethod) -> Result<SslConnectorBuilder, ErrorStack> {
+ let mut ctx = try!(ctx(method));
+ try!(ctx.set_default_verify_paths());
+ // From https://github.com/python/cpython/blob/c30098c8c6014f3340a369a31df9c74bdbacc269/Lib/ssl.py#L191
+ try!(ctx.set_cipher_list("ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:DH+CHACHA20:ECDH+AES256:\
+ DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:DH+HIGH:RSA+AESGCM:\
+ RSA+AES:RSA+HIGH:!aNULL:!eNULL:!MD5:!3DES"));
+
+ Ok(SslConnectorBuilder(ctx))
+ }
+
+ /// Returns a shared reference to the inner `SslContextBuilder`.
+ pub fn builder(&self) -> &SslContextBuilder {
+ &self.0
+ }
+
+ /// Returns a mutable reference to the inner `SslContextBuilder`.
+ pub fn builder_mut(&mut self) -> &mut SslContextBuilder {
+ &mut self.0
+ }
+
+ /// Consumes the builder, returning a `SslConnector`.
+ pub fn build(self) -> SslConnector {
+ SslConnector(self.0.build())
+ }
+}
+
+/// A type which wraps client-side streams in a TLS session.
+///
+/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL
+/// structures, configuring cipher suites, session options, hostname verification, and more.
+///
+/// OpenSSL's built in hostname verification is used when linking against OpenSSL 1.0.2 or 1.1.0,
+/// and a custom implementation is used when linking against OpenSSL 1.0.1.
+pub struct SslConnector(SslContext);
+
+impl SslConnector {
+ /// Initiates a client-side TLS session on a stream.
+ ///
+ /// The domain is used for SNI and hostname verification.
+ pub fn connect<S>(&self, domain: &str, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
+ where S: Read + Write
+ {
+ let mut ssl = try!(Ssl::new(&self.0));
+ try!(ssl.set_hostname(domain));
+ try!(setup_verify(&mut ssl, domain));
+
+ ssl.connect(stream)
+ }
+}
+
+/// A builder for `SslAcceptor`s.
+pub struct SslAcceptorBuilder(SslContextBuilder);
+
+impl SslAcceptorBuilder {
+ /// Creates a new builder configured to connect to non-legacy clients. This should generally be
+ /// considered a reasonable default choice.
+ ///
+ /// This corresponds to the intermediate configuration of Mozilla's server side TLS
+ /// recommendations. See its [documentation][docs] for more details on specifics.
+ ///
+ /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
+ pub fn mozilla_intermediate<I>(method: SslMethod,
+ private_key: &PKeyRef,
+ certificate: &X509Ref,
+ chain: I)
+ -> Result<SslAcceptorBuilder, ErrorStack>
+ where I: IntoIterator,
+ I::Item: AsRef<X509Ref>
+ {
+ let mut ctx = try!(ctx(method));
+ let dh = try!(get_dh());
+ try!(ctx.set_tmp_dh(&dh));
+ try!(setup_curves(&mut ctx));
+ try!(ctx.set_cipher_list("ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
+ ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\
+ ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\
+ DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:\
+ ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:\
+ ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:\
+ ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:\
+ ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:\
+ DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:\
+ DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:\
+ ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:\
+ EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:\
+ AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:\
+ DES-CBC3-SHA:!DSS"));
+ SslAcceptorBuilder::finish_setup(ctx, private_key, certificate, chain)
+ }
+
+ /// Creates a new builder configured to connect to modern clients.
+ ///
+ /// This corresponds to the modern configuration of Mozilla's server side TLS recommendations.
+ /// See its [documentation][docs] for more details on specifics.
+ ///
+ /// [docs]: https://wiki.mozilla.org/Security/Server_Side_TLS
+ pub fn mozilla_modern<I>(method: SslMethod,
+ private_key: &PKeyRef,
+ certificate: &X509Ref,
+ chain: I)
+ -> Result<SslAcceptorBuilder, ErrorStack>
+ where I: IntoIterator,
+ I::Item: AsRef<X509Ref>
+ {
+ let mut ctx = try!(ctx(method));
+ try!(setup_curves(&mut ctx));
+ try!(ctx.set_cipher_list("ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\
+ ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:\
+ ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\
+ ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:\
+ ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"));
+ SslAcceptorBuilder::finish_setup(ctx, private_key, certificate, chain)
+ }
+
+ fn finish_setup<I>(mut ctx: SslContextBuilder,
+ private_key: &PKeyRef,
+ certificate: &X509Ref,
+ chain: I)
+ -> Result<SslAcceptorBuilder, ErrorStack>
+ where I: IntoIterator,
+ I::Item: AsRef<X509Ref>
+ {
+ try!(ctx.set_private_key(private_key));
+ try!(ctx.set_certificate(certificate));
+ try!(ctx.check_private_key());
+ for cert in chain {
+ try!(ctx.add_extra_chain_cert(cert.as_ref().to_owned()));
+ }
+ Ok(SslAcceptorBuilder(ctx))
+ }
+
+ /// Returns a shared reference to the inner `SslContextBuilder`.
+ pub fn builder(&self) -> &SslContextBuilder {
+ &self.0
+ }
+
+ /// Returns a mutable reference to the inner `SslContextBuilder`.
+ pub fn builder_mut(&mut self) -> &mut SslContextBuilder {
+ &mut self.0
+ }
+
+ /// Consumes the builder, returning a `SslAcceptor`.
+ pub fn build(self) -> SslAcceptor {
+ SslAcceptor(self.0.build())
+ }
+}
+
+#[cfg(ossl101)]
+fn get_dh() -> Result<Dh, ErrorStack> {
+ Dh::from_pem(DHPARAM_PEM.as_bytes())
+}
+
+#[cfg(not(ossl101))]
+fn get_dh() -> Result<Dh, ErrorStack> {
+ use ffi;
+
+ use cvt_p;
+ use types::OpenSslType;
+
+ // manually call into ffi to avoid forcing the features
+ unsafe { cvt_p(ffi::DH_get_2048_256()).map(|p| Dh::from_ptr(p)) }
+}
+
+#[cfg(ossl101)]
+fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
+ use ec_key::EcKey;
+ use nid;
+
+ let curve = try!(EcKey::new_by_curve_name(nid::X9_62_PRIME256V1));
+ ctx.set_tmp_ecdh(&curve)
+}
+
+#[cfg(ossl102)]
+fn setup_curves(ctx: &mut SslContextBuilder) -> Result<(), ErrorStack> {
+ ctx._set_ecdh_auto(true)
+}
+
+#[cfg(ossl110)]
+fn setup_curves(_: &mut SslContextBuilder) -> Result<(), ErrorStack> {
+ Ok(())
+}
+
+/// A type which wraps server-side streams in a TLS session.
+///
+/// OpenSSL's default configuration is highly insecure. This connector manages the OpenSSL
+/// structures, configuring cipher suites, session options, and more.
+pub struct SslAcceptor(SslContext);
+
+impl SslAcceptor {
+ /// Initiates a server-side TLS session on a stream.
+ pub fn accept<S>(&self, stream: S) -> Result<SslStream<S>, HandshakeError<S>>
+ where S: Read + Write
+ {
+ let ssl = try!(Ssl::new(&self.0));
+ ssl.accept(stream)
+ }
+}
+
+#[cfg(any(ossl102, ossl110))]
+fn setup_verify(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> {
+ ssl.set_verify(SSL_VERIFY_PEER);
+ let param = ssl._param_mut();
+ param.set_hostflags(::verify::X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
+ param.set_host(domain)
+}
+
+#[cfg(ossl101)]
+fn setup_verify(ssl: &mut Ssl, domain: &str) -> Result<(), ErrorStack> {
+ let domain = domain.to_owned();
+ ssl.set_verify_callback(SSL_VERIFY_PEER,
+ move |p, x| verify::verify_callback(&domain, p, x));
+ Ok(())
+}
+
+#[cfg(ossl101)]
+mod verify {
+ use std::net::IpAddr;
+ use std::str;
+
+ use nid;
+ use x509::{X509StoreContextRef, X509Ref, X509NameRef, GeneralName};
+ use stack::Stack;
+ use types::OpenSslTypeRef;
+
+ pub fn verify_callback(domain: &str,
+ preverify_ok: bool,
+ x509_ctx: &X509StoreContextRef)
+ -> bool {
+ if !preverify_ok || x509_ctx.error_depth() != 0 {
+ return preverify_ok;
+ }
+
+ match x509_ctx.current_cert() {
+ Some(x509) => verify_hostname(domain, &x509),
+ None => true,
+ }
+ }
+
+ fn verify_hostname(domain: &str, cert: &X509Ref) -> bool {
+ match cert.subject_alt_names() {
+ Some(names) => verify_subject_alt_names(domain, names),
+ None => verify_subject_name(domain, &cert.subject_name()),
+ }
+ }
+
+ fn verify_subject_alt_names(domain: &str, names: Stack<GeneralName>) -> bool {
+ let ip = domain.parse();
+
+ for name in &names {
+ match ip {
+ Ok(ip) => {
+ if let Some(actual) = name.ipaddress() {
+ if matches_ip(&ip, actual) {
+ return true;
+ }
+ }
+ }
+ Err(_) => {
+ if let Some(pattern) = name.dnsname() {
+ if matches_dns(pattern, domain, false) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ false
+ }
+
+ fn verify_subject_name(domain: &str, subject_name: &X509NameRef) -> bool {
+ if let Some(pattern) = subject_name.entries_by_nid(nid::COMMONNAME).next() {
+ let pattern = match str::from_utf8(pattern.data().as_slice()) {
+ Ok(pattern) => pattern,
+ Err(_) => return false,
+ };
+
+ // Unlike with SANs, IP addresses in the subject name don't have a
+ // different encoding. We need to pass this down to matches_dns to
+ // disallow wildcard matches with bogus patterns like *.0.0.1
+ let is_ip = domain.parse::<IpAddr>().is_ok();
+
+ if matches_dns(&pattern, domain, is_ip) {
+ return true;
+ }
+ }
+
+ false
+ }
+
+ fn matches_dns(mut pattern: &str, mut hostname: &str, is_ip: bool) -> bool {
+ // first strip trailing . off of pattern and hostname to normalize
+ if pattern.ends_with('.') {
+ pattern = &pattern[..pattern.len() - 1];
+ }
+ if hostname.ends_with('.') {
+ hostname = &hostname[..hostname.len() - 1];
+ }
+
+ matches_wildcard(pattern, hostname, is_ip).unwrap_or_else(|| pattern == hostname)
+ }
+
+ fn matches_wildcard(pattern: &str, hostname: &str, is_ip: bool) -> Option<bool> {
+ // IP addresses and internationalized domains can't involved in wildcards
+ if is_ip || pattern.starts_with("xn--") {
+ return None;
+ }
+
+ let wildcard_location = match pattern.find('*') {
+ Some(l) => l,
+ None => return None,
+ };
+
+ let mut dot_idxs = pattern.match_indices('.').map(|(l, _)| l);
+ let wildcard_end = match dot_idxs.next() {
+ Some(l) => l,
+ None => return None,
+ };
+
+ // Never match wildcards if the pattern has less than 2 '.'s (no *.com)
+ //
+ // This is a bit dubious, as it doesn't disallow other TLDs like *.co.uk.
+ // Chrome has a black- and white-list for this, but Firefox (via NSS) does
+ // the same thing we do here.
+ //
+ // The Public Suffix (https://www.publicsuffix.org/) list could
+ // potentically be used here, but it's both huge and updated frequently
+ // enough that management would be a PITA.
+ if dot_idxs.next().is_none() {
+ return None;
+ }
+
+ // Wildcards can only be in the first component
+ if wildcard_location > wildcard_end {
+ return None;
+ }
+
+ let hostname_label_end = match hostname.find('.') {
+ Some(l) => l,
+ None => return None,
+ };
+
+ // check that the non-wildcard parts are identical
+ if pattern[wildcard_end..] != hostname[hostname_label_end..] {
+ return Some(false);
+ }
+
+ let wildcard_prefix = &pattern[..wildcard_location];
+ let wildcard_suffix = &pattern[wildcard_location + 1..wildcard_end];
+
+ let hostname_label = &hostname[..hostname_label_end];
+
+ // check the prefix of the first label
+ if !hostname_label.starts_with(wildcard_prefix) {
+ return Some(false);
+ }
+
+ // and the suffix
+ if !hostname_label[wildcard_prefix.len()..].ends_with(wildcard_suffix) {
+ return Some(false);
+ }
+
+ Some(true)
+ }
+
+ fn matches_ip(expected: &IpAddr, actual: &[u8]) -> bool {
+ match (expected, actual.len()) {
+ (&IpAddr::V4(ref addr), 4) => actual == addr.octets(),
+ (&IpAddr::V6(ref addr), 16) => {
+ let segments = [((actual[0] as u16) << 8) | actual[1] as u16,
+ ((actual[2] as u16) << 8) | actual[3] as u16,
+ ((actual[4] as u16) << 8) | actual[5] as u16,
+ ((actual[6] as u16) << 8) | actual[7] as u16,
+ ((actual[8] as u16) << 8) | actual[9] as u16,
+ ((actual[10] as u16) << 8) | actual[11] as u16,
+ ((actual[12] as u16) << 8) | actual[13] as u16,
+ ((actual[14] as u16) << 8) | actual[15] as u16];
+ segments == addr.segments()
+ }
+ _ => false,
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ #[cfg(any(all(feature = "v102", ossl102), all(feature = "v110", ossl110)))]
+ #[test]
+ fn check_dhparam() {
+ use dh::Dh;
+
+ let expected = String::from_utf8(Dh::get_2048_256().unwrap().to_pem().unwrap()).unwrap();
+ assert_eq!(expected.trim(), super::DHPARAM_PEM.trim());
+ }
+}