From 44fc78e97429d58298f4fefc5f8132f3d2154f32 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Sat, 9 Nov 2019 17:30:44 +0100 Subject: [PATCH] feat(transport): Add system root anchors for TLS (#114) As per #101, it is sometimes desirable to use standard web PKI roots for gRPC clients. This commit adds a method to ClientTlsConfig to add the trust roots from the system certificate store: - OpenSSL uses `openssl-probe` to search the system for roots. - Rustls uses `rustls-native-certs` to load the system roots. Enabling the `openssl-roots` or `rustls-roots` feature for `tonic` in `Cargo.toml` will add system roots by default when configuring a gRPC client. --- tonic/Cargo.toml | 4 ++++ tonic/src/lib.rs | 6 ++++++ tonic/src/transport/endpoint.rs | 9 +++++++++ tonic/src/transport/service/tls.rs | 24 ++++++++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index 716aa1785..feaba7a00 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -34,6 +34,8 @@ transport = [ ] openssl = ["openssl1", "tokio-openssl", "tls"] rustls = ["tokio-rustls", "tls"] +openssl-roots = ["openssl-probe"] +rustls-roots = ["rustls-native-certs"] tls = [] [[bench]] @@ -73,9 +75,11 @@ tower-load = { version = "=0.3.0-alpha.2", optional = true } # openssl tokio-openssl = { version = "=0.4.0-alpha.6", optional = true } openssl1 = { package = "openssl", version = "0.10", optional = true } +openssl-probe = { version = "0.1", optional = true } # rustls tokio-rustls = { version = "=0.12.0-alpha.5", optional = true } +rustls-native-certs = { version = "0.1", optional = true } [dev-dependencies] static_assertions = "1.0" diff --git a/tonic/src/lib.rs b/tonic/src/lib.rs index d7657d77b..8f1fdabda 100644 --- a/tonic/src/lib.rs +++ b/tonic/src/lib.rs @@ -22,8 +22,14 @@ //! for [`tonic-build`]. Enabled by default. //! - `openssl`: Enables the `openssl` based tls options for the `transport` feature`. Not //! enabled by default. +//! - `openssl-roots`: Adds system trust roots to `openssl`-based gRPC clients using the +//! `openssl-probe` crate. Not enabled by default. `openssl` must be enabled to use +//! `openssl-roots`. //! - `rustls`: Enables the `ruslts` based tls options for the `transport` feature`. Not //! enabled by default. +//! - `rustls-roots`: Adds system trust roots to `rustls`-based gRPC clients using the +//! `rustls-native-certs` crate. Not enabled by default. `rustls` must be enabled to use +//! `openssl-roots`. //! - `prost`: Enables the [`prost`] based gRPC [`Codec`] implementation. //! //! # Structure diff --git a/tonic/src/transport/endpoint.rs b/tonic/src/transport/endpoint.rs index 8a5ed4b37..832830059 100644 --- a/tonic/src/transport/endpoint.rs +++ b/tonic/src/transport/endpoint.rs @@ -259,18 +259,27 @@ impl ClientTlsConfig { } /// Sets the domain name against which to verify the server's TLS certificate. + /// + /// This has no effect if `rustls_client_config` or `openssl_connector` is used to configure + /// Rustls or OpenSSL respectively. pub fn domain_name(&mut self, domain_name: impl Into) -> &mut Self { self.domain = Some(domain_name.into()); self } /// Sets the CA Certificate against which to verify the server's TLS certificate. + /// + /// This has no effect if `rustls_client_config` or `openssl_connector` is used to configure + /// Rustls or OpenSSL respectively. pub fn ca_certificate(&mut self, ca_certificate: Certificate) -> &mut Self { self.cert = Some(ca_certificate); self } /// Sets the client identity to present to the server. + /// + /// This has no effect if `rustls_client_config` or `openssl_connector` is used to configure + /// Rustls or OpenSSL respectively. pub fn identity(&mut self, identity: Identity) -> &mut Self { self.identity = Some(identity); self diff --git a/tonic/src/transport/service/tls.rs b/tonic/src/transport/service/tls.rs index 2c8532be3..7a9b2d114 100644 --- a/tonic/src/transport/service/tls.rs +++ b/tonic/src/transport/service/tls.rs @@ -6,6 +6,10 @@ use openssl1::{ ssl::{select_next_proto, AlpnError, SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, x509::{store::X509StoreBuilder, X509}, }; +#[cfg(feature = "openssl-roots")] +use openssl_probe; +#[cfg(feature = "rustls-roots")] +use rustls_native_certs; use std::{fmt, sync::Arc}; use tokio::net::TcpStream; #[cfg(feature = "rustls")] @@ -37,6 +41,8 @@ enum TlsError { CertificateParseError, #[cfg(feature = "rustls")] PrivateKeyParseError, + #[cfg(feature = "openssl-roots")] + TrustAnchorsConfigurationError(openssl1::error::ErrorStack), } #[derive(Clone)] @@ -63,6 +69,15 @@ impl TlsConnector { let mut config = SslConnector::builder(SslMethod::tls())?; config.set_alpn_protos(ALPN_H2_WIRE)?; + #[cfg(feature = "openssl-roots")] + { + openssl_probe::init_ssl_cert_env_vars(); + match config.cert_store_mut().set_default_paths() { + Ok(()) => (), + Err(e) => return Err(Box::new(TlsError::TrustAnchorsConfigurationError(e))), + }; + } + if let Some(cert) = cert { let ca = X509::from_pem(&cert.pem[..])?; config.cert_store_mut().add_cert(ca)?; @@ -106,6 +121,11 @@ impl TlsConnector { config.set_single_client_cert(client_cert, client_key); } + #[cfg(feature = "rustls-roots")] + { + config.root_store = rustls_native_certs::load_native_certs()?; + } + if let Some(cert) = ca_cert { let mut buf = std::io::Cursor::new(&cert.pem[..]); config.root_store.add_pem_file(&mut buf).unwrap(); @@ -336,6 +356,10 @@ impl fmt::Display for TlsError { f, "Error parsing TLS private key - no RSA or PKCS8-encoded keys found." ), + #[cfg(feature = "openssl-roots")] + TlsError::TrustAnchorsConfigurationError(stack) => { + f.write_fmt(format_args!("Error adding trust anchors - {}", stack)) + } } } }