From 81abb0967110bf1caa0ab71c018e765282ba6f7c Mon Sep 17 00:00:00 2001 From: Jonathan Hoyland Date: Tue, 16 Jan 2024 14:49:18 +0000 Subject: [PATCH] Expose `set_compliance_policy` and `get_ciphers` --- boring/src/ssl/mod.rs | 80 +++++++++++++++++++++++++++++++++++++- boring/src/ssl/test/mod.rs | 77 ++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 1524d8e0..8c914ea6 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -89,7 +89,7 @@ use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; use crate::ssl::bio::BioMethod; use crate::ssl::callbacks::*; use crate::ssl::error::InnerError; -use crate::stack::{Stack, StackRef}; +use crate::stack::{Stack, StackRef, Stackable}; use crate::x509::store::{X509Store, X509StoreBuilderRef, X509StoreRef}; use crate::x509::verify::X509VerifyParamRef; use crate::x509::{ @@ -701,6 +701,27 @@ impl SslCurve { pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::NID_P256Kyber768Draft00); } +/// A compliance policy. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg(not(feature = "fips"))] +pub struct CompliancePolicy(ffi::ssl_compliance_policy_t); + +#[cfg(not(feature = "fips"))] +impl CompliancePolicy { + /// Does nothing, however setting this does not undo other policies, so trying to set this is an error. + pub const NONE: Self = Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_none); + + /// Configures a TLS connection to try and be compliant with NIST requirements, but does not guarantee success. + /// This policy can be called even if Boring is not built with FIPS. + pub const FIPS_202205: Self = + Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_fips_202205); + + /// Partially configures a TLS connection to be compliant with WPA3. Callers must enforce certificate chain requirements themselves. + /// Use of this policy is less secure than the default and not recommended. + pub const WPA3_192_202304: Self = + Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_wpa3_192_202304); +} + /// A standard implementation of protocol selection for Application Layer Protocol Negotiation /// (ALPN). /// @@ -1262,7 +1283,9 @@ impl SslContextBuilder { /// Sets the list of supported ciphers for protocols before TLSv1.3. /// - /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3. + /// The `set_ciphersuites` method controls the cipher suites for TLSv1.3 in OpenSSL. + /// BoringSSL doesn't implement `set_ciphersuites`. + /// See https://github.com/google/boringssl/blob/master/include/openssl/ssl.h#L1542-L1544 /// /// See [`ciphers`] for details on the format. /// @@ -1281,6 +1304,25 @@ impl SslContextBuilder { } } + /// Gets the list of supported ciphers for protocols before TLSv1.3. + /// + /// See [`ciphers`] for details on the format + /// + /// This corresponds to [`SSL_CTX_get_ciphers`]. + /// + /// [`ciphers`]: https://www.openssl.org/docs/manmaster/man1/ciphers.html + /// [`SSL_CTX_set_cipher_list`]: https://www.openssl.org/docs/manmaster/man3/SSL_CTX_get_ciphers.html + pub fn ciphers(&self) -> Option<&StackRef> { + unsafe { + let ciphers = ffi::SSL_CTX_get_ciphers(self.as_ptr()); + if ciphers.is_null() { + None + } else { + Some(StackRef::from_ptr(ciphers)) + } + } + } + /// Sets the options used by the context, returning the old set. /// /// This corresponds to [`SSL_CTX_set_options`]. @@ -1856,6 +1898,17 @@ impl SslContextBuilder { } } + /// Sets the context's compliance policy. + /// + /// This corresponds to [`SSL_CTX_set_compliance_policy`] + /// + /// [`SSL_CTX_set_compliance_policy`] https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_compliance_policy + /// This feature isn't available in the certified version of BoringSSL. + #[cfg(not(feature = "fips"))] + pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { + unsafe { cvt_0i(ffi::SSL_CTX_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } + } + /// Consumes the builder, returning a new `SslContext`. pub fn build(self) -> SslContext { self.ctx @@ -1936,6 +1989,25 @@ impl SslContext { Index::from_raw(idx) } } + + /// Gets the list of supported ciphers for protocols before TLSv1.3. + /// + /// See [`ciphers`] for details on the format + /// + /// This corresponds to [`SSL_CTX_get_ciphers`]. + /// + /// [`ciphers`]: https://www.openssl.org/docs/manmaster/man1/ciphers.html + /// [`SSL_CTX_set_cipher_list`]: https://www.openssl.org/docs/manmaster/man3/SSL_CTX_get_ciphers.html + pub fn ciphers(&self) -> Option<&StackRef> { + unsafe { + let ciphers = ffi::SSL_CTX_get_ciphers(self.as_ptr()); + if ciphers.is_null() { + None + } else { + Some(StackRef::from_ptr(ciphers)) + } + } + } } impl SslContextRef { @@ -2181,6 +2253,10 @@ impl ClientHello<'_> { /// Information about a cipher. pub struct SslCipher(*mut ffi::SSL_CIPHER); +impl Stackable for SslCipher { + type StackType = ffi::stack_st_SSL_CIPHER; +} + unsafe impl ForeignType for SslCipher { type CType = ffi::SSL_CIPHER; type Ref = SslCipherRef; diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 90f203e4..08ef7e28 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -21,6 +21,9 @@ use crate::ssl::{ use crate::x509::verify::X509CheckFlags; use crate::x509::{X509Name, X509}; +#[cfg(not(feature = "fips"))] +use super::CompliancePolicy; + mod custom_verify; mod private_key_method; mod server; @@ -917,6 +920,80 @@ fn server_set_default_curves_list() { ssl.server_set_default_curves_list(); } +#[test] +fn test_get_ciphers() { + let ctx_builder = SslContext::builder(SslMethod::tls()).unwrap(); + let ctx_builder_ciphers: Vec<&str> = ctx_builder + .ciphers() + .unwrap() + .into_iter() + .map(|v| v.name()) + .collect(); + assert!(!(ctx_builder_ciphers.is_empty())); + + let ctx = ctx_builder.build(); + let ctx_ciphers: Vec<&str> = ctx + .ciphers() + .unwrap() + .into_iter() + .map(|v| v.name()) + .collect(); + assert!(!(ctx_ciphers.is_empty())); + + assert_eq!(ctx_builder_ciphers.len(), ctx_ciphers.len()); + + for (ctx_builder_cipher, ctx_cipher) in ctx_builder_ciphers.into_iter().zip(ctx_ciphers) { + assert_eq!(ctx_builder_cipher, ctx_cipher); + } +} + +#[test] +#[cfg(not(feature = "fips"))] +fn test_set_compliance() { + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_compliance_policy(CompliancePolicy::FIPS_202205) + .unwrap(); + + assert_eq!(ctx.max_proto_version().unwrap(), SslVersion::TLS1_3); + assert_eq!(ctx.min_proto_version().unwrap(), SslVersion::TLS1_2); + + const FIPS_CIPHERS: [&str; 4] = [ + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + ]; + + let ciphers = ctx.ciphers().unwrap(); + assert_eq!(ciphers.len(), FIPS_CIPHERS.len()); + + for cipher in ciphers.into_iter().zip(FIPS_CIPHERS) { + assert_eq!(cipher.0.name(), cipher.1) + } + + let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); + ctx.set_compliance_policy(CompliancePolicy::WPA3_192_202304) + .unwrap(); + + assert_eq!(ctx.max_proto_version().unwrap(), SslVersion::TLS1_3); + assert_eq!(ctx.min_proto_version().unwrap(), SslVersion::TLS1_2); + + const WPA3_192_CIPHERS: [&str; 2] = [ + "ECDHE-ECDSA-AES256-GCM-SHA384", + "ECDHE-RSA-AES256-GCM-SHA384", + ]; + + let ciphers = ctx.ciphers().unwrap(); + assert_eq!(ciphers.len(), WPA3_192_CIPHERS.len()); + + for cipher in ciphers.into_iter().zip(WPA3_192_CIPHERS) { + assert_eq!(cipher.0.name(), cipher.1) + } + + ctx.set_compliance_policy(CompliancePolicy::NONE) + .expect_err("Testing expect err if set compliance policy to NONE"); +} + #[test] fn drop_ex_data_in_context() { let index = SslContext::new_ex_index::<&'static str>().unwrap();