diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-04-16 13:22:49 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-04-17 07:56:25 -0300 |
| commit | 4df4c6c48950f6a5652ea0a4e0cd7da33d2aa554 (patch) | |
| tree | 1e61a84e0fcc12b5b86e72332c479e6e8f340433 /mullvad-rpc/src | |
| parent | 41a5db158de9ade895f85dd252e8cbba37080e30 (diff) | |
| download | mullvadvpn-4df4c6c48950f6a5652ea0a4e0cd7da33d2aa554.tar.xz mullvadvpn-4df4c6c48950f6a5652ea0a4e0cd7da33d2aa554.zip | |
Configure SNI domain for TLS handshake with master
Diffstat (limited to 'mullvad-rpc/src')
| -rw-r--r-- | mullvad-rpc/src/https_client_with_sni.rs | 157 | ||||
| -rw-r--r-- | mullvad-rpc/src/lib.rs | 9 |
2 files changed, 164 insertions, 2 deletions
diff --git a/mullvad-rpc/src/https_client_with_sni.rs b/mullvad-rpc/src/https_client_with_sni.rs new file mode 100644 index 0000000000..c7a082c989 --- /dev/null +++ b/mullvad-rpc/src/https_client_with_sni.rs @@ -0,0 +1,157 @@ +extern crate tokio_service; +extern crate tokio_tls; + +use std::fmt; +use std::io; +use std::str; +use std::sync::Arc; + +use futures::{Future, Poll}; +use hyper::client::{Client, Connect, HttpConnector}; +use hyper::{Body, Uri}; +use hyper_tls::MaybeHttpsStream; +use jsonrpc_client_http::ClientCreator; +pub use native_tls::Error; +use native_tls::TlsConnector; +use tokio_core::reactor::Handle; + +use self::tokio_service::Service; +use self::tokio_tls::TlsConnectorExt; + +/// Number of threads in the thread pool doing DNS resolutions. +/// Since DNS is resolved via blocking syscall they must be run on separate threads. +static DNS_THREADS: usize = 2; + +pub struct HttpsClientWithSni { + sni_hostname: String, +} + +impl HttpsClientWithSni { + pub fn new(sni_hostname: String) -> Self { + HttpsClientWithSni { sni_hostname } + } +} + +impl ClientCreator for HttpsClientWithSni { + type Connect = HttpsConnectorWithSni<HttpConnector>; + type Error = Error; + + fn create(&self, handle: &Handle) -> Result<Client<Self::Connect, Body>, Self::Error> { + let mut connector = HttpsConnectorWithSni::new(DNS_THREADS, handle)?; + connector.set_sni_hostname(Some(self.sni_hostname.clone())); + let client = Client::configure().connector(connector).build(handle); + Ok(client) + } +} + +/// A Connector for the `https` scheme. +#[derive(Clone)] +pub struct HttpsConnectorWithSni<T> { + sni_hostname: Option<String>, + http: T, + tls: Arc<TlsConnector>, +} + +impl HttpsConnectorWithSni<HttpConnector> { + /// Construct a new HttpsConnectorWithSni. + /// + /// Takes number of DNS worker threads. + /// + /// This uses hyper's default `HttpConnector`, and default `TlsConnector`. + /// If you wish to use something besides the defaults, use `From::from`. + fn new(threads: usize, handle: &Handle) -> Result<Self, Error> { + let mut http = HttpConnector::new(threads, handle); + http.enforce_http(false); + let tls = TlsConnector::builder()?.build()?; + Ok(HttpsConnectorWithSni::from((http, tls))) + } +} + +impl<T> HttpsConnectorWithSni<T> +where + T: Connect, +{ + /// Configure a hostname to use with SNI. + /// + /// Configures the TLS connection handshake to request a certificate for a given domain, + /// instead of the domain obtained from the URI. Use `None` to use the domain from the URI. + fn set_sni_hostname(&mut self, hostname: Option<String>) { + self.sni_hostname = hostname; + } +} + +impl<T> From<(T, TlsConnector)> for HttpsConnectorWithSni<T> { + fn from(args: (T, TlsConnector)) -> HttpsConnectorWithSni<T> { + HttpsConnectorWithSni { + sni_hostname: None, + http: args.0, + tls: Arc::new(args.1), + } + } +} + +impl<T> fmt::Debug for HttpsConnectorWithSni<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("HttpsConnectorWithSni").finish() + } +} + +impl<T: Connect> Service for HttpsConnectorWithSni<T> { + type Request = Uri; + type Response = MaybeHttpsStream<T::Output>; + type Error = io::Error; + type Future = HttpsConnecting<T::Output>; + + fn call(&self, uri: Uri) -> Self::Future { + let is_https = uri.scheme() == Some("https"); + let maybe_host = self.sni_hostname + .as_ref() + .map(String::as_str) + .or_else(|| uri.host()) + .map(str::to_owned); + let host = match maybe_host { + Some(host) => host, + None => { + return HttpsConnecting(Box::new(::futures::future::err(io::Error::new( + io::ErrorKind::InvalidInput, + "invalid url, missing host", + )))); + } + }; + let connecting = self.http.connect(uri); + let tls = self.tls.clone(); + + let fut: BoxedFut<T::Output> = if is_https { + let fut = connecting.and_then(move |tcp| { + tls.connect_async(&host, tcp) + .map(|conn| MaybeHttpsStream::Https(conn)) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + }); + Box::new(fut) + } else { + Box::new(connecting.map(|tcp| MaybeHttpsStream::Http(tcp))) + }; + HttpsConnecting(fut) + } +} + +type BoxedFut<T> = Box<Future<Item = MaybeHttpsStream<T>, Error = io::Error>>; + +/// A Future representing work to connect to a URL, and a TLS handshake. +pub struct HttpsConnecting<T>(BoxedFut<T>); + + +impl<T> Future for HttpsConnecting<T> { + type Item = MaybeHttpsStream<T>; + type Error = io::Error; + + fn poll(&mut self) -> Poll<Self::Item, Self::Error> { + self.0.poll() + } +} + +impl<T> fmt::Debug for HttpsConnecting<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad("HttpsConnecting") + } +} diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs index 6a1a682a1b..82f26b9f28 100644 --- a/mullvad-rpc/src/lib.rs +++ b/mullvad-rpc/src/lib.rs @@ -46,6 +46,9 @@ pub mod rest; mod cached_dns_resolver; use cached_dns_resolver::CachedDnsResolver; +mod https_client_with_sni; +use https_client_with_sni::HttpsClientWithSni; + static MASTER_API_HOST: &str = "api.mullvad.net"; @@ -77,7 +80,8 @@ impl MullvadRpcFactory { /// Spawns a tokio core on a new thread and returns a `HttpHandle` running on that core. pub fn new_connection(&mut self) -> Result<HttpHandle, HttpError> { - self.setup_connection(HttpTransport::new()?) + let client = HttpsClientWithSni::new(MASTER_API_HOST.to_owned()); + self.setup_connection(HttpTransport::with_client(client)?) } /// Create and returns a `HttpHandle` running on the given core handle. @@ -85,7 +89,8 @@ impl MullvadRpcFactory { &mut self, handle: &Handle, ) -> Result<HttpHandle, HttpError> { - self.setup_connection(HttpTransport::shared(handle)?) + let client = HttpsClientWithSni::new(MASTER_API_HOST.to_owned()); + self.setup_connection(HttpTransport::with_client_shared(client, handle)?) } fn setup_connection(&mut self, transport: HttpTransport) -> Result<HttpHandle, HttpError> { |
