diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index cc83f2e6..0ebb11e6 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -255,6 +255,7 @@ pub struct TlsConnector { use_sni: bool, accept_invalid_hostnames: bool, accept_invalid_certs: bool, + expect_custom_cn: Option, } impl TlsConnector { @@ -285,6 +286,7 @@ impl TlsConnector { use_sni: builder.use_sni, accept_invalid_hostnames: builder.accept_invalid_hostnames, accept_invalid_certs: builder.accept_invalid_certs, + expect_custom_cn: builder.expect_custom_cn.clone(), }) } @@ -297,11 +299,14 @@ impl TlsConnector { .configure()? .use_server_name_indication(self.use_sni) .verify_hostname(!self.accept_invalid_hostnames); + if self.accept_invalid_certs { ssl.set_verify(SslVerifyMode::NONE); } - let s = ssl.connect(domain, stream)?; + let expected_cn = self.expect_custom_cn.as_ref().map(|s| &**s).unwrap_or(domain); + + let s = ssl.connect(expected_cn, stream)?; Ok(TlsStream(s)) } } diff --git a/src/imp/schannel.rs b/src/imp/schannel.rs index d38e6f37..fd9cfb71 100644 --- a/src/imp/schannel.rs +++ b/src/imp/schannel.rs @@ -189,6 +189,7 @@ pub struct TlsConnector { use_sni: bool, accept_invalid_hostnames: bool, accept_invalid_certs: bool, + expect_custom_cn: Option, } impl TlsConnector { @@ -207,6 +208,7 @@ impl TlsConnector { use_sni: builder.use_sni, accept_invalid_hostnames: builder.accept_invalid_hostnames, accept_invalid_certs: builder.accept_invalid_certs, + expect_custom_cn: builder.expect_custom_cn.clone(), }) } @@ -221,9 +223,10 @@ impl TlsConnector { } let cred = builder.acquire(Direction::Outbound)?; let mut builder = tls_stream::Builder::new(); + let expected_cn = self.expect_custom_cn.as_ref().map(|s| &**s).unwrap_or(domain); builder .cert_store(self.roots.clone()) - .domain(domain) + .domain(expected_cn) .use_sni(self.use_sni) .accept_invalid_hostnames(self.accept_invalid_hostnames); if self.accept_invalid_certs { diff --git a/src/imp/security_framework.rs b/src/imp/security_framework.rs index 4008b080..7a1cfd5e 100644 --- a/src/imp/security_framework.rs +++ b/src/imp/security_framework.rs @@ -266,6 +266,7 @@ pub struct TlsConnector { use_sni: bool, danger_accept_invalid_hostnames: bool, danger_accept_invalid_certs: bool, + expect_custom_cn: Option, } impl TlsConnector { @@ -282,6 +283,7 @@ impl TlsConnector { use_sni: builder.use_sni, danger_accept_invalid_hostnames: builder.accept_invalid_hostnames, danger_accept_invalid_certs: builder.accept_invalid_certs, + expect_custom_cn: builder.expect_custom_cn.clone(), }) } @@ -303,8 +305,9 @@ impl TlsConnector { builder.use_sni(self.use_sni); builder.danger_accept_invalid_hostnames(self.danger_accept_invalid_hostnames); builder.danger_accept_invalid_certs(self.danger_accept_invalid_certs); + let expected_cn = self.expect_custom_cn.as_ref().map(|s| &**s).unwrap_or(domain); - match builder.handshake(domain, stream) { + match builder.handshake(expected_cn, stream) { Ok(stream) => Ok(TlsStream { stream, cert: None }), Err(e) => Err(e.into()), } diff --git a/src/lib.rs b/src/lib.rs index 34f3fa0a..4703d604 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -334,6 +334,7 @@ pub struct TlsConnectorBuilder { root_certificates: Vec, accept_invalid_certs: bool, accept_invalid_hostnames: bool, + expect_custom_cn: Option, use_sni: bool, } @@ -400,6 +401,26 @@ impl TlsConnectorBuilder { self } + /// Sets an expected CN the hostname validation is performed against. + /// + /// CN (Common Name) is the name that the identifies the connected server. + /// Its integrity is verified via PKI-based chain of trust, but it is up to the client + /// to check that CN matches the expected; i.e. that the connection is made to the + /// intended server, and not to a man-in-the-middle one. + /// This is automatically done unless `danger_accept_invalid_hostnames` is set. + /// + /// The default value is the domain name name of the server, + /// which is the normal usage in the context of HTTPS. + /// + /// In some cases, you might need to perform the hostname validation against a custom CN. + /// Use only if you know what you are doing. + /// + /// This value is ignored if `danger_accept_invalid_hostnames` is set. + pub fn expect_custom_cn(&mut self, cn: impl Into) -> &mut TlsConnectorBuilder { + self.expect_custom_cn = Some(cn.into()); + self + } + /// Controls the use of hostname verification. /// /// Defaults to `false`. @@ -462,6 +483,7 @@ impl TlsConnector { use_sni: true, accept_invalid_certs: false, accept_invalid_hostnames: false, + expect_custom_cn: None, } } diff --git a/src/test.rs b/src/test.rs index f52d7fa7..d70e6af6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -50,6 +50,23 @@ mod tests { builder.connect("goggle.com", s).unwrap(); } + #[test] + fn connect_custom_cn() { + // The certificate is for *.s3.amazonaws.com; + // a 2-level subdomain "bucket.name" doesn't match the wildcard + let builder = p!(TlsConnector::builder() + .build()); + let s = p!(TcpStream::connect("example.bucket.s3.amazonaws.com:443")); + builder.connect("example.bucket.s3.amazonaws.com", s).is_err(); + + // The certificate is for *.s3.amazonaws.com which "bucket_name" matches to + let builder = p!(TlsConnector::builder() + .expect_custom_cn("example-bucket.s3.amazonaws.com") + .build()); + let s = p!(TcpStream::connect("example.bucket.s3.amazonaws.com:443")); + builder.connect("example.bucket.s3.amazonaws.com", s).unwrap(); + } + #[test] fn server() { let buf = include_bytes!("../test/identity.p12");