rust: bssl-tls: More TLS context protocol controls Bug: 479599893 Signed-off-by: Xiangfei Ding <xfding@google.com> Change-Id: I98a5071072687b70b5df2641ba920ac76a6a6964 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/91388 Presubmit-BoringSSL-Verified: boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/rust/bssl-tls/src/context.rs b/rust/bssl-tls/src/context.rs index 60f4196..6ccfb56 100644 --- a/rust/bssl-tls/src/context.rs +++ b/rust/bssl-tls/src/context.rs
@@ -24,8 +24,17 @@ ptr::NonNull, // }; +use bssl_crypto::FfiSlice; + use crate::{ - config::CompliancePolicy, + check_lib_error, + config::{ + CompliancePolicy, + ConfigurationError, + KeyExchangeGroupFlag, + KeyExchangeGroups, + ProtocolVersion, // + }, connection::{ Client, Server, @@ -33,7 +42,8 @@ methods::HasTlsConnectionMethod, // }, context::methods::HasTlsContextMethod, - errors::Error, // + errors::Error, + has_duplicates, // }; mod credentials; @@ -164,6 +174,182 @@ &mut *(methods as *mut methods::RustContextMethods<M>) } } + + /// Set the minimum acceptable protocol. + /// + /// If `version` is set to `None`, the library will choose a default minimum version. + /// - For TLS, it is TLS 1.3 + /// - For DTLS, it is DTLS 1.2 + /// + /// This configuration fails if the protocol version is incompatible with the selected TLS + /// flavour, such as setting TLS 1.3 for DTLS flavour. + pub fn with_min_protocol( + &mut self, + version: Option<ProtocolVersion>, + ) -> Result<&mut Self, Error> { + let version = version.map_or(0, |ver| ver as u16); + check_lib_error!(unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self` + bssl_sys::SSL_CTX_set_min_proto_version(self.ptr(), version) + }); + Ok(self) + } + + /// Set the maximum acceptable protocol. + /// + /// If `version` is set to `None`, the library will choose a default maximum version. + /// - For TLS, it is TLS 1.3 + /// - For DTLS, it is DTLS 1.2 + /// + /// This configuration fails if the protocol version is incompatible with the selected TLS + /// flavour, such as setting TLS 1.3 for DTLS flavour. + pub fn with_max_protocol( + &mut self, + version: Option<ProtocolVersion>, + ) -> Result<&mut Self, Error> { + let version = version.map_or(0, |ver| ver as u16); + check_lib_error!(unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self` + bssl_sys::SSL_CTX_set_max_proto_version(self.ptr(), version) + }); + Ok(self) + } + + /// Set acceptable key exchange groups. + /// + /// This method sets up the acceptable key exchange groups and the list of groups advertised by + /// the client. + /// + /// If the supplied list is empty, the context will revert back to a default list of groups. + /// + /// If the `flags` list is not empty, its length must match that of `groups`. + /// + /// This method returns [`ConfigurationError::InvalidParameters`] if the key exchange groups are not unique in the list. + pub fn with_key_exchange_groups( + &mut self, + groups: &[KeyExchangeGroups], + flags: &[KeyExchangeGroupFlag], + ) -> Result<&mut Self, Error> { + // TODO(@xfding): maybe we should use zerocopy here when v0.9 is finalised + let groups: &[u16] = unsafe { + // Safety: `KeyExchangeGroups` has the same layout as a `u16`, + // so `&[KeyExchangeGroups]` and `&[u16]` has the same layout. + core::mem::transmute(groups) + }; + let flags: &[u32] = unsafe { + // Safety: `KeyExchangeGroupFlag` has the same layout as a `u32` through + // `repr(transparent)`. + core::mem::transmute(flags) + }; + if !flags.is_empty() && groups.len() != flags.len() { + return Err(Error::Configuration(ConfigurationError::InvalidParameters)); + } + if has_duplicates(groups) { + return Err(Error::Configuration( + ConfigurationError::DuplicatedKeyExchangeGroup, + )); + } + + check_lib_error!(unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self` + if flags.is_empty() { + bssl_sys::SSL_CTX_set1_group_ids(self.ptr(), groups.as_ffi_ptr(), groups.len()) + } else { + bssl_sys::SSL_CTX_set1_group_ids_with_flags( + self.ptr(), + groups.as_ffi_ptr(), + flags.as_ffi_ptr(), + groups.len(), + ) + } + }); + Ok(self) + } + + /// Set a list of cipher suites, in the order of descending preference. + /// + /// This method accepts a string that conforms to OpenSSL [cipher suite configuration] language + /// specification. + /// + /// # Available cipher rules are: + /// + /// - `ALL` matches all ciphers, except for deprecated ciphers which must be + /// named explicitly. + /// + /// - `kRSA`, `kDHE`, `kECDHE`, and `kPSK` match ciphers using plain RSA, DHE, + /// ECDHE, and plain PSK key exchanges, respectively. Note that ECDHE_PSK is + /// matched by `kECDHE` and not `kPSK`. + /// + /// - `aRSA`, `aECDSA`, and `aPSK` match ciphers authenticated by RSA, ECDSA, and + /// a pre-shared key, respectively. + /// + /// - `RSA`, `DHE`, `ECDHE`, `PSK`, `ECDSA`, and `PSK` are aliases for the + /// corresponding `k*` or `a*` cipher rule. `RSA` is an alias for `kRSA`, not + /// `aRSA`. + /// + /// - `3DES`, `AES128`, `AES256`, `AES`, `AESGCM`, `CHACHA20` match ciphers + /// whose bulk cipher use the corresponding encryption scheme. Note that + /// `AES`, `AES128`, and `AES256` match both CBC and GCM ciphers. + /// + /// - `SHA1`, and its alias `SHA`, match legacy cipher suites using HMAC-SHA1. + /// + /// # Deprecated cipher rules: + /// + /// - `kEDH`, `EDH`, `kEECDH`, and `EECDH` are legacy aliases for `kDHE`, `DHE`, + /// `kECDHE`, and `ECDHE`, respectively. + /// + /// - `HIGH` is an alias for `ALL`. + /// + /// - `FIPS` is an alias for `HIGH`. + /// + /// - `SSLv3` and `TLSv1` match ciphers available in TLS 1.1 or earlier. + /// `TLSv1_2` matches ciphers new in TLS 1.2. This is confusing and should not + /// be used. + /// + /// [cipher suite configuration]: <https://docs.openssl.org/3.0/man1/openssl-ciphers/#cipher-list-format> + pub fn with_ciphers(&mut self, cipher_ops: &str) -> Result<&mut Self, Error> { + let Ok(cipher_ops) = alloc::ffi::CString::new(cipher_ops) else { + return Err(Error::Configuration(ConfigurationError::InvalidString)); + }; + check_lib_error!(unsafe { + // Safety: + // - the validity of the handle `self.0` is witnessed by `self`; + // - the string has been checked for internal NUL bytes and NUL-terminated. + bssl_sys::SSL_CTX_set_strict_cipher_list(self.ptr(), cipher_ops.as_ptr()) + }); + Ok(self) + } + + /// Set TLS 1.2 session lifetime in seconds. + /// + /// If 0 seconds are specified, BoringSSL will pick a default timeout for the connections. + pub fn with_tls12_timeout(&mut self, seconds: u32) -> &mut Self { + unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self`. + bssl_sys::SSL_CTX_set_timeout(self.ptr(), seconds) + }; + self + } + + /// Set TLS 1.3 session lifetime in seconds. + /// + /// If 0 seconds are specified, BoringSSL will pick a default timeout for the connections. + pub fn with_tls13_timeout(&mut self, seconds: u32) -> &mut Self { + unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self`. + bssl_sys::SSL_CTX_set_session_psk_dhe_timeout(self.ptr(), seconds) + }; + self + } + + /// Set whether shutting down the connection sends out a `close_notify` alert. + pub fn with_quiet_shutdown(&mut self, quiet: bool) -> &mut Self { + unsafe { + // Safety: the validity of the handle `self.0` is witnessed by `self`. + bssl_sys::SSL_CTX_set_quiet_shutdown(self.ptr(), if quiet { 1 } else { 0 }); + } + self + } } impl<M> Drop for TlsContextBuilder<M> {
diff --git a/rust/bssl-tls/src/lib.rs b/rust/bssl-tls/src/lib.rs index 71e343e..6390057 100644 --- a/rust/bssl-tls/src/lib.rs +++ b/rust/bssl-tls/src/lib.rs
@@ -47,6 +47,11 @@ #[doc(hidden)] mod macros; +fn has_duplicates<T: Ord + Eq>(list: &[T]) -> bool { + let mut seen = alloc::collections::BTreeSet::new(); + list.iter().any(|elem| !seen.insert(elem)) +} + #[allow(unused)] pub(crate) trait Methods { /// Safety: `ssl` must outlive `'a` and it must be passed in from BoringSSL