diff --git a/Cargo.toml b/Cargo.toml index 230a2ccb9..719e558d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "5.0.0-alpha.2" +version = "5.0.0" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,9 +19,9 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "5.0.0-alpha.2", path = "./boring-sys" } -boring = { version = "5.0.0-alpha.2", path = "./boring" } -tokio-boring = { version = "5.0.0-alpha.2", path = "./tokio-boring" } +boring-sys = { version = ">=4.21.1, <6.0.0", path = "./boring-sys" } # Cargo doesn't like prerelease ranges. ok as of "5.0.0" +boring = { version = "5.0.0", path = "./boring" } +tokio-boring = { version = "5.0.0", path = "./tokio-boring" } bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] } bitflags = "2.9" diff --git a/boring/Cargo.toml b/boring/Cargo.toml index d76c92945..22f5a87fb 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -17,6 +17,9 @@ features = ["rpk", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] +# Enable SslCredential (requires boring-sys v5) +credential = [] + # Controlling the build # Use a FIPS-validated version of BoringSSL. @@ -30,7 +33,7 @@ legacy-compat-deprecated = [] # default branch of boringSSL. Alternatively, a version of boringSSL that # implements the same feature set can be provided by setting # `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. -rpk = ["boring-sys/rpk"] +rpk = ["credential", "boring-sys/rpk"] # Applies a patch to enable `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This # feature is necessary in order to compile the bindings for the default branch diff --git a/boring/src/ssl/async_callbacks.rs b/boring/src/ssl/async_callbacks.rs index ad9683a08..23464030d 100644 --- a/boring/src/ssl/async_callbacks.rs +++ b/boring/src/ssl/async_callbacks.rs @@ -4,8 +4,10 @@ use super::{ Ssl, SslAlert, SslContextBuilder, SslRef, SslSession, SslSignatureAlgorithm, SslVerifyError, SslVerifyMode, }; +#[cfg(feature = "credential")] use crate::error::ErrorStack; use crate::ex_data::Index; +#[cfg(feature = "credential")] use crate::ssl::SslCredentialBuilder; use std::convert::identity; use std::future::Future; @@ -173,6 +175,7 @@ impl SslContextBuilder { } } +#[cfg(feature = "credential")] impl SslCredentialBuilder { /// Configures a custom private key method on the context. /// diff --git a/boring/src/ssl/credential.rs b/boring/src/ssl/credential.rs new file mode 100644 index 000000000..df8fb0c30 --- /dev/null +++ b/boring/src/ssl/credential.rs @@ -0,0 +1,211 @@ +#[cfg(feature = "rpk")] +use crate::cvt_p; +use crate::error::ErrorStack; +use crate::ex_data::Index; +use crate::pkey::{PKeyRef, Private}; +use crate::ssl::callbacks; +use crate::ssl::PrivateKeyMethod; +use crate::{cvt_0i, cvt_n}; +use crate::{ffi, free_data_box}; +use foreign_types::{ForeignType, ForeignTypeRef}; +use openssl_macros::corresponds; +use std::any::TypeId; +use std::collections::HashMap; +use std::ffi::{c_int, c_void}; +use std::mem; +use std::ptr; +use std::sync::{LazyLock, Mutex}; + +static SSL_CREDENTIAL_INDEXES: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + +foreign_type_and_impl_send_sync! { + type CType = ffi::SSL_CREDENTIAL; + fn drop = ffi::SSL_CREDENTIAL_free; + + /// A credential. + pub struct SslCredential; +} + +impl SslCredential { + /// Create a credential suitable for a handshake using a raw public key. + #[corresponds(SSL_CREDENTIAL_new_raw_public_key)] + #[cfg(feature = "rpk")] + pub fn new_raw_public_key() -> Result { + unsafe { + Ok(SslCredentialBuilder(Self::from_ptr(cvt_p( + ffi::SSL_CREDENTIAL_new_raw_public_key(), + )?))) + } + } + + /// Returns a new extra data index. + /// + /// Each invocation of this function is guaranteed to return a distinct index. These can be used + /// to store data in the context that can be retrieved later by callbacks, for example. + #[corresponds(SSL_C_get_ex_new_index)] + pub fn new_ex_index() -> Result, ErrorStack> + where + T: 'static + Sync + Send, + { + unsafe { + ffi::init(); + let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::)))?; + Ok(Index::from_raw(idx)) + } + } + + // FIXME should return a result? + pub(crate) fn cached_ex_index() -> Index + where + T: 'static + Sync + Send, + { + unsafe { + let idx = *SSL_CREDENTIAL_INDEXES + .lock() + .unwrap_or_else(|e| e.into_inner()) + .entry(TypeId::of::()) + .or_insert_with(|| Self::new_ex_index::().unwrap().as_raw()); + Index::from_raw(idx) + } + } +} + +impl SslCredentialRef { + /// Returns a reference to the extra data at the specified index. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + #[must_use] + pub fn ex_data(&self, index: Index) -> Option<&T> { + unsafe { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&*(data as *const T)) + } + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_get_ex_data)] + pub(crate) unsafe fn ex_data_mut( + &mut self, + index: Index, + ) -> Option<&mut T> { + let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); + if data.is_null() { + None + } else { + Some(&mut *(data as *mut T)) + } + } + + // Unsafe because SSL contexts are not guaranteed to be unique, we call + // this only from SslCredentialBuilder. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub(crate) unsafe fn replace_ex_data( + &mut self, + index: Index, + data: T, + ) -> Option { + if let Some(old) = self.ex_data_mut(index) { + return Some(mem::replace(old, data)); + } + + unsafe { + let data = Box::into_raw(Box::new(data)) as *mut c_void; + ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); + } + + None + } +} + +/// A builder for [`SslCredential`] +pub struct SslCredentialBuilder(SslCredential); + +impl SslCredentialBuilder { + /// Sets or overwrites the extra data at the specified index. + /// + /// This can be used to provide data to callbacks registered with the context. Use the + /// `SslCredential::new_ex_index` method to create an `Index`. + /// + /// Any previous value will be returned and replaced by the new one. + #[corresponds(SSL_CREDENTIAL_set_ex_data)] + pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { + unsafe { self.0.replace_ex_data(index, data) } + } + + // Sets the private key of the credential. + #[corresponds(SSL_CREDENTIAL_set1_private_key)] + pub fn set_private_key(&mut self, private_key: &PKeyRef) -> Result<(), ErrorStack> { + unsafe { + cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key( + self.0.as_ptr(), + private_key.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Configures a custom private key method on the credential. + /// + /// See [`PrivateKeyMethod`] for more details. + #[corresponds(SSL_CREDENTIAL_set_private_key_method)] + pub fn set_private_key_method(&mut self, method: M) -> Result<(), ErrorStack> + where + M: PrivateKeyMethod, + { + unsafe { + self.replace_ex_data(SslCredential::cached_ex_index::(), method); + + cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( + self.0.as_ptr(), + &ffi::SSL_PRIVATE_KEY_METHOD { + sign: Some(callbacks::raw_sign::), + decrypt: Some(callbacks::raw_decrypt::), + complete: Some(callbacks::raw_complete::), + }, + )) + .map(|_| ()) + } + } + + // Sets the SPKI of the raw public key credential. + // + // If `spki` is `None`, the SPKI is extracted from the credential's private key. + #[corresponds(SSL_CREDENTIAL_set1_spki)] + #[cfg(feature = "rpk")] + pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> { + unsafe { + let spki = spki + .map(|spki| { + cvt_p(ffi::CRYPTO_BUFFER_new( + spki.as_ptr(), + spki.len(), + ptr::null_mut(), + )) + }) + .transpose()? + .unwrap_or(ptr::null_mut()); + + let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ()); + + if !spki.is_null() { + ffi::CRYPTO_BUFFER_free(spki); + } + + ret + } + } + + #[must_use] + pub fn build(self) -> SslCredential { + self.0 + } +} + +unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { + ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) +} diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 486adb2db..7d7bf3c4c 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -108,6 +108,8 @@ pub use self::async_callbacks::{ pub use self::connector::{ ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, }; +#[cfg(feature = "credential")] +pub use self::credential::{SslCredential, SslCredentialBuilder, SslCredentialRef}; pub use self::ech::{SslEchKeys, SslEchKeysRef}; pub use self::error::{Error, ErrorCode, HandshakeError}; @@ -115,6 +117,8 @@ mod async_callbacks; mod bio; mod callbacks; mod connector; +#[cfg(feature = "credential")] +mod credential; mod ech; mod error; mod mut_only; @@ -477,8 +481,6 @@ static SSL_INDEXES: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static SESSION_CTX_INDEX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); -static SSL_CREDENTIAL_INDEXES: LazyLock>> = - LazyLock::new(|| Mutex::new(HashMap::new())); static X509_FLAG_INDEX: LazyLock> = LazyLock::new(|| SslContext::new_ex_index().unwrap()); @@ -2025,6 +2027,7 @@ impl SslContextBuilder { /// Adds a credential. #[corresponds(SSL_CTX_add1_credential)] + #[cfg(feature = "credential")] pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_CTX_add1_credential( @@ -3844,6 +3847,7 @@ impl SslRef { /// Adds a credential. #[corresponds(SSL_add1_credential)] + #[cfg(feature = "credential")] pub fn add_credential(&mut self, credential: &SslCredentialRef) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_add1_credential(self.as_ptr(), credential.as_ptr())).map(|_| ()) } } @@ -4452,186 +4456,6 @@ impl SslStreamBuilder { } } -foreign_type_and_impl_send_sync! { - type CType = ffi::SSL_CREDENTIAL; - fn drop = ffi::SSL_CREDENTIAL_free; - - /// A credential. - pub struct SslCredential; -} - -impl SslCredential { - /// Create a credential suitable for a handshake using a raw public key. - #[corresponds(SSL_CREDENTIAL_new_raw_public_key)] - #[cfg(feature = "rpk")] - pub fn new_raw_public_key() -> Result { - unsafe { - Ok(SslCredentialBuilder(Self::from_ptr(cvt_p( - ffi::SSL_CREDENTIAL_new_raw_public_key(), - )?))) - } - } - - /// Returns a new extra data index. - /// - /// Each invocation of this function is guaranteed to return a distinct index. These can be used - /// to store data in the context that can be retrieved later by callbacks, for example. - #[corresponds(SSL_C_get_ex_new_index)] - pub fn new_ex_index() -> Result, ErrorStack> - where - T: 'static + Sync + Send, - { - unsafe { - ffi::init(); - let idx = cvt_n(get_new_ssl_credential_idx(Some(free_data_box::)))?; - Ok(Index::from_raw(idx)) - } - } - - // FIXME should return a result? - fn cached_ex_index() -> Index - where - T: 'static + Sync + Send, - { - unsafe { - let idx = *SSL_CREDENTIAL_INDEXES - .lock() - .unwrap_or_else(|e| e.into_inner()) - .entry(TypeId::of::()) - .or_insert_with(|| Self::new_ex_index::().unwrap().as_raw()); - Index::from_raw(idx) - } - } -} - -impl SslCredentialRef { - /// Returns a reference to the extra data at the specified index. - #[corresponds(SSL_CREDENTIAL_get_ex_data)] - #[must_use] - pub fn ex_data(&self, index: Index) -> Option<&T> { - unsafe { - let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&*(data as *const T)) - } - } - } - - // Unsafe because SSL contexts are not guaranteed to be unique, we call - // this only from SslCredentialBuilder. - #[corresponds(SSL_CREDENTIAL_get_ex_data)] - unsafe fn ex_data_mut(&mut self, index: Index) -> Option<&mut T> { - let data = ffi::SSL_CREDENTIAL_get_ex_data(self.as_ptr(), index.as_raw()); - if data.is_null() { - None - } else { - Some(&mut *(data as *mut T)) - } - } - - // Unsafe because SSL contexts are not guaranteed to be unique, we call - // this only from SslCredentialBuilder. - #[corresponds(SSL_CREDENTIAL_set_ex_data)] - unsafe fn replace_ex_data(&mut self, index: Index, data: T) -> Option { - if let Some(old) = self.ex_data_mut(index) { - return Some(mem::replace(old, data)); - } - - unsafe { - let data = Box::into_raw(Box::new(data)) as *mut c_void; - ffi::SSL_CREDENTIAL_set_ex_data(self.as_ptr(), index.as_raw(), data); - } - - None - } -} - -/// A builder for [`SslCredential`] -pub struct SslCredentialBuilder(SslCredential); - -impl SslCredentialBuilder { - /// Sets or overwrites the extra data at the specified index. - /// - /// This can be used to provide data to callbacks registered with the context. Use the - /// `SslCredential::new_ex_index` method to create an `Index`. - /// - /// Any previous value will be returned and replaced by the new one. - #[corresponds(SSL_CREDENTIAL_set_ex_data)] - pub fn replace_ex_data(&mut self, index: Index, data: T) -> Option { - unsafe { self.0.replace_ex_data(index, data) } - } - - // Sets the private key of the credential. - #[corresponds(SSL_CREDENTIAL_set1_private_key)] - pub fn set_private_key(&mut self, private_key: &PKeyRef) -> Result<(), ErrorStack> { - unsafe { - cvt_0i(ffi::SSL_CREDENTIAL_set1_private_key( - self.0.as_ptr(), - private_key.as_ptr(), - )) - .map(|_| ()) - } - } - - /// Configures a custom private key method on the credential. - /// - /// See [`PrivateKeyMethod`] for more details. - #[corresponds(SSL_CREDENTIAL_set_private_key_method)] - pub fn set_private_key_method(&mut self, method: M) -> Result<(), ErrorStack> - where - M: PrivateKeyMethod, - { - unsafe { - self.replace_ex_data(SslCredential::cached_ex_index::(), method); - - cvt_0i(ffi::SSL_CREDENTIAL_set_private_key_method( - self.0.as_ptr(), - &ffi::SSL_PRIVATE_KEY_METHOD { - sign: Some(callbacks::raw_sign::), - decrypt: Some(callbacks::raw_decrypt::), - complete: Some(callbacks::raw_complete::), - }, - )) - .map(|_| ()) - } - } - - // Sets the SPKI of the raw public key credential. - // - // If `spki` is `None`, the SPKI is extracted from the credential's private key. - #[corresponds(SSL_CREDENTIAL_set1_spki)] - #[cfg(feature = "rpk")] - pub fn set_spki_bytes(&mut self, spki: Option<&[u8]>) -> Result<(), ErrorStack> { - unsafe { - let spki = spki - .map(|spki| { - cvt_p(ffi::CRYPTO_BUFFER_new( - spki.as_ptr(), - spki.len(), - ptr::null_mut(), - )) - }) - .transpose()? - .unwrap_or(ptr::null_mut()); - - let ret = cvt_0i(ffi::SSL_CREDENTIAL_set1_spki(self.0.as_ptr(), spki)).map(|_| ()); - - if !spki.is_null() { - ffi::CRYPTO_BUFFER_free(spki); - } - - ret - } - } - - #[must_use] - pub fn build(self) -> SslCredential { - self.0 - } -} - /// A certificate type. #[cfg(feature = "rpk")] #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -4778,7 +4602,3 @@ unsafe fn get_new_idx(f: ffi::CRYPTO_EX_free) -> c_int { unsafe fn get_new_ssl_idx(f: ffi::CRYPTO_EX_free) -> c_int { ffi::SSL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) } - -unsafe fn get_new_ssl_credential_idx(f: ffi::CRYPTO_EX_free) -> c_int { - ffi::SSL_CREDENTIAL_get_ex_new_index(0, ptr::null_mut(), ptr::null_mut(), None, f) -}