From fd87ae395e0332aee6c8203b30636f2d6886334b Mon Sep 17 00:00:00 2001 From: Jeff Cantrill Date: Thu, 2 Apr 2026 11:28:20 -0400 Subject: [PATCH] LOG-8968: Enhance TLS config of OpenSSL to allow setting curves --- .gitignore | 3 ++ lib/vector-core/src/tls/incoming.rs | 30 ++++++++++++++-- lib/vector-core/src/tls/mod.rs | 2 ++ lib/vector-core/src/tls/settings.rs | 54 +++++++++++++++++++++++++++-- patch/openssl/src/ssl/connector.rs | 6 ++-- patch/openssl/src/ssl/error.rs | 6 +++- patch/openssl/src/ssl/mod.rs | 12 +++++-- 7 files changed, 102 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 1963fcca25fcc..f9510e67efc15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.work/ +.vscode + # Rust cargo-timing*.html **/*.rs.bk diff --git a/lib/vector-core/src/tls/incoming.rs b/lib/vector-core/src/tls/incoming.rs index 0d959bb170e1d..61dd598fd735f 100644 --- a/lib/vector-core/src/tls/incoming.rs +++ b/lib/vector-core/src/tls/incoming.rs @@ -33,8 +33,34 @@ impl TlsSettings { if self.identity.is_none() { Err(TlsError::MissingRequiredIdentity) } else { - let mut acceptor = - SslAcceptor::mozilla_intermediate(SslMethod::tls()).context(CreateAcceptorSnafu)?; + // Use custom acceptor if TLS version, ciphersuites, or curves are configured + let mut acceptor = if self.min_tls_version.is_some() + || self.ciphersuites.is_some() + || self.curves.is_some() + { + SslAcceptor::custom( + SslMethod::tls(), + &self.min_tls_version, + &self.ciphersuites, + &self.curves, + ) + .map_err(|e| match e { + openssl::ssl::ErrorEx::OpenSslError { error_stack } => { + TlsError::CreateAcceptor { + source: error_stack, + } + } + openssl::ssl::ErrorEx::InvalidTlsVersion + | openssl::ssl::ErrorEx::InvalidCiphersuite => TlsError::SslBuildError { + source: openssl::error::ErrorStack::get(), + }, + openssl::ssl::ErrorEx::InvalidCurve { error_stack } => TlsError::SetCurves { + source: error_stack, + }, + })? + } else { + SslAcceptor::mozilla_intermediate(SslMethod::tls()).context(CreateAcceptorSnafu)? + }; self.apply_context_base(&mut acceptor, true)?; Ok(acceptor.build()) } diff --git a/lib/vector-core/src/tls/mod.rs b/lib/vector-core/src/tls/mod.rs index 5e2e06aa4462b..1f25cfbe8b3b9 100644 --- a/lib/vector-core/src/tls/mod.rs +++ b/lib/vector-core/src/tls/mod.rs @@ -108,6 +108,8 @@ pub enum TlsError { source ))] EncodeAlpnProtocols { source: TryFromIntError }, + #[snafu(display("Error setting TLS curves: {}", source))] + SetCurves { source: ErrorStack }, #[snafu(display("PKCS#12 parse failed: {}", source))] ParsePkcs12 { source: ErrorStack }, #[snafu(display("TCP bind failed: {}", source))] diff --git a/lib/vector-core/src/tls/settings.rs b/lib/vector-core/src/tls/settings.rs index 3a766c6b44f69..a02c76071fa9d 100644 --- a/lib/vector-core/src/tls/settings.rs +++ b/lib/vector-core/src/tls/settings.rs @@ -9,7 +9,7 @@ use super::{ AddCertToStoreSnafu, AddExtraChainCertSnafu, CaStackPushSnafu, EncodeAlpnProtocolsSnafu, FileOpenFailedSnafu, FileReadFailedSnafu, MaybeTls, NewCaStackSnafu, NewStoreBuilderSnafu, ParsePkcs12Snafu, PrivateKeyParseSnafu, Result, SetAlpnProtocolsSnafu, SetCertificateSnafu, - SetPrivateKeySnafu, SetVerifyCertSnafu, TlsError, X509ParseSnafu, + SetCurvesSnafu, SetPrivateKeySnafu, SetVerifyCertSnafu, TlsError, X509ParseSnafu, }; use cfg_if::cfg_if; use lookup::lookup_v2::OptionalValuePath; @@ -162,6 +162,15 @@ pub struct TlsConfig { /// TLS ciphersuites to enable. pub ciphersuites: Option, + + /// TLS elliptic curve groups (curves) to enable. + /// + /// Specifies the list of elliptic curve groups (curves) to use for TLS connections. + /// The curves are specified as a colon-separated list of curve names. + /// + /// Common curve names include: prime256v1, secp384r1, secp521r1, X25519, X448. + #[configurable(metadata(docs::examples = "prime256v1:secp384r1"))] + pub curves: Option, } impl TlsConfig { @@ -186,6 +195,7 @@ pub struct TlsSettings { server_name: Option, pub min_tls_version: Option, pub ciphersuites: Option, + pub curves: Option, } /// Identity store in PEM format @@ -230,6 +240,7 @@ impl TlsSettings { server_name: options.server_name.clone(), min_tls_version: options.min_tls_version.clone(), ciphersuites: options.ciphersuites.clone(), + curves: options.curves.clone(), }) } @@ -347,6 +358,11 @@ impl TlsSettings { } } + // Configure curves if specified + if let Some(ref curves) = self.curves { + context.set_groups_list(curves).context(SetCurvesSnafu)?; + } + Ok(()) } @@ -880,7 +896,7 @@ mod test { }, ]; for t in tests { - match builder.set_min_tls_version_and_ciphersuites(&t.text, &None) { + match builder.set_min_tls_version_and_ciphersuites(&t.text, &None, &None) { Ok(()) => { assert!(t.want.is_ok()); assert_eq!(builder.min_proto_version(), t.num); @@ -921,7 +937,11 @@ mod test { }, ]; for t in tests { - match builder.set_min_tls_version_and_ciphersuites(&t.min_tls_version, &t.ciphersuite) { + match builder.set_min_tls_version_and_ciphersuites( + &t.min_tls_version, + &t.ciphersuite, + &None, + ) { Ok(()) => assert!(t.want.is_ok()), Err(e) => assert_eq!(t.want.err().unwrap(), e), } @@ -949,4 +969,32 @@ mod test { }, } } + + #[test] + fn from_options_curves() { + let options = TlsConfig { + curves: Some("prime256v1:secp384r1".to_string()), + ..Default::default() + }; + let settings = TlsSettings::from_options(Some(&options)).expect("Failed to parse curves"); + assert_eq!(settings.curves, Some("prime256v1:secp384r1".to_string())); + } + + #[test] + fn from_options_with_curves_applied() { + use openssl::ssl::SslConnector; + + let options = TlsConfig { + curves: Some("prime256v1:secp384r1:secp521r1".to_string()), + ..Default::default() + }; + let settings = TlsSettings::from_options(Some(&options)).expect("Failed to parse curves"); + + // Test that the context can be created with curves + let mut builder = + SslConnector::builder(SslMethod::tls()).expect("Failed to create SSL builder"); + settings + .apply_context(&mut builder) + .expect("Failed to apply context with curves"); + } } diff --git a/patch/openssl/src/ssl/connector.rs b/patch/openssl/src/ssl/connector.rs index 14aadfe3e2569..f65a509645b0a 100644 --- a/patch/openssl/src/ssl/connector.rs +++ b/patch/openssl/src/ssl/connector.rs @@ -317,14 +317,14 @@ impl SslAcceptor { Ok(SslAcceptorBuilder(ctx)) } - /// Creates a new builder configured with a minimum supported TLS version and a set of ciphersuites + /// Creates a new builder configured with a minimum supported TLS version, a set of ciphersuites, and elliptic curves /// - pub fn custom(method: SslMethod, min_tls_version: &Option, ciphersuites: &Option) -> Result { + pub fn custom(method: SslMethod, min_tls_version: &Option, ciphersuites: &Option, curves: &Option) -> Result { let mut ctx = ctx(method).map_err(|e| ErrorEx::OpenSslError { error_stack: e })?; let dh = Dh::params_from_pem(FFDHE_2048.as_bytes()).map_err(|e| ErrorEx::OpenSslError { error_stack: e })?; ctx.set_tmp_dh(&dh).map_err(|e| ErrorEx::OpenSslError { error_stack: e })?; setup_curves(&mut ctx).map_err(|e| ErrorEx::OpenSslError { error_stack: e })?; - ctx.set_min_tls_version_and_ciphersuites(min_tls_version, ciphersuites)?; + ctx.set_min_tls_version_and_ciphersuites(min_tls_version, ciphersuites, curves)?; Ok(SslAcceptorBuilder(ctx)) } diff --git a/patch/openssl/src/ssl/error.rs b/patch/openssl/src/ssl/error.rs index ede4e371df166..0113056f858bc 100644 --- a/patch/openssl/src/ssl/error.rs +++ b/patch/openssl/src/ssl/error.rs @@ -191,6 +191,9 @@ pub enum ErrorEx { }, InvalidTlsVersion, InvalidCiphersuite, + InvalidCurve { + error_stack: ErrorStack + }, } impl PartialEq for ErrorEx { @@ -198,7 +201,8 @@ impl PartialEq for ErrorEx { match (self, other) { (ErrorEx::OpenSslError{..}, ErrorEx::OpenSslError{..}) | (ErrorEx::InvalidTlsVersion, ErrorEx::InvalidTlsVersion) - | (ErrorEx::InvalidCiphersuite, ErrorEx::InvalidCiphersuite) => true, + | (ErrorEx::InvalidCiphersuite, ErrorEx::InvalidCiphersuite) + | (ErrorEx::InvalidCurve{..}, ErrorEx::InvalidCurve{..}) => true, _ => false, } } diff --git a/patch/openssl/src/ssl/mod.rs b/patch/openssl/src/ssl/mod.rs index 419847da342ab..b85b06ca6ae39 100644 --- a/patch/openssl/src/ssl/mod.rs +++ b/patch/openssl/src/ssl/mod.rs @@ -1774,9 +1774,9 @@ impl SslContextBuilder { self.0 } - /// Sets the context's minimal TLS version, specified as "VersionTLS1[0..3]", and a comma-separated list of ciphersuites. + /// Sets the context's minimal TLS version, specified as "VersionTLS1[0..3]", a comma-separated list of ciphersuites, and a colon-separated list of curves. /// - pub fn set_min_tls_version_and_ciphersuites(&mut self, min_tls_version: &Option, ciphersuites: &Option) -> Result<(), ErrorEx>{ + pub fn set_min_tls_version_and_ciphersuites(&mut self, min_tls_version: &Option, ciphersuites: &Option, curves: &Option) -> Result<(), ErrorEx>{ let mut min_proto_version = SslVersion::TLS1; if let Some(min_tls_version) = min_tls_version { min_proto_version = match min_tls_version.as_str() { @@ -1800,6 +1800,14 @@ impl SslContextBuilder { return Err(ErrorEx::InvalidCiphersuite); } } + if let Some(ref curves) = curves { + if !curves.is_empty() { + self.set_groups_list(curves) + .map_err(|e| ErrorEx::InvalidCurve { error_stack: e })?; + } else { + return Err(ErrorEx::InvalidCurve { error_stack: crate::error::ErrorStack::get() }); + } + } Ok(()) } }