Skip to content

Commit

Permalink
Ability to disable server name checking in TLS client (#118)
Browse files Browse the repository at this point in the history
* Add self_signed and full_pki constructors to the TLS ClientConfig and deprecate legacy new

* Add the ability to disable server name checking in the TLS client

* fix feature builds
  • Loading branch information
jadamcrain committed Apr 27, 2023
1 parent 11a761b commit a5cc6ad
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 42 deletions.
75 changes: 54 additions & 21 deletions ffi/rodbus-ffi/src/client.rs
Expand Up @@ -117,29 +117,9 @@ pub(crate) unsafe fn client_channel_create_tls(
decode_level: ffi::DecodeLevel,
listener: ffi::ClientStateListener,
) -> Result<*mut crate::ClientChannel, ffi::ParamError> {
use std::path::Path;

let runtime = runtime.as_ref().ok_or(ffi::ParamError::NullParameter)?;

let password = tls_config.password().to_string_lossy();
let optional_password = match password.as_ref() {
"" => None,
password => Some(password),
};

let tls_config = rodbus::client::TlsClientConfig::new(
&tls_config.dns_name().to_string_lossy(),
Path::new(tls_config.peer_cert_path().to_string_lossy().as_ref()),
Path::new(tls_config.local_cert_path().to_string_lossy().as_ref()),
Path::new(tls_config.private_key_path().to_string_lossy().as_ref()),
optional_password,
tls_config.min_tls_version().into(),
tls_config.certificate_mode().into(),
)
.map_err(|err| {
tracing::error!("TLS error: {}", err);
err
})?;
let tls_config: rodbus::client::TlsClientConfig = tls_config.try_into()?;

let host_addr = get_host_addr(host, port)?;

Expand Down Expand Up @@ -417,3 +397,56 @@ impl From<ffi::PortStateListener> for Box<dyn Listener<rodbus::client::PortState
Box::new(PortStateListener { inner: x })
}
}

#[cfg(feature = "tls")]
impl TryFrom<ffi::TlsClientConfig> for rodbus::client::TlsClientConfig {
type Error = ffi::ParamError;

fn try_from(value: ffi::TlsClientConfig) -> Result<Self, Self::Error> {
use std::path::Path;

let optional_password = match value.password().to_str()? {
"" => None,
password => Some(password),
};

let peer_cert_path = Path::new(value.peer_cert_path().to_str()?);
let local_cert_path = Path::new(value.local_cert_path().to_str()?);
let private_key_path = Path::new(value.private_key_path().to_str()?);

let config = match value.certificate_mode() {
ffi::CertificateMode::AuthorityBased => {
let expected_subject_name = value.dns_name().to_str()?;

let expected_subject_name =
if value.allow_server_name_wildcard && expected_subject_name == "*" {
None
} else {
Some(expected_subject_name.to_string())
};

rodbus::client::TlsClientConfig::full_pki(
expected_subject_name,
peer_cert_path,
local_cert_path,
private_key_path,
optional_password,
value.min_tls_version().into(),
)
}
ffi::CertificateMode::SelfSigned => rodbus::client::TlsClientConfig::self_signed(
peer_cert_path,
local_cert_path,
private_key_path,
optional_password,
value.min_tls_version().into(),
),
}
.map_err(|err| {
tracing::error!("TLS error: {}", err);
err
})?;

Ok(config)
}
}
7 changes: 7 additions & 0 deletions ffi/rodbus-ffi/src/lib.rs
Expand Up @@ -24,6 +24,7 @@ pub use iterator::*;
pub use list::*;
pub use runtime::*;
pub use server::*;
use std::str::Utf8Error;

pub mod ffi;

Expand Down Expand Up @@ -65,3 +66,9 @@ impl From<crate::runtime::RuntimeError> for std::os::raw::c_int {
err.into()
}
}

