Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement client certificate validation #162

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/imp/openssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -307,6 +307,18 @@ 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())?;
// below call is required if the ca is not already trusted
acceptor.cert_store_mut().add_cert((client_ca_cert.0).0.to_owned())?;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is required to run the tests as-is. Not sure if this should stay in.

}
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())?;
}
Expand Down
43 changes: 43 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Protocol>,
max_protocol: Option<Protocol>,
#[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<Certificate>
}

impl TlsAcceptorBuilder {
Expand All @@ -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<Certificate>) -> &mut TlsAcceptorBuilder {
self.client_cert_verification_ca_cert = client_cert_verification_ca_cert;
self
}

/// Creates a new `TlsAcceptor`.
pub fn build(&self) -> Result<TlsAcceptor> {
let acceptor = imp::TlsAcceptor::new(self)?;
Expand Down Expand Up @@ -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
}
}

Expand Down
67 changes: 66 additions & 1 deletion src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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() {
Expand Down