Skip to content

Commit 148917b

Browse files
committed
Improve TLS support
- Use builder pattern - Support TLS on Mac/iOS/Windows as well as on Linux with OpenSSL - Support not pinning the server certificate Signed-off-by: Richard Whitehouse <richard.whitehouse@metaswitch.com>
1 parent b686adb commit 148917b

File tree

4 files changed

+142
-63
lines changed

4 files changed

+142
-63
lines changed

CHANGELOG.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88
### Added
99

1010
### Changed
11-
- Use hyper-openssl rather than hyper-tls
12-
- `https_connector` renamed `https_pinned_connector`
11+
- Use hyper-openssl on Linux, instead of hyper-tls
12+
- Use a builder pattern to created client connectors
13+
- Allow HTTPS connectors to be built which don't pin the server CA certificate
14+
- Allow HTTPS to work on Mac/Windows/iOS
15+
- Enforce that HTTPS is used if we are using a HTTPS connector.
1316
- Return Results, rather than unwrapping errors on client creation
1417
- openssl 0.10
1518

Cargo.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ chrono = "0.4.6"
3232

3333
[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies]
3434
hyper-openssl = "0.7.1"
35-
openssl = "0.10.14"
35+
openssl = "0.10.28"
36+
37+
[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies]
38+
hyper-tls = "0.4.1"
39+
native-tls = "0.2"

src/connector.rs

+131-57
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,144 @@
11
//! Utility methods for instantiating common connectors for clients.
2-
use std::path::Path;
2+
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
3+
use std::convert::From as _;
4+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
5+
use std::path::{Path, PathBuf};
36

47
use hyper;
58

6-
/// Returns a function which creates an http-connector. Used for instantiating
7-
/// clients with custom connectors
8-
pub fn http_connector() -> hyper::client::HttpConnector {
9-
hyper::client::HttpConnector::new(4)
9+
/// HTTP Connector construction
10+
#[derive(Debug)]
11+
pub struct Connector;
12+
13+
impl Connector {
14+
/// Alows building a HTTP(S) connector. Used for instantiating clients with custom
15+
/// connectors.
16+
pub fn builder() -> Builder {
17+
Builder { dns_threads: 4 }
18+
}
1019
}
1120

12-
/// Returns a function which creates an https-connector which is pinned to a specific
13-
/// CA certificate
14-
///
15-
/// # Arguments
16-
///
17-
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
18-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
19-
pub fn https_pinned_connector<CA>(
20-
ca_certificate: CA,
21-
) -> Result<hyper_openssl::HttpsConnector<hyper::client::HttpConnector>, openssl::error::ErrorStack>
22-
where
23-
CA: AsRef<Path>,
24-
{
25-
// SSL implementation
26-
let mut ssl = openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls())?;
21+
/// Builder for HTTP(S) connectors
22+
#[derive(Debug)]
23+
pub struct Builder {
24+
dns_threads: usize,
25+
}
2726

28-
let ca_certificate = ca_certificate.as_ref().to_owned();
27+
impl Builder {
28+
/// Configure the number of threads. Default is 4.
29+
pub fn dns_threads(mut self, threads: usize) -> Self {
30+
self.dns_threads = threads;
31+
self
32+
}
2933

30-
// Server authentication
31-
ssl.set_ca_file(ca_certificate)?;
34+
/// Use HTTPS instead of HTTP
35+
pub fn https(self) -> HttpsBuilder {
36+
HttpsBuilder {
37+
dns_threads: self.dns_threads,
38+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
39+
server_cert: None,
40+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
41+
client_cert: None,
42+
}
43+
}
3244

33-
let mut connector = hyper::client::HttpConnector::new(4);
34-
connector.enforce_http(false);
45+
/// Build a HTTP connector
46+
pub fn build(self) -> hyper::client::HttpConnector {
47+
hyper::client::HttpConnector::new(self.dns_threads)
48+
}
49+
}
3550

36-
hyper_openssl::HttpsConnector::<hyper::client::HttpConnector>::with_connector(connector, ssl)
51+
/// Builder for HTTPS connectors
52+
#[derive(Debug)]
53+
pub struct HttpsBuilder {
54+
dns_threads: usize,
55+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
56+
server_cert: Option<PathBuf>,
57+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
58+
client_cert: Option<(PathBuf, PathBuf)>,
3759
}
3860

39-
/// Returns a function which creates https-connectors for mutually authenticated connections.
40-
/// # Arguments
41-
///
42-
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
43-
/// * `client_key` - Path to the client private key
44-
/// * `client_certificate` - Path to the client's public certificate associated with the private key
45-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
46-
pub fn https_mutual_connector<CA, K, C>(
47-
ca_certificate: CA,
48-
client_key: K,
49-
client_certificate: C,
50-
) -> Result<hyper_openssl::HttpsConnector<hyper::client::HttpConnector>, openssl::error::ErrorStack>
51-
where
52-
CA: AsRef<Path>,
53-
K: AsRef<Path>,
54-
C: AsRef<Path>,
55-
{
56-
// SSL implementation
57-
let mut ssl = openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls())?;
58-
59-
// Server authentication
60-
ssl.set_ca_file(ca_certificate)?;
61-
62-
// Client authentication
63-
ssl.set_private_key_file(client_key, openssl::ssl::SslFiletype::PEM)?;
64-
ssl.set_certificate_chain_file(client_certificate)?;
65-
ssl.check_private_key()?;
66-
67-
let mut connector = hyper::client::HttpConnector::new(4);
68-
connector.enforce_http(false);
69-
hyper_openssl::HttpsConnector::<hyper::client::HttpConnector>::with_connector(connector, ssl)
61+
impl HttpsBuilder {
62+
/// Configure the number of threads. Default is 4.
63+
pub fn dns_threads(mut self, threads: usize) -> Self {
64+
self.dns_threads = threads;
65+
self
66+
}
67+
68+
/// Pin the CA certificate for the server's certificate.
69+
///
70+
/// # Arguments
71+
///
72+
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
73+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
74+
pub fn pin_server<CA>(mut self, ca_certificate: CA) -> Self
75+
where
76+
CA: AsRef<Path>,
77+
{
78+
self.server_cert = Some(ca_certificate.as_ref().to_owned());
79+
self
80+
}
81+
82+
/// Provide the Client Certificate and Key for the connection for Mutual TLS
83+
///
84+
/// # Arguments
85+
///
86+
/// * `client_key` - Path to the client private key
87+
/// * `client_certificate` - Path to the client's public certificate associated with the private key
88+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
89+
pub fn client<K, C>(mut self, client_key: K, client_certificate: C) -> Self
90+
where
91+
K: AsRef<Path>,
92+
C: AsRef<Path>,
93+
{
94+
self.client_cert = Some((
95+
client_key.as_ref().to_owned(),
96+
client_certificate.as_ref().to_owned(),
97+
));
98+
self
99+
}
100+
101+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
102+
/// Build the HTTPS connector. Will fail if the provided certificates/keys can't be loaded
103+
/// or the SSL connector can't be created
104+
pub fn build(
105+
self,
106+
) -> Result<
107+
hyper_openssl::HttpsConnector<hyper::client::HttpConnector>,
108+
openssl::error::ErrorStack,
109+
> {
110+
// SSL implementation
111+
let mut ssl = openssl::ssl::SslConnector::builder(openssl::ssl::SslMethod::tls())?;
112+
113+
if let Some(ca_certificate) = self.server_cert {
114+
// Server authentication
115+
ssl.set_ca_file(ca_certificate)?;
116+
}
117+
118+
if let Some((client_key, client_certificate)) = self.client_cert {
119+
// Client authentication
120+
ssl.set_private_key_file(client_key, openssl::ssl::SslFiletype::PEM)?;
121+
ssl.set_certificate_chain_file(client_certificate)?;
122+
ssl.check_private_key()?;
123+
}
124+
125+
let mut connector = hyper::client::HttpConnector::new(self.dns_threads);
126+
connector.enforce_http(false);
127+
hyper_openssl::HttpsConnector::<hyper::client::HttpConnector>::with_connector(
128+
connector, ssl,
129+
)
130+
}
131+
132+
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
133+
/// Build the HTTPS connector. Will if the SSL connector can't be created.
134+
pub fn build(
135+
self,
136+
) -> Result<hyper_tls::HttpsConnector<hyper::client::HttpConnector>, native_tls::Error> {
137+
let tls = native_tls::TlsConnector::new()?.into();
138+
let mut connector = hyper::client::HttpConnector::new(self.dns_threads);
139+
connector.enforce_http(false);
140+
let mut connector = hyper_tls::HttpsConnector::from((connector, tls));
141+
connector.https_only(true);
142+
Ok(connector)
143+
}
70144
}

src/lib.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ pub mod client;
2525

2626
/// Module with utilities for creating connectors with hyper.
2727
pub mod connector;
28-
pub use connector::http_connector;
29-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
30-
pub use connector::{https_mutual_connector, https_pinned_connector};
28+
pub use connector::Connector;
3129

3230
pub mod composites;
3331
pub use composites::{CompositeMakeService, CompositeService, NotFound};

0 commit comments

Comments
 (0)