diff --git a/Cargo.lock b/Cargo.lock index a52d536156..2b1e75a5a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4162,6 +4162,7 @@ dependencies = [ "async-trait", "futures", "log", + "rustls", "rustls-pemfile", "webpki", "webpki-roots", diff --git a/commons/zenoh-cfg-properties/src/config.rs b/commons/zenoh-cfg-properties/src/config.rs index 0300310fe8..68cfa03b8e 100644 --- a/commons/zenoh-cfg-properties/src/config.rs +++ b/commons/zenoh-cfg-properties/src/config.rs @@ -343,24 +343,15 @@ pub const ZN_MAX_SESSIONS_MULTICAST_KEY: u64 = 0x84; pub const ZN_MAX_SESSIONS_MULTICAST_STR: &str = "max_sessions_multicast"; pub const ZN_MAX_SESSIONS_MULTICAST_DEFAULT: &str = "1024"; -/// The file path containing the TLS server private key. -/// String key: `"tls_client_private_key"`. -/// Accepted values: ``. -/// Default value: None. +/// The file path containing the TLS client private key. pub const ZN_TLS_CLIENT_PRIVATE_KEY_KEY: u64 = 0x85; pub const ZN_TLS_CLIENT_PRIVATE_KEY_STR: &str = "tls_client_private_key"; -/// The file path containing the TLS server certificate. -/// String key: `"tls_client_private_key"`. -/// Accepted values: ``. -/// Default value: None. +/// The file path containing the TLS client certificate. pub const ZN_TLS_CLIENT_CERTIFICATE_KEY: u64 = 0x86; pub const ZN_TLS_CLIENT_CERTIFICATE_STR: &str = "tls_client_certificate"; -/// The file path containing the TLS server certificate. -/// String key: `"tls_private_key"`. -/// Accepted values: `"true"`, `"false"`. -/// Default value: `"false"`. +/// Whether to use tls with client authentication pub const ZN_TLS_CLIENT_AUTH_KEY: u64 = 0x87; pub const ZN_TLS_CLIENT_AUTH_STR: &str = "tls_client_auth"; pub const ZN_TLS_CLIENT_AUTH_DEFAULT: &str = ZN_FALSE; @@ -372,3 +363,8 @@ pub const ZN_TLS_CLIENT_AUTH_DEFAULT: &str = ZN_FALSE; pub const ZN_QUERIES_DEFAULT_TIMEOUT_KEY: u64 = 0x88; pub const ZN_QUERIES_DEFAULT_TIMEOUT_STR: &str = "local_routing"; pub const ZN_QUERIES_DEFAULT_TIMEOUT_DEFAULT: &str = "10000"; + +/// Whether or not to verify that servers have certs valid for their ip or common name +pub const ZN_TLS_SERVER_NAME_VERIFICATION_KEY: u64 = 0x89; +pub const ZN_TLS_SERVER_NAME_VERIFICATION_STR: &str = "server_name_verification"; +pub const ZN_TLS_SERVER_NAME_VERIFICATION_DEFAULT: &str = ZN_TRUE; diff --git a/commons/zenoh-config/src/lib.rs b/commons/zenoh-config/src/lib.rs index cdc20f261f..500deccd6e 100644 --- a/commons/zenoh-config/src/lib.rs +++ b/commons/zenoh-config/src/lib.rs @@ -282,6 +282,7 @@ validated_struct::validator! { client_auth: Option, client_private_key: Option, client_certificate: Option, + server_name_verification: Option }, pub compression: #[derive(Default)] /// **Experimental** compression feature. diff --git a/io/zenoh-links/zenoh-link-tls/Cargo.toml b/io/zenoh-links/zenoh-link-tls/Cargo.toml index 3282dfffc8..d862406e44 100644 --- a/io/zenoh-links/zenoh-link-tls/Cargo.toml +++ b/io/zenoh-links/zenoh-link-tls/Cargo.toml @@ -26,6 +26,7 @@ description = "Internal crate for zenoh." [dependencies] async-rustls = { workspace = true } +rustls = { workspace = true } async-std = { workspace = true } async-trait = { workspace = true } futures = { workspace = true } diff --git a/io/zenoh-links/zenoh-link-tls/src/lib.rs b/io/zenoh-links/zenoh-link-tls/src/lib.rs index cd59268457..b965fd620f 100644 --- a/io/zenoh-links/zenoh-link-tls/src/lib.rs +++ b/io/zenoh-links/zenoh-link-tls/src/lib.rs @@ -23,7 +23,7 @@ use async_std::net::ToSocketAddrs; use async_trait::async_trait; use config::{ TLS_CLIENT_AUTH, TLS_CLIENT_CERTIFICATE_FILE, TLS_CLIENT_PRIVATE_KEY_FILE, - TLS_ROOT_CA_CERTIFICATE_FILE, TLS_SERVER_CERTIFICATE_FILE, TLS_SERVER_PRIVATE_KEY_FILE, + TLS_ROOT_CA_CERTIFICATE_FILE, TLS_SERVER_CERTIFICATE_FILE, TLS_SERVER_PRIVATE_KEY_FILE, TLS_SERVER_NAME_VERIFICATION, }; use zenoh_cfg_properties::Properties; use zenoh_config::{Config, ZN_FALSE, ZN_TRUE}; @@ -33,6 +33,7 @@ use zenoh_protocol::core::{endpoint::Address, Locator}; use zenoh_result::{bail, zerror, ZResult}; mod unicast; +mod verify; pub use unicast::*; // Default MTU (TLS PDU) in bytes. @@ -99,6 +100,12 @@ impl ConfigurationInspector for TlsConfigurator { tls_client_certificate.into(), ); } + if let Some(server_name_verification) = c.server_name_verification() { + match server_name_verification { + true => properties.insert(TLS_SERVER_NAME_VERIFICATION.into(), ZN_TRUE.into()), + false => properties.insert(TLS_SERVER_NAME_VERIFICATION.into(), ZN_FALSE.into()), + }; + } Ok(properties) } @@ -136,6 +143,9 @@ pub mod config { pub const TLS_CLIENT_AUTH: &str = ZN_TLS_CLIENT_AUTH_STR; pub const TLS_CLIENT_AUTH_DEFAULT: &str = ZN_TLS_CLIENT_AUTH_DEFAULT; + + pub const TLS_SERVER_NAME_VERIFICATION: &str = ZN_TLS_SERVER_NAME_VERIFICATION_STR; + pub const TLS_SERVER_NAME_VERIFICATION_DEFAULT: &str = ZN_TLS_SERVER_NAME_VERIFICATION_DEFAULT; } pub async fn get_tls_addr(address: &Address<'_>) -> ZResult { diff --git a/io/zenoh-links/zenoh-link-tls/src/unicast.rs b/io/zenoh-links/zenoh-link-tls/src/unicast.rs index 421885c1ff..8eed405f61 100644 --- a/io/zenoh-links/zenoh-link-tls/src/unicast.rs +++ b/io/zenoh-links/zenoh-link-tls/src/unicast.rs @@ -12,8 +12,9 @@ // ZettaScale Zenoh Team, // use crate::{ - config::*, get_tls_addr, get_tls_host, get_tls_server_name, TLS_ACCEPT_THROTTLE_TIME, - TLS_DEFAULT_MTU, TLS_LINGER_TIMEOUT, TLS_LOCATOR_PREFIX, + config::*, get_tls_addr, get_tls_host, get_tls_server_name, + verify::WebPkiVerifierAnyServerName, TLS_ACCEPT_THROTTLE_TIME, TLS_DEFAULT_MTU, + TLS_LINGER_TIMEOUT, TLS_LOCATOR_PREFIX, }; use async_rustls::rustls::server::AllowAnyAuthenticatedClient; use async_rustls::rustls::version::TLS13; @@ -603,6 +604,18 @@ impl TlsClientConfig { client_auth = value.parse()? } + let server_name_verification: bool = + if let Some(value) = config.get(TLS_SERVER_NAME_VERIFICATION) { + value + } else { + TLS_SERVER_NAME_VERIFICATION_DEFAULT + } + .parse()?; + + if !server_name_verification { + log::warn!("Skipping name verification of servers"); + } + // Allows mixed user-generated CA and webPKI CA log::debug!("Loading default Web PKI certificates."); let mut root_cert_store: RootCertStore = RootCertStore { @@ -640,19 +653,37 @@ impl TlsClientConfig { bail!("No private key found"); } - ClientConfig::builder() + let builder = ClientConfig::builder() .with_safe_default_cipher_suites() .with_safe_default_kx_groups() .with_protocol_versions(&[&TLS13]) - .unwrap() - .with_root_certificates(root_cert_store) - .with_single_cert(certs, keys.remove(0)) - .expect("bad certificate/key") + .expect("Config parameters should be valid"); + + if server_name_verification { + builder + .with_root_certificates(root_cert_store) + .with_client_auth_cert(certs, keys.remove(0)) + } else { + builder + .with_custom_certificate_verifier(Arc::new(WebPkiVerifierAnyServerName::new( + root_cert_store, + ))) + .with_client_auth_cert(certs, keys.remove(0)) + } + .expect("bad certificate/key") } else { - ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_cert_store) - .with_no_client_auth() + let builder = ClientConfig::builder().with_safe_defaults(); + if server_name_verification { + builder + .with_root_certificates(root_cert_store) + .with_no_client_auth() + } else { + builder + .with_custom_certificate_verifier(Arc::new(WebPkiVerifierAnyServerName::new( + root_cert_store, + ))) + .with_no_client_auth() + } }; Ok(TlsClientConfig { client_config: cc }) } diff --git a/io/zenoh-links/zenoh-link-tls/src/verify.rs b/io/zenoh-links/zenoh-link-tls/src/verify.rs new file mode 100644 index 0000000000..126c34ab16 --- /dev/null +++ b/io/zenoh-links/zenoh-link-tls/src/verify.rs @@ -0,0 +1,39 @@ +use std::time::SystemTime; +use rustls::client::verify_server_cert_signed_by_trust_anchor; +use rustls::server::ParsedCertificate; +use async_rustls::rustls::{client::{ServerCertVerifier, ServerCertVerified}, Certificate, ServerName, RootCertStore}; + +impl ServerCertVerifier for WebPkiVerifierAnyServerName { + /// Will verify the certificate is valid in the following ways: + /// - Signed by a trusted `RootCertStore` CA + /// - Not Expired + fn verify_server_cert( + &self, + end_entity: &Certificate, + intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + now: SystemTime, + ) -> Result { + let cert = ParsedCertificate::try_from(end_entity)?; + verify_server_cert_signed_by_trust_anchor(&cert, &self.roots, intermediates, now)?; + Ok(ServerCertVerified::assertion()) + } +} + +/// `ServerCertVerifier` that verifies that the server is signed by a trusted root, but allows any serverName +/// see the trait impl for more information. +pub struct WebPkiVerifierAnyServerName { + roots: RootCertStore, +} + +#[allow(unreachable_pub)] +impl WebPkiVerifierAnyServerName { + /// Constructs a new `WebPkiVerifierAnyServerName`. + /// + /// `roots` is the set of trust anchors to trust for issuing server certs. + pub fn new(roots: RootCertStore) -> Self { + Self { roots } + } +}