From b4c5e93700f384cc7a9dc003500a0af2bceaca0c Mon Sep 17 00:00:00 2001 From: Guillaume Xavier Taillon Date: Fri, 24 Apr 2020 23:50:48 +0000 Subject: [PATCH 1/4] Add client certificate validation. Related to sfackler/rust-native-tls#161 --- src/imp/openssl.rs | 13 ++++++++++++- src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index 421eda29..a3088fbf 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -16,7 +16,7 @@ use std::fmt; use std::io; use std::sync::Once; -use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder}; +use {Protocol, TlsAcceptorBuilder, TlsConnectorBuilder, TlsClientCertificateVerification}; use self::openssl::pkey::Private; #[cfg(have_min_max_version)] @@ -307,6 +307,17 @@ impl TlsAcceptor { let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; acceptor.set_private_key(&builder.identity.0.pkey)?; acceptor.set_certificate(&builder.identity.0.cert)?; + + if let Some(client_ca_cert) = &builder.client_cert_verification_ca_cert { + acceptor.add_client_ca((client_ca_cert.0).0.as_ref())?; + } + let verify_mode = match &builder.client_cert_verification { + TlsClientCertificateVerification::DoNotRequestCertificate => SslVerifyMode::NONE, + TlsClientCertificateVerification::RequestCertificate => SslVerifyMode::PEER, + TlsClientCertificateVerification::RequireCertificate => SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT, + }; + acceptor.set_verify(verify_mode); + for cert in builder.identity.0.chain.iter().rev() { acceptor.add_extra_chain_cert(cert.to_owned())?; } diff --git a/src/lib.rs b/src/lib.rs index 3edcb86f..055af9f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -482,11 +482,30 @@ impl TlsConnector { } } +/// Client certificate verification modes +pub enum TlsClientCertificateVerification { + /// The server will not request certificates from the client. + /// + /// # Warning + /// The client will not be able to send any certificates with this setting. + DoNotRequestCertificate, + /// The server will request a certificate from the client, then will validate + /// any certificate it receives. The client may choose not to send any. + RequestCertificate, + /// The server will request a certificate from the client, then will validate + /// any certificate it receives or reject the connection none are provided. + RequireCertificate, +} + /// A builder for `TlsAcceptor`s. pub struct TlsAcceptorBuilder { identity: Identity, min_protocol: Option, max_protocol: Option, + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + client_cert_verification: TlsClientCertificateVerification, + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + client_cert_verification_ca_cert: Option } impl TlsAcceptorBuilder { @@ -510,6 +529,26 @@ impl TlsAcceptorBuilder { self } + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + /// Sets the verification mode for client certificates. + /// + /// Defaults to `TlsClientCertificateVerification::DoNotRequestCertificate`. + pub fn client_cert_verification(&mut self, client_cert_verification: TlsClientCertificateVerification) -> &mut TlsAcceptorBuilder { + self.client_cert_verification = client_cert_verification; + self + } + + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + /// Sets which ca to tell the client is acceptable to send to the server. + /// + /// A value of `None` will not tell the client it is acceptable to send certificates signed by any ca. + /// + /// Defaults `None`. + pub fn client_cert_verification_ca_cert(&mut self, client_cert_verification_ca_cert: Option) -> &mut TlsAcceptorBuilder { + self.client_cert_verification_ca_cert = client_cert_verification_ca_cert; + self + } + /// Creates a new `TlsAcceptor`. pub fn build(&self) -> Result { let acceptor = imp::TlsAcceptor::new(self)?; @@ -574,6 +613,10 @@ impl TlsAcceptor { identity, min_protocol: Some(Protocol::Tlsv10), max_protocol: None, + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + client_cert_verification: TlsClientCertificateVerification::DoNotRequestCertificate, + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + client_cert_verification_ca_cert: None } } From 28578ac9ebe429354ae1047e8dc385ff85ad7465 Mon Sep 17 00:00:00 2001 From: Guillaume Xavier Taillon Date: Sat, 25 Apr 2020 00:22:24 +0000 Subject: [PATCH 2/4] Update tests --- src/test.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index f52d7fa7..19a85ec7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -126,7 +126,7 @@ mod tests { } #[test] - fn peer_certificate() { + fn no_peer_certificate() { let buf = include_bytes!("../test/identity.p12"); let identity = p!(Identity::from_pkcs12(buf, "mypass")); let builder = p!(TlsAcceptor::new(identity)); @@ -155,6 +155,71 @@ mod tests { p!(j.join()); } + #[test] + fn require_peer_certificate_no_cert() { + let buf = include_bytes!("../test/identity.p12"); + let identity = p!(Identity::from_pkcs12(buf, "mypass")); + let builder = p!(TlsAcceptor::builder(identity) + .client_cert_verification(TlsClientCertificateVerification::RequireCertificate) + .build()); + + let listener = p!(TcpListener::bind("0.0.0.0:0")); + let port = p!(listener.local_addr()).port(); + + let j = thread::spawn(move || { + let socket = p!(listener.accept()).0; + let socket = builder.accept(socket); + assert!(socket.is_err()); + }); + + let root_ca = include_bytes!("../test/root-ca.der"); + let root_ca = Certificate::from_der(root_ca).unwrap(); + + let socket = p!(TcpStream::connect(("localhost", port))); + let builder = p!(TlsConnector::builder() + .add_root_certificate(root_ca) + .build()); + let socket = builder.connect("foobar.com", socket); + assert!(socket.is_err()); + + p!(j.join()); + } + + #[test] + fn request_peer_certificate_some_cert() { + let buf = include_bytes!("../test/identity.p12"); + let identity = p!(Identity::from_pkcs12(buf, "mypass")); + let root_ca = include_bytes!("../test/root-ca.der"); + let root_ca = Certificate::from_der(root_ca).unwrap(); + let builder = p!(TlsAcceptor::builder(identity.clone()) + .client_cert_verification(TlsClientCertificateVerification::RequestCertificate) + .client_cert_verification_ca_cert(Some(root_ca.clone())) + .build()); + + let listener = p!(TcpListener::bind("0.0.0.0:0")); + let port = p!(listener.local_addr()).port(); + + let cert_der = include_bytes!("../test/cert.der"); + let j = thread::spawn(move || { + let socket = p!(listener.accept()).0; + let socket = p!(builder.accept(socket)); + let cert = socket.peer_certificate().unwrap().unwrap(); + assert_eq!(cert.to_der().unwrap(), &cert_der[..]); + }); + + + let socket = p!(TcpStream::connect(("localhost", port))); + let builder = p!(TlsConnector::builder() + .add_root_certificate(root_ca) + .identity(identity) + .build()); + let socket = p!(builder.connect("foobar.com", socket)); + + let cert = socket.peer_certificate().unwrap().unwrap(); + assert_eq!(cert.to_der().unwrap(), &cert_der[..]); + + p!(j.join()); + } #[test] fn server_tls11_only() { From 9fe88c7bf25de1fbe045d12d4cda987094277284 Mon Sep 17 00:00:00 2001 From: Guillaume Xavier Taillon Date: Sat, 25 Apr 2020 00:22:43 +0000 Subject: [PATCH 3/4] Automatically trust the client ca --- src/imp/openssl.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/imp/openssl.rs b/src/imp/openssl.rs index a3088fbf..5b0003eb 100644 --- a/src/imp/openssl.rs +++ b/src/imp/openssl.rs @@ -310,6 +310,8 @@ impl TlsAcceptor { if let Some(client_ca_cert) = &builder.client_cert_verification_ca_cert { acceptor.add_client_ca((client_ca_cert.0).0.as_ref())?; + // below call is required if the ca is not already trusted + acceptor.cert_store_mut().add_cert((client_ca_cert.0).0.to_owned())?; } let verify_mode = match &builder.client_cert_verification { TlsClientCertificateVerification::DoNotRequestCertificate => SslVerifyMode::NONE, @@ -317,7 +319,6 @@ impl TlsAcceptor { TlsClientCertificateVerification::RequireCertificate => SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT, }; acceptor.set_verify(verify_mode); - for cert in builder.identity.0.chain.iter().rev() { acceptor.add_extra_chain_cert(cert.to_owned())?; } From c3d11283d3b83f2e7d23fa8fc1cc5a1933034c26 Mon Sep 17 00:00:00 2001 From: Guillaume Xavier Taillon Date: Sat, 25 Apr 2020 01:35:14 +0000 Subject: [PATCH 4/4] Only test client cert on impl'd targets --- src/test.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test.rs b/src/test.rs index 19a85ec7..7077633f 100644 --- a/src/test.rs +++ b/src/test.rs @@ -156,6 +156,7 @@ mod tests { p!(j.join()); } #[test] + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] fn require_peer_certificate_no_cert() { let buf = include_bytes!("../test/identity.p12"); let identity = p!(Identity::from_pkcs12(buf, "mypass")); @@ -186,6 +187,7 @@ mod tests { } #[test] + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] fn request_peer_certificate_some_cert() { let buf = include_bytes!("../test/identity.p12"); let identity = p!(Identity::from_pkcs12(buf, "mypass"));