impl From<Utf8Error> for crate::ffi::ParamError {
fn from(_: Utf8Error) -> Self {
Self::InvalidUtf8
}
}
3 changes: 3 additions & 0 deletions ffi/rodbus-schema/src/client.rs
Expand Up @@ -465,6 +465,7 @@ fn build_tls_client_config(
) -> BackTraced<FunctionArgStructHandle> {
let min_tls_version_field = Name::create("min_tls_version")?;
let certificate_mode_field = Name::create("certificate_mode")?;
let allow_server_name_wildcard = Name::create("allow_server_name_wildcard")?;

let tls_client_config = lib.declare_function_argument_struct("tls_client_config")?;
let tls_client_config = lib.define_function_argument_struct(tls_client_config)?
Expand Down Expand Up @@ -495,11 +496,13 @@ fn build_tls_client_config(
"Minimum TLS version allowed",
)?
.add(&certificate_mode_field, common.certificate_mode.clone(), "Certificate validation mode")?
.add(allow_server_name_wildcard.clone(), Primitive::Bool, "If set to true, a '*' may be used for {struct:tls_client_config.dns_name} to bypass server name validation")?
.doc("TLS client configuration")?
.end_fields()?
.begin_initializer("init", InitializerType::Normal, "Initialize a TLS client configuration")?
.default_variant(&min_tls_version_field, "v12")?
.default_variant(&certificate_mode_field, "authority_based")?
.default(&allow_server_name_wildcard, false)?
.end_initializer()?
.build()?;

Expand Down
1 change: 1 addition & 0 deletions ffi/rodbus-schema/src/common.rs
Expand Up @@ -125,6 +125,7 @@ fn build_error_type(lib: &mut LibraryBuilder) -> BackTraced<ErrorTypeHandle> {
.add_error("invalid_dns_name", "Invalid DNS name")?
.add_error("bad_tls_config", "Bad TLS configuration")?
.add_error("shutdown", "The task has been shutdown")?
.add_error("invalid_utf8", "String argument was not valid UTF-8")?
.doc("Error type that indicates a bad parameter or bad programmer logic")?
.build()?;

Expand Down
9 changes: 3 additions & 6 deletions rodbus/examples/client.rs
Expand Up @@ -116,14 +116,12 @@ async fn run_tls(tls_config: TlsClientConfig) -> Result<(), Box<dyn std::error::
fn get_self_signed_config() -> Result<TlsClientConfig, Box<dyn std::error::Error>> {
use std::path::Path;
// ANCHOR: tls_self_signed_config
let tls_config = TlsClientConfig::new(
"test.com",
let tls_config = TlsClientConfig::self_signed(
Path::new("./certs/self_signed/entity2_cert.pem"),
Path::new("./certs/self_signed/entity1_cert.pem"),
Path::new("./certs/self_signed/entity1_key.pem"),
None, // no password
MinTlsVersion::V1_2,
CertificateMode::SelfSigned,
)?;
// ANCHOR_END: tls_self_signed_config

Expand All @@ -134,14 +132,13 @@ fn get_self_signed_config() -> Result<TlsClientConfig, Box<dyn std::error::Error
fn get_ca_chain_config() -> Result<TlsClientConfig, Box<dyn std::error::Error>> {
use std::path::Path;
// ANCHOR: tls_ca_chain_config
let tls_config = TlsClientConfig::new(
"test.com",
let tls_config = TlsClientConfig::full_pki(
Some("test.com".to_string()),
Path::new("./certs/ca_chain/ca_cert.pem"),
Path::new("./certs/ca_chain/client_cert.pem"),
Path::new("./certs/ca_chain/client_key.pem"),
None, // no password
MinTlsVersion::V1_2,
CertificateMode::AuthorityBased,
)?;
// ANCHOR_END: tls_ca_chain_config

Expand Down
100 changes: 85 additions & 15 deletions rodbus/src/tcp/tls/client.rs
@@ -1,4 +1,5 @@
use std::convert::TryFrom;
use std::net::Ipv4Addr;

use sfio_rustls_config::NameVerifier;
use std::path::Path;
Expand All @@ -17,7 +18,7 @@ use crate::DecodeLevel;

/// TLS configuration
pub struct TlsClientConfig {
dns_name: rustls::ServerName,
server_name: rustls::ServerName,
config: Arc<rustls::ClientConfig>,
}

Expand Down Expand Up @@ -67,39 +68,108 @@ pub(crate) fn create_tls_channel(
}

impl TlsClientConfig {
/// Create a TLS master config
/// Legacy method for creating a client TLS configuration
#[deprecated(
since = "1.3.0",
note = "Please use `full_pki` or `self_signed` instead"
)]
pub fn new(
name: &str,
server_name: &str,
peer_cert_path: &Path,
local_cert_path: &Path,
private_key_path: &Path,
password: Option<&str>,
min_tls_version: MinTlsVersion,
certificate_mode: CertificateMode,
) -> Result<Self, TlsError> {
let dns_name = rustls::ServerName::try_from(name).map_err(|_| TlsError::InvalidDnsName)?;

let config = match certificate_mode {
CertificateMode::SelfSigned => sfio_rustls_config::client::self_signed(
min_tls_version.into(),
match certificate_mode {
CertificateMode::AuthorityBased => Self::full_pki(
Some(server_name.to_string()),
peer_cert_path,
local_cert_path,
private_key_path,
password,
)?,
CertificateMode::AuthorityBased => sfio_rustls_config::client::authority(
min_tls_version.into(),
NameVerifier::equal_to(name.to_string()),
min_tls_version,
),
CertificateMode::SelfSigned => Self::self_signed(
peer_cert_path,
local_cert_path,
private_key_path,
password,
)?,
min_tls_version,
),
}
}

/// Create a TLS client configuration that expects a full PKI with an authority, and possibly
/// intermediate CA certificates.
///
/// If `server_subject_name` is specified, than the client will verify that the name is present in the
/// SAN extension or in the Common Name of the client certificate.
///
/// If `server_subject_name` is set to None, then no server name validation is performed, and
/// any authenticated server is allowed.
pub fn full_pki(
server_subject_name: Option<String>,
peer_cert_path: &Path,
local_cert_path: &Path,
private_key_path: &Path,
password: Option<&str>,
min_tls_version: MinTlsVersion,
) -> Result<Self, TlsError> {
let (name_verifier, server_name) = match server_subject_name {
None => (
NameVerifier::any(),
rustls::ServerName::IpAddress(Ipv4Addr::UNSPECIFIED.into()),
),
Some(x) => {
let server_name = rustls::ServerName::try_from(x.as_str())?;
(NameVerifier::equal_to(x), server_name)
}
};

let config = sfio_rustls_config::client::authority(
min_tls_version.into(),
name_verifier,
peer_cert_path,
local_cert_path,
private_key_path,
password,
)?;

Ok(Self {
server_name,
config: Arc::new(config),
})
}

/// Create a TLS client configuration that expects the client to present a single certificate.
///
/// In lieu of performing server subject name validation, the client validates:
///
/// 1) That the server presents a single certificate
/// 2) That the certificate is a byte-for-byte match with the one loaded in `peer_cert_path`.
/// 3) That the certificate's Validity (not before / not after) is currently valid.
///
pub fn self_signed(
peer_cert_path: &Path,
local_cert_path: &Path,
private_key_path: &Path,
password: Option<&str>,
min_tls_version: MinTlsVersion,
) -> Result<Self, TlsError> {
let config = sfio_rustls_config::client::self_signed(
min_tls_version.into(),
peer_cert_path,
local_cert_path,
private_key_path,
password,
)?;

Ok(Self {
// it doesn't matter what we put here, it just needs to be an IP so that the client won't send an SNI extension
server_name: rustls::ServerName::IpAddress(Ipv4Addr::UNSPECIFIED.into()),
config: Arc::new(config),
dns_name,
})
}

Expand All @@ -109,7 +179,7 @@ impl TlsClientConfig {
endpoint: &HostAddr,
) -> Result<PhysLayer, String> {
let connector = tokio_rustls::TlsConnector::from(self.config.clone());
match connector.connect(self.dns_name.clone(), socket).await {
match connector.connect(self.server_name.clone(), socket).await {
Err(err) => Err(format!(
"failed to establish TLS session with {endpoint}: {err}"
)),
Expand Down
7 changes: 7 additions & 0 deletions rodbus/src/tcp/tls/mod.rs
Expand Up @@ -3,6 +3,7 @@ pub(crate) mod server;

pub(crate) use client::*;
pub(crate) use server::*;
use tokio_rustls::rustls::client::InvalidDnsNameError;

/// Determines how the certificate(s) presented by the peer are validated
///
Expand Down Expand Up @@ -83,3 +84,9 @@ impl From<MinTlsVersion> for sfio_rustls_config::MinProtocolVersion {
}
}
}

impl From<InvalidDnsNameError> for TlsError {
fn from(_: InvalidDnsNameError) -> Self {
Self::InvalidDnsName
}
}

0 comments on commit a5cc6ad

Please sign in to comment.