Skip to content

Commit

Permalink
feat(tls): add an option for optional TLS client authentication
Browse files Browse the repository at this point in the history
Previously there were only two options for client authentication –
either no authentication or mandatory authentication. With this change,
a server can allow for optional authentication with a given root CA
certificate and enforce client authentication on a per-request basis.

Refs: #687
  • Loading branch information
dufkan committed Nov 26, 2022
1 parent 933f560 commit 0280b64
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 12 deletions.
3 changes: 2 additions & 1 deletion examples/src/tls_client_auth/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use pb::{EchoRequest, EchoResponse};
use std::pin::Pin;
use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig};
use tonic::{Request, Response, Status};
use tonic::transport::server::ClientTlsAuth;

type EchoResult<T> = Result<Response<T>, Status>;
type ResponseStream = Pin<Box<dyn Stream<Item = Result<EchoResponse, Status>> + Send>>;
Expand Down Expand Up @@ -67,7 +68,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

let tls = ServerTlsConfig::new()
.identity(server_identity)
.client_ca_root(client_ca_cert);
.client_auth(ClientTlsAuth::Mandatory(client_ca_cert));

Server::builder()
.tls_config(tls)?
Expand Down
3 changes: 3 additions & 0 deletions tonic/src/transport/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ pub use self::channel::ClientTlsConfig;
pub use self::server::ServerTlsConfig;
#[cfg(feature = "tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub use self::server::ClientTlsAuth;
#[cfg(feature = "tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
pub use self::tls::Identity;

type BoxFuture<T, E> =
Expand Down
2 changes: 2 additions & 0 deletions tonic/src/transport/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub use crate::server::NamedService;
pub use conn::{Connected, TcpConnectInfo};
#[cfg(feature = "tls")]
pub use tls::ServerTlsConfig;
#[cfg(feature = "tls")]
pub use tls::ClientTlsAuth;

#[cfg(feature = "tls")]
pub use conn::TlsConnectInfo;
Expand Down
26 changes: 20 additions & 6 deletions tonic/src/transport/server/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::fmt;
#[derive(Clone, Default)]
pub struct ServerTlsConfig {
identity: Option<Identity>,
client_ca_root: Option<Certificate>,
client_auth: ClientTlsAuth,
}

#[cfg(feature = "tls")]
Expand All @@ -26,7 +26,7 @@ impl ServerTlsConfig {
pub fn new() -> Self {
ServerTlsConfig {
identity: None,
client_ca_root: None,
client_auth: ClientTlsAuth::None,
}
}

Expand All @@ -38,15 +38,29 @@ impl ServerTlsConfig {
}
}

/// Sets a certificate against which to validate client TLS certificates.
pub fn client_ca_root(self, cert: Certificate) -> Self {
/// Sets the [`ClientTlsAuth`] approach.
pub fn client_auth(self, client_auth: ClientTlsAuth) -> Self {
ServerTlsConfig {
client_ca_root: Some(cert),
client_auth,
..self
}
}

pub(crate) fn tls_acceptor(&self) -> Result<TlsAcceptor, crate::Error> {
TlsAcceptor::new(self.identity.clone().unwrap(), self.client_ca_root.clone())
TlsAcceptor::new(self.identity.clone().unwrap(), self.client_auth.clone())
}
}

/// Client TLS authentication options.
#[cfg(feature = "tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "tls")))]
#[derive(Clone, Debug, Default)]
pub enum ClientTlsAuth {
/// No client authentication.
#[default]
None,
/// Optional client authentication with a provided root CA certificate.
Optional(Certificate),
/// Mandatory client authentication with a provided root CA certificate.
Mandatory(Certificate),
}
16 changes: 11 additions & 5 deletions tonic/src/transport/service/tls.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::io::BoxedIo;
use crate::transport::{
server::{Connected, TlsStream},
server::{Connected, TlsStream, ClientTlsAuth},
Certificate, Identity,
};
#[cfg(feature = "tls-roots")]
Expand Down Expand Up @@ -126,13 +126,19 @@ impl TlsAcceptor {
#[cfg(feature = "tls")]
pub(crate) fn new(
identity: Identity,
client_ca_root: Option<Certificate>,
client_auth: ClientTlsAuth
) -> Result<Self, crate::Error> {
let builder = ServerConfig::builder().with_safe_defaults();

let builder = match client_ca_root {
None => builder.with_no_client_auth(),
Some(cert) => {
let builder = match client_auth {
ClientTlsAuth::None => builder.with_no_client_auth(),
ClientTlsAuth::Optional(cert) => {
use tokio_rustls::rustls::server::AllowAnyAnonymousOrAuthenticatedClient;
let mut roots = RootCertStore::empty();
rustls_keys::add_certs_from_pem(std::io::Cursor::new(&cert.pem[..]), &mut roots)?;
builder.with_client_cert_verifier(AllowAnyAnonymousOrAuthenticatedClient::new(roots))
}
ClientTlsAuth::Mandatory(cert) => {
use tokio_rustls::rustls::server::AllowAnyAuthenticatedClient;
let mut roots = RootCertStore::empty();
rustls_keys::add_certs_from_pem(std::io::Cursor::new(&cert.pem[..]), &mut roots)?;
Expand Down

0 comments on commit 0280b64

Please sign in to comment